1<?php
2// $Header: /cvsroot/html2ps/box.frame.php,v 1.24 2007/02/18 09:55:10 Konstantin Exp $
3
4class FrameBox extends GenericContainerBox {
5  function &create(&$root, &$pipeline) {
6    $box =& new FrameBox($root, $pipeline);
7    $box->readCSS($pipeline->get_current_css_state());
8    return $box;
9  }
10
11  function reflow(&$parent, &$context) {
12    // If frame contains no boxes (for example, the src link is broken)
13    // we just return - no further processing will be done
14    if (count($this->content) == 0) { return; };
15
16    // First box contained in a frame should always fill all its height
17    $this->content[0]->put_full_height($this->get_height());
18
19    $hc = new HCConstraint(array($this->get_height(), false),
20                           array($this->get_height(), false),
21                           array($this->get_height(), false));
22    $this->content[0]->put_height_constraint($hc);
23
24    $context->push_collapsed_margin(0);
25    $context->push_container_uid($this->uid);
26
27    $this->reflow_content($context);
28
29    $context->pop_collapsed_margin();
30    $context->pop_container_uid();
31  }
32
33  /**
34   * Reflow absolutely positioned block box. Note that according to CSS 2.1
35   * the only types of boxes which could be absolutely positioned are
36   * 'block' and 'table'
37   *
38   * @param FlowContext $context A flow context object containing the additional layout data.
39   *
40   * @link http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo CSS 2.1: Relationships between 'display', 'position', and 'float'
41   */
42  function reflow_absolute(&$context) {
43    GenericFormattedBox::reflow($this->parent, $context);
44
45    $position_strategy =& new StrategyPositionAbsolute();
46    $position_strategy->apply($this);
47
48    /**
49     * As sometimes left/right values may not be set, we need to use the "fit" width here.
50     * If box have a width constraint, 'get_max_width' will return constrained value;
51     * othersise, an intrictic width will be returned.
52     *
53     * Note that get_max_width returns width _including_ external space line margins, borders and padding;
54     * as we're setting the "internal" - content width, we must subtract "extra" space width from the
55     * value received
56     *
57     * @see GenericContainerBox::get_max_width()
58     */
59
60    $this->put_width($this->get_max_width($context) - $this->_get_hor_extra());
61
62    /**
63     * Update the width, as it should be calculated based upon containing block width, not real parent.
64     * After this we should remove width constraints or we may encounter problem
65     * in future when we'll try to call get_..._width functions for this box
66     *
67     * @todo Update the family of get_..._width function so that they would apply constraint
68     * using the containing block width, not "real" parent width
69     */
70    $wc = $this->get_css_property(CSS_WIDTH);
71
72    $containing_block =& $this->_get_containing_block();
73    $this->put_width($wc->apply($this->get_width(),
74                                $containing_block['right'] - $containing_block['left']));
75    $this->setCSSProperty(CSS_WIDTH, new WCNone());
76
77    /**
78     * Layout element's children
79     */
80    $this->reflow_content($context);
81
82    /**
83     * As absolute-positioned box generated new flow contexy, extend the height to fit all floats
84     */
85    $this->fitFloats($context);
86
87    /**
88     * If element have been positioned using 'right' or 'bottom' property,
89     * we need to offset it, as we assumed it had zero width and height at
90     * the moment we placed it
91     */
92    $right = $this->get_css_property(CSS_RIGHT);
93    $left = $this->get_css_property(CSS_LEFT);
94    if ($left->isAuto() && !$right->isAuto()) {
95      $this->offset(-$this->get_width(), 0);
96    };
97
98    $bottom = $this->get_css_property(CSS_BOTTOM);
99    $top = $this->get_css_property(CSS_TOP);
100    if ($top->isAuto() && !$bottom->isAuto()) {
101      $this->offset(0, $this->get_height());
102    };
103  }
104
105  function FrameBox(&$root, &$pipeline) {
106    $css_state =& $pipeline->get_current_css_state();
107
108    // Inherit 'border' CSS value from parent (FRAMESET tag), if current FRAME
109    // has no FRAMEBORDER attribute, and FRAMESET has one
110    $parent = $root->parent();
111    if (!$root->has_attribute('frameborder') &&
112        $parent->has_attribute('frameborder')) {
113      $parent_border = $css_state->get_propertyOnLevel(CSS_BORDER, CSS_PROPERTY_LEVEL_PARENT);
114      $css_state->set_property(CSS_BORDER, $parent_border->copy());
115    }
116
117    $this->GenericContainerBox($root);
118
119    // If NO src attribute specified, just return.
120    if (!$root->has_attribute('src')) { return; };
121
122    // Determine the fullly qualified URL of the frame content
123    $src  = $root->get_attribute('src');
124    $url  = $pipeline->guess_url($src);
125    $data = $pipeline->fetch($url);
126
127    /**
128     * If framed page could not be fetched return immediately
129     */
130    if (is_null($data)) { return; };
131
132    /**
133     * Render only iframes containing HTML only
134     *
135     * Note that content-type header may contain additional information after the ';' sign
136     */
137    $content_type = $data->get_additional_data('Content-Type');
138    $content_type_array = explode(';', $content_type);
139    if ($content_type_array[0] != "text/html") { return; };
140
141    $html = $data->get_content();
142
143    // Remove control symbols if any
144    $html = preg_replace('/[\x00-\x07]/', "", $html);
145    $converter = Converter::create();
146    $html = $converter->to_utf8($html, $data->detect_encoding());
147    $html = html2xhtml($html);
148    $tree = TreeBuilder::build($html);
149
150    // Save current stylesheet, as each frame may load its own stylesheets
151    //
152    $pipeline->pushCSS();
153    $css =& $pipeline->get_current_css();
154    $css->scan_styles($tree, $pipeline);
155
156    $frame_root = traverse_dom_tree_pdf($tree);
157    $box_child  =& create_pdf_box($frame_root, $pipeline);
158    $this->add_child($box_child);
159
160    // Restore old stylesheet
161    //
162    $pipeline->pop_css();
163
164    $pipeline->pop_base_url();
165  }
166
167  /**
168   * Note that if both top and bottom are 'auto', box will use vertical coordinate
169   * calculated using guess_corder in 'reflow' method which could be used if this
170   * box had 'position: static'
171   */
172  function _positionAbsoluteVertically($containing_block) {
173    $bottom = $this->get_css_property(CSS_BOTTOM);
174    $top    = $this->get_css_property(CSS_TOP);
175
176    if (!$top->isAuto()) {
177      if ($top->isPercentage()) {
178        $top_value = ($containing_block['top'] - $containing_block['bottom']) / 100 * $top->getPercentage();
179      } else {
180        $top_value = $top->getPoints();
181      };
182      $this->put_top($containing_block['top'] - $top_value - $this->get_extra_top());
183    } elseif (!$bottom->isAuto()) {
184      if ($bottom->isPercentage()) {
185        $bottom_value = ($containing_block['top'] - $containing_block['bottom']) / 100 * $bottom->getPercentage();
186      } else {
187        $bottom_value = $bottom->getPoints();
188      };
189      $this->put_top($containing_block['bottom'] + $bottom_value + $this->get_extra_bottom());
190    };
191  }
192
193  /**
194   * Note that  if both  'left' and 'right'  are 'auto', box  will use
195   * horizontal coordinate  calculated using guess_corder  in 'reflow'
196   * method which could be used if this box had 'position: static'
197   */
198  function _positionAbsoluteHorizontally($containing_block) {
199    $left  = $this->get_css_property(CSS_LEFT);
200    $right = $this->get_css_property(CSS_RIGHT);
201
202    if (!$left->isAuto()) {
203      if ($left->isPercentage()) {
204        $left_value = ($containing_block['right'] - $containing_block['left']) / 100 * $left->getPercentage();
205      } else {
206        $left_value = $left->getPoints();
207      };
208      $this->put_left($containing_block['left'] + $left_value + $this->get_extra_left());
209    } elseif (!$right->isAuto()) {
210      if ($right->isPercentage()) {
211        $right_value = ($containing_block['right'] - $containing_block['left']) / 100 * $right->getPercentage();
212      } else {
213        $right_value = $right->getPoints();
214      };
215      $this->put_left($containing_block['right'] - $right_value - $this->get_extra_right());
216    };
217  }
218}
219
220class FramesetBox extends GenericContainerBox {
221  var $rows;
222  var $cols;
223
224  function &create(&$root, &$pipeline) {
225    $box =& new FramesetBox($root, $pipeline);
226    $box->readCSS($pipeline->get_current_css_state());
227    return $box;
228  }
229
230  function FramesetBox(&$root, $pipeline) {
231    $this->GenericContainerBox($root);
232    $this->create_content($root, $pipeline);
233
234    // Now determine the frame layout inside the frameset
235    $this->rows = $root->has_attribute('rows') ? $root->get_attribute('rows') : "100%";
236    $this->cols = $root->has_attribute('cols') ? $root->get_attribute('cols') : "100%";
237  }
238
239  function reflow(&$parent, &$context) {
240    $viewport =& $context->get_viewport();
241
242    // Frameset always fill all available space in viewport
243    $this->put_left($viewport->get_left() + $this->get_extra_left());
244    $this->put_top($viewport->get_top() - $this->get_extra_top());
245
246    $this->put_full_width($viewport->get_width());
247    $this->setCSSProperty(CSS_WIDTH, new WCConstant($viewport->get_width()));
248
249    $this->put_full_height($viewport->get_height());
250    $this->put_height_constraint(new WCConstant($viewport->get_height()));
251
252    // Parse layout-control values
253    $rows = guess_lengths($this->rows, $this->get_height());
254    $cols = guess_lengths($this->cols, $this->get_width());
255
256    // Now reflow all frames in frameset
257    $cur_col = 0;
258    $cur_row = 0;
259    for ($i=0; $i < count($this->content); $i++) {
260      // Had we run out of cols/rows?
261      if ($cur_row >= count($rows)) {
262        // In valid HTML we never should get here, but someone can provide less frame cells
263        // than frames. Extra frames will not be rendered at all
264        return;
265      }
266
267      $frame =& $this->content[$i];
268
269      /**
270       * Depending on the source HTML, FramesetBox may contain some non-frame boxes;
271       * we'll just ignore them
272       */
273      if (!is_a($frame, "FramesetBox") &&
274          !is_a($frame, "FrameBox")) {
275        continue;
276      };
277
278      // Guess frame size and position
279      $frame->put_left($this->get_left() + array_sum(array_slice($cols, 0, $cur_col)) + $frame->get_extra_left());
280      $frame->put_top($this->get_top()   - array_sum(array_slice($rows, 0, $cur_row)) - $frame->get_extra_top());
281
282      $frame->put_full_width($cols[$cur_col]);
283      $frame->setCSSProperty(CSS_WIDTH, new WCConstant($frame->get_width()));
284
285      $frame->put_full_height($rows[$cur_row]);
286      $frame->put_height_constraint(new WCConstant($frame->get_height()));
287
288      // Reflow frame contents
289      $context->push_viewport(FlowViewport::create($frame));
290      $frame->reflow($this, $context);
291      $context->pop_viewport();
292
293      // Move to the next frame position
294      // Next columns
295      $cur_col ++;
296      if ($cur_col >= count($cols)) {
297        // Next row
298        $cur_col = 0;
299        $cur_row ++;
300      }
301    }
302  }
303}
304?>