Compare commits

...

10 commits

Author SHA1 Message Date
subframe7536
63d607e49e add config for editor of showcase #346 2025-02-26 14:20:51 +08:00
Cyberczy
46d1727412
Update README.md (#345) 2025-02-23 08:07:04 +08:00
subframe7536
38f41ae254 extract cn static checker 2025-02-22 20:13:35 +08:00
subframe7536
b9627c8d6d error handle for config file 2025-02-22 20:12:46 +08:00
subframe7536
0ea5915099 simplify run_build 2025-02-22 20:12:11 +08:00
subframe7536
919f361f29 optimize italic fea 2025-02-20 09:17:37 +08:00
Song
97aa3c225d
remove --forgive (#340) 2025-02-20 00:02:39 +08:00
subframe7536
ea1dcb4cfc speed up mono build and CN base font instantiation 2025-02-19 23:05:18 +08:00
subframe7536
c479ad0ff8 use ProcessPoolExecutor instead 2025-02-19 20:56:26 +08:00
subframe7536
52019d85d9 use ThreadPoolExecutor 2025-02-19 19:51:35 +08:00
4 changed files with 160 additions and 107 deletions

View file

@ -36,6 +36,7 @@ The CN version contails the glyphs of simplified and traditional Chinese, and Ja
- Generated by [CodeImg](https://github.com/subframe7536/vscode-codeimg)
- Theme: [Maple](https://github.com/subframe7536/vscode-theme-maple)
- Config: font size 16px, line height 1.8, default letter spacing
## Download
@ -57,7 +58,12 @@ brew install --cask font-maple-mono-nf-cn
### Arch Linux
```shell
# Maple Mono
paru -S ttf-maple-beta
# Maple Mono NF
paru -S ttf-maple-beta-nf
# Maple Mono NF CN
paru -S ttf-maple-beta-nf-cn
```
## Feature Configurations

247
build.py
View file

@ -1,12 +1,12 @@
#!/usr/bin/env python3
import argparse
from concurrent.futures import ProcessPoolExecutor, as_completed
import importlib.util
import json
import re
import shutil
import time
from functools import partial
from multiprocessing import Pool
from os import environ, getcwd, listdir, makedirs, path, remove, getenv
from typing import Callable
from fontTools.ttLib import TTFont, newTable
@ -37,7 +37,7 @@ def check_ftcli():
if not package_installed:
print(
f"{package_name} is not found. Please run `pip install foundrytools-cli`"
f" {package_name} is not found. Please run `pip install foundrytools-cli`"
)
exit(1)
@ -179,11 +179,6 @@ def parse_args():
action="store_true",
help="Build font archives with config and license. If has `--cache` flag, only archive Nerd-Font and CN formats",
)
build_group.add_argument(
"--forgive",
action="store_true",
help="Forgive errors",
)
return parser.parse_args()
@ -275,7 +270,6 @@ class FontConfig:
}
self.glyph_width = 600
self.glyph_width_cn_narrow = 1000
self.forgive = False
self.__load_config(args.normal)
self.__load_args(args)
@ -319,8 +313,14 @@ class FontConfig:
else {**getattr(self, prop), **val},
)
except ():
print("Fail to load config.json. Please check your config.json.")
except FileNotFoundError:
print(f"🚨 Config file not found: {config_file_path}, use default config")
pass
except json.JSONDecodeError:
print(f"❗ Error: Invalid JSON in config file: {config_file_path}")
exit(1)
except Exception as e: # Catch any other unexpected error
print(f"❗ An unexpected error occurred: {e}")
exit(1)
def __load_args(self, args):
@ -357,9 +357,6 @@ class FontConfig:
if args.apply_fea_file:
self.apply_fea_file = True
if args.forgive:
self.forgive = True
if args.cn_rebuild:
self.cn["clean_cache"] = True
self.cn["use_static_base_font"] = False
@ -439,11 +436,11 @@ class BuildOption:
self.is_nf_built = False
self.is_cn_built = False
self.has_cache = (
self.check_cache_dir(self.output_variable, count=2)
and self.check_cache_dir(self.output_otf)
and self.check_cache_dir(self.output_ttf)
and self.check_cache_dir(self.output_ttf_hinted)
and self.check_cache_dir(self.output_woff2)
self.__check_cache_dir(self.output_variable, count=2)
and self.__check_cache_dir(self.output_otf)
and self.__check_cache_dir(self.output_ttf)
and self.__check_cache_dir(self.output_ttf_hinted)
and self.__check_cache_dir(self.output_woff2)
)
self.github_mirror = environ.get("GITHUB", "github.com")
@ -482,53 +479,68 @@ class BuildOption:
'\nNo `"cn.enable": true` in config.json or no `--cn` / `--cn-both` in argv. Skip CN build.'
)
return False
return self.__ensure_cn_static_fonts(
clean_cache=config.cn["clean_cache"],
use_static=config.cn["use_static_base_font"],
pool_size=config.pool_size,
)
if config.cn["clean_cache"]:
def __ensure_cn_static_fonts(
self, clean_cache: bool, use_static: bool, pool_size: int
) -> bool:
if clean_cache:
print("Clean CN static fonts")
shutil.rmtree(self.cn_static_dir, ignore_errors=True)
if (
not path.exists(self.cn_static_dir)
or listdir(self.cn_static_dir).__len__() != 16
):
tag = "cn-base"
if is_ci() or config.cn["use_static_base_font"]:
return download_cn_base_font(
tag=tag,
zip_path="cn-base-static.zip",
target_dir=self.cn_static_dir,
github_mirror=self.github_mirror,
)
if path.exists(self.cn_static_dir) and len(listdir(self.cn_static_dir)) == 16:
return True
if not config.cn["use_static_base_font"] or not path.exists(
self.cn_static_dir
tag = "cn-base"
if is_ci() or use_static:
if download_cn_base_font(
tag=tag,
zip_path="cn-base-static.zip",
target_dir=self.cn_static_dir,
github_mirror=self.github_mirror,
):
if (
path.exists(self.cn_variable_dir)
and listdir(self.cn_variable_dir).__len__() == 2
):
print("No static CN fonts but detect CN base fonts")
instantiate_cn_base(self.cn_variable_dir, self.cn_static_dir)
return True
return True
result = download_cn_base_font(
tag=tag,
zip_path="cn-base-variable.zip",
target_dir=self.cn_variable_dir,
github_mirror=self.github_mirror,
)
if result:
instantiate_cn_base(self.cn_variable_dir, self.cn_static_dir)
return True
# Try using variable fonts if static fonts aren't available
if (
path.exists(self.cn_variable_dir)
and len(listdir(self.cn_variable_dir)) == 2
):
print(
"No static CN fonts but detect variable version, start instantiating..."
)
instantiate_cn_base(
cn_variable_dir=self.cn_variable_dir,
cn_static_dir=self.cn_static_dir,
pool_size=pool_size,
)
return True
print("\nCN base fonts don't exist. Skip CN build.")
return False
return True
# Download variable fonts and instantiate if necessary
if download_cn_base_font(
tag=tag,
zip_path="cn-base-variable.zip",
target_dir=self.cn_variable_dir,
github_mirror=self.github_mirror,
):
instantiate_cn_base(
cn_variable_dir=self.cn_variable_dir,
cn_static_dir=self.cn_static_dir,
pool_size=pool_size,
)
return True
def check_cache_dir(self, cache_dir: str, count: int = 16) -> bool:
print("\nCN base fonts don't exist. Skip CN build.")
return False
def __check_cache_dir(self, cache_dir: str, count: int = 16) -> bool:
if not path.isdir(cache_dir):
return False
return listdir(cache_dir).__len__() == count
return len(listdir(cache_dir)) == count
def handle_ligatures(
@ -546,19 +558,38 @@ def handle_ligatures(
)
def instantiate_cn_base(cn_variable_dir: str, cn_static_dir: str):
def instantiate_cn_var(f: str, base_dir: str, output_dir: str):
run(
f"ftcli converter vf2i -out {output_dir} {joinPaths(base_dir, f)}",
log=True,
)
def optimize_cn_base(f: str, base_dir: str):
font_path = joinPaths(base_dir, f)
print(f"✨ Optimize {font_path}")
run(f"ftcli ttf fix-contours {font_path}")
run(f"ftcli ttf remove-overlaps {font_path}")
run(
f"ftcli utils del-table -t kern -t GPOS {font_path}",
)
def instantiate_cn_base(cn_variable_dir: str, cn_static_dir: str, pool_size: int):
print("=========================================")
print("Instantiating CN Base font, be patient...")
print("=========================================")
run(
f"ftcli converter vf2i {cn_variable_dir} -out {cn_static_dir}",
log=True,
run_build(
pool_size=pool_size,
fn=partial(
instantiate_cn_var, base_dir=cn_variable_dir, output_dir=cn_static_dir
),
dir=cn_variable_dir,
)
run(f"ftcli ttf fix-contours {cn_static_dir}", log=True)
run(f"ftcli ttf remove-overlaps {cn_static_dir}", log=True)
run(
f"ftcli utils del-table -t kern -t GPOS {cn_static_dir}",
log=True,
run_build(
pool_size=pool_size,
fn=partial(optimize_cn_base, base_dir=cn_static_dir),
dir=cn_static_dir,
)
@ -735,6 +766,17 @@ def add_gasp(font: TTFont):
def build_mono(f: str, font_config: FontConfig, build_option: BuildOption):
print(f"👉 Minimal version for {f}")
source_path = joinPaths(build_option.output_ttf, f)
run(f"ftcli fix italic-angle {source_path}")
run(f"ftcli fix monospace {source_path}")
run(f"ftcli fix strip-names {source_path}")
if font_config.debug:
run(f"ftcli ttf dehint {source_path}")
else:
# dehint, remove overlap and fix contours
run(f"ftcli ttf fix-contours --silent {source_path}")
font = TTFont(source_path)
style_compact = f.split("-")[-1].split(".")[0]
@ -776,6 +818,12 @@ def build_mono(f: str, font_config: FontConfig, build_option: BuildOption):
freeze_config=font_config.feature_freeze,
)
verify_glyph_width(
font=font,
expect_widths=font_config.get_valid_glyph_width_list(),
file_name=postscript_name,
)
remove(source_path)
target_path = joinPaths(build_option.output_ttf, f"{postscript_name}.ttf")
font.save(target_path)
@ -889,10 +937,15 @@ def build_nf(
preferred_family_name=f"{font_config.family_name} NF",
preferred_style_name=style_in_17,
)
verify_glyph_width(
font=nf_font,
expect_widths=font_config.get_valid_glyph_width_list(),
file_name=postscript_name,
)
target_path = joinPaths(
build_option.output_nf,
f"{font_config.family_name_compact}-NF-{style_compact_nf}.ttf",
f"{postscript_name}.ttf",
)
nf_font.save(target_path)
nf_font.close()
@ -973,22 +1026,44 @@ def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
"slng": "Latn, Hans, Hant, Jpan",
}
cn_font["meta"] = meta
verify_glyph_width(
font=cn_font,
expect_widths=font_config.get_valid_glyph_width_list(True),
file_name=postscript_name,
)
target_path = joinPaths(
build_option.output_cn,
f"{font_config.family_name_compact}-{build_option.cn_suffix_compact}-{style_compact_cn}.ttf",
f"{postscript_name}.ttf",
)
cn_font.save(target_path)
cn_font.close()
def wrapped_fn(shutdown_event, fn, filename):
if shutdown_event.is_set():
return
try:
return fn(filename)
except Exception as e:
shutdown_event.set()
raise e
def run_build(pool_size: int, fn: Callable, dir: str):
if pool_size > 1:
with Pool(pool_size) as p:
p.map(fn, listdir(dir))
else:
for f in listdir(dir):
files = listdir(dir)
if pool_size <= 1:
for f in files:
fn(f)
return
with ProcessPoolExecutor(max_workers=pool_size) as executor:
futures = {
executor.submit(fn, f): f for f in files
}
for future in as_completed(futures):
future.result()
def main():
@ -1067,7 +1142,7 @@ def main():
verify_glyph_width(
font=font,
expect_widths=font_config.get_valid_glyph_width_list(),
forgive=font_config.forgive,
file_name=basename,
)
add_gasp(font)
@ -1088,18 +1163,8 @@ def main():
run(f"ftcli fix monospace {build_option.output_variable}")
print("Instantiate TTF")
run(
f"ftcli converter vf2i {build_option.output_variable} -out {build_option.output_ttf}"
f"ftcli converter vf2i -out {build_option.output_ttf} {build_option.output_variable}"
)
print("Fix static TTF")
run(f"ftcli fix italic-angle {build_option.output_ttf}")
run(f"ftcli fix monospace {build_option.output_ttf}")
run(f"ftcli fix strip-names {build_option.output_ttf}")
if font_config.debug:
run(f"ftcli ttf dehint {build_option.output_ttf}")
else:
# dehint, remove overlap and fix contours
run(f"ftcli ttf fix-contours --silent {build_option.output_ttf}")
_build_mono = partial(
build_mono, font_config=font_config, build_option=build_option
@ -1143,14 +1208,6 @@ def main():
drop_mac_names(build_option.output_ttf)
build_option.is_nf_built = True
verify_glyph_width(
font=TTFont(
joinPaths(build_option.output_nf, listdir(build_option.output_nf)[0])
),
expect_widths=font_config.get_valid_glyph_width_list(),
forgive=font_config.forgive,
)
# =========================================================================================
# ==================================== Build CN =======================================
# =========================================================================================
@ -1180,14 +1237,6 @@ def main():
build_option.is_cn_built = True
verify_glyph_width(
font=TTFont(
joinPaths(build_option.output_cn, listdir(build_option.output_cn)[0])
),
expect_widths=font_config.get_valid_glyph_width_list(True),
forgive=font_config.forgive,
)
# =========================================================================================
# ================================== Write Config =====================================
# =========================================================================================

View file

@ -2,7 +2,8 @@
#/> otclasses
@Uppercase = [A Aacute Abreve Abreveacute Abrevedotbelow Abrevegrave Abrevehookabove Abrevetilde Acaron Acircumflex Acircumflexacute Acircumflexdotbelow Acircumflexgrave Acircumflexhookabove Acircumflextilde Adieresis Adotbelow Agrave Ahookabove Amacron Aogonek Aring Atilde AE AEacute B C Cacute Ccaron Ccedilla Ccircumflex Cdotaccent D Eth Dcaron Dcroat E Eacute Ebreve Ecaron Ecircumflex Ecircumflexacute Ecircumflexdotbelow Ecircumflexgrave Ecircumflexhookabove Ecircumflextilde Edieresis Edotaccent Edotbelow Egrave Ehookabove Emacron Eogonek Eopen Etilde Schwa F G Gacute Gbreve Gcaron Gcircumflex Gcommaaccent Gdotaccent H Hbar Hcircumflex I IJ IJ_acute Iacute Ibreve Icircumflex Idieresis Idotaccent Idotbelow Igrave Ihookabove Imacron Iogonek Itilde J Jcircumflex K Kcommaaccent L Lacute Lcaron Lcommaaccent Ldot Lslash M N Nacute Ncaron Ncommaaccent Ntilde Eng O Oacute Obreve Ocircumflex Ocircumflexacute Ocircumflexdotbelow Ocircumflexgrave Ocircumflexhookabove Ocircumflextilde Odieresis Odotbelow Ograve Ohookabove Ohorn Ohornacute Ohorndotbelow Ohorngrave Ohornhookabove Ohorntilde Ohungarumlaut Omacron Oogonek Oslash Oslashacute Otilde OE P Thorn Q R Racute Rcaron Rcommaaccent S Sacute Scaron Scedilla Scircumflex Scommaaccent Germandbls T Tbar Tcaron Tcedilla Tcommaaccent U Uacute Ubreve Ucircumflex Udieresis Udotbelow Ugrave Uhookabove Uhorn Uhornacute Uhorndotbelow Uhorngrave Uhornhookabove Uhorntilde Uhungarumlaut Umacron Uogonek Uring Utilde V W Wacute Wcircumflex Wdieresis Wgrave X Y Yacute Ycircumflex Ydieresis Ydotbelow Ygrave Yhookabove Ymacron Ytilde Z Zacute Zcaron Zdotaccent A-cy Be-cy Ve-cy Ge-cy Gje-cy Gheupturn-cy Ghestroke-cy De-cy Ie-cy Io-cy Zhe-cy Ze-cy Ii-cy Iishort-cy Ka-cy Kje-cy El-cy Em-cy En-cy O-cy Pe-cy Er-cy Es-cy Te-cy U-cy Ushort-cy Ef-cy Ha-cy Che-cy Tse-cy Sha-cy Shcha-cy Dzhe-cy Softsign-cy Yeru-cy Hardsign-cy Lje-cy Nje-cy Dze-cy E-cy Ereversed-cy I-cy Yi-cy Je-cy Tshe-cy Iu-cy Ia-cy Dje-cy Kadescender-cy Endescender-cy Ustraight-cy Ustraightstroke-cy Chedescender-cy Shha-cy Schwa-cy Zhedieresis-cy Zedieresis-cy Idieresis-cy Odieresis-cy Obarred-cy Chedieresis-cy Alpha Beta Gamma Delta Epsilon Zeta Eta Theta Iota Kappa Lambda Mu Nu Xi Omicron Pi Rho Sigma Tau Upsilon Phi Chi Psi Omega Alphatonos Epsilontonos Etatonos Iotatonos Omicrontonos Upsilontonos Omegatonos Iotadieresis Upsilondieresis KaiSymbol];
@zero = [zero zero.zero];
@Digit = [zero zero.zero one two three four five six seven eight nine];
@one = [one one.cv04];
@Digit = [@zero @one two three four five six seven eight nine];
@DigitHex = [a b c d e f A B C D E F];
@Space = [space nbspace];
@SpaceExlusion = [@Space bullet periodcentered period];

View file

@ -191,7 +191,7 @@ def match_unicode_names(file_path: str) -> dict[str, str]:
# https://github.com/subframe7536/maple-font/issues/314
def verify_glyph_width(font: TTFont, expect_widths: list[int], forgive: bool):
def verify_glyph_width(font: TTFont, expect_widths: list[int], file_name: str = None):
print("Verify glyph width...")
result: tuple[str, int] = []
for name in font.getGlyphNames():
@ -204,12 +204,9 @@ def verify_glyph_width(font: TTFont, expect_widths: list[int], forgive: bool):
for item in result:
print(f"{item[0]} => {item[1]}")
if forgive:
print("❗Forgive it")
else:
raise Exception(
f"The font may contain glyphs that width is not in {expect_widths}, which may broke monospace rule."
)
raise Exception(
f"{file_name or 'The font'} may contain glyphs that width is not in {expect_widths}, which may broke monospace rule."
)
def compress_folder(