David PHAM-VAN

Improve Text rendering

@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 ## 3.3.0 3 ## 3.3.0
4 4
5 - Implement To be signed flieds 5 - Implement To be signed flieds
  6 +- Improve Text rendering
6 7
7 ## 3.2.0 8 ## 3.2.0
8 9
@@ -33,14 +33,14 @@ enum TextDirection { ltr, rtl } @@ -33,14 +33,14 @@ enum TextDirection { ltr, rtl }
33 abstract class _Span { 33 abstract class _Span {
34 _Span(this.style); 34 _Span(this.style);
35 35
36 - final TextStyle? style; 36 + final TextStyle style;
37 37
38 - PdfPoint offset = PdfPoint.zero; 38 + var offset = PdfPoint.zero;
39 39
40 - late double left;  
41 - late double top;  
42 - double? width;  
43 - double? height; 40 + double get left;
  41 + double get top;
  42 + double get width;
  43 + double get height;
44 44
45 @override 45 @override
46 String toString() { 46 String toString() {
@@ -83,13 +83,13 @@ class _TextDecoration { @@ -83,13 +83,13 @@ class _TextDecoration {
83 } 83 }
84 final x1 = spans[startSpan].offset.x + spans[startSpan].left; 84 final x1 = spans[startSpan].offset.x + spans[startSpan].left;
85 final x2 = 85 final x2 =
86 - spans[endSpan].offset.x + spans[endSpan].left + spans[endSpan].width!; 86 + spans[endSpan].offset.x + spans[endSpan].left + spans[endSpan].width;
87 var y1 = spans[startSpan].offset.y + spans[startSpan].top; 87 var y1 = spans[startSpan].offset.y + spans[startSpan].top;
88 - var y2 = y1 + spans[startSpan].height!; 88 + var y2 = y1 + spans[startSpan].height;
89 89
90 for (var n = startSpan + 1; n <= endSpan; n++) { 90 for (var n = startSpan + 1; n <= endSpan; n++) {
91 final ny1 = spans[n].offset.y + spans[n].top; 91 final ny1 = spans[n].offset.y + spans[n].top;
92 - final ny2 = ny1 + spans[n].height!; 92 + final ny2 = ny1 + spans[n].height;
93 y1 = math.min(y1, ny1); 93 y1 = math.min(y1, ny1);
94 y2 = math.max(y2, ny2); 94 y2 = math.max(y2, ny2);
95 } 95 }
@@ -233,7 +233,7 @@ class _TextDecoration { @@ -233,7 +233,7 @@ class _TextDecoration {
233 class _Word extends _Span { 233 class _Word extends _Span {
234 _Word( 234 _Word(
235 this.text, 235 this.text,
236 - TextStyle? style, 236 + TextStyle style,
237 this.metrics, 237 this.metrics,
238 ) : super(style); 238 ) : super(style);
239 239
@@ -476,7 +476,7 @@ class _Line { @@ -476,7 +476,7 @@ class _Line {
476 final int firstSpan; 476 final int firstSpan;
477 final int countSpan; 477 final int countSpan;
478 478
479 - TextAlign get textAlign => parent.textAlign; 479 + TextAlign get textAlign => parent._textAlign;
480 480
481 final double baseline; 481 final double baseline;
482 482
@@ -518,7 +518,7 @@ class _Line { @@ -518,7 +518,7 @@ class _Line {
518 if (textDirection == TextDirection.rtl) { 518 if (textDirection == TextDirection.rtl) {
519 for (var span in spans) { 519 for (var span in spans) {
520 span.offset = PdfPoint( 520 span.offset = PdfPoint(
521 - totalWidth - (span.offset.x + span.width!) - delta, 521 + totalWidth - (span.offset.x + span.width) - delta,
522 span.offset.y - baseline, 522 span.offset.y - baseline,
523 ); 523 );
524 } 524 }
@@ -537,34 +537,31 @@ class _Line { @@ -537,34 +537,31 @@ class _Line {
537 class RichText extends Widget { 537 class RichText extends Widget {
538 RichText({ 538 RichText({
539 required this.text, 539 required this.text,
540 - TextAlign? textAlign, 540 + this.textAlign,
541 this.textDirection, 541 this.textDirection,
542 - bool? softWrap, 542 + this.softWrap,
543 this.tightBounds = false, 543 this.tightBounds = false,
544 this.textScaleFactor = 1.0, 544 this.textScaleFactor = 1.0,
545 - int? maxLines,  
546 - }) : _textAlign = textAlign,  
547 - _softWrap = softWrap,  
548 - _maxLines = maxLines; 545 + this.maxLines,
  546 + });
549 547
550 static bool debug = false; 548 static bool debug = false;
551 549
552 final InlineSpan text; 550 final InlineSpan text;
553 551
554 - TextAlign get textAlign => _textAlign!;  
555 - TextAlign? _textAlign; 552 + final TextAlign? textAlign;
  553 +
  554 + late TextAlign _textAlign;
556 555
557 final TextDirection? textDirection; 556 final TextDirection? textDirection;
558 557
559 final double textScaleFactor; 558 final double textScaleFactor;
560 559
561 - bool get softWrap => _softWrap!;  
562 - bool? _softWrap; 560 + final bool? softWrap;
563 561
564 final bool tightBounds; 562 final bool tightBounds;
565 563
566 - int? get maxLines => _maxLines;  
567 - int? _maxLines; 564 + final int? maxLines;
568 565
569 final List<_Span> _spans = <_Span>[]; 566 final List<_Span> _spans = <_Span>[];
570 567
@@ -591,9 +588,9 @@ class RichText extends Widget { @@ -591,9 +588,9 @@ class RichText extends Widget {
591 588
592 final theme = Theme.of(context); 589 final theme = Theme.of(context);
593 final defaultstyle = theme.defaultTextStyle; 590 final defaultstyle = theme.defaultTextStyle;
594 - _softWrap ??= theme.softWrap;  
595 - _maxLines ??= theme.maxLines;  
596 - _textAlign ??= theme.textAlign; 591 + final _softWrap = softWrap ?? theme.softWrap;
  592 + final _maxLines = maxLines ?? theme.maxLines;
  593 + _textAlign = textAlign ?? theme.textAlign;
597 final _textDirection = textDirection ?? Directionality.of(context); 594 final _textDirection = textDirection ?? Directionality.of(context);
598 595
599 final constraintWidth = constraints.hasBoundedWidth 596 final constraintWidth = constraints.hasBoundedWidth
@@ -606,8 +603,8 @@ class RichText extends Widget { @@ -606,8 +603,8 @@ class RichText extends Widget {
606 var offsetX = 0.0; 603 var offsetX = 0.0;
607 var offsetY = 0.0; 604 var offsetY = 0.0;
608 605
609 - double? top;  
610 - double? bottom; 606 + var top = 0.0;
  607 + var bottom = 0.0;
611 608
612 final lines = <_Line>[]; 609 final lines = <_Line>[];
613 var spanCount = 0; 610 var spanCount = 0;
@@ -635,7 +632,10 @@ class RichText extends Widget { @@ -635,7 +632,10 @@ class RichText extends Widget {
635 .split('\n'); 632 .split('\n');
636 633
637 for (var line = 0; line < spanLines.length; line++) { 634 for (var line = 0; line < spanLines.length; line++) {
638 - for (var word in spanLines[line].split(RegExp(r'\s'))) { 635 + final words = spanLines[line].split(RegExp(r'\s'));
  636 + for (var index = 0; index < words.length; index++) {
  637 + final word = words[index];
  638 +
639 if (word.isEmpty) { 639 if (word.isEmpty) {
640 offsetX += space.advanceWidth * style.wordSpacing! + 640 offsetX += space.advanceWidth * style.wordSpacing! +
641 style.letterSpacing!; 641 style.letterSpacing!;
@@ -647,41 +647,54 @@ class RichText extends Widget { @@ -647,41 +647,54 @@ class RichText extends Widget {
647 (style.fontSize! * textScaleFactor)) * 647 (style.fontSize! * textScaleFactor)) *
648 (style.fontSize! * textScaleFactor); 648 (style.fontSize! * textScaleFactor);
649 649
650 - if (offsetX + metrics.width > constraintWidth && spanCount > 0) {  
651 - overflow = true;  
652 - lines.add(_Line(  
653 - this,  
654 - spanStart,  
655 - spanCount,  
656 - bottom ?? 0,  
657 - offsetX -  
658 - space.advanceWidth * style.wordSpacing! -  
659 - style.letterSpacing!,  
660 - _textDirection,  
661 - ));  
662 -  
663 - spanStart += spanCount;  
664 - spanCount = 0;  
665 -  
666 - if (maxLines != null && lines.length >= maxLines!) {  
667 - return false;  
668 - }  
669 -  
670 - offsetX = 0.0;  
671 - offsetY += bottom! - top! + style.lineSpacing!;  
672 - top = null;  
673 - bottom = null;  
674 -  
675 - if (offsetY > constraintHeight) {  
676 - return false; 650 + if (offsetX + metrics.width > constraintWidth + 0.00001) {
  651 + if (spanCount > 0 && metrics.width <= constraintWidth) {
  652 + overflow = true;
  653 + lines.add(_Line(
  654 + this,
  655 + spanStart,
  656 + spanCount,
  657 + bottom,
  658 + offsetX -
  659 + space.advanceWidth * style.wordSpacing! -
  660 + style.letterSpacing!,
  661 + _textDirection,
  662 + ));
  663 +
  664 + spanStart += spanCount;
  665 + spanCount = 0;
  666 +
  667 + if (_maxLines != null && lines.length >= _maxLines) {
  668 + return false;
  669 + }
  670 +
  671 + offsetX = 0.0;
  672 + offsetY += bottom - top + style.lineSpacing!;
  673 +
  674 + top = 0;
  675 + bottom = 0;
  676 +
  677 + if (offsetY > constraintHeight) {
  678 + return false;
  679 + }
  680 + } else {
  681 + // One word Overflow, try to split it.
  682 + final pos = _splitWord(word, font, style, constraintWidth);
  683 +
  684 + words[index] = word.substring(0, pos);
  685 + words.insert(index + 1, word.substring(pos));
  686 +
  687 + // Try again
  688 + index--;
  689 + continue;
677 } 690 }
678 } 691 }
679 692
680 final baseline = span.baseline! * textScaleFactor; 693 final baseline = span.baseline! * textScaleFactor;
681 final mt = tightBounds ? metrics.top : metrics.descent; 694 final mt = tightBounds ? metrics.top : metrics.descent;
682 final mb = tightBounds ? metrics.bottom : metrics.ascent; 695 final mb = tightBounds ? metrics.bottom : metrics.ascent;
683 - top = math.min(top ?? mt + baseline, mt + baseline);  
684 - bottom = math.max(bottom ?? mb + baseline, mb + baseline); 696 + top = math.min(top, mt + baseline);
  697 + bottom = math.max(bottom, mb + baseline);
685 698
686 final wd = _Word( 699 final wd = _Word(
687 word, 700 word,
@@ -707,12 +720,12 @@ class RichText extends Widget { @@ -707,12 +720,12 @@ class RichText extends Widget {
707 style.letterSpacing!; 720 style.letterSpacing!;
708 } 721 }
709 722
710 - if (softWrap && line < spanLines.length - 1) { 723 + if (_softWrap && line < spanLines.length - 1) {
711 lines.add(_Line( 724 lines.add(_Line(
712 this, 725 this,
713 spanStart, 726 spanStart,
714 spanCount, 727 spanCount,
715 - bottom ?? 0, 728 + bottom,
716 offsetX - 729 offsetX -
717 space.advanceWidth * style.wordSpacing! - 730 space.advanceWidth * style.wordSpacing! -
718 style.letterSpacing!, 731 style.letterSpacing!,
@@ -720,19 +733,19 @@ class RichText extends Widget { @@ -720,19 +733,19 @@ class RichText extends Widget {
720 733
721 spanStart += spanCount; 734 spanStart += spanCount;
722 735
723 - if (maxLines != null && lines.length >= maxLines!) { 736 + if (_maxLines != null && lines.length >= _maxLines) {
724 spanCount = 0; 737 spanCount = 0;
725 return false; 738 return false;
726 } 739 }
727 740
728 offsetX = 0.0; 741 offsetX = 0.0;
729 if (spanCount > 0) { 742 if (spanCount > 0) {
730 - offsetY += bottom! - top! + style.lineSpacing!; 743 + offsetY += bottom - top + style.lineSpacing!;
731 } else { 744 } else {
732 offsetY += space.ascent + space.descent + style.lineSpacing!; 745 offsetY += space.ascent + space.descent + style.lineSpacing!;
733 } 746 }
734 - top = null;  
735 - bottom = null; 747 + top = 0;
  748 + bottom = 0;
736 spanCount = 0; 749 spanCount = 0;
737 750
738 if (offsetY > constraintHeight) { 751 if (offsetY > constraintHeight) {
@@ -746,13 +759,13 @@ class RichText extends Widget { @@ -746,13 +759,13 @@ class RichText extends Widget {
746 } else if (span is WidgetSpan) { 759 } else if (span is WidgetSpan) {
747 span.child.layout( 760 span.child.layout(
748 context, 761 context,
749 - BoxConstraints.tight(PdfPoint(  
750 - double.infinity,  
751 - style!.fontSize! * textScaleFactor,  
752 - ))); 762 + BoxConstraints(
  763 + maxWidth: constraintWidth,
  764 + maxHeight: constraintHeight,
  765 + ));
753 final ws = _WidgetSpan( 766 final ws = _WidgetSpan(
754 span.child, 767 span.child,
755 - style, 768 + style!,
756 ); 769 );
757 770
758 if (offsetX + ws.width > constraintWidth && spanCount > 0) { 771 if (offsetX + ws.width > constraintWidth && spanCount > 0) {
@@ -761,7 +774,7 @@ class RichText extends Widget { @@ -761,7 +774,7 @@ class RichText extends Widget {
761 this, 774 this,
762 spanStart, 775 spanStart,
763 spanCount, 776 spanCount,
764 - bottom ?? 0, 777 + bottom,
765 offsetX, 778 offsetX,
766 _textDirection, 779 _textDirection,
767 )); 780 ));
@@ -769,14 +782,14 @@ class RichText extends Widget { @@ -769,14 +782,14 @@ class RichText extends Widget {
769 spanStart += spanCount; 782 spanStart += spanCount;
770 spanCount = 0; 783 spanCount = 0;
771 784
772 - if (maxLines != null && lines.length > maxLines!) { 785 + if (_maxLines != null && lines.length > _maxLines) {
773 return false; 786 return false;
774 } 787 }
775 788
776 offsetX = 0.0; 789 offsetX = 0.0;
777 - offsetY += bottom! - top! + style.lineSpacing!;  
778 - top = null;  
779 - bottom = null; 790 + offsetY += bottom - top + style.lineSpacing!;
  791 + top = 0;
  792 + bottom = 0;
780 793
781 if (offsetY > constraintHeight) { 794 if (offsetY > constraintHeight) {
782 return false; 795 return false;
@@ -784,9 +797,9 @@ class RichText extends Widget { @@ -784,9 +797,9 @@ class RichText extends Widget {
784 } 797 }
785 798
786 final baseline = span.baseline! * textScaleFactor; 799 final baseline = span.baseline! * textScaleFactor;
787 - top = math.min(top ?? baseline, baseline); 800 + top = math.min(top, baseline);
788 bottom = math.max( 801 bottom = math.max(
789 - bottom ?? ws.height + baseline, 802 + bottom,
790 ws.height + baseline, 803 ws.height + baseline,
791 ); 804 );
792 805
@@ -815,7 +828,7 @@ class RichText extends Widget { @@ -815,7 +828,7 @@ class RichText extends Widget {
815 this, 828 this,
816 spanStart, 829 spanStart,
817 spanCount, 830 spanCount,
818 - bottom ?? 0, 831 + bottom,
819 offsetX, 832 offsetX,
820 _textDirection, 833 _textDirection,
821 )); 834 ));
@@ -840,7 +853,7 @@ class RichText extends Widget { @@ -840,7 +853,7 @@ class RichText extends Widget {
840 } 853 }
841 854
842 box = PdfRect(0, 0, constraints.constrainWidth(width), 855 box = PdfRect(0, 0, constraints.constrainWidth(width),
843 - constraints.constrainHeight(offsetY + (bottom ?? 0) - (top ?? 0))); 856 + constraints.constrainHeight(offsetY + bottom - top));
844 } 857 }
845 858
846 @override 859 @override
@@ -889,7 +902,7 @@ class RichText extends Widget { @@ -889,7 +902,7 @@ class RichText extends Widget {
889 902
890 if (span.style != currentStyle) { 903 if (span.style != currentStyle) {
891 currentStyle = span.style; 904 currentStyle = span.style;
892 - if (currentStyle!.color != currentColor) { 905 + if (currentStyle.color != currentColor) {
893 currentColor = currentStyle.color; 906 currentColor = currentStyle.color;
894 context.canvas.setFillColor(currentColor); 907 context.canvas.setFillColor(currentColor);
895 } 908 }
@@ -912,6 +925,29 @@ class RichText extends Widget { @@ -912,6 +925,29 @@ class RichText extends Widget {
912 ); 925 );
913 } 926 }
914 } 927 }
  928 +
  929 + int _splitWord(String word, PdfFont font, TextStyle style, double maxWidth) {
  930 + var low = 0;
  931 + var high = word.length;
  932 + var pos = (low + high) ~/ 2;
  933 +
  934 + while (low + 1 < high) {
  935 + final metrics = font.stringMetrics(word.substring(0, pos),
  936 + letterSpacing:
  937 + style.letterSpacing! / (style.fontSize! * textScaleFactor)) *
  938 + (style.fontSize! * textScaleFactor);
  939 +
  940 + if (metrics.width > maxWidth) {
  941 + high = pos;
  942 + } else {
  943 + low = pos;
  944 + }
  945 +
  946 + pos = (low + high) ~/ 2;
  947 + }
  948 +
  949 + return pos;
  950 + }
915 } 951 }
916 952
917 class Text extends RichText { 953 class Text extends RichText {