text_frame_reflower.cls.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. <?php
  2. /**
  3. * DOMPDF - PHP5 HTML to PDF renderer
  4. *
  5. * File: $RCSfile: text_frame_reflower.cls.php,v $
  6. * Created on: 2004-06-17
  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: text_frame_reflower.cls.php 216 2010-03-11 22:49:18Z ryan.masten $ */
  39. /**
  40. * Reflows text frames.
  41. *
  42. * @access private
  43. * @package dompdf
  44. */
  45. class Text_Frame_Reflower extends Frame_Reflower {
  46. protected $_block_parent; // Nearest block-level ancestor
  47. function __construct(Text_Frame_Decorator $frame) {
  48. parent::__construct($frame);
  49. $this->_block_parent = null;
  50. // Handle text transform
  51. $transform = $this->_frame->get_style()->text_transform;
  52. switch ( strtolower($transform) ) {
  53. case "capitalize":
  54. $this->_frame->set_text( ucwords($this->_frame->get_text()) );
  55. break;
  56. case "uppercase":
  57. $this->_frame->set_text( strtoupper($this->_frame->get_text()) );
  58. break;
  59. case "lowercase":
  60. $this->_frame->set_text( strtolower($this->_frame->get_text()) );
  61. break;
  62. default:
  63. // Do nothing
  64. break;
  65. }
  66. }
  67. //........................................................................
  68. protected function _collapse_white_space($text) {
  69. //$text = $this->_frame->get_text();
  70. // if ( $this->_block_parent->get_current_line("w") == 0 )
  71. // $text = ltrim($text, " \n\r\t");
  72. return preg_replace("/[\s\n]+/u", " ", $text);
  73. }
  74. //........................................................................
  75. protected function _line_break($text) {
  76. $style = $this->_frame->get_style();
  77. $size = $style->font_size;
  78. $font = $style->font_family;
  79. // Determine the available width
  80. $line_width = $this->_frame->get_containing_block("w");
  81. $current_line_width = $this->_block_parent->get_current_line("w");
  82. $available_width = $line_width - $current_line_width;
  83. // split the text into words
  84. $words = preg_split('/([\s-]+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
  85. $wc = count($words);
  86. // Account for word-spacing
  87. $word_spacing = $style->length_in_pt($style->word_spacing);
  88. // Determine the frame width including margin, padding & border
  89. $text_width = Font_Metrics::get_text_width($text, $font, $size, $word_spacing);
  90. $mbp_width =
  91. $style->length_in_pt( array( $style->margin_left,
  92. $style->border_left_width,
  93. $style->padding_left,
  94. $style->padding_right,
  95. $style->border_right_width,
  96. $style->margin_right), $line_width );
  97. $frame_width = $text_width + $mbp_width;
  98. // Debugging:
  99. // pre_r("Text: '" . htmlspecialchars($text). "'");
  100. // pre_r("width: " .$frame_width);
  101. // pre_r("textwidth + delta: $text_width + $mbp_width");
  102. // pre_r("font-size: $size");
  103. // pre_r("cb[w]: " .$line_width);
  104. // pre_r("available width: " . $available_width);
  105. // pre_r("current line width: " . $current_line_width);
  106. // pre_r($words);
  107. if ( $frame_width <= $available_width )
  108. return false;
  109. // Determine the split point
  110. $width = 0;
  111. $str = "";
  112. reset($words);
  113. for ($i = 0; $i < count($words); $i += 2) {
  114. $word = $words[$i] . (isset($words[$i+1]) ? $words[$i+1] : "");
  115. $word_width = Font_Metrics::get_text_width($word, $font, $size, $word_spacing);
  116. if ( $width + $word_width + $mbp_width > $available_width )
  117. break;
  118. $width += $word_width;
  119. $str .= $word;
  120. }
  121. // The first word has overflowed. Force it onto the line
  122. if ( $current_line_width == 0 && $width == 0 ) {
  123. $width += $word_width;
  124. $str .= $word;
  125. }
  126. $offset = mb_strlen($str);
  127. // More debugging:
  128. // pre_var_dump($str);
  129. // pre_r("Width: ". $width);
  130. // pre_r("Offset: " . $offset);
  131. return $offset;
  132. }
  133. //........................................................................
  134. protected function _newline_break($text) {
  135. if ( ($i = mb_strpos($text, "\n")) === false)
  136. return false;
  137. return $i+1;
  138. }
  139. //........................................................................
  140. protected function _layout_line() {
  141. $style = $this->_frame->get_style();
  142. $text = $this->_frame->get_text();
  143. $size = $style->font_size;
  144. $font = $style->font_family;
  145. $word_spacing = $style->length_in_pt($style->word_spacing);
  146. // Determine the text height
  147. $style->height = Font_Metrics::get_font_height( $font, $size );
  148. $split = false;
  149. $add_line = false;
  150. // Handle text transform:
  151. // http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
  152. switch ($style->text_transform) {
  153. default:
  154. break;
  155. case "capitalize":
  156. $text = mb_convert_case($text, MB_CASE_TITLE, 'UTF-8');
  157. break;
  158. case "uppercase":
  159. $text = mb_convert_case($text, MB_CASE_UPPER, 'UTF-8');
  160. break;
  161. case "lowercase":
  162. $text = mb_convert_case($text, MB_CASE_LOWER, 'UTF-8');
  163. break;
  164. }
  165. // Handle white-space property:
  166. // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
  167. switch ($style->white_space) {
  168. default:
  169. case "normal":
  170. $this->_frame->set_text( $text = $this->_collapse_white_space($text) );
  171. if ( $text == "" )
  172. break;
  173. $split = $this->_line_break($text);
  174. break;
  175. case "pre":
  176. $split = $this->_newline_break($text);
  177. $add_line = $split !== false;
  178. break;
  179. case "nowrap":
  180. $this->_frame->set_text( $text = $this->_collapse_white_space($text) );
  181. break;
  182. case "pre-wrap":
  183. $split = $this->_newline_break($text);
  184. if ( ($tmp = $this->_line_break($text)) !== false ) {
  185. $add_line = $split < $tmp;
  186. $split = min($tmp, $split);
  187. } else
  188. $add_line = true;
  189. break;
  190. case "pre-line":
  191. // Collapse white-space except for \n
  192. $this->_frame->set_text( $text = preg_replace( "/[ \t]+/u", " ", $text ) );
  193. if ( $text == "" )
  194. break;
  195. $split = $this->_newline_break($text);
  196. if ( ($tmp = $this->_line_break($text)) !== false ) {
  197. $add_line = $split < $tmp;
  198. $split = min($tmp, $split);
  199. } else
  200. $add_line = true;
  201. break;
  202. }
  203. // Handle degenerate case
  204. if ( $text === "" )
  205. return;
  206. if ( $split !== false) {
  207. // Handle edge cases
  208. if ( $split == 0 && $text === " " ) {
  209. $this->_frame->set_text("");
  210. return;
  211. }
  212. if ( $split == 0 ) {
  213. // Trim newlines from the beginning of the line
  214. //$this->_frame->set_text(ltrim($text, "\n\r"));
  215. $this->_block_parent->add_line();
  216. $this->_frame->position();
  217. // Layout the new line
  218. $this->_layout_line();
  219. } else if ( $split < mb_strlen($this->_frame->get_text()) ) {
  220. // split the line if required
  221. $this->_frame->split_text($split);
  222. // Remove any trailing newlines
  223. $t = $this->_frame->get_text();
  224. if ( $split > 1 && $t[$split-1] === "\n" )
  225. $this->_frame->set_text( mb_substr($t, 0, -1) );
  226. }
  227. if ( $add_line ) {
  228. $this->_block_parent->add_line();
  229. $this->_frame->position();
  230. }
  231. // Set our new width
  232. $this->_frame->recalculate_width();
  233. } else {
  234. $this->_frame->recalculate_width();
  235. }
  236. }
  237. //........................................................................
  238. function reflow() {
  239. $this->_block_parent = $this->_frame->find_block_parent();
  240. // Left trim the text if this is the first text on the line and we're
  241. // collapsing white space
  242. // if ( $this->_block_parent->get_current_line("w") == 0 &&
  243. // ($this->_frame->get_style()->white_space !== "pre" ||
  244. // $this->_frame->get_style()->white_space !== "pre-wrap") ) {
  245. // $this->_frame->set_text( ltrim( $this->_frame->get_text() ) );
  246. // }
  247. $this->_frame->position();
  248. $this->_layout_line();
  249. }
  250. //........................................................................
  251. // Returns an array(0 => min, 1 => max, "min" => min, "max" => max) of the
  252. // minimum and maximum widths of this frame
  253. function get_min_max_width() {
  254. $style = $this->_frame->get_style();
  255. $this->_block_parent = $this->_frame->find_block_parent();
  256. $line_width = $this->_frame->get_containing_block("w");
  257. $str = $text = $this->_frame->get_text();
  258. $size = $style->font_size;
  259. $font = $style->font_family;
  260. $spacing = $style->length_in_pt($style->word_spacing);
  261. switch($style->white_space) {
  262. default:
  263. case "normal":
  264. $str = preg_replace("/[\s\n]+/u"," ", $str);
  265. case "pre-wrap":
  266. case "pre-line":
  267. // Find the longest word (i.e. minimum length)
  268. // This technique (using arrays & an anonymous function) is actually
  269. // faster than doing a single-pass character by character scan. Heh,
  270. // yes I took the time to bench it ;)
  271. $words = array_flip(preg_split("/[\s-]+/u",$str, -1, PREG_SPLIT_DELIM_CAPTURE));
  272. array_walk($words, create_function('&$val,$str',
  273. '$val = Font_Metrics::get_text_width($str, "'.$font.'", '.$size.', '.$spacing.');'));
  274. arsort($words);
  275. $min = reset($words);
  276. break;
  277. case "pre":
  278. $lines = array_flip(preg_split("/\n/u", $str));
  279. array_walk($lines, create_function('&$val,$str',
  280. '$val = Font_Metrics::get_text_width($str, "'.$font.'", '.$size.', '.$spacing.');'));
  281. arsort($lines);
  282. $min = reset($lines);
  283. break;
  284. case "nowrap":
  285. $min = Font_Metrics::get_text_width($this->_collapse_white_space($str), $font, $size, $spacing);
  286. break;
  287. }
  288. switch ($style->white_space) {
  289. default:
  290. case "normal":
  291. case "nowrap":
  292. $str = preg_replace("/[\s\n]+/u"," ", $text);
  293. break;
  294. case "pre-line":
  295. $str = preg_replace( "/[ \t]+/u", " ", $text);
  296. case "pre-wrap":
  297. // Find the longest word (i.e. minimum length)
  298. $lines = array_flip(preg_split("/\n/", $text));
  299. array_walk($lines, create_function('&$val,$str',
  300. '$val = Font_Metrics::get_text_width($str, "'.$font.'", '.$size.', '.$spacing.');'));
  301. arsort($lines);
  302. reset($lines);
  303. $str = key($lines);
  304. break;
  305. }
  306. $max = Font_Metrics::get_text_width($str, $font, $size, $spacing);
  307. $delta = $style->length_in_pt(array($style->margin_left,
  308. $style->border_left_width,
  309. $style->padding_left,
  310. $style->padding_right,
  311. $style->border_right_width,
  312. $style->margin_right), $line_width);
  313. $min += $delta;
  314. $max += $delta;
  315. return array($min, $max, "min" => $min, "max" => $max);
  316. }
  317. }