454 lines
12 KiB
PHP
454 lines
12 KiB
PHP
|
|
<?php
|
||
|
|
// $Header: /cvsroot/html2ps/box.generic.php,v 1.72 2007/01/24 18:55:44 Konstantin Exp $
|
||
|
|
|
||
|
|
require_once(HTML2PS_DIR.'globals.php');
|
||
|
|
|
||
|
|
class GenericBox {
|
||
|
|
var $_cache;
|
||
|
|
var $_css;
|
||
|
|
var $_left;
|
||
|
|
var $_top;
|
||
|
|
var $_parent;
|
||
|
|
var $baseline;
|
||
|
|
var $default_baseline;
|
||
|
|
|
||
|
|
var $_cached_base_font_size;
|
||
|
|
|
||
|
|
function GenericBox() {
|
||
|
|
$this->_cache = array();
|
||
|
|
$this->_css = array();
|
||
|
|
$this->_cached_base_font_size = null;
|
||
|
|
|
||
|
|
$this->_left = 0;
|
||
|
|
$this->_top = 0;
|
||
|
|
|
||
|
|
$this->_parent = null;
|
||
|
|
|
||
|
|
$this->baseline = 0;
|
||
|
|
$this->default_baseline = 0;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Assign an unique box identifier
|
||
|
|
*/
|
||
|
|
$GLOBALS['g_box_uid']++;
|
||
|
|
$this->uid = $GLOBALS['g_box_uid'];
|
||
|
|
}
|
||
|
|
|
||
|
|
function destroy() {
|
||
|
|
unset($this->_cache);
|
||
|
|
unset($this->_css);
|
||
|
|
unset($this->_left);
|
||
|
|
unset($this->_top);
|
||
|
|
unset($this->_parent);
|
||
|
|
unset($this->baseline);
|
||
|
|
unset($this->default_baseline);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* see getProperty for optimization description
|
||
|
|
*/
|
||
|
|
function setCSSProperty($code, $value) {
|
||
|
|
static $cache = array();
|
||
|
|
if (!isset($cache[$code])) {
|
||
|
|
$cache[$code] =& CSS::get_handler($code);
|
||
|
|
};
|
||
|
|
|
||
|
|
$cache[$code]->replace_array($value, $this->_css);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Optimization: this function is called very often,
|
||
|
|
* so even a slight overhead for CSS::get_handler call
|
||
|
|
* accumulates in a significiant processing delay.
|
||
|
|
*/
|
||
|
|
function &getCSSProperty($code) {
|
||
|
|
static $cache = array();
|
||
|
|
if (!isset($cache[$code])) {
|
||
|
|
$cache[$code] =& CSS::get_handler($code);
|
||
|
|
};
|
||
|
|
|
||
|
|
$value =& $cache[$code]->get($this->_css);
|
||
|
|
return $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function show_postponed(&$driver) {
|
||
|
|
$this->show($driver);
|
||
|
|
}
|
||
|
|
|
||
|
|
function copy_style(&$box) {
|
||
|
|
// TODO: object references
|
||
|
|
$this->_css = $box->_css;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Optimization: _readCSSLength is usually called several times
|
||
|
|
* while initializing box object. $base_font_size cound be calculated
|
||
|
|
* only once and stored in a static variable.
|
||
|
|
*/
|
||
|
|
function _readCSSLengths($state, $property_list) {
|
||
|
|
if (is_null($this->_cached_base_font_size)) {
|
||
|
|
$font =& $this->getCSSProperty(CSS_FONT);
|
||
|
|
$this->_cached_base_font_size = $font->size->getPoints();
|
||
|
|
};
|
||
|
|
|
||
|
|
foreach ($property_list as $property) {
|
||
|
|
$value =& $state->getProperty($property);
|
||
|
|
|
||
|
|
if ($value === CSS_PROPERTY_INHERIT) {
|
||
|
|
$value =& $state->getInheritedProperty($property);
|
||
|
|
};
|
||
|
|
|
||
|
|
if (is_object($value)) {
|
||
|
|
$value =& $value->copy();
|
||
|
|
$value->doInherit($state);
|
||
|
|
$value->units2pt($this->_cached_base_font_size);
|
||
|
|
};
|
||
|
|
|
||
|
|
$this->setCSSProperty($property, $value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function _readCSS($state, $property_list) {
|
||
|
|
foreach ($property_list as $property) {
|
||
|
|
$value = $state->getProperty($property);
|
||
|
|
|
||
|
|
// Note that order is important; composite object-value could be inherited and
|
||
|
|
// object itself could contain subvalues with 'inherit' value
|
||
|
|
|
||
|
|
if ($value === CSS_PROPERTY_INHERIT) {
|
||
|
|
$value = $state->getInheritedProperty($property);
|
||
|
|
};
|
||
|
|
|
||
|
|
if (is_object($value)) {
|
||
|
|
$value = $value->copy();
|
||
|
|
$value->doInherit($state);
|
||
|
|
};
|
||
|
|
|
||
|
|
$this->setCSSProperty($property, $value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function readCSS(&$state) {
|
||
|
|
/**
|
||
|
|
* Determine font size to be used in this box (required for em/ex units)
|
||
|
|
*/
|
||
|
|
$value = $state->getProperty(CSS_FONT);
|
||
|
|
if ($value === CSS_PROPERTY_INHERIT) {
|
||
|
|
$value = $state->getInheritedProperty(CSS_FONT);
|
||
|
|
};
|
||
|
|
$base_font_size = $state->getBaseFontSize();
|
||
|
|
|
||
|
|
if (is_object($value)) {
|
||
|
|
$value = $value->copy();
|
||
|
|
$value->doInherit($state);
|
||
|
|
$value->units2pt($base_font_size);
|
||
|
|
};
|
||
|
|
|
||
|
|
$this->setCSSProperty(CSS_FONT, $value);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Continue working with other properties
|
||
|
|
*/
|
||
|
|
|
||
|
|
$this->_readCSS($state,
|
||
|
|
array(CSS_COLOR,
|
||
|
|
CSS_DISPLAY,
|
||
|
|
CSS_VISIBILITY));
|
||
|
|
|
||
|
|
$this->_readCSSLengths($state,
|
||
|
|
array(CSS_VERTICAL_ALIGN));
|
||
|
|
|
||
|
|
// '-html2ps-link-destination'
|
||
|
|
global $g_config;
|
||
|
|
if ($g_config["renderlinks"]) {
|
||
|
|
$this->_readCSS($state,
|
||
|
|
array(CSS_HTML2PS_LINK_DESTINATION));
|
||
|
|
};
|
||
|
|
|
||
|
|
// Save ID attribute value
|
||
|
|
$id = $state->getProperty(CSS_HTML2PS_LINK_DESTINATION);
|
||
|
|
if (!empty($id)) {
|
||
|
|
if (!isset($GLOBALS['__html_box_id_map'][$id])) {
|
||
|
|
$GLOBALS['__html_box_id_map'][$id] =& $this;
|
||
|
|
};
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function show(&$driver) {
|
||
|
|
// If debugging mode is on, draw the box outline
|
||
|
|
global $g_config;
|
||
|
|
if ($g_config['debugbox']) {
|
||
|
|
// Copy the border object of current box
|
||
|
|
$driver->setlinewidth(0.1);
|
||
|
|
$driver->setrgbcolor(0,0,0);
|
||
|
|
$driver->rect($this->get_left(), $this->get_top(), $this->get_width(), -$this->get_height());
|
||
|
|
$driver->stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set current text color
|
||
|
|
// Note that text color is used not only for text drawing (for example, list item markers
|
||
|
|
// are drawn with text color)
|
||
|
|
$color = $this->getCSSProperty(CSS_COLOR);
|
||
|
|
$color->apply($driver);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Render box having position: fixed or contained in such box
|
||
|
|
* (Default behaviour)
|
||
|
|
*/
|
||
|
|
function show_fixed(&$driver) {
|
||
|
|
return $this->show($driver);
|
||
|
|
}
|
||
|
|
|
||
|
|
function pre_reflow_images() {}
|
||
|
|
|
||
|
|
function offset($dx, $dy) {
|
||
|
|
$this->_left += $dx;
|
||
|
|
$this->_top += $dy;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate the content upper-left corner position in curent flow
|
||
|
|
function guess_corner(&$parent) {
|
||
|
|
$this->put_left($parent->_current_x + $this->get_extra_left());
|
||
|
|
$this->put_top($parent->_current_y - $this->get_extra_top());
|
||
|
|
}
|
||
|
|
|
||
|
|
function put_left($value) {
|
||
|
|
$this->_left = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function put_top($value) {
|
||
|
|
$this->_top = $value + $this->getBaselineOffset();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get Y coordinate of the top content area edge
|
||
|
|
*/
|
||
|
|
function get_top() {
|
||
|
|
return
|
||
|
|
$this->_top -
|
||
|
|
$this->getBaselineOffset();
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_right() {
|
||
|
|
return $this->get_left() + $this->get_width();
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_left() {
|
||
|
|
return $this->_left;
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_bottom() {
|
||
|
|
return $this->get_top() - $this->get_height();
|
||
|
|
}
|
||
|
|
|
||
|
|
function getBaselineOffset() {
|
||
|
|
return $this->baseline - $this->default_baseline;
|
||
|
|
}
|
||
|
|
|
||
|
|
function reflow_anchors(&$driver, &$anchors) {
|
||
|
|
if ($this->is_null()) {
|
||
|
|
return;
|
||
|
|
};
|
||
|
|
|
||
|
|
$link_destination = $this->getCSSProperty(CSS_HTML2PS_LINK_DESTINATION);
|
||
|
|
|
||
|
|
if ($link_destination !== "") {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Y=0 designates the bottom edge of the first page (without margins)
|
||
|
|
* Y axis is oriented to the bottom.
|
||
|
|
*
|
||
|
|
* Here we calculate the offset from the bottom edge of first page PRINTABLE AREA
|
||
|
|
* to the bottom edge of the current box
|
||
|
|
*/
|
||
|
|
$y2 = $this->get_bottom() - mm2pt($driver->media->margins['bottom']);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Now let's calculate the number of the page corresponding to this offset.
|
||
|
|
* Note that $y2>0 for the first page and $y2<0 on all subsequent pages
|
||
|
|
*/
|
||
|
|
$page_fraction = $y2 / mm2pt($driver->media->real_height());
|
||
|
|
|
||
|
|
/**
|
||
|
|
* After the last operation we've got the "page fraction" between
|
||
|
|
* bottom of the first page and box bottom edge;
|
||
|
|
*
|
||
|
|
* it will be equal to:
|
||
|
|
* 1 for the top of the first page,
|
||
|
|
* 0 for the bottom of the first page
|
||
|
|
* -Epsilon for the top of the first page
|
||
|
|
* -1 for the bottom of the second page
|
||
|
|
* -n+1 for the bottom of the N-th page.
|
||
|
|
*/
|
||
|
|
$page_fraction2 = -$page_fraction+1;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Here:
|
||
|
|
* 0 for the top of the first page,
|
||
|
|
* 1 for the bottom of the first page
|
||
|
|
* 1+Epsilon for the top of the first page
|
||
|
|
* 2 for the bottom of the second page
|
||
|
|
* n for the bottom of the N-th page.
|
||
|
|
*
|
||
|
|
* Keeping in mind said above, we may calculate the real page number,
|
||
|
|
* rounding it UP after calculation. The reason of rounding UP is simple:
|
||
|
|
* pages are numbered starting at 1.
|
||
|
|
*/
|
||
|
|
$page = ceil($page_fraction2);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Now let's calculate the coordinates on this particular page
|
||
|
|
*
|
||
|
|
* X coordinate calculation is pretty straight forward (and, actually, unused, as it would be
|
||
|
|
* a bad idea to scroll PDF horiaontally).
|
||
|
|
*/
|
||
|
|
$x = $this->get_left();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Y coordinate should be calculated relatively to the bottom page edge
|
||
|
|
*/
|
||
|
|
$y = mm2pt($driver->media->real_height()) * ($page - $page_fraction2) + mm2pt($driver->media->margins['bottom']);
|
||
|
|
|
||
|
|
$anchors[$link_destination] = new Anchor($link_destination,
|
||
|
|
$page,
|
||
|
|
$x,
|
||
|
|
$y);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function reflow(&$parent, &$context) {}
|
||
|
|
|
||
|
|
function reflow_inline() { }
|
||
|
|
|
||
|
|
function out_of_flow() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_bottom_margin() { return $this->get_bottom(); }
|
||
|
|
|
||
|
|
function get_top_margin() {
|
||
|
|
return $this->get_top();
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_full_height() { return $this->get_height(); }
|
||
|
|
function get_width() { return $this->width; }
|
||
|
|
|
||
|
|
function get_full_width() {
|
||
|
|
return $this->width;
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_height() {
|
||
|
|
return $this->height;
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_baseline() {
|
||
|
|
return $this->baseline;
|
||
|
|
}
|
||
|
|
|
||
|
|
function is_container() { return false; }
|
||
|
|
|
||
|
|
function isVisibleInFlow() { return true; }
|
||
|
|
|
||
|
|
function reflow_text() { return true; }
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Note that linebox is started by any non-whitespace inline element; all whitespace elements before
|
||
|
|
* that moment should be ignored.
|
||
|
|
*
|
||
|
|
* @param boolean $linebox_started Flag indicating that a new line box have just started and it already contains
|
||
|
|
* some inline elements
|
||
|
|
* @param boolean $previous_whitespace Flag indicating that a previous inline element was an whitespace element.
|
||
|
|
*/
|
||
|
|
function reflow_whitespace(&$linebox_started, &$previous_whitespace) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
function is_null() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isCell() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isTableRow() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isTableSection() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// CSS 2.1:
|
||
|
|
// 9.2.1 Block-level elements and block boxes
|
||
|
|
// Block-level elements are those elements of the source document that are formatted visually as blocks
|
||
|
|
// (e.g., paragraphs). Several values of the 'display' property make an element block-level:
|
||
|
|
// 'block', 'list-item', 'compact' and 'run-in' (part of the time; see compact and run-in boxes), and 'table'.
|
||
|
|
//
|
||
|
|
function isBlockLevel() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function hasAbsolutePositionedParent() {
|
||
|
|
if (is_null($this->parent)) {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
|
||
|
|
return
|
||
|
|
$this->parent->getCSSProperty(CSS_POSITION) == POSITION_ABSOLUTE ||
|
||
|
|
$this->parent->hasAbsolutePositionedParent();
|
||
|
|
}
|
||
|
|
|
||
|
|
function hasFixedPositionedParent() {
|
||
|
|
if (is_null($this->parent)) {
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
|
||
|
|
return
|
||
|
|
$this->parent->getCSSProperty(CSS_POSITION) == POSITION_FIXED ||
|
||
|
|
$this->parent->hasFixedPositionedParent();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Box can be expanded if it has no width constrains and
|
||
|
|
* all it parents has no width constraints
|
||
|
|
*/
|
||
|
|
function mayBeExpanded() {
|
||
|
|
$wc = $this->getCSSProperty(CSS_WIDTH);
|
||
|
|
if (!$wc->isNull()) { return false; };
|
||
|
|
|
||
|
|
if ($this->getCSSProperty(CSS_FLOAT) <> FLOAT_NONE) {
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
|
||
|
|
if ($this->getCSSProperty(CSS_POSITION) <> POSITION_STATIC &&
|
||
|
|
$this->getCSSProperty(CSS_POSITION) <> POSITION_RELATIVE) {
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
|
||
|
|
if (is_null($this->parent)) {
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
|
||
|
|
return $this->parent->mayBeExpanded();
|
||
|
|
}
|
||
|
|
|
||
|
|
function isLineBreak() {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function get_min_width_natural($context) {
|
||
|
|
return $this->get_min_width($context);
|
||
|
|
}
|
||
|
|
|
||
|
|
function is_note_call() {
|
||
|
|
return isset($this->note_call);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* DOM compatibility */
|
||
|
|
function &get_parent_node() {
|
||
|
|
return $this->parent;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
?>
|