Showing
14 changed files
with
276 additions
and
60 deletions
@@ -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
-
Please register or login to post a comment