Repository navigation
Draw every layer of a multiple background (#609) - #723
Open
gravitypdf-bot wants to merge 2 commits into
Open
gravitypdf-bot wants to merge 2 commits into
gravitypdf-bot wants to merge 2 commits into
Conversation
A background or background-image with several comma-separated layers drew only its first layer, and the layer longhands were read as one value or dropped. In standard mode the shorthand and the longhands now keep a value for each layer, joined by BackgroundLayers so the list cascades like any other value, and SetBackground() reads every layer. Blocks, tables, rows, cells, body and @page paint the layers from the last to the first, over the colour of the last layer and under the clip of its background-clip. A single layer is read and painted as before, and legacy mode keeps drawing only the first layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isting helpers (#609) 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.
Fixes #609
Part of #552, part of #520
Problem
A
backgroundorbackground-imagewith several comma-separated layers drew only its first layer, and the layer longhands (background-repeat: no-repeat, repeat-xand the rest) were read as one value or dropped:Measured on
gravitypdfat384d5b5d, on a 60 × 30 mm block, from the patterns and shadings the page paints:backgroundurl(bg.jpg) no-repeat, linear-gradient(red, blue)linear-gradient(red, blue), url(bg.jpg)background-image: url(bg.jpg), url(bg.jpg); background-repeat: no-repeaturl(bg.jpg) no-repeat, #0f0linear-gradient(…), radial-gradient(…)background-position: right bottom, left topThe shorthand parser fixed before #532 (#583) already read every layer, but kept only the first. The longhands still matched the first keyword they found, and
SetBackground()and everything that paints its result held one image per box.Fix
Mpdf\Css\BackgroundLayers(new) keeps a longhand's list of per-layer values in one string, so it cascades, inherits and resolves CSS-wide keywords like any other value. The values are joined with\x1F, which no CSS value holds; a comma would not do, since a layer's image can hold commas (a data URI, a gradient's stops). A list of one is the value itself, so a single-layer document carries exactly the strings it did before.valueFor()gives a layer its value, repeating a shorter list as CSS does.NormalizeProperties, in standard mode:backgroundnow sets every longhand to the list of its layers' values, each with its initial value where the layer leaves it out, and the colour from the last layer only.background-image,-repeat,-position,-size,-originand-clipsplit on commas outside parentheses (sorgb(…), gradient arguments andurl()s stay whole) and read each item the way the shorthand reads that part; a list with an item that is not a value of the property is dropped, as a browser drops it. A value with no top-level comma takes the same path as before.parseCssBackground()becameparseBackgroundLayers(), which returns every layer instead of the first. The initial values come fromCssWideKeywords::initialValue(), and the size check the shorthand andbackground-sizeshare isisBackgroundSize().Mpdf::SetBackground()reads each layer throughbackgroundLayer()(the old body, unchanged), giving each its own repeat, position, size and origin from the lists. One drawable layer returns the same array as before; several return['layers' => [...]], top first.body/@page, blocks inPaintDivBB(), tables twice, rows and cells in_tableWrite(), and repeated header/footer rows and cells inTableHeaderFooter()) loop overbackgroundLayers(), which gives the layers bottom first, or the single background on its own. The loop bodies are the old code with$x['background-image']read as$bg. Table backgrounds go into slots that paint every gradient before every image, so a list's gradients go into its images' slot to keep the authored order; a single background keeps its old slots. InPaintDivBB()each layer starts from the whole box, since the gradient branch narrows$wand$hto its origin box.background-clip. mPDF clips a box's colour and images to one path. With a list,setCSS()takes the value of the bottom layer, the one the colour uses, as the issue says (draw the layers "under the same clip").Nothing here touches the shadow or clip-path building in
PaintDivBB()orBlockTag; the change inPaintDivBB()is in the background-image part after them. I expect textual conflicts with #714, #715 and #719 only if they touch those last lines ofPaintDivBB().Legacy mode
Under
'cssMode' => 'legacy'the longhands are read as before and the shorthand gives the first layer only, soSetBackground()never sees a list and every painting loop runs once over the same array as before. I checked byte-identical output againstgravitypdffor seven documents (layered and single backgrounds on blocks, tables, rows, cells,bodyand@page) in legacy mode, and for the single-layer ones in standard mode too.Tests
tests/Mpdf/MultipleBackgroundsTest.phpreads, in paint order, the shadings and image patterns the first page paints (shading type for a gradient; steps, box and placement for an image):nonelayer that is skipped;background-repeatvalues and per-layerbackground-positionandbackground-size: the third layer takes the first repeat again;@page;background-clipclipping the box;tests/Mpdf/Css/NormalizePropertiesTest.phpcovers each longhand's list (including a data URI with commas in it), the lists that are dropped, the shorthand's per-layer values, and legacy mode reading only the top layer.With the
src/changes reverted, 13 of the 15MultipleBackgroundsTesttests fail; the image over a colour and the legacy case pass, as they should.tests/Snapshots/MultipleBackgroundsSnapshotTest.php(fixturemultiple-backgrounds) draws each case under a caption saying what should be seen, on blocks and a table cell, and a named@pagewith an image over a gradient on page 2. Against the pre-fixsrc/it fails on both pages (RMSE 0.22 and 0.90).The
background-shorthandfixture's "Two layers" caption said "mPDF draws only the first layer"; I reworded it, which shortens it to one line and moves the cases below it up. Its drawing is unchanged.Checks
composer test: OK, 12762 tests, 31 skipped (the same skips as ongravitypdf); the newmultiple-backgroundsfixture is added andbackground-shorthandis refreshed for its captioncomposer cs: clean--memory-limit=2G): the same 23 errors as ongravitypdf(the out-of-date baseline Regenerate the PHPStan baseline for PHPStan 2.3, so the Lint job passes again #716 regenerates), none newphp -lon PHP 5.6 (docker) for each changed PHP file: no syntax errors🤖 Generated with Claude Code