diff --git a/CHANGELOG-GRAVITYPDF.md b/CHANGELOG-GRAVITYPDF.md
index a674b3d97..35468e8cb 100644
--- a/CHANGELOG-GRAVITYPDF.md
+++ b/CHANGELOG-GRAVITYPDF.md
@@ -80,6 +80,9 @@ Read this section before upgrading from upstream mPDF. Each entry says what chan
* A bidirectional embedding, as in `……
…`, is opened again for the inline element's text after the block.
* **Stylesheet rules that name `sup`, `sub` or `center` apply**, in both CSS modes. mPDF left the three tags out of `allowedCSStags`, so `sup { color: red }`, `p sub { … }` and `center { … }` did nothing while a class rule on the same element worked. A rule's `font-size` and `vertical-align` take the place of the 55% size and the raise or drop mPDF gives the tags by default, rather than adding to them, so normalize.css's `sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline }` now draws them at 75% on the baseline. To keep ignoring such rules, pass an `allowedCSStags` without `SUP`, `SUB` and `CENTER`. [#649] [#680]
* **`min-width` and `max-width` hold the width of a block**, as in a browser, in standard mode. mPDF never read them on a block, so `max-width: 100mm; margin: 0 auto`, the usual centred container, filled the page, `width: 150mm; max-width: 100mm` was 150mm wide, and `width: 50mm; min-width: 80mm` was 50mm. Now the width is held within the two, `max-width` first, then `min-width`, a percentage taken of the container's inner width as `width` is, and the block's auto margins, direction or float place the narrowed box. Floats and positioned blocks take the same limits. Set `'cssMode' => \Mpdf\CssMode::LEGACY` to ignore the two properties as before. [#588] [#691]
+* **A background with several comma-separated layers draws every layer**, as in a browser, in standard mode. mPDF drew only the first, so `background: url(logo.png) no-repeat, linear-gradient(red, blue)` lost the gradient and `background-image: url(a.png), url(b.png)` lost `b.png`. Now each layer is drawn, the first on top and the last at the bottom, over the colour the last layer names, on blocks, tables, rows, cells, `body` and `@page`. Set `'cssMode' => \Mpdf\CssMode::LEGACY` to draw only the first layer as before. [#552] [#609]
+ * `background-image`, `-repeat`, `-position`, `-size`, `-origin` and `-clip` take a comma-separated list, one value per layer. A list shorter than the list of images repeats, and a longer one is cut short, so `background-position: right bottom, left top` with one image places it at the bottom right; before, the list was dropped.
+ * Every layer is clipped to the box the last layer's `background-clip` names, the one the colour takes.
* `box-sizing` is not read, so the limits measure the content, and a block with padding and borders is wider by them. `min-width` and `max-width` on a table or cell are still not read.
New features
@@ -965,3 +968,4 @@ These changes do not change output.
[#691]: https://github.com/GravityPDF/mpdf/pull/691
[#584]: https://github.com/GravityPDF/mpdf/issues/584
[#689]: https://github.com/GravityPDF/mpdf/pull/689
+[#609]: https://github.com/GravityPDF/mpdf/issues/609
diff --git a/src/Css/BackgroundLayers.php b/src/Css/BackgroundLayers.php
new file mode 100644
index 000000000..8061e7130
--- /dev/null
+++ b/src/Css/BackgroundLayers.php
@@ -0,0 +1,67 @@
+ 'i',
+ 'BACKGROUND-REPEAT' => 'r',
+ 'BACKGROUND-POSITION' => 'p',
+ 'BACKGROUND-SIZE' => 's',
+ 'BACKGROUND-ORIGIN' => 'o',
+ 'BACKGROUND-CLIP' => 'k',
+ ];
+
/**
* @var array
*/
@@ -234,7 +247,7 @@ public function normalize($prop)
$this->processPageSizeProperty($k, $v);
} elseif ($k === 'BREAK-BEFORE' || $k === 'BREAK-AFTER' || $k === 'BREAK-INSIDE') {
$this->processBreakProperty($k, $v);
- } elseif (in_array($k, ['BACKGROUND', 'BACKGROUND-IMAGE', 'BACKGROUND-REPEAT', 'BACKGROUND-POSITION'], true)) {
+ } elseif ($k === 'BACKGROUND' || array_key_exists($k, self::LAYERED_LONGHANDS)) {
$this->processBackgroundProperty($k, $v);
} elseif ($k === 'IMAGE-ORIENTATION') {
$this->processImageOrientationProperty($v);
@@ -569,23 +582,16 @@ protected function processBorderShorthandProperty($key, $value)
* Parse CSS background shorthand property.
*
* Reads the colour, image, position with an optional "/ size", repeat, attachment and boxes of each
- * comma-separated layer, in any order. Only the first layer is drawn, over the colour, which only the
- * last layer may carry.
+ * comma-separated layer, in any order. Only the last layer may carry the colour.
*
* @param string $s Background property value
- * @return array|false The parts given, keyed 'c' (color), 'i' (image), 'r' (repeat), 'p' (position),
- * 's' (size), 'o' (origin) and 'k' (clip), or false when the value is not a background
+ * @return array[]|false The parts of each layer, the top layer first, keyed 'c' (color), 'i' (image), 'r' (repeat),
+ * 'p' (position), 's' (size), 'o' (origin) and 'k' (clip); an empty list for a CSS-wide
+ * keyword; or false when the value is not a background
*/
- protected function parseCssBackground($s)
+ private function parseBackgroundLayers($s)
{
- $layers = [[]];
- foreach ($this->splitComponents($s) as $component) {
- if ($component === ',') {
- $layers[] = [];
- } else {
- $layers[count($layers) - 1][] = $component;
- }
- }
+ $layers = $this->splitLayers($s);
// A CSS-wide keyword gives the initial value, no background. The standard CSS mode passes it to the longhands
// before this, for CssMerger to resolve
@@ -601,11 +607,27 @@ protected function parseCssBackground($s)
}
}
- if (isset($layers[$last]['c'])) {
- $layers[0]['c'] = $layers[$last]['c'];
+ return $layers;
+ }
+
+ /**
+ * Splits a value at the commas outside its function calls and quoted strings
+ *
+ * @param string $value
+ * @return string[][] The components of each comma-separated part
+ */
+ private function splitLayers($value)
+ {
+ $layers = [[]];
+ foreach ($this->splitComponents($value) as $component) {
+ if ($component === ',') {
+ $layers[] = [];
+ } else {
+ $layers[count($layers) - 1][] = $component;
+ }
}
- return $layers[0];
+ return $layers;
}
/**
@@ -613,7 +635,7 @@ protected function parseCssBackground($s)
* A single box keyword sets both the origin and the clip.
*
* @param string[] $components
- * @return array|false The parts given, keyed as parseCssBackground() returns them, or false when a component is none of them
+ * @return array|false The parts given, keyed as parseBackgroundLayers() returns them, or false when a component is none of them
*/
private function parseBackgroundLayer(array $components)
{
@@ -645,14 +667,11 @@ private function parseBackgroundLayer(array $components)
if ($i + 1 < $count && $components[$i + 1] === '/') {
$i += 2;
$size = isset($components[$i]) ? [strtolower($components[$i])] : [];
- if ($size !== ['cover'] && $size !== ['contain']) {
- $sizeLength = '/^(auto|' . self::NUMBER . '([a-z]+|%)?)$/';
- if (!$size || !preg_match($sizeLength, $size[0])) {
- return false;
- }
- if ($i + 1 < $count && preg_match($sizeLength, strtolower($components[$i + 1]))) {
- $size[] = strtolower($components[++$i]);
- }
+ if (!$this->isBackgroundSize($size)) {
+ return false;
+ }
+ if ($size !== ['cover'] && $size !== ['contain'] && $i + 1 < $count && $this->isBackgroundSize([$size[0], strtolower($components[$i + 1])])) {
+ $size[] = strtolower($components[++$i]);
}
$layer['s'] = implode(' ', $size);
}
@@ -707,6 +726,19 @@ private function isBackgroundPosition($component)
return preg_match('/^(left|right|top|bottom|center|[+-]?' . self::NUMBER . '([a-z]+|%)?)$/', $component) === 1;
}
+ /**
+ * Whether components are a background size: cover, contain, or one or two lengths, percentages or auto
+ *
+ * @param string[] $components Lowercased components
+ * @return bool
+ */
+ private function isBackgroundSize(array $components)
+ {
+ $length = '(auto|' . self::NUMBER . '([a-z]+|%)?)';
+
+ return preg_match('/^(cover|contain|' . $length . '( ' . $length . ')?)$/', implode(' ', $components)) === 1;
+ }
+
/**
* Whether a component of a shorthand is a colour
*
@@ -1039,7 +1071,7 @@ private function dropsNegativeLineHeight()
* Sets only the parts a background shorthand names, besides its colour and image, as mPDF v7 read it: the
* repeat, position and size only with an image, which they have nothing to apply to without
*
- * @param array $bg The shorthand as parseCssBackground() reads it
+ * @param array $bg The top layer of the shorthand, as parseBackgroundLayers() reads it
* @return void
*/
private function mergeBackgroundParts(array $bg)
@@ -1065,7 +1097,8 @@ private function mergeBackgroundParts(array $bg)
/**
* Process background related CSS properties.
*
- * Handles BACKGROUND, BACKGROUND-IMAGE, BACKGROUND-REPEAT, and BACKGROUND-POSITION.
+ * Handles BACKGROUND and its longhands. The standard CSS mode reads a comma-separated list of layers into each
+ * of them, kept as BackgroundLayers joins them.
*
* @param string $property Property name
* @param string $value Property value
@@ -1073,29 +1106,42 @@ private function mergeBackgroundParts(array $bg)
*/
protected function processBackgroundProperty($property, $value)
{
+ if ($property !== 'BACKGROUND' && $this->mpdf->cssMode === CssMode::STANDARD && strpos($value, ',') !== false) {
+ $layers = $this->splitLayers($value);
+ if (count($layers) > 1) {
+ $this->processLayeredBackgroundProperty($property, $layers);
+ return;
+ }
+ }
+
switch ($property) {
case 'BACKGROUND':
- $bg = $this->parseCssBackground($value);
+ $layers = $this->parseBackgroundLayers($value);
// A value that is not a background is dropped, as a browser drops it
- if ($bg === false) {
+ if ($layers === false) {
break;
}
- $this->properties['BACKGROUND-COLOR'] = isset($bg['c']) ? $bg['c'] : 'transparent';
- $this->properties['BACKGROUND-IMAGE'] = isset($bg['i']) ? $bg['i'] : '';
+ $layers = $layers ?: [[]];
+ $bottom = $layers[count($layers) - 1];
+ $this->properties['BACKGROUND-COLOR'] = isset($bottom['c']) ? $bottom['c'] : 'transparent';
if (!$this->resetsOmittedParts()) {
- $this->mergeBackgroundParts($bg);
+ // mPDF v7 drew only the top layer
+ $this->properties['BACKGROUND-IMAGE'] = isset($layers[0]['i']) ? $layers[0]['i'] : '';
+ $this->mergeBackgroundParts($layers[0]);
break;
}
// A part not given takes its initial value, replacing a longhand given before it in the cascade
- $this->properties['BACKGROUND-REPEAT'] = isset($bg['r']) ? $bg['r'] : 'repeat';
- $this->properties['BACKGROUND-POSITION'] = !empty($bg['p']) ? $bg['p'] : '0% 0%';
- $this->properties['BACKGROUND-SIZE'] = isset($bg['s']) ? $bg['s'] : 'auto';
- $this->properties['BACKGROUND-ORIGIN'] = isset($bg['o']) ? $bg['o'] : 'padding-box';
- $this->properties['BACKGROUND-CLIP'] = isset($bg['k']) ? $bg['k'] : 'border-box';
+ foreach (self::LAYERED_LONGHANDS as $longhand => $part) {
+ $values = [];
+ foreach ($layers as $layer) {
+ $values[] = isset($layer[$part]) && $layer[$part] !== false ? $layer[$part] : CssWideKeywords::initialValue($longhand);
+ }
+ $this->properties[$longhand] = BackgroundLayers::join($values);
+ }
break;
case 'BACKGROUND-IMAGE':
@@ -1124,9 +1170,59 @@ protected function processBackgroundProperty($property, $value)
$this->properties['BACKGROUND-POSITION'] = $normalizedPosition;
}
break;
+
+ default:
+ $this->properties[$property] = $value;
}
}
+ /**
+ * Sets a background longhand that lists a value for each layer. The list is dropped when one of its values is not
+ * a value of the property, as a browser drops it
+ *
+ * @param string $property Property name
+ * @param string[][] $layers The components of each layer's value
+ * @return void
+ */
+ private function processLayeredBackgroundProperty($property, array $layers)
+ {
+ $values = [];
+ foreach ($layers as $components) {
+ $value = $this->layerValue($property, $components);
+ if ($value === false) {
+ return;
+ }
+ $values[] = $value;
+ }
+
+ $this->properties[$property] = BackgroundLayers::join($values);
+ }
+
+ /**
+ * Reads one layer's value of a background longhand, the way the shorthand reads that part
+ *
+ * @param string $property Property name
+ * @param string[] $components
+ * @return string|false The value, or false when it is not a value of the property
+ */
+ private function layerValue($property, array $components)
+ {
+ if ($property === 'BACKGROUND-SIZE') {
+ return $this->isBackgroundSize($components) ? implode(' ', $components) : false;
+ }
+
+ $part = self::LAYERED_LONGHANDS[$property];
+ $layer = $this->parseBackgroundLayer($components);
+ if (!isset($layer[$part]) || $layer[$part] === false) {
+ return false;
+ }
+
+ // One box keyword sets both the origin and the clip in the shorthand; a longhand takes one keyword
+ $expected = $part === 'o' || $part === 'k' ? ['o' => $layer[$part], 'k' => $layer[$part]] : [$part => $layer[$part]];
+
+ return $layer == $expected ? $layer[$part] : false;
+ }
+
/**
* Process border radius property.
*
diff --git a/src/Mpdf.php b/src/Mpdf.php
index b700f3353..52fb36958 100644
--- a/src/Mpdf.php
+++ b/src/Mpdf.php
@@ -5,6 +5,7 @@
use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables;
use Mpdf\Conversion;
+use Mpdf\Css\BackgroundLayers;
use Mpdf\Css\Border;
use Mpdf\Css\BorderRadius;
use Mpdf\Css\CommentParser;
@@ -2822,7 +2823,64 @@ function _resizeBackgroundImage($imw, $imh, $cw, $ch, $resize, $repx, $repy, $pb
return [$imw, $imh, $repx, $repy];
}
+ /**
+ * Reads an element's background image or images
+ *
+ * @param array $properties The element's CSS
+ * @param float $maxwidth The width percentages and font-relative lengths are read against
+ * @return array|false What backgroundLayer() returns for a background of one layer; for more than one, the same of
+ * each layer drawn, the top layer first, under the key 'layers'; or false when none is drawn
+ */
function SetBackground(&$properties, &$maxwidth)
+ {
+ $layers = [];
+ $images = BackgroundLayers::split($properties['BACKGROUND-IMAGE']);
+ foreach ($images as $n => $image) {
+ if ($image === '') {
+ continue;
+ }
+
+ $layer = $properties;
+ $layer['BACKGROUND-IMAGE'] = $image;
+ // The clip is the bottom layer's for every layer, which setCSS() reads
+ foreach (['BACKGROUND-REPEAT', 'BACKGROUND-POSITION', 'BACKGROUND-SIZE', 'BACKGROUND-ORIGIN'] as $property) {
+ if (isset($properties[$property])) {
+ $layer[$property] = BackgroundLayers::valueFor($properties[$property], $n);
+ }
+ }
+
+ $layer = $this->backgroundLayer($layer, $maxwidth);
+ if ($layer) {
+ $layers[] = $layer;
+ }
+ }
+
+ if (count($layers) < 2) {
+ return $layers ? $layers[0] : false;
+ }
+
+ return ['layers' => $layers];
+ }
+
+ /**
+ * The layers of a background SetBackground() read, in the order they are painted: the bottom layer first
+ *
+ * @param array $background
+ * @return array[]
+ */
+ private function backgroundLayers(array $background)
+ {
+ return isset($background['layers']) ? array_reverse($background['layers']) : [$background];
+ }
+
+ /**
+ * Reads one layer of an element's background
+ *
+ * @param array $properties The element's CSS, with the layer's values
+ * @param float $maxwidth The width percentages and font-relative lengths are read against
+ * @return array|false The gradient, or the image and how it is placed, or false when the image cannot be read
+ */
+ private function backgroundLayer(array $properties, $maxwidth)
{
if (isset($properties['BACKGROUND-ORIGIN']) && ($properties['BACKGROUND-ORIGIN'] == 'border-box' || $properties['BACKGROUND-ORIGIN'] == 'content-box')) {
$origin = $properties['BACKGROUND-ORIGIN'];
@@ -2951,23 +3009,25 @@ function PrintBodyBackgrounds()
}
}
if ($this->bodyBackgroundImage) {
- if (isset($this->bodyBackgroundImage['gradient']) && $this->bodyBackgroundImage['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $this->bodyBackgroundImage['gradient'])) {
- $g = $this->gradient->parseMozGradient($this->bodyBackgroundImage['gradient']);
- if ($g) {
- $s .= $this->gradient->Gradient($clx, $cly, $clw, $clh, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend'], true);
- }
- } elseif ($this->bodyBackgroundImage['image_id']) { // Background pattern
- $n = count($this->patterns) + 1;
- // If using resize, uses TrimBox (not including the bleed)
- list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($this->bodyBackgroundImage['orig_w'], $this->bodyBackgroundImage['orig_h'], $clw, $clh, $this->bodyBackgroundImage['resize'], $this->bodyBackgroundImage['x_repeat'], $this->bodyBackgroundImage['y_repeat']);
+ foreach ($this->backgroundLayers($this->bodyBackgroundImage) as $bg) {
+ if (isset($bg['gradient']) && $bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $s .= $this->gradient->Gradient($clx, $cly, $clw, $clh, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend'], true);
+ }
+ } elseif ($bg['image_id']) { // Background pattern
+ $n = count($this->patterns) + 1;
+ // If using resize, uses TrimBox (not including the bleed)
+ list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($bg['orig_w'], $bg['orig_h'], $clw, $clh, $bg['resize'], $bg['x_repeat'], $bg['y_repeat']);
- $this->patterns[$n] = ['x' => $clx, 'y' => $cly, 'w' => $clw, 'h' => $clh, 'pgh' => $this->h, 'image_id' => $this->bodyBackgroundImage['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $this->bodyBackgroundImage['x_pos'], 'y_pos' => $this->bodyBackgroundImage['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $this->bodyBackgroundImage['itype']];
- if (($this->bodyBackgroundImage['opacity'] > 0 || $this->bodyBackgroundImage['opacity'] === '0') && $this->bodyBackgroundImage['opacity'] < 1) {
- $opac = $this->SetAlpha($this->bodyBackgroundImage['opacity'], 'Normal', true);
- } else {
- $opac = '';
+ $this->patterns[$n] = ['x' => $clx, 'y' => $cly, 'w' => $clw, 'h' => $clh, 'pgh' => $this->h, 'image_id' => $bg['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $bg['x_pos'], 'y_pos' => $bg['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $bg['itype']];
+ if (($bg['opacity'] > 0 || $bg['opacity'] === '0') && $bg['opacity'] < 1) {
+ $opac = $this->SetAlpha($bg['opacity'], 'Normal', true);
+ } else {
+ $opac = '';
+ }
+ $s .= sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, ($clx * Mpdf::SCALE), ($cly * Mpdf::SCALE), $clw * Mpdf::SCALE, $clh * Mpdf::SCALE) . "\n";
}
- $s .= sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, ($clx * Mpdf::SCALE), ($cly * Mpdf::SCALE), $clw * Mpdf::SCALE, $clh * Mpdf::SCALE) . "\n";
}
}
/* -- END BACKGROUNDS -- */
@@ -13295,9 +13355,31 @@ function TableHeaderFooter($content = '', $tablestartpage = '', $tablestartcolum
}
if (isset($tablehf['background-image']) && $paintcell) {
- if ($tablehf['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $tablehf['background-image']['gradient'])) {
- $g = $this->gradient->parseMozGradient($tablehf['background-image']['gradient']);
- if ($g) {
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($tablehf['background-image']['layers']) ? 8 : 7;
+ foreach ($this->backgroundLayers($tablehf['background-image']) as $bg) {
+ if ($bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ if ($table['borders_separate']) {
+ $px = $x + ($table['border_spacing_H'] / 2);
+ $py = $y + ($table['border_spacing_V'] / 2);
+ $pw = $w - $table['border_spacing_H'];
+ $ph = $h - $table['border_spacing_V'];
+ } else {
+ $px = $x;
+ $py = $y;
+ $pw = $w;
+ $ph = $h;
+ }
+ if ($this->ColActive) {
+ $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
+ } else {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ }
+ } elseif ($bg['image_id']) { // Background pattern
+ $n = count($this->patterns) + 1;
if ($table['borders_separate']) {
$px = $x + ($table['border_spacing_H'] / 2);
$py = $y + ($table['border_spacing_V'] / 2);
@@ -13310,35 +13392,17 @@ function TableHeaderFooter($content = '', $tablestartpage = '', $tablestartcolum
$ph = $h;
}
if ($this->ColActive) {
- $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
- } else {
- $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
- }
- }
- } elseif ($tablehf['background-image']['image_id']) { // Background pattern
- $n = count($this->patterns) + 1;
- if ($table['borders_separate']) {
- $px = $x + ($table['border_spacing_H'] / 2);
- $py = $y + ($table['border_spacing_V'] / 2);
- $pw = $w - $table['border_spacing_H'];
- $ph = $h - $table['border_spacing_V'];
- } else {
- $px = $x;
- $py = $y;
- $pw = $w;
- $ph = $h;
- }
- if ($this->ColActive) {
- list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($tablehf['background-image']['orig_w'], $tablehf['background-image']['orig_h'], $pw, $ph, $tablehf['background-image']['resize'], $tablehf['background-image']['x_repeat'], $tablehf['background-image']['y_repeat']);
- $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $tablehf['background-image']['itype']];
- if ($tablehf['background-image']['opacity'] > 0 && $tablehf['background-image']['opacity'] < 1) {
- $opac = $this->SetAlpha($tablehf['background-image']['opacity'], 'Normal', true);
+ list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($bg['orig_w'], $bg['orig_h'], $pw, $ph, $bg['resize'], $bg['x_repeat'], $bg['y_repeat']);
+ $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $bg['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $bg['x_pos'], 'y_pos' => $bg['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $bg['itype']];
+ if ($bg['opacity'] > 0 && $bg['opacity'] < 1) {
+ $opac = $this->SetAlpha($bg['opacity'], 'Normal', true);
+ } else {
+ $opac = '';
+ }
+ $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
} else {
- $opac = '';
+ $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $bg['image_id'], 'orig_w' => $bg['orig_w'], 'orig_h' => $bg['orig_h'], 'x_pos' => $bg['x_pos'], 'y_pos' => $bg['y_pos'], 'x_repeat' => $bg['x_repeat'], 'y_repeat' => $bg['y_repeat'], 'clippath' => '', 'resize' => $bg['resize'], 'opacity' => $bg['opacity'], 'itype' => $bg['itype']];
}
- $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
- } else {
- $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $tablehf['background-image']['orig_w'], 'orig_h' => $tablehf['background-image']['orig_h'], 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $tablehf['background-image']['x_repeat'], 'y_repeat' => $tablehf['background-image']['y_repeat'], 'clippath' => '', 'resize' => $tablehf['background-image']['resize'], 'opacity' => $tablehf['background-image']['opacity'], 'itype' => $tablehf['background-image']['itype']];
}
}
}
@@ -13476,9 +13540,42 @@ function TableHeaderFooter($content = '', $tablestartpage = '', $tablestartcolum
}
if (isset($content[$i][0]['trbackground-images']) && ($colctr == 1 || $table['borders_separate'])) {
- if ($content[$i][0]['trbackground-images']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $content[$i][0]['trbackground-images']['gradient'])) {
- $g = $this->gradient->parseMozGradient($content[$i][0]['trbackground-images']['gradient']);
- if ($g) {
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($content[$i][0]['trbackground-images']['layers']) ? 5 : 4;
+ foreach ($this->backgroundLayers($content[$i][0]['trbackground-images']) as $bg) {
+ if ($bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $gx = $x0;
+ $gy = $y;
+ $gh = $h;
+ $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
+ if ($table['borders_separate']) {
+ $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
+ $clx = $x + ($table['border_spacing_H'] / 2);
+ $cly = $y + ($table['border_spacing_V'] / 2);
+ $clw = $w - $table['border_spacing_H'];
+ $clh = $h - $table['border_spacing_V'];
+ // Set clipping path
+ $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
+ } else {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ }
+ } else {
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+
+ $clippath = '';
$gx = $x0;
$gy = $y;
$gh = $h;
@@ -13491,40 +13588,11 @@ function TableHeaderFooter($content = '', $tablestartpage = '', $tablestartcolum
$clh = $h - $table['border_spacing_V'];
// Set clipping path
$s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
- $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
+ $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
} else {
- $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
}
- } else {
- $image_id = $content[$i][0]['trbackground-images']['image_id'];
- $orig_w = $content[$i][0]['trbackground-images']['orig_w'];
- $orig_h = $content[$i][0]['trbackground-images']['orig_h'];
- $x_pos = $content[$i][0]['trbackground-images']['x_pos'];
- $y_pos = $content[$i][0]['trbackground-images']['y_pos'];
- $x_repeat = $content[$i][0]['trbackground-images']['x_repeat'];
- $y_repeat = $content[$i][0]['trbackground-images']['y_repeat'];
- $resize = $content[$i][0]['trbackground-images']['resize'];
- $opacity = $content[$i][0]['trbackground-images']['opacity'];
- $itype = $content[$i][0]['trbackground-images']['itype'];
-
- $clippath = '';
- $gx = $x0;
- $gy = $y;
- $gh = $h;
- $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
- if ($table['borders_separate']) {
- $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
- $clx = $x + ($table['border_spacing_H'] / 2);
- $cly = $y + ($table['border_spacing_V'] / 2);
- $clw = $w - $table['border_spacing_H'];
- $clh = $h - $table['border_spacing_V'];
- // Set clipping path
- $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
- $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
- } else {
- $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
- }
}
}
}
@@ -18754,146 +18822,153 @@ function PaintDivBB($divider = '', $blockstate = 0, $blvl = 0)
}
if (isset($this->blk[$blvl]['background-image'])) {
- if (isset($this->blk[$blvl]['background-image']['gradient']) && $this->blk[$blvl]['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $this->blk[$blvl]['background-image']['gradient'])) {
- $g = $this->gradient->parseMozGradient($this->blk[$blvl]['background-image']['gradient']);
- if ($g) {
- $gx = $x0;
- $gy = $y0;
+ // A gradient layer is sized from the whole box
+ $bgw = $w;
+ $bgh = $h;
+ foreach ($this->backgroundLayers($this->blk[$blvl]['background-image']) as $bg) {
+ $w = $bgw;
+ $h = $bgh;
+ if (isset($bg['gradient']) && $bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $gx = $x0;
+ $gy = $y0;
+ // origin specifies the background-positioning-area (bpa)
+ if ($bg['origin'] == 'padding-box') {
+ $gx += $this->blk[$blvl]['border_left']['w'];
+ $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
+ if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
+ $gy += $this->blk[$blvl]['border_top']['w'];
+ }
+ if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
+ $gy1 = $y1 - $this->blk[$blvl]['border_bottom']['w'];
+ } else {
+ $gy1 = $y1;
+ }
+ $h = $gy1 - $gy;
+ } elseif ($bg['origin'] == 'content-box') {
+ $gx += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
+ $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
+ if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
+ $gy += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
+ }
+ if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
+ $gy1 = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
+ } else {
+ $gy1 = $y1 - $this->blk[$blvl]['padding_bottom'];
+ }
+ $h = $gy1 - $gy;
+ }
+
+ if (isset($bg['size']['w']) && $bg['size']['w']) {
+ $size = $bg['size'];
+ if ($size['w'] != 'contain' && $size['w'] != 'cover') {
+ if (stristr($size['w'], '%')) {
+ $size['w'] = (float) $size['w'];
+ $size['w'] /= 100;
+ $w *= $size['w'];
+ } elseif ($size['w'] != 'auto') {
+ $w = $size['w'];
+ }
+ if (stristr($size['h'], '%')) {
+ $size['h'] = (float) $size['h'];
+ $size['h'] /= 100;
+ $h *= $size['h'];
+ } elseif ($size['h'] != 'auto') {
+ $h = $size['h'];
+ }
+ }
+ }
+ $this->pageBackgrounds[$blvl][] = [
+ 'gradient' => true,
+ 'x' => $gx,
+ 'y' => $gy,
+ 'w' => $w,
+ 'h' => $h,
+ 'gradtype' => $g['type'],
+ 'stops' => $g['stops'],
+ 'colorspace' => $g['colorspace'],
+ 'coords' => $g['coords'],
+ 'extend' => $g['extend'],
+ 'clippath' => $s,
+ 'visibility' => $this->visibility,
+ 'z-index' => $this->current_layer
+ ];
+ }
+
+ } else {
+
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+ $size = $bg['size'];
// origin specifies the background-positioning-area (bpa)
- if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') {
- $gx += $this->blk[$blvl]['border_left']['w'];
- $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
+
+ $bpa = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h];
+
+ if ($bg['origin'] == 'padding-box') {
+
+ $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'];
+ $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
- $gy += $this->blk[$blvl]['border_top']['w'];
+ $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'];
+ } else {
+ $bpa['y'] = $y0;
}
if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
- $gy1 = $y1 - $this->blk[$blvl]['border_bottom']['w'];
+ $bpay = $y1 - $this->blk[$blvl]['border_bottom']['w'];
} else {
- $gy1 = $y1;
+ $bpay = $y1;
}
- $h = $gy1 - $gy;
- } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') {
- $gx += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
- $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
+ $bpa['h'] = $bpay - $bpa['y'];
+
+ } elseif ($bg['origin'] == 'content-box') {
+
+ $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
+ $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
- $gy += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
+ $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
+ } else {
+ $bpa['y'] = $y0 + $this->blk[$blvl]['padding_top'];
}
if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
- $gy1 = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
+ $bpay = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
} else {
- $gy1 = $y1 - $this->blk[$blvl]['padding_bottom'];
+ $bpay = $y1 - $this->blk[$blvl]['padding_bottom'];
}
- $h = $gy1 - $gy;
- }
+ $bpa['h'] = $bpay - $bpa['y'];
- if (isset($this->blk[$blvl]['background-image']['size']['w']) && $this->blk[$blvl]['background-image']['size']['w']) {
- $size = $this->blk[$blvl]['background-image']['size'];
- if ($size['w'] != 'contain' && $size['w'] != 'cover') {
- if (stristr($size['w'], '%')) {
- $size['w'] = (float) $size['w'];
- $size['w'] /= 100;
- $w *= $size['w'];
- } elseif ($size['w'] != 'auto') {
- $w = $size['w'];
- }
- if (stristr($size['h'], '%')) {
- $size['h'] = (float) $size['h'];
- $size['h'] /= 100;
- $h *= $size['h'];
- } elseif ($size['h'] != 'auto') {
- $h = $size['h'];
- }
- }
}
+
$this->pageBackgrounds[$blvl][] = [
- 'gradient' => true,
- 'x' => $gx,
- 'y' => $gy,
+ 'x' => $x0,
+ 'y' => $y0,
'w' => $w,
'h' => $h,
- 'gradtype' => $g['type'],
- 'stops' => $g['stops'],
- 'colorspace' => $g['colorspace'],
- 'coords' => $g['coords'],
- 'extend' => $g['extend'],
+ 'image_id' => $image_id,
+ 'orig_w' => $orig_w,
+ 'orig_h' => $orig_h,
+ 'x_pos' => $x_pos,
+ 'y_pos' => $y_pos,
+ 'x_repeat' => $x_repeat,
+ 'y_repeat' => $y_repeat,
'clippath' => $s,
+ 'resize' => $resize,
+ 'opacity' => $opacity,
+ 'itype' => $itype,
'visibility' => $this->visibility,
- 'z-index' => $this->current_layer
+ 'z-index' => $this->current_layer,
+ 'size' => $size,
+ 'bpa' => $bpa
];
}
-
- } else {
-
- $image_id = $this->blk[$blvl]['background-image']['image_id'];
- $orig_w = $this->blk[$blvl]['background-image']['orig_w'];
- $orig_h = $this->blk[$blvl]['background-image']['orig_h'];
- $x_pos = $this->blk[$blvl]['background-image']['x_pos'];
- $y_pos = $this->blk[$blvl]['background-image']['y_pos'];
- $x_repeat = $this->blk[$blvl]['background-image']['x_repeat'];
- $y_repeat = $this->blk[$blvl]['background-image']['y_repeat'];
- $resize = $this->blk[$blvl]['background-image']['resize'];
- $opacity = $this->blk[$blvl]['background-image']['opacity'];
- $itype = $this->blk[$blvl]['background-image']['itype'];
- $size = $this->blk[$blvl]['background-image']['size'];
- // origin specifies the background-positioning-area (bpa)
-
- $bpa = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h];
-
- if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') {
-
- $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'];
- $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']);
- if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
- $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'];
- } else {
- $bpa['y'] = $y0;
- }
- if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
- $bpay = $y1 - $this->blk[$blvl]['border_bottom']['w'];
- } else {
- $bpay = $y1;
- }
- $bpa['h'] = $bpay - $bpa['y'];
-
- } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') {
-
- $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'];
- $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']);
- if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) {
- $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top'];
- } else {
- $bpa['y'] = $y0 + $this->blk[$blvl]['padding_top'];
- }
- if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') {
- $bpay = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']);
- } else {
- $bpay = $y1 - $this->blk[$blvl]['padding_bottom'];
- }
- $bpa['h'] = $bpay - $bpa['y'];
-
- }
-
- $this->pageBackgrounds[$blvl][] = [
- 'x' => $x0,
- 'y' => $y0,
- 'w' => $w,
- 'h' => $h,
- 'image_id' => $image_id,
- 'orig_w' => $orig_w,
- 'orig_h' => $orig_h,
- 'x_pos' => $x_pos,
- 'y_pos' => $y_pos,
- 'x_repeat' => $x_repeat,
- 'y_repeat' => $y_repeat,
- 'clippath' => $s,
- 'resize' => $resize,
- 'opacity' => $opacity,
- 'itype' => $itype,
- 'visibility' => $this->visibility,
- 'z-index' => $this->current_layer,
- 'size' => $size,
- 'bpa' => $bpa
- ];
}
}
/* -- END BACKGROUNDS -- */
@@ -19388,6 +19463,8 @@ function setCSS($arrayaux, $type = '', $tag = '')
break;
case 'BACKGROUND-CLIP':
+ // Every layer is drawn under the clip of the bottom one, which the colour takes
+ $v = BackgroundLayers::bottomValue($v, isset($arrayaux['BACKGROUND-IMAGE']) ? $arrayaux['BACKGROUND-IMAGE'] : '');
if (strtoupper($v) == 'PADDING-BOX') {
$this->blk[$this->blklvl]['background_clip'] = 'padding-box';
} elseif (strtoupper($v) == 'CONTENT-BOX') {
@@ -23457,23 +23534,27 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
}
if (isset($table['background-image'])) {
- if ($table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) {
- $g = $this->gradient->parseMozGradient($table['background-image']['gradient']);
- if ($g) {
- $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($table['background-image']['layers']) ? 2 : 1;
+ foreach ($this->backgroundLayers($table['background-image']) as $bg) {
+ if ($bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ } else {
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+ $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
- } else {
- $image_id = $table['background-image']['image_id'];
- $orig_w = $table['background-image']['orig_w'];
- $orig_h = $table['background-image']['orig_h'];
- $x_pos = $table['background-image']['x_pos'];
- $y_pos = $table['background-image']['y_pos'];
- $x_repeat = $table['background-image']['x_repeat'];
- $y_repeat = $table['background-image']['y_repeat'];
- $resize = $table['background-image']['resize'];
- $opacity = $table['background-image']['opacity'];
- $itype = $table['background-image']['itype'];
- $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
}
/* -- END BACKGROUNDS -- */
@@ -23805,9 +23886,31 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
}
if (isset($cell['background-image']) && $paintcell) {
- if (isset($cell['background-image']['gradient']) && $cell['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $cell['background-image']['gradient'])) {
- $g = $this->gradient->parseMozGradient($cell['background-image']['gradient']);
- if ($g) {
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($cell['background-image']['layers']) ? 8 : 7;
+ foreach ($this->backgroundLayers($cell['background-image']) as $bg) {
+ if (isset($bg['gradient']) && $bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ if ($table['borders_separate']) {
+ $px = $x + ($table['border_spacing_H'] / 2);
+ $py = $y + ($table['border_spacing_V'] / 2);
+ $pw = $w - $table['border_spacing_H'];
+ $ph = $h - $table['border_spacing_V'];
+ } else {
+ $px = $x;
+ $py = $y;
+ $pw = $w;
+ $ph = $h;
+ }
+ if ($this->ColActive) {
+ $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
+ } else {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ }
+ } elseif (isset($bg['image_id']) && $bg['image_id']) { // Background pattern
+ $n = count($this->patterns) + 1;
if ($table['borders_separate']) {
$px = $x + ($table['border_spacing_H'] / 2);
$py = $y + ($table['border_spacing_V'] / 2);
@@ -23820,45 +23923,27 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
$ph = $h;
}
if ($this->ColActive) {
- $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']);
- } else {
- $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
- }
- }
- } elseif (isset($cell['background-image']['image_id']) && $cell['background-image']['image_id']) { // Background pattern
- $n = count($this->patterns) + 1;
- if ($table['borders_separate']) {
- $px = $x + ($table['border_spacing_H'] / 2);
- $py = $y + ($table['border_spacing_V'] / 2);
- $pw = $w - $table['border_spacing_H'];
- $ph = $h - $table['border_spacing_V'];
- } else {
- $px = $x;
- $py = $y;
- $pw = $w;
- $ph = $h;
- }
- if ($this->ColActive) {
- list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($cell['background-image']['orig_w'], $cell['background-image']['orig_h'], $pw, $ph, $cell['background-image']['resize'], $cell['background-image']['x_repeat'], $cell['background-image']['y_repeat']);
- $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $cell['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $cell['background-image']['x_pos'], 'y_pos' => $cell['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat];
- if ($cell['background-image']['opacity'] > 0 && $cell['background-image']['opacity'] < 1) {
- $opac = $this->SetAlpha($cell['background-image']['opacity'], 'Normal', true);
+ list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($bg['orig_w'], $bg['orig_h'], $pw, $ph, $bg['resize'], $bg['x_repeat'], $bg['y_repeat']);
+ $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $bg['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $bg['x_pos'], 'y_pos' => $bg['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat];
+ if ($bg['opacity'] > 0 && $bg['opacity'] < 1) {
+ $opac = $this->SetAlpha($bg['opacity'], 'Normal', true);
+ } else {
+ $opac = '';
+ }
+ $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
} else {
- $opac = '';
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+ $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
- $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE));
- } else {
- $image_id = $cell['background-image']['image_id'];
- $orig_w = $cell['background-image']['orig_w'];
- $orig_h = $cell['background-image']['orig_h'];
- $x_pos = $cell['background-image']['x_pos'];
- $y_pos = $cell['background-image']['y_pos'];
- $x_repeat = $cell['background-image']['x_repeat'];
- $y_repeat = $cell['background-image']['y_repeat'];
- $resize = $cell['background-image']['resize'];
- $opacity = $cell['background-image']['opacity'];
- $itype = $cell['background-image']['itype'];
- $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
}
}
@@ -24151,9 +24236,41 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
}
}
if (isset($table['trbackground-images'][$i]) && ($j == 0 || $table['borders_separate'])) {
- if (isset($table['trbackground-images'][$i]['gradient']) && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['trbackground-images'][$i]['gradient'])) {
- $g = $this->gradient->parseMozGradient($table['trbackground-images'][$i]['gradient']);
- if ($g) {
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($table['trbackground-images'][$i]['layers']) ? 5 : 4;
+ foreach ($this->backgroundLayers($table['trbackground-images'][$i]) as $bg) {
+ if (isset($bg['gradient']) && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $gx = $x0;
+ $gy = $y;
+ $gh = $h;
+ $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
+ if ($table['borders_separate']) {
+ $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
+ $clx = $x + ($table['border_spacing_H'] / 2);
+ $cly = $y + ($table['border_spacing_V'] / 2);
+ $clw = $w - $table['border_spacing_H'];
+ $clh = $h - $table['border_spacing_V'];
+ // Set clipping path
+ $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
+ } else {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ }
+ } else {
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+ $clippath = '';
$gx = $x0;
$gy = $y;
$gh = $h;
@@ -24166,39 +24283,11 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
$clh = $h - $table['border_spacing_V'];
// Set clipping path
$s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
- $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s];
+ $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
} else {
- $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
}
- } else {
- $image_id = $table['trbackground-images'][$i]['image_id'];
- $orig_w = $table['trbackground-images'][$i]['orig_w'];
- $orig_h = $table['trbackground-images'][$i]['orig_h'];
- $x_pos = $table['trbackground-images'][$i]['x_pos'];
- $y_pos = $table['trbackground-images'][$i]['y_pos'];
- $x_repeat = $table['trbackground-images'][$i]['x_repeat'];
- $y_repeat = $table['trbackground-images'][$i]['y_repeat'];
- $resize = $table['trbackground-images'][$i]['resize'];
- $opacity = $table['trbackground-images'][$i]['opacity'];
- $itype = $table['trbackground-images'][$i]['itype'];
- $clippath = '';
- $gx = $x0;
- $gy = $y;
- $gh = $h;
- $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R'];
- if ($table['borders_separate']) {
- $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']);
- $clx = $x + ($table['border_spacing_H'] / 2);
- $cly = $y + ($table['border_spacing_V'] / 2);
- $clw = $w - $table['border_spacing_H'];
- $clh = $h - $table['border_spacing_V'];
- // Set clipping path
- $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6
- $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
- } else {
- $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
- }
}
}
}
@@ -24366,23 +24455,27 @@ function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $spl
}
if (isset($table['background-image'])) {
- if (isset($table['background-image']['gradient']) && $table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) {
- $g = $this->gradient->parseMozGradient($table['background-image']['gradient']);
- if ($g) {
- $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ // The layers of a list are drawn in their order, so its gradients go in with its images
+ $gradientSlot = isset($table['background-image']['layers']) ? 2 : 1;
+ foreach ($this->backgroundLayers($table['background-image']) as $bg) {
+ if (isset($bg['gradient']) && $bg['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $bg['gradient'])) {
+ $g = $this->gradient->parseMozGradient($bg['gradient']);
+ if ($g) {
+ $this->tableBackgrounds[$level * 9 + $gradientSlot][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => ''];
+ }
+ } else {
+ $image_id = $bg['image_id'];
+ $orig_w = $bg['orig_w'];
+ $orig_h = $bg['orig_h'];
+ $x_pos = $bg['x_pos'];
+ $y_pos = $bg['y_pos'];
+ $x_repeat = $bg['x_repeat'];
+ $y_repeat = $bg['y_repeat'];
+ $resize = $bg['resize'];
+ $opacity = $bg['opacity'];
+ $itype = $bg['itype'];
+ $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
- } else {
- $image_id = $table['background-image']['image_id'];
- $orig_w = $table['background-image']['orig_w'];
- $orig_h = $table['background-image']['orig_h'];
- $x_pos = $table['background-image']['x_pos'];
- $y_pos = $table['background-image']['y_pos'];
- $x_repeat = $table['background-image']['x_repeat'];
- $y_repeat = $table['background-image']['y_repeat'];
- $resize = $table['background-image']['resize'];
- $opacity = $table['background-image']['opacity'];
- $itype = $table['background-image']['itype'];
- $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype];
}
}
/* -- END BACKGROUNDS -- */
diff --git a/tests/Mpdf/Css/NormalizePropertiesTest.php b/tests/Mpdf/Css/NormalizePropertiesTest.php
index afb1ae2a4..83a6014ef 100644
--- a/tests/Mpdf/Css/NormalizePropertiesTest.php
+++ b/tests/Mpdf/Css/NormalizePropertiesTest.php
@@ -456,9 +456,28 @@ public function providerBackground()
'linear-gradient(to right, red, blue) #fff',
['BACKGROUND-COLOR' => '#fff', 'BACKGROUND-IMAGE' => 'linear-gradient(to right, red, blue)'],
],
- 'the colour of the last layer under the first' => [
+ 'every layer, with the colour of the last' => [
'url(a.png) no-repeat, url(b.png) repeat-y #fff',
- ['BACKGROUND-COLOR' => '#fff', 'BACKGROUND-IMAGE' => 'a.png', 'BACKGROUND-REPEAT' => 'no-repeat'],
+ [
+ 'BACKGROUND-COLOR' => '#fff',
+ 'BACKGROUND-IMAGE' => BackgroundLayers::join(['a.png', 'b.png']),
+ 'BACKGROUND-REPEAT' => BackgroundLayers::join(['no-repeat', 'repeat-y']),
+ 'BACKGROUND-POSITION' => BackgroundLayers::join(['0% 0%', '0% 0%']),
+ 'BACKGROUND-SIZE' => BackgroundLayers::join(['auto', 'auto']),
+ 'BACKGROUND-ORIGIN' => BackgroundLayers::join(['padding-box', 'padding-box']),
+ 'BACKGROUND-CLIP' => BackgroundLayers::join(['border-box', 'border-box']),
+ ],
+ ],
+ 'a gradient and an image, each with its own parts' => [
+ 'linear-gradient(to right, red, rgb(0 0 255)) center / 50% auto content-box, url("a,b.png") no-repeat right bottom',
+ [
+ 'BACKGROUND-IMAGE' => BackgroundLayers::join(['linear-gradient(to right, red, rgb(0,0,255))', 'a,b.png']),
+ 'BACKGROUND-REPEAT' => BackgroundLayers::join(['repeat', 'no-repeat']),
+ 'BACKGROUND-POSITION' => BackgroundLayers::join(['50% 50%', '100% 100%']),
+ 'BACKGROUND-SIZE' => BackgroundLayers::join(['50% auto', 'auto']),
+ 'BACKGROUND-ORIGIN' => BackgroundLayers::join(['content-box', 'padding-box']),
+ 'BACKGROUND-CLIP' => BackgroundLayers::join(['content-box', 'border-box']),
+ ],
],
'none' => [
'none',
@@ -516,6 +535,85 @@ public function providerInvalidBackground()
];
}
+ /**
+ * A background longhand with a comma-separated list keeps a value for each layer
+ *
+ * @dataProvider providerLayeredLonghand
+ *
+ * @param string $property
+ * @param string $value
+ * @param string[] $expected Each layer's value
+ */
+ public function testReadsAValueForEachLayer($property, $value, array $expected)
+ {
+ $this->assertSame([$property => BackgroundLayers::join($expected)], $this->normalizeProperties->normalize([$property => $value]));
+ }
+
+ /**
+ * Background longhand lists and each layer's value
+ *
+ * @return array[]
+ */
+ public function providerLayeredLonghand()
+ {
+ return [
+ 'images, a gradient and none' => ['BACKGROUND-IMAGE', 'url(a.png), linear-gradient(red, blue), none', ['a.png', 'linear-gradient(red, blue)', '']],
+ 'an image with commas in its address' => ['BACKGROUND-IMAGE', 'url(data:image/png;base64,AB,CD), url("B.png")', ['data:image/png;base64,AB,CD', 'B.png']],
+ 'repeats' => ['BACKGROUND-REPEAT', 'no-repeat, repeat-x', ['no-repeat', 'repeat-x']],
+ 'positions' => ['BACKGROUND-POSITION', 'right bottom, 10mm 5mm', ['100% 100%', '10mm 5mm']],
+ 'sizes' => ['BACKGROUND-SIZE', 'cover, 50% auto, 10mm', ['cover', '50% auto', '10mm']],
+ 'origins' => ['BACKGROUND-ORIGIN', 'content-box, border-box', ['content-box', 'border-box']],
+ 'clips' => ['BACKGROUND-CLIP', 'padding-box, border-box', ['padding-box', 'border-box']],
+ ];
+ }
+
+ /**
+ * A background longhand list with a value that is not a value of the property is dropped
+ *
+ * @dataProvider providerInvalidLayeredLonghand
+ *
+ * @param string $property
+ * @param string $value
+ */
+ public function testDropsAnInvalidLayerList($property, $value)
+ {
+ $this->assertSame([], $this->normalizeProperties->normalize([$property => $value]));
+ }
+
+ /**
+ * Background longhand lists that are not lists of the property's values
+ *
+ * @return string[][]
+ */
+ public function providerInvalidLayeredLonghand()
+ {
+ return [
+ 'a colour among the images' => ['BACKGROUND-IMAGE', 'url(a.png), #fff'],
+ 'an unknown repeat' => ['BACKGROUND-REPEAT', 'no-repeat, bogus'],
+ 'an empty position' => ['BACKGROUND-POSITION', 'left,'],
+ 'three sizes in a layer' => ['BACKGROUND-SIZE', '10mm 10mm 10mm, auto'],
+ 'two boxes in a layer' => ['BACKGROUND-ORIGIN', 'padding-box content-box, border-box'],
+ ];
+ }
+
+ /**
+ * The legacy CSS mode reads only the top layer of a list, with the colour of the last
+ */
+ public function testTheLegacyModeReadsOnlyTheTopLayer()
+ {
+ $this->mpdf->cssMode = CssMode::LEGACY;
+
+ $result = $this->normalizeProperties->normalize([
+ 'BACKGROUND' => 'url(a.png) no-repeat, url(b.png) #fff',
+ 'BACKGROUND-POSITION' => 'right bottom, left top',
+ ]);
+
+ $this->assertSame('#fff', $result['BACKGROUND-COLOR']);
+ $this->assertSame('a.png', $result['BACKGROUND-IMAGE']);
+ $this->assertSame('no-repeat', $result['BACKGROUND-REPEAT']);
+ $this->assertArrayNotHasKey('BACKGROUND-POSITION', $result);
+ }
+
public function testNonExistentFontFamily()
{
$result = $this->normalizeProperties->normalize(['FONT-FAMILY' => 'abc']);
diff --git a/tests/Mpdf/MultipleBackgroundsTest.php b/tests/Mpdf/MultipleBackgroundsTest.php
new file mode 100644
index 000000000..bf4a4ba53
--- /dev/null
+++ b/tests/Mpdf/MultipleBackgroundsTest.php
@@ -0,0 +1,211 @@
+paintedLayers('
Text
');
+
+ $this->assertSame($painted, array_column($layers, 'kind'));
+ }
+
+ /**
+ * Backgrounds and what they paint, from the bottom up
+ *
+ * @return array[]
+ */
+ public function providerLayerOrder()
+ {
+ return [
+ 'an image over a gradient' => ['background: url(IMG) no-repeat, linear-gradient(red, blue)', ['shading', 'pattern']],
+ 'a gradient over an image' => ['background: linear-gradient(red, blue), url(IMG)', ['pattern', 'shading']],
+ 'two images' => ['background-image: url(IMG), url(IMG); background-repeat: no-repeat', ['pattern', 'pattern']],
+ 'a gradient over a gradient' => ['background-image: linear-gradient(red, blue), radial-gradient(green, yellow)', ['shading', 'shading']],
+ 'a layer of none' => ['background-image: url(IMG), none, linear-gradient(red, blue)', ['shading', 'pattern']],
+ ];
+ }
+
+ /**
+ * The bottom layer of a gradient over a gradient is the one listed last
+ */
+ public function testPaintsTheLastGradientAtTheBottom()
+ {
+ $layers = $this->paintedLayers('Text
');
+
+ // ShadingType 2 is axial, 3 radial
+ $this->assertSame(['3', '2'], array_column($layers, 'type'));
+ }
+
+ /**
+ * The colour the last layer names is painted under the images
+ */
+ public function testPaintsTheColourOfTheLastLayerUnderTheImage()
+ {
+ $pdf = $this->render('Text
');
+
+ $stream = $this->pageContents($pdf)[0];
+ $this->assertStringContainsString('0.000 1.000 0.000 rg', $stream);
+ $this->assertLessThan(strpos($stream, '/Pattern cs'), strpos($stream, '0.000 1.000 0.000 rg'));
+ }
+
+ /**
+ * A layer takes its own position and size from the lists, and a repeat list shorter than the list of images
+ * repeats: the third of three layers takes the first repeat
+ */
+ public function testEachLayerTakesItsOwnValues()
+ {
+ $layers = $this->paintedLayers('Text
');
+
+ $this->assertSame([
+ // The bottom layer, 20mm (56.693pt) wide and in proportion, at the top left
+ ['kind' => 'pattern', 'step' => '99999 99999', 'box' => '56.693 65.764', 'at' => '42.520 730.772'],
+ // Repeated across, at the bottom right of the 170.079pt by 85.039pt box
+ ['kind' => 'pattern', 'step' => '75.000 99999', 'box' => self::IMAGE_SIZE, 'at' => '137.598 711.496'],
+ // The top layer, once at the top left
+ ['kind' => 'pattern', 'step' => '99999 99999', 'box' => self::IMAGE_SIZE, 'at' => '42.520 709.536'],
+ ], $layers);
+ }
+
+ /**
+ * A longhand list longer than the list of images is cut short
+ */
+ public function testCutsAListLongerThanTheImages()
+ {
+ $layers = $this->paintedLayers('Text
');
+
+ $this->assertSame([['kind' => 'pattern', 'step' => '99999 99999', 'box' => self::IMAGE_SIZE, 'at' => '137.598 711.496']], $layers);
+ }
+
+ /**
+ * A table cell draws every layer, the last one at the bottom
+ */
+ public function testPaintsEveryLayerOfATableCell()
+ {
+ $layers = $this->paintedLayers('');
+
+ $this->assertSame(['shading', 'pattern'], array_column($layers, 'kind'));
+ }
+
+ /**
+ * A table draws every layer, the last one at the bottom
+ */
+ public function testPaintsEveryLayerOfATable()
+ {
+ $layers = $this->paintedLayers('');
+
+ $this->assertSame(['pattern', 'shading'], array_column($layers, 'kind'));
+ }
+
+ /**
+ * A table row draws every layer, the last one at the bottom
+ */
+ public function testPaintsEveryLayerOfATableRow()
+ {
+ $layers = $this->paintedLayers('');
+
+ $this->assertSame(['pattern', 'shading'], array_column($layers, 'kind'));
+ }
+
+ /**
+ * The page box of an @page rule draws every layer, the last one at the bottom
+ */
+ public function testPaintsEveryLayerOfThePage()
+ {
+ $layers = $this->paintedLayers('Text');
+
+ $this->assertSame(['shading', 'pattern'], array_column($layers, 'kind'));
+ }
+
+ /**
+ * The colour, and the layers with it, are clipped as the bottom layer's background-clip has it
+ */
+ public function testClipsToTheBoxOfTheBottomLayer()
+ {
+ $pdf = $this->render('Text
');
+
+ // The content box starts 5mm (14.173pt) in from the block's left edge at 42.520pt
+ $this->assertStringContainsString(' q 0 w 56.693 ', $this->pageContents($pdf)[0]);
+ }
+
+ /**
+ * The legacy CSS mode draws only the top layer, as mPDF v7 did
+ */
+ public function testTheLegacyModeDrawsOnlyTheTopLayer()
+ {
+ $layers = $this->paintedLayers('Text
', ['cssMode' => CssMode::LEGACY]);
+
+ $this->assertSame(['pattern'], array_column($layers, 'kind'));
+ }
+
+ /**
+ * Puts the test image's path in place of IMG
+ *
+ * @param string $css
+ * @return string
+ */
+ private function withImage($css)
+ {
+ return str_replace('IMG', $this->backgroundImage(), $css);
+ }
+
+ /**
+ * The gradients and images the first page paints, from the bottom up. A gradient gives its shading type; an
+ * image pattern its steps, its box and where it is placed, in points
+ *
+ * @param string $html
+ * @param array $config
+ * @return array[]
+ */
+ private function paintedLayers($html, $config = [])
+ {
+ $pdf = $this->assertDrawsSilently(function (Mpdf $mpdf) use ($html) {
+ $mpdf->WriteHTML($html);
+ }, $config);
+
+ preg_match_all('#/(Sh\d+) sh|/Pattern cs /(P\d+) scn#', $this->pageContents($pdf)[0], $fills, PREG_SET_ORDER);
+
+ $layers = [];
+ foreach ($fills as $fill) {
+ $name = isset($fill[2]) ? $fill[2] : $fill[1];
+ preg_match('#/' . $name . ' (\d+) 0 R#', $pdf, $ref);
+ $object = $this->object($pdf, $ref[1]);
+
+ if (isset($fill[2])) {
+ preg_match('#/BBox \[0 0 ([\d.]+ [\d.]+)\]\n/XStep ([\d.]+)\n/YStep ([\d.]+)\n/Matrix \[1 0 0 1 ([\d.]+ [\d.]+)\]#', $object, $pattern);
+ $layers[] = ['kind' => 'pattern', 'step' => $pattern[2] . ' ' . $pattern[3], 'box' => $pattern[1], 'at' => $pattern[4]];
+ } else {
+ preg_match('#/ShadingType (\d)#', $object, $shading);
+ $layers[] = ['kind' => 'shading', 'type' => $shading[1]];
+ }
+ }
+
+ return $layers;
+ }
+
+}
diff --git a/tests/Snapshots/BackgroundShorthandSnapshotTest.php b/tests/Snapshots/BackgroundShorthandSnapshotTest.php
index 448ed703c..a5d3a7962 100644
--- a/tests/Snapshots/BackgroundShorthandSnapshotTest.php
+++ b/tests/Snapshots/BackgroundShorthandSnapshotTest.php
@@ -55,7 +55,7 @@ public function generatePdf()
Two layers
- background: url(bg.jpg) no-repeat, #ccccff. mPDF draws only the first layer, over the colour of the last: the image at the top left, once, on a light blue box.
+ background: url(bg.jpg) no-repeat, #ccccff. The image at the top left, once, on a light blue box: the colour is the last layer's.
A background that is not a background is dropped
diff --git a/tests/Snapshots/MultipleBackgroundsSnapshotTest.php b/tests/Snapshots/MultipleBackgroundsSnapshotTest.php
new file mode 100644
index 000000000..7894f8458
--- /dev/null
+++ b/tests/Snapshots/MultipleBackgroundsSnapshotTest.php
@@ -0,0 +1,77 @@
+
+
+
+ Multiple backgrounds
+
+ background: url(bg.jpg) no-repeat, linear-gradient(red, blue). The image once at the top left, over a red to blue gradient.
+
+
+ background: linear-gradient(red, blue) 0 0 / 50% 100%, url(bg.jpg). A red to blue gradient over the left half, covering the tiled image, which shows on the right half.
+
+
+ background-image: url(bg.jpg), url(bg.jpg); background-repeat: no-repeat; background-position: left top, right bottom. The image twice, once in the top left corner and once in the bottom right.
+
+
+ background: url(bg.jpg) no-repeat, #ccccff. The image once at the top left, on a light blue box: the colour is the last layer's.
+
+
+ background-image: linear-gradient(to right, red, blue), radial-gradient(yellow, green); background-size: 50% 100%, auto. A red to blue gradient over the left half; the right half shows the rest of a yellow to green radial gradient, yellow at the middle of the box and green at its right edge.
+
+
+ Three images, background-repeat: no-repeat, repeat-x; background-position: right top, left bottom, left top; background-size: 10mm. A small image once at the top right, a row of them along the bottom, and one at the top left, which takes the first repeat again.
+
+
+ A table cell with background: url(bg.jpg) no-repeat, linear-gradient(red, blue). The image once at the top left, over a red to blue gradient.
+
+
+
+
This page's @page rule has background: url(bg.jpg) no-repeat center, linear-gradient(#ffffcc, #99ccff). The image once in the middle of the page, over a pale yellow to pale blue gradient.
+
+ mpdf = $this->createMpdf();
+ $this->mpdf->SetBasePath(__DIR__ . '/../data');
+
+ $this->mpdf->WriteHTML($html);
+ }
+
+}
diff --git a/tests/data/snapshots/background-shorthand.pdf b/tests/data/snapshots/background-shorthand.pdf
index b10728698..835a8e891 100644
Binary files a/tests/data/snapshots/background-shorthand.pdf and b/tests/data/snapshots/background-shorthand.pdf differ
diff --git a/tests/data/snapshots/multiple-backgrounds.pdf b/tests/data/snapshots/multiple-backgrounds.pdf
new file mode 100644
index 000000000..0196cd6f4
Binary files /dev/null and b/tests/data/snapshots/multiple-backgrounds.pdf differ