Max 10k pairs per kern subtable (Fixes #1)

- Fixes script
- Adds kern diff script to compare fonts to helpers
- Moved ttfconv to helpers folder
- Updated README
This commit is contained in:
2025-08-22 11:16:03 +02:00
parent 7bc0539bc4
commit eee6b0d739
4 changed files with 87 additions and 21 deletions

View File

@@ -2,9 +2,6 @@
## Overview
> [!WARNING]
> Currently, the `kern` table workaround does not appear to work correctly and requires further investigation. This script is still under development.
`kobofix.py` is a Python script designed to process TTF fonts for Kobo e-readers.
It generates a renamed font, fixes PANOSE information based on the filename, adjusts the baseline with the `font-line` utility, and adds a legacy `kern` table which allows the `kepub` engine for improved rendering of kerned pairs.
@@ -64,9 +61,11 @@ To process fonts from my [ebook-fonts](https://github.com/nicoverbruggen/ebook-f
To process all fonts with the "Kobo Fix" preset, simply run:
```bash
./kobofix.py --prefix KF --remove-prefix="NV" --line-percent 20 *.ttf
./kobofix.py --prefix KF --remove-prefix="NV" --line-percent 0 *.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.)
### Generating NV fonts
Tight spacing, with a custom font family name:

65
helpers/kerndiff.py Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import sys
from fontTools.ttLib import TTFont
def analyze_kern_table(font_path):
"""
Loads a font and analyzes its 'kern' table, if present.
"""
try:
font = TTFont(font_path)
except Exception as e:
print(f"Error: Could not open font file at {font_path}. Reason: {e}")
return
print(f"--- Analyzing 'kern' table in: {font_path} ---")
if 'kern' not in font:
print("No 'kern' table found.")
return
kern_table = font['kern']
print(f" > Table version: {kern_table.version}")
if not hasattr(kern_table, 'kernTables') or not kern_table.kernTables:
print(" > No subtables found.")
return
print(f" > Number of subtables: {len(kern_table.kernTables)}")
for i, subtable in enumerate(kern_table.kernTables):
print(f"\n --- Subtable {i+1} ---")
if hasattr(subtable, 'coverage'):
print(f" > Coverage flags (as integer): {subtable.coverage}")
# A more detailed breakdown of flags
coverage_int = int(subtable.coverage)
print(f" > Coverage flags breakdown:")
print(f" - Horizontal Kerning: {'Yes' if coverage_int & 1 else 'No'} (bit 0)")
print(f" - Minimum Values: {'Yes' if coverage_int & 2 else 'No'} (bit 1)")
print(f" - Cross-stream Kerning: {'Yes' if coverage_int & 4 else 'No'} (bit 2)")
print(f" - Variation Kerning: {'Yes' if coverage_int & 8 else 'No'} (bit 3)")
if hasattr(subtable, 'kernTable'):
print(f" > Found {len(subtable.kernTable)} kerning pairs.")
# Print the first 20 kerning pairs and values for inspection
print(" > Sample of kerning pairs (glyph1, glyph2) -> value:")
for pair, value in list(subtable.kernTable.items())[:20]:
print(f" - ({pair[0]}, {pair[1]}) -> {value}")
else:
print(" > Subtable has no 'kernTable' attribute.")
def main():
"""
Main function to compare two fonts.
"""
# Replace these with the actual paths to your font files
working_font = "./KC_Garamond-Regular.ttf"
broken_font = "./KF_Garamond-Regular.ttf"
analyze_kern_table(working_font)
print("\n" + "="*50 + "\n")
analyze_kern_table(broken_font)
if __name__ == "__main__":
main()

View File

@@ -341,30 +341,32 @@ class FontProcessor:
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.
Older devices like some Kobo models only recognize the 'kern' table.
This function creates a new `kern` table from the extracted GPOS pairs.
Splits into multiple subtables if there are more than 10,000 pairs.
"""
if not kern_pairs:
return 0
kern_table = newTable("kern")
kern_table.version = 0
kern_table.kernTables = []
subtable = KernTable_format_0()
subtable.version = 0
subtable.length = None
subtable.coverage = 1
subtable.kernTable = {
tuple(k): int(v)
for k, v in kern_pairs.items()
if v
}
kern_table.kernTables.append(subtable)
# 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
kern_table.kernTables.append(subtable)
font["kern"] = kern_table
return len(subtable.kernTable)
return len(items)
# ============================================================
# Name table methods
# ============================================================