Allow MultiPage to relayout individual pages with support for flex
Showing
6 changed files
with
166 additions
and
20 deletions
@@ -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()); |
No preview for this file type
No preview for this file type
-
Please register or login to post a comment