OLD | NEW |
---|---|
(Empty) | |
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 <cmath> | |
10 #include <utility> | |
11 | |
12 #include "base/mac/foundation_util.h" | |
13 #include "base/mac/scoped_cftyperef.h" | |
14 #include "base/sys_string_conversions.h" | |
15 #include "skia/ext/skia_utils_mac.h" | |
16 | |
17 namespace { | |
18 | |
19 // Returns the pixel height of |ct_font|. | |
20 CGFloat GetCTFontPixelSize(CTFontRef ct_font) { | |
21 return CTFontGetAscent(ct_font) + CTFontGetDescent(ct_font); | |
Nico
2012/06/22 18:04:10
Have you checked that GetAscent() actually returns
Alexei Svitkine (slow)
2012/06/22 19:35:29
I'll have to check.
Alexei Svitkine (slow)
2012/07/16 22:43:08
I've verified that the metrics values returned are
| |
22 } | |
23 | |
24 // Creates a CTFont with the given font name and pixel size. Ownership is | |
25 // transferred to the caller. | |
26 CTFontRef CreateCTFontWithPixelSize(const std::string& font_name, | |
27 const int target_pixel_size) { | |
28 // Epsilon value used for comparing font sizes. | |
29 const CGFloat kEpsilon = 0.001; | |
30 // The observed pixel to points ratio for Lucida Grande on 10.6. Other fonts | |
31 // have other ratios and the documentation doesn't provide a guarantee that | |
32 // the relation is linear. So this ratio is used as a first try before | |
33 // falling back to the bisection method. | |
34 const CGFloat kPixelsToPointsRatio = 0.849088; | |
35 | |
36 base::mac::ScopedCFTypeRef<CFStringRef> font_name_cf_string( | |
37 base::SysUTF8ToCFStringRef(font_name)); | |
38 | |
39 // First, try using |kPixelsToPointsRatio|. | |
40 CGFloat point_size = target_pixel_size * kPixelsToPointsRatio; | |
41 base::mac::ScopedCFTypeRef<CTFontRef> ct_font( | |
42 CTFontCreateWithName(font_name_cf_string, point_size, NULL)); | |
43 CGFloat actual_pixel_size = GetCTFontPixelSize(ct_font); | |
44 if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon) | |
45 return ct_font.release(); | |
46 | |
47 // |kPixelsToPointsRatio| wasn't correct. Use the bisection method to find the | |
48 // right size. | |
49 | |
50 // First, find the initial bisection range, so that the point size that | |
51 // corresponds to |target_pixel_size| is between |lo| and |hi|. | |
52 CGFloat lo = 0; | |
53 CGFloat hi = point_size; | |
54 while (actual_pixel_size < target_pixel_size) { | |
55 lo = hi; | |
56 hi *= 2; | |
57 ct_font.reset(CTFontCreateWithName(font_name_cf_string, hi, NULL)); | |
58 actual_pixel_size = GetCTFontPixelSize(ct_font); | |
59 } | |
60 | |
61 // Now, bisect to find the right size. | |
62 while (lo < hi) { | |
63 point_size = (hi - lo) * 0.5 + lo; | |
64 ct_font.reset(CTFontCreateWithName(font_name_cf_string, point_size, NULL)); | |
65 actual_pixel_size = GetCTFontPixelSize(ct_font); | |
66 if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon) | |
67 break; | |
68 if (target_pixel_size > actual_pixel_size) | |
69 lo = point_size; | |
70 else | |
71 hi = point_size; | |
72 } | |
73 | |
74 return ct_font.release(); | |
75 } | |
76 | |
77 } // namespace | |
78 | |
79 namespace gfx { | |
80 | |
81 RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) { | |
82 } | |
83 | |
84 RenderTextMac::~RenderTextMac() { | |
85 } | |
86 | |
87 base::i18n::TextDirection RenderTextMac::GetTextDirection() { | |
88 return base::i18n::LEFT_TO_RIGHT; | |
89 } | |
90 | |
91 Size RenderTextMac::GetStringSize() { | |
92 EnsureLayout(); | |
93 return string_size_; | |
94 } | |
95 | |
96 int RenderTextMac::GetBaseline() { | |
97 EnsureLayout(); | |
98 return common_baseline_; | |
99 } | |
100 | |
101 SelectionModel RenderTextMac::FindCursorPosition(const Point& point) { | |
102 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
103 return SelectionModel(); | |
104 } | |
105 | |
106 std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() { | |
107 EnsureLayout(); | |
108 if (!runs_valid_) | |
109 ComputeRuns(); | |
110 | |
111 std::vector<RenderText::FontSpan> spans; | |
112 for (size_t i = 0; i < runs_.size(); ++i) { | |
113 gfx::Font font(runs_[i].font_name, runs_[i].text_size); | |
114 const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run); | |
115 const ui::Range range(cf_range.location, | |
116 cf_range.location + cf_range.length); | |
117 spans.push_back(RenderText::FontSpan(font, range)); | |
118 } | |
119 | |
120 return spans; | |
121 } | |
122 | |
123 SelectionModel RenderTextMac::AdjacentCharSelectionModel( | |
124 const SelectionModel& selection, | |
125 VisualCursorDirection direction) { | |
126 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
127 return SelectionModel(); | |
128 } | |
129 | |
130 SelectionModel RenderTextMac::AdjacentWordSelectionModel( | |
131 const SelectionModel& selection, | |
132 VisualCursorDirection direction) { | |
133 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
134 return SelectionModel(); | |
135 } | |
136 | |
137 void RenderTextMac::GetGlyphBounds(size_t index, | |
138 ui::Range* xspan, | |
139 int* height) { | |
140 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
141 } | |
142 | |
143 std::vector<Rect> RenderTextMac::GetSubstringBounds(ui::Range range) { | |
144 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
145 return std::vector<Rect>(); | |
146 } | |
147 | |
148 bool RenderTextMac::IsCursorablePosition(size_t position) { | |
149 // TODO(asvitkine): Implement this. http://crbug.com/131618 | |
150 return false; | |
151 } | |
152 | |
153 void RenderTextMac::ResetLayout() { | |
154 line_.reset(); | |
155 runs_.clear(); | |
156 runs_valid_ = false; | |
157 } | |
158 | |
159 void RenderTextMac::EnsureLayout() { | |
160 if (line_.get()) | |
161 return; | |
162 runs_.clear(); | |
163 runs_valid_ = false; | |
164 | |
165 const Font& font = GetFont(); | |
166 CTFontRef ct_font = | |
167 CreateCTFontWithPixelSize(font.GetFontName(), font.GetFontSize()); | |
168 | |
169 const void* keys[] = { kCTFontAttributeName }; | |
170 const void* values[] = { ct_font }; | |
171 base::mac::ScopedCFTypeRef<CFDictionaryRef> attributes( | |
172 CFDictionaryCreate(NULL, keys, values, arraysize(keys), NULL, NULL)); | |
173 | |
174 base::mac::ScopedCFTypeRef<CFStringRef> cf_text( | |
175 base::SysUTF16ToCFStringRef(text())); | |
176 base::mac::ScopedCFTypeRef<CFAttributedStringRef> attr_text( | |
177 CFAttributedStringCreate(NULL, cf_text, attributes)); | |
178 base::mac::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable( | |
179 CFAttributedStringCreateMutableCopy(NULL, 0, attr_text)); | |
180 | |
181 ApplyStyles(attr_text_mutable, ct_font); | |
182 line_.reset(CTLineCreateWithAttributedString(attr_text_mutable)); | |
183 | |
184 CGFloat ascent = 0; | |
185 CGFloat descent = 0; | |
186 CGFloat leading = 0; | |
187 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+. | |
188 double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading); | |
189 string_size_ = Size(width, ascent + descent + leading); | |
190 common_baseline_ = ascent; | |
191 } | |
192 | |
193 void RenderTextMac::DrawVisualText(Canvas* canvas) { | |
194 DCHECK(line_); | |
195 if (!runs_valid_) | |
196 ComputeRuns(); | |
197 | |
198 internal::SkiaTextRenderer renderer(canvas); | |
199 ApplyFadeEffects(&renderer); | |
200 ApplyTextShadows(&renderer); | |
201 | |
202 for (size_t i = 0; i < runs_.size(); ++i) { | |
203 const TextRun& run = runs_[i]; | |
204 renderer.SetForegroundColor(run.foreground); | |
205 renderer.SetTextSize(run.text_size); | |
206 renderer.SetFontFamilyWithStyle(run.font_name, run.font_style); | |
207 renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0], | |
208 run.glyphs.size()); | |
209 renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width, | |
210 run.style); | |
211 } | |
212 } | |
213 | |
214 RenderTextMac::TextRun::TextRun() | |
215 : ct_run(NULL), | |
216 origin(SkPoint::Make(0, 0)), | |
217 width(0), | |
218 font_style(Font::NORMAL), | |
219 text_size(0), | |
220 foreground(SK_ColorBLACK) { | |
221 } | |
222 | |
223 RenderTextMac::TextRun::~TextRun() { | |
224 } | |
225 | |
226 void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string, | |
227 CTFontRef font) { | |
228 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/Cor eText_StringAttributes_Ref/Reference/reference.html | |
229 for (size_t i = 0; i < style_ranges().size(); ++i) { | |
230 const StyleRange& style = style_ranges()[i]; | |
231 const CFRange range = CFRangeMake(style.range.start(), | |
232 style.range.length()); | |
233 | |
234 CGColorRef foreground = gfx::SkColorToCGColorRef(style.foreground); | |
Nico
2012/06/22 18:04:10
color space?
Alexei Svitkine (slow)
2012/06/22 19:35:29
Can you elaborate the concern? This code only sets
Nico
2012/06/22 19:38:05
Ok.
| |
235 CFAttributedStringSetAttribute(attr_string, range, | |
236 kCTForegroundColorAttributeName, | |
237 foreground); | |
238 | |
239 if (style.font_style & Font::UNDERLINED) { | |
240 CTUnderlineStyle value = kCTUnderlineStyleSingle; | |
241 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &value); | |
Nico
2012/06/22 18:04:10
Leaks (?)
Alexei Svitkine (slow)
2012/06/22 19:35:29
Fixed.
Why is this case different than kCTForegro
Nico
2012/06/22 19:38:05
I didn't notice this for the color because SkCOlor
Alexei Svitkine (slow)
2012/07/16 22:43:08
It indeed seems to be the case. If I CFRelease() |
Alexei Svitkine (slow)
2012/07/21 14:10:58
Thinking about this some more, this really sounds
Nico
2012/07/21 16:17:23
That sounds right to me. Bonus points for filing a
Alexei Svitkine (slow)
2012/07/23 14:33:35
I did some more testing here and it appears it's a
| |
242 CFAttributedStringSetAttribute(attr_string, range, | |
243 kCTUnderlineStyleAttributeName, | |
244 underline); | |
245 } | |
246 | |
247 if (style.font_style & (Font::BOLD | Font::ITALIC)) { | |
248 int traits = 0; | |
249 if (style.font_style & Font::BOLD) | |
250 traits |= kCTFontBoldTrait; | |
251 if (style.font_style & Font::ITALIC) | |
252 traits |= kCTFontItalicTrait; | |
253 CTFontRef styled_font = | |
254 CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits); | |
Nico
2012/06/22 18:04:10
Leaks?
Alexei Svitkine (slow)
2012/06/22 19:35:29
Fixed.
| |
255 CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName, | |
256 styled_font); | |
257 } | |
258 } | |
259 } | |
260 | |
261 void RenderTextMac::ComputeRuns() { | |
262 DCHECK(line_); | |
263 | |
264 CFArrayRef ct_runs = CTLineGetGlyphRuns(line_); | |
265 const CFIndex ct_runs_count = CFArrayGetCount(ct_runs); | |
266 | |
267 Point offset(GetTextOrigin()); | |
268 // Skia will draw glyphs with respect to the baseline. | |
269 offset.Offset(0, common_baseline_); | |
270 | |
271 const SkScalar x = SkIntToScalar(offset.x()); | |
272 const SkScalar y = SkIntToScalar(offset.y()); | |
273 SkPoint run_origin = SkPoint::Make(offset.x(), offset.y()); | |
274 | |
275 const CFRange empty_cf_range = CFRangeMake(0, 0); | |
276 for (CFIndex i = 0; i < ct_runs_count; ++i) { | |
277 CTRunRef ct_run = | |
278 base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i)); | |
279 const size_t glyph_count = CTRunGetGlyphCount(ct_run); | |
280 const double run_width = | |
281 CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL); | |
282 if (glyph_count == 0) { | |
283 run_origin.offset(run_width, 0); | |
284 continue; | |
285 } | |
286 | |
287 runs_.push_back(TextRun()); | |
288 TextRun* run = &runs_.back(); | |
289 run->ct_run = ct_run; | |
290 run->origin = run_origin; | |
291 run->width = run_width; | |
292 run->glyphs.resize(glyph_count); | |
293 CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]); | |
294 | |
295 run->glyph_positions.resize(glyph_count); | |
296 const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run); | |
297 std::vector<CGPoint> positions; | |
298 if (positions_ptr == NULL) { | |
299 positions.resize(glyph_count); | |
300 CTRunGetPositions(ct_run, empty_cf_range, &positions[0]); | |
301 positions_ptr = &positions[0]; | |
302 } | |
303 for (size_t glyph = 0; glyph < glyph_count; glyph++) { | |
304 SkPoint* point = &run->glyph_positions[glyph]; | |
305 point->set(x + SkDoubleToScalar(positions_ptr[glyph].x), | |
306 y + SkDoubleToScalar(positions_ptr[glyph].y)); | |
307 } | |
308 | |
309 CFDictionaryRef attributes = CTRunGetAttributes(ct_run); | |
310 CTFontRef ct_font = | |
311 base::mac::GetValueFromDictionary<CTFontRef>(attributes, | |
312 kCTFontAttributeName); | |
313 base::mac::ScopedCFTypeRef<CFStringRef> font_name_ref( | |
314 CTFontCopyFamilyName(ct_font)); | |
315 run->font_name = base::SysCFStringRefToUTF8(font_name_ref); | |
316 run->text_size = GetCTFontPixelSize(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 run->foreground = gfx::CGColorRefToSkColor(foreground); | |
328 | |
329 const CFNumberRef underline = | |
330 base::mac::GetValueFromDictionary<CFNumberRef>( | |
331 attributes, kCTUnderlineStyleAttributeName); | |
332 CTUnderlineStyle value = kCTUnderlineStyleNone; | |
333 if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value)) | |
334 run->style.underline = (value == kCTUnderlineStyleSingle); | |
335 | |
336 run_origin.offset(run_width, 0); | |
337 } | |
338 runs_valid_ = true; | |
339 } | |
340 | |
341 RenderText* RenderText::CreateRenderText() { | |
342 return new RenderTextMac; | |
343 } | |
344 | |
345 } // namespace gfx | |
OLD | NEW |