def minimalTTF(): fb = FontBuilder(1024, isTTF=True) fb.updateHead(unitsPerEm=1000, created=0, modified=0) fb.setupGlyphOrder([ ".notdef", ".null", "A", "Aacute", "V", "acutecomb", "gravecomb", "A.alt" ]) fb.setupCharacterMap({ 65: "A", 192: "Aacute", 86: "V", 769: "acutecomb", 768: "gravecomb" }) advanceWidths = { ".notdef": 600, "A": 600, "Aacute": 600, "V": 600, ".null": 600, "acutecomb": 0, "gravecomb": 0, "A.alt": 600 } familyName = "HelloTestFont" styleName = "TotallyNormal" nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"), styleName=dict(en="TotallyNormal", nl="TotaalNormaal")) nameStrings['psName'] = familyName + "-" + styleName glyphs = { ".notdef": test_glyph(), ".null": test_glyph(), "A": test_glyph(), "Aacute": test_glyph(), "V": test_glyph(), "acutecomb": test_glyph(), "gravecomb": test_glyph(), "A.alt": test_glyph() } fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader() fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() return fb
def _setupFontBuilder(isTTF, unitsPerEm=1024): fb = FontBuilder(unitsPerEm, isTTF=isTTF) fb.setupGlyphOrder([".notdef", ".null", "A", "a"]) fb.setupCharacterMap({65: "A", 97: "a"}) advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600} familyName = "HelloTestFont" styleName = "TotallyNormal" nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"), styleName=dict(en="TotallyNormal", nl="TotaalNormaal")) nameStrings['psName'] = familyName + "-" + styleName return fb, advanceWidths, nameStrings
def test_unicodeVariationSequences(tmpdir): familyName = "UVSTestFont" styleName = "Regular" nameStrings = dict(familyName=familyName, styleName=styleName) nameStrings['psName'] = familyName + "-" + styleName glyphOrder = [".notdef", "space", "zero", "zero.slash"] cmap = {ord(" "): "space", ord("0"): "zero"} uvs = [ (0x0030, 0xFE00, "zero.slash"), (0x0030, 0xFE01, None), # not an official sequence, just testing ] metrics = {gn: (600, 0) for gn in glyphOrder} pen = TTGlyphPen(None) glyph = pen.glyph() # empty placeholder glyphs = {gn: glyph for gn in glyphOrder} fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(cmap, uvs) fb.setupGlyf(glyphs) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) fb.setupOS2() fb.setupPost() outPath = os.path.join(str(tmpdir), "test_uvs.ttf") fb.save(outPath) _verifyOutput(outPath, tables=["cmap"]) uvs = [ (0x0030, 0xFE00, "zero.slash"), (0x0030, 0xFE01, "zero"), # should result in the exact same subtable data, due to cmap[0x0030] == "zero" ] fb.setupCharacterMap(cmap, uvs) fb.save(outPath) _verifyOutput(outPath, tables=["cmap"])
def test_build_var(tmpdir): outPath = os.path.join(str(tmpdir), "test_var.ttf") fb = FontBuilder(1024, isTTF=True) fb.setupGlyphOrder([".notdef", ".null", "A", "a"]) fb.setupCharacterMap({65: "A", 97: "a"}) advanceWidths = {".notdef": 600, "A": 600, "a": 600, ".null": 600} familyName = "HelloTestFont" styleName = "TotallyNormal" nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"), styleName=dict(en="TotallyNormal", nl="TotaalNormaal")) nameStrings['psName'] = familyName + "-" + styleName pen = TTGlyphPen(None) pen.moveTo((100, 0)) pen.lineTo((100, 400)) pen.lineTo((500, 400)) pen.lineTo((500, 000)) pen.closePath() glyph = pen.glyph() pen = TTGlyphPen(None) emptyGlyph = pen.glyph() glyphs = {".notdef": emptyGlyph, "A": glyph, "a": glyph, ".null": emptyGlyph} fb.setupGlyf(glyphs) metrics = {} glyphTable = fb.font["glyf"] for gn, advanceWidth in advanceWidths.items(): metrics[gn] = (advanceWidth, glyphTable[gn].xMin) fb.setupHorizontalMetrics(metrics) fb.setupHorizontalHeader(ascent=824, descent=200) fb.setupNameTable(nameStrings) axes = [ ('LEFT', 0, 0, 100, "Left"), ('RGHT', 0, 0, 100, "Right"), ('UPPP', 0, 0, 100, "Up"), ('DOWN', 0, 0, 100, "Down"), ] instances = [ dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"), dict(location=dict(LEFT=0, RGHT=100, UPPP=100, DOWN=0), stylename="Right Up"), ] fb.setupFvar(axes, instances) variations = {} # Four (x, y) pairs and four phantom points: leftDeltas = [(-200, 0), (-200, 0), (0, 0), (0, 0), None, None, None, None] rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None] upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None] downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None] variations['a'] = [ TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas), TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas), TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas), TupleVariation(dict(DOWN=(0, 1, 1)), downDeltas), ] fb.setupGvar(variations) fb.setupOS2() fb.setupPost() fb.setupDummyDSIG() fb.save(outPath) _verifyOutput(outPath)
def build(instance, opts): font = instance.parent source = font.masters[0] fea, marks = makeFeatures(instance, source) glyphOrder = [] advanceWidths = {} characterMap = {} charStrings = {} source.blueValues = [] source.otherBlues = [] for zone in sorted(source.alignmentZones): pos = zone.position size = zone.size vals = sorted((pos, pos + size)) if pos == 0 or size >= 0: source.blueValues.extend(vals) else: source.otherBlues.extend(vals) fontinfo = f""" FontName {instance.fontName} OrigEmSqUnits {font.upm} DominantV {source.verticalStems} DominantH {source.horizontalStems} BaselineOvershoot {source.blueValues[0]} BaselineYCoord {source.blueValues[1]} LcHeight {source.blueValues[2]} LcOvershoot {source.blueValues[3] - source.blueValues[2]} CapHeight {source.blueValues[4]} CapOvershoot {source.blueValues[5] - source.blueValues[4]} AscenderHeight {source.blueValues[6]} AscenderOvershoot {source.blueValues[7] - source.blueValues[6]} Baseline5 {source.otherBlues[1]} Baseline5Overshoot {source.otherBlues[0] - source.otherBlues[1]} FlexOK true BlueFuzz 1 """ layerSet = {g.name: g.layers[source.id] for g in font.glyphs} for glyph in font.glyphs: if not glyph.export: continue name = glyph.name glyphOrder.append(name) for code in glyph.unicodes: characterMap[int(code, 16)] = name layer = glyph.layers[source.id] width = 0 if name in marks else layer.width # Draw glyph and remove overlaps. path = Path() layer.draw(DecomposePathPen(path, layerSet=layerSet)) path.simplify(fix_winding=True, keep_starting_points=True) # Autohint. pen = BezPen(None, True) path.draw(pen) bez = "\n".join(["% " + name, "sc", *pen.bez, "ed", ""]) hinted = hint_bez_glyph(fontinfo, bez) program = [width] + convertBezToT2(hinted) # Build CharString. charStrings[name] = T2CharString(program=program) advanceWidths[name] = width # Make sure .notdef is glyph index 0. glyphOrder.pop(glyphOrder.index(".notdef")) glyphOrder.insert(0, ".notdef") version = float(opts.version) vendor = font.customParameters["vendorID"] names = { "copyright": font.copyright, "familyName": instance.familyName, "styleName": instance.name, "uniqueFontIdentifier": f"{version:.03f};{vendor};{instance.fontName}", "fullName": instance.fullName, "version": f"Version {version:.03f}", "psName": instance.fontName, "manufacturer": font.manufacturer, "designer": font.designer, "description": font.customParameters["description"], "vendorURL": font.manufacturerURL, "designerURL": font.designerURL, "licenseDescription": font.customParameters["license"], "licenseInfoURL": font.customParameters["licenseURL"], "sampleText": font.customParameters["sampleText"], } date = int(font.date.timestamp()) - epoch_diff fb = FontBuilder(font.upm, isTTF=False) fb.updateHead(fontRevision=version, created=date, modified=date) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(characterMap) fb.setupNameTable(names, mac=False) fb.setupHorizontalHeader( ascent=source.ascender, descent=source.descender, lineGap=source.customParameters["typoLineGap"], ) privateDict = { "BlueValues": source.blueValues, "OtherBlues": source.otherBlues, "StemSnapH": source.horizontalStems, "StemSnapV": source.verticalStems, "StdHW": source.horizontalStems[0], "StdVW": source.verticalStems[0], } fontInfo = { "FullName": names["fullName"], "Notice": names["copyright"].replace("©", "\(c\)"), "version": f"{version:07.03f}", "Weight": instance.name, } fb.setupCFF(names["psName"], fontInfo, charStrings, privateDict) metrics = {} for i, (name, width) in enumerate(advanceWidths.items()): bounds = charStrings[name].calcBounds(None) or [0] metrics[name] = (width, bounds[0]) fb.setupHorizontalMetrics(metrics) codePages = [ CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"] ] fb.setupOS2( version=4, sTypoAscender=source.ascender, sTypoDescender=source.descender, sTypoLineGap=source.customParameters["typoLineGap"], usWinAscent=source.ascender, usWinDescent=-source.descender, sxHeight=source.xHeight, sCapHeight=source.capHeight, achVendID=vendor, fsType=calcBits(font.customParameters["fsType"], 0, 16), fsSelection=calcFsSelection(instance), ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32), ulCodePageRange1=calcBits(codePages, 0, 32), ) ut = int(source.customParameters["underlineThickness"]) up = int(source.customParameters["underlinePosition"]) fb.setupPost(underlineThickness=ut, underlinePosition=up + ut // 2) meta = newTable("meta") meta.data = {"dlng": "Arab", "slng": "Arab"} fb.font["meta"] = meta fb.addOpenTypeFeatures(fea) return fb.font
def build(instance, opts, glyphOrder): font = instance.parent master = font.masters[0] advanceWidths = {} characterMap = {} charStrings = {} colorLayers = {} for name in glyphOrder: glyph = font.glyphs[name] if not glyph.export: continue for layer in glyph.layers: if "colorPalette" in layer.attributes: index = layer.attributes["colorPalette"] if name not in colorLayers: colorLayers[name] = [] if layer.layerId == layer.associatedMasterId: # master layer colorLayers[name].append((name, int(index))) else: assert False, "can’t handle non-master color layers" if glyph.unicode: characterMap[int(glyph.unicode, 16)] = name layer = getLayer(glyph, instance) charStrings[name] = draw(layer, instance).getCharString() advanceWidths[name] = layer.width # XXX glyphOrder.pop(glyphOrder.index(".notdef")) glyphOrder.pop(glyphOrder.index("space")) glyphOrder.insert(0, ".notdef") glyphOrder.insert(1, "space") version = float(opts.version) vendor = get_property(font, "vendorID") names = { "copyright": font.copyright, "familyName": instance.familyName, "styleName": instance.name, "uniqueFontIdentifier": f"{version:.03f};{vendor};{instance.fontName}", "fullName": instance.fullName, "version": f"Version {version:.03f}", "psName": instance.fontName, "manufacturer": font.manufacturer, "designer": font.designer, "vendorURL": font.manufacturerURL, "designerURL": font.designerURL, "licenseDescription": get_property(font, "licenses"), "licenseInfoURL": get_property(font, "licenseURL"), "sampleText": get_property(font, "sampleTexts"), } fb = FontBuilder(font.upm, isTTF=False) date = font.date.replace(tzinfo=datetime.timezone.utc) stat = opts.glyphs.stat() fb.updateHead( fontRevision=version, created=int(date.timestamp()) - mac_epoch_diff, modified=int(stat.st_mtime) - mac_epoch_diff, ) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(characterMap) fb.setupNameTable(names, mac=False) fb.setupHorizontalHeader( ascent=master.ascender, descent=master.descender, lineGap=master.customParameters["hheaLineGap"], ) if opts.debug: fb.setupCFF(names["psName"], {}, charStrings, {}) fb.font["CFF "].compile(fb.font) else: fb.setupCFF2(charStrings) metrics = {} for name, width in advanceWidths.items(): bounds = charStrings[name].calcBounds(None) or [0] metrics[name] = (width, bounds[0]) fb.setupHorizontalMetrics(metrics) fb.setupPost( underlinePosition=master.customParameters["underlinePosition"], underlineThickness=master.customParameters["underlineThickness"], ) # Compile to get font bbox fb.font["head"].compile(fb.font) codePages = [ CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"] ] fb.setupOS2( version=4, sTypoAscender=master.ascender, sTypoDescender=master.descender, sTypoLineGap=master.customParameters["typoLineGap"], usWinAscent=fb.font["head"].yMax, usWinDescent=-fb.font["head"].yMin, sxHeight=master.xHeight, sCapHeight=master.capHeight, achVendID=vendor, fsType=calcBits(font.customParameters["fsType"], 0, 16), fsSelection=calcFsSelection(instance), ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32), ulCodePageRange1=calcBits(codePages, 0, 32), ) fea = makeFeatures(instance, master, opts, glyphOrder) fb.addOpenTypeFeatures(fea) palettes = master.customParameters["Color Palettes"] palettes = [[tuple(v / 255 for v in c) for c in p] for p in palettes] fb.setupCPAL(palettes) fb.setupCOLR(colorLayers) instance.font = fb.font if opts.debug: fb.font.save(f"{instance.fontName}.otf") return fb.font
def build(instance, opts): font = instance.parent master = font.masters[0] fea, marks = makeFeatures(instance, master) glyphOrder = [] advanceWidths = {} characterMap = {} charStrings = {} for glyph in font.glyphs: if not glyph.export: continue name = glyph.name glyphOrder.append(name) if glyph.unicode: characterMap[int(glyph.unicode, 16)] = name layer = getLayer(glyph, instance) width = 0 if name in marks else layer.width path = Path() draw(layer, instance, path.getPen()) path.simplify(fix_winding=True, keep_starting_points=True) pen = T2CharStringPen(width, None) path.draw(pen) charStrings[name] = pen.getCharString(optimize=False) advanceWidths[name] = width # XXX glyphOrder.pop(glyphOrder.index(".notdef")) glyphOrder.pop(glyphOrder.index("space")) glyphOrder.insert(0, ".notdef") glyphOrder.insert(1, "space") version = float(opts.version) vendor = font.customParameters["vendorID"] names = { "copyright": font.copyright, "familyName": instance.familyName, "styleName": instance.name, "uniqueFontIdentifier": f"{version:.03f};{vendor};{instance.fontName}", "fullName": instance.fullName, "version": f"Version {version:.03f}", "psName": instance.fontName, "manufacturer": font.manufacturer, "designer": font.designer, "description": font.customParameters["description"], "vendorURL": font.manufacturerURL, "designerURL": font.designerURL, "licenseDescription": font.customParameters["license"], "licenseInfoURL": font.customParameters["licenseURL"], "sampleText": font.customParameters["sampleText"], } date = int(font.date.timestamp()) - epoch_diff fb = FontBuilder(font.upm, isTTF=False) fb.updateHead(fontRevision=version, created=date, modified=date) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(characterMap) fb.setupNameTable(names, mac=False) fb.setupHorizontalHeader(ascent=master.ascender, descent=master.descender, lineGap=master.customParameters["typoLineGap"]) privateDict = { "BlueValues": [], "OtherBlues": [], "StemSnapH": master.horizontalStems, "StemSnapV": master.verticalStems, "StdHW": master.horizontalStems[0], "StdVW": master.verticalStems[0], } for zone in sorted(master.alignmentZones): pos = zone.position size = zone.size vals = privateDict["BlueValues"] if pos == 0 or size >= 0 else privateDict["OtherBlues"] vals.extend(sorted((pos, pos + size))) fontInfo = { "FullName": names["fullName"], "Notice": names["copyright"].replace("©", "\(c\)"), "version": f"{version:07.03f}", "Weight": instance.name, } fb.setupCFF(names["psName"], fontInfo, charStrings, privateDict) metrics = {} for i, (name, width) in enumerate(advanceWidths.items()): bounds = charStrings[name].calcBounds(None) or [0] metrics[name] = (width, bounds[0]) fb.setupHorizontalMetrics(metrics) codePages = [CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"]] fb.setupOS2(version=4, sTypoAscender=master.ascender, sTypoDescender=master.descender, sTypoLineGap=master.customParameters["typoLineGap"], usWinAscent=master.ascender, usWinDescent=-master.descender, sxHeight=master.xHeight, sCapHeight=master.capHeight, achVendID=vendor, fsType=calcBits(font.customParameters["fsType"], 0, 16), fsSelection=calcFsSelection(instance), ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32), ulCodePageRange1=calcBits(codePages, 0, 32)) ut = int(master.customParameters["underlineThickness"]) up = int(master.customParameters["underlinePosition"]) fb.setupPost(underlineThickness=ut, underlinePosition=up + ut//2) fb.addOpenTypeFeatures(fea) cidinfo = f""" FontName ({names["psName"]}) FamilyName ({names["familyName"]}) Weight ({fontInfo["Weight"]}) version ({fontInfo["version"]}) Notice ({fontInfo["Notice"]}) Registry Adobe Ordering Identity Supplement 0 """ cidmap = f"mergefonts {instance.fontName}\n" \ + "\n".join([f"{i} {n}" for i, n in enumerate(glyphOrder)]) return fb.font, cidinfo, cidmap