ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / ui / gfx / render_text_harfbuzz.cc
blob0580abe3375e09bddae437c08a958ee6a7d16977
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.
5 #include "ui/gfx/render_text_harfbuzz.h"
7 #include <limits>
9 #include "base/i18n/bidi_line_iterator.h"
10 #include "base/i18n/break_iterator.h"
11 #include "base/i18n/char_iterator.h"
12 #include "base/profiler/scoped_tracker.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/trace_event/trace_event.h"
15 #include "third_party/harfbuzz-ng/src/hb.h"
16 #include "third_party/icu/source/common/unicode/ubidi.h"
17 #include "third_party/skia/include/core/SkColor.h"
18 #include "third_party/skia/include/core/SkTypeface.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/font_fallback.h"
21 #include "ui/gfx/font_render_params.h"
22 #include "ui/gfx/harfbuzz_font_skia.h"
23 #include "ui/gfx/range/range_f.h"
24 #include "ui/gfx/utf16_indexing.h"
26 #if defined(OS_WIN)
27 #include "ui/gfx/font_fallback_win.h"
28 #endif
30 using gfx::internal::RoundRangeF;
32 namespace gfx {
34 namespace {
36 // Text length limit. Longer strings are slow and not fully tested.
37 const size_t kMaxTextLength = 10000;
39 // The maximum number of scripts a Unicode character can belong to. This value
40 // is arbitrarily chosen to be a good limit because it is unlikely for a single
41 // character to belong to more scripts.
42 const size_t kMaxScripts = 5;
44 // Returns true if characters of |block_code| may trigger font fallback.
45 // Dingbats and emoticons can be rendered through the color emoji font file,
46 // therefore it needs to be trigerred as fallbacks. See crbug.com/448909
47 bool IsUnusualBlockCode(UBlockCode block_code) {
48 return block_code == UBLOCK_GEOMETRIC_SHAPES ||
49 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS ||
50 block_code == UBLOCK_DINGBATS ||
51 block_code == UBLOCK_EMOTICONS;
54 bool IsBracket(UChar32 character) {
55 static const char kBrackets[] = { '(', ')', '{', '}', '<', '>', };
56 static const char* kBracketsEnd = kBrackets + arraysize(kBrackets);
57 return std::find(kBrackets, kBracketsEnd, character) != kBracketsEnd;
60 // Returns the boundary between a special and a regular character. Special
61 // characters are brackets or characters that satisfy |IsUnusualBlockCode|.
62 size_t FindRunBreakingCharacter(const base::string16& text,
63 size_t run_start,
64 size_t run_break) {
65 const int32 run_length = static_cast<int32>(run_break - run_start);
66 base::i18n::UTF16CharIterator iter(text.c_str() + run_start, run_length);
67 const UChar32 first_char = iter.get();
68 // The newline character should form a single run so that the line breaker
69 // can handle them easily.
70 if (first_char == '\n')
71 return run_start + 1;
73 const UBlockCode first_block = ublock_getCode(first_char);
74 const bool first_block_unusual = IsUnusualBlockCode(first_block);
75 const bool first_bracket = IsBracket(first_char);
77 while (iter.Advance() && iter.array_pos() < run_length) {
78 const UChar32 current_char = iter.get();
79 const UBlockCode current_block = ublock_getCode(current_char);
80 const bool block_break = current_block != first_block &&
81 (first_block_unusual || IsUnusualBlockCode(current_block));
82 if (block_break || current_char == '\n' ||
83 first_bracket != IsBracket(current_char)) {
84 return run_start + iter.array_pos();
87 return run_break;
90 // If the given scripts match, returns the one that isn't USCRIPT_COMMON or
91 // USCRIPT_INHERITED, i.e. the more specific one. Otherwise returns
92 // USCRIPT_INVALID_CODE.
93 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) {
94 if (first == second ||
95 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED)) {
96 return first;
98 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED)
99 return second;
100 return USCRIPT_INVALID_CODE;
103 // Writes the script and the script extensions of the character with the
104 // Unicode |codepoint|. Returns the number of written scripts.
105 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) {
106 UErrorCode icu_error = U_ZERO_ERROR;
107 // ICU documentation incorrectly states that the result of
108 // |uscript_getScriptExtensions| will contain the regular script property.
109 // Write the character's script property to the first element.
110 scripts[0] = uscript_getScript(codepoint, &icu_error);
111 if (U_FAILURE(icu_error))
112 return 0;
113 // Fill the rest of |scripts| with the extensions.
114 int count = uscript_getScriptExtensions(codepoint, scripts + 1,
115 kMaxScripts - 1, &icu_error);
116 if (U_FAILURE(icu_error))
117 count = 0;
118 return count + 1;
121 // Intersects the script extensions set of |codepoint| with |result| and writes
122 // to |result|, reading and updating |result_size|.
123 void ScriptSetIntersect(UChar32 codepoint,
124 UScriptCode* result,
125 size_t* result_size) {
126 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE };
127 int count = GetScriptExtensions(codepoint, scripts);
129 size_t out_size = 0;
131 for (size_t i = 0; i < *result_size; ++i) {
132 for (int j = 0; j < count; ++j) {
133 UScriptCode intersection = ScriptIntersect(result[i], scripts[j]);
134 if (intersection != USCRIPT_INVALID_CODE) {
135 result[out_size++] = intersection;
136 break;
141 *result_size = out_size;
144 // Find the longest sequence of characters from 0 and up to |length| that
145 // have at least one common UScriptCode value. Writes the common script value to
146 // |script| and returns the length of the sequence. Takes the characters' script
147 // extensions into account. http://www.unicode.org/reports/tr24/#ScriptX
149 // Consider 3 characters with the script values {Kana}, {Hira, Kana}, {Kana}.
150 // Without script extensions only the first script in each set would be taken
151 // into account, resulting in 3 runs where 1 would be enough.
152 // TODO(ckocagil): Write a unit test for the case above.
153 int ScriptInterval(const base::string16& text,
154 size_t start,
155 size_t length,
156 UScriptCode* script) {
157 DCHECK_GT(length, 0U);
159 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE };
161 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length);
162 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts);
163 *script = scripts[0];
165 while (char_iterator.Advance()) {
166 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size);
167 if (scripts_size == 0U)
168 return char_iterator.array_pos();
169 *script = scripts[0];
172 return length;
175 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without
176 // hb-icu. See http://crbug.com/356929
177 inline hb_script_t ICUScriptToHBScript(UScriptCode script) {
178 if (script == USCRIPT_INVALID_CODE)
179 return HB_SCRIPT_INVALID;
180 return hb_script_from_string(uscript_getShortName(script), -1);
183 // Helper template function for |TextRunHarfBuzz::GetClusterAt()|. |Iterator|
184 // can be a forward or reverse iterator type depending on the text direction.
185 template <class Iterator>
186 void GetClusterAtImpl(size_t pos,
187 Range range,
188 Iterator elements_begin,
189 Iterator elements_end,
190 bool reversed,
191 Range* chars,
192 Range* glyphs) {
193 Iterator element = std::upper_bound(elements_begin, elements_end, pos);
194 chars->set_end(element == elements_end ? range.end() : *element);
195 glyphs->set_end(reversed ? elements_end - element : element - elements_begin);
197 DCHECK(element != elements_begin);
198 while (--element != elements_begin && *element == *(element - 1));
199 chars->set_start(*element);
200 glyphs->set_start(
201 reversed ? elements_end - element : element - elements_begin);
202 if (reversed)
203 *glyphs = Range(glyphs->end(), glyphs->start());
205 DCHECK(!chars->is_reversed());
206 DCHECK(!chars->is_empty());
207 DCHECK(!glyphs->is_reversed());
208 DCHECK(!glyphs->is_empty());
211 // Internal class to generate Line structures. If |multiline| is true, the text
212 // is broken into lines at |words| boundaries such that each line is no longer
213 // than |max_width|. If |multiline| is false, only outputs a single Line from
214 // the given runs. |min_baseline| and |min_height| are the minimum baseline and
215 // height for each line.
216 // TODO(ckocagil): Expose the interface of this class in the header and test
217 // this class directly.
218 class HarfBuzzLineBreaker {
219 public:
220 HarfBuzzLineBreaker(size_t max_width,
221 int min_baseline,
222 float min_height,
223 bool multiline,
224 const base::string16& text,
225 const BreakList<size_t>* words,
226 const internal::TextRunList& run_list)
227 : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)),
228 min_baseline_(min_baseline),
229 min_height_(min_height),
230 multiline_(multiline),
231 text_(text),
232 words_(words),
233 run_list_(run_list),
234 text_x_(0),
235 line_x_(0),
236 max_descent_(0),
237 max_ascent_(0) {
238 DCHECK_EQ(multiline_, (words_ != nullptr));
239 AdvanceLine();
242 // Breaks the run at given |run_index| into Line structs.
243 void AddRun(int run_index) {
244 const internal::TextRunHarfBuzz* run = run_list_.runs()[run_index];
245 base::char16 first_char = text_[run->range.start()];
246 if (multiline_ && first_char == '\n') {
247 AdvanceLine();
248 } else if (multiline_ && (line_x_ + SkFloatToScalar(run->width)) >
249 max_width_) {
250 BreakRun(run_index);
251 } else {
252 AddSegment(run_index, run->range, run->width);
256 // Finishes line breaking and outputs the results. Can be called at most once.
257 void Finalize(std::vector<internal::Line>* lines, SizeF* size) {
258 DCHECK(!lines_.empty());
259 // Add an empty line to finish the line size calculation and remove it.
260 AdvanceLine();
261 lines_.pop_back();
262 *size = total_size_;
263 lines->swap(lines_);
266 private:
267 // A (line index, segment index) pair that specifies a segment in |lines_|.
268 typedef std::pair<size_t, size_t> SegmentHandle;
270 internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) {
271 return &lines_[handle.first].segments[handle.second];
274 // Breaks a run into segments that fit in the last line in |lines_| and adds
275 // them. Adds a new Line to the back of |lines_| whenever a new segment can't
276 // be added without the Line's width exceeding |max_width_|.
277 void BreakRun(int run_index) {
278 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]);
279 SkScalar width = 0;
280 size_t next_char = run.range.start();
282 // Break the run until it fits the current line.
283 while (next_char < run.range.end()) {
284 const size_t current_char = next_char;
285 const bool skip_line =
286 BreakRunAtWidth(run, current_char, &width, &next_char);
287 AddSegment(run_index, Range(current_char, next_char),
288 SkScalarToFloat(width));
289 if (skip_line)
290 AdvanceLine();
294 // Starting from |start_char|, finds a suitable line break position at or
295 // before available width using word break. If the current position is at the
296 // beginning of a line, this function will not roll back to |start_char| and
297 // |*next_char| will be greater than |start_char| (to avoid constructing empty
298 // lines).
299 // Returns whether to skip the line before |*next_char|.
300 // TODO(ckocagil): Check clusters to avoid breaking ligatures and diacritics.
301 // TODO(ckocagil): We might have to reshape after breaking at ligatures.
302 // See whether resolving the TODO above resolves this too.
303 // TODO(ckocagil): Do not reserve width for whitespace at the end of lines.
304 bool BreakRunAtWidth(const internal::TextRunHarfBuzz& run,
305 size_t start_char,
306 SkScalar* width,
307 size_t* next_char) {
308 DCHECK(words_);
309 DCHECK(run.range.Contains(Range(start_char, start_char + 1)));
310 SkScalar available_width = max_width_ - line_x_;
311 BreakList<size_t>::const_iterator word = words_->GetBreak(start_char);
312 BreakList<size_t>::const_iterator next_word = word + 1;
313 // Width from |std::max(word->first, start_char)| to the current character.
314 SkScalar word_width = 0;
315 *width = 0;
317 for (size_t i = start_char; i < run.range.end(); ++i) {
318 // |word| holds the word boundary at or before |i|, and |next_word| holds
319 // the word boundary right after |i|. Advance both |word| and |next_word|
320 // when |i| reaches |next_word|.
321 if (next_word != words_->breaks().end() && i >= next_word->first) {
322 word = next_word++;
323 word_width = 0;
326 Range glyph_range = run.CharRangeToGlyphRange(Range(i, i + 1));
327 SkScalar char_width = ((glyph_range.end() >= run.glyph_count)
328 ? SkFloatToScalar(run.width)
329 : run.positions[glyph_range.end()].x()) -
330 run.positions[glyph_range.start()].x();
332 *width += char_width;
333 word_width += char_width;
335 if (*width > available_width) {
336 if (line_x_ != 0 || word_width < *width) {
337 // Roll back one word.
338 *width -= word_width;
339 *next_char = std::max(word->first, start_char);
340 } else if (char_width < *width) {
341 // Roll back one character.
342 *width -= char_width;
343 *next_char = i;
344 } else {
345 // Continue from the next character.
346 *next_char = i + 1;
348 return true;
352 *next_char = run.range.end();
353 return false;
356 // RTL runs are broken in logical order but displayed in visual order. To find
357 // the text-space coordinate (where it would fall in a single-line text)
358 // |x_range| of RTL segments, segment widths are applied in reverse order.
359 // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}.
360 void UpdateRTLSegmentRanges() {
361 if (rtl_segments_.empty())
362 return;
363 float x = SegmentFromHandle(rtl_segments_[0])->x_range.start();
364 for (size_t i = rtl_segments_.size(); i > 0; --i) {
365 internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]);
366 const float segment_width = segment->width;
367 segment->x_range = Range(x, x + segment_width);
368 x += segment_width;
370 rtl_segments_.clear();
373 // Finishes the size calculations of the last Line in |lines_|. Adds a new
374 // Line to the back of |lines_|.
375 void AdvanceLine() {
376 if (!lines_.empty()) {
377 internal::Line* line = &lines_.back();
378 std::sort(line->segments.begin(), line->segments.end(),
379 [this](const internal::LineSegment& s1,
380 const internal::LineSegment& s2) -> bool {
381 return run_list_.logical_to_visual(s1.run) <
382 run_list_.logical_to_visual(s2.run);
384 line->size.set_height(std::max(min_height_, max_descent_ + max_ascent_));
385 line->baseline =
386 std::max(min_baseline_, SkScalarRoundToInt(max_ascent_));
387 line->preceding_heights = std::ceil(total_size_.height());
388 total_size_.set_height(total_size_.height() + line->size.height());
389 total_size_.set_width(std::max(total_size_.width(), line->size.width()));
391 max_descent_ = 0;
392 max_ascent_ = 0;
393 line_x_ = 0;
394 lines_.push_back(internal::Line());
397 // Adds a new segment with the given properties to |lines_.back()|.
398 void AddSegment(int run_index, Range char_range, float width) {
399 if (char_range.is_empty()) {
400 DCHECK_EQ(0, width);
401 return;
403 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]);
405 internal::LineSegment segment;
406 segment.run = run_index;
407 segment.char_range = char_range;
408 segment.x_range = Range(
409 SkScalarCeilToInt(text_x_),
410 SkScalarCeilToInt(text_x_ + SkFloatToScalar(width)));
411 segment.width = width;
413 internal::Line* line = &lines_.back();
414 line->segments.push_back(segment);
416 SkPaint paint;
417 paint.setTypeface(run.skia_face.get());
418 paint.setTextSize(SkIntToScalar(run.font_size));
419 paint.setAntiAlias(run.render_params.antialiasing);
420 SkPaint::FontMetrics metrics;
421 paint.getFontMetrics(&metrics);
423 line->size.set_width(line->size.width() + width);
424 max_descent_ = std::max(max_descent_, metrics.fDescent);
425 // fAscent is always negative.
426 max_ascent_ = std::max(max_ascent_, -metrics.fAscent);
428 if (run.is_rtl) {
429 rtl_segments_.push_back(
430 SegmentHandle(lines_.size() - 1, line->segments.size() - 1));
431 // If this is the last segment of an RTL run, reprocess the text-space x
432 // ranges of all segments from the run.
433 if (char_range.end() == run.range.end())
434 UpdateRTLSegmentRanges();
436 text_x_ += SkFloatToScalar(width);
437 line_x_ += SkFloatToScalar(width);
440 const SkScalar max_width_;
441 const int min_baseline_;
442 const float min_height_;
443 const bool multiline_;
444 const base::string16& text_;
445 const BreakList<size_t>* const words_;
446 const internal::TextRunList& run_list_;
448 // Stores the resulting lines.
449 std::vector<internal::Line> lines_;
451 // Text space and line space x coordinates of the next segment to be added.
452 SkScalar text_x_;
453 SkScalar line_x_;
455 float max_descent_;
456 float max_ascent_;
458 // Size of the multiline text, not including the currently processed line.
459 SizeF total_size_;
461 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|.
462 std::vector<SegmentHandle> rtl_segments_;
464 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker);
467 } // namespace
469 namespace internal {
471 Range RoundRangeF(const RangeF& range_f) {
472 return Range(std::floor(range_f.start() + 0.5f),
473 std::floor(range_f.end() + 0.5f));
476 TextRunHarfBuzz::TextRunHarfBuzz()
477 : width(0.0f),
478 preceding_run_widths(0.0f),
479 is_rtl(false),
480 level(0),
481 script(USCRIPT_INVALID_CODE),
482 glyph_count(static_cast<size_t>(-1)),
483 font_size(0),
484 font_style(0),
485 strike(false),
486 diagonal_strike(false),
487 underline(false) {}
489 TextRunHarfBuzz::~TextRunHarfBuzz() {}
491 void TextRunHarfBuzz::GetClusterAt(size_t pos,
492 Range* chars,
493 Range* glyphs) const {
494 DCHECK(range.Contains(Range(pos, pos + 1)));
495 DCHECK(chars);
496 DCHECK(glyphs);
498 if (glyph_count == 0) {
499 *chars = range;
500 *glyphs = Range();
501 return;
504 if (is_rtl) {
505 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(),
506 true, chars, glyphs);
507 return;
510 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(),
511 false, chars, glyphs);
514 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const {
515 DCHECK(range.Contains(char_range));
516 DCHECK(!char_range.is_reversed());
517 DCHECK(!char_range.is_empty());
519 Range start_glyphs;
520 Range end_glyphs;
521 Range temp_range;
522 GetClusterAt(char_range.start(), &temp_range, &start_glyphs);
523 GetClusterAt(char_range.end() - 1, &temp_range, &end_glyphs);
525 return is_rtl ? Range(end_glyphs.start(), start_glyphs.end()) :
526 Range(start_glyphs.start(), end_glyphs.end());
529 size_t TextRunHarfBuzz::CountMissingGlyphs() const {
530 static const int kMissingGlyphId = 0;
531 size_t missing = 0;
532 for (size_t i = 0; i < glyph_count; ++i)
533 missing += (glyphs[i] == kMissingGlyphId) ? 1 : 0;
534 return missing;
537 RangeF TextRunHarfBuzz::GetGraphemeBounds(
538 base::i18n::BreakIterator* grapheme_iterator,
539 size_t text_index) {
540 DCHECK_LT(text_index, range.end());
541 if (glyph_count == 0)
542 return RangeF(preceding_run_widths, preceding_run_widths + width);
544 Range chars;
545 Range glyphs;
546 GetClusterAt(text_index, &chars, &glyphs);
547 const float cluster_begin_x = positions[glyphs.start()].x();
548 const float cluster_end_x = glyphs.end() < glyph_count ?
549 positions[glyphs.end()].x() : SkFloatToScalar(width);
551 // A cluster consists of a number of code points and corresponds to a number
552 // of glyphs that should be drawn together. A cluster can contain multiple
553 // graphemes. In order to place the cursor at a grapheme boundary inside the
554 // cluster, we simply divide the cluster width by the number of graphemes.
555 if (chars.length() > 1 && grapheme_iterator) {
556 int before = 0;
557 int total = 0;
558 for (size_t i = chars.start(); i < chars.end(); ++i) {
559 if (grapheme_iterator->IsGraphemeBoundary(i)) {
560 if (i < text_index)
561 ++before;
562 ++total;
565 DCHECK_GT(total, 0);
566 if (total > 1) {
567 if (is_rtl)
568 before = total - before - 1;
569 DCHECK_GE(before, 0);
570 DCHECK_LT(before, total);
571 const int cluster_width = cluster_end_x - cluster_begin_x;
572 const int grapheme_begin_x = cluster_begin_x + static_cast<int>(0.5f +
573 cluster_width * before / static_cast<float>(total));
574 const int grapheme_end_x = cluster_begin_x + static_cast<int>(0.5f +
575 cluster_width * (before + 1) / static_cast<float>(total));
576 return RangeF(preceding_run_widths + grapheme_begin_x,
577 preceding_run_widths + grapheme_end_x);
581 return RangeF(preceding_run_widths + cluster_begin_x,
582 preceding_run_widths + cluster_end_x);
585 TextRunList::TextRunList() : width_(0.0f) {}
587 TextRunList::~TextRunList() {}
589 void TextRunList::Reset() {
590 runs_.clear();
591 width_ = 0.0f;
594 void TextRunList::InitIndexMap() {
595 if (runs_.size() == 1) {
596 visual_to_logical_ = logical_to_visual_ = std::vector<int32_t>(1, 0);
597 return;
599 const size_t num_runs = runs_.size();
600 std::vector<UBiDiLevel> levels(num_runs);
601 for (size_t i = 0; i < num_runs; ++i)
602 levels[i] = runs_[i]->level;
603 visual_to_logical_.resize(num_runs);
604 ubidi_reorderVisual(&levels[0], num_runs, &visual_to_logical_[0]);
605 logical_to_visual_.resize(num_runs);
606 ubidi_reorderLogical(&levels[0], num_runs, &logical_to_visual_[0]);
609 void TextRunList::ComputePrecedingRunWidths() {
610 // Precalculate run width information.
611 width_ = 0.0f;
612 for (size_t i = 0; i < runs_.size(); ++i) {
613 TextRunHarfBuzz* run = runs_[visual_to_logical_[i]];
614 run->preceding_run_widths = width_;
615 width_ += run->width;
619 } // namespace internal
621 RenderTextHarfBuzz::RenderTextHarfBuzz()
622 : RenderText(),
623 update_layout_run_list_(false),
624 update_display_run_list_(false),
625 update_grapheme_iterator_(false),
626 update_display_text_(false),
627 glyph_width_for_test_(0u) {
628 set_truncate_length(kMaxTextLength);
631 RenderTextHarfBuzz::~RenderTextHarfBuzz() {}
633 scoped_ptr<RenderText> RenderTextHarfBuzz::CreateInstanceOfSameType() const {
634 return make_scoped_ptr(new RenderTextHarfBuzz);
637 const base::string16& RenderTextHarfBuzz::GetDisplayText() {
638 // TODO(oshima): Consider supporting eliding multi-line text.
639 // This requires max_line support first.
640 if (multiline() ||
641 elide_behavior() == NO_ELIDE ||
642 elide_behavior() == FADE_TAIL) {
643 // Call UpdateDisplayText to clear |display_text_| and |text_elided_|
644 // on the RenderText class.
645 UpdateDisplayText(0);
646 update_display_text_ = false;
647 display_run_list_.reset();
648 return layout_text();
651 EnsureLayoutRunList();
652 DCHECK(!update_display_text_);
653 return text_elided() ? display_text() : layout_text();
656 Size RenderTextHarfBuzz::GetStringSize() {
657 const SizeF size_f = GetStringSizeF();
658 return Size(std::ceil(size_f.width()), size_f.height());
661 SizeF RenderTextHarfBuzz::GetStringSizeF() {
662 EnsureLayout();
663 return total_size_;
666 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) {
667 EnsureLayout();
669 int x = ToTextPoint(point).x();
670 float offset = 0;
671 size_t run_index = GetRunContainingXCoord(x, &offset);
673 internal::TextRunList* run_list = GetRunList();
674 if (run_index >= run_list->size())
675 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT);
676 const internal::TextRunHarfBuzz& run = *run_list->runs()[run_index];
677 for (size_t i = 0; i < run.glyph_count; ++i) {
678 const SkScalar end =
679 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x();
680 const SkScalar middle = (end + run.positions[i].x()) / 2;
682 if (offset < middle) {
683 return SelectionModel(DisplayIndexToTextIndex(
684 run.glyph_to_char[i] + (run.is_rtl ? 1 : 0)),
685 (run.is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD));
687 if (offset < end) {
688 return SelectionModel(DisplayIndexToTextIndex(
689 run.glyph_to_char[i] + (run.is_rtl ? 0 : 1)),
690 (run.is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD));
693 return EdgeSelectionModel(CURSOR_RIGHT);
696 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() {
697 EnsureLayout();
699 internal::TextRunList* run_list = GetRunList();
700 std::vector<RenderText::FontSpan> spans;
701 for (auto* run : run_list->runs()) {
702 SkString family_name;
703 run->skia_face->getFamilyName(&family_name);
704 Font font(family_name.c_str(), run->font_size);
705 spans.push_back(RenderText::FontSpan(
706 font,
707 Range(DisplayIndexToTextIndex(run->range.start()),
708 DisplayIndexToTextIndex(run->range.end()))));
711 return spans;
714 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) {
715 EnsureLayout();
716 const size_t run_index =
717 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD));
718 internal::TextRunList* run_list = GetRunList();
719 // Return edge bounds if the index is invalid or beyond the layout text size.
720 if (run_index >= run_list->size())
721 return Range(GetStringSize().width());
722 const size_t layout_index = TextIndexToDisplayIndex(index);
723 internal::TextRunHarfBuzz* run = run_list->runs()[run_index];
724 RangeF bounds =
725 run->GetGraphemeBounds(GetGraphemeIterator(), layout_index);
726 // If cursor is enabled, extend the last glyph up to the rightmost cursor
727 // position since clients expect them to be contiguous.
728 if (cursor_enabled() && run_index == run_list->size() - 1 &&
729 index == (run->is_rtl ? run->range.start() : run->range.end() - 1))
730 bounds.set_end(std::ceil(bounds.end()));
731 return RoundRangeF(run->is_rtl ?
732 RangeF(bounds.end(), bounds.start()) : bounds);
735 int RenderTextHarfBuzz::GetDisplayTextBaseline() {
736 EnsureLayout();
737 return lines()[0].baseline;
740 SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel(
741 const SelectionModel& selection,
742 VisualCursorDirection direction) {
743 DCHECK(!update_display_run_list_);
745 internal::TextRunList* run_list = GetRunList();
746 internal::TextRunHarfBuzz* run;
748 size_t run_index = GetRunContainingCaret(selection);
749 if (run_index >= run_list->size()) {
750 // The cursor is not in any run: we're at the visual and logical edge.
751 SelectionModel edge = EdgeSelectionModel(direction);
752 if (edge.caret_pos() == selection.caret_pos())
753 return edge;
754 int visual_index = (direction == CURSOR_RIGHT) ? 0 : run_list->size() - 1;
755 run = run_list->runs()[run_list->visual_to_logical(visual_index)];
756 } else {
757 // If the cursor is moving within the current run, just move it by one
758 // grapheme in the appropriate direction.
759 run = run_list->runs()[run_index];
760 size_t caret = selection.caret_pos();
761 bool forward_motion = run->is_rtl == (direction == CURSOR_LEFT);
762 if (forward_motion) {
763 if (caret < DisplayIndexToTextIndex(run->range.end())) {
764 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
765 return SelectionModel(caret, CURSOR_BACKWARD);
767 } else {
768 if (caret > DisplayIndexToTextIndex(run->range.start())) {
769 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
770 return SelectionModel(caret, CURSOR_FORWARD);
773 // The cursor is at the edge of a run; move to the visually adjacent run.
774 int visual_index = run_list->logical_to_visual(run_index);
775 visual_index += (direction == CURSOR_LEFT) ? -1 : 1;
776 if (visual_index < 0 || visual_index >= static_cast<int>(run_list->size()))
777 return EdgeSelectionModel(direction);
778 run = run_list->runs()[run_list->visual_to_logical(visual_index)];
780 bool forward_motion = run->is_rtl == (direction == CURSOR_LEFT);
781 return forward_motion ? FirstSelectionModelInsideRun(run) :
782 LastSelectionModelInsideRun(run);
785 SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel(
786 const SelectionModel& selection,
787 VisualCursorDirection direction) {
788 if (obscured())
789 return EdgeSelectionModel(direction);
791 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
792 bool success = iter.Init();
793 DCHECK(success);
794 if (!success)
795 return selection;
797 // Match OS specific word break behavior.
798 #if defined(OS_WIN)
799 size_t pos;
800 if (direction == CURSOR_RIGHT) {
801 pos = std::min(selection.caret_pos() + 1, text().length());
802 while (iter.Advance()) {
803 pos = iter.pos();
804 if (iter.IsWord() && pos > selection.caret_pos())
805 break;
807 } else { // direction == CURSOR_LEFT
808 // Notes: We always iterate words from the beginning.
809 // This is probably fast enough for our usage, but we may
810 // want to modify WordIterator so that it can start from the
811 // middle of string and advance backwards.
812 pos = std::max<int>(selection.caret_pos() - 1, 0);
813 while (iter.Advance()) {
814 if (iter.IsWord()) {
815 size_t begin = iter.pos() - iter.GetString().length();
816 if (begin == selection.caret_pos()) {
817 // The cursor is at the beginning of a word.
818 // Move to previous word.
819 break;
820 } else if (iter.pos() >= selection.caret_pos()) {
821 // The cursor is in the middle or at the end of a word.
822 // Move to the top of current word.
823 pos = begin;
824 break;
826 pos = iter.pos() - iter.GetString().length();
830 return SelectionModel(pos, CURSOR_FORWARD);
831 #else
832 internal::TextRunList* run_list = GetRunList();
833 SelectionModel cur(selection);
834 for (;;) {
835 cur = AdjacentCharSelectionModel(cur, direction);
836 size_t run = GetRunContainingCaret(cur);
837 if (run == run_list->size())
838 break;
839 const bool is_forward =
840 run_list->runs()[run]->is_rtl == (direction == CURSOR_LEFT);
841 size_t cursor = cur.caret_pos();
842 if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor))
843 break;
845 return cur;
846 #endif
849 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
850 DCHECK(!update_display_run_list_);
851 DCHECK(Range(0, text().length()).Contains(range));
852 Range layout_range(TextIndexToDisplayIndex(range.start()),
853 TextIndexToDisplayIndex(range.end()));
854 DCHECK(Range(0, GetDisplayText().length()).Contains(layout_range));
856 std::vector<Rect> rects;
857 if (layout_range.is_empty())
858 return rects;
859 std::vector<Range> bounds;
861 internal::TextRunList* run_list = GetRunList();
863 // Add a Range for each run/selection intersection.
864 for (size_t i = 0; i < run_list->size(); ++i) {
865 internal::TextRunHarfBuzz* run =
866 run_list->runs()[run_list->visual_to_logical(i)];
867 Range intersection = run->range.Intersect(layout_range);
868 if (!intersection.IsValid())
869 continue;
870 DCHECK(!intersection.is_reversed());
871 const Range leftmost_character_x = RoundRangeF(run->GetGraphemeBounds(
872 GetGraphemeIterator(),
873 run->is_rtl ? intersection.end() - 1 : intersection.start()));
874 const Range rightmost_character_x = RoundRangeF(run->GetGraphemeBounds(
875 GetGraphemeIterator(),
876 run->is_rtl ? intersection.start() : intersection.end() - 1));
877 Range range_x(leftmost_character_x.start(), rightmost_character_x.end());
878 DCHECK(!range_x.is_reversed());
879 if (range_x.is_empty())
880 continue;
882 // Union this with the last range if they're adjacent.
883 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin());
884 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) {
885 range_x = Range(bounds.back().GetMin(), range_x.GetMax());
886 bounds.pop_back();
888 bounds.push_back(range_x);
890 for (Range& bound : bounds) {
891 std::vector<Rect> current_rects = TextBoundsToViewBounds(bound);
892 rects.insert(rects.end(), current_rects.begin(), current_rects.end());
894 return rects;
897 size_t RenderTextHarfBuzz::TextIndexToDisplayIndex(size_t index) {
898 return TextIndexToGivenTextIndex(GetDisplayText(), index);
901 size_t RenderTextHarfBuzz::DisplayIndexToTextIndex(size_t index) {
902 if (!obscured())
903 return index;
904 const size_t text_index = UTF16OffsetToIndex(text(), 0, index);
905 DCHECK_LE(text_index, text().length());
906 return text_index;
909 bool RenderTextHarfBuzz::IsValidCursorIndex(size_t index) {
910 if (index == 0 || index == text().length())
911 return true;
912 if (!IsValidLogicalIndex(index))
913 return false;
914 base::i18n::BreakIterator* grapheme_iterator = GetGraphemeIterator();
915 return !grapheme_iterator || grapheme_iterator->IsGraphemeBoundary(index);
918 void RenderTextHarfBuzz::OnLayoutTextAttributeChanged(bool text_changed) {
919 update_layout_run_list_ = true;
920 OnDisplayTextAttributeChanged();
923 void RenderTextHarfBuzz::OnDisplayTextAttributeChanged() {
924 update_display_text_ = true;
925 update_grapheme_iterator_ = true;
928 void RenderTextHarfBuzz::EnsureLayout() {
929 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
930 tracked_objects::ScopedTracker tracking_profile(
931 FROM_HERE_WITH_EXPLICIT_FUNCTION(
932 "431326 RenderTextHarfBuzz::EnsureLayout"));
934 EnsureLayoutRunList();
936 if (update_display_run_list_) {
937 DCHECK(text_elided());
938 const base::string16& display_text = GetDisplayText();
939 display_run_list_.reset(new internal::TextRunList);
941 if (!display_text.empty()) {
942 TRACE_EVENT0("ui", "RenderTextHarfBuzz:EnsureLayout1");
944 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
945 // fixed.
946 tracked_objects::ScopedTracker tracking_profile1(
947 FROM_HERE_WITH_EXPLICIT_FUNCTION(
948 "431326 RenderTextHarfBuzz::EnsureLayout1"));
949 ItemizeTextToRuns(display_text, display_run_list_.get());
951 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
952 // fixed.
953 tracked_objects::ScopedTracker tracking_profile2(
954 FROM_HERE_WITH_EXPLICIT_FUNCTION(
955 "431326 RenderTextHarfBuzz::EnsureLayout12"));
956 ShapeRunList(display_text, display_run_list_.get());
958 update_display_run_list_ = false;
960 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
961 // fixed.
962 tracked_objects::ScopedTracker tracking_profile14(
963 FROM_HERE_WITH_EXPLICIT_FUNCTION(
964 "431326 RenderTextHarfBuzz::EnsureLayout14"));
965 std::vector<internal::Line> empty_lines;
966 set_lines(&empty_lines);
969 if (lines().empty()) {
970 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
971 tracked_objects::ScopedTracker tracking_profile2(
972 FROM_HERE_WITH_EXPLICIT_FUNCTION(
973 "431326 RenderTextHarfBuzz::EnsureLayout2"));
975 internal::TextRunList* run_list = GetRunList();
976 HarfBuzzLineBreaker line_breaker(
977 display_rect().width(), font_list().GetBaseline(),
978 std::max(font_list().GetHeight(), min_line_height()), multiline(),
979 GetDisplayText(), multiline() ? &GetLineBreaks() : nullptr, *run_list);
981 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
982 tracked_objects::ScopedTracker tracking_profile3(
983 FROM_HERE_WITH_EXPLICIT_FUNCTION(
984 "431326 RenderTextHarfBuzz::EnsureLayout3"));
986 for (size_t i = 0; i < run_list->size(); ++i)
987 line_breaker.AddRun(i);
988 std::vector<internal::Line> lines;
989 line_breaker.Finalize(&lines, &total_size_);
990 set_lines(&lines);
994 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) {
995 internal::SkiaTextRenderer renderer(canvas);
996 DrawVisualTextInternal(&renderer);
999 void RenderTextHarfBuzz::DrawVisualTextInternal(
1000 internal::SkiaTextRenderer* renderer) {
1001 DCHECK(!update_layout_run_list_);
1002 DCHECK(!update_display_run_list_);
1003 DCHECK(!update_display_text_);
1004 if (lines().empty())
1005 return;
1007 ApplyFadeEffects(renderer);
1008 ApplyTextShadows(renderer);
1009 ApplyCompositionAndSelectionStyles();
1011 internal::TextRunList* run_list = GetRunList();
1012 for (size_t i = 0; i < lines().size(); ++i) {
1013 const internal::Line& line = lines()[i];
1014 const Vector2d origin = GetLineOffset(i) + Vector2d(0, line.baseline);
1015 SkScalar preceding_segment_widths = 0;
1016 for (const internal::LineSegment& segment : line.segments) {
1017 const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
1018 renderer->SetTypeface(run.skia_face.get());
1019 renderer->SetTextSize(SkIntToScalar(run.font_size));
1020 renderer->SetFontRenderParams(run.render_params,
1021 subpixel_rendering_suppressed());
1022 Range glyphs_range = run.CharRangeToGlyphRange(segment.char_range);
1023 scoped_ptr<SkPoint[]> positions(new SkPoint[glyphs_range.length()]);
1024 SkScalar offset_x = preceding_segment_widths -
1025 ((glyphs_range.GetMin() != 0)
1026 ? run.positions[glyphs_range.GetMin()].x()
1027 : 0);
1028 for (size_t j = 0; j < glyphs_range.length(); ++j) {
1029 positions[j] = run.positions[(glyphs_range.is_reversed()) ?
1030 (glyphs_range.start() - j) :
1031 (glyphs_range.start() + j)];
1032 positions[j].offset(SkIntToScalar(origin.x()) + offset_x,
1033 SkIntToScalar(origin.y()));
1035 for (BreakList<SkColor>::const_iterator it =
1036 colors().GetBreak(segment.char_range.start());
1037 it != colors().breaks().end() &&
1038 it->first < segment.char_range.end();
1039 ++it) {
1040 const Range intersection =
1041 colors().GetRange(it).Intersect(segment.char_range);
1042 const Range colored_glyphs = run.CharRangeToGlyphRange(intersection);
1043 // The range may be empty if a portion of a multi-character grapheme is
1044 // selected, yielding two colors for a single glyph. For now, this just
1045 // paints the glyph with a single style, but it should paint it twice,
1046 // clipped according to selection bounds. See http://crbug.com/366786
1047 if (colored_glyphs.is_empty())
1048 continue;
1050 renderer->SetForegroundColor(it->second);
1051 renderer->DrawPosText(
1052 &positions[colored_glyphs.start() - glyphs_range.start()],
1053 &run.glyphs[colored_glyphs.start()], colored_glyphs.length());
1054 int start_x = SkScalarRoundToInt(
1055 positions[colored_glyphs.start() - glyphs_range.start()].x());
1056 int end_x = SkScalarRoundToInt(
1057 (colored_glyphs.end() == glyphs_range.end())
1058 ? (SkFloatToScalar(segment.width) + preceding_segment_widths +
1059 SkIntToScalar(origin.x()))
1060 : positions[colored_glyphs.end() - glyphs_range.start()].x());
1061 renderer->DrawDecorations(start_x, origin.y(), end_x - start_x,
1062 run.underline, run.strike,
1063 run.diagonal_strike);
1065 preceding_segment_widths += SkFloatToScalar(segment.width);
1069 renderer->EndDiagonalStrike();
1071 UndoCompositionAndSelectionStyles();
1074 size_t RenderTextHarfBuzz::GetRunContainingCaret(
1075 const SelectionModel& caret) {
1076 DCHECK(!update_display_run_list_);
1077 size_t layout_position = TextIndexToDisplayIndex(caret.caret_pos());
1078 LogicalCursorDirection affinity = caret.caret_affinity();
1079 internal::TextRunList* run_list = GetRunList();
1080 for (size_t i = 0; i < run_list->size(); ++i) {
1081 internal::TextRunHarfBuzz* run = run_list->runs()[i];
1082 if (RangeContainsCaret(run->range, layout_position, affinity))
1083 return i;
1085 return run_list->size();
1088 size_t RenderTextHarfBuzz::GetRunContainingXCoord(float x,
1089 float* offset) const {
1090 DCHECK(!update_display_run_list_);
1091 const internal::TextRunList* run_list = GetRunList();
1092 if (x < 0)
1093 return run_list->size();
1094 // Find the text run containing the argument point (assumed already offset).
1095 float current_x = 0;
1096 for (size_t i = 0; i < run_list->size(); ++i) {
1097 size_t run = run_list->visual_to_logical(i);
1098 current_x += run_list->runs()[run]->width;
1099 if (x < current_x) {
1100 *offset = x - (current_x - run_list->runs()[run]->width);
1101 return run;
1104 return run_list->size();
1107 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun(
1108 const internal::TextRunHarfBuzz* run) {
1109 size_t position = DisplayIndexToTextIndex(run->range.start());
1110 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD);
1111 return SelectionModel(position, CURSOR_BACKWARD);
1114 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun(
1115 const internal::TextRunHarfBuzz* run) {
1116 size_t position = DisplayIndexToTextIndex(run->range.end());
1117 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD);
1118 return SelectionModel(position, CURSOR_FORWARD);
1121 void RenderTextHarfBuzz::ItemizeTextToRuns(
1122 const base::string16& text,
1123 internal::TextRunList* run_list_out) {
1124 const bool is_text_rtl = GetTextDirection(text) == base::i18n::RIGHT_TO_LEFT;
1125 DCHECK_NE(0U, text.length());
1127 // If ICU fails to itemize the text, we create a run that spans the entire
1128 // text. This is needed because leaving the runs set empty causes some clients
1129 // to misbehave since they expect non-zero text metrics from a non-empty text.
1130 base::i18n::BiDiLineIterator bidi_iterator;
1131 if (!bidi_iterator.Open(text, is_text_rtl, false)) {
1132 internal::TextRunHarfBuzz* run = new internal::TextRunHarfBuzz;
1133 run->range = Range(0, text.length());
1134 run_list_out->add(run);
1135 run_list_out->InitIndexMap();
1136 return;
1139 // Temporarily apply composition underlines and selection colors.
1140 ApplyCompositionAndSelectionStyles();
1142 // Build the list of runs from the script items and ranged styles. Use an
1143 // empty color BreakList to avoid breaking runs at color boundaries.
1144 BreakList<SkColor> empty_colors;
1145 empty_colors.SetMax(text.length());
1146 internal::StyleIterator style(empty_colors, styles());
1148 for (size_t run_break = 0; run_break < text.length();) {
1149 internal::TextRunHarfBuzz* run = new internal::TextRunHarfBuzz;
1150 run->range.set_start(run_break);
1151 run->font_style = (style.style(BOLD) ? Font::BOLD : 0) |
1152 (style.style(ITALIC) ? Font::ITALIC : 0);
1153 run->strike = style.style(STRIKE);
1154 run->diagonal_strike = style.style(DIAGONAL_STRIKE);
1155 run->underline = style.style(UNDERLINE);
1156 int32 script_item_break = 0;
1157 bidi_iterator.GetLogicalRun(run_break, &script_item_break, &run->level);
1158 // Odd BiDi embedding levels correspond to RTL runs.
1159 run->is_rtl = (run->level % 2) == 1;
1160 // Find the length and script of this script run.
1161 script_item_break = ScriptInterval(text, run_break,
1162 script_item_break - run_break, &run->script) + run_break;
1164 // Find the next break and advance the iterators as needed.
1165 run_break = std::min(
1166 static_cast<size_t>(script_item_break),
1167 TextIndexToGivenTextIndex(text, style.GetRange().end()));
1169 // Break runs at certain characters that need to be rendered separately to
1170 // prevent either an unusual character from forcing a fallback font on the
1171 // entire run, or brackets from being affected by a fallback font.
1172 // http://crbug.com/278913, http://crbug.com/396776
1173 if (run_break > run->range.start())
1174 run_break = FindRunBreakingCharacter(text, run->range.start(), run_break);
1176 DCHECK(IsValidCodePointIndex(text, run_break));
1177 style.UpdatePosition(DisplayIndexToTextIndex(run_break));
1178 run->range.set_end(run_break);
1180 run_list_out->add(run);
1183 // Undo the temporarily applied composition underlines and selection colors.
1184 UndoCompositionAndSelectionStyles();
1186 run_list_out->InitIndexMap();
1189 bool RenderTextHarfBuzz::CompareFamily(
1190 const base::string16& text,
1191 const std::string& family,
1192 const gfx::FontRenderParams& render_params,
1193 internal::TextRunHarfBuzz* run,
1194 std::string* best_family,
1195 gfx::FontRenderParams* best_render_params,
1196 size_t* best_missing_glyphs) {
1197 if (!ShapeRunWithFont(text, family, render_params, run))
1198 return false;
1200 const size_t missing_glyphs = run->CountMissingGlyphs();
1201 if (missing_glyphs < *best_missing_glyphs) {
1202 *best_family = family;
1203 *best_render_params = render_params;
1204 *best_missing_glyphs = missing_glyphs;
1206 return missing_glyphs == 0;
1209 void RenderTextHarfBuzz::ShapeRunList(const base::string16& text,
1210 internal::TextRunList* run_list) {
1211 for (auto* run : run_list->runs())
1212 ShapeRun(text, run);
1213 run_list->ComputePrecedingRunWidths();
1216 void RenderTextHarfBuzz::ShapeRun(const base::string16& text,
1217 internal::TextRunHarfBuzz* run) {
1218 const Font& primary_font = font_list().GetPrimaryFont();
1219 const std::string primary_family = primary_font.GetFontName();
1220 run->font_size = primary_font.GetFontSize();
1222 std::string best_family;
1223 FontRenderParams best_render_params;
1224 size_t best_missing_glyphs = std::numeric_limits<size_t>::max();
1226 for (const Font& font : font_list().GetFonts()) {
1227 if (CompareFamily(text, font.GetFontName(), font.GetFontRenderParams(),
1228 run, &best_family, &best_render_params,
1229 &best_missing_glyphs))
1230 return;
1233 #if defined(OS_WIN)
1234 Font uniscribe_font;
1235 std::string uniscribe_family;
1236 const base::char16* run_text = &(text[run->range.start()]);
1237 if (GetUniscribeFallbackFont(primary_font, run_text, run->range.length(),
1238 &uniscribe_font)) {
1239 uniscribe_family = uniscribe_font.GetFontName();
1240 if (CompareFamily(text, uniscribe_family,
1241 uniscribe_font.GetFontRenderParams(), run,
1242 &best_family, &best_render_params, &best_missing_glyphs))
1243 return;
1245 #endif
1247 std::vector<std::string> fallback_families =
1248 GetFallbackFontFamilies(primary_family);
1250 #if defined(OS_WIN)
1251 // Append fonts in the fallback list of the Uniscribe font.
1252 if (!uniscribe_family.empty()) {
1253 std::vector<std::string> uniscribe_fallbacks =
1254 GetFallbackFontFamilies(uniscribe_family);
1255 fallback_families.insert(fallback_families.end(),
1256 uniscribe_fallbacks.begin(), uniscribe_fallbacks.end());
1258 #endif
1260 // Try shaping with the fallback fonts.
1261 for (const auto& family : fallback_families) {
1262 if (family == primary_family)
1263 continue;
1264 #if defined(OS_WIN)
1265 if (family == uniscribe_family)
1266 continue;
1267 #endif
1268 FontRenderParamsQuery query(false);
1269 query.families.push_back(family);
1270 query.pixel_size = run->font_size;
1271 query.style = run->font_style;
1272 FontRenderParams fallback_render_params = GetFontRenderParams(query, NULL);
1273 if (CompareFamily(text, family, fallback_render_params, run, &best_family,
1274 &best_render_params, &best_missing_glyphs))
1275 return;
1278 if (!best_family.empty() &&
1279 (best_family == run->family ||
1280 ShapeRunWithFont(text, best_family, best_render_params, run)))
1281 return;
1283 run->glyph_count = 0;
1284 run->width = 0.0f;
1287 bool RenderTextHarfBuzz::ShapeRunWithFont(const base::string16& text,
1288 const std::string& font_family,
1289 const FontRenderParams& params,
1290 internal::TextRunHarfBuzz* run) {
1291 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1292 tracked_objects::ScopedTracker tracking_profile0(
1293 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1294 "431326 RenderTextHarfBuzz::ShapeRunWithFont0"));
1296 skia::RefPtr<SkTypeface> skia_face =
1297 internal::CreateSkiaTypeface(font_family, run->font_style);
1298 if (skia_face == NULL)
1299 return false;
1300 run->skia_face = skia_face;
1301 run->family = font_family;
1302 run->render_params = params;
1304 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1305 tracked_objects::ScopedTracker tracking_profile01(
1306 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1307 "431326 RenderTextHarfBuzz::ShapeRunWithFont01"));
1309 hb_font_t* harfbuzz_font = CreateHarfBuzzFont(
1310 run->skia_face.get(), SkIntToScalar(run->font_size), run->render_params,
1311 subpixel_rendering_suppressed());
1313 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1314 tracked_objects::ScopedTracker tracking_profile1(
1315 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1316 "431326 RenderTextHarfBuzz::ShapeRunWithFont1"));
1318 // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz
1319 // buffer holds our text, run information to be used by the shaping engine,
1320 // and the resulting glyph data.
1321 hb_buffer_t* buffer = hb_buffer_create();
1323 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1324 tracked_objects::ScopedTracker tracking_profile1q(
1325 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1326 "431326 RenderTextHarfBuzz::ShapeRunWithFont11"));
1328 hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16*>(text.c_str()),
1329 text.length(), run->range.start(), run->range.length());
1331 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1332 tracked_objects::ScopedTracker tracking_profile12(
1333 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1334 "431326 RenderTextHarfBuzz::ShapeRunWithFont12"));
1336 hb_buffer_set_script(buffer, ICUScriptToHBScript(run->script));
1338 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1339 tracked_objects::ScopedTracker tracking_profile13(
1340 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1341 "431326 RenderTextHarfBuzz::ShapeRunWithFont13"));
1343 hb_buffer_set_direction(buffer,
1344 run->is_rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
1346 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1347 tracked_objects::ScopedTracker tracking_profile14(
1348 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1349 "431326 RenderTextHarfBuzz::ShapeRunWithFont14"));
1351 // TODO(ckocagil): Should we determine the actual language?
1352 hb_buffer_set_language(buffer, hb_language_get_default());
1354 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1355 tracked_objects::ScopedTracker tracking_profile15(
1356 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1357 "431326 RenderTextHarfBuzz::ShapeRunWithFont15"));
1359 // Shape the text.
1360 hb_shape(harfbuzz_font, buffer, NULL, 0);
1362 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1363 tracked_objects::ScopedTracker tracking_profile2(
1364 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1365 "431326 RenderTextHarfBuzz::ShapeRunWithFont2"));
1367 // Populate the run fields with the resulting glyph data in the buffer.
1368 unsigned int glyph_count = 0;
1369 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count);
1370 run->glyph_count = glyph_count;
1371 hb_glyph_position_t* hb_positions =
1372 hb_buffer_get_glyph_positions(buffer, NULL);
1373 run->glyphs.reset(new uint16[run->glyph_count]);
1374 run->glyph_to_char.resize(run->glyph_count);
1375 run->positions.reset(new SkPoint[run->glyph_count]);
1376 run->width = 0.0f;
1378 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed.
1379 tracked_objects::ScopedTracker tracking_profile3(
1380 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1381 "431326 RenderTextHarfBuzz::ShapeRunWithFont3"));
1383 for (size_t i = 0; i < run->glyph_count; ++i) {
1384 DCHECK_LE(infos[i].codepoint, std::numeric_limits<uint16>::max());
1385 run->glyphs[i] = static_cast<uint16>(infos[i].codepoint);
1386 run->glyph_to_char[i] = infos[i].cluster;
1387 const SkScalar x_offset = SkFixedToScalar(hb_positions[i].x_offset);
1388 const SkScalar y_offset = SkFixedToScalar(hb_positions[i].y_offset);
1389 run->positions[i].set(run->width + x_offset, -y_offset);
1390 run->width += (glyph_width_for_test_ > 0)
1391 ? glyph_width_for_test_
1392 : SkFixedToFloat(hb_positions[i].x_advance);
1393 // Round run widths if subpixel positioning is off to match native behavior.
1394 if (!run->render_params.subpixel_positioning)
1395 run->width = std::floor(run->width + 0.5f);
1398 hb_buffer_destroy(buffer);
1399 hb_font_destroy(harfbuzz_font);
1400 return true;
1403 void RenderTextHarfBuzz::EnsureLayoutRunList() {
1404 if (update_layout_run_list_) {
1405 layout_run_list_.Reset();
1407 const base::string16& text = layout_text();
1408 if (!text.empty()) {
1409 TRACE_EVENT0("ui", "RenderTextHarfBuzz:EnsureLayoutRunList");
1410 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
1411 // fixed.
1412 tracked_objects::ScopedTracker tracking_profile1(
1413 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1414 "431326 RenderTextHarfBuzz::EnsureLayout1"));
1415 ItemizeTextToRuns(text, &layout_run_list_);
1417 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
1418 // fixed.
1419 tracked_objects::ScopedTracker tracking_profile2(
1420 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1421 "431326 RenderTextHarfBuzz::EnsureLayout12"));
1422 ShapeRunList(text, &layout_run_list_);
1425 // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is
1426 // fixed.
1427 tracked_objects::ScopedTracker tracking_profile14(
1428 FROM_HERE_WITH_EXPLICIT_FUNCTION(
1429 "431326 RenderTextHarfBuzz::EnsureLayout14"));
1431 std::vector<internal::Line> empty_lines;
1432 set_lines(&empty_lines);
1433 display_run_list_.reset();
1434 update_display_text_ = true;
1435 update_layout_run_list_ = false;
1437 if (update_display_text_) {
1438 UpdateDisplayText(multiline() ? 0 : layout_run_list_.width());
1439 update_display_text_ = false;
1440 update_display_run_list_ = text_elided();
1444 base::i18n::BreakIterator* RenderTextHarfBuzz::GetGraphemeIterator() {
1445 if (update_grapheme_iterator_) {
1446 update_grapheme_iterator_ = false;
1447 grapheme_iterator_.reset(new base::i18n::BreakIterator(
1448 GetDisplayText(),
1449 base::i18n::BreakIterator::BREAK_CHARACTER));
1450 if (!grapheme_iterator_->Init())
1451 grapheme_iterator_.reset();
1453 return grapheme_iterator_.get();
1456 size_t RenderTextHarfBuzz::TextIndexToGivenTextIndex(
1457 const base::string16& given_text,
1458 size_t index) {
1459 DCHECK(given_text == layout_text() || given_text == display_text());
1460 DCHECK_LE(index, text().length());
1461 ptrdiff_t i = obscured() ? UTF16IndexToOffset(text(), 0, index) : index;
1462 CHECK_GE(i, 0);
1463 // Clamp indices to the length of the given layout or display text.
1464 return std::min<size_t>(given_text.length(), i);
1467 internal::TextRunList* RenderTextHarfBuzz::GetRunList() {
1468 DCHECK(!update_layout_run_list_);
1469 DCHECK(!update_display_run_list_);
1470 return text_elided() ? display_run_list_.get() : &layout_run_list_;
1473 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const {
1474 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList();
1477 } // namespace gfx