Example #1
0
def featureVarsTestFont():
    fb = FontBuilder(unitsPerEm=100)
    fb.setupGlyphOrder([".notdef", "f", "f_f", "dollar", "dollar.rvrn"])
    fb.setupCharacterMap({ord("f"): "f", ord("$"): "dollar"})
    fb.setupNameTable({
        "familyName": "TestFeatureVars",
        "styleName": "Regular"
    })
    fb.setupPost()
    fb.setupFvar(axes=[("wght", 100, 400, 900, "Weight")], instances=[])
    fb.addOpenTypeFeatures("""\
        feature dlig {
            sub f f by f_f;
        } dlig;
    """)
    fb.addFeatureVariations([([{
        "wght": (0.20886, 1.0)
    }], {
        "dollar": "dollar.rvrn"
    })],
                            featureTag="rvrn")
    buf = io.BytesIO()
    fb.save(buf)
    buf.seek(0)

    return TTFont(buf)
Example #2
0
def test_subset_feature_variations():
    fb = FontBuilder(unitsPerEm=100)
    fb.setupGlyphOrder([".notdef", "f", "f_f", "dollar", "dollar.rvrn"])
    fb.setupCharacterMap({ord("f"): "f", ord("$"): "dollar"})
    fb.setupNameTable({
        "familyName": "TestFeatureVars",
        "styleName": "Regular"
    })
    fb.setupPost()
    fb.setupFvar(axes=[("wght", 100, 400, 900, "Weight")], instances=[])
    fb.addOpenTypeFeatures("""\
        feature dlig {
            sub f f by f_f;
        } dlig;
    """)
    fb.addFeatureVariations([([{
        "wght": (0.20886, 1.0)
    }], {
        "dollar": "dollar.rvrn"
    })],
                            featureTag="rvrn")
    buf = io.BytesIO()
    fb.save(buf)
    buf.seek(0)

    font = TTFont(buf)

    options = subset.Options()
    subsetter = subset.Subsetter(options)
    subsetter.populate(unicodes=[ord("f"), ord("$")])
    subsetter.subset(font)

    featureTags = {
        r.FeatureTag
        for r in font["GSUB"].table.FeatureList.FeatureRecord
    }
    # 'dlig' is discretionary so it is dropped by default
    assert "dlig" not in featureTags
    assert "f_f" not in font.getGlyphOrder()
    # 'rvrn' is required so it is kept by default
    assert "rvrn" in featureTags
    assert "dollar.rvrn" in font.getGlyphOrder()
def fontData(cmap, glyphOrder):
    glyphOrder = sorted(set(glyphOrder) | set(["space", ".notdef", ".fallbackGlyph"]))
    fea = f"""
languagesystem DFLT dflt;
languagesystem arab dflt;

{featurePartWriter(glyphOrder, "init")}
{featurePartWriter(glyphOrder, "medi")}
{featurePartWriter(glyphOrder, "fina")}
"""
    fb = FontBuilder(1024, isTTF=True)
    fb.setupGlyphOrder(glyphOrder)
    fb.setupCharacterMap({uni: names[0] for uni, names in cmap.items()})
    fb.addOpenTypeFeatures(fea)
    fb.setupGlyf({glyphName: _dummyGlyph for glyphName in glyphOrder})
    fb.setupHorizontalMetrics({glyphName: (0, 0) for glyphName in glyphOrder})
    fb.setupHorizontalHeader(ascent=0, descent=0)
    data = io.BytesIO()
    fb.save(data)
    data = data.getvalue()
    data = AppKit.NSData.dataWithBytes_length_(data, len(data))
    return data, fb.font
Example #4
0
def build(instance, opts):
    font = instance.parent
    master = font.masters[0]

    glyphOrder = []
    advanceWidths = {}
    characterMap = {}
    charStrings = {}
    colorLayers = {}
    for glyph in font.glyphs:
        if not glyph.export:
            continue
        name = glyph.name
        for layer in glyph.layers:
            if layer.name.startswith("Color "):
                _, index = layer.name.split(" ")
                if name not in colorLayers:
                    colorLayers[name] = []
                colorLayers[name].append((name, int(index)))

        glyphOrder.append(name)
        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 = 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,
        "vendorURL": font.manufacturerURL,
        "designerURL": font.designerURL,
        "licenseDescription": font.customParameters["license"],
        "licenseInfoURL": font.customParameters["licenseURL"],
        "sampleText": font.customParameters["sampleText"],
    }

    fb = FontBuilder(font.upm, isTTF=False)
    fb.updateHead(fontRevision=version)
    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, {})
    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()

    fea = makeFeatures(instance, master, opts)
    fb.addOpenTypeFeatures(fea)

    palettes = master.customParameters["Color Palettes"]
    palettes = [[tuple(int(v) / 255 for v in c.split(",")) for c in p]
                for p in palettes]
    fb.setupCPAL(palettes)
    fb.setupCOLR(colorLayers)

    instance.font = fb.font
    axes = [
        instance.weightValue,
        instance.widthValue,
        instance.customValue,
        instance.customValue1,
        instance.customValue2,
        instance.customValue3,
    ]
    instance.axes = {}
    for i, axis in enumerate(font.customParameters["Axes"]):
        instance.axes[axis["Tag"]] = axes[i]

    if opts.debug:
        fb.font.save(f"{instance.fontName}.otf")
        fb.font.saveXML(f"{instance.fontName}.ttx")

    return fb.font
Example #5
0
def test_subset_single_pos_format():
    fb = FontBuilder(unitsPerEm=1000)
    fb.setupGlyphOrder([".notdef", "a", "b", "c"])
    fb.setupCharacterMap({ord("a"): "a", ord("b"): "b", ord("c"): "c"})
    fb.setupNameTable({
        "familyName": "TestSingePosFormat",
        "styleName": "Regular"
    })
    fb.setupPost()
    fb.addOpenTypeFeatures("""
        feature kern {
            pos a -50;
            pos b -40;
            pos c -50;
        } kern;
    """)

    buf = io.BytesIO()
    fb.save(buf)
    buf.seek(0)

    font = TTFont(buf)

    # The input font has a SinglePos Format 2 subtable where each glyph has
    # different ValueRecords
    assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
        '<Lookup>',
        '  <LookupType value="1"/>',
        '  <LookupFlag value="0"/>',
        '  <!-- SubTableCount=1 -->',
        '  <SinglePos index="0" Format="2">',
        '    <Coverage Format="1">',
        '      <Glyph value="a"/>',
        '      <Glyph value="b"/>',
        '      <Glyph value="c"/>',
        '    </Coverage>',
        '    <ValueFormat value="4"/>',
        '    <!-- ValueCount=3 -->',
        '    <Value index="0" XAdvance="-50"/>',
        '    <Value index="1" XAdvance="-40"/>',
        '    <Value index="2" XAdvance="-50"/>',
        '  </SinglePos>',
        '</Lookup>',
    ]

    options = subset.Options()
    subsetter = subset.Subsetter(options)
    subsetter.populate(unicodes=[ord("a"), ord("c")])
    subsetter.subset(font)

    # All the subsetted glyphs from the original SinglePos Format2 subtable
    # now have the same ValueRecord, so we use a more compact Format 1 subtable.
    assert getXML(font["GPOS"].table.LookupList.Lookup[0].toXML, font) == [
        '<Lookup>',
        '  <LookupType value="1"/>',
        '  <LookupFlag value="0"/>',
        '  <!-- SubTableCount=1 -->',
        '  <SinglePos index="0" Format="1">',
        '    <Coverage Format="1">',
        '      <Glyph value="a"/>',
        '      <Glyph value="c"/>',
        '    </Coverage>',
        '    <ValueFormat value="4"/>',
        '    <Value XAdvance="-50"/>',
        '  </SinglePos>',
        '</Lookup>',
    ]
