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 <algorithm> 10#include <cmath> 11#include <utility> 12 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" 17 18namespace gfx { 19 20RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) { 21} 22 23RenderTextMac::~RenderTextMac() { 24} 25 26Size RenderTextMac::GetStringSize() { 27 EnsureLayout(); 28 return Size(std::ceil(string_size_.width()), string_size_.height()); 29} 30 31SizeF RenderTextMac::GetStringSizeF() { 32 EnsureLayout(); 33 return string_size_; 34} 35 36SelectionModel RenderTextMac::FindCursorPosition(const Point& point) { 37 // TODO(asvitkine): Implement this. http://crbug.com/131618 38 return SelectionModel(); 39} 40 41std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() { 42 EnsureLayout(); 43 if (!runs_valid_) 44 ComputeRuns(); 45 46 std::vector<RenderText::FontSpan> spans; 47 for (size_t i = 0; i < runs_.size(); ++i) { 48 gfx::Font font(runs_[i].font_name, runs_[i].text_size); 49 const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run); 50 const Range range(cf_range.location, cf_range.location + cf_range.length); 51 spans.push_back(RenderText::FontSpan(font, range)); 52 } 53 54 return spans; 55} 56 57int RenderTextMac::GetLayoutTextBaseline() { 58 EnsureLayout(); 59 return common_baseline_; 60} 61 62SelectionModel RenderTextMac::AdjacentCharSelectionModel( 63 const SelectionModel& selection, 64 VisualCursorDirection direction) { 65 // TODO(asvitkine): Implement this. http://crbug.com/131618 66 return SelectionModel(); 67} 68 69SelectionModel RenderTextMac::AdjacentWordSelectionModel( 70 const SelectionModel& selection, 71 VisualCursorDirection direction) { 72 // TODO(asvitkine): Implement this. http://crbug.com/131618 73 return SelectionModel(); 74} 75 76Range RenderTextMac::GetGlyphBounds(size_t index) { 77 // TODO(asvitkine): Implement this. http://crbug.com/131618 78 return Range(); 79} 80 81std::vector<Rect> RenderTextMac::GetSubstringBounds(const Range& range) { 82 // TODO(asvitkine): Implement this. http://crbug.com/131618 83 return std::vector<Rect>(); 84} 85 86size_t RenderTextMac::TextIndexToLayoutIndex(size_t index) const { 87 // TODO(asvitkine): Implement this. http://crbug.com/131618 88 return index; 89} 90 91size_t RenderTextMac::LayoutIndexToTextIndex(size_t index) const { 92 // TODO(asvitkine): Implement this. http://crbug.com/131618 93 return index; 94} 95 96bool RenderTextMac::IsCursorablePosition(size_t position) { 97 // TODO(asvitkine): Implement this. http://crbug.com/131618 98 return true; 99} 100 101void RenderTextMac::ResetLayout() { 102 line_.reset(); 103 attributes_.reset(); 104 runs_.clear(); 105 runs_valid_ = false; 106} 107 108void RenderTextMac::EnsureLayout() { 109 if (line_.get()) 110 return; 111 runs_.clear(); 112 runs_valid_ = false; 113 114 const Font& font = GetPrimaryFont(); 115 CTFontRef ct_font = base::mac::NSToCFCast(font.GetNativeFont()); 116 117 const void* keys[] = { kCTFontAttributeName }; 118 const void* values[] = { ct_font }; 119 base::ScopedCFTypeRef<CFDictionaryRef> attributes( 120 CFDictionaryCreate(NULL, 121 keys, 122 values, 123 arraysize(keys), 124 NULL, 125 &kCFTypeDictionaryValueCallBacks)); 126 127 base::ScopedCFTypeRef<CFStringRef> cf_text( 128 base::SysUTF16ToCFStringRef(text())); 129 base::ScopedCFTypeRef<CFAttributedStringRef> attr_text( 130 CFAttributedStringCreate(NULL, cf_text, attributes)); 131 base::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable( 132 CFAttributedStringCreateMutableCopy(NULL, 0, attr_text)); 133 134 // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the 135 // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc. 136 137 ApplyStyles(attr_text_mutable, ct_font); 138 line_.reset(CTLineCreateWithAttributedString(attr_text_mutable)); 139 140 CGFloat ascent = 0; 141 CGFloat descent = 0; 142 CGFloat leading = 0; 143 // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+. 144 double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading); 145 // Ensure ascent and descent are not smaller than ones of the font list. 146 // Keep them tall enough to draw often-used characters. 147 // For example, if a text field contains a Japanese character, which is 148 // smaller than Latin ones, and then later a Latin one is inserted, this 149 // ensures that the text baseline does not shift. 150 CGFloat font_list_height = font_list().GetHeight(); 151 CGFloat font_list_baseline = font_list().GetBaseline(); 152 ascent = std::max(ascent, font_list_baseline); 153 descent = std::max(descent, font_list_height - font_list_baseline); 154 string_size_ = SizeF(width, ascent + descent + leading); 155 common_baseline_ = ascent; 156} 157 158void RenderTextMac::DrawVisualText(Canvas* canvas) { 159 DCHECK(line_); 160 if (!runs_valid_) 161 ComputeRuns(); 162 163 internal::SkiaTextRenderer renderer(canvas); 164 ApplyFadeEffects(&renderer); 165 ApplyTextShadows(&renderer); 166 167 for (size_t i = 0; i < runs_.size(); ++i) { 168 const TextRun& run = runs_[i]; 169 renderer.SetForegroundColor(run.foreground); 170 renderer.SetTextSize(run.text_size); 171 renderer.SetFontFamilyWithStyle(run.font_name, run.font_style); 172 renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0], 173 run.glyphs.size()); 174 renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width, 175 run.underline, run.strike, run.diagonal_strike); 176 } 177} 178 179RenderTextMac::TextRun::TextRun() 180 : ct_run(NULL), 181 origin(SkPoint::Make(0, 0)), 182 width(0), 183 font_style(Font::NORMAL), 184 text_size(0), 185 foreground(SK_ColorBLACK), 186 underline(false), 187 strike(false), 188 diagonal_strike(false) { 189} 190 191RenderTextMac::TextRun::~TextRun() { 192} 193 194void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string, 195 CTFontRef font) { 196 // Temporarily apply composition underlines and selection colors. 197 ApplyCompositionAndSelectionStyles(); 198 199 // Note: CFAttributedStringSetAttribute() does not appear to retain the values 200 // passed in, as can be verified via CFGetRetainCount(). To ensure the 201 // attribute objects do not leak, they are saved to |attributes_|. 202 // Clear the attributes storage. 203 attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); 204 205 // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html 206 internal::StyleIterator style(colors(), styles()); 207 const size_t layout_text_length = GetLayoutText().length(); 208 for (size_t i = 0, end = 0; i < layout_text_length; i = end) { 209 end = TextIndexToLayoutIndex(style.GetRange().end()); 210 const CFRange range = CFRangeMake(i, end - i); 211 base::ScopedCFTypeRef<CGColorRef> foreground( 212 gfx::CGColorCreateFromSkColor(style.color())); 213 CFAttributedStringSetAttribute(attr_string, range, 214 kCTForegroundColorAttributeName, foreground); 215 CFArrayAppendValue(attributes_, foreground); 216 217 if (style.style(UNDERLINE)) { 218 CTUnderlineStyle value = kCTUnderlineStyleSingle; 219 base::ScopedCFTypeRef<CFNumberRef> underline_value( 220 CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); 221 CFAttributedStringSetAttribute(attr_string, range, 222 kCTUnderlineStyleAttributeName, 223 underline_value); 224 CFArrayAppendValue(attributes_, underline_value); 225 } 226 227 const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) | 228 (style.style(ITALIC) ? kCTFontItalicTrait : 0); 229 if (traits != 0) { 230 base::ScopedCFTypeRef<CTFontRef> styled_font( 231 CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits)); 232 // TODO(asvitkine): Handle |styled_font| == NULL case better. 233 if (styled_font) { 234 CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName, 235 styled_font); 236 CFArrayAppendValue(attributes_, styled_font); 237 } 238 } 239 240 style.UpdatePosition(LayoutIndexToTextIndex(end)); 241 } 242 243 // Undo the temporarily applied composition underlines and selection colors. 244 UndoCompositionAndSelectionStyles(); 245} 246 247void RenderTextMac::ComputeRuns() { 248 DCHECK(line_); 249 250 CFArrayRef ct_runs = CTLineGetGlyphRuns(line_); 251 const CFIndex ct_runs_count = CFArrayGetCount(ct_runs); 252 253 // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be 254 // updated based on alignment changes without resetting the layout. 255 gfx::Vector2d text_offset = GetLineOffset(0); 256 // Skia will draw glyphs with respect to the baseline. 257 text_offset += gfx::Vector2d(0, common_baseline_); 258 259 const SkScalar x = SkIntToScalar(text_offset.x()); 260 const SkScalar y = SkIntToScalar(text_offset.y()); 261 SkPoint run_origin = SkPoint::Make(x, y); 262 263 const CFRange empty_cf_range = CFRangeMake(0, 0); 264 for (CFIndex i = 0; i < ct_runs_count; ++i) { 265 CTRunRef ct_run = 266 base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i)); 267 const size_t glyph_count = CTRunGetGlyphCount(ct_run); 268 const double run_width = 269 CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL); 270 if (glyph_count == 0) { 271 run_origin.offset(run_width, 0); 272 continue; 273 } 274 275 runs_.push_back(TextRun()); 276 TextRun* run = &runs_.back(); 277 run->ct_run = ct_run; 278 run->origin = run_origin; 279 run->width = run_width; 280 run->glyphs.resize(glyph_count); 281 CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]); 282 // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero 283 // width (this has been observed at the beginning of a string containing 284 // Arabic content). Passing these to Skia will trigger an assertion; 285 // instead set their values to 0. 286 for (size_t glyph = 0; glyph < glyph_count; glyph++) { 287 if (run->glyphs[glyph] == 65535) 288 run->glyphs[glyph] = 0; 289 } 290 291 run->glyph_positions.resize(glyph_count); 292 const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run); 293 std::vector<CGPoint> positions; 294 if (positions_ptr == NULL) { 295 positions.resize(glyph_count); 296 CTRunGetPositions(ct_run, empty_cf_range, &positions[0]); 297 positions_ptr = &positions[0]; 298 } 299 for (size_t glyph = 0; glyph < glyph_count; glyph++) { 300 SkPoint* point = &run->glyph_positions[glyph]; 301 point->set(x + SkDoubleToScalar(positions_ptr[glyph].x), 302 y + SkDoubleToScalar(positions_ptr[glyph].y)); 303 } 304 305 // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle 306 // this better. Also, support strike and diagonal_strike. 307 CFDictionaryRef attributes = CTRunGetAttributes(ct_run); 308 CTFontRef ct_font = 309 base::mac::GetValueFromDictionary<CTFontRef>(attributes, 310 kCTFontAttributeName); 311 base::ScopedCFTypeRef<CFStringRef> font_name_ref( 312 CTFontCopyFamilyName(ct_font)); 313 run->font_name = base::SysCFStringRefToUTF8(font_name_ref); 314 run->text_size = CTFontGetSize(ct_font); 315 316 CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font); 317 if (traits & kCTFontBoldTrait) 318 run->font_style |= Font::BOLD; 319 if (traits & kCTFontItalicTrait) 320 run->font_style |= Font::ITALIC; 321 322 const CGColorRef foreground = 323 base::mac::GetValueFromDictionary<CGColorRef>( 324 attributes, kCTForegroundColorAttributeName); 325 if (foreground) 326 run->foreground = gfx::CGColorRefToSkColor(foreground); 327 328 const CFNumberRef underline = 329 base::mac::GetValueFromDictionary<CFNumberRef>( 330 attributes, kCTUnderlineStyleAttributeName); 331 CTUnderlineStyle value = kCTUnderlineStyleNone; 332 if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value)) 333 run->underline = (value == kCTUnderlineStyleSingle); 334 335 run_origin.offset(run_width, 0); 336 } 337 runs_valid_ = true; 338} 339 340RenderText* RenderText::CreateInstance() { 341 return new RenderTextMac; 342} 343 344} // namespace gfx 345