From 18fc5aca1bdb8090506eeaa958b3e0daced0f952 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sun, 23 Apr 2023 23:16:36 -0700 Subject: [PATCH 01/27] Started paragraph bindings. --- lib/web_ui/skwasm/BUILD.gn | 1 + lib/web_ui/skwasm/paragraph.cpp | 88 +++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 lib/web_ui/skwasm/paragraph.cpp diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 7fb55ceab5bc9..6d558f208a160 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -13,6 +13,7 @@ wasm_lib("skwasm") { "fonts.cpp", "helpers.h", "paint.cpp", + "paragraph.cpp", "path.cpp", "picture.cpp", "shaders.cpp", diff --git a/lib/web_ui/skwasm/paragraph.cpp b/lib/web_ui/skwasm/paragraph.cpp new file mode 100644 index 0000000000000..07ce7361f735b --- /dev/null +++ b/lib/web_ui/skwasm/paragraph.cpp @@ -0,0 +1,88 @@ +// 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 "export.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" + +using namespace skia::textlayout; + +SKWASM_EXPORT TextStyle *textStyle_create() { + return new TextStyle(); +} + +SKWASM_EXPORT void textStyle_setColor(TextStyle *style, SkColor color) { + style->setColor(color); +} + +SKWASM_EXPORT void textStyle_setDecoration(TextStyle *style, TextDecoration decoration) { + style->setDecoration(decoration); +} + +SKWASM_EXPORT void textStyle_setDecorationColor(TextStyle *style, SkColor color) { + style->setDecorationColor(color); +} + +SKWASM_EXPORT void textStyle_setDecorationStyle( + TextStyle *style, + TextDecorationStyle decorationStyle +) { + style->setDecorationStyle(decorationStyle); +} + +SKWASM_EXPORT void textStyle_setDecorationThickness(TextStyle *style, SkScalar thickness) { + style->setDecorationThicknessMultiplier(thickness); +} + +SKWASM_EXPORT void textStyle_setFontStyle(TextStyle *style, SkFontStyle fontStyle) { + style->setFontStyle(fontStyle); +} + +SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle *style, TextBaseline baseline) { + style->setTextBaseline(baseline); +} + +SKWASM_EXPORT void textStyle_setFontFamilies( + TextStyle *style, + SkString* fontFamilies, + int count +) { + std::vector families; + families.reserve(count); + for (int i = 0; i < count; i++) { + families.push_back(fontFamilies[i]); + } + style->setFontFamilies(std::move(families)); +} + +SKWASM_EXPORT void textStyle_setFontSize(TextStyle *style, SkScalar size) { + style->setFontSize(size); +} + +SKWASM_EXPORT void textStyle_setLetterSpacing(TextStyle *style, SkScalar letterSpacing) { + style->setLetterSpacing(letterSpacing); +} + +SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle *style, SkScalar wordSpacing) { + style->setWordSpacing(wordSpacing); +} + +SKWASM_EXPORT void textStyle_setHeight(TextStyle *style, SkScalar height) { + style->setHeight(height); +} + +SKWASM_EXPORT void textStyle_setLocale(TextStyle *style, SkString *locale) { + style->setLocale(*locale); +} + +SKWASM_EXPORT void textStyle_setBackground(TextStyle *style, SkPaint *paint) { + style->setBackgroundColor(*paint); +} + +SKWASM_EXPORT void textStyle_setForeground(TextStyle *style, SkPaint *paint) { + style->setForegroundColor(*paint); +} + +SKWASM_EXPORT ParagraphStyle *paragraphStyle_create() { + return new ParagraphStyle(); +} \ No newline at end of file From 4b890861e44775b5d5c33b4d4124e2e930f69384 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 24 Apr 2023 23:34:53 -0700 Subject: [PATCH 02/27] Implemented TextStyle (except for FontVariations). --- .../skwasm/skwasm_impl/font_collection.dart | 23 +-- .../engine/skwasm/skwasm_impl/paragraph.dart | 134 ++++++++++++++++++ .../skwasm/skwasm_impl/raw/raw_skstring.dart | 11 ++ .../skwasm_impl/raw/text/raw_text_style.dart | 110 ++++++++++++++ lib/web_ui/lib/text.dart | 3 + lib/web_ui/skwasm/BUILD.gn | 2 + lib/web_ui/skwasm/paragraph.cpp | 78 +--------- lib/web_ui/skwasm/text/text_style.cpp | 118 +++++++++++++++ 8 files changed, 385 insertions(+), 94 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart create mode 100644 lib/web_ui/skwasm/text/text_style.cpp 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 f99c01f484e65..2657da91c7c4e 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 @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:ffi'; import 'dart:js_interop'; @@ -33,16 +32,11 @@ class SkwasmFontCollection implements FlutterFontCollection { // https://github.com/dart-lang/sdk/issues/52142 final List familyHandles = []; for (final FontFamily family in manifest.families) { - final List rawUtf8Bytes = utf8.encode(family.name); - final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); - final Pointer stringDataPointer = skStringGetData(stringHandle); - for (int i = 0; i < rawUtf8Bytes.length; i++) { - stringDataPointer[i] = rawUtf8Bytes[i]; - } - familyHandles.add(stringHandle.address); + final SkStringHandle familyNameHandle = skStringFromDartString(family.name); + familyHandles.add(familyNameHandle.address); for (final FontAsset fontAsset in family.fontAssets) { fontFutures.add(() async { - final FontLoadError? error = await _downloadFontAsset(fontAsset, stringHandle); + final FontLoadError? error = await _downloadFontAsset(fontAsset, familyNameHandle); if (error == null) { loadedFonts.add(fontAsset.asset); } else { @@ -101,14 +95,9 @@ class SkwasmFontCollection implements FlutterFontCollection { } bool success; if (fontFamily != null) { - final List rawUtf8Bytes = utf8.encode(fontFamily); - final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); - final Pointer stringDataPointer = skStringGetData(stringHandle); - for (int i = 0; i < rawUtf8Bytes.length; i++) { - stringDataPointer[i] = rawUtf8Bytes[i]; - } - success = fontCollectionRegisterFont(_handle, dataHandle, stringHandle); - skStringFree(stringHandle); + final SkStringHandle familyHandle = skStringFromDartString(fontFamily); + success = fontCollectionRegisterFont(_handle, dataHandle, familyHandle); + skStringFree(familyHandle); } else { success = fontCollectionRegisterFont(_handle, dataHandle, nullptr); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index a76aa6c275985..f53eff604acf3 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -4,6 +4,10 @@ // ignore_for_file: avoid_unused_constructor_parameters +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; +import 'package:ui/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart'; import 'package:ui/ui.dart' as ui; // TODO(jacksongardner): implement everything in this file @@ -157,6 +161,136 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { } class SkwasmTextStyle implements ui.TextStyle { + factory SkwasmTextStyle({ + ui.Color? color, + ui.TextDecoration? decoration, + ui.Color? decorationColor, + ui.TextDecorationStyle? decorationStyle, + double? decorationThickness, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.TextBaseline? textBaseline, + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? letterSpacing, + double? wordSpacing, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations, + }) { + final TextStyleHandle handle = textStyleCreate(); + if (color != null) { + textStyleSetColor(handle, color.value); + } + if (decoration != null) { + textStyleSetDecoration(handle, decoration.maskValue); + } + if (decorationColor != null) { + textStyleSetDecorationColor(handle, decorationColor.value); + } + if (decorationStyle != null) { + textStyleSetDecorationStyle(handle, decorationStyle.index); + } + if (decorationThickness != null) { + textStyleSetDecorationThickness(handle, decorationThickness); + } + if (fontWeight != null || fontStyle != null) { + fontWeight ??= ui.FontWeight.normal; + fontStyle ??= ui.FontStyle.normal; + textStyleSetFontStyle(handle, fontWeight.value, fontStyle.index); + } + if (textBaseline != null) { + textStyleSetTextBaseline(handle, textBaseline.index); + } + if (fontFamily != null || + (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) { + int count = fontFamily != null ? 1 : 0; + if (fontFamilyFallback != null) { + count += fontFamilyFallback.length; + } + withStackScope((StackScope scope) { + final Pointer familiesPtr = + scope.allocPointerArray(count).cast(); + int nativeIndex = 0; + if (fontFamily != null) { + familiesPtr[nativeIndex] = skStringFromDartString(fontFamily); + nativeIndex++; + } + if (fontFamilyFallback != null) { + for (final String family in fontFamilyFallback) { + familiesPtr[nativeIndex] = skStringFromDartString(family); + nativeIndex++; + } + } + textStyleSetFontFamilies(handle, familiesPtr, count); + for (int i = 0; i < count; i++) { + skStringFree(familiesPtr[i]); + } + }); + } + if (fontSize != null) { + textStyleSetFontSize(handle, fontSize); + } + if (letterSpacing != null) { + textStyleSetLetterSpacing(handle, letterSpacing); + } + if (wordSpacing != null) { + textStyleSetWordSpacing(handle, wordSpacing); + } + if (height != null) { + textStyleSetHeight(handle, height); + } + if (leadingDistribution != null) { + textStyleSetHalfLeading( + handle, + leadingDistribution == ui.TextLeadingDistribution.even + ); + } + if (locale != null) { + final SkStringHandle localeHandle = + skStringFromDartString(locale.toLanguageTag()); + textStyleSetLocale(handle, localeHandle); + skStringFree(localeHandle); + } + if (background != null) { + background as SkwasmPaint; + textStyleSetBackground(handle, background.handle); + } + if (foreground != null) { + foreground as SkwasmPaint; + textStyleSetForeground(handle, foreground.handle); + } + if (shadows != null) { + for (final ui.Shadow shadow in shadows) { + textStyleAddShadow( + handle, + shadow.color.value, + shadow.offset.dx, + shadow.offset.dy, + shadow.blurSigma, + ); + } + } + if (fontFeatures != null) { + for (final ui.FontFeature feature in fontFeatures) { + final SkStringHandle featureName = skStringFromDartString(feature.feature); + textStyleAddFontFeature(handle, featureName, feature.value); + skStringFree(featureName); + } + } + // TODO(jacksongardner): Set font variations + return SkwasmTextStyle._(handle); + } + + SkwasmTextStyle._(this.handle); + + final TextStyleHandle handle; } class SkwasmParagraphBuilder implements ui.ParagraphBuilder { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart index 1b5af811542ad..01e9eb23f3ce2 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart @@ -5,6 +5,7 @@ @DefaultAsset('skwasm') library skwasm_impl; +import 'dart:convert'; import 'dart:ffi'; final class RawSkString extends Opaque {} @@ -18,3 +19,13 @@ external Pointer skStringGetData(SkStringHandle handle); @Native(symbol: 'skString_free', isLeaf: true) external void skStringFree(SkStringHandle handle); + +SkStringHandle skStringFromDartString(String string) { + final List rawUtf8Bytes = utf8.encode(string); + final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); + final Pointer stringDataPointer = skStringGetData(stringHandle); + for (int i = 0; i < rawUtf8Bytes.length; i++) { + stringDataPointer[i] = rawUtf8Bytes[i]; + } + return stringHandle; +} diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart new file mode 100644 index 0000000000000..e2de17a6660f6 --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart @@ -0,0 +1,110 @@ +// 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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawTextStyle extends Opaque {} + +typedef TextStyleHandle = Pointer; + +@Native(symbol: 'textStyle_create', isLeaf: true) +external TextStyleHandle textStyleCreate(); + +@Native(symbol: 'textStyle_dispose', isLeaf: true) +external void textStyleDispose(TextStyleHandle handle); + +@Native(symbol: 'textStyle_setColor', isLeaf: true) +external void textStyleSetColor(TextStyleHandle handle, int color); + +@Native(symbol: 'textStyle_setDecoration', isLeaf: true) +external void textStyleSetDecoration(TextStyleHandle handle, int decoration); + +@Native(symbol: 'textStyle_setDecorationColor', isLeaf: true) +external void textStyleSetDecorationColor(TextStyleHandle handle, int decorationColor); + +@Native(symbol: 'textStyle_setDecorationStyle', isLeaf: true) +external void textStyleSetDecorationStyle(TextStyleHandle handle, int decorationStyle); + +@Native(symbol: 'textStyle_setDecorationThickness', isLeaf: true) +external void textStyleSetDecorationThickness(TextStyleHandle handle, double decorationThickness); + +@Native(symbol: 'textStyle_setFontStyle', isLeaf: true) +external void textStyleSetFontStyle( + TextStyleHandle handle, + int weight, + int slant +); + +@Native(symbol: 'textStyle_setTextBaseline', isLeaf: true) +external void textStyleSetTextBaseline(TextStyleHandle handle, int baseline); + +@Native, + Int count +)>(symbol: 'textStyle_setFontFamilies', isLeaf: true) +external void textStyleSetFontFamilies( + TextStyleHandle handle, + Pointer families, + int count +); + +@Native(symbol: 'textStyle_setFontSize', isLeaf: true) +external void textStyleSetFontSize(TextStyleHandle handle, double size); + +@Native(symbol: 'textStyle_setLetterSpacing', isLeaf: true) +external void textStyleSetLetterSpacing(TextStyleHandle handle, double spacing); + +@Native(symbol: 'textStyle_setWordSpacing', isLeaf: true) +external void textStyleSetWordSpacing(TextStyleHandle handle, double spacing); + +@Native(symbol: 'textStyle_setHeight', isLeaf: true) +external void textStyleSetHeight(TextStyleHandle handle, double height); + +@Native(symbol: 'textStyle_setHalfLeading', isLeaf: true) +external void textStyleSetHalfLeading(TextStyleHandle handle, bool halfLeading); + +@Native(symbol: 'textStyle_setLocale', isLeaf: true) +external void textStyleSetLocale(TextStyleHandle handle, SkStringHandle locale); + +@Native(symbol: 'textStyle_setBackground', isLeaf: true) +external void textStyleSetBackground(TextStyleHandle handle, PaintHandle paint); + +@Native(symbol: 'textStyle_setForeground', isLeaf: true) +external void textStyleSetForeground(TextStyleHandle handle, PaintHandle paint); + +@Native(symbol: 'textStyle_addShadow', isLeaf: true) +external void textStyleAddShadow( + TextStyleHandle handle, + int color, + double offsetX, + double offsetY, + double blurSigma, +); + +@Native(symbol: 'textStyle_addFontFeature', isLeaf: true) +external void textStyleAddFontFeature( + TextStyleHandle handle, + SkStringHandle featureName, + int value, +); diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 3ad62f0fafc90..b6fb33607daac 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -229,6 +229,9 @@ class TextDecoration { } final int _mask; + + int get maskValue => _mask; + bool contains(TextDecoration other) { return (_mask | other._mask) == _mask; } diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 6d558f208a160..f2bb10850549e 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -19,6 +19,7 @@ wasm_lib("skwasm") { "shaders.cpp", "string.cpp", "surface.cpp", + "text/text_style.cpp", "wrappers.h", ] @@ -35,6 +36,7 @@ wasm_lib("skwasm") { "-lexports.js", "-sEXPORTED_FUNCTIONS=[stackAlloc]", "-sEXPORTED_RUNTIME_METHODS=[addFunction,removeFunction]", + "-Wno-pthreads-mem-growth", ] if (is_debug) { diff --git a/lib/web_ui/skwasm/paragraph.cpp b/lib/web_ui/skwasm/paragraph.cpp index 07ce7361f735b..7a90059c29d06 100644 --- a/lib/web_ui/skwasm/paragraph.cpp +++ b/lib/web_ui/skwasm/paragraph.cpp @@ -7,82 +7,6 @@ using namespace skia::textlayout; -SKWASM_EXPORT TextStyle *textStyle_create() { - return new TextStyle(); -} - -SKWASM_EXPORT void textStyle_setColor(TextStyle *style, SkColor color) { - style->setColor(color); -} - -SKWASM_EXPORT void textStyle_setDecoration(TextStyle *style, TextDecoration decoration) { - style->setDecoration(decoration); -} - -SKWASM_EXPORT void textStyle_setDecorationColor(TextStyle *style, SkColor color) { - style->setDecorationColor(color); -} - -SKWASM_EXPORT void textStyle_setDecorationStyle( - TextStyle *style, - TextDecorationStyle decorationStyle -) { - style->setDecorationStyle(decorationStyle); -} - -SKWASM_EXPORT void textStyle_setDecorationThickness(TextStyle *style, SkScalar thickness) { - style->setDecorationThicknessMultiplier(thickness); -} - -SKWASM_EXPORT void textStyle_setFontStyle(TextStyle *style, SkFontStyle fontStyle) { - style->setFontStyle(fontStyle); -} - -SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle *style, TextBaseline baseline) { - style->setTextBaseline(baseline); -} - -SKWASM_EXPORT void textStyle_setFontFamilies( - TextStyle *style, - SkString* fontFamilies, - int count -) { - std::vector families; - families.reserve(count); - for (int i = 0; i < count; i++) { - families.push_back(fontFamilies[i]); - } - style->setFontFamilies(std::move(families)); -} - -SKWASM_EXPORT void textStyle_setFontSize(TextStyle *style, SkScalar size) { - style->setFontSize(size); -} - -SKWASM_EXPORT void textStyle_setLetterSpacing(TextStyle *style, SkScalar letterSpacing) { - style->setLetterSpacing(letterSpacing); -} - -SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle *style, SkScalar wordSpacing) { - style->setWordSpacing(wordSpacing); -} - -SKWASM_EXPORT void textStyle_setHeight(TextStyle *style, SkScalar height) { - style->setHeight(height); -} - -SKWASM_EXPORT void textStyle_setLocale(TextStyle *style, SkString *locale) { - style->setLocale(*locale); -} - -SKWASM_EXPORT void textStyle_setBackground(TextStyle *style, SkPaint *paint) { - style->setBackgroundColor(*paint); -} - -SKWASM_EXPORT void textStyle_setForeground(TextStyle *style, SkPaint *paint) { - style->setForegroundColor(*paint); -} - SKWASM_EXPORT ParagraphStyle *paragraphStyle_create() { return new ParagraphStyle(); -} \ No newline at end of file +} diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp new file mode 100644 index 0000000000000..6ac858b69e5fe --- /dev/null +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -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 "../export.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" + +using namespace skia::textlayout; + +SKWASM_EXPORT TextStyle *textStyle_create() { + return new TextStyle(); +} + +SKWASM_EXPORT void textStyle_dispose(TextStyle *style) { + delete style; +} + +SKWASM_EXPORT void textStyle_setColor(TextStyle *style, SkColor color) { + style->setColor(color); +} + +SKWASM_EXPORT void textStyle_setDecoration(TextStyle *style, TextDecoration decoration) { + style->setDecoration(decoration); +} + +SKWASM_EXPORT void textStyle_setDecorationColor(TextStyle *style, SkColor color) { + style->setDecorationColor(color); +} + +SKWASM_EXPORT void textStyle_setDecorationStyle( + TextStyle *style, + TextDecorationStyle decorationStyle +) { + style->setDecorationStyle(decorationStyle); +} + +SKWASM_EXPORT void textStyle_setDecorationThickness(TextStyle *style, SkScalar thickness) { + style->setDecorationThicknessMultiplier(thickness); +} + +SKWASM_EXPORT void textStyle_setFontStyle( + TextStyle *style, + int weight, + SkFontStyle::Slant slant +) { + style->setFontStyle(SkFontStyle(weight, SkFontStyle::kNormal_Width, slant)); +} + +SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle *style, TextBaseline baseline) { + style->setTextBaseline(baseline); +} + +SKWASM_EXPORT void textStyle_setFontFamilies( + TextStyle *style, + SkString** fontFamilies, + int count +) { + std::vector families; + families.reserve(count); + for (int i = 0; i < count; i++) { + families.push_back(*fontFamilies[i]); + } + style->setFontFamilies(std::move(families)); +} + +SKWASM_EXPORT void textStyle_setFontSize(TextStyle *style, SkScalar size) { + style->setFontSize(size); +} + +SKWASM_EXPORT void textStyle_setLetterSpacing(TextStyle *style, SkScalar letterSpacing) { + style->setLetterSpacing(letterSpacing); +} + +SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle *style, SkScalar wordSpacing) { + style->setWordSpacing(wordSpacing); +} + +SKWASM_EXPORT void textStyle_setHeight(TextStyle *style, SkScalar height) { + style->setHeight(height); +} + +SKWASM_EXPORT void textStyle_setHalfLeading(TextStyle *style, bool halfLeading) { + style->setHalfLeading(halfLeading); +} + +SKWASM_EXPORT void textStyle_setLocale(TextStyle *style, SkString *locale) { + style->setLocale(*locale); +} + +SKWASM_EXPORT void textStyle_setBackground(TextStyle *style, SkPaint *paint) { + style->setBackgroundColor(*paint); +} + +SKWASM_EXPORT void textStyle_setForeground(TextStyle *style, SkPaint *paint) { + style->setForegroundColor(*paint); +} + +SKWASM_EXPORT void textStyle_addShadow( + TextStyle *style, + SkColor color, + SkScalar offsetX, + SkScalar offsetY, + SkScalar blurSigma +) { + style->addShadow( + TextShadow(color, {offsetX, offsetY}, blurSigma) + ); +} + +SKWASM_EXPORT void textStyle_addFontFeature( + TextStyle *style, + SkString *featureName, + int value +) { + style->addFontFeature(*featureName, value); +} + +// TODO(jacksongardner): implement font variations From 2e0b325d46faa918a5f8fdf0e5262c479ca21a01 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 25 Apr 2023 11:55:47 -0700 Subject: [PATCH 03/27] Implemented strut style. --- .../lib/src/engine/skwasm/skwasm_impl.dart | 2 + .../engine/skwasm/skwasm_impl/paragraph.dart | 110 +++++++++++---- .../skwasm_impl/raw/text/raw_strut_style.dart | 57 ++++++++ .../engine/skwasm/skwasm_impl/renderer.dart | 38 ++++- lib/web_ui/skwasm/BUILD.gn | 3 +- .../paragraph_style.cpp} | 6 +- lib/web_ui/skwasm/text/strut_style.cpp | 56 ++++++++ lib/web_ui/skwasm/text/text_style.cpp | 130 +++++++++--------- 8 files changed, 300 insertions(+), 102 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart rename lib/web_ui/skwasm/{paragraph.cpp => text/paragraph_style.cpp} (71%) create mode 100644 lib/web_ui/skwasm/text/strut_style.cpp diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index 13d223e640888..181460d0ec088 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -31,6 +31,8 @@ export 'skwasm_impl/raw/raw_skdata.dart'; export 'skwasm_impl/raw/raw_skstring.dart'; export 'skwasm_impl/raw/raw_surface.dart'; export 'skwasm_impl/raw/skwasm_module.dart'; +export 'skwasm_impl/raw/text/raw_strut_style.dart'; +export 'skwasm_impl/raw/text/raw_text_style.dart'; export 'skwasm_impl/renderer.dart'; export 'skwasm_impl/scene_builder.dart'; export 'skwasm_impl/shaders.dart'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index f53eff604acf3..77f9baf612871 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -7,7 +7,6 @@ import 'dart:ffi'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; -import 'package:ui/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart'; import 'package:ui/ui.dart' as ui; // TODO(jacksongardner): implement everything in this file @@ -157,7 +156,22 @@ class SkwasmParagraph implements ui.Paragraph { } } -class SkwasmParagraphStyle implements ui.ParagraphStyle { +void withScopedFontList( + List fontFamilies, + void Function(Pointer, int) callback) { + withStackScope((StackScope scope) { + final Pointer familiesPtr = + scope.allocPointerArray(fontFamilies.length).cast(); + int nativeIndex = 0; + for (int i = 0; i < fontFamilies.length; i++) { + familiesPtr[nativeIndex] = skStringFromDartString(fontFamilies[i]); + nativeIndex++; + } + callback(familiesPtr, fontFamilies.length); + for (int i = 0; i < fontFamilies.length; i++) { + skStringFree(familiesPtr[i]); + } + }); } class SkwasmTextStyle implements ui.TextStyle { @@ -208,31 +222,16 @@ class SkwasmTextStyle implements ui.TextStyle { if (textBaseline != null) { textStyleSetTextBaseline(handle, textBaseline.index); } - if (fontFamily != null || - (fontFamilyFallback != null && fontFamilyFallback.isNotEmpty)) { - int count = fontFamily != null ? 1 : 0; - if (fontFamilyFallback != null) { - count += fontFamilyFallback.length; + if (fontFamily != null || fontFamilyFallback != null) { + final List fontFamilies = [ + if (fontFamily != null) fontFamily, + if (fontFamilyFallback != null) ...fontFamilyFallback, + ]; + if (fontFamilies.isNotEmpty) { + withScopedFontList(fontFamilies, + (Pointer families, int count) => + textStyleSetFontFamilies(handle, families, count)); } - withStackScope((StackScope scope) { - final Pointer familiesPtr = - scope.allocPointerArray(count).cast(); - int nativeIndex = 0; - if (fontFamily != null) { - familiesPtr[nativeIndex] = skStringFromDartString(fontFamily); - nativeIndex++; - } - if (fontFamilyFallback != null) { - for (final String family in fontFamilyFallback) { - familiesPtr[nativeIndex] = skStringFromDartString(family); - nativeIndex++; - } - } - textStyleSetFontFamilies(handle, familiesPtr, count); - for (int i = 0; i < count; i++) { - skStringFree(familiesPtr[i]); - } - }); } if (fontSize != null) { textStyleSetFontSize(handle, fontSize); @@ -253,7 +252,7 @@ class SkwasmTextStyle implements ui.TextStyle { ); } if (locale != null) { - final SkStringHandle localeHandle = + final SkStringHandle localeHandle = skStringFromDartString(locale.toLanguageTag()); textStyleSetLocale(handle, localeHandle); skStringFree(localeHandle); @@ -293,6 +292,63 @@ class SkwasmTextStyle implements ui.TextStyle { final TextStyleHandle handle; } +class SkwasmStrutStyle implements ui.StrutStyle { + factory SkwasmStrutStyle({ + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight, + }) { + final StrutStyleHandle handle = strutStyleCreate(); + if (fontFamily != null || fontFamilyFallback != null) { + final List fontFamilies = [ + if (fontFamily != null) fontFamily, + if (fontFamilyFallback != null) ...fontFamilyFallback, + ]; + if (fontFamilies.isNotEmpty) { + withScopedFontList(fontFamilies, + (Pointer families, int count) => + strutStyleSetFontFamilies(handle, families, count)); + } + } + if (fontSize != null) { + strutStyleSetFontSize(handle, fontSize); + } + if (height != null) { + strutStyleSetHeight(handle, height); + } + if (leadingDistribution != null) { + strutStyleSetHalfLeading( + handle, + leadingDistribution == ui.TextLeadingDistribution.even); + } + if (leading != null) { + strutStyleSetLeading(handle, leading); + } + if (fontWeight != null || fontStyle != null) { + fontWeight ??= ui.FontWeight.normal; + fontStyle ??= ui.FontStyle.normal; + strutStyleSetFontStyle(handle, fontWeight.value, fontStyle.index); + } + if (forceStrutHeight != null) { + strutStyleSetForceStrutHeight(handle, forceStrutHeight); + } + return SkwasmStrutStyle._(handle); + } + + SkwasmStrutStyle._(this.handle); + + final StrutStyleHandle handle; +} + +class SkwasmParagraphStyle implements ui.ParagraphStyle { +} + class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override void addPlaceholder( diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart new file mode 100644 index 0000000000000..d9f925b125444 --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart @@ -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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawStrutStyle extends Opaque {} + +typedef StrutStyleHandle = Pointer; + +@Native(symbol: 'strutStyle_create', isLeaf: true) +external StrutStyleHandle strutStyleCreate(); + +@Native(symbol: 'strutStyle_dispose', isLeaf: true) +external void strutStyleDispose(StrutStyleHandle handle); + +@Native families, + Int count +)>(symbol: 'strutStyle_setFontFamilies', isLeaf: true) +external void strutStyleSetFontFamilies( + StrutStyleHandle handle, + Pointer families, + int count +); + +@Native(symbol: 'strutStyle_setFontSize', isLeaf: true) +external void strutStyleSetFontSize(StrutStyleHandle handle, double fontSize); + +@Native(symbol: 'strutStyle_setHeight', isLeaf: true) +external void strutStyleSetHeight(StrutStyleHandle handle, double height); + +@Native(symbol: 'strutStyle_setHalfLeading', isLeaf: true) +external void strutStyleSetHalfLeading(StrutStyleHandle handle, bool height); + +@Native(symbol: 'strutStyle_setLeading', isLeaf: true) +external void strutStyleSetLeading(StrutStyleHandle handle, double leading); + +@Native(symbol: 'strutStyle_setFontStyle', isLeaf: true) +external void strutStyleSetFontStyle( + StrutStyleHandle handle, + int weight, + int slant, +); + +@Native(symbol: 'strutStyle_setForceStrutHeight', isLeaf: true) +external void strutStyleSetForceStrutHeight(StrutStyleHandle handle, bool forceStrutHeight); 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 73b8e4e21fc83..b26dbe8730a09 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 @@ -159,9 +159,17 @@ class SkwasmRenderer implements Renderer { ui.FontWeight? fontWeight, ui.FontStyle? fontStyle, bool? forceStrutHeight - }) { - throw UnimplementedError('createStrutStyle not yet implemented'); - } + }) => SkwasmStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); @override ui.Gradient createSweepGradient( @@ -205,7 +213,29 @@ class SkwasmRenderer implements Renderer { List? shadows, List? fontFeatures, List? fontVariations - }) => SkwasmTextStyle(); + }) => SkwasmTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background, + foreground: foreground, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, + ); @override ui.Vertices createVertices( diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index f2bb10850549e..057eae416f246 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -13,12 +13,13 @@ wasm_lib("skwasm") { "fonts.cpp", "helpers.h", "paint.cpp", - "paragraph.cpp", "path.cpp", "picture.cpp", "shaders.cpp", "string.cpp", "surface.cpp", + "text/paragraph_style.cpp", + "text/strut_style.cpp", "text/text_style.cpp", "wrappers.h", ] diff --git a/lib/web_ui/skwasm/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp similarity index 71% rename from lib/web_ui/skwasm/paragraph.cpp rename to lib/web_ui/skwasm/text/paragraph_style.cpp index 7a90059c29d06..ed8673f621c5f 100644 --- a/lib/web_ui/skwasm/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "export.h" +#include "../export.h" #include "third_party/skia/modules/skparagraph/include/Paragraph.h" using namespace skia::textlayout; -SKWASM_EXPORT ParagraphStyle *paragraphStyle_create() { - return new ParagraphStyle(); +SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { + return new ParagraphStyle(); } diff --git a/lib/web_ui/skwasm/text/strut_style.cpp b/lib/web_ui/skwasm/text/strut_style.cpp new file mode 100644 index 0000000000000..41f9edf52e7fd --- /dev/null +++ b/lib/web_ui/skwasm/text/strut_style.cpp @@ -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. + +#include "../export.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" + +using namespace skia::textlayout; + +SKWASM_EXPORT StrutStyle* strutStyle_create() { + return new StrutStyle(); +} + +SKWASM_EXPORT void strutStyle_dispose(StrutStyle* style) { + delete style; +} + +SKWASM_EXPORT void strutStyle_setFontFamilies(StrutStyle* style, + SkString** fontFamilies, + int count) { + std::vector families; + families.reserve(count); + for (int i = 0; i < count; i++) { + families.push_back(*fontFamilies[i]); + } + style->setFontFamilies(std::move(families)); +} + +SKWASM_EXPORT void strutStyle_setFontSize(StrutStyle* style, + SkScalar fontSize) { + style->setFontSize(fontSize); +} + +SKWASM_EXPORT void strutStyle_setHeight(StrutStyle* style, SkScalar height) { + style->setHeight(height); +} + +SKWASM_EXPORT void strutStyle_setHalfLeading(StrutStyle* style, + bool halfLeading) { + style->setHalfLeading(halfLeading); +} + +SKWASM_EXPORT void strutStyle_setLeading(StrutStyle* style, SkScalar leading) { + style->setLeading(leading); +} + +SKWASM_EXPORT void strutStyle_setFontStyle(StrutStyle* style, + int weight, + SkFontStyle::Slant slant) { + style->setFontStyle(SkFontStyle(weight, SkFontStyle::kNormal_Width, slant)); +} + +SKWASM_EXPORT void strutStyle_setForceStrutHeight(StrutStyle* style, + bool forceStrutHeight) { + style->setForceStrutHeight(forceStrutHeight); +} diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index 6ac858b69e5fe..fdc9b4c180fda 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -7,112 +7,108 @@ using namespace skia::textlayout; -SKWASM_EXPORT TextStyle *textStyle_create() { - return new TextStyle(); +SKWASM_EXPORT TextStyle* textStyle_create() { + return new TextStyle(); } -SKWASM_EXPORT void textStyle_dispose(TextStyle *style) { - delete style; +SKWASM_EXPORT void textStyle_dispose(TextStyle* style) { + delete style; } -SKWASM_EXPORT void textStyle_setColor(TextStyle *style, SkColor color) { - style->setColor(color); +SKWASM_EXPORT void textStyle_setColor(TextStyle* style, SkColor color) { + style->setColor(color); } -SKWASM_EXPORT void textStyle_setDecoration(TextStyle *style, TextDecoration decoration) { - style->setDecoration(decoration); +SKWASM_EXPORT void textStyle_setDecoration(TextStyle* style, + TextDecoration decoration) { + style->setDecoration(decoration); } -SKWASM_EXPORT void textStyle_setDecorationColor(TextStyle *style, SkColor color) { - style->setDecorationColor(color); +SKWASM_EXPORT void textStyle_setDecorationColor(TextStyle* style, + SkColor color) { + style->setDecorationColor(color); } SKWASM_EXPORT void textStyle_setDecorationStyle( - TextStyle *style, - TextDecorationStyle decorationStyle -) { - style->setDecorationStyle(decorationStyle); + TextStyle* style, + TextDecorationStyle decorationStyle) { + style->setDecorationStyle(decorationStyle); } -SKWASM_EXPORT void textStyle_setDecorationThickness(TextStyle *style, SkScalar thickness) { - style->setDecorationThicknessMultiplier(thickness); +SKWASM_EXPORT void textStyle_setDecorationThickness(TextStyle* style, + SkScalar thickness) { + style->setDecorationThicknessMultiplier(thickness); } -SKWASM_EXPORT void textStyle_setFontStyle( - TextStyle *style, - int weight, - SkFontStyle::Slant slant -) { - style->setFontStyle(SkFontStyle(weight, SkFontStyle::kNormal_Width, slant)); +SKWASM_EXPORT void textStyle_setFontStyle(TextStyle* style, + int weight, + SkFontStyle::Slant slant) { + style->setFontStyle(SkFontStyle(weight, SkFontStyle::kNormal_Width, slant)); } -SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle *style, TextBaseline baseline) { - style->setTextBaseline(baseline); +SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle* style, + TextBaseline baseline) { + style->setTextBaseline(baseline); } -SKWASM_EXPORT void textStyle_setFontFamilies( - TextStyle *style, - SkString** fontFamilies, - int count -) { - std::vector families; - families.reserve(count); - for (int i = 0; i < count; i++) { - families.push_back(*fontFamilies[i]); - } - style->setFontFamilies(std::move(families)); +SKWASM_EXPORT void textStyle_setFontFamilies(TextStyle* style, + SkString** fontFamilies, + int count) { + std::vector families; + families.reserve(count); + for (int i = 0; i < count; i++) { + families.push_back(*fontFamilies[i]); + } + style->setFontFamilies(std::move(families)); } -SKWASM_EXPORT void textStyle_setFontSize(TextStyle *style, SkScalar size) { - style->setFontSize(size); +SKWASM_EXPORT void textStyle_setFontSize(TextStyle* style, SkScalar size) { + style->setFontSize(size); } -SKWASM_EXPORT void textStyle_setLetterSpacing(TextStyle *style, SkScalar letterSpacing) { - style->setLetterSpacing(letterSpacing); +SKWASM_EXPORT void textStyle_setLetterSpacing(TextStyle* style, + SkScalar letterSpacing) { + style->setLetterSpacing(letterSpacing); } -SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle *style, SkScalar wordSpacing) { - style->setWordSpacing(wordSpacing); +SKWASM_EXPORT void textStyle_setWordSpacing(TextStyle* style, + SkScalar wordSpacing) { + style->setWordSpacing(wordSpacing); } -SKWASM_EXPORT void textStyle_setHeight(TextStyle *style, SkScalar height) { - style->setHeight(height); +SKWASM_EXPORT void textStyle_setHeight(TextStyle* style, SkScalar height) { + style->setHeight(height); } -SKWASM_EXPORT void textStyle_setHalfLeading(TextStyle *style, bool halfLeading) { - style->setHalfLeading(halfLeading); +SKWASM_EXPORT void textStyle_setHalfLeading(TextStyle* style, + bool halfLeading) { + style->setHalfLeading(halfLeading); } -SKWASM_EXPORT void textStyle_setLocale(TextStyle *style, SkString *locale) { - style->setLocale(*locale); +SKWASM_EXPORT void textStyle_setLocale(TextStyle* style, SkString* locale) { + style->setLocale(*locale); } -SKWASM_EXPORT void textStyle_setBackground(TextStyle *style, SkPaint *paint) { - style->setBackgroundColor(*paint); +SKWASM_EXPORT void textStyle_setBackground(TextStyle* style, SkPaint* paint) { + style->setBackgroundColor(*paint); } -SKWASM_EXPORT void textStyle_setForeground(TextStyle *style, SkPaint *paint) { - style->setForegroundColor(*paint); +SKWASM_EXPORT void textStyle_setForeground(TextStyle* style, SkPaint* paint) { + style->setForegroundColor(*paint); } -SKWASM_EXPORT void textStyle_addShadow( - TextStyle *style, - SkColor color, - SkScalar offsetX, - SkScalar offsetY, - SkScalar blurSigma -) { - style->addShadow( - TextShadow(color, {offsetX, offsetY}, blurSigma) - ); +SKWASM_EXPORT void textStyle_addShadow(TextStyle* style, + SkColor color, + SkScalar offsetX, + SkScalar offsetY, + SkScalar blurSigma) { + style->addShadow(TextShadow(color, {offsetX, offsetY}, blurSigma)); } -SKWASM_EXPORT void textStyle_addFontFeature( - TextStyle *style, - SkString *featureName, - int value -) { - style->addFontFeature(*featureName, value); +SKWASM_EXPORT void textStyle_addFontFeature(TextStyle* style, + SkString* featureName, + int value) { + style->addFontFeature(*featureName, value); } // TODO(jacksongardner): implement font variations From 3c09cfb09ad994929ae01fd083d788d30b0e17f8 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 25 Apr 2023 14:14:03 -0700 Subject: [PATCH 04/27] Implement Paragraph Style. --- .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../engine/skwasm/skwasm_impl/paragraph.dart | 84 +++++++++++++++++++ .../raw/text/raw_paragraph_style.dart | 51 +++++++++++ .../engine/skwasm/skwasm_impl/renderer.dart | 15 +++- lib/web_ui/skwasm/text/paragraph_style.cpp | 56 ++++++++++++- 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index 181460d0ec088..282e34b6e3459 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -31,6 +31,7 @@ export 'skwasm_impl/raw/raw_skdata.dart'; export 'skwasm_impl/raw/raw_skstring.dart'; export 'skwasm_impl/raw/raw_surface.dart'; export 'skwasm_impl/raw/skwasm_module.dart'; +export 'skwasm_impl/raw/text/raw_paragraph_style.dart'; export 'skwasm_impl/raw/text/raw_strut_style.dart'; export 'skwasm_impl/raw/text/raw_text_style.dart'; export 'skwasm_impl/renderer.dart'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 77f9baf612871..08e0bbffc0543 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -347,6 +347,90 @@ class SkwasmStrutStyle implements ui.StrutStyle { } class SkwasmParagraphStyle implements ui.ParagraphStyle { + factory SkwasmParagraphStyle({ + ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale, + }) { + final ParagraphStyleHandle handle = paragraphStyleCreate(); + if (textAlign != null) { + paragraphStyleSetTextAlign(handle, textAlign.index); + } + if (textDirection != null) { + paragraphStyleSetTextDirection(handle, textDirection.index); + } + if (maxLines != null) { + paragraphStyleSetMaxLines(handle, maxLines); + } + if (height != null) { + paragraphStyleSetHeight(handle, height); + } + if (textHeightBehavior != null) { + paragraphStyleSetTextHeightBehavior( + handle, + textHeightBehavior.applyHeightToFirstAscent, + textHeightBehavior.applyHeightToLastDescent, + ); + } + if (ellipsis != null) { + final SkStringHandle ellipsisHandle = skStringFromDartString(ellipsis); + paragraphStyleSetEllipsis(handle, ellipsisHandle); + skStringFree(ellipsisHandle); + } + if (strutStyle != null) { + strutStyle as SkwasmStrutStyle; + paragraphStyleSetStrutStyle(handle, strutStyle.handle); + } + if (fontFamily != null || + fontSize != null || + fontWeight != null || + fontStyle != null || + textHeightBehavior != null || + locale != null) { + final TextStyleHandle textStyleHandle = textStyleCreate(); + if (fontFamily != null) { + withScopedFontList([fontFamily], + (Pointer families, int count) => + textStyleSetFontFamilies(textStyleHandle, families, count)); + } + if (fontSize != null) { + textStyleSetFontSize(textStyleHandle, fontSize); + } + if (fontWeight != null || fontStyle != null) { + fontWeight ??= ui.FontWeight.normal; + fontStyle ??= ui.FontStyle.normal; + textStyleSetFontStyle(textStyleHandle, fontWeight.value, fontStyle.index); + } + if (textHeightBehavior != null) { + textStyleSetHalfLeading( + textStyleHandle, + textHeightBehavior.leadingDistribution == ui.TextLeadingDistribution.even, + ); + } + if (locale != null) { + final SkStringHandle localeHandle = + skStringFromDartString(locale.toLanguageTag()); + textStyleSetLocale(textStyleHandle, localeHandle); + skStringFree(localeHandle); + } + paragraphStyleSetTextStyle(handle, textStyleHandle); + textStyleDispose(textStyleHandle); + } + return SkwasmParagraphStyle._(handle); + } + + SkwasmParagraphStyle._(this.handle); + + final ParagraphStyleHandle handle; } class SkwasmParagraphBuilder implements ui.ParagraphBuilder { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart new file mode 100644 index 0000000000000..e65a5f7690066 --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart @@ -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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawParagraphStyle extends Opaque {} +typedef ParagraphStyleHandle = Pointer; + +@Native(symbol: 'paragraphStyle_create', isLeaf: true) +external ParagraphStyleHandle paragraphStyleCreate(); + +@Native(symbol: 'paragraphStyle_dispose', isLeaf: true) +external void paragraphStyleDispose(ParagraphStyleHandle handle); + +@Native(symbol: 'paragraphStyle_setTextAlign', isLeaf: true) +external void paragraphStyleSetTextAlign(ParagraphStyleHandle handle, int textAlign); + +@Native(symbol: 'paragraphStyle_setTextDirection', isLeaf: true) +external void paragraphStyleSetTextDirection(ParagraphStyleHandle handle, int textDirection); + +@Native(symbol: 'paragraphStyle_setMaxLines', isLeaf: true) +external void paragraphStyleSetMaxLines(ParagraphStyleHandle handle, int maxLines); + +@Native(symbol: 'paragraphStyle_setHeight', isLeaf: true) +external void paragraphStyleSetHeight(ParagraphStyleHandle handle, double height); + +@Native(symbol: 'paragraphStyle_setTextHeightBehavior', isLeaf: true) +external void paragraphStyleSetTextHeightBehavior( + ParagraphStyleHandle handle, + bool applyHeightToFirstAscent, + bool applyHeightToLastDescent, +); + +@Native(symbol: 'paragraphStyle_setEllipsis', isLeaf: true) +external void paragraphStyleSetEllipsis(ParagraphStyleHandle handle, SkStringHandle ellipsis); + +@Native(symbol: 'paragraphStyle_setStrutStyle', isLeaf: true) +external void paragraphStyleSetStrutStyle(ParagraphStyleHandle handle, StrutStyleHandle strutStyle); + +@Native(symbol: 'paragraphStyle_setTextStyle', isLeaf: true) +external void paragraphStyleSetTextStyle(ParagraphStyleHandle handle, TextStyleHandle textStyle); 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 b26dbe8730a09..8a12a946b77b2 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 @@ -120,7 +120,20 @@ class SkwasmRenderer implements Renderer { ui.StrutStyle? strutStyle, String? ellipsis, ui.Locale? locale - }) => SkwasmParagraphStyle(); + }) => SkwasmParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + ); @override ui.Path createPath() => SkwasmPath(); diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index ed8673f621c5f..d471195af137c 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -8,5 +8,59 @@ using namespace skia::textlayout; SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { - return new ParagraphStyle(); + auto style = new ParagraphStyle(); + + // This is the default behavior in Flutter + style->setReplaceTabCharacters(true); + return style; +} + +SKWASM_EXPORT void paragraphStyle_dispose(ParagraphStyle *style) { + delete style; +} + +SKWASM_EXPORT void paragraphStyle_setTextAlign(ParagraphStyle *style, TextAlign align) { + style->setTextAlign(align); +} + +SKWASM_EXPORT void paragraphStyle_setTextDirection(ParagraphStyle *style, TextDirection direction) { + style->setTextDirection(direction); +} + +SKWASM_EXPORT void paragraphStyle_setMaxLines(ParagraphStyle *style, size_t maxLines) { + style->setMaxLines(maxLines); +} + +SKWASM_EXPORT void paragraphStyle_setHeight(ParagraphStyle *style, SkScalar height) { + style->setHeight(height); +} + +SKWASM_EXPORT void paragraphStyle_setTextHeightBehavior( + ParagraphStyle *style, + bool applyHeightToFirstAscent, + bool applyHeightToLastDescent +) { + TextHeightBehavior behavior; + if (!applyHeightToFirstAscent && !applyHeightToLastDescent) { + behavior = kDisableAll; + } else if (!applyHeightToLastDescent) { + behavior = kDisableLastDescent; + } else if (!applyHeightToFirstAscent) { + behavior = kDisableFirstAscent; + } else { + behavior = kAll; + } + style->setTextHeightBehavior(behavior); +} + +SKWASM_EXPORT void paragraphStyle_setEllipsis(ParagraphStyle *style, SkString *ellipsis) { + style->setEllipsis(*ellipsis); +} + +SKWASM_EXPORT void paragraphStyle_setStrutStyle(ParagraphStyle *style, StrutStyle* strutStyle) { + style->setStrutStyle(*strutStyle); +} + +SKWASM_EXPORT void paragraphStyle_setTextStyle(ParagraphStyle *style, TextStyle *textStyle) { + style->setTextStyle(*textStyle); } From d50588c38e70e832ffc0af502688461d421717c4 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 25 Apr 2023 22:08:11 -0700 Subject: [PATCH 05/27] Implemented paragraph. --- .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../engine/skwasm/skwasm_impl/paragraph.dart | 141 +++++++++++------- .../skwasm/skwasm_impl/raw/raw_memory.dart | 10 ++ .../skwasm_impl/raw/text/raw_paragraph.dart | 121 +++++++++++++++ lib/web_ui/lib/text.dart | 11 -- lib/web_ui/skwasm/BUILD.gn | 1 + lib/web_ui/skwasm/text/paragraph.cpp | 126 ++++++++++++++++ 7 files changed, 346 insertions(+), 65 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart create mode 100644 lib/web_ui/skwasm/text/paragraph.cpp diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index 282e34b6e3459..d6d2407470e11 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -31,6 +31,7 @@ export 'skwasm_impl/raw/raw_skdata.dart'; export 'skwasm_impl/raw/raw_skstring.dart'; export 'skwasm_impl/raw/raw_surface.dart'; export 'skwasm_impl/raw/skwasm_module.dart'; +export 'skwasm_impl/raw/text/raw_paragraph.dart'; export 'skwasm_impl/raw/text/raw_paragraph_style.dart'; export 'skwasm_impl/raw/text/raw_strut_style.dart'; export 'skwasm_impl/raw/text/raw_text_style.dart'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 08e0bbffc0543..0208acf9f0c63 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -11,19 +11,9 @@ import 'package:ui/ui.dart' as ui; // TODO(jacksongardner): implement everything in this file class SkwasmLineMetrics implements ui.LineMetrics { - factory SkwasmLineMetrics({ - required bool hardBreak, - required double ascent, - required double descent, - required double unscaledAscent, - required double height, - required double width, - required double left, - required double baseline, - required int lineNumber, - }) { - throw UnimplementedError(); - } + SkwasmLineMetrics._(this.handle); + + final LineMetricsHandle handle; @override bool get hardBreak { @@ -72,87 +62,129 @@ class SkwasmLineMetrics implements ui.LineMetrics { } class SkwasmParagraph implements ui.Paragraph { - @override - double get width { - return 0.0; - } + SkwasmParagraph(this.handle); + + ParagraphHandle handle; + bool _isDisposed = true; @override - double get height { - return 0.0; - } + double get width => paragraphGetWidth(handle); @override - double get longestLine { - return 0.0; - } + double get height => paragraphGetHeight(handle); @override - double get minIntrinsicWidth { - return 0.0; - } + double get longestLine => paragraphGetLongestLine(handle); @override - double get maxIntrinsicWidth { - return 0.0; - } + double get minIntrinsicWidth => paragraphGetMinIntrinsicWidth(handle); @override - double get alphabeticBaseline { - return 0.0; - } + double get maxIntrinsicWidth => paragraphGetMaxIntrinsicWidth(handle); @override - double get ideographicBaseline { - return 0.0; - } + double get alphabeticBaseline => paragraphGetAlphabeticBaseline(handle); @override - bool get didExceedMaxLines { - return false; - } + double get ideographicBaseline => paragraphGetIdeographicBaseline(handle); @override - void layout(ui.ParagraphConstraints constraints) { - } + bool get didExceedMaxLines => paragraphGetDidExceedMaxLines(handle); @override - List getBoxesForRange(int start, int end, - {ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight, - ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight}) { - return []; + void layout(ui.ParagraphConstraints constraints) => + paragraphLayout(handle, constraints.width); + + List _convertTextBoxList(TextBoxListHandle listHandle) { + final int length = textBoxListGetLength(listHandle); + return withStackScope((StackScope scope) { + final RawRect tempRect = scope.allocFloatArray(4); + return List.generate(length, (int index) { + final int textDirectionIndex = + textBoxListGetBoxAtIndex(listHandle, index, tempRect); + return ui.TextBox.fromLTRBD( + tempRect[0], + tempRect[1], + tempRect[2], + tempRect[3], + ui.TextDirection.values[textDirectionIndex], + ); + }); + }); } @override - ui.TextPosition getPositionForOffset(ui.Offset offset) { - return const ui.TextPosition(offset: 0); - } + List getBoxesForRange( + int start, + int end, { + ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight, + ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight + }) { + final TextBoxListHandle listHandle = paragraphGetBoxesForRange( + handle, + start, + end, + boxHeightStyle.index, + boxWidthStyle.index + ); + final List boxes = _convertTextBoxList(listHandle); + textBoxListDispose(listHandle); + return boxes; + } + + @override + ui.TextPosition getPositionForOffset(ui.Offset offset) => withStackScope((StackScope scope) { + final Pointer outAffinity = scope.allocIntArray(1); + final int position = paragraphGetPositionForOffset( + handle, + offset.dx, + offset.dy, + outAffinity + ); + return ui.TextPosition( + offset: position, + affinity: ui.TextAffinity.values[outAffinity[0]], + ); + }); @override - ui.TextRange getWordBoundary(ui.TextPosition position) { - return const ui.TextRange(start: 0, end: 0); - } + ui.TextRange getWordBoundary(ui.TextPosition position) => withStackScope((StackScope scope) { + final Pointer outRange = scope.allocSizeArray(2); + paragraphGetWordBoundary(handle, position.offset, outRange); + return ui.TextRange(start: outRange[0], end: outRange[1]); + }); @override ui.TextRange getLineBoundary(ui.TextPosition position) { + // TODO(jacksongardner): Implement this one line metrics are usable. return const ui.TextRange(start: 0, end: 0); } @override List getBoxesForPlaceholders() { - return []; + final TextBoxListHandle listHandle = paragraphGetBoxesForPlaceholders(handle); + final List boxes = _convertTextBoxList(listHandle); + textBoxListDispose(listHandle); + return boxes; } @override List computeLineMetrics() { - return []; + final int lineCount = paragraphGetLineCount(handle); + return List.generate(lineCount, + (int index) => SkwasmLineMetrics._(paragraphGetLineMetricsAtIndex(handle, index)) + ); } @override - bool get debugDisposed => false; + bool get debugDisposed => _isDisposed; @override void dispose() { + if (!_isDisposed) { + paragraphDispose(handle); + _isDisposed = true; + } } } @@ -451,7 +483,8 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override ui.Paragraph build() { - return SkwasmParagraph(); + // TODO(jacksongardner): implement this. + return SkwasmParagraph(nullptr); } @override 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 9c88cc55091d2..e971f1a05ae85 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 @@ -157,6 +157,11 @@ class StackScope { return pointer; } + Pointer allocIntArray(int count) { + final int length = count * sizeOf(); + return stackAlloc(length).cast(); + } + Pointer allocInt8Array(int count) { final int length = count * sizeOf(); return stackAlloc(length).cast(); @@ -177,6 +182,11 @@ class StackScope { return stackAlloc(length).cast(); } + Pointer allocSizeArray(int count) { + final int length = count * sizeOf(); + return stackAlloc(length).cast(); + } + Pointer> allocPointerArray(int count) { final int length = count * sizeOf>(); return stackAlloc(length).cast>(); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart new file mode 100644 index 0000000000000..3092c9860869c --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart @@ -0,0 +1,121 @@ +// 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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawParagraph extends Opaque {} +typedef ParagraphHandle = Pointer; + +final class RawLineMetrics extends Opaque {} +typedef LineMetricsHandle = Pointer; + +final class RawTextBoxList extends Opaque {} +typedef TextBoxListHandle = Pointer; + +@Native(symbol: 'paragraph_dispose', isLeaf: true) +external void paragraphDispose(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getWidth', isLeaf: true) +external double paragraphGetWidth(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getHeight', isLeaf: true) +external double paragraphGetHeight(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getLongestLine', isLeaf: true) +external double paragraphGetLongestLine(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getMinIntrinsicWidth', isLeaf: true) +external double paragraphGetMinIntrinsicWidth(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getMaxIntrinsicWidth', isLeaf: true) +external double paragraphGetMaxIntrinsicWidth(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getAlphabeticBaseline', isLeaf: true) +external double paragraphGetAlphabeticBaseline(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getIdeographicBaseline', isLeaf: true) +external double paragraphGetIdeographicBaseline(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getDidExceedMaxLines', isLeaf: true) +external bool paragraphGetDidExceedMaxLines(ParagraphHandle handle); + +@Native(symbol: 'paragraph_layout', isLeaf: true) +external void paragraphLayout(ParagraphHandle handle, double width); + +@Native +)>(symbol: 'paragraph_getPositionForOffset', isLeaf: true) +external int paragraphGetPositionForOffset( + ParagraphHandle handle, + double offsetX, + double offsetY, + Pointer outAffinity, +); + +@Native, +)>(symbol: 'paragraph_getWordBoundary', isLeaf: true) +external void paragraphGetWordBoundary( + ParagraphHandle handle, + int position, + Pointer outRange, // Two `size_t`s, start and end +); + +@Native(symbol: 'paragraph_getLineCount', isLeaf: true) +external int paragraphGetLineCount(ParagraphHandle handle); + +@Native(symbol: 'paragraph_getLineMetricsAtIndex', isLeaf: true) +external LineMetricsHandle paragraphGetLineMetricsAtIndex( + ParagraphHandle handle, + int index, +); + +@Native(symbol: 'textBoxList_dispose', isLeaf: true) +external void textBoxListDispose(TextBoxListHandle handle); + +@Native(symbol: 'textBoxList_getLength', isLeaf: true) +external int textBoxListGetLength(TextBoxListHandle handle); + +@Native(symbol: 'textBoxList_getBoxAtIndex', isLeaf: true) +external int textBoxListGetBoxAtIndex( + TextBoxListHandle handle, + int index, + RawRect outRect, +); + +@Native(symbol: 'paragraph_getBoxesForRange', isLeaf: true) +external TextBoxListHandle paragraphGetBoxesForRange( + ParagraphHandle handle, + int start, + int end, + int heightStyle, + int widthStyle, +); + +@Native( + symbol: 'paragraph_getBoxesForPlaceholders', isLeaf: true) +external TextBoxListHandle paragraphGetBoxesForPlaceholders(ParagraphHandle handle); diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index b6fb33607daac..1f93ce1935597 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -627,17 +627,6 @@ enum BoxWidthStyle { } abstract class LineMetrics { - factory LineMetrics({ - required bool hardBreak, - required double ascent, - required double descent, - required double unscaledAscent, - required double height, - required double width, - required double left, - required double baseline, - required int lineNumber, - }) = engine.EngineLineMetrics; bool get hardBreak; double get ascent; double get descent; diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 057eae416f246..654c97cc9b4ac 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -18,6 +18,7 @@ wasm_lib("skwasm") { "shaders.cpp", "string.cpp", "surface.cpp", + "text/paragraph.cpp", "text/paragraph_style.cpp", "text/strut_style.cpp", "text/text_style.cpp", diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp new file mode 100644 index 0000000000000..4eee0b0eb2aa5 --- /dev/null +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -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 "../export.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" + +using namespace skia::textlayout; + +SKWASM_EXPORT void paragraph_dispose(Paragraph *paragraph) { + delete paragraph; +} + +SKWASM_EXPORT SkScalar paragraph_getWidth(Paragraph *paragraph) { + return paragraph->getMaxWidth(); +} + +SKWASM_EXPORT SkScalar paragraph_getHeight(Paragraph *paragraph) { + return paragraph->getHeight(); +} + +SKWASM_EXPORT SkScalar paragraph_getLongestLine(Paragraph *paragraph) { + return paragraph->getLongestLine(); +} + +SKWASM_EXPORT SkScalar paragraph_getMinIntrinsicWidth(Paragraph *paragraph) { + return paragraph->getMinIntrinsicWidth(); +} + +SKWASM_EXPORT SkScalar paragraph_getMaxIntrinsicWidth(Paragraph *paragraph) { + return paragraph->getMaxIntrinsicWidth(); +} + +SKWASM_EXPORT SkScalar paragraph_getAlphabeticBaseline(Paragraph *paragraph) { + return paragraph->getAlphabeticBaseline(); +} + +SKWASM_EXPORT SkScalar paragraph_getIdeographicBaseline(Paragraph *paragraph) { + return paragraph->getIdeographicBaseline(); +} + +SKWASM_EXPORT bool paragraph_getDidExceedMaxLines(Paragraph *paragraph) { + return paragraph->didExceedMaxLines(); +} + +SKWASM_EXPORT void paragraph_layout(Paragraph *paragraph, SkScalar width) { + paragraph->layout(width); +} + +SKWASM_EXPORT int32_t paragraph_getPositionForOffset( + Paragraph *paragraph, + SkScalar offsetX, + SkScalar offsetY, + Affinity* outAffinity +) { + auto position = paragraph->getGlyphPositionAtCoordinate(offsetX, offsetY); + if (outAffinity) { + *outAffinity = position.affinity; + } + return position.position; +} + +SKWASM_EXPORT void paragraph_getWordBoundary( + Paragraph *paragraph, + unsigned int position, + size_t *outRange // Two `size_t`s, start and end +) { + auto range = paragraph->getWordBoundary(position); + outRange[0] = range.start; + outRange[1] = range.end; +} + +SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph *paragraph) { + return paragraph->lineNumber(); +} + +SKWASM_EXPORT LineMetrics *paragraph_getLineMetricsAtIndex( + Paragraph *paragraph, + size_t index +) { + auto metrics = new LineMetrics(); + paragraph->getLineMetricsAt(index, metrics); + return metrics; +} + +struct TextBoxList { + std::vector boxes; +}; + +SKWASM_EXPORT void textBoxList_dispose(TextBoxList *list) { + delete list; +} + +SKWASM_EXPORT size_t textBoxList_getLength(TextBoxList *list) { + return list->boxes.size(); +} + +SKWASM_EXPORT TextDirection textBoxList_getBoxAtIndex( + TextBoxList *list, + size_t index, + SkRect *outRect +) { + const auto& box = list->boxes[index]; + *outRect = box.rect; + return box.direction; +} + +SKWASM_EXPORT TextBoxList *paragraph_getBoxesForRange( + Paragraph *paragraph, + int start, + int end, + RectHeightStyle heightStyle, + RectWidthStyle widthStyle +) { + + return new TextBoxList{paragraph->getRectsForRange( + start, + end, + heightStyle, + widthStyle + )}; +} + +SKWASM_EXPORT TextBoxList* paragraph_getBoxesForPlaceholders(Paragraph *paragraph) { + return new TextBoxList{paragraph->getRectsForPlaceholders()}; +} From d45d86aa8316c73e308ba4593230238b6132cab6 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 25 Apr 2023 22:53:49 -0700 Subject: [PATCH 06/27] Implemented line metrics. --- .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../engine/skwasm/skwasm_impl/paragraph.dart | 55 +++++++++--------- .../raw/text/raw_line_metrics.dart | 47 ++++++++++++++++ .../skwasm_impl/raw/text/raw_paragraph.dart | 6 +- lib/web_ui/skwasm/BUILD.gn | 1 + lib/web_ui/skwasm/text/line_metrics.cpp | 56 +++++++++++++++++++ lib/web_ui/skwasm/text/paragraph.cpp | 4 ++ 7 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart create mode 100644 lib/web_ui/skwasm/text/line_metrics.cpp diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index d6d2407470e11..bb5c6de4e1b3a 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -31,6 +31,7 @@ export 'skwasm_impl/raw/raw_skdata.dart'; export 'skwasm_impl/raw/raw_skstring.dart'; export 'skwasm_impl/raw/raw_surface.dart'; export 'skwasm_impl/raw/skwasm_module.dart'; +export 'skwasm_impl/raw/text/raw_line_metrics.dart'; export 'skwasm_impl/raw/text/raw_paragraph.dart'; export 'skwasm_impl/raw/text/raw_paragraph_style.dart'; export 'skwasm_impl/raw/text/raw_strut_style.dart'; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 0208acf9f0c63..3ed65d954a18f 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -14,50 +14,40 @@ class SkwasmLineMetrics implements ui.LineMetrics { SkwasmLineMetrics._(this.handle); final LineMetricsHandle handle; + bool _isDisposed = false; @override - bool get hardBreak { - throw UnimplementedError(); - } + bool get hardBreak => lineMetricsGetHardBreak(handle); @override - double get ascent { - throw UnimplementedError(); - } + double get ascent => lineMetricsGetAscent(handle); @override - double get descent { - throw UnimplementedError(); - } + double get descent => lineMetricsGetDescent(handle); @override - double get unscaledAscent { - throw UnimplementedError(); - } + double get unscaledAscent => lineMetricsGetUnscaledAscent(handle); @override - double get height { - throw UnimplementedError(); - } + double get height => lineMetricsGetHeight(handle); @override - double get width { - throw UnimplementedError(); - } + double get width => lineMetricsGetWidth(handle); @override - double get left { - throw UnimplementedError(); - } + double get left => lineMetricsGetLeft(handle); @override - double get baseline { - throw UnimplementedError(); - } + double get baseline => lineMetricsGetBaseline(handle); @override - int get lineNumber { - throw UnimplementedError(); + int get lineNumber => lineMetricsGetLineNumber(handle); + + void dispose() { + if (_isDisposed) { + lineMetricsDispose(handle); + _isDisposed = true; + } } } @@ -65,7 +55,7 @@ class SkwasmParagraph implements ui.Paragraph { SkwasmParagraph(this.handle); ParagraphHandle handle; - bool _isDisposed = true; + bool _isDisposed = false; @override double get width => paragraphGetWidth(handle); @@ -156,8 +146,15 @@ class SkwasmParagraph implements ui.Paragraph { @override ui.TextRange getLineBoundary(ui.TextPosition position) { - // TODO(jacksongardner): Implement this one line metrics are usable. - return const ui.TextRange(start: 0, end: 0); + final int lineNumber = paragraphGetLineNumberAt(handle, position.offset); + final LineMetricsHandle metricsHandle = + paragraphGetLineMetricsAtIndex(handle, lineNumber); + final ui.TextRange range = ui.TextRange( + start: lineMetricsGetStartIndex(metricsHandle), + end: lineMetricsGetEndIndex(metricsHandle), + ); + lineMetricsDispose(metricsHandle); + return range; } @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart new file mode 100644 index 0000000000000..19819b8e99967 --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart @@ -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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +final class RawLineMetrics extends Opaque {} +typedef LineMetricsHandle = Pointer; + +@Native(symbol: 'lineMetrics_dispose', isLeaf: true) +external void lineMetricsDispose(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getHardBreak', isLeaf: true) +external bool lineMetricsGetHardBreak(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getAscent', isLeaf: true) +external double lineMetricsGetAscent(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getDescent', isLeaf: true) +external double lineMetricsGetDescent(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getUnscaledAscent', isLeaf: true) +external double lineMetricsGetUnscaledAscent(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getHeight', isLeaf: true) +external double lineMetricsGetHeight(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getWidth', isLeaf: true) +external double lineMetricsGetWidth(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getLeft', isLeaf: true) +external double lineMetricsGetLeft(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getBaseline', isLeaf: true) +external double lineMetricsGetBaseline(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getLineNumber', isLeaf: true) +external int lineMetricsGetLineNumber(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getStartIndex', isLeaf: true) +external int lineMetricsGetStartIndex(LineMetricsHandle handle); + +@Native(symbol: 'lineMetrics_getEndIndex', isLeaf: true) +external int lineMetricsGetEndIndex(LineMetricsHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart index 3092c9860869c..d5ce621f55fc4 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart @@ -12,9 +12,6 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; final class RawParagraph extends Opaque {} typedef ParagraphHandle = Pointer; -final class RawLineMetrics extends Opaque {} -typedef LineMetricsHandle = Pointer; - final class RawTextBoxList extends Opaque {} typedef TextBoxListHandle = Pointer; @@ -75,6 +72,9 @@ external void paragraphGetWordBoundary( @Native(symbol: 'paragraph_getLineCount', isLeaf: true) external int paragraphGetLineCount(ParagraphHandle handle); +@Native(symbol: 'paragraph_getLineNumberAt', isLeaf: true) +external int paragraphGetLineNumberAt(ParagraphHandle handle, int characterIndex); + @NativefHardBreak; +} + +SKWASM_EXPORT SkScalar lineMetrics_getAscent(LineMetrics *metrics) { + return metrics->fAscent; +} + +SKWASM_EXPORT SkScalar lineMetrics_getDescent(LineMetrics *metrics) { + return metrics->fDescent; +} + +SKWASM_EXPORT SkScalar lineMetrics_getUnscaledAscent(LineMetrics *metrics) { + return metrics->fUnscaledAscent; +} + +SKWASM_EXPORT SkScalar lineMetrics_getHeight(LineMetrics *metrics) { + return metrics->fHeight; +} + +SKWASM_EXPORT SkScalar lineMetrics_getWidth(LineMetrics *metrics) { + return metrics->fWidth; +} + +SKWASM_EXPORT SkScalar lineMetrics_getLeft(LineMetrics *metrics) { + return metrics->fLeft; +} + +SKWASM_EXPORT SkScalar lineMetrics_getBaseline(LineMetrics *metrics) { + return metrics->fBaseline; +} + +SKWASM_EXPORT int lineMetrics_getLineNumber(LineMetrics *metrics) { + return metrics->fLineNumber; +} + +SKWASM_EXPORT size_t lineMetrics_getStartIndex(LineMetrics *metrics) { + return metrics->fStartIndex; +} + +SKWASM_EXPORT size_t lineMetrics_getEndIndex(LineMetrics *metrics) { + return metrics->fEndIndex; +} diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index 4eee0b0eb2aa5..6d0e50050359b 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -74,6 +74,10 @@ SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph *paragraph) { return paragraph->lineNumber(); } +SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph *paragraph, size_t characterIndex) { + return paragraph->getLineNumberAt(characterIndex); +} + SKWASM_EXPORT LineMetrics *paragraph_getLineMetricsAtIndex( Paragraph *paragraph, size_t index From 166bfc936a572359e4765c66ab59d8e0d8ed6709 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 25 Apr 2023 23:55:08 -0700 Subject: [PATCH 07/27] Implement paragraph builder. --- .../lib/src/engine/skwasm/skwasm_impl.dart | 1 + .../skwasm/skwasm_impl/font_collection.dart | 14 ++-- .../engine/skwasm/skwasm_impl/paragraph.dart | 37 +++++++++-- .../skwasm/skwasm_impl/raw/raw_skstring.dart | 21 ++++++ .../raw/text/raw_paragraph_builder.dart | 66 +++++++++++++++++++ .../engine/skwasm/skwasm_impl/renderer.dart | 3 +- lib/web_ui/skwasm/BUILD.gn | 1 + lib/web_ui/skwasm/fonts.cpp | 7 +- lib/web_ui/skwasm/string.cpp | 14 ++++ lib/web_ui/skwasm/text/paragraph_builder.cpp | 60 +++++++++++++++++ lib/web_ui/skwasm/wrappers.h | 7 ++ 11 files changed, 212 insertions(+), 19 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart create mode 100644 lib/web_ui/skwasm/text/paragraph_builder.cpp diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart index bb5c6de4e1b3a..c1c5eade1687e 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl.dart @@ -33,6 +33,7 @@ export 'skwasm_impl/raw/raw_surface.dart'; export 'skwasm_impl/raw/skwasm_module.dart'; export 'skwasm_impl/raw/text/raw_line_metrics.dart'; export 'skwasm_impl/raw/text/raw_paragraph.dart'; +export 'skwasm_impl/raw/text/raw_paragraph_builder.dart'; export 'skwasm_impl/raw/text/raw_paragraph_style.dart'; export 'skwasm_impl/raw/text/raw_strut_style.dart'; export 'skwasm_impl/raw/text/raw_text_style.dart'; 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 2657da91c7c4e..779604333d172 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 @@ -12,14 +12,14 @@ import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; class SkwasmFontCollection implements FlutterFontCollection { - SkwasmFontCollection() : _handle = fontCollectionCreate(); + SkwasmFontCollection() : handle = fontCollectionCreate(); - FontCollectionHandle _handle; + FontCollectionHandle handle; @override void clear() { - fontCollectionDispose(_handle); - _handle = fontCollectionCreate(); + fontCollectionDispose(handle); + handle = fontCollectionCreate(); } @override @@ -78,7 +78,7 @@ class SkwasmFontCollection implements FlutterFontCollection { wasmMemory.set(chunk, dataAddress.toJS); dataAddress += chunk.length.toDart.toInt(); } - final bool result = fontCollectionRegisterFont(_handle, fontData, familyNameHandle); + final bool result = fontCollectionRegisterFont(handle, fontData, familyNameHandle); skDataDispose(fontData); if (!result) { return FontInvalidDataError(assetManager.getAssetUrl(asset.asset)); @@ -96,10 +96,10 @@ class SkwasmFontCollection implements FlutterFontCollection { bool success; if (fontFamily != null) { final SkStringHandle familyHandle = skStringFromDartString(fontFamily); - success = fontCollectionRegisterFont(_handle, dataHandle, familyHandle); + success = fontCollectionRegisterFont(handle, dataHandle, familyHandle); skStringFree(familyHandle); } else { - success = fontCollectionRegisterFont(_handle, dataHandle, nullptr); + success = fontCollectionRegisterFont(handle, dataHandle, nullptr); } skDataDispose(dataHandle); return success; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 3ed65d954a18f..fb436f0c101ef 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -463,6 +463,20 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { } class SkwasmParagraphBuilder implements ui.ParagraphBuilder { + factory SkwasmParagraphBuilder( + SkwasmParagraphStyle style, + SkwasmFontCollection collection, + ) => SkwasmParagraphBuilder._(paragraphBuilderCreate( + style.handle, + collection.handle, + )); + + SkwasmParagraphBuilder._(this.handle); + final ParagraphBuilderHandle handle; + + @override + List placeholderScales = []; + @override void addPlaceholder( double width, @@ -472,29 +486,40 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { double? baselineOffset, ui.TextBaseline? baseline }) { + paragraphBuilderAddPlaceholder( + handle, + width * scale, + height * scale, + alignment.index, + (baselineOffset ?? height) * scale, + (baseline ?? ui.TextBaseline.alphabetic).index, + ); + placeholderScales.add(scale); } @override void addText(String text) { + final SkString16Handle stringHandle = skString16FromDartString(text); + paragraphBuilderAddText(handle, stringHandle); + skString16Free(stringHandle); } @override ui.Paragraph build() { - // TODO(jacksongardner): implement this. - return SkwasmParagraph(nullptr); + return SkwasmParagraph(paragraphBuilderBuild(handle)); } @override - int get placeholderCount => 0; - - @override - List get placeholderScales => []; + int get placeholderCount => placeholderScales.length; @override void pop() { + paragraphBuilderPop(handle); } @override void pushStyle(ui.TextStyle style) { + style as SkwasmTextStyle; + paragraphBuilderPushStyle(handle, style.handle); } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart index 01e9eb23f3ce2..84a500ebc5950 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart @@ -11,6 +11,9 @@ import 'dart:ffi'; final class RawSkString extends Opaque {} typedef SkStringHandle = Pointer; +final class RawSkString16 extends Opaque {} +typedef SkString16Handle = Pointer; + @Native(symbol: 'skString_allocate', isLeaf: true) external SkStringHandle skStringAllocate(int size); @@ -20,6 +23,15 @@ external Pointer skStringGetData(SkStringHandle handle); @Native(symbol: 'skString_free', isLeaf: true) external void skStringFree(SkStringHandle handle); +@Native(symbol: 'skString16_allocate', isLeaf: true) +external SkString16Handle skString16Allocate(int size); + +@Native Function(SkString16Handle)>(symbol: 'skString16_getData', isLeaf: true) +external Pointer skString16GetData(SkString16Handle handle); + +@Native(symbol: 'skString16_free', isLeaf: true) +external void skString16Free(SkString16Handle handle); + SkStringHandle skStringFromDartString(String string) { final List rawUtf8Bytes = utf8.encode(string); final SkStringHandle stringHandle = skStringAllocate(rawUtf8Bytes.length); @@ -29,3 +41,12 @@ SkStringHandle skStringFromDartString(String string) { } return stringHandle; } + +SkString16Handle skString16FromDartString(String string) { + final SkString16Handle stringHandle = skString16Allocate(string.length); + final Pointer stringDataPointer = skString16GetData(stringHandle); + for (int i = 0; i < string.length; i++) { + stringDataPointer[i] = string.codeUnitAt(i); + } + return stringHandle; +} diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart new file mode 100644 index 0000000000000..eaca68847bcf2 --- /dev/null +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart @@ -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. + +@DefaultAsset('skwasm') +library skwasm_impl; + +import 'dart:ffi'; + +import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; + +final class RawParagraphBuilder extends Opaque {} +typedef ParagraphBuilderHandle = Pointer; + +@Native(symbol: 'paragraphBuilder_create', isLeaf: true) +external ParagraphBuilderHandle paragraphBuilderCreate( + ParagraphStyleHandle styleHandle, + FontCollectionHandle fontCollectionHandle, +); + +@Native(symbol: 'paragraphBuilder_dispose', isLeaf: true) +external void paragraphBuilderDispose(ParagraphBuilderHandle handle); + +@Native(symbol: 'paragraphBuilder_addPlaceholder', isLeaf: true) +external void paragraphBuilderAddPlaceholder( + ParagraphBuilderHandle handle, + double width, + double height, + int alignment, + double baslineOffset, + int baseline, +); + +@Native(symbol: 'paragraphBuilder_addText', isLeaf: true) +external void paragraphBuilderAddText( + ParagraphBuilderHandle handle, + SkString16Handle text, +); + +@Native(symbol: 'paragraphBuilder_pushStyle', isLeaf: true) +external void paragraphBuilderPushStyle( + ParagraphBuilderHandle handle, + TextStyleHandle styleHandle, +); + +@Native(symbol: 'paragraphBuilder_pop', isLeaf: true) +external void paragraphBuilderPop(ParagraphBuilderHandle handle); + +@Native(symbol: 'paragraphBuilder_build', isLeaf: true) +external ParagraphHandle paragraphBuilderBuild(ParagraphBuilderHandle handle); 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 8a12a946b77b2..f3cfd38bf2c33 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 @@ -105,7 +105,8 @@ class SkwasmRenderer implements Renderer { ui.Paint createPaint() => SkwasmPaint(); @override - ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => SkwasmParagraphBuilder(); + ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => + SkwasmParagraphBuilder(style as SkwasmParagraphStyle, fontCollection); @override ui.ParagraphStyle createParagraphStyle({ diff --git a/lib/web_ui/skwasm/BUILD.gn b/lib/web_ui/skwasm/BUILD.gn index 3c6da505246b1..362a75a1a7b84 100644 --- a/lib/web_ui/skwasm/BUILD.gn +++ b/lib/web_ui/skwasm/BUILD.gn @@ -20,6 +20,7 @@ wasm_lib("skwasm") { "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", diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index 22b66419cf8e9..c7c491cb24dc8 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -3,16 +3,13 @@ // found in the LICENSE file. #include "export.h" +#include "wrappers.h" #include "third_party/skia/include/core/SkFontMgr.h" #include "third_party/skia/modules/skparagraph/include/FontCollection.h" #include "third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h" using namespace skia::textlayout; - -struct FlutterFontCollection { - sk_sp collection; - sk_sp provider; -}; +using namespace Skwasm; SKWASM_EXPORT FlutterFontCollection* fontCollection_create() { auto collection = sk_make_sp(); diff --git a/lib/web_ui/skwasm/string.cpp b/lib/web_ui/skwasm/string.cpp index 77e66d705dcfc..54aa669d33de3 100644 --- a/lib/web_ui/skwasm/string.cpp +++ b/lib/web_ui/skwasm/string.cpp @@ -17,3 +17,17 @@ SKWASM_EXPORT char* skString_getData(SkString* string) { SKWASM_EXPORT void skString_free(SkString* string) { return delete string; } + +SKWASM_EXPORT std::u16string *skString16_allocate(size_t length) { + std::u16string *string = new std::u16string(); + string->resize(length); + return string; +} + +SKWASM_EXPORT char16_t *skString16_getData(std::u16string *string) { + return string->data(); +} + +SKWASM_EXPORT void skString16_free(std::u16string *string) { + delete string; +} diff --git a/lib/web_ui/skwasm/text/paragraph_builder.cpp b/lib/web_ui/skwasm/text/paragraph_builder.cpp new file mode 100644 index 0000000000000..2a5c5dec2d84c --- /dev/null +++ b/lib/web_ui/skwasm/text/paragraph_builder.cpp @@ -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 "../export.h" +#include "../wrappers.h" +#include "third_party/skia/modules/skparagraph/include/ParagraphBuilder.h" + +using namespace skia::textlayout; +using namespace Skwasm; + +SKWASM_EXPORT ParagraphBuilder *paragraphBuilder_create( + ParagraphStyle *style, + FlutterFontCollection *collection +) { + return ParagraphBuilder::make(*style, collection->collection).release(); +} + +SKWASM_EXPORT void paragraphBuilder_dispose(ParagraphBuilder *builder) { + delete builder; +} + +SKWASM_EXPORT void paragraphBuilder_addPlaceholder( + ParagraphBuilder *builder, + SkScalar width, + SkScalar height, + PlaceholderAlignment alignment, + SkScalar baselineOffset, + TextBaseline baseline +) { + builder->addPlaceholder(PlaceholderStyle( + width, + height, + alignment, + baseline, + baselineOffset + )); +} + +SKWASM_EXPORT void paragraphBuilder_addText( + ParagraphBuilder *builder, + std::u16string *text +) { + builder->addText(*text); +} + +SKWASM_EXPORT void paragraphBuilder_pushStyle( + ParagraphBuilder *builder, + TextStyle *style +) { + builder->pushStyle(*style); +} + +SKWASM_EXPORT void paragraphBuilder_pop(ParagraphBuilder *builder) { + builder->pop(); +} + +SKWASM_EXPORT Paragraph *paragraphBuilder_build(ParagraphBuilder *builder) { + return builder->Build().release(); +} diff --git a/lib/web_ui/skwasm/wrappers.h b/lib/web_ui/skwasm/wrappers.h index 2acc209015d36..ffb356bba9c67 100644 --- a/lib/web_ui/skwasm/wrappers.h +++ b/lib/web_ui/skwasm/wrappers.h @@ -7,6 +7,8 @@ #include #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/modules/skparagraph/include/FontCollection.h" +#include "third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h" namespace Skwasm { @@ -31,4 +33,9 @@ inline void makeCurrent(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle) { } } +struct FlutterFontCollection { + sk_sp collection; + sk_sp provider; +}; + } // namespace Skwasm From 9d09a1d28e9b8c8b501623ba9cdca24a8eeeab07 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Wed, 26 Apr 2023 08:28:55 -0700 Subject: [PATCH 08/27] Rendering working, but layout doesn't. --- .../lib/src/engine/skwasm/skwasm_impl/canvas.dart | 8 ++++++-- .../src/engine/skwasm/skwasm_impl/paragraph.dart | 4 ++-- .../engine/skwasm/skwasm_impl/raw/raw_canvas.dart | 13 +++++++++++++ .../engine/skwasm/skwasm_impl/raw/raw_memory.dart | 10 ---------- .../skwasm/skwasm_impl/raw/text/raw_paragraph.dart | 8 ++++---- lib/web_ui/skwasm/canvas.cpp | 12 ++++++++++++ lib/web_ui/skwasm/text/paragraph.cpp | 2 +- 7 files changed, 38 insertions(+), 19 deletions(-) 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 e886b6985d6de..bcc1c65731e3e 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 @@ -205,8 +205,12 @@ class SkwasmCanvas implements ui.Canvas { } @override - void drawParagraph(ui.Paragraph uiParagraph, ui.Offset offset) { - // TODO(jacksongardner): implement this + void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { + canvasDrawParagraph( + _handle, + (paragraph as SkwasmParagraph).handle, + offset.dx, + offset.dy,); } @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index fb436f0c101ef..8d3f98afed324 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -124,7 +124,7 @@ class SkwasmParagraph implements ui.Paragraph { @override ui.TextPosition getPositionForOffset(ui.Offset offset) => withStackScope((StackScope scope) { - final Pointer outAffinity = scope.allocIntArray(1); + final Pointer outAffinity = scope.allocInt32Array(1); final int position = paragraphGetPositionForOffset( handle, offset.dx, @@ -139,7 +139,7 @@ class SkwasmParagraph implements ui.Paragraph { @override ui.TextRange getWordBoundary(ui.TextPosition position) => withStackScope((StackScope scope) { - final Pointer outRange = scope.allocSizeArray(2); + final Pointer outRange = scope.allocInt32Array(2); paragraphGetWordBoundary(handle, position.offset, outRange); return ui.TextRange(start: outRange[0], end: outRange[1]); }); 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 276fc0dd02461..8571c000f7338 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 @@ -137,6 +137,19 @@ external void canvasDrawShadow( bool transparentOccluder, ); +@Native(symbol: 'canvas_drawParagraph', isLeaf: true) +external void canvasDrawParagraph( + CanvasHandle handle, + ParagraphHandle paragraphHandle, + double x, + double y, +); + @Native( symbol: 'canvas_getTransform', isLeaf: true) external void canvasGetTransform(CanvasHandle canvas, RawMatrix44 outMatrix); 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 e971f1a05ae85..9c88cc55091d2 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 @@ -157,11 +157,6 @@ class StackScope { return pointer; } - Pointer allocIntArray(int count) { - final int length = count * sizeOf(); - return stackAlloc(length).cast(); - } - Pointer allocInt8Array(int count) { final int length = count * sizeOf(); return stackAlloc(length).cast(); @@ -182,11 +177,6 @@ class StackScope { return stackAlloc(length).cast(); } - Pointer allocSizeArray(int count) { - final int length = count * sizeOf(); - return stackAlloc(length).cast(); - } - Pointer> allocPointerArray(int count) { final int length = count * sizeOf>(); return stackAlloc(length).cast>(); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart index d5ce621f55fc4..c255dd27ebdd0 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart @@ -49,24 +49,24 @@ external void paragraphLayout(ParagraphHandle handle, double width); ParagraphHandle, Float, Float, - Pointer + Pointer )>(symbol: 'paragraph_getPositionForOffset', isLeaf: true) external int paragraphGetPositionForOffset( ParagraphHandle handle, double offsetX, double offsetY, - Pointer outAffinity, + Pointer outAffinity, ); @Native, + Pointer, )>(symbol: 'paragraph_getWordBoundary', isLeaf: true) external void paragraphGetWordBoundary( ParagraphHandle handle, int position, - Pointer outRange, // Two `size_t`s, start and end + Pointer outRange, // Two `size_t`s, start and end ); @Native(symbol: 'paragraph_getLineCount', isLeaf: true) diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 9a07aef09bba1..6edcdbecfa334 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -8,6 +8,9 @@ #include "third_party/skia/include/core/SkPoint3.h" #include "third_party/skia/include/utils/SkShadowUtils.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" + +using namespace skia::textlayout; using namespace Skwasm; @@ -205,6 +208,15 @@ SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper, devicePixelRatio * kShadowLightRadius, outAmbient, outSpot, flags); } +SKWASM_EXPORT void canvas_drawParagraph( + CanvasWrapper *wrapper, + Paragraph *paragraph, + SkScalar x, + SkScalar y +) { + paragraph->paint(wrapper->canvas, x, y); +} + SKWASM_EXPORT void canvas_drawPicture(CanvasWrapper* wrapper, SkPicture* picture) { makeCurrent(wrapper->context); diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index 6d0e50050359b..548f3001778e8 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -63,7 +63,7 @@ SKWASM_EXPORT int32_t paragraph_getPositionForOffset( SKWASM_EXPORT void paragraph_getWordBoundary( Paragraph *paragraph, unsigned int position, - size_t *outRange // Two `size_t`s, start and end + int32_t *outRange // Two `int32_t`s, start and end ) { auto range = paragraph->getWordBoundary(position); outRange[0] = range.start; From 9e89585bdc02620baa777edfc38afebd4b3f10b6 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 27 Apr 2023 12:04:04 -0700 Subject: [PATCH 09/27] Make sure to install a fallback font in Skwasm. --- lib/web_ui/lib/src/engine/canvaskit/fonts.dart | 4 ++-- .../skwasm/skwasm_impl/font_collection.dart | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 5f4fbabb0a723..78121dc553b59 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -108,8 +108,8 @@ class SkiaFontCollection implements FlutterFontCollection { } } - /// We need a default fallback font for CanvasKit, in order to - /// avoid crashing while laying out text with an unregistered font. We chose + /// We need a default fallback font for CanvasKit, in order to avoid + /// crashing while laying out text with an unregistered font. We chose /// Roboto to match Android. if (!loadedRoboto) { // Download Roboto and add it to the font buffers. 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 779604333d172..9c28e3f9ab4e7 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 @@ -11,6 +11,14 @@ import 'dart:typed_data'; import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; +// This URL was found by using the Google Fonts Developer API to find the URL +// for Roboto. The API warns that this URL is not stable. In order to update +// this, list out all of the fonts and find the URL for the regular +// Roboto font. The API reference is here: +// https://developers.google.com/fonts/docs/developer_api +const String _robotoUrl = + 'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf'; + class SkwasmFontCollection implements FlutterFontCollection { SkwasmFontCollection() : handle = fontCollectionCreate(); @@ -28,6 +36,15 @@ class SkwasmFontCollection implements FlutterFontCollection { final List loadedFonts = []; final Map fontFailures = {}; + /// We need a default fallback font for Skwasm, in order to avoid crashing + /// while laying out text with an unregistered font. We chose Roboto to + /// match Android. + if (!manifest.families.any((FontFamily family) => family.name == 'Roboto')) { + manifest.families.add( + FontFamily('sans-serif', [FontAsset(_robotoUrl, {})]) + ); + } + // We can't restore the pointers directly due to a bug in dart2wasm // https://github.com/dart-lang/sdk/issues/52142 final List familyHandles = []; @@ -45,6 +62,7 @@ class SkwasmFontCollection implements FlutterFontCollection { }()); } } + await Future.wait(fontFutures); // Wait until all the downloading and registering is complete before From cc2a1d4860d81adeb95f898aa2a2fd8b74afae09 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 27 Apr 2023 13:30:44 -0700 Subject: [PATCH 10/27] Include ICU data. --- third_party/canvaskit/BUILD.gn | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/third_party/canvaskit/BUILD.gn b/third_party/canvaskit/BUILD.gn index af6b617bd8624..d4d0801220f82 100644 --- a/third_party/canvaskit/BUILD.gn +++ b/third_party/canvaskit/BUILD.gn @@ -71,11 +71,13 @@ copy("canvaskit_chromium_group") { # This toolchain is only to be used by skwasm_group below. wasm_toolchain("skwasm") { 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" + # Include ICU data. Eventually we'd like to omit this, like we do in the + # canvaskit_chromium build, but as of right now the client ICU APIs are + # only available in private skia headers, which we can't include in our own + # sources. Once skia provides this capability in their public headers, we + # can use client ICU instead. + skia_use_icu = true + skia_use_client_icu = false skia_use_libjpeg_turbo_decode = false skia_use_libpng_decode = false From 91d5ceae9e7025fc65a24557b7d55678af51fccd Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sat, 29 Apr 2023 15:27:28 -0700 Subject: [PATCH 11/27] Some changes for fallbacks and some unit tests ported. --- .../skwasm/skwasm_impl/font_collection.dart | 2 +- lib/web_ui/skwasm/fonts.cpp | 2 +- lib/web_ui/skwasm/text/paragraph_style.cpp | 1 + .../test/ui/paragraph_builder_test.dart | 4 +- lib/web_ui/test/ui/text_golden_test.dart | 556 ++++++++++++++++++ lib/web_ui/test/ui/utils.dart | 19 + 6 files changed, 580 insertions(+), 4 deletions(-) create mode 100644 lib/web_ui/test/ui/text_golden_test.dart 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 9c28e3f9ab4e7..845f7c147a3d3 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 @@ -41,7 +41,7 @@ class SkwasmFontCollection implements FlutterFontCollection { /// match Android. if (!manifest.families.any((FontFamily family) => family.name == 'Roboto')) { manifest.families.add( - FontFamily('sans-serif', [FontAsset(_robotoUrl, {})]) + FontFamily('Roboto', [FontAsset(_robotoUrl, {})]) ); } diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index c7c491cb24dc8..45926d19ad512 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -15,7 +15,7 @@ SKWASM_EXPORT FlutterFontCollection* fontCollection_create() { auto collection = sk_make_sp(); auto provider = sk_make_sp(); collection->enableFontFallback(); - collection->setDefaultFontManager(provider); + collection->setDefaultFontManager(provider, "Roboto"); return new FlutterFontCollection{ std::move(collection), std::move(provider), diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index d471195af137c..07c064def0636 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -12,6 +12,7 @@ SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { // This is the default behavior in Flutter style->setReplaceTabCharacters(true); + return style; } diff --git a/lib/web_ui/test/ui/paragraph_builder_test.dart b/lib/web_ui/test/ui/paragraph_builder_test.dart index fe31eed072861..cb533d995e250 100644 --- a/lib/web_ui/test/ui/paragraph_builder_test.dart +++ b/lib/web_ui/test/ui/paragraph_builder_test.dart @@ -28,7 +28,7 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 800.0)); expect(paragraph.width, isNonZero); expect(paragraph.height, isNonZero); - }, skip: isSkwasm); + }); test('the presence of foreground style should not throw', () { final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); @@ -38,5 +38,5 @@ Future testMain() async { builder.addText('hi'); expect(() => builder.build(), returnsNormally); - }, skip: isSkwasm); + }); } diff --git a/lib/web_ui/test/ui/text_golden_test.dart b/lib/web_ui/test/ui/text_golden_test.dart new file mode 100644 index 0000000000000..77e7d69bb4b9d --- /dev/null +++ b/lib/web_ui/test/ui/text_golden_test.dart @@ -0,0 +1,556 @@ +// 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:math' as math; + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart' as ui; +import 'package:web_engine_tester/golden_tester.dart'; + +import '../common/test_initialization.dart'; +import 'utils.dart'; + +// TODO(yjbanov): tests that render using Noto are not hermetic, as those fonts +// come from fonts.google.com, where fonts can change any time. +// These tests are skipped. +// https://github.com/flutter/flutter/issues/86432 +const bool kIssue86432Exists = true; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +Future testMain() async { + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); + + test('text styles - default', () async { + await testTextStyle('default'); + }); + + test('text styles - center aligned', () async { + await testTextStyle('center aligned', + paragraphTextAlign: ui.TextAlign.center); + }); + + test('text styles - right aligned', () async { + await testTextStyle('right aligned', + paragraphTextAlign: ui.TextAlign.right); + }); + + test('text styles - rtl', () async { + await testTextStyle('rtl', paragraphTextDirection: ui.TextDirection.rtl); + }); + + test('text styles - multiline', () async { + await testTextStyle('multiline', layoutWidth: 50); + }); + + test('text styles - max lines', () async { + await testTextStyle('max lines', paragraphMaxLines: 1, layoutWidth: 50); + }); + + test('text styles - ellipsis', () async { + await testTextStyle('ellipsis', + paragraphMaxLines: 1, paragraphEllipsis: '...', layoutWidth: 60); + }); + + test('text styles - paragraph font family', () async { + await testTextStyle('paragraph font family', paragraphFontFamily: 'Ahem'); + }); + + test('text styles - paragraph font size', () async { + await testTextStyle('paragraph font size', paragraphFontSize: 22); + }); + + test('text styles - paragraph height', () async { + await testTextStyle('paragraph height', + layoutWidth: 50, paragraphHeight: 1.5); + }); + + test('text styles - paragraph text height behavior', () async { + await testTextStyle('paragraph text height behavior', + layoutWidth: 50, + paragraphHeight: 1.5, + paragraphTextHeightBehavior: const ui.TextHeightBehavior( + applyHeightToFirstAscent: false, + applyHeightToLastDescent: false, + )); + }); + + test('text styles - paragraph weight', () async { + await testTextStyle('paragraph weight', + paragraphFontWeight: ui.FontWeight.w900); + }); + + test('text style - paragraph font style', () async { + await testTextStyle( + 'paragraph font style', + paragraphFontStyle: ui.FontStyle.italic, + ); + }); + + // TODO(yjbanov): locales specified in paragraph styles don't work: + // https://github.com/flutter/flutter/issues/74687 + // TODO(yjbanov): spaces are not rendered correctly: + // https://github.com/flutter/flutter/issues/74742 + test('text styles - paragraph locale zh_CN', () async { + await testTextStyle('paragraph locale zh_CN', + outerText: '次 化 刃 直 入 令', + innerText: '', + paragraphLocale: const ui.Locale('zh', 'CN')); + }, skip: kIssue86432Exists); + + test('text styles - paragraph locale zh_TW', () async { + await testTextStyle('paragraph locale zh_TW', + outerText: '次 化 刃 直 入 令', + innerText: '', + paragraphLocale: const ui.Locale('zh', 'TW')); + }, skip: kIssue86432Exists); + + test('text styles - paragraph locale ja', () async { + await testTextStyle('paragraph locale ja', + outerText: '次 化 刃 直 入 令', + innerText: '', + paragraphLocale: const ui.Locale('ja')); + }, skip: kIssue86432Exists); + + test('text styles - paragraph locale ko', () async { + await testTextStyle('paragraph locale ko', + outerText: '次 化 刃 直 入 令', + innerText: '', + paragraphLocale: const ui.Locale('ko')); + }, skip: kIssue86432Exists); + + test('text styles - color', () async { + await testTextStyle('color', color: const ui.Color(0xFF009900)); + }); + + test('text styles - decoration', () async { + await testTextStyle('decoration', + decoration: ui.TextDecoration.underline); + }); + + test('text styles - decoration style', () async { + await testTextStyle('decoration style', + decoration: ui.TextDecoration.underline, + decorationStyle: ui.TextDecorationStyle.dashed); + }); + + test('text styles - decoration thickness', () async { + await testTextStyle('decoration thickness', + decoration: ui.TextDecoration.underline, decorationThickness: 5.0); + }); + + test('text styles - font weight', () async { + await testTextStyle('font weight', fontWeight: ui.FontWeight.w900); + }); + + test('text styles - font style', () async { + await testTextStyle('font style', fontStyle: ui.FontStyle.italic); + }); + + // TODO(yjbanov): not sure how to test this. + test('text styles - baseline', () async { + await testTextStyle('baseline', + textBaseline: ui.TextBaseline.ideographic); + }); + + test('text styles - font family', () async { + await testTextStyle('font family', fontFamily: 'Ahem'); + }); + + test('text styles - non-existent font family', () async { + await testTextStyle('non-existent font family', + fontFamily: 'DoesNotExist'); + }); + + test('text styles - family fallback', () async { + await testTextStyle('family fallback', + fontFamily: 'DoesNotExist', fontFamilyFallback: ['Ahem']); + }); + + test('text styles - font size', () async { + await testTextStyle('font size', fontSize: 24); + }); + + // A regression test for the special case when CanvasKit would default to + // a positive font size when Flutter specifies zero. + // + // See: https://github.com/flutter/flutter/issues/98248 + test('text styles - zero font size', () async { + // This only sets the inner text style, but not the paragraph style, so + // "Hello" should be visible, but "World!" should disappear. + await testTextStyle('zero font size', fontSize: 0); + + // This sets the paragraph font size to zero, but the inner text gets + // an explicit non-zero size that should override paragraph properties, + // so this time "Hello" should disappear, but "World!" should still be + // visible. + await testTextStyle('zero paragraph font size', paragraphFontSize: 0, fontSize: 14); + }); + + test('text styles - letter spacing', () async { + await testTextStyle('letter spacing', letterSpacing: 5); + }); + + test('text styles - word spacing', () async { + await testTextStyle('word spacing', + innerText: 'Beautiful World!', wordSpacing: 25); + }); + + test('text styles - height', () async { + await testTextStyle('height', height: 2); + }); + + test('text styles - leading distribution', () async { + await testTextStyle('half leading', + height: 20, + fontSize: 10, + leadingDistribution: ui.TextLeadingDistribution.even); + await testTextStyle( + 'half leading inherited from paragraph', + height: 20, + fontSize: 10, + paragraphTextHeightBehavior: const ui.TextHeightBehavior( + leadingDistribution: ui.TextLeadingDistribution.even, + ), + ); + await testTextStyle( + 'text style half leading overrides paragraph style half leading', + height: 20, + fontSize: 10, + leadingDistribution: ui.TextLeadingDistribution.proportional, + paragraphTextHeightBehavior: const ui.TextHeightBehavior( + leadingDistribution: ui.TextLeadingDistribution.even, + ), + ); + }); + + // TODO(yjbanov): locales specified in text styles don't work: + // https://github.com/flutter/flutter/issues/74687 + // TODO(yjbanov): spaces are not rendered correctly: + // https://github.com/flutter/flutter/issues/74742 + test('text styles - locale zh_CN', () async { + await testTextStyle('locale zh_CN', + innerText: '次 化 刃 直 入 令', + outerText: '', + locale: const ui.Locale('zh', 'CN')); + }, skip: kIssue86432Exists); + + test('text styles - locale zh_TW', () async { + await testTextStyle('locale zh_TW', + innerText: '次 化 刃 直 入 令', + outerText: '', + locale: const ui.Locale('zh', 'TW')); + }, skip: kIssue86432Exists); + + test('text styles - locale ja', () async { + await testTextStyle('locale ja', + innerText: '次 化 刃 直 入 令', + outerText: '', + locale: const ui.Locale('ja')); + }, skip: kIssue86432Exists); + + test('text styles - locale ko', () async { + await testTextStyle('locale ko', + innerText: '次 化 刃 直 入 令', + outerText: '', + locale: const ui.Locale('ko')); + }, skip: kIssue86432Exists); + + test('text styles - background', () async { + await testTextStyle('background', + background: ui.Paint()..color = const ui.Color(0xFF00FF00)); + }); + + test('text styles - foreground', () async { + await testTextStyle('foreground', + foreground: ui.Paint()..color = const ui.Color(0xFF0000FF)); + }); + + test('text styles - foreground and background', () async { + await testTextStyle( + 'foreground and background', + foreground: ui.Paint()..color = const ui.Color(0xFFFF5555), + background: ui.Paint()..color = const ui.Color(0xFF007700), + ); + }); + + test('text styles - background and color', () async { + await testTextStyle( + 'background and color', + color: const ui.Color(0xFFFFFF00), + background: ui.Paint()..color = const ui.Color(0xFF007700), + ); + }); + + test('text styles - shadows', () async { + await testTextStyle('shadows', shadows: [ + const ui.Shadow( + color: ui.Color(0xFF999900), + offset: ui.Offset(10, 10), + blurRadius: 5, + ), + const ui.Shadow( + color: ui.Color(0xFF009999), + offset: ui.Offset(-10, -10), + blurRadius: 10, + ), + ]); + }); + + test('text styles - old style figures', () async { + await testTextStyle( + 'old style figures', + paragraphFontFamily: 'Roboto', + paragraphFontSize: 24, + outerText: '0 1 2 3 4 5 ', + innerText: '0 1 2 3 4 5', + fontFeatures: [const ui.FontFeature.oldstyleFigures()], + ); + }); + + test('text styles - stylistic set 1', () async { + await testTextStyle( + 'stylistic set 1', + paragraphFontFamily: 'Roboto', + paragraphFontSize: 24, + outerText: 'g', + innerText: 'g', + fontFeatures: [ui.FontFeature.stylisticSet(1)], + ); + }); + + test('text styles - stylistic set 2', () async { + await testTextStyle( + 'stylistic set 2', + paragraphFontFamily: 'Roboto', + paragraphFontSize: 24, + outerText: 'α', + innerText: 'α', + fontFeatures: [ui.FontFeature.stylisticSet(2)], + ); + }); + + test('text styles - override font family', () async { + await testTextStyle( + 'override font family', + paragraphFontFamily: 'Ahem', + fontFamily: 'Roboto', + ); + }); + + test('text styles - override font size', () async { + await testTextStyle( + 'override font size', + paragraphFontSize: 36, + fontSize: 18, + ); + }); + + test('text style - override font weight', () async { + await testTextStyle( + 'override font weight', + paragraphFontWeight: ui.FontWeight.w900, + fontWeight: ui.FontWeight.normal, + ); + }); + + test('text style - override font style', () async { + await testTextStyle( + 'override font style', + paragraphFontStyle: ui.FontStyle.italic, + fontStyle: ui.FontStyle.normal, + ); + }); + + test('text style - characters from multiple fallback fonts', () async { + await testTextStyle( + 'multi-font characters', + // This character is claimed by multiple fonts. This test makes sure + // we can find a font supporting it. + outerText: '欢', + innerText: '', + ); + }, skip: kIssue86432Exists); + + test('text style - symbols', () async { + // One of the CJK fonts loaded in one of the tests above also contains + // some of these symbols. To make sure the test produces predictable + // results we reset the fallback data forcing the engine to reload + // fallbacks, which for this test will only load Noto Symbols. + await testTextStyle( + 'symbols', + outerText: '← ↑ → ↓ ', + innerText: '', + ); + }, skip: kIssue86432Exists); +} + +/// A convenience function for testing paragraph and text styles. +/// +/// Renders a paragraph with two pieces of text, [outerText] and [innerText]. +/// [outerText] is added to the root of the paragraph where only paragraph +/// style applies. [innerText] is added under a text style with properties +/// set from the arguments to this method. Parameters with prefix "paragraph" +/// are applied to the paragraph style. Others are applied to the text style. +/// +/// [name] is the name of the test used as the description on the golden as +/// well as in the golden file name. Avoid special characters. Spaces are OK; +/// they are replaced by "_" in the file name. +/// +/// Use [layoutWidth] to customize the width of the paragraph constraints. +Future testTextStyle( + // Test properties + String name, { + double? layoutWidth, + // Top-level text where only paragraph style applies + String outerText = 'Hello ', + // Second-level text where paragraph and text styles both apply. + String innerText = 'World!', + + // ParagraphStyle properties + ui.TextAlign? paragraphTextAlign, + ui.TextDirection? paragraphTextDirection, + int? paragraphMaxLines, + String? paragraphFontFamily, + double? paragraphFontSize, + double? paragraphHeight, + ui.TextHeightBehavior? paragraphTextHeightBehavior, + ui.FontWeight? paragraphFontWeight, + ui.FontStyle? paragraphFontStyle, + ui.StrutStyle? paragraphStrutStyle, + String? paragraphEllipsis, + ui.Locale? paragraphLocale, + + // TextStyle properties + ui.Color? color, + ui.TextDecoration? decoration, + ui.Color? decorationColor, + ui.TextDecorationStyle? decorationStyle, + double? decorationThickness, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.TextBaseline? textBaseline, + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? letterSpacing, + double? wordSpacing, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, +}) async { + late ui.Rect region; + ui.Picture renderPicture() { + const double testWidth = 512; + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + canvas.translate(30, 10); + final ui.ParagraphBuilder descriptionBuilder = + ui.ParagraphBuilder(ui.ParagraphStyle()); + descriptionBuilder.addText(name); + final ui.Paragraph descriptionParagraph = descriptionBuilder.build(); + descriptionParagraph + .layout(const ui.ParagraphConstraints(width: testWidth / 2 - 70)); + const ui.Offset descriptionOffset = ui.Offset(testWidth / 2 + 30, 0); + canvas.drawParagraph(descriptionParagraph, descriptionOffset); + + final ui.ParagraphBuilder pb = ui.ParagraphBuilder(ui.ParagraphStyle( + textAlign: paragraphTextAlign, + textDirection: paragraphTextDirection, + maxLines: paragraphMaxLines, + fontFamily: paragraphFontFamily, + fontSize: paragraphFontSize, + height: paragraphHeight, + textHeightBehavior: paragraphTextHeightBehavior, + fontWeight: paragraphFontWeight, + fontStyle: paragraphFontStyle, + strutStyle: paragraphStrutStyle, + ellipsis: paragraphEllipsis, + locale: paragraphLocale, + )); + + pb.addText(outerText); + + pb.pushStyle(ui.TextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background, + foreground: foreground, + shadows: shadows, + fontFeatures: fontFeatures, + )); + pb.addText(innerText); + pb.pop(); + final ui.Paragraph p = pb.build(); + p.layout(ui.ParagraphConstraints(width: layoutWidth ?? testWidth / 2)); + canvas.drawParagraph(p, ui.Offset.zero); + + canvas.drawPath( + ui.Path() + ..moveTo(-10, 0) + ..lineTo(-20, 0) + ..lineTo(-20, p.height) + ..lineTo(-10, p.height), + ui.Paint() + ..style = ui.PaintingStyle.stroke + ..strokeWidth = 1.0, + ); + canvas.drawPath( + ui.Path() + ..moveTo(testWidth / 2 + 10, 0) + ..lineTo(testWidth / 2 + 20, 0) + ..lineTo(testWidth / 2 + 20, p.height) + ..lineTo(testWidth / 2 + 10, p.height), + ui.Paint() + ..style = ui.PaintingStyle.stroke + ..strokeWidth = 1.0, + ); + const double padding = 20; + region = ui.Rect.fromLTRB( + 0, + 0, + testWidth, + math.max( + descriptionOffset.dy + descriptionParagraph.height + padding, + p.height + padding, + ), + ); + return recorder.endRecording(); + } + + // Render once to trigger font downloads. + renderPicture(); + await waitForFallbackFontsToStabilize(); + final ui.Picture picture = renderPicture(); + await drawPictureUsingCurrentRenderer(picture); + + await matchGoldenFile( + 'ui_text_styles_${name.replaceAll(' ', '_')}.png', + region: region, + ); +} diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index 38f976ad91f3f..f8e6d00b53aff 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -23,6 +23,25 @@ Future drawPictureUsingCurrentRenderer(Picture picture) async { await renderer.renderScene(sb.build()); } +Future waitForFallbackFontsToStabilize() async { + if (!isCanvasKit) { + return; + } + + // Fallback fonts start downloading as a post-frame callback. + CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); + // Font downloading begins asynchronously so we inject a timer before checking the download queue. + await Future.delayed(Duration.zero); + while (notoDownloadQueue.isPending || + notoDownloadQueue.downloader.debugActiveDownloadCount > 0) { + await notoDownloadQueue.debugWhenIdle(); + await notoDownloadQueue.downloader.debugWhenIdle(); + CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); + // Dummy timer for the same reason as above. + await Future.delayed(Duration.zero); + } +} + /// Returns [true] if this test is running in the CanvasKit renderer. bool get isCanvasKit => renderer is CanvasKitRenderer; From c685a4930cd0b4850f8d9d69f262d29680816614 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 1 May 2023 13:04:09 -0700 Subject: [PATCH 12/27] Set the right default color. --- lib/web_ui/skwasm/text/paragraph_style.cpp | 5 +++++ lib/web_ui/skwasm/text/text_style.cpp | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index 07c064def0636..92ccc9ac996e2 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -13,6 +13,11 @@ SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { // This is the default behavior in Flutter style->setReplaceTabCharacters(true); + // Default text style has a black color + TextStyle textStyle; + textStyle.setColor(SK_ColorBLACK); + style->setTextStyle(textStyle); + return style; } diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index fdc9b4c180fda..a6686ff767be5 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -8,7 +8,11 @@ using namespace skia::textlayout; SKWASM_EXPORT TextStyle* textStyle_create() { - return new TextStyle(); + auto style = new TextStyle(); + + // Default color in flutter is black. + style->setColor(SK_ColorBLACK); + return style; } SKWASM_EXPORT void textStyle_dispose(TextStyle* style) { From 2ab9c22c5bd95dcbea87b02039145062f2b4fbbd Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 4 May 2023 09:21:53 -0700 Subject: [PATCH 13/27] Fix analyzer issues. --- lib/web_ui/test/ui/text_golden_test.dart | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/web_ui/test/ui/text_golden_test.dart b/lib/web_ui/test/ui/text_golden_test.dart index 77e7d69bb4b9d..d06752a0ce28a 100644 --- a/lib/web_ui/test/ui/text_golden_test.dart +++ b/lib/web_ui/test/ui/text_golden_test.dart @@ -12,12 +12,6 @@ import 'package:web_engine_tester/golden_tester.dart'; import '../common/test_initialization.dart'; import 'utils.dart'; -// TODO(yjbanov): tests that render using Noto are not hermetic, as those fonts -// come from fonts.google.com, where fonts can change any time. -// These tests are skipped. -// https://github.com/flutter/flutter/issues/86432 -const bool kIssue86432Exists = true; - void main() { internalBootstrapBrowserTest(() => testMain); } @@ -103,28 +97,28 @@ Future testMain() async { outerText: '次 化 刃 直 入 令', innerText: '', paragraphLocale: const ui.Locale('zh', 'CN')); - }, skip: kIssue86432Exists); + }); test('text styles - paragraph locale zh_TW', () async { await testTextStyle('paragraph locale zh_TW', outerText: '次 化 刃 直 入 令', innerText: '', paragraphLocale: const ui.Locale('zh', 'TW')); - }, skip: kIssue86432Exists); + }); test('text styles - paragraph locale ja', () async { await testTextStyle('paragraph locale ja', outerText: '次 化 刃 直 入 令', innerText: '', paragraphLocale: const ui.Locale('ja')); - }, skip: kIssue86432Exists); + }); test('text styles - paragraph locale ko', () async { await testTextStyle('paragraph locale ko', outerText: '次 化 刃 直 入 令', innerText: '', paragraphLocale: const ui.Locale('ko')); - }, skip: kIssue86432Exists); + }); test('text styles - color', () async { await testTextStyle('color', color: const ui.Color(0xFF009900)); @@ -240,28 +234,28 @@ Future testMain() async { innerText: '次 化 刃 直 入 令', outerText: '', locale: const ui.Locale('zh', 'CN')); - }, skip: kIssue86432Exists); + }); test('text styles - locale zh_TW', () async { await testTextStyle('locale zh_TW', innerText: '次 化 刃 直 入 令', outerText: '', locale: const ui.Locale('zh', 'TW')); - }, skip: kIssue86432Exists); + }); test('text styles - locale ja', () async { await testTextStyle('locale ja', innerText: '次 化 刃 直 入 令', outerText: '', locale: const ui.Locale('ja')); - }, skip: kIssue86432Exists); + }); test('text styles - locale ko', () async { await testTextStyle('locale ko', innerText: '次 化 刃 直 入 令', outerText: '', locale: const ui.Locale('ko')); - }, skip: kIssue86432Exists); + }); test('text styles - background', () async { await testTextStyle('background', @@ -377,7 +371,7 @@ Future testMain() async { outerText: '欢', innerText: '', ); - }, skip: kIssue86432Exists); + }); test('text style - symbols', () async { // One of the CJK fonts loaded in one of the tests above also contains @@ -389,7 +383,7 @@ Future testMain() async { outerText: '← ↑ → ↓ ', innerText: '', ); - }, skip: kIssue86432Exists); + }); } /// A convenience function for testing paragraph and text styles. From 85d3e2719be6e780d5db4939fb4809d748cc4377 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Thu, 4 May 2023 15:02:46 -0700 Subject: [PATCH 14/27] Remove unused import. --- lib/web_ui/test/ui/paragraph_builder_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/test/ui/paragraph_builder_test.dart b/lib/web_ui/test/ui/paragraph_builder_test.dart index cb533d995e250..4139ebe593fa2 100644 --- a/lib/web_ui/test/ui/paragraph_builder_test.dart +++ b/lib/web_ui/test/ui/paragraph_builder_test.dart @@ -7,7 +7,6 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; import '../common/test_initialization.dart'; -import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); From ad4b204f8e1d5198dbd8a0419d1e30692dcdceaf Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Fri, 5 May 2023 17:32:01 -0700 Subject: [PATCH 15/27] Refactored font fallback stuff to prepare for skwasm. --- .../src/engine/canvaskit/font_fallbacks.dart | 445 ++++------- .../lib/src/engine/canvaskit/fonts.dart | 68 +- .../src/engine/canvaskit/interval_tree.dart | 8 +- .../lib/src/engine/canvaskit/noto_font.dart | 20 +- lib/web_ui/lib/src/engine/canvaskit/text.dart | 16 +- lib/web_ui/lib/src/engine/fonts.dart | 8 + .../skwasm/skwasm_impl/font_collection.dart | 51 ++ .../lib/src/engine/text/font_collection.dart | 7 + .../test/canvaskit/canvas_golden_test.dart | 709 +----------------- lib/web_ui/test/canvaskit/common.dart | 14 +- .../canvaskit/fallback_fonts_golden_test.dart | 195 ++--- .../test/canvaskit/interval_tree_test.dart | 46 +- .../test/common/test_initialization.dart | 2 +- lib/web_ui/test/ui/text_golden_test.dart | 3 +- lib/web_ui/test/ui/utils.dart | 19 - 15 files changed, 384 insertions(+), 1227 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart index 63827fcaeb1b6..d1ddb28c4c961 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart @@ -3,47 +3,47 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:typed_data'; import 'package:ui/src/engine.dart'; -/// Global static font fallback data. -class FontFallbackData { - - factory FontFallbackData() => - FontFallbackData._(getFallbackFontData(configuration.useColorEmoji)); +abstract class FallbackFontRegistry { + List getMissingCodePoints(List codePoints, List fontFamilies); + Future loadFallbackFont(String familyName, String string); + void updateFallbackFontFamilies(List families); +} - FontFallbackData._(this.fallbackFonts) : +/// Global static font fallback data. +class FontFallbackManager { + factory FontFallbackManager(FallbackFontRegistry registry) => + FontFallbackManager._( + registry, + getFallbackFontData(configuration.useColorEmoji) + ); + + 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'), - notoTree = createNotoFontTree(fallbackFonts); + notoTree = createNotoFontTree(fallbackFonts) { + downloadQueue = FallbackFontDownloadQueue(this); + } - static FontFallbackData get instance => _instance; - static FontFallbackData _instance = FontFallbackData(); + final FallbackFontRegistry registry; - /// Resets the fallback font data. - /// - /// After calling this method fallback fonts will be loaded from scratch. - /// - /// Used for tests. - static void debugReset() { - _instance = FontFallbackData(); - notoDownloadQueue = FallbackFontDownloadQueue(); - } + late final FallbackFontDownloadQueue downloadQueue; - /// Code units that no known font has a glyph for. - final Set codeUnitsWithNoKnownFont = {}; + /// Code points that no known font has a glyph for. + final Set codePointsWithNoKnownFont = {}; - /// Code units which are known to be covered by at least one fallback font. - final Set knownCoveredCodeUnits = {}; + /// Code points which are known to be covered by at least one fallback font. + final Set knownCoveredCodePoints = {}; final List fallbackFonts; - /// Index of all font families by code unit range. + /// Index of all font families by code point range. final IntervalTree notoTree; final NotoFont _notoSansSC; @@ -54,33 +54,40 @@ class FontFallbackData { final NotoFont _notoSymbols; + Future _idleFuture = Future.value(); + static IntervalTree createNotoFontTree(List fallbackFonts) { - final Map> ranges = - >{}; + final Map> ranges = + >{}; for (final NotoFont font in fallbackFonts) { // ignore: prefer_foreach - for (final CodeunitRange range in font.computeUnicodeRanges()) { - ranges.putIfAbsent(font, () => []).add(range); + for (final CodePointRange range in font.computeUnicodeRanges()) { + ranges.putIfAbsent(font, () => []).add(range); } } return IntervalTree.createFromRanges(ranges); } - /// Fallback fonts which have been registered and loaded. - final List registeredFallbackFonts = []; - final List globalFontFallbacks = ['Roboto']; - /// A list of code units to check against the global fallback fonts. - final Set _codeUnitsToCheckAgainstFallbackFonts = {}; + /// A list of code points to check against the global fallback fonts. + final Set _codePointsToCheckAgainstFallbackFonts = {}; - /// This is [true] if we have scheduled a check for missing code units. + /// This is [true] if we have scheduled a check for missing code points. /// /// We only do this once a frame, since checking if a font supports certain - /// code units is very expensive. - bool _scheduledCodeUnitCheck = false; + /// code points is very expensive. + bool _scheduledCodePointCheck = false; + + Future debugWhenIdle() { + if (assertionsEnabled) { + return _idleFuture; + } else { + throw UnimplementedError(); + } + } /// Determines if the given [text] contains any code points which are not /// supported by the current set of fonts. @@ -91,29 +98,17 @@ class FontFallbackData { return; } - // If the text is ASCII, then skip this check. - bool isAscii = true; - for (int i = 0; i < text.length; i++) { - if (text.codeUnitAt(i) >= 160) { - isAscii = false; - break; - } - } - if (isAscii) { - return; - } - - // We have a cache of code units which are known to be covered by at least - // one of our fallback fonts, and a cache of code units which are known not + // We have a cache of code points which are known to be covered by at least + // one of our fallback fonts, and a cache of code points which are known not // to be covered by any fallback font. From the given text, construct a set - // of code units which need to be checked. + // of code points which need to be checked. final Set runesToCheck = {}; for (final int rune in text.runes) { - // Filter out code units which ASCII, known to be covered, or known not - // to be covered. - if (!(rune < 160 || - knownCoveredCodeUnits.contains(rune) || - codeUnitsWithNoKnownFont.contains(rune))) { + // Filter out code points that don't need checking. + if (!(rune < 160 || // ASCII and Unicode control points. + knownCoveredCodePoints.contains(rune) || // Points we've already covered + codePointsWithNoKnownFont.contains(rune)) // Points that don't have a fallback font + ) { runesToCheck.add(rune); } } @@ -121,120 +116,40 @@ class FontFallbackData { return; } - final List codeUnits = runesToCheck.toList(); - - final List fonts = []; - for (final String font in fontFamilies) { - final List? typefacesForFamily = - CanvasKitRenderer.instance.fontCollection.familyToFontMap[font]; - if (typefacesForFamily != null) { - fonts.addAll(typefacesForFamily); - } - } - final List codeUnitsSupported = - List.filled(codeUnits.length, false); - final String testString = String.fromCharCodes(codeUnits); - for (final SkFont font in fonts) { - final Uint16List glyphs = font.getGlyphIDs(testString); - assert(glyphs.length == codeUnitsSupported.length); - for (int i = 0; i < glyphs.length; i++) { - codeUnitsSupported[i] |= glyphs[i] != 0 || _isControlCode(codeUnits[i]); - } - } - - if (codeUnitsSupported.any((bool x) => !x)) { - final List missingCodeUnits = []; - for (int i = 0; i < codeUnitsSupported.length; i++) { - if (!codeUnitsSupported[i]) { - missingCodeUnits.add(codeUnits[i]); - } - } - _codeUnitsToCheckAgainstFallbackFonts.addAll(missingCodeUnits); - if (!_scheduledCodeUnitCheck) { - _scheduledCodeUnitCheck = true; - CanvasKitRenderer.instance.rasterizer.addPostFrameCallback(_ensureFallbackFonts); + final List codePoints = runesToCheck.toList(); + final List missingCodePoints = + registry.getMissingCodePoints(codePoints, fontFamilies); + + if (missingCodePoints.isNotEmpty) { + _codePointsToCheckAgainstFallbackFonts.addAll(missingCodePoints); + if (!_scheduledCodePointCheck) { + _scheduledCodePointCheck = true; + _idleFuture = Future.delayed(Duration.zero, () async { + _ensureFallbackFonts(); + _scheduledCodePointCheck = false; + await downloadQueue.waitForIdle(); + }); } } } - /// Returns [true] if [codepoint] is a Unicode control code. - bool _isControlCode(int codepoint) { - return codepoint < 32 || (codepoint > 127 && codepoint < 160); - } - - /// Checks the missing code units against the current set of fallback fonts + /// Checks the missing code points against the current set of fallback fonts /// and starts downloading new fallback fonts if the current set can't cover - /// the code units. + /// the code points. void _ensureFallbackFonts() { - _scheduledCodeUnitCheck = false; - // We don't know if the remaining code units are covered by our fallback + _scheduledCodePointCheck = false; + // We don't know if the remaining code points are covered by our fallback // fonts. Check them and update the cache. - if (_codeUnitsToCheckAgainstFallbackFonts.isEmpty) { + if (_codePointsToCheckAgainstFallbackFonts.isEmpty) { return; } - final List codeUnits = _codeUnitsToCheckAgainstFallbackFonts.toList(); - _codeUnitsToCheckAgainstFallbackFonts.clear(); - final List codeUnitsSupported = - List.filled(codeUnits.length, false); - final String testString = String.fromCharCodes(codeUnits); - - for (final String font in globalFontFallbacks) { - final List? fontsForFamily = - CanvasKitRenderer.instance.fontCollection.familyToFontMap[font]; - if (fontsForFamily == null) { - printWarning('A fallback font was registered but we ' - 'cannot retrieve the typeface for it.'); - continue; - } - for (final SkFont font in fontsForFamily) { - final Uint16List glyphs = font.getGlyphIDs(testString); - assert(glyphs.length == codeUnitsSupported.length); - for (int i = 0; i < glyphs.length; i++) { - final bool codeUnitSupported = glyphs[i] != 0; - if (codeUnitSupported) { - knownCoveredCodeUnits.add(codeUnits[i]); - } - codeUnitsSupported[i] |= - codeUnitSupported || _isControlCode(codeUnits[i]); - } - } - - // Once we've checked every typeface for this family, check to see if - // every code unit has been covered in order to avoid unnecessary checks. - bool keepGoing = false; - for (final bool supported in codeUnitsSupported) { - if (!supported) { - keepGoing = true; - break; - } - } - - if (!keepGoing) { - return; - } - } - - // If we reached here, then there are some code units which aren't covered - // by the global fallback fonts. Remove the ones which were covered and - // try to find fallback fonts which cover them. - for (int i = codeUnits.length - 1; i >= 0; i--) { - if (codeUnitsSupported[i]) { - codeUnits.removeAt(i); - } - } - findFontsForMissingCodeunits(codeUnits); + final List codePoints = _codePointsToCheckAgainstFallbackFonts.toList(); + _codePointsToCheckAgainstFallbackFonts.clear(); + final List missingCodePoints = registry.getMissingCodePoints(codePoints, globalFontFallbacks); + findFontsForMissingCodePoints(missingCodePoints); } - void registerFallbackFont(String family, Uint8List bytes) { - final SkTypeface? typeface = - canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer); - if (typeface == null) { - printWarning('Failed to parse fallback font $family as a font.'); - return; - } - // Insert emoji font before all other fallback fonts so we use the emoji - // whenever it's available. - registeredFallbackFonts.add(RegisteredFont(bytes, family, typeface)); + void registerFallbackFont(String family) { // Insert emoji font before all other fallback fonts so we use the emoji // whenever it's available. if (family == 'Noto Color Emoji' || family == 'Noto Emoji') { @@ -248,79 +163,77 @@ class FontFallbackData { } } - Future findFontsForMissingCodeunits(List codeUnits) async { - final FontFallbackData data = FontFallbackData.instance; - + void findFontsForMissingCodePoints(List codePoints) { Set fonts = {}; - final Set coveredCodeUnits = {}; - final Set missingCodeUnits = {}; - for (final int codeUnit in codeUnits) { - final List fontsForUnit = data.notoTree.intersections(codeUnit); - fonts.addAll(fontsForUnit); - if (fontsForUnit.isNotEmpty) { - coveredCodeUnits.add(codeUnit); + final Set coveredCodePoints = {}; + final Set missingCodePoints = {}; + for (final int codePoint in codePoints) { + final List fontsForPoint = notoTree.intersections(codePoint); + fonts.addAll(fontsForPoint); + if (fontsForPoint.isNotEmpty) { + coveredCodePoints.add(codePoint); } else { - missingCodeUnits.add(codeUnit); + missingCodePoints.add(codePoint); } } - // The call to `findMinimumFontsForCodeUnits` will remove all code units that - // were matched by `fonts` from `unmatchedCodeUnits`. - final Set unmatchedCodeUnits = Set.from(coveredCodeUnits); - fonts = findMinimumFontsForCodeUnits(unmatchedCodeUnits, fonts); + // The call to `findMinimumFontsForCodePoints` will remove all code points that + // were matched by `fonts` from `unmatchedCodePoints`. + final Set unmatchedCodePoints = Set.from(coveredCodePoints); + fonts = findMinimumFontsForCodePoints(unmatchedCodePoints, fonts); - fonts.forEach(notoDownloadQueue.add); + fonts.forEach(downloadQueue.add); // We looked through the Noto font tree and didn't find any font families - // covering some code units. - if (missingCodeUnits.isNotEmpty || unmatchedCodeUnits.isNotEmpty) { - if (!notoDownloadQueue.isPending) { + // covering some code points. + if (missingCodePoints.isNotEmpty || unmatchedCodePoints.isNotEmpty) { + if (!downloadQueue.isPending) { printWarning('Could not find a set of Noto fonts to display all missing ' 'characters. Please add a font asset for the missing characters.' ' See: https://flutter.dev/docs/cookbook/design/fonts'); - data.codeUnitsWithNoKnownFont.addAll(missingCodeUnits); + codePointsWithNoKnownFont.addAll(missingCodePoints); } } } - /// Finds the minimum set of fonts which covers all of the [codeUnits]. + /// Finds the minimum set of fonts which covers all of the [codePoints]. /// - /// Removes all code units covered by [fonts] from [codeUnits]. The code - /// units remaining in the [codeUnits] set after calling this function do not + /// Removes all code points covered by [fonts] from [codePoints]. The code + /// points remaining in the [codePoints] set after calling this function do not /// have a font that covers them and can be omitted next time to avoid /// searching for fonts unnecessarily. /// /// Since set cover is NP-complete, we approximate using a greedy algorithm - /// which finds the font which covers the most code units. If multiple CJK - /// fonts match the same number of code units, we choose one based on the user's + /// which finds the font which covers the most code points. If multiple CJK + /// fonts match the same number of code points, we choose one based on the user's /// locale. - Set findMinimumFontsForCodeUnits( - Set codeUnits, Set fonts) { - assert(fonts.isNotEmpty || codeUnits.isEmpty); + Set findMinimumFontsForCodePoints( + Set codePoints, Set fonts) { + assert(fonts.isNotEmpty || codePoints.isEmpty); final Set minimumFonts = {}; final List bestFonts = []; final String language = domWindow.navigator.language; - while (codeUnits.isNotEmpty) { - int maxCodeUnitsCovered = 0; + while (codePoints.isNotEmpty) { + int maxCodePointsCovered = 0; bestFonts.clear(); for (final NotoFont font in fonts) { - int codeUnitsCovered = 0; - for (final int codeUnit in codeUnits) { - if (font.contains(codeUnit)) { - codeUnitsCovered++; + int codePointsCovered = 0; + for (final int codePoint in codePoints) { + if (font.contains(codePoint)) { + codePointsCovered++; } } - if (codeUnitsCovered > maxCodeUnitsCovered) { + if (codePointsCovered > maxCodePointsCovered) { bestFonts.clear(); bestFonts.add(font); - maxCodeUnitsCovered = codeUnitsCovered; - } else if (codeUnitsCovered == maxCodeUnitsCovered) { + maxCodePointsCovered = codePointsCovered; + } else if (codePointsCovered == maxCodePointsCovered) { bestFonts.add(font); } } - if (maxCodeUnitsCovered == 0) { + if (maxCodePointsCovered == 0) { // Fonts cannot cover remaining unmatched characters. break; } @@ -373,8 +286,8 @@ class FontFallbackData { } } } - codeUnits.removeWhere((int codeUnit) { - return bestFont.contains(codeUnit); + codePoints.removeWhere((int codePoint) { + return bestFont.contains(codePoint); }); minimumFonts.add(bestFont); } @@ -383,32 +296,29 @@ class FontFallbackData { } class FallbackFontDownloadQueue { - NotoDownloader downloader = NotoDownloader(); + FallbackFontDownloadQueue(this.fallbackManager); + + final FontFallbackManager fallbackManager; + + static const String _defaultFallbackFontsUrlPrefix = 'https://fonts.gstatic.com/s/'; + String? fallbackFontUrlPrefixOverride; + String get fallbackFontUrlPrefix => fallbackFontUrlPrefixOverride ?? _defaultFallbackFontsUrlPrefix; final Set downloadedFonts = {}; final Map pendingFonts = {}; - bool get isPending => pendingFonts.isNotEmpty || _fontsLoading != null; + bool get isPending => pendingFonts.isNotEmpty; - Future? _fontsLoading; - bool get debugIsLoadingFonts => _fontsLoading != null; + void Function(String family)? debugOnLoadFontFamily; - Future debugWhenIdle() async { - if (assertionsEnabled) { - await Future.delayed(Duration.zero); - while (isPending) { - if (_fontsLoading != null) { - await _fontsLoading; - } - if (pendingFonts.isNotEmpty) { - await Future.delayed(const Duration(milliseconds: 100)); - if (pendingFonts.isEmpty) { - await Future.delayed(const Duration(milliseconds: 100)); - } - } - } + Completer? _idleCompleter; + + Future waitForIdle() { + if (_idleCompleter == null) { + // We're already idle + return Future.value(); } else { - throw UnimplementedError(); + return _idleCompleter!.future; } } @@ -419,6 +329,7 @@ class FallbackFontDownloadQueue { } final bool firstInBatch = pendingFonts.isEmpty; pendingFonts[font.url] = font; + _idleCompleter ??= Completer(); if (firstInBatch) { Timer.run(startDownloads); } @@ -426,13 +337,14 @@ class FallbackFontDownloadQueue { Future startDownloads() async { final Map> downloads = >{}; - final Map downloadedData = {}; + final List downloadedFontFamilies = []; for (final NotoFont font in pendingFonts.values) { downloads[font.url] = Future(() async { - ByteBuffer buffer; try { - buffer = await downloader.downloadAsBytes(font.url, - debugDescription: font.name); + final String url = '$fallbackFontUrlPrefix${font.url}'; + debugOnLoadFontFamily?.call(font.name); + await fallbackManager.registry.loadFallbackFont(font.name, url); + downloadedFontFamilies.add(font.url); } catch (e) { pendingFonts.remove(font.url); printWarning('Failed to load font ${font.name} at ${font.url}'); @@ -440,7 +352,6 @@ class FallbackFontDownloadQueue { return; } downloadedFonts.add(font); - downloadedData[font.url] = buffer.asUint8List(); }); } @@ -449,86 +360,22 @@ class FallbackFontDownloadQueue { // Register fallback fonts in a predictable order. Otherwise, the fonts // change their precedence depending on the download order causing // visual differences between app reloads. - final List downloadOrder = - (downloadedData.keys.toList()..sort()).reversed.toList(); - for (final String url in downloadOrder) { + downloadedFontFamilies.sort(); + for (final String url in downloadedFontFamilies) { final NotoFont font = pendingFonts.remove(url)!; - final Uint8List bytes = downloadedData[url]!; - FontFallbackData.instance.registerFallbackFont(font.name, bytes); - if (pendingFonts.isEmpty) { - (renderer.fontCollection as SkiaFontCollection).registerDownloadedFonts(); - sendFontChangeMessage(); - } - } - - if (pendingFonts.isNotEmpty) { - await startDownloads(); + fallbackManager.registerFallbackFont(font.name); } - } -} -class NotoDownloader { - int get debugActiveDownloadCount => _debugActiveDownloadCount; - int _debugActiveDownloadCount = 0; - - static const String _defaultFallbackFontsUrlPrefix = 'https://fonts.gstatic.com/s/'; - String? fallbackFontUrlPrefixOverride; - String get fallbackFontUrlPrefix => fallbackFontUrlPrefixOverride ?? _defaultFallbackFontsUrlPrefix; - - /// Returns a future that resolves when there are no pending downloads. - /// - /// Useful in tests to make sure that fonts are loaded before working with - /// text. - Future debugWhenIdle() async { - if (assertionsEnabled) { - // Some downloads begin asynchronously in a microtask or in a Timer.run. - // Let those run before waiting for downloads to finish. - await Future.delayed(Duration.zero); - while (_debugActiveDownloadCount > 0) { - await Future.delayed(const Duration(milliseconds: 100)); - // If we started with a non-zero count and hit zero while waiting, wait a - // little more to make sure another download doesn't get chained after - // the last one (e.g. font file download after font CSS download). - if (_debugActiveDownloadCount == 0) { - await Future.delayed(const Duration(milliseconds: 100)); - } - } + if (pendingFonts.isEmpty) { + fallbackManager.registry.updateFallbackFontFamilies( + fallbackManager.globalFontFallbacks + ); + sendFontChangeMessage(); + final Completer idleCompleter = _idleCompleter!; + _idleCompleter = null; + idleCompleter.complete(); } else { - throw UnimplementedError(); - } - } - - /// Downloads the [url] and returns it as a [ByteBuffer]. - /// - /// Override this for testing. - Future downloadAsBytes(String url, {String? debugDescription}) async { - if (assertionsEnabled) { - _debugActiveDownloadCount += 1; - } - final Future data = httpFetchByteBuffer('$fallbackFontUrlPrefix$url'); - if (assertionsEnabled) { - unawaited(data.whenComplete(() { - _debugActiveDownloadCount -= 1; - })); - } - return data; - } - - /// Downloads the [url] and returns is as a [String]. - /// - /// Override this for testing. - Future downloadAsString(String url, {String? debugDescription}) async { - if (assertionsEnabled) { - _debugActiveDownloadCount += 1; - } - final Future data = httpFetchText('$fallbackFontUrlPrefix$url'); - if (assertionsEnabled) { - unawaited(data.whenComplete(() { - _debugActiveDownloadCount -= 1; - })); + await startDownloads(); } - return data; } } - -FallbackFontDownloadQueue notoDownloadQueue = FallbackFontDownloadQueue(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 78121dc553b59..4e1dfc0d1beec 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -19,6 +19,10 @@ const String _robotoUrl = class SkiaFontCollection implements FlutterFontCollection { final Set _downloadedFontFamilies = {}; + @override + late FontFallbackManager fontFallbackManager = + FontFallbackManager(SkiaFallbackRegistry(this)); + /// Fonts that started the download process, but are not yet registered. /// /// /// Once downloaded successfully, this map is cleared and the resulting @@ -26,6 +30,7 @@ class SkiaFontCollection implements FlutterFontCollection { final List _unregisteredFonts = []; final List _registeredFonts = []; + final List registeredFallbackFonts = []; /// Returns fonts that have been downloaded, registered, and parsed. /// @@ -59,8 +64,7 @@ class SkiaFontCollection implements FlutterFontCollection { .add(SkFont(font.typeface)); } - for (final RegisteredFont font - in FontFallbackData.instance.registeredFallbackFonts) { + for (final RegisteredFont font in registeredFallbackFonts) { _fontProvider!.registerFont(font.bytes, font.family); familyToFontMap .putIfAbsent(font.family, () => []) @@ -216,6 +220,12 @@ class SkiaFontCollection implements FlutterFontCollection { @override void clear() {} + + @override + void debugResetFallbackFonts() { + fontFallbackManager = FontFallbackManager(SkiaFallbackRegistry(this)); + registeredFallbackFonts.clear(); + } } /// Represents a font that has been registered. @@ -254,3 +264,57 @@ class FontDownloadResult { final UnregisteredFont? font; final FontLoadError? error; } + +class SkiaFallbackRegistry implements FallbackFontRegistry { + SkiaFallbackRegistry(this.fontCollection); + + SkiaFontCollection fontCollection; + + @override + List getMissingCodePoints(List codeUnits, List fontFamilies) { + final List fonts = []; + for (final String font in fontFamilies) { + final List? typefacesForFamily = fontCollection.familyToFontMap[font]; + if (typefacesForFamily != null) { + fonts.addAll(typefacesForFamily); + } + } + final List codePointsSupported = + List.filled(codeUnits.length, false); + final String testString = String.fromCharCodes(codeUnits); + for (final SkFont font in fonts) { + final Uint16List glyphs = font.getGlyphIDs(testString); + assert(glyphs.length == codePointsSupported.length); + for (int i = 0; i < glyphs.length; i++) { + codePointsSupported[i] |= glyphs[i] != 0; + } + } + + final List missingCodeUnits = []; + for (int i = 0; i < codePointsSupported.length; i++) { + if (!codePointsSupported[i]) { + missingCodeUnits.add(codeUnits[i]); + } + } + return missingCodeUnits; + } + + @override + Future loadFallbackFont(String familyName, String url) async { + final ByteBuffer buffer = await httpFetchByteBuffer(url); + final SkTypeface? typeface = + canvasKit.Typeface.MakeFreeTypeFaceFromData(buffer); + if (typeface == null) { + printWarning('Failed to parse fallback font $familyName as a font.'); + return; + } + fontCollection.registeredFallbackFonts.add( + RegisteredFont(buffer.asUint8List(), familyName, typeface) + ); + } + + @override + void updateFallbackFontFamilies(List families) { + fontCollection.registerDownloadedFonts(); + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart index 59657e7032ee3..f037ae277815f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart @@ -4,7 +4,7 @@ import 'dart:math' as math; -import 'noto_font.dart' show CodeunitRange; +import 'noto_font.dart' show CodePointRange; /// A tree which stores a set of intervals that can be queried for intersection. class IntervalTree { @@ -14,12 +14,12 @@ class IntervalTree { /// /// When the interval tree is queried, it will return a list of [T]s which /// have a range which contains the point. - factory IntervalTree.createFromRanges(Map> rangesMap) { + factory IntervalTree.createFromRanges(Map> rangesMap) { assert(rangesMap.isNotEmpty); // Get a list of all the ranges ordered by start index. final List> intervals = >[]; - rangesMap.forEach((T key, List rangeList) { - for (final CodeunitRange range in rangeList) { + rangesMap.forEach((T key, List rangeList) { + for (final CodePointRange range in rangeList) { intervals.add(IntervalTreeNode(key, range.start, range.end)); } }); diff --git a/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart b/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart index f58c2dd0efbfa..b16fbaa51c23e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart @@ -11,9 +11,9 @@ class NotoFont { final String url; final String _packedRanges; // A sorted list of Unicode ranges. - late final List _ranges = _unpackFontRange(_packedRanges); + late final List _ranges = _unpackFontRange(_packedRanges); - List computeUnicodeRanges() => _ranges; + List computeUnicodeRanges() => _ranges; // Returns `true` if this font has a glyph for the given [codeunit]. bool contains(int codeUnit) { @@ -23,7 +23,7 @@ class NotoFont { int max = _ranges.length - 1; while (min <= max) { final int mid = (min + max) ~/ 2; - final CodeunitRange range = _ranges[mid]; + final CodePointRange range = _ranges[mid]; if (range.start > codeUnit) { max = mid - 1; } else { @@ -38,8 +38,8 @@ class NotoFont { } } -class CodeunitRange { - const CodeunitRange(this.start, this.end); +class CodePointRange { + const CodePointRange(this.start, this.end); final int start; final int end; @@ -50,10 +50,10 @@ class CodeunitRange { @override bool operator ==(Object other) { - if (other is! CodeunitRange) { + if (other is! CodePointRange) { return false; } - final CodeunitRange range = other; + final CodePointRange range = other; return range.start == start && range.end == end; } @@ -73,15 +73,15 @@ class MutableInt { int value; } -List _unpackFontRange(String packedRange) { +List _unpackFontRange(String packedRange) { final MutableInt i = MutableInt(0); - final List ranges = []; + final List ranges = []; while (i.value < packedRange.length) { final int rangeStart = _consumeInt36(packedRange, i, until: _kCharPipe); final int rangeLength = _consumeInt36(packedRange, i, until: _kCharSemicolon); final int rangeEnd = rangeStart + rangeLength; - ranges.add(CodeunitRange(rangeStart, rangeEnd)); + ranges.add(CodePointRange(rangeStart, rangeEnd)); } return ranges; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 815012dcd84ef..fa2dec9a3f999 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -5,17 +5,9 @@ import 'dart:typed_data'; import 'package:meta/meta.dart'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../util.dart'; -import 'canvaskit_api.dart'; -import 'font_fallbacks.dart'; -import 'native_memory.dart'; -import 'painting.dart'; -import 'renderer.dart'; -import 'text_fragmenter.dart'; -import 'util.dart'; - final bool _ckRequiresClientICU = canvasKit.ParagraphBuilder.RequiresClientICU(); final List _testFonts = ['FlutterTest', 'Ahem']; @@ -866,7 +858,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { if (style.fontFamilyFallback != null) { fontFamilies.addAll(style.fontFamilyFallback!); } - FontFallbackData.instance.ensureFontsSupportText(text, fontFamilies); + renderer.fontCollection.fontFallbackManager!.ensureFontsSupportText(text, fontFamilies); _paragraphBuilder.addText(text); } @@ -975,6 +967,8 @@ List _getEffectiveFontFamilies(String? fontFamily, !fontFamilyFallback.every((String font) => fontFamily == font)) { fontFamilies.addAll(fontFamilyFallback); } - fontFamilies.addAll(FontFallbackData.instance.globalFontFallbacks); + fontFamilies.addAll( + renderer.fontCollection.fontFallbackManager!.globalFontFallbacks + ); return fontFamilies; } diff --git a/lib/web_ui/lib/src/engine/fonts.dart b/lib/web_ui/lib/src/engine/fonts.dart index 0e401d6fa84fb..07c4154891f2b 100644 --- a/lib/web_ui/lib/src/engine/fonts.dart +++ b/lib/web_ui/lib/src/engine/fonts.dart @@ -124,6 +124,14 @@ abstract class FlutterFontCollection { /// Completes when fonts from FontManifest.json have been loaded. Future loadAssetFonts(FontManifest manifest); + // The font fallback manager for this font collection. HTML renderer doesn't + // have a font fallback manager and just relies on the browser to fall back + // properly. + FontFallbackManager? get fontFallbackManager; + + // Reset the state of font fallbacks. Only to be used in testing. + void debugResetFallbackFonts(); + // Unregisters all fonts. void clear(); } 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 845f7c147a3d3..f9343c62a806f 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 @@ -24,6 +24,10 @@ class SkwasmFontCollection implements FlutterFontCollection { FontCollectionHandle handle; + @override + late final FontFallbackManager fontFallbackManager = + FontFallbackManager(SkwasmFallbackRegistry(this)); + @override void clear() { fontCollectionDispose(handle); @@ -104,6 +108,28 @@ class SkwasmFontCollection implements FlutterFontCollection { return null; } + Future loadFontFromUrl(String familyName, String url) async { + final HttpFetchResponse response = await httpFetch(url); + int length = 0; + final List chunks = []; + await response.read((JSUint8Array1 chunk) { + length += chunk.length.toDart.toInt(); + chunks.add(chunk); + }); + final SkDataHandle fontData = skDataCreate(length); + int dataAddress = skDataGetPointer(fontData).cast().address; + final JSUint8Array1 wasmMemory = createUint8ArrayFromBuffer(skwasmInstance.wasmMemory.buffer); + for (final JSUint8Array1 chunk in chunks) { + wasmMemory.set(chunk, dataAddress.toJS); + dataAddress += chunk.length.toDart.toInt(); + } + final SkStringHandle familyNameHandle = skStringFromDartString(familyName); + final bool result = fontCollectionRegisterFont(handle, fontData, familyNameHandle); + skStringFree(familyNameHandle); + skDataDispose(fontData); + return result; + } + @override Future loadFontFromList(Uint8List list, {String? fontFamily}) async { final SkDataHandle dataHandle = skDataCreate(list.length); @@ -122,4 +148,29 @@ class SkwasmFontCollection implements FlutterFontCollection { skDataDispose(dataHandle); return success; } + + @override + void debugResetFallbackFonts() { + // TODO: implement debugResetFallbackFonts + } +} + +class SkwasmFallbackRegistry implements FallbackFontRegistry { + SkwasmFallbackRegistry(this.fontCollection); + + final SkwasmFontCollection fontCollection; + @override + List getMissingCodePoints(List codePoints, List fontFamilies) { + // TODO: implement getMissingCodePoints + throw UnimplementedError(); + } + + @override + Future loadFallbackFont(String familyName, String url) => + fontCollection.loadFontFromUrl(familyName, url); + + @override + void updateFallbackFontFamilies(List families) { + // TODO: implement updateFallbackFontFamilies + } } diff --git a/lib/web_ui/lib/src/engine/text/font_collection.dart b/lib/web_ui/lib/src/engine/text/font_collection.dart index c6666183a3fbf..bf1612ff2dcae 100644 --- a/lib/web_ui/lib/src/engine/text/font_collection.dart +++ b/lib/web_ui/lib/src/engine/text/font_collection.dart @@ -51,6 +51,9 @@ class HtmlFontCollection implements FlutterFontCollection { return _loadFontFaceBytes(fontFamily, list); } + @override + Null get fontFallbackManager => null; + /// Unregister all fonts that have been registered. @override void clear() { @@ -171,4 +174,8 @@ class HtmlFontCollection implements FlutterFontCollection { } return true; } + + @override + void debugResetFallbackFonts() { + } } diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index f58485f1f0124..234f4bf27594c 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -25,16 +25,8 @@ void testMain() { setUpCanvasKitTest(); setUp(() { - expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0); - expect(notoDownloadQueue.isPending, isFalse); - - FontFallbackData.debugReset(); - notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; - }); - - tearDown(() { - expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0); - expect(notoDownloadQueue.isPending, isFalse); + renderer.fontCollection.debugResetFallbackFonts(); + renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; }); test('renders using non-recording canvas if weak refs are supported', @@ -50,369 +42,6 @@ void testMain() { ); }); - test('text styles - default', () async { - await testTextStyle('default'); - }); - - test('text styles - center aligned', () async { - await testTextStyle('center aligned', - paragraphTextAlign: ui.TextAlign.center); - }); - - test('text styles - right aligned', () async { - await testTextStyle('right aligned', - paragraphTextAlign: ui.TextAlign.right); - }); - - test('text styles - rtl', () async { - await testTextStyle('rtl', paragraphTextDirection: ui.TextDirection.rtl); - }); - - test('text styles - multiline', () async { - await testTextStyle('multiline', layoutWidth: 50); - }); - - test('text styles - max lines', () async { - await testTextStyle('max lines', paragraphMaxLines: 1, layoutWidth: 50); - }); - - test('text styles - ellipsis', () async { - await testTextStyle('ellipsis', - paragraphMaxLines: 1, paragraphEllipsis: '...', layoutWidth: 60); - }); - - test('text styles - paragraph font family', () async { - await testTextStyle('paragraph font family', paragraphFontFamily: 'Ahem'); - }); - - test('text styles - paragraph font size', () async { - await testTextStyle('paragraph font size', paragraphFontSize: 22); - }); - - test('text styles - paragraph height', () async { - await testTextStyle('paragraph height', - layoutWidth: 50, paragraphHeight: 1.5); - }); - - test('text styles - paragraph text height behavior', () async { - await testTextStyle('paragraph text height behavior', - layoutWidth: 50, - paragraphHeight: 1.5, - paragraphTextHeightBehavior: const ui.TextHeightBehavior( - applyHeightToFirstAscent: false, - applyHeightToLastDescent: false, - )); - }); - - test('text styles - paragraph weight', () async { - await testTextStyle('paragraph weight', - paragraphFontWeight: ui.FontWeight.w900); - }); - - test('text style - paragraph font style', () async { - await testTextStyle( - 'paragraph font style', - paragraphFontStyle: ui.FontStyle.italic, - ); - }); - - // TODO(yjbanov): locales specified in paragraph styles don't work: - // https://github.com/flutter/flutter/issues/74687 - // TODO(yjbanov): spaces are not rendered correctly: - // https://github.com/flutter/flutter/issues/74742 - test('text styles - paragraph locale zh_CN', () async { - await testTextStyle('paragraph locale zh_CN', - outerText: '次 化 刃 直 入 令', - innerText: '', - paragraphLocale: const ui.Locale('zh', 'CN')); - }); - - test('text styles - paragraph locale zh_TW', () async { - await testTextStyle('paragraph locale zh_TW', - outerText: '次 化 刃 直 入 令', - innerText: '', - paragraphLocale: const ui.Locale('zh', 'TW')); - }); - - test('text styles - paragraph locale ja', () async { - await testTextStyle('paragraph locale ja', - outerText: '次 化 刃 直 入 令', - innerText: '', - paragraphLocale: const ui.Locale('ja')); - }); - - test('text styles - paragraph locale ko', () async { - await testTextStyle('paragraph locale ko', - outerText: '次 化 刃 直 入 令', - innerText: '', - paragraphLocale: const ui.Locale('ko')); - }); - - test('text styles - color', () async { - await testTextStyle('color', color: const ui.Color(0xFF009900)); - }); - - test('text styles - decoration', () async { - await testTextStyle('decoration', - decoration: ui.TextDecoration.underline); - }); - - test('text styles - decoration style', () async { - await testTextStyle('decoration style', - decoration: ui.TextDecoration.underline, - decorationStyle: ui.TextDecorationStyle.dashed); - }); - - test('text styles - decoration thickness', () async { - await testTextStyle('decoration thickness', - decoration: ui.TextDecoration.underline, decorationThickness: 5.0); - }); - - test('text styles - font weight', () async { - await testTextStyle('font weight', fontWeight: ui.FontWeight.w900); - }); - - test('text styles - font style', () async { - await testTextStyle('font style', fontStyle: ui.FontStyle.italic); - }); - - // TODO(yjbanov): not sure how to test this. - test('text styles - baseline', () async { - await testTextStyle('baseline', - textBaseline: ui.TextBaseline.ideographic); - }); - - test('text styles - font family', () async { - await testTextStyle('font family', fontFamily: 'Ahem'); - }); - - test('text styles - non-existent font family', () async { - await testTextStyle('non-existent font family', - fontFamily: 'DoesNotExist'); - }); - - test('text styles - family fallback', () async { - await testTextStyle('family fallback', - fontFamily: 'DoesNotExist', fontFamilyFallback: ['Ahem']); - }); - - test('text styles - font size', () async { - await testTextStyle('font size', fontSize: 24); - }); - - // A regression test for the special case when CanvasKit would default to - // a positive font size when Flutter specifies zero. - // - // See: https://github.com/flutter/flutter/issues/98248 - test('text styles - zero font size', () async { - // This only sets the inner text style, but not the paragraph style, so - // "Hello" should be visible, but "World!" should disappear. - await testTextStyle('zero font size', fontSize: 0); - - // This sets the paragraph font size to zero, but the inner text gets - // an explicit non-zero size that should override paragraph properties, - // so this time "Hello" should disappear, but "World!" should still be - // visible. - await testTextStyle('zero paragraph font size', paragraphFontSize: 0, fontSize: 14); - }); - - test('text styles - letter spacing', () async { - await testTextStyle('letter spacing', letterSpacing: 5); - }); - - test('text styles - word spacing', () async { - await testTextStyle('word spacing', - innerText: 'Beautiful World!', wordSpacing: 25); - }); - - test('text styles - height', () async { - await testTextStyle('height', height: 2); - }); - - test('text styles - leading distribution', () async { - await testTextStyle('half leading', - height: 20, - fontSize: 10, - leadingDistribution: ui.TextLeadingDistribution.even); - await testTextStyle( - 'half leading inherited from paragraph', - height: 20, - fontSize: 10, - paragraphTextHeightBehavior: const ui.TextHeightBehavior( - leadingDistribution: ui.TextLeadingDistribution.even, - ), - ); - await testTextStyle( - 'text style half leading overrides paragraph style half leading', - height: 20, - fontSize: 10, - leadingDistribution: ui.TextLeadingDistribution.proportional, - paragraphTextHeightBehavior: const ui.TextHeightBehavior( - leadingDistribution: ui.TextLeadingDistribution.even, - ), - ); - }); - - // TODO(yjbanov): locales specified in text styles don't work: - // https://github.com/flutter/flutter/issues/74687 - // TODO(yjbanov): spaces are not rendered correctly: - // https://github.com/flutter/flutter/issues/74742 - test('text styles - locale zh_CN', () async { - await testTextStyle('locale zh_CN', - innerText: '次 化 刃 直 入 令', - outerText: '', - locale: const ui.Locale('zh', 'CN')); - }); - - test('text styles - locale zh_TW', () async { - await testTextStyle('locale zh_TW', - innerText: '次 化 刃 直 入 令', - outerText: '', - locale: const ui.Locale('zh', 'TW')); - }); - - test('text styles - locale ja', () async { - await testTextStyle('locale ja', - innerText: '次 化 刃 直 入 令', - outerText: '', - locale: const ui.Locale('ja')); - }); - - test('text styles - locale ko', () async { - await testTextStyle('locale ko', - innerText: '次 化 刃 直 入 令', - outerText: '', - locale: const ui.Locale('ko')); - }); - - test('text styles - background', () async { - await testTextStyle('background', - background: CkPaint()..color = const ui.Color(0xFF00FF00)); - }); - - test('text styles - foreground', () async { - await testTextStyle('foreground', - foreground: CkPaint()..color = const ui.Color(0xFF0000FF)); - }); - - test('text styles - foreground and background', () async { - await testTextStyle( - 'foreground and background', - foreground: CkPaint()..color = const ui.Color(0xFFFF5555), - background: CkPaint()..color = const ui.Color(0xFF007700), - ); - }); - - test('text styles - background and color', () async { - await testTextStyle( - 'background and color', - color: const ui.Color(0xFFFFFF00), - background: CkPaint()..color = const ui.Color(0xFF007700), - ); - }); - - test('text styles - shadows', () async { - await testTextStyle('shadows', shadows: [ - const ui.Shadow( - color: ui.Color(0xFF999900), - offset: ui.Offset(10, 10), - blurRadius: 5, - ), - const ui.Shadow( - color: ui.Color(0xFF009999), - offset: ui.Offset(-10, -10), - blurRadius: 10, - ), - ]); - }); - - test('text styles - old style figures', () async { - await testTextStyle( - 'old style figures', - paragraphFontFamily: 'Roboto', - paragraphFontSize: 24, - outerText: '0 1 2 3 4 5 ', - innerText: '0 1 2 3 4 5', - fontFeatures: [const ui.FontFeature.oldstyleFigures()], - ); - }); - - test('text styles - stylistic set 1', () async { - await testTextStyle( - 'stylistic set 1', - paragraphFontFamily: 'Roboto', - paragraphFontSize: 24, - outerText: 'g', - innerText: 'g', - fontFeatures: [ui.FontFeature.stylisticSet(1)], - ); - }); - - test('text styles - stylistic set 2', () async { - await testTextStyle( - 'stylistic set 2', - paragraphFontFamily: 'Roboto', - paragraphFontSize: 24, - outerText: 'α', - innerText: 'α', - fontFeatures: [ui.FontFeature.stylisticSet(2)], - ); - }); - - test('text styles - override font family', () async { - await testTextStyle( - 'override font family', - paragraphFontFamily: 'Ahem', - fontFamily: 'Roboto', - ); - }); - - test('text styles - override font size', () async { - await testTextStyle( - 'override font size', - paragraphFontSize: 36, - fontSize: 18, - ); - }); - - test('text style - override font weight', () async { - await testTextStyle( - 'override font weight', - paragraphFontWeight: ui.FontWeight.w900, - fontWeight: ui.FontWeight.normal, - ); - }); - - test('text style - override font style', () async { - await testTextStyle( - 'override font style', - paragraphFontStyle: ui.FontStyle.italic, - fontStyle: ui.FontStyle.normal, - ); - }); - - test('text style - characters from multiple fallback fonts', () async { - await testTextStyle( - 'multi-font characters', - // This character is claimed by multiple fonts. This test makes sure - // we can find a font supporting it. - outerText: '欢', - innerText: '', - ); - }); - - test('text style - symbols', () async { - // One of the CJK fonts loaded in one of the tests above also contains - // some of these symbols. To make sure the test produces predictable - // results we reset the fallback data forcing the engine to reload - // fallbacks, which for this test will only load Noto Symbols. - await testTextStyle( - 'symbols', - outerText: '← ↑ → ↓ ', - innerText: '', - ); - }); - test( 'text style - foreground/background/color do not leak across paragraphs', () async { @@ -505,131 +134,6 @@ void testMain() { ); }); - test('sample Chinese text', () async { - await testSampleText( - 'chinese', - '也称乱数假文或者哑元文本, ' - '是印刷及排版领域所常用的虚拟文字。' - '由于曾经一台匿名的打印机刻意打乱了' - '一盒印刷字体从而造出一本字体样品书', - ); - }); - - test('sample Armenian text', () async { - await testSampleText( - 'armenian', - 'տպագրության և տպագրական արդյունաբերության համար նախատեսված մոդելային տեքստ է', - ); - }); - - test('sample Albanian text', () async { - await testSampleText( - 'albanian', - 'është një tekst shabllon i industrisë së printimit dhe shtypshkronjave Lorem Ipsum ka qenë teksti shabllon', - ); - }); - - test('sample Arabic text', () async { - await testSampleText( - 'arabic', - 'هناك حقيقة مثبتة منذ زمن طويل وهي أن المحتوى المقروء لصفحة ما سيلهي', - textDirection: ui.TextDirection.rtl, - ); - }); - - test('sample Bulgarian text', () async { - await testSampleText( - 'bulgarian', - 'е елементарен примерен текст използван в печатарската и типографската индустрия', - ); - }); - - test('sample Catalan text', () async { - await testSampleText( - 'catalan', - 'és un text de farciment usat per la indústria de la tipografia i la impremta', - ); - }); - - test('sample English text', () async { - await testSampleText( - 'english', - 'Lorem Ipsum is simply dummy text of the printing and typesetting industry', - ); - }); - - test('sample Greek text', () async { - await testSampleText( - 'greek', - 'είναι απλά ένα κείμενο χωρίς νόημα για τους επαγγελματίες της τυπογραφίας και στοιχειοθεσίας', - ); - }); - - test('sample Hebrew text', () async { - await testSampleText( - 'hebrew', - 'זוהי עובדה מבוססת שדעתו של הקורא תהיה מוסחת על ידי טקטס קריא כאשר הוא יביט בפריסתו', - textDirection: ui.TextDirection.rtl, - ); - }); - - test('sample Hindi text', () async { - await testSampleText( - 'hindi', - 'छपाई और अक्षर योजन उद्योग का एक साधारण डमी पाठ है सन १५०० के बाद से अभी तक इस उद्योग का मानक डमी पाठ मन गया जब एक अज्ञात मुद्रक ने नमूना लेकर एक नमूना किताब बनाई', - ); - }); - - test('sample Thai text', () async { - await testSampleText( - 'thai', - 'คือ เนื้อหาจำลองแบบเรียบๆ ที่ใช้กันในธุรกิจงานพิมพ์หรืองานเรียงพิมพ์ มันได้กลายมาเป็นเนื้อหาจำลองมาตรฐานของธุรกิจดังกล่าวมาตั้งแต่ศตวรรษที่', - ); - }); - - test('sample Georgian text', () async { - await testSampleText( - 'georgian', - 'საბეჭდი და ტიპოგრაფიული ინდუსტრიის უშინაარსო ტექსტია. იგი სტანდარტად', - ); - }); - - test('sample Bengali text', () async { - await testSampleText( - 'bengali', - 'ঈদের জামাত মসজিদে, মানতে হবে স্বাস্থ্যবিধি: ধর্ম মন্ত্রণালয়', - ); - }); - - test('hindi svayan test', () async { - await testSampleText('hindi_svayan', 'स्वयं'); - }); - - // We've seen text break when we load many fonts simultaneously. This test - // combines text in multiple languages into one long paragraph to make sure - // we can handle it. - test('sample multilingual text', () async { - await testSampleText( - 'multilingual', - '也称乱数假文或者哑元文本, 是印刷及排版领域所常用的虚拟文字。 ' - 'տպագրության և տպագրական արդյունաբերության համար ' - 'është një tekst shabllon i industrisë së printimit ' - ' زمن طويل وهي أن المحتوى المقروء لصفحة ما سيلهي ' - 'е елементарен примерен текст използван в печатарската ' - 'és un text de farciment usat per la indústria de la ' - 'Lorem Ipsum is simply dummy text of the printing ' - 'είναι απλά ένα κείμενο χωρίς νόημα για τους επαγγελματίες ' - ' זוהי עובדה מבוססת שדעתו של הקורא תהיה מוסחת על ידי טקטס קריא ' - 'छपाई और अक्षर योजन उद्योग का एक साधारण डमी पाठ है सन ' - 'คือ เนื้อหาจำลองแบบเรียบๆ ที่ใช้กันในธุรกิจงานพิมพ์หรืองานเรียงพิมพ์ ' - 'საბეჭდი და ტიპოგრაფიული ინდუსტრიის უშინაარსო ტექსტია ', - ); - }); - - test('emoji text with skin tone', () async { - await testSampleText('emoji_with_skin_tone', '👋🏿 👋🏾 👋🏽 👋🏼 👋🏻'); - }, timeout: const Timeout.factor(2)); - // Make sure we clear the canvas in between frames. test('empty frame after contentful frame', () async { // First draw a frame with a red rectangle @@ -714,31 +218,6 @@ void testMain() { }); } -Future testSampleText(String language, String text, - {ui.TextDirection textDirection = ui.TextDirection.ltr}) async { - const double testWidth = 300; - double paragraphHeight = 0; - final CkPicture picture = await generatePictureWhenFontsStable(() { - final CkPictureRecorder recorder = CkPictureRecorder(); - final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest); - final CkParagraphBuilder paragraphBuilder = - CkParagraphBuilder(CkParagraphStyle( - textDirection: textDirection, - )); - paragraphBuilder.addText(text); - final CkParagraph paragraph = paragraphBuilder.build(); - paragraph.layout(const ui.ParagraphConstraints(width: testWidth - 20)); - canvas.drawParagraph(paragraph, const ui.Offset(10, 10)); - paragraphHeight = paragraph.height; - return recorder.endRecording(); - }); - await matchPictureGolden( - 'canvaskit_sample_text_$language.png', - picture, - region: ui.Rect.fromLTRB(0, 0, testWidth, paragraphHeight + 20), - ); -} - typedef ParagraphFactory = CkParagraph Function(); void drawTestPicture(CkCanvas canvas) { @@ -1057,187 +536,3 @@ CkImage generateTestImage() { 4 * 20)!; return CkImage(skImage); } - -/// A convenience function for testing paragraph and text styles. -/// -/// Renders a paragraph with two pieces of text, [outerText] and [innerText]. -/// [outerText] is added to the root of the paragraph where only paragraph -/// style applies. [innerText] is added under a text style with properties -/// set from the arguments to this method. Parameters with prefix "paragraph" -/// are applied to the paragraph style. Others are applied to the text style. -/// -/// [name] is the name of the test used as the description on the golden as -/// well as in the golden file name. Avoid special characters. Spaces are OK; -/// they are replaced by "_" in the file name. -/// -/// Use [layoutWidth] to customize the width of the paragraph constraints. -Future testTextStyle( - // Test properties - String name, { - double? layoutWidth, - // Top-level text where only paragraph style applies - String outerText = 'Hello ', - // Second-level text where paragraph and text styles both apply. - String innerText = 'World!', - - // ParagraphStyle properties - ui.TextAlign? paragraphTextAlign, - ui.TextDirection? paragraphTextDirection, - int? paragraphMaxLines, - String? paragraphFontFamily, - double? paragraphFontSize, - double? paragraphHeight, - ui.TextHeightBehavior? paragraphTextHeightBehavior, - ui.FontWeight? paragraphFontWeight, - ui.FontStyle? paragraphFontStyle, - ui.StrutStyle? paragraphStrutStyle, - String? paragraphEllipsis, - ui.Locale? paragraphLocale, - - // TextStyle properties - ui.Color? color, - ui.TextDecoration? decoration, - ui.Color? decorationColor, - ui.TextDecorationStyle? decorationStyle, - double? decorationThickness, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.TextBaseline? textBaseline, - String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? letterSpacing, - double? wordSpacing, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - ui.Locale? locale, - CkPaint? background, - CkPaint? foreground, - List? shadows, - List? fontFeatures, -}) async { - late ui.Rect region; - CkPicture renderPicture() { - const double testWidth = 512; - final CkPictureRecorder recorder = CkPictureRecorder(); - final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest); - canvas.translate(30, 10); - final CkParagraphBuilder descriptionBuilder = - CkParagraphBuilder(CkParagraphStyle()); - descriptionBuilder.addText(name); - final CkParagraph descriptionParagraph = descriptionBuilder.build(); - descriptionParagraph - .layout(const ui.ParagraphConstraints(width: testWidth / 2 - 70)); - const ui.Offset descriptionOffset = ui.Offset(testWidth / 2 + 30, 0); - canvas.drawParagraph(descriptionParagraph, descriptionOffset); - - final CkParagraphBuilder pb = CkParagraphBuilder(CkParagraphStyle( - textAlign: paragraphTextAlign, - textDirection: paragraphTextDirection, - maxLines: paragraphMaxLines, - fontFamily: paragraphFontFamily, - fontSize: paragraphFontSize, - height: paragraphHeight, - textHeightBehavior: paragraphTextHeightBehavior, - fontWeight: paragraphFontWeight, - fontStyle: paragraphFontStyle, - strutStyle: paragraphStrutStyle, - ellipsis: paragraphEllipsis, - locale: paragraphLocale, - )); - - pb.addText(outerText); - - pb.pushStyle(CkTextStyle( - color: color, - decoration: decoration, - decorationColor: decorationColor, - decorationStyle: decorationStyle, - decorationThickness: decorationThickness, - fontWeight: fontWeight, - fontStyle: fontStyle, - textBaseline: textBaseline, - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - letterSpacing: letterSpacing, - wordSpacing: wordSpacing, - height: height, - leadingDistribution: leadingDistribution, - locale: locale, - background: background, - foreground: foreground, - shadows: shadows, - fontFeatures: fontFeatures, - )); - pb.addText(innerText); - pb.pop(); - final CkParagraph p = pb.build(); - p.layout(ui.ParagraphConstraints(width: layoutWidth ?? testWidth / 2)); - canvas.drawParagraph(p, ui.Offset.zero); - - canvas.drawPath( - CkPath() - ..moveTo(-10, 0) - ..lineTo(-20, 0) - ..lineTo(-20, p.height) - ..lineTo(-10, p.height), - CkPaint() - ..style = ui.PaintingStyle.stroke - ..strokeWidth = 1.0, - ); - canvas.drawPath( - CkPath() - ..moveTo(testWidth / 2 + 10, 0) - ..lineTo(testWidth / 2 + 20, 0) - ..lineTo(testWidth / 2 + 20, p.height) - ..lineTo(testWidth / 2 + 10, p.height), - CkPaint() - ..style = ui.PaintingStyle.stroke - ..strokeWidth = 1.0, - ); - const double padding = 20; - region = ui.Rect.fromLTRB( - 0, - 0, - testWidth, - math.max( - descriptionOffset.dy + descriptionParagraph.height + padding, - p.height + padding, - ), - ); - return recorder.endRecording(); - } - - // Render once to trigger font downloads. - final CkPicture picture = await generatePictureWhenFontsStable(renderPicture); - await matchPictureGolden( - 'canvaskit_text_styles_${name.replaceAll(' ', '_')}.png', - picture, - region: region, - ); - expect(notoDownloadQueue.debugIsLoadingFonts, isFalse); - expect(notoDownloadQueue.pendingFonts, isEmpty); - expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0); -} - -typedef PictureGenerator = CkPicture Function(); - -Future generatePictureWhenFontsStable( - PictureGenerator generator) async { - CkPicture picture = generator(); - // Fallback fonts start downloading as a post-frame callback. - CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); - // Font downloading begins asynchronously so we inject a timer before checking the download queue. - await Future.delayed(Duration.zero); - while (notoDownloadQueue.isPending || - notoDownloadQueue.downloader.debugActiveDownloadCount > 0) { - await notoDownloadQueue.debugWhenIdle(); - await notoDownloadQueue.downloader.debugWhenIdle(); - picture = generator(); - CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); - // Dummy timer for the same reason as above. - await Future.delayed(Duration.zero); - } - return picture; -} diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 842d3a4a70a92..17153116fa164 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -22,19 +22,17 @@ void setUpCanvasKitTest() { setUpTestViewDimensions: false, ); - setUpAll(() { - // Ahem must be added to font fallbacks list regardless of where it was - // downloaded from. - FontFallbackData.instance.globalFontFallbacks.add('Ahem'); - }); - tearDown(() { HtmlViewEmbedder.instance.debugClear(); SurfaceFactory.instance.debugClear(); }); - setUp(() => notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'); - tearDown(() => notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = null); + setUp(() => + renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride + = 'assets/fallback_fonts/'); + tearDown(() => + renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride + = null); } /// Utility function for CanvasKit tests to draw pictures without diff --git a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart index f8bff49380d7f..b5cdc1a27e597 100644 --- a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart @@ -2,9 +2,7 @@ // 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:math' as math; -import 'dart:typed_data'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; @@ -31,23 +29,27 @@ void testMain() { /// Used to save and restore [ui.window.onPlatformMessage] after each test. ui.PlatformMessageCallback? savedCallback; + final List downloadedFontFamilies = []; + setUp(() { - FontFallbackData.debugReset(); - notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; + renderer.fontCollection.debugResetFallbackFonts(); + renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; + renderer.fontCollection.fontFallbackManager!.downloadQueue.debugOnLoadFontFamily + = (String family) => downloadedFontFamilies.add(family); savedCallback = ui.window.onPlatformMessage; }); tearDown(() { + downloadedFontFamilies.clear(); ui.window.onPlatformMessage = savedCallback; }); test('Roboto is always a fallback font', () { - expect(FontFallbackData.instance.globalFontFallbacks, contains('Roboto')); + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, contains('Roboto')); }); test('will download Noto Sans Arabic if Arabic text is added', () async { - final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; - expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the // fallback font. @@ -56,10 +58,9 @@ void testMain() { ); pb.addText('مرحبا'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); - expect(FontFallbackData.instance.globalFontFallbacks, + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, contains('Noto Sans Arabic')); final CkPictureRecorder recorder = CkPictureRecorder(); @@ -86,8 +87,7 @@ void testMain() { test('will put the Noto Color Emoji font before other fallback fonts in the list', () async { - final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; - expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the // Arabic fallback font. @@ -96,10 +96,9 @@ void testMain() { ); pb.addText('مرحبا'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); - expect(FontFallbackData.instance.globalFontFallbacks, + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, ['Roboto', 'Noto Sans Arabic']); pb = CkParagraphBuilder( @@ -111,10 +110,9 @@ void testMain() { final CkParagraph paragraph = pb.build(); paragraph.layout(const ui.ParagraphConstraints(width: 1000)); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); - expect(FontFallbackData.instance.globalFontFallbacks, [ + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, [ 'Roboto', 'Noto Color Emoji', 'Noto Sans Arabic', @@ -123,8 +121,7 @@ void testMain() { test('will download Noto Color Emojis and Noto Symbols if no matching Noto Font', () async { - final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; - expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the // fallback font. @@ -133,10 +130,9 @@ void testMain() { ); pb.addText('Hello 😊'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); - expect(FontFallbackData.instance.globalFontFallbacks, + expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, contains('Noto Color Emoji')); final CkPictureRecorder recorder = CkPictureRecorder(); @@ -165,19 +161,14 @@ void testMain() { // 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. test( - 'Can find fonts for two adjacent unmatched code units from different fonts', + 'Can find fonts for two adjacent unmatched code points from different fonts', () async { - final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; - final LoggingDownloader loggingDownloader = - LoggingDownloader(NotoDownloader()); - notoDownloadQueue.downloader = loggingDownloader; // Try rendering text that requires fallback fonts, initially before the fonts are loaded. CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect( - loggingDownloader.log, + downloadedFontFamilies, [ 'Noto Sans SC', 'Noto Sans Kannada', @@ -185,60 +176,49 @@ void testMain() { ); // Do the same thing but this time with loaded fonts. - loggingDownloader.log.clear(); + downloadedFontFamilies.clear(); CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); - expect(loggingDownloader.log, isEmpty); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); + expect(downloadedFontFamilies, isEmpty); }); test('can find glyph for 2/3 symbol', () async { - final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; - final LoggingDownloader loggingDownloader = - LoggingDownloader(NotoDownloader()); - notoDownloadQueue.downloader = loggingDownloader; // Try rendering text that requires fallback fonts, initially before the fonts are loaded. CkParagraphBuilder(CkParagraphStyle()).addText('⅔'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect( - loggingDownloader.log, + downloadedFontFamilies, [ 'Noto Sans', ], ); // Do the same thing but this time with loaded fonts. - loggingDownloader.log.clear(); + downloadedFontFamilies.clear(); CkParagraphBuilder(CkParagraphStyle()).addText('⅔'); - rasterizer.debugRunPostFrameCallbacks(); - await notoDownloadQueue.debugWhenIdle(); - expect(loggingDownloader.log, isEmpty); + await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); + expect(downloadedFontFamilies, isEmpty); }); - test('findMinimumFontsForCodeunits for all supported code units', () async { - final LoggingDownloader loggingDownloader = - LoggingDownloader(NotoDownloader()); - notoDownloadQueue.downloader = loggingDownloader; - - // Collect all supported code units from all fallback fonts in the Noto + test('findMinimumFontsForCodePoints for all supported code points', () async { + // Collect all supported code points from all fallback fonts in the Noto // font tree. final Set testedFonts = {}; - final Set supportedUniqueCodeUnits = {}; + final Set supportedUniqueCodePoints = {}; final IntervalTree notoTree = - FontFallbackData.instance.notoTree; - for (final NotoFont font in FontFallbackData.instance.fallbackFonts) { + renderer.fontCollection.fontFallbackManager!.notoTree; + for (final NotoFont font in renderer.fontCollection.fontFallbackManager!.fallbackFonts) { testedFonts.add(font.name); - for (final CodeunitRange range in font.computeUnicodeRanges()) { - for (int codeUnit = range.start; codeUnit < range.end; codeUnit++) { - supportedUniqueCodeUnits.add(codeUnit); + for (final CodePointRange range in font.computeUnicodeRanges()) { + for (int codePoint = range.start; codePoint < range.end; codePoint++) { + supportedUniqueCodePoints.add(codePoint); } } } expect( - supportedUniqueCodeUnits.length, greaterThan(10000)); // sanity check + supportedUniqueCodePoints.length, greaterThan(10000)); // sanity check expect( testedFonts, unorderedEquals({ @@ -385,9 +365,9 @@ void testMain() { 'Noto Sans Zanabazar Square', })); - // Construct random paragraphs out of supported code units. + // Construct random paragraphs out of supported code points. final math.Random random = math.Random(0); - final List supportedCodeUnits = supportedUniqueCodeUnits.toList() + final List supportedCodePoints = supportedUniqueCodePoints.toList() ..shuffle(random); const int paragraphLength = 3; const int totalTestSize = 1000; @@ -396,27 +376,27 @@ void testMain() { batchStart < totalTestSize; batchStart += paragraphLength) { final int batchEnd = - math.min(batchStart + paragraphLength, supportedCodeUnits.length); - final Set codeUnits = {}; + math.min(batchStart + paragraphLength, supportedCodePoints.length); + final Set codePoints = {}; for (int i = batchStart; i < batchEnd; i += 1) { - codeUnits.add(supportedCodeUnits[i]); + codePoints.add(supportedCodePoints[i]); } final Set fonts = {}; - for (final int codeUnit in codeUnits) { - final List fontsForUnit = notoTree.intersections(codeUnit); + for (final int codePoint in codePoints) { + final List fontsForPoint = notoTree.intersections(codePoint); - // All code units are extracted from the same tree, so there must - // be at least one font supporting each code unit - expect(fontsForUnit, isNotEmpty); - fonts.addAll(fontsForUnit); + // All code points are extracted from the same tree, so there must + // be at least one font supporting each code point + expect(fontsForPoint, isNotEmpty); + fonts.addAll(fontsForPoint); } try { - FontFallbackData.instance.findMinimumFontsForCodeUnits(codeUnits, fonts); + renderer.fontCollection.fontFallbackManager!.findMinimumFontsForCodePoints(codePoints, fonts); } catch (e) { print( - 'findMinimumFontsForCodeunits failed:\n' - ' Code units: ${codeUnits.join(', ')}\n' + 'findMinimumFontsForCodePoints failed:\n' + ' Code points: ${codePoints.join(', ')}\n' ' Fonts: ${fonts.map((NotoFont f) => f.name).join(', ')}', ); rethrow; @@ -427,72 +407,3 @@ void testMain() { skip: isSafari, timeout: const Timeout.factor(4)); } - -class TestDownloader extends NotoDownloader { - // Where to redirect downloads to. - static final Map mockDownloads = {}; - @override - Future downloadAsString(String url, - {String? debugDescription}) async { - if (mockDownloads.containsKey(url)) { - url = mockDownloads[url]!; - final Uri uri = Uri.parse(url); - expect(uri.isScheme('http'), isFalse); - expect(uri.isScheme('https'), isFalse); - return super.downloadAsString(url); - } else { - return ''; - } - } - - @override - Future downloadAsBytes(String url, {String? debugDescription}) { - if (mockDownloads.containsKey(url)) { - url = mockDownloads[url]!; - final Uri uri = Uri.parse(url); - expect(uri.isScheme('http'), isFalse); - expect(uri.isScheme('https'), isFalse); - return super.downloadAsBytes(url); - } else { - return Future.value(Uint8List(0).buffer); - } - } -} - -class LoggingDownloader implements NotoDownloader { - LoggingDownloader(this.delegate); - - final List log = []; - - final NotoDownloader delegate; - - @override - Future debugWhenIdle() { - return delegate.debugWhenIdle(); - } - - @override - Future downloadAsBytes(String url, {String? debugDescription}) { - log.add(debugDescription ?? url); - return delegate.downloadAsBytes(url); - } - - @override - Future downloadAsString(String url, {String? debugDescription}) { - log.add(debugDescription ?? url); - return delegate.downloadAsString(url); - } - - @override - int get debugActiveDownloadCount => delegate.debugActiveDownloadCount; - - @override - String? get fallbackFontUrlPrefixOverride => - delegate.fallbackFontUrlPrefixOverride; - - @override set fallbackFontUrlPrefixOverride(String? override) => - delegate.fallbackFontUrlPrefixOverride; - - @override - String get fallbackFontUrlPrefix => delegate.fallbackFontUrlPrefix; -} diff --git a/lib/web_ui/test/canvaskit/interval_tree_test.dart b/lib/web_ui/test/canvaskit/interval_tree_test.dart index 39eb3ff917fd4..a106faa893e9e 100644 --- a/lib/web_ui/test/canvaskit/interval_tree_test.dart +++ b/lib/web_ui/test/canvaskit/interval_tree_test.dart @@ -13,9 +13,9 @@ void main() { void testMain() { group('$IntervalTree', () { test('is balanced', () { - final Map> ranges = >{ - 'A': const [CodeunitRange(0, 5), CodeunitRange(6, 10)], - 'B': const [CodeunitRange(4, 6)], + final Map> ranges = >{ + 'A': const [CodePointRange(0, 5), CodePointRange(6, 10)], + 'B': const [CodePointRange(4, 6)], }; // Should create a balanced 3-node tree with a root with a left and right @@ -30,23 +30,23 @@ void testMain() { expect(root.right!.right, isNull); // Should create a balanced 15-node tree (4 layers deep). - final Map> ranges2 = >{ - 'A': const [ - CodeunitRange(1, 1), - CodeunitRange(2, 2), - CodeunitRange(3, 3), - CodeunitRange(4, 4), - CodeunitRange(5, 5), - CodeunitRange(6, 6), - CodeunitRange(7, 7), - CodeunitRange(8, 8), - CodeunitRange(9, 9), - CodeunitRange(10, 10), - CodeunitRange(11, 11), - CodeunitRange(12, 12), - CodeunitRange(13, 13), - CodeunitRange(14, 14), - CodeunitRange(15, 15), + final Map> ranges2 = >{ + 'A': const [ + CodePointRange(1, 1), + CodePointRange(2, 2), + CodePointRange(3, 3), + CodePointRange(4, 4), + CodePointRange(5, 5), + CodePointRange(6, 6), + CodePointRange(7, 7), + CodePointRange(8, 8), + CodePointRange(9, 9), + CodePointRange(10, 10), + CodePointRange(11, 11), + CodePointRange(12, 12), + CodePointRange(13, 13), + CodePointRange(14, 14), + CodePointRange(15, 15), ], }; @@ -66,9 +66,9 @@ void testMain() { }); test('finds values whose intervals overlap with a given point', () { - final Map> ranges = >{ - 'A': const [CodeunitRange(0, 5), CodeunitRange(7, 10)], - 'B': const [CodeunitRange(4, 6)], + final Map> ranges = >{ + 'A': const [CodePointRange(0, 5), CodePointRange(7, 10)], + 'B': const [CodePointRange(4, 6)], }; final IntervalTree tree = IntervalTree.createFromRanges(ranges); diff --git a/lib/web_ui/test/common/test_initialization.dart b/lib/web_ui/test/common/test_initialization.dart index 812210624111e..427d47dcd3b02 100644 --- a/lib/web_ui/test/common/test_initialization.dart +++ b/lib/web_ui/test/common/test_initialization.dart @@ -27,10 +27,10 @@ void setUpUnitTests({ 'useColorEmoji': true, }) as engine.JsFlutterConfiguration); engine.debugSetConfiguration(config); - engine.notoDownloadQueue.downloader.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; debugFontsScope = configureDebugFontsAssetScope(fakeAssetManager); await engine.initializeEngine(assetManager: fakeAssetManager); + engine.renderer.fontCollection.fontFallbackManager?.downloadQueue.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'; if (setUpTestViewDimensions) { // Force-initialize FlutterViewEmbedder so it doesn't overwrite test pixel ratio. diff --git a/lib/web_ui/test/ui/text_golden_test.dart b/lib/web_ui/test/ui/text_golden_test.dart index d06752a0ce28a..3ac0ac2e3e582 100644 --- a/lib/web_ui/test/ui/text_golden_test.dart +++ b/lib/web_ui/test/ui/text_golden_test.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; 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:web_engine_tester/golden_tester.dart'; @@ -539,7 +540,7 @@ Future testTextStyle( // Render once to trigger font downloads. renderPicture(); - await waitForFallbackFontsToStabilize(); + await renderer.fontCollection.fontFallbackManager?.debugWhenIdle(); final ui.Picture picture = renderPicture(); await drawPictureUsingCurrentRenderer(picture); diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index f8e6d00b53aff..38f976ad91f3f 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -23,25 +23,6 @@ Future drawPictureUsingCurrentRenderer(Picture picture) async { await renderer.renderScene(sb.build()); } -Future waitForFallbackFontsToStabilize() async { - if (!isCanvasKit) { - return; - } - - // Fallback fonts start downloading as a post-frame callback. - CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); - // Font downloading begins asynchronously so we inject a timer before checking the download queue. - await Future.delayed(Duration.zero); - while (notoDownloadQueue.isPending || - notoDownloadQueue.downloader.debugActiveDownloadCount > 0) { - await notoDownloadQueue.debugWhenIdle(); - await notoDownloadQueue.downloader.debugWhenIdle(); - CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); - // Dummy timer for the same reason as above. - await Future.delayed(Duration.zero); - } -} - /// Returns [true] if this test is running in the CanvasKit renderer. bool get isCanvasKit => renderer is CanvasKitRenderer; From 3e958be26691b074d5dd145d4e3a73faa39c627a Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sat, 6 May 2023 00:20:40 -0700 Subject: [PATCH 16/27] Wired up font fallbacks to skwasm, but they aren't working. --- .../skwasm/skwasm_impl/font_collection.dart | 114 +++++++++++++----- .../skwasm/skwasm_impl/raw/raw_fonts.dart | 43 ++++++- lib/web_ui/skwasm/fonts.cpp | 75 ++++++++++-- 3 files changed, 185 insertions(+), 47 deletions(-) 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 f9343c62a806f..8ef979fca976f 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 @@ -19,11 +19,28 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; const String _robotoUrl = 'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf'; +class SkwasmTypeface { + SkwasmTypeface(SkDataHandle data) : handle = typefaceCreate(data); + + bool _isDisposed = false; + + void dispose() { + if (!_isDisposed) { + _isDisposed = true; + typefaceDispose(handle); + } + } + + TypefaceHandle handle; +} + class SkwasmFontCollection implements FlutterFontCollection { SkwasmFontCollection() : handle = fontCollectionCreate(); FontCollectionHandle handle; + final Map> registeredTypefaces = >{}; + @override late final FontFallbackManager fontFallbackManager = FontFallbackManager(SkwasmFallbackRegistry(this)); @@ -49,15 +66,10 @@ class SkwasmFontCollection implements FlutterFontCollection { ); } - // We can't restore the pointers directly due to a bug in dart2wasm - // https://github.com/dart-lang/sdk/issues/52142 - final List familyHandles = []; for (final FontFamily family in manifest.families) { - final SkStringHandle familyNameHandle = skStringFromDartString(family.name); - familyHandles.add(familyNameHandle.address); for (final FontAsset fontAsset in family.fontAssets) { fontFutures.add(() async { - final FontLoadError? error = await _downloadFontAsset(fontAsset, familyNameHandle); + final FontLoadError? error = await _downloadFontAsset(fontAsset, family.name); if (error == null) { loadedFonts.add(fontAsset.asset); } else { @@ -68,16 +80,10 @@ class SkwasmFontCollection implements FlutterFontCollection { } await Future.wait(fontFutures); - - // Wait until all the downloading and registering is complete before - // freeing the handles to the family name strings. - familyHandles - .map((int address) => SkStringHandle.fromAddress(address)) - .forEach(skStringFree); return AssetFontsResult(loadedFonts, fontFailures); } - Future _downloadFontAsset(FontAsset asset, SkStringHandle familyNameHandle) async { + Future _downloadFontAsset(FontAsset asset, String family) async { final HttpFetchResponse response; try { response = await assetManager.loadAsset(asset.asset); @@ -100,12 +106,17 @@ class SkwasmFontCollection implements FlutterFontCollection { wasmMemory.set(chunk, dataAddress.toJS); dataAddress += chunk.length.toDart.toInt(); } - final bool result = fontCollectionRegisterFont(handle, fontData, familyNameHandle); + final SkwasmTypeface typeface = SkwasmTypeface(fontData); skDataDispose(fontData); - if (!result) { + if (typeface.handle != nullptr) { + final SkStringHandle familyNameHandle = skStringFromDartString(family); + fontCollectionRegisterTypeface(handle, typeface.handle, familyNameHandle); + registeredTypefaces.putIfAbsent(family, () => []).add(typeface); + skStringFree(familyNameHandle); + return null; + } else { return FontInvalidDataError(assetManager.getAssetUrl(asset.asset)); } - return null; } Future loadFontFromUrl(String familyName, String url) async { @@ -123,11 +134,17 @@ class SkwasmFontCollection implements FlutterFontCollection { wasmMemory.set(chunk, dataAddress.toJS); dataAddress += chunk.length.toDart.toInt(); } + + final SkwasmTypeface typeface = SkwasmTypeface(fontData); + skDataDispose(fontData); + if (typeface.handle == nullptr) { + return false; + } final SkStringHandle familyNameHandle = skStringFromDartString(familyName); - final bool result = fontCollectionRegisterFont(handle, fontData, familyNameHandle); + fontCollectionRegisterTypeface(handle, typeface.handle, familyNameHandle); + registeredTypefaces.putIfAbsent(familyName, () => []).add(typeface); skStringFree(familyNameHandle); - skDataDispose(fontData); - return result; + return true; } @override @@ -137,21 +154,24 @@ class SkwasmFontCollection implements FlutterFontCollection { for (int i = 0; i < list.length; i++) { dataPointer[i] = list[i]; } - bool success; + final SkwasmTypeface typeface = SkwasmTypeface(dataHandle); + skDataDispose(dataHandle); + if (typeface.handle == nullptr) { + return false; + } + if (fontFamily != null) { final SkStringHandle familyHandle = skStringFromDartString(fontFamily); - success = fontCollectionRegisterFont(handle, dataHandle, familyHandle); + fontCollectionRegisterTypeface(handle, typeface.handle, familyHandle); skStringFree(familyHandle); } else { - success = fontCollectionRegisterFont(handle, dataHandle, nullptr); + fontCollectionRegisterTypeface(handle, typeface.handle, nullptr); } - skDataDispose(dataHandle); - return success; + return true; } @override void debugResetFallbackFonts() { - // TODO: implement debugResetFallbackFonts } } @@ -160,17 +180,47 @@ class SkwasmFallbackRegistry implements FallbackFontRegistry { final SkwasmFontCollection fontCollection; @override - List getMissingCodePoints(List codePoints, List fontFamilies) { - // TODO: implement getMissingCodePoints - throw UnimplementedError(); - } + List getMissingCodePoints(List codePoints, List fontFamilies) + => withStackScope((StackScope scope) { + final List typefaces = fontFamilies + .map((String family) => fontCollection.registeredTypefaces[family]) + .fold(const Iterable.empty(), + (Iterable accumulated, List? typefaces) => + typefaces == null ? accumulated : accumulated.followedBy(typefaces)).toList(); + final Pointer typefaceBuffer = scope.allocPointerArray(typefaces.length).cast(); + for (int i = 0; i < typefaces.length; i++) { + typefaceBuffer[i] = typefaces[i].handle; + } + final Pointer codePointBuffer = scope.allocInt32Array(codePoints.length); + for (int i = 0; i < codePoints.length; i++) { + codePointBuffer[i] = codePoints[i]; + } + final int missingCodePointCount = typefacesFilterCoveredCodePoints( + typefaceBuffer, + typefaces.length, + codePointBuffer, + codePoints.length + ); + return List.generate(missingCodePointCount, (int index) => codePointBuffer[index]); + }); @override Future loadFallbackFont(String familyName, String url) => fontCollection.loadFontFromUrl(familyName, url); @override - void updateFallbackFontFamilies(List families) { - // TODO: implement updateFallbackFontFamilies - } + void updateFallbackFontFamilies(List families) => withStackScope((StackScope scope) { + final Pointer familyPointers = + scope.allocPointerArray(families.length).cast(); + for (int i = 0; i < families.length; i++) { + familyPointers[i] = skStringFromDartString(families[i]); + } + fontCollectionSetDefaultFontFamilies( + fontCollection.handle, + familyPointers, + families.length); + for (int i = 0; i < families.length; i++) { + skStringFree(familyPointers[i]); + } + }); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart index 5bbd928d1ce2e..3d00faf631167 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart @@ -12,19 +12,52 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; final class RawFontCollection extends Opaque {} typedef FontCollectionHandle = Pointer; +final class RawTypeface extends Opaque {} +typedef TypefaceHandle = Pointer; + @Native(symbol: 'fontCollection_create', isLeaf: true) external FontCollectionHandle fontCollectionCreate(); @Native(symbol: 'fontCollection_dispose', isLeaf: true) external void fontCollectionDispose(FontCollectionHandle handle); -@Native(symbol: 'typeface_create', isLeaf: true) +external TypefaceHandle typefaceCreate(SkDataHandle fontData); + +@Native(symbol: 'typeface_dispose', isLeaf: true) +external void typefaceDispose(TypefaceHandle handle); + +@Native, + Int, + Pointer, + Int, +)>(symbol: 'typefaces_filterCoveredCodePoints', isLeaf: true) +external int typefacesFilterCoveredCodePoints( + Pointer typefaces, + int typefaceCount, + Pointer codepoints, + int codePointCount, +); + +@Native(symbol: 'fontCollection_registerFont', isLeaf: true) -external bool fontCollectionRegisterFont( +)>(symbol: 'fontCollection_registerTypeface', isLeaf: true) +external void fontCollectionRegisterTypeface( FontCollectionHandle handle, - SkDataHandle fontData, + TypefaceHandle typeface, SkStringHandle fontName, ); + +@Native, + Int, +)>(symbol: 'fontCollection_setDefaultFontFamilies', isLeaf: true) +external void fontCollectionSetDefaultFontFamilies( + FontCollectionHandle handle, + Pointer families, + int familyCount, +); diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index 45926d19ad512..805752181a7ea 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -8,6 +8,8 @@ #include "third_party/skia/modules/skparagraph/include/FontCollection.h" #include "third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h" +#include + using namespace skia::textlayout; using namespace Skwasm; @@ -26,21 +28,74 @@ SKWASM_EXPORT void fontCollection_dispose(FlutterFontCollection* collection) { delete collection; } -SKWASM_EXPORT bool fontCollection_registerFont( - FlutterFontCollection* collection, - SkData* fontData, - SkString* fontName) { +SKWASM_EXPORT SkTypeface* typeface_create(SkData* fontData) { fontData->ref(); - auto typeFace = + auto typeface = SkFontMgr::RefDefault()->makeFromData(sk_sp(fontData)); - if (!typeFace) { - return false; + return typeface.release(); +} + +SKWASM_EXPORT void typeface_dispose(SkTypeface *typeface) { + typeface->unref(); +} + +// Calculates the code points that are not covered by the specified typefaces. +// This function mutates the `codePoints` buffer in place and returns the count +// of code points that are not covered by the fonts. +SKWASM_EXPORT int typefaces_filterCoveredCodePoints( + SkTypeface **typefaces, + int typefaceCount, + SkUnichar* codePoints, + int codePointCount +) { + std::unique_ptr glyphBuffer = std::make_unique(codePointCount); + SkGlyphID* glyphPointer = glyphBuffer.get(); + int remainingCodePointCount = codePointCount; + for (int typefaceIndex = 0; typefaceIndex < typefaceCount; typefaceIndex++) { + typefaces[typefaceIndex]->unicharsToGlyphs(codePoints, remainingCodePointCount, glyphPointer); + int outputIndex = 0; + for (int inputIndex = 0; inputIndex < remainingCodePointCount; inputIndex++) { + if (glyphPointer[inputIndex] == 0) { + if (outputIndex != inputIndex) { + codePoints[outputIndex] = codePoints[inputIndex]; + } + outputIndex++; + } + } + if (outputIndex == 0) { + return 0; + } else { + remainingCodePointCount = outputIndex; + } } + return remainingCodePointCount; +} + +SKWASM_EXPORT void fontCollection_registerTypeface( + FlutterFontCollection* collection, + SkTypeface* typeface, + SkString* fontName) { + typeface->ref(); if (fontName) { SkString alias = *fontName; - collection->provider->registerTypeface(std::move(typeFace), alias); + collection->provider->registerTypeface(sk_sp(typeface), alias); } else { - collection->provider->registerTypeface(std::move(typeFace)); + collection->provider->registerTypeface(sk_sp(typeface)); + } +} + +SKWASM_EXPORT void fontCollection_setDefaultFontFamilies( + FlutterFontCollection* collection, + SkString **familyNames, + int familyCount +) { + std::vector families; + families.reserve(familyCount); + for (int i = 0; i < familyCount; i++) { + families.push_back(*familyNames[i]); } - return true; + collection->collection->setDefaultFontManager( + collection->provider, + std::move(families) + ); } From 87b4f28ae6a5a748b692120c44478c019cff72bd Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sun, 7 May 2023 11:25:31 -0700 Subject: [PATCH 17/27] Fallback fonts working in Skwasm now! --- .../skwasm/skwasm_impl/font_collection.dart | 1 + .../engine/skwasm/skwasm_impl/paragraph.dart | 18 +- .../raw/text/raw_paragraph_style.dart | 4 +- .../skwasm_impl/raw/text/raw_text_style.dart | 8 +- lib/web_ui/skwasm/fonts.cpp | 6 +- lib/web_ui/skwasm/text/paragraph_style.cpp | 5 +- lib/web_ui/skwasm/text/text_style.cpp | 13 +- lib/web_ui/skwasm/wrappers.h | 1 + lib/web_ui/test/ui/text_golden_test.dart | 154 ++++++++++++++++++ 9 files changed, 190 insertions(+), 20 deletions(-) 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 8ef979fca976f..284481b026feb 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 @@ -179,6 +179,7 @@ class SkwasmFallbackRegistry implements FallbackFontRegistry { SkwasmFallbackRegistry(this.fontCollection); final SkwasmFontCollection fontCollection; + @override List getMissingCodePoints(List codePoints, List fontFamilies) => withStackScope((StackScope scope) { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 8d3f98afed324..2c3bbc650821b 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -6,6 +6,7 @@ import 'dart:ffi'; +import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; @@ -227,7 +228,9 @@ class SkwasmTextStyle implements ui.TextStyle { List? fontFeatures, List? fontVariations, }) { - final TextStyleHandle handle = textStyleCreate(); + final TextStyleHandle handle = textStyleCreate( + (renderer.fontCollection as SkwasmFontCollection).handle + ); if (color != null) { textStyleSetColor(handle, color.value); } @@ -259,7 +262,7 @@ class SkwasmTextStyle implements ui.TextStyle { if (fontFamilies.isNotEmpty) { withScopedFontList(fontFamilies, (Pointer families, int count) => - textStyleSetFontFamilies(handle, families, count)); + textStyleAddFontFamilies(handle, families, count)); } } if (fontSize != null) { @@ -390,7 +393,9 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { String? ellipsis, ui.Locale? locale, }) { - final ParagraphStyleHandle handle = paragraphStyleCreate(); + final ParagraphStyleHandle handle = paragraphStyleCreate( + (renderer.fontCollection as SkwasmFontCollection).handle, + ); if (textAlign != null) { paragraphStyleSetTextAlign(handle, textAlign.index); } @@ -425,11 +430,13 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { fontStyle != null || textHeightBehavior != null || locale != null) { - final TextStyleHandle textStyleHandle = textStyleCreate(); + final TextStyleHandle textStyleHandle = textStyleCreate( + (renderer.fontCollection as SkwasmFontCollection).handle, + ); if (fontFamily != null) { withScopedFontList([fontFamily], (Pointer families, int count) => - textStyleSetFontFamilies(textStyleHandle, families, count)); + textStyleAddFontFamilies(textStyleHandle, families, count)); } if (fontSize != null) { textStyleSetFontSize(textStyleHandle, fontSize); @@ -499,6 +506,7 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override void addText(String text) { + renderer.fontCollection.fontFallbackManager?.ensureFontsSupportText(text, ['Roboto']); final SkString16Handle stringHandle = skString16FromDartString(text); paragraphBuilderAddText(handle, stringHandle); skString16Free(stringHandle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart index e65a5f7690066..48de046f76bfe 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart @@ -12,8 +12,8 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; final class RawParagraphStyle extends Opaque {} typedef ParagraphStyleHandle = Pointer; -@Native(symbol: 'paragraphStyle_create', isLeaf: true) -external ParagraphStyleHandle paragraphStyleCreate(); +@Native(symbol: 'paragraphStyle_create', isLeaf: true) +external ParagraphStyleHandle paragraphStyleCreate(FontCollectionHandle fontCollection); @Native(symbol: 'paragraphStyle_dispose', isLeaf: true) external void paragraphStyleDispose(ParagraphStyleHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart index e2de17a6660f6..d994237929f5c 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart @@ -13,8 +13,8 @@ final class RawTextStyle extends Opaque {} typedef TextStyleHandle = Pointer; -@Native(symbol: 'textStyle_create', isLeaf: true) -external TextStyleHandle textStyleCreate(); +@Native(symbol: 'textStyle_create', isLeaf: true) +external TextStyleHandle textStyleCreate(FontCollectionHandle handle); @Native(symbol: 'textStyle_dispose', isLeaf: true) external void textStyleDispose(TextStyleHandle handle); @@ -52,8 +52,8 @@ external void textStyleSetTextBaseline(TextStyleHandle handle, int baseline); TextStyleHandle, Pointer, Int count -)>(symbol: 'textStyle_setFontFamilies', isLeaf: true) -external void textStyleSetFontFamilies( +)>(symbol: 'textStyle_addFontFamilies', isLeaf: true) +external void textStyleAddFontFamilies( TextStyleHandle handle, Pointer families, int count diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index 805752181a7ea..26dd690ed8d12 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -21,6 +21,7 @@ SKWASM_EXPORT FlutterFontCollection* fontCollection_create() { return new FlutterFontCollection{ std::move(collection), std::move(provider), + { SkString("Roboto") }, }; } @@ -94,8 +95,5 @@ SKWASM_EXPORT void fontCollection_setDefaultFontFamilies( for (int i = 0; i < familyCount; i++) { families.push_back(*familyNames[i]); } - collection->collection->setDefaultFontManager( - collection->provider, - std::move(families) - ); + collection->fallbackFontFamilies = families; } diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index 92ccc9ac996e2..e72f30a4fdad6 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -3,11 +3,13 @@ // found in the LICENSE file. #include "../export.h" +#include "../wrappers.h" #include "third_party/skia/modules/skparagraph/include/Paragraph.h" using namespace skia::textlayout; +using namespace Skwasm; -SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { +SKWASM_EXPORT ParagraphStyle* paragraphStyle_create(FlutterFontCollection *collection) { auto style = new ParagraphStyle(); // This is the default behavior in Flutter @@ -16,6 +18,7 @@ SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { // Default text style has a black color TextStyle textStyle; textStyle.setColor(SK_ColorBLACK); + textStyle.setFontFamilies(collection->fallbackFontFamilies); style->setTextStyle(textStyle); return style; diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index a6686ff767be5..bab2249ce4dd0 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -3,15 +3,20 @@ // found in the LICENSE file. #include "../export.h" +#include "../wrappers.h" #include "third_party/skia/modules/skparagraph/include/Paragraph.h" using namespace skia::textlayout; +using namespace Skwasm; -SKWASM_EXPORT TextStyle* textStyle_create() { +SKWASM_EXPORT TextStyle* textStyle_create(FlutterFontCollection* collection) { auto style = new TextStyle(); // Default color in flutter is black. style->setColor(SK_ColorBLACK); + + // Add fallback fonts + style->setFontFamilies(collection->fallbackFontFamilies); return style; } @@ -55,11 +60,11 @@ SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle* style, style->setTextBaseline(baseline); } -SKWASM_EXPORT void textStyle_setFontFamilies(TextStyle* style, +SKWASM_EXPORT void textStyle_addFontFamilies(TextStyle* style, SkString** fontFamilies, int count) { - std::vector families; - families.reserve(count); + std::vector families = style->getFontFamilies(); + families.reserve(families.size() + count); for (int i = 0; i < count; i++) { families.push_back(*fontFamilies[i]); } diff --git a/lib/web_ui/skwasm/wrappers.h b/lib/web_ui/skwasm/wrappers.h index ffb356bba9c67..53c11b77f3570 100644 --- a/lib/web_ui/skwasm/wrappers.h +++ b/lib/web_ui/skwasm/wrappers.h @@ -36,6 +36,7 @@ inline void makeCurrent(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle) { struct FlutterFontCollection { sk_sp collection; sk_sp provider; + std::vector fallbackFontFamilies; }; } // namespace Skwasm diff --git a/lib/web_ui/test/ui/text_golden_test.dart b/lib/web_ui/test/ui/text_golden_test.dart index 3ac0ac2e3e582..c4cf8b30736b9 100644 --- a/lib/web_ui/test/ui/text_golden_test.dart +++ b/lib/web_ui/test/ui/text_golden_test.dart @@ -385,6 +385,131 @@ Future testMain() async { innerText: '', ); }); + + test('sample Chinese text', () async { + await testSampleText( + 'chinese', + '也称乱数假文或者哑元文本, ' + '是印刷及排版领域所常用的虚拟文字。' + '由于曾经一台匿名的打印机刻意打乱了' + '一盒印刷字体从而造出一本字体样品书', + ); + }); + + test('sample Armenian text', () async { + await testSampleText( + 'armenian', + 'տպագրության և տպագրական արդյունաբերության համար նախատեսված մոդելային տեքստ է', + ); + }); + + test('sample Albanian text', () async { + await testSampleText( + 'albanian', + 'është një tekst shabllon i industrisë së printimit dhe shtypshkronjave Lorem Ipsum ka qenë teksti shabllon', + ); + }); + + test('sample Arabic text', () async { + await testSampleText( + 'arabic', + 'هناك حقيقة مثبتة منذ زمن طويل وهي أن المحتوى المقروء لصفحة ما سيلهي', + textDirection: ui.TextDirection.rtl, + ); + }); + + test('sample Bulgarian text', () async { + await testSampleText( + 'bulgarian', + 'е елементарен примерен текст използван в печатарската и типографската индустрия', + ); + }); + + test('sample Catalan text', () async { + await testSampleText( + 'catalan', + 'és un text de farciment usat per la indústria de la tipografia i la impremta', + ); + }); + + test('sample English text', () async { + await testSampleText( + 'english', + 'Lorem Ipsum is simply dummy text of the printing and typesetting industry', + ); + }); + + test('sample Greek text', () async { + await testSampleText( + 'greek', + 'είναι απλά ένα κείμενο χωρίς νόημα για τους επαγγελματίες της τυπογραφίας και στοιχειοθεσίας', + ); + }); + + test('sample Hebrew text', () async { + await testSampleText( + 'hebrew', + 'זוהי עובדה מבוססת שדעתו של הקורא תהיה מוסחת על ידי טקטס קריא כאשר הוא יביט בפריסתו', + textDirection: ui.TextDirection.rtl, + ); + }); + + test('sample Hindi text', () async { + await testSampleText( + 'hindi', + 'छपाई और अक्षर योजन उद्योग का एक साधारण डमी पाठ है सन १५०० के बाद से अभी तक इस उद्योग का मानक डमी पाठ मन गया जब एक अज्ञात मुद्रक ने नमूना लेकर एक नमूना किताब बनाई', + ); + }); + + test('sample Thai text', () async { + await testSampleText( + 'thai', + 'คือ เนื้อหาจำลองแบบเรียบๆ ที่ใช้กันในธุรกิจงานพิมพ์หรืองานเรียงพิมพ์ มันได้กลายมาเป็นเนื้อหาจำลองมาตรฐานของธุรกิจดังกล่าวมาตั้งแต่ศตวรรษที่', + ); + }); + + test('sample Georgian text', () async { + await testSampleText( + 'georgian', + 'საბეჭდი და ტიპოგრაფიული ინდუსტრიის უშინაარსო ტექსტია. იგი სტანდარტად', + ); + }); + + test('sample Bengali text', () async { + await testSampleText( + 'bengali', + 'ঈদের জামাত মসজিদে, মানতে হবে স্বাস্থ্যবিধি: ধর্ম মন্ত্রণালয়', + ); + }); + + test('hindi svayan test', () async { + await testSampleText('hindi_svayan', 'स्वयं'); + }); + + // We've seen text break when we load many fonts simultaneously. This test + // combines text in multiple languages into one long paragraph to make sure + // we can handle it. + test('sample multilingual text', () async { + await testSampleText( + 'multilingual', + '也称乱数假文或者哑元文本, 是印刷及排版领域所常用的虚拟文字。 ' + 'տպագրության և տպագրական արդյունաբերության համար ' + 'është një tekst shabllon i industrisë së printimit ' + ' زمن طويل وهي أن المحتوى المقروء لصفحة ما سيلهي ' + 'е елементарен примерен текст използван в печатарската ' + 'és un text de farciment usat per la indústria de la ' + 'Lorem Ipsum is simply dummy text of the printing ' + 'είναι απλά ένα κείμενο χωρίς νόημα για τους επαγγελματίες ' + ' זוהי עובדה מבוססת שדעתו של הקורא תהיה מוסחת על ידי טקטס קריא ' + 'छपाई और अक्षर योजन उद्योग का एक साधारण डमी पाठ है सन ' + 'คือ เนื้อหาจำลองแบบเรียบๆ ที่ใช้กันในธุรกิจงานพิมพ์หรืองานเรียงพิมพ์ ' + 'საბეჭდი და ტიპოგრაფიული ინდუსტრიის უშინაარსო ტექსტია ', + ); + }); + + test('emoji text with skin tone', () async { + await testSampleText('emoji_with_skin_tone', '👋🏿 👋🏾 👋🏽 👋🏼 👋🏻'); + }, timeout: const Timeout.factor(2)); } /// A convenience function for testing paragraph and text styles. @@ -549,3 +674,32 @@ Future testTextStyle( region: region, ); } + +Future testSampleText(String language, String text, + {ui.TextDirection textDirection = ui.TextDirection.ltr}) async { + const double testWidth = 300; + double paragraphHeight = 0; + ui.Picture renderPicture() { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + final ui.ParagraphBuilder paragraphBuilder = + ui.ParagraphBuilder(ui.ParagraphStyle( + textDirection: textDirection, + )); + paragraphBuilder.addText(text); + final ui.Paragraph paragraph = paragraphBuilder.build(); + paragraph.layout(const ui.ParagraphConstraints(width: testWidth - 20)); + canvas.drawParagraph(paragraph, const ui.Offset(10, 10)); + paragraphHeight = paragraph.height; + return recorder.endRecording(); + } + // Render once to trigger font downloads. + renderPicture(); + await renderer.fontCollection.fontFallbackManager?.debugWhenIdle(); + final ui.Picture picture = renderPicture(); + await drawPictureUsingCurrentRenderer(picture); + await matchGoldenFile( + 'ui_sample_text_$language.png', + region: ui.Rect.fromLTRB(0, 0, testWidth, paragraphHeight + 20), + ); +} From 50d928833cd19ad12b2b5d39f8807ad2c733bf8b Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sun, 7 May 2023 17:21:06 -0700 Subject: [PATCH 18/27] Fix font ordering. --- .../engine/skwasm/skwasm_impl/paragraph.dart | 46 ++++++++++++++----- lib/web_ui/skwasm/text/text_style.cpp | 12 +++-- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 2c3bbc650821b..bb49a70db9af2 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -2,15 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: avoid_unused_constructor_parameters - import 'dart:ffi'; import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; -// TODO(jacksongardner): implement everything in this file class SkwasmLineMetrics implements ui.LineMetrics { SkwasmLineMetrics._(this.handle); @@ -254,8 +251,9 @@ class SkwasmTextStyle implements ui.TextStyle { if (textBaseline != null) { textStyleSetTextBaseline(handle, textBaseline.index); } + List? fontFamilies; if (fontFamily != null || fontFamilyFallback != null) { - final List fontFamilies = [ + fontFamilies = [ if (fontFamily != null) fontFamily, if (fontFamilyFallback != null) ...fontFamilyFallback, ]; @@ -316,12 +314,13 @@ class SkwasmTextStyle implements ui.TextStyle { } } // TODO(jacksongardner): Set font variations - return SkwasmTextStyle._(handle); + return SkwasmTextStyle._(handle, fontFamilies); } - SkwasmTextStyle._(this.handle); + SkwasmTextStyle._(this.handle, this.fontFamilies); final TextStyleHandle handle; + final List? fontFamilies; } class SkwasmStrutStyle implements ui.StrutStyle { @@ -459,14 +458,14 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { skStringFree(localeHandle); } paragraphStyleSetTextStyle(handle, textStyleHandle); - textStyleDispose(textStyleHandle); } - return SkwasmParagraphStyle._(handle); + return SkwasmParagraphStyle._(handle, fontFamily); } - SkwasmParagraphStyle._(this.handle); + SkwasmParagraphStyle._(this.handle, this.defaultFontFamily); final ParagraphStyleHandle handle; + final String? defaultFontFamily; } class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @@ -476,10 +475,12 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { ) => SkwasmParagraphBuilder._(paragraphBuilderCreate( style.handle, collection.handle, - )); + ), style); - SkwasmParagraphBuilder._(this.handle); + SkwasmParagraphBuilder._(this.handle, this.style); final ParagraphBuilderHandle handle; + final SkwasmParagraphStyle style; + final List textStyleStack = []; @override List placeholderScales = []; @@ -504,9 +505,28 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { placeholderScales.add(scale); } + List _getEffectiveFonts() { + final List fallbackFonts = renderer.fontCollection.fontFallbackManager!.globalFontFallbacks; + final List? currentFonts = + textStyleStack.isEmpty ? null : textStyleStack.last.fontFamilies; + if (currentFonts != null) { + return [ + ...currentFonts, + ...fallbackFonts, + ]; + } else if (style.defaultFontFamily != null) { + return [ + style.defaultFontFamily!, + ...fallbackFonts, + ]; + } else { + return fallbackFonts; + } + } + @override void addText(String text) { - renderer.fontCollection.fontFallbackManager?.ensureFontsSupportText(text, ['Roboto']); + renderer.fontCollection.fontFallbackManager!.ensureFontsSupportText(text, _getEffectiveFonts()); final SkString16Handle stringHandle = skString16FromDartString(text); paragraphBuilderAddText(handle, stringHandle); skString16Free(stringHandle); @@ -522,12 +542,14 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override void pop() { + textStyleStack.removeLast(); paragraphBuilderPop(handle); } @override void pushStyle(ui.TextStyle style) { style as SkwasmTextStyle; + textStyleStack.add(style); paragraphBuilderPushStyle(handle, style.handle); } } diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index bab2249ce4dd0..5f587fe57e4a2 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -63,12 +63,16 @@ SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle* style, SKWASM_EXPORT void textStyle_addFontFamilies(TextStyle* style, SkString** fontFamilies, int count) { - std::vector families = style->getFontFamilies(); - families.reserve(families.size() + count); + const std::vector ¤tFamilies = style->getFontFamilies(); + std::vector newFamilies; + newFamilies.reserve(currentFamilies.size() + count); for (int i = 0; i < count; i++) { - families.push_back(*fontFamilies[i]); + newFamilies.push_back(*fontFamilies[i]); } - style->setFontFamilies(std::move(families)); + for (const auto& family : currentFamilies) { + newFamilies.push_back(family); + } + style->setFontFamilies(std::move(newFamilies)); } SKWASM_EXPORT void textStyle_setFontSize(TextStyle* style, SkScalar size) { From 4f69c01805fce9fc40ebdbae6b22a3b7036a2241 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sun, 7 May 2023 17:46:08 -0700 Subject: [PATCH 19/27] Move fallback fonts out of CanvasKit directory. --- lib/web_ui/dev/roll_fallback_fonts.dart | 1 - lib/web_ui/lib/src/engine.dart | 8 ++++---- .../src/engine/{canvaskit => }/font_fallback_data.dart | 0 .../lib/src/engine/{canvaskit => }/font_fallbacks.dart | 0 .../lib/src/engine/{canvaskit => }/interval_tree.dart | 0 lib/web_ui/lib/src/engine/{canvaskit => }/noto_font.dart | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) rename lib/web_ui/lib/src/engine/{canvaskit => }/font_fallback_data.dart (100%) rename lib/web_ui/lib/src/engine/{canvaskit => }/font_fallbacks.dart (100%) rename lib/web_ui/lib/src/engine/{canvaskit => }/interval_tree.dart (100%) rename lib/web_ui/lib/src/engine/{canvaskit => }/noto_font.dart (98%) diff --git a/lib/web_ui/dev/roll_fallback_fonts.dart b/lib/web_ui/dev/roll_fallback_fonts.dart index 7cf9f5ad6eaa0..18db6efd9a7b6 100644 --- a/lib/web_ui/dev/roll_fallback_fonts.dart +++ b/lib/web_ui/dev/roll_fallback_fonts.dart @@ -182,7 +182,6 @@ class RollFallbackFontsCommand extends Command 'lib', 'src', 'engine', - 'canvaskit', 'font_fallback_data.dart', )); await fontDataFile.writeAsString(sb.toString()); diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index a6c832c188754..f49c42cce499d 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -26,21 +26,17 @@ export 'engine/canvaskit/canvaskit_canvas.dart'; export 'engine/canvaskit/color_filter.dart'; export 'engine/canvaskit/embedded_views.dart'; export 'engine/canvaskit/embedded_views_diff.dart'; -export 'engine/canvaskit/font_fallback_data.dart'; -export 'engine/canvaskit/font_fallbacks.dart'; export 'engine/canvaskit/fonts.dart'; export 'engine/canvaskit/image.dart'; export 'engine/canvaskit/image_filter.dart'; export 'engine/canvaskit/image_wasm_codecs.dart'; export 'engine/canvaskit/image_web_codecs.dart'; -export 'engine/canvaskit/interval_tree.dart'; export 'engine/canvaskit/layer.dart'; export 'engine/canvaskit/layer_scene_builder.dart'; export 'engine/canvaskit/layer_tree.dart'; export 'engine/canvaskit/mask_filter.dart'; export 'engine/canvaskit/n_way_canvas.dart'; export 'engine/canvaskit/native_memory.dart'; -export 'engine/canvaskit/noto_font.dart'; export 'engine/canvaskit/painting.dart'; export 'engine/canvaskit/path.dart'; export 'engine/canvaskit/path_metrics.dart'; @@ -63,6 +59,8 @@ export 'engine/dom.dart'; export 'engine/embedder.dart'; export 'engine/engine_canvas.dart'; export 'engine/font_change_util.dart'; +export 'engine/font_fallback_data.dart'; +export 'engine/font_fallbacks.dart'; export 'engine/fonts.dart'; export 'engine/frame_reference.dart'; export 'engine/global_styles.dart'; @@ -106,6 +104,7 @@ export 'engine/html/surface_stats.dart'; export 'engine/html/transform.dart'; export 'engine/html_image_codec.dart'; export 'engine/initialization.dart'; +export 'engine/interval_tree.dart'; export 'engine/js_interop/js_loader.dart'; export 'engine/js_interop/js_promise.dart'; export 'engine/js_interop/js_typed_data.dart'; @@ -115,6 +114,7 @@ export 'engine/mouse_cursor.dart'; export 'engine/navigation/history.dart'; export 'engine/navigation/js_url_strategy.dart'; export 'engine/navigation/url_strategy.dart'; +export 'engine/noto_font.dart'; export 'engine/onscreen_logging.dart'; export 'engine/picture.dart'; export 'engine/platform_dispatcher.dart'; diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart b/lib/web_ui/lib/src/engine/font_fallback_data.dart similarity index 100% rename from lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart rename to lib/web_ui/lib/src/engine/font_fallback_data.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/font_fallbacks.dart similarity index 100% rename from lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart rename to lib/web_ui/lib/src/engine/font_fallbacks.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart b/lib/web_ui/lib/src/engine/interval_tree.dart similarity index 100% rename from lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart rename to lib/web_ui/lib/src/engine/interval_tree.dart diff --git a/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart b/lib/web_ui/lib/src/engine/noto_font.dart similarity index 98% rename from lib/web_ui/lib/src/engine/canvaskit/noto_font.dart rename to lib/web_ui/lib/src/engine/noto_font.dart index b16fbaa51c23e..737e758f4895b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart +++ b/lib/web_ui/lib/src/engine/noto_font.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../text/unicode_range.dart'; +import 'text/unicode_range.dart'; class NotoFont { NotoFont(this.name, this.url, this._packedRanges); From d58f7fc879bf4741cd3f99137e5dfd9cc92b5769 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Sun, 7 May 2023 22:55:02 -0700 Subject: [PATCH 20/27] Cascade text styles from outer scopes. --- .../skwasm/skwasm_impl/font_collection.dart | 37 +-- .../engine/skwasm/skwasm_impl/paragraph.dart | 250 ++++++++++-------- .../raw/text/raw_paragraph_style.dart | 4 +- .../skwasm_impl/raw/text/raw_text_style.dart | 10 +- lib/web_ui/skwasm/text/paragraph_style.cpp | 3 +- lib/web_ui/skwasm/text/text_style.cpp | 13 +- 6 files changed, 180 insertions(+), 137 deletions(-) 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 284481b026feb..718aaff44bb6d 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 @@ -35,12 +35,27 @@ class SkwasmTypeface { } class SkwasmFontCollection implements FlutterFontCollection { - SkwasmFontCollection() : handle = fontCollectionCreate(); - - FontCollectionHandle handle; + SkwasmFontCollection() { + setDefaultFontFamilies(['Roboto']); + } + FontCollectionHandle handle = fontCollectionCreate(); + TextStyleHandle defaultTextStyle = textStyleCreate(); final Map> registeredTypefaces = >{}; + void setDefaultFontFamilies(List families) => withStackScope((StackScope scope) { + final Pointer familyPointers = + scope.allocPointerArray(families.length).cast(); + for (int i = 0; i < families.length; i++) { + familyPointers[i] = skStringFromDartString(families[i]); + } + textStyleClearFontFamilies(defaultTextStyle); + textStyleAddFontFamilies(defaultTextStyle, familyPointers, families.length); + for (int i = 0; i < families.length; i++) { + skStringFree(familyPointers[i]); + } + }); + @override late final FontFallbackManager fontFallbackManager = FontFallbackManager(SkwasmFallbackRegistry(this)); @@ -210,18 +225,6 @@ class SkwasmFallbackRegistry implements FallbackFontRegistry { fontCollection.loadFontFromUrl(familyName, url); @override - void updateFallbackFontFamilies(List families) => withStackScope((StackScope scope) { - final Pointer familyPointers = - scope.allocPointerArray(families.length).cast(); - for (int i = 0; i < families.length; i++) { - familyPointers[i] = skStringFromDartString(families[i]); - } - fontCollectionSetDefaultFontFamilies( - fontCollection.handle, - familyPointers, - families.length); - for (int i = 0; i < families.length; i++) { - skStringFree(familyPointers[i]); - } - }); + void updateFallbackFontFamilies(List families) => + fontCollection.setDefaultFontFamilies(families); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index bb49a70db9af2..5599f98325a1c 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -202,78 +202,75 @@ void withScopedFontList( } class SkwasmTextStyle implements ui.TextStyle { - factory SkwasmTextStyle({ - ui.Color? color, - ui.TextDecoration? decoration, - ui.Color? decorationColor, - ui.TextDecorationStyle? decorationStyle, - double? decorationThickness, - ui.FontWeight? fontWeight, - ui.FontStyle? fontStyle, - ui.TextBaseline? textBaseline, - String? fontFamily, - List? fontFamilyFallback, - double? fontSize, - double? letterSpacing, - double? wordSpacing, - double? height, - ui.TextLeadingDistribution? leadingDistribution, - ui.Locale? locale, - ui.Paint? background, - ui.Paint? foreground, - List? shadows, - List? fontFeatures, - List? fontVariations, - }) { - final TextStyleHandle handle = textStyleCreate( - (renderer.fontCollection as SkwasmFontCollection).handle - ); + SkwasmTextStyle({ + this.color, + this.decoration, + this.decorationColor, + this.decorationStyle, + this.decorationThickness, + this.fontWeight, + this.fontStyle, + this.textBaseline, + this.fontFamily, + this.fontFamilyFallback, + this.fontSize, + this.letterSpacing, + this.wordSpacing, + this.height, + this.leadingDistribution, + this.locale, + this.background, + this.foreground, + this.shadows, + this.fontFeatures, + this.fontVariations, + }); + + void applyToHandle(TextStyleHandle handle) { if (color != null) { - textStyleSetColor(handle, color.value); + textStyleSetColor(handle, color!.value); } if (decoration != null) { - textStyleSetDecoration(handle, decoration.maskValue); + textStyleSetDecoration(handle, decoration!.maskValue); } if (decorationColor != null) { - textStyleSetDecorationColor(handle, decorationColor.value); + textStyleSetDecorationColor(handle, decorationColor!.value); } if (decorationStyle != null) { - textStyleSetDecorationStyle(handle, decorationStyle.index); + textStyleSetDecorationStyle(handle, decorationStyle!.index); } if (decorationThickness != null) { - textStyleSetDecorationThickness(handle, decorationThickness); + textStyleSetDecorationThickness(handle, decorationThickness!); } if (fontWeight != null || fontStyle != null) { - fontWeight ??= ui.FontWeight.normal; - fontStyle ??= ui.FontStyle.normal; - textStyleSetFontStyle(handle, fontWeight.value, fontStyle.index); + textStyleSetFontStyle( + handle, + (fontWeight ?? ui.FontWeight.normal).value, + (fontStyle ?? ui.FontStyle.normal).index + ); } if (textBaseline != null) { - textStyleSetTextBaseline(handle, textBaseline.index); + textStyleSetTextBaseline(handle, textBaseline!.index); } - List? fontFamilies; - if (fontFamily != null || fontFamilyFallback != null) { - fontFamilies = [ - if (fontFamily != null) fontFamily, - if (fontFamilyFallback != null) ...fontFamilyFallback, - ]; - if (fontFamilies.isNotEmpty) { - withScopedFontList(fontFamilies, - (Pointer families, int count) => - textStyleAddFontFamilies(handle, families, count)); - } + + final List effectiveFontFamilies = fontFamilies; + if (effectiveFontFamilies.isNotEmpty) { + withScopedFontList(effectiveFontFamilies, + (Pointer families, int count) => + textStyleAddFontFamilies(handle, families, count)); } + if (fontSize != null) { - textStyleSetFontSize(handle, fontSize); + textStyleSetFontSize(handle, fontSize!); } if (letterSpacing != null) { - textStyleSetLetterSpacing(handle, letterSpacing); + textStyleSetLetterSpacing(handle, letterSpacing!); } if (wordSpacing != null) { - textStyleSetWordSpacing(handle, wordSpacing); + textStyleSetWordSpacing(handle, wordSpacing!); } if (height != null) { - textStyleSetHeight(handle, height); + textStyleSetHeight(handle, height!); } if (leadingDistribution != null) { textStyleSetHalfLeading( @@ -283,20 +280,18 @@ class SkwasmTextStyle implements ui.TextStyle { } if (locale != null) { final SkStringHandle localeHandle = - skStringFromDartString(locale.toLanguageTag()); + skStringFromDartString(locale!.toLanguageTag()); textStyleSetLocale(handle, localeHandle); skStringFree(localeHandle); } if (background != null) { - background as SkwasmPaint; - textStyleSetBackground(handle, background.handle); + textStyleSetBackground(handle, (background! as SkwasmPaint).handle); } if (foreground != null) { - foreground as SkwasmPaint; - textStyleSetForeground(handle, foreground.handle); + textStyleSetForeground(handle, (foreground! as SkwasmPaint).handle); } if (shadows != null) { - for (final ui.Shadow shadow in shadows) { + for (final ui.Shadow shadow in shadows!) { textStyleAddShadow( handle, shadow.color.value, @@ -307,20 +302,40 @@ class SkwasmTextStyle implements ui.TextStyle { } } if (fontFeatures != null) { - for (final ui.FontFeature feature in fontFeatures) { + for (final ui.FontFeature feature in fontFeatures!) { final SkStringHandle featureName = skStringFromDartString(feature.feature); textStyleAddFontFeature(handle, featureName, feature.value); skStringFree(featureName); } } - // TODO(jacksongardner): Set font variations - return SkwasmTextStyle._(handle, fontFamilies); } - SkwasmTextStyle._(this.handle, this.fontFamilies); - - final TextStyleHandle handle; - final List? fontFamilies; + List get fontFamilies => [ + if (fontFamily != null) fontFamily!, + if (fontFamilyFallback != null) ...fontFamilyFallback!, + ]; + + final ui.Color? color; + final ui.TextDecoration? decoration; + final ui.Color? decorationColor; + final ui.TextDecorationStyle? decorationStyle; + final double? decorationThickness; + final ui.FontWeight? fontWeight; + final ui.FontStyle? fontStyle; + final ui.TextBaseline? textBaseline; + final String? fontFamily; + final List? fontFamilyFallback; + final double? fontSize; + final double? letterSpacing; + final double? wordSpacing; + final double? height; + final ui.TextLeadingDistribution? leadingDistribution; + final ui.Locale? locale; + final ui.Paint? background; + final ui.Paint? foreground; + final List? shadows; + final List? fontFeatures; + final List? fontVariations; } class SkwasmStrutStyle implements ui.StrutStyle { @@ -392,9 +407,7 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { String? ellipsis, ui.Locale? locale, }) { - final ParagraphStyleHandle handle = paragraphStyleCreate( - (renderer.fontCollection as SkwasmFontCollection).handle, - ); + final ParagraphStyleHandle handle = paragraphStyleCreate(); if (textAlign != null) { paragraphStyleSetTextAlign(handle, textAlign.index); } @@ -423,51 +436,52 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { strutStyle as SkwasmStrutStyle; paragraphStyleSetStrutStyle(handle, strutStyle.handle); } - if (fontFamily != null || - fontSize != null || - fontWeight != null || - fontStyle != null || - textHeightBehavior != null || - locale != null) { - final TextStyleHandle textStyleHandle = textStyleCreate( - (renderer.fontCollection as SkwasmFontCollection).handle, + final TextStyleHandle textStyleHandle = textStyleCopy( + (renderer.fontCollection as SkwasmFontCollection).defaultTextStyle, + ); + if (fontFamily != null) { + withScopedFontList([fontFamily], + (Pointer families, int count) => + textStyleAddFontFamilies(textStyleHandle, families, count)); + } + if (fontSize != null) { + textStyleSetFontSize(textStyleHandle, fontSize); + } + if (fontWeight != null || fontStyle != null) { + fontWeight ??= ui.FontWeight.normal; + fontStyle ??= ui.FontStyle.normal; + textStyleSetFontStyle(textStyleHandle, fontWeight.value, fontStyle.index); + } + if (textHeightBehavior != null) { + textStyleSetHalfLeading( + textStyleHandle, + textHeightBehavior.leadingDistribution == ui.TextLeadingDistribution.even, ); - if (fontFamily != null) { - withScopedFontList([fontFamily], - (Pointer families, int count) => - textStyleAddFontFamilies(textStyleHandle, families, count)); - } - if (fontSize != null) { - textStyleSetFontSize(textStyleHandle, fontSize); - } - if (fontWeight != null || fontStyle != null) { - fontWeight ??= ui.FontWeight.normal; - fontStyle ??= ui.FontStyle.normal; - textStyleSetFontStyle(textStyleHandle, fontWeight.value, fontStyle.index); - } - if (textHeightBehavior != null) { - textStyleSetHalfLeading( - textStyleHandle, - textHeightBehavior.leadingDistribution == ui.TextLeadingDistribution.even, - ); - } - if (locale != null) { - final SkStringHandle localeHandle = - skStringFromDartString(locale.toLanguageTag()); - textStyleSetLocale(textStyleHandle, localeHandle); - skStringFree(localeHandle); - } - paragraphStyleSetTextStyle(handle, textStyleHandle); } - return SkwasmParagraphStyle._(handle, fontFamily); + if (locale != null) { + final SkStringHandle localeHandle = + skStringFromDartString(locale.toLanguageTag()); + textStyleSetLocale(textStyleHandle, localeHandle); + skStringFree(localeHandle); + } + paragraphStyleSetTextStyle(handle, textStyleHandle); + return SkwasmParagraphStyle._(handle, textStyleHandle, fontFamily); } - SkwasmParagraphStyle._(this.handle, this.defaultFontFamily); + SkwasmParagraphStyle._(this.handle, this.textStyleHandle, this.defaultFontFamily); final ParagraphStyleHandle handle; + final TextStyleHandle textStyleHandle; final String? defaultFontFamily; } +class _TextStyleStackEntry { + _TextStyleStackEntry(this.style, this.handle); + + SkwasmTextStyle style; + TextStyleHandle handle; +} + class SkwasmParagraphBuilder implements ui.ParagraphBuilder { factory SkwasmParagraphBuilder( SkwasmParagraphStyle style, @@ -480,7 +494,7 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { SkwasmParagraphBuilder._(this.handle, this.style); final ParagraphBuilderHandle handle; final SkwasmParagraphStyle style; - final List textStyleStack = []; + final List<_TextStyleStackEntry> textStyleStack = <_TextStyleStackEntry>[]; @override List placeholderScales = []; @@ -508,7 +522,7 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { List _getEffectiveFonts() { final List fallbackFonts = renderer.fontCollection.fontFallbackManager!.globalFontFallbacks; final List? currentFonts = - textStyleStack.isEmpty ? null : textStyleStack.last.fontFamilies; + textStyleStack.isEmpty ? null : textStyleStack.last.style.fontFamilies; if (currentFonts != null) { return [ ...currentFonts, @@ -526,7 +540,9 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override void addText(String text) { - renderer.fontCollection.fontFallbackManager!.ensureFontsSupportText(text, _getEffectiveFonts()); + renderer.fontCollection.fontFallbackManager!.ensureFontsSupportText( + text, _getEffectiveFonts() + ); final SkString16Handle stringHandle = skString16FromDartString(text); paragraphBuilderAddText(handle, stringHandle); skString16Free(stringHandle); @@ -542,14 +558,28 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { @override void pop() { - textStyleStack.removeLast(); + final TextStyleHandle textStyleHandle = textStyleStack.removeLast().handle; + textStyleDispose(textStyleHandle); paragraphBuilderPop(handle); } @override - void pushStyle(ui.TextStyle style) { - style as SkwasmTextStyle; - textStyleStack.add(style); - paragraphBuilderPushStyle(handle, style.handle); + void pushStyle(ui.TextStyle textStyle) { + textStyle as SkwasmTextStyle; + TextStyleHandle sourceStyleHandle = nullptr; + if (textStyleStack.isNotEmpty) { + sourceStyleHandle = textStyleStack.last.handle; + } + if (sourceStyleHandle == nullptr) { + sourceStyleHandle = style.textStyleHandle; + } + if (sourceStyleHandle == nullptr) { + sourceStyleHandle = + (renderer.fontCollection as SkwasmFontCollection).defaultTextStyle; + } + final TextStyleHandle styleHandle = textStyleCopy(sourceStyleHandle); + textStyle.applyToHandle(styleHandle); + textStyleStack.add(_TextStyleStackEntry(textStyle, styleHandle)); + paragraphBuilderPushStyle(handle, styleHandle); } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart index 48de046f76bfe..e65a5f7690066 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart @@ -12,8 +12,8 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; final class RawParagraphStyle extends Opaque {} typedef ParagraphStyleHandle = Pointer; -@Native(symbol: 'paragraphStyle_create', isLeaf: true) -external ParagraphStyleHandle paragraphStyleCreate(FontCollectionHandle fontCollection); +@Native(symbol: 'paragraphStyle_create', isLeaf: true) +external ParagraphStyleHandle paragraphStyleCreate(); @Native(symbol: 'paragraphStyle_dispose', isLeaf: true) external void paragraphStyleDispose(ParagraphStyleHandle handle); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart index d994237929f5c..6a7d49f9d6012 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart @@ -13,8 +13,11 @@ final class RawTextStyle extends Opaque {} typedef TextStyleHandle = Pointer; -@Native(symbol: 'textStyle_create', isLeaf: true) -external TextStyleHandle textStyleCreate(FontCollectionHandle handle); +@Native(symbol: 'textStyle_create', isLeaf: true) +external TextStyleHandle textStyleCreate(); + +@Native(symbol: 'textStyle_copy', isLeaf: true) +external TextStyleHandle textStyleCopy(TextStyleHandle handle); @Native(symbol: 'textStyle_dispose', isLeaf: true) external void textStyleDispose(TextStyleHandle handle); @@ -48,6 +51,9 @@ external void textStyleSetFontStyle( @Native(symbol: 'textStyle_setTextBaseline', isLeaf: true) external void textStyleSetTextBaseline(TextStyleHandle handle, int baseline); +@Native(symbol: 'textStyle_clearFontFamilies', isLeaf: true) +external void textStyleClearFontFamilies(TextStyleHandle handle); + @Native, diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index e72f30a4fdad6..23414f2fcf5bf 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -9,7 +9,7 @@ using namespace skia::textlayout; using namespace Skwasm; -SKWASM_EXPORT ParagraphStyle* paragraphStyle_create(FlutterFontCollection *collection) { +SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { auto style = new ParagraphStyle(); // This is the default behavior in Flutter @@ -18,7 +18,6 @@ SKWASM_EXPORT ParagraphStyle* paragraphStyle_create(FlutterFontCollection *colle // Default text style has a black color TextStyle textStyle; textStyle.setColor(SK_ColorBLACK); - textStyle.setFontFamilies(collection->fallbackFontFamilies); style->setTextStyle(textStyle); return style; diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index 5f587fe57e4a2..62a35a8788703 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -9,17 +9,18 @@ using namespace skia::textlayout; using namespace Skwasm; -SKWASM_EXPORT TextStyle* textStyle_create(FlutterFontCollection* collection) { +SKWASM_EXPORT TextStyle* textStyle_create() { auto style = new TextStyle(); // Default color in flutter is black. style->setColor(SK_ColorBLACK); - - // Add fallback fonts - style->setFontFamilies(collection->fallbackFontFamilies); return style; } +SKWASM_EXPORT TextStyle* textStyle_copy(TextStyle *style) { + return new TextStyle(*style); +} + SKWASM_EXPORT void textStyle_dispose(TextStyle* style) { delete style; } @@ -60,6 +61,10 @@ SKWASM_EXPORT void textStyle_setTextBaseline(TextStyle* style, style->setTextBaseline(baseline); } +SKWASM_EXPORT void textStyle_clearFontFamilies(TextStyle* style) { + style->setFontFamilies({}); +} + SKWASM_EXPORT void textStyle_addFontFamilies(TextStyle* style, SkString** fontFamilies, int count) { From 408e6d724c111d50b7f52417996ae31f24c4c7f0 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 8 May 2023 10:32:21 -0700 Subject: [PATCH 21/27] Implemented font variations. --- .../engine/skwasm/skwasm_impl/paragraph.dart | 21 ++++++ .../skwasm_impl/raw/text/raw_text_style.dart | 13 ++++ lib/web_ui/skwasm/text/text_style.cpp | 20 +++++- .../canvaskit/font_variation_golden_test.dart | 65 ------------------- lib/web_ui/test/ui/text_golden_test.dart | 42 ++++++++++++ 5 files changed, 95 insertions(+), 66 deletions(-) delete mode 100644 lib/web_ui/test/canvaskit/font_variation_golden_test.dart diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 5599f98325a1c..43b4bea609aad 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -308,6 +308,27 @@ class SkwasmTextStyle implements ui.TextStyle { skStringFree(featureName); } } + + if (fontVariations != null && fontVariations!.isNotEmpty) { + final int variationCount = fontVariations!.length; + withStackScope((StackScope scope) { + final Pointer axisBuffer = scope.allocUint32Array(variationCount); + final Pointer valueBuffer = scope.allocFloatArray(variationCount); + for (int i = 0; i < variationCount; i++) { + final ui.FontVariation variation = fontVariations![i]; + final String axis = variation.axis; + assert(axis.length == 4); // 4 byte code + final int axisNumber = + axis.codeUnitAt(0) << 24 | + axis.codeUnitAt(1) << 16 | + axis.codeUnitAt(2) << 8 | + axis.codeUnitAt(3); + axisBuffer[i] = axisNumber; + valueBuffer[i] = variation.value; + } + textStyleSetFontVariations(handle, axisBuffer, valueBuffer, variationCount); + }); + } } List get fontFamilies => [ diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart index 6a7d49f9d6012..3a8aca91674ae 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart @@ -114,3 +114,16 @@ external void textStyleAddFontFeature( SkStringHandle featureName, int value, ); + +@Native, + Pointer, + Int +)>(symbol: 'textStyle_setFontVariations', isLeaf: true) +external void textStyleSetFontVariations( + TextStyleHandle handle, + Pointer axes, + Pointer values, + int count, +); diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index 62a35a8788703..76acdea194b68 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -129,4 +129,22 @@ SKWASM_EXPORT void textStyle_addFontFeature(TextStyle* style, style->addFontFeature(*featureName, value); } -// TODO(jacksongardner): implement font variations +SKWASM_EXPORT void textStyle_setFontVariations( + TextStyle* style, + SkFourByteTag* axes, + float* values, + int count +) { + std::vector coordinates; + for (int i = 0; i < count; i++) { + coordinates.push_back({ + axes[i], + values[i] + }); + } + SkFontArguments::VariationPosition position = { + coordinates.data(), + static_cast(coordinates.size()) + }; + style->setFontArguments(SkFontArguments().setVariationDesignPosition(position)); +} diff --git a/lib/web_ui/test/canvaskit/font_variation_golden_test.dart b/lib/web_ui/test/canvaskit/font_variation_golden_test.dart deleted file mode 100644 index 3b856829dc0b0..0000000000000 --- a/lib/web_ui/test/canvaskit/font_variation_golden_test.dart +++ /dev/null @@ -1,65 +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 'package:test/bootstrap/browser.dart'; -import 'package:test/test.dart'; - -import 'package:ui/src/engine.dart'; -import 'package:ui/ui.dart' as ui; - -import 'common.dart'; - -void main() { - internalBootstrapBrowserTest(() => testMain); -} - -void testMain() { - setUpAll(() async { - await ui.webOnlyInitializePlatform(); - }); - - group('font variation', () { - test('is correctly rendered', () async { - const double testWidth = 300; - final CkPictureRecorder recorder = CkPictureRecorder(); - final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest); - final CkParagraphBuilder builder = - CkParagraphBuilder(CkParagraphStyle( - fontSize: 40.0, - textDirection: ui.TextDirection.ltr, - )); - - builder.pushStyle(CkTextStyle( - fontFamily: 'RobotoVariable', - )); - builder.addText('Normal\n'); - builder.pop(); - - ui.FontVariation weight(double w) => ui.FontVariation('wght', w); - builder.pushStyle(CkTextStyle( - fontFamily: 'RobotoVariable', - fontVariations: [weight(900)], - )); - builder.addText('Heavy\n'); - builder.pop(); - - builder.pushStyle(CkTextStyle( - fontFamily: 'RobotoVariable', - fontVariations: [weight(100)], - )); - builder.addText('Light\n'); - builder.pop(); - - final CkParagraph paragraph = builder.build(); - paragraph.layout(const ui.ParagraphConstraints(width: testWidth - 20)); - canvas.drawParagraph(paragraph, const ui.Offset(10, 10)); - final CkPicture picture = recorder.endRecording(); - await matchPictureGolden( - 'font_variation.png', - picture, - region: ui.Rect.fromLTRB(0, 0, testWidth, paragraph.height + 20), - ); - }); - }); -} diff --git a/lib/web_ui/test/ui/text_golden_test.dart b/lib/web_ui/test/ui/text_golden_test.dart index c4cf8b30736b9..f77a583df5d79 100644 --- a/lib/web_ui/test/ui/text_golden_test.dart +++ b/lib/web_ui/test/ui/text_golden_test.dart @@ -510,6 +510,48 @@ Future testMain() async { test('emoji text with skin tone', () async { await testSampleText('emoji_with_skin_tone', '👋🏿 👋🏾 👋🏽 👋🏼 👋🏻'); }, timeout: const Timeout.factor(2)); + + test('font variations are correctly rendered', () async { + const double testWidth = 300; + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + final ui.ParagraphBuilder builder = + ui.ParagraphBuilder(ui.ParagraphStyle( + fontSize: 40.0, + textDirection: ui.TextDirection.ltr, + )); + + builder.pushStyle(ui.TextStyle( + fontFamily: 'RobotoVariable', + )); + builder.addText('Normal\n'); + builder.pop(); + + ui.FontVariation weight(double w) => ui.FontVariation('wght', w); + builder.pushStyle(ui.TextStyle( + fontFamily: 'RobotoVariable', + fontVariations: [weight(900)], + )); + builder.addText('Heavy\n'); + builder.pop(); + + builder.pushStyle(ui.TextStyle( + fontFamily: 'RobotoVariable', + fontVariations: [weight(100)], + )); + builder.addText('Light\n'); + builder.pop(); + + final ui.Paragraph paragraph = builder.build(); + paragraph.layout(const ui.ParagraphConstraints(width: testWidth - 20)); + canvas.drawParagraph(paragraph, const ui.Offset(10, 10)); + final ui.Picture picture = recorder.endRecording(); + await drawPictureUsingCurrentRenderer(picture); + await matchGoldenFile( + 'ui_text_font_variation.png', + region: ui.Rect.fromLTRB(0, 0, testWidth, paragraph.height + 20), + ); + }); } /// A convenience function for testing paragraph and text styles. From bdaeddd56b2e70e9bde3f2a7235c3dfa27b9058a Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 8 May 2023 12:01:09 -0700 Subject: [PATCH 22/27] Ported font fallback tests. --- .../skwasm/skwasm_impl/font_collection.dart | 4 +- .../skwasm/skwasm_impl/raw/raw_fonts.dart | 11 --- lib/web_ui/skwasm/fonts.cpp | 14 ---- lib/web_ui/skwasm/wrappers.h | 1 - .../fallback_fonts_golden_test.dart | 76 ++++++++++--------- 5 files changed, 44 insertions(+), 62 deletions(-) rename lib/web_ui/test/{canvaskit => ui}/fallback_fonts_golden_test.dart (88%) 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 718aaff44bb6d..31d4af62a74b7 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 @@ -57,7 +57,7 @@ class SkwasmFontCollection implements FlutterFontCollection { }); @override - late final FontFallbackManager fontFallbackManager = + late FontFallbackManager fontFallbackManager = FontFallbackManager(SkwasmFallbackRegistry(this)); @override @@ -187,6 +187,8 @@ class SkwasmFontCollection implements FlutterFontCollection { @override void debugResetFallbackFonts() { + setDefaultFontFamilies([]); + fontFallbackManager = FontFallbackManager(SkwasmFallbackRegistry(this)); } } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart index 3d00faf631167..83ce54e125456 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart @@ -50,14 +50,3 @@ external void fontCollectionRegisterTypeface( TypefaceHandle typeface, SkStringHandle fontName, ); - -@Native, - Int, -)>(symbol: 'fontCollection_setDefaultFontFamilies', isLeaf: true) -external void fontCollectionSetDefaultFontFamilies( - FontCollectionHandle handle, - Pointer families, - int familyCount, -); diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index 26dd690ed8d12..c29233886a70e 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -21,7 +21,6 @@ SKWASM_EXPORT FlutterFontCollection* fontCollection_create() { return new FlutterFontCollection{ std::move(collection), std::move(provider), - { SkString("Roboto") }, }; } @@ -84,16 +83,3 @@ SKWASM_EXPORT void fontCollection_registerTypeface( collection->provider->registerTypeface(sk_sp(typeface)); } } - -SKWASM_EXPORT void fontCollection_setDefaultFontFamilies( - FlutterFontCollection* collection, - SkString **familyNames, - int familyCount -) { - std::vector families; - families.reserve(familyCount); - for (int i = 0; i < familyCount; i++) { - families.push_back(*familyNames[i]); - } - collection->fallbackFontFamilies = families; -} diff --git a/lib/web_ui/skwasm/wrappers.h b/lib/web_ui/skwasm/wrappers.h index 53c11b77f3570..ffb356bba9c67 100644 --- a/lib/web_ui/skwasm/wrappers.h +++ b/lib/web_ui/skwasm/wrappers.h @@ -36,7 +36,6 @@ inline void makeCurrent(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle) { struct FlutterFontCollection { sk_sp collection; sk_sp provider; - std::vector fallbackFontFamilies; }; } // namespace Skwasm diff --git a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart b/lib/web_ui/test/ui/fallback_fonts_golden_test.dart similarity index 88% rename from lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart rename to lib/web_ui/test/ui/fallback_fonts_golden_test.dart index b5cdc1a27e597..8d84501cb1926 100644 --- a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/ui/fallback_fonts_golden_test.dart @@ -9,8 +9,10 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; +import 'package:web_engine_tester/golden_tester.dart'; -import 'common.dart'; +import '../common/test_initialization.dart'; +import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -20,9 +22,12 @@ const ui.Rect kDefaultRegion = ui.Rect.fromLTRB(0, 0, 100, 100); void testMain() { group('Font fallbacks', () { - setUpCanvasKitTest(); + setUpUnitTests( + emulateTesterEnvironment: false, + setUpTestViewDimensions: false, + ); - setUpAll(() { + setUp(() { debugDisableFontFallbacks = false; }); @@ -53,8 +58,8 @@ void testMain() { // Creating this paragraph should cause us to start to download the // fallback font. - CkParagraphBuilder pb = CkParagraphBuilder( - CkParagraphStyle(), + ui.ParagraphBuilder pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.addText('مرحبا'); @@ -63,27 +68,27 @@ void testMain() { expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, contains('Noto Sans Arabic')); - final CkPictureRecorder recorder = CkPictureRecorder(); - final CkCanvas canvas = recorder.beginRecording(kDefaultRegion); + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); - pb = CkParagraphBuilder( - CkParagraphStyle(), + pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.pushStyle(ui.TextStyle(fontSize: 32)); pb.addText('مرحبا'); pb.pop(); - final CkParagraph paragraph = pb.build(); + final ui.Paragraph paragraph = pb.build(); paragraph.layout(const ui.ParagraphConstraints(width: 1000)); canvas.drawParagraph(paragraph, ui.Offset.zero); + await drawPictureUsingCurrentRenderer(recorder.endRecording()); - await matchPictureGolden( - 'canvaskit_font_fallback_arabic.png', - recorder.endRecording(), + await matchGoldenFile( + 'ui_font_fallback_arabic.png', region: kDefaultRegion, ); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 - }, skip: isSafari || isFirefox); + }); test('will put the Noto Color Emoji font before other fallback fonts in the list', () async { @@ -91,8 +96,8 @@ void testMain() { // Creating this paragraph should cause us to start to download the // Arabic fallback font. - CkParagraphBuilder pb = CkParagraphBuilder( - CkParagraphStyle(), + ui.ParagraphBuilder pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.addText('مرحبا'); @@ -101,13 +106,13 @@ void testMain() { expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, ['Roboto', 'Noto Sans Arabic']); - pb = CkParagraphBuilder( - CkParagraphStyle(), + pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.pushStyle(ui.TextStyle(fontSize: 26)); pb.addText('Hello 😊 مرحبا'); pb.pop(); - final CkParagraph paragraph = pb.build(); + final ui.Paragraph paragraph = pb.build(); paragraph.layout(const ui.ParagraphConstraints(width: 1000)); await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); @@ -125,8 +130,8 @@ void testMain() { // Creating this paragraph should cause us to start to download the // fallback font. - CkParagraphBuilder pb = CkParagraphBuilder( - CkParagraphStyle(), + ui.ParagraphBuilder pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.addText('Hello 😊'); @@ -135,27 +140,27 @@ void testMain() { expect(renderer.fontCollection.fontFallbackManager!.globalFontFallbacks, contains('Noto Color Emoji')); - final CkPictureRecorder recorder = CkPictureRecorder(); - final CkCanvas canvas = recorder.beginRecording(kDefaultRegion); + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); - pb = CkParagraphBuilder( - CkParagraphStyle(), + pb = ui.ParagraphBuilder( + ui.ParagraphStyle(), ); pb.pushStyle(ui.TextStyle(fontSize: 26)); pb.addText('Hello 😊'); pb.pop(); - final CkParagraph paragraph = pb.build(); + final ui.Paragraph paragraph = pb.build(); paragraph.layout(const ui.ParagraphConstraints(width: 1000)); canvas.drawParagraph(paragraph, ui.Offset.zero); + await drawPictureUsingCurrentRenderer(recorder.endRecording()); - await matchPictureGolden( - 'canvaskit_font_fallback_emoji.png', - recorder.endRecording(), + await matchGoldenFile( + 'ui_font_fallback_emoji.png', region: kDefaultRegion, ); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 - }, skip: isSafari || isFirefox); + }); // 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 @@ -165,7 +170,7 @@ void testMain() { () async { // Try rendering text that requires fallback fonts, initially before the fonts are loaded. - CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); + ui.ParagraphBuilder(ui.ParagraphStyle()).addText('ヽಠ'); await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect( downloadedFontFamilies, @@ -177,7 +182,7 @@ void testMain() { // Do the same thing but this time with loaded fonts. downloadedFontFamilies.clear(); - CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); + ui.ParagraphBuilder(ui.ParagraphStyle()).addText('ヽಠ'); await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect(downloadedFontFamilies, isEmpty); }); @@ -185,7 +190,7 @@ void testMain() { test('can find glyph for 2/3 symbol', () async { // Try rendering text that requires fallback fonts, initially before the fonts are loaded. - CkParagraphBuilder(CkParagraphStyle()).addText('⅔'); + ui.ParagraphBuilder(ui.ParagraphStyle()).addText('⅔'); await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect( downloadedFontFamilies, @@ -196,7 +201,7 @@ void testMain() { // Do the same thing but this time with loaded fonts. downloadedFontFamilies.clear(); - CkParagraphBuilder(CkParagraphStyle()).addText('⅔'); + ui.ParagraphBuilder(ui.ParagraphStyle()).addText('⅔'); await renderer.fontCollection.fontFallbackManager!.debugWhenIdle(); expect(downloadedFontFamilies, isEmpty); }); @@ -404,6 +409,7 @@ void testMain() { } }); }, - skip: isSafari, + // HTML renderer doesn't use the fallback font manager. + skip: isHtml, timeout: const Timeout.factor(4)); } From 841cf5dde38f3bfd0dd6c35062b297cf9d8d7e75 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 8 May 2023 16:02:17 -0700 Subject: [PATCH 23/27] Formatting fixes. --- .../lib/src/engine/canvaskit/fonts.dart | 2 +- .../skwasm/skwasm_impl/font_collection.dart | 6 +- .../engine/skwasm/skwasm_impl/paragraph.dart | 14 +- .../engine/skwasm/skwasm_impl/renderer.dart | 2 +- .../lib/src/engine/text/font_collection.dart | 2 +- lib/web_ui/skwasm/canvas.cpp | 10 +- lib/web_ui/skwasm/fonts.cpp | 23 +-- lib/web_ui/skwasm/string.cpp | 8 +- lib/web_ui/skwasm/text/line_metrics.cpp | 48 +++--- lib/web_ui/skwasm/text/paragraph.cpp | 138 ++++++++---------- lib/web_ui/skwasm/text/paragraph_builder.cpp | 51 +++---- lib/web_ui/skwasm/text/paragraph_style.cpp | 64 ++++---- lib/web_ui/skwasm/text/text_style.cpp | 26 ++-- lib/web_ui/test/canvaskit/common.dart | 4 +- 14 files changed, 188 insertions(+), 210 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 4e1dfc0d1beec..79ee8fc4aafbf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -220,7 +220,7 @@ class SkiaFontCollection implements FlutterFontCollection { @override void clear() {} - + @override void debugResetFallbackFonts() { fontFallbackManager = FontFallbackManager(SkiaFallbackRegistry(this)); 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 31d4af62a74b7..979da4ac2aff4 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 @@ -184,7 +184,7 @@ class SkwasmFontCollection implements FlutterFontCollection { } return true; } - + @override void debugResetFallbackFonts() { setDefaultFontFamilies([]); @@ -202,7 +202,7 @@ class SkwasmFallbackRegistry implements FallbackFontRegistry { => withStackScope((StackScope scope) { final List typefaces = fontFamilies .map((String family) => fontCollection.registeredTypefaces[family]) - .fold(const Iterable.empty(), + .fold(const Iterable.empty(), (Iterable accumulated, List? typefaces) => typefaces == null ? accumulated : accumulated.followedBy(typefaces)).toList(); final Pointer typefaceBuffer = scope.allocPointerArray(typefaces.length).cast(); @@ -227,6 +227,6 @@ class SkwasmFallbackRegistry implements FallbackFontRegistry { fontCollection.loadFontFromUrl(familyName, url); @override - void updateFallbackFontFamilies(List families) => + void updateFallbackFontFamilies(List families) => fontCollection.setDefaultFontFamilies(families); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index 43b4bea609aad..ce0e4f17ba84c 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -80,7 +80,7 @@ class SkwasmParagraph implements ui.Paragraph { bool get didExceedMaxLines => paragraphGetDidExceedMaxLines(handle); @override - void layout(ui.ParagraphConstraints constraints) => + void layout(ui.ParagraphConstraints constraints) => paragraphLayout(handle, constraints.width); List _convertTextBoxList(TextBoxListHandle listHandle) { @@ -88,7 +88,7 @@ class SkwasmParagraph implements ui.Paragraph { return withStackScope((StackScope scope) { final RawRect tempRect = scope.allocFloatArray(4); return List.generate(length, (int index) { - final int textDirectionIndex = + final int textDirectionIndex = textBoxListGetBoxAtIndex(listHandle, index, tempRect); return ui.TextBox.fromLTRBD( tempRect[0], @@ -145,7 +145,7 @@ class SkwasmParagraph implements ui.Paragraph { @override ui.TextRange getLineBoundary(ui.TextPosition position) { final int lineNumber = paragraphGetLineNumberAt(handle, position.offset); - final LineMetricsHandle metricsHandle = + final LineMetricsHandle metricsHandle = paragraphGetLineMetricsAtIndex(handle, lineNumber); final ui.TextRange range = ui.TextRange( start: lineMetricsGetStartIndex(metricsHandle), @@ -166,7 +166,7 @@ class SkwasmParagraph implements ui.Paragraph { @override List computeLineMetrics() { final int lineCount = paragraphGetLineCount(handle); - return List.generate(lineCount, + return List.generate(lineCount, (int index) => SkwasmLineMetrics._(paragraphGetLineMetricsAtIndex(handle, index)) ); } @@ -318,7 +318,7 @@ class SkwasmTextStyle implements ui.TextStyle { final ui.FontVariation variation = fontVariations![i]; final String axis = variation.axis; assert(axis.length == 4); // 4 byte code - final int axisNumber = + final int axisNumber = axis.codeUnitAt(0) << 24 | axis.codeUnitAt(1) << 16 | axis.codeUnitAt(2) << 8 | @@ -461,7 +461,7 @@ class SkwasmParagraphStyle implements ui.ParagraphStyle { (renderer.fontCollection as SkwasmFontCollection).defaultTextStyle, ); if (fontFamily != null) { - withScopedFontList([fontFamily], + withScopedFontList([fontFamily], (Pointer families, int count) => textStyleAddFontFamilies(textStyleHandle, families, count)); } @@ -595,7 +595,7 @@ class SkwasmParagraphBuilder implements ui.ParagraphBuilder { sourceStyleHandle = style.textStyleHandle; } if (sourceStyleHandle == nullptr) { - sourceStyleHandle = + sourceStyleHandle = (renderer.fontCollection as SkwasmFontCollection).defaultTextStyle; } final TextStyleHandle styleHandle = textStyleCopy(sourceStyleHandle); 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 f3cfd38bf2c33..cb3be07646535 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 @@ -105,7 +105,7 @@ class SkwasmRenderer implements Renderer { ui.Paint createPaint() => SkwasmPaint(); @override - ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => + ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => SkwasmParagraphBuilder(style as SkwasmParagraphStyle, fontCollection); @override diff --git a/lib/web_ui/lib/src/engine/text/font_collection.dart b/lib/web_ui/lib/src/engine/text/font_collection.dart index bf1612ff2dcae..95d0fea5eb80f 100644 --- a/lib/web_ui/lib/src/engine/text/font_collection.dart +++ b/lib/web_ui/lib/src/engine/text/font_collection.dart @@ -174,7 +174,7 @@ class HtmlFontCollection implements FlutterFontCollection { } return true; } - + @override void debugResetFallbackFonts() { } diff --git a/lib/web_ui/skwasm/canvas.cpp b/lib/web_ui/skwasm/canvas.cpp index 6edcdbecfa334..bad7efee94ec9 100644 --- a/lib/web_ui/skwasm/canvas.cpp +++ b/lib/web_ui/skwasm/canvas.cpp @@ -208,12 +208,10 @@ SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper, devicePixelRatio * kShadowLightRadius, outAmbient, outSpot, flags); } -SKWASM_EXPORT void canvas_drawParagraph( - CanvasWrapper *wrapper, - Paragraph *paragraph, - SkScalar x, - SkScalar y -) { +SKWASM_EXPORT void canvas_drawParagraph(CanvasWrapper* wrapper, + Paragraph* paragraph, + SkScalar x, + SkScalar y) { paragraph->paint(wrapper->canvas, x, y); } diff --git a/lib/web_ui/skwasm/fonts.cpp b/lib/web_ui/skwasm/fonts.cpp index c29233886a70e..313e24dd63eb6 100644 --- a/lib/web_ui/skwasm/fonts.cpp +++ b/lib/web_ui/skwasm/fonts.cpp @@ -3,10 +3,10 @@ // found in the LICENSE file. #include "export.h" -#include "wrappers.h" #include "third_party/skia/include/core/SkFontMgr.h" #include "third_party/skia/modules/skparagraph/include/FontCollection.h" #include "third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h" +#include "wrappers.h" #include @@ -35,26 +35,27 @@ SKWASM_EXPORT SkTypeface* typeface_create(SkData* fontData) { return typeface.release(); } -SKWASM_EXPORT void typeface_dispose(SkTypeface *typeface) { +SKWASM_EXPORT void typeface_dispose(SkTypeface* typeface) { typeface->unref(); } // Calculates the code points that are not covered by the specified typefaces. // This function mutates the `codePoints` buffer in place and returns the count // of code points that are not covered by the fonts. -SKWASM_EXPORT int typefaces_filterCoveredCodePoints( - SkTypeface **typefaces, - int typefaceCount, - SkUnichar* codePoints, - int codePointCount -) { - std::unique_ptr glyphBuffer = std::make_unique(codePointCount); +SKWASM_EXPORT int typefaces_filterCoveredCodePoints(SkTypeface** typefaces, + int typefaceCount, + SkUnichar* codePoints, + int codePointCount) { + std::unique_ptr glyphBuffer = + std::make_unique(codePointCount); SkGlyphID* glyphPointer = glyphBuffer.get(); int remainingCodePointCount = codePointCount; for (int typefaceIndex = 0; typefaceIndex < typefaceCount; typefaceIndex++) { - typefaces[typefaceIndex]->unicharsToGlyphs(codePoints, remainingCodePointCount, glyphPointer); + typefaces[typefaceIndex]->unicharsToGlyphs( + codePoints, remainingCodePointCount, glyphPointer); int outputIndex = 0; - for (int inputIndex = 0; inputIndex < remainingCodePointCount; inputIndex++) { + for (int inputIndex = 0; inputIndex < remainingCodePointCount; + inputIndex++) { if (glyphPointer[inputIndex] == 0) { if (outputIndex != inputIndex) { codePoints[outputIndex] = codePoints[inputIndex]; diff --git a/lib/web_ui/skwasm/string.cpp b/lib/web_ui/skwasm/string.cpp index 54aa669d33de3..d7591a546c462 100644 --- a/lib/web_ui/skwasm/string.cpp +++ b/lib/web_ui/skwasm/string.cpp @@ -18,16 +18,16 @@ SKWASM_EXPORT void skString_free(SkString* string) { return delete string; } -SKWASM_EXPORT std::u16string *skString16_allocate(size_t length) { - std::u16string *string = new std::u16string(); +SKWASM_EXPORT std::u16string* skString16_allocate(size_t length) { + std::u16string* string = new std::u16string(); string->resize(length); return string; } -SKWASM_EXPORT char16_t *skString16_getData(std::u16string *string) { +SKWASM_EXPORT char16_t* skString16_getData(std::u16string* string) { return string->data(); } -SKWASM_EXPORT void skString16_free(std::u16string *string) { +SKWASM_EXPORT void skString16_free(std::u16string* string) { delete string; } diff --git a/lib/web_ui/skwasm/text/line_metrics.cpp b/lib/web_ui/skwasm/text/line_metrics.cpp index 968df58c4c357..c7e13c214e033 100644 --- a/lib/web_ui/skwasm/text/line_metrics.cpp +++ b/lib/web_ui/skwasm/text/line_metrics.cpp @@ -7,50 +7,50 @@ using namespace skia::textlayout; -SKWASM_EXPORT void lineMetrics_dispose(LineMetrics *metrics) { - delete metrics; +SKWASM_EXPORT void lineMetrics_dispose(LineMetrics* metrics) { + delete metrics; } -SKWASM_EXPORT bool lineMetrics_getHardBreak(LineMetrics *metrics) { - return metrics->fHardBreak; +SKWASM_EXPORT bool lineMetrics_getHardBreak(LineMetrics* metrics) { + return metrics->fHardBreak; } -SKWASM_EXPORT SkScalar lineMetrics_getAscent(LineMetrics *metrics) { - return metrics->fAscent; +SKWASM_EXPORT SkScalar lineMetrics_getAscent(LineMetrics* metrics) { + return metrics->fAscent; } -SKWASM_EXPORT SkScalar lineMetrics_getDescent(LineMetrics *metrics) { - return metrics->fDescent; +SKWASM_EXPORT SkScalar lineMetrics_getDescent(LineMetrics* metrics) { + return metrics->fDescent; } -SKWASM_EXPORT SkScalar lineMetrics_getUnscaledAscent(LineMetrics *metrics) { - return metrics->fUnscaledAscent; +SKWASM_EXPORT SkScalar lineMetrics_getUnscaledAscent(LineMetrics* metrics) { + return metrics->fUnscaledAscent; } -SKWASM_EXPORT SkScalar lineMetrics_getHeight(LineMetrics *metrics) { - return metrics->fHeight; +SKWASM_EXPORT SkScalar lineMetrics_getHeight(LineMetrics* metrics) { + return metrics->fHeight; } -SKWASM_EXPORT SkScalar lineMetrics_getWidth(LineMetrics *metrics) { - return metrics->fWidth; +SKWASM_EXPORT SkScalar lineMetrics_getWidth(LineMetrics* metrics) { + return metrics->fWidth; } -SKWASM_EXPORT SkScalar lineMetrics_getLeft(LineMetrics *metrics) { - return metrics->fLeft; +SKWASM_EXPORT SkScalar lineMetrics_getLeft(LineMetrics* metrics) { + return metrics->fLeft; } -SKWASM_EXPORT SkScalar lineMetrics_getBaseline(LineMetrics *metrics) { - return metrics->fBaseline; +SKWASM_EXPORT SkScalar lineMetrics_getBaseline(LineMetrics* metrics) { + return metrics->fBaseline; } -SKWASM_EXPORT int lineMetrics_getLineNumber(LineMetrics *metrics) { - return metrics->fLineNumber; +SKWASM_EXPORT int lineMetrics_getLineNumber(LineMetrics* metrics) { + return metrics->fLineNumber; } -SKWASM_EXPORT size_t lineMetrics_getStartIndex(LineMetrics *metrics) { - return metrics->fStartIndex; +SKWASM_EXPORT size_t lineMetrics_getStartIndex(LineMetrics* metrics) { + return metrics->fStartIndex; } -SKWASM_EXPORT size_t lineMetrics_getEndIndex(LineMetrics *metrics) { - return metrics->fEndIndex; +SKWASM_EXPORT size_t lineMetrics_getEndIndex(LineMetrics* metrics) { + return metrics->fEndIndex; } diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index 548f3001778e8..34fe9af8d93e5 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -2,129 +2,119 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "../export.h" #include "third_party/skia/modules/skparagraph/include/Paragraph.h" +#include "../export.h" using namespace skia::textlayout; -SKWASM_EXPORT void paragraph_dispose(Paragraph *paragraph) { - delete paragraph; +SKWASM_EXPORT void paragraph_dispose(Paragraph* paragraph) { + delete paragraph; } -SKWASM_EXPORT SkScalar paragraph_getWidth(Paragraph *paragraph) { - return paragraph->getMaxWidth(); +SKWASM_EXPORT SkScalar paragraph_getWidth(Paragraph* paragraph) { + return paragraph->getMaxWidth(); } -SKWASM_EXPORT SkScalar paragraph_getHeight(Paragraph *paragraph) { - return paragraph->getHeight(); +SKWASM_EXPORT SkScalar paragraph_getHeight(Paragraph* paragraph) { + return paragraph->getHeight(); } -SKWASM_EXPORT SkScalar paragraph_getLongestLine(Paragraph *paragraph) { - return paragraph->getLongestLine(); +SKWASM_EXPORT SkScalar paragraph_getLongestLine(Paragraph* paragraph) { + return paragraph->getLongestLine(); } -SKWASM_EXPORT SkScalar paragraph_getMinIntrinsicWidth(Paragraph *paragraph) { - return paragraph->getMinIntrinsicWidth(); +SKWASM_EXPORT SkScalar paragraph_getMinIntrinsicWidth(Paragraph* paragraph) { + return paragraph->getMinIntrinsicWidth(); } -SKWASM_EXPORT SkScalar paragraph_getMaxIntrinsicWidth(Paragraph *paragraph) { - return paragraph->getMaxIntrinsicWidth(); +SKWASM_EXPORT SkScalar paragraph_getMaxIntrinsicWidth(Paragraph* paragraph) { + return paragraph->getMaxIntrinsicWidth(); } -SKWASM_EXPORT SkScalar paragraph_getAlphabeticBaseline(Paragraph *paragraph) { - return paragraph->getAlphabeticBaseline(); +SKWASM_EXPORT SkScalar paragraph_getAlphabeticBaseline(Paragraph* paragraph) { + return paragraph->getAlphabeticBaseline(); } -SKWASM_EXPORT SkScalar paragraph_getIdeographicBaseline(Paragraph *paragraph) { - return paragraph->getIdeographicBaseline(); +SKWASM_EXPORT SkScalar paragraph_getIdeographicBaseline(Paragraph* paragraph) { + return paragraph->getIdeographicBaseline(); } -SKWASM_EXPORT bool paragraph_getDidExceedMaxLines(Paragraph *paragraph) { - return paragraph->didExceedMaxLines(); +SKWASM_EXPORT bool paragraph_getDidExceedMaxLines(Paragraph* paragraph) { + return paragraph->didExceedMaxLines(); } -SKWASM_EXPORT void paragraph_layout(Paragraph *paragraph, SkScalar width) { - paragraph->layout(width); +SKWASM_EXPORT void paragraph_layout(Paragraph* paragraph, SkScalar width) { + paragraph->layout(width); } -SKWASM_EXPORT int32_t paragraph_getPositionForOffset( - Paragraph *paragraph, - SkScalar offsetX, - SkScalar offsetY, - Affinity* outAffinity -) { - auto position = paragraph->getGlyphPositionAtCoordinate(offsetX, offsetY); - if (outAffinity) { - *outAffinity = position.affinity; - } - return position.position; +SKWASM_EXPORT int32_t paragraph_getPositionForOffset(Paragraph* paragraph, + SkScalar offsetX, + SkScalar offsetY, + Affinity* outAffinity) { + auto position = paragraph->getGlyphPositionAtCoordinate(offsetX, offsetY); + if (outAffinity) { + *outAffinity = position.affinity; + } + return position.position; } SKWASM_EXPORT void paragraph_getWordBoundary( - Paragraph *paragraph, + Paragraph* paragraph, unsigned int position, - int32_t *outRange // Two `int32_t`s, start and end + int32_t* outRange // Two `int32_t`s, start and end ) { - auto range = paragraph->getWordBoundary(position); - outRange[0] = range.start; - outRange[1] = range.end; + auto range = paragraph->getWordBoundary(position); + outRange[0] = range.start; + outRange[1] = range.end; } -SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph *paragraph) { - return paragraph->lineNumber(); +SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph* paragraph) { + return paragraph->lineNumber(); } -SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph *paragraph, size_t characterIndex) { - return paragraph->getLineNumberAt(characterIndex); +SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph* paragraph, + size_t characterIndex) { + return paragraph->getLineNumberAt(characterIndex); } -SKWASM_EXPORT LineMetrics *paragraph_getLineMetricsAtIndex( - Paragraph *paragraph, - size_t index -) { - auto metrics = new LineMetrics(); - paragraph->getLineMetricsAt(index, metrics); - return metrics; +SKWASM_EXPORT LineMetrics* paragraph_getLineMetricsAtIndex(Paragraph* paragraph, + size_t index) { + auto metrics = new LineMetrics(); + paragraph->getLineMetricsAt(index, metrics); + return metrics; } struct TextBoxList { - std::vector boxes; + std::vector boxes; }; -SKWASM_EXPORT void textBoxList_dispose(TextBoxList *list) { - delete list; +SKWASM_EXPORT void textBoxList_dispose(TextBoxList* list) { + delete list; } -SKWASM_EXPORT size_t textBoxList_getLength(TextBoxList *list) { - return list->boxes.size(); +SKWASM_EXPORT size_t textBoxList_getLength(TextBoxList* list) { + return list->boxes.size(); } -SKWASM_EXPORT TextDirection textBoxList_getBoxAtIndex( - TextBoxList *list, - size_t index, - SkRect *outRect -) { - const auto& box = list->boxes[index]; - *outRect = box.rect; - return box.direction; +SKWASM_EXPORT TextDirection textBoxList_getBoxAtIndex(TextBoxList* list, + size_t index, + SkRect* outRect) { + const auto& box = list->boxes[index]; + *outRect = box.rect; + return box.direction; } -SKWASM_EXPORT TextBoxList *paragraph_getBoxesForRange( - Paragraph *paragraph, +SKWASM_EXPORT TextBoxList* paragraph_getBoxesForRange( + Paragraph* paragraph, int start, int end, RectHeightStyle heightStyle, - RectWidthStyle widthStyle -) { - - return new TextBoxList{paragraph->getRectsForRange( - start, - end, - heightStyle, - widthStyle - )}; + RectWidthStyle widthStyle) { + return new TextBoxList{ + paragraph->getRectsForRange(start, end, heightStyle, widthStyle)}; } -SKWASM_EXPORT TextBoxList* paragraph_getBoxesForPlaceholders(Paragraph *paragraph) { - return new TextBoxList{paragraph->getRectsForPlaceholders()}; +SKWASM_EXPORT TextBoxList* paragraph_getBoxesForPlaceholders( + Paragraph* paragraph) { + return new TextBoxList{paragraph->getRectsForPlaceholders()}; } diff --git a/lib/web_ui/skwasm/text/paragraph_builder.cpp b/lib/web_ui/skwasm/text/paragraph_builder.cpp index 2a5c5dec2d84c..74b25fa5293de 100644 --- a/lib/web_ui/skwasm/text/paragraph_builder.cpp +++ b/lib/web_ui/skwasm/text/paragraph_builder.cpp @@ -9,52 +9,41 @@ using namespace skia::textlayout; using namespace Skwasm; -SKWASM_EXPORT ParagraphBuilder *paragraphBuilder_create( - ParagraphStyle *style, - FlutterFontCollection *collection -) { - return ParagraphBuilder::make(*style, collection->collection).release(); +SKWASM_EXPORT ParagraphBuilder* paragraphBuilder_create( + ParagraphStyle* style, + FlutterFontCollection* collection) { + return ParagraphBuilder::make(*style, collection->collection).release(); } -SKWASM_EXPORT void paragraphBuilder_dispose(ParagraphBuilder *builder) { - delete builder; +SKWASM_EXPORT void paragraphBuilder_dispose(ParagraphBuilder* builder) { + delete builder; } SKWASM_EXPORT void paragraphBuilder_addPlaceholder( - ParagraphBuilder *builder, + ParagraphBuilder* builder, SkScalar width, SkScalar height, PlaceholderAlignment alignment, SkScalar baselineOffset, - TextBaseline baseline -) { - builder->addPlaceholder(PlaceholderStyle( - width, - height, - alignment, - baseline, - baselineOffset - )); + TextBaseline baseline) { + builder->addPlaceholder( + PlaceholderStyle(width, height, alignment, baseline, baselineOffset)); } -SKWASM_EXPORT void paragraphBuilder_addText( - ParagraphBuilder *builder, - std::u16string *text -) { - builder->addText(*text); +SKWASM_EXPORT void paragraphBuilder_addText(ParagraphBuilder* builder, + std::u16string* text) { + builder->addText(*text); } -SKWASM_EXPORT void paragraphBuilder_pushStyle( - ParagraphBuilder *builder, - TextStyle *style -) { - builder->pushStyle(*style); +SKWASM_EXPORT void paragraphBuilder_pushStyle(ParagraphBuilder* builder, + TextStyle* style) { + builder->pushStyle(*style); } -SKWASM_EXPORT void paragraphBuilder_pop(ParagraphBuilder *builder) { - builder->pop(); +SKWASM_EXPORT void paragraphBuilder_pop(ParagraphBuilder* builder) { + builder->pop(); } -SKWASM_EXPORT Paragraph *paragraphBuilder_build(ParagraphBuilder *builder) { - return builder->Build().release(); +SKWASM_EXPORT Paragraph* paragraphBuilder_build(ParagraphBuilder* builder) { + return builder->Build().release(); } diff --git a/lib/web_ui/skwasm/text/paragraph_style.cpp b/lib/web_ui/skwasm/text/paragraph_style.cpp index 23414f2fcf5bf..9a9dc82fac616 100644 --- a/lib/web_ui/skwasm/text/paragraph_style.cpp +++ b/lib/web_ui/skwasm/text/paragraph_style.cpp @@ -23,52 +23,58 @@ SKWASM_EXPORT ParagraphStyle* paragraphStyle_create() { return style; } -SKWASM_EXPORT void paragraphStyle_dispose(ParagraphStyle *style) { +SKWASM_EXPORT void paragraphStyle_dispose(ParagraphStyle* style) { delete style; } -SKWASM_EXPORT void paragraphStyle_setTextAlign(ParagraphStyle *style, TextAlign align) { - style->setTextAlign(align); +SKWASM_EXPORT void paragraphStyle_setTextAlign(ParagraphStyle* style, + TextAlign align) { + style->setTextAlign(align); } -SKWASM_EXPORT void paragraphStyle_setTextDirection(ParagraphStyle *style, TextDirection direction) { - style->setTextDirection(direction); +SKWASM_EXPORT void paragraphStyle_setTextDirection(ParagraphStyle* style, + TextDirection direction) { + style->setTextDirection(direction); } -SKWASM_EXPORT void paragraphStyle_setMaxLines(ParagraphStyle *style, size_t maxLines) { - style->setMaxLines(maxLines); +SKWASM_EXPORT void paragraphStyle_setMaxLines(ParagraphStyle* style, + size_t maxLines) { + style->setMaxLines(maxLines); } -SKWASM_EXPORT void paragraphStyle_setHeight(ParagraphStyle *style, SkScalar height) { - style->setHeight(height); +SKWASM_EXPORT void paragraphStyle_setHeight(ParagraphStyle* style, + SkScalar height) { + style->setHeight(height); } SKWASM_EXPORT void paragraphStyle_setTextHeightBehavior( - ParagraphStyle *style, + ParagraphStyle* style, bool applyHeightToFirstAscent, - bool applyHeightToLastDescent -) { - TextHeightBehavior behavior; - if (!applyHeightToFirstAscent && !applyHeightToLastDescent) { - behavior = kDisableAll; - } else if (!applyHeightToLastDescent) { - behavior = kDisableLastDescent; - } else if (!applyHeightToFirstAscent) { - behavior = kDisableFirstAscent; - } else { - behavior = kAll; - } - style->setTextHeightBehavior(behavior); + bool applyHeightToLastDescent) { + TextHeightBehavior behavior; + if (!applyHeightToFirstAscent && !applyHeightToLastDescent) { + behavior = kDisableAll; + } else if (!applyHeightToLastDescent) { + behavior = kDisableLastDescent; + } else if (!applyHeightToFirstAscent) { + behavior = kDisableFirstAscent; + } else { + behavior = kAll; + } + style->setTextHeightBehavior(behavior); } -SKWASM_EXPORT void paragraphStyle_setEllipsis(ParagraphStyle *style, SkString *ellipsis) { - style->setEllipsis(*ellipsis); +SKWASM_EXPORT void paragraphStyle_setEllipsis(ParagraphStyle* style, + SkString* ellipsis) { + style->setEllipsis(*ellipsis); } -SKWASM_EXPORT void paragraphStyle_setStrutStyle(ParagraphStyle *style, StrutStyle* strutStyle) { - style->setStrutStyle(*strutStyle); +SKWASM_EXPORT void paragraphStyle_setStrutStyle(ParagraphStyle* style, + StrutStyle* strutStyle) { + style->setStrutStyle(*strutStyle); } -SKWASM_EXPORT void paragraphStyle_setTextStyle(ParagraphStyle *style, TextStyle *textStyle) { - style->setTextStyle(*textStyle); +SKWASM_EXPORT void paragraphStyle_setTextStyle(ParagraphStyle* style, + TextStyle* textStyle) { + style->setTextStyle(*textStyle); } diff --git a/lib/web_ui/skwasm/text/text_style.cpp b/lib/web_ui/skwasm/text/text_style.cpp index 76acdea194b68..b520f52a5424d 100644 --- a/lib/web_ui/skwasm/text/text_style.cpp +++ b/lib/web_ui/skwasm/text/text_style.cpp @@ -17,7 +17,7 @@ SKWASM_EXPORT TextStyle* textStyle_create() { return style; } -SKWASM_EXPORT TextStyle* textStyle_copy(TextStyle *style) { +SKWASM_EXPORT TextStyle* textStyle_copy(TextStyle* style) { return new TextStyle(*style); } @@ -68,7 +68,7 @@ SKWASM_EXPORT void textStyle_clearFontFamilies(TextStyle* style) { SKWASM_EXPORT void textStyle_addFontFamilies(TextStyle* style, SkString** fontFamilies, int count) { - const std::vector ¤tFamilies = style->getFontFamilies(); + const std::vector& currentFamilies = style->getFontFamilies(); std::vector newFamilies; newFamilies.reserve(currentFamilies.size() + count); for (int i = 0; i < count; i++) { @@ -129,22 +129,16 @@ SKWASM_EXPORT void textStyle_addFontFeature(TextStyle* style, style->addFontFeature(*featureName, value); } -SKWASM_EXPORT void textStyle_setFontVariations( - TextStyle* style, - SkFourByteTag* axes, - float* values, - int count -) { +SKWASM_EXPORT void textStyle_setFontVariations(TextStyle* style, + SkFourByteTag* axes, + float* values, + int count) { std::vector coordinates; for (int i = 0; i < count; i++) { - coordinates.push_back({ - axes[i], - values[i] - }); + coordinates.push_back({axes[i], values[i]}); } SkFontArguments::VariationPosition position = { - coordinates.data(), - static_cast(coordinates.size()) - }; - style->setFontArguments(SkFontArguments().setVariationDesignPosition(position)); + coordinates.data(), static_cast(coordinates.size())}; + style->setFontArguments( + SkFontArguments().setVariationDesignPosition(position)); } diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 17153116fa164..a8a2b04ce9469 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -27,10 +27,10 @@ void setUpCanvasKitTest() { SurfaceFactory.instance.debugClear(); }); - setUp(() => + setUp(() => renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride = 'assets/fallback_fonts/'); - tearDown(() => + tearDown(() => renderer.fontCollection.fontFallbackManager!.downloadQueue.fallbackFontUrlPrefixOverride = null); } From f152ade9c853f5aa39179d63f6b1ac465a4d95ca Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 8 May 2023 17:18:16 -0700 Subject: [PATCH 24/27] Make line metrics constructible. --- .../lib/src/engine/canvaskit/renderer.dart | 44 ++++++++++--------- lib/web_ui/lib/src/engine/html/renderer.dart | 23 ++++++++++ lib/web_ui/lib/src/engine/renderer.dart | 12 +++++ .../engine/skwasm/skwasm_impl/paragraph.dart | 22 ++++++++++ .../raw/text/raw_line_metrics.dart | 23 ++++++++++ .../engine/skwasm/skwasm_impl/renderer.dart | 23 ++++++++++ .../engine/skwasm/skwasm_stub/renderer.dart | 13 ++++++ lib/web_ui/lib/text.dart | 22 ++++++++++ lib/web_ui/skwasm/text/line_metrics.cpp | 22 ++++++++++ 9 files changed, 184 insertions(+), 20 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index cec642f014188..45f4df4b70f95 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -6,28 +6,9 @@ import 'dart:async'; import 'dart:math' as math; import 'dart:typed_data'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../dom.dart'; -import '../embedder.dart'; -import '../html_image_codec.dart'; -import '../initialization.dart'; -import '../profiler.dart'; -import '../renderer.dart'; -import 'canvaskit_api.dart'; -import 'canvaskit_canvas.dart'; -import 'fonts.dart'; -import 'image.dart'; -import 'image_filter.dart'; -import 'layer_scene_builder.dart'; -import 'painting.dart'; -import 'path.dart'; -import 'picture_recorder.dart'; -import 'rasterizer.dart'; -import 'shader.dart'; -import 'text.dart'; -import 'vertices.dart'; - enum CanvasKitVariant { /// The appropriate variant is chosen based on the browser. /// @@ -406,4 +387,27 @@ class CanvasKitRenderer implements Renderer { return CkFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List()); }); } + + @override + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber + }) => EngineLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber + ); } diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart index 21de71dd1baee..728c8bc367ce2 100644 --- a/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -339,4 +339,27 @@ class HtmlRenderer implements Renderer { Future createFragmentProgram(String assetKey) { return Future.value(HtmlFragmentProgram()); } + + @override + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber + }) => EngineLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber + ); } diff --git a/lib/web_ui/lib/src/engine/renderer.dart b/lib/web_ui/lib/src/engine/renderer.dart index 5dbaaad432bfb..20fd055b1c13d 100644 --- a/lib/web_ui/lib/src/engine/renderer.dart +++ b/lib/web_ui/lib/src/engine/renderer.dart @@ -161,6 +161,18 @@ abstract class Renderer { ui.Path copyPath(ui.Path src); ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2); + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber, + }); + ui.TextStyle createTextStyle({ required ui.Color? color, required ui.TextDecoration? decoration, diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index ce0e4f17ba84c..4d0d027aa409a 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -9,6 +9,28 @@ import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; class SkwasmLineMetrics implements ui.LineMetrics { + factory SkwasmLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber, + }) => SkwasmLineMetrics._(lineMetricsCreate( + hardBreak, + ascent, + descent, + unscaledAscent, + height, + width, + left, + baseline, + lineNumber, + )); + SkwasmLineMetrics._(this.handle); final LineMetricsHandle handle; diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart index 19819b8e99967..c00d4acd1641e 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart @@ -10,6 +10,29 @@ import 'dart:ffi'; final class RawLineMetrics extends Opaque {} typedef LineMetricsHandle = Pointer; +@Native(symbol: 'lineMetrics_create', isLeaf: true) +external LineMetricsHandle lineMetricsCreate( + bool hardBreak, + double ascent, + double descent, + double unscaledAscent, + double height, + double width, + double left, + double baseline, + int lineNumber +); + @Native(symbol: 'lineMetrics_dispose', isLeaf: true) external void lineMetricsDispose(LineMetricsHandle handle); 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 cb3be07646535..59594feaf68ca 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 @@ -353,4 +353,27 @@ class SkwasmRenderer implements Renderer { return SkwasmFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List()); }); } + + @override + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber + }) => SkwasmLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber + ); } 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 40082de28d793..524de360fb16f 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 @@ -175,4 +175,17 @@ class SkwasmRenderer implements Renderer { return CkFragmentProgram.fromBytes(assetKey, data.buffer.asUint8List()); }); } + + @override + ui.LineMetrics createLineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber + }) => throw UnimplementedError('Skwasm not implemented on this platform.'); } diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 1f93ce1935597..491966a0f88c2 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -627,6 +627,28 @@ enum BoxWidthStyle { } abstract class LineMetrics { + factory LineMetrics({ + required bool hardBreak, + required double ascent, + required double descent, + required double unscaledAscent, + required double height, + required double width, + required double left, + required double baseline, + required int lineNumber, + }) => engine.renderer.createLineMetrics( + hardBreak: hardBreak, + ascent: ascent, + descent: descent, + unscaledAscent: unscaledAscent, + height: height, + width: width, + left: left, + baseline: baseline, + lineNumber: lineNumber, + ); + bool get hardBreak; double get ascent; double get descent; diff --git a/lib/web_ui/skwasm/text/line_metrics.cpp b/lib/web_ui/skwasm/text/line_metrics.cpp index c7e13c214e033..c163dc322dee2 100644 --- a/lib/web_ui/skwasm/text/line_metrics.cpp +++ b/lib/web_ui/skwasm/text/line_metrics.cpp @@ -7,6 +7,28 @@ using namespace skia::textlayout; +SKWASM_EXPORT LineMetrics* lineMetrics_create(bool hardBreak, + double ascent, + double descent, + double unscaledAscent, + double height, + double width, + double left, + double baseline, + size_t lineNumber) { + auto metrics = new LineMetrics(); + metrics->fHardBreak = hardBreak; + metrics->fAscent = ascent; + metrics->fDescent = descent; + metrics->fUnscaledAscent = unscaledAscent; + metrics->fHeight = height; + metrics->fWidth = width; + metrics->fLeft = left; + metrics->fBaseline = baseline; + metrics->fLineNumber = lineNumber; + return metrics; +} + SKWASM_EXPORT void lineMetrics_dispose(LineMetrics* metrics) { delete metrics; } From 2dce16a54d52369baa43780a77aef3230ba59210 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 8 May 2023 17:19:53 -0700 Subject: [PATCH 25/27] Update licenses golden. --- ci/licenses_golden/licenses_flutter | 40 +++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a1989101ea127..15c1ceb8e3b3c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1866,21 +1866,17 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.da ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/fonts.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart + ../../../flutter/LICENSE @@ -1904,6 +1900,8 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/dom.dart + ../../../flutter/L ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/embedder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_change_util.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallback_data.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/global_styles.dart + ../../../flutter/LICENSE @@ -1947,6 +1945,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/surface_stats.dart + ../ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/transform.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/initialization.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/interval_tree.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_loader.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_promise.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart + ../../../flutter/LICENSE @@ -1957,6 +1956,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart + ../../../fl ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart + ../../../flutter/LICENSE @@ -2015,6 +2015,12 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_sk ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart + ../../../flutter/LICENSE @@ -2074,6 +2080,12 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/picture.cpp + ../../../flutter/LICENS ORIGIN: ../../../flutter/lib/web_ui/skwasm/shaders.cpp + ../../../flutter/LICENSE 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/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 +ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/paragraph_style.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/strut_style.cpp + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/skwasm/text/text_style.cpp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/skwasm/wrappers.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/runtime/dart_isolate.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/runtime/dart_isolate.h + ../../../flutter/LICENSE @@ -4469,21 +4481,17 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/native_memory.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart @@ -4507,6 +4515,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/embedder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_change_util.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallback_data.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/global_styles.dart @@ -4550,6 +4560,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface_stats.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/transform.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/initialization.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/interval_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_loader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_promise.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart @@ -4560,6 +4571,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/js_url_strategy.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/url_strategy.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -4618,6 +4630,12 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skda FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_skstring.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/skwasm_module.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_line_metrics.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_builder.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_paragraph_style.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_strut_style.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/text/raw_text_style.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart @@ -4677,6 +4695,12 @@ FILE: ../../../flutter/lib/web_ui/skwasm/picture.cpp FILE: ../../../flutter/lib/web_ui/skwasm/shaders.cpp FILE: ../../../flutter/lib/web_ui/skwasm/string.cpp FILE: ../../../flutter/lib/web_ui/skwasm/surface.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 +FILE: ../../../flutter/lib/web_ui/skwasm/text/paragraph_style.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/text/strut_style.cpp +FILE: ../../../flutter/lib/web_ui/skwasm/text/text_style.cpp FILE: ../../../flutter/lib/web_ui/skwasm/wrappers.h FILE: ../../../flutter/runtime/dart_isolate.cc FILE: ../../../flutter/runtime/dart_isolate.h From 67a5a55fa2a1bbd063dacdb5855b0baf4d655222 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 13:30:45 -0700 Subject: [PATCH 26/27] Apply suggestions from Mouad's code review Co-authored-by: Mouad Debbar --- lib/web_ui/lib/src/engine/font_fallbacks.dart | 6 ++---- lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/lib/src/engine/font_fallbacks.dart b/lib/web_ui/lib/src/engine/font_fallbacks.dart index d1ddb28c4c961..a87e5a0566739 100644 --- a/lib/web_ui/lib/src/engine/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/font_fallbacks.dart @@ -61,10 +61,8 @@ class FontFallbackManager { >{}; for (final NotoFont font in fallbackFonts) { - // ignore: prefer_foreach - for (final CodePointRange range in font.computeUnicodeRanges()) { - ranges.putIfAbsent(font, () => []).add(range); - } + final fontRanges = ranges.putIfAbsent(font, () => []); + fontRanges.addAll(font.computeUnicodeRanges()); } return IntervalTree.createFromRanges(ranges); 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 bcc1c65731e3e..ddf1326effef2 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 @@ -210,7 +210,8 @@ class SkwasmCanvas implements ui.Canvas { _handle, (paragraph as SkwasmParagraph).handle, offset.dx, - offset.dy,); + offset.dy, + ); } @override From 97d5e46e4690b633a79445f478316a9657525550 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 9 May 2023 13:43:32 -0700 Subject: [PATCH 27/27] Fix analyzer issue. --- lib/web_ui/lib/src/engine/font_fallbacks.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/font_fallbacks.dart b/lib/web_ui/lib/src/engine/font_fallbacks.dart index a87e5a0566739..27b7f2269be36 100644 --- a/lib/web_ui/lib/src/engine/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/font_fallbacks.dart @@ -61,7 +61,8 @@ class FontFallbackManager { >{}; for (final NotoFont font in fallbackFonts) { - final fontRanges = ranges.putIfAbsent(font, () => []); + final List fontRanges = + ranges.putIfAbsent(font, () => []); fontRanges.addAll(font.computeUnicodeRanges()); }