PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

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

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