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

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, 5 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
« no previous file with comments | « ui/gfx/render_text_mac.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
22 }
23
24 // Creates a CTFont with the given font name and pixel size. Ownership is
25 // transferred to the caller.
26 //
27 // Note: This code makes use of pixel sizes (rather than view coordinate sizes)
28 // because it draws to an underlying Skia canvas, which is normally pixel based.
29 CTFontRef CreateCTFontWithPixelSize(const std::string& font_name,
30 const int target_pixel_size) {
31 // Epsilon value used for comparing font sizes.
32 const CGFloat kEpsilon = 0.001;
33 // The observed pixel to points ratio for Lucida Grande on 10.6. Other fonts
34 // have other ratios and the documentation doesn't provide a guarantee that
35 // the relation is linear. So this ratio is used as a first try before
36 // falling back to the bisection method.
37 const CGFloat kPixelsToPointsRatio = 0.849088;
38
39 base::mac::ScopedCFTypeRef<CFStringRef> font_name_cf_string(
40 base::SysUTF8ToCFStringRef(font_name));
41
42 // First, try using |kPixelsToPointsRatio|.
43 CGFloat point_size = target_pixel_size * kPixelsToPointsRatio;
44 base::mac::ScopedCFTypeRef<CTFontRef> ct_font(
45 CTFontCreateWithName(font_name_cf_string, point_size, NULL));
46 CGFloat actual_pixel_size = GetCTFontPixelSize(ct_font);
47 if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon)
48 return ct_font.release();
49
50 // |kPixelsToPointsRatio| wasn't correct. Use the bisection method to find the
51 // right size.
52
53 // First, find the initial bisection range, so that the point size that
54 // corresponds to |target_pixel_size| is between |lo| and |hi|.
55 CGFloat lo = 0;
56 CGFloat hi = point_size;
57 while (actual_pixel_size < target_pixel_size) {
58 lo = hi;
59 hi *= 2;
60 ct_font.reset(CTFontCreateWithName(font_name_cf_string, hi, NULL));
61 actual_pixel_size = GetCTFontPixelSize(ct_font);
62 }
63
64 // Now, bisect to find the right size.
65 while (lo < hi) {
66 point_size = (hi - lo) * 0.5 + lo;
67 ct_font.reset(CTFontCreateWithName(font_name_cf_string, point_size, NULL));
68 actual_pixel_size = GetCTFontPixelSize(ct_font);
69 if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon)
70 break;
71 if (target_pixel_size > actual_pixel_size)
72 lo = point_size;
73 else
74 hi = point_size;
75 }
76
77 return ct_font.release();
78 }
79
80 } // namespace
81
82 namespace gfx {
83
84 RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) {
85 }
86
87 RenderTextMac::~RenderTextMac() {
88 }
89
90 base::i18n::TextDirection RenderTextMac::GetTextDirection() {
91 return base::i18n::LEFT_TO_RIGHT;
92 }
93
94 Size RenderTextMac::GetStringSize() {
95 EnsureLayout();
96 return string_size_;
97 }
98
99 int RenderTextMac::GetBaseline() {
100 EnsureLayout();
101 return common_baseline_;
102 }
103
104 SelectionModel RenderTextMac::FindCursorPosition(const Point& point) {
105 // TODO(asvitkine): Implement this. http://crbug.com/131618
106 return SelectionModel();
107 }
108
109 std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() {
110 EnsureLayout();
111 if (!runs_valid_)
112 ComputeRuns();
113
114 std::vector<RenderText::FontSpan> spans;
115 for (size_t i = 0; i < runs_.size(); ++i) {
116 gfx::Font font(runs_[i].font_name, runs_[i].text_size);
117 const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run);
118 const ui::Range range(cf_range.location,
119 cf_range.location + cf_range.length);
120 spans.push_back(RenderText::FontSpan(font, range));
121 }
122
123 return spans;
124 }
125
126 SelectionModel RenderTextMac::AdjacentCharSelectionModel(
127 const SelectionModel& selection,
128 VisualCursorDirection direction) {
129 // TODO(asvitkine): Implement this. http://crbug.com/131618
130 return SelectionModel();
131 }
132
133 SelectionModel RenderTextMac::AdjacentWordSelectionModel(
134 const SelectionModel& selection,
135 VisualCursorDirection direction) {
136 // TODO(asvitkine): Implement this. http://crbug.com/131618
137 return SelectionModel();
138 }
139
140 void RenderTextMac::GetGlyphBounds(size_t index,
141 ui::Range* xspan,
142 int* height) {
143 // TODO(asvitkine): Implement this. http://crbug.com/131618
144 }
145
146 std::vector<Rect> RenderTextMac::GetSubstringBounds(ui::Range range) {
147 // TODO(asvitkine): Implement this. http://crbug.com/131618
148 return std::vector<Rect>();
149 }
150
151 bool RenderTextMac::IsCursorablePosition(size_t position) {
152 // TODO(asvitkine): Implement this. http://crbug.com/131618
153 return false;
154 }
155
156 void RenderTextMac::ResetLayout() {
157 line_.reset();
158 runs_.clear();
159 runs_valid_ = false;
160 }
161
162 void RenderTextMac::EnsureLayout() {
163 if (line_.get())
164 return;
165 runs_.clear();
166 runs_valid_ = false;
167
168 const Font& font = GetFont();
169 CTFontRef ct_font =
170 CreateCTFontWithPixelSize(font.GetFontName(), font.GetFontSize());
171
172 const void* keys[] = { kCTFontAttributeName };
173 const void* values[] = { ct_font };
174 base::mac::ScopedCFTypeRef<CFDictionaryRef> attributes(
175 CFDictionaryCreate(NULL, keys, values, arraysize(keys), NULL, NULL));
176
177 base::mac::ScopedCFTypeRef<CFStringRef> cf_text(
178 base::SysUTF16ToCFStringRef(text()));
179 base::mac::ScopedCFTypeRef<CFAttributedStringRef> attr_text(
180 CFAttributedStringCreate(NULL, cf_text, attributes));
181 base::mac::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable(
182 CFAttributedStringCreateMutableCopy(NULL, 0, attr_text));
183
184 ApplyStyles(attr_text_mutable, ct_font);
185 line_.reset(CTLineCreateWithAttributedString(attr_text_mutable));
186
187 CGFloat ascent = 0;
188 CGFloat descent = 0;
189 CGFloat leading = 0;
190 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
191 double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading);
192 string_size_ = Size(width, ascent + descent + leading);
193 common_baseline_ = ascent;
194 }
195
196 void RenderTextMac::DrawVisualText(Canvas* canvas) {
197 DCHECK(line_);
198 if (!runs_valid_)
199 ComputeRuns();
200
201 internal::SkiaTextRenderer renderer(canvas);
202 ApplyFadeEffects(&renderer);
203 ApplyTextShadows(&renderer);
204
205 for (size_t i = 0; i < runs_.size(); ++i) {
206 const TextRun& run = runs_[i];
207 renderer.SetForegroundColor(run.foreground);
208 renderer.SetTextSize(run.text_size);
209 renderer.SetFontFamilyWithStyle(run.font_name, run.font_style);
210 renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0],
211 run.glyphs.size());
212 renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width,
213 run.style);
214 }
215 }
216
217 RenderTextMac::TextRun::TextRun()
218 : ct_run(NULL),
219 origin(SkPoint::Make(0, 0)),
220 width(0),
221 font_style(Font::NORMAL),
222 text_size(0),
223 foreground(SK_ColorBLACK) {
224 }
225
226 RenderTextMac::TextRun::~TextRun() {
227 }
228
229 void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string,
230 CTFontRef font) {
231 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/Cor eText_StringAttributes_Ref/Reference/reference.html
232 for (size_t i = 0; i < style_ranges().size(); ++i) {
233 const StyleRange& style = style_ranges()[i];
234 const CFRange range = CFRangeMake(style.range.start(),
235 style.range.length());
236
237 // Note: CFAttributedStringSetAttribute() does not appear to retain the
238 // values passed in, as can be verified via CFGetRetainCount().
239 //
240 // TODO(asvitkine): The attributed string appears to hold weak refs to these
241 // objects (it does not release them either), so we need to keep track of
242 // them ourselves and release them at an appropriate time.
243
244 CGColorRef foreground = gfx::SkColorToCGColorRef(style.foreground);
245 CFAttributedStringSetAttribute(attr_string, range,
246 kCTForegroundColorAttributeName,
247 foreground);
248
249 if (style.underline) {
250 CTUnderlineStyle value = kCTUnderlineStyleSingle;
251 CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
252 CFAttributedStringSetAttribute(attr_string, range,
253 kCTUnderlineStyleAttributeName,
254 underline);
255 }
256
257 if (style.font_style & (Font::BOLD | Font::ITALIC)) {
258 int traits = 0;
259 if (style.font_style & Font::BOLD)
260 traits |= kCTFontBoldTrait;
261 if (style.font_style & Font::ITALIC)
262 traits |= kCTFontItalicTrait;
263 CTFontRef styled_font =
264 CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits);
265 // TODO(asvitkine): Handle |styled_font| == NULL case better.
266 if (styled_font) {
267 CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
268 styled_font);
269 }
270 }
271 }
272 }
273
274 void RenderTextMac::ComputeRuns() {
275 DCHECK(line_);
276
277 CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
278 const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
279
280 Point offset(GetTextOrigin());
281 // Skia will draw glyphs with respect to the baseline.
282 offset.Offset(0, common_baseline_);
283
284 const SkScalar x = SkIntToScalar(offset.x());
285 const SkScalar y = SkIntToScalar(offset.y());
286 SkPoint run_origin = SkPoint::Make(offset.x(), offset.y());
287
288 const CFRange empty_cf_range = CFRangeMake(0, 0);
289 for (CFIndex i = 0; i < ct_runs_count; ++i) {
290 CTRunRef ct_run =
291 base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
292 const size_t glyph_count = CTRunGetGlyphCount(ct_run);
293 const double run_width =
294 CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
295 if (glyph_count == 0) {
296 run_origin.offset(run_width, 0);
297 continue;
298 }
299
300 runs_.push_back(TextRun());
301 TextRun* run = &runs_.back();
302 run->ct_run = ct_run;
303 run->origin = run_origin;
304 run->width = run_width;
305 run->glyphs.resize(glyph_count);
306 CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
307 // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
308 // width (this has been observed at the beginning of a string containing
309 // Arabic content). Passing these to Skia will trigger an assertion;
310 // instead set their values to 0.
311 for (size_t glyph = 0; glyph < glyph_count; glyph++) {
312 if (run->glyphs[glyph] == 65535)
313 run->glyphs[glyph] = 0;
314 }
315
316 run->glyph_positions.resize(glyph_count);
317 const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
318 std::vector<CGPoint> positions;
319 if (positions_ptr == NULL) {
320 positions.resize(glyph_count);
321 CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
322 positions_ptr = &positions[0];
323 }
324 for (size_t glyph = 0; glyph < glyph_count; glyph++) {
325 SkPoint* point = &run->glyph_positions[glyph];
326 point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
327 y + SkDoubleToScalar(positions_ptr[glyph].y));
328 }
329
330 // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
331 // this better.
332 CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
333 CTFontRef ct_font =
334 base::mac::GetValueFromDictionary<CTFontRef>(attributes,
335 kCTFontAttributeName);
336 base::mac::ScopedCFTypeRef<CFStringRef> font_name_ref(
337 CTFontCopyFamilyName(ct_font));
338 run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
339 run->text_size = GetCTFontPixelSize(ct_font);
340
341 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
342 if (traits & kCTFontBoldTrait)
343 run->font_style |= Font::BOLD;
344 if (traits & kCTFontItalicTrait)
345 run->font_style |= Font::ITALIC;
346
347 const CGColorRef foreground =
348 base::mac::GetValueFromDictionary<CGColorRef>(
349 attributes, kCTForegroundColorAttributeName);
350 if (foreground)
351 run->foreground = gfx::CGColorRefToSkColor(foreground);
352
353 const CFNumberRef underline =
354 base::mac::GetValueFromDictionary<CFNumberRef>(
355 attributes, kCTUnderlineStyleAttributeName);
356 CTUnderlineStyle value = kCTUnderlineStyleNone;
357 if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
358 run->style.underline = (value == kCTUnderlineStyleSingle);
359
360 run_origin.offset(run_width, 0);
361 }
362 runs_valid_ = true;
363 }
364
365 RenderText* RenderText::CreateInstance() {
366 return new RenderTextMac;
367 }
368
369 } // namespace gfx
OLDNEW
« no previous file with comments | « ui/gfx/render_text_mac.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698