1 // Copyright 2014 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/render_text_harfbuzz.h"
6 
7 #include <limits>
8 #include <set>
9 
10 #include "base/command_line.h"
11 #include "base/containers/mru_cache.h"
12 #include "base/containers/span.h"
13 #include "base/feature_list.h"
14 #include "base/hash/hash.h"
15 #include "base/i18n/base_i18n_switches.h"
16 #include "base/i18n/break_iterator.h"
17 #include "base/i18n/char_iterator.h"
18 #include "base/i18n/rtl.h"
19 #include "base/macros.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/metrics/histogram_macros.h"
22 #include "base/no_destructor.h"
23 #include "base/numerics/safe_conversions.h"
24 #include "base/stl_util.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "base/task/current_thread.h"
30 #include "base/trace_event/trace_event.h"
31 #include "build/build_config.h"
32 #include "third_party/icu/source/common/unicode/ubidi.h"
33 #include "third_party/icu/source/common/unicode/uscript.h"
34 #include "third_party/icu/source/common/unicode/utf16.h"
35 #include "third_party/skia/include/core/SkColor.h"
36 #include "third_party/skia/include/core/SkFontMetrics.h"
37 #include "third_party/skia/include/core/SkTypeface.h"
38 #include "ui/gfx/bidi_line_iterator.h"
39 #include "ui/gfx/canvas.h"
40 #include "ui/gfx/decorated_text.h"
41 #include "ui/gfx/font.h"
42 #include "ui/gfx/font_fallback.h"
43 #include "ui/gfx/font_render_params.h"
44 #include "ui/gfx/harfbuzz_font_skia.h"
45 #include "ui/gfx/platform_font.h"
46 #include "ui/gfx/range/range_f.h"
47 #include "ui/gfx/skia_util.h"
48 #include "ui/gfx/switches.h"
49 #include "ui/gfx/text_utils.h"
50 #include "ui/gfx/utf16_indexing.h"
51 
52 #if defined(OS_APPLE)
53 #include "base/mac/foundation_util.h"
54 #include "base/mac/mac_util.h"
55 #include "third_party/skia/include/ports/SkTypeface_mac.h"
56 #endif
57 
58 #if defined(OS_ANDROID)
59 #include "base/android/locale_utils.h"
60 #endif  // defined(OS_ANDROID)
61 
62 #include <hb.h>
63 
64 namespace gfx {
65 
66 namespace {
67 
68 // Text length limit. Longer strings are slow and not fully tested.
69 const size_t kMaxTextLength = 10000;
70 
71 // The maximum number of scripts a Unicode character can belong to. This value
72 // is arbitrarily chosen to be a good limit because it is unlikely for a single
73 // character to belong to more scripts.
74 const size_t kMaxScripts = 32;
75 
76 // Font fallback mechanism used to Shape runs (see ShapeRuns(...)).
77 // These values are persisted to logs. Entries should not be renumbered and
78 // numeric values should never be reused.
79 enum class ShapeRunFallback {
80   FAILED = 0,
81   NO_FALLBACK = 1,
82   FALLBACK = 2,
83   FALLBACKS = 3,
84   kMaxValue = FALLBACKS
85 };
86 
87 // Log the fallback font mechanism used for shaping to UMA (see ShapeRuns(...)).
RecordShapeRunsFallback(ShapeRunFallback fallback)88 void RecordShapeRunsFallback(ShapeRunFallback fallback) {
89   UMA_HISTOGRAM_ENUMERATION("RenderTextHarfBuzz.ShapeRunsFallback", fallback);
90 }
91 
92 // Returns whether the codepoint has the 'extended pictographic' property.
IsExtendedPictographicCodepoint(UChar32 codepoint)93 bool IsExtendedPictographicCodepoint(UChar32 codepoint) {
94   return u_hasBinaryProperty(codepoint, UCHAR_EXTENDED_PICTOGRAPHIC);
95 }
96 
97 // Returns whether the codepoint has emoji properties.
IsEmojiRelatedCodepoint(UChar32 codepoint)98 bool IsEmojiRelatedCodepoint(UChar32 codepoint) {
99   return u_hasBinaryProperty(codepoint, UCHAR_EMOJI) ||
100          u_hasBinaryProperty(codepoint, UCHAR_EMOJI_PRESENTATION) ||
101          u_hasBinaryProperty(codepoint, UCHAR_REGIONAL_INDICATOR);
102 }
103 
104 // Returns true if |codepoint| is a bracket. This is used to avoid "matching"
105 // brackets picking different font fallbacks, thereby appearing mismatched.
IsBracket(UChar32 codepoint)106 bool IsBracket(UChar32 codepoint) {
107   return u_getIntPropertyValue(codepoint, UCHAR_BIDI_PAIRED_BRACKET_TYPE) !=
108          U_BPT_NONE;
109 }
110 
111 // Writes the script and the script extensions of the Unicode |codepoint|.
112 // Returns the number of written scripts.
GetScriptExtensions(UChar32 codepoint,UScriptCode * scripts)113 size_t GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) {
114   // Fill |scripts| with the script extensions.
115   UErrorCode icu_error = U_ZERO_ERROR;
116   size_t count =
117       uscript_getScriptExtensions(codepoint, scripts, kMaxScripts, &icu_error);
118   DCHECK_NE(icu_error, U_BUFFER_OVERFLOW_ERROR) << " #ext: " << count;
119   if (U_FAILURE(icu_error))
120     return 0;
121 
122   return count;
123 }
124 
125 // Intersects the script extensions set of |codepoint| with |result| and writes
126 // to |result|, reading and updating |result_size|. The output |result| will be
127 // a subset of the input |result| (thus |result_size| can only be smaller).
ScriptSetIntersect(UChar32 codepoint,UScriptCode * result,size_t * result_size)128 void ScriptSetIntersect(UChar32 codepoint,
129                         UScriptCode* result,
130                         size_t* result_size) {
131   // Each codepoint has a Script property and a Script Extensions (Scx)
132   // property.
133   //
134   // The implicit Script property values 'Common' and 'Inherited' indicate that
135   // a codepoint is widely used in many scripts, rather than being associated
136   // to a specific script.
137   //
138   // However, some codepoints that are assigned a value of 'Common' or
139   // 'Inherited' are not commonly used with all scripts, but rather only with a
140   // limited set of scripts. The Script Extension property is used to specify
141   // the set of script which borrow the codepoint.
142   //
143   // Calls to GetScriptExtensions(...) return the set of scripts where the
144   // codepoints can be used.
145   // (see table 7 from http://www.unicode.org/reports/tr24/tr24-29.html)
146   //
147   //     Script     Script Extensions   ->  Results
148   //  1) Common       {Common}          ->  {Common}
149   //     Inherited    {Inherited}       ->  {Inherited}
150   //  2) Latin        {Latn}            ->  {Latn}
151   //     Inherited    {Latn}            ->  {Latn}
152   //  3) Common       {Hira Kana}       ->  {Hira Kana}
153   //     Inherited    {Hira Kana}       ->  {Hira Kana}
154   //  4) Devanagari   {Deva Dogr Kthi Mahj}  ->  {Deva Dogr Kthi Mahj}
155   //     Myanmar      {Cakm Mymr Tale}  ->  {Cakm Mymr Tale}
156   //
157   // For most of the codepoints, the script extensions set contains only one
158   // element. For CJK codepoints, it's common to see 3-4 scripts. For really
159   // rare cases, the set can go above 20 scripts.
160   UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE };
161   size_t count = GetScriptExtensions(codepoint, scripts);
162 
163   // Implicit script 'inherited' is inheriting scripts from preceding codepoint.
164   if (count == 1 && scripts[0] == USCRIPT_INHERITED)
165     return;
166 
167   // Perform the intersection of both script set.
168   auto scripts_span = base::span<UScriptCode>(scripts, count);
169   DCHECK(!base::Contains(scripts_span, USCRIPT_INHERITED));
170   auto results_span = base::span<UScriptCode>(result, *result_size);
171 
172   size_t out_size = 0;
173   for (UScriptCode current : results_span) {
174     if (base::Contains(scripts_span, current))
175       result[out_size++] = current;
176   }
177 
178   *result_size = out_size;
179 }
180 
181 struct GraphemeProperties {
182   bool has_control = false;
183   bool has_bracket = false;
184   bool has_pictographic = false;
185   bool has_emoji = false;
186   UBlockCode block = UBLOCK_NO_BLOCK;
187 };
188 
189 // Returns the properties for the codepoints part of the given text.
RetrieveGraphemeProperties(const base::StringPiece16 & text,bool retrieve_block)190 GraphemeProperties RetrieveGraphemeProperties(const base::StringPiece16& text,
191                                               bool retrieve_block) {
192   GraphemeProperties properties;
193   bool first_char = true;
194   for (base::i18n::UTF16CharIterator iter(text); !iter.end(); iter.Advance()) {
195     const UChar32 codepoint = iter.get();
196 
197     if (first_char) {
198       first_char = false;
199       if (retrieve_block)
200         properties.block = ublock_getCode(codepoint);
201     }
202 
203     if (codepoint == '\n' || codepoint == ' ')
204       properties.has_control = true;
205     if (IsBracket(codepoint))
206       properties.has_bracket = true;
207     if (IsExtendedPictographicCodepoint(codepoint))
208       properties.has_pictographic = true;
209     if (IsEmojiRelatedCodepoint(codepoint))
210       properties.has_emoji = true;
211   }
212 
213   return properties;
214 }
215 
216 // Return whether the grapheme properties are compatible and the grapheme can
217 // be merge together in the same grapheme cluster.
AreGraphemePropertiesCompatible(const GraphemeProperties & first,const GraphemeProperties & second)218 bool AreGraphemePropertiesCompatible(const GraphemeProperties& first,
219                                      const GraphemeProperties& second) {
220   // There are 5 constrains to grapheme to be compatible.
221   // 1) The newline character and control characters should form a single run so
222   //  that the line breaker can handle them easily.
223   // 2) Parentheses should be put in a separate run to avoid using different
224   // fonts while rendering matching parentheses (see http://crbug.com/396776).
225   // 3) Pictographic graphemes should be put in separate run to avoid altering
226   // fonts selection while rendering adjacent text (see
227   // http://crbug.com/278913).
228   // 4) Emoji graphemes should be put in separate run (see
229   // http://crbug.com/530021 and http://crbug.com/533721).
230   // 5) The 'COMMON' script needs to be split by unicode block. Codepoints are
231   // spread across blocks and supported with different fonts.
232   return !first.has_control && !second.has_control &&
233          first.has_bracket == second.has_bracket &&
234          first.has_pictographic == second.has_pictographic &&
235          first.has_emoji == second.has_emoji && first.block == second.block;
236 }
237 
238 // Returns the end of the current grapheme cluster. This function is finding the
239 // breaking point where grapheme properties are no longer compatible
240 // (see: UNICODE TEXT SEGMENTATION (http://unicode.org/reports/tr29/).
241 // Breaks between |run_start| and |run_end| and force break after the grapheme
242 // starting at |run_break|.
FindRunBreakingCharacter(const base::string16 & text,UScriptCode script,size_t run_start,size_t run_break,size_t run_end)243 size_t FindRunBreakingCharacter(const base::string16& text,
244                                 UScriptCode script,
245                                 size_t run_start,
246                                 size_t run_break,
247                                 size_t run_end) {
248   const size_t run_length = run_end - run_start;
249   const base::StringPiece16 run_text(text.c_str() + run_start, run_length);
250   const bool is_common_script = (script == USCRIPT_COMMON);
251 
252   DCHECK(!run_text.empty());
253 
254   // Create an iterator to split the text in graphemes.
255   base::i18n::BreakIterator grapheme_iterator(
256       run_text, base::i18n::BreakIterator::BREAK_CHARACTER);
257   if (!grapheme_iterator.Init() || !grapheme_iterator.Advance()) {
258     // In case of error, isolate the first character in a separate run.
259     NOTREACHED();
260     return run_start + 1;
261   }
262 
263   // Retrieve the first grapheme and its codepoint properties.
264   const base::StringPiece16 first_grapheme_text =
265       grapheme_iterator.GetStringPiece();
266   const GraphemeProperties first_grapheme_properties =
267       RetrieveGraphemeProperties(first_grapheme_text, is_common_script);
268 
269   // Append subsequent graphemes in this grapheme cluster if they are
270   // compatible, otherwise break the current run.
271   while (grapheme_iterator.Advance()) {
272     const base::StringPiece16 current_grapheme_text =
273         grapheme_iterator.GetStringPiece();
274     const GraphemeProperties current_grapheme_properties =
275         RetrieveGraphemeProperties(current_grapheme_text, is_common_script);
276 
277     const size_t current_breaking_position =
278         run_start + grapheme_iterator.prev();
279     if (!AreGraphemePropertiesCompatible(first_grapheme_properties,
280                                          current_grapheme_properties)) {
281       return current_breaking_position;
282     }
283 
284     // Break if the beginning of this grapheme is after |run_break|.
285     if (run_start + grapheme_iterator.prev() >= run_break) {
286       DCHECK_LE(current_breaking_position, run_end);
287       return current_breaking_position;
288     }
289   }
290 
291   // Do not break this run, returns end of the text.
292   return run_end;
293 }
294 
295 // Find the longest sequence of characters from 0 and up to |length| that have
296 // at least one common UScriptCode value. Writes the common script value to
297 // |script| and returns the length of the sequence. Takes the characters' script
298 // extensions into account. http://www.unicode.org/reports/tr24/#ScriptX
299 //
300 // Consider 3 characters with the script values {Kana}, {Hira, Kana}, {Kana}.
301 // Without script extensions only the first script in each set would be taken
302 // into account, resulting in 3 runs where 1 would be enough.
ScriptInterval(const base::string16 & text,size_t start,size_t length,UScriptCode * script)303 size_t ScriptInterval(const base::string16& text,
304                       size_t start,
305                       size_t length,
306                       UScriptCode* script) {
307   DCHECK_GT(length, 0U);
308 
309   UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE };
310 
311   base::i18n::UTF16CharIterator char_iterator(
312       base::StringPiece16(text.c_str() + start, length));
313   size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts);
314   *script = scripts[0];
315 
316   while (char_iterator.Advance()) {
317     ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size);
318     if (scripts_size == 0U)
319       return char_iterator.array_pos();
320     *script = scripts[0];
321   }
322 
323   return length;
324 }
325 
326 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without
327 // hb-icu. See http://crbug.com/356929
ICUScriptToHBScript(UScriptCode script)328 inline hb_script_t ICUScriptToHBScript(UScriptCode script) {
329   if (script == USCRIPT_INVALID_CODE)
330     return HB_SCRIPT_INVALID;
331   return hb_script_from_string(uscript_getShortName(script), -1);
332 }
333 
FontWasAlreadyTried(sk_sp<SkTypeface> typeface,std::set<SkFontID> * fallback_fonts)334 bool FontWasAlreadyTried(sk_sp<SkTypeface> typeface,
335                          std::set<SkFontID>* fallback_fonts) {
336   return fallback_fonts->count(typeface->uniqueID()) != 0;
337 }
338 
MarkFontAsTried(sk_sp<SkTypeface> typeface,std::set<SkFontID> * fallback_fonts)339 void MarkFontAsTried(sk_sp<SkTypeface> typeface,
340                      std::set<SkFontID>* fallback_fonts) {
341   fallback_fonts->insert(typeface->uniqueID());
342 }
343 
344 // Whether |segment| corresponds to the newline character.
IsNewlineSegment(const base::string16 & text,const internal::LineSegment & segment)345 bool IsNewlineSegment(const base::string16& text,
346                       const internal::LineSegment& segment) {
347   const size_t offset = segment.char_range.start();
348   const size_t length = segment.char_range.length();
349   DCHECK_LT(segment.char_range.start() + length - 1, text.length());
350   return (length == 1 && (text[offset] == '\r' || text[offset] == '\n')) ||
351          (length == 2 && text[offset] == '\r' && text[offset + 1] == '\n');
352 }
353 
354 // Returns the line index considering the newline character. Line index is
355 // incremented if the caret is right after the newline character, i.e, the
356 // cursor affinity is |CURSOR_BACKWARD| while containing the newline character.
LineIndexForNewline(const size_t line_index,const base::string16 & text,const internal::LineSegment & segment,const SelectionModel & caret)357 size_t LineIndexForNewline(const size_t line_index,
358                            const base::string16& text,
359                            const internal::LineSegment& segment,
360                            const SelectionModel& caret) {
361   bool at_newline = IsNewlineSegment(text, segment) &&
362                     caret.caret_affinity() == CURSOR_BACKWARD;
363   return line_index + (at_newline ? 1 : 0);
364 }
365 
366 // Helper template function for |TextRunHarfBuzz::GetClusterAt()|. |Iterator|
367 // can be a forward or reverse iterator type depending on the text direction.
368 // Returns true on success, or false if an error is encountered.
369 template <class Iterator>
GetClusterAtImpl(size_t pos,Range range,Iterator elements_begin,Iterator elements_end,bool reversed,Range * chars,Range * glyphs)370 bool GetClusterAtImpl(size_t pos,
371                       Range range,
372                       Iterator elements_begin,
373                       Iterator elements_end,
374                       bool reversed,
375                       Range* chars,
376                       Range* glyphs) {
377   Iterator element = std::upper_bound(elements_begin, elements_end, pos);
378   if (element == elements_begin) {
379     *chars = range;
380     *glyphs = Range();
381     return false;
382   }
383 
384   chars->set_end(element == elements_end ? range.end() : *element);
385   glyphs->set_end(reversed ? elements_end - element : element - elements_begin);
386   while (--element != elements_begin && *element == *(element - 1));
387   chars->set_start(*element);
388   glyphs->set_start(
389       reversed ? elements_end - element : element - elements_begin);
390   if (reversed)
391     *glyphs = Range(glyphs->end(), glyphs->start());
392 
393   DCHECK(!chars->is_reversed());
394   DCHECK(!chars->is_empty());
395   DCHECK(!glyphs->is_reversed());
396   DCHECK(!glyphs->is_empty());
397   return true;
398 }
399 
400 // Internal class to generate Line structures. If |multiline| is true, the text
401 // is broken into lines at |words| boundaries such that each line is no longer
402 // than |max_width|. If |multiline| is false, only outputs a single Line from
403 // the given runs. |min_baseline| and |min_height| are the minimum baseline and
404 // height for each line.
405 // TODO(ckocagil): Expose the interface of this class in the header and test
406 //                 this class directly.
407 class HarfBuzzLineBreaker {
408  public:
HarfBuzzLineBreaker(size_t max_width,int min_baseline,float min_height,float glyph_height_for_test,WordWrapBehavior word_wrap_behavior,const base::string16 & text,const BreakList<size_t> * words,const internal::TextRunList & run_list)409   HarfBuzzLineBreaker(size_t max_width,
410                       int min_baseline,
411                       float min_height,
412                       float glyph_height_for_test,
413                       WordWrapBehavior word_wrap_behavior,
414                       const base::string16& text,
415                       const BreakList<size_t>* words,
416                       const internal::TextRunList& run_list)
417       : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)),
418         min_baseline_(min_baseline),
419         min_height_(min_height),
420         glyph_height_for_test_(glyph_height_for_test),
421         word_wrap_behavior_(word_wrap_behavior),
422         text_(text),
423         words_(words),
424         run_list_(run_list),
425         max_descent_(0),
426         max_ascent_(0),
427         text_x_(0),
428         available_width_(max_width_) {
429     AdvanceLine();
430   }
431 
432   // Constructs a single line for |text_| using |run_list_|.
ConstructSingleLine()433   void ConstructSingleLine() {
434     for (size_t i = 0; i < run_list_.size(); i++) {
435       const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]);
436       internal::LineSegment segment;
437       segment.run = i;
438       segment.char_range = run.range;
439       segment.x_range = RangeF(SkScalarToFloat(text_x_),
440                                SkScalarToFloat(text_x_) + run.shape.width);
441       AddLineSegment(segment, false);
442     }
443   }
444 
445   // Constructs multiple lines for |text_| based on words iteration approach.
ConstructMultiLines()446   void ConstructMultiLines() {
447     DCHECK(words_);
448     for (auto iter = words_->breaks().begin(); iter != words_->breaks().end();
449          iter++) {
450       const Range word_range = words_->GetRange(iter);
451       std::vector<internal::LineSegment> word_segments;
452       SkScalar word_width = GetWordWidth(word_range, &word_segments);
453 
454       // If the last word is '\n', we should advance a new line after adding
455       // the word to the current line.
456       bool new_line = false;
457       if (!word_segments.empty() &&
458           IsNewlineSegment(text_, word_segments.back())) {
459         new_line = true;
460 
461         // Subtract the width of newline segments, they are not drawn.
462         if (word_segments.size() != 1u || available_width_ != max_width_)
463           word_width -= word_segments.back().width();
464       }
465 
466       // If the word is not the first word in the line and it can't fit into
467       // the current line, advance a new line.
468       if (word_width > available_width_ && available_width_ != max_width_)
469         AdvanceLine();
470       if (!word_segments.empty())
471         AddWordToLine(word_segments);
472       if (new_line)
473         AdvanceLine();
474     }
475   }
476 
477   // Finishes line breaking and outputs the results. Can be called at most once.
FinalizeLines(std::vector<internal::Line> * lines,SizeF * size)478   void FinalizeLines(std::vector<internal::Line>* lines, SizeF* size) {
479     DCHECK(!lines_.empty());
480     // If the last character of the text is a new line character, then the last
481     // line is any empty string, which contains no segments. This means that the
482     // display_text_index will not have been set in AdvanceLine. So here, set
483     // display_text_index to the text length, which is the true text index of
484     // the final line.
485     internal::Line* line = &lines_.back();
486     if (line->display_text_index == 0)
487       line->display_text_index = text_.size();
488     // Add an empty line to finish the line size calculation and remove it.
489     AdvanceLine();
490     lines_.pop_back();
491     *size = total_size_;
492     lines->swap(lines_);
493   }
494 
495  private:
496   // A (line index, segment index) pair that specifies a segment in |lines_|.
497   typedef std::pair<size_t, size_t> SegmentHandle;
498 
SegmentFromHandle(const SegmentHandle & handle)499   internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) {
500     return &lines_[handle.first].segments[handle.second];
501   }
502 
503   // Finishes the size calculations of the last Line in |lines_|. Adds a new
504   // Line to the back of |lines_|.
AdvanceLine()505   void AdvanceLine() {
506     if (!lines_.empty()) {
507       internal::Line* line = &lines_.back();
508       // Compute the line start while the line segments are in the logical order
509       // so that the start of the line is the start of the char range,
510       // regardless of i18n.
511       if (!line->segments.empty())
512         line->display_text_index = line->segments[0].char_range.start();
513       std::sort(line->segments.begin(), line->segments.end(),
514                 [this](const internal::LineSegment& s1,
515                        const internal::LineSegment& s2) -> bool {
516                   return run_list_.logical_to_visual(s1.run) <
517                          run_list_.logical_to_visual(s2.run);
518                 });
519 
520       line->size.set_height(
521           glyph_height_for_test_
522               ? glyph_height_for_test_
523               : std::max(min_height_, max_descent_ + max_ascent_));
524 
525       line->baseline = std::max(min_baseline_, SkScalarRoundToInt(max_ascent_));
526       line->preceding_heights = base::ClampCeil(total_size_.height());
527       // Subtract newline segment's width from |total_size_| because it's not
528       // drawn.
529       float line_width = line->size.width();
530       if (!line->segments.empty() &&
531           IsNewlineSegment(text_, line->segments.back())) {
532         line_width -= line->segments.back().width();
533       }
534       if (line->segments.size() > 1 &&
535           IsNewlineSegment(text_, line->segments.front())) {
536         line_width -= line->segments.front().width();
537       }
538       total_size_.set_height(total_size_.height() + line->size.height());
539       total_size_.set_width(std::max(total_size_.width(), line_width));
540     }
541     max_descent_ = 0;
542     max_ascent_ = 0;
543     available_width_ = max_width_;
544     lines_.push_back(internal::Line());
545   }
546 
547   // Adds word to the current line. A word may contain multiple segments. If the
548   // word is the first word in line and its width exceeds |available_width_|,
549   // ignore/truncate/wrap it according to |word_wrap_behavior_|.
AddWordToLine(const std::vector<internal::LineSegment> & word_segments)550   void AddWordToLine(const std::vector<internal::LineSegment>& word_segments) {
551     DCHECK(!lines_.empty());
552     DCHECK(!word_segments.empty());
553 
554     bool has_truncated = false;
555     for (const internal::LineSegment& segment : word_segments) {
556       if (has_truncated)
557         break;
558 
559       if (IsNewlineSegment(text_, segment) ||
560           segment.width() <= available_width_ ||
561           word_wrap_behavior_ == IGNORE_LONG_WORDS) {
562         AddLineSegment(segment, true);
563       } else {
564         DCHECK(word_wrap_behavior_ == TRUNCATE_LONG_WORDS ||
565                word_wrap_behavior_ == WRAP_LONG_WORDS);
566         has_truncated = (word_wrap_behavior_ == TRUNCATE_LONG_WORDS);
567 
568         const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]);
569         internal::LineSegment remaining_segment = segment;
570         while (!remaining_segment.char_range.is_empty()) {
571           size_t cutoff_pos = GetCutoffPos(remaining_segment);
572           SkScalar width = run.GetGlyphWidthForCharRange(
573               Range(remaining_segment.char_range.start(), cutoff_pos));
574           if (width > 0) {
575             internal::LineSegment cut_segment;
576             cut_segment.run = remaining_segment.run;
577             cut_segment.char_range =
578                 Range(remaining_segment.char_range.start(), cutoff_pos);
579             cut_segment.x_range = RangeF(SkScalarToFloat(text_x_),
580                                          SkScalarToFloat(text_x_ + width));
581             AddLineSegment(cut_segment, true);
582             // Updates old segment range.
583             remaining_segment.char_range.set_start(cutoff_pos);
584             remaining_segment.x_range.set_start(SkScalarToFloat(text_x_));
585           }
586           if (has_truncated)
587             break;
588           if (!remaining_segment.char_range.is_empty())
589             AdvanceLine();
590         }
591       }
592     }
593   }
594 
595   // Add a line segment to the current line. Note that, in order to keep the
596   // visual order correct for ltr and rtl language, we need to merge segments
597   // that belong to the same run.
AddLineSegment(const internal::LineSegment & segment,bool multiline)598   void AddLineSegment(const internal::LineSegment& segment, bool multiline) {
599     DCHECK(!lines_.empty());
600     internal::Line* line = &lines_.back();
601     const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]);
602     if (!line->segments.empty()) {
603       internal::LineSegment& last_segment = line->segments.back();
604       // Merge segments that belong to the same run.
605       if (last_segment.run == segment.run) {
606         DCHECK_EQ(last_segment.char_range.end(), segment.char_range.start());
607         // Check there is less than a pixel between one run and the next.
608         DCHECK_LE(
609             std::abs(last_segment.x_range.end() - segment.x_range.start()),
610             1.0f);
611         last_segment.char_range.set_end(segment.char_range.end());
612         last_segment.x_range.set_end(SkScalarToFloat(text_x_) +
613                                      segment.width());
614         if (run.font_params.is_rtl &&
615             last_segment.char_range.end() == run.range.end())
616           UpdateRTLSegmentRanges();
617         line->size.set_width(line->size.width() + segment.width());
618         text_x_ += segment.width();
619         available_width_ -= segment.width();
620         return;
621       }
622     }
623     line->segments.push_back(segment);
624     line->size.set_width(line->size.width() + segment.width());
625 
626     // Newline characters are not drawn for multi-line, ignore their metrics.
627     if (!multiline || !IsNewlineSegment(text_, segment)) {
628       SkFont font(run.font_params.skia_face, run.font_params.font_size);
629       font.setEdging(run.font_params.render_params.antialiasing
630                          ? SkFont::Edging::kAntiAlias
631                          : SkFont::Edging::kAlias);
632       SkFontMetrics metrics;
633       font.getMetrics(&metrics);
634 
635       // max_descent_ is y-down, fDescent is y-down, baseline_offset is y-down
636       max_descent_ = std::max(
637           max_descent_, metrics.fDescent + run.font_params.baseline_offset);
638       // max_ascent_ is y-up, fAscent is y-down, baseline_offset is y-down
639       max_ascent_ = std::max(
640           max_ascent_, -(metrics.fAscent + run.font_params.baseline_offset));
641     }
642 
643     if (run.font_params.is_rtl) {
644       rtl_segments_.push_back(
645           SegmentHandle(lines_.size() - 1, line->segments.size() - 1));
646       // If this is the last segment of an RTL run, reprocess the text-space x
647       // ranges of all segments from the run.
648       if (segment.char_range.end() == run.range.end())
649         UpdateRTLSegmentRanges();
650     }
651     text_x_ += segment.width();
652     available_width_ -= segment.width();
653   }
654 
655   // Finds the end position |end_pos| in |segment| where the preceding width is
656   // no larger than |available_width_|.
GetCutoffPos(const internal::LineSegment & segment) const657   size_t GetCutoffPos(const internal::LineSegment& segment) const {
658     DCHECK(!segment.char_range.is_empty());
659     const internal::TextRunHarfBuzz& run =
660         *(run_list_.runs()[segment.run]).get();
661     size_t end_pos = segment.char_range.start();
662     SkScalar width = 0;
663     while (end_pos < segment.char_range.end()) {
664       const SkScalar char_width =
665           run.GetGlyphWidthForCharRange(Range(end_pos, end_pos + 1));
666       if (width + char_width > available_width_)
667         break;
668       width += char_width;
669       end_pos++;
670     }
671 
672     const size_t valid_end_pos = std::max(
673         segment.char_range.start(),
674         static_cast<uint32_t>(FindValidBoundaryBefore(text_, end_pos)));
675     if (end_pos != valid_end_pos) {
676       end_pos = valid_end_pos;
677       width = run.GetGlyphWidthForCharRange(
678           Range(segment.char_range.start(), end_pos));
679     }
680 
681     // |max_width_| might be smaller than a single character. In this case we
682     // need to put at least one character in the line. Note that, we should
683     // not separate surrogate pair or combining characters.
684     // See RenderTextHarfBuzzTest.Multiline_MinWidth for an example.
685     if (width == 0 && available_width_ == max_width_) {
686       end_pos = std::min(
687           segment.char_range.end(),
688           static_cast<uint32_t>(FindValidBoundaryAfter(text_, end_pos + 1)));
689     }
690 
691     return end_pos;
692   }
693 
694   // Gets the glyph width for |word_range|, and splits the |word| into different
695   // segments based on its runs.
GetWordWidth(const Range & word_range,std::vector<internal::LineSegment> * segments) const696   SkScalar GetWordWidth(const Range& word_range,
697                         std::vector<internal::LineSegment>* segments) const {
698     DCHECK(words_);
699     if (word_range.is_empty() || segments == nullptr)
700       return 0;
701     size_t run_start_index = run_list_.GetRunIndexAt(word_range.start());
702     size_t run_end_index = run_list_.GetRunIndexAt(word_range.end() - 1);
703     SkScalar width = 0;
704     for (size_t i = run_start_index; i <= run_end_index; i++) {
705       const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]);
706       const Range char_range = run.range.Intersect(word_range);
707       DCHECK(!char_range.is_empty());
708       const SkScalar char_width = run.GetGlyphWidthForCharRange(char_range);
709       width += char_width;
710 
711       internal::LineSegment segment;
712       segment.run = i;
713       segment.char_range = char_range;
714       segment.x_range = RangeF(SkScalarToFloat(text_x_ + width - char_width),
715                                SkScalarToFloat(text_x_ + width));
716       segments->push_back(segment);
717     }
718     return width;
719   }
720 
721   // RTL runs are broken in logical order but displayed in visual order. To find
722   // the text-space coordinate (where it would fall in a single-line text)
723   // |x_range| of RTL segments, segment widths are applied in reverse order.
724   // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}.
UpdateRTLSegmentRanges()725   void UpdateRTLSegmentRanges() {
726     if (rtl_segments_.empty())
727       return;
728     float x = SegmentFromHandle(rtl_segments_[0])->x_range.start();
729     for (size_t i = rtl_segments_.size(); i > 0; --i) {
730       internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]);
731       const float segment_width = segment->width();
732       segment->x_range = RangeF(x, x + segment_width);
733       x += segment_width;
734     }
735     rtl_segments_.clear();
736   }
737 
738   const SkScalar max_width_;
739   const int min_baseline_;
740   const float min_height_;
741   const float glyph_height_for_test_;
742   const WordWrapBehavior word_wrap_behavior_;
743   const base::string16& text_;
744   const BreakList<size_t>* const words_;
745   const internal::TextRunList& run_list_;
746 
747   // Stores the resulting lines.
748   std::vector<internal::Line> lines_;
749 
750   float max_descent_;
751   float max_ascent_;
752 
753   // Text space x coordinates of the next segment to be added.
754   SkScalar text_x_;
755   // Stores available width in the current line.
756   SkScalar available_width_;
757 
758   // Size of the multiline text, not including the currently processed line.
759   SizeF total_size_;
760 
761   // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|.
762   std::vector<SegmentHandle> rtl_segments_;
763 
764   DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker);
765 };
766 
767 // Applies a forced text rendering direction if specified by a command-line
768 // switch.
ApplyForcedDirection(UBiDiLevel * level)769 void ApplyForcedDirection(UBiDiLevel* level) {
770   static bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch(
771       switches::kForceTextDirection);
772   if (!has_switch)
773     return;
774 
775   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
776   if (command_line->HasSwitch(switches::kForceTextDirection)) {
777     std::string force_flag =
778         command_line->GetSwitchValueASCII(switches::kForceTextDirection);
779 
780     if (force_flag == switches::kForceDirectionRTL)
781       *level = UBIDI_RTL;
782     if (force_flag == switches::kForceDirectionLTR)
783       *level = UBIDI_LTR;
784   }
785 }
786 
CreateFontParams(const Font & primary_font,UBiDiLevel bidi_level,UScriptCode script,const internal::StyleIterator & style)787 internal::TextRunHarfBuzz::FontParams CreateFontParams(
788     const Font& primary_font,
789     UBiDiLevel bidi_level,
790     UScriptCode script,
791     const internal::StyleIterator& style) {
792   internal::TextRunHarfBuzz::FontParams font_params(primary_font);
793   font_params.italic = style.style(TEXT_STYLE_ITALIC);
794   font_params.baseline_type = style.baseline();
795   font_params.font_size = style.font_size_override();
796   font_params.strike = style.style(TEXT_STYLE_STRIKE);
797   font_params.underline = style.style(TEXT_STYLE_UNDERLINE);
798   font_params.heavy_underline = style.style(TEXT_STYLE_HEAVY_UNDERLINE);
799   font_params.weight = style.weight();
800   font_params.level = bidi_level;
801   font_params.script = script;
802   // Odd BiDi embedding levels correspond to RTL runs.
803   font_params.is_rtl = (font_params.level % 2) == 1;
804   return font_params;
805 }
806 
807 }  // namespace
808 
809 namespace internal {
810 
CreateSkiaTypeface(const Font & font,bool italic,Font::Weight weight)811 sk_sp<SkTypeface> CreateSkiaTypeface(const Font& font,
812                                      bool italic,
813                                      Font::Weight weight) {
814 #if defined(OS_APPLE)
815   const Font::FontStyle style = italic ? Font::ITALIC : Font::NORMAL;
816   Font font_with_style = font.Derive(0, style, weight);
817   if (!font_with_style.GetNativeFont())
818     return nullptr;
819 
820   return SkMakeTypefaceFromCTFont(
821       base::mac::NSToCFCast(font_with_style.GetNativeFont()));
822 #else
823   SkFontStyle skia_style(
824       static_cast<int>(weight), SkFontStyle::kNormal_Width,
825       italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant);
826   return sk_sp<SkTypeface>(SkTypeface::MakeFromName(
827       font.GetFontName().c_str(), skia_style));
828 #endif
829 }
830 
FontParams(const Font & template_font)831 TextRunHarfBuzz::FontParams::FontParams(const Font& template_font)
832     : font(template_font) {}
833 TextRunHarfBuzz::FontParams::~FontParams() = default;
834 TextRunHarfBuzz::FontParams::FontParams(
835     const TextRunHarfBuzz::FontParams& other) = default;
836 TextRunHarfBuzz::FontParams& TextRunHarfBuzz::FontParams::operator=(
837     const TextRunHarfBuzz::FontParams& other) = default;
838 
operator ==(const FontParams & other) const839 bool TextRunHarfBuzz::FontParams::operator==(const FontParams& other) const {
840   // Empirically, |script| and |weight| are the highest entropy members.
841   return script == other.script && weight == other.weight &&
842          skia_face == other.skia_face && render_params == other.render_params &&
843          font_size == other.font_size &&
844          baseline_offset == other.baseline_offset &&
845          baseline_type == other.baseline_type && italic == other.italic &&
846          strike == other.strike && underline == other.underline &&
847          heavy_underline == other.heavy_underline && is_rtl == other.is_rtl &&
848          level == other.level;
849 }
850 
851 void TextRunHarfBuzz::FontParams::
ComputeRenderParamsFontSizeAndBaselineOffset()852     ComputeRenderParamsFontSizeAndBaselineOffset() {
853   render_params = font.GetFontRenderParams();
854   if (font_size == 0)
855     font_size = font.GetFontSize();
856   baseline_offset = 0;
857   if (baseline_type != NORMAL_BASELINE) {
858     // Calculate a slightly smaller font. The ratio here is somewhat arbitrary.
859     // Proportions from 5/9 to 5/7 all look pretty good.
860     const float ratio = 5.0f / 9.0f;
861     font_size = base::ClampRound(font.GetFontSize() * ratio);
862     switch (baseline_type) {
863       case SUPERSCRIPT:
864         baseline_offset = font.GetCapHeight() - font.GetHeight();
865         break;
866       case SUPERIOR:
867         baseline_offset =
868             base::ClampRound(font.GetCapHeight() * ratio) - font.GetCapHeight();
869         break;
870       case SUBSCRIPT:
871         baseline_offset = font.GetHeight() - font.GetBaseline();
872         break;
873       case INFERIOR:  // Fall through.
874       default:
875         break;
876     }
877   }
878 }
879 
operator ()(const FontParams & key) const880 size_t TextRunHarfBuzz::FontParams::Hash::operator()(
881     const FontParams& key) const {
882   // In practice, |font|, |skia_face|, |render_params|, and |baseline_offset|
883   // have not yet been set when this is called.
884   return static_cast<size_t>(key.italic) << 0 ^
885          static_cast<size_t>(key.strike) << 1 ^
886          static_cast<size_t>(key.underline) << 2 ^
887          static_cast<size_t>(key.heavy_underline) << 3 ^
888          static_cast<size_t>(key.is_rtl) << 4 ^
889          static_cast<size_t>(key.weight) << 8 ^
890          static_cast<size_t>(key.font_size) << 12 ^
891          static_cast<size_t>(key.baseline_type) << 16 ^
892          static_cast<size_t>(key.level) << 20 ^
893          static_cast<size_t>(key.script) << 24;
894 }
895 
SetRenderParamsRematchFont(const Font & new_font,const FontRenderParams & new_render_params)896 bool TextRunHarfBuzz::FontParams::SetRenderParamsRematchFont(
897     const Font& new_font,
898     const FontRenderParams& new_render_params) {
899   // This takes the font family name from new_font, and calls
900   // SkTypeface::makeFromName() with that family name and the style information
901   // internal to this text run. So it triggers a new font match and looks for
902   // adjacent fonts in the family. This works for styling, e.g. styling a run in
903   // bold, italic or underline, but breaks font fallback in certain scenarios,
904   // as the fallback font may be of a different weight and style than the run's
905   // own, so this can lead to a failure of instantiating the correct fallback
906   // font.
907   sk_sp<SkTypeface> new_skia_face(
908       internal::CreateSkiaTypeface(new_font, italic, weight));
909   if (!new_skia_face)
910     return false;
911 
912   skia_face = new_skia_face;
913   font = new_font;
914   render_params = new_render_params;
915   return true;
916 }
917 
SetRenderParamsOverrideSkiaFaceFromFont(const Font & fallback_font,const FontRenderParams & new_render_params)918 bool TextRunHarfBuzz::FontParams::SetRenderParamsOverrideSkiaFaceFromFont(
919     const Font& fallback_font,
920     const FontRenderParams& new_render_params) {
921   PlatformFont* platform_font = fallback_font.platform_font();
922   sk_sp<SkTypeface> new_skia_face = platform_font->GetNativeSkTypeface();
923 
924   // If pass-through of the Skia native handle fails for PlatformFonts other
925   // than PlatformFontSkia, perform rematching.
926   if (!new_skia_face)
927     return SetRenderParamsRematchFont(fallback_font, new_render_params);
928 
929   skia_face = new_skia_face;
930   font = fallback_font;
931   render_params = new_render_params;
932   return true;
933 }
934 
935 TextRunHarfBuzz::ShapeOutput::ShapeOutput() = default;
936 TextRunHarfBuzz::ShapeOutput::~ShapeOutput() = default;
937 TextRunHarfBuzz::ShapeOutput::ShapeOutput(
938     const TextRunHarfBuzz::ShapeOutput& other) = default;
939 TextRunHarfBuzz::ShapeOutput& TextRunHarfBuzz::ShapeOutput::operator=(
940     const TextRunHarfBuzz::ShapeOutput& other) = default;
941 TextRunHarfBuzz::ShapeOutput::ShapeOutput(
942     TextRunHarfBuzz::ShapeOutput&& other) = default;
943 TextRunHarfBuzz::ShapeOutput& TextRunHarfBuzz::ShapeOutput::operator=(
944     TextRunHarfBuzz::ShapeOutput&& other) = default;
945 
TextRunHarfBuzz(const Font & template_font)946 TextRunHarfBuzz::TextRunHarfBuzz(const Font& template_font)
947     : font_params(template_font) {}
948 
~TextRunHarfBuzz()949 TextRunHarfBuzz::~TextRunHarfBuzz() {}
950 
CharRangeToGlyphRange(const Range & char_range) const951 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const {
952   DCHECK(range.Contains(char_range));
953   DCHECK(!char_range.is_reversed());
954   DCHECK(!char_range.is_empty());
955 
956   Range start_glyphs;
957   Range end_glyphs;
958   Range temp_range;
959   GetClusterAt(char_range.start(), &temp_range, &start_glyphs);
960   GetClusterAt(char_range.end() - 1, &temp_range, &end_glyphs);
961 
962   return font_params.is_rtl ? Range(end_glyphs.start(), start_glyphs.end())
963                             : Range(start_glyphs.start(), end_glyphs.end());
964 }
965 
CountMissingGlyphs() const966 size_t TextRunHarfBuzz::CountMissingGlyphs() const {
967   return shape.missing_glyph_count;
968 }
969 
GetClusterAt(size_t pos,Range * chars,Range * glyphs) const970 void TextRunHarfBuzz::GetClusterAt(size_t pos,
971                                    Range* chars,
972                                    Range* glyphs) const {
973   DCHECK(chars);
974   DCHECK(glyphs);
975 
976   bool success = true;
977   if (shape.glyph_count == 0 || !range.Contains(Range(pos, pos + 1))) {
978     *chars = range;
979     *glyphs = Range();
980     success = false;
981   }
982 
983   if (font_params.is_rtl) {
984     success &=
985         GetClusterAtImpl(pos, range, shape.glyph_to_char.rbegin(),
986                          shape.glyph_to_char.rend(), true, chars, glyphs);
987   } else {
988     success &=
989         GetClusterAtImpl(pos, range, shape.glyph_to_char.begin(),
990                          shape.glyph_to_char.end(), false, chars, glyphs);
991   }
992 
993   if (!success) {
994     std::string glyph_to_char_string;
995     for (size_t i = 0; i < shape.glyph_count && i < shape.glyph_to_char.size();
996          ++i) {
997       glyph_to_char_string += base::NumberToString(i) + "->" +
998                               base::NumberToString(shape.glyph_to_char[i]) +
999                               ", ";
1000     }
1001     LOG(ERROR) << " TextRunHarfBuzz error, please report at crbug.com/724880:"
1002                << " range: " << range.ToString()
1003                << ", rtl: " << font_params.is_rtl << ","
1004                << " level: '" << font_params.level
1005                << "', script: " << font_params.script << ","
1006                << " font: '" << font_params.font.GetActualFontName() << "',"
1007                << " glyph_count: " << shape.glyph_count << ", pos: " << pos
1008                << ","
1009                << " glyph_to_char: " << glyph_to_char_string;
1010   }
1011 }
1012 
GetGraphemeBounds(RenderTextHarfBuzz * render_text,size_t text_index) const1013 RangeF TextRunHarfBuzz::GetGraphemeBounds(RenderTextHarfBuzz* render_text,
1014                                           size_t text_index) const {
1015   DCHECK_LT(text_index, range.end());
1016   if (shape.glyph_count == 0)
1017     return RangeF(preceding_run_widths, preceding_run_widths + shape.width);
1018 
1019   Range chars;
1020   Range glyphs;
1021   GetClusterAt(text_index, &chars, &glyphs);
1022   const float cluster_begin_x = shape.positions[glyphs.start()].x();
1023   const float cluster_end_x = glyphs.end() < shape.glyph_count
1024                                   ? shape.positions[glyphs.end()].x()
1025                                   : SkFloatToScalar(shape.width);
1026   DCHECK_LE(cluster_begin_x, cluster_end_x);
1027 
1028   // A cluster consists of a number of code points and corresponds to a number
1029   // of glyphs that should be drawn together. A cluster can contain multiple
1030   // graphemes. In order to place the cursor at a grapheme boundary inside the
1031   // cluster, we simply divide the cluster width by the number of graphemes.
1032   ptrdiff_t code_point_count = UTF16IndexToOffset(render_text->GetDisplayText(),
1033                                                   chars.start(), chars.end());
1034   if (code_point_count > 1) {
1035     int before = 0;
1036     int total = 0;
1037     for (size_t i = chars.start(); i < chars.end(); ++i) {
1038       if (render_text->IsGraphemeBoundary(i)) {
1039         if (i < text_index)
1040           ++before;
1041         ++total;
1042       }
1043     }
1044     // With ICU 65.1, DCHECK_GT() below fails.
1045     // See https://crbug.com/1017047 for more details.
1046     //
1047     // DCHECK_GT(total, 0);
1048 
1049     // It's possible for |text_index| to point to a diacritical mark, at the end
1050     // of |chars|. In this case all the grapheme boundaries come before it. Just
1051     // provide the bounds of the last grapheme.
1052     if (before == total)
1053       --before;
1054 
1055     if (total > 1) {
1056       if (font_params.is_rtl)
1057         before = total - before - 1;
1058       DCHECK_GE(before, 0);
1059       DCHECK_LT(before, total);
1060       const float cluster_start = preceding_run_widths + cluster_begin_x;
1061       const float average_width = (cluster_end_x - cluster_begin_x) / total;
1062       return RangeF(cluster_start + average_width * before,
1063                     cluster_start + average_width * (before + 1));
1064     }
1065   }
1066 
1067   return RangeF(preceding_run_widths + cluster_begin_x,
1068                 preceding_run_widths + cluster_end_x);
1069 }
1070 
GetGraphemeSpanForCharRange(RenderTextHarfBuzz * render_text,const Range & char_range) const1071 RangeF TextRunHarfBuzz::GetGraphemeSpanForCharRange(
1072     RenderTextHarfBuzz* render_text,
1073     const Range& char_range) const {
1074   if (char_range.is_empty())
1075     return RangeF();
1076 
1077   DCHECK(!char_range.is_reversed());
1078   DCHECK(range.Contains(char_range));
1079   size_t left_index = char_range.start();
1080   size_t right_index =
1081       UTF16OffsetToIndex(render_text->GetDisplayText(), char_range.end(), -1);
1082   DCHECK_LE(left_index, right_index);
1083   if (font_params.is_rtl)
1084     std::swap(left_index, right_index);
1085 
1086   const RangeF left_span = GetGraphemeBounds(render_text, left_index);
1087   return left_index == right_index
1088              ? left_span
1089              : RangeF(left_span.start(),
1090                       GetGraphemeBounds(render_text, right_index).end());
1091 }
1092 
GetGlyphWidthForCharRange(const Range & char_range) const1093 SkScalar TextRunHarfBuzz::GetGlyphWidthForCharRange(
1094     const Range& char_range) const {
1095   if (char_range.is_empty())
1096     return 0;
1097 
1098   DCHECK(range.Contains(char_range));
1099   Range glyph_range = CharRangeToGlyphRange(char_range);
1100 
1101   // The |glyph_range| might be empty or invalid on Windows if a multi-character
1102   // grapheme is divided into different runs (e.g., there are two font sizes or
1103   // colors for a single glyph). In this case it might cause the browser crash,
1104   // see crbug.com/526234.
1105   if (glyph_range.start() >= glyph_range.end()) {
1106     NOTREACHED() << "The glyph range is empty or invalid! Its char range: ["
1107         << char_range.start() << ", " << char_range.end()
1108         << "], and its glyph range: [" << glyph_range.start() << ", "
1109         << glyph_range.end() << "].";
1110     return 0;
1111   }
1112 
1113   return ((glyph_range.end() == shape.glyph_count)
1114               ? SkFloatToScalar(shape.width)
1115               : shape.positions[glyph_range.end()].x()) -
1116          shape.positions[glyph_range.start()].x();
1117 }
1118 
UpdateFontParamsAndShape(const FontParams & new_font_params,const ShapeOutput & new_shape)1119 void TextRunHarfBuzz::UpdateFontParamsAndShape(
1120     const FontParams& new_font_params,
1121     const ShapeOutput& new_shape) {
1122   if (new_shape.missing_glyph_count < shape.missing_glyph_count) {
1123     font_params = new_font_params;
1124     shape = new_shape;
1125     // Note that |new_shape.glyph_to_char| is indexed from the beginning of
1126     // |range|, while |shape.glyph_to_char| is indexed from the beginning of
1127     // its embedding text.
1128     for (size_t i = 0; i < shape.glyph_to_char.size(); ++i)
1129       shape.glyph_to_char[i] += range.start();
1130   }
1131 }
1132 
TextRunList()1133 TextRunList::TextRunList() : width_(0.0f) {}
1134 
~TextRunList()1135 TextRunList::~TextRunList() {}
1136 
Reset()1137 void TextRunList::Reset() {
1138   runs_.clear();
1139   width_ = 0.0f;
1140 }
1141 
InitIndexMap()1142 void TextRunList::InitIndexMap() {
1143   if (runs_.size() == 1) {
1144     visual_to_logical_ = logical_to_visual_ = std::vector<int32_t>(1, 0);
1145     return;
1146   }
1147   const size_t num_runs = runs_.size();
1148   std::vector<UBiDiLevel> levels(num_runs);
1149   for (size_t i = 0; i < num_runs; ++i)
1150     levels[i] = runs_[i]->font_params.level;
1151   visual_to_logical_.resize(num_runs);
1152   ubidi_reorderVisual(&levels[0], num_runs, &visual_to_logical_[0]);
1153   logical_to_visual_.resize(num_runs);
1154   ubidi_reorderLogical(&levels[0], num_runs, &logical_to_visual_[0]);
1155 }
1156 
ComputePrecedingRunWidths()1157 void TextRunList::ComputePrecedingRunWidths() {
1158   // Precalculate run width information.
1159   width_ = 0.0f;
1160   for (size_t i = 0; i < runs_.size(); ++i) {
1161     const auto& run = runs_[visual_to_logical_[i]];
1162     run->preceding_run_widths = width_;
1163     width_ += run->shape.width;
1164   }
1165 }
1166 
GetRunIndexAt(size_t position) const1167 size_t TextRunList::GetRunIndexAt(size_t position) const {
1168   for (size_t i = 0; i < runs_.size(); ++i) {
1169     if (runs_[i]->range.start() <= position && runs_[i]->range.end() > position)
1170       return i;
1171   }
1172   return runs_.size();
1173 }
1174 
1175 namespace {
1176 
1177 // ShapeRunWithFont cache. Views makes repeated calls to ShapeRunWithFont
1178 // with the same arguments in several places, and typesetting is very expensive.
1179 // To compensate for this, encapsulate all of the input arguments to
1180 // ShapeRunWithFont in ShapeRunWithFontInput, all of the output arguments in
1181 // TextRunHarfBuzz::ShapeOutput, and add ShapeRunCache to map between the two.
1182 // This is analogous to the blink::ShapeCache.
1183 // https://crbug.com/826265
1184 
1185 // Input for the stateless implementation of ShapeRunWithFont.
1186 struct ShapeRunWithFontInput {
ShapeRunWithFontInputgfx::internal::__anon56ccd9690311::ShapeRunWithFontInput1187   ShapeRunWithFontInput(const base::string16& full_text,
1188                         const TextRunHarfBuzz::FontParams& font_params,
1189                         Range full_range,
1190                         bool obscured,
1191                         float glyph_width_for_test,
1192                         int obscured_glyph_spacing,
1193                         bool subpixel_rendering_suppressed)
1194       : skia_face(font_params.skia_face),
1195         render_params(font_params.render_params),
1196         script(font_params.script),
1197         font_size(font_params.font_size),
1198         obscured_glyph_spacing(obscured_glyph_spacing),
1199         glyph_width_for_test(glyph_width_for_test),
1200         is_rtl(font_params.is_rtl),
1201         obscured(obscured),
1202         subpixel_rendering_suppressed(subpixel_rendering_suppressed) {
1203     // hb_buffer_add_utf16 will read the previous and next 5 unicode characters
1204     // (which can have a maximum length of 2 uint16_t) as "context" that is used
1205     // only for Arabic (which is RTL). Read the previous and next 10 uint16_ts
1206     // to ensure that we capture all of this context if we're using RTL.
1207     size_t kContextSize = is_rtl ? 10 : 0;
1208     size_t context_start = full_range.start() < kContextSize
1209                                ? 0
1210                                : full_range.start() - kContextSize;
1211     size_t context_end =
1212         std::min(full_text.length(), full_range.end() + kContextSize);
1213     range = Range(full_range.start() - context_start,
1214                   full_range.end() - context_start);
1215     text = full_text.substr(context_start, context_end - context_start);
1216 
1217     // Pre-compute the hash to avoid having to re-hash at every comparison.
1218     // Attempt to minimize collisions by including the typeface, script, font
1219     // size, text and the text range.
1220     hash = base::HashInts(hash, skia_face->uniqueID());
1221     hash = base::HashInts(hash, script);
1222     hash = base::HashInts(hash, font_size);
1223     hash = base::Hash(text);
1224     hash = base::HashInts(hash, range.start());
1225     hash = base::HashInts(hash, range.length());
1226   }
1227 
operator ==gfx::internal::__anon56ccd9690311::ShapeRunWithFontInput1228   bool operator==(const ShapeRunWithFontInput& other) const {
1229     return text == other.text && skia_face == other.skia_face &&
1230            render_params == other.render_params &&
1231            font_size == other.font_size && range == other.range &&
1232            script == other.script && is_rtl == other.is_rtl &&
1233            obscured == other.obscured &&
1234            glyph_width_for_test == other.glyph_width_for_test &&
1235            obscured_glyph_spacing == other.obscured_glyph_spacing &&
1236            subpixel_rendering_suppressed == other.subpixel_rendering_suppressed;
1237   }
1238 
1239   struct Hash {
operator ()gfx::internal::__anon56ccd9690311::ShapeRunWithFontInput::Hash1240     size_t operator()(const ShapeRunWithFontInput& key) const {
1241       return key.hash;
1242     }
1243   };
1244 
1245   sk_sp<SkTypeface> skia_face;
1246   FontRenderParams render_params;
1247   UScriptCode script;
1248   int font_size;
1249   int obscured_glyph_spacing;
1250   float glyph_width_for_test;
1251   bool is_rtl;
1252   bool obscured;
1253   bool subpixel_rendering_suppressed;
1254 
1255   // The parts of the input text that may be read by hb_buffer_add_utf16.
1256   base::string16 text;
1257   // The conversion of the input range to a range within |text|.
1258   Range range;
1259   // The hash is cached to avoid repeated calls.
1260   size_t hash = 0;
1261 };
1262 
1263 // An MRU cache of the results from calling ShapeRunWithFont. The maximum cache
1264 // size used in blink::ShapeCache is 10k. A Finch experiment showed that
1265 // reducing the cache size to 1k has no performance impact.
1266 constexpr int kShapeRunCacheSize = 1000;
1267 using ShapeRunCacheBase = base::HashingMRUCache<ShapeRunWithFontInput,
1268                                                 TextRunHarfBuzz::ShapeOutput,
1269                                                 ShapeRunWithFontInput::Hash>;
1270 class ShapeRunCache : public ShapeRunCacheBase {
1271  public:
ShapeRunCache()1272   ShapeRunCache() : ShapeRunCacheBase(kShapeRunCacheSize) {}
1273 };
1274 
ShapeRunWithFont(const ShapeRunWithFontInput & in,TextRunHarfBuzz::ShapeOutput * out)1275 void ShapeRunWithFont(const ShapeRunWithFontInput& in,
1276                       TextRunHarfBuzz::ShapeOutput* out) {
1277   TRACE_EVENT0("ui", "RenderTextHarfBuzz::ShapeRunWithFontInternal");
1278 
1279   hb_font_t* harfbuzz_font =
1280       CreateHarfBuzzFont(in.skia_face, SkIntToScalar(in.font_size),
1281                          in.render_params, in.subpixel_rendering_suppressed);
1282 
1283   // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz
1284   // buffer holds our text, run information to be used by the shaping engine,
1285   // and the resulting glyph data.
1286   hb_buffer_t* buffer = hb_buffer_create();
1287   // Note that the value of the |item_offset| argument (here specified as
1288   // |in.range.start()|) does affect the result, so we will have to adjust
1289   // the computed offsets.
1290   hb_buffer_add_utf16(
1291       buffer, reinterpret_cast<const uint16_t*>(in.text.c_str()),
1292       static_cast<int>(in.text.length()), in.range.start(), in.range.length());
1293   hb_buffer_set_script(buffer, ICUScriptToHBScript(in.script));
1294   hb_buffer_set_direction(buffer,
1295                           in.is_rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
1296   // TODO(ckocagil): Should we determine the actual language?
1297   hb_buffer_set_language(buffer, hb_language_get_default());
1298 
1299   // Shape the text.
1300   hb_shape(harfbuzz_font, buffer, NULL, 0);
1301 
1302   // Populate the run fields with the resulting glyph data in the buffer.
1303   unsigned int glyph_count = 0;
1304   hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count);
1305   out->glyph_count = glyph_count;
1306   hb_glyph_position_t* hb_positions =
1307       hb_buffer_get_glyph_positions(buffer, NULL);
1308   out->glyphs.resize(out->glyph_count);
1309   out->glyph_to_char.resize(out->glyph_count);
1310   out->positions.resize(out->glyph_count);
1311   out->width = 0.0f;
1312 
1313   // Font on MAC like ".SF NS Text" may have a negative x_offset. Positive
1314   // x_offset are also found on Windows (e.g. "Segoe UI"). It requires tests
1315   // relying on the behavior of |glyph_width_for_test_| to also be given a zero
1316   // x_offset, otherwise expectations get thrown off
1317   // (see: http://crbug.com/1056220).
1318   const bool force_zero_offset = in.glyph_width_for_test > 0;
1319   constexpr uint16_t kMissingGlyphId = 0;
1320 
1321   out->missing_glyph_count = 0;
1322   for (size_t i = 0; i < out->glyph_count; ++i) {
1323     DCHECK_LE(infos[i].codepoint, std::numeric_limits<uint16_t>::max());
1324     uint16_t glyph = static_cast<uint16_t>(infos[i].codepoint);
1325     out->glyphs[i] = glyph;
1326     if (glyph == kMissingGlyphId)
1327       out->missing_glyph_count += 1;
1328     DCHECK_GE(infos[i].cluster, in.range.start());
1329     out->glyph_to_char[i] = infos[i].cluster - in.range.start();
1330     const SkScalar x_offset =
1331         force_zero_offset ? 0
1332                           : HarfBuzzUnitsToSkiaScalar(hb_positions[i].x_offset);
1333     const SkScalar y_offset =
1334         HarfBuzzUnitsToSkiaScalar(hb_positions[i].y_offset);
1335     out->positions[i].set(out->width + x_offset, -y_offset);
1336 
1337     if (in.glyph_width_for_test == 0)
1338       out->width += HarfBuzzUnitsToFloat(hb_positions[i].x_advance);
1339     else if (hb_positions[i].x_advance)  // Leave zero-width glyphs alone.
1340       out->width += in.glyph_width_for_test;
1341 
1342     if (in.obscured)
1343       out->width += in.obscured_glyph_spacing;
1344 
1345     // When subpixel positioning is not enabled, glyph width is rounded to avoid
1346     // fractional width. Disable this conversion when a glyph width is provided
1347     // for testing. Using an integral glyph width has the same behavior as
1348     // disabling the subpixel positioning.
1349     const bool force_subpixel_for_test = in.glyph_width_for_test != 0;
1350 
1351     // Round run widths if subpixel positioning is off to match native behavior.
1352     if (!in.render_params.subpixel_positioning && !force_subpixel_for_test)
1353       out->width = std::round(out->width);
1354   }
1355 
1356   hb_buffer_destroy(buffer);
1357   hb_font_destroy(harfbuzz_font);
1358 }
1359 
GetApplicationLocale()1360 std::string GetApplicationLocale() {
1361 #if defined(OS_ANDROID)
1362   // TODO(etienneb): Android locale should work the same way than base locale.
1363   return base::android::GetDefaultLocaleString();
1364 #else
1365   return base::i18n::GetConfiguredLocale();
1366 #endif
1367 }
1368 
1369 }  // namespace
1370 
1371 }  // namespace internal
1372 
RenderTextHarfBuzz()1373 RenderTextHarfBuzz::RenderTextHarfBuzz()
1374     : RenderText(),
1375       update_layout_run_list_(false),
1376       update_display_run_list_(false),
1377       update_display_text_(false),
1378       locale_(internal::GetApplicationLocale()) {
1379   set_truncate_length(kMaxTextLength);
1380 }
1381 
~RenderTextHarfBuzz()1382 RenderTextHarfBuzz::~RenderTextHarfBuzz() {}
1383 
GetDisplayText()1384 const base::string16& RenderTextHarfBuzz::GetDisplayText() {
1385   // TODO(krb): Consider other elision modes for multiline.
1386   if ((multiline() && (max_lines() == 0 || elide_behavior() != ELIDE_TAIL)) ||
1387       elide_behavior() == NO_ELIDE || elide_behavior() == FADE_TAIL) {
1388     // Call UpdateDisplayText to clear |display_text_| and |text_elided_|
1389     // on the RenderText class.
1390     UpdateDisplayText(0);
1391     update_display_text_ = false;
1392     display_run_list_.reset();
1393     return GetLayoutText();
1394   }
1395 
1396   EnsureLayoutRunList();
1397   DCHECK(!update_display_text_);
1398   return text_elided() ? display_text() : GetLayoutText();
1399 }
1400 
GetStringSizeF()1401 SizeF RenderTextHarfBuzz::GetStringSizeF() {
1402   EnsureLayout();
1403   return total_size_;
1404 }
1405 
GetLineSizeF(const SelectionModel & caret)1406 SizeF RenderTextHarfBuzz::GetLineSizeF(const SelectionModel& caret) {
1407   const internal::ShapedText* shaped_text = GetShapedText();
1408   const auto& caret_run = GetRunContainingCaret(caret);
1409   for (const auto& line : shaped_text->lines()) {
1410     for (const internal::LineSegment& segment : line.segments) {
1411       if (segment.run == caret_run)
1412         return line.size;
1413     }
1414   }
1415 
1416   return shaped_text->lines().back().size;
1417 }
1418 
GetSubstringBounds(const Range & range)1419 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
1420   EnsureLayout();
1421   DCHECK(!update_display_run_list_);
1422   DCHECK(range.IsBoundedBy(Range(0, text().length())));
1423   const Range grapheme_range = ExpandRangeToGraphemeBoundary(range);
1424   const Range display_range(TextIndexToDisplayIndex(grapheme_range.start()),
1425                             TextIndexToDisplayIndex(grapheme_range.end()));
1426   DCHECK(IsValidDisplayRange(display_range));
1427 
1428   std::vector<Rect> rects;
1429   if (display_range.is_empty())
1430     return rects;
1431 
1432   internal::TextRunList* run_list = GetRunList();
1433   const internal::ShapedText* shaped_text = GetShapedText();
1434   for (size_t line_index = 0; line_index < shaped_text->lines().size();
1435        ++line_index) {
1436     const internal::Line& line = shaped_text->lines()[line_index];
1437     // Only the last line can be empty.
1438     DCHECK(!line.segments.empty() ||
1439            (line_index == shaped_text->lines().size() - 1));
1440     float line_start_x =
1441         line.segments.empty()
1442             ? 0
1443             : run_list->runs()[line.segments[0].run]->preceding_run_widths;
1444 
1445     if (line.segments.size() > 1 && IsNewlineSegment(line.segments[0]))
1446       line_start_x += line.segments[0].width();
1447 
1448     std::vector<Rect> current_line_rects;
1449     for (const internal::LineSegment& segment : line.segments) {
1450       const Range intersection = segment.char_range.Intersect(display_range);
1451       DCHECK(!intersection.is_reversed());
1452       if (!intersection.is_empty()) {
1453         const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
1454         RangeF selected_span =
1455             run.GetGraphemeSpanForCharRange(this, intersection);
1456         DCHECK(!selected_span.is_reversed());
1457         int start_x = base::ClampFloor(selected_span.start() - line_start_x);
1458         int end_x = base::ClampCeil(selected_span.end() - line_start_x);
1459         Rect rect(start_x, 0, end_x - start_x,
1460                   base::ClampCeil(line.size.height()));
1461         current_line_rects.push_back(rect + GetLineOffset(line_index));
1462       }
1463     }
1464     MergeIntersectingRects(current_line_rects);
1465     rects.insert(rects.end(), current_line_rects.begin(),
1466                  current_line_rects.end());
1467   }
1468   return rects;
1469 }
1470 
GetCursorSpan(const Range & text_range)1471 RangeF RenderTextHarfBuzz::GetCursorSpan(const Range& text_range) {
1472   DCHECK(!text_range.is_reversed());
1473   EnsureLayout();
1474   const size_t index = text_range.start();
1475   size_t run_index =
1476       GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD));
1477   internal::TextRunList* run_list = GetRunList();
1478 
1479   // Return zero if the text is empty.
1480   if (run_list->size() == 0 || text().empty())
1481     return RangeF(0);
1482 
1483   // Use the last run if the index is invalid or beyond the layout text size.
1484   Range valid_range(text_range.start(), text_range.end());
1485   if (run_index >= run_list->size()) {
1486     valid_range = Range(text().length() - 1, text().length());
1487     run_index = run_list->size() - 1;
1488   }
1489 
1490   internal::TextRunHarfBuzz* run = run_list->runs()[run_index].get();
1491 
1492   size_t next_grapheme_start = valid_range.end();
1493   if (!IsValidCursorIndex(next_grapheme_start)) {
1494     next_grapheme_start =
1495         IndexOfAdjacentGrapheme(next_grapheme_start, CURSOR_FORWARD);
1496   }
1497 
1498   Range display_range(TextIndexToDisplayIndex(valid_range.start()),
1499                       TextIndexToDisplayIndex(next_grapheme_start));
1500   DCHECK(IsValidDisplayRange(display_range));
1501 
1502   // Although highly likely, there's no guarantee that a single text run is used
1503   // for the entire cursor span. For example, Unicode Variation Selectors are
1504   // incorrectly placed in the next run; see crbug.com/775404. (For these, the
1505   // variation selector has zero width, so it's safe to ignore the second run).
1506   // TODO(tapted): Change this to a DCHECK when crbug.com/775404 is fixed.
1507   display_range = display_range.Intersect(run->range);
1508 
1509   RangeF bounds = run->GetGraphemeSpanForCharRange(this, display_range);
1510   return run->font_params.is_rtl ? RangeF(bounds.end(), bounds.start())
1511                                  : bounds;
1512 }
1513 
GetLineContainingCaret(const SelectionModel & caret)1514 size_t RenderTextHarfBuzz::GetLineContainingCaret(const SelectionModel& caret) {
1515   EnsureLayout();
1516 
1517   if (caret.caret_pos() == 0)
1518     return 0;
1519 
1520   if (!multiline()) {
1521     DCHECK_EQ(1u, GetShapedText()->lines().size());
1522     return 0;
1523   }
1524 
1525   size_t layout_position = TextIndexToDisplayIndex(caret.caret_pos());
1526   LogicalCursorDirection affinity = caret.caret_affinity();
1527   const internal::ShapedText* shaped_text = GetShapedText();
1528   for (size_t line_index = 0; line_index < shaped_text->lines().size();
1529        ++line_index) {
1530     const internal::Line& line = shaped_text->lines()[line_index];
1531     for (const internal::LineSegment& segment : line.segments) {
1532       if (RangeContainsCaret(segment.char_range, layout_position, affinity))
1533         return LineIndexForNewline(line_index, text(), segment, caret);
1534     }
1535   }
1536 
1537   return shaped_text->lines().size() - 1;
1538 }
1539 
AdjacentCharSelectionModel(const SelectionModel & selection,VisualCursorDirection direction)1540 SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel(
1541     const SelectionModel& selection,
1542     VisualCursorDirection direction) {
1543   DCHECK(!update_display_run_list_);
1544 
1545   internal::TextRunList* run_list = GetRunList();
1546   internal::TextRunHarfBuzz* run;
1547 
1548   size_t run_index = GetRunContainingCaret(selection);
1549   if (run_index >= run_list->size()) {
1550     // The cursor is not in any run: we're at the visual and logical edge.
1551     SelectionModel edge = EdgeSelectionModel(direction);
1552     if (edge.caret_pos() == selection.caret_pos())
1553       return edge;
1554     int visual_index = (direction == CURSOR_RIGHT) ? 0 : run_list->size() - 1;
1555     run = run_list->runs()[run_list->visual_to_logical(visual_index)].get();
1556   } else {
1557     // If the cursor is moving within the current run, just move it by one
1558     // grapheme in the appropriate direction.
1559     run = run_list->runs()[run_index].get();
1560     size_t caret = selection.caret_pos();
1561     bool forward_motion = run->font_params.is_rtl == (direction == CURSOR_LEFT);
1562     if (forward_motion) {
1563       if (caret < DisplayIndexToTextIndex(run->range.end())) {
1564         caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
1565         return SelectionModel(caret, CURSOR_BACKWARD);
1566       }
1567     } else {
1568       if (caret > DisplayIndexToTextIndex(run->range.start())) {
1569         caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
1570         return SelectionModel(caret, CURSOR_FORWARD);
1571       }
1572     }
1573     // The cursor is at the edge of a run; move to the visually adjacent run.
1574     int visual_index = run_list->logical_to_visual(run_index);
1575     visual_index += (direction == CURSOR_LEFT) ? -1 : 1;
1576     if (visual_index < 0 || visual_index >= static_cast<int>(run_list->size()))
1577       return EdgeSelectionModel(direction);
1578     run = run_list->runs()[run_list->visual_to_logical(visual_index)].get();
1579   }
1580   bool forward_motion = run->font_params.is_rtl == (direction == CURSOR_LEFT);
1581   return forward_motion ? FirstSelectionModelInsideRun(run) :
1582                           LastSelectionModelInsideRun(run);
1583 }
1584 
AdjacentWordSelectionModel(const SelectionModel & selection,VisualCursorDirection direction)1585 SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel(
1586     const SelectionModel& selection,
1587     VisualCursorDirection direction) {
1588   if (obscured())
1589     return EdgeSelectionModel(direction);
1590 
1591   base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
1592   bool success = iter.Init();
1593   DCHECK(success);
1594   if (!success)
1595     return selection;
1596 
1597   internal::TextRunList* run_list = GetRunList();
1598   SelectionModel current(selection);
1599   for (;;) {
1600     current = AdjacentCharSelectionModel(current, direction);
1601     size_t run = GetRunContainingCaret(current);
1602     if (run == run_list->size())
1603       break;
1604     size_t cursor = current.caret_pos();
1605 #if defined(OS_WIN)
1606     // Windows generally advances to the start of a word in either direction.
1607     // TODO: Break on the end of a word when the neighboring text is
1608     // punctuation.
1609     if (iter.IsStartOfWord(cursor))
1610       break;
1611 #else
1612     const bool is_forward =
1613         run_list->runs()[run]->font_params.is_rtl == (direction == CURSOR_LEFT);
1614     if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor))
1615       break;
1616 #endif  // defined(OS_WIN)
1617   }
1618   return current;
1619 }
1620 
AdjacentLineSelectionModel(const SelectionModel & selection,VisualCursorDirection direction)1621 SelectionModel RenderTextHarfBuzz::AdjacentLineSelectionModel(
1622     const SelectionModel& selection,
1623     VisualCursorDirection direction) {
1624   DCHECK(direction == CURSOR_UP || direction == CURSOR_DOWN);
1625 
1626   size_t line = GetLineContainingCaret(selection);
1627   if (line == 0 && direction == CURSOR_UP) {
1628     reset_cached_cursor_x();
1629     return SelectionModel(0, CURSOR_BACKWARD);
1630   }
1631   if (line == GetShapedText()->lines().size() - 1 && direction == CURSOR_DOWN) {
1632     reset_cached_cursor_x();
1633     return SelectionModel(text().length(), CURSOR_FORWARD);
1634   }
1635 
1636   direction == CURSOR_UP ? --line : ++line;
1637   Rect bounds = GetCursorBounds(selection, true);
1638   Point target = bounds.origin();
1639   if (cached_cursor_x())
1640     target.set_x(cached_cursor_x().value());
1641   else
1642     set_cached_cursor_x(target.x());
1643   if (direction == CURSOR_UP)
1644     target.Offset(0, -bounds.size().height() / 2);
1645   else
1646     target.Offset(0, bounds.size().height() * 3 / 2);
1647   SelectionModel next = FindCursorPosition(target, Point());
1648   size_t next_line = GetLineContainingCaret(next);
1649 
1650   // If the |target| position is at the newline character, the caret is drawn to
1651   // the next line. e.g., when the caret is at the beginning of the line in RTL
1652   // text. Move the caret to the position of the previous character to move the
1653   // caret to the previous line.
1654   if (next_line == line + 1)
1655     next = SelectionModel(next.caret_pos() - 1, next.caret_affinity());
1656 
1657   return next;
1658 }
1659 
OnLayoutTextAttributeChanged(bool text_changed)1660 void RenderTextHarfBuzz::OnLayoutTextAttributeChanged(bool text_changed) {
1661   RenderText::OnLayoutTextAttributeChanged(text_changed);
1662 
1663   update_layout_run_list_ = true;
1664   OnDisplayTextAttributeChanged();
1665 }
1666 
OnDisplayTextAttributeChanged()1667 void RenderTextHarfBuzz::OnDisplayTextAttributeChanged() {
1668   update_display_text_ = true;
1669   set_shaped_text(nullptr);
1670 }
1671 
EnsureLayout()1672 void RenderTextHarfBuzz::EnsureLayout() {
1673   EnsureLayoutRunList();
1674 
1675   if (update_display_run_list_) {
1676     DCHECK(text_elided());
1677     const base::string16& display_text = GetDisplayText();
1678     display_run_list_ = std::make_unique<internal::TextRunList>();
1679 
1680     if (!display_text.empty())
1681       ItemizeAndShapeText(display_text, display_run_list_.get());
1682     update_display_run_list_ = false;
1683     set_shaped_text(nullptr);
1684   }
1685 
1686   if (!has_shaped_text()) {
1687     internal::TextRunList* run_list = GetRunList();
1688     const int height = std::max(font_list().GetHeight(), min_line_height());
1689     HarfBuzzLineBreaker line_breaker(
1690         display_rect().width(),
1691         DetermineBaselineCenteringText(height, font_list()), height,
1692         glyph_height_for_test_, word_wrap_behavior(), GetDisplayText(),
1693         multiline() ? &GetLineBreaks() : nullptr, *run_list);
1694 
1695     if (multiline())
1696       line_breaker.ConstructMultiLines();
1697     else
1698       line_breaker.ConstructSingleLine();
1699     std::vector<internal::Line> lines;
1700     line_breaker.FinalizeLines(&lines, &total_size_);
1701     if (multiline() && max_lines()) {
1702       // TODO(crbug.com/866720): no more than max_lines() should be rendered.
1703       // Remove the IsHomogeneous() condition for the following DCHECK when the
1704       // bug is fixed.
1705       if (IsHomogeneous()) {
1706         DCHECK_LE(lines.size(), max_lines());
1707       }
1708     }
1709 
1710     set_shaped_text(std::make_unique<internal::ShapedText>(lines));
1711   }
1712 }
1713 
DrawVisualText(internal::SkiaTextRenderer * renderer,const std::vector<Range> & selections)1714 void RenderTextHarfBuzz::DrawVisualText(internal::SkiaTextRenderer* renderer,
1715                                         const std::vector<Range>& selections) {
1716   DCHECK(!update_layout_run_list_);
1717   DCHECK(!update_display_run_list_);
1718   DCHECK(!update_display_text_);
1719 
1720   const internal::ShapedText* shaped_text = GetShapedText();
1721   if (shaped_text->lines().empty())
1722     return;
1723 
1724   ApplyFadeEffects(renderer);
1725   ApplyTextShadows(renderer);
1726 
1727   // Apply the selected text color to the [un-reversed] selection range.
1728   BreakList<SkColor> colors = layout_colors();
1729   for (auto selection : selections) {
1730     if (!selection.is_empty()) {
1731       const Range grapheme_range = ExpandRangeToGraphemeBoundary(selection);
1732       colors.ApplyValue(
1733           selection_color(),
1734           Range(TextIndexToDisplayIndex(grapheme_range.GetMin()),
1735                 TextIndexToDisplayIndex(grapheme_range.GetMax())));
1736     }
1737   }
1738 
1739   internal::TextRunList* run_list = GetRunList();
1740   const base::string16& display_text = GetDisplayText();
1741   for (size_t i = 0; i < shaped_text->lines().size(); ++i) {
1742     const internal::Line& line = shaped_text->lines()[i];
1743     const Vector2d origin = GetLineOffset(i) + Vector2d(0, line.baseline);
1744     SkScalar preceding_segment_widths = 0;
1745     for (const internal::LineSegment& segment : line.segments) {
1746       // Don't draw the newline glyph (crbug.com/680430).
1747       if (IsNewlineSegment(display_text, segment))
1748         continue;
1749 
1750       const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
1751       renderer->SetTypeface(run.font_params.skia_face);
1752       renderer->SetTextSize(SkIntToScalar(run.font_params.font_size));
1753       renderer->SetFontRenderParams(run.font_params.render_params,
1754                                     subpixel_rendering_suppressed());
1755       Range glyphs_range = run.CharRangeToGlyphRange(segment.char_range);
1756       std::vector<SkPoint> positions(glyphs_range.length());
1757       SkScalar offset_x = preceding_segment_widths -
1758                           ((glyphs_range.GetMin() != 0)
1759                                ? run.shape.positions[glyphs_range.GetMin()].x()
1760                                : 0);
1761       for (size_t j = 0; j < glyphs_range.length(); ++j) {
1762         positions[j] = run.shape.positions[(glyphs_range.is_reversed())
1763                                                ? (glyphs_range.start() - j)
1764                                                : (glyphs_range.start() + j)];
1765         positions[j].offset(
1766             SkIntToScalar(origin.x()) + offset_x,
1767             SkIntToScalar(origin.y() + run.font_params.baseline_offset));
1768       }
1769       for (auto it = colors.GetBreak(segment.char_range.start());
1770            it != colors.breaks().end() && it->first < segment.char_range.end();
1771            ++it) {
1772         const Range intersection =
1773             colors.GetRange(it).Intersect(segment.char_range);
1774         const Range colored_glyphs = run.CharRangeToGlyphRange(intersection);
1775         // The range may be empty if a portion of a multi-character grapheme is
1776         // selected, yielding two colors for a single glyph. For now, this just
1777         // paints the glyph with a single style, but it should paint it twice,
1778         // clipped according to selection bounds. See http://crbug.com/366786
1779         if (colored_glyphs.is_empty())
1780           continue;
1781 
1782         renderer->SetForegroundColor(it->second);
1783         renderer->DrawPosText(
1784             &positions[colored_glyphs.start() - glyphs_range.start()],
1785             &run.shape.glyphs[colored_glyphs.start()], colored_glyphs.length());
1786         int start_x = SkScalarRoundToInt(
1787             positions[colored_glyphs.start() - glyphs_range.start()].x());
1788         int end_x = SkScalarRoundToInt(
1789             (colored_glyphs.end() == glyphs_range.end())
1790                 ? (SkFloatToScalar(segment.width()) + preceding_segment_widths +
1791                    SkIntToScalar(origin.x()))
1792                 : positions[colored_glyphs.end() - glyphs_range.start()].x());
1793         if (run.font_params.heavy_underline)
1794           renderer->DrawUnderline(start_x, origin.y(), end_x - start_x, 2.0);
1795         else if (run.font_params.underline)
1796           renderer->DrawUnderline(start_x, origin.y(), end_x - start_x);
1797         if (run.font_params.strike)
1798           renderer->DrawStrike(start_x, origin.y(), end_x - start_x,
1799                                strike_thickness_factor());
1800       }
1801       preceding_segment_widths += SkFloatToScalar(segment.width());
1802     }
1803   }
1804 }
1805 
GetRunContainingCaret(const SelectionModel & caret)1806 size_t RenderTextHarfBuzz::GetRunContainingCaret(
1807     const SelectionModel& caret) {
1808   DCHECK(!update_display_run_list_);
1809   size_t layout_position = TextIndexToDisplayIndex(caret.caret_pos());
1810   LogicalCursorDirection affinity = caret.caret_affinity();
1811   internal::TextRunList* run_list = GetRunList();
1812   for (size_t i = 0; i < run_list->size(); ++i) {
1813     internal::TextRunHarfBuzz* run = run_list->runs()[i].get();
1814     if (RangeContainsCaret(run->range, layout_position, affinity))
1815       return i;
1816   }
1817   return run_list->size();
1818 }
1819 
FirstSelectionModelInsideRun(const internal::TextRunHarfBuzz * run)1820 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun(
1821     const internal::TextRunHarfBuzz* run) {
1822   size_t position = DisplayIndexToTextIndex(run->range.start());
1823   position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD);
1824   return SelectionModel(position, CURSOR_BACKWARD);
1825 }
1826 
LastSelectionModelInsideRun(const internal::TextRunHarfBuzz * run)1827 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun(
1828     const internal::TextRunHarfBuzz* run) {
1829   size_t position = DisplayIndexToTextIndex(run->range.end());
1830   position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD);
1831   return SelectionModel(position, CURSOR_FORWARD);
1832 }
1833 
ItemizeAndShapeText(const base::string16 & text,internal::TextRunList * run_list)1834 void RenderTextHarfBuzz::ItemizeAndShapeText(const base::string16& text,
1835                                              internal::TextRunList* run_list) {
1836   CommonizedRunsMap commonized_run_map;
1837   ItemizeTextToRuns(text, run_list, &commonized_run_map);
1838 
1839   for (auto iter = commonized_run_map.begin(); iter != commonized_run_map.end();
1840        ++iter) {
1841     internal::TextRunHarfBuzz::FontParams font_params = iter->first;
1842     font_params.ComputeRenderParamsFontSizeAndBaselineOffset();
1843     ShapeRuns(text, font_params, std::move(iter->second));
1844   }
1845 
1846   run_list->InitIndexMap();
1847   run_list->ComputePrecedingRunWidths();
1848 }
1849 
ItemizeTextToRuns(const base::string16 & text,internal::TextRunList * out_run_list,CommonizedRunsMap * out_commonized_run_map)1850 void RenderTextHarfBuzz::ItemizeTextToRuns(
1851     const base::string16& text,
1852     internal::TextRunList* out_run_list,
1853     CommonizedRunsMap* out_commonized_run_map) {
1854   TRACE_EVENT1("ui", "RenderTextHarfBuzz::ItemizeTextToRuns", "text_length",
1855                text.length());
1856   DCHECK(!text.empty());
1857   const Font& primary_font = font_list().GetPrimaryFont();
1858 
1859   // If ICU fails to itemize the text, we create a run that spans the entire
1860   // text. This is needed because leaving the runs set empty causes some clients
1861   // to misbehave since they expect non-zero text metrics from a non-empty text.
1862   ui::gfx::BiDiLineIterator bidi_iterator;
1863 
1864   if (!bidi_iterator.Open(text, GetTextDirectionForGivenText(text))) {
1865     auto run = std::make_unique<internal::TextRunHarfBuzz>(
1866         font_list().GetPrimaryFont());
1867     run->range = Range(0, text.length());
1868     internal::TextRunHarfBuzz::FontParams font_params(primary_font);
1869     (*out_commonized_run_map)[font_params].push_back(run.get());
1870     out_run_list->Add(std::move(run));
1871     return;
1872   }
1873 
1874   // Iterator to split ranged styles and baselines. The color attributes don't
1875   // break text runs to keep ligature between graphemes (e.g. Arabic word).
1876   internal::StyleIterator style = GetLayoutTextStyleIterator();
1877 
1878   // Split the original text by logical runs, then each logical run by common
1879   // script and each sequence at special characters and style boundaries. This
1880   // invariant holds: bidi_run_start <= script_run_start <= breaking_run_start
1881   // <= breaking_run_end <= script_run_end <= bidi_run_end
1882   for (size_t bidi_run_start = 0; bidi_run_start < text.length();) {
1883     // Determine the longest logical run (e.g. same bidi direction) from this
1884     // point.
1885     int32_t bidi_run_break = 0;
1886     UBiDiLevel bidi_level = 0;
1887     bidi_iterator.GetLogicalRun(bidi_run_start, &bidi_run_break, &bidi_level);
1888     size_t bidi_run_end = static_cast<size_t>(bidi_run_break);
1889     DCHECK_LT(bidi_run_start, bidi_run_end);
1890 
1891     ApplyForcedDirection(&bidi_level);
1892 
1893     for (size_t script_run_start = bidi_run_start;
1894          script_run_start < bidi_run_end;) {
1895       // Find the longest sequence of characters that have at least one common
1896       // UScriptCode value.
1897       UScriptCode script = USCRIPT_INVALID_CODE;
1898       size_t script_run_end =
1899           ScriptInterval(text, script_run_start,
1900                          bidi_run_end - script_run_start, &script) +
1901           script_run_start;
1902       DCHECK_LT(script_run_start, script_run_end);
1903 
1904       for (size_t breaking_run_start = script_run_start;
1905            breaking_run_start < script_run_end;) {
1906         // Find the break boundary for style. The style won't break a grapheme
1907         // since the style of the first character is applied to the whole
1908         // grapheme.
1909         style.IncrementToPosition(breaking_run_start);
1910         size_t text_style_end = style.GetTextBreakingRange().end();
1911 
1912         // Break runs at certain characters that need to be rendered separately
1913         // to prevent an unusual character from forcing a fallback font on the
1914         // entire run. After script intersection, many codepoints end up in the
1915         // script COMMON but can't be rendered together.
1916         size_t breaking_run_end = FindRunBreakingCharacter(
1917             text, script, breaking_run_start, text_style_end, script_run_end);
1918 
1919         DCHECK_LT(breaking_run_start, breaking_run_end);
1920         DCHECK(IsValidCodePointIndex(text, breaking_run_end));
1921 
1922         // Set the font params for the current run for the current run break.
1923         internal::TextRunHarfBuzz::FontParams font_params =
1924             CreateFontParams(primary_font, bidi_level, script, style);
1925 
1926         // Create the current run from [breaking_run_start, breaking_run_end[.
1927         auto run = std::make_unique<internal::TextRunHarfBuzz>(primary_font);
1928         run->range = Range(breaking_run_start, breaking_run_end);
1929 
1930         // Add the created run to the set of runs.
1931         (*out_commonized_run_map)[font_params].push_back(run.get());
1932         out_run_list->Add(std::move(run));
1933 
1934         // Move to the next run.
1935         breaking_run_start = breaking_run_end;
1936       }
1937 
1938       // Move to the next script sequence.
1939       script_run_start = script_run_end;
1940     }
1941 
1942     // Move to the next direction sequence.
1943     bidi_run_start = bidi_run_end;
1944   }
1945 
1946   // Add trace event to track incorrect usage of fallback fonts.
1947   // TODO(https://crbug.com/995789): Remove the following code when the issue
1948   // is fixed.
1949   bool tracing_enabled;
1950   TRACE_EVENT_CATEGORY_GROUP_ENABLED("fonts", &tracing_enabled);
1951   if (tracing_enabled) {
1952     std::string logging_str;
1953     for (const auto& iter : *out_commonized_run_map) {
1954       const internal::TextRunHarfBuzz::FontParams& font_params = iter.first;
1955       for (const auto* run : iter.second) {
1956         base::i18n::UTF16CharIterator text_iter(base::StringPiece16(
1957             text.c_str() + run->range.start(), run->range.length()));
1958         const UChar32 first_char = text_iter.get();
1959         const UBlockCode first_block = ublock_getCode(first_char);
1960         const char* script_name = uscript_getShortName(font_params.script);
1961         base::StringAppendF(&logging_str, "block=%d script=%s\n",
1962                             static_cast<int>(first_block),
1963                             script_name ? script_name : "");
1964       }
1965     }
1966     TRACE_EVENT_INSTANT1("fonts", "RenderTextHarfBuzz::ItemizeTextToRuns::Runs",
1967                          TRACE_EVENT_SCOPE_THREAD, "runs", logging_str);
1968   }
1969 }
1970 
ShapeRuns(const base::string16 & text,const internal::TextRunHarfBuzz::FontParams & font_params,std::vector<internal::TextRunHarfBuzz * > runs)1971 void RenderTextHarfBuzz::ShapeRuns(
1972     const base::string16& text,
1973     const internal::TextRunHarfBuzz::FontParams& font_params,
1974     std::vector<internal::TextRunHarfBuzz*> runs) {
1975   TRACE_EVENT1("ui", "RenderTextHarfBuzz::ShapeRuns", "run_count", runs.size());
1976 
1977   // Runs with a single newline character should be skipped since they can't be
1978   // rendered (see http://crbug/680430). The following code sets the runs
1979   // shaping output to report report the missing glyph and removes the runs from
1980   // the vector of runs to shape. The newline character doesn't have a
1981   // glyph, which otherwise forces this function to go through the expensive
1982   // font fallbacks before reporting a missing glyph (see http://crbug/972090).
1983   std::vector<internal::TextRunHarfBuzz*> need_shaping_runs;
1984   for (internal::TextRunHarfBuzz*& run : runs) {
1985     if ((run->range.length() == 1 && (text[run->range.start()] == '\r' ||
1986                                       text[run->range.start()] == '\n')) ||
1987         (run->range.length() == 2 && text[run->range.start()] == '\r' &&
1988          text[run->range.start() + 1] == '\n')) {
1989       // Newline runs can't be shaped. Shape this run as if the glyph is
1990       // missing.
1991       run->font_params = font_params;
1992       run->shape.missing_glyph_count = 1;
1993       run->shape.glyph_count = 1;
1994       run->shape.glyphs.resize(run->shape.glyph_count);
1995       run->shape.glyph_to_char.resize(run->shape.glyph_count);
1996       run->shape.positions.resize(run->shape.glyph_count);
1997       run->shape.width = glyph_width_for_test_;
1998     } else {
1999       // This run needs shaping.
2000       need_shaping_runs.push_back(run);
2001     }
2002   }
2003   runs.swap(need_shaping_runs);
2004   if (runs.empty()) {
2005     RecordShapeRunsFallback(ShapeRunFallback::NO_FALLBACK);
2006     return;
2007   }
2008 
2009   // Keep a set of fonts already tried for shaping runs.
2010   std::set<SkFontID> fallback_fonts_already_tried;
2011   std::vector<Font> fallback_font_candidates;
2012 
2013   // Shaping with primary configured fonts from font_list().
2014   for (const Font& font : font_list().GetFonts()) {
2015     internal::TextRunHarfBuzz::FontParams test_font_params = font_params;
2016     if (test_font_params.SetRenderParamsRematchFont(
2017             font, font.GetFontRenderParams()) &&
2018         !FontWasAlreadyTried(test_font_params.skia_face,
2019                              &fallback_fonts_already_tried)) {
2020       ShapeRunsWithFont(text, test_font_params, &runs);
2021       MarkFontAsTried(test_font_params.skia_face,
2022                       &fallback_fonts_already_tried);
2023       fallback_font_candidates.push_back(font);
2024     }
2025     if (runs.empty()) {
2026       RecordShapeRunsFallback(ShapeRunFallback::NO_FALLBACK);
2027       return;
2028     }
2029   }
2030 
2031   const Font& primary_font = font_list().GetPrimaryFont();
2032 
2033   // Find fallback fonts for the remaining runs using a worklist algorithm. Try
2034   // to shape the first run by using GetFallbackFont(...) and then try shaping
2035   // other runs with the same font. If the first font can't be shaped, remove it
2036   // and continue with the remaining runs until the worklist is empty. The
2037   // fallback font returned by GetFallbackFont(...) depends on the text of the
2038   // run and the results may differ between runs.
2039   std::vector<internal::TextRunHarfBuzz*> remaining_unshaped_runs;
2040   while (!runs.empty()) {
2041     Font fallback_font(primary_font);
2042     bool fallback_found;
2043     internal::TextRunHarfBuzz* current_run = *runs.begin();
2044     {
2045       SCOPED_UMA_HISTOGRAM_LONG_TIMER("RenderTextHarfBuzz.GetFallbackFontTime");
2046       TRACE_EVENT1("ui", "RenderTextHarfBuzz::GetFallbackFont", "script",
2047                    TRACE_STR_COPY(uscript_getShortName(font_params.script)));
2048       const base::StringPiece16 run_text(&text[current_run->range.start()],
2049                                          current_run->range.length());
2050       fallback_found =
2051           GetFallbackFont(primary_font, locale_, run_text, &fallback_font);
2052     }
2053 
2054     if (fallback_found) {
2055       internal::TextRunHarfBuzz::FontParams test_font_params = font_params;
2056       if (test_font_params.SetRenderParamsOverrideSkiaFaceFromFont(
2057               fallback_font, fallback_font.GetFontRenderParams()) &&
2058           !FontWasAlreadyTried(test_font_params.skia_face,
2059                                &fallback_fonts_already_tried)) {
2060         ShapeRunsWithFont(text, test_font_params, &runs);
2061         MarkFontAsTried(test_font_params.skia_face,
2062                         &fallback_fonts_already_tried);
2063       }
2064     }
2065 
2066     // Remove the first run if not fully shaped with its associated fallback
2067     // font.
2068     if (!runs.empty() && runs[0] == current_run) {
2069       remaining_unshaped_runs.push_back(current_run);
2070       runs.erase(runs.begin());
2071     }
2072   }
2073   runs.swap(remaining_unshaped_runs);
2074   if (runs.empty()) {
2075     RecordShapeRunsFallback(ShapeRunFallback::FALLBACK);
2076     return;
2077   }
2078 
2079   std::vector<Font> fallback_font_list;
2080   {
2081     SCOPED_UMA_HISTOGRAM_LONG_TIMER("RenderTextHarfBuzz.GetFallbackFontsTime");
2082     TRACE_EVENT1("ui", "RenderTextHarfBuzz::GetFallbackFonts", "script",
2083                  TRACE_STR_COPY(uscript_getShortName(font_params.script)));
2084     fallback_font_list = GetFallbackFonts(primary_font);
2085 
2086 #if defined(OS_WIN)
2087     // Append fonts in the fallback list of the fallback fonts.
2088     // TODO(tapted): Investigate whether there's a case that benefits from this
2089     // on Mac.
2090     for (const auto& fallback_font : fallback_font_candidates) {
2091       std::vector<Font> fallback_fonts = GetFallbackFonts(fallback_font);
2092       fallback_font_list.insert(fallback_font_list.end(),
2093                                 fallback_fonts.begin(), fallback_fonts.end());
2094     }
2095 
2096     // Add Segoe UI and its associated linked fonts to the fallback font list to
2097     // ensure that the fallback list covers the basic cases.
2098     // http://crbug.com/467459. On some Windows configurations the default font
2099     // could be a raster font like System, which would not give us a reasonable
2100     // fallback font list.
2101     Font segoe("Segoe UI", 13);
2102     if (!FontWasAlreadyTried(segoe.platform_font()->GetNativeSkTypeface(),
2103                              &fallback_fonts_already_tried)) {
2104       std::vector<Font> default_fallback_families = GetFallbackFonts(segoe);
2105       fallback_font_list.insert(fallback_font_list.end(),
2106                                 default_fallback_families.begin(),
2107                                 default_fallback_families.end());
2108     }
2109 #endif
2110   }
2111 
2112   // Use a set to track the fallback fonts and avoid duplicate entries.
2113   SCOPED_UMA_HISTOGRAM_LONG_TIMER(
2114       "RenderTextHarfBuzz.ShapeRunsWithFallbackFontsTime");
2115   TRACE_EVENT1("ui", "RenderTextHarfBuzz::ShapeRunsWithFallbackFonts",
2116                "fonts_count", fallback_font_list.size());
2117 
2118   // Try shaping with the fallback fonts.
2119   for (const auto& font : fallback_font_list) {
2120     std::string font_name = font.GetFontName();
2121 
2122     FontRenderParamsQuery query;
2123     query.families.push_back(font_name);
2124     query.pixel_size = font_params.font_size;
2125     query.style = font_params.italic ? Font::ITALIC : 0;
2126     FontRenderParams fallback_render_params = GetFontRenderParams(query, NULL);
2127     internal::TextRunHarfBuzz::FontParams test_font_params = font_params;
2128     if (test_font_params.SetRenderParamsOverrideSkiaFaceFromFont(
2129             font, fallback_render_params) &&
2130         !FontWasAlreadyTried(test_font_params.skia_face,
2131                              &fallback_fonts_already_tried)) {
2132       ShapeRunsWithFont(text, test_font_params, &runs);
2133       MarkFontAsTried(test_font_params.skia_face,
2134                       &fallback_fonts_already_tried);
2135     }
2136     if (runs.empty()) {
2137       TRACE_EVENT_INSTANT2("ui", "RenderTextHarfBuzz::FallbackFont",
2138                            TRACE_EVENT_SCOPE_THREAD, "font_name",
2139                            TRACE_STR_COPY(font_name.c_str()),
2140                            "primary_font_name", primary_font.GetFontName());
2141       RecordShapeRunsFallback(ShapeRunFallback::FALLBACKS);
2142       return;
2143     }
2144   }
2145 
2146   for (internal::TextRunHarfBuzz*& run : runs) {
2147     if (run->shape.missing_glyph_count == std::numeric_limits<size_t>::max()) {
2148       run->shape.glyph_count = 0;
2149       run->shape.width = 0.0f;
2150     }
2151   }
2152 
2153   RecordShapeRunsFallback(ShapeRunFallback::FAILED);
2154 }
2155 
ShapeRunsWithFont(const base::string16 & text,const internal::TextRunHarfBuzz::FontParams & font_params,std::vector<internal::TextRunHarfBuzz * > * in_out_runs)2156 void RenderTextHarfBuzz::ShapeRunsWithFont(
2157     const base::string16& text,
2158     const internal::TextRunHarfBuzz::FontParams& font_params,
2159     std::vector<internal::TextRunHarfBuzz*>* in_out_runs) {
2160   // ShapeRunWithFont can be extremely slow, so use cached results if possible.
2161   // Only do this on the UI thread, to avoid synchronization overhead (and
2162   // because almost all calls are on the UI thread. Also avoid caching long
2163   // strings, to avoid blowing up the cache size.
2164   constexpr size_t kMaxRunLengthToCache = 25;
2165   static base::NoDestructor<internal::ShapeRunCache> cache;
2166 
2167   std::vector<internal::TextRunHarfBuzz*> runs_with_missing_glyphs;
2168   for (internal::TextRunHarfBuzz*& run : *in_out_runs) {
2169     // First do a cache lookup.
2170     bool can_use_cache = base::CurrentUIThread::IsSet() &&
2171                          run->range.length() <= kMaxRunLengthToCache;
2172     bool found_in_cache = false;
2173     const internal::ShapeRunWithFontInput cache_key(
2174         text, font_params, run->range, obscured(), glyph_width_for_test_,
2175         obscured_glyph_spacing(), subpixel_rendering_suppressed());
2176     if (can_use_cache) {
2177       auto found = cache.get()->Get(cache_key);
2178       if (found != cache.get()->end()) {
2179         run->UpdateFontParamsAndShape(font_params, found->second);
2180         found_in_cache = true;
2181       }
2182     }
2183 
2184     // If that fails, compute the shape of the run, and add the result to the
2185     // cache.
2186     // TODO(ccameron): Coalesce calls to ShapeRunsWithFont when possible.
2187     if (!found_in_cache) {
2188       internal::TextRunHarfBuzz::ShapeOutput output;
2189       ShapeRunWithFont(cache_key, &output);
2190       run->UpdateFontParamsAndShape(font_params, output);
2191       if (can_use_cache)
2192         cache.get()->Put(cache_key, output);
2193     }
2194 
2195     // Check to see if we still have missing glyphs.
2196     if (run->shape.missing_glyph_count)
2197       runs_with_missing_glyphs.push_back(run);
2198   }
2199   in_out_runs->swap(runs_with_missing_glyphs);
2200 }
2201 
EnsureLayoutRunList()2202 void RenderTextHarfBuzz::EnsureLayoutRunList() {
2203   // Update layout run list if the device scale factor has changed since the
2204   // layout run list was last updated, as changes in device scale factor change
2205   // subpixel positioning, at least on Linux and Chrome OS.
2206   const float device_scale_factor = GetFontRenderParamsDeviceScaleFactor();
2207 
2208   if (update_layout_run_list_ || device_scale_factor_ != device_scale_factor) {
2209     device_scale_factor_ = device_scale_factor;
2210     layout_run_list_.Reset();
2211 
2212     const base::string16& text = GetLayoutText();
2213     if (!text.empty())
2214       ItemizeAndShapeText(text, &layout_run_list_);
2215 
2216     display_run_list_.reset();
2217     update_display_text_ = true;
2218     update_layout_run_list_ = false;
2219   }
2220   if (update_display_text_) {
2221     set_shaped_text(nullptr);
2222     UpdateDisplayText(multiline() ? 0 : layout_run_list_.width());
2223     update_display_text_ = false;
2224     update_display_run_list_ = text_elided();
2225   }
2226 }
2227 
2228 // Returns the current run list, |display_run_list_| if the text is elided, or
2229 // |layout_run_list_| otherwise.
GetRunList()2230 internal::TextRunList* RenderTextHarfBuzz::GetRunList() {
2231   DCHECK(!update_layout_run_list_);
2232   DCHECK(!update_display_run_list_);
2233   return text_elided() ? display_run_list_.get() : &layout_run_list_;
2234 }
2235 
GetRunList() const2236 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const {
2237   return const_cast<RenderTextHarfBuzz*>(this)->GetRunList();
2238 }
2239 
IsValidDisplayRange(Range display_range)2240 bool RenderTextHarfBuzz::IsValidDisplayRange(Range display_range) {
2241   // The |display_text_| is an elided version of |layout_text_|. Removing
2242   // codepoints from the text may break the conversion for codepoint offsets
2243   // between text to display_text offset. For elding behaviors that truncate
2244   // codepoint at the end, the conversion will work just fine. But for eliding
2245   // behavior that truncate at the beginning of middle of the text, the offsets
2246   // are completely wrong and should not be used.
2247   // TODO(http://crbug.com/1085014): Fix eliding for the broken cases.
2248   switch (elide_behavior()) {
2249     case NO_ELIDE:
2250     case FADE_TAIL:
2251       return display_range.IsBoundedBy(Range(0, GetDisplayText().length()));
2252     case TRUNCATE:
2253     case ELIDE_TAIL:
2254       return display_range.IsBoundedBy(Range(0, GetLayoutText().length()));
2255     case ELIDE_HEAD:
2256     case ELIDE_MIDDLE:
2257     case ELIDE_EMAIL:
2258       return !text_elided();
2259   }
2260 }
2261 
GetDecoratedTextForRange(const Range & range,DecoratedText * decorated_text)2262 bool RenderTextHarfBuzz::GetDecoratedTextForRange(
2263     const Range& range,
2264     DecoratedText* decorated_text) {
2265   if (obscured())
2266     return false;
2267 
2268   EnsureLayout();
2269 
2270   decorated_text->attributes.clear();
2271   decorated_text->text = GetTextFromRange(range);
2272 
2273   const internal::TextRunList* run_list = GetRunList();
2274   for (size_t i = 0; i < run_list->size(); i++) {
2275     const internal::TextRunHarfBuzz& run = *run_list->runs()[i];
2276 
2277     const Range intersection = range.Intersect(run.range);
2278     DCHECK(!intersection.is_reversed());
2279 
2280     if (!intersection.is_empty()) {
2281       int style = Font::NORMAL;
2282       if (run.font_params.italic)
2283         style |= Font::ITALIC;
2284       if (run.font_params.underline || run.font_params.heavy_underline)
2285         style |= Font::UNDERLINE;
2286 
2287       // Get range relative to the decorated text.
2288       DecoratedText::RangedAttribute attribute(
2289           Range(intersection.start() - range.GetMin(),
2290                 intersection.end() - range.GetMin()),
2291           run.font_params.font.Derive(0, style, run.font_params.weight));
2292 
2293       attribute.strike = run.font_params.strike;
2294       decorated_text->attributes.push_back(attribute);
2295     }
2296   }
2297   return true;
2298 }
2299 
2300 }  // namespace gfx
2301