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) && !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
评论:
想知道Electron的window版本怎么打包,才能像你的SwitchHost一样 就几M
你说的几 M 的版本应该是以前用 Python 的版本,现在用 Electron 了最少也有几十 M。
这样啊,谢谢解答,原来是我弄混了
请教一下,文章里,如何将oc代码编译为二进制文件。没有写过oc,不是很懂这里。
在 Xcode 中新建一个项目,类型选择“Command Line Tool”,在 main.m 中把 oc 代码贴进去,然后在顶部 Product 菜单中点击 Build 就可以。
可以从 GitHub 仓库中查看最新的代码。
大佬请教一下,我需要用 electron 修改 hosts,不是为了实现 SwitchHost 这样的功能,仅仅是为了将生产业务需要的特定 domain 给添加进去,在 win上 提权并修改 hosts 相对比较简单,mac 上不知道该如何解决权限问题,网上检索到 sudo-prompt,electron-sudo 等都比较老旧,测试了下也不行。发现 SwitchHost 可以不提权直接修改 hosts 不知道是怎么做到的,源码太多了,找了下实在是没找到,麻烦你答疑解惑一下。