summaryrefslogtreecommitdiffstats
path: root/lib/silfont/scripts/psftuneraliases.py
blob: f71d0974fd90c6e55313e89b3f21b9cf56c88f29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/env python
__doc__ = '''Merge lookup and feature aliases into TypeTuner feature file'''
__url__ = 'http://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2019 SIL International  (http://www.sil.org)'
__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
__author__ = 'Bob Hallissy'

from silfont.core import execute
from xml.etree import ElementTree as ET
from fontTools import ttLib
import csv
import struct

argspec = [
    ('input', {'help': 'Input TypeTuner feature file'}, {'type': 'infile'}),
    ('output', {'help': 'Output TypeTuner feature file'}, {}),
    ('-m','--mapping', {'help': 'Input csv mapping file'}, {'type': 'incsv'}),
    ('-f','--ttf', {'help': 'Compiled TTF file'}, {}),
    ('-l','--log',{'help': 'Optional log file'}, {'type': 'outfile', 'def': '_tuneraliases.log', 'optlog': True}),
    ]

def doit(args) :
    logger = args.logger

    if args.mapping is None and args.ttf is None:
        logger.log("One or both of -m and -f must be provided", "S")
    featdoc = ET.parse(args.input)
    root = featdoc.getroot()
    if root.tag != 'all_features':
        logger.log("Invalid TypeTuner feature file: missing root element", "S")

    # Whitespace to add after each new alias:
    tail = '\n\t\t'

    # Find or add alliaes element
    aliases = root.find('aliases')
    if aliases is None:
        aliases = ET.SubElement(root,'aliases')
        aliases.tail = '\n'

    added = set()
    duplicates = set()
    def setalias(name, value):
        # detect duplicate names in input
        if name in added:
            duplicates.add(name)
        else:
            added.add(name)
        # modify existing or add new alias
        alias = aliases.find('alias[@name="{}"]'.format(name))
        if alias is None:
            alias = ET.SubElement(aliases, 'alias', {'name': name, 'value': value})
            alias.tail = tail
        else:
            alias.set('value', value)

    # Process mapping file if present:
    if args.mapping:
        # Mapping file is assumed to come from psfbuildfea, and should look like:
        #      lookupname,table,index
        # e.g. DigitAlternates,GSUB,51
        for (name,table,value) in args.mapping:
            setalias(name, value)

    # Process the ttf file if present
    if args.ttf:
        # Generate aliases for features.
        # In this code featureID means the key used in FontUtils for finding the feature, e.g., "calt _2"
        def dotable(t):     # Common routine for GPOS and GSUB
            currtag = None
            currtagindex = None
            flist = []     # list, in order, of (featureTag, featureID), per Font::TTF
            for i in range(0,t.FeatureList.FeatureCount):
                newtag = str(t.FeatureList.FeatureRecord[i].FeatureTag)
                if currtag is None or currtag != newtag:
                    flist.append((newtag, newtag))
                    currtag = newtag
                    currtagindex = 0
                else:
                    flist.append( (currtag, '{} _{}'.format(currtag, currtagindex)))
                    currtagindex += 1
            fslList = {}     # dictionary keyed by feature_script_lang values returning featureID
            for s in t.ScriptList.ScriptRecord:
                currtag = str(s.ScriptTag)
                # At present only looking at the dflt lang entries
                for findex in s.Script.DefaultLangSys.FeatureIndex:
                    fslList['{}_{}_dflt'.format(flist[findex][0],currtag)] = flist[findex][1]
            # Now that we have them all, add them in sorted order.
            for name, value in sorted(fslList.items()):
                setalias(name,value)

        # Open the TTF for processing
        try:
            f = ttLib.TTFont(args.ttf)
        except Exception as e:
            logger.log("Couldn't open font '{}' for reading : {}".format(args.ttf, str(e)),"S")
        # Grab features from GSUB and GPOS
        for tag in ('GSUB', 'GPOS'):
            try:
                dotable(f[tag].table)
            except Exception as e:
                logger.log("Failed to process {} table: {}".format(tag, str(e)), "W")
        # Grab features from Graphite:
        try:
            for tag in sorted(f['Feat'].features.keys()):
                if tag == '1':
                    continue
                name = 'gr_' + tag
                value = str(struct.unpack('>L', tag.encode())[0])
                setalias(name,value)
        except Exception as e:
            logger.log("Failed to process Feat table: {}".format(str(e)), "W")

    if len(duplicates):
        logger.log("The following aliases defined more than once in input: {}".format(", ".join(sorted(duplicates))), "S")

    # Success. Write the result
    featdoc.write(args.output, encoding='UTF-8', xml_declaration=True)

def cmd() : execute(None,doit,argspec)
if __name__ == "__main__": cmd()