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