Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG-GRAVITYPDF.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ Bugfixes
* `text-transform: none` on an inline element, such as a `<span>` in an uppercase paragraph, did not undo the transform it inherits. Under the standard `cssMode` only; set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep the transform. [#552] [#539] [#648]
* A block inside an inline element, such as `<span class="note">…<div>…</div>…</span>` or a card written as `<a href="…"><div>…</div></a>`, lost the inline element's colour, font and other inherited text properties, and so did the inline element's text after the block. A link lost its colour and underline on the block's text and did not cover it. In a table cell the block kept the style, but the text after the inline element did too. A block now inherits the text properties of the inline elements it opens in, and the text after it is drawn in their style until they end. Text before the inline element, on the line before the block, is no longer drawn in the inline element's colour or covered by its link. Under the standard `cssMode` only; set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep the old output. [#541] [#653]
* Styling a table row or row group, such as `tr.total { font-weight: bold }` or `<thead style="color: #fff">`, did not change the text of its cells, only their background. A `tr`, `thead`, `tbody` or `tfoot` now hands every inherited property to its cells, over the table's and under the cell's own, and a `tbody` rule reaches the rows written straight into a table. A row's `font-size: 80%` is taken of its row group's size, and a cell's of its row's. A row's `currentColor` is its row group's colour, and a cell's is its row's. A `th` stays bold, and is centred only when it inherits no `text-align` from its row, row group or table, as HTML's rendering rules have it. Under the standard `cssMode` only; set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep the old output. [#542] [#655]
* `text-outline-width` with no `text-outline-color` raised an undefined-key warning (`outline-COLOR`) and drew the outline black. The warning is gone in either `cssMode`. In standard mode the outline is now drawn in the colour of the text, as `text-outline-color` is `currentColor` by default, and `text-outline` with a width alone, such as `text-outline: 0.2mm`, which was ignored, outlines the text in its colour too. A text outline in `currentColor`, written or by default, is passed on as the keyword, so a `<span>` of another colour inside the outlined text is outlined in its own colour. Set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep black outlines and to ignore that shorthand. [#552] [#650] [#681]

### Images and SVG

Expand Down Expand Up @@ -950,3 +951,5 @@ These changes do not change output.
[#679]: https://github.com/GravityPDF/mpdf/pull/679
[#649]: https://github.com/GravityPDF/mpdf/issues/649
[#680]: https://github.com/GravityPDF/mpdf/pull/680
[#650]: https://github.com/GravityPDF/mpdf/issues/650
[#681]: https://github.com/GravityPDF/mpdf/pull/681
14 changes: 9 additions & 5 deletions src/Css/CssMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ class CssMerger
'BORDER-RIGHT-COLOR',
'BORDER-BOTTOM-COLOR',
'BORDER-LEFT-COLOR',
'TEXT-OUTLINE-COLOR',
];

/**
* The properties that resolve currentColor in legacy mode only. Standard mode keeps the keyword in them because
* they are inherited: each descendant draws it in its own colour, as the default of an outline with no colour
*/
const LEGACY_CURRENT_COLOR_PROPERTIES = ['TEXT-OUTLINE-COLOR'];

/**
* The other properties mPDF reads a colour from that can name currentColor, which legacy mode ignores in them. A
* background image names it in the stops of a gradient
Expand Down Expand Up @@ -417,11 +422,10 @@ private function mergeBlockExtras($tag)
*/
private function resolveCurrentColor($inherit, array $inherited = [])
{
$properties = self::CURRENT_COLOR_PROPERTIES;

if ($this->mpdf->cssMode === CssMode::STANDARD) {
$properties = array_merge($properties, self::STANDARD_CURRENT_COLOR_PROPERTIES);
$standard = $this->mpdf->cssMode === CssMode::STANDARD;
$properties = array_merge(self::CURRENT_COLOR_PROPERTIES, $standard ? self::STANDARD_CURRENT_COLOR_PROPERTIES : self::LEGACY_CURRENT_COLOR_PROPERTIES);

if ($standard) {
foreach (['BOX-SHADOW', 'TEXT-SHADOW'] as $property) {
if (isset($this->cssProperties[$property])) {
$this->cssProperties[$property] = ShadowParser::withColor($this->cssProperties[$property], 'currentcolor');
Expand Down
12 changes: 7 additions & 5 deletions src/Css/NormalizeProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -1174,23 +1174,25 @@ protected function processBorderRadiusProperty($property, $value)
/**
* Process text outline CSS properties.
*
* Handles TEXT-OUTLINE shorthand.
* Handles TEXT-OUTLINE shorthand. In standard mode a width alone sets the colour to its initial value, currentColor;
* legacy mode ignores it.
*
* @param string $v Property value
* @return void
*/
protected function processTextOutlineProperty($v)
{
$prop = preg_split('/\s+/', trim($v));
$count = count($prop);

if (strtolower(trim($v)) === 'none') {
$this->properties['TEXT-OUTLINE'] = 'none';
} elseif (count($prop) === 2) {
} elseif ($count === 2 || $count === 3) {
$this->properties['TEXT-OUTLINE-WIDTH'] = $prop[0];
$this->properties['TEXT-OUTLINE-COLOR'] = $prop[1];
} elseif (count($prop) === 3) {
$this->properties['TEXT-OUTLINE-COLOR'] = end($prop);
} elseif ($count === 1 && $this->resetsOmittedParts()) {
$this->properties['TEXT-OUTLINE-WIDTH'] = $prop[0];
$this->properties['TEXT-OUTLINE-COLOR'] = $prop[2];
$this->properties['TEXT-OUTLINE-COLOR'] = 'currentcolor';
}
}

Expand Down
25 changes: 21 additions & 4 deletions src/Mpdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -9880,11 +9880,25 @@ function _SetTextRendering($mode)
$this->pageoutput[$this->page]['TextRendering'] = $tr;
}

function SetTextOutline($params = [])
/**
* Sets the line width, stroke colour and rendering mode text is drawn with: stroked for a text outline, or else
* filled, with the width and stroke colour reset
*
* @param array $params The text parameters, which name the outline's width and colour
* @param array|string|null $textColor The colour of the text, which an outline with no colour takes in standard
* mode, as text-outline-color is currentColor by default. Null for black
* @return void
*/
function SetTextOutline($params = [], $textColor = null)
{
if (isset($params['outline-s']) && $params['outline-s']) {
$this->SetLineWidth($params['outline-WIDTH']);
$this->SetDColor($params['outline-COLOR']);
if (isset($params['outline-COLOR'])) {
$this->SetDColor($params['outline-COLOR']);
} elseif ($this->cssMode === CssMode::STANDARD) {
$this->SetDColor($textColor ?: $this->colorConverter->convert(0, $this->PDFAXwarnings));
}
// Legacy mode strokes an outline with no colour in the stroke colour already set, as mPDF v7 did
$tr = ('2 Tr');
if ($this->page > 0 && ((isset($this->pageoutput[$this->page]['TextRendering']) && $this->pageoutput[$this->page]['TextRendering'] != $tr) || !isset($this->pageoutput[$this->page]['TextRendering']))) {
$this->writer->write($tr);
Expand Down Expand Up @@ -17333,7 +17347,7 @@ function printbuffer($arrayaux, $blockstate = 0, $is_table = false, $table_draft
}
if (isset($vetor[9]) and ! empty($vetor[9])) { // Text parameters - Outline + hyphens
$this->textparam = $vetor[9];
$this->SetTextOutline($this->textparam);
$this->SetTextOutline($this->textparam, isset($vetor[3]) ? $vetor[3] : null);
// mPDF 5.7.3 inline text-decoration parameters
if ($is_table && $this->shrin_k) {
if (isset($this->textparam['text-baseline'])) {
Expand Down Expand Up @@ -20009,7 +20023,10 @@ function setCSS($arrayaux, $type = '', $tag = '')
break;

case 'TEXT-OUTLINE-COLOR':
if (strtoupper($v) == 'INVERT') {
if (strtolower($v) === 'currentcolor') {
// Left unset, the outline is drawn in the colour of each piece of text
unset($this->textparam['outline-COLOR']);
} elseif (strtoupper($v) == 'INVERT') {
if ($this->colorarray) {
$cor = $this->colorarray;
$this->textparam['outline-COLOR'] = $this->colorConverter->invert($cor);
Expand Down
118 changes: 115 additions & 3 deletions tests/Mpdf/CurrentColorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ class CurrentColorTest extends TestCase
/** A border's colour in legacy mode when it names none */
const BLACK = '0.000 0.000 0.000 rg';

/** A text outline stroked in #0a0 */
const GREEN_OUTLINE = '0.000 0.667 0.000 RG 2 Tr BT';

/** A text outline stroked in #c00 */
const RED_OUTLINE = '0.800 0.000 0.000 RG 2 Tr BT';

/** A text outline stroked in #00f */
const BLUE_OUTLINE = '0.000 0.000 1.000 RG 2 Tr BT';

/** A text outline stroked in black, as legacy mode strokes one that names no colour */
const BLACK_OUTLINE = 'w 0.000 G 2 Tr BT';

/** A shadow's colour in legacy mode when it names none: #888888 */
const GREY = '0.533 0.533 0.533 rg';

Expand Down Expand Up @@ -339,7 +351,8 @@ public function boxShadows()

/**
* In standard mode every property mPDF reads a colour from resolves currentColor to the element's colour when its
* properties are merged, in a value with other parts and in a gradient, and not in an image's address
* properties are merged, in a value with other parts and in a gradient, and not in an image's address. A text
* outline keeps the keyword, which is drawn in the colour of each piece of text
*/
public function testEveryColourPropertyResolvesCurrentColor()
{
Expand All @@ -351,7 +364,7 @@ public function testEveryColourPropertyResolvesCurrentColor()
$this->assertStringEndsWith(' #0a0', $properties['BORDER-RIGHT']);
$this->assertSame('1mm 1mm #0a0, inset 1mm 1mm rgb(0,0,255)', $properties['BOX-SHADOW']);
$this->assertSame('1mm 1mm #0a0', $properties['TEXT-SHADOW']);
$this->assertSame('#0a0', $properties['TEXT-OUTLINE-COLOR']);
$this->assertSame('currentcolor', strtolower($properties['TEXT-OUTLINE-COLOR']));

$gradient = $this->merged('BLOCK', 'DIV', self::GRADIENT, CssMode::STANDARD);
$url = $this->merged('BLOCK', 'DIV', 'color: #0a0; background-image: url(currentcolor.png)', CssMode::STANDARD);
Expand Down Expand Up @@ -402,7 +415,7 @@ public function testATextOutlineInCurrentColorIsDrawnInTheTextsColour($mode)
{
$pdf = $this->uncompressed('<p style="color: #0a0; text-outline: 0.1mm currentColor">subject</p>', $mode);

$this->assertStringContainsString('0.000 0.667 0.000 RG 2 Tr BT', $pdf);
$this->assertStringContainsString(self::GREEN_OUTLINE, $pdf);
}

/**
Expand Down Expand Up @@ -561,6 +574,105 @@ private function inModesAndContexts(array $contexts, array $cases)
return $data;
}

/**
* The issue's document: a text outline with no colour, and none for the text, is drawn in black with no warning
* (#650)
*
* @dataProvider modes
*
* @param string $mode
*/
public function testATextOutlineWithNoColourIsDrawnWithoutAWarning($mode)
{
$pdf = $this->uncompressed('<p style="text-outline-width: 0.2mm">subject</p>', $mode);

$this->assertStringContainsString(self::BLACK_OUTLINE, $pdf);
}

/**
* In standard mode a text outline with no colour is drawn in the colour of the text, which it inherits here.
* Legacy mode keeps the black it drew before
*
* @dataProvider outlinesWithNoColourInContexts
*
* @param string $mode
* @param string $context
* @param string $style
* @param string $expected
*/
public function testATextOutlineWithNoColourTakesTheTextsColour($mode, $context, $style, $expected)
{
$pdf = $this->uncompressed($this->subject($context, $style, 'color: #0a0'), $mode);

$this->assertStringContainsString($expected, $pdf);
}

/**
* A text outline with no colour in each context, with the stroke each mode draws it in
*
* @return array[]
*/
public function outlinesWithNoColourInContexts()
{
return $this->inModesAndContexts(self::CONTEXTS, ['no colour' => ['text-outline-width: 0.2mm', self::GREEN_OUTLINE, self::BLACK_OUTLINE]]);
}

/**
* In standard mode a text outline in currentColor, written or by default, strokes each piece of text in that
* piece's colour, as currentColor is inherited as the keyword
*
* @dataProvider outlinesInCurrentColor
*
* @param string $style
*/
public function testATextOutlineInCurrentColorFollowsEachPieceOfText($style)
{
$pdf = $this->uncompressed('<p style="color: #0a0; ' . $style . '">before <span style="color: #00f">inside</span> after</p>', CssMode::STANDARD);

$this->assertSame(2, substr_count($pdf, self::GREEN_OUTLINE));
$this->assertSame(1, substr_count($pdf, self::BLUE_OUTLINE));
}

/**
* Text outlines in currentColor: with no colour, the shorthand with a width alone, which resets a colour given
* before it, and the colour written
*
* @return string[][]
*/
public function outlinesInCurrentColor()
{
return [
'no colour' => ['text-outline-width: 0.2mm'],
'shorthand with a width alone' => ['text-outline: 0.2mm'],
'shorthand after a colour' => ['text-outline-color: #c00; text-outline: 0.2mm'],
'currentColor' => ['text-outline-width: 0.2mm; text-outline-color: currentColor'],
];
}

/**
* Legacy mode ignores the text-outline shorthand with a width alone, as it did
*/
public function testLegacyModeIgnoresTheTextOutlineShorthandWithAWidthAlone()
{
$pdf = $this->uncompressed('<p style="color: #0a0; text-outline: 0.2mm">subject</p>', CssMode::LEGACY);

$this->assertStringNotContainsString('2 Tr BT', $pdf);
}

/**
* An outline colour inherited from a parent is kept by a child that sets only a width
*
* @dataProvider modes
*
* @param string $mode
*/
public function testAnInheritedTextOutlineColourIsKept($mode)
{
$pdf = $this->uncompressed('<p style="color: #0a0; text-outline-color: #c00">before <span style="text-outline-width: 0.2mm">inside</span></p>', $mode);

$this->assertStringContainsString(self::RED_OUTLINE, $pdf);
}

/**
* Both modes
*
Expand Down