Showing
14 changed files
with
437 additions
and
221 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,228 +847,232 @@ class RichText extends Widget with SpanningWidget { | @@ -669,228 +847,232 @@ class RichText extends Widget with SpanningWidget { | ||
669 | var spanStart = 0; | 847 | var spanStart = 0; |
670 | var overflow = false; | 848 | var overflow = false; |
671 | 849 | ||
672 | - text.visitChildren(( | ||
673 | - InlineSpan span, | ||
674 | - TextStyle? style, | ||
675 | - AnnotationBuilder? annotation, | ||
676 | - ) { | ||
677 | - if (span is TextSpan) { | ||
678 | - if (span.text == null) { | ||
679 | - return true; | ||
680 | - } | 850 | + _preprocessed ??= _preprocessSpans(context); |
681 | 851 | ||
682 | - final font = style!.font!.getFont(context)!; | 852 | + void _buildLines() { |
853 | + for (final span in _preprocessed!) { | ||
854 | + final style = span.style; | ||
855 | + final annotation = span.annotation; | ||
683 | 856 | ||
684 | - final space = | ||
685 | - font.stringMetrics(' ') * (style.fontSize! * textScaleFactor); | 857 | + if (span is TextSpan) { |
858 | + if (span.text == null) { | ||
859 | + continue; | ||
860 | + } | ||
686 | 861 | ||
687 | - final spanLines = (_textDirection == TextDirection.rtl | ||
688 | - ? arabic.convert(span.text!) | ||
689 | - : span.text)! | ||
690 | - .split('\n'); | 862 | + final font = style!.font!.getFont(context); |
691 | 863 | ||
692 | - for (var line = 0; line < spanLines.length; line++) { | ||
693 | - final words = spanLines[line].split(RegExp(r'\s')); | ||
694 | - for (var index = 0; index < words.length; index++) { | ||
695 | - final word = words[index]; | 864 | + final space = |
865 | + font.stringMetrics(' ') * (style.fontSize! * textScaleFactor); | ||
696 | 866 | ||
697 | - if (word.isEmpty) { | ||
698 | - offsetX += space.advanceWidth * style.wordSpacing! + | ||
699 | - style.letterSpacing!; | ||
700 | - continue; | ||
701 | - } | 867 | + final spanLines = (_textDirection == TextDirection.rtl |
868 | + ? arabic.convert(span.text!) | ||
869 | + : span.text)! | ||
870 | + .split('\n'); | ||
702 | 871 | ||
703 | - final metrics = font.stringMetrics(word, | ||
704 | - letterSpacing: style.letterSpacing! / | ||
705 | - (style.fontSize! * textScaleFactor)) * | ||
706 | - (style.fontSize! * textScaleFactor); | ||
707 | - | ||
708 | - if (_softWrap && | ||
709 | - offsetX + metrics.width > constraintWidth + 0.00001) { | ||
710 | - if (spanCount > 0 && metrics.width <= constraintWidth) { | ||
711 | - overflow = true; | ||
712 | - lines.add(_Line( | ||
713 | - this, | ||
714 | - spanStart, | ||
715 | - spanCount, | ||
716 | - bottom, | ||
717 | - offsetX - | ||
718 | - space.advanceWidth * style.wordSpacing! - | ||
719 | - style.letterSpacing!, | ||
720 | - _textDirection, | ||
721 | - true, | ||
722 | - )); | ||
723 | - | ||
724 | - spanStart += spanCount; | ||
725 | - spanCount = 0; | ||
726 | - | ||
727 | - offsetX = 0.0; | ||
728 | - offsetY += bottom - top; | ||
729 | - top = 0; | ||
730 | - bottom = 0; | 872 | + for (var line = 0; line < spanLines.length; line++) { |
873 | + final words = spanLines[line].split(RegExp(r'\s')); | ||
874 | + for (var index = 0; index < words.length; index++) { | ||
875 | + final word = words[index]; | ||
731 | 876 | ||
732 | - if (_maxLines != null && lines.length >= _maxLines) { | ||
733 | - return false; | ||
734 | - } | 877 | + if (word.isEmpty) { |
878 | + offsetX += space.advanceWidth * style.wordSpacing! + | ||
879 | + style.letterSpacing!; | ||
880 | + continue; | ||
881 | + } | ||
735 | 882 | ||
736 | - if (offsetY > constraintHeight) { | ||
737 | - return false; | 883 | + final metrics = font.stringMetrics(word, |
884 | + letterSpacing: style.letterSpacing! / | ||
885 | + (style.fontSize! * textScaleFactor)) * | ||
886 | + (style.fontSize! * textScaleFactor); | ||
887 | + | ||
888 | + if (_softWrap && | ||
889 | + offsetX + metrics.width > constraintWidth + 0.00001) { | ||
890 | + if (spanCount > 0 && metrics.width <= constraintWidth) { | ||
891 | + overflow = true; | ||
892 | + lines.add(_Line( | ||
893 | + this, | ||
894 | + spanStart, | ||
895 | + spanCount, | ||
896 | + bottom, | ||
897 | + offsetX - | ||
898 | + space.advanceWidth * style.wordSpacing! - | ||
899 | + style.letterSpacing!, | ||
900 | + _textDirection, | ||
901 | + true, | ||
902 | + )); | ||
903 | + | ||
904 | + spanStart += spanCount; | ||
905 | + spanCount = 0; | ||
906 | + | ||
907 | + offsetX = 0.0; | ||
908 | + offsetY += bottom - top; | ||
909 | + top = 0; | ||
910 | + bottom = 0; | ||
911 | + | ||
912 | + if (_maxLines != null && lines.length >= _maxLines) { | ||
913 | + return; | ||
914 | + } | ||
915 | + | ||
916 | + if (offsetY > constraintHeight) { | ||
917 | + return; | ||
918 | + } | ||
919 | + | ||
920 | + offsetY += style.lineSpacing! * textScaleFactor; | ||
921 | + } else { | ||
922 | + // One word Overflow, try to split it. | ||
923 | + final pos = _splitWord(word, font, style, constraintWidth); | ||
924 | + | ||
925 | + if (pos < word.length) { | ||
926 | + words[index] = word.substring(0, pos); | ||
927 | + words.insert(index + 1, word.substring(pos)); | ||
928 | + | ||
929 | + // Try again | ||
930 | + index--; | ||
931 | + continue; | ||
932 | + } | ||
738 | } | 933 | } |
934 | + } | ||
739 | 935 | ||
740 | - offsetY += style.lineSpacing! * textScaleFactor; | ||
741 | - } else { | ||
742 | - // One word Overflow, try to split it. | ||
743 | - final pos = _splitWord(word, font, style, constraintWidth); | 936 | + final baseline = span.baseline * textScaleFactor; |
937 | + final mt = tightBounds ? metrics.top : metrics.descent; | ||
938 | + final mb = tightBounds ? metrics.bottom : metrics.ascent; | ||
939 | + top = math.min(top, mt + baseline); | ||
940 | + bottom = math.max(bottom, mb + baseline); | ||
744 | 941 | ||
745 | - if (pos < word.length) { | ||
746 | - words[index] = word.substring(0, pos); | ||
747 | - words.insert(index + 1, word.substring(pos)); | 942 | + final wd = _Word( |
943 | + word, | ||
944 | + style, | ||
945 | + metrics, | ||
946 | + ); | ||
947 | + wd.offset = PdfPoint(offsetX, -offsetY + baseline); | ||
948 | + _spans.add(wd); | ||
949 | + spanCount++; | ||
950 | + | ||
951 | + _appendDecoration( | ||
952 | + spanCount > 1, | ||
953 | + _TextDecoration( | ||
954 | + style, | ||
955 | + annotation, | ||
956 | + _spans.length - 1, | ||
957 | + _spans.length - 1, | ||
958 | + ), | ||
959 | + ); | ||
960 | + | ||
961 | + offsetX += metrics.advanceWidth + | ||
962 | + space.advanceWidth * style.wordSpacing! + | ||
963 | + style.letterSpacing!; | ||
964 | + } | ||
748 | 965 | ||
749 | - // Try again | ||
750 | - index--; | ||
751 | - continue; | ||
752 | - } | 966 | + if (line < spanLines.length - 1) { |
967 | + lines.add(_Line( | ||
968 | + this, | ||
969 | + spanStart, | ||
970 | + spanCount, | ||
971 | + bottom, | ||
972 | + offsetX - | ||
973 | + space.advanceWidth * style.wordSpacing! - | ||
974 | + style.letterSpacing!, | ||
975 | + _textDirection, | ||
976 | + false, | ||
977 | + )); | ||
978 | + | ||
979 | + spanStart += spanCount; | ||
980 | + | ||
981 | + offsetX = 0.0; | ||
982 | + if (spanCount > 0) { | ||
983 | + offsetY += bottom - top; | ||
984 | + } else { | ||
985 | + offsetY += space.ascent + space.descent; | ||
753 | } | 986 | } |
754 | - } | 987 | + top = 0; |
988 | + bottom = 0; | ||
989 | + spanCount = 0; | ||
755 | 990 | ||
756 | - final baseline = span.baseline! * textScaleFactor; | ||
757 | - final mt = tightBounds ? metrics.top : metrics.descent; | ||
758 | - final mb = tightBounds ? metrics.bottom : metrics.ascent; | ||
759 | - top = math.min(top, mt + baseline); | ||
760 | - bottom = math.max(bottom, mb + baseline); | 991 | + if (_maxLines != null && lines.length >= _maxLines) { |
992 | + return; | ||
993 | + } | ||
761 | 994 | ||
762 | - final wd = _Word( | ||
763 | - word, | ||
764 | - style, | ||
765 | - metrics, | ||
766 | - ); | ||
767 | - wd.offset = PdfPoint(offsetX, -offsetY + baseline); | ||
768 | - _spans.add(wd); | ||
769 | - spanCount++; | ||
770 | - | ||
771 | - _appendDecoration( | ||
772 | - spanCount > 1, | ||
773 | - _TextDecoration( | ||
774 | - style, | ||
775 | - annotation, | ||
776 | - _spans.length - 1, | ||
777 | - _spans.length - 1, | ||
778 | - ), | ||
779 | - ); | ||
780 | - | ||
781 | - offsetX += metrics.advanceWidth + | ||
782 | - space.advanceWidth * style.wordSpacing! + | ||
783 | - style.letterSpacing!; | 995 | + if (offsetY > constraintHeight) { |
996 | + return; | ||
997 | + } | ||
998 | + | ||
999 | + offsetY += style.lineSpacing! * textScaleFactor; | ||
1000 | + } | ||
784 | } | 1001 | } |
785 | 1002 | ||
786 | - if (line < spanLines.length - 1) { | 1003 | + offsetX -= |
1004 | + space.advanceWidth * style.wordSpacing! - style.letterSpacing!; | ||
1005 | + } else if (span is WidgetSpan) { | ||
1006 | + span.child.layout( | ||
1007 | + context, | ||
1008 | + BoxConstraints( | ||
1009 | + maxWidth: constraintWidth, | ||
1010 | + maxHeight: constraintHeight, | ||
1011 | + )); | ||
1012 | + final ws = _WidgetSpan( | ||
1013 | + span.child, | ||
1014 | + style!, | ||
1015 | + span.baseline, | ||
1016 | + ); | ||
1017 | + | ||
1018 | + if (offsetX + ws.width > constraintWidth && spanCount > 0) { | ||
1019 | + overflow = true; | ||
787 | lines.add(_Line( | 1020 | lines.add(_Line( |
788 | this, | 1021 | this, |
789 | spanStart, | 1022 | spanStart, |
790 | spanCount, | 1023 | spanCount, |
791 | bottom, | 1024 | bottom, |
792 | - offsetX - | ||
793 | - space.advanceWidth * style.wordSpacing! - | ||
794 | - style.letterSpacing!, | 1025 | + offsetX, |
795 | _textDirection, | 1026 | _textDirection, |
796 | - false, | 1027 | + true, |
797 | )); | 1028 | )); |
798 | 1029 | ||
799 | spanStart += spanCount; | 1030 | spanStart += spanCount; |
1031 | + spanCount = 0; | ||
800 | 1032 | ||
801 | - offsetX = 0.0; | ||
802 | - if (spanCount > 0) { | ||
803 | - offsetY += bottom - top; | ||
804 | - } else { | ||
805 | - offsetY += space.ascent + space.descent; | 1033 | + if (_maxLines != null && lines.length > _maxLines) { |
1034 | + return; | ||
806 | } | 1035 | } |
1036 | + | ||
1037 | + offsetX = 0.0; | ||
1038 | + offsetY += bottom - top; | ||
807 | top = 0; | 1039 | top = 0; |
808 | bottom = 0; | 1040 | bottom = 0; |
809 | - spanCount = 0; | ||
810 | - | ||
811 | - if (_maxLines != null && lines.length >= _maxLines) { | ||
812 | - return false; | ||
813 | - } | ||
814 | 1041 | ||
815 | if (offsetY > constraintHeight) { | 1042 | if (offsetY > constraintHeight) { |
816 | - return false; | 1043 | + return; |
817 | } | 1044 | } |
818 | 1045 | ||
819 | offsetY += style.lineSpacing! * textScaleFactor; | 1046 | offsetY += style.lineSpacing! * textScaleFactor; |
820 | } | 1047 | } |
821 | - } | ||
822 | - | ||
823 | - offsetX -= | ||
824 | - space.advanceWidth * style.wordSpacing! - style.letterSpacing!; | ||
825 | - } else if (span is WidgetSpan) { | ||
826 | - span.child.layout( | ||
827 | - context, | ||
828 | - BoxConstraints( | ||
829 | - maxWidth: constraintWidth, | ||
830 | - maxHeight: constraintHeight, | ||
831 | - )); | ||
832 | - final ws = _WidgetSpan( | ||
833 | - span.child, | ||
834 | - style!, | ||
835 | - ); | ||
836 | 1048 | ||
837 | - if (offsetX + ws.width > constraintWidth && spanCount > 0) { | ||
838 | - overflow = true; | ||
839 | - lines.add(_Line( | ||
840 | - this, | ||
841 | - spanStart, | ||
842 | - spanCount, | 1049 | + final baseline = span.baseline * textScaleFactor; |
1050 | + top = math.min(top, baseline); | ||
1051 | + bottom = math.max( | ||
843 | bottom, | 1052 | bottom, |
844 | - offsetX, | ||
845 | - _textDirection, | ||
846 | - true, | ||
847 | - )); | ||
848 | - | ||
849 | - spanStart += spanCount; | ||
850 | - spanCount = 0; | ||
851 | - | ||
852 | - if (_maxLines != null && lines.length > _maxLines) { | ||
853 | - return false; | ||
854 | - } | 1053 | + ws.height + baseline, |
1054 | + ); | ||
855 | 1055 | ||
856 | - offsetX = 0.0; | ||
857 | - offsetY += bottom - top; | ||
858 | - top = 0; | ||
859 | - bottom = 0; | 1056 | + ws.offset = PdfPoint(offsetX, -offsetY + baseline); |
1057 | + _spans.add(ws); | ||
1058 | + spanCount++; | ||
860 | 1059 | ||
861 | - if (offsetY > constraintHeight) { | ||
862 | - return false; | ||
863 | - } | 1060 | + _appendDecoration( |
1061 | + spanCount > 1, | ||
1062 | + _TextDecoration( | ||
1063 | + style, | ||
1064 | + annotation, | ||
1065 | + _spans.length - 1, | ||
1066 | + _spans.length - 1, | ||
1067 | + ), | ||
1068 | + ); | ||
864 | 1069 | ||
865 | - offsetY += style.lineSpacing! * textScaleFactor; | 1070 | + offsetX += ws.left + ws.width; |
866 | } | 1071 | } |
867 | - | ||
868 | - final baseline = span.baseline! * textScaleFactor; | ||
869 | - top = math.min(top, baseline); | ||
870 | - bottom = math.max( | ||
871 | - bottom, | ||
872 | - ws.height + baseline, | ||
873 | - ); | ||
874 | - | ||
875 | - ws.offset = PdfPoint(offsetX, -offsetY + baseline); | ||
876 | - _spans.add(ws); | ||
877 | - spanCount++; | ||
878 | - | ||
879 | - _appendDecoration( | ||
880 | - spanCount > 1, | ||
881 | - _TextDecoration( | ||
882 | - style, | ||
883 | - annotation, | ||
884 | - _spans.length - 1, | ||
885 | - _spans.length - 1, | ||
886 | - ), | ||
887 | - ); | ||
888 | - | ||
889 | - offsetX += ws.left + ws.width; | ||
890 | } | 1072 | } |
1073 | + } | ||
891 | 1074 | ||
892 | - return true; | ||
893 | - }, defaultstyle, null); | 1075 | + _buildLines(); |
894 | 1076 | ||
895 | if (spanCount > 0) { | 1077 | if (spanCount > 0) { |
896 | lines.add(_Line( | 1078 | lines.add(_Line( |
@@ -111,6 +111,7 @@ class TextStyle { | @@ -111,6 +111,7 @@ class TextStyle { | ||
111 | Font? fontBold, | 111 | Font? fontBold, |
112 | Font? fontItalic, | 112 | Font? fontItalic, |
113 | Font? fontBoldItalic, | 113 | Font? fontBoldItalic, |
114 | + this.fontFallback = const [], | ||
114 | this.fontSize, | 115 | this.fontSize, |
115 | this.fontWeight, | 116 | this.fontWeight, |
116 | this.fontStyle, | 117 | this.fontStyle, |
@@ -165,6 +166,7 @@ class TextStyle { | @@ -165,6 +166,7 @@ class TextStyle { | ||
165 | fontBold: Font.helveticaBold(), | 166 | fontBold: Font.helveticaBold(), |
166 | fontItalic: Font.helveticaOblique(), | 167 | fontItalic: Font.helveticaOblique(), |
167 | fontBoldItalic: Font.helveticaBoldOblique(), | 168 | fontBoldItalic: Font.helveticaBoldOblique(), |
169 | + fontFallback: const [], | ||
168 | fontSize: _defaultFontSize, | 170 | fontSize: _defaultFontSize, |
169 | fontWeight: FontWeight.normal, | 171 | fontWeight: FontWeight.normal, |
170 | fontStyle: FontStyle.normal, | 172 | fontStyle: FontStyle.normal, |
@@ -192,7 +194,10 @@ class TextStyle { | @@ -192,7 +194,10 @@ class TextStyle { | ||
192 | 194 | ||
193 | final Font? fontBoldItalic; | 195 | final Font? fontBoldItalic; |
194 | 196 | ||
195 | - // font height, in pdf unit | 197 | + /// The ordered list of font to fall back on when a glyph cannot be found in a higher priority font. |
198 | + final List<Font> fontFallback; | ||
199 | + | ||
200 | + /// font height, in pdf unit | ||
196 | final double? fontSize; | 201 | final double? fontSize; |
197 | 202 | ||
198 | /// The typeface thickness to use when painting the text (e.g., bold). | 203 | /// The typeface thickness to use when painting the text (e.g., bold). |
@@ -233,6 +238,7 @@ class TextStyle { | @@ -233,6 +238,7 @@ class TextStyle { | ||
233 | Font? fontBold, | 238 | Font? fontBold, |
234 | Font? fontItalic, | 239 | Font? fontItalic, |
235 | Font? fontBoldItalic, | 240 | Font? fontBoldItalic, |
241 | + List<Font>? fontFallback, | ||
236 | double? fontSize, | 242 | double? fontSize, |
237 | FontWeight? fontWeight, | 243 | FontWeight? fontWeight, |
238 | FontStyle? fontStyle, | 244 | FontStyle? fontStyle, |
@@ -255,6 +261,7 @@ class TextStyle { | @@ -255,6 +261,7 @@ class TextStyle { | ||
255 | fontBold: fontBold ?? this.fontBold, | 261 | fontBold: fontBold ?? this.fontBold, |
256 | fontItalic: fontItalic ?? this.fontItalic, | 262 | fontItalic: fontItalic ?? this.fontItalic, |
257 | fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic, | 263 | fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic, |
264 | + fontFallback: fontFallback ?? this.fontFallback, | ||
258 | fontSize: fontSize ?? this.fontSize, | 265 | fontSize: fontSize ?? this.fontSize, |
259 | fontWeight: fontWeight ?? this.fontWeight, | 266 | fontWeight: fontWeight ?? this.fontWeight, |
260 | fontStyle: fontStyle ?? this.fontStyle, | 267 | fontStyle: fontStyle ?? this.fontStyle, |
@@ -339,6 +346,7 @@ class TextStyle { | @@ -339,6 +346,7 @@ class TextStyle { | ||
339 | fontBold: other.fontBold, | 346 | fontBold: other.fontBold, |
340 | fontItalic: other.fontItalic, | 347 | fontItalic: other.fontItalic, |
341 | fontBoldItalic: other.fontBoldItalic, | 348 | fontBoldItalic: other.fontBoldItalic, |
349 | + fontFallback: [...other.fontFallback, ...fontFallback], | ||
342 | fontSize: other.fontSize, | 350 | fontSize: other.fontSize, |
343 | fontWeight: other.fontWeight, | 351 | fontWeight: other.fontWeight, |
344 | fontStyle: other.fontStyle, | 352 | fontStyle: other.fontStyle, |
@@ -23,6 +23,8 @@ import 'text.dart'; | @@ -23,6 +23,8 @@ import 'text.dart'; | ||
23 | import 'text_style.dart'; | 23 | import 'text_style.dart'; |
24 | import 'widget.dart'; | 24 | import 'widget.dart'; |
25 | 25 | ||
26 | +typedef DefaultThemeDataBuilder = ThemeData Function(); | ||
27 | + | ||
26 | @immutable | 28 | @immutable |
27 | class ThemeData extends Inherited { | 29 | class ThemeData extends Inherited { |
28 | factory ThemeData({ | 30 | factory ThemeData({ |
@@ -100,6 +102,7 @@ class ThemeData extends Inherited { | @@ -100,6 +102,7 @@ class ThemeData extends Inherited { | ||
100 | Font? italic, | 102 | Font? italic, |
101 | Font? boldItalic, | 103 | Font? boldItalic, |
102 | Font? icons, | 104 | Font? icons, |
105 | + List<Font>? fontFallback, | ||
103 | }) { | 106 | }) { |
104 | final defaultStyle = TextStyle.defaultStyle().copyWith( | 107 | final defaultStyle = TextStyle.defaultStyle().copyWith( |
105 | font: base, | 108 | font: base, |
@@ -107,6 +110,7 @@ class ThemeData extends Inherited { | @@ -107,6 +110,7 @@ class ThemeData extends Inherited { | ||
107 | fontBold: bold, | 110 | fontBold: bold, |
108 | fontItalic: italic, | 111 | fontItalic: italic, |
109 | fontBoldItalic: boldItalic, | 112 | fontBoldItalic: boldItalic, |
113 | + fontFallback: fontFallback, | ||
110 | ); | 114 | ); |
111 | final fontSize = defaultStyle.fontSize!; | 115 | final fontSize = defaultStyle.fontSize!; |
112 | 116 | ||
@@ -130,7 +134,34 @@ class ThemeData extends Inherited { | @@ -130,7 +134,34 @@ class ThemeData extends Inherited { | ||
130 | ); | 134 | ); |
131 | } | 135 | } |
132 | 136 | ||
133 | - factory ThemeData.base() => ThemeData.withFont(); | 137 | + factory ThemeData.base() => |
138 | + buildThemeData == null ? ThemeData.withFont() : buildThemeData!(); | ||
139 | + | ||
140 | + static DefaultThemeDataBuilder? buildThemeData; | ||
141 | + | ||
142 | + final TextStyle defaultTextStyle; | ||
143 | + | ||
144 | + final TextStyle paragraphStyle; | ||
145 | + | ||
146 | + final TextStyle header0; | ||
147 | + final TextStyle header1; | ||
148 | + final TextStyle header2; | ||
149 | + final TextStyle header3; | ||
150 | + final TextStyle header4; | ||
151 | + final TextStyle header5; | ||
152 | + | ||
153 | + final TextStyle bulletStyle; | ||
154 | + | ||
155 | + final TextStyle tableHeader; | ||
156 | + | ||
157 | + final TextStyle tableCell; | ||
158 | + | ||
159 | + final TextAlign textAlign; | ||
160 | + final bool softWrap; | ||
161 | + final int? maxLines; | ||
162 | + final TextOverflow overflow; | ||
163 | + | ||
164 | + final IconThemeData iconTheme; | ||
134 | 165 | ||
135 | ThemeData copyWith({ | 166 | ThemeData copyWith({ |
136 | TextStyle? defaultTextStyle, | 167 | TextStyle? defaultTextStyle, |
@@ -168,30 +199,6 @@ class ThemeData extends Inherited { | @@ -168,30 +199,6 @@ class ThemeData extends Inherited { | ||
168 | maxLines: maxLines ?? this.maxLines, | 199 | maxLines: maxLines ?? this.maxLines, |
169 | iconTheme: iconTheme ?? this.iconTheme, | 200 | iconTheme: iconTheme ?? this.iconTheme, |
170 | ); | 201 | ); |
171 | - | ||
172 | - final TextStyle defaultTextStyle; | ||
173 | - | ||
174 | - final TextStyle paragraphStyle; | ||
175 | - | ||
176 | - final TextStyle header0; | ||
177 | - final TextStyle header1; | ||
178 | - final TextStyle header2; | ||
179 | - final TextStyle header3; | ||
180 | - final TextStyle header4; | ||
181 | - final TextStyle header5; | ||
182 | - | ||
183 | - final TextStyle bulletStyle; | ||
184 | - | ||
185 | - final TextStyle tableHeader; | ||
186 | - | ||
187 | - final TextStyle tableCell; | ||
188 | - | ||
189 | - final TextAlign textAlign; | ||
190 | - final bool softWrap; | ||
191 | - final int? maxLines; | ||
192 | - final TextOverflow overflow; | ||
193 | - | ||
194 | - final IconThemeData iconTheme; | ||
195 | } | 202 | } |
196 | 203 | ||
197 | class Theme extends StatelessWidget { | 204 | class Theme extends StatelessWidget { |
@@ -24,9 +24,9 @@ import 'package:test/test.dart'; | @@ -24,9 +24,9 @@ import 'package:test/test.dart'; | ||
24 | import 'utils.dart'; | 24 | import 'utils.dart'; |
25 | 25 | ||
26 | late Document pdf; | 26 | late Document pdf; |
27 | -Font? ttf; | ||
28 | -Font? ttfBold; | ||
29 | -Font? asian; | 27 | +late Font ttf; |
28 | +late Font ttfBold; | ||
29 | +late Font asian; | ||
30 | 30 | ||
31 | Iterable<TextDecoration> permute( | 31 | Iterable<TextDecoration> permute( |
32 | List<TextDecoration> prefix, List<TextDecoration> remaining) sync* { | 32 | List<TextDecoration> prefix, List<TextDecoration> remaining) sync* { |
No preview for this file type
-
Please register or login to post a comment