Revert "[CanvasKit] Automatically fall back to Noto fonts (#23096)" (#23357)

This reverts commit eff27c74bf8c74781786f3c3724347e12764018a.

Reason for revert: Incorrect logic breaks Gallery
This commit is contained in:
Harry Terkelsen 2020-12-30 16:16:51 -08:00 committed by GitHub
parent 82b4ae86d6
commit f9f4d0168d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 81 additions and 1049 deletions

View File

@ -1209,7 +1209,6 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
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/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

View File

@ -32,7 +32,6 @@ part 'engine/canvaskit/canvaskit_api.dart';
part 'engine/canvaskit/color_filter.dart';
part 'engine/canvaskit/embedded_views.dart';
part 'engine/canvaskit/fonts.dart';
part 'engine/canvaskit/font_fallbacks.dart';
part 'engine/canvaskit/image.dart';
part 'engine/canvaskit/image_filter.dart';
part 'engine/canvaskit/initialization.dart';

View File

@ -176,8 +176,7 @@ class CkCanvas {
void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
skCanvas.drawPoints(
toSkPointMode(pointMode),
// TODO(hterkelsen): Don't convert this to 2d after we move to CK 0.21.
rawPointsToSkPoints2d(points),
points,
paint.skiaObject,
);
}
@ -193,10 +192,10 @@ class CkCanvas {
skCanvas.drawRect(toSkRect(rect), paint.skiaObject);
}
void drawShadow(
CkPath path, ui.Color color, double elevation, bool transparentOccluder) {
drawSkShadow(skCanvas, path, color, elevation, transparentOccluder,
ui.window.devicePixelRatio);
void drawShadow(CkPath path, ui.Color color, double elevation,
bool transparentOccluder) {
drawSkShadow(skCanvas, path, color, elevation,
transparentOccluder, ui.window.devicePixelRatio);
}
void drawVertices(
@ -238,8 +237,7 @@ class CkCanvas {
}
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
final _CkManagedSkImageFilterConvertible convertible =
filter as _CkManagedSkImageFilterConvertible;
final _CkManagedSkImageFilterConvertible convertible = filter as _CkManagedSkImageFilterConvertible;
return skCanvas.saveLayer(
null,
toSkRect(bounds),
@ -269,8 +267,8 @@ class CkCanvas {
class RecordingCkCanvas extends CkCanvas {
RecordingCkCanvas(SkCanvas skCanvas, ui.Rect bounds)
: pictureSnapshot = CkPictureSnapshot(bounds),
super(skCanvas);
: pictureSnapshot = CkPictureSnapshot(bounds),
super(skCanvas);
@override
final CkPictureSnapshot pictureSnapshot;
@ -312,8 +310,7 @@ class RecordingCkCanvas extends CkCanvas {
CkPaint paint,
) {
super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
_addCommand(
CkDrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
_addCommand(CkDrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
}
@override
@ -326,8 +323,7 @@ class RecordingCkCanvas extends CkCanvas {
ui.BlendMode blendMode,
) {
super.drawAtlasRaw(paint, atlas, rstTransforms, rects, colors, blendMode);
_addCommand(CkDrawAtlasCommand(
paint, atlas, rstTransforms, rects, colors, blendMode));
_addCommand(CkDrawAtlasCommand(paint, atlas, rstTransforms, rects, colors, blendMode));
}
@override
@ -422,11 +418,10 @@ class RecordingCkCanvas extends CkCanvas {
}
@override
void drawShadow(
CkPath path, ui.Color color, double elevation, bool transparentOccluder) {
void drawShadow(CkPath path, ui.Color color, double elevation,
bool transparentOccluder) {
super.drawShadow(path, color, elevation, transparentOccluder);
_addCommand(
CkDrawShadowCommand(path, color, elevation, transparentOccluder));
_addCommand(CkDrawShadowCommand(path, color, elevation, transparentOccluder));
}
@override
@ -632,7 +627,7 @@ class CkTransformCommand extends CkPaintCommand {
@override
void apply(SkCanvas canvas) {
canvas.concat(toSkMatrixFromFloat32(matrix4));
}
}
}
class CkSkewCommand extends CkPaintCommand {
@ -665,8 +660,7 @@ class CkClipRectCommand extends CkPaintCommand {
}
class CkDrawArcCommand extends CkPaintCommand {
CkDrawArcCommand(
this.oval, this.startAngle, this.sweepAngle, this.useCenter, this.paint);
CkDrawArcCommand(this.oval, this.startAngle, this.sweepAngle, this.useCenter, this.paint);
final ui.Rect oval;
final double startAngle;
@ -688,8 +682,7 @@ class CkDrawArcCommand extends CkPaintCommand {
}
class CkDrawAtlasCommand extends CkPaintCommand {
CkDrawAtlasCommand(this.paint, this.atlas, this.rstTransforms, this.rects,
this.colors, this.blendMode);
CkDrawAtlasCommand(this.paint, this.atlas, this.rstTransforms, this.rects, this.colors, this.blendMode);
final CkPaint paint;
final CkImage atlas;
@ -814,8 +807,7 @@ class CkDrawPointsCommand extends CkPaintCommand {
void apply(SkCanvas canvas) {
canvas.drawPoints(
toSkPointMode(pointMode),
// TODO(hterkelsen): Don't convert this to 2d after we move to CK 0.21.
rawPointsToSkPoints2d(points),
points,
paint.skiaObject,
);
}
@ -932,7 +924,7 @@ class CkDrawImageCommand extends CkPaintCommand {
final CkPaint paint;
CkDrawImageCommand(CkImage image, this.offset, this.paint)
: this.image = image.clone();
: this.image = image.clone();
@override
void apply(SkCanvas canvas) {
@ -957,7 +949,7 @@ class CkDrawImageRectCommand extends CkPaintCommand {
final CkPaint paint;
CkDrawImageRectCommand(CkImage image, this.src, this.dst, this.paint)
: this.image = image.clone();
: this.image = image.clone();
@override
void apply(SkCanvas canvas) {
@ -978,7 +970,7 @@ class CkDrawImageRectCommand extends CkPaintCommand {
class CkDrawImageNineCommand extends CkPaintCommand {
CkDrawImageNineCommand(CkImage image, this.center, this.dst, this.paint)
: this.image = image.clone();
: this.image = image.clone();
final CkImage image;
final ui.Rect center;
@ -1069,8 +1061,7 @@ class CkSaveLayerWithFilterCommand extends CkPaintCommand {
@override
void apply(SkCanvas canvas) {
final _CkManagedSkImageFilterConvertible convertible =
filter as _CkManagedSkImageFilterConvertible;
final _CkManagedSkImageFilterConvertible convertible = filter as _CkManagedSkImageFilterConvertible;
return canvas.saveLayer(
null,
toSkRect(bounds),

View File

@ -49,7 +49,7 @@ class CanvasKit {
external SkMaskFilterNamespace get MaskFilter;
external SkColorFilterNamespace get ColorFilter;
external SkImageFilterNamespace get ImageFilter;
external SkPathNamespace get Path;
external SkPath MakePathFromOp(SkPath path1, SkPath path2, SkPathOp pathOp);
external SkTonalColors computeTonalColors(SkTonalColors inTonalColors);
external SkVertices MakeVertices(
SkVertexMode mode,
@ -96,6 +96,10 @@ class CanvasKit {
external SkSurface MakeSWCanvasSurface(html.CanvasElement canvas);
external void setCurrentContext(int glContext);
/// Creates an [SkPath] using commands obtained from [SkPath.toCmds].
// TODO(yjbanov): switch to CanvasKit.Path.MakeFromCmds when it's available.
external SkPath MakePathFromCmds(List<dynamic> pathCommands);
/// Creates an image from decoded pixels represented as a list of bytes.
///
/// The pixel data must match the [width], [height], [alphaType], [colorType],
@ -719,7 +723,7 @@ class SkImage {
SkTileMode tileModeY,
Float32List? matrix, // 3x3 matrix
);
external Uint8List readPixels(int srcX, int srcY, SkImageInfo imageInfo);
external Uint8List readPixels(SkImageInfo imageInfo, int srcX, int srcY);
external SkData encodeToData();
external bool isAliasOf(SkImage other);
external bool isDeleted();
@ -779,7 +783,7 @@ class SkShader {
@JS()
class SkMaskFilterNamespace {
external SkMaskFilter MakeBlur(
SkBlurStyle blurStyle, double sigma, bool respectCTM);
SkBlurStyle blurStyle, double sigma, bool respectCTM);
}
// This needs to be bound to top-level because SkPaint is initialized
@ -860,14 +864,6 @@ class SkImageFilter {
external void delete();
}
@JS()
class SkPathNamespace {
external SkPath MakeFromOp(SkPath path1, SkPath path2, SkPathOp pathOp);
/// Creates an [SkPath] using commands obtained from [SkPath.toCmds].
external SkPath MakeFromCmds(List<dynamic> pathCommands);
}
// Mappings from SkMatrix-index to input-index.
const List<int> _skMatrixIndexToMatrix4Index = <int>[
0, 4, 12, // Row 1
@ -1187,8 +1183,7 @@ class SkPath {
/// Serializes the path into a list of commands.
///
/// The list can be used to create a new [SkPath] using
/// [CanvasKit.Path.MakeFromCmds].
/// The list can be used to create a new [SkPath] using [CanvasKit.MakePathFromCmds].
external List<dynamic> toCmds();
external void delete();
@ -1409,7 +1404,7 @@ class SkCanvas {
);
external void drawPoints(
SkPointMode pointMode,
List<Float32List> points,
Float32List points,
SkPaint paint,
);
external void drawRRect(
@ -1791,8 +1786,7 @@ abstract class Collector {
/// Uses timers to delete objects in batches and outside the animation frame.
class ProductionCollector implements Collector {
ProductionCollector() {
_skObjectFinalizationRegistry =
SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) {
_skObjectFinalizationRegistry = SkObjectFinalizationRegistry(js.allowInterop((SkDeletable deletable) {
// This is called when GC decides to collect the wrapper object and
// notify us, which may happen after the object is already deleted
// explicitly, e.g. when its ref count drops to zero. When that happens
@ -1973,8 +1967,7 @@ bool browserSupportsFinalizationRegistry =
/// Sets the value of [browserSupportsFinalizationRegistry] to its true value.
void debugResetBrowserSupportsFinalizationRegistry() {
browserSupportsFinalizationRegistry =
_finalizationRegistryConstructor != null;
browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null;
}
@JS()

View File

@ -1,867 +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.
// @dart = 2.12
part of engine;
/// Whether or not "Noto Sans Symbols" and "Noto Color Emoji" fonts have been
/// downloaded. We download these as fallbacks when no other font covers the
/// given code units.
bool _downloadedSymbolsAndEmoji = false;
final Set<int> codeUnitsWithNoKnownFont = <int>{};
Future<void> _findFontsForMissingCodeunits(List<int> codeunits) async {
_ensureNotoFontTreeCreated();
// If all of the code units are known to have no Noto Font which covers them,
// then just give up. We have already logged a warning.
if (codeunits.every((u) => codeUnitsWithNoKnownFont.contains(u))) {
return;
}
Set<_NotoFont> fonts = <_NotoFont>{};
Set<int> coveredCodeUnits = <int>{};
Set<int> missingCodeUnits = <int>{};
for (int codeunit in codeunits) {
List<_NotoFont> fontsForUnit = _lookupNotoFontsForCodeunit(codeunit);
fonts.addAll(fontsForUnit);
if (fontsForUnit.isNotEmpty) {
coveredCodeUnits.add(codeunit);
} else {
missingCodeUnits.add(codeunit);
}
}
fonts = _findMinimumFontsForCodeunits(coveredCodeUnits, fonts);
for (_NotoFont font in fonts) {
if (_resolvedNotoFonts[font] == null) {
String googleFontCss = await html.window
.fetch(font.googleFontsCssUrl)
.then((dynamic response) =>
response.text().then<String>((dynamic x) => x as String));
final _ResolvedNotoFont resolvedFont =
_makeResolvedNotoFontFromCss(googleFontCss, font.name);
_registerResolvedFont(font, resolvedFont);
}
}
Set<_ResolvedNotoSubset> resolvedFonts = <_ResolvedNotoSubset>{};
for (int codeunit in coveredCodeUnits) {
resolvedFonts.addAll(_lookupResolvedFontsForCodeunit(codeunit));
}
for (_ResolvedNotoSubset resolvedFont in resolvedFonts) {
skiaFontCollection.registerFallbackFont(
resolvedFont.url, resolvedFont.name);
}
if (missingCodeUnits.isNotEmpty) {
if (!_downloadedSymbolsAndEmoji) {
await _registerSymbolsAndEmoji();
} else {
html.window.console
.log('Could not find a Noto font to display all missing characters. '
'Please add a font asset for the missing characters.');
codeUnitsWithNoKnownFont.addAll(missingCodeUnits);
}
}
await skiaFontCollection.ensureFontsLoaded();
sendFontChangeMessage();
}
/// Parse the CSS file for a font and make a list of resolved subsets.
///
/// A CSS file from Google Fonts looks like this:
///
/// /* [0] */
/// @font-face {
/// font-family: 'Noto Sans KR';
/// font-style: normal;
/// font-weight: 400;
/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.0.woff2) format('woff2');
/// unicode-range: U+f9ca-fa0b, U+ff03-ff05, U+ff07, U+ff0a-ff0b, U+ff0d-ff19, U+ff1b, U+ff1d, U+ff20-ff5b, U+ff5d, U+ffe0-ffe3, U+ffe5-ffe6;
/// }
/// /* [1] */
/// @font-face {
/// font-family: 'Noto Sans KR';
/// font-style: normal;
/// font-weight: 400;
/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.1.woff2) format('woff2');
/// unicode-range: U+f92f-f980, U+f982-f9c9;
/// }
/// /* [2] */
/// @font-face {
/// font-family: 'Noto Sans KR';
/// font-style: normal;
/// font-weight: 400;
/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.2.woff2) format('woff2');
/// unicode-range: U+d723-d728, U+d72a-d733, U+d735-d748, U+d74a-d74f, U+d752-d753, U+d755-d757, U+d75a-d75f, U+d762-d764, U+d766-d768, U+d76a-d76b, U+d76d-d76f, U+d771-d787, U+d789-d78b, U+d78d-d78f, U+d791-d797, U+d79a, U+d79c, U+d79e-d7a3, U+f900-f909, U+f90b-f92e;
/// }
_ResolvedNotoFont _makeResolvedNotoFontFromCss(String css, String name) {
List<_ResolvedNotoSubset> subsets = <_ResolvedNotoSubset>[];
bool resolvingFontFace = false;
String? fontFaceUrl;
List<_UnicodeRange>? fontFaceUnicodeRanges;
for (final String line in LineSplitter.split(css)) {
// Search for the beginning of a @font-face.
if (!resolvingFontFace) {
if (line == '@font-face {') {
resolvingFontFace = true;
} else {
continue;
}
} else {
// We are resolving a @font-face, read out the url and ranges.
if (line.startsWith(' src:')) {
int urlStart = line.indexOf('url(');
if (urlStart == -1) {
throw new Exception('Unable to resolve Noto font URL: $line');
}
int urlEnd = line.indexOf(')');
fontFaceUrl = line.substring(urlStart + 4, urlEnd);
} else if (line.startsWith(' unicode-range:')) {
fontFaceUnicodeRanges = <_UnicodeRange>[];
String rangeString = line.substring(17, line.length - 1);
List<String> rawRanges = rangeString.split(', ');
for (final String rawRange in rawRanges) {
List<String> startEnd = rawRange.split('-');
if (startEnd.length == 1) {
String singleRange = startEnd.single;
assert(singleRange.startsWith('U+'));
int rangeValue = int.parse(singleRange.substring(2), radix: 16);
fontFaceUnicodeRanges.add(_UnicodeRange(rangeValue, rangeValue));
} else {
assert(startEnd.length == 2);
String startRange = startEnd[0];
String endRange = startEnd[1];
assert(startRange.startsWith('U+'));
int startValue = int.parse(startRange.substring(2), radix: 16);
int endValue = int.parse(endRange, radix: 16);
fontFaceUnicodeRanges.add(_UnicodeRange(startValue, endValue));
}
}
} else if (line == '}') {
subsets.add(
_ResolvedNotoSubset(fontFaceUrl!, name, fontFaceUnicodeRanges!));
resolvingFontFace = false;
} else {
continue;
}
}
}
return _ResolvedNotoFont(name, subsets);
}
void _registerResolvedFont(_NotoFont font, _ResolvedNotoFont resolvedFont) {
_resolvedNotoFonts[font] = resolvedFont;
for (_ResolvedNotoSubset subset in resolvedFont.subsets) {
for (_UnicodeRange range in subset.ranges) {
_resolvedNotoTreeRoot = _insertNotoFontRange<_ResolvedNotoSubset>(
range, subset, _resolvedNotoTreeRoot);
}
}
assert(
_verifyNotoTree(_resolvedNotoTreeRoot),
'Resolved Noto tree is invalid: '
'${_verifyNotoSubtree(_resolvedNotoTreeRoot).reason}');
}
/// In the case where none of the known Noto Fonts cover a set of code units,
/// try the Symbols and Emoji fonts. We don't know the exact range of code units
/// that are covered by these fonts, so we download them and hope for the best.
Future<void> _registerSymbolsAndEmoji() async {
const String symbolsUrl =
'https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols';
const String emojiUrl =
'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat';
String symbolsCss = await html.window.fetch(symbolsUrl).then(
(dynamic response) =>
response.text().then<String>((dynamic x) => x as String));
String emojiCss = await html.window.fetch(emojiUrl).then((dynamic response) =>
response.text().then<String>((dynamic x) => x as String));
String extractUrlFromCss(String css) {
for (final String line in LineSplitter.split(css)) {
if (line.startsWith(' src:')) {
int urlStart = line.indexOf('url(');
if (urlStart == -1) {
throw new Exception('Unable to resolve Noto font URL: $line');
}
int urlEnd = line.indexOf(')');
return line.substring(urlStart + 4, urlEnd);
}
}
throw Exception('Unable to determine URL for Noto font');
}
String symbolsFontUrl = extractUrlFromCss(symbolsCss);
String emojiFontUrl = extractUrlFromCss(emojiCss);
skiaFontCollection.registerFallbackFont(symbolsFontUrl, 'Noto Sans Symbols');
skiaFontCollection.registerFallbackFont(
emojiFontUrl, 'Noto Color Emoji Compat');
_downloadedSymbolsAndEmoji = true;
}
/// Finds the minimum set of fonts which covers all of the [codeunits].
///
/// Since set cover is NP-complete, we approximate using a greedy algorithm
/// which finds the font which covers the most codeunits. If multiple CJK
/// fonts match the same number of codeunits, we choose one based on the user's
/// locale.
Set<_NotoFont> _findMinimumFontsForCodeunits(
Iterable<int> codeunits, Set<_NotoFont> fonts) {
List<int> unmatchedCodeunits = List<int>.from(codeunits);
Set<_NotoFont> minimumFonts = <_NotoFont>{};
List<_NotoFont> bestFonts = <_NotoFont>[];
int maxCodeunitsCovered = 0;
String language = html.window.navigator.language;
// This is guaranteed to terminate because [codeunits] is a list of fonts
// which we've already determined are covered by [fonts].
while (unmatchedCodeunits.isNotEmpty) {
for (var font in fonts) {
int codeunitsCovered = 0;
for (int codeunit in unmatchedCodeunits) {
if (font.matchesCodeunit(codeunit)) {
codeunitsCovered++;
}
}
if (codeunitsCovered > maxCodeunitsCovered) {
bestFonts.clear();
bestFonts.add(font);
maxCodeunitsCovered = codeunitsCovered;
} else if (codeunitsCovered == maxCodeunitsCovered) {
bestFonts.add(font);
}
}
assert(bestFonts.isNotEmpty);
// If the list of best fonts are all CJK fonts, choose the best one based
// on locale. Otherwise just choose the first font.
_NotoFont bestFont = bestFonts.first;
if (bestFonts.length > 1) {
if (bestFonts.every((font) => _cjkFonts.contains(font))) {
if (language == 'zh-Hans' ||
language == 'zh-CN' ||
language == 'zh-SG' ||
language == 'zh-MY') {
if (bestFonts.contains(_notoSansSC)) {
bestFont = _notoSansSC;
}
} else if (language == 'zh-Hant' ||
language == 'zh-TW' ||
language == 'zh-MO') {
if (bestFonts.contains(_notoSansTC)) {
bestFont = _notoSansTC;
}
} else if (language == 'zh-HK') {
if (bestFonts.contains(_notoSansHK)) {
bestFont = _notoSansHK;
}
} else if (language == 'ja') {
if (bestFonts.contains(_notoSansJP)) {
bestFont = _notoSansJP;
}
}
}
}
unmatchedCodeunits
.removeWhere((codeunit) => bestFont.matchesCodeunit(codeunit));
minimumFonts.add(bestFont);
}
return minimumFonts;
}
void _ensureNotoFontTreeCreated() {
if (_notoTreeRoot != null) {
return;
}
for (_NotoFont font in _notoFonts) {
for (_UnicodeRange range in font.unicodeRanges) {
_notoTreeRoot =
_insertNotoFontRange<_NotoFont>(range, font, _notoTreeRoot);
}
}
assert(
_verifyNotoTree(_notoTreeRoot),
'The Noto font tree is invalid: '
'${_verifyNotoSubtree(_notoTreeRoot).reason}');
}
List<_NotoFont> _lookupNotoFontsForCodeunit(int codeunit) {
List<_NotoFont> lookupHelper(_NotoTreeNode<_NotoFont> node) {
if (node.range.contains(codeunit)) {
return node.fonts;
}
if (node.range.start > codeunit) {
if (node.left != null) {
return lookupHelper(node.left!);
} else {
return const <_NotoFont>[];
}
} else {
if (node.right != null) {
return lookupHelper(node.right!);
} else {
return const <_NotoFont>[];
}
}
}
return lookupHelper(_notoTreeRoot!);
}
List<_ResolvedNotoSubset> _lookupResolvedFontsForCodeunit(int codeunit) {
List<_ResolvedNotoSubset> lookupHelper(
_NotoTreeNode<_ResolvedNotoSubset> node) {
if (node.range.contains(codeunit)) {
return node.fonts;
}
if (node.range.start > codeunit) {
if (node.left != null) {
return lookupHelper(node.left!);
} else {
return const <_ResolvedNotoSubset>[];
}
} else {
if (node.right != null) {
return lookupHelper(node.right!);
} else {
return const <_ResolvedNotoSubset>[];
}
}
}
return lookupHelper(_resolvedNotoTreeRoot!);
}
class _NotoFont {
final String name;
final List<_UnicodeRange> unicodeRanges;
const _NotoFont(this.name, this.unicodeRanges);
bool matchesCodeunit(int codeunit) {
for (_UnicodeRange range in unicodeRanges) {
if (range.contains(codeunit)) {
return true;
}
}
return false;
}
String get googleFontsCssUrl =>
'https://fonts.googleapis.com/css2?family=${name.replaceAll(' ', '+')}';
}
class _UnicodeRange {
final int start;
final int end;
const _UnicodeRange(this.start, this.end);
bool contains(int codeUnit) {
return start <= codeUnit && codeUnit <= end;
}
@override
bool operator ==(dynamic other) {
if (other is! _UnicodeRange) {
return false;
}
_UnicodeRange range = other;
return range.start == start && range.end == end;
}
@override
int get hashCode => ui.hashValues(start, end);
}
class _ResolvedNotoFont {
final String name;
final List<_ResolvedNotoSubset> subsets;
const _ResolvedNotoFont(this.name, this.subsets);
}
class _ResolvedNotoSubset {
final String url;
final String name;
final List<_UnicodeRange> ranges;
const _ResolvedNotoSubset(this.url, this.name, this.ranges);
}
const _NotoFont _notoSansSC = _NotoFont('Noto Sans SC', <_UnicodeRange>[
_UnicodeRange(12288, 12591),
_UnicodeRange(12800, 13311),
_UnicodeRange(19968, 40959),
_UnicodeRange(65072, 65135),
_UnicodeRange(65280, 65519),
]);
const _NotoFont _notoSansTC = _NotoFont('Noto Sans TC', <_UnicodeRange>[
_UnicodeRange(12288, 12351),
_UnicodeRange(12549, 12585),
_UnicodeRange(19968, 40959),
]);
const _NotoFont _notoSansHK = _NotoFont('Noto Sans HK', <_UnicodeRange>[
_UnicodeRange(12288, 12351),
_UnicodeRange(12549, 12585),
_UnicodeRange(19968, 40959),
]);
const _NotoFont _notoSansJP = _NotoFont('Noto Sans JP', <_UnicodeRange>[
_UnicodeRange(12288, 12543),
_UnicodeRange(19968, 40959),
_UnicodeRange(65280, 65519),
]);
const List<_NotoFont> _cjkFonts = <_NotoFont>[
_notoSansSC,
_notoSansTC,
_notoSansHK,
_notoSansJP,
];
const List<_NotoFont> _notoFonts = <_NotoFont>[
_notoSansSC,
_notoSansTC,
_notoSansHK,
_notoSansJP,
_NotoFont('Noto Naskh Arabic UI', <_UnicodeRange>[
_UnicodeRange(1536, 1791),
_UnicodeRange(8204, 8206),
_UnicodeRange(8208, 8209),
_UnicodeRange(8271, 8271),
_UnicodeRange(11841, 11841),
_UnicodeRange(64336, 65023),
_UnicodeRange(65132, 65276),
]),
_NotoFont('Noto Sans Armenian', <_UnicodeRange>[
_UnicodeRange(1328, 1424),
_UnicodeRange(64275, 64279),
]),
_NotoFont('Noto Sans Bengali UI', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(2433, 2555),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Myanmar UI', <_UnicodeRange>[
_UnicodeRange(4096, 4255),
_UnicodeRange(8204, 8205),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Egyptian Hieroglyphs', <_UnicodeRange>[
_UnicodeRange(77824, 78894),
]),
_NotoFont('Noto Sans Ethiopic', <_UnicodeRange>[
_UnicodeRange(4608, 5017),
_UnicodeRange(11648, 11742),
_UnicodeRange(43777, 43822),
]),
_NotoFont('Noto Sans Georgian', <_UnicodeRange>[
_UnicodeRange(1417, 1417),
_UnicodeRange(4256, 4351),
_UnicodeRange(11520, 11567),
]),
_NotoFont('Noto Sans Gujarati UI', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(2688, 2815),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
_UnicodeRange(43056, 43065),
]),
_NotoFont('Noto Sans Gurmukhi UI', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(2561, 2677),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
_UnicodeRange(9772, 9772),
_UnicodeRange(43056, 43065),
]),
_NotoFont('Noto Sans Hebrew', <_UnicodeRange>[
_UnicodeRange(1424, 1535),
_UnicodeRange(8362, 8362),
_UnicodeRange(9676, 9676),
_UnicodeRange(64285, 64335),
]),
_NotoFont('Noto Sans Devanagari UI', <_UnicodeRange>[
_UnicodeRange(2304, 2431),
_UnicodeRange(7376, 7414),
_UnicodeRange(7416, 7417),
_UnicodeRange(8204, 9205),
_UnicodeRange(8360, 8360),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
_UnicodeRange(43056, 43065),
_UnicodeRange(43232, 43259),
]),
_NotoFont('Noto Sans Kannada UI', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(3202, 3314),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Khmer UI', <_UnicodeRange>[
_UnicodeRange(6016, 6143),
_UnicodeRange(8204, 8204),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans KR', <_UnicodeRange>[
_UnicodeRange(12593, 12686),
_UnicodeRange(12800, 12828),
_UnicodeRange(12896, 12923),
_UnicodeRange(44032, 55215),
]),
_NotoFont('Noto Sans Lao UI', <_UnicodeRange>[
_UnicodeRange(3713, 3807),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Malayalam UI', <_UnicodeRange>[
_UnicodeRange(775, 775),
_UnicodeRange(803, 803),
_UnicodeRange(2404, 2405),
_UnicodeRange(3330, 3455),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Sinhala', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(3458, 3572),
_UnicodeRange(8204, 8205),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Tamil UI', <_UnicodeRange>[
_UnicodeRange(2404, 2405),
_UnicodeRange(2946, 3066),
_UnicodeRange(8204, 8205),
_UnicodeRange(8377, 8377),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Telugu UI', <_UnicodeRange>[
_UnicodeRange(2385, 2386),
_UnicodeRange(2404, 2405),
_UnicodeRange(3072, 3199),
_UnicodeRange(7386, 7386),
_UnicodeRange(8204, 8205),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans Thai UI', <_UnicodeRange>[
_UnicodeRange(3585, 3675),
_UnicodeRange(8204, 8205),
_UnicodeRange(9676, 9676),
]),
_NotoFont('Noto Sans', <_UnicodeRange>[
_UnicodeRange(0, 255),
_UnicodeRange(305, 305),
_UnicodeRange(338, 339),
_UnicodeRange(699, 700),
_UnicodeRange(710, 710),
_UnicodeRange(730, 730),
_UnicodeRange(732, 732),
_UnicodeRange(8192, 8303),
_UnicodeRange(8308, 8308),
_UnicodeRange(8364, 8364),
_UnicodeRange(8482, 8482),
_UnicodeRange(8593, 8593),
_UnicodeRange(8595, 8595),
_UnicodeRange(8722, 8722),
_UnicodeRange(8725, 8725),
_UnicodeRange(65279, 65279),
_UnicodeRange(65533, 65533),
_UnicodeRange(1024, 1119),
_UnicodeRange(1168, 1169),
_UnicodeRange(1200, 1201),
_UnicodeRange(8470, 8470),
_UnicodeRange(1120, 1327),
_UnicodeRange(7296, 7304),
_UnicodeRange(8372, 8372),
_UnicodeRange(11744, 11775),
_UnicodeRange(42560, 42655),
_UnicodeRange(65070, 65071),
_UnicodeRange(880, 1023),
_UnicodeRange(7936, 8191),
_UnicodeRange(256, 591),
_UnicodeRange(601, 601),
_UnicodeRange(7680, 7935),
_UnicodeRange(8224, 8224),
_UnicodeRange(8352, 8363),
_UnicodeRange(8365, 8399),
_UnicodeRange(8467, 8467),
_UnicodeRange(11360, 11391),
_UnicodeRange(42784, 43007),
_UnicodeRange(258, 259),
_UnicodeRange(272, 273),
_UnicodeRange(296, 297),
_UnicodeRange(360, 361),
_UnicodeRange(416, 417),
_UnicodeRange(431, 432),
_UnicodeRange(7840, 7929),
_UnicodeRange(8363, 8363),
]),
];
// TODO(hterkelsen): Add unit tests for the Red-Black tree code.
/// A node in a red-black tree for Noto Fonts.
class _NotoTreeNode<T> {
_NotoTreeNode<T>? parent;
_NotoTreeNode<T>? left;
_NotoTreeNode<T>? right;
/// If `true`, then this node is black. Otherwise it is red.
bool isBlack = false;
bool get isRed => !isBlack;
final _UnicodeRange range;
final List<T> fonts;
_NotoTreeNode(this.range) : this.fonts = <T>[];
}
/// Associates [range] with [font] in the Noto Font tree.
///
/// Returns the root node.
_NotoTreeNode<T> _insertNotoFontRange<T>(
_UnicodeRange range, T font, _NotoTreeNode<T>? root) {
_NotoTreeNode<T>? newNode = _insertNotoFontRangeHelper(root, range, font);
if (newNode != null) {
_repairNotoFontTree(newNode);
// Make sure the root node is correctly set.
_NotoTreeNode<T> newRoot = newNode;
while (newRoot.parent != null) {
newRoot = newRoot.parent!;
}
return newRoot;
}
return root!;
}
/// Recurses the font tree and associates [range] with [font].
///
/// If a new node is created, it is returned so we can repair the tree.
_NotoTreeNode<T>? _insertNotoFontRangeHelper<T>(
_NotoTreeNode<T>? root, _UnicodeRange range, T font) {
if (root != null) {
if (root.range == range) {
// The root node range is the same as the range we're inserting.
root.fonts.add(font);
return null;
}
if (range.start < root.range.start) {
assert(range.end < root.range.start,
'Overlapping Unicode range in Noto Tree');
if (root.left != null) {
return _insertNotoFontRangeHelper<T>(root.left, range, font);
} else {
_NotoTreeNode<T> newNode = _NotoTreeNode<T>(range);
newNode.fonts.add(font);
newNode.parent = root;
root.left = newNode;
return newNode;
}
} else {
assert(root.range.end < range.start,
'Overlapping Unicode range in Noto Tree');
if (root.right != null) {
return _insertNotoFontRangeHelper<T>(root.right, range, font);
} else {
_NotoTreeNode<T> newNode = _NotoTreeNode<T>(range);
newNode.fonts.add(font);
newNode.parent = root;
root.right = newNode;
return newNode;
}
}
} else {
// If [root] is null, then the tree is empty. Create a new root.
_NotoTreeNode<T> newRoot = _NotoTreeNode<T>(range);
newRoot.fonts.add(font);
return newRoot;
}
}
void _rotateLeft(_NotoTreeNode node) {
// We will only ever call this on nodes which have a right child.
_NotoTreeNode newNode = node.right!;
_NotoTreeNode? parent = node.parent;
node.right = newNode.left;
newNode.left = node;
node.parent = newNode;
if (node.right != null) {
node.right!.parent = node;
}
if (parent != null) {
if (node == parent.left) {
parent.left = newNode;
} else {
parent.right = newNode;
}
}
newNode.parent = parent;
}
void _rotateRight(_NotoTreeNode node) {
// We will only ever call this on nodes which have a left child.
_NotoTreeNode newNode = node.left!;
_NotoTreeNode? parent = node.parent;
node.left = newNode.right;
newNode.right = node;
node.parent = newNode;
if (node.left != null) {
node.left!.parent = node;
}
if (parent != null) {
if (node == parent.left) {
parent.left = newNode;
} else {
parent.right = newNode;
}
}
newNode.parent = parent;
}
void _repairNotoFontTree(_NotoTreeNode node) {
if (node.parent == null) {
// This is the root node. The root node must be black.
node.isBlack = true;
return;
} else if (node.parent!.isBlack) {
// Do nothing.
return;
}
// If we've reached here, then (1) node's parent is non-null and (2) node's
// parent is red, which means that node's parent is not the root node and
// therefore (3) node's grandparent is not null;
_NotoTreeNode parent = node.parent!;
_NotoTreeNode grandparent = parent.parent!;
_NotoTreeNode? uncle;
if (parent == grandparent.left) {
uncle = grandparent.right;
} else {
uncle = grandparent.left;
}
if (uncle != null && uncle.isRed) {
parent.isBlack = true;
uncle.isBlack = true;
grandparent.isBlack = false;
_repairNotoFontTree(grandparent);
return;
}
if (node == parent.right && parent == grandparent.left) {
_rotateLeft(parent);
node = node.left!;
} else if (node == parent.left && parent == grandparent.right) {
_rotateRight(parent);
node = node.right!;
}
parent = node.parent!;
grandparent = parent.parent!;
if (node == parent.left) {
_rotateRight(grandparent);
} else {
_rotateLeft(grandparent);
}
parent.isBlack = true;
grandparent.isBlack = false;
}
bool _verifyNotoTree(_NotoTreeNode? root) {
_VerifyNotoTreeResult result = _verifyNotoSubtree(root);
return result.isValid;
}
_VerifyNotoTreeResult _verifyNotoSubtree(_NotoTreeNode? node) {
if (node == null) {
// Leaves of the tree are represented as null nodes. Leaf nodes are black.
return _VerifyNotoTreeResult(true, 1);
}
if (node.parent == null) {
// This is the root node of the tree. The root node must be black.
if (!node.isBlack) {
return _VerifyNotoTreeResult(false, 0, 'Root node is red');
}
}
int blackNodesOnPath = 0;
if (node.isRed) {
// Both of a red tree node's children must be black.
if ((node.left != null && !node.left!.isBlack) ||
(node.right != null && !node.right!.isBlack)) {
return _VerifyNotoTreeResult(false, -1, 'Red node has a red child');
}
} else {
blackNodesOnPath = 1;
}
_VerifyNotoTreeResult leftResult = _verifyNotoSubtree(node.left);
_VerifyNotoTreeResult rightResult = _verifyNotoSubtree(node.right);
if (!leftResult.isValid) {
return leftResult;
} else if (!rightResult.isValid) {
return rightResult;
} else if (leftResult.blackNodesOnPath != rightResult.blackNodesOnPath) {
return _VerifyNotoTreeResult(
false,
-1,
"The number of black nodes on the path from "
"root to leaf isn't the same for all leaves");
}
return _VerifyNotoTreeResult(
true, blackNodesOnPath + leftResult.blackNodesOnPath);
}
class _VerifyNotoTreeResult {
/// Whether or not the tree conforms to the Red-Black tree invariants.
final bool isValid;
/// The number of black nodes on the path from the node to the root.
final int blackNodesOnPath;
/// A human-readable reason why the tree is invalid.
final String? reason;
const _VerifyNotoTreeResult(this.isValid, this.blackNodesOnPath,
[this.reason]);
}
/// The root of the unresolved Noto font Red-Black Tree.
_NotoTreeNode<_NotoFont>? _notoTreeRoot;
/// The root of the resolved Noto font Red-Black Tree.
_NotoTreeNode<_ResolvedNotoSubset>? _resolvedNotoTreeRoot;
Map<_NotoFont, _ResolvedNotoFont> _resolvedNotoFonts =
<_NotoFont, _ResolvedNotoFont>{};

View File

@ -24,24 +24,13 @@ class SkiaFontCollection {
final Set<String?> registeredFamilies = <String?>{};
final Map<String, List<SkTypeface>> familyToTypefaceMap =
<String, List<SkTypeface>>{};
final List<String> globalFontFallbacks = <String>[];
final Map<String, int> _fontFallbackCounts = <String, int>{};
Future<void> ensureFontsLoaded() async {
await _loadFonts();
fontProvider = canvasKit.TypefaceFontProvider.Make();
familyToTypefaceMap.clear();
for (var font in _registeredFonts) {
fontProvider.registerFont(font.bytes, font.flutterFamily);
familyToTypefaceMap
.putIfAbsent(font.flutterFamily, () => <SkTypeface>[])
.add(font.typeface);
}
}
@ -151,15 +140,6 @@ class SkiaFontCollection {
return _RegisteredFont(bytes, family, actualFamily);
}
void registerFallbackFont(String url, String family) {
_fontFallbackCounts.putIfAbsent(family, () => 0);
int fontFallbackTag = _fontFallbackCounts[family]!;
_fontFallbackCounts[family] = _fontFallbackCounts[family]! + 1;
String countedFamily = '$family $fontFallbackTag';
_unloadedFonts.add(_registerFont(url, countedFamily));
globalFontFallbacks.add(countedFamily);
}
String? _readActualFamilyName(Uint8List bytes) {
final SkFontMgr tmpFontMgr = canvasKit.FontMgr.FromData([bytes])!;
String? actualFamily = tmpFontMgr.getFamilyName(0);
@ -189,12 +169,5 @@ class _RegisteredFont {
/// The font family that was parsed from the font's bytes.
final String actualFamily;
/// The [SkTypeface] created from this font's [bytes].
///
/// This is used to determine which code points are supported by this font.
final SkTypeface typeface;
_RegisteredFont(this.bytes, this.flutterFamily, this.actualFamily)
: this.typeface =
canvasKit.FontMgr.RefDefault().MakeTypefaceFromData(bytes);
_RegisteredFont(this.bytes, this.flutterFamily, this.actualFamily);
}

View File

@ -300,7 +300,7 @@ class CkImage implements ui.Image, StackTraceDebugger {
width: skImage.width(),
height: skImage.height(),
);
bytes = skImage.readPixels(0, 0, imageInfo);
bytes = skImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = skImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return

View File

@ -11,7 +11,8 @@ part of engine;
external String? get requestedRendererType;
/// Whether to use CanvasKit as the rendering backend.
bool get useCanvasKit => _autoDetect ? _detectRenderer() : _useSkia;
bool get useCanvasKit =>
_autoDetect ? _detectRenderer() : _useSkia;
/// Returns true if CanvasKit is used.
///
@ -40,13 +41,9 @@ const bool _autoDetect =
const bool _useSkia =
bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false);
/// If set to true, forces CPU-only rendering (i.e. no WebGL).
///
/// This is mainly used for testing or for apps that want to ensure they
/// run on devices which don't support WebGL.
const bool canvasKitForceCpuOnly = bool.fromEnvironment(
'FLUTTER_WEB_CANVASKIT_FORCE_CPU_ONLY',
defaultValue: false);
// If set to true, forces CPU-only rendering (i.e. no WebGL).
const bool canvasKitForceCpuOnly =
bool.fromEnvironment('FLUTTER_WEB_CANVASKIT_FORCE_CPU_ONLY', defaultValue: false);
/// The URL to use when downloading the CanvasKit script and associated wasm.
///
@ -55,7 +52,7 @@ const bool canvasKitForceCpuOnly = bool.fromEnvironment(
/// NPM, update this URL to `https://unpkg.com/canvaskit-wasm@0.34.0/bin/`.
const String canvasKitBaseUrl = String.fromEnvironment(
'FLUTTER_WEB_CANVASKIT_URL',
defaultValue: 'https://unpkg.com/canvaskit-wasm@0.20.0/bin/',
defaultValue: 'https://unpkg.com/canvaskit-wasm@0.19.0/bin/',
);
/// Initialize CanvasKit.
@ -66,10 +63,8 @@ Future<void> initializeCanvasKit() {
late StreamSubscription<html.Event> loadSubscription;
loadSubscription = domRenderer.canvasKitScript!.onLoad.listen((_) {
loadSubscription.cancel();
final CanvasKitInitPromise canvasKitInitPromise =
CanvasKitInit(CanvasKitInitOptions(
locateFile: js.allowInterop(
(String file, String unusedBase) => canvasKitBaseUrl + file),
final CanvasKitInitPromise canvasKitInitPromise = CanvasKitInit(CanvasKitInitOptions(
locateFile: js.allowInterop((String file, String unusedBase) => canvasKitBaseUrl + file),
));
canvasKitInitPromise.then(js.allowInterop((CanvasKit ck) {
canvasKit = ck;

View File

@ -265,7 +265,7 @@ class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
) {
final CkPath path1 = uiPath1 as CkPath;
final CkPath path2 = uiPath2 as CkPath;
final SkPath newPath = canvasKit.Path.MakeFromOp(
final SkPath newPath = canvasKit.MakePathFromOp(
path1.skiaObject,
path2.skiaObject,
toSkPathOp(operation),
@ -320,7 +320,7 @@ class CkPath extends ManagedSkiaObject<SkPath> implements ui.Path {
@override
SkPath resurrect() {
final SkPath path = canvasKit.Path.MakeFromCmds(_cachedCommands!);
final SkPath path = canvasKit.MakePathFromCmds(_cachedCommands!);
path.setFillType(toSkFillType(_fillType));
return path;
}

View File

@ -62,7 +62,11 @@ class CkParagraphStyle implements ui.ParagraphStyle {
skTextStyle.fontSize = fontSize;
}
skTextStyle.fontFamilies = _getEffectiveFontFamilies(fontFamily);
if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
skTextStyle.fontFamilies = [fontFamily];
return skTextStyle;
}
@ -70,8 +74,20 @@ class CkParagraphStyle implements ui.ParagraphStyle {
static SkStrutStyleProperties toSkStrutStyleProperties(ui.StrutStyle value) {
EngineStrutStyle style = value as EngineStrutStyle;
final SkStrutStyleProperties skStrutStyle = SkStrutStyleProperties();
skStrutStyle.fontFamilies =
_getEffectiveFontFamilies(style._fontFamily, style._fontFamilyFallback);
if (style._fontFamily != null) {
String fontFamily = style._fontFamily!;
if (!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
final List<String> fontFamilies = <String>[fontFamily];
if (style._fontFamilyFallback != null) {
fontFamilies.addAll(style._fontFamilyFallback!);
}
skStrutStyle.fontFamilies = fontFamilies;
} else {
// If no strut font family is given, default to Roboto.
skStrutStyle.fontFamilies = ['Roboto'];
}
if (style._fontSize != null) {
skStrutStyle.fontSize = style._fontSize;
@ -263,8 +279,18 @@ class CkTextStyle implements ui.TextStyle {
properties.locale = locale.toLanguageTag();
}
properties.fontFamilies =
_getEffectiveFontFamilies(fontFamily, fontFamilyFallback);
if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
List<String> fontFamilies = <String>[fontFamily];
if (fontFamilyFallback != null &&
!fontFamilyFallback.every((font) => fontFamily == font)) {
fontFamilies.addAll(fontFamilyFallback);
}
properties.fontFamilies = fontFamilies;
if (fontWeight != null || fontStyle != null) {
properties.fontStyle = toSkFontStyle(fontWeight, fontStyle);
@ -538,6 +564,7 @@ class CkParagraph extends ManagedSkiaObject<SkParagraph>
@override
void layout(ui.ParagraphConstraints constraints) {
assert(constraints.width != null); // ignore: unnecessary_null_comparison
_lastLayoutConstraints = constraints;
// TODO(het): CanvasKit throws an exception when laid out with
@ -633,65 +660,8 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
return properties;
}
/// Determines if the given [text] contains any code points which are not
/// supported by the current set of fonts.
void _ensureFontsSupportText(String text) {
// TODO(hterkelsen): Make this faster for the common case where the text
// is supported by the given fonts.
// 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;
}
CkTextStyle style = _peekStyle();
List<String> fontFamilies =
_getEffectiveFontFamilies(style.fontFamily, style.fontFamilyFallback);
List<SkTypeface> typefaces = <SkTypeface>[];
for (var font in fontFamilies) {
List<SkTypeface>? typefacesForFamily =
skiaFontCollection.familyToTypefaceMap[font];
if (typefacesForFamily != null) {
typefaces.addAll(typefacesForFamily);
}
}
// List<int> codeUnits = text.codeUnits;
List<bool> codeUnitsSupported = List<bool>.filled(text.length, false);
for (SkTypeface typeface in typefaces) {
SkFont font = SkFont(typeface);
Uint8List glyphs = font.getGlyphIDs(text);
assert(glyphs.length == codeUnitsSupported.length);
for (int i = 0; i < glyphs.length; i++) {
codeUnitsSupported[i] |=
glyphs[i] != 0 || _isControlCode(text.codeUnitAt(i));
}
}
if (codeUnitsSupported.any((x) => !x)) {
List<int> missingCodeUnits = <int>[];
for (int i = 0; i < codeUnitsSupported.length; i++) {
if (!codeUnitsSupported[i]) {
missingCodeUnits.add(text.codeUnitAt(i));
}
}
_findFontsForMissingCodeunits(missingCodeUnits);
}
}
/// Returns [true] if [codepoint] is a Unicode control code.
bool _isControlCode(int codepoint) {
return codepoint < 32 || (codepoint > 127 && codepoint < 160);
}
@override
void addText(String text) {
_ensureFontsSupportText(text);
_commands.add(_ParagraphCommand.addText(text));
_paragraphBuilder.addText(text);
}
@ -742,10 +712,8 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
_styleStack.add(skStyle);
_commands.add(_ParagraphCommand.pushStyle(ckStyle));
if (skStyle.foreground != null || skStyle.background != null) {
final SkPaint foreground =
skStyle.foreground?.skiaObject ?? _defaultTextStylePaint;
final SkPaint background =
skStyle.background?.skiaObject ?? _defaultTextStylePaint;
final SkPaint foreground = skStyle.foreground?.skiaObject ?? _defaultTextStylePaint;
final SkPaint background = skStyle.background?.skiaObject ?? _defaultTextStylePaint;
_paragraphBuilder.pushPaintStyle(
skStyle.skTextStyle, foreground, background);
} else {
@ -788,18 +756,3 @@ enum _ParagraphCommandType {
pushStyle,
addPlaceholder,
}
List<String> _getEffectiveFontFamilies(String? fontFamily,
[List<String>? fontFamilyFallback]) {
if (fontFamily == null ||
!skiaFontCollection.registeredFamilies.contains(fontFamily)) {
fontFamily = 'Roboto';
}
List<String> fontFamilies = <String>[fontFamily];
if (fontFamilyFallback != null &&
!fontFamilyFallback.every((font) => fontFamily == font)) {
fontFamilies.addAll(fontFamilyFallback);
}
fontFamilies.addAll(skiaFontCollection.globalFontFallbacks);
return fontFamilies;
}

View File

@ -836,7 +836,7 @@ void _pathTests() {
expect(measure2, isNull);
});
test('SkPath.toCmds and CanvasKit.Path.MakeFromCmds', () {
test('SkPath.toCmds and CanvasKit.MakePathFromCmds', () {
const ui.Rect rect = ui.Rect.fromLTRB(0, 0, 10, 10);
final SkPath path = SkPath();
path.addRect(toSkRect(rect));
@ -848,7 +848,7 @@ void _pathTests() {
<num>[5], // close
]);
final SkPath copy = canvasKit.Path.MakeFromCmds(path.toCmds());
final SkPath copy = canvasKit.MakePathFromCmds(path.toCmds());
expect(fromSkRect(copy.getBounds()), rect);
});
}
@ -1055,11 +1055,7 @@ void _canvasTests() {
test('drawPoints', () {
canvas.drawPoints(
canvasKit.PointMode.Lines,
<Float32List>[
Float32List.fromList([0, 0]),
Float32List.fromList([10, 10]),
Float32List.fromList([0, 10]),
],
Float32List.fromList([0, 0, 10, 10, 0, 10]),
SkPaint(),
);
});