mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This reverts commit eff27c74bf8c74781786f3c3724347e12764018a. Reason for revert: Incorrect logic breaks Gallery
This commit is contained in:
parent
82b4ae86d6
commit
f9f4d0168d
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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>{};
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user