dart_pdf icon indicating copy to clipboard operation
dart_pdf copied to clipboard

An inner widget tree cannot be bigger than a page: A Widget cannot be drawn partially on one page and the remaining on another page: It's insecable.

Open jazzbpn opened this issue 4 years ago • 31 comments

After visiting this official docs via https://pub.dev/documentation/pdf/latest/widgets/MultiPage-class.html. I found The Wrap Widget here is able to rearrange its children to span them across multiple pages. But a child of Wrap must fit in a page, or an error will raise.

Use case: I am trying to create the cv pdf where user input their data via form and the cv will be generated according to user input. In some cases like in experience section of Curriculum Vitae out of many sections, the input content exceeds the single page like in this image.

Screenshot_20210510_091958

Note: Till today if Wrap or Column do not fit in a page it will give error like Exception: This widget created more than 20 pages. This may be an issue in the widget or the document. OR
Exception: Widget won't fit into the page as its height (889.6392) exceed a page height (841.8897637795275). You probably need a SpanningWidget or use a single page layout This overflow feature will be used in lot's of dynamic pdf generation and it is one of the most import feature in my project.

So, Now I want to know:

  1. Feasibility of this feature and
  2. If this feature is feasible, is there any plans to implement in near future?

jazzbpn avatar May 10 '21 10:05 jazzbpn

@DavBfr I want to know the feasibility of this feature because this feature is the most important thing and highly dependent task in my project.

So, If this feature is feasible, is there any plan to implement in near future?

jazzbpn avatar May 13 '21 03:05 jazzbpn

@jazzbpn can you write me a sample code that does not work?

DavBfr avatar May 13 '21 10:05 DavBfr

Please look at this _ExpWidget(..) which will contains dynamic content ,according to the work experience of the user. This _ExpWidget(..) might be bigger than one A4 page. So, when the content is bigger than page we will get this common issue :

Exception: This widget created more than 20 pages. This may be an issue in the widget or the document. See https://pub.dev/documentation/pdf/latest/widgets/MultiPage-class.html

After visiting this official docs it is clearly mention An inner widget tree cannot be bigger than a page: A Widget cannot be drawn partially on one page and the remaining on another page: It's insecable.

So, How to resolve this kind of dynamic content issues which might be greater than the page? This overflow feature will be used in lot's of dynamic content generation and it is one of the most import feature in my project.

