From 7439d3ee14ea8b9e61f656ab092b8d83c72e0dc9 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Sun, 21 Apr 2013 11:08:12 -0400 Subject: [PATCH] Fix incorrect handling of leading spaces in text wrapping --- CHANGELOG | 1 tests/Framework/Mime.php | 46 +++++++++++ program/lib/Roundcube/rcube_mime.php | 146 +++++++++++++++++++++++------------- 3 files changed, 140 insertions(+), 53 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dcfa0d7..7743032 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix incorrect handling of leading spaces in text wrapping - Fix unintentional messages list jumps on click in Internet Explorer (#1489056) - Fix list of required configuration options (#1489055) - Fix DB error when creating a new contact and a group is selected (#1489051) diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index 7cd5207..1e4fac8 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -564,82 +564,122 @@ /** - * Improved wordwrap function. + * Improved wordwrap function with multibyte support. + * The code is based on Zend_Text_MultiByte::wordWrap(). * - * @param string $string Text to wrap - * @param int $width Line width - * @param string $break Line separator - * @param bool $cut Enable to cut word - * @param string $charset Charset of $string + * @param string $string Text to wrap + * @param int $width Line width + * @param string $break Line separator + * @param bool $cut Enable to cut word + * @param string $charset Charset of $string + * @param bool $wrap_quoted When enabled quoted lines will not be wrapped * * @return string Text */ - public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null) + public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true) { - if ($charset && function_exists('mb_internal_encoding')) { - mb_internal_encoding($charset); + if (!$charset) { + $charset = RCUBE_CHARSET; } - $para = preg_split('/\r?\n/', $string); - $string = ''; + // detect available functions + $strlen_func = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen'; + $strpos_func = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos'; + $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos'; + $substr_func = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr'; - while (count($para)) { - $line = array_shift($para); - if ($line[0] == '>') { - $string .= $line . (count($para) ? $break : ''); - continue; - } + // Convert \r\n to \n, this is our line-separator + $string = str_replace("\r\n", "\n", $string); + $separator = "\n"; // must be 1 character length + $result = array(); - $list = explode(' ', $line); - $len = 0; - while (count($list)) { - $line = array_shift($list); - $l = mb_strlen($line); - $space = $len ? 1 : 0; - $newlen = $len + $l + $space; + while (($stringLength = $strlen_func($string, $charset)) > 0) { + $breakPos = $strpos_func($string, $separator, 0, $charset); - if ($newlen <= $width) { - $string .= ($space ? ' ' : '').$line; - $len += ($space + $l); + // quoted line (do not wrap) + if ($wrap_quoted && $string[0] == '>') { + if ($breakPos === $stringLength - 1 || $breakPos === false) { + $subString = $string; + $cutLength = null; } else { - if ($l > $width) { - if ($cut) { - $start = 0; - while ($l) { - $str = mb_substr($line, $start, $width); - $strlen = mb_strlen($str); - $string .= ($len ? $break : '').$str; - $start += $strlen; - $l -= $strlen; - $len = $strlen; + $subString = $substr_func($string, 0, $breakPos, $charset); + $cutLength = $breakPos + 1; + } + } + // next line found and current line is shorter than the limit + else if ($breakPos !== false && $breakPos < $width) { + if ($breakPos === $stringLength - 1) { + $subString = $string; + $cutLength = null; + } + else { + $subString = $substr_func($string, 0, $breakPos, $charset); + $cutLength = $breakPos + 1; + } + } + else { + $subString = $substr_func($string, 0, $width, $charset); + + // last line + if ($subString === $string) { + $cutLength = null; + } + else { + $nextChar = $substr_func($string, $width, 1, $charset); + + if ($nextChar === ' ' || $nextChar === $separator) { + $afterNextChar = $substr_func($string, $width + 1, 1, $charset); + + if ($afterNextChar === false) { + $subString .= $nextChar; + } + + $cutLength = $strlen_func($subString, $charset) + 1; + } + else { + if ($strrpos_func[0] == 'm') { + $spacePos = $strrpos_func($subString, ' ', 0, $charset); + } + else { + $spacePos = $strrpos_func($subString, ' ', $charset); + } + + if ($spacePos !== false) { + $subString = $substr_func($subString, 0, $spacePos, $charset); + $cutLength = $spacePos + 1; + } + else if ($cut === false) { + $spacePos = $strpos_func($string, ' ', 0, $charset); + + if ($spacePos !== false) { + $subString = $substr_func($string, 0, $spacePos, $charset); + $cutLength = $spacePos + 1; + } + else { + $subString = $string; + $cutLength = null; } } else { - $string .= ($len ? $break : '').$line; - if (count($list)) { - $string .= $break; - } - $len = 0; + $subString = $substr_func($subString, 0, $width, $charset); + $cutLength = $width; } - } - else { - $string .= $break.$line; - $len = $l; } } } - if (count($para)) { - $string .= $break; + $result[] = $subString; + + if ($cutLength !== null) { + $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset); + } + else { + break; } } - if ($charset && function_exists('mb_internal_encoding')) { - mb_internal_encoding(RCUBE_CHARSET); - } - - return $string; + return implode($break, $result); } diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php index 1f9a8c5..f15379e 100644 --- a/tests/Framework/Mime.php +++ b/tests/Framework/Mime.php @@ -142,4 +142,50 @@ $this->assertEquals($unfolded, rcube_mime::unfold_flowed($flowed), "Test correct unfolding of quoted lines"); } + + /** + * Test wordwrap() + */ + function test_wordwrap() + { + $samples = array( + array( + array("aaaa aaaa\n aaaa"), + "aaaa aaaa\n aaaa", + ), + array( + array("123456789 123456789 123456789 123", 29), + "123456789 123456789 123456789\n123", + ), + array( + array("123456789 3456789 123456789", 29), + "123456789 3456789 123456789", + ), + array( + array("123456789 123456789 123456789 123", 29), + "123456789 123456789 123456789\n 123", + ), + array( + array("abc", 1, "\n", true), + "a\nb\nc", + ), + array( + array("ąść", 1, "\n", true, 'UTF-8'), + "ą\nś\nć", + ), + array( + array(">abc\n>def", 2, "\n", true), + ">abc\n>def", + ), + array( + array("abc def", 3, "-"), + "abc-def", + ), + ); + + foreach ($samples as $sample) { + $this->assertEquals($sample[1], call_user_func_array(array('rcube_mime', 'wordwrap'), $sample[0]), "Test text wrapping"); + } + } + } -- Gitblit v1.9.1