PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/dompdf/dompdf/include/frame_reflower.cls.php

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