DEV Community

AsyncMonk
AsyncMonk

Posted on

Four checks before I send a web report as a PDF

A client asked for a PDF copy of the reporting page I had built for them. I hit print, saved as PDF, sent it over. Next morning: "the table only has eight rows, where is the rest?" The page had forty-six rows. Nothing had been deleted. The rows disappeared somewhere between the browser and the file.

My first guess was that the export tool had cut corners, so I redid it through a completely different converter. Eight rows again. Same eight rows, same missing thirty-eight. When two unrelated implementations fail identically, the bug is not in either of them.

To figure out what was actually happening I stopped using the client's data and built my own sample instead: a static report page styled the way these pages usually look, forty-six detail rows, four summary cards, header and footer, all numbers invented. I exported it two ways — the browser's own print-to-PDF, and the HTML to PDF converter on ImgIng (https://imging.ai/ ) — and then pulled both files apart with a script to count pages, page size, body font size, extractable characters and link annotations. Every number below comes from that sample.

The eight rows turned out to be my own markup. The table was sitting inside a fixed height container with overflow:auto. Rows below the fold never take part in layout, and a PDF has no scrolling, so the only thing either engine can paint is the one screenful that is laid out. Both paths exported rows R01 through R08 and dropped the rest, text retention fell to 13.4%, and neither interface warned me about any of it. This is not an exotic mistake either — most admin table components ship with a fixed height scroll body and a sticky header by default, so you can hit this without ever writing an overflow rule yourself.

46 rows inside a scrolling container, only 8 of them make it into the PDF on either path

The fix has to happen before you export: drop the height constraint, let the table grow with the page, confirm the page actually got taller, then convert. For a report I deliver repeatedly I now keep a print stylesheet that restores natural height, so I do not have to remember.

The second thing I check is pagination, because without a print stylesheet each path just follows its own default. The browser gave me 2 pages of Letter at 612×792pt and shrank the content to fit the paper, which pushed thirteen pixel table text down to 6.74pt. The other path followed the real rendered size and produced 1 custom page at 9.75pt. That font size gap is not a rendering quality difference — zoom into the same block on both and the glyphs are equally sharp — it is purely the consequence of not having to shrink anything. What actually settles the question is telling the page what you want: once I added @page, repeated the header on every page and kept rows from breaking mid-row, both paths produced identical output, 7 pages of A4 with the header repeated 7 times. And @page does work inside @media print; I moved it out to the top level once, convinced it had to be bare, and the two files differed by about thirty bytes.

Same report page: 2,996 selectable characters at 156,370 bytes, versus a screenshot based export with 0 selectable characters at 995,232 bytes

Third, selectable text. Rasterising the whole page into an image and dropping it into a PDF makes pagination problems vanish, and I wanted to know what that costs, so I built that export myself as a control. Same report page: the screenshot version has zero selectable characters and weighs 995,232 bytes; the laid out version has 2,996 selectable characters at 156,370 bytes, roughly a sixth of the size. A client who wants to copy an order number or search the document gets nothing out of the first one.

The fourth check has nothing to do with rendering. Import, directory reading, resource mapping, live preview, and the lossless minimisation applied after the PDF comes back all happen locally in the browser with zero upload; only after you click convert does it send one POST to its own same-origin endpoint, carrying a self contained HTML snapshot with scripts stripped and resources inlined, and the server side Chromium / Skia returns the real PDF. I captured one of those requests and the body was 1,604,339 bytes. That is not a complaint, it is something you have to know before you press the button on a report full of client data. Summary pages I convert without thinking; anything with real amounts and contact details gets anonymised first.

One thing I have not solved: Chinese line breaks land differently. The rendering server does not have my local Chinese fonts, so the same intro paragraph wraps at a different word and half a sentence moves to the next line. When the layout has to match the screen exactly, inlining the font into the page is the only workaround I have found so far.

All four checks take me about three minutes. Counting rows in the exported file would have saved me one embarrassing morning.

Top comments (0)