OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 // | 4 // |
5 // This file implements utility functions for eliding and formatting UI text. | 5 // This file implements utility functions for eliding and formatting UI text. |
6 // | 6 // |
7 // Note that several of the functions declared in text_elider.h are implemented | 7 // Note that several of the functions declared in text_elider.h are implemented |
8 // in this file using helper classes in an unnamed namespace. | 8 // in this file using helper classes in an unnamed namespace. |
9 | 9 |
10 #include "ui/base/text/text_elider.h" | 10 #include "ui/gfx/text_elider.h" |
11 | 11 |
12 #include <string> | 12 #include <string> |
13 #include <vector> | 13 #include <vector> |
14 | 14 |
15 #include "base/files/file_path.h" | 15 #include "base/files/file_path.h" |
16 #include "base/i18n/break_iterator.h" | 16 #include "base/i18n/break_iterator.h" |
17 #include "base/i18n/char_iterator.h" | 17 #include "base/i18n/char_iterator.h" |
18 #include "base/i18n/rtl.h" | 18 #include "base/i18n/rtl.h" |
19 #include "base/memory/scoped_ptr.h" | 19 #include "base/memory/scoped_ptr.h" |
20 #include "base/strings/string_split.h" | 20 #include "base/strings/string_split.h" |
21 #include "base/strings/string_util.h" | 21 #include "base/strings/string_util.h" |
22 #include "base/strings/sys_string_conversions.h" | 22 #include "base/strings/sys_string_conversions.h" |
23 #include "base/strings/utf_string_conversions.h" | 23 #include "base/strings/utf_string_conversions.h" |
24 #include "net/base/escape.h" | 24 #include "net/base/escape.h" |
25 #include "net/base/net_util.h" | 25 #include "net/base/net_util.h" |
26 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 26 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
27 #include "third_party/icu/source/common/unicode/rbbi.h" | 27 #include "third_party/icu/source/common/unicode/rbbi.h" |
28 #include "third_party/icu/source/common/unicode/uloc.h" | 28 #include "third_party/icu/source/common/unicode/uloc.h" |
29 #include "ui/gfx/font_list.h" | 29 #include "ui/gfx/font_list.h" |
30 #include "ui/gfx/text_utils.h" | 30 #include "ui/gfx/text_utils.h" |
31 #include "url/gurl.h" | 31 #include "url/gurl.h" |
32 | 32 |
33 namespace ui { | 33 namespace gfx { |
34 | 34 |
35 // U+2026 in utf8 | 35 // U+2026 in utf8 |
36 const char kEllipsis[] = "\xE2\x80\xA6"; | 36 const char kEllipsis[] = "\xE2\x80\xA6"; |
37 const char16 kEllipsisUTF16[] = { 0x2026, 0 }; | 37 const char16 kEllipsisUTF16[] = { 0x2026, 0 }; |
38 const char16 kForwardSlash = '/'; | 38 const char16 kForwardSlash = '/'; |
39 | 39 |
40 namespace { | 40 namespace { |
41 | 41 |
42 // Helper class to split + elide text, while respecting UTF16 surrogate pairs. | 42 // Helper class to split + elide text, while respecting UTF16 surrogate pairs. |
43 class StringSlicer { | 43 class StringSlicer { |
(...skipping 591 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
635 int lstr_len = rstr_len + ((max_len - 3) % 2); | 635 int lstr_len = rstr_len + ((max_len - 3) % 2); |
636 output->assign(input.substr(0, lstr_len) + ASCIIToUTF16("...") + | 636 output->assign(input.substr(0, lstr_len) + ASCIIToUTF16("...") + |
637 input.substr(input.length() - rstr_len)); | 637 input.substr(input.length() - rstr_len)); |
638 break; | 638 break; |
639 } | 639 } |
640 } | 640 } |
641 | 641 |
642 return true; | 642 return true; |
643 } | 643 } |
644 | 644 |
645 } // namespace ui | |
646 | |
647 namespace { | 645 namespace { |
648 | 646 |
649 // Internal class used to track progress of a rectangular string elide | 647 // Internal class used to track progress of a rectangular string elide |
650 // operation. Exists so the top-level ElideRectangleString() function | 648 // operation. Exists so the top-level ElideRectangleString() function |
651 // can be broken into smaller methods sharing this state. | 649 // can be broken into smaller methods sharing this state. |
652 class RectangleString { | 650 class RectangleString { |
653 public: | 651 public: |
654 RectangleString(size_t max_rows, size_t max_cols, | 652 RectangleString(size_t max_rows, size_t max_cols, |
655 bool strict, string16 *output) | 653 bool strict, string16 *output) |
656 : max_rows_(max_rows), | 654 : max_rows_(max_rows), |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
807 } | 805 } |
808 | 806 |
809 // Internal class used to track progress of a rectangular text elide | 807 // Internal class used to track progress of a rectangular text elide |
810 // operation. Exists so the top-level ElideRectangleText() function | 808 // operation. Exists so the top-level ElideRectangleText() function |
811 // can be broken into smaller methods sharing this state. | 809 // can be broken into smaller methods sharing this state. |
812 class RectangleText { | 810 class RectangleText { |
813 public: | 811 public: |
814 RectangleText(const gfx::FontList& font_list, | 812 RectangleText(const gfx::FontList& font_list, |
815 int available_pixel_width, | 813 int available_pixel_width, |
816 int available_pixel_height, | 814 int available_pixel_height, |
817 ui::WordWrapBehavior wrap_behavior, | 815 WordWrapBehavior wrap_behavior, |
818 std::vector<string16>* lines) | 816 std::vector<string16>* lines) |
819 : font_list_(font_list), | 817 : font_list_(font_list), |
820 line_height_(font_list.GetHeight()), | 818 line_height_(font_list.GetHeight()), |
821 available_pixel_width_(available_pixel_width), | 819 available_pixel_width_(available_pixel_width), |
822 available_pixel_height_(available_pixel_height), | 820 available_pixel_height_(available_pixel_height), |
823 wrap_behavior_(wrap_behavior), | 821 wrap_behavior_(wrap_behavior), |
824 current_width_(0), | 822 current_width_(0), |
825 current_height_(0), | 823 current_height_(0), |
826 last_line_ended_in_lf_(false), | 824 last_line_ended_in_lf_(false), |
827 lines_(lines), | 825 lines_(lines), |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
875 // The height of each line of text. | 873 // The height of each line of text. |
876 const int line_height_; | 874 const int line_height_; |
877 | 875 |
878 // The number of pixels of available width in the rectangle. | 876 // The number of pixels of available width in the rectangle. |
879 const int available_pixel_width_; | 877 const int available_pixel_width_; |
880 | 878 |
881 // The number of pixels of available height in the rectangle. | 879 // The number of pixels of available height in the rectangle. |
882 const int available_pixel_height_; | 880 const int available_pixel_height_; |
883 | 881 |
884 // The wrap behavior for words that are too long to fit on a single line. | 882 // The wrap behavior for words that are too long to fit on a single line. |
885 const ui::WordWrapBehavior wrap_behavior_; | 883 const WordWrapBehavior wrap_behavior_; |
886 | 884 |
887 // The current running width. | 885 // The current running width. |
888 int current_width_; | 886 int current_width_; |
889 | 887 |
890 // The current running height. | 888 // The current running height. |
891 int current_height_; | 889 int current_height_; |
892 | 890 |
893 // The current line of text. | 891 // The current line of text. |
894 string16 current_line_; | 892 string16 current_line_; |
895 | 893 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
931 int RectangleText::Finalize() { | 929 int RectangleText::Finalize() { |
932 // Remove trailing whitespace from the last line or remove the last line | 930 // Remove trailing whitespace from the last line or remove the last line |
933 // completely, if it's just whitespace. | 931 // completely, if it's just whitespace. |
934 if (!insufficient_height_ && !lines_->empty()) { | 932 if (!insufficient_height_ && !lines_->empty()) { |
935 TrimWhitespace(lines_->back(), TRIM_TRAILING, &lines_->back()); | 933 TrimWhitespace(lines_->back(), TRIM_TRAILING, &lines_->back()); |
936 if (lines_->back().empty() && !last_line_ended_in_lf_) | 934 if (lines_->back().empty() && !last_line_ended_in_lf_) |
937 lines_->pop_back(); | 935 lines_->pop_back(); |
938 } | 936 } |
939 if (last_line_ended_in_lf_) | 937 if (last_line_ended_in_lf_) |
940 lines_->push_back(string16()); | 938 lines_->push_back(string16()); |
941 return (insufficient_width_ ? ui::INSUFFICIENT_SPACE_HORIZONTAL : 0) | | 939 return (insufficient_width_ ? INSUFFICIENT_SPACE_HORIZONTAL : 0) | |
942 (insufficient_height_ ? ui::INSUFFICIENT_SPACE_VERTICAL : 0); | 940 (insufficient_height_ ? INSUFFICIENT_SPACE_VERTICAL : 0); |
943 } | 941 } |
944 | 942 |
945 void RectangleText::AddLine(const string16& line) { | 943 void RectangleText::AddLine(const string16& line) { |
946 const int line_width = gfx::GetStringWidth(line, font_list_); | 944 const int line_width = gfx::GetStringWidth(line, font_list_); |
947 if (line_width <= available_pixel_width_) { | 945 if (line_width <= available_pixel_width_) { |
948 AddToCurrentLineWithWidth(line, line_width); | 946 AddToCurrentLineWithWidth(line, line_width); |
949 } else { | 947 } else { |
950 // Iterate over positions that are valid to break the line at. In general, | 948 // Iterate over positions that are valid to break the line at. In general, |
951 // these are word boundaries but after any punctuation following the word. | 949 // these are word boundaries but after any punctuation following the word. |
952 base::i18n::BreakIterator words(line, | 950 base::i18n::BreakIterator words(line, |
(...skipping 24 matching lines...) Expand all Loading... |
977 NewLine(); | 975 NewLine(); |
978 } | 976 } |
979 | 977 |
980 int RectangleText::WrapWord(const string16& word) { | 978 int RectangleText::WrapWord(const string16& word) { |
981 // Word is so wide that it must be fragmented. | 979 // Word is so wide that it must be fragmented. |
982 string16 text = word; | 980 string16 text = word; |
983 int lines_added = 0; | 981 int lines_added = 0; |
984 bool first_fragment = true; | 982 bool first_fragment = true; |
985 while (!insufficient_height_ && !text.empty()) { | 983 while (!insufficient_height_ && !text.empty()) { |
986 string16 fragment = | 984 string16 fragment = |
987 ui::ElideText(text, font_list_, available_pixel_width_, | 985 ElideText(text, font_list_, available_pixel_width_, |
988 ui::TRUNCATE_AT_END); | 986 TRUNCATE_AT_END); |
989 // At least one character has to be added at every line, even if the | 987 // At least one character has to be added at every line, even if the |
990 // available space is too small. | 988 // available space is too small. |
991 if(fragment.empty()) | 989 if(fragment.empty()) |
992 fragment = text.substr(0, 1); | 990 fragment = text.substr(0, 1); |
993 if (!first_fragment && NewLine()) | 991 if (!first_fragment && NewLine()) |
994 lines_added++; | 992 lines_added++; |
995 AddToCurrentLine(fragment); | 993 AddToCurrentLine(fragment); |
996 text = text.substr(fragment.length()); | 994 text = text.substr(fragment.length()); |
997 first_fragment = false; | 995 first_fragment = false; |
998 } | 996 } |
999 return lines_added; | 997 return lines_added; |
1000 } | 998 } |
1001 | 999 |
1002 int RectangleText::AddWordOverflow(const string16& word) { | 1000 int RectangleText::AddWordOverflow(const string16& word) { |
1003 int lines_added = 0; | 1001 int lines_added = 0; |
1004 | 1002 |
1005 // Unless this is the very first word, put it on a new line. | 1003 // Unless this is the very first word, put it on a new line. |
1006 if (!current_line_.empty()) { | 1004 if (!current_line_.empty()) { |
1007 if (!NewLine()) | 1005 if (!NewLine()) |
1008 return 0; | 1006 return 0; |
1009 lines_added++; | 1007 lines_added++; |
1010 } | 1008 } |
1011 | 1009 |
1012 if (wrap_behavior_ == ui::IGNORE_LONG_WORDS) { | 1010 if (wrap_behavior_ == IGNORE_LONG_WORDS) { |
1013 current_line_ = word; | 1011 current_line_ = word; |
1014 current_width_ = available_pixel_width_; | 1012 current_width_ = available_pixel_width_; |
1015 } else if (wrap_behavior_ == ui::WRAP_LONG_WORDS) { | 1013 } else if (wrap_behavior_ == WRAP_LONG_WORDS) { |
1016 lines_added += WrapWord(word); | 1014 lines_added += WrapWord(word); |
1017 } else { | 1015 } else { |
1018 const ui::ElideBehavior elide_behavior = | 1016 const ElideBehavior elide_behavior = |
1019 (wrap_behavior_ == ui::ELIDE_LONG_WORDS ? ui::ELIDE_AT_END : | 1017 (wrap_behavior_ == ELIDE_LONG_WORDS ? ELIDE_AT_END : TRUNCATE_AT_END); |
1020 ui::TRUNCATE_AT_END); | |
1021 const string16 elided_word = | 1018 const string16 elided_word = |
1022 ui::ElideText(word, font_list_, available_pixel_width_, elide_behavior); | 1019 ElideText(word, font_list_, available_pixel_width_, elide_behavior); |
1023 AddToCurrentLine(elided_word); | 1020 AddToCurrentLine(elided_word); |
1024 insufficient_width_ = true; | 1021 insufficient_width_ = true; |
1025 } | 1022 } |
1026 | 1023 |
1027 return lines_added; | 1024 return lines_added; |
1028 } | 1025 } |
1029 | 1026 |
1030 int RectangleText::AddWord(const string16& word) { | 1027 int RectangleText::AddWord(const string16& word) { |
1031 int lines_added = 0; | 1028 int lines_added = 0; |
1032 string16 trimmed; | 1029 string16 trimmed; |
1033 TrimWhitespace(word, TRIM_TRAILING, &trimmed); | 1030 TrimWhitespace(word, TRIM_TRAILING, &trimmed); |
1034 const int trimmed_width = gfx::GetStringWidth(trimmed, font_list_); | 1031 const int trimmed_width = gfx::GetStringWidth(trimmed, font_list_); |
1035 if (trimmed_width <= available_pixel_width_) { | 1032 if (trimmed_width <= available_pixel_width_) { |
1036 // Word can be made to fit, no need to fragment it. | 1033 // Word can be made to fit, no need to fragment it. |
1037 if ((current_width_ + trimmed_width > available_pixel_width_) && NewLine()) | 1034 if ((current_width_ + trimmed_width > available_pixel_width_) && NewLine()) |
1038 lines_added++; | 1035 lines_added++; |
1039 // Append the non-trimmed word, in case more words are added after. | 1036 // Append the non-trimmed word, in case more words are added after. |
1040 AddToCurrentLine(word); | 1037 AddToCurrentLine(word); |
1041 } else { | 1038 } else { |
1042 lines_added = AddWordOverflow(wrap_behavior_ == ui::IGNORE_LONG_WORDS ? | 1039 lines_added = AddWordOverflow(wrap_behavior_ == IGNORE_LONG_WORDS ? |
1043 trimmed : word); | 1040 trimmed : word); |
1044 } | 1041 } |
1045 return lines_added; | 1042 return lines_added; |
1046 } | 1043 } |
1047 | 1044 |
1048 void RectangleText::AddToCurrentLine(const string16& text) { | 1045 void RectangleText::AddToCurrentLine(const string16& text) { |
1049 AddToCurrentLineWithWidth(text, gfx::GetStringWidth(text, font_list_)); | 1046 AddToCurrentLineWithWidth(text, gfx::GetStringWidth(text, font_list_)); |
1050 } | 1047 } |
1051 | 1048 |
1052 void RectangleText::AddToCurrentLineWithWidth(const string16& text, | 1049 void RectangleText::AddToCurrentLineWithWidth(const string16& text, |
(...skipping 15 matching lines...) Expand all Loading... |
1068 } else { | 1065 } else { |
1069 insufficient_height_ = true; | 1066 insufficient_height_ = true; |
1070 } | 1067 } |
1071 current_height_ += line_height_; | 1068 current_height_ += line_height_; |
1072 current_width_ = 0; | 1069 current_width_ = 0; |
1073 return line_added; | 1070 return line_added; |
1074 } | 1071 } |
1075 | 1072 |
1076 } // namespace | 1073 } // namespace |
1077 | 1074 |
1078 namespace ui { | |
1079 | |
1080 bool ElideRectangleString(const string16& input, size_t max_rows, | 1075 bool ElideRectangleString(const string16& input, size_t max_rows, |
1081 size_t max_cols, bool strict, string16* output) { | 1076 size_t max_cols, bool strict, string16* output) { |
1082 RectangleString rect(max_rows, max_cols, strict, output); | 1077 RectangleString rect(max_rows, max_cols, strict, output); |
1083 rect.Init(); | 1078 rect.Init(); |
1084 rect.AddString(input); | 1079 rect.AddString(input); |
1085 return rect.Finalize(); | 1080 return rect.Finalize(); |
1086 } | 1081 } |
1087 | 1082 |
1088 int ElideRectangleText(const string16& input, | 1083 int ElideRectangleText(const string16& input, |
1089 const gfx::FontList& font_list, | 1084 const gfx::FontList& font_list, |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1167 // Found a valid break point. | 1162 // Found a valid break point. |
1168 index = char_iterator.getIndex(); | 1163 index = char_iterator.getIndex(); |
1169 } else { | 1164 } else { |
1170 // String has leading whitespace, return the elide string. | 1165 // String has leading whitespace, return the elide string. |
1171 return kElideString; | 1166 return kElideString; |
1172 } | 1167 } |
1173 } | 1168 } |
1174 return string.substr(0, index) + kElideString; | 1169 return string.substr(0, index) + kElideString; |
1175 } | 1170 } |
1176 | 1171 |
1177 } // namespace ui | 1172 } // namespace gfx |
OLD | NEW |