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 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
#!/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:
# 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*(.*)$")
HINTS_RE = re.compile(r"^[HVD]Stem2?: ")
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(fl)
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 proc.test(HINTS_RE, gl):
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")
|