Skip to content

Commit bcd8c71

Browse files
authored
fix infinite loop in findMinimumFontsForCodeunits (flutter#24441)
1 parent df131a1 commit bcd8c71

File tree

4 files changed

+210
-53
lines changed

4 files changed

+210
-53
lines changed

lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ bool _registeredSymbolsAndEmoji = false;
1212

1313
final Set<int> codeUnitsWithNoKnownFont = <int>{};
1414

15-
Future<void> _findFontsForMissingCodeunits(List<int> codeunits) async {
15+
Future<void> findFontsForMissingCodeunits(List<int> codeunits) async {
1616
_ensureNotoFontTreeCreated();
1717

1818
// If all of the code units are known to have no Noto Font which covers them,
1919
// then just give up. We have already logged a warning.
2020
if (codeunits.every((u) => codeUnitsWithNoKnownFont.contains(u))) {
2121
return;
2222
}
23-
Set<_NotoFont> fonts = <_NotoFont>{};
23+
Set<NotoFont> fonts = <NotoFont>{};
2424
Set<int> coveredCodeUnits = <int>{};
2525
Set<int> missingCodeUnits = <int>{};
2626
for (int codeunit in codeunits) {
27-
List<_NotoFont> fontsForUnit = _notoTree!.intersections(codeunit);
27+
List<NotoFont> fontsForUnit = _notoTree!.intersections(codeunit);
2828
fonts.addAll(fontsForUnit);
2929
if (fontsForUnit.isNotEmpty) {
3030
coveredCodeUnits.add(codeunit);
@@ -33,15 +33,15 @@ Future<void> _findFontsForMissingCodeunits(List<int> codeunits) async {
3333
}
3434
}
3535

36-
fonts = _findMinimumFontsForCodeunits(coveredCodeUnits, fonts);
36+
fonts = findMinimumFontsForCodeunits(coveredCodeUnits, fonts);
3737

38-
for (_NotoFont font in fonts) {
38+
for (NotoFont font in fonts) {
3939
await font.ensureResolved();
4040
}
4141

4242
Set<_ResolvedNotoSubset> resolvedFonts = <_ResolvedNotoSubset>{};
4343
for (int codeunit in coveredCodeUnits) {
44-
for (_NotoFont font in fonts) {
44+
for (NotoFont font in fonts) {
4545
if (font.resolvedFont == null) {
4646
// We failed to resolve the font earlier.
4747
continue;
@@ -232,18 +232,20 @@ Future<void> _registerSymbolsAndEmoji() async {
232232
/// which finds the font which covers the most codeunits. If multiple CJK
233233
/// fonts match the same number of codeunits, we choose one based on the user's
234234
/// locale.
235-
Set<_NotoFont> _findMinimumFontsForCodeunits(
236-
Iterable<int> codeunits, Set<_NotoFont> fonts) {
235+
Set<NotoFont> findMinimumFontsForCodeunits(
236+
Iterable<int> codeunits, Set<NotoFont> fonts) {
237+
assert(fonts.isNotEmpty || codeunits.isEmpty);
237238
List<int> unmatchedCodeunits = List<int>.from(codeunits);
238-
Set<_NotoFont> minimumFonts = <_NotoFont>{};
239-
List<_NotoFont> bestFonts = <_NotoFont>[];
240-
int maxCodeunitsCovered = 0;
239+
Set<NotoFont> minimumFonts = <NotoFont>{};
240+
List<NotoFont> bestFonts = <NotoFont>[];
241241

242242
String language = html.window.navigator.language;
243243

244244
// This is guaranteed to terminate because [codeunits] is a list of fonts
245245
// which we've already determined are covered by [fonts].
246246
while (unmatchedCodeunits.isNotEmpty) {
247+
int maxCodeunitsCovered = 0;
248+
bestFonts.clear();
247249
for (var font in fonts) {
248250
int codeunitsCovered = 0;
249251
for (int codeunit in unmatchedCodeunits) {
@@ -259,10 +261,13 @@ Set<_NotoFont> _findMinimumFontsForCodeunits(
259261
bestFonts.add(font);
260262
}
261263
}
262-
assert(bestFonts.isNotEmpty);
264+
assert(
265+
bestFonts.isNotEmpty,
266+
'Did not find any fonts that cover code units: ${unmatchedCodeunits.join(', ')}',
267+
);
263268
// If the list of best fonts are all CJK fonts, choose the best one based
264269
// on locale. Otherwise just choose the first font.
265-
_NotoFont bestFont = bestFonts.first;
270+
NotoFont bestFont = bestFonts.first;
266271
if (bestFonts.length > 1) {
267272
if (bestFonts.every((font) => _cjkFonts.contains(font))) {
268273
if (language == 'zh-Hans' ||
@@ -301,27 +306,27 @@ void _ensureNotoFontTreeCreated() {
301306
return;
302307
}
303308

304-
Map<_NotoFont, List<CodeunitRange>> ranges =
305-
<_NotoFont, List<CodeunitRange>>{};
309+
Map<NotoFont, List<CodeunitRange>> ranges =
310+
<NotoFont, List<CodeunitRange>>{};
306311

307-
for (_NotoFont font in _notoFonts) {
312+
for (NotoFont font in _notoFonts) {
308313
for (CodeunitRange range in font.unicodeRanges) {
309314
ranges.putIfAbsent(font, () => <CodeunitRange>[]).add(range);
310315
}
311316
}
312317

313-
_notoTree = IntervalTree<_NotoFont>.createFromRanges(ranges);
318+
_notoTree = IntervalTree<NotoFont>.createFromRanges(ranges);
314319
}
315320

316-
class _NotoFont {
321+
class NotoFont {
317322
final String name;
318323
final List<CodeunitRange> unicodeRanges;
319324

320325
Completer<void>? _decodingCompleter;
321326

322327
_ResolvedNotoFont? resolvedFont;
323328

324-
_NotoFont(this.name, this.unicodeRanges);
329+
NotoFont(this.name, this.unicodeRanges);
325330

326331
bool matchesCodeunit(int codeunit) {
327332
for (CodeunitRange range in unicodeRanges) {
@@ -397,45 +402,45 @@ class _ResolvedNotoSubset {
397402
String toString() => '_ResolvedNotoSubset($family, $url)';
398403
}
399404

400-
_NotoFont _notoSansSC = _NotoFont('Noto Sans SC', <CodeunitRange>[
405+
NotoFont _notoSansSC = NotoFont('Noto Sans SC', <CodeunitRange>[
401406
CodeunitRange(12288, 12591),
402407
CodeunitRange(12800, 13311),
403408
CodeunitRange(19968, 40959),
404409
CodeunitRange(65072, 65135),
405410
CodeunitRange(65280, 65519),
406411
]);
407412

408-
_NotoFont _notoSansTC = _NotoFont('Noto Sans TC', <CodeunitRange>[
413+
NotoFont _notoSansTC = NotoFont('Noto Sans TC', <CodeunitRange>[
409414
CodeunitRange(12288, 12351),
410415
CodeunitRange(12549, 12585),
411416
CodeunitRange(19968, 40959),
412417
]);
413418

414-
_NotoFont _notoSansHK = _NotoFont('Noto Sans HK', <CodeunitRange>[
419+
NotoFont _notoSansHK = NotoFont('Noto Sans HK', <CodeunitRange>[
415420
CodeunitRange(12288, 12351),
416421
CodeunitRange(12549, 12585),
417422
CodeunitRange(19968, 40959),
418423
]);
419424

420-
_NotoFont _notoSansJP = _NotoFont('Noto Sans JP', <CodeunitRange>[
425+
NotoFont _notoSansJP = NotoFont('Noto Sans JP', <CodeunitRange>[
421426
CodeunitRange(12288, 12543),
422427
CodeunitRange(19968, 40959),
423428
CodeunitRange(65280, 65519),
424429
]);
425430

426-
List<_NotoFont> _cjkFonts = <_NotoFont>[
431+
List<NotoFont> _cjkFonts = <NotoFont>[
427432
_notoSansSC,
428433
_notoSansTC,
429434
_notoSansHK,
430435
_notoSansJP,
431436
];
432437

433-
List<_NotoFont> _notoFonts = <_NotoFont>[
438+
List<NotoFont> _notoFonts = <NotoFont>[
434439
_notoSansSC,
435440
_notoSansTC,
436441
_notoSansHK,
437442
_notoSansJP,
438-
_NotoFont('Noto Naskh Arabic UI', <CodeunitRange>[
443+
NotoFont('Noto Naskh Arabic UI', <CodeunitRange>[
439444
CodeunitRange(1536, 1791),
440445
CodeunitRange(8204, 8206),
441446
CodeunitRange(8208, 8209),
@@ -444,44 +449,44 @@ List<_NotoFont> _notoFonts = <_NotoFont>[
444449
CodeunitRange(64336, 65023),
445450
CodeunitRange(65132, 65276),
446451
]),
447-
_NotoFont('Noto Sans Armenian', <CodeunitRange>[
452+
NotoFont('Noto Sans Armenian', <CodeunitRange>[
448453
CodeunitRange(1328, 1424),
449454
CodeunitRange(64275, 64279),
450455
]),
451-
_NotoFont('Noto Sans Bengali UI', <CodeunitRange>[
456+
NotoFont('Noto Sans Bengali UI', <CodeunitRange>[
452457
CodeunitRange(2404, 2405),
453458
CodeunitRange(2433, 2555),
454459
CodeunitRange(8204, 8205),
455460
CodeunitRange(8377, 8377),
456461
CodeunitRange(9676, 9676),
457462
]),
458-
_NotoFont('Noto Sans Myanmar UI', <CodeunitRange>[
463+
NotoFont('Noto Sans Myanmar UI', <CodeunitRange>[
459464
CodeunitRange(4096, 4255),
460465
CodeunitRange(8204, 8205),
461466
CodeunitRange(9676, 9676),
462467
]),
463-
_NotoFont('Noto Sans Egyptian Hieroglyphs', <CodeunitRange>[
468+
NotoFont('Noto Sans Egyptian Hieroglyphs', <CodeunitRange>[
464469
CodeunitRange(77824, 78894),
465470
]),
466-
_NotoFont('Noto Sans Ethiopic', <CodeunitRange>[
471+
NotoFont('Noto Sans Ethiopic', <CodeunitRange>[
467472
CodeunitRange(4608, 5017),
468473
CodeunitRange(11648, 11742),
469474
CodeunitRange(43777, 43822),
470475
]),
471-
_NotoFont('Noto Sans Georgian', <CodeunitRange>[
476+
NotoFont('Noto Sans Georgian', <CodeunitRange>[
472477
CodeunitRange(1417, 1417),
473478
CodeunitRange(4256, 4351),
474479
CodeunitRange(11520, 11567),
475480
]),
476-
_NotoFont('Noto Sans Gujarati UI', <CodeunitRange>[
481+
NotoFont('Noto Sans Gujarati UI', <CodeunitRange>[
477482
CodeunitRange(2404, 2405),
478483
CodeunitRange(2688, 2815),
479484
CodeunitRange(8204, 8205),
480485
CodeunitRange(8377, 8377),
481486
CodeunitRange(9676, 9676),
482487
CodeunitRange(43056, 43065),
483488
]),
484-
_NotoFont('Noto Sans Gurmukhi UI', <CodeunitRange>[
489+
NotoFont('Noto Sans Gurmukhi UI', <CodeunitRange>[
485490
CodeunitRange(2404, 2405),
486491
CodeunitRange(2561, 2677),
487492
CodeunitRange(8204, 8205),
@@ -490,13 +495,13 @@ List<_NotoFont> _notoFonts = <_NotoFont>[
490495
CodeunitRange(9772, 9772),
491496
CodeunitRange(43056, 43065),
492497
]),
493-
_NotoFont('Noto Sans Hebrew', <CodeunitRange>[
498+
NotoFont('Noto Sans Hebrew', <CodeunitRange>[
494499
CodeunitRange(1424, 1535),
495500
CodeunitRange(8362, 8362),
496501
CodeunitRange(9676, 9676),
497502
CodeunitRange(64285, 64335),
498503
]),
499-
_NotoFont('Noto Sans Devanagari UI', <CodeunitRange>[
504+
NotoFont('Noto Sans Devanagari UI', <CodeunitRange>[
500505
CodeunitRange(2304, 2431),
501506
CodeunitRange(7376, 7414),
502507
CodeunitRange(7416, 7417),
@@ -507,29 +512,29 @@ List<_NotoFont> _notoFonts = <_NotoFont>[
507512
CodeunitRange(43056, 43065),
508513
CodeunitRange(43232, 43259),
509514
]),
510-
_NotoFont('Noto Sans Kannada UI', <CodeunitRange>[
515+
NotoFont('Noto Sans Kannada UI', <CodeunitRange>[
511516
CodeunitRange(2404, 2405),
512517
CodeunitRange(3202, 3314),
513518
CodeunitRange(8204, 8205),
514519
CodeunitRange(8377, 8377),
515520
CodeunitRange(9676, 9676),
516521
]),
517-
_NotoFont('Noto Sans Khmer UI', <CodeunitRange>[
522+
NotoFont('Noto Sans Khmer UI', <CodeunitRange>[
518523
CodeunitRange(6016, 6143),
519524
CodeunitRange(8204, 8204),
520525
CodeunitRange(9676, 9676),
521526
]),
522-
_NotoFont('Noto Sans KR', <CodeunitRange>[
527+
NotoFont('Noto Sans KR', <CodeunitRange>[
523528
CodeunitRange(12593, 12686),
524529
CodeunitRange(12800, 12828),
525530
CodeunitRange(12896, 12923),
526531
CodeunitRange(44032, 55215),
527532
]),
528-
_NotoFont('Noto Sans Lao UI', <CodeunitRange>[
533+
NotoFont('Noto Sans Lao UI', <CodeunitRange>[
529534
CodeunitRange(3713, 3807),
530535
CodeunitRange(9676, 9676),
531536
]),
532-
_NotoFont('Noto Sans Malayalam UI', <CodeunitRange>[
537+
NotoFont('Noto Sans Malayalam UI', <CodeunitRange>[
533538
CodeunitRange(775, 775),
534539
CodeunitRange(803, 803),
535540
CodeunitRange(2404, 2405),
@@ -538,33 +543,33 @@ List<_NotoFont> _notoFonts = <_NotoFont>[
538543
CodeunitRange(8377, 8377),
539544
CodeunitRange(9676, 9676),
540545
]),
541-
_NotoFont('Noto Sans Sinhala', <CodeunitRange>[
546+
NotoFont('Noto Sans Sinhala', <CodeunitRange>[
542547
CodeunitRange(2404, 2405),
543548
CodeunitRange(3458, 3572),
544549
CodeunitRange(8204, 8205),
545550
CodeunitRange(9676, 9676),
546551
]),
547-
_NotoFont('Noto Sans Tamil UI', <CodeunitRange>[
552+
NotoFont('Noto Sans Tamil UI', <CodeunitRange>[
548553
CodeunitRange(2404, 2405),
549554
CodeunitRange(2946, 3066),
550555
CodeunitRange(8204, 8205),
551556
CodeunitRange(8377, 8377),
552557
CodeunitRange(9676, 9676),
553558
]),
554-
_NotoFont('Noto Sans Telugu UI', <CodeunitRange>[
559+
NotoFont('Noto Sans Telugu UI', <CodeunitRange>[
555560
CodeunitRange(2385, 2386),
556561
CodeunitRange(2404, 2405),
557562
CodeunitRange(3072, 3199),
558563
CodeunitRange(7386, 7386),
559564
CodeunitRange(8204, 8205),
560565
CodeunitRange(9676, 9676),
561566
]),
562-
_NotoFont('Noto Sans Thai UI', <CodeunitRange>[
567+
NotoFont('Noto Sans Thai UI', <CodeunitRange>[
563568
CodeunitRange(3585, 3675),
564569
CodeunitRange(8204, 8205),
565570
CodeunitRange(9676, 9676),
566571
]),
567-
_NotoFont('Noto Sans', <CodeunitRange>[
572+
NotoFont('Noto Sans', <CodeunitRange>[
568573
CodeunitRange(0, 255),
569574
CodeunitRange(305, 305),
570575
CodeunitRange(338, 339),
@@ -639,7 +644,7 @@ class FallbackFontDownloadQueue {
639644
downloads.add(Future<void>(() async {
640645
ByteBuffer buffer;
641646
try {
642-
buffer = await downloader.downloadAsBytes(subset.url);
647+
buffer = await downloader.downloadAsBytes(subset.url, debugDescription: subset.family);
643648
} catch (e) {
644649
html.window.console
645650
.warn('Failed to load font ${subset.family} at ${subset.url}');
@@ -695,7 +700,7 @@ class NotoDownloader {
695700
/// Downloads the [url] and returns it as a [ByteBuffer].
696701
///
697702
/// Override this for testing.
698-
Future<ByteBuffer> downloadAsBytes(String url) {
703+
Future<ByteBuffer> downloadAsBytes(String url, {String? debugDescription}) {
699704
if (assertionsEnabled) {
700705
_debugActiveDownloadCount += 1;
701706
}
@@ -714,7 +719,7 @@ class NotoDownloader {
714719
/// Downloads the [url] and returns is as a [String].
715720
///
716721
/// Override this for testing.
717-
Future<String> downloadAsString(String url) {
722+
Future<String> downloadAsString(String url, {String? debugDescription}) {
718723
if (assertionsEnabled) {
719724
_debugActiveDownloadCount += 1;
720725
}
@@ -731,6 +736,12 @@ class NotoDownloader {
731736
}
732737

733738
/// The Noto font interval tree.
734-
IntervalTree<_NotoFont>? _notoTree;
739+
IntervalTree<NotoFont>? _notoTree;
740+
741+
/// Returns the tree of Noto fonts for tests.
742+
IntervalTree<NotoFont> get debugNotoTree {
743+
_ensureNotoFontTreeCreated();
744+
return _notoTree!;
745+
}
735746

736747
FallbackFontDownloadQueue notoDownloadQueue = FallbackFontDownloadQueue();

lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class IntervalTree<T> {
1818
/// have a range which contains the point.
1919
factory IntervalTree.createFromRanges(Map<T, List<CodeunitRange>> rangesMap) {
2020
// Get a list of all the ranges ordered by start index.
21-
List<IntervalTreeNode<T>> intervals = <IntervalTreeNode<T>>[];
21+
final List<IntervalTreeNode<T>> intervals = <IntervalTreeNode<T>>[];
2222
rangesMap.forEach((T key, List<CodeunitRange> rangeList) {
2323
for (CodeunitRange range in rangeList) {
2424
intervals.add(IntervalTreeNode<T>(key, range.start, range.end));
@@ -93,6 +93,16 @@ class IntervalTreeNode<T> {
9393

9494
IntervalTreeNode(this.value, this.low, this.high) : computedHigh = high;
9595

96+
Iterable<T> enumerateAllElements() sync* {
97+
if (left != null) {
98+
yield* left!.enumerateAllElements();
99+
}
100+
yield value;
101+
if (right != null) {
102+
yield* right!.enumerateAllElements();
103+
}
104+
}
105+
96106
bool contains(int x) {
97107
return low <= x && x <= high;
98108
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
714714
missingCodeUnits.add(codeUnits[i]);
715715
}
716716
}
717-
_findFontsForMissingCodeunits(missingCodeUnits);
717+
findFontsForMissingCodeunits(missingCodeUnits);
718718
}
719719
}
720720

0 commit comments

Comments
 (0)