Linearization
A linearized PDF is arranged so that everything needed to display page 1 sits at the front of the file, followed by hint tables telling a viewer which byte range holds each of the remaining pages. A reader fetching the file over HTTP can render the first page from the first range request and then fetch only the page the user jumps to. Acrobat calls this Fast Web View. It is a layout property, not a compression setting: the same objects come out, in a different order, and the file usually ends up slightly larger because of the hint stream and the duplicated cross-reference section.
Reach for it when documents are served over the network and are large enough that the whole-file download is noticeable. For files that are downloaded and opened locally it buys nothing.
Doing it
pdfWriter, err := reader.ToWriter(nil)
if err != nil {
return err
}
optimize.Linearize(pdfWriter)
return pdfWriter.WriteToFile(outputPath)optimize.Linearize is a wrapper over PdfWriter.SetLinearized(true), kept in the
optimize package so it can be found alongside the size options. It is not an
Optimizer: it changes the write strategy rather than transforming the object list, so it
is not passed to SetOptimizer and does not go into a chain.
Nothing happens at the call site. Write dispatches to the linearized writer, which
rewrites the document into the layout of ISO 32000-1 Annex F: header, linearization
parameter dictionary, first cross-reference section, hint stream, first-page section,
remaining objects, main cross-reference section. The parameter dictionary has to be the
first indirect object, so every object in the document is renumbered.
WriteLinearized(io.WriteSeeker) is the same thing in one call, equivalent to
SetLinearized(true) followed by Write. The seekable writer is accepted for the benefit
of a future in-place variant; the current implementation buffers regardless, so a plain
Write or WriteToFile is no worse.
Creator output goes through SetPdfWriterAccessFunc, which hands you the writer before
the document is assembled:
c.SetPdfWriterAccessFunc(func(w *model.PdfWriter) error {
optimize.Linearize(w)
return nil
})Object streams
LinearizeWithOptions takes a model.LinearizationOptions, whose one field is
UseObjectStreams. With it set, compressible objects in the main section are packed into
object streams and the cross-reference tables become cross-reference streams, which
recovers most of the size that linearization costs.
optimize.LinearizeWithOptions(pdfWriter, model.LinearizationOptions{
UseObjectStreams: true,
})The first-page section is deliberately left uncompressed, so page 1 still renders without decoding a container first. Objects excluded from packing are pages, page tree nodes, the catalog, the encryption dictionary, signature dictionaries, existing object stream and cross-reference stream containers, and anything that is not an indirect dictionary. Cross-reference streams require PDF 1.5, so the output version is raised to 1.5 if it was lower.
Prefer this over optimize.Options{UseObjectStreams: true} when linearizing. The
linearized writer needs to decide for itself what may be packed and what has to stay
addressable in the first-page section.
Limitations
Linearizing a document with no pages returns linearization: cannot linearize a document with no pages. This is one of the few places where the writer refuses outright rather
than degrading.
UseObjectStreams uses a single object stream container, which caps out at 65535 entries.
A document with more compressible main-section objects than that fails with an error
saying object stream splitting is not yet supported. Linearizing without object streams
has no such limit.
Peak memory runs at roughly three times the output file size. The implementation holds the live object graph, a serialized copy of every object, and the assembled section buffers at the same time, because the byte offsets in the parameter dictionary and hint stream depend on lengths that are only known once everything has been laid out. Batch jobs on large files should account for that.
Encryption is compatible. Objects are encrypted as they are serialized and the encryption dictionary is pinned into the first-page section, since a reader following the linearization protocol has to decrypt that range immediately.
Signing an already linearized file gives up the linearization. Signing is an incremental update that appends objects at the end, which leaves the parameter dictionary advertising a length and hint offsets that no longer match the file, and some validators reject the signature that results. UniPDF handles that by superseding the dictionary with an empty stub, so readers stop treating the output as linearized while the original bytes, and therefore the signature, stay valid. It does this only on the signing path and only when no earlier revision was already signed; a non-signing incremental update over a linearized file comes out byte-identical.
A signature created during the linearized write itself is fine. Signature dictionaries are kept out of object streams and their file offsets are patched once the layout has converged, so the signing flow can still find them.
The optimizer chain still runs when linearization is enabled, so SetOptimizer and
Linearize combine. See optimizer options for what the chain
does.