mirror of
https://github.com/nicoverbruggen/kobo-font-fix.git
synced 2026-04-01 06:50:09 +02:00
Refactor CLI flags into modal options and add presets
This is a big commit that fixes a variety of issues and prevents fonts
from growing far too large without any benefits. Previously, converted
fonts would use multiple subtables for the `kern` table, but sadly those
are usually not read by renderers, so we now don't save al kern pairs,
but we prioritize.
The full list of changes can be found below.
---
Replace individual boolean flags with modal --kern and --hint options:
- --hint {skip,additive,overwrite,strip}: controls hinting behavior,
including new ttfautohint support (additive/overwrite modes)
- --kern {add-legacy-kern,legacy-kern-only,skip}: replaces the old
--skip-kobo-kern and --remove-gpos flags
- --preset {nv,kf}: bundled configurations for common workflows
Add upfront dependency checking (`ttfautohint`, `font-line`) so missing
tools are caught before any processing begins.
Fix GPOS Extension lookup (type 9) support: kern pairs were silently
missed in fonts that wrap PairPos subtables in Extension lookups.
Rework legacy kern table writing to respect format 0 size constraints.
The subtable length field is uint16, limiting a single subtable to
10,920 pairs. Since most renderers (including Kobo) only read the first
subtable, we write exactly one and prioritize pairs by Unicode range.
=> Basic Latin > Latin-1 Supplement > Latin Extended > rest
This way, the most commonly encountered kerning pairs are preserved
when truncation is needed (and it usually is, for quality fonts).
This commit is contained in:
84
README.md
84
README.md
@@ -18,12 +18,17 @@ Python 3, FontTools, `font-line`.
|
||||
|
||||
You can install them like so:
|
||||
|
||||
|
||||
```bash
|
||||
pip3 install fonttools
|
||||
pip3 install font-line
|
||||
```
|
||||
|
||||
If you want to use the `--hint additive` or `--hint overwrite` options, you also need `ttfautohint`:
|
||||
|
||||
```bash
|
||||
brew install ttfautohint # macOS
|
||||
```
|
||||
|
||||
On macOS, if you're using the built-in version of Python (via Xcode), you may need to first add a folder to your `PATH` to make `font-line` available, like:
|
||||
|
||||
```bash
|
||||
@@ -69,90 +74,45 @@ You can customize what the script does. For more information, consult:
|
||||
```
|
||||
|
||||
Given the right arguments, you can:
|
||||
- Skip the `kern` step
|
||||
- Control kerning behavior (`--kern`): add a legacy kern table (default), remove GPOS after extraction, or skip entirely
|
||||
- Control hinting (`--hint`): strip hints, apply ttfautohint to unhinted fonts, apply ttfautohint to all fonts, or skip (default)
|
||||
- Use a custom name for a font
|
||||
- Use a custom name for the prefix
|
||||
- Remove the `GPOS` table entirely
|
||||
- Adjust the percentage of the `font-line` setting
|
||||
- Skip running `font-line` altogether
|
||||
|
||||
For debugging purposes, you can run the script with the `--verbose` flag.
|
||||
|
||||
## Examples
|
||||
## Presets
|
||||
|
||||
### Generating KF fonts
|
||||
The script includes presets for common workflows. If no preset or flags are provided, you will be prompted to choose one.
|
||||
|
||||
This applies the KF prefix, applies 20 percent line spacing and adds a Kobo `kern` table. Ideal if you have an existing TrueType font and you want it on your Kobo device.
|
||||
### NV preset
|
||||
|
||||
The `--name` parameter is used to change the name of the font family.
|
||||
Prepares fonts for the [ebook-fonts](https://github.com/nicoverbruggen/ebook-fonts) repository. Applies the NV prefix and 20% line spacing. Does not modify kerning or hinting.
|
||||
|
||||
```bash
|
||||
./kobofix.py --prefix KF --name="Fonty" --line-percent 20 --remove-hints *.ttf
|
||||
./kobofix.py --preset nv *.ttf
|
||||
```
|
||||
|
||||
To process fonts from my [ebook-fonts](https://github.com/nicoverbruggen/ebook-fonts) collection which are prefixed with "NV", you can replace the prefix and make adjustments in bulk.
|
||||
|
||||
To process all fonts with the "Kobo Fix" preset, simply run:
|
||||
You can override individual settings, for example to use relaxed spacing:
|
||||
|
||||
```bash
|
||||
./kobofix.py --prefix KF --remove-prefix="NV" --line-percent 0 *.ttf
|
||||
./kobofix.py --preset nv --line-percent 50 *.ttf
|
||||
```
|
||||
|
||||
(In this case, we'll set --line-percent to 0 so the line height changes aren't made, because the fonts in the NV Collection should already have those changes applied.)
|
||||
### KF preset
|
||||
|
||||
The expected output is then:
|
||||
|
||||
```
|
||||
nico@m1ni kobo-font-fix % ./kobofix.py --prefix KF --remove-prefix NV *.ttf --line-percent 0
|
||||
|
||||
Processing: NV-Elstob-Bold.ttf
|
||||
--remove-prefix enabled: using 'Elstob' as the new family name.
|
||||
Renaming the font to: KF Elstob Bold
|
||||
PANOSE corrected: bWeight 8->8, bLetterForm 2->2
|
||||
Kerning: extracted 342467 pairs; wrote 342467 to legacy 'kern' table.
|
||||
Saved: KF_Elstob-Bold.ttf
|
||||
Skipping line adjustment step.
|
||||
|
||||
Processing: NV-Elstob-BoldItalic.ttf
|
||||
--remove-prefix enabled: using 'Elstob' as the new family name.
|
||||
Renaming the font to: KF Elstob Bold Italic
|
||||
PANOSE corrected: bWeight 8->8, bLetterForm 3->3
|
||||
Kerning: extracted 300746 pairs; wrote 300746 to legacy 'kern' table.
|
||||
Saved: KF_Elstob-BoldItalic.ttf
|
||||
Skipping line adjustment step.
|
||||
|
||||
Processing: NV-Elstob-Italic.ttf
|
||||
--remove-prefix enabled: using 'Elstob' as the new family name.
|
||||
Renaming the font to: KF Elstob Italic
|
||||
PANOSE corrected: bWeight 5->5, bLetterForm 3->3
|
||||
Kerning: extracted 286857 pairs; wrote 286856 to legacy 'kern' table.
|
||||
Saved: KF_Elstob-Italic.ttf
|
||||
Skipping line adjustment step.
|
||||
|
||||
Processing: NV-Elstob-Regular.ttf
|
||||
--remove-prefix enabled: using 'Elstob' as the new family name.
|
||||
Renaming the font to: KF Elstob
|
||||
PANOSE corrected: bWeight 5->5, bLetterForm 2->2
|
||||
Kerning: extracted 313998 pairs; wrote 313998 to legacy 'kern' table.
|
||||
Saved: KF_Elstob-Regular.ttf
|
||||
Skipping line adjustment step.
|
||||
|
||||
==================================================
|
||||
Processed 4/4 fonts successfully.
|
||||
```
|
||||
|
||||
### Generating NV fonts
|
||||
|
||||
Tight spacing, with a custom font family name:
|
||||
Prepares KF fonts from NV fonts for use on Kobo devices. Applies the KF prefix, replaces the NV prefix, and adds a legacy kern table. No line spacing changes are made (since NV fonts already have those applied).
|
||||
|
||||
```bash
|
||||
./kobofix.py --prefix NV --name="Fonty" --line-percent 20 --skip-kobo-kern *.ttf
|
||||
./kobofix.py --preset kf *.ttf
|
||||
```
|
||||
|
||||
Relaxed spacing, with a custom font family name:
|
||||
### Custom processing
|
||||
|
||||
You can also specify all flags manually:
|
||||
|
||||
```bash
|
||||
./kobofix.py --prefix NV --name="Fonty" --line-percent 50 --skip-kobo-kern *.ttf
|
||||
./kobofix.py --prefix KF --name="Fonty" --line-percent 20 --kern add-legacy-kern *.ttf
|
||||
```
|
||||
|
||||
You can play around with `--line-percent` to see what works for you.
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ttfconv.py
|
||||
|
||||
# This script converts OTF fonts to TTF fonts using the font-tools library.
|
||||
# It processes a list of font files provided as command-line arguments.
|
||||
|
||||
# The font-tools library must be installed: `pip install fonttools`.
|
||||
|
||||
from fontTools.ttLib import TTFont
|
||||
import os
|
||||
import sys
|
||||
|
||||
def convert_font(input_file_path):
|
||||
"""
|
||||
Converts a font file to a TTF font file.
|
||||
This function currently assumes the input is OTF and the output is TTF.
|
||||
|
||||
Args:
|
||||
input_file_path (str): The path to the input font file.
|
||||
"""
|
||||
if not os.path.exists(input_file_path):
|
||||
print(f"❌ Error: The file '{input_file_path}' was not found.")
|
||||
return
|
||||
|
||||
try:
|
||||
output_file_path = os.path.splitext(input_file_path)[0] + ".ttf"
|
||||
font = TTFont(input_file_path)
|
||||
font.save(output_file_path)
|
||||
|
||||
print(f"✅ Converted: {os.path.basename(input_file_path)} -> {os.path.basename(output_file_path)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ An error occurred during conversion of '{os.path.basename(input_file_path)}': {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python font_converter.py <font_file1> <font_file2> ...")
|
||||
print("Example: python font_converter.py MyFont.otf AnotherFont.otf")
|
||||
print("You can also use a wildcard: python font_converter.py *.otf")
|
||||
else:
|
||||
for file_path in sys.argv[1:]:
|
||||
if file_path.lower().endswith(".otf"):
|
||||
convert_font(file_path)
|
||||
else:
|
||||
print(f"⚠️ Skipping '{file_path}': This script only converts OTF to TTF.")
|
||||
print(f"To convert other formats, please provide the correct extension.")
|
||||
|
||||
print("\nProcessing complete.")
|
||||
240
kobofix.py
240
kobofix.py
@@ -30,11 +30,24 @@ from fontTools.ttLib import TTFont, newTable
|
||||
from fontTools.ttLib.tables._k_e_r_n import KernTable_format_0
|
||||
|
||||
# -------------
|
||||
# DEFAULTS
|
||||
# PRESETS
|
||||
# -------------
|
||||
#
|
||||
DEFAULT_PREFIX = "KF"
|
||||
DEFAULT_LINE_PERCENT = 20
|
||||
PRESETS = {
|
||||
"nv": {
|
||||
"prefix": "NV",
|
||||
"line_percent": 20,
|
||||
"kern": "skip",
|
||||
"hint": "skip",
|
||||
},
|
||||
"kf": {
|
||||
"prefix": "KF",
|
||||
"line_percent": 0,
|
||||
"kern": "add-legacy-kern",
|
||||
"hint": "skip",
|
||||
"remove_prefix": "NV",
|
||||
},
|
||||
}
|
||||
|
||||
# -------------
|
||||
# STYLE MAPPING
|
||||
@@ -73,8 +86,8 @@ class FontProcessor:
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
prefix: str = DEFAULT_PREFIX,
|
||||
line_percent: int = DEFAULT_LINE_PERCENT
|
||||
prefix: str,
|
||||
line_percent: int,
|
||||
):
|
||||
"""
|
||||
Initialize the font processor with configurable values.
|
||||
@@ -334,50 +347,118 @@ class FontProcessor:
|
||||
lookup_list = getattr(gpos, "LookupList", None)
|
||||
if lookup_list and lookup_list.Lookup:
|
||||
for lookup in lookup_list.Lookup:
|
||||
# Only process Pair Adjustment lookups (type 2)
|
||||
if getattr(lookup, "LookupType", None) == 2:
|
||||
for subtable in getattr(lookup, "SubTable", []):
|
||||
lookup_type = getattr(lookup, "LookupType", None)
|
||||
subtables = getattr(lookup, "SubTable", [])
|
||||
|
||||
# Unwrap Extension lookups (type 9) to get the inner subtables
|
||||
if lookup_type == 9:
|
||||
unwrapped = []
|
||||
for ext_subtable in subtables:
|
||||
ext_type = getattr(ext_subtable, "ExtensionLookupType", None)
|
||||
inner = getattr(ext_subtable, "ExtSubTable", None)
|
||||
if ext_type == 2 and inner is not None:
|
||||
unwrapped.append(inner)
|
||||
subtables = unwrapped
|
||||
lookup_type = 2 if unwrapped else None
|
||||
|
||||
if lookup_type != 2:
|
||||
continue
|
||||
|
||||
for subtable in subtables:
|
||||
fmt = getattr(subtable, "Format", None)
|
||||
if fmt == 1:
|
||||
format1_pairs = self._extract_format1_pairs(subtable)
|
||||
for key, value in format1_pairs.items():
|
||||
# Only add if not already present (first value wins)
|
||||
if key not in pairs:
|
||||
pairs[key] = value
|
||||
extracted = self._extract_format1_pairs(subtable)
|
||||
elif fmt == 2:
|
||||
format2_pairs = self._extract_format2_pairs(subtable)
|
||||
for key, value in format2_pairs.items():
|
||||
# Only add if not already present (first value wins)
|
||||
extracted = self._extract_format2_pairs(subtable)
|
||||
else:
|
||||
continue
|
||||
for key, value in extracted.items():
|
||||
if key not in pairs:
|
||||
pairs[key] = value
|
||||
return pairs
|
||||
|
||||
@staticmethod
|
||||
def _glyph_priority(glyph_name: str, cmap_reverse: Dict[str, int]) -> int:
|
||||
"""
|
||||
Assign a priority to a glyph for kern pair sorting.
|
||||
Lower values = higher priority. Pairs involving common glyphs
|
||||
are prioritized so they fit within the subtable size limit.
|
||||
"""
|
||||
cp = cmap_reverse.get(glyph_name)
|
||||
if cp is None:
|
||||
return 4 # unmapped glyphs (ligatures, alternates, etc.)
|
||||
if cp <= 0x007F:
|
||||
return 0 # Basic Latin (A-Z, a-z, digits, punctuation)
|
||||
if cp <= 0x00FF:
|
||||
return 1 # Latin-1 Supplement (accented chars, common symbols)
|
||||
if cp <= 0x024F:
|
||||
return 2 # Latin Extended-A and B
|
||||
return 3 # everything else
|
||||
|
||||
@staticmethod
|
||||
def add_legacy_kern(font: TTFont, kern_pairs: Dict[Tuple[str, str], int]) -> int:
|
||||
"""
|
||||
Create or replace a legacy 'kern' table with the supplied pairs.
|
||||
Splits into multiple subtables if there are more than 10,000 pairs.
|
||||
|
||||
The legacy kern table format has strict size constraints:
|
||||
- Most renderers (including Kobo's WebKit-based engine) only read the
|
||||
first subtable, so we write exactly one.
|
||||
- Format 0 subtables have a uint16 length field (max 65,535 bytes).
|
||||
With a 14-byte header and 6 bytes per pair, this allows at most
|
||||
(65,535 - 14) / 6 = 10,920 pairs before the length overflows.
|
||||
|
||||
When a font has more pairs than this (common with class-based GPOS
|
||||
kerning, which can expand to 100k+ individual pairs), we prioritize
|
||||
by Unicode range so the most commonly encountered pairs are kept:
|
||||
- Basic Latin (U+0000-007F): English, digits, punctuation
|
||||
- Latin-1 Supplement (U+0080-00FF): Western European accented chars
|
||||
- Latin Extended-A/B (U+0100-024F): Central/Eastern European chars
|
||||
- Everything else and unmapped glyphs (ligatures, alternates)
|
||||
|
||||
This means all English kerning is preserved, most Western European
|
||||
kerning (French, German, Spanish, etc.) is preserved, and only less
|
||||
common extended Latin pairings are dropped when truncation is needed.
|
||||
"""
|
||||
if not kern_pairs:
|
||||
return 0
|
||||
|
||||
MAX_PAIRS = 10920
|
||||
items = [(tuple(k), int(v)) for k, v in kern_pairs.items() if v]
|
||||
|
||||
if len(items) > MAX_PAIRS:
|
||||
# Build reverse cmap (glyph name -> codepoint) for prioritization
|
||||
cmap_reverse = {}
|
||||
if "cmap" in font:
|
||||
for table in font["cmap"].tables:
|
||||
if hasattr(table, "cmap"):
|
||||
for cp, glyph_name in table.cmap.items():
|
||||
if glyph_name not in cmap_reverse:
|
||||
cmap_reverse[glyph_name] = cp
|
||||
|
||||
# Sort by priority of both glyphs (lower = more common)
|
||||
items.sort(key=lambda pair: (
|
||||
FontProcessor._glyph_priority(pair[0][0], cmap_reverse) +
|
||||
FontProcessor._glyph_priority(pair[0][1], cmap_reverse)
|
||||
))
|
||||
|
||||
logger.warning(f" Kerning: {len(items)} pairs exceed the subtable limit of {MAX_PAIRS}. "
|
||||
f"Keeping the {MAX_PAIRS} most common pairs.")
|
||||
items = items[:MAX_PAIRS]
|
||||
|
||||
kern_table = newTable("kern")
|
||||
kern_table.version = 0
|
||||
kern_table.kernTables = []
|
||||
|
||||
# Max pairs per subtable
|
||||
MAX_PAIRS = 10000
|
||||
items = [(tuple(k), int(v)) for k, v in kern_pairs.items() if v]
|
||||
|
||||
for i in range(0, len(items), MAX_PAIRS):
|
||||
chunk = dict(items[i:i + MAX_PAIRS])
|
||||
subtable = KernTable_format_0()
|
||||
subtable.version = 0
|
||||
subtable.length = None
|
||||
subtable.coverage = 1
|
||||
subtable.kernTable = chunk
|
||||
subtable.kernTable = dict(items)
|
||||
kern_table.kernTables.append(subtable)
|
||||
|
||||
# Additional subtables are not created because most renderers
|
||||
# (including Kobo's WebKit-based engine) only read the first one.
|
||||
|
||||
font["kern"] = kern_table
|
||||
|
||||
return len(items)
|
||||
@@ -645,8 +726,7 @@ class FontProcessor:
|
||||
# ============================================================
|
||||
|
||||
def process_font(self,
|
||||
kern: bool,
|
||||
remove_gpos: bool,
|
||||
kern_mode: str,
|
||||
font_path: str,
|
||||
new_name: Optional[str] = None,
|
||||
remove_prefix: Optional[str] = None,
|
||||
@@ -693,7 +773,11 @@ class FontProcessor:
|
||||
self.check_and_fix_panose(font, font_path)
|
||||
self.update_weight_metadata(font, font_path)
|
||||
|
||||
if kern:
|
||||
# Note: As of firmware 4.45, Kobo reads GPOS kerning data correctly,
|
||||
# but only when webkitTextRendering=optimizeLegibility is enabled.
|
||||
# Since this setting is disabled by default, a legacy kern table is
|
||||
# still needed for most users.
|
||||
if kern_mode in ("add-legacy-kern", "legacy-kern-only"):
|
||||
had_kern = "kern" in font
|
||||
had_gpos = "GPOS" in font
|
||||
|
||||
@@ -711,14 +795,12 @@ class FontProcessor:
|
||||
logger.info(" Kerning: GPOS table found but contained no kern pairs, no 'kern' table created.")
|
||||
else:
|
||||
logger.info(" Kerning: no kerning data found (no GPOS or 'kern' table), no pairs written.")
|
||||
else:
|
||||
logger.info(" Skipping `kern` step.")
|
||||
|
||||
# The GPOS table is removed after the kerning data has been extracted
|
||||
# and written to the `kern` table. This ensures the information is not lost.
|
||||
if remove_gpos and kern and "GPOS" in font:
|
||||
if kern_mode == "legacy-kern-only" and "GPOS" in font:
|
||||
del font["GPOS"]
|
||||
logger.info(" Removed GPOS table from the font.")
|
||||
else:
|
||||
logger.info(" Skipping `kern` step.")
|
||||
|
||||
if hint_mode == "strip":
|
||||
self.strip_hints(font)
|
||||
@@ -809,62 +891,95 @@ def validate_font_files(font_paths: List[str]) -> Tuple[List[str], List[str]]:
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
preset_names = ", ".join(PRESETS.keys())
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Process fonts for Kobo e-readers: add prefix, kern table, "
|
||||
"PANOSE validation, and line adjustments.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
epilog=f"""
|
||||
Presets:
|
||||
nv Prepare fonts for the ebook-fonts repository. Applies NV prefix,
|
||||
20%% line spacing. Does not modify kerning or hinting.
|
||||
kf Prepare KF fonts from NV fonts. Applies KF prefix, replaces NV
|
||||
prefix, adds legacy kern table. No line spacing changes.
|
||||
|
||||
Examples:
|
||||
For a default experience, which will prefix the font with KF, add `kern` table and adjust line-height:
|
||||
%(prog)s *.ttf
|
||||
Using a preset:
|
||||
%(prog)s --preset nv *.ttf
|
||||
%(prog)s --preset kf *.ttf
|
||||
|
||||
If you want to rename the font:
|
||||
%(prog)s --prefix KF --name="Fonty" --line-percent 20 *.ttf
|
||||
Custom processing:
|
||||
%(prog)s --prefix KF --name="Fonty" --line-percent 20 --kern add-legacy-kern *.ttf
|
||||
|
||||
If you want to keep the line-height because for a given font the default was fine:
|
||||
%(prog)s --line-percent 0 *.ttf
|
||||
|
||||
For improved legacy support, you can remove the GPOS table (not recommended):
|
||||
%(prog)s --prefix KF --name="Fonty" --line-percent 20 --remove-gpos *.ttf
|
||||
|
||||
To remove a specific prefix, like "NV", before applying a new one:
|
||||
%(prog)s --prefix KF --remove-prefix="NV" *.ttf
|
||||
If no preset or flags are provided, you will be prompted to choose a preset.
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument("fonts", nargs="+",
|
||||
help="Font files to process (*.ttf). You can use a wildcard (glob).")
|
||||
parser.add_argument("--preset", type=str, choices=PRESETS.keys(),
|
||||
help=f"Use a preset configuration ({preset_names}).")
|
||||
parser.add_argument("--name", type=str,
|
||||
help="Optional new family name for all fonts. Other font metadata like copyright info is unaffected.")
|
||||
parser.add_argument("--prefix", type=str, default=DEFAULT_PREFIX,
|
||||
help=f"Prefix to add to font names. Set to empty string to omit prefix. (Default: {DEFAULT_PREFIX})")
|
||||
parser.add_argument("--line-percent", type=int, default=DEFAULT_LINE_PERCENT,
|
||||
help=f"Line spacing adjustment percentage. Set to 0 to make no changes to line spacing. (Default: {DEFAULT_LINE_PERCENT})")
|
||||
parser.add_argument("--skip-kobo-kern", action="store_true",
|
||||
help="Skip the creation of the legacy 'kern' table from GPOS data.")
|
||||
parser.add_argument("--remove-gpos", action="store_true",
|
||||
help="Remove the GPOS table after converting kerning to a 'kern' table. Does not work if `--skip-kobo-kern` is set.")
|
||||
parser.add_argument("--prefix", type=str,
|
||||
help="Prefix to add to font names. Set to empty string to omit prefix.")
|
||||
parser.add_argument("--line-percent", type=int,
|
||||
help="Line spacing adjustment percentage. Set to 0 to make no changes to line spacing.")
|
||||
parser.add_argument("--kern", type=str,
|
||||
choices=["add-legacy-kern", "legacy-kern-only", "skip"],
|
||||
help="Kerning mode: 'add-legacy-kern' extracts GPOS pairs into a legacy kern table, "
|
||||
"'legacy-kern-only' does the same but removes the GPOS table afterwards, "
|
||||
"'skip' leaves kerning untouched.")
|
||||
parser.add_argument("--verbose", action="store_true",
|
||||
help="Enable verbose output.")
|
||||
parser.add_argument("--remove-prefix", type=str,
|
||||
help="Remove a leading prefix from font names before applying the new prefix. Only works if `--name` is not used. (e.g., --remove-prefix=\"NV\")")
|
||||
parser.add_argument("--hint", type=str, default="skip",
|
||||
parser.add_argument("--hint", type=str,
|
||||
choices=["skip", "additive", "overwrite", "strip"],
|
||||
help="Hinting mode: 'skip' does nothing (default), 'additive' runs ttfautohint on fonts lacking hints, "
|
||||
help="Hinting mode: 'skip' does nothing, 'additive' runs ttfautohint on fonts lacking hints, "
|
||||
"'overwrite' runs ttfautohint on all fonts, 'strip' removes all TrueType hints.")
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.remove_gpos and args.skip_kobo_kern:
|
||||
parser.error("--remove-gpos and --skip-kobo-kern cannot be used together.")
|
||||
if args.verbose:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
# Determine which flags were explicitly set by the user
|
||||
manual_flags = {k for k in ("prefix", "line_percent", "kern", "hint", "remove_prefix", "name")
|
||||
if getattr(args, k) is not None}
|
||||
|
||||
# If no preset and no manual flags, prompt the user to choose a preset
|
||||
if args.preset is None and not manual_flags:
|
||||
logger.info("No preset or flags specified. Available presets:")
|
||||
for name, values in PRESETS.items():
|
||||
logger.info(f" {name}")
|
||||
choice = input("\nChoose a preset: ").strip().lower()
|
||||
if choice not in PRESETS:
|
||||
logger.error(f"Unknown preset '{choice}'. Available: {preset_names}")
|
||||
sys.exit(1)
|
||||
args.preset = choice
|
||||
|
||||
# Apply preset values as defaults, then let explicit flags override
|
||||
if args.preset:
|
||||
preset = PRESETS[args.preset]
|
||||
for key, value in preset.items():
|
||||
if key not in manual_flags:
|
||||
setattr(args, key, value)
|
||||
|
||||
# Fill in remaining defaults for any unset flags
|
||||
if args.prefix is None:
|
||||
parser.error("--prefix is required when not using a preset.")
|
||||
if args.line_percent is None:
|
||||
parser.error("--line-percent is required when not using a preset.")
|
||||
if args.kern is None:
|
||||
args.kern = "skip"
|
||||
if args.hint is None:
|
||||
args.hint = "skip"
|
||||
|
||||
if args.name and args.remove_prefix:
|
||||
parser.error("--name and --remove-prefix cannot be used together. Use --name to set the font name directly, or --remove-prefix to strip an existing prefix.")
|
||||
|
||||
if args.verbose:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
check_dependencies(args.hint, args.line_percent)
|
||||
|
||||
valid_files, invalid_files = validate_font_files(args.fonts)
|
||||
@@ -894,8 +1009,7 @@ Examples:
|
||||
success_count = 0
|
||||
for font_path in valid_files:
|
||||
if processor.process_font(
|
||||
not args.skip_kobo_kern,
|
||||
args.remove_gpos,
|
||||
args.kern,
|
||||
font_path,
|
||||
args.name,
|
||||
args.remove_prefix,
|
||||
|
||||
Reference in New Issue
Block a user