David PHAM-VAN

Improve Theme override

... ... @@ -17,6 +17,7 @@
library widget;
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
... ... @@ -28,6 +29,7 @@ part 'widgets/container.dart';
part 'widgets/content.dart';
part 'widgets/document.dart';
part 'widgets/flex.dart';
part 'widgets/font.dart';
part 'widgets/geometry.dart';
part 'widgets/grid_view.dart';
part 'widgets/image.dart';
... ...
... ... @@ -17,53 +17,70 @@
part of widget;
class Header extends StatelessWidget {
Header({this.level = 1, this.text, this.child})
Header(
{this.level = 1,
this.text,
this.child,
this.decoration,
this.margin,
this.padding,
this.textStyle})
: assert(level >= 0 && level <= 5);
final String text;
final Widget child;
final int level;
final BoxDecoration decoration;
final EdgeInsets margin;
final EdgeInsets padding;
final TextStyle textStyle;
@override
Widget build(Context context) {
BoxDecoration _decoration;
EdgeInsets _margin;
EdgeInsets _padding;
double _textSize;
BoxDecoration _decoration = decoration;
EdgeInsets _margin = margin;
EdgeInsets _padding = padding;
TextStyle _textStyle = textStyle;
switch (level) {
case 0:
_margin = const EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm);
_padding = const EdgeInsets.only(bottom: 1.0 * PdfPageFormat.mm);
_decoration =
_margin ??= const EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm);
_padding ??= const EdgeInsets.only(bottom: 1.0 * PdfPageFormat.mm);
_decoration ??=
const BoxDecoration(border: BoxBorder(bottom: true, width: 1.0));
_textSize = 2.0;
_textStyle ??= Theme.of(context).header0;
break;
case 1:
_margin = const EdgeInsets.only(
_margin ??= const EdgeInsets.only(
top: 3.0 * PdfPageFormat.mm, bottom: 5.0 * PdfPageFormat.mm);
_decoration =
_decoration ??=
const BoxDecoration(border: BoxBorder(bottom: true, width: 0.2));
_textSize = 1.5;
_textStyle ??= Theme.of(context).header1;
break;
case 2:
_margin = const EdgeInsets.only(
_margin ??= const EdgeInsets.only(
top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
_textSize = 1.4;
_textStyle ??= Theme.of(context).header2;
break;
case 3:
_margin = const EdgeInsets.only(
_margin ??= const EdgeInsets.only(
top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
_textSize = 1.3;
_textStyle ??= Theme.of(context).header3;
break;
case 4:
_margin = const EdgeInsets.only(
_margin ??= const EdgeInsets.only(
top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
_textSize = 1.2;
_textStyle ??= Theme.of(context).header4;
break;
case 5:
_margin = const EdgeInsets.only(
_margin ??= const EdgeInsets.only(
top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
_textSize = 1.1;
_textStyle ??= Theme.of(context).header5;
break;
}
return Container(
... ... @@ -71,53 +88,96 @@ class Header extends StatelessWidget {
margin: _margin,
padding: _padding,
decoration: _decoration,
child: child ?? Text(text, textScaleFactor: _textSize),
child: child ?? Text(text, style: _textStyle),
);
}
}
class Paragraph extends StatelessWidget {
Paragraph({this.text});
Paragraph(
{this.text,
this.textAlign = TextAlign.justify,
this.style,
this.margin = const EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm),
this.padding});
final String text;
final TextAlign textAlign;
final TextStyle style;
final EdgeInsets margin;
final EdgeInsets padding;
@override
Widget build(Context context) {
return Container(
margin: const EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm),
margin: margin,
padding: padding,
child: Text(
text,
textAlign: TextAlign.justify,
style: Theme.of(context).paragraphStyle,
textAlign: textAlign,
style: style ?? Theme.of(context).paragraphStyle,
),
);
}
}
class Bullet extends StatelessWidget {
Bullet({this.text});
Bullet(
{this.text,
this.textAlign = TextAlign.left,
this.style,
this.margin = const EdgeInsets.only(bottom: 2.0 * PdfPageFormat.mm),
this.padding,
this.bulletSize = 2.0 * PdfPageFormat.mm,
this.bulletMargin = const EdgeInsets.only(
top: 1.5 * PdfPageFormat.mm,
left: 5.0 * PdfPageFormat.mm,
right: 2.0 * PdfPageFormat.mm,
),
this.bulletShape = BoxShape.circle,
this.bulletColor = PdfColor.black});
final String text;
final TextAlign textAlign;
final TextStyle style;
final EdgeInsets margin;
final EdgeInsets padding;
final EdgeInsets bulletMargin;
final double bulletSize;
final BoxShape bulletShape;
final PdfColor bulletColor;
@override
Widget build(Context context) {
return Container(
margin: const EdgeInsets.only(bottom: 2.0 * PdfPageFormat.mm),
margin: margin,
padding: padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 2.0 * PdfPageFormat.mm,
height: 2.0 * PdfPageFormat.mm,
margin: const EdgeInsets.only(
top: 0.5 * PdfPageFormat.mm,
left: 5.0 * PdfPageFormat.mm,
right: 2.0 * PdfPageFormat.mm,
),
decoration: const BoxDecoration(
color: PdfColor.black, shape: BoxShape.circle),
width: bulletSize,
height: bulletSize,
margin: bulletMargin,
decoration:
BoxDecoration(color: bulletColor, shape: bulletShape),
),
Expanded(child: Text(text, style: Theme.of(context).bulletStyle))
Expanded(
child: Text(text,
textAlign: textAlign,
style: Theme.of(context).bulletStyle))
]));
}
}
... ...
... ... @@ -85,8 +85,7 @@ class Page extends BasePage {
final BoxConstraints constraints = BoxConstraints(
maxWidth: pageFormat.width, maxHeight: pageFormat.height);
final Theme calculatedTheme =
theme ?? document.theme ?? Theme(document.document);
final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();
final Map<Type, Inherited> inherited = <Type, Inherited>{};
inherited[calculatedTheme.runtimeType] = calculatedTheme;
final Context context =
... ... @@ -142,9 +141,10 @@ class MultiPage extends Page {
this.crossAxisAlignment = CrossAxisAlignment.start,
this.header,
this.footer,
Theme theme,
EdgeInsets margin})
: _buildList = build,
super(pageFormat: pageFormat, margin: margin);
super(pageFormat: pageFormat, margin: margin, theme: theme);
final BuildListCallback _buildList;
... ... @@ -164,8 +164,7 @@ class MultiPage extends Page {
maxWidth: pageFormat.width, maxHeight: pageFormat.height);
final BoxConstraints childConstraints =
BoxConstraints(maxWidth: constraints.maxWidth - margin.horizontal);
final Theme calculatedTheme =
theme ?? document.theme ?? Theme(document.document);
final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();
final Map<Type, Inherited> inherited = <Type, Inherited>{};
inherited[calculatedTheme.runtimeType] = calculatedTheme;
Context context;
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of widget;
enum Type1Fonts {
courier,
courierBold,
courierBoldOblique,
courierOblique,
helvetica,
helveticaBold,
helveticaBoldOblique,
helveticaOblique,
times,
timesBold,
timesBoldItalic,
timesItalic,
symbol,
zapfDingbats
}
/// Lazy font declaration, registers the font in the document only if needed.
/// Tries to register a font only once
class Font {
Font() : font = null;
Font.type1(this.font) : assert(font != null);
factory Font.courier() => Font.type1(Type1Fonts.courier);
factory Font.courierBold() => Font.type1(Type1Fonts.courierBold);
factory Font.courierBoldOblique() =>
Font.type1(Type1Fonts.courierBoldOblique);
factory Font.courierOblique() => Font.type1(Type1Fonts.courierOblique);
factory Font.helvetica() => Font.type1(Type1Fonts.helvetica);
factory Font.helveticaBold() => Font.type1(Type1Fonts.helveticaBold);
factory Font.helveticaBoldOblique() =>
Font.type1(Type1Fonts.helveticaBoldOblique);
factory Font.helveticaOblique() => Font.type1(Type1Fonts.helveticaOblique);
factory Font.times() => Font.type1(Type1Fonts.times);
factory Font.timesBold() => Font.type1(Type1Fonts.timesBold);
factory Font.timesBoldItalic() => Font.type1(Type1Fonts.timesBoldItalic);
factory Font.timesItalic() => Font.type1(Type1Fonts.timesItalic);
factory Font.symbol() => Font.type1(Type1Fonts.symbol);
factory Font.zapfDingbats() => Font.type1(Type1Fonts.zapfDingbats);
final Type1Fonts font;
@protected
PdfFont buildFont(PdfDocument pdfDocument) {
const Map<Type1Fonts, String> type1Map = <Type1Fonts, String>{
Type1Fonts.courier: 'Courier',
Type1Fonts.courierBold: 'Courier-Bold',
Type1Fonts.courierBoldOblique: 'Courier-BoldOblique',
Type1Fonts.courierOblique: 'Courier-Oblique',
Type1Fonts.helvetica: 'Helvetica',
Type1Fonts.helveticaBold: 'Helvetica-Bold',
Type1Fonts.helveticaBoldOblique: 'Helvetica-BoldOblique',
Type1Fonts.helveticaOblique: 'Helvetica-Oblique',
Type1Fonts.times: 'Times-Roman',
Type1Fonts.timesBold: 'Times-Bold',
Type1Fonts.timesBoldItalic: 'Times-BoldItalic',
Type1Fonts.timesItalic: 'Times-Italic',
Type1Fonts.symbol: 'Symbol',
Type1Fonts.zapfDingbats: 'ZapfDingbats'
};
final String fontName = type1Map[font];
final PdfFont existing = pdfDocument.fonts.firstWhere(
(PdfFont font) => font.subtype == '/Type1' && font.fontName == fontName,
orElse: () => null,
);
if (existing != null) {
return existing;
}
switch (font) {
case Type1Fonts.courier:
return PdfFont.courier(pdfDocument);
case Type1Fonts.courierBold:
return PdfFont.courierBold(pdfDocument);
case Type1Fonts.courierBoldOblique:
return PdfFont.courierBoldOblique(pdfDocument);
case Type1Fonts.courierOblique:
return PdfFont.courierOblique(pdfDocument);
case Type1Fonts.helvetica:
return PdfFont.helvetica(pdfDocument);
case Type1Fonts.helveticaBold:
return PdfFont.helveticaBold(pdfDocument);
case Type1Fonts.helveticaBoldOblique:
return PdfFont.helveticaBoldOblique(pdfDocument);
case Type1Fonts.helveticaOblique:
return PdfFont.helveticaOblique(pdfDocument);
case Type1Fonts.times:
return PdfFont.times(pdfDocument);
case Type1Fonts.timesBold:
return PdfFont.timesBold(pdfDocument);
case Type1Fonts.timesBoldItalic:
return PdfFont.timesBoldItalic(pdfDocument);
case Type1Fonts.timesItalic:
return PdfFont.timesItalic(pdfDocument);
case Type1Fonts.symbol:
return PdfFont.symbol(pdfDocument);
case Type1Fonts.zapfDingbats:
return PdfFont.zapfDingbats(pdfDocument);
}
return PdfFont.helvetica(pdfDocument);
}
PdfFont _pdfFont;
PdfFont getFont(Context context) {
if (_pdfFont == null) {
final PdfDocument pdfDocument = context.page.pdfDocument;
_pdfFont = buildFont(pdfDocument);
}
return _pdfFont;
}
}
class TtfFont extends Font {
TtfFont(this.data);
final ByteData data;
@override
PdfFont buildFont(PdfDocument pdfDocument) {
return PdfTtfFont(pdfDocument, data);
}
}
... ...
... ... @@ -16,69 +16,6 @@
part of widget;
@immutable
class TextStyle {
const TextStyle({
this.color = PdfColor.black,
@required this.font,
this.fontSize = _defaultFontSize,
this.letterSpacing = 1.0,
this.wordSpacing = 1.0,
this.lineSpacing = 0.0,
this.height = 1.0,
this.background,
}) : assert(font != null),
assert(color != null);
final PdfColor color;
final PdfFont font;
// font height, in pdf unit
final double fontSize;
static const double _defaultFontSize = 12.0 * PdfPageFormat.point;
// spacing between letters, 1.0 being natural spacing
final double letterSpacing;
// spacing between lines, in pdf unit
final double lineSpacing;
// spacing between words, 1.0 being natural spacing
final double wordSpacing;
final double height;
final PdfColor background;
TextStyle copyWith({
PdfColor color,
PdfFont font,
double fontSize,
double letterSpacing,
double wordSpacing,
double lineSpacing,
double height,
PdfColor background,
}) {
return TextStyle(
color: color ?? this.color,
font: font ?? this.font,
fontSize: fontSize ?? this.fontSize,
letterSpacing: letterSpacing ?? this.letterSpacing,
wordSpacing: wordSpacing ?? this.wordSpacing,
lineSpacing: lineSpacing ?? this.lineSpacing,
height: height ?? this.height,
background: background ?? this.background,
);
}
@override
String toString() =>
'TextStyle(color:$color font:$font letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)';
}
enum TextAlign { left, right, center, justify }
class _Word {
... ... @@ -235,9 +172,10 @@ class RichText extends Widget {
}
final TextStyle style = span.style ?? defaultstyle;
final PdfFont font = style.font.getFont(context);
final PdfFontMetrics space =
style.font.stringMetrics(' ') * (style.fontSize * textScaleFactor);
font.stringMetrics(' ') * (style.fontSize * textScaleFactor);
for (String word in span.text.split(' ')) {
if (word.isEmpty) {
... ... @@ -246,7 +184,7 @@ class RichText extends Widget {
}
final PdfFontMetrics metrics =
style.font.stringMetrics(word) * (style.fontSize * textScaleFactor);
font.stringMetrics(word) * (style.fontSize * textScaleFactor);
if (offsetX + metrics.width > constraintWidth) {
if (wCount == 0) {
... ... @@ -326,7 +264,7 @@ class RichText extends Widget {
}
context.canvas.drawString(
currentStyle.font,
currentStyle.font.getFont(context),
currentStyle.fontSize * textScaleFactor,
word.text,
box.x + word.offset.x,
... ...
... ... @@ -16,55 +16,129 @@
part of widget;
class Theme extends Inherited {
Theme(this.document);
final PdfDocument document;
static Theme of(Context context) {
return context.inherited[Theme];
@immutable
class TextStyle {
const TextStyle({
this.color = PdfColor.black,
@required this.font,
this.fontSize = _defaultFontSize,
this.letterSpacing = 1.0,
this.wordSpacing = 1.0,
this.lineSpacing = 0.0,
this.height = 1.0,
this.background,
}) : assert(font != null),
assert(color != null);
final PdfColor color;
final Font font;
// font height, in pdf unit
final double fontSize;
static const double _defaultFontSize = 12.0 * PdfPageFormat.point;
// spacing between letters, 1.0 being natural spacing
final double letterSpacing;
// spacing between lines, in pdf unit
final double lineSpacing;
// spacing between words, 1.0 being natural spacing
final double wordSpacing;
final double height;
final PdfColor background;
TextStyle copyWith({
PdfColor color,
Font font,
double fontSize,
double letterSpacing,
double wordSpacing,
double lineSpacing,
double height,
PdfColor background,
}) {
return TextStyle(
color: color ?? this.color,
font: font ?? this.font,
fontSize: fontSize ?? this.fontSize,
letterSpacing: letterSpacing ?? this.letterSpacing,
wordSpacing: wordSpacing ?? this.wordSpacing,
lineSpacing: lineSpacing ?? this.lineSpacing,
height: height ?? this.height,
background: background ?? this.background,
);
}
TextStyle _defaultTextStyle;
@override
String toString() =>
'TextStyle(color:$color font:$font letterSpacing:$letterSpacing wordSpacing:$wordSpacing lineSpacing:$lineSpacing height:$height background:$background)';
}
TextStyle get defaultTextStyle {
_defaultTextStyle ??= TextStyle(font: PdfFont.helvetica(document));
return _defaultTextStyle;
@immutable
class Theme extends Inherited {
Theme({
@required this.defaultTextStyle,
@required this.defaultTextStyleBold,
@required this.paragraphStyle,
@required this.header0,
@required this.header1,
@required this.header2,
@required this.header3,
@required this.header4,
@required this.header5,
@required this.bulletStyle,
@required this.tableHeader,
@required this.tableCell,
});
factory Theme.withFont(Font baseFont, Font baseFontBold) {
final TextStyle defaultTextStyle = TextStyle(font: baseFont);
final TextStyle defaultTextStyleBold = TextStyle(font: baseFontBold);
final double fontSize = defaultTextStyle.fontSize;
return Theme(
defaultTextStyle: defaultTextStyle,
defaultTextStyleBold: defaultTextStyleBold,
paragraphStyle: defaultTextStyle.copyWith(lineSpacing: 5.0),
bulletStyle: defaultTextStyle.copyWith(lineSpacing: 5.0),
header0: defaultTextStyleBold.copyWith(fontSize: fontSize * 2.0),
header1: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.5),
header2: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.4),
header3: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.3),
header4: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.2),
header5: defaultTextStyleBold.copyWith(fontSize: fontSize * 1.1),
tableHeader: defaultTextStyleBold,
tableCell: defaultTextStyle);
}
TextStyle _defaultTextStyleBold;
factory Theme.base() =>
Theme.withFont(Font.helvetica(), Font.helveticaBold());
TextStyle get defaultTextStyleBold {
_defaultTextStyleBold ??=
defaultTextStyle.copyWith(font: PdfFont.helveticaBold(document));
return _defaultTextStyleBold;
static Theme of(Context context) {
return context.inherited[Theme];
}
TextStyle _paragraphStyle;
final TextStyle defaultTextStyle;
TextStyle get paragraphStyle {
_paragraphStyle ??= defaultTextStyle.copyWith(lineSpacing: 5.0);
return _paragraphStyle;
}
final TextStyle defaultTextStyleBold;
TextStyle _bulletStyle;
final TextStyle paragraphStyle;
TextStyle get bulletStyle {
_bulletStyle ??= defaultTextStyle.copyWith(lineSpacing: 5.0);
return _bulletStyle;
}
final TextStyle header0;
final TextStyle header1;
final TextStyle header2;
final TextStyle header3;
final TextStyle header4;
final TextStyle header5;
TextStyle _tableHeader;
final TextStyle bulletStyle;
TextStyle get tableHeader {
_tableHeader ??= defaultTextStyleBold;
return _tableHeader;
}
TextStyle _tableCell;
final TextStyle tableHeader;
TextStyle get tableCell {
_tableCell ??= defaultTextStyle;
return _tableCell;
}
final TextStyle tableCell;
}
... ...
... ... @@ -16,7 +16,6 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
... ... @@ -28,8 +27,7 @@ void main() {
final Document pdf = Document();
final TextStyle symbol =
TextStyle(font: PdfFont.zapfDingbats(pdf.document));
final TextStyle symbol = TextStyle(font: Font.zapfDingbats());
final List<int> imData = zlib.decode(base64.decode(
'eJz7//8/w388uOTCT6a4Ez96Q47++I+OI479mEVALyNU7z9seuNP/mAm196Ekz8YR+0dWHtBmJC9S+7/Zog89iMIKLYaHQPVJGLTD7MXpDfq+I9goNhPdPPDjv3YlnH6Jye6+2H21l/6yeB/4HsSDr1bQXrRwq8HqHcGyF6QXp9933N0tn/7Y7vn+/9gLPaih0PDlV9MIAzVm6ez7dsfzW3f/oMwzAx0e7FhoJutdbcj9MKw9frnL2J2POfBpxeEg478YLba/X0Wsl6lBXf+s0bP/s8ePXeWePJCvPEJNYMRZIYWSO/cq/9Z/Nv+M4bO+M8YDjFDJGkhzvSE7A6jRTdnsQR2wfXCMLHuMC5byyidvGgWE5JeZDOIcYdR+TpmkBno+mFmAAC+DGhl'));
... ... @@ -80,8 +78,9 @@ void main() {
border: BoxBorder(top: true, width: 1.0)),
child: Text("That's all Folks!",
textAlign: TextAlign.center,
style: Theme.of(context).defaultTextStyle.copyWith(
font: PdfFont.timesBoldItalic(pdf.document)),
style: Theme.of(context)
.defaultTextStyle
.copyWith(font: Font.timesBoldItalic()),
textScaleFactor: 3.0)),
])));
... ... @@ -98,10 +97,6 @@ void main() {
children: List<Widget>.generate(
9, (int n) => FittedBox(child: Text('${n + 1}')))))));
final Uint8List robotoData = File('open-sans.ttf').readAsBytesSync();
final PdfTtfFont roboto =
PdfTtfFont(pdf.document, robotoData.buffer.asByteData());
pdf.addPage(MultiPage(
pageFormat: const PdfPageFormat(400.0, 200.0),
margin: const EdgeInsets.all(10.0),
... ...
... ... @@ -69,3 +69,10 @@ Future<PdfImage> pdfImageFromImageProvider(
stream.addListener(listener, onError: errorListener);
return completer.future;
}
/// Loads a font from an asset bundle key. If used multiple times with the same font name,
/// it will be included multiple times in the pdf file
Future<TtfFont> fontFromAssetBundle(String key, AssetBundle bundle) async {
final ByteData data = await bundle.load(key);
return TtfFont(data);
}
... ...