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

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: merge 252563003 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* data,
73 hb_codepoint_t unicode,
74 hb_codepoint_t variation_selector,
75 hb_codepoint_t* glyph,
76 void* user_data) {
77 FontData* font_data = reinterpret_cast<FontData*>(data);
78 GlyphCache* cache = font_data->glyph_cache_;
79
80 bool exists = cache->count(unicode) != 0;
81 if (!exists) {
82 SkPaint* paint = &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* data,
92 hb_codepoint_t glyph,
93 void* user_data) {
94 FontData* font_data = reinterpret_cast<FontData*>(data);
95 hb_position_t advance = 0;
96
97 GetGlyphWidthAndExtents(&font_data->paint_, glyph, &advance, 0);
98 return advance;
99 }
100
101 hb_bool_t GetGlyphHorizontalOrigin(hb_font_t* font,
102 void* data,
103 hb_codepoint_t glyph,
104 hb_position_t* x,
105 hb_position_t* y,
106 void* user_data) {
107 // Just return true, like the HarfBuzz-FreeType implementation.
108 return true;
109 }
110
111 hb_bool_t GetGlyphExtents(hb_font_t* font,
112 void* data,
113 hb_codepoint_t glyph,
114 hb_glyph_extents_t* extents,
115 void* user_data) {
116 FontData* font_data = reinterpret_cast<FontData*>(data);
117
118 GetGlyphWidthAndExtents(&font_data->paint_, glyph, 0, extents);
119 return true;
120 }
121
122 hb_font_funcs_t* GetFontFuncs() {
123 static hb_font_funcs_t* font_funcs = 0;
124
125 // We don't set callback functions which we can't support.
126 // HarfBuzz will use the fallback implementation if they aren't set.
127 // TODO(ckocagil): Merge Blink's kerning funcs.
128 if (!font_funcs) {
129 font_funcs = hb_font_funcs_create();
130 hb_font_funcs_set_glyph_func(font_funcs, GetGlyph, 0, 0);
131 hb_font_funcs_set_glyph_h_advance_func(
132 font_funcs, GetGlyphHorizontalAdvance, 0, 0);
133 hb_font_funcs_set_glyph_h_origin_func(
134 font_funcs, GetGlyphHorizontalOrigin, 0, 0);
135 hb_font_funcs_set_glyph_extents_func(
136 font_funcs, GetGlyphExtents, 0, 0);
137 hb_font_funcs_make_immutable(font_funcs);
138 }
139 return font_funcs;
140 }
141
142 hb_blob_t* GetFontTable(hb_face_t* face, hb_tag_t tag, void* user_data) {
143 SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data);
144
145 const size_t table_size = typeface->getTableSize(tag);
146 if (!table_size)
147 return 0;
148
149 char* buffer = reinterpret_cast<char*>(malloc(table_size));
Alexei Svitkine (slow) 2014/05/12 15:58:31 Can you use scoped_ptr for this instead? If not,
ckocagil 2014/05/13 15:03:48 scoped_ptr is not possible since we pass the owner
150 if (!buffer)
151 return 0;
152 size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer);
153 if (table_size != actual_size) {
154 free(buffer);
155 return 0;
156 }
157
158 return hb_blob_create(const_cast<char*>(buffer), table_size,
159 HB_MEMORY_MODE_WRITABLE, buffer, free);
160 }
161
162 template<typename Type>
163 void DeleteByType(void* data) {
164 Type* typed_data = reinterpret_cast<Type*>(data);
165 delete typed_data;
166 }
167
168 void UnrefSkTypeface(void* data) {
169 SkTypeface* skia_face = reinterpret_cast<SkTypeface*>(data);
170 SkSafeUnref(skia_face);
171 }
172
173 hb_face_t* CreateHarfBuzzFace(SkTypeface* skia_face) {
174 SkSafeRef(skia_face);
175 hb_face_t* face = hb_face_create_for_tables(GetFontTable, skia_face,
176 UnrefSkTypeface);
177 CHECK(face);
178 return face;
179 }
180
181 hb_font_t* CreateHarfBuzzFont(SkTypeface* skia_face, int text_size) {
182 typedef std::pair<hb_face_t*, GlyphCache> FaceCache;
183
184 // TODO(ckocagil): This shouldn't grow indefinitely. Maybe use base::MRUCache?
185 static std::map<SkFontID, FaceCache> face_caches;
186
187 FaceCache* face_cache = &face_caches[skia_face->uniqueID()];
188 if (face_cache->first == 0) {
189 hb_face_t* harfbuzz_face = CreateHarfBuzzFace(skia_face);
190 *face_cache = FaceCache(harfbuzz_face, GlyphCache());
191 }
192
193 hb_font_t* harfbuzz_font = hb_font_create(face_cache->first);
194 // TODO(ckocagil): Investigate whether disabling hinting here has any effect
195 // on text quality.
196 unsigned int upem = hb_face_get_upem(face_cache->first);
Alexei Svitkine (slow) 2014/05/12 15:58:31 The params to hb_font_set_scale() seem to be ints,
ckocagil 2014/05/13 15:03:48 Done.
197 hb_font_set_scale(harfbuzz_font, upem, upem);
198 FontData* hb_font_data = new FontData(&face_cache->second);
199 hb_font_data->paint_.setTypeface(skia_face);
200 hb_font_data->paint_.setTextSize(text_size);
201 hb_font_set_funcs(harfbuzz_font, GetFontFuncs(), hb_font_data,
202 DeleteByType<FontData>);
203 hb_font_make_immutable(harfbuzz_font);
204 return harfbuzz_font;
205 }
206
207 size_t CharToGlyph(const internal::TextRunHarfBuzz& run, size_t pos) {
208 DCHECK(run.range.start() <= pos && pos < run.range.end());
209
210 if (run.direction == UBIDI_LTR) {
211 for (size_t i = 0; i < run.glyph_count - 1; ++i)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}, since the body is multi-line.
ckocagil 2014/05/13 15:03:48 Done.
212 if (pos < run.glyph_to_char[i + 1])
213 return i;
214 return run.glyph_count - 1;
215 }
216
217 for (size_t i = 0; i < run.glyph_count; ++i)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}, since the body is multi-line.
ckocagil 2014/05/13 15:03:48 Done.
218 if (pos >= run.glyph_to_char[i])
219 return i;
220 NOTREACHED();
221 return 0;
222 }
223
224 // Returns the corresponding glyph range of the given character range.
225 // |range| is in text-space (0 corresponds to |GetLayoutText()[0]|).
226 // Returned value is in run-space (0 corresponds to the first glyph in the run).
227 Range CharRangeToGlyphRange(const internal::TextRunHarfBuzz& run,
228 const Range& range) {
229 DCHECK(run.range.Contains(range));
230 DCHECK(!range.is_reversed());
231 DCHECK(!range.is_empty());
232
233 const size_t first = CharToGlyph(run, range.start());
234 const size_t last = CharToGlyph(run, range.end() - 1);
235 return Range(std::min(first, last), std::max(first, last) + 1);
236 }
237
238 // Returns true if characters of |block_code| may trigger font fallback.
239 bool IsUnusualBlockCode(const UBlockCode block_code) {
240 return block_code == UBLOCK_GEOMETRIC_SHAPES ||
241 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS;
242 }
243
244 bool HasMissingGlyphs(const internal::TextRunHarfBuzz& run) {
245 static const int kMissingGlyphId = 0;
246 for (size_t i = 0; i < run.glyph_count; ++i)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}'s
ckocagil 2014/05/13 15:03:48 Done.
247 if (run.glyphs[i] == kMissingGlyphId)
248 return true;
249 return false;
250 }
251
252 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) {
253 if (first == second ||
254 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED))
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}'s
ckocagil 2014/05/13 15:03:48 Done.
255 return first;
256 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED)
257 return second;
258 return USCRIPT_INVALID_CODE;
259 }
260
261 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) {
262 UErrorCode icu_error = U_ZERO_ERROR;
263 // ICU documentation incorrectly states that the result of
264 // |uscript_getScriptExtensions| will contain the regular script property.
265 // Call |uscript_getScript| to get the script property.
266 scripts[0] = uscript_getScript(codepoint, &icu_error);
267 if (U_FAILURE(icu_error))
268 return 0;
269 int count = uscript_getScriptExtensions(codepoint, scripts + 1,
270 kMaxScripts - 1, &icu_error);
271 if (U_FAILURE(icu_error))
272 count = 0;
273 return count + 1;
274 }
275
276 // Intersects the script extensions set of |codepoint| with |result| and writes
277 // to |result|, reading and updating |result_size|.
278 void ScriptSetIntersect(UChar32 codepoint,
279 UScriptCode* result,
280 size_t* result_size) {
281 UScriptCode scripts[kMaxScripts] = {USCRIPT_INVALID_CODE};
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: Spaces between the {}'s. Note: I think this
ckocagil 2014/05/13 15:03:48 Done. Yes, none of the values matter, they are re
282 int count = GetScriptExtensions(codepoint, scripts);
283
284 UScriptCode* out = result;
285 size_t out_size = 0;
286
287 for (size_t i = 0; i < *result_size; ++i) {
288 for (int j = 0; j < count; ++j) {
289 UScriptCode intersection = ScriptIntersect(result[i], scripts[j]);
290 if (intersection != USCRIPT_INVALID_CODE) {
291 *out++ = intersection;
292 ++out_size;
293 break;
294 }
295 }
296 }
297
298 *result_size = out_size;
299 }
300
301 // Find the longest sequence of characters from 0 and up to |length| that
302 // have at least one common UScriptCode value. Writes the common script value to
303 // |script| and returns the length of the sequence. Takes the characters' script
304 // extensions into account. http://www.unicode.org/reports/tr24/#ScriptX
305 int ScriptInterval(const base::char16* text,
306 size_t length,
307 UScriptCode* script) {
308 DCHECK_GE(length, 0U);
309
310 UScriptCode scripts[kMaxScripts] = {USCRIPT_INVALID_CODE};
311
312 base::i18n::UTF16CharIterator char_iterator(text, length);
313 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts);
314 *script = scripts[0];
315
316 while (char_iterator.Advance()) {
317 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size);
318 if (scripts_size > 0U)
319 *script = scripts[0];
320 else
321 return char_iterator.array_pos();
322 }
323
324 return length;
325 }
326
327 } // namespace
328
329 namespace internal {
330
331 TextRunHarfBuzz::TextRunHarfBuzz()
332 : width(0),
333 preceding_run_widths(0),
334 direction(UBIDI_LTR),
335 level(0),
336 script(USCRIPT_INVALID_CODE),
337 glyph_count(-1),
338 font_size(0),
339 font_style(0),
340 strike(false),
341 diagonal_strike(false),
342 underline(false) {}
343
344 TextRunHarfBuzz::~TextRunHarfBuzz() {}
345
346 } // namespace internal
347
348 RenderTextHarfBuzz::RenderTextHarfBuzz()
349 : RenderText(),
350 needs_layout_(false) {}
351
352 RenderTextHarfBuzz::~RenderTextHarfBuzz() {}
353
354 Size RenderTextHarfBuzz::GetStringSize() {
355 EnsureLayout();
356 return Size(lines()[0].size.width(), font_list().GetHeight());
357 }
358
359 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) {
360 EnsureLayout();
361
362 int x = ToTextPoint(point).x();
363 int offset = 0;
364 size_t run_index = GetRunContainingXCoord(x, &offset);
365 if (run_index >= runs_.size())
366 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT);
367 const internal::TextRunHarfBuzz& run = *runs_[run_index];
368
369 for (size_t i = 0; i < run.glyph_count; ++i) {
370 const SkScalar end =
371 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x();
372 const SkScalar middle = (end + run.positions[i].x()) / 2;
373 const bool is_rtl = run.direction == UBIDI_RTL;
374 if (offset < middle)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}'s
ckocagil 2014/05/13 15:03:48 Done.
375 return SelectionModel(LayoutIndexToTextIndex(
376 run.glyph_to_char[i] + (is_rtl ? 1 : 0)),
377 (is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD));
378 if (offset < end)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}'s
ckocagil 2014/05/13 15:03:48 Done.
379 return SelectionModel(LayoutIndexToTextIndex(
380 run.glyph_to_char[i] + (is_rtl ? 0 : 1)),
381 (is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD));
382 }
383 return EdgeSelectionModel(CURSOR_RIGHT);
384 }
385
386 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() {
387 NOTIMPLEMENTED();
Alexei Svitkine (slow) 2014/05/12 15:58:31 It would be good to implement this since it's exer
ckocagil 2014/05/13 15:03:48 We use Skia fonts in RenderTextHarfBuzz, not gfx::
388 return std::vector<RenderText::FontSpan>();
389 }
390
391 int RenderTextHarfBuzz::GetLayoutTextBaseline() {
392 EnsureLayout();
393 return lines()[0].baseline;
394 }
395
396 SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel(
397 const SelectionModel& selection,
398 VisualCursorDirection direction) {
399 DCHECK(!needs_layout_);
400 internal::TextRunHarfBuzz* run;
401 size_t run_index = GetRunContainingCaret(selection);
402 if (run_index >= runs_.size()) {
403 // The cursor is not in any run: we're at the visual and logical edge.
404 SelectionModel edge = EdgeSelectionModel(direction);
405 if (edge.caret_pos() == selection.caret_pos())
406 return edge;
407 int visual_index = (direction == CURSOR_RIGHT) ? 0 : runs_.size() - 1;
408 run = runs_[visual_to_logical_[visual_index]];
409 } else {
410 // If the cursor is moving within the current run, just move it by one
411 // grapheme in the appropriate direction.
412 run = runs_[run_index];
413 size_t caret = selection.caret_pos();
414 bool forward_motion =
415 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT);
416 if (forward_motion) {
417 if (caret < LayoutIndexToTextIndex(run->range.end())) {
418 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
419 return SelectionModel(caret, CURSOR_BACKWARD);
420 }
421 } else {
422 if (caret > LayoutIndexToTextIndex(run->range.start())) {
423 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
424 return SelectionModel(caret, CURSOR_FORWARD);
425 }
426 }
427 // The cursor is at the edge of a run; move to the visually adjacent run.
428 int visual_index = logical_to_visual_[run_index];
429 visual_index += (direction == CURSOR_LEFT) ? -1 : 1;
430 if (visual_index < 0 || visual_index >= static_cast<int>(runs_.size()))
431 return EdgeSelectionModel(direction);
432 run = runs_[visual_to_logical_[visual_index]];
433 }
434 bool forward_motion =
435 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT);
436 return forward_motion ? FirstSelectionModelInsideRun(run) :
437 LastSelectionModelInsideRun(run);
438 }
439
440 SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel(
441 const SelectionModel& selection,
442 VisualCursorDirection direction) {
443 // TODO(ckocagil): This implementation currently matches RenderTextWin, but it
444 // should match the native behavior on other platforms.
445 if (obscured())
446 return EdgeSelectionModel(direction);
447
448 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
449 bool success = iter.Init();
450 DCHECK(success);
451 if (!success)
452 return selection;
453
454 size_t pos;
455 if (direction == CURSOR_RIGHT) {
456 pos = std::min(selection.caret_pos() + 1, text().length());
457 while (iter.Advance()) {
458 pos = iter.pos();
459 if (iter.IsWord() && pos > selection.caret_pos())
460 break;
461 }
462 } else { // direction == CURSOR_LEFT
463 // Notes: We always iterate words from the beginning.
464 // This is probably fast enough for our usage, but we may
465 // want to modify WordIterator so that it can start from the
466 // middle of string and advance backwards.
467 pos = std::max<int>(selection.caret_pos() - 1, 0);
468 while (iter.Advance()) {
469 if (iter.IsWord()) {
470 size_t begin = iter.pos() - iter.GetString().length();
471 if (begin == selection.caret_pos()) {
472 // The cursor is at the beginning of a word.
473 // Move to previous word.
474 break;
475 } else if (iter.pos() >= selection.caret_pos()) {
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: No else's after break.
ckocagil 2014/05/13 15:03:48 Done.
476 // The cursor is in the middle or at the end of a word.
477 // Move to the top of current word.
478 pos = begin;
479 break;
480 } else {
481 pos = iter.pos() - iter.GetString().length();
482 }
483 }
484 }
485 }
486 return SelectionModel(pos, CURSOR_FORWARD);
487 }
488
489 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) {
490 const size_t run_index =
491 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD));
492 // Return edge bounds if the index is invalid or beyond the layout text size.
493 if (run_index >= runs_.size())
494 return Range(GetStringSize().width());
495 const size_t layout_index = TextIndexToLayoutIndex(index);
496 return Range(GetGlyphXBoundary(run_index, layout_index, false),
497 GetGlyphXBoundary(run_index, layout_index, true));
498 }
499
500 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
501 DCHECK(!needs_layout_);
502 DCHECK(Range(0, text().length()).Contains(range));
503 Range layout_range(TextIndexToLayoutIndex(range.start()),
504 TextIndexToLayoutIndex(range.end()));
505 DCHECK(Range(0, GetLayoutText().length()).Contains(layout_range));
506
507 std::vector<Rect> rects;
508 if (layout_range.is_empty())
509 return rects;
510 std::vector<Range> bounds;
511
512 // Add a Range for each run/selection intersection.
513 // TODO(msw): The bounds should probably not always be leading the range ends.
514 for (size_t i = 0; i < runs_.size(); ++i) {
515 const internal::TextRunHarfBuzz* run = runs_[visual_to_logical_[i]];
516 Range intersection = run->range.Intersect(layout_range);
517 if (intersection.IsValid()) {
518 DCHECK(!intersection.is_reversed());
519 Range range_x(GetGlyphXBoundary(i, intersection.start(), false),
520 GetGlyphXBoundary(i, intersection.end(), false));
521 if (range_x.is_empty())
522 continue;
523 range_x = Range(range_x.GetMin(), range_x.GetMax());
524 // Union this with the last range if they're adjacent.
525 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin());
526 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) {
527 range_x = Range(bounds.back().GetMin(), range_x.GetMax());
528 bounds.pop_back();
529 }
530 bounds.push_back(range_x);
531 }
532 }
533 for (size_t i = 0; i < bounds.size(); ++i) {
534 std::vector<Rect> current_rects = TextBoundsToViewBounds(bounds[i]);
535 rects.insert(rects.end(), current_rects.begin(), current_rects.end());
536 }
537 return rects;
538 }
539
540 size_t RenderTextHarfBuzz::TextIndexToLayoutIndex(size_t index) const {
541 DCHECK_LE(index, text().length());
542 ptrdiff_t i = obscured() ? UTF16IndexToOffset(text(), 0, index) : index;
543 CHECK_GE(i, 0);
544 // Clamp layout indices to the length of the text actually used for layout.
545 return std::min<size_t>(GetLayoutText().length(), i);
546 }
547
548 size_t RenderTextHarfBuzz::LayoutIndexToTextIndex(size_t index) const {
549 if (!obscured())
550 return index;
551
552 DCHECK_LE(index, GetLayoutText().length());
553 const size_t text_index = UTF16OffsetToIndex(text(), 0, index);
554 DCHECK_LE(text_index, text().length());
555 return text_index;
556 }
557
558 bool RenderTextHarfBuzz::IsValidCursorIndex(size_t index) {
559 if (index == 0 || index == text().length())
560 return true;
561 if (!IsValidLogicalIndex(index))
562 return false;
563 EnsureLayout();
564 // Disallow indices amid multi-character graphemes by checking glyph bounds.
565 // These characters are not surrogate-pairs, but may yield a single glyph:
566 // \x0915\x093f - (ki) - one of many Devanagari biconsonantal conjuncts.
567 // \x0e08\x0e33 - (cho chan + sara am) - a Thai consonant and vowel pair.
568 return GetGlyphBounds(index) != GetGlyphBounds(index - 1);
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.
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: Either the indent is wrong here or you're mis
ckocagil 2014/05/13 15:03:48 Wrong indent, fixed.
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 // The range may be empty if a portion of a multi-character grapheme is
669 // selected, yielding two colors for a single glyph. For now, this just
670 // paints the glyph with a single style, but it should paint it twice,
671 // clipped according to selection bounds. See http://crbug.com/366786
672 if (colored_glyphs.is_empty())
673 continue;
674
675 renderer.SetForegroundColor(it->second);
676 renderer.DrawPosText(&run.positions[colored_glyphs.start()],
677 &run.glyphs[colored_glyphs.start()],
678 colored_glyphs.length());
679 int width = (colored_glyphs.end() == run.glyph_count ? run.width :
680 run.positions[colored_glyphs.end()].x()) -
681 run.positions[colored_glyphs.start()].x();
682 renderer.DrawDecorations(0, 0, width, run.underline, run.strike,
683 run.diagonal_strike);
684 }
685
686 canvas->Restore();
687 current_x += run.width;
688 }
689
690 renderer.EndDiagonalStrike();
691
692 UndoCompositionAndSelectionStyles();
693 }
694
695 size_t RenderTextHarfBuzz::GetRunContainingCaret(
696 const SelectionModel& caret) const {
697 DCHECK(!needs_layout_);
698 size_t layout_position = TextIndexToLayoutIndex(caret.caret_pos());
699 LogicalCursorDirection affinity = caret.caret_affinity();
700 for (size_t run = 0; run < runs_.size(); ++run)
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: {}'s
ckocagil 2014/05/13 15:03:48 Done.
701 if (RangeContainsCaret(runs_[run]->range, layout_position, affinity))
702 return run;
703 return runs_.size();
704 }
705
706 size_t RenderTextHarfBuzz::GetRunContainingXCoord(int x, int* offset) const {
707 DCHECK(!needs_layout_);
708 if (x < 0)
709 return runs_.size();
710 // Find the text run containing the argument point (assumed already offset).
711 int current_x = 0;
712 for (size_t i = 0; i < runs_.size(); ++i) {
713 size_t run = visual_to_logical_[i];
714 current_x += runs_[run]->width;
715 if (x < current_x) {
716 *offset = x - (current_x - runs_[run]->width);
717 return run;
718 }
719 }
720 return runs_.size();
721 }
722
723 int RenderTextHarfBuzz::GetGlyphXBoundary(size_t run_index,
724 size_t text_index,
725 bool trailing) {
726 const internal::TextRunHarfBuzz& run = *runs_[run_index];
727 int x = run.preceding_run_widths;
728
729 Range glyph_range;
730 if (text_index == run.range.end()) {
731 trailing = true;
732 glyph_range = run.direction == UBIDI_LTR ?
733 Range(run.glyph_count - 1, run.glyph_count) : Range(0, 1);
734 } else {
735 glyph_range = CharRangeToGlyphRange(run, Range(text_index, text_index + 1));
736 }
737 const int trailing_step = trailing ? 1 : 0;
738 const size_t glyph_pos = glyph_range.start() +
739 (run.direction == UBIDI_LTR ? trailing_step : (1 - trailing_step));
740 x += glyph_pos < run.glyph_count ?
741 SkScalarRoundToInt(run.positions[glyph_pos].x()) : run.width;
742 return x;
743 }
744
745 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun(
746 const internal::TextRunHarfBuzz* run) {
747 size_t position = LayoutIndexToTextIndex(run->range.start());
748 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD);
749 return SelectionModel(position, CURSOR_BACKWARD);
750 }
751
752 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun(
753 const internal::TextRunHarfBuzz* run) {
754 size_t position = LayoutIndexToTextIndex(run->range.end());
755 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD);
756 return SelectionModel(position, CURSOR_FORWARD);
757 }
758
759 void RenderTextHarfBuzz::ItemizeText() {
760 const base::string16& text = GetLayoutText();
761 const bool is_rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
762 DCHECK_NE(0U, text.length());
763
764 bool fake_runs = false;
765 UErrorCode result = U_ZERO_ERROR;
766
767 UBiDi* line = ubidi_openSized(text.length(), 0, &result);
768 if (U_FAILURE(result)) {
769 NOTREACHED();
770 fake_runs = true;
771 } else {
772 ubidi_setPara(line, text.c_str(), text.length(),
773 is_rtl ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &result);
Alexei Svitkine (slow) 2014/05/12 15:58:31 Nit: align
ckocagil 2014/05/13 15:03:48 Done.
774 if (U_FAILURE(result)) {
775 NOTREACHED();
776 fake_runs = true;
777 }
778 }
779
780 // Temporarily apply composition underlines and selection colors.
781 ApplyCompositionAndSelectionStyles();
782
783 // Build the list of runs from the script items and ranged styles. Use an
784 // empty color BreakList to avoid breaking runs at color boundaries.
785 BreakList<SkColor> empty_colors;
786 empty_colors.SetMax(text.length());
787 internal::StyleIterator style(empty_colors, styles());
788
789 for (size_t run_break = 0; run_break < text.length();) {
790 internal::TextRunHarfBuzz* run = new internal::TextRunHarfBuzz;
791 run->range.set_start(run_break);
792 run->font_style = (style.style(BOLD) ? Font::BOLD : 0) |
793 (style.style(ITALIC) ? Font::ITALIC : 0);
794 run->strike = style.style(STRIKE);
795 run->diagonal_strike = style.style(DIAGONAL_STRIKE);
796 run->underline = style.style(UNDERLINE);
797
798 if (fake_runs) {
799 run_break = text.length();
800 } else {
801 int32 script_item_break = 0;
802 ubidi_getLogicalRun(line, run_break, &script_item_break, &run->level);
803 // Find the length and script of this script run.
804 script_item_break = ScriptInterval(text.c_str() + run_break,
805 script_item_break - run_break, &run->script) + run_break;
806
807 // Find the next break and advance the iterators as needed.
808 run_break = std::min(static_cast<size_t>(script_item_break),
809 TextIndexToLayoutIndex(style.GetRange().end()));
810
811 // Break runs adjacent to character substrings in certain code blocks.
812 // This avoids using their fallback fonts for more characters than needed,
813 // in cases like "\x25B6 Media Title", etc. http://crbug.com/278913
814 if (run_break > run->range.start()) {
815 const size_t run_start = run->range.start();
816 const int32 run_length = static_cast<int32>(run_break - run_start);
817 base::i18n::UTF16CharIterator iter(text.c_str() + run_start,
818 run_length);
819 const UBlockCode first_block_code = ublock_getCode(iter.get());
820 const bool first_block_unusual = IsUnusualBlockCode(first_block_code);
821 while (iter.Advance() && iter.array_pos() < run_length) {
822 const UBlockCode current_block_code = ublock_getCode(iter.get());
823 if (current_block_code != first_block_code &&
824 (first_block_unusual || IsUnusualBlockCode(current_block_code))) {
825 run_break = run_start + iter.array_pos();
826 break;
827 }
828 }
829 }
830 }
831
832 DCHECK(IsValidCodePointIndex(text, run_break));
833 style.UpdatePosition(LayoutIndexToTextIndex(run_break));
834 run->range.set_end(run_break);
835 const UChar* uchar_start = ubidi_getText(line);
836 // TODO(ckocagil): Add |ubidi_getBaseDirection| to i18n::BiDiLineIterator
837 // and remove the bare ICU use here.
838 run->direction = ubidi_getBaseDirection(uchar_start + run->range.start(),
839 run->range.length());
840 if (run->direction == UBIDI_NEUTRAL)
841 run->direction = is_rtl ? UBIDI_RTL : UBIDI_LTR;
842 runs_.push_back(run);
843 }
844
845 ubidi_close(line);
846
847 // Undo the temporarily applied composition underlines and selection colors.
848 UndoCompositionAndSelectionStyles();
849
850 const size_t num_runs = runs_.size();
851 scoped_ptr<UBiDiLevel[]> levels(new UBiDiLevel[num_runs]);
852 for (size_t i = 0; i < num_runs; ++i)
853 levels[i] = runs_[i]->level;
854 visual_to_logical_.resize(num_runs);
855 ubidi_reorderVisual(levels.get(), num_runs, &visual_to_logical_[0]);
856 logical_to_visual_.resize(num_runs);
857 ubidi_reorderLogical(levels.get(), num_runs, &logical_to_visual_[0]);
858 }
859
860 void RenderTextHarfBuzz::ShapeRun(internal::TextRunHarfBuzz* run) {
861 const base::string16& text = GetLayoutText();
862 // TODO(ckocagil|yukishiino): Implement font fallback.
863 const Font& primary_font = font_list().GetPrimaryFont();
864 run->skia_face = internal::CreateSkiaTypeface(primary_font.GetFontName(),
865 run->font_style);
866 run->font_size = primary_font.GetFontSize();
867
868 hb_font_t* harfbuzz_font = CreateHarfBuzzFont(run->skia_face.get(),
869 run->font_size);
870
871 // Create a HarfBuzz buffer and add the string to be shaped. The HarfBuzz
872 // buffer holds our text, run information to be used by the shaping engine,
873 // and the resulting glyph data.
874 hb_buffer_t* buffer = hb_buffer_create();
875 hb_buffer_add_utf16(buffer, reinterpret_cast<const uint16*>(text.c_str()),
876 text.length(), run->range.start(), run->range.length());
877 hb_buffer_set_script(buffer, hb_icu_script_to_script(run->script));
878 hb_buffer_set_direction(buffer,
879 run->direction == UBIDI_LTR ? HB_DIRECTION_LTR : HB_DIRECTION_RTL);
880 // TODO(ckocagil): Should we determine the actual language?
881 hb_buffer_set_language(buffer, hb_language_get_default());
882
883 // Shape the text.
884 hb_shape(harfbuzz_font, buffer, NULL, 0);
885
886 // Populate the run fields with the resulting glyph data in the buffer.
887 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &run->glyph_count);
888 hb_glyph_position_t* hb_positions = hb_buffer_get_glyph_positions(buffer,
889 NULL);
890 run->glyphs.reset(new uint16[run->glyph_count]);
891 run->glyph_to_char.reset(new uint32[run->glyph_count]);
892 run->positions.reset(new SkPoint[run->glyph_count]);
893 for (size_t i = 0; i < run->glyph_count; ++i) {
894 run->glyphs[i] = infos[i].codepoint;
895 run->glyph_to_char[i] = infos[i].cluster;
896 const int x_offset =
897 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_offset));
898 const int y_offset =
899 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].y_offset));
900 run->positions[i].set(run->width + x_offset, y_offset);
901 run->width +=
902 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_advance));
903 }
904
905 hb_buffer_destroy(buffer);
906 hb_font_destroy(harfbuzz_font);
907 }
908
909 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698