JKQtPlotter/lib/jkqtmathtext/resources/xits/tools/sfdnormalize.py
2019-11-16 14:16:45 +01:00

235 lines
7.7 KiB
Python

#!/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")