Compare commits
10 commits
5add635649
...
63d607e49e
Author | SHA1 | Date | |
---|---|---|---|
|
63d607e49e | ||
|
46d1727412 | ||
|
38f41ae254 | ||
|
b9627c8d6d | ||
|
0ea5915099 | ||
|
919f361f29 | ||
|
97aa3c225d | ||
|
ea1dcb4cfc | ||
|
c479ad0ff8 | ||
|
52019d85d9 |
4 changed files with 160 additions and 107 deletions
|
@ -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
247
build.py
|
@ -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 =====================================
|
||||
# =========================================================================================
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue