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