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