#!/usr/bin/env python # SFD normalizer (discards GUI information from SFD files) # (c) 2004, 2005 Stepan Roh (PUBLIC DOMAIN) # (c) 2009 Alexey Kryukov # (c) 2018 Khaled Hosny # (c) 2018 Skef Iterum # # usage: ./sfdnormalize.py sfd_file(s) # will rewrites files in place # changes done: # SplineFontDB - fix version number on 3.0 # WinInfo - discarded # DisplaySize - discarded # AntiAlias - discarded # FitToEm - discarded # Compacted - discarded # GenTags - discarded # Flags - discarded O (open), H (changed since last hinting - often irrelevant) # Refer - changed S (selected) to N (not selected) # Fore, Back, SplineSet, Grid # - all points have 4 masked out from flags (selected) # ModificationTime - discarded # Validated - discarded # Empty glyph positions dropped # Hinting dropped # !!! Always review changes done by this utility !!! from __future__ import print_function from collections import OrderedDict import sys, re fealines_tok = '__X_FEALINES_X__' FONT_RE = re.compile(r"^SplineFontDB:\s(\d+\.?\d*)") DROP_RE = re.compile(r"^(WinInfo|DisplaySize|AntiAlias|FitToEm|Compacted|GenTags|ModificationTime|DupEnc)") SPLINESET_RE = re.compile(r"^(Fore|Back|SplineSet|Grid)\s*$") STARTCHAR_RE = re.compile(r"^StartChar:\s*(\S+)\s*$") ENCODING_RE = re.compile(r"^Encoding:\s*(\d+)\s+(\-?\d+)\s*(\d*)\s*$") BITMAPFONT_RE = re.compile(r"^(BitmapFont:\s+\d+\s+)(\d+)(\s+\d+\s+\d+\s+\d+)") BDFCHAR_RE = re.compile(r"^BDFChar:\s*(\d+)(\s+.*)$") EMPTY_FLAGS_RE = re.compile(r"^Flags:\s*$") DROP_FLAGS_RE = re.compile(r"^(Flags:.*?)[HO](.*)$") SELECTED_POINT_RE = re.compile(r"(\s+[mcl]+?\s)(\d+)(\s*)$") SELECTED_REF_RE = re.compile(r"(-?\d+\s+)S(\s+-?\d+)") OTFFEATNAME_RE = re.compile(r"OtfFeatName:\s*'(....)'\s*(\d+)\s*(.*)$") FEASUBPOS_RE = re.compile(r"^(Position|PairPos|LCarets|Ligature|Substitution|MultipleSubs|AlternateSubs)2?:") fealine_order = {'Position': 1, 'PairPos': 2, 'LCarets': 3, 'Ligature': 4, 'Substitution': 5, 'MultipleSubs': 6, 'AlternateSubs': 7 } # The following class is used to emulate variable assignment in # conditions: while testing if a pattern corresponds to a specific # regular expression we also preserve the 'match' object for future use. class RegexpProcessor: def test(self, cp, string): self.m = cp.search(string) return not(self.m is None) def match(self): return self.m def clear_selected(m): pt = int(m.group(2)) & ~4; return m.group(1) + str(pt) + m.group(3) def process_sfd_file(sfdname, outname): fp = open(sfdname, 'rt') out = open(outname, 'wt') fl = fp.readline() proc = RegexpProcessor() if proc.test(FONT_RE, fl) == False: print("%s is not a valid spline font database" % sfdname) return out.write("SplineFontDB: 3.0\n") curglyph = '' cur_gid = 0 in_spline_set = False max_dec_enc = 0 max_unicode = 0 new_gid = 0 in_chars = False in_bdf = False bmp_header = () bdf = OrderedDict() glyphs = OrderedDict() feat_names = {} fl = fp.readline() while fl: if proc.test(DROP_RE, fl): fl = fp.readline() continue elif in_chars: # Cleanup glyph flags fl = DROP_FLAGS_RE.sub(r"\1\2", fl) fl = DROP_FLAGS_RE.sub(r"\1\2", fl) # If we have removed all previously specified glyph flags, # then don't output the "Flags" line for this glyph if proc.test(EMPTY_FLAGS_RE, fl): fl = fp.readline() continue if proc.test(SPLINESET_RE, fl): in_spline_set = True; elif fl.startswith("EndSplineSet"): in_spline_set = False; elif (in_spline_set): # Deselect selected points fl = SELECTED_POINT_RE.sub(clear_selected, fl) if fl.startswith("BeginChars:"): in_chars = True; elif fl.startswith("EndChars"): in_chars = False; out.write("BeginChars: %s %s\n" % (max_dec_enc + 1, len(glyphs))) for glyph in glyphs.values(): out.write("\n") out.write("StartChar: %s\n" % glyph['name']) out.write("Encoding: %s %s %s\n" % (glyph["dec_enc"], glyph['unicode'], glyph["gid"])) for gl in glyph['lines']: if gl.startswith("Refer: "): # deselect selected references gl = SELECTED_REF_RE.sub(r"\1N\2", gl) elif gl.endswith(" [ddx={} ddy={} ddh={} ddv={}]\n"): gl = gl.replace(" [ddx={} ddy={} ddh={} ddv={}]", "") elif gl == fealines_tok: for (flt, fll) in sorted(glyph['fealines']): out.write(fll) continue elif gl.startswith("Validated:"): continue out.write(gl) out.write("EndChar\n") out.write("EndChars\n") elif proc.test(STARTCHAR_RE, fl): curglyph = proc.match().group(1) glyph = { 'name' : curglyph, 'lines' : [] , 'fealines': [] } while curglyph in glyphs: curglyph = curglyph + '#' glyphs[curglyph] = glyph elif proc.test(ENCODING_RE, fl): dec_enc = int(proc.match().group(1)) unicode_enc = int(proc.match().group(2)) gid = int(proc.match().group(3)) max_dec_enc = max(max_dec_enc, dec_enc) max_unicode = max(max_unicode, unicode_enc) glyphs[curglyph]['dec_enc'] = dec_enc; glyphs[curglyph]['unicode'] = unicode_enc; glyphs[curglyph]['gid'] = gid; elif proc.test(FEASUBPOS_RE, fl): fea_type = proc.match().group(1) if len(glyphs[curglyph]['fealines']) == 0: glyphs[curglyph]['lines'].append(fealines_tok) glyphs[curglyph]['fealines'].append((fealine_order.get(fea_type, 0), fl)) elif fl.startswith("EndChar"): curglyph = ''; elif proc.test(BITMAPFONT_RE, fl): in_bdf = True; bdf_header = (proc.match().group(1), str(len(glyphs)), proc.match().group(3)) elif fl.startswith("EndBitmapFont"): out.write(''.join(bdf_header) + "\n") max_bdf = int(bdf_header[1]) for gid in range(0, max_bdf): if gid in bdf: for bdfl in bdf[gid]['lines']: out.write(bdfl) out.write("EndBitmapFont\n") in_bdf = False; bdf = {} bdf_header = () elif proc.test(BDFCHAR_RE, fl): cur_gid = int(proc.match().group(1)) bdf_char = { 'gid' : cur_gid, 'lines' : [] } bdf_char['lines'].append("BDFChar: " + str(cur_gid) + proc.match().group(2) + "\n") bdf[cur_gid] = bdf_char elif proc.test(OTFFEATNAME_RE, fl): while proc.test(OTFFEATNAME_RE, fl): tag, lang, name = proc.match().groups() feat_names[(tag, lang)] = name fl = fp.readline() for feat in sorted(feat_names): out.write("OtfFeatName: '%s' %s %s\n" % (feat[0], feat[1], feat_names[feat])) continue else: if not in_chars and not in_bdf: out.write(fl); elif in_chars and curglyph != '': glyphs[curglyph]['lines'].append(fl) elif in_bdf: bdf[cur_gid]['lines'].append(fl) fl = fp.readline() fp.close() out.close() # Program entry point argc = len(sys.argv) if argc > 2: process_sfd_file(sys.argv[1], sys.argv[2]) else: print("Usage: sfdnormalize.py input_file.sfd output_file.sfd")