summaryrefslogtreecommitdiffstats
path: root/src/silfont/scripts/psfcopymeta.py
blob: 8b67505f124e73a81262f36adb565ac55a0501c1 (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
145
146
147
148
#!/usr/bin/env python3
__doc__ = '''Copy metadata between fonts in different (related) families
Usually run against the master (regular) font in each family then data synced within family afterwards'''
__url__ = 'https://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2017 SIL International (https://www.sil.org)'
__license__ = 'Released under the MIT License (https://opensource.org/licenses/MIT)'
__author__ = 'David Raymond'

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

argspec = [
    ('fromfont',{'help': 'From font file'}, {'type': 'infont'}),
    ('tofont',{'help': 'To font file'}, {'type': 'infont'}),
    ('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_copymeta.log'}),
    ('-r','--reportonly', {'help': 'Report issues but no updating', 'action': 'store_true', 'default': False},{})
    ]

def doit(args) :

    fields = ["copyright", "openTypeNameDescription", "openTypeNameDesigner", "openTypeNameDesignerURL", "openTypeNameLicense", # General feilds
                "openTypeNameLicenseURL", "openTypeNameManufacturer", "openTypeNameManufacturerURL", "openTypeOS2CodePageRanges",
                "openTypeOS2UnicodeRanges", "openTypeOS2VendorID", "trademark",
                "openTypeNameVersion", "versionMajor", "versionMinor", # Version fields
                "ascender", "descender", "openTypeHheaAscender", "openTypeHheaDescender", "openTypeHheaLineGap", # Design fields
                "openTypeOS2TypoAscender", "openTypeOS2TypoDescender", "openTypeOS2TypoLineGap", "openTypeOS2WinAscent", "openTypeOS2WinDescent"]
    libfields = ["public.postscriptNames", "public.glyphOrder", "com.schriftgestaltung.glyphOrder"]

    fromfont = args.fromfont
    tofont = args.tofont
    logger = args.logger
    reportonly = args.reportonly

    updatemessage = " to be updated: " if reportonly else " updated: "
    precision = fromfont.paramset["precision"]
    # Increase screen logging level to W unless specific level supplied on command-line
    if not(args.quiet or "scrlevel" in args.paramsobj.sets["command line"]) : logger.scrlevel = "W"

    # Process fontinfo.plist
    ffi = fromfont.fontinfo
    tfi = tofont.fontinfo
    fupdated = False
    for field in fields:
        if field in ffi :
            felem = ffi[field][1]
            ftag = felem.tag
            ftext = felem.text
            if ftag == 'real' : ftext = processnum(ftext,precision)
            message = field + updatemessage

            if field in tfi : # Need to compare values to see if update is needed
                telem = tfi[field][1]
                ttag = telem.tag
                ttext = telem.text
                if ttag == 'real' : ttext = processnum(ttext,precision)

                if ftag in ("real", "integer", "string") :
                    if ftext != ttext :
                        if field == "openTypeNameLicense" : # Too long to display all
                            addmess = " Old: '" + ttext[0:80] + "...' New: '" + ftext[0:80] + "...'"
                        else: addmess = " Old: '" + ttext + "' New: '" + str(ftext) + "'"
                        telem.text = ftext
                        logger.log(message + addmess, "W")
                        fupdated = True
                elif ftag in ("true, false") :
                    if ftag != ttag :
                        fti.setelem(field, ET.fromstring("<" + ftag + "/>"))
                        logger.log(message + " Old: '" + ttag + "' New: '" + str(ftag) + "'", "W")
                        fupdated = True
                elif ftag == "array" : # Assume simple array with just values to compare
                    farray = []
                    for subelem in felem : farray.append(subelem.text)
                    tarray = []
                    for subelem in telem : tarray.append(subelem.text)
                    if farray != tarray :
                        tfi.setelem(field, ET.fromstring(ET.tostring(felem)))
                        logger.log(message + "Some values different Old: " + str(tarray) + " New: " + str(farray), "W")
                        fupdated = True
                else : logger.log("Non-standard fontinfo field type: "+ ftag + " in " + fontname, "S")
            else :
                tfi.addelem(field, ET.fromstring(ET.tostring(felem)))
                logger.log(message + "is missing from destination font so will be copied from source font", "W")
                fupdated = True
        else: # Field not in from font
            if field in tfi :
                logger.log( field +  " is missing from source font but present in destination font", "E")
            else :
                logger.log( field +  " is in neither font", "W")

    # Process lib.plist - currently just public.postscriptNames and glyph order fields which are all simple dicts or arrays
    flib = fromfont.lib
    tlib = tofont.lib
    lupdated = False
    for field in libfields:
        action = None
        if field in flib:
            if field in tlib:  # Need to compare values to see if update is needed
                if flib.getval(field) != tlib.getval(field):
                    action = "Updatefield"
            else:
                action = "Copyfield"
        else:
            action = "Error" if field == ("public.GlyphOrder", "public.postscriptNames") else "Warn"
            issue = field + " not in source font lib.plist"

        # Process the actions, create log messages etc
        if action is None or action == "Ignore":
            pass
        elif action == "Warn":
            logger.log(field + " needs manual correction: " + issue, "W")
        elif action == "Error":
            logger.log(field + " needs manual correction: " + issue, "E")
        elif action in ("Updatefield", "Copyfield"):  # Updating actions
            lupdated = True
            message = field + updatemessage
            if action == "Copyfield":
                message = message + "is missing so will be copied from source font"
                tlib.addelem(field, ET.fromstring(ET.tostring(flib[field][1])))
            elif action == "Updatefield":
                message = message + "Some values different"
                tlib.setelem(field, ET.fromstring(ET.tostring(flib[field][1])))
            logger.log(message, "W")
        else:
            logger.log("Uncoded action: " + action + " - oops", "X")

    # Now update on disk
    if not reportonly:
        if fupdated:
            logger.log("Writing updated fontinfo.plist", "P")
            UFO.writeXMLobject(tfi, tofont.outparams, tofont.ufodir, "fontinfo.plist", True, fobject=True)
        if lupdated:
            logger.log("Writing updated lib.plist", "P")
            UFO.writeXMLobject(tlib, tofont.outparams, tofont.ufodir, "lib.plist", True, fobject=True)

    return


def processnum(text, precision) : # Apply same processing to real numbers that normalization will
    if precision is not None:
        val = round(float(text), precision)
        if val == int(val) : val = int(val) # Removed trailing decimal .0
        text = str(val)
    return text


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