/
PTYFontInfo.m
278 lines (248 loc) · 10.3 KB
/
PTYFontInfo.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//
// PTYFontInfo.m
// iTerm
//
// Created by George Nachman on 12/17/12.
//
//
#import "PTYFontInfo.h"
#import "DebugLogging.h"
#import "FontSizeEstimator.h"
#import "iTermAdvancedSettingsModel.h"
#import "NSObject+iTerm.h"
@implementation NSFont(PTYFontInfo)
- (BOOL)it_fontIsOnLigatureBlacklist {
static NSSet<NSString *> *blacklist;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
blacklist = [[NSSet setWithArray:@[ @"AndaleMono",
@"Courier",
@"LetterGothicStd",
@"Menlo",
@"Monaco",
@"OCRAStd",
@"OratorStd",
@"Osaka",
@"PTMono",
@"SFMono" ]] retain];
});
NSString *myName = self.fontName;
for (NSString *blacklistedNamePrefix in blacklist) {
if ([myName hasPrefix:blacklistedNamePrefix]) {
return YES;
}
}
return NO;
}
- (NSInteger)it_ligatureLevel {
if ([self.fontName hasPrefix:@"Iosevka"]) {
return 2;
} else if ([self it_fontIsOnLigatureBlacklist]) {
return 0;
} else {
return 1;
}
}
- (BOOL)it_defaultLigatures {
// Some fonts have great ligatures but unlike FiraCode you need to ask for them. FiraCode gives
// you ligatures whether you like it or not.
static NSSet *fontsWithDefaultLigatures;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fontsWithDefaultLigatures = [[NSSet setWithArray:@[ ]] retain];
});
BOOL result = [fontsWithDefaultLigatures containsObject:self.fontName];
DLog(@"Default ligatures for '%@' is %@", self.fontName, @(result));
return result;
}
@end
@implementation PTYFontInfo {
NSFont *font_;
PTYFontInfo *boldVersion_;
PTYFontInfo *italicVersion_;
}
@synthesize font = font_;
@synthesize boldVersion = boldVersion_;
@synthesize italicVersion = italicVersion_;
+ (PTYFontInfo *)fontForAsciiCharacter:(BOOL)isAscii
asciiFont:(PTYFontInfo *)asciiFont
nonAsciiFont:(PTYFontInfo *)nonAsciiFont
useBoldFont:(BOOL)useBoldFont
useItalicFont:(BOOL)useItalicFont
usesNonAsciiFont:(BOOL)useNonAsciiFont
renderBold:(BOOL *)renderBold
renderItalic:(BOOL *)renderItalic {
BOOL isBold = *renderBold && useBoldFont;
BOOL isItalic = *renderItalic && useItalicFont;
*renderBold = NO;
*renderItalic = NO;
PTYFontInfo *theFont;
BOOL usePrimary = !useNonAsciiFont || isAscii;
PTYFontInfo *rootFontInfo = usePrimary ? asciiFont : nonAsciiFont;
theFont = rootFontInfo;
if (isBold && isItalic) {
theFont = rootFontInfo.boldItalicVersion;
if (!theFont && rootFontInfo.boldVersion) {
theFont = rootFontInfo.boldVersion;
*renderItalic = YES;
} else if (!theFont && rootFontInfo.italicVersion) {
theFont = rootFontInfo.italicVersion;
*renderBold = YES;
} else if (!theFont) {
theFont = rootFontInfo;
*renderBold = YES;
*renderItalic = YES;
}
} else if (isBold) {
theFont = rootFontInfo.boldVersion;
if (!theFont) {
theFont = rootFontInfo;
*renderBold = YES;
}
} else if (isItalic) {
theFont = rootFontInfo.italicVersion;
if (!theFont) {
theFont = rootFontInfo;
*renderItalic = YES;
}
}
return theFont;
}
+ (PTYFontInfo *)fontInfoWithFont:(NSFont *)font {
PTYFontInfo *fontInfo = [[[PTYFontInfo alloc] init] autorelease];
fontInfo.font = font;
return fontInfo;
}
- (void)dealloc {
[font_ release];
[boldVersion_ release];
[italicVersion_ release];
[_boldItalicVersion release];
[super dealloc];
}
- (void)setFont:(NSFont *)font {
assert(font != nil);
[font_ autorelease];
font_ = [font retain];
_ligatureLevel = font.it_ligatureLevel;
_hasDefaultLigatures = font.it_defaultLigatures;
_baselineOffset = [self computedBaselineOffset];
_underlineOffset = [self computedUnderlineOffset];
}
- (CGFloat)descender {
// See issue 4957 for the Monaco hack.
CGFloat extraDescender = 0;
if (![font_.fontName isEqualToString:@"Monaco"]) {
extraDescender = 0.5;
}
CGFloat descender = self.font.descender + extraDescender;
return descender;
}
- (CGFloat)computedBaselineOffset {
if ([iTermAdvancedSettingsModel useExperimentalFontMetrics]) {
NSTextContainer *textContainer = [FontSizeEstimator textContainer];
NSLayoutManager *layoutManager = [FontSizeEstimator layoutManagerForFont:font_ textContainer:textContainer];
CGFloat lineHeight = [layoutManager usedRectForTextContainer:textContainer].size.height;
CGFloat baselineOffsetFromTop = [layoutManager defaultBaselineOffsetForFont:font_];
return -round(lineHeight - baselineOffsetFromTop);
} else {
return -(floorf(font_.leading) - floorf(self.descender));
}
}
// From https://github.com/DrawKit/DrawKit/blob/master/framework/Code/NSBezierPath%2BText.m#L648
- (CGFloat)computedUnderlineOffset {
NSLayoutManager *layoutManager = [[[NSLayoutManager alloc] init] autorelease];
NSTextContainer *textContainer = [[[NSTextContainer alloc] init] autorelease];
[layoutManager addTextContainer:textContainer];
NSDictionary *attributes = @{ NSFontAttributeName: font_,
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) };
NSAttributedString *attributedString = [[[NSAttributedString alloc] initWithString:@"M" attributes:attributes] autorelease];
NSTextStorage *textStorage = [[[NSTextStorage alloc] initWithAttributedString:attributedString] autorelease];
[textStorage addLayoutManager:layoutManager];
NSUInteger glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:0];
return [[layoutManager typesetter] baselineOffsetInLayoutManager:layoutManager
glyphIndex:glyphIndex] / -2.0;
}
// Issue 4294 reveals that merely upconverting the weight of a font once is not sufficient because
// it might go from Regular to Medium. You need to keep trying until you find a font that is relatively
// bold. This is a nice way to do it because the user could, e.g., pick a "thin" font and get the
// "medium" version for "bold" text. We convertWeight: until the weight is at least 4 higher than
// the original font. See the table in the docs for convertWeight:ofFont: for what this means.
- (NSFont *)boldVersionOfFont:(NSFont *)font {
NSFontManager *fontManager = [NSFontManager sharedFontManager];
NSInteger weight = [fontManager weightOfFont:font];
NSInteger minimumAcceptableWeight = weight + [iTermAdvancedSettingsModel minimumWeightDifferenceForBoldFont];
DLog(@"Looking for a bold version of %@, whose weight is %@", font, @(weight));
NSFont *lastFont = font;
// Sometimes the heavier version of a font is oblique (issue 4442). So
// check the traits to make sure nothing significant changes.
const NSFontTraitMask kImmutableTraits = (NSItalicFontMask |
NSNarrowFontMask |
NSExpandedFontMask |
NSCondensedFontMask |
NSSmallCapsFontMask |
NSPosterFontMask |
NSCompressedFontMask |
NSFixedPitchFontMask |
NSUnitalicFontMask);
NSFontTraitMask requiredTraits = ([fontManager traitsOfFont:font] & kImmutableTraits);
DLog(@"Required traits: %x", (int)requiredTraits);
NSMutableArray<NSFont *> *fonts = [NSMutableArray array];
[fonts addObject:font];
while (lastFont) {
NSFont *heavierFont = [fontManager convertWeight:YES ofFont:lastFont];
if (heavierFont == lastFont) {
// This is how fontManager is documented to fail.
return nil;
}
if ([fonts containsObject:heavierFont]) {
DLog(@" * cycle detected among fonts *\n%@", [fonts arrayByAddingObject:heavierFont]);
return nil;
}
[fonts addObject:heavierFont];
NSInteger weight = [fontManager weightOfFont:heavierFont];
DLog(@" next bolder font is %@ with a weight of %@", heavierFont, @(weight));
NSFontTraitMask maskedTraits = ([fontManager traitsOfFont:heavierFont] & kImmutableTraits);
DLog(@" masked traits=%x", (int)maskedTraits);
if (maskedTraits == requiredTraits && weight >= minimumAcceptableWeight) {
DLog(@" accepted!");
return heavierFont;
}
lastFont = heavierFont;
}
DLog(@"Failed to find a bold version that's bold enough");
return nil;
}
- (PTYFontInfo *)computedBoldVersion {
NSFont *boldFont = [self boldVersionOfFont:font_];
DLog(@"Bold version of %@ is %@", font_, boldFont);
if (boldFont && boldFont != font_) {
return [PTYFontInfo fontInfoWithFont:boldFont];
} else {
DLog(@"Failed to find a bold version of %@", font_);
return nil;
}
}
- (PTYFontInfo *)computedItalicVersion {
NSFontManager* fontManager = [NSFontManager sharedFontManager];
NSFont* italicFont = [fontManager convertFont:font_ toHaveTrait:NSItalicFontMask];
DLog(@"Italic version of %@ is %@", font_, italicFont);
if (italicFont && italicFont != font_) {
return [PTYFontInfo fontInfoWithFont:italicFont];
} else {
DLog(@"Failed to find an italic version of %@", font_);
return nil;
}
}
- (PTYFontInfo *)computedBoldItalicVersion {
PTYFontInfo *temp = [self computedBoldVersion];
return [temp computedItalicVersion];
}
- (BOOL)isEqual:(id)object {
PTYFontInfo *other = [PTYFontInfo castFrom:object];
if (!other) {
return NO;
}
return [self.font isEqual:other.font];
}
@end