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