Example #6
0
def build(instance, isTTF, version):
    font = instance.parent
    source = font.masters[0]

    fea, marks = makeFeatures(instance, source)

    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
    """

    characterMap = {}
    glyphs = {}
    metrics = {}
    layerSet = {g.name: g.layers[source.id] for g in font.glyphs}

    if isTTF:
        from fontTools.pens.cu2quPen import Cu2QuPen
        from fontTools.pens.recordingPen import RecordingPen

        for glyph in font.glyphs:
            layer = glyph.layers[source.id]
            pen = RecordingPen()
            layer.draw(pen)
            layer.paths = []
            layer.components = []
            pen.replay(Cu2QuPen(layer.getPen(), 1.0, reverse_direction=True))

    for glyph in font.glyphs:
        if not glyph.export and not isTTF:
            continue
        name = glyph.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

        pen = BoundsPen(layerSet)
        layer.draw(pen)
        metrics[name] = (width, pen.bounds[0] if pen.bounds else 0)

        if isTTF:
            from fontTools.pens.ttGlyphPen import TTGlyphPen

            pen = TTGlyphPen(layerSet)
            if layer.paths:
                # Decompose and remove overlaps.
                path = Path()
                layer.draw(DecomposePathPen(path, layerSet))
                path.simplify(fix_winding=True, keep_starting_points=True)
                path.draw(pen)
            else:
                # Composite-only glyph, no need to decompose.
                layer.draw(FlattenComponentsPen(pen, layerSet))
            glyphs[name] = pen.glyph()
        else:
            from fontTools.pens.t2CharStringPen import T2CharStringPen

            # Draw glyph and remove overlaps.
            path = Path()
            layer.draw(DecomposePathPen(path, layerSet))
            path.simplify(fix_winding=True, keep_starting_points=True)

            # Build CharString.
            pen = T2CharStringPen(width, None)
            path.draw(pen)
            glyphs[name] = pen.getCharString()

    vendor = font.customParameters["vendorID"]
    names = {
        "copyright": font.copyright,
        "familyName": instance.familyName,
        "styleName": instance.name,
        "uniqueFontIdentifier": f"{version};{vendor};{instance.fontName}",
        "fullName": instance.fullName,
        "version": f"Version {version}",
        "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=isTTF)
    fb.updateHead(fontRevision=version, created=date, modified=date)
    fb.setupGlyphOrder(font.glyphOrder)
    fb.setupCharacterMap(characterMap)
    fb.setupNameTable(names, mac=False)
    fb.setupHorizontalHeader(
        ascent=source.ascender,
        descent=source.descender,
        lineGap=source.customParameters["typoLineGap"],
    )

    if isTTF:
        fb.setupGlyf(glyphs)
    else:
        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}",
            "Weight": instance.name,
        }

        fb.setupCFF(names["psName"], fontInfo, glyphs, privateDict)

    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),
    )

    underlineThickness = int(source.customParameters["underlineThickness"])
    underlinePosition = int(source.customParameters["underlinePosition"])
    fb.setupPost(
        keepGlyphNames=False,
        underlineThickness=underlineThickness,
        underlinePosition=underlinePosition + underlineThickness // 2,
    )

    fb.font["meta"] = meta = newTable("meta")
    meta.data = {"dlng": "Arab", "slng": "Arab"}

    fb.addOpenTypeFeatures(fea)

    if isTTF:
        from fontTools.ttLib.tables import ttProgram

        fb.setupDummyDSIG()

        fb.font["gasp"] = gasp = newTable("gasp")
        gasp.gaspRange = {0xFFFF: 15}

        fb.font["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
        prep.program.fromAssembly(assembly)
    else:
        from cffsubr import subroutinize

        subroutinize(fb.font)

    return fb.font
Example #7
0
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
Example #8
0
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
Example #9
0
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