1<?php
2// $Header: /cvsroot/html2ps/box.generic.php,v 1.73 2007/05/06 18:49:29 Konstantin Exp $
3
4require_once(HTML2PS_DIR.'globals.php');
5
6class GenericBox {
7  var $_cache;
8  var $_css;
9  var $_left;
10  var $_top;
11  var $_parent;
12  var $baseline;
13  var $default_baseline;
14  var $_tagname;
15  var $_id;
16
17  var $_cached_base_font_size;
18
19  function GenericBox() {
20    $this->_cache = array();
21    $this->_css   = array();
22    $this->_cached_base_font_size = null;
23
24    $this->_left   = 0;
25    $this->_top    = 0;
26
27    $this->_parent = null;
28
29    $this->baseline = 0;
30    $this->default_baseline = 0;
31
32    $this->set_tagname(null);
33
34    /**
35     * Assign an unique box identifier
36     */
37    $GLOBALS['g_box_uid']++;
38    $this->uid = $GLOBALS['g_box_uid'];
39
40    $this->_id = null;
41  }
42
43  function destroy() {
44    unset($this->_cache);
45    unset($this->_css);
46    unset($this->_left);
47    unset($this->_top);
48    unset($this->_parent);
49    unset($this->baseline);
50    unset($this->default_baseline);
51  }
52
53  /**
54   * see get_property for optimization description
55   */
56  function setCSSProperty($code, $value) {
57    static $cache = array();
58    if (!isset($cache[$code])) {
59      $cache[$code] =& CSS::get_handler($code);
60    };
61
62    $cache[$code]->replace_array($value, $this->_css);
63  }
64
65  /**
66   * Optimization: this function is called very often,
67   * so even a slight overhead for CSS::get_handler call
68   * accumulates in a significiant processing delay.
69   */
70  function &get_css_property($code) {
71    static $cache = array();
72    if (!isset($cache[$code])) {
73      $cache[$code] =& CSS::get_handler($code);
74    };
75
76    $value =& $cache[$code]->get($this->_css);
77    return $value;
78  }
79
80  function get_tagname() {
81    return $this->_tagname;
82  }
83
84  function set_tagname($tagname) {
85    $this->_tagname = $tagname;
86  }
87
88  function get_content() {
89    return '';
90  }
91
92  function show_postponed(&$driver) {
93    $this->show($driver);
94  }
95
96  function copy_style(&$box) {
97    // TODO: object references
98    $this->_css = $box->_css;
99  }
100
101  /**
102   * Optimization: _readCSSLength is usually called several times
103   * while initializing box object. $base_font_size cound be calculated
104   * only once and stored in a static variable.
105   */
106  function _readCSSLengths($state, $property_list) {
107    if (is_null($this->_cached_base_font_size)) {
108      $font =& $this->get_css_property(CSS_FONT);
109      $this->_cached_base_font_size = $font->size->getPoints();
110    };
111
112    foreach ($property_list as $property) {
113      $value =& $state->get_property($property);
114
115      if ($value === CSS_PROPERTY_INHERIT) {
116        $value =& $state->getInheritedProperty($property);
117      };
118
119      if (is_object($value)) {
120        $value =& $value->copy();
121        $value->doInherit($state);
122        $value->units2pt($this->_cached_base_font_size);
123      };
124
125      $this->setCSSProperty($property, $value);
126    }
127  }
128
129  function _readCSS($state, $property_list) {
130    foreach ($property_list as $property) {
131      $value = $state->get_property($property);
132
133      // Note that order is important; composite object-value could be inherited and
134      // object itself could contain subvalues with 'inherit' value
135
136      if ($value === CSS_PROPERTY_INHERIT) {
137        $value = $state->getInheritedProperty($property);
138      };
139
140      if (is_object($value)) {
141        $value = $value->copy();
142        $value->doInherit($state);
143      };
144
145      $this->setCSSProperty($property, $value);
146    }
147  }
148
149  function readCSS(&$state) {
150    /**
151     * Determine font size to be used in this box (required for em/ex units)
152     */
153    $value = $state->get_property(CSS_FONT);
154    if ($value === CSS_PROPERTY_INHERIT) {
155      $value = $state->getInheritedProperty(CSS_FONT);
156    };
157    $base_font_size = $state->getBaseFontSize();
158
159    if (is_object($value)) {
160      $value = $value->copy();
161      $value->doInherit($state);
162      $value->units2pt($base_font_size);
163    };
164
165    $this->setCSSProperty(CSS_FONT, $value);
166
167    /**
168     * Continue working with other properties
169     */
170
171    $this->_readCSS($state,
172                    array(CSS_COLOR,
173                          CSS_DISPLAY,
174                          CSS_VISIBILITY));
175
176    $this->_readCSSLengths($state,
177                           array(CSS_VERTICAL_ALIGN));
178
179    // '-html2ps-link-destination'
180    global $g_config;
181    if ($g_config["renderlinks"]) {
182      $this->_readCSS($state,
183                      array(CSS_HTML2PS_LINK_DESTINATION));
184    };
185
186    // Save ID attribute value
187    $id = $state->get_property(CSS_HTML2PS_LINK_DESTINATION);
188    if (!is_null($id)) {
189      $this->set_id($id);
190    };
191  }
192
193  function set_id($id) {
194    $this->_id = $id;
195
196    if (!isset($GLOBALS['__html_box_id_map'][$id])) {
197      $GLOBALS['__html_box_id_map'][$id] =& $this;
198    };
199  }
200
201  function get_id() {
202    return $this->_id;
203  }
204
205  function show(&$driver) {
206    // If debugging mode is on, draw the box outline
207    global $g_config;
208    if ($g_config['debugbox']) {
209      // Copy the border object of current box
210      $driver->setlinewidth(0.1);
211      $driver->setrgbcolor(0,0,0);
212      $driver->rect($this->get_left(), $this->get_top(), $this->get_width(), -$this->get_height());
213      $driver->stroke();
214    }
215
216    // Set current text color
217    // Note that text color is used not only for text drawing (for example, list item markers
218    // are drawn with text color)
219    $color = $this->get_css_property(CSS_COLOR);
220    $color->apply($driver);
221  }
222
223  /**
224   * Render box having position: fixed or contained in such box
225   * (Default behaviour)
226   */
227  function show_fixed(&$driver) {
228    return $this->show($driver);
229  }
230
231  function pre_reflow_images() {}
232
233  function set_top($value) {
234    $this->_top = $value;
235  }
236
237  function set_left($value) {
238    $this->_left = $value;
239  }
240
241  function offset($dx, $dy) {
242    $this->_left += $dx;
243    $this->_top  += $dy;
244  }
245
246  // Calculate the content upper-left corner position in curent flow
247  function guess_corner(&$parent) {
248    $this->put_left($parent->_current_x + $this->get_extra_left());
249    $this->put_top($parent->_current_y - $this->get_extra_top());
250  }
251
252  function put_left($value) {
253    $this->_left = $value;
254  }
255
256  function put_top($value)  {
257    $this->_top = $value + $this->getBaselineOffset();
258  }
259
260  /**
261   * Get Y coordinate of the top content area edge
262   */
263  function get_top() {
264    return
265      $this->_top -
266      $this->getBaselineOffset();
267  }
268
269  function get_right() {
270    return $this->get_left() + $this->get_width();
271  }
272
273  function get_left() {
274    return $this->_left;
275  }
276
277  function get_bottom() {
278    return $this->get_top() - $this->get_height();
279  }
280
281  function getBaselineOffset() {
282    return $this->baseline - $this->default_baseline;
283  }
284
285  function &make_anchor(&$media, $link_destination, $page_heights) {
286    $page_index = 0;
287    $pages_count = count($page_heights);
288    $bottom = mm2pt($media->height() - $media->margins['top']);
289    do {
290      $bottom -= $page_heights[$page_index];
291      $page_index ++;
292    } while ($this->get_top() < $bottom && $page_index < $pages_count);
293
294    /**
295     * Now let's calculate the coordinates on this particular page
296     *
297     * X coordinate calculation is pretty straightforward (and, actually, unused, as it would be
298     * a bad idea to scroll PDF horiaontally).
299     */
300    $x = $this->get_left();
301
302    /**
303     * Y coordinate should be calculated relatively to the bottom page edge
304     */
305    $y = ($this->get_top() - $bottom) + (mm2pt($media->real_height()) - $page_heights[$page_index-1]) + mm2pt($media->margins['bottom']);
306
307    $anchor =& new Anchor($link_destination,
308                          $page_index,
309                          $x,
310                          $y);
311    return $anchor;
312  }
313
314  function reflow_anchors(&$driver, &$anchors, $page_heights) {
315    if ($this->is_null()) {
316      return;
317    };
318
319    $link_destination = $this->get_css_property(CSS_HTML2PS_LINK_DESTINATION);
320    if (!is_null($link_destination)) {
321      $anchors[$link_destination] =& $this->make_anchor($driver->media, $link_destination, $page_heights);
322    };
323  }
324
325  function reflow(&$parent, &$context) {}
326
327  function reflow_inline() { }
328
329  function out_of_flow() {
330    return false;
331  }
332
333  function get_bottom_margin() { return $this->get_bottom(); }
334
335  function get_top_margin() {
336    return $this->get_top();
337  }
338
339  function get_full_height() { return $this->get_height(); }
340  function get_width() { return $this->width; }
341
342  function get_full_width() {
343    return $this->width;
344  }
345
346  function get_height() {
347    return $this->height;
348  }
349
350  function get_baseline() {
351    return $this->baseline;
352  }
353
354  function is_container() { return false; }
355
356  function isVisibleInFlow() { return true; }
357
358  function reflow_text() { return true; }
359
360  /**
361   * Note that linebox is started by any non-whitespace inline element; all whitespace elements before
362   * that moment should be ignored.
363   *
364   * @param boolean $linebox_started Flag indicating that a new line box have just started and it already contains
365   * some inline elements
366   * @param boolean $previous_whitespace Flag indicating that a previous inline element was an whitespace element.
367   */
368  function reflow_whitespace(&$linebox_started, &$previous_whitespace) {
369    return;
370  }
371
372  function is_null() {
373    return false;
374  }
375
376  function isCell() {
377    return false;
378  }
379
380  function isTableRow() {
381    return false;
382  }
383
384  function isTableSection() {
385    return false;
386  }
387
388  // CSS 2.1:
389  // 9.2.1 Block-level elements and block boxes
390  // Block-level elements are those elements of the source document that are formatted visually as blocks
391  // (e.g., paragraphs). Several values of the 'display' property make an element block-level:
392  // 'block', 'list-item', 'compact' and 'run-in' (part of the time; see compact and run-in boxes), and 'table'.
393  //
394  function isBlockLevel() {
395    return false;
396  }
397
398  function hasAbsolutePositionedParent() {
399    if (is_null($this->parent)) {
400      return false;
401    };
402
403    return
404      $this->parent->get_css_property(CSS_POSITION) == POSITION_ABSOLUTE ||
405      $this->parent->hasAbsolutePositionedParent();
406  }
407
408  function hasFixedPositionedParent() {
409    if (is_null($this->parent)) {
410      return false;
411    };
412
413    return
414      $this->parent->get_css_property(CSS_POSITION) == POSITION_FIXED ||
415      $this->parent->hasFixedPositionedParent();
416  }
417
418  /**
419   * Box can be expanded if it has no width constrains and
420   * all it parents has no width constraints
421   */
422  function mayBeExpanded() {
423    $wc = $this->get_css_property(CSS_WIDTH);
424    if (!$wc->isNull()) { return false; };
425
426    if ($this->get_css_property(CSS_FLOAT) <> FLOAT_NONE) {
427      return true;
428    };
429
430    if ($this->get_css_property(CSS_POSITION) <> POSITION_STATIC &&
431        $this->get_css_property(CSS_POSITION) <> POSITION_RELATIVE) {
432      return true;
433    };
434
435    if (is_null($this->parent)) {
436      return true;
437    };
438
439    return $this->parent->mayBeExpanded();
440  }
441
442  function isLineBreak() {
443    return false;
444  }
445
446  function get_min_width_natural($context) {
447    return $this->get_min_width($context);
448  }
449
450  function is_note_call() {
451    return isset($this->note_call);
452  }
453
454  /* DOM compatibility */
455  function &get_parent_node() {
456    return $this->parent;
457  }
458}
459?>