| 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 |