Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/render_text_harfbuzz.h" | 5 #include "ui/gfx/render_text_harfbuzz.h" |
| 6 | 6 |
| 7 #include <limits> | 7 #include <limits> |
| 8 #include <set> | 8 #include <set> |
| 9 | 9 |
| 10 #include "base/i18n/bidi_line_iterator.h" | 10 #include "base/i18n/bidi_line_iterator.h" |
| 11 #include "base/i18n/break_iterator.h" | 11 #include "base/i18n/break_iterator.h" |
| 12 #include "base/i18n/char_iterator.h" | 12 #include "base/i18n/char_iterator.h" |
| 13 #include "base/profiler/scoped_tracker.h" | 13 #include "base/profiler/scoped_tracker.h" |
| 14 #include "base/strings/string_util.h" | 14 #include "base/strings/string_util.h" |
| 15 #include "base/strings/utf_string_conversions.h" | 15 #include "base/strings/utf_string_conversions.h" |
| 16 #include "base/trace_event/trace_event.h" | 16 #include "base/trace_event/trace_event.h" |
| 17 #include "third_party/harfbuzz-ng/src/hb.h" | 17 #include "third_party/harfbuzz-ng/src/hb.h" |
| 18 #include "third_party/icu/source/common/unicode/ubidi.h" | 18 #include "third_party/icu/source/common/unicode/ubidi.h" |
| 19 #include "third_party/icu/source/common/unicode/utf16.h" | |
| 19 #include "third_party/skia/include/core/SkColor.h" | 20 #include "third_party/skia/include/core/SkColor.h" |
| 20 #include "third_party/skia/include/core/SkTypeface.h" | 21 #include "third_party/skia/include/core/SkTypeface.h" |
| 21 #include "ui/gfx/canvas.h" | 22 #include "ui/gfx/canvas.h" |
| 22 #include "ui/gfx/font_fallback.h" | 23 #include "ui/gfx/font_fallback.h" |
| 23 #include "ui/gfx/font_render_params.h" | 24 #include "ui/gfx/font_render_params.h" |
| 24 #include "ui/gfx/geometry/safe_integer_conversions.h" | 25 #include "ui/gfx/geometry/safe_integer_conversions.h" |
| 25 #include "ui/gfx/harfbuzz_font_skia.h" | 26 #include "ui/gfx/harfbuzz_font_skia.h" |
| 26 #include "ui/gfx/range/range_f.h" | 27 #include "ui/gfx/range/range_f.h" |
| 27 #include "ui/gfx/utf16_indexing.h" | 28 #include "ui/gfx/utf16_indexing.h" |
| 28 | 29 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 42 // The maximum number of scripts a Unicode character can belong to. This value | 43 // The maximum number of scripts a Unicode character can belong to. This value |
| 43 // is arbitrarily chosen to be a good limit because it is unlikely for a single | 44 // is arbitrarily chosen to be a good limit because it is unlikely for a single |
| 44 // character to belong to more scripts. | 45 // character to belong to more scripts. |
| 45 const size_t kMaxScripts = 5; | 46 const size_t kMaxScripts = 5; |
| 46 | 47 |
| 47 // Returns true if characters of |block_code| may trigger font fallback. | 48 // Returns true if characters of |block_code| may trigger font fallback. |
| 48 // Dingbats and emoticons can be rendered through the color emoji font file, | 49 // Dingbats and emoticons can be rendered through the color emoji font file, |
| 49 // therefore it needs to be trigerred as fallbacks. See crbug.com/448909 | 50 // therefore it needs to be trigerred as fallbacks. See crbug.com/448909 |
| 50 bool IsUnusualBlockCode(UBlockCode block_code) { | 51 bool IsUnusualBlockCode(UBlockCode block_code) { |
| 51 return block_code == UBLOCK_GEOMETRIC_SHAPES || | 52 return block_code == UBLOCK_GEOMETRIC_SHAPES || |
| 52 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS || | 53 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS; |
| 53 block_code == UBLOCK_DINGBATS || | |
| 54 block_code == UBLOCK_EMOTICONS; | |
| 55 } | 54 } |
| 56 | 55 |
| 57 bool IsBracket(UChar32 character) { | 56 bool IsBracket(UChar32 character) { |
| 58 static const char kBrackets[] = { '(', ')', '{', '}', '<', '>', }; | 57 static const char kBrackets[] = { '(', ')', '{', '}', '<', '>', }; |
| 59 static const char* kBracketsEnd = kBrackets + arraysize(kBrackets); | 58 static const char* kBracketsEnd = kBrackets + arraysize(kBrackets); |
| 60 return std::find(kBrackets, kBracketsEnd, character) != kBracketsEnd; | 59 return std::find(kBrackets, kBracketsEnd, character) != kBracketsEnd; |
| 61 } | 60 } |
| 62 | 61 |
| 63 // Returns the boundary between a special and a regular character. Special | 62 // Returns the boundary between a special and a regular character. Special |
| 64 // characters are brackets or characters that satisfy |IsUnusualBlockCode|. | 63 // characters are brackets or characters that satisfy |IsUnusualBlockCode|. |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 83 const bool block_break = current_block != first_block && | 82 const bool block_break = current_block != first_block && |
| 84 (first_block_unusual || IsUnusualBlockCode(current_block)); | 83 (first_block_unusual || IsUnusualBlockCode(current_block)); |
| 85 if (block_break || current_char == '\n' || | 84 if (block_break || current_char == '\n' || |
| 86 first_bracket != IsBracket(current_char)) { | 85 first_bracket != IsBracket(current_char)) { |
| 87 return run_start + iter.array_pos(); | 86 return run_start + iter.array_pos(); |
| 88 } | 87 } |
| 89 } | 88 } |
| 90 return run_break; | 89 return run_break; |
| 91 } | 90 } |
| 92 | 91 |
| 93 // If the given scripts match, returns the one that isn't USCRIPT_COMMON or | 92 // If the given scripts match, returns the one that isn't USCRIPT_INHERITED, |
| 94 // USCRIPT_INHERITED, i.e. the more specific one. Otherwise returns | 93 // i.e. the more specific one. Otherwise returns USCRIPT_INVALID_CODE. |
| 95 // USCRIPT_INVALID_CODE. | |
| 96 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { | 94 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { |
| 97 if (first == second || | 95 if (first == second || second == USCRIPT_INHERITED) |
| 98 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED)) { | |
| 99 return first; | 96 return first; |
| 100 } | 97 if (first == USCRIPT_INHERITED) |
| 101 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED) | |
| 102 return second; | 98 return second; |
| 103 return USCRIPT_INVALID_CODE; | 99 return USCRIPT_INVALID_CODE; |
| 104 } | 100 } |
| 105 | 101 |
| 106 // Writes the script and the script extensions of the character with the | 102 // Writes the script and the script extensions of the character with the |
| 107 // Unicode |codepoint|. Returns the number of written scripts. | 103 // Unicode |codepoint|. Returns the number of written scripts. |
| 108 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { | 104 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { |
| 109 UErrorCode icu_error = U_ZERO_ERROR; | 105 UErrorCode icu_error = U_ZERO_ERROR; |
| 110 // ICU documentation incorrectly states that the result of | 106 // ICU documentation incorrectly states that the result of |
| 111 // |uscript_getScriptExtensions| will contain the regular script property. | 107 // |uscript_getScriptExtensions| will contain the regular script property. |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 159 UScriptCode* script) { | 155 UScriptCode* script) { |
| 160 DCHECK_GT(length, 0U); | 156 DCHECK_GT(length, 0U); |
| 161 | 157 |
| 162 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; | 158 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; |
| 163 | 159 |
| 164 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length); | 160 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length); |
| 165 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); | 161 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); |
| 166 *script = scripts[0]; | 162 *script = scripts[0]; |
| 167 | 163 |
| 168 while (char_iterator.Advance()) { | 164 while (char_iterator.Advance()) { |
| 165 // Special handling to merge white space into the previous run. | |
| 166 if (u_isUWhiteSpace(char_iterator.get())) | |
| 167 continue; | |
| 169 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); | 168 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); |
| 170 if (scripts_size == 0U) | 169 if (scripts_size == 0U) |
| 171 return char_iterator.array_pos(); | 170 return char_iterator.array_pos(); |
| 172 *script = scripts[0]; | 171 *script = scripts[0]; |
| 173 } | 172 } |
| 174 | 173 |
| 175 return length; | 174 return length; |
| 176 } | 175 } |
| 177 | 176 |
| 178 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without | 177 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 204 reversed ? elements_end - element : element - elements_begin); | 203 reversed ? elements_end - element : element - elements_begin); |
| 205 if (reversed) | 204 if (reversed) |
| 206 *glyphs = Range(glyphs->end(), glyphs->start()); | 205 *glyphs = Range(glyphs->end(), glyphs->start()); |
| 207 | 206 |
| 208 DCHECK(!chars->is_reversed()); | 207 DCHECK(!chars->is_reversed()); |
| 209 DCHECK(!chars->is_empty()); | 208 DCHECK(!chars->is_empty()); |
| 210 DCHECK(!glyphs->is_reversed()); | 209 DCHECK(!glyphs->is_reversed()); |
| 211 DCHECK(!glyphs->is_empty()); | 210 DCHECK(!glyphs->is_empty()); |
| 212 } | 211 } |
| 213 | 212 |
| 213 // Returns true if the code point |c| is a combining mark character in Unicode. | |
| 214 bool CharIsMark(UChar32 c) { | |
| 215 int8_t char_type = u_charType(c); | |
| 216 return char_type == U_NON_SPACING_MARK || char_type == U_ENCLOSING_MARK || | |
| 217 char_type == U_COMBINING_SPACING_MARK; | |
| 218 } | |
| 219 | |
| 220 // Gets the code point of |str| at the given code unit position |index|. If | |
| 221 // |index| is a surrogate code unit, returns the whole code point (unless the | |
| 222 // code unit is unpaired, in which case it just returns the surrogate value). | |
| 223 UChar32 GetCodePointAt(const base::string16& str, size_t index) { | |
| 224 UChar32 c; | |
| 225 U16_GET(str.data(), 0, index, str.size(), c); | |
| 226 return c; | |
| 227 } | |
| 228 | |
| 214 // Internal class to generate Line structures. If |multiline| is true, the text | 229 // Internal class to generate Line structures. If |multiline| is true, the text |
| 215 // is broken into lines at |words| boundaries such that each line is no longer | 230 // is broken into lines at |words| boundaries such that each line is no longer |
| 216 // than |max_width|. If |multiline| is false, only outputs a single Line from | 231 // than |max_width|. If |multiline| is false, only outputs a single Line from |
| 217 // the given runs. |min_baseline| and |min_height| are the minimum baseline and | 232 // the given runs. |min_baseline| and |min_height| are the minimum baseline and |
| 218 // height for each line. | 233 // height for each line. |
| 219 // TODO(ckocagil): Expose the interface of this class in the header and test | 234 // TODO(ckocagil): Expose the interface of this class in the header and test |
| 220 // this class directly. | 235 // this class directly. |
| 221 class HarfBuzzLineBreaker { | 236 class HarfBuzzLineBreaker { |
| 222 public: | 237 public: |
| 223 HarfBuzzLineBreaker(size_t max_width, | 238 HarfBuzzLineBreaker(size_t max_width, |
| 224 int min_baseline, | 239 int min_baseline, |
| 225 float min_height, | 240 float min_height, |
| 226 bool multiline, | |
| 227 WordWrapBehavior word_wrap_behavior, | 241 WordWrapBehavior word_wrap_behavior, |
| 228 const base::string16& text, | 242 const base::string16& text, |
| 229 const BreakList<size_t>* words, | 243 const BreakList<size_t>* words, |
| 230 const internal::TextRunList& run_list) | 244 const internal::TextRunList& run_list) |
| 231 : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)), | 245 : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)), |
| 232 min_baseline_(min_baseline), | 246 min_baseline_(min_baseline), |
| 233 min_height_(min_height), | 247 min_height_(min_height), |
| 234 multiline_(multiline), | |
| 235 word_wrap_behavior_(word_wrap_behavior), | 248 word_wrap_behavior_(word_wrap_behavior), |
| 236 text_(text), | 249 text_(text), |
| 237 words_(words), | 250 words_(words), |
| 238 run_list_(run_list), | 251 run_list_(run_list), |
| 252 max_descent_(0), | |
| 253 max_ascent_(0), | |
| 239 text_x_(0), | 254 text_x_(0), |
| 240 line_x_(0), | 255 available_width_(max_width_) { |
| 241 max_descent_(0), | |
| 242 max_ascent_(0) { | |
| 243 DCHECK_EQ(multiline_, (words_ != nullptr)); | |
| 244 AdvanceLine(); | 256 AdvanceLine(); |
| 245 } | 257 } |
| 246 | 258 |
| 247 // Breaks the run at given |run_index| into Line structs. | 259 // Constructs a single line for |text_| using |run_list_|. |
| 248 void AddRun(int run_index) { | 260 void ConstructSingleLine() { |
| 249 const internal::TextRunHarfBuzz* run = run_list_.runs()[run_index]; | 261 for (size_t i = 0; i < run_list_.size(); i++) { |
| 250 base::char16 first_char = text_[run->range.start()]; | 262 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]); |
| 251 if (multiline_ && first_char == '\n') { | 263 internal::LineSegment segment; |
| 252 AdvanceLine(); | 264 segment.run = i; |
| 253 } else if (multiline_ && (line_x_ + SkFloatToScalar(run->width)) > | 265 segment.char_range = run.range; |
| 254 max_width_) { | 266 segment.x_range = Range(SkScalarCeilToInt(text_x_), |
| 255 BreakRun(run_index); | 267 SkScalarCeilToInt(text_x_ + run.width)); |
| 256 } else { | 268 segment.width = run.width; |
| 257 AddSegment(run_index, run->range, run->width); | 269 AddLineSegment(segment); |
| 270 } | |
| 271 } | |
| 272 | |
| 273 // Constructs multiple lines for |text_| based on words iteration approach. | |
| 274 void ConstructMultiLines() { | |
| 275 DCHECK(words_); | |
| 276 for (auto iter = words_->breaks().begin(); iter != words_->breaks().end(); | |
| 277 iter++) { | |
| 278 const Range word_range = words_->GetRange(iter); | |
| 279 std::vector<internal::LineSegment> word_segments; | |
| 280 SkScalar word_width = GetWordWidth(word_range, &word_segments); | |
| 281 | |
| 282 // If the last word is '\n', we should advance a new line after adding | |
| 283 // the word to the current line. | |
| 284 bool new_line = false; | |
| 285 | |
| 286 if (!word_segments.empty() && | |
| 287 text_[word_segments.back().char_range.start()] == '\n') { | |
| 288 new_line = true; | |
| 289 word_width -= word_segments.back().width; | |
| 290 word_segments.pop_back(); | |
| 291 } | |
| 292 | |
| 293 // If the word is not the first word in the line and it can't fit into | |
| 294 // the current line, advance a new line. | |
| 295 if (word_width > available_width_ && available_width_ != max_width_) | |
| 296 AdvanceLine(); | |
| 297 AddWordToLine(word_segments); | |
| 298 if (new_line) | |
| 299 AdvanceLine(); | |
| 258 } | 300 } |
| 259 } | 301 } |
| 260 | 302 |
| 261 // Finishes line breaking and outputs the results. Can be called at most once. | 303 // Finishes line breaking and outputs the results. Can be called at most once. |
| 262 void Finalize(std::vector<internal::Line>* lines, SizeF* size) { | 304 void FinalizeLines(std::vector<internal::Line>* lines, SizeF* size) { |
| 263 DCHECK(!lines_.empty()); | 305 DCHECK(!lines_.empty()); |
| 264 // Add an empty line to finish the line size calculation and remove it. | 306 // Add an empty line to finish the line size calculation and remove it. |
| 265 AdvanceLine(); | 307 AdvanceLine(); |
| 266 lines_.pop_back(); | 308 lines_.pop_back(); |
| 267 *size = total_size_; | 309 *size = total_size_; |
| 268 lines->swap(lines_); | 310 lines->swap(lines_); |
| 269 } | 311 } |
| 270 | 312 |
| 271 private: | 313 private: |
| 272 // A (line index, segment index) pair that specifies a segment in |lines_|. | 314 // A (line index, segment index) pair that specifies a segment in |lines_|. |
| 273 typedef std::pair<size_t, size_t> SegmentHandle; | 315 typedef std::pair<size_t, size_t> SegmentHandle; |
| 274 | 316 |
| 275 internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) { | 317 internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) { |
| 276 return &lines_[handle.first].segments[handle.second]; | 318 return &lines_[handle.first].segments[handle.second]; |
| 277 } | 319 } |
| 278 | 320 |
| 279 // Breaks a run into segments that fit in the last line in |lines_| and adds | |
| 280 // them. Adds a new Line to the back of |lines_| whenever a new segment can't | |
| 281 // be added without the Line's width exceeding |max_width_|. | |
| 282 void BreakRun(int run_index) { | |
| 283 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]); | |
| 284 SkScalar width = 0; | |
| 285 size_t next_char = run.range.start(); | |
| 286 | |
| 287 // Break the run until it fits the current line. | |
| 288 while (next_char < run.range.end()) { | |
| 289 const size_t current_char = next_char; | |
| 290 size_t end_char = next_char; | |
| 291 const bool skip_line = | |
| 292 BreakRunAtWidth(run, current_char, &width, &end_char, &next_char); | |
| 293 AddSegment(run_index, Range(current_char, end_char), | |
| 294 SkScalarToFloat(width)); | |
| 295 if (skip_line) | |
| 296 AdvanceLine(); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 // Starting from |start_char|, finds a suitable line break position at or | |
| 301 // before available width using word break. If the current position is at the | |
| 302 // beginning of a line, this function will not roll back to |start_char| and | |
| 303 // |*next_char| will be greater than |start_char| (to avoid constructing empty | |
| 304 // lines). It stores the end of the segment range to |end_char|, which can be | |
| 305 // smaller than |*next_char| for certain word wrapping behavior. | |
| 306 // Returns whether to skip the line before |*next_char|. | |
| 307 // TODO(ckocagil): We might have to reshape after breaking at ligatures. | |
| 308 // See whether resolving the TODO above resolves this too. | |
| 309 // TODO(ckocagil): Do not reserve width for whitespace at the end of lines. | |
| 310 bool BreakRunAtWidth(const internal::TextRunHarfBuzz& run, | |
| 311 size_t start_char, | |
| 312 SkScalar* width, | |
| 313 size_t* end_char, | |
| 314 size_t* next_char) { | |
| 315 DCHECK(words_); | |
| 316 DCHECK(run.range.Contains(Range(start_char, start_char + 1))); | |
| 317 SkScalar available_width = max_width_ - line_x_; | |
| 318 BreakList<size_t>::const_iterator word = words_->GetBreak(start_char); | |
| 319 BreakList<size_t>::const_iterator next_word = word + 1; | |
| 320 // Width from |std::max(word->first, start_char)| to the current character. | |
| 321 SkScalar word_width = 0; | |
| 322 *width = 0; | |
| 323 | |
| 324 Range char_range; | |
| 325 SkScalar truncated_width = 0; | |
| 326 for (size_t i = start_char; i < run.range.end(); i += char_range.length()) { | |
| 327 // |word| holds the word boundary at or before |i|, and |next_word| holds | |
| 328 // the word boundary right after |i|. Advance both |word| and |next_word| | |
| 329 // when |i| reaches |next_word|. | |
| 330 if (next_word != words_->breaks().end() && i >= next_word->first) { | |
| 331 if (*width > available_width) { | |
| 332 DCHECK_NE(WRAP_LONG_WORDS, word_wrap_behavior_); | |
| 333 *next_char = i; | |
| 334 if (word_wrap_behavior_ != TRUNCATE_LONG_WORDS) | |
| 335 *end_char = *next_char; | |
| 336 else | |
| 337 *width = truncated_width; | |
| 338 return true; | |
| 339 } | |
| 340 word = next_word++; | |
| 341 word_width = 0; | |
| 342 } | |
| 343 | |
| 344 Range glyph_range; | |
| 345 run.GetClusterAt(i, &char_range, &glyph_range); | |
| 346 DCHECK_LT(0U, char_range.length()); | |
| 347 | |
| 348 SkScalar char_width = ((glyph_range.end() >= run.glyph_count) | |
| 349 ? SkFloatToScalar(run.width) | |
| 350 : run.positions[glyph_range.end()].x()) - | |
| 351 run.positions[glyph_range.start()].x(); | |
| 352 | |
| 353 *width += char_width; | |
| 354 word_width += char_width; | |
| 355 | |
| 356 // TODO(mukai): implement ELIDE_LONG_WORDS. | |
| 357 if (*width > available_width) { | |
| 358 if (line_x_ != 0 || word_width < *width) { | |
| 359 // Roll back one word. | |
| 360 *width -= word_width; | |
| 361 *next_char = std::max(word->first, start_char); | |
| 362 *end_char = *next_char; | |
| 363 return true; | |
| 364 } else if (word_wrap_behavior_ == WRAP_LONG_WORDS) { | |
| 365 if (char_width < *width) { | |
| 366 // Roll back one character. | |
| 367 *width -= char_width; | |
| 368 *next_char = i; | |
| 369 } else { | |
| 370 // Continue from the next character. | |
| 371 *next_char = i + char_range.length(); | |
| 372 } | |
| 373 *end_char = *next_char; | |
| 374 return true; | |
| 375 } | |
| 376 } else { | |
| 377 *end_char = char_range.end(); | |
| 378 truncated_width = *width; | |
| 379 } | |
| 380 } | |
| 381 | |
| 382 if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS) | |
| 383 *width = truncated_width; | |
| 384 *end_char = *next_char = run.range.end(); | |
| 385 return false; | |
| 386 } | |
| 387 | |
| 388 // RTL runs are broken in logical order but displayed in visual order. To find | |
| 389 // the text-space coordinate (where it would fall in a single-line text) | |
| 390 // |x_range| of RTL segments, segment widths are applied in reverse order. | |
| 391 // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}. | |
| 392 void UpdateRTLSegmentRanges() { | |
| 393 if (rtl_segments_.empty()) | |
| 394 return; | |
| 395 float x = SegmentFromHandle(rtl_segments_[0])->x_range.start(); | |
| 396 for (size_t i = rtl_segments_.size(); i > 0; --i) { | |
| 397 internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]); | |
| 398 const float segment_width = segment->width; | |
| 399 segment->x_range = Range(x, x + segment_width); | |
| 400 x += segment_width; | |
| 401 } | |
| 402 rtl_segments_.clear(); | |
| 403 } | |
| 404 | |
| 405 // Finishes the size calculations of the last Line in |lines_|. Adds a new | 321 // Finishes the size calculations of the last Line in |lines_|. Adds a new |
| 406 // Line to the back of |lines_|. | 322 // Line to the back of |lines_|. |
| 407 void AdvanceLine() { | 323 void AdvanceLine() { |
| 408 if (!lines_.empty()) { | 324 if (!lines_.empty()) { |
| 409 internal::Line* line = &lines_.back(); | 325 internal::Line* line = &lines_.back(); |
| 410 std::sort(line->segments.begin(), line->segments.end(), | 326 std::sort(line->segments.begin(), line->segments.end(), |
| 411 [this](const internal::LineSegment& s1, | 327 [this](const internal::LineSegment& s1, |
| 412 const internal::LineSegment& s2) -> bool { | 328 const internal::LineSegment& s2) -> bool { |
| 413 return run_list_.logical_to_visual(s1.run) < | 329 return run_list_.logical_to_visual(s1.run) < |
| 414 run_list_.logical_to_visual(s2.run); | 330 run_list_.logical_to_visual(s2.run); |
| 415 }); | 331 }); |
| 416 line->size.set_height(std::max(min_height_, max_descent_ + max_ascent_)); | 332 line->size.set_height(std::max(min_height_, max_descent_ + max_ascent_)); |
| 417 line->baseline = | 333 line->baseline = std::max(min_baseline_, SkScalarRoundToInt(max_ascent_)); |
| 418 std::max(min_baseline_, SkScalarRoundToInt(max_ascent_)); | |
| 419 line->preceding_heights = std::ceil(total_size_.height()); | 334 line->preceding_heights = std::ceil(total_size_.height()); |
| 420 total_size_.set_height(total_size_.height() + line->size.height()); | 335 total_size_.set_height(total_size_.height() + line->size.height()); |
| 421 total_size_.set_width(std::max(total_size_.width(), line->size.width())); | 336 total_size_.set_width(std::max(total_size_.width(), line->size.width())); |
| 422 } | 337 } |
| 423 max_descent_ = 0; | 338 max_descent_ = 0; |
| 424 max_ascent_ = 0; | 339 max_ascent_ = 0; |
| 425 line_x_ = 0; | 340 available_width_ = max_width_; |
| 426 lines_.push_back(internal::Line()); | 341 lines_.push_back(internal::Line()); |
| 427 } | 342 } |
| 428 | 343 |
| 429 // Adds a new segment with the given properties to |lines_.back()|. | 344 // Adds word to the current line. A word may contain multiple segments. If the |
| 430 void AddSegment(int run_index, Range char_range, float width) { | 345 // word is the first word in line and its width exceeds |available_width_|, |
| 431 if (char_range.is_empty()) { | 346 // ignore/truncate/wrap it according to |word_wrap_behavior_|. |
| 432 DCHECK_EQ(0, width); | 347 void AddWordToLine(const std::vector<internal::LineSegment>& word_segments) { |
| 348 DCHECK(!lines_.empty()); | |
| 349 if (word_segments.empty()) | |
| 433 return; | 350 return; |
| 351 | |
| 352 bool has_truncated = false; | |
| 353 for (const internal::LineSegment& segment : word_segments) { | |
| 354 if (has_truncated) | |
| 355 break; | |
| 356 if (segment.width <= available_width_ || | |
| 357 word_wrap_behavior_ == IGNORE_LONG_WORDS) { | |
| 358 AddLineSegment(segment); | |
| 359 } else { | |
| 360 DCHECK(word_wrap_behavior_ == TRUNCATE_LONG_WORDS || | |
| 361 word_wrap_behavior_ == WRAP_LONG_WORDS); | |
| 362 has_truncated = (word_wrap_behavior_ == TRUNCATE_LONG_WORDS); | |
| 363 | |
| 364 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]); | |
| 365 internal::LineSegment remaining_segment = segment; | |
| 366 while (!remaining_segment.char_range.is_empty()) { | |
| 367 size_t cutoff_pos = GetCutoffPos(remaining_segment); | |
| 368 // Corrects the boundary position if necessary. The surrogate pair and | |
| 369 // combined characters should not be separated. | |
| 370 FindValidBoundaryBefore(remaining_segment, &cutoff_pos); | |
|
msw
2015/06/01 23:27:18
Can this be part of GetCutoffPos?
xdai1
2015/06/02 03:49:56
Done.
| |
| 371 SkScalar width = | |
| 372 remaining_segment.char_range.start() == cutoff_pos | |
|
msw
2015/06/01 23:27:18
nit: put this ==/0 logic in GetGlyphWidthForCharRa
xdai1
2015/06/02 03:49:56
Done.
| |
| 373 ? 0 | |
| 374 : run.GetGlyphWidthForCharRange(Range( | |
| 375 remaining_segment.char_range.start(), cutoff_pos)); | |
| 376 | |
| 377 // |max_width_| might be smaller than a single character. In this case | |
|
msw
2015/06/01 23:27:18
Ditto, can this be part of GetCutoffPos?
xdai1
2015/06/02 03:49:56
Done.
| |
| 378 // we need to put at least one character in the line. Note that, we | |
| 379 // should not separate surrogate pair or combined characters. | |
| 380 // See RenderTextTest.Multiline_MinWidth for an example. | |
| 381 if (width == 0 && available_width_ == max_width_) { | |
| 382 // Skip all zero width characters. | |
| 383 for (cutoff_pos = remaining_segment.char_range.start(); | |
| 384 cutoff_pos < remaining_segment.char_range.end(); | |
| 385 cutoff_pos++) { | |
| 386 SkScalar char_width = run.GetGlyphWidthForCharRange( | |
| 387 Range(cutoff_pos, cutoff_pos + 1)); | |
| 388 if (char_width > 0) | |
| 389 break; | |
| 390 } | |
| 391 // Don't separate surrogate pair or combined characters. | |
| 392 cutoff_pos = FindValidBoundaryAfter(remaining_segment, cutoff_pos); | |
| 393 width = run.GetGlyphWidthForCharRange( | |
| 394 Range(remaining_segment.char_range.start(), cutoff_pos)); | |
| 395 } | |
| 396 | |
| 397 if (width > 0) { | |
| 398 internal::LineSegment cut_segment; | |
| 399 cut_segment.run = remaining_segment.run; | |
| 400 cut_segment.char_range = | |
| 401 Range(remaining_segment.char_range.start(), cutoff_pos); | |
| 402 cut_segment.width = width; | |
| 403 cut_segment.x_range = Range(remaining_segment.x_range.start(), | |
| 404 SkScalarCeilToInt(text_x_ + width)); | |
| 405 AddLineSegment(cut_segment); | |
| 406 // Updates old segment range. | |
| 407 remaining_segment.char_range.set_start(cutoff_pos); | |
| 408 remaining_segment.x_range.set_start(SkScalarCeilToInt(text_x_)); | |
| 409 remaining_segment.width -= width; | |
| 410 } | |
| 411 if (has_truncated) | |
| 412 break; | |
| 413 if (!remaining_segment.char_range.is_empty()) | |
| 414 AdvanceLine(); | |
| 415 } | |
| 416 } | |
| 434 } | 417 } |
| 435 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]); | 418 } |
| 436 | 419 |
| 437 internal::LineSegment segment; | 420 // Add a line segment to the current line. Note that, in order to keep the |
| 438 segment.run = run_index; | 421 // visual order correct for ltr and rtl language, we need to merge segments |
| 439 segment.char_range = char_range; | 422 // that belong to the same run. |
| 440 segment.x_range = Range( | 423 void AddLineSegment(const internal::LineSegment& segment) { |
| 441 SkScalarCeilToInt(text_x_), | 424 DCHECK(!lines_.empty()); |
| 442 SkScalarCeilToInt(text_x_ + SkFloatToScalar(width))); | |
| 443 segment.width = width; | |
| 444 | |
| 445 internal::Line* line = &lines_.back(); | 425 internal::Line* line = &lines_.back(); |
| 426 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]); | |
| 427 if (!line->segments.empty()) { | |
| 428 internal::LineSegment& last_segment = line->segments.back(); | |
| 429 // Merge segments that belong to the same run. | |
| 430 if (last_segment.run == segment.run) { | |
| 431 DCHECK(last_segment.char_range.end() == segment.char_range.start()); | |
|
msw
2015/06/01 23:27:18
nit: DCHECK_EQ here and below.
xdai1
2015/06/02 03:49:56
Done.
| |
| 432 DCHECK(last_segment.x_range.end() == segment.x_range.start()); | |
| 433 last_segment.char_range.set_end(segment.char_range.end()); | |
| 434 last_segment.width += segment.width; | |
| 435 last_segment.x_range.set_end( | |
| 436 SkScalarCeilToInt(text_x_ + segment.width)); | |
| 437 if (run.is_rtl && last_segment.char_range.end() == run.range.end()) | |
| 438 UpdateRTLSegmentRanges(); | |
| 439 line->size.set_width(line->size.width() + segment.width); | |
| 440 text_x_ += segment.width; | |
| 441 available_width_ -= segment.width; | |
| 442 return; | |
| 443 } | |
| 444 } | |
| 446 line->segments.push_back(segment); | 445 line->segments.push_back(segment); |
| 447 | 446 |
| 448 SkPaint paint; | 447 SkPaint paint; |
| 449 paint.setTypeface(run.skia_face.get()); | 448 paint.setTypeface(run.skia_face.get()); |
| 450 paint.setTextSize(SkIntToScalar(run.font_size)); | 449 paint.setTextSize(SkIntToScalar(run.font_size)); |
| 451 paint.setAntiAlias(run.render_params.antialiasing); | 450 paint.setAntiAlias(run.render_params.antialiasing); |
| 452 SkPaint::FontMetrics metrics; | 451 SkPaint::FontMetrics metrics; |
| 453 paint.getFontMetrics(&metrics); | 452 paint.getFontMetrics(&metrics); |
| 454 | 453 |
| 455 line->size.set_width(line->size.width() + width); | 454 line->size.set_width(line->size.width() + segment.width); |
| 456 // TODO(dschuyler): Account for stylized baselines in string sizing. | 455 // TODO(dschuyler): Account for stylized baselines in string sizing. |
| 457 max_descent_ = std::max(max_descent_, metrics.fDescent); | 456 max_descent_ = std::max(max_descent_, metrics.fDescent); |
| 458 // fAscent is always negative. | 457 // fAscent is always negative. |
| 459 max_ascent_ = std::max(max_ascent_, -metrics.fAscent); | 458 max_ascent_ = std::max(max_ascent_, -metrics.fAscent); |
| 460 | 459 |
| 461 if (run.is_rtl) { | 460 if (run.is_rtl) { |
| 462 rtl_segments_.push_back( | 461 rtl_segments_.push_back( |
| 463 SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); | 462 SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); |
| 464 // If this is the last segment of an RTL run, reprocess the text-space x | 463 // If this is the last segment of an RTL run, reprocess the text-space x |
| 465 // ranges of all segments from the run. | 464 // ranges of all segments from the run. |
| 466 if (char_range.end() == run.range.end()) | 465 if (segment.char_range.end() == run.range.end()) |
| 467 UpdateRTLSegmentRanges(); | 466 UpdateRTLSegmentRanges(); |
| 468 } | 467 } |
| 469 text_x_ += SkFloatToScalar(width); | 468 text_x_ += segment.width; |
| 470 line_x_ += SkFloatToScalar(width); | 469 available_width_ -= segment.width; |
| 470 } | |
| 471 | |
| 472 // Finds the end position |end_pos| in |segment| where the preceding width is | |
| 473 // no larger than |available_width_|. | |
| 474 size_t GetCutoffPos(const internal::LineSegment& segment) const { | |
| 475 DCHECK(!segment.char_range.is_empty()); | |
| 476 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]); | |
| 477 size_t end_pos = segment.char_range.start(); | |
| 478 SkScalar width = 0; | |
| 479 while (end_pos < segment.char_range.end()) { | |
| 480 const SkScalar char_width = | |
| 481 run.GetGlyphWidthForCharRange(Range(end_pos, end_pos + 1)); | |
| 482 if (width + char_width > available_width_) | |
| 483 break; | |
| 484 width += char_width; | |
| 485 end_pos++; | |
| 486 } | |
| 487 return end_pos; | |
| 488 } | |
| 489 | |
| 490 // Finds the first valid position after |start_pos| for |segment|. The | |
| 491 // surrogate pair and combined characters should not be separated. If there | |
| 492 // is no surrogate pair or combined characters, return |start_pos| + 1. | |
| 493 size_t FindValidBoundaryAfter(const internal::LineSegment& segment, | |
|
msw
2015/06/01 23:27:18
Can we avoid copying modified versions of text_eli
xdai1
2015/06/02 03:49:56
Put these functions in text_util.h and text_util.c
| |
| 494 const size_t start_pos) { | |
| 495 DCHECK_GE(start_pos, segment.char_range.start()); | |
| 496 DCHECK_LT(start_pos, segment.char_range.end()); | |
| 497 | |
| 498 size_t index = start_pos; | |
| 499 // Check if it is a surrogate pair. | |
| 500 if (index <= text_.size() - 2 && U16_IS_LEAD(text_[index]) && | |
| 501 U16_IS_TRAIL(text_[index + 1])) { | |
| 502 return index + 2; | |
| 503 } | |
| 504 | |
| 505 // Check if it is a combined character. | |
| 506 while (index < segment.char_range.end() && | |
| 507 CharIsMark(GetCodePointAt(text_, index))) { | |
| 508 index++; | |
| 509 } | |
| 510 // If |index| straddles a surrogate pair, go forward. | |
| 511 U16_SET_CP_LIMIT(text_.data(), 0, index, text_.size()); | |
| 512 | |
| 513 return index == start_pos ? start_pos + 1 : index; | |
| 514 } | |
| 515 | |
| 516 // Corrects the boundary position |end_pos| if necessary. The surrogate | |
| 517 // pair and combined characters should not be separated. | |
| 518 void FindValidBoundaryBefore(const internal::LineSegment& segment, | |
| 519 size_t* end_pos) { | |
| 520 DCHECK_GE(*end_pos, segment.char_range.start()); | |
| 521 DCHECK_LE(*end_pos, segment.char_range.end()); | |
| 522 if (*end_pos == segment.char_range.start()) | |
| 523 return; | |
| 524 | |
| 525 size_t index = *end_pos; | |
| 526 // Check the surrogate pair was separated, go back. | |
| 527 if (index > 0 && index < text_.size() - 1 && | |
| 528 U16_IS_LEAD(text_[index - 1]) && U16_IS_TRAIL(text_[index])) { | |
| 529 *end_pos = index - 1; | |
| 530 return; | |
| 531 } | |
| 532 | |
| 533 // Check if it is a combined character. | |
| 534 while (index > segment.char_range.start() && | |
| 535 CharIsMark(GetCodePointAt(text_, index - 1))) { | |
| 536 index--; | |
| 537 } | |
| 538 // If |index| straddles a surrogate pair, go back. | |
| 539 U16_SET_CP_START(text_.data(), segment.char_range.start(), index); | |
| 540 | |
| 541 *end_pos = index; | |
| 542 } | |
| 543 | |
| 544 // Gets the glyph width for |word_range|, and splits the |word| into different | |
| 545 // segments based on its runs. | |
| 546 SkScalar GetWordWidth(const Range& word_range, | |
| 547 std::vector<internal::LineSegment>* segments) const { | |
| 548 DCHECK(words_); | |
| 549 if (word_range.is_empty() || segments == nullptr) | |
| 550 return 0; | |
| 551 size_t run_start_index = run_list_.GetRunIndexAt(word_range.start()); | |
| 552 size_t run_end_index = run_list_.GetRunIndexAt(word_range.end() - 1); | |
| 553 SkScalar width = 0; | |
| 554 for (size_t i = run_start_index; i <= run_end_index; i++) { | |
| 555 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[i]); | |
| 556 const Range char_range = run.range.Intersect(word_range); | |
| 557 DCHECK(!char_range.is_empty()); | |
| 558 const SkScalar char_width = run.GetGlyphWidthForCharRange(char_range); | |
| 559 width += char_width; | |
| 560 | |
| 561 internal::LineSegment segment; | |
| 562 segment.run = i; | |
| 563 segment.char_range = char_range; | |
| 564 segment.width = char_width; | |
| 565 segment.x_range = Range(SkScalarCeilToInt(text_x_ + width - char_width), | |
| 566 SkScalarCeilToInt(text_x_ + width)); | |
| 567 segments->push_back(segment); | |
| 568 } | |
| 569 return width; | |
| 570 } | |
| 571 | |
| 572 // RTL runs are broken in logical order but displayed in visual order. To find | |
| 573 // the text-space coordinate (where it would fall in a single-line text) | |
| 574 // |x_range| of RTL segments, segment widths are applied in reverse order. | |
| 575 // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}. | |
| 576 void UpdateRTLSegmentRanges() { | |
| 577 if (rtl_segments_.empty()) | |
| 578 return; | |
| 579 float x = SegmentFromHandle(rtl_segments_[0])->x_range.start(); | |
| 580 for (size_t i = rtl_segments_.size(); i > 0; --i) { | |
| 581 internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]); | |
| 582 const float segment_width = segment->width; | |
| 583 segment->x_range = Range(x, x + segment_width); | |
| 584 x += segment_width; | |
| 585 } | |
| 586 rtl_segments_.clear(); | |
| 471 } | 587 } |
| 472 | 588 |
| 473 const SkScalar max_width_; | 589 const SkScalar max_width_; |
| 474 const int min_baseline_; | 590 const int min_baseline_; |
| 475 const float min_height_; | 591 const float min_height_; |
| 476 const bool multiline_; | |
| 477 const WordWrapBehavior word_wrap_behavior_; | 592 const WordWrapBehavior word_wrap_behavior_; |
| 478 const base::string16& text_; | 593 const base::string16& text_; |
| 479 const BreakList<size_t>* const words_; | 594 const BreakList<size_t>* const words_; |
| 480 const internal::TextRunList& run_list_; | 595 const internal::TextRunList& run_list_; |
| 481 | 596 |
| 482 // Stores the resulting lines. | 597 // Stores the resulting lines. |
| 483 std::vector<internal::Line> lines_; | 598 std::vector<internal::Line> lines_; |
| 484 | 599 |
| 485 // Text space and line space x coordinates of the next segment to be added. | |
| 486 SkScalar text_x_; | |
| 487 SkScalar line_x_; | |
| 488 | |
| 489 float max_descent_; | 600 float max_descent_; |
| 490 float max_ascent_; | 601 float max_ascent_; |
| 491 | 602 |
| 603 // Text space x coordinates of the next segment to be added. | |
| 604 SkScalar text_x_; | |
| 605 // Stores available width in the current line. | |
| 606 SkScalar available_width_; | |
| 607 | |
| 492 // Size of the multiline text, not including the currently processed line. | 608 // Size of the multiline text, not including the currently processed line. |
| 493 SizeF total_size_; | 609 SizeF total_size_; |
| 494 | 610 |
| 495 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. | 611 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. |
| 496 std::vector<SegmentHandle> rtl_segments_; | 612 std::vector<SegmentHandle> rtl_segments_; |
| 497 | 613 |
| 498 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker); | 614 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker); |
| 499 }; | 615 }; |
| 500 | 616 |
| 501 // Function object for case insensitive string comparison. | 617 // Function object for case insensitive string comparison. |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 525 baseline_offset(0), | 641 baseline_offset(0), |
| 526 baseline_type(0), | 642 baseline_type(0), |
| 527 font_style(0), | 643 font_style(0), |
| 528 strike(false), | 644 strike(false), |
| 529 diagonal_strike(false), | 645 diagonal_strike(false), |
| 530 underline(false) { | 646 underline(false) { |
| 531 } | 647 } |
| 532 | 648 |
| 533 TextRunHarfBuzz::~TextRunHarfBuzz() {} | 649 TextRunHarfBuzz::~TextRunHarfBuzz() {} |
| 534 | 650 |
| 535 void TextRunHarfBuzz::GetClusterAt(size_t pos, | |
| 536 Range* chars, | |
| 537 Range* glyphs) const { | |
| 538 DCHECK(range.Contains(Range(pos, pos + 1))); | |
| 539 DCHECK(chars); | |
| 540 DCHECK(glyphs); | |
| 541 | |
| 542 if (glyph_count == 0) { | |
| 543 *chars = range; | |
| 544 *glyphs = Range(); | |
| 545 return; | |
| 546 } | |
| 547 | |
| 548 if (is_rtl) { | |
| 549 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(), | |
| 550 true, chars, glyphs); | |
| 551 return; | |
| 552 } | |
| 553 | |
| 554 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(), | |
| 555 false, chars, glyphs); | |
| 556 } | |
| 557 | |
| 558 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const { | 651 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const { |
| 559 DCHECK(range.Contains(char_range)); | 652 DCHECK(range.Contains(char_range)); |
| 560 DCHECK(!char_range.is_reversed()); | 653 DCHECK(!char_range.is_reversed()); |
| 561 DCHECK(!char_range.is_empty()); | 654 DCHECK(!char_range.is_empty()); |
| 562 | 655 |
| 563 Range start_glyphs; | 656 Range start_glyphs; |
| 564 Range end_glyphs; | 657 Range end_glyphs; |
| 565 Range temp_range; | 658 Range temp_range; |
| 566 GetClusterAt(char_range.start(), &temp_range, &start_glyphs); | 659 GetClusterAt(char_range.start(), &temp_range, &start_glyphs); |
| 567 GetClusterAt(char_range.end() - 1, &temp_range, &end_glyphs); | 660 GetClusterAt(char_range.end() - 1, &temp_range, &end_glyphs); |
| 568 | 661 |
| 569 return is_rtl ? Range(end_glyphs.start(), start_glyphs.end()) : | 662 return is_rtl ? Range(end_glyphs.start(), start_glyphs.end()) : |
| 570 Range(start_glyphs.start(), end_glyphs.end()); | 663 Range(start_glyphs.start(), end_glyphs.end()); |
| 571 } | 664 } |
| 572 | 665 |
| 573 size_t TextRunHarfBuzz::CountMissingGlyphs() const { | 666 size_t TextRunHarfBuzz::CountMissingGlyphs() const { |
| 574 static const int kMissingGlyphId = 0; | 667 static const int kMissingGlyphId = 0; |
| 575 size_t missing = 0; | 668 size_t missing = 0; |
| 576 for (size_t i = 0; i < glyph_count; ++i) | 669 for (size_t i = 0; i < glyph_count; ++i) |
| 577 missing += (glyphs[i] == kMissingGlyphId) ? 1 : 0; | 670 missing += (glyphs[i] == kMissingGlyphId) ? 1 : 0; |
| 578 return missing; | 671 return missing; |
| 579 } | 672 } |
| 580 | 673 |
| 674 void TextRunHarfBuzz::GetClusterAt(size_t pos, | |
| 675 Range* chars, | |
| 676 Range* glyphs) const { | |
| 677 DCHECK(range.Contains(Range(pos, pos + 1))); | |
| 678 DCHECK(chars); | |
| 679 DCHECK(glyphs); | |
| 680 | |
| 681 if (glyph_count == 0) { | |
| 682 *chars = range; | |
| 683 *glyphs = Range(); | |
| 684 return; | |
| 685 } | |
| 686 | |
| 687 if (is_rtl) { | |
| 688 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(), | |
| 689 true, chars, glyphs); | |
| 690 return; | |
| 691 } | |
| 692 | |
| 693 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(), | |
| 694 false, chars, glyphs); | |
| 695 } | |
| 696 | |
| 581 RangeF TextRunHarfBuzz::GetGraphemeBounds( | 697 RangeF TextRunHarfBuzz::GetGraphemeBounds( |
| 582 base::i18n::BreakIterator* grapheme_iterator, | 698 base::i18n::BreakIterator* grapheme_iterator, |
| 583 size_t text_index) { | 699 size_t text_index) { |
| 584 DCHECK_LT(text_index, range.end()); | 700 DCHECK_LT(text_index, range.end()); |
| 585 if (glyph_count == 0) | 701 if (glyph_count == 0) |
| 586 return RangeF(preceding_run_widths, preceding_run_widths + width); | 702 return RangeF(preceding_run_widths, preceding_run_widths + width); |
| 587 | 703 |
| 588 Range chars; | 704 Range chars; |
| 589 Range glyphs; | 705 Range glyphs; |
| 590 GetClusterAt(text_index, &chars, &glyphs); | 706 GetClusterAt(text_index, &chars, &glyphs); |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 619 cluster_width * (before + 1) / static_cast<float>(total)); | 735 cluster_width * (before + 1) / static_cast<float>(total)); |
| 620 return RangeF(preceding_run_widths + grapheme_begin_x, | 736 return RangeF(preceding_run_widths + grapheme_begin_x, |
| 621 preceding_run_widths + grapheme_end_x); | 737 preceding_run_widths + grapheme_end_x); |
| 622 } | 738 } |
| 623 } | 739 } |
| 624 | 740 |
| 625 return RangeF(preceding_run_widths + cluster_begin_x, | 741 return RangeF(preceding_run_widths + cluster_begin_x, |
| 626 preceding_run_widths + cluster_end_x); | 742 preceding_run_widths + cluster_end_x); |
| 627 } | 743 } |
| 628 | 744 |
| 745 SkScalar TextRunHarfBuzz::GetGlyphWidthForCharRange( | |
| 746 const Range& char_range) const { | |
| 747 DCHECK(range.Contains(char_range)); | |
| 748 Range glyph_range = CharRangeToGlyphRange(char_range); | |
| 749 return ((glyph_range.end() == glyph_count) | |
| 750 ? SkFloatToScalar(width) | |
| 751 : positions[glyph_range.end()].x()) - | |
| 752 positions[glyph_range.start()].x(); | |
| 753 } | |
| 754 | |
| 629 TextRunList::TextRunList() : width_(0.0f) {} | 755 TextRunList::TextRunList() : width_(0.0f) {} |
| 630 | 756 |
| 631 TextRunList::~TextRunList() {} | 757 TextRunList::~TextRunList() {} |
| 632 | 758 |
| 633 void TextRunList::Reset() { | 759 void TextRunList::Reset() { |
| 634 runs_.clear(); | 760 runs_.clear(); |
| 635 width_ = 0.0f; | 761 width_ = 0.0f; |
| 636 } | 762 } |
| 637 | 763 |
| 638 void TextRunList::InitIndexMap() { | 764 void TextRunList::InitIndexMap() { |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 653 void TextRunList::ComputePrecedingRunWidths() { | 779 void TextRunList::ComputePrecedingRunWidths() { |
| 654 // Precalculate run width information. | 780 // Precalculate run width information. |
| 655 width_ = 0.0f; | 781 width_ = 0.0f; |
| 656 for (size_t i = 0; i < runs_.size(); ++i) { | 782 for (size_t i = 0; i < runs_.size(); ++i) { |
| 657 TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; | 783 TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; |
| 658 run->preceding_run_widths = width_; | 784 run->preceding_run_widths = width_; |
| 659 width_ += run->width; | 785 width_ += run->width; |
| 660 } | 786 } |
| 661 } | 787 } |
| 662 | 788 |
| 789 size_t TextRunList::GetRunIndexAt(size_t position) const { | |
| 790 for (size_t i = 0; i < runs_.size(); ++i) { | |
| 791 if (runs_[i]->range.start() <= position && runs_[i]->range.end() > position) | |
| 792 return i; | |
| 793 } | |
| 794 return runs_.size(); | |
| 795 } | |
| 796 | |
| 663 } // namespace internal | 797 } // namespace internal |
| 664 | 798 |
| 665 RenderTextHarfBuzz::RenderTextHarfBuzz() | 799 RenderTextHarfBuzz::RenderTextHarfBuzz() |
| 666 : RenderText(), | 800 : RenderText(), |
| 667 update_layout_run_list_(false), | 801 update_layout_run_list_(false), |
| 668 update_display_run_list_(false), | 802 update_display_run_list_(false), |
| 669 update_grapheme_iterator_(false), | 803 update_grapheme_iterator_(false), |
| 670 update_display_text_(false), | 804 update_display_text_(false), |
| 671 glyph_width_for_test_(0u) { | 805 glyph_width_for_test_(0u) { |
| 672 set_truncate_length(kMaxTextLength); | 806 set_truncate_length(kMaxTextLength); |
| (...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1001 if (lines().empty()) { | 1135 if (lines().empty()) { |
| 1002 // TODO(ckocagil): Remove ScopedTracker below once crbug.com/441028 is | 1136 // TODO(ckocagil): Remove ScopedTracker below once crbug.com/441028 is |
| 1003 // fixed. | 1137 // fixed. |
| 1004 scoped_ptr<tracked_objects::ScopedTracker> tracking_profile( | 1138 scoped_ptr<tracked_objects::ScopedTracker> tracking_profile( |
| 1005 new tracked_objects::ScopedTracker( | 1139 new tracked_objects::ScopedTracker( |
| 1006 FROM_HERE_WITH_EXPLICIT_FUNCTION("441028 HarfBuzzLineBreaker"))); | 1140 FROM_HERE_WITH_EXPLICIT_FUNCTION("441028 HarfBuzzLineBreaker"))); |
| 1007 | 1141 |
| 1008 internal::TextRunList* run_list = GetRunList(); | 1142 internal::TextRunList* run_list = GetRunList(); |
| 1009 HarfBuzzLineBreaker line_breaker( | 1143 HarfBuzzLineBreaker line_breaker( |
| 1010 display_rect().width(), font_list().GetBaseline(), | 1144 display_rect().width(), font_list().GetBaseline(), |
| 1011 std::max(font_list().GetHeight(), min_line_height()), multiline(), | 1145 std::max(font_list().GetHeight(), min_line_height()), |
| 1012 word_wrap_behavior(), GetDisplayText(), | 1146 word_wrap_behavior(), GetDisplayText(), |
| 1013 multiline() ? &GetLineBreaks() : nullptr, *run_list); | 1147 multiline() ? &GetLineBreaks() : nullptr, *run_list); |
| 1014 | 1148 |
| 1015 tracking_profile.reset(); | 1149 tracking_profile.reset(); |
| 1016 | 1150 |
| 1017 for (size_t i = 0; i < run_list->size(); ++i) | 1151 if (multiline()) |
| 1018 line_breaker.AddRun(i); | 1152 line_breaker.ConstructMultiLines(); |
| 1153 else | |
| 1154 line_breaker.ConstructSingleLine(); | |
| 1019 std::vector<internal::Line> lines; | 1155 std::vector<internal::Line> lines; |
| 1020 line_breaker.Finalize(&lines, &total_size_); | 1156 line_breaker.FinalizeLines(&lines, &total_size_); |
| 1021 set_lines(&lines); | 1157 set_lines(&lines); |
| 1022 } | 1158 } |
| 1023 } | 1159 } |
| 1024 | 1160 |
| 1025 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) { | 1161 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) { |
| 1026 internal::SkiaTextRenderer renderer(canvas); | 1162 internal::SkiaTextRenderer renderer(canvas); |
| 1027 DrawVisualTextInternal(&renderer); | 1163 DrawVisualTextInternal(&renderer); |
| 1028 } | 1164 } |
| 1029 | 1165 |
| 1030 void RenderTextHarfBuzz::DrawVisualTextInternal( | 1166 void RenderTextHarfBuzz::DrawVisualTextInternal( |
| (...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1479 DCHECK(!update_layout_run_list_); | 1615 DCHECK(!update_layout_run_list_); |
| 1480 DCHECK(!update_display_run_list_); | 1616 DCHECK(!update_display_run_list_); |
| 1481 return text_elided() ? display_run_list_.get() : &layout_run_list_; | 1617 return text_elided() ? display_run_list_.get() : &layout_run_list_; |
| 1482 } | 1618 } |
| 1483 | 1619 |
| 1484 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { | 1620 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { |
| 1485 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList(); | 1621 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList(); |
| 1486 } | 1622 } |
| 1487 | 1623 |
| 1488 } // namespace gfx | 1624 } // namespace gfx |
| OLD | NEW |