renderPdf function
Renders template with data and returns a complete PDF byte buffer.
Implementation
Future<Uint8List> renderPdf(
ReportTemplate template,
Map<String, dynamic> data,
) async {
print('DEBUG: renderPdf started');
final document = pw.Document();
final embeddedFonts = <String, pw.Font>{};
for (final font in template.fonts) {
if (font.data.isEmpty || !font.isPdfCompatible) continue;
embeddedFonts[font.family] = pw.Font.ttf(
ByteData.sublistView(base64Decode(font.data)),
);
}
final pagination = _paginateReport(template, data);
final pageCount = pagination.pages.length;
final renderedAt = DateTime.now();
for (var i = 0; i < pageCount; i++) {
print('PAGE $i staticElements keys: ${pagination.pages[i].staticElements.keys.map((e) => e.id).toList()}');
print('PAGE $i flowedElements keys: ${pagination.pages[i].flowedElements.keys.map((e) => e.id).toList()}');
print('PAGE $i tables keys: ${pagination.pages[i].tables.keys.map((e) => e.id).toList()}');
}
for (var pageIndex = 0; pageIndex < pageCount; pageIndex++) {
final page = pagination.pages[pageIndex];
document.addPage(
pw.Page(
pageFormat: template.pdfPageFormat,
margin: pw.EdgeInsets.zero,
build: (context) {
final children = <pw.Widget>[];
// 1. Process header, footer, and standard body elements
for (final element in template.elements) {
final isVisible = isElementVisible(
element,
data,
pageNumber: pageIndex + 1,
pageCount: pageCount,
);
if (!isVisible) continue;
if (element.band == 'header' || element.band == 'footer') {
children.add(
pw.Positioned(
left: element.x,
top: element.y,
child: _pdfElement(
element,
data,
template: template,
embeddedFonts: embeddedFonts,
pageNumber: pageIndex + 1,
pageCount: pageCount,
now: renderedAt,
),
),
);
} else if (element.band == 'body') {
final sec = element.section.trim();
if (sec.isEmpty || sec == 'body') {
if (element.type == ElementType.table) {
final tableSlice = page.tables[element];
if (tableSlice != null) {
children.add(
pw.Positioned(
left: element.x,
top: tableSlice.tableY,
child: _pdfElement(
element,
data,
template: template,
embeddedFonts: embeddedFonts,
pageNumber: pageIndex + 1,
pageCount: pageCount,
now: renderedAt,
tableRows: tableSlice.rows,
tableRowOffset: tableSlice.rowOffset,
includeTableSummary: tableSlice.showSummary,
),
),
);
}
} else if (element.flow) {
final flowSlice = page.flowedElements[element];
if (flowSlice != null) {
children.add(
pw.Positioned(
left: element.x,
top: flowSlice.elementY,
child: _pdfElement(
element,
data,
template: template,
embeddedFonts: embeddedFonts,
pageNumber: pageIndex + 1,
pageCount: pageCount,
now: renderedAt,
flowLines: flowSlice.lines,
),
),
);
}
} else {
final staticY = page.staticElements[element];
if (staticY != null) {
children.add(
pw.Positioned(
left: element.x,
top: staticY,
child: _pdfElement(
element,
data,
template: template,
embeddedFonts: embeddedFonts,
pageNumber: pageIndex + 1,
pageCount: pageCount,
now: renderedAt,
),
),
);
}
}
}
}
}
// 2. Process repeater sections
for (final entry in page.sections.entries) {
final secName = entry.key;
final secPageSlice = entry.value;
final items = resolvePath(secName, data);
final list = items is List ? items : const [];
for (final itemSlice in secPageSlice.items) {
final item = list[itemSlice.itemIndex];
final itemMap = item is Map<String, dynamic> ? item : <String, dynamic>{};
final itemData = {...data, ...itemMap};
for (final entryElem in itemSlice.elements.entries) {
final element = entryElem.key;
final elemSlice = entryElem.value;
children.add(
pw.Positioned(
left: element.x,
top: elemSlice.elementY,
child: _pdfElement(
element,
itemData,
template: template,
embeddedFonts: embeddedFonts,
pageNumber: pageIndex + 1,
pageCount: pageCount,
now: renderedAt,
flowLines: elemSlice.lines,
),
),
);
}
}
}
return pw.Stack(
fit: pw.StackFit.expand,
children: children,
);
},
),
);
}
print('DEBUG: renderPdf finished, pages: ${pagination.pages.length}');
return document.save();
}