Allow MultiPage to relayout individual pages with support for flex
Showing
6 changed files
with
179 additions
and
33 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, | ||
| 85 | - PdfPageFormat pageFormat, | ||
| 86 | - BuildListCallback build, | ||
| 87 | - this.crossAxisAlignment = CrossAxisAlignment.start, | ||
| 88 | - this.header, | ||
| 89 | - this.footer, | ||
| 90 | - ThemeData theme, | ||
| 91 | - this.maxPages = 20, | ||
| 92 | - PageOrientation orientation, | ||
| 93 | - EdgeInsets margin}) | ||
| 94 | - : _buildList = build, | 79 | + MultiPage({ |
| 80 | + PageTheme pageTheme, | ||
| 81 | + PdfPageFormat pageFormat, | ||
| 82 | + BuildListCallback build, | ||
| 83 | + this.mainAxisAlignment = MainAxisAlignment.start, | ||
| 84 | + this.crossAxisAlignment = CrossAxisAlignment.start, | ||
| 85 | + this.header, | ||
| 86 | + this.footer, | ||
| 87 | + ThemeData theme, | ||
| 88 | + this.maxPages = 20, | ||
| 89 | + PageOrientation orientation, | ||
| 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; |
| 325 | - if (child is SpanningWidget && child.canSpan) { | ||
| 326 | - final WidgetContext context = child.saveContext(); | ||
| 327 | - context.apply(widget.widgetContext); | 339 | + final int flex = child is Flexible ? child.flex : 0; |
| 340 | + if (flex > 0) { | ||
| 341 | + totalFlex += flex; | ||
| 342 | + lastFlexChild = child; | ||
| 343 | + } else { | ||
| 344 | + if (child is SpanningWidget && child.canSpan) { | ||
| 345 | + final WidgetContext context = child.saveContext(); | ||
| 346 | + context.apply(widget.widgetContext); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + child.layout(page.context, widget.constraints, parentUsesSize: false); | ||
| 350 | + assert(child.box != null); | ||
| 351 | + allocatedSize += child.box.height; | ||
| 328 | } | 352 | } |
| 329 | - child.layout(page.context, widget.constraints, parentUsesSize: false); | ||
| 330 | - assert(child.box != null); | ||
| 331 | - _paintChild( | ||
| 332 | - page.context, widget.child, widget.x, widget.y, pageFormat.height); | ||
| 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