blob: bd62b5d4aa07586f45cf61026dd8c5460164b25a [file] [log] [blame]
// 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"
#import <AppKit/AppKit.h>
#include <ApplicationServices/ApplicationServices.h>
#include <CoreText/CoreText.h>
#include <algorithm>
#include <cmath>
#include <utility>
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/sys_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/skia/include/ports/SkTypeface_mac.h"
namespace {
// This function makes a copy of |font| with the given symbolic traits. On OSX
// 10.11, CTFontCreateCopyWithSymbolicTraits has the right behavior but
// CTFontCreateWithFontDescriptor does not. The opposite holds true for OSX
// 10.10.
base::ScopedCFTypeRef<CTFontRef> CopyFontWithSymbolicTraits(CTFontRef font,
int sym_traits) {
if (base::mac::IsOSElCapitanOrLater()) {
return base::ScopedCFTypeRef<CTFontRef>(CTFontCreateCopyWithSymbolicTraits(
font, 0, nullptr, sym_traits, sym_traits));
}
base::ScopedCFTypeRef<CTFontDescriptorRef> orig_desc(
CTFontCopyFontDescriptor(font));
base::ScopedCFTypeRef<CFDictionaryRef> orig_attributes(
CTFontDescriptorCopyAttributes(orig_desc));
// Make a mutable copy of orig_attributes.
base::ScopedCFTypeRef<CFMutableDictionaryRef> attributes(
CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, orig_attributes));
base::ScopedCFTypeRef<CFMutableDictionaryRef> traits(
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
base::ScopedCFTypeRef<CFNumberRef> n(
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &sym_traits));
CFDictionarySetValue(traits, kCTFontSymbolicTrait, n.release());
CFDictionarySetValue(attributes, kCTFontTraitsAttribute, traits.release());
base::ScopedCFTypeRef<CFStringRef> family_name(CTFontCopyFamilyName(font));
CFDictionarySetValue(attributes, kCTFontNameAttribute, family_name.release());
base::ScopedCFTypeRef<CTFontDescriptorRef> desc(
CTFontDescriptorCreateWithAttributes(attributes));
return base::ScopedCFTypeRef<CTFontRef>(
CTFontCreateWithFontDescriptor(desc, 0.0, nullptr));
}
} // namespace
namespace gfx {
namespace internal {
// Note: this is only used by RenderTextHarfbuzz.
sk_sp<SkTypeface> CreateSkiaTypeface(const Font& font,
bool italic,
Font::Weight weight) {
const Font::FontStyle style = italic ? Font::ITALIC : Font::NORMAL;
Font font_with_style = font.Derive(0, style, weight);
if (!font_with_style.GetNativeFont())
return nullptr;
return sk_sp<SkTypeface>(SkCreateTypefaceFromCTFont(
base::mac::NSToCFCast(font_with_style.GetNativeFont())));
}
} // namespace internal
RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) {}
RenderTextMac::~RenderTextMac() {}
std::unique_ptr<RenderText> RenderTextMac::CreateInstanceOfSameType() const {
return base::WrapUnique(new RenderTextMac);
}
bool RenderTextMac::MultilineSupported() const {
return false;
}
const base::string16& RenderTextMac::GetDisplayText() {
return text_elided() ? display_text() : layout_text();
}
Size RenderTextMac::GetStringSize() {
SizeF size_f = GetStringSizeF();
return Size(std::ceil(size_f.width()), size_f.height());
}
SizeF RenderTextMac::GetStringSizeF() {
EnsureLayout();
return string_size_;
}
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) {
const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run);
const Range range(cf_range.location, cf_range.location + cf_range.length);
spans.push_back(RenderText::FontSpan(
Font(base::mac::CFToNSCast(runs_[i].ct_font.get())), range));
}
return spans;
}
int RenderTextMac::GetDisplayTextBaseline() {
EnsureLayout();
return common_baseline_;
}
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();
}
Range RenderTextMac::GetGlyphBounds(size_t index) {
// TODO(asvitkine): Implement this. http://crbug.com/131618
return Range();
}
std::vector<Rect> RenderTextMac::GetSubstringBounds(const Range& range) {
// TODO(asvitkine): Implement this. http://crbug.com/131618
return std::vector<Rect>();
}
size_t RenderTextMac::TextIndexToDisplayIndex(size_t index) {
// TODO(asvitkine): Implement this. http://crbug.com/131618
return index;
}
size_t RenderTextMac::DisplayIndexToTextIndex(size_t index) {
// TODO(asvitkine): Implement this. http://crbug.com/131618
return index;
}
bool RenderTextMac::IsValidCursorIndex(size_t index) {
// TODO(asvitkine): Implement this. http://crbug.com/131618
return IsValidLogicalIndex(index);
}
void RenderTextMac::OnLayoutTextAttributeChanged(bool text_changed) {
DCHECK(!multiline()) << "RenderTextMac does not support multi line";
if (text_changed) {
if (elide_behavior() != NO_ELIDE && elide_behavior() != FADE_TAIL &&
!layout_text().empty()) {
UpdateDisplayText(std::ceil(GetLayoutTextWidth()));
} else {
UpdateDisplayText(0);
}
}
InvalidateStyle();
}
void RenderTextMac::OnDisplayTextAttributeChanged() {
OnLayoutTextAttributeChanged(true);
}
void RenderTextMac::OnTextColorChanged() {
InvalidateStyle();
}
void RenderTextMac::EnsureLayout() {
if (line_.get())
return;
runs_.clear();
runs_valid_ = false;
line_ = EnsureLayoutInternal(GetDisplayText(), &attributes_);
string_size_ = GetCTLineSize(line_.get(), &common_baseline_);
}
void RenderTextMac::DrawVisualText(internal::SkiaTextRenderer* renderer) {
DCHECK(line_);
if (!runs_valid_)
ComputeRuns();
ApplyFadeEffects(renderer);
ApplyTextShadows(renderer);
renderer->SetFontRenderParams(
font_list().GetPrimaryFont().GetFontRenderParams(),
subpixel_rendering_suppressed());
for (size_t i = 0; i < runs_.size(); ++i) {
const TextRun& run = runs_[i];
renderer->SetForegroundColor(run.foreground);
renderer->SetTextSize(CTFontGetSize(run.ct_font));
// The painter adds its own ref. So don't |release()| it from the ref ptr in
// TextRun.
renderer->SetTypeface(run.typeface);
renderer->DrawPosText(&run.glyph_positions[0], &run.glyphs[0],
run.glyphs.size());
renderer->DrawDecorations(run.origin.x(), run.origin.y(), run.width,
run.underline, run.strike, run.diagonal_strike);
}
renderer->EndDiagonalStrike();
}
RenderTextMac::TextRun::TextRun()
: ct_run(NULL),
origin(SkPoint::Make(0, 0)),
width(0),
foreground(SK_ColorBLACK),
underline(false),
strike(false),
diagonal_strike(false) {}
RenderTextMac::TextRun::TextRun(TextRun&& other) = default;
RenderTextMac::TextRun::~TextRun() {}
float RenderTextMac::GetLayoutTextWidth() {
base::ScopedCFTypeRef<CFMutableArrayRef> attributes_owner;
base::ScopedCFTypeRef<CTLineRef> line(
EnsureLayoutInternal(layout_text(), &attributes_owner));
SkScalar baseline;
return GetCTLineSize(line.get(), &baseline).width();
}
SizeF RenderTextMac::GetCTLineSize(CTLineRef line, SkScalar* baseline) {
CGFloat ascent = 0;
CGFloat descent = 0;
CGFloat leading = 0;
// TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
double width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
// Ensure ascent and descent are not smaller than ones of the font list.
// Keep them tall enough to draw often-used characters.
// For example, if a text field contains a Japanese character, which is
// smaller than Latin ones, and then later a Latin one is inserted, this
// ensures that the text baseline does not shift.
CGFloat font_list_height = font_list().GetHeight();
CGFloat font_list_baseline = font_list().GetBaseline();
ascent = std::max(ascent, font_list_baseline);
descent = std::max(descent, font_list_height - font_list_baseline);
*baseline = ascent;
return SizeF(width, std::max(ascent + descent + leading,
static_cast<CGFloat>(min_line_height())));
}
base::ScopedCFTypeRef<CTLineRef> RenderTextMac::EnsureLayoutInternal(
const base::string16& text,
base::ScopedCFTypeRef<CFMutableArrayRef>* attributes_owner) {
CTFontRef ct_font =
base::mac::NSToCFCast(font_list().GetPrimaryFont().GetNativeFont());
const void* keys[] = {kCTFontAttributeName};
const void* values[] = {ct_font};
base::ScopedCFTypeRef<CFDictionaryRef> attributes(
CFDictionaryCreate(NULL, keys, values, arraysize(keys), NULL,
&kCFTypeDictionaryValueCallBacks));
base::ScopedCFTypeRef<CFStringRef> cf_text(base::SysUTF16ToCFStringRef(text));
base::ScopedCFTypeRef<CFAttributedStringRef> attr_text(
CFAttributedStringCreate(NULL, cf_text, attributes));
base::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable(
CFAttributedStringCreateMutableCopy(NULL, 0, attr_text));
// TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
// natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
*attributes_owner = ApplyStyles(text, attr_text_mutable, ct_font);
return base::ScopedCFTypeRef<CTLineRef>(
CTLineCreateWithAttributedString(attr_text_mutable));
}
base::ScopedCFTypeRef<CFMutableArrayRef> RenderTextMac::ApplyStyles(
const base::string16& text,
CFMutableAttributedStringRef attr_string,
CTFontRef font) {
// Temporarily apply composition underlines and selection colors.
ApplyCompositionAndSelectionStyles();
// Note: CFAttributedStringSetAttribute() does not appear to retain the values
// passed in, as can be verified via CFGetRetainCount(). To ensure the
// attribute objects do not leak, they are saved to |attributes_|.
// Clear the attributes storage.
base::ScopedCFTypeRef<CFMutableArrayRef> attributes(
CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
// https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
internal::StyleIterator style(colors(), baselines(), weights(), styles());
const size_t layout_text_length = CFAttributedStringGetLength(attr_string);
for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
end = TextIndexToGivenTextIndex(text, style.GetRange().end());
const CFRange range = CFRangeMake(i, end - i);
base::ScopedCFTypeRef<CGColorRef> foreground(
skia::CGColorCreateFromSkColor(style.color()));
CFAttributedStringSetAttribute(attr_string, range,
kCTForegroundColorAttributeName, foreground);
CFArrayAppendValue(attributes, foreground);
if (style.style(UNDERLINE)) {
CTUnderlineStyle value = kCTUnderlineStyleSingle;
base::ScopedCFTypeRef<CFNumberRef> underline_value(
CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
CFAttributedStringSetAttribute(
attr_string, range, kCTUnderlineStyleAttributeName, underline_value);
CFArrayAppendValue(attributes, underline_value);
}
// TODO(mboc): Apply font weights other than bold below.
const int traits =
(style.style(ITALIC) ? kCTFontItalicTrait : 0) |
(style.weight() >= Font::Weight::BOLD ? kCTFontBoldTrait : 0);
if (traits != 0) {
base::ScopedCFTypeRef<CTFontRef> styled_font =
CopyFontWithSymbolicTraits(font, traits);
// TODO(asvitkine): Handle |styled_font| == NULL case better.
if (styled_font) {
CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
styled_font);
CFArrayAppendValue(attributes, styled_font);
}
}
style.UpdatePosition(DisplayIndexToTextIndex(end));
}
// Undo the temporarily applied composition underlines and selection colors.
UndoCompositionAndSelectionStyles();
return attributes;
}
void RenderTextMac::ComputeRuns() {
DCHECK(line_);
CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
// TODO(asvitkine): Don't use GetLineOffset() until draw time, since it may be
// updated based on alignment changes without resetting the layout.
Vector2d text_offset = GetLineOffset(0);
// Skia will draw glyphs with respect to the baseline.
text_offset += Vector2d(0, common_baseline_);
const SkScalar x = SkIntToScalar(text_offset.x());
const SkScalar y = SkIntToScalar(text_offset.y());
SkPoint run_origin = SkPoint::Make(x, 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_.emplace_back();
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. Also, support strike and diagonal_strike.
CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
CTFontRef ct_font = base::mac::GetValueFromDictionary<CTFontRef>(
attributes, kCTFontAttributeName);
run->ct_font.reset(ct_font, base::scoped_policy::RETAIN);
run->typeface.reset(SkCreateTypefaceFromCTFont(ct_font));
const CGColorRef foreground = base::mac::GetValueFromDictionary<CGColorRef>(
attributes, kCTForegroundColorAttributeName);
if (foreground)
run->foreground = skia::CGColorRefToSkColor(foreground);
const CFNumberRef underline =
base::mac::GetValueFromDictionary<CFNumberRef>(
attributes, kCTUnderlineStyleAttributeName);
CTUnderlineStyle value = kCTUnderlineStyleNone;
if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
run->underline = (value == kCTUnderlineStyleSingle);
run_origin.offset(run_width, 0);
}
runs_valid_ = true;
}
void RenderTextMac::InvalidateStyle() {
line_.reset();
attributes_.reset();
runs_.clear();
runs_valid_ = false;
}
} // namespace gfx