| 1 |
lars |
1 |
<?php
|
|
|
2 |
// $Header: /cvsroot/html2ps/box.table.php,v 1.59 2007/04/01 12:11:24 Konstantin Exp $
|
|
|
3 |
|
|
|
4 |
class CellSpan {
|
|
|
5 |
var $row;
|
|
|
6 |
var $column;
|
|
|
7 |
var $size;
|
|
|
8 |
}
|
|
|
9 |
|
|
|
10 |
/**
|
|
|
11 |
* It is assumed that every row contains at least one cell
|
|
|
12 |
*/
|
|
|
13 |
class TableBox extends GenericContainerBox {
|
|
|
14 |
var $cwc;
|
|
|
15 |
var $_cached_min_widths;
|
|
|
16 |
|
|
|
17 |
function TableBox() {
|
|
|
18 |
$this->GenericContainerBox();
|
|
|
19 |
|
|
|
20 |
// List of column width constraints
|
|
|
21 |
$this->cwc = array();
|
|
|
22 |
|
|
|
23 |
$this->_cached_min_widths = null;
|
|
|
24 |
}
|
|
|
25 |
|
|
|
26 |
function readCSS(&$state) {
|
|
|
27 |
parent::readCSS($state);
|
|
|
28 |
|
|
|
29 |
$this->_readCSS($state,
|
|
|
30 |
array(CSS_BORDER_COLLAPSE,
|
|
|
31 |
CSS_TABLE_LAYOUT));
|
|
|
32 |
|
|
|
33 |
$this->_readCSSLengths($state,
|
|
|
34 |
array(CSS_HTML2PS_CELLPADDING,
|
|
|
35 |
CSS_HTML2PS_CELLSPACING));
|
|
|
36 |
}
|
|
|
37 |
|
|
|
38 |
function &cell($r, $c) {
|
|
|
39 |
return $this->content[$r]->content[$c];
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
function rows_count() {
|
|
|
43 |
return count($this->content);
|
|
|
44 |
}
|
|
|
45 |
|
|
|
46 |
// NOTE: assumes that rows are already normalized!
|
|
|
47 |
function cols_count() {
|
|
|
48 |
return count($this->content[0]->content);
|
|
|
49 |
}
|
|
|
50 |
|
|
|
51 |
// FIXME: just a stub
|
|
|
52 |
function append_line(&$e) {}
|
|
|
53 |
|
|
|
54 |
function &create(&$root, &$pipeline) {
|
|
|
55 |
$box =& new TableBox();
|
|
|
56 |
$box->readCSS($pipeline->get_current_css_state());
|
|
|
57 |
|
|
|
58 |
// This row should not inherit any table specific properties!
|
|
|
59 |
// 'overflow' for example
|
|
|
60 |
//
|
|
|
61 |
$css_state =& $pipeline->get_current_css_state();
|
|
|
62 |
$css_state->pushDefaultState();
|
|
|
63 |
|
|
|
64 |
$row =& new TableRowBox($root);
|
|
|
65 |
$row->readCSS($css_state);
|
|
|
66 |
|
|
|
67 |
$box->add_child($row);
|
|
|
68 |
|
|
|
69 |
$css_state->popState();
|
|
|
70 |
|
|
|
71 |
// Setup cellspacing / cellpadding values
|
|
|
72 |
if ($box->get_css_property(CSS_BORDER_COLLAPSE) == BORDER_COLLAPSE) {
|
|
|
73 |
$handler =& CSS::get_handler(CSS_PADDING);
|
|
|
74 |
$box->setCSSProperty(CSS_PADDING, $handler->default_value());
|
|
|
75 |
};
|
|
|
76 |
|
|
|
77 |
// Set text-align to 'left'; all browsers I've ever seen prevent inheriting of
|
|
|
78 |
// 'text-align' property by the tables.
|
|
|
79 |
// Say, in the following example the text inside the table cell will be aligned left,
|
|
|
80 |
// instead of inheriting 'center' value.
|
|
|
81 |
//
|
|
|
82 |
// <div style="text-align: center; background-color: green;">
|
|
|
83 |
// <table width="100" bgcolor="red">
|
|
|
84 |
// <tr><td>TEST
|
|
|
85 |
// </table>
|
|
|
86 |
// </div>
|
|
|
87 |
$handler =& CSS::get_handler(CSS_TEXT_ALIGN);
|
|
|
88 |
$handler->css('left', $pipeline);
|
|
|
89 |
|
|
|
90 |
// Parse table contents
|
|
|
91 |
$child = $root->first_child();
|
|
|
92 |
$col_index = 0;
|
|
|
93 |
while ($child) {
|
|
|
94 |
if ($child->node_type() === XML_ELEMENT_NODE) {
|
|
|
95 |
if ($child->tagname() === 'colgroup') {
|
|
|
96 |
// COLGROUP tags do not generate boxes; they contain information on the columns
|
|
|
97 |
//
|
|
|
98 |
$col_index = $box->parse_colgroup_tag($child, $col_index);
|
|
|
99 |
} else {
|
|
|
100 |
$child_box =& create_pdf_box($child, $pipeline);
|
|
|
101 |
$box->add_child($child_box);
|
|
|
102 |
};
|
|
|
103 |
};
|
|
|
104 |
|
|
|
105 |
$child = $child->next_sibling();
|
|
|
106 |
};
|
|
|
107 |
|
|
|
108 |
$box->normalize($pipeline);
|
|
|
109 |
$box->normalize_cwc();
|
|
|
110 |
$box->normalize_rhc();
|
|
|
111 |
$box->normalize_parent();
|
|
|
112 |
|
|
|
113 |
return $box;
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
// Parse the data in COL node;
|
|
|
117 |
// currently only 'width' attribute is parsed
|
|
|
118 |
//
|
|
|
119 |
// @param $root reference to a COL dom node
|
|
|
120 |
// @param $index index of column corresponding to this node
|
|
|
121 |
function parse_col(&$root, $index) {
|
|
|
122 |
if ($root->has_attribute('width')) {
|
|
|
123 |
// The value if 'width' attrubute is "multi-length";
|
|
|
124 |
// it means that it could be:
|
|
|
125 |
// 1. absolute value (10)
|
|
|
126 |
// 2. percentage value (10%)
|
|
|
127 |
// 3. relative value (3* or just *)
|
|
|
128 |
//
|
|
|
129 |
|
|
|
130 |
// TODO: support for relative values
|
|
|
131 |
|
|
|
132 |
$value = $root->get_attribute('width');
|
|
|
133 |
if (is_percentage($value)) {
|
|
|
134 |
$this->cwc[$index] = new WCFraction(((int)$value) / 100);
|
|
|
135 |
} else {
|
|
|
136 |
$this->cwc[$index] = new WCConstant(px2pt((int)$value));
|
|
|
137 |
};
|
|
|
138 |
};
|
|
|
139 |
}
|
|
|
140 |
|
|
|
141 |
// Traverse the COLGROUP node and save the column-specific information
|
|
|
142 |
//
|
|
|
143 |
// @param $root COLGROUP node
|
|
|
144 |
// @param $start_index index of the first column in this column group
|
|
|
145 |
// @return index of column after the last processed
|
|
|
146 |
//
|
|
|
147 |
function parse_colgroup_tag(&$root, $start_index) {
|
|
|
148 |
$index = $start_index;
|
|
|
149 |
|
|
|
150 |
// COLGROUP may contain zero or more COLs
|
|
|
151 |
//
|
|
|
152 |
$child = $root->first_child();
|
|
|
153 |
while ($child) {
|
|
|
154 |
if ($child->tagname() === 'col') {
|
|
|
155 |
$this->parse_col($child, $index);
|
|
|
156 |
$index ++;
|
|
|
157 |
};
|
|
|
158 |
$child = $child->next_sibling();
|
|
|
159 |
};
|
|
|
160 |
|
|
|
161 |
return $index;
|
|
|
162 |
}
|
|
|
163 |
|
|
|
164 |
function normalize_parent() {
|
|
|
165 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
166 |
$this->content[$i]->parent =& $this;
|
|
|
167 |
|
|
|
168 |
for ($j=0; $j<count($this->content[$i]->content); $j++) {
|
|
|
169 |
$this->content[$i]->content[$j]->parent =& $this;
|
|
|
170 |
|
|
|
171 |
// Set the column number for the cell to further reference
|
|
|
172 |
$this->content[$i]->content[$j]->column = $j;
|
|
|
173 |
|
|
|
174 |
// Set the column number for the cell to further reference
|
|
|
175 |
$this->content[$i]->content[$j]->row = $i;
|
|
|
176 |
}
|
|
|
177 |
}
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
// Normalize row height constraints
|
|
|
181 |
//
|
|
|
182 |
// no return value
|
|
|
183 |
//
|
|
|
184 |
function normalize_rhc() {
|
|
|
185 |
// Initialize the constraint array with the empty constraints
|
|
|
186 |
$this->rhc = array();
|
|
|
187 |
for ($i=0, $size = count($this->content); $i < $size; $i++) {
|
|
|
188 |
$this->rhc[$i] = new HCConstraint(null, null, null);
|
|
|
189 |
};
|
|
|
190 |
|
|
|
191 |
// Scan all cells
|
|
|
192 |
for ($i=0, $num_rows = count($this->content); $i < $num_rows; $i++) {
|
|
|
193 |
$row =& $this->content[$i];
|
|
|
194 |
|
|
|
195 |
for ($j=0, $num_cells = count($row->content); $j < $num_cells; $j++) {
|
|
|
196 |
$cell = $row->content[$j];
|
|
|
197 |
|
|
|
198 |
// Ignore cells with rowspans
|
|
|
199 |
if ($cell->rowspan > 1) { continue; }
|
|
|
200 |
|
|
|
201 |
// Put current cell width constraint as a columns with constraint
|
|
|
202 |
$this->rhc[$i] = merge_height_constraint($this->rhc[$i], $cell->get_height_constraint());
|
|
|
203 |
|
|
|
204 |
// Now reset the cell width constraint; cell width should be affected by ceolumn constraint only
|
|
|
205 |
$hc = new HCConstraint(null, null, null);
|
|
|
206 |
$cell->put_height_constraint($hc);
|
|
|
207 |
};
|
|
|
208 |
};
|
|
|
209 |
}
|
|
|
210 |
|
|
|
211 |
// Normalize column width constraints
|
|
|
212 |
// Note that cwc array may be partially prefilled by a GOLGROUP/COL-generated constraints!
|
|
|
213 |
//
|
|
|
214 |
function normalize_cwc() {
|
|
|
215 |
// Note we've called 'normalize' method prior to 'normalize_cwc',
|
|
|
216 |
// so we already have all rows of equal length
|
|
|
217 |
//
|
|
|
218 |
for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) {
|
|
|
219 |
// Check if there's already COL-generated constraint for this column
|
|
|
220 |
//
|
|
|
221 |
if (!isset($this->cwc[$i])) {
|
|
|
222 |
$this->cwc[$i] = new WCNone;
|
|
|
223 |
};
|
|
|
224 |
}
|
|
|
225 |
|
|
|
226 |
// For each column (we should have table already normalized - so lengths of all rows are equal)
|
|
|
227 |
for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) {
|
|
|
228 |
|
|
|
229 |
// For each row
|
|
|
230 |
for ($j=0, $num_rows = count($this->content); $j < $num_rows; $j++) {
|
|
|
231 |
$cell =& $this->content[$j]->content[$i];
|
|
|
232 |
|
|
|
233 |
// Ignore cells with colspans
|
|
|
234 |
if ($cell->colspan > 1) { continue; }
|
|
|
235 |
|
|
|
236 |
// Put current cell width constraint as a columns with constraint
|
|
|
237 |
$this->cwc[$i] = merge_width_constraint($this->cwc[$i], $cell->get_css_property(CSS_WIDTH));
|
|
|
238 |
|
|
|
239 |
// Now reset the cell width constraint; cell width should be affected by ceolumn constraint only
|
|
|
240 |
$cell->setCSSProperty(CSS_WIDTH, new WCNone);
|
|
|
241 |
}
|
|
|
242 |
}
|
|
|
243 |
|
|
|
244 |
// Now fix the overconstrained columns; first of all, sum of all percentage-constrained
|
|
|
245 |
// columns should be less or equal than 100%. If sum is greater, the last column
|
|
|
246 |
// percentage is reduced in order to get 100% as a result.
|
|
|
247 |
$rest = 1;
|
|
|
248 |
for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) {
|
|
|
249 |
// Get current CWC
|
|
|
250 |
$wc =& $this->cwc[$i];
|
|
|
251 |
|
|
|
252 |
if ($wc->isFraction()) {
|
|
|
253 |
$wc->fraction = min($rest, $wc->fraction);
|
|
|
254 |
$rest -= $wc->fraction;
|
|
|
255 |
};
|
|
|
256 |
};
|
|
|
257 |
|
|
|
258 |
/**
|
|
|
259 |
* Now, let's process cells spanninig several columns.
|
|
|
260 |
*/
|
|
|
261 |
|
|
|
262 |
/**
|
|
|
263 |
* Let's check if there's any colspanning cells filling the whole table width and
|
|
|
264 |
* containing non-100% percentage constraint
|
|
|
265 |
*/
|
|
|
266 |
|
|
|
267 |
// For each row
|
|
|
268 |
for ($j=0; $j<count($this->content); $j++) {
|
|
|
269 |
/**
|
|
|
270 |
* Check if the first cell in this row satisfies the above condition
|
|
|
271 |
*/
|
|
|
272 |
|
|
|
273 |
$cell =& $this->content[$j]->content[0];
|
|
|
274 |
|
|
|
275 |
/**
|
|
|
276 |
* Note that there should be '>='; '==' is not enough, as sometimes cell is declared to span
|
|
|
277 |
* more columns than there are in the table
|
|
|
278 |
*/
|
|
|
279 |
$cell_wc = $cell->get_css_property(CSS_WIDTH);
|
|
|
280 |
if (!$cell->is_fake() &&
|
|
|
281 |
$cell_wc->isFraction() &&
|
|
|
282 |
$cell->colspan >= count($this->content[$j])) {
|
|
|
283 |
|
|
|
284 |
/**
|
|
|
285 |
* Clear the constraint; anyway, it should be replaced with 100% in this case, as
|
|
|
286 |
* this cell is the only cell in the row
|
|
|
287 |
*/
|
|
|
288 |
|
|
|
289 |
$wc = new WCNone;
|
|
|
290 |
$cell->setCSSProperty(CSS_WIDTH, $wc);
|
|
|
291 |
};
|
|
|
292 |
};
|
|
|
293 |
}
|
|
|
294 |
|
|
|
295 |
/**
|
|
|
296 |
* Normalize table by adding fake cells for colspans and rowspans
|
|
|
297 |
* Also, if there is any empty rows (without cells), add at least one fake cell
|
|
|
298 |
*/
|
|
|
299 |
function normalize(&$pipeline) {
|
|
|
300 |
/**
|
|
|
301 |
* Fix empty rows by adding a fake cell
|
|
|
302 |
*/
|
|
|
303 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
304 |
$row =& $this->content[$i];
|
|
|
305 |
if (count($row->content) == 0) {
|
|
|
306 |
$this->content[$i]->add_fake_cell_before(0, $pipeline);
|
|
|
307 |
};
|
|
|
308 |
};
|
|
|
309 |
|
|
|
310 |
/**
|
|
|
311 |
* first, normalize colspans
|
|
|
312 |
*/
|
|
|
313 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
314 |
$this->content[$i]->normalize($pipeline);
|
|
|
315 |
};
|
|
|
316 |
|
|
|
317 |
/**
|
|
|
318 |
* second, normalize rowspans
|
|
|
319 |
*
|
|
|
320 |
* We should scan table column-by-column searching for row-spanned cells;
|
|
|
321 |
* consider the following example:
|
|
|
322 |
*
|
|
|
323 |
* <table>
|
|
|
324 |
* <tr>
|
|
|
325 |
* <td>A1</td>
|
|
|
326 |
* <td rowspan="3">B1</td>
|
|
|
327 |
* <td>C1</td>
|
|
|
328 |
* </tr>
|
|
|
329 |
*
|
|
|
330 |
* <tr>
|
|
|
331 |
* <td rowspan="2">A2</td>
|
|
|
332 |
* <td>C2</td>
|
|
|
333 |
* </tr>
|
|
|
334 |
*
|
|
|
335 |
* <tr>
|
|
|
336 |
* <td>C3</td>
|
|
|
337 |
* </tr>
|
|
|
338 |
* </table>
|
|
|
339 |
*/
|
|
|
340 |
|
|
|
341 |
$i_col = 0;
|
|
|
342 |
do {
|
|
|
343 |
$flag = false;
|
|
|
344 |
for ($i_row=0; $i_row<count($this->content); $i_row++) {
|
|
|
345 |
$row =& $this->content[$i_row];
|
|
|
346 |
if ($i_col < count($row->content)) {
|
|
|
347 |
$flag = true;
|
|
|
348 |
|
|
|
349 |
// Check if this rowspan runs off the last row
|
|
|
350 |
$row->content[$i_col]->rowspan = min($row->content[$i_col]->rowspan,
|
|
|
351 |
count($this->content) - $i_row);
|
|
|
352 |
|
|
|
353 |
if ($row->content[$i_col]->rowspan > 1) {
|
|
|
354 |
|
|
|
355 |
// Note that min($i_row + $row->content[$i_col]->rowspan, count($this->content)) is
|
|
|
356 |
// required, as we cannot be sure that table actually contains the number
|
|
|
357 |
// of rows used in rowspan
|
|
|
358 |
//
|
|
|
359 |
for ($k=$i_row+1; $k<min($i_row + $row->content[$i_col]->rowspan, count($this->content)); $k++) {
|
|
|
360 |
|
|
|
361 |
// Note that if rowspanned cell have a colspan, we should insert SEVERAL fake cells!
|
|
|
362 |
//
|
|
|
363 |
for ($cs = 0; $cs < $row->content[$i_col]->colspan; $cs++) {
|
|
|
364 |
$this->content[$k]->add_fake_cell_before($i_col, $pipeline);
|
|
|
365 |
};
|
|
|
366 |
};
|
|
|
367 |
};
|
|
|
368 |
};
|
|
|
369 |
};
|
|
|
370 |
|
|
|
371 |
$i_col ++;
|
|
|
372 |
} while ($flag);
|
|
|
373 |
|
|
|
374 |
// third, make all rows equal in length by padding with fake-cells
|
|
|
375 |
$length = 0;
|
|
|
376 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
377 |
$length = max($length, count($this->content[$i]->content));
|
|
|
378 |
}
|
|
|
379 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
380 |
$row =& $this->content[$i];
|
|
|
381 |
while ($length > count($row->content)) {
|
|
|
382 |
$row->append_fake_cell($pipeline);
|
|
|
383 |
}
|
|
|
384 |
}
|
|
|
385 |
}
|
|
|
386 |
|
|
|
387 |
// Overrides default 'add_child' in GenericFormattedBox
|
|
|
388 |
function add_child(&$item) {
|
|
|
389 |
// Check if we're trying to add table cell to current table directly, without any table-rows
|
|
|
390 |
if ($item->isCell()) {
|
|
|
391 |
// Add cell to the last row
|
|
|
392 |
$last_row =& $this->content[count($this->content)-1];
|
|
|
393 |
$last_row->add_child($item);
|
|
|
394 |
|
|
|
395 |
} elseif ($item->isTableRow()) {
|
|
|
396 |
// If previous row is empty, remove it (get rid of automatically generated table row in constructor)
|
|
|
397 |
if (count($this->content) > 0) {
|
|
|
398 |
if (count($this->content[count($this->content)-1]->content) == 0) {
|
|
|
399 |
array_pop($this->content);
|
|
|
400 |
}
|
|
|
401 |
};
|
|
|
402 |
|
|
|
403 |
// Just add passed row
|
|
|
404 |
$this->content[] =& $item;
|
|
|
405 |
} elseif ($item->isTableSection()) {
|
|
|
406 |
// Add table section rows to current table, then drop section box
|
|
|
407 |
for ($i=0, $size = count($item->content); $i < $size; $i++) {
|
|
|
408 |
$this->add_child($item->content[$i]);
|
|
|
409 |
}
|
|
|
410 |
};
|
|
|
411 |
}
|
|
|
412 |
|
|
|
413 |
// Table-specific functions
|
|
|
414 |
|
|
|
415 |
// PREDICATES
|
|
|
416 |
function is_constrained_column($index) {
|
|
|
417 |
return !is_a($this->get_cwc($index),"wcnone");
|
|
|
418 |
}
|
|
|
419 |
|
|
|
420 |
// ROWSPANS
|
|
|
421 |
function table_have_rowspan($x,$y) {
|
|
|
422 |
return $this->content[$y]->content[$x]->rowspan;
|
|
|
423 |
}
|
|
|
424 |
|
|
|
425 |
function table_fit_rowspans($heights) {
|
|
|
426 |
$spans = $this->get_rowspans();
|
|
|
427 |
|
|
|
428 |
// Scan all cells spanning several rows
|
|
|
429 |
foreach ($spans as $span) {
|
|
|
430 |
$cell =& $this->content[$span->row]->content[$span->column];
|
|
|
431 |
|
|
|
432 |
// now check if cell height is less than sum of spanned rows heights
|
|
|
433 |
$row_heights = array_slice($heights, $span->row, $span->size);
|
|
|
434 |
|
|
|
435 |
// Vertical-align current cell
|
|
|
436 |
// calculate (approximate) row baseline
|
|
|
437 |
$baseline = $this->content[$span->row]->get_row_baseline();
|
|
|
438 |
|
|
|
439 |
// apply vertical-align
|
|
|
440 |
$vertical_align = $cell->get_css_property(CSS_VERTICAL_ALIGN);
|
|
|
441 |
|
|
|
442 |
$va_fun = CSSVerticalAlign::value2pdf($vertical_align);
|
|
|
443 |
$va_fun->apply_cell($cell, array_sum($row_heights), $baseline);
|
|
|
444 |
|
|
|
445 |
if (array_sum($row_heights) > $cell->get_full_height()) {
|
|
|
446 |
// Make cell fill all available vertical space
|
|
|
447 |
$cell->put_full_height(array_sum($row_heights));
|
|
|
448 |
};
|
|
|
449 |
}
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
function get_rowspans() {
|
|
|
453 |
$spans = array();
|
|
|
454 |
|
|
|
455 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
456 |
$spans = array_merge($spans, $this->content[$i]->get_rowspans($i));
|
|
|
457 |
};
|
|
|
458 |
|
|
|
459 |
return $spans;
|
|
|
460 |
}
|
|
|
461 |
|
|
|
462 |
// ROW-RELATED
|
|
|
463 |
|
|
|
464 |
/**
|
|
|
465 |
* Calculate set of row heights
|
|
|
466 |
*
|
|
|
467 |
* At the moment (*), we have a sum of total content heights of percentage constraned rows in
|
|
|
468 |
* $ch variable, and a "free" (e.g. table height - sum of all non-percentage constrained heights) height
|
|
|
469 |
* in the $h variable. Obviously, percentage-constrained rows should be expanded to fill the free space
|
|
|
470 |
*
|
|
|
471 |
* On the other size, there should be a maximal value to expand them to; for example, if sum of
|
|
|
472 |
* percentage constraints is 33%, then all these rows should fill only 1/3 of the table height,
|
|
|
473 |
* whatever the content height of other rows is. In this case, other (non-constrained) rows
|
|
|
474 |
* should be expanded to fill space left.
|
|
|
475 |
*
|
|
|
476 |
* In the latter case, if there's no non-constrained rows, the additional space should be filled by
|
|
|
477 |
* "plain" rows without any constraints
|
|
|
478 |
*
|
|
|
479 |
* @param $minheight the minimal allowed height of the row; as we'll need to expand rows later
|
|
|
480 |
* and rows containing totally empty cells will have zero height
|
|
|
481 |
* @return array of row heights in media points
|
|
|
482 |
*/
|
|
|
483 |
function _row_heights($minheight) {
|
|
|
484 |
$heights = array();
|
|
|
485 |
$cheights = array();
|
|
|
486 |
$height = $this->get_height();
|
|
|
487 |
|
|
|
488 |
// Calculate "content" and "constrained" heights of table rows
|
|
|
489 |
|
|
|
490 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
491 |
$heights[] = max($minheight, $this->content[$i]->row_height());
|
|
|
492 |
|
|
|
493 |
// Apply row height constraint
|
|
|
494 |
// we need to specify box which parent will serve as a base for height calculation;
|
|
|
495 |
|
|
|
496 |
$hc = $this->get_rhc($i);
|
|
|
497 |
$cheights[] = $hc->apply($heights[$i], $this->content[$i], null);
|
|
|
498 |
};
|
|
|
499 |
|
|
|
500 |
// Collapse "constrained" heights of percentage-constrained rows, if they're
|
|
|
501 |
// taking more that available space
|
|
|
502 |
|
|
|
503 |
$flags = $this->get_non_percentage_constrained_height_flags();
|
|
|
504 |
$h = $height;
|
|
|
505 |
$ch = 0;
|
|
|
506 |
for ($i=0; $i<count($heights); $i++) {
|
|
|
507 |
if ($flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; };
|
|
|
508 |
};
|
|
|
509 |
// (*) see note in the function description
|
|
|
510 |
if ($ch > 0) {
|
|
|
511 |
$scale = $h / $ch;
|
|
|
512 |
|
|
|
513 |
if ($scale < 1) {
|
|
|
514 |
for ($i=0; $i<count($heights); $i++) {
|
|
|
515 |
if (!$flags[$i]) { $cheights[$i] *= $scale; };
|
|
|
516 |
};
|
|
|
517 |
};
|
|
|
518 |
};
|
|
|
519 |
|
|
|
520 |
// Expand non-constrained rows, if there's free space still
|
|
|
521 |
|
|
|
522 |
$flags = $this->get_non_constrained_height_flags();
|
|
|
523 |
$h = $height;
|
|
|
524 |
$ch = 0;
|
|
|
525 |
for ($i=0; $i<count($cheights); $i++) {
|
|
|
526 |
if (!$flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; };
|
|
|
527 |
};
|
|
|
528 |
// (*) see note in the function description
|
|
|
529 |
if ($ch > 0) {
|
|
|
530 |
$scale = $h / $ch;
|
|
|
531 |
|
|
|
532 |
if ($scale < 1) {
|
|
|
533 |
for ($i=0; $i<count($heights); $i++) {
|
|
|
534 |
if ($flags[$i]) { $cheights[$i] *= $scale; };
|
|
|
535 |
};
|
|
|
536 |
};
|
|
|
537 |
};
|
|
|
538 |
|
|
|
539 |
// Expand percentage-constrained rows, if there's free space still
|
|
|
540 |
|
|
|
541 |
$flags = $this->get_non_percentage_constrained_height_flags();
|
|
|
542 |
$h = $height;
|
|
|
543 |
$ch = 0;
|
|
|
544 |
for ($i=0; $i<count($cheights); $i++) {
|
|
|
545 |
if ($flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; };
|
|
|
546 |
};
|
|
|
547 |
// (*) see note in the function description
|
|
|
548 |
if ($ch > 0) {
|
|
|
549 |
$scale = $h / $ch;
|
|
|
550 |
|
|
|
551 |
if ($scale < 1) {
|
|
|
552 |
for ($i=0; $i<count($heights); $i++) {
|
|
|
553 |
if (!$flags[$i]) { $cheights[$i] *= $scale; };
|
|
|
554 |
};
|
|
|
555 |
};
|
|
|
556 |
};
|
|
|
557 |
|
|
|
558 |
// Get the actual row height
|
|
|
559 |
for ($i=0; $i<count($heights); $i++) {
|
|
|
560 |
$heights[$i] = max($heights[$i], $cheights[$i]);
|
|
|
561 |
};
|
|
|
562 |
|
|
|
563 |
return $heights;
|
|
|
564 |
}
|
|
|
565 |
|
|
|
566 |
function table_resize_rows(&$heights) {
|
|
|
567 |
$row_top = $this->get_top();
|
|
|
568 |
|
|
|
569 |
$size = count($heights);
|
|
|
570 |
for ($i=0; $i<$size; $i++) {
|
|
|
571 |
$this->content[$i]->table_resize_row($heights[$i], $row_top);
|
|
|
572 |
$row_top -= $heights[$i];
|
|
|
573 |
}
|
|
|
574 |
|
|
|
575 |
// Set table height to sum of row heights
|
|
|
576 |
$this->put_height(array_sum($heights));
|
|
|
577 |
}
|
|
|
578 |
|
|
|
579 |
// // Calculate given table row height
|
|
|
580 |
// //
|
|
|
581 |
// // @param $index zero-based row index
|
|
|
582 |
// // @return value of row height (in media points)
|
|
|
583 |
// //
|
|
|
584 |
// function table_row_height($index) {
|
|
|
585 |
// // Select row
|
|
|
586 |
// $row =& $this->content[$index];
|
|
|
587 |
|
|
|
588 |
// // Calculate height of each cell contained in this row
|
|
|
589 |
// $height = 0;
|
|
|
590 |
// for ($i=0; $i<count($row->content); $i++) {
|
|
|
591 |
// if ($this->table_have_rowspan($i, $index) <= 1) {
|
|
|
592 |
// $height = max($height, $row->content[$i]->get_full_height());
|
|
|
593 |
// }
|
|
|
594 |
// }
|
|
|
595 |
|
|
|
596 |
// return $height;
|
|
|
597 |
// }
|
|
|
598 |
|
|
|
599 |
// function get_row_baseline($index) {
|
|
|
600 |
// // Get current row
|
|
|
601 |
// $row =& $this->content[$index];
|
|
|
602 |
// // Calculate maximal baseline for each cell contained
|
|
|
603 |
// $baseline = 0;
|
|
|
604 |
// for ($i = 0; $i < count($row->content); $i++) {
|
|
|
605 |
// // Cell baseline is the baseline of its first line box inside this cell
|
|
|
606 |
// if (count($row->content[$i]->content) > 0) {
|
|
|
607 |
// $baseline = max($baseline, $row->content[$i]->content[0]->baseline);
|
|
|
608 |
// };
|
|
|
609 |
// };
|
|
|
610 |
// return $baseline;
|
|
|
611 |
// }
|
|
|
612 |
|
|
|
613 |
// Width constraints
|
|
|
614 |
function get_cwc($col) {
|
|
|
615 |
return $this->cwc[$col];
|
|
|
616 |
}
|
|
|
617 |
|
|
|
618 |
// Get height constraint for the given row
|
|
|
619 |
//
|
|
|
620 |
// @param $row number of row (zero-based)
|
|
|
621 |
//
|
|
|
622 |
// @return HCConstraint object
|
|
|
623 |
//
|
|
|
624 |
function get_rhc($row) {
|
|
|
625 |
return $this->rhc[$row];
|
|
|
626 |
}
|
|
|
627 |
|
|
|
628 |
// Width calculation
|
|
|
629 |
//
|
|
|
630 |
// Note that if table have no width constraint AND some columns are percentage constrained,
|
|
|
631 |
// then the width of the table can be determined based on the minimal column width;
|
|
|
632 |
// e.g. if some column have minimal width of 10px and 10% width constraint,
|
|
|
633 |
// then table will have minimal width of 100px. If there's several percentage-constrained columns,
|
|
|
634 |
// then we choose from the generated values the maximal one
|
|
|
635 |
//
|
|
|
636 |
// Of course, all of the above can be applied ONLY to table without width constraint;
|
|
|
637 |
// of theres any w.c. applied to the table, it will have greater than column constraints
|
|
|
638 |
//
|
|
|
639 |
// We must take constrained table width into account; if there's a width constraint,
|
|
|
640 |
// then we must choose the maximal value between the constrained width and sum of minimal
|
|
|
641 |
// columns widths - so, expanding the constrained width in case it is not enough to fit
|
|
|
642 |
// the table contents
|
|
|
643 |
//
|
|
|
644 |
// @param $context referene to a flow context object
|
|
|
645 |
// @return minimal box width (including the padding/margin/border width! NOT content width)
|
|
|
646 |
//
|
|
|
647 |
function get_min_width(&$context) {
|
|
|
648 |
$widths = $this->get_table_columns_min_widths($context);
|
|
|
649 |
$maxw = $this->get_table_columns_max_widths($context);
|
|
|
650 |
|
|
|
651 |
// Expand some columns to fit colspanning cells
|
|
|
652 |
$widths = $this->_table_apply_colspans($widths, $context, 'get_min_width', $widths, $maxw);
|
|
|
653 |
|
|
|
654 |
$width = array_sum($widths);
|
|
|
655 |
$base_width = $width;
|
|
|
656 |
|
|
|
657 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
658 |
if (!$wc->isNull()) {
|
|
|
659 |
// Check if constrained table width should be expanded to fit the table contents
|
|
|
660 |
//
|
|
|
661 |
$width = max($width, $wc->apply(0, $this->parent->get_available_width($context)));
|
|
|
662 |
} else {
|
|
|
663 |
// Now check if there's any percentage column width constraints (note that
|
|
|
664 |
// if we've get here, than the table width is not constrained). Calculate
|
|
|
665 |
// the table width basing on these values and select the maximal value
|
|
|
666 |
//
|
|
|
667 |
for ($i=0; $i<$this->cols_count(); $i++) {
|
|
|
668 |
$cwc = $this->get_cwc($i);
|
|
|
669 |
|
|
|
670 |
$width = max($width,
|
|
|
671 |
min($cwc->apply_inverse($widths[$i], $base_width),
|
|
|
672 |
$this->parent->get_available_width($context) - $this->_get_hor_extra()));
|
|
|
673 |
};
|
|
|
674 |
};
|
|
|
675 |
|
|
|
676 |
return $width + $this->_get_hor_extra();
|
|
|
677 |
}
|
|
|
678 |
|
|
|
679 |
function get_min_width_natural(&$context) {
|
|
|
680 |
return $this->get_min_width($context);
|
|
|
681 |
}
|
|
|
682 |
|
|
|
683 |
function get_max_width(&$context) {
|
|
|
684 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
685 |
|
|
|
686 |
if ($wc->isConstant()) {
|
|
|
687 |
return $wc->apply(0, $this->parent->get_available_width($context));
|
|
|
688 |
} else {
|
|
|
689 |
$widths = $this->get_table_columns_max_widths($context);
|
|
|
690 |
$minwc = $this->get_table_columns_min_widths($context);
|
|
|
691 |
|
|
|
692 |
$widths = $this->_table_apply_colspans($widths, $context, 'get_max_width', $minwc, $widths);
|
|
|
693 |
|
|
|
694 |
$width = array_sum($widths);
|
|
|
695 |
$base_width = $width;
|
|
|
696 |
|
|
|
697 |
// Now check if there's any percentage column width constraints (note that
|
|
|
698 |
// if we've get here, than the table width is not constrained). Calculate
|
|
|
699 |
// the table width based on these values and select the maximal value
|
|
|
700 |
//
|
|
|
701 |
for ($i=0; $i<$this->cols_count(); $i++) {
|
|
|
702 |
$cwc = $this->get_cwc($i);
|
|
|
703 |
|
|
|
704 |
$width = max($width,
|
|
|
705 |
min($cwc->apply_inverse($widths[$i], $base_width),
|
|
|
706 |
$this->parent->get_available_width($context) - $this->_get_hor_extra()));
|
|
|
707 |
};
|
|
|
708 |
|
|
|
709 |
return $width + $this->_get_hor_extra();
|
|
|
710 |
}
|
|
|
711 |
}
|
|
|
712 |
|
|
|
713 |
function get_max_width_natural(&$context) {
|
|
|
714 |
return $this->get_max_width($context);
|
|
|
715 |
}
|
|
|
716 |
|
|
|
717 |
function get_width() {
|
|
|
718 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
719 |
$pwc = $this->parent->get_css_property(CSS_WIDTH);
|
|
|
720 |
|
|
|
721 |
if (!$this->parent->isCell() ||
|
|
|
722 |
!$pwc->isNull() ||
|
|
|
723 |
!$wc->isFraction()) {
|
|
|
724 |
$width = $wc->apply($this->width, $this->parent->width);
|
|
|
725 |
} else {
|
|
|
726 |
$width = $this->width;
|
|
|
727 |
};
|
|
|
728 |
|
|
|
729 |
// Note that table 'padding' property for is handled differently
|
|
|
730 |
// by different browsers; for example, IE 6 ignores it completely,
|
|
|
731 |
// while FF 1.5 subtracts horizontal padding value from constrained
|
|
|
732 |
// table width. We emulate FF behavior here
|
|
|
733 |
return $width -
|
|
|
734 |
$this->get_padding_left() -
|
|
|
735 |
$this->get_padding_right();
|
|
|
736 |
}
|
|
|
737 |
|
|
|
738 |
function table_column_widths(&$context) {
|
|
|
739 |
$table_layout = $this->get_css_property(CSS_TABLE_LAYOUT);
|
|
|
740 |
switch ($table_layout) {
|
|
|
741 |
case TABLE_LAYOUT_FIXED:
|
|
|
742 |
// require_once(HTML2PS_DIR.'strategy.table.layout.fixed.php');
|
|
|
743 |
// $strategy =& new StrategyTableLayoutFixed();
|
|
|
744 |
// break;
|
|
|
745 |
case TABLE_LAYOUT_AUTO:
|
|
|
746 |
default:
|
|
|
747 |
require_once(HTML2PS_DIR.'strategy.table.layout.auto.php');
|
|
|
748 |
$strategy =& new StrategyTableLayoutAuto();
|
|
|
749 |
break;
|
|
|
750 |
};
|
|
|
751 |
|
|
|
752 |
return $strategy->apply($this, $context);
|
|
|
753 |
}
|
|
|
754 |
|
|
|
755 |
// Extend some columns widths (if needed) to fit colspanned cell contents
|
|
|
756 |
//
|
|
|
757 |
function _table_apply_colspans($widths, &$context, $width_fun, $minwc, $maxwc) {
|
|
|
758 |
$colspans = $this->get_colspans();
|
|
|
759 |
|
|
|
760 |
foreach ($colspans as $colspan) {
|
|
|
761 |
$cell = $this->content[$colspan->row]->content[$colspan->column];
|
|
|
762 |
|
|
|
763 |
// apply colspans to the corresponsing colspanned-cell dimension
|
|
|
764 |
//
|
|
|
765 |
$cell_width = $cell->$width_fun($context);
|
|
|
766 |
|
|
|
767 |
// Apply cell constraint width, if any AND if table width is constrained
|
|
|
768 |
// if table width is not constrained, we should not do this, as current value
|
|
|
769 |
// of $table->get_width is maximal width (parent width), not the actual
|
|
|
770 |
// width of the table
|
|
|
771 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
772 |
if (!$wc->isNull()) {
|
|
|
773 |
$cell_wc = $cell->get_css_property(CSS_WIDTH);
|
|
|
774 |
$cell_width = $cell_wc->apply($cell_width, $this->get_width());
|
|
|
775 |
|
|
|
776 |
// On the other side, constrained with cannot be less than cell minimal width
|
|
|
777 |
$cell_width = max($cell_width, $cell->get_min_width($context));
|
|
|
778 |
};
|
|
|
779 |
|
|
|
780 |
// now select the pre-calculated widths of columns covered by this cell
|
|
|
781 |
// select the list of resizable columns covered by this cell
|
|
|
782 |
$spanned_widths = array();
|
|
|
783 |
$spanned_resizable = array();
|
|
|
784 |
|
|
|
785 |
for ($i=$colspan->column; $i < $colspan->column+$colspan->size; $i++) {
|
|
|
786 |
$spanned_widths[] = $widths[$i];
|
|
|
787 |
$spanned_resizable[] = ($minwc[$i] != $maxwc[$i]);
|
|
|
788 |
}
|
|
|
789 |
|
|
|
790 |
// Sometimes we may encounter the colspan over the empty columns (I mean ALL columns are empty); in this case
|
|
|
791 |
// we need to make these columns reizable in order to fit colspanned cell contents
|
|
|
792 |
//
|
|
|
793 |
if (array_sum($spanned_widths) == 0) {
|
|
|
794 |
for ($i=0; $i<count($spanned_widths); $i++) {
|
|
|
795 |
$spanned_widths[$i] = EPSILON;
|
|
|
796 |
$spanned_resizable[$i] = true;
|
|
|
797 |
};
|
|
|
798 |
};
|
|
|
799 |
|
|
|
800 |
// The same problem may arise when all colspanned columns are not resizable; in this case we'll force all
|
|
|
801 |
// of them to be resized
|
|
|
802 |
$any_resizable = false;
|
|
|
803 |
for ($i=0; $i<count($spanned_widths); $i++) {
|
|
|
804 |
$any_resizable |= $spanned_resizable[$i];
|
|
|
805 |
};
|
|
|
806 |
if (!$any_resizable) {
|
|
|
807 |
for ($i=0; $i<count($spanned_widths); $i++) {
|
|
|
808 |
$spanned_resizable[$i] = true;
|
|
|
809 |
};
|
|
|
810 |
}
|
|
|
811 |
|
|
|
812 |
// Expand resizable columns
|
|
|
813 |
//
|
|
|
814 |
$spanned_widths = expand_to_with_flags($cell_width,$spanned_widths,$spanned_resizable);
|
|
|
815 |
|
|
|
816 |
// Store modified widths
|
|
|
817 |
array_splice($widths, $colspan->column, $colspan->size, $spanned_widths);
|
|
|
818 |
};
|
|
|
819 |
|
|
|
820 |
return $widths;
|
|
|
821 |
}
|
|
|
822 |
|
|
|
823 |
function get_table_columns_max_widths(&$context) {
|
|
|
824 |
$widths = array();
|
|
|
825 |
|
|
|
826 |
for ($i=0; $i<count($this->content[0]->content); $i++) {
|
|
|
827 |
$widths[] = 0;
|
|
|
828 |
};
|
|
|
829 |
|
|
|
830 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
831 |
// Calculate column widths for a current row
|
|
|
832 |
$roww = $this->content[$i]->get_table_columns_max_widths($context);
|
|
|
833 |
for ($j=0; $j<count($roww); $j++) {
|
|
|
834 |
// $widths[$j] = max($roww[$j], isset($widths[$j]) ? $widths[$j] : 0);
|
|
|
835 |
$widths[$j] = max($roww[$j], $widths[$j]);
|
|
|
836 |
}
|
|
|
837 |
}
|
|
|
838 |
|
|
|
839 |
// Use column width constraints - column should not be wider its constrained width
|
|
|
840 |
for ($i=0; $i<count($widths); $i++) {
|
|
|
841 |
$cwc = $this->get_cwc($i);
|
|
|
842 |
|
|
|
843 |
// Newertheless, percentage constraints should not be applied IF table
|
|
|
844 |
// does not have constrained width
|
|
|
845 |
//
|
|
|
846 |
if (!is_a($cwc,"wcfraction")) {
|
|
|
847 |
$widths[$i] = $cwc->apply($widths[$i], $this->get_width());
|
|
|
848 |
};
|
|
|
849 |
}
|
|
|
850 |
|
|
|
851 |
// TODO: colspans
|
|
|
852 |
|
|
|
853 |
return $widths;
|
|
|
854 |
}
|
|
|
855 |
|
|
|
856 |
/**
|
|
|
857 |
* Optimization: calculated widths are cached
|
|
|
858 |
*/
|
|
|
859 |
function get_table_columns_min_widths(&$context) {
|
|
|
860 |
if (!is_null($this->_cached_min_widths)) {
|
|
|
861 |
return $this->_cached_min_widths;
|
|
|
862 |
};
|
|
|
863 |
|
|
|
864 |
$widths = array();
|
|
|
865 |
|
|
|
866 |
for ($i=0; $i<count($this->content[0]->content); $i++) {
|
|
|
867 |
$widths[] = 0;
|
|
|
868 |
};
|
|
|
869 |
|
|
|
870 |
$content_size = count($this->content);
|
|
|
871 |
for ($i=0; $i<$content_size; $i++) {
|
|
|
872 |
// Calculate column widths for a current row
|
|
|
873 |
$roww = $this->content[$i]->get_table_columns_min_widths($context);
|
|
|
874 |
|
|
|
875 |
$row_size = count($roww);
|
|
|
876 |
for ($j=0; $j<$row_size; $j++) {
|
|
|
877 |
$widths[$j] = max($roww[$j], $widths[$j]);
|
|
|
878 |
}
|
|
|
879 |
}
|
|
|
880 |
|
|
|
881 |
$this->_cached_min_widths = $widths;
|
|
|
882 |
return $widths;
|
|
|
883 |
}
|
|
|
884 |
|
|
|
885 |
function get_colspans() {
|
|
|
886 |
$colspans = array();
|
|
|
887 |
|
|
|
888 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
889 |
$colspans = array_merge($colspans, $this->content[$i]->get_colspans($i));
|
|
|
890 |
};
|
|
|
891 |
|
|
|
892 |
return $colspans;
|
|
|
893 |
}
|
|
|
894 |
|
|
|
895 |
function check_constrained_colspan($col) {
|
|
|
896 |
for ($i=0; $i<$this->rows_count(); $i++) {
|
|
|
897 |
$cell =& $this->cell($i, $col);
|
|
|
898 |
$cell_wc = $cell->get_css_property(CSS_WIDTH);
|
|
|
899 |
|
|
|
900 |
if ($cell->colspan > 1 &&
|
|
|
901 |
!$cell_wc->isNull()) {
|
|
|
902 |
return true;
|
|
|
903 |
};
|
|
|
904 |
};
|
|
|
905 |
return false;
|
|
|
906 |
}
|
|
|
907 |
|
|
|
908 |
// Tries to change minimal constrained width so that columns will fit into the given
|
|
|
909 |
// table width
|
|
|
910 |
//
|
|
|
911 |
// Note that every width constraint have its own priority; first, the unconstrained columns are collapsed,
|
|
|
912 |
// then - percentage constrained and after all - columns having fixed width
|
|
|
913 |
//
|
|
|
914 |
// @param $width table width
|
|
|
915 |
// @param $minw array of unconstrained minimal widths
|
|
|
916 |
// @param $minwc array of constrained minimal widths
|
|
|
917 |
// @return list of normalized minimal constrained widths
|
|
|
918 |
//
|
|
|
919 |
function normalize_min_widths($width, $minw, $minwc) {
|
|
|
920 |
// Check if sum of constrained widths is too big
|
|
|
921 |
// Note that we compare sum of constrained width with the MAXIMAL value of table width and
|
|
|
922 |
// sum of uncostrained minimal width; it will prevent from unneeded collapsing of table cells
|
|
|
923 |
// if table content will expand its width anyway
|
|
|
924 |
//
|
|
|
925 |
$twidth = max($width, array_sum($minw));
|
|
|
926 |
|
|
|
927 |
// compare with sum of minimal constrained widths
|
|
|
928 |
//
|
|
|
929 |
if (array_sum($minwc) > $twidth) {
|
|
|
930 |
$delta = array_sum($minwc) - $twidth;
|
|
|
931 |
|
|
|
932 |
// Calculate the amount of difference between minimal and constrained minimal width for each columns
|
|
|
933 |
$diff = array();
|
|
|
934 |
for ($i=0; $i<count($minw); $i++) {
|
|
|
935 |
// Do no modify width of columns taking part in constrained colspans
|
|
|
936 |
if (!$this->check_constrained_colspan($i)) {
|
|
|
937 |
$diff[$i] = $minwc[$i] - $minw[$i];
|
|
|
938 |
} else {
|
|
|
939 |
$diff[$i] = 0;
|
|
|
940 |
};
|
|
|
941 |
}
|
|
|
942 |
|
|
|
943 |
// If no difference is found, we can collapse no columns
|
|
|
944 |
// otherwise scale some columns...
|
|
|
945 |
$cwdelta = array_sum($diff);
|
|
|
946 |
|
|
|
947 |
if ($cwdelta > 0) {
|
|
|
948 |
for ($i=0; $i<count($minw); $i++) {
|
|
|
949 |
// $minwc[$i] = max(0,- ($minwc[$i] - $minw[$i]) / $cwdelta * $delta + $minwc[$i]);
|
|
|
950 |
$minwc[$i] = max(0, -$diff[$i] / $cwdelta * $delta + $minwc[$i]);
|
|
|
951 |
}
|
|
|
952 |
}
|
|
|
953 |
}
|
|
|
954 |
|
|
|
955 |
return $minwc;
|
|
|
956 |
}
|
|
|
957 |
|
|
|
958 |
function table_have_colspan($x, $y) {
|
|
|
959 |
return $this->content[$y]->content[$x]->colspan;
|
|
|
960 |
}
|
|
|
961 |
|
|
|
962 |
// Flow-control
|
|
|
963 |
function reflow(&$parent, &$context) {
|
|
|
964 |
if ($this->get_css_property(CSS_FLOAT) === FLOAT_NONE) {
|
|
|
965 |
$status = $this->reflow_static_normal($parent, $context);
|
|
|
966 |
} else {
|
|
|
967 |
$status = $this->reflow_static_float($parent, $context);
|
|
|
968 |
}
|
|
|
969 |
|
|
|
970 |
return $status;
|
|
|
971 |
}
|
|
|
972 |
|
|
|
973 |
function reflow_absolute(&$context) {
|
|
|
974 |
GenericFormattedBox::reflow($parent, $context);
|
|
|
975 |
|
|
|
976 |
// Calculate margin values if they have been set as a percentage
|
|
|
977 |
$this->_calc_percentage_margins($parent);
|
|
|
978 |
|
|
|
979 |
// Calculate width value if it had been set as a percentage
|
|
|
980 |
$this->_calc_percentage_width($parent, $context);
|
|
|
981 |
|
|
|
982 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
983 |
if (!$wc->isNull()) {
|
|
|
984 |
$col_width = $this->get_table_columns_min_widths($context);
|
|
|
985 |
$maxw = $this->get_table_columns_max_widths($context);
|
|
|
986 |
$col_width = $this->_table_apply_colspans($col_width, $context, 'get_min_width', $col_width, $maxw);
|
|
|
987 |
|
|
|
988 |
if (array_sum($col_width) > $this->get_width()) {
|
|
|
989 |
$wc = new WCConstant(array_sum($col_width));
|
|
|
990 |
};
|
|
|
991 |
};
|
|
|
992 |
|
|
|
993 |
$position_strategy =& new StrategyPositionAbsolute();
|
|
|
994 |
$position_strategy->apply($this);
|
|
|
995 |
|
|
|
996 |
$this->reflow_content($context);
|
|
|
997 |
}
|
|
|
998 |
|
|
|
999 |
/**
|
|
|
1000 |
* TODO: unlike block elements, table unconstrained width is determined
|
|
|
1001 |
* with its content, so it may be smaller than parent available width!
|
|
|
1002 |
*/
|
|
|
1003 |
function reflow_static_normal(&$parent, &$context) {
|
|
|
1004 |
GenericFormattedBox::reflow($parent, $context);
|
|
|
1005 |
|
|
|
1006 |
// Calculate margin values if they have been set as a percentage
|
|
|
1007 |
$this->_calc_percentage_margins($parent);
|
|
|
1008 |
|
|
|
1009 |
// Calculate width value if it had been set as a percentage
|
|
|
1010 |
$this->_calc_percentage_width($parent, $context);
|
|
|
1011 |
|
|
|
1012 |
$wc = $this->get_css_property(CSS_WIDTH);
|
|
|
1013 |
if (!$wc->isNull()) {
|
|
|
1014 |
$col_width = $this->get_table_columns_min_widths($context);
|
|
|
1015 |
$maxw = $this->get_table_columns_max_widths($context);
|
|
|
1016 |
$col_width = $this->_table_apply_colspans($col_width, $context, 'get_min_width', $col_width, $maxw);
|
|
|
1017 |
|
|
|
1018 |
if (array_sum($col_width) > $this->get_width()) {
|
|
|
1019 |
$wc = new WCConstant(array_sum($col_width));
|
|
|
1020 |
};
|
|
|
1021 |
};
|
|
|
1022 |
|
|
|
1023 |
// As table width can be deterimined by its contents, we may calculate auto values
|
|
|
1024 |
// only AFTER the contents have been reflown; thus, we'll offset the table
|
|
|
1025 |
// as a whole by a value of left margin AFTER the content reflow
|
|
|
1026 |
|
|
|
1027 |
// Do margin collapsing
|
|
|
1028 |
$y = $this->collapse_margin($parent, $context);
|
|
|
1029 |
|
|
|
1030 |
// At this moment we have top parent/child collapsed margin at the top of context object
|
|
|
1031 |
// margin stack
|
|
|
1032 |
|
|
|
1033 |
$y = $this->apply_clear($y, $context);
|
|
|
1034 |
|
|
|
1035 |
// Store calculated Y coordinate as current Y in the parent box
|
|
|
1036 |
$parent->_current_y = $y;
|
|
|
1037 |
|
|
|
1038 |
// Terminate current parent line-box
|
|
|
1039 |
$parent->close_line($context);
|
|
|
1040 |
|
|
|
1041 |
// And add current box to the parent's line-box (alone)
|
|
|
1042 |
$parent->append_line($this);
|
|
|
1043 |
|
|
|
1044 |
// Determine upper-left _content_ corner position of current box
|
|
|
1045 |
// Also see note above regarding margins
|
|
|
1046 |
$border = $this->get_css_property(CSS_BORDER);
|
|
|
1047 |
$padding = $this->get_css_property(CSS_PADDING);
|
|
|
1048 |
|
|
|
1049 |
$this->put_left($parent->_current_x +
|
|
|
1050 |
$border->left->get_width() +
|
|
|
1051 |
$padding->left->value);
|
|
|
1052 |
|
|
|
1053 |
// Note that top margin already used above during maring collapsing
|
|
|
1054 |
$this->put_top($parent->_current_y - $border->top->get_width() - $padding->top->value);
|
|
|
1055 |
|
|
|
1056 |
/**
|
|
|
1057 |
* By default, child block box will fill all available parent width;
|
|
|
1058 |
* note that actual width will be smaller because of non-zero padding, border and margins
|
|
|
1059 |
*/
|
|
|
1060 |
$this->put_full_width($parent->get_available_width($context));
|
|
|
1061 |
|
|
|
1062 |
// Reflow contents
|
|
|
1063 |
$this->reflow_content($context);
|
|
|
1064 |
|
|
|
1065 |
// Update the collapsed margin value with current box bottom margin
|
|
|
1066 |
$margin = $this->get_css_property(CSS_MARGIN);
|
|
|
1067 |
|
|
|
1068 |
$context->pop_collapsed_margin();
|
|
|
1069 |
$context->pop_collapsed_margin();
|
|
|
1070 |
$context->push_collapsed_margin($margin->bottom->value);
|
|
|
1071 |
|
|
|
1072 |
// Calculate margins and/or width is 'auto' values have been specified
|
|
|
1073 |
$this->_calc_auto_width_margins($parent);
|
|
|
1074 |
$this->offset($margin->left->value, 0);
|
|
|
1075 |
|
|
|
1076 |
// Extend parent's height to fit current box
|
|
|
1077 |
$parent->extend_height($this->get_bottom_margin());
|
|
|
1078 |
// Terminate parent's line box
|
|
|
1079 |
$parent->close_line($context);
|
|
|
1080 |
}
|
|
|
1081 |
|
|
|
1082 |
// Get a list of boolean values indicating if table rows are height constrained
|
|
|
1083 |
//
|
|
|
1084 |
// @return array containing 'true' value at index I if I-th row is not height-constrained
|
|
|
1085 |
// and 'false' otherwise
|
|
|
1086 |
//
|
|
|
1087 |
function get_non_constrained_flags() {
|
|
|
1088 |
$flags = array();
|
|
|
1089 |
|
|
|
1090 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
1091 |
$hc = $this->get_rhc($i);
|
|
|
1092 |
$flags[$i] =
|
|
|
1093 |
(is_null($hc->constant)) &&
|
|
|
1094 |
(is_null($hc->min)) &&
|
|
|
1095 |
(is_null($hc->max));
|
|
|
1096 |
};
|
|
|
1097 |
|
|
|
1098 |
return $flags;
|
|
|
1099 |
}
|
|
|
1100 |
|
|
|
1101 |
// Get a list of boolean values indicating if table rows are height constrained using percentage values
|
|
|
1102 |
//
|
|
|
1103 |
// @return array containing 'true' value at index I if I-th row is not height-constrained
|
|
|
1104 |
// and 'false' otherwise
|
|
|
1105 |
//
|
|
|
1106 |
function get_non_percentage_constrained_height_flags() {
|
|
|
1107 |
$flags = array();
|
|
|
1108 |
|
|
|
1109 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
1110 |
$hc = $this->get_rhc($i);
|
|
|
1111 |
$flags[$i] =
|
|
|
1112 |
(!is_null($hc->constant) ? !$hc->constant[1] : true) &&
|
|
|
1113 |
(!is_null($hc->min) ? !$hc->min[1] : true) &&
|
|
|
1114 |
(!is_null($hc->max) ? !$hc->max[1] : true);
|
|
|
1115 |
};
|
|
|
1116 |
|
|
|
1117 |
return $flags;
|
|
|
1118 |
}
|
|
|
1119 |
|
|
|
1120 |
function get_non_constrained_height_flags() {
|
|
|
1121 |
$flags = array();
|
|
|
1122 |
|
|
|
1123 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
1124 |
$hc = $this->get_rhc($i);
|
|
|
1125 |
|
|
|
1126 |
$flags[$i] = $hc->is_null();
|
|
|
1127 |
};
|
|
|
1128 |
|
|
|
1129 |
return $flags;
|
|
|
1130 |
}
|
|
|
1131 |
|
|
|
1132 |
// Get a list of boolean values indicating if table columns are height constrained
|
|
|
1133 |
//
|
|
|
1134 |
// @return array containing 'true' value at index I if I-th columns is not width-constrained
|
|
|
1135 |
// and 'false' otherwise
|
|
|
1136 |
//
|
|
|
1137 |
function get_non_constrained_width_flags() {
|
|
|
1138 |
$flags = array();
|
|
|
1139 |
|
|
|
1140 |
for ($i=0; $i<$this->cols_count(); $i++) {
|
|
|
1141 |
$wc = $this->get_cwc($i);
|
|
|
1142 |
$flags[$i] = is_a($wc,"wcnone");
|
|
|
1143 |
};
|
|
|
1144 |
|
|
|
1145 |
return $flags;
|
|
|
1146 |
}
|
|
|
1147 |
|
|
|
1148 |
function get_non_constant_constrained_width_flags() {
|
|
|
1149 |
$flags = array();
|
|
|
1150 |
|
|
|
1151 |
for ($i=0; $i<$this->cols_count(); $i++) {
|
|
|
1152 |
$wc = $this->get_cwc($i);
|
|
|
1153 |
$flags[$i] = !is_a($wc,"WCConstant");
|
|
|
1154 |
};
|
|
|
1155 |
|
|
|
1156 |
return $flags;
|
|
|
1157 |
}
|
|
|
1158 |
|
|
|
1159 |
function check_if_column_image_constrained($col) {
|
|
|
1160 |
for ($i=0; $i<$this->rows_count(); $i++) {
|
|
|
1161 |
$cell =& $this->cell($i, $col);
|
|
|
1162 |
for ($j=0; $j<count($cell->content); $j++) {
|
|
|
1163 |
if (!$cell->content[$j]->is_null() &&
|
|
|
1164 |
!is_a($cell->content[$j], "GenericImgBox")) {
|
|
|
1165 |
return false;
|
|
|
1166 |
};
|
|
|
1167 |
};
|
|
|
1168 |
};
|
|
|
1169 |
return true;
|
|
|
1170 |
}
|
|
|
1171 |
|
|
|
1172 |
function get_non_image_constrained_width_flags() {
|
|
|
1173 |
$flags = array();
|
|
|
1174 |
|
|
|
1175 |
for ($i=0; $i<$this->cols_count(); $i++) {
|
|
|
1176 |
$flags[$i] = !$this->check_if_column_image_constrained($i);
|
|
|
1177 |
};
|
|
|
1178 |
|
|
|
1179 |
return $flags;
|
|
|
1180 |
}
|
|
|
1181 |
|
|
|
1182 |
// Get a list of boolean values indicating if table rows are NOT constant constrained
|
|
|
1183 |
//
|
|
|
1184 |
// @return array containing 'true' value at index I if I-th row is height-constrained
|
|
|
1185 |
// and 'false' otherwise
|
|
|
1186 |
//
|
|
|
1187 |
function get_non_constant_constrained_flags() {
|
|
|
1188 |
$flags = array();
|
|
|
1189 |
|
|
|
1190 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
1191 |
$hc = $this->get_rhc($i);
|
|
|
1192 |
$flags[$i] = is_null($hc->constant);
|
|
|
1193 |
};
|
|
|
1194 |
|
|
|
1195 |
return $flags;
|
|
|
1196 |
}
|
|
|
1197 |
|
|
|
1198 |
function reflow_content(&$context) {
|
|
|
1199 |
// Reflow content
|
|
|
1200 |
|
|
|
1201 |
// Reset current Y value
|
|
|
1202 |
//
|
|
|
1203 |
$this->_current_y = $this->get_top();
|
|
|
1204 |
|
|
|
1205 |
// Determine the base table width
|
|
|
1206 |
// if width constraint exists, the actual table width will not be changed anyway
|
|
|
1207 |
//
|
|
|
1208 |
$this->put_width(min($this->get_max_width($context), $this->get_width()));
|
|
|
1209 |
|
|
|
1210 |
// Calculate widths of table columns
|
|
|
1211 |
$columns = $this->table_column_widths($context);
|
|
|
1212 |
|
|
|
1213 |
// Collapse table to minimum width (if width is not constrained)
|
|
|
1214 |
$real_width = array_sum($columns);
|
|
|
1215 |
$this->put_width($real_width);
|
|
|
1216 |
|
|
|
1217 |
// If width is constrained, and is less than calculated, update the width constraint
|
|
|
1218 |
//
|
|
|
1219 |
// if ($this->get_width() < $real_width) {
|
|
|
1220 |
// // $this->put_width_constraint(new WCConstant($real_width));
|
|
|
1221 |
// };
|
|
|
1222 |
|
|
|
1223 |
// Flow cells horizontally in each table row
|
|
|
1224 |
for ($i=0; $i<count($this->content); $i++) {
|
|
|
1225 |
// Row flow started
|
|
|
1226 |
// Reset current X coordinate to the far left of the table
|
|
|
1227 |
$this->_current_x = $this->get_left();
|
|
|
1228 |
|
|
|
1229 |
// Flow each cell in the row
|
|
|
1230 |
$span = 0;
|
|
|
1231 |
for ($j=0; $j<count($this->content[$i]->content); $j++) {
|
|
|
1232 |
// Skip cells covered by colspans (fake cells, anyway)
|
|
|
1233 |
if ($span == 0) {
|
|
|
1234 |
// Flow current cell
|
|
|
1235 |
// Any colspans here?
|
|
|
1236 |
$span = $this->table_have_colspan($j, $i);
|
|
|
1237 |
|
|
|
1238 |
// Get sum of width for the current cell (or several cells in colspan)
|
|
|
1239 |
// In most cases, $span == 1 here (just a single cell)
|
|
|
1240 |
$cw = array_sum(array_slice($columns, $j, $span));
|
|
|
1241 |
|
|
|
1242 |
// store calculated width of the current cell
|
|
|
1243 |
$cell =& $this->content[$i]->content[$j];
|
|
|
1244 |
$cell->put_full_width($cw);
|
|
|
1245 |
$cell->setCSSProperty(CSS_WIDTH,
|
|
|
1246 |
new WCConstant($cw -
|
|
|
1247 |
$cell->_get_hor_extra()));
|
|
|
1248 |
|
|
|
1249 |
// TODO: check for rowspans
|
|
|
1250 |
|
|
|
1251 |
// Flow cell
|
|
|
1252 |
$this->content[$i]->content[$j]->reflow($this, $context);
|
|
|
1253 |
|
|
|
1254 |
// Offset current X value by the cell width
|
|
|
1255 |
$this->_current_x += $cw;
|
|
|
1256 |
};
|
|
|
1257 |
|
|
|
1258 |
// Current cell have been processed or skipped
|
|
|
1259 |
$span = max(0, $span-1);
|
|
|
1260 |
}
|
|
|
1261 |
|
|
|
1262 |
// calculate row height and do vertical align
|
|
|
1263 |
// $this->table_fit_row($i);
|
|
|
1264 |
|
|
|
1265 |
// row height calculation offset current Y coordinate by the row height calculated
|
|
|
1266 |
// $this->_current_y -= $this->table_row_height($i);
|
|
|
1267 |
$this->_current_y -= $this->content[$i]->row_height();
|
|
|
1268 |
}
|
|
|
1269 |
|
|
|
1270 |
// Calculate (and possibly adjust height of table rows)
|
|
|
1271 |
$heights = $this->_row_heights(0.1);
|
|
|
1272 |
|
|
|
1273 |
// adjust row heights to fit cells spanning several rows
|
|
|
1274 |
foreach ($this->get_rowspans() as $rowspan) {
|
|
|
1275 |
// Get height of the cell
|
|
|
1276 |
$cell_height = $this->content[$rowspan->row]->content[$rowspan->column]->get_full_height();
|
|
|
1277 |
|
|
|
1278 |
// Get calculated height of the spanned-over rows
|
|
|
1279 |
$cell_row_heights = array_slice($heights, $rowspan->row, $rowspan->size);
|
|
|
1280 |
|
|
|
1281 |
// Get list of non-constrained columns
|
|
|
1282 |
$flags = array_slice($this->get_non_constrained_flags(), $rowspan->row, $rowspan->size);
|
|
|
1283 |
|
|
|
1284 |
// Expand row heights (only for non-constrained columns)
|
|
|
1285 |
$new_heights = expand_to_with_flags($cell_height,
|
|
|
1286 |
$cell_row_heights,
|
|
|
1287 |
$flags);
|
|
|
1288 |
|
|
|
1289 |
// Check if rows could not be expanded
|
|
|
1290 |
// if (array_sum($new_heights) < $cell_height-1) {
|
|
|
1291 |
if (array_sum($new_heights) < $cell_height - EPSILON) {
|
|
|
1292 |
// Get list of non-constant-constrained columns
|
|
|
1293 |
$flags = array_slice($this->get_non_constant_constrained_flags(), $rowspan->row, $rowspan->size);
|
|
|
1294 |
|
|
|
1295 |
// use non-constant-constrained rows
|
|
|
1296 |
$new_heights = expand_to_with_flags($cell_height,
|
|
|
1297 |
$cell_row_heights,
|
|
|
1298 |
$flags);
|
|
|
1299 |
};
|
|
|
1300 |
|
|
|
1301 |
// Update the rows heights
|
|
|
1302 |
array_splice($heights,
|
|
|
1303 |
$rowspan->row,
|
|
|
1304 |
$rowspan->size,
|
|
|
1305 |
$new_heights);
|
|
|
1306 |
}
|
|
|
1307 |
|
|
|
1308 |
// Now expand rows to full table height
|
|
|
1309 |
$table_height = max($this->get_height(), array_sum($heights));
|
|
|
1310 |
|
|
|
1311 |
// Get list of non-constrained columns
|
|
|
1312 |
$flags = $this->get_non_constrained_height_flags();
|
|
|
1313 |
|
|
|
1314 |
// Expand row heights (only for non-constrained columns)
|
|
|
1315 |
$heights = expand_to_with_flags($table_height,
|
|
|
1316 |
$heights,
|
|
|
1317 |
$flags);
|
|
|
1318 |
|
|
|
1319 |
// Check if rows could not be expanded
|
|
|
1320 |
if (array_sum($heights) < $table_height - EPSILON) {
|
|
|
1321 |
// Get list of non-constant-constrained columns
|
|
|
1322 |
$flags = $this->get_non_constant_constrained_flags();
|
|
|
1323 |
|
|
|
1324 |
// use non-constant-constrained rows
|
|
|
1325 |
$heights = expand_to_with_flags($table_height,
|
|
|
1326 |
$heights,
|
|
|
1327 |
$flags);
|
|
|
1328 |
};
|
|
|
1329 |
|
|
|
1330 |
// Now we calculated row heights, time to actually resize them
|
|
|
1331 |
$this->table_resize_rows($heights);
|
|
|
1332 |
|
|
|
1333 |
// Update size of cells spanning several rows
|
|
|
1334 |
$this->table_fit_rowspans($heights);
|
|
|
1335 |
|
|
|
1336 |
// Expand total table height, if needed
|
|
|
1337 |
$total_height = array_sum($heights);
|
|
|
1338 |
if ($total_height > $this->get_height()) {
|
|
|
1339 |
$hc = new HCConstraint(array($total_height, false),
|
|
|
1340 |
array($total_height, false),
|
|
|
1341 |
array($total_height, false));
|
|
|
1342 |
$this->put_height_constraint($hc);
|
|
|
1343 |
};
|
|
|
1344 |
}
|
|
|
1345 |
|
|
|
1346 |
function isBlockLevel() {
|
|
|
1347 |
return true;
|
|
|
1348 |
}
|
|
|
1349 |
}
|
|
|
1350 |
?>
|