PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Frame.php

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