svg 转 ttf 或其他字体
王二麻麻 Lv2

本文主要介绍如何把 SVG 格式转成 TTF 文件,当然也可以生成其他格式的字体,如 WOFF、WOFF2 等。

SVG 中记录了各种路径信息,对于少量的 SVG 可以直接引用 SVG 文件,但是当有成千上万个 SVG 文件时,就可以直接转成 TTF 等格式的字体文件,统一了风格,压缩了大小。

SVG 文件转字体格式文件

SVG 字体的基本格式

使用工具把KaiTi_GB2312字体转为svg格式,格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata> Created by xxx </metadata>
<defs>
<font id="stroke-font" horiz-adv-x="1024">
<font-face font-family="KaiTi_GB2312" units-per-em="256" underline-position="-22" underline-thickness="12"/>
<missing-glyph horiz-adv-x="256"/>
<glyph glyph-name=".notdef"></glyph>
<glyph unicode="!" horiz-adv-x="128" d="M66,58l-7,0C54,127 52,165 52,170C52,175 54,179 58,180C61,181 65,181 69,180C72,179 74,175 74,168C74,161 71,124 66,58M78,21C78,14 75,9 70,6C65,3 60,4 55,7C50,10 47,14 48,21C49,28 51,32 56,33C61,34 65,34 70,33C75,32 77,28 78,21z"/>
<glyph unicode="&quot;" horiz-adv-x="128" d="M48,94l-5,0C39,140 37,165 37,170C37,175 39,178 42,179C45,180 48,180 51,179C54,178 55,175 55,170C55,165 53,140 48,94M83,94l-5,0C74,140 72,165 72,170C72,175 74,178 77,179C80,180 83,180 86,179C89,178 90,175 90,170C90,165 88,140 83,94z"/>
...
</font>
</defs>
</svg>
  1. <svg>:SVG 字体的根元素,用于包裹整个字体定义。
  2. <metadata>: 定义了字体的描述信息和元数据,如作者、版本、版权信息、制作日期、许可证等其他说明信息。
  3. <defs>: 定义可可重复使用的的元素和属性。
  4. <font>:定义 SVG 字体的容器。
  5. <font-face>:定义字体的各种属性,如字体名称、字体文件路径等。
  6. <glyph>:定义字体中的每个字符或图标,包括其路径和一些属性。
  7. <missing-glyph>:定义当文本中使用的字符不在字体中时的替代显示内容。
  8. <path>: 定义了字符的路径信息。

这些标签共同构成了 SVG 字体的定义和使用规范。

SVG 字体文件整合

现有一批SVG文件,每个文件中定义里一个或多个描述路径的信息。需要把这些SVG文件批量合成一个SVG字体文件。

SVG定义了多个<path>路径,甚至有些SVG文件还会包含多个<g>标签,==在字体文件中,不允许出现<g>标签,且最多只允许出现一个<path>标签,==。

单个SVG文件举例:

image

SVG格式如下:

1
2
3
4
5
6
7
8
9
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="600px" height="850px">
<path id="_x31__1_" fill="#000000" d="M402.832,333.167c-0.672,2-2.844,3-6.5,3c-3.672,0-18.344,0.5-44,1.5 c-25.672,1-50.5,3.172-74.5,6.5c-24,3.344-38.672,5.5-44,6.5c-5.344,1-9.344,1.344-12,1c-2.672-0.328-5.844-2.156-9.5-5.5 c-3.672-3.328-4.172-5.328-1.5-6c2.656-0.656,7.5-1,14.5-1s20.828-1.328,41.5-4c20.656-2.656,42.5-5.828,65.5-9.5 c23-3.656,38.5-5.5,46.5-5.5s14.156,1.672,18.5,5C401.66,328.511,403.488,331.167,402.832,333.167z"/><path id="_x32__1_" fill="#000000" d="M310.332,384.667c-0.893,13.987-1.387,26.778-1.482,38.364 c-0.006,0.662-1.174,5.134-5.127,5.831c-4.25,0.75-5.891-3.737-5.892-4.167c-0.037-43.306-1.036-72.825-2.999-88.528 c10.656,6,16.656,10.172,18,12.5c1.328,2.344,1.5,5,0.5,8S311.332,369.011,310.332,384.667z"/><path id="_x33__1_" fill="#000000" d="M352.332,380.667c-10.589,2.126-25.152,3.275-43.689,3.47 c-0.94,0.01-7.252,1.308-7.128-4.317c0.097-4.369,5.748-4.719,6.63-4.753c10.626-0.403,20.525-2.03,29.688-4.9 c10.656-3.328,16.828-5,18.5-5c1.656,0,4.328,1.344,8,4c3.656,2.672,5.5,4.672,5.5,6 C369.832,376.511,363.988,378.339,352.332,380.667z"/>
<g>
<path id="_x34__1_" fill="#FF0000" d="M236.332,445.167c-3-4-4.5-6.828-4.5-8.5c0-1.656,1.828-4,5.5-7 c3.656-3,6.156-9.328,7.5-19c1.328-9.656,2-16.328,2-20c0-3.656-1-6.156-3-7.5c-2-1.328-2.844-3-2.5-5c0.328-2,2.328-2.828,6-2.5 c3.656,0.344,7.156,1.344,10.5,3c3.328,1.672,5,4.344,5,8c0,3.672-1.344,11.344-4,23c-2.672,11.672-3.5,17.844-2.5,18.5 c0.451,0.303-3.538,7.768-7.692,12.028C243.574,445.39,237.98,447.364,236.332,445.167z"/>
<path id="_x35__1_" fill="#FF0000" d="M252.332,445.167c-4.344,4-7.344,6-9,6c-1.672,0-4-2-7-6 c-1.633-2.178,1.668-8.688,7.433-12.722c4.822-3.375,12.112-4.577,12.567-4.278c1,0.672,12.156-0.656,33.5-4 c21.328-3.328,37-5.5,47-6.5s15.828-2.328,17.5-4c1.656-1.656,3.328-2.5,5-2.5c1.656,0,6,2.172,13,6.5 c2.382,1.479,4.359,2.819,5.931,4.023c0.692,0.53,3.493,2.779,2.002,4.13c-1.32,1.197-6.563,3.125-9.813,3.688 c-7.05,1.221-15.478,1.114-16.62,0.159c-2-1.656-6.5-2.5-13.5-2.5s-22.344,2-46,6C270.66,437.167,256.66,441.167,252.332,445.167 z"/>
<path id="_x36__1_" fill="#FF0000" d="M372.248,417.741c7,4.344,10.5,7.5,10.5,9.5s-0.844,3.5-2.5,4.5c-1.672,1-3.172,5-4.5,12 c-1.344,7-3.5,16.5-6.5,28.5s-7.344,23.344-13,34c-5.672,10.656-11,17.5-16,20.5s-9,4.5-12,4.5s-4.344-2.5-4-7.5 c0.175-2.658,1.157-8.937,3.558-14.255c2.113-4.684,5.658-8.616,6.081-8.785c1.552-0.618,3.172-1.771,4.861-3.46 c2.656-2.656,6.328-11.5,11-26.5c4.656-15,7-25.156,7-30.5c0-5.328-1-8.828-3-10.5 C351.748,428.085,365.248,413.413,372.248,417.741z"/>
<path id="_x37__1_" fill="#FF0000" d="M327.916,530.825c-3,0-4.344-2.5-4-7.5c0.328-5-4-11.328-13-19c-9-7.656-12.844-12-11.5-13 c1.328-1,6.328,0.344,15,4c8.656,3.672,14.156,5.5,16.5,5.5c0.856,0,1.736-0.18,2.639-0.54c0.746-0.297,4.558,6.759,3.419,14.16 C335.745,522.434,329.473,530.825,327.916,530.825z"/>
</g>
</svg>

需要做的工作有:

  • 修改<svg><glyph>标签,并增加 glyph-nameunicode 属性

  • 忽略<g>标签

  • 合并<path>标签

unicode 属性值是自定义的码位,已经在文件中定义好了,通过读取文件的方式给每个<glyph>写上属性即可。

现有svg文件主要是汉字的笔顺图,path 信息已经有了,需要依次给每一笔添加一个码位信息。

image

image

image

image

码位对应信息如下:

id内码汉字笔顺笔画数笔顺起始DEC内码笔顺起始HEX内码笔顺结束DEC内码笔顺结束HEX内码
84U+4E1012154983077F0025983080F0028

