PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 All 166 releases
woocommerce-pos / vendor_prefixed / dompdf / dompdf / src / Frame.php

Frame.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.21, at vendor_prefixed/dompdf/dompdf/src/Frame.php

913 lines 28.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @package dompdf
5 * @link https://github.com/dompdf/dompdf
6 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 */
8 namespace WCPOS\Vendor\Dompdf;
9
10 use WCPOS\Vendor\Dompdf\Css\Style;
11 use WCPOS\Vendor\Dompdf\Frame\FrameListIterator;
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 * The DOMElement or DOMText object this frame represents
29 *
30 * @var \DOMElement|\DOMText
31 */
32 protected $_node;
33 /**
34 * Unique identifier for this frame. Used to reference this frame
35 * via the node.
36 *
37 * @var int
38 */
39 protected $_id;
40 /**
41 * Unique id counter
42 *
43 * @var int
44 */
45 public static $ID_COUNTER = 0;
46 /*protected*/
47 /**
48 * This frame's calculated style
49 *
50 * @var Style|null
51 */
52 protected $_style;
53 /**
54 * This frame's parent in the document tree.
55 *
56 * @var Frame|null
57 */
58 protected $_parent;
59 /**
60 * This frame's first child. All children are handled as a
61 * doubly-linked list.
62 *
63 * @var Frame|null
64 */
65 protected $_first_child;
66 /**
67 * This frame's last child.
68 *
69 * @var Frame|null
70 */
71 protected $_last_child;
72 /**
73 * This frame's previous sibling in the document tree.
74 *
75 * @var Frame|null
76 */
77 protected $_prev_sibling;
78 /**
79 * This frame's next sibling in the document tree.
80 *
81 * @var Frame|null
82 */
83 protected $_next_sibling;
84 /**
85 * This frame's containing block (used in layout): array(x, y, w, h)
86 *
87 * @var float[]
88 */
89 protected $_containing_block;
90 /**
91 * Position on the page of the top-left corner of the margin box of
92 * this frame: array(x,y)
93 *
94 * @var float[]
95 */
96 protected $_position;
97 /**
98 * Absolute opacity of this frame
99 *
100 * @var float
101 */
102 protected $_opacity;
103 /**
104 * This frame's decorator
105 *
106 * @var FrameDecorator\AbstractFrameDecorator
107 */
108 protected $_decorator;
109 /**
110 * This frame's containing line box
111 *
112 * @var LineBox|null
113 */
114 protected $_containing_line;
115 /**
116 * @var array
117 */
118 protected $_is_cache = [];
119 /**
120 * Tells whether the frame was already pushed to the next page
121 *
122 * @var bool
123 */
124 public $_already_pushed = \false;
125 /**
126 * @var bool
127 */
128 public $_float_next_line = \false;
129 /**
130 * @var int
131 */
132 public static $_ws_state = self::WS_SPACE;
133 /**
134 * Class constructor
135 *
136 * @param \DOMNode $node the DOMNode this frame represents
137 */
138 public function __construct(\DOMNode $node)
139 {
140 $this->_node = $node;
141 $this->_parent = null;
142 $this->_first_child = null;
143 $this->_last_child = null;
144 $this->_prev_sibling = $this->_next_sibling = null;
145 $this->_style = null;
146 $this->_containing_block = ["x" => null, "y" => null, "w" => null, "h" => null];
147 $this->_containing_block[0] =& $this->_containing_block["x"];
148 $this->_containing_block[1] =& $this->_containing_block["y"];
149 $this->_containing_block[2] =& $this->_containing_block["w"];
150 $this->_containing_block[3] =& $this->_containing_block["h"];
151 $this->_position = ["x" => null, "y" => null];
152 $this->_position[0] =& $this->_position["x"];
153 $this->_position[1] =& $this->_position["y"];
154 $this->_opacity = 1.0;
155 $this->_decorator = null;
156 $this->set_id(self::$ID_COUNTER++);
157 }
158 /**
159 * WIP : preprocessing to remove all the unused whitespace
160 */
161 protected function ws_trim()
162 {
163 if ($this->ws_keep()) {
164 return;
165 }
166 if (self::$_ws_state === self::WS_SPACE) {
167 $node = $this->_node;
168 if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
169 $node->nodeValue = \preg_replace("/[ \t\r\n\f]+/u", " ", \trim($node->nodeValue));
170 self::$_ws_state = self::WS_TEXT;
171 }
172 }
173 }
174 /**
175 * @return bool
176 */
177 protected function ws_keep()
178 {
179 $whitespace = $this->get_style()->white_space;
180 return \in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
181 }
182 /**
183 * @return bool
184 */
185 protected function ws_is_text()
186 {
187 $node = $this->get_node();
188 if ($node->nodeName === "img") {
189 return \true;
190 }
191 if (!$this->is_in_flow()) {
192 return \false;
193 }
194 if ($this->is_text_node()) {
195 return \trim($node->nodeValue) !== "";
196 }
197 return \true;
198 }
199 /**
200 * "Destructor": forcibly free all references held by this frame
201 *
202 * @param bool $recursive if true, call dispose on all children
203 */
204 public function dispose($recursive = \false)
205 {
206 if ($recursive) {
207 while ($child = $this->_first_child) {
208 $child->dispose(\true);
209 }
210 }
211 // Remove this frame from the tree
212 if ($this->_prev_sibling) {
213 $this->_prev_sibling->_next_sibling = $this->_next_sibling;
214 }
215 if ($this->_next_sibling) {
216 $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
217 }
218 if ($this->_parent && $this->_parent->_first_child === $this) {
219 $this->_parent->_first_child = $this->_next_sibling;
220 }
221 if ($this->_parent && $this->_parent->_last_child === $this) {
222 $this->_parent->_last_child = $this->_prev_sibling;
223 }
224 if ($this->_parent) {
225 $this->_parent->get_node()->removeChild($this->_node);
226 }
227 $this->_style = null;
228 unset($this->_style);
229 }
230 /**
231 * Re-initialize the frame
232 */
233 public function reset()
234 {
235 $this->_position["x"] = null;
236 $this->_position["y"] = null;
237 $this->_containing_block["x"] = null;
238 $this->_containing_block["y"] = null;
239 $this->_containing_block["w"] = null;
240 $this->_containing_block["h"] = null;
241 $this->_style->reset();
242 }
243 /**
244 * @return \DOMElement|\DOMText
245 */
246 public function get_node()
247 {
248 return $this->_node;
249 }
250 /**
251 * @return int
252 */
253 public function get_id()
254 {
255 return $this->_id;
256 }
257 /**
258 * @return Style
259 */
260 public function get_style()
261 {
262 return $this->_style;
263 }
264 /**
265 * @deprecated
266 * @return Style
267 */
268 public function get_original_style()
269 {
270 return $this->_style;
271 }
272 /**
273 * @return Frame
274 */
275 public function get_parent()
276 {
277 return $this->_parent;
278 }
279 /**
280 * @return FrameDecorator\AbstractFrameDecorator
281 */
282 public function get_decorator()
283 {
284 return $this->_decorator;
285 }
286 /**
287 * @return Frame
288 */
289 public function get_first_child()
290 {
291 return $this->_first_child;
292 }
293 /**
294 * @return Frame
295 */
296 public function get_last_child()
297 {
298 return $this->_last_child;
299 }
300 /**
301 * @return Frame
302 */
303 public function get_prev_sibling()
304 {
305 return $this->_prev_sibling;
306 }
307 /**
308 * @return Frame
309 */
310 public function get_next_sibling()
311 {
312 return $this->_next_sibling;
313 }
314 /**
315 * @return FrameListIterator
316 */
317 public function get_children() : FrameListIterator
318 {
319 return new FrameListIterator($this);
320 }
321 // Layout property accessors
322 /**
323 * Containing block dimensions
324 *
325 * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
326 *
327 * @return float[]|float
328 */
329 public function get_containing_block($i = null)
330 {
331 if (isset($i)) {
332 return $this->_containing_block[$i];
333 }
334 return $this->_containing_block;
335 }
336 /**
337 * Block position
338 *
339 * @param string|null $i The key of the wanted position value (x, y)
340 *
341 * @return float[]|float
342 */
343 public function get_position($i = null)
344 {
345 if (isset($i)) {
346 return $this->_position[$i];
347 }
348 return $this->_position;
349 }
350 //........................................................................
351 /**
352 * Return the width of the margin box of the frame, in pt. Meaningless
353 * unless the width has been calculated properly.
354 *
355 * @return float
356 */
357 public function get_margin_width() : float
358 {
359 $style = $this->_style;
360 return (float) $style->length_in_pt([$style->width, $style->margin_left, $style->margin_right, $style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right], $this->_containing_block["w"]);
361 }
362 /**
363 * Return the height of the margin box of the frame, in pt. Meaningless
364 * unless the height has been calculated properly.
365 *
366 * @return float
367 */
368 public function get_margin_height() : float
369 {
370 $style = $this->_style;
371 return (float) $style->length_in_pt([$style->height, (float) $style->length_in_pt([$style->border_top_width, $style->border_bottom_width, $style->margin_top, $style->margin_bottom, $style->padding_top, $style->padding_bottom], $this->_containing_block["w"])], $this->_containing_block["h"]);
372 }
373 /**
374 * Return the content box (x,y,w,h) of the frame.
375 *
376 * Width and height might be reported as 0 if they have not been resolved
377 * yet.
378 *
379 * @return float[]
380 */
381 public function get_content_box() : array
382 {
383 $style = $this->_style;
384 $cb = $this->_containing_block;
385 $x = $this->_position["x"] + (float) $style->length_in_pt([$style->margin_left, $style->border_left_width, $style->padding_left], $cb["w"]);
386 $y = $this->_position["y"] + (float) $style->length_in_pt([$style->margin_top, $style->border_top_width, $style->padding_top], $cb["w"]);
387 $w = (float) $style->length_in_pt($style->width, $cb["w"]);
388 $h = (float) $style->length_in_pt($style->height, $cb["h"]);
389 return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h];
390 }
391 /**
392 * Return the padding box (x,y,w,h) of the frame.
393 *
394 * Width and height might be reported as 0 if they have not been resolved
395 * yet.
396 *
397 * @return float[]
398 */
399 public function get_padding_box() : array
400 {
401 $style = $this->_style;
402 $cb = $this->_containing_block;
403 $x = $this->_position["x"] + (float) $style->length_in_pt([$style->margin_left, $style->border_left_width], $cb["w"]);
404 $y = $this->_position["y"] + (float) $style->length_in_pt([$style->margin_top, $style->border_top_width], $cb["h"]);
405 $w = (float) $style->length_in_pt([$style->padding_left, $style->width, $style->padding_right], $cb["w"]);
406 $h = (float) $style->length_in_pt([$style->padding_top, $style->padding_bottom, $style->length_in_pt($style->height, $cb["h"])], $cb["w"]);
407 return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h];
408 }
409 /**
410 * Return the border box of the frame.
411 *
412 * Width and height might be reported as 0 if they have not been resolved
413 * yet.
414 *
415 * @return float[]
416 */
417 public function get_border_box() : array
418 {
419 $style = $this->_style;
420 $cb = $this->_containing_block;
421 $x = $this->_position["x"] + (float) $style->length_in_pt($style->margin_left, $cb["w"]);
422 $y = $this->_position["y"] + (float) $style->length_in_pt($style->margin_top, $cb["w"]);
423 $w = (float) $style->length_in_pt([$style->border_left_width, $style->padding_left, $style->width, $style->padding_right, $style->border_right_width], $cb["w"]);
424 $h = (float) $style->length_in_pt([$style->border_top_width, $style->padding_top, $style->padding_bottom, $style->border_bottom_width, $style->length_in_pt($style->height, $cb["h"])], $cb["w"]);
425 return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h];
426 }
427 /**
428 * @param float|null $opacity
429 *
430 * @return float
431 */
432 public function get_opacity(?float $opacity = null) : float
433 {
434 if ($opacity !== null) {
435 $this->set_opacity($opacity);
436 }
437 return $this->_opacity;
438 }
439 /**
440 * @return LineBox|null
441 */
442 public function &get_containing_line()
443 {
444 return $this->_containing_line;
445 }
446 //........................................................................
447 // Set methods
448 /**
449 * @param int $id
450 */
451 public function set_id($id)
452 {
453 $this->_id = $id;
454 // We can only set attributes of DOMElement objects (nodeType == 1).
455 // Since these are the only objects that we can assign CSS rules to,
456 // this shortcoming is okay.
457 if ($this->_node->nodeType == \XML_ELEMENT_NODE) {
458 $this->_node->setAttribute("frame_id", $id);
459 }
460 }
461 /**
462 * @param Style $style
463 */
464 public function set_style(Style $style) : void
465 {
466 // $style->set_frame($this);
467 $this->_style = $style;
468 }
469 /**
470 * @param FrameDecorator\AbstractFrameDecorator $decorator
471 */
472 public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
473 {
474 $this->_decorator = $decorator;
475 }
476 /**
477 * @param float|float[]|null $x
478 * @param float|null $y
479 * @param float|null $w
480 * @param float|null $h
481 */
482 public function set_containing_block($x = null, $y = null, $w = null, $h = null)
483 {
484 if (\is_array($x)) {
485 list($x, $y, $w, $h) = [$x["x"] ?? null, $x["y"] ?? null, $x["w"] ?? null, $x["h"] ?? null];
486 }
487 if (\is_numeric($x)) {
488 $this->_containing_block["x"] = $x;
489 }
490 if (\is_numeric($y)) {
491 $this->_containing_block["y"] = $y;
492 }
493 if (\is_numeric($w)) {
494 $this->_containing_block["w"] = $w;
495 }
496 if (\is_numeric($h)) {
497 $this->_containing_block["h"] = $h;
498 }
499 }
500 /**
501 * @param float|float[]|null $x
502 * @param float|null $y
503 */
504 public function set_position($x = null, $y = null)
505 {
506 if (\is_array($x)) {
507 list($x, $y) = [$x["x"], $x["y"]];
508 }
509 if (\is_numeric($x)) {
510 $this->_position["x"] = $x;
511 }
512 if (\is_numeric($y)) {
513 $this->_position["y"] = $y;
514 }
515 }
516 /**
517 * @param float $opacity
518 */
519 public function set_opacity(float $opacity) : void
520 {
521 $parent = $this->get_parent();
522 $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
523 $this->_opacity = $base_opacity * $opacity;
524 }
525 /**
526 * @param LineBox $line
527 */
528 public function set_containing_line(LineBox $line)
529 {
530 $this->_containing_line = $line;
531 }
532 /**
533 * Indicates if the margin height is auto sized
534 *
535 * @return bool
536 */
537 public function is_auto_height()
538 {
539 $style = $this->_style;
540 return \in_array("auto", [$style->height, $style->margin_top, $style->margin_bottom, $style->border_top_width, $style->border_bottom_width, $style->padding_top, $style->padding_bottom, $this->_containing_block["h"]], \true);
541 }
542 /**
543 * Indicates if the margin width is auto sized
544 *
545 * @return bool
546 */
547 public function is_auto_width()
548 {
549 $style = $this->_style;
550 return \in_array("auto", [$style->width, $style->margin_left, $style->margin_right, $style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right, $this->_containing_block["w"]], \true);
551 }
552 /**
553 * Tells if the frame is a text node
554 *
555 * @return bool
556 */
557 public function is_text_node() : bool
558 {
559 if (isset($this->_is_cache["text_node"])) {
560 return $this->_is_cache["text_node"];
561 }
562 return $this->_is_cache["text_node"] = $this->get_node()->nodeName === "#text";
563 }
564 /**
565 * @return bool
566 */
567 public function is_positioned() : bool
568 {
569 if (isset($this->_is_cache["positioned"])) {
570 return $this->_is_cache["positioned"];
571 }
572 $position = $this->get_style()->position;
573 return $this->_is_cache["positioned"] = \in_array($position, Style::POSITIONED_TYPES, \true);
574 }
575 /**
576 * @return bool
577 */
578 public function is_absolute() : bool
579 {
580 if (isset($this->_is_cache["absolute"])) {
581 return $this->_is_cache["absolute"];
582 }
583 return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
584 }
585 /**
586 * Whether the frame is a block container.
587 *
588 * @return bool
589 */
590 public function is_block() : bool
591 {
592 if (isset($this->_is_cache["block"])) {
593 return $this->_is_cache["block"];
594 }
595 return $this->_is_cache["block"] = \in_array($this->get_style()->display, Style::BLOCK_TYPES, \true);
596 }
597 /**
598 * Whether the frame has a block-level display type.
599 *
600 * @return bool
601 */
602 public function is_block_level() : bool
603 {
604 if (isset($this->_is_cache["block_level"])) {
605 return $this->_is_cache["block_level"];
606 }
607 $display = $this->get_style()->display;
608 return $this->_is_cache["block_level"] = \in_array($display, Style::BLOCK_LEVEL_TYPES, \true);
609 }
610 /**
611 * Whether the frame has an inline-level display type.
612 *
613 * @return bool
614 */
615 public function is_inline_level() : bool
616 {
617 if (isset($this->_is_cache["inline_level"])) {
618 return $this->_is_cache["inline_level"];
619 }
620 $display = $this->get_style()->display;
621 return $this->_is_cache["inline_level"] = \in_array($display, Style::INLINE_LEVEL_TYPES, \true);
622 }
623 /**
624 * @return bool
625 */
626 public function is_in_flow() : bool
627 {
628 if (isset($this->_is_cache["in_flow"])) {
629 return $this->_is_cache["in_flow"];
630 }
631 return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
632 }
633 /**
634 * @return bool
635 */
636 public function is_pre() : bool
637 {
638 if (isset($this->_is_cache["pre"])) {
639 return $this->_is_cache["pre"];
640 }
641 $white_space = $this->get_style()->white_space;
642 return $this->_is_cache["pre"] = \in_array($white_space, ["pre", "pre-wrap"], \true);
643 }
644 /**
645 * @return bool
646 */
647 public function is_table() : bool
648 {
649 if (isset($this->_is_cache["table"])) {
650 return $this->_is_cache["table"];
651 }
652 $display = $this->get_style()->display;
653 return $this->_is_cache["table"] = \in_array($display, Style::TABLE_TYPES, \true);
654 }
655 /**
656 * Inserts a new child at the beginning of the Frame
657 *
658 * @param Frame $child The new Frame to insert
659 * @param bool $update_node Whether or not to update the DOM
660 */
661 public function prepend_child(Frame $child, $update_node = \true)
662 {
663 if ($update_node) {
664 $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
665 }
666 // Remove the child from its parent
667 if ($child->_parent) {
668 $child->_parent->remove_child($child, \false);
669 }
670 $child->_parent = $this;
671 $decorator = $child->get_decorator();
672 // force an update to the cached parent
673 if ($decorator !== null) {
674 $decorator->get_parent(\false);
675 }
676 $child->_prev_sibling = null;
677 // Handle the first child
678 if (!$this->_first_child) {
679 $this->_first_child = $child;
680 $this->_last_child = $child;
681 $child->_next_sibling = null;
682 } else {
683 $this->_first_child->_prev_sibling = $child;
684 $child->_next_sibling = $this->_first_child;
685 $this->_first_child = $child;
686 }
687 }
688 /**
689 * Inserts a new child at the end of the Frame
690 *
691 * @param Frame $child The new Frame to insert
692 * @param bool $update_node Whether or not to update the DOM
693 */
694 public function append_child(Frame $child, $update_node = \true)
695 {
696 if ($update_node) {
697 $this->_node->appendChild($child->_node);
698 }
699 // Remove the child from its parent
700 if ($child->_parent) {
701 $child->_parent->remove_child($child, \false);
702 }
703 $child->_parent = $this;
704 $decorator = $child->get_decorator();
705 // force an update to the cached parent
706 if ($decorator !== null) {
707 $decorator->get_parent(\false);
708 }
709 $child->_next_sibling = null;
710 // Handle the first child
711 if (!$this->_last_child) {
712 $this->_first_child = $child;
713 $this->_last_child = $child;
714 $child->_prev_sibling = null;
715 } else {
716 $this->_last_child->_next_sibling = $child;
717 $child->_prev_sibling = $this->_last_child;
718 $this->_last_child = $child;
719 }
720 }
721 /**
722 * Inserts a new child immediately before the specified frame
723 *
724 * @param Frame $new_child The new Frame to insert
725 * @param Frame $ref The Frame after the new Frame
726 * @param bool $update_node Whether or not to update the DOM
727 *
728 * @throws Exception
729 */
730 public function insert_child_before(Frame $new_child, Frame $ref, $update_node = \true)
731 {
732 if ($ref === $this->_first_child) {
733 $this->prepend_child($new_child, $update_node);
734 return;
735 }
736 if (\is_null($ref)) {
737 $this->append_child($new_child, $update_node);
738 return;
739 }
740 if ($ref->_parent !== $this) {
741 throw new Exception("Reference child is not a child of this node.");
742 }
743 // Update the node
744 if ($update_node) {
745 $this->_node->insertBefore($new_child->_node, $ref->_node);
746 }
747 // Remove the child from its parent
748 if ($new_child->_parent) {
749 $new_child->_parent->remove_child($new_child, \false);
750 }
751 $new_child->_parent = $this;
752 $decorator = $new_child->get_decorator();
753 // force an update to the cached parent
754 if ($decorator !== null) {
755 $decorator->get_parent(\false);
756 }
757 $new_child->_next_sibling = $ref;
758 $new_child->_prev_sibling = $ref->_prev_sibling;
759 if ($ref->_prev_sibling) {
760 $ref->_prev_sibling->_next_sibling = $new_child;
761 }
762 $ref->_prev_sibling = $new_child;
763 }
764 /**
765 * Inserts a new child immediately after the specified frame
766 *
767 * @param Frame $new_child The new Frame to insert
768 * @param Frame $ref The Frame before the new Frame
769 * @param bool $update_node Whether or not to update the DOM
770 *
771 * @throws Exception
772 */
773 public function insert_child_after(Frame $new_child, Frame $ref, $update_node = \true)
774 {
775 if ($ref === $this->_last_child) {
776 $this->append_child($new_child, $update_node);
777 return;
778 }
779 if (\is_null($ref)) {
780 $this->prepend_child($new_child, $update_node);
781 return;
782 }
783 if ($ref->_parent !== $this) {
784 throw new Exception("Reference child is not a child of this node.");
785 }
786 // Update the node
787 if ($update_node) {
788 if ($ref->_next_sibling) {
789 $next_node = $ref->_next_sibling->_node;
790 $this->_node->insertBefore($new_child->_node, $next_node);
791 } else {
792 $new_child->_node = $this->_node->appendChild($new_child->_node);
793 }
794 }
795 // Remove the child from its parent
796 if ($new_child->_parent) {
797 $new_child->_parent->remove_child($new_child, \false);
798 }
799 $new_child->_parent = $this;
800 $decorator = $new_child->get_decorator();
801 // force an update to the cached parent
802 if ($decorator !== null) {
803 $decorator->get_parent(\false);
804 }
805 $new_child->_prev_sibling = $ref;
806 $new_child->_next_sibling = $ref->_next_sibling;
807 if ($ref->_next_sibling) {
808 $ref->_next_sibling->_prev_sibling = $new_child;
809 }
810 $ref->_next_sibling = $new_child;
811 }
812 /**
813 * Remove a child frame
814 *
815 * @param Frame $child
816 * @param bool $update_node Whether or not to remove the DOM node
817 *
818 * @throws Exception
819 * @return Frame The removed child frame
820 */
821 public function remove_child(Frame $child, $update_node = \true)
822 {
823 if ($child->_parent !== $this) {
824 throw new Exception("Child not found in this frame");
825 }
826 if ($update_node) {
827 $this->_node->removeChild($child->_node);
828 }
829 if ($child === $this->_first_child) {
830 $this->_first_child = $child->_next_sibling;
831 }
832 if ($child === $this->_last_child) {
833 $this->_last_child = $child->_prev_sibling;
834 }
835 if ($child->_prev_sibling) {
836 $child->_prev_sibling->_next_sibling = $child->_next_sibling;
837 }
838 if ($child->_next_sibling) {
839 $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
840 }
841 $child->_next_sibling = null;
842 $child->_prev_sibling = null;
843 $child->_parent = null;
844 // Force an update to the cached decorator parent
845 $decorator = $child->get_decorator();
846 if ($decorator !== null) {
847 $decorator->get_parent(\false);
848 }
849 return $child;
850 }
851 //........................................................................
852 // Debugging function:
853 /**
854 * @return string
855 */
856 public function __toString()
857 {
858 // Skip empty text frames
859 // if ( $this->is_text_node() &&
860 // preg_replace("/\s/", "", $this->_node->data) === "" )
861 // return "";
862 $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
863 //$str .= spl_object_hash($this->_node) . "<br/>";
864 $str .= "Id: " . $this->get_id() . "<br/>";
865 $str .= "Class: " . \get_class($this) . "<br/>";
866 if ($this->is_text_node()) {
867 $tmp = \htmlspecialchars($this->_node->nodeValue);
868 $str .= "<pre>'" . \mb_substr($tmp, 0, 70, "UTF-8") . (\mb_strlen($tmp, "UTF-8") > 70 ? "..." : "") . "'</pre>";
869 } elseif ($css_class = $this->_node->getAttribute("class")) {
870 $str .= "CSS class: '{$css_class}'<br/>";
871 }
872 if ($this->_parent) {
873 $str .= "\nParent:" . $this->_parent->_node->nodeName . " (" . \spl_object_hash($this->_parent->_node) . ") " . "<br/>";
874 }
875 if ($this->_prev_sibling) {
876 $str .= "Prev: " . $this->_prev_sibling->_node->nodeName . " (" . \spl_object_hash($this->_prev_sibling->_node) . ") " . "<br/>";
877 }
878 if ($this->_next_sibling) {
879 $str .= "Next: " . $this->_next_sibling->_node->nodeName . " (" . \spl_object_hash($this->_next_sibling->_node) . ") " . "<br/>";
880 }
881 $d = $this->get_decorator();
882 while ($d && $d != $d->get_decorator()) {
883 $str .= "Decorator: " . \get_class($d) . "<br/>";
884 $d = $d->get_decorator();
885 }
886 $str .= "Position: " . Helpers::pre_r($this->_position, \true);
887 $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, \true);
888 $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), \true);
889 $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), \true);
890 $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
891 if ($this->_decorator instanceof FrameDecorator\Block) {
892 $str .= "Lines:<pre>";
893 foreach ($this->_decorator->get_line_boxes() as $line) {
894 foreach ($line->get_frames() as $frame) {
895 if ($frame instanceof FrameDecorator\Text) {
896 $str .= "\ntext: ";
897 $str .= "'" . \htmlspecialchars($frame->get_text()) . "'";
898 } else {
899 $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . \spl_object_hash($frame->get_node()) . ")";
900 }
901 }
902 $str .= "\ny => " . $line->y . "\n" . "w => " . $line->w . "\n" . "h => " . $line->h . "\n" . "left => " . $line->left . "\n" . "right => " . $line->right . "\n";
903 }
904 $str .= "</pre>";
905 }
906 $str .= "\n";
907 if (\php_sapi_name() === "cli") {
908 $str = \strip_tags(\str_replace(["<br/>", "<b>", "</b>"], ["\n", "", ""], $str));
909 }
910 return $str;
911 }
912 }
913