frame.cls.php 27 KB

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