Electron中读取系统字体列表(macOS)

最近在使用 Electron 开发的过程中遇到一个需求:用户想自定义界面以及编辑器的字体。

一开始,我以为这个问题很简单,但遍历了 Electron 官网上的文档之后却没有找到相关 API,搜索了一圈也没有结果,同时,也没有找到 Node.js 对获取系统字体的支持,才发现这个问题可能还没有官方支持的解决方案。

下面记录一下我的处理方法,希望起到抛砖引玉的效果。

Step 1. 字体列表程序

要获取系统字体列表,一个办法是使用 Objective-C 等语言调用系统 API。比如:

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%@", [[[NSFontManager sharedFontManager] availableFontFamilies] description]);
    }
    return 0;
}

编译、执行,输出类似这样:

$ ./fontlist
2017-02-05 13:59:11.853 fontlist[25805:2039116] (
    "Al Bayan",
    "Al Nile",
    "Al Tarikh",
    "American Typewriter",
    "Andale Mono",
    "Anonymous Pro",
    Arial,
    "Arial Black",
    "Arial Hebrew",
    "Arial Hebrew Scholar",
    "Arial Narrow",
    "Arial Rounded MT Bold",
    "Arial Unicode MS",

...

可以看到,系统上安装的字体都被打印出来了。

或者 Swift 版本:

import Cocoa

var fonts = NSFontManager.shared().availableFontFamilies
fonts = fonts.sorted()
for fn in fonts {
print(fn)
}

不过 Swift 版本编译生成的文件比 Objective-C 生成的文件要大很多,这儿我们采用 Objective-C 的版本。你可以点击这儿下载我编译好的版本

Step 2. 在 Node.js 中执行 fontlist

将上一步生成的可执行文件命名为 fontlist,接下来,就可以使用 Node.js 中的 child_process.execFile 方法来执行它,并获取结果了。

一个实现如下:

'use strict';

const path = require('path');
const execFile = require('child_process').execFile;
const platform = process.platform;
const bin = path.join(__dirname, 'bin', platform, 'fontlist');

const font_exceptions = ['iconfont'];

function tryToGetFonts(s) {
    let fonts = [];
    let m = s.match(/\([\s\S]+?\)/);
    if (m) {
        let a = m[0].replace(/\(|\)/g, '').split('\n');
        fonts = fonts.concat(a.map(i => {
            return i.replace(/^\s+|\s+$/g, '').replace(/\,$/, '');
        }));
    }

    return fonts;
}

exports.getFonts = (callback) => {
    if (platform !== 'darwin') {
        setTimeout(() => {
            callback(`Error: font-list not support on ${platform}.`);
        }, 0);
        return;
    }

    execFile(bin, (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
        }
        let fonts = [];
        if (stdout) {
            fonts = fonts.concat(tryToGetFonts(stdout));
        }
        if (stderr) {
            fonts = fonts.concat(tryToGetFonts(stderr));
        }

        let dict = {};
        fonts.map(i => {
            if (i) {
                dict[i] = 1;
            }
        });
        fonts = [];
        for (let k in dict) {
            if (dict.hasOwnProperty(k) && !font_exceptions.includes(k)) {
                fonts.push(k);
            }
        }
        fonts.sort();
        callback(null, fonts);
    });
};

使用方法类似这样:

require('font-list').getFonts((err, fonts) => {
    if (err) {
        console.log(err);
    } else {
        console.log(fonts);
    }
});

如果一切正常的话,回调函数中的 fonts 会是一个数组,值就是系统中安装的所有字体的列表。

全部源码以及完整示例在 GitHub 上:node-font-list

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s