diff --git a/CHANGELOG-GRAVITYPDF.md b/CHANGELOG-GRAVITYPDF.md index c31d56d83..4e4d5967c 100644 --- a/CHANGELOG-GRAVITYPDF.md +++ b/CHANGELOG-GRAVITYPDF.md @@ -177,10 +177,10 @@ 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. [#552] [#575] -* `@import` ignored its media list, and did not load a URL that does not end in `.css`, such as a Google Fonts stylesheet. [#552] [#575] +* `@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 ` + +
@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/LineHeightZeroTest.php b/tests/Mpdf/LineHeightZeroTest.php index 6289996db..fac7b4306 100644 --- a/tests/Mpdf/LineHeightZeroTest.php +++ b/tests/Mpdf/LineHeightZeroTest.php @@ -4,7 +4,8 @@ /** * line-height: 0 gives lines no height, rather than being read as normal, a line height smaller than the font is - * kept rather than stretched down to the baseline, and a negative line-height is ignored. + * kept rather than stretched down to the baseline, and a negative line-height is ignored. In cssMode legacy each is + * read as mPDF v7 read it. */ class LineHeightZeroTest extends \Yoast\PHPUnitPolyfills\TestCases\TestCase { @@ -36,6 +37,19 @@ public function providerZero() ]; } + /** + * In cssMode legacy a line-height of zero is returned as normal: as a number, the normal line height of the font, + * whatever the PHP version, and as a length, the normal factor + */ + public function testZeroIsNotAZeroLineHeightInLegacyMode() + { + $mpdf = new Mpdf(['cssMode' => CssMode::LEGACY]); + + $this->assertSame('N', $mpdf->fixLineheight('0')); + $this->assertSame('N', $mpdf->fixLineheight('0.0')); + $this->assertSame($mpdf->normalLineheight, $mpdf->fixLineheight('0px')); + } + /** * Values other than zero are returned as they were */ @@ -49,53 +63,70 @@ public function testOtherValuesAreUnchanged() } /** + * Lines take the height line-height gives them. In cssMode legacy line-height: 0 is read as normal, or on a table + * as not set, and a line height smaller than the font is stretched down to the baseline + * * @dataProvider providerHeights * * @param string $html The document * @param float $height How far it moves down the page, in mm + * @param float $legacy How far it moves down the page in legacy mode, in mm */ - public function testBlockHeight($html, $height) + public function testBlockHeight($html, $height, $legacy) { - $this->assertEqualsWithDelta($height, $this->heightOf($html), 0.001); + $this->assertEqualsWithDelta($height, $this->heightOf($html), 0.001, 'standard'); + $this->assertEqualsWithDelta($legacy, $this->heightOf($html, CssMode::LEGACY), 0.01, 'legacy'); } /** - * @return array[] Documents of three lines of 10pt text, and their height + * @return array[] Documents of three lines of 10pt text, their height, and their height in legacy mode */ public function providerHeights() { $lines = 'aa' . $lines . '
', 0.0], - 'zero length on a paragraph' => ['' . $lines . '
', 0.0], - 'zero inherited from a div' => ['' . $lines . '
' . $lines . '
', 0.0, 14.04], + 'zero length on a paragraph' => ['' . $lines . '
', 0.0, 14.08], + 'zero inherited from a div' => ['' . $lines . '
| ' . $lines . ' |
| ' . $lines . ' |
| ' . $lines . ' |
' . $lines . '
', 3.0], - 'a factor smaller than the font' => ['' . $lines . '
', 1.5 * 10 / Mpdf::SCALE], + 'a length smaller than the font' => ['' . $lines . '
', 3.0, 5.16], + 'a factor smaller than the font' => ['' . $lines . '
', 1.5 * 10 / Mpdf::SCALE, 6.31], ]; } /** + * A negative line-height is ignored. In cssMode legacy it is applied, and squashes the lines + * * @dataProvider providerNegative * * @param string $negative A document with a negative line-height * @param string $without The same document without it + * @param float $legacy How far the document with it moves down the page in legacy mode, in mm */ - public function testNegativeLineHeightIsIgnored($negative, $without) + public function testNegativeLineHeight($negative, $without, $legacy) { - $this->assertEqualsWithDelta($this->heightOf($without), $this->heightOf($negative), 0.001); + $this->assertEqualsWithDelta($this->heightOf($without), $this->heightOf($negative), 0.001, 'standard'); + $this->assertEqualsWithDelta($legacy, $this->heightOf($negative, CssMode::LEGACY), 0.01, 'legacy'); } /** - * @return array[] Documents with a negative line-height, and the same documents without it + * @return array[] Documents with a negative line-height, the same documents without it, and the height of the + * first in legacy mode */ public function providerNegative() { @@ -105,30 +136,35 @@ public function providerNegative() 'number' => [ '' . $lines . '
' . $lines . '
' . $lines . '
' . $lines . '
' . $lines . '
' . $lines . '
| ' . $lines . ' |
| ' . $lines . ' |
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]); } /** - * Atext
'); + $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/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(); + ?> + + +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
+ +First
Second
Third
The green line after line-height: 1mm
+First
Second
Third
The green line after line-height: 0.5
+First
Second
Third
First
Second
Third
First
Second
Third
| First Second Third |
+ First Second |
+ One line | +
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
- -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
-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
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 | -
@media print
+@media not screen
+@media not print
+@media screen
+@media print and (orientation: portrait)
+@media print and (orientation: landscape)
+@media (min-width: 768px)
+@media (700px <= width <= 800px)
+@media (max-width: 768px)
+@media (min-width: 1000px)
+@media (hover: hover)
+<style media="not print">
+@media print and (orientation: landscape)
+@media (min-width: 1000px)
+@media print and (orientation: portrait)
+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
-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)
-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)
-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">
-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)
-