Skip to content

Commit 0514b5c

Browse files
ssddanbrownbsweeney
authored andcommitted
Refactored CSS length handling into its own class
Done to extract the logic out of the style class and for easier usage elsewhere, and help reduce repetition such as extracting/checking the unit and value from the length string. Also splits out testing to its own class and simplifies AbstractTag's convertSize handling.
1 parent fd6f9db commit 0514b5c

File tree

6 files changed

+211
-165
lines changed

6 files changed

+211
-165
lines changed

src/Svg/CssLength.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace Svg;
4+
5+
class CssLength
6+
{
7+
/**
8+
* Array of valid css length units.
9+
* Should be pre-sorted so no earlier length is contained within a latter (eg. 'in' within 'vmin').
10+
*
11+
* @var string[]
12+
*/
13+
protected static $units = [
14+
'vmin',
15+
'px',
16+
'%',
17+
'pt',
18+
'cm',
19+
'mm',
20+
'q',
21+
'in',
22+
'pc',
23+
'em',
24+
'rem',
25+
'ex',
26+
'ch',
27+
'vw',
28+
'vh',
29+
'vmax'
30+
];
31+
32+
/**
33+
* A list of units that are inch-relative, and their unit division within an inch.
34+
*
35+
* @var array<string, float>
36+
*/
37+
protected static $inchDivisions = [
38+
'in' => 1,
39+
'cm' => 2.54,
40+
'mm' => 25.4,
41+
'q' => 101.6,
42+
'pc' => 6,
43+
'pt' => 72,
44+
];
45+
46+
/**
47+
* The CSS length unit indicator.
48+
* Will be lower-case and one of the units listed in the '$units' array or empty.
49+
*
50+
* @var string
51+
*/
52+
protected $unit = '';
53+
54+
/**
55+
* The numeric value of the given length.
56+
*
57+
* @var float
58+
*/
59+
protected $value = 0;
60+
61+
/**
62+
* The original unparsed length provided.
63+
*
64+
* @var string
65+
*/
66+
protected $unparsed;
67+
68+
public function __construct(string $length)
69+
{
70+
$this->unparsed = $length;
71+
$this->parseLengthComponents($length);
72+
}
73+
74+
/**
75+
* Parse out the unit and value components from the given string length.
76+
*/
77+
protected function parseLengthComponents(string $length): void
78+
{
79+
$length = strtolower($length);
80+
81+
foreach (self::$units as $unit) {
82+
$pos = strpos($length, $unit);
83+
if ($pos) {
84+
$this->value = floatval(substr($length, 0, $pos));
85+
$this->unit = $unit;
86+
return;
87+
}
88+
}
89+
90+
$this->unit = '';
91+
$this->value = floatval($length);
92+
}
93+
94+
/**
95+
* Get the unit type of this css length.
96+
* Units are standardised to be lower-cased.
97+
*
98+
* @return string
99+
*/
100+
public function getUnit(): string
101+
{
102+
return $this->unit;
103+
}
104+
105+
/**
106+
* Get this CSS length in the equivalent pixel count size.
107+
*
108+
* @param float $referenceSize
109+
* @param float $dpi
110+
*
111+
* @return float
112+
*/
113+
public function toPixels(float $referenceSize = 11.0, float $dpi = 96.0): float
114+
{
115+
// Standard relative units
116+
if (in_array($this->unit, ['em', 'rem', 'ex', 'ch'])) {
117+
return $this->value * $referenceSize;
118+
}
119+
120+
// Percentage relative units
121+
if (in_array($this->unit, ['%', 'vw', 'vh', 'vmin', 'vmax'])) {
122+
return $this->value * ($referenceSize / 100);
123+
}
124+
125+
// Inch relative units
126+
if (in_array($this->unit, array_keys(static::$inchDivisions))) {
127+
$inchValue = $this->value * $dpi;
128+
$division = static::$inchDivisions[$this->unit];
129+
return $inchValue / $division;
130+
}
131+
132+
return $this->value;
133+
}
134+
}

