Repository navigation
Save a picture from a PDF with the context menu - #1144
Merged
Merged
Conversation
A PDF names its picture by where its object sits rather than by a path, so the image lines the parser wrote carried no reference at all and the context menu's "Save image as..." never appeared on one. The marker now carries `pdf:<page>:<content-stream path>`, written for an untagged page's image objects, for the images a tagged page's tree never claims, and for a real Figure element's picture (found through the marked-content ids its content names). The picture is read back through pdfium at the resolution the file stores it in, converted from whichever bitmap layout pdfium hands back, and written as PNG; a protected file is reopened with the password the session was given. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
The context menu's Save image as… item, added for pictures in EPUB, HTML, Markdown, CHM, FB2 and
comic books, never appears in a PDF. The reader still announces the picture —
[Image]or[Figure: description]— andF/Gnavigation stops on it, so the file plainly knows where thepicture is; it just had no way to name it.
The other formats name a picture by a path: an entry in the archive, a file beside the HTML, or a
data:URI. A PDF has none of those. Its pictures are objects in a page's content stream, reachedthrough a form
XObjector not, so the marker the parser writes carried an empty reference andimage_file_name_atreturnedNone. The menu item is only added when that returnsSome, which iswhy it was missing rather than failing when pressed.
The change
The marker now names the picture.
parser/pdf/images.rs'sappend_imagetakes a reference andstores it on the marker. Every image line the PDF parser writes gets one:
pdf:<page>:<path>, where a path is slash-separated indices:0is the page's first top-levelobject,
0/2the third object inside it when it is a form. The parser already had the top edgeand the path of each announced image (
PageContent::image_paths), so the line it wrote can nowsay which one it is.
(
UnclaimedImagesnow carries tops and paths).Figureelement's picture by marked-content id(
structure.rs).find_image_pathwalks the element's subtree for a leaf id that is on an imageobject, which is how pdfium reports the association (
FPDFPageObj_GetMarkedContentIDand theMCIDparameter on each mark). The lookup covers images Paperback does not announce, such as apage-sized picture, so a figure whose
/Altdescribes a full-page illustration can still save it.PdfPage::image_paths_by_mcidbuilds it, and the walk only builds that map when the tree containsa figure at all.
The picture comes out of pdfium at its stored resolution.
pdfium/page.rsgainedPdfPage::image_png(path), which resolves the path throughFPDFPage_GetObjectandFPDFFormObj_GetObject, then renders the object withFPDFImageObj_GetBitmap— ignoring the pagematrix, so a photo placed small still saves at full size — and converts whatever layout pdfium
returns (BGRA, BGRx, BGR or Gray, each with its own stride padding) into RGBA before encoding PNG.
The image is re-encoded rather than passed through as the file's original JPEG/Flate bytes, because
FPDFImageObj_GetImageDataRawreturns the stream still filtered rather than a viewable file.Saving in
session/images.rsbranches on thepdf:reference before the document extension,since the reference itself says which reader is needed. It reopens the document to read the
picture;
DocumentSessionnow keeps the password it was opened with so a protected file can belooked at a second time. The save dialog offers a name like
image 25 453.png— the translated stem,the 1-based page number and the object path — so several pictures from one book do not collide.
doc/readme.mdlists PDFs among the formats whose pictures can be saved. No new translatablestrings: the menu items and the
imagefile-name stem are the existing ones.What it does not do
bytes inside the file.
/SMaskor a stencil mask) is not applied. Pdfium keeps the mask as a separateobject; the picture on its own is what was asked for, and a masked image with transparency saves
with the transparent pixels opaque.
Figurewhose content names no image on the page still gets its line and its description, butno menu item, because there is nothing to write.
Testing
demo.pdfpage 25 has an image at the top; saving it produced a 401×168 PNG of the"Chapter 2 / The place from where we started" heading graphic, which renders correctly.
demo.pdfeach wrote a valid PNG. That file isuntagged, so it exercises the content-stream path.
/Figureelement wraps a 2×2 red image with/Alt (A red square)reads as[Figure: A red square]and saves a 2×2 red PNG, which exercises themarked-content-id path.
cargo test -p paperback-core: 1500 passed, 0 failed. New unit tests cover the reference storedon the marker and the PDF file name; the bitmap conversion tests cover BGRA, BGR, Gray and padded
rows.
cargo +nightly-2026-09-27 fmt --checkand clippy are clean for the touched crates.🤖 Generated with Claude Code