PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / dompdf / src / Frame.php

Frame.php in MBE eShip trunk, at lib/dompdf/src/Frame.php

1,218 lines 30.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package dompdf
4 * @link https://github.com/dompdf/dompdf
5 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
6 */
7 namespace Dompdf;
8
9 use Dompdf\Css\Style;
10 use Dompdf\Frame\FrameListIterator;
11
12 /**
13 * The main Frame class
14 *
15 * This class represents a single HTML element. This class stores
16 * positioning information as well as containing block location and
17 * dimensions. Style information for the element is stored in a {@link
18 * Style} object. Tree structure is maintained via the parent & children
19 * links.
20 *
21 * @package dompdf
22 */
23 class Frame
24 {
25 const WS_TEXT = 1;
26 const WS_SPACE = 2;
27
28 /**
29 * The DOMElement or DOMText object this frame represents
30 *
31 * @var \DOMElement|\DOMText
32 */
33 protected $_node;
34
35 /**
36 * Unique identifier for this frame. Used to reference this frame
37 * via the node.
38 *
39 * @var int
40 */
41 protected $_id;
42
43 /**
44 * Unique id counter
45 *
46 * @var int
47 */
48 public static $ID_COUNTER = 0; /*protected*/
49
50 /**
51 * This frame's calculated style
52 *
53 * @var Style
54 */
55 protected $_style;
56
57 /**
58 * This frame's parent in the document tree.
59 *
60 * @var Frame
61 */
62 protected $_parent;
63
64 /**
65 * This frame's first child. All children are handled as a
66 * doubly-linked list.
67 *
68 * @var Frame
69 */
70 protected $_first_child;
71
72 /**
73 * This frame's last child.
74 *
75 * @var Frame
76 */
77 protected $_last_child;
78
79 /**
80 * This frame's previous sibling in the document tree.
81 *
82 * @var Frame
83 */
84 protected $_prev_sibling;
85
86 /**
87 * This frame's next sibling in the document tree.
88 *
89 * @var Frame
90 */
91 protected $_next_sibling;
92
93 /**
94 * This frame's containing block (used in layout): array(x, y, w, h)
95 *
96 * @var float[]
97 */
98 protected $_containing_block;
99
100 /**
101 * Position on the page of the top-left corner of the margin box of
102 * this frame: array(x,y)
103 *
104 * @var float[]
105 */
106 protected $_position;
107
108 /**
109 * Absolute opacity of this frame
110 *
111 * @var float
112 */
113 protected $_opacity;
114
115 /**
116 * This frame's decorator
117 *
118 * @var FrameDecorator\AbstractFrameDecorator
119 */
120 protected $_decorator;
121
122 /**
123 * This frame's containing line box
124 *
125 * @var LineBox|null
126 */
127 protected $_containing_line;
128
129 /**
130 * @var array
131 */
132 protected $_is_cache = [];
133
134 /**
135 * Tells whether the frame was already pushed to the next page
136 *
137 * @var bool
138 */
139 public $_already_pushed = false;
140
141 /**
142 * @var bool
143 */
144 public $_float_next_line = false;
145
146 /**
147 * @var int
148 */
149 public static $_ws_state = self::WS_SPACE;
150
151 /**
152 * Class constructor
153 *
154 * @param \DOMNode $node the DOMNode this frame represents
155 */
156 public function __construct(\DOMNode $node)
157 {
158 $this->_node = $node;
159
160 $this->_parent = null;
161 $this->_first_child = null;
162 $this->_last_child = null;
163 $this->_prev_sibling = $this->_next_sibling = null;
164
165 $this->_style = null;
166
167 $this->_containing_block = [
168 "x" => null,
169 "y" => null,
170 "w" => null,
171 "h" => null,
172 ];
173
174 $this->_containing_block[0] =& $this->_containing_block["x"];
175 $this->_containing_block[1] =& $this->_containing_block["y"];
176 $this->_containing_block[2] =& $this->_containing_block["w"];
177 $this->_containing_block[3] =& $this->_containing_block["h"];
178
179 $this->_position = [
180 "x" => null,
181 "y" => null,
182 ];
183
184 $this->_position[0] =& $this->_position["x"];
185 $this->_position[1] =& $this->_position["y"];
186
187 $this->_opacity = 1.0;
188 $this->_decorator = null;
189
190 $this->set_id(self::$ID_COUNTER++);
191 }
192
193 /**
194 * WIP : preprocessing to remove all the unused whitespace
195 */
196 protected function ws_trim()
197 {
198 if ($this->ws_keep()) {
199 return;
200 }
201
202 if (self::$_ws_state === self::WS_SPACE) {
203 $node = $this->_node;
204
205 if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
206 $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
207 self::$_ws_state = self::WS_TEXT;
208 }
209 }
210 }
211
212 /**
213 * @return bool
214 */
215 protected function ws_keep()
216 {
217 $whitespace = $this->get_style()->white_space;
218
219 return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
220 }
221
222 /**
223 * @return bool
224 */
225 protected function ws_is_text()
226 {
227 $node = $this->get_node();
228
229 if ($node->nodeName === "img") {
230 return true;
231 }
232
233 if (!$this->is_in_flow()) {
234 return false;
235 }
236
237 if ($this->is_text_node()) {
238 return trim($node->nodeValue) !== "";
239 }
240
241 return true;
242 }
243
244 /**
245 * "Destructor": forcibly free all references held by this frame
246 *
247 * @param bool $recursive if true, call dispose on all children
248 */
249 public function dispose($recursive = false)
250 {
251 if ($recursive) {
252 while ($child = $this->_first_child) {
253 $child->dispose(true);
254 }
255 }
256
257 // Remove this frame from the tree
258 if ($this->_prev_sibling) {
259 $this->_prev_sibling->_next_sibling = $this->_next_sibling;
260 }
261
262 if ($this->_next_sibling) {
263 $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
264 }
265
266 if ($this->_parent && $this->_parent->_first_child === $this) {
267 $this->_parent->_first_child = $this->_next_sibling;
268 }
269
270 if ($this->_parent && $this->_parent->_last_child === $this) {
271 $this->_parent->_last_child = $this->_prev_sibling;
272 }
273
274 if ($this->_parent) {
275 $this->_parent->get_node()->removeChild($this->_node);
276 }
277
278 $this->_style = null;
279 unset($this->_style);
280 }
281
282 /**
283 * Re-initialize the frame
284 */
285 public function reset()
286 {
287 $this->_position["x"] = null;
288 $this->_position["y"] = null;
289
290 $this->_containing_block["x"] = null;
291 $this->_containing_block["y"] = null;
292 $this->_containing_block["w"] = null;
293 $this->_containing_block["h"] = null;
294
295 $this->_style->reset();
296 }
297
298 /**
299 * @return \DOMElement|\DOMText
300 */
301 public function get_node()
302 {
303 return $this->_node;
304 }
305
306 /**
307 * @return int
308 */
309 public function get_id()
310 {
311 return $this->_id;
312 }
313
314 /**
315 * @return Style
316 */
317 public function get_style()
318 {
319 return $this->_style;
320 }
321
322 /**
323 * @deprecated
324 * @return Style
325 */
326 public function get_original_style()
327 {
328 return $this->_style;
329 }
330
331 /**
332 * @return Frame
333 */
334 public function get_parent()
335 {
336 return $this->_parent;
337 }
338
339 /**
340 * @return FrameDecorator\AbstractFrameDecorator
341 */
342 public function get_decorator()
343 {
344 return $this->_decorator;
345 }
346
347 /**
348 * @return Frame
349 */
350 public function get_first_child()
351 {
352 return $this->_first_child;
353 }
354
355 /**
356 * @return Frame
357 */
358 public function get_last_child()
359 {
360 return $this->_last_child;
361 }
362
363 /**
364 * @return Frame
365 */
366 public function get_prev_sibling()
367 {
368 return $this->_prev_sibling;
369 }
370
371 /**
372 * @return Frame
373 */
374 public function get_next_sibling()
375 {
376 return $this->_next_sibling;
377 }
378
379 /**
380 * @return FrameListIterator
381 */
382 public function get_children(): FrameListIterator
383 {
384 return new FrameListIterator($this);
385 }
386
387 // Layout property accessors
388
389 /**
390 * Containing block dimensions
391 *
392 * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
393 *
394 * @return float[]|float
395 */
396 public function get_containing_block($i = null)
397 {
398 if (isset($i)) {
399 return $this->_containing_block[$i];
400 }
401
402 return $this->_containing_block;
403 }
404
405 /**
406 * Block position
407 *
408 * @param string|null $i The key of the wanted position value (x, y)
409 *
410 * @return float[]|float
411 */
412 public function get_position($i = null)
413 {
414 if (isset($i)) {
415 return $this->_position[$i];
416 }
417
418 return $this->_position;
419 }
420
421 //........................................................................
422
423 /**
424 * Return the width of the margin box of the frame, in pt. Meaningless
425 * unless the width has been calculated properly.
426 *
427 * @return float
428 */
429 public function get_margin_width(): float
430 {
431 $style = $this->_style;
432
433 return (float)$style->length_in_pt([
434 $style->width,
435 $style->margin_left,
436 $style->margin_right,
437 $style->border_left_width,
438 $style->border_right_width,
439 $style->padding_left,
440 $style->padding_right
441 ], $this->_containing_block["w"]);
442 }
443
444 /**
445 * Return the height of the margin box of the frame, in pt. Meaningless
446 * unless the height has been calculated properly.
447 *
448 * @return float
449 */
450 public function get_margin_height(): float
451 {
452 $style = $this->_style;
453
454 return (float)$style->length_in_pt(
455 [
456 $style->height,
457 (float)$style->length_in_pt(
458 [
459 $style->border_top_width,
460 $style->border_bottom_width,
461 $style->margin_top,
462 $style->margin_bottom,
463 $style->padding_top,
464 $style->padding_bottom
465 ], $this->_containing_block["w"]
466 )
467 ],
468 $this->_containing_block["h"]
469 );
470 }
471
472 /**
473 * Return the content box (x,y,w,h) of the frame.
474 *
475 * Width and height might be reported as 0 if they have not been resolved
476 * yet.
477 *
478 * @return float[]
479 */
480 public function get_content_box(): array
481 {
482 $style = $this->_style;
483 $cb = $this->_containing_block;
484
485 $x = $this->_position["x"] +
486 (float)$style->length_in_pt(
487 [
488 $style->margin_left,
489 $style->border_left_width,
490 $style->padding_left
491 ],
492 $cb["w"]
493 );
494
495 $y = $this->_position["y"] +
496 (float)$style->length_in_pt(
497 [
498 $style->margin_top,
499 $style->border_top_width,
500 $style->padding_top
501 ], $cb["w"]
502 );
503
504 $w = (float)$style->length_in_pt($style->width, $cb["w"]);
505
506 $h = (float)$style->length_in_pt($style->height, $cb["h"]);
507
508 return [0 => $x, "x" => $x,
509 1 => $y, "y" => $y,
510 2 => $w, "w" => $w,
511 3 => $h, "h" => $h];
512 }
513
514 /**
515 * Return the padding box (x,y,w,h) of the frame.
516 *
517 * Width and height might be reported as 0 if they have not been resolved
518 * yet.
519 *
520 * @return float[]
521 */
522 public function get_padding_box(): array
523 {
524 $style = $this->_style;
525 $cb = $this->_containing_block;
526
527 $x = $this->_position["x"] +
528 (float)$style->length_in_pt(
529 [
530 $style->margin_left,
531 $style->border_left_width
532 ],
533 $cb["w"]
534 );
535
536 $y = $this->_position["y"] +
537 (float)$style->length_in_pt(
538 [
539 $style->margin_top,
540 $style->border_top_width
541 ],
542 $cb["h"]
543 );
544
545 $w = (float)$style->length_in_pt(
546 [
547 $style->padding_left,
548 $style->width,
549 $style->padding_right
550 ],
551 $cb["w"]
552 );
553
554 $h = (float)$style->length_in_pt(
555 [
556 $style->padding_top,
557 $style->padding_bottom,
558 $style->length_in_pt($style->height, $cb["h"])
559 ],
560 $cb["w"]
561 );
562
563 return [0 => $x, "x" => $x,
564 1 => $y, "y" => $y,
565 2 => $w, "w" => $w,
566 3 => $h, "h" => $h];
567 }
568
569 /**
570 * Return the border box of the frame.
571 *
572 * Width and height might be reported as 0 if they have not been resolved
573 * yet.
574 *
575 * @return float[]
576 */
577 public function get_border_box(): array
578 {
579 $style = $this->_style;
580 $cb = $this->_containing_block;
581
582 $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
583
584 $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
585
586 $w = (float)$style->length_in_pt(
587 [
588 $style->border_left_width,
589 $style->padding_left,
590 $style->width,
591 $style->padding_right,
592 $style->border_right_width
593 ],
594 $cb["w"]
595 );
596
597 $h = (float)$style->length_in_pt(
598 [
599 $style->border_top_width,
600 $style->padding_top,
601 $style->padding_bottom,
602 $style->border_bottom_width,
603 $style->length_in_pt($style->height, $cb["h"])
604 ],
605 $cb["w"]
606 );
607
608 return [0 => $x, "x" => $x,
609 1 => $y, "y" => $y,
610 2 => $w, "w" => $w,
611 3 => $h, "h" => $h];
612 }
613
614 /**
615 * @param float|null $opacity
616 *
617 * @return float
618 */
619 public function get_opacity(?float $opacity = null): float
620 {
621 if ($opacity !== null) {
622 $this->set_opacity($opacity);
623 }
624
625 return $this->_opacity;
626 }
627
628 /**
629 * @return LineBox|null
630 */
631 public function &get_containing_line()
632 {
633 return $this->_containing_line;
634 }
635
636 //........................................................................
637 // Set methods
638
639 /**
640 * @param int $id
641 */
642 public function set_id($id)
643 {
644 $this->_id = $id;
645
646 // We can only set attributes of DOMElement objects (nodeType == 1).
647 // Since these are the only objects that we can assign CSS rules to,
648 // this shortcoming is okay.
649 if ($this->_node->nodeType == XML_ELEMENT_NODE) {
650 $this->_node->setAttribute("frame_id", $id);
651 }
652 }
653
654 /**
655 * @param Style $style
656 */
657 public function set_style(Style $style): void
658 {
659 // $style->set_frame($this);
660 $this->_style = $style;
661 }
662
663 /**
664 * @param FrameDecorator\AbstractFrameDecorator $decorator
665 */
666 public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
667 {
668 $this->_decorator = $decorator;
669 }
670
671 /**
672 * @param float|float[]|null $x
673 * @param float|null $y
674 * @param float|null $w
675 * @param float|null $h
676 */
677 public function set_containing_block($x = null, $y = null, $w = null, $h = null)
678 {
679 if (is_array($x)) {
680 foreach ($x as $key => $val) {
681 $$key = $val;
682 }
683 }
684
685 if (is_numeric($x)) {
686 $this->_containing_block["x"] = $x;
687 }
688
689 if (is_numeric($y)) {
690 $this->_containing_block["y"] = $y;
691 }
692
693 if (is_numeric($w)) {
694 $this->_containing_block["w"] = $w;
695 }
696
697 if (is_numeric($h)) {
698 $this->_containing_block["h"] = $h;
699 }
700 }
701
702 /**
703 * @param float|float[]|null $x
704 * @param float|null $y
705 */
706 public function set_position($x = null, $y = null)
707 {
708 if (is_array($x)) {
709 list($x, $y) = [$x["x"], $x["y"]];
710 }
711
712 if (is_numeric($x)) {
713 $this->_position["x"] = $x;
714 }
715
716 if (is_numeric($y)) {
717 $this->_position["y"] = $y;
718 }
719 }
720
721 /**
722 * @param float $opacity
723 */
724 public function set_opacity(float $opacity): void
725 {
726 $parent = $this->get_parent();
727 $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
728 $this->_opacity = $base_opacity * $opacity;
729 }
730
731 /**
732 * @param LineBox $line
733 */
734 public function set_containing_line(LineBox $line)
735 {
736 $this->_containing_line = $line;
737 }
738
739 /**
740 * Indicates if the margin height is auto sized
741 *
742 * @return bool
743 */
744 public function is_auto_height()
745 {
746 $style = $this->_style;
747
748 return in_array(
749 "auto",
750 [
751 $style->height,
752 $style->margin_top,
753 $style->margin_bottom,
754 $style->border_top_width,
755 $style->border_bottom_width,
756 $style->padding_top,
757 $style->padding_bottom,
758 $this->_containing_block["h"]
759 ],
760 true
761 );
762 }
763
764 /**
765 * Indicates if the margin width is auto sized
766 *
767 * @return bool
768 */
769 public function is_auto_width()
770 {
771 $style = $this->_style;
772
773 return in_array(
774 "auto",
775 [
776 $style->width,
777 $style->margin_left,
778 $style->margin_right,
779 $style->border_left_width,
780 $style->border_right_width,
781 $style->padding_left,
782 $style->padding_right,
783 $this->_containing_block["w"]
784 ],
785 true
786 );
787 }
788
789 /**
790 * Tells if the frame is a text node
791 *
792 * @return bool
793 */
794 public function is_text_node(): bool
795 {
796 if (isset($this->_is_cache["text_node"])) {
797 return $this->_is_cache["text_node"];
798 }
799
800 return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
801 }
802
803 /**
804 * @return bool
805 */
806 public function is_positioned(): bool
807 {
808 if (isset($this->_is_cache["positioned"])) {
809 return $this->_is_cache["positioned"];
810 }
811
812 $position = $this->get_style()->position;
813
814 return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
815 }
816
817 /**
818 * @return bool
819 */
820 public function is_absolute(): bool
821 {
822 if (isset($this->_is_cache["absolute"])) {
823 return $this->_is_cache["absolute"];
824 }
825
826 return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
827 }
828
829 /**
830 * Whether the frame is a block container.
831 *
832 * @return bool
833 */
834 public function is_block(): bool
835 {
836 if (isset($this->_is_cache["block"])) {
837 return $this->_is_cache["block"];
838 }
839
840 return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
841 }
842
843 /**
844 * Whether the frame has a block-level display type.
845 *
846 * @return bool
847 */
848 public function is_block_level(): bool
849 {
850 if (isset($this->_is_cache["block_level"])) {
851 return $this->_is_cache["block_level"];
852 }
853
854 $display = $this->get_style()->display;
855
856 return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true);
857 }
858
859 /**
860 * Whether the frame has an inline-level display type.
861 *
862 * @return bool
863 */
864 public function is_inline_level(): bool
865 {
866 if (isset($this->_is_cache["inline_level"])) {
867 return $this->_is_cache["inline_level"];
868 }
869
870 $display = $this->get_style()->display;
871
872 return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
873 }
874
875 /**
876 * @return bool
877 */
878 public function is_in_flow(): bool
879 {
880 if (isset($this->_is_cache["in_flow"])) {
881 return $this->_is_cache["in_flow"];
882 }
883
884 return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
885 }
886
887 /**
888 * @return bool
889 */
890 public function is_pre(): bool
891 {
892 if (isset($this->_is_cache["pre"])) {
893 return $this->_is_cache["pre"];
894 }
895
896 $white_space = $this->get_style()->white_space;
897
898 return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true);
899 }
900
901 /**
902 * @return bool
903 */
904 public function is_table(): bool
905 {
906 if (isset($this->_is_cache["table"])) {
907 return $this->_is_cache["table"];
908 }
909
910 $display = $this->get_style()->display;
911
912 return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
913 }
914
915
916 /**
917 * Inserts a new child at the beginning of the Frame
918 *
919 * @param Frame $child The new Frame to insert
920 * @param bool $update_node Whether or not to update the DOM
921 */
922 public function prepend_child(Frame $child, $update_node = true)
923 {
924 if ($update_node) {
925 $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
926 }
927
928 // Remove the child from its parent
929 if ($child->_parent) {
930 $child->_parent->remove_child($child, false);
931 }
932
933 $child->_parent = $this;
934 $child->_prev_sibling = null;
935
936 // Handle the first child
937 if (!$this->_first_child) {
938 $this->_first_child = $child;
939 $this->_last_child = $child;
940 $child->_next_sibling = null;
941 } else {
942 $this->_first_child->_prev_sibling = $child;
943 $child->_next_sibling = $this->_first_child;
944 $this->_first_child = $child;
945 }
946 }
947
948 /**
949 * Inserts a new child at the end of the Frame
950 *
951 * @param Frame $child The new Frame to insert
952 * @param bool $update_node Whether or not to update the DOM
953 */
954 public function append_child(Frame $child, $update_node = true)
955 {
956 if ($update_node) {
957 $this->_node->appendChild($child->_node);
958 }
959
960 // Remove the child from its parent
961 if ($child->_parent) {
962 $child->_parent->remove_child($child, false);
963 }
964
965 $child->_parent = $this;
966 $decorator = $child->get_decorator();
967 // force an update to the cached parent
968 if ($decorator !== null) {
969 $decorator->get_parent(false);
970 }
971 $child->_next_sibling = null;
972
973 // Handle the first child
974 if (!$this->_last_child) {
975 $this->_first_child = $child;
976 $this->_last_child = $child;
977 $child->_prev_sibling = null;
978 } else {
979 $this->_last_child->_next_sibling = $child;
980 $child->_prev_sibling = $this->_last_child;
981 $this->_last_child = $child;
982 }
983 }
984
985 /**
986 * Inserts a new child immediately before the specified frame
987 *
988 * @param Frame $new_child The new Frame to insert
989 * @param Frame $ref The Frame after the new Frame
990 * @param bool $update_node Whether or not to update the DOM
991 *
992 * @throws Exception
993 */
994 public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
995 {
996 if ($ref === $this->_first_child) {
997 $this->prepend_child($new_child, $update_node);
998
999 return;
1000 }
1001
1002 if (is_null($ref)) {
1003 $this->append_child($new_child, $update_node);
1004
1005 return;
1006 }
1007
1008 if ($ref->_parent !== $this) {
1009 throw new Exception("Reference child is not a child of this node.");
1010 }
1011
1012 // Update the node
1013 if ($update_node) {
1014 $this->_node->insertBefore($new_child->_node, $ref->_node);
1015 }
1016
1017 // Remove the child from its parent
1018 if ($new_child->_parent) {
1019 $new_child->_parent->remove_child($new_child, false);
1020 }
1021
1022 $new_child->_parent = $this;
1023 $new_child->_next_sibling = $ref;
1024 $new_child->_prev_sibling = $ref->_prev_sibling;
1025
1026 if ($ref->_prev_sibling) {
1027 $ref->_prev_sibling->_next_sibling = $new_child;
1028 }
1029
1030 $ref->_prev_sibling = $new_child;
1031 }
1032
1033 /**
1034 * Inserts a new child immediately after the specified frame
1035 *
1036 * @param Frame $new_child The new Frame to insert
1037 * @param Frame $ref The Frame before the new Frame
1038 * @param bool $update_node Whether or not to update the DOM
1039 *
1040 * @throws Exception
1041 */
1042 public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
1043 {
1044 if ($ref === $this->_last_child) {
1045 $this->append_child($new_child, $update_node);
1046
1047 return;
1048 }
1049
1050 if (is_null($ref)) {
1051 $this->prepend_child($new_child, $update_node);
1052
1053 return;
1054 }
1055
1056 if ($ref->_parent !== $this) {
1057 throw new Exception("Reference child is not a child of this node.");
1058 }
1059
1060 // Update the node
1061 if ($update_node) {
1062 if ($ref->_next_sibling) {
1063 $next_node = $ref->_next_sibling->_node;
1064 $this->_node->insertBefore($new_child->_node, $next_node);
1065 } else {
1066 $new_child->_node = $this->_node->appendChild($new_child->_node);
1067 }
1068 }
1069
1070 // Remove the child from its parent
1071 if ($new_child->_parent) {
1072 $new_child->_parent->remove_child($new_child, false);
1073 }
1074
1075 $new_child->_parent = $this;
1076 $new_child->_prev_sibling = $ref;
1077 $new_child->_next_sibling = $ref->_next_sibling;
1078
1079 if ($ref->_next_sibling) {
1080 $ref->_next_sibling->_prev_sibling = $new_child;
1081 }
1082
1083 $ref->_next_sibling = $new_child;
1084 }
1085
1086 /**
1087 * Remove a child frame
1088 *
1089 * @param Frame $child
1090 * @param bool $update_node Whether or not to remove the DOM node
1091 *
1092 * @throws Exception
1093 * @return Frame The removed child frame
1094 */
1095 public function remove_child(Frame $child, $update_node = true)
1096 {
1097 if ($child->_parent !== $this) {
1098 throw new Exception("Child not found in this frame");
1099 }
1100
1101 if ($update_node) {
1102 $this->_node->removeChild($child->_node);
1103 }
1104
1105 if ($child === $this->_first_child) {
1106 $this->_first_child = $child->_next_sibling;
1107 }
1108
1109 if ($child === $this->_last_child) {
1110 $this->_last_child = $child->_prev_sibling;
1111 }
1112
1113 if ($child->_prev_sibling) {
1114 $child->_prev_sibling->_next_sibling = $child->_next_sibling;
1115 }
1116
1117 if ($child->_next_sibling) {
1118 $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
1119 }
1120
1121 $child->_next_sibling = null;
1122 $child->_prev_sibling = null;
1123 $child->_parent = null;
1124
1125 return $child;
1126 }
1127
1128 //........................................................................
1129
1130 // Debugging function:
1131 /**
1132 * @return string
1133 */
1134 public function __toString()
1135 {
1136 // Skip empty text frames
1137 // if ( $this->is_text_node() &&
1138 // preg_replace("/\s/", "", $this->_node->data) === "" )
1139 // return "";
1140
1141
1142 $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
1143 //$str .= spl_object_hash($this->_node) . "<br/>";
1144 $str .= "Id: " . $this->get_id() . "<br/>";
1145 $str .= "Class: " . get_class($this) . "<br/>";
1146
1147 if ($this->is_text_node()) {
1148 $tmp = htmlspecialchars($this->_node->nodeValue);
1149 $str .= "<pre>'" . mb_substr($tmp, 0, 70) .
1150 (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
1151 } elseif ($css_class = $this->_node->getAttribute("class")) {
1152 $str .= "CSS class: '$css_class'<br/>";
1153 }
1154
1155 if ($this->_parent) {
1156 $str .= "\nParent:" . $this->_parent->_node->nodeName .
1157 " (" . spl_object_hash($this->_parent->_node) . ") " .
1158 "<br/>";
1159 }
1160
1161 if ($this->_prev_sibling) {
1162 $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
1163 " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
1164 "<br/>";
1165 }
1166
1167 if ($this->_next_sibling) {
1168 $str .= "Next: " . $this->_next_sibling->_node->nodeName .
1169 " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
1170 "<br/>";
1171 }
1172
1173 $d = $this->get_decorator();
1174 while ($d && $d != $d->get_decorator()) {
1175 $str .= "Decorator: " . get_class($d) . "<br/>";
1176 $d = $d->get_decorator();
1177 }
1178
1179 $str .= "Position: " . Helpers::pre_r($this->_position, true);
1180 $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
1181 $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
1182 $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
1183
1184 $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
1185
1186 if ($this->_decorator instanceof FrameDecorator\Block) {
1187 $str .= "Lines:<pre>";
1188 foreach ($this->_decorator->get_line_boxes() as $line) {
1189 foreach ($line->get_frames() as $frame) {
1190 if ($frame instanceof FrameDecorator\Text) {
1191 $str .= "\ntext: ";
1192 $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
1193 } else {
1194 $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
1195 }
1196 }
1197
1198 $str .=
1199 "\ny => " . $line->y . "\n" .
1200 "w => " . $line->w . "\n" .
1201 "h => " . $line->h . "\n" .
1202 "left => " . $line->left . "\n" .
1203 "right => " . $line->right . "\n";
1204 }
1205 $str .= "</pre>";
1206 }
1207
1208 $str .= "\n";
1209 if (php_sapi_name() === "cli") {
1210 $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
1211 ["\n", "", ""],
1212 $str));
1213 }
1214
1215 return $str;
1216 }
1217 }
1218