PageRenderTime 60ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/smartpdf/code/include/PageFrameDecorator.php

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