afdko
afdko copied to clipboard
psstemhist – return Python object?
It would be quite useful to have the results of psstemhist available to Python scripting.
I have made some headway into a script (which I hoped to use to apply stem data to UFOs automatically). This script just reads the expected .txt files and converts the data within into Python objects.
Here we go:
'''
Show values found in .vstem and .hstem text files
created by psstemhist, and compare them to UFO stem data.
'''
import os
import re
import argparse
import plistlib
class StemInfo(object):
'''
save stem attributes in an object
'''
def __init__(self, value, count, glyph_list):
self.value = int(value)
self.count = int(count)
self.glyph_list = glyph_list
def get_args():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
'ufo_dir',
help='directory with UFOs',
action='store',
)
return parser.parse_args()
def read_stem_file(stem_file_path):
'''
Read a stem txt file and convert the data within into objects
'''
with open(stem_file_path, 'r') as sf:
# the first two lines are headers
stem_data = sf.read().splitlines()[2:]
stem_values = []
stem_objects = []
for line in stem_data:
line = line.strip()
stem_count = int(line.split()[0])
stem_value = int(line.split()[1])
glyphs = ' '.join(line.split()[2:])
glyphs = re.sub(r'[\[\]]', '', glyphs)
glyph_list = glyphs.split()
si = StemInfo(stem_value, stem_count, glyph_list)
stem_values.append(stem_value)
stem_objects.append(si)
return stem_objects
def get_stems_from_files(ufo_path):
'''
Find hstm.txt and vstm.txt files adjacent to UFO, read an convert them.
'''
h_stem_file = ufo_path + '.hstm.txt'
v_stem_file = ufo_path + '.vstm.txt'
if os.path.exists(h_stem_file):
h_stems = read_stem_file(h_stem_file)
else:
print(h_stem_file, 'does not exist')
h_stems = []
if os.path.exists(v_stem_file):
v_stems = read_stem_file(v_stem_file)
else:
print(v_stem_file, 'does not exist')
v_stems = []
return v_stems, h_stems
def fancy_box(my_string):
'''
surround a string with a box
'''
return (
'┌' + '─' * (len(my_string) + 2) + '┐\n'
'│' + ' ' + my_string + ' ' + '│\n'
'└' + '─' * (len(my_string) + 2) + '┘'
)
def stem_synopsis(stem):
'''
nicely-formatted summary of stem attributes
'''
return '\t{} | {} | ({})'.format(
str(stem.value).rjust(4),
' '.join(stem.glyph_list),
stem.count)
def get_existing_stems(ufo):
'''
find which stem values may already exist in a UFO file
'''
fi_pl_path = os.path.join(ufo, 'fontinfo.plist')
with open(fi_pl_path, 'rb') as fi_plist:
fontinfo_dict = plistlib.load(fi_plist)
vstm_existing = fontinfo_dict.get('postscriptStemSnapV', [])
hstm_existing = fontinfo_dict.get('postscriptStemSnapH', [])
return vstm_existing, hstm_existing
args = get_args()
ufos = []
for root, folders, files in os.walk(args.ufo_dir):
for folder in folders:
if re.match(r'.*\.ufo', folder) and folder != 'font.ufo':
ufos.append((os.path.join(root, folder)))
ufos.sort()
for ufo in ufos:
print(fancy_box(os.path.basename(ufo)))
vstm_existing, hstm_existing = get_existing_stems(ufo)
vstm_stemhist, hstm_stemhist = get_stems_from_files(ufo)
print('existing V stems:', vstm_existing)
sorted_v_stems = sorted(
vstm_stemhist, key=lambda s: s.count, reverse=True)
for stem in sorted_v_stems:
if stem.count > 1:
print(stem_synopsis(stem))
print('existing H stems:', hstm_existing)
sorted_h_stems = sorted(
hstm_stemhist, key=lambda s: s.count, reverse=True)
for stem in sorted_h_stems:
if stem.count > 1:
print(stem_synopsis(stem))
print('-' * 80)
The result looks something like this:
➜ scriptjes python apply_stemhist.py /Users/fgriessh/work/source-serif-pro
┌───────────────────┐
│ SourceSerif_1.ufo │
└───────────────────┘
existing V stems: [85, 95]
84 | S a b d h k l m n q one eight | (13)
95 | B D E F H I L P R o | (11)
85 | L S f h i m n p r t | (10)
81 | b d f g h j q s | (9)
90 | b d e p q two five nine | (8)
94 | B G H Q T U Y | (7)
89 | J a t three six nine | (6)
83 | e f u four eight | (6)
76 | E F T u | (6)
109 | C D G O Q | (5)
229 | A X Y | (4)
82 | a g p | (3)
51 | N U | (3)
existing H stems: [56, 41]
47 | A B D E F H I J K L M P Q R T U W X Y g | (42)
46 | A B D H K P R U V W X Y a c e o s | (20)
50 | C E F G L O Q S T V Z three | (18)
45 | G N Q R o s zero two three five six eight nine | (15)
36 | f h i k l m n p q r x y one | (13)
41 | K N U V W X Y g | (9)
57 | b d p q | (8)
178 | E T Z | (4)
39 | k v w x | (4)
82 | two five seven | (3)
72 | h m n | (3)
65 | c e u | (3)
42 | e y z | (3)
177 | E F | (2)
128 | i j | (2)
71 | U u | (2)
61 | five six | (2)
58 | d u | (2)
56 | Q R | (2)
55 | f t | (2)
53 | f t | (2)
52 | a t | (2)
43 | G z | (2)
40 | g y | (2)
--------------------------------------------------------------------------------
┌───────────────────┐
│ SourceSerif_2.ufo │
└───────────────────┘
existing V stems: [190, 200]
190 | B b d f h i j k l m n p q r t u two nine | (21)
198 | D E F H I L P T U Y | (12)
205 | C G O Q W | (7)
200 | D M c e o five | (7)
202 | B b d e p q | (6)
188 | J N v six | (5)
187 | b h p q | (4)
186 | G m y | (4)
185 | R x three nine | (4)
165 | G g four | (3)
70 | M N | (3)
existing H stems: [74, 60]
63 | A B D E F G H I J K L M N P R S T U W X Y | (37)
59 | A C E F G K N T V W Y s y | (16)
62 | B D H K P R U V W X | (14)
51 | f h i k l m n r w x y one | (13)
60 | C L O P Q R V Z | (9)
54 | V a c e x zero two three nine | (9)
55 | o zero three five six eight | (8)
64 | E F J M P W | (7)
61 | A K O X Y | (7)
47 | k v w x y z | (6)
99 | U b d p q | (5)
107 | h m n u | (4)
96 | b d p q | (4)
189 | T Z | (3)
170 | E F | (3)
140 | two five seven | (3)
52 | k p q | (3)
46 | g y | (3)
172 | i j | (2)
152 | g y | (2)
108 | c e | (2)
75 | f t | (2)
72 | u six | (2)
66 | N | (2)
65 | M g | (2)
58 | E three | (2)
56 | B x | (2)
50 | w x | (2)
--------------------------------------------------------------------------------
@frankrolf are you saying you would like to have this functionality integrated into the psautohint package, so you could do something like import psautohint and then set-up and run the stemhist functionality, which would return some Python object containing these results? That seems totally feasible. I guess I would leave the text formatting of the values to the user, though 😺
@frankrolf the snippet below prints the dictionary of the values you're after. This works with psautohint v2.1.2, but beware that the get_fontinfo_list call will need to be updated once the change made in adobe-type-tools/psautohint#276 ships.
from psautohint import FontParseError
from psautohint.autohint import (
openFile,
get_fontinfo_list,
get_glyph_list,
get_glyph_reports,
)
class PSAutohintOptions(object):
def __init__(self):
self.read_hints = False
self.allowChanges = False
self.writeToDefaultLayer = False
self.noHintSub = False
self.excludeGlyphList = []
self.hCounterGlyphs = []
self.vCounterGlyphs = []
self.round_coords = True
self.hintAll = True
self.noFlex = True
self.allow_no_blues = True
self.logOnly = True
self.printDefaultFDDict = False
self.printFDDictList = False
self.inputPaths = []
self.outputPaths = []
self.report_all_stems = True
# For zones instead of stems adjust the values below
self.report_zones = False
self.report_stems = True
font_file_path = 'myfont.ufo'
glyph_names_list = ['A', 'a', 'ampersand']
options = PSAutohintOptions()
options.glyphList = glyph_names_list
try:
psfont = openFile(font_file_path, options)
glyph_list = get_glyph_list(options, psfont, font_file_path)
fontinfo_list = get_fontinfo_list(
options, psfont, None, glyph_list, False)
reports = get_glyph_reports(
options, psfont, glyph_list, fontinfo_list)
print(reports.glyphs)
except FontParseError as e:
print(e)
This should be a simple matter of plumbing now -- deciding on what arguments to use to retrieve the GlyphReport object.