Skip to content

Commit

Permalink
Merge pull request #1029 from googlefonts/vtt-fix
Browse files Browse the repository at this point in the history
transfer-vtt-hints: Fixes
  • Loading branch information
m4rc1e authored Sep 17, 2024
2 parents e241d97 + 6a69c8e commit 58a889c
Showing 1 changed file with 56 additions and 34 deletions.
90 changes: 56 additions & 34 deletions Lib/gftools/scripts/transfer_vtt_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
Optional,
cppStyleComment,
Literal,
Combine,
)
import fontTools
from fontTools.ttLib import TTFont
from fontTools.misc.cliTools import makeOutputFileName
from copy import deepcopy
Expand All @@ -29,9 +31,20 @@
__all__ = ["transfer_hints"]


MAXP_ATTRS = {
"maxZones",
"maxTwilightPoints",
"maxStorage",
"maxFunctionDefs",
"maxInstructionDefs",
"maxStackElements",
"maxSizeOfInstructions",
}


# TSI3 parser
tsi3_func_name = Word(alphas) # Function name consists of alphabetic characters
integer = Word(nums).setParseAction(
integer = Combine(Optional("-") + Word(nums)).setParseAction(
lambda t: int(t[0])
) # Define integers and convert them to int
tsi3_args = (
Expand Down Expand Up @@ -115,24 +128,16 @@ def _tsi3_to_string(instructions: list[SimpleNamespace]):


def _update_tsi1(instructions: list[SimpleNamespace], gid_map, glyf):
new_instructions = [
i for i in instructions if i.name != "OFFSET[R]" if i.name != "SVTCA[Y]"
]
if instructions[0].name != "USEMYMETRICS[]":
comp_pos = 0
else:
comp_pos = 1
for component in glyf.components:
new_instructions.insert(
comp_pos,
SimpleNamespace(
name="OFFSET[R]",
args=[gid_map[component.glyphName], component.x, component.y],
),
)
comp_pos += 1
if new_instructions[-1].name == "USEMYMETRICS[]":
new_instructions.pop()
new_instructions = []
component_idx = 0
for instruction in instructions:
if instruction.name == "OFFSET[R]":
component = glyf.components[component_idx]
instruction.args[0] = gid_map[component.glyphName]
instruction.args[1] = component.x
instruction.args[2] = component.y
component_idx += 1
new_instructions.append(instruction)
return new_instructions


Expand Down Expand Up @@ -170,9 +175,7 @@ def transfer_tsi1(source_font: TTFont, target_font: TTFont, glyph_name: str):
target_glyph_order = {
name: idx for idx, name in enumerate(target_font.getGlyphOrder())
}

glyph_instructions = tsi1_parser.parseString(existing_program)
glyph = target_font["glyf"][glyph_name]
updated_instructions = _update_tsi1(
glyph_instructions, target_glyph_order, target_font["glyf"][glyph_name]
)
Expand All @@ -187,24 +190,29 @@ def printer(msg, items):
print(f"{msg}:\nGID,Glyph_Name:\n{item_list}\n")


def transfer_hints(source_font: TTFont, target_font: TTFont):
# transfer TSI3 (VTT talk glyph instructions)
def transfer_hints(source_font: TTFont, target_font: TTFont, skip_components=False):
target_font["TSI0"] = fontTools.ttLib.newTable("TSI0")
target_font["TSI2"] = fontTools.ttLib.newTable("TSI2")
# Add a blank TSI1 and TSI3 table
for tbl in ("TSI1", "TSI3"):
target_font[tbl] = fontTools.ttLib.newTable(tbl)
setattr(target_font[tbl], "glyphPrograms", {})
setattr(target_font[tbl], "extraPrograms", {})

target_gid = {name: idx for idx, name in enumerate(target_font.getGlyphOrder())}
matched_glyphs = source_font.getGlyphSet().keys() & target_font.getGlyphSet().keys()
matched_glyphs = (
source_font["TSI1"].glyphPrograms.keys() & target_font.getGlyphSet().keys()
)
unmatched_glyphs = (
target_font.getGlyphSet().keys() - source_font.getGlyphSet().keys()
)
unmatched_glyphs = set((target_gid[g], g) for g in unmatched_glyphs)
for tbl in ("TSI1", "TSI3"):
if tbl not in source_font:
raise ValueError(f"Source font does not have {tbl} table")
target_font[tbl] = deepcopy(source_font[tbl])

missing_hints = set()
for glyph_name in matched_glyphs:
source_is_composite = source_font["glyf"][glyph_name].isComposite()
target_is_composite = target_font["glyf"][glyph_name].isComposite()
if source_is_composite and target_is_composite:
if source_is_composite and target_is_composite and not skip_components:
transfer_tsi1(source_font, target_font, glyph_name)
elif source_is_composite and not target_is_composite:
missing_hints.add((target_gid[glyph_name], glyph_name))
Expand All @@ -229,31 +237,45 @@ def transfer_hints(source_font: TTFont, target_font: TTFont):
missing_hints,
)

# copy over other hinting tables
for tbl in ("TSI0", "TSI2", "TSI5", "fpgm", "prep", "TSIC", "maxp", "cvt "):
# Copy over other hinting tables
for tbl in ("TSI5", "fpgm", "prep", "TSIC", "cvt "):
target_font[tbl] = deepcopy(source_font[tbl])

# Copy over relevant maxp attributes
for maxp_attr in MAXP_ATTRS:
setattr(target_font["maxp"], maxp_attr, getattr(source_font["maxp"], maxp_attr))

# Copy over extraPrograms
for tbl in ("TSI1", "TSI3"):
target_font[tbl].extraPrograms = source_font[tbl].extraPrograms

transferred = len(matched_glyphs) - len(missing_hints)
total = len(target_font.getGlyphSet().keys())
print(f"Transferred {transferred}/{total} glyphs")
print("Please still check glyphs look good on Windows platforms")


def main():
def main(args=None):
parser = argparse.ArgumentParser(description="Transfer VTT hints between two fonts")
parser.add_argument("source", type=str, help="Source font file")
parser.add_argument("target", type=str, help="Target font file")
parser.add_argument(
"--skip-components",
action="store_true",
default=False,
help="Skip component hints",
)
output = parser.add_mutually_exclusive_group(required=False)
output.add_argument("-o", "--out", type=str, help="Output file")
output.add_argument(
"-i", "--inplace", action="store_true", help="Inplace modification"
)
args = parser.parse_args()
args = parser.parse_args(args)

source_font = TTFont(args.source)
target_font = TTFont(args.target)

transfer_hints(source_font, target_font)
transfer_hints(source_font, target_font, args.skip_components)

if args.inplace:
target_font.save(args.target)
Expand Down

0 comments on commit 58a889c

Please sign in to comment.