Page_Frame_Decorator

Decorates frames for page layout.

Defined (1)

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

/lib/dompdf/include/page_frame_decorator.cls.php  
  1. class Page_Frame_Decorator extends Frame_Decorator { 
  2.  
  3. /** 
  4. * y value of bottom page margin 
  5. * @var float 
  6. */ 
  7. protected $_bottom_page_margin; 
  8.  
  9. /** 
  10. * Flag indicating page is full. 
  11. * @var bool 
  12. */ 
  13. protected $_page_full; 
  14.  
  15. /** 
  16. * Number of tables currently being reflowed 
  17. * @var int 
  18. */ 
  19. protected $_in_table; 
  20.  
  21. /** 
  22. * The pdf renderer 
  23. * @var Renderer 
  24. */ 
  25. protected $_renderer; 
  26.  
  27. /** 
  28. * This page's floating frames 
  29. *  
  30. * @var array 
  31. */ 
  32. protected $_floating_frames = array(); 
  33.  
  34. //........................................................................ 
  35.  
  36. /** 
  37. * Class constructor 
  38. * @param Frame $frame the frame to decorate 
  39. * @param DOMPDF $dompdf 
  40. */ 
  41. function __construct(Frame $frame, DOMPDF $dompdf) { 
  42. parent::__construct($frame, $dompdf); 
  43. $this->_page_full = false; 
  44. $this->_in_table = 0; 
  45. $this->_bottom_page_margin = null; 
  46.  
  47. /** 
  48. * Set the renderer used for this pdf 
  49. * @param Renderer $renderer the renderer to use 
  50. */ 
  51. function set_renderer($renderer) { 
  52. $this->_renderer = $renderer; 
  53.  
  54. /** 
  55. * Return the renderer used for this pdf 
  56. * @return Renderer 
  57. */ 
  58. function get_renderer() { 
  59. return $this->_renderer; 
  60.  
  61. /** 
  62. * Set the frame's containing block. Overridden to set $this->_bottom_page_margin. 
  63. * @param float $x 
  64. * @param float $y 
  65. * @param float $w 
  66. * @param float $h 
  67. */ 
  68. function set_containing_block($x = null, $y = null, $w = null, $h = null) { 
  69. parent::set_containing_block($x, $y, $w, $h); 
  70. //$w = $this->get_containing_block("w"); 
  71. if ( isset($h) ) 
  72. $this->_bottom_page_margin = $h; // - $this->_frame->get_style()->length_in_pt($this->_frame->get_style()->margin_bottom, $w); 
  73.  
  74. /** 
  75. * Returns true if the page is full and is no longer accepting frames. 
  76. * @return bool 
  77. */ 
  78. function is_full() { 
  79. return $this->_page_full; 
  80.  
  81. /** 
  82. * Start a new page by resetting the full flag. 
  83. */ 
  84. function next_page() { 
  85. $this->_floating_frames = array(); 
  86. $this->_renderer->new_page(); 
  87. $this->_page_full = false; 
  88.  
  89. /** 
  90. * Indicate to the page that a table is currently being reflowed. 
  91. */ 
  92. function table_reflow_start() { 
  93. $this->_in_table++; 
  94.  
  95. /** 
  96. * Indicate to the page that table reflow is finished. 
  97. */ 
  98. function table_reflow_end() { 
  99. $this->_in_table--; 
  100.  
  101. /** 
  102. * Return whether we are currently in a nested table or not 
  103. * @return bool 
  104. */ 
  105. function in_nested_table() { 
  106. return $this->_in_table > 1; 
  107.  
  108. /** 
  109. * Check if a forced page break is required before $frame. This uses the 
  110. * frame's page_break_before property as well as the preceeding frame's 
  111. * page_break_after property. 
  112. * @link http://www.w3.org/TR/CSS21/page.html#forced 
  113. * @param Frame $frame the frame to check 
  114. * @return bool true if a page break occured 
  115. */ 
  116. function check_forced_page_break(Frame $frame) { 
  117.  
  118. // Skip check if page is already split 
  119. if ( $this->_page_full ) 
  120. return null; 
  121.  
  122. $block_types = array("block", "list-item", "table", "inline"); 
  123. $page_breaks = array("always", "left", "right"); 
  124.  
  125. $style = $frame->get_style(); 
  126.  
  127. if ( !in_array($style->display, $block_types) ) 
  128. return false; 
  129.  
  130. // Find the previous block-level sibling 
  131. $prev = $frame->get_prev_sibling(); 
  132.  
  133. while ( $prev && !in_array($prev->get_style()->display, $block_types) ) 
  134. $prev = $prev->get_prev_sibling(); 
  135.  
  136.  
  137. if ( in_array($style->page_break_before, $page_breaks) ) { 
  138.  
  139. // Prevent cascading splits 
  140. $frame->split(null, true); 
  141. // We have to grab the style again here because split() resets 
  142. // $frame->style to the frame's orignal style. 
  143. $frame->get_style()->page_break_before = "auto"; 
  144. $this->_page_full = true; 
  145.  
  146. return true; 
  147.  
  148. if ( $prev && in_array($prev->get_style()->page_break_after, $page_breaks) ) { 
  149. // Prevent cascading splits 
  150. $frame->split(null, true); 
  151. $prev->get_style()->page_break_after = "auto"; 
  152. $this->_page_full = true; 
  153. return true; 
  154.  
  155. if( $prev && $prev->get_last_child() && $frame->get_node()->nodeName != "body" ) { 
  156. $prev_last_child = $prev->get_last_child(); 
  157. if ( in_array($prev_last_child->get_style()->page_break_after, $page_breaks) ) { 
  158. $frame->split(null, true); 
  159. $prev_last_child->get_style()->page_break_after = "auto"; 
  160. $this->_page_full = true; 
  161. return true; 
  162.  
  163.  
  164. return false; 
  165.  
  166. /** 
  167. * Determine if a page break is allowed before $frame 
  168. * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks 
  169. *  
  170. * In the normal flow, page breaks can occur at the following places: 
  171. *  
  172. * 1. In the vertical margin between block boxes. When a page 
  173. * break occurs here, the used values of the relevant 
  174. * 'margin-top' and 'margin-bottom' properties are set to '0'. 
  175. * 2. Between line boxes inside a block box. 
  176. * These breaks are subject to the following rules: 
  177. *  
  178. * * Rule A: Breaking at (1) is allowed only if the 
  179. * 'page-break-after' and 'page-break-before' properties of 
  180. * all the elements generating boxes that meet at this margin 
  181. * allow it, which is when at least one of them has the value 
  182. * 'always', 'left', or 'right', or when all of them are 
  183. * 'auto'. 
  184. * * Rule B: However, if all of them are 'auto' and the 
  185. * nearest common ancestor of all the elements has a 
  186. * 'page-break-inside' value of 'avoid', then breaking here is 
  187. * not allowed. 
  188. * * Rule C: Breaking at (2) is allowed only if the number of 
  189. * line boxes between the break and the start of the enclosing 
  190. * block box is the value of 'orphans' or more, and the number 
  191. * of line boxes between the break and the end of the box is 
  192. * the value of 'widows' or more. 
  193. * * Rule D: In addition, breaking at (2) is allowed only if 
  194. * the 'page-break-inside' property is 'auto'. 
  195. * If the above doesn't provide enough break points to keep 
  196. * content from overflowing the page boxes, then rules B and D are 
  197. * dropped in order to find additional breakpoints. 
  198. * If that still does not lead to sufficient break points, rules A 
  199. * and C are dropped as well, to find still more break points. 
  200. * We will also allow breaks between table rows. However, when 
  201. * splitting a table, the table headers should carry over to the 
  202. * next page (but they don't yet). 
  203. *  
  204. * @param Frame $frame the frame to check 
  205. * @return bool true if a break is allowed, false otherwise 
  206. */ 
  207. protected function _page_break_allowed(Frame $frame) { 
  208.  
  209. $block_types = array("block", "list-item", "table", "-dompdf-image"); 
  210. dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName. ")"); 
  211. $display = $frame->get_style()->display; 
  212.  
  213. // Block Frames (1): 
  214. if ( in_array($display, $block_types) ) { 
  215.  
  216. // Avoid breaks within table-cells 
  217. if ( $this->_in_table ) { 
  218. dompdf_debug("page-break", "In table: " . $this->_in_table); 
  219. return false; 
  220.  
  221. // Rules A & B 
  222.  
  223. if ( $frame->get_style()->page_break_before === "avoid" ) { 
  224. dompdf_debug("page-break", "before: avoid"); 
  225. return false; 
  226.  
  227. // Find the preceeding block-level sibling 
  228. $prev = $frame->get_prev_sibling(); 
  229. while ( $prev && !in_array($prev->get_style()->display, $block_types) ) 
  230. $prev = $prev->get_prev_sibling(); 
  231.  
  232. // Does the previous element allow a page break after? 
  233. if ( $prev && $prev->get_style()->page_break_after === "avoid" ) { 
  234. dompdf_debug("page-break", "after: avoid"); 
  235. return false; 
  236.  
  237. // If both $prev & $frame have the same parent, check the parent's 
  238. // page_break_inside property. 
  239. $parent = $frame->get_parent(); 
  240. if ( $prev && $parent && $parent->get_style()->page_break_inside === "avoid" ) { 
  241. dompdf_debug("page-break", "parent inside: avoid"); 
  242. return false; 
  243.  
  244. // To prevent cascading page breaks when a top-level element has 
  245. // page-break-inside: avoid, ensure that at least one frame is 
  246. // on the page before splitting. 
  247. if ( $parent->get_node()->nodeName === "body" && !$prev ) { 
  248. // We are the body's first child 
  249. dompdf_debug("page-break", "Body's first child."); 
  250. return false; 
  251.  
  252. // If the frame is the first block-level frame, use the value from 
  253. // $frame's parent instead. 
  254. if ( !$prev && $parent ) 
  255. return $this->_page_break_allowed( $parent ); 
  256.  
  257. dompdf_debug("page-break", "block: break allowed"); 
  258. return true; 
  259.  
  260.  
  261. // Inline frames (2): 
  262. else if ( in_array($display, Style::$INLINE_TYPES) ) { 
  263.  
  264. // Avoid breaks within table-cells 
  265. if ( $this->_in_table ) { 
  266. dompdf_debug("page-break", "In table: " . $this->_in_table); 
  267. return false; 
  268.  
  269. // Rule C 
  270. $block_parent = $frame->find_block_parent(); 
  271. if ( count($block_parent->get_line_boxes() ) < $frame->get_style()->orphans ) { 
  272. dompdf_debug("page-break", "orphans"); 
  273. return false; 
  274.  
  275. // FIXME: Checking widows is tricky without having laid out the 
  276. // remaining line boxes. Just ignore it for now... 
  277.  
  278. // Rule D 
  279. $p = $block_parent; 
  280. while ($p) { 
  281. if ( $p->get_style()->page_break_inside === "avoid" ) { 
  282. dompdf_debug("page-break", "parent->inside: avoid"); 
  283. return false; 
  284. $p = $p->find_block_parent(); 
  285.  
  286. // To prevent cascading page breaks when a top-level element has 
  287. // page-break-inside: avoid, ensure that at least one frame with 
  288. // some content is on the page before splitting. 
  289. $prev = $frame->get_prev_sibling(); 
  290. while ( $prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "") ) 
  291. $prev = $prev->get_prev_sibling(); 
  292.  
  293. if ( $block_parent->get_node()->nodeName === "body" && !$prev ) { 
  294. // We are the body's first child 
  295. dompdf_debug("page-break", "Body's first child."); 
  296. return false; 
  297.  
  298. // Skip breaks on empty text nodes 
  299. if ( $frame->is_text_node() && 
  300. $frame->get_node()->nodeValue == "" ) 
  301. return false; 
  302.  
  303. dompdf_debug("page-break", "inline: break allowed"); 
  304. return true; 
  305.  
  306. // Table-rows 
  307. } else if ( $display === "table-row" ) { 
  308.  
  309. // Simply check if the parent table's page_break_inside property is 
  310. // not 'avoid' 
  311. $p = Table_Frame_Decorator::find_parent_table($frame); 
  312.  
  313. while ($p) { 
  314. if ( $p->get_style()->page_break_inside === "avoid" ) { 
  315. dompdf_debug("page-break", "parent->inside: avoid"); 
  316. return false; 
  317. $p = $p->find_block_parent(); 
  318.  
  319. // Avoid breaking after the first row of a table 
  320. if ( $p && $p->get_first_child() === $frame) { 
  321. dompdf_debug("page-break", "table: first-row"); 
  322. return false; 
  323.  
  324. // If this is a nested table, prevent the page from breaking 
  325. if ( $this->_in_table > 1 ) { 
  326. dompdf_debug("page-break", "table: nested table"); 
  327. return false; 
  328.  
  329. dompdf_debug("page-break", "table-row/row-groups: break allowed"); 
  330. return true; 
  331.  
  332. } else if ( in_array($display, Table_Frame_Decorator::$ROW_GROUPS) ) { 
  333.  
  334. // Disallow breaks at row-groups: only split at row boundaries 
  335. return false; 
  336.  
  337. } else { 
  338.  
  339. dompdf_debug("page-break", "? " . $frame->get_style()->display . ""); 
  340. return false; 
  341.  
  342.  
  343. /** 
  344. * Check if $frame will fit on the page. If the frame does not fit,  
  345. * the frame tree is modified so that a page break occurs in the 
  346. * correct location. 
  347. * @param Frame $frame the frame to check 
  348. * @return Frame the frame following the page break 
  349. */ 
  350. function check_page_break(Frame $frame) { 
  351. // Do not split if we have already or if the frame was already  
  352. // pushed to the next page (prevents infinite loops) 
  353. if ( $this->_page_full || $frame->_already_pushed ) { 
  354. return false; 
  355.  
  356. // If the frame is absolute of fixed it shouldn't break 
  357. $p = $frame; 
  358. do { 
  359. if ( $p->is_absolute() ) 
  360. return false; 
  361. } while ( $p = $p->get_parent() ); 
  362.  
  363. $margin_height = $frame->get_margin_height(); 
  364.  
  365. // FIXME If the row is taller than the page and  
  366. // if it the first of the page, we don't break 
  367. if ( $frame->get_style()->display === "table-row" && 
  368. !$frame->get_prev_sibling() &&  
  369. $margin_height > $this->get_margin_height() ) 
  370. return false; 
  371.  
  372. // Determine the frame's maximum y value 
  373. $max_y = $frame->get_position("y") + $margin_height; 
  374.  
  375. // If a split is to occur here, then the bottom margins & paddings of all 
  376. // parents of $frame must fit on the page as well: 
  377. $p = $frame->get_parent(); 
  378. while ( $p ) { 
  379. $style = $p->get_style(); 
  380. $max_y += $style->length_in_pt(array($style->margin_bottom,  
  381. $style->padding_bottom,  
  382. $style->border_bottom_width)); 
  383. $p = $p->get_parent(); 
  384.  
  385.  
  386. // Check if $frame flows off the page 
  387. if ( $max_y <= $this->_bottom_page_margin ) 
  388. // no: do nothing 
  389. return false; 
  390.  
  391. dompdf_debug("page-break", "check_page_break"); 
  392. dompdf_debug("page-break", "in_table: " . $this->_in_table); 
  393.  
  394. // yes: determine page break location 
  395. $iter = $frame; 
  396. $flg = false; 
  397.  
  398. $in_table = $this->_in_table; 
  399.  
  400. dompdf_debug("page-break", "Starting search"); 
  401. while ( $iter ) { 
  402. // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). ""; 
  403. if ( $iter === $this ) { 
  404. dompdf_debug("page-break", "reached root."); 
  405. // We've reached the root in our search. Just split at $frame. 
  406. break; 
  407.  
  408. if ( $this->_page_break_allowed($iter) ) { 
  409. dompdf_debug("page-break", "break allowed, splitting."); 
  410. $iter->split(null, true); 
  411. $this->_page_full = true; 
  412. $this->_in_table = $in_table; 
  413. $frame->_already_pushed = true; 
  414. return true; 
  415.  
  416. if ( !$flg && $next = $iter->get_last_child() ) { 
  417. dompdf_debug("page-break", "following last child."); 
  418.  
  419. if ( $next->is_table() ) 
  420. $this->_in_table++; 
  421.  
  422. $iter = $next; 
  423. continue; 
  424.  
  425. if ( $next = $iter->get_prev_sibling() ) { 
  426. dompdf_debug("page-break", "following prev sibling."); 
  427.  
  428. if ( $next->is_table() && !$iter->is_table() ) 
  429. $this->_in_table++; 
  430.  
  431. else if ( !$next->is_table() && $iter->is_table() ) 
  432. $this->_in_table--; 
  433.  
  434. $iter = $next; 
  435. $flg = false; 
  436. continue; 
  437.  
  438. if ( $next = $iter->get_parent() ) { 
  439. dompdf_debug("page-break", "following parent."); 
  440.  
  441. if ( $iter->is_table() ) 
  442. $this->_in_table--; 
  443.  
  444. $iter = $next; 
  445. $flg = true; 
  446. continue; 
  447.  
  448. break; 
  449.  
  450. $this->_in_table = $in_table; 
  451.  
  452. // No valid page break found. Just break at $frame. 
  453. dompdf_debug("page-break", "no valid break found, just splitting."); 
  454.  
  455. // If we are in a table, backtrack to the nearest top-level table row 
  456. if ( $this->_in_table ) { 
  457. $iter = $frame; 
  458. while ($iter && $iter->get_style()->display !== "table-row") 
  459. $iter = $iter->get_parent(); 
  460.  
  461. $iter->split(null, true); 
  462. } else { 
  463. $frame->split(null, true); 
  464.  
  465. $this->_page_full = true; 
  466. $frame->_already_pushed = true; 
  467. return true; 
  468.  
  469. //........................................................................ 
  470.  
  471. function split(Frame $frame = null, $force_pagebreak = false) { 
  472. // Do nothing 
  473.  
  474. /** 
  475. * Add a floating frame 
  476. * @param Frame $frame 
  477. * @return void 
  478. */ 
  479. function add_floating_frame(Frame $frame) { 
  480. array_unshift($this->_floating_frames, $frame); 
  481.  
  482. /** 
  483. * @return Frame[] 
  484. */ 
  485. function get_floating_frames() {  
  486. return $this->_floating_frames;  
  487.  
  488. public function remove_floating_frame($key) { 
  489. unset($this->_floating_frames[$key]); 
  490.  
  491. public function get_lowest_float_offset(Frame $child) { 
  492. $style = $child->get_style(); 
  493. $side = $style->clear; 
  494. $float = $style->float; 
  495.  
  496. $y = 0; 
  497.  
  498. foreach($this->_floating_frames as $key => $frame) { 
  499. if ( $side === "both" || $frame->get_style()->float === $side ) { 
  500. $y = max($y, $frame->get_position("y") + $frame->get_margin_height()); 
  501.  
  502. if ( $float !== "none" ) { 
  503. $this->remove_floating_frame($key); 
  504.  
  505. return $y; 
  506.