Frame.php 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  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 = [];
  128. /**
  129. * Tells whether 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 whether 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 = [
  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 = [
  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, ["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. // If this represents a generated node then child nodes represent generated content.
  273. // Remove the children since the content will be generated next time this frame is reflowed.
  274. if ($this->_node->nodeName === "dompdf_generated" && $this->_style->content != "normal") {
  275. foreach ($this->get_children() as $child) {
  276. $this->remove_child($child);
  277. }
  278. }
  279. }
  280. /**
  281. * @return \DOMElement|\DOMText
  282. */
  283. public function get_node()
  284. {
  285. return $this->_node;
  286. }
  287. /**
  288. * @return string
  289. */
  290. public function get_id()
  291. {
  292. return $this->_id;
  293. }
  294. /**
  295. * @return Style
  296. */
  297. public function get_style()
  298. {
  299. return $this->_style;
  300. }
  301. /**
  302. * @return Style
  303. */
  304. public function get_original_style()
  305. {
  306. return $this->_original_style;
  307. }
  308. /**
  309. * @return Frame
  310. */
  311. public function get_parent()
  312. {
  313. return $this->_parent;
  314. }
  315. /**
  316. * @return \Dompdf\FrameDecorator\AbstractFrameDecorator
  317. */
  318. public function get_decorator()
  319. {
  320. return $this->_decorator;
  321. }
  322. /**
  323. * @return Frame
  324. */
  325. public function get_first_child()
  326. {
  327. return $this->_first_child;
  328. }
  329. /**
  330. * @return Frame
  331. */
  332. public function get_last_child()
  333. {
  334. return $this->_last_child;
  335. }
  336. /**
  337. * @return Frame
  338. */
  339. public function get_prev_sibling()
  340. {
  341. return $this->_prev_sibling;
  342. }
  343. /**
  344. * @return Frame
  345. */
  346. public function get_next_sibling()
  347. {
  348. return $this->_next_sibling;
  349. }
  350. /**
  351. * @return FrameList|Frame[]
  352. */
  353. public function get_children()
  354. {
  355. if (isset($this->_frame_list)) {
  356. return $this->_frame_list;
  357. }
  358. $this->_frame_list = new FrameList($this);
  359. return $this->_frame_list;
  360. }
  361. // Layout property accessors
  362. /**
  363. * Containing block dimensions
  364. *
  365. * @param $i string The key of the wanted containing block's dimension (x, y, w, h)
  366. *
  367. * @return float[]|float
  368. */
  369. public function get_containing_block($i = null)
  370. {
  371. if (isset($i)) {
  372. return $this->_containing_block[$i];
  373. }
  374. return $this->_containing_block;
  375. }
  376. /**
  377. * Block position
  378. *
  379. * @param $i string The key of the wanted position value (x, y)
  380. *
  381. * @return array|float
  382. */
  383. public function get_position($i = null)
  384. {
  385. if (isset($i)) {
  386. return $this->_position[$i];
  387. }
  388. return $this->_position;
  389. }
  390. //........................................................................
  391. /**
  392. * Return the height of the margin box of the frame, in pt. Meaningless
  393. * unless the height has been calculated properly.
  394. *
  395. * @return float
  396. */
  397. public function get_margin_height()
  398. {
  399. $style = $this->_style;
  400. return (
  401. (float)$style->length_in_pt(
  402. [
  403. $style->height,
  404. (float)$style->length_in_pt(
  405. [
  406. $style->border_top_width,
  407. $style->border_bottom_width,
  408. $style->margin_top,
  409. $style->margin_bottom,
  410. $style->padding_top,
  411. $style->padding_bottom
  412. ], $this->_containing_block["w"]
  413. )
  414. ],
  415. $this->_containing_block["h"]
  416. )
  417. );
  418. }
  419. /**
  420. * Return the width of the margin box of the frame, in pt. Meaningless
  421. * unless the width has been calculated properly.
  422. *
  423. * @return float
  424. */
  425. public function get_margin_width()
  426. {
  427. $style = $this->_style;
  428. return (float)$style->length_in_pt([
  429. $style->width,
  430. $style->margin_left,
  431. $style->margin_right,
  432. $style->border_left_width,
  433. $style->border_right_width,
  434. $style->padding_left,
  435. $style->padding_right
  436. ], $this->_containing_block["w"]);
  437. }
  438. /**
  439. * @return float
  440. */
  441. public function get_break_margins()
  442. {
  443. $style = $this->_style;
  444. return (
  445. (float)$style->length_in_pt(
  446. [
  447. //$style->height,
  448. (float)$style->length_in_pt(
  449. [
  450. $style->border_top_width,
  451. $style->border_bottom_width,
  452. $style->margin_top,
  453. $style->margin_bottom,
  454. $style->padding_top,
  455. $style->padding_bottom
  456. ], $this->_containing_block["w"]
  457. )
  458. ],
  459. $this->_containing_block["h"]
  460. )
  461. );
  462. }
  463. /**
  464. * Return the content box (x,y,w,h) of the frame
  465. *
  466. * @return array
  467. */
  468. public function get_content_box()
  469. {
  470. $style = $this->_style;
  471. $cb = $this->_containing_block;
  472. $x = $this->_position["x"] +
  473. (float)$style->length_in_pt(
  474. [
  475. $style->margin_left,
  476. $style->border_left_width,
  477. $style->padding_left
  478. ],
  479. $cb["w"]
  480. );
  481. $y = $this->_position["y"] +
  482. (float)$style->length_in_pt(
  483. [
  484. $style->margin_top,
  485. $style->border_top_width,
  486. $style->padding_top
  487. ],
  488. $cb["w"]);
  489. $w = $style->length_in_pt($style->width, $cb["w"]);
  490. $h = $style->length_in_pt($style->height, $cb["h"]);
  491. return [0 => $x, "x" => $x,
  492. 1 => $y, "y" => $y,
  493. 2 => $w, "w" => $w,
  494. 3 => $h, "h" => $h];
  495. }
  496. /**
  497. * Return the padding box (x,y,w,h) of the frame
  498. *
  499. * @return array
  500. */
  501. public function get_padding_box()
  502. {
  503. $style = $this->_style;
  504. $cb = $this->_containing_block;
  505. $x = $this->_position["x"] +
  506. (float)$style->length_in_pt(
  507. [
  508. $style->margin_left,
  509. $style->border_left_width
  510. ],
  511. $cb["w"]);
  512. $y = $this->_position["y"] +
  513. (float)$style->length_in_pt(
  514. [
  515. $style->margin_top,
  516. $style->border_top_width
  517. ],
  518. $cb["h"]
  519. );
  520. $w = $style->length_in_pt(
  521. [
  522. $style->padding_left,
  523. $style->width,
  524. $style->padding_right
  525. ],
  526. $cb["w"]
  527. );
  528. $h = $style->length_in_pt(
  529. [
  530. $style->padding_top,
  531. $style->padding_bottom,
  532. $style->length_in_pt($style->height, $cb["h"])
  533. ],
  534. $cb["w"]
  535. );
  536. return [0 => $x, "x" => $x,
  537. 1 => $y, "y" => $y,
  538. 2 => $w, "w" => $w,
  539. 3 => $h, "h" => $h];
  540. }
  541. /**
  542. * Return the border box of the frame
  543. *
  544. * @return array
  545. */
  546. public function get_border_box()
  547. {
  548. $style = $this->_style;
  549. $cb = $this->_containing_block;
  550. $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
  551. $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
  552. $w = $style->length_in_pt(
  553. [
  554. $style->border_left_width,
  555. $style->padding_left,
  556. $style->width,
  557. $style->padding_right,
  558. $style->border_right_width
  559. ],
  560. $cb["w"]);
  561. $h = $style->length_in_pt(
  562. [
  563. $style->border_top_width,
  564. $style->padding_top,
  565. $style->padding_bottom,
  566. $style->border_bottom_width,
  567. $style->length_in_pt($style->height, $cb["h"])
  568. ],
  569. $cb["w"]);
  570. return [0 => $x, "x" => $x,
  571. 1 => $y, "y" => $y,
  572. 2 => $w, "w" => $w,
  573. 3 => $h, "h" => $h];
  574. }
  575. /**
  576. * @param null $opacity
  577. *
  578. * @return float
  579. */
  580. public function get_opacity($opacity = null)
  581. {
  582. if ($opacity !== null) {
  583. $this->set_opacity($opacity);
  584. }
  585. return $this->_opacity;
  586. }
  587. /**
  588. * @return LineBox
  589. */
  590. public function &get_containing_line()
  591. {
  592. return $this->_containing_line;
  593. }
  594. //........................................................................
  595. // Set methods
  596. /**
  597. * @param $id
  598. */
  599. public function set_id($id)
  600. {
  601. $this->_id = $id;
  602. // We can only set attributes of DOMElement objects (nodeType == 1).
  603. // Since these are the only objects that we can assign CSS rules to,
  604. // this shortcoming is okay.
  605. if ($this->_node->nodeType == XML_ELEMENT_NODE) {
  606. $this->_node->setAttribute("frame_id", $id);
  607. }
  608. }
  609. /**
  610. * @param Style $style
  611. */
  612. public function set_style(Style $style)
  613. {
  614. if (is_null($this->_style)) {
  615. $this->_original_style = clone $style;
  616. }
  617. //$style->set_frame($this);
  618. $this->_style = $style;
  619. }
  620. /**
  621. * @param \Dompdf\FrameDecorator\AbstractFrameDecorator $decorator
  622. */
  623. public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
  624. {
  625. $this->_decorator = $decorator;
  626. }
  627. /**
  628. * @param null $x
  629. * @param null $y
  630. * @param null $w
  631. * @param null $h
  632. */
  633. public function set_containing_block($x = null, $y = null, $w = null, $h = null)
  634. {
  635. if (is_array($x)) {
  636. foreach ($x as $key => $val) {
  637. $$key = $val;
  638. }
  639. }
  640. if (is_numeric($x)) {
  641. $this->_containing_block["x"] = $x;
  642. }
  643. if (is_numeric($y)) {
  644. $this->_containing_block["y"] = $y;
  645. }
  646. if (is_numeric($w)) {
  647. $this->_containing_block["w"] = $w;
  648. }
  649. if (is_numeric($h)) {
  650. $this->_containing_block["h"] = $h;
  651. }
  652. }
  653. /**
  654. * @param null $x
  655. * @param null $y
  656. */
  657. public function set_position($x = null, $y = null)
  658. {
  659. if (is_array($x)) {
  660. list($x, $y) = [$x["x"], $x["y"]];
  661. }
  662. if (is_numeric($x)) {
  663. $this->_position["x"] = $x;
  664. }
  665. if (is_numeric($y)) {
  666. $this->_position["y"] = $y;
  667. }
  668. }
  669. /**
  670. * @param $opacity
  671. */
  672. public function set_opacity($opacity)
  673. {
  674. $parent = $this->get_parent();
  675. $base_opacity = (($parent && $parent->_opacity !== null) ? $parent->_opacity : 1.0);
  676. $this->_opacity = $base_opacity * $opacity;
  677. }
  678. /**
  679. * @param LineBox $line
  680. */
  681. public function set_containing_line(LineBox $line)
  682. {
  683. $this->_containing_line = $line;
  684. }
  685. /**
  686. * Indicates if the margin height is auto sized
  687. *
  688. * @return bool
  689. */
  690. public function is_auto_height()
  691. {
  692. $style = $this->_style;
  693. return in_array(
  694. "auto",
  695. [
  696. $style->height,
  697. $style->margin_top,
  698. $style->margin_bottom,
  699. $style->border_top_width,
  700. $style->border_bottom_width,
  701. $style->padding_top,
  702. $style->padding_bottom,
  703. $this->_containing_block["h"]
  704. ],
  705. true
  706. );
  707. }
  708. /**
  709. * Indicates if the margin width is auto sized
  710. *
  711. * @return bool
  712. */
  713. public function is_auto_width()
  714. {
  715. $style = $this->_style;
  716. return in_array(
  717. "auto",
  718. [
  719. $style->width,
  720. $style->margin_left,
  721. $style->margin_right,
  722. $style->border_left_width,
  723. $style->border_right_width,
  724. $style->padding_left,
  725. $style->padding_right,
  726. $this->_containing_block["w"]
  727. ],
  728. true
  729. );
  730. }
  731. /**
  732. * Tells if the frame is a text node
  733. *
  734. * @return bool
  735. */
  736. public function is_text_node()
  737. {
  738. if (isset($this->_is_cache["text_node"])) {
  739. return $this->_is_cache["text_node"];
  740. }
  741. return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
  742. }
  743. /**
  744. * @return bool
  745. */
  746. public function is_positionned()
  747. {
  748. if (isset($this->_is_cache["positionned"])) {
  749. return $this->_is_cache["positionned"];
  750. }
  751. $position = $this->get_style()->position;
  752. return $this->_is_cache["positionned"] = in_array($position, Style::$POSITIONNED_TYPES);
  753. }
  754. /**
  755. * @return bool
  756. */
  757. public function is_absolute()
  758. {
  759. if (isset($this->_is_cache["absolute"])) {
  760. return $this->_is_cache["absolute"];
  761. }
  762. $position = $this->get_style()->position;
  763. return $this->_is_cache["absolute"] = ($position === "absolute" || $position === "fixed");
  764. }
  765. /**
  766. * @return bool
  767. */
  768. public function is_block()
  769. {
  770. if (isset($this->_is_cache["block"])) {
  771. return $this->_is_cache["block"];
  772. }
  773. return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::$BLOCK_TYPES);
  774. }
  775. /**
  776. * @return bool
  777. */
  778. public function is_inline_block()
  779. {
  780. if (isset($this->_is_cache["inline_block"])) {
  781. return $this->_is_cache["inline_block"];
  782. }
  783. return $this->_is_cache["inline_block"] = ($this->get_style()->display === 'inline-block');
  784. }
  785. /**
  786. * @return bool
  787. */
  788. public function is_in_flow()
  789. {
  790. if (isset($this->_is_cache["in_flow"])) {
  791. return $this->_is_cache["in_flow"];
  792. }
  793. return $this->_is_cache["in_flow"] = !($this->get_style()->float !== "none" || $this->is_absolute());
  794. }
  795. /**
  796. * @return bool
  797. */
  798. public function is_pre()
  799. {
  800. if (isset($this->_is_cache["pre"])) {
  801. return $this->_is_cache["pre"];
  802. }
  803. $white_space = $this->get_style()->white_space;
  804. return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"]);
  805. }
  806. /**
  807. * @return bool
  808. */
  809. public function is_table()
  810. {
  811. if (isset($this->_is_cache["table"])) {
  812. return $this->_is_cache["table"];
  813. }
  814. $display = $this->get_style()->display;
  815. return $this->_is_cache["table"] = in_array($display, Style::$TABLE_TYPES);
  816. }
  817. /**
  818. * Inserts a new child at the beginning of the Frame
  819. *
  820. * @param $child Frame The new Frame to insert
  821. * @param $update_node boolean Whether or not to update the DOM
  822. */
  823. public function prepend_child(Frame $child, $update_node = true)
  824. {
  825. if ($update_node) {
  826. $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
  827. }
  828. // Remove the child from its parent
  829. if ($child->_parent) {
  830. $child->_parent->remove_child($child, false);
  831. }
  832. $child->_parent = $this;
  833. $child->_prev_sibling = null;
  834. // Handle the first child
  835. if (!$this->_first_child) {
  836. $this->_first_child = $child;
  837. $this->_last_child = $child;
  838. $child->_next_sibling = null;
  839. } else {
  840. $this->_first_child->_prev_sibling = $child;
  841. $child->_next_sibling = $this->_first_child;
  842. $this->_first_child = $child;
  843. }
  844. }
  845. /**
  846. * Inserts a new child at the end of the Frame
  847. *
  848. * @param $child Frame The new Frame to insert
  849. * @param $update_node boolean Whether or not to update the DOM
  850. */
  851. public function append_child(Frame $child, $update_node = true)
  852. {
  853. if ($update_node) {
  854. $this->_node->appendChild($child->_node);
  855. }
  856. // Remove the child from its parent
  857. if ($child->_parent) {
  858. $child->_parent->remove_child($child, false);
  859. }
  860. $child->_parent = $this;
  861. $decorator = $child->get_decorator();
  862. // force an update to the cached parent
  863. if ($decorator !== null) {
  864. $decorator->get_parent(false);
  865. }
  866. $child->_next_sibling = null;
  867. // Handle the first child
  868. if (!$this->_last_child) {
  869. $this->_first_child = $child;
  870. $this->_last_child = $child;
  871. $child->_prev_sibling = null;
  872. } else {
  873. $this->_last_child->_next_sibling = $child;
  874. $child->_prev_sibling = $this->_last_child;
  875. $this->_last_child = $child;
  876. }
  877. }
  878. /**
  879. * Inserts a new child immediately before the specified frame
  880. *
  881. * @param $new_child Frame The new Frame to insert
  882. * @param $ref Frame The Frame after the new Frame
  883. * @param $update_node boolean Whether or not to update the DOM
  884. *
  885. * @throws Exception
  886. */
  887. public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
  888. {
  889. if ($ref === $this->_first_child) {
  890. $this->prepend_child($new_child, $update_node);
  891. return;
  892. }
  893. if (is_null($ref)) {
  894. $this->append_child($new_child, $update_node);
  895. return;
  896. }
  897. if ($ref->_parent !== $this) {
  898. throw new Exception("Reference child is not a child of this node.");
  899. }
  900. // Update the node
  901. if ($update_node) {
  902. $this->_node->insertBefore($new_child->_node, $ref->_node);
  903. }
  904. // Remove the child from its parent
  905. if ($new_child->_parent) {
  906. $new_child->_parent->remove_child($new_child, false);
  907. }
  908. $new_child->_parent = $this;
  909. $new_child->_next_sibling = $ref;
  910. $new_child->_prev_sibling = $ref->_prev_sibling;
  911. if ($ref->_prev_sibling) {
  912. $ref->_prev_sibling->_next_sibling = $new_child;
  913. }
  914. $ref->_prev_sibling = $new_child;
  915. }
  916. /**
  917. * Inserts a new child immediately after the specified frame
  918. *
  919. * @param $new_child Frame The new Frame to insert
  920. * @param $ref Frame The Frame before the new Frame
  921. * @param $update_node boolean Whether or not to update the DOM
  922. *
  923. * @throws Exception
  924. */
  925. public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
  926. {
  927. if ($ref === $this->_last_child) {
  928. $this->append_child($new_child, $update_node);
  929. return;
  930. }
  931. if (is_null($ref)) {
  932. $this->prepend_child($new_child, $update_node);
  933. return;
  934. }
  935. if ($ref->_parent !== $this) {
  936. throw new Exception("Reference child is not a child of this node.");
  937. }
  938. // Update the node
  939. if ($update_node) {
  940. if ($ref->_next_sibling) {
  941. $next_node = $ref->_next_sibling->_node;
  942. $this->_node->insertBefore($new_child->_node, $next_node);
  943. } else {
  944. $new_child->_node = $this->_node->appendChild($new_child->_node);
  945. }
  946. }
  947. // Remove the child from its parent
  948. if ($new_child->_parent) {
  949. $new_child->_parent->remove_child($new_child, false);
  950. }
  951. $new_child->_parent = $this;
  952. $new_child->_prev_sibling = $ref;
  953. $new_child->_next_sibling = $ref->_next_sibling;
  954. if ($ref->_next_sibling) {
  955. $ref->_next_sibling->_prev_sibling = $new_child;
  956. }
  957. $ref->_next_sibling = $new_child;
  958. }
  959. /**
  960. * Remove a child frame
  961. *
  962. * @param Frame $child
  963. * @param boolean $update_node Whether or not to remove the DOM node
  964. *
  965. * @throws Exception
  966. * @return Frame The removed child frame
  967. */
  968. public function remove_child(Frame $child, $update_node = true)
  969. {
  970. if ($child->_parent !== $this) {
  971. throw new Exception("Child not found in this frame");
  972. }
  973. if ($update_node) {
  974. $this->_node->removeChild($child->_node);
  975. }
  976. if ($child === $this->_first_child) {
  977. $this->_first_child = $child->_next_sibling;
  978. }
  979. if ($child === $this->_last_child) {
  980. $this->_last_child = $child->_prev_sibling;
  981. }
  982. if ($child->_prev_sibling) {
  983. $child->_prev_sibling->_next_sibling = $child->_next_sibling;
  984. }
  985. if ($child->_next_sibling) {
  986. $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
  987. }
  988. $child->_next_sibling = null;
  989. $child->_prev_sibling = null;
  990. $child->_parent = null;
  991. return $child;
  992. }
  993. //........................................................................
  994. // Debugging function:
  995. /**
  996. * @return string
  997. */
  998. public function __toString()
  999. {
  1000. // Skip empty text frames
  1001. // if ( $this->is_text_node() &&
  1002. // preg_replace("/\s/", "", $this->_node->data) === "" )
  1003. // return "";
  1004. $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
  1005. //$str .= spl_object_hash($this->_node) . "<br/>";
  1006. $str .= "Id: " . $this->get_id() . "<br/>";
  1007. $str .= "Class: " . get_class($this) . "<br/>";
  1008. if ($this->is_text_node()) {
  1009. $tmp = htmlspecialchars($this->_node->nodeValue);
  1010. $str .= "<pre>'" . mb_substr($tmp, 0, 70) .
  1011. (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
  1012. } elseif ($css_class = $this->_node->getAttribute("class")) {
  1013. $str .= "CSS class: '$css_class'<br/>";
  1014. }
  1015. if ($this->_parent) {
  1016. $str .= "\nParent:" . $this->_parent->_node->nodeName .
  1017. " (" . spl_object_hash($this->_parent->_node) . ") " .
  1018. "<br/>";
  1019. }
  1020. if ($this->_prev_sibling) {
  1021. $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
  1022. " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
  1023. "<br/>";
  1024. }
  1025. if ($this->_next_sibling) {
  1026. $str .= "Next: " . $this->_next_sibling->_node->nodeName .
  1027. " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
  1028. "<br/>";
  1029. }
  1030. $d = $this->get_decorator();
  1031. while ($d && $d != $d->get_decorator()) {
  1032. $str .= "Decorator: " . get_class($d) . "<br/>";
  1033. $d = $d->get_decorator();
  1034. }
  1035. $str .= "Position: " . Helpers::pre_r($this->_position, true);
  1036. $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
  1037. $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
  1038. $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
  1039. $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
  1040. if ($this->_decorator instanceof FrameDecorator\Block) {
  1041. $str .= "Lines:<pre>";
  1042. foreach ($this->_decorator->get_line_boxes() as $line) {
  1043. foreach ($line->get_frames() as $frame) {
  1044. if ($frame instanceof FrameDecorator\Text) {
  1045. $str .= "\ntext: ";
  1046. $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
  1047. } else {
  1048. $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
  1049. }
  1050. }
  1051. $str .=
  1052. "\ny => " . $line->y . "\n" .
  1053. "w => " . $line->w . "\n" .
  1054. "h => " . $line->h . "\n" .
  1055. "left => " . $line->left . "\n" .
  1056. "right => " . $line->right . "\n";
  1057. }
  1058. $str .= "</pre>";
  1059. }
  1060. $str .= "\n";
  1061. if (php_sapi_name() === "cli") {
  1062. $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
  1063. ["\n", "", ""],
  1064. $str));
  1065. }
  1066. return $str;
  1067. }
  1068. }