OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ui/gfx/render_text_harfbuzz.h" | |
6 | |
7 #include <map> | |
8 | |
9 #include "base/i18n/break_iterator.h" | |
10 #include "base/i18n/char_iterator.h" | |
11 #include "third_party/harfbuzz-ng/src/hb-icu.h" | |
12 #include "third_party/harfbuzz-ng/src/hb.h" | |
13 #include "third_party/icu/source/common/unicode/ubidi.h" | |
14 #include "third_party/skia/include/core/SkColor.h" | |
15 #include "third_party/skia/include/core/SkTypeface.h" | |
16 #include "ui/gfx/canvas.h" | |
17 #include "ui/gfx/utf16_indexing.h" | |
18 | |
19 #if defined(OS_WIN) | |
20 #include "ui/gfx/font_smoothing_win.h" | |
21 #endif | |
22 | |
23 namespace gfx { | |
24 | |
25 namespace { | |
26 | |
27 // The maximum number of scripts a unicode character can belong to. | |
28 const size_t kMaxScripts = 5; | |
29 | |
30 typedef std::map<uint32_t, uint16_t> GlyphCache; | |
31 | |
32 // Font data provider for HarfBuzz using Skia. Copied from Blink. | |
33 // TODO(ckocagil): Eliminate the duplication. http://crbug.com/368375 | |
34 struct FontData { | |
35 FontData(GlyphCache* glyph_cache) : glyph_cache_(glyph_cache) {} | |
36 | |
37 SkPaint paint_; | |
38 GlyphCache* glyph_cache_; | |
39 }; | |
40 | |
41 hb_position_t SkiaScalarToHarfBuzzPosition(SkScalar value) { | |
42 return SkScalarToFixed(value); | |
43 } | |
44 | |
45 template<typename Type> | |
46 void DeleteByType(void* data) { | |
47 Type* typed_data = reinterpret_cast<Type*>(data); | |
48 delete typed_data; | |
49 } | |
50 | |
51 template<typename Type> | |
52 void DeleteArrayByType(void* data) { | |
53 Type* typed_data = reinterpret_cast<Type*>(data); | |
54 delete[] typed_data; | |
55 } | |
56 | |
57 void GetGlyphWidthAndExtents(SkPaint* paint, | |
58 hb_codepoint_t codepoint, | |
59 hb_position_t* width, | |
60 hb_glyph_extents_t* extents) { | |
61 CHECK_LE(codepoint, 0xFFFFU); | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Does this need to be a CHECK rather than a DCHECK?
ckocagil
2014/05/16 13:47:25
This is actually supposed to be a DCHECK, I mistra
| |
62 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); | |
63 | |
64 SkScalar sk_width; | |
65 SkRect sk_bounds; | |
66 uint16_t glyph = codepoint; | |
67 | |
68 paint->getTextWidths(&glyph, sizeof(glyph), &sk_width, &sk_bounds); | |
69 if (width) | |
70 *width = SkiaScalarToHarfBuzzPosition(sk_width); | |
71 if (extents) { | |
72 // Invert y-axis because Skia is y-grows-down but we set up HarfBuzz to be | |
73 // y-grows-up. | |
74 extents->x_bearing = SkiaScalarToHarfBuzzPosition(sk_bounds.fLeft); | |
75 extents->y_bearing = SkiaScalarToHarfBuzzPosition(-sk_bounds.fTop); | |
76 extents->width = SkiaScalarToHarfBuzzPosition(sk_bounds.width()); | |
77 extents->height = SkiaScalarToHarfBuzzPosition(-sk_bounds.height()); | |
78 } | |
79 } | |
80 | |
81 hb_bool_t GetGlyph(hb_font_t* font, | |
82 void* data, | |
83 hb_codepoint_t unicode, | |
84 hb_codepoint_t variation_selector, | |
85 hb_codepoint_t* glyph, | |
86 void* user_data) { | |
87 FontData* font_data = reinterpret_cast<FontData*>(data); | |
88 GlyphCache* cache = font_data->glyph_cache_; | |
89 | |
90 bool exists = cache->count(unicode) != 0; | |
91 if (!exists) { | |
92 SkPaint* paint = &font_data->paint_; | |
93 paint->setTextEncoding(SkPaint::kUTF32_TextEncoding); | |
94 paint->textToGlyphs(&unicode, sizeof(hb_codepoint_t), &(*cache)[unicode]); | |
95 } | |
96 *glyph = (*cache)[unicode]; | |
97 return !!*glyph; | |
98 } | |
99 | |
100 hb_position_t GetGlyphHorizontalAdvance(hb_font_t* font, | |
101 void* data, | |
102 hb_codepoint_t glyph, | |
103 void* user_data) { | |
104 FontData* font_data = reinterpret_cast<FontData*>(data); | |
105 hb_position_t advance = 0; | |
106 | |
107 GetGlyphWidthAndExtents(&font_data->paint_, glyph, &advance, 0); | |
108 return advance; | |
109 } | |
110 | |
111 hb_bool_t GetGlyphHorizontalOrigin(hb_font_t* font, | |
112 void* data, | |
113 hb_codepoint_t glyph, | |
114 hb_position_t* x, | |
115 hb_position_t* y, | |
116 void* user_data) { | |
117 // Just return true, like the HarfBuzz-FreeType implementation. | |
118 return true; | |
119 } | |
120 | |
121 hb_bool_t GetGlyphExtents(hb_font_t* font, | |
122 void* data, | |
123 hb_codepoint_t glyph, | |
124 hb_glyph_extents_t* extents, | |
125 void* user_data) { | |
126 FontData* font_data = reinterpret_cast<FontData*>(data); | |
127 | |
128 GetGlyphWidthAndExtents(&font_data->paint_, glyph, 0, extents); | |
129 return true; | |
130 } | |
131 | |
132 hb_font_funcs_t* GetFontFuncs() { | |
133 static hb_font_funcs_t* font_funcs = 0; | |
134 | |
135 // We don't set callback functions which we can't support. | |
136 // HarfBuzz will use the fallback implementation if they aren't set. | |
137 // TODO(ckocagil): Merge Blink's kerning funcs. | |
138 if (!font_funcs) { | |
139 font_funcs = hb_font_funcs_create(); | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Is this intentionally leaked? If so, please add a
ckocagil
2014/05/16 13:47:25
Done.
| |
140 hb_font_funcs_set_glyph_func(font_funcs, GetGlyph, 0, 0); | |
141 hb_font_funcs_set_glyph_h_advance_func( | |
142 font_funcs, GetGlyphHorizontalAdvance, 0, 0); | |
143 hb_font_funcs_set_glyph_h_origin_func( | |
144 font_funcs, GetGlyphHorizontalOrigin, 0, 0); | |
145 hb_font_funcs_set_glyph_extents_func( | |
146 font_funcs, GetGlyphExtents, 0, 0); | |
147 hb_font_funcs_make_immutable(font_funcs); | |
148 } | |
149 return font_funcs; | |
150 } | |
151 | |
152 hb_blob_t* GetFontTable(hb_face_t* face, hb_tag_t tag, void* user_data) { | |
153 SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data); | |
154 | |
155 const size_t table_size = typeface->getTableSize(tag); | |
156 if (!table_size) | |
157 return 0; | |
158 | |
159 char* buffer = new char[table_size]; | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
How about:
scoped_ptr<char[]> buffer(new char[tab
ckocagil
2014/05/16 13:47:25
Oh, good idea. Done! Sadly I had to assign the rel
| |
160 if (!buffer) | |
161 return 0; | |
162 size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer); | |
163 if (table_size != actual_size) { | |
164 delete[] buffer; | |
165 return 0; | |
166 } | |
167 | |
168 return hb_blob_create(buffer, table_size, HB_MEMORY_MODE_WRITABLE, buffer, | |
169 DeleteArrayByType<char>); | |
170 } | |
171 | |
172 void UnrefSkTypeface(void* data) { | |
173 SkTypeface* skia_face = reinterpret_cast<SkTypeface*>(data); | |
174 SkSafeUnref(skia_face); | |
175 } | |
176 | |
177 hb_face_t* CreateHarfBuzzFace(SkTypeface* skia_face) { | |
178 SkSafeRef(skia_face); | |
179 hb_face_t* face = hb_face_create_for_tables(GetFontTable, skia_face, | |
180 UnrefSkTypeface); | |
181 CHECK(face); | |
182 return face; | |
183 } | |
184 | |
185 hb_font_t* CreateHarfBuzzFont(SkTypeface* skia_face, int text_size) { | |
186 typedef std::pair<hb_face_t*, GlyphCache> FaceCache; | |
187 | |
188 // TODO(ckocagil): This shouldn't grow indefinitely. Maybe use base::MRUCache? | |
189 static std::map<SkFontID, FaceCache> face_caches; | |
190 | |
191 FaceCache* face_cache = &face_caches[skia_face->uniqueID()]; | |
192 if (face_cache->first == 0) { | |
193 hb_face_t* harfbuzz_face = CreateHarfBuzzFace(skia_face); | |
194 *face_cache = FaceCache(harfbuzz_face, GlyphCache()); | |
195 } | |
196 | |
197 hb_font_t* harfbuzz_font = hb_font_create(face_cache->first); | |
198 // TODO(ckocagil): Investigate whether disabling hinting here has any effect | |
199 // on text quality. | |
200 int upem = hb_face_get_upem(face_cache->first); | |
201 hb_font_set_scale(harfbuzz_font, upem, upem); | |
202 FontData* hb_font_data = new FontData(&face_cache->second); | |
203 hb_font_data->paint_.setTypeface(skia_face); | |
204 hb_font_data->paint_.setTextSize(text_size); | |
205 hb_font_set_funcs(harfbuzz_font, GetFontFuncs(), hb_font_data, | |
206 DeleteByType<FontData>); | |
207 hb_font_make_immutable(harfbuzz_font); | |
208 return harfbuzz_font; | |
209 } | |
210 | |
211 size_t CharToGlyph(const internal::TextRunHarfBuzz& run, size_t pos) { | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Can you add short 1-line comments for all these fu
ckocagil
2014/05/16 13:47:25
Done.
| |
212 DCHECK(run.range.start() <= pos && pos < run.range.end()); | |
213 | |
214 if (run.direction == UBIDI_LTR) { | |
215 for (size_t i = 0; i < run.glyph_count - 1; ++i) { | |
216 if (pos < run.glyph_to_char[i + 1]) | |
217 return i; | |
218 } | |
219 return run.glyph_count - 1; | |
220 } | |
221 | |
222 for (size_t i = 0; i < run.glyph_count; ++i) { | |
223 if (pos >= run.glyph_to_char[i]) | |
224 return i; | |
225 } | |
226 NOTREACHED(); | |
227 return 0; | |
228 } | |
229 | |
230 // Returns the corresponding glyph range of the given character range. | |
231 // |range| is in text-space (0 corresponds to |GetLayoutText()[0]|). | |
232 // Returned value is in run-space (0 corresponds to the first glyph in the run). | |
233 Range CharRangeToGlyphRange(const internal::TextRunHarfBuzz& run, | |
234 const Range& range) { | |
235 DCHECK(run.range.Contains(range)); | |
236 DCHECK(!range.is_reversed()); | |
237 DCHECK(!range.is_empty()); | |
238 | |
239 const size_t first = CharToGlyph(run, range.start()); | |
240 const size_t last = CharToGlyph(run, range.end() - 1); | |
241 return Range(std::min(first, last), std::max(first, last) + 1); | |
242 } | |
243 | |
244 // Returns true if characters of |block_code| may trigger font fallback. | |
245 bool IsUnusualBlockCode(const UBlockCode block_code) { | |
246 return block_code == UBLOCK_GEOMETRIC_SHAPES || | |
247 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS; | |
248 } | |
249 | |
250 bool HasMissingGlyphs(const internal::TextRunHarfBuzz& run) { | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Can this be a method on TextRunHarfbuzz rather tha
ckocagil
2014/05/16 13:47:25
Good idea, but should we make it a proper class wi
Alexei Svitkine (slow)
2014/05/16 18:22:52
I think it's OK to have a struct with a few method
ckocagil
2014/05/20 18:35:10
Done, moved these to the struct.
| |
251 static const int kMissingGlyphId = 0; | |
252 for (size_t i = 0; i < run.glyph_count; ++i) { | |
253 if (run.glyphs[i] == kMissingGlyphId) | |
254 return true; | |
255 } | |
256 return false; | |
257 } | |
258 | |
259 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { | |
260 if (first == second || | |
261 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED)) { | |
262 return first; | |
263 } | |
264 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED) | |
265 return second; | |
266 return USCRIPT_INVALID_CODE; | |
267 } | |
268 | |
269 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { | |
270 UErrorCode icu_error = U_ZERO_ERROR; | |
271 // ICU documentation incorrectly states that the result of | |
272 // |uscript_getScriptExtensions| will contain the regular script property. | |
273 // Call |uscript_getScript| to get the script property. | |
274 scripts[0] = uscript_getScript(codepoint, &icu_error); | |
275 if (U_FAILURE(icu_error)) | |
276 return 0; | |
277 int count = uscript_getScriptExtensions(codepoint, scripts + 1, | |
278 kMaxScripts - 1, &icu_error); | |
279 if (U_FAILURE(icu_error)) | |
280 count = 0; | |
281 return count + 1; | |
282 } | |
283 | |
284 // Intersects the script extensions set of |codepoint| with |result| and writes | |
285 // to |result|, reading and updating |result_size|. | |
286 void ScriptSetIntersect(UChar32 codepoint, | |
287 UScriptCode* result, | |
288 size_t* result_size) { | |
289 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; | |
290 int count = GetScriptExtensions(codepoint, scripts); | |
291 | |
292 UScriptCode* out = result; | |
293 size_t out_size = 0; | |
294 | |
295 for (size_t i = 0; i < *result_size; ++i) { | |
296 for (int j = 0; j < count; ++j) { | |
297 UScriptCode intersection = ScriptIntersect(result[i], scripts[j]); | |
298 if (intersection != USCRIPT_INVALID_CODE) { | |
299 *out++ = intersection; | |
300 ++out_size; | |
301 break; | |
302 } | |
303 } | |
304 } | |
305 | |
306 *result_size = out_size; | |
307 } | |
308 | |
309 // Find the longest sequence of characters from 0 and up to |length| that | |
310 // have at least one common UScriptCode value. Writes the common script value to | |
311 // |script| and returns the length of the sequence. Takes the characters' script | |
312 // extensions into account. http://www.unicode.org/reports/tr24/#ScriptX | |
313 int ScriptInterval(const base::char16* text, | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Can |text| be passed by const ref?
ckocagil
2014/05/16 13:47:25
Done.
| |
314 size_t length, | |
Alexei Svitkine (slow)
2014/05/13 15:23:27
Nit: Align params.
ckocagil
2014/05/16 13:47:25
Done.
| |
315 UScriptCode* script) { | |
316 DCHECK_GE(length, 0U); | |
317 | |
318 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; | |
319 | |
320 base::i18n::UTF16CharIterator char_iterator(text, length); | |
321 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); | |
322 *script = scripts[0]; | |
323 | |
324 while (char_iterator.Advance()) { | |
325 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); | |
326 if (scripts_size > 0U) | |
327 *script = scripts[0]; | |
328 else | |
329 return char_iterator.array_pos(); | |
330 } | |
331 | |
332 return length; | |
333 } | |
334 | |
335 } // namespace | |
336 | |
337 namespace internal { | |
338 | |
339 TextRunHarfBuzz::TextRunHarfBuzz() | |
340 : width(0), | |
341 preceding_run_widths(0), | |
342 direction(UBIDI_LTR), | |
343 level(0), | |
344 script(USCRIPT_INVALID_CODE), | |
345 glyph_count(-1), | |
346 font_size(0), | |
347 font_style(0), | |
348 strike(false), | |
349 diagonal_strike(false), | |
350 underline(false) {} | |
351 | |
352 TextRunHarfBuzz::~TextRunHarfBuzz() {} | |
353 | |
354 } // namespace internal | |
355 | |
356 RenderTextHarfBuzz::RenderTextHarfBuzz() | |
357 : RenderText(), | |
358 needs_layout_(false) {} | |
359 | |
360 RenderTextHarfBuzz::~RenderTextHarfBuzz() {} | |
361 | |
362 Size RenderTextHarfBuzz::GetStringSize() { | |
363 EnsureLayout(); | |
364 return Size(lines()[0].size.width(), font_list().GetHeight()); | |
365 } | |
366 | |
367 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) { | |
368 EnsureLayout(); | |
369 | |
370 int x = ToTextPoint(point).x(); | |
371 int offset = 0; | |
372 size_t run_index = GetRunContainingXCoord(x, &offset); | |
373 if (run_index >= runs_.size()) | |
374 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); | |
375 const internal::TextRunHarfBuzz& run = *runs_[run_index]; | |
376 | |
377 for (size_t i = 0; i < run.glyph_count; ++i) { | |
378 const SkScalar end = | |
379 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x(); | |
380 const SkScalar middle = (end + run.positions[i].x()) / 2; | |
381 const bool is_rtl = run.direction == UBIDI_RTL; | |
382 if (offset < middle) { | |
383 return SelectionModel(LayoutIndexToTextIndex( | |
384 run.glyph_to_char[i] + (is_rtl ? 1 : 0)), | |
385 (is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD)); | |
386 } | |
387 if (offset < end) { | |
388 return SelectionModel(LayoutIndexToTextIndex( | |
389 run.glyph_to_char[i] + (is_rtl ? 0 : 1)), | |
390 (is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD)); | |
391 } | |
392 } | |
393 return EdgeSelectionModel(CURSOR_RIGHT); | |
394 } | |
395 | |
396 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() { | |
397 NOTIMPLEMENTED(); | |
398 return std::vector<RenderText::FontSpan>(); | |
399 } | |
400 | |
401 int RenderTextHarfBuzz::GetLayoutTextBaseline() { | |
402 EnsureLayout(); | |
403 return lines()[0].baseline; | |
404 } | |
405 | |
406 SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel( | |
407 const SelectionModel& selection, | |
408 VisualCursorDirection direction) { | |
409 DCHECK(!needs_layout_); | |
410 internal::TextRunHarfBuzz* run; | |
411 size_t run_index = GetRunContainingCaret(selection); | |
412 if (run_index >= runs_.size()) { | |
413 // The cursor is not in any run: we're at the visual and logical edge. | |
414 SelectionModel edge = EdgeSelectionModel(direction); | |
415 if (edge.caret_pos() == selection.caret_pos()) | |
416 return edge; | |
417 int visual_index = (direction == CURSOR_RIGHT) ? 0 : runs_.size() - 1; | |
418 run = runs_[visual_to_logical_[visual_index]]; | |
419 } else { | |
420 // If the cursor is moving within the current run, just move it by one | |
421 // grapheme in the appropriate direction. | |
422 run = runs_[run_index]; | |
423 size_t caret = selection.caret_pos(); | |
424 bool forward_motion = | |
425 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT); | |
426 if (forward_motion) { | |
427 if (caret < LayoutIndexToTextIndex(run->range.end())) { | |
428 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); | |
429 return SelectionModel(caret, CURSOR_BACKWARD); | |
430 } | |
431 } else { | |
432 if (caret > LayoutIndexToTextIndex(run->range.start())) { | |
433 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); | |
434 return SelectionModel(caret, CURSOR_FORWARD); | |
435 } | |
436 } | |
437 // The cursor is at the edge of a run; move to the visually adjacent run. | |
438 int visual_index = logical_to_visual_[run_index]; | |
439 visual_index += (direction == CURSOR_LEFT) ? -1 : 1; | |
440 if (visual_index < 0 || visual_index >= static_cast<int>(runs_.size())) | |
441 return EdgeSelectionModel(direction); | |
442 run = runs_[visual_to_logical_[visual_index]]; | |
443 } | |
444 bool forward_motion = | |
445 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT); | |
446 return forward_motion ? FirstSelectionModelInsideRun(run) : | |
447 LastSelectionModelInsideRun(run); | |
448 } | |
449 | |
450 SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel( | |
451 const SelectionModel& selection, | |
452 VisualCursorDirection direction) { | |
453 // TODO(ckocagil): This implementation currently matches RenderTextWin, but it | |
454 // should match the native behavior on other platforms. | |
455 if (obscured()) | |
456 return EdgeSelectionModel(direction); | |
457 | |
458 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
459 bool success = iter.Init(); | |
460 DCHECK(success); | |
461 if (!success) | |
462 return selection; | |
463 | |
464 size_t pos; | |
465 if (direction == CURSOR_RIGHT) { | |
466 pos = std::min(selection.caret_pos() + 1, text().length()); | |
467 while (iter.Advance()) { | |
468 pos = iter.pos(); | |
469 if (iter.IsWord() && pos > selection.caret_pos()) | |
470 break; | |
471 } | |
472 } else { // direction == CURSOR_LEFT | |
473 // Notes: We always iterate words from the beginning. | |
474 // This is probably fast enough for our usage, but we may | |
475 // want to modify WordIterator so that it can start from the | |
476 // middle of string and advance backwards. | |
477 pos = std::max<int>(selection.caret_pos() - 1, 0); | |
478 while (iter.Advance()) { | |
479 if (iter.IsWord()) { | |
480 size_t begin = iter.pos() - iter.GetString().length(); | |
481 if (begin == selection.caret_pos()) { | |
482 // The cursor is at the beginning of a word. | |
483 // Move to previous word. | |
484 break; | |
485 } else if (iter.pos() >= selection.caret_pos()) { | |
486 // The cursor is in the middle or at the end of a word. | |
487 // Move to the top of current word. | |
488 pos = begin; | |
489 break; | |
490 } | |
491 pos = iter.pos() - iter.GetString().length(); | |
492 } | |
493 } | |
494 } | |
495 return SelectionModel(pos, CURSOR_FORWARD); | |
496 } | |
497 | |
498 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) { | |
499 const size_t run_index = | |
500 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); | |
501 // Return edge bounds if the index is invalid or beyond the layout text size. | |
502 if (run_index >= runs_.size()) | |
503 return Range(GetStringSize().width()); | |
504 const size_t layout_index = TextIndexToLayoutIndex(index); | |
505 return Range(GetGlyphXBoundary(run_index, layout_index, false), | |
506 GetGlyphXBoundary(run_index, layout_index, true)); | |
507 } | |
508 | |
509 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { | |
510 DCHECK(!needs_layout_); | |
511 DCHECK(Range(0, text().length()).Contains(range)); | |
512 Range layout_range(TextIndexToLayoutIndex(range.start()), | |
513 TextIndexToLayoutIndex(range.end())); | |
514 DCHECK(Range(0, GetLayoutText().length()).Contains(layout_range)); | |
515 | |
516 std::vector<Rect> rects; | |
517 if (layout_range.is_empty()) | |
518 return rects; | |
519 std::vector<Range> bounds; | |
520 | |
521 // Add a Range for each run/selection intersection. | |
522 // TODO(msw): The bounds should probably not always be leading the range ends. | |
523 for (size_t i = 0; i < runs_.size(); ++i) { | |
524 const internal::TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; | |
525 Range intersection = run->range.Intersect(layout_range); | |
526 if (intersection.IsValid()) { | |
527 DCHECK(!intersection.is_reversed()); | |
528 Range range_x(GetGlyphXBoundary(i, intersection.start(), false), | |
529 GetGlyphXBoundary(i, intersection.end(), false)); | |
530 if (range_x.is_empty()) | |
531 continue; | |
532 range_x = Range(range_x.GetMin(), range_x.GetMax()); | |
533 // Union this with the last range if they're adjacent. | |
534 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin()); | |
535 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) { | |
536 range_x = Range(bounds.back().GetMin(), range_x.GetMax()); | |
537 bounds.pop_back(); | |
538 } | |
539 bounds.push_back(range_x); | |
540 } | |
541 } | |
542 for (size_t i = 0; i < bounds.size(); ++i) { | |
543 std::vector<Rect> current_rects = TextBoundsToViewBounds(bounds[i]); | |
544 rects.insert(rects.end(), current_rects.begin(), current_rects.end()); | |
545 } | |
546 return rects; | |
547 } | |
548 | |
549 size_t RenderTextHarfBuzz::TextIndexToLayoutIndex(size_t index) const { | |
550 DCHECK_LE(index, text().length()); | |
551 ptrdiff_t i = obscured() ? UTF16IndexToOffset(text(), 0, index) : index; | |
552 CHECK_GE(i, 0); | |
553 // Clamp layout indices to the length of the text actually used for layout. | |
554 return std::min<size_t>(GetLayoutText().length(), i); | |
555 } | |
556 | |
557 size_t RenderTextHarfBuzz::LayoutIndexToTextIndex(size_t index) const { | |
558 if (!obscured()) | |
559 return index; | |
560 | |
561 DCHECK_LE(index, GetLayoutText().length()); | |
562 const size_t text_index = UTF16OffsetToIndex(text(), 0, index); | |
563 DCHECK_LE(text_index, text().length()); | |
564 return text_index; | |
565 } | |
566 | |
567 bool RenderTextHarfBuzz::IsValidCursorIndex(size_t index) { | |
568 if (index == 0 || index == text().length()) | |
569 return true; | |
570 if (!IsValidLogicalIndex(index)) | |
571 return false; | |
572 EnsureLayout(); | |
573 // Disallow indices amid multi-character graphemes by checking glyph bounds. | |
574 // These characters are not surrogate-pairs, but may yield a single glyph: | |
575 // \x0915\x093f - (ki) - one of many Devanagari biconsonantal conjuncts. | |
576 // \x0e08\x0e33 - (cho chan + sara am) - a Thai consonant and vowel pair. | |
577 return GetGlyphBounds(index) != GetGlyphBounds(index - 1); | |
578 } | |
579 | |
580 void RenderTextHarfBuzz::ResetLayout() { | |
581 needs_layout_ = true; | |
582 } | |
583 | |
584 void RenderTextHarfBuzz::EnsureLayout() { | |
585 if (needs_layout_) { | |
586 runs_.clear(); | |
587 | |
588 if (!GetLayoutText().empty()) { | |
589 ItemizeText(); | |
590 | |
591 for (size_t i = 0; i < runs_.size(); ++i) | |
592 ShapeRun(runs_[i]); | |
593 | |
594 // Precalculate run width information. | |
595 size_t preceding_run_widths = 0; | |
596 for (size_t i = 0; i < runs_.size(); ++i) { | |
597 internal::TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; | |
598 run->preceding_run_widths = preceding_run_widths; | |
599 preceding_run_widths += run->width; | |
600 } | |
601 } | |
602 | |
603 needs_layout_ = false; | |
604 std::vector<internal::Line> empty_lines; | |
605 set_lines(&empty_lines); | |
606 } | |
607 | |
608 if (lines().empty()) { | |
609 std::vector<internal::Line> lines; | |
610 lines.push_back(internal::Line()); | |
611 | |
612 int current_x = 0; | |
613 SkPaint paint; | |
614 | |
615 for (size_t i = 0; i < runs_.size(); ++i) { | |
616 const internal::TextRunHarfBuzz& run = *runs_[visual_to_logical_[i]]; | |
617 internal::LineSegment segment; | |
618 segment.x_range = Range(current_x, current_x + run.width); | |
619 segment.char_range = run.range; | |
620 segment.run = i; | |
621 lines[0].segments.push_back(segment); | |
622 | |
623 paint.setTypeface(run.skia_face.get()); | |
624 paint.setTextSize(run.font_size); | |
625 SkPaint::FontMetrics metrics; | |
626 paint.getFontMetrics(&metrics); | |
627 | |
628 lines[0].size.set_width(lines[0].size.width() + run.width); | |
629 lines[0].size.set_height(std::max(lines[0].size.height(), | |
630 SkScalarRoundToInt(metrics.fDescent - metrics.fAscent))); | |
631 lines[0].baseline = std::max(lines[0].baseline, | |
632 SkScalarRoundToInt(-metrics.fAscent)); | |
633 } | |
634 | |
635 set_lines(&lines); | |
636 } | |
637 } | |
638 | |
639 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) { | |
640 DCHECK(!needs_layout_); | |
641 | |
642 int current_x = 0; | |
643 | |
644 internal::SkiaTextRenderer renderer(canvas); | |
645 ApplyFadeEffects(&renderer); | |
646 ApplyTextShadows(&renderer); | |
647 | |
648 #if defined(OS_WIN) | |
649 bool smoothing_enabled; | |
650 bool cleartype_enabled; | |
651 GetCachedFontSmoothingSettings(&smoothing_enabled, &cleartype_enabled); | |
652 // Note that |cleartype_enabled| corresponds to Skia's |enable_lcd_text|. | |
653 renderer.SetFontSmoothingSettings( | |
654 smoothing_enabled, cleartype_enabled && !background_is_transparent(), | |
655 smoothing_enabled /* subpixel_positioning */); | |
656 #endif | |
657 | |
658 ApplyCompositionAndSelectionStyles(); | |
659 | |
660 const Vector2d line_offset = GetLineOffset(0); | |
661 | |
662 for (size_t i = 0; i < runs_.size(); ++i) { | |
663 const internal::TextRunHarfBuzz& run = *runs_[visual_to_logical_[i]]; | |
664 renderer.SetTypeface(run.skia_face.get()); | |
665 renderer.SetTextSize(run.font_size); | |
666 | |
667 canvas->Save(); | |
668 Vector2d origin = line_offset + Vector2d(current_x, lines()[0].baseline); | |
669 canvas->Translate(origin); | |
670 | |
671 for (BreakList<SkColor>::const_iterator it = | |
672 colors().GetBreak(run.range.start()); | |
673 it != colors().breaks().end() && it->first < run.range.end(); | |
674 ++it) { | |
675 const Range intersection = colors().GetRange(it).Intersect(run.range); | |
676 const Range colored_glyphs = CharRangeToGlyphRange(run, intersection); | |
677 // The range may be empty if a portion of a multi-character grapheme is | |
678 // selected, yielding two colors for a single glyph. For now, this just | |
679 // paints the glyph with a single style, but it should paint it twice, | |
680 // clipped according to selection bounds. See http://crbug.com/366786 | |
681 if (colored_glyphs.is_empty()) | |
682 continue; | |
683 | |
684 renderer.SetForegroundColor(it->second); | |
685 renderer.DrawPosText(&run.positions[colored_glyphs.start()], | |
686 &run.glyphs[colored_glyphs.start()], | |
687 colored_glyphs.length()); | |
688 int width = (colored_glyphs.end() == run.glyph_count ? run.width : | |
689 run.positions[colored_glyphs.end()].x()) - | |
690 run.positions[colored_glyphs.start()].x(); | |
691 renderer.DrawDecorations(0, 0, width, run.underline, run.strike, | |
692 run.diagonal_strike); | |
693 } | |
694 | |
695 canvas->Restore(); | |
696 current_x += run.width; | |
697 } | |
698 | |
699 renderer.EndDiagonalStrike(); | |
700 | |
701 UndoCompositionAndSelectionStyles(); | |
702 } | |
703 | |
704 size_t RenderTextHarfBuzz::GetRunContainingCaret( | |
705 const SelectionModel& caret) const { | |
706 DCHECK(!needs_layout_); | |
707 size_t layout_position = TextIndexToLayoutIndex(caret.caret_pos()); | |
708 LogicalCursorDirection affinity = caret.caret_affinity(); | |
709 for (size_t run = 0; run < runs_.size(); ++run) { | |
710 if (RangeContainsCaret(runs_[run]->range, layout_position, affinity)) | |
711 return run; | |
712 } | |
713 return runs_.size(); | |
714 } | |
715 | |
716 size_t RenderTextHarfBuzz::GetRunContainingXCoord(int x, int* offset) const { | |
717 DCHECK(!needs_layout_); | |
718 if (x < 0) | |
719 return runs_.size(); | |
720 // Find the text run containing the argument point (assumed already offset). | |
721 int current_x = 0; | |
722 for (size_t i = 0; i < runs_.size(); ++i) { | |
723 size_t run = visual_to_logical_[i]; | |
724 current_x += runs_[run]->width; | |
725 if (x < current_x) { | |
726 *offset = x - (current_x - runs_[run]->width); | |
727 return run; | |
728 } | |
729 } | |
730 return runs_.size(); | |
731 } | |
732 | |
733 int RenderTextHarfBuzz::GetGlyphXBoundary(size_t run_index, | |
734 size_t text_index, | |
735 bool trailing) { | |
736 const internal::TextRunHarfBuzz& run = *runs_[run_index]; | |
737 int x = run.preceding_run_widths; | |
738 | |
739 Range glyph_range; | |
740 if (text_index == run.range.end()) { | |
741 trailing = true; | |
742 glyph_range = run.direction == UBIDI_LTR ? | |
743 Range(run.glyph_count - 1, run.glyph_count) : Range(0, 1); | |
744 } else { | |
745 glyph_range = CharRangeToGlyphRange(run, Range(text_index, text_index + 1)); | |
746 } | |
747 const int trailing_step = trailing ? 1 : 0; | |
748 const size_t glyph_pos = glyph_range.start() + | |
749 (run.direction == UBIDI_LTR ? trailing_step : (1 - trailing_step)); | |
750 x += glyph_pos < run.glyph_count ? | |
751 SkScalarRoundToInt(run.positions[glyph_pos].x()) : run.width; | |
752 return x; | |
753 } | |
754 | |
755 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( | |
756 const internal::TextRunHarfBuzz* run) { | |
757 size_t position = LayoutIndexToTextIndex(run->range.start()); | |
758 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); | |
759 return SelectionModel(position, CURSOR_BACKWARD); | |
760 } | |
761 | |
762 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( | |
763 const internal::TextRunHarfBuzz* run) { | |
764 size_t position = LayoutIndexToTextIndex(run->range.end()); | |
765 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); | |
766 return SelectionModel(position, CURSOR_FORWARD); | |
767 } | |
768 | |
769 void RenderTextHarfBuzz::ItemizeText() { | |
770 const base::string16& text = GetLayoutText(); | |
771 const bool is_rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT; | |
772 DCHECK_NE(0U, text.length()); | |
773 | |
774 bool fake_runs = false; | |
775 UErrorCode result = U_ZERO_ERROR; | |
776 | |
777 UBiDi* line = ubidi_openSized(text.length(), 0, &result); | |
778 if (U_FAILURE(result)) { | |
779 NOTREACHED(); | |
780 fake_runs = true; | |
781 } else { | |
782 ubidi_setPara(line, text.c_str(), text.length(), | |
783 is_rtl ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, | |
784 &result); | |
785 if (U_FAILURE(result)) { | |
786 NOTREACHED(); | |
787 fake_runs = true; | |
788 } | |
789 } | |
790 | |
791 // Temporarily apply composition underlines and selection colors. | |
792 ApplyCompositionAndSelectionStyles(); | |
793 | |
794 // Build the list of runs from the script items and ranged styles. Use an | |
795 // empty color BreakList to avoid breaking runs at color boundaries. | |
796 BreakList<SkColor> empty_colors; | |
797 empty_colors.SetMax(text.length()); | |
798 internal::StyleIterator style(empty_colors, styles()); | |
799 | |
800 for (size_t run_break = 0; run_break < text.length();) { | |
801 internal::TextRunHarfBuzz* run = new internal::TextRunHarfBuzz; | |
802 run->range.set_start(run_break); | |
803 run->font_style = (style.style(BOLD) ? Font::BOLD : 0) | | |
804 (style.style(ITALIC) ? Font::ITALIC : 0); | |
805 run->strike = style.style(STRIKE); | |
806 run->diagonal_strike = style.style(DIAGONAL_STRIKE); | |
807 run->underline = style.style(UNDERLINE); | |
808 | |
809 if (fake_runs) { | |
810 run_break = text.length(); | |
811 } else { | |
812 int32 script_item_break = 0; | |
813 ubidi_getLogicalRun(line, run_break, &script_item_break, &run->level); | |
814 // Find the length and script of this script run. | |
815 script_item_break = ScriptInterval(text.c_str() + run_break, | |
816 script_item_break - run_break, &run->script) + run_break; | |
817 | |
818 // Find the next break and advance the iterators as needed. | |
819 run_break = std::min(static_cast<size_t>(script_item_break), | |
820 TextIndexToLayoutIndex(style.GetRange().end())); | |
821 | |
822 // Break runs adjacent to character substrings in certain code blocks. | |
823 // This avoids using their fallback fonts for more characters than needed, | |
824 // in cases like "\x25B6 Media Title", etc. http://crbug.com/278913 | |
825 if (run_break > run->range.start()) { | |
826 const size_t run_start = run->range.start(); | |
827 const int32 run_length = static_cast<int32>(run_break - run_start); | |
828 base::i18n::UTF16CharIterator iter(text.c_str() + run_start, | |
829 run_length); | |
830 const UBlockCode first_block_code = ublock_getCode(iter.get()); | |
831 const bool first_block_unusual = IsUnusualBlockCode(first_block_code); | |
832 while (iter.Advance() && iter.array_pos() < run_length) { | |
833 const UBlockCode current_block_code = ublock_getCode(iter.get()); | |
834 if (current_block_code != first_block_code && | |
835 (first_block_unusual || IsUnusualBlockCode(current_block_code))) { | |
836 run_break = run_start + iter.array_pos(); | |
837 break; | |
838 } | |
839 } | |
840 } | |
841 } | |
842 | |
843 DCHECK(IsValidCodePointIndex(text, run_break)); | |
844 style.UpdatePosition(LayoutIndexToTextIndex(run_break)); | |
845 run->range.set_end(run_break); | |
846 const UChar* uchar_start = ubidi_getText(line); | |
847 // TODO(ckocagil): Add |ubidi_getBaseDirection| to i18n::BiDiLineIterator | |
848 // and remove the bare ICU use here. | |
849 run->direction = ubidi_getBaseDirection(uchar_start + run->range.start(), | |
850 run->range.length()); | |
851 if (run->direction == UBIDI_NEUTRAL) | |
852 run->direction = is_rtl ? UBIDI_RTL : UBIDI_LTR; | |
853 runs_.push_back(run); | |
854 } | |
855 | |
856 ubidi_close(line); | |
857 | |
858 // Undo the temporarily applied composition underlines and selection colors. | |
859 UndoCompositionAndSelectionStyles(); | |
860 | |
861 const size_t num_runs = runs_.size(); | |
862 scoped_ptr<UBiDiLevel[]> levels(new UBiDiLevel[num_runs]); | |
863 for (size_t i = 0; i < num_runs; ++i) | |
864 levels[i] = runs_[i]->level; | |
865 visual_to_logical_.resize(num_runs); | |
866 ubidi_reorderVisual(levels.get(), num_runs, &visual_to_logical_[0]); | |
867 logical_to_visual_.resize(num_runs); | |
868 ubidi_reorderLogical(levels.get(), num_runs, &logical_to_visual_[0]); | |
869 } | |
870 | |
871 void RenderTextHarfBuzz::ShapeRun(internal::TextRunHarfBuzz* run) { | |
872 const base::string16& text = GetLayoutText(); | |
873 // TODO(ckocagil|yukishiino): Implement font fallback. | |
874 const Font& primary_font = font_list().GetPrimaryFont(); | |
875 run->skia_face = internal::CreateSkiaTypeface(primary_font.GetFontName(), | |
876 run->font_style); | |
877 run->font_size = primary_font.GetFontSize(); | |
878 | |
879 hb_font_t* harfbuzz_font = CreateHarfBuzzFont(run->skia_face.get(), | |
880 run->font_size); | |
881 | |
882 // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz | |
883 // buffer holds our text, run information to be used by the shaping engine, | |
884 // and the resulting glyph data. | |
885 hb_buffer_t* buffer = hb_buffer_create(); | |
886 hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16*>(text.c_str()), | |
887 text.length(), run->range.start(), run->range.length()); | |
888 hb_buffer_set_script(buffer, hb_icu_script_to_script(run->script)); | |
889 hb_buffer_set_direction(buffer, | |
890 run->direction == UBIDI_LTR ? HB_DIRECTION_LTR : HB_DIRECTION_RTL); | |
891 // TODO(ckocagil): Should we determine the actual language? | |
892 hb_buffer_set_language(buffer, hb_language_get_default()); | |
893 | |
894 // Shape the text. | |
895 hb_shape(harfbuzz_font, buffer, NULL, 0); | |
896 | |
897 // Populate the run fields with the resulting glyph data in the buffer. | |
898 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &run->glyph_count); | |
899 hb_glyph_position_t* hb_positions = hb_buffer_get_glyph_positions(buffer, | |
900 NULL); | |
901 run->glyphs.reset(new uint16[run->glyph_count]); | |
902 run->glyph_to_char.reset(new uint32[run->glyph_count]); | |
903 run->positions.reset(new SkPoint[run->glyph_count]); | |
904 for (size_t i = 0; i < run->glyph_count; ++i) { | |
905 run->glyphs[i] = infos[i].codepoint; | |
906 run->glyph_to_char[i] = infos[i].cluster; | |
907 const int x_offset = | |
908 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_offset)); | |
909 const int y_offset = | |
910 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].y_offset)); | |
911 run->positions[i].set(run->width + x_offset, y_offset); | |
912 run->width += | |
913 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_advance)); | |
914 } | |
915 | |
916 hb_buffer_destroy(buffer); | |
917 hb_font_destroy(harfbuzz_font); | |
918 } | |
919 | |
920 } // namespace gfx | |
OLD | NEW |