David PHAM-VAN

Improve MultiPage Widget

@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 * Add available dimensions for PdfPageFormat 2 * Add available dimensions for PdfPageFormat
3 * Add Document properties 3 * Add Document properties
4 * Add Page.orientation to force landscape or portrait 4 * Add Page.orientation to force landscape or portrait
  5 +* Improve MultiPage Widget
5 6
6 # 1.3.3 7 # 1.3.3
7 * Fix a bug with the RichText Widget 8 * Fix a bug with the RichText Widget
@@ -33,6 +33,7 @@ part 'widgets/font.dart'; @@ -33,6 +33,7 @@ part 'widgets/font.dart';
33 part 'widgets/geometry.dart'; 33 part 'widgets/geometry.dart';
34 part 'widgets/grid_view.dart'; 34 part 'widgets/grid_view.dart';
35 part 'widgets/image.dart'; 35 part 'widgets/image.dart';
  36 +part 'widgets/multi_page.dart';
36 part 'widgets/placeholders.dart'; 37 part 'widgets/placeholders.dart';
37 part 'widgets/stack.dart'; 38 part 'widgets/stack.dart';
38 part 'widgets/table.dart'; 39 part 'widgets/table.dart';
@@ -190,160 +190,3 @@ class Page { @@ -190,160 +190,3 @@ class Page {
190 } 190 }
191 } 191 }
192 } 192 }
193 -  
194 -class MultiPage extends Page {  
195 - const MultiPage(  
196 - {PdfPageFormat pageFormat = PdfPageFormat.a4,  
197 - BuildListCallback build,  
198 - this.crossAxisAlignment = CrossAxisAlignment.start,  
199 - this.header,  
200 - this.footer,  
201 - Theme theme,  
202 - PageOrientation orientation = PageOrientation.natural,  
203 - EdgeInsets margin})  
204 - : _buildList = build,  
205 - super(  
206 - pageFormat: pageFormat,  
207 - margin: margin,  
208 - theme: theme,  
209 - orientation: orientation);  
210 -  
211 - final BuildListCallback _buildList;  
212 -  
213 - final CrossAxisAlignment crossAxisAlignment;  
214 -  
215 - final BuildCallback header;  
216 -  
217 - final BuildCallback footer;  
218 -  
219 - void paintChild(  
220 - Context context, Widget child, double x, double y, double pageHeight) {  
221 - if (mustRotate) {  
222 - final EdgeInsets _margin = margin;  
223 - context.canvas  
224 - ..saveContext()  
225 - ..setTransform(Matrix4.identity()  
226 - ..rotateZ(-math.pi / 2)  
227 - ..translate(x - pageHeight + _margin.top - _margin.left,  
228 - y + _margin.left - _margin.bottom));  
229 - child.paint(context);  
230 - context.canvas.restoreContext();  
231 - } else {  
232 - child.box = PdfRect(x, y, child.box.width, child.box.height);  
233 - child.paint(context);  
234 - }  
235 - }  
236 -  
237 - @override  
238 - void generate(Document document) {  
239 - if (_buildList == null) {  
240 - return;  
241 - }  
242 -  
243 - final EdgeInsets _margin = margin;  
244 - final bool _mustRotate = mustRotate;  
245 - final double pageHeight =  
246 - _mustRotate ? pageFormat.width : pageFormat.height;  
247 - final double pageHeightMargin =  
248 - _mustRotate ? _margin.horizontal : _margin.vertical;  
249 - final BoxConstraints constraints = BoxConstraints(  
250 - maxWidth: _mustRotate  
251 - ? (pageFormat.height - _margin.vertical)  
252 - : (pageFormat.width - _margin.horizontal));  
253 - final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();  
254 - final Map<Type, Inherited> inherited = <Type, Inherited>{};  
255 - inherited[calculatedTheme.runtimeType] = calculatedTheme;  
256 - Context context;  
257 - double offsetEnd;  
258 - double offsetStart;  
259 - int index = 0;  
260 - final Context baseContext =  
261 - Context(document: document.document, inherited: inherited);  
262 - final List<Widget> children = _buildList(baseContext);  
263 - WidgetContext widgetContext;  
264 -  
265 - while (index < children.length) {  
266 - final Widget child = children[index];  
267 -  
268 - if (context == null) {  
269 - final PdfPage pdfPage =  
270 - PdfPage(document.document, pageFormat: pageFormat);  
271 - final PdfGraphics canvas = pdfPage.getGraphics();  
272 - context = baseContext.copyWith(page: pdfPage, canvas: canvas);  
273 - assert(() {  
274 - if (Document.debug) {  
275 - debugPaint(context);  
276 - }  
277 - return true;  
278 - }());  
279 - offsetStart = pageHeight -  
280 - (_mustRotate ? pageHeightMargin - margin.bottom : _margin.top);  
281 - offsetEnd =  
282 - _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;  
283 - if (header != null) {  
284 - final Widget headerWidget = header(context);  
285 - if (headerWidget != null) {  
286 - headerWidget.layout(context, constraints, parentUsesSize: false);  
287 - paintChild(context, headerWidget, _margin.left,  
288 - offsetStart - headerWidget.box.height, pageFormat.height);  
289 - offsetStart -= headerWidget.box.height;  
290 - }  
291 - }  
292 -  
293 - if (footer != null) {  
294 - final Widget footerWidget = footer(context);  
295 - if (footerWidget != null) {  
296 - footerWidget.layout(context, constraints, parentUsesSize: false);  
297 - paintChild(context, footerWidget, _margin.left, _margin.bottom,  
298 - pageFormat.height);  
299 - offsetEnd += footerWidget.box.height;  
300 - }  
301 - }  
302 - }  
303 -  
304 - if (widgetContext != null && child is SpanningWidget) {  
305 - child.restoreContext(widgetContext);  
306 - widgetContext = null;  
307 - }  
308 -  
309 - child.layout(context, constraints, parentUsesSize: false);  
310 -  
311 - if (offsetStart - child.box.height < offsetEnd) {  
312 - if (child.box.height <= pageHeight - pageHeightMargin &&  
313 - !(child is SpanningWidget)) {  
314 - context = null;  
315 - continue;  
316 - }  
317 -  
318 - if (!(child is SpanningWidget)) {  
319 - throw Exception(  
320 - 'Widget won\'t fit into the page as its height (${child.box.height}) '  
321 - 'exceed a page height (${pageHeight - pageHeightMargin}). '  
322 - 'You probably need a SpanningWidget or use a single page layout');  
323 - }  
324 -  
325 - final SpanningWidget span = child;  
326 -  
327 - child.layout(  
328 - context, constraints.copyWith(maxHeight: offsetStart - offsetEnd),  
329 - parentUsesSize: false);  
330 - paintChild(context, child, _margin.left, offsetStart - child.box.height,  
331 - pageFormat.height);  
332 -  
333 - if (span.canSpan) {  
334 - widgetContext = span.saveContext();  
335 - } else {  
336 - index++;  
337 - }  
338 -  
339 - context = null;  
340 - continue;  
341 - }  
342 -  
343 - paintChild(context, child, _margin.left, offsetStart - child.box.height,  
344 - pageFormat.height);  
345 - offsetStart -= child.box.height;  
346 - index++;  
347 - }  
348 - }  
349 -}  
  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 WidgetContext {}
  20 +
  21 +abstract class SpanningWidget extends Widget {
  22 + bool get canSpan => false;
  23 +
  24 + @protected
  25 + WidgetContext saveContext();
  26 +
  27 + @protected
  28 + void restoreContext(WidgetContext context);
  29 +}
  30 +
  31 +class NewPage extends Widget {
  32 + @override
  33 + void layout(Context context, BoxConstraints constraints,
  34 + {bool parentUsesSize = false}) {
  35 + box = PdfRect.zero;
  36 + }
  37 +}
  38 +
  39 +class MultiPage extends Page {
  40 + const MultiPage(
  41 + {PdfPageFormat pageFormat = PdfPageFormat.a4,
  42 + BuildListCallback build,
  43 + this.crossAxisAlignment = CrossAxisAlignment.start,
  44 + this.header,
  45 + this.footer,
  46 + Theme theme,
  47 + PageOrientation orientation = PageOrientation.natural,
  48 + EdgeInsets margin})
  49 + : _buildList = build,
  50 + super(
  51 + pageFormat: pageFormat,
  52 + margin: margin,
  53 + theme: theme,
  54 + orientation: orientation);
  55 +
  56 + final BuildListCallback _buildList;
  57 +
  58 + final CrossAxisAlignment crossAxisAlignment;
  59 +
  60 + final BuildCallback header;
  61 +
  62 + final BuildCallback footer;
  63 +
  64 + void _paintChild(
  65 + Context context, Widget child, double x, double y, double pageHeight) {
  66 + if (mustRotate) {
  67 + final EdgeInsets _margin = margin;
  68 + context.canvas
  69 + ..saveContext()
  70 + ..setTransform(Matrix4.identity()
  71 + ..rotateZ(-math.pi / 2)
  72 + ..translate(x - pageHeight + _margin.top - _margin.left,
  73 + y + _margin.left - _margin.bottom));
  74 + child.paint(context);
  75 + context.canvas.restoreContext();
  76 + } else {
  77 + child.box = PdfRect(x, y, child.box.width, child.box.height);
  78 + child.paint(context);
  79 + }
  80 + }
  81 +
  82 + @override
  83 + void generate(Document document) {
  84 + if (_buildList == null) {
  85 + return;
  86 + }
  87 +
  88 + final EdgeInsets _margin = margin;
  89 + final bool _mustRotate = mustRotate;
  90 + final double pageHeight =
  91 + _mustRotate ? pageFormat.width : pageFormat.height;
  92 + final double pageHeightMargin =
  93 + _mustRotate ? _margin.horizontal : _margin.vertical;
  94 + final BoxConstraints constraints = BoxConstraints(
  95 + maxWidth: _mustRotate
  96 + ? (pageFormat.height - _margin.vertical)
  97 + : (pageFormat.width - _margin.horizontal));
  98 + final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();
  99 + final Map<Type, Inherited> inherited = <Type, Inherited>{};
  100 + inherited[calculatedTheme.runtimeType] = calculatedTheme;
  101 + Context context;
  102 + double offsetEnd;
  103 + double offsetStart;
  104 + int index = 0;
  105 + int sameCount = 0;
  106 + final Context baseContext =
  107 + Context(document: document.document, inherited: inherited);
  108 + final List<Widget> children = _buildList(baseContext);
  109 + WidgetContext widgetContext;
  110 +
  111 + while (index < children.length) {
  112 + final Widget child = children[index];
  113 +
  114 + assert(() {
  115 + // Detect too big widgets
  116 + if (sameCount++ > 20) {
  117 + throw Exception(
  118 + 'This widget created more than 20 pages. This may be an issue in the widget or the document.');
  119 + }
  120 + return true;
  121 + }());
  122 +
  123 + // Create a new page if we don't already have one
  124 + if (context == null || child is NewPage) {
  125 + final PdfPage pdfPage =
  126 + PdfPage(document.document, pageFormat: pageFormat);
  127 + context =
  128 + baseContext.copyWith(page: pdfPage, canvas: pdfPage.getGraphics());
  129 +
  130 + assert(() {
  131 + if (Document.debug) {
  132 + debugPaint(context);
  133 + }
  134 + return true;
  135 + }());
  136 +
  137 + offsetStart = pageHeight -
  138 + (_mustRotate ? pageHeightMargin - margin.bottom : _margin.top);
  139 + offsetEnd =
  140 + _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
  141 +
  142 + if (header != null) {
  143 + final Widget headerWidget = header(context);
  144 + if (headerWidget != null) {
  145 + headerWidget.layout(context, constraints, parentUsesSize: false);
  146 + _paintChild(context, headerWidget, _margin.left,
  147 + offsetStart - headerWidget.box.height, pageFormat.height);
  148 + offsetStart -= headerWidget.box.height;
  149 + }
  150 + }
  151 +
  152 + if (footer != null) {
  153 + final Widget footerWidget = footer(context);
  154 + if (footerWidget != null) {
  155 + footerWidget.layout(context, constraints, parentUsesSize: false);
  156 + _paintChild(context, footerWidget, _margin.left, _margin.bottom,
  157 + pageFormat.height);
  158 + offsetEnd += footerWidget.box.height;
  159 + }
  160 + }
  161 + }
  162 +
  163 + // If we are processing a multi-page widget, we restore its context
  164 + if (widgetContext != null && child is SpanningWidget) {
  165 + child.restoreContext(widgetContext);
  166 + widgetContext = null;
  167 + }
  168 +
  169 + child.layout(context, constraints, parentUsesSize: false);
  170 +
  171 + // What to do if the widget is too big for the page?
  172 + if (offsetStart - child.box.height < offsetEnd) {
  173 + // If it is not a multi=page widget and its height
  174 + // is smaller than a full new page, we schedule a new page creation
  175 + if (child.box.height <= pageHeight - pageHeightMargin &&
  176 + !(child is SpanningWidget)) {
  177 + context = null;
  178 + continue;
  179 + }
  180 +
  181 + // Else we crash if the widget is too big and cannot be splitted
  182 + if (!(child is SpanningWidget)) {
  183 + throw Exception(
  184 + 'Widget won\'t fit into the page as its height (${child.box.height}) '
  185 + 'exceed a page height (${pageHeight - pageHeightMargin}). '
  186 + 'You probably need a SpanningWidget or use a single page layout');
  187 + }
  188 +
  189 + final SpanningWidget span = child;
  190 +
  191 + child.layout(
  192 + context, constraints.copyWith(maxHeight: offsetStart - offsetEnd),
  193 + parentUsesSize: false);
  194 + _paintChild(context, child, _margin.left,
  195 + offsetStart - child.box.height, pageFormat.height);
  196 +
  197 + // Has it finished spanning?
  198 + if (span.canSpan) {
  199 + widgetContext = span.saveContext();
  200 + } else {
  201 + sameCount = 0;
  202 + index++;
  203 + }
  204 +
  205 + // Schedule a new page
  206 + context = null;
  207 + continue;
  208 + }
  209 +
  210 + _paintChild(context, child, _margin.left, offsetStart - child.box.height,
  211 + pageFormat.height);
  212 + offsetStart -= child.box.height;
  213 + sameCount = 0;
  214 + index++;
  215 + }
  216 + }
  217 +}
@@ -92,18 +92,6 @@ abstract class Widget { @@ -92,18 +92,6 @@ abstract class Widget {
92 } 92 }
93 } 93 }
94 94
95 -class WidgetContext {}  
96 -  
97 -abstract class SpanningWidget extends Widget {  
98 - bool get canSpan => false;  
99 -  
100 - @protected  
101 - WidgetContext saveContext();  
102 -  
103 - @protected  
104 - void restoreContext(WidgetContext context);  
105 -}  
106 -  
107 abstract class StatelessWidget extends Widget { 95 abstract class StatelessWidget extends Widget {
108 StatelessWidget() : super(); 96 StatelessWidget() : super();
109 97