Home | History | Annotate | Download | only in gfx
      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