Repository navigation
Hold the bytes of a vector image only when they were fetched - #869
Draft
gravitypdf-bot wants to merge 1 commit into
Draft
gravitypdf-bot wants to merge 1 commit into
gravitypdf-bot wants to merge 1 commit into
Conversation
A data URI or an imageVars entry brings its bytes on every call, so the copy kept for the next pass is not needed. getImage() writes the md5 of those bytes back to $file, so tests pin that no route asks for that md5 once an unwound pass has removed the form object, and list every call to getImage(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gravitypdf-bot
force-pushed
the
fix/860-vector-look-ahead-cost
branch
from
November 6, 2026 07:59
cda5131 to
eaff6df
Compare
jakejackson1
marked this pull request as draft
November 6, 2026 08:34
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.
Fixes #860, with the small option only. The benchmarks below show the other two options aren't worth their risk.
The change
ImageProcessor::$vectorDataheld the bytes of every SVG and WMF until the document ended, so a later pass could parse them again without fetching them again. A data URI orimageVarsimage brings its bytes on every call, so that copy was never needed.getImage()now holds only what the fetcher returned.A fetched file is still held, and still fetched once. Output doesn't change.
Is the copy really unused?
getImage()rewrites$fileto the md5 by reference, so a later call with the bare md5 would have needed the stored bytes. Reading every caller:Tag/Img.php,Tag/Input.php,Tag/Meter.phpand_setListMarker()keep the md5 in$objattr['file'], which the print code only uses to indexformobjectsandimages. It never goes back togetImage().Image()takes$fileby value, so backgrounds and watermarks don't see the md5.getImage($this->noImageFile)passes a public property by reference, so a data URI orimageVarsimage there does become its md5, and later calls pass that. The snapshot holds the property andformobjectsalike, so they unwind together and the md5 never outlives its form object.VectorImageSourceTestpins this in CI:<img>(alone, in tables, in a repeating header), block backgrounds, list markers and watermarks, each through a moved kept block and through the contents with an index, from a data URI and fromimageVars. A data URI orimageVarsimage is never fetched, and the same routes with the image innoImageFilenever fetch its md5.noImageFileis restored with its form object.srclists everygetImage()call, so a new one fails until it's checked.I confirmed these fail when they should, by breaking a copy of the code: leaving
noImageFileout of the snapshot fails 21 tests, and makingTag/Img.phpkeep the md5 and pass it on the next pass fails the<img>routes. A newgetImage()call failed the scan in the same way on the previous revision of this file.What they can't do is prove the absence of a route nobody has written. The scan sees calls, not an existing call's
$filebeing stored somewhere new.<input type="image">isn't in the routes either: with an SVG it raisesUndefined array key "cs"with or without this change, which is a separate bug. Its call is in the scan.I also ran the full suite once on a copy of the code from before this change that logs each read of a stored md5, and none occurred. 58 extra documents, 29 for each source, gave identical PDF hashes with and without the change.
The issue also asked whether a data URI is held twice, as key and value. It isn't: the key is the md5.
Memory
Fetched bytes only, against current
gravitypdf. Hot font cache, 10 runs each, PHP 8.5. Time and PDF size didn't change.toc-preHTML+ indextoc-preHTML+ indextoc-preHTML+ indexThese figures come from a scratch copy with the same store-only-fetched change, not this exact commit. The SVGs are synthetic.
Why not the rest of #860
Hot font cache, 10 runs each, one process per run. "Before" is
81a78b6f, the first parent of the #857 merge; "after" is9e1090e7. Before isn't a fair baseline: its output has the bug, with the gradients missing from most of these documents, so it does less work. Other agents were running, so timings carry tens of ms of noise.toc-preHTML+ indextoc-preHTML+ indextoc-preHTML+ indextoc-preHTML+ indexReal-world SVGs, other PHP versions, and SVGs with text or embedded images weren't measured.
Tests
testAnSvgIsHeldOnlyWhenItWasFetchedrenders through an unwound pass from a data URI, animageVarsentry and a file, checking what's held and that the gradient survives. It fails without the fix.VectorImageSourceTestis described above. There's no snapshot, as this doesn't change rendering.Run locally on PHP 8.5:
composer test(13579 tests, 31 skipped) andcomposer cspass, and PHPStan 2.2.17 reports nothing forImageProcessor, though CI pins another version. PHP 5.6 wasn't run locally, so that's left to CI.