source: sandbox/filemanager/tp/dompdf/include/page_frame_decorator.cls.php @ 1575

Revision 1575, 15.7 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Implentação, melhorias do modulo 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,v 1.13 2006/07/07 21:31:04 benjcarson Exp $ */
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   * Check if a forced page break is required before $frame.  This uses the
159   * frame's page_break_before property as well as the preceeding frame's
160   * page_break_after property.
161   *
162   * @link http://www.w3.org/TR/CSS21/page.html#forced
163   *
164   * @param Frame $frame the frame to check
165   * @return bool true if a page break occured
166   */
167  function check_forced_page_break(Frame $frame) {
168
169    // Skip check if page is already split
170    if ( $this->_page_full )
171      return;
172   
173    $block_types = array("block", "list-item", "table");
174    $page_breaks = array("always", "left", "right");
175   
176    $style = $frame->get_style();
177
178    if ( !in_array($style->display, $block_types) )
179      return false;
180
181    // Find the previous block-level sibling
182    $prev = $frame->get_prev_sibling();
183    while ( $prev && !in_array($prev->get_style()->display, $block_types) )
184      $prev = $prev->get_prev_sibling();
185
186    if ( in_array($style->page_break_before, $page_breaks) ) {
187
188      // Prevent cascading splits
189      $frame->split();
190      // We have to grab the style again here because split() resets
191      // $frame->style to the frame's orignal style.
192      $frame->get_style()->page_break_before = "auto";
193      $this->_page_full = true;
194      return true;
195    }
196
197    if ( ($prev && in_array($prev->get_style()->page_break_after, $page_breaks)) ) {
198      // Prevent cascading splits
199      $frame->split();
200      $prev->get_style()->page_break_after = "auto";
201      $this->_page_full = true;
202      return true;
203    }
204   
205    return false;
206  }
207
208  /**
209   * Determine if a page break is allowed before $frame
210   *
211   * @param Frame $frame the frame to check
212   * @return bool true if a break is allowed, false otherwise
213   */
214  protected function _page_break_allowed(Frame $frame) {
215    /**
216     *
217     * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
218     * /*
219     * In the normal flow, page breaks can occur at the following places:
220     *
221     *    1. In the vertical margin between block boxes. When a page
222     *    break occurs here, the used values of the relevant
223     *    'margin-top' and 'margin-bottom' properties are set to '0'.
224     *    2. Between line boxes inside a block box.
225     *
226     * These breaks are subject to the following rules:
227     *
228     *   * Rule A: Breaking at (1) is allowed only if the
229     *     'page-break-after' and 'page-break-before' properties of
230     *     all the elements generating boxes that meet at this margin
231     *     allow it, which is when at least one of them has the value
232     *     'always', 'left', or 'right', or when all of them are
233     *     'auto'.
234     *
235     *   * Rule B: However, if all of them are 'auto' and the
236     *     nearest common ancestor of all the elements has a
237     *     'page-break-inside' value of 'avoid', then breaking here is
238     *     not allowed.
239     *
240     *   * Rule C: Breaking at (2) is allowed only if the number of
241     *     line boxes between the break and the start of the enclosing
242     *     block box is the value of 'orphans' or more, and the number
243     *     of line boxes between the break and the end of the box is
244     *     the value of 'widows' or more.
245     *
246     *   * Rule D: In addition, breaking at (2) is allowed only if
247     *     the 'page-break-inside' property is 'auto'.
248     *
249     * If the above doesn't provide enough break points to keep
250     * content from overflowing the page boxes, then rules B and D are
251     * dropped in order to find additional breakpoints.
252     *
253     * If that still does not lead to sufficient break points, rules A
254     * and C are dropped as well, to find still more break points.
255     *
256     * [endquote]
257     *
258     * We will also allow breaks between table rows.  However, when
259     * splitting a table, the table headers should carry over to the
260     * next page (but they don't yet).
261     */
262
263    $block_types = array("block", "list-item", "table");
264//      echo "\nbreak_allowed: " . $frame->get_node()->nodeName ."\n";
265    $display = $frame->get_style()->display;
266   
267    // Block Frames (1):
268    if ( in_array($display, $block_types) ) {
269
270      // Avoid breaks within table-cells
271      if ( $this->_in_table ) {
272//          echo "In table: " . $this->_in_table ."\n";
273        return false;
274      }
275     
276      // Rules A & B
277     
278      if ( $frame->get_style()->page_break_before == "avoid" ) {
279//          echo "before: avoid\n";
280        return false;
281      }
282     
283      // Find the preceeding block-level sibling
284      $prev = $frame->get_prev_sibling();
285      while ( $prev && !in_array($prev->get_style()->display, $block_types) )
286        $prev = $prev->get_prev_sibling();
287
288      // Does the previous element allow a page break after?
289      if ( $prev && $prev->get_style()->page_break_after == "avoid" ) {
290//        echo "after: avoid\n";
291        return false;
292      }
293
294      // If both $prev & $frame have the same parent, check the parent's
295      // page_break_inside property.
296      $parent = $frame->get_parent();
297      if ( $prev && $parent && $parent->get_style()->page_break_inside == "avoid" ) {
298//          echo "parent inside: avoid\n";
299        return false;
300      }
301
302      // To prevent cascading page breaks when a top-level element has
303      // page-break-inside: avoid, ensure that at least one frame is
304      // on the page before splitting.
305      if ( $parent->get_node()->nodeName == "body" && !$prev ) {
306        // We are the body's first child
307//          echo "Body's first child.\n";
308        return false;
309      }
310     
311      // If the frame is the first block-level frame, use the value from
312      // $frame's parent instead.
313      if ( !$prev && $parent )
314        return $this->_page_break_allowed( $parent );
315
316//       echo "block: break allowed\n";     
317      return true;
318   
319    }
320
321    // Inline frames (2):   
322    else if ( in_array($display, Style::$INLINE_TYPES) ) {
323     
324      // Avoid breaks within table-cells
325      if ( $this->_in_table ) {
326//          echo "In table: " . $this->_in_table ."\n";
327        return false;
328      }
329     
330      // Rule C
331      $block_parent = $frame->find_block_parent();
332      if ( count($block_parent->get_lines() ) < $frame->get_style()->orphans ) {
333//          echo "orphans\n";
334        return false;
335      }
336     
337      // FIXME: Checking widows is tricky without having laid out the
338      // remaining line boxes.  Just ignore it for now...
339
340      // Rule D
341      if ( $block_parent->get_style()->page_break_inside == "avoid" ) {
342//          echo "parent->inside: avoid\n";
343        return false;
344      }
345
346      // To prevent cascading page breaks when a top-level element has
347      // page-break-inside: avoid, ensure that at least one frame with
348      // some content is on the page before splitting.
349      $prev = $frame->get_prev_sibling();
350      while ( $prev && ($prev->get_node()->nodeName == "#text" && trim($prev->get_node()->nodeValue) == "") )
351        $prev = $prev->get_prev_sibling();
352     
353      if ( $block_parent->get_node()->nodeName == "body" && !$prev ) {
354        // We are the body's first child
355//          echo "Body's first child.\n";
356        return false;
357      }
358
359      // Skip breaks on empty text nodes
360      if ( $frame->get_node()->nodeName == "#text" &&
361           $frame->get_node()->nodeValue == "" )
362        return false;
363     
364//       echo "inline: break allowed\n";
365      return true;
366 
367    // Table-rows
368    } else if ( $display == "table-row" ) {
369
370      // Simply check if the parent table's page_break_inside property is
371      // not 'avoid'
372      $p = Table_Frame_Decorator::find_parent_table($frame);
373
374      if ( $p->get_style()->page_break_inside == "avoid" ) {
375//          echo "table: page-break-inside: avoid\n";
376        return false;
377      }
378
379      // Check the table's parent element
380      $table_parent = $p->get_parent();
381     
382      if ( $table_parent->get_style()->page_break_inside == "avoid" ) {
383//          echo "table->parent: page-break-inside: avoid\n";
384        return false;
385      }
386
387      // Avoid breaking after the first row of a table
388      if ( $p->get_first_child() === $frame) {
389//         echo "table: first-row\n";
390        return false;
391      }
392
393//       echo "table-row/row-groups: break allowed\n";
394      return true;
395
396    } else if ( in_array($display, Table_Frame_Decorator::$ROW_GROUPS) ) {
397
398      // Disallow breaks at row-groups: only split at row boundaries
399      return false;
400
401    } else {
402
403//       echo "? " . $frame->get_style()->display . "\n";
404      return false;
405    }
406 
407  }
408 
409  /**
410   * Check if $frame will fit on the page.  If the frame does not fit,
411   * the frame tree is modified so that a page break occurs in the
412   * correct location.
413   *
414   * @param Frame $frame the frame to check
415   * @return Frame the frame following the page break
416   */
417  function check_page_break(Frame $frame) {
418    // Do not split if we have already
419    if ( $this->_page_full )
420      return false;
421   
422    // Determine the frame's maximum y value
423    $max_y = $frame->get_position("y") + $frame->get_margin_height();
424
425    // If a split is to occur here, then the bottom margins & paddings of all
426    // parents of $frame must fit on the page as well:
427    $p = $frame->get_parent();
428    while ( $p ) {
429      $style = $p->get_style();
430      $max_y += $style->length_in_pt(array($style->margin_bottom,
431                                           $style->padding_bottom,
432                                           $style->border_bottom_width));
433      $p = $p->get_parent();
434    }
435
436   
437    // Check if $frame flows off the page   
438    if ( $max_y <= $this->_bottom_page_margin )
439      // no: do nothing (?)
440      return false;
441
442//    echo "check_page_break\n";
443   
444    // yes: determine page break location
445    $iter = $frame;
446    $flg = false;
447
448    $in_table = $this->_in_table;
449   
450//     echo "Starting search\n";
451    while ( $iter ) {
452//       echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".(string)$iter->get_node(). "\n";
453      if ( $iter === $this ) {
454//         echo "reached root.\n";
455        // We've reached the root in our search.  Just split at $frame.
456        break;
457      }
458     
459      if ( $this->_page_break_allowed($iter) ) {
460//        echo "break allowed, splitting.\n";
461        $iter->split();
462        $this->_page_full = true;
463        $this->_in_table = $in_table;
464        return true;
465      }
466
467      if ( !$flg && $next = $iter->get_last_child() ) {
468//         echo "following last child.\n";
469       
470        if ( in_array($next->get_style()->display, Style::$TABLE_TYPES) )
471          $this->_in_table++;
472
473        $iter = $next;
474        continue;
475      }
476
477      if ( $next = $iter->get_prev_sibling() ) {
478//         echo "following prev sibling.\n";
479
480        if ( in_array($next->get_style()->display, Style::$TABLE_TYPES) &&
481             !in_array($iter->get_style()->display, Style::$TABLE_TYPES) )
482          $this->_in_table++;
483
484        else if ( !in_array($next->get_style()->display, Style::$TABLE_TYPES) &&
485                  in_array($iter->get_style()->display, Style::$TABLE_TYPES) )
486          $this->_in_table--;
487       
488        $iter = $next;
489        $flg = false;
490        continue;
491      }
492
493      if ( $next = $iter->get_parent() ) {
494//         echo "following parent.\n";
495
496        if ( in_array($iter->get_style()->display, Style::$TABLE_TYPES) )
497          $this->_in_table--;
498       
499        $iter = $next;
500        $flg = true;
501        continue;
502      }
503
504      break;
505       
506    }
507
508    $this->_in_table = $in_table;
509
510    // No valid page break found.  Just break at $frame.
511//     echo "no valid break found, just splitting.\n";
512
513    // If we are in a table, backtrack to the nearest table row
514    if ( $this->_in_table ) {
515      $tr = $frame;
516      while ($tr && $tr->get_style()->display != "table-row" )
517        $tr = $tr->get_parent();
518     
519      $tr->split();
520      $this->_page_full = true;
521      return true;
522    }       
523
524    $frame->split();
525    $this->_page_full = true;
526    return true;
527   
528  }
529 
530  //........................................................................
531
532  function split($frame = null) {
533    // Do nothing
534  }
535
536}
537?>
Note: See TracBrowser for help on using the repository browser.