Let an embedded SVG keep the class it was styled by (mirrors mpdf/mpdf#1405) - #41
Merged
Merged
Conversation
AdjustHTML() writes an embedded SVG out to a file and replaces it with an img element pointing at that file. Everything the svg tag carried is dropped on the way, so a stylesheet written against `.chart` has nothing left to match and the picture is drawn at whatever size the SVG asks for. There is no other way to style one embedded SVG differently from the next: the img elements the rest of the document uses all look the same to a selector. Read the class off the svg tag and put it on the img. The value is read in the three forms an attribute value may take, the way the rest of the parser reads one, and a double quote is dropped from it because no class name can hold one and it would end the attribute early. Only the svg tag's own class is taken - a class on a shape inside it belongs to the SVG reader and has nothing to do with the img element. All twenty existing snapshots are unchanged. An SVG written with unquoted attribute values is not well-formed XML, so the SVG reader cannot draw it whatever class it carries; that form is covered in the unit test but left out of the snapshot. mpdf#1405 reads the value with a single expression built around lookbehinds that does not cover the unquoted form. Co-authored-by: Claude Opus 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.
Summary
This mirrors mpdf/mpdf#1405, so the fork carries the fix while the upstream PR sits open. It closes mpdf/mpdf#1404.
AdjustHTML()writes an embedded SVG out to a file and replaces it with an<img>pointing at that file:Everything the
svgtag carried is dropped on the way. A stylesheet written against.charthas nothing left to match, so the picture is drawn at whatever size the SVG asks for and there is no way to say anything about it. Stylingimginstead reaches every image in the document, which is the workaround the reporter was left with.Read the class off the
svgtag and put it on theimg.Try it
Before: the circle, with no frame — the rule matched nothing.
After: the circle inside a red frame.
Test plan
tests/Mpdf/SvgClassTest.php, eight cases. Six read the<img>thatAdjustHTML()produced directly, which is narrower than rendering and says exactly what was carried over.testTheClassIsCarriedOverToTheImgover four writings of the attribute — double quoted, single quoted, unquoted, and with spaces around the=. All four fail ongravitypdf, which produces an<img>with no class at all.testAnSvgWithNoClassBecomesAnImgWithNoneandtestTheImgIsStillWrittenWhenTheClassIsEmpty— controls; no stray empty attribute either way.testAClassOnSomethingInsideTheSvgIsNotTaken— aclasson acircleinside the SVG belongs to the SVG reader and must not be lifted onto the image. Passes on both, and is what holds the pattern anchored to the opening tag.testAStyleRuleWrittenForTheClassReachesTheImage— renders and asserts the red stroke is in the content stream. Fails ongravitypdf.tests/Snapshots/SvgClassSnapshotTest.phpdraws the same circle five times: with no class, with a border and padding, with a width, with a width in a single-quoted attribute, and with three classes at once including a left margin. The document istests/data/snapshots/svg-class.pdf. Rendered ongravitypdfit differs by 65,187 pixels — every one of them is drawn at its native size with nothing applied.gravitypdfare byte-identical once the creation date and the random/IDare normalised, and the new one compares at zero differing pixels against a fresh render. The temporary SVG filename is random per run but does not reach the PDF, so two renders are byte-identical as well.composer test— 1130 tests, 2650 assertions, up from 1122/2643.composer csclean.phpstanoutput identical togravitypdf.More info — reading the attribute, and the unquoted form
The pattern
The three alternatives are the same three the fork's own attribute reader uses, from
AdjustHTML():^<svg\b[^>]*?cannot cross the>, so only the opening tag is looked at. A double quote is dropped from the value because no CSS class name can hold one and it would end the attribute early.mpdf/mpdf#1405reads it with one expression built around lookbehinds:which its own test notes does not handle the unquoted form.
The unquoted form
An SVG written with unquoted attribute values is not well-formed XML, so the SVG reader cannot parse the file whatever class was lifted off it — with
class=narrowin the document,Svg.phpreports four undefined keys andTag\Imgthen divides by zero. The class is read correctly, whichtestTheClassIsCarriedOverToTheImgcovers, but the snapshot leaves that form out because there would be no picture to look at.Only class
The issue asks for
class, and that is all this carries.idandstyleare dropped the same way, but a selector is the thing there is no substitute for:img { … }reaches every image in the document, whereas an inline style on thesvgtag could be moved onto a wrapper by hand.