mirror of
https://github.com/nicoverbruggen/kobo-font-fix.git
synced 2026-03-25 01:20:07 +01:00
Add different hinting options
This commit is contained in:
119
kobofix.py
119
kobofix.py
@@ -554,6 +554,60 @@ class FontProcessor:
|
|||||||
else:
|
else:
|
||||||
logger.info(" PANOSE check passed, no modifications required.")
|
logger.info(" PANOSE check passed, no modifications required.")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Hinting methods
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _font_has_hints(font: TTFont) -> bool:
|
||||||
|
"""Check whether a font contains TrueType hinting data."""
|
||||||
|
if "fpgm" in font or "prep" in font or "cvt " in font:
|
||||||
|
return True
|
||||||
|
if "glyf" in font:
|
||||||
|
for glyph_name in font.getGlyphOrder():
|
||||||
|
glyph = font["glyf"][glyph_name]
|
||||||
|
if hasattr(glyph, 'program') and glyph.program and glyph.program.getAssembly():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def strip_hints(font: TTFont) -> None:
|
||||||
|
"""Remove all TrueType hints from the font."""
|
||||||
|
hints_removed = False
|
||||||
|
for table in ("fpgm", "prep", "cvt "):
|
||||||
|
if table in font:
|
||||||
|
del font[table]
|
||||||
|
hints_removed = True
|
||||||
|
if "glyf" in font:
|
||||||
|
for glyph_name in font.getGlyphOrder():
|
||||||
|
glyph = font["glyf"][glyph_name]
|
||||||
|
if hasattr(glyph, 'removeHinting'):
|
||||||
|
glyph.removeHinting()
|
||||||
|
hints_removed = True
|
||||||
|
if hints_removed:
|
||||||
|
logger.info(" Removed TrueType hints from the font.")
|
||||||
|
else:
|
||||||
|
logger.info(" No TrueType hints found to remove.")
|
||||||
|
|
||||||
|
def apply_ttfautohint(self, font_path: str) -> bool:
|
||||||
|
"""Run ttfautohint on a saved font file, replacing it in-place."""
|
||||||
|
try:
|
||||||
|
hinted_path = font_path + ".hinted"
|
||||||
|
subprocess.run(
|
||||||
|
["ttfautohint", font_path, hinted_path],
|
||||||
|
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
os.replace(hinted_path, font_path)
|
||||||
|
logger.info(" Applied ttfautohint.")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f" ttfautohint failed: {e}")
|
||||||
|
# Clean up temp file if it exists
|
||||||
|
hinted_path = font_path + ".hinted"
|
||||||
|
if os.path.exists(hinted_path):
|
||||||
|
os.remove(hinted_path)
|
||||||
|
return False
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Line adjustment methods
|
# Line adjustment methods
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -566,10 +620,6 @@ class FontProcessor:
|
|||||||
after the external utility has run.
|
after the external utility has run.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if subprocess.run(["which", "font-line"], capture_output=True).returncode != 0:
|
|
||||||
logger.error(" font-line utility not found. Please install it first.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
subprocess.run(["font-line", "percent", str(self.line_percent), font_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
subprocess.run(["font-line", "percent", str(self.line_percent), font_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||||
|
|
||||||
base, ext = os.path.splitext(font_path)
|
base, ext = os.path.splitext(font_path)
|
||||||
@@ -600,7 +650,7 @@ class FontProcessor:
|
|||||||
font_path: str,
|
font_path: str,
|
||||||
new_name: Optional[str] = None,
|
new_name: Optional[str] = None,
|
||||||
remove_prefix: Optional[str] = None,
|
remove_prefix: Optional[str] = None,
|
||||||
remove_hints: bool = False,
|
hint_mode: str = "skip",
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Process a single font file.
|
Process a single font file.
|
||||||
@@ -670,38 +720,18 @@ class FontProcessor:
|
|||||||
del font["GPOS"]
|
del font["GPOS"]
|
||||||
logger.info(" Removed GPOS table from the font.")
|
logger.info(" Removed GPOS table from the font.")
|
||||||
|
|
||||||
# Remove TrueType hints if requested
|
if hint_mode == "strip":
|
||||||
if remove_hints:
|
self.strip_hints(font)
|
||||||
hints_removed = False
|
|
||||||
# Remove fpgm (Font Program) table
|
|
||||||
if "fpgm" in font:
|
|
||||||
del font["fpgm"]
|
|
||||||
hints_removed = True
|
|
||||||
# Remove prep (Control Value Program) table
|
|
||||||
if "prep" in font:
|
|
||||||
del font["prep"]
|
|
||||||
hints_removed = True
|
|
||||||
# Remove cvt (Control Value Table)
|
|
||||||
if "cvt " in font:
|
|
||||||
del font["cvt "]
|
|
||||||
hints_removed = True
|
|
||||||
# Remove hints from glyf table using the built-in removeHinting method
|
|
||||||
if "glyf" in font:
|
|
||||||
for glyph_name in font.getGlyphOrder():
|
|
||||||
glyph = font["glyf"][glyph_name]
|
|
||||||
if hasattr(glyph, 'removeHinting'):
|
|
||||||
glyph.removeHinting()
|
|
||||||
hints_removed = True
|
|
||||||
|
|
||||||
if hints_removed:
|
|
||||||
logger.info(" Removed TrueType hints from the font.")
|
|
||||||
else:
|
|
||||||
logger.info(" No TrueType hints found to remove.")
|
|
||||||
|
|
||||||
output_path = self._generate_output_path(font_path, metadata)
|
output_path = self._generate_output_path(font_path, metadata)
|
||||||
font.save(output_path)
|
font.save(output_path)
|
||||||
logger.info(f" Saved: {output_path}")
|
logger.info(f" Saved: {output_path}")
|
||||||
|
|
||||||
|
if hint_mode == "overwrite":
|
||||||
|
self.apply_ttfautohint(output_path)
|
||||||
|
elif hint_mode == "additive" and not self._font_has_hints(font):
|
||||||
|
self.apply_ttfautohint(output_path)
|
||||||
|
|
||||||
if self.line_percent != 0:
|
if self.line_percent != 0:
|
||||||
self.apply_line_adjustment(output_path)
|
self.apply_line_adjustment(output_path)
|
||||||
else:
|
else:
|
||||||
@@ -736,6 +766,21 @@ class FontProcessor:
|
|||||||
return os.path.join(dirname, f"{base_name}{ext.lower()}")
|
return os.path.join(dirname, f"{base_name}{ext.lower()}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_dependencies(hint_mode: str, line_percent: int) -> None:
|
||||||
|
"""Check that all required external tools are available before processing."""
|
||||||
|
missing = []
|
||||||
|
if hint_mode in ("additive", "overwrite"):
|
||||||
|
if subprocess.run(["which", "ttfautohint"], capture_output=True).returncode != 0:
|
||||||
|
missing.append("ttfautohint")
|
||||||
|
if line_percent != 0:
|
||||||
|
if subprocess.run(["which", "font-line"], capture_output=True).returncode != 0:
|
||||||
|
missing.append("font-line")
|
||||||
|
if missing:
|
||||||
|
logger.error(f"Missing required dependencies: {', '.join(missing)}")
|
||||||
|
logger.error("Please install them before running this script.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def validate_font_files(font_paths: List[str]) -> Tuple[List[str], List[str]]:
|
def validate_font_files(font_paths: List[str]) -> Tuple[List[str], List[str]]:
|
||||||
"""Validate font files for processing."""
|
"""Validate font files for processing."""
|
||||||
valid_files = []
|
valid_files = []
|
||||||
@@ -803,8 +848,10 @@ Examples:
|
|||||||
help="Enable verbose output.")
|
help="Enable verbose output.")
|
||||||
parser.add_argument("--remove-prefix", type=str,
|
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\")")
|
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("--remove-hints", action="store_true",
|
parser.add_argument("--hint", type=str, default="skip",
|
||||||
help="Remove TrueType hints from the font. This may improve render quality on some devices and reduce file size.")
|
choices=["skip", "additive", "overwrite", "strip"],
|
||||||
|
help="Hinting mode: 'skip' does nothing (default), 'additive' runs ttfautohint on fonts lacking hints, "
|
||||||
|
"'overwrite' runs ttfautohint on all fonts, 'strip' removes all TrueType hints.")
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -817,6 +864,8 @@ Examples:
|
|||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
check_dependencies(args.hint, args.line_percent)
|
||||||
|
|
||||||
valid_files, invalid_files = validate_font_files(args.fonts)
|
valid_files, invalid_files = validate_font_files(args.fonts)
|
||||||
|
|
||||||
@@ -850,7 +899,7 @@ Examples:
|
|||||||
font_path,
|
font_path,
|
||||||
args.name,
|
args.name,
|
||||||
args.remove_prefix,
|
args.remove_prefix,
|
||||||
args.remove_hints,
|
args.hint,
|
||||||
):
|
):
|
||||||
success_count += 1
|
success_count += 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user