#! /usr/bin/env python3

# Illustrates how a fonttools script can construct variable fonts.
#
# This script reads Roboto-Thin.ttf, Roboto-Regular.ttf, and
# Roboto-Black.ttf from /tmp/Roboto, and writes a Multiple Master GX
# font named "Roboto.ttf" into the current working directory.
# This output font supports interpolation along the Weight axis,
# and it contains named instances for "Thin", "Light", "Regular",
# "Bold", and "Black".
#
# All input fonts must contain the same set of glyphs, and these glyphs
# need to have the same control points in the same order. Note that this
# is *not* the case for the normal Roboto fonts that can be downloaded
# from Google. This demo script prints a warning for any problematic
# glyphs; in the resulting font, these glyphs will not be interpolated
# and get rendered in the "Regular" weight.
#
# Usage:
# $ mkdir /tmp/Roboto && cp Roboto-*.ttf /tmp/Roboto
# $ ./interpolate.py && open Roboto.ttf


from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._n_a_m_e import NameRecord
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis, NamedInstance
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r, TupleVariation
import logging


def AddFontVariations(font):
    assert "fvar" not in font
    fvar = font["fvar"] = table__f_v_a_r()

    weight = Axis()
    weight.axisTag = "wght"
    weight.nameID = AddName(font, "Weight").nameID
    weight.minValue, weight.defaultValue, weight.maxValue = (100, 400, 900)
    fvar.axes.append(weight)

    # https://www.microsoft.com/typography/otspec/os2.htm#wtc
    for name, wght in (
            ("Thin", 100),
            ("Light", 300),
            ("Regular", 400),
            ("Bold", 700),
            ("Black", 900)):
        inst = NamedInstance()
        inst.nameID = AddName(font, name).nameID
        inst.coordinates = {"wght": wght}
        fvar.instances.append(inst)


def AddName(font, name):
    """(font, "Bold") --> NameRecord"""
    nameTable = font.get("name")
    namerec = NameRecord()
    namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
    namerec.string = name.encode("mac_roman")
    namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
    nameTable.names.append(namerec)
    return namerec


def AddGlyphVariations(font, thin, regular, black):
    assert "gvar" not in font
    gvar = font["gvar"] = table__g_v_a_r()
    gvar.version = 1
    gvar.reserved = 0
    gvar.variations = {}
    for glyphName in regular.getGlyphOrder():
        regularCoord = GetCoordinates(regular, glyphName)
        thinCoord = GetCoordinates(thin, glyphName)
        blackCoord = GetCoordinates(black, glyphName)
        if not regularCoord or not blackCoord or not thinCoord:            
            logging.warning("glyph %s not present in all input fonts",
                            glyphName)
            continue
        if (len(regularCoord) != len(blackCoord) or
            len(regularCoord) != len(thinCoord)):
            logging.warning("glyph %s has not the same number of "
                            "control points in all input fonts", glyphName)
            continue
        thinDelta = []
        blackDelta = []
        for ((regX, regY), (blackX, blackY), (thinX, thinY)) in \
                zip(regularCoord, blackCoord, thinCoord):
            thinDelta.append(((thinX - regX, thinY - regY)))
            blackDelta.append((blackX - regX, blackY - regY))
        thinVar = TupleVariation({"wght": (-1.0, -1.0, 0.0)}, thinDelta)
        blackVar = TupleVariation({"wght": (0.0, 1.0, 1.0)}, blackDelta)
        gvar.variations[glyphName] = [thinVar, blackVar]


def GetCoordinates(font, glyphName):
    """font, glyphName --> glyph coordinates as expected by "gvar" table

    The result includes four "phantom points" for the glyph metrics,
    as mandated by the "gvar" spec.
    """
    glyphTable = font["glyf"]
    glyph = glyphTable.glyphs.get(glyphName)
    if glyph is None:
        return None
    glyph.expand(glyphTable)
    glyph.recalcBounds(glyphTable)
    if glyph.isComposite():
        coord = [c.getComponentInfo()[1][-2:] for c in glyph.components]
    else:
        coord = [c for c in glyph.getCoordinates(glyphTable)[0]]
    # Add phantom points for (left, right, top, bottom) positions.
    horizontalAdvanceWidth, leftSideBearing = font["hmtx"].metrics[glyphName]


    leftSideX = glyph.xMin - leftSideBearing
    rightSideX = leftSideX + horizontalAdvanceWidth

    # XXX these are incorrect.  Load vmtx and fix.
    topSideY = glyph.yMax
    bottomSideY = -glyph.yMin

    coord.extend([(leftSideX, 0),
                  (rightSideX, 0),
                  (0, topSideY),
                  (0, bottomSideY)])
    return coord


def main():
    logging.basicConfig(format="%(levelname)s: %(message)s")
    thin = TTFont("/tmp/Roboto/Roboto-Thin.ttf")
    regular = TTFont("/tmp/Roboto/Roboto-Regular.ttf")
    black = TTFont("/tmp/Roboto/Roboto-Black.ttf")
    out = regular
    AddFontVariations(out)
    AddGlyphVariations(out, thin, regular, black)
    out.save("./Roboto.ttf")


if __name__ == "__main__":
    import sys
    sys.exit(main())
