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>
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"
20 // This function makes a copy of |font| with the given symbolic traits. This
21 // function is similar to CTFontCreateCopyWithSymbolicTraits, but this function
22 // works on OSX 10.10, unlike CTFontCreateCopyWithSymbolicTraits.
23 base::ScopedCFTypeRef
<CTFontRef
> CopyFontWithSymbolicTraits(CTFontRef font
,
25 base::ScopedCFTypeRef
<CTFontDescriptorRef
> orig_desc(
26 CTFontCopyFontDescriptor(font
));
27 base::ScopedCFTypeRef
<CFDictionaryRef
> orig_attributes(
28 CTFontDescriptorCopyAttributes(orig_desc
));
29 // Make a mutable copy of orig_attributes.
30 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> attributes(
31 CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, orig_attributes
));
33 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> traits(
34 CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
35 &kCFTypeDictionaryKeyCallBacks
,
36 &kCFTypeDictionaryValueCallBacks
));
37 base::ScopedCFTypeRef
<CFNumberRef
> n(
38 CFNumberCreate(kCFAllocatorDefault
, kCFNumberIntType
, &sym_traits
));
39 CFDictionarySetValue(traits
, kCTFontSymbolicTrait
, n
.release());
40 CFDictionarySetValue(attributes
, kCTFontTraitsAttribute
, traits
.release());
42 base::ScopedCFTypeRef
<CFStringRef
> family_name(CTFontCopyFamilyName(font
));
43 CFDictionarySetValue(attributes
, kCTFontNameAttribute
, family_name
.release());
45 base::ScopedCFTypeRef
<CTFontDescriptorRef
> desc(
46 CTFontDescriptorCreateWithAttributes(attributes
));
47 return base::ScopedCFTypeRef
<CTFontRef
>(
48 CTFontCreateWithFontDescriptor(desc
, 0.0, nullptr));
55 RenderTextMac::RenderTextMac()
56 : common_baseline_(0), runs_valid_(false) {
59 RenderTextMac::~RenderTextMac() {
62 scoped_ptr
<RenderText
> RenderTextMac::CreateInstanceOfSameType() const {
63 return make_scoped_ptr(new RenderTextMac
);
66 bool RenderTextMac::MultilineSupported() const {
70 const base::string16
& RenderTextMac::GetDisplayText() {
71 return text_elided() ? display_text() : layout_text();
74 Size
RenderTextMac::GetStringSize() {
75 SizeF size_f
= GetStringSizeF();
76 return Size(std::ceil(size_f
.width()), size_f
.height());
79 SizeF
RenderTextMac::GetStringSizeF() {
84 SelectionModel
RenderTextMac::FindCursorPosition(const Point
& point
) {
85 // TODO(asvitkine): Implement this. http://crbug.com/131618
86 return SelectionModel();
89 std::vector
<RenderText::FontSpan
> RenderTextMac::GetFontSpansForTesting() {
94 std::vector
<RenderText::FontSpan
> spans
;
95 for (size_t i
= 0; i
< runs_
.size(); ++i
) {
96 Font
font(runs_
[i
].font_name
, runs_
[i
].text_size
);
97 const CFRange cf_range
= CTRunGetStringRange(runs_
[i
].ct_run
);
98 const Range
range(cf_range
.location
, cf_range
.location
+ cf_range
.length
);
99 spans
.push_back(RenderText::FontSpan(font
, range
));
105 int RenderTextMac::GetDisplayTextBaseline() {
107 return common_baseline_
;
110 SelectionModel
RenderTextMac::AdjacentCharSelectionModel(
111 const SelectionModel
& selection
,
112 VisualCursorDirection direction
) {
113 // TODO(asvitkine): Implement this. http://crbug.com/131618
114 return SelectionModel();
117 SelectionModel
RenderTextMac::AdjacentWordSelectionModel(
118 const SelectionModel
& selection
,
119 VisualCursorDirection direction
) {
120 // TODO(asvitkine): Implement this. http://crbug.com/131618
121 return SelectionModel();
124 Range
RenderTextMac::GetGlyphBounds(size_t index
) {
125 // TODO(asvitkine): Implement this. http://crbug.com/131618
129 std::vector
<Rect
> RenderTextMac::GetSubstringBounds(const Range
& range
) {
130 // TODO(asvitkine): Implement this. http://crbug.com/131618
131 return std::vector
<Rect
>();
134 size_t RenderTextMac::TextIndexToDisplayIndex(size_t index
) {
135 // TODO(asvitkine): Implement this. http://crbug.com/131618
139 size_t RenderTextMac::DisplayIndexToTextIndex(size_t index
) {
140 // TODO(asvitkine): Implement this. http://crbug.com/131618
144 bool RenderTextMac::IsValidCursorIndex(size_t index
) {
145 // TODO(asvitkine): Implement this. http://crbug.com/131618
146 return IsValidLogicalIndex(index
);
149 void RenderTextMac::OnLayoutTextAttributeChanged(bool text_changed
) {
150 DCHECK(!multiline()) << "RenderTextMac does not support multi line";
152 if (elide_behavior() != NO_ELIDE
&&
153 elide_behavior() != FADE_TAIL
&&
154 !layout_text().empty()) {
155 UpdateDisplayText(std::ceil(GetLayoutTextWidth()));
157 UpdateDisplayText(0);
166 void RenderTextMac::OnDisplayTextAttributeChanged() {
167 OnLayoutTextAttributeChanged(true);
170 void RenderTextMac::EnsureLayout() {
176 line_
= EnsureLayoutInternal(GetDisplayText(), &attributes_
);
177 string_size_
= GetCTLineSize(line_
.get(), &common_baseline_
);
180 void RenderTextMac::DrawVisualText(Canvas
* canvas
) {
185 internal::SkiaTextRenderer
renderer(canvas
);
186 ApplyFadeEffects(&renderer
);
187 ApplyTextShadows(&renderer
);
189 for (size_t i
= 0; i
< runs_
.size(); ++i
) {
190 const TextRun
& run
= runs_
[i
];
191 renderer
.SetForegroundColor(run
.foreground
);
192 renderer
.SetTextSize(run
.text_size
);
193 renderer
.SetFontFamilyWithStyle(run
.font_name
, run
.font_style
);
194 renderer
.DrawPosText(&run
.glyph_positions
[0], &run
.glyphs
[0],
196 renderer
.DrawDecorations(run
.origin
.x(), run
.origin
.y(), run
.width
,
197 run
.underline
, run
.strike
, run
.diagonal_strike
);
200 renderer
.EndDiagonalStrike();
203 RenderTextMac::TextRun::TextRun()
205 origin(SkPoint::Make(0, 0)),
207 font_style(Font::NORMAL
),
209 foreground(SK_ColorBLACK
),
212 diagonal_strike(false) {
215 RenderTextMac::TextRun::~TextRun() {
218 float RenderTextMac::GetLayoutTextWidth() {
219 base::ScopedCFTypeRef
<CFMutableArrayRef
> attributes_owner
;
220 base::ScopedCFTypeRef
<CTLineRef
> line(
221 EnsureLayoutInternal(layout_text(), &attributes_owner
));
223 return GetCTLineSize(line
.get(), &baseline
).width();
226 gfx::SizeF
RenderTextMac::GetCTLineSize(CTLineRef line
, SkScalar
* baseline
) {
230 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
231 double width
= CTLineGetTypographicBounds(line
, &ascent
, &descent
, &leading
);
232 // Ensure ascent and descent are not smaller than ones of the font list.
233 // Keep them tall enough to draw often-used characters.
234 // For example, if a text field contains a Japanese character, which is
235 // smaller than Latin ones, and then later a Latin one is inserted, this
236 // ensures that the text baseline does not shift.
237 CGFloat font_list_height
= font_list().GetHeight();
238 CGFloat font_list_baseline
= font_list().GetBaseline();
239 ascent
= std::max(ascent
, font_list_baseline
);
240 descent
= std::max(descent
, font_list_height
- font_list_baseline
);
243 width
, std::max(ascent
+ descent
+ leading
,
244 static_cast<CGFloat
>(min_line_height())));
247 base::ScopedCFTypeRef
<CTLineRef
> RenderTextMac::EnsureLayoutInternal(
248 const base::string16
& text
,
249 base::ScopedCFTypeRef
<CFMutableArrayRef
>* attributes_owner
) {
250 CTFontRef ct_font
= base::mac::NSToCFCast(
251 font_list().GetPrimaryFont().GetNativeFont());
253 const void* keys
[] = { kCTFontAttributeName
};
254 const void* values
[] = { ct_font
};
255 base::ScopedCFTypeRef
<CFDictionaryRef
> attributes(
256 CFDictionaryCreate(NULL
,
261 &kCFTypeDictionaryValueCallBacks
));
263 base::ScopedCFTypeRef
<CFStringRef
> cf_text(
264 base::SysUTF16ToCFStringRef(text
));
265 base::ScopedCFTypeRef
<CFAttributedStringRef
> attr_text(
266 CFAttributedStringCreate(NULL
, cf_text
, attributes
));
267 base::ScopedCFTypeRef
<CFMutableAttributedStringRef
> attr_text_mutable(
268 CFAttributedStringCreateMutableCopy(NULL
, 0, attr_text
));
270 // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
271 // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
273 *attributes_owner
= ApplyStyles(text
, attr_text_mutable
, ct_font
);
274 return base::ScopedCFTypeRef
<CTLineRef
>(
275 CTLineCreateWithAttributedString(attr_text_mutable
));
278 base::ScopedCFTypeRef
<CFMutableArrayRef
> RenderTextMac::ApplyStyles(
279 const base::string16
& text
,
280 CFMutableAttributedStringRef attr_string
,
282 // Temporarily apply composition underlines and selection colors.
283 ApplyCompositionAndSelectionStyles();
285 // Note: CFAttributedStringSetAttribute() does not appear to retain the values
286 // passed in, as can be verified via CFGetRetainCount(). To ensure the
287 // attribute objects do not leak, they are saved to |attributes_|.
288 // Clear the attributes storage.
289 base::ScopedCFTypeRef
<CFMutableArrayRef
> attributes(
290 CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
));
292 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
293 internal::StyleIterator
style(colors(), baselines(), styles());
294 const size_t layout_text_length
= CFAttributedStringGetLength(attr_string
);
295 for (size_t i
= 0, end
= 0; i
< layout_text_length
; i
= end
) {
296 end
= TextIndexToGivenTextIndex(text
, style
.GetRange().end());
297 const CFRange range
= CFRangeMake(i
, end
- i
);
298 base::ScopedCFTypeRef
<CGColorRef
> foreground(
299 CGColorCreateFromSkColor(style
.color()));
300 CFAttributedStringSetAttribute(attr_string
, range
,
301 kCTForegroundColorAttributeName
, foreground
);
302 CFArrayAppendValue(attributes
, foreground
);
304 if (style
.style(UNDERLINE
)) {
305 CTUnderlineStyle value
= kCTUnderlineStyleSingle
;
306 base::ScopedCFTypeRef
<CFNumberRef
> underline_value(
307 CFNumberCreate(NULL
, kCFNumberSInt32Type
, &value
));
308 CFAttributedStringSetAttribute(attr_string
, range
,
309 kCTUnderlineStyleAttributeName
,
311 CFArrayAppendValue(attributes
, underline_value
);
314 const int traits
= (style
.style(BOLD
) ? kCTFontBoldTrait
: 0) |
315 (style
.style(ITALIC
) ? kCTFontItalicTrait
: 0);
317 base::ScopedCFTypeRef
<CTFontRef
> styled_font
=
318 CopyFontWithSymbolicTraits(font
, traits
);
319 // TODO(asvitkine): Handle |styled_font| == NULL case better.
321 CFAttributedStringSetAttribute(attr_string
, range
, kCTFontAttributeName
,
323 CFArrayAppendValue(attributes
, styled_font
);
327 style
.UpdatePosition(DisplayIndexToTextIndex(end
));
330 // Undo the temporarily applied composition underlines and selection colors.
331 UndoCompositionAndSelectionStyles();
336 void RenderTextMac::ComputeRuns() {
339 CFArrayRef ct_runs
= CTLineGetGlyphRuns(line_
);
340 const CFIndex ct_runs_count
= CFArrayGetCount(ct_runs
);
342 // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
343 // updated based on alignment changes without resetting the layout.
344 Vector2d text_offset
= GetLineOffset(0);
345 // Skia will draw glyphs with respect to the baseline.
346 text_offset
+= Vector2d(0, common_baseline_
);
348 const SkScalar x
= SkIntToScalar(text_offset
.x());
349 const SkScalar y
= SkIntToScalar(text_offset
.y());
350 SkPoint run_origin
= SkPoint::Make(x
, y
);
352 const CFRange empty_cf_range
= CFRangeMake(0, 0);
353 for (CFIndex i
= 0; i
< ct_runs_count
; ++i
) {
355 base::mac::CFCast
<CTRunRef
>(CFArrayGetValueAtIndex(ct_runs
, i
));
356 const size_t glyph_count
= CTRunGetGlyphCount(ct_run
);
357 const double run_width
=
358 CTRunGetTypographicBounds(ct_run
, empty_cf_range
, NULL
, NULL
, NULL
);
359 if (glyph_count
== 0) {
360 run_origin
.offset(run_width
, 0);
364 runs_
.push_back(TextRun());
365 TextRun
* run
= &runs_
.back();
366 run
->ct_run
= ct_run
;
367 run
->origin
= run_origin
;
368 run
->width
= run_width
;
369 run
->glyphs
.resize(glyph_count
);
370 CTRunGetGlyphs(ct_run
, empty_cf_range
, &run
->glyphs
[0]);
371 // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
372 // width (this has been observed at the beginning of a string containing
373 // Arabic content). Passing these to Skia will trigger an assertion;
374 // instead set their values to 0.
375 for (size_t glyph
= 0; glyph
< glyph_count
; glyph
++) {
376 if (run
->glyphs
[glyph
] == 65535)
377 run
->glyphs
[glyph
] = 0;
380 run
->glyph_positions
.resize(glyph_count
);
381 const CGPoint
* positions_ptr
= CTRunGetPositionsPtr(ct_run
);
382 std::vector
<CGPoint
> positions
;
383 if (positions_ptr
== NULL
) {
384 positions
.resize(glyph_count
);
385 CTRunGetPositions(ct_run
, empty_cf_range
, &positions
[0]);
386 positions_ptr
= &positions
[0];
388 for (size_t glyph
= 0; glyph
< glyph_count
; glyph
++) {
389 SkPoint
* point
= &run
->glyph_positions
[glyph
];
390 point
->set(x
+ SkDoubleToScalar(positions_ptr
[glyph
].x
),
391 y
+ SkDoubleToScalar(positions_ptr
[glyph
].y
));
394 // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
395 // this better. Also, support strike and diagonal_strike.
396 CFDictionaryRef attributes
= CTRunGetAttributes(ct_run
);
398 base::mac::GetValueFromDictionary
<CTFontRef
>(attributes
,
399 kCTFontAttributeName
);
400 base::ScopedCFTypeRef
<CFStringRef
> font_name_ref(
401 CTFontCopyFamilyName(ct_font
));
402 run
->font_name
= base::SysCFStringRefToUTF8(font_name_ref
);
403 run
->text_size
= CTFontGetSize(ct_font
);
405 CTFontSymbolicTraits traits
= CTFontGetSymbolicTraits(ct_font
);
406 if (traits
& kCTFontBoldTrait
)
407 run
->font_style
|= Font::BOLD
;
408 if (traits
& kCTFontItalicTrait
)
409 run
->font_style
|= Font::ITALIC
;
411 const CGColorRef foreground
=
412 base::mac::GetValueFromDictionary
<CGColorRef
>(
413 attributes
, kCTForegroundColorAttributeName
);
415 run
->foreground
= CGColorRefToSkColor(foreground
);
417 const CFNumberRef underline
=
418 base::mac::GetValueFromDictionary
<CFNumberRef
>(
419 attributes
, kCTUnderlineStyleAttributeName
);
420 CTUnderlineStyle value
= kCTUnderlineStyleNone
;
421 if (underline
&& CFNumberGetValue(underline
, kCFNumberSInt32Type
, &value
))
422 run
->underline
= (value
== kCTUnderlineStyleSingle
);
424 run_origin
.offset(run_width
, 0);