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

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: Alexei's 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 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698