Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6c0b4f3

Browse files
author
Nurhan Turgut
committed
Changing the font loading to work in all browsers.
1 parent 6ffb86c commit 6c0b4f3

File tree

2 files changed

+166
-20
lines changed

2 files changed

+166
-20
lines changed

lib/web_ui/lib/src/engine/text/font_collection.dart

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const String _robotoFontUrl = 'packages/ui/assets/Roboto-Regular.ttf';
1616
/// font manifest. If test fonts are enabled, then call
1717
/// [registerTestFonts] as well.
1818
class FontCollection {
19-
_FontManager _assetFontManager;
20-
_FontManager _testFontManager;
19+
FontManager _assetFontManager;
20+
FontManager _testFontManager;
2121

2222
/// Reads the font manifest using the [assetManager] and registers all of the
2323
/// fonts declared within.
@@ -49,7 +49,7 @@ class FontCollection {
4949
}
5050

5151
if (supportsFontLoadingApi) {
52-
_assetFontManager = _FontManager();
52+
_assetFontManager = FontManager();
5353
} else {
5454
_assetFontManager = _PolyfillFontManager();
5555
}
@@ -75,7 +75,7 @@ class FontCollection {
7575

7676
/// Registers fonts that are used by tests.
7777
void debugRegisterTestFonts() {
78-
_testFontManager = _FontManager();
78+
_testFontManager = FontManager();
7979
_testFontManager.registerAsset(
8080
_ahemFontFamily, 'url($_ahemFontUrl)', const <String, String>{});
8181
_testFontManager.registerAsset(
@@ -100,40 +100,78 @@ class FontCollection {
100100
}
101101

102102
/// Manages a collection of fonts and ensures they are loaded.
103-
class _FontManager {
103+
class FontManager {
104104
final List<Future<void>> _fontLoadingFutures = <Future<void>>[];
105105

106-
factory _FontManager() {
106+
factory FontManager() {
107107
if (supportsFontLoadingApi) {
108-
return _FontManager._();
108+
return FontManager._();
109109
} else {
110110
return _PolyfillFontManager();
111111
}
112112
}
113113

114-
_FontManager._();
115-
114+
FontManager._();
115+
116+
/// Registers assets to Flutter Web Engine.
117+
///
118+
/// Browsers and browesers versions differ siginificantly on how a valid font
119+
/// family name should be formatted. Notable issues are:
120+
/// Safari12 and Firefox crash if you create a [html.FontFace] with a font
121+
/// family that is not correct CSS syntax. Font family names accepted on these
122+
/// browsers, when wrapped it in quotes.
123+
/// Additionally, for Safari12 to work [html.FontFace] name should be loaded
124+
/// correctly on the first try.
125+
/// A font in Chrome chrashes if a [html.FontFace] is loaded only with quotes.
126+
/// Unlike Safari12 if a valid version is loaded afterwards it will show
127+
/// that fonts normally.
128+
/// In Safari 13 the [html.FontFace] should be loaded with unquoted family
129+
/// names.
130+
/// In order to avoid all these browser compatibility issues this method;
131+
/// detects the family names that might cause a conflict, loads it with
132+
/// quotes and loads it again without quotes. For all the other family names
133+
/// [html.FontFace] is loaded only once.
134+
/// See: https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#Valid_family_names
135+
/// See: https://drafts.csswg.org/css-fonts-3/#font-family-prop
116136
void registerAsset(
117137
String family,
118138
String asset,
119139
Map<String, String> descriptors,
120140
) {
121-
// Safari and Firefox crash if you create a [html.FontFace] with a font
122-
// family that is not correct CSS syntax. To ensure the font family is
123-
// accepted on these browsers, wrap it in quotes.
124-
// See: https://drafts.csswg.org/css-fonts-3/#font-family-prop
125-
if (browserEngine == BrowserEngine.firefox) {
126-
family = "'$family'";
141+
final String familyNameInQuotes = "'$family'";
142+
// Regular expression to detect punctuations. For example font family
143+
// 'Ahem!' falls into this category.
144+
final RegExp punctuations = RegExp(r"[.,:;!`\/#\$\%\^&~\*=\-_(){}]");
145+
// Regular expression to detect tokens starting with a digit.
146+
// For example font family 'Goudy Bookletter 1911' falls into this
147+
// category.
148+
final RegExp startWithDigit = RegExp(r"\b\d");
149+
// Fonts names when a package dependency is added has '/' in the family
150+
// names such as 'package/material_design_icons_flutter/...'
151+
if (family.contains('/') ||
152+
punctuations.hasMatch(family) ||
153+
startWithDigit.hasMatch(family)) {
154+
// Load a font family name with special chracters once here wrapped in
155+
// quotes.
156+
_loadFontFace(familyNameInQuotes, asset, descriptors);
127157
}
158+
// Load all font fonts, without quoted family names.
159+
_loadFontFace(family, asset, descriptors);
160+
}
161+
162+
void _loadFontFace(
163+
String family,
164+
String asset,
165+
Map<String, String> descriptors,
166+
) {
128167
// try/catch because `new FontFace` can crash with an improper font family.
129168
try {
130169
final html.FontFace fontFace = html.FontFace(family, asset, descriptors);
131-
_fontLoadingFutures.add(fontFace
132-
.load()
133-
.then((_) => html.document.fonts.add(fontFace), onError: (dynamic e) {
170+
_fontLoadingFutures.add(fontFace.load().then((_) {
171+
html.document.fonts.add(fontFace);
172+
}, onError: (dynamic e) {
134173
html.window.console
135174
.warn('Error while trying to load font family "$family":\n$e');
136-
return null;
137175
}));
138176
} catch (e) {
139177
html.window.console
@@ -153,7 +191,7 @@ class _FontManager {
153191
/// The CSS Font Loading API is not implemented in IE 11 or Edge. To tell if a
154192
/// font is loaded, we continuously measure some text using that font until the
155193
/// width changes.
156-
class _PolyfillFontManager extends _FontManager {
194+
class _PolyfillFontManager extends FontManager {
157195
_PolyfillFontManager() : super._();
158196

159197
/// A String containing characters whose width varies greatly between fonts.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:html' as html;
6+
7+
import 'package:ui/src/engine.dart';
8+
9+
import 'package:test/test.dart';
10+
11+
void main() {
12+
group('$FontManager', () {
13+
FontManager fontManager;
14+
const String _testFontUrl = 'packages/ui/assets/ahem.ttf';
15+
16+
setUp(() {
17+
fontManager = FontManager();
18+
});
19+
20+
tearDown(() {
21+
html.document.fonts.clear();
22+
});
23+
24+
test('Register Asset with no special characters', () async {
25+
final String _testFontFamily = "Ahem";
26+
final List<String> fontFamilyList = List<String>();
27+
28+
fontManager.registerAsset(
29+
_testFontFamily, 'url($_testFontUrl)', const <String, String>{});
30+
await fontManager.ensureFontsLoaded();
31+
html.document.fonts
32+
.forEach((html.FontFace f, html.FontFace f2, html.FontFaceSet s) {
33+
fontFamilyList.add(f.family);
34+
});
35+
36+
expect(fontFamilyList.length, equals(1));
37+
expect(fontFamilyList.first, 'Ahem');
38+
});
39+
40+
test('Register Asset twice with special character slash', () async {
41+
final String _testFontFamily = '/Ahem';
42+
final List<String> fontFamilyList = List<String>();
43+
44+
fontManager.registerAsset(
45+
_testFontFamily, 'url($_testFontUrl)', const <String, String>{});
46+
await fontManager.ensureFontsLoaded();
47+
html.document.fonts
48+
.forEach((html.FontFace f, html.FontFace f2, html.FontFaceSet s) {
49+
fontFamilyList.add(f.family);
50+
});
51+
52+
expect(fontFamilyList.length, equals(2));
53+
expect(fontFamilyList, contains('\'/Ahem\''));
54+
expect(fontFamilyList, contains('/Ahem'));
55+
});
56+
57+
test('Register Asset twice with exclamation mark', () async {
58+
final String _testFontFamily = 'Ahem!!ahem';
59+
final List<String> fontFamilyList = List<String>();
60+
61+
fontManager.registerAsset(
62+
_testFontFamily, 'url($_testFontUrl)', const <String, String>{});
63+
await fontManager.ensureFontsLoaded();
64+
html.document.fonts
65+
.forEach((html.FontFace f, html.FontFace f2, html.FontFaceSet s) {
66+
fontFamilyList.add(f.family);
67+
});
68+
69+
expect(fontFamilyList.length, equals(2));
70+
expect(fontFamilyList, contains('\'Ahem!!ahem\''));
71+
expect(fontFamilyList, contains('Ahem!!ahem'));
72+
});
73+
74+
test('Register Asset twice with coma', () async {
75+
final String _testFontFamily = 'Ahem ,ahem';
76+
final List<String> fontFamilyList = List<String>();
77+
78+
fontManager.registerAsset(
79+
_testFontFamily, 'url($_testFontUrl)', const <String, String>{});
80+
await fontManager.ensureFontsLoaded();
81+
html.document.fonts
82+
.forEach((html.FontFace f, html.FontFace f2, html.FontFaceSet s) {
83+
fontFamilyList.add(f.family);
84+
});
85+
86+
expect(fontFamilyList.length, equals(2));
87+
expect(fontFamilyList, contains('\'Ahem ,ahem\''));
88+
expect(fontFamilyList, contains('Ahem ,ahem'));
89+
});
90+
91+
test('Register Asset twice with a digit at the start of a token', () async {
92+
final String testFontFamily = 'Ahem 1998';
93+
final List<String> fontFamilyList = List<String>();
94+
95+
fontManager.registerAsset(
96+
testFontFamily, 'url($_testFontUrl)', const <String, String>{});
97+
await fontManager.ensureFontsLoaded();
98+
html.document.fonts
99+
.forEach((html.FontFace f, html.FontFace f2, html.FontFaceSet s) {
100+
fontFamilyList.add(f.family);
101+
});
102+
103+
expect(fontFamilyList.length, equals(2));
104+
expect(fontFamilyList, contains('Ahem 1998'));
105+
expect(fontFamilyList, contains('\'Ahem 1998\''));
106+
});
107+
});
108+
}

0 commit comments

Comments
 (0)