David PHAM-VAN

Add a Flutter like widget system

  1 +# 1.3.0
  2 +* Add a Flutter like widget system
  3 +
1 # 1.2.0 4 # 1.2.0
2 * Change license to Apache 2.0 5 * Change license to Apache 2.0
3 * Improve PdfRect 6 * Improve PdfRect
1 # Pdf creation library for dart / flutter 1 # Pdf creation library for dart / flutter
2 2
3 -This is a low-level Pdf creation library. 3 +This library is divided in two parts:
  4 +
  5 +* a low-level Pdf creation library that takes care of the pdf bits generation.
  6 +* a Widgets system similar to Flutter's, for easy high-level Pdf creation.
  7 +
4 It can create a full multi-pages document with graphics, 8 It can create a full multi-pages document with graphics,
5 -images and text using TrueType fonts. 9 +images and text using TrueType fonts. With the ease of use you already know.
6 10
7 > Use `printing` package <https://pub.dartlang.org/packages/printing> 11 > Use `printing` package <https://pub.dartlang.org/packages/printing>
8 > for full flutter print and share operation. 12 > for full flutter print and share operation.
9 13
10 -The coordinate system is using the internal Pdf system:  
11 - * (0.0, 0.0) is bottom-left 14 +The coordinate system is using the internal Pdf unit:
12 * 1.0 is defined as 1 / 72.0 inch 15 * 1.0 is defined as 1 / 72.0 inch
13 * you can use the constants for centimeters, milimeters and inch defined in PdfPageFormat 16 * you can use the constants for centimeters, milimeters and inch defined in PdfPageFormat
14 17
15 Example: 18 Example:
16 ```dart 19 ```dart
17 -final pdf = PdfDocument();  
18 -final page = PdfPage(pdf, pageFormat: PdfPageFormat.letter);  
19 -final g = page.getGraphics();  
20 -final font = PdfFont(pdf);  
21 -  
22 -g.setColor(PdfColor(0.0, 1.0, 1.0));  
23 -g.drawRect(50.0, 30.0, 100.0, 50.0);  
24 -g.fillPath();  
25 -  
26 -g.setColor(PdfColor(0.3, 0.3, 0.3));  
27 -g.drawString(font, 12.0, "Hello World!", 5.0 * PdfPageFormat.mm, 300.0);  
28 -  
29 -var file = File('file.pdf');  
30 -file.writeAsBytesSync(pdf.save()); 20 +final pdf = Document()
  21 + ..addPage(Page(
  22 + pageFormat: PdfPageFormat.a4,
  23 + build: (Context context) {
  24 + return Center(
  25 + child: Text("Hello World"),
  26 + ); // Center
  27 + })); // Page