This is the sample code from the example:

  import 'dart:async';
  import 'dart:math';
  import 'dart:typed_data';

  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:pdf/pdf.dart';
  import 'package:pdf/widgets.dart' as pw;
  import 'package:printing_demo/data.dart';

  const PdfColor green = PdfColor.fromInt(0xff9ce5d0);
  const PdfColor lightGreen = PdfColor.fromInt(0xffcdf1e7);
  const sep = 120.0;

  Future<Uint8List> generateResume(PdfPageFormat format, CustomData data) async {
    final doc = pw.Document(title: 'My Résumé', author: 'David PHAM-VAN');

    final profileImage = pw.MemoryImage(
      (await rootBundle.load('assets/profile.png')).buffer.asUint8List(),
    );

    final pageTheme = await _myPageTheme(format);
    final font =
        pw.Font.ttf(await rootBundle.load('assets/MaterialIcons-Regular.ttf'));
    // pw.Font.ttf(
    // await rootBundle.load('assets/MaterialIconsTwoTone-Regular.otf'));

    doc.addPage(
      pw.MultiPage(
        pageTheme: pageTheme,
        build: (pw.Context context) => [
          pw.Partitions(
            children: [
              pw.Partition(
                child: pw.Column(
                  crossAxisAlignment: pw.CrossAxisAlignment.start,
                  children: <pw.Widget>[
                    pw.Container(
                      padding: const pw.EdgeInsets.only(left: 30, bottom: 20),
                      child: pw.Column(
                        crossAxisAlignment: pw.CrossAxisAlignment.start,
                        children: <pw.Widget>[
                          pw.Text('Parnella Charlesbois',
                              textScaleFactor: 2,
                              style: pw.Theme.of(context)
                                  .defaultTextStyle
                                  .copyWith(fontWeight: pw.FontWeight.bold)),
                          pw.Padding(padding: const pw.EdgeInsets.only(top: 10)),
                          pw.Text('Electrotyper',
                              textScaleFactor: 1.2,
                              style: pw.Theme.of(context)
                                  .defaultTextStyle
                                  .copyWith(
                                      fontWeight: pw.FontWeight.bold,
                                      color: green)),
                          pw.Padding(padding: const pw.EdgeInsets.only(top: 20)),
                          pw.Row(
                            crossAxisAlignment: pw.CrossAxisAlignment.start,
                            mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                            children: <pw.Widget>[
                              pw.Column(
                                crossAxisAlignment: pw.CrossAxisAlignment.start,
                                children: <pw.Widget>[
                                  pw.Text('568 Port Washington Road'),
                                  pw.Text('Nordegg, AB T0M 2H0'),
                                  pw.Text('Canada, ON'),
                                ],
                              ),
                              pw.Column(
                                crossAxisAlignment: pw.CrossAxisAlignment.start,
                                children: <pw.Widget>[
                                  pw.Text('+1 403-721-6898'),
                                  _UrlText('[email protected]',
                                      'mailto:[email protected]'),
                                  _UrlText(
                                      'wholeprices.ca', 'https://wholeprices.ca'),
                                ],
                              ),
                              pw.Padding(padding: pw.EdgeInsets.zero)
                            ],
                          ),
                        ],
                      ),
                    ),
                    _ExpWidget(font),
                    pw.SizedBox(height: 20),
                    _Category(title: 'Education'),
                    _Block(title: 'Bachelor Of Commerce'),
                    _Block(title: 'Bachelor Interior Design'),
                  ],
                ),
              ),
              pw.Partition(
                width: sep,
                child: pw.Column(
                  children: [
                    pw.Container(
                      height: pageTheme.pageFormat.availableHeight,
                      child: pw.Column(
                        crossAxisAlignment: pw.CrossAxisAlignment.center,
                        mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                        children: <pw.Widget>[
                          pw.ClipOval(
                            child: pw.Container(
                              width: 100,
                              height: 100,
                              color: lightGreen,
                              child: pw.Image(profileImage),
                            ),
                          ),
                          pw.Column(children: <pw.Widget>[
                            _Percent(size: 60, value: .7, title: pw.Text('Word')),
                            _Percent(
                                size: 60, value: .4, title: pw.Text('Excel')),
                          ]),
                          pw.BarcodeWidget(
                            data: 'Parnella Charlesbois',
                            width: 60,
                            height: 60,
                            barcode: pw.Barcode.qrCode(),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ],
      ),
    );
    return doc.save();
  }

  Future<pw.PageTheme> _myPageTheme(PdfPageFormat format) async {
    // final bgShape = await rootBundle.loadString('assets/resume.svg');
    final bgShape = await rootBundle.loadString('assets/dancing.svg');

    format = format.applyMargin(
        left: 2.0 * PdfPageFormat.cm,
        top: 4.0 * PdfPageFormat.cm,
        right: 2.0 * PdfPageFormat.cm,
        bottom: 2.0 * PdfPageFormat.cm);
    return pw.PageTheme(
      pageFormat: format,
      theme: pw.ThemeData.withFont(
        base: pw.Font.ttf(await rootBundle.load('assets/open-sans.ttf')),
        bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')),
        icons: pw.Font.ttf(await rootBundle.load('assets/material.ttf')),
      ),
      buildBackground: (pw.Context context) {
        return pw.FullPage(
          ignoreMargins: true,
          child: pw.Stack(
            children: [
              pw.Positioned(
                child: pw.SvgImage(
                    svg: bgShape,
                    colorFilter: PdfColor.fromHex("#ff0000"),
                    height: 30,
                    width: 30),
                left: 0,
                top: 0,
              ),
              pw.Positioned(
                child: pw.Transform.rotate(
                    angle: pi,
                    child: pw.SvgImage(svg: bgShape, height: 30, width: 30)),
                right: 0,
                bottom: 0,
              ),
            ],
          ),
        );
      },
    );
  }

  class _Block extends pw.StatelessWidget {
    _Block({required this.title, this.icon, this.font});

    final String title;
    final pw.Font? font;
    final pw.IconData? icon;

    @override
    pw.Widget build(pw.Context context) {
      return pw.Column(
          crossAxisAlignment: pw.CrossAxisAlignment.start,
          children: <pw.Widget>[
            pw.Row(
                crossAxisAlignment: pw.CrossAxisAlignment.start,
                children: <pw.Widget>[
                  pw.Container(
                    width: 6,
                    height: 6,
                    margin: const pw.EdgeInsets.only(top: 2.5, left: 2, right: 5),
                    decoration: const pw.BoxDecoration(
                        color: green, shape: pw.BoxShape.circle),
                  ),
                  pw.Text(title,
                      style: pw.Theme.of(context)
                          .defaultTextStyle
                          .copyWith(fontWeight: pw.FontWeight.bold)),
                  pw.Spacer(),
                  if (icon != null)
                    pw.Icon(icon!,
                        color: PdfColor.fromHex("#ff0000"), size: 18, font: font),
                ]),
            pw.Container(
              decoration: const pw.BoxDecoration(
                  border: pw.Border(left: pw.BorderSide(color: green, width: 2))),
              padding: const pw.EdgeInsets.only(left: 10, top: 5, bottom: 5),
              margin: const pw.EdgeInsets.only(left: 5),
              child: pw.Column(
                  crossAxisAlignment: pw.CrossAxisAlignment.start,
                  children: <pw.Widget>[
                    pw.Lorem(length: 20),
                  ]),
            ),
          ]);
    }
  }

  class _Category extends pw.StatelessWidget {
    _Category({required this.title});

    final String title;

    @override
    pw.Widget build(pw.Context context) {
      return pw.Container(
          decoration: const pw.BoxDecoration(
            color: lightGreen,
            borderRadius: pw.BorderRadius.all(pw.Radius.circular(6)),
          ),
          margin: const pw.EdgeInsets.only(bottom: 10, top: 20),
          padding: const pw.EdgeInsets.fromLTRB(10, 7, 10, 4),
          child: pw.Text(title, textScaleFactor: 1.5));
    }
  }

  class _Percent extends pw.StatelessWidget {
    _Percent({
      required this.size,
      required this.value,
      required this.title,
      this.fontSize = 1.2,
      this.color = green,
      this.backgroundColor = PdfColors.grey300,
      this.strokeWidth = 5,
    });

    final double size;

    final double value;

    final pw.Widget title;

    final double fontSize;

    final PdfColor color;

    final PdfColor backgroundColor;

    final double strokeWidth;

    @override
    pw.Widget build(pw.Context context) {
      final widgets = <pw.Widget>[
        pw.Container(
          width: size,
          height: size,
          child: pw.Stack(
            alignment: pw.Alignment.center,
            fit: pw.StackFit.expand,
            children: <pw.Widget>[
              pw.Center(
                child: pw.Text(
                  '${(value * 100).round().toInt()}%',
                  textScaleFactor: fontSize,
                ),
              ),
              pw.CircularProgressIndicator(
                value: value,
                backgroundColor: backgroundColor,
                color: color,
                strokeWidth: strokeWidth,
              ),
            ],
          ),
        )
      ];

      widgets.add(title);

      return pw.Column(children: widgets);
    }
  }

  class _UrlText extends pw.StatelessWidget {
    _UrlText(this.text, this.url);

    final String text;
    final String url;

    @override
    pw.Widget build(pw.Context context) {
      return pw.UrlLink(
        destination: url,
        child: pw.Text(text,
            style: const pw.TextStyle(
              decoration: pw.TextDecoration.underline,
              color: PdfColors.blue,
            )),
      );
    }
  }

  class _ExpWidget extends pw.StatelessWidget {
    pw.Font font;
    _ExpWidget(this.font);
    @override
    pw.Widget build(pw.Context context) {
      return pw.Column(children: [
        _Category(title: 'Work Experience'),
        _Block(
            title: 'Tour bus driver',
            icon: const pw.IconData(0xe530),
            font: font),
        _Block(
            title: 'Logging equipment operator', icon: const pw.IconData(0xe30d)),
        _Block(title: 'Foot doctor', icon: const pw.IconData(0xe3f3), font: font),
        _Block(
            title: 'Unicorn trainer',
            icon: const pw.IconData(0xe7e9),
            font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
        _Block(
            title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      ]);
    }
  }

jazzbpn avatar May 14 '21 02:05 jazzbpn

@DavBfr Will you please help me with this issue? I am waiting for your reply.

jazzbpn avatar May 17 '21 03:05 jazzbpn

@jazzbpn I tested that, and yes it's not working like this. You can find another way to layout your document: do use the top-level MultiPage's children list to draw your text elements, use overflow: TextOverflow.span to allow the large texts to use multiple pages. Or you can sponsor me to make it work with this layout.

DavBfr avatar May 17 '21 10:05 DavBfr

On Mon, May 17, 2021 at 4:03 PM David PHAM-VAN @.***> wrote:

@jazzbpn https://github.com/jazzbpn I tested that, and yes it's not working like this. You can find another way to layout your document: do use the top-level MultiPage's children list to draw your text elements, use overflow: TextOverflow.span to allow the large texts to use multiple pages. Or you can sponsor me to make it work with this layout.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DavBfr/dart_pdf/issues/684#issuecomment-842204295, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWVWC3N5GURMDDEK6URONDTODUPFANCNFSM44Q4P3HQ .

How much will I have to sponser to make it work?

jazzbpn avatar May 17 '21 10:05 jazzbpn

@DavBfr How much will I have to sponser to make it work?

jazzbpn avatar May 17 '21 10:05 jazzbpn

@DavBfr Any updates for this issue.

jazzbpn avatar Jun 20 '21 05:06 jazzbpn

@jazzbpn sorry, no progress yet.

DavBfr avatar Jun 21 '21 11:06 DavBfr

On Mon, Jun 21, 2021 at 5:32 PM David PHAM-VAN @.***> wrote:

@jazzbpn https://github.com/jazzbpn sorry, no progress yet.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DavBfr/dart_pdf/issues/684#issuecomment-864968632, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWVWC6GISX3OB7K7RMOJ5LTT4RDRANCNFSM44Q4P3HQ .

How much time will I need to wait for this feature? My project is completely depend upon this feature. Do you want me to sponsor more? If yes how much will It cost to make this work?

jazzbpn avatar Jun 22 '21 02:06 jazzbpn

@jazzbpn If you want it to go faster, I can help you to make it work without this feature, usually there is another way around.

DavBfr avatar Jun 22 '21 10:06 DavBfr

That sounds good. I want to know how?

On Tue, Jun 22, 2021 at 4:33 PM David PHAM-VAN @.***> wrote:

@jazzbpn https://github.com/jazzbpn If you want it to go faster, I can help you to make it work without this feature, usually there is another way around.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DavBfr/dart_pdf/issues/684#issuecomment-865877475, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWVWC6OJOUVVFMH6PDLJLTTUBTBLANCNFSM44Q4P3HQ .

jazzbpn avatar Jun 22 '21 10:06 jazzbpn

Great! What is the other way?

jazzbpn avatar Jun 22 '21 10:06 jazzbpn

Send me your code, I'll take a look

DavBfr avatar Jun 22 '21 11:06 DavBfr

/*
      * Copyright (C) 2017, David PHAM-VAN <[email protected]>
      *
      * 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.
      */

import 'dart:async';
import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing_demo/data.dart';

const PdfColor green = PdfColor.fromInt(0xff9ce5d0);
const PdfColor lightGreen = PdfColor.fromInt(0xffcdf1e7);
const sep = 120.0;

Future<Uint8List> generateResume(PdfPageFormat format, CustomData data) async {
  final doc = pw.Document(title: 'My Résumé', author: 'David PHAM-VAN');

  final profileImage = pw.MemoryImage(
    (await rootBundle.load('assets/profile.png')).buffer.asUint8List(),
  );

  final pageTheme = await _myPageTheme(format);
  final font =
      pw.Font.ttf(await rootBundle.load('assets/MaterialIcons-Regular.ttf'));
  // pw.Font.ttf(
  // await rootBundle.load('assets/MaterialIconsTwoTone-Regular.otf'));

  doc.addPage(
    pw.MultiPage(
      pageTheme: pageTheme,
      build: (pw.Context context) => [
        pw.Partitions(
          children: [
            pw.Partition(
              child: pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.start,
                children: <pw.Widget>[
                  pw.Container(
                    padding: const pw.EdgeInsets.only(left: 30, bottom: 20),
                    child: pw.Column(
                      crossAxisAlignment: pw.CrossAxisAlignment.start,
                      children: <pw.Widget>[
                        pw.Text('Parnella Charlesbois',
                            textScaleFactor: 2,
                            style: pw.Theme.of(context)
                                .defaultTextStyle
                                .copyWith(fontWeight: pw.FontWeight.bold)),
                        pw.Padding(padding: const pw.EdgeInsets.only(top: 10)),
                        pw.Text('Electrotyper',
                            textScaleFactor: 1.2,
                            style: pw.Theme.of(context)
                                .defaultTextStyle
                                .copyWith(
                                    fontWeight: pw.FontWeight.bold,
                                    color: green)),
                        pw.Padding(padding: const pw.EdgeInsets.only(top: 20)),
                        pw.Row(
                          crossAxisAlignment: pw.CrossAxisAlignment.start,
                          mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                          children: <pw.Widget>[
                            pw.Column(
                              crossAxisAlignment: pw.CrossAxisAlignment.start,
                              children: <pw.Widget>[
                                pw.Text('568 Port Washington Road'),
                                pw.Text('Nordegg, AB T0M 2H0'),
                                pw.Text('Canada, ON'),
                              ],
                            ),
                            pw.Column(
                              crossAxisAlignment: pw.CrossAxisAlignment.start,
                              children: <pw.Widget>[
                                pw.Text('+1 403-721-6898'),
                                _UrlText('[email protected]',
                                    'mailto:[email protected]'),
                                _UrlText(
                                    'wholeprices.ca', 'https://wholeprices.ca'),
                              ],
                            ),
                            pw.Padding(padding: pw.EdgeInsets.zero)
                          ],
                        ),
                      ],
                    ),
                  ),
                  _ExpWidget(font),
                  pw.SizedBox(height: 20),
                  _Category(title: 'Education'),
                  _Block(title: 'Bachelor Of Commerce'),
                  _Block(title: 'Bachelor Interior Design'),
                ],
              ),
            ),
            pw.Partition(
              width: sep,
              child: pw.Column(
                children: [
                  pw.Container(
                    height: pageTheme.pageFormat.availableHeight,
                    child: pw.Column(
                      crossAxisAlignment: pw.CrossAxisAlignment.center,
                      mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                      children: <pw.Widget>[
                        pw.ClipOval(
                          child: pw.Container(
                            width: 100,
                            height: 100,
                            color: lightGreen,
                            child: pw.Image(profileImage),
                          ),
                        ),
                        pw.Column(children: <pw.Widget>[
                          _Percent(size: 60, value: .7, title: pw.Text('Word')),
                          _Percent(
                              size: 60, value: .4, title: pw.Text('Excel')),
                        ]),
                        pw.BarcodeWidget(
                          data: 'Parnella Charlesbois',
                          width: 60,
                          height: 60,
                          barcode: pw.Barcode.qrCode(),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
      ],
    ),
  );
  return doc.save();
}

Future<pw.PageTheme> _myPageTheme(PdfPageFormat format) async {
  // final bgShape = await rootBundle.loadString('assets/resume.svg');
  final bgShape = await rootBundle.loadString('assets/dancing.svg');

  format = format.applyMargin(
      left: 2.0 * PdfPageFormat.cm,
      top: 4.0 * PdfPageFormat.cm,
      right: 2.0 * PdfPageFormat.cm,
      bottom: 2.0 * PdfPageFormat.cm);
  return pw.PageTheme(
    pageFormat: format,
    theme: pw.ThemeData.withFont(
      base: pw.Font.ttf(await rootBundle.load('assets/open-sans.ttf')),
      bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')),
      icons: pw.Font.ttf(await rootBundle.load('assets/material.ttf')),
    ),
    buildBackground: (pw.Context context) {
      return pw.FullPage(
        ignoreMargins: true,
        child: pw.Stack(
          children: [
            pw.Positioned(
              child: pw.SvgImage(
                  svg: bgShape,
                  colorFilter: PdfColor.fromHex("#ff0000"),
                  height: 30,
                  width: 30),
              left: 0,
              top: 0,
            ),
            pw.Positioned(
              child: pw.Transform.rotate(
                  angle: pi,
                  child: pw.SvgImage(svg: bgShape, height: 30, width: 30)),
              right: 0,
              bottom: 0,
            ),
          ],
        ),
      );
    },
  );
}

class _Block extends pw.StatelessWidget {
  _Block({required this.title, this.icon, this.font});

  final String title;
  final pw.Font? font;
  final pw.IconData? icon;

  @override
  pw.Widget build(pw.Context context) {
    return pw.Column(
        crossAxisAlignment: pw.CrossAxisAlignment.start,
        children: <pw.Widget>[
          pw.Row(
              crossAxisAlignment: pw.CrossAxisAlignment.start,
              children: <pw.Widget>[
                pw.Container(
                  width: 6,
                  height: 6,
                  margin: const pw.EdgeInsets.only(top: 2.5, left: 2, right: 5),
                  decoration: const pw.BoxDecoration(
                      color: green, shape: pw.BoxShape.circle),
                ),
                pw.Text(title,
                    style: pw.Theme.of(context)
                        .defaultTextStyle
                        .copyWith(fontWeight: pw.FontWeight.bold)),
                pw.Spacer(),
                if (icon != null)
                  pw.Icon(icon!,
                      color: PdfColor.fromHex("#ff0000"), size: 18, font: font),
              ]),
          pw.Container(
            decoration: const pw.BoxDecoration(
                border: pw.Border(left: pw.BorderSide(color: green, width: 2))),
            padding: const pw.EdgeInsets.only(left: 10, top: 5, bottom: 5),
            margin: const pw.EdgeInsets.only(left: 5),
            child: pw.Column(
                crossAxisAlignment: pw.CrossAxisAlignment.start,
                children: <pw.Widget>[
                  pw.Lorem(length: 20),
                ]),
          ),
        ]);
  }
}

class _Category extends pw.StatelessWidget {
  _Category({required this.title});

  final String title;

  @override
  pw.Widget build(pw.Context context) {
    return pw.Container(
        decoration: const pw.BoxDecoration(
          color: lightGreen,
          borderRadius: pw.BorderRadius.all(pw.Radius.circular(6)),
        ),
        margin: const pw.EdgeInsets.only(bottom: 10, top: 20),
        padding: const pw.EdgeInsets.fromLTRB(10, 7, 10, 4),
        child: pw.Text(title, textScaleFactor: 1.5));
  }
}

class _Percent extends pw.StatelessWidget {
  _Percent({
    required this.size,
    required this.value,
    required this.title,
    this.fontSize = 1.2,
    this.color = green,
    this.backgroundColor = PdfColors.grey300,
    this.strokeWidth = 5,
  });

  final double size;

  final double value;

  final pw.Widget title;

  final double fontSize;

  final PdfColor color;

  final PdfColor backgroundColor;

  final double strokeWidth;

  @override
  pw.Widget build(pw.Context context) {
    final widgets = <pw.Widget>[
      pw.Container(
        width: size,
        height: size,
        child: pw.Stack(
          alignment: pw.Alignment.center,
          fit: pw.StackFit.expand,
          children: <pw.Widget>[
            pw.Center(
              child: pw.Text(
                '${(value * 100).round().toInt()}%',
                textScaleFactor: fontSize,
              ),
            ),
            pw.CircularProgressIndicator(
              value: value,
              backgroundColor: backgroundColor,
              color: color,
              strokeWidth: strokeWidth,
            ),
          ],
        ),
      )
    ];

    widgets.add(title);

    return pw.Column(children: widgets);
  }
}

class _UrlText extends pw.StatelessWidget {
  _UrlText(this.text, this.url);

  final String text;
  final String url;

  @override
  pw.Widget build(pw.Context context) {
    return pw.UrlLink(
      destination: url,
      child: pw.Text(text,
          style: const pw.TextStyle(
            decoration: pw.TextDecoration.underline,
            color: PdfColors.blue,
          )),
    );
  }
}

class _ExpWidget extends pw.StatelessWidget {
  pw.Font font;
  _ExpWidget(this.font);
  @override
  pw.Widget build(pw.Context context) {
    return pw.Wrap(children: [
      _Category(title: 'Work Experience'),
      // @read This _Block will be created as per user entry which will be dynamic
      _Block(
          title: 'Tour bus driver',
          icon: const pw.IconData(0xe530),
          font: font),
      _Block(
          title: 'Logging equipment operator', icon: const pw.IconData(0xe30d)),
      _Block(title: 'Foot doctor', icon: const pw.IconData(0xe3f3), font: font),
      _Block(
          title: 'Unicorn trainer',
          icon: const pw.IconData(0xe7e9),
          font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
      _Block(
          title: 'Chief chatter', icon: const pw.IconData(0xe000), font: font),
    ]);
  }
}

_Note: ExpWidget class may exceed the single A4 size page.

jazzbpn avatar Jun 22 '21 11:06 jazzbpn

Hey @DavBfr, Once you mentioned you can help me to make it work without this feature with another way. Is there any update?

jazzbpn avatar Jul 08 '21 10:07 jazzbpn

I thought I did. Apparently, my comment did not pass.

import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
 
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
 
const PdfColor green = PdfColor.fromInt(0xff9ce5d0);
const PdfColor lightGreen = PdfColor.fromInt(0xffcdf1e7);
const sep = 120.0;
 
Future<Uint8List> generateResume(PdfPageFormat format) async {
  final doc = pw.Document(title: 'My Résumé', author: 'David PHAM-VAN');
 
  final pageTheme = await _myPageTheme(format);
  final font = await PdfGoogleFonts.materialIcons();
 
  // pw.Font.ttf(
  // await rootBundle.load('assets/MaterialIconsTwoTone-Regular.otf'));
 
  doc.addPage(
    pw.MultiPage(
      pageTheme: pageTheme,
      build: (pw.Context context) => [
        pw.Container(
          padding: const pw.EdgeInsets.only(left: 30, bottom: 20),
          child: pw.Column(
            crossAxisAlignment: pw.CrossAxisAlignment.start,
            children: <pw.Widget>[
              pw.Text('Parnella Charlesbois',
                  textScaleFactor: 2,
                  style: pw.Theme.of(context)
                      .defaultTextStyle
                      .copyWith(fontWeight: pw.FontWeight.bold)),
              pw.Padding(padding: const pw.EdgeInsets.only(top: 10)),
              pw.Text('Electrotyper',
                  textScaleFactor: 1.2,
                  style: pw.Theme.of(context)
                      .defaultTextStyle
                      .copyWith(fontWeight: pw.FontWeight.bold, color: green)),
              pw.Padding(padding: const pw.EdgeInsets.only(top: 20)),
              pw.Row(
                crossAxisAlignment: pw.CrossAxisAlignment.start,
                mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                children: <pw.Widget>[
                  pw.Column(
                    crossAxisAlignment: pw.CrossAxisAlignment.start,
                    children: <pw.Widget>[
                      pw.Text('568 Port Washington Road'),
                      pw.Text('Nordegg, AB T0M 2H0'),
                      pw.Text('Canada, ON'),
                    ],
                  ),
                  pw.Column(
                    crossAxisAlignment: pw.CrossAxisAlignment.start,
                    children: <pw.Widget>[
                      pw.Text('+1 403-721-6898'),
                      _UrlText('[email protected]',
                          'mailto:[email protected]'),
                      _UrlText('wholeprices.ca', 'https://wholeprices.ca'),
                    ],
                  ),
                  pw.Padding(padding: pw.EdgeInsets.zero)
                ],
              ),
            ],
          ),
        ),
        ..._ExpWidget(context, font),
        pw.SizedBox(height: 20),
        // _Category(title: 'Education'),
        // _Block(title: 'Bachelor Of Commerce'),
        // _Block(title: 'Bachelor Interior Design'),
      ],
 
      // pw.Partition(
      //   width: sep,
      //   child: pw.Column(
      //     children: [
      //       pw.Container(
      //         height: pageTheme.pageFormat.availableHeight,
      //         child: pw.Column(
      //           crossAxisAlignment: pw.CrossAxisAlignment.center,
      //           mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
      //           children: <pw.Widget>[
      //             pw.ClipOval(
      //               child: pw.Container(
      //                 width: 100,
      //                 height: 100,
      //                 color: lightGreen,
      //                 child: pw.Image(profileImage),
      //               ),
      //             ),
      //             pw.Column(children: <pw.Widget>[
      //               _Percent(size: 60, value: .7, title: pw.Text('Word')),
      //               _Percent(
      //                   size: 60, value: .4, title: pw.Text('Excel')),
      //             ]),
      //             pw.BarcodeWidget(
      //               data: 'Parnella Charlesbois',
      //               width: 60,
      //               height: 60,
      //               barcode: pw.Barcode.qrCode(),
      //             ),
      //           ],
      //         ),
      //       ),
      //     ],
      //   ),
      // )
    ),
  );
  return doc.save();
}
 
Future<pw.PageTheme> _myPageTheme(PdfPageFormat format) async {
  // final bgShape = await rootBundle.loadString('assets/resume.svg');
  final bgShape = '''<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 150 230" xmlns="http://www.w3.org/2000/svg">
  <polygon style="fill: rgb(205, 241, 231);" points="0 0 0 230 60 0 "/>
  <polygon style="fill: rgb(156, 229, 208);" points="0 0 0 100 100 0 "/>
  <polygon style="fill: rgb(205, 241, 231);" points="30 0 110 50 150 0 "/>
</svg>''';
 
  final profileImage = await networkImage(
      'https://www.fakepersongenerator.com/Face/female/female20151024334209870.jpg');
 
  format = format.applyMargin(
      left: 2.0 * PdfPageFormat.cm,
      top: 4.0 * PdfPageFormat.cm,
      right: 2.0 * PdfPageFormat.cm + sep,
      bottom: 2.0 * PdfPageFormat.cm);
  return pw.PageTheme(
    pageFormat: format,
    theme: pw.ThemeData.withFont(
      base: await PdfGoogleFonts.openSansRegular(),
      bold: await PdfGoogleFonts.openSansBold(),
      icons: await PdfGoogleFonts.materialIcons(),
    ),
    buildForeground: (context) {
      if (context.pageNumber > 1) return pw.SizedBox();
 
      return pw.FullPage(
        ignoreMargins: true,
        child: pw.Container(
          alignment: pw.Alignment.topRight,
          margin: pw.EdgeInsets.only(
            right: 2.0 * PdfPageFormat.cm,
            top: 4.0 * PdfPageFormat.cm,
            bottom: 2.0 * PdfPageFormat.cm,
          ),
          child: pw.Column(
            children: [
              pw.Container(
                height: format.availableHeight,
                child: pw.Column(
                  crossAxisAlignment: pw.CrossAxisAlignment.center,
                  mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
                  children: <pw.Widget>[
                    pw.ClipOval(
                      child: pw.Container(
                        width: 100,
                        height: 100,
                        color: lightGreen,
                        child: pw.Image(profileImage),
                      ),
                    ),
                    pw.Column(children: <pw.Widget>[
                      _Percent(size: 60, value: .7, title: pw.Text('Word')),
                      _Percent(size: 60, value: .4, title: pw.Text('Excel')),
                    ]),
                    pw.BarcodeWidget(
                      data: 'Parnella Charlesbois',
                      width: 60,
                      height: 60,
                      barcode: pw.Barcode.qrCode(),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );
    },
    buildBackground: (pw.Context context) {
      return pw.FullPage(
        ignoreMargins: true,
        child: pw.Stack(
          children: [
            pw.Positioned(
              child: pw.SvgImage(
                  svg: bgShape,
                  colorFilter: PdfColor.fromHex('#ff0000'),
                  height: 30,
                  width: 30),
              left: 0,
              top: 0,
            ),
            pw.Positioned(
              child: pw.Transform.rotate(
                  angle: pi,
                  child: pw.SvgImage(svg: bgShape, height: 30, width: 30)),
              right: 0,
              bottom: 0,
            ),
          ],
        ),
      );
    },
  );
}
 
Iterable<pw.Widget> _Block(
    {required pw.Context context,
    required String title,
    pw.IconData? icon,
    pw.Font? font}) {
  return [
    pw.Row(
        crossAxisAlignment: pw.CrossAxisAlignment.start,
        children: <pw.Widget>[
          pw.Container(
            width: 6,
            height: 6,
            margin: const pw.EdgeInsets.only(top: 2.5, left: 2, right: 5),
            decoration:
                const pw.BoxDecoration(color: green, shape: pw.BoxShape.circle),
          ),
          pw.Text(title,
              style: pw.Theme.of(context)
                  .defaultTextStyle
                  .copyWith(fontWeight: pw.FontWeight.bold)),
          pw.Spacer(),
          if (icon != null)
            pw.Icon(icon!,
                color: PdfColor.fromHex('#ff0000'), size: 18, font: font),
        ]),
    pw.Container(
      decoration: const pw.BoxDecoration(
          border: pw.Border(left: pw.BorderSide(color: green, width: 2))),
      padding: const pw.EdgeInsets.only(left: 10, top: 5, bottom: 5),
      margin: const pw.EdgeInsets.only(left: 5),
      child: pw.Text(pw.LoremText().paragraph(200),
          overflow: pw.TextOverflow.span),
    ),
  ];
}
 
class _Category extends pw.StatelessWidget {
  _Category({required this.title});
 
  final String title;
 
  @override
  pw.Widget build(pw.Context context) {
    return pw.Container(
        decoration: const pw.BoxDecoration(
          color: lightGreen,
          borderRadius: pw.BorderRadius.all(pw.Radius.circular(6)),
        ),
        margin: const pw.EdgeInsets.only(bottom: 10, top: 20),
        padding: const pw.EdgeInsets.fromLTRB(10, 7, 10, 4),
        child: pw.Text(title, textScaleFactor: 1.5));
  }
}
 
class _Percent extends pw.StatelessWidget {
  _Percent({
    required this.size,
    required this.value,
    required this.title,
    this.fontSize = 1.2,
    this.color = green,
    this.backgroundColor = PdfColors.grey300,
    this.strokeWidth = 5,
  });
 
  final double size;
 
  final double value;
 
  final pw.Widget title;
 
  final double fontSize;
 
  final PdfColor color;
 
  final PdfColor backgroundColor;
 
  final double strokeWidth;
 
  @override
  pw.Widget build(pw.Context context) {
    final widgets = <pw.Widget>[
      pw.Container(
        width: size,
        height: size,
        child: pw.Stack(
          alignment: pw.Alignment.center,
          fit: pw.StackFit.expand,
          children: <pw.Widget>[
            pw.Center(
              child: pw.Text(
                '${(value * 100).round().toInt()}%',
                textScaleFactor: fontSize,
              ),
            ),
            pw.CircularProgressIndicator(
              value: value,
              backgroundColor: backgroundColor,
              color: color,
              strokeWidth: strokeWidth,
            ),
          ],
        ),
      )
    ];
 
    widgets.add(title);
 
    return pw.Column(children: widgets);
  }
}
 
class _UrlText extends pw.StatelessWidget {
  _UrlText(this.text, this.url);
 
  final String text;
  final String url;
 
  @override
  pw.Widget build(pw.Context context) {
    return pw.UrlLink(
      destination: url,
      child: pw.Text(text,
          style: const pw.TextStyle(
            decoration: pw.TextDecoration.underline,
            color: PdfColors.blue,
          )),
    );
  }
}
 
Iterable<pw.Widget> _ExpWidget(pw.Context context, pw.Font font) {
  return [
    _Category(title: 'Work Experience'),
    // @read This _Block will be created as per user entry which will be dynamic
    ..._Block(
        context: context,
        title: 'Tour bus driver',
        icon: const pw.IconData(0xe530),
        font: font),
    ..._Block(
        context: context,
        title: 'Logging equipment operator',
        icon: const pw.IconData(0xe30d)),
    ..._Block(
        context: context,
        title: 'Foot doctor',
        icon: const pw.IconData(0xe3f3),
        font: font),
    ..._Block(
        context: context,
        title: 'Unicorn trainer',
        icon: const pw.IconData(0xe7e9),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
    ..._Block(
        context: context,
        title: 'Chief chatter',
        icon: const pw.IconData(0xe000),
        font: font),
  ];
}

DavBfr avatar Jul 08 '21 11:07 DavBfr

Working! But how to resolve such kinds of issues in future? How will this type of issue occurs and what are the ways to resolve it? Will you please elaborate? Is this issue occurs because of child column?

jazzbpn avatar Jul 08 '21 12:07 jazzbpn

Just found the new package https://pub.dev/packages/syncfusion_flutter_pdf But, here we need to build all widget on graphic level. This package will also not be helpful to create PDF where the content is dynamic.

@DavBfr Your library is much more easy to implement. So, Is there any way to add this feature or resolve this multipage issue soon?

jazzbpn avatar Jul 09 '21 04:07 jazzbpn

Thanks, I try my best to make it easy. I'm still working on that issue, but it requires deep changes to the layout engine, which takes time, especially to keep the API compatible.

DavBfr avatar Jul 09 '21 17:07 DavBfr

@DavBfr Any Updates on this feature?

jazzbpn avatar Sep 22 '21 12:09 jazzbpn

Sorry, no progress yet.

DavBfr avatar Sep 22 '21 12:09 DavBfr

Is there anything I can do to make this feature workable?

jazzbpn avatar Sep 22 '21 12:09 jazzbpn

Any update on this issue?

On Wed, Sep 22, 2021 at 6:11 PM David PHAM-VAN @.***> wrote:

Sorry, no progress yet.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DavBfr/dart_pdf/issues/684#issuecomment-924881514, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWVWC4GAJWNVDVSGIVSWEDUDHDPBANCNFSM44Q4P3HQ .

jazzbpn avatar Nov 09 '21 13:11 jazzbpn

no progress

DavBfr avatar Nov 14 '21 12:11 DavBfr

Hello Dav, This is one of the most important features which will be needed for most of dynamic content creators. This features is easily available in all other libraries except dart. So, why are you ignoring such user segments. How much will it take to implement this feature? Is there anything we nee need to do to make this happen? Let me know. I already waited more than 1 year for this features.

Thank you! Regards Bipin

On Sun, 14 Nov 2021 at 17:58 David PHAM-VAN @.***> wrote:

no progress

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DavBfr/dart_pdf/issues/684#issuecomment-968279265, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACWVWC47VPDDCNIJWNHNP4DUL6RYJANCNFSM44Q4P3HQ .

jazzbpn avatar Nov 18 '21 04:11 jazzbpn

@DavBfr Will you please see this? https://stackoverflow.com/questions/70434570/an-inner-widget-tree-cannot-be-bigger-than-a-page-a-widget-cannot-be-drawn-part

jazzbpn avatar Dec 21 '21 11:12 jazzbpn

Hello guys,

This issue is also affecting our project.

For now our solution is split the text in many pw.Text(...) widgets by each word, but we are concerned about memory consumption and performance for big PDFs.

Do you guys know any better way or hint about that topic?

Your library is really amazing and pretty easy to use.

Thank you

niltonvasques avatar May 24 '22 17:05 niltonvasques

@niltonvasques With your Text widget, use the attribute overflow: span. It will automatically do that for you.

DavBfr avatar May 25 '22 10:05 DavBfr

@DavBfr hey gorgeous developer once we worked on a project. I sponsored you to make me a couple of resume templates a few months ago in may. Now I am also facing this above mentioned issue. Please do some work to resolve this issue. We will be sponsoring you too as much as we can. I am ready to become your sponsor member on monthly basis but please do some work to resolve this issue forever. Also please provide me a work around to solve it. I also have emailed you about this. So need your serious attention on this issue.

The project that i am working on is very powerful it can generate high incomes as even i do not have to much pdf resume resources still it's getting ranked and now it is on 32 number in my country on playstore i will show you in email if you ask fo it. So please again i request you to provide a powerful solution to this annoying problem. Also after providing this your package will get top position in pdf libraries. Thanks in advance.

Zia-Ur-Rahman-Ch avatar Aug 24 '22 09:08 Zia-Ur-Rahman-Ch