The library, on your own machine
The engine that writes the documents goes inside your own software, in Rust or in Python, and creates its pages on your machine. This page says what that means for the work you already have.
Summary
What this is about
A PDF file keeps exactly the same look everywhere. You open it on your computer, your neighbour opens it on a telephone, the print shop runs it on its machine, and it is the same page down to the millimetre. That is why invoices, contracts and payslips travel in that form.
HQF Development makes the engine that writes those files. Not a program you open and click around in: an engine, which the person who writes your software puts inside their own.
What a library is
In computing, a library is a set of ready-made parts. Think of a car engine. A manufacturer does not build one from scratch for every model: it takes an engine that exists, drops it into the body it drew, and connects the pedal to it. The engine turns the wheels; the car stays theirs.
A library is that engine. Whoever writes your invoicing software keeps their software, their screens, their habits, their way of working. They slip our engine inside, they tell it here are the invoice lines, here is the logo, here is the total, and the engine hands back the PDF file.
The other way round, and what changes
There is another way: send the information to a computer somewhere on the internet, which creates the document and sends it back. It is like taking your letter to a print shop across town. It works very well, and the team offers that too. But you have to carry the envelope over there, wait, and come back for it.
With the library, the print shop is in the next room. Three things change, and all three are felt straight away.
- It is immediate. The document is created on your own machine, at the moment of the click that asked for it. Nothing leaves, nothing comes back.
- Your information stays with you. Your customers' names, their addresses, their amounts stay on your machine from start to finish. The library works entirely on the spot, with what it carries in its own package. Your data never leaves the machine that runs it.
- It stands on its own. Your software creates its documents even when the connection is down and on an isolated site. The only machine to watch is your own.
What it makes
Invoices, order confirmations, delivery notes, payslips, contracts, statements, sheets of labels, catalogues, certificates, boarding passes, whole books. With tables that carry on from one page to the next, logos, photographs, the QR codes a telephone reads to pay, boxes people fill in on screen, and a password that locks the file when you ask for one.
What you receive, and how it reaches you
You create an account on this website, you pay, and the file to install is waiting for you inside that account. You sign in, you click on it, and it is saved onto your computer, exactly like any other file you download. The same page holds your invoice.
Beside the file sits your licence key: a string of letters and figures, yours alone, which your software is given once and which tells the engine it is a paid copy. Hand the file and that string to the person who develops your software, and they have everything.
Everything comes from here, from your own account. When a newer file is ready, it is waiting in the same place, and you come back for it when it suits you.
What you buy is the right to use the engine, built and ready to run. Its source stays with HQF Development, the way a car maker sells the engine and keeps the drawings it was built from.
Who this page is for
Hand the address of this page to the person who develops your software: further down, they will find everything they need to judge it.
Technically
One engine, reached two ways — a Rust library and a Python wheel — the public surface they expose, how both are downloaded from your account here, and the licence key they read at run time.
Two bindings
The engine is written in Rust and is used from Rust directly, as an ordinary dependency of your project.
The Python side is a compiled extension shipped already built: nothing to compile on the target, no system library to install, no PDF toolchain on the box. It is built against CPython's stable ABI: one Python wheel serves every version of the interpreter from CPython 3.9 up, on Linux, Windows and macOS.
A C API, for calling the engine from other languages, is planned for a future version.
What it writes
- Text: TrueType and OpenType parsed and embedded, cut down to the glyphs actually drawn; Type 1 carried whole, the program as it stands; a WOFF file, the shape a browser is served, read as the font inside its wrapper; ligatures, small capitals, old-style figures, kerning; a fallback font behind another; the fourteen standard fonts reading software is expected to provide; Type 3 fonts whose glyphs are drawings; vertical writing.
- Layout: paragraphs broken to a width, columns, lists, tab stops and leader dots, styled spans inside a paragraph, tables paginated over as many pages as they need, blocks stacked without a single coordinate written by hand.
- Drawing: paths, curves, arcs, rounded corners, clipping, axial and radial gradients, tiling patterns, transparency and blend modes, soft masks, reusable drawings placed as often as wanted for the weight of one.
- Images: JPEG, PNG and raw samples; grey, RGB, CMYK, palette; alpha, stencil masks, clipping paths, ICC profiles, orientation.
- Colour: RGB, CMYK, grey, calibrated spaces, L*a*b*, ICC profiles, spot colour, several named colorants at once, indexed tables.
- Codes: Code 128, Code 39, Code 93, ITF-14, Codabar, EAN-13, UPC-A, EAN-8, UPC-E, GS1 DataBar, the two-digit and five-digit add-ons, QR code, Data Matrix, PDF417, Aztec.
- Interactive parts: text fields, check boxes, drop-downs, radio groups, push buttons, signature fields, links, bookmarks, page labels, article threads, layers, page transitions.
- Standards: PDF/A for archiving, PDF/X for press, PDF/UA for accessibility, Factur-X for electronic invoicing, XMP metadata with schemas of your own, full page tagging.
- Protection: AES-256 encryption, user and owner password, permissions per action.
- Reading: open a PDF written by another program and read its pages, metadata, links, form fields, bookmarks, layers and structure; take a whole page of it into the document you are writing.
- File weight and time to the first page: compression setting, object streams, linearised output.
What is delivered, and how a build gets hold of it
Delivery is a download from your account on this website. Sign in and the download page lists what your licence covers, each artefact pinned to a version: the Python wheel for the Python side, and the compiled Rust library for the Rust side. A click downloads the file. If you paid by card, the same link is also sent to the address you gave.
The Python wheel installs into a virtual environment as it stands. The Rust side is declared in your manifest against the copy you downloaded, so a build resolves it from your own store the way it resolves any other pinned dependency — and resolves it inside a network that reaches nothing outside itself.
Your licence key sits on the same account page as the artefacts and the invoices. It is a signed string, read at run time from a file, an environment variable or a secrets manager and handed to the document in one call. What the licence buys is the right to run the compiled engine, which is what the terms of sale set out; the source stays with HQF Development.
A newer version is published to the same page, so upgrading is downloading the artefact you want and moving your pin to it. The licence key you already hold goes on working.
What it asks of the machine that runs it
It works on the spot. Everything it needs is in the package: the fourteen standard font metrics, the one colour profile it builds itself, the code tables. A check reads the delivered file itself and lists every outside call it is able to make: it fails if a single one of them could open a connection, look up a name on the network or start an encrypted session. Your data stays on the machine that runs it.
A signed licence key, handed over by the team on purchase, is what unlocks it. The key is read from a file, an environment variable or a secrets manager, and given to the document in one call. Before a key is given, the engine writes an evaluation copy: every page comes out with a watermark, every letter is traced as a shape instead of being written as text, the file carries no font, no form and no structure are written, and a handful of calls refuse outright, naming the licence they require. It is a working trial of the shape of a document.
How a document is assembled
One shape, always: create a document, add resources to it — a font, an image, a drawing, a gradient — each of which hands back a handle, fill a content stream with drawing operators, make a page of it, add the page to the document, ask for the bytes.
Three layout engines sit above that groundwork and spare you every coordinate: the text flow breaks a paragraph to a width, the table measures its rows from what they carry and paginates itself, the stack places blocks one under the other. Alongside them: colour, images, forms, navigation, metadata, conformance, protection, and reading an existing PDF.
The detail, with its figures
The numbers family by family, the same program written on both sides, and the shape a refusal comes back in.
The public surface at a glance
Version 1.307.1 of the engine exposes 44 public modules and 220 names at the root of the Rust crate; the Python side declares 312 classes, because one namespace there holds what Rust keeps in a module per family. 109 complete examples ship with it, each in two twin copies, one in Rust and one in Python. Before a change enters the library, every one of them is run in both languages and the two files compared byte for byte, so that none of them ages quietly.
| Family | What is in it |
|---|---|
| Document and page |
Document, Page, Content, Version, Compression, PageGroup, PageTurn, TabOrder
|
| Fonts and text |
Font, FontHandle, StandardFont, Type1Font, Type3Font, MissingGlyph, Run, WritingMode
|
| Layout |
TextFlow, Table, Row, Cell, Columns, ColumnWidth, RichText, Style, Rect, Shadow
|
| Stacked blocks, table rules and stamps |
layout: Stack, Block, FittedTable, Rule, Stroke, Border, Padding, Margin, VerticalFlow, TaggedTable, Stamp
|
| Colour |
Color, Rgb, Cmyk, Paint, CalGray, CalRgb, Lab, IccBased, Indexed, Separation, DeviceN, OutputIntent
|
| Images and fitting |
Image, ImageHandle, ImageSpace, Resolution, Orientation, ClippingPath, FitBox, FitMode, Anchor, Turn, Mirror
|
| Gradients, patterns, functions |
Axial, Radial, TilingPattern, Spacing, Function, Calculation, Op
|
| Graphics state |
ExtGState, BlendMode, SoftMask, OverprintMode, BlackPointCompensation
|
| Drawings and layers |
Drawing, Layer, LayerConfiguration, LayerRule, LayerPolicy, LayerIntent, LayerPurpose, LayerWork, PageRole
|
| Codes |
Code128, Ean13, Ean8, UpcA, QrCode, DataMatrix, Pdf417, Aztec
|
| Forms and signing |
TextField, CheckBox, ChoiceField, RadioGroup, PushButton, SignatureField, FieldBorder, Signature, Signer, IncrementalUpdate
|
| Navigation |
Bookmark, Link, Article, PageLabel, OpenAction, PageFit, ViewerPreferences, Transition
and everything the action module holds
|
| Annotations | the annotation module: squares, circles, lines, polygons and polylines |
| Accessibility |
StructureTree, StructElement, Marks, Scope, Artifact
|
| Metadata and standards |
metadata: Metadata, Xmp, XmpSchema, PdfA, PdfX, PdfUa, Invoice, Attachment; Standard, Report, Finding
|
| Protection |
Encryption, Permissions
|
| Reading |
read: Reader, ImportedPage, Information, FormEntry, Outline, LayerEntry, Structure, Parts
|
| Licence and reporting |
License, Error, Warning, ResourceKind
|
| Low level |
cos, filter, incremental, platform
|
The shortest useful document
In Rust
use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::{Document, License, Page};
let mut c = Content::new();
c.set_fill_rgb(0.20, 0.40, 0.80)?;
c.rect(50.0, 700.0, 495.0, 90.0)?;
c.fill();
let mut page = Page::a4();
page.content = c.into_bytes();
let mut doc = Document::new();
doc.set_license(License::from_key(key)?);
doc.set_info(Name::new("Title"), "hqf-pdf sample invoice");
doc.add_page(page)?;
let bytes = doc.to_bytes()?;
In Python
import hqf_pdf
content = hqf_pdf.Content()
content.set_fill(hqf_pdf.Rgb(0.20, 0.40, 0.80))
content.rect(50.0, 700.0, 495.0, 90.0)
content.fill()
page = hqf_pdf.Page.a4()
page.set_content(content)
document = hqf_pdf.Document()
document.set_license(hqf_pdf.License.from_key(key))
document.set_info("Title", "hqf-pdf sample invoice")
document.add_page(page)
data = document.to_bytes()
Text in an embedded font
Only the glyphs the document draws go into the file: a page holding one line carries one line's worth of font, not the whole font. Measuring goes through the same handle that draws, which is what keeps text inside its box.
In Rust
let font = Font::parse(fs::read(&font_path)?)?;
let handle = doc.add_font(font);
let mut c = Content::new();
c.begin_text();
c.set_font(&handle, 14.0)?;
c.text_position(60.0, 760.0)?;
for line in &lines {
c.show_glyphs(&handle.glyphs(line));
c.text_position(0.0, -28.0)?;
}
c.end_text();
let width = handle.measure(lines.last().map_or("", String::as_str), 14.0);
In Python
font = hqf_pdf.Font.from_path(font_path)
handle = document.add_font(font)
content = hqf_pdf.Content()
content.begin_text()
content.set_font(handle, 14.0)
content.text_position(60.0, 760.0)
for line in lines:
content.show_glyphs(handle.glyphs(line))
content.text_position(0.0, -28.0)
content.end_text()
width = handle.measure(lines[-1], 14.0)
A paragraph that breaks itself
In Rust
let flow = TextFlow::new(&handle, 11.0).align(align).leading(15.0);
let lines = flow.break_lines(words.paragraph, box_width);
c.begin_text();
flow.draw(&mut c, &lines, x, top, box_width)?;
c.end_text();
In Python
flow = hqf_pdf.TextFlow(handle, 11.0, align=align, leading=15.0)
lines = flow.break_lines(words.paragraph, box_width)
flow.draw(content, lines, x, top, box_width)
A table that carries on page after page
A row height is never given: it is measured from the text the row carries, once the column widths are known. That is what lets the table decide, row by row, whether the next one still fits. Fitting draws nothing: it returns the geometry, so a table can be measured before anyone commits to where it goes. Call it once per page until it reports that it is done, or ask for the whole run in one call and get one placement per page.
In Rust
let columns = Columns::new(
vec![
ColumnWidth::Fraction(1.0),
ColumnWidth::Points(46.0),
ColumnWidth::Points(94.0),
ColumnWidth::Points(94.0),
],
TABLE_WIDTH,
)?;
let mut table = Table::new(columns);
table.header(1);
table
.rule(Rule::Frame, Stroke::black(0.8))
.rule(Rule::HorizontalOther, Stroke::new(0.25, Rgb::gray(0.75)));
let pad = Padding::symmetric(5.0, 4.0);
table.push(
Row::new()
.cell(Cell::new(text, 9.0, label).padding(pad))
.cell(Cell::new(text, 9.0, value).padding(pad).align(Align::Right)),
);
let mut start = 0;
loop {
let placed = table.fit(TableFrame::new(MARGIN, TOP, TOP - BOTTOM), start)?;
let mut content = Content::new();
placed.draw(&mut content)?;
let mut page = Page::a4();
page.content = content.into_bytes();
doc.add_page(page)?;
match placed.outcome() {
FitProgress::Done => break,
FitProgress::BoxFull { next_row } => start = next_row,
FitProgress::RowTooTall { row } => {
return Err(format!("row {row} is taller than a page").into());
}
outcome => return Err(format!("the table fitted to {outcome:?}").into()),
}
}
In Python
columns = hqf_pdf.Columns(
[
hqf_pdf.ColumnWidth.fraction(1.0),
hqf_pdf.ColumnWidth.points(46.0),
hqf_pdf.ColumnWidth.points(94.0),
hqf_pdf.ColumnWidth.points(94.0),
],
TABLE_WIDTH,
)
table = hqf_pdf.Table(columns)
table.header(1)
table.rule(hqf_pdf.Rule.frame(), hqf_pdf.Stroke(0.8))
grey = hqf_pdf.Rgb.gray(0.75)
table.rule(hqf_pdf.Rule.horizontal_other(), hqf_pdf.Stroke(0.25, grey))
pad = hqf_pdf.Padding.symmetric(5.0, 4.0)
table.push(
hqf_pdf.Row(
[
hqf_pdf.Cell(font, 9.0, label, padding=pad),
hqf_pdf.Cell(font, 9.0, value, padding=pad, align=hqf_pdf.Align.Right),
]
)
)
start = 0
while True:
placed = table.fit(MARGIN, TOP, TOP - BOTTOM, start)
content = hqf_pdf.Content()
placed.draw(content)
page = hqf_pdf.Page.a4()
page.set_content(content)
document.add_page(page)
if placed.done:
break
start = placed.next_row
How refusals come back
In Rust, everything that can refuse returns a result carrying the library's own error type: a non-exhaustive enum of 96 variants, each naming precisely what happened — a malformed font, a malformed image, no such font, no such page, two fields named alike, something missing for print, something missing for archiving, something the conformance level forbids, a row taller than its box, no licence, an invalid licence, an encrypted file. A customer matches on the exact case rather than on message text.
Data from outside — a PDF, a font, an image, a profile — that does not hold up is an ordinary outcome, returned as such and never a crash. 16 panics remain on production paths. Each states the invariant it holds, and each is unreachable from any value a customer supplies.
In Python the same refusals come back as exceptions, every one of the library's own under a single class a customer can catch whole. The 2 layout cases — a table row taller than the box it was given, and a stack block in the same case — share a class of their own beneath it: a pagination loop catches them together by that shared class, or each apart.
Beside refusals, remarks. Writing the bytes has a reporting form that hands back the bytes and the list of warnings the document has about itself; the file is written and valid either way, and a warning points at a document that is probably not the one the customer meant. One of them names a font that went in against the permission stated inside the font file itself: the licence the font was bought under is what settles the matter, and the remark is what puts the question in front of the customer, who holds it. Checking against a standard goes further: it returns everything known to stand between the document and that standard, writing nothing and changing nothing.
What the current version covers
Version 1.307.1 of the engine: writing and reading PDF, all three generations of protection on the reading side and AES-256 encryption on the writing side, PDF/A-2, PDF/A-3 and PDF/A-4, PDF/X-4, PDF/UA-1 and PDF/UA-2, Factur-X, full tagging, PAdES signing with the customer supplying the signer, linearised output, object streams, and taking pages from other files.
The manual
The engine's full manual — every call, every setting, every pitfall — is handed over in direct conversation with the team.
What really helps is the part that answers your question, with the example that matches your case. So the route is this. You register on this site, you write to us about what you are trying to do — an invoice carrying its XML, a table of two thousand rows, a sheet going to press — and the team sends you back the part of the manual that covers exactly that, with the code on both sides, Rust and Python.
You are talking to the people who wrote the engine, and the answer comes cut to fit your project. In the meantime, the 109 examples shipped with the library cover most of what it can be asked for, and each one runs as it stands.
Where to go next
Everything it puts on a page, group by group The other way: ask the service for the document Create an account, and tell us what you are building Ask us anything this page left out