31 ``` 28 ```
32 29
33 To load an image it is possible to use the dart library [image](https://pub.dartlang.org/packages/image): 30 To load an image it is possible to use the dart library [image](https://pub.dartlang.org/packages/image):
1 import 'dart:io'; 1 import 'dart:io';
2 2
3 import 'package:pdf/pdf.dart'; 3 import 'package:pdf/pdf.dart';
  4 +import 'package:pdf/widgets.dart';
4 5
5 void main() { 6 void main() {
6 - final pdf = PdfDocument(deflate: zlib.encode);  
7 - final page = PdfPage(pdf, pageFormat: PdfPageFormat.letter);  
8 - final g = page.getGraphics();  
9 - final font = g.defaultFont;  
10 - final top = page.pageFormat.height; 7 + final pdf = Document(deflate: zlib.encode);
11 8
12 - g.setColor(PdfColor(0.0, 1.0, 1.0));  
13 - g.drawRect(50.0 * PdfPageFormat.mm, top - 80.0 * PdfPageFormat.mm,  
14 - 100.0 * PdfPageFormat.mm, 50.0 * PdfPageFormat.mm);  
15 - g.fillPath();  
16 -  
17 - g.setColor(PdfColor(0.3, 0.3, 0.3));  
18 - g.drawString(font, 12.0, "Hello World!", 10.0 * PdfPageFormat.mm,  
19 - top - 10.0 * PdfPageFormat.mm); 9 + pdf.addPage(MultiPage(
  10 + pageFormat:
  11 + PdfPageFormat.letter.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
  12 + crossAxisAlignment: CrossAxisAlignment.start,
  13 + header: (Context context) {
  14 + if (context.pageNumber == 1) return null;
  15 + return Container(
  16 + alignment: Alignment.centerRight,
  17 + margin: EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
  18 + padding: EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
  19 + decoration: BoxDecoration(
  20 + border:
  21 + BoxBorder(bottom: true, width: 0.5, color: PdfColor.grey)),
  22 + child: Text("Portable Document Format",
  23 + style: Theme.of(context)
  24 + .defaultTextStyle
  25 + .copyWith(color: PdfColor.grey)));
  26 + },
  27 + footer: (Context context) {
  28 + return Container(
  29 + alignment: Alignment.centerRight,
  30 + margin: EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
  31 + child: Text("Page ${context.pageNumber}",
  32 + style: Theme.of(context)
  33 + .defaultTextStyle
  34 + .copyWith(color: PdfColor.grey)));
  35 + },
  36 + build: (Context context) => <Widget>[
  37 + Header(
  38 + level: 0,
  39 + child: Row(
  40 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  41 + children: <Widget>[
  42 + Text("Portable Document Format", textScaleFactor: 2.0),
  43 + PdfLogo()
  44 + ])),
  45 + Paragraph(
  46 + text:
  47 + "The Portable Document Format (PDF) is a file format developed by Adobe in the 1990s to present documents, including text formatting and images, in a manner independent of application software, hardware, and operating systems. Based on the PostScript language, each PDF file encapsulates a complete description of a fixed-layout flat document, including the text, fonts, vector graphics, raster images and other information needed to display it. PDF was standardized as an open format, ISO 32000, in 2008, and no longer requires any royalties for its implementation."),
  48 + Paragraph(
  49 + text:
  50 + "Today, PDF files may contain a variety of content besides flat text and graphics including logical structuring elements, interactive elements such as annotations and form-fields, layers, rich media (including video content) and three dimensional objects using U3D or PRC, and various other data formats.[citation needed] The PDF specification also provides for encryption and digital signatures, file attachments and metadata to enable workflows requiring these features."),
  51 + Header(level: 1, text: "History and standardization"),
  52 + Paragraph(
  53 + text:
  54 + "Adobe Systems made the PDF specification available free of charge in 1993. In the early years PDF was popular mainly in desktop publishing workflows, and competed with a variety of formats such as DjVu, Envoy, Common Ground Digital Paper, Farallon Replica and even Adobe's own PostScript format."),
  55 + Paragraph(
  56 + text:
  57 + "PDF was a proprietary format controlled by Adobe until it was released as an open standard on July 1, 2008, and published by the International Organization for Standardization as ISO 32000-1:2008, at which time control of the specification passed to an ISO Committee of volunteer industry experts. In 2008, Adobe published a Public Patent License to ISO 32000-1 granting royalty-free rights for all patents owned by Adobe that are necessary to make, use, sell, and distribute PDF compliant implementations."),
  58 + Paragraph(
  59 + text:
  60 + "PDF 1.7, the sixth edition of the PDF specification that became ISO 32000-1, includes some proprietary technologies defined only by Adobe, such as Adobe XML Forms Architecture (XFA) and JavaScript extension for Acrobat, which are referenced by ISO 32000-1 as normative and indispensable for the full implementation of the ISO 32000-1 specification. These proprietary technologies are not standardized and their specification is published only on Adobe's website. Many of them are also not supported by popular third-party implementations of PDF."),
  61 + Paragraph(
  62 + text:
  63 + "On July 28, 2017, ISO 32000-2:2017 (PDF 2.0) was published. ISO 32000-2 does not include any proprietary technologies as normative references."),
  64 + Header(level: 1, text: "Technical foundations"),
  65 + Paragraph(text: "The PDF combines three technologies:"),
  66 + Bullet(
  67 + text:
  68 + "A subset of the PostScript page description programming language, for generating the layout and graphics."),
  69 + Bullet(
  70 + text:
  71 + "A font-embedding/replacement system to allow fonts to travel with the documents."),
  72 + Bullet(
  73 + text:
  74 + "A structured storage system to bundle these elements and any associated content into a single file, with data compression where appropriate."),
  75 + Header(level: 2, text: "PostScript"),
  76 + Paragraph(
  77 + text:
  78 + "PostScript is a page description language run in an interpreter to generate an image, a process requiring many resources. It can handle graphics and standard features of programming languages such as if and loop commands. PDF is largely based on PostScript but simplified to remove flow control features like these, while graphics commands such as lineto remain."),
  79 + Paragraph(
  80 + text:
  81 + "Often, the PostScript-like PDF code is generated from a source PostScript file. The graphics commands that are output by the PostScript code are collected and tokenized. Any files, graphics, or fonts to which the document refers also are collected. Then, everything is compressed to a single file. Therefore, the entire PostScript world (fonts, layout, measurements) remains intact."),
  82 + Column(
  83 + crossAxisAlignment: CrossAxisAlignment.start,
  84 + children: <Widget>[
  85 + Paragraph(
  86 + text:
  87 + "As a document format, PDF has several advantages over PostScript:"),
  88 + Bullet(
  89 + text:
  90 + "PDF contains tokenized and interpreted results of the PostScript source code, for direct correspondence between changes to items in the PDF page description and changes to the resulting page appearance."),
  91 + Bullet(
  92 + text:
  93 + "PDF (from version 1.4) supports graphic transparency; PostScript does not."),
  94 + Bullet(
  95 + text:
  96 + "PostScript is an interpreted programming language with an implicit global state, so instructions accompanying the description of one page can affect the appearance of any following page. Therefore, all preceding pages in a PostScript document must be processed to determine the correct appearance of a given page, whereas each page in a PDF document is unaffected by the others. As a result, PDF viewers allow the user to quickly jump to the final pages of a long document, whereas a PostScript viewer needs to process all pages sequentially before being able to display the destination page (unless the optional PostScript Document Structuring Conventions have been carefully complied with)."),
  97 + ]),
  98 + Header(level: 1, text: "Content"),
  99 + Paragraph(
  100 + text:
  101 + "A PDF file is often a combination of vector graphics, text, and bitmap graphics. The basic types of content in a PDF are:"),
  102 + Bullet(
  103 + text:
  104 + "Text stored as content streams (i.e., not encoded in plain text)"),
  105 + Bullet(
  106 + text:
  107 + "Vector graphics for illustrations and designs that consist of shapes and lines"),
  108 + Bullet(
  109 + text:
  110 + "Raster graphics for photographs and other types of image"),
  111 + Bullet(text: "Multimedia objects in the document"),
  112 + Paragraph(
  113 + text:
  114 + "In later PDF revisions, a PDF document can also support links (inside document or web page), forms, JavaScript (initially available as plugin for Acrobat 3.0), or any other types of embedded contents that can be handled using plug-ins."),
  115 + Paragraph(
  116 + text:
  117 + "PDF 1.6 supports interactive 3D documents embedded in the PDF - 3D drawings can be embedded using U3D or PRC and various other data formats."),
  118 + Paragraph(
  119 + text:
  120 + "Two PDF files that look similar on a computer screen may be of very different sizes. For example, a high resolution raster image takes more space than a low resolution one. Typically higher resolution is needed for printing documents than for displaying them on screen. Other things that may increase the size of a file is embedding full fonts, especially for Asiatic scripts, and storing text as graphics. "),
  121 + Header(level: 1, text: "File formats and Adobe Acrobat versions"),
  122 + Paragraph(
  123 + text:
  124 + "The PDF file format has changed several times, and continues to evolve, along with the release of new versions of Adobe Acrobat. There have been nine versions of PDF and the corresponding version of the software:"),
  125 + Table.fromTextArray(context: context, data: [
  126 + ["Date", "PDF Version", "Acrobat Version"],
  127 + ["1993", "PDF 1.0", "Acrobat 1"],
  128 + ["1994", "PDF 1.1", "Acrobat 2"],
  129 + ["1996", "PDF 1.2", "Acrobat 3"],
  130 + ["1999", "PDF 1.3", "Acrobat 4"],
  131 + ["2001", "PDF 1.4", "Acrobat 5"],
  132 + ["2003", "PDF 1.5", "Acrobat 6"],
  133 + ["2005", "PDF 1.6", "Acrobat 7"],
  134 + ["2006", "PDF 1.7", "Acrobat 8"],
  135 + ["2008", "PDF 1.7", "Acrobat 9"],
  136 + ["2009", "PDF 1.7", "Acrobat 9.1"],
  137 + ["2010", "PDF 1.7", "Acrobat X"],
  138 + ["2012", "PDF 1.7", "Acrobat XI"],
  139 + ["2017", "PDF 2.0", "Acrobat DC"],
  140 + ]),
  141 + Padding(padding: EdgeInsets.all(10)),
  142 + Paragraph(
  143 + text:
  144 + "Text is available under the Creative Commons Attribution Share Alike License.")
  145 + ]));
20 146
21 var file = File('example.pdf'); 147 var file = File('example.pdf');
22 - file.writeAsBytesSync(pdf.save()); 148 + file.writeAsBytesSync(pdf.document.save());
23 } 149 }
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +library widget;
  18 +
  19 +import 'dart:math' as math;
  20 +
  21 +import 'package:meta/meta.dart';
  22 +import 'package:pdf/pdf.dart';
  23 +import 'package:vector_math/vector_math_64.dart';
  24 +
  25 +part 'widgets/basic.dart';
  26 +part 'widgets/clip.dart';
  27 +part 'widgets/container.dart';
  28 +part 'widgets/content.dart';
  29 +part 'widgets/document.dart';
  30 +part 'widgets/flex.dart';
  31 +part 'widgets/geometry.dart';
  32 +part 'widgets/grid_view.dart';
  33 +part 'widgets/image.dart';
  34 +part 'widgets/placeholders.dart';
  35 +part 'widgets/table.dart';
  36 +part 'widgets/text.dart';
  37 +part 'widgets/theme.dart';
  38 +part 'widgets/widget.dart';
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +enum BoxFit { fill, contain, cover, fitWidth, fitHeight, none, scaleDown }
  20 +
  21 +class LimitedBox extends SingleChildWidget {
  22 + LimitedBox({
  23 + this.maxWidth = double.infinity,
  24 + this.maxHeight = double.infinity,
  25 + Widget child,
  26 + }) : assert(maxWidth != null && maxWidth >= 0.0),
  27 + assert(maxHeight != null && maxHeight >= 0.0),
  28 + super(child: child);
  29 +
  30 + final double maxWidth;
  31 +
  32 + final double maxHeight;
  33 +
  34 + BoxConstraints _limitConstraints(BoxConstraints constraints) {
  35 + return BoxConstraints(
  36 + minWidth: constraints.minWidth,
  37 + maxWidth: constraints.hasBoundedWidth
  38 + ? constraints.maxWidth
  39 + : constraints.constrainWidth(maxWidth),
  40 + minHeight: constraints.minHeight,
  41 + maxHeight: constraints.hasBoundedHeight
  42 + ? constraints.maxHeight
  43 + : constraints.constrainHeight(maxHeight));
  44 + }
  45 +
  46 + @override
  47 + void layout(Context context, BoxConstraints constraints,
  48 + {parentUsesSize = false}) {
  49 + PdfPoint size;
  50 + if (child != null) {
  51 + child.layout(context, _limitConstraints(constraints),
  52 + parentUsesSize: true);
  53 + size = constraints.constrain(child.box.size);
  54 + } else {
  55 + size = _limitConstraints(constraints).constrain(PdfPoint.zero);
  56 + }
  57 + box = PdfRect.fromPoints(PdfPoint.zero, size);
  58 + }
  59 +}
  60 +
  61 +class Padding extends SingleChildWidget {
  62 + Padding({
  63 + @required this.padding,
  64 + Widget child,
  65 + }) : assert(padding != null),
  66 + super(child: child);
  67 +
  68 + final EdgeInsets padding;
  69 +
  70 + @override
  71 + void layout(Context context, BoxConstraints constraints,
  72 + {parentUsesSize = false}) {
  73 + if (child != null) {
  74 + final childConstraints = constraints.deflate(padding);
  75 + child.layout(context, childConstraints, parentUsesSize: parentUsesSize);
  76 + box = constraints.constrainRect(
  77 + width: child.box.width + padding.horizontal,
  78 + height: child.box.height + padding.vertical);
  79 + } else {
  80 + box = constraints.constrainRect(
  81 + width: padding.horizontal, height: padding.vertical);
  82 + }
  83 + }
  84 +
  85 + @override
  86 + void debugPaint(Context context) {
  87 + context.canvas
  88 + ..setFillColor(PdfColor.lime)
  89 + ..moveTo(box.x, box.y)
  90 + ..lineTo(box.right, box.y)
  91 + ..lineTo(box.right, box.top)
  92 + ..lineTo(box.x, box.top)
  93 + ..moveTo(box.x + padding.left, box.y + padding.bottom)
  94 + ..lineTo(box.x + padding.left, box.top - padding.top)
  95 + ..lineTo(box.right - padding.right, box.top - padding.top)
  96 + ..lineTo(box.right - padding.right, box.y + padding.bottom)
  97 + ..fillPath();
  98 + }
  99 +
  100 + @override
  101 + void paint(Context context) {
  102 + assert(() {
  103 + if (Document.debug) debugPaint(context);
  104 + return true;
  105 + }());
  106 +
  107 + if (child != null) {
  108 + final mat = Matrix4.identity();
  109 + mat.translate(box.x + padding.left, box.y + padding.bottom);
  110 + context.canvas
  111 + ..saveContext()
  112 + ..setTransform(mat);
  113 + child.paint(context);
  114 + context.canvas.restoreContext();
  115 + }
  116 + }
  117 +}
  118 +
  119 +class Transform extends SingleChildWidget {
  120 + Transform({
  121 + @required this.transform,
  122 + this.origin,
  123 + this.alignment,
  124 + Widget child,
  125 + }) : assert(transform != null),
  126 + super(child: child);
  127 +
  128 + /// Creates a widget that transforms its child using a rotation around the
  129 + /// center.
  130 + Transform.rotate({
  131 + @required double angle,
  132 + this.origin,
  133 + this.alignment = Alignment.center,
  134 + Widget child,
  135 + }) : transform = Matrix4.rotationZ(angle),
  136 + super(child: child);
  137 +
  138 + /// Creates a widget that transforms its child using a translation.
  139 + Transform.translate({
  140 + @required PdfPoint offset,
  141 + Widget child,
  142 + }) : transform = Matrix4.translationValues(offset.x, offset.y, 0.0),
  143 + origin = null,
  144 + alignment = null,
  145 + super(child: child);
  146 +
  147 + /// Creates a widget that scales its child uniformly.
  148 + Transform.scale({
  149 + @required double scale,
  150 + this.origin,
  151 + this.alignment = Alignment.center,
  152 + Widget child,
  153 + }) : transform = Matrix4.diagonal3Values(scale, scale, 1.0),
  154 + super(child: child);
  155 +
  156 + /// The matrix to transform the child by during painting.
  157 + final Matrix4 transform;
  158 +
  159 + /// The origin of the coordinate system
  160 + final PdfPoint origin;
  161 +
  162 + /// The alignment of the origin, relative to the size of the box.
  163 + final Alignment alignment;
  164 +
  165 + Matrix4 get _effectiveTransform {
  166 + if (origin == null && alignment == null) return transform;
  167 + final Matrix4 result = Matrix4.identity();
  168 + if (origin != null) result.translate(origin.x, origin.y);
  169 + PdfPoint translation;
  170 + if (alignment != null) {
  171 + translation = alignment.alongSize(box.size);
  172 + result.translate(translation.x, translation.y);
  173 + }
  174 + result.multiply(transform);
  175 + if (alignment != null) result.translate(-translation.x, -translation.y);
  176 + if (origin != null) result.translate(-origin.x, -origin.y);
  177 + return result;
  178 + }
  179 +
  180 + @override
  181 + void paint(Context context) {
  182 + assert(() {
  183 + if (Document.debug) debugPaint(context);
  184 + return true;
  185 + }());
  186 +
  187 + if (child != null) {
  188 + final mat = _effectiveTransform;
  189 + context.canvas
  190 + ..saveContext()
  191 + ..setTransform(mat);
  192 + child.paint(context);
  193 + context.canvas.restoreContext();
  194 + }
  195 + }
  196 +}
  197 +
  198 +/// A widget that aligns its child within itself and optionally sizes itself
  199 +/// based on the child's size.
  200 +class Align extends SingleChildWidget {
  201 + Align(
  202 + {this.alignment = Alignment.center,
  203 + this.widthFactor,
  204 + this.heightFactor,
  205 + Widget child})
  206 + : assert(alignment != null),
  207 + assert(widthFactor == null || widthFactor >= 0.0),
  208 + assert(heightFactor == null || heightFactor >= 0.0),
  209 + super(child: child);
  210 +
  211 + /// How to align the child.
  212 + final Alignment alignment;
  213 +
  214 + /// If non-null, sets its width to the child's width multiplied by this factor.
  215 + final double widthFactor;
  216 +
  217 + /// If non-null, sets its height to the child's height multiplied by this factor.
  218 + final double heightFactor;
  219 +
  220 + @override
  221 + void layout(Context context, BoxConstraints constraints,
  222 + {parentUsesSize = false}) {
  223 + final bool shrinkWrapWidth =
  224 + widthFactor != null || constraints.maxWidth == double.infinity;
  225 + final bool shrinkWrapHeight =
  226 + heightFactor != null || constraints.maxHeight == double.infinity;
  227 +
  228 + if (child != null) {
  229 + child.layout(context, constraints.loosen(), parentUsesSize: true);
  230 +
  231 + box = constraints.constrainRect(
  232 + width: shrinkWrapWidth
  233 + ? child.box.width * (widthFactor ?? 1.0)
  234 + : double.infinity,
  235 + height: shrinkWrapHeight
  236 + ? child.box.height * (heightFactor ?? 1.0)
  237 + : double.infinity);
  238 +
  239 + child.box = alignment.inscribe(child.box.size, box);
  240 + } else {
  241 + box = constraints.constrainRect(
  242 + width: shrinkWrapWidth ? 0.0 : double.infinity,
  243 + height: shrinkWrapHeight ? 0.0 : double.infinity);
  244 + }
  245 + }
  246 +}
  247 +
  248 +/// A widget that imposes additional constraints on its child.
  249 +class ConstrainedBox extends SingleChildWidget {
  250 + ConstrainedBox({@required this.constraints, Widget child})
  251 + : assert(constraints != null),
  252 + super(child: child);
  253 +
  254 + /// The additional constraints to impose on the child.
  255 + final BoxConstraints constraints;
  256 +
  257 + @override
  258 + void layout(Context context, BoxConstraints constraints,
  259 + {parentUsesSize = false}) {
  260 + if (child != null) {
  261 + child.layout(context, this.constraints.enforce(constraints),
  262 + parentUsesSize: true);
  263 + box = child.box;
  264 + } else {
  265 + box = PdfRect.fromPoints(PdfPoint.zero,
  266 + this.constraints.enforce(constraints).constrain(PdfPoint.zero));
  267 + }
  268 + }
  269 +}
  270 +
  271 +class Center extends Align {
  272 + Center({double widthFactor, double heightFactor, Widget child})
  273 + : super(
  274 + widthFactor: widthFactor, heightFactor: heightFactor, child: child);
  275 +}
  276 +
  277 +/// Scales and positions its child within itself according to [fit].
  278 +class FittedBox extends SingleChildWidget {
  279 + FittedBox({
  280 + this.fit = BoxFit.contain,
  281 + this.alignment = Alignment.center,
  282 + Widget child,
  283 + }) : assert(fit != null),
  284 + assert(alignment != null),
  285 + super(child: child);
  286 +
  287 + /// How to inscribe the child into the space allocated during layout.
  288 + final BoxFit fit;
  289 +
  290 + /// How to align the child within its parent's bounds.
  291 + final Alignment alignment;
  292 +
  293 + @override
  294 + void layout(Context context, BoxConstraints constraints,
  295 + {parentUsesSize = false}) {
  296 + PdfPoint size;
  297 + if (child != null) {
  298 + child.layout(context, const BoxConstraints(), parentUsesSize: true);
  299 + size = constraints
  300 + .constrainSizeAndAttemptToPreserveAspectRatio(child.box.size);
  301 + } else {
  302 + size = constraints.smallest;
  303 + }
  304 + box = PdfRect.fromPoints(PdfPoint.zero, size);
  305 + }
  306 +
  307 + @override
  308 + void paint(Context context) {
  309 + if (child != null) {
  310 + final PdfPoint childSize = child.box.size;
  311 + final FittedSizes sizes = applyBoxFit(fit, childSize, box.size);
  312 + final double scaleX = sizes.destination.x / sizes.source.x;
  313 + final double scaleY = sizes.destination.y / sizes.source.y;
  314 + final PdfRect sourceRect = alignment.inscribe(
  315 + sizes.source, PdfRect.fromPoints(PdfPoint.zero, childSize));
  316 + final PdfRect destinationRect =
  317 + alignment.inscribe(sizes.destination, box);
  318 +
  319 + final mat =
  320 + Matrix4.translationValues(destinationRect.x, destinationRect.y, 0.0)
  321 + ..scale(scaleX, scaleY, 1.0)
  322 + ..translate(-sourceRect.x, -sourceRect.y);
  323 +
  324 + context.canvas
  325 + ..saveContext()
  326 + ..drawRect(box.x, box.y, box.width, box.height)
  327 + ..clipPath()
  328 + ..setTransform(mat);
  329 + child.paint(context);
  330 + context.canvas.restoreContext();
  331 + }
  332 + }
  333 +}
  334 +
  335 +class AspectRatio extends SingleChildWidget {
  336 + AspectRatio({@required this.aspectRatio, Widget child})
  337 + : assert(aspectRatio != null),
  338 + super(child: child);
  339 +
  340 + /// The aspect ratio to attempt to use.
  341 + final double aspectRatio;
  342 +
  343 + PdfPoint _applyAspectRatio(BoxConstraints constraints) {
  344 + if (constraints.isTight) return constraints.smallest;
  345 +
  346 + double width = constraints.maxWidth;
  347 + double height;
  348 +
  349 + if (width.isFinite) {
  350 + height = width / aspectRatio;
  351 + } else {
  352 + height = constraints.maxHeight;
  353 + width = height * aspectRatio;
  354 + }
  355 +
  356 + if (width > constraints.maxWidth) {
  357 + width = constraints.maxWidth;
  358 + height = width / aspectRatio;
  359 + }
  360 +
  361 + if (height > constraints.maxHeight) {
  362 + height = constraints.maxHeight;
  363 + width = height * aspectRatio;
  364 + }
  365 +
  366 + if (width < constraints.minWidth) {
  367 + width = constraints.minWidth;
  368 + height = width / aspectRatio;
  369 + }
  370 +
  371 + if (height < constraints.minHeight) {
  372 + height = constraints.minHeight;
  373 + width = height * aspectRatio;
  374 + }
  375 +
  376 + return constraints.constrain(PdfPoint(width, height));
  377 + }
  378 +
  379 + @override
  380 + void layout(Context context, BoxConstraints constraints,
  381 + {parentUsesSize = false}) {
  382 + box = PdfRect.fromPoints(PdfPoint.zero, _applyAspectRatio(constraints));
  383 + if (child != null)
  384 + child.layout(context,
  385 + BoxConstraints.tightFor(width: box.width, height: box.height));
  386 + }
  387 +}
  388 +
  389 +typedef CustomPainter(PdfGraphics canvas, PdfPoint size);
  390 +
  391 +class CustomPaint extends SingleChildWidget {
  392 + CustomPaint(
  393 + {this.painter,
  394 + this.foregroundPainter,
  395 + this.size = PdfPoint.zero,
  396 + Widget child})
  397 + : super(child: child);
  398 +
  399 + final CustomPainter painter;
  400 + final CustomPainter foregroundPainter;
  401 + final PdfPoint size;
  402 +
  403 + @override
  404 + void layout(Context context, BoxConstraints constraints,
  405 + {parentUsesSize = false}) {
  406 + if (child != null) {
  407 + child.layout(context, constraints.tighten(width: size.x, height: size.y),
  408 + parentUsesSize: parentUsesSize);
  409 + box = child.box;
  410 + } else {
  411 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.constrain(size));
  412 + }
  413 + }
  414 +
  415 + @override
  416 + void paint(Context context) {
  417 + assert(() {
  418 + if (Document.debug) debugPaint(context);
  419 + return true;
  420 + }());
  421 +
  422 + final mat = Matrix4.identity();
  423 + mat.translate(box.x, box.y);
  424 + context.canvas
  425 + ..saveContext()
  426 + ..setTransform(mat);
  427 + if (painter != null) painter(context.canvas, box.size);
  428 + if (child != null) child.paint(context);
  429 + if (foregroundPainter != null) foregroundPainter(context.canvas, box.size);
  430 + context.canvas.restoreContext();
  431 + }
  432 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class ClipRect extends SingleChildWidget {
  20 + ClipRect({Widget child}) : super(child: child);
  21 +
  22 + @protected
  23 + void debugPaint(Context context) {
  24 + context.canvas
  25 + ..setStrokeColor(PdfColor.deepPurple)
  26 + ..drawRect(box.x, box.y, box.width, box.height)
  27 + ..strokePath();
  28 + }
  29 +
  30 + @override
  31 + void paint(Context context) {
  32 + assert(() {
  33 + if (Document.debug) debugPaint(context);
  34 + return true;
  35 + }());
  36 +
  37 + if (child != null) {
  38 + final mat = Matrix4.identity();
  39 + mat.translate(box.x, box.y);
  40 + context.canvas
  41 + ..saveContext()
  42 + ..drawRect(box.x, box.y, box.width, box.height)
  43 + ..clipPath()
  44 + ..setTransform(mat);
  45 + child.paint(context);
  46 + context.canvas.restoreContext();
  47 + }
  48 + }
  49 +}
  50 +
  51 +class ClipRRect extends SingleChildWidget {
  52 + ClipRRect({
  53 + Widget child,
  54 + this.horizontalRadius,
  55 + this.verticalRadius,
  56 + }) : super(child: child);
  57 +
  58 + final double horizontalRadius;
  59 + final double verticalRadius;
  60 +
  61 + @protected
  62 + void debugPaint(Context context) {
  63 + context.canvas
  64 + ..setStrokeColor(PdfColor.deepPurple)
  65 + ..drawRRect(
  66 + box.x, box.y, box.width, box.height, horizontalRadius, verticalRadius)
  67 + ..strokePath();
  68 + }
  69 +
  70 + @override
  71 + void paint(Context context) {
  72 + assert(() {
  73 + if (Document.debug) debugPaint(context);
  74 + return true;
  75 + }());
  76 +
  77 + if (child != null) {
  78 + final mat = Matrix4.identity();
  79 + mat.translate(box.x, box.y);
  80 + context.canvas
  81 + ..saveContext()
  82 + ..drawRRect(box.x, box.y, box.width, box.height, horizontalRadius,
  83 + verticalRadius)
  84 + ..clipPath()
  85 + ..setTransform(mat);
  86 + child.paint(context);
  87 + context.canvas.restoreContext();
  88 + }
  89 + }
  90 +}
  91 +
  92 +class ClipOval extends SingleChildWidget {
  93 + ClipOval({Widget child}) : super(child: child);
  94 +
  95 + @protected
  96 + void debugPaint(Context context) {
  97 + final rx = box.width / 2.0;
  98 + final ry = box.height / 2.0;
  99 +
  100 + context.canvas
  101 + ..setStrokeColor(PdfColor.deepPurple)
  102 + ..drawEllipse(box.x + rx, box.y + ry, rx, ry)
  103 + ..strokePath();
  104 + }
  105 +
  106 + @override
  107 + void paint(Context context) {
  108 + assert(() {
  109 + if (Document.debug) debugPaint(context);
  110 + return true;
  111 + }());
  112 +
  113 + final rx = box.width / 2.0;
  114 + final ry = box.height / 2.0;
  115 +
  116 + if (child != null) {
  117 + final mat = Matrix4.identity();
  118 + mat.translate(box.x, box.y);
  119 + context.canvas
  120 + ..saveContext()
  121 + ..drawEllipse(box.x + rx, box.y + ry, rx, ry)
  122 + ..clipPath()
  123 + ..setTransform(mat);
  124 + child.paint(context);
  125 + context.canvas.restoreContext();
  126 + }
  127 + }
  128 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +enum DecorationPosition { background, foreground }
  20 +
  21 +@immutable
  22 +class BoxBorder {
  23 + const BoxBorder(
  24 + {this.left = false,
  25 + this.top = false,
  26 + this.right = false,
  27 + this.bottom = false,
  28 + this.color = PdfColor.black,
  29 + this.width = 1.0})
  30 + : assert(color != null),
  31 + assert(width != null),
  32 + assert(width >= 0.0);
  33 +
  34 + final bool top;
  35 + final bool bottom;
  36 + final bool left;
  37 + final bool right;
  38 +
  39 + /// The color of the
  40 + final PdfColor color;
  41 +
  42 + /// The width of the
  43 + final double width;
  44 +
  45 + void paintBorders(Context context, PdfRect box) {
  46 + assert(box.x != null);
  47 + assert(box.y != null);
  48 + assert(box.width != null);
  49 + assert(box.height != null);
  50 +
  51 + if (top || bottom || left || right) {
  52 + context.canvas
  53 + ..setStrokeColor(color)
  54 + ..setLineWidth(width);
  55 +
  56 + if (top) {
  57 + context.canvas.drawLine(box.x, box.top, box.right, box.top);
  58 + }
  59 +
  60 + if (right) {
  61 + if (!top) {
  62 + context.canvas.moveTo(box.right, box.top);
  63 + }
  64 + context.canvas.lineTo(box.right, box.y);
  65 + }
  66 +
  67 + if (bottom) {
  68 + if (!right) {
  69 + context.canvas.moveTo(box.right, box.y);
  70 + }
  71 + context.canvas.lineTo(box.x, box.y);
  72 + }
  73 +
  74 + if (left) {
  75 + if (!bottom) {
  76 + context.canvas.moveTo(box.x, box.y);
  77 + context.canvas.lineTo(box.x, box.top);
  78 + } else if (right && top) {
  79 + context.canvas.closePath();
  80 + } else
  81 + context.canvas.lineTo(box.x, box.top);
  82 + }
  83 +
  84 + context.canvas.strokePath();
  85 + }
  86 + }
  87 +}
  88 +
  89 +enum BoxShape { circle, rectangle }
  90 +
  91 +@immutable
  92 +class BoxDecoration {
  93 + const BoxDecoration(
  94 + {this.color,
  95 + this.border,
  96 + this.borderRadius,
  97 + this.shape = BoxShape.rectangle});
  98 +
  99 + /// The color to fill in the background of the box.
  100 + final PdfColor color;
  101 + final BoxBorder border;
  102 + final double borderRadius;
  103 + final BoxShape shape;
  104 +
  105 + void paintBackground(Context context, PdfRect box) {
  106 + assert(box.x != null);
  107 + assert(box.y != null);
  108 + assert(box.width != null);
  109 + assert(box.height != null);
  110 +
  111 + if (color != null) {
  112 + switch (shape) {
  113 + case BoxShape.rectangle:
  114 + if (borderRadius == null)
  115 + context.canvas.drawRect(box.x, box.y, box.width, box.height);
  116 + else
  117 + context.canvas.drawRRect(box.x, box.y, box.width, box.height,
  118 + borderRadius, borderRadius);
  119 +
  120 + break;
  121 + case BoxShape.circle:
  122 + context.canvas.drawEllipse(box.x + box.width / 2.0,
  123 + box.y + box.height / 2.0, box.width / 2.0, box.height / 2.0);
  124 + break;
  125 + }
  126 + context.canvas
  127 + ..setFillColor(color)
  128 + ..fillPath();
  129 + }
  130 + }
  131 +}
  132 +
  133 +class DecoratedBox extends SingleChildWidget {
  134 + DecoratedBox(
  135 + {@required this.decoration,
  136 + this.position = DecorationPosition.background,
  137 + Widget child})
  138 + : assert(decoration != null),
  139 + assert(position != null),
  140 + super(child: child);
  141 +
  142 + /// What decoration to paint.
  143 + final BoxDecoration decoration;
  144 +
  145 + /// Whether to paint the box decoration behind or in front of the child.
  146 + final DecorationPosition position;
  147 +
  148 + @override
  149 + void paint(Context context) {
  150 + if (position == DecorationPosition.background) {
  151 + decoration.paintBackground(context, box);
  152 + decoration.border?.paintBorders(context, box);
  153 + }
  154 + super.paint(context);
  155 + if (position == DecorationPosition.foreground) {
  156 + decoration.paintBackground(context, box);
  157 + decoration.border?.paintBorders(context, box);
  158 + }
  159 + }
  160 +}
  161 +
  162 +class Container extends StatelessWidget {
  163 + Container({
  164 + this.alignment,
  165 + this.padding,
  166 + PdfColor color,
  167 + BoxDecoration decoration,
  168 + this.foregroundDecoration,
  169 + double width,
  170 + double height,
  171 + BoxConstraints constraints,
  172 + this.margin,
  173 + this.transform,
  174 + this.child,
  175 + }) : assert(
  176 + color == null || decoration == null,
  177 + 'Cannot provide both a color and a decoration\n'
  178 + 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'),
  179 + decoration =
  180 + decoration ?? (color != null ? BoxDecoration(color: color) : null),
  181 + constraints = (width != null || height != null)
  182 + ? constraints?.tighten(width: width, height: height) ??
  183 + BoxConstraints.tightFor(width: width, height: height)
  184 + : constraints,
  185 + super();
  186 +
  187 + final Widget child;
  188 +
  189 + final Alignment alignment;
  190 +
  191 + final EdgeInsets padding;
  192 +
  193 + /// The decoration to paint behind the [child].
  194 + final BoxDecoration decoration;
  195 +
  196 + /// The decoration to paint in front of the [child].
  197 + final BoxDecoration foregroundDecoration;
  198 +
  199 + /// Additional constraints to apply to the child.
  200 + final BoxConstraints constraints;
  201 +
  202 + /// Empty space to surround the [decoration] and [child].
  203 + final EdgeInsets margin;
  204 +
  205 + /// The transformation matrix to apply before painting the container.
  206 + final Matrix4 transform;
  207 +
  208 + @override
  209 + Widget build(Context context) {
  210 + Widget current = child;
  211 +
  212 + if (child == null && (constraints == null || !constraints.isTight)) {
  213 + current = LimitedBox(
  214 + maxWidth: 0.0,
  215 + maxHeight: 0.0,
  216 + child: ConstrainedBox(constraints: const BoxConstraints.expand()));
  217 + }
  218 +
  219 + if (alignment != null)
  220 + current = Align(alignment: alignment, child: current);
  221 +
  222 + if (padding != null) current = Padding(padding: padding, child: current);
  223 +
  224 + if (decoration != null)
  225 + current = DecoratedBox(decoration: decoration, child: current);
  226 +
  227 + if (foregroundDecoration != null) {
  228 + current = DecoratedBox(
  229 + decoration: foregroundDecoration,
  230 + position: DecorationPosition.foreground,
  231 + child: current);
  232 + }
  233 +
  234 + if (constraints != null)
  235 + current = ConstrainedBox(constraints: constraints, child: current);
  236 +
  237 + if (margin != null) current = Padding(padding: margin, child: current);
  238 +
  239 + if (transform != null)
  240 + current = Transform(transform: transform, child: current);
  241 +
  242 + return current;
  243 + }
  244 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class Header extends StatelessWidget {
  20 + final String text;
  21 + final Widget child;
  22 + final int level;
  23 +
  24 + Header({this.level = 1, this.text, this.child})
  25 + : assert(level >= 0 && level <= 5);
  26 +
  27 + @override
  28 + Widget build(Context context) {
  29 + BoxDecoration _decoration;
  30 + EdgeInsets _margin;
  31 + EdgeInsets _padding;
  32 + double _textSize;
  33 + switch (level) {
  34 + case 0:
  35 + _margin = EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm);
  36 + _padding = EdgeInsets.only(bottom: 1.0 * PdfPageFormat.mm);
  37 + _decoration =
  38 + BoxDecoration(border: BoxBorder(bottom: true, width: 1.0));
  39 + _textSize = 2.0;
  40 + break;
  41 + case 1:
  42 + _margin = EdgeInsets.only(
  43 + top: 3.0 * PdfPageFormat.mm, bottom: 5.0 * PdfPageFormat.mm);
  44 + _decoration =
  45 + BoxDecoration(border: BoxBorder(bottom: true, width: 0.2));
  46 + _textSize = 1.5;
  47 + break;
  48 + case 2:
  49 + _margin = EdgeInsets.only(
  50 + top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
  51 + _textSize = 1.4;
  52 + break;
  53 + case 3:
  54 + _margin = EdgeInsets.only(
  55 + top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
  56 + _textSize = 1.3;
  57 + break;
  58 + case 4:
  59 + _margin = EdgeInsets.only(
  60 + top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
  61 + _textSize = 1.2;
  62 + break;
  63 + case 5:
  64 + _margin = EdgeInsets.only(
  65 + top: 2.0 * PdfPageFormat.mm, bottom: 4.0 * PdfPageFormat.mm);
  66 + _textSize = 1.1;
  67 + break;
  68 + }
  69 + return Container(
  70 + alignment: Alignment.topLeft,
  71 + margin: _margin,
  72 + padding: _padding,
  73 + decoration: _decoration,
  74 + child: child ?? Text(text, textScaleFactor: _textSize),
  75 + );
  76 + }
  77 +}
  78 +
  79 +class Paragraph extends StatelessWidget {
  80 + final String text;
  81 +
  82 + Paragraph({this.text});
  83 +
  84 + @override
  85 + Widget build(Context context) {
  86 + return Container(
  87 + margin: EdgeInsets.only(bottom: 5.0 * PdfPageFormat.mm),
  88 + child: Text(
  89 + text,
  90 + textAlign: TextAlign.justify,
  91 + style: Theme.of(context).paragraphStyle,
  92 + ),
  93 + );
  94 + }
  95 +}
  96 +
  97 +class Bullet extends StatelessWidget {
  98 + final String text;
  99 +
  100 + Bullet({this.text});
  101 +
  102 + @override
  103 + Widget build(Context context) {
  104 + return Container(
  105 + margin: EdgeInsets.only(bottom: 2.0 * PdfPageFormat.mm),
  106 + child: Row(
  107 + crossAxisAlignment: CrossAxisAlignment.start,
  108 + children: <Widget>[
  109 + Container(
  110 + width: 2.0 * PdfPageFormat.mm,
  111 + height: 2.0 * PdfPageFormat.mm,
  112 + margin: EdgeInsets.only(
  113 + top: 0.5 * PdfPageFormat.mm,
  114 + left: 5.0 * PdfPageFormat.mm,
  115 + right: 2.0 * PdfPageFormat.mm,
  116 + ),
  117 + decoration: BoxDecoration(
  118 + color: PdfColor.black, shape: BoxShape.circle),
  119 + ),
  120 + Expanded(child: Text(text, style: Theme.of(context).bulletStyle))
  121 + ]));
  122 + }
  123 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +abstract class BasePage {
  20 + final PdfPageFormat pageFormat;
  21 +
  22 + BasePage({this.pageFormat}) : assert(pageFormat != null);
  23 +
  24 + @protected
  25 + void generate(Document document);
  26 +}
  27 +
  28 +class Document {
  29 + static var debug = false;
  30 +
  31 + final PdfDocument document;
  32 +
  33 + final Theme theme;
  34 +
  35 + Document(
  36 + {PdfPageMode pageMode = PdfPageMode.none,
  37 + DeflateCallback deflate,
  38 + this.theme})
  39 + : document = PdfDocument(pageMode: pageMode, deflate: deflate);
  40 +
  41 + void addPage(BasePage page) {
  42 + page.generate(this);
  43 + }
  44 +}
  45 +
  46 +typedef Widget BuildCallback(Context context);
  47 +typedef List<Widget> BuildListCallback(Context context);
  48 +
  49 +class Page extends BasePage {
  50 + final EdgeInsets margin;
  51 + final BuildCallback _build;
  52 + final Theme theme;
  53 +
  54 + Page(
  55 + {PdfPageFormat pageFormat = PdfPageFormat.a4,
  56 + BuildCallback build,
  57 + this.theme,
  58 + EdgeInsets margin})
  59 + : margin = margin ??
  60 + EdgeInsets.fromLTRB(pageFormat.marginLeft, pageFormat.marginTop,
  61 + pageFormat.marginRight, pageFormat.marginBottom),
  62 + _build = build,
  63 + super(pageFormat: pageFormat);
  64 +
  65 + void debugPaint(Context context) {
  66 + context.canvas
  67 + ..setFillColor(PdfColor.lightGreen)
  68 + ..moveTo(0.0, 0.0)
  69 + ..lineTo(pageFormat.width, 0.0)
  70 + ..lineTo(pageFormat.width, pageFormat.height)
  71 + ..lineTo(0.0, pageFormat.height)
  72 + ..moveTo(margin.left, margin.bottom)
  73 + ..lineTo(margin.left, pageFormat.height - margin.top)
  74 + ..lineTo(pageFormat.width - margin.right, pageFormat.height - margin.top)
  75 + ..lineTo(pageFormat.width - margin.right, margin.bottom)
  76 + ..fillPath();
  77 + }
  78 +
  79 + @override
  80 + void generate(Document document) {
  81 + final pdfPage = PdfPage(document.document, pageFormat: pageFormat);
  82 + final canvas = pdfPage.getGraphics();
  83 + final constraints = BoxConstraints(
  84 + maxWidth: pageFormat.width, maxHeight: pageFormat.height);
  85 +
  86 + final calculatedTheme = theme ?? document.theme ?? Theme(document.document);
  87 + final inherited = Map<Type, Inherited>();
  88 + inherited[calculatedTheme.runtimeType] = calculatedTheme;
  89 + final context =
  90 + Context(page: pdfPage, canvas: canvas, inherited: inherited);
  91 + if (_build != null) {
  92 + final child = _build(context);
  93 + layout(child, context, constraints);
  94 + paint(child, context);
  95 + }
  96 + }
  97 +
  98 + @protected
  99 + void layout(Widget child, Context context, BoxConstraints constraints,
  100 + {parentUsesSize = false}) {
  101 + if (child != null) {
  102 + final childConstraints = BoxConstraints(
  103 + minWidth: constraints.minWidth,
  104 + minHeight: constraints.minHeight,
  105 + maxWidth: constraints.hasBoundedWidth
  106 + ? constraints.maxWidth - margin.horizontal
  107 + : margin.horizontal,
  108 + maxHeight: constraints.hasBoundedHeight
  109 + ? constraints.maxHeight - margin.vertical
  110 + : margin.vertical);
  111 + child.layout(context, childConstraints, parentUsesSize: parentUsesSize);
  112 + child.box = PdfRect(
  113 + margin.left,
  114 + pageFormat.height - child.box.height - margin.top,
  115 + child.box.width,
  116 + child.box.height);
  117 + }
  118 + }
  119 +
  120 + @protected
  121 + void paint(Widget child, Context context) {
  122 + assert(() {
  123 + if (Document.debug) debugPaint(context);
  124 + return true;
  125 + }());
  126 +
  127 + if (child != null) {
  128 + child.paint(context);
  129 + }
  130 + }
  131 +}
  132 +
  133 +class MultiPage extends Page {
  134 + final BuildListCallback _buildList;
  135 + final CrossAxisAlignment crossAxisAlignment;
  136 + final BuildCallback header;
  137 + final BuildCallback footer;
  138 +
  139 + MultiPage(
  140 + {PdfPageFormat pageFormat = PdfPageFormat.a4,
  141 + BuildListCallback build,
  142 + this.crossAxisAlignment = CrossAxisAlignment.start,
  143 + this.header,
  144 + this.footer,
  145 + EdgeInsets margin})
  146 + : _buildList = build,
  147 + super(pageFormat: pageFormat, margin: margin);
  148 +
  149 + @override
  150 + void generate(Document document) {
  151 + if (_buildList == null) return;
  152 +
  153 + final constraints = BoxConstraints(
  154 + maxWidth: pageFormat.width, maxHeight: pageFormat.height);
  155 + final childConstraints =
  156 + BoxConstraints(maxWidth: constraints.maxWidth - margin.horizontal);
  157 + final calculatedTheme = theme ?? document.theme ?? Theme(document.document);
  158 + final inherited = Map<Type, Inherited>();
  159 + inherited[calculatedTheme.runtimeType] = calculatedTheme;
  160 + Context context;
  161 + double offsetEnd;
  162 + double offsetStart;
  163 + var index = 0;
  164 + final children = _buildList(Context(inherited: inherited));
  165 + WidgetContext widgetContext;
  166 +
  167 + while (index < children.length) {
  168 + final child = children[index];
  169 +
  170 + if (context == null) {
  171 + final pdfPage = PdfPage(document.document, pageFormat: pageFormat);
  172 + final canvas = pdfPage.getGraphics();
  173 + context = Context(page: pdfPage, canvas: canvas, inherited: inherited);
  174 + assert(() {
  175 + if (Document.debug) debugPaint(context);
  176 + return true;
  177 + }());
  178 + offsetStart = pageFormat.height - margin.top;
  179 + offsetEnd = margin.bottom;
  180 + if (header != null) {
  181 + final headerWidget = header(context);
  182 + if (headerWidget != null) {
  183 + headerWidget.layout(context, childConstraints,
  184 + parentUsesSize: false);
  185 + headerWidget.box = PdfRect(
  186 + margin.left,
  187 + offsetStart - headerWidget.box.height,
  188 + headerWidget.box.width,
  189 + headerWidget.box.height);
  190 + headerWidget.paint(context);
  191 + offsetStart -= headerWidget.box.height;
  192 + }
  193 + }
  194 +
  195 + if (footer != null) {
  196 + final footerWidget = footer(context);
  197 + if (footerWidget != null) {
  198 + footerWidget.layout(context, childConstraints,
  199 + parentUsesSize: false);
  200 + footerWidget.box = PdfRect(margin.left, margin.bottom,
  201 + footerWidget.box.width, footerWidget.box.height);
  202 + footerWidget.paint(context);
  203 + offsetEnd += footerWidget.box.height;
  204 + }
  205 + }
  206 + }
  207 +
  208 + if (widgetContext != null && child is SpanningWidget) {
  209 + (child as SpanningWidget).restoreContext(widgetContext);
  210 + widgetContext = null;
  211 + }
  212 +
  213 + child.layout(context, childConstraints, parentUsesSize: false);
  214 +
  215 + if (offsetStart - child.box.height < offsetEnd) {
  216 + if (child.box.height < pageFormat.height - margin.vertical) {
  217 + context = null;
  218 + continue;
  219 + }
  220 +
  221 + if (!(child is SpanningWidget)) {
  222 + throw Exception("Widget won't fit into the page");
  223 + }
  224 +
  225 + final span = child as SpanningWidget;
  226 +
  227 + child.layout(context,
  228 + childConstraints.copyWith(maxHeight: offsetStart - offsetEnd),
  229 + parentUsesSize: false);
  230 + child.box = PdfRect(margin.left, offsetStart - child.box.height,
  231 + child.box.width, child.box.height);
  232 + child.paint(context);
  233 +
  234 + if (span.canSpan) {
  235 + widgetContext = span.saveContext();
  236 + } else {
  237 + index++;
  238 + }
  239 +
  240 + context = null;
  241 + continue;
  242 + }
  243 +
  244 + child.box = PdfRect(margin.left, offsetStart - child.box.height,
  245 + child.box.width, child.box.height);
  246 + child.paint(context);
  247 + offsetStart -= child.box.height;
  248 + index++;
  249 + }
  250 + }
  251 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +enum FlexFit {
  20 + tight,
  21 + loose,
  22 +}
  23 +
  24 +enum Axis {
  25 + horizontal,
  26 + vertical,
  27 +}
  28 +
  29 +enum MainAxisSize {
  30 + min,
  31 + max,
  32 +}
  33 +
  34 +enum MainAxisAlignment {
  35 + start,
  36 + end,
  37 + center,
  38 + spaceBetween,
  39 + spaceAround,
  40 + spaceEvenly,
  41 +}
  42 +
  43 +enum CrossAxisAlignment {
  44 + start,
  45 + end,
  46 + center,
  47 + stretch,
  48 +}
  49 +
  50 +enum VerticalDirection {
  51 + up,
  52 + down,
  53 +}
  54 +
  55 +typedef _ChildSizingFunction = double Function(Widget child, double extent);
  56 +
  57 +class Flex extends MultiChildWidget {
  58 + Flex({
  59 + @required this.direction,
  60 + this.mainAxisAlignment = MainAxisAlignment.start,
  61 + this.mainAxisSize = MainAxisSize.max,
  62 + this.crossAxisAlignment = CrossAxisAlignment.center,
  63 + this.verticalDirection = VerticalDirection.down,
  64 + List<Widget> children = const <Widget>[],
  65 + }) : assert(direction != null),
  66 + assert(mainAxisAlignment != null),
  67 + assert(mainAxisSize != null),
  68 + assert(crossAxisAlignment != null),
  69 + super(children: children);
  70 +
  71 + final Axis direction;
  72 +
  73 + final MainAxisAlignment mainAxisAlignment;
  74 +
  75 + final MainAxisSize mainAxisSize;
  76 +
  77 + final CrossAxisAlignment crossAxisAlignment;
  78 +
  79 + final VerticalDirection verticalDirection;
  80 +
  81 + double _getIntrinsicSize(
  82 + {Axis sizingDirection,
  83 + double
  84 + extent, // the extent in the direction that isn't the sizing direction
  85 + _ChildSizingFunction
  86 + childSize // a method to find the size in the sizing direction
  87 + }) {
  88 + if (direction == sizingDirection) {
  89 + // INTRINSIC MAIN SIZE
  90 + // Intrinsic main size is the smallest size the flex container can take
  91 + // while maintaining the min/max-content contributions of its flex items.
  92 + double totalFlex = 0.0;
  93 + double inflexibleSpace = 0.0;
  94 + double maxFlexFractionSoFar = 0.0;
  95 +
  96 + for (var child in children) {
  97 + final int flex = child._flex;
  98 + totalFlex += flex;
  99 + if (flex > 0) {
  100 + final double flexFraction = childSize(child, extent) / child._flex;
  101 + maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
  102 + } else {
  103 + inflexibleSpace += childSize(child, extent);
  104 + }
  105 + }
  106 + return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
  107 + } else {
  108 + // INTRINSIC CROSS SIZE
  109 + // Intrinsic cross size is the max of the intrinsic cross sizes of the
  110 + // children, after the flexible children are fit into the available space,
  111 + // with the children sized using their max intrinsic dimensions.
  112 +
  113 + // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction.
  114 + final double availableMainSpace = extent;
  115 + int totalFlex = 0;
  116 + double inflexibleSpace = 0.0;
  117 + double maxCrossSize = 0.0;
  118 + for (var child in children) {
  119 + final int flex = child._flex;
  120 + totalFlex += flex;
  121 + double mainSize;
  122 + double crossSize;
  123 + if (flex == 0) {
  124 + switch (direction) {
  125 + case Axis.horizontal:
  126 + mainSize = child.box.width;
  127 + crossSize = childSize(child, mainSize);
  128 + break;
  129 + case Axis.vertical:
  130 + mainSize = child.box.height;
  131 + crossSize = childSize(child, mainSize);
  132 + break;
  133 + }
  134 + inflexibleSpace += mainSize;
  135 + maxCrossSize = math.max(maxCrossSize, crossSize);
  136 + }
  137 + }
  138 +
  139 + // Determine the spacePerFlex by allocating the remaining available space.
  140 + // When you're over-constrained spacePerFlex can be negative.
  141 + final double spacePerFlex =
  142 + math.max(0.0, (availableMainSpace - inflexibleSpace) / totalFlex);
  143 +
  144 + // Size remaining (flexible) items, find the maximum cross size.
  145 + for (var child in children) {
  146 + final int flex = child._flex;
  147 + if (flex > 0)
  148 + maxCrossSize =
  149 + math.max(maxCrossSize, childSize(child, spacePerFlex * flex));
  150 + }
  151 +
  152 + return maxCrossSize;
  153 + }
  154 + }
  155 +
  156 + double computeMinIntrinsicWidth(double height) {
  157 + return _getIntrinsicSize(
  158 + sizingDirection: Axis.horizontal,
  159 + extent: height,
  160 + childSize: (Widget child, double extent) => child.box.width);
  161 + }
  162 +
  163 + double computeMaxIntrinsicWidth(double height) {
  164 + return _getIntrinsicSize(
  165 + sizingDirection: Axis.horizontal,
  166 + extent: height,
  167 + childSize: (Widget child, double extent) => child.box.width);
  168 + }
  169 +
  170 + double computeMinIntrinsicHeight(double width) {
  171 + return _getIntrinsicSize(
  172 + sizingDirection: Axis.vertical,
  173 + extent: width,
  174 + childSize: (Widget child, double extent) => child.box.height);
  175 + }
  176 +
  177 + double computeMaxIntrinsicHeight(double width) {
  178 + return _getIntrinsicSize(
  179 + sizingDirection: Axis.vertical,
  180 + extent: width,
  181 + childSize: (Widget child, double extent) => child.box.height);
  182 + }
  183 +
  184 + double _getCrossSize(Widget child) {
  185 + switch (direction) {
  186 + case Axis.horizontal:
  187 + return child.box.height;
  188 + case Axis.vertical:
  189 + return child.box.width;
  190 + }
  191 + return null;
  192 + }
  193 +
  194 + double _getMainSize(Widget child) {
  195 + switch (direction) {
  196 + case Axis.horizontal:
  197 + return child.box.width;
  198 + case Axis.vertical:
  199 + return child.box.height;
  200 + }
  201 + return null;
  202 + }
  203 +
  204 + @override
  205 + void layout(Context context, BoxConstraints constraints,
  206 + {parentUsesSize = false}) {
  207 + // Determine used flex factor, size inflexible items, calculate free space.
  208 + int totalFlex = 0;
  209 + final totalChildren = children.length;
  210 + Widget lastFlexChild;
  211 + assert(constraints != null);
  212 + final double maxMainSize = direction == Axis.horizontal
  213 + ? constraints.maxWidth
  214 + : constraints.maxHeight;
  215 + final bool canFlex = maxMainSize < double.infinity;
  216 +
  217 + double crossSize = 0.0;
  218 + double allocatedSize =
  219 + 0.0; // Sum of the sizes of the non-flexible children.
  220 +
  221 + for (var child in children) {
  222 + final int flex = child._flex;
  223 + if (flex > 0) {
  224 + assert(() {
  225 + final String dimension =
  226 + direction == Axis.horizontal ? 'width' : 'height';
  227 + if (!canFlex &&
  228 + (mainAxisSize == MainAxisSize.max ||
  229 + child._fit == FlexFit.tight)) {
  230 + throw Exception(
  231 + 'Flex children have non-zero flex but incoming $dimension constraints are unbounded.');
  232 + } else {
  233 + return true;
  234 + }
  235 + }());
  236 + totalFlex += child._flex;
  237 + } else {
  238 + BoxConstraints innerConstraints;
  239 + if (crossAxisAlignment == CrossAxisAlignment.stretch) {
  240 + switch (direction) {
  241 + case Axis.horizontal:
  242 + innerConstraints = BoxConstraints(
  243 + minHeight: constraints.maxHeight,
  244 + maxHeight: constraints.maxHeight);
  245 + break;
  246 + case Axis.vertical:
  247 + innerConstraints = BoxConstraints(
  248 + minWidth: constraints.maxWidth,
  249 + maxWidth: constraints.maxWidth);
  250 + break;
  251 + }
  252 + } else {
  253 + switch (direction) {
  254 + case Axis.horizontal:
  255 + innerConstraints =
  256 + BoxConstraints(maxHeight: constraints.maxHeight);
  257 + break;
  258 + case Axis.vertical:
  259 + innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
  260 + break;
  261 + }
  262 + }
  263 + child.layout(context, innerConstraints, parentUsesSize: true);
  264 + allocatedSize += _getMainSize(child);
  265 + crossSize = math.max(crossSize, _getCrossSize(child));
  266 + }
  267 + lastFlexChild = child;
  268 + }
  269 +
  270 + // Distribute free space to flexible children, and determine baseline.
  271 + final double freeSpace =
  272 + math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
  273 + double allocatedFlexSpace = 0.0;
  274 + if (totalFlex > 0) {
  275 + final double spacePerFlex =
  276 + canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
  277 +
  278 + for (var child in children) {
  279 + final int flex = child._flex;
  280 + if (flex > 0) {
  281 + final double maxChildExtent = canFlex
  282 + ? (child == lastFlexChild
  283 + ? (freeSpace - allocatedFlexSpace)
  284 + : spacePerFlex * flex)
  285 + : double.infinity;
  286 + double minChildExtent;
  287 + switch (child._fit) {
  288 + case FlexFit.tight:
  289 + assert(maxChildExtent < double.infinity);
  290 + minChildExtent = maxChildExtent;
  291 + break;
  292 + case FlexFit.loose:
  293 + minChildExtent = 0.0;
  294 + break;
  295 + }
  296 + assert(minChildExtent != null);
  297 + BoxConstraints innerConstraints;
  298 + if (crossAxisAlignment == CrossAxisAlignment.stretch) {
  299 + switch (direction) {
  300 + case Axis.horizontal:
  301 + innerConstraints = BoxConstraints(
  302 + minWidth: minChildExtent,
  303 + maxWidth: maxChildExtent,
  304 + minHeight: constraints.maxHeight,
  305 + maxHeight: constraints.maxHeight);
  306 + break;
  307 + case Axis.vertical:
  308 + innerConstraints = BoxConstraints(
  309 + minWidth: constraints.maxWidth,
  310 + maxWidth: constraints.maxWidth,
  311 + minHeight: minChildExtent,
  312 + maxHeight: maxChildExtent);
  313 + break;
  314 + }
  315 + } else {
  316 + switch (direction) {
  317 + case Axis.horizontal:
  318 + innerConstraints = BoxConstraints(
  319 + minWidth: minChildExtent,
  320 + maxWidth: maxChildExtent,
  321 + maxHeight: constraints.maxHeight);
  322 + break;
  323 + case Axis.vertical:
  324 + innerConstraints = BoxConstraints(
  325 + maxWidth: constraints.maxWidth,
  326 + minHeight: minChildExtent,
  327 + maxHeight: maxChildExtent);
  328 + break;
  329 + }
  330 + }
  331 + child.layout(context, innerConstraints, parentUsesSize: true);
  332 + final double childSize = _getMainSize(child);
  333 + assert(childSize <= maxChildExtent);
  334 + allocatedSize += childSize;
  335 + allocatedFlexSpace += maxChildExtent;
  336 + crossSize = math.max(crossSize, _getCrossSize(child));
  337 + }
  338 + }
  339 + }
  340 +
  341 + // Align items along the main axis.
  342 + final double idealSize = canFlex && mainAxisSize == MainAxisSize.max
  343 + ? maxMainSize
  344 + : allocatedSize;
  345 + double actualSize;
  346 + double actualSizeDelta;
  347 + PdfPoint size;
  348 + switch (direction) {
  349 + case Axis.horizontal:
  350 + size = constraints.constrain(PdfPoint(idealSize, crossSize));
  351 + actualSize = size.x;
  352 + crossSize = size.y;
  353 + break;
  354 + case Axis.vertical:
  355 + size = constraints.constrain(PdfPoint(crossSize, idealSize));
  356 + actualSize = size.y;
  357 + crossSize = size.x;
  358 + break;
  359 + }
  360 +
  361 + box = PdfRect.fromPoints(PdfPoint.zero, size);
  362 + actualSizeDelta = actualSize - allocatedSize;
  363 +
  364 + final double remainingSpace = math.max(0.0, actualSizeDelta);
  365 + double leadingSpace;
  366 + double betweenSpace;
  367 + final bool flipMainAxis = (verticalDirection == VerticalDirection.down &&
  368 + direction == Axis.vertical) ||
  369 + (verticalDirection == VerticalDirection.up &&
  370 + direction == Axis.horizontal);
  371 + switch (mainAxisAlignment) {
  372 + case MainAxisAlignment.start:
  373 + leadingSpace = 0.0;
  374 + betweenSpace = 0.0;
  375 + break;
  376 + case MainAxisAlignment.end:
  377 + leadingSpace = remainingSpace;
  378 + betweenSpace = 0.0;
  379 + break;
  380 + case MainAxisAlignment.center:
  381 + leadingSpace = remainingSpace / 2.0;
  382 + betweenSpace = 0.0;
  383 + break;
  384 + case MainAxisAlignment.spaceBetween:
  385 + leadingSpace = 0.0;
  386 + betweenSpace =
  387 + totalChildren > 1 ? remainingSpace / (totalChildren - 1) : 0.0;
  388 + break;
  389 + case MainAxisAlignment.spaceAround:
  390 + betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0;
  391 + leadingSpace = betweenSpace / 2.0;
  392 + break;
  393 + case MainAxisAlignment.spaceEvenly:
  394 + betweenSpace =
  395 + totalChildren > 0 ? remainingSpace / (totalChildren + 1) : 0.0;
  396 + leadingSpace = betweenSpace;
  397 + break;
  398 + }
  399 +
  400 + // Position elements
  401 + final bool flipCrossAxis = (verticalDirection == VerticalDirection.down &&
  402 + direction == Axis.horizontal) ||
  403 + (verticalDirection == VerticalDirection.up &&
  404 + direction == Axis.vertical);
  405 + double childMainPosition =
  406 + flipMainAxis ? actualSize - leadingSpace : leadingSpace;
  407 + for (var child in children) {
  408 + double childCrossPosition;
  409 + switch (crossAxisAlignment) {
  410 + case CrossAxisAlignment.start:
  411 + childCrossPosition =
  412 + flipCrossAxis ? crossSize - _getCrossSize(child) : 0.0;
  413 + break;
  414 + case CrossAxisAlignment.end:
  415 + childCrossPosition =
  416 + !flipCrossAxis ? crossSize - _getCrossSize(child) : 0.0;
  417 + break;
  418 + case CrossAxisAlignment.center:
  419 + childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0;
  420 + break;
  421 + case CrossAxisAlignment.stretch:
  422 + childCrossPosition = 0.0;
  423 + break;
  424 + }
  425 +
  426 + if (flipMainAxis) childMainPosition -= _getMainSize(child);
  427 + switch (direction) {
  428 + case Axis.horizontal:
  429 + child.box = PdfRect(box.x + childMainPosition,
  430 + box.y + childCrossPosition, child.box.width, child.box.height);
  431 + break;
  432 + case Axis.vertical:
  433 + child.box = PdfRect(childCrossPosition, childMainPosition,
  434 + child.box.width, child.box.height);
  435 + break;
  436 + }
  437 + if (flipMainAxis) {
  438 + childMainPosition -= betweenSpace;
  439 + } else {
  440 + childMainPosition += _getMainSize(child) + betweenSpace;
  441 + }
  442 + }
  443 + }
  444 +
  445 + @override
  446 + void paint(Context context) {
  447 + super.paint(context);
  448 +
  449 + final mat = Matrix4.identity();
  450 + mat.translate(box.x, box.y);
  451 + context.canvas
  452 + ..saveContext()
  453 + ..setTransform(mat);
  454 + for (var child in children) {
  455 + child.paint(context);
  456 + }
  457 + context.canvas.restoreContext();
  458 + }
  459 +}
  460 +
  461 +class Row extends Flex {
  462 + Row({
  463 + MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  464 + MainAxisSize mainAxisSize = MainAxisSize.max,
  465 + CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  466 + VerticalDirection verticalDirection = VerticalDirection.down,
  467 + List<Widget> children = const <Widget>[],
  468 + }) : super(
  469 + children: children,
  470 + direction: Axis.horizontal,
  471 + mainAxisAlignment: mainAxisAlignment,
  472 + mainAxisSize: mainAxisSize,
  473 + crossAxisAlignment: crossAxisAlignment,
  474 + verticalDirection: verticalDirection,
  475 + );
  476 +}
  477 +
  478 +class Column extends Flex {
  479 + Column({
  480 + MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
  481 + MainAxisSize mainAxisSize = MainAxisSize.max,
  482 + CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
  483 + VerticalDirection verticalDirection = VerticalDirection.down,
  484 + List<Widget> children = const <Widget>[],
  485 + }) : super(
  486 + children: children,
  487 + direction: Axis.vertical,
  488 + mainAxisAlignment: mainAxisAlignment,
  489 + mainAxisSize: mainAxisSize,
  490 + crossAxisAlignment: crossAxisAlignment,
  491 + verticalDirection: verticalDirection,
  492 + );
  493 +}
  494 +
  495 +class Expanded extends SingleChildWidget {
  496 + Expanded({
  497 + int flex = 1,
  498 + @required Widget child,
  499 + }) : super(child: child) {
  500 + this._flex = flex;
  501 + this._fit = FlexFit.tight;
  502 + }
  503 +}
  504 +
  505 +class ListView extends Flex {
  506 + ListView(
  507 + {Axis direction = Axis.vertical,
  508 + EdgeInsets padding,
  509 + double spacing = 0.0,
  510 + List<Widget> children = const []})
  511 + : super(
  512 + direction: direction,
  513 + mainAxisAlignment: MainAxisAlignment.start,
  514 + mainAxisSize: MainAxisSize.max,
  515 + crossAxisAlignment: CrossAxisAlignment.center,
  516 + verticalDirection: VerticalDirection.down,
  517 + children: children);
  518 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +@immutable
  20 +class BoxConstraints {
  21 + /// The minimum width that satisfies the constraints.
  22 + final double minWidth;
  23 +
  24 + /// The maximum width that satisfies the constraints.
  25 + ///
  26 + /// Might be [double.infinity].
  27 + final double maxWidth;
  28 +
  29 + /// The minimum height that satisfies the constraints.
  30 + final double minHeight;
  31 +
  32 + /// The maximum height that satisfies the constraints.
  33 + ///
  34 + /// Might be [double.infinity].
  35 + final double maxHeight;
  36 +
  37 + /// Creates box constraints with the given constraints.
  38 + const BoxConstraints(
  39 + {this.minWidth = 0.0,
  40 + this.maxWidth = double.infinity,
  41 + this.minHeight = 0.0,
  42 + this.maxHeight = double.infinity});
  43 +
  44 + bool get hasBoundedWidth => maxWidth < double.infinity;
  45 +
  46 + bool get hasBoundedHeight => maxHeight < double.infinity;
  47 +
  48 + bool get hasInfiniteWidth => minWidth >= double.infinity;
  49 +
  50 + bool get hasInfiniteHeight => minHeight >= double.infinity;
  51 +
  52 + /// The biggest size that satisfies the constraints.
  53 + PdfPoint get biggest => PdfPoint(constrainWidth(), constrainHeight());
  54 +
  55 + /// The smallest size that satisfies the constraints.
  56 + PdfPoint get smallest => PdfPoint(constrainWidth(0.0), constrainHeight(0.0));
  57 +
  58 + /// Whether there is exactly one width value that satisfies the constraints.
  59 + bool get hasTightWidth => minWidth >= maxWidth;
  60 +
  61 + /// Whether there is exactly one height value that satisfies the constraints.
  62 + bool get hasTightHeight => minHeight >= maxHeight;
  63 +
  64 + /// Whether there is exactly one size that satisfies the constraints.
  65 + bool get isTight => hasTightWidth && hasTightHeight;
  66 +
  67 + PdfPoint constrain(PdfPoint size) {
  68 + final result = PdfPoint(constrainWidth(size.x), constrainHeight(size.y));
  69 + return result;
  70 + }
  71 +
  72 + PdfRect constrainRect(
  73 + {double width = double.infinity, double height = double.infinity}) {
  74 + final result = PdfPoint(constrainWidth(width), constrainHeight(height));
  75 + return PdfRect.fromPoints(PdfPoint.zero, result);
  76 + }
  77 +
  78 + double constrainWidth([double width = double.infinity]) {
  79 + return width.clamp(minWidth, maxWidth);
  80 + }
  81 +
  82 + double constrainHeight([double height = double.infinity]) {
  83 + return height.clamp(minHeight, maxHeight);
  84 + }
  85 +
  86 + /// Returns a size that attempts to meet the conditions
  87 + PdfPoint constrainSizeAndAttemptToPreserveAspectRatio(PdfPoint size) {
  88 + if (isTight) {
  89 + PdfPoint result = smallest;
  90 +
  91 + return result;
  92 + }
  93 +
  94 + double width = size.x;
  95 + double height = size.y;
  96 + assert(width > 0.0);
  97 + assert(height > 0.0);
  98 + final double aspectRatio = width / height;
  99 +
  100 + if (width > maxWidth) {
  101 + width = maxWidth;
  102 + height = width / aspectRatio;
  103 + }
  104 +
  105 + if (height > maxHeight) {
  106 + height = maxHeight;
  107 + width = height * aspectRatio;
  108 + }
  109 +
  110 + if (width < minWidth) {
  111 + width = minWidth;
  112 + height = width / aspectRatio;
  113 + }
  114 +
  115 + if (height < minHeight) {
  116 + height = minHeight;
  117 + width = height * aspectRatio;
  118 + }
  119 +
  120 + PdfPoint result = PdfPoint(constrainWidth(width), constrainHeight(height));
  121 + return result;
  122 + }
  123 +
  124 + /// Returns new box constraints with a tight width and/or height as close to
  125 + /// the given width and height as possible while still respecting the original
  126 + /// box constraints.
  127 + BoxConstraints tighten({double width, double height}) {
  128 + return BoxConstraints(
  129 + minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth),
  130 + maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth),
  131 + minHeight:
  132 + height == null ? minHeight : height.clamp(minHeight, maxHeight),
  133 + maxHeight:
  134 + height == null ? maxHeight : height.clamp(minHeight, maxHeight));
  135 + }
  136 +
  137 + /// Creates box constraints that require the given width or height.
  138 + const BoxConstraints.tightFor({double width, double height})
  139 + : minWidth = width != null ? width : 0.0,
  140 + maxWidth = width != null ? width : double.infinity,
  141 + minHeight = height != null ? height : 0.0,
  142 + maxHeight = height != null ? height : double.infinity;
  143 +
  144 + /// Creates box constraints that expand to fill another box constraints.
  145 + const BoxConstraints.expand({double width, double height})
  146 + : minWidth = width != null ? width : double.infinity,
  147 + maxWidth = width != null ? width : double.infinity,
  148 + minHeight = height != null ? height : double.infinity,
  149 + maxHeight = height != null ? height : double.infinity;
  150 +
  151 + /// Returns new box constraints that are smaller by the given edge dimensions.
  152 + BoxConstraints deflate(EdgeInsets edges) {
  153 + assert(edges != null);
  154 + final double horizontal = edges.horizontal;
  155 + final double vertical = edges.vertical;
  156 + final double deflatedMinWidth = math.max(0.0, minWidth - horizontal);
  157 + final double deflatedMinHeight = math.max(0.0, minHeight - vertical);
  158 + return BoxConstraints(
  159 + minWidth: deflatedMinWidth,
  160 + maxWidth: math.max(deflatedMinWidth, maxWidth - horizontal),
  161 + minHeight: deflatedMinHeight,
  162 + maxHeight: math.max(deflatedMinHeight, maxHeight - vertical));
  163 + }
  164 +
  165 + /// Returns new box constraints that remove the minimum width and height requirements.
  166 + BoxConstraints loosen() {
  167 + return BoxConstraints(
  168 + minWidth: 0.0,
  169 + maxWidth: maxWidth,
  170 + minHeight: 0.0,
  171 + maxHeight: maxHeight);
  172 + }
  173 +
  174 + /// Returns new box constraints that respect the given constraints while being
  175 + /// as close as possible to the original constraints.
  176 + BoxConstraints enforce(BoxConstraints constraints) {
  177 + return BoxConstraints(
  178 + minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
  179 + maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
  180 + minHeight:
  181 + minHeight.clamp(constraints.minHeight, constraints.maxHeight),
  182 + maxHeight:
  183 + maxHeight.clamp(constraints.minHeight, constraints.maxHeight));
  184 + }
  185 +
  186 + BoxConstraints copyWith(
  187 + {double minWidth, double maxWidth, double minHeight, double maxHeight}) {
  188 + return BoxConstraints(
  189 + minWidth: minWidth ?? this.minWidth,
  190 + maxWidth: maxWidth ?? this.maxWidth,
  191 + minHeight: minHeight ?? this.minHeight,
  192 + maxHeight: maxHeight ?? this.maxHeight);
  193 + }
  194 +
  195 + @override
  196 + String toString() {
  197 + return "BoxConstraint <$minWidth, $maxWidth> <$minHeight, $maxHeight>";
  198 + }
  199 +}
  200 +
  201 +@immutable
  202 +class EdgeInsets {
  203 + const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);
  204 +
  205 + const EdgeInsets.all(double value)
  206 + : left = value,
  207 + top = value,
  208 + right = value,
  209 + bottom = value;
  210 +
  211 + const EdgeInsets.only(
  212 + {this.left = 0.0, this.top = 0.0, this.right = 0.0, this.bottom = 0.0});
  213 +
  214 + const EdgeInsets.symmetric({double vertical = 0.0, double horizontal = 0.0})
  215 + : left = horizontal,
  216 + top = vertical,
  217 + right = horizontal,
  218 + bottom = vertical;
  219 +
  220 + static const EdgeInsets zero = EdgeInsets.only();
  221 +
  222 + final double left;
  223 +
  224 + final double top;
  225 +
  226 + final double right;
  227 +
  228 + final double bottom;
  229 +
  230 + /// The total offset in the horizontal direction.
  231 + double get horizontal => left + right;
  232 +
  233 + /// The total offset in the vertical direction.
  234 + double get vertical => top + bottom;
  235 +
  236 + EdgeInsets copyWith({
  237 + double left,
  238 + double top,
  239 + double right,
  240 + double bottom,
  241 + }) {
  242 + return EdgeInsets.only(
  243 + left: left ?? this.left,
  244 + top: top ?? this.top,
  245 + right: right ?? this.right,
  246 + bottom: bottom ?? this.bottom,
  247 + );
  248 + }
  249 +
  250 + /// Returns the sum of two [EdgeInsets] objects.
  251 + EdgeInsets add(EdgeInsets other) {
  252 + return EdgeInsets.fromLTRB(
  253 + left + other.left,
  254 + top + other.top,
  255 + right + other.right,
  256 + bottom + other.bottom,
  257 + );
  258 + }
  259 +}
  260 +
  261 +class Alignment {
  262 + const Alignment(this.x, this.y)
  263 + : assert(x != null),
  264 + assert(y != null);
  265 +
  266 + /// The distance fraction in the horizontal direction.
  267 + final double x;
  268 +
  269 + /// The distance fraction in the vertical direction.
  270 + final double y;
  271 +
  272 + /// The top left corner.
  273 + static const Alignment topLeft = Alignment(-1.0, 1.0);
  274 +
  275 + /// The center point along the top edge.
  276 + static const Alignment topCenter = Alignment(0.0, 1.0);
  277 +
  278 + /// The top right corner.
  279 + static const Alignment topRight = Alignment(1.0, 1.0);
  280 +
  281 + /// The center point along the left edge.
  282 + static const Alignment centerLeft = Alignment(-1.0, 0.0);
  283 +
  284 + /// The center point, both horizontally and vertically.
  285 + static const Alignment center = Alignment(0.0, 0.0);
  286 +
  287 + /// The center point along the right edge.
  288 + static const Alignment centerRight = Alignment(1.0, 0.0);
  289 +
  290 + /// The bottom left corner.
  291 + static const Alignment bottomLeft = Alignment(-1.0, -1.0);
  292 +
  293 + /// The center point along the bottom edge.
  294 + static const Alignment bottomCenter = Alignment(0.0, -1.0);
  295 +
  296 + /// The bottom right corner.
  297 + static const Alignment bottomRight = Alignment(1.0, -1.0);
  298 +
  299 + /// Returns the offset that is this fraction within the given size.
  300 + PdfPoint alongSize(PdfPoint other) {
  301 + final double centerX = other.x / 2.0;
  302 + final double centerY = other.y / 2.0;
  303 + return PdfPoint(centerX + x * centerX, centerY + y * centerY);
  304 + }
  305 +
  306 + /// Returns the point that is this fraction within the given rect.
  307 + PdfPoint withinRect(PdfRect rect) {
  308 + final double halfWidth = rect.width / 2.0;
  309 + final double halfHeight = rect.height / 2.0;
  310 + return PdfPoint(
  311 + rect.left + halfWidth + x * halfWidth,
  312 + rect.top + halfHeight + y * halfHeight,
  313 + );
  314 + }
  315 +
  316 + /// Returns a rect of the given size, aligned within given rect as specified
  317 + /// by this alignment.
  318 + PdfRect inscribe(PdfPoint size, PdfRect rect) {
  319 + final double halfWidthDelta = (rect.width - size.x) / 2.0;
  320 + final double halfHeightDelta = (rect.height - size.y) / 2.0;
  321 + return PdfRect(
  322 + rect.x + halfWidthDelta + x * halfWidthDelta,
  323 + rect.y + halfHeightDelta + y * halfHeightDelta,
  324 + size.x,
  325 + size.y,
  326 + );
  327 + }
  328 +
  329 + @override
  330 + String toString() => "($x, $y)";
  331 +}
  332 +
  333 +/// The pair of sizes returned by [applyBoxFit].
  334 +@immutable
  335 +class FittedSizes {
  336 + const FittedSizes(this.source, this.destination);
  337 +
  338 + /// The size of the part of the input to show on the output.
  339 + final PdfPoint source;
  340 +
  341 + /// The size of the part of the output on which to show the input.
  342 + final PdfPoint destination;
  343 +}
  344 +
  345 +FittedSizes applyBoxFit(BoxFit fit, PdfPoint inputSize, PdfPoint outputSize) {
  346 + if (inputSize.y <= 0.0 ||
  347 + inputSize.x <= 0.0 ||
  348 + outputSize.y <= 0.0 ||
  349 + outputSize.x <= 0.0)
  350 + return const FittedSizes(PdfPoint.zero, PdfPoint.zero);
  351 +
  352 + PdfPoint sourceSize, destinationSize;
  353 + switch (fit) {
  354 + case BoxFit.fill:
  355 + sourceSize = inputSize;
  356 + destinationSize = outputSize;
  357 + break;
  358 + case BoxFit.contain:
  359 + sourceSize = inputSize;
  360 + if (outputSize.x / outputSize.y > sourceSize.x / sourceSize.y)
  361 + destinationSize =
  362 + PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
  363 + else
  364 + destinationSize =
  365 + PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
  366 + break;
  367 + case BoxFit.cover:
  368 + if (outputSize.x / outputSize.y > inputSize.x / inputSize.y) {
  369 + sourceSize =
  370 + PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
  371 + } else {
  372 + sourceSize =
  373 + PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
  374 + }
  375 + destinationSize = outputSize;
  376 + break;
  377 + case BoxFit.fitWidth:
  378 + sourceSize =
  379 + PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
  380 + destinationSize =
  381 + PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
  382 + break;
  383 + case BoxFit.fitHeight:
  384 + sourceSize =
  385 + PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
  386 + destinationSize =
  387 + PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
  388 + break;
  389 + case BoxFit.none:
  390 + sourceSize = PdfPoint(math.min(inputSize.x, outputSize.x),
  391 + math.min(inputSize.y, outputSize.y));
  392 + destinationSize = sourceSize;
  393 + break;
  394 + case BoxFit.scaleDown:
  395 + sourceSize = inputSize;
  396 + destinationSize = inputSize;
  397 + final double aspectRatio = inputSize.x / inputSize.y;
  398 + if (destinationSize.y > outputSize.y)
  399 + destinationSize = PdfPoint(outputSize.y * aspectRatio, outputSize.y);
  400 + if (destinationSize.x > outputSize.x)
  401 + destinationSize = PdfPoint(outputSize.x, outputSize.x / aspectRatio);
  402 + break;
  403 + }
  404 + return FittedSizes(sourceSize, destinationSize);
  405 +}
  406 +
  407 +PdfPoint transformPoint(Matrix4 transform, PdfPoint point) {
  408 + final Vector3 position3 = Vector3(point.x, point.y, 0.0);
  409 + final Vector3 transformed3 = transform.perspectiveTransform(position3);
  410 + return PdfPoint(transformed3.x, transformed3.y);
  411 +}
  412 +
  413 +PdfRect transformRect(Matrix4 transform, PdfRect rect) {
  414 + final point1 = transformPoint(transform, rect.topLeft);
  415 + final point2 = transformPoint(transform, rect.topRight);
  416 + final point3 = transformPoint(transform, rect.bottomLeft);
  417 + final point4 = transformPoint(transform, rect.bottomRight);
  418 + return PdfRect.fromLTRB(
  419 + math.min(point1.x, math.min(point2.x, math.min(point3.x, point4.x))),
  420 + math.min(point1.y, math.min(point2.y, math.min(point3.y, point4.y))),
  421 + math.max(point1.x, math.max(point2.x, math.max(point3.x, point4.x))),
  422 + math.max(point1.y, math.max(point2.y, math.max(point3.y, point4.y))));
  423 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class GridView extends MultiChildWidget {
  20 + GridView(
  21 + {this.direction = Axis.vertical,
  22 + this.padding = EdgeInsets.zero,
  23 + @required this.crossAxisCount,
  24 + this.mainAxisSpacing = 0.0,
  25 + this.crossAxisSpacing = 0.0,
  26 + this.childAspectRatio = double.infinity,
  27 + List<Widget> children = const []})
  28 + : assert(padding != null),
  29 + super(children: children);
  30 +
  31 + final Axis direction;
  32 + final EdgeInsets padding;
  33 + final int crossAxisCount;
  34 + final double mainAxisSpacing;
  35 + final double crossAxisSpacing;
  36 + final double childAspectRatio;
  37 +
  38 + double _childCrossAxis;
  39 + double _childMainAxis;
  40 + double _totalMain;
  41 + double _totalCross;
  42 + int _mainAxisCount;
  43 +
  44 + @override
  45 + void layout(Context context, BoxConstraints constraints,
  46 + {parentUsesSize = false}) {
  47 + double mainAxisExtent;
  48 + double crossAxisExtent;
  49 + switch (direction) {
  50 + case Axis.vertical:
  51 + mainAxisExtent = constraints.maxHeight - padding.vertical;
  52 + crossAxisExtent = constraints.maxWidth - padding.horizontal;
  53 + break;
  54 + case Axis.horizontal:
  55 + mainAxisExtent = constraints.maxWidth - padding.horizontal;
  56 + crossAxisExtent = constraints.maxHeight - padding.vertical;
  57 + break;
  58 + }
  59 +
  60 + _mainAxisCount = (children.length / crossAxisCount).ceil();
  61 + _childCrossAxis = crossAxisExtent / crossAxisCount -
  62 + (crossAxisSpacing * (crossAxisCount - 1) / crossAxisCount);
  63 + _childMainAxis = math.min(
  64 + _childCrossAxis * childAspectRatio,
  65 + mainAxisExtent / _mainAxisCount -
  66 + (mainAxisSpacing * (_mainAxisCount - 1) / _mainAxisCount));
  67 + _totalMain =
  68 + (_childMainAxis + mainAxisSpacing) * _mainAxisCount - mainAxisSpacing;
  69 + _totalCross = (_childCrossAxis + crossAxisSpacing) * crossAxisCount -
  70 + crossAxisSpacing;
  71 +
  72 + var startX = padding.left;
  73 + var startY = 0.0;
  74 + var mainAxis;
  75 + var crossAxis;
  76 + BoxConstraints innerConstraints;
  77 + switch (direction) {
  78 + case Axis.vertical:
  79 + innerConstraints = BoxConstraints.tightFor(
  80 + width: _childCrossAxis, height: _childMainAxis);
  81 + crossAxis = startX;
  82 + mainAxis = startY;
  83 + break;
  84 + case Axis.horizontal:
  85 + innerConstraints = BoxConstraints.tightFor(
  86 + width: _childMainAxis, height: _childCrossAxis);
  87 + mainAxis = startX;
  88 + crossAxis = startY;
  89 + break;
  90 + }
  91 +
  92 + var c = 0;
  93 + for (var child in children) {
  94 + child.layout(context, innerConstraints);
  95 +
  96 + switch (direction) {
  97 + case Axis.vertical:
  98 + child.box = PdfRect.fromPoints(
  99 + PdfPoint(
  100 + (_childCrossAxis - child.box.width) / 2.0 + crossAxis,
  101 + _totalMain +
  102 + padding.bottom -
  103 + (_childMainAxis - child.box.height) / 2.0 -
  104 + mainAxis -
  105 + child.box.height),
  106 + child.box.size);
  107 + break;
  108 + case Axis.horizontal:
  109 + child.box = PdfRect.fromPoints(
  110 + PdfPoint(
  111 + (_childMainAxis - child.box.width) / 2.0 + mainAxis,
  112 + _totalCross +
  113 + padding.bottom -
  114 + (_childCrossAxis - child.box.height) / 2.0 -
  115 + crossAxis -
  116 + child.box.height),
  117 + child.box.size);
  118 + break;
  119 + }
  120 +
  121 + if (++c >= crossAxisCount) {
  122 + mainAxis += _childMainAxis + mainAxisSpacing;
  123 + crossAxis = startX;
  124 + c = 0;
  125 + } else {
  126 + crossAxis += _childCrossAxis + crossAxisSpacing;
  127 + }
  128 + }
  129 +
  130 + switch (direction) {
  131 + case Axis.vertical:
  132 + box = constraints.constrainRect(
  133 + width: _totalCross + padding.horizontal,
  134 + height: _totalMain + padding.vertical);
  135 + break;
  136 + case Axis.horizontal:
  137 + box = constraints.constrainRect(
  138 + width: _totalMain + padding.horizontal,
  139 + height: _totalCross + padding.vertical);
  140 + break;
  141 + }
  142 + }
  143 +
  144 + @override
  145 + void debugPaint(Context context) {
  146 + super.debugPaint(context);
  147 +
  148 + context.canvas
  149 + ..setFillColor(PdfColor.lime)
  150 + ..moveTo(box.left, box.bottom)
  151 + ..lineTo(box.right, box.bottom)
  152 + ..lineTo(box.right, box.top)
  153 + ..lineTo(box.left, box.top)
  154 + ..moveTo(box.left + padding.left, box.bottom + padding.bottom)
  155 + ..lineTo(box.left + padding.left, box.top - padding.top)
  156 + ..lineTo(box.right - padding.right, box.top - padding.top)
  157 + ..lineTo(box.right - padding.right, box.bottom + padding.bottom)
  158 + ..fillPath();
  159 +
  160 + for (var c = 1; c < crossAxisCount; c++) {
  161 + switch (direction) {
  162 + case Axis.vertical:
  163 + context.canvas
  164 + ..drawRect(
  165 + box.left +
  166 + padding.left +
  167 + (_childCrossAxis + crossAxisSpacing) * c -
  168 + crossAxisSpacing,
  169 + box.bottom + padding.bottom,
  170 + math.max(crossAxisSpacing, 1.0),
  171 + box.height - padding.vertical)
  172 + ..fillPath();
  173 + break;
  174 + case Axis.horizontal:
  175 + break;
  176 + }
  177 + }
  178 +
  179 + for (var c = 1; c < _mainAxisCount; c++) {
  180 + switch (direction) {
  181 + case Axis.vertical:
  182 + context.canvas
  183 + ..drawRect(
  184 + box.left + padding.left,
  185 + box.bottom +
  186 + padding.bottom +
  187 + (_childMainAxis + mainAxisSpacing) * c -
  188 + mainAxisSpacing,
  189 + box.width - padding.horizontal,
  190 + math.max(mainAxisSpacing, 1.0))
  191 + ..fillPath();
  192 + break;
  193 + case Axis.horizontal:
  194 + break;
  195 + }
  196 + }
  197 + }
  198 +
  199 + @override
  200 + void paint(Context context) {
  201 + super.paint(context);
  202 +
  203 + final mat = Matrix4.identity();
  204 + mat.translate(box.x, box.y);
  205 + context.canvas
  206 + ..saveContext()
  207 + ..setTransform(mat);
  208 + for (var child in children) {
  209 + child.paint(context);
  210 + }
  211 + context.canvas.restoreContext();
  212 + }
  213 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class Image extends Widget {
  20 + final PdfImage image;
  21 + final double aspectRatio;
  22 + final BoxFit fit;
  23 +
  24 + Image(this.image, {this.fit = BoxFit.contain})
  25 + : assert(image != null),
  26 + aspectRatio = (image.height.toDouble() / image.width.toDouble());
  27 +
  28 + @override
  29 + void layout(Context context, BoxConstraints constraints,
  30 + {parentUsesSize = false}) {
  31 + final w = constraints.hasBoundedWidth
  32 + ? constraints.maxWidth
  33 + : constraints.constrainWidth(image.width.toDouble());
  34 + final h = constraints.hasBoundedHeight
  35 + ? constraints.maxHeight
  36 + : constraints.constrainHeight(image.height.toDouble());
  37 +
  38 + final FittedSizes sizes = applyBoxFit(
  39 + fit,
  40 + PdfPoint(image.width.toDouble(), image.height.toDouble()),
  41 + PdfPoint(w, h));
  42 + box = PdfRect.fromPoints(PdfPoint.zero, sizes.destination);
  43 + }
  44 +
  45 + @override
  46 + void paint(Context context) {
  47 + super.paint(context);
  48 +
  49 + context.canvas.drawImage(image, box.x, box.y, box.width, box.height);
  50 + }
  51 +}
  52 +
  53 +class Shape extends Widget {
  54 + final String shape;
  55 + final PdfColor strokeColor;
  56 + final PdfColor fillColor;
  57 + final double width;
  58 + final double height;
  59 + final double aspectRatio;
  60 + final BoxFit fit;
  61 +
  62 + Shape(
  63 + this.shape, {
  64 + this.strokeColor,
  65 + this.fillColor,
  66 + this.width = 1.0,
  67 + this.height = 1.0,
  68 + this.fit = BoxFit.contain,
  69 + }) : assert(width != null && width > 0.0),
  70 + assert(height != null && height > 0.0),
  71 + aspectRatio = height / width;
  72 +
  73 + @override
  74 + void layout(Context context, BoxConstraints constraints,
  75 + {parentUsesSize = false}) {
  76 + final w = constraints.hasBoundedWidth
  77 + ? constraints.maxWidth
  78 + : constraints.constrainWidth(width);
  79 + final h = constraints.hasBoundedHeight
  80 + ? constraints.maxHeight
  81 + : constraints.constrainHeight(height);
  82 +
  83 + final FittedSizes sizes =
  84 + applyBoxFit(fit, PdfPoint(width, height), PdfPoint(w, h));
  85 + box = PdfRect.fromPoints(PdfPoint.zero, sizes.destination);
  86 + }
  87 +
  88 + @override
  89 + void paint(Context context) {
  90 + super.paint(context);
  91 +
  92 + final mat = Matrix4.identity();
  93 + mat.translate(box.x, box.y + box.height);
  94 + mat.scale(box.width / width, -box.height / height);
  95 + context.canvas
  96 + ..saveContext()
  97 + ..setTransform(mat);
  98 +
  99 + if (fillColor != null) {
  100 + context.canvas
  101 + ..setFillColor(fillColor)
  102 + ..drawShape(shape, stroke: false)
  103 + ..fillPath();
  104 + }
  105 +
  106 + if (strokeColor != null) {
  107 + context.canvas
  108 + ..setStrokeColor(strokeColor)
  109 + ..drawShape(shape, stroke: true);
  110 + }
  111 +
  112 + context.canvas.restoreContext();
  113 + }
  114 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class Placeholder extends Widget {
  20 + final PdfColor color;
  21 + final double strokeWidth;
  22 + final double fallbackWidth;
  23 + final double fallbackHeight;
  24 +
  25 + Placeholder(
  26 + {this.color = const PdfColor.fromInt(0xFF455A64),
  27 + this.strokeWidth = 2.0,
  28 + this.fallbackWidth = 400.0,
  29 + this.fallbackHeight = 400.0});
  30 +
  31 + @override
  32 + void layout(Context context, BoxConstraints constraints,
  33 + {parentUsesSize = false}) {
  34 + box = PdfRect(
  35 + 0.0,
  36 + 0.0,
  37 + constraints.constrainWidth(
  38 + constraints.hasBoundedWidth ? constraints.maxWidth : fallbackWidth),
  39 + constraints.constrainHeight(constraints.hasBoundedHeight
  40 + ? constraints.maxHeight
  41 + : fallbackHeight));
  42 + }
  43 +
  44 + @protected
  45 + void paint(Context context) {
  46 + super.paint(context);
  47 +
  48 + context.canvas
  49 + ..setStrokeColor(color)
  50 + ..moveTo(box.x, box.y)
  51 + ..lineTo(box.right, box.top)
  52 + ..moveTo(box.x, box.top)
  53 + ..lineTo(box.right, box.y)
  54 + ..drawRect(box.x, box.y, box.width, box.height)
  55 + ..setLineWidth(strokeWidth)
  56 + ..strokePath();
  57 + }
  58 +}
  59 +
  60 +class PdfLogo extends StatelessWidget {
  61 + PdfLogo({this.color = PdfColor.red, this.fit = BoxFit.contain});
  62 +
  63 + final PdfColor color;
  64 + final BoxFit fit;
  65 +
  66 + static const pdf =
  67 + "M 2.424 26.712 L 2.424 26.712 C 2.076 26.712 1.742 26.599 1.457 26.386 C 0.416 25.605 0.276 24.736 0.342 24.144 C 0.524 22.516 2.537 20.812 6.327 19.076 C 7.831 15.78 9.262 11.719 10.115 8.326 C 9.117 6.154 8.147 3.336 8.854 1.683 C 9.102 1.104 9.411 0.66 9.988 0.468 C 10.216 0.392 10.792 0.296 11.004 0.296 C 11.508 0.296 11.951 0.945 12.265 1.345 C 12.56 1.721 13.229 2.518 11.892 8.147 C 13.24 10.931 15.15 13.767 16.98 15.709 C 18.291 15.472 19.419 15.351 20.338 15.351 C 21.904 15.351 22.853 15.716 23.24 16.468 C 23.56 17.09 23.429 17.817 22.85 18.628 C 22.293 19.407 21.525 19.819 20.63 19.819 C 19.414 19.819 17.998 19.051 16.419 17.534 C 13.582 18.127 10.269 19.185 7.591 20.356 C 6.755 22.13 5.954 23.559 5.208 24.607 C 4.183 26.042 3.299 26.712 2.424 26.712 Z M 5.086 21.586 C 2.949 22.787 2.078 23.774 2.015 24.33 C 2.005 24.422 1.978 24.664 2.446 25.022 C 2.595 24.975 3.465 24.578 5.086 21.586 Z M 18.723 17.144 C 19.538 17.771 19.737 18.088 20.27 18.088 C 20.504 18.088 21.171 18.078 21.48 17.647 C 21.629 17.438 21.687 17.304 21.71 17.232 C 21.587 17.167 21.424 17.035 20.535 17.035 C 20.03 17.036 19.395 17.058 18.723 17.144 Z M 11.253 10.562 C 10.538 13.036 9.594 15.707 8.579 18.126 C 10.669 17.315 12.941 16.607 15.075 16.106 C 13.725 14.538 12.376 12.58 11.253 10.562 Z M 10.646 2.1 C 10.548 2.133 9.316 3.857 10.742 5.316 C 11.691 3.201 10.689 2.086 10.646 2.1 Z";
  68 +
  69 + @override
  70 + Widget build(Context context) {
  71 + return Shape(pdf, width: 24.0, height: 27.0, fillColor: color, fit: fit);
  72 + }
  73 +}
  74 +
  75 +class FlutterLogo extends PdfLogo {}
  76 +
  77 +class LoremText {
  78 + LoremText({math.Random random}) : random = random ?? math.Random(978);
  79 +
  80 + final math.Random random;
  81 +
  82 + static final words =
  83 + "ad adipiscing aliqua aliquip amet anim aute cillum commodo consectetur consequat culpa cupidatat deserunt do dolor dolore duis ea eiusmod elit enim esse est et eu ex excepteur exercitation fugiat id in incididunt ipsum irure labore laboris laborum lorem magna minim mollit nisi non nostrud nulla occaecat officia pariatur proident qui quis reprehenderit sed sint sit sunt tempor ullamco ut velit veniam voluptate"
  84 + .split(" ");
  85 +
  86 + String word() {
  87 + return words[random.nextInt(words.length - 1)];
  88 + }
  89 +
  90 + String sentence(int length) {
  91 + final wordList = List<String>();
  92 + for (var i = 0; i < length; i++) {
  93 + var w = word();
  94 + if (i < length - 1 && random.nextInt(10) == 0) w += ",";
  95 + wordList.add(w);
  96 + }
  97 + var text = wordList.join(" ") + ".";
  98 + return text[0].toUpperCase() + text.substring(1);
  99 + }
  100 +
  101 + String paragraph(int length) {
  102 + var wordsCount = 0;
  103 + final sentenceList = List<String>();
  104 + var n = 0;
  105 + while (wordsCount < length) {
  106 + n++;
  107 + if (n > 100) break;
  108 + var count = math.min(length,
  109 + math.max(10, math.min(3, random.nextInt(length - wordsCount))));
  110 + sentenceList.add(sentence(count));
  111 + wordsCount += count;
  112 + }
  113 + return sentenceList.join(" ");
  114 + }
  115 +}
  116 +
  117 +class Lorem extends StatelessWidget {
  118 + Lorem(
  119 + {this.length = 50,
  120 + this.random,
  121 + this.style,
  122 + this.textAlign = TextAlign.left,
  123 + this.softWrap = true,
  124 + this.textScaleFactor = 1.0,
  125 + this.maxLines});
  126 +
  127 + final int length;
  128 + final math.Random random;
  129 + final TextStyle style;
  130 + final TextAlign textAlign;
  131 + final bool softWrap;
  132 + final double textScaleFactor;
  133 + final int maxLines;
  134 +
  135 + @override
  136 + Widget build(Context context) {
  137 + final lorem = LoremText(random: random);
  138 + final text = lorem.paragraph(length);
  139 +
  140 + return Text(text,
  141 + style: style,
  142 + textAlign: textAlign,
  143 + softWrap: softWrap,
  144 + textScaleFactor: textScaleFactor,
  145 + maxLines: maxLines);
  146 + }
  147 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +/// A horizontal group of cells in a [Table].
  20 +@immutable
  21 +class TableRow {
  22 + const TableRow({this.children, this.repeat = false});
  23 +
  24 + /// The widgets that comprise the cells in this row.
  25 + final List<Widget> children;
  26 +
  27 + /// Repeat this row on all pages
  28 + final bool repeat;
  29 +}
  30 +
  31 +enum TableCellVerticalAlignment { bottom, middle, top }
  32 +
  33 +enum TableWidth { min, max }
  34 +
  35 +class TableBorder extends BoxBorder {
  36 + const TableBorder(
  37 + {bool left = true,
  38 + bool top = true,
  39 + bool right = true,
  40 + bool bottom = true,
  41 + this.horizontalInside = true,
  42 + this.verticalInside = true,
  43 + PdfColor color = PdfColor.black,
  44 + double width = 1.0})
  45 + : super(
  46 + left: left,
  47 + top: top,
  48 + right: right,
  49 + bottom: bottom,
  50 + color: color,
  51 + width: width);
  52 +
  53 + final bool horizontalInside;
  54 + final bool verticalInside;
  55 +
  56 + @override
  57 + void paintBorders(Context context, PdfRect box,
  58 + [List<double> widths, List<double> heights]) {
  59 + super.paintBorders(context, box);
  60 +
  61 + if (verticalInside) {
  62 + var offset = box.x;
  63 + for (var width in widths.sublist(0, widths.length - 1)) {
  64 + offset += width;
  65 + context.canvas.moveTo(offset, box.y);
  66 + context.canvas.lineTo(offset, box.top);
  67 + }
  68 + context.canvas.strokePath();
  69 + }
  70 +
  71 + if (horizontalInside) {
  72 + var offset = box.top;
  73 + for (var height in heights.sublist(0, heights.length - 1)) {
  74 + offset -= height;
  75 + context.canvas.moveTo(box.x, offset);
  76 + context.canvas.lineTo(box.right, offset);
  77 + }
  78 + context.canvas.strokePath();
  79 + }
  80 + }
  81 +}
  82 +
  83 +class _TableContext extends WidgetContext {
  84 + var firstLine = 0;
  85 + var lastLine = 0;
  86 +}
  87 +
  88 +/// A widget that uses the table layout algorithm for its children.
  89 +class Table extends Widget implements SpanningWidget {
  90 + Table(
  91 + {this.children = const <TableRow>[],
  92 + this.border,
  93 + this.defaultVerticalAlignment = TableCellVerticalAlignment.top,
  94 + this.tableWidth = TableWidth.max})
  95 + : assert(children != null),
  96 + super();
  97 +
  98 + @override
  99 + bool get canSpan => true;
  100 +
  101 + /// The rows of the table.
  102 + final List<TableRow> children;
  103 +
  104 + final TableBorder border;
  105 +
  106 + final TableCellVerticalAlignment defaultVerticalAlignment;
  107 +
  108 + final TableWidth tableWidth;
  109 +
  110 + final _widths = List<double>();
  111 + final _heights = List<double>();
  112 +
  113 + var _context = _TableContext();
  114 +
  115 + @override
  116 + WidgetContext saveContext() {
  117 + return _context;
  118 + }
  119 +
  120 + @override
  121 + void restoreContext(WidgetContext context) {
  122 + _context = context;
  123 + _context.firstLine = _context.lastLine;
  124 + }
  125 +
  126 + @override
  127 + void layout(Context context, BoxConstraints constraints,
  128 + {parentUsesSize = false}) {
  129 + // Compute required width for all row/columns width flex
  130 + final flex = List<double>();
  131 + _widths.clear();
  132 + _heights.clear();
  133 + var index = 0;
  134 +
  135 + for (var row in children) {
  136 + var n = 0;
  137 + for (var child in row.children) {
  138 + child.layout(context, BoxConstraints());
  139 + final calculatedWidth =
  140 + child.box.width == double.infinity ? 0.0 : child.box.width;
  141 + final childFlex = child._flex.toDouble();
  142 + if (flex.length < n + 1) {
  143 + flex.add(childFlex);
  144 + _widths.add(calculatedWidth);
  145 + } else {
  146 + if (childFlex > 0) {
  147 + flex[n] *= childFlex;
  148 + }
  149 + _widths[n] = math.max(_widths[n], calculatedWidth);
  150 + }
  151 + n++;
  152 + }
  153 + }
  154 +
  155 + final maxWidth = _widths.reduce((a, b) => a + b);
  156 +
  157 + // Compute column widths using flex and estimated width
  158 + if (constraints.hasBoundedWidth) {
  159 + final totalFlex = flex.reduce((a, b) => a + b);
  160 + var flexSpace = 0.0;
  161 + for (var n = 0; n < _widths.length; n++) {
  162 + if (flex[n] == 0.0) {
  163 + var newWidth = _widths[n] / maxWidth * constraints.maxWidth;
  164 + if ((tableWidth == TableWidth.max && totalFlex == 0.0) ||
  165 + newWidth < _widths[n]) {
  166 + _widths[n] = newWidth;
  167 + }
  168 + flexSpace += _widths[n];
  169 + }
  170 + }
  171 + final spacePerFlex = totalFlex > 0.0
  172 + ? ((constraints.maxWidth - flexSpace) / totalFlex)
  173 + : double.nan;
  174 +
  175 + for (var n = 0; n < _widths.length; n++) {
  176 + if (flex[n] > 0.0) {
  177 + var newWidth = spacePerFlex * flex[n];
  178 + _widths[n] = newWidth;
  179 + }
  180 + }
  181 + }
  182 +
  183 + final totalWidth = _widths.reduce((a, b) => a + b);
  184 +
  185 + // Compute final widths
  186 + var totalHeight = 0.0;
  187 + index = 0;
  188 + for (var row in children) {
  189 + if (index++ < _context.firstLine && !row.repeat) continue;
  190 +
  191 + var n = 0;
  192 + var x = 0.0;
  193 +
  194 + var lineHeight = 0.0;
  195 + for (var child in row.children) {
  196 + final childConstraints = BoxConstraints.tightFor(width: _widths[n]);
  197 + child.layout(context, childConstraints);
  198 + child.box = PdfRect(x, totalHeight, child.box.width, child.box.height);
  199 + x += _widths[n];
  200 + lineHeight = math.max(lineHeight, child.box.height);
  201 + n++;
  202 + }
  203 +
  204 + if (totalHeight + lineHeight > constraints.maxHeight) {
  205 + index--;
  206 + break;
  207 + }
  208 + totalHeight += lineHeight;
  209 + _heights.add(lineHeight);
  210 + }
  211 + _context.lastLine = index;
  212 +
  213 + // Compute final y position
  214 + index = 0;
  215 + for (var row in children) {
  216 + if (index++ < _context.firstLine && !row.repeat) continue;
  217 +
  218 + for (var child in row.children) {
  219 + child.box = PdfRect(
  220 + child.box.x,
  221 + totalHeight - child.box.y - child.box.height,
  222 + child.box.width,
  223 + child.box.height);
  224 + }
  225 +
  226 + if (index >= _context.lastLine) break;
  227 + }
  228 +
  229 + box = PdfRect(0.0, 0.0, totalWidth, totalHeight);
  230 + }
  231 +
  232 + @override
  233 + void paint(Context context) {
  234 + super.paint(context);
  235 +
  236 + final mat = Matrix4.identity();
  237 + mat.translate(box.x, box.y);
  238 + context.canvas
  239 + ..saveContext()
  240 + ..setTransform(mat);
  241 +
  242 + var index = 0;
  243 + for (var row in children) {
  244 + if (index++ < _context.firstLine && !row.repeat) continue;
  245 + for (var child in row.children) {
  246 + child.paint(context);
  247 + }
  248 + if (index >= _context.lastLine) break;
  249 + }
  250 + context.canvas.restoreContext();
  251 +
  252 + if (border != null) {
  253 + border.paintBorders(context, box, _widths, _heights);
  254 + }
  255 + }
  256 +
  257 + factory Table.fromTextArray(
  258 + {@required Context context, @required List<List<String>> data}) {
  259 + final rows = List<TableRow>();
  260 + for (var row in data) {
  261 + final tableRow = List<Widget>();
  262 + if (row == data.first) {
  263 + for (var cell in row) {
  264 + tableRow.add(Container(
  265 + alignment: Alignment.center,
  266 + margin: EdgeInsets.all(5),
  267 + child: Text(cell, style: Theme.of(context).tableHeader)));
  268 + }
  269 + } else {
  270 + for (var cell in row) {
  271 + tableRow.add(Container(
  272 + margin: EdgeInsets.all(5),
  273 + child: Text(cell, style: Theme.of(context).tableCell)));
  274 + }
  275 + }
  276 + rows.add(TableRow(children: tableRow, repeat: row == data.first));
  277 + }
  278 + return Table(
  279 + border: TableBorder(), tableWidth: TableWidth.max, children: rows);
  280 + }
  281 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +@immutable
  20 +class TextStyle {
  21 + const TextStyle({
  22 + this.color = PdfColor.black,
  23 + 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,
  29 + this.background,
  30 + }) : assert(font != null),
  31 + assert(color != null);
  32 +
  33 + final PdfColor color;
  34 +
  35 + final PdfFont font;
  36 +
  37 + final double fontSize;
  38 +
  39 + static const double _defaultFontSize = 12.0 * PdfPageFormat.point;
  40 +
  41 + final double letterSpacing;
  42 +
  43 + final double lineSpacing;
  44 +
  45 + final double wordSpacing;
  46 +
  47 + final double height;
  48 +
  49 + final PdfColor background;
  50 +
  51 + TextStyle copyWith({
  52 + PdfColor color,
  53 + PdfFont font,
  54 + double fontSize,
  55 + double letterSpacing,
  56 + double wordSpacing,
  57 + double lineSpacing,
  58 + double height,
  59 + PdfColor background,
  60 + }) {
  61 + return TextStyle(
  62 + color: color ?? this.color,
  63 + font: font ?? this.font,
  64 + fontSize: fontSize ?? this.fontSize,
  65 + letterSpacing: letterSpacing ?? this.letterSpacing,
  66 + wordSpacing: wordSpacing ?? this.wordSpacing,
  67 + lineSpacing: lineSpacing ?? this.lineSpacing,
  68 + height: height ?? this.height,
  69 + background: background ?? this.background,
  70 + );
  71 + }
  72 +}
  73 +
  74 +enum TextAlign { left, right, center, justify }
  75 +
  76 +class _Word {
  77 + final String text;
  78 + PdfRect _box;
  79 +
  80 + _Word(this.text, this._box);
  81 +
  82 + String toString() {
  83 + return "Word $text $_box";
  84 + }
  85 +}
  86 +
  87 +class Text extends Widget {
  88 + final String data;
  89 +
  90 + TextStyle style;
  91 +
  92 + final TextAlign textAlign;
  93 +
  94 + final double textScaleFactor;
  95 +
  96 + final int maxLines;
  97 +
  98 + Text(
  99 + this.data, {
  100 + this.style,
  101 + this.textAlign = TextAlign.left,
  102 + softWrap = true,
  103 + this.textScaleFactor = 1.0,
  104 + int maxLines,
  105 + }) : maxLines = !softWrap ? 1 : maxLines,
  106 + assert(data != null);
  107 +
  108 + final _words = List<_Word>();
  109 +
  110 + double _realignLine(
  111 + List<_Word> words, double totalWidth, double wordsWidth, bool last) {
  112 + var delta = 0.0;
  113 + switch (textAlign) {
  114 + case TextAlign.left:
  115 + return wordsWidth;
  116 + case TextAlign.right:
  117 + delta = totalWidth - wordsWidth;
  118 + break;
  119 + case TextAlign.center:
  120 + delta = (totalWidth - wordsWidth) / 2.0;
  121 + break;
  122 + case TextAlign.justify:
  123 + if (last) return wordsWidth;
  124 + delta = (totalWidth - wordsWidth) / (words.length - 1);
  125 + var x = 0.0;
  126 + for (var word in words) {
  127 + word._box = PdfRect(
  128 + word._box.x + x, word._box.y, word._box.width, word._box.height);
  129 + x += delta;
  130 + }
  131 + return totalWidth;
  132 + }
  133 + for (var word in words) {
  134 + word._box = PdfRect(
  135 + word._box.x + delta, word._box.y, word._box.width, word._box.height);
  136 + }
  137 + return totalWidth;
  138 + }
  139 +
  140 + @override
  141 + void layout(Context context, BoxConstraints constraints,
  142 + {parentUsesSize = false}) {
  143 + _words.clear();
  144 +
  145 + if (style == null) {
  146 + style = Theme.of(context).defaultTextStyle;
  147 + }
  148 +
  149 + final cw = constraints.hasBoundedWidth
  150 + ? constraints.maxWidth
  151 + : constraints.constrainWidth();
  152 + final ch = constraints.hasBoundedHeight
  153 + ? constraints.maxHeight
  154 + : constraints.constrainHeight();
  155 +
  156 + var x = 0.0;
  157 + var y = 0.0;
  158 + var w = 0.0;
  159 + var h = 0.0;
  160 + var lh = 0.0;
  161 +
  162 + final space =
  163 + style.font.stringBounds(" ") * (style.fontSize * textScaleFactor);
  164 +
  165 + var lines = 1;
  166 + var wCount = 0;
  167 + var lineStart = 0;
  168 +
  169 + for (var word in data.split(" ")) {
  170 + final box =
  171 + style.font.stringBounds(word) * (style.fontSize * textScaleFactor);
  172 +
  173 + var ww = box.width;
  174 + var wh = box.height;
  175 +
  176 + if (x + ww > cw) {
  177 + if (wCount == 0) break;
  178 + w = math.max(
  179 + w,
  180 + _realignLine(
  181 + _words.sublist(lineStart), cw, x - space.width, false));
  182 + lineStart += wCount;
  183 + if (maxLines != null && ++lines > maxLines) break;
  184 +
  185 + x = 0.0;
  186 + y += lh + style.lineSpacing;
  187 + h += lh + style.lineSpacing;
  188 + lh = 0.0;
  189 + if (y > ch) break;
  190 + wCount = 0;
  191 + }
  192 +
  193 + var wx = x;
  194 + var wy = y;
  195 +
  196 + x += ww + space.width;
  197 + lh = math.max(lh, wh);
  198 +
  199 + final wd = _Word(word, PdfRect(box.x + wx, box.y + wy + wh, ww, wh));
  200 + _words.add(wd);
  201 + wCount++;
  202 + }
  203 + w = math.max(
  204 + w, _realignLine(_words.sublist(lineStart), cw, x - space.width, true));
  205 + h += lh;
  206 + box = PdfRect(0.0, 0.0, constraints.constrainWidth(w),
  207 + constraints.constrainHeight(h));
  208 + }
  209 +
  210 + @protected
  211 + void debugPaint(Context context) {
  212 + context.canvas
  213 + ..setStrokeColor(PdfColor.blue)
  214 + ..drawRect(box.x, box.y, box.width, box.height)
  215 + ..strokePath();
  216 + }
  217 +
  218 + @override
  219 + void paint(Context context) {
  220 + super.paint(context);
  221 + context.canvas.setFillColor(style.color);
  222 +
  223 + for (var word in _words) {
  224 + context.canvas.drawString(style.font, style.fontSize * textScaleFactor,
  225 + word.text, box.x + word._box.x, box.y + box.height - word._box.y);
  226 + }
  227 + }
  228 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +class Theme extends Inherited {
  20 + final PdfDocument document;
  21 +
  22 + Theme(this.document);
  23 +
  24 + static Theme of(Context context) {
  25 + return context.inherited[Theme];
  26 + }
  27 +
  28 + TextStyle _defaultTextStyle;
  29 +
  30 + TextStyle get defaultTextStyle {
  31 + if (_defaultTextStyle == null) {
  32 + _defaultTextStyle = TextStyle(font: PdfFont.helvetica(document));
  33 + }
  34 + return _defaultTextStyle;
  35 + }
  36 +
  37 + TextStyle _defaultTextStyleBold;
  38 +
  39 + TextStyle get defaultTextStyleBold {
  40 + if (_defaultTextStyleBold == null) {
  41 + _defaultTextStyleBold =
  42 + defaultTextStyle.copyWith(font: PdfFont.helveticaBold(document));
  43 + }
  44 + return _defaultTextStyleBold;
  45 + }
  46 +
  47 + TextStyle _paragraphStyle;
  48 +
  49 + TextStyle get paragraphStyle {
  50 + if (_paragraphStyle == null) {
  51 + _paragraphStyle = defaultTextStyle.copyWith(lineSpacing: 5.0);
  52 + }
  53 + return _paragraphStyle;
  54 + }
  55 +
  56 + TextStyle _bulletStyle;
  57 +
  58 + TextStyle get bulletStyle {
  59 + if (_bulletStyle == null) {
  60 + _bulletStyle = defaultTextStyle.copyWith(lineSpacing: 5.0);
  61 + }
  62 + return _bulletStyle;
  63 + }
  64 +
  65 + TextStyle _tableHeader;
  66 +
  67 + TextStyle get tableHeader {
  68 + if (_tableHeader == null) {
  69 + _tableHeader = defaultTextStyleBold;
  70 + }
  71 + return _tableHeader;
  72 + }
  73 +
  74 + TextStyle _tableCell;
  75 +
  76 + TextStyle get tableCell {
  77 + if (_tableCell == null) {
  78 + _tableCell = defaultTextStyle;
  79 + }
  80 + return _tableCell;
  81 + }
  82 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +@immutable
  20 +class Context {
  21 + final PdfPage page;
  22 + final PdfGraphics canvas;
  23 +
  24 + final Map<Type, Inherited> inherited;
  25 +
  26 + get pageNumber => page.pdfDocument.pdfPageList.pages.indexOf(page) + 1;
  27 +
  28 + const Context({this.page, this.canvas, this.inherited});
  29 +
  30 + Context copyWith(
  31 + {PdfPage page, PdfGraphics canvas, Map<Type, Inherited> inherited}) {
  32 + return Context(
  33 + page: page ?? this.page,
  34 + canvas: canvas ?? this.canvas,
  35 + inherited: inherited ?? this.inherited);
  36 + }
  37 +
  38 + Context inheritFrom(Inherited object) {
  39 + final inherited = this.inherited;
  40 + inherited[object.runtimeType] = object;
  41 + return copyWith(inherited: inherited);
  42 + }
  43 +}
  44 +
  45 +class Inherited {}
  46 +
  47 +abstract class Widget {
  48 + PdfRect box;
  49 + var _flex = 0;
  50 + FlexFit _fit = FlexFit.loose;
  51 +
  52 + Widget();
  53 +
  54 + @protected
  55 + void layout(Context context, BoxConstraints constraints,
  56 + {parentUsesSize = false});
  57 +
  58 + @protected
  59 + void paint(Context context) {
  60 + assert(() {
  61 + if (Document.debug) debugPaint(context);
  62 + return true;
  63 + }());
  64 + }
  65 +
  66 + @protected
  67 + void debugPaint(Context context) {
  68 + context.canvas
  69 + ..setStrokeColor(PdfColor.purple)
  70 + ..drawRect(box.x, box.y, box.width, box.height)
  71 + ..strokePath();
  72 + }
  73 +}
  74 +
  75 +class WidgetContext {}
  76 +
  77 +abstract class SpanningWidget {
  78 + bool get canSpan => false;
  79 +
  80 + @protected
  81 + WidgetContext saveContext();
  82 +
  83 + @protected
  84 + void restoreContext(WidgetContext context);
  85 +}
  86 +
  87 +abstract class StatelessWidget extends Widget {
  88 + Widget _child;
  89 +
  90 + StatelessWidget() : super();
  91 +
  92 + @override
  93 + void layout(Context context, BoxConstraints constraints,
  94 + {parentUsesSize = false}) {
  95 + if (_child == null) _child = build(context);
  96 +
  97 + if (_child != null) {
  98 + _child.layout(context, constraints, parentUsesSize: parentUsesSize);
  99 + box = _child.box;
  100 + } else {
  101 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.smallest);
  102 + }
  103 + }
  104 +
  105 + @override
  106 + void paint(Context context) {
  107 + super.paint(context);
  108 +
  109 + if (_child != null) {
  110 + final mat = Matrix4.identity();
  111 + mat.translate(box.x, box.y);
  112 + context.canvas
  113 + ..saveContext()
  114 + ..setTransform(mat);
  115 + _child.paint(context);
  116 + context.canvas.restoreContext();
  117 + }
  118 + }
  119 +
  120 + @protected
  121 + Widget build(Context context);
  122 +}
  123 +
  124 +abstract class SingleChildWidget extends Widget {
  125 + SingleChildWidget({this.child}) : super();
  126 +
  127 + final Widget child;
  128 +
  129 + @override
  130 + void layout(Context context, BoxConstraints constraints,
  131 + {parentUsesSize = false}) {
  132 + if (child != null) {
  133 + child.layout(context, constraints, parentUsesSize: parentUsesSize);
  134 + box = child.box;
  135 + } else {
  136 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.smallest);
  137 + }
  138 + }
  139 +
  140 + @override
  141 + void paint(Context context) {
  142 + super.paint(context);
  143 +
  144 + if (child != null) {
  145 + final mat = Matrix4.identity();
  146 + mat.translate(box.x, box.y);
  147 + context.canvas
  148 + ..saveContext()
  149 + ..setTransform(mat);
  150 + child.paint(context);
  151 + context.canvas.restoreContext();
  152 + }
  153 + }
  154 +}
  155 +
  156 +abstract class MultiChildWidget extends Widget {
  157 + MultiChildWidget({this.children = const <Widget>[]}) : super();
  158 +
  159 + final List<Widget> children;
  160 +}
@@ -2,7 +2,7 @@ name: pdf @@ -2,7 +2,7 @@ name: pdf
2 author: David PHAM-VAN <dev.nfet.net@gmail.com> 2 author: David PHAM-VAN <dev.nfet.net@gmail.com>
3 description: A pdf producer for Dart. It can create pdf files for both web or flutter. 3 description: A pdf producer for Dart. It can create pdf files for both web or flutter.
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
5 -version: 1.2.0 5 +version: 1.3.0
6 6
7 environment: 7 environment:
8 sdk: ">=1.8.0 <3.0.0" 8 sdk: ">=1.8.0 <3.0.0"
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'dart:convert';
  18 +import 'dart:io';
  19 +
  20 +import 'package:pdf/pdf.dart';
  21 +import 'package:pdf/widgets.dart';
  22 +import 'package:test/test.dart';
  23 +
  24 +void main() {
  25 + test('Pdf', () {
  26 + Document.debug = true;
  27 +
  28 + var pdf = Document();
  29 +
  30 + final symbol = TextStyle(font: PdfFont.zapfDingbats(pdf.document));
  31 +
  32 + final imData = zlib.decode(base64.decode(
  33 + "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"));
  34 + final im = PdfImage(pdf.document, image: imData, width: 16, height: 20);
  35 +
  36 + pdf.addPage(Page(
  37 + pageFormat: PdfPageFormat(400.0, 400.0),
  38 + margin: EdgeInsets.all(10.0),
  39 + build: (Context context) => Column(children: <Widget>[
  40 + Container(
  41 + padding: EdgeInsets.all(5),
  42 + margin: EdgeInsets.only(bottom: 10),
  43 + decoration: BoxDecoration(
  44 + color: PdfColor.amber,
  45 + border: BoxBorder(
  46 + top: true,
  47 + bottom: true,
  48 + left: true,
  49 + right: true,
  50 + width: 2.0)),
  51 + child: Text("Hello World",
  52 + textScaleFactor: 2.0, textAlign: TextAlign.center)),
  53 + Align(alignment: Alignment.topLeft, child: Text("Left align")),
  54 + Padding(padding: EdgeInsets.all(5.0)),
  55 + Row(
  56 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  57 + children: <Widget>[
  58 + Image(im),
  59 + PdfLogo(),
  60 + Column(children: <Widget>[
  61 + Text("(", style: symbol),
  62 + Text("4", style: symbol),
  63 + ]),
  64 + ]),
  65 + Padding(
  66 + padding: EdgeInsets.only(left: 30, top: 20),
  67 + child: Lorem(textAlign: TextAlign.justify)),
  68 + Expanded(
  69 + child: Transform.scale(
  70 + child: Transform.rotate(
  71 + child: FittedBox(child: Text("Expanded")),
  72 + angle: 0.2),
  73 + scale: 0.9)),
  74 + Container(
  75 + padding: EdgeInsets.only(top: 5),
  76 + decoration:
  77 + BoxDecoration(border: BoxBorder(top: true, width: 1.0)),
  78 + child: Text("That's all Folks!",
  79 + textAlign: TextAlign.center,
  80 + style: Theme.of(context).defaultTextStyle.copyWith(
  81 + font: PdfFont.timesBoldItalic(pdf.document)),
  82 + textScaleFactor: 3.0)),
  83 + ])));
  84 +
  85 + pdf.addPage(Page(
  86 + pageFormat: PdfPageFormat(400.0, 400.0),
  87 + margin: EdgeInsets.all(10.0),
  88 + build: (Context context) => Center(
  89 + child: GridView(
  90 + crossAxisCount: 3,
  91 + direction: Axis.vertical,
  92 + crossAxisSpacing: 10.0,
  93 + mainAxisSpacing: 10.0,
  94 + padding: EdgeInsets.all(10.0),
  95 + children: List<Widget>.generate(
  96 + 9, (n) => FittedBox(child: Text("${n + 1}")))))));
  97 +
  98 + pdf.addPage(MultiPage(
  99 + pageFormat: PdfPageFormat(400.0, 200.0),
  100 + margin: EdgeInsets.all(10.0),
  101 + build: (Context context) => <Widget>[
  102 + Table.fromTextArray(context: context, data: [
  103 + ["Company", "Contact", "Country"],
  104 + ["Alfreds Futterkiste", "Maria Anders", "Germany"],
  105 + ["Centro comercial Moctezuma", "Francisco Chang", "Mexico"],
  106 + ["Ernst Handel", "Roland Mendel", "Austria"],
  107 + ["Island Trading", "Helen Bennett", "UK"],
  108 + ["Laughing Bacchus Winecellars", "Yoshi Tannamuri", "Canada"],
  109 + ["Magazzini Alimentari Riuniti", "Giovanni Rovelli", "Italy"],
  110 + ["Spaceage Stereo", "Igor Cavalcanti Pereira", "Brasil"],
  111 + ["Team Uno", "Frantisek Stefánek", "Czech Republic"],
  112 + ["Isaly's", "Michelle J. Kristensen", "Danmark"],
  113 + ["Albers", "Marjolaine Laramée", "France"],
  114 + ["Dynatronics Accessories", "Cong Ch'en", "China"],
  115 + ["York Steak House", "Outi Vuorinen", "Finland"],
  116 + ["Weathervane", "Else Jeremiassen", "Iceland"],
  117 + ]),
  118 + CustomPaint(
  119 + size: PdfPoint(50, 50),
  120 + painter: (PdfGraphics canvas, PdfPoint size) {
  121 + canvas
  122 + ..setColor(PdfColor.indigo)
  123 + ..drawRRect(0, 0, size.x, size.y, 10, 10)
  124 + ..fillPath();
  125 + }),
  126 + ]));
  127 +
  128 + var file = File('widgets.pdf');
  129 + file.writeAsBytesSync(pdf.document.save());
  130 + });
  131 +}
  1 +# 1.3.0
  2 +* Add a Flutter like widget system
  3 +
1 # 1.2.0 4 # 1.2.0
2 * Fix compileSdkVersion to match appcompat 5 * Fix compileSdkVersion to match appcompat
3 * Change license to Apache 2.0 6 * Change license to Apache 2.0
1 # Pdf Printing Example 1 # Pdf Printing Example
2 2
3 -Flutter example project  
4 -  
5 -## Getting Started  
6 -  
7 -1. to run the example, start a simulator and run:  
8 -  
9 - ```shell  
10 - flutter run  
11 - ``` 3 +```dart
  4 +void printPdf() {
  5 + Printing.layoutPdf(onLayout: (PdfPageFormat format) {
  6 + final pdf = Document()
  7 + ..addPage(Page(
  8 + pageFormat: PdfPageFormat.a4,
  9 + build: (Context context) {
  10 + return Center(
  11 + child: Text("Hello World"),
  12 + ); // Center
  13 + }));
  14 + return pdf.save();
  15 + }); // Page
  16 +}
  17 +```
1 -import 'dart:io';  
2 import 'dart:async'; 1 import 'dart:async';
3 2
  3 +import 'package:flutter/widgets.dart' as fw;
  4 +
4 import 'package:pdf/pdf.dart'; 5 import 'package:pdf/pdf.dart';
  6 +import 'package:pdf/widgets.dart';
  7 +import 'package:printing/printing.dart';
5 8
6 -Future<PdfDocument> generateDocument(PdfPageFormat format) async {  
7 - final pdf = PdfDocument(deflate: zlib.encode);  
8 - final page = PdfPage(pdf,  
9 - pageFormat: format.applyMargin(  
10 - left: 2.0 * PdfPageFormat.cm,  
11 - top: 2.0 * PdfPageFormat.cm,  
12 - right: 2.0 * PdfPageFormat.cm,  
13 - bottom: 2.0 * PdfPageFormat.cm));  
14 - final g = page.getGraphics();  
15 - final font = PdfFont.helvetica(pdf);  
16 - final top = page.pageFormat.height - page.pageFormat.marginTop; 9 +const green = PdfColor.fromInt(0xff9ce5d0);
  10 +const lightGreen = PdfColor.fromInt(0xffcdf1e7);
17 11
18 - g.setColor(PdfColor.orange);  
19 - g.drawRect(  
20 - page.pageFormat.marginLeft,  
21 - page.pageFormat.marginBottom,  
22 - page.pageFormat.width -  
23 - page.pageFormat.marginRight -  
24 - page.pageFormat.marginLeft,  
25 - page.pageFormat.height -  
26 - page.pageFormat.marginTop -  
27 - page.pageFormat.marginBottom);  
28 - g.strokePath(); 12 +class MyPage extends Page {
  13 + MyPage(
  14 + {PdfPageFormat pageFormat = PdfPageFormat.a4,
  15 + BuildCallback build,
  16 + EdgeInsets margin})
  17 + : super(pageFormat: pageFormat, margin: margin, build: build);
29 18
30 - g.setColor(PdfColor(0.0, 1.0, 1.0));  
31 - g.drawRRect(  
32 - 50.0 * PdfPageFormat.mm,  
33 - top - 80.0 * PdfPageFormat.mm,  
34 - 100.0 * PdfPageFormat.mm,  
35 - 50.0 * PdfPageFormat.mm,  
36 - 20.0,  
37 - 20.0,  
38 - );  
39 - g.fillPath(); 19 + void paint(Widget child, Context context) {
  20 + context.canvas
  21 + ..setColor(lightGreen)
  22 + ..moveTo(0, pageFormat.height)
  23 + ..lineTo(0, pageFormat.height - 230)
  24 + ..lineTo(60, pageFormat.height)
  25 + ..fillPath()
  26 + ..setColor(green)
  27 + ..moveTo(0, pageFormat.height)
  28 + ..lineTo(0, pageFormat.height - 100)
  29 + ..lineTo(100, pageFormat.height)
  30 + ..fillPath()
  31 + ..setColor(lightGreen)
  32 + ..moveTo(30, pageFormat.height)
  33 + ..lineTo(110, pageFormat.height - 50)
  34 + ..lineTo(150, pageFormat.height)
  35 + ..fillPath()
  36 + ..moveTo(pageFormat.width, 0)
  37 + ..lineTo(pageFormat.width, 230)
  38 + ..lineTo(pageFormat.width - 60, 0)
  39 + ..fillPath()
  40 + ..setColor(green)
  41 + ..moveTo(pageFormat.width, 0)
  42 + ..lineTo(pageFormat.width, 100)
  43 + ..lineTo(pageFormat.width - 100, 0)
  44 + ..fillPath()
  45 + ..setColor(lightGreen)
  46 + ..moveTo(pageFormat.width - 30, 0)
  47 + ..lineTo(pageFormat.width - 110, 50)
  48 + ..lineTo(pageFormat.width - 150, 0)
  49 + ..fillPath();
40 50
41 - g.setColor(PdfColor(0.3, 0.3, 0.3));  
42 - g.drawString(  
43 - font,  
44 - 12.0,  
45 - "Hello World!",  
46 - page.pageFormat.marginLeft + 10.0 * PdfPageFormat.mm,  
47 - top - 10.0 * PdfPageFormat.mm); 51 + super.paint(child, context);
  52 + }
  53 +}
48 54
49 - {  
50 - final page = PdfPage(pdf,  
51 - pageFormat: format.applyMargin(  
52 - left: 2.0 * PdfPageFormat.cm,  
53 - top: 2.0 * PdfPageFormat.cm,  
54 - right: 2.0 * PdfPageFormat.cm,  
55 - bottom: 2.0 * PdfPageFormat.cm));  
56 - final g = page.getGraphics();  
57 - final font = PdfFont.helvetica(pdf);  
58 - final top = page.pageFormat.height - page.pageFormat.marginTop; 55 +class Block extends StatelessWidget {
  56 + Block({this.title});
59 57
60 - g.setColor(PdfColor.orange);  
61 - g.drawRect(  
62 - page.pageFormat.marginLeft,  
63 - page.pageFormat.marginBottom,  
64 - page.pageFormat.width -  
65 - page.pageFormat.marginRight -  
66 - page.pageFormat.marginLeft,  
67 - page.pageFormat.height -  
68 - page.pageFormat.marginTop -  
69 - page.pageFormat.marginBottom);  
70 - g.strokePath(); 58 + final String title;
  59 +
  60 + @override
  61 + Widget build(Context context) {
  62 + return Column(
  63 + crossAxisAlignment: CrossAxisAlignment.start,
  64 + children: <Widget>[
  65 + Row(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
  66 + Container(
  67 + width: 6,
  68 + height: 6,
  69 + margin: EdgeInsets.only(top: 2.5, left: 2, right: 5),
  70 + decoration: BoxDecoration(color: green, shape: BoxShape.circle),
  71 + ),
  72 + Text(title, style: Theme.of(context).defaultTextStyleBold),
  73 + ]),
  74 + Container(
  75 + decoration: BoxDecoration(
  76 + border: BoxBorder(left: true, color: green, width: 2)),
  77 + padding: EdgeInsets.only(left: 10, top: 5, bottom: 5),
  78 + margin: EdgeInsets.only(left: 5),
  79 + child: Column(
  80 + crossAxisAlignment: CrossAxisAlignment.start,
  81 + children: <Widget>[
  82 + Lorem(length: 20),
  83 + ]),
  84 + ),
  85 + ]);
  86 + }
  87 +}
71 88
72 - g.setColor(PdfColor(0.0, 1.0, 1.0));  
73 - g.drawRRect(  
74 - 50.0 * PdfPageFormat.mm,  
75 - top - 80.0 * PdfPageFormat.mm,  
76 - 100.0 * PdfPageFormat.mm,  
77 - 50.0 * PdfPageFormat.mm,  
78 - 20.0,  
79 - 20.0,  
80 - );  
81 - g.fillPath(); 89 +class Category extends StatelessWidget {
  90 + Category({this.title});
82 91
83 - g.setColor(PdfColor(0.3, 0.3, 0.3));  
84 - g.drawString(  
85 - font,  
86 - 12.0,  
87 - "Hello World!",  
88 - page.pageFormat.marginLeft + 10.0 * PdfPageFormat.mm,  
89 - top - 10.0 * PdfPageFormat.mm); 92 + final String title;
  93 +
  94 + @override
  95 + Widget build(Context context) {
  96 + return Container(
  97 + decoration: BoxDecoration(color: lightGreen, borderRadius: 6),
  98 + margin: EdgeInsets.only(bottom: 10, top: 20),
  99 + padding: EdgeInsets.fromLTRB(10, 7, 10, 4),
  100 + child: Text(title, textScaleFactor: 1.5));
90 } 101 }
91 - return pdf; 102 +}
  103 +
  104 +Future<PdfDocument> generateDocument(PdfPageFormat format) async {
  105 + final pdf = PdfDoc();
  106 +
  107 + final profileImage = await pdfImageFromImageProvider(
  108 + pdf: pdf.document,
  109 + image: fw.NetworkImage(
  110 + "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&s=200"),
  111 + onError: (dynamic exception, StackTrace stackTrace) {
  112 + print("error");
  113 + });
  114 +
  115 + pdf.addPage(MyPage(
  116 + pageFormat: format.applyMargin(
  117 + left: 2.0 * PdfPageFormat.cm,
  118 + top: 4.0 * PdfPageFormat.cm,
  119 + right: 2.0 * PdfPageFormat.cm,
  120 + bottom: 2.0 * PdfPageFormat.cm),
  121 + build: (Context context) => Row(children: <Widget>[
  122 + Expanded(
  123 + child: Column(
  124 + crossAxisAlignment: CrossAxisAlignment.start,
  125 + children: <Widget>[
  126 + Container(
  127 + padding: EdgeInsets.only(left: 30, bottom: 20),
  128 + child: Column(
  129 + crossAxisAlignment: CrossAxisAlignment.start,
  130 + children: <Widget>[
  131 + Text("Parnella Charlesbois",
  132 + textScaleFactor: 2.0,
  133 + style: Theme.of(context).defaultTextStyleBold),
  134 + Padding(padding: EdgeInsets.only(top: 10)),
  135 + Text("Electrotyper",
  136 + textScaleFactor: 1.2,
  137 + style: Theme.of(context)
  138 + .defaultTextStyleBold
  139 + .copyWith(color: green)),
  140 + Padding(padding: EdgeInsets.only(top: 20)),
  141 + Row(
  142 + crossAxisAlignment: CrossAxisAlignment.start,
  143 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
  144 + children: <Widget>[
  145 + Column(
  146 + crossAxisAlignment:
  147 + CrossAxisAlignment.start,
  148 + children: <Widget>[
  149 + Text("568 Port Washington Road"),
  150 + Text("Nordegg, AB T0M 2H0"),
  151 + Text("Canada, ON"),
  152 + ]),
  153 + Column(
  154 + crossAxisAlignment:
  155 + CrossAxisAlignment.start,
  156 + children: <Widget>[
  157 + Text("+1 403-721-6898"),
  158 + Text("p.charlesbois@yahoo.com"),
  159 + Text("wholeprices.ca")
  160 + ]),
  161 + Padding(padding: EdgeInsets.zero)
  162 + ]),
  163 + ])),
  164 + Category(title: "Work Experience"),
  165 + Block(title: "Tour bus driver"),
  166 + Block(title: "Logging equipment operator"),
  167 + Block(title: "Foot doctor"),
  168 + Category(title: "Education"),
  169 + Block(title: "Bachelor Of Commerce"),
  170 + Block(title: "Bachelor Interior Design"),
  171 + ])),
  172 + Container(
  173 + height: double.infinity,
  174 + width: 10,
  175 + decoration: BoxDecoration(
  176 + border: BoxBorder(left: true, color: green, width: 2)),
  177 + ),
  178 + Column(
  179 + crossAxisAlignment: CrossAxisAlignment.start,
  180 + children: <Widget>[
  181 + ClipOval(
  182 + child: Container(
  183 + width: 100,
  184 + height: 100,
  185 + color: lightGreen,
  186 + child: profileImage == null
  187 + ? Container()
  188 + : Image(profileImage)))
  189 + ])
  190 + ]),
  191 + ));
  192 + return pdf.document;
92 } 193 }
@@ -17,12 +17,15 @@ @@ -17,12 +17,15 @@
17 library printing; 17 library printing;
18 18
19 import 'dart:async'; 19 import 'dart:async';
  20 +import 'dart:io';
20 import 'dart:typed_data'; 21 import 'dart:typed_data';
21 import 'dart:ui' as ui; 22 import 'dart:ui' as ui;
22 23
23 import 'package:flutter/services.dart'; 24 import 'package:flutter/services.dart';
24 import 'package:flutter/widgets.dart'; 25 import 'package:flutter/widgets.dart';
25 import 'package:pdf/pdf.dart'; 26 import 'package:pdf/pdf.dart';
  27 +import 'package:pdf/widgets.dart';
26 28
27 part 'src/asset_utils.dart'; 29 part 'src/asset_utils.dart';
28 part 'src/printing.dart'; 30 part 'src/printing.dart';
  31 +part 'src/widgets.dart';
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of printing;
  18 +
  19 +class PdfDoc extends Document {
  20 + PdfDoc({bool compress = true})
  21 + : super(deflate: compress ? zlib.encode : null);
  22 +}
@@ -2,7 +2,7 @@ name: printing @@ -2,7 +2,7 @@ name: printing
2 author: David PHAM-VAN <dev.nfet.net@gmail.com> 2 author: David PHAM-VAN <dev.nfet.net@gmail.com>
3 description: Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers 3 description: Plugin that allows Flutter apps to generate and print documents to android or ios compatible printers
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/printing
5 -version: 1.2.0 5 +version: 1.3.0
6 6
7 environment: 7 environment:
8 sdk: ">=1.19.0 <3.0.0" 8 sdk: ">=1.19.0 <3.0.0"
@@ -10,7 +10,7 @@ environment: @@ -10,7 +10,7 @@ environment:
10 dependencies: 10 dependencies:
11 flutter: 11 flutter:
12 sdk: flutter 12 sdk: flutter
13 - pdf: "^1.2.0" 13 + pdf: "^1.3.0"
14 14
15 flutter: 15 flutter:
16 plugin: 16 plugin: