Passed
Push — master ( 6f9ff4...75f13c )
by
unknown
01:05
created

src/FrameReflower/Block.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @package dompdf
4
 * @link    http://dompdf.github.com/
5
 * @author  Benj Carson <[email protected]>
6
 * @author  Fabien Ménager <[email protected]>
7
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8
 */
9
namespace Dompdf\FrameReflower;
10
11
use Dompdf\Frame;
12
use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13
use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
14
use Dompdf\FrameDecorator\Text as TextFrameDecorator;
15
use Dompdf\Exception;
16
use Dompdf\Css\Style;
17
18
/**
19
 * Reflows block frames
20
 *
21
 * @package dompdf
22
 */
23
class Block extends AbstractFrameReflower
24
{
25
    // Minimum line width to justify, as fraction of available width
26
    const MIN_JUSTIFY_WIDTH = 0.80;
27
28
    /**
29
     * @var BlockFrameDecorator
30
     */
31
    protected $_frame;
32
33
    function __construct(BlockFrameDecorator $frame)
34
    {
35
        parent::__construct($frame);
36
    }
37
38
    /**
39
     *  Calculate the ideal used value for the width property as per:
40
     *  http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins
41
     *
42
     * @param float $width
43
     *
44
     * @return array
45
     */
46
    protected function _calculate_width($width)
47
    {
48
        $frame = $this->_frame;
49
        $style = $frame->get_style();
50
        $w = $frame->get_containing_block("w");
51
52
        if ($style->position === "fixed") {
53
            $w = $frame->get_parent()->get_containing_block("w");
54
        }
55
56
        $rm = $style->length_in_pt($style->margin_right, $w);
57
        $lm = $style->length_in_pt($style->margin_left, $w);
58
59
        $left = $style->length_in_pt($style->left, $w);
60
        $right = $style->length_in_pt($style->right, $w);
61
62
        // Handle 'auto' values
63
        $dims = array($style->border_left_width,
64
            $style->border_right_width,
65
            $style->padding_left,
66
            $style->padding_right,
67
            $width !== "auto" ? $width : 0,
68
            $rm !== "auto" ? $rm : 0,
69
            $lm !== "auto" ? $lm : 0);
70
71
        // absolutely positioned boxes take the 'left' and 'right' properties into account
72
        if ($frame->is_absolute()) {
73
            $absolute = true;
74
            $dims[] = $left !== "auto" ? $left : 0;
75
            $dims[] = $right !== "auto" ? $right : 0;
76
        } else {
77
            $absolute = false;
78
        }
79
80
        $sum = (float)$style->length_in_pt($dims, $w);
81
82
        // Compare to the containing block
83
        $diff = $w - $sum;
84
85
        if ($diff > 0) {
86
            if ($absolute) {
87
                // resolve auto properties: see
88
                // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
89
90
                if ($width === "auto" && $left === "auto" && $right === "auto") {
91
                    if ($lm === "auto") {
92
                        $lm = 0;
93
                    }
94
                    if ($rm === "auto") {
95
                        $rm = 0;
96
                    }
97
98
                    // Technically, the width should be "shrink-to-fit" i.e. based on the
99
                    // preferred width of the content...  a little too costly here as a
100
                    // special case.  Just get the width to take up the slack:
101
                    $left = 0;
102
                    $right = 0;
103
                    $width = $diff;
104
                } else if ($width === "auto") {
105
                    if ($lm === "auto") {
106
                        $lm = 0;
107
                    }
108
                    if ($rm === "auto") {
109
                        $rm = 0;
110
                    }
111
                    if ($left === "auto") {
112
                        $left = 0;
113
                    }
114
                    if ($right === "auto") {
115
                        $right = 0;
116
                    }
117
118
                    $width = $diff;
119
                } else if ($left === "auto") {
120
                    if ($lm === "auto") {
121
                        $lm = 0;
122
                    }
123
                    if ($rm === "auto") {
124
                        $rm = 0;
125
                    }
126
                    if ($right === "auto") {
127
                        $right = 0;
128
                    }
129
130
                    $left = $diff;
131
                } else if ($right === "auto") {
132
                    if ($lm === "auto") {
133
                        $lm = 0;
134
                    }
135
                    if ($rm === "auto") {
136
                        $rm = 0;
137
                    }
138
139
                    $right = $diff;
140
                }
141
142
            } else {
143
                // Find auto properties and get them to take up the slack
144
                if ($width === "auto") {
145
                    $width = $diff;
146
                } else if ($lm === "auto" && $rm === "auto") {
147
                    $lm = $rm = round($diff / 2);
148
                } else if ($lm === "auto") {
149
                    $lm = $diff;
150
                } else if ($rm === "auto") {
151
                    $rm = $diff;
152
                }
153
            }
154
        } else if ($diff < 0) {
155
            // We are over constrained--set margin-right to the difference
156
            $rm = $diff;
157
        }
158
159
        return array(
160
            "width" => $width,
161
            "margin_left" => $lm,
162
            "margin_right" => $rm,
163
            "left" => $left,
164
            "right" => $right,
165
        );
166
    }
167
168
    /**
169
     * Call the above function, but resolve max/min widths
170
     *
171
     * @throws Exception
172
     * @return array
173
     */
174
    protected function _calculate_restricted_width()
175
    {
176
        $frame = $this->_frame;
177
        $style = $frame->get_style();
178
        $cb = $frame->get_containing_block();
179
180
        if ($style->position === "fixed") {
181
            $cb = $frame->get_root()->get_containing_block();
182
        }
183
184
        //if ( $style->position === "absolute" )
185
        //  $cb = $frame->find_positionned_parent()->get_containing_block();
186
187
        if (!isset($cb["w"])) {
188
            throw new Exception("Box property calculation requires containing block width");
189
        }
190
191
        // Treat width 100% as auto
192
        if ($style->width === "100%") {
193
            $width = "auto";
194
        } else {
195
            $width = $style->length_in_pt($style->width, $cb["w"]);
196
        }
197
198
        $calculate_width = $this->_calculate_width($width);
199
        $margin_left = $calculate_width['margin_left'];
200
        $margin_right = $calculate_width['margin_right'];
201
        $width =  $calculate_width['width'];
202
        $left =  $calculate_width['left'];
203
        $right =  $calculate_width['right'];
204
205
        // Handle min/max width
206
        $min_width = $style->length_in_pt($style->min_width, $cb["w"]);
207
        $max_width = $style->length_in_pt($style->max_width, $cb["w"]);
208
209
        if ($max_width !== "none" && $min_width > $max_width) {
210
            list($max_width, $min_width) = array($min_width, $max_width);
211
        }
212
213
        if ($max_width !== "none" && $width > $max_width) {
214
            extract($this->_calculate_width($max_width));
215
        }
216
217
        if ($width < $min_width) {
218
            $calculate_width = $this->_calculate_width($min_width);
219
            $margin_left = $calculate_width['margin_left'];
220
            $margin_right = $calculate_width['margin_right'];
221
            $width =  $calculate_width['width'];
222
            $left =  $calculate_width['left'];
223
            $right =  $calculate_width['right'];
224
        }
225
226
        return array($width, $margin_left, $margin_right, $left, $right);
227
    }
228
229
    /**
230
     * Determine the unrestricted height of content within the block
231
     * not by adding each line's height, but by getting the last line's position.
232
     * This because lines could have been pushed lower by a clearing element.
233
     *
234
     * @return float
235
     */
236
    protected function _calculate_content_height()
237
    {
238
        $height = 0;
239
        $lines = $this->_frame->get_line_boxes();
240
        if (count($lines) > 0) {
241
            $last_line = end($lines);
242
            $content_box = $this->_frame->get_content_box();
243
            $height = $last_line->y + $last_line->h - $content_box["y"];
244
        }
245
        return $height;
246
    }
247
248
    /**
249
     * Determine the frame's restricted height
250
     *
251
     * @return array
252
     */
253
    protected function _calculate_restricted_height()
254
    {
255
        $frame = $this->_frame;
256
        $style = $frame->get_style();
257
        $content_height = $this->_calculate_content_height();
258
        $cb = $frame->get_containing_block();
259
260
        $height = $style->length_in_pt($style->height, $cb["h"]);
261
262
        $top = $style->length_in_pt($style->top, $cb["h"]);
263
        $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
264
265
        $margin_top = $style->length_in_pt($style->margin_top, $cb["h"]);
266
        $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["h"]);
267
268
        if ($frame->is_absolute()) {
269
270
            // see http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height
271
272
            $dims = array($top !== "auto" ? $top : 0,
273
                $style->margin_top !== "auto" ? $style->margin_top : 0,
274
                $style->padding_top,
275
                $style->border_top_width,
276
                $height !== "auto" ? $height : 0,
277
                $style->border_bottom_width,
278
                $style->padding_bottom,
279
                $style->margin_bottom !== "auto" ? $style->margin_bottom : 0,
280
                $bottom !== "auto" ? $bottom : 0);
281
282
            $sum = (float)$style->length_in_pt($dims, $cb["h"]);
283
284
            $diff = $cb["h"] - $sum;
285
286
            if ($diff > 0) {
287
                if ($height === "auto" && $top === "auto" && $bottom === "auto") {
288
                    if ($margin_top === "auto") {
289
                        $margin_top = 0;
290
                    }
291
                    if ($margin_bottom === "auto") {
292
                        $margin_bottom = 0;
293
                    }
294
295
                    $height = $diff;
296
                } else if ($height === "auto" && $top === "auto") {
297
                    if ($margin_top === "auto") {
298
                        $margin_top = 0;
299
                    }
300
                    if ($margin_bottom === "auto") {
301
                        $margin_bottom = 0;
302
                    }
303
304
                    $height = $content_height;
305
                    $top = $diff - $content_height;
306
                } else if ($height === "auto" && $bottom === "auto") {
307
                    if ($margin_top === "auto") {
308
                        $margin_top = 0;
309
                    }
310
                    if ($margin_bottom === "auto") {
311
                        $margin_bottom = 0;
312
                    }
313
314
                    $height = $content_height;
315
                    $bottom = $diff - $content_height;
316
                } else if ($top === "auto" && $bottom === "auto") {
317
                    if ($margin_top === "auto") {
318
                        $margin_top = 0;
319
                    }
320
                    if ($margin_bottom === "auto") {
321
                        $margin_bottom = 0;
322
                    }
323
324
                    $bottom = $diff;
325
                } else if ($top === "auto") {
326
                    if ($margin_top === "auto") {
327
                        $margin_top = 0;
328
                    }
329
                    if ($margin_bottom === "auto") {
330
                        $margin_bottom = 0;
331
                    }
332
333
                    $top = $diff;
334
                } else if ($height === "auto") {
335
                    if ($margin_top === "auto") {
336
                        $margin_top = 0;
337
                    }
338
                    if ($margin_bottom === "auto") {
339
                        $margin_bottom = 0;
340
                    }
341
342
                    $height = $diff;
343
                } else if ($bottom === "auto") {
344
                    if ($margin_top === "auto") {
345
                        $margin_top = 0;
346
                    }
347
                    if ($margin_bottom === "auto") {
348
                        $margin_bottom = 0;
349
                    }
350
351
                    $bottom = $diff;
352
                } else {
353
                    if ($style->overflow === "visible") {
354
                        // set all autos to zero
355
                        if ($margin_top === "auto") {
356
                            $margin_top = 0;
357
                        }
358
                        if ($margin_bottom === "auto") {
359
                            $margin_bottom = 0;
360
                        }
361
                        if ($top === "auto") {
362
                            $top = 0;
363
                        }
364
                        if ($bottom === "auto") {
365
                            $bottom = 0;
366
                        }
367
                        if ($height === "auto") {
368
                            $height = $content_height;
369
                        }
370
                    }
371
372
                    // FIXME: overflow hidden
373
                }
374
            }
375
376
        } else {
377
            // Expand the height if overflow is visible
378
            if ($height === "auto" && $content_height > $height /* && $style->overflow === "visible" */) {
379
                $height = $content_height;
380
            }
381
382
            // FIXME: this should probably be moved to a seperate function as per
383
            // _calculate_restricted_width
384
385
            // Only handle min/max height if the height is independent of the frame's content
386
            if (!($style->overflow === "visible" || ($style->overflow === "hidden" && $height === "auto"))) {
387
                $min_height = $style->min_height;
388
                $max_height = $style->max_height;
389
390 View Code Duplication
                if (isset($cb["h"])) {
391
                    $min_height = $style->length_in_pt($min_height, $cb["h"]);
392
                    $max_height = $style->length_in_pt($max_height, $cb["h"]);
393
                } else if (isset($cb["w"])) {
394
                    if (mb_strpos($min_height, "%") !== false) {
395
                        $min_height = 0;
396
                    } else {
397
                        $min_height = $style->length_in_pt($min_height, $cb["w"]);
398
                    }
399
400
                    if (mb_strpos($max_height, "%") !== false) {
401
                        $max_height = "none";
402
                    } else {
403
                        $max_height = $style->length_in_pt($max_height, $cb["w"]);
404
                    }
405
                }
406
407 View Code Duplication
                if ($max_height !== "none" && $min_height > $max_height) {
408
                    // Swap 'em
409
                    list($max_height, $min_height) = array($min_height, $max_height);
410
                }
411
412
                if ($max_height !== "none" && $height > $max_height) {
413
                    $height = $max_height;
414
                }
415
416
                if ($height < $min_height) {
417
                    $height = $min_height;
418
                }
419
            }
420
        }
421
422
        return array($height, $margin_top, $margin_bottom, $top, $bottom);
423
    }
424
425
    /**
426
     * Adjust the justification of each of our lines.
427
     * http://www.w3.org/TR/CSS21/text.html#propdef-text-align
428
     */
429
    protected function _text_align()
430
    {
431
        $style = $this->_frame->get_style();
432
        $w = $this->_frame->get_containing_block("w");
433
        $width = (float)$style->length_in_pt($style->width, $w);
434
435
        switch ($style->text_align) {
436
            default:
437
            case "left":
438
                foreach ($this->_frame->get_line_boxes() as $line) {
439
                    if (!$line->left) {
440
                        continue;
441
                    }
442
443
                    foreach ($line->get_frames() as $frame) {
444
                        if ($frame instanceof BlockFrameDecorator) {
445
                            continue;
446
                        }
447
                        $frame->set_position($frame->get_position("x") + $line->left);
448
                    }
449
                }
450
                return;
451
452 View Code Duplication
            case "right":
453
                foreach ($this->_frame->get_line_boxes() as $line) {
454
                    // Move each child over by $dx
455
                    $dx = $width - $line->w - $line->right;
456
457
                    foreach ($line->get_frames() as $frame) {
458
                        // Block frames are not aligned by text-align
459
                        if ($frame instanceof BlockFrameDecorator) {
460
                            continue;
461
                        }
462
463
                        $frame->set_position($frame->get_position("x") + $dx);
464
                    }
465
                }
466
                break;
467
468
            case "justify":
469
                // We justify all lines except the last one
470
                $lines = $this->_frame->get_line_boxes(); // needs to be a variable (strict standards)
471
                $last_line = array_pop($lines);
472
473
                foreach ($lines as $i => $line) {
474
                    if ($line->br) {
475
                        unset($lines[$i]);
476
                    }
477
                }
478
479
                // One space character's width. Will be used to get a more accurate spacing
480
                $space_width = $this->get_dompdf()->getFontMetrics()->getTextWidth(" ", $style->font_family, $style->font_size);
481
482
                foreach ($lines as $line) {
483 View Code Duplication
                    if ($line->left) {
484
                        foreach ($line->get_frames() as $frame) {
485
                            if (!$frame instanceof TextFrameDecorator) {
486
                                continue;
487
                            }
488
489
                            $frame->set_position($frame->get_position("x") + $line->left);
490
                        }
491
                    }
492
493
                    // Set the spacing for each child
494
                    if ($line->wc > 1) {
495
                        $spacing = ($width - ($line->left + $line->w + $line->right) + $space_width) / ($line->wc - 1);
496
                    } else {
497
                        $spacing = 0;
498
                    }
499
500
                    $dx = 0;
501
                    foreach ($line->get_frames() as $frame) {
502
                        if (!$frame instanceof TextFrameDecorator) {
503
                            continue;
504
                        }
505
506
                        $text = $frame->get_text();
507
                        $spaces = mb_substr_count($text, " ");
508
509
                        $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
510
                        $_spacing = $spacing + $char_spacing;
511
512
                        $frame->set_position($frame->get_position("x") + $dx);
513
                        $frame->set_text_spacing($_spacing);
514
515
                        $dx += $spaces * $_spacing;
516
                    }
517
518
                    // The line (should) now occupy the entire width
519
                    $line->w = $width;
520
                }
521
522
                // Adjust the last line if necessary
523 View Code Duplication
                if ($last_line->left) {
524
                    foreach ($last_line->get_frames() as $frame) {
525
                        if ($frame instanceof BlockFrameDecorator) {
526
                            continue;
527
                        }
528
                        $frame->set_position($frame->get_position("x") + $last_line->left);
529
                    }
530
                }
531
                break;
532
533
            case "center":
534 View Code Duplication
            case "centre":
535
                foreach ($this->_frame->get_line_boxes() as $line) {
536
                    // Centre each line by moving each frame in the line by:
537
                    $dx = ($width + $line->left - $line->w - $line->right) / 2;
538
539
                    foreach ($line->get_frames() as $frame) {
540
                        // Block frames are not aligned by text-align
541
                        if ($frame instanceof BlockFrameDecorator) {
542
                            continue;
543
                        }
544
545
                        $frame->set_position($frame->get_position("x") + $dx);
546
                    }
547
                }
548
                break;
549
        }
550
    }
551
552
    /**
553
     * Align inline children vertically.
554
     * Aligns each child vertically after each line is reflowed
555
     */
556
    function vertical_align()
557
    {
558
        $canvas = null;
559
560
        foreach ($this->_frame->get_line_boxes() as $line) {
561
562
            $height = $line->h;
563
564
            foreach ($line->get_frames() as $frame) {
565
                $style = $frame->get_style();
566
                $isInlineBlock = (
567
                    '-dompdf-image' === $style->display
568
                    || 'inline-block' === $style->display
569
                    || 'inline-table' === $style->display
570
                );
571
                if (!$isInlineBlock && $style->display !== "inline") {
572
                    continue;
573
                }
574
575
                if (!isset($canvas)) {
576
                    $canvas = $frame->get_root()->get_dompdf()->get_canvas();
577
                }
578
579
                $baseline = $canvas->get_font_baseline($style->font_family, $style->font_size);
580
                $y_offset = 0;
581
582
                //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
583
                if($isInlineBlock) {
584
                    $lineFrames = $line->get_frames();
585
                    if (count($lineFrames) == 1) {
586
                        continue;
587
                    }
588
                    $frameBox = $frame->get_frame()->get_border_box();
589
                    $imageHeightDiff = $height * 0.8 - (float)$frameBox['h'];
590
591
                    $align = $frame->get_style()->vertical_align;
592
                    if (in_array($align, Style::$vertical_align_keywords) === true) {
593
                        switch ($align) {
594
                            case "middle":
595
                                $y_offset = $imageHeightDiff / 2;
596
                                break;
597
598
                            case "sub":
599
                                $y_offset = 0.3 * $height + $imageHeightDiff;
600
                                break;
601
602
                            case "super":
603
                                $y_offset = -0.2 * $height + $imageHeightDiff;
604
                                break;
605
606
                            case "text-top": // FIXME: this should be the height of the frame minus the height of the text
607
                                $y_offset = $height - (float)$style->length_in_pt($style->line_height, $style->font_size);
0 ignored issues
show
The property line_height does not seem to exist. Did you mean default_line_height?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
608
                                break;
609
610
                            case "top":
611
                                break;
612
613
                            case "text-bottom": // FIXME: align bottom of image with the descender?
614
                            case "bottom":
615
                                $y_offset = 0.3 * $height + $imageHeightDiff;
616
                                break;
617
618
                            case "baseline":
619
                            default:
620
                                $y_offset = $imageHeightDiff;
621
                                break;
622
                        }
623
                    } else {
624
                        $y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - (float)$frameBox['h'];
625
                    }
626
                } else {
627
                    $parent = $frame->get_parent();
628
                    if ($parent instanceof TableCellFrameDecorator) {
629
                        $align = "baseline";
630
                    } else {
631
                        $align = $parent->get_style()->vertical_align;
632
                    }
633
                    if (in_array($align, Style::$vertical_align_keywords) === true) {
634
                        switch ($align) {
635
                            case "middle":
636
                                $y_offset = ($height * 0.8 - $baseline) / 2;
637
                                break;
638
639
                            case "sub":
640
                                $y_offset = $height * 0.8 - $baseline * 0.5;
641
                                break;
642
643
                            case "super":
644
                                $y_offset = $height * 0.8 - $baseline * 1.4;
645
                                break;
646
647
                            case "text-top":
648
                            case "top": // Not strictly accurate, but good enough for now
649
                                break;
650
651
                            case "text-bottom":
652
                            case "bottom":
653
                                $y_offset = $height * 0.8 - $baseline;
654
                                break;
655
656
                            case "baseline":
657
                            default:
658
                                $y_offset = $height * 0.8 - $baseline;
659
                                break;
660
                        }
661
                    } else {
662
                        $y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
663
                    }
664
                }
665
666
                if ($y_offset !== 0) {
667
                    $frame->move(0, $y_offset);
668
                }
669
            }
670
        }
671
    }
