Skip to content

Commit

Permalink
Mac: Implement GetFallbackFontFamilies for Harfbuzz
Browse files Browse the repository at this point in the history
From 10.8 onwards, CoreText provides
CTFontCopyDefaultCascadeListForLanguages() which is exactly what we
want. For 10.6 and 10.7, use an older private interface,
CTFontCopyDefaultCascadeList().

Adds tests to ensure the feature detection and API behavior is sane
across all OSX versions we support.

BUG=439039, 462477
NOPRESUBMIT=true

Review URL: https://codereview.chromium.org/971673002

Cr-Commit-Position: refs/heads/master@{#319176}
  • Loading branch information
tapted authored and Commit bot committed Mar 5, 2015
1 parent 4449d97 commit 2daf544
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 23 deletions.
1 change: 1 addition & 0 deletions base/mac/foundation_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ CF_CAST_DECL(CFUUID);
CF_CAST_DECL(CGColor);

CF_CAST_DECL(CTFont);
CF_CAST_DECL(CTFontDescriptor);
CF_CAST_DECL(CTRun);

CF_CAST_DECL(SecACL);
Expand Down
1 change: 1 addition & 0 deletions base/mac/foundation_util.mm
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ CTFontRef NSToCFCast(NSFont* ns_val) {

CF_CAST_DEFN(CGColor);

CF_CAST_DEFN(CTFontDescriptor);
CF_CAST_DEFN(CTRun);

#if defined(OS_IOS)
Expand Down
3 changes: 2 additions & 1 deletion ui/gfx/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ component("gfx") {
"font.h",
"font_fallback.h",
"font_fallback_linux.cc",
"font_fallback_mac.cc",
"font_fallback_mac.mm",
"font_fallback_win.cc",
"font_fallback_win.h",
"font_list.cc",
Expand Down Expand Up @@ -420,6 +420,7 @@ test("gfx_unittests") {
"color_utils_unittest.cc",
"display_change_notifier_unittest.cc",
"display_unittest.cc",
"font_fallback_mac_unittest.cc",
"font_list_unittest.cc",
"geometry/box_unittest.cc",
"geometry/cubic_bezier_unittest.cc",
Expand Down
4 changes: 3 additions & 1 deletion ui/gfx/font_fallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
#include <string>
#include <vector>

#include "ui/gfx/gfx_export.h"

namespace gfx {

// Given a font family name, returns the names of font families that are
// suitable for fallback.
std::vector<std::string> GetFallbackFontFamilies(
GFX_EXPORT std::vector<std::string> GetFallbackFontFamilies(
const std::string& font_family);

} // namespace gfx
Expand Down
20 changes: 0 additions & 20 deletions ui/gfx/font_fallback_mac.cc

This file was deleted.

102 changes: 102 additions & 0 deletions ui/gfx/font_fallback_mac.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2015 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/font_fallback.h"

#import <Foundation/Foundation.h>
#include <dlfcn.h>
#include <string>
#include <vector>

#import "base/strings/sys_string_conversions.h"
#include "base/mac/foundation_util.h"
#import "base/mac/mac_util.h"

// CTFontCopyDefaultCascadeListForLanguages() doesn't exist in the 10.6 SDK.
// There is only the following. It doesn't exist in the public header files,
// but is an exported symbol so should always link.
extern "C" CFArrayRef CTFontCopyDefaultCascadeList(CTFontRef font_ref);

namespace {

// Wrapper for CTFontCopyDefaultCascadeListForLanguages() which should appear in
// CoreText.h from 10.8 onwards.
// TODO(tapted): Delete this wrapper when only 10.8+ is supported.
CFArrayRef CTFontCopyDefaultCascadeListForLanguagesWrapper(
CTFontRef font_ref,
CFArrayRef language_pref_list) {
typedef CFArrayRef (*MountainLionPrototype)(CTFontRef, CFArrayRef);
static const MountainLionPrototype cascade_with_languages_function =
reinterpret_cast<MountainLionPrototype>(
dlsym(RTLD_DEFAULT, "CTFontCopyDefaultCascadeListForLanguages"));
if (cascade_with_languages_function)
return cascade_with_languages_function(font_ref, language_pref_list);

// Fallback to the 10.6 Private API.
DCHECK(base::mac::IsOSLionOrEarlier());
return CTFontCopyDefaultCascadeList(font_ref);
}

} // namespace

namespace gfx {

std::vector<std::string> GetFallbackFontFamilies(
const std::string& font_family) {
// On Mac "There is a system default cascade list (which is polymorphic, based
// on the user's language setting and current font)" - CoreText Programming
// Guide.
// The CoreText APIs provide CTFontCreateForString(font, string, range), but
// it requires a text string "hint", and the returned font can't be
// represented by name for easy retrieval later.
// In 10.8, CTFontCopyDefaultCascadeListForLanguages(font, language_list)
// showed up which is a good fit GetFallbackFontFamilies().

// Size doesn't matter for querying the cascade list.
const CGFloat font_size = 10.0;
base::ScopedCFTypeRef<CFStringRef> cfname(
base::SysUTF8ToCFStringRef(font_family));

// Using CTFontCreateWithName here works, but CoreText emits stderr spam along
// the lines of `CTFontCreateWithName() using name "Arial" and got font with
// PostScript name "ArialMT".` Instead, create a descriptor.
const void* attribute_keys[] = {kCTFontFamilyNameAttribute};
const void* attribute_values[] = {cfname.get()};
base::ScopedCFTypeRef<CFDictionaryRef> attributes(CFDictionaryCreate(
kCFAllocatorDefault, attribute_keys, attribute_values, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
base::ScopedCFTypeRef<CTFontDescriptorRef> descriptor(
CTFontDescriptorCreateWithAttributes(attributes));
base::ScopedCFTypeRef<CTFontRef> base_font(
CTFontCreateWithFontDescriptor(descriptor, font_size, nullptr));

if (!base_font)
return std::vector<std::string>(1, font_family);

NSArray* languages = [[NSUserDefaults standardUserDefaults]
stringArrayForKey:@"AppleLanguages"];
CFArrayRef languages_cf = base::mac::NSToCFCast(languages);
base::ScopedCFTypeRef<CFArrayRef> cascade_list(
CTFontCopyDefaultCascadeListForLanguagesWrapper(base_font, languages_cf));

std::vector<std::string> fallback_fonts;

const CFIndex fallback_count = CFArrayGetCount(cascade_list);
for (CFIndex i = 0; i < fallback_count; ++i) {
CTFontDescriptorRef descriptor =
base::mac::CFCastStrict<CTFontDescriptorRef>(
CFArrayGetValueAtIndex(cascade_list, i));
base::ScopedCFTypeRef<CFStringRef> font_name(
base::mac::CFCastStrict<CFStringRef>(CTFontDescriptorCopyAttribute(
descriptor, kCTFontFamilyNameAttribute)));
fallback_fonts.push_back(base::SysCFStringRefToUTF8(font_name));
}

if (fallback_fonts.empty())
return std::vector<std::string>(1, font_family);

return fallback_fonts;
}

} // namespace gfx
21 changes: 21 additions & 0 deletions ui/gfx/font_fallback_mac_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2015 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/font_fallback.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace gfx {

// A targeted test for GetFallbackFontFamilies on Mac. It uses a system API that
// only became publicly available in the 10.8 SDK. This test is to ensure it
// behaves sensibly on all supported OS versions.
GTEST_TEST(FontFallbackMacTest, GetFallbackFontFamilies) {
std::vector<std::string> fallback_families = GetFallbackFontFamilies("Arial");
// If there is only one fallback, it means the only fallback is the font
// itself.
EXPECT_LT(1u, fallback_families.size());
}

} // namespace gfx
2 changes: 1 addition & 1 deletion ui/gfx/gfx.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
'font.h',
'font_fallback.h',
'font_fallback_linux.cc',
'font_fallback_mac.cc',
'font_fallback_mac.mm',
'font_fallback_win.cc',
'font_fallback_win.h',
'font_list.cc',
Expand Down
1 change: 1 addition & 0 deletions ui/gfx/gfx_tests.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
'color_utils_unittest.cc',
'display_change_notifier_unittest.cc',
'display_unittest.cc',
'font_fallback_mac_unittest.cc',
'font_list_unittest.cc',
'font_render_params_linux_unittest.cc',
'geometry/box_unittest.cc',
Expand Down
1 change: 1 addition & 0 deletions ui/gfx/render_text_harfbuzz.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class GFX_EXPORT RenderTextHarfBuzz : public RenderText {
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, HarfBuzz_SubglyphGraphemePartition);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, HarfBuzz_NonExistentFont);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, HarfBuzz_UniscribeFallback);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, HarfBuzz_UnicodeFallback);

// Specify the width of a glyph for test. The width of glyphs is very
// platform-dependent and environment-dependent. Otherwise multiline test
Expand Down
18 changes: 18 additions & 0 deletions ui/gfx/render_text_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2569,6 +2569,24 @@ TEST_F(RenderTextTest, HarfBuzz_UniscribeFallback) {
}
#endif // defined(OS_WIN)

// Ensure that the fallback fonts offered by gfx::GetFallbackFontFamilies() are
// tried. Note this test assumes the font "Arial" doesn't provide a unicode
// glyph for a particular character, and that there exists a system fallback
// font which does.
#if defined(OS_WIN) || defined(OS_MACOSX)
TEST_F(RenderTextTest, HarfBuzz_UnicodeFallback) {
RenderTextHarfBuzz render_text;
render_text.SetFontList(FontList("Arial, 12px"));

// Korean character "han".
render_text.SetText(WideToUTF16(L"\xd55c"));
render_text.EnsureLayout();
internal::TextRunList* run_list = render_text.GetRunList();
ASSERT_EQ(1U, run_list->size());
EXPECT_EQ(0U, run_list->runs()[0]->CountMissingGlyphs());
}
#endif // defined(OS_WIN) || defined(OS_MACOSX)

// Ensure that the width reported by RenderText is sufficient for drawing. Draws
// to a canvas and checks whether any pixel beyond the width is colored.
TEST_F(RenderTextTest, TextDoesntClip) {
Expand Down

0 comments on commit 2daf544

Please sign in to comment.