Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
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
?>