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,17 +847,19 @@ class RichText extends Widget with SpanningWidget { @@ -669,17 +847,19 @@ 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 - ) { 850 + _preprocessed ??= _preprocessSpans(context);
  851 +
  852 + void _buildLines() {
  853 + for (final span in _preprocessed!) {
  854 + final style = span.style;
  855 + final annotation = span.annotation;
  856 +
677 if (span is TextSpan) { 857 if (span is TextSpan) {
678 if (span.text == null) { 858 if (span.text == null) {
679 - return true; 859 + continue;
680 } 860 }
681 861
682 - final font = style!.font!.getFont(context)!; 862 + final font = style!.font!.getFont(context);
683 863
684 final space = 864 final space =
685 font.stringMetrics(' ') * (style.fontSize! * textScaleFactor); 865 font.stringMetrics(' ') * (style.fontSize! * textScaleFactor);
@@ -730,11 +910,11 @@ class RichText extends Widget with SpanningWidget { @@ -730,11 +910,11 @@ class RichText extends Widget with SpanningWidget {
730 bottom = 0; 910 bottom = 0;
731 911
732 if (_maxLines != null && lines.length >= _maxLines) { 912 if (_maxLines != null && lines.length >= _maxLines) {
733 - return false; 913 + return;
734 } 914 }
735 915
736 if (offsetY > constraintHeight) { 916 if (offsetY > constraintHeight) {
737 - return false; 917 + return;
738 } 918 }
739 919
740 offsetY += style.lineSpacing! * textScaleFactor; 920 offsetY += style.lineSpacing! * textScaleFactor;
@@ -753,7 +933,7 @@ class RichText extends Widget with SpanningWidget { @@ -753,7 +933,7 @@ class RichText extends Widget with SpanningWidget {
753 } 933 }
754 } 934 }
755 935
756 - final baseline = span.baseline! * textScaleFactor; 936 + final baseline = span.baseline * textScaleFactor;
757 final mt = tightBounds ? metrics.top : metrics.descent; 937 final mt = tightBounds ? metrics.top : metrics.descent;
758 final mb = tightBounds ? metrics.bottom : metrics.ascent; 938 final mb = tightBounds ? metrics.bottom : metrics.ascent;
759 top = math.min(top, mt + baseline); 939 top = math.min(top, mt + baseline);
@@ -809,11 +989,11 @@ class RichText extends Widget with SpanningWidget { @@ -809,11 +989,11 @@ class RichText extends Widget with SpanningWidget {
809 spanCount = 0; 989 spanCount = 0;
810 990
811 if (_maxLines != null && lines.length >= _maxLines) { 991 if (_maxLines != null && lines.length >= _maxLines) {
812 - return false; 992 + return;
813 } 993 }
814 994
815 if (offsetY > constraintHeight) { 995 if (offsetY > constraintHeight) {
816 - return false; 996 + return;
817 } 997 }
818 998
819 offsetY += style.lineSpacing! * textScaleFactor; 999 offsetY += style.lineSpacing! * textScaleFactor;
@@ -832,6 +1012,7 @@ class RichText extends Widget with SpanningWidget { @@ -832,6 +1012,7 @@ class RichText extends Widget with SpanningWidget {
832 final ws = _WidgetSpan( 1012 final ws = _WidgetSpan(
833 span.child, 1013 span.child,
834 style!, 1014 style!,
  1015 + span.baseline,
835 ); 1016 );
836 1017
837 if (offsetX + ws.width > constraintWidth && spanCount > 0) { 1018 if (offsetX + ws.width > constraintWidth && spanCount > 0) {
@@ -850,7 +1031,7 @@ class RichText extends Widget with SpanningWidget { @@ -850,7 +1031,7 @@ class RichText extends Widget with SpanningWidget {
850 spanCount = 0; 1031 spanCount = 0;
851 1032
852 if (_maxLines != null && lines.length > _maxLines) { 1033 if (_maxLines != null && lines.length > _maxLines) {
853 - return false; 1034 + return;
854 } 1035 }
855 1036
856 offsetX = 0.0; 1037 offsetX = 0.0;
@@ -859,13 +1040,13 @@ class RichText extends Widget with SpanningWidget { @@ -859,13 +1040,13 @@ class RichText extends Widget with SpanningWidget {
859 bottom = 0; 1040 bottom = 0;
860 1041
861 if (offsetY > constraintHeight) { 1042 if (offsetY > constraintHeight) {
862 - return false; 1043 + return;
863 } 1044 }
864 1045
865 offsetY += style.lineSpacing! * textScaleFactor; 1046 offsetY += style.lineSpacing! * textScaleFactor;
866 } 1047 }
867 1048
868 - final baseline = span.baseline! * textScaleFactor; 1049 + final baseline = span.baseline * textScaleFactor;
869 top = math.min(top, baseline); 1050 top = math.min(top, baseline);
870 bottom = math.max( 1051 bottom = math.max(
871 bottom, 1052 bottom,
@@ -888,9 +1069,10 @@ class RichText extends Widget with SpanningWidget { @@ -888,9 +1069,10 @@ class RichText extends Widget with SpanningWidget {
888 1069
889 offsetX += ws.left + ws.width; 1070 offsetX += ws.left + ws.width;
890 } 1071 }
  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