From e7f6df6f974d120fcce29d352e1de3d7e18c5a0b Mon Sep 17 00:00:00 2001 From: Jake Jackson Date: Wed, 30 Sep 2026 18:08:31 +1000 Subject: [PATCH 1/2] Match @media and @import media queries as before under cssMode legacy In legacy mode MediaQueryProcessor::matches() applies a media query list when it contains the medium CSSselectMedia names, or all, anywhere, as it did before #575. That covers @media blocks and the media attributes of + +

@import "text.css";

'; + + $this->assertSame( + ['screen.css', 'print.css', 'layered.css', 'fallback.css', 'versioned.css?v=2', 'third.css', 'wide.css', 'narrow.css', 'second.css'], + $this->cssLoader->extractExternalStylesheetUrls($html) + ); + } + /** * Only an @import among a stylesheet's rules is loaded: not one in a comment, a string or a block, and not one - * after a block left open + * after a block left open. The same holds in cssMode legacy, which does not load the URL without .css */ public function testExtractExternalStylesheetUrlsReadsOnlyImportRules() { @@ -129,6 +158,9 @@ public function testExtractExternalStylesheetUrlsReadsOnlyImportRules() ['https://fonts.example/css2?family=Inter:wght@300;400&display=swap', 'after.css', 'next.css'], $this->cssLoader->extractExternalStylesheetUrls($html) ); + + $this->mpdf->cssMode = CssMode::LEGACY; + $this->assertSame(['after.css', 'next.css'], $this->cssLoader->extractExternalStylesheetUrls($html)); } /** @@ -170,6 +202,27 @@ public function testProcessExternalCssImportsQueuesImportsThatMatch() $this->assertSame(2, $count); } + /** + * In cssMode legacy an @import in a loaded stylesheet is queued when it is written as url(), whatever follows it, + * and not when it is a string or in a comment + */ + public function testProcessExternalCssImportsQueuesEachUrlInLegacyMode() + { + $this->mpdf->cssMode = CssMode::LEGACY; + $externalCss = []; + $count = 0; + + $this->cssLoader->processExternalCssImports( + '/* @import url(old.css); */ @import "base.css"; @import url(screen.css) screen; @import url(print.css) print; p { color: red; }', + 'http://example.com/css/main.css', + $externalCss, + $count + ); + + $this->assertSame(['http://example.com/css/screen.css', 'http://example.com/css/print.css'], $externalCss); + $this->assertSame(2, $count); + } + public function testResolveBackgroundUrls() { $css = 'body { background: url(images/bg.jpg); }'; diff --git a/tests/Mpdf/Css/MediaQueryProcessorTest.php b/tests/Mpdf/Css/MediaQueryProcessorTest.php index e6461e9fd..9147701af 100644 --- a/tests/Mpdf/Css/MediaQueryProcessorTest.php +++ b/tests/Mpdf/Css/MediaQueryProcessorTest.php @@ -2,6 +2,7 @@ namespace Mpdf\Css; +use Mpdf\CssMode; use Mpdf\Mpdf; use Mpdf\SizeConverter; use Psr\Log\NullLogger; @@ -81,77 +82,81 @@ public function testEveryListMatchesWithoutCssSelectMedia() /** * A media query list is matched against the print medium and a portrait A4 page, 210mm (about 794px) by 297mm - * (about 1123px) + * (about 1123px). In cssMode legacy it applies when it contains print or all anywhere, whatever the page * * @dataProvider mediaQueryLists * * @param string $mediaQueryList * @param bool $expected + * @param bool $legacy Whether it applies in legacy mode */ - public function testMatchesAPortraitA4Page($mediaQueryList, $expected) + public function testMatchesAPortraitA4Page($mediaQueryList, $expected, $legacy) { - $this->assertSame($expected, $this->processor->matches($mediaQueryList)); + $this->assertSame($expected, $this->processor->matches($mediaQueryList), 'standard'); + + $this->mpdf->cssMode = CssMode::LEGACY; + $this->assertSame($legacy, $this->processor->matches($mediaQueryList), 'legacy'); } /** - * Media query lists, and whether each applies to print on a portrait A4 page + * Media query lists, whether each applies to print on a portrait A4 page, and whether it does in legacy mode * * @return array[] */ public function mediaQueryLists() { return [ - 'an empty list' => ['', true], - 'print' => ['print', true], - 'print in upper case' => ['PRINT', true], - 'all' => ['all', true], - 'only print' => ['only print', true], - 'screen' => ['screen', false], - 'an unknown type' => ['tv', false], - 'a list naming print second' => ['screen, print', true], - 'not print' => ['not print', false], - 'not all' => ['not all', false], - 'not screen' => ['not screen', true], - 'only screen with a condition that holds' => ['only screen and (min-width: 1px)', false], - 'not screen with a condition' => ['not screen and (min-width: 1px)', true], - - 'portrait' => ['(orientation: portrait)', true], - 'landscape' => ['print and (orientation: landscape)', false], - 'not landscape' => ['not print and (orientation: landscape)', true], - 'an unknown orientation' => ['(orientation: sideways)', false], - - 'a min-width the page is wider than' => ['(min-width: 768px)', true], - 'a min-width the page is narrower than' => ['(min-width: 1000px)', false], - 'a max-width the page is wider than' => ['(max-width: 768px)', false], - 'a max-width the page is narrower than' => ['print and (max-width: 800px)', true], - 'the page width in mm' => ['(width: 210mm)', true], - 'a width between two lengths' => ['(min-width: 20cm) and (max-width: 22cm)', true], - 'a min-height in inches' => ['(min-height: 11in)', true], - 'a min-width in em' => ['(min-width: 40em)', true], - 'a min-width in rem the page is narrower than' => ['(min-width: 100rem)', false], - 'a zero without a unit' => ['(min-width: 0)', true], - 'a number without a unit' => ['(min-width: 10)', false], - 'a length mPDF cannot read' => ['(min-width: calc(1px + 1px))', false], - 'the width in a boolean context' => ['(width)', true], - - 'range: width >= a length' => ['(width >= 600px)', true], - 'range: width < a length' => ['(width < 600px)', false], - 'range: a length <= width' => ['(600px <= width)', true], - 'range: width between two lengths' => ['(700px < width < 800px)', true], - 'range: width between two lengths it is outside' => ['(800px < width <= 900px)', false], - 'range: height from the top' => ['(1200px > height > 1000px)', true], - 'range: two operators facing apart' => ['(700px < width > 800px)', false], - - 'and' => ['(min-width: 1px) and (max-width: 1px)', false], - 'or' => ['(max-width: 1px) or (min-height: 1px)', true], - 'not' => ['not (max-width: 600px)', true], - 'nested parentheses' => ['print and ((max-width: 1px) or (orientation: portrait))', true], - 'and mixed with or' => ['(min-width: 1px) and (min-height: 1px) or (width)', false], - 'an unknown feature' => ['(hover: hover)', false], - 'an unknown feature negated' => ['not (hover: hover)', false], - 'an unknown feature beside one that holds, with or' => ['(hover: hover) or (orientation: portrait)', true], - 'a condition left unfinished' => ['print and', false], - 'unbalanced parentheses' => ['(min-width: 1px', false], + 'an empty list' => ['', true, false], + 'print' => ['print', true, true], + 'print in upper case' => ['PRINT', true, true], + 'all' => ['all', true, true], + 'only print' => ['only print', true, true], + 'screen' => ['screen', false, false], + 'an unknown type' => ['tv', false, false], + 'a list naming print second' => ['screen, print', true, true], + 'not print' => ['not print', false, true], + 'not all' => ['not all', false, true], + 'not screen' => ['not screen', true, false], + 'only screen with a condition that holds' => ['only screen and (min-width: 1px)', false, false], + 'not screen with a condition' => ['not screen and (min-width: 1px)', true, false], + + 'portrait' => ['(orientation: portrait)', true, false], + 'landscape' => ['print and (orientation: landscape)', false, true], + 'not landscape' => ['not print and (orientation: landscape)', true, true], + 'an unknown orientation' => ['(orientation: sideways)', false, false], + + 'a min-width the page is wider than' => ['(min-width: 768px)', true, false], + 'a min-width the page is narrower than' => ['(min-width: 1000px)', false, false], + 'a max-width the page is wider than' => ['(max-width: 768px)', false, false], + 'a max-width the page is narrower than' => ['print and (max-width: 800px)', true, true], + 'the page width in mm' => ['(width: 210mm)', true, false], + 'a width between two lengths' => ['(min-width: 20cm) and (max-width: 22cm)', true, false], + 'a min-height in inches' => ['(min-height: 11in)', true, false], + 'a min-width in em' => ['(min-width: 40em)', true, false], + 'a min-width in rem the page is narrower than' => ['(min-width: 100rem)', false, false], + 'a zero without a unit' => ['(min-width: 0)', true, false], + 'a number without a unit' => ['(min-width: 10)', false, false], + 'a length mPDF cannot read' => ['(min-width: calc(1px + 1px))', false, false], + 'the width in a boolean context' => ['(width)', true, false], + + 'range: width >= a length' => ['(width >= 600px)', true, false], + 'range: width < a length' => ['(width < 600px)', false, false], + 'range: a length <= width' => ['(600px <= width)', true, false], + 'range: width between two lengths' => ['(700px < width < 800px)', true, false], + 'range: width between two lengths it is outside' => ['(800px < width <= 900px)', false, false], + 'range: height from the top' => ['(1200px > height > 1000px)', true, false], + 'range: two operators facing apart' => ['(700px < width > 800px)', false, false], + + 'and' => ['(min-width: 1px) and (max-width: 1px)', false, false], + 'or' => ['(max-width: 1px) or (min-height: 1px)', true, false], + 'not' => ['not (max-width: 600px)', true, false], + 'nested parentheses' => ['print and ((max-width: 1px) or (orientation: portrait))', true, true], + 'and mixed with or' => ['(min-width: 1px) and (min-height: 1px) or (width)', false, false], + 'an unknown feature' => ['(hover: hover)', false, false], + 'an unknown feature negated' => ['not (hover: hover)', false, false], + 'an unknown feature beside one that holds, with or' => ['(hover: hover) or (orientation: portrait)', true, false], + 'a condition left unfinished' => ['print and', false, true], + 'unbalanced parentheses' => ['(min-width: 1px', false, false], ]; } @@ -187,4 +192,44 @@ public function testFilterByMediaQueryLeavesOutANegatedMatch() $this->assertSame('', $this->processor->filterByMediaQuery('', $pattern)); } + + /** + * In cssMode legacy the page is not read: on a landscape page, a query naming print applies whatever its + * orientation, and one naming no medium does not + */ + public function testLegacyModeDoesNotReadThePage() + { + $this->mpdf->cssMode = CssMode::LEGACY; + $this->mpdf->AddPage('L'); + + $this->assertTrue($this->processor->matches('print and (orientation: portrait)')); + $this->assertFalse($this->processor->matches('(orientation: landscape)')); + $this->assertFalse($this->processor->matches('(min-width: 1000px)')); + } + + /** + * In cssMode legacy, with CSSselectMedia set to screen, a list containing screen applies, negated or not, and + * print does not + */ + public function testLegacyModeMatchesAnotherMediumByName() + { + $this->mpdf->cssMode = CssMode::LEGACY; + $this->mpdf->CSSselectMedia = 'screen'; + + $this->assertTrue($this->processor->matches('not screen')); + $this->assertTrue($this->processor->matches('screen and (max-width: 1px)')); + $this->assertFalse($this->processor->matches('print')); + } + + /** + * In cssMode legacy a '; + + $this->assertSame($html, $this->processor->filterByMediaQuery($html, $pattern)); + } } diff --git a/tests/Mpdf/CssManagerTest.php b/tests/Mpdf/CssManagerTest.php index b18279599..3b2d4a71b 100644 --- a/tests/Mpdf/CssManagerTest.php +++ b/tests/Mpdf/CssManagerTest.php @@ -213,6 +213,7 @@ public function testReadCSS_ImportMatchesMediaAndAnyUrl() $this->createCssFile('screen.css', 'h1 { color: #ff0000; }'); $this->createCssFile('theme', 'p { color: #00ff00; }'); $this->mpdf->CSSselectMedia = 'print'; + $this->mpdf->cssMode = CssMode::STANDARD; $this->cssManager->ReadCSS(''); @@ -220,6 +221,22 @@ public function testReadCSS_ImportMatchesMediaAndAnyUrl() $this->assertSame('#00ff00', $this->cssManager->CSS['P']['COLOR']); } + /** + * In cssMode legacy an @import is loaded whatever its media query list when its URL ends in .css, and not + * otherwise + */ + public function testReadCSS_ImportLoadsEachCssFileInLegacyMode() + { + $this->createCssFile('screen.css', 'h1 { color: #ff0000; }'); + $this->createCssFile('theme', 'p { color: #00ff00; }'); + $this->mpdf->CSSselectMedia = 'print'; + + $this->cssManager->ReadCSS(''); + + $this->assertSame('#ff0000', $this->cssManager->CSS['H1']['COLOR']); + $this->assertArrayNotHasKey('P', $this->cssManager->CSS); + } + public function testReadCSS_WithBackgroundUrlRewriting() { $this->createCssFile('theme.css', '.header { background: url(images/logo.png); }'); diff --git a/tests/Mpdf/MediaQueryTest.php b/tests/Mpdf/MediaQueryTest.php index 39b7bb99b..79215cb9e 100644 --- a/tests/Mpdf/MediaQueryTest.php +++ b/tests/Mpdf/MediaQueryTest.php @@ -6,7 +6,8 @@ /** * @media blocks and media attributes, matched against the print medium and the page's size and orientation rather - * than by looking for the word print in them + * than by looking for the word print in them. In cssMode legacy they are matched by looking for the word, as mPDF v7 + * did. */ class MediaQueryTest extends TestCase { @@ -21,50 +22,76 @@ class MediaQueryTest extends TestCase * * @dataProvider mediaQueries * + * @param string $mode * @param string $mediaQueryList * @param string $expected */ - public function testAnAtMediaBlockAppliesWhenItsQueryMatches($mediaQueryList, $expected) + public function testAnAtMediaBlockAppliesWhenItsQueryMatches($mode, $mediaQueryList, $expected) { - $colours = $this->textColours('

text

'); + $colours = $this->textColours( + '

text

', + ['cssMode' => $mode] + ); $this->assertSame(['text' => $expected], $colours); } /** - * Media query lists, and the colour the paragraph is drawn in + * Media query lists, and the colour the paragraph is drawn in each mode * * @return array[] */ public function mediaQueries() { return [ - 'not print' => ['not print', self::BLACK], - 'landscape' => ['print and (orientation: landscape)', self::BLACK], - 'a min-width narrower than the page' => ['(min-width: 768px)', self::GREEN], + 'not print' => [CssMode::STANDARD, 'not print', self::BLACK], + 'landscape' => [CssMode::STANDARD, 'print and (orientation: landscape)', self::BLACK], + 'a min-width narrower than the page' => [CssMode::STANDARD, '(min-width: 768px)', self::GREEN], + 'legacy: not print' => [CssMode::LEGACY, 'not print', self::GREEN], + 'legacy: landscape' => [CssMode::LEGACY, 'print and (orientation: landscape)', self::GREEN], + 'legacy: a min-width narrower than the page' => [CssMode::LEGACY, '(min-width: 768px)', self::BLACK], ]; } /** - * A block for landscape pages applies on a landscape page + * A block for landscape pages applies on a landscape page, in either mode + * + * @dataProvider eachMode + * + * @param string $mode */ - public function testALandscapeBlockAppliesOnALandscapePage() + public function testALandscapeBlockAppliesOnALandscapePage($mode) { $pages = $this->pages($this->render( '

text

', - ['format' => 'A4-L'] + ['format' => 'A4-L', 'cssMode' => $mode] )); $this->assertStringContainsString(self::GREEN, $pages[0]); } /** - * A

text

'); + $colours = $this->textColours('

text

', ['cssMode' => $mode]); + + $this->assertSame(['text' => $mode === CssMode::LEGACY ? self::GREEN : self::BLACK], $colours); + } - $this->assertSame(['text' => self::BLACK], $colours); + /** + * @return array[] Each value of cssMode + */ + public function eachMode() + { + return [ + 'standard' => [CssMode::STANDARD], + 'legacy' => [CssMode::LEGACY], + ]; } } diff --git a/tests/Snapshots/MediaQueryLegacySnapshotTest.php b/tests/Snapshots/MediaQueryLegacySnapshotTest.php new file mode 100644 index 000000000..5ad03b62e --- /dev/null +++ b/tests/Snapshots/MediaQueryLegacySnapshotTest.php @@ -0,0 +1,48 @@ + 'The queries containing print apply: the first paragraph is green and bold, and the third large and red. not screen and screen do not: the second and fourth are black at the normal size.', + 'orientation' => 'Both queries contain print, so both apply whatever the page: the first paragraph is green and bold, the second large and red.', + 'width' => 'No query contains print or all, so none applies: all four paragraphs are black at the normal size.', + 'unknown' => 'The (hover: hover) query contains neither print nor all: the first paragraph is black at the normal size. The media attribute not print contains print, so its block applies: the second paragraph is large and red.', + 'landscape' => 'The queries containing print apply whatever the page, and the width query does not: the first paragraph is green and bold, the second black at the normal size, the third large and red.', + ]; + } + +} diff --git a/tests/Snapshots/MediaQuerySnapshot.php b/tests/Snapshots/MediaQuerySnapshot.php new file mode 100644 index 000000000..6958ac2df --- /dev/null +++ b/tests/Snapshots/MediaQuerySnapshot.php @@ -0,0 +1,121 @@ +captions(); + + ob_start(); + ?> + + + +

Media queries on a portrait A4 page

+ +

Media types

+

+
+

@media print

+

@media not screen

+

@media not print

+

@media screen

+
+ +

Orientation

+

+
+

@media print and (orientation: portrait)

+

@media print and (orientation: landscape)

+
+ +

Width

+

+
+

@media (min-width: 768px)

+

@media (700px <= width <= 800px)

+

@media (max-width: 768px)

+

@media (min-width: 1000px)

+
+ +

Unknown features and media attributes

+

+
+

@media (hover: hover)

+

<style media="not print">

+
+ + + +

Media queries on a landscape A4 page

+

+
+

@media print and (orientation: landscape)

+

@media (min-width: 1000px)

+

@media print and (orientation: portrait)

+
+ mpdf = $this->createMpdf(['cssMode' => $this->mode()]); + + $this->mpdf->WriteHTML($portrait); + $this->mpdf->AddPage('L'); + $this->mpdf->WriteHTML($landscape); + } + +} diff --git a/tests/Snapshots/MediaQuerySnapshotTest.php b/tests/Snapshots/MediaQuerySnapshotTest.php index c17e36f1b..450f208ec 100644 --- a/tests/Snapshots/MediaQuerySnapshotTest.php +++ b/tests/Snapshots/MediaQuerySnapshotTest.php @@ -2,13 +2,15 @@ namespace Snapshots; +use Mpdf\CssMode; + /** - * @media blocks and media attributes matched against the print medium and the page's size and orientation: a - * portrait A4 page, then a landscape one. + * @media blocks and media attributes matched against the print medium and the page's size and orientation, in + * cssMode standard * * @group snapshot */ -class MediaQuerySnapshotTest extends Snapshot +class MediaQuerySnapshotTest extends MediaQuerySnapshot { /** @@ -22,98 +24,25 @@ public function getId() } /** - * Paragraphs each styled by an @media block, under a caption saying what should be seen + * @return string */ - public function generatePdf() + protected function mode() { - ob_start(); - ?> - - - -

Media queries on a portrait A4 page

- -

Media types

-

print and not screen apply: the first two paragraphs are green and bold. not print and screen do not: the last two are black at the normal size.

-
-

@media print

-

@media not screen

-

@media not print

-

@media screen

-
- -

Orientation

-

The page is portrait: the first paragraph is green and bold, the second black at the normal size.

-
-

@media print and (orientation: portrait)

-

@media print and (orientation: landscape)

-
- -

Width

-

The page is 210mm, about 794px, wide: the first two paragraphs are green and bold, the last two black at the normal size.

-
-

@media (min-width: 768px)

-

@media (700px <= width <= 800px)

-

@media (max-width: 768px)

-

@media (min-width: 1000px)

-
- -

Unknown features and media attributes

-

A feature mPDF does not know makes its query fail, and a <style media="not print"> block is left out: both paragraphs are black at the normal size.

-
-

@media (hover: hover)

-

<style media="not print">

-
- - - -

Media queries on a landscape A4 page

-

This page's stylesheet is matched against this page, 297mm (about 1123px) wide: the first two paragraphs are green and bold, the third black at the normal size.

-
-

@media print and (orientation: landscape)

-

@media (min-width: 1000px)

-

@media print and (orientation: portrait)

-
- mpdf = $this->createMpdf(); + return CssMode::STANDARD; + } - $this->mpdf->WriteHTML($portrait); - $this->mpdf->AddPage('L'); - $this->mpdf->WriteHTML($landscape); + /** + * @return string[] + */ + protected function captions() + { + return [ + 'types' => 'print and not screen apply: the first two paragraphs are green and bold. not print and screen do not: the last two are black at the normal size.', + 'orientation' => 'The page is portrait: the first paragraph is green and bold, the second black at the normal size.', + 'width' => 'The page is 210mm, about 794px, wide: the first two paragraphs are green and bold, the last two black at the normal size.', + 'unknown' => 'A feature mPDF does not know makes its query fail, and a <style media="not print"> block is left out: both paragraphs are black at the normal size.', + 'landscape' => 'This page\'s stylesheet is matched against this page, 297mm (about 1123px) wide: the first two paragraphs are green and bold, the third black at the normal size.', + ]; } } diff --git a/tests/data/snapshots/media-queries-legacy.pdf b/tests/data/snapshots/media-queries-legacy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6e09daa23604f965d2657dae21d3b57330c977f5 GIT binary patch literal 37288 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 Date: Wed, 30 Sep 2026 18:08:35 +1000 Subject: [PATCH 2/2] Read line-height: 0 and a negative line-height as before under cssMode legacy In legacy mode fixLineheight() returns a zero line height as normal, and a table skips line-height: 0 and takes the block's line height. A line box starts at the baseline, so a line height smaller than the font is stretched down to it. A negative line-height is kept, on its own and in the font shorthand. fixLineheight() used to return a factor of 0, which _computeLineheight() took for 'N' before PHP 8 and for the configured normal factor on PHP 8, so a table cell with line-height: 0 was drawn at two heights. It returns 'N', as older PHP read it. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG-GRAVITYPDF.md | 2 +- src/Css/NormalizeProperties.php | 15 ++- src/Mpdf.php | 13 +- src/Tag/Table.php | 7 +- tests/Mpdf/LineHeightZeroTest.php | 64 +++++++--- .../LineHeightZeroLegacySnapshotTest.php | 50 ++++++++ tests/Snapshots/LineHeightZeroSnapshot.php | 118 ++++++++++++++++++ .../Snapshots/LineHeightZeroSnapshotTest.php | 110 ++++------------ .../snapshots/line-height-zero-legacy.pdf | Bin 0 -> 36211 bytes 9 files changed, 269 insertions(+), 110 deletions(-) create mode 100644 tests/Snapshots/LineHeightZeroLegacySnapshotTest.php create mode 100644 tests/Snapshots/LineHeightZeroSnapshot.php create mode 100644 tests/data/snapshots/line-height-zero-legacy.pdf diff --git a/CHANGELOG-GRAVITYPDF.md b/CHANGELOG-GRAVITYPDF.md index c36b915a3..9d4bd037a 100644 --- a/CHANGELOG-GRAVITYPDF.md +++ b/CHANGELOG-GRAVITYPDF.md @@ -175,7 +175,7 @@ Bugfixes * `border-color: rgb(255, 0, 0)` or `cmyk(0, 100, 0, 0)`, with spaces after the commas, drew black. * A declaration mPDF cannot read replaced the value before it. A `calc()`, `min()`, `max()`, `clamp()` or `var()` length became 0, so `margin: calc(…)` removed the default margins, and a colour mPDF does not know, or one written with `var()`, drew the text black. Such a declaration is now dropped, as a browser drops it, so the earlier declaration, the default or the inherited value applies. [#552] [#565] * `vw`, `vh`, `vmin`, `vmax`, `Q` and `ch` were read as pixels, `+5mm` and `1e+1mm` as 0, and `1,5mm` as 1mm. The units are now resolved against the page and the font size, the numbers are read, and `1,5mm` is dropped. [#552] [#565] -* `line-height: 0` gave lines a normal height. Lines now have no height, as in a browser. A line height well below the font size, such as `0.5` or `1mm`, was stretched down to the baseline; it is now kept. A negative `line-height`, which shrank lines to odd heights, is now ignored. [#552] [#567] +* `line-height: 0` gave lines a normal height. Lines now have no height, as in a browser. A line height well below the font size, such as `0.5` or `1mm`, was stretched down to the baseline; it is now kept. A negative `line-height`, which shrank lines to odd heights, is now ignored. Set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep the old reading of all three. [#552] [#567] [#643] * The rule after an `@supports`, `@layer`, `@keyframes`, `@container` or other block at-rule was lost, and so were the rules inside `@supports` and `@layer`. The rule after `@charset`, `@namespace` or `@import` was lost too. `@supports` and `@layer` blocks are now unwrapped as `@media` blocks are, except `@supports not`, and other at-rules are removed whole. [#524] [#566] * `@media` was matched by looking for the word `print`, so `@media not print` applied, `orientation` was ignored and `min-width` never matched. Media queries are now evaluated for the print medium against the page current when the stylesheet is read: media types with `not` and `only`; `and`, `or` and `not` conditions; and `width`, `height` and `orientation`, with `min-`/`max-` prefixes and range syntax such as `(width >= 600px)`. An A4 page is about 794px wide, so `@media (min-width: 768px)` rules now apply to it. Set `'cssMode' => \Mpdf\CssMode::LEGACY` to keep matching by the word `print` or `all`. [#552] [#575] [#643] * `@import` ignored its media list, and did not load a URL that does not end in `.css`, such as a Google Fonts stylesheet. Legacy mode loads them as before, except that in either mode an `@import` is read only from `'); $y = $mpdf->y; diff --git a/tests/Snapshots/LineHeightZeroLegacySnapshotTest.php b/tests/Snapshots/LineHeightZeroLegacySnapshotTest.php new file mode 100644 index 000000000..219fd3671 --- /dev/null +++ b/tests/Snapshots/LineHeightZeroLegacySnapshotTest.php @@ -0,0 +1,50 @@ + 'line-height: 0 is read as normal: the three lines are normally spaced on the blue background, and the green line starts below it.', + 'zero-length' => 'line-height: 0px: the same.', + 'zero-parent' => 'line-height: 0 on the div around the paragraph: the same.', + 'one-mm' => 'line-height: 1mm: each line is stretched down to the baseline, so the lines are about 2mm apart, on a background about 6mm tall.', + 'half' => 'line-height: 0.5: each line is stretched down to the baseline, so the background is about 21pt tall rather than 18pt.', + 'negative' => 'line-height: -1 and -2mm inside a div with line-height: 2 are applied: the lines of the first two paragraphs are drawn over one another on a thin background. Only the third, which sets nothing, is double spaced.', + 'cell' => 'line-height: 0 in the middle cell is read as normal: its two lines are normally spaced, and the row is as tall as the three lines of the first cell.', + ]; + } + +} diff --git a/tests/Snapshots/LineHeightZeroSnapshot.php b/tests/Snapshots/LineHeightZeroSnapshot.php new file mode 100644 index 000000000..14124ea2f --- /dev/null +++ b/tests/Snapshots/LineHeightZeroSnapshot.php @@ -0,0 +1,118 @@ +captions(); + + ob_start(); + ?> + + +

