robotools / extractor

Tools for extracting data from font binaries into UFO objects.
MIT License
51 stars 15 forks source link

os2.panose error with TTFont built from fontTools fontBuilder #58

Closed NightFurySL2001 closed 1 year ago

NightFurySL2001 commented 1 year ago

fontTools provide a method to build a whole font from scratch using fontBuilder: https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/fontBuilder.py . It is quite helpful in making a new font from scratch.

However, running the minimal reproduce code below raises an error:

from fontTools.fontBuilder import FontBuilder
from fontTools.pens.t2CharStringPen import T2CharStringPen

from ufoLib2 import Font as UFOFont
from extractor.formats.opentype import (
    extractOpenTypeInfo,
    extractOpenTypeGlyphs,
    extractGlyphOrder,
)

# provided minimal OTF build in fontBuilder
def drawTestGlyph(pen):
    pen.moveTo((100, 100))
    pen.lineTo((100, 1000))
    pen.curveTo((200, 900), (400, 900), (500, 1000))
    pen.lineTo((500, 100))
    pen.closePath()

fb = FontBuilder(1024, isTTF=False)
fb.setupGlyphOrder([".notdef", ".null", "space", "A", "a"])
fb.setupCharacterMap({32: "space", 65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "space": 500, "A": 600, "a": 600, ".null": 0}

familyName = "HelloTestFont"
styleName = "TotallyNormal"
version = "0.1"

nameStrings = dict(
    familyName=dict(en=familyName, nl="HalloTestFont"),
    styleName=dict(en=styleName, nl="TotaalNormaal"),
    uniqueFontIdentifier="fontBuilder: " + familyName + "." + styleName,
    fullName=familyName + "-" + styleName,
    psName=familyName + "-" + styleName,
    version="Version " + version,
)

pen = T2CharStringPen(600, None)
drawTestGlyph(pen)
charString = pen.getCharString()
charStrings = {
    ".notdef": charString,
    "space": charString,
    "A": charString,
    "a": charString,
    ".null": charString,
}
fb.setupCFF(nameStrings["psName"], {"FullName": nameStrings["psName"]}, charStrings, {})
lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()}
metrics = {}
for gn, advanceWidth in advanceWidths.items():
    metrics[gn] = (advanceWidth, lsb[gn])
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupNameTable(nameStrings)
fb.setupOS2(sTypoAscender=824, usWinAscent=824, usWinDescent=200)
fb.setupPost()

# output to ufo, shoudln't be a problem with using ufoLib2 right?
outputpath = "test.ufo"
ttfont = fb.font
destination = UFOFont()
extractOpenTypeInfo(ttfont, destination)
extractOpenTypeGlyphs(ttfont, destination)
extractGlyphOrder(ttfont, destination)
destination.save(outputpath)

Error:

Traceback (most recent call last):
  File "d:\Desktop\coding\svg2font\testbuildbug.py", line 62, in <module>
    extractOpenTypeInfo(ttfont, destination)
  File "D:\Desktop\coding\svg2font\kivy_venv\lib\site-packages\extractor\formats\opentype.py", line 233, in extractOpenTypeInfo
    _extracInfoOS2(source, info)
  File "D:\Desktop\coding\svg2font\kivy_venv\lib\site-packages\extractor\formats\opentype.py", line 372, in _extracInfoOS2
    os2.panose.bFamilyType,
AttributeError: 'dict' object has no attribute 'bFamilyType'
benkiel commented 1 year ago

Are you sure that the fontBuilder font has a valid Panose entry?

NightFurySL2001 commented 1 year ago

I think so? At least saving the font with fontBuilder directly (fb.save("test.otf")) does not raise any errors. https://github.com/fonttools/fonttools/blob/3235454fcd720f270e18ddf00b058df83da63547/Lib/fontTools/fontBuilder.py#L266

anthrotype commented 1 year ago

looks to me like a bug in the fontBuilder, because it leaves the OS/2 table's panose attribute as a dictionary (as in _panoseDefaults), whereas it should set it to a Panose object like the OS/2 table's decompile method does. The reason it works when saving from the fontBuilder is that the fonttools' sstruct.pack method accepts either an object or a dictionary and so compile works regardless. We should fix it in fonttools. @NightFurySL2001 do you mind filing an issue or working on a PR even? As a workaround, you could temporarily save the fontBuilder's font (to a tempfile or BytesIO stream), reload it and then try to extractor it. I believe it will work then, for the panose attribute will be an object and not a dict upon decompiling.

benkiel commented 1 year ago

@anthrotype thank you! Closing this.