render_text_mac.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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_mac.h"
6
7#include <ApplicationServices/ApplicationServices.h>
8
9#include <algorithm>
10#include <cmath>
11#include <utility>
12
13#include "base/mac/foundation_util.h"
14#include "base/mac/scoped_cftyperef.h"
15#include "base/strings/sys_string_conversions.h"
16#include "skia/ext/skia_utils_mac.h"
17
18namespace gfx {
19
20RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) {
21}
22
23RenderTextMac::~RenderTextMac() {
24}
25
26Size RenderTextMac::GetStringSize() {
27  EnsureLayout();
28  return Size(std::ceil(string_size_.width()), string_size_.height());
29}
30
31SizeF RenderTextMac::GetStringSizeF() {
32  EnsureLayout();
33  return string_size_;
34}
35
36SelectionModel RenderTextMac::FindCursorPosition(const Point& point) {
37  // TODO(asvitkine): Implement this. http://crbug.com/131618
38  return SelectionModel();
39}
40
41std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() {
42  EnsureLayout();
43  if (!runs_valid_)
44    ComputeRuns();
45
46  std::vector<RenderText::FontSpan> spans;
47  for (size_t i = 0; i < runs_.size(); ++i) {
48    Font font(runs_[i].font_name, runs_[i].text_size);
49    const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run);
50    const Range range(cf_range.location, cf_range.location + cf_range.length);
51    spans.push_back(RenderText::FontSpan(font, range));
52  }
53
54  return spans;
55}
56
57int RenderTextMac::GetLayoutTextBaseline() {
58  EnsureLayout();
59  return common_baseline_;
60}
61
62SelectionModel RenderTextMac::AdjacentCharSelectionModel(
63    const SelectionModel& selection,
64    VisualCursorDirection direction) {
65  // TODO(asvitkine): Implement this. http://crbug.com/131618
66  return SelectionModel();
67}
68
69SelectionModel RenderTextMac::AdjacentWordSelectionModel(
70    const SelectionModel& selection,
71    VisualCursorDirection direction) {
72  // TODO(asvitkine): Implement this. http://crbug.com/131618
73  return SelectionModel();
74}
75
76Range RenderTextMac::GetGlyphBounds(size_t index) {
77  // TODO(asvitkine): Implement this. http://crbug.com/131618
78  return Range();
79}
80
81std::vector<Rect> RenderTextMac::GetSubstringBounds(const Range& range) {
82  // TODO(asvitkine): Implement this. http://crbug.com/131618
83  return std::vector<Rect>();
84}
85
86size_t RenderTextMac::TextIndexToLayoutIndex(size_t index) const {
87  // TODO(asvitkine): Implement this. http://crbug.com/131618
88  return index;
89}
90
91size_t RenderTextMac::LayoutIndexToTextIndex(size_t index) const {
92  // TODO(asvitkine): Implement this. http://crbug.com/131618
93  return index;
94}
95
96bool RenderTextMac::IsValidCursorIndex(size_t index) {
97  // TODO(asvitkine): Implement this. http://crbug.com/131618
98  return IsValidLogicalIndex(index);
99}
100
101void RenderTextMac::ResetLayout() {
102  line_.reset();
103  attributes_.reset();
104  runs_.clear();
105  runs_valid_ = false;
106}
107
108void RenderTextMac::EnsureLayout() {
109  if (line_.get())
110    return;
111  runs_.clear();
112  runs_valid_ = false;
113
114  CTFontRef ct_font = base::mac::NSToCFCast(
115      font_list().GetPrimaryFont().GetNativeFont());
116
117  const void* keys[] = { kCTFontAttributeName };
118  const void* values[] = { ct_font };
119  base::ScopedCFTypeRef<CFDictionaryRef> attributes(
120      CFDictionaryCreate(NULL,
121                         keys,
122                         values,
123                         arraysize(keys),
124                         NULL,
125                         &kCFTypeDictionaryValueCallBacks));
126
127  base::ScopedCFTypeRef<CFStringRef> cf_text(
128      base::SysUTF16ToCFStringRef(text()));
129  base::ScopedCFTypeRef<CFAttributedStringRef> attr_text(
130      CFAttributedStringCreate(NULL, cf_text, attributes));
131  base::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable(
132      CFAttributedStringCreateMutableCopy(NULL, 0, attr_text));
133
134  // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
135  // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
136
137  ApplyStyles(attr_text_mutable, ct_font);
138  line_.reset(CTLineCreateWithAttributedString(attr_text_mutable));
139
140  CGFloat ascent = 0;
141  CGFloat descent = 0;
142  CGFloat leading = 0;
143  // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
144  double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading);
145  // Ensure ascent and descent are not smaller than ones of the font list.
146  // Keep them tall enough to draw often-used characters.
147  // For example, if a text field contains a Japanese character, which is
148  // smaller than Latin ones, and then later a Latin one is inserted, this
149  // ensures that the text baseline does not shift.
150  CGFloat font_list_height = font_list().GetHeight();
151  CGFloat font_list_baseline = font_list().GetBaseline();
152  ascent = std::max(ascent, font_list_baseline);
153  descent = std::max(descent, font_list_height - font_list_baseline);
154  string_size_ = SizeF(width, ascent + descent + leading);
155  common_baseline_ = ascent;
156}
157
158void RenderTextMac::DrawVisualText(Canvas* canvas) {
159  DCHECK(line_);
160  if (!runs_valid_)
161    ComputeRuns();
162
163  internal::SkiaTextRenderer renderer(canvas);
164  ApplyFadeEffects(&renderer);
165  ApplyTextShadows(&renderer);
166
167  for (size_t i = 0; i < runs_.size(); ++i) {
168    const TextRun& run = runs_[i];
169    renderer.SetForegroundColor(run.foreground);
170    renderer.SetTextSize(run.text_size);
171    renderer.SetFontFamilyWithStyle(run.font_name, run.font_style);
172    renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0],
173                         run.glyphs.size());
174    renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width,
175                             run.underline, run.strike, run.diagonal_strike);
176  }
177
178  renderer.EndDiagonalStrike();
179}
180
181RenderTextMac::TextRun::TextRun()
182    : ct_run(NULL),
183      origin(SkPoint::Make(0, 0)),
184      width(0),
185      font_style(Font::NORMAL),
186      text_size(0),
187      foreground(SK_ColorBLACK),
188      underline(false),
189      strike(false),
190      diagonal_strike(false) {
191}
192
193RenderTextMac::TextRun::~TextRun() {
194}
195
196void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string,
197                                CTFontRef font) {
198  // Temporarily apply composition underlines and selection colors.
199  ApplyCompositionAndSelectionStyles();
200
201  // Note: CFAttributedStringSetAttribute() does not appear to retain the values
202  // passed in, as can be verified via CFGetRetainCount(). To ensure the
203  // attribute objects do not leak, they are saved to |attributes_|.
204  // Clear the attributes storage.
205  attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
206
207  // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
208  internal::StyleIterator style(colors(), styles());
209  const size_t layout_text_length = GetLayoutText().length();
210  for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
211    end = TextIndexToLayoutIndex(style.GetRange().end());
212    const CFRange range = CFRangeMake(i, end - i);
213    base::ScopedCFTypeRef<CGColorRef> foreground(
214        CGColorCreateFromSkColor(style.color()));
215    CFAttributedStringSetAttribute(attr_string, range,
216        kCTForegroundColorAttributeName, foreground);
217    CFArrayAppendValue(attributes_, foreground);
218
219    if (style.style(UNDERLINE)) {
220      CTUnderlineStyle value = kCTUnderlineStyleSingle;
221      base::ScopedCFTypeRef<CFNumberRef> underline_value(
222          CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
223      CFAttributedStringSetAttribute(attr_string, range,
224                                     kCTUnderlineStyleAttributeName,
225                                     underline_value);
226      CFArrayAppendValue(attributes_, underline_value);
227    }
228
229    const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) |
230                       (style.style(ITALIC) ? kCTFontItalicTrait : 0);
231    if (traits != 0) {
232      base::ScopedCFTypeRef<CTFontRef> styled_font(
233          CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits));
234      // TODO(asvitkine): Handle |styled_font| == NULL case better.
235      if (styled_font) {
236        CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
237                                       styled_font);
238        CFArrayAppendValue(attributes_, styled_font);
239      }
240    }
241
242    style.UpdatePosition(LayoutIndexToTextIndex(end));
243  }
244
245  // Undo the temporarily applied composition underlines and selection colors.
246  UndoCompositionAndSelectionStyles();
247}
248
249void RenderTextMac::ComputeRuns() {
250  DCHECK(line_);
251
252  CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
253  const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
254
255  // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
256  // updated based on alignment changes without resetting the layout.
257  Vector2d text_offset = GetLineOffset(0);
258  // Skia will draw glyphs with respect to the baseline.
259  text_offset += Vector2d(0, common_baseline_);
260
261  const SkScalar x = SkIntToScalar(text_offset.x());
262  const SkScalar y = SkIntToScalar(text_offset.y());
263  SkPoint run_origin = SkPoint::Make(x, y);
264
265  const CFRange empty_cf_range = CFRangeMake(0, 0);
266  for (CFIndex i = 0; i < ct_runs_count; ++i) {
267    CTRunRef ct_run =
268        base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
269    const size_t glyph_count = CTRunGetGlyphCount(ct_run);
270    const double run_width =
271        CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
272    if (glyph_count == 0) {
273      run_origin.offset(run_width, 0);
274      continue;
275    }
276
277    runs_.push_back(TextRun());
278    TextRun* run = &runs_.back();
279    run->ct_run = ct_run;
280    run->origin = run_origin;
281    run->width = run_width;
282    run->glyphs.resize(glyph_count);
283    CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
284    // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
285    // width (this has been observed at the beginning of a string containing
286    // Arabic content). Passing these to Skia will trigger an assertion;
287    // instead set their values to 0.
288    for (size_t glyph = 0; glyph < glyph_count; glyph++) {
289      if (run->glyphs[glyph] == 65535)
290        run->glyphs[glyph] = 0;
291    }
292
293    run->glyph_positions.resize(glyph_count);
294    const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
295    std::vector<CGPoint> positions;
296    if (positions_ptr == NULL) {
297      positions.resize(glyph_count);
298      CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
299      positions_ptr = &positions[0];
300    }
301    for (size_t glyph = 0; glyph < glyph_count; glyph++) {
302      SkPoint* point = &run->glyph_positions[glyph];
303      point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
304                 y + SkDoubleToScalar(positions_ptr[glyph].y));
305    }
306
307    // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
308    //                  this better. Also, support strike and diagonal_strike.
309    CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
310    CTFontRef ct_font =
311        base::mac::GetValueFromDictionary<CTFontRef>(attributes,
312                                                     kCTFontAttributeName);
313    base::ScopedCFTypeRef<CFStringRef> font_name_ref(
314        CTFontCopyFamilyName(ct_font));
315    run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
316    run->text_size = CTFontGetSize(ct_font);
317
318    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
319    if (traits & kCTFontBoldTrait)
320      run->font_style |= Font::BOLD;
321    if (traits & kCTFontItalicTrait)
322      run->font_style |= Font::ITALIC;
323
324    const CGColorRef foreground =
325        base::mac::GetValueFromDictionary<CGColorRef>(
326            attributes, kCTForegroundColorAttributeName);
327    if (foreground)
328      run->foreground = CGColorRefToSkColor(foreground);
329
330    const CFNumberRef underline =
331        base::mac::GetValueFromDictionary<CFNumberRef>(
332            attributes, kCTUnderlineStyleAttributeName);
333    CTUnderlineStyle value = kCTUnderlineStyleNone;
334    if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
335      run->underline = (value == kCTUnderlineStyleSingle);
336
337    run_origin.offset(run_width, 0);
338  }
339  runs_valid_ = true;
340}
341
342RenderText* RenderText::CreateNativeInstance() {
343  return new RenderTextMac;
344}
345
346}  // namespace gfx
347