OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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_pango.h" | |
6 | |
7 #include <pango/pangocairo.h> | |
8 #include <algorithm> | |
9 #include <string> | |
10 #include <vector> | |
11 | |
12 #include "base/i18n/break_iterator.h" | |
13 #include "base/logging.h" | |
14 #include "third_party/skia/include/core/SkTypeface.h" | |
15 #include "ui/gfx/canvas.h" | |
16 #include "ui/gfx/font.h" | |
17 #include "ui/gfx/font_list.h" | |
18 #include "ui/gfx/font_render_params.h" | |
19 #include "ui/gfx/pango_util.h" | |
20 #include "ui/gfx/platform_font_pango.h" | |
21 #include "ui/gfx/utf16_indexing.h" | |
22 | |
23 namespace gfx { | |
24 | |
25 namespace { | |
26 | |
27 // Returns the preceding element in a GSList (O(n)). | |
28 GSList* GSListPrevious(GSList* head, GSList* item) { | |
29 GSList* prev = NULL; | |
30 for (GSList* cur = head; cur != item; cur = cur->next) { | |
31 DCHECK(cur); | |
32 prev = cur; | |
33 } | |
34 return prev; | |
35 } | |
36 | |
37 // Returns true if the given visual cursor |direction| is logically forward | |
38 // motion in the given Pango |item|. | |
39 bool IsForwardMotion(VisualCursorDirection direction, const PangoItem* item) { | |
40 bool rtl = item->analysis.level & 1; | |
41 return rtl == (direction == CURSOR_LEFT); | |
42 } | |
43 | |
44 // Checks whether |range| contains |index|. This is not the same as calling | |
45 // range.Contains(Range(index)), which returns true if |index| == |range.end()|. | |
46 bool IndexInRange(const Range& range, size_t index) { | |
47 return index >= range.start() && index < range.end(); | |
48 } | |
49 | |
50 // Sets underline metrics on |renderer| according to Pango font |desc|. | |
51 void SetPangoUnderlineMetrics(PangoFontDescription *desc, | |
52 internal::SkiaTextRenderer* renderer) { | |
53 PangoFontMetrics* metrics = GetPangoFontMetrics(desc); | |
54 int thickness = pango_font_metrics_get_underline_thickness(metrics); | |
55 // Pango returns the position "above the baseline". Change its sign to convert | |
56 // it to a vertical offset from the baseline. | |
57 int position = -pango_font_metrics_get_underline_position(metrics); | |
58 | |
59 // Note: pango_quantize_line_geometry() guarantees pixel boundaries, so | |
60 // PANGO_PIXELS() is safe to use. | |
61 pango_quantize_line_geometry(&thickness, &position); | |
62 int thickness_pixels = PANGO_PIXELS(thickness); | |
63 int position_pixels = PANGO_PIXELS(position); | |
64 | |
65 // Ugly hack: make sure that underlines don't get clipped. See | |
66 // http://crbug.com/393117. | |
67 int descent_pixels = PANGO_PIXELS(pango_font_metrics_get_descent(metrics)); | |
68 position_pixels = std::min(position_pixels, | |
69 descent_pixels - thickness_pixels); | |
70 | |
71 renderer->SetUnderlineMetrics(thickness_pixels, position_pixels); | |
72 } | |
73 | |
74 } // namespace | |
75 | |
76 // TODO(xji): index saved in upper layer is utf16 index. Pango uses utf8 index. | |
77 // Since caret_pos is used internally, we could save utf8 index for caret_pos | |
78 // to avoid conversion. | |
79 | |
80 RenderTextPango::RenderTextPango() | |
81 : layout_(NULL), | |
82 current_line_(NULL), | |
83 log_attrs_(NULL), | |
84 num_log_attrs_(0), | |
85 layout_text_(NULL) { | |
86 } | |
87 | |
88 RenderTextPango::~RenderTextPango() { | |
89 ResetLayout(); | |
90 } | |
91 | |
92 scoped_ptr<RenderText> RenderTextPango::CreateInstanceOfSameType() const { | |
93 return scoped_ptr<RenderTextPango>(new RenderTextPango); | |
94 } | |
95 | |
96 Size RenderTextPango::GetStringSize() { | |
97 EnsureLayout(); | |
98 int width = 0, height = 0; | |
99 pango_layout_get_pixel_size(layout_, &width, &height); | |
100 | |
101 // Pango returns 0 widths for very long strings (of 0x40000 chars or more). | |
102 // This is caused by an int overflow in pango_glyph_string_extents_range. | |
103 // Absurdly long strings may even report non-zero garbage values for width; | |
104 // while detecting that isn't worthwhile, this handles the 0 width cases. | |
105 const long kAbsurdLength = 100000; | |
106 if (width == 0 && g_utf8_strlen(layout_text_, -1) > kAbsurdLength) | |
107 width = font_list().GetExpectedTextWidth(g_utf8_strlen(layout_text_, -1)); | |
108 | |
109 // Keep a consistent height between this particular string's PangoLayout and | |
110 // potentially larger text supported by the FontList. | |
111 // For example, if a text field contains a Japanese character, which is | |
112 // smaller than Latin ones, and then later a Latin one is inserted, this | |
113 // ensures that the text baseline does not shift. | |
114 return Size(width, std::max(height, font_list().GetHeight())); | |
115 } | |
116 | |
117 SelectionModel RenderTextPango::FindCursorPosition(const Point& point) { | |
118 EnsureLayout(); | |
119 | |
120 if (text().empty()) | |
121 return SelectionModel(0, CURSOR_FORWARD); | |
122 | |
123 Point p(ToTextPoint(point)); | |
124 | |
125 // When the point is outside of text, return HOME/END position. | |
126 if (p.x() < 0) | |
127 return EdgeSelectionModel(CURSOR_LEFT); | |
128 if (p.x() > GetStringSize().width()) | |
129 return EdgeSelectionModel(CURSOR_RIGHT); | |
130 | |
131 int caret_pos = 0, trailing = 0; | |
132 pango_layout_xy_to_index(layout_, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE, | |
133 &caret_pos, &trailing); | |
134 | |
135 DCHECK_GE(trailing, 0); | |
136 if (trailing > 0) { | |
137 caret_pos = g_utf8_offset_to_pointer(layout_text_ + caret_pos, | |
138 trailing) - layout_text_; | |
139 DCHECK_LE(static_cast<size_t>(caret_pos), strlen(layout_text_)); | |
140 } | |
141 | |
142 return SelectionModel(LayoutIndexToTextIndex(caret_pos), | |
143 (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD); | |
144 } | |
145 | |
146 std::vector<RenderText::FontSpan> RenderTextPango::GetFontSpansForTesting() { | |
147 EnsureLayout(); | |
148 | |
149 std::vector<RenderText::FontSpan> spans; | |
150 for (GSList* it = current_line_->runs; it; it = it->next) { | |
151 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(it->data)->item; | |
152 const int start = LayoutIndexToTextIndex(item->offset); | |
153 const int end = LayoutIndexToTextIndex(item->offset + item->length); | |
154 const Range range(start, end); | |
155 | |
156 ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font)); | |
157 spans.push_back(RenderText::FontSpan(Font(desc.get()), range)); | |
158 } | |
159 | |
160 return spans; | |
161 } | |
162 | |
163 int RenderTextPango::GetLayoutTextBaseline() { | |
164 EnsureLayout(); | |
165 return PANGO_PIXELS(pango_layout_get_baseline(layout_)); | |
166 } | |
167 | |
168 SelectionModel RenderTextPango::AdjacentCharSelectionModel( | |
169 const SelectionModel& selection, | |
170 VisualCursorDirection direction) { | |
171 GSList* run = GetRunContainingCaret(selection); | |
172 if (!run) { | |
173 // The cursor is not in any run: we're at the visual and logical edge. | |
174 SelectionModel edge = EdgeSelectionModel(direction); | |
175 if (edge.caret_pos() == selection.caret_pos()) | |
176 return edge; | |
177 else | |
178 run = (direction == CURSOR_RIGHT) ? | |
179 current_line_->runs : g_slist_last(current_line_->runs); | |
180 } else { | |
181 // If the cursor is moving within the current run, just move it by one | |
182 // grapheme in the appropriate direction. | |
183 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
184 size_t caret = selection.caret_pos(); | |
185 if (IsForwardMotion(direction, item)) { | |
186 if (caret < LayoutIndexToTextIndex(item->offset + item->length)) { | |
187 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); | |
188 return SelectionModel(caret, CURSOR_BACKWARD); | |
189 } | |
190 } else { | |
191 if (caret > LayoutIndexToTextIndex(item->offset)) { | |
192 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); | |
193 return SelectionModel(caret, CURSOR_FORWARD); | |
194 } | |
195 } | |
196 // The cursor is at the edge of a run; move to the visually adjacent run. | |
197 // TODO(xji): Keep a vector of runs to avoid using a singly-linked list. | |
198 run = (direction == CURSOR_RIGHT) ? | |
199 run->next : GSListPrevious(current_line_->runs, run); | |
200 if (!run) | |
201 return EdgeSelectionModel(direction); | |
202 } | |
203 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
204 return IsForwardMotion(direction, item) ? | |
205 FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item); | |
206 } | |
207 | |
208 SelectionModel RenderTextPango::AdjacentWordSelectionModel( | |
209 const SelectionModel& selection, | |
210 VisualCursorDirection direction) { | |
211 if (obscured()) | |
212 return EdgeSelectionModel(direction); | |
213 | |
214 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
215 bool success = iter.Init(); | |
216 DCHECK(success); | |
217 if (!success) | |
218 return selection; | |
219 | |
220 SelectionModel cur(selection); | |
221 for (;;) { | |
222 cur = AdjacentCharSelectionModel(cur, direction); | |
223 GSList* run = GetRunContainingCaret(cur); | |
224 if (!run) | |
225 break; | |
226 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
227 size_t cursor = cur.caret_pos(); | |
228 if (IsForwardMotion(direction, item) ? | |
229 iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) | |
230 break; | |
231 } | |
232 | |
233 return cur; | |
234 } | |
235 | |
236 Range RenderTextPango::GetGlyphBounds(size_t index) { | |
237 EnsureLayout(); | |
238 PangoRectangle pos; | |
239 pango_layout_index_to_pos(layout_, TextIndexToLayoutIndex(index), &pos); | |
240 // TODO(derat): Support fractional ranges for subpixel positioning? | |
241 return Range(PANGO_PIXELS(pos.x), PANGO_PIXELS(pos.x + pos.width)); | |
242 } | |
243 | |
244 std::vector<Rect> RenderTextPango::GetSubstringBounds(const Range& range) { | |
245 DCHECK_LE(range.GetMax(), text().length()); | |
246 if (range.is_empty()) | |
247 return std::vector<Rect>(); | |
248 | |
249 EnsureLayout(); | |
250 int* ranges = NULL; | |
251 int n_ranges = 0; | |
252 pango_layout_line_get_x_ranges(current_line_, | |
253 TextIndexToLayoutIndex(range.GetMin()), | |
254 TextIndexToLayoutIndex(range.GetMax()), | |
255 &ranges, | |
256 &n_ranges); | |
257 | |
258 const int height = GetStringSize().height(); | |
259 | |
260 std::vector<Rect> bounds; | |
261 for (int i = 0; i < n_ranges; ++i) { | |
262 // TODO(derat): Support fractional bounds for subpixel positioning? | |
263 int x = PANGO_PIXELS(ranges[2 * i]); | |
264 int width = PANGO_PIXELS(ranges[2 * i + 1]) - x; | |
265 Rect rect(x, 0, width, height); | |
266 rect.set_origin(ToViewPoint(rect.origin())); | |
267 bounds.push_back(rect); | |
268 } | |
269 g_free(ranges); | |
270 return bounds; | |
271 } | |
272 | |
273 size_t RenderTextPango::TextIndexToLayoutIndex(size_t index) const { | |
274 DCHECK(layout_); | |
275 ptrdiff_t offset = UTF16IndexToOffset(text(), 0, index); | |
276 // Clamp layout indices to the length of the text actually used for layout. | |
277 offset = std::min<size_t>(offset, g_utf8_strlen(layout_text_, -1)); | |
278 const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset); | |
279 return (layout_pointer - layout_text_); | |
280 } | |
281 | |
282 size_t RenderTextPango::LayoutIndexToTextIndex(size_t index) const { | |
283 DCHECK(layout_); | |
284 const char* layout_pointer = layout_text_ + index; | |
285 const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer); | |
286 return UTF16OffsetToIndex(text(), 0, offset); | |
287 } | |
288 | |
289 bool RenderTextPango::IsValidCursorIndex(size_t index) { | |
290 if (index == 0 || index == text().length()) | |
291 return true; | |
292 if (!IsValidLogicalIndex(index)) | |
293 return false; | |
294 | |
295 EnsureLayout(); | |
296 ptrdiff_t offset = UTF16IndexToOffset(text(), 0, index); | |
297 // Check that the index is marked as a legitimate cursor position by Pango. | |
298 return offset < num_log_attrs_ && log_attrs_[offset].is_cursor_position; | |
299 } | |
300 | |
301 void RenderTextPango::ResetLayout() { | |
302 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every | |
303 // operation that triggers ResetLayout(). | |
304 if (layout_) { | |
305 // TODO(msw): Keep |layout_| across text changes, etc.; it can be re-used. | |
306 g_object_unref(layout_); | |
307 layout_ = NULL; | |
308 } | |
309 if (current_line_) { | |
310 pango_layout_line_unref(current_line_); | |
311 current_line_ = NULL; | |
312 } | |
313 if (log_attrs_) { | |
314 g_free(log_attrs_); | |
315 log_attrs_ = NULL; | |
316 num_log_attrs_ = 0; | |
317 } | |
318 layout_text_ = NULL; | |
319 } | |
320 | |
321 void RenderTextPango::EnsureLayout() { | |
322 if (layout_ == NULL) { | |
323 cairo_surface_t* surface = | |
324 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); | |
325 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_surface_status(surface)); | |
326 cairo_t* cr = cairo_create(surface); | |
327 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_status(cr)); | |
328 | |
329 layout_ = pango_cairo_create_layout(cr); | |
330 CHECK_NE(static_cast<PangoLayout*>(NULL), layout_); | |
331 cairo_destroy(cr); | |
332 cairo_surface_destroy(surface); | |
333 | |
334 SetUpPangoLayout(layout_, GetLayoutText(), font_list(), GetTextDirection(), | |
335 Canvas::DefaultCanvasTextAlignment()); | |
336 | |
337 // No width set so that the x-axis position is relative to the start of the | |
338 // text. ToViewPoint and ToTextPoint take care of the position conversion | |
339 // between text space and view spaces. | |
340 pango_layout_set_width(layout_, -1); | |
341 // TODO(xji): If RenderText will be used for displaying purpose, such as | |
342 // label, we will need to remove the single-line-mode setting. | |
343 pango_layout_set_single_paragraph_mode(layout_, true); | |
344 | |
345 layout_text_ = pango_layout_get_text(layout_); | |
346 SetupPangoAttributes(layout_); | |
347 | |
348 current_line_ = pango_layout_get_line_readonly(layout_, 0); | |
349 CHECK_NE(static_cast<PangoLayoutLine*>(NULL), current_line_); | |
350 pango_layout_line_ref(current_line_); | |
351 | |
352 pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_); | |
353 } | |
354 } | |
355 | |
356 void RenderTextPango::SetupPangoAttributes(PangoLayout* layout) { | |
357 PangoAttrList* attrs = pango_attr_list_new(); | |
358 | |
359 // Splitting text runs to accommodate styling can break Arabic glyph shaping. | |
360 // Only split text runs as needed for bold and italic font styles changes. | |
361 BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin(); | |
362 BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin(); | |
363 while (bold != styles()[BOLD].breaks().end() && | |
364 italic != styles()[ITALIC].breaks().end()) { | |
365 const int style = (bold->second ? Font::BOLD : 0) | | |
366 (italic->second ? Font::ITALIC : 0); | |
367 const size_t bold_end = styles()[BOLD].GetRange(bold).end(); | |
368 const size_t italic_end = styles()[ITALIC].GetRange(italic).end(); | |
369 const size_t style_end = std::min(bold_end, italic_end); | |
370 if (style != font_list().GetFontStyle()) { | |
371 // TODO(derat): Don't interpret gfx::FontList font descriptions as Pango | |
372 // font descriptions: http://crbug.com/393067 | |
373 FontList derived_font_list = font_list().DeriveWithStyle(style); | |
374 ScopedPangoFontDescription desc( | |
375 derived_font_list.GetFontDescriptionString()); | |
376 | |
377 PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get()); | |
378 pango_attr->start_index = | |
379 TextIndexToLayoutIndex(std::max(bold->first, italic->first)); | |
380 pango_attr->end_index = TextIndexToLayoutIndex(style_end); | |
381 pango_attr_list_insert(attrs, pango_attr); | |
382 } | |
383 bold += bold_end == style_end ? 1 : 0; | |
384 italic += italic_end == style_end ? 1 : 0; | |
385 } | |
386 DCHECK(bold == styles()[BOLD].breaks().end()); | |
387 DCHECK(italic == styles()[ITALIC].breaks().end()); | |
388 | |
389 pango_layout_set_attributes(layout, attrs); | |
390 pango_attr_list_unref(attrs); | |
391 } | |
392 | |
393 void RenderTextPango::DrawVisualText(Canvas* canvas) { | |
394 DCHECK(layout_); | |
395 | |
396 // Skia will draw glyphs with respect to the baseline. | |
397 Vector2d offset(GetLineOffset(0) + Vector2d(0, GetLayoutTextBaseline())); | |
398 | |
399 SkScalar x = SkIntToScalar(offset.x()); | |
400 SkScalar y = SkIntToScalar(offset.y()); | |
401 | |
402 std::vector<SkPoint> pos; | |
403 std::vector<uint16> glyphs; | |
404 | |
405 internal::SkiaTextRenderer renderer(canvas); | |
406 ApplyFadeEffects(&renderer); | |
407 ApplyTextShadows(&renderer); | |
408 renderer.SetFontRenderParams( | |
409 font_list().GetPrimaryFont().GetFontRenderParams(), | |
410 background_is_transparent()); | |
411 | |
412 // Temporarily apply composition underlines and selection colors. | |
413 ApplyCompositionAndSelectionStyles(); | |
414 | |
415 internal::StyleIterator style(colors(), styles()); | |
416 for (GSList* it = current_line_->runs; it; it = it->next) { | |
417 // Skip painting runs outside the display area. | |
418 if (SkScalarTruncToInt(x) >= display_rect().right()) | |
419 break; | |
420 | |
421 PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data); | |
422 int glyph_count = run->glyphs->num_glyphs; | |
423 if (glyph_count == 0) | |
424 continue; | |
425 | |
426 ScopedPangoFontDescription desc( | |
427 pango_font_describe(run->item->analysis.font)); | |
428 const std::string family_name = | |
429 pango_font_description_get_family(desc.get()); | |
430 renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get())); | |
431 | |
432 glyphs.resize(glyph_count); | |
433 pos.resize(glyph_count); | |
434 | |
435 // Track the current glyph and the glyph at the start of its styled range. | |
436 int glyph_index = 0; | |
437 int style_start_glyph_index = glyph_index; | |
438 | |
439 // Track the x-coordinates for each styled range (|x| marks the current). | |
440 SkScalar style_start_x = x; | |
441 | |
442 // Track the current style and its text (not layout) index range. | |
443 style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index)); | |
444 Range style_range = style.GetRange(); | |
445 | |
446 do { | |
447 const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index]; | |
448 glyphs[glyph_index] = static_cast<uint16>(glyph.glyph); | |
449 // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units | |
450 // are not rounded to the pixel grid if subpixel positioning is enabled. | |
451 pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset), | |
452 y + pango_units_to_double(glyph.geometry.y_offset)); | |
453 x += pango_units_to_double(glyph.geometry.width); | |
454 | |
455 ++glyph_index; | |
456 // If this is the last glyph of the range or the last glyph inside the | |
457 // display area (which would cause early termination of the loop), paint | |
458 // the range. | |
459 const size_t glyph_text_index = (glyph_index == glyph_count) ? | |
460 style_range.end() : GetGlyphTextIndex(run, glyph_index); | |
461 if (!IndexInRange(style_range, glyph_text_index) || | |
462 SkScalarTruncToInt(x) >= display_rect().right()) { | |
463 // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph | |
464 // but can span multiple styles, Pango splits the | |
465 // styles evenly over the glyph. We can do this too by | |
466 // clipping and drawing the glyph several times. | |
467 renderer.SetForegroundColor(style.color()); | |
468 const int font_style = (style.style(BOLD) ? Font::BOLD : 0) | | |
469 (style.style(ITALIC) ? Font::ITALIC : 0); | |
470 renderer.SetFontFamilyWithStyle(family_name, font_style); | |
471 renderer.DrawPosText(&pos[style_start_glyph_index], | |
472 &glyphs[style_start_glyph_index], | |
473 glyph_index - style_start_glyph_index); | |
474 if (style.style(UNDERLINE)) | |
475 SetPangoUnderlineMetrics(desc.get(), &renderer); | |
476 renderer.DrawDecorations(style_start_x, y, x - style_start_x, | |
477 style.style(UNDERLINE), style.style(STRIKE), | |
478 style.style(DIAGONAL_STRIKE)); | |
479 style.UpdatePosition(glyph_text_index); | |
480 style_range = style.GetRange(); | |
481 style_start_glyph_index = glyph_index; | |
482 style_start_x = x; | |
483 } | |
484 // Terminates loop when the end of the range has been reached or the next | |
485 // glyph falls outside the display area. | |
486 } while (glyph_index < glyph_count && | |
487 SkScalarTruncToInt(x) < display_rect().right()); | |
488 } | |
489 | |
490 renderer.EndDiagonalStrike(); | |
491 | |
492 // Undo the temporarily applied composition underlines and selection colors. | |
493 UndoCompositionAndSelectionStyles(); | |
494 } | |
495 | |
496 GSList* RenderTextPango::GetRunContainingCaret( | |
497 const SelectionModel& caret) const { | |
498 size_t position = TextIndexToLayoutIndex(caret.caret_pos()); | |
499 LogicalCursorDirection affinity = caret.caret_affinity(); | |
500 GSList* run = current_line_->runs; | |
501 while (run) { | |
502 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
503 Range item_range(item->offset, item->offset + item->length); | |
504 if (RangeContainsCaret(item_range, position, affinity)) | |
505 return run; | |
506 run = run->next; | |
507 } | |
508 return NULL; | |
509 } | |
510 | |
511 SelectionModel RenderTextPango::FirstSelectionModelInsideRun( | |
512 const PangoItem* item) { | |
513 size_t caret = IndexOfAdjacentGrapheme( | |
514 LayoutIndexToTextIndex(item->offset), CURSOR_FORWARD); | |
515 return SelectionModel(caret, CURSOR_BACKWARD); | |
516 } | |
517 | |
518 SelectionModel RenderTextPango::LastSelectionModelInsideRun( | |
519 const PangoItem* item) { | |
520 size_t caret = IndexOfAdjacentGrapheme( | |
521 LayoutIndexToTextIndex(item->offset + item->length), CURSOR_BACKWARD); | |
522 return SelectionModel(caret, CURSOR_FORWARD); | |
523 } | |
524 | |
525 size_t RenderTextPango::GetGlyphTextIndex(PangoLayoutRun* run, | |
526 int glyph_index) const { | |
527 return LayoutIndexToTextIndex(run->item->offset + | |
528 run->glyphs->log_clusters[glyph_index]); | |
529 } | |
530 | |
531 RenderText* RenderText::CreateNativeInstance() { | |
532 return new RenderTextPango; | |
533 } | |
534 | |
535 } // namespace gfx | |
OLD | NEW |