[go: nahoru, domu]

blob: 7f69c2658d776398d13c9d2275e870067feeff02 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
* Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <math.h>
#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#import "third_party/blink/renderer/platform/wtf/hash_set.h"
#import "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
namespace blink {
namespace {
const NSFontTraitMask SYNTHESIZED_FONT_TRAITS =
(NSBoldFontMask | NSItalicFontMask);
const NSFontTraitMask IMPORTANT_FONT_TRAITS =
(NSCompressedFontMask | NSCondensedFontMask | NSExpandedFontMask |
NSItalicFontMask | NSNarrowFontMask | NSPosterFontMask |
NSSmallCapsFontMask);
BOOL AcceptableChoice(NSFontTraitMask desired_traits,
NSFontTraitMask candidate_traits) {
desired_traits &= ~SYNTHESIZED_FONT_TRAITS;
return (candidate_traits & desired_traits) == desired_traits;
}
BOOL BetterChoice(NSFontTraitMask desired_traits,
NSInteger desired_weight,
NSFontTraitMask chosen_traits,
NSInteger chosen_weight,
NSFontTraitMask candidate_traits,
NSInteger candidate_weight) {
if (!AcceptableChoice(desired_traits, candidate_traits))
return NO;
// A list of the traits we care about.
// The top item in the list is the worst trait to mismatch; if a font has this
// and we didn't ask for it, we'd prefer any other font in the family.
const NSFontTraitMask kMasks[] = {NSPosterFontMask, NSSmallCapsFontMask,
NSItalicFontMask, NSCompressedFontMask,
NSCondensedFontMask, NSExpandedFontMask,
NSNarrowFontMask};
for (NSFontTraitMask mask : kMasks) {
BOOL desired = (desired_traits & mask) != 0;
BOOL chosen_has_unwanted_trait = desired != ((chosen_traits & mask) != 0);
BOOL candidate_has_unwanted_trait =
desired != ((candidate_traits & mask) != 0);
if (!candidate_has_unwanted_trait && chosen_has_unwanted_trait)
return YES;
if (!chosen_has_unwanted_trait && candidate_has_unwanted_trait)
return NO;
}
NSInteger chosen_weight_delta_magnitude = abs(chosen_weight - desired_weight);
NSInteger candidate_weight_delta_magnitude =
abs(candidate_weight - desired_weight);
// If both are the same distance from the desired weight, prefer the candidate
// if it is further from medium.
if (chosen_weight_delta_magnitude == candidate_weight_delta_magnitude)
return abs(candidate_weight - 6) > abs(chosen_weight - 6);
// Otherwise, prefer the one closer to the desired weight.
return candidate_weight_delta_magnitude < chosen_weight_delta_magnitude;
}
NSFontWeight ToFontWeight(blink::FontSelectionValue font_weight) {
if (font_weight <= 50 || font_weight >= 950)
return NSFontWeightRegular;
const NSFontWeight ns_font_weights[] = {
NSFontWeightUltraLight, NSFontWeightThin, NSFontWeightLight,
NSFontWeightRegular, NSFontWeightMedium, NSFontWeightSemibold,
NSFontWeightBold, NSFontWeightHeavy, NSFontWeightBlack,
};
size_t select_weight = roundf(font_weight / 100) - 1;
DCHECK_GE(select_weight, 0ul);
DCHECK_LE(select_weight, std::size(ns_font_weights));
return ns_font_weights[select_weight];
}
} // namespace
NSFont* MatchUniqueFont(const AtomicString& unique_font_name, float size) {
// Note the header documentation: when matching, the system first searches for
// fonts with its value as their PostScript name, then falls back to searching
// for fonts with its value as their family name, and then falls back to
// searching for fonts with its value as their display name.
NSString* desired_name = unique_font_name;
NSDictionary* attributes = @{
base::apple::CFToNSPtrCast(kCTFontNameAttribute) : desired_name,
base::apple::CFToNSPtrCast(kCTFontSizeAttribute) : @(size)
};
base::ScopedCFTypeRef<CTFontDescriptorRef> descriptor(
CTFontDescriptorCreateWithAttributes(
base::apple::NSToCFPtrCast(attributes)));
base::ScopedCFTypeRef<CTFontRef> matched_font(
CTFontCreateWithFontDescriptor(descriptor, 0, nullptr));
DCHECK(matched_font);
// CoreText will usually give us *something* but not always an exactly matched
// font.
NSString* matched_postscript_name = base::apple::CFToNSOwnershipCast(
CTFontCopyName(matched_font, kCTFontPostScriptNameKey));
NSString* matched_full_font_name = base::apple::CFToNSOwnershipCast(
CTFontCopyName(matched_font, kCTFontFullNameKey));
// If the found font does not match in PostScript name or full font name, it's
// not the exact match that is required, so return nullptr.
if ([matched_postscript_name caseInsensitiveCompare:desired_name] !=
NSOrderedSame &&
[matched_full_font_name caseInsensitiveCompare:desired_name] !=
NSOrderedSame) {
return nullptr;
}
return base::apple::CFToNSOwnershipCast(matched_font.release());
}
// Family name is somewhat of a misnomer here. We first attempt to find an
// exact match comparing the desiredFamily to the PostScript name of the
// installed fonts. If that fails we then do a search based on the family
// names of the installed fonts.
NSFont* MatchNSFontFamily(const AtomicString& desired_family_string,
NSFontTraitMask desired_traits,
FontSelectionValue desired_weight,
float size) {
DCHECK_NE(desired_family_string, FontCache::LegacySystemFontFamily());
if (desired_family_string == font_family_names::kSystemUi) {
NSFont* font = [NSFont systemFontOfSize:size
weight:ToFontWeight(desired_weight)];
if (desired_traits & IMPORTANT_FONT_TRAITS)
font = [NSFontManager.sharedFontManager convertFont:font
toHaveTrait:desired_traits];
return font;
}
NSString* desired_family = desired_family_string;
NSFontManager* font_manager = NSFontManager.sharedFontManager;
// From macOS 10.15 `NSFontManager.availableFonts` does not list certain
// fonts that availableMembersOfFontFamily actually shows results for, for
// example "Hiragino Kaku Gothic ProN" is not listed, only Hiragino Sans is
// listed. We previously enumerated availableFontFamilies and looked for a
// case-insensitive string match here, but instead, we can rely on
// availableMembersOfFontFamily here to do a case-insensitive comparison, then
// set available_family to desired_family if the result was not empty.
// See https://crbug.com/1000542
NSString* available_family = nil;
NSArray* fonts_in_family =
[font_manager availableMembersOfFontFamily:desired_family];
if (fonts_in_family && fonts_in_family.count) {
available_family = desired_family;
}
NSInteger app_kit_font_weight = ToAppKitFontWeight(desired_weight);
if (!available_family) {
// Match by PostScript name.
NSEnumerator* available_fonts =
font_manager.availableFonts.objectEnumerator;
NSString* available_font;
NSFont* name_matched_font = nil;
NSFontTraitMask desired_traits_for_name_match =
desired_traits | (app_kit_font_weight >= 7 ? NSBoldFontMask : 0);
while ((available_font = [available_fonts nextObject])) {
if ([desired_family caseInsensitiveCompare:available_font] ==
NSOrderedSame) {
name_matched_font = [NSFont fontWithName:available_font size:size];
// Special case Osaka-Mono. According to <rdar://problem/3999467>, we
// need to treat Osaka-Mono as fixed pitch.
if ([desired_family caseInsensitiveCompare:@"Osaka-Mono"] ==
NSOrderedSame &&
desired_traits_for_name_match == 0) {
return name_matched_font;
}
NSFontTraitMask traits = [font_manager traitsOfFont:name_matched_font];
if ((traits & desired_traits_for_name_match) ==
desired_traits_for_name_match) {
return [font_manager convertFont:name_matched_font
toHaveTrait:desired_traits_for_name_match];
}
available_family = name_matched_font.familyName;
break;
}
}
}
// Found a family, now figure out what weight and traits to use.
BOOL chose_font = false;
NSInteger chosen_weight = 0;
NSFontTraitMask chosen_traits = 0;
NSString* chosen_full_name = nil;
NSArray<NSArray*>* fonts =
[font_manager availableMembersOfFontFamily:available_family];
for (NSArray* font_info in fonts) {
// Each font is represented by an array of four elements:
// TODO(https://crbug.com/1442008): The docs say that font_info[0] is the
// PostScript name of the font, but it's treated as the full name here.
// Either the docs are wrong and we should note that here for future readers
// of the code, or the docs are right and we should fix this.
// https://developer.apple.com/documentation/appkit/nsfontmanager/1462316-availablemembersoffontfamily
NSString* font_full_name = font_info[0];
// font_info[1] is "the part of the font name used in the font panel that's
// not the font name". This is not needed.
NSInteger font_weight = [font_info[2] intValue];
NSFontTraitMask font_traits = [font_info[3] unsignedIntValue];
BOOL new_winner;
if (!chose_font) {
new_winner = AcceptableChoice(desired_traits, font_traits);
} else {
new_winner =
BetterChoice(desired_traits, app_kit_font_weight, chosen_traits,
chosen_weight, font_traits, font_weight);
}
if (new_winner) {
chose_font = YES;
chosen_weight = font_weight;
chosen_traits = font_traits;
chosen_full_name = font_full_name;
if (chosen_weight == app_kit_font_weight &&
(chosen_traits & IMPORTANT_FONT_TRAITS) ==
(desired_traits & IMPORTANT_FONT_TRAITS))
break;
}
}
if (!chose_font)
return nil;
NSFont* font = [NSFont fontWithName:chosen_full_name size:size];
if (!font)
return nil;
NSFontTraitMask actual_traits = 0;
if (desired_traits & NSFontItalicTrait)
actual_traits = [font_manager traitsOfFont:font];
NSInteger actual_weight = [font_manager weightOfFont:font];
bool synthetic_bold = app_kit_font_weight >= 7 && actual_weight < 7;
bool synthetic_italic = (desired_traits & NSFontItalicTrait) &&
!(actual_traits & NSFontItalicTrait);
// There are some malformed fonts that will be correctly returned by
// -fontWithFamily:traits:weight:size: as a match for a particular trait,
// though -[NSFontManager traitsOfFont:] incorrectly claims the font does not
// have the specified trait. This could result in applying
// synthetic bold on top of an already-bold font, as reported in
// <http://bugs.webkit.org/show_bug.cgi?id=6146>. To work around this
// problem, if we got an apparent exact match, but the requested traits
// aren't present in the matched font, we'll try to get a font from the same
// family without those traits (to apply the synthetic traits to later).
NSFontTraitMask non_synthetic_traits = desired_traits;
if (synthetic_bold)
non_synthetic_traits &= ~NSBoldFontMask;
if (synthetic_italic)
non_synthetic_traits &= ~NSItalicFontMask;
if (non_synthetic_traits != desired_traits) {
NSFont* font_without_synthetic_traits =
[font_manager fontWithFamily:available_family
traits:non_synthetic_traits
weight:chosen_weight
size:size];
if (font_without_synthetic_traits)
font = font_without_synthetic_traits;
}
return font;
}
int ToAppKitFontWeight(FontSelectionValue font_weight) {
float weight = font_weight;
if (weight <= 50 || weight >= 950)
return 5;
size_t select_weight = roundf(weight / 100) - 1;
static int app_kit_font_weights[] = {
2, // FontWeight100
3, // FontWeight200
4, // FontWeight300
5, // FontWeight400
6, // FontWeight500
8, // FontWeight600
9, // FontWeight700
10, // FontWeight800
12, // FontWeight900
};
DCHECK_GE(select_weight, 0ul);
DCHECK_LE(select_weight, std::size(app_kit_font_weights));
return app_kit_font_weights[select_weight];
}
} // namespace blink