1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/gfx/text_utils.h"
6
7 #include <stdint.h>
8
9 #include "base/i18n/char_iterator.h"
10 #include "base/i18n/rtl.h"
11 #include "base/numerics/safe_conversions.h"
12 #include "third_party/icu/source/common/unicode/uchar.h"
13 #include "third_party/icu/source/common/unicode/utf16.h"
14 #include "ui/gfx/font_list.h"
15 #include "ui/gfx/geometry/insets.h"
16 #include "ui/gfx/geometry/rect.h"
17 #include "ui/gfx/geometry/size.h"
18
19 namespace gfx {
20
21 using base::i18n::UTF16CharIterator;
22
23 namespace {
24
25 // Returns true if the specified character must be elided from a string.
26 // Examples are combining marks and whitespace.
IsCombiningMark(UChar32 c)27 bool IsCombiningMark(UChar32 c) {
28 const int8_t char_type = u_charType(c);
29 return char_type == U_NON_SPACING_MARK || char_type == U_ENCLOSING_MARK ||
30 char_type == U_COMBINING_SPACING_MARK;
31 }
32
IsSpace(UChar32 c)33 bool IsSpace(UChar32 c) {
34 // Ignore NUL character.
35 if (!c)
36 return false;
37 const int8_t char_type = u_charType(c);
38 return char_type == U_SPACE_SEPARATOR || char_type == U_LINE_SEPARATOR ||
39 char_type == U_PARAGRAPH_SEPARATOR || char_type == U_CONTROL_CHAR;
40 }
41
42 } // namespace
43
RemoveAcceleratorChar(const base::string16 & s,base::char16 accelerator_char,int * accelerated_char_pos,int * accelerated_char_span)44 base::string16 RemoveAcceleratorChar(const base::string16& s,
45 base::char16 accelerator_char,
46 int* accelerated_char_pos,
47 int* accelerated_char_span) {
48 bool escaped = false;
49 ptrdiff_t last_char_pos = -1;
50 int last_char_span = 0;
51 UTF16CharIterator chars(s);
52 base::string16 accelerator_removed;
53
54 accelerator_removed.reserve(s.size());
55 while (!chars.end()) {
56 int32_t c = chars.get();
57 int array_pos = chars.array_pos();
58 chars.Advance();
59
60 if (c != accelerator_char || escaped) {
61 int span = chars.array_pos() - array_pos;
62 if (escaped && c != accelerator_char) {
63 last_char_pos = accelerator_removed.size();
64 last_char_span = span;
65 }
66 for (int i = 0; i < span; i++)
67 accelerator_removed.push_back(s[array_pos + i]);
68 escaped = false;
69 } else {
70 escaped = true;
71 }
72 }
73
74 if (accelerated_char_pos)
75 *accelerated_char_pos = last_char_pos;
76 if (accelerated_char_span)
77 *accelerated_char_span = last_char_span;
78
79 return accelerator_removed;
80 }
81
FindValidBoundaryBefore(const base::string16 & text,size_t index,bool trim_whitespace)82 size_t FindValidBoundaryBefore(const base::string16& text,
83 size_t index,
84 bool trim_whitespace) {
85 UTF16CharIterator it = UTF16CharIterator::LowerBound(text, index);
86
87 // First, move left until we're positioned on a code point that is not a
88 // combining mark.
89 while (!it.start() && IsCombiningMark(it.get()))
90 it.Rewind();
91
92 // Next, maybe trim whitespace to the left of the current position.
93 if (trim_whitespace) {
94 while (!it.start() && IsSpace(it.PreviousCodePoint()))
95 it.Rewind();
96 }
97
98 return it.array_pos();
99 }
100
FindValidBoundaryAfter(const base::string16 & text,size_t index,bool trim_whitespace)101 size_t FindValidBoundaryAfter(const base::string16& text,
102 size_t index,
103 bool trim_whitespace) {
104 UTF16CharIterator it = UTF16CharIterator::UpperBound(text, index);
105
106 // First, move right until we're positioned on a code point that is not a
107 // combining mark.
108 while (!it.end() && IsCombiningMark(it.get()))
109 it.Advance();
110
111 // Next, maybe trim space at the current position.
112 if (trim_whitespace) {
113 // A mark combining with a space is renderable, so we'll prevent
114 // trimming spaces with combining marks.
115 while (!it.end() && IsSpace(it.get()) &&
116 !IsCombiningMark(it.NextCodePoint())) {
117 it.Advance();
118 }
119 }
120
121 return it.array_pos();
122 }
123
MaybeFlipForRTL(HorizontalAlignment alignment)124 HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment) {
125 if (base::i18n::IsRTL() &&
126 (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) {
127 alignment =
128 (alignment == gfx::ALIGN_LEFT) ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
129 }
130 return alignment;
131 }
132
GetStringSize(const base::string16 & text,const FontList & font_list)133 Size GetStringSize(const base::string16& text, const FontList& font_list) {
134 return Size(GetStringWidth(text, font_list), font_list.GetHeight());
135 }
136
AdjustVisualBorderForFont(const FontList & font_list,const Insets & desired_visual_padding)137 Insets AdjustVisualBorderForFont(const FontList& font_list,
138 const Insets& desired_visual_padding) {
139 Insets result = desired_visual_padding;
140 const int baseline = font_list.GetBaseline();
141 const int leading_space = baseline - font_list.GetCapHeight();
142 const int descender = font_list.GetHeight() - baseline;
143 result.set_top(std::max(0, result.top() - leading_space));
144 result.set_bottom(std::max(0, result.bottom() - descender));
145 return result;
146 }
147
GetFontCapHeightCenterOffset(const gfx::FontList & original_font,const gfx::FontList & to_center)148 int GetFontCapHeightCenterOffset(const gfx::FontList& original_font,
149 const gfx::FontList& to_center) {
150 const int original_cap_height = original_font.GetCapHeight();
151 const int original_cap_leading =
152 original_font.GetBaseline() - original_cap_height;
153 const int to_center_cap_height = to_center.GetCapHeight();
154 const int to_center_leading = to_center.GetBaseline() - to_center_cap_height;
155
156 const int cap_height_diff = original_cap_height - to_center_cap_height;
157 const int new_cap_top =
158 original_cap_leading + std::lround(cap_height_diff / 2.0f);
159 const int new_top = new_cap_top - to_center_leading;
160
161 // Since we assume the old font starts at zero, the new top is the adjustment.
162 return new_top;
163 }
164
165 } // namespace gfx
166