Zero, small and negative line heights

+ +

line-height: 0

+

+
+

First
            Second
                          Third

+

The green line after line-height: 0

+
+ +

+
+

First
            Second
                          Third

+

The green line after line-height: 0px

+
+ +

+
+

First
            Second
                          Third

+
+

The green line after the div

+ +

Smaller than the font

+

+
+

First
            Second
                          Third

+

The green line after line-height: 1mm

+
+ +

+
+

First
Second
Third

+

The green line after line-height: 0.5

+
+ +

Negative line heights

+

+
+

First
Second
Third

+
+
+

First
Second
Third

+
+
+

First
Second
Third

+
+ +

Table cell

+

+ + + + + + +
First
Second
Third
First
            Second
One line
+ mpdf = $this->createMpdf(['cssMode' => $this->mode()]); + + $this->mpdf->WriteHTML($html); + } + +} diff --git a/tests/Snapshots/LineHeightZeroSnapshotTest.php b/tests/Snapshots/LineHeightZeroSnapshotTest.php index 1aec26feb..6122f5214 100644 --- a/tests/Snapshots/LineHeightZeroSnapshotTest.php +++ b/tests/Snapshots/LineHeightZeroSnapshotTest.php @@ -2,13 +2,15 @@ namespace Snapshots; +use Mpdf\CssMode; + /** * line-height: 0 giving lines no height rather than a normal one, a line height smaller than the font kept rather than - * stretched down to the baseline, and a negative line-height ignored. + * stretched down to the baseline, and a negative line-height ignored, in cssMode standard * * @group snapshot */ -class LineHeightZeroSnapshotTest extends Snapshot +class LineHeightZeroSnapshotTest extends LineHeightZeroSnapshot { /** @@ -22,95 +24,27 @@ public function getId() } /** - * Paragraphs of three short lines with a small, zero or negative line-height, each under a caption saying what - * should be seen + * @return string */ - public function generatePdf() + protected function mode() { - ob_start(); - ?> - - -

Zero, small and negative line heights

- -

line-height: 0

-

The three lines are drawn at the same height, the blue background has no height, and the green line starts straight after it, over the lower half of the text.

-
-

First
            Second
                          Third

-

The green line after line-height: 0

-
- -

line-height: 0px: the same.

-
-

First
            Second
                          Third

-

The green line after line-height: 0px

-
- -

line-height: 0 on the div around the paragraph: the same.

-
-

First
            Second
                          Third

-
-

The green line after the div

- -

Smaller than the font

-

line-height: 1mm: each line 1mm below the one before, so they overlap, on a background 3mm tall.

-
-

First
            Second
                          Third

-

The green line after line-height: 1mm

-
- -

line-height: 0.5: each line half the font size (6pt) below the one before, so they overlap, on a background 18pt tall.

-
-

First
Second
Third

-

The green line after line-height: 0.5

-
- -

Negative line heights

-

line-height: -1 and -2mm inside a div with line-height: 2 are ignored: both paragraphs are double spaced, like the third, which sets nothing.

-
-

First
Second
Third

-
-
-

First
Second
Third

-
-
-

First
Second
Third

-
- -

Table cell

-

line-height: 0 in the middle cell: its lines overlap and the row is only as tall as the other cells, which are normal.

- - - - - - -
First
Second
Third
First
            Second
One line
- mpdf = $this->createMpdf(); + return CssMode::STANDARD; + } - $this->mpdf->WriteHTML($html); + /** + * @return string[] + */ + protected function captions() + { + return [ + 'zero' => 'The three lines are drawn at the same height, the blue background has no height, and the green line starts straight after it, over the lower half of the text.', + 'zero-length' => 'line-height: 0px: the same.', + 'zero-parent' => 'line-height: 0 on the div around the paragraph: the same.', + 'one-mm' => 'line-height: 1mm: each line 1mm below the one before, so they overlap, on a background 3mm tall.', + 'half' => 'line-height: 0.5: each line half the font size (6pt) below the one before, so they overlap, on a background 18pt tall.', + 'negative' => 'line-height: -1 and -2mm inside a div with line-height: 2 are ignored: both paragraphs are double spaced, like the third, which sets nothing.', + 'cell' => 'line-height: 0 in the middle cell: its lines overlap and the row is only as tall as the other cells, which are normal.', + ]; } } diff --git a/tests/data/snapshots/line-height-zero-legacy.pdf b/tests/data/snapshots/line-height-zero-legacy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f19dec6f93e632b273765cd8a0e563a636836aed GIT binary patch literal 36211 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