Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(95)

Side by Side Diff: ui/gfx/render_text_mac.cc

Issue 10543057: Initial RenderTextMac implementation using CoreText. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698