You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter/Dart-PDF:多页表格长文本如何避免InstanceOfTooManyPages错误

解决PDF表格内超长文本跨多页显示的问题

问题描述

我有一段超长文本需要跨多页显示,该文本处于一个同样跨多页的表格中,因此抛出了InstanceOfTooManyPages错误。以下是使用Table和Paragraph实现的代码,短文本场景下代码可正常运行,但尝试过Paragraph、Text、Wrap及容器布局等方式后仍无法解决问题,请求帮助实现长文本跨多页显示。

// Erstelle eine Tabelle mit der hierarchischen Struktur
pdf.addPage(pw.MultiPage(
  maxPages: 999,
  pageFormat: PdfPageFormat.a4,
  build: (context) => [
    pw.Table(
      columnWidths: {
        0: pw.FlexColumnWidth(1),
        1: pw.FlexColumnWidth(4),
        2: pw.FlexColumnWidth(1),
        3: pw.FlexColumnWidth(1),
        4: pw.FlexColumnWidth(2),
        5: pw.FlexColumnWidth(2),
      },
      children: [
        pw.TableRow(
          //decoration: pw.BoxDecoration(color: PdfColors.grey300),
          children: [
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Pos.Nr.', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Leistungsbeschreibung', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Menge', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Einheit', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Einheitspreis', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Gesamtpreis', style: headerStyle)),
          ],
        ),
        ...categoriesAndItems.map((item) {
          endTime = DateTime.now();
          duration = endTime.difference(startTime);
          print("PDF item created. (after ${duration.inMilliseconds}ms)");
          if (item['isCategory']) {
            return pw.TableRow(
              children: [
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Text(item['posnr'],
                      style: textStyles[item['level'] - 1])),
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Column(
                      crossAxisAlignment: pw.CrossAxisAlignment.start,
                      children: [
                        pw.Text(item['position'],
                            style: textStyles[item['level'] - 1]),
//here the critical text block
                        pw.Padding(
                            padding: const pw.EdgeInsets.all(4),
                            child: pw.Paragraph(
                                text: item['description'],
                                style: textStyles[3])),
                      ]),
                ),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text('', style: textStyles[item['level'] - 1])),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text('', style: textStyles[item['level'] - 1])),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text('', style: textStyles[item['level'] - 1])),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text(item['totalPrice'],
                        style: textStyles[item['level'] - 1])),
              ],
            );
          } else {
            return pw.TableRow(
              children: [
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Text(item['posnr'], style: textStyles[2])),
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Column(
                      crossAxisAlignment: pw.CrossAxisAlignment.start,
                      children: [
                        pw.Text(item['position'], style: textStyles[2]),
                        pw.Padding(
                            padding: const pw.EdgeInsets.all(4),
//here another critical text block
                            child: pw.Paragraph(
                                text: item['description'],
                                style: textStyles[3])),
                      ]),
                ),
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Align(
                    alignment: pw
                        .Alignment.centerRight, // Setzt den Text rechtsbündig
                    child: pw.Text(item['quantity'], style: textStyles[2]),
                  ),
                ),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text(item['unit'], style: textStyles[2])),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text(item['unitPrice'], style: textStyles[2])),
                pw.Padding(
                    padding: const pw.EdgeInsets.all(4),
                    child: pw.Text(item['totalPrice'], style: textStyles[2])),
              ],
            );
          }
        }),
      ],
    ),
  ],
));

解决方案

核心思路

pdf库中的TableRow无法跨页拆分,因此需要将包含超长文本的条目拆分为多个可分页的表格行,配合MultiPage的自动分页能力实现跨页显示。具体通过拆分超长文本段落、逐行构建表格内容来实现。

修改后的代码

pdf.addPage(pw.MultiPage(
  maxPages: 999,
  pageFormat: PdfPageFormat.a4,
  build: (context) {
    final List<pw.Widget> content = [];

    // 添加固定表头
    content.add(pw.Table(
      columnWidths: {
        0: pw.FlexColumnWidth(1),
        1: pw.FlexColumnWidth(4),
        2: pw.FlexColumnWidth(1),
        3: pw.FlexColumnWidth(1),
        4: pw.FlexColumnWidth(2),
        5: pw.FlexColumnWidth(2),
      },
      children: [
        pw.TableRow(
          children: [
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Pos.Nr.', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Leistungsbeschreibung', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Menge', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Einheit', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Einheitspreis', style: headerStyle)),
            pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Text('Gesamtpreis', style: headerStyle)),
          ],
        ),
      ],
    ));

    // 逐个处理数据条目
    for (final item in categoriesAndItems) {
      endTime = DateTime.now();
      duration = endTime.difference(startTime);
      print("PDF item created. (after ${duration.inMilliseconds}ms)");

      // 构建各列基础内容
      final pw.Widget posCell = pw.Padding(
        padding: const pw.EdgeInsets.all(4),
        child: pw.Text(item['posnr'], style: item['isCategory'] ? textStyles[item['level'] - 1] : textStyles[2]),
      );

      final pw.Widget positionCell = pw.Padding(
        padding: const pw.EdgeInsets.all(4),
        child: pw.Text(item['position'], style: item['isCategory'] ? textStyles[item['level'] - 1] : textStyles[2]),
      );

      final pw.Widget quantityCell = item['isCategory']
          ? pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('', style: textStyles[item['level'] - 1]))
          : pw.Padding(
              padding: const pw.EdgeInsets.all(4),
              child: pw.Align(alignment: pw.Alignment.centerRight, child: pw.Text(item['quantity'], style: textStyles[2])));

      final pw.Widget unitCell = item['isCategory']
          ? pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('', style: textStyles[item['level'] - 1]))
          : pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text(item['unit'], style: textStyles[2]));

      final pw.Widget unitPriceCell = item['isCategory']
          ? pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('', style: textStyles[item['level'] - 1]))
          : pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text(item['unitPrice'], style: textStyles[2]));

      final pw.Widget totalPriceCell = pw.Padding(
        padding: const pw.EdgeInsets.all(4),
        child: pw.Text(item['totalPrice'], style: item['isCategory'] ? textStyles[item['level'] - 1] : textStyles[2]),
      );

      // 拆分超长描述文本为多个段落
      final descriptionParagraph = pw.Paragraph(text: item['description'], style: textStyles[3]);
      final remainingHeight = context.pageFormat.height - context.pageHeaderHeight - context.pageFooterHeight - context.y;
      final splitParagraphs = descriptionParagraph.split(remainingHeight);

      // 添加第一行(包含完整条目信息+第一段描述)
      content.add(pw.Table(
        columnWidths: {
          0: pw.FlexColumnWidth(1),
          1: pw.FlexColumnWidth(4),
          2: pw.FlexColumnWidth(1),
          3: pw.FlexColumnWidth(1),
          4: pw.FlexColumnWidth(2),
          5: pw.FlexColumnWidth(2),
        },
        children: [
          pw.TableRow(
            children: [
              posCell,
              pw.Padding(
                padding: const pw.EdgeInsets.all(4),
                child: pw.Column(
                  crossAxisAlignment: pw.CrossAxisAlignment.start,
                  children: [
                    positionCell,
                    pw.Padding(padding: const pw.EdgeInsets.all(4), child: splitParagraphs.first),
                  ],
                ),
              ),
              quantityCell,
              unitCell,
              unitPriceCell,
              totalPriceCell,
            ],
          ),
        ],
      ));

      // 添加剩余描述段落的续行(仅填充描述列,其他列留空)
      for (int i = 1; i < splitParagraphs.length; i++) {
        content.add(pw.Table(
          columnWidths: {
            0: pw.FlexColumnWidth(1),
            1: pw.FlexColumnWidth(4),
            2: pw.FlexColumnWidth(1),
            3: pw.FlexColumnWidth(1),
            4: pw.FlexColumnWidth(2),
            5: pw.FlexColumnWidth(2),
          },
          children: [
            pw.TableRow(
              children: [
                pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('')),
                pw.Padding(
                  padding: const pw.EdgeInsets.all(4),
                  child: pw.Padding(padding: const pw.EdgeInsets.all(4), child: splitParagraphs[i]),
                ),
                pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('')),
                pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('')),
                pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('')),
                pw.Padding(padding: const pw.EdgeInsets.all(4), child: pw.Text('')),
              ],
            ),
          ],
        ));
      }
    }

    return content;
  },
));

关键细节

  • 段落拆分:通过paragraph.split(remainingHeight)计算当前页面剩余空间,将超长文本拆分为多个适配页面的段落片段。
  • 逐行构建:不再生成单一的大表格,而是将每个条目拆分为主行+若干续行,让MultiPage自动处理分页。
  • 结构保持:续行中仅填充描述列,其他列留空,确保表格整体结构一致。

内容的提问来源于stack exchange,提问作者Moritz Mauruschat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:37:04