Electron 中读取系统字体列表

最近在使用 Electron 开发的过程中遇到一个需求:用户想自定义界面以及编辑器的字体。这就需要在 Electron 中获取系统所有已安装的字体列表。

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

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

探索

由于每个系统获取字体的方法不一样,需要分别处理。下面以 macOS 为例讲述获取字体的方法。如果对过程不感兴趣,你也可以直接接到文章最后,查看【资源】部分,了解如何下载已打包好的 npm 库。

字体列表程序

在 macOS 中要获取系统字体列表,一个办法是使用 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 的版本。你可以点击这儿下载我编译好的版本

在 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) &amp;&amp; !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 上,支持 macOS、Windows 以及 Linux:node-font-list

由于平台差异,三个系统中获取字体使用的是不同的方法,macOS 中即是上面提到的方法,Linux 中使用了系统命令 fc-list,Windows 则通过 PowerShell 获取已安装的字体列表(如果失败则再用 VBScript 尝试)。

这个库也已被发布到了 npm 上,可以通过以下命令安装:

npm install font-list
分类:编程标签:NodeJSElectronJavaScript

相关文章:

评论:

xiaoshi657

想知道Electron的window版本怎么打包,才能像你的SwitchHost一样 就几M

oldj

你说的几 M 的版本应该是以前用 Python 的版本,现在用 Electron 了最少也有几十 M。

xiaoshi657

这样啊,谢谢解答,原来是我弄混了

千江水

请教一下,文章里,如何将oc代码编译为二进制文件。没有写过oc,不是很懂这里。

oldj

在 Xcode 中新建一个项目,类型选择“Command Line Tool”,在 main.m 中把 oc 代码贴进去,然后在顶部 Product 菜单中点击 Build 就可以。

可以从 GitHub 仓库中查看最新的代码。

xavier

大佬请教一下,我需要用 electron 修改 hosts,不是为了实现 SwitchHost 这样的功能,仅仅是为了将生产业务需要的特定 domain 给添加进去,在 win上 提权并修改 hosts 相对比较简单,mac 上不知道该如何解决权限问题,网上检索到 sudo-prompt,electron-sudo 等都比较老旧,测试了下也不行。发现 SwitchHost 可以不提权直接修改 hosts 不知道是怎么做到的,源码太多了,找了下实在是没找到,麻烦你答疑解惑一下。

发表评论: