David PHAM-VAN

Allow MultiPage to relayout individual pages with support for flex

1 # Changelog 1 # Changelog
2 2
  3 +## 1.9.0
  4 +
  5 +- Allow MultiPage to relayout individual pages with support for flex
  6 +
3 ## 1.8.1 7 ## 1.8.1
4 8
5 - Fix Wrap break condition 9 - Fix Wrap break condition
@@ -50,15 +50,11 @@ class NewPage extends Widget { @@ -50,15 +50,11 @@ class NewPage extends Widget {
50 class _MultiPageWidget { 50 class _MultiPageWidget {
51 const _MultiPageWidget({ 51 const _MultiPageWidget({
52 @required this.child, 52 @required this.child,
53 - @required this.x,  
54 - @required this.y,  
55 @required this.constraints, 53 @required this.constraints,
56 @required this.widgetContext, 54 @required this.widgetContext,
57 }); 55 });
58 56
59 final Widget child; 57 final Widget child;
60 - final double x;  
61 - final double y;  
62 final BoxConstraints constraints; 58 final BoxConstraints constraints;
63 final WidgetContext widgetContext; 59 final WidgetContext widgetContext;
64 } 60 }
@@ -80,18 +76,21 @@ class _MultiPageInstance { @@ -80,18 +76,21 @@ class _MultiPageInstance {
80 } 76 }
81 77
82 class MultiPage extends Page { 78 class MultiPage extends Page {
83 - MultiPage(  
84 - {PageTheme pageTheme, 79 + MultiPage({
  80 + PageTheme pageTheme,
85 PdfPageFormat pageFormat, 81 PdfPageFormat pageFormat,
86 BuildListCallback build, 82 BuildListCallback build,
  83 + this.mainAxisAlignment = MainAxisAlignment.start,
87 this.crossAxisAlignment = CrossAxisAlignment.start, 84 this.crossAxisAlignment = CrossAxisAlignment.start,
88 this.header, 85 this.header,
89 this.footer, 86 this.footer,
90 ThemeData theme, 87 ThemeData theme,
91 this.maxPages = 20, 88 this.maxPages = 20,
92 PageOrientation orientation, 89 PageOrientation orientation,
93 - EdgeInsets margin})  
94 - : _buildList = build, 90 + EdgeInsets margin,
  91 + }) : _buildList = build,
  92 + assert(mainAxisAlignment != null),
  93 + assert(crossAxisAlignment != null),
95 assert(maxPages != null && maxPages > 0), 94 assert(maxPages != null && maxPages > 0),
96 super( 95 super(
97 pageTheme: pageTheme, 96 pageTheme: pageTheme,
@@ -108,6 +107,8 @@ class MultiPage extends Page { @@ -108,6 +107,8 @@ class MultiPage extends Page {
108 107
109 final BuildCallback footer; 108 final BuildCallback footer;
110 109
  110 + final MainAxisAlignment mainAxisAlignment;
  111 +
111 final List<_MultiPageInstance> _pages = <_MultiPageInstance>[]; 112 final List<_MultiPageInstance> _pages = <_MultiPageInstance>[];
112 113
113 final int maxPages; 114 final int maxPages;
@@ -202,7 +203,7 @@ class MultiPage extends Page { @@ -202,7 +203,7 @@ class MultiPage extends Page {
202 }()); 203 }());
203 204
204 offsetStart = pageHeight - 205 offsetStart = pageHeight -
205 - (_mustRotate ? pageHeightMargin - margin.bottom : _margin.top); 206 + (_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
206 offsetEnd = 207 offsetEnd =
207 _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom; 208 _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
208 209
@@ -268,8 +269,6 @@ class MultiPage extends Page { @@ -268,8 +269,6 @@ class MultiPage extends Page {
268 _pages.last.widgets.add( 269 _pages.last.widgets.add(
269 _MultiPageWidget( 270 _MultiPageWidget(
270 child: child, 271 child: child,
271 - x: _margin.left,  
272 - y: offsetStart - child.box.height,  
273 constraints: localConstraints, 272 constraints: localConstraints,
274 widgetContext: widgetContext?.clone(), 273 widgetContext: widgetContext?.clone(),
275 ), 274 ),
@@ -289,8 +288,6 @@ class MultiPage extends Page { @@ -289,8 +288,6 @@ class MultiPage extends Page {
289 _pages.last.widgets.add( 288 _pages.last.widgets.add(
290 _MultiPageWidget( 289 _MultiPageWidget(
291 child: child, 290 child: child,
292 - x: _margin.left,  
293 - y: offsetStart - child.box.height,  
294 constraints: constraints, 291 constraints: constraints,
295 widgetContext: child is SpanningWidget && canSpan 292 widgetContext: child is SpanningWidget && canSpan
296 ? child.saveContext().clone() 293 ? child.saveContext().clone()
@@ -307,8 +304,22 @@ class MultiPage extends Page { @@ -307,8 +304,22 @@ class MultiPage extends Page {
307 @override 304 @override
308 void postProcess(Document document) { 305 void postProcess(Document document) {
309 final EdgeInsets _margin = margin; 306 final EdgeInsets _margin = margin;
  307 + final bool _mustRotate = mustRotate;
  308 + final double pageHeight =
  309 + _mustRotate ? pageFormat.width : pageFormat.height;
  310 + final double pageWidth = _mustRotate ? pageFormat.height : pageFormat.width;
  311 + final double pageHeightMargin =
  312 + _mustRotate ? _margin.horizontal : _margin.vertical;
  313 + final double pageWidthMargin =
  314 + _mustRotate ? _margin.vertical : _margin.horizontal;
  315 + final double availableWidth = pageWidth - pageWidthMargin;
310 316
311 for (_MultiPageInstance page in _pages) { 317 for (_MultiPageInstance page in _pages) {
  318 + double offsetStart = pageHeight -
  319 + (_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
  320 + double offsetEnd =
  321 + _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
  322 +
312 if (pageTheme.buildBackground != null) { 323 if (pageTheme.buildBackground != null) {
313 final Widget child = pageTheme.buildBackground(page.context); 324 final Widget child = pageTheme.buildBackground(page.context);
314 if (child != null) { 325 if (child != null) {
@@ -320,16 +331,25 @@ class MultiPage extends Page { @@ -320,16 +331,25 @@ class MultiPage extends Page {
320 } 331 }
321 } 332 }
322 333
  334 + int totalFlex = 0;
  335 + double allocatedSize = 0;
  336 + Widget lastFlexChild;
323 for (_MultiPageWidget widget in page.widgets) { 337 for (_MultiPageWidget widget in page.widgets) {
324 final Widget child = widget.child; 338 final Widget child = widget.child;
  339 + final int flex = child is Flexible ? child.flex : 0;
  340 + if (flex > 0) {
  341 + totalFlex += flex;
  342 + lastFlexChild = child;
  343 + } else {
325 if (child is SpanningWidget && child.canSpan) { 344 if (child is SpanningWidget && child.canSpan) {
326 final WidgetContext context = child.saveContext(); 345 final WidgetContext context = child.saveContext();
327 context.apply(widget.widgetContext); 346 context.apply(widget.widgetContext);
328 } 347 }
  348 +
329 child.layout(page.context, widget.constraints, parentUsesSize: false); 349 child.layout(page.context, widget.constraints, parentUsesSize: false);
330 assert(child.box != null); 350 assert(child.box != null);
331 - _paintChild(  
332 - page.context, widget.child, widget.x, widget.y, pageFormat.height); 351 + allocatedSize += child.box.height;
  352 + }
333 } 353 }
334 354
335 if (header != null) { 355 if (header != null) {
@@ -338,6 +358,7 @@ class MultiPage extends Page { @@ -338,6 +358,7 @@ class MultiPage extends Page {
338 headerWidget.layout(page.context, page.constraints, 358 headerWidget.layout(page.context, page.constraints,
339 parentUsesSize: false); 359 parentUsesSize: false);
340 assert(headerWidget.box != null); 360 assert(headerWidget.box != null);
  361 + offsetStart -= headerWidget.box.height;
341 _paintChild(page.context, headerWidget, _margin.left, 362 _paintChild(page.context, headerWidget, _margin.left,
342 page.offsetStart - headerWidget.box.height, pageFormat.height); 363 page.offsetStart - headerWidget.box.height, pageFormat.height);
343 } 364 }
@@ -349,11 +370,114 @@ class MultiPage extends Page { @@ -349,11 +370,114 @@ class MultiPage extends Page {
349 footerWidget.layout(page.context, page.constraints, 370 footerWidget.layout(page.context, page.constraints,
350 parentUsesSize: false); 371 parentUsesSize: false);
351 assert(footerWidget.box != null); 372 assert(footerWidget.box != null);
  373 + offsetEnd += footerWidget.box.height;
352 _paintChild(page.context, footerWidget, _margin.left, _margin.bottom, 374 _paintChild(page.context, footerWidget, _margin.left, _margin.bottom,
353 pageFormat.height); 375 pageFormat.height);
354 } 376 }
355 } 377 }
356 378
  379 + final double freeSpace =
  380 + math.max(0, offsetStart - offsetEnd - allocatedSize);
  381 +
  382 + final double spacePerFlex =
  383 + totalFlex > 0 ? (freeSpace / totalFlex) : double.nan;
  384 + double allocatedFlexSpace = 0;
  385 +
  386 + double leadingSpace = 0;
  387 + double betweenSpace = 0;
  388 +
  389 + if (totalFlex == 0) {
  390 + final int totalChildren = page.widgets.length;
  391 +
  392 + switch (mainAxisAlignment) {
  393 + case MainAxisAlignment.start:
  394 + leadingSpace = 0.0;
  395 + betweenSpace = 0.0;
  396 + break;
  397 + case MainAxisAlignment.end:
  398 + leadingSpace = freeSpace;
  399 + betweenSpace = 0.0;
  400 + break;
  401 + case MainAxisAlignment.center:
  402 + leadingSpace = freeSpace / 2.0;
  403 + betweenSpace = 0.0;
  404 + break;
  405 + case MainAxisAlignment.spaceBetween:
  406 + leadingSpace = 0.0;
  407 + betweenSpace =
  408 + totalChildren > 1 ? freeSpace / (totalChildren - 1) : 0.0;
  409 + break;
  410 + case MainAxisAlignment.spaceAround:
  411 + betweenSpace = totalChildren > 0 ? freeSpace / totalChildren : 0.0;
  412 + leadingSpace = betweenSpace / 2.0;
  413 + break;
  414 + case MainAxisAlignment.spaceEvenly:
  415 + betweenSpace =
  416 + totalChildren > 0 ? freeSpace / (totalChildren + 1) : 0.0;
  417 + leadingSpace = betweenSpace;
  418 + break;
  419 + }
  420 + }
  421 +
  422 + for (_MultiPageWidget widget in page.widgets) {
  423 + final Widget child = widget.child;
  424 +
  425 + final int flex = child is Flexible ? child.flex : 0;
  426 + final FlexFit fit = child is Flexible ? child.fit : FlexFit.loose;
  427 + if (flex > 0) {
  428 + assert(child is! SpanningWidget);
  429 + final double maxChildExtent = child == lastFlexChild
  430 + ? (freeSpace - allocatedFlexSpace)
  431 + : spacePerFlex * flex;
  432 + double minChildExtent;
  433 + switch (fit) {
  434 + case FlexFit.tight:
  435 + assert(maxChildExtent < double.infinity);
  436 + minChildExtent = maxChildExtent;
  437 + break;
  438 + case FlexFit.loose:
  439 + minChildExtent = 0.0;
  440 + break;
  441 + }
  442 + assert(minChildExtent != null);
  443 + final BoxConstraints innerConstraints = BoxConstraints(
  444 + minWidth: widget.constraints.maxWidth,
  445 + maxWidth: widget.constraints.maxWidth,
  446 + minHeight: minChildExtent,
  447 + maxHeight: maxChildExtent);
  448 +
  449 + child.layout(page.context, innerConstraints, parentUsesSize: false);
  450 + assert(child.box != null);
  451 + final double childSize = child.box.height;
  452 + assert(childSize <= maxChildExtent);
  453 + allocatedSize += childSize;
  454 + allocatedFlexSpace += maxChildExtent;
  455 + }
  456 + }
  457 +
  458 + double pos = offsetStart - leadingSpace;
  459 + for (_MultiPageWidget widget in page.widgets) {
  460 + pos -= widget.child.box.height;
  461 + double x;
  462 + switch (crossAxisAlignment) {
  463 + case CrossAxisAlignment.start:
  464 + x = 0;
  465 + break;
  466 + case CrossAxisAlignment.end:
  467 + x = availableWidth - widget.child.box.width;
  468 + break;
  469 + case CrossAxisAlignment.center:
  470 + x = availableWidth / 2 - widget.child.box.width / 2;
  471 + break;
  472 + case CrossAxisAlignment.stretch:
  473 + x = 0;
  474 + break;
  475 + }
  476 + _paintChild(page.context, widget.child, _margin.left + x, pos,
  477 + pageFormat.height);
  478 + pos -= betweenSpace;
  479 + }
  480 +
357 if (pageTheme.buildForeground != null) { 481 if (pageTheme.buildForeground != null) {
358 final Widget child = pageTheme.buildForeground(page.context); 482 final Widget child = pageTheme.buildForeground(page.context);
359 if (child != null) { 483 if (child != null) {
@@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl
4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf 4 homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf
5 repository: https://github.com/DavBfr/dart_pdf 5 repository: https://github.com/DavBfr/dart_pdf
6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues 6 issue_tracker: https://github.com/DavBfr/dart_pdf/issues
7 -version: 1.8.1 7 +version: 1.9.0
8 8
9 environment: 9 environment:
10 sdk: ">=2.3.0 <3.0.0" 10 sdk: ">=2.3.0 <3.0.0"
@@ -93,6 +93,24 @@ void main() { @@ -93,6 +93,24 @@ void main() {
93 ); 93 );
94 }); 94 });
95 95
  96 + test('MultiPage Spacer', () {
  97 + pdf.addPage(
  98 + MultiPage(
  99 + mainAxisAlignment: MainAxisAlignment.center,
  100 + crossAxisAlignment: CrossAxisAlignment.center,
  101 + build: (Context context) => <Widget>[
  102 + for (int i = 0; i < 60; i++) Text('Begin $i'),
  103 + Spacer(), // Defaults to a flex of one.
  104 + Text('Middle'),
  105 + // Gives twice the space between Middle and End than Begin and Middle.
  106 + Spacer(flex: 2),
  107 + // Expanded(flex: 2, child: SizedBox.shrink()),
  108 + Text('End'),
  109 + ],
  110 + ),
  111 + );
  112 + });
  113 +
96 tearDownAll(() { 114 tearDownAll(() {
97 final File file = File('widgets-flex.pdf'); 115 final File file = File('widgets-flex.pdf');
98 file.writeAsBytesSync(pdf.save()); 116 file.writeAsBytesSync(pdf.save());