Frame_Reflower

Base reflower class.

Defined (1)

The class is defined in the following location(s).

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