David PHAM-VAN

Improve MultiPage Widget

... ... @@ -2,6 +2,7 @@
* Add available dimensions for PdfPageFormat
* Add Document properties
* Add Page.orientation to force landscape or portrait
* Improve MultiPage Widget
# 1.3.3
* Fix a bug with the RichText Widget
... ...
... ... @@ -33,6 +33,7 @@ part 'widgets/font.dart';
part 'widgets/geometry.dart';
part 'widgets/grid_view.dart';
part 'widgets/image.dart';
part 'widgets/multi_page.dart';
part 'widgets/placeholders.dart';
part 'widgets/stack.dart';
part 'widgets/table.dart';
... ...
... ... @@ -190,160 +190,3 @@ class Page {
}
}
}
class MultiPage extends Page {
const MultiPage(
{PdfPageFormat pageFormat = PdfPageFormat.a4,
BuildListCallback build,
this.crossAxisAlignment = CrossAxisAlignment.start,
this.header,
this.footer,
Theme theme,
PageOrientation orientation = PageOrientation.natural,
EdgeInsets margin})
: _buildList = build,
super(
pageFormat: pageFormat,
margin: margin,
theme: theme,
orientation: orientation);
final BuildListCallback _buildList;
final CrossAxisAlignment crossAxisAlignment;
final BuildCallback header;
final BuildCallback footer;
void paintChild(
Context context, Widget child, double x, double y, double pageHeight) {
if (mustRotate) {
final EdgeInsets _margin = margin;
context.canvas
..saveContext()
..setTransform(Matrix4.identity()
..rotateZ(-math.pi / 2)
..translate(x - pageHeight + _margin.top - _margin.left,
y + _margin.left - _margin.bottom));
child.paint(context);
context.canvas.restoreContext();
} else {
child.box = PdfRect(x, y, child.box.width, child.box.height);
child.paint(context);
}
}
@override
void generate(Document document) {
if (_buildList == null) {
return;
}
final EdgeInsets _margin = margin;
final bool _mustRotate = mustRotate;
final double pageHeight =
_mustRotate ? pageFormat.width : pageFormat.height;
final double pageHeightMargin =
_mustRotate ? _margin.horizontal : _margin.vertical;
final BoxConstraints constraints = BoxConstraints(
maxWidth: _mustRotate
? (pageFormat.height - _margin.vertical)
: (pageFormat.width - _margin.horizontal));
final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();
final Map<Type, Inherited> inherited = <Type, Inherited>{};
inherited[calculatedTheme.runtimeType] = calculatedTheme;
Context context;
double offsetEnd;
double offsetStart;
int index = 0;
final Context baseContext =
Context(document: document.document, inherited: inherited);
final List<Widget> children = _buildList(baseContext);
WidgetContext widgetContext;
while (index < children.length) {
final Widget child = children[index];
if (context == null) {
final PdfPage pdfPage =
PdfPage(document.document, pageFormat: pageFormat);
final PdfGraphics canvas = pdfPage.getGraphics();
context = baseContext.copyWith(page: pdfPage, canvas: canvas);
assert(() {
if (Document.debug) {
debugPaint(context);
}
return true;
}());
offsetStart = pageHeight -
(_mustRotate ? pageHeightMargin - margin.bottom : _margin.top);
offsetEnd =
_mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
if (header != null) {
final Widget headerWidget = header(context);
if (headerWidget != null) {
headerWidget.layout(context, constraints, parentUsesSize: false);
paintChild(context, headerWidget, _margin.left,
offsetStart - headerWidget.box.height, pageFormat.height);
offsetStart -= headerWidget.box.height;
}
}
if (footer != null) {
final Widget footerWidget = footer(context);
if (footerWidget != null) {
footerWidget.layout(context, constraints, parentUsesSize: false);
paintChild(context, footerWidget, _margin.left, _margin.bottom,
pageFormat.height);
offsetEnd += footerWidget.box.height;
}
}
}
if (widgetContext != null && child is SpanningWidget) {
child.restoreContext(widgetContext);
widgetContext = null;
}
child.layout(context, constraints, parentUsesSize: false);
if (offsetStart - child.box.height < offsetEnd) {
if (child.box.height <= pageHeight - pageHeightMargin &&
!(child is SpanningWidget)) {
context = null;
continue;
}
if (!(child is SpanningWidget)) {
throw Exception(
'Widget won\'t fit into the page as its height (${child.box.height}) '
'exceed a page height (${pageHeight - pageHeightMargin}). '
'You probably need a SpanningWidget or use a single page layout');
}
final SpanningWidget span = child;
child.layout(
context, constraints.copyWith(maxHeight: offsetStart - offsetEnd),
parentUsesSize: false);
paintChild(context, child, _margin.left, offsetStart - child.box.height,
pageFormat.height);
if (span.canSpan) {
widgetContext = span.saveContext();
} else {
index++;
}
context = null;
continue;
}
paintChild(context, child, _margin.left, offsetStart - child.box.height,
pageFormat.height);
offsetStart -= child.box.height;
index++;
}
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
part of widget;
class WidgetContext {}
abstract class SpanningWidget extends Widget {
bool get canSpan => false;
@protected
WidgetContext saveContext();
@protected
void restoreContext(WidgetContext context);
}
class NewPage extends Widget {
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
box = PdfRect.zero;
}
}
class MultiPage extends Page {
const MultiPage(
{PdfPageFormat pageFormat = PdfPageFormat.a4,
BuildListCallback build,
this.crossAxisAlignment = CrossAxisAlignment.start,
this.header,
this.footer,
Theme theme,
PageOrientation orientation = PageOrientation.natural,
EdgeInsets margin})
: _buildList = build,
super(
pageFormat: pageFormat,
margin: margin,
theme: theme,
orientation: orientation);
final BuildListCallback _buildList;
final CrossAxisAlignment crossAxisAlignment;
final BuildCallback header;
final BuildCallback footer;
void _paintChild(
Context context, Widget child, double x, double y, double pageHeight) {
if (mustRotate) {
final EdgeInsets _margin = margin;
context.canvas
..saveContext()
..setTransform(Matrix4.identity()
..rotateZ(-math.pi / 2)
..translate(x - pageHeight + _margin.top - _margin.left,
y + _margin.left - _margin.bottom));
child.paint(context);
context.canvas.restoreContext();
} else {
child.box = PdfRect(x, y, child.box.width, child.box.height);
child.paint(context);
}
}
@override
void generate(Document document) {
if (_buildList == null) {
return;
}
final EdgeInsets _margin = margin;
final bool _mustRotate = mustRotate;
final double pageHeight =
_mustRotate ? pageFormat.width : pageFormat.height;
final double pageHeightMargin =
_mustRotate ? _margin.horizontal : _margin.vertical;
final BoxConstraints constraints = BoxConstraints(
maxWidth: _mustRotate
? (pageFormat.height - _margin.vertical)
: (pageFormat.width - _margin.horizontal));
final Theme calculatedTheme = theme ?? document.theme ?? Theme.base();
final Map<Type, Inherited> inherited = <Type, Inherited>{};
inherited[calculatedTheme.runtimeType] = calculatedTheme;
Context context;
double offsetEnd;
double offsetStart;
int index = 0;
int sameCount = 0;
final Context baseContext =
Context(document: document.document, inherited: inherited);
final List<Widget> children = _buildList(baseContext);
WidgetContext widgetContext;
while (index < children.length) {
final Widget child = children[index];
assert(() {
// Detect too big widgets
if (sameCount++ > 20) {
throw Exception(
'This widget created more than 20 pages. This may be an issue in the widget or the document.');
}
return true;
}());
// Create a new page if we don't already have one
if (context == null || child is NewPage) {
final PdfPage pdfPage =
PdfPage(document.document, pageFormat: pageFormat);
context =
baseContext.copyWith(page: pdfPage, canvas: pdfPage.getGraphics());
assert(() {
if (Document.debug) {
debugPaint(context);
}
return true;
}());
offsetStart = pageHeight -
(_mustRotate ? pageHeightMargin - margin.bottom : _margin.top);
offsetEnd =
_mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
if (header != null) {
final Widget headerWidget = header(context);
if (headerWidget != null) {
headerWidget.layout(context, constraints, parentUsesSize: false);
_paintChild(context, headerWidget, _margin.left,
offsetStart - headerWidget.box.height, pageFormat.height);
offsetStart -= headerWidget.box.height;
}
}
if (footer != null) {
final Widget footerWidget = footer(context);
if (footerWidget != null) {
footerWidget.layout(context, constraints, parentUsesSize: false);
_paintChild(context, footerWidget, _margin.left, _margin.bottom,
pageFormat.height);
offsetEnd += footerWidget.box.height;
}
}
}
// If we are processing a multi-page widget, we restore its context
if (widgetContext != null && child is SpanningWidget) {
child.restoreContext(widgetContext);
widgetContext = null;
}
child.layout(context, constraints, parentUsesSize: false);
// What to do if the widget is too big for the page?
if (offsetStart - child.box.height < offsetEnd) {
// If it is not a multi=page widget and its height
// is smaller than a full new page, we schedule a new page creation
if (child.box.height <= pageHeight - pageHeightMargin &&
!(child is SpanningWidget)) {
context = null;
continue;
}
// Else we crash if the widget is too big and cannot be splitted
if (!(child is SpanningWidget)) {
throw Exception(
'Widget won\'t fit into the page as its height (${child.box.height}) '
'exceed a page height (${pageHeight - pageHeightMargin}). '
'You probably need a SpanningWidget or use a single page layout');
}
final SpanningWidget span = child;
child.layout(
context, constraints.copyWith(maxHeight: offsetStart - offsetEnd),
parentUsesSize: false);
_paintChild(context, child, _margin.left,
offsetStart - child.box.height, pageFormat.height);
// Has it finished spanning?
if (span.canSpan) {
widgetContext = span.saveContext();
} else {
sameCount = 0;
index++;
}
// Schedule a new page
context = null;
continue;
}
_paintChild(context, child, _margin.left, offsetStart - child.box.height,
pageFormat.height);
offsetStart -= child.box.height;
sameCount = 0;
index++;
}
}
}
... ...
... ... @@ -92,18 +92,6 @@ abstract class Widget {
}
}
class WidgetContext {}
abstract class SpanningWidget extends Widget {
bool get canSpan => false;
@protected
WidgetContext saveContext();
@protected
void restoreContext(WidgetContext context);
}
abstract class StatelessWidget extends Widget {
StatelessWidget() : super();
... ...