David PHAM-VAN

Add TextDecoration

1 # Changelog 1 # Changelog
2 2
  3 +## 1.3.21
  4 +
  5 +- Add TextDecoration
  6 +
3 ## 1.3.20 7 ## 1.3.20
4 8
5 - Fix Transform.rotateBox 9 - Fix Transform.rotateBox
@@ -19,12 +19,10 @@ part of widget; @@ -19,12 +19,10 @@ part of widget;
19 enum TextAlign { left, right, center, justify } 19 enum TextAlign { left, right, center, justify }
20 20
21 abstract class _Span { 21 abstract class _Span {
22 - _Span(this.style, this.annotation); 22 + _Span(this.style);
23 23
24 final TextStyle style; 24 final TextStyle style;
25 25
26 - final AnnotationBuilder annotation;  
27 -  
28 PdfPoint offset = PdfPoint.zero; 26 PdfPoint offset = PdfPoint.zero;
29 27
30 double left; 28 double left;
@@ -51,9 +49,143 @@ abstract class _Span { @@ -51,9 +49,143 @@ abstract class _Span {
51 ); 49 );
52 } 50 }
53 51
  52 +class _TextDecoration {
  53 + _TextDecoration(this.style, this.annotation) : assert(style != null);
  54 +
  55 + static const double _space = -0.15;
  56 +
  57 + final TextStyle style;
  58 +
  59 + final AnnotationBuilder annotation;
  60 +
  61 + PdfRect box = PdfRect.zero;
  62 +
  63 + void backgroundPaint(
  64 + Context context,
  65 + double textScaleFactor,
  66 + PdfRect globalBox,
  67 + ) {
  68 + if (annotation != null) {
  69 + final PdfRect spanBox = PdfRect(
  70 + globalBox.x + box.left,
  71 + globalBox.top + box.bottom,
  72 + box.width,
  73 + box.height,
  74 + );
  75 + annotation.build(context, spanBox);
  76 + }
  77 +
  78 + if (style.background != null) {
  79 + final PdfRect boundingBox = PdfRect(
  80 + globalBox.x + box.left,
  81 + globalBox.top + box.bottom,
  82 + box.width,
  83 + box.height,
  84 + );
  85 + style.background.paint(context, boundingBox);
  86 + context.canvas.setFillColor(style.color);
  87 + }
  88 + }
  89 +
  90 + void foregroundPaint(
  91 + Context context,
  92 + double textScaleFactor,
  93 + PdfRect globalBox,
  94 + ) {
  95 + if (style.decoration != null) {
  96 + final PdfFont font = style.font.getFont(context);
  97 + final double space =
  98 + _space * style.fontSize * textScaleFactor * style.decorationThickness;
  99 +
  100 + context.canvas
  101 + ..setStrokeColor(style.decorationColor ?? style.color)
  102 + ..setLineWidth(style.decorationThickness *
  103 + style.fontSize *
  104 + textScaleFactor *
  105 + 0.05);
  106 +
  107 + if (style.decoration.contains(TextDecoration.underline)) {
  108 + final double base =
  109 + -font.descent * style.fontSize * textScaleFactor / 2;
  110 +
  111 + context.canvas.drawLine(
  112 + globalBox.x + box.left,
  113 + globalBox.top + box.bottom + base,
  114 + globalBox.x + box.right,
  115 + globalBox.top + box.bottom + base,
  116 + );
  117 + if (style.decorationStyle == TextDecorationStyle.double) {
  118 + context.canvas.drawLine(
  119 + globalBox.x + box.left,
  120 + globalBox.top + box.bottom + base + space,
  121 + globalBox.x + box.right,
  122 + globalBox.top + box.bottom + base + space,
  123 + );
  124 + }
  125 + context.canvas.strokePath();
  126 + }
  127 +
  128 + if (style.decoration.contains(TextDecoration.overline)) {
  129 + final double base = style.fontSize * textScaleFactor;
  130 + context.canvas.drawLine(
  131 + globalBox.x + box.left,
  132 + globalBox.top + box.bottom + base,
  133 + globalBox.x + box.right,
  134 + globalBox.top + box.bottom + base,
  135 + );
  136 + if (style.decorationStyle == TextDecorationStyle.double) {
  137 + context.canvas.drawLine(
  138 + globalBox.x + box.left,
  139 + globalBox.top + box.bottom + base - space,
  140 + globalBox.x + box.right,
  141 + globalBox.top + box.bottom + base - space,
  142 + );
  143 + }
  144 + context.canvas.strokePath();
  145 + }
  146 +
  147 + if (style.decoration.contains(TextDecoration.lineThrough)) {
  148 + final double base =
  149 + (1 - font.descent) * style.fontSize * textScaleFactor / 2;
  150 + context.canvas.drawLine(
  151 + globalBox.x + box.left,
  152 + globalBox.top + box.bottom + base,
  153 + globalBox.x + box.right,
  154 + globalBox.top + box.bottom + base,
  155 + );
  156 + if (style.decorationStyle == TextDecorationStyle.double) {
  157 + context.canvas.drawLine(
  158 + globalBox.x + box.left,
  159 + globalBox.top + box.bottom + base + space,
  160 + globalBox.x + box.right,
  161 + globalBox.top + box.bottom + base + space,
  162 + );
  163 + }
  164 + context.canvas.strokePath();
  165 + }
  166 + }
  167 + }
  168 +
  169 + void debugPaint(
  170 + Context context,
  171 + double textScaleFactor,
  172 + PdfRect globalBox,
  173 + ) {
  174 + context.canvas
  175 + ..setLineWidth(.5)
  176 + ..drawRect(
  177 + globalBox.x + box.x, globalBox.top + box.y, box.width, box.height)
  178 + ..setStrokeColor(PdfColors.yellow)
  179 + ..strokePath();
  180 + }
  181 +}
  182 +
54 class _Word extends _Span { 183 class _Word extends _Span {
55 - _Word(this.text, TextStyle style, this.metrics, AnnotationBuilder annotation)  
56 - : super(style, annotation); 184 + _Word(
  185 + this.text,
  186 + TextStyle style,
  187 + this.metrics,
  188 + ) : super(style);
57 189
58 final String text; 190 final String text;
59 191
@@ -100,6 +232,7 @@ class _Word extends _Span { @@ -100,6 +232,7 @@ class _Word extends _Span {
100 const double deb = 5; 232 const double deb = 5;
101 233
102 context.canvas 234 context.canvas
  235 + ..setLineWidth(.5)
103 ..drawRect(globalBox.x + offset.x + metrics.left, 236 ..drawRect(globalBox.x + offset.x + metrics.left,
104 globalBox.top + offset.y + metrics.top, metrics.width, metrics.height) 237 globalBox.top + offset.y + metrics.top, metrics.width, metrics.height)
105 ..setStrokeColor(PdfColors.orange) 238 ..setStrokeColor(PdfColors.orange)
@@ -115,10 +248,10 @@ class _Word extends _Span { @@ -115,10 +248,10 @@ class _Word extends _Span {
115 } 248 }
116 249
117 class _WidgetSpan extends _Span { 250 class _WidgetSpan extends _Span {
118 - _WidgetSpan(this.widget, TextStyle style, AnnotationBuilder annotation) 251 + _WidgetSpan(this.widget, TextStyle style)
119 : assert(widget != null), 252 : assert(widget != null),
120 assert(style != null), 253 assert(style != null),
121 - super(style, annotation); 254 + super(style);
122 255
123 final Widget widget; 256 final Widget widget;
124 257
@@ -159,6 +292,20 @@ class _WidgetSpan extends _Span { @@ -159,6 +292,20 @@ class _WidgetSpan extends _Span {
159 widget.box.size); 292 widget.box.size);
160 widget.paint(context); 293 widget.paint(context);
161 } 294 }
  295 +
  296 + @override
  297 + void debugPaint(
  298 + Context context,
  299 + double textScaleFactor,
  300 + PdfRect globalBox,
  301 + ) {
  302 + context.canvas
  303 + ..setLineWidth(.5)
  304 + ..drawRect(
  305 + globalBox.x + offset.x, globalBox.top + offset.y, width, height)
  306 + ..setStrokeColor(PdfColors.orange)
  307 + ..strokePath();
  308 + }
162 } 309 }
163 310
164 @immutable 311 @immutable
@@ -272,8 +419,11 @@ class RichText extends Widget { @@ -272,8 +419,11 @@ class RichText extends Widget {
272 419
273 final List<_Span> _spans = <_Span>[]; 420 final List<_Span> _spans = <_Span>[];
274 421
  422 + final List<_TextDecoration> _decorations = <_TextDecoration>[];
  423 +
275 double _realignLine( 424 double _realignLine(
276 List<_Span> spans, 425 List<_Span> spans,
  426 + List<_TextDecoration> decorations,
277 double totalWidth, 427 double totalWidth,
278 double wordsWidth, 428 double wordsWidth,
279 bool last, 429 bool last,
@@ -306,6 +456,14 @@ class RichText extends Widget { @@ -306,6 +456,14 @@ class RichText extends Widget {
306 for (_Span span in spans) { 456 for (_Span span in spans) {
307 span.offset = span.offset.translate(delta, -baseline); 457 span.offset = span.offset.translate(delta, -baseline);
308 } 458 }
  459 +
  460 + for (_TextDecoration decoration in decorations) {
  461 + decoration.box = PdfRect.fromPoints(
  462 + decoration.box.offset.translate(delta, -baseline),
  463 + decoration.box.size,
  464 + );
  465 + }
  466 +
309 return totalWidth; 467 return totalWidth;
310 } 468 }
311 469
@@ -313,6 +471,7 @@ class RichText extends Widget { @@ -313,6 +471,7 @@ class RichText extends Widget {
313 void layout(Context context, BoxConstraints constraints, 471 void layout(Context context, BoxConstraints constraints,
314 {bool parentUsesSize = false}) { 472 {bool parentUsesSize = false}) {
315 _spans.clear(); 473 _spans.clear();
  474 + _decorations.clear();
316 475
317 final TextStyle defaultstyle = Theme.of(context).defaultTextStyle; 476 final TextStyle defaultstyle = Theme.of(context).defaultTextStyle;
318 477
@@ -330,8 +489,9 @@ class RichText extends Widget { @@ -330,8 +489,9 @@ class RichText extends Widget {
330 double bottom; 489 double bottom;
331 490
332 int lines = 1; 491 int lines = 1;
333 - int wCount = 0;  
334 - int lineStart = 0; 492 + int spanCount = 0;
  493 + int spanStart = 0;
  494 + int decorationStart = 0;
335 495
336 text.visitChildren((InlineSpan span, TextStyle style) { 496 text.visitChildren((InlineSpan span, TextStyle style) {
337 if (span is TextSpan) { 497 if (span is TextSpan) {
@@ -355,18 +515,20 @@ class RichText extends Widget { @@ -355,18 +515,20 @@ class RichText extends Widget {
355 final PdfFontMetrics metrics = 515 final PdfFontMetrics metrics =
356 font.stringMetrics(word) * (style.fontSize * textScaleFactor); 516 font.stringMetrics(word) * (style.fontSize * textScaleFactor);
357 517
358 - if (offsetX + metrics.width > constraintWidth && wCount > 0) { 518 + if (offsetX + metrics.width > constraintWidth && spanCount > 0) {
359 width = math.max( 519 width = math.max(
360 width, 520 width,
361 _realignLine( 521 _realignLine(
362 - _spans.sublist(lineStart), 522 + _spans.sublist(spanStart),
  523 + _decorations.sublist(decorationStart),
363 constraintWidth, 524 constraintWidth,
364 offsetX - space.advanceWidth * style.wordSpacing, 525 offsetX - space.advanceWidth * style.wordSpacing,
365 false, 526 false,
366 bottom, 527 bottom,
367 )); 528 ));
368 529
369 - lineStart += wCount; 530 + spanStart += spanCount;
  531 + decorationStart = _decorations.length;
370 532
371 if (maxLines != null && ++lines > maxLines) { 533 if (maxLines != null && ++lines > maxLines) {
372 break; 534 break;
@@ -380,7 +542,7 @@ class RichText extends Widget { @@ -380,7 +542,7 @@ class RichText extends Widget {
380 if (offsetY > constraintHeight) { 542 if (offsetY > constraintHeight) {
381 return false; 543 return false;
382 } 544 }
383 - wCount = 0; 545 + spanCount = 0;
384 } 546 }
385 547
386 final double baseline = span.baseline * textScaleFactor; 548 final double baseline = span.baseline * textScaleFactor;
@@ -397,12 +559,22 @@ class RichText extends Widget { @@ -397,12 +559,22 @@ class RichText extends Widget {
397 word, 559 word,
398 style, 560 style,
399 metrics, 561 metrics,
400 - span.annotation,  
401 ); 562 );
402 wd.offset = PdfPoint(offsetX, -offsetY + baseline); 563 wd.offset = PdfPoint(offsetX, -offsetY + baseline);
403 -  
404 _spans.add(wd); 564 _spans.add(wd);
405 - wCount++; 565 + spanCount++;
  566 +
  567 + final _TextDecoration td = _TextDecoration(
  568 + style,
  569 + span.annotation,
  570 + );
  571 + td.box = PdfRect(
  572 + offsetX,
  573 + -offsetY + metrics.descent + baseline,
  574 + metrics.maxWidth + space.advanceWidth * style.wordSpacing,
  575 + metrics.maxHeight);
  576 + _decorations.add(td);
  577 +
406 offsetX += 578 offsetX +=
407 metrics.advanceWidth + space.advanceWidth * style.wordSpacing; 579 metrics.advanceWidth + space.advanceWidth * style.wordSpacing;
408 } 580 }
@@ -411,21 +583,32 @@ class RichText extends Widget { @@ -411,21 +583,32 @@ class RichText extends Widget {
411 width = math.max( 583 width = math.max(
412 width, 584 width,
413 _realignLine( 585 _realignLine(
414 - _spans.sublist(lineStart), 586 + _spans.sublist(spanStart),
  587 + _decorations.sublist(decorationStart),
415 constraintWidth, 588 constraintWidth,
416 offsetX - space.advanceWidth * style.wordSpacing, 589 offsetX - space.advanceWidth * style.wordSpacing,
417 false, 590 false,
418 bottom, 591 bottom,
419 )); 592 ));
420 593
421 - lineStart += wCount; 594 + spanStart += spanCount;
  595 + decorationStart = _decorations.length;
  596 +
  597 + if (_decorations.isNotEmpty) {
  598 + // remove the last space
  599 + _decorations.last.box = PdfRect.fromPoints(
  600 + _decorations.last.box.offset,
  601 + _decorations.last.box.size
  602 + .translate(-space.advanceWidth * style.wordSpacing, 0),
  603 + );
  604 + }
422 605
423 if (maxLines != null && ++lines > maxLines) { 606 if (maxLines != null && ++lines > maxLines) {
424 break; 607 break;
425 } 608 }
426 609
427 offsetX = 0.0; 610 offsetX = 0.0;
428 - if (wCount > 0) { 611 + if (spanCount > 0) {
429 offsetY += bottom - top + style.lineSpacing; 612 offsetY += bottom - top + style.lineSpacing;
430 } else { 613 } else {
431 offsetY += space.ascent + space.descent + style.lineSpacing; 614 offsetY += space.ascent + space.descent + style.lineSpacing;
@@ -436,7 +619,7 @@ class RichText extends Widget { @@ -436,7 +619,7 @@ class RichText extends Widget {
436 if (offsetY > constraintHeight) { 619 if (offsetY > constraintHeight) {
437 return false; 620 return false;
438 } 621 }
439 - wCount = 0; 622 + spanCount = 0;
440 } 623 }
441 } 624 }
442 625
@@ -451,21 +634,22 @@ class RichText extends Widget { @@ -451,21 +634,22 @@ class RichText extends Widget {
451 final _WidgetSpan ws = _WidgetSpan( 634 final _WidgetSpan ws = _WidgetSpan(
452 span.child, 635 span.child,
453 style, 636 style,
454 - span.annotation,  
455 ); 637 );
456 638
457 - if (offsetX + ws.width > constraintWidth && wCount > 0) { 639 + if (offsetX + ws.width > constraintWidth && spanCount > 0) {
458 width = math.max( 640 width = math.max(
459 width, 641 width,
460 _realignLine( 642 _realignLine(
461 - _spans.sublist(lineStart), 643 + _spans.sublist(spanStart),
  644 + _decorations.sublist(decorationStart),
462 constraintWidth, 645 constraintWidth,
463 offsetX, 646 offsetX,
464 false, 647 false,
465 bottom, 648 bottom,
466 )); 649 ));
467 650
468 - lineStart += wCount; 651 + spanStart += spanCount;
  652 + decorationStart = _decorations.length;
469 653
470 if (maxLines != null && ++lines > maxLines) { 654 if (maxLines != null && ++lines > maxLines) {
471 return false; 655 return false;
@@ -479,7 +663,7 @@ class RichText extends Widget { @@ -479,7 +663,7 @@ class RichText extends Widget {
479 if (offsetY > constraintHeight) { 663 if (offsetY > constraintHeight) {
480 return false; 664 return false;
481 } 665 }
482 - wCount = 0; 666 + spanCount = 0;
483 } 667 }
484 668
485 final double baseline = span.baseline * textScaleFactor; 669 final double baseline = span.baseline * textScaleFactor;
@@ -491,7 +675,15 @@ class RichText extends Widget { @@ -491,7 +675,15 @@ class RichText extends Widget {
491 675
492 ws.offset = PdfPoint(offsetX, -offsetY + baseline); 676 ws.offset = PdfPoint(offsetX, -offsetY + baseline);
493 _spans.add(ws); 677 _spans.add(ws);
494 - wCount++; 678 + spanCount++;
  679 +
  680 + final _TextDecoration td = _TextDecoration(
  681 + style,
  682 + span.annotation,
  683 + );
  684 + td.box = PdfRect(offsetX, -offsetY + baseline, ws.width, ws.height);
  685 + _decorations.add(td);
  686 +
495 offsetX += ws.left + ws.width; 687 offsetX += ws.left + ws.width;
496 } 688 }
497 689
@@ -501,7 +693,8 @@ class RichText extends Widget { @@ -501,7 +693,8 @@ class RichText extends Widget {
501 width = math.max( 693 width = math.max(
502 width, 694 width,
503 _realignLine( 695 _realignLine(
504 - _spans.sublist(lineStart), 696 + _spans.sublist(spanStart),
  697 + _decorations.sublist(decorationStart),
505 lines > 1 ? constraintWidth : offsetX, 698 lines > 1 ? constraintWidth : offsetX,
506 offsetX, 699 offsetX,
507 true, 700 true,
@@ -519,6 +712,7 @@ class RichText extends Widget { @@ -519,6 +712,7 @@ class RichText extends Widget {
519 void debugPaint(Context context) { 712 void debugPaint(Context context) {
520 context.canvas 713 context.canvas
521 ..setStrokeColor(PdfColors.blue) 714 ..setStrokeColor(PdfColors.blue)
  715 + ..setLineWidth(1)
522 ..drawRect(box.x, box.y, box.width, box.height) 716 ..drawRect(box.x, box.y, box.width, box.height)
523 ..strokePath(); 717 ..strokePath();
524 } 718 }
@@ -529,6 +723,21 @@ class RichText extends Widget { @@ -529,6 +723,21 @@ class RichText extends Widget {
529 TextStyle currentStyle; 723 TextStyle currentStyle;
530 PdfColor currentColor; 724 PdfColor currentColor;
531 725
  726 + for (_TextDecoration decoration in _decorations) {
  727 + assert(() {
  728 + if (Document.debug && RichText.debug) {
  729 + decoration.debugPaint(context, textScaleFactor, box);
  730 + }
  731 + return true;
  732 + }());
  733 +
  734 + decoration.backgroundPaint(
  735 + context,
  736 + textScaleFactor,
  737 + box,
  738 + );
  739 + }
  740 +
532 for (_Span span in _spans) { 741 for (_Span span in _spans) {
533 assert(() { 742 assert(() {
534 if (Document.debug && RichText.debug) { 743 if (Document.debug && RichText.debug) {
@@ -545,12 +754,6 @@ class RichText extends Widget { @@ -545,12 +754,6 @@ class RichText extends Widget {
545 } 754 }
546 } 755 }
547 756
548 - if (span.annotation != null) {  
549 - final PdfRect spanBox = PdfRect(box.x + span.offset.x + span.left,  
550 - box.top + span.offset.y + span.top, span.width, span.height);  
551 - span.annotation.build(context, spanBox);  
552 - }  
553 -  
554 span.paint( 757 span.paint(
555 context, 758 context,
556 currentStyle, 759 currentStyle,
@@ -558,6 +761,14 @@ class RichText extends Widget { @@ -558,6 +761,14 @@ class RichText extends Widget {
558 PdfPoint(box.left, box.top), 761 PdfPoint(box.left, box.top),
559 ); 762 );
560 } 763 }
  764 +
  765 + for (_TextDecoration decoration in _decorations) {
  766 + decoration.foregroundPaint(
  767 + context,
  768 + textScaleFactor,
  769 + box,
  770 + );
  771 + }
561 } 772 }
562 } 773 }
563 774
@@ -20,6 +20,74 @@ enum FontWeight { normal, bold } @@ -20,6 +20,74 @@ enum FontWeight { normal, bold }
20 20
21 enum FontStyle { normal, italic } 21 enum FontStyle { normal, italic }
22 22
  23 +enum TextDecorationStyle { solid, double }
  24 +
  25 +/// A linear decoration to draw near the text.
  26 +class TextDecoration {
  27 + const TextDecoration._(this._mask);
  28 +
  29 + /// Creates a decoration that paints the union of all the given decorations.
  30 + factory TextDecoration.combine(List<TextDecoration> decorations) {
  31 + int mask = 0;
  32 + for (TextDecoration decoration in decorations) {
  33 + mask |= decoration._mask;
  34 + }
  35 + return TextDecoration._(mask);
  36 + }
  37 +
  38 + final int _mask;
  39 +
  40 + /// Whether this decoration will paint at least as much decoration as the given decoration.
  41 + bool contains(TextDecoration other) {
  42 + return (_mask | other._mask) == _mask;
  43 + }
  44 +
  45 + /// Do not draw a decoration
  46 + static const TextDecoration none = TextDecoration._(0x0);
  47 +
  48 + /// Draw a line underneath each line of text
  49 + static const TextDecoration underline = TextDecoration._(0x1);
  50 +
  51 + /// Draw a line above each line of text
  52 + static const TextDecoration overline = TextDecoration._(0x2);
  53 +
  54 + /// Draw a line through each line of text
  55 + static const TextDecoration lineThrough = TextDecoration._(0x4);
  56 +
  57 + @override
  58 + bool operator ==(dynamic other) {
  59 + if (other is! TextDecoration) {
  60 + return false;
  61 + }
  62 + final TextDecoration typedOther = other;
  63 + return _mask == typedOther._mask;
  64 + }
  65 +
  66 + @override
  67 + int get hashCode => _mask.hashCode;
  68 +
  69 + @override
  70 + String toString() {
  71 + if (_mask == 0) {
  72 + return 'TextDecoration.none';
  73 + }
  74 + final List<String> values = <String>[];
  75 + if (_mask & underline._mask != 0) {
  76 + values.add('underline');
  77 + }
  78 + if (_mask & overline._mask != 0) {
  79 + values.add('overline');
  80 + }
  81 + if (_mask & lineThrough._mask != 0) {
  82 + values.add('lineThrough');
  83 + }
  84 + if (values.length == 1) {
  85 + return 'TextDecoration.${values[0]}';
  86 + }
  87 + return 'TextDecoration.combine([${values.join(", ")}])';
  88 + }
  89 +}
  90 +
23 @immutable 91 @immutable
24 class TextStyle { 92 class TextStyle {
25 const TextStyle({ 93 const TextStyle({
@@ -38,6 +106,10 @@ class TextStyle { @@ -38,6 +106,10 @@ class TextStyle {
38 this.lineSpacing, 106 this.lineSpacing,
39 this.height, 107 this.height,
40 this.background, 108 this.background,
  109 + this.decoration,
  110 + this.decorationColor,
  111 + this.decorationStyle,
  112 + this.decorationThickness,
41 }) : assert(inherit || color != null), 113 }) : assert(inherit || color != null),
42 assert(inherit || font != null), 114 assert(inherit || font != null),
43 assert(inherit || fontSize != null), 115 assert(inherit || fontSize != null),
@@ -47,6 +119,10 @@ class TextStyle { @@ -47,6 +119,10 @@ class TextStyle {
47 assert(inherit || wordSpacing != null), 119 assert(inherit || wordSpacing != null),
48 assert(inherit || lineSpacing != null), 120 assert(inherit || lineSpacing != null),
49 assert(inherit || height != null), 121 assert(inherit || height != null),
  122 + assert(inherit || decoration != null),
  123 + assert(inherit || decorationColor != null),
  124 + assert(inherit || decorationStyle != null),
  125 + assert(inherit || decorationThickness != null),
50 fontNormal = fontNormal ?? 126 fontNormal = fontNormal ??
51 (fontStyle != FontStyle.italic && fontWeight != FontWeight.bold 127 (fontStyle != FontStyle.italic && fontWeight != FontWeight.bold
52 ? font 128 ? font
@@ -78,6 +154,10 @@ class TextStyle { @@ -78,6 +154,10 @@ class TextStyle {
78 wordSpacing: 1.0, 154 wordSpacing: 1.0,
79 lineSpacing: 0.0, 155 lineSpacing: 0.0,
80 height: 1.0, 156 height: 1.0,
  157 + decoration: TextDecoration.none,
  158 + decorationColor: null,
  159 + decorationStyle: TextDecorationStyle.solid,
  160 + decorationThickness: 1,
81 ); 161 );
82 } 162 }
83 163
@@ -115,7 +195,15 @@ class TextStyle { @@ -115,7 +195,15 @@ class TextStyle {
115 195
116 final double height; 196 final double height;
117 197
118 - final PdfColor background; 198 + final BoxDecoration background;
  199 +
  200 + final TextDecoration decoration;
  201 +
  202 + final PdfColor decorationColor;
  203 +
  204 + final TextDecorationStyle decorationStyle;
  205 +
  206 + final double decorationThickness;
119 207
120 TextStyle copyWith({ 208 TextStyle copyWith({
121 PdfColor color, 209 PdfColor color,
@@ -131,7 +219,11 @@ class TextStyle { @@ -131,7 +219,11 @@ class TextStyle {
131 double wordSpacing, 219 double wordSpacing,
132 double lineSpacing, 220 double lineSpacing,
133 double height, 221 double height,
134 - PdfColor background, 222 + BoxDecoration background,
  223 + TextDecoration decoration,
  224 + PdfColor decorationColor,
  225 + TextDecorationStyle decorationStyle,
  226 + double decorationThickness,
135 }) { 227 }) {
136 return TextStyle( 228 return TextStyle(
137 inherit: inherit, 229 inherit: inherit,
@@ -149,6 +241,10 @@ class TextStyle { @@ -149,6 +241,10 @@ class TextStyle {
149 lineSpacing: lineSpacing ?? this.lineSpacing, 241 lineSpacing: lineSpacing ?? this.lineSpacing,
150 height: height ?? this.height, 242 height: height ?? this.height,
151 background: background ?? this.background, 243 background: background ?? this.background,
  244 + decoration: decoration ?? this.decoration,
  245 + decorationColor: decorationColor ?? this.decorationColor,
  246 + decorationStyle: decorationStyle ?? this.decorationStyle,
  247 + decorationThickness: decorationThickness ?? this.decorationThickness,
152 ); 248 );
153 } 249 }
154 250
@@ -169,6 +265,7 @@ class TextStyle { @@ -169,6 +265,7 @@ class TextStyle {
169 double wordSpacingDelta = 0.0, 265 double wordSpacingDelta = 0.0,
170 double heightFactor = 1.0, 266 double heightFactor = 1.0,
171 double heightDelta = 0.0, 267 double heightDelta = 0.0,
  268 + TextDecoration decoration = TextDecoration.none,
172 }) { 269 }) {
173 assert(fontSizeFactor != null); 270 assert(fontSizeFactor != null);
174 assert(fontSizeDelta != null); 271 assert(fontSizeDelta != null);
@@ -184,6 +281,7 @@ class TextStyle { @@ -184,6 +281,7 @@ class TextStyle {
184 assert(heightFactor != null); 281 assert(heightFactor != null);
185 assert(heightDelta != null); 282 assert(heightDelta != null);
186 assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0)); 283 assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0));
  284 + assert(decoration != null);
187 285
188 return TextStyle( 286 return TextStyle(
189 inherit: inherit, 287 inherit: inherit,
@@ -205,6 +303,7 @@ class TextStyle { @@ -205,6 +303,7 @@ class TextStyle {
205 : wordSpacing * wordSpacingFactor + wordSpacingDelta, 303 : wordSpacing * wordSpacingFactor + wordSpacingDelta,
206 height: height == null ? null : height * heightFactor + heightDelta, 304 height: height == null ? null : height * heightFactor + heightDelta,
207 background: background, 305 background: background,
  306 + decoration: decoration,
208 ); 307 );
209 } 308 }
210 309
@@ -234,6 +333,10 @@ class TextStyle { @@ -234,6 +333,10 @@ class TextStyle {
234 lineSpacing: other.lineSpacing, 333 lineSpacing: other.lineSpacing,
235 height: other.height, 334 height: other.height,
236 background: other.background, 335 background: other.background,
  336 + decoration: other.decoration,
  337 + decorationColor: other.decorationColor,
  338 + decorationStyle: other.decorationStyle,
  339 + decorationThickness: other.decorationThickness,
237 ); 340 );
238 } 341 }
239 342
@@ -262,5 +365,5 @@ class TextStyle { @@ -262,5 +365,5 @@ class TextStyle {
262 365
263 @override 366 @override
264 String toString() => 367 String toString() =>
265 - 'TextStyle(color:$color font:$font size:$fontSize weight:$fontWeight style:$fontStyle letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)'; 368 + 'TextStyle(color:$color font:$font size:$fontSize weight:$fontWeight style:$fontStyle letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background decoration:$decoration decorationColor:$decorationColor decorationStyle:$decorationStyle decorationThickness:$decorationThickness)';
266 } 369 }
@@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
5 repository: https://github.com/DavBfr/dart_pdf 5 repository: https://github.com/DavBfr/dart_pdf
6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
7 -version: 1.3.20 7 +version: 1.3.21
8 8
9 environment: 9 environment:
10 sdk: ">=2.1.0 <3.0.0" 10 sdk: ">=2.1.0 <3.0.0"
@@ -74,6 +74,7 @@ void main() { @@ -74,6 +74,7 @@ void main() {
74 'Internal link', 74 'Internal link',
75 style: TextStyle( 75 style: TextStyle(
76 color: PdfColors.blue, 76 color: PdfColors.blue,
  77 + decoration: TextDecoration.underline,
77 ), 78 ),
78 ))), 79 ))),
79 Padding(padding: const EdgeInsets.all(5)), 80 Padding(padding: const EdgeInsets.all(5)),
@@ -215,6 +216,7 @@ void main() { @@ -215,6 +216,7 @@ void main() {
215 style: TextStyle( 216 style: TextStyle(
216 color: PdfColors.grey, 217 color: PdfColors.grey,
217 fontSize: 8, 218 fontSize: 8,
  219 + decoration: TextDecoration.underline,
218 ), 220 ),
219 ), 221 ),
220 destination: 'https://github.com/DavBfr/dart_pdf/')), 222 destination: 'https://github.com/DavBfr/dart_pdf/')),
@@ -26,6 +26,18 @@ Document pdf; @@ -26,6 +26,18 @@ Document pdf;
26 Font ttf; 26 Font ttf;
27 Font ttfBold; 27 Font ttfBold;
28 28
  29 +Iterable<TextDecoration> permute(
  30 + List<TextDecoration> prefix, List<TextDecoration> remaining) sync* {
  31 + yield TextDecoration.combine(prefix);
  32 + if (remaining.isNotEmpty) {
  33 + for (TextDecoration decoration in remaining) {
  34 + final List<TextDecoration> next = List<TextDecoration>.from(remaining);
  35 + next.remove(decoration);
  36 + yield* permute(prefix + <TextDecoration>[decoration], next);
  37 + }
  38 + }
  39 +}
  40 +
29 void main() { 41 void main() {
30 setUpAll(() { 42 setUpAll(() {
31 Document.debug = true; 43 Document.debug = true;
@@ -135,12 +147,48 @@ void main() { @@ -135,12 +147,48 @@ void main() {
135 para, 147 para,
136 style: TextStyle( 148 style: TextStyle(
137 font: ttf, 149 font: ttf,
138 - background: PdfColors.purple50, 150 + background: BoxDecoration(color: PdfColors.purple50),
139 ), 151 ),
140 ), 152 ),
141 ])); 153 ]));
142 }); 154 });
143 155
  156 + test('Text Widgets decoration', () {
  157 + final List<Widget> widgets = <Widget>[];
  158 + final List<TextDecoration> decorations = <TextDecoration>[
  159 + TextDecoration.underline,
  160 + TextDecoration.lineThrough,
  161 + TextDecoration.overline
  162 + ];
  163 +
  164 + final Set<TextDecoration> decorationSet = Set<TextDecoration>.from(
  165 + permute(
  166 + <TextDecoration>[],
  167 + decorations,
  168 + ),
  169 + );
  170 +
  171 + for (TextDecorationStyle decorationStyle in TextDecorationStyle.values) {
  172 + for (TextDecoration decoration in decorationSet) {
  173 + widgets.add(
  174 + Text(
  175 + decoration.toString().replaceAll('.', ' '),
  176 + style: TextStyle(
  177 + font: ttf,
  178 + decoration: decoration,
  179 + decorationColor: PdfColors.red,
  180 + decorationStyle: decorationStyle),
  181 + ),
  182 + );
  183 + widgets.add(
  184 + SizedBox(height: 5),
  185 + );
  186 + }
  187 + }
  188 +
  189 + pdf.addPage(MultiPage(build: (Context context) => widgets));
  190 + });
  191 +
144 test('Text Widgets RichText', () { 192 test('Text Widgets RichText', () {
145 final math.Random rnd = math.Random(42); 193 final math.Random rnd = math.Random(42);
146 final String para = LoremText(random: rnd).paragraph(40); 194 final String para = LoremText(random: rnd).paragraph(40);
@@ -168,6 +216,7 @@ void main() { @@ -168,6 +216,7 @@ void main() {
168 style: TextStyle( 216 style: TextStyle(
169 font: ttf, 217 font: ttf,
170 fontSize: 20, 218 fontSize: 20,
  219 + decoration: TextDecoration.underline,
171 ), 220 ),
172 children: <InlineSpan>[ 221 children: <InlineSpan>[
173 TextSpan( 222 TextSpan(