Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: ui/gfx/render_text_harfbuzz.cc

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

Powered by Google App Engine
This is Rietveld 408576698