86a52425a00e60ce9a66a538f61b9a4139fc9be9
[platform/framework/web/crosswalk.git] / src / ui / gfx / render_text_pango.cc
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   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));
63 }
64
65 }  // namespace
66
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.
70
71 RenderTextPango::RenderTextPango()
72     : layout_(NULL),
73       current_line_(NULL),
74       log_attrs_(NULL),
75       num_log_attrs_(0),
76       layout_text_(NULL) {
77 }
78
79 RenderTextPango::~RenderTextPango() {
80   ResetLayout();
81 }
82
83 Size RenderTextPango::GetStringSize() {
84   EnsureLayout();
85   int width = 0, height = 0;
86   pango_layout_get_pixel_size(layout_, &width, &height);
87
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));
95
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()));
102 }
103
104 SelectionModel RenderTextPango::FindCursorPosition(const Point& point) {
105   EnsureLayout();
106
107   if (text().empty())
108     return SelectionModel(0, CURSOR_FORWARD);
109
110   Point p(ToTextPoint(point));
111
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);
117
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);
121
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_));
127   }
128
129   return SelectionModel(LayoutIndexToTextIndex(caret_pos),
130                         (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD);
131 }
132
133 std::vector<RenderText::FontSpan> RenderTextPango::GetFontSpansForTesting() {
134   EnsureLayout();
135
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);
142
143     ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font));
144     spans.push_back(RenderText::FontSpan(Font(desc.get()), range));
145   }
146
147   return spans;
148 }
149
150 int RenderTextPango::GetLayoutTextBaseline() {
151   EnsureLayout();
152   return PANGO_PIXELS(pango_layout_get_baseline(layout_));
153 }
154
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);
176       }
177     } else {
178       if (caret > LayoutIndexToTextIndex(item->offset)) {
179         caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
180         return SelectionModel(caret, CURSOR_FORWARD);
181       }
182     }
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);
189   }
190   PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
191   return IsForwardMotion(direction, item) ?
192       FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item);
193 }
194
195 SelectionModel RenderTextPango::AdjacentWordSelectionModel(
196     const SelectionModel& selection,
197     VisualCursorDirection direction) {
198   if (obscured())
199     return EdgeSelectionModel(direction);
200
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;
206
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;
218   }
219
220   return cur;
221 }
222
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));
229 }
230
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>();
235
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);
244
245   const int height = GetStringSize().height();
246
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);
255   }
256   g_free(ranges);
257   return bounds;
258 }
259
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_);
267 }
268
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);
274 }
275
276 bool RenderTextPango::IsValidCursorIndex(size_t index) {
277   if (index == 0 || index == text().length())
278     return true;
279   if (!IsValidLogicalIndex(index))
280     return false;
281
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;
286 }
287
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;
295   }
296   if (current_line_) {
297     pango_layout_line_unref(current_line_);
298     current_line_ = NULL;
299   }
300   if (log_attrs_) {
301     g_free(log_attrs_);
302     log_attrs_ = NULL;
303     num_log_attrs_ = 0;
304   }
305   layout_text_ = NULL;
306 }
307
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));
315
316     layout_ = pango_cairo_create_layout(cr);
317     CHECK_NE(static_cast<PangoLayout*>(NULL), layout_);
318     cairo_destroy(cr);
319     cairo_surface_destroy(surface);
320
321     SetUpPangoLayout(layout_, GetLayoutText(), font_list(), GetTextDirection(),
322                      Canvas::DefaultCanvasTextAlignment());
323
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);
331
332     layout_text_ = pango_layout_get_text(layout_);
333     SetupPangoAttributes(layout_);
334
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_);
338
339     pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_);
340   }
341 }
342
343 void RenderTextPango::SetupPangoAttributes(PangoLayout* layout) {
344   PangoAttrList* attrs = pango_attr_list_new();
345
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());
363
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);
369     }
370     bold += bold_end == style_end ? 1 : 0;
371     italic += italic_end == style_end ? 1 : 0;
372   }
373   DCHECK(bold == styles()[BOLD].breaks().end());
374   DCHECK(italic == styles()[ITALIC].breaks().end());
375
376   pango_layout_set_attributes(layout, attrs);
377   pango_attr_list_unref(attrs);
378 }
379
380 void RenderTextPango::DrawVisualText(Canvas* canvas) {
381   DCHECK(layout_);
382
383   // Skia will draw glyphs with respect to the baseline.
384   Vector2d offset(GetLineOffset(0) + Vector2d(0, GetLayoutTextBaseline()));
385
386   SkScalar x = SkIntToScalar(offset.x());
387   SkScalar y = SkIntToScalar(offset.y());
388
389   std::vector<SkPoint> pos;
390   std::vector<uint16> glyphs;
391
392   internal::SkiaTextRenderer renderer(canvas);
393   ApplyFadeEffects(&renderer);
394   ApplyTextShadows(&renderer);
395   renderer.SetFontRenderParams(
396       font_list().GetPrimaryFont().GetFontRenderParams(),
397       background_is_transparent());
398
399   // Temporarily apply composition underlines and selection colors.
400   ApplyCompositionAndSelectionStyles();
401
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;
409
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()));
415
416     glyphs.resize(glyph_count);
417     pos.resize(glyph_count);
418
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;
422
423     // Track the x-coordinates for each styled range (|x| marks the current).
424     SkScalar style_start_x = x;
425
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();
429
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);
438
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;
463       }
464     } while (glyph_index < glyph_count);
465   }
466
467   renderer.EndDiagonalStrike();
468
469   // Undo the temporarily applied composition underlines and selection colors.
470   UndoCompositionAndSelectionStyles();
471 }
472
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;
484   }
485   return NULL;
486 }
487
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);
493 }
494
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);
500 }
501
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]);
506 }
507
508 RenderText* RenderText::CreateNativeInstance() {
509   return new RenderTextPango;
510 }
511
512 }  // namespace gfx