src/Svg/Document.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,12 @@ function ($parser, $name) {}
140140
public function handleSizeAttributes($attributes){
141141
if ($this->width === null) {
142142
if (isset($attributes["width"])) {
143-
$width = Style::convertSize($attributes["width"], 400);
143+
$width = $this->convertSize($attributes["width"], 400);
144144
$this->width = $width;
145145
}
146146

147147
if (isset($attributes["height"])) {
148-
$height = Style::convertSize($attributes["height"], 300);
148+
$height = $this->convertSize($attributes["height"], 300);
149149
$this->height = $height;
150150
}
151151

src/Svg/Style.php

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -388,104 +388,6 @@ static function parseCssStyle($style)
388388
return $styles;
389389
}
390390

391-
/**
392-
* Convert a size to a float
393-
*
394-
* @param string $size SVG size
395-
* @param float $dpi DPI
396-
* @param float $referenceSize Reference size
397-
*
398-
* @return float|null
399-
*/
400-
static function convertSize($size, $referenceSize = 11.0, $dpi = 96.0) {
401-
$size = trim(strtolower($size));
402-
403-
if (is_numeric($size)) {
404-
return $size;
405-
}
406-
407-
// Has to be before 'in' to not be mis-identified
408-
if ($pos = strpos($size, "vmin")) {
409-
$val = floatval(substr($size, 0, $pos));
410-
return $referenceSize * ($val / 100);
411-
}
412-
413-
if ($pos = strpos($size, "px")) {
414-
return floatval(substr($size, 0, $pos));
415-
}
416-
417-
if ($pos = strpos($size, "%")) {
418-
return $referenceSize * substr($size, 0, $pos) / 100;
419-
}
420-
421-
if ($pos = strpos($size, "pt")) {
422-
$val = floatval(substr($size, 0, $pos));
423-
return ($val * $dpi) / 72;
424-
}
425-
426-
if ($pos = strpos($size, "cm")) {
427-
$val = floatval(substr($size, 0, $pos));
428-
return ($val * $dpi) / 2.54;
429-
}
430-
431-
if ($pos = strpos($size, "mm")) {
432-
$val = floatval(substr($size, 0, $pos));
433-
return (($val * $dpi) / 2.54) / 10;
434-
}
435-
436-
if ($pos = strpos($size, "q")) {
437-
$val = floatval(substr($size, 0, $pos));
438-
return (($val * $dpi) / 2.54) / 40;
439-
}
440-
441-
if ($pos = strpos($size, "in")) {
442-
$val = floatval(substr($size, 0, $pos));
443-
return ($val * $dpi);
444-
}
445-
446-
if ($pos = strpos($size, "pc")) {
447-
$val = floatval(substr($size, 0, $pos));
448-
return ($val * $dpi) / 6;
449-
}
450-
451-
if ($pos = strpos($size, "em")) {
452-
$val = floatval(substr($size, 0, $pos));
453-
return $referenceSize * $val;
454-
}
455-
456-
if ($pos = strpos($size, "rem")) {
457-
$val = floatval(substr($size, 0, $pos));
458-
return $referenceSize * $val;
459-
}
460-
461-
if ($pos = strpos($size, "ex")) {
462-
$val = floatval(substr($size, 0, $pos));
463-
return $referenceSize * $val;
464-
}
465-
466-
if ($pos = strpos($size, "ch")) {
467-
$val = floatval(substr($size, 0, $pos));
468-
return $referenceSize * $val;
469-
}
470-
471-
if ($pos = strpos($size, "vw")) {
472-
$val = floatval(substr($size, 0, $pos));
473-
return $referenceSize * ($val / 100);
474-
}
475-
476-
if ($pos = strpos($size, "vh")) {
477-
$val = floatval(substr($size, 0, $pos));
478-
return $referenceSize * ($val / 100);
479-
}
480-
481-
if ($pos = strpos($size, "vmax")) {
482-
$val = floatval(substr($size, 0, $pos));
483-
return $referenceSize * ($val / 100);
484-
}
485-
486-
return null;
487-
}
488-
489391
static $colorNames = array(
490392
'antiquewhite' => '#FAEBD7',
491393
'aqua' => '#00FFFF',

src/Svg/Tag/AbstractTag.php

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace Svg\Tag;
1010

11+
use Svg\CssLength;
1112
use Svg\Document;
1213
use Svg\Style;
1314

@@ -200,51 +201,36 @@ protected function applyTransform($attributes)
200201
*/
201202
protected function convertSize(string $size, float $pxReference): float
202203
{
204+
$length = new CssLength($size);
203205
$reference = $pxReference;
204206
$defaultFontSize = 12;
205207

206-
if (strpos($size, "em")) {
207-
$reference = $this->style->fontSize ?? $defaultFontSize;
208-
return Style::convertSize($size, $reference);
208+
switch ($length->getUnit()) {
209+
case "em":
210+
$reference = $this->style->fontSize ?? $defaultFontSize;
211+
break;
212+
case "rem":
213+
$reference = $this->document->style->fontSize ?? $defaultFontSize;
214+
break;
215+
case "ex":
216+
case "ch":
217+
$emRef = $this->style->fontSize ?? $defaultFontSize;
218+
$reference = $emRef * 0.5;
219+
break;
220+
case "vw":
221+
$reference = $this->getDocument()->getWidth();
222+
break;
223+
case "vh":
224+
$reference = $this->getDocument()->getHeight();
225+
break;
226+
case "vmin":
227+
$reference = min($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
228+
break;
229+
case "vmax":
230+
$reference = max($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
231+
break;
209232
}
210233

211-
if (strpos($size, "rem")) {
212-
$reference = $this->document->style->fontSize ?? $defaultFontSize;
213-
return Style::convertSize($size, $reference);
214-
}
215-
216-
if (strpos($size, "ex")) {
217-
$emRef = $this->style->fontSize ?? $defaultFontSize;
218-
$reference = $emRef * 0.5;
219-
return Style::convertSize($size, $reference);
220-
}
221-
222-
if (strpos($size, "ch")) {
223-
$emRef = $this->style->fontSize ?? $defaultFontSize;
224-
$reference = $emRef * 0.5;
225-
return Style::convertSize($size, $reference);
226-
}
227-
228-
if (strpos($size, "vw")) {
229-
$reference = $this->getDocument()->getWidth();
230-
return Style::convertSize($size, $reference);
231-
}
232-
233-
if (strpos($size, "vh")) {
234-
$reference = $this->getDocument()->getHeight();
235-
return Style::convertSize($size, $reference);
236-
}
237-
238-
if (strpos($size, "vmin")) {
239-
$reference = min($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
240-
return Style::convertSize($size, $reference);
241-
}
242-
243-
if (strpos($size, "vmax")) {
244-
$reference = max($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
245-
return Style::convertSize($size, $reference);
246-
}
247-
248-
return Style::convertSize($size, $reference);
234+
return (new CssLength($size))->toPixels($reference);
249235
}
250236
}

tests/Svg/CssLengthTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace Svg\Tests;
4+
5+
use Svg\CssLength;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class CssLengthTest extends TestCase
9+
{
10+
public function test_toPixels()
11+
{
12+
$convert = function(string $size, float $reference = 11.0, float $dpi = 96.0) {
13+
return (new CssLength($size))->toPixels($reference, $dpi);
14+
};
15+
16+
// Absolute lengths
17+
$this->assertEquals(1, $convert('1'));
18+
$this->assertEquals(10, $convert("10px"));
19+
$this->assertEquals((10 * 96) / 72 , $convert("10pt"));
20+
$this->assertEquals((10 * 72) / 72 , $convert("10pt", 11, 72));
21+
$this->assertEquals(8, $convert("80%", 10, 72));
22+
$this->assertEquals((10 * 96) / 2.54, $convert("10cm"));
23+
$this->assertEquals((10 * 96) / 25.4, $convert("10mm"));
24+
$this->assertEquals(10 * 96, $convert("10in"));
25+
$this->assertEquals((10 * 96) / 6, $convert("10pc"));
26+
27+
// Relative lengths
28+
$this->assertEquals(200, $convert("10em", 20));
29+
$this->assertEquals(200, $convert("10ex", 20));
30+
$this->assertEquals(200, $convert("10ch", 20));
31+
$this->assertEquals(200, $convert("10rem", 20));
32+
$this->assertEquals(2, $convert("10vw", 20));
33+
$this->assertEquals(2, $convert("10vh", 20));
34+
$this->assertEquals(2, $convert("10vmin", 20));
35+
$this->assertEquals(2, $convert("10vmax", 20));
36+
}
37+
38+
public function test_getUnit()
39+
{
40+
$this->assertEquals('em', (new CssLength('30em'))->getUnit());
41+
$this->assertEquals('%', (new CssLength('100%'))->getUnit());
42+
$this->assertEquals('vmin', (new CssLength('40vmin'))->getUnit());
43+
$this->assertEquals('q', (new CssLength('50Q'))->getUnit());
44+
$this->assertEquals('', (new CssLength('50GB'))->getUnit());
45+
}
46+
47+
}
48+

0 commit comments

Comments
 (0)