frame.cls.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <?php
  2. /**
  3. * DOMPDF - PHP5 HTML to PDF renderer
  4. *
  5. * File: $RCSfile: frame.cls.php,v $
  6. * Created on: 2004-06-02
  7. *
  8. * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation; either
  13. * version 2.1 of the License, or (at your option) any later version.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public License
  21. * along with this library in the file LICENSE.LGPL; if not, write to the
  22. * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  23. * 02111-1307 USA
  24. *
  25. * Alternatively, you may distribute this software under the terms of the
  26. * PHP License, version 3.0 or later. A copy of this license should have
  27. * been distributed with this file in the file LICENSE.PHP . If this is not
  28. * the case, you can obtain a copy at http://www.php.net/license/3_0.txt.
  29. *
  30. * The latest version of DOMPDF might be available at:
  31. * http://www.dompdf.com/
  32. *
  33. * @link http://www.dompdf.com/
  34. * @copyright 2004 Benj Carson
  35. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  36. * @package dompdf
  37. */
  38. /* $Id: frame.cls.php 216 2010-03-11 22:49:18Z ryan.masten $ */
  39. /**
  40. * The main Frame class
  41. *
  42. * This class represents a single HTML element. This class stores
  43. * positioning information as well as containing block location and
  44. * dimensions. Style information for the element is stored in a {@link
  45. * Style} object. Tree structure is maintained via the parent & children
  46. * links.
  47. *
  48. * @access protected
  49. * @package dompdf
  50. */
  51. class Frame {
  52. /**
  53. * The DOMNode object this frame represents
  54. *
  55. * @var DOMNode
  56. */
  57. protected $_node;
  58. /**
  59. * Unique identifier for this frame. Used to reference this frame
  60. * via the node.
  61. *
  62. * @var string
  63. */
  64. protected $_id;
  65. /**
  66. * Unique id counter
  67. */
  68. static protected $ID_COUNTER = 0;
  69. /**
  70. * This frame's calculated style
  71. *
  72. * @var Style
  73. */
  74. protected $_style;
  75. /**
  76. * This frame's original style. Needed for cases where frames are
  77. * split across pages.
  78. *
  79. * @var Style
  80. */
  81. protected $_original_style;
  82. /**
  83. * This frame's parent in the document tree.
  84. *
  85. * @var Frame
  86. */
  87. protected $_parent;
  88. /**
  89. * This frame's first child. All children are handled as a
  90. * doubly-linked list.
  91. *
  92. * @var Frame
  93. */
  94. protected $_first_child;
  95. /**
  96. * This frame's last child.
  97. *
  98. * @var Frame
  99. */
  100. protected $_last_child;
  101. /**
  102. * This frame's previous sibling in the document tree.
  103. *
  104. * @var Frame
  105. */
  106. protected $_prev_sibling;
  107. /**
  108. * This frame's next sibling in the document tree.
  109. *
  110. * @var Frame
  111. */
  112. protected $_next_sibling;
  113. /**
  114. * This frame's containing block (used in layout): array(x, y, w, h)
  115. *
  116. * @var array
  117. */
  118. protected $_containing_block;
  119. /**
  120. * Position on the page of the top-left corner of the margin box of
  121. * this frame: array(x,y)
  122. *
  123. * @var array
  124. */
  125. protected $_position;
  126. /**
  127. * This frame's decorator
  128. *
  129. * @var Frame_Decorator
  130. */
  131. protected $_decorator;
  132. /**
  133. * Class constructor
  134. *
  135. * @param DOMNode $node the DOMNode this frame represents
  136. */
  137. function __construct(DomNode $node) {
  138. $this->_node = $node;
  139. $this->_parent = null;
  140. $this->_first_child = null;
  141. $this->_last_child = null;
  142. $this->_prev_sibling = $this->_next_sibling = null;
  143. $this->_style = null;
  144. $this->_original_style = null;
  145. $this->_containing_block = array("x" => null,
  146. "y" => null,
  147. "w" => null,
  148. "h" => null);
  149. $this->_position = array("x" => null,
  150. "y" => null);
  151. $this->_decorator = null;
  152. $this->set_id( self::$ID_COUNTER++ );
  153. }
  154. /**
  155. * "Destructor": forcibly free all references held by this frame
  156. *
  157. * @param bool $recursive if true, call dispose on all children
  158. */
  159. function dispose($recursive = false) {
  160. if ( $recursive ) {
  161. while ( $child = $this->_first_child )
  162. $child->dispose(true);
  163. }
  164. // Remove this frame from the tree
  165. if ( $this->_prev_sibling ) {
  166. $this->_prev_sibling->_next_sibling = $this->_next_sibling;
  167. }
  168. if ( $this->_next_sibling ) {
  169. $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
  170. }
  171. if ( $this->_parent && $this->_parent->_first_child === $this ) {
  172. $this->_parent->_first_child = $this->_next_sibling;
  173. }
  174. if ( $this->_parent && $this->_parent->_last_child === $this ) {
  175. $this->_parent->_last_child = $this->_prev_sibling;
  176. }
  177. if ( $this->_parent ) {
  178. $this->_parent->get_node()->removeChild($this->_node);
  179. }
  180. $this->_style->dispose();
  181. unset($this->_style);
  182. $this->_original_style->dispose();
  183. unset($this->_original_style);
  184. }
  185. // Re-initialize the frame
  186. function reset() {
  187. $this->_position = array("x" => null,
  188. "y" => null);
  189. $this->_containing_block = array("x" => null,
  190. "y" => null,
  191. "w" => null,
  192. "h" => null);
  193. unset($this->_style);
  194. $this->_style = clone $this->_original_style;
  195. }
  196. //........................................................................
  197. // Accessor methods
  198. function get_node() { return $this->_node; }
  199. function get_id() { return $this->_id; }
  200. function get_style() { return $this->_style; }
  201. function get_original_style() { return $this->_original_style; }
  202. function get_parent() { return $this->_parent; }
  203. function get_decorator() { return $this->_decorator; }
  204. function get_first_child() { return $this->_first_child; }
  205. function get_last_child() { return $this->_last_child; }
  206. function get_prev_sibling() { return $this->_prev_sibling; }
  207. function get_next_sibling() { return $this->_next_sibling; }
  208. function get_children() { return new FrameList($this); }
  209. // Layout property accessors
  210. function get_containing_block($i = null) {
  211. if ( isset($i) )
  212. return $this->_containing_block[$i];
  213. return $this->_containing_block;
  214. }
  215. function get_position($i = null) {
  216. if ( isset($i) )
  217. return $this->_position[$i];
  218. return array($this->_position["x"],
  219. $this->_position["y"],
  220. "x"=>$this->_position["x"],
  221. "y"=>$this->_position["y"]);
  222. }
  223. //........................................................................
  224. // Return the height of the margin box of the frame, in pt. Meaningless
  225. // unless the height has been calculated properly.
  226. function get_margin_height() {
  227. return $this->_style->length_in_pt(array($this->_style->height,
  228. $this->_style->margin_top,
  229. $this->_style->margin_bottom,
  230. $this->_style->border_top_width,
  231. $this->_style->border_bottom_width,
  232. $this->_style->padding_top,
  233. $this->_style->padding_bottom),
  234. $this->_containing_block["w"]);
  235. }
  236. // Return the width of the margin box of the frame, in pt. Meaningless
  237. // unless the width has been calculted properly.
  238. function get_margin_width() {
  239. return $this->_style->length_in_pt(array($this->_style->width,
  240. $this->_style->margin_left,
  241. $this->_style->margin_right,
  242. $this->_style->border_left_width,
  243. $this->_style->border_right_width,
  244. $this->_style->padding_left,
  245. $this->_style->padding_right),
  246. $this->_containing_block["w"]);
  247. }
  248. // Return the padding box (x,y,w,h) of the frame
  249. function get_padding_box() {
  250. $x = $this->_position["x"] +
  251. $this->_style->length_in_pt(array($this->_style->margin_left,
  252. $this->_style->border_left_width),
  253. $this->_containing_block["w"]);
  254. $y = $this->_position["y"] +
  255. $this->_style->length_in_pt(array($this->_style->margin_top,
  256. $this->_style->border_top_width),
  257. $this->_containing_block["w"]);
  258. $w = $this->_style->length_in_pt(array($this->_style->padding_left,
  259. $this->_style->width,
  260. $this->_style->padding_right),
  261. $this->_containing_block["w"]);
  262. $h = $this->_style->length_in_pt(array($this->_style->padding_top,
  263. $this->_style->height,
  264. $this->_style->padding_bottom),
  265. $this->_containing_block["w"]);
  266. return array(0 => $x, "x" => $x,
  267. 1 => $y, "y" => $y,
  268. 2 => $w, "w" => $w,
  269. 3 => $h, "h" => $h);
  270. }
  271. // Return the border box of the frame
  272. function get_border_box() {
  273. $x = $this->_position["x"] +
  274. $this->_style->length_in_pt($this->_style->margin_left,
  275. $this->_containing_block["w"]);
  276. $y = $this->_position["y"] +
  277. $this->_style->length_in_pt($this->_style->margin_top,
  278. $this->_containing_block["w"]);
  279. $w = $this->_style->length_in_pt(array($this->_style->border_left_width,
  280. $this->_style->padding_left,
  281. $this->_style->width,
  282. $this->_style->padding_right,
  283. $this->_style->border_right_width),
  284. $this->_containing_block["w"]);
  285. $h = $this->_style->length_in_pt(array($this->_style->border_top_width,
  286. $this->_style->padding_top,
  287. $this->_style->height,
  288. $this->_style->padding_bottom,
  289. $this->_style->border_bottom_width),
  290. $this->_containing_block["w"]);
  291. return array(0 => $x, "x" => $x,
  292. 1 => $y, "y" => $y,
  293. 2 => $w, "w" => $w,
  294. 3 => $h, "h" => $h);
  295. }
  296. //........................................................................
  297. // Set methods
  298. function set_id($id) {
  299. $this->_id = $id;
  300. // We can only set attributes of DOMElement objects (nodeType == 1).
  301. // Since these are the only objects that we can assign CSS rules to,
  302. // this shortcoming is okay.
  303. if ( $this->_node->nodeType == 1)
  304. $this->_node->setAttribute("frame_id", $id);
  305. }
  306. function set_style(Style $style) {
  307. if ( is_null($this->_style) )
  308. $this->_original_style = clone $style;
  309. $this->_style = $style;
  310. }
  311. function set_decorator(Frame_Decorator $decorator) {
  312. $this->_decorator = $decorator;
  313. }
  314. function set_containing_block($x = null, $y = null, $w = null, $h = null) {
  315. if ( is_array($x) ){
  316. foreach($x AS $key => $val){
  317. $$key = $val;
  318. }
  319. }
  320. if (is_numeric($x)) {
  321. $this->_containing_block[0] = $x;
  322. $this->_containing_block["x"] = $x;
  323. }
  324. if (is_numeric($y)) {
  325. $this->_containing_block[1] = $y;
  326. $this->_containing_block["y"] = $y;
  327. }
  328. if (is_numeric($w)) {
  329. $this->_containing_block[2] = $w;
  330. $this->_containing_block["w"] = $w;
  331. }
  332. if (is_numeric($h)) {
  333. $this->_containing_block[3] = $h;
  334. $this->_containing_block["h"] = $h;
  335. }
  336. }
  337. function set_position($x = null, $y = null) {
  338. if ( is_array($x) )
  339. extract($x);
  340. if ( is_numeric($x) ) {
  341. $this->_position[0] = $x;
  342. $this->_position["x"] = $x;
  343. }
  344. if ( is_numeric($y) ) {
  345. $this->_position[1] = $y;
  346. $this->_position["y"] = $y;
  347. }
  348. }
  349. //........................................................................
  350. function prepend_child(Frame $child, $update_node = true) {
  351. if ( $update_node )
  352. $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
  353. // Remove the child from its parent
  354. if ( $child->_parent )
  355. $child->_parent->remove_child($child, false);
  356. $child->_parent = $this;
  357. $child->_prev_sibling = null;
  358. // Handle the first child
  359. if ( !$this->_first_child ) {
  360. $this->_first_child = $child;
  361. $this->_last_child = $child;
  362. $child->_next_sibling = null;
  363. } else {
  364. $this->_first_child->_prev_sibling = $child;
  365. $child->_next_sibling = $this->_first_child;
  366. $this->_first_child = $child;
  367. }
  368. }
  369. function append_child(Frame $child, $update_node = true) {
  370. if ( $update_node )
  371. $this->_node->appendChild($child->_node);
  372. // Remove the child from its parent
  373. if ( $child->_parent )
  374. $child->_parent->remove_child($child, false);
  375. $child->_parent = $this;
  376. $child->_next_sibling = null;
  377. // Handle the first child
  378. if ( !$this->_last_child ) {
  379. $this->_first_child = $child;
  380. $this->_last_child = $child;
  381. $child->_prev_sibling = null;
  382. } else {
  383. $this->_last_child->_next_sibling = $child;
  384. $child->_prev_sibling = $this->_last_child;
  385. $this->_last_child = $child;
  386. }
  387. }
  388. // Inserts a new child immediately before the specified frame
  389. function insert_child_before(Frame $new_child, Frame $ref, $update_node = true) {
  390. if ( $ref === $this->_first_child ) {
  391. $this->prepend_child($new_child, $update_node);
  392. return;
  393. }
  394. if ( is_null($ref) ) {
  395. $this->append_child($new_child, $update_node);
  396. return;
  397. }
  398. if ( $ref->_parent !== $this )
  399. throw new DOMPDF_Exception("Reference child is not a child of this node.");
  400. // Update the node
  401. if ( $update_node )
  402. $this->_node->insertBefore($new_child->_node, $ref->_node);
  403. // Remove the child from its parent
  404. if ( $new_child->_parent )
  405. $new_child->_parent->remove_child($new_child, false);
  406. $new_child->_parent = $this;
  407. $new_child->_next_sibling = $ref;
  408. $new_child->_prev_sibling = $ref->_prev_sibling;
  409. if ( $ref->_prev_sibling )
  410. $ref->_prev_sibling->_next_sibling = $new_child;
  411. $ref->_prev_sibling = $new_child;
  412. }
  413. // Inserts a new child immediately after the specified frame
  414. function insert_child_after(Frame $new_child, Frame $ref, $update_node = true) {
  415. if ( $ref === $this->_last_child ) {
  416. $this->append_child($new_child, $update_node);
  417. return;
  418. }
  419. if ( is_null($ref) ) {
  420. $this->prepend_child($new_child, $update_node);
  421. return;
  422. }
  423. if ( $ref->_parent !== $this )
  424. throw new DOMPDF_Exception("Reference child is not a child of this node.");
  425. // Update the node
  426. if ( $update_node ) {
  427. if ( $ref->_next_sibling ) {
  428. $next_node = $ref->_next_sibling->_node;
  429. $this->_node->insertBefore($new_child->_node, $next_node);
  430. } else {
  431. $new_child->_node = $this->_node->appendChild($new_child);
  432. }
  433. }
  434. // Remove the child from its parent
  435. if ( $new_child->_parent)
  436. $new_child->_parent->remove_child($new_child, false);
  437. $new_child->_parent = $this;
  438. $new_child->_prev_sibling = $ref;
  439. $new_child->_next_sibling = $ref->_next_sibling;
  440. if ( $ref->_next_sibling )
  441. $ref->_next_sibling->_prev_sibling = $new_child;
  442. $ref->_next_sibling = $new_child;
  443. }
  444. function remove_child(Frame $child, $update_node = true) {
  445. if ( $child->_parent !== $this )
  446. throw new DOMPDF_Exception("Child not found in this frame");
  447. if ( $update_node )
  448. $this->_node->removeChild($child->_node);
  449. if ( $child === $this->_first_child )
  450. $this->_first_child = $child->_next_sibling;
  451. if ( $child === $this->_last_child )
  452. $this->_last_child = $child->_prev_sibling;
  453. if ( $child->_prev_sibling )
  454. $child->_prev_sibling->_next_sibling = $child->_next_sibling;
  455. if ( $child->_next_sibling )
  456. $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
  457. $child->_next_sibling = null;
  458. $child->_prev_sibling = null;
  459. $child->_parent = null;
  460. return $child;
  461. }
  462. //........................................................................
  463. // Debugging function:
  464. function __toString() {
  465. // Skip empty text frames
  466. // if ( $this->_node->nodeName === "#text" &&
  467. // preg_replace("/\s/", "", $this->_node->data) === "" )
  468. // return "";
  469. $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
  470. //$str .= spl_object_hash($this->_node) . "<br/>";
  471. $str .= "Id: " .$this->get_id() . "<br/>";
  472. $str .= "Class: " .get_class($this) . "<br/>";
  473. if ( $this->_node->nodeName === "#text" ) {
  474. $tmp = htmlspecialchars($this->_node->nodeValue);
  475. $str .= "<pre>'" . mb_substr($tmp,0,70) .
  476. (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
  477. }
  478. if ( $this->_parent )
  479. $str .= "\nParent:" . $this->_parent->_node->nodeName .
  480. " (" . spl_object_hash($this->_parent->_node) . ") " .
  481. "<br/>";
  482. if ( $this->_prev_sibling )
  483. $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
  484. " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
  485. "<br/>";
  486. if ( $this->_next_sibling )
  487. $str .= "Next: " . $this->_next_sibling->_node->nodeName .
  488. " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
  489. "<br/>";
  490. $d = $this->get_decorator();
  491. while ($d && $d != $d->get_decorator()) {
  492. $str .= "Decorator: " . get_class($d) . "<br/>";
  493. $d = $d->get_decorator();
  494. }
  495. $str .= "Position: " . pre_r($this->_position, true);
  496. $str .= "\nContaining block: " . pre_r($this->_containing_block, true);
  497. $str .= "\nMargin width: " . pre_r($this->get_margin_width(), true);
  498. $str .= "\nMargin height: " . pre_r($this->get_margin_height(), true);
  499. $str .= "\nStyle: <pre>". $this->_style->__toString() . "</pre>";
  500. if ( $this->_decorator instanceof Block_Frame_Decorator ) {
  501. $str .= "Lines:<pre>";
  502. foreach ($this->_decorator->get_lines() as $line) {
  503. foreach ($line["frames"] as $frame) {
  504. if ($frame instanceof Text_Frame_Decorator) {
  505. $str .= "\ntext: ";
  506. $str .= "'". htmlspecialchars($frame->get_text()) ."'";
  507. } else {
  508. $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
  509. }
  510. }
  511. $str .=
  512. //"\ncount => " . $line["count"] . "\n".
  513. "\ny => " . $line["y"] . "\n" .
  514. "w => " . $line["w"] . "\n" .
  515. "h => " . $line["h"] . "\n";
  516. }
  517. $str .= "</pre>";
  518. }
  519. $str .= "\n";
  520. if ( php_sapi_name() === "cli" )
  521. $str = strip_tags(str_replace(array("<br/>","<b>","</b>"),
  522. array("\n","",""),
  523. $str));
  524. return $str;
  525. }
  526. }
  527. //------------------------------------------------------------------------
  528. /**
  529. * Linked-list IteratorAggregate
  530. *
  531. * @access private
  532. * @package dompdf
  533. */
  534. class FrameList implements IteratorAggregate {
  535. protected $_frame;
  536. function __construct($frame) { $this->_frame = $frame; }
  537. function getIterator() { return new FrameListIterator($this->_frame); }
  538. }
  539. /**
  540. * Linked-list Iterator
  541. *
  542. * Returns children in order and allows for list to change during iteration,
  543. * provided the changes occur to or after the current element
  544. *
  545. * @access private
  546. * @package dompdf
  547. */
  548. class FrameListIterator implements Iterator {
  549. protected $_parent;
  550. protected $_cur;
  551. protected $_num;
  552. function __construct(Frame $frame) {
  553. $this->_parent = $frame;
  554. $this->_cur = $frame->get_first_child();
  555. $this->_num = 0;
  556. }
  557. function rewind() {
  558. $this->_cur = $this->_parent->get_first_child();
  559. $this->_num = 0;
  560. }
  561. function valid() {
  562. return isset($this->_cur);// && ($this->_cur->get_prev_sibling() === $this->_prev);
  563. }
  564. function key() { return $this->_num; }
  565. function current() { return $this->_cur; }
  566. function next() {
  567. $ret = $this->_cur;
  568. if ( !$ret )
  569. return null;
  570. $this->_cur = $this->_cur->get_next_sibling();
  571. $this->_num++;
  572. return $ret;
  573. }
  574. }
  575. //------------------------------------------------------------------------
  576. /**
  577. * Pre-order IteratorAggregate
  578. *
  579. * @access private
  580. * @package dompdf
  581. */
  582. class FrameTreeList implements IteratorAggregate {
  583. protected $_root;
  584. function __construct(Frame $root) { $this->_root = $root; }
  585. function getIterator() { return new FrameTreeIterator($this->_root); }
  586. }
  587. /**
  588. * Pre-order Iterator
  589. *
  590. * Returns frames in preorder traversal order (parent then children)
  591. *
  592. * @access private
  593. * @package dompdf
  594. */
  595. class FrameTreeIterator implements Iterator {
  596. protected $_root;
  597. protected $_stack = array();
  598. protected $_num;
  599. function __construct(Frame $root) {
  600. $this->_stack[] = $this->_root = $root;
  601. $this->_num = 0;
  602. }
  603. function rewind() {
  604. $this->_stack = array($this->_root);
  605. $this->_num = 0;
  606. }
  607. function valid() { return count($this->_stack) > 0; }
  608. function key() { return $this->_num; }
  609. function current() { return end($this->_stack); }
  610. function next() {
  611. $b = end($this->_stack);
  612. // Pop last element
  613. unset($this->_stack[ key($this->_stack) ]);
  614. $this->_num++;
  615. // Push all children onto the stack in reverse order
  616. if ( $c = $b->get_last_child() ) {
  617. $this->_stack[] = $c;
  618. while ( $c = $c->get_prev_sibling() )
  619. $this->_stack[] = $c;
  620. }
  621. return $b;
  622. }
  623. }