David PHAM-VAN

Add TextDecoration

# Changelog
## 1.3.21
- Add TextDecoration
## 1.3.20
- Fix Transform.rotateBox
... ...
... ... @@ -19,12 +19,10 @@ part of widget;
enum TextAlign { left, right, center, justify }
abstract class _Span {
_Span(this.style, this.annotation);
_Span(this.style);
final TextStyle style;
final AnnotationBuilder annotation;
PdfPoint offset = PdfPoint.zero;
double left;
... ... @@ -51,9 +49,143 @@ abstract class _Span {
);
}
class _TextDecoration {
_TextDecoration(this.style, this.annotation) : assert(style != null);
static const double _space = -0.15;
final TextStyle style;
final AnnotationBuilder annotation;
PdfRect box = PdfRect.zero;
void backgroundPaint(
Context context,
double textScaleFactor,
PdfRect globalBox,
) {
if (annotation != null) {
final PdfRect spanBox = PdfRect(
globalBox.x + box.left,
globalBox.top + box.bottom,
box.width,
box.height,
);
annotation.build(context, spanBox);
}
if (style.background != null) {
final PdfRect boundingBox = PdfRect(
globalBox.x + box.left,
globalBox.top + box.bottom,
box.width,
box.height,
);
style.background.paint(context, boundingBox);
context.canvas.setFillColor(style.color);
}
}
void foregroundPaint(
Context context,
double textScaleFactor,
PdfRect globalBox,
) {
if (style.decoration != null) {
final PdfFont font = style.font.getFont(context);
final double space =
_space * style.fontSize * textScaleFactor * style.decorationThickness;
context.canvas
..setStrokeColor(style.decorationColor ?? style.color)
..setLineWidth(style.decorationThickness *
style.fontSize *
textScaleFactor *
0.05);
if (style.decoration.contains(TextDecoration.underline)) {
final double base =
-font.descent * style.fontSize * textScaleFactor / 2;
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base,
globalBox.x + box.right,
globalBox.top + box.bottom + base,
);
if (style.decorationStyle == TextDecorationStyle.double) {
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base + space,
globalBox.x + box.right,
globalBox.top + box.bottom + base + space,
);
}
context.canvas.strokePath();
}
if (style.decoration.contains(TextDecoration.overline)) {
final double base = style.fontSize * textScaleFactor;
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base,
globalBox.x + box.right,
globalBox.top + box.bottom + base,
);
if (style.decorationStyle == TextDecorationStyle.double) {
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base - space,
globalBox.x + box.right,
globalBox.top + box.bottom + base - space,
);
}
context.canvas.strokePath();
}
if (style.decoration.contains(TextDecoration.lineThrough)) {
final double base =
(1 - font.descent) * style.fontSize * textScaleFactor / 2;
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base,
globalBox.x + box.right,
globalBox.top + box.bottom + base,
);
if (style.decorationStyle == TextDecorationStyle.double) {
context.canvas.drawLine(
globalBox.x + box.left,
globalBox.top + box.bottom + base + space,
globalBox.x + box.right,
globalBox.top + box.bottom + base + space,
);
}
context.canvas.strokePath();
}
}
}
void debugPaint(
Context context,
double textScaleFactor,
PdfRect globalBox,
) {
context.canvas
..setLineWidth(.5)
..drawRect(
globalBox.x + box.x, globalBox.top + box.y, box.width, box.height)
..setStrokeColor(PdfColors.yellow)
..strokePath();
}
}
class _Word extends _Span {
_Word(this.text, TextStyle style, this.metrics, AnnotationBuilder annotation)
: super(style, annotation);
_Word(
this.text,
TextStyle style,
this.metrics,
) : super(style);
final String text;
... ... @@ -100,6 +232,7 @@ class _Word extends _Span {
const double deb = 5;
context.canvas
..setLineWidth(.5)
..drawRect(globalBox.x + offset.x + metrics.left,
globalBox.top + offset.y + metrics.top, metrics.width, metrics.height)
..setStrokeColor(PdfColors.orange)
... ... @@ -115,10 +248,10 @@ class _Word extends _Span {
}
class _WidgetSpan extends _Span {
_WidgetSpan(this.widget, TextStyle style, AnnotationBuilder annotation)
_WidgetSpan(this.widget, TextStyle style)
: assert(widget != null),
assert(style != null),
super(style, annotation);
super(style);
final Widget widget;
... ... @@ -159,6 +292,20 @@ class _WidgetSpan extends _Span {
widget.box.size);
widget.paint(context);
}
@override
void debugPaint(
Context context,
double textScaleFactor,
PdfRect globalBox,
) {
context.canvas
..setLineWidth(.5)
..drawRect(
globalBox.x + offset.x, globalBox.top + offset.y, width, height)
..setStrokeColor(PdfColors.orange)
..strokePath();
}
}
@immutable
... ... @@ -272,8 +419,11 @@ class RichText extends Widget {
final List<_Span> _spans = <_Span>[];
final List<_TextDecoration> _decorations = <_TextDecoration>[];
double _realignLine(
List<_Span> spans,
List<_TextDecoration> decorations,
double totalWidth,
double wordsWidth,
bool last,
... ... @@ -306,6 +456,14 @@ class RichText extends Widget {
for (_Span span in spans) {
span.offset = span.offset.translate(delta, -baseline);
}
for (_TextDecoration decoration in decorations) {
decoration.box = PdfRect.fromPoints(
decoration.box.offset.translate(delta, -baseline),
decoration.box.size,
);
}
return totalWidth;
}
... ... @@ -313,6 +471,7 @@ class RichText extends Widget {
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
_spans.clear();
_decorations.clear();
final TextStyle defaultstyle = Theme.of(context).defaultTextStyle;
... ... @@ -330,8 +489,9 @@ class RichText extends Widget {
double bottom;
int lines = 1;
int wCount = 0;
int lineStart = 0;
int spanCount = 0;
int spanStart = 0;
int decorationStart = 0;
text.visitChildren((InlineSpan span, TextStyle style) {
if (span is TextSpan) {
... ... @@ -355,18 +515,20 @@ class RichText extends Widget {
final PdfFontMetrics metrics =
font.stringMetrics(word) * (style.fontSize * textScaleFactor);
if (offsetX + metrics.width > constraintWidth && wCount > 0) {
if (offsetX + metrics.width > constraintWidth && spanCount > 0) {
width = math.max(
width,
_realignLine(
_spans.sublist(lineStart),
_spans.sublist(spanStart),
_decorations.sublist(decorationStart),
constraintWidth,
offsetX - space.advanceWidth * style.wordSpacing,
false,
bottom,
));
lineStart += wCount;
spanStart += spanCount;
decorationStart = _decorations.length;
if (maxLines != null && ++lines > maxLines) {
break;
... ... @@ -380,7 +542,7 @@ class RichText extends Widget {
if (offsetY > constraintHeight) {
return false;
}
wCount = 0;
spanCount = 0;
}
final double baseline = span.baseline * textScaleFactor;
... ... @@ -397,12 +559,22 @@ class RichText extends Widget {
word,
style,
metrics,
span.annotation,
);
wd.offset = PdfPoint(offsetX, -offsetY + baseline);
_spans.add(wd);
wCount++;
spanCount++;
final _TextDecoration td = _TextDecoration(
style,
span.annotation,
);
td.box = PdfRect(
offsetX,
-offsetY + metrics.descent + baseline,
metrics.maxWidth + space.advanceWidth * style.wordSpacing,
metrics.maxHeight);
_decorations.add(td);
offsetX +=
metrics.advanceWidth + space.advanceWidth * style.wordSpacing;
}
... ... @@ -411,21 +583,32 @@ class RichText extends Widget {
width = math.max(
width,
_realignLine(
_spans.sublist(lineStart),
_spans.sublist(spanStart),
_decorations.sublist(decorationStart),
constraintWidth,
offsetX - space.advanceWidth * style.wordSpacing,
false,
bottom,
));
lineStart += wCount;
spanStart += spanCount;
decorationStart = _decorations.length;
if (_decorations.isNotEmpty) {
// remove the last space
_decorations.last.box = PdfRect.fromPoints(
_decorations.last.box.offset,
_decorations.last.box.size
.translate(-space.advanceWidth * style.wordSpacing, 0),
);
}
if (maxLines != null && ++lines > maxLines) {
break;
}
offsetX = 0.0;
if (wCount > 0) {
if (spanCount > 0) {
offsetY += bottom - top + style.lineSpacing;
} else {
offsetY += space.ascent + space.descent + style.lineSpacing;
... ... @@ -436,7 +619,7 @@ class RichText extends Widget {
if (offsetY > constraintHeight) {
return false;
}
wCount = 0;
spanCount = 0;
}
}
... ... @@ -451,21 +634,22 @@ class RichText extends Widget {
final _WidgetSpan ws = _WidgetSpan(
span.child,
style,
span.annotation,
);
if (offsetX + ws.width > constraintWidth && wCount > 0) {
if (offsetX + ws.width > constraintWidth && spanCount > 0) {
width = math.max(
width,
_realignLine(
_spans.sublist(lineStart),
_spans.sublist(spanStart),
_decorations.sublist(decorationStart),
constraintWidth,
offsetX,
false,
bottom,
));
lineStart += wCount;
spanStart += spanCount;
decorationStart = _decorations.length;
if (maxLines != null && ++lines > maxLines) {
return false;
... ... @@ -479,7 +663,7 @@ class RichText extends Widget {
if (offsetY > constraintHeight) {
return false;
}
wCount = 0;
spanCount = 0;
}
final double baseline = span.baseline * textScaleFactor;
... ... @@ -491,7 +675,15 @@ class RichText extends Widget {
ws.offset = PdfPoint(offsetX, -offsetY + baseline);
_spans.add(ws);
wCount++;
spanCount++;
final _TextDecoration td = _TextDecoration(
style,
span.annotation,
);
td.box = PdfRect(offsetX, -offsetY + baseline, ws.width, ws.height);
_decorations.add(td);
offsetX += ws.left + ws.width;
}
... ... @@ -501,7 +693,8 @@ class RichText extends Widget {
width = math.max(
width,
_realignLine(
_spans.sublist(lineStart),
_spans.sublist(spanStart),
_decorations.sublist(decorationStart),
lines > 1 ? constraintWidth : offsetX,
offsetX,
true,
... ... @@ -519,6 +712,7 @@ class RichText extends Widget {
void debugPaint(Context context) {
context.canvas
..setStrokeColor(PdfColors.blue)
..setLineWidth(1)
..drawRect(box.x, box.y, box.width, box.height)
..strokePath();
}
... ... @@ -529,6 +723,21 @@ class RichText extends Widget {
TextStyle currentStyle;
PdfColor currentColor;
for (_TextDecoration decoration in _decorations) {
assert(() {
if (Document.debug && RichText.debug) {
decoration.debugPaint(context, textScaleFactor, box);
}
return true;
}());
decoration.backgroundPaint(
context,
textScaleFactor,
box,
);
}
for (_Span span in _spans) {
assert(() {
if (Document.debug && RichText.debug) {
... ... @@ -545,12 +754,6 @@ class RichText extends Widget {
}
}
if (span.annotation != null) {
final PdfRect spanBox = PdfRect(box.x + span.offset.x + span.left,
box.top + span.offset.y + span.top, span.width, span.height);
span.annotation.build(context, spanBox);
}
span.paint(
context,
currentStyle,
... ... @@ -558,6 +761,14 @@ class RichText extends Widget {
PdfPoint(box.left, box.top),
);
}
for (_TextDecoration decoration in _decorations) {
decoration.foregroundPaint(
context,
textScaleFactor,
box,
);
}
}
}
... ...
... ... @@ -20,6 +20,74 @@ enum FontWeight { normal, bold }
enum FontStyle { normal, italic }
enum TextDecorationStyle { solid, double }
/// A linear decoration to draw near the text.
class TextDecoration {
const TextDecoration._(this._mask);
/// Creates a decoration that paints the union of all the given decorations.
factory TextDecoration.combine(List<TextDecoration> decorations) {
int mask = 0;
for (TextDecoration decoration in decorations) {
mask |= decoration._mask;
}
return TextDecoration._(mask);
}
final int _mask;
/// Whether this decoration will paint at least as much decoration as the given decoration.
bool contains(TextDecoration other) {
return (_mask | other._mask) == _mask;
}
/// Do not draw a decoration
static const TextDecoration none = TextDecoration._(0x0);
/// Draw a line underneath each line of text
static const TextDecoration underline = TextDecoration._(0x1);
/// Draw a line above each line of text
static const TextDecoration overline = TextDecoration._(0x2);
/// Draw a line through each line of text
static const TextDecoration lineThrough = TextDecoration._(0x4);
@override
bool operator ==(dynamic other) {
if (other is! TextDecoration) {
return false;
}
final TextDecoration typedOther = other;
return _mask == typedOther._mask;
}
@override
int get hashCode => _mask.hashCode;
@override
String toString() {
if (_mask == 0) {
return 'TextDecoration.none';
}
final List<String> values = <String>[];
if (_mask & underline._mask != 0) {
values.add('underline');
}
if (_mask & overline._mask != 0) {
values.add('overline');
}
if (_mask & lineThrough._mask != 0) {
values.add('lineThrough');
}
if (values.length == 1) {
return 'TextDecoration.${values[0]}';
}
return 'TextDecoration.combine([${values.join(", ")}])';
}
}
@immutable
class TextStyle {
const TextStyle({
... ... @@ -38,6 +106,10 @@ class TextStyle {
this.lineSpacing,
this.height,
this.background,
this.decoration,
this.decorationColor,
this.decorationStyle,
this.decorationThickness,
}) : assert(inherit || color != null),
assert(inherit || font != null),
assert(inherit || fontSize != null),
... ... @@ -47,6 +119,10 @@ class TextStyle {
assert(inherit || wordSpacing != null),
assert(inherit || lineSpacing != null),
assert(inherit || height != null),
assert(inherit || decoration != null),
assert(inherit || decorationColor != null),
assert(inherit || decorationStyle != null),
assert(inherit || decorationThickness != null),
fontNormal = fontNormal ??
(fontStyle != FontStyle.italic && fontWeight != FontWeight.bold
? font
... ... @@ -78,6 +154,10 @@ class TextStyle {
wordSpacing: 1.0,
lineSpacing: 0.0,
height: 1.0,
decoration: TextDecoration.none,
decorationColor: null,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: 1,
);
}
... ... @@ -115,7 +195,15 @@ class TextStyle {
final double height;
final PdfColor background;
final BoxDecoration background;
final TextDecoration decoration;
final PdfColor decorationColor;
final TextDecorationStyle decorationStyle;
final double decorationThickness;
TextStyle copyWith({
PdfColor color,
... ... @@ -131,7 +219,11 @@ class TextStyle {
double wordSpacing,
double lineSpacing,
double height,
PdfColor background,
BoxDecoration background,
TextDecoration decoration,
PdfColor decorationColor,
TextDecorationStyle decorationStyle,
double decorationThickness,
}) {
return TextStyle(
inherit: inherit,
... ... @@ -149,6 +241,10 @@ class TextStyle {
lineSpacing: lineSpacing ?? this.lineSpacing,
height: height ?? this.height,
background: background ?? this.background,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
decorationStyle: decorationStyle ?? this.decorationStyle,
decorationThickness: decorationThickness ?? this.decorationThickness,
);
}
... ... @@ -169,6 +265,7 @@ class TextStyle {
double wordSpacingDelta = 0.0,
double heightFactor = 1.0,
double heightDelta = 0.0,
TextDecoration decoration = TextDecoration.none,
}) {
assert(fontSizeFactor != null);
assert(fontSizeDelta != null);
... ... @@ -184,6 +281,7 @@ class TextStyle {
assert(heightFactor != null);
assert(heightDelta != null);
assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0));
assert(decoration != null);
return TextStyle(
inherit: inherit,
... ... @@ -205,6 +303,7 @@ class TextStyle {
: wordSpacing * wordSpacingFactor + wordSpacingDelta,
height: height == null ? null : height * heightFactor + heightDelta,
background: background,
decoration: decoration,
);
}
... ... @@ -234,6 +333,10 @@ class TextStyle {
lineSpacing: other.lineSpacing,
height: other.height,
background: other.background,
decoration: other.decoration,
decorationColor: other.decorationColor,
decorationStyle: other.decorationStyle,
decorationThickness: other.decorationThickness,
);
}
... ... @@ -262,5 +365,5 @@ class TextStyle {
@override
String toString() =>
'TextStyle(color:$color font:$font size:$fontSize weight:$fontWeight style:$fontStyle letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)';
'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)';
}
... ...
... ... @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
repository: https://github.com/DavBfr/dart_pdf
issue_tracker: https://github.com/DavBfr/dart_pdf/issues
version: 1.3.20
version: 1.3.21
environment:
sdk: ">=2.1.0 <3.0.0"
... ...
... ... @@ -74,6 +74,7 @@ void main() {
'Internal link',
style: TextStyle(
color: PdfColors.blue,
decoration: TextDecoration.underline,
),
))),
Padding(padding: const EdgeInsets.all(5)),
... ... @@ -215,6 +216,7 @@ void main() {
style: TextStyle(
color: PdfColors.grey,
fontSize: 8,
decoration: TextDecoration.underline,
),
),
destination: 'https://github.com/DavBfr/dart_pdf/')),
... ...
... ... @@ -26,6 +26,18 @@ Document pdf;
Font ttf;
Font ttfBold;
Iterable<TextDecoration> permute(
List<TextDecoration> prefix, List<TextDecoration> remaining) sync* {
yield TextDecoration.combine(prefix);
if (remaining.isNotEmpty) {
for (TextDecoration decoration in remaining) {
final List<TextDecoration> next = List<TextDecoration>.from(remaining);
next.remove(decoration);
yield* permute(prefix + <TextDecoration>[decoration], next);
}
}
}
void main() {
setUpAll(() {
Document.debug = true;
... ... @@ -135,12 +147,48 @@ void main() {
para,
style: TextStyle(
font: ttf,
background: PdfColors.purple50,
background: BoxDecoration(color: PdfColors.purple50),
),
),
]));
});
test('Text Widgets decoration', () {
final List<Widget> widgets = <Widget>[];
final List<TextDecoration> decorations = <TextDecoration>[
TextDecoration.underline,
TextDecoration.lineThrough,
TextDecoration.overline
];
final Set<TextDecoration> decorationSet = Set<TextDecoration>.from(
permute(
<TextDecoration>[],
decorations,
),
);
for (TextDecorationStyle decorationStyle in TextDecorationStyle.values) {
for (TextDecoration decoration in decorationSet) {
widgets.add(
Text(
decoration.toString().replaceAll('.', ' '),
style: TextStyle(
font: ttf,
decoration: decoration,
decorationColor: PdfColors.red,
decorationStyle: decorationStyle),
),
);
widgets.add(
SizedBox(height: 5),
);
}
}
pdf.addPage(MultiPage(build: (Context context) => widgets));
});
test('Text Widgets RichText', () {
final math.Random rnd = math.Random(42);
final String para = LoremText(random: rnd).paragraph(40);
... ... @@ -168,6 +216,7 @@ void main() {
style: TextStyle(
font: ttf,
fontSize: 20,
decoration: TextDecoration.underline,
),
children: <InlineSpan>[
TextSpan(
... ...