David PHAM-VAN

Improve Theme and TextStyle

@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 * Allow Annotations in TextSpan 4 * Allow Annotations in TextSpan
5 * Add SizedBox Widget 5 * Add SizedBox Widget
6 * Fix RichText Widget word spacing 6 * Fix RichText Widget word spacing
  7 +* Improve Theme and TextStyle
7 8
8 # 1.3.7 9 # 1.3.7
9 * Add Pdf Creation date 10 * Add Pdf Creation date
@@ -67,22 +67,25 @@ class TextSpan { @@ -67,22 +67,25 @@ class TextSpan {
67 67
68 String toPlainText() { 68 String toPlainText() {
69 final StringBuffer buffer = StringBuffer(); 69 final StringBuffer buffer = StringBuffer();
70 - visitTextSpan((TextSpan span) { 70 + visitTextSpan((TextSpan span, TextStyle style) {
71 buffer.write(span.text); 71 buffer.write(span.text);
72 return true; 72 return true;
73 - }); 73 + }, null);
74 return buffer.toString(); 74 return buffer.toString();
75 } 75 }
76 76
77 - bool visitTextSpan(bool visitor(TextSpan span)) { 77 + bool visitTextSpan(bool visitor(TextSpan span, TextStyle parentStyle),
  78 + TextStyle parentStyle) {
  79 + final TextStyle _style = parentStyle?.merge(style);
  80 +
78 if (text != null) { 81 if (text != null) {
79 - if (!visitor(this)) { 82 + if (!visitor(this, _style)) {
80 return false; 83 return false;
81 } 84 }
82 } 85 }
83 if (children != null) { 86 if (children != null) {
84 for (TextSpan child in children) { 87 for (TextSpan child in children) {
85 - if (!child.visitTextSpan(visitor)) { 88 + if (!child.visitTextSpan(visitor, _style)) {
86 return false; 89 return false;
87 } 90 }
88 } 91 }
@@ -170,18 +173,17 @@ class RichText extends Widget { @@ -170,18 +173,17 @@ class RichText extends Widget {
170 int wCount = 0; 173 int wCount = 0;
171 int lineStart = 0; 174 int lineStart = 0;
172 175
173 - text.visitTextSpan((TextSpan span) { 176 + text.visitTextSpan((TextSpan span, TextStyle style) {
174 if (span.text == null) { 177 if (span.text == null) {
175 return true; 178 return true;
176 } 179 }
177 180
178 - final TextStyle style = span.style ?? defaultstyle;  
179 - final PdfFont font = style.font.getFont(context); 181 + final PdfFont font = style.paintFont.getFont(context);
180 182
181 final PdfFontMetrics space = 183 final PdfFontMetrics space =
182 font.stringMetrics(' ') * (style.fontSize * textScaleFactor); 184 font.stringMetrics(' ') * (style.fontSize * textScaleFactor);
183 185
184 - for (String word in span.text.split(' ')) { 186 + for (String word in span.text.split(RegExp(r'\s'))) {
185 if (word.isEmpty) { 187 if (word.isEmpty) {
186 offsetX += space.advanceWidth * style.wordSpacing; 188 offsetX += space.advanceWidth * style.wordSpacing;
187 continue; 189 continue;
@@ -229,7 +231,7 @@ class RichText extends Widget { @@ -229,7 +231,7 @@ class RichText extends Widget {
229 231
230 offsetX -= space.advanceWidth * style.wordSpacing; 232 offsetX -= space.advanceWidth * style.wordSpacing;
231 return true; 233 return true;
232 - }); 234 + }, defaultstyle);
233 235
234 width = math.max( 236 width = math.max(
235 width, 237 width,
@@ -283,7 +285,7 @@ class RichText extends Widget { @@ -283,7 +285,7 @@ class RichText extends Widget {
283 } 285 }
284 286
285 context.canvas.drawString( 287 context.canvas.drawString(
286 - currentStyle.font.getFont(context), 288 + currentStyle.paintFont.getFont(context),
287 currentStyle.fontSize * textScaleFactor, 289 currentStyle.fontSize * textScaleFactor,
288 word.text, 290 word.text,
289 box.x + word.offset.x, 291 box.x + word.offset.x,
@@ -16,27 +16,75 @@ @@ -16,27 +16,75 @@
16 16
17 part of widget; 17 part of widget;
18 18
  19 +enum FontWeight { normal, bold }
  20 +
  21 +enum FontStyle { normal, italic }
  22 +
19 @immutable 23 @immutable
20 class TextStyle { 24 class TextStyle {
21 const TextStyle({ 25 const TextStyle({
22 - this.color = PdfColors.black,  
23 - @required this.font,  
24 - this.fontSize = _defaultFontSize,  
25 - this.letterSpacing = 1.0,  
26 - this.wordSpacing = 1.0,  
27 - this.lineSpacing = 0.0,  
28 - this.height = 1.0, 26 + this.inherit = true,
  27 + this.color,
  28 + this.font,
  29 + this.fontBold,
  30 + this.fontItalic,
  31 + this.fontBoldItalic,
  32 + this.fontSize,
  33 + this.fontWeight,
  34 + this.fontStyle,
  35 + this.letterSpacing,
  36 + this.wordSpacing,
  37 + this.lineSpacing,
  38 + this.height,
29 this.background, 39 this.background,
30 - }) : assert(font != null),  
31 - assert(color != null); 40 + }) : assert(inherit || color != null),
  41 + assert(inherit || font != null),
  42 + assert(inherit || fontSize != null),
  43 + assert(inherit || fontWeight != null),
  44 + assert(inherit || fontStyle != null),
  45 + assert(inherit || letterSpacing != null),
  46 + assert(inherit || wordSpacing != null),
  47 + assert(inherit || lineSpacing != null),
  48 + assert(inherit || height != null);
  49 +
  50 + factory TextStyle.defaultStyle() {
  51 + return TextStyle(
  52 + color: PdfColors.black,
  53 + font: Font.helvetica(),
  54 + fontBold: Font.helveticaBold(),
  55 + fontItalic: Font.helveticaOblique(),
  56 + fontBoldItalic: Font.helveticaBoldOblique(),
  57 + fontSize: _defaultFontSize,
  58 + fontWeight: FontWeight.normal,
  59 + fontStyle: FontStyle.normal,
  60 + letterSpacing: 1.0,
  61 + wordSpacing: 1.0,
  62 + lineSpacing: 0.0,
  63 + height: 1.0,
  64 + );
  65 + }
  66 +
  67 + final bool inherit;
32 68
33 final PdfColor color; 69 final PdfColor color;
34 70
35 final Font font; 71 final Font font;
36 72
  73 + final Font fontBold;
  74 +
  75 + final Font fontItalic;
  76 +
  77 + final Font fontBoldItalic;
  78 +
37 // font height, in pdf unit 79 // font height, in pdf unit
38 final double fontSize; 80 final double fontSize;
39 81
  82 + /// The typeface thickness to use when painting the text (e.g., bold).
  83 + final FontWeight fontWeight;
  84 +
  85 + /// The typeface variant to use when drawing the letters (e.g., italics).
  86 + final FontStyle fontStyle;
  87 +
40 static const double _defaultFontSize = 12.0 * PdfPageFormat.point; 88 static const double _defaultFontSize = 12.0 * PdfPageFormat.point;
41 89
42 // spacing between letters, 1.0 being natural spacing 90 // spacing between letters, 1.0 being natural spacing
@@ -55,7 +103,12 @@ class TextStyle { @@ -55,7 +103,12 @@ class TextStyle {
55 TextStyle copyWith({ 103 TextStyle copyWith({
56 PdfColor color, 104 PdfColor color,
57 Font font, 105 Font font,
  106 + Font fontBold,
  107 + Font fontItalic,
  108 + Font fontBoldItalic,
58 double fontSize, 109 double fontSize,
  110 + FontWeight fontWeight,
  111 + FontStyle fontStyle,
59 double letterSpacing, 112 double letterSpacing,
60 double wordSpacing, 113 double wordSpacing,
61 double lineSpacing, 114 double lineSpacing,
@@ -63,9 +116,15 @@ class TextStyle { @@ -63,9 +116,15 @@ class TextStyle {
63 PdfColor background, 116 PdfColor background,
64 }) { 117 }) {
65 return TextStyle( 118 return TextStyle(
  119 + inherit: inherit,
66 color: color ?? this.color, 120 color: color ?? this.color,
67 font: font ?? this.font, 121 font: font ?? this.font,
  122 + fontBold: fontBold ?? this.fontBold,
  123 + fontItalic: fontItalic ?? this.fontItalic,
  124 + fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic,
68 fontSize: fontSize ?? this.fontSize, 125 fontSize: fontSize ?? this.fontSize,
  126 + fontWeight: fontWeight ?? this.fontWeight,
  127 + fontStyle: fontStyle ?? this.fontStyle,
69 letterSpacing: letterSpacing ?? this.letterSpacing, 128 letterSpacing: letterSpacing ?? this.letterSpacing,
70 wordSpacing: wordSpacing ?? this.wordSpacing, 129 wordSpacing: wordSpacing ?? this.wordSpacing,
71 lineSpacing: lineSpacing ?? this.lineSpacing, 130 lineSpacing: lineSpacing ?? this.lineSpacing,
@@ -74,15 +133,79 @@ class TextStyle { @@ -74,15 +133,79 @@ class TextStyle {
74 ); 133 );
75 } 134 }
76 135
  136 + /// Creates a copy of this text style replacing or altering the specified
  137 + /// properties.
  138 + TextStyle apply({
  139 + PdfColor color,
  140 + Font font,
  141 + Font fontBold,
  142 + Font fontItalic,
  143 + Font fontBoldItalic,
  144 + double fontSizeFactor = 1.0,
  145 + double fontSizeDelta = 0.0,
  146 + double letterSpacingFactor = 1.0,
  147 + double letterSpacingDelta = 0.0,
  148 + double wordSpacingFactor = 1.0,
  149 + double wordSpacingDelta = 0.0,
  150 + double heightFactor = 1.0,
  151 + double heightDelta = 0.0,
  152 + }) {
  153 + assert(fontSizeFactor != null);
  154 + assert(fontSizeDelta != null);
  155 + assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
  156 + assert(letterSpacingFactor != null);
  157 + assert(letterSpacingDelta != null);
  158 + assert(letterSpacing != null ||
  159 + (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
  160 + assert(wordSpacingFactor != null);
  161 + assert(wordSpacingDelta != null);
  162 + assert(wordSpacing != null ||
  163 + (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
  164 + assert(heightFactor != null);
  165 + assert(heightDelta != null);
  166 + assert(heightFactor != null || (heightFactor == 1.0 && heightDelta == 0.0));
  167 +
  168 + return TextStyle(
  169 + inherit: inherit,
  170 + color: color ?? this.color,
  171 + font: font ?? this.font,
  172 + fontBold: fontBold ?? this.fontBold,
  173 + fontItalic: fontItalic ?? this.fontItalic,
  174 + fontBoldItalic: fontBoldItalic ?? this.fontBoldItalic,
  175 + fontSize:
  176 + fontSize == null ? null : fontSize * fontSizeFactor + fontSizeDelta,
  177 + fontWeight: fontWeight,
  178 + fontStyle: fontStyle,
  179 + letterSpacing: letterSpacing == null
  180 + ? null
  181 + : letterSpacing * letterSpacingFactor + letterSpacingDelta,
  182 + wordSpacing: wordSpacing == null
  183 + ? null
  184 + : wordSpacing * wordSpacingFactor + wordSpacingDelta,
  185 + height: height == null ? null : height * heightFactor + heightDelta,
  186 + background: background,
  187 + );
  188 + }
  189 +
  190 + /// Returns a new text style that is a combination of this style and the given
  191 + /// [other] style.
77 TextStyle merge(TextStyle other) { 192 TextStyle merge(TextStyle other) {
78 if (other == null) { 193 if (other == null) {
79 return this; 194 return this;
80 } 195 }
81 196
  197 + if (!other.inherit) {
  198 + return other;
  199 + }
  200 +
82 return copyWith( 201 return copyWith(
83 color: other.color, 202 color: other.color,
84 font: other.font, 203 font: other.font,
  204 + fontItalic: other.fontItalic,
  205 + fontBoldItalic: other.fontBoldItalic,
85 fontSize: other.fontSize, 206 fontSize: other.fontSize,
  207 + fontWeight: other.fontWeight,
  208 + fontStyle: other.fontStyle,
86 letterSpacing: other.letterSpacing, 209 letterSpacing: other.letterSpacing,
87 wordSpacing: other.wordSpacing, 210 wordSpacing: other.wordSpacing,
88 lineSpacing: other.lineSpacing, 211 lineSpacing: other.lineSpacing,
@@ -91,16 +214,31 @@ class TextStyle { @@ -91,16 +214,31 @@ class TextStyle {
91 ); 214 );
92 } 215 }
93 216
  217 + Font get paintFont {
  218 + if (fontWeight == FontWeight.normal) {
  219 + if (fontStyle == FontStyle.normal) {
  220 + return font;
  221 + } else {
  222 + return fontItalic ?? font;
  223 + }
  224 + } else {
  225 + if (fontStyle == FontStyle.normal) {
  226 + return fontBold ?? font;
  227 + } else {
  228 + return fontBoldItalic ?? fontBold ?? fontItalic ?? font;
  229 + }
  230 + }
  231 + }
  232 +
94 @override 233 @override
95 String toString() => 234 String toString() =>
96 - 'TextStyle(color:$color font:$font letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)'; 235 + 'TextStyle(color:$color font:$paintFont size:$fontSize weight:$fontWeight style:$fontStyle letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)';
97 } 236 }
98 237
99 @immutable 238 @immutable
100 class Theme extends Inherited { 239 class Theme extends Inherited {
101 - Theme({ 240 + const Theme({
102 @required this.defaultTextStyle, 241 @required this.defaultTextStyle,
103 - @required this.defaultTextStyleBold,  
104 @required this.paragraphStyle, 242 @required this.paragraphStyle,
105 @required this.header0, 243 @required this.header0,
106 @required this.header1, 244 @required this.header1,
@@ -113,32 +251,33 @@ class Theme extends Inherited { @@ -113,32 +251,33 @@ class Theme extends Inherited {
113 @required this.tableCell, 251 @required this.tableCell,
114 }); 252 });
115 253
116 - factory Theme.withFont(Font baseFont, Font baseFontBold) {  
117 - final TextStyle defaultTextStyle = TextStyle(font: baseFont);  
118 - final TextStyle defaultTextStyleBold = TextStyle(font: baseFontBold);  
119 - final double fontSize = defaultTextStyle.fontSize; 254 + factory Theme.withFont({Font base, Font bold, Font italic, Font boldItalic}) {
  255 + final TextStyle defaultStyle = TextStyle.defaultStyle().copyWith(
  256 + font: base,
  257 + fontBold: bold,
  258 + fontItalic: italic,
  259 + fontBoldItalic: boldItalic);
  260 + final double fontSize = defaultStyle.fontSize;
120 261
121 return Theme( 262 return Theme(
122 - defaultTextStyle: defaultTextStyle,  
123 - defaultTextStyleBold: defaultTextStyleBold,  
124 - paragraphStyle: defaultTextStyle.copyWith(lineSpacing: 5),  
125 - bulletStyle: defaultTextStyle.copyWith(lineSpacing: 5),  
126 - header0: defaultTextStyleBold.copyWith(fontSize: fontSize * 2.0),  
127 - header1: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.5),  
128 - header2: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.4),  
129 - header3: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.3),  
130 - header4: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.2),  
131 - header5: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.1),  
132 - tableHeader: defaultTextStyleBold,  
133 - tableCell: defaultTextStyle); 263 + defaultTextStyle: defaultStyle,
  264 + paragraphStyle: defaultStyle.copyWith(lineSpacing: 5),
  265 + bulletStyle: defaultStyle.copyWith(lineSpacing: 5),
  266 + header0: defaultStyle.copyWith(fontSize: fontSize * 2.0),
  267 + header1: defaultStyle.copyWith(fontSize: fontSize * 1.5),
  268 + header2: defaultStyle.copyWith(fontSize: fontSize * 1.4),
  269 + header3: defaultStyle.copyWith(fontSize: fontSize * 1.3),
  270 + header4: defaultStyle.copyWith(fontSize: fontSize * 1.2),
  271 + header5: defaultStyle.copyWith(fontSize: fontSize * 1.1),
  272 + tableHeader: defaultStyle.copyWith(
  273 + fontSize: fontSize * 0.8, fontWeight: FontWeight.bold),
  274 + tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8));
134 } 275 }
135 276
136 - factory Theme.base() =>  
137 - Theme.withFont(Font.helvetica(), Font.helveticaBold()); 277 + factory Theme.base() => Theme.withFont();
138 278
139 Theme copyWith({ 279 Theme copyWith({
140 TextStyle defaultTextStyle, 280 TextStyle defaultTextStyle,
141 - TextStyle defaultTextStyleBold,  
142 TextStyle paragraphStyle, 281 TextStyle paragraphStyle,
143 TextStyle header0, 282 TextStyle header0,
144 TextStyle header1, 283 TextStyle header1,
@@ -152,8 +291,6 @@ class Theme extends Inherited { @@ -152,8 +291,6 @@ class Theme extends Inherited {
152 }) => 291 }) =>
153 Theme( 292 Theme(
154 defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle, 293 defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle,
155 - defaultTextStyleBold:  
156 - defaultTextStyleBold ?? this.defaultTextStyleBold,  
157 paragraphStyle: paragraphStyle ?? this.paragraphStyle, 294 paragraphStyle: paragraphStyle ?? this.paragraphStyle,
158 bulletStyle: bulletStyle ?? this.bulletStyle, 295 bulletStyle: bulletStyle ?? this.bulletStyle,
159 header0: header0 ?? this.header0, 296 header0: header0 ?? this.header0,
@@ -171,8 +308,6 @@ class Theme extends Inherited { @@ -171,8 +308,6 @@ class Theme extends Inherited {
171 308
172 final TextStyle defaultTextStyle; 309 final TextStyle defaultTextStyle;
173 310
174 - final TextStyle defaultTextStyleBold;  
175 -  
176 final TextStyle paragraphStyle; 311 final TextStyle paragraphStyle;
177 312
178 final TextStyle header0; 313 final TextStyle header0;
@@ -57,7 +57,9 @@ class Context { @@ -57,7 +57,9 @@ class Context {
57 } 57 }
58 } 58 }
59 59
60 -class Inherited {} 60 +class Inherited {
  61 + const Inherited();
  62 +}
61 63
62 abstract class Widget { 64 abstract class Widget {
63 Widget(); 65 Widget();
@@ -33,8 +33,8 @@ void main() { @@ -33,8 +33,8 @@ void main() {
33 final Document pdf = Document( 33 final Document pdf = Document(
34 title: 'Widgets Test', 34 title: 'Widgets Test',
35 theme: Theme.withFont( 35 theme: Theme.withFont(
36 - Font.ttf(defaultFont.buffer.asByteData()),  
37 - Font.ttf(defaultFontBold.buffer.asByteData()), 36 + base: Font.ttf(defaultFont.buffer.asByteData()),
  37 + bold: Font.ttf(defaultFontBold.buffer.asByteData()),
38 )); 38 ));
39 39
40 final TextStyle symbol = TextStyle(font: Font.zapfDingbats()); 40 final TextStyle symbol = TextStyle(font: Font.zapfDingbats());
@@ -175,12 +175,12 @@ void main() { @@ -175,12 +175,12 @@ void main() {
175 text: 'Hello ', 175 text: 'Hello ',
176 style: Theme.of(context).defaultTextStyle, 176 style: Theme.of(context).defaultTextStyle,
177 children: <TextSpan>[ 177 children: <TextSpan>[
178 - TextSpan( 178 + const TextSpan(
179 text: 'bold', 179 text: 'bold',
180 - style: Theme.of(context)  
181 - .defaultTextStyleBold  
182 - .copyWith(  
183 - fontSize: 20, color: PdfColors.blue)), 180 + style: TextStyle(
  181 + fontWeight: FontWeight.bold,
  182 + fontSize: 20,
  183 + color: PdfColors.blue)),
184 const TextSpan( 184 const TextSpan(
185 text: ' world!', 185 text: ' world!',
186 ), 186 ),
@@ -71,7 +71,10 @@ class Block extends StatelessWidget { @@ -71,7 +71,10 @@ class Block extends StatelessWidget {
71 decoration: 71 decoration:
72 const BoxDecoration(color: green, shape: BoxShape.circle), 72 const BoxDecoration(color: green, shape: BoxShape.circle),
73 ), 73 ),
74 - Text(title, style: Theme.of(context).defaultTextStyleBold), 74 + Text(title,
  75 + style: Theme.of(context)
  76 + .defaultTextStyle
  77 + .copyWith(fontWeight: FontWeight.bold)),
75 ]), 78 ]),
76 Container( 79 Container(
77 decoration: const BoxDecoration( 80 decoration: const BoxDecoration(
@@ -132,13 +135,17 @@ Future<PdfDocument> generateDocument(PdfPageFormat format) async { @@ -132,13 +135,17 @@ Future<PdfDocument> generateDocument(PdfPageFormat format) async {
132 children: <Widget>[ 135 children: <Widget>[
133 Text('Parnella Charlesbois', 136 Text('Parnella Charlesbois',
134 textScaleFactor: 2, 137 textScaleFactor: 2,
135 - style: Theme.of(context).defaultTextStyleBold), 138 + style: Theme.of(context)
  139 + .defaultTextStyle
  140 + .copyWith(fontWeight: FontWeight.bold)),
136 Padding(padding: const EdgeInsets.only(top: 10)), 141 Padding(padding: const EdgeInsets.only(top: 10)),
137 Text('Electrotyper', 142 Text('Electrotyper',
138 textScaleFactor: 1.2, 143 textScaleFactor: 1.2,
139 style: Theme.of(context) 144 style: Theme.of(context)
140 - .defaultTextStyleBold  
141 - .copyWith(color: green)), 145 + .defaultTextStyle
  146 + .copyWith(
  147 + fontWeight: FontWeight.bold,
  148 + color: green)),
142 Padding(padding: const EdgeInsets.only(top: 20)), 149 Padding(padding: const EdgeInsets.only(top: 20)),
143 Row( 150 Row(
144 crossAxisAlignment: CrossAxisAlignment.start, 151 crossAxisAlignment: CrossAxisAlignment.start,