OLD | NEW |
---|---|
(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 "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/render_text.h" | |
18 #include "ui/gfx/utf16_indexing.h" | |
19 | |
20 namespace gfx { | |
21 | |
22 namespace { | |
23 | |
24 // Font data provider for HarfBuzz using Skia. Copied from Blink. | |
25 struct HarfBuzzFontData { | |
26 HarfBuzzFontData(std::map<uint32_t, uint16_t>* glyph_cache) | |
27 : glyph_cache_(glyph_cache) {} | |
28 | |
29 SkPaint paint_; | |
30 std::map<uint32_t, uint16_t>* glyph_cache_; | |
31 }; | |
32 | |
33 hb_position_t SkiaScalarToHarfBuzzPosition(SkScalar value) { | |
34 return SkScalarToFixed(value); | |
35 } | |
36 | |
37 void SkiaGetGlyphWidthAndExtents(SkPaint* paint, | |
38 hb_codepoint_t codepoint, | |
39 hb_position_t* width, | |
40 hb_glyph_extents_t* extents) { | |
41 CHECK(codepoint <= 0xFFFF); | |
42 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); | |
43 | |
44 SkScalar skWidth; | |
45 SkRect skBounds; | |
46 uint16_t glyph = codepoint; | |
47 | |
48 paint->getTextWidths(&glyph, sizeof(glyph), &skWidth, &skBounds); | |
49 if (width) | |
50 *width = SkiaScalarToHarfBuzzPosition(skWidth); | |
51 if (extents) { | |
52 // Invert y-axis because Skia is y-grows-down but we set up HarfBuzz to be | |
53 // y-grows-up. | |
54 extents->x_bearing = SkiaScalarToHarfBuzzPosition(skBounds.fLeft); | |
55 extents->y_bearing = SkiaScalarToHarfBuzzPosition(-skBounds.fTop); | |
56 extents->width = SkiaScalarToHarfBuzzPosition(skBounds.width()); | |
57 extents->height = SkiaScalarToHarfBuzzPosition(-skBounds.height()); | |
58 } | |
59 } | |
60 | |
61 hb_bool_t HarfBuzzGetGlyph(hb_font_t* font, | |
62 void* font_data, | |
63 hb_codepoint_t unicode, | |
64 hb_codepoint_t variation_selector, | |
65 hb_codepoint_t* glyph, | |
66 void* user_data) { | |
67 HarfBuzzFontData* hb_font_data = | |
68 reinterpret_cast<HarfBuzzFontData*>(font_data); | |
69 std::map<uint32_t, uint16_t>& cache = *hb_font_data->glyph_cache_; | |
70 | |
71 bool exists = cache.count(unicode) != 0; | |
72 if (!exists) { | |
73 SkPaint* paint = &hb_font_data->paint_; | |
74 paint->setTextEncoding(SkPaint::kUTF32_TextEncoding); | |
75 uint16_t glyph16; | |
76 paint->textToGlyphs(&unicode, sizeof(hb_codepoint_t), &glyph16); | |
77 cache[unicode] = glyph16; | |
78 *glyph = glyph16; | |
79 } | |
80 *glyph = cache[unicode]; | |
81 return !!*glyph; | |
82 } | |
83 | |
84 hb_position_t HarfBuzzGetGlyphHorizontalAdvance(hb_font_t* font, | |
85 void* font_data, | |
86 hb_codepoint_t glyph, | |
87 void* user_data) { | |
88 HarfBuzzFontData* hb_font_data = | |
89 reinterpret_cast<HarfBuzzFontData*>(font_data); | |
90 hb_position_t advance = 0; | |
91 | |
92 SkiaGetGlyphWidthAndExtents(&hb_font_data->paint_, glyph, &advance, 0); | |
93 return advance; | |
94 } | |
95 | |
96 hb_bool_t HarfBuzzGetGlyphHorizontalOrigin(hb_font_t* font, | |
97 void* font_data, | |
98 hb_codepoint_t glyph, | |
99 hb_position_t* x, | |
100 hb_position_t* y, | |
101 void* user_data) { | |
102 // Just return true, following the way that HarfBuzz-FreeType | |
103 // implementation does. | |
104 return true; | |
105 } | |
106 | |
107 hb_bool_t HarfBuzzGetGlyphExtents(hb_font_t* font, | |
108 void* font_data, | |
109 hb_codepoint_t glyph, | |
110 hb_glyph_extents_t* extents, | |
111 void* user_data) { | |
112 HarfBuzzFontData* hb_font_data = | |
113 reinterpret_cast<HarfBuzzFontData*>(font_data); | |
114 | |
115 SkiaGetGlyphWidthAndExtents(&hb_font_data->paint_, glyph, 0, extents); | |
116 return true; | |
117 } | |
118 | |
119 hb_font_funcs_t* HarfBuzzSkiaGetFontFuncs() { | |
120 static hb_font_funcs_t* harfbuzz_skia_font_funcs = 0; | |
121 | |
122 // We don't set callback functions which we can't support. | |
123 // HarfBuzz will use the fallback implementation if they aren't set. | |
124 if (!harfbuzz_skia_font_funcs) { | |
125 harfbuzz_skia_font_funcs = hb_font_funcs_create(); | |
126 hb_font_funcs_set_glyph_func( | |
127 harfbuzz_skia_font_funcs, HarfBuzzGetGlyph, 0, 0); | |
128 hb_font_funcs_set_glyph_h_advance_func( | |
129 harfbuzz_skia_font_funcs, HarfBuzzGetGlyphHorizontalAdvance, 0, 0); | |
130 hb_font_funcs_set_glyph_h_origin_func( | |
131 harfbuzz_skia_font_funcs, HarfBuzzGetGlyphHorizontalOrigin, 0, 0); | |
132 hb_font_funcs_set_glyph_extents_func( | |
133 harfbuzz_skia_font_funcs, HarfBuzzGetGlyphExtents, 0, 0); | |
134 hb_font_funcs_make_immutable(harfbuzz_skia_font_funcs); | |
135 } | |
136 return harfbuzz_skia_font_funcs; | |
137 } | |
138 | |
139 hb_blob_t* HarfBuzzSkiaGetTable(hb_face_t* face, | |
140 hb_tag_t tag, | |
141 void* user_data) { | |
142 SkTypeface* typeface = reinterpret_cast<SkTypeface*>(user_data); | |
143 | |
144 const size_t table_size = typeface->getTableSize(tag); | |
145 if (!table_size) | |
146 return 0; | |
147 | |
148 char* buffer = reinterpret_cast<char*>(malloc(table_size)); | |
149 if (!buffer) | |
150 return 0; | |
151 size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer); | |
152 if (table_size != actual_size) { | |
153 free(buffer); | |
154 return 0; | |
155 } | |
156 | |
157 return hb_blob_create(const_cast<char*>(buffer), table_size, | |
158 HB_MEMORY_MODE_WRITABLE, buffer, free); | |
159 } | |
160 | |
161 template<typename Type> | |
162 void DeleteByType(void* data) { | |
163 Type* typed_data = reinterpret_cast<Type*>(data); | |
164 delete typed_data; | |
165 } | |
166 | |
167 // TODO(ckocagil): Implement a cache for HarfBuzz faces. SkTypeface provides | |
168 // unique face IDs, which we can use as keys. Also see base::MRUCache. | |
169 hb_face_t* CreateHarfBuzzFace(SkTypeface* skia_face) { | |
170 hb_face_t* face = hb_face_create_for_tables(HarfBuzzSkiaGetTable, skia_face, | |
171 0); | |
172 CHECK(face); | |
173 return face; | |
174 } | |
175 | |
176 hb_font_t* CreateHarfBuzzFont(SkTypeface* skia_face, int text_size) { | |
177 typedef std::map<uint32_t, uint16_t> GlyphCache; | |
178 // TODO: This shouldn't grow indefinitely. See the comment above | |
179 // CreateHarfBuzzFace; | |
180 static std::map<SkFontID, GlyphCache> glyph_caches; | |
181 | |
182 SkFontID font_id = skia_face->uniqueID(); | |
183 if (glyph_caches.count(font_id) == 0) | |
184 glyph_caches[font_id] = GlyphCache(); | |
185 | |
186 hb_face_t* harfbuzz_face = CreateHarfBuzzFace(skia_face); | |
187 hb_font_t* harfbuzz_font = hb_font_create(harfbuzz_face); | |
188 unsigned int upem = hb_face_get_upem(harfbuzz_face); | |
189 hb_face_destroy(harfbuzz_face); | |
190 hb_font_set_scale(harfbuzz_font, upem, upem); | |
191 HarfBuzzFontData* hb_font_data = new HarfBuzzFontData(&glyph_caches[font_id]); | |
192 hb_font_data->paint_.setTypeface(skia_face); | |
193 hb_font_data->paint_.setTextSize(text_size); | |
194 hb_font_set_funcs(harfbuzz_font, HarfBuzzSkiaGetFontFuncs(), hb_font_data, | |
195 DeleteByType<HarfBuzzFontData>); | |
196 hb_font_make_immutable(harfbuzz_font); | |
197 return harfbuzz_font; | |
198 } | |
199 | |
200 size_t CharToGlyph(const internal::TextRunHarfBuzz& run, size_t pos) { | |
201 DCHECK(run.range.start() <= pos && pos < run.range.end()); | |
202 | |
203 if (run.direction == UBIDI_LTR) { | |
204 for (size_t i = 0; i < run.glyph_count - 1; ++i) { | |
205 if (pos < run.glyph_to_char[i + 1]) | |
206 return i; | |
207 } | |
208 return run.glyph_count - 1; | |
209 } | |
210 | |
211 for (size_t i = 0; i < run.glyph_count; ++i) { | |
212 if (pos >= run.glyph_to_char[i]) | |
213 return i; | |
214 } | |
215 DCHECK(false); | |
216 return -1; | |
217 } | |
218 | |
219 Range CharRangeToGlyphRange(const internal::TextRunHarfBuzz& run, | |
220 const Range& range) { | |
221 DCHECK(run.range.Contains(range)); | |
222 DCHECK(!range.is_reversed()); | |
223 DCHECK(!range.is_empty()); | |
224 | |
225 size_t first = CharToGlyph(run, range.start()); | |
226 size_t last = CharToGlyph(run, range.end() - 1); | |
227 return Range(std::min(first, last), std::max(first, last) + 1); | |
228 } | |
229 | |
230 } // namespace | |
231 | |
232 namespace internal { | |
233 | |
234 TextRunHarfBuzz::TextRunHarfBuzz() | |
235 : direction(UBIDI_LTR), | |
236 glyph_count(-1), | |
237 width(0) {} | |
238 | |
239 TextRunHarfBuzz::~TextRunHarfBuzz() { | |
240 SkSafeUnref(skia_face); | |
241 } | |
242 | |
243 } // namespace internal | |
244 | |
245 RenderTextHarfBuzz::RenderTextHarfBuzz() | |
246 : RenderText(), | |
247 num_runs_(0), | |
248 needs_layout_(true) {} | |
249 | |
250 RenderTextHarfBuzz::~RenderTextHarfBuzz() {} | |
251 | |
252 Size RenderTextHarfBuzz::GetStringSize() { | |
253 EnsureLayout(); | |
254 int width = 0; | |
255 for (size_t i = 0; i < num_runs_; ++i) | |
256 width += runs_[i].width; | |
257 return Size(width, font_list().GetHeight()); | |
258 } | |
259 | |
260 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) { | |
261 EnsureLayout(); | |
262 | |
263 int x = ToTextPoint(point).x(); | |
264 int offset = 0; | |
265 size_t run_index = GetRunContainingXCoord(x, &offset); | |
266 if (run_index >= num_runs_) | |
267 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); | |
268 internal::TextRunHarfBuzz& run = runs_[run_index]; | |
269 | |
270 for (size_t i = 0; i < run.glyph_count; ++i) { | |
271 const SkScalar end = | |
272 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x(); | |
273 const SkScalar middle = (end + run.positions[i].x()) / 2; | |
274 if (offset < middle) | |
275 return SelectionModel(LayoutIndexToTextIndex( | |
276 run.glyph_to_char[i] + (run.direction == UBIDI_RTL)), | |
277 CURSOR_BACKWARD); | |
278 if (offset < end) | |
279 return SelectionModel(LayoutIndexToTextIndex( | |
280 run.glyph_to_char[i] + (run.direction == UBIDI_LTR)), | |
281 CURSOR_FORWARD); | |
282 } | |
283 return EdgeSelectionModel(CURSOR_RIGHT); | |
284 } | |
285 | |
286 bool RenderTextHarfBuzz::IsCursorablePosition(size_t position) { | |
287 if (position == 0 || position == text().length()) | |
288 return true; | |
289 EnsureLayout(); | |
290 | |
291 // TODO: Can this be simplified by using |TextRunHarfBuzz::glyph_to_char|? | |
292 // Check that the index is at a valid code point (not mid-surrgate-pair), | |
293 // that it is not truncated from layout text (its glyph is shown on screen), | |
294 // and that its glyph has distinct bounds (not mid-multi-character-grapheme). | |
295 // An example of a multi-character-grapheme that is not a surrogate-pair is: | |
296 // \x0915\x093f - (ki) - one of many Devanagari biconsonantal conjuncts. | |
297 return gfx::IsValidCodePointIndex(text(), position) && | |
298 position < LayoutIndexToTextIndex(GetLayoutText().length()) && | |
299 GetGlyphBounds(position) != GetGlyphBounds(position - 1); | |
300 } | |
301 | |
302 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() { | |
303 NOTIMPLEMENTED(); | |
304 return std::vector<RenderText::FontSpan>(); | |
305 } | |
306 | |
307 int RenderTextHarfBuzz::GetLayoutTextBaseline() { | |
308 EnsureLayout(); | |
309 return lines()[0].baseline; | |
310 } | |
311 | |
312 SelectionModel RenderTextHarfBuzz::AdjacentCharSelectionModel( | |
313 const SelectionModel& selection, | |
314 VisualCursorDirection direction) { | |
315 DCHECK(!needs_layout_); | |
316 internal::TextRunHarfBuzz* run; | |
317 size_t run_index = GetRunContainingCaret(selection); | |
318 if (run_index >= num_runs_) { | |
319 // The cursor is not in any run: we're at the visual and logical edge. | |
320 SelectionModel edge = EdgeSelectionModel(direction); | |
321 if (edge.caret_pos() == selection.caret_pos()) | |
322 return edge; | |
323 run_index = (direction == CURSOR_RIGHT) ? 0 : num_runs_ - 1; | |
324 run = &runs_[run_index]; | |
325 } else { | |
326 // If the cursor is moving within the current run, just move it by one | |
327 // grapheme in the appropriate direction. | |
328 run = &runs_[run_index]; | |
329 size_t caret = selection.caret_pos(); | |
330 bool forward_motion = | |
331 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT); | |
332 if (forward_motion) { | |
333 if (caret < LayoutIndexToTextIndex(run->range.end())) { | |
334 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); | |
335 return SelectionModel(caret, CURSOR_BACKWARD); | |
336 } | |
337 } else { | |
338 if (caret > LayoutIndexToTextIndex(run->range.start())) { | |
339 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); | |
340 return SelectionModel(caret, CURSOR_FORWARD); | |
341 } | |
342 } | |
343 // The cursor is at the edge of a run; move to the visually adjacent run. | |
344 run_index += (direction == CURSOR_LEFT) ? -1 : 1; | |
345 if (run_index < 0 || run_index >= static_cast<int>(num_runs_)) | |
346 return EdgeSelectionModel(direction); | |
347 run = &runs_[run_index]; | |
348 } | |
349 bool forward_motion = | |
350 (run->direction == UBIDI_RTL) == (direction == CURSOR_LEFT); | |
351 return forward_motion ? FirstSelectionModelInsideRun(run) : | |
352 LastSelectionModelInsideRun(run); | |
353 } | |
354 | |
355 SelectionModel RenderTextHarfBuzz::AdjacentWordSelectionModel( | |
356 const SelectionModel& selection, | |
357 VisualCursorDirection direction) { | |
358 if (obscured()) | |
359 return EdgeSelectionModel(direction); | |
360 | |
361 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
362 bool success = iter.Init(); | |
363 DCHECK(success); | |
364 if (!success) | |
365 return selection; | |
366 | |
367 size_t pos; | |
368 if (direction == CURSOR_RIGHT) { | |
369 pos = std::min(selection.caret_pos() + 1, text().length()); | |
370 while (iter.Advance()) { | |
371 pos = iter.pos(); | |
372 if (iter.IsWord() && pos > selection.caret_pos()) | |
373 break; | |
374 } | |
375 } else { // direction == CURSOR_LEFT | |
376 // Notes: We always iterate words from the beginning. | |
377 // This is probably fast enough for our usage, but we may | |
378 // want to modify WordIterator so that it can start from the | |
379 // middle of string and advance backwards. | |
380 pos = std::max<int>(selection.caret_pos() - 1, 0); | |
381 while (iter.Advance()) { | |
382 if (iter.IsWord()) { | |
383 size_t begin = iter.pos() - iter.GetString().length(); | |
384 if (begin == selection.caret_pos()) { | |
385 // The cursor is at the beginning of a word. | |
386 // Move to previous word. | |
387 break; | |
388 } else if (iter.pos() >= selection.caret_pos()) { | |
389 // The cursor is in the middle or at the end of a word. | |
390 // Move to the top of current word. | |
391 pos = begin; | |
392 break; | |
393 } else { | |
394 pos = iter.pos() - iter.GetString().length(); | |
395 } | |
396 } | |
397 } | |
398 } | |
399 return SelectionModel(pos, CURSOR_FORWARD); | |
400 } | |
401 | |
402 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) { | |
403 const size_t run_index = | |
404 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); | |
405 // Return edge bounds if the index is invalid or beyond the layout text size. | |
406 if (run_index >= num_runs_) | |
407 return Range(GetStringSize().width()); | |
408 const size_t layout_index = TextIndexToLayoutIndex(index); | |
409 return Range(GetGlyphXBoundary(run_index, layout_index, false), | |
410 GetGlyphXBoundary(run_index, layout_index, true)); | |
411 } | |
412 | |
413 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { | |
414 DCHECK(!needs_layout_); | |
415 DCHECK(Range(0, text().length()).Contains(range)); | |
416 Range layout_range(TextIndexToLayoutIndex(range.start()), | |
417 TextIndexToLayoutIndex(range.end())); | |
418 DCHECK(Range(0, GetLayoutText().length()).Contains(layout_range)); | |
419 | |
420 std::vector<Rect> rects; | |
421 if (layout_range.is_empty()) | |
422 return rects; | |
423 std::vector<Range> bounds; | |
424 | |
425 // Add a Range for each run/selection intersection. | |
426 // TODO(msw): The bounds should probably not always be leading the range ends. | |
427 for (size_t i = 0; i < num_runs_; ++i) { | |
428 Range intersection = runs_[i].range.Intersect(layout_range); | |
429 if (intersection.IsValid()) { | |
430 DCHECK(!intersection.is_reversed()); | |
431 Range range_x(GetGlyphXBoundary(i, intersection.start(), false), | |
432 GetGlyphXBoundary(i, intersection.end(), false)); | |
433 if (range_x.is_empty()) | |
434 continue; | |
435 range_x = Range(range_x.GetMin(), range_x.GetMax()); | |
436 // Union this with the last range if they're adjacent. | |
437 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin()); | |
438 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) { | |
439 range_x = Range(bounds.back().GetMin(), range_x.GetMax()); | |
440 bounds.pop_back(); | |
441 } | |
442 bounds.push_back(range_x); | |
443 } | |
444 } | |
445 for (size_t i = 0; i < bounds.size(); ++i) { | |
446 std::vector<Rect> current_rects = TextBoundsToViewBounds(bounds[i]); | |
447 rects.insert(rects.end(), current_rects.begin(), current_rects.end()); | |
448 } | |
449 return rects; | |
450 } | |
451 | |
452 size_t RenderTextHarfBuzz::TextIndexToLayoutIndex(size_t index) const { | |
453 DCHECK_LE(index, text().length()); | |
454 ptrdiff_t i = obscured() ? UTF16IndexToOffset(text(), 0, index) : index; | |
455 CHECK_GE(i, 0); | |
456 // Clamp layout indices to the length of the text actually used for layout. | |
457 return std::min<size_t>(GetLayoutText().length(), i); | |
458 } | |
459 | |
460 size_t RenderTextHarfBuzz::LayoutIndexToTextIndex(size_t index) const { | |
461 if (!obscured()) | |
462 return index; | |
463 | |
464 DCHECK_LE(index, GetLayoutText().length()); | |
465 const size_t text_index = UTF16OffsetToIndex(text(), 0, index); | |
466 DCHECK_LE(text_index, text().length()); | |
467 return text_index; | |
468 } | |
469 | |
470 void RenderTextHarfBuzz::ResetLayout() { | |
471 needs_layout_ = true; | |
472 } | |
473 | |
474 void RenderTextHarfBuzz::EnsureLayout() { | |
475 if (needs_layout_) { | |
476 // TODO: Skip complex processing if text isn't complex. | |
477 // TODO: Handle styles. | |
478 UErrorCode result = U_ZERO_ERROR; | |
479 const base::string16& text = GetLayoutText(); | |
480 | |
481 UBiDi* line = ubidi_openSized(text.length(), 0, &result); | |
482 CHECK(U_SUCCESS(result)); | |
483 | |
484 ubidi_setPara(line, text.c_str(), text.length(), UBIDI_DEFAULT_LTR, NULL, | |
485 &result); | |
486 CHECK(U_SUCCESS(result)); | |
487 | |
488 num_runs_ = ubidi_countRuns(line, &result); | |
489 CHECK(U_SUCCESS(result)); | |
490 | |
491 runs_.reset(new internal::TextRunHarfBuzz[num_runs_]); | |
492 for (size_t i = 0; i < num_runs_; ++i) { | |
493 internal::TextRunHarfBuzz& run = runs_[i]; | |
494 int32 start = 0; | |
495 int32 length = 0; | |
496 UBiDiDirection direction = ubidi_getVisualRun(line, i, &start, &length); | |
497 DCHECK_GT(length, 0); | |
498 ShapeRun(Range(start, start + length), direction, &runs_[i]); | |
499 } | |
500 | |
501 ubidi_close(line); | |
502 needs_layout_ = false; | |
503 std::vector<internal::Line> empty_lines; | |
504 set_lines(&empty_lines); | |
505 } | |
506 | |
507 if (lines().empty()) { | |
508 // TODO: implement single-line lines() support for now. | |
509 std::vector<internal::Line> lines; | |
510 lines.push_back(internal::Line()); | |
511 int current_x = 0; | |
512 for (size_t i = 0; i < num_runs_; ++i) { | |
513 const internal::TextRunHarfBuzz& run = runs_[i]; | |
514 internal::LineSegment segment; | |
515 segment.x_range = Range(current_x, current_x + run.width); | |
516 segment.char_range = run.range; | |
517 segment.run = i; | |
518 lines[0].segments.push_back(segment); | |
519 | |
520 Canvas canvas; | |
521 internal::SkiaTextRenderer renderer(&canvas); | |
522 renderer.SetTypeface(run.skia_face); | |
523 renderer.SetTextSize(run.font_size); | |
524 SkPaint::FontMetrics metrics; | |
525 renderer.paint().getFontMetrics(&metrics); | |
526 | |
527 lines[0].size.set_width(lines[0].size.width() + run.width); | |
528 lines[0].size.set_height(std::max(lines[0].size.height(), | |
529 SkScalarRoundToInt(metrics.fDescent - metrics.fAscent))); | |
530 lines[0].baseline = std::max(lines[0].baseline, | |
531 SkScalarRoundToInt(-metrics.fAscent)); | |
532 } | |
533 set_lines(&lines); | |
534 } | |
535 } | |
536 | |
537 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) { | |
538 int current_x = 0; | |
539 | |
540 internal::SkiaTextRenderer renderer(canvas); | |
541 ApplyFadeEffects(&renderer); | |
542 ApplyTextShadows(&renderer); | |
543 | |
544 ApplyCompositionAndSelectionStyles(); | |
545 | |
546 const Vector2d line_offset = GetLineOffset(0); | |
547 | |
548 for (size_t i = 0; i < num_runs_; ++i) { | |
549 const internal::TextRunHarfBuzz& run = runs_[i]; | |
550 | |
551 renderer.SetTypeface(run.skia_face); | |
552 renderer.SetTextSize(run.font_size); | |
553 | |
554 canvas->Save(); | |
555 canvas->Translate(line_offset + Vector2d(current_x, 0)); | |
556 | |
557 for (BreakList<SkColor>::const_iterator it = | |
558 colors().GetBreak(run.range.start()); | |
559 it != colors().breaks().end() && it->first < run.range.end(); | |
560 ++it) { | |
561 const Range intersection = colors().GetRange(it).Intersect(run.range); | |
562 const Range colored_glyphs = CharRangeToGlyphRange(run, intersection); | |
563 DCHECK(!colored_glyphs.is_empty()); | |
564 | |
565 renderer.SetForegroundColor(it->second); | |
566 renderer.DrawPosText(&run.positions[colored_glyphs.start()], | |
567 &run.glyphs[colored_glyphs.start()], | |
568 colored_glyphs.length()); | |
569 } | |
570 | |
571 canvas->Restore(); | |
572 current_x += run.width; | |
573 } | |
574 | |
575 UndoCompositionAndSelectionStyles(); | |
576 } | |
577 | |
578 void RenderTextHarfBuzz::ShapeRun(Range range, | |
579 UBiDiDirection direction, | |
580 internal::TextRunHarfBuzz* run) { | |
581 const base::string16& text = GetLayoutText(); | |
582 | |
583 run->range = range; | |
584 run->direction = direction; | |
585 | |
586 const Font& primary_font = font_list().GetPrimaryFont(); | |
587 Canvas canvas; | |
588 internal::SkiaTextRenderer renderer(&canvas); | |
589 | |
590 ApplyFadeEffects(&renderer); | |
591 ApplyTextShadows(&renderer); | |
592 | |
593 renderer.SetFontSmoothingSettings(true, true); | |
594 renderer.SetTextSize(primary_font.GetFontSize()); | |
595 renderer.SetFontFamilyWithStyle(primary_font.GetFontName(), 0); | |
596 | |
597 run->skia_face = SkRef(renderer.paint().getTypeface()); | |
598 run->font_size = renderer.paint().getTextSize(); | |
599 hb_font_t* harfbuzz_font = CreateHarfBuzzFont(run->skia_face, run->font_size); | |
600 | |
601 hb_buffer_t* text_buffer = hb_buffer_create(); | |
602 hb_buffer_add_utf16(text_buffer, | |
603 reinterpret_cast<const uint16*>(text.c_str()), | |
604 text.length(), run->range.start(), run->range.length()); | |
605 hb_buffer_guess_segment_properties(text_buffer); | |
eae
2014/03/07 18:43:06
You really don't want to do this. We've had a lot
ckocagil
2014/05/01 22:02:00
Done, we now split text by script runs taking into
| |
606 | |
607 hb_shape(harfbuzz_font, text_buffer, NULL, 0); | |
608 | |
609 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(text_buffer, | |
610 &run->glyph_count); | |
611 CHECK(run->glyph_count != -1); | |
612 run->glyphs.reset(new uint16[run->glyph_count]); | |
613 for (size_t j = 0; j < run->glyph_count; ++j) | |
614 run->glyphs[j] = infos[j].codepoint; | |
615 | |
616 run->glyph_to_char.reset(new uint32[run->glyph_count]); | |
617 for (size_t j = 0; j < run->glyph_count; ++j) | |
618 run->glyph_to_char[j] = infos[j].cluster; | |
619 | |
620 SkPaint::FontMetrics metrics; | |
621 renderer.paint().getFontMetrics(&metrics); | |
622 | |
623 hb_glyph_position_t* hb_positions = | |
624 hb_buffer_get_glyph_positions(text_buffer, NULL); | |
625 | |
626 run->positions.reset(new SkPoint[run->glyph_count]); | |
627 | |
628 for (size_t j = 0; j < run->glyph_count; ++j) { | |
629 const int x_offset = | |
630 SkScalarRoundToInt(SkFixedToScalar(hb_positions[j].x_offset)); | |
631 const int y_offset = | |
632 SkScalarRoundToInt(SkFixedToScalar(hb_positions[j].y_offset)); | |
633 run->positions[j].set(run->width + x_offset, | |
634 y_offset - metrics.fAscent); | |
635 run->width += | |
636 SkScalarRoundToInt(SkFixedToScalar(hb_positions[j].x_advance)); | |
637 } | |
638 | |
639 hb_buffer_destroy(text_buffer); | |
640 hb_font_destroy(harfbuzz_font); | |
641 } | |
642 | |
643 size_t RenderTextHarfBuzz::GetRunContainingCaret( | |
644 const SelectionModel& caret) const { | |
645 DCHECK(!needs_layout_); | |
646 size_t layout_position = TextIndexToLayoutIndex(caret.caret_pos()); | |
647 LogicalCursorDirection affinity = caret.caret_affinity(); | |
648 for (size_t run = 0; run < num_runs_; ++run) | |
649 if (RangeContainsCaret(runs_[run].range, layout_position, affinity)) | |
650 return run; | |
651 return num_runs_; | |
652 } | |
653 | |
654 int RenderTextHarfBuzz::GetGlyphXBoundary(size_t run_index, | |
655 size_t text_index, | |
656 bool trailing) { | |
657 internal::TextRunHarfBuzz& run = runs_[run_index]; | |
658 | |
659 int x = 0; | |
660 | |
661 for (size_t i = 0; i < run_index; ++i) | |
662 x += runs_[i].width; | |
663 | |
664 const bool last_glyph = text_index == run.range.end(); | |
665 Range glyph_range; | |
666 if (last_glyph) { | |
667 trailing = true; | |
668 glyph_range = run.direction == UBIDI_LTR ? | |
669 Range(run.glyph_count - 1, run.glyph_count) : Range(0, 1); | |
670 } else { | |
671 glyph_range = CharRangeToGlyphRange(run, Range(text_index, text_index + 1)); | |
672 } | |
673 int trailing_step = trailing; | |
674 size_t glyph_pos = glyph_range.start() + | |
675 (run.direction == UBIDI_LTR ? trailing : !trailing); | |
676 x += glyph_pos < run.glyph_count ? | |
677 SkScalarRoundToInt(run.positions[glyph_pos].x()) : run.width; | |
678 return x; | |
679 } | |
680 | |
681 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( | |
682 const internal::TextRunHarfBuzz* run) { | |
683 size_t position = LayoutIndexToTextIndex(run->range.start()); | |
684 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); | |
685 return SelectionModel(position, CURSOR_BACKWARD); | |
686 } | |
687 | |
688 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( | |
689 const internal::TextRunHarfBuzz* run) { | |
690 size_t position = LayoutIndexToTextIndex(run->range.end()); | |
691 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); | |
692 return SelectionModel(position, CURSOR_FORWARD); | |
693 } | |
694 | |
695 size_t RenderTextHarfBuzz::GetRunContainingXCoord(int x, int* offset) const { | |
696 DCHECK(!needs_layout_); | |
697 if (x < 0) | |
698 return num_runs_; | |
699 // Find the text run containing the argument point (assumed already offset). | |
700 int current_x = 0; | |
701 for (size_t run = 0; run < num_runs_; ++run) { | |
702 current_x += runs_[run].width; | |
703 if (x < current_x) { | |
704 *offset = x - (current_x - runs_[run].width); | |
705 return run; | |
706 } | |
707 } | |
708 return num_runs_; | |
709 } | |
710 | |
711 } // namespace gfx | |
OLD | NEW |