Download raw (13.8 KB)
# -*- coding: utf-8 -*-
# Glyphtracer
# Copyright (C) 2010 Jussi Pakkanen
# version 1.4 (c) 2015 Stéphanie Vilayphiou
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Glyphtracer library files and stuff
import os, subprocess, tempfile
import fontforge, psMat
BLANK_FONT = "../utils/blank.sfd"
font = fontforge.open(BLANK_FONT)
program_name = 'Glyphtracer'
program_version = '1.4'
def entry_to_upper(e):
return (e[0].capitalize(), e[1]-32)
# The format of letter lists is as follows:
#
# Each element is a tuple. The first element is a string with the
# glyph's name *as used by Fontforge*. Not the unicode name
# or anything else. It is always in pure ASCII. The
# second element is the corresponding Unicode code point.
# These are read only lists that define different glyph groups.
# In the future they may be parsed from a conf file.
latin_lowercase_list = [('a', 97), ('b', 98), ('c', 99), ('d', 100), ('e', 101), ('f', 102),\
('g', 103), ('h', 104), ('i', 105), ('j', 106), ('k', 107), ('l', 108),\
('m', 109), ('n', 110), ('o', 111), ('p', 112), ('q', 113), ('r', 114),\
('s', 115), ('t', 116), ('u', 117), ('v', 118), ('w', 119), ('x', 120),\
('y', 121), ('z', 122)]
latin_uppercase_list = [entry_to_upper(x) for x in latin_lowercase_list]
latin_accented_lower_list = [('agrave', 224), ('aacute', 225), ('acircumflex', 226),\
('atilde', 227), ('adieresis', 228), ('aring', 229),\
('egrave', 232), ('eacute', 233), ('ecircumflex', 234),\
('edieresis', 235), ('igrave', 236), ('iacute', 237),\
('icircumflex', 238), ('idieresis', 239), ('ntilde', 241),\
('ograve', 242), ('oacute', 243), ('ocircumflex', 244),\
('otilde', 245), ('odieresis', 246), ('ugrave', 249),\
('uacute', 250), ('ucircumflex', 251), ('udieresis', 252),\
('yacute', 253), ('ydieresis', 255)]
# Most lower case letters are at a fixed distance from their upper case variants.
# Some are not, thus some of these lists will appear a bit messy.
latin_accented_upper_list = [entry_to_upper(x) for x in latin_accented_lower_list[:-1]] \
+ [('Ydieresis', 376)]
latin_diacritics_list = [('asciicircum', 94), ('grave', 96), ('dieresis', 168), ('macron', 175), \
('acute', 180), ('cedilla', 184)]
latin_extra_lower_list = [('ccedilla', 231), ('eth', 240), ('oslash', 248),\
('thorn', 254), ('ae', 230), ('oe', 339), ('germandbls', 223)]
latin_extra_upper_list = [entry_to_upper(x) for x in latin_extra_lower_list[:-3]]\
+ [('AE', 198), ('OE', 338)]
number_list = [('zero', 48), ('one', 49), ('two', 50), ('three', 51), ('four', 52), ('five', 53),\
('six', 54), ('seven', 55), ('eight', 56), ('nine', 57), ('onequarter', 188), ('onehalf', 189), ('threequarters', 190),\
('uni2160', 8544), ('uni2161', 8545),\
('uni2162', 8546), ('uni2163', 8547), ('uni2164', 8548), ('uni2165', 8549), ('uni2166', 8550), ('uni2167', 8551),\
('uni2168', 8552), ('uni2169', 8553), ('uni216A', 8554), ('uni216B', 8555), ('uni216C', 8556), ('uni216D', 8557), ('uni216E', 8558), ('uni216F', 8559)]
punctuation_list = [('exclam', 33), ('exclamdown', 161), ('question', 63), ('questiondown', 191),\
('period', 46), ('comma', 44), ('colon', 58), ('semicolon', 59),\
('slash', 47), ('backslash', 92), ('hyphen', 45), ('underscore', 95),\
('endash', 8211), ('emdash', 8212), ('ellipsis', 8230), ('periodcentered', 183)]
brackets_list = [('parenleft', 40), ('parenright', 41), ('bracketleft', 91), ('bracketright', 93),\
('braceleft', 123), ('braceright', 125), ('less', 60), ('greater', 62)]
quotation_list = [('quotesingle', 39,), ('quotedbl', 34), ('quoteleft', 8216), ('quoteright', 8217),\
('quotesinglbase', 8218), ('quotedblleft', 8220), ('quotedblright', 8221),\
('quotedblbase', 8222), ('guillemotleft', 171), ('guillemotright', 187),\
('guilsinglleft', 8249), ('guilsinglright', 8250),]
symbol_list = [('numbersign', 35), ('percent', 37), ('ampersand', 38), ('asterisk', 42),\
('plus', 43), ('multiply', 215), ('divide', 247), ('equal', 61), ('at', 64),\
('asciitilde', 126), ('copyright', 169), ('registered', 174),\
('trademark', 8482), ('paragraph', 182), ('section', 167), ('bar', 124), ('brokenbar', 166),\
('uniFFFD', 65533), ('ordfeminine', 170), ('logicalnot', 172), ('degree', 176),\
('plusminus', 177), ('uni00B2', 178), ('uni00B3', 179), ('mu', 181), ('uni00B9', 185), ('uni00BA', 186)]
currency_list = [('dollar', 36), ('cent', 162), ('Euro', 8364), ('sterling', 163),\
('yen', 165), ('currency', 164)]
cyrillic_upper = [('afii10017', 1040), ('afii10018', 1041), ('afii10019', 1042),\
('afii10020', 1043), ('afii10021', 1044), ('afii10022', 1045),\
('afii10024', 1046), ('afii10025', 1047), ('afii10026', 1048),\
('afii10027', 1049), ('afii10028', 1050), ('afii10029', 1051),\
('afii10030', 1052), ('afii10031', 1053), ('afii10032', 1054),\
('afii10033', 1055), ('afii10034', 1056), ('afii10035', 1057),\
('afii10036', 1058), ('afii10037', 1059), ('afii10038', 1060),\
('afii10039', 1061), ('afii10040', 1062), ('afii10041', 1063),\
('afii10042', 1064), ('afii10043', 1065), ('afii10044', 1066),\
('afii10045', 1067), ('afii10046', 1068), ('afii10047', 1169),\
('afii10048', 1070), ('afii10049', 1071)]
cyrillic_lower = [('afii10065', 1072), ('afii10066', 1073), ('afii10067', 1074),\
('afii10068', 1075), ('afii10069', 1076), ('afii10070', 1077),\
('afii10072', 1078), ('afii10073', 1079), ('afii10073', 1080),\
('afii10075', 1081), ('afii10076', 1082), ('afii10077', 1083),\
('afii10078', 1084), ('afii10079', 1085), ('afii10080', 1086),\
('afii10081', 1087), ('afii10082', 1088), ('afii10083', 1089),\
('afii10084', 1090), ('afii10085', 1091), ('afii10086', 1092),\
('afii10089', 1093), ('afii10088', 1094), ('afii10089', 1095),\
('afii10090', 1096), ('afii10091', 1097), ('afii10092', 1098),\
('afii10093', 1099), ('afii10094', 1100), ('afii10095', 1101),\
('afii10096', 1102), ('afii10097', 1103)]
glyph_groups = [('latin lower case', latin_lowercase_list),\
('latin upper case', latin_uppercase_list),\
('latin accented lower case', latin_accented_lower_list),\
('latin accented upper case', latin_accented_upper_list),\
('latin diacritics', latin_diacritics_list),\
('latin extra lower case', latin_extra_lower_list),
('latin extra upper case', latin_extra_upper_list),
('numbers', number_list),\
('brackets', brackets_list),\
('punctuation', punctuation_list),\
('quotation', quotation_list),\
('symbols', symbol_list),\
('currency', currency_list),\
('cyrillic lowercase', cyrillic_lower),\
('cyrillic uppercase', cyrillic_upper)]
class LetterBox(object):
def __init__(self, rectangle):
self.r = rectangle
self.taken = False
def contains(self, x, y):
return self.r.contains(x, y)
class GlyphInfo(object):
def __init__(self, name, codepoint):
self.name = name
self.codepoint = codepoint
self.box = None
def i_haz_potrace():
p = subprocess.Popen('potrace -h', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
return p.returncode == 0
def data_to_glyphinfo(data):
return GlyphInfo(data[0], data[1])
def integerise(command_line):
return [int(x) for x in command_line.split()[0:-1]]
def parse_postscript(commands):
point_sets = []
points = []
assert(commands[0].endswith('moveto'))
for cmd in commands:
if cmd.endswith('moveto'):
assert(len(points) == 0)
points.append(integerise(cmd))
elif cmd.endswith('rcurveto'):
points.append(integerise(cmd))
elif cmd.endswith('rlineto'):
points.append(integerise(cmd))
elif cmd.endswith('closepath'):
point_sets.append(points)
points = []
elif cmd == 'fill':
pass # There is more than one blob in the image. But that's ok.
else:
raise RuntimeError('Unknown PostScript command: ' + cmd)
assert(len(points) == 0)
return point_sets
def potrace_image(filename):
svg = filename + ".svg"
p = subprocess.Popen('autotrace -output-file=' + svg + ' ' + filename, shell=True, stdout=subprocess.PIPE)
(so, se) = p.communicate()
return svg
def crop_and_trace(image, box, codepoint):
tfile = tempfile.NamedTemporaryFile(suffix='.png')
tempname = tfile.name
cropped = image.copy(box)
if not cropped.save(tempname):
raise RuntimeError('Could not save cropped image')
#svg = potrace_image(tempname)
font[codepoint].importOutlines(tempname)
font[codepoint].autoTrace()
#os.unlink(tempname)
tfile.close()
return
def convert_points(pointlist):
pointlist = to_absolute(pointlist)
return flip_curve(pointlist)
def to_absolute(pointlist):
starting_point = pointlist[0]
assert(len(starting_point) == 2)
converted = [starting_point]
current_point = starting_point
for p in pointlist[1:]:
if len(p) == 2:
newp = [current_point[0] + p[0], current_point[1] + p[1]]
elif len(p) == 6:
newp = [0]*6
newp[0] = current_point[0] + p[0]
newp[1] = current_point[1] + p[1]
newp[2] = current_point[0] + p[2]
newp[3] = current_point[1] + p[3]
newp[4] = current_point[0] + p[4]
newp[5] = current_point[1] + p[5]
else:
raise RuntimeError('Unknown point size error.')
converted.append(newp)
current_point = [newp[-2], newp[-1]]
return converted
def flip_curve(curve):
first = curve[0]
last = curve[-1]
assert(first[0] == last[-2])
assert(first[1] == last[-1])
flipped = [first]
for i in range(len(curve))[:0:-1]:
curp = curve[i]
if i == 0:
prevp = first
else:
prevp = curve[i-1]
if len(curp) == 6:
newp = curp[2:4] + curp[0:2] + prevp[-2:]
elif len(curp) == 2:
newp = prevp[-2:]
flipped.append(newp)
return flipped
def pointlist_to_str(points, scale):
return ' '.join([str(scale*p) for p in points])
def process_glyph(image, glyph, font_height):
font.createMappedChar(glyph.name)
font.createChar(glyph.codepoint)
font[glyph.name].vwidth = font_height
crop_and_trace(image, glyph.box.r, glyph.codepoint)
def max_y(glyphs):
"""Return the the height of the tallest letter box."""
return reduce(lambda x, y: max(x, y.box.r.height()), glyphs, 0)
def createSpaceGlyphs(name, codepoint, value):
font.createMappedChar(name)
font.createChar(codepoint)
font[name].width = value;
def write_sfd(ofilename, fontname, image, glyphs, font_height, font_ascent):
font_height = int(font_height)
font.ascent = int(font_ascent)
font.descent = font_height - int(font_ascent)
print("Generating SFD")
for glyph in glyphs:
print(glyph.name, glyph.box.r)
process_glyph(image, glyph, int())
font.selection.all()
font.autoWidth(100, 30)
font.autoHint()
# CREATE SPACE CHARACTERS
createSpaceGlyphs('space', 32, int(font_height / 4))
createSpaceGlyphs('uni2000', 8192, int(font_height / 2)) # en-quad
createSpaceGlyphs('uni2001', 8193, font_height) # em-quad
createSpaceGlyphs('uni2002', 8194, int(font_height / 2)) # en-space
createSpaceGlyphs('uni2003', 8195, font_height) # em-space
createSpaceGlyphs('uni2004', 8196, int(font_height / 3)) # three per em space
createSpaceGlyphs('uni2005', 8197, int(font_height / 4)) # four per em space
createSpaceGlyphs('uni2006', 8198, int(font_height / 6)) # six per em space
#createSpaceGlyphs('uni2007', 8199, int(font['five'].width)) # figure space
#createSpaceGlyphs('uni2008', 8200, int(font['period'].width)) # punctuation space
createSpaceGlyphs('uni2009', 8201, int(font_height / 5)) # thin space
createSpaceGlyphs('uni200A', 8202, int(font_height / 6)) # hair space
createSpaceGlyphs('uni200B', 8203, 0) # zero-width space
createSpaceGlyphs('uni202F', 8239, int(font_height / 5)) # narrow no-break space
createSpaceGlyphs('uni205F', 8287, int(font_height / 18 * 4)) # mathematical space
createSpaceGlyphs('uniFEFF', 65279, 0) # zero width no-break space
print("save")
font.save(ofilename)