mirror of
https://github.com/jkriege2/JKQtPlotter.git
synced 2024-11-16 02:25:50 +08:00
235 lines
7.7 KiB
Python
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")
|
||
|
|