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_mac.h"
7 #include <ApplicationServices/ApplicationServices.h>
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "skia/ext/skia_utils_mac.h"
19 RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) {
22 RenderTextMac::~RenderTextMac() {
25 Size
RenderTextMac::GetStringSize() {
30 int RenderTextMac::GetBaseline() {
32 return common_baseline_
;
35 SelectionModel
RenderTextMac::FindCursorPosition(const Point
& point
) {
36 // TODO(asvitkine): Implement this. http://crbug.com/131618
37 return SelectionModel();
40 std::vector
<RenderText::FontSpan
> RenderTextMac::GetFontSpansForTesting() {
45 std::vector
<RenderText::FontSpan
> spans
;
46 for (size_t i
= 0; i
< runs_
.size(); ++i
) {
47 gfx::Font
font(runs_
[i
].font_name
, runs_
[i
].text_size
);
48 const CFRange cf_range
= CTRunGetStringRange(runs_
[i
].ct_run
);
49 const ui::Range
range(cf_range
.location
,
50 cf_range
.location
+ cf_range
.length
);
51 spans
.push_back(RenderText::FontSpan(font
, range
));
57 SelectionModel
RenderTextMac::AdjacentCharSelectionModel(
58 const SelectionModel
& selection
,
59 VisualCursorDirection direction
) {
60 // TODO(asvitkine): Implement this. http://crbug.com/131618
61 return SelectionModel();
64 SelectionModel
RenderTextMac::AdjacentWordSelectionModel(
65 const SelectionModel
& selection
,
66 VisualCursorDirection direction
) {
67 // TODO(asvitkine): Implement this. http://crbug.com/131618
68 return SelectionModel();
71 void RenderTextMac::GetGlyphBounds(size_t index
,
74 // TODO(asvitkine): Implement this. http://crbug.com/131618
77 std::vector
<Rect
> RenderTextMac::GetSubstringBounds(const ui::Range
& range
) {
78 // TODO(asvitkine): Implement this. http://crbug.com/131618
79 return std::vector
<Rect
>();
82 size_t RenderTextMac::TextIndexToLayoutIndex(size_t index
) const {
83 // TODO(asvitkine): Implement this. http://crbug.com/131618
87 size_t RenderTextMac::LayoutIndexToTextIndex(size_t index
) const {
88 // TODO(asvitkine): Implement this. http://crbug.com/131618
92 bool RenderTextMac::IsCursorablePosition(size_t position
) {
93 // TODO(asvitkine): Implement this. http://crbug.com/131618
97 void RenderTextMac::ResetLayout() {
104 void RenderTextMac::EnsureLayout() {
110 const Font
& font
= GetFont();
111 base::mac::ScopedCFTypeRef
<CFStringRef
> font_name_cf_string(
112 base::SysUTF8ToCFStringRef(font
.GetFontName()));
113 base::mac::ScopedCFTypeRef
<CTFontRef
> ct_font(
114 CTFontCreateWithName(font_name_cf_string
, font
.GetFontSize(), NULL
));
116 const void* keys
[] = { kCTFontAttributeName
};
117 const void* values
[] = { ct_font
};
118 base::mac::ScopedCFTypeRef
<CFDictionaryRef
> attributes(
119 CFDictionaryCreate(NULL
, keys
, values
, arraysize(keys
), NULL
,
120 &kCFTypeDictionaryValueCallBacks
));
122 base::mac::ScopedCFTypeRef
<CFStringRef
> cf_text(
123 base::SysUTF16ToCFStringRef(text()));
124 base::mac::ScopedCFTypeRef
<CFAttributedStringRef
> attr_text(
125 CFAttributedStringCreate(NULL
, cf_text
, attributes
));
126 base::mac::ScopedCFTypeRef
<CFMutableAttributedStringRef
> attr_text_mutable(
127 CFAttributedStringCreateMutableCopy(NULL
, 0, attr_text
));
129 // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
130 // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
132 ApplyStyles(attr_text_mutable
, ct_font
);
133 line_
.reset(CTLineCreateWithAttributedString(attr_text_mutable
));
138 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
139 double width
= CTLineGetTypographicBounds(line_
, &ascent
, &descent
, &leading
);
140 string_size_
= Size(width
, ascent
+ descent
+ leading
);
141 common_baseline_
= ascent
;
144 void RenderTextMac::DrawVisualText(Canvas
* canvas
) {
149 internal::SkiaTextRenderer
renderer(canvas
);
150 ApplyFadeEffects(&renderer
);
151 ApplyTextShadows(&renderer
);
153 for (size_t i
= 0; i
< runs_
.size(); ++i
) {
154 const TextRun
& run
= runs_
[i
];
155 renderer
.SetForegroundColor(run
.foreground
);
156 renderer
.SetTextSize(run
.text_size
);
157 renderer
.SetFontFamilyWithStyle(run
.font_name
, run
.font_style
);
158 renderer
.DrawPosText(&run
.glyph_positions
[0], &run
.glyphs
[0],
160 renderer
.DrawDecorations(run
.origin
.x(), run
.origin
.y(), run
.width
,
161 run
.underline
, run
.strike
, run
.diagonal_strike
);
165 RenderTextMac::TextRun::TextRun()
167 origin(SkPoint::Make(0, 0)),
169 font_style(Font::NORMAL
),
171 foreground(SK_ColorBLACK
),
174 diagonal_strike(false) {
177 RenderTextMac::TextRun::~TextRun() {
180 void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string
,
182 // Temporarily apply composition underlines and selection colors.
183 ApplyCompositionAndSelectionStyles();
185 // Note: CFAttributedStringSetAttribute() does not appear to retain the values
186 // passed in, as can be verified via CFGetRetainCount(). To ensure the
187 // attribute objects do not leak, they are saved to |attributes_|.
188 // Clear the attributes storage.
189 attributes_
.reset(CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
));
191 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
192 internal::StyleIterator
style(colors(), styles());
193 const size_t layout_text_length
= GetLayoutText().length();
194 for (size_t i
= 0, end
= 0; i
< layout_text_length
; i
= end
) {
195 end
= TextIndexToLayoutIndex(style
.GetRange().end());
196 const CFRange range
= CFRangeMake(i
, end
- i
);
197 base::mac::ScopedCFTypeRef
<CGColorRef
> foreground(
198 gfx::CGColorCreateFromSkColor(style
.color()));
199 CFAttributedStringSetAttribute(attr_string
, range
,
200 kCTForegroundColorAttributeName
, foreground
);
201 CFArrayAppendValue(attributes_
, foreground
);
203 if (style
.style(UNDERLINE
)) {
204 CTUnderlineStyle value
= kCTUnderlineStyleSingle
;
205 base::mac::ScopedCFTypeRef
<CFNumberRef
> underline_value(
206 CFNumberCreate(NULL
, kCFNumberSInt32Type
, &value
));
207 CFAttributedStringSetAttribute(attr_string
, range
,
208 kCTUnderlineStyleAttributeName
,
210 CFArrayAppendValue(attributes_
, underline_value
);
213 const int traits
= (style
.style(BOLD
) ? kCTFontBoldTrait
: 0) |
214 (style
.style(ITALIC
) ? kCTFontItalicTrait
: 0);
216 base::mac::ScopedCFTypeRef
<CTFontRef
> styled_font(
217 CTFontCreateCopyWithSymbolicTraits(font
, 0.0, NULL
, traits
, traits
));
218 // TODO(asvitkine): Handle |styled_font| == NULL case better.
220 CFAttributedStringSetAttribute(attr_string
, range
, kCTFontAttributeName
,
222 CFArrayAppendValue(attributes_
, styled_font
);
226 style
.UpdatePosition(LayoutIndexToTextIndex(end
));
229 // Undo the temporarily applied composition underlines and selection colors.
230 UndoCompositionAndSelectionStyles();
233 void RenderTextMac::ComputeRuns() {
236 CFArrayRef ct_runs
= CTLineGetGlyphRuns(line_
);
237 const CFIndex ct_runs_count
= CFArrayGetCount(ct_runs
);
239 gfx::Vector2d text_offset
= GetTextOffset();
240 // Skia will draw glyphs with respect to the baseline.
241 text_offset
+= gfx::Vector2d(0, common_baseline_
);
243 const SkScalar x
= SkIntToScalar(text_offset
.x());
244 const SkScalar y
= SkIntToScalar(text_offset
.y());
245 SkPoint run_origin
= SkPoint::Make(x
, y
);
247 const CFRange empty_cf_range
= CFRangeMake(0, 0);
248 for (CFIndex i
= 0; i
< ct_runs_count
; ++i
) {
250 base::mac::CFCast
<CTRunRef
>(CFArrayGetValueAtIndex(ct_runs
, i
));
251 const size_t glyph_count
= CTRunGetGlyphCount(ct_run
);
252 const double run_width
=
253 CTRunGetTypographicBounds(ct_run
, empty_cf_range
, NULL
, NULL
, NULL
);
254 if (glyph_count
== 0) {
255 run_origin
.offset(run_width
, 0);
259 runs_
.push_back(TextRun());
260 TextRun
* run
= &runs_
.back();
261 run
->ct_run
= ct_run
;
262 run
->origin
= run_origin
;
263 run
->width
= run_width
;
264 run
->glyphs
.resize(glyph_count
);
265 CTRunGetGlyphs(ct_run
, empty_cf_range
, &run
->glyphs
[0]);
266 // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
267 // width (this has been observed at the beginning of a string containing
268 // Arabic content). Passing these to Skia will trigger an assertion;
269 // instead set their values to 0.
270 for (size_t glyph
= 0; glyph
< glyph_count
; glyph
++) {
271 if (run
->glyphs
[glyph
] == 65535)
272 run
->glyphs
[glyph
] = 0;
275 run
->glyph_positions
.resize(glyph_count
);
276 const CGPoint
* positions_ptr
= CTRunGetPositionsPtr(ct_run
);
277 std::vector
<CGPoint
> positions
;
278 if (positions_ptr
== NULL
) {
279 positions
.resize(glyph_count
);
280 CTRunGetPositions(ct_run
, empty_cf_range
, &positions
[0]);
281 positions_ptr
= &positions
[0];
283 for (size_t glyph
= 0; glyph
< glyph_count
; glyph
++) {
284 SkPoint
* point
= &run
->glyph_positions
[glyph
];
285 point
->set(x
+ SkDoubleToScalar(positions_ptr
[glyph
].x
),
286 y
+ SkDoubleToScalar(positions_ptr
[glyph
].y
));
289 // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
290 // this better. Also, support strike and diagonal_strike.
291 CFDictionaryRef attributes
= CTRunGetAttributes(ct_run
);
293 base::mac::GetValueFromDictionary
<CTFontRef
>(attributes
,
294 kCTFontAttributeName
);
295 base::mac::ScopedCFTypeRef
<CFStringRef
> font_name_ref(
296 CTFontCopyFamilyName(ct_font
));
297 run
->font_name
= base::SysCFStringRefToUTF8(font_name_ref
);
298 run
->text_size
= CTFontGetSize(ct_font
);
300 CTFontSymbolicTraits traits
= CTFontGetSymbolicTraits(ct_font
);
301 if (traits
& kCTFontBoldTrait
)
302 run
->font_style
|= Font::BOLD
;
303 if (traits
& kCTFontItalicTrait
)
304 run
->font_style
|= Font::ITALIC
;
306 const CGColorRef foreground
=
307 base::mac::GetValueFromDictionary
<CGColorRef
>(
308 attributes
, kCTForegroundColorAttributeName
);
310 run
->foreground
= gfx::CGColorRefToSkColor(foreground
);
312 const CFNumberRef underline
=
313 base::mac::GetValueFromDictionary
<CFNumberRef
>(
314 attributes
, kCTUnderlineStyleAttributeName
);
315 CTUnderlineStyle value
= kCTUnderlineStyleNone
;
316 if (underline
&& CFNumberGetValue(underline
, kCFNumberSInt32Type
, &value
))
317 run
->underline
= (value
== kCTUnderlineStyleSingle
);
319 run_origin
.offset(run_width
, 0);
324 RenderText
* RenderText::CreateInstance() {
325 return new RenderTextMac
;