Desmond Hume Desmond Hume - 1 year ago 276
iOS Question

How to disable font smoothing in Xcode 9?

I've got a great programming font Deccy that only looks good with font smoothing (anti aliasing) disabled in Xcode. With Xcode 8 the following would do the trick:

defaults write com.apple.dt.Xcode NSFontDefaultScreenFontSubstitutionEnabled -bool YES
defaults write com.apple.dt.Xcode AppleAntiAliasingThreshold 24


But this no longer works with Xcode 9.

Would it be possible to disable font smoothing in Xcode 9?

s4y s4y
Answer Source

Mischief managed?

Here's a screenshot of my Xcode 9 with Deccy at 13pt:

The text appears crisp, with no blurred edges.

I believe the above is what you want. Here's stock Xcode displaying the same file:

Though the font was designed with crisp pixel boundaries, it appears here, blurred, as if seen through the haze of one too many hours programming.

But how?

I probed deep for a noninvasive way to accomplish this, and failed. As far as I can tell, the text rendering path in Xcode 9 very deliberately turns on font smoothing.

Before going any further, please file a feature request with Apple. It only takes a few minutes, and it's your best hope for an answer that that can be discussed in front of those with sound security instincts and strained cardiovasculature:

https://bugreport.apple.com/

I wrote an Xcode plugin. You might have heard that Xcode 9 uses code signing restrictions to forbid the loading of plugins. This is true, but a few mavericks press on, and tonight we ride with them.

Step one

There is a tool, update_xcode_plugins. You can use it to strip the code signature from your copy of Xcode, and with it the bundle-loading restriction:

$ gem install update_xcode_plugins
$ update_xcode_plugins --unsign

If you change your mind, you can do this to revert to (a backup copy, I think?) of signed Xcode:

$ update_xcode_plugins --restore

Step two

There is another tool, Alcatraz. It's a plugin manager for Xcode. I chose to install it because it provides a plugin which provides a project template for plugins. I followed these instructions to install Alcatraz, which boil down to this:

$ git clone https://github.com/alcatraz/Alcatraz.git
$ cd Alcatraz
$ xcodebuild

I launched Xcode, clicked through the dialog warning me about the new plugin, and then used the newly-added Window > Package Manager to install the "Xcode Plugin" template.

Step three

I made a project with this template.

As I write this, the "Xcode Plugin" template hasn't been updated to support Xcode 9. No worries. I ran this command to grab Xcode 9's compatibility UUID:

$ defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID

I visited my new project's Info.plist and added that UUID to the DVTPlugInCompatibilityUUIDs array.

Then, I linked SourceEditor.framework into my project. That was a two-step process:

  1. Visit the target's Build Settings. Add this to Framework Search Paths:

    /Applications/Xcode.app/Contents/SharedFrameworks/
    
  2. Visit the target's Build Phases. Add a new "Link Binary With Libraries" phase. Hit the plus. Navigate to the directory above (you can just press the / key and then paste the path in) and choose SourceEditor.framework. It should appear in the list. The project should still build.

Then, I made my plugin's .mm file look like this (I deleted the .h file, it's unneeded for this PoC):

#import <AppKit/AppKit.h>
#include <dlfcn.h>

extern void CGContextSetAllowsFontAntialiasing(CGContextRef, BOOL);

static void hooked_sourceEditorSetFontSmoothingStyle(CGContextRef ctx) {
    CGContextSetAllowsFontAntialiasing(ctx, NO);
}

@interface NoAA : NSObject
@end

@implementation NoAA

+ (void)pluginDidLoad:(NSBundle *)plugin
{
    NSArray *allowedLoaders = [plugin objectForInfoDictionaryKey:@"me.delisa.XcodePluginBase.AllowedLoaders"];
    if (![allowedLoaders containsObject:[[NSBundle mainBundle] bundleIdentifier]])
        return;

    Class cls = NSClassFromString(@"SourceEditorScrollView");
    NSBundle* bundle = [NSBundle bundleForClass:cls];
    void *handle = dlopen(bundle.executablePath.fileSystemRepresentation, RTLD_NOW);
    if (!handle)
        return;
    uint8_t* set_font_smoothing_fn = dlsym(handle, "sourceEditorSetFontSmoothingStyle");
    if (!set_font_smoothing_fn)
        goto fin;
    void* set_font_smoothing_fn_page = (void*)((long)set_font_smoothing_fn & -PAGE_SIZE);
    if (mprotect(set_font_smoothing_fn_page, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC))
        goto fin;
    set_font_smoothing_fn[0] = 0xe9; // jmp
    uint32_t* jmp_arg = (uint32_t*)(set_font_smoothing_fn + 1);
    *jmp_arg = (uint32_t)((long)hooked_sourceEditorSetFontSmoothingStyle - (long)(jmp_arg + 1));
    mprotect(set_font_smoothing_fn_page, PAGE_SIZE, PROT_READ | PROT_EXEC);

fin:
    dlclose(handle);
}

@end

…I think the gotos add character. Basically, it just defines a function that takes a CGContextRef and turns off text antialiasing for it. Then, it overwrites the beginning of a function inside the SourceEditor framework which ordinarily configures antialiasing settings — don't need that anymore — to jump to our function instead. It does all of this in an incredibly unsafe way, so if something goes wrong, Xcode may politely crash.

Build and run the project, which automatically installs the plugin. Accept the addition of your plugin, and that's that.

What now?

If anything here doesn't work because I messed up, let me know. I'm not planning to roll this into an Alcatraz plugin myself, but you or anyone else should free to do so with credit (after filing a feature request with Apple).

Happy hacking!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download