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;
}
}