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
|
from fontTools.t1Lib import T1Font, T1Error
from fontTools.agl import AGL2UV
from fontTools.misc.psLib import PSInterpreter
from fontTools.misc.transform import Transform
from extractor.tools import RelaxedInfo
# specification: http://partners.adobe.com/public/developer/en/font/T1_SPEC.PDF
# ----------------
# Public Functions
# ----------------
def isType1(pathOrFile):
try:
font = T1Font(pathOrFile)
del font
except T1Error:
return False
return True
def extractFontFromType1(pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, customFunctions=[]):
source = T1Font(pathOrFile)
destination.lib["public.glyphOrder"] = _extractType1GlyphOrder(source)
if doInfo:
extractType1Info(source, destination)
if doGlyphs:
extractType1Glyphs(source, destination)
if doKerning:
# kerning extraction is not supported yet.
# in theory, it could be retried from an AFM.
# we need to find the AFM naming rules so that we can sniff for the file.
pass
for function in customFunctions:
function(source, destination)
def extractType1Info(source, destination):
info = RelaxedInfo(destination.info)
_extractType1FontInfo(source, info)
_extractType1Private(source, info)
_extractType1FontMatrix(source, info)
# ----
# Info
# ----
def _extractType1FontInfo(source, info):
sourceInfo = source["FontInfo"]
# FontName
info.postscriptFontName = source["FontName"]
# version
version = sourceInfo.get("version")
if version is not None:
# the spec says that version will be a string and no formatting info is given.
# so, only move forward if the string can actually be parsed.
try:
# 1. convert to a float
version = float(version)
# 2. convert it back to a string
version = "%.3f" % version
# 3. split.
versionMajor, versionMinor = version.split(".")
# 4. convert.
versionMajor = int(versionMajor)
versionMinor = int(versionMinor)
# 5. set.
info.versionMajor = int(versionMajor)
info.versionMinor = int(versionMinor)
except ValueError:
# couldn't parse. leve the object with the default values.
pass
# Notice
notice = sourceInfo.get("Notice")
if notice:
info.copyright = notice
# FullName
fullName = sourceInfo.get("FullName")
if fullName:
info.postscriptFullName = fullName
# FamilyName
familyName = sourceInfo.get("FamilyName")
if familyName:
info.familyName = familyName
# Weight
postscriptWeightName = sourceInfo.get("Weight")
if postscriptWeightName:
info.postscriptWeightName = postscriptWeightName
# ItalicAngle
info.italicAngle = sourceInfo.get("ItalicAngle")
# IsFixedPitch
info.postscriptIsFixedPitch = sourceInfo.get("isFixedPitch")
# UnderlinePosition/Thickness
info.postscriptUnderlinePosition = sourceInfo.get("UnderlinePosition")
info.postscriptUnderlineThickness = sourceInfo.get("UnderlineThickness")
def _extractType1FontMatrix(source, info):
# units per em
matrix = source["FontMatrix"]
matrix = Transform(*matrix).inverse()
info.unitsPerEm = int(round(matrix[3]))
def _extractType1Private(source, info):
private = source["Private"]
# UniqueID
info.openTypeNameUniqueID = private.get("UniqueID", None)
# BlueValues and OtherBlues
info.postscriptBlueValues = private.get("BlueValues", [])
info.postscriptOtherBlues = private.get("OtherBlues", [])
# FamilyBlues and FamilyOtherBlues
info.postscriptFamilyBlues = private.get("FamilyBlues", [])
info.postscriptFamilyOtherBlues = private.get("FamilyOtherBlues", [])
# BlueScale/Shift/Fuzz
info.postscriptBlueScale = private.get("BlueScale", None)
info.postscriptBlueShift = private.get("BlueShift", None)
info.postscriptBlueFuzz = private.get("BlueFuzz", None)
# StemSnapH/V
info.postscriptStemSnapH = private.get("StemSnapH", [])
info.postscriptStemSnapV = private.get("StemSnapV", [])
# ForceBold
info.postscriptForceBold = bool(private.get("ForceBold", None))
# --------
# Outlines
# --------
def extractType1Glyphs(source, destination):
glyphSet = source.getGlyphSet()
for glyphName in sorted(glyphSet.keys()):
sourceGlyph = glyphSet[glyphName]
# make the new glyph
destination.newGlyph(glyphName)
destinationGlyph = destination[glyphName]
# outlines
pen = destinationGlyph.getPen()
sourceGlyph.draw(pen)
# width
destinationGlyph.width = sourceGlyph.width
# synthesize the unicode value
destinationGlyph.unicode = AGL2UV.get(glyphName)
# -----------
# Glyph order
# -----------
class GlyphOrderPSInterpreter(PSInterpreter):
def __init__(self):
PSInterpreter.__init__(self)
self.glyphOrder = []
self.collectTokenForGlyphOrder = False
def do_literal(self, token):
result = PSInterpreter.do_literal(self, token)
if token == "/FontName":
self.collectTokenForGlyphOrder = False
if self.collectTokenForGlyphOrder:
self.glyphOrder.append(result.value)
if token == "/CharStrings":
self.collectTokenForGlyphOrder = True
return result
def _extractType1GlyphOrder(t1Font):
interpreter = GlyphOrderPSInterpreter()
interpreter.interpret(t1Font.data)
return interpreter.glyphOrder
|