source: trunk/filemanager/tp/dompdf/include/page_frame_decorator.cls.php @ 2000

Revision 2000, 17.1 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Implementação do módulo gerenciador de arquivos

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