cc: Add more eviction categories to picture layer impl.
[chromium-blink-merge.git] / ui / gfx / render_text_pango.cc
blob86a52425a00e60ce9a66a538f61b9a4139fc9be9
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.
5 #include "ui/gfx/render_text_pango.h"
7 #include <pango/pangocairo.h>
8 #include <algorithm>
9 #include <string>
10 #include <vector>
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"
23 namespace gfx {
25 namespace {
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;
34 return prev;
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);
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();
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 pango_quantize_line_geometry(&thickness, &position);
59 // Note: pango_quantize_line_geometry() guarantees pixel boundaries, so
60 // PANGO_PIXELS() is safe to use.
61 renderer->SetUnderlineMetrics(PANGO_PIXELS(thickness),
62 PANGO_PIXELS(position));
65 } // namespace
67 // TODO(xji): index saved in upper layer is utf16 index. Pango uses utf8 index.
68 // Since caret_pos is used internally, we could save utf8 index for caret_pos
69 // to avoid conversion.
71 RenderTextPango::RenderTextPango()
72 : layout_(NULL),
73 current_line_(NULL),
74 log_attrs_(NULL),
75 num_log_attrs_(0),
76 layout_text_(NULL) {
79 RenderTextPango::~RenderTextPango() {
80 ResetLayout();
83 Size RenderTextPango::GetStringSize() {
84 EnsureLayout();
85 int width = 0, height = 0;
86 pango_layout_get_pixel_size(layout_, &width, &height);
88 // Pango returns 0 widths for very long strings (of 0x40000 chars or more).
89 // This is caused by an int overflow in pango_glyph_string_extents_range.
90 // Absurdly long strings may even report non-zero garbage values for width;
91 // while detecting that isn't worthwhile, this handles the 0 width cases.
92 const long kAbsurdLength = 100000;
93 if (width == 0 && g_utf8_strlen(layout_text_, -1) > kAbsurdLength)
94 width = font_list().GetExpectedTextWidth(g_utf8_strlen(layout_text_, -1));
96 // Keep a consistent height between this particular string's PangoLayout and
97 // potentially larger text supported by the FontList.
98 // For example, if a text field contains a Japanese character, which is
99 // smaller than Latin ones, and then later a Latin one is inserted, this
100 // ensures that the text baseline does not shift.
101 return Size(width, std::max(height, font_list().GetHeight()));
104 SelectionModel RenderTextPango::FindCursorPosition(const Point& point) {
105 EnsureLayout();
107 if (text().empty())
108 return SelectionModel(0, CURSOR_FORWARD);
110 Point p(ToTextPoint(point));
112 // When the point is outside of text, return HOME/END position.
113 if (p.x() < 0)
114 return EdgeSelectionModel(CURSOR_LEFT);
115 if (p.x() > GetStringSize().width())
116 return EdgeSelectionModel(CURSOR_RIGHT);
118 int caret_pos = 0, trailing = 0;
119 pango_layout_xy_to_index(layout_, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE,
120 &caret_pos, &trailing);
122 DCHECK_GE(trailing, 0);
123 if (trailing > 0) {
124 caret_pos = g_utf8_offset_to_pointer(layout_text_ + caret_pos,
125 trailing) - layout_text_;
126 DCHECK_LE(static_cast<size_t>(caret_pos), strlen(layout_text_));
129 return SelectionModel(LayoutIndexToTextIndex(caret_pos),
130 (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD);
133 std::vector<RenderText::FontSpan> RenderTextPango::GetFontSpansForTesting() {
134 EnsureLayout();
136 std::vector<RenderText::FontSpan> spans;
137 for (GSList* it = current_line_->runs; it; it = it->next) {
138 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(it->data)->item;
139 const int start = LayoutIndexToTextIndex(item->offset);
140 const int end = LayoutIndexToTextIndex(item->offset + item->length);
141 const Range range(start, end);
143 ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font));
144 spans.push_back(RenderText::FontSpan(Font(desc.get()), range));
147 return spans;
150 int RenderTextPango::GetLayoutTextBaseline() {
151 EnsureLayout();
152 return PANGO_PIXELS(pango_layout_get_baseline(layout_));
155 SelectionModel RenderTextPango::AdjacentCharSelectionModel(
156 const SelectionModel& selection,
157 VisualCursorDirection direction) {
158 GSList* run = GetRunContainingCaret(selection);
159 if (!run) {
160 // The cursor is not in any run: we're at the visual and logical edge.
161 SelectionModel edge = EdgeSelectionModel(direction);
162 if (edge.caret_pos() == selection.caret_pos())
163 return edge;
164 else
165 run = (direction == CURSOR_RIGHT) ?
166 current_line_->runs : g_slist_last(current_line_->runs);
167 } else {
168 // If the cursor is moving within the current run, just move it by one
169 // grapheme in the appropriate direction.
170 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
171 size_t caret = selection.caret_pos();
172 if (IsForwardMotion(direction, item)) {
173 if (caret < LayoutIndexToTextIndex(item->offset + item->length)) {
174 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
175 return SelectionModel(caret, CURSOR_BACKWARD);
177 } else {
178 if (caret > LayoutIndexToTextIndex(item->offset)) {
179 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
180 return SelectionModel(caret, CURSOR_FORWARD);
183 // The cursor is at the edge of a run; move to the visually adjacent run.
184 // TODO(xji): Keep a vector of runs to avoid using a singly-linked list.
185 run = (direction == CURSOR_RIGHT) ?
186 run->next : GSListPrevious(current_line_->runs, run);
187 if (!run)
188 return EdgeSelectionModel(direction);
190 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
191 return IsForwardMotion(direction, item) ?
192 FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item);
195 SelectionModel RenderTextPango::AdjacentWordSelectionModel(
196 const SelectionModel& selection,
197 VisualCursorDirection direction) {
198 if (obscured())
199 return EdgeSelectionModel(direction);
201 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
202 bool success = iter.Init();
203 DCHECK(success);
204 if (!success)
205 return selection;
207 SelectionModel cur(selection);
208 for (;;) {
209 cur = AdjacentCharSelectionModel(cur, direction);
210 GSList* run = GetRunContainingCaret(cur);
211 if (!run)
212 break;
213 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
214 size_t cursor = cur.caret_pos();
215 if (IsForwardMotion(direction, item) ?
216 iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor))
217 break;
220 return cur;
223 Range RenderTextPango::GetGlyphBounds(size_t index) {
224 EnsureLayout();
225 PangoRectangle pos;
226 pango_layout_index_to_pos(layout_, TextIndexToLayoutIndex(index), &pos);
227 // TODO(derat): Support fractional ranges for subpixel positioning?
228 return Range(PANGO_PIXELS(pos.x), PANGO_PIXELS(pos.x + pos.width));
231 std::vector<Rect> RenderTextPango::GetSubstringBounds(const Range& range) {
232 DCHECK_LE(range.GetMax(), text().length());
233 if (range.is_empty())
234 return std::vector<Rect>();
236 EnsureLayout();
237 int* ranges = NULL;
238 int n_ranges = 0;
239 pango_layout_line_get_x_ranges(current_line_,
240 TextIndexToLayoutIndex(range.GetMin()),
241 TextIndexToLayoutIndex(range.GetMax()),
242 &ranges,
243 &n_ranges);
245 const int height = GetStringSize().height();
247 std::vector<Rect> bounds;
248 for (int i = 0; i < n_ranges; ++i) {
249 // TODO(derat): Support fractional bounds for subpixel positioning?
250 int x = PANGO_PIXELS(ranges[2 * i]);
251 int width = PANGO_PIXELS(ranges[2 * i + 1]) - x;
252 Rect rect(x, 0, width, height);
253 rect.set_origin(ToViewPoint(rect.origin()));
254 bounds.push_back(rect);
256 g_free(ranges);
257 return bounds;
260 size_t RenderTextPango::TextIndexToLayoutIndex(size_t index) const {
261 DCHECK(layout_);
262 ptrdiff_t offset = UTF16IndexToOffset(text(), 0, index);
263 // Clamp layout indices to the length of the text actually used for layout.
264 offset = std::min<size_t>(offset, g_utf8_strlen(layout_text_, -1));
265 const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset);
266 return (layout_pointer - layout_text_);
269 size_t RenderTextPango::LayoutIndexToTextIndex(size_t index) const {
270 DCHECK(layout_);
271 const char* layout_pointer = layout_text_ + index;
272 const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer);
273 return UTF16OffsetToIndex(text(), 0, offset);
276 bool RenderTextPango::IsValidCursorIndex(size_t index) {
277 if (index == 0 || index == text().length())
278 return true;
279 if (!IsValidLogicalIndex(index))
280 return false;
282 EnsureLayout();
283 ptrdiff_t offset = UTF16IndexToOffset(text(), 0, index);
284 // Check that the index is marked as a legitimate cursor position by Pango.
285 return offset < num_log_attrs_ && log_attrs_[offset].is_cursor_position;
288 void RenderTextPango::ResetLayout() {
289 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every
290 // operation that triggers ResetLayout().
291 if (layout_) {
292 // TODO(msw): Keep |layout_| across text changes, etc.; it can be re-used.
293 g_object_unref(layout_);
294 layout_ = NULL;
296 if (current_line_) {
297 pango_layout_line_unref(current_line_);
298 current_line_ = NULL;
300 if (log_attrs_) {
301 g_free(log_attrs_);
302 log_attrs_ = NULL;
303 num_log_attrs_ = 0;
305 layout_text_ = NULL;
308 void RenderTextPango::EnsureLayout() {
309 if (layout_ == NULL) {
310 cairo_surface_t* surface =
311 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
312 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_surface_status(surface));
313 cairo_t* cr = cairo_create(surface);
314 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_status(cr));
316 layout_ = pango_cairo_create_layout(cr);
317 CHECK_NE(static_cast<PangoLayout*>(NULL), layout_);
318 cairo_destroy(cr);
319 cairo_surface_destroy(surface);
321 SetUpPangoLayout(layout_, GetLayoutText(), font_list(), GetTextDirection(),
322 Canvas::DefaultCanvasTextAlignment());
324 // No width set so that the x-axis position is relative to the start of the
325 // text. ToViewPoint and ToTextPoint take care of the position conversion
326 // between text space and view spaces.
327 pango_layout_set_width(layout_, -1);
328 // TODO(xji): If RenderText will be used for displaying purpose, such as
329 // label, we will need to remove the single-line-mode setting.
330 pango_layout_set_single_paragraph_mode(layout_, true);
332 layout_text_ = pango_layout_get_text(layout_);
333 SetupPangoAttributes(layout_);
335 current_line_ = pango_layout_get_line_readonly(layout_, 0);
336 CHECK_NE(static_cast<PangoLayoutLine*>(NULL), current_line_);
337 pango_layout_line_ref(current_line_);
339 pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_);
343 void RenderTextPango::SetupPangoAttributes(PangoLayout* layout) {
344 PangoAttrList* attrs = pango_attr_list_new();
346 // Splitting text runs to accommodate styling can break Arabic glyph shaping.
347 // Only split text runs as needed for bold and italic font styles changes.
348 BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin();
349 BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin();
350 while (bold != styles()[BOLD].breaks().end() &&
351 italic != styles()[ITALIC].breaks().end()) {
352 const int style = (bold->second ? Font::BOLD : 0) |
353 (italic->second ? Font::ITALIC : 0);
354 const size_t bold_end = styles()[BOLD].GetRange(bold).end();
355 const size_t italic_end = styles()[ITALIC].GetRange(italic).end();
356 const size_t style_end = std::min(bold_end, italic_end);
357 if (style != font_list().GetFontStyle()) {
358 // TODO(derat): Don't interpret gfx::FontList font descriptions as Pango
359 // font descriptions: http://crbug.com/393067
360 FontList derived_font_list = font_list().DeriveWithStyle(style);
361 ScopedPangoFontDescription desc(
362 derived_font_list.GetFontDescriptionString());
364 PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get());
365 pango_attr->start_index =
366 TextIndexToLayoutIndex(std::max(bold->first, italic->first));
367 pango_attr->end_index = TextIndexToLayoutIndex(style_end);
368 pango_attr_list_insert(attrs, pango_attr);
370 bold += bold_end == style_end ? 1 : 0;
371 italic += italic_end == style_end ? 1 : 0;
373 DCHECK(bold == styles()[BOLD].breaks().end());
374 DCHECK(italic == styles()[ITALIC].breaks().end());
376 pango_layout_set_attributes(layout, attrs);
377 pango_attr_list_unref(attrs);
380 void RenderTextPango::DrawVisualText(Canvas* canvas) {
381 DCHECK(layout_);
383 // Skia will draw glyphs with respect to the baseline.
384 Vector2d offset(GetLineOffset(0) + Vector2d(0, GetLayoutTextBaseline()));
386 SkScalar x = SkIntToScalar(offset.x());
387 SkScalar y = SkIntToScalar(offset.y());
389 std::vector<SkPoint> pos;
390 std::vector<uint16> glyphs;
392 internal::SkiaTextRenderer renderer(canvas);
393 ApplyFadeEffects(&renderer);
394 ApplyTextShadows(&renderer);
395 renderer.SetFontRenderParams(
396 font_list().GetPrimaryFont().GetFontRenderParams(),
397 background_is_transparent());
399 // Temporarily apply composition underlines and selection colors.
400 ApplyCompositionAndSelectionStyles();
402 internal::StyleIterator style(colors(), styles());
403 for (GSList* it = current_line_->runs; it; it = it->next) {
404 PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data);
405 int glyph_count = run->glyphs->num_glyphs;
406 // TODO(msw): Skip painting runs outside the display rect area, like Win.
407 if (glyph_count == 0)
408 continue;
410 ScopedPangoFontDescription desc(
411 pango_font_describe(run->item->analysis.font));
412 const std::string family_name =
413 pango_font_description_get_family(desc.get());
414 renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get()));
416 glyphs.resize(glyph_count);
417 pos.resize(glyph_count);
419 // Track the current glyph and the glyph at the start of its styled range.
420 int glyph_index = 0;
421 int style_start_glyph_index = glyph_index;
423 // Track the x-coordinates for each styled range (|x| marks the current).
424 SkScalar style_start_x = x;
426 // Track the current style and its text (not layout) index range.
427 style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index));
428 Range style_range = style.GetRange();
430 do {
431 const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index];
432 glyphs[glyph_index] = static_cast<uint16>(glyph.glyph);
433 // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units
434 // are not rounded to the pixel grid if subpixel positioning is enabled.
435 pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset),
436 y + pango_units_to_double(glyph.geometry.y_offset));
437 x += pango_units_to_double(glyph.geometry.width);
439 ++glyph_index;
440 const size_t glyph_text_index = (glyph_index == glyph_count) ?
441 style_range.end() : GetGlyphTextIndex(run, glyph_index);
442 if (!IndexInRange(style_range, glyph_text_index)) {
443 // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph
444 // but can span multiple styles, Pango splits the
445 // styles evenly over the glyph. We can do this too by
446 // clipping and drawing the glyph several times.
447 renderer.SetForegroundColor(style.color());
448 const int font_style = (style.style(BOLD) ? Font::BOLD : 0) |
449 (style.style(ITALIC) ? Font::ITALIC : 0);
450 renderer.SetFontFamilyWithStyle(family_name, font_style);
451 renderer.DrawPosText(&pos[style_start_glyph_index],
452 &glyphs[style_start_glyph_index],
453 glyph_index - style_start_glyph_index);
454 if (style.style(UNDERLINE))
455 SetPangoUnderlineMetrics(desc.get(), &renderer);
456 renderer.DrawDecorations(style_start_x, y, x - style_start_x,
457 style.style(UNDERLINE), style.style(STRIKE),
458 style.style(DIAGONAL_STRIKE));
459 style.UpdatePosition(glyph_text_index);
460 style_range = style.GetRange();
461 style_start_glyph_index = glyph_index;
462 style_start_x = x;
464 } while (glyph_index < glyph_count);
467 renderer.EndDiagonalStrike();
469 // Undo the temporarily applied composition underlines and selection colors.
470 UndoCompositionAndSelectionStyles();
473 GSList* RenderTextPango::GetRunContainingCaret(
474 const SelectionModel& caret) const {
475 size_t position = TextIndexToLayoutIndex(caret.caret_pos());
476 LogicalCursorDirection affinity = caret.caret_affinity();
477 GSList* run = current_line_->runs;
478 while (run) {
479 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
480 Range item_range(item->offset, item->offset + item->length);
481 if (RangeContainsCaret(item_range, position, affinity))
482 return run;
483 run = run->next;
485 return NULL;
488 SelectionModel RenderTextPango::FirstSelectionModelInsideRun(
489 const PangoItem* item) {
490 size_t caret = IndexOfAdjacentGrapheme(
491 LayoutIndexToTextIndex(item->offset), CURSOR_FORWARD);
492 return SelectionModel(caret, CURSOR_BACKWARD);
495 SelectionModel RenderTextPango::LastSelectionModelInsideRun(
496 const PangoItem* item) {
497 size_t caret = IndexOfAdjacentGrapheme(
498 LayoutIndexToTextIndex(item->offset + item->length), CURSOR_BACKWARD);
499 return SelectionModel(caret, CURSOR_FORWARD);
502 size_t RenderTextPango::GetGlyphTextIndex(PangoLayoutRun* run,
503 int glyph_index) const {
504 return LayoutIndexToTextIndex(run->item->offset +
505 run->glyphs->log_clusters[glyph_index]);
508 RenderText* RenderText::CreateNativeInstance() {
509 return new RenderTextPango;
512 } // namespace gfx