summaryrefslogtreecommitdiffstats
path: root/lib/silfont/scripts/psfdeleteglyphs.py
blob: 94caa3633865fbe62fb07c62d09f4a23d09e02d5 (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python
__doc__ = '''Deletes glyphs from a UFO based on list. Can instead delete glyphs not in list.'''
__url__ = 'http://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2018 SIL International (http://www.sil.org)'
__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
__author__ = 'Victor Gaultney'

from silfont.core import execute
from xml.etree import ElementTree as ET

argspec = [
    ('ifont', {'help': 'Input font file'}, {'type': 'infont'}),
    ('ofont', {'help': 'Output font file', 'nargs': '?'}, {'type': 'outfont'}),
    ('-i', '--input', {'help': 'Input text file, one glyphname per line'}, {'type': 'infile', 'def': 'glyphlist.txt'}),
    ('--reverse',{'help': 'Remove glyphs not in list instead', 'action': 'store_true', 'default': False},{}),
    ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': 'deletedglyphs.log'})]

def doit(args) :
    font = args.ifont
    listinput = args.input
    logger = args.logger

    glyphlist = []
    for line in listinput.readlines():
        glyphlist.append(line.strip())

    deletelist = []

    if args.reverse:
        for glyphname in font.deflayer:
            if glyphname not in glyphlist:
                deletelist.append(glyphname)
    else:
        for glyphname in font.deflayer:
            if glyphname in glyphlist:
                deletelist.append(glyphname)

    secondarylayers = [x for x in font.layers if x.layername != "public.default"]

    liststocheck = ('public.glyphOrder', 'public.postscriptNames', 'com.schriftgestaltung.glyphOrder')
    liblists = [[],[],[]]; inliblists = [[],[],[]]
    if hasattr(font, 'lib'):
        for (i,listn) in enumerate(liststocheck):
            if listn in font.lib:
                liblists[i] = font.lib.getval(listn)
    else:
        logger.log("No lib.plist found in font", "W")

    # Now loop round deleting the glyphs etc
    logger.log("Deleted glyphs:", "I")

    # With groups and kerning, create dicts representing then plists (to make deletion of members easier) and indexes by glyph/member name
    kgroupprefixes = {"public.kern1.": 1, "public.kern2.": 2}
    gdict = {}
    kdict = {}
    groupsbyglyph = {}
    ksetsbymember = {}

    groups = font.groups if hasattr(font, "groups") else []
    kerning = font.kerning if hasattr(font, "kerning") else []
    if groups:
        for gname in groups:
            group = groups.getval(gname)
            gdict[gname] = group
            for glyph in group:
                if glyph in groupsbyglyph:
                    groupsbyglyph[glyph].append(gname)
                else:
                    groupsbyglyph[glyph] = [gname]
    if kerning:
        for setname in kerning:
            kset = kerning.getval(setname)
            kdict[setname] = kset
            for member in kset:
                if member in ksetsbymember:
                    ksetsbymember[member].append(setname)
                else:
                    ksetsbymember[member] = [setname]

    # Loop round doing the deleting
    for glyphn in sorted(deletelist):
        # Delete from all layers
        font.deflayer.delGlyph(glyphn)
        deletedfrom = "Default layer"
        for layer in secondarylayers:
            if glyphn in layer:
                deletedfrom += ", " + layer.layername
                layer.delGlyph(glyphn)
        # Check to see if the deleted glyph is in any of liststocheck
        stillin = None
        for (i, liblist) in enumerate(liblists):
            if glyphn in liblist:
                inliblists[i].append(glyphn)
                stillin = stillin + ", " + liststocheck[i] if stillin else liststocheck[i]

        logger.log("  " + glyphn + " deleted from: " + deletedfrom, "I")
        if stillin: logger.log("  " + glyphn + " is still in " + stillin, "I")

        # Process groups.plist and kerning.plist

        tocheck = (glyphn, "public.kern1." + glyphn, "public.kern2." + glyphn)
        # First delete whole groups and kern pair sets
        for kerngroup in tocheck[1:]: # Don't check glyphn when deleting groups:
            if kerngroup in gdict: gdict.pop(kerngroup)
        for setn in tocheck:
            if setn in kdict: kdict.pop(setn)
        # Now delete members within groups and kern pair sets
        if glyphn in groupsbyglyph:
            for groupn in groupsbyglyph[glyphn]:
                if groupn in gdict: # Need to check still there, since whole group may have been deleted above
                    group = gdict[groupn]
                    del group[group.index(glyphn)]
        for member in tocheck:
            if member in ksetsbymember:
                for setn in ksetsbymember[member]:
                    if setn in kdict: del kdict[setn][member]
        # Now need to recreate groups.plist and kerning.plist
        if groups:
            for group in list(groups): groups.remove(group)  # Empty existing contents
            for gname in gdict:
                elem = ET.Element("array")
                if gdict[gname]: # Only create if group is not empty
                    for glyph in gdict[gname]:
                        ET.SubElement(elem, "string").text = glyph
                    groups.setelem(gname, elem)
        if kerning:
            for kset in list(kerning): kerning.remove(kset)  # Empty existing contents
            for kset in kdict:
                elem = ET.Element("dict")
                if kdict[kset]:
                    for member in kdict[kset]:
                        ET.SubElement(elem, "key").text = member
                        ET.SubElement(elem, "integer").text = str(kdict[kset][member])
                    kerning.setelem(kset, elem)

    logger.log(str(len(deletelist)) + " glyphs deleted. Set logging to I to see details", "P")
    inalist = set(inliblists[0] + inliblists[1] + inliblists[2])
    if inalist: logger.log(str(len(inalist)) + " of the deleted glyphs are still in some lib.plist entries.", "W")

    return font

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