PDF converter * distributed under the OSL-3.0 License * * @package Html2pdf * @author Laurent MINGUET * @copyright 2017 Laurent MINGUET */ namespace Spipu\Html2Pdf; /** * Class CssConverter */ class CssConverter { private $htmlColor = array(); // list of the HTML colors /** * fontsize ratios * @var float[] */ private $fontSizeRatio = [ 'smaller' => 0.8, 'larger' => 1.25, 'xx-small' => 0.512, 'x-small' => 0.64, 'small' => 0.8, 'medium' => 1., 'large' => 1.25, 'x-large' => 1.5625, 'xx-large' => 1.953125, ]; public function __construct() { $this->htmlColor = \TCPDF_COLORS::$webcolor; } /** * convert a distance to mm * * @param string $css distance to convert * @param float $old parent distance * * @return float */ public function convertToMM($css, $old = 0.) { $css = trim($css); if (preg_match('/^[0-9\.\-]+$/isU', $css)) { $css.= 'px'; } if (preg_match('/^[0-9\.\-]+px$/isU', $css)) { $css = 25.4/96. * str_replace('px', '', $css); } elseif (preg_match('/^[0-9\.\-]+pt$/isU', $css)) { $css = 25.4/72. * str_replace('pt', '', $css); } elseif (preg_match('/^[0-9\.\-]+in$/isU', $css)) { $css = 25.4 * str_replace('in', '', $css); } elseif (preg_match('/^[0-9\.\-]+mm$/isU', $css)) { $css = 1.*str_replace('mm', '', $css); } elseif (preg_match('/^[0-9\.\-]+%$/isU', $css)) { $css = 1.*$old*str_replace('%', '', $css)/100.; } else { $css = null; } return $css; } /** * @param string $css font size to convert * @param float $parent parent font size * @return float */ public function convertFontSize($css, $parent = 0.) { $css = trim($css); if (array_key_exists($css, $this->fontSizeRatio)) { $css = ($this->fontSizeRatio[$css] * $parent).'mm'; } return $this->convertToMM($css, $parent); } /** * convert a css radius * * @access public * @param string $css * @return float $value */ public function convertToRadius($css) { // explode the value $css = explode(' ', $css); foreach ($css as $k => $v) { $v = trim($v); if ($v !== '') { $v = $this->convertToMM($v, 0); if ($v !== null) { $css[$k] = $v; } else { unset($css[$k]); } } else { unset($css[$k]); } } return array_values($css); } /** * convert a css color to an RGB array * * @param string $css * @param &boolean $res * * @return array (r, g, b) */ public function convertToColor($css, &$res) { // prepare the value $css = trim($css); $res = true; // if transparent => return null if (strtolower($css) === 'transparent') { return array(null, null, null); } // HTML color if (isset($this->htmlColor[strtolower($css)])) { $css = $this->htmlColor[strtolower($css)]; $r = floatVal(hexdec(substr($css, 0, 2))); $g = floatVal(hexdec(substr($css, 2, 2))); $b = floatVal(hexdec(substr($css, 4, 2))); return array($r, $g, $b); } // like #FFFFFF if (preg_match('/^#[0-9A-Fa-f]{6}$/isU', $css)) { $r = floatVal(hexdec(substr($css, 1, 2))); $g = floatVal(hexdec(substr($css, 3, 2))); $b = floatVal(hexdec(substr($css, 5, 2))); return array($r, $g, $b); } // like #FFF if (preg_match('/^#[0-9A-F]{3}$/isU', $css)) { $r = floatVal(hexdec(substr($css, 1, 1).substr($css, 1, 1))); $g = floatVal(hexdec(substr($css, 2, 1).substr($css, 2, 1))); $b = floatVal(hexdec(substr($css, 3, 1).substr($css, 3, 1))); return array($r, $g, $b); } // like rgb(100, 100, 100) $sub = '[\s]*([0-9%\.]+)[\s]*'; if (preg_match('/rgb\('.$sub.','.$sub.','.$sub.'\)/isU', $css, $match)) { $r = $this->convertSubColor($match[1]); $g = $this->convertSubColor($match[2]); $b = $this->convertSubColor($match[3]); return array($r * 255., $g * 255., $b * 255.); } // like cmyk(100, 100, 100, 100) $sub = '[\s]*([0-9%\.]+)[\s]*'; if (preg_match('/cmyk\('.$sub.','.$sub.','.$sub.','.$sub.'\)/isU', $css, $match)) { $c = $this->convertSubColor($match[1]); $m = $this->convertSubColor($match[2]); $y = $this->convertSubColor($match[3]); $k = $this->convertSubColor($match[4]); return array($c * 100., $m * 100., $y * 100., $k * 100.); } $res = false; return array(0., 0., 0.); } /** * color value to convert * * @access protected * @param string $c * @return float $c 0.->1. */ protected function convertSubColor($c) { if (substr($c, -1) === '%') { $c = floatVal(substr($c, 0, -1)) / 100.; } else { $c = floatVal($c); if ($c > 1) { $c = $c / 255.; } } return $c; } /** * Analyse a background * * @param string $css css background properties * @param &array $value parsed values (by reference, because, ther is a legacy of the parent CSS properties) * * @return void */ public function convertBackground($css, &$value) { // is there a image ? $text = '/url\(([^)]*)\)/isU'; if (preg_match($text, $css, $match)) { // get the image $value['image'] = $this->convertBackgroundImage($match[0]); // remove if from the css properties $css = preg_replace($text, '', $css); $css = preg_replace('/[\s]+/', ' ', $css); } // protect some spaces $css = preg_replace('/,[\s]+/', ',', $css); // explode the values $css = explode(' ', $css); // background position to parse $pos = ''; // foreach value foreach ($css as $val) { // try to parse the value as a color $ok = false; $color = $this->convertToColor($val, $ok); // if ok => it is a color if ($ok) { $value['color'] = $color; // else if transparent => no coloàr } elseif ($val === 'transparent') { $value['color'] = null; // else } else { // try to parse the value as a repeat $repeat = $this->convertBackgroundRepeat($val); // if ok => it is repeat if ($repeat) { $value['repeat'] = $repeat; // else => it could only be a position } else { $pos.= ($pos ? ' ' : '').$val; } } } // if we have a position to parse if ($pos) { // try to read it $pos = $this->convertBackgroundPosition($pos, $ok); if ($ok) { $value['position'] = $pos; } } } /** * Parse a background color * * @param string $css * * @return string|null $value */ public function convertBackgroundColor($css) { $res = null; if ($css === 'transparent') { return null; } return $this->convertToColor($css, $res); } /** * Parse a background image * * @param string $css * * @return string|null $value */ public function convertBackgroundImage($css) { if ($css === 'none') { return null; } if (preg_match('/^url\(([^)]*)\)$/isU', $css, $match)) { return $match[1]; } return null; } /** * Parse a background position * * @param string $css * @param boolean &$res flag if convert is ok or not * * @return array (x, y) */ public function convertBackgroundPosition($css, &$res) { // init the res $res = false; // explode the value $css = explode(' ', $css); // we must have 2 values. if 0 or >2 : error. if 1 => put center for 2 if (count($css)<2) { if (!$css[0]) { return null; } $css[1] = 'center'; } if (count($css)>2) { return null; } // prepare the values $x = 0; $y = 0; $res = true; // convert the first value if ($css[0] === 'left') { $x = '0%'; } elseif ($css[0] === 'center') { $x = '50%'; } elseif ($css[0] === 'right') { $x = '100%'; } elseif ($css[0] === 'top') { $y = '0%'; } elseif ($css[0] === 'bottom') { $y = '100%'; } elseif (preg_match('/^[-]?[0-9\.]+%$/isU', $css[0])) { $x = $css[0]; } elseif ($this->convertToMM($css[0])) { $x = $this->convertToMM($css[0]); } else { $res = false; } // convert the second value if ($css[1] === 'left') { $x = '0%'; } elseif ($css[1] === 'right') { $x = '100%'; } elseif ($css[1] === 'top') { $y = '0%'; } elseif ($css[1] === 'center') { $y = '50%'; } elseif ($css[1] === 'bottom') { $y = '100%'; } elseif (preg_match('/^[-]?[0-9\.]+%$/isU', $css[1])) { $y = $css[1]; } elseif ($this->convertToMM($css[1])) { $y = $this->convertToMM($css[1]); } else { $res = false; } // return the values return array($x, $y); } /** * Parse a background repeat * * @param string $css * * @return array|null background repeat as array */ public function convertBackgroundRepeat($css) { switch ($css) { case 'repeat': return array(true, true); case 'repeat-x': return array(true, false); case 'repeat-y': return array(false, true); case 'no-repeat': return array(false, false); } return null; } }