David PHAM-VAN

Improve TextOverflow support

... ... @@ -59,12 +59,14 @@ class Calendar extends StatelessWidget {
) {
return Container(
color: PdfColors.blue200,
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.only(top: 8, left: 8, bottom: 8),
child: Text(
DateFormat.EEEE().format(date),
style: const TextStyle(
fontSize: 15,
),
maxLines: 1,
overflow: TextOverflow.clip,
),
);
}
... ...
... ... @@ -101,6 +101,7 @@ Future<Uint8List> generateCertificate(
),
pw.Text(
data.name,
textAlign: pw.TextAlign.center,
style: pw.TextStyle(
fontWeight: pw.FontWeight.bold,
fontSize: 20,
... ...
... ... @@ -16,6 +16,7 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
... ... @@ -25,7 +26,14 @@ Future<Uint8List> generateDocument(
PdfPageFormat format, CustomData data) async {
final doc = pw.Document(pageMode: PdfPageMode.outlines);
final font1 = await rootBundle.load('assets/open-sans.ttf');
final font2 = await rootBundle.load('assets/open-sans-bold.ttf');
doc.addPage(pw.MultiPage(
theme: pw.ThemeData.withFont(
base: pw.Font.ttf(font1),
bold: pw.Font.ttf(font2),
),
pageFormat: format.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
orientation: pw.PageOrientation.portrait,
crossAxisAlignment: pw.CrossAxisAlignment.start,
... ...
... ... @@ -222,6 +222,7 @@ class Invoice {
child: pw.BarcodeWidget(
barcode: pw.Barcode.pdf417(),
data: 'Invoice# $invoiceNumber',
drawText: false,
),
),
pw.Text(
... ...
... ... @@ -55,136 +55,138 @@ Future<Uint8List> generateReport(
bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')),
);
// Top bar chart
final chart1 = pw.Chart(
left: pw.Container(
alignment: pw.Alignment.topCenter,
margin: const pw.EdgeInsets.only(right: 5, top: 10),
child: pw.Transform.rotateBox(
angle: pi / 2,
child: pw.Text('Amount'),
),
),
overlay: pw.ChartLegend(
position: const pw.Alignment(-.7, 1),
decoration: pw.BoxDecoration(
color: PdfColors.white,
border: pw.Border.all(
color: PdfColors.black,
width: .5,
),
),
),
grid: pw.CartesianGrid(
xAxis: pw.FixedAxis.fromStrings(
List<String>.generate(
dataTable.length, (index) => dataTable[index][0] as String),
marginStart: 30,
marginEnd: 30,
ticks: true,
),
yAxis: pw.FixedAxis(
[0, 100, 200, 300, 400, 500, 600, 700],
format: (v) => '\$$v',
divisions: true,
),
),
datasets: [
pw.BarDataSet(
color: PdfColors.blue100,
legend: tableHeaders[2],
width: 15,
offset: -10,
borderColor: baseColor,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][2] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
pw.BarDataSet(
color: PdfColors.amber100,
legend: tableHeaders[1],
width: 15,
offset: 10,
borderColor: PdfColors.amber,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][1] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
],
);
// Left curved line chart
final chart2 = pw.Chart(
right: pw.ChartLegend(),
grid: pw.CartesianGrid(
xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]),
yAxis: pw.FixedAxis(
[0, 200, 400, 600],
divisions: true,
),
),
datasets: [
pw.LineDataSet(
legend: 'Expense',
drawSurface: true,
isCurved: true,
drawPoints: false,
color: baseColor,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][2] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
],
);
// Data table
final table = pw.Table.fromTextArray(
border: null,
headers: tableHeaders,
data: List<List<dynamic>>.generate(
dataTable.length,
(index) => <dynamic>[
dataTable[index][0],
dataTable[index][1],
dataTable[index][2],
(dataTable[index][1] as num) - (dataTable[index][2] as num),
],
),
headerStyle: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
),
headerDecoration: pw.BoxDecoration(
color: baseColor,
),
rowDecoration: pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: baseColor,
width: .5,
),
),
),
cellAlignment: pw.Alignment.centerRight,
cellAlignments: {0: pw.Alignment.centerLeft},
);
// Add page to the PDF
document.addPage(
pw.Page(
pageFormat: pageFormat,
theme: theme,
build: (context) {
// Top bar chart
final chart1 = pw.Chart(
left: pw.Container(
alignment: pw.Alignment.topCenter,
margin: const pw.EdgeInsets.only(right: 5, top: 10),
child: pw.Transform.rotateBox(
angle: pi / 2,
child: pw.Text('Amount'),
),
),
overlay: pw.ChartLegend(
position: const pw.Alignment(-.7, 1),
decoration: pw.BoxDecoration(
color: PdfColors.white,
border: pw.Border.all(
color: PdfColors.black,
width: .5,
),
),
),
grid: pw.CartesianGrid(
xAxis: pw.FixedAxis.fromStrings(
List<String>.generate(
dataTable.length, (index) => dataTable[index][0] as String),
marginStart: 30,
marginEnd: 30,
ticks: true,
),
yAxis: pw.FixedAxis(
[0, 100, 200, 300, 400, 500, 600, 700],
format: (v) => '\$$v',
divisions: true,
),
),
datasets: [
pw.BarDataSet(
color: PdfColors.blue100,
legend: tableHeaders[2],
width: 15,
offset: -10,
borderColor: baseColor,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][2] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
pw.BarDataSet(
color: PdfColors.amber100,
legend: tableHeaders[1],
width: 15,
offset: 10,
borderColor: PdfColors.amber,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][1] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
],
);
// Left curved line chart
final chart2 = pw.Chart(
grid: pw.CartesianGrid(
xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]),
yAxis: pw.FixedAxis(
[0, 200, 400, 600],
divisions: true,
),
),
datasets: [
pw.LineDataSet(
drawSurface: true,
isCurved: true,
drawPoints: false,
color: baseColor,
data: List<pw.LineChartValue>.generate(
dataTable.length,
(i) {
final v = dataTable[i][2] as num;
return pw.LineChartValue(i.toDouble(), v.toDouble());
},
),
),
],
);
// Data table
final table = pw.Table.fromTextArray(
border: null,
headers: tableHeaders,
data: List<List<dynamic>>.generate(
dataTable.length,
(index) => <dynamic>[
dataTable[index][0],
dataTable[index][1],
dataTable[index][2],
(dataTable[index][1] as num) - (dataTable[index][2] as num),
],
),
headerStyle: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
),
headerDecoration: pw.BoxDecoration(
color: baseColor,
),
rowDecoration: pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: baseColor,
width: .5,
),
),
),
cellAlignment: pw.Alignment.centerRight,
cellAlignments: {0: pw.Alignment.centerLeft},
);
// Page layout
return pw.Column(
children: [
... ... @@ -196,17 +198,7 @@ Future<Uint8List> generateReport(
pw.Divider(thickness: 4),
pw.Expanded(flex: 3, child: chart1),
pw.Divider(),
pw.Expanded(
flex: 2,
child: pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(child: chart2),
pw.SizedBox(width: 10),
pw.Expanded(child: table),
],
),
),
pw.Expanded(flex: 2, child: chart2),
pw.SizedBox(height: 10),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
... ... @@ -275,34 +267,38 @@ Future<Uint8List> generateReport(
PdfColors.lime300,
];
return pw.SizedBox(
height: 400,
child: pw.Chart(
title: pw.Text(
'Expense breakdown',
style: pw.TextStyle(
color: baseColor,
fontSize: 20,
),
),
grid: pw.PieGrid(),
datasets: List<pw.Dataset>.generate(dataTable.length, (index) {
final data = dataTable[index];
final color = chartColors[index % chartColors.length];
final textColor =
color.luminance < 0.2 ? PdfColors.white : PdfColors.black;
return pw.Column(
children: [
pw.Flexible(
child: pw.Chart(
title: pw.Text(
'Expense breakdown',
style: pw.TextStyle(
color: baseColor,
fontSize: 20,
),
),
grid: pw.PieGrid(),
datasets: List<pw.Dataset>.generate(dataTable.length, (index) {
final data = dataTable[index];
final color = chartColors[index % chartColors.length];
final textColor =
color.luminance < 0.2 ? PdfColors.white : PdfColors.black;
final value = (data[2] as num).toDouble();
final pct = (value / expense * 100).round();
final value = (data[2] as num).toDouble();
final pct = (value / expense * 100).round();
return pw.PieDataSet(
legend: '${data[0]}\n$pct%',
value: value,
color: color,
legendStyle: pw.TextStyle(fontSize: 10, color: textColor),
);
}),
),
return pw.PieDataSet(
legend: '${data[0]}\n$pct%',
value: value,
color: color,
legendStyle: pw.TextStyle(fontSize: 10, color: textColor),
);
}),
),
),
table,
],
);
},
),
... ...
... ... @@ -141,6 +141,7 @@ Future<Uint8List> generateResume(PdfPageFormat format, CustomData data) async {
width: 60,
height: 60,
barcode: pw.Barcode.qrCode(),
drawText: false,
),
],
),
... ...
... ... @@ -11,6 +11,7 @@
- Fix textScalingFactor with lineSpacing
- Implement SpanningWidget on RichText
- Passthrough SpanningWidget on SingleChildWidget and StatelessWidget
- Improve TextOverflow support
## 3.2.0
... ...
... ... @@ -33,11 +33,14 @@ enum TextDirection { ltr, rtl }
/// How overflowing text should be handled.
enum TextOverflow {
/// Span to the next page when possible.
span,
/// Clip the overflowing text to fix its container.
clip,
/// Render overflowing text outside of its container.
visible,
/// Span to the next page when possible.
span,
}
abstract class _Span {
... ... @@ -611,7 +614,9 @@ class RichText extends Widget with SpanningWidget {
final _context = _RichTextContext();
final TextOverflow overflow;
final TextOverflow? overflow;
var _mustClip = false;
void _appendDecoration(bool append, _TextDecoration td) {
if (append && _decorations.isNotEmpty) {
... ... @@ -638,6 +643,7 @@ class RichText extends Widget with SpanningWidget {
final _maxLines = maxLines ?? theme.maxLines;
_textAlign = textAlign ?? theme.textAlign;
final _textDirection = textDirection ?? Directionality.of(context);
final _overflow = this.overflow ?? theme.overflow;
final constraintWidth = constraints.hasBoundedWidth
? constraints.maxWidth
... ... @@ -728,12 +734,14 @@ class RichText extends Widget with SpanningWidget {
// One word Overflow, try to split it.
final pos = _splitWord(word, font, style, constraintWidth);
words[index] = word.substring(0, pos);
words.insert(index + 1, word.substring(pos));
if (pos < word.length) {
words[index] = word.substring(0, pos);
words.insert(index + 1, word.substring(pos));
// Try again
index--;
continue;
// Try again
index--;
continue;
}
}
}
... ... @@ -910,7 +918,10 @@ class RichText extends Widget with SpanningWidget {
..endOffset = offsetY - _context.startOffset
..spanEnd = _spans.length;
if (this.overflow != TextOverflow.span) {
if (_overflow != TextOverflow.span) {
if (_overflow != TextOverflow.visible) {
_mustClip = true;
}
return;
}
... ... @@ -949,6 +960,13 @@ class RichText extends Widget with SpanningWidget {
TextStyle? currentStyle;
PdfColor? currentColor;
if (_mustClip) {
context.canvas
..saveContext()
..drawBox(box!)
..clipPath();
}
for (var decoration in _decorations) {
assert(() {
if (Document.debug && RichText.debug) {
... ... @@ -997,6 +1015,10 @@ class RichText extends Widget with SpanningWidget {
_spans,
);
}
if (_mustClip) {
context.canvas.restoreContext();
}
}
int _splitWord(String word, PdfFont font, TextStyle style, double maxWidth) {
... ... @@ -1019,7 +1041,7 @@ class RichText extends Widget with SpanningWidget {
pos = (low + high) ~/ 2;
}
return pos;
return math.max(1, pos);
}
@override
... ... @@ -1050,7 +1072,7 @@ class Text extends RichText {
bool tightBounds = false,
double textScaleFactor = 1.0,
int? maxLines,
TextOverflow overflow = TextOverflow.visible,
TextOverflow? overflow,
}) : super(
text: TextSpan(text: text, style: style),
textAlign: textAlign,
... ...
... ... @@ -39,6 +39,7 @@ class ThemeData extends Inherited {
TextStyle? tableCell,
bool? softWrap,
TextAlign? textAlign,
TextOverflow? overflow,
int? maxLines,
IconThemeData? iconTheme,
}) {
... ... @@ -56,6 +57,7 @@ class ThemeData extends Inherited {
tableHeader: tableHeader,
tableCell: tableCell,
softWrap: softWrap,
overflow: overflow,
textAlign: textAlign,
maxLines: maxLines,
iconTheme: iconTheme,
... ... @@ -75,6 +77,7 @@ class ThemeData extends Inherited {
required this.tableHeader,
required this.tableCell,
required this.softWrap,
required this.overflow,
required this.textAlign,
required this.iconTheme,
this.maxLines,
... ... @@ -121,6 +124,7 @@ class ThemeData extends Inherited {
fontSize: fontSize * 0.8, fontWeight: FontWeight.bold),
tableCell: defaultStyle.copyWith(fontSize: fontSize * 0.8),
softWrap: true,
overflow: TextOverflow.visible,
textAlign: TextAlign.left,
iconTheme: IconThemeData.fallback(icons),
);
... ... @@ -143,6 +147,7 @@ class ThemeData extends Inherited {
bool? softWrap,
TextAlign? textAlign,
int? maxLines,
TextOverflow? overflow,
IconThemeData? iconTheme,
}) =>
ThemeData._(
... ... @@ -158,6 +163,7 @@ class ThemeData extends Inherited {
tableHeader: this.tableHeader.merge(tableHeader),
tableCell: this.tableCell.merge(tableCell),
softWrap: softWrap ?? this.softWrap,
overflow: overflow ?? this.overflow,
textAlign: textAlign ?? this.textAlign,
maxLines: maxLines ?? this.maxLines,
iconTheme: iconTheme ?? this.iconTheme,
... ... @@ -183,6 +189,7 @@ class ThemeData extends Inherited {
final TextAlign textAlign;
final bool softWrap;
final int? maxLines;
final TextOverflow overflow;
final IconThemeData iconTheme;
}
... ... @@ -216,6 +223,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
required this.child,
this.textAlign,
this.softWrap = true,
this.overflow,
this.maxLines,
}) : assert(maxLines == null || maxLines > 0);
... ... @@ -224,6 +232,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
TextAlign? textAlign,
bool? softWrap,
int? maxLines,
TextOverflow? overflow,
required Widget child,
}) {
return Builder(
... ... @@ -234,6 +243,7 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
style: parent.defaultTextStyle.merge(style),
textAlign: textAlign ?? parent.textAlign,
softWrap: softWrap ?? parent.softWrap,
overflow: overflow ?? parent.overflow,
maxLines: maxLines ?? parent.maxLines,
child: child,
);
... ... @@ -251,12 +261,15 @@ class DefaultTextStyle extends StatelessWidget implements Inherited {
final int? maxLines;
final TextOverflow? overflow;
@override
Widget build(Context context) {
final theme = Theme.of(context).copyWith(
defaultTextStyle: style,
textAlign: textAlign,
softWrap: softWrap,
overflow: overflow,
maxLines: maxLines,
);
... ...