Skip to content

Draw a page of an existing PDF like an image, or under every page - #102

Merged
czj merged 1 commit into
mainfrom
claude/confident-pasteur-jz0fj6
Nov 13, 2026
Merged

czj merged 1 commit into
mainfrom
claude/confident-pasteur-jz0fj6

Conversation

@czj

@czj czj commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Companies keep their letterhead and pre-printed forms as PDFs made by a designer, and drawing on top of one is among the most common requests to a PDF library; Prawn dropped its template support in 1.0. Gamba.combine could read those files, but only to put whole pages one after another. Closes #73.

pdf_page draws a page of another PDF with the options image takes, and background_pdf puts one under every page. Each page becomes a form XObject that shows what a viewer shows of it.

  • Placed like images: pdf_page "terms.pdf", page: 2, width: 200 takes a path, an IO or the bytes of a PDF, with at, position, vposition, width, height, scale and fit.
  • Page backgrounds: Prawn::Document.new(background_pdf: "letterhead.pdf", background_pdf_page: 1) draws it at its own size from the top left corner; start_new_page(background_pdf: …) names another for one page, or false for none.
  • Written once: a page drawn on a thousand pages is one form, and pages of one file share their fonts and images. The crop box and /Rotate are applied; several content streams are joined, wherever they were divided.
  • Same defences: the reader and limits of Gamba.combine, with the same errors. Under pdf_a: true, a page whose fonts are not embedded raises PdfAViolation, and the input fuzzer now corrupts PDFs too.
  • Native API: background_pdf and background_pdf_page as document options, and pdf_page as a box sized like an image. Object copying moves out of Combine into Gamba::Copier and Gamba::PageBoxes, which both use.
Proof

test/pdf_page_test.rb draws a page at its size, a page turned by /Rotate and a cropped, turned page, and compares each with the page shown alone: same pixels in MuPDF and Poppler, and qpdf --check passes. Fifty pages with a background and one more page of the same file write two forms and one font. Other tests cover backgrounds per page, content split between an operator and its operands, errors (page out of range, missing file, not a PDF, password), PDF/A with Helvetica (raises) and with an embedded TrueType font (passes), and the native box. 4,500 corrupted PDFs drawn as pages, on three seeds, each render or raise MalformedPDF. rake test: 1345 runs, 0 failures; RuboCop is clean; combine_test.rb is unchanged and passes.


Generated by Claude Code

pdf_page draws a page of another PDF (a path, an IO or its bytes) with the options image takes, and background_pdf puts one under every page, start_new_page naming another or none. The page is a form XObject showing its crop box turned by its /Rotate, written once however many pages draw it; pages of one file share their resources. Under pdf_a, a page whose fonts are not embedded raises. Gamba's own API takes background_pdf as a document option and pdf_page as a box.

The object copying of Gamba.combine moves to Gamba::Copier and its page boxes to Gamba::PageBoxes, both shared with the import. The input fuzzer now corrupts PDFs too.

Assisted-by: Claude [Claude Code]
@czj
czj merged commit 196bbf9 into main Nov 13, 2026
8 checks passed
@czj
czj deleted the claude/confident-pasteur-jz0fj6 branch November 13, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A page of an existing PDF cannot serve as a background

2 participants