PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/pdf/box.container.php

http://simpleinvoices.googlecode.com/
PHP | 1116 lines | 625 code | 179 blank | 312 comment | 151 complexity | 29f44f54ef079acfcb4930d6f42be2ee MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, GPL-2.0, LGPL-3.0
  1. <?php
  2. // $Header: /cvsroot/html2ps/box.container.php,v 1.68 2007/05/06 18:49:29 Konstantin Exp $
  3. require_once(HTML2PS_DIR.'strategy.width.min.php');
  4. require_once(HTML2PS_DIR.'strategy.width.min.nowrap.php');
  5. require_once(HTML2PS_DIR.'strategy.width.max.php');
  6. require_once(HTML2PS_DIR.'strategy.width.max.natural.php');
  7. /**
  8. * @package HTML2PS
  9. * @subpackage Document
  10. *
  11. * This file contains the abstract class describing the behavior of document element
  12. * containing some other document elements.
  13. */
  14. /**
  15. * @package HTML2PS
  16. * @subpackage Document
  17. *
  18. * The GenericContainerBox class is a common superclass for all document elements able
  19. * to contain other elements. This class does provide the line-box handling utilies and
  20. * some minor float related-functions.
  21. *
  22. */
  23. class GenericContainerBox extends GenericFormattedBox {
  24. /**
  25. * @var Array A list of contained elements (of type GenericFormattedBox)
  26. * @access public
  27. */
  28. var $content;
  29. var $_first_line;
  30. /**
  31. * @var Array A list of child nodes in the current line box; changes dynamically
  32. * during the reflow process.
  33. * @access private
  34. */
  35. var $_line;
  36. /**
  37. * Sometimes floats may appear inside the line box, consider the following code,
  38. * for example: "<div>text<div style='float:left'>float</div>word</div>". In
  39. * this case, the floating DIV should be rendered below the "text word" line;
  40. * thus, we need to keep a list of deferred floating elements and render them
  41. * when current line box closes.
  42. *
  43. * @var Array A list of floats which should be flown after current line box ends;
  44. * @access private
  45. */
  46. var $_deferred_floats;
  47. /**
  48. * @var float Current output X value inside the current element
  49. * @access public
  50. */
  51. var $_current_x;
  52. /**
  53. * @var float Current output Y value inside the current element
  54. * @access public
  55. */
  56. var $_current_y;
  57. function destroy() {
  58. for ($i=0, $size = count($this->content); $i < $size; $i++) {
  59. $this->content[$i]->destroy();
  60. };
  61. unset($this->content);
  62. parent::destroy();
  63. }
  64. /**
  65. * Render current container box using the specified output method.
  66. *
  67. * @param OutputDriver $driver The output driver object
  68. *
  69. * @return Boolean flag indicating the success or 'null' value in case of critical rendering
  70. * error
  71. */
  72. function show(&$driver) {
  73. GenericFormattedBox::show($driver);
  74. $overflow = $this->getCSSProperty(CSS_OVERFLOW);
  75. /**
  76. * Sometimes the content may overflow container boxes. This situation arise, for example,
  77. * for relative-positioned child boxes, boxes having constrained height and in some
  78. * other cases. If the container box does not have CSS 'overflow' property
  79. * set to 'visible' value, the content should be visually clipped using container box
  80. * padding area.
  81. */
  82. if ($overflow !== OVERFLOW_VISIBLE) {
  83. $driver->save();
  84. $this->_setupClip($driver);
  85. };
  86. /**
  87. * Render child elements
  88. */
  89. for ($i=0, $size = count($this->content); $i < $size; $i++) {
  90. $child =& $this->content[$i];
  91. /**
  92. * We'll check the visibility property here
  93. * Reason: all boxes (except the top-level one) are contained in some other box,
  94. * so every box will pass this check. The alternative is to add this check into every
  95. * box class show member.
  96. *
  97. * The only exception of absolute positioned block boxes which are drawn separately;
  98. * their show method is called explicitly; the similar check should be performed there
  99. */
  100. if ($child->isVisibleInFlow()) {
  101. /**
  102. * To reduce the drawing overhead, we'll check if some part if current child element
  103. * belongs to current output page. If not, there will be no reason to draw this
  104. * child this time.
  105. *
  106. * @see OutputDriver::contains()
  107. *
  108. * @todo In rare cases the element content may be placed outside the element itself;
  109. * in such situantion content may be visible on the page, while element is not.
  110. * This situation should be resolved somehow.
  111. */
  112. if ($driver->contains($child)) {
  113. if (is_null($child->show($driver))) {
  114. return null;
  115. };
  116. };
  117. };
  118. }
  119. /**
  120. * Restore previous clipping mode, if it have been modified for non-'overflow: visible'
  121. * box.
  122. */
  123. if ($overflow !== OVERFLOW_VISIBLE) {
  124. $driver->restore();
  125. };
  126. return true;
  127. }
  128. /**
  129. * Render current fixed-positioned container box using the specified output method. Unlike
  130. * the 'show' method, there's no check if current page viewport contains current element, as
  131. * fixed-positioned may be drawn on the page margins, outside the viewport.
  132. *
  133. * @param OutputDriver $driver The output driver object
  134. *
  135. * @return Boolean flag indicating the success or 'null' value in case of critical rendering
  136. * error
  137. *
  138. * @see GenericContainerBox::show()
  139. *
  140. * @todo the 'show' and 'show_fixed' method code are almost the same except the child element
  141. * method called in the inner loop; also, no check is done if current viewport contains this element,
  142. * thus sllowinf printing data on page margins, where no data should be printed normally
  143. * I suppose some more generic method containing the common code should be made.
  144. */
  145. function show_fixed(&$driver) {
  146. GenericFormattedBox::show($driver);
  147. $overflow = $this->getCSSProperty(CSS_OVERFLOW);
  148. /**
  149. * Sometimes the content may overflow container boxes. This situation arise, for example,
  150. * for relative-positioned child boxes, boxes having constrained height and in some
  151. * other cases. If the container box does not have CSS 'overflow' property
  152. * set to 'visible' value, the content should be visually clipped using container box
  153. * padding area.
  154. */
  155. if ($overflow !== OVERFLOW_VISIBLE) {
  156. // Save graphics state (of course, BEFORE the clipping area will be set)
  157. $driver->save();
  158. $this->_setupClip($driver);
  159. };
  160. /**
  161. * Render child elements
  162. */
  163. $size = count($this->content);
  164. for ($i=0; $i < $size; $i++) {
  165. /**
  166. * We'll check the visibility property here
  167. * Reason: all boxes (except the top-level one) are contained in some other box,
  168. * so every box will pass this check. The alternative is to add this check into every
  169. * box class show member.
  170. *
  171. * The only exception of absolute positioned block boxes which are drawn separately;
  172. * their show method is called explicitly; the similar check should be performed there
  173. */
  174. $child =& $this->content[$i];
  175. if ($child->getCSSProperty(CSS_VISIBILITY) === VISIBILITY_VISIBLE) {
  176. // Fixed-positioned blocks are displayed separately;
  177. // If we call them now, they will be drawn twice
  178. if ($child->getCSSProperty(CSS_POSITION) != POSITION_FIXED) {
  179. if (is_null($child->show_fixed($driver))) {
  180. return null;
  181. };
  182. };
  183. };
  184. }
  185. /**
  186. * Restore previous clipping mode, if it have been modified for non-'overflow: visible'
  187. * box.
  188. */
  189. if ($overflow !== OVERFLOW_VISIBLE) {
  190. $driver->restore();
  191. };
  192. return true;
  193. }
  194. function _find(&$box) {
  195. $size = count($this->content);
  196. for ($i=0; $i<$size; $i++) {
  197. if ($this->content[$i]->uid == $box->uid) {
  198. return $i;
  199. };
  200. }
  201. return null;
  202. }
  203. // Inserts new child box at the specified (zero-based) offset; 0 stands for first child
  204. //
  205. // @param $index index to insert child at
  206. // @param $box child to be inserted
  207. //
  208. function insert_child($index, &$box) {
  209. $box->parent =& $this;
  210. // Offset the content array
  211. for ($i = count($this->content)-1; $i>= $index; $i--) {
  212. $this->content[$i+1] =& $this->content[$i];
  213. };
  214. $this->content[$index] =& $box;
  215. }
  216. function insert_before(&$what, &$where) {
  217. if ($where) {
  218. $index = $this->_find($where);
  219. if (is_null($index)) {
  220. return null;
  221. };
  222. $this->insert_child($index, $what);
  223. } else {
  224. // If 'where' is not specified, 'what' should become the last child
  225. $this->add_child($what);
  226. };
  227. return $what;
  228. }
  229. function add_child(&$box) {
  230. $this->append_child($box);
  231. }
  232. function append_child(&$box) {
  233. // In general, this function is called like following:
  234. // $box->add_child(create_pdf_box(...))
  235. // As create_pdf_box _may_ return null value (for example, for an empty text node),
  236. // we should process the case of $box == null here
  237. if ($box) {
  238. $box->parent =& $this;
  239. $this->content[] =& $box;
  240. };
  241. }
  242. // Get first child of current box which actually will be drawn
  243. // on the page. So, whitespace and null boxes will be ignored
  244. //
  245. // See description of is_null for null box definition.
  246. // (not only NullBox is treated as null box)
  247. //
  248. // @return reference to the first visible child of current box
  249. function &get_first() {
  250. $size = count($this->content);
  251. for ($i=0; $i<$size; $i++) {
  252. if (!is_whitespace($this->content[$i]) &&
  253. !$this->content[$i]->is_null()) {
  254. return $this->content[$i];
  255. };
  256. };
  257. // We use this construct to avoid notice messages in PHP 4.4 and PHP 5
  258. $dummy = null;
  259. return $dummy;
  260. }
  261. // Get first text or image child of current box which actually will be drawn
  262. // on the page.
  263. //
  264. // See description of is_null for null box definition.
  265. // (not only NullBox is treated as null box)
  266. //
  267. // @return reference to the first visible child of current box
  268. function &get_first_data() {
  269. $size = count($this->content);
  270. for ($i=0; $i<$size; $i++) {
  271. if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) {
  272. if (is_container($this->content[$i])) {
  273. $data =& $this->content[$i]->get_first_data();
  274. if (!is_null($data)) { return $data; };
  275. } else {
  276. return $this->content[$i];
  277. };
  278. };
  279. };
  280. // We use this construct to avoid notice messages in PHP 4.4 and PHP 5
  281. $dummy = null;
  282. return $dummy;
  283. }
  284. // Get last child of current box which actually will be drawn
  285. // on the page. So, whitespace and null boxes will be ignored
  286. //
  287. // See description of is_null for null box definition.
  288. // (not only NullBox is treated as null box)
  289. //
  290. // @return reference to the last visible child of current box
  291. function &get_last() {
  292. for ($i=count($this->content)-1; $i>=0; $i--) {
  293. if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) {
  294. return $this->content[$i];
  295. };
  296. };
  297. // We use this construct to avoid notice messages in PHP 4.4 and PHP 5
  298. $dummy = null;
  299. return $dummy;
  300. }
  301. function offset_if_first(&$box, $dx, $dy) {
  302. if ($this->is_first($box)) {
  303. // The top-level box (page box) should never be offset
  304. if ($this->parent) {
  305. if (!$this->parent->offset_if_first($box, $dx, $dy)) {
  306. $this->offset($dx, $dy);
  307. return true;
  308. };
  309. };
  310. };
  311. return false;
  312. }
  313. function offset($dx, $dy) {
  314. parent::offset($dx, $dy);
  315. $this->_current_x += $dx;
  316. $this->_current_y += $dy;
  317. // Offset contents
  318. $size = count($this->content);
  319. for ($i=0; $i < $size; $i++) {
  320. $this->content[$i]->offset($dx, $dy);
  321. }
  322. }
  323. function GenericContainerBox() {
  324. $this->GenericFormattedBox();
  325. // By default, box does not have any content
  326. $this->content = array();
  327. // Initialize line box
  328. $this->_line = array();
  329. // Initialize floats-related stuff
  330. $this->_deferred_floats = array();
  331. $this->_additional_text_indent = 0;
  332. // Current-point
  333. $this->_current_x = 0;
  334. $this->_current_y = 0;
  335. // Initialize floating children array
  336. $this->_floats = array();
  337. }
  338. function add_deferred_float(&$float) {
  339. $this->_deferred_floats[] =& $float;
  340. }
  341. /**
  342. * Create the child nodes of current container object using the parsed HTML data
  343. *
  344. * @param mixed $root node corresponding to the current container object
  345. */
  346. function create_content(&$root, &$pipeline) {
  347. // Initialize content
  348. $child = $root->first_child();
  349. while ($child) {
  350. $box_child =& create_pdf_box($child, $pipeline);
  351. $this->add_child($box_child);
  352. $child = $child->next_sibling();
  353. };
  354. }
  355. // Content-handling functions
  356. function is_container() {
  357. return true;
  358. }
  359. function get_content() {
  360. return join('', array_map(array($this, 'get_content_callback'), $this->content));
  361. }
  362. function get_content_callback(&$node) {
  363. return $node->get_content();
  364. }
  365. // Get total height of this box content (including floats, if any)
  366. // Note that floats can be contained inside children, so we'll need to use
  367. // this function recusively
  368. function get_real_full_height() {
  369. $content_size = count($this->content);
  370. $overflow = $this->getCSSProperty(CSS_OVERFLOW);
  371. // Treat items with overflow: hidden specifically,
  372. // as floats flown out of this boxes will not be visible
  373. if ($overflow == OVERFLOW_HIDDEN) {
  374. return $this->get_full_height();
  375. };
  376. // Check if this object is totally empty
  377. $first = $this->get_first();
  378. if (is_null($first)) {
  379. return 0;
  380. };
  381. // Initialize the vertical extent taken by content using the
  382. // very first child
  383. $max_top = $first->get_top_margin();
  384. $min_bottom = $first->get_bottom_margin();
  385. for ($i=0; $i<$content_size; $i++) {
  386. if (!$this->content[$i]->is_null()) {
  387. // Check if top margin of current child is to the up
  388. // of vertical extent top margin
  389. $max_top = max($max_top, $this->content[$i]->get_top_margin());
  390. /**
  391. * Check if current child bottom margin will extend
  392. * the vertical space OR if it contains floats extending
  393. * this, unless this child have overflow: hidden, because this
  394. * will prevent additional content to be visible
  395. */
  396. if (!$this->content[$i]->is_container()) {
  397. $min_bottom = min($min_bottom,
  398. $this->content[$i]->get_bottom_margin());
  399. } else {
  400. $content_overflow = $this->content[$i]->getCSSProperty(CSS_OVERFLOW);
  401. if ($content_overflow == OVERFLOW_HIDDEN) {
  402. $min_bottom = min($min_bottom,
  403. $this->content[$i]->get_bottom_margin());
  404. } else {
  405. $min_bottom = min($min_bottom,
  406. $this->content[$i]->get_bottom_margin(),
  407. $this->content[$i]->get_top_margin() -
  408. $this->content[$i]->get_real_full_height());
  409. };
  410. };
  411. };
  412. }
  413. return max(0, $max_top - $min_bottom) + $this->_get_vert_extra();
  414. }
  415. // LINE-LENGTH RELATED FUNCTIONS
  416. function _line_length() {
  417. $sum = 0;
  418. $size = count($this->_line);
  419. for ($i=0; $i < $size; $i++) {
  420. // Note that the line length should include the inline boxes margin/padding
  421. // as inline boxes are not directly included to the parent line box,
  422. // we'll need to check the parent of current line box element,
  423. // and, if it is an inline box, AND this element is last or first contained element
  424. // add correcponsing padding value
  425. $element =& $this->_line[$i];
  426. if (isset($element->wrapped) && !is_null($element->wrapped)) {
  427. if ($i==0) {
  428. $sum += $element->get_full_width() - $element->getWrappedWidth();
  429. } else {
  430. $sum += $element->getWrappedWidthAndHyphen();
  431. };
  432. } else {
  433. $sum += $element->get_full_width();
  434. };
  435. if ($element->parent) {
  436. $first = $element->parent->get_first();
  437. $last = $element->parent->get_last();
  438. if (!is_null($first) && $first->uid === $element->uid) {
  439. $sum += $element->parent->get_extra_line_left();
  440. }
  441. if (!is_null($last) && $last->uid === $element->uid) {
  442. $sum += $element->parent->get_extra_line_right();
  443. }
  444. };
  445. }
  446. if ($this->_first_line) {
  447. $ti = $this->getCSSProperty(CSS_TEXT_INDENT);
  448. $sum += $ti->calculate($this);
  449. $sum += $this->_additional_text_indent;
  450. };
  451. return $sum;
  452. }
  453. function _line_length_delta(&$context) {
  454. return max($this->get_available_width($context) - $this->_line_length(),0);
  455. }
  456. /**
  457. * Get the last box in current line box
  458. */
  459. function &last_in_line() {
  460. $size = count($this->_line);
  461. if ($size < 1) {
  462. $dummy = null;
  463. return $dummy;
  464. };
  465. return $this->_line[$size-1];
  466. }
  467. // WIDTH
  468. function get_min_width_natural(&$context) {
  469. $content_size = count($this->content);
  470. /**
  471. * If box does not have any context, its minimal width is determined by extra horizontal space:
  472. * padding, border width and margins
  473. */
  474. if ($content_size == 0) {
  475. $min_width = $this->_get_hor_extra();
  476. return $min_width;
  477. };
  478. /**
  479. * If we're in 'nowrap' mode, minimal and maximal width will be equal
  480. */
  481. $white_space = $this->getCSSProperty(CSS_WHITE_SPACE);
  482. $pseudo_nowrap = $this->getCSSProperty(CSS_HTML2PS_NOWRAP);
  483. if ($white_space == WHITESPACE_NOWRAP ||
  484. $pseudo_nowrap == NOWRAP_NOWRAP) {
  485. $min_width = $this->get_min_nowrap_width($context);
  486. return $min_width;
  487. }
  488. /**
  489. * We need to add text indent size to the width of the first item
  490. */
  491. $start_index = 0;
  492. while ($start_index < $content_size &&
  493. $this->content[$start_index]->out_of_flow()) {
  494. $start_index++;
  495. };
  496. if ($start_index < $content_size) {
  497. $ti = $this->getCSSProperty(CSS_TEXT_INDENT);
  498. $minw =
  499. $ti->calculate($this) +
  500. $this->content[$start_index]->get_min_width_natural($context);
  501. } else {
  502. $minw = 0;
  503. };
  504. for ($i=$start_index; $i<$content_size; $i++) {
  505. $item =& $this->content[$i];
  506. if (!$item->out_of_flow()) {
  507. $minw = max($minw, $item->get_min_width($context));
  508. };
  509. }
  510. /**
  511. * Apply width constraint to min width. Return maximal value
  512. */
  513. $wc = $this->getCSSProperty(CSS_WIDTH);
  514. $containing_block =& $this->_get_containing_block();
  515. $min_width = $minw;
  516. return $min_width;
  517. }
  518. function get_min_width(&$context) {
  519. $strategy = new StrategyWidthMin();
  520. return $strategy->apply($this, $context);
  521. }
  522. function get_min_nowrap_width(&$context) {
  523. $strategy = new StrategyWidthMinNowrap();
  524. return $strategy->apply($this, $context);
  525. }
  526. // Note: <table width="100%" inside some block box cause this box to expand
  527. // $limit - maximal width which should not be exceeded; by default, there's no limit at all
  528. //
  529. function get_max_width_natural(&$context, $limit=10E6) {
  530. $strategy = new StrategyWidthMaxNatural($limit);
  531. return $strategy->apply($this, $context);
  532. }
  533. function get_max_width(&$context, $limit=10E6) {
  534. $strategy = new StrategyWidthMax($limit);
  535. return $strategy->apply($this, $context);
  536. }
  537. function close_line(&$context, $lastline = false) {
  538. // Align line-box using 'text-align' property
  539. $size = count($this->_line);
  540. if ($size > 0) {
  541. $last_item =& $this->_line[$size-1];
  542. if (is_whitespace($last_item)) {
  543. $last_item->width = 0;
  544. $last_item->height = 0;
  545. };
  546. };
  547. // Note that text-align should not be applied to the block boxes!
  548. // As block boxes will be alone in the line-box, we can check
  549. // if the very first box in the line is inline; if not - no justification should be made
  550. //
  551. if ($size > 0) {
  552. if (is_inline($this->_line[0])) {
  553. $cb = CSSTextAlign::value2pdf($this->getCSSProperty(CSS_TEXT_ALIGN));
  554. $cb($this, $context, $lastline);
  555. } else {
  556. // Nevertheless, CENTER tag and P/DIV with ALIGN attribute set should affect the
  557. // position of non-inline children.
  558. $cb = CSSPseudoAlign::value2pdf($this->getCSSProperty(CSS_HTML2PS_ALIGN));
  559. $cb($this, $context, $lastline);
  560. };
  561. };
  562. // Apply vertical align to all of the line content
  563. // first, we need to aling all baseline-aligned boxes to determine the basic line-box height, top and bottom edges
  564. // then, SUP and SUP positioned boxes (as they can extend the top and bottom edges, but not affected themselves)
  565. // then, MIDDLE, BOTTOM and TOP positioned boxes in the given order
  566. //
  567. $baselined = array();
  568. $baseline = 0;
  569. $height = 0;
  570. for ($i=0; $i < $size; $i++) {
  571. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  572. if ($vertical_align == VA_BASELINE) {
  573. // Add current baseline-aligned item to the baseline
  574. //
  575. $baselined[] =& $this->_line[$i];
  576. $baseline = max($baseline,
  577. $this->_line[$i]->default_baseline);
  578. };
  579. };
  580. $size_baselined = count($baselined);
  581. for ($i=0; $i < $size_baselined; $i++) {
  582. $baselined[$i]->baseline = $baseline;
  583. $height = max($height,
  584. $baselined[$i]->get_full_height() + $baselined[$i]->getBaselineOffset(),
  585. $baselined[$i]->get_ascender() + $baselined[$i]->get_descender());
  586. };
  587. // SUB vertical align
  588. //
  589. for ($i=0; $i < $size; $i++) {
  590. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  591. if ($vertical_align == VA_SUB) {
  592. $this->_line[$i]->baseline =
  593. $baseline + $this->_line[$i]->get_full_height()/2;
  594. };
  595. }
  596. // SUPER vertical align
  597. //
  598. for ($i=0; $i < $size; $i++) {
  599. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  600. if ($vertical_align == VA_SUPER) {
  601. $this->_line[$i]->baseline = $this->_line[$i]->get_full_height()/2;
  602. };
  603. }
  604. // MIDDLE vertical align
  605. //
  606. $middle = 0;
  607. for ($i=0; $i < $size; $i++) {
  608. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  609. if ($vertical_align == VA_MIDDLE) {
  610. $middle = max($middle, $this->_line[$i]->get_full_height() / 2);
  611. };
  612. };
  613. if ($middle * 2 > $height) {
  614. // Offset already aligned items
  615. //
  616. for ($i=0; $i < $size; $i++) {
  617. $this->_line[$i]->baseline += ($middle - $height/2);
  618. };
  619. $height = $middle * 2;
  620. };
  621. for ($i=0; $i < $size; $i++) {
  622. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  623. if ($vertical_align == VA_MIDDLE) {
  624. $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + ($height/2 - $this->_line[$i]->get_full_height()/2);
  625. };
  626. }
  627. // BOTTOM vertical align
  628. //
  629. $bottom = 0;
  630. for ($i=0; $i < $size; $i++) {
  631. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  632. if ($vertical_align == VA_BOTTOM) {
  633. $bottom = max($bottom, $this->_line[$i]->get_full_height());
  634. };
  635. };
  636. if ($bottom > $height) {
  637. // Offset already aligned items
  638. //
  639. for ($i=0; $i < $size; $i++) {
  640. $this->_line[$i]->baseline += ($bottom - $height);
  641. };
  642. $height = $bottom;
  643. };
  644. for ($i=0; $i < $size; $i++) {
  645. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  646. if ($vertical_align == VA_BOTTOM) {
  647. $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + $height - $this->_line[$i]->get_full_height();
  648. };
  649. }
  650. // TOP vertical align
  651. //
  652. $bottom = 0;
  653. for ($i=0; $i < $size; $i++) {
  654. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  655. if ($vertical_align == VA_TOP) {
  656. $bottom = max($bottom, $this->_line[$i]->get_full_height());
  657. };
  658. };
  659. if ($bottom > $height) {
  660. $height = $bottom;
  661. };
  662. for ($i=0; $i < $size; $i++) {
  663. $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN);
  664. if ($vertical_align == VA_TOP) {
  665. $this->_line[$i]->baseline = $this->_line[$i]->default_baseline;
  666. };
  667. }
  668. // Calculate the bottom Y coordinate of last line box
  669. //
  670. $line_bottom = $this->_current_y;
  671. foreach ($this->_line AS $line_element) {
  672. // This line is required; say, we have sequence of text and image inside the container,
  673. // AND image have greater baseline than text; in out case, text will be offset to the bottom
  674. // of the page and we lose the gap between text and container bottom edge, unless we'll re-extend
  675. // containier height
  676. // Note that we're using the colapsed margin value to get the Y coordinate to extend height to,
  677. // as bottom margin may be collapsed with parent
  678. $effective_bottom =
  679. $line_element->get_top() -
  680. $line_element->get_height() -
  681. $line_element->get_extra_bottom();
  682. $this->extend_height($effective_bottom);
  683. $line_bottom = min($effective_bottom, $line_bottom);
  684. }
  685. $this->extend_height($line_bottom);
  686. // Clear the line box
  687. $this->_line = array();
  688. // Reset current X coordinate to the far left
  689. $this->_current_x = $this->get_left();
  690. // Extend Y coordinate
  691. $this->_current_y = $line_bottom;
  692. // Render the deferred floats
  693. for ($i = 0, $size = count($this->_deferred_floats); $i < $size; $i++) {
  694. $this->_deferred_floats[$i]->reflow_static_float($this, $context);
  695. };
  696. // Clear deferred float list
  697. $this->_deferred_floats = array();
  698. // modify the current-x value, so that next inline box will not intersect any floating boxes
  699. $this->_current_x = $context->float_left_x($this->_current_x, $this->_current_y);
  700. $this->_first_line = false;
  701. }
  702. function append_line(&$item) {
  703. $this->_line[] =& $item;
  704. }
  705. // Line box should be treated as empty in following cases:
  706. // 1. It is really empty (so, it contains 0 boxes)
  707. // 2. It contains only whitespace boxes
  708. function line_box_empty() {
  709. $size = count($this->_line);
  710. if ($size == 0) { return true; }
  711. // Scan line box
  712. for ($i=0; $i<$size; $i++) {
  713. if (!is_whitespace($this->_line[$i]) &&
  714. !$this->_line[$i]->is_null()) { return false; };
  715. }
  716. // No non-whitespace boxes were found
  717. return true;
  718. }
  719. function reflow_anchors(&$viewport, &$anchors, $page_heights) {
  720. GenericFormattedBox::reflow_anchors($viewport, $anchors, $page_heights);
  721. $size = count($this->content);
  722. for ($i=0; $i<$size; $i++) {
  723. $this->content[$i]->reflow_anchors($viewport, $anchors, $page_heights);
  724. }
  725. }
  726. function fitFloats(&$context) {
  727. $float_bottom = $context->float_bottom();
  728. if (!is_null($float_bottom)) {
  729. $this->extend_height($float_bottom);
  730. };
  731. $float_right = $context->float_right();
  732. if (!is_null($float_right)) {
  733. $this->extend_width($float_right);
  734. };
  735. }
  736. function reflow_content(&$context) {
  737. $text_indent = $this->getCSSProperty(CSS_TEXT_INDENT);
  738. $this->close_line($context);
  739. $this->_first_line = true;
  740. // If first child is inline - apply text-indent
  741. $first = $this->get_first();
  742. if (!is_null($first)) {
  743. if (is_inline($first)) {
  744. $this->_current_x += $text_indent->calculate($this);
  745. $this->_current_x += $this->_additional_text_indent;
  746. };
  747. };
  748. $this->height = 0;
  749. // Reset current Y value
  750. $this->_current_y = $this->get_top();
  751. $size = count($this->content);
  752. for ($i=0; $i < $size; $i++) {
  753. $child =& $this->content[$i];
  754. $child->reflow($this, $context);
  755. };
  756. $this->close_line($context, true);
  757. }
  758. function reflow_inline() {
  759. $size = count($this->content);
  760. for ($i=0; $i<$size; $i++) {
  761. $this->content[$i]->reflow_inline();
  762. };
  763. }
  764. function reflow_text(&$viewport) {
  765. $size = count($this->content);
  766. for ($i=0; $i<$size; $i++) {
  767. if (is_null($this->content[$i]->reflow_text($viewport))) {
  768. return null;
  769. };
  770. }
  771. return true;
  772. }
  773. /**
  774. * Position/size current box as floating one
  775. */
  776. function reflow_static_float(&$parent, &$context) {
  777. // Defer the float rendering till the next line box
  778. if (!$parent->line_box_empty()) {
  779. $parent->add_deferred_float($this);
  780. return;
  781. };
  782. // Calculate margin values if they have been set as a percentage
  783. $this->_calc_percentage_margins($parent);
  784. $this->_calc_percentage_padding($parent);
  785. // Calculate width value if it have been set as a percentage
  786. $this->_calc_percentage_width($parent, $context);
  787. // Calculate margins and/or width is 'auto' values have been specified
  788. $this->_calc_auto_width_margins($parent);
  789. // Determine the actual width of the floating box
  790. // Note that get_max_width returns both content and extra width
  791. $this->put_full_width($this->get_max_width_natural($context, $this->parent->get_width()));
  792. // We need to call this function before determining the horizontal coordinate
  793. // as after vertical offset the additional space to the left may apperar
  794. $y = $this->apply_clear($parent->_current_y, $context);
  795. // determine the position of top-left floating box corner
  796. if ($this->getCSSProperty(CSS_FLOAT) === FLOAT_RIGHT) {
  797. $context->float_right_xy($parent, $this->get_full_width(), $x, $y);
  798. $x -= $this->get_full_width();
  799. } else {
  800. $context->float_left_xy($parent, $this->get_full_width(), $x, $y);
  801. };
  802. // Note that $x and $y contain just a free space corner coordinate;
  803. // If our float has a margin/padding space, we'll need to offset ot a little;
  804. // Remember that float margins are never collapsed!
  805. $this->moveto($x + $this->get_extra_left(), $y - $this->get_extra_top());
  806. // Reflow contents.
  807. // Note that floating box creates a new float flow context for it children.
  808. $context->push_floats();
  809. // Floating box create a separate margin collapsing context
  810. $context->push_collapsed_margin(0);
  811. $this->reflow_content($context);
  812. $context->pop_collapsed_margin();
  813. // Floats and boxes with overflow: hidden
  814. // should completely enclose its child floats
  815. $this->fitFloats($context);
  816. // restore old float flow context
  817. $context->pop_floats();
  818. // Add this box to the list of floats in current context
  819. $context->add_float($this);
  820. // Now fix the value of _current_x for the parent box; it is required
  821. // in the following case:
  822. // <body><img align="left">some text
  823. // in such situation floating image is flown immediately, but it the close_line call have been made before,
  824. // so _current_x value of container box will be still equal to ots left content edge; by calling float_left_x again,
  825. // we'll force "some text" to be offset to the right
  826. $parent->_current_x = $context->float_left_x($parent->_current_x, $parent->_current_y);
  827. }
  828. function reflow_whitespace(&$linebox_started, &$previous_whitespace) {
  829. $previous_whitespace = false;
  830. $linebox_started = false;
  831. $size = count($this->content);
  832. for ($i=0; $i<$size; $i++) {
  833. $child =& $this->content[$i];
  834. $child->reflow_whitespace($linebox_started, $previous_whitespace);
  835. };
  836. // remove the last whitespace in block box
  837. $this->remove_last_whitespace();
  838. // Non-inline box have terminated; we may be sure that line box will be closed
  839. // at this moment and new line box after this will be generated
  840. if (!is_inline($this)) {
  841. $linebox_started = false;
  842. };
  843. return;
  844. }
  845. function remove_last_whitespace() {
  846. if (count($this->content) == 0) {
  847. return;
  848. };
  849. $i = count($this->content)-1;
  850. $last = $this->content[$i];
  851. while ($i >= 0 && is_whitespace($this->content[$i])) {
  852. $this->remove($this->content[$i]);
  853. $i --;
  854. if ($i >= 0) {
  855. $last = $this->content[$i];
  856. };
  857. };
  858. if ($i >= 0) {
  859. if (is_container($this->content[$i])) {
  860. $this->content[$i]->remove_last_whitespace();
  861. };
  862. };
  863. }
  864. function remove(&$box) {
  865. $size = count($this->content);
  866. for ($i=0; $i<$size; $i++) {
  867. if ($this->content[$i]->uid === $box->uid) {
  868. $this->content[$i] = NullBox::create();
  869. };
  870. };
  871. return;
  872. }
  873. function is_first(&$box) {
  874. $first =& $this->get_first();
  875. // Check if there's no first box at all
  876. //
  877. if (is_null($first)) { return false; };
  878. return $first->uid == $box->uid;
  879. }
  880. function is_null() {
  881. $size = count($this->content);
  882. for ($i=0; $i<$size; $i++) {
  883. if (!$this->content[$i]->is_null()) { return false; };
  884. };
  885. return true;
  886. }
  887. // Calculate the available widths - e.g. content width minus space occupied by floats;
  888. // as floats may not fill the whole height of this box, this value depends on Y-coordinate.
  889. // We use current_Y in calculations
  890. //
  891. function get_available_width(&$context) {
  892. $left_float_width = $context->float_left_x($this->get_left(), $this->_current_y) - $this->get_left();
  893. $right_float_width = $this->get_right() - $context->float_right_x($this->get_right(), $this->_current_y);
  894. return $this->get_width() - $left_float_width - $right_float_width;
  895. }
  896. function pre_reflow_images() {
  897. $size = count($this->content);
  898. for ($i=0; $i<$size; $i++) {
  899. $this->content[$i]->pre_reflow_images();
  900. };
  901. }
  902. function _setupClip(&$driver) {
  903. if (!is_null($this->parent)) {
  904. $this->parent->_setupClip($driver);
  905. };
  906. $overflow = $this->getCSSProperty(CSS_OVERFLOW);
  907. if ($overflow !== OVERFLOW_VISIBLE && !$GLOBALS['g_config']['debugnoclip']) {
  908. $driver->moveto( $this->get_left_border() , $this->get_top_border());
  909. $driver->lineto( $this->get_right_border(), $this->get_top_border());
  910. $driver->lineto( $this->get_right_border(), $this->get_bottom_border());
  911. $driver->lineto( $this->get_left_border() , $this->get_bottom_border());
  912. $driver->closepath();
  913. $driver->clip();
  914. };
  915. }
  916. /**
  917. * DOMish functions
  918. */
  919. function &get_element_by_id($id) {
  920. if (isset($GLOBALS['__html_box_id_map'])) {
  921. return $GLOBALS['__html_box_id_map'][$id];
  922. } else {
  923. $dummy = null;
  924. return $dummy;
  925. };
  926. }
  927. /*
  928. * this is just a fake at the moment
  929. */
  930. function get_body() {
  931. return $this;
  932. }
  933. function getChildNodes() {
  934. return $this->content;
  935. }
  936. }
  937. ?>