All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.0.13 - 2026-09-04
- Overlay text drawn in a font registered via
WithFontis now embedded as a Type0/Identity-H composite font, so non-Latin scripts (Arabic, CJK, Cyrillic, …) render correctly instead of as?(#37)document/render/overlay.go:renderTextemitted the raw UTF-8 bytes as a literal string ((…) Tj) whileWriteOverlayToModifierregistered the font as a bare/Subtype /TrueTypedict with no/Encoding,/Widthsor/FirstChar. Viewers read those bytes as single-byte codes and substituted a glyph for each one. Overlay text in a registered TrueType font is now encoded as big-endian glyph IDs (<…> Tj), matching what the normal render path has always done.document/render/overlay.go: newOverlayFontRegistryallocates one font object per family across everyOverlaycall, andExistingDocument.Saveflushes it once. A font used on many pages is therefore embedded a single time and subsetted over all glyphs the document actually uses, instead of once per page.document/render/overlay.go: bold/italic variants with no dedicated registered face now fall back to the base family's embedded font rather than silently degrading to Helvetica, mirroringPDFRenderer.resolveTextFont.document/render/pdftarget.go: the Type0/CIDFont writer (writeType0Font,writeFontDescriptor,writeCIDFont,writeToUnicodeCMap,writeCompressedStream,subsetFontData) is now written against anobjectSinkinterface so it serves bothpdf.Writer(new documents) andpdf.Modifier(incremental updates) instead of being duplicated.document/render/overlay_test.go,template/overlay_test.go: regression coverage for glyph-ID encoding, the variant fallback, the emitted/Type0+/Identity-H+/FontFile2structure, and single embedding across pages_validation/validation_test.go:TestOverlayEmbeddedFontvalidates an overlay-with-embedded-font PDF through pdfcpu
ParseSignatureInfono longer truncates a CMS blob whose DER ends in a zero byte, which made ECDSA signature verification fail intermittentlysignature/verify.go:extractContentsHexstripped the fixed-width/Contentsplaceholder padding withstrings.TrimRight(hex, "0"), so a signature whose own final byte was0x00came back one byte short and failed to parse asasn1: syntax error: data truncated. The outer DER tag-length header is now read to slice off exactly the signature, whatever it ends with. An all-zero placeholder is also rejected with a clear message instead of reaching the ASN.1 parser.- This affected ECDSA in practice — its DER length and trailing byte vary per signature, so roughly 1 in 256 signatures hit it, which showed up as a flaky
TestSign_WithTimestamp_ECDSAin CI. RSA was unaffected because its signature length is fixed. signature/sign_test.go: deterministic coverage for a payload ending in0x00, long-form DER lengths, and malformed contents
1.0.12 - 2026-09-03
- Reader now supports compressed object streams (
/ObjStm) and predictor-encoded streams, so PDFs written with a cross-reference stream — the default for most modern producers — can be opened, overlaid and merged instead of failing withpdf: object N not found in xref(#35)pdf/objstm.go: new/ObjStmreader — decodes the stream body, parses theNobject-number/offset header pairs up to/First, and resolves objects from it. Decoded streams are cached so sibling objects do not re-inflate the same body, and each object is parsed from its own bounded slice of the body.pdf/reader.go:parseXRefEntriesnow records type-2 cross-reference entries,GetObjectresolves them through the object stream, andMaxObjectNumbercounts them — sogpdf.Open,gpdf.MergeandOverlayall work on these files. Entries from a newer xref section keep winning over an older/Prevsection.pdf/predictor.go:/DecodeParmspredictor support (PNG predictors 10–15 and the TIFF predictor 2). Real cross-reference streams are almost always written with/Predictor 12, so without this the decoded xref entries were garbage.pdf/objstm_test.go,pdf/predictor_test.go: coverage for object-stream reads, merging an object-stream PDF, stale xref indices, newest-section-wins precedence, and each predictor filter type
- Incremental update xref entries are now exactly 20 bytes, so the output of
Open→Overlay→Saveis no longer reported as damaged by strict readerspdf/modifier.go:writeIncrementalXRefwrote"%010d %05d n \r\n"— 21 bytes. Readers index the table by fixed-width offset, so the extra byte shifted every following entry and forced a cross-reference reconstruction (qpdf --check:invalid xref entry). The non-incremental writer inpdf/xref.gowas already correct; its doc comment showed the wrong width and has been corrected.pdf/modifier_extra_test.go: regression test asserting every appended xref entry line is 20 bytes
_validationnow resolves again:github.com/hhrutter/lzwwas pinned tov1.0.4, a version that no longer exists upstream, and thego.sumentry forgithub.com/hhrutter/tiff v1.0.4no longer matched the module. The indirect requirements are realigned with whatpdfcpu v0.9.1declares (lzw v1.0.0,tiff v1.0.1); the recorded checksums are verified againstsum.golang.org._validation/validation_test.go:TestQPDF_ObjectStreamRoundTripconverts a generated PDF withqpdf --object-streams=generate, then merges and overlays it and validates the result with pdfcpu (skipped whenqpdfis not installed)
1.0.11 - 2026-05-18
ResolvedFont.IDnow uses the font registration key instead of the resolved family name, so per-family overrides (e.g. registering a CJK font under a custom key) are honored at render time (#30)template/fontresolver.go: the resolver stores the input registration key onResolvedFont.IDso downstream font lookups in the renderer match the registered font, not the resolved family fallbacktemplate/fontresolver_test.go: regression coverage for registration-key vs family-name ID- Golden refresh:
_examples/testdata/golden/32_cjk_text.pdfand32d_cjk_mixed.pdfregenerated to reflect the corrected font ID embedded in the content stream
1.0.10 - 2026-05-07
- Per-column horizontal text alignment for tables (#26)
- Builder:
template.ColumnAlign(aligns ...document.TextAlign)— sets the horizontal alignment for each column in both the header and body. Columns without a provided alignment fall back to the default left alignment. - JSON / GoTemplate schema:
table.columnAlign: ["left", "center", "right"]
- Builder:
- Example tests:
_examples/{builder,json,gotemplate}/38_table_column_align_test.gowith shared golden
1.0.9 - 2026-05-06
- AutoRow/Row no longer splits across page boundaries — a row that does not fit in the remaining space on the current page now moves as a whole to the next page, instead of placing the columns that fit on the current page and re-rendering the full row on the next (#24)
template/grid.go:RowBuilder.build()now setsBreakInside=BreakAvoidon the horizontal Box so rows are treated as atomic layout unitsdocument/layout/block.go:layoutVerticalChildfalls back to a normal split when aBreakAvoidchild is the first node on a fresh page, preventing an infinite loop of empty pages when a row is taller than a full page
1.0.8 - 2026-05-06
- Padding support on Text elements following the CSS box model (#23)
- Builder:
template.TextPadding(Edges) - JSON / GoTemplate schema:
text.style.padding(uniform, e.g."10mm") andtext.style.paddings(CSS shorthand[top, right, bottom, left], 1–4 values) - Layout engine:
Style.PaddingandStyle.Borderparticipate in text flow — wrap width shrinks to the inner content area, lines are offset by the top/left inset, andBgColorfills the padded box (document/layout/flow.go)
- Builder:
- Example tests:
_examples/{builder,json,gotemplate}/36_text_padding_test.gowith shared golden
1.0.7 - 2026-04-29
- Borders and backgrounds for tables, images, and boxes
- Builder:
Border(opts...)withBorderWidth,BorderWidths,BorderColor,BorderLine; applied viaWithTableBorder,WithImageBorder,WithBoxBorder,WithTextBorder, plusWithImageBackground/ table & box background options - JSON / GoTemplate schema:
SchemaBorder { width, widths, color, style };image.border/image.background;table.border/table.cellBorder/table.borderCollapse/table.background - Layout engine support: borders participate in box-model sizing and pagination (
document/layout/{block,engine,paging}.go)
- Builder:
- Example tests:
_examples/{builder,json,gotemplate}/35_border_test.gowith shared golden
1.0.6 - 2026-04-20
- Minimum display size constraints for images and QR codes — raise layout overflow to the next page when the target box would render below
minWidth/minHeight(#19)- Builder:
MinDisplayWidth(v)/MinDisplayHeight(v)options on Image and QR - JSON / GoTemplate schema:
image.minWidth/image.minHeight/qr.minWidth/qr.minHeight - Layout engine propagates overflow when the constraint is violated (
document/layout/block.go)
- Builder:
- Example tests:
_examples/{builder,json,gotemplate}/34_image_min_size_test.gowith shared golden
1.0.5 - 2026-04-19
- Text alignment precision for Standard 14 fonts — right/center alignment now lands at the true container edge instead of drifting up to ~17pt for large bold text
template/fontresolver.go:MeasureStringandLineBreaknow use Adobe Core 14 AFM advance widths when no TTF is registered, instead of thecharCount × size × 0.5approximationResolvenow normalizes an emptyFontFamilytoHelvetica, matching the PDF renderer's default- Metrics (Ascender / Descender / CapHeight) from AFM are returned for Standard 14 fonts — previously hard-coded 0.8 / -0.2 / 0.7 fallback
pdf/font/standard14.go— Adobe Standard 14 font constants,IsStandard14,Standard14Metrics,Standard14Width,NewStandard14Fontpdf/font/standard14_data.go— AFM-derived width tables and metrics for Helvetica / Times / Courier / Symbol / ZapfDingbats families (14 fonts, printable ASCII coverage)- Tests:
pdf/font/standard14_test.go,template/fontresolver_test.go
1.0.4 - 2026-04-07
- AcroForm flatten support — flatten form fields into static content (#17)
1.0.3 - 2026-03-23
- Multi-page table support — tables inside Row/Col now automatically split across pages
layoutHorizontalpropagates child overflow to the paginator- Table headers repeat on each continuation page (existing
layoutTablelogic)
1.0.2 - 2026-03-23
- PDF document merging — combine pages from multiple PDFs into one (#11)
pdf.MergePDFs(): Core merge engine with object reference remappinggpdf.Merge(): High-level facade withSource,PageRange,WithMergeMetadata()pdf.Writer.AddRawPage(),PageTreeRef(): Raw page insertion support- Merge examples: basic merge, page range extraction, metadata, merge + overlay, issue #11 scenario
1.0.1 - 2026-03-22
- RFC 3161 timestamping for digital signatures
1.0.0 - 2026-03-22
- Existing PDF overlay — open, read, and modify existing PDFs
pdf.Reader: PDF parser with XRef table/stream parsing, page tree traversal, object cachingpdf.Modifier: Incremental Update engine (non-destructive append to existing PDF)template.ExistingDocument: High-level API withOverlay(),EachPage(),Save()gpdf.Open(): Facade entry point for opening existing PDFsrender.OverlayRenderer: Content stream capture for overlay rendering
- Overlay examples: text watermark, page numbers, stamps, confidential header, facade usage
0.9.0 - 2026-03-05
- Absolute positioning for placing elements at exact XY coordinates
textIndentandcellVAlignsupport in JSON/GoTemplate schema- Comprehensive English documentation for gpdf core
- CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md
- GitHub Issue templates (bug report, feature request) and Pull Request template
- CHANGELOG.md
- GoDoc enrichment with
doc.gofiles, missing comments, and example tests - Test coverage improved to 92.0%
- Moved Benchmark section after Features in all READMEs
- Unified architecture diagrams to English across all README translations
- Reduced cyclomatic complexity of
applySchemaStyle
- Stabilized golden tests by using version-independent Producer metadata
0.8.0 - 2026-03-03
- Image fit modes (contain, cover, fill, none)
- Image embedding from file paths
- PNG alpha transparency support
- JSON schema and Go template examples for all features
- Restructured
_examples/intobuilder/,json/,gotemplate/,component/subdirectories - Unified golden files across builder/json/gotemplate into shared directory
- Reduced cyclomatic complexity in
layoutImageandparseColor
0.7.0 - 2026-03-02
- Reusable components (Invoice, Report, Letter templates)
- Fuzz testing for all packages
- PDF output validation with pdfcpu
0.6.0 - 2026-03-02
- Go template integration (
gpdf.FromGoTemplate) - JSON schema generation (
gpdf.FromJSON)
- UTF-8 to WinAnsiEncoding conversion in PDF literal strings
0.5.0 - 2026-03-02
- Layer 1: PDF Primitives (Writer, XRef, Font, Stream, Image)
- Layer 2: Document Model (Node, Box, Style, Layout Engine)
- Layer 3: Template API (Builder, 12-column Grid, Components)
- CJK support (TrueType + CMap + subsetting)
- Tables with headers, column widths, striped rows, vertical alignment
- Headers & Footers with page numbers
- Multiple units (pt, mm, cm, in, em, %)
- Color spaces (RGB, Grayscale, CMYK)
- JPEG/PNG image embedding
- Document metadata (title, author, subject, creator)
- QR code generation with error correction levels
- Barcode generation (Code 128)
- Text decorations (underline, strikethrough, letter spacing, text indent)
- Lists (bulleted and numbered)
- Buildinfo package with version in PDF Producer metadata
- Benchmarks (10-30x faster than alternatives)
- CI/CD with GitHub Actions
- Multi-language READMEs (EN, JA, ZH, KO, ES, PT)
- Reed-Solomon coefficient order in QR code encoder
- binary.Write return value handling for errcheck lint