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

/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php

https://bitbucket.org/openemr/openemr
PHP | 460 lines | 324 code | 49 blank | 87 comment | 27 complexity | 87b419efb992eb00fe190edaec1d4c5d MD5 | raw file
Possible License(s): Apache-2.0, AGPL-1.0, GPL-2.0, LGPL-3.0, BSD-3-Clause, Unlicense, MPL-2.0, GPL-3.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package dompdf
  4. * @link http://dompdf.github.com/
  5. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  6. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  7. */
  8. namespace Dompdf\FrameReflower;
  9. use Dompdf\Adapter\CPDF;
  10. use Dompdf\Css\Style;
  11. use Dompdf\Dompdf;
  12. use Dompdf\Helpers;
  13. use Dompdf\Frame;
  14. use Dompdf\FrameDecorator\Block;
  15. use Dompdf\Frame\Factory;
  16. /**
  17. * Base reflower class
  18. *
  19. * Reflower objects are responsible for determining the width and height of
  20. * individual frames. They also create line and page breaks as necessary.
  21. *
  22. * @package dompdf
  23. */
  24. abstract class AbstractFrameReflower
  25. {
  26. /**
  27. * Frame for this reflower
  28. *
  29. * @var Frame
  30. */
  31. protected $_frame;
  32. /**
  33. * Cached min/max size
  34. *
  35. * @var array
  36. */
  37. protected $_min_max_cache;
  38. function __construct(Frame $frame)
  39. {
  40. $this->_frame = $frame;
  41. $this->_min_max_cache = null;
  42. }
  43. function dispose()
  44. {
  45. }
  46. /**
  47. * @return Dompdf
  48. */
  49. function get_dompdf()
  50. {
  51. return $this->_frame->get_dompdf();
  52. }
  53. /**
  54. * Collapse frames margins
  55. * http://www.w3.org/TR/CSS2/box.html#collapsing-margins
  56. */
  57. protected function _collapse_margins()
  58. {
  59. $frame = $this->_frame;
  60. $cb = $frame->get_containing_block();
  61. $style = $frame->get_style();
  62. if (!$frame->is_in_flow()) {
  63. return;
  64. }
  65. $t = $style->length_in_pt($style->margin_top, $cb["h"]);
  66. $b = $style->length_in_pt($style->margin_bottom, $cb["h"]);
  67. // Handle 'auto' values
  68. if ($t === "auto") {
  69. $style->margin_top = "0pt";
  70. $t = 0;
  71. }
  72. if ($b === "auto") {
  73. $style->margin_bottom = "0pt";
  74. $b = 0;
  75. }
  76. // Collapse vertical margins:
  77. $n = $frame->get_next_sibling();
  78. if ($n && !$n->is_block()) {
  79. while ($n = $n->get_next_sibling()) {
  80. if ($n->is_block()) {
  81. break;
  82. }
  83. if (!$n->get_first_child()) {
  84. $n = null;
  85. break;
  86. }
  87. }
  88. }
  89. if ($n) {
  90. $n_style = $n->get_style();
  91. $b = max($b, $n_style->length_in_pt($n_style->margin_top, $cb["h"]));
  92. $n_style->margin_top = "0pt";
  93. $style->margin_bottom = $b . "pt";
  94. }
  95. // Collapse our first child's margin
  96. /*$f = $this->_frame->get_first_child();
  97. if ( $f && !$f->is_block() ) {
  98. while ( $f = $f->get_next_sibling() ) {
  99. if ( $f->is_block() ) {
  100. break;
  101. }
  102. if ( !$f->get_first_child() ) {
  103. $f = null;
  104. break;
  105. }
  106. }
  107. }
  108. // Margin are collapsed only between block elements
  109. if ( $f ) {
  110. $f_style = $f->get_style();
  111. $t = max($t, $f_style->length_in_pt($f_style->margin_top, $cb["h"]));
  112. $style->margin_top = $t."pt";
  113. $f_style->margin_bottom = "0pt";
  114. }*/
  115. }
  116. //........................................................................
  117. abstract function reflow(Block $block = null);
  118. //........................................................................
  119. // Required for table layout: Returns an array(0 => min, 1 => max, "min"
  120. // => min, "max" => max) of the minimum and maximum widths of this frame.
  121. // This provides a basic implementation. Child classes should override
  122. // this if necessary.
  123. function get_min_max_width()
  124. {
  125. if (!is_null($this->_min_max_cache)) {
  126. return $this->_min_max_cache;
  127. }
  128. $style = $this->_frame->get_style();
  129. // Account for margins & padding
  130. $dims = array($style->padding_left,
  131. $style->padding_right,
  132. $style->border_left_width,
  133. $style->border_right_width,
  134. $style->margin_left,
  135. $style->margin_right);
  136. $cb_w = $this->_frame->get_containing_block("w");
  137. $delta = $style->length_in_pt($dims, $cb_w);
  138. // Handle degenerate case
  139. if (!$this->_frame->get_first_child()) {
  140. return $this->_min_max_cache = array(
  141. $delta, $delta,
  142. "min" => $delta,
  143. "max" => $delta,
  144. );
  145. }
  146. $low = array();
  147. $high = array();
  148. for ($iter = $this->_frame->get_children()->getIterator();
  149. $iter->valid();
  150. $iter->next()) {
  151. $inline_min = 0;
  152. $inline_max = 0;
  153. // Add all adjacent inline widths together to calculate max width
  154. while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) {
  155. $child = $iter->current();
  156. $minmax = $child->get_min_max_width();
  157. if (in_array($iter->current()->get_style()->white_space, array("pre", "nowrap"))) {
  158. $inline_min += $minmax["min"];
  159. } else {
  160. $low[] = $minmax["min"];
  161. }
  162. $inline_max += $minmax["max"];
  163. $iter->next();
  164. }
  165. if ($inline_max > 0) $high[] = $inline_max;
  166. if ($inline_min > 0) $low[] = $inline_min;
  167. if ($iter->valid()) {
  168. list($low[], $high[]) = $iter->current()->get_min_max_width();
  169. continue;
  170. }
  171. }
  172. $min = count($low) ? max($low) : 0;
  173. $max = count($high) ? max($high) : 0;
  174. // Use specified width if it is greater than the minimum defined by the
  175. // content. If the width is a percentage ignore it for now.
  176. $width = $style->width;
  177. if ($width !== "auto" && !Helpers::is_percent($width)) {
  178. $width = $style->length_in_pt($width, $cb_w);
  179. if ($min < $width) $min = $width;
  180. if ($max < $width) $max = $width;
  181. }
  182. $min += $delta;
  183. $max += $delta;
  184. return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max);
  185. }
  186. /**
  187. * Parses a CSS string containing quotes and escaped hex characters
  188. *
  189. * @param $string string The CSS string to parse
  190. * @param $single_trim
  191. * @return string
  192. */
  193. protected function _parse_string($string, $single_trim = false)
  194. {
  195. if ($single_trim) {
  196. $string = preg_replace('/^[\"\']/', "", $string);
  197. $string = preg_replace('/[\"\']$/', "", $string);
  198. } else {
  199. $string = trim($string, "'\"");
  200. }
  201. $string = str_replace(array("\\\n", '\\"', "\\'"),
  202. array("", '"', "'"), $string);
  203. // Convert escaped hex characters into ascii characters (e.g. \A => newline)
  204. $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
  205. function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); },
  206. $string);
  207. return $string;
  208. }
  209. /**
  210. * Parses a CSS "quotes" property
  211. *
  212. * @return array|null An array of pairs of quotes
  213. */
  214. protected function _parse_quotes()
  215. {
  216. // Matches quote types
  217. $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
  218. $quotes = $this->_frame->get_style()->quotes;
  219. // split on spaces, except within quotes
  220. if (!preg_match_all($re, "$quotes", $matches, PREG_SET_ORDER)) {
  221. return null;
  222. }
  223. $quotes_array = array();
  224. foreach ($matches as $_quote) {
  225. $quotes_array[] = $this->_parse_string($_quote[0], true);
  226. }
  227. if (empty($quotes_array)) {
  228. $quotes_array = array('"', '"');
  229. }
  230. return array_chunk($quotes_array, 2);
  231. }
  232. /**
  233. * Parses the CSS "content" property
  234. *
  235. * @return string|null The resulting string
  236. */
  237. protected function _parse_content()
  238. {
  239. // Matches generated content
  240. $re = "/\n" .
  241. "\s(counters?\\([^)]*\\))|\n" .
  242. "\A(counters?\\([^)]*\\))|\n" .
  243. "\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n" .
  244. "\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" .
  245. "\s([^\s\"']+)|\n" .
  246. "\A([^\s\"']+)\n" .
  247. "/xi";
  248. $content = $this->_frame->get_style()->content;
  249. $quotes = $this->_parse_quotes();
  250. // split on spaces, except within quotes
  251. if (!preg_match_all($re, $content, $matches, PREG_SET_ORDER)) {
  252. return null;
  253. }
  254. $text = "";
  255. foreach ($matches as $match) {
  256. if (isset($match[2]) && $match[2] !== "") {
  257. $match[1] = $match[2];
  258. }
  259. if (isset($match[6]) && $match[6] !== "") {
  260. $match[4] = $match[6];
  261. }
  262. if (isset($match[8]) && $match[8] !== "") {
  263. $match[7] = $match[8];
  264. }
  265. if (isset($match[1]) && $match[1] !== "") {
  266. // counters?(...)
  267. $match[1] = mb_strtolower(trim($match[1]));
  268. // Handle counter() references:
  269. // http://www.w3.org/TR/CSS21/generate.html#content
  270. $i = mb_strpos($match[1], ")");
  271. if ($i === false) {
  272. continue;
  273. }
  274. preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]+)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $match[1], $args);
  275. $counter_id = $args[3];
  276. if (strtolower($args[1]) == 'counter') {
  277. // counter(name [,style])
  278. if (isset($args[5])) {
  279. $type = trim($args[5]);
  280. } else {
  281. $type = null;
  282. }
  283. $p = $this->_frame->lookup_counter_frame($counter_id);
  284. $text .= $p->counter_value($counter_id, $type);
  285. } else if (strtolower($args[1]) == 'counters') {
  286. // counters(name, string [,style])
  287. if (isset($args[5])) {
  288. $string = $this->_parse_string($args[5]);
  289. } else {
  290. $string = "";
  291. }
  292. if (isset($args[7])) {
  293. $type = trim($args[7]);
  294. } else {
  295. $type = null;
  296. }
  297. $p = $this->_frame->lookup_counter_frame($counter_id);
  298. $tmp = array();
  299. while ($p) {
  300. // We only want to use the counter values when they actually increment the counter
  301. if (array_key_exists($counter_id, $p->_counters)) {
  302. array_unshift($tmp, $p->counter_value($counter_id, $type));
  303. }
  304. $p = $p->lookup_counter_frame($counter_id);
  305. }
  306. $text .= implode($string, $tmp);
  307. } else {
  308. // countertops?
  309. continue;
  310. }
  311. } else if (isset($match[4]) && $match[4] !== "") {
  312. // String match
  313. $text .= $this->_parse_string($match[4]);
  314. } else if (isset($match[7]) && $match[7] !== "") {
  315. // Directive match
  316. if ($match[7] === "open-quote") {
  317. // FIXME: do something here
  318. $text .= $quotes[0][0];
  319. } else if ($match[7] === "close-quote") {
  320. // FIXME: do something else here
  321. $text .= $quotes[0][1];
  322. } else if ($match[7] === "no-open-quote") {
  323. // FIXME:
  324. } else if ($match[7] === "no-close-quote") {
  325. // FIXME:
  326. } else if (mb_strpos($match[7], "attr(") === 0) {
  327. $i = mb_strpos($match[7], ")");
  328. if ($i === false) {
  329. continue;
  330. }
  331. $attr = mb_substr($match[7], 5, $i - 5);
  332. if ($attr == "") {
  333. continue;
  334. }
  335. $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr);
  336. } else {
  337. continue;
  338. }
  339. }
  340. }
  341. return $text;
  342. }
  343. /**
  344. * Sets the generated content of a generated frame
  345. */
  346. protected function _set_content()
  347. {
  348. $frame = $this->_frame;
  349. $style = $frame->get_style();
  350. // if the element was pushed to a new page use the saved counter value, otherwise use the CSS reset value
  351. if ($style->counter_reset && ($reset = $style->counter_reset) !== "none") {
  352. $vars = preg_split('/\s+/', trim($reset), 2);
  353. $frame->reset_counter($vars[0], (isset($frame->_counters['__' . $vars[0]]) ? $frame->_counters['__' . $vars[0]] : (isset($vars[1]) ? $vars[1] : 0)));
  354. }
  355. if ($style->counter_increment && ($increment = $style->counter_increment) !== "none") {
  356. $frame->increment_counters($increment);
  357. }
  358. if ($style->content && !$frame->get_first_child() && $frame->get_node()->nodeName === "dompdf_generated") {
  359. $content = $this->_parse_content();
  360. // add generated content to the font subset
  361. // FIXME: This is currently too late because the font subset has already been generated.
  362. // See notes in issue #750.
  363. if ($frame->get_dompdf()->get_option("enable_font_subsetting") && $frame->get_dompdf()->get_canvas() instanceof CPDF) {
  364. $frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $content);
  365. }
  366. $node = $frame->get_node()->ownerDocument->createTextNode($content);
  367. $new_style = $style->get_stylesheet()->create_style();
  368. $new_style->inherit($style);
  369. $new_frame = new Frame($node);
  370. $new_frame->set_style($new_style);
  371. Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
  372. $frame->append_child($new_frame);
  373. }
  374. }
  375. }