summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortsepez@chromium.org <tsepez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-22 21:19:02 +0000
committertsepez@chromium.org <tsepez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-22 21:19:02 +0000
commitaf4b9df213e776858d025ec39059831ce15904d0 (patch)
tree8eb5cf37b46397605469d6506329693b5735802e
parented56a7a029ec8515ab30ba13ee1dcdb4721c32d4 (diff)
downloadchromium_src-af4b9df213e776858d025ec39059831ce15904d0.zip
chromium_src-af4b9df213e776858d025ec39059831ce15904d0.tar.gz
chromium_src-af4b9df213e776858d025ec39059831ce15904d0.tar.bz2
Last part of change for bug 49747.
Implements the two-dimensional elider in app/text_elider.cc, and calls it from js_modal_dialog to reformat the message text prior to displaying an alert box. The elider is implemented using the abstractions introduced in the previous CLs for bug 49747. BUG=49746 TEST=TextEliderTest.* Review URL: http://codereview.chromium.org/5964007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@69981 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--app/text_elider.cc167
-rw-r--r--app/text_elider.h19
-rw-r--r--app/text_elider_unittest.cc118
-rw-r--r--chrome/browser/ui/app_modal_dialogs/js_modal_dialog.cc9
4 files changed, 307 insertions, 6 deletions
diff --git a/app/text_elider.cc b/app/text_elider.cc
index bfaec34..855e3c3 100644
--- a/app/text_elider.cc
+++ b/app/text_elider.cc
@@ -6,6 +6,8 @@
#include "app/text_elider.h"
#include "base/file_path.h"
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/char_iterator.h"
#include "base/i18n/rtl.h"
#include "base/string_split.h"
#include "base/string_util.h"
@@ -498,3 +500,168 @@ bool ElideString(const std::wstring& input, int max_len, std::wstring* output) {
}
} // namespace gfx
+
+namespace {
+
+// Internal class used to track progress of a rectangular string elide
+// operation. Exists so the top-level ElideRectangleString() function
+// can be broken into smaller methods sharing this state.
+class RectangleString {
+ public:
+ RectangleString(size_t max_rows, size_t max_cols, string16 *output)
+ : max_rows_(max_rows),
+ max_cols_(max_cols),
+ current_row_(0),
+ current_col_(0),
+ suppressed_(false),
+ output_(output) {}
+
+ // Perform deferred initializions following creation. Must be called
+ // before any input can be added via AddString().
+ void Init() { output_->clear(); }
+
+ // Add an input string, reformatting to fit the desired dimensions.
+ // AddString() may be called multiple times to concatenate together
+ // multiple strings into the region (the current caller doesn't do
+ // this, however).
+ void AddString(const string16& input);
+
+ // Perform any deferred output processing. Must be called after the
+ // last AddString() call has occured.
+ bool Finalize();
+
+ private:
+ // Add a line to the rectangular region at the current position,
+ // either by itself or by breaking it into words.
+ void AddLine(const string16& line);
+
+ // Add a word to the rectangluar region at the current position,
+ // either by itelf or by breaking it into characters.
+ void AddWord(const string16& word);
+
+ // Add text to the output string if the rectangular boundaries
+ // have not been exceeded, advancing the current position.
+ void Append(const string16& string);
+
+ // Add a newline to the output string if the rectangular boundaries
+ // have not been exceeded, resetting the current position to the
+ // beginning of the next line.
+ void NewLine();
+
+ // Maximum number of rows allowed in the output string.
+ size_t max_rows_;
+
+ // Maximum number of characters allowed in the output string.
+ size_t max_cols_;
+
+ // Current row position, always incremented and may exceed max_rows_
+ // when the input can not fit in the region. We stop appending to
+ // the output string, however, when this condition occurs. In the
+ // future, we may want to expose this value to allow the caller to
+ // determine how many rows would actually be required to hold the
+ // formatted string.
+ size_t current_row_;
+
+ // Current character position, should never exceed max_cols_.
+ size_t current_col_;
+
+ // True when some of the input has been truncated.
+ bool suppressed_;
+
+ // String onto which the output is accumulated.
+ string16 *output_;
+};
+
+void RectangleString::AddString(const string16& input) {
+ base::BreakIterator lines(&input, base::BreakIterator::BREAK_NEWLINE);
+ if (lines.Init()) {
+ while (lines.Advance())
+ AddLine(lines.GetString());
+ } else {
+ NOTREACHED() << "BreakIterator (lines) init failed";
+ }
+}
+
+bool RectangleString::Finalize() {
+ if (suppressed_) {
+ output_->append(ASCIIToUTF16("..."));
+ return true;
+ }
+ return false;
+}
+
+void RectangleString::AddLine(const string16& line) {
+ if (line.length() < max_cols_) {
+ Append(line);
+ } else {
+ base::BreakIterator words(&line, base::BreakIterator::BREAK_SPACE);
+ if (words.Init()) {
+ while (words.Advance())
+ AddWord(words.GetString());
+ } else {
+ NOTREACHED() << "BreakIterator (words) init failed";
+ }
+ }
+ // Account for naturally-occuring newlines.
+ ++current_row_;
+ current_col_ = 0;
+}
+
+void RectangleString::AddWord(const string16& word) {
+ if (word.length() < max_cols_) {
+ // Word can be made to fit, no need to fragment it.
+ if (current_col_ + word.length() >= max_cols_)
+ NewLine();
+ Append(word);
+ } else {
+ // Word is so big that it must be fragmented.
+ int array_start = 0;
+ int char_start = 0;
+ base::UTF16CharIterator chars(&word);
+ while (!chars.end()) {
+ // When boundary is hit, add as much as will fit on this line.
+ if (current_col_ + (chars.char_pos() - char_start) >= max_cols_) {
+ Append(word.substr(array_start, chars.array_pos() - array_start));
+ NewLine();
+ array_start = chars.array_pos();
+ char_start = chars.char_pos();
+ }
+ chars.Advance();
+ }
+ // add last remaining fragment, if any.
+ if (array_start != chars.array_pos())
+ Append(word.substr(array_start, chars.array_pos() - array_start));
+ }
+}
+
+void RectangleString::Append(const string16& string) {
+ if (current_row_ < max_rows_)
+ output_->append(string);
+ else
+ suppressed_ = true;
+ current_col_ += string.length();
+}
+
+void RectangleString::NewLine() {
+ if (current_row_ < max_rows_)
+ output_->append(ASCIIToUTF16("\n"));
+ else
+ suppressed_ = true;
+ ++current_row_;
+ current_col_ = 0;
+}
+
+} // namespace
+
+namespace gfx {
+
+bool ElideRectangleString(const string16& input, size_t max_rows,
+ size_t max_cols, string16* output) {
+ RectangleString rect(max_rows, max_cols, output);
+ rect.Init();
+ rect.AddString(input);
+ return rect.Finalize();
+}
+
+} // namespace gfx
+
diff --git a/app/text_elider.h b/app/text_elider.h
index c0d6c33..d6da860 100644
--- a/app/text_elider.h
+++ b/app/text_elider.h
@@ -88,9 +88,11 @@ class SortedDisplayURL {
string16 display_url_;
};
-// Function to elide strings when the font information is unknown. As
-// opposed to the above functions, the ElideString() function operates
-// in terms of character units, not pixels.
+// Functions to elide strings when the font information is unknown. As
+// opposed to the above functions, the ElideString() and
+// ElideRectangleString() functions operate in terms of character units,
+// not pixels.
+
// If the size of |input| is more than |max_len|, this function returns
// true and |input| is shortened into |output| by removing chars in the
// middle (they are replaced with up to 3 dots, as size permits).
@@ -101,6 +103,17 @@ class SortedDisplayURL {
// TODO(tsepez): Doesn't handle bidi properly
bool ElideString(const std::wstring& input, int max_len, std::wstring* output);
+// Reformat |input| into |output| so that it fits into a |max_rows| by
+// |max_cols| rectangle of characters. Input newlines are respected, but
+// lines that are too long are broken into pieces, first at naturally
+// occuring whitespace boundaries, and then intra-word (respecting UTF-16
+// surrogate pairs) as necssary. Truncation (indicated by an added 3 dots)
+// occurs if the result is still too long. Returns true if the input had
+// to be truncated (and not just reformatted).
+bool ElideRectangleString(const string16& input, size_t max_rows,
+ size_t max_cols, string16* output);
+
+
} // namespace gfx.
#endif // APP_TEXT_ELIDER_H_
diff --git a/app/text_elider_unittest.cc b/app/text_elider_unittest.cc
index e5a29d9..ab1a73f 100644
--- a/app/text_elider_unittest.cc
+++ b/app/text_elider_unittest.cc
@@ -319,6 +319,122 @@ TEST(TextEliderTest, ElideString) {
std::wstring output;
EXPECT_EQ(cases[i].result,
gfx::ElideString(cases[i].input, cases[i].max_len, &output));
- EXPECT_TRUE(output == cases[i].output);
+ EXPECT_EQ(cases[i].output, output);
}
}
+
+TEST(TextEliderTest, ElideRectangleString) {
+ struct TestData {
+ const wchar_t* input;
+ int max_rows;
+ int max_cols;
+ bool result;
+ const wchar_t* output;
+ } cases[] = {
+ { L"", 0, 0, false, L"" },
+ { L"", 1, 1, false, L"" },
+ { L"Hi, my name is\nTom", 0, 0, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 0, true, L"\n..." },
+ { L"Hi, my name is\nTom", 0, 1, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 1, true, L"H\n..." },
+ { L"Hi, my name is\nTom", 2, 1, true, L"H\ni\n..." },
+ { L"Hi, my name is\nTom", 3, 1, true, L"H\ni\n,\n..." },
+ { L"Hi, my name is\nTom", 4, 1, true, L"H\ni\n,\n \n..." },
+ { L"Hi, my name is\nTom", 5, 1, true, L"H\ni\n,\n \nm\n..." },
+ { L"Hi, my name is\nTom", 0, 2, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 2, true, L"Hi\n..." },
+ { L"Hi, my name is\nTom", 2, 2, true, L"Hi\n, \n..." },
+ { L"Hi, my name is\nTom", 3, 2, true, L"Hi\n, \nmy\n..." },
+ { L"Hi, my name is\nTom", 4, 2, true, L"Hi\n, \nmy\n n\n..." },
+ { L"Hi, my name is\nTom", 5, 2, true, L"Hi\n, \nmy\n n\nam\n..." },
+ { L"Hi, my name is\nTom", 0, 3, true, L"..." },
+ { L"Hi, my name is\nTom", 1, 3, true, L"Hi,\n..." },
+ { L"Hi, my name is\nTom", 2, 3, true, L"Hi,\n my\n..." },
+ { L"Hi, my name is\nTom", 3, 3, true, L"Hi,\n my\n na\n..." },
+ { L"Hi, my name is\nTom", 4, 3, true, L"Hi,\n my\n na\nme \n..." },
+ { L"Hi, my name is\nTom", 5, 3, true, L"Hi,\n my\n na\nme \nis\n..." },
+ { L"Hi, my name is\nTom", 1, 4, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 4, true, L"Hi, \nmy n\n..." },
+ { L"Hi, my name is\nTom", 3, 4, true, L"Hi, \nmy n\name \n..." },
+ { L"Hi, my name is\nTom", 4, 4, true, L"Hi, \nmy n\name \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 4, false, L"Hi, \nmy n\name \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 5, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 5, true, L"Hi, \nmy na\n..." },
+ { L"Hi, my name is\nTom", 3, 5, true, L"Hi, \nmy na\nme \n..." },
+ { L"Hi, my name is\nTom", 4, 5, true, L"Hi, \nmy na\nme \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 5, false, L"Hi, \nmy na\nme \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 6, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 6, true, L"Hi, \nmy \n..." },
+ { L"Hi, my name is\nTom", 3, 6, true, L"Hi, \nmy \nname \n..." },
+ { L"Hi, my name is\nTom", 4, 6, true, L"Hi, \nmy \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 6, false, L"Hi, \nmy \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 7, true, L"Hi, \n..." },
+ { L"Hi, my name is\nTom", 2, 7, true, L"Hi, \nmy \n..." },
+ { L"Hi, my name is\nTom", 3, 7, true, L"Hi, \nmy \nname \n..." },
+ { L"Hi, my name is\nTom", 4, 7, true, L"Hi, \nmy \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 5, 7, false, L"Hi, \nmy \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 8, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 8, true, L"Hi, my \nname \n..." },
+ { L"Hi, my name is\nTom", 3, 8, true, L"Hi, my \nname \nis\n..." },
+ { L"Hi, my name is\nTom", 4, 8, false, L"Hi, my \nname \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 9, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 9, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 9, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 10, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 10, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 10, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 11, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 11, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 11, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 12, true, L"Hi, my \n..." },
+ { L"Hi, my name is\nTom", 2, 12, true, L"Hi, my \nname is\n..." },
+ { L"Hi, my name is\nTom", 3, 12, false, L"Hi, my \nname is\nTom" },
+ { L"Hi, my name is\nTom", 1, 13, true, L"Hi, my name \n..." },
+ { L"Hi, my name is\nTom", 2, 13, true, L"Hi, my name \nis\n..." },
+ { L"Hi, my name is\nTom", 3, 13, false, L"Hi, my name \nis\nTom" },
+ { L"Hi, my name is\nTom", 1, 20, true, L"Hi, my name is\n..." },
+ { L"Hi, my name is\nTom", 2, 20, false, L"Hi, my name is\nTom" },
+ { L"Hi, my name is Tom", 1, 40, false, L"Hi, my name is Tom" },
+ };
+ string16 output;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ EXPECT_EQ(cases[i].result,
+ gfx::ElideRectangleString(WideToUTF16(cases[i].input),
+ cases[i].max_rows, cases[i].max_cols,
+ &output));
+ EXPECT_EQ(cases[i].output, UTF16ToWide(output));
+ }
+}
+
+TEST(TextEliderTest, ElideRectangleWide16) {
+ // Two greek words separated by space.
+ const string16 str(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9"
+ L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2"));
+ const string16 out1(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\n"
+ L"\x03cc\x03c3\x03bc\x03b9\n"
+ L"..."));
+ const string16 out2(WideToUTF16(
+ L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9\x03bf\x03c2\x0020\n"
+ L"\x0399\x03c3\x03c4\x03cc\x03c2"));
+ string16 output;
+ EXPECT_TRUE(gfx::ElideRectangleString(str, 2, 4, &output));
+ EXPECT_EQ(out1, output);
+ EXPECT_FALSE(gfx::ElideRectangleString(str, 2, 12, &output));
+ EXPECT_EQ(out2, output);
+}
+
+TEST(TextEliderTest, ElideRectangleWide32) {
+ // Four U+1D49C MATHEMATICAL SCRIPT CAPITAL A followed by space "aaaaa".
+ const string16 str(UTF8ToUTF16(
+ "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C"
+ " aaaaa"));
+ const string16 out(UTF8ToUTF16(
+ "\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\xF0\x9D\x92\x9C\n"
+ "\xF0\x9D\x92\x9C \naaa\n..."));
+ string16 output;
+ EXPECT_TRUE(gfx::ElideRectangleString(str, 3, 3, &output));
+ EXPECT_EQ(out, output);
+}
+
diff --git a/chrome/browser/ui/app_modal_dialogs/js_modal_dialog.cc b/chrome/browser/ui/app_modal_dialogs/js_modal_dialog.cc
index 8fe0499..801562d 100644
--- a/chrome/browser/ui/app_modal_dialogs/js_modal_dialog.cc
+++ b/chrome/browser/ui/app_modal_dialogs/js_modal_dialog.cc
@@ -6,6 +6,7 @@
#include "app/text_elider.h"
#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_shutdown.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/tab_contents/tab_contents.h"
@@ -17,7 +18,8 @@
namespace {
// The maximum sizes of various texts passed to us from javascript.
-const int kMessageTextMaxSize = 3000;
+const int kMessageTextMaxRows = 32;
+const int kMessageTextMaxCols = 132;
const int kDefaultPromptTextSize = 2000;
} // namespace
@@ -40,7 +42,10 @@ JavaScriptAppModalDialog::JavaScriptAppModalDialog(
reply_msg_(reply_msg) {
// We trim the various parts of the message dialog because otherwise we can
// overflow the message dialog (and crash/hang the GTK+ version).
- gfx::ElideString(message_text, kMessageTextMaxSize, &message_text_);
+ string16 elided_text;
+ gfx::ElideRectangleString(WideToUTF16(message_text),
+ kMessageTextMaxRows, kMessageTextMaxCols, &elided_text);
+ message_text_ = UTF16ToWide(elided_text);
gfx::ElideString(default_prompt_text, kDefaultPromptTextSize,
&default_prompt_text_);