672
673
    /**
674
     * @param Frame $child
675
     */
676
    function process_clear(Frame $child)
677
    {
678
        $child_style = $child->get_style();
679
        $root = $this->_frame->get_root();
680
681
        // Handle "clear"
682
        if ($child_style->clear !== "none") {
683
            //TODO: this is a WIP for handling clear/float frames that are in between inline frames
684
            if ($child->get_prev_sibling() !== null) {
685
                $this->_frame->add_line();
686
            }
687
            if ($child_style->float !== "none" && $child->get_next_sibling()) {
688
                $this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1);
689
            }
690
691
            $lowest_y = $root->get_lowest_float_offset($child);
692
693
            // If a float is still applying, we handle it
694
            if ($lowest_y) {
695
                if ($child->is_in_flow()) {
696
                    $line_box = $this->_frame->get_current_line_box();
697
                    $line_box->y = $lowest_y + $child->get_margin_height();
698
                    $line_box->left = 0;
699
                    $line_box->right = 0;
700
                }
701
702
                $child->move(0, $lowest_y - $child->get_position("y"));
703
            }
704
        }
705
    }
706
707
    /**
708
     * @param Frame $child
709
     * @param float $cb_x
710
     * @param float $cb_w
711
     */
712
    function process_float(Frame $child, $cb_x, $cb_w)