java 处理代码如下:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class Svg2Font {

/**
* key: 内码16进制
*/
static Map<String, BaseInfo> map = new HashMap<>();
static StringBuilder sb = new StringBuilder();

static {
List<String> lines;
try {
lines = FileUtils.readLines(new File("D:\\java\\IdeaProjects\\project2020\\svg\\src\\main\\resources\\svg2ttf.txt"), "UTF-8");
lines.stream().forEach(line -> {
if(!line.startsWith("#")) {

map.put(line.split("\t")[1].replace("U+", ""), new BaseInfo(
Integer.parseInt(line.split("\t")[0]), line.split("\t")[2].replace("U+", ""),
line.split("\t")[2].trim(),
line.split("\t")[3],
Integer.parseInt(line.split("\t")[4]),
Integer.parseInt(line.split("\t")[5]),
line.split("\t")[6],
line.split("\t")[7],
line.split("\t")[8]));
}
});
} catch (Exception e) {
e.printStackTrace();
}


sb.append("<?xml version=\"1.0\" standalone=\"no\"?>")
.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" >")
.append("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\">")
.append("<metadata> Created by xxx </metadata>")
// create by xxx, horiz-adv-x 1024
.append("<defs><font id=\"stroke-font\" horiz-adv-x=\"1024\">")
// font family
// 这个glyph的horiz-adv-x属性被设置为800,表示其宽度为800单位(在这个例子中,单位是units-per-em中定义的1000分之一)
.append("<font-face font-family=\"stroke-font\" units-per-em=\"1024\" ascent=\"768\" descent=\"-192\"/>")
.append("<missing-glyph horiz-adv-x=\"1024\"/>")
.append("<glyph glyph-name=\".notdef\"></glyph>")
;
}

public static void main(String[] args) throws Exception {
List<String> lines = FileUtils.readLines(new File("D:\\java\\IdeaProjects\\project2020\\svg\\src\\main\\resources\\svg2ttf.txt"), "UTF-8");
String code = "";
for (String line : lines) {
if(line.startsWith("#")) {
continue;
}
code = line.split("\t")[1].replace("U+", "");
System.err.println("&#x"+code+";");
Collection<File> files = FileUtils.listFiles(new File("E:\\java\\nginx-1.16.1\\nginx-1.16.1\\html\\strokes\\" + Integer.parseInt(code, 16)), new String[]{"svg"}, false);
files.stream().sorted();
svgFileWriteToSvgFont(files, code);
}
sb.append("</font></defs></svg>");
FileUtils.write(new File("F:\\logs\\strokeFont.svg"), sb.toString(), "UTF-8");
}

public static void svgFileWriteToSvgFont(Collection<File> svgFiles, String code) throws IOException {
int i = 0;
for (File svgFile : svgFiles) {
String s = FileUtils.readFileToString(svgFile, "UTF-8");
String hex = Integer.toHexString(map.get(code).getStartCodeDec()+i);
s = s.replace("<svg xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\" width=\"600px\" height=\"850px\">", "<glyph glyph-name=\""+hex+"\" unicode=\"&#x"+hex+";\">")
.replace("</svg>", "</glyph>").replaceAll("id=\"[^\"]+\"", "")
.replace("<g>", "").replace("</g>", "")

.replaceAll("\"/>[\\r\\n \\t]*<path[\\r\\n \\t]+fill=\"#[^\\\"]+\"[\\r\\n \\t]+d=\"", " ")
.replaceAll("\"</path>[\\r\\n \\t]*<path[\\r\\n \\t]+fill=\"#[^\\\"]+\"[\\r\\n \\t]+d=\"", " ")
;
sb.append(s);
i++;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseInfo {
int order;
String code;
String c;
String strokeOrder;
int strokeCount;

Integer startCodeDec;
String startCodeHex;
String endCodeDec;
String endCodeHex;
}

经过java处理后得到了一个SVG 字体文件。

SVG 转 TTF

通过在线工具svg-to-ttf,可以把svg 转 ttf,即使是10M甚至更大的文件也可以轻易转换。等待一会就得到了一个ttf字体。

image

字体微调

为什么要微调?

在网页上查看一下字符显示的效果,html代码:(“我想要起飞”是默认字体显示,“&#xF0025; &#xF0026; &#xF0027; &#xF0028;”是新字体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>font test</title>
<style>
@font-face {
font-family: strokeFont;
src: url("./strokeFont (4).ttf") format("truetype");
}
body {
font-family: "strokeFont";
font-size: 250%;
}
</style>
</head>
<body>
&#xF0025;&#xF0026;&#xF0027;&#xF0028;
</body>
</html>

正常字符和字体中字符的显示对比:

image

虽然已经生成了字体文件,但是实际显示的时候还是有问题。这里我们主要解决2个问题:

  1. 字符宽度太宽
  2. 字符显示过小

如何微调?

使用字体工具 FontCreator打开新生成的字体,通过脚本微调,参数有:

  • Scale:基于基准“轴线中心”,水平百分比 400, 垂直百分比 400
  • Width:减少 右侧 2250

  • Width:减少 左侧 650

  • Move: 水平 0, 垂直 -20

调整 Scale 解决了字形大小的问题,Width解决了宽度过大的问题,Move解决了基线略低的问题。参数值需要根据情况调整。

经过调整后,看下效果:

image

基本已经达到效果了。

image