David PHAM-VAN

Improve TextOverflow support

@@ -59,12 +59,14 @@ class Calendar extends StatelessWidget { @@ -59,12 +59,14 @@ class Calendar extends StatelessWidget {
59 ) { 59 ) {
60 return Container( 60 return Container(
61 color: PdfColors.blue200, 61 color: PdfColors.blue200,
62 - padding: const EdgeInsets.all(8.0), 62 + padding: const EdgeInsets.only(top: 8, left: 8, bottom: 8),
63 child: Text( 63 child: Text(
64 DateFormat.EEEE().format(date), 64 DateFormat.EEEE().format(date),
65 style: const TextStyle( 65 style: const TextStyle(
66 fontSize: 15, 66 fontSize: 15,
67 ), 67 ),
  68 + maxLines: 1,
  69 + overflow: TextOverflow.clip,
68 ), 70 ),
69 ); 71 );
70 } 72 }
@@ -101,6 +101,7 @@ Future<Uint8List> generateCertificate( @@ -101,6 +101,7 @@ Future<Uint8List> generateCertificate(
101 ), 101 ),
102 pw.Text( 102 pw.Text(
103 data.name, 103 data.name,
  104 + textAlign: pw.TextAlign.center,
104 style: pw.TextStyle( 105 style: pw.TextStyle(
105 fontWeight: pw.FontWeight.bold, 106 fontWeight: pw.FontWeight.bold,
106 fontSize: 20, 107 fontSize: 20,
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 import 'dart:typed_data'; 17 import 'dart:typed_data';
18 18
  19 +import 'package:flutter/services.dart';
19 import 'package:pdf/pdf.dart'; 20 import 'package:pdf/pdf.dart';
20 import 'package:pdf/widgets.dart' as pw; 21 import 'package:pdf/widgets.dart' as pw;
21 22
@@ -25,7 +26,14 @@ Future<Uint8List> generateDocument( @@ -25,7 +26,14 @@ Future<Uint8List> generateDocument(
25 PdfPageFormat format, CustomData data) async { 26 PdfPageFormat format, CustomData data) async {
26 final doc = pw.Document(pageMode: PdfPageMode.outlines); 27 final doc = pw.Document(pageMode: PdfPageMode.outlines);
27 28
  29 + final font1 = await rootBundle.load('assets/open-sans.ttf');
  30 + final font2 = await rootBundle.load('assets/open-sans-bold.ttf');
  31 +
28 doc.addPage(pw.MultiPage( 32 doc.addPage(pw.MultiPage(
  33 + theme: pw.ThemeData.withFont(
  34 + base: pw.Font.ttf(font1),
  35 + bold: pw.Font.ttf(font2),
  36 + ),
29 pageFormat: format.copyWith(marginBottom: 1.5 * PdfPageFormat.cm), 37 pageFormat: format.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
30 orientation: pw.PageOrientation.portrait, 38 orientation: pw.PageOrientation.portrait,
31 crossAxisAlignment: pw.CrossAxisAlignment.start, 39 crossAxisAlignment: pw.CrossAxisAlignment.start,
@@ -222,6 +222,7 @@ class Invoice { @@ -222,6 +222,7 @@ class Invoice {
222 child: pw.BarcodeWidget( 222 child: pw.BarcodeWidget(
223 barcode: pw.Barcode.pdf417(), 223 barcode: pw.Barcode.pdf417(),
224 data: 'Invoice# $invoiceNumber', 224 data: 'Invoice# $invoiceNumber',
  225 + drawText: false,
225 ), 226 ),
226 ), 227 ),
227 pw.Text( 228 pw.Text(
@@ -55,12 +55,6 @@ Future<Uint8List> generateReport( @@ -55,12 +55,6 @@ Future<Uint8List> generateReport(
55 bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')), 55 bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')),
56 ); 56 );
57 57
58 - // Add page to the PDF  
59 - document.addPage(  
60 - pw.Page(  
61 - pageFormat: pageFormat,  
62 - theme: theme,  
63 - build: (context) {  
64 // Top bar chart 58 // Top bar chart
65 final chart1 = pw.Chart( 59 final chart1 = pw.Chart(
66 left: pw.Container( 60 left: pw.Container(
@@ -129,6 +123,7 @@ Future<Uint8List> generateReport( @@ -129,6 +123,7 @@ Future<Uint8List> generateReport(
129 123
130 // Left curved line chart 124 // Left curved line chart
131 final chart2 = pw.Chart( 125 final chart2 = pw.Chart(
  126 + right: pw.ChartLegend(),
132 grid: pw.CartesianGrid( 127 grid: pw.CartesianGrid(
133 xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]), 128 xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]),
134 yAxis: pw.FixedAxis( 129 yAxis: pw.FixedAxis(
@@ -138,6 +133,7 @@ Future<Uint8List> generateReport( @@ -138,6 +133,7 @@ Future<Uint8List> generateReport(
138 ), 133 ),
139 datasets: [ 134 datasets: [
140 pw.LineDataSet( 135 pw.LineDataSet(
  136 + legend: 'Expense',
141 drawSurface: true, 137 drawSurface: true,
142 isCurved: true, 138 isCurved: true,
143 drawPoints: false, 139 drawPoints: false,
@@ -185,6 +181,12 @@ Future<Uint8List> generateReport( @@ -185,6 +181,12 @@ Future<Uint8List> generateReport(
185 cellAlignments: {0: pw.Alignment.centerLeft}, 181 cellAlignments: {0: pw.Alignment.centerLeft},
186 ); 182 );
187 183
  184 + // Add page to the PDF
  185 + document.addPage(
  186 + pw.Page(
  187 + pageFormat: pageFormat,
  188 + theme: theme,
  189 + build: (context) {
188 // Page layout 190 // Page layout
189 return pw.Column( 191 return pw.Column(
190 children: [ 192 children: [
@@ -196,17 +198,7 @@ Future<Uint8List> generateReport( @@ -196,17 +198,7 @@ Future<Uint8List> generateReport(
196 pw.Divider(thickness: 4), 198 pw.Divider(thickness: 4),
197 pw.Expanded(flex: 3, child: chart1), 199 pw.Expanded(flex: 3, child: chart1),
198 pw.Divider(), 200 pw.Divider(),
199 - pw.Expanded(  
200 - flex: 2,  
201 - child: pw.Row(  
202 - crossAxisAlignment: pw.CrossAxisAlignment.start,  
203 - children: [  
204 - pw.Expanded(child: chart2),  
205 - pw.SizedBox(width: 10),  
206 - pw.Expanded(child: table),  
207 - ],  
208 - ),  
209 - ), 201 + pw.Expanded(flex: 2, child: chart2),
210 pw.SizedBox(height: 10), 202 pw.SizedBox(height: 10),
211 pw.Row( 203 pw.Row(
212 crossAxisAlignment: pw.CrossAxisAlignment.start, 204 crossAxisAlignment: pw.CrossAxisAlignment.start,
@@ -275,8 +267,9 @@ Future<Uint8List> generateReport( @@ -275,8 +267,9 @@ Future<Uint8List> generateReport(
275 PdfColors.lime300, 267 PdfColors.lime300,
276 ]; 268 ];
277 269
278 - return pw.SizedBox(  
279 - height: 400, 270 + return pw.Column(
  271 + children: [
  272 + pw.Flexible(
280 child: pw.Chart( 273 child: pw.Chart(
281 title: pw.Text( 274 title: pw.Text(
282 'Expense breakdown', 275 'Expense breakdown',
@@ -303,6 +296,9 @@ Future<Uint8List> generateReport( @@ -303,6 +296,9 @@ Future<Uint8List> generateReport(
303 ); 296 );
304 }), 297 }),
305 ), 298 ),
  299 + ),
  300 + table,
  301 + ],
306 ); 302 );
307 }, 303 },
308 ), 304 ),
@@ -141,6 +141,7 @@ Future<Uint8List> generateResume(PdfPageFormat format, CustomData data) async { @@ -141,6 +141,7 @@ Future<Uint8List> generateResume(PdfPageFormat format, CustomData data) async {
141 width: 60, 141 width: 60,
142 height: 60, 142 height: 60,
143 barcode: pw.Barcode.qrCode(), 143 barcode: pw.Barcode.qrCode(),
  144 + drawText: false,
144 ), 145 ),
145 ], 146 ],
146 ), 147 ),
@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 - Fix textScalingFactor with lineSpacing 11 - Fix textScalingFactor with lineSpacing
12 - Implement SpanningWidget on RichText 12 - Implement SpanningWidget on RichText
13 - Passthrough SpanningWidget on SingleChildWidget and StatelessWidget 13 - Passthrough SpanningWidget on SingleChildWidget and StatelessWidget
  14 +- Improve TextOverflow support
14 15
15 ## 3.2.0 16 ## 3.2.0
16 17
@@ -33,11 +33,14 @@ enum TextDirection { ltr, rtl } @@ -33,11 +33,14 @@ enum TextDirection { ltr, rtl }
33 33
34 /// How overflowing text should be handled. 34 /// How overflowing text should be handled.
35 enum TextOverflow { 35 enum TextOverflow {
36 - /// Span to the next page when possible.  
37 - span, 36 + /// Clip the overflowing text to fix its container.
  37 + clip,
38 38
39 /// Render overflowing text outside of its container. 39 /// Render overflowing text outside of its container.
40 visible, 40 visible,
  41 +
  42 + /// Span to the next page when possible.
  43 + span,
41 } 44 }
42 45
43 abstract class _Span { 46 abstract class _Span {
@@ -611,7 +614,9 @@ class RichText extends Widget with SpanningWidget { @@ -611,7 +614,9 @@ class RichText extends Widget with SpanningWidget {
611 614
612 final _context = _RichTextContext(); 615 final _context = _RichTextContext();
613 616
614 - final TextOverflow overflow; 617 + final TextOverflow? overflow;
  618 +
  619 + var _mustClip = false;
615 620
616 void _appendDecoration(bool append, _TextDecoration td) { 621 void _appendDecoration(bool append, _TextDecoration td) {
617 if (append && _decorations.isNotEmpty) { 622 if (append && _decorations.isNotEmpty) {
@@ -638,6 +643,7 @@ class RichText extends Widget with SpanningWidget { @@ -638,6 +643,7 @@ class RichText extends Widget with SpanningWidget {
638 final _maxLines = maxLines ?? theme.maxLines; 643 final _maxLines = maxLines ?? theme.maxLines;
639 _textAlign = textAlign ?? theme.textAlign; 644 _textAlign = textAlign ?? theme.textAlign;
640 final _textDirection = textDirection ?? Directionality.of(context); 645 final _textDirection = textDirection ?? Directionality.of(context);
  646 + final _overflow = this.overflow ?? theme.overflow;
641 647
642 final constraintWidth = constraints.hasBoundedWidth 648 final constraintWidth = constraints.hasBoundedWidth
643 ? constraints.maxWidth 649 ? constraints.maxWidth
@@ -728,6 +734,7 @@ class RichText extends Widget with SpanningWidget { @@ -728,6 +734,7 @@ class RichText extends Widget with SpanningWidget {
728 // One word Overflow, try to split it. 734 // One word Overflow, try to split it.
729 final pos = _splitWord(word, font, style, constraintWidth); 735 final pos = _splitWord(word, font, style, constraintWidth);
730 736
  737 + if (pos < word.length) {
731 words[index] = word.substring(0, pos); 738 words[index] = word.substring(0, pos);
732 words.insert(index + 1, word.substring(pos)); 739 words.insert(index + 1, word.substring(pos));
733 740
@@ -736,6 +743,7 @@ class RichText extends Widget with SpanningWidget { @@ -736,6 +743,7 @@ class RichText extends Widget with SpanningWidget {
736 continue; 743 continue;
737 } 744 }
738 } 745 }
  746 + }
739 747
740 final baseline = span.baseline! * textScaleFactor; 748 final baseline = span.baseline! * textScaleFactor;
741 final mt = tightBounds ? metrics.top : metrics.descent; 749 final mt = tightBounds ? metrics.top : metrics.descent;
@@ -910,7 +918,10 @@ class RichText extends Widget with SpanningWidget { @@ -910,7 +918,10 @@ class RichText extends Widget with SpanningWidget {
910 ..endOffset = offsetY - _context.startOffset 918 ..endOffset = offsetY - _context.startOffset
911 ..spanEnd = _spans.length; 919 ..spanEnd = _spans.length;
912 920
913 - if (this.overflow != TextOverflow.span) { 921 + if (_overflow != TextOverflow.span) {
  922 + if (_overflow != TextOverflow.visible) {
  923 + _mustClip = true;
  924 + }
914 return; 925 return;
915 } 926 }
916 927
@@ -949,6 +960,13 @@ class RichText extends Widget with SpanningWidget { @@ -949,6 +960,13 @@ class RichText extends Widget with SpanningWidget {
949 TextStyle? currentStyle; 960 TextStyle? currentStyle;
950 PdfColor? currentColor; 961 PdfColor? currentColor;
951 962
  963 + if (_mustClip) {
  964 + context.canvas
  965 + ..saveContext()
  966 + ..drawBox(box!)
  967 + ..clipPath();
  968 + }
  969 +
952 for (var decoration in _decorations) { 970 for (var decoration in _decorations) {
953 assert(() { 971 assert(() {
954 if (Document.debug && RichText.debug) { 972 if (Document.debug && RichText.debug) {
@@ -997,6 +1015,10 @@ class RichText extends Widget with SpanningWidget { @@ -997,6 +1015,10 @@ class RichText extends Widget with SpanningWidget {
997 _spans, 1015 _spans,
998 ); 1016 );
999 } 1017 }
  1018 +
  1019 + if (_mustClip) {
  1020 + context.canvas.restoreContext();
  1021 + }
1000 } 1022 }
1001 1023
1002 int _splitWord(String word, PdfFont font, TextStyle style, double maxWidth) { 1024 int _splitWord(String word, PdfFont font, TextStyle style, double maxWidth) {
@@ -1019,7 +1041,7 @@ class RichText extends Widget with SpanningWidget { @@ -1019,7 +1041,7 @@ class RichText extends Widget with SpanningWidget {
1019 pos = (low + high) ~/ 2; 1041 pos = (low + high) ~/ 2;
1020 } 1042 }
1021 1043
1022 - return pos; 1044 + return math.max(1, pos);
1023 } 1045 }
1024 1046
1025 @override 1047 @override
@@ -1050,7 +1072,7 @@ class Text extends RichText { @@ -1050,7 +1072,7 @@ class Text extends RichText {
1050 bool tightBounds = false, 1072 bool tightBounds = false,
1051 double textScaleFactor = 1.0, 1073 double textScaleFactor = 1.0,
1052 int? maxLines, 1074 int? maxLines,
1053 - TextOverflow overflow = TextOverflow.visible, 1075 + TextOverflow? overflow,
1054 }) : super( 1076 }) : super(
1055 text: TextSpan(text: text, style: style), 1077 text: TextSpan(text: text, style: style),
1056 textAlign: textAlign, 1078 textAlign: textAlign,
@@ -39,6 +39,7 @@ class ThemeData extends Inherited { @@ -39,6 +39,7 @@ class ThemeData extends Inherited {
39 TextStyle? tableCell, 39 TextStyle? tableCell,
40 bool? softWrap, 40 bool? softWrap,
41 TextAlign? textAlign, 41 TextAlign? textAlign,
  42 + TextOverflow? overflow,
42 int? maxLines, 43 int? maxLines,
43 IconThemeData? iconTheme, 44 IconThemeData? iconTheme,
44 }) { 45 }) {
@@ -56,6 +57,7 @@ class ThemeData extends Inherited { @@ -56,6 +57,7 @@ class ThemeData extends Inherited {
56 tableHeader: tableHeader, 57 tableHeader: tableHeader,
57 tableCell: tableCell, 58 tableCell: tableCell,
58 softWrap: softWrap, 59 softWrap: softWrap,
  60 + overflow: overflow,
59 textAlign: textAlign, 61 textAlign: textAlign,
60 maxLines: maxLines, 62 maxLines: maxLines,
61 iconTheme: iconTheme, 63 iconTheme: iconTheme,
@@ -75,6 +77,7 @@ class ThemeData extends Inherited { @@ -75,6 +77,7 @@ class ThemeData extends Inherited {
75 required this.tableHeader, 77 required this.tableHeader,
76 required this.tableCell, 78 required this.tableCell,
77 required this.softWrap, 79 required this.softWrap,
  80 + required this.overflow,
78 required this.textAlign, 81 required this.textAlign,
79 required this.iconTheme, 82 required this.iconTheme,
80 this.maxLines, 83 this.maxLines,
@@ -121,6 +124,7 @@ class ThemeData extends Inherited { @@ -121,6 +124,7 @@ class ThemeData extends Inherited {
121 fontSize: fontSize * 0.8, fontWeight: FontWeight.bold), 124 fontSize: fontSize * 0.8, fontWeight: FontWeight.bold),
122 tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8), 125 tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8),
123 softWrap: true, 126 softWrap: true,
  127 + overflow: TextOverflow.visible,
124 textAlign: TextAlign.left, 128 textAlign: TextAlign.left,
125 iconTheme: IconThemeData.fallback(icons), 129 iconTheme: IconThemeData.fallback(icons),
126 ); 130 );
@@ -143,6 +147,7 @@ class ThemeData extends Inherited { @@ -143,6 +147,7 @@ class ThemeData extends Inherited {
143 bool? softWrap, 147 bool? softWrap,
144 TextAlign? textAlign, 148 TextAlign? textAlign,
145 int? maxLines, 149 int? maxLines,
  150 + TextOverflow? overflow,
146 IconThemeData? iconTheme, 151 IconThemeData? iconTheme,
147 }) => 152 }) =>
148 ThemeData._( 153 ThemeData._(
@@ -158,6 +163,7 @@ class ThemeData extends Inherited { @@ -158,6 +163,7 @@ class ThemeData extends Inherited {
158 tableHeader: this.tableHeader.merge(tableHeader), 163 tableHeader: this.tableHeader.merge(tableHeader),
159 tableCell: this.tableCell.merge(tableCell), 164 tableCell: this.tableCell.merge(tableCell),
160 softWrap: softWrap ?? this.softWrap, 165 softWrap: softWrap ?? this.softWrap,
  166 + overflow: overflow ?? this.overflow,
161 textAlign: textAlign ?? this.textAlign, 167 textAlign: textAlign ?? this.textAlign,
162 maxLines: maxLines ?? this.maxLines, 168 maxLines: maxLines ?? this.maxLines,
163 iconTheme: iconTheme ?? this.iconTheme, 169 iconTheme: iconTheme ?? this.iconTheme,
@@ -183,6 +189,7 @@ class ThemeData extends Inherited { @@ -183,6 +189,7 @@ class ThemeData extends Inherited {
183 final TextAlign textAlign; 189 final TextAlign textAlign;
184 final bool softWrap; 190 final bool softWrap;
185 final int? maxLines; 191 final int? maxLines;
  192 + final TextOverflow overflow;
186 193
187 final IconThemeData iconTheme; 194 final IconThemeData iconTheme;
188 } 195 }
@@ -216,6 +223,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited { @@ -216,6 +223,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
216 required this.child, 223 required this.child,
217 this.textAlign, 224 this.textAlign,
218 this.softWrap = true, 225 this.softWrap = true,
  226 + this.overflow,
219 this.maxLines, 227 this.maxLines,
220 }) : assert(maxLines == null || maxLines > 0); 228 }) : assert(maxLines == null || maxLines > 0);
221 229
@@ -224,6 +232,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited { @@ -224,6 +232,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
224 TextAlign? textAlign, 232 TextAlign? textAlign,
225 bool? softWrap, 233 bool? softWrap,
226 int? maxLines, 234 int? maxLines,
  235 + TextOverflow? overflow,
227 required Widget child, 236 required Widget child,
228 }) { 237 }) {
229 return Builder( 238 return Builder(
@@ -234,6 +243,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited { @@ -234,6 +243,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
234 style: parent.defaultTextStyle.merge(style), 243 style: parent.defaultTextStyle.merge(style),
235 textAlign: textAlign ?? parent.textAlign, 244 textAlign: textAlign ?? parent.textAlign,
236 softWrap: softWrap ?? parent.softWrap, 245 softWrap: softWrap ?? parent.softWrap,
  246 + overflow: overflow ?? parent.overflow,
237 maxLines: maxLines ?? parent.maxLines, 247 maxLines: maxLines ?? parent.maxLines,
238 child: child, 248 child: child,
239 ); 249 );
@@ -251,12 +261,15 @@ class DefaultTextStyle extends StatelessWidget implements Inherited { @@ -251,12 +261,15 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
251 261
252 final int? maxLines; 262 final int? maxLines;
253 263
  264 + final TextOverflow? overflow;
  265 +
254 @override 266 @override
255 Widget build(Context context) { 267 Widget build(Context context) {
256 final theme = Theme.of(context).copyWith( 268 final theme = Theme.of(context).copyWith(
257 defaultTextStyle: style, 269 defaultTextStyle: style,
258 textAlign: textAlign, 270 textAlign: textAlign,
259 softWrap: softWrap, 271 softWrap: softWrap,
  272 + overflow: overflow,
260 maxLines: maxLines, 273 maxLines: maxLines,
261 ); 274 );
262 275