David PHAM-VAN

Implement fallback font

@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 4
5 - Move files 5 - Move files
6 - Depreciate Font.stringSize 6 - Depreciate Font.stringSize
  7 +- Implement fallback font
7 8
8 ## 3.6.6 9 ## 3.6.6
9 10
@@ -161,6 +161,9 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management @@ -161,6 +161,9 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
161 /// Calculate the [PdfFontMetrics] for this glyph 161 /// Calculate the [PdfFontMetrics] for this glyph
162 PdfFontMetrics glyphMetrics(int charCode); 162 PdfFontMetrics glyphMetrics(int charCode);
163 163
  164 + /// is this Rune supported by this font
  165 + bool isRuneSupported(int charCode);
  166 +
164 /// Calculate the [PdfFontMetrics] for this string 167 /// Calculate the [PdfFontMetrics] for this string
165 PdfFontMetrics stringMetrics(String s, {double letterSpacing = 0}) { 168 PdfFontMetrics stringMetrics(String s, {double letterSpacing = 0}) {
166 if (s.isEmpty) { 169 if (s.isEmpty) {
@@ -188,4 +188,9 @@ class PdfTtfFont extends PdfFont { @@ -188,4 +188,9 @@ class PdfTtfFont extends PdfFont {
188 final metrics = bytes.map(glyphMetrics); 188 final metrics = bytes.map(glyphMetrics);
189 return PdfFontMetrics.append(metrics, letterSpacing: letterSpacing); 189 return PdfFontMetrics.append(metrics, letterSpacing: letterSpacing);
190 } 190 }
  191 +
  192 + @override
  193 + bool isRuneSupported(int charCode) {
  194 + return font.charToGlyphIndexMap.containsKey(charCode);
  195 + }
191 } 196 }
@@ -64,6 +64,11 @@ class PdfType1Font extends PdfFont { @@ -64,6 +64,11 @@ class PdfType1Font extends PdfFont {
64 64
65 @override 65 @override
66 PdfFontMetrics glyphMetrics(int charCode) { 66 PdfFontMetrics glyphMetrics(int charCode) {
  67 + if (!isRuneSupported(charCode)) {
  68 + throw Exception(
  69 + 'Unable to display U+${charCode.toRadixString(16)} with $fontName');
  70 + }
  71 +
67 return PdfFontMetrics( 72 return PdfFontMetrics(
68 left: 0, 73 left: 0,
69 top: descent, 74 top: descent,
@@ -72,4 +77,9 @@ class PdfType1Font extends PdfFont { @@ -72,4 +77,9 @@ class PdfType1Font extends PdfFont {
72 : PdfFont.defaultGlyphWidth, 77 : PdfFont.defaultGlyphWidth,
73 bottom: ascent); 78 bottom: ascent);
74 } 79 }
  80 +
  81 + @override
  82 + bool isRuneSupported(int charCode) {
  83 + return charCode >= 0x00 && charCode <= 0xff;
  84 + }
75 } 85 }
@@ -66,7 +66,7 @@ class SvgText extends SvgOperation { @@ -66,7 +66,7 @@ class SvgText extends SvgOperation {
66 66
67 final font = painter.getFontCache( 67 final font = painter.getFontCache(
68 _brush.fontFamily!, _brush.fontStyle!, _brush.fontWeight!)!; 68 _brush.fontFamily!, _brush.fontStyle!, _brush.fontWeight!)!;
69 - final pdfFont = font.getFont(Context(document: painter.document))!; 69 + final pdfFont = font.getFont(Context(document: painter.document));
70 final metrics = pdfFont.stringMetrics(text) * _brush.fontSize!.sizeValue; 70 final metrics = pdfFont.stringMetrics(text) * _brush.fontSize!.sizeValue;
71 offset = PdfPoint((x ?? offset.x) + dx, (y ?? offset.y) + dy); 71 offset = PdfPoint((x ?? offset.x) + dx, (y ?? offset.y) + dy);
72 72
@@ -392,7 +392,7 @@ class AnnotationTextField extends AnnotationBuilder { @@ -392,7 +392,7 @@ class AnnotationTextField extends AnnotationBuilder {
392 fieldFlags: fieldFlags, 392 fieldFlags: fieldFlags,
393 value: value, 393 value: value,
394 defaultValue: defaultValue, 394 defaultValue: defaultValue,
395 - font: _textStyle.font!.getFont(context)!, 395 + font: _textStyle.font!.getFont(context),
396 fontSize: _textStyle.fontSize!, 396 fontSize: _textStyle.fontSize!,
397 textColor: _textStyle.color!, 397 textColor: _textStyle.color!,
398 ), 398 ),
@@ -94,7 +94,7 @@ class _BarcodeWidget extends Widget { @@ -94,7 +94,7 @@ class _BarcodeWidget extends Widget {
94 final font = textStyle!.font!.getFont(context); 94 final font = textStyle!.font!.getFont(context);
95 95
96 for (final text in textList) { 96 for (final text in textList) {
97 - final metrics = font!.stringMetrics(text.text); 97 + final metrics = font.stringMetrics(text.text);
98 98
99 final top = box!.top - 99 final top = box!.top -
100 text.top - 100 text.top -
@@ -43,7 +43,7 @@ enum Type1Fonts { @@ -43,7 +43,7 @@ enum Type1Fonts {
43 class Font { 43 class Font {
44 Font() : font = null; 44 Font() : font = null;
45 45
46 - Font.type1(Type1Fonts this.font); 46 + Font.type1(this.font);
47 47
48 factory Font.courier() => Font.type1(Type1Fonts.courier); 48 factory Font.courier() => Font.type1(Type1Fonts.courier);
49 factory Font.courierBold() => Font.type1(Type1Fonts.courierBold); 49 factory Font.courierBold() => Font.type1(Type1Fonts.courierBold);
@@ -127,13 +127,13 @@ class Font { @@ -127,13 +127,13 @@ class Font {
127 127
128 PdfFont? _pdfFont; 128 PdfFont? _pdfFont;
129 129
130 - PdfFont? getFont(Context context) { 130 + PdfFont getFont(Context context) {
131 if (_pdfFont == null || _pdfFont!.pdfDocument != context.document) { 131 if (_pdfFont == null || _pdfFont!.pdfDocument != context.document) {
132 final pdfDocument = context.document; 132 final pdfDocument = context.document;
133 _pdfFont = buildFont(pdfDocument); 133 _pdfFont = buildFont(pdfDocument);
134 } 134 }
135 135
136 - return _pdfFont; 136 + return _pdfFont!;
137 } 137 }
138 138
139 @override 139 @override
@@ -278,7 +278,7 @@ class TextField extends StatelessWidget { @@ -278,7 +278,7 @@ class TextField extends StatelessWidget {
278 fieldFlags: fieldFlags, 278 fieldFlags: fieldFlags,
279 value: value, 279 value: value,
280 defaultValue: defaultValue, 280 defaultValue: defaultValue,
281 - font: _textStyle.font!.getFont(context)!, 281 + font: _textStyle.font!.getFont(context),
282 fontSize: _textStyle.fontSize!, 282 fontSize: _textStyle.fontSize!,
283 textColor: _textStyle.color!, 283 textColor: _textStyle.color!,
284 ); 284 );
@@ -21,9 +21,11 @@ import 'package:pdf/pdf.dart'; @@ -21,9 +21,11 @@ import 'package:pdf/pdf.dart';
21 import 'package:pdf/src/pdf/font/arabic.dart' as arabic; 21 import 'package:pdf/src/pdf/font/arabic.dart' as arabic;
22 22
23 import 'annotations.dart'; 23 import 'annotations.dart';
  24 +import 'basic.dart';
24 import 'document.dart'; 25 import 'document.dart';
25 import 'geometry.dart'; 26 import 'geometry.dart';
26 import 'multi_page.dart'; 27 import 'multi_page.dart';
  28 +import 'placeholders.dart';
27 import 'text_style.dart'; 29 import 'text_style.dart';
28 import 'theme.dart'; 30 import 'theme.dart';
29 import 'widget.dart'; 31 import 'widget.dart';
@@ -170,7 +172,7 @@ class _TextDecoration { @@ -170,7 +172,7 @@ class _TextDecoration {
170 0.05); 172 0.05);
171 173
172 if (style.decoration!.contains(TextDecoration.underline)) { 174 if (style.decoration!.contains(TextDecoration.underline)) {
173 - final base = -font!.descent * style.fontSize! * textScaleFactor / 2; 175 + final base = -font.descent * style.fontSize! * textScaleFactor / 2;
174 176
175 context.canvas.drawLine( 177 context.canvas.drawLine(
176 globalBox!.x + box!.left, 178 globalBox!.x + box!.left,
@@ -209,7 +211,7 @@ class _TextDecoration { @@ -209,7 +211,7 @@ class _TextDecoration {
209 } 211 }
210 212
211 if (style.decoration!.contains(TextDecoration.lineThrough)) { 213 if (style.decoration!.contains(TextDecoration.lineThrough)) {
212 - final base = (1 - font!.descent) * style.fontSize! * textScaleFactor / 2; 214 + final base = (1 - font.descent) * style.fontSize! * textScaleFactor / 2;
213 context.canvas.drawLine( 215 context.canvas.drawLine(
214 globalBox!.x + box!.left, 216 globalBox!.x + box!.left,
215 globalBox.top + box.bottom + base, 217 globalBox.top + box.bottom + base,
@@ -281,7 +283,7 @@ class _Word extends _Span { @@ -281,7 +283,7 @@ class _Word extends _Span {
281 PdfPoint point, 283 PdfPoint point,
282 ) { 284 ) {
283 context.canvas.drawString( 285 context.canvas.drawString(
284 - style.font!.getFont(context)!, 286 + style.font!.getFont(context),
285 style.fontSize! * textScaleFactor, 287 style.fontSize! * textScaleFactor,
286 text, 288 text,
287 point.x + offset.x, 289 point.x + offset.x,
@@ -316,10 +318,12 @@ class _Word extends _Span { @@ -316,10 +318,12 @@ class _Word extends _Span {
316 } 318 }
317 319
318 class _WidgetSpan extends _Span { 320 class _WidgetSpan extends _Span {
319 - _WidgetSpan(this.widget, TextStyle style) : super(style); 321 + _WidgetSpan(this.widget, TextStyle style, this.baseline) : super(style);
320 322
321 final Widget widget; 323 final Widget widget;
322 324
  325 + final double baseline;
  326 +
323 @override 327 @override
324 double get left => 0; 328 double get left => 0;
325 329
@@ -365,11 +369,21 @@ class _WidgetSpan extends _Span { @@ -365,11 +369,21 @@ class _WidgetSpan extends _Span {
365 double textScaleFactor, 369 double textScaleFactor,
366 PdfRect? globalBox, 370 PdfRect? globalBox,
367 ) { 371 ) {
  372 + const deb = 5;
  373 +
368 context.canvas 374 context.canvas
369 ..setLineWidth(.5) 375 ..setLineWidth(.5)
370 ..drawRect( 376 ..drawRect(
371 globalBox!.x + offset.x, globalBox.top + offset.y, width, height) 377 globalBox!.x + offset.x, globalBox.top + offset.y, width, height)
372 ..setStrokeColor(PdfColors.orange) 378 ..setStrokeColor(PdfColors.orange)
  379 + ..strokePath()
  380 + ..drawLine(
  381 + globalBox.x + offset.x - deb,
  382 + globalBox.top + offset.y - baseline,
  383 + globalBox.x + offset.x + width + deb,
  384 + globalBox.top + offset.y - baseline,
  385 + )
  386 + ..setStrokeColor(PdfColors.deepPurple)
373 ..strokePath(); 387 ..strokePath();
374 } 388 }
375 } 389 }
@@ -382,14 +396,24 @@ typedef _VisitorCallback = bool Function( @@ -382,14 +396,24 @@ typedef _VisitorCallback = bool Function(
382 396
383 @immutable 397 @immutable
384 abstract class InlineSpan { 398 abstract class InlineSpan {
385 - const InlineSpan({this.style, this.baseline, this.annotation}); 399 + const InlineSpan({
  400 + this.style,
  401 + required this.baseline,
  402 + this.annotation,
  403 + });
386 404
387 final TextStyle? style; 405 final TextStyle? style;
388 406
389 - final double? baseline; 407 + final double baseline;
390 408
391 final AnnotationBuilder? annotation; 409 final AnnotationBuilder? annotation;
392 410
  411 + InlineSpan copyWith({
  412 + TextStyle? style,
  413 + double? baseline,
  414 + AnnotationBuilder? annotation,
  415 + });
  416 +
393 String toPlainText() { 417 String toPlainText() {
394 final buffer = StringBuffer(); 418 final buffer = StringBuffer();
395 visitChildren(( 419 visitChildren((
@@ -424,6 +448,19 @@ class WidgetSpan extends InlineSpan { @@ -424,6 +448,19 @@ class WidgetSpan extends InlineSpan {
424 /// The widget to embed inline within text. 448 /// The widget to embed inline within text.
425 final Widget child; 449 final Widget child;
426 450
  451 + @override
  452 + InlineSpan copyWith({
  453 + TextStyle? style,
  454 + double? baseline,
  455 + AnnotationBuilder? annotation,
  456 + }) =>
  457 + WidgetSpan(
  458 + child: child,
  459 + style: style ?? this.style,
  460 + baseline: baseline ?? this.baseline,
  461 + annotation: annotation ?? this.annotation,
  462 + );
  463 +
427 /// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk. 464 /// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk.
428 @override 465 @override
429 bool visitChildren( 466 bool visitChildren(
@@ -452,6 +489,20 @@ class TextSpan extends InlineSpan { @@ -452,6 +489,20 @@ class TextSpan extends InlineSpan {
452 final List<InlineSpan>? children; 489 final List<InlineSpan>? children;
453 490
454 @override 491 @override
  492 + InlineSpan copyWith({
  493 + TextStyle? style,
  494 + double? baseline,
  495 + AnnotationBuilder? annotation,
  496 + }) =>
  497 + TextSpan(
  498 + style: style ?? this.style,
  499 + text: text,
  500 + baseline: baseline ?? this.baseline,
  501 + children: children,
  502 + annotation: annotation ?? this.annotation,
  503 + );
  504 +
  505 + @override
455 bool visitChildren( 506 bool visitChildren(
456 _VisitorCallback visitor, 507 _VisitorCallback visitor,
457 TextStyle? parentStyle, 508 TextStyle? parentStyle,
@@ -624,6 +675,8 @@ class RichText extends Widget with SpanningWidget { @@ -624,6 +675,8 @@ class RichText extends Widget with SpanningWidget {
624 675
625 var _mustClip = false; 676 var _mustClip = false;
626 677
  678 + List<InlineSpan>? _preprocessed;
  679 +
627 void _appendDecoration(bool append, _TextDecoration td) { 680 void _appendDecoration(bool append, _TextDecoration td) {
628 if (append && _decorations.isNotEmpty) { 681 if (append && _decorations.isNotEmpty) {
629 final last = _decorations.last; 682 final last = _decorations.last;
@@ -637,6 +690,132 @@ class RichText extends Widget with SpanningWidget { @@ -637,6 +690,132 @@ class RichText extends Widget with SpanningWidget {
637 _decorations.add(td); 690 _decorations.add(td);
638 } 691 }
639 692
  693 + InlineSpan _addText({
  694 + required List<int> text,
  695 + int start = 0,
  696 + int? end,
  697 + double baseline = 0,
  698 + required TextStyle style,
  699 + AnnotationBuilder? annotation,
  700 + }) {
  701 + return TextSpan(
  702 + text: String.fromCharCodes(text, start, end),
  703 + style: style,
  704 + baseline: baseline,
  705 + annotation: annotation,
  706 + );
  707 + }
  708 +
  709 + InlineSpan _addPlaceholder({
  710 + double baseline = 0,
  711 + required TextStyle style,
  712 + AnnotationBuilder? annotation,
  713 + }) {
  714 + return WidgetSpan(
  715 + child: SizedBox(
  716 + height: style.fontSize,
  717 + width: style.fontSize! / 2,
  718 + child: Placeholder(
  719 + color: style.color!,
  720 + strokeWidth: 1,
  721 + ),
  722 + ),
  723 + style: style,
  724 + baseline: baseline,
  725 + annotation: annotation,
  726 + );
  727 + }
  728 +
  729 + /// Check available characters in the fonts
  730 + /// use fallback if needed and replace emojis
  731 + List<InlineSpan> _preprocessSpans(Context context) {
  732 + final theme = Theme.of(context);
  733 + final defaultstyle = theme.defaultTextStyle;
  734 + final spans = <InlineSpan>[];
  735 +
  736 + text.visitChildren((
  737 + InlineSpan span,
  738 + TextStyle? style,
  739 + AnnotationBuilder? annotation,
  740 + ) {
  741 + if (span is! TextSpan) {
  742 + spans.add(span.copyWith(style: style, annotation: annotation));
  743 + return true;
  744 + }
  745 + if (span.text == null) {
  746 + return true;
  747 + }
  748 +
  749 + final font = style!.font!.getFont(context);
  750 +
  751 + var text = span.text!.runes.toList();
  752 +
  753 + for (var index = 0; index < text.length; index++) {
  754 + final rune = text[index];
  755 + if (rune == 0x0a) {
  756 + continue;
  757 + }
  758 +
  759 + if (!font.isRuneSupported(rune)) {
  760 + if (index > 0) {
  761 + spans.add(_addText(
  762 + text: text,
  763 + end: index,
  764 + style: style,
  765 + baseline: span.baseline,
  766 + annotation: annotation,
  767 + ));
  768 + }
  769 + var found = false;
  770 + for (final fb in style.fontFallback) {
  771 + final font = fb.getFont(context);
  772 + if (font.isRuneSupported(rune)) {
  773 + spans.add(_addText(
  774 + text: [rune],
  775 + style: style.copyWith(
  776 + font: fb,
  777 + fontNormal: fb,
  778 + fontBold: fb,
  779 + fontBoldItalic: fb,
  780 + fontItalic: fb,
  781 + ),
  782 + baseline: span.baseline,
  783 + annotation: annotation,
  784 + ));
  785 + found = true;
  786 + break;
  787 + }
  788 + }
  789 + if (!found) {
  790 + spans.add(_addPlaceholder(
  791 + style: style,
  792 + baseline: span.baseline,
  793 + annotation: annotation,
  794 + ));
  795 + assert(() {
  796 + print(
  797 + 'Unable to find a font to draw "${String.fromCharCode(rune)}" (U+${rune.toRadixString(16)}) try to provide a TextStyle.fontFallback');
  798 + return true;
  799 + }());
  800 + }
  801 + text = text.sublist(index + 1);
  802 + index = -1;
  803 + }
  804 + }
  805 +
  806 + spans.add(_addText(
  807 + text: text,
  808 + style: style,
  809 + baseline: span.baseline,
  810 + annotation: annotation,
  811 + ));
  812 +
  813 + return true;
  814 + }, defaultstyle, null);
  815 +
  816 + return spans;
  817 + }
  818 +
640 @override 819 @override
641 void layout(Context context, BoxConstraints constraints, 820 void layout(Context context, BoxConstraints constraints,
642 {bool parentUsesSize = false}) { 821 {bool parentUsesSize = false}) {
@@ -644,7 +823,6 @@ class RichText extends Widget with SpanningWidget { @@ -644,7 +823,6 @@ class RichText extends Widget with SpanningWidget {
644 _decorations.clear(); 823 _decorations.clear();
645 824
646 final theme = Theme.of(context); 825 final theme = Theme.of(context);
647 - final defaultstyle = theme.defaultTextStyle;  
648 final _softWrap = softWrap ?? theme.softWrap; 826 final _softWrap = softWrap ?? theme.softWrap;
649 final _maxLines = maxLines ?? theme.maxLines; 827 final _maxLines = maxLines ?? theme.maxLines;
650 _textAlign = textAlign ?? theme.textAlign; 828 _textAlign = textAlign ?? theme.textAlign;
@@ -669,228 +847,232 @@ class RichText extends Widget with SpanningWidget { @@ -669,228 +847,232 @@ class RichText extends Widget with SpanningWidget {
669 var spanStart = 0; 847 var spanStart = 0;
670 var overflow = false; 848 var overflow = false;
671 849
672 - text.visitChildren((  
673 - InlineSpan span,  
674 - TextStyle? style,  
675 - AnnotationBuilder? annotation,  
676 - ) {  
677 - if (span is TextSpan) {  
678 - if (span.text == null) {  
679 - return true;  
680 - } 850 + _preprocessed ??= _preprocessSpans(context);
681 851
682 - final font = style!.font!.getFont(context)!; 852 + void _buildLines() {
  853 + for (final span in _preprocessed!) {
  854 + final style = span.style;
  855 + final annotation = span.annotation;
683 856
684 - final space =  
685 - font.stringMetrics(' ') * (style.fontSize! * textScaleFactor); 857 + if (span is TextSpan) {
  858 + if (span.text == null) {
  859 + continue;
  860 + }
686 861
687 - final spanLines = (_textDirection == TextDirection.rtl  
688 - ? arabic.convert(span.text!)  
689 - : span.text)!  
690 - .split('\n'); 862 + final font = style!.font!.getFont(context);
691 863
692 - for (var line = 0; line < spanLines.length; line++) {  
693 - final words = spanLines[line].split(RegExp(r'\s'));  
694 - for (var index = 0; index < words.length; index++) {  
695 - final word = words[index]; 864 + final space =
  865 + font.stringMetrics(' ') * (style.fontSize! * textScaleFactor);
696 866
697 - if (word.isEmpty) {  
698 - offsetX += space.advanceWidth * style.wordSpacing! +  
699 - style.letterSpacing!;  
700 - continue;  
701 - } 867 + final spanLines = (_textDirection == TextDirection.rtl
  868 + ? arabic.convert(span.text!)
  869 + : span.text)!
  870 + .split('\n');
702 871
703 - final metrics = font.stringMetrics(word,  
704 - letterSpacing: style.letterSpacing! /  
705 - (style.fontSize! * textScaleFactor)) *  
706 - (style.fontSize! * textScaleFactor);  
707 -  
708 - if (_softWrap &&  
709 - offsetX + metrics.width > constraintWidth + 0.00001) {  
710 - if (spanCount > 0 && metrics.width <= constraintWidth) {  
711 - overflow = true;  
712 - lines.add(_Line(  
713 - this,  
714 - spanStart,  
715 - spanCount,  
716 - bottom,  
717 - offsetX -  
718 - space.advanceWidth * style.wordSpacing! -  
719 - style.letterSpacing!,  
720 - _textDirection,  
721 - true,  
722 - ));  
723 -  
724 - spanStart += spanCount;  
725 - spanCount = 0;  
726 -  
727 - offsetX = 0.0;  
728 - offsetY += bottom - top;  
729 - top = 0;  
730 - bottom = 0; 872 + for (var line = 0; line < spanLines.length; line++) {
  873 + final words = spanLines[line].split(RegExp(r'\s'));
  874 + for (var index = 0; index < words.length; index++) {
  875 + final word = words[index];
731 876
732 - if (_maxLines != null && lines.length >= _maxLines) {  
733 - return false;  
734 - } 877 + if (word.isEmpty) {
  878 + offsetX += space.advanceWidth * style.wordSpacing! +
  879 + style.letterSpacing!;
  880 + continue;
  881 + }
735 882
736 - if (offsetY > constraintHeight) {  
737 - return false; 883 + final metrics = font.stringMetrics(word,
  884 + letterSpacing: style.letterSpacing! /
  885 + (style.fontSize! * textScaleFactor)) *
  886 + (style.fontSize! * textScaleFactor);
  887 +
  888 + if (_softWrap &&
  889 + offsetX + metrics.width > constraintWidth + 0.00001) {
  890 + if (spanCount > 0 && metrics.width <= constraintWidth) {
  891 + overflow = true;
  892 + lines.add(_Line(
  893 + this,
  894 + spanStart,
  895 + spanCount,
  896 + bottom,
  897 + offsetX -
  898 + space.advanceWidth * style.wordSpacing! -
  899 + style.letterSpacing!,
  900 + _textDirection,
  901 + true,
  902 + ));
  903 +
  904 + spanStart += spanCount;
  905 + spanCount = 0;
  906 +
  907 + offsetX = 0.0;
  908 + offsetY += bottom - top;
  909 + top = 0;
  910 + bottom = 0;
  911 +
  912 + if (_maxLines != null && lines.length >= _maxLines) {
  913 + return;
  914 + }
  915 +
  916 + if (offsetY > constraintHeight) {
  917 + return;
  918 + }
  919 +
  920 + offsetY += style.lineSpacing! * textScaleFactor;
  921 + } else {
  922 + // One word Overflow, try to split it.
  923 + final pos = _splitWord(word, font, style, constraintWidth);
  924 +
  925 + if (pos < word.length) {
  926 + words[index] = word.substring(0, pos);
  927 + words.insert(index + 1, word.substring(pos));
  928 +
  929 + // Try again
  930 + index--;
  931 + continue;
  932 + }
738 } 933 }
  934 + }
739 935
740 - offsetY += style.lineSpacing! * textScaleFactor;  
741 - } else {  
742 - // One word Overflow, try to split it.  
743 - final pos = _splitWord(word, font, style, constraintWidth); 936 + final baseline = span.baseline * textScaleFactor;
  937 + final mt = tightBounds ? metrics.top : metrics.descent;
  938 + final mb = tightBounds ? metrics.bottom : metrics.ascent;
  939 + top = math.min(top, mt + baseline);
  940 + bottom = math.max(bottom, mb + baseline);
744 941
745 - if (pos < word.length) {  
746 - words[index] = word.substring(0, pos);  
747 - words.insert(index + 1, word.substring(pos)); 942 + final wd = _Word(
  943 + word,
  944 + style,
  945 + metrics,
  946 + );
  947 + wd.offset = PdfPoint(offsetX, -offsetY + baseline);
  948 + _spans.add(wd);
  949 + spanCount++;
  950 +
  951 + _appendDecoration(
  952 + spanCount > 1,
  953 + _TextDecoration(
  954 + style,
  955 + annotation,
  956 + _spans.length - 1,
  957 + _spans.length - 1,
  958 + ),
  959 + );
  960 +
  961 + offsetX += metrics.advanceWidth +
  962 + space.advanceWidth * style.wordSpacing! +
  963 + style.letterSpacing!;
  964 + }
748 965
749 - // Try again  
750 - index--;  
751 - continue;  
752 - } 966 + if (line < spanLines.length - 1) {
  967 + lines.add(_Line(
  968 + this,
  969 + spanStart,
  970 + spanCount,
  971 + bottom,
  972 + offsetX -
  973 + space.advanceWidth * style.wordSpacing! -
  974 + style.letterSpacing!,
  975 + _textDirection,
  976 + false,
  977 + ));
  978 +
  979 + spanStart += spanCount;
  980 +
  981 + offsetX = 0.0;
  982 + if (spanCount > 0) {
  983 + offsetY += bottom - top;
  984 + } else {
  985 + offsetY += space.ascent + space.descent;
753 } 986 }
754 - } 987 + top = 0;
  988 + bottom = 0;
  989 + spanCount = 0;
755 990
756 - final baseline = span.baseline! * textScaleFactor;  
757 - final mt = tightBounds ? metrics.top : metrics.descent;  
758 - final mb = tightBounds ? metrics.bottom : metrics.ascent;  
759 - top = math.min(top, mt + baseline);  
760 - bottom = math.max(bottom, mb + baseline); 991 + if (_maxLines != null && lines.length >= _maxLines) {
  992 + return;
  993 + }
761 994
762 - final wd = _Word(  
763 - word,  
764 - style,  
765 - metrics,  
766 - );  
767 - wd.offset = PdfPoint(offsetX, -offsetY + baseline);  
768 - _spans.add(wd);  
769 - spanCount++;  
770 -  
771 - _appendDecoration(  
772 - spanCount > 1,  
773 - _TextDecoration(  
774 - style,  
775 - annotation,  
776 - _spans.length - 1,  
777 - _spans.length - 1,  
778 - ),  
779 - );  
780 -  
781 - offsetX += metrics.advanceWidth +  
782 - space.advanceWidth * style.wordSpacing! +  
783 - style.letterSpacing!; 995 + if (offsetY > constraintHeight) {
  996 + return;
  997 + }
  998 +
  999 + offsetY += style.lineSpacing! * textScaleFactor;
  1000 + }
784 } 1001 }
785 1002
786 - if (line < spanLines.length - 1) { 1003 + offsetX -=
  1004 + space.advanceWidth * style.wordSpacing! - style.letterSpacing!;
  1005 + } else if (span is WidgetSpan) {
  1006 + span.child.layout(
  1007 + context,
  1008 + BoxConstraints(
  1009 + maxWidth: constraintWidth,
  1010 + maxHeight: constraintHeight,
  1011 + ));
  1012 + final ws = _WidgetSpan(
  1013 + span.child,
  1014 + style!,
  1015 + span.baseline,
  1016 + );
  1017 +
  1018 + if (offsetX + ws.width > constraintWidth && spanCount > 0) {
  1019 + overflow = true;
787 lines.add(_Line( 1020 lines.add(_Line(
788 this, 1021 this,
789 spanStart, 1022 spanStart,
790 spanCount, 1023 spanCount,
791 bottom, 1024 bottom,
792 - offsetX -  
793 - space.advanceWidth * style.wordSpacing! -  
794 - style.letterSpacing!, 1025 + offsetX,
795 _textDirection, 1026 _textDirection,
796 - false, 1027 + true,
797 )); 1028 ));
798 1029
799 spanStart += spanCount; 1030 spanStart += spanCount;
  1031 + spanCount = 0;
800 1032
801 - offsetX = 0.0;  
802 - if (spanCount > 0) {  
803 - offsetY += bottom - top;  
804 - } else {  
805 - offsetY += space.ascent + space.descent; 1033 + if (_maxLines != null && lines.length > _maxLines) {
  1034 + return;
806 } 1035 }
  1036 +
  1037 + offsetX = 0.0;
  1038 + offsetY += bottom - top;
807 top = 0; 1039 top = 0;
808 bottom = 0; 1040 bottom = 0;
809 - spanCount = 0;  
810 -  
811 - if (_maxLines != null && lines.length >= _maxLines) {  
812 - return false;  
813 - }  
814 1041
815 if (offsetY > constraintHeight) { 1042 if (offsetY > constraintHeight) {
816 - return false; 1043 + return;
817 } 1044 }
818 1045
819 offsetY += style.lineSpacing! * textScaleFactor; 1046 offsetY += style.lineSpacing! * textScaleFactor;
820 } 1047 }
821 - }  
822 -  
823 - offsetX -=  
824 - space.advanceWidth * style.wordSpacing! - style.letterSpacing!;  
825 - } else if (span is WidgetSpan) {  
826 - span.child.layout(  
827 - context,  
828 - BoxConstraints(  
829 - maxWidth: constraintWidth,  
830 - maxHeight: constraintHeight,  
831 - ));  
832 - final ws = _WidgetSpan(  
833 - span.child,  
834 - style!,  
835 - );  
836 1048
837 - if (offsetX + ws.width > constraintWidth && spanCount > 0) {  
838 - overflow = true;  
839 - lines.add(_Line(  
840 - this,  
841 - spanStart,  
842 - spanCount, 1049 + final baseline = span.baseline * textScaleFactor;
  1050 + top = math.min(top, baseline);
  1051 + bottom = math.max(
843 bottom, 1052 bottom,
844 - offsetX,  
845 - _textDirection,  
846 - true,  
847 - ));  
848 -  
849 - spanStart += spanCount;  
850 - spanCount = 0;  
851 -  
852 - if (_maxLines != null && lines.length > _maxLines) {  
853 - return false;  
854 - } 1053 + ws.height + baseline,
  1054 + );
855 1055
856 - offsetX = 0.0;  
857 - offsetY += bottom - top;  
858 - top = 0;  
859 - bottom = 0; 1056 + ws.offset = PdfPoint(offsetX, -offsetY + baseline);
  1057 + _spans.add(ws);
  1058 + spanCount++;
860 1059
861 - if (offsetY > constraintHeight) {  
862 - return false;  
863 - } 1060 + _appendDecoration(
  1061 + spanCount > 1,
  1062 + _TextDecoration(
  1063 + style,
  1064 + annotation,
  1065 + _spans.length - 1,
  1066 + _spans.length - 1,
  1067 + ),
  1068 + );
864 1069
865 - offsetY += style.lineSpacing! * textScaleFactor; 1070 + offsetX += ws.left + ws.width;
866 } 1071 }
867 -  
868 - final baseline = span.baseline! * textScaleFactor;  
869 - top = math.min(top, baseline);  
870 - bottom = math.max(  
871 - bottom,  
872 - ws.height + baseline,  
873 - );  
874 -  
875 - ws.offset = PdfPoint(offsetX, -offsetY + baseline);  
876 - _spans.add(ws);  
877 - spanCount++;  
878 -  
879 - _appendDecoration(  
880 - spanCount > 1,  
881 - _TextDecoration(  
882 - style,  
883 - annotation,  
884 - _spans.length - 1,  
885 - _spans.length - 1,  
886 - ),  
887 - );  
888 -  
889 - offsetX += ws.left + ws.width;  
890 } 1072 }
  1073 + }
891 1074
892 - return true;  
893 - }, defaultstyle, null); 1075 + _buildLines();
894 1076
895 if (spanCount > 0) { 1077 if (spanCount > 0) {
896 lines.add(_Line( 1078 lines.add(_Line(
@@ -111,6 +111,7 @@ class TextStyle { @@ -111,6 +111,7 @@ class TextStyle {
111 Font? fontBold, 111 Font? fontBold,
112 Font? fontItalic, 112 Font? fontItalic,
113 Font? fontBoldItalic, 113 Font? fontBoldItalic,
  114 + this.fontFallback = const [],
114 this.fontSize, 115 this.fontSize,
115 this.fontWeight, 116 this.fontWeight,
116 this.fontStyle, 117 this.fontStyle,
@@ -165,6 +166,7 @@ class TextStyle { @@ -165,6 +166,7 @@ class TextStyle {
165 fontBold: Font.helveticaBold(), 166 fontBold: Font.helveticaBold(),
166 fontItalic: Font.helveticaOblique(), 167 fontItalic: Font.helveticaOblique(),
167 fontBoldItalic: Font.helveticaBoldOblique(), 168 fontBoldItalic: Font.helveticaBoldOblique(),
  169 + fontFallback: const [],
168 fontSize: _defaultFontSize, 170 fontSize: _defaultFontSize,
169 fontWeight: FontWeight.normal, 171 fontWeight: FontWeight.normal,
170 fontStyle: FontStyle.normal, 172 fontStyle: FontStyle.normal,
@@ -192,7 +194,10 @@ class TextStyle { @@ -192,7 +194,10 @@ class TextStyle {
192 194
193 final Font? fontBoldItalic; 195 final Font? fontBoldItalic;
194 196
195 - // font height, in pdf unit 197 + /// The ordered list of font to fall back on when a glyph cannot be found in a higher priority font.
  198 + final List<Font> fontFallback;
  199 +
  200 + /// font height, in pdf unit
196 final double? fontSize; 201 final double? fontSize;
197 202
198 /// The typeface thickness to use when painting the text (e.g., bold). 203 /// The typeface thickness to use when painting the text (e.g., bold).
@@ -233,6 +238,7 @@ class TextStyle { @@ -233,6 +238,7 @@ class TextStyle {
233 Font? fontBold, 238 Font? fontBold,
234 Font? fontItalic, 239 Font? fontItalic,
235 Font? fontBoldItalic, 240 Font? fontBoldItalic,
  241 + List<Font>? fontFallback,
236 double? fontSize, 242 double? fontSize,
237 FontWeight? fontWeight, 243 FontWeight? fontWeight,
238 FontStyle? fontStyle, 244 FontStyle? fontStyle,
@@ -255,6 +261,7 @@ class TextStyle { @@ -255,6 +261,7 @@ class TextStyle {
255 fontBold: fontBold ?? this.fontBold, 261 fontBold: fontBold ?? this.fontBold,
256 fontItalic: fontItalic ?? this.fontItalic, 262 fontItalic: fontItalic ?? this.fontItalic,
257 fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic, 263 fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic,
  264 + fontFallback: fontFallback ?? this.fontFallback,
258 fontSize: fontSize ?? this.fontSize, 265 fontSize: fontSize ?? this.fontSize,
259 fontWeight: fontWeight ?? this.fontWeight, 266 fontWeight: fontWeight ?? this.fontWeight,
260 fontStyle: fontStyle ?? this.fontStyle, 267 fontStyle: fontStyle ?? this.fontStyle,
@@ -339,6 +346,7 @@ class TextStyle { @@ -339,6 +346,7 @@ class TextStyle {
339 fontBold: other.fontBold, 346 fontBold: other.fontBold,
340 fontItalic: other.fontItalic, 347 fontItalic: other.fontItalic,
341 fontBoldItalic: other.fontBoldItalic, 348 fontBoldItalic: other.fontBoldItalic,
  349 + fontFallback: [...other.fontFallback, ...fontFallback],
342 fontSize: other.fontSize, 350 fontSize: other.fontSize,
343 fontWeight: other.fontWeight, 351 fontWeight: other.fontWeight,
344 fontStyle: other.fontStyle, 352 fontStyle: other.fontStyle,
@@ -23,6 +23,8 @@ import 'text.dart'; @@ -23,6 +23,8 @@ import 'text.dart';
23 import 'text_style.dart'; 23 import 'text_style.dart';
24 import 'widget.dart'; 24 import 'widget.dart';
25 25
  26 +typedef DefaultThemeDataBuilder = ThemeData Function();
  27 +
26 @immutable 28 @immutable
27 class ThemeData extends Inherited { 29 class ThemeData extends Inherited {
28 factory ThemeData({ 30 factory ThemeData({
@@ -100,6 +102,7 @@ class ThemeData extends Inherited { @@ -100,6 +102,7 @@ class ThemeData extends Inherited {
100 Font? italic, 102 Font? italic,
101 Font? boldItalic, 103 Font? boldItalic,
102 Font? icons, 104 Font? icons,
  105 + List<Font>? fontFallback,
103 }) { 106 }) {
104 final defaultStyle = TextStyle.defaultStyle().copyWith( 107 final defaultStyle = TextStyle.defaultStyle().copyWith(
105 font: base, 108 font: base,
@@ -107,6 +110,7 @@ class ThemeData extends Inherited { @@ -107,6 +110,7 @@ class ThemeData extends Inherited {
107 fontBold: bold, 110 fontBold: bold,
108 fontItalic: italic, 111 fontItalic: italic,
109 fontBoldItalic: boldItalic, 112 fontBoldItalic: boldItalic,
  113 + fontFallback: fontFallback,
110 ); 114 );
111 final fontSize = defaultStyle.fontSize!; 115 final fontSize = defaultStyle.fontSize!;
112 116
@@ -130,7 +134,34 @@ class ThemeData extends Inherited { @@ -130,7 +134,34 @@ class ThemeData extends Inherited {
130 ); 134 );
131 } 135 }
132 136
133 - factory ThemeData.base() => ThemeData.withFont(); 137 + factory ThemeData.base() =>
  138 + buildThemeData == null ? ThemeData.withFont() : buildThemeData!();
  139 +
  140 + static DefaultThemeDataBuilder? buildThemeData;
  141 +
  142 + final TextStyle defaultTextStyle;
  143 +
  144 + final TextStyle paragraphStyle;
  145 +
  146 + final TextStyle header0;
  147 + final TextStyle header1;
  148 + final TextStyle header2;
  149 + final TextStyle header3;
  150 + final TextStyle header4;
  151 + final TextStyle header5;
  152 +
  153 + final TextStyle bulletStyle;
  154 +
  155 + final TextStyle tableHeader;
  156 +
  157 + final TextStyle tableCell;
  158 +
  159 + final TextAlign textAlign;
  160 + final bool softWrap;
  161 + final int? maxLines;
  162 + final TextOverflow overflow;
  163 +
  164 + final IconThemeData iconTheme;
134 165
135 ThemeData copyWith({ 166 ThemeData copyWith({
136 TextStyle? defaultTextStyle, 167 TextStyle? defaultTextStyle,
@@ -168,30 +199,6 @@ class ThemeData extends Inherited { @@ -168,30 +199,6 @@ class ThemeData extends Inherited {
168 maxLines: maxLines ?? this.maxLines, 199 maxLines: maxLines ?? this.maxLines,
169 iconTheme: iconTheme ?? this.iconTheme, 200 iconTheme: iconTheme ?? this.iconTheme,
170 ); 201 );
171 -  
172 - final TextStyle defaultTextStyle;  
173 -  
174 - final TextStyle paragraphStyle;  
175 -  
176 - final TextStyle header0;  
177 - final TextStyle header1;  
178 - final TextStyle header2;  
179 - final TextStyle header3;  
180 - final TextStyle header4;  
181 - final TextStyle header5;  
182 -  
183 - final TextStyle bulletStyle;  
184 -  
185 - final TextStyle tableHeader;  
186 -  
187 - final TextStyle tableCell;  
188 -  
189 - final TextAlign textAlign;  
190 - final bool softWrap;  
191 - final int? maxLines;  
192 - final TextOverflow overflow;  
193 -  
194 - final IconThemeData iconTheme;  
195 } 202 }
196 203
197 class Theme extends StatelessWidget { 204 class Theme extends StatelessWidget {
@@ -24,9 +24,9 @@ import 'package:test/test.dart'; @@ -24,9 +24,9 @@ import 'package:test/test.dart';
24 import 'utils.dart'; 24 import 'utils.dart';
25 25
26 late Document pdf; 26 late Document pdf;
27 -Font? ttf;  
28 -Font? ttfBold;  
29 -Font? asian; 27 +late Font ttf;
  28 +late Font ttfBold;
  29 +late Font asian;
30 30
31 Iterable<TextDecoration> permute( 31 Iterable<TextDecoration> permute(
32 List<TextDecoration> prefix, List<TextDecoration> remaining) sync* { 32 List<TextDecoration> prefix, List<TextDecoration> remaining) sync* {
No preview for this file type