New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Combine nested components in TTF-Fonts #1506
Comments
So this is how far I got after some hours of trial and error: font = TTFont('fontfile.ttf')
glyphTable = font["glyf"]
glyphNames = font.getGlyphNames()
for glyphname in glyphNames:
glyph = glyphTable[glyphname]
if glyph.isComposite():
for compo in glyph.components:
cname = compo.glyphName
# only needed if component is moved
if compo.x == 0 and compo.y == 0:
continue
cglyph = glyphTable[cname]
if cglyph.isComposite() and len(cglyph.components) == 1:
# replace with nested component and adjust x/y position
compo.glyphName = cglyph.components[0].glyphName
compo.x += cglyph.components[0].x
compo.y += cglyph.components[0].y
compo.flags = cglyph.components[0].flags
glyphTable.compile(font)
font.save('fontfile-fixed.ttf') This works fine so far, the accents appear at the correct place on MacOS 10.6. However I don't know if it's correct to just copy the
Do I have to care about the instructions? What would be the correct way to handle this? Could anyone with more insights in the TT Spec and fonttools point me to the right direction? Thanks in advance for any hints. |
So after some more hours of trial and error, here is my final script, which deals with components consisting of more than one nested component like #! /usr/bin/env python
"""Script to combine multi-level components with component offsets. While this is allowed by the spec, fonts using this (e.g. Fira Sans) hit a bug in pre-10.9 Apple font engines, so this script makes them work on older MacOS versions."""
from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._g_l_y_f import GlyphComponent
def processComponent(glyphTable, glyph, compo, dx, dy):
changed = False
cname = compo.glyphName
cglyph = glyphTable[cname]
if cglyph.isComposite():
# combine all nested components recursively
for i in range(len(cglyph.components)):
g = glyphTable[cglyph.components[i].glyphName]
if g.isComposite():
if processComponent(glyphTable, glyph, cglyph.components[i], cglyph.components[i].x + dx, cglyph.components[i].y + dy):
changed = True
else:
glyph.components.append(GlyphComponent())
nc = glyph.components[-1]
nc.glyphName = cglyph.components[i].glyphName
nc.x = cglyph.components[i].x + dx
nc.y = cglyph.components[i].y + dy
nc.flags = cglyph.components[i].flags
changed = True
return changed
def combineNestedComponents(font):
if not 'glyf' in font:
return False
changed = False
glyphTable = font["glyf"]
glyphNames = font.getGlyphNames()
# loop over all glyphs
for glyphname in glyphNames:
glyph = glyphTable[glyphname]
if glyph.isComposite():
for compo in glyph.components:
dx = compo.x
dy = compo.y
# if component is not shifted, everything works fine
if dx == 0 and dy == 0:
continue
if processComponent(glyphTable, glyph, compo, dx, dy):
# delete original component if combined
glyph.components.remove(compo)
changed = True
glyphTable.compile(font)
return changed
def usage():
prog = os.path.basename(sys.argv[0])
print("usage: ", prog, " font.ttf [...]")
sys.exit(1)
def main():
ttfonts = []
if len(sys.argv) > 1:
for arg in sys.argv[1:]:
base, ext = os.path.splitext(arg)
if ext == '.ttf':
ttfonts.append(arg)
else:
print("Not ttf: " + arg)
usage()
else:
usage()
for filename in ttfonts:
base, ext = os.path.splitext(filename)
font = TTFont(filename)
if combineNestedComponents(font):
font.save(base + "-fixed" + ext)
else:
print("nothing to do for " + filename)
if __name__ == "__main__":
import sys, os
sys.exit(main()) |
Does this have a chance to work on variable fonts? Returned "nothing to do" for mine. |
This was a fix to make static TTFs with nested components work on MacOS 10.6 to 10.8. These Systems don't support variable fonts, so you don't need it. |
Will the static fonts be fine?
|
Looking at the script, the output you're getting seems to say your font does not contain any nested components to begin with. That message should be as valid for a VF as for a static font. That said, the script probably doesn't do the right thing for a VF if it does contain nested components. |
Thanks for taking the time trying to help me @justvanrossum and @artcs! I ran the script for the VF writing to the same file and I must've ran it twice which is why it told me "nothing to do". It is able to flatten the components for VFs but doesn't handle marks above the flattened components well (they lose their alignment with variation I think). Luckily there's a solution printed right there in fontbakery: fonttools/fontbakery#2961 In case anyone bumps into this again: I'm adding the ufo2ft filter like this: #fixMaster.py
import sys
from plistlib import load, dump
path = sys.argv[-1]
def fixLib(path):
with open(path, 'rb') as fp:
pl = load(fp)
pl['com.github.googlei18n.ufo2ft.filters'] = [{ 'name': 'flattenComponents', 'pre': 1 }]
with open(path, 'wb') as fp:
dump(pl, fp)
fixLib(path + 'lib.plist') ran like this |
@hyvyys FYI, Simon added a |
The original script does only combine components, that have an offset defined, so it does not flatten all the components. For that you should better use fontmake as suggested by anthrotype above or use the decompose-ttf snippet. |
Older MacOS versions (10.6.8) have a problem with nested components in TTF-Fonts. E.g. Fira Fonts have some accents misplaced, cause they are built from nested components (see this issue).
This was fixed in Fira 4.1.6 but returned in later versions. I currently work around this issue (I still have clients using 10.6) by fixing this manually in the ttx-output (e.g. replacing uni0308.case by uni0308 and adjusting offsets). Could this be automated via a fonttools snippet or maybe a pyftsubset option? Any hints welcome.
The text was updated successfully, but these errors were encountered: