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
相关产品推荐
相关产品推荐

