深度字体安装器主要实现字体安装、卸载、预览;安装在系统目录,可以保证系统多用户可以使用新安装的字体。

架构设计

架构设计图.png

主要由以下三部分构成:

  • deepin-font-installer 为字体安装器主进程,一共有三个页面(首页、字体信息页面、字体列表页面)构成。

  • libdeepin-font-installer 为核心库,提供获取字体信息、安装、卸载、重装、预览控件接口。

  • dde-font-preview-plugin 为深度文件管理器字体预览接口。

深度字体安装器核心技术有:

  • 字体信息获取
  • 预览控件实现

前端界面实现

deepin_20180310184202.png

基于DTK开发,确保基本风格和 Deepin 全家桶应用保持一致;主界面 DMainWindow 为基础,布局使用 QStackedLayout 实现多页面切换。

拖放功能

拖放由两部分组成:拖动和释放,拖动是将被拖放对象进行移动,释放是将被拖放对象放下,其实就是鼠标左键移动和松开鼠标左键过程。

所以就需要重写 dragEnterEvent() 和 dropEvent() 两个函数,前者为拖放进入事件,后者为释放事件,然后得到所有路径后进行处理,达到拖拽字体文件或文件夹效果,还需开启拖放功能: setAcceptDrops(true);

字体安装首页:

展示图标(使用 QSvgWidget 可以保证在高分屏下渲染清晰)、提示文本(提示用户可以拖拽字体文件到此窗口)、选择文件按钮(DLinkButton),一共有三个控件,当”选择文件”按钮被按下后弹出选择文件对话框(QFileDialog)。

文件选择对话框:

调用QFileDialog,支持多个文件选择,过滤非字体文件类型,并且保存上一次打开目录(通过保存配置文件)。

字体信息页面:

当只有打开或拖拽一个文件才会显示此页面,主要展示字体名称、风格、类型、版本、版权、描述信息,通过调用 libdeepin-font-installer 获取,当获取到的某个信息为空的时候显示 “Unknown”,因为某些字体文件离里没有版权或描述信息。

字体批量安装页面:

打开两个或者两个以上显示此页面,列表控件使用 DSimpleListView,item 遇到 hover 状态的时候显示关闭按钮(从列表中移除),只剩一个文件的时候自动到跳转字体信息页面。

核心接口实现(libdeepin-font-installer)

就先说说freetype吧,因为使用到了它;根据官方得知,它是一款字体渲染引擎,使用C语言编写,旨在实现小巧,高效,高度可定制和便携,同时能够生成大多数矢量和位图字体格式的高质量输出(字形图像),知名的GNU/Linux、iOS、Android都使用到了它,应该可以说运行 freetype 的设备超过10亿,哈哈哈。

获取字体信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 字体路径
const QString filePath = "xxx/xxx/xx.ttf";

FT_Library m_library = 0;
FT_Face m_face = 0;

//初始化freetype
FT_Init_FreeType(&m_library);
FT_Error error = FT_New_Face(m_library, filePath.toUtf8().constData(), 0, &m_face);

// 字体文件错误,无法打开
if (error != 0) {
return;
}

// 得到字体名称
const QString familyName = QString::fromLatin1(m_face->family_name);
// 得到字体样式
const QString styleName = QString::fromLatin1(m_face->style_name);

FT_SfntName sname;
int count = FT_Get_Sfnt_Name_Count(m_face);

for (int i(0); i < count; ++i) {
// 跳过无效信息
if (FT_Get_Sfnt_Name(m_face, i, &sname) != 0) {
continue;
}

// only handle the unicode names for US langid.
if (!(sname.platform_id == TT_PLATFORM_MICROSOFT &&
sname.encoding_id == TT_MS_ID_UNICODE_CS &&
sname.language_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)) {
continue;
}

// 由于返回的是UTF16BE编码,需要转换UTF8,具体实现查看项目源代码
switch (sname.name_id) {
case TT_NAME_ID_COPYRIGHT:
// 得到版权字符串(需转换)
qDebug() << (char *) sname.string;
break;

case TT_NAME_ID_VERSION_STRING:
// 得到版本号字符串(需转换)
qDebug() << (char *) sname.string;
break;

case TT_NAME_ID_DESCRIPTION:
// 得到描述信息字符串(需转换)
qDebug() << (char *) sname.string;
break;
}
}

// 释放内存
FT_Done_Face(m_face);
FT_Done_FreeType(m_library);

获取所有字体路径

这个有几种方案:

  • 遍历 /usr/share/fonts 与 ~/.local/share/fonts 所有文件,判断是否为字体mime类型

  • 使用fontconfig获取,能够得到系统目录和用户目录

  • 执行 fc-list : file 得到输出后处理数据

这三种方法都用过了,最后还是选择第三种,具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
QStringList getAllFontPath()
{
QStringList pathList;
QProcess *process = new QProcess;
process->start("fc-list", QStringList() << ":" << "file");
// 等待完成
process->waitForFinished(-1);

// 读取所有输出内容
QString output = process->readAllStandardOutput();
// 分行
QStringList lines = output.split(QChar('\n'));
// 释放QProcess内存
process->deleteLater();

// 遍历分行内容
for (QString line : lines) {
// 把末尾:字体去除,添加到list
pathList << line.remove(QChar(':')).simplified();
}

return pathList;
}

安装、卸载、重装实现

安装卸载就是文件复制删除操作,然后刷新字体缓存,写成独立程序(dfont-install、dfont-uninstall),可以保证弹出 pkexec 授权对话框的提示内容不同,拷贝删除操作使用QT封装好的类 QFile::copy 与 QFile::remove ,重装通过执行 cp -f 覆盖文件。

安装目录: 统一安装在 /usr/share/fonts/deepin-font-install 下(deepin-font-installer安装时创建此目录),不用能把所有字体都拷贝到同一个目录里,fc-cache 会遍历整个目录进行刷新,当一个目录有100个字体,fc-cache 就会从第一个到最后一个进行刷新缓存,导致速度很慢,所以就需要子目录来解决此问题。

批量安装进度获取: 总文件数量 ÷ 当前正在安装的序号,使用 JSON 输出与 GUI 程序进行通信,所有字体文件安装完成后刷新字体缓存。

提权处理

由于安装在系统目录,操作都需要 root 权限,使用 pkexec 来运行二进制进行提权处理,提供对应的配置文件来进行提权对话框的内容显示。

配置文件目录:/usr/share/polkit-1/actions/

参考:freedesktop官方文档

字体预览控件

预览效果图.png

控件基于QWidget,传入一个字体文件路径,然后载入该字体(使用QFontDatabase类),重载paintEvent事件绘画预览内容,整个窗口大小为屏幕大小 ÷ 1.5。

获取预览内容

加载所有语言的示例文本内容,检查打开的字体是否支持当前系统语言的预览内容显示(使用freetype库),如果不支持就判断英文的预览内容,还是不支持的话自动生成一串 unicode 进行预览(例如图标字体,由freetype生成);字号由小到大,一到三行为固定字符串;三行以下为预览内容。

获取预览内容流程图.png

获取预览样式

通过freetype获取打开字体的样式字符串,所有样式:Italic、Regular、Bold、Light、Thin、ExtraLight、ExtraBold、Medium、DemiBold、Black,然后判断字符串(QString类的contains方法),然后设置为相应的样式。

文件管理器预览插件(dde-font-preview-plugin)

基于文件管理器文件预览插件接口 DFMFilePreview 和 libdeepin-font-installer 提供的字体预览接口,在文件管理器的文件预览中实现对字体文件的空格预览

最后

项目地址:https://github.com/linuxdeepin/deepin-font-installer