David PHAM-VAN

Implement TextStyle.letterSpacing

@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 4
5 - Allow MultiPage to relayout individual pages with support for flex 5 - Allow MultiPage to relayout individual pages with support for flex
6 - Implement BoxShadow for rect and circle BoxDecorations 6 - Implement BoxShadow for rect and circle BoxDecorations
  7 +- Implement TextStyle.letterSpacing
7 8
8 ## 1.8.1 9 ## 1.8.1
9 10
@@ -140,7 +140,7 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management @@ -140,7 +140,7 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
140 @Deprecated('Use `glyphMetrics` instead') 140 @Deprecated('Use `glyphMetrics` instead')
141 PdfRect glyphBounds(int charCode) => glyphMetrics(charCode).toPdfRect(); 141 PdfRect glyphBounds(int charCode) => glyphMetrics(charCode).toPdfRect();
142 142
143 - PdfFontMetrics stringMetrics(String s) { 143 + PdfFontMetrics stringMetrics(String s, {double letterSpacing = 0}) {
144 if (s.isEmpty) { 144 if (s.isEmpty) {
145 return PdfFontMetrics.zero; 145 return PdfFontMetrics.zero;
146 } 146 }
@@ -148,7 +148,7 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management @@ -148,7 +148,7 @@ See https://github.com/DavBfr/dart_pdf/wiki/Fonts-Management
148 try { 148 try {
149 final Uint8List chars = latin1.encode(s); 149 final Uint8List chars = latin1.encode(s);
150 final Iterable<PdfFontMetrics> metrics = chars.map(glyphMetrics); 150 final Iterable<PdfFontMetrics> metrics = chars.map(glyphMetrics);
151 - return PdfFontMetrics.append(metrics); 151 + return PdfFontMetrics.append(metrics, letterSpacing: letterSpacing);
152 } catch (_) { 152 } catch (_) {
153 assert(() { 153 assert(() {
154 print(_cannotDecodeMessage); 154 print(_cannotDecodeMessage);
@@ -39,7 +39,10 @@ class PdfFontMetrics { @@ -39,7 +39,10 @@ class PdfFontMetrics {
39 assert(top <= bottom), 39 assert(top <= bottom),
40 assert((descent ?? top) <= (ascent ?? bottom)); 40 assert((descent ?? top) <= (ascent ?? bottom));
41 41
42 - factory PdfFontMetrics.append(Iterable<PdfFontMetrics> metrics) { 42 + factory PdfFontMetrics.append(
  43 + Iterable<PdfFontMetrics> metrics, {
  44 + double letterSpacing = 0,
  45 + }) {
43 if (metrics.isEmpty) { 46 if (metrics.isEmpty) {
44 return PdfFontMetrics.zero; 47 return PdfFontMetrics.zero;
45 } 48 }
@@ -51,10 +54,12 @@ class PdfFontMetrics { @@ -51,10 +54,12 @@ class PdfFontMetrics {
51 double ascent; 54 double ascent;
52 double descent; 55 double descent;
53 double lastBearing; 56 double lastBearing;
  57 + double spacing;
54 58
55 for (PdfFontMetrics metric in metrics) { 59 for (PdfFontMetrics metric in metrics) {
56 left ??= metric.left; 60 left ??= metric.left;
57 - right += metric.advanceWidth; 61 + spacing = metric.advanceWidth > 0 ? letterSpacing : 0;
  62 + right += metric.advanceWidth + spacing;
58 lastBearing = metric.rightBearing; 63 lastBearing = metric.rightBearing;
59 64
60 top = math.min(top ?? metric.top, metric.top); 65 top = math.min(top ?? metric.top, metric.top);
@@ -66,11 +71,11 @@ class PdfFontMetrics { @@ -66,11 +71,11 @@ class PdfFontMetrics {
66 return PdfFontMetrics( 71 return PdfFontMetrics(
67 left: left, 72 left: left,
68 top: top, 73 top: top,
69 - right: right - lastBearing, 74 + right: right - lastBearing - spacing,
70 bottom: bottom, 75 bottom: bottom,
71 ascent: ascent, 76 ascent: ascent,
72 descent: descent, 77 descent: descent,
73 - advanceWidth: right); 78 + advanceWidth: right - spacing);
74 } 79 }
75 80
76 static const PdfFontMetrics zero = 81 static const PdfFontMetrics zero =
@@ -156,9 +156,9 @@ class PdfTtfFont extends PdfFont { @@ -156,9 +156,9 @@ class PdfTtfFont extends PdfFont {
156 } 156 }
157 157
158 @override 158 @override
159 - PdfFontMetrics stringMetrics(String s) { 159 + PdfFontMetrics stringMetrics(String s, {double letterSpacing = 0}) {
160 if (s.isEmpty || !font.unicode) { 160 if (s.isEmpty || !font.unicode) {
161 - return super.stringMetrics(s); 161 + return super.stringMetrics(s, letterSpacing: letterSpacing);
162 } 162 }
163 163
164 final Runes runes = s.runes; 164 final Runes runes = s.runes;
@@ -166,6 +166,6 @@ class PdfTtfFont extends PdfFont { @@ -166,6 +166,6 @@ class PdfTtfFont extends PdfFont {
166 runes.forEach(bytes.add); 166 runes.forEach(bytes.add);
167 167
168 final Iterable<PdfFontMetrics> metrics = bytes.map(glyphMetrics); 168 final Iterable<PdfFontMetrics> metrics = bytes.map(glyphMetrics);
169 - return PdfFontMetrics.append(metrics); 169 + return PdfFontMetrics.append(metrics, letterSpacing: letterSpacing);
170 } 170 }
171 } 171 }
@@ -262,6 +262,7 @@ class _Word extends _Span { @@ -262,6 +262,7 @@ class _Word extends _Span {
262 point.x + offset.x, 262 point.x + offset.x,
263 point.y + offset.y, 263 point.y + offset.y,
264 mode: style.renderingMode, 264 mode: style.renderingMode,
  265 + charSpace: style.letterSpacing,
265 ); 266 );
266 } 267 }
267 268
@@ -569,12 +570,15 @@ class RichText extends Widget { @@ -569,12 +570,15 @@ class RichText extends Widget {
569 for (int line = 0; line < spanLines.length; line++) { 570 for (int line = 0; line < spanLines.length; line++) {
570 for (String word in spanLines[line].split(RegExp(r'\s'))) { 571 for (String word in spanLines[line].split(RegExp(r'\s'))) {
571 if (word.isEmpty) { 572 if (word.isEmpty) {
572 - offsetX += space.advanceWidth * style.wordSpacing; 573 + offsetX +=
  574 + space.advanceWidth * style.wordSpacing + style.letterSpacing;
573 continue; 575 continue;
574 } 576 }
575 577
576 - final PdfFontMetrics metrics =  
577 - font.stringMetrics(word) * (style.fontSize * textScaleFactor); 578 + final PdfFontMetrics metrics = font.stringMetrics(word,
  579 + letterSpacing: style.letterSpacing /
  580 + (style.fontSize * textScaleFactor)) *
  581 + (style.fontSize * textScaleFactor);
578 582
579 if (offsetX + metrics.width > constraintWidth && spanCount > 0) { 583 if (offsetX + metrics.width > constraintWidth && spanCount > 0) {
580 width = math.max( 584 width = math.max(
@@ -583,7 +587,9 @@ class RichText extends Widget { @@ -583,7 +587,9 @@ class RichText extends Widget {
583 _spans.sublist(spanStart), 587 _spans.sublist(spanStart),
584 _decorations.sublist(decorationStart), 588 _decorations.sublist(decorationStart),
585 constraintWidth, 589 constraintWidth,
586 - offsetX - space.advanceWidth * style.wordSpacing, 590 + offsetX -
  591 + space.advanceWidth * style.wordSpacing -
  592 + style.letterSpacing,
587 false, 593 false,
588 bottom, 594 bottom,
589 )); 595 ));
@@ -632,8 +638,9 @@ class RichText extends Widget { @@ -632,8 +638,9 @@ class RichText extends Widget {
632 ), 638 ),
633 ); 639 );
634 640
635 - offsetX +=  
636 - metrics.advanceWidth + space.advanceWidth * style.wordSpacing; 641 + offsetX += metrics.advanceWidth +
  642 + space.advanceWidth * style.wordSpacing +
  643 + style.letterSpacing;
637 } 644 }
638 645
639 if (softWrap && line < spanLines.length - 1) { 646 if (softWrap && line < spanLines.length - 1) {
@@ -643,7 +650,9 @@ class RichText extends Widget { @@ -643,7 +650,9 @@ class RichText extends Widget {
643 _spans.sublist(spanStart), 650 _spans.sublist(spanStart),
644 _decorations.sublist(decorationStart), 651 _decorations.sublist(decorationStart),
645 constraintWidth, 652 constraintWidth,
646 - offsetX - space.advanceWidth * style.wordSpacing, 653 + offsetX -
  654 + space.advanceWidth * style.wordSpacing -
  655 + style.letterSpacing,
647 true, 656 true,
648 bottom, 657 bottom,
649 )); 658 ));
@@ -672,7 +681,7 @@ class RichText extends Widget { @@ -672,7 +681,7 @@ class RichText extends Widget {
672 } 681 }
673 } 682 }
674 683
675 - offsetX -= space.advanceWidth * style.wordSpacing; 684 + offsetX -= space.advanceWidth * style.wordSpacing - style.letterSpacing;
676 } else if (span is WidgetSpan) { 685 } else if (span is WidgetSpan) {
677 span.child.layout( 686 span.child.layout(
678 context, 687 context,
@@ -157,10 +157,10 @@ class TextStyle { @@ -157,10 +157,10 @@ class TextStyle {
157 fontSize: _defaultFontSize, 157 fontSize: _defaultFontSize,
158 fontWeight: FontWeight.normal, 158 fontWeight: FontWeight.normal,
159 fontStyle: FontStyle.normal, 159 fontStyle: FontStyle.normal,
160 - letterSpacing: 1.0,  
161 - wordSpacing: 1.0,  
162 - lineSpacing: 0.0,  
163 - height: 1.0, 160 + letterSpacing: 0,
  161 + wordSpacing: 1,
  162 + lineSpacing: 0,
  163 + height: 1,
164 decoration: TextDecoration.none, 164 decoration: TextDecoration.none,
165 decorationColor: null, 165 decorationColor: null,
166 decorationStyle: TextDecorationStyle.solid, 166 decorationStyle: TextDecorationStyle.solid,
@@ -131,9 +131,12 @@ void main() { @@ -131,9 +131,12 @@ void main() {
131 final String para = LoremText().paragraph(40); 131 final String para = LoremText().paragraph(40);
132 132
133 final List<Widget> widgets = <Widget>[]; 133 final List<Widget> widgets = <Widget>[];
134 - for (double spacing = 0.0; spacing < 10.0; spacing += 2.0) { 134 + for (double spacing = -1.0; spacing < 8.0; spacing += 2.0) {
135 widgets.add( 135 widgets.add(
136 - Text(para, style: TextStyle(font: ttf, letterSpacing: spacing)), 136 + Text(
  137 + '[$spacing] $para',
  138 + style: TextStyle(font: ttf, letterSpacing: spacing),
  139 + ),
137 ); 140 );
138 widgets.add( 141 widgets.add(
139 SizedBox(height: 30), 142 SizedBox(height: 30),