+2
README.md
+2
README.md
+12
docs/fzg_build_guide.md
+12
docs/fzg_build_guide.md
···
1
+
# VSCode 上的 FZG 编译指南。
2
+
3
+
*(以 VSCodium 为例)*
4
+
5
+
```
6
+
$ git clone --recurse-submodules https://tangled.org/@dwn.dwnfonts.cc/fzg
7
+
$ codium .
8
+
```
9
+
10
+
这时候会打开 VSCodium。点击顶部搜索框,搜索 `>Python: Create Environment`,选择 `Venv`,**不要**勾选 `requirements.txt`。
11
+
12
+
导航到 [tools/build.py](../tools/build.py),点击 `Python Debugger: Debug Python File` 开始编译。你也可以使用 `python -m tools.build`
+241
-107
tools/build.py
+241
-107
tools/build.py
···
1
1
import math
2
2
import shutil
3
3
import zipfile
4
+
import threading
5
+
import logging # 导入logging模块
4
6
5
7
from fontTools.ttLib import TTFont
6
8
from kbitfont import KbitFont
7
-
from pixel_font_builder import FontBuilder, WeightName, SerifStyle, SlantStyle, WidthStyle, Glyph
9
+
from pixel_font_builder import (
10
+
FontBuilder,
11
+
WeightName,
12
+
SerifStyle,
13
+
SlantStyle,
14
+
WidthStyle,
15
+
Glyph,
16
+
)
8
17
9
18
from tools import path_define, options
10
19
from tools.kbitx_marge_selected import advanced_merge_kbitx_files
···
12
21
13
22
from fzg import fix_metrics
14
23
24
+
# 配置logging
25
+
logging.basicConfig(
26
+
level=logging.INFO,
27
+
format="%(asctime)s - %(levelname)s - %(message)s",
28
+
datefmt="%Y-%m-%d %H:%M:%S",
29
+
)
30
+
31
+
15
32
def fix_mono_mode(font: TTFont):
16
-
font['post'].isFixedPitch = 1
17
-
font['OS/2'].panose.bFamilyType = 2
18
-
font['OS/2'].panose.bProportion = 9 # 修改字体的 Panose 属性,使其能够被识别为等宽字体
19
-
font['OS/2'].xAvgCharWidth = 600 # 修复 Panose 属性引起的字宽问题
20
-
font['OS/2'].achVendID = "ZLAB"
21
-
font['OS/2'].ulCodePageRange1 = 0b1100000000101100000000000001101
22
-
font['OS/2'].ulCodePageRange2 = 0b10000110101010000000000000000 # 为字体添加微软编码页属性,防止某些程序不识别
33
+
font["post"].isFixedPitch = 1
34
+
font["OS/2"].panose.bFamilyType = 2
35
+
font["OS/2"].panose.bProportion = (
36
+
9 # 修改字体的 Panose 属性,使其能够被识别为等宽字体
37
+
)
38
+
font["OS/2"].xAvgCharWidth = 600 # 修复 Panose 属性引起的字宽问题
39
+
font["OS/2"].achVendID = "ZLAB"
40
+
font["OS/2"].ulCodePageRange1 = 0b1100000000101100000000000001101
41
+
font["OS/2"].ulCodePageRange2 = (
42
+
0b10000110101010000000000000000 # 为字体添加微软编码页属性,防止某些程序不识别
43
+
)
23
44
24
45
25
-
def main():
26
-
fix_metrics.main()
46
+
def process_region(region):
47
+
"""处理单个地区的kbitx文件合并"""
48
+
logging.info(f"开始处理地区: {region}")
27
49
28
-
if path_define.build_dir.exists():
29
-
shutil.rmtree(path_define.build_dir)
30
-
path_define.outputs_dir.mkdir(parents=True)
31
-
path_define.releases_dir.mkdir(parents=True)
50
+
if region != "CN":
51
+
merge_kbitx_files(
52
+
path_define.data_dir.joinpath(f"ZLabsBitmapCN.kbitx"),
53
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_diff.kbitx"),
54
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_fallback.kbitx"),
55
+
)
56
+
else:
57
+
shutil.copy(
58
+
path_define.data_dir.joinpath("ZLabsBitmapCN.kbitx"),
59
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_fallback.kbitx"),
60
+
)
61
+
merge_kbitx_files(
62
+
path_define.data_dir.joinpath(f"Galmuri11.kbitx"),
63
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_fallback.kbitx"),
64
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_temp.kbitx"),
65
+
)
66
+
merge_kbitx_files(
67
+
path_define.data_dir.joinpath(f"ZLabsBitmap{region}_temp.kbitx"),
68
+
path_define.data_dir.joinpath(f"Fairfax.kbitx"),
69
+
path_define.data_dir.joinpath(f"FZG_{region}.kbitx"),
70
+
)
71
+
merge_kbitx_files(
72
+
path_define.data_dir.joinpath(f"FZG_{region}.kbitx"),
73
+
path_define.src_dir.joinpath(f"ZLabsBitmap{region}_meta.kbitx"),
74
+
path_define.data_dir.joinpath(f"FZG_{region}.kbitx"),
75
+
)
32
76
33
-
# shutil.copy(path_define.src_dir.joinpath('ZLabsBitmapCN.kbitx'), path_define.data_dir)
34
-
# there is fixed version of cn, don't copy again
35
-
for region in ['CN', 'HC', 'JP']:
36
-
# advanced_merge_kbitx_files(path_define.data_dir.joinpath(f'ZLabsBitmapCN.kbitx'),
37
-
# path_define.data_dir.joinpath(f'ZLabsBitmap{region}_diff.kbitx'),
38
-
# path_define.src_dir.joinpath(f'flags_{region}.txt'),
39
-
# path_define.data_dir.joinpath(f'ZLabsBitmap{region}.kbitx'))
40
-
# just make the fallback version
41
-
if region != 'CN':
42
-
merge_kbitx_files(path_define.data_dir.joinpath(f'ZLabsBitmapCN.kbitx'),
43
-
path_define.data_dir.joinpath(f'ZLabsBitmap{region}_diff.kbitx'),
44
-
path_define.data_dir.joinpath(f'ZLabsBitmap{region}_fallback.kbitx'))
45
-
else:
46
-
shutil.copy(path_define.data_dir.joinpath('ZLabsBitmapCN.kbitx'), path_define.data_dir.joinpath(f'ZLabsBitmap{region}_fallback.kbitx'))
47
-
merge_kbitx_files(path_define.data_dir.joinpath(f'Galmuri11.kbitx'),
48
-
path_define.data_dir.joinpath(f'ZLabsBitmap{region}_fallback.kbitx'),
49
-
path_define.data_dir.joinpath(f'ZLabsBitmap{region}_temp.kbitx'))
50
-
merge_kbitx_files(path_define.data_dir.joinpath(f'ZLabsBitmap{region}_temp.kbitx'),
51
-
path_define.data_dir.joinpath(f'Fairfax.kbitx'),
52
-
path_define.data_dir.joinpath(f'FZG_{region}.kbitx'))
53
-
merge_kbitx_files(path_define.data_dir.joinpath(f'FZG_{region}.kbitx'),
54
-
path_define.src_dir.joinpath(f'ZLabsBitmap{region}_meta.kbitx'),
55
-
path_define.data_dir.joinpath(f'FZG_{region}.kbitx'))
77
+
logging.info(f"完成处理地区: {region}")
56
78
57
79
58
-
for language_flavor in options.language_flavors:
59
-
kbit_font = KbitFont.load_kbitx(path_define.data_dir.joinpath(f'FZG_{language_flavor}.kbitx'))
80
+
def process_otf(language_flavor, builder):
81
+
"""处理OTF字体格式"""
82
+
otf_font = builder.to_otf_builder().font
83
+
fix_mono_mode(otf_font)
60
84
85
+
otf_font.save(
86
+
path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.otf")
87
+
)
88
+
logging.info(f"已创建 {language_flavor} otf")
61
89
62
-
builder = FontBuilder()
63
-
builder.font_metric.font_size = kbit_font.props.em_height
64
-
builder.font_metric.horizontal_layout.ascent = kbit_font.props.line_ascent
65
-
builder.font_metric.horizontal_layout.descent = -kbit_font.props.line_descent
66
-
builder.font_metric.horizontal_layout.line_gap = 1
67
-
builder.font_metric.vertical_layout.ascent = math.ceil(kbit_font.props.line_height / 2)
68
-
builder.font_metric.vertical_layout.descent = -math.floor(kbit_font.props.line_height / 2)
69
-
builder.font_metric.x_height = kbit_font.props.x_height
70
-
builder.font_metric.cap_height = kbit_font.props.cap_height
90
+
# otf_font.flavor = "woff"
91
+
# otf_font.save(
92
+
# path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.otf.woff")
93
+
# )
94
+
# logging.info(f"已创建 {language_flavor} otf.woff")
95
+
96
+
# otf_font.flavor = "woff2"
97
+
# otf_font.save(
98
+
# path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.otf.woff2")
99
+
# )
100
+
# logging.info(f"已创建 {language_flavor} otf.woff2")
101
+
102
+
103
+
def process_ttf(language_flavor, builder):
104
+
"""处理TTF字体格式"""
105
+
ttf_font = builder.to_ttf_builder().font
106
+
fix_mono_mode(ttf_font)
107
+
108
+
ttf_font.save(
109
+
path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.ttf")
110
+
)
111
+
logging.info(f"已创建 {language_flavor} ttf")
112
+
113
+
# ttf_font.flavor = "woff"
114
+
# ttf_font.save(
115
+
# path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.ttf.woff")
116
+
# )
117
+
# logging.info(f"已创建 {language_flavor} ttf.woff")
71
118
72
-
builder.meta_info.version = kbit_font.names.version
73
-
builder.meta_info.weight_name = WeightName.REGULAR
74
-
builder.meta_info.serif_style = SerifStyle.SERIF
75
-
builder.meta_info.slant_style = SlantStyle.NORMAL
76
-
builder.meta_info.width_style = WidthStyle.MONOSPACED
77
-
builder.meta_info.manufacturer = kbit_font.names.manufacturer
78
-
builder.meta_info.designer = kbit_font.names.designer
79
-
builder.meta_info.description = kbit_font.names.description
80
-
builder.meta_info.copyright_info = kbit_font.names.copyright
81
-
builder.meta_info.license_info = kbit_font.names.license_description
82
-
builder.meta_info.vendor_url = kbit_font.names.vendor_url
83
-
builder.meta_info.designer_url = kbit_font.names.designer_url
84
-
builder.meta_info.license_url = kbit_font.names.license_url
85
-
builder.meta_info.sample_text = kbit_font.names.sample_text
119
+
# ttf_font.flavor = "woff2"
120
+
# ttf_font.save(
121
+
# path_define.outputs_dir.joinpath(f"FZG_{language_flavor.upper()}.ttf.woff2")
122
+
# )
123
+
# logging.info(f"已创建 {language_flavor} ttf.woff2")
86
124
87
-
if language_flavor == 'HC_fallback' or language_flavor == 'JP_fallback':
88
-
builder.meta_info.family_name = kbit_font.names.family + ' Fallback'
89
-
else:
90
-
builder.meta_info.family_name = kbit_font.names.family
91
125
126
+
def process_language_flavor(language_flavor):
127
+
"""处理单个语言变体的字体生成,并行处理OTF和TTF"""
128
+
logging.info(f"开始处理语言变体: {language_flavor}")
92
129
93
-
k_glyph_notdef = kbit_font.named_glyphs['.notdef']
94
-
builder.glyphs.append(Glyph(
95
-
name='.notdef',
96
-
horizontal_offset=(k_glyph_notdef.x, k_glyph_notdef.y - k_glyph_notdef.height),
130
+
kbit_font = KbitFont.load_kbitx(
131
+
path_define.data_dir.joinpath(f"FZG_{language_flavor}.kbitx")
132
+
)
133
+
134
+
builder = FontBuilder()
135
+
builder.font_metric.font_size = kbit_font.props.em_height
136
+
builder.font_metric.horizontal_layout.ascent = kbit_font.props.line_ascent
137
+
builder.font_metric.horizontal_layout.descent = -kbit_font.props.line_descent
138
+
builder.font_metric.horizontal_layout.line_gap = 1
139
+
builder.font_metric.vertical_layout.ascent = math.ceil(
140
+
kbit_font.props.line_height / 2
141
+
)
142
+
builder.font_metric.vertical_layout.descent = -math.floor(
143
+
kbit_font.props.line_height / 2
144
+
)
145
+
builder.font_metric.x_height = kbit_font.props.x_height
146
+
builder.font_metric.cap_height = kbit_font.props.cap_height
147
+
148
+
builder.meta_info.version = kbit_font.names.version
149
+
builder.meta_info.weight_name = WeightName.REGULAR
150
+
builder.meta_info.serif_style = SerifStyle.SERIF
151
+
builder.meta_info.slant_style = SlantStyle.NORMAL
152
+
builder.meta_info.width_style = WidthStyle.MONOSPACED
153
+
builder.meta_info.manufacturer = kbit_font.names.manufacturer
154
+
builder.meta_info.designer = kbit_font.names.designer
155
+
builder.meta_info.description = kbit_font.names.description
156
+
builder.meta_info.copyright_info = kbit_font.names.copyright
157
+
builder.meta_info.license_info = kbit_font.names.license_description
158
+
builder.meta_info.vendor_url = kbit_font.names.vendor_url
159
+
builder.meta_info.designer_url = kbit_font.names.designer_url
160
+
builder.meta_info.license_url = kbit_font.names.license_url
161
+
builder.meta_info.sample_text = kbit_font.names.sample_text
162
+
163
+
if language_flavor == "HC_fallback" or language_flavor == "JP_fallback":
164
+
builder.meta_info.family_name = kbit_font.names.family + " Fallback"
165
+
else:
166
+
builder.meta_info.family_name = kbit_font.names.family
167
+
168
+
k_glyph_notdef = kbit_font.named_glyphs[".notdef"]
169
+
builder.glyphs.append(
170
+
Glyph(
171
+
name=".notdef",
172
+
horizontal_offset=(
173
+
k_glyph_notdef.x,
174
+
k_glyph_notdef.y - k_glyph_notdef.height,
175
+
),
97
176
advance_width=k_glyph_notdef.advance,
98
-
vertical_offset=(k_glyph_notdef.width // 2, kbit_font.props.em_ascent - k_glyph_notdef.y),
177
+
vertical_offset=(
178
+
k_glyph_notdef.width // 2,
179
+
kbit_font.props.em_ascent - k_glyph_notdef.y,
180
+
),
99
181
advance_height=kbit_font.props.em_height,
100
-
bitmap=[[0 if color <= 127 else 1 for color in bitmap_row] for bitmap_row in k_glyph_notdef.bitmap],
101
-
))
182
+
bitmap=[
183
+
[0 if color <= 127 else 1 for color in bitmap_row]
184
+
for bitmap_row in k_glyph_notdef.bitmap
185
+
],
186
+
)
187
+
)
102
188
103
-
for code_point, k_glyph in sorted(kbit_font.characters.items()):
104
-
glyph_name = f'{code_point:04X}'
105
-
builder.character_mapping[code_point] = glyph_name
106
-
builder.glyphs.append(Glyph(
189
+
for code_point, k_glyph in sorted(kbit_font.characters.items()):
190
+
glyph_name = f"{code_point:04X}"
191
+
builder.character_mapping[code_point] = glyph_name
192
+
builder.glyphs.append(
193
+
Glyph(
107
194
name=glyph_name,
108
195
horizontal_offset=(k_glyph.x, k_glyph.y - k_glyph.height),
109
196
advance_width=k_glyph.advance,
110
-
vertical_offset=(k_glyph.width // 2, kbit_font.props.em_ascent - k_glyph.y),
197
+
vertical_offset=(
198
+
k_glyph.width // 2,
199
+
kbit_font.props.em_ascent - k_glyph.y,
200
+
),
111
201
advance_height=kbit_font.props.em_height,
112
-
bitmap=[[0 if color <= 127 else 1 for color in bitmap_row] for bitmap_row in k_glyph.bitmap],
113
-
))
202
+
bitmap=[
203
+
[0 if color <= 127 else 1 for color in bitmap_row]
204
+
for bitmap_row in k_glyph.bitmap
205
+
],
206
+
)
207
+
)
114
208
115
-
otf_font = builder.to_otf_builder().font
116
-
fix_mono_mode(otf_font)
209
+
# 创建并启动OTF和TTF处理线程,实现并行处理
210
+
otf_thread = threading.Thread(target=process_otf, args=(language_flavor, builder))
211
+
ttf_thread = threading.Thread(target=process_ttf, args=(language_flavor, builder))
117
212
118
-
otf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.otf'))
119
-
print(f'Create {language_flavor} otf')
213
+
otf_thread.start()
214
+
ttf_thread.start()
120
215
121
-
otf_font.flavor = 'woff'
122
-
otf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.otf.woff'))
123
-
print(f'Create {language_flavor} otf.woff')
216
+
# 等待两个线程完成
217
+
otf_thread.join()
218
+
ttf_thread.join()
124
219
125
-
otf_font.flavor = 'woff2'
126
-
otf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.otf.woff2'))
127
-
print(f'Create {language_flavor} otf.woff2')
220
+
logging.info(f"完成处理语言变体: {language_flavor}")
128
221
129
-
ttf_font = builder.to_ttf_builder().font
130
-
fix_mono_mode(ttf_font)
222
+
223
+
def process_font_format(font_format):
224
+
"""处理单个字体格式的压缩包生成"""
225
+
logging.info(f"开始处理字体格式: {font_format}")
226
+
227
+
with zipfile.ZipFile(
228
+
path_define.releases_dir.joinpath(f"FZG_-{font_format}.zip"), "w"
229
+
) as file:
230
+
file.write(path_define.project_root_dir.joinpath("LICENSE-OFL"), "LICENSE")
231
+
for font_file_path in path_define.outputs_dir.iterdir():
232
+
if font_file_path.name.endswith(f".{font_format}"):
233
+
file.write(font_file_path, font_file_path.name)
234
+
235
+
logging.info(f"已创建 {font_format} zip")
236
+
237
+
238
+
def main():
239
+
logging.info("开始执行字体处理程序")
240
+
241
+
fix_metrics.main()
242
+
243
+
if path_define.build_dir.exists():
244
+
shutil.rmtree(path_define.build_dir)
245
+
path_define.outputs_dir.mkdir(parents=True)
246
+
path_define.releases_dir.mkdir(parents=True)
247
+
248
+
# 处理地区的多线程实现
249
+
region_threads = []
250
+
for region in ["CN", "HC", "JP"]:
251
+
thread = threading.Thread(target=process_region, args=(region,))
252
+
region_threads.append(thread)
253
+
thread.start()
131
254
132
-
ttf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.ttf'))
133
-
print(f'Create {language_flavor} ttf')
255
+
# 等待所有地区处理线程完成
256
+
for thread in region_threads:
257
+
thread.join()
134
258
135
-
ttf_font.flavor = 'woff'
136
-
ttf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.ttf.woff'))
137
-
print(f'Create {language_flavor} ttf.woff')
259
+
# 处理语言变体的多线程实现
260
+
flavor_threads = []
261
+
for language_flavor in options.language_flavors:
262
+
thread = threading.Thread(
263
+
target=process_language_flavor, args=(language_flavor,)
264
+
)
265
+
flavor_threads.append(thread)
266
+
thread.start()
138
267
139
-
ttf_font.flavor = 'woff2'
140
-
ttf_font.save(path_define.outputs_dir.joinpath(f'FZG_{language_flavor.upper()}.ttf.woff2'))
141
-
print(f'Create {language_flavor} ttf.woff2')
268
+
# 等待所有语言变体处理线程完成
269
+
for thread in flavor_threads:
270
+
thread.join()
142
271
272
+
# 处理字体格式的多线程实现
273
+
format_threads = []
143
274
for font_format in options.font_formats:
144
-
with zipfile.ZipFile(path_define.releases_dir.joinpath(f'FZG_-{font_format}.zip'), 'w') as file:
145
-
file.write(path_define.project_root_dir.joinpath('LICENSE-OFL'), 'LICENSE')
146
-
for font_file_path in path_define.outputs_dir.iterdir():
147
-
if font_file_path.name.endswith(f'.{font_format}'):
148
-
file.write(font_file_path, font_file_path.name)
149
-
print(f'Create {font_format} zip')
275
+
thread = threading.Thread(target=process_font_format, args=(font_format,))
276
+
format_threads.append(thread)
277
+
thread.start()
278
+
279
+
# 等待所有字体格式处理线程完成
280
+
for thread in format_threads:
281
+
thread.join()
282
+
283
+
logging.info("字体处理程序执行完成")
150
284
151
285
152
-
if __name__ == '__main__':
286
+
if __name__ == "__main__":
153
287
main()
+5
-9
tools/options.py
+5
-9
tools/options.py
···
1
1
from typing import Literal, get_args
2
2
3
3
type LanguageFlavor = Literal[
4
-
'CN',
5
-
'HC',
6
-
'JP',
4
+
"CN",
5
+
"HC",
6
+
"JP",
7
7
]
8
8
language_flavors = list[LanguageFlavor](get_args(LanguageFlavor.__value__))
9
9
10
10
type FontFormat = Literal[
11
-
'otf',
12
-
'otf.woff',
13
-
'otf.woff2',
14
-
'ttf',
15
-
'ttf.woff',
16
-
'ttf.woff2',
11
+
"otf",
12
+
"ttf",
17
13
]
18
14
font_formats = list[FontFormat](get_args(FontFormat.__value__))