Showing
6 changed files
with
407 additions
and
38 deletions
@@ -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( |
-
Please register or login to post a comment