maple-font/build.py

1324 lines
43 KiB
Python
Raw Permalink Normal View History

2024-12-14 12:31:18 +00:00
#!/usr/bin/env python3
2024-10-13 11:14:59 +00:00
import argparse
2025-02-19 12:56:26 +00:00
from concurrent.futures import ProcessPoolExecutor, as_completed
2024-03-02 14:59:59 +00:00
import importlib.util
import json
2024-12-27 03:13:48 +00:00
import re
import shutil
2024-05-24 03:29:45 +00:00
import time
2024-05-13 05:27:54 +00:00
from functools import partial
from os import environ, getcwd, listdir, makedirs, path, remove, getenv
2024-10-13 15:29:39 +00:00
from typing import Callable
2024-03-23 06:39:53 +00:00
from fontTools.ttLib import TTFont, newTable
2024-12-27 04:53:53 +00:00
from fontTools.feaLib.builder import addOpenTypeFeatures
2024-03-16 09:03:23 +00:00
from fontTools.merge import Merger
from source.py.utils import (
check_font_patcher,
verify_glyph_width,
compress_folder,
download_cn_base_font,
get_font_forge_bin,
2024-12-27 05:47:39 +00:00
get_font_name,
2024-12-03 10:02:41 +00:00
is_ci,
2024-12-26 16:07:50 +00:00
match_unicode_names,
run,
set_font_name,
joinPaths,
)
2024-11-23 11:26:41 +00:00
from source.py.feature import freeze_feature, get_freeze_config_str
2025-01-27 03:48:24 +00:00
FONT_VERSION = "v7.0-beta36"
2024-07-29 05:21:36 +00:00
# =========================================================================================
2024-10-13 11:14:59 +00:00
def check_ftcli():
package_name = "foundryToolsCLI"
package_installed = importlib.util.find_spec(package_name) is not None
if not package_installed:
print(
2025-02-22 12:13:35 +00:00
f"{package_name} is not found. Please run `pip install foundrytools-cli`"
)
2024-10-13 11:14:59 +00:00
exit(1)
2024-07-29 05:21:36 +00:00
# =========================================================================================
2024-10-13 13:49:20 +00:00
2024-10-13 11:14:59 +00:00
def parse_args():
parser = argparse.ArgumentParser(
description="✨ Builder and optimizer for Maple Mono",
)
parser.add_argument(
"-v",
"--version",
action="version",
2024-12-14 02:47:04 +00:00
version=f"Maple Mono Builder v{FONT_VERSION}",
)
parser.add_argument(
2024-10-27 08:25:43 +00:00
"-d",
"--dry",
2024-10-27 08:25:43 +00:00
dest="dry",
action="store_true",
help="Output config and exit",
)
2024-10-13 11:14:59 +00:00
parser.add_argument(
"--debug",
action="store_true",
2024-12-04 01:52:32 +00:00
help="Add `Debug` suffix to family name, skip optimization",
)
feature_group = parser.add_argument_group("Feature Options")
feature_group.add_argument(
2024-10-27 08:25:43 +00:00
"-n",
2024-10-13 11:14:59 +00:00
"--normal",
2024-10-27 08:25:43 +00:00
dest="normal",
2024-10-13 11:14:59 +00:00
action="store_true",
help="Use normal preset, just like `JetBrains Mono` with slashed zero",
2024-10-27 08:25:43 +00:00
)
feature_group.add_argument(
"--feat",
type=lambda x: x.strip().split(","),
2024-11-23 12:04:05 +00:00
help="Freeze font features, splited by `,` (e.g. `--feat zero,cv01,ss07,ss08`). No effect on variable format",
)
2024-12-27 04:53:53 +00:00
feature_group.add_argument(
"--apply-fea-file",
default=None,
action="store_true",
help="Load feature file from `source/features/{regular,italic}.fea` to variable font",
)
hint_group = feature_group.add_mutually_exclusive_group()
hint_group.add_argument(
"--hinted",
2024-10-27 08:25:43 +00:00
dest="hinted",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-13 11:14:59 +00:00
action="store_true",
2025-02-19 03:22:40 +00:00
help="Use hinted font as base font in NF / CN / NF-CN (default)",
2024-10-13 11:14:59 +00:00
)
hint_group.add_argument(
"--no-hinted",
2024-10-27 08:25:43 +00:00
dest="hinted",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_false",
2025-02-18 13:42:52 +00:00
help="Use unhinted font as base font in NF / CN / NF-CN",
2024-10-27 08:25:43 +00:00
)
liga_group = feature_group.add_mutually_exclusive_group()
liga_group.add_argument(
2024-10-27 08:25:43 +00:00
"--liga",
dest="liga",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-13 11:14:59 +00:00
action="store_true",
2025-02-19 03:22:40 +00:00
help="Preserve all the ligatures (default)",
2024-10-13 11:14:59 +00:00
)
liga_group.add_argument(
"--no-liga",
2024-10-27 08:25:43 +00:00
dest="liga",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_false",
help="Remove all the ligatures",
2024-10-13 11:14:59 +00:00
)
feature_group.add_argument(
"--cn-narrow",
action="store_true",
help="Make CN characters narrow (experimental)",
)
build_group = parser.add_argument_group("Build Options")
nf_group = build_group.add_mutually_exclusive_group()
nf_group.add_argument(
2024-10-27 08:25:43 +00:00
"--nerd-font",
dest="nerd_font",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_true",
2025-02-19 03:22:40 +00:00
help="Build Nerd-Font version (default)",
2024-10-27 08:25:43 +00:00
)
nf_group.add_argument(
2024-10-27 08:25:43 +00:00
"--no-nerd-font",
dest="nerd_font",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_false",
help="Do not build Nerd-Font version",
2024-10-27 08:25:43 +00:00
)
cn_group = build_group.add_mutually_exclusive_group()
cn_group.add_argument(
2024-10-27 08:25:43 +00:00
"--cn",
dest="cn",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_true",
help="Build Chinese version",
2024-10-27 08:25:43 +00:00
)
cn_group.add_argument(
2024-10-27 08:25:43 +00:00
"--no-cn",
dest="cn",
2024-10-27 08:36:38 +00:00
default=None,
2024-10-27 08:25:43 +00:00
action="store_false",
2025-02-19 03:22:40 +00:00
help="Do not build Chinese version (default)",
2024-10-27 08:25:43 +00:00
)
build_group.add_argument(
"--cn-both",
action="store_true",
help="Build both `Maple Mono CN` and `Maple Mono NF CN`. Nerd-Font version must be enabled",
)
2024-12-05 13:13:48 +00:00
build_group.add_argument(
"--ttf-only",
action="store_true",
2025-02-18 13:42:52 +00:00
help="Only build TTF format",
2024-12-05 13:13:48 +00:00
)
build_group.add_argument(
"--cache",
action="store_true",
2024-11-23 12:04:05 +00:00
help="Reuse font cache of TTF, OTF and Woff2 formats",
2024-10-13 11:14:59 +00:00
)
build_group.add_argument(
"--cn-rebuild",
action="store_true",
help="Reinstantiate CN base font",
)
build_group.add_argument(
2024-12-04 02:23:46 +00:00
"--archive",
2024-10-13 11:14:59 +00:00
action="store_true",
help="Build font archives with config and license. If has `--cache` flag, only archive Nerd-Font and CN formats",
)
2024-08-31 06:34:53 +00:00
2024-10-13 11:14:59 +00:00
return parser.parse_args()
2024-08-31 06:34:53 +00:00
2024-10-13 13:49:20 +00:00
2024-10-13 11:14:59 +00:00
# =========================================================================================
class FontConfig:
def __init__(self, args):
2024-12-04 02:23:46 +00:00
self.archive = None
2024-10-13 11:14:59 +00:00
self.use_cn_both = None
2024-12-05 13:13:48 +00:00
self.ttf_only = None
2024-12-04 01:52:32 +00:00
self.debug = None
2024-12-27 04:53:53 +00:00
self.apply_fea_file = None
2024-10-13 11:14:59 +00:00
# the number of parallel tasks
# when run in codespace, this will be 1
self.pool_size = 1 if not getenv("CODESPACE_NAME") else 4
# font family name
self.family_name = "Maple Mono"
self.family_name_compact = "MapleMono"
# whether to use hinted ttf as base font
self.use_hinted = True
# whether to enable ligature
self.enable_liga = True
2024-10-13 11:14:59 +00:00
self.feature_freeze = {
"cv01": "ignore",
"cv02": "ignore",
"cv03": "ignore",
"cv04": "ignore",
"cv31": "ignore",
"cv32": "ignore",
"cv33": "ignore",
"cv34": "ignore",
"cv35": "ignore",
"cv36": "ignore",
"cv96": "ignore",
"cv97": "ignore",
2024-10-13 11:14:59 +00:00
"cv98": "ignore",
"cv99": "ignore",
"ss01": "ignore",
"ss02": "ignore",
"ss03": "ignore",
"ss04": "ignore",
"ss05": "ignore",
"ss06": "ignore",
"ss07": "ignore",
"ss08": "ignore",
2024-10-13 11:14:59 +00:00
"zero": "ignore",
}
# Nerd-Font settings
2024-10-13 11:14:59 +00:00
self.nerd_font = {
# whether to enable Nerd-Font
2024-10-13 11:14:59 +00:00
"enable": True,
# target version of Nerd-Font if font-patcher not exists
2024-10-13 11:14:59 +00:00
"version": "3.2.1",
# whether to make icon width fixed
"mono": False,
# prefer to use Font Patcher instead of using prebuild NerdFont base font
# if you want to custom build Nerd-Font using font-patcher, you need to set this to True
2024-10-13 11:14:59 +00:00
"use_font_patcher": False,
# symbol Fonts settings.
# default args: ["--complete"]
# if not, will use font-patcher to generate fonts
# full args: https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-file#font-patcher
"glyphs": ["--complete"],
# extra args for font-patcher
# default args: ["-l", "--careful", "--outputdir", output_nf]
# if "mono" is set to True, "--mono" will be added
# full args: https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-file#font-patcher
"extra_args": [],
}
# chinese font settings
self.cn = {
# whether to build Chinese fonts
# skip if Chinese base fonts are not founded
"enable": False,
# whether to patch Nerd-Font
2024-10-13 11:14:59 +00:00
"with_nerd_font": True,
# fix design language and supported languages
"fix_meta_table": True,
# whether to clean instantiated base CN fonts
"clean_cache": False,
# whether to narrow CN glyphs
"narrow": False,
# whether to hint CN font (will increase about 33% size)
"use_hinted": False,
# whether to use pre-instantiated static CN font as base font
"use_static_base_font": True,
2024-10-13 11:14:59 +00:00
}
2025-02-19 03:13:15 +00:00
self.glyph_width = 600
self.glyph_width_cn_narrow = 1000
2024-12-29 14:05:45 +00:00
self.__load_config(args.normal)
self.__load_args(args)
2024-10-13 11:14:59 +00:00
2024-12-14 11:24:35 +00:00
ver = FONT_VERSION
self.beta = None
2024-12-14 11:24:35 +00:00
if "-" in FONT_VERSION:
ver, beta = FONT_VERSION.split("-")
self.beta = beta
major, minor = ver.split(".")
2025-01-22 02:58:09 +00:00
if major.startswith("v"):
2025-01-22 02:58:09 +00:00
major = major[1:]
2024-12-14 11:24:35 +00:00
self.version_str = f"Version {major}.{minor:03}"
2024-12-29 14:05:45 +00:00
def __load_config(self, use_normal):
2024-10-13 11:14:59 +00:00
try:
config_file_path = (
2024-12-29 14:05:45 +00:00
"./source/preset-normal.json" if use_normal else "config.json"
)
with open(config_file_path, "r") as f:
2024-10-13 11:14:59 +00:00
data = json.load(f)
for prop in [
"family_name",
"use_hinted",
"enable_liga",
"pool_size",
"github_mirror",
"feature_freeze",
"nerd_font",
"cn",
]:
if prop in data:
val = data[prop]
setattr(
self,
prop,
val
if type(val) is not dict
else {**getattr(self, prop), **val},
)
2024-10-13 11:14:59 +00:00
2025-02-22 12:12:46 +00:00
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}")
2024-12-29 14:05:45 +00:00
exit(1)
def __load_args(self, args):
self.archive = args.archive
self.use_cn_both = args.cn_both
self.debug = args.debug
2024-10-13 11:14:59 +00:00
2024-12-29 14:05:45 +00:00
if "font_forge_bin" not in self.nerd_font:
self.nerd_font["font_forge_bin"] = get_font_forge_bin()
2024-12-29 14:05:45 +00:00
if args.feat is not None:
for f in args.feat:
if f in self.feature_freeze:
self.feature_freeze[f] = "enable"
2024-10-27 08:25:43 +00:00
2024-12-29 14:05:45 +00:00
if args.hinted is not None:
self.use_hinted = args.hinted
2024-10-27 08:25:43 +00:00
2024-12-29 14:05:45 +00:00
if args.liga is not None:
self.enable_liga = args.liga
2024-10-13 11:14:59 +00:00
2024-12-29 14:05:45 +00:00
if args.nerd_font is not None:
self.nerd_font["enable"] = args.nerd_font
2024-10-13 11:14:59 +00:00
2024-12-29 14:05:45 +00:00
if args.cn is not None:
self.cn["enable"] = args.cn
2024-10-13 11:14:59 +00:00
2024-12-29 14:05:45 +00:00
if args.cn_narrow:
self.cn["narrow"] = True
2024-12-05 13:13:48 +00:00
2024-12-29 14:05:45 +00:00
if args.ttf_only:
self.ttf_only = True
2024-12-27 04:53:53 +00:00
2024-12-29 14:05:45 +00:00
if args.apply_fea_file:
self.apply_fea_file = True
if args.cn_rebuild:
self.cn["clean_cache"] = True
self.cn["use_static_base_font"] = False
2024-12-29 14:05:45 +00:00
name_arr = [word.capitalize() for word in self.family_name.split(" ")]
if not self.enable_liga:
name_arr.append("NL")
if self.debug:
name_arr.append("Debug")
self.family_name = " ".join(name_arr)
self.family_name_compact = "".join(name_arr)
2024-10-13 11:14:59 +00:00
2024-11-23 11:26:41 +00:00
self.freeze_config_str = get_freeze_config_str(
self.feature_freeze, self.enable_liga
)
def should_build_nf_cn(self) -> bool:
return self.cn["with_nerd_font"] and self.nerd_font["enable"]
def toggle_nf_cn_config(self) -> bool:
if not self.nerd_font["enable"]:
print("❗Nerd-Font version is disabled, skip toggle.")
return False
self.cn["with_nerd_font"] = not self.cn["with_nerd_font"]
return True
2025-02-19 03:13:15 +00:00
def get_valid_glyph_width_list(self, cn=False):
if cn:
return [
0,
self.glyph_width,
self.glyph_width_cn_narrow
if self.cn["narrow"]
else 2 * self.glyph_width,
]
else:
return [0, self.glyph_width]
2024-07-28 05:05:01 +00:00
2024-10-13 11:14:59 +00:00
class BuildOption:
def __init__(self, config: FontConfig):
# paths
self.src_dir = "source"
self.output_dir = "fonts"
self.output_otf = joinPaths(self.output_dir, "OTF")
self.output_ttf = joinPaths(self.output_dir, "TTF")
self.output_ttf_hinted = joinPaths(self.output_dir, "TTF-AutoHint")
self.output_variable = joinPaths(self.output_dir, "Variable")
self.output_woff2 = joinPaths(self.output_dir, "Woff2")
self.output_nf = joinPaths(self.output_dir, "NF")
self.ttf_base_dir = joinPaths(
2024-10-13 11:14:59 +00:00
self.output_dir, "TTF-AutoHint" if config.use_hinted else "TTF"
)
2024-08-02 01:22:52 +00:00
2024-12-03 10:02:41 +00:00
self.cn_variable_dir = f"{self.src_dir}/cn"
self.cn_static_dir = f"{self.cn_variable_dir}/static"
2024-10-13 11:14:59 +00:00
self.cn_base_font_dir = None
self.cn_suffix = None
self.cn_suffix_compact = None
self.output_cn = None
# In these subfamilies:
# - NameID1 should be the family name
# - NameID2 should be the subfamily name
# - NameID16 and NameID17 should be removed
# Other subfamilies:
# - NameID1 should be the family name, append with subfamily name without "Italic"
# - NameID2 should be the "Regular" or "Italic"
# - NameID16 should be the family name
# - NameID17 should be the subfamily name
# https://github.com/subframe7536/maple-font/issues/182
# https://github.com/subframe7536/maple-font/issues/183
#
# same as `ftcli assistant commit . --ls 400 700`
# https://github.com/ftCLI/FoundryTools-CLI/issues/166#issuecomment-2095756721
self.skip_subfamily_list = ["Regular", "Bold", "Italic", "BoldItalic"]
2024-12-08 03:36:16 +00:00
self.is_nf_built = False
self.is_cn_built = False
2024-12-29 14:05:45 +00:00
self.has_cache = (
2025-02-22 12:13:35 +00:00
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)
2024-12-29 14:05:45 +00:00
)
self.github_mirror = environ.get("GITHUB", "github.com")
2024-10-13 11:14:59 +00:00
def load_cn_dir_and_suffix(self, with_nerd_font: bool) -> None:
2024-10-13 11:14:59 +00:00
if with_nerd_font:
self.cn_base_font_dir = self.output_nf
self.cn_suffix = "NF CN"
self.cn_suffix_compact = "NF-CN"
else:
self.cn_base_font_dir = joinPaths(self.output_dir, "TTF")
2024-10-13 11:14:59 +00:00
self.cn_suffix = self.cn_suffix_compact = "CN"
self.output_cn = joinPaths(self.output_dir, self.cn_suffix_compact)
2024-10-13 11:14:59 +00:00
def should_use_font_patcher(self, config: FontConfig) -> bool:
if not (
len(config.nerd_font["extra_args"]) > 0
or config.nerd_font["use_font_patcher"]
or config.nerd_font["glyphs"] != ["--complete"]
):
return False
if check_font_patcher(
version=config.nerd_font["version"],
github_mirror=self.github_mirror,
) and not path.exists(config.nerd_font["font_forge_bin"]):
print(
f"FontForge bin({config.nerd_font['font_forge_bin']}) not found. Use prebuild Nerd-Font base font instead."
)
return False
return True
2024-12-03 10:02:41 +00:00
def should_build_cn(self, config: FontConfig) -> bool:
if not config.cn["enable"] and not config.use_cn_both:
print(
'\nNo `"cn.enable": true` in config.json or no `--cn` / `--cn-both` in argv. Skip CN build.'
)
2024-12-03 10:02:41 +00:00
return False
2025-02-22 12:13:35 +00:00
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,
)
2025-02-22 12:13:35 +00:00
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)
2025-02-22 12:13:35 +00:00
if path.exists(self.cn_static_dir) and len(listdir(self.cn_static_dir)) == 16:
return True
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,
):
return True
# Try using variable fonts if static fonts aren't available
if (
2025-02-22 12:13:35 +00:00
path.exists(self.cn_variable_dir)
and len(listdir(self.cn_variable_dir)) == 2
):
2025-02-22 12:13:35 +00:00
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
# 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
print("\nCN base fonts don't exist. Skip CN build.")
return False
2024-12-03 10:02:41 +00:00
2025-02-22 12:13:35 +00:00
def __check_cache_dir(self, cache_dir: str, count: int = 16) -> bool:
2025-01-21 03:38:11 +00:00
if not path.isdir(cache_dir):
return False
2025-02-22 12:13:35 +00:00
return len(listdir(cache_dir)) == count
2024-10-13 11:14:59 +00:00
def handle_ligatures(
font: TTFont, enable_ligature: bool, freeze_config: dict[str, str]
):
2024-10-01 07:10:18 +00:00
"""
whether to enable ligatures and freeze font features
2024-10-01 07:10:18 +00:00
"""
2024-10-01 07:10:18 +00:00
freeze_feature(
font=font,
calt=enable_ligature,
moving_rules=["ss03", "ss07", "ss08"],
2024-10-13 11:14:59 +00:00
config=freeze_config,
2024-10-01 07:10:18 +00:00
)
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_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_build(
pool_size=pool_size,
fn=partial(optimize_cn_base, base_dir=cn_static_dir),
dir=cn_static_dir,
)
2024-12-29 14:05:45 +00:00
def parse_style_name(style_name_compact: str, skip_subfamily_list: list[str]):
2024-05-07 05:11:27 +00:00
is_italic = style_name_compact.endswith("Italic")
2024-05-07 00:55:25 +00:00
2024-05-07 05:11:27 +00:00
_style_name = style_name_compact
if is_italic and style_name_compact[0] != "I":
_style_name = style_name_compact[:-6] + " Italic"
2024-05-07 00:55:25 +00:00
2024-05-07 05:11:27 +00:00
if style_name_compact in skip_subfamily_list:
2024-12-29 14:05:45 +00:00
return "", _style_name, _style_name, True
2024-05-07 00:55:25 +00:00
else:
return (
2024-05-07 05:11:27 +00:00
" " + style_name_compact.replace("Italic", ""),
2024-05-07 00:55:25 +00:00
"Italic" if is_italic else "Regular",
_style_name,
2024-12-29 14:05:45 +00:00
False,
2024-05-07 00:55:25 +00:00
)
def fix_cn_cv(font: TTFont):
2024-05-22 00:54:59 +00:00
gsub_table = font["GSUB"].table
config = {
"cv96": ["quoteleft", "quoteright", "quotedblleft", "quotedblright"],
"cv97": ["ellipsis"],
"cv98": ["emdash"],
}
2024-09-01 07:13:14 +00:00
for feature_record in gsub_table.FeatureList.FeatureRecord:
if feature_record.FeatureTag in config:
sub_table = gsub_table.LookupList.Lookup[
feature_record.Feature.LookupListIndex[0]
].SubTable[0]
sub_table.mapping = {
2025-01-07 12:57:50 +00:00
value: f"{value}.full" for value in config[feature_record.FeatureTag]
}
2024-09-01 07:13:14 +00:00
def remove_locl(font: TTFont):
gsub = font["GSUB"]
features_to_remove = []
for feature in gsub.table.FeatureList.FeatureRecord:
feature_tag = feature.FeatureTag
if feature_tag == "locl":
features_to_remove.append(feature)
for feature in features_to_remove:
gsub.table.FeatureList.FeatureRecord.remove(feature)
2024-05-22 00:54:59 +00:00
def drop_mac_names(dir: str):
run(f"ftcli name del-mac-names -r {dir}")
2024-12-27 03:13:48 +00:00
def get_new_name_from_map(old_name: str, map: dict[str, str]):
new_name = map.get(old_name)
if not new_name:
arr = re.split(r"[\._]", old_name, maxsplit=2)
if map.get(arr[0]):
new_name = map.get(arr[0]) + old_name[len(arr[0]) :]
return new_name
2024-12-14 11:24:35 +00:00
def rename_glyph_name(
font: TTFont,
2024-12-26 16:07:50 +00:00
map: dict[str, str],
2024-12-14 11:24:35 +00:00
post_extra_names: bool = True,
):
print("Rename glyph names")
2024-12-10 06:42:34 +00:00
glyph_names = font.getGlyphOrder()
2024-12-27 03:13:48 +00:00
extra_names = font["post"].extraNames
2024-12-10 06:42:34 +00:00
modified = False
2025-01-21 03:38:11 +00:00
merged_map = {
**map,
**{
"uni2047.liga": "question_question.liga",
"dotlessi": "idotless",
"f_f": "f_f.liga",
},
2024-12-27 04:53:53 +00:00
}
2025-01-21 03:38:11 +00:00
2024-12-10 06:42:34 +00:00
for i, _ in enumerate(glyph_names):
2024-12-27 03:13:48 +00:00
old_name = str(glyph_names[i])
2024-12-27 02:17:56 +00:00
2025-01-21 03:38:11 +00:00
new_name = get_new_name_from_map(old_name, merged_map)
2024-12-27 03:13:48 +00:00
if not new_name or new_name == old_name:
2024-12-27 02:17:56 +00:00
continue
# print(f"[Rename] {old_name} -> {new_name}")
2024-12-27 03:13:48 +00:00
glyph_names[i] = new_name
2024-12-27 02:17:56 +00:00
modified = True
2024-12-27 03:13:48 +00:00
if post_extra_names and old_name in extra_names:
extra_names[extra_names.index(old_name)] = new_name
2024-12-26 16:07:50 +00:00
2024-12-10 06:42:34 +00:00
if modified:
font.setGlyphOrder(glyph_names)
2024-10-13 11:14:59 +00:00
def get_unique_identifier(
2024-12-14 11:24:35 +00:00
font_config: FontConfig,
postscript_name: str,
narrow: bool = False,
ignore_suffix: bool = False,
2024-10-13 11:14:59 +00:00
) -> str:
2024-12-27 05:47:39 +00:00
if ignore_suffix:
suffix = ""
else:
suffix = font_config.freeze_config_str
if "CN" in postscript_name and narrow:
suffix += "Narrow;"
2024-12-14 11:24:35 +00:00
2024-12-27 05:47:39 +00:00
if "NF" in postscript_name:
nf_ver = font_config.nerd_font["version"]
suffix = f"NF{nf_ver};{suffix}"
2024-12-26 16:07:50 +00:00
beta_str = f"-{font_config.beta}" if font_config.beta else ""
2024-12-14 11:24:35 +00:00
return f"{font_config.version_str}{beta_str};SUBF;{postscript_name};2024;FL830;{suffix}"
2024-07-27 14:28:49 +00:00
def change_glyph_width(font: TTFont, match_width: int, target_width: int):
font["hhea"].advanceWidthMax = target_width
for name in font.getGlyphOrder():
glyph = font["glyf"][name]
width, lsb = font["hmtx"][name]
2024-09-20 08:23:43 +00:00
if width != match_width:
continue
if glyph.numberOfContours == 0:
font["hmtx"][name] = (target_width, lsb)
continue
delta = round((target_width - width) / 2)
glyph.coordinates.translate((delta, 0))
glyph.xMin, glyph.yMin, glyph.xMax, glyph.yMax = (
glyph.coordinates.calcIntBounds()
)
font["hmtx"][name] = (target_width, lsb + delta)
2024-12-29 14:05:45 +00:00
def update_font_names(
font: TTFont,
2025-01-03 02:50:45 +00:00
family_name: str, # NameID 1
style_name: str, # NameID 2
unique_identifier: str, # NameID 3
full_name: str, # NameID 4
version_str: str, # NameID 5
postscript_name: str, # NameID 6
2024-12-29 14:05:45 +00:00
is_skip_subfamily: bool,
2025-01-03 02:50:45 +00:00
preferred_family_name: str = None, # NameID 16
preferred_style_name: str = None, # NameID 17
2024-12-29 14:05:45 +00:00
):
set_font_name(font, family_name, 1)
set_font_name(font, style_name, 2)
set_font_name(font, unique_identifier, 3)
set_font_name(font, full_name, 4)
set_font_name(font, version_str, 5)
set_font_name(font, postscript_name, 6)
2025-01-03 02:50:45 +00:00
if not is_skip_subfamily and preferred_family_name and preferred_style_name:
set_font_name(font, preferred_family_name, 16)
set_font_name(font, preferred_style_name, 17)
2024-12-29 14:05:45 +00:00
def add_gasp(font: TTFont):
print("Fix GASP table")
gasp = newTable("gasp")
gasp.gaspRange = {65535: 15}
font["gasp"] = gasp
2024-10-13 11:14:59 +00:00
def build_mono(f: str, font_config: FontConfig, build_option: BuildOption):
print(f"👉 Minimal version for {f}")
2024-12-29 14:05:45 +00:00
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}")
2024-12-29 14:05:45 +00:00
font = TTFont(source_path)
style_compact = f.split("-")[-1].split(".")[0]
2024-12-29 14:05:45 +00:00
style_with_prefix_space, style_in_2, style_in_17, is_skip_subfamily = (
parse_style_name(
style_name_compact=style_compact,
skip_subfamily_list=build_option.skip_subfamily_list,
)
)
2024-10-13 11:14:59 +00:00
postscript_name = f"{font_config.family_name_compact}-{style_compact}"
2024-12-29 14:05:45 +00:00
update_font_names(
font=font,
family_name=font_config.family_name + style_with_prefix_space,
style_name=style_in_2,
full_name=f"{font_config.family_name} {style_in_17}",
version_str=font_config.version_str,
postscript_name=postscript_name,
unique_identifier=get_unique_identifier(
2024-12-14 11:24:35 +00:00
font_config=font_config,
postscript_name=postscript_name,
),
2024-12-29 14:05:45 +00:00
is_skip_subfamily=is_skip_subfamily,
2025-01-03 02:50:45 +00:00
preferred_family_name=font_config.family_name,
preferred_style_name=style_in_17,
)
2024-05-07 05:11:27 +00:00
2024-05-07 01:51:53 +00:00
# https://github.com/ftCLI/FoundryTools-CLI/issues/166#issuecomment-2095433585
if style_with_prefix_space == " Thin":
2024-05-07 00:55:25 +00:00
font["OS/2"].usWeightClass = 250
elif style_with_prefix_space == " ExtraLight":
2024-05-07 00:55:25 +00:00
font["OS/2"].usWeightClass = 275
2024-03-02 14:59:59 +00:00
handle_ligatures(
font=font,
enable_ligature=font_config.enable_liga,
freeze_config=font_config.feature_freeze,
)
2024-07-27 14:28:49 +00:00
2025-02-19 16:02:39 +00:00
verify_glyph_width(
font=font,
expect_widths=font_config.get_valid_glyph_width_list(),
file_name=postscript_name,
)
2024-12-29 14:05:45 +00:00
remove(source_path)
target_path = joinPaths(build_option.output_ttf, f"{postscript_name}.ttf")
font.save(target_path)
2024-03-17 09:54:43 +00:00
font.close()
2024-08-31 06:34:53 +00:00
2025-02-18 13:42:52 +00:00
# Autohint version
print(f"Auto hint {postscript_name}.ttf")
run(f"ftcli ttf autohint {target_path} -out {build_option.output_ttf_hinted}")
2024-12-05 13:13:48 +00:00
if font_config.ttf_only:
return
2025-02-18 13:42:52 +00:00
# Woff2 version
print(f"Convert {postscript_name}.ttf to WOFF2")
2025-01-03 02:50:45 +00:00
run(
f"ftcli converter ft2wf {target_path} -out {build_option.output_woff2} -f woff2"
)
2024-03-04 02:32:52 +00:00
2025-02-18 13:42:52 +00:00
# OTF version
_otf_path = joinPaths(
2024-12-29 14:05:45 +00:00
build_option.output_otf, path.basename(target_path).replace(".ttf", ".otf")
)
print(f"Convert {postscript_name}.ttf to OTF")
2025-01-03 02:50:45 +00:00
run(
f"ftcli converter ttf2otf --silent {target_path} -out {build_option.output_otf}"
)
2024-12-04 01:52:32 +00:00
if not font_config.debug:
print(f"Optimize {postscript_name}.otf")
run(f"ftcli otf fix-contours --silent {_otf_path}")
2024-12-04 01:52:32 +00:00
run(f"ftcli otf fix-version {_otf_path}")
2024-03-04 02:32:52 +00:00
2024-10-13 15:29:39 +00:00
def build_nf_by_prebuild_nerd_font(
font_basename: str, font_config: FontConfig, build_option: BuildOption
) -> TTFont:
merger = Merger()
return merger.merge(
[
joinPaths(build_option.ttf_base_dir, font_basename),
2024-10-13 15:29:39 +00:00
f"{build_option.src_dir}/MapleMono-NF-Base{'-Mono' if font_config.nerd_font['mono'] else ''}.ttf",
]
)
2024-10-13 11:14:59 +00:00
2024-10-13 15:29:39 +00:00
def build_nf_by_font_patcher(
font_basename: str, font_config: FontConfig, build_option: BuildOption
) -> TTFont:
"""
full args: https://github.com/ryanoasis/nerd-fonts?tab=readme-ov-file#font-patcher
"""
_nf_args = [
font_config.nerd_font["font_forge_bin"],
"FontPatcher/font-patcher",
"-l",
"--careful",
"--outputdir",
build_option.output_nf,
] + font_config.nerd_font["glyphs"]
2024-10-13 11:14:59 +00:00
2024-10-13 15:29:39 +00:00
if font_config.nerd_font["mono"]:
_nf_args += ["--mono"]
2024-10-13 11:14:59 +00:00
2024-10-13 15:29:39 +00:00
_nf_args += font_config.nerd_font["extra_args"]
run(_nf_args + [joinPaths(build_option.ttf_base_dir, font_basename)], log=True)
nf_file_name = "NerdFont"
2024-10-13 11:14:59 +00:00
if font_config.nerd_font["mono"]:
nf_file_name += "Mono"
_path = joinPaths(
2024-10-13 15:29:39 +00:00
build_option.output_nf, font_basename.replace("-", f"{nf_file_name}-")
)
font = TTFont(_path)
remove(_path)
return font
2024-10-13 15:29:39 +00:00
def build_nf(
f: str,
get_ttfont: Callable[[str, FontConfig, BuildOption], TTFont],
2024-10-13 15:29:39 +00:00
font_config: FontConfig,
build_option: BuildOption,
):
print(f"👉 NerdFont version for {f}")
2024-10-13 11:14:59 +00:00
makedirs(build_option.output_nf, exist_ok=True)
nf_font = get_ttfont(f, font_config, build_option)
2024-03-04 02:32:52 +00:00
2024-03-17 09:54:43 +00:00
# format font name
style_compact_nf = f.split("-")[-1].split(".")[0]
2024-03-16 09:03:23 +00:00
2024-12-29 14:05:45 +00:00
style_nf_with_prefix_space, style_in_2, style_in_17, is_skip_sufamily = (
parse_style_name(
style_name_compact=style_compact_nf,
skip_subfamily_list=build_option.skip_subfamily_list,
)
)
2024-03-04 02:32:52 +00:00
2024-10-13 11:14:59 +00:00
postscript_name = f"{font_config.family_name_compact}-NF-{style_compact_nf}"
2024-12-29 14:05:45 +00:00
update_font_names(
font=nf_font,
family_name=f"{font_config.family_name} NF{style_nf_with_prefix_space}",
style_name=style_in_2,
full_name=f"{font_config.family_name} NF {style_in_17}",
version_str=font_config.version_str,
postscript_name=postscript_name,
unique_identifier=get_unique_identifier(
2024-12-14 11:24:35 +00:00
font_config=font_config,
postscript_name=postscript_name,
),
2024-12-29 14:05:45 +00:00
is_skip_subfamily=is_skip_sufamily,
2025-01-03 02:50:45 +00:00
preferred_family_name=f"{font_config.family_name} NF",
preferred_style_name=style_in_17,
)
2025-02-19 16:02:39 +00:00
verify_glyph_width(
font=nf_font,
expect_widths=font_config.get_valid_glyph_width_list(),
file_name=postscript_name,
)
2024-05-07 05:11:27 +00:00
2024-12-29 14:05:45 +00:00
target_path = joinPaths(
2024-10-13 11:14:59 +00:00
build_option.output_nf,
2025-02-19 16:02:39 +00:00
f"{postscript_name}.ttf",
2024-10-13 11:14:59 +00:00
)
2024-12-29 14:05:45 +00:00
nf_font.save(target_path)
2024-03-17 09:54:43 +00:00
nf_font.close()
2024-03-04 02:32:52 +00:00
2024-10-13 11:14:59 +00:00
def build_cn(f: str, font_config: FontConfig, build_option: BuildOption):
style_compact_cn = f.split("-")[-1].split(".")[0]
print(f"👉 {build_option.cn_suffix_compact} version for {f}")
2024-03-16 09:03:23 +00:00
2024-03-17 09:54:43 +00:00
merger = Merger()
2024-12-14 11:24:35 +00:00
cn_font = merger.merge(
2024-03-17 09:54:43 +00:00
[
joinPaths(build_option.cn_base_font_dir, f),
joinPaths(
2024-12-03 10:02:41 +00:00
build_option.cn_static_dir, f"MapleMonoCN-{style_compact_cn}.ttf"
2024-10-13 11:14:59 +00:00
),
2024-03-17 09:54:43 +00:00
]
)
2024-12-29 14:05:45 +00:00
style_cn_with_prefix_space, style_in_2, style_in_17, is_skip_subfamily = (
parse_style_name(
style_name_compact=style_compact_cn,
skip_subfamily_list=build_option.skip_subfamily_list,
)
)
2024-10-13 11:14:59 +00:00
postscript_name = f"{font_config.family_name_compact}-{build_option.cn_suffix_compact}-{style_compact_cn}"
2024-12-29 14:05:45 +00:00
update_font_names(
font=cn_font,
family_name=f"{font_config.family_name} {build_option.cn_suffix}{style_cn_with_prefix_space}",
style_name=style_in_2,
full_name=f"{font_config.family_name} {build_option.cn_suffix} {style_in_17}",
version_str=font_config.version_str,
postscript_name=postscript_name,
unique_identifier=get_unique_identifier(
2024-12-14 11:24:35 +00:00
font_config=font_config,
postscript_name=postscript_name,
2024-10-13 11:14:59 +00:00
narrow=font_config.cn["narrow"],
),
2024-12-29 14:05:45 +00:00
is_skip_subfamily=is_skip_subfamily,
2025-01-03 02:50:45 +00:00
preferred_family_name=f"{font_config.family_name} {build_option.cn_suffix}",
preferred_style_name=style_in_17,
2024-03-17 09:54:43 +00:00
)
2024-05-07 05:11:27 +00:00
2024-12-14 11:24:35 +00:00
cn_font["OS/2"].xAvgCharWidth = 600
2024-05-22 00:54:59 +00:00
# https://github.com/subframe7536/maple-font/issues/188
# https://github.com/subframe7536/maple-font/issues/313
fix_cn_cv(cn_font)
2024-05-22 00:54:59 +00:00
handle_ligatures(
2024-12-14 11:24:35 +00:00
font=cn_font,
enable_ligature=font_config.enable_liga,
freeze_config=font_config.feature_freeze,
)
2024-07-28 05:05:01 +00:00
2024-10-13 11:14:59 +00:00
if font_config.cn["narrow"]:
2025-02-19 03:13:15 +00:00
change_glyph_width(
font=cn_font,
match_width=2 * font_config.glyph_width,
target_width=font_config.glyph_width_cn_narrow,
)
2024-09-01 07:13:14 +00:00
# https://github.com/subframe7536/maple-font/issues/239
# already removed at merge time
2024-09-03 11:44:00 +00:00
# remove_locl(font)
2024-09-01 07:13:14 +00:00
2024-10-13 11:14:59 +00:00
if font_config.cn["fix_meta_table"]:
2024-03-23 06:39:53 +00:00
# add code page, Latin / Japanese / Simplify Chinese / Traditional Chinese
2024-12-14 11:24:35 +00:00
cn_font["OS/2"].ulCodePageRange1 = 1 << 0 | 1 << 17 | 1 << 18 | 1 << 20
2024-03-23 06:39:53 +00:00
# fix meta table, https://learn.microsoft.com/en-us/typography/opentype/spec/meta
meta = newTable("meta")
meta.data = {
"dlng": "Latn, Hans, Hant, Jpan",
"slng": "Latn, Hans, Hant, Jpan",
}
2024-12-14 11:24:35 +00:00
cn_font["meta"] = meta
2025-02-19 16:02:39 +00:00
verify_glyph_width(
font=cn_font,
expect_widths=font_config.get_valid_glyph_width_list(True),
file_name=postscript_name,
)
2024-12-29 14:05:45 +00:00
target_path = joinPaths(
2024-10-13 11:14:59 +00:00
build_option.output_cn,
2025-02-19 16:02:39 +00:00
f"{postscript_name}.ttf",
2024-03-17 09:54:43 +00:00
)
2024-12-29 14:05:45 +00:00
cn_font.save(target_path)
2024-12-14 11:24:35 +00:00
cn_font.close()
2025-02-19 12:56:26 +00:00
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):
2025-02-19 11:51:35 +00:00
files = listdir(dir)
if pool_size <= 1:
for f in files:
fn(f)
2025-02-19 11:51:35 +00:00
return
2025-02-22 12:12:11 +00:00
with ProcessPoolExecutor(max_workers=pool_size) as executor:
futures = {
executor.submit(fn, f): f for f in files
}
2025-02-19 11:51:35 +00:00
2025-02-22 12:12:11 +00:00
for future in as_completed(futures):
future.result()
2024-12-03 10:02:41 +00:00
2024-03-17 09:54:43 +00:00
def main():
2024-10-13 11:14:59 +00:00
check_ftcli()
parsed_args = parse_args()
font_config = FontConfig(args=parsed_args)
2024-10-13 11:14:59 +00:00
build_option = BuildOption(font_config)
build_option.load_cn_dir_and_suffix(font_config.should_build_nf_cn())
2024-10-13 11:14:59 +00:00
if parsed_args.dry:
print("font_config:", json.dumps(font_config.__dict__, indent=4))
if not is_ci():
print("build_option:", json.dumps(build_option.__dict__, indent=4))
print("parsed_args:", json.dumps(parsed_args.__dict__, indent=4))
return
should_use_cache = parsed_args.cache
if not should_use_cache:
print("🧹 Clean cache...\n")
shutil.rmtree(build_option.output_dir, ignore_errors=True)
shutil.rmtree(build_option.output_woff2, ignore_errors=True)
2024-10-13 11:14:59 +00:00
makedirs(build_option.output_dir, exist_ok=True)
makedirs(build_option.output_variable, exist_ok=True)
2024-05-24 03:29:45 +00:00
start_time = time.time()
2025-01-23 08:29:21 +00:00
print("🚩 Start building ...\n")
2024-03-17 09:54:43 +00:00
# =========================================================================================
# =================================== Build basic =====================================
2024-03-17 09:54:43 +00:00
# =========================================================================================
2024-12-29 14:05:45 +00:00
if not should_use_cache or not build_option.has_cache:
input_files = [
2024-12-27 04:53:53 +00:00
joinPaths(build_option.src_dir, "MapleMono-Italic[wght]-VF.ttf"),
joinPaths(build_option.src_dir, "MapleMono[wght]-VF.ttf"),
]
for input_file in input_files:
font = TTFont(input_file)
basename = path.basename(input_file)
2025-01-23 08:29:21 +00:00
print(f"👉 Variable version for {basename}")
2024-12-10 06:42:34 +00:00
# fix auto rename by FontLab
2024-12-27 02:17:56 +00:00
rename_glyph_name(
font=font,
map=match_unicode_names(
input_file.replace(".ttf", ".glyphs").replace("-VF", "")
),
)
2024-12-10 06:42:34 +00:00
2024-12-27 04:53:53 +00:00
if font_config.apply_fea_file:
fea_path = joinPaths(
build_option.src_dir,
"features/italic.fea"
if "Italic" in input_file
else "features/regular.fea",
)
print(f"Apply feature file [{fea_path}] into [{basename}]")
2024-12-27 04:53:53 +00:00
addOpenTypeFeatures(
font,
fea_path,
)
2024-12-27 05:47:39 +00:00
set_font_name(
font,
get_unique_identifier(
font_config=font_config,
postscript_name=get_font_name(font, 6),
ignore_suffix=True,
2024-12-27 05:47:39 +00:00
),
3,
)
2025-02-19 03:13:15 +00:00
verify_glyph_width(
2025-02-19 07:32:57 +00:00
font=font,
expect_widths=font_config.get_valid_glyph_width_list(),
2025-02-19 16:02:39 +00:00
file_name=basename,
2025-02-19 03:13:15 +00:00
)
add_gasp(font)
font.save(
input_file.replace(
build_option.src_dir, build_option.output_variable
).replace("-VF", "")
)
print("\n✨ Instatiate and optimize fonts...\n")
2024-12-04 01:52:32 +00:00
print("Check and optimize variable fonts")
2024-12-04 01:52:32 +00:00
if not font_config.debug:
run(f"ftcli fix decompose-transformed {build_option.output_variable}")
run(f"ftcli fix italic-angle {build_option.output_variable}")
run(f"ftcli fix monospace {build_option.output_variable}")
print("Instantiate TTF")
run(
f"ftcli converter vf2i -out {build_option.output_ttf} {build_option.output_variable}"
)
_build_mono = partial(
build_mono, font_config=font_config, build_option=build_option
)
run_build(font_config.pool_size, _build_mono, build_option.output_ttf)
drop_mac_names(build_option.output_variable)
drop_mac_names(build_option.output_ttf)
2024-12-05 13:13:48 +00:00
if not font_config.ttf_only:
drop_mac_names(build_option.output_ttf_hinted)
drop_mac_names(build_option.output_otf)
drop_mac_names(build_option.output_woff2)
2024-03-17 09:54:43 +00:00
# =========================================================================================
# ==================================== Build NF =======================================
2024-03-17 09:54:43 +00:00
# =========================================================================================
if font_config.nerd_font["enable"]:
use_font_patcher = build_option.should_use_font_patcher(font_config)
get_ttfont = (
build_nf_by_font_patcher
if use_font_patcher
else build_nf_by_prebuild_nerd_font
2024-10-13 15:29:39 +00:00
)
2024-10-13 11:14:59 +00:00
_build_fn = partial(
build_nf,
get_ttfont=get_ttfont,
2024-10-13 11:14:59 +00:00
font_config=font_config,
build_option=build_option,
)
_version = font_config.nerd_font["version"]
print(
f"\n🔧 Patch Nerd-Font v{_version} using {'Font Patcher' if use_font_patcher else 'prebuild base font'}...\n"
)
run_build(font_config.pool_size, _build_fn, build_option.output_ttf)
drop_mac_names(build_option.output_ttf)
2024-12-08 03:36:16 +00:00
build_option.is_nf_built = True
2024-03-17 09:54:43 +00:00
# =========================================================================================
# ==================================== Build CN =======================================
2024-03-17 09:54:43 +00:00
# =========================================================================================
if build_option.should_build_cn(font_config):
def _build_cn():
print(
f"\n🔎 Build CN fonts {'with Nerd-Font' if font_config.should_build_nf_cn() else ''}...\n"
)
2024-10-13 11:14:59 +00:00
makedirs(build_option.output_cn, exist_ok=True)
fn = partial(build_cn, font_config=font_config, build_option=build_option)
run_build(font_config.pool_size, fn, build_option.cn_base_font_dir)
2024-03-17 09:54:43 +00:00
if font_config.cn["use_hinted"]:
print("Auto hinting all glyphs")
run(f"ftcli ttf autohint {build_option.output_cn}")
2024-09-14 07:51:38 +00:00
drop_mac_names(build_option.cn_base_font_dir)
_build_cn()
2024-05-24 03:29:45 +00:00
2025-02-19 08:05:31 +00:00
if font_config.use_cn_both and font_config.toggle_nf_cn_config():
build_option.load_cn_dir_and_suffix(font_config.should_build_nf_cn())
_build_cn()
2024-09-14 07:51:38 +00:00
2024-12-08 03:36:16 +00:00
build_option.is_cn_built = True
# =========================================================================================
# ================================== Write Config =====================================
# =========================================================================================
2024-07-09 08:48:01 +00:00
with open(
joinPaths(build_option.output_dir, "build-config.json"), "w", encoding="utf-8"
2024-07-09 08:48:01 +00:00
) as config_file:
2024-10-13 11:14:59 +00:00
result = {
2024-12-14 09:49:19 +00:00
"version": FONT_VERSION,
2024-10-13 11:14:59 +00:00
"family_name": font_config.family_name,
"use_hinted": font_config.use_hinted,
"ligature": font_config.enable_liga,
2024-10-13 11:14:59 +00:00
"feature_freeze": font_config.feature_freeze,
"nerd_font": font_config.nerd_font,
"cn": font_config.cn,
}
del result["nerd_font"]["font_forge_bin"]
2024-12-08 03:36:16 +00:00
result["nerd_font"]["enable"] = build_option.is_nf_built
result["cn"]["enable"] = build_option.is_cn_built
2024-08-02 08:10:11 +00:00
config_file.write(
json.dumps(
2024-10-13 11:14:59 +00:00
result,
2024-08-02 08:10:11 +00:00
indent=4,
)
)
2024-08-31 06:34:53 +00:00
# =========================================================================================
2024-12-05 13:13:48 +00:00
# ==================================== archive ========================================
2024-08-31 06:34:53 +00:00
# =========================================================================================
2024-12-04 02:23:46 +00:00
if font_config.archive:
print("\n🚀 archive files...\n")
2024-12-04 02:23:46 +00:00
# archive fonts
archive_dir_name = "archive"
archive_dir = joinPaths(build_option.output_dir, archive_dir_name)
makedirs(archive_dir, exist_ok=True)
2024-12-04 02:23:46 +00:00
# archive fonts
2024-10-13 11:14:59 +00:00
for f in listdir(build_option.output_dir):
2024-12-04 02:23:46 +00:00
if f == archive_dir_name or f.endswith(".json"):
2024-03-17 09:54:43 +00:00
continue
if should_use_cache and f not in ["CN", "NF", "NF-CN"]:
continue
sha256, zip_file_name_without_ext = compress_folder(
family_name_compact=font_config.family_name_compact,
suffix="-unhinted" if not font_config.use_hinted else "",
source_file_or_dir_path=joinPaths(build_option.output_dir, f),
build_config_path=joinPaths(
build_option.output_dir, "build-config.json"
),
2024-12-04 02:23:46 +00:00
target_parent_dir_path=archive_dir,
2024-10-13 11:14:59 +00:00
)
with open(
joinPaths(archive_dir, f"{zip_file_name_without_ext}.sha256"),
"w",
encoding="utf-8",
) as hash_file:
hash_file.write(sha256)
2024-12-04 02:23:46 +00:00
print(f"👉 archive: {f}")
# =========================================================================================
# ===================================== Finish ========================================
# =========================================================================================
if is_ci():
return
freeze_str = (
font_config.freeze_config_str
if font_config.freeze_config_str != ""
else "default config"
)
end_time = time.time()
date_time_fmt = time.strftime("%H:%M:%S", time.localtime(end_time))
time_diff = end_time - start_time
2025-01-21 03:38:11 +00:00
output = joinPaths(getcwd().replace("\\", "/"), build_option.output_dir)
print(
2025-01-21 03:38:11 +00:00
f"\n🏁 Build finished at {date_time_fmt}, cost {time_diff:.2f} s, family name is {font_config.family_name}, {freeze_str}\n See your fonts in {output}"
)
2024-05-24 03:29:45 +00:00
2024-03-17 09:54:43 +00:00
if __name__ == "__main__":
main()