Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions runtime/doc/gui.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,22 @@ Apple documentation for `NSFontWeight` for possible values): >
:set guifont=-monospace-Light:h11
:set guifont=-monospace-Medium:h11
<
MacVim also supports the ":f" option for OpenType font features, using the
same syntax as the Win32 GUI (see above): >
:set guifont=Maple\ Mono:h14:fss19=1:fcalt=0
Each ":f" entry specifies a single feature as tag=value, where tag is a
4-character OpenType feature tag and value is the parameter (0 to disable, 1
or higher to enable/select a variant). A bare tag is equivalent to "tag=1".
This requires the font to support the given features (e.g. character variants
"cv01"-"cv99" or stylistic sets "ss01"-"ss20"); unsupported features are
ignored. Default features (calt, liga, etc.) are preserved unless explicitly
overridden. Features in 'guifontwide' are ignored; the 'guifont' features
apply to all rendered text.
Note: This requires macOS 10.10 or later, and currently only has an effect if
'Use Core Text renderer' is enabled in the GUI preferences pane. Standard
ligatures are controlled by 'macligatures', which takes precedence over a
"liga" entry here.

Mono-spaced fonts *E236*

Note that the fonts must be mono-spaced (all characters have the same width).
Expand Down
1 change: 1 addition & 0 deletions src/MacVim/MMBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
- (void)setFullScreenBackgroundColor:(int)color;

- (void)setAntialias:(BOOL)antialias;
- (void)setFontFeatures:(NSString *)features;
- (void)setLigatures:(BOOL)ligatures;
- (void)setThinStrokes:(BOOL)thinStrokes;
- (void)setBlurRadius:(int)radius;
Expand Down
13 changes: 13 additions & 0 deletions src/MacVim/MMBackend.m
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,19 @@ - (void)setAntialias:(BOOL)antialias
[self queueMessage:msgid data:nil];
}

- (void)setFontFeatures:(NSString *)features
{
// NOTE: Unlike setFont:wide: an empty string must still be sent since it
// means "reset to the font's default features".
int len = (int)[features lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSMutableData *data = [NSMutableData data];
[data appendBytes:&len length:sizeof(int)];
if (len > 0)
[data appendBytes:[features UTF8String] length:len];

[self queueMessage:SetFontFeaturesMsgID data:data];
}

- (void)setLigatures:(BOOL)ligatures
{
int msgid = ligatures ? EnableLigaturesMsgID : DisableLigaturesMsgID;
Expand Down
3 changes: 3 additions & 0 deletions src/MacVim/MMCoreTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ NS_ASSUME_NONNULL_BEGIN
BOOL antialias;
BOOL ligatures;
BOOL thinStrokes;
NSString *fontFeatures; ///< Raw OpenType font features string
NSArray *fontFeatureSettings; ///< Parsed kCTFontFeatureSettingsAttribute array, nil if none

BOOL forceRefreshFont; // when true, don't early out of setFont calls.

Expand Down Expand Up @@ -138,6 +140,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setPreEditRow:(int)row column:(int)col;
- (void)setMouseShape:(int)shape;
- (void)setAntialias:(BOOL)state;
- (void)setFontFeatures:(NSString *)features;
- (void)setLigatures:(BOOL)state;
- (void)setThinStrokes:(BOOL)state;
- (void)setImControl:(BOOL)enable;
Expand Down
57 changes: 57 additions & 0 deletions src/MacVim/MMCoreTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,23 @@ - (void)invertBlockFromRow:(int)row column:(int)col numRows:(int)nrows
return [@"m" sizeWithAttributes:a].width;
}

/// Returns a font derived from the supplied one with the given OpenType
/// feature settings (a kCTFontFeatureSettingsAttribute-style array) applied.
static NSFont *
fontWithFeatureSettings(NSFont *base, NSArray *settings)
{
if (settings == nil || base == nil)
return base;
CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes(
(CFDictionaryRef)@{ (NSString *)kCTFontFeatureSettingsAttribute: settings });
CTFontRef derived = CTFontCreateCopyWithAttributes(
(CTFontRef)base, 0.0, NULL, desc);
CFRelease(desc);
if (derived == NULL)
return base;
return [(NSFont *)derived autorelease];
}

typedef struct {
unsigned color;
int shape;
Expand Down Expand Up @@ -284,6 +301,8 @@ - (void)dealloc
[characterStrings release]; characterStrings = nil;
[fontVariants release]; fontVariants = nil;
[characterLines release]; characterLines = nil;
[fontFeatures release]; fontFeatures = nil;
[fontFeatureSettings release]; fontFeatureSettings = nil;

[helper setTextView:nil];
[helper release]; helper = nil;
Expand Down Expand Up @@ -582,6 +601,43 @@ - (void)setAntialias:(BOOL)state
antialias = state;
}

- (void)setFontFeatures:(NSString *)features
{
if (fontFeatures == features || [fontFeatures isEqualToString:features])
return;
[fontFeatures release];
fontFeatures = [features copy];
[fontFeatureSettings release];
fontFeatureSettings = nil;

#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
if (features.length > 0) {
if (@available(macos 10.10, *)) {
// Parse the feature list ("tag" or "tag=N" entries, comma
// separated) into the array of dictionaries CoreText expects for
// kCTFontFeatureSettingsAttribute.
NSMutableArray *settings = [NSMutableArray array];
for (NSString *entry in [features componentsSeparatedByString:@","]) {
NSArray *kv = [entry componentsSeparatedByString:@"="];
NSString *tag = kv[0];
if (tag.length != 4)
continue; // already validated by Vim; be defensive
int value = kv.count > 1 ? [kv[1] intValue] : 1;
[settings addObject:@{
(NSString *)kCTFontOpenTypeFeatureTag: tag,
(NSString *)kCTFontOpenTypeFeatureValue: @(value),
}];
}
if (settings.count > 0)
fontFeatureSettings = [settings retain];
}
}
#endif

[fontVariants removeAllObjects];
[characterLines removeAllObjects];
}

- (void)setLigatures:(BOOL)state
{
ligatures = state;
Expand Down Expand Up @@ -2116,6 +2172,7 @@ - (NSFont *)fontVariantForTextFlags:(int)textFlags {
fontRef = [NSFontManager.sharedFontManager convertFont:fontRef toHaveTrait:NSFontItalicTrait];
if (textFlags & DRAW_BOLD)
fontRef = [NSFontManager.sharedFontManager convertFont:fontRef toHaveTrait:NSFontBoldTrait];
fontRef = fontWithFeatureSettings(fontRef, fontFeatureSettings);

fontVariants[cacheFlags] = fontRef;
}
Expand Down
1 change: 1 addition & 0 deletions src/MacVim/MMTextView.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
- (void)performBatchDrawWithData:(NSData *)data;
- (void)setMouseShape:(int)shape;
- (void)setAntialias:(BOOL)antialias;
- (void)setFontFeatures:(NSString *)features;
- (void)setLigatures:(BOOL)ligatures;
- (void)setThinStrokes:(BOOL)thinStrokes;
- (void)setImControl:(BOOL)enable;
Expand Down
5 changes: 5 additions & 0 deletions src/MacVim/MMTextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,11 @@ - (void)setAntialias:(BOOL)state
antialias = state;
}

- (void)setFontFeatures:(NSString *)features
{
// Not supported by this renderer; only MMCoreTextView handles this.
}

- (void)setLigatures:(BOOL)state
{
ligatures = state;
Expand Down
11 changes: 11 additions & 0 deletions src/MacVim/MMVimController.m
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,17 @@ - (void)handleMessage:(int)msgid data:(NSData *)data
[[[windowController vimView] textView] setAntialias:NO];
}
break;
case SetFontFeaturesMsgID:
{
const void *bytes = [data bytes];
int len = *((int*)bytes); bytes += sizeof(int);
NSString *features = len > 0
? [[[NSString alloc] initWithBytes:(void*)bytes length:len
encoding:NSUTF8StringEncoding] autorelease]
: @"";
[[[windowController vimView] textView] setFontFeatures:features];
}
break;
case EnableLigaturesMsgID:
{
[[[windowController vimView] textView] setLigatures:YES];
Expand Down
1 change: 1 addition & 0 deletions src/MacVim/MacVim.h
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ extern const char * const MMVimMsgIDStrings[];
MSG(EnableThinStrokesMsgID) \
MSG(DisableThinStrokesMsgID) \
MSG(ShowDefinitionMsgID) \
MSG(SetFontFeaturesMsgID) \
MSG(LoopBackMsgID) /* Simple message that Vim will reflect back to MacVim */ \
MSG(LastMsgID) \

Expand Down
83 changes: 70 additions & 13 deletions src/MacVim/gui_macvim.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
static BOOL MMShareFindPboard = YES;

static GuiFont gui_macvim_font_with_name(char_u *name);
static NSString *fontFeaturesFromFontString(NSString *font);
static int specialKeyToNSKey(int key);
static int vimModMaskToEventModifierFlags(int mods);

Expand Down Expand Up @@ -1150,6 +1151,12 @@
: font)
wide:YES];

// Apply any OpenType font feature settings given as ":f" modifiers in
// the font description. An empty list resets the renderer to the font's
// default features.
[[MMBackend sharedInstance] setFontFeatures:
fontFeaturesFromFontString((NSString *)font)];

return OK;
}

Expand All @@ -1164,6 +1171,51 @@
}


/*
* Check a single OpenType font feature setting from a ":f" 'guifont'
* modifier, e.g. "ss19=1": a four-character printable ASCII tag, optionally
* followed by "=" and a non-negative number (a bare tag is equivalent to
* "tag=1").
*/
static BOOL
isValidFontFeature(NSString *feature)
{
if ([feature length] < 4)
return NO;
for (NSUInteger i = 0; i < 4; ++i) {
unichar ch = [feature characterAtIndex:i];
if (ch < 0x20 || ch > 0x7e || ch == ',' || ch == '=')
return NO;
}
if ([feature length] == 4)
return YES;
if ([feature characterAtIndex:4] != '=' || [feature length] == 5)
return NO;
for (NSUInteger i = 5; i < [feature length]; ++i) {
unichar ch = [feature characterAtIndex:i];
if (ch < '0' || ch > '9')
return NO;
}
return YES;
}

/*
* Extract the ":f" feature modifiers from a font description string
* ("Name:h12:fss19=1:fcalt=0") as a comma-separated list ("ss19=1,calt=0").
*/
static NSString *
fontFeaturesFromFontString(NSString *font)
{
NSArray *components = [font componentsSeparatedByString:@":"];
NSMutableArray *features = [NSMutableArray array];
for (NSUInteger i = 1; i < [components count]; ++i) {
NSString *c = [components objectAtIndex:i];
if ([c length] >= 2 && [c characterAtIndex:0] == 'f')
[features addObject:[c substringFromIndex:1]];
}
return [features componentsJoinedByString:@","];
}

/*
* Return GuiFont in allocated memory. The caller must free it using
* gui_mch_free_font().
Expand All @@ -1179,22 +1231,25 @@
int size = MMDefaultFontSize;
BOOL parseFailed = NO;

// The font description is the font name followed by optional
// colon-separated modifiers: "h<size>" and "f<feature>" (an OpenType font
// feature setting, using the same syntax as the Win32 GUI, see
// |gui-font|). Example: "Menlo:h13:fss19=1:fcalt=0".
NSArray *components = [fontName componentsSeparatedByString:@":"];
if ([components count] == 2) {
NSString *sizeString = [components lastObject];
if ([sizeString length] > 0
&& [sizeString characterAtIndex:0] == 'h') {
sizeString = [sizeString substringFromIndex:1];
if ([sizeString length] > 0) {
size = (int)round([sizeString floatValue]);
fontName = [components objectAtIndex:0];
}
NSString *featureSuffix = @"";
for (NSUInteger i = 1; i < [components count] && !parseFailed; ++i) {
NSString *c = [components objectAtIndex:i];
if ([c length] >= 2 && [c characterAtIndex:0] == 'h') {
size = (int)round([[c substringFromIndex:1] floatValue]);
} else if ([c length] >= 2 && [c characterAtIndex:0] == 'f'
&& isValidFontFeature([c substringFromIndex:1])) {
featureSuffix = [featureSuffix stringByAppendingFormat:@":%@", c];
} else {
parseFailed = YES;
}
} else if ([components count] > 2) {
parseFailed = YES;
}
if ([components count] > 1)
fontName = [components objectAtIndex:0];

const BOOL isSystemFont = [fontName hasPrefix:MMSystemFontAlias];
if (isSystemFont) {
Expand All @@ -1221,15 +1276,17 @@
if ([fontName isEqualToString:MMDefaultFontName]
|| isSystemFont
|| [NSFont fontWithName:fontName size:size])
return [[NSString alloc] initWithFormat:@"%@:h%d", fontName, size];
return [[NSString alloc] initWithFormat:@"%@:h%d%@",
fontName, size, featureSuffix];

// If font loading failed, try to replace underscores with spaces for
// user convenience. This really only works if the name is a family
// name because PostScript names should not have spaces.
if ([fontName rangeOfString:@"_"].location != NSNotFound) {
fontName = [fontName stringByReplacingOccurrencesOfString:@"_" withString:@" "];
if ([NSFont fontWithName:fontName size:size]) {
return [[NSString alloc] initWithFormat:@"%@:h%d", fontName, size];
return [[NSString alloc] initWithFormat:@"%@:h%d%@",
fontName, size, featureSuffix];
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/testdir/test_macvim.vim
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,30 @@ func Test_macvim_invalid_options()

call assert_fails("let &fuoptions='abcdef'", 'E474:')
endfunc

" Test OpenType font feature settings (":f" modifiers) in 'guifont'
func Test_macvim_guifont_features()
" 'guifont' is only parsed and validated when the GUI is running.
if !has('gui_running')
throw 'Skipped: GUI not running'
endif

let save_guifont = &guifont

set guifont=Menlo:h13:fss01=1:fcalt=0
call assert_match(':fss01=1:fcalt=0', getfontname())
set guifont=Menlo:h13:fzero
call assert_match(':fzero', getfontname())

" Invalid feature settings must be rejected as invalid fonts.
call assert_fails("set guifont=Menlo:h13:fabc", 'E596:')
call assert_fails("set guifont=Menlo:h13:fabcde", 'E596:')
call assert_fails("set guifont=Menlo:h13:fss01=x", 'E596:')
call assert_fails("set guifont=Menlo:h13:fss01=", 'E596:')

" A font without features resets to the font's defaults.
set guifont=Menlo:h13
call assert_equal('Menlo:h13', getfontname())

let &guifont = save_guifont
endfunc
Loading