Building a PDF Creation Library in MQL5 (Part 3): The Byte-Exact Writer

Why this article was written
Part 2 showed what goes wrong when a human maintains byte offsets by hand: edit one string and three numbers rot at once, and the format will not tell you.
Part 3 removes the human from that job entirely. The whole article rests on a single rule:
Never calculate a byte offset. Ask the buffer where it is.
Every number that describes the file is derived from the file, at the moment it is written. There is nothing left to keep in sync.
What is inside
CPdfBuffer — bytes, and the current position. A growable byte array that
knows its own length. PutByte, PutText, PutLine, PutInt, PutReal,
PutPadded, PutBuffer, SaveToFile.
- Growth is doubling, not incremental. The article explains why that turns the cost of building a document from quadratic into linear — about 18 reallocations for its example rather than thousands.
PutRealhas a job you would not expect: stopping MQL5 from writing exponent notation. A value below half the last printed digit is clamped to zero, trailing zeros are trimmed, and-0becomes0. A PDF with1e-07in it is not a valid PDF.SaveToFilewritesm_lengthbytes, not the array. Writing the whole array would append a tail of zero bytes after%%EOF.
CPdfWriter — objects, and where they landed. It hands out object numbers,
records where each one lands, and writes the cross-reference table from what it
observed.
- The line the class exists for is
BeginObject: recordm_offset[id] = m_out.Length(), then write the header. One line, and the entire category of bug from Part 2 is gone. - Numbers before objects.
NewObjectId()reserves an id you can reference before the object exists — which is how a page can point at a font that has not been written yet. - Three guards on
BeginObject: an id that was never allocated, an object written twice, an object opened while another is still open. - Failing once. The first error is recorded and printed; later ones are ignored, so the message you see is the one that actually matters.
Finishrefuses to produce a broken file. It checks that nothing is still open, that the root exists, and that every allocated id was actually written.Saverefuses unlessFinishsucceeded.- A binary marker.
Begin()writes four high bytes in a comment after the header, so transfer tools treat the file as binary rather than text.
The demo that proves it. Demo03_HelloWriter.mq5 rebuilds Part 1's file
through the new API — 599 bytes, 5 objects, validator reports no problems.
Every offset shifted by +6 or +7 because of the binary marker, and not one of
them had to be touched by hand. That is the whole argument, measured.
And one that is actually useful. Demo03_TextToPdf.mq5 reads a text file and
paginates it into a PDF: Courier so character width is predictable, pages
reserved in advance, TL/T* for leading, a page N of M footer, and
EscapePdf for backslashes and parentheses — backslash first, and the
article explains why that order is not optional.
An honest note on scope
PDF syntax is still assembled as text. Dictionary keys and resource names are the caller's business. String escaping covers only parentheses and the backslash — control characters and bytes above 126 need an encoding decision this part has not made, so Part 5 replaces that function. Proportional font widths are not measured; Part 6 is entirely about that.
And the honest headline: the library still cannot draw. Part 4 starts on it.
PdfValidate.mqh Include/MQLPDF/ Part 2, carried over unchanged
PdfDefines.mqh Include/MQLPDF/ shared constants <- today
PdfBuffer.mqh Include/MQLPDF/ bytes, and the current offset <- today
PdfWriter.mqh Include/MQLPDF/ objects, xref table, trailer <- todayMore in this series
Generating real PDF documents from MetaTrader in pure MQL5 — statements, trade tickets and performance reports — with no DLL and no external library. About this series
- Part 1Writing a PDF by HandSeptember 24, 2026
- Part 2Why PDFs BreakOctober 19, 2026
- Part 3The Byte-Exact Writer — you are hereOctober 29, 2026
Continue reading on MQL5.com
The full article with complete source code is published on MQL5.com.


