Do a better job of picking the best match from CoreText
This commit is contained in:
parent
d152af1f14
commit
b1cb16c7f8
1 changed files with 111 additions and 41 deletions
|
@ -20,8 +20,15 @@
|
||||||
#include <CoreText/CoreText.h>
|
#include <CoreText/CoreText.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void process_font(CollectionResult& ret, NSFontDescriptor *font, int bold, bool italic,
|
struct FontMatch {
|
||||||
std::vector<int> const& characters) {
|
NSURL *url;
|
||||||
|
NSCharacterSet *codepoints;
|
||||||
|
double weight;
|
||||||
|
double width;
|
||||||
|
bool italic;
|
||||||
|
};
|
||||||
|
|
||||||
|
void process_descriptor(NSFontDescriptor *font, std::vector<FontMatch>& out) {
|
||||||
// For whatever reason there is no NSFontURLAttribute
|
// For whatever reason there is no NSFontURLAttribute
|
||||||
NSURL *url = [font objectForKey:(__bridge NSString *)kCTFontURLAttribute];
|
NSURL *url = [font objectForKey:(__bridge NSString *)kCTFontURLAttribute];
|
||||||
if (!url)
|
if (!url)
|
||||||
|
@ -29,23 +36,90 @@ void process_font(CollectionResult& ret, NSFontDescriptor *font, int bold, bool
|
||||||
|
|
||||||
NSDictionary *attributes = [font objectForKey:NSFontTraitsAttribute];
|
NSDictionary *attributes = [font objectForKey:NSFontTraitsAttribute];
|
||||||
double weight = [attributes[NSFontWeightTrait] doubleValue];
|
double weight = [attributes[NSFontWeightTrait] doubleValue];
|
||||||
double slant = [attributes[NSFontSlantTrait] doubleValue];
|
double width = [attributes[NSFontWidthTrait] doubleValue];
|
||||||
|
bool italic = [attributes[NSFontSlantTrait] doubleValue] > 0.03;
|
||||||
|
|
||||||
if (italic != (slant > 0.03))
|
// CoreText sometimes reports a slant of zero (or a very small slant)
|
||||||
return;
|
// despite the macStyle field indicating that the font is italic, so
|
||||||
if (bold == 0 && weight > 0)
|
// double check by reading that field from the ttf
|
||||||
return;
|
if (!italic) @autoreleasepool {
|
||||||
if (bold == 1 && weight < 0.4)
|
NSFont *nsfont = [NSFont fontWithDescriptor:font size:10];
|
||||||
return;
|
auto data = (__bridge_transfer NSData *)CTFontCopyTable((__bridge CTFontRef)nsfont, kCTFontTableHead, 0);
|
||||||
|
if (data.length > 45) {
|
||||||
NSCharacterSet *codepoints = [font objectForKey:NSFontCharacterSetAttribute];
|
italic = static_cast<const char *>(data.bytes)[45] & 2;
|
||||||
for (int chr : characters) {
|
|
||||||
if (![codepoints longCharacterIsMember:chr]) {
|
|
||||||
ret.missing += chr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.paths.push_back(url.fileSystemRepresentation);
|
out.push_back({url, [font objectForKey:NSFontCharacterSetAttribute], weight, width, italic});
|
||||||
|
}
|
||||||
|
|
||||||
|
void select_best(CollectionResult& ret, std::vector<FontMatch>& options, double bold,
|
||||||
|
bool italic, std::vector<int> const& characters) {
|
||||||
|
if (options.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Prioritize fonts with the width closest to normal
|
||||||
|
sort(begin(options), end(options), [](FontMatch const& lft, FontMatch const& rgt) {
|
||||||
|
return std::abs(lft.width) < std::abs(rgt.width);
|
||||||
|
});
|
||||||
|
|
||||||
|
const FontMatch *match = nullptr;
|
||||||
|
|
||||||
|
auto check_weight = [&](FontMatch const& m) {
|
||||||
|
if (m.weight < bold)
|
||||||
|
return false;
|
||||||
|
if (match && m.weight >= match->weight)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto check_slant = [&](FontMatch const& m) {
|
||||||
|
return (italic && m.italic) || (!italic && !m.italic);
|
||||||
|
};
|
||||||
|
|
||||||
|
// First look for the best valid match
|
||||||
|
for (auto const& m : options) {
|
||||||
|
if (check_slant(m) && check_weight(m))
|
||||||
|
match = &m;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
// Either none are italic, or none are heavy enough
|
||||||
|
// Just pick the heaviest one (italic if applicable)
|
||||||
|
for (auto const& m : options) {
|
||||||
|
if (!check_slant(m) || (match && m.weight >= match->weight))
|
||||||
|
continue;
|
||||||
|
match = &m;
|
||||||
|
ret.fake_bold = bold > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
// We want italic but there is none, or we want regular and there is
|
||||||
|
// only italic. Don't complain about fake italic in the latter case.
|
||||||
|
ret.fake_italic = italic;
|
||||||
|
for (auto const& m : options) {
|
||||||
|
if (check_weight(m))
|
||||||
|
match = &m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
// No correct weight match even in non-italic, so just pick the heaviest
|
||||||
|
ret.fake_bold = bold > 0;
|
||||||
|
for (auto const& m : options) {
|
||||||
|
if (match && m.weight >= match->weight)
|
||||||
|
continue;
|
||||||
|
match = &m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& best = *match;
|
||||||
|
ret.paths.push_back(best.url.fileSystemRepresentation);
|
||||||
|
for (int chr : characters) {
|
||||||
|
if (![best.codepoints longCharacterIsMember:chr]) {
|
||||||
|
ret.missing += chr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,8 +128,28 @@ CollectionResult CoreTextFontFileLister::GetFontPaths(std::string const& facenam
|
||||||
std::vector<int> const& characters) {
|
std::vector<int> const& characters) {
|
||||||
CollectionResult ret;
|
CollectionResult ret;
|
||||||
|
|
||||||
|
double desired_weight;
|
||||||
|
if (bold == 0)
|
||||||
|
desired_weight = 0;
|
||||||
|
else if (bold == 1)
|
||||||
|
desired_weight = 0.4;
|
||||||
|
else if (bold < 400)
|
||||||
|
desired_weight = -0.4;
|
||||||
|
else if (bold < 500)
|
||||||
|
desired_weight = 0;
|
||||||
|
else if (bold < 600)
|
||||||
|
desired_weight = 0.23;
|
||||||
|
else if (bold < 700)
|
||||||
|
desired_weight = 0.3;
|
||||||
|
else if (bold < 800)
|
||||||
|
desired_weight = 0.4;
|
||||||
|
else
|
||||||
|
desired_weight = 0.62;
|
||||||
|
|
||||||
|
std::vector<FontMatch> fonts;
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSString *name = @(facename.c_str() + (facename[0] == '@'));
|
NSString *name = @(facename.c_str() + (facename[0] == '@'));
|
||||||
|
|
||||||
NSArray *attrs = @[
|
NSArray *attrs = @[
|
||||||
[NSFontDescriptor fontDescriptorWithFontAttributes:@{NSFontFamilyAttribute: name}],
|
[NSFontDescriptor fontDescriptorWithFontAttributes:@{NSFontFamilyAttribute: name}],
|
||||||
[NSFontDescriptor fontDescriptorWithFontAttributes:@{NSFontFaceAttribute: name}],
|
[NSFontDescriptor fontDescriptorWithFontAttributes:@{NSFontFaceAttribute: name}],
|
||||||
|
@ -64,34 +158,10 @@ CollectionResult CoreTextFontFileLister::GetFontPaths(std::string const& facenam
|
||||||
|
|
||||||
auto font_collection = [NSFontCollection fontCollectionWithDescriptors:attrs];
|
auto font_collection = [NSFontCollection fontCollectionWithDescriptors:attrs];
|
||||||
for (NSFontDescriptor *desc in font_collection.matchingDescriptors) {
|
for (NSFontDescriptor *desc in font_collection.matchingDescriptors) {
|
||||||
process_font(ret, desc, bold, italic, characters);
|
process_descriptor(desc, fonts);
|
||||||
|
|
||||||
// If we didn't get any results, check for non-bold/italic variants
|
|
||||||
// and collect them with a warning
|
|
||||||
if (ret.paths.empty() && bold) {
|
|
||||||
process_font(ret, desc, 0, italic, characters);
|
|
||||||
if (!ret.paths.empty())
|
|
||||||
ret.fake_bold = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret.paths.empty() && italic) {
|
|
||||||
process_font(ret, desc, bold, false, characters);
|
|
||||||
if (!ret.paths.empty())
|
|
||||||
ret.fake_italic = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret.paths.empty() && bold && italic) {
|
|
||||||
process_font(ret, desc, 0, false, characters);
|
|
||||||
if (!ret.paths.empty()) {
|
|
||||||
ret.fake_bold = true;
|
|
||||||
ret.fake_italic = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
select_best(ret, fonts, desired_weight, italic, characters);
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(begin(ret.paths), end(ret.paths));
|
|
||||||
ret.paths.erase(unique(begin(ret.paths), end(ret.paths)), end(ret.paths));
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue