attribute_translator.cls.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <?php
  2. /**
  3. * @package dompdf
  4. * @link http://dompdf.github.com/
  5. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  6. * @author Fabien Ménager <fabien.menager@gmail.com>
  7. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  8. */
  9. /**
  10. * Translates HTML 4.0 attributes into CSS rules
  11. *
  12. * @package dompdf
  13. */
  14. class Attribute_Translator {
  15. static $_style_attr = "_html_style_attribute";
  16. // Munged data originally from
  17. // http://www.w3.org/TR/REC-html40/index/attributes.html
  18. // http://www.cs.tut.fi/~jkorpela/html2css.html
  19. static private $__ATTRIBUTE_LOOKUP = array(
  20. //'caption' => array ( 'align' => '', ),
  21. 'img' => array(
  22. 'align' => array(
  23. 'bottom' => 'vertical-align: baseline;',
  24. 'middle' => 'vertical-align: middle;',
  25. 'top' => 'vertical-align: top;',
  26. 'left' => 'float: left;',
  27. 'right' => 'float: right;'
  28. ),
  29. 'border' => 'border-width: %0.2F px;',
  30. 'height' => 'height: %s px;',
  31. 'hspace' => 'padding-left: %1$0.2F px; padding-right: %1$0.2F px;',
  32. 'vspace' => 'padding-top: %1$0.2F px; padding-bottom: %1$0.2F px;',
  33. 'width' => 'width: %s px;',
  34. ),
  35. 'table' => array(
  36. 'align' => array(
  37. 'left' => 'margin-left: 0; margin-right: auto;',
  38. 'center' => 'margin-left: auto; margin-right: auto;',
  39. 'right' => 'margin-left: auto; margin-right: 0;'
  40. ),
  41. 'bgcolor' => 'background-color: %s;',
  42. 'border' => '!set_table_border',
  43. 'cellpadding' => '!set_table_cellpadding',//'border-spacing: %0.2F; border-collapse: separate;',
  44. 'cellspacing' => '!set_table_cellspacing',
  45. 'frame' => array(
  46. 'void' => 'border-style: none;',
  47. 'above' => 'border-top-style: solid;',
  48. 'below' => 'border-bottom-style: solid;',
  49. 'hsides' => 'border-left-style: solid; border-right-style: solid;',
  50. 'vsides' => 'border-top-style: solid; border-bottom-style: solid;',
  51. 'lhs' => 'border-left-style: solid;',
  52. 'rhs' => 'border-right-style: solid;',
  53. 'box' => 'border-style: solid;',
  54. 'border' => 'border-style: solid;'
  55. ),
  56. 'rules' => '!set_table_rules',
  57. 'width' => 'width: %s;',
  58. ),
  59. 'hr' => array(
  60. 'align' => '!set_hr_align', // Need to grab width to set 'left' & 'right' correctly
  61. 'noshade' => 'border-style: solid;',
  62. 'size' => '!set_hr_size', //'border-width: %0.2F px;',
  63. 'width' => 'width: %s;',
  64. ),
  65. 'div' => array(
  66. 'align' => 'text-align: %s;',
  67. ),
  68. 'h1' => array(
  69. 'align' => 'text-align: %s;',
  70. ),
  71. 'h2' => array(
  72. 'align' => 'text-align: %s;',
  73. ),
  74. 'h3' => array(
  75. 'align' => 'text-align: %s;',
  76. ),
  77. 'h4' => array(
  78. 'align' => 'text-align: %s;',
  79. ),
  80. 'h5' => array(
  81. 'align' => 'text-align: %s;',
  82. ),
  83. 'h6' => array(
  84. 'align' => 'text-align: %s;',
  85. ),
  86. 'p' => array(
  87. 'align' => 'text-align: %s;',
  88. ),
  89. // 'col' => array(
  90. // 'align' => '',
  91. // 'valign' => '',
  92. // ),
  93. // 'colgroup' => array(
  94. // 'align' => '',
  95. // 'valign' => '',
  96. // ),
  97. 'tbody' => array(
  98. 'align' => '!set_table_row_align',
  99. 'valign' => '!set_table_row_valign',
  100. ),
  101. 'td' => array(
  102. 'align' => 'text-align: %s;',
  103. 'bgcolor' => '!set_background_color',
  104. 'height' => 'height: %s;',
  105. 'nowrap' => 'white-space: nowrap;',
  106. 'valign' => 'vertical-align: %s;',
  107. 'width' => 'width: %s;',
  108. ),
  109. 'tfoot' => array(
  110. 'align' => '!set_table_row_align',
  111. 'valign' => '!set_table_row_valign',
  112. ),
  113. 'th' => array(
  114. 'align' => 'text-align: %s;',
  115. 'bgcolor' => '!set_background_color',
  116. 'height' => 'height: %s;',
  117. 'nowrap' => 'white-space: nowrap;',
  118. 'valign' => 'vertical-align: %s;',
  119. 'width' => 'width: %s;',
  120. ),
  121. 'thead' => array(
  122. 'align' => '!set_table_row_align',
  123. 'valign' => '!set_table_row_valign',
  124. ),
  125. 'tr' => array(
  126. 'align' => '!set_table_row_align',
  127. 'bgcolor' => '!set_table_row_bgcolor',
  128. 'valign' => '!set_table_row_valign',
  129. ),
  130. 'body' => array(
  131. 'background' => 'background-image: url(%s);',
  132. 'bgcolor' => '!set_background_color',
  133. 'link' => '!set_body_link',
  134. 'text' => '!set_color',
  135. ),
  136. 'br' => array(
  137. 'clear' => 'clear: %s;',
  138. ),
  139. 'basefont' => array(
  140. 'color' => '!set_color',
  141. 'face' => 'font-family: %s;',
  142. 'size' => '!set_basefont_size',
  143. ),
  144. 'font' => array(
  145. 'color' => '!set_color',
  146. 'face' => 'font-family: %s;',
  147. 'size' => '!set_font_size',
  148. ),
  149. 'dir' => array(
  150. 'compact' => 'margin: 0.5em 0;',
  151. ),
  152. 'dl' => array(
  153. 'compact' => 'margin: 0.5em 0;',
  154. ),
  155. 'menu' => array(
  156. 'compact' => 'margin: 0.5em 0;',
  157. ),
  158. 'ol' => array(
  159. 'compact' => 'margin: 0.5em 0;',
  160. 'start' => 'counter-reset: -dompdf-default-counter %d;',
  161. 'type' => 'list-style-type: %s;',
  162. ),
  163. 'ul' => array(
  164. 'compact' => 'margin: 0.5em 0;',
  165. 'type' => 'list-style-type: %s;',
  166. ),
  167. 'li' => array(
  168. 'type' => 'list-style-type: %s;',
  169. 'value' => 'counter-reset: -dompdf-default-counter %d;',
  170. ),
  171. 'pre' => array(
  172. 'width' => 'width: %s;',
  173. ),
  174. );
  175. static protected $_last_basefont_size = 3;
  176. static protected $_font_size_lookup = array(
  177. // For basefont support
  178. -3 => "4pt",
  179. -2 => "5pt",
  180. -1 => "6pt",
  181. 0 => "7pt",
  182. 1 => "8pt",
  183. 2 => "10pt",
  184. 3 => "12pt",
  185. 4 => "14pt",
  186. 5 => "18pt",
  187. 6 => "24pt",
  188. 7 => "34pt",
  189. // For basefont support
  190. 8 => "48pt",
  191. 9 => "44pt",
  192. 10 => "52pt",
  193. 11 => "60pt",
  194. );
  195. /**
  196. * @param Frame $frame
  197. */
  198. static function translate_attributes(Frame $frame) {
  199. $node = $frame->get_node();
  200. $tag = $node->nodeName;
  201. if ( !isset(self::$__ATTRIBUTE_LOOKUP[$tag]) ) {
  202. return;
  203. }
  204. $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag];
  205. $attrs = $node->attributes;
  206. $style = rtrim($node->getAttribute(self::$_style_attr), "; ");
  207. if ( $style != "" ) {
  208. $style .= ";";
  209. }
  210. foreach ($attrs as $attr => $attr_node ) {
  211. if ( !isset($valid_attrs[$attr]) ) {
  212. continue;
  213. }
  214. $value = $attr_node->value;
  215. $target = $valid_attrs[$attr];
  216. // Look up $value in $target, if $target is an array:
  217. if ( is_array($target) ) {
  218. if ( isset($target[$value]) ) {
  219. $style .= " " . self::_resolve_target($node, $target[$value], $value);
  220. }
  221. }
  222. else {
  223. // otherwise use target directly
  224. $style .= " " . self::_resolve_target($node, $target, $value);
  225. }
  226. }
  227. if ( !is_null($style) ) {
  228. $style = ltrim($style);
  229. $node->setAttribute(self::$_style_attr, $style);
  230. }
  231. }
  232. /**
  233. * @param DOMNode $node
  234. * @param string $target
  235. * @param string $value
  236. *
  237. * @return string
  238. */
  239. static protected function _resolve_target(DOMNode $node, $target, $value) {
  240. if ( $target[0] === "!" ) {
  241. // Function call
  242. $func = "_" . mb_substr($target, 1);
  243. return self::$func($node, $value);
  244. }
  245. return $value ? sprintf($target, $value) : "";
  246. }
  247. /**
  248. * @param DOMElement $node
  249. * @param string $new_style
  250. */
  251. static function append_style(DOMElement $node, $new_style) {
  252. $style = rtrim($node->getAttribute(self::$_style_attr), ";");
  253. $style .= $new_style;
  254. $style = ltrim($style, ";");
  255. $node->setAttribute(self::$_style_attr, $style);
  256. }
  257. /**
  258. * @param DOMNode $node
  259. *
  260. * @return DOMNodeList|DOMElement[]
  261. */
  262. static protected function get_cell_list(DOMNode $node) {
  263. $xpath = new DOMXpath($node->ownerDocument);
  264. switch($node->nodeName) {
  265. default:
  266. case "table":
  267. $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th";
  268. break;
  269. case "tbody":
  270. case "tfoot":
  271. case "thead":
  272. $query = "tr/td | tr/th";
  273. break;
  274. case "tr":
  275. $query = "td | th";
  276. break;
  277. }
  278. return $xpath->query($query, $node);
  279. }
  280. /**
  281. * @param string $value
  282. *
  283. * @return string
  284. */
  285. static protected function _get_valid_color($value) {
  286. if ( preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches) ) {
  287. $value = "#$matches[1]";
  288. }
  289. return $value;
  290. }
  291. /**
  292. * @param DOMElement $node
  293. * @param string $value
  294. *
  295. * @return string
  296. */
  297. static protected function _set_color(DOMElement $node, $value) {
  298. $value = self::_get_valid_color($value);
  299. return "color: $value;";
  300. }
  301. /**
  302. * @param DOMElement $node
  303. * @param string $value
  304. *
  305. * @return string
  306. */
  307. static protected function _set_background_color(DOMElement $node, $value) {
  308. $value = self::_get_valid_color($value);
  309. return "background-color: $value;";
  310. }
  311. /**
  312. * @param DOMElement $node
  313. * @param string $value
  314. *
  315. * @return null
  316. */
  317. static protected function _set_table_cellpadding(DOMElement $node, $value) {
  318. $cell_list = self::get_cell_list($node);
  319. foreach ($cell_list as $cell) {
  320. self::append_style($cell, "; padding: {$value}px;");
  321. }
  322. return null;
  323. }
  324. /**
  325. * @param DOMElement $node
  326. * @param string $value
  327. *
  328. * @return string
  329. */
  330. static protected function _set_table_border(DOMElement $node, $value) {
  331. $cell_list = self::get_cell_list($node);
  332. foreach ($cell_list as $cell) {
  333. $style = rtrim($cell->getAttribute(self::$_style_attr));
  334. $style .= "; border-width: " . ($value > 0 ? 1 : 0) . "pt; border-style: inset;";
  335. $style = ltrim($style, ";");
  336. $cell->setAttribute(self::$_style_attr, $style);
  337. }
  338. $style = rtrim($node->getAttribute(self::$_style_attr), ";");
  339. $style .= "; border-width: $value" . "px; ";
  340. return ltrim($style, "; ");
  341. }
  342. /**
  343. * @param DOMElement $node
  344. * @param string $value
  345. *
  346. * @return string
  347. */
  348. static protected function _set_table_cellspacing(DOMElement $node, $value) {
  349. $style = rtrim($node->getAttribute(self::$_style_attr), ";");
  350. if ( $value == 0 ) {
  351. $style .= "; border-collapse: collapse;";
  352. }
  353. else {
  354. $style .= "; border-spacing: {$value}px; border-collapse: separate;";
  355. }
  356. return ltrim($style, ";");
  357. }
  358. /**
  359. * @param DOMElement $node
  360. * @param string $value
  361. *
  362. * @return null|string
  363. */
  364. static protected function _set_table_rules(DOMElement $node, $value) {
  365. $new_style = "; border-collapse: collapse;";
  366. switch ($value) {
  367. case "none":
  368. $new_style .= "border-style: none;";
  369. break;
  370. case "groups":
  371. // FIXME: unsupported
  372. return null;
  373. case "rows":
  374. $new_style .= "border-style: solid none solid none; border-width: 1px; ";
  375. break;
  376. case "cols":
  377. $new_style .= "border-style: none solid none solid; border-width: 1px; ";
  378. break;
  379. case "all":
  380. $new_style .= "border-style: solid; border-width: 1px; ";
  381. break;
  382. default:
  383. // Invalid value
  384. return null;
  385. }
  386. $cell_list = self::get_cell_list($node);
  387. foreach ($cell_list as $cell) {
  388. $style = $cell->getAttribute(self::$_style_attr);
  389. $style .= $new_style;
  390. $cell->setAttribute(self::$_style_attr, $style);
  391. }
  392. $style = rtrim($node->getAttribute(self::$_style_attr), ";");
  393. $style .= "; border-collapse: collapse; ";
  394. return ltrim($style, "; ");
  395. }
  396. /**
  397. * @param DOMElement $node
  398. * @param string $value
  399. *
  400. * @return string
  401. */
  402. static protected function _set_hr_size(DOMElement $node, $value) {
  403. $style = rtrim($node->getAttribute(self::$_style_attr), ";");
  404. $style .= "; border-width: ".max(0, $value-2)."; ";
  405. return ltrim($style, "; ");
  406. }
  407. /**
  408. * @param DOMElement $node
  409. * @param string $value
  410. *
  411. * @return null|string
  412. */
  413. static protected function _set_hr_align(DOMElement $node, $value) {
  414. $style = rtrim($node->getAttribute(self::$_style_attr),";");
  415. $width = $node->getAttribute("width");
  416. if ( $width == "" ) {
  417. $width = "100%";
  418. }
  419. $remainder = 100 - (double)rtrim($width, "% ");
  420. switch ($value) {
  421. case "left":
  422. $style .= "; margin-right: $remainder %;";
  423. break;
  424. case "right":
  425. $style .= "; margin-left: $remainder %;";
  426. break;
  427. case "center":
  428. $style .= "; margin-left: auto; margin-right: auto;";
  429. break;
  430. default:
  431. return null;
  432. }
  433. return ltrim($style, "; ");
  434. }
  435. /**
  436. * @param DOMElement $node
  437. * @param string $value
  438. *
  439. * @return null
  440. */
  441. static protected function _set_table_row_align(DOMElement $node, $value) {
  442. $cell_list = self::get_cell_list($node);
  443. foreach ($cell_list as $cell) {
  444. self::append_style($cell, "; text-align: $value;");
  445. }
  446. return null;
  447. }
  448. /**
  449. * @param DOMElement $node
  450. * @param string $value
  451. *
  452. * @return null
  453. */
  454. static protected function _set_table_row_valign(DOMElement $node, $value) {
  455. $cell_list = self::get_cell_list($node);
  456. foreach ($cell_list as $cell) {
  457. self::append_style($cell, "; vertical-align: $value;");
  458. }
  459. return null;
  460. }
  461. /**
  462. * @param DOMElement $node
  463. * @param string $value
  464. *
  465. * @return null
  466. */
  467. static protected function _set_table_row_bgcolor(DOMElement $node, $value) {
  468. $cell_list = self::get_cell_list($node);
  469. $value = self::_get_valid_color($value);
  470. foreach ($cell_list as $cell) {
  471. self::append_style($cell, "; background-color: $value;");
  472. }
  473. return null;
  474. }
  475. /**
  476. * @param DOMElement $node
  477. * @param string $value
  478. *
  479. * @return null
  480. */
  481. static protected function _set_body_link(DOMElement $node, $value) {
  482. $a_list = $node->getElementsByTagName("a");
  483. $value = self::_get_valid_color($value);
  484. foreach ($a_list as $a) {
  485. self::append_style($a, "; color: $value;");
  486. }
  487. return null;
  488. }
  489. /**
  490. * @param DOMElement $node
  491. * @param string $value
  492. *
  493. * @return null
  494. */
  495. static protected function _set_basefont_size(DOMElement $node, $value) {
  496. // FIXME: ? we don't actually set the font size of anything here, just
  497. // the base size for later modification by <font> tags.
  498. self::$_last_basefont_size = $value;
  499. return null;
  500. }
  501. /**
  502. * @param DOMElement $node
  503. * @param string $value
  504. *
  505. * @return string
  506. */
  507. static protected function _set_font_size(DOMElement $node, $value) {
  508. $style = $node->getAttribute(self::$_style_attr);
  509. if ( $value[0] === "-" || $value[0] === "+" ) {
  510. $value = self::$_last_basefont_size + (int)$value;
  511. }
  512. if ( isset(self::$_font_size_lookup[$value]) ) {
  513. $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";";
  514. }
  515. else {
  516. $style .= "; font-size: $value;";
  517. }
  518. return ltrim($style, "; ");
  519. }
  520. }