PageRenderTime 65ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/include/html2pdf_v4.03/_class/parsingCss.class.php

https://bitbucket.org/sleininger/stock_online
PHP | 1812 lines | 1204 code | 232 blank | 376 comment | 293 complexity | e61cea4c670a0d925f11861a9deaf02d MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * HTML2PDF Librairy - parsingCss class
  4. *
  5. * HTML => PDF convertor
  6. * distributed under the LGPL License
  7. *
  8. * @author Laurent MINGUET <webmaster@html2pdf.fr>
  9. * @version 4.03
  10. */
  11. class HTML2PDF_parsingCss
  12. {
  13. /**
  14. * reference to the pdf object
  15. * @var TCPDF
  16. */
  17. protected $_pdf = null;
  18. protected $_htmlColor = array(); // list of the HTML colors
  19. protected $_onlyLeft = false; // flag if we are in a sub html => only "text-align:left" is used
  20. protected $_defaultFont = null; // default font to use if the asked font does not exist
  21. public $value = array(); // current values
  22. public $css = array(); // css values
  23. public $cssKeys = array(); // css key, for the execution order
  24. public $table = array(); // level history
  25. /**
  26. * Constructor
  27. *
  28. * @param &HTML2PDF_myPdf reference to the PDF $object
  29. * @access public
  30. */
  31. public function __construct(&$pdf)
  32. {
  33. $this->_init();
  34. $this->setPdfParent($pdf);
  35. }
  36. /**
  37. * Set the HTML2PDF parent object
  38. *
  39. * @param &HTML2PDF reference to the HTML2PDF parent $object
  40. * @access public
  41. */
  42. public function setPdfParent(&$pdf)
  43. {
  44. $this->_pdf = &$pdf;
  45. }
  46. /**
  47. * Inform that we want only "test-align:left" because we are in a sub HTML
  48. *
  49. * @access public
  50. */
  51. public function setOnlyLeft()
  52. {
  53. $this->value['text-align'] = 'left';
  54. $this->_onlyLeft = true;
  55. }
  56. /**
  57. * Get the vales of the parent, if exist
  58. *
  59. * @return array CSS values
  60. * @access public
  61. */
  62. public function getOldValues()
  63. {
  64. return isset($this->table[count($this->table)-1]) ? $this->table[count($this->table)-1] : $this->value;
  65. }
  66. /**
  67. * define the Default Font to use, if the font does not exist, or if no font asked
  68. *
  69. * @param string default font-family. If null : Arial for no font asked, and error fot ont does not exist
  70. * @return string old default font-family
  71. * @access public
  72. */
  73. public function setDefaultFont($default = null)
  74. {
  75. $old = $this->_defaultFont;
  76. $this->_defaultFont = $default;
  77. if ($default) $this->value['font-family'] = $default;
  78. return $old;
  79. }
  80. /**
  81. * Init the object
  82. *
  83. * @access protected
  84. */
  85. protected function _init()
  86. {
  87. // get the Web Colors from TCPDF
  88. require(K_PATH_MAIN.'htmlcolors.php');
  89. $this->_htmlColor = $webcolor;
  90. // init the Style
  91. $this->table = array();
  92. $this->value = array();
  93. $this->initStyle();
  94. // Init the styles without legacy
  95. $this->resetStyle();
  96. }
  97. /**
  98. * Init the CSS Style
  99. *
  100. * @access public
  101. */
  102. public function initStyle()
  103. {
  104. $this->value['id_tag'] = 'body'; // tag name
  105. $this->value['id_name'] = null; // tag - attribute name
  106. $this->value['id_id'] = null; // tag - attribute id
  107. $this->value['id_class'] = null; // tag - attribute class
  108. $this->value['id_lst'] = array('*'); // tag - list of legacy
  109. $this->value['mini-size'] = 1.; // specific size report for sup, sub
  110. $this->value['mini-decal'] = 0; // specific position report for sup, sub
  111. $this->value['font-family'] = 'Arial';
  112. $this->value['font-bold'] = false;
  113. $this->value['font-italic'] = false;
  114. $this->value['font-underline'] = false;
  115. $this->value['font-overline'] = false;
  116. $this->value['font-linethrough'] = false;
  117. $this->value['text-transform'] = 'none';
  118. $this->value['font-size'] = $this->convertToMM('10pt');
  119. $this->value['text-indent'] = 0;
  120. $this->value['text-align'] = 'left';
  121. $this->value['vertical-align'] = 'middle';
  122. $this->value['line-height'] = 'normal';
  123. $this->value['position'] = null;
  124. $this->value['x'] = null;
  125. $this->value['y'] = null;
  126. $this->value['width'] = 0;
  127. $this->value['height'] = 0;
  128. $this->value['top'] = null;
  129. $this->value['right'] = null;
  130. $this->value['bottom'] = null;
  131. $this->value['left'] = null;
  132. $this->value['float'] = null;
  133. $this->value['display'] = null;
  134. $this->value['rotate'] = null;
  135. $this->value['overflow'] = 'visible';
  136. $this->value['color'] = array(0, 0, 0);
  137. $this->value['background'] = array('color' => null, 'image' => null, 'position' => null, 'repeat' => null);
  138. $this->value['border'] = array();
  139. $this->value['padding'] = array();
  140. $this->value['margin'] = array();
  141. $this->value['margin-auto'] = false;
  142. $this->value['list-style-type'] = '';
  143. $this->value['list-style-image'] = '';
  144. $this->value['xc'] = null;
  145. $this->value['yc'] = null;
  146. }
  147. /**
  148. * Init the CSS Style without legacy
  149. *
  150. * @param string tag name
  151. * @access public
  152. */
  153. public function resetStyle($tagName = '')
  154. {
  155. // prepare somme values
  156. $border = $this->readBorder('solid 1px #000000');
  157. $units = array(
  158. '1px' => $this->convertToMM('1px'),
  159. '5px' => $this->convertToMM('5px'),
  160. );
  161. // prepare the Collapse attribute
  162. $collapse = isset($this->value['border']['collapse']) ? $this->value['border']['collapse'] : false;
  163. if (!in_array($tagName, array('tr', 'td', 'th', 'thead', 'tbody', 'tfoot'))) $collapse = false;
  164. // set the global css values
  165. $this->value['position'] = null;
  166. $this->value['x'] = null;
  167. $this->value['y'] = null;
  168. $this->value['width'] = 0;
  169. $this->value['height'] = 0;
  170. $this->value['top'] = null;
  171. $this->value['right'] = null;
  172. $this->value['bottom'] = null;
  173. $this->value['left'] = null;
  174. $this->value['float'] = null;
  175. $this->value['display'] = null;
  176. $this->value['rotate'] = null;
  177. $this->value['overflow'] = 'visible';
  178. $this->value['background'] = array('color' => null, 'image' => null, 'position' => null, 'repeat' => null);
  179. $this->value['border'] = array(
  180. 't' => $this->readBorder('none'),
  181. 'r' => $this->readBorder('none'),
  182. 'b' => $this->readBorder('none'),
  183. 'l' => $this->readBorder('none'),
  184. 'radius' => array(
  185. 'tl' => array(0, 0),
  186. 'tr' => array(0, 0),
  187. 'br' => array(0, 0),
  188. 'bl' => array(0, 0)
  189. ),
  190. 'collapse' => $collapse,
  191. );
  192. // specific values for some tags
  193. if (!in_array($tagName, array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) {
  194. $this->value['margin'] = array('t'=>0,'r'=>0,'b'=>0,'l'=>0);
  195. }
  196. if (in_array($tagName, array('input', 'select', 'textarea'))) {
  197. $this->value['border']['t'] = null;
  198. $this->value['border']['r'] = null;
  199. $this->value['border']['b'] = null;
  200. $this->value['border']['l'] = null;
  201. }
  202. if ($tagName=='p') {
  203. $this->value['margin']['t'] = null;
  204. $this->value['margin']['b'] = null;
  205. }
  206. if ($tagName=='blockquote') {
  207. $this->value['margin']['t'] = 3;
  208. $this->value['margin']['r'] = 3;
  209. $this->value['margin']['b'] = 3;
  210. $this->value['margin']['l'] = 6;
  211. }
  212. $this->value['margin-auto'] = false;
  213. if (in_array($tagName, array('blockquote', 'div', 'fieldset'))) {
  214. $this->value['vertical-align'] = 'top';
  215. }
  216. if (in_array($tagName, array('fieldset', 'legend'))) {
  217. $this->value['border'] = array(
  218. 't' => $border,
  219. 'r' => $border,
  220. 'b' => $border,
  221. 'l' => $border,
  222. 'radius' => array(
  223. 'tl' => array($units['5px'], $units['5px']),
  224. 'tr' => array($units['5px'], $units['5px']),
  225. 'br' => array($units['5px'], $units['5px']),
  226. 'bl' => array($units['5px'], $units['5px'])
  227. ),
  228. 'collapse' => false,
  229. );
  230. }
  231. if (in_array($tagName, array('ul', 'li'))) {
  232. $this->value['list-style-type'] = '';
  233. $this->value['list-style-image'] = '';
  234. }
  235. if (!in_array($tagName, array('tr', 'td'))) {
  236. $this->value['padding'] = array(
  237. 't' => 0,
  238. 'r' => 0,
  239. 'b' => 0,
  240. 'l' => 0
  241. );
  242. } else {
  243. $this->value['padding'] = array(
  244. 't' => $units['1px'],
  245. 'r' => $units['1px'],
  246. 'b' => $units['1px'],
  247. 'l' => $units['1px']
  248. );
  249. }
  250. if ($tagName=='hr') {
  251. $this->value['border'] = array(
  252. 't' => $border,
  253. 'r' => $border,
  254. 'b' => $border,
  255. 'l' => $border,
  256. 'radius' => array(
  257. 'tl' => array(0, 0),
  258. 'tr' => array(0, 0),
  259. 'br' => array(0, 0),
  260. 'bl' => array(0, 0)
  261. ),
  262. 'collapse' => false,
  263. );
  264. $this->convertBackground('#FFFFFF', $this->value['background']);
  265. }
  266. $this->value['xc'] = null;
  267. $this->value['yc'] = null;
  268. }
  269. /**
  270. * Init the PDF Font
  271. *
  272. * @access public
  273. */
  274. public function fontSet()
  275. {
  276. $family = strtolower($this->value['font-family']);
  277. $b = ($this->value['font-bold'] ? 'B' : '');
  278. $i = ($this->value['font-italic'] ? 'I' : '');
  279. $u = ($this->value['font-underline'] ? 'U' : '');
  280. $d = ($this->value['font-linethrough'] ? 'D' : '');
  281. $o = ($this->value['font-overline'] ? 'O' : '');
  282. // font style
  283. $style = $b.$i;
  284. if ($this->_defaultFont) {
  285. if($family=='arial')
  286. $family='helvetica';
  287. elseif($family=='symbol' || $family=='zapfdingbats')
  288. $style='';
  289. $fontkey = $family.$style;
  290. if (!$this->_pdf->isLoadedFont($fontkey))
  291. $family = $this->_defaultFont;
  292. }
  293. if($family=='arial')
  294. $family='helvetica';
  295. elseif($family=='symbol' || $family=='zapfdingbats')
  296. $style='';
  297. // complete style
  298. $style.= $u.$d.$o;
  299. // size : mm => pt
  300. $size = $this->value['font-size'];
  301. $size = 72 * $size / 25.4;
  302. // apply the font
  303. $this->_pdf->SetFont($family, $style, $this->value['mini-size']*$size);
  304. $this->_pdf->setTextColorArray($this->value['color']);
  305. if ($this->value['background']['color'])
  306. $this->_pdf->setFillColorArray($this->value['background']['color']);
  307. else
  308. $this->_pdf->setFillColor(255);
  309. }
  310. /**
  311. * add a level in the CSS history
  312. *
  313. * @access public
  314. */
  315. public function save()
  316. {
  317. array_push($this->table, $this->value);
  318. }
  319. /**
  320. * remove a level in the CSS history
  321. *
  322. * @access public
  323. */
  324. public function load()
  325. {
  326. if (count($this->table)) {
  327. $this->value = array_pop($this->table);
  328. }
  329. }
  330. /**
  331. * restore the Y positiony (used after a span)
  332. *
  333. * @access public
  334. */
  335. public function restorePosition()
  336. {
  337. if ($this->value['y']==$this->_pdf->getY()) $this->_pdf->setY($this->value['yc'], false);
  338. }
  339. /**
  340. * set the New position for the current Tag
  341. *
  342. * @access public
  343. */
  344. public function setPosition()
  345. {
  346. // get the current position
  347. $currentX = $this->_pdf->getX();
  348. $currentY = $this->_pdf->getY();
  349. // save it
  350. $this->value['xc'] = $currentX;
  351. $this->value['yc'] = $currentY;
  352. if ($this->value['position']=='relative' || $this->value['position']=='absolute') {
  353. if ($this->value['right']!==null) {
  354. $x = $this->getLastWidth(true) - $this->value['right'] - $this->value['width'];
  355. if ($this->value['margin']['r']) $x-= $this->value['margin']['r'];
  356. } else {
  357. $x = $this->value['left'];
  358. if ($this->value['margin']['l']) $x+= $this->value['margin']['l'];
  359. }
  360. if ($this->value['bottom']!==null) {
  361. $y = $this->getLastHeight(true) - $this->value['bottom'] - $this->value['height'];
  362. if ($this->value['margin']['b']) $y-= $this->value['margin']['b'];
  363. } else {
  364. $y = $this->value['top'];
  365. if ($this->value['margin']['t']) $y+= $this->value['margin']['t'];
  366. }
  367. if ($this->value['position']=='relative') {
  368. $this->value['x'] = $currentX + $x;
  369. $this->value['y'] = $currentY + $y;
  370. } else {
  371. $this->value['x'] = $this->_getLastAbsoluteX()+$x;
  372. $this->value['y'] = $this->_getLastAbsoluteY()+$y;
  373. }
  374. } else {
  375. $this->value['x'] = $currentX;
  376. $this->value['y'] = $currentY;
  377. if ($this->value['margin']['l']) $this->value['x']+= $this->value['margin']['l'];
  378. if ($this->value['margin']['t']) $this->value['y']+= $this->value['margin']['t'];
  379. }
  380. // save the new position
  381. $this->_pdf->setXY($this->value['x'], $this->value['y']);
  382. }
  383. /**
  384. * Analise the CSS style to convert it into Form style
  385. *
  386. * @access public
  387. * @param array styles
  388. */
  389. public function getFormStyle()
  390. {
  391. $prop = array();
  392. $prop['alignment'] = $this->value['text-align'];
  393. if (isset($this->value['background']['color']) && is_array($this->value['background']['color'])) {
  394. $prop['fillColor'] = $this->value['background']['color'];
  395. }
  396. if (isset($this->value['border']['t']['color'])) {
  397. $prop['strokeColor'] = $this->value['border']['t']['color'];
  398. }
  399. if (isset($this->value['border']['t']['width'])) {
  400. $prop['lineWidth'] = $this->value['border']['t']['width'];
  401. }
  402. if (isset($this->value['border']['t']['type'])) {
  403. $prop['borderStyle'] = $this->value['border']['t']['type'];
  404. }
  405. if (!empty($this->value['color'])) {
  406. $prop['textColor'] = $this->value['color'];
  407. }
  408. if (!empty($this->value['font-size'])) {
  409. $prop['textSize'] = $this->value['font-size'];
  410. }
  411. return $prop;
  412. }
  413. /**
  414. * Analise the CSS style to convert it into SVG style
  415. *
  416. * @access public
  417. * @param string tag name
  418. * @param array styles
  419. */
  420. public function getSvgStyle($tagName, &$param)
  421. {
  422. // prepare
  423. $tagName = strtolower($tagName);
  424. $id = isset($param['id']) ? strtolower(trim($param['id'])) : null; if (!$id) $id = null;
  425. $name = isset($param['name']) ? strtolower(trim($param['name'])) : null; if (!$name) $name = null;
  426. // read the class attribute
  427. $class = array();
  428. $tmp = isset($param['class']) ? strtolower(trim($param['class'])) : '';
  429. $tmp = explode(' ', $tmp);
  430. foreach ($tmp as $k => $v) {
  431. $v = trim($v);
  432. if ($v) $class[] = $v;
  433. }
  434. // identify the tag, and the direct styles
  435. $this->value['id_tag'] = $tagName;
  436. $this->value['id_name'] = $name;
  437. $this->value['id_id'] = $id;
  438. $this->value['id_class'] = $class;
  439. $this->value['id_lst'] = array();
  440. $this->value['id_lst'][] = '*';
  441. $this->value['id_lst'][] = $tagName;
  442. if (!isset($this->value['svg'])) {
  443. $this->value['svg'] = array(
  444. 'stroke' => null,
  445. 'stroke-width' => $this->convertToMM('1pt'),
  446. 'fill' => null,
  447. 'fill-opacity' => null,
  448. );
  449. }
  450. if (count($class)) {
  451. foreach ($class as $v) {
  452. $this->value['id_lst'][] = '*.'.$v;
  453. $this->value['id_lst'][] = '.'.$v;
  454. $this->value['id_lst'][] = $tagName.'.'.$v;
  455. }
  456. }
  457. if ($id) {
  458. $this->value['id_lst'][] = '*#'.$id;
  459. $this->value['id_lst'][] = '#'.$id;
  460. $this->value['id_lst'][] = $tagName.'#'.$id;
  461. }
  462. // CSS style
  463. $styles = $this->_getFromCSS();
  464. // adding the style from the tag
  465. $styles = array_merge($styles, $param['style']);
  466. if (isset($styles['stroke'])) $this->value['svg']['stroke'] = $this->convertToColor($styles['stroke'], $res);
  467. if (isset($styles['stroke-width'])) $this->value['svg']['stroke-width'] = $this->convertToMM($styles['stroke-width']);
  468. if (isset($styles['fill'])) $this->value['svg']['fill'] = $this->convertToColor($styles['fill'], $res);
  469. if (isset($styles['fill-opacity'])) $this->value['svg']['fill-opacity'] = 1.*$styles['fill-opacity'];
  470. return $this->value['svg'];
  471. }
  472. /**
  473. * analyse the css properties from the HTML parsing
  474. *
  475. * @access public
  476. * @param string $tagName
  477. * @param array $param
  478. * @param array $legacy
  479. */
  480. public function analyse($tagName, &$param, $legacy = null)
  481. {
  482. // prepare the informations
  483. $tagName = strtolower($tagName);
  484. $id = isset($param['id']) ? strtolower(trim($param['id'])) : null; if (!$id) $id = null;
  485. $name = isset($param['name']) ? strtolower(trim($param['name'])) : null; if (!$name) $name = null;
  486. // get the class names to use
  487. $class = array();
  488. $tmp = isset($param['class']) ? strtolower(trim($param['class'])) : '';
  489. $tmp = explode(' ', $tmp);
  490. foreach ($tmp as $k => $v) {
  491. $v = trim($v);
  492. if ($v) $class[] = $v;
  493. }
  494. // prepare the values, and the list of css tags to identify
  495. $this->value['id_tag'] = $tagName;
  496. $this->value['id_name'] = $name;
  497. $this->value['id_id'] = $id;
  498. $this->value['id_class'] = $class;
  499. $this->value['id_lst'] = array();
  500. $this->value['id_lst'][] = '*';
  501. $this->value['id_lst'][] = $tagName;
  502. if (count($class)) {
  503. foreach ($class as $v) {
  504. $this->value['id_lst'][] = '*.'.$v;
  505. $this->value['id_lst'][] = '.'.$v;
  506. $this->value['id_lst'][] = $tagName.'.'.$v;
  507. }
  508. }
  509. if ($id) {
  510. $this->value['id_lst'][] = '*#'.$id;
  511. $this->value['id_lst'][] = '#'.$id;
  512. $this->value['id_lst'][] = $tagName.'#'.$id;
  513. }
  514. // get the css styles from class
  515. $styles = $this->_getFromCSS();
  516. // merge with the css styles from tag
  517. $styles = array_merge($styles, $param['style']);
  518. if (isset($param['allwidth']) && !isset($styles['width'])) $styles['width'] = '100%';
  519. // reset some styles, depending on the tag name
  520. $this->resetStyle($tagName);
  521. // add the legacy values
  522. if ($legacy) {
  523. foreach ($legacy as $legacyName => $legacyValue) {
  524. if (is_array($legacyValue)) {
  525. foreach($legacyValue as $legacy2Name => $legacy2Value)
  526. $this->value[$legacyName][$legacy2Name] = $legacy2Value;
  527. } else {
  528. $this->value[$legacyName] = $legacyValue;
  529. }
  530. }
  531. }
  532. // some flags
  533. $correctWidth = false;
  534. $noWidth = true;
  535. // read all the css styles
  536. foreach ($styles as $nom => $val) {
  537. switch($nom)
  538. {
  539. case 'font-family':
  540. $val = explode(',', $val);
  541. $val = trim($val[0]);
  542. if ($val) $this->value['font-family'] = $val;
  543. break;
  544. case 'font-weight':
  545. $this->value['font-bold'] = ($val=='bold');
  546. break;
  547. case 'font-style':
  548. $this->value['font-italic'] = ($val=='italic');
  549. break;
  550. case 'text-decoration':
  551. $val = explode(' ', $val);
  552. $this->value['font-underline'] = (in_array('underline', $val));
  553. $this->value['font-overline'] = (in_array('overline', $val));
  554. $this->value['font-linethrough'] = (in_array('line-through', $val));
  555. break;
  556. case 'text-indent':
  557. $this->value['text-indent'] = $this->convertToMM($val);
  558. break;
  559. case 'text-transform':
  560. if (!in_array($val, array('none', 'capitalize', 'uppercase', 'lowercase'))) $val = 'none';
  561. $this->value['text-transform'] = $val;
  562. break;
  563. case 'font-size':
  564. $val = $this->convertToMM($val, $this->value['font-size']);
  565. if ($val) $this->value['font-size'] = $val;
  566. break;
  567. case 'color':
  568. $res = null;
  569. $this->value['color'] = $this->convertToColor($val, $res);
  570. if ($tagName=='hr') {
  571. $this->value['border']['l']['color'] = $this->value['color'];
  572. $this->value['border']['t']['color'] = $this->value['color'];
  573. $this->value['border']['r']['color'] = $this->value['color'];
  574. $this->value['border']['b']['color'] = $this->value['color'];
  575. }
  576. break;
  577. case 'text-align':
  578. $val = strtolower($val);
  579. if (!in_array($val, array('left', 'right', 'center', 'justify', 'li_right'))) $val = 'left';
  580. $this->value['text-align'] = $val;
  581. break;
  582. case 'vertical-align':
  583. $this->value['vertical-align'] = $val;
  584. break;
  585. case 'width':
  586. $this->value['width'] = $this->convertToMM($val, $this->getLastWidth());
  587. if ($this->value['width'] && substr($val, -1)=='%') $correctWidth=true;
  588. $noWidth = false;
  589. break;
  590. case 'height':
  591. $this->value['height'] = $this->convertToMM($val, $this->getLastHeight());
  592. break;
  593. case 'line-height':
  594. if (preg_match('/^[0-9\.]+$/isU', $val)) $val = floor($val*100).'%';
  595. $this->value['line-height'] = $val;
  596. break;
  597. case 'rotate':
  598. if (!in_array($val, array(0, -90, 90, 180, 270, -180, -270))) $val = null;
  599. if ($val<0) $val+= 360;
  600. $this->value['rotate'] = $val;
  601. break;
  602. case 'overflow':
  603. if (!in_array($val, array('visible', 'hidden'))) $val = 'visible';
  604. $this->value['overflow'] = $val;
  605. break;
  606. case 'padding':
  607. $val = explode(' ', $val);
  608. foreach ($val as $k => $v) {
  609. $v = trim($v);
  610. if ($v!='') {
  611. $val[$k] = $v;
  612. } else {
  613. unset($val[$k]);
  614. }
  615. }
  616. $val = array_values($val);
  617. $this->_duplicateBorder($val);
  618. $this->value['padding']['t'] = $this->convertToMM($val[0], 0);
  619. $this->value['padding']['r'] = $this->convertToMM($val[1], 0);
  620. $this->value['padding']['b'] = $this->convertToMM($val[2], 0);
  621. $this->value['padding']['l'] = $this->convertToMM($val[3], 0);
  622. break;
  623. case 'padding-top':
  624. $this->value['padding']['t'] = $this->convertToMM($val, 0);
  625. break;
  626. case 'padding-right':
  627. $this->value['padding']['r'] = $this->convertToMM($val, 0);
  628. break;
  629. case 'padding-bottom':
  630. $this->value['padding']['b'] = $this->convertToMM($val, 0);
  631. break;
  632. case 'padding-left':
  633. $this->value['padding']['l'] = $this->convertToMM($val, 0);
  634. break;
  635. case 'margin':
  636. if ($val=='auto') {
  637. $this->value['margin-auto'] = true;
  638. break;
  639. }
  640. $val = explode(' ', $val);
  641. foreach ($val as $k => $v) {
  642. $v = trim($v);
  643. if ($v!='') {
  644. $val[$k] = $v;
  645. } else {
  646. unset($val[$k]);
  647. }
  648. }
  649. $val = array_values($val);
  650. $this->_duplicateBorder($val);
  651. $this->value['margin']['t'] = $this->convertToMM($val[0], 0);
  652. $this->value['margin']['r'] = $this->convertToMM($val[1], 0);
  653. $this->value['margin']['b'] = $this->convertToMM($val[2], 0);
  654. $this->value['margin']['l'] = $this->convertToMM($val[3], 0);
  655. break;
  656. case 'margin-top':
  657. $this->value['margin']['t'] = $this->convertToMM($val, 0);
  658. break;
  659. case 'margin-right':
  660. $this->value['margin']['r'] = $this->convertToMM($val, 0);
  661. break;
  662. case 'margin-bottom':
  663. $this->value['margin']['b'] = $this->convertToMM($val, 0);
  664. break;
  665. case 'margin-left':
  666. $this->value['margin']['l'] = $this->convertToMM($val, 0);
  667. break;
  668. case 'border':
  669. $val = $this->readBorder($val);
  670. $this->value['border']['t'] = $val;
  671. $this->value['border']['r'] = $val;
  672. $this->value['border']['b'] = $val;
  673. $this->value['border']['l'] = $val;
  674. break;
  675. case 'border-style':
  676. $val = explode(' ', $val);
  677. foreach ($val as $valK => $valV) {
  678. if (!in_array($valV, array('solid', 'dotted', 'dashed'))) {
  679. $val[$valK] = null;
  680. }
  681. }
  682. $this->_duplicateBorder($val);
  683. if ($val[0]) $this->value['border']['t']['type'] = $val[0];
  684. if ($val[1]) $this->value['border']['r']['type'] = $val[1];
  685. if ($val[2]) $this->value['border']['b']['type'] = $val[2];
  686. if ($val[3]) $this->value['border']['l']['type'] = $val[3];
  687. break;
  688. case 'border-top-style':
  689. if (in_array($val, array('solid', 'dotted', 'dashed'))) {
  690. $this->value['border']['t']['type'] = $val;
  691. }
  692. break;
  693. case 'border-right-style':
  694. if (in_array($val, array('solid', 'dotted', 'dashed'))) {
  695. $this->value['border']['r']['type'] = $val;
  696. }
  697. break;
  698. case 'border-bottom-style':
  699. if (in_array($val, array('solid', 'dotted', 'dashed'))) {
  700. $this->value['border']['b']['type'] = $val;
  701. }
  702. break;
  703. case 'border-left-style':
  704. if (in_array($val, array('solid', 'dotted', 'dashed')))
  705. $this->value['border']['l']['type'] = $val;
  706. break;
  707. case 'border-color':
  708. $res = false;
  709. $val = preg_replace('/,[\s]+/', ',', $val);
  710. $val = explode(' ', $val);
  711. foreach ($val as $valK => $valV) {
  712. $val[$valK] = $this->convertToColor($valV, $res);
  713. if (!$res) {
  714. $val[$valK] = null;
  715. }
  716. }
  717. $this->_duplicateBorder($val);
  718. if (is_array($val[0])) $this->value['border']['t']['color'] = $val[0];
  719. if (is_array($val[1])) $this->value['border']['r']['color'] = $val[1];
  720. if (is_array($val[2])) $this->value['border']['b']['color'] = $val[2];
  721. if (is_array($val[3])) $this->value['border']['l']['color'] = $val[3];
  722. break;
  723. case 'border-top-color':
  724. $res = false;
  725. $val = $this->convertToColor($val, $res);
  726. if ($res) $this->value['border']['t']['color'] = $val;
  727. break;
  728. case 'border-right-color':
  729. $res = false;
  730. $val = $this->convertToColor($val, $res);
  731. if ($res) $this->value['border']['r']['color'] = $val;
  732. break;
  733. case 'border-bottom-color':
  734. $res = false;
  735. $val = $this->convertToColor($val, $res);
  736. if ($res) $this->value['border']['b']['color'] = $val;
  737. break;
  738. case 'border-left-color':
  739. $res = false;
  740. $val = $this->convertToColor($val, $res);
  741. if ($res) $this->value['border']['l']['color'] = $val;
  742. break;
  743. case 'border-width':
  744. $val = explode(' ', $val);
  745. foreach ($val as $valK => $valV) {
  746. $val[$valK] = $this->convertToMM($valV, 0);
  747. }
  748. $this->_duplicateBorder($val);
  749. if ($val[0]) $this->value['border']['t']['width'] = $val[0];
  750. if ($val[1]) $this->value['border']['r']['width'] = $val[1];
  751. if ($val[2]) $this->value['border']['b']['width'] = $val[2];
  752. if ($val[3]) $this->value['border']['l']['width'] = $val[3];
  753. break;
  754. case 'border-top-width':
  755. $val = $this->convertToMM($val, 0);
  756. if ($val) $this->value['border']['t']['width'] = $val;
  757. break;
  758. case 'border-right-width':
  759. $val = $this->convertToMM($val, 0);
  760. if ($val) $this->value['border']['r']['width'] = $val;
  761. break;
  762. case 'border-bottom-width':
  763. $val = $this->convertToMM($val, 0);
  764. if ($val) $this->value['border']['b']['width'] = $val;
  765. break;
  766. case 'border-left-width':
  767. $val = $this->convertToMM($val, 0);
  768. if ($val) $this->value['border']['l']['width'] = $val;
  769. break;
  770. case 'border-collapse':
  771. if ($tagName=='table') $this->value['border']['collapse'] = ($val=='collapse');
  772. break;
  773. case 'border-radius':
  774. $val = explode('/', $val);
  775. if (count($val)>2) {
  776. break;
  777. }
  778. $valH = $this->convertToRadius(trim($val[0]));
  779. if (count($valH)<1 || count($valH)>4) {
  780. break;
  781. }
  782. if (!isset($valH[1])) $valH[1] = $valH[0];
  783. if (!isset($valH[2])) $valH = array($valH[0], $valH[0], $valH[1], $valH[1]);
  784. if (!isset($valH[3])) $valH[3] = $valH[1];
  785. if (isset($val[1])) {
  786. $valV = $this->convertToRadius(trim($val[1]));
  787. if (count($valV)<1 || count($valV)>4) {
  788. break;
  789. }
  790. if (!isset($valV[1])) $valV[1] = $valV[0];
  791. if (!isset($valV[2])) $valV = array($valV[0], $valV[0], $valV[1], $valV[1]);
  792. if (!isset($valV[3])) $valV[3] = $valV[1];
  793. } else {
  794. $valV = $valH;
  795. }
  796. $this->value['border']['radius'] = array(
  797. 'tl' => array($valH[0], $valV[0]),
  798. 'tr' => array($valH[1], $valV[1]),
  799. 'br' => array($valH[2], $valV[2]),
  800. 'bl' => array($valH[3], $valV[3])
  801. );
  802. break;
  803. case 'border-top-left-radius':
  804. $val = $this->convertToRadius($val);
  805. if (count($val)<1 || count($val)>2) {
  806. break;
  807. }
  808. $this->value['border']['radius']['tl'] = array($val[0], isset($val[1]) ? $val[1] : $val[0]);
  809. break;
  810. case 'border-top-right-radius':
  811. $val = $this->convertToRadius($val);
  812. if (count($val)<1 || count($val)>2) {
  813. break;
  814. }
  815. $this->value['border']['radius']['tr'] = array($val[0], isset($val[1]) ? $val[1] : $val[0]);
  816. break;
  817. case 'border-bottom-right-radius':
  818. $val = $this->convertToRadius($val);
  819. if (count($val)<1 || count($val)>2) {
  820. break;
  821. }
  822. $this->value['border']['radius']['br'] = array($val[0], isset($val[1]) ? $val[1] : $val[0]);
  823. break;
  824. case 'border-bottom-left-radius':
  825. $val = $this->convertToRadius($val);
  826. if (count($val)<1 || count($val)>2) {
  827. break;
  828. }
  829. $this->value['border']['radius']['bl'] = array($val[0], isset($val[1]) ? $val[1] : $val[0]);
  830. break;
  831. case 'border-top':
  832. $this->value['border']['t'] = $this->readBorder($val);
  833. break;
  834. case 'border-right':
  835. $this->value['border']['r'] = $this->readBorder($val);
  836. break;
  837. case 'border-bottom':
  838. $this->value['border']['b'] = $this->readBorder($val);
  839. break;
  840. case 'border-left':
  841. $this->value['border']['l'] = $this->readBorder($val);
  842. break;
  843. case 'background-color':
  844. $this->value['background']['color'] = $this->convertBackgroundColor($val);
  845. break;
  846. case 'background-image':
  847. $this->value['background']['image'] = $this->convertBackgroundImage($val);
  848. break;
  849. case 'background-position':
  850. $res = null;
  851. $this->value['background']['position'] = $this->convertBackgroundPosition($val, $res);
  852. break;
  853. case 'background-repeat':
  854. $this->value['background']['repeat'] = $this->convertBackgroundRepeat($val);
  855. break;
  856. case 'background':
  857. $this->convertBackground($val, $this->value['background']);
  858. break;
  859. case 'position':
  860. if ($val=='absolute') $this->value['position'] = 'absolute';
  861. else if ($val=='relative') $this->value['position'] = 'relative';
  862. else $this->value['position'] = null;
  863. break;
  864. case 'float':
  865. if ($val=='left') $this->value['float'] = 'left';
  866. else if ($val=='right') $this->value['float'] = 'right';
  867. else $this->value['float'] = null;
  868. break;
  869. case 'display':
  870. if ($val=='inline') $this->value['display'] = 'inline';
  871. else if ($val=='block') $this->value['display'] = 'block';
  872. else if ($val=='none') $this->value['display'] = 'none';
  873. else $this->value['display'] = null;
  874. break;
  875. case 'top':
  876. case 'bottom':
  877. case 'left':
  878. case 'right':
  879. $this->value[$nom] = $val;
  880. break;
  881. case 'list-style':
  882. case 'list-style-type':
  883. case 'list-style-image':
  884. if ($nom=='list-style') $nom = 'list-style-type';
  885. $this->value[$nom] = $val;
  886. break;
  887. default:
  888. break;
  889. }
  890. }
  891. $return = true;
  892. // only for P tag
  893. if ($this->value['margin']['t']===null) $this->value['margin']['t'] = $this->value['font-size'];
  894. if ($this->value['margin']['b']===null) $this->value['margin']['b'] = $this->value['font-size'];
  895. // force the text align to left, if asked by html2pdf
  896. if ($this->_onlyLeft) $this->value['text-align'] = 'left';
  897. // correction on the width (quick box)
  898. if ($noWidth && in_array($tagName, array('div', 'blockquote', 'fieldset')) && $this->value['position']!='absolute') {
  899. $this->value['width'] = $this->getLastWidth();
  900. $this->value['width']-= $this->value['margin']['l'] + $this->value['margin']['r'];
  901. } else {
  902. if ($correctWidth) {
  903. if (!in_array($tagName, array('table', 'div', 'blockquote', 'fieldset', 'hr'))) {
  904. $this->value['width']-= $this->value['padding']['l'] + $this->value['padding']['r'];
  905. $this->value['width']-= $this->value['border']['l']['width'] + $this->value['border']['r']['width'];
  906. }
  907. if (in_array($tagName, array('th', 'td'))) {
  908. $this->value['width']-= $this->convertToMM(isset($param['cellspacing']) ? $param['cellspacing'] : '2px');
  909. $return = false;
  910. }
  911. if ($this->value['width']<0) $this->value['width']=0;
  912. } else {
  913. if ($this->value['width']) {
  914. if ($this->value['border']['l']['width']) $this->value['width'] += $this->value['border']['l']['width'];
  915. if ($this->value['border']['r']['width']) $this->value['width'] += $this->value['border']['r']['width'];
  916. if ($this->value['padding']['l']) $this->value['width'] += $this->value['padding']['l'];
  917. if ($this->value['padding']['r']) $this->value['width'] += $this->value['padding']['r'];
  918. }
  919. }
  920. }
  921. if ($this->value['height']) {
  922. if ($this->value['border']['b']['width']) $this->value['height'] += $this->value['border']['b']['width'];
  923. if ($this->value['border']['t']['width']) $this->value['height'] += $this->value['border']['t']['width'];
  924. if ($this->value['padding']['b']) $this->value['height'] += $this->value['padding']['b'];
  925. if ($this->value['padding']['t']) $this->value['height'] += $this->value['padding']['t'];
  926. }
  927. if ($this->value['top']!=null) $this->value['top'] = $this->convertToMM($this->value['top'], $this->getLastHeight(true));
  928. if ($this->value['bottom']!=null) $this->value['bottom'] = $this->convertToMM($this->value['bottom'], $this->getLastHeight(true));
  929. if ($this->value['left']!=null) $this->value['left'] = $this->convertToMM($this->value['left'], $this->getLastWidth(true));
  930. if ($this->value['right']!=null) $this->value['right'] = $this->convertToMM($this->value['right'], $this->getLastWidth(true));
  931. if ($this->value['top'] && $this->value['bottom'] && $this->value['height']) $this->value['bottom'] = null;
  932. if ($this->value['left'] && $this->value['right'] && $this->value['width']) $this->value['right'] = null;
  933. return $return;
  934. }
  935. /**
  936. * get the height of the current line
  937. *
  938. * @access public
  939. * @return float $height in mm
  940. */
  941. public function getLineHeight()
  942. {
  943. $val = $this->value['line-height'];
  944. if ($val=='normal') $val = '108%';
  945. return $this->convertToMM($val, $this->value['font-size']);
  946. }
  947. /**
  948. * get the width of the parent
  949. *
  950. * @access public
  951. * @param boolean $mode true => adding padding and border
  952. * @return float $width in mm
  953. */
  954. public function getLastWidth($mode = false)
  955. {
  956. for ($k=count($this->table)-1; $k>=0; $k--) {
  957. if ($this->table[$k]['width']) {
  958. $w = $this->table[$k]['width'];
  959. if ($mode) {
  960. $w+= $this->table[$k]['border']['l']['width'] + $this->table[$k]['padding']['l'] + 0.02;
  961. $w+= $this->table[$k]['border']['r']['width'] + $this->table[$k]['padding']['r'] + 0.02;
  962. }
  963. return $w;
  964. }
  965. }
  966. return $this->_pdf->getW() - $this->_pdf->getlMargin() - $this->_pdf->getrMargin();
  967. }
  968. /**
  969. * get the height of the parent
  970. *
  971. * @access public
  972. * @param boolean $mode true => adding padding and border
  973. * @return float $height in mm
  974. */
  975. public function getLastHeight($mode = false)
  976. {
  977. for ($k=count($this->table)-1; $k>=0; $k--) {
  978. if ($this->table[$k]['height']) {
  979. $h = $this->table[$k]['height'];
  980. if ($mode) {
  981. $h+= $this->table[$k]['border']['t']['width'] + $this->table[$k]['padding']['t'] + 0.02;
  982. $h+= $this->table[$k]['border']['b']['width'] + $this->table[$k]['padding']['b'] + 0.02;
  983. }
  984. return $h;
  985. }
  986. }
  987. return $this->_pdf->getH() - $this->_pdf->gettMargin() - $this->_pdf->getbMargin();
  988. }
  989. /**
  990. * get the value of the float property
  991. *
  992. * @access public
  993. * @return $float left/right
  994. */
  995. public function getFloat()
  996. {
  997. if ($this->value['float']=='left') return 'left';
  998. if ($this->value['float']=='right') return 'right';
  999. return null;
  1000. }
  1001. /**
  1002. * get the last value for a specific key
  1003. *
  1004. * @access public
  1005. * @param string $key
  1006. * @return mixed
  1007. */
  1008. public function getLastValue($key)
  1009. {
  1010. $nb = count($this->table);
  1011. if ($nb>0) {
  1012. return $this->table[$nb-1][$key];
  1013. } else {
  1014. return null;
  1015. }
  1016. }
  1017. /**
  1018. * get the last absolute X
  1019. *
  1020. * @access protected
  1021. * @return float $x
  1022. */
  1023. protected function _getLastAbsoluteX()
  1024. {
  1025. for ($k=count($this->table)-1; $k>=0; $k--) {
  1026. if ($this->table[$k]['x'] && $this->table[$k]['position']) return $this->table[$k]['x'];
  1027. }
  1028. return $this->_pdf->getlMargin();
  1029. }
  1030. /**
  1031. * get the last absolute Y
  1032. *
  1033. * @access protected
  1034. * @return float $y
  1035. */
  1036. protected function _getLastAbsoluteY()
  1037. {
  1038. for ($k=count($this->table)-1; $k>=0; $k--) {
  1039. if ($this->table[$k]['y'] && $this->table[$k]['position']) return $this->table[$k]['y'];
  1040. }
  1041. return $this->_pdf->gettMargin();
  1042. }
  1043. /**
  1044. * get the CSS properties of the current tag
  1045. *
  1046. * @access protected
  1047. * @return array $styles
  1048. */
  1049. protected function _getFromCSS()
  1050. {
  1051. // styles to apply
  1052. $styles = array();
  1053. // list of the selectors to get in the CSS files
  1054. $getit = array();
  1055. // get the list of the selectors of each tags
  1056. $lst = array();
  1057. $lst[] = $this->value['id_lst'];
  1058. for ($i=count($this->table)-1; $i>=0; $i--) {
  1059. $lst[] = $this->table[$i]['id_lst'];
  1060. }
  1061. // foreach selectors in the CSS files, verify if it match with the list of selectors
  1062. foreach ($this->cssKeys as $key => $num) {
  1063. if ($this->_getReccursiveStyle($key, $lst)) {
  1064. $getit[$key] = $num;
  1065. }
  1066. }
  1067. // if we have selectors
  1068. if (count($getit)) {
  1069. // get them, but in the definition order, because of priority
  1070. asort($getit);
  1071. foreach ($getit as $key => $val) $styles = array_merge($styles, $this->css[$key]);
  1072. }
  1073. return $styles;
  1074. }
  1075. /**
  1076. * identify if the selector $key match with the list of tag selectors
  1077. *
  1078. * @access protected
  1079. * @param string $key CSS selector to analyse
  1080. * @param array $lst list of the selectors of each tags
  1081. * @param string $next next step of parsing the selector
  1082. * @return boolean
  1083. */
  1084. protected function _getReccursiveStyle($key, $lst, $next = null)
  1085. {
  1086. // if next step
  1087. if ($next!==null) {
  1088. // we remove this step
  1089. if ($next) $key = trim(substr($key, 0, -strlen($next)));
  1090. array_shift($lst);
  1091. // if no more step to identify => return false
  1092. if (!count($lst)) {
  1093. return false;
  1094. }
  1095. }
  1096. // for each selector of the current step
  1097. foreach ($lst[0] as $name) {
  1098. // if selector = key => ok
  1099. if ($key==$name) {
  1100. return true;
  1101. }
  1102. // if the end of the key = the selector and the next step is ok => ok
  1103. if (substr($key, -strlen(' '.$name))==' '.$name && $this->_getReccursiveStyle($key, $lst, $name)) {
  1104. return true;
  1105. }
  1106. }
  1107. // if we are not in the first step, we analyse the sub steps (the pareng tag of the current tag)
  1108. if ($next!==null && $this->_getReccursiveStyle($key, $lst, '')) {
  1109. return true;
  1110. }
  1111. // no corresponding found
  1112. return false;
  1113. }
  1114. /**
  1115. * Analyse a border
  1116. *
  1117. * @access public
  1118. * @param string $css css border properties
  1119. * @return array border properties
  1120. */
  1121. public function readBorder($css)
  1122. {
  1123. // border none
  1124. $none = array('type' => 'none', 'width' => 0, 'color' => array(0, 0, 0));
  1125. // default value
  1126. $type = 'solid';
  1127. $width = $this->convertToMM('1pt');
  1128. $color = array(0, 0, 0);
  1129. // clean up the values
  1130. $css = explode(' ', $css);
  1131. foreach ($css as $k => $v) {
  1132. $v = trim($v);
  1133. if ($v) $css[$k] = $v;
  1134. else unset($css[$k]);
  1135. }
  1136. $css = array_values($css);
  1137. // read the values
  1138. $res = null;
  1139. foreach ($css as $value) {
  1140. // if no border => return none
  1141. if ($value=='none' || $value=='hidden') {
  1142. return $none;
  1143. }
  1144. // try to convert the value as a distance
  1145. $tmp = $this->convertToMM($value);
  1146. // if the convert is ok => it is a width
  1147. if ($tmp!==null) {
  1148. $width = $tmp;
  1149. // else, it could be the type
  1150. } else if (in_array($value, array('solid', 'dotted', 'dashed', 'double'))) {
  1151. $type = $value;
  1152. // else, it could be the color
  1153. } else {
  1154. $tmp = $this->convertToColor($value, $res);
  1155. if ($res) $color = $tmp;
  1156. }
  1157. }
  1158. // if no witdh => return none
  1159. if (!$width) return $none;
  1160. // return the border properties
  1161. return array('type' => $type, 'width' => $width, 'color' => $color);
  1162. }
  1163. /**
  1164. * duplicate the borders if needed
  1165. *
  1166. * @access protected
  1167. * @param &array $val
  1168. */
  1169. protected function _duplicateBorder(&$val)
  1170. {
  1171. // 1 value => L => RTB
  1172. if (count($val)==1) {
  1173. $val[1] = $val[0];
  1174. $val[2] = $val[0];
  1175. $val[3] = $val[0];
  1176. // 2 values => L => R & T => B
  1177. } else if (count($val)==2) {
  1178. $val[2] = $val[0];
  1179. $val[3] = $val[1];
  1180. // 3 values => T => B
  1181. } else if (count($val)==3) {
  1182. $val[3] = $val[1];
  1183. }
  1184. }
  1185. /**
  1186. * Analyse a background
  1187. *
  1188. * @access public
  1189. * @param string $css css background properties
  1190. * @param &array $value parsed values (by reference, because, ther is a legacy of the parent CSS properties)
  1191. */
  1192. public function convertBackground($css, &$value)
  1193. {
  1194. // is there a image ?
  1195. $text = '/url\(([^)]*)\)/isU';
  1196. if (preg_match($text, $css, $match)) {
  1197. // get the image
  1198. $value['image'] = $this->convertBackgroundImage($match[0]);
  1199. // remove if from the css properties
  1200. $css = preg_replace($text, '', $css);
  1201. $css = preg_replace('/[\s]+/', ' ', $css);
  1202. }
  1203. // protect some spaces
  1204. $css = preg_replace('/,[\s]+/', ',', $css);
  1205. // explode the values
  1206. $css = explode(' ', $css);
  1207. // background position to parse
  1208. $pos = '';
  1209. // foreach value
  1210. foreach ($css as $val) {
  1211. // try to parse the value as a color
  1212. $ok = false;
  1213. $color = $this->convertToColor($val, $ok);
  1214. // if ok => it is a color
  1215. if ($ok) {
  1216. $value['color'] = $color;
  1217. // else if transparent => no coloĂ r
  1218. } else if ($val=='transparent') {
  1219. $value['color'] = null;
  1220. // else
  1221. } else {
  1222. // try to parse the value as a repeat
  1223. $repeat = $this->convertBackgroundRepeat($val);
  1224. // if ok => it is repeat
  1225. if ($repeat) {
  1226. $value['repeat'] = $repeat;
  1227. // else => it could only be a position
  1228. } else {
  1229. $pos.= ($pos ? ' ' : '').$val;
  1230. }
  1231. }
  1232. }
  1233. // if we have a position to parse
  1234. if ($pos) {
  1235. // try to read it
  1236. $pos = $this->convertBackgroundPosition($pos, $ok);
  1237. if ($ok) $value['position'] = $pos;
  1238. }
  1239. }
  1240. /**
  1241. * parse a background color
  1242. *
  1243. * @access public
  1244. * @param string $css
  1245. * @return string $value
  1246. */
  1247. public function convertBackgroundColor($css)
  1248. {
  1249. $res = null;
  1250. if ($css=='transparent') return null;
  1251. else return $this->convertToColor($css, $res);
  1252. }
  1253. /**
  1254. * parse a background image
  1255. *
  1256. * @access public
  1257. * @param string $css
  1258. * @return string $value
  1259. */
  1260. public function convertBackgroundImage($css)
  1261. {
  1262. if ($css=='none')
  1263. return null;
  1264. else if (preg_match('/^url\(([^)]*)\)$/isU', $css, $match))
  1265. return $match[1];
  1266. else
  1267. return null;
  1268. }
  1269. /**
  1270. * parse a background position
  1271. *
  1272. * @access public
  1273. * @param string $css
  1274. * @param &boolean $res flag if conver is ok or not
  1275. * @return array ($x, $y)
  1276. */
  1277. public function convertBackgroundPosition($css, &$res)
  1278. {
  1279. // init the res
  1280. $res = false;
  1281. // explode the value
  1282. $css = explode(' ', $css);
  1283. // we must have 2 values. if 0 or >2 : error. if 1 => put center for 2
  1284. if (count($css)<2) {
  1285. if (!$css[0]) return null;
  1286. $css[1] = 'center';
  1287. }
  1288. if (count($css)>2) return null;
  1289. // prepare the values
  1290. $x = 0;
  1291. $y = 0;
  1292. $res = true;
  1293. // convert the first value
  1294. if ($css[0]=='left') $x = '0%';
  1295. else if ($css[0]=='center') $x = '50%';
  1296. else if ($css[0]=='right') $x = '100%';
  1297. else if ($css[0]=='top') $y = '0%';
  1298. else if ($css[0]=='bottom') $y = '100%';
  1299. else if (preg_match('/^[-]?[0-9\.]+%$/isU', $css[0])) $x = $css[0];
  1300. else if ($this->convertToMM($css[0])) $x = $this->convertToMM($css[0]);
  1301. else $res = false;
  1302. // convert the second value
  1303. if ($css[1]=='left') $x = '0%';
  1304. else if ($css[1]=='right') $x = '100%';
  1305. else if ($css[1]=='top') $y = '0%';
  1306. else if ($css[1]=='center') $y = '50%';
  1307. else if ($css[1]=='bottom') $y = '100%';
  1308. else if (preg_match('/^[-]?[0-9\.]+%$/isU', $css[1])) $y = $css[1];
  1309. else if ($this->convertToMM($css[1])) $y = $this->convertToMM($css[1]);
  1310. else $res = false;
  1311. // return the values
  1312. return array($x, $y);
  1313. }
  1314. /**
  1315. * parse a background repeat
  1316. *
  1317. * @access public
  1318. * @param string $css
  1319. * @return string $value
  1320. */
  1321. public function convertBackgroundRepeat($css)
  1322. {
  1323. switch($css)
  1324. {
  1325. case 'repeat':
  1326. return array(true, true);
  1327. case 'repeat-x':
  1328. return array(true, false);
  1329. case 'repeat-y':
  1330. return array(false, true);
  1331. case 'no-repeat':
  1332. return array(false, false);
  1333. }
  1334. return null;
  1335. }
  1336. /**
  1337. * convert a distance to mm
  1338. *
  1339. * @access public
  1340. * @param string $css distance to convert
  1341. * @param float $old parent distance
  1342. * @return float $value
  1343. */
  1344. public function convertToMM($css, $old=0.)
  1345. {
  1346. $css = trim($css);
  1347. if (preg_match('/^[0-9\.\-]+$/isU', $css)) $css.= 'px';
  1348. if (preg_match('/^[0-9\.\-]+px$/isU', $css)) $css = 25.4/96. * str_replace('px', '', $css);
  1349. else if (preg_match('/^[0-9\.\-]+pt$/isU', $css)) $css = 25.4/72. * str_replace('pt', '', $css);
  1350. else if (preg_match('/^[0-9\.\-]+in$/isU', $css)) $css = 25.4 * str_replace('in', '', $css);
  1351. else if (preg_match('/^[0-9\.\-]+mm$/isU', $css)) $css = 1.*str_replace('mm', '', $css);
  1352. else if (preg_match('/^[0-9\.\-]+%$/isU', $css)) $css = 1.*$old*str_replace('%', '', $css)/100.;
  1353. else $css = null;
  1354. return $css;
  1355. }
  1356. /**
  1357. * convert a css radius
  1358. *
  1359. * @access public
  1360. * @param string $css
  1361. * @return float $value
  1362. */
  1363. public function convertToRadius($css)
  1364. {
  1365. // explode the value
  1366. $css = explode(' ', $css);
  1367. foreach ($css as $k => $v) {
  1368. $v = trim($v);
  1369. if ($v) {
  1370. $v = $this->convertToMM($v, 0);
  1371. if ($v!==null) {
  1372. $css[$k] = $v;
  1373. } else {
  1374. unset($css[$k]);
  1375. }
  1376. } else {
  1377. unset($css[$k]);
  1378. }
  1379. }
  1380. return array_values($css);
  1381. }
  1382. /**
  1383. * convert a css color
  1384. *
  1385. * @access public
  1386. * @param string $css
  1387. * @param &boolean $res
  1388. * @return array (r,g, b)
  1389. */
  1390. public function convertToColor($css, &$res)
  1391. {
  1392. // prepare the value
  1393. $css = trim($css);
  1394. $res = true;
  1395. // if transparent => return null
  1396. if (strtolower($css)=='transparent') return array(null, null, null);
  1397. // HTML color
  1398. if (isset($this->_htmlColor[strtolower($css)])) {
  1399. $css = $this->_htmlColor[strtolower($css)];
  1400. $r = floatVal(hexdec(substr($css, 0, 2)));
  1401. $v = floatVal(hexdec(substr($css, 2, 2)));
  1402. $b = floatVal(hexdec(substr($css, 4, 2)));
  1403. return array($r, $v, $b);
  1404. }
  1405. // like #FFFFFF
  1406. if (preg_match('/^#[0-9A-Fa-f]{6}$/isU', $css)) {
  1407. $r = floatVal(hexdec(substr($css, 1, 2)));
  1408. $v = floatVal(hexdec(substr($css, 3, 2)));
  1409. $b = floatVal(hexdec(substr($css, 5, 2)));
  1410. return array($r, $v, $b);
  1411. }
  1412. // like #FFF
  1413. if (preg_match('/^#[0-9A-F]{3}$/isU', $css)) {
  1414. $r = floatVal(hexdec(substr($css, 1, 1).substr($css, 1, 1)));
  1415. $v = floatVal(hexdec(substr($css, 2, 1).substr($css, 2, 1)));
  1416. $b = floatVal(hexdec(substr($css, 3, 1).substr($css, 3, 1)));
  1417. return array($r, $v, $b);
  1418. }
  1419. // like rgb(100, 100, 100)
  1420. if (preg_match('/rgb\([\s]*([0-9%\.]+)[\s]*,[\s]*([0-9%\.]+)[\s]*,[\s]*([0-9%\.]+)[\s]*\)/isU', $css, $match)) {
  1421. $r = $this->_convertSubColor($match[1]);
  1422. $v = $this->_convertSubColor($match[2]);
  1423. $b = $this->_convertSubColor($match[3]);
  1424. return array($r*255., $v*255., $b*255.);
  1425. }
  1426. // like cmyk(100, 100, 100, 100)
  1427. if (preg_match('/cmyk\([\s]*([0-9%\.]+)[\s]*,[\s]*([0-9%\.]+)[\s]*,[\s]*([0-9%\.]+)[\s]*,[\s]*([0-9%\.]+)[\s]*\)/isU', $css, $match)) {
  1428. $c = $this->_convertSubColor($match[1]);
  1429. $m = $this->_convertSubColor($match[2]);
  1430. $y = $this->_convertSubColor($match[3]);
  1431. $k = $this->_convertSubColor($match[4]);
  1432. return array($c*100., $m*100., $y*100., $k*100.);
  1433. }
  1434. $res = false;
  1435. return array(0., 0., 0.);
  1436. }
  1437. /**
  1438. * color value to convert
  1439. *
  1440. * @access protected
  1441. * @param string $c
  1442. * @return float $c 0.->1.
  1443. */
  1444. protected function _convertSubColor($c)
  1445. {
  1446. if (substr($c, -1)=='%') {
  1447. $c = floatVal(substr($c, 0, -1))/100.;
  1448. } else {
  1449. $c = floatVal($c);
  1450. if ($c>1) $c = $c/255.;
  1451. }
  1452. return $c;
  1453. }
  1454. /**
  1455. * read a css content
  1456. *
  1457. * @access protected
  1458. * @param &string $code
  1459. */
  1460. protected function _analyseStyle(&$code)
  1461. {
  1462. // clean the spaces
  1463. $code = preg_replace('/[\s]+/', ' ', $code);
  1464. // remove the comments
  1465. $code = preg_replace('/\/\*.*?\*\//s', '', $code);
  1466. // split each CSS code "selector { value }"
  1467. preg_match_all('/([^{}]+){([^}]*)}/isU', $code, $match);
  1468. // for each CSS code
  1469. for ($k=0; $k<count($match[0]); $k++) {
  1470. // selectors
  1471. $names = strtolower(trim($match[1][$k]));
  1472. // css style
  1473. $styles = trim($match[2][$k]);
  1474. // explode each value
  1475. $styles = explode(';', $styles);
  1476. // parse each value
  1477. $css = array();
  1478. foreach ($styles as $style) {
  1479. $tmp = explode(':', $style);
  1480. if (count($tmp)>1) {
  1481. $cod = $tmp[0]; unset($tmp[0]); $tmp = implode(':', $tmp);
  1482. $css[trim(strtolower($cod))] = trim($tmp);
  1483. }
  1484. }
  1485. // explode the names
  1486. $names = explode(',', $names);
  1487. // save the values for each names
  1488. foreach ($names as $name) {
  1489. // clean the name
  1490. $name = trim($name);
  1491. // if a selector with somethink lige :hover => continue
  1492. if (strpos($name, ':')!==false) continue;
  1493. // save the value
  1494. if (!isset($this->css[$name]))
  1495. $this->css[$name] = $css;
  1496. else
  1497. $this->css[$name] = array_merge($this->css[$name], $css);
  1498. }
  1499. }
  1500. // get he list of the keys
  1501. $this->cssKeys = array_flip(array_keys($this->css));
  1502. }
  1503. /**
  1504. * Extract the css files from a html code
  1505. *
  1506. * @access public
  1507. * @param string &$html
  1508. */
  1509. public function readStyle(&$html)
  1510. {
  1511. // the CSS content
  1512. $style = ' ';
  1513. // extract the link tags, and remove them in the html code
  1514. preg_match_all('/<link([^>]*)>/isU', $html, $match);
  1515. $html = preg_replace('/<link[^>]*>/isU', '', $html);
  1516. $html = preg_replace('/<\/link[^>]*>/isU', '', $html);
  1517. // analyse each link tag
  1518. foreach ($match[1] as $code) {
  1519. $tmp = array();
  1520. // read the attributes name=value
  1521. $prop = '([a-zA-Z0-9_]+)=([^"\'\s>]+)';
  1522. preg_match_all('/'.$prop.'/is', $code, $match);
  1523. for ($k=0; $k<count($match[0]); $k++) {
  1524. $tmp[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
  1525. }
  1526. // read the attributes name="value"
  1527. $prop = '([a-zA-Z0-9_]+)=["]([^"]*)["]';
  1528. preg_match_all('/'.$prop.'/is', $code, $match);
  1529. for ($k=0; $k<count($match[0]); $k++) {
  1530. $tmp[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
  1531. }
  1532. // read the attributes name='value'
  1533. $prop = "([a-zA-Z0-9_]+)=[']([^']*)[']";
  1534. preg_match_all('/'.$prop.'/is', $code, $match);
  1535. for ($k=0; $k<count($match[0]); $k++) {
  1536. $tmp[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
  1537. }
  1538. // if type text/css => we keep it
  1539. if (isset($tmp['type']) && strtolower($tmp['type'])=='text/css' && isset($tmp['href'])) {
  1540. // get the href
  1541. $url = $tmp['href'];
  1542. // get the content of the css file
  1543. $content = @file_get_contents($url);
  1544. // if "http://" in the url
  1545. if (strpos($url, 'http://')!==false) {
  1546. // get the domain "http://xxx/"
  1547. $url = str_replace('http://', '', $url);
  1548. $url = explode('/', $url);
  1549. $urlMain = 'http://'.$url[0].'/';
  1550. // get the absolute url of the path
  1551. $urlSelf = $url; unset($urlSelf[count($urlSelf)-1]); $urlSelf = 'http://'.implode('/', $urlSelf).'/';
  1552. // adapt the url in the css content
  1553. $content = preg_replace('/url\(([^\\\\][^)]*)\)/isU', 'url('.$urlSelf.'$1)', $content);
  1554. $content = preg_replace('/url\((\\\\[^)]*)\)/isU', 'url('.$urlMain.'$1)', $content);
  1555. } else {
  1556. // @TODO correction on url in absolute on a local css content
  1557. // $content = preg_replace('/url\(([^)]*)\)/isU', 'url('.dirname($url).'/$1)', $content);
  1558. }
  1559. // add to the CSS content
  1560. $style.= $content."\n";
  1561. }
  1562. }
  1563. // extract the style tags des tags style, and remove them in the html code
  1564. preg_match_all('/<style[^>]*>(.*)<\/style[^>]*>/isU', $html, $match);
  1565. $html = preg_replace('/<style[^>]*>(.*)<\/style[^>]*>/isU', '', $html);
  1566. // analyse each style tags
  1567. foreach ($match[1] as $code) {
  1568. // add to the CSS content
  1569. $code = str_replace('<!--', '', $code);
  1570. $code = str_replace('-->', '', $code);
  1571. $style.= $code."\n";
  1572. }
  1573. //analyse the css content
  1574. $this->_analyseStyle($style);
  1575. }
  1576. }