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    gfx::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::IsCursorablePosition(size_t position) {
97  // TODO(asvitkine): Implement this. http://crbug.com/131618
98  return true;
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  const Font& font = GetPrimaryFont();
115  CTFontRef ct_font = base::mac::NSToCFCast(font.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
179RenderTextMac::TextRun::TextRun()
180    : ct_run(NULL),
181      origin(SkPoint::Make(0, 0)),
182      width(0),
183      font_style(Font::NORMAL),
184      text_size(0),
185      foreground(SK_ColorBLACK),
186      underline(false),
187      strike(false),
188      diagonal_strike(false) {
189}
190
191RenderTextMac::TextRun::~TextRun() {
192}
193
194void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string,
195                                CTFontRef font) {
196  // Temporarily apply composition underlines and selection colors.
197  ApplyCompositionAndSelectionStyles();
198
199  // Note: CFAttributedStringSetAttribute() does not appear to retain the values
200  // passed in, as can be verified via CFGetRetainCount(). To ensure the
201  // attribute objects do not leak, they are saved to |attributes_|.
202  // Clear the attributes storage.
203  attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
204
205  // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
206  internal::StyleIterator style(colors(), styles());
207  const size_t layout_text_length = GetLayoutText().length();
208  for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
209    end = TextIndexToLayoutIndex(style.GetRange().end());
210    const CFRange range = CFRangeMake(i, end - i);
211    base::ScopedCFTypeRef<CGColorRef> foreground(
212        gfx::CGColorCreateFromSkColor(style.color()));
213    CFAttributedStringSetAttribute(attr_string, range,
214        kCTForegroundColorAttributeName, foreground);
215    CFArrayAppendValue(attributes_, foreground);
216
217    if (style.style(UNDERLINE)) {
218      CTUnderlineStyle value = kCTUnderlineStyleSingle;
219      base::ScopedCFTypeRef<CFNumberRef> underline_value(
220          CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
221      CFAttributedStringSetAttribute(attr_string, range,
222                                     kCTUnderlineStyleAttributeName,
223                                     underline_value);
224      CFArrayAppendValue(attributes_, underline_value);
225    }
226
227    const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) |
228                       (style.style(ITALIC) ? kCTFontItalicTrait : 0);
229    if (traits != 0) {
230      base::ScopedCFTypeRef<CTFontRef> styled_font(
231          CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits));
232      // TODO(asvitkine): Handle |styled_font| == NULL case better.
233      if (styled_font) {
234        CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
235                                       styled_font);
236        CFArrayAppendValue(attributes_, styled_font);
237      }
238    }
239
240    style.UpdatePosition(LayoutIndexToTextIndex(end));
241  }
242
243  // Undo the temporarily applied composition underlines and selection colors.
244  UndoCompositionAndSelectionStyles();
245}
246
247void RenderTextMac::ComputeRuns() {
248  DCHECK(line_);
249
250  CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
251  const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
252
253  // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
254  // updated based on alignment changes without resetting the layout.
255  gfx::Vector2d text_offset = GetLineOffset(0);
256  // Skia will draw glyphs with respect to the baseline.
257  text_offset += gfx::Vector2d(0, common_baseline_);
258
259  const SkScalar x = SkIntToScalar(text_offset.x());
260  const SkScalar y = SkIntToScalar(text_offset.y());
261  SkPoint run_origin = SkPoint::Make(x, y);
262
263  const CFRange empty_cf_range = CFRangeMake(0, 0);
264  for (CFIndex i = 0; i < ct_runs_count; ++i) {
265    CTRunRef ct_run =
266        base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
267    const size_t glyph_count = CTRunGetGlyphCount(ct_run);
268    const double run_width =
269        CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
270    if (glyph_count == 0) {
271      run_origin.offset(run_width, 0);
272      continue;
273    }
274
275    runs_.push_back(TextRun());
276    TextRun* run = &runs_.back();
277    run->ct_run = ct_run;
278    run->origin = run_origin;
279    run->width = run_width;
280    run->glyphs.resize(glyph_count);
281    CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
282    // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
283    // width (this has been observed at the beginning of a string containing
284    // Arabic content). Passing these to Skia will trigger an assertion;
285    // instead set their values to 0.
286    for (size_t glyph = 0; glyph < glyph_count; glyph++) {
287      if (run->glyphs[glyph] == 65535)
288        run->glyphs[glyph] = 0;
289    }
290
291    run->glyph_positions.resize(glyph_count);
292    const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
293    std::vector<CGPoint> positions;
294    if (positions_ptr == NULL) {
295      positions.resize(glyph_count);
296      CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
297      positions_ptr = &positions[0];
298    }
299    for (size_t glyph = 0; glyph < glyph_count; glyph++) {
300      SkPoint* point = &run->glyph_positions[glyph];
301      point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
302                 y + SkDoubleToScalar(positions_ptr[glyph].y));
303    }
304
305    // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
306    //                  this better. Also, support strike and diagonal_strike.
307    CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
308    CTFontRef ct_font =
309        base::mac::GetValueFromDictionary<CTFontRef>(attributes,
310                                                     kCTFontAttributeName);
311    base::ScopedCFTypeRef<CFStringRef> font_name_ref(
312        CTFontCopyFamilyName(ct_font));
313    run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
314    run->text_size = CTFontGetSize(ct_font);
315
316    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
317    if (traits & kCTFontBoldTrait)
318      run->font_style |= Font::BOLD;
319    if (traits & kCTFontItalicTrait)
320      run->font_style |= Font::ITALIC;
321
322    const CGColorRef foreground =
323        base::mac::GetValueFromDictionary<CGColorRef>(
324            attributes, kCTForegroundColorAttributeName);
325    if (foreground)
326      run->foreground = gfx::CGColorRefToSkColor(foreground);
327
328    const CFNumberRef underline =
329        base::mac::GetValueFromDictionary<CFNumberRef>(
330            attributes, kCTUnderlineStyleAttributeName);
331    CTUnderlineStyle value = kCTUnderlineStyleNone;
332    if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
333      run->underline = (value == kCTUnderlineStyleSingle);
334
335    run_origin.offset(run_width, 0);
336  }
337  runs_valid_ = true;
338}
339
340RenderText* RenderText::CreateInstance() {
341  return new RenderTextMac;
342}
343
344}  // namespace gfx
345