David PHAM-VAN

Add Wrap Widget

@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 * Add better debugPaint on Align Widget 5 * Add better debugPaint on Align Widget
6 * Fix Transform placement when Alignment and Origin are Null 6 * Fix Transform placement when Alignment and Origin are Null
7 * Add Transform.rotateBox constructor 7 * Add Transform.rotateBox constructor
  8 +* Add Wrap Widget
8 9
9 ## 1.3.15 10 ## 1.3.15
10 11
@@ -42,3 +42,4 @@ part 'widgets/table.dart'; @@ -42,3 +42,4 @@ part 'widgets/table.dart';
42 part 'widgets/text.dart'; 42 part 'widgets/text.dart';
43 part 'widgets/theme.dart'; 43 part 'widgets/theme.dart';
44 part 'widgets/widget.dart'; 44 part 'widgets/widget.dart';
  45 +part 'widgets/wrap.dart';
@@ -46,6 +46,14 @@ class BoxConstraints { @@ -46,6 +46,14 @@ class BoxConstraints {
46 minHeight = height != null ? height : double.infinity, 46 minHeight = height != null ? height : double.infinity,
47 maxHeight = height != null ? height : double.infinity; 47 maxHeight = height != null ? height : double.infinity;
48 48
  49 + const BoxConstraints.tightForFinite({
  50 + double width = double.infinity,
  51 + double height = double.infinity,
  52 + }) : minWidth = width != double.infinity ? width : 0.0,
  53 + maxWidth = width != double.infinity ? width : double.infinity,
  54 + minHeight = height != double.infinity ? height : 0.0,
  55 + maxHeight = height != double.infinity ? height : double.infinity;
  56 +
49 /// The minimum width that satisfies the constraints. 57 /// The minimum width that satisfies the constraints.
50 final double minWidth; 58 final double minWidth;
51 59
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +part of widget;
  18 +
  19 +/// How [Wrap] should align objects.
  20 +enum WrapAlignment {
  21 + start,
  22 + end,
  23 + center,
  24 + spaceBetween,
  25 + spaceAround,
  26 + spaceEvenly
  27 +}
  28 +
  29 +/// Who [Wrap] should align children within a run in the cross axis.
  30 +enum WrapCrossAlignment { start, end, center }
  31 +
  32 +class _RunMetrics {
  33 + _RunMetrics(this.mainAxisExtent, this.crossAxisExtent, this.childCount);
  34 +
  35 + final double mainAxisExtent;
  36 + final double crossAxisExtent;
  37 + final int childCount;
  38 +}
  39 +
  40 +/// A widget that displays its children in multiple horizontal or vertical runs.
  41 +class Wrap extends MultiChildWidget {
  42 + /// Creates a wrap layout.
  43 +
  44 + Wrap({
  45 + this.direction = Axis.horizontal,
  46 + this.alignment = WrapAlignment.start,
  47 + this.spacing = 0.0,
  48 + this.runAlignment = WrapAlignment.start,
  49 + this.runSpacing = 0.0,
  50 + this.crossAxisAlignment = WrapCrossAlignment.start,
  51 + this.verticalDirection = VerticalDirection.down,
  52 + List<Widget> children = const <Widget>[],
  53 + }) : assert(direction != null),
  54 + assert(alignment != null),
  55 + assert(spacing != null),
  56 + assert(runAlignment != null),
  57 + assert(runSpacing != null),
  58 + assert(crossAxisAlignment != null),
  59 + super(children: children);
  60 +
  61 + /// The direction to use as the main axis.
  62 + final Axis direction;
  63 +
  64 + /// How the children within a run should be placed in the main axis.
  65 + final WrapAlignment alignment;
  66 +
  67 + /// How much space to place between children in a run in the main axis.
  68 + final double spacing;
  69 +
  70 + /// How the runs themselves should be placed in the cross axis.
  71 + final WrapAlignment runAlignment;
  72 +
  73 + /// How much space to place between the runs themselves in the cross axis.
  74 + final double runSpacing;
  75 +
  76 + /// How the children within a run should be aligned relative to each other in
  77 + /// the cross axis.
  78 + final WrapCrossAlignment crossAxisAlignment;
  79 +
  80 + /// Determines the order to lay children out vertically and how to interpret
  81 + /// `start` and `end` in the vertical direction.
  82 + final VerticalDirection verticalDirection;
  83 +
  84 + bool get textDirection => false;
  85 +
  86 + bool _hasVisualOverflow = false;
  87 +
  88 + bool get _debugHasNecessaryDirections {
  89 + assert(direction != null);
  90 + assert(alignment != null);
  91 + assert(runAlignment != null);
  92 + assert(crossAxisAlignment != null);
  93 + if (children.length > 1) {
  94 + // i.e. there's more than one child
  95 + switch (direction) {
  96 + case Axis.horizontal:
  97 + assert(textDirection != null,
  98 + 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
  99 + break;
  100 + case Axis.vertical:
  101 + assert(verticalDirection != null,
  102 + 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
  103 + break;
  104 + }
  105 + }
  106 + if (alignment == WrapAlignment.start || alignment == WrapAlignment.end) {
  107 + switch (direction) {
  108 + case Axis.horizontal:
  109 + assert(textDirection != null,
  110 + 'Horizontal $runtimeType with alignment $alignment has a null textDirection, so the alignment cannot be resolved.');
  111 + break;
  112 + case Axis.vertical:
  113 + assert(verticalDirection != null,
  114 + 'Vertical $runtimeType with alignment $alignment has a null verticalDirection, so the alignment cannot be resolved.');
  115 + break;
  116 + }
  117 + }
  118 + if (runAlignment == WrapAlignment.start ||
  119 + runAlignment == WrapAlignment.end) {
  120 + switch (direction) {
  121 + case Axis.horizontal:
  122 + assert(verticalDirection != null,
  123 + 'Horizontal $runtimeType with runAlignment $runAlignment has a null verticalDirection, so the alignment cannot be resolved.');
  124 + break;
  125 + case Axis.vertical:
  126 + assert(textDirection != null,
  127 + 'Vertical $runtimeType with runAlignment $runAlignment has a null textDirection, so the alignment cannot be resolved.');
  128 + break;
  129 + }
  130 + }
  131 + if (crossAxisAlignment == WrapCrossAlignment.start ||
  132 + crossAxisAlignment == WrapCrossAlignment.end) {
  133 + switch (direction) {
  134 + case Axis.horizontal:
  135 + assert(verticalDirection != null,
  136 + 'Horizontal $runtimeType with crossAxisAlignment $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
  137 + break;
  138 + case Axis.vertical:
  139 + assert(textDirection != null,
  140 + 'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
  141 + break;
  142 + }
  143 + }
  144 + return true;
  145 + }
  146 +
  147 + double _getMainAxisExtent(Widget child) {
  148 + switch (direction) {
  149 + case Axis.horizontal:
  150 + return child.box.width;
  151 + case Axis.vertical:
  152 + return child.box.height;
  153 + }
  154 + return 0.0;
  155 + }
  156 +
  157 + double _getCrossAxisExtent(Widget child) {
  158 + switch (direction) {
  159 + case Axis.horizontal:
  160 + return child.box.height;
  161 + case Axis.vertical:
  162 + return child.box.width;
  163 + }
  164 + return 0.0;
  165 + }
  166 +
  167 + PdfPoint _getOffset(double mainAxisOffset, double crossAxisOffset) {
  168 + switch (direction) {
  169 + case Axis.horizontal:
  170 + return PdfPoint(mainAxisOffset, crossAxisOffset);
  171 + case Axis.vertical:
  172 + return PdfPoint(crossAxisOffset, mainAxisOffset);
  173 + }
  174 + return PdfPoint.zero;
  175 + }
  176 +
  177 + double _getChildCrossAxisOffset(bool flipCrossAxis, double runCrossAxisExtent,
  178 + double childCrossAxisExtent) {
  179 + final double freeSpace = runCrossAxisExtent - childCrossAxisExtent;
  180 + switch (crossAxisAlignment) {
  181 + case WrapCrossAlignment.start:
  182 + return flipCrossAxis ? freeSpace : 0.0;
  183 + case WrapCrossAlignment.end:
  184 + return flipCrossAxis ? 0.0 : freeSpace;
  185 + case WrapCrossAlignment.center:
  186 + return freeSpace / 2.0;
  187 + }
  188 + return 0.0;
  189 + }
  190 +
  191 + @override
  192 + void layout(Context context, BoxConstraints constraints,
  193 + {bool parentUsesSize = false}) {
  194 + assert(_debugHasNecessaryDirections);
  195 + _hasVisualOverflow = false;
  196 +
  197 + if (children.isEmpty) {
  198 + box = PdfRect.fromPoints(PdfPoint.zero, constraints.smallest);
  199 + return;
  200 + }
  201 +
  202 + BoxConstraints childConstraints;
  203 + double mainAxisLimit = 0.0;
  204 + bool flipMainAxis = false;
  205 + bool flipCrossAxis = false;
  206 +
  207 + switch (direction) {
  208 + case Axis.horizontal:
  209 + childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
  210 + mainAxisLimit = constraints.maxWidth;
  211 + if (verticalDirection == VerticalDirection.down) {
  212 + flipCrossAxis = true;
  213 + }
  214 + break;
  215 + case Axis.vertical:
  216 + childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
  217 + mainAxisLimit = constraints.maxHeight;
  218 + if (verticalDirection == VerticalDirection.down) {
  219 + flipMainAxis = true;
  220 + }
  221 + break;
  222 + }
  223 +
  224 + assert(childConstraints != null);
  225 + assert(mainAxisLimit != null);
  226 +
  227 + final double spacing = this.spacing;
  228 + final double runSpacing = this.runSpacing;
  229 + final List<_RunMetrics> runMetrics = <_RunMetrics>[];
  230 + final Map<Widget, int> childRunMetrics = <Widget, int>{};
  231 + double mainAxisExtent = 0.0;
  232 + double crossAxisExtent = 0.0;
  233 + double runMainAxisExtent = 0.0;
  234 + double runCrossAxisExtent = 0.0;
  235 + int childCount = 0;
  236 +
  237 + for (Widget child in children) {
  238 + child.layout(context, childConstraints, parentUsesSize: true);
  239 +
  240 + final double childMainAxisExtent = _getMainAxisExtent(child);
  241 + final double childCrossAxisExtent = _getCrossAxisExtent(child);
  242 +
  243 + if (childCount > 0 &&
  244 + runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
  245 + mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
  246 + crossAxisExtent += runCrossAxisExtent;
  247 + if (runMetrics.isNotEmpty) {
  248 + crossAxisExtent += runSpacing;
  249 + }
  250 + runMetrics.add(
  251 + _RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
  252 + runMainAxisExtent = 0.0;
  253 + runCrossAxisExtent = 0.0;
  254 + childCount = 0;
  255 + }
  256 +
  257 + runMainAxisExtent += childMainAxisExtent;
  258 +
  259 + if (childCount > 0) {
  260 + runMainAxisExtent += spacing;
  261 + }
  262 +
  263 + runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
  264 + childCount += 1;
  265 +
  266 + childRunMetrics[child] = runMetrics.length;
  267 + }
  268 +
  269 + if (childCount > 0) {
  270 + mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
  271 + crossAxisExtent += runCrossAxisExtent;
  272 + if (runMetrics.isNotEmpty) {
  273 + crossAxisExtent += runSpacing;
  274 + }
  275 + runMetrics
  276 + .add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
  277 + }
  278 +
  279 + final int runCount = runMetrics.length;
  280 + assert(runCount > 0);
  281 +
  282 + double containerMainAxisExtent = 0.0;
  283 + double containerCrossAxisExtent = 0.0;
  284 +
  285 + switch (direction) {
  286 + case Axis.horizontal:
  287 + box = PdfRect.fromPoints(PdfPoint.zero,
  288 + constraints.constrain(PdfPoint(mainAxisExtent, crossAxisExtent)));
  289 + containerMainAxisExtent = box.width;
  290 + containerCrossAxisExtent = box.height;
  291 + break;
  292 + case Axis.vertical:
  293 + box = PdfRect.fromPoints(PdfPoint.zero,
  294 + constraints.constrain(PdfPoint(crossAxisExtent, mainAxisExtent)));
  295 + containerMainAxisExtent = box.height;
  296 + containerCrossAxisExtent = box.width;
  297 + break;
  298 + }
  299 +
  300 + _hasVisualOverflow = containerMainAxisExtent < mainAxisExtent ||
  301 + containerCrossAxisExtent < crossAxisExtent;
  302 +
  303 + final double crossAxisFreeSpace =
  304 + math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
  305 + double runLeadingSpace = 0.0;
  306 + double runBetweenSpace = 0.0;
  307 +
  308 + switch (runAlignment) {
  309 + case WrapAlignment.start:
  310 + break;
  311 + case WrapAlignment.end:
  312 + runLeadingSpace = crossAxisFreeSpace;
  313 + break;
  314 + case WrapAlignment.center:
  315 + runLeadingSpace = crossAxisFreeSpace / 2.0;
  316 + break;
  317 + case WrapAlignment.spaceBetween:
  318 + runBetweenSpace =
  319 + runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
  320 + break;
  321 + case WrapAlignment.spaceAround:
  322 + runBetweenSpace = crossAxisFreeSpace / runCount;
  323 + runLeadingSpace = runBetweenSpace / 2.0;
  324 + break;
  325 + case WrapAlignment.spaceEvenly:
  326 + runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
  327 + runLeadingSpace = runBetweenSpace;
  328 + break;
  329 + }
  330 +
  331 + runBetweenSpace += runSpacing;
  332 + double crossAxisOffset = flipCrossAxis
  333 + ? containerCrossAxisExtent - runLeadingSpace
  334 + : runLeadingSpace;
  335 +
  336 + int currentWidget = 0;
  337 + for (int i = 0; i < runCount; ++i) {
  338 + final _RunMetrics metrics = runMetrics[i];
  339 + final double runMainAxisExtent = metrics.mainAxisExtent;
  340 + final double runCrossAxisExtent = metrics.crossAxisExtent;
  341 + final int childCount = metrics.childCount;
  342 +
  343 + final double mainAxisFreeSpace =
  344 + math.max(0.0, containerMainAxisExtent - runMainAxisExtent);
  345 + double childLeadingSpace = 0.0;
  346 + double childBetweenSpace = 0.0;
  347 +
  348 + switch (alignment) {
  349 + case WrapAlignment.start:
  350 + break;
  351 + case WrapAlignment.end:
  352 + childLeadingSpace = mainAxisFreeSpace;
  353 + break;
  354 + case WrapAlignment.center:
  355 + childLeadingSpace = mainAxisFreeSpace / 2.0;
  356 + break;
  357 + case WrapAlignment.spaceBetween:
  358 + childBetweenSpace =
  359 + childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
  360 + break;
  361 + case WrapAlignment.spaceAround:
  362 + childBetweenSpace = mainAxisFreeSpace / childCount;
  363 + childLeadingSpace = childBetweenSpace / 2.0;
  364 + break;
  365 + case WrapAlignment.spaceEvenly:
  366 + childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
  367 + childLeadingSpace = childBetweenSpace;
  368 + break;
  369 + }
  370 +
  371 + childBetweenSpace += spacing;
  372 + double childMainPosition = flipMainAxis
  373 + ? containerMainAxisExtent - childLeadingSpace
  374 + : childLeadingSpace;
  375 +
  376 + if (flipCrossAxis) {
  377 + crossAxisOffset -= runCrossAxisExtent;
  378 + }
  379 +
  380 + for (Widget child in children.sublist(currentWidget)) {
  381 + final int runIndex = childRunMetrics[child];
  382 + if (runIndex != i) {
  383 + break;
  384 + }
  385 +
  386 + currentWidget++;
  387 + final double childMainAxisExtent = _getMainAxisExtent(child);
  388 + final double childCrossAxisExtent = _getCrossAxisExtent(child);
  389 + final double childCrossAxisOffset = _getChildCrossAxisOffset(
  390 + flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
  391 + if (flipMainAxis) {
  392 + childMainPosition -= childMainAxisExtent;
  393 + }
  394 + child.box = PdfRect.fromPoints(
  395 + _getOffset(
  396 + childMainPosition, crossAxisOffset + childCrossAxisOffset),
  397 + child.box.size);
  398 + if (flipMainAxis) {
  399 + childMainPosition -= childBetweenSpace;
  400 + } else {
  401 + childMainPosition += childMainAxisExtent + childBetweenSpace;
  402 + }
  403 + }
  404 +
  405 + if (flipCrossAxis) {
  406 + crossAxisOffset -= runBetweenSpace;
  407 + } else {
  408 + crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
  409 + }
  410 + }
  411 + }
  412 +
  413 + @override
  414 + void paint(Context context) {
  415 + super.paint(context);
  416 +
  417 + context.canvas.saveContext();
  418 +
  419 + if (_hasVisualOverflow) {
  420 + context.canvas
  421 + ..drawRect(box.left, box.bottom, box.width, box.height)
  422 + ..clipPath();
  423 + }
  424 +
  425 + final Matrix4 mat = Matrix4.identity();
  426 + mat.translate(box.x, box.y);
  427 + context.canvas.setTransform(mat);
  428 + for (Widget child in children) {
  429 + child.paint(context);
  430 + }
  431 +
  432 + context.canvas.restoreContext();
  433 + }
  434 +}
@@ -32,6 +32,7 @@ import 'widget_table_test.dart' as widget_table; @@ -32,6 +32,7 @@ import 'widget_table_test.dart' as widget_table;
32 import 'widget_test.dart' as widget; 32 import 'widget_test.dart' as widget;
33 import 'widget_text_test.dart' as widget_text; 33 import 'widget_text_test.dart' as widget_text;
34 import 'widget_theme_test.dart' as widget_theme; 34 import 'widget_theme_test.dart' as widget_theme;
  35 +import 'widget_wrap_test.dart' as widget_wrap;
35 36
36 void main() { 37 void main() {
37 annotations.main(); 38 annotations.main();
@@ -49,5 +50,6 @@ void main() { @@ -49,5 +50,6 @@ void main() {
49 widget_table.main(); 50 widget_table.main();
50 widget_text.main(); 51 widget_text.main();
51 widget_theme.main(); 52 widget_theme.main();
  53 + widget_wrap.main();
52 widget.main(); 54 widget.main();
53 } 55 }
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +import 'dart:io';
  18 +import 'dart:math' as math;
  19 +
  20 +import 'package:test/test.dart';
  21 +import 'package:pdf/pdf.dart';
  22 +import 'package:pdf/widgets.dart';
  23 +
  24 +Document pdf;
  25 +
  26 +void main() {
  27 + setUpAll(() {
  28 + Document.debug = true;
  29 + pdf = Document();
  30 + });
  31 +
  32 + test('Wrap Widget Horizontal 1', () {
  33 + final List<Widget> wraps = <Widget>[];
  34 + for (VerticalDirection direction in VerticalDirection.values) {
  35 + wraps.add(Text('$direction'));
  36 + for (WrapAlignment alignment in WrapAlignment.values) {
  37 + wraps.add(Text('$alignment'));
  38 + wraps.add(
  39 + Wrap(
  40 + direction: Axis.horizontal,
  41 + verticalDirection: direction,
  42 + alignment: alignment,
  43 + children: List<Widget>.generate(
  44 + 40,
  45 + (int n) => Text('${n + 1}'),
  46 + ),
  47 + ),
  48 + );
  49 + }
  50 + }
  51 +
  52 + pdf.addPage(
  53 + Page(
  54 + pageFormat: const PdfPageFormat(400, 800),
  55 + margin: const EdgeInsets.all(10),
  56 + build: (Context context) => Column(
  57 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  58 + children: wraps,
  59 + ),
  60 + ),
  61 + );
  62 + });
  63 +
  64 + test('Wrap Widget Vertical 1', () {
  65 + final List<Widget> wraps = <Widget>[];
  66 + for (VerticalDirection direction in VerticalDirection.values) {
  67 + wraps.add(Transform.rotateBox(child: Text('$direction'), angle: 1.57));
  68 + for (WrapAlignment alignment in WrapAlignment.values) {
  69 + wraps.add(Transform.rotateBox(child: Text('$alignment'), angle: 1.57));
  70 + wraps.add(
  71 + Wrap(
  72 + direction: Axis.vertical,
  73 + verticalDirection: direction,
  74 + alignment: alignment,
  75 + children: List<Widget>.generate(
  76 + 40,
  77 + (int n) => Text('${n + 1}'),
  78 + ),
  79 + ),
  80 + );
  81 + }
  82 + }
  83 +
  84 + pdf.addPage(
  85 + Page(
  86 + pageFormat: const PdfPageFormat(800, 400),
  87 + margin: const EdgeInsets.all(10),
  88 + build: (Context context) => Row(
  89 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  90 + children: wraps,
  91 + ),
  92 + ),
  93 + );
  94 + });
  95 +
  96 + test('Wrap Widget Horizontal 2', () {
  97 + final List<Widget> wraps = <Widget>[];
  98 + for (WrapCrossAlignment alignment in WrapCrossAlignment.values) {
  99 + final math.Random rnd = math.Random(42);
  100 + wraps.add(Text('$alignment'));
  101 + wraps.add(
  102 + Wrap(
  103 + direction: Axis.horizontal,
  104 + crossAxisAlignment: alignment,
  105 + runSpacing: 20,
  106 + spacing: 20,
  107 + children: List<Widget>.generate(
  108 + 20,
  109 + (int n) => SizedBox(
  110 + width: rnd.nextDouble() * 100,
  111 + height: rnd.nextDouble() * 50,
  112 + child: Placeholder(),
  113 + )),
  114 + ),
  115 + );
  116 + }
  117 +
  118 + pdf.addPage(
  119 + Page(
  120 + pageFormat: const PdfPageFormat(400, 800),
  121 + margin: const EdgeInsets.all(10),
  122 + build: (Context context) => Column(
  123 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  124 + children: wraps,
  125 + ),
  126 + ),
  127 + );
  128 + });
  129 +
  130 + test('Wrap Widget Vertical 2', () {
  131 + final List<Widget> wraps = <Widget>[];
  132 + for (WrapCrossAlignment alignment in WrapCrossAlignment.values) {
  133 + final math.Random rnd = math.Random(42);
  134 + wraps.add(Transform.rotateBox(child: Text('$alignment'), angle: 1.57));
  135 + wraps.add(
  136 + Wrap(
  137 + direction: Axis.vertical,
  138 + crossAxisAlignment: alignment,
  139 + runSpacing: 20,
  140 + spacing: 20,
  141 + children: List<Widget>.generate(
  142 + 20,
  143 + (int n) => SizedBox(
  144 + width: rnd.nextDouble() * 50,
  145 + height: rnd.nextDouble() * 100,
  146 + child: Placeholder(),
  147 + )),
  148 + ),
  149 + );
  150 + }
  151 +
  152 + pdf.addPage(
  153 + Page(
  154 + pageFormat: const PdfPageFormat(800, 400),
  155 + margin: const EdgeInsets.all(10),
  156 + build: (Context context) => Row(
  157 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  158 + children: wraps,
  159 + ),
  160 + ),
  161 + );
  162 + });
  163 +
  164 + test('Wrap Widget Horizontal 3', () {
  165 + final List<Widget> wraps = <Widget>[];
  166 + for (WrapAlignment alignment in WrapAlignment.values) {
  167 + final math.Random rnd = math.Random(42);
  168 + wraps.add(Text('$alignment'));
  169 + wraps.add(
  170 + SizedBox(
  171 + height: 110,
  172 + child: Wrap(
  173 + direction: Axis.horizontal,
  174 + runAlignment: alignment,
  175 + spacing: 20,
  176 + children: List<Widget>.generate(
  177 + 15,
  178 + (int n) => SizedBox(
  179 + width: rnd.nextDouble() * 100,
  180 + height: 20,
  181 + child: Placeholder(),
  182 + )),
  183 + ),
  184 + ),
  185 + );
  186 + }
  187 +
  188 + pdf.addPage(
  189 + Page(
  190 + pageFormat: const PdfPageFormat(400, 800),
  191 + margin: const EdgeInsets.all(10),
  192 + build: (Context context) => Column(
  193 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  194 + children: wraps,
  195 + ),
  196 + ),
  197 + );
  198 + });
  199 +
  200 + test('Wrap Widget Vertical 3', () {
  201 + final List<Widget> wraps = <Widget>[];
  202 + for (WrapAlignment alignment in WrapAlignment.values) {
  203 + final math.Random rnd = math.Random(42);
  204 + wraps.add(Transform.rotateBox(child: Text('$alignment'), angle: 1.57));
  205 + wraps.add(
  206 + SizedBox(
  207 + width: 110,
  208 + child: Wrap(
  209 + direction: Axis.vertical,
  210 + runAlignment: alignment,
  211 + spacing: 20,
  212 + children: List<Widget>.generate(
  213 + 15,
  214 + (int n) => SizedBox(
  215 + width: 20,
  216 + height: rnd.nextDouble() * 100,
  217 + child: Placeholder(),
  218 + )),
  219 + ),
  220 + ),
  221 + );
  222 + }
  223 +
  224 + pdf.addPage(
  225 + Page(
  226 + pageFormat: const PdfPageFormat(800, 400),
  227 + margin: const EdgeInsets.all(10),
  228 + build: (Context context) => Row(
  229 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  230 + children: wraps,
  231 + ),
  232 + ),
  233 + );
  234 + });
  235 +
  236 + test('Wrap Widget Overlay', () {
  237 + final math.Random rnd = math.Random(42);
  238 + pdf.addPage(
  239 + Page(
  240 + pageFormat: const PdfPageFormat(200, 200),
  241 + margin: const EdgeInsets.all(10),
  242 + build: (Context context) => Wrap(
  243 + spacing: 10,
  244 + runSpacing: 10,
  245 + children: List<Widget>.generate(
  246 + 15,
  247 + (int n) => SizedBox(
  248 + width: rnd.nextDouble() * 100,
  249 + height: rnd.nextDouble() * 100,
  250 + child: Placeholder(),
  251 + )),
  252 + ),
  253 + ),
  254 + );
  255 + });
  256 +
  257 + test('Wrap Widget Empty', () {
  258 + pdf.addPage(Page(build: (Context context) => Wrap()));
  259 + });
  260 +
  261 + tearDownAll(() {
  262 + final File file = File('widgets-wrap.pdf');
  263 + file.writeAsBytesSync(pdf.save());
  264 + });
  265 +}