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 Size(std::ceil(string_size_.width()), string_size_.height());
     29 }
     30 
     31 SizeF RenderTextMac::GetStringSizeF() {
     32   EnsureLayout();
     33   return string_size_;
     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     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 
     57 int RenderTextMac::GetLayoutTextBaseline() {
     58   EnsureLayout();
     59   return common_baseline_;
     60 }
     61 
     62 SelectionModel RenderTextMac::AdjacentCharSelectionModel(
     63     const SelectionModel& selection,
     64     VisualCursorDirection direction) {
     65   // TODO(asvitkine): Implement this. http://crbug.com/131618
     66   return SelectionModel();
     67 }
     68 
     69 SelectionModel RenderTextMac::AdjacentWordSelectionModel(
     70     const SelectionModel& selection,
     71     VisualCursorDirection direction) {
     72   // TODO(asvitkine): Implement this. http://crbug.com/131618
     73   return SelectionModel();
     74 }
     75 
     76 Range RenderTextMac::GetGlyphBounds(size_t index) {
     77   // TODO(asvitkine): Implement this. http://crbug.com/131618
     78   return Range();
     79 }
     80 
     81 std::vector<Rect> RenderTextMac::GetSubstringBounds(const Range& range) {
     82   // TODO(asvitkine): Implement this. http://crbug.com/131618
     83   return std::vector<Rect>();
     84 }
     85 
     86 size_t RenderTextMac::TextIndexToLayoutIndex(size_t index) const {
     87   // TODO(asvitkine): Implement this. http://crbug.com/131618
     88   return index;
     89 }
     90 
     91 size_t RenderTextMac::LayoutIndexToTextIndex(size_t index) const {
     92   // TODO(asvitkine): Implement this. http://crbug.com/131618
     93   return index;
     94 }
     95 
     96 bool RenderTextMac::IsValidCursorIndex(size_t index) {
     97   // TODO(asvitkine): Implement this. http://crbug.com/131618
     98   return IsValidLogicalIndex(index);
     99 }
    100 
    101 void RenderTextMac::ResetLayout() {
    102   line_.reset();
    103   attributes_.reset();
    104   runs_.clear();
    105   runs_valid_ = false;
    106 }
    107 
    108 void RenderTextMac::EnsureLayout() {
    109   if (line_.get())
    110     return;
    111   runs_.clear();
    112   runs_valid_ = false;
    113 
    114   CTFontRef ct_font = base::mac::NSToCFCast(
    115       font_list().GetPrimaryFont().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 
    158 void 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   renderer.EndDiagonalStrike();
    179 }
    180 
    181 RenderTextMac::TextRun::TextRun()
    182     : ct_run(NULL),
    183       origin(SkPoint::Make(0, 0)),
    184       width(0),
    185       font_style(Font::NORMAL),
    186       text_size(0),
    187       foreground(SK_ColorBLACK),
    188       underline(false),
    189       strike(false),
    190       diagonal_strike(false) {
    191 }
    192 
    193 RenderTextMac::TextRun::~TextRun() {
    194 }
    195 
    196 void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string,
    197                                 CTFontRef font) {
    198   // Temporarily apply composition underlines and selection colors.
    199   ApplyCompositionAndSelectionStyles();
    200 
    201   // Note: CFAttributedStringSetAttribute() does not appear to retain the values
    202   // passed in, as can be verified via CFGetRetainCount(). To ensure the
    203   // attribute objects do not leak, they are saved to |attributes_|.
    204   // Clear the attributes storage.
    205   attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
    206 
    207   // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
    208   internal::StyleIterator style(colors(), styles());
    209   const size_t layout_text_length = GetLayoutText().length();
    210   for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
    211     end = TextIndexToLayoutIndex(style.GetRange().end());
    212     const CFRange range = CFRangeMake(i, end - i);
    213     base::ScopedCFTypeRef<CGColorRef> foreground(
    214         CGColorCreateFromSkColor(style.color()));
    215     CFAttributedStringSetAttribute(attr_string, range,
    216         kCTForegroundColorAttributeName, foreground);
    217     CFArrayAppendValue(attributes_, foreground);
    218 
    219     if (style.style(UNDERLINE)) {
    220       CTUnderlineStyle value = kCTUnderlineStyleSingle;
    221       base::ScopedCFTypeRef<CFNumberRef> underline_value(
    222           CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
    223       CFAttributedStringSetAttribute(attr_string, range,
    224                                      kCTUnderlineStyleAttributeName,
    225                                      underline_value);
    226       CFArrayAppendValue(attributes_, underline_value);
    227     }
    228 
    229     const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) |
    230                        (style.style(ITALIC) ? kCTFontItalicTrait : 0);
    231     if (traits != 0) {
    232       base::ScopedCFTypeRef<CTFontRef> styled_font(
    233           CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits));
    234       // TODO(asvitkine): Handle |styled_font| == NULL case better.
    235       if (styled_font) {
    236         CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
    237                                        styled_font);
    238         CFArrayAppendValue(attributes_, styled_font);
    239       }
    240     }
    241 
    242     style.UpdatePosition(LayoutIndexToTextIndex(end));
    243   }
    244 
    245   // Undo the temporarily applied composition underlines and selection colors.
    246   UndoCompositionAndSelectionStyles();
    247 }
    248 
    249 void RenderTextMac::ComputeRuns() {
    250   DCHECK(line_);
    251 
    252   CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
    253   const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
    254 
    255   // TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
    256   // updated based on alignment changes without resetting the layout.
    257   Vector2d text_offset = GetLineOffset(0);
    258   // Skia will draw glyphs with respect to the baseline.
    259   text_offset += Vector2d(0, common_baseline_);
    260 
    261   const SkScalar x = SkIntToScalar(text_offset.x());
    262   const SkScalar y = SkIntToScalar(text_offset.y());
    263   SkPoint run_origin = SkPoint::Make(x, y);
    264 
    265   const CFRange empty_cf_range = CFRangeMake(0, 0);
    266   for (CFIndex i = 0; i < ct_runs_count; ++i) {
    267     CTRunRef ct_run =
    268         base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
    269     const size_t glyph_count = CTRunGetGlyphCount(ct_run);
    270     const double run_width =
    271         CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
    272     if (glyph_count == 0) {
    273       run_origin.offset(run_width, 0);
    274       continue;
    275     }
    276 
    277     runs_.push_back(TextRun());
    278     TextRun* run = &runs_.back();
    279     run->ct_run = ct_run;
    280     run->origin = run_origin;
    281     run->width = run_width;
    282     run->glyphs.resize(glyph_count);
    283     CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
    284     // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
    285     // width (this has been observed at the beginning of a string containing
    286     // Arabic content). Passing these to Skia will trigger an assertion;
    287     // instead set their values to 0.
    288     for (size_t glyph = 0; glyph < glyph_count; glyph++) {
    289       if (run->glyphs[glyph] == 65535)
    290         run->glyphs[glyph] = 0;
    291     }
    292 
    293     run->glyph_positions.resize(glyph_count);
    294     const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
    295     std::vector<CGPoint> positions;
    296     if (positions_ptr == NULL) {
    297       positions.resize(glyph_count);
    298       CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
    299       positions_ptr = &positions[0];
    300     }
    301     for (size_t glyph = 0; glyph < glyph_count; glyph++) {
    302       SkPoint* point = &run->glyph_positions[glyph];
    303       point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
    304                  y + SkDoubleToScalar(positions_ptr[glyph].y));
    305     }
    306 
    307     // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
    308     //                  this better. Also, support strike and diagonal_strike.
    309     CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
    310     CTFontRef ct_font =
    311         base::mac::GetValueFromDictionary<CTFontRef>(attributes,
    312                                                      kCTFontAttributeName);
    313     base::ScopedCFTypeRef<CFStringRef> font_name_ref(
    314         CTFontCopyFamilyName(ct_font));
    315     run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
    316     run->text_size = CTFontGetSize(ct_font);
    317 
    318     CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
    319     if (traits & kCTFontBoldTrait)
    320       run->font_style |= Font::BOLD;
    321     if (traits & kCTFontItalicTrait)
    322       run->font_style |= Font::ITALIC;
    323 
    324     const CGColorRef foreground =
    325         base::mac::GetValueFromDictionary<CGColorRef>(
    326             attributes, kCTForegroundColorAttributeName);
    327     if (foreground)
    328       run->foreground = CGColorRefToSkColor(foreground);
    329 
    330     const CFNumberRef underline =
    331         base::mac::GetValueFromDictionary<CFNumberRef>(
    332             attributes, kCTUnderlineStyleAttributeName);
    333     CTUnderlineStyle value = kCTUnderlineStyleNone;
    334     if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
    335       run->underline = (value == kCTUnderlineStyleSingle);
    336 
    337     run_origin.offset(run_width, 0);
    338   }
    339   runs_valid_ = true;
    340 }
    341 
    342 RenderText* RenderText::CreateNativeInstance() {
    343   return new RenderTextMac;
    344 }
    345 
    346 }  // namespace gfx
    347