PageRenderTime 102ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/system/helpers/dompdf/include/page_frame_decorator.cls.php

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