mac: Add the flag "-gline-tables-only" to reduce dSYM size. (attempt #2)
[chromium-blink-merge.git] / ui / gfx / render_text_mac.cc
blob1c6e78da0dfe543920392d696784ed29ffb930a0
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>
9 #include <algorithm>
10 #include <cmath>
11 #include <utility>
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"
18 namespace gfx {
20 RenderTextMac::RenderTextMac()
21 : common_baseline_(0), runs_valid_(false) {
24 RenderTextMac::~RenderTextMac() {
27 scoped_ptr<RenderText> RenderTextMac::CreateInstanceOfSameType() const {
28 return make_scoped_ptr(new RenderTextMac);
31 bool RenderTextMac::MultilineSupported() const {
32 return false;
35 const base::string16& RenderTextMac::GetDisplayText() {
36 return text_elided() ? display_text() : layout_text();
39 Size RenderTextMac::GetStringSize() {
40 SizeF size_f = GetStringSizeF();
41 return Size(std::ceil(size_f.width()), size_f.height());
44 SizeF RenderTextMac::GetStringSizeF() {
45 EnsureLayout();
46 return string_size_;
49 SelectionModel RenderTextMac::FindCursorPosition(const Point& point) {
50 // TODO(asvitkine): Implement this. http://crbug.com/131618
51 return SelectionModel();
54 std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() {
55 EnsureLayout();
56 if (!runs_valid_)
57 ComputeRuns();
59 std::vector<RenderText::FontSpan> spans;
60 for (size_t i = 0; i < runs_.size(); ++i) {
61 Font font(runs_[i].font_name, runs_[i].text_size);
62 const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run);
63 const Range range(cf_range.location, cf_range.location + cf_range.length);
64 spans.push_back(RenderText::FontSpan(font, range));
67 return spans;
70 int RenderTextMac::GetDisplayTextBaseline() {
71 EnsureLayout();
72 return common_baseline_;
75 SelectionModel RenderTextMac::AdjacentCharSelectionModel(
76 const SelectionModel& selection,
77 VisualCursorDirection direction) {
78 // TODO(asvitkine): Implement this. http://crbug.com/131618
79 return SelectionModel();
82 SelectionModel RenderTextMac::AdjacentWordSelectionModel(
83 const SelectionModel& selection,
84 VisualCursorDirection direction) {
85 // TODO(asvitkine): Implement this. http://crbug.com/131618
86 return SelectionModel();
89 Range RenderTextMac::GetGlyphBounds(size_t index) {
90 // TODO(asvitkine): Implement this. http://crbug.com/131618
91 return Range();
94 std::vector<Rect> RenderTextMac::GetSubstringBounds(const Range& range) {
95 // TODO(asvitkine): Implement this. http://crbug.com/131618
96 return std::vector<Rect>();
99 size_t RenderTextMac::TextIndexToDisplayIndex(size_t index) {
100 // TODO(asvitkine): Implement this. http://crbug.com/131618
101 return index;
104 size_t RenderTextMac::DisplayIndexToTextIndex(size_t index) {
105 // TODO(asvitkine): Implement this. http://crbug.com/131618
106 return index;
109 bool RenderTextMac::IsValidCursorIndex(size_t index) {
110 // TODO(asvitkine): Implement this. http://crbug.com/131618
111 return IsValidLogicalIndex(index);
114 void RenderTextMac::OnLayoutTextAttributeChanged(bool text_changed) {
115 DCHECK(!multiline()) << "RenderTextMac does not support multi line";
116 if (text_changed) {
117 if (elide_behavior() != NO_ELIDE &&
118 elide_behavior() != FADE_TAIL &&
119 !layout_text().empty()) {
120 UpdateDisplayText(std::ceil(GetLayoutTextWidth()));
121 } else {
122 UpdateDisplayText(0);
125 line_.reset();
126 attributes_.reset();
127 runs_.clear();
128 runs_valid_ = false;
131 void RenderTextMac::OnDisplayTextAttributeChanged() {
132 OnLayoutTextAttributeChanged(true);
135 void RenderTextMac::EnsureLayout() {
136 if (line_.get())
137 return;
138 runs_.clear();
139 runs_valid_ = false;
141 line_ = EnsureLayoutInternal(GetDisplayText(), &attributes_);
142 string_size_ = GetCTLineSize(line_.get(), &common_baseline_);
145 void RenderTextMac::DrawVisualText(Canvas* canvas) {
146 DCHECK(line_);
147 if (!runs_valid_)
148 ComputeRuns();
150 internal::SkiaTextRenderer renderer(canvas);
151 ApplyFadeEffects(&renderer);
152 ApplyTextShadows(&renderer);
154 for (size_t i = 0; i < runs_.size(); ++i) {
155 const TextRun& run = runs_[i];
156 renderer.SetForegroundColor(run.foreground);
157 renderer.SetTextSize(run.text_size);
158 renderer.SetFontFamilyWithStyle(run.font_name, run.font_style);
159 renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0],
160 run.glyphs.size());
161 renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width,
162 run.underline, run.strike, run.diagonal_strike);
165 renderer.EndDiagonalStrike();
168 RenderTextMac::TextRun::TextRun()
169 : ct_run(NULL),
170 origin(SkPoint::Make(0, 0)),
171 width(0),
172 font_style(Font::NORMAL),
173 text_size(0),
174 foreground(SK_ColorBLACK),
175 underline(false),
176 strike(false),
177 diagonal_strike(false) {
180 RenderTextMac::TextRun::~TextRun() {
183 float RenderTextMac::GetLayoutTextWidth() {
184 base::ScopedCFTypeRef<CFMutableArrayRef> attributes_owner;
185 base::ScopedCFTypeRef<CTLineRef> line(
186 EnsureLayoutInternal(layout_text(), &attributes_owner));
187 SkScalar baseline;
188 return GetCTLineSize(line.get(), &baseline).width();
191 gfx::SizeF RenderTextMac::GetCTLineSize(CTLineRef line, SkScalar* baseline) {
192 CGFloat ascent = 0;
193 CGFloat descent = 0;
194 CGFloat leading = 0;
195 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
196 double width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
197 // Ensure ascent and descent are not smaller than ones of the font list.
198 // Keep them tall enough to draw often-used characters.
199 // For example, if a text field contains a Japanese character, which is
200 // smaller than Latin ones, and then later a Latin one is inserted, this
201 // ensures that the text baseline does not shift.
202 CGFloat font_list_height = font_list().GetHeight();
203 CGFloat font_list_baseline = font_list().GetBaseline();
204 ascent = std::max(ascent, font_list_baseline);
205 descent = std::max(descent, font_list_height - font_list_baseline);
206 *baseline = ascent;
207 return SizeF(
208 width, std::max(ascent + descent + leading,
209 static_cast<CGFloat>(min_line_height())));
212 base::ScopedCFTypeRef<CTLineRef> RenderTextMac::EnsureLayoutInternal(
213 const base::string16& text,
214 base::ScopedCFTypeRef<CFMutableArrayRef>* attributes_owner) {
215 CTFontRef ct_font = base::mac::NSToCFCast(
216 font_list().GetPrimaryFont().GetNativeFont());
218 const void* keys[] = { kCTFontAttributeName };
219 const void* values[] = { ct_font };
220 base::ScopedCFTypeRef<CFDictionaryRef> attributes(
221 CFDictionaryCreate(NULL,
222 keys,
223 values,
224 arraysize(keys),
225 NULL,
226 &kCFTypeDictionaryValueCallBacks));
228 base::ScopedCFTypeRef<CFStringRef> cf_text(
229 base::SysUTF16ToCFStringRef(text));
230 base::ScopedCFTypeRef<CFAttributedStringRef> attr_text(
231 CFAttributedStringCreate(NULL, cf_text, attributes));
232 base::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable(
233 CFAttributedStringCreateMutableCopy(NULL, 0, attr_text));
235 // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
236 // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
238 *attributes_owner = ApplyStyles(text, attr_text_mutable, ct_font);
239 return base::ScopedCFTypeRef<CTLineRef>(
240 CTLineCreateWithAttributedString(attr_text_mutable));
243 base::ScopedCFTypeRef<CFMutableArrayRef> RenderTextMac::ApplyStyles(
244 const base::string16& text,
245 CFMutableAttributedStringRef attr_string,
246 CTFontRef font) {
247 // Temporarily apply composition underlines and selection colors.
248 ApplyCompositionAndSelectionStyles();
250 // Note: CFAttributedStringSetAttribute() does not appear to retain the values
251 // passed in, as can be verified via CFGetRetainCount(). To ensure the
252 // attribute objects do not leak, they are saved to |attributes_|.
253 // Clear the attributes storage.
254 base::ScopedCFTypeRef<CFMutableArrayRef> attributes(
255 CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
257 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
258 internal::StyleIterator style(colors(), baselines(), styles());
259 const size_t layout_text_length = CFAttributedStringGetLength(attr_string);
260 for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
261 end = TextIndexToGivenTextIndex(text, style.GetRange().end());
262 const CFRange range = CFRangeMake(i, end - i);
263 base::ScopedCFTypeRef<CGColorRef> foreground(
264 CGColorCreateFromSkColor(style.color()));
265 CFAttributedStringSetAttribute(attr_string, range,
266 kCTForegroundColorAttributeName, foreground);
267 CFArrayAppendValue(attributes, foreground);
269 if (style.style(UNDERLINE)) {
270 CTUnderlineStyle value = kCTUnderlineStyleSingle;
271 base::ScopedCFTypeRef<CFNumberRef> underline_value(
272 CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
273 CFAttributedStringSetAttribute(attr_string, range,
274 kCTUnderlineStyleAttributeName,
275 underline_value);
276 CFArrayAppendValue(attributes, underline_value);
279 const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) |
280 (style.style(ITALIC) ? kCTFontItalicTrait : 0);
281 if (traits != 0) {
282 base::ScopedCFTypeRef<CTFontRef> styled_font(
283 CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits));
284 // TODO(asvitkine): Handle |styled_font| == NULL case better.
285 if (styled_font) {
286 CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
287 styled_font);
288 CFArrayAppendValue(attributes, styled_font);
292 style.UpdatePosition(DisplayIndexToTextIndex(end));
295 // Undo the temporarily applied composition underlines and selection colors.
296 UndoCompositionAndSelectionStyles();
298 return attributes;
301 void RenderTextMac::ComputeRuns() {
302 DCHECK(line_);
304 CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
305 const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
307 // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
308 // updated based on alignment changes without resetting the layout.
309 Vector2d text_offset = GetLineOffset(0);
310 // Skia will draw glyphs with respect to the baseline.
311 text_offset += Vector2d(0, common_baseline_);
313 const SkScalar x = SkIntToScalar(text_offset.x());
314 const SkScalar y = SkIntToScalar(text_offset.y());
315 SkPoint run_origin = SkPoint::Make(x, y);
317 const CFRange empty_cf_range = CFRangeMake(0, 0);
318 for (CFIndex i = 0; i < ct_runs_count; ++i) {
319 CTRunRef ct_run =
320 base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
321 const size_t glyph_count = CTRunGetGlyphCount(ct_run);
322 const double run_width =
323 CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
324 if (glyph_count == 0) {
325 run_origin.offset(run_width, 0);
326 continue;
329 runs_.push_back(TextRun());
330 TextRun* run = &runs_.back();
331 run->ct_run = ct_run;
332 run->origin = run_origin;
333 run->width = run_width;
334 run->glyphs.resize(glyph_count);
335 CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
336 // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
337 // width (this has been observed at the beginning of a string containing
338 // Arabic content). Passing these to Skia will trigger an assertion;
339 // instead set their values to 0.
340 for (size_t glyph = 0; glyph < glyph_count; glyph++) {
341 if (run->glyphs[glyph] == 65535)
342 run->glyphs[glyph] = 0;
345 run->glyph_positions.resize(glyph_count);
346 const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
347 std::vector<CGPoint> positions;
348 if (positions_ptr == NULL) {
349 positions.resize(glyph_count);
350 CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
351 positions_ptr = &positions[0];
353 for (size_t glyph = 0; glyph < glyph_count; glyph++) {
354 SkPoint* point = &run->glyph_positions[glyph];
355 point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
356 y + SkDoubleToScalar(positions_ptr[glyph].y));
359 // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
360 // this better. Also, support strike and diagonal_strike.
361 CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
362 CTFontRef ct_font =
363 base::mac::GetValueFromDictionary<CTFontRef>(attributes,
364 kCTFontAttributeName);
365 base::ScopedCFTypeRef<CFStringRef> font_name_ref(
366 CTFontCopyFamilyName(ct_font));
367 run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
368 run->text_size = CTFontGetSize(ct_font);
370 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
371 if (traits & kCTFontBoldTrait)
372 run->font_style |= Font::BOLD;
373 if (traits & kCTFontItalicTrait)
374 run->font_style |= Font::ITALIC;
376 const CGColorRef foreground =
377 base::mac::GetValueFromDictionary<CGColorRef>(
378 attributes, kCTForegroundColorAttributeName);
379 if (foreground)
380 run->foreground = CGColorRefToSkColor(foreground);
382 const CFNumberRef underline =
383 base::mac::GetValueFromDictionary<CFNumberRef>(
384 attributes, kCTUnderlineStyleAttributeName);
385 CTUnderlineStyle value = kCTUnderlineStyleNone;
386 if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
387 run->underline = (value == kCTUnderlineStyleSingle);
389 run_origin.offset(run_width, 0);
391 runs_valid_ = true;
394 } // namespace gfx