xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/interpolate_layout.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1"""
2Interpolate OpenType Layout tables (GDEF / GPOS / GSUB).
3"""
4
5from fontTools.ttLib import TTFont
6from fontTools.varLib import models, VarLibError, load_designspace, load_masters
7from fontTools.varLib.merger import InstancerMerger
8import os.path
9import logging
10from copy import deepcopy
11from pprint import pformat
12
13log = logging.getLogger("fontTools.varLib.interpolate_layout")
14
15
16def interpolate_layout(designspace, loc, master_finder=lambda s: s, mapped=False):
17    """
18    Interpolate GPOS from a designspace file and location.
19
20    If master_finder is set, it should be a callable that takes master
21    filename as found in designspace file and map it to master font
22    binary as to be opened (eg. .ttf or .otf).
23
24    If mapped is False (default), then location is mapped using the
25    map element of the axes in designspace file.  If mapped is True,
26    it is assumed that location is in designspace's internal space and
27    no mapping is performed.
28    """
29    if hasattr(designspace, "sources"):  # Assume a DesignspaceDocument
30        pass
31    else:  # Assume a file path
32        from fontTools.designspaceLib import DesignSpaceDocument
33
34        designspace = DesignSpaceDocument.fromfile(designspace)
35
36    ds = load_designspace(designspace)
37    log.info("Building interpolated font")
38
39    log.info("Loading master fonts")
40    master_fonts = load_masters(designspace, master_finder)
41    font = deepcopy(master_fonts[ds.base_idx])
42
43    log.info("Location: %s", pformat(loc))
44    if not mapped:
45        loc = {name: ds.axes[name].map_forward(v) for name, v in loc.items()}
46    log.info("Internal location: %s", pformat(loc))
47    loc = models.normalizeLocation(loc, ds.internal_axis_supports)
48    log.info("Normalized location: %s", pformat(loc))
49
50    # Assume single-model for now.
51    model = models.VariationModel(ds.normalized_master_locs)
52    assert 0 == model.mapping[ds.base_idx]
53
54    merger = InstancerMerger(font, model, loc)
55
56    log.info("Building interpolated tables")
57    # TODO GSUB/GDEF
58    merger.mergeTables(font, master_fonts, ["GPOS"])
59    return font
60
61
62def main(args=None):
63    """Interpolate GDEF/GPOS/GSUB tables for a point on a designspace"""
64    from fontTools import configLogger
65    import argparse
66    import sys
67
68    parser = argparse.ArgumentParser(
69        "fonttools varLib.interpolate_layout",
70        description=main.__doc__,
71    )
72    parser.add_argument(
73        "designspace_filename", metavar="DESIGNSPACE", help="Input TTF files"
74    )
75    parser.add_argument(
76        "locations",
77        metavar="LOCATION",
78        type=str,
79        nargs="+",
80        help="Axis locations (e.g. wdth=120",
81    )
82    parser.add_argument(
83        "-o",
84        "--output",
85        metavar="OUTPUT",
86        help="Output font file (defaults to <designspacename>-instance.ttf)",
87    )
88    parser.add_argument(
89        "-l",
90        "--loglevel",
91        metavar="LEVEL",
92        default="INFO",
93        help="Logging level (defaults to INFO)",
94    )
95
96    args = parser.parse_args(args)
97
98    if not args.output:
99        args.output = os.path.splitext(args.designspace_filename)[0] + "-instance.ttf"
100
101    configLogger(level=args.loglevel)
102
103    finder = lambda s: s.replace("master_ufo", "master_ttf_interpolatable").replace(
104        ".ufo", ".ttf"
105    )
106
107    loc = {}
108    for arg in args.locations:
109        tag, val = arg.split("=")
110        loc[tag] = float(val)
111
112    font = interpolate_layout(args.designspace_filename, loc, finder)
113    log.info("Saving font %s", args.output)
114    font.save(args.output)
115
116
117if __name__ == "__main__":
118    import sys
119
120    if len(sys.argv) > 1:
121        sys.exit(main())
122    import doctest
123
124    sys.exit(doctest.testmod().failed)
125