Milad akarie
Committed by David PHAM-VAN

Add RTL support to EdgeInsets widget

Add RTL support Page and MultiPage
... ... @@ -22,8 +22,7 @@ import 'point.dart';
class PdfRect {
const PdfRect(this.x, this.y, this.width, this.height);
factory PdfRect.fromLTRB(
double left, double top, double right, double bottom) {
factory PdfRect.fromLTRB(double left, double top, double right, double bottom) {
return PdfRect(left, top, right - left, bottom - top);
}
... ... @@ -36,14 +35,18 @@ class PdfRect {
static const PdfRect zero = PdfRect(0, 0, 0, 0);
double get left => x;
double get bottom => y;
double get right => x + width;
double get top => y + height;
@Deprecated('type => horizontalCenter')
double get horizondalCenter => horizontalCenter;
double get horizontalCenter => x + width / 2;
double get verticalCenter => y + height / 2;
@override
... ... @@ -54,19 +57,36 @@ class PdfRect {
}
PdfPoint get offset => PdfPoint(x, y);
PdfPoint get size => PdfPoint(width, height);
PdfPoint get topLeft => PdfPoint(x, y);
PdfPoint get topRight => PdfPoint(right, y);
PdfPoint get bottomLeft => PdfPoint(x, top);
PdfPoint get bottomRight => PdfPoint(right, top);
/// Returns a new rectangle with edges moved outwards by the given delta.
PdfRect inflate(double delta) {
return PdfRect.fromLTRB(
left - delta, top - delta, right + delta, bottom + delta);
return PdfRect.fromLTRB(left - delta, top - delta, right + delta, bottom + delta);
}
/// Returns a new rectangle with edges moved inwards by the given delta.
PdfRect deflate(double delta) => inflate(-delta);
PdfRect copyWith({
double? x,
double? y,
double? width,
double? height,
}) {
return PdfRect(
x ?? this.x,
y ?? this.y,
width ?? this.width,
height ?? this.height,
);
}
}
... ...
... ... @@ -16,6 +16,7 @@
import 'dart:math' as math;
import 'package:pdf/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../pdf.dart';
... ... @@ -80,46 +81,48 @@ class Padding extends SingleChildWidget {
Widget? child,
}) : super(child: child);
final EdgeInsets padding;
final EdgeInsetsGeometry padding;
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
final effectivePadding = padding.resolve(Directionality.of(context));
if (child != null) {
final childConstraints = constraints.deflate(padding);
final childConstraints = constraints.deflate(effectivePadding);
child!.layout(context, childConstraints, parentUsesSize: parentUsesSize);
assert(child!.box != null);
box = constraints.constrainRect(
width: child!.box!.width + padding.horizontal,
height: child!.box!.height + padding.vertical);
width: child!.box!.width + effectivePadding.horizontal,
height: child!.box!.height + effectivePadding.vertical);
} else {
box = constraints.constrainRect(
width: padding.horizontal, height: padding.vertical);
width: effectivePadding.horizontal, height: effectivePadding.vertical);
}
}
@override
void debugPaint(Context context) {
final effectivePadding = padding.resolve(Directionality.of(context));
context.canvas
..setFillColor(PdfColors.lime)
..moveTo(box!.x, box!.y)
..lineTo(box!.right, box!.y)
..lineTo(box!.right, box!.top)
..lineTo(box!.x, box!.top)
..moveTo(box!.x + padding.left, box!.y + padding.bottom)
..lineTo(box!.x + padding.left, box!.top - padding.top)
..lineTo(box!.right - padding.right, box!.top - padding.top)
..lineTo(box!.right - padding.right, box!.y + padding.bottom)
..moveTo(box!.x + effectivePadding.left, box!.y + effectivePadding.bottom)
..lineTo(box!.x + effectivePadding.left, box!.top - effectivePadding.top)
..lineTo(box!.right - effectivePadding.right, box!.top - effectivePadding.top)
..lineTo(box!.right - effectivePadding.right, box!.y + effectivePadding.bottom)
..fillPath();
}
@override
void paint(Context context) {
super.paint(context);
final effectivePadding = padding.resolve(Directionality.of(context));
if (child != null) {
final mat = Matrix4.identity();
mat.translate(box!.x + padding.left, box!.y + padding.bottom);
mat.translate(box!.x + effectivePadding.left, box!.y + effectivePadding.bottom);
context.canvas
..saveContext()
..setTransform(mat);
... ...
... ... @@ -20,16 +20,13 @@ import 'package:meta/meta.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../pdf.dart';
import 'basic.dart';
import '../../widgets.dart';
@immutable
class BoxConstraints {
/// Creates box constraints with the given constraints.
const BoxConstraints(
{this.minWidth = 0.0,
this.maxWidth = double.infinity,
this.minHeight = 0.0,
this.maxHeight = double.infinity});
{this.minWidth = 0.0, this.maxWidth = double.infinity, this.minHeight = 0.0, this.maxHeight = double.infinity});
/// Creates box constraints that require the given width or height.
const BoxConstraints.tightFor({double? width, double? height})
... ... @@ -104,8 +101,7 @@ class BoxConstraints {
return result;
}
PdfRect constrainRect(
{double width = double.infinity, double height = double.infinity}) {
PdfRect constrainRect({double width = double.infinity, double height = double.infinity}) {
final result = PdfPoint(constrainWidth(width), constrainHeight(height));
return PdfRect.fromPoints(PdfPoint.zero, result);
}
... ... @@ -162,10 +158,8 @@ class BoxConstraints {
return BoxConstraints(
minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth),
maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth),
minHeight:
height == null ? minHeight : height.clamp(minHeight, maxHeight),
maxHeight:
height == null ? maxHeight : height.clamp(minHeight, maxHeight));
minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight),
maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight));
}
/// Returns new box constraints that are smaller by the given edge dimensions.
... ... @@ -197,17 +191,11 @@ class BoxConstraints {
return BoxConstraints(
minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
minHeight:
minHeight.clamp(constraints.minHeight, constraints.maxHeight),
maxHeight:
maxHeight.clamp(constraints.minHeight, constraints.maxHeight));
minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),
maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight));
}
BoxConstraints copyWith(
{double? minWidth,
double? maxWidth,
double? minHeight,
double? maxHeight}) {
BoxConstraints copyWith({double? minWidth, double? maxWidth, double? minHeight, double? maxHeight}) {
return BoxConstraints(
minWidth: minWidth ?? this.minWidth,
maxWidth: maxWidth ?? this.maxWidth,
... ... @@ -222,7 +210,96 @@ class BoxConstraints {
}
@immutable
class EdgeInsets {
abstract class EdgeInsetsGeometry {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const EdgeInsetsGeometry();
double get _bottom;
double get _end;
double get _left;
double get _right;
double get _start;
double get _top;
/// The total offset in the horizontal direction.
double get horizontal => _left + _right + _start + _end;
/// The total offset in the vertical direction.
double get vertical => _top + _bottom;
/// Convert this instance into an [EdgeInsets], which uses literal coordinates
/// (i.e. the `left` coordinate being explicitly a distance from the left, and
/// the `right` coordinate being explicitly a distance from the right).
///
/// See also:
///
/// * [EdgeInsets], for which this is a no-op (returns itself).
/// * [EdgeInsetsDirectional], which flips the horizontal direction
/// based on the `direction` argument.
EdgeInsets resolve(TextDirection? direction);
/// Returns the sum of two [EdgeInsetsGeometry] objects.
///
/// If you know you are adding two [EdgeInsets] or two [EdgeInsetsDirectional]
/// objects, consider using the `+` operator instead, which always returns an
/// object of the same type as the operands, and is typed accordingly.
///
/// If [add] is applied to two objects of the same type ([EdgeInsets] or
/// [EdgeInsetsDirectional]), an object of that type will be returned (though
/// this is not reflected in the type system). Otherwise, an object
/// representing a combination of both is returned. That object can be turned
/// into a concrete [EdgeInsets] using [resolve].
EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
return _MixedEdgeInsets.fromLRSETB(
_left + other._left,
_right + other._right,
_start + other._start,
_end + other._end,
_top + other._top,
_bottom + other._bottom,
);
}
@override
String toString() {
if (_start == 0.0 && _end == 0.0) {
if (_left == 0.0 && _right == 0.0 && _top == 0.0 && _bottom == 0.0) {
return 'EdgeInsets.zero';
}
if (_left == _right && _right == _top && _top == _bottom) {
return 'EdgeInsets.all(${_left.toStringAsFixed(1)})';
}
return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
'${_top.toStringAsFixed(1)}, '
'${_right.toStringAsFixed(1)}, '
'${_bottom.toStringAsFixed(1)})';
}
if (_left == 0.0 && _right == 0.0) {
return 'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
'${_top.toStringAsFixed(1)}, '
'${_end.toStringAsFixed(1)}, '
'${_bottom.toStringAsFixed(1)})';
}
return 'EdgeInsets(${_left.toStringAsFixed(1)}, '
'${_top.toStringAsFixed(1)}, '
'${_right.toStringAsFixed(1)}, '
'${_bottom.toStringAsFixed(1)})'
' + '
'EdgeInsetsDirectional(${_start.toStringAsFixed(1)}, '
'0.0, '
'${_end.toStringAsFixed(1)}, '
'0.0)';
}
}
@immutable
class EdgeInsets extends EdgeInsetsGeometry {
const EdgeInsets.fromLTRB(this.left, this.top, this.right, this.bottom);
const EdgeInsets.all(double value)
... ... @@ -231,8 +308,7 @@ class EdgeInsets {
right = value,
bottom = value;
const EdgeInsets.only(
{this.left = 0.0, this.top = 0.0, this.right = 0.0, this.bottom = 0.0});
const EdgeInsets.only({this.left = 0.0, this.top = 0.0, this.right = 0.0, this.bottom = 0.0});
const EdgeInsets.symmetric({double vertical = 0.0, double horizontal = 0.0})
: left = horizontal,
... ... @@ -242,19 +318,45 @@ class EdgeInsets {
static const EdgeInsets zero = EdgeInsets.only();
/// The offset from the left.
final double left;
@override
double get _left => left;
/// The offset from the top.
final double top;
@override
double get _top => top;
/// The offset from the right.
final double right;
@override
double get _right => right;
/// The offset from the bottom.
final double bottom;
/// The total offset in the horizontal direction.
double get horizontal => left + right;
@override
double get _bottom => bottom;
/// The total offset in the vertical direction.
double get vertical => top + bottom;
@override
double get _start => 0.0;
@override
double get _end => 0.0;
/// Returns the sum of two [EdgeInsets].
EdgeInsets operator +(EdgeInsets other) {
return EdgeInsets.fromLTRB(
left + other.left,
top + other.top,
right + other.right,
bottom + other.bottom,
);
}
EdgeInsets copyWith({
double? left,
... ... @@ -270,18 +372,198 @@ class EdgeInsets {
);
}
/// Returns the sum of two [EdgeInsets] objects.
EdgeInsets add(EdgeInsets other) {
return EdgeInsets.fromLTRB(
left + other.left,
@override
EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
if (other is EdgeInsets) {
return this + other;
}
return super.add(other);
}
@override
EdgeInsets resolve(TextDirection? direction) => this;
}
class _MixedEdgeInsets extends EdgeInsetsGeometry {
const _MixedEdgeInsets.fromLRSETB(this._left, this._right, this._start, this._end, this._top, this._bottom);
@override
final double _left;
@override
final double _right;
@override
final double _start;
@override
final double _end;
@override
final double _top;
@override
final double _bottom;
@override
EdgeInsets resolve(TextDirection? direction) {
assert(direction != null);
switch (direction!) {
case TextDirection.rtl:
return EdgeInsets.fromLTRB(_end + _left, _top, _start + _right, _bottom);
case TextDirection.ltr:
return EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom);
}
}
}
/// An immutable set of offsets in each of the four cardinal directions, but
/// whose horizontal components are dependent on the writing direction.
///
/// This can be used to indicate padding from the left in [TextDirection.ltr]
/// text and padding from the right in [TextDirection.rtl] text without having
/// to be aware of the current text direction.
///
/// See also:
///
/// * [EdgeInsets], a variant that uses physical labels (left and right instead
/// of start and end).
class EdgeInsetsDirectional extends EdgeInsetsGeometry {
/// Creates insets from offsets from the start, top, end, and bottom.
const EdgeInsetsDirectional.fromSTEB(this.start, this.top, this.end, this.bottom);
/// Creates insets with only the given values non-zero.
///
/// {@tool snippet}
///
/// A margin indent of 40 pixels on the leading side:
///
/// ```dart
/// const EdgeInsetsDirectional.only(start: 40.0)
/// ```
/// {@end-tool}
const EdgeInsetsDirectional.only({
this.start = 0.0,
this.top = 0.0,
this.end = 0.0,
this.bottom = 0.0,
});
/// Creates insets with symmetric vertical and horizontal offsets.
///
/// This is equivalent to [EdgeInsets.symmetric], since the inset is the same
/// with either [TextDirection]. This constructor is just a convenience for
/// type compatibility.
///
/// {@tool snippet}
/// Eight pixel margin above and below, no horizontal margins:
///
/// ```dart
/// const EdgeInsetsDirectional.symmetric(vertical: 8.0)
/// ```
/// {@end-tool}
const EdgeInsetsDirectional.symmetric({
double horizontal = 0.0,
double vertical = 0.0,
}) : start = horizontal,
end = horizontal,
top = vertical,
bottom = vertical;
/// Creates insets where all the offsets are `value`.
///
/// {@tool snippet}
///
/// Typical eight-pixel margin on all sides:
///
/// ```dart
/// const EdgeInsetsDirectional.all(8.0)
/// ```
/// {@end-tool}
const EdgeInsetsDirectional.all(double value)
: start = value,
top = value,
end = value,
bottom = value;
/// An [EdgeInsetsDirectional] with zero offsets in each direction.
///
/// Consider using [EdgeInsets.zero] instead, since that object has the same
/// effect, but will be cheaper to [resolve].
static const EdgeInsetsDirectional zero = EdgeInsetsDirectional.only();
/// The offset from the start side, the side from which the user will start
/// reading text.
///
/// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
/// value by the [resolve] method.
final double start;
@override
double get _start => start;
/// The offset from the top.
///
/// This value is passed through to [EdgeInsets.top] unmodified by the
/// [resolve] method.
final double top;
@override
double get _top => top;
/// The offset from the end side, the side on which the user ends reading
/// text.
///
/// This value is normalized into an [EdgeInsets.left] or [EdgeInsets.right]
/// value by the [resolve] method.
final double end;
@override
double get _end => end;
/// The offset from the bottom.
///
/// This value is passed through to [EdgeInsets.bottom] unmodified by the
/// [resolve] method.
final double bottom;
@override
double get _bottom => bottom;
@override
double get _left => 0.0;
@override
double get _right => 0.0;
@override
EdgeInsetsGeometry add(EdgeInsetsGeometry other) {
if (other is EdgeInsetsDirectional) {
return this + other;
}
return super.add(other);
}
/// Returns the sum of two [EdgeInsetsDirectional] objects.
EdgeInsetsDirectional operator +(EdgeInsetsDirectional other) {
return EdgeInsetsDirectional.fromSTEB(
start + other.start,
top + other.top,
right + other.right,
end + other.end,
bottom + other.bottom,
);
}
@override
String toString() => 'EdgeInsets $left, $top, $right, $bottom';
EdgeInsets resolve(TextDirection? direction) {
assert(direction != null);
switch (direction!) {
case TextDirection.rtl:
return EdgeInsets.fromLTRB(end, top, start, bottom);
case TextDirection.ltr:
return EdgeInsets.fromLTRB(start, top, end, bottom);
}
}
}
class Alignment {
... ... @@ -374,10 +656,7 @@ class FittedSizes {
}
FittedSizes applyBoxFit(BoxFit fit, PdfPoint inputSize, PdfPoint outputSize) {
if (inputSize.y <= 0.0 ||
inputSize.x <= 0.0 ||
outputSize.y <= 0.0 ||
outputSize.x <= 0.0) {
if (inputSize.y <= 0.0 || inputSize.x <= 0.0 || outputSize.y <= 0.0 || outputSize.x <= 0.0) {
return const FittedSizes(PdfPoint.zero, PdfPoint.zero);
}
... ... @@ -390,38 +669,29 @@ FittedSizes applyBoxFit(BoxFit fit, PdfPoint inputSize, PdfPoint outputSize) {
case BoxFit.contain:
sourceSize = inputSize;
if (outputSize.x / outputSize.y > sourceSize.x / sourceSize.y) {
destinationSize =
PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
destinationSize = PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
} else {
destinationSize =
PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
destinationSize = PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
}
break;
case BoxFit.cover:
if (outputSize.x / outputSize.y > inputSize.x / inputSize.y) {
sourceSize =
PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
sourceSize = PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
} else {
sourceSize =
PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
sourceSize = PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
}
destinationSize = outputSize;
break;
case BoxFit.fitWidth:
sourceSize =
PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
destinationSize =
PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
sourceSize = PdfPoint(inputSize.x, inputSize.x * outputSize.y / outputSize.x);
destinationSize = PdfPoint(outputSize.x, sourceSize.y * outputSize.x / sourceSize.x);
break;
case BoxFit.fitHeight:
sourceSize =
PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
destinationSize =
PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
sourceSize = PdfPoint(inputSize.y * outputSize.x / outputSize.y, inputSize.y);
destinationSize = PdfPoint(sourceSize.x * outputSize.y / sourceSize.y, outputSize.y);
break;
case BoxFit.none:
sourceSize = PdfPoint(math.min(inputSize.x, outputSize.x),
math.min(inputSize.y, outputSize.y));
sourceSize = PdfPoint(math.min(inputSize.x, outputSize.x), math.min(inputSize.y, outputSize.y));
destinationSize = sourceSize;
break;
case BoxFit.scaleDown:
... ...
... ... @@ -61,14 +61,12 @@ mixin SpanningWidget on Widget {
/// Called before relayout to restore the saved state and
/// restart the layout in the same conditions
@protected
void applyContext(covariant WidgetContext context) =>
saveContext().apply(context);
void applyContext(covariant WidgetContext context) => saveContext().apply(context);
}
class NewPage extends Widget {
@override
void layout(Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
void layout(Context context, BoxConstraints constraints, {bool parentUsesSize = false}) {
box = PdfRect.zero;
}
}
... ... @@ -151,7 +149,7 @@ class MultiPage extends Page {
ThemeData? theme,
this.maxPages = 20,
PageOrientation? orientation,
EdgeInsets? margin,
EdgeInsetsGeometry? margin,
TextDirection? textDirection,
}) : _buildList = build,
assert(maxPages > 0),
... ... @@ -185,10 +183,9 @@ class MultiPage extends Page {
/// This is not checked with a Release build.
final int maxPages;
void _paintChild(
Context context, Widget child, double x, double y, double pageHeight) {
void _paintChild(Context context, Widget child, double x, double y, double pageHeight) {
if (mustRotate) {
final _margin = margin!;
final _margin = resolvedMargin!;
context.canvas
..saveContext()
..setTransform(Matrix4.identity()
... ... @@ -198,10 +195,21 @@ class MultiPage extends Page {
y + _margin.left - _margin.bottom,
));
final availableWidth = pageHeight - resolvedMargin!.vertical;
if (pageTheme.textDirection == TextDirection.rtl) {
child.box = child.box!.copyWith(
x: (availableWidth - child.box!.width) + child.box!.x,
);
}
child.paint(context);
context.canvas.restoreContext();
} else {
child.box = PdfRect(x, y, child.box!.width, child.box!.height);
var childXPos = x;
if (pageTheme.textDirection == TextDirection.rtl) {
final availableWidth = pageFormat.width - resolvedMargin!.horizontal;
childXPos = (availableWidth - child.box!.width) + x;
}
child.box = child.box!.copyWith(x: childXPos, y: y);
child.paint(context);
}
}
... ... @@ -211,33 +219,26 @@ class MultiPage extends Page {
assert(pageFormat.width > 0 && pageFormat.width < double.infinity);
assert(pageFormat.height > 0 && pageFormat.height < double.infinity);
final _margin = margin;
final _margin = resolvedMargin!;
final _mustRotate = mustRotate;
final pageHeight = _mustRotate ? pageFormat.width : pageFormat.height;
final pageHeightMargin =
_mustRotate ? _margin!.horizontal : _margin!.vertical;
final pageHeightMargin = _mustRotate ? _margin.horizontal : _margin.vertical;
final constraints = BoxConstraints(
maxWidth: _mustRotate
? (pageFormat.height - _margin.vertical)
: (pageFormat.width - _margin.horizontal));
maxWidth: _mustRotate ? (pageFormat.height - _margin.vertical) : (pageFormat.width - _margin.horizontal));
final fullConstraints = mustRotate
? BoxConstraints(
maxWidth: pageFormat.height - _margin.vertical,
maxHeight: pageFormat.width - _margin.horizontal)
maxWidth: pageFormat.height - _margin.vertical, maxHeight: pageFormat.width - _margin.horizontal)
: BoxConstraints(
maxWidth: pageFormat.width - _margin.horizontal,
maxHeight: pageFormat.height - _margin.vertical);
maxWidth: pageFormat.width - _margin.horizontal, maxHeight: pageFormat.height - _margin.vertical);
final calculatedTheme = theme ?? document.theme ?? ThemeData.base();
Context? context;
late double offsetEnd;
double? offsetStart;
var _index = 0;
var sameCount = 0;
final baseContext =
Context(document: document.document).inheritFromAll(<Inherited>[
final baseContext = Context(document: document.document).inheritFromAll(<Inherited>[
calculatedTheme,
if (pageTheme.textDirection != null)
InheritedDirectionality(pageTheme.textDirection),
if (pageTheme.textDirection != null) InheritedDirectionality(pageTheme.textDirection),
]);
final children = _buildList(baseContext);
WidgetContext? widgetContext;
... ... @@ -272,10 +273,8 @@ class MultiPage extends Page {
return true;
}());
offsetStart = pageHeight -
(_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
offsetEnd =
_mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
offsetStart = pageHeight - (_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
offsetEnd = _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
_pages.add(_MultiPageInstance(
context: context,
... ... @@ -327,8 +326,7 @@ class MultiPage extends Page {
// Else we crash if the widget is too big and cannot be separated
if (!canSpan) {
throw Exception(
'Widget won\'t fit into the page as its height (${child.box!.height}) '
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');
}
... ... @@ -340,8 +338,7 @@ class MultiPage extends Page {
span.applyContext(savedContext);
}
final localConstraints =
constraints.copyWith(maxHeight: offsetStart - offsetEnd);
final localConstraints = constraints.copyWith(maxHeight: offsetStart - offsetEnd);
span.layout(context, localConstraints, parentUsesSize: false);
assert(span.box != null);
widgetContext = span.saveContext();
... ... @@ -368,8 +365,7 @@ class MultiPage extends Page {
_MultiPageWidget(
child: child,
constraints: constraints,
widgetContext:
child is SpanningWidget && canSpan ? child.cloneContext() : null,
widgetContext: child is SpanningWidget && canSpan ? child.cloneContext() : null,
),
);
... ... @@ -381,28 +377,24 @@ class MultiPage extends Page {
@override
void postProcess(Document document) {
final _margin = margin;
final _margin = resolvedMargin!;
final _mustRotate = mustRotate;
final pageHeight = _mustRotate ? pageFormat.width : pageFormat.height;
final pageWidth = _mustRotate ? pageFormat.height : pageFormat.width;
final pageHeightMargin =
_mustRotate ? _margin!.horizontal : _margin!.vertical;
final pageHeightMargin = _mustRotate ? _margin.horizontal : _margin.vertical;
final pageWidthMargin = _mustRotate ? _margin.vertical : _margin.horizontal;
final availableWidth = pageWidth - pageWidthMargin;
for (final page in _pages) {
var offsetStart = pageHeight -
(_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
var offsetEnd =
_mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
var offsetStart = pageHeight - (_mustRotate ? pageHeightMargin - _margin.bottom : _margin.top);
var offsetEnd = _mustRotate ? pageHeightMargin - _margin.left : _margin.bottom;
if (pageTheme.buildBackground != null) {
final child = pageTheme.buildBackground!(page.context);
child.layout(page.context, page.fullConstraints, parentUsesSize: false);
assert(child.box != null);
_paintChild(page.context, child, _margin.left, _margin.bottom,
pageFormat.height);
_paintChild(page.context, child, _margin.left, _margin.bottom, pageFormat.height);
}
var totalFlex = 0;
... ... @@ -428,23 +420,20 @@ class MultiPage extends Page {
if (header != null) {
final headerWidget = header!(page.context);
headerWidget.layout(page.context, page.constraints,
parentUsesSize: false);
headerWidget.layout(page.context, page.constraints, parentUsesSize: false);
assert(headerWidget.box != null);
offsetStart -= headerWidget.box!.height;
_paintChild(page.context, headerWidget, _margin.left,
page.offsetStart! - headerWidget.box!.height, pageFormat.height);
_paintChild(
page.context, headerWidget, _margin.left, page.offsetStart! - headerWidget.box!.height, pageFormat.height);
}
if (footer != null) {
final footerWidget = footer!(page.context);
footerWidget.layout(page.context, page.constraints,
parentUsesSize: false);
footerWidget.layout(page.context, page.constraints, parentUsesSize: false);
assert(footerWidget.box != null);
offsetEnd += footerWidget.box!.height;
_paintChild(page.context, footerWidget, _margin.left, _margin.bottom,
pageFormat.height);
_paintChild(page.context, footerWidget, _margin.left, _margin.bottom, pageFormat.height);
}
final freeSpace = math.max(0.0, offsetStart - offsetEnd - allocatedSize);
... ... @@ -473,16 +462,14 @@ class MultiPage extends Page {
break;
case MainAxisAlignment.spaceBetween:
leadingSpace = 0.0;
betweenSpace =
totalChildren > 1 ? freeSpace / (totalChildren - 1) : 0.0;
betweenSpace = totalChildren > 1 ? freeSpace / (totalChildren - 1) : 0.0;
break;
case MainAxisAlignment.spaceAround:
betweenSpace = totalChildren > 0 ? freeSpace / totalChildren : 0.0;
leadingSpace = betweenSpace / 2.0;
break;
case MainAxisAlignment.spaceEvenly:
betweenSpace =
totalChildren > 0 ? freeSpace / (totalChildren + 1) : 0.0;
betweenSpace = totalChildren > 0 ? freeSpace / (totalChildren + 1) : 0.0;
leadingSpace = betweenSpace;
break;
}
... ... @@ -494,11 +481,8 @@ class MultiPage extends Page {
final flex = child is Flexible ? child.flex : 0;
final fit = child is Flexible ? child.fit : FlexFit.loose;
if (flex > 0) {
assert(child is! SpanningWidget || child.canSpan == false,
'Cannot have a spanning widget flexible');
final maxChildExtent = child == lastFlexChild
? (freeSpace - allocatedFlexSpace)
: spacePerFlex * flex;
assert(child is! SpanningWidget || child.canSpan == false, 'Cannot have a spanning widget flexible');
final maxChildExtent = child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex;
late double minChildExtent;
switch (fit) {
case FlexFit.tight:
... ... @@ -547,8 +531,7 @@ class MultiPage extends Page {
if (child is SpanningWidget && child.canSpan) {
child.applyContext(widget.widgetContext!);
}
_paintChild(page.context, widget.child, _margin.left + x, pos,
pageFormat.height);
_paintChild(page.context, widget.child, _margin.left + x, pos, pageFormat.height);
pos -= betweenSpace;
}
... ... @@ -557,8 +540,7 @@ class MultiPage extends Page {
child.layout(page.context, page.fullConstraints, parentUsesSize: false);
assert(child.box != null);
_paintChild(page.context, child, _margin.left, _margin.bottom,
pageFormat.height);
_paintChild(page.context, child, _margin.left, _margin.bottom, pageFormat.height);
}
}
}
... ...
... ... @@ -40,7 +40,7 @@ class Page {
required BuildCallback build,
ThemeData? theme,
PageOrientation? orientation,
EdgeInsets? margin,
EdgeInsetsGeometry? margin,
bool clip = false,
TextDirection? textDirection,
}) : assert(
... ... @@ -77,11 +77,14 @@ class Page {
PdfPage? _pdfPage;
EdgeInsets? get margin => pageTheme.margin;
EdgeInsetsGeometry? get margin => pageTheme.margin;
EdgeInsets? get resolvedMargin => margin?.resolve(pageTheme.textDirection);
@protected
void debugPaint(Context context) {
final _margin = margin!;
final _margin = resolvedMargin!;
context.canvas
..setFillColor(PdfColors.lightGreen)
..moveTo(0, 0)
... ... @@ -90,8 +93,7 @@ class Page {
..lineTo(0, pageFormat.height)
..moveTo(_margin.left, _margin.bottom)
..lineTo(_margin.left, pageFormat.height - _margin.top)
..lineTo(
pageFormat.width - _margin.right, pageFormat.height - _margin.top)
..lineTo(pageFormat.width - _margin.right, pageFormat.height - _margin.top)
..lineTo(pageFormat.width - _margin.right, _margin.bottom)
..fillPath();
}
... ... @@ -99,8 +101,7 @@ class Page {
void generate(Document document, {bool insert = true, int? index}) {
if (index != null) {
if (insert) {
_pdfPage =
PdfPage(document.document, pageFormat: pageFormat, index: index);
_pdfPage = PdfPage(document.document, pageFormat: pageFormat, index: index);
} else {
_pdfPage = document.document.page(index);
}
... ... @@ -112,14 +113,12 @@ class Page {
void postProcess(Document document) {
final canvas = _pdfPage!.getGraphics();
canvas.reset();
final _margin = margin;
final _margin = resolvedMargin;
var constraints = mustRotate
? BoxConstraints(
maxWidth: pageFormat.height - _margin!.vertical,
maxHeight: pageFormat.width - _margin.horizontal)
maxWidth: pageFormat.height - _margin!.vertical, maxHeight: pageFormat.width - _margin.horizontal)
: BoxConstraints(
maxWidth: pageFormat.width - _margin!.horizontal,
maxHeight: pageFormat.height - _margin.vertical);
maxWidth: pageFormat.width - _margin!.horizontal, maxHeight: pageFormat.height - _margin.vertical);
final calculatedTheme = theme ?? document.theme ?? ThemeData.base();
final context = Context(
... ... @@ -128,8 +127,7 @@ class Page {
canvas: canvas,
).inheritFromAll(<Inherited>[
calculatedTheme,
if (pageTheme.textDirection != null)
InheritedDirectionality(pageTheme.textDirection),
if (pageTheme.textDirection != null) InheritedDirectionality(pageTheme.textDirection),
]);
Widget? background;
... ... @@ -141,8 +139,7 @@ class Page {
final size = layout(content, context, constraints);
if (_pdfPage!.pageFormat.height == double.infinity) {
_pdfPage!.pageFormat =
_pdfPage!.pageFormat.copyWith(width: size.x, height: size.y);
_pdfPage!.pageFormat = _pdfPage!.pageFormat.copyWith(width: size.x, height: size.y);
constraints = mustRotate
? BoxConstraints(
maxWidth: _pdfPage!.pageFormat.height - _margin.vertical,
... ... @@ -181,43 +178,49 @@ class Page {
}
@protected
PdfPoint layout(Widget child, Context context, BoxConstraints constraints,
{bool parentUsesSize = false}) {
final _margin = margin!;
PdfPoint layout(Widget child, Context context, BoxConstraints constraints, {bool parentUsesSize = false}) {
final _margin = resolvedMargin!;
child.layout(context, constraints, parentUsesSize: parentUsesSize);
assert(child.box != null);
final width = pageFormat.width == double.infinity
? child.box!.width + _margin.left + _margin.right
: pageFormat.width;
final width =
pageFormat.width == double.infinity ? child.box!.width + _margin.left + _margin.right : pageFormat.width;
final height = pageFormat.height == double.infinity
? child.box!.height + _margin.top + _margin.bottom
: pageFormat.height;
final height =
pageFormat.height == double.infinity ? child.box!.height + _margin.top + _margin.bottom : pageFormat.height;
child.box = PdfRect(_margin.left, height - child.box!.height - _margin.top,
child.box!.width, child.box!.height);
child.box = PdfRect(_margin.left, height - child.box!.height - _margin.top, child.box!.width, child.box!.height);
return PdfPoint(width, height);
}
@protected
void paint(Widget child, Context context) {
if (pageTheme.clip) {
final _margin = margin!;
context.canvas
..saveContext()
..drawRect(
final _margin = resolvedMargin!;
final box = PdfRect(
_margin.left,
_margin.bottom,
pageFormat.width - _margin.horizontal,
pageFormat.height - _margin.vertical,
)
);
if (pageTheme.clip) {
context.canvas
..saveContext()
..drawRect(box.x, box.y, box.width, box.height)
..clipPath();
}
if (pageTheme.textDirection == TextDirection.rtl) {
child.box = PdfRect(
((mustRotate ? box.height : box.width) - child.box!.width) + child.box!.x,
child.box!.y,
child.box!.width,
child.box!.height,
);
}
if (mustRotate) {
final _margin = margin!;
final _margin = resolvedMargin!;
context.canvas
..saveContext()
..setTransform(Matrix4.identity()
... ...
... ... @@ -30,7 +30,7 @@ class PageTheme {
this.buildForeground,
this.theme,
PageOrientation? orientation,
EdgeInsets? margin,
EdgeInsetsGeometry? margin,
this.clip = false,
this.textDirection,
}) : pageFormat = pageFormat ?? PdfPageFormat.standard,
... ... @@ -41,7 +41,7 @@ class PageTheme {
final PageOrientation orientation;
final EdgeInsets? _margin;
final EdgeInsetsGeometry? _margin;
final BuildCallback? buildBackground;
... ... @@ -54,27 +54,30 @@ class PageTheme {
final TextDirection? textDirection;
bool get mustRotate =>
(orientation == PageOrientation.landscape &&
pageFormat.height > pageFormat.width) ||
(orientation == PageOrientation.portrait &&
pageFormat.width > pageFormat.height);
(orientation == PageOrientation.landscape && pageFormat.height > pageFormat.width) ||
(orientation == PageOrientation.portrait && pageFormat.width > pageFormat.height);
EdgeInsets? get margin {
EdgeInsetsGeometry? get margin {
if (_margin != null) {
final effectiveMargin = _margin!.resolve(textDirection);
if (mustRotate) {
return EdgeInsets.fromLTRB(
_margin!.bottom, _margin!.left, _margin!.top, _margin!.right);
effectiveMargin.bottom,
effectiveMargin.left,
effectiveMargin.top,
effectiveMargin.right,
);
} else {
return _margin;
}
}
if (mustRotate) {
return EdgeInsets.fromLTRB(pageFormat.marginBottom, pageFormat.marginLeft,
pageFormat.marginTop, pageFormat.marginRight);
return EdgeInsets.fromLTRB(
pageFormat.marginBottom, pageFormat.marginLeft, pageFormat.marginTop, pageFormat.marginRight);
} else {
return EdgeInsets.fromLTRB(pageFormat.marginLeft, pageFormat.marginTop,
pageFormat.marginRight, pageFormat.marginBottom);
return EdgeInsets.fromLTRB(
pageFormat.marginLeft, pageFormat.marginTop, pageFormat.marginRight, pageFormat.marginBottom);
}
}
... ...
... ... @@ -40,11 +40,7 @@ final _yellowBox = Container(
color: PdfColors.yellow,
);
final _greenBox = Container(
width: 50,
height: 50,
color: PdfColors.green,
);
void main() {
setUpAll(() {
Document.debug = true;
... ... @@ -144,9 +140,8 @@ void main() {
width: 150,
height: 150,
child: Wrap(
children: [_blueBox, _redBox,_yellowBox],
)
),
children: [_blueBox, _redBox, _yellowBox],
)),
),
);
});
... ... @@ -160,9 +155,8 @@ void main() {
width: 150,
height: 150,
child: Wrap(
children: [_blueBox, _redBox,_yellowBox],
)
),
children: [_blueBox, _redBox, _yellowBox],
)),
),
);
});
... ... @@ -178,9 +172,8 @@ void main() {
spacing: 10,
runSpacing: 10,
runAlignment: WrapAlignment.center,
children: [_blueBox, _redBox,_yellowBox],
)
),
children: [_blueBox, _redBox, _yellowBox],
)),
),
);
});
... ... @@ -197,9 +190,96 @@ void main() {
spacing: 10,
runSpacing: 10,
runAlignment: WrapAlignment.end,
children: [_blueBox, _redBox,_yellowBox],
)
children: [_blueBox, _redBox, _yellowBox],
)),
),
);
});
test('RTL Page Should render child aligned right', () {
pdf.addPage(
Page(
textDirection: TextDirection.rtl,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return _blueBox;
},
),
);
});
test('LTR Page Should render child aligned left', () {
pdf.addPage(
Page(
textDirection: TextDirection.ltr,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return _blueBox;
},
),
);
});
test('RTL Multi Page Should render child aligned right', () {
pdf.addPage(
MultiPage(
textDirection: TextDirection.rtl,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return [
ListView(children: [
for(int i = 0; i < 30; i++)
Text('Hello World')
]),
];
},
),
);
});
test('LTR Multi Page Should render child aligned left', () {
pdf.addPage(
MultiPage(
textDirection: TextDirection.ltr,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return [
ListView(children: [
for(int i = 0; i < 30; i++)
Text('Hello World')
]),
];
},
),
);
});
test('Should render a blue box padded from right', () {
pdf.addPage(
Page(
textDirection: TextDirection.rtl,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 20),
child: _blueBox,
);
},
),
);
});
test('Should render a blue box padded from left', () {
pdf.addPage(
Page(
textDirection: TextDirection.ltr,
pageFormat: const PdfPageFormat(150, 150),
build: (Context context) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 20),
child: _blueBox,
);
},
),
);
});
... ...