8. fix:: namespace — normalization
← codes:: · Index · Next: data:: →
19 operations that modify the PDF and save a new file. The original is never touched.
8.1 How to use it
fix:: is the only namespace that writes, so it runs under its own command:
pdfl fix input.pdf script.pdfl --output fixed.pdf
Options:
| Option | What it does |
|---|---|
--output <file> |
Output PDF (required) |
--dry-run |
Lists the operations without saving anything |
--report json|csv|html|pdf |
Report format |
--report-file <file> |
Writes the report to a file |
Under pdfl run, any fix:: call raises an error pointing to the right command —
so nobody applies corrections while believing they are only validating.
How operations work
// This script needs no checks: these are commands, executed in order.
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::add_page_numbers()
fix::add_watermark("DRAFT")
Every call is validated at call time (nonexistent page, invalid rotation,
missing file) and only then applied. The report carries a fixes field with what
was done:
"fixes": [
"TrimBox set to [8.5, 8.5, 586.5, 833.5]",
"page numbering added",
"watermark \"DRAFT\" added"
]
Nothing stops you from mixing validation and correction in the same script:
// Validate before fixing — a failed precondition shows up in the report
check "Preconditions" {
require doc.page_count > 0
assert !struct::check_encryption(), "file is encrypted, cannot fix it"
}
fix::add_page_numbers()
8.2 Page boxes
fix::set_page_size(width, height)
Sets the MediaBox on every page.
// A4 in points — or use units and let the language convert
fix::set_page_size(595, 842)
fix::set_page_size(210mm, 297mm) // identical, and more readable
fix::set_crop_box(x0, y0, x1, y1), set_trim_box, set_bleed_box
Set the matching box on every page. Coordinates in points, from the bottom-left to the top-right corner.
// The file arrived from the publisher without production boxes:
// TrimBox = final area; BleedBox = with 3 mm of bleed around it
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::set_bleed_box(0, 0, 595, 842)
8.3 Pages
fix::rotate_page([page,] degrees)
Rotates by 90, 180 or 270 degrees. Without a page number, rotates all of them.
fix::rotate_page(90) // every page
fix::rotate_page(3, 180) // page 3 only
fix::delete_page(n) and fix::duplicate_page(n)
fix::delete_page(1) // drop the draft cover
fix::duplicate_page(1) // duplicate the cover (the copy goes right after)
Deleting the only page in a document is refused with a clear message.
fix::reorder_pages([new, order])
New page order. The list must use every page exactly once.
// A 4-page document with the cover at the end: bring it to the front
fix::reorder_pages([4, 1, 2, 3])
fix::split_document(from, to, "output.pdf")
Saves a page range to another file. The document being edited stays intact.
// Separate cover from body for different suppliers
fix::split_document(1, 2, "cover.pdf")
fix::split_document(3, 50, "body.pdf")
fix::merge_documents("other.pdf")
Appends the pages of another PDF at the end.
fix::merge_documents("attachments/warranty.pdf")
fix::merge_documents("attachments/size_chart.pdf")
8.4 Content
fix::add_watermark("text")
Diagonal grey watermark on every page.
fix::add_watermark("DRAFT — DO NOT PRINT")
fix::add_stamps("text")
Red stamp in the top-right corner of each page.
fix::add_stamps("APPROVED 2026-08-02")
fix::add_page_numbers()
n / total numbering in the footer of every page.
fix::add_page_numbers()
fix::remove_annotations() and fix::remove_attachments()
Remove annotations (comments, review markup) and attached files.
// Before sending to the print shop: review comments must not show up,
// and attachments only inflate the file
fix::remove_annotations()
fix::remove_attachments()
fix::flatten_layers()
Removes the optional content (OCG) structure, leaving all content permanently visible.
// Layers with "English version" turned off can be re-enabled by mistake
// at the shop — flattening removes the risk
fix::flatten_layers()
8.5 Optimization
The operations in this section only write when the file gets smaller. If rewriting produces a larger file, the original is kept.
fix::remove_unused_resources()
Discards objects unreachable from the trailer.
fix::remove_unused_resources()
fix::downsample_images([dpi])
Resamples images above the target DPI (default 300). DPI is computed from the actual printed size of the image on the page.
// An e-mail approval copy does not need 300 DPI
fix::downsample_images(96)
// A digital-print version
fix::downsample_images(200)
CMYK images are preserved. Resampling them would require converting to RGB, which would destroy the prepress separations. In print-shop files, the savings come from the RGB images.
fix::compress_images([quality])
Re-encodes images as JPEG at the given quality (1 to 100, default 85).
fix::compress_images(70)
Not available
subset_fonts and linearize_document do not exist as fix:: operations and
raise an unknown-function error:
- subset_fonts: it was implemented and measured. Professional producers
already embed only the glyphs in use, so the measured gain was 0.5% at best and
nil elsewhere — not worth the risk of corrupting fonts. To check whether
fonts are subset, use
prepress::subset_fonts(). - linearize_document: it requires generating hint tables (§7.14 of the PDF specification). No Rust library does this, and a partial implementation would not be recognized as "Fast Web View" by readers.
8.6 Complete examples
Preparing a publisher's file for the print shop
// prepare_for_print.pdfl
// Usage: pdfl fix publisher.pdf prepare_for_print.pdfl --output print.pdf
check "Preconditions" {
require doc.page_count > 0
assert !struct::check_encryption(),
"file is encrypted — ask the publisher for the open version"
}
// Production boxes the publisher never defined
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::set_bleed_box(0, 0, 595, 842)
// Cleanup: review comments and attachments do not go to print
fix::remove_annotations()
fix::remove_attachments()
fix::flatten_layers()
fix::remove_unused_resources()
Lightweight version for e-mail approval
// email_version.pdfl
// Usage: pdfl fix final.pdf email_version.pdfl --output approval.pdf
fix::downsample_images(96)
fix::compress_images(70)
fix::add_watermark("PROOF — NOT THE FINAL VERSION")
fix::add_page_numbers()
Checking the result with pdfl itself:
pdfl fix final.pdf email_version.pdfl --output approval.pdf
pdfl inspect approval.pdf # size, DPI and warnings for the new file
Splitting a book into cover and body
// split.pdfl
// Usage: pdfl fix book.pdf split.pdfl --output book_processed.pdf
check "Expected structure" {
assert doc.page_count > 4,
"book has only #{doc.page_count} pages — unexpected structure"
}
fix::split_document(1, 2, "output/cover.pdf")
fix::split_document(3, doc.page_count, "output/body.pdf")