Showing
26 changed files
with
3897 additions
and
123 deletions
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 | } |
pdf/lib/widgets.dart
0 → 100644
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'; |
pdf/lib/widgets/basic.dart
0 → 100644
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 | +} |
pdf/lib/widgets/clip.dart
0 → 100644
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 | +} |
pdf/lib/widgets/container.dart
0 → 100644
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 | +} |
pdf/lib/widgets/content.dart
0 → 100644
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 | +} |
pdf/lib/widgets/document.dart
0 → 100644
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 | +} |
pdf/lib/widgets/flex.dart
0 → 100644
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 | +} |
pdf/lib/widgets/geometry.dart
0 → 100644
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 | +} |
pdf/lib/widgets/grid_view.dart
0 → 100644
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 | +} |
pdf/lib/widgets/image.dart
0 → 100644
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 | +} |
pdf/lib/widgets/placeholders.dart
0 → 100644
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 | +} |
pdf/lib/widgets/table.dart
0 → 100644
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 | +} |
pdf/lib/widgets/text.dart
0 → 100644
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 | +} |
pdf/lib/widgets/theme.dart
0 → 100644
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 | +} |
pdf/lib/widgets/widget.dart
0 → 100644
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" |
pdf/test/widget_test.dart
0 → 100644
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 | # 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'; |
printing/lib/src/widgets.dart
0 → 100644
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: |
-
Please register or login to post a comment