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

Commit 4b662ed

Browse files
author
Nurhan Turgut
committed
Changing the font loading to work in all browsers.
1 parent f2a5a1f commit 4b662ed

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
@@ -14,8 +14,8 @@ const String _testFontUrl = 'packages/ui/assets/ahem.ttf';
1414
/// font manifest. If test fonts are enabled, then call
1515
/// [registerTestFonts] as well.
1616
class FontCollection {
17-
_FontManager _assetFontManager;
18-
_FontManager _testFontManager;
17+
FontManager _assetFontManager;
18+
FontManager _testFontManager;
1919

2020
/// Reads the font manifest using the [assetManager] and registers all of the
2121
/// fonts declared within.
@@ -47,7 +47,7 @@ class FontCollection {
4747
}
4848

4949
if (supportsFontLoadingApi) {
50-
_assetFontManager = _FontManager();
50+
_assetFontManager = FontManager();
5151
} else {
5252
_assetFontManager = _PolyfillFontManager();
5353
}
@@ -73,7 +73,7 @@ class FontCollection {
7373

7474
/// Registers fonts that are used by tests.
7575
void debugRegisterTestFonts() {
76-
_testFontManager = _FontManager();
76+
_testFontManager = FontManager();
7777
_testFontManager.registerAsset(
7878
_testFontFamily, 'url($_testFontUrl)', const <String, String>{});
7979
}
@@ -96,40 +96,78 @@ class FontCollection {
9696
}
9797

9898
/// Manages a collection of fonts and ensures they are loaded.
99-
class _FontManager {
99+
class FontManager {
100100
final List<Future<void>> _fontLoadingFutures = <Future<void>>[];
101101

102-
factory _FontManager() {
102+
factory FontManager() {
103103
if (supportsFontLoadingApi) {
104-
return _FontManager._();
104+
return FontManager._();
105105
} else {
106106
return _PolyfillFontManager();
107107
}
108108
}
109109

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

155193
/// 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)