diff options
author | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-23 16:51:25 +0000 |
---|---|---|
committer | asvitkine@chromium.org <asvitkine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-23 16:51:25 +0000 |
commit | 04a0754f1de4d1a7ed0132542c489c630d918cf3 (patch) | |
tree | 05159d73946ade6c4e443917d7c50252f7d68043 /ui/gfx/render_text_mac.cc | |
parent | 6852fb0c12ca6789695927262912f287e7dd350e (diff) | |
download | chromium_src-04a0754f1de4d1a7ed0132542c489c630d918cf3.zip chromium_src-04a0754f1de4d1a7ed0132542c489c630d918cf3.tar.gz chromium_src-04a0754f1de4d1a7ed0132542c489c630d918cf3.tar.bz2 |
Initial RenderTextMac implementation using CoreText.
Has support for drawing and sizing the text, but not selection and cursor movement.
BUG=125664
TEST=Existing RenderText unit tests and manual testing by enabling RenderText on Mac in print_web_view_helper.cc code.
Review URL: https://chromiumcodereview.appspot.com/10543057
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147862 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/gfx/render_text_mac.cc')
-rw-r--r-- | ui/gfx/render_text_mac.cc | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/ui/gfx/render_text_mac.cc b/ui/gfx/render_text_mac.cc new file mode 100644 index 0000000..b157ecc --- /dev/null +++ b/ui/gfx/render_text_mac.cc @@ -0,0 +1,369 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/render_text_mac.h" + +#include <ApplicationServices/ApplicationServices.h> + +#include <cmath> +#include <utility> + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/sys_string_conversions.h" +#include "skia/ext/skia_utils_mac.h" + +namespace { + +// Returns the pixel height of |ct_font|. +CGFloat GetCTFontPixelSize(CTFontRef ct_font) { + return CTFontGetAscent(ct_font) + CTFontGetDescent(ct_font); +} + +// Creates a CTFont with the given font name and pixel size. Ownership is +// transferred to the caller. +// +// Note: This code makes use of pixel sizes (rather than view coordinate sizes) +// because it draws to an underlying Skia canvas, which is normally pixel based. +CTFontRef CreateCTFontWithPixelSize(const std::string& font_name, + const int target_pixel_size) { + // Epsilon value used for comparing font sizes. + const CGFloat kEpsilon = 0.001; + // The observed pixel to points ratio for Lucida Grande on 10.6. Other fonts + // have other ratios and the documentation doesn't provide a guarantee that + // the relation is linear. So this ratio is used as a first try before + // falling back to the bisection method. + const CGFloat kPixelsToPointsRatio = 0.849088; + + base::mac::ScopedCFTypeRef<CFStringRef> font_name_cf_string( + base::SysUTF8ToCFStringRef(font_name)); + + // First, try using |kPixelsToPointsRatio|. + CGFloat point_size = target_pixel_size * kPixelsToPointsRatio; + base::mac::ScopedCFTypeRef<CTFontRef> ct_font( + CTFontCreateWithName(font_name_cf_string, point_size, NULL)); + CGFloat actual_pixel_size = GetCTFontPixelSize(ct_font); + if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon) + return ct_font.release(); + + // |kPixelsToPointsRatio| wasn't correct. Use the bisection method to find the + // right size. + + // First, find the initial bisection range, so that the point size that + // corresponds to |target_pixel_size| is between |lo| and |hi|. + CGFloat lo = 0; + CGFloat hi = point_size; + while (actual_pixel_size < target_pixel_size) { + lo = hi; + hi *= 2; + ct_font.reset(CTFontCreateWithName(font_name_cf_string, hi, NULL)); + actual_pixel_size = GetCTFontPixelSize(ct_font); + } + + // Now, bisect to find the right size. + while (lo < hi) { + point_size = (hi - lo) * 0.5 + lo; + ct_font.reset(CTFontCreateWithName(font_name_cf_string, point_size, NULL)); + actual_pixel_size = GetCTFontPixelSize(ct_font); + if (std::fabs(actual_pixel_size - target_pixel_size) < kEpsilon) + break; + if (target_pixel_size > actual_pixel_size) + lo = point_size; + else + hi = point_size; + } + + return ct_font.release(); +} + +} // namespace + +namespace gfx { + +RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) { +} + +RenderTextMac::~RenderTextMac() { +} + +base::i18n::TextDirection RenderTextMac::GetTextDirection() { + return base::i18n::LEFT_TO_RIGHT; +} + +Size RenderTextMac::GetStringSize() { + EnsureLayout(); + return string_size_; +} + +int RenderTextMac::GetBaseline() { + EnsureLayout(); + return common_baseline_; +} + +SelectionModel RenderTextMac::FindCursorPosition(const Point& point) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return SelectionModel(); +} + +std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() { + EnsureLayout(); + if (!runs_valid_) + ComputeRuns(); + + std::vector<RenderText::FontSpan> spans; + for (size_t i = 0; i < runs_.size(); ++i) { + gfx::Font font(runs_[i].font_name, runs_[i].text_size); + const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run); + const ui::Range range(cf_range.location, + cf_range.location + cf_range.length); + spans.push_back(RenderText::FontSpan(font, range)); + } + + return spans; +} + +SelectionModel RenderTextMac::AdjacentCharSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return SelectionModel(); +} + +SelectionModel RenderTextMac::AdjacentWordSelectionModel( + const SelectionModel& selection, + VisualCursorDirection direction) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return SelectionModel(); +} + +void RenderTextMac::GetGlyphBounds(size_t index, + ui::Range* xspan, + int* height) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 +} + +std::vector<Rect> RenderTextMac::GetSubstringBounds(ui::Range range) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return std::vector<Rect>(); +} + +bool RenderTextMac::IsCursorablePosition(size_t position) { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return false; +} + +void RenderTextMac::ResetLayout() { + line_.reset(); + runs_.clear(); + runs_valid_ = false; +} + +void RenderTextMac::EnsureLayout() { + if (line_.get()) + return; + runs_.clear(); + runs_valid_ = false; + + const Font& font = GetFont(); + CTFontRef ct_font = + CreateCTFontWithPixelSize(font.GetFontName(), font.GetFontSize()); + + const void* keys[] = { kCTFontAttributeName }; + const void* values[] = { ct_font }; + base::mac::ScopedCFTypeRef<CFDictionaryRef> attributes( + CFDictionaryCreate(NULL, keys, values, arraysize(keys), NULL, NULL)); + + base::mac::ScopedCFTypeRef<CFStringRef> cf_text( + base::SysUTF16ToCFStringRef(text())); + base::mac::ScopedCFTypeRef<CFAttributedStringRef> attr_text( + CFAttributedStringCreate(NULL, cf_text, attributes)); + base::mac::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable( + CFAttributedStringCreateMutableCopy(NULL, 0, attr_text)); + + ApplyStyles(attr_text_mutable, ct_font); + line_.reset(CTLineCreateWithAttributedString(attr_text_mutable)); + + CGFloat ascent = 0; + CGFloat descent = 0; + CGFloat leading = 0; + // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+. + double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading); + string_size_ = Size(width, ascent + descent + leading); + common_baseline_ = ascent; +} + +void RenderTextMac::DrawVisualText(Canvas* canvas) { + DCHECK(line_); + if (!runs_valid_) + ComputeRuns(); + + internal::SkiaTextRenderer renderer(canvas); + ApplyFadeEffects(&renderer); + ApplyTextShadows(&renderer); + + for (size_t i = 0; i < runs_.size(); ++i) { + const TextRun& run = runs_[i]; + renderer.SetForegroundColor(run.foreground); + renderer.SetTextSize(run.text_size); + renderer.SetFontFamilyWithStyle(run.font_name, run.font_style); + renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0], + run.glyphs.size()); + renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width, + run.style); + } +} + +RenderTextMac::TextRun::TextRun() + : ct_run(NULL), + origin(SkPoint::Make(0, 0)), + width(0), + font_style(Font::NORMAL), + text_size(0), + foreground(SK_ColorBLACK) { +} + +RenderTextMac::TextRun::~TextRun() { +} + +void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string, + CTFontRef font) { + // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html + for (size_t i = 0; i < style_ranges().size(); ++i) { + const StyleRange& style = style_ranges()[i]; + const CFRange range = CFRangeMake(style.range.start(), + style.range.length()); + + // Note: CFAttributedStringSetAttribute() does not appear to retain the + // values passed in, as can be verified via CFGetRetainCount(). + // + // TODO(asvitkine): The attributed string appears to hold weak refs to these + // objects (it does not release them either), so we need to keep track of + // them ourselves and release them at an appropriate time. + + CGColorRef foreground = gfx::SkColorToCGColorRef(style.foreground); + CFAttributedStringSetAttribute(attr_string, range, + kCTForegroundColorAttributeName, + foreground); + + if (style.underline) { + CTUnderlineStyle value = kCTUnderlineStyleSingle; + CFNumberRef underline = CFNumberCreate(NULL, kCFNumberSInt32Type, &value); + CFAttributedStringSetAttribute(attr_string, range, + kCTUnderlineStyleAttributeName, + underline); + } + + if (style.font_style & (Font::BOLD | Font::ITALIC)) { + int traits = 0; + if (style.font_style & Font::BOLD) + traits |= kCTFontBoldTrait; + if (style.font_style & Font::ITALIC) + traits |= kCTFontItalicTrait; + CTFontRef styled_font = + CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits); + // TODO(asvitkine): Handle |styled_font| == NULL case better. + if (styled_font) { + CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName, + styled_font); + } + } + } +} + +void RenderTextMac::ComputeRuns() { + DCHECK(line_); + + CFArrayRef ct_runs = CTLineGetGlyphRuns(line_); + const CFIndex ct_runs_count = CFArrayGetCount(ct_runs); + + Point offset(GetTextOrigin()); + // Skia will draw glyphs with respect to the baseline. + offset.Offset(0, common_baseline_); + + const SkScalar x = SkIntToScalar(offset.x()); + const SkScalar y = SkIntToScalar(offset.y()); + SkPoint run_origin = SkPoint::Make(offset.x(), offset.y()); + + const CFRange empty_cf_range = CFRangeMake(0, 0); + for (CFIndex i = 0; i < ct_runs_count; ++i) { + CTRunRef ct_run = + base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i)); + const size_t glyph_count = CTRunGetGlyphCount(ct_run); + const double run_width = + CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL); + if (glyph_count == 0) { + run_origin.offset(run_width, 0); + continue; + } + + runs_.push_back(TextRun()); + TextRun* run = &runs_.back(); + run->ct_run = ct_run; + run->origin = run_origin; + run->width = run_width; + run->glyphs.resize(glyph_count); + CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]); + // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero + // width (this has been observed at the beginning of a string containing + // Arabic content). Passing these to Skia will trigger an assertion; + // instead set their values to 0. + for (size_t glyph = 0; glyph < glyph_count; glyph++) { + if (run->glyphs[glyph] == 65535) + run->glyphs[glyph] = 0; + } + + run->glyph_positions.resize(glyph_count); + const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run); + std::vector<CGPoint> positions; + if (positions_ptr == NULL) { + positions.resize(glyph_count); + CTRunGetPositions(ct_run, empty_cf_range, &positions[0]); + positions_ptr = &positions[0]; + } + for (size_t glyph = 0; glyph < glyph_count; glyph++) { + SkPoint* point = &run->glyph_positions[glyph]; + point->set(x + SkDoubleToScalar(positions_ptr[glyph].x), + y + SkDoubleToScalar(positions_ptr[glyph].y)); + } + + // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle + // this better. + CFDictionaryRef attributes = CTRunGetAttributes(ct_run); + CTFontRef ct_font = + base::mac::GetValueFromDictionary<CTFontRef>(attributes, + kCTFontAttributeName); + base::mac::ScopedCFTypeRef<CFStringRef> font_name_ref( + CTFontCopyFamilyName(ct_font)); + run->font_name = base::SysCFStringRefToUTF8(font_name_ref); + run->text_size = GetCTFontPixelSize(ct_font); + + CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font); + if (traits & kCTFontBoldTrait) + run->font_style |= Font::BOLD; + if (traits & kCTFontItalicTrait) + run->font_style |= Font::ITALIC; + + const CGColorRef foreground = + base::mac::GetValueFromDictionary<CGColorRef>( + attributes, kCTForegroundColorAttributeName); + if (foreground) + run->foreground = gfx::CGColorRefToSkColor(foreground); + + const CFNumberRef underline = + base::mac::GetValueFromDictionary<CFNumberRef>( + attributes, kCTUnderlineStyleAttributeName); + CTUnderlineStyle value = kCTUnderlineStyleNone; + if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value)) + run->style.underline = (value == kCTUnderlineStyleSingle); + + run_origin.offset(run_width, 0); + } + runs_valid_ = true; +} + +RenderText* RenderText::CreateInstance() { + return new RenderTextMac; +} + +} // namespace gfx |