713
    {
714
        $child_style = $child->get_style();
715
        $root = $this->_frame->get_root();
716
717
        // Handle "float"
718
        if ($child_style->float !== "none") {
719
            $root->add_floating_frame($child);
720
721
            // Remove next frame's beginning whitespace
722
            $next = $child->get_next_sibling();
723
            if ($next && $next instanceof TextFrameDecorator) {
724
                $next->set_text(ltrim($next->get_text()));
725
            }
726
727
            $line_box = $this->_frame->get_current_line_box();
728
            list($old_x, $old_y) = $child->get_position();
729
730
            $float_x = $cb_x;
731
            $float_y = $old_y;
732
            $float_w = $child->get_margin_width();
733
734
            if ($child_style->clear === "none") {
735
                switch ($child_style->float) {
736
                    case "left":
737
                        $float_x += $line_box->left;
738
                        break;
739
                    case "right":
740
                        $float_x += ($cb_w - $line_box->right - $float_w);
741
                        break;
742
                }
743
            } else {
744
                if ($child_style->float === "right") {
745
                    $float_x += ($cb_w - $float_w);
746
                }
747
            }
748
749
            if ($cb_w < $float_x + $float_w - $old_x) {
750
                // TODO handle when floating elements don't fit
751
            }
752
753
            $line_box->get_float_offsets();
754
755
            if ($child->_float_next_line) {
756
                $float_y += $line_box->h;
757
            }
758
759
            $child->set_position($float_x, $float_y);
760
            $child->move($float_x - $old_x, $float_y - $old_y, true);
761
        }
762
    }
763
764
    /**
765
     * @param BlockFrameDecorator $block
766
     * @return mixed|void
767
     */
768
    function reflow(BlockFrameDecorator $block = null)
769
    {
770
771
        // Check if a page break is forced
772
        $page = $this->_frame->get_root();
773
        $page->check_forced_page_break($this->_frame);
774
775
        // Bail if the page is full
776
        if ($page->is_full()) {
777
            return;
778
        }
779
780
        // Generated content
781
        $this->_set_content();
782
783
        // Collapse margins if required
784
        $this->_collapse_margins();
785
786
        $style = $this->_frame->get_style();
787
        $cb = $this->_frame->get_containing_block();
788
789
        if ($style->position === "fixed") {
790
            $cb = $this->_frame->get_root()->get_containing_block();
791
        }
792
793
        // Determine the constraints imposed by this frame: calculate the width
794
        // of the content area:
795
        list($w, $left_margin, $right_margin, $left, $right) = $this->_calculate_restricted_width();
796
797
        // Store the calculated properties
798
        $style->width = $w;
799
        $style->margin_left = $left_margin;
800
        $style->margin_right = $right_margin;
801
        $style->left = $left;
802
        $style->right = $right;
803
804
        // Update the position
805
        $this->_frame->position();
806
        list($x, $y) = $this->_frame->get_position();
807
808
        // Adjust the first line based on the text-indent property
809
        $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
810
        $this->_frame->increase_line_width($indent);
811
812
        // Determine the content edge
813
        $top = (float)$style->length_in_pt(array($style->margin_top,
814
            $style->padding_top,
815
            $style->border_top_width), $cb["h"]);
816
817
        $bottom = (float)$style->length_in_pt(array($style->border_bottom_width,
818
            $style->margin_bottom,
819
            $style->padding_bottom), $cb["h"]);
820
821
        $cb_x = $x + (float)$left_margin + (float)$style->length_in_pt(array($style->border_left_width,
822
                $style->padding_left), $cb["w"]);
823
824
        $cb_y = $y + $top;
825
826
        $cb_h = ($cb["h"] + $cb["y"]) - $bottom - $cb_y;
827
828
        // Set the y position of the first line in this block
829
        $line_box = $this->_frame->get_current_line_box();
830
        $line_box->y = $cb_y;
831
        $line_box->get_float_offsets();
832
833
        // Set the containing blocks and reflow each child
834 View Code Duplication
        foreach ($this->_frame->get_children() as $child) {
835
836
            // Bail out if the page is full
837
            if ($page->is_full()) {
838
                break;
839
            }
840
841
            $child->set_containing_block($cb_x, $cb_y, $w, $cb_h);
842
843
            $this->process_clear($child);
844
845
            $child->reflow($this->_frame);
846
847
            // Don't add the child to the line if a page break has occurred
848
            if ($page->check_page_break($child)) {
849
                break;
850
            }
851
852
            $this->process_float($child, $cb_x, $w);
853
        }
854
855
        // Determine our height
856
        list($height, $margin_top, $margin_bottom, $top, $bottom) = $this->_calculate_restricted_height();
857
        $style->height = $height;
858
        $style->margin_top = $margin_top;
859
        $style->margin_bottom = $margin_bottom;
860
        $style->top = $top;
861
        $style->bottom = $bottom;
862
863
        $orig_style = $this->_frame->get_original_style();
864
865
        $needs_reposition = ($style->position === "absolute" && ($style->right !== "auto" || $style->bottom !== "auto"));
866
867
        // Absolute positioning measurement
868
        if ($needs_reposition) {
869
            if ($orig_style->width === "auto" && ($orig_style->left === "auto" || $orig_style->right === "auto")) {
870
                $width = 0;
871
                foreach ($this->_frame->get_line_boxes() as $line) {
872
                    $width = max($line->w, $width);
873
                }
874
                $style->width = $width;
875
            }
876
877
            $style->left = $orig_style->left;
878
            $style->right = $orig_style->right;
879
        }
880
881
        // Calculate inline-block / float auto-widths
882
        if (($style->display === "inline-block" || $style->float !== 'none') && $orig_style->width === 'auto') {
883
            $width = 0;
884
885
            foreach ($this->_frame->get_line_boxes() as $line) {
886
                $line->recalculate_width();
887
888
                $width = max($line->w, $width);
889
            }
890
891
            if ($width === 0) {
892
                foreach ($this->_frame->get_children() as $child) {
893
                    $width += $child->calculate_auto_width();
894
                }
895
            }
896
897
            $style->width = $width;
898
        }
899
900
        $this->_text_align();
901
        $this->vertical_align();
902
903
        // Absolute positioning
904
        if ($needs_reposition) {
905
            list($x, $y) = $this->_frame->get_position();
906
            $this->_frame->position();
907
            list($new_x, $new_y) = $this->_frame->get_position();
908
            $this->_frame->move($new_x - $x, $new_y - $y, true);
909
        }
910
911
        if ($block && $this->_frame->is_in_flow()) {
912
            $block->add_frame_to_line($this->_frame);
913
914
            // May be inline-block
915
            if ($style->display === "block") {
916
                $block->add_line();
917
            }
918
        }
919
    }
920
921
    /**
922
     * Determine current frame width based on contents
923
     *
924
     * @return float
925
     */
926
    public function calculate_auto_width()
927
    {
928
        $width = 0;
929
930
        foreach ($this->_frame->get_line_boxes() as $line) {
931
            $line_width = 0;
932
933
            foreach ($line->get_frames() as $frame) {
934
                if ($frame->get_original_style()->width == 'auto') {
935
                    $line_width += $frame->calculate_auto_width();
936
                } else {
937
                    $line_width += $frame->get_margin_width();
938
                }
939
            }
940
941
            $width = max($line_width, $width);
942
        }
943
944
        $this->_frame->get_style()->width = $width;
945
946
        return $this->_frame->get_margin_width();
947
    }
948
}
949