Skip to content

PDF reports

PdfReportBuilder (insightdock/pdf/report.h) lays out multi-page PDF reports — section headings, tables, embedded charts with pagination — on top of PoDoFo. It requires building with -DINSIGHTDOCK_WITH_PODOFO=ON (see Getting started).

For embedding a single chart into a PDF you're already drawing with raw PoDoFo calls, use ChartExporter::draw_pdf / draw_pdf_vector directly — see Embedding a single chart below. Reach for PdfReportBuilder when you want automatic pagination, section titles, and tables across a multi-page document.

Setting up the document

PdfReportBuilder doesn't own the PoDoFo document or painter — you create those yourself and pass references in, so you stay in control of page size, fonts, and saving:

#include "insightdock/charts.h"
#include "insightdock/pdf/podofo.h"
#include <podofo/podofo.h>

using namespace PoDoFo;
using namespace insightdock::charts;

PdfMemDocument doc;
PdfPainter painter;
auto &page = doc.GetPages().CreatePage(PdfPageSize::A4);
painter.SetCanvas(page);

PdfFont &font = doc.GetFonts().GetStandard14Font(PdfStandard14FontType::Helvetica);
PdfFont &font_bold = doc.GetFonts().GetStandard14Font(PdfStandard14FontType::HelveticaBold);
painter.TextState.SetFont(font, 10);

pdf::PdfReportBuilder builder(doc, painter, font, font_bold);

Coordinates throughout PdfReportBuilder are millimetres from the page's top-left corner (jsPDF-style), not PDF's native bottom-left points — this is deliberate, so report layout code reads like a page-flow document rather than PDF geometry.

Section headings and a header bar

builder.draw_header_bar("Quarterly Chart Report");
builder.section_title("Revenue Overview");

section_title advances the builder's internal vertical cursor (y_mm()); everything drawn after it — tables, chart flows — stacks below automatically.

Embedding charts with automatic pagination

draw_chart_flow is the usual way to place a chart: it calls ensure_space() first (starting a new page if there isn't enough room left on the current one), optionally draws a caption and border, draws the chart, then advances the cursor by the chart height plus a gap — so the next element you draw lands below it without manual bookkeeping:

BarChart bar;
bar.options().width = 480;
bar.options().height = 280;
bar.options().title = "Quarterly Sales";
bar.set_categories({"Q1", "Q2", "Q3", "Q4"});
bar.add_series("2024", {30, 45, 60, 50});
bar.add_series("2025", {40, 50, 55, 65});

ExportOptions raster_opts;
raster_opts.supersample = 2;   // sharper embedded raster output

builder.draw_chart_flow(pdf::PdfPageLayout::margin_l_mm, builder.content_width_mm(), 90,
                        bar, "Quarterly Sales by Year", raster_opts);

ExportOptions::pdf_backend still applies here — pass PdfBackend::Vector for pie, bar, line, area and combo charts to get native vector paths and selectable text instead of a rasterized-and-embedded PNG (see Vector export):

ExportOptions vector_opts;
vector_opts.pdf_backend = PdfBackend::Vector;

PieChart pie;
pie.options().width = 280;
pie.options().height = 280;
pie.options().background = Color::white();
pie.add_slice("A", 50);
pie.add_slice("B", 30);
pie.add_slice("C", 20);

builder.draw_chart_flow(pdf::PdfPageLayout::margin_l_mm, 90, 90, pie, "Category Split", vector_opts);

If you need to place a chart at an exact position without touching the flow cursor or triggering a page break — e.g. a fixed logo-adjacent chart in a custom header — use draw_chart instead of draw_chart_flow.

Tables

builder.draw_table(
    "Top Devices",
    {"MAC", "Hostname", "Bytes"},
    {
        {"aa:bb:cc:dd:ee:ff", "laptop-01", "1.2 GB"},
        {"11:22:33:44:55:66", "phone-02", "480 MB"},
    },
    {60.0, 60.0, 40.0} // column widths in mm
);

Cell contents that need manual line breaks (long MAC/IP lists) can go through pdf::multiline_cell() first to split them across lines within a cell.

Footers and finishing the document

builder.draw_footers("demo-user", "Generated by podofo_report_example");
doc.Save("report_builder_example.pdf");

Starting a new page manually

draw_chart_flow/ensure_space paginate automatically when content doesn't fit, but you can also force a break — for a hard section boundary, for instance:

builder.new_page();               // default top margin
builder.new_page(/*top_y_mm=*/15.0);

Embedding a single chart without the report builder

If you're drawing a PDF page yourself with raw PoDoFo calls and only need one or two charts embedded — no report layout, no pagination — skip PdfReportBuilder and call ChartExporter directly. Coordinates here are PDF points, not millimetres, and (x, y) is the chart's lower-left corner (native PDF convention, unlike the top-left convention PdfReportBuilder uses):

ExportOptions raster_opts;
raster_opts.supersample = 2;

ChartExporter::draw_pdf(doc, painter, bar, /*x=*/72, /*y=*/500, /*w=*/220, /*h=*/130, raster_opts);

draw_pdf_vector is the vector-backend equivalent (native paths for all built-in charts; custom charts without draw() fall back to raster, same as PdfBackend::Vector above).

Full working example

examples/podofo_report_example.cc builds both a hand-assembled single-page PDF (raw ChartExporter::draw_pdf calls) and a full PdfReportBuilder-driven multi-page report in one program:

cmake -B build-podofo -DINSIGHTDOCK_WITH_PODOFO=ON -DINSIGHTDOCK_PODOFO_DIR=/path/to/podofo
cmake --build build-podofo --target podofo_report_example
export LD_LIBRARY_PATH="$(pwd)/build-podofo/podofo/target:${LD_LIBRARY_PATH}"
./build-podofo/podofo_report_example

See Examples for the full source.