woff 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +24 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +13 -0
- data/README.md +25 -4
- data/Rakefile +5 -0
- data/bin/rake +17 -0
- data/lib/woff.rb +8 -1
- data/lib/woff/builder.rb +66 -15
- data/lib/woff/file.rb +152 -0
- data/lib/woff/version.rb +1 -1
- data/requirements.txt +1 -0
- data/spec/builder_spec.rb +49 -0
- data/spec/data/font-with-no-metadata.woff +0 -0
- data/spec/spec_helper.rb +7 -0
- data/woff.gemspec +9 -2
- data/woffTools/Lib/woffTools/__init__.py +1176 -0
- data/woffTools/Lib/woffTools/test/__init__.py +0 -0
- data/woffTools/Lib/woffTools/test/test_validate.py +2657 -0
- data/woffTools/Lib/woffTools/tools/__init__.py +0 -0
- data/woffTools/Lib/woffTools/tools/css.py +292 -0
- data/woffTools/Lib/woffTools/tools/info.py +296 -0
- data/woffTools/Lib/woffTools/tools/proof.py +210 -0
- data/woffTools/Lib/woffTools/tools/support.py +417 -0
- data/woffTools/Lib/woffTools/tools/validate.py +2504 -0
- data/woffTools/License.txt +21 -0
- data/woffTools/README.txt +31 -0
- data/woffTools/setup.py +35 -0
- data/woffTools/woff-all +28 -0
- data/woffTools/woff-css +5 -0
- data/woffTools/woff-info +5 -0
- data/woffTools/woff-proof +5 -0
- data/woffTools/woff-validate +5 -0
- metadata +94 -9
- data/lib/woff/data.rb +0 -44
File without changes
|
@@ -0,0 +1,292 @@
|
|
1
|
+
"""
|
2
|
+
A module for automatically creating CSS @font-face rules from
|
3
|
+
WOFF files. *makeFontFaceRule* is the only public function.
|
4
|
+
|
5
|
+
This can also be used as a command line tool for generating
|
6
|
+
CSS @font-face rules from WOFF files.
|
7
|
+
"""
|
8
|
+
|
9
|
+
# import test
|
10
|
+
|
11
|
+
importErrors = []
|
12
|
+
try:
|
13
|
+
import numpy
|
14
|
+
except:
|
15
|
+
importErrors.append("numpy")
|
16
|
+
try:
|
17
|
+
import fontTools
|
18
|
+
except ImportError:
|
19
|
+
importErrors.append("fontTools")
|
20
|
+
try:
|
21
|
+
import woffTools
|
22
|
+
except ImportError:
|
23
|
+
importErrors.append("woffTools")
|
24
|
+
|
25
|
+
if importErrors:
|
26
|
+
import sys
|
27
|
+
print "Could not import needed module(s):", ", ".join(importErrors)
|
28
|
+
sys.exit()
|
29
|
+
|
30
|
+
# import
|
31
|
+
|
32
|
+
import os
|
33
|
+
import urllib
|
34
|
+
import optparse
|
35
|
+
from woffTools import WOFFFont
|
36
|
+
from woffTools.tools.support import findUniqueFileName
|
37
|
+
|
38
|
+
# -----------
|
39
|
+
# Descriptors
|
40
|
+
# -----------
|
41
|
+
|
42
|
+
def makeFontFaceFontFamily(font):
|
43
|
+
familyPriority = [
|
44
|
+
(21, 1, 0, 0), # WWS Family Name, Mac, English, Roman
|
45
|
+
(21, 1, None, None), # WWS Family Name, Mac, Any, Any
|
46
|
+
(21, None, None, None), # WWS Family Name, Any, Any, Any
|
47
|
+
(16, 1, 0, 0), # Preferred Family, Mac, English, Roman
|
48
|
+
(16, 1, None, None), # Preferred Family, Mac, Any, Any
|
49
|
+
(16, None, None, None), # Preferred Family, Any, Any, Any
|
50
|
+
(1, 1, 0, 0), # Font Family Name, Mac, English, Roman
|
51
|
+
(1, 1, None, None), # Font Family Name, Mac, Any, Any
|
52
|
+
(1, None, None, None) # Font Family Name, Any, Any, Any
|
53
|
+
]
|
54
|
+
familyName = _skimNameIDs(font, familyPriority)
|
55
|
+
descriptor = "font-family: \"%s\";" % familyName
|
56
|
+
return descriptor
|
57
|
+
|
58
|
+
def makeFontFaceSrc(font, fileName, doLocalSrc=True):
|
59
|
+
sources = []
|
60
|
+
# notes about "local"
|
61
|
+
sources.append("")
|
62
|
+
sources.append("/* The \"local\" lines will cause the browser to look for a locally */")
|
63
|
+
sources.append("/* installed font with the specified name before downloading the WOFF file. */")
|
64
|
+
if not doLocalSrc:
|
65
|
+
sources.append("/* Remove the commenting if you want this behavior. */")
|
66
|
+
else:
|
67
|
+
sources.append("/* Remove the lines if you don't want this behavior. */")
|
68
|
+
# postscript name
|
69
|
+
postscriptPriority = [
|
70
|
+
(6, 1, 0, 0), # Postscript Name, Mac, English, Roman
|
71
|
+
(6, 1, None, None), # Postscript Name, Mac, Any, Any
|
72
|
+
(6, None, None, None), # Postscript Name, Any, Any, Any
|
73
|
+
]
|
74
|
+
postscriptName = _skimNameIDs(font, postscriptPriority)
|
75
|
+
# full name
|
76
|
+
fullNamePriority = [
|
77
|
+
(4, 1, 0, 0), # Full Font Name, Mac, English, Roman
|
78
|
+
(4, 1, None, None), # Full Font Name, Mac, Any, Any
|
79
|
+
(4, None, None, None), # Full Font Name, Any, Any, Any
|
80
|
+
]
|
81
|
+
fullName = _skimNameIDs(font, fullNamePriority)
|
82
|
+
# store
|
83
|
+
s = "local(\"%s\")" % postscriptName
|
84
|
+
if not doLocalSrc:
|
85
|
+
s = "/* " + s + " */"
|
86
|
+
sources.append(s)
|
87
|
+
if postscriptName != fullName:
|
88
|
+
s = "local(\"%s\")" % fullName
|
89
|
+
if not doLocalSrc:
|
90
|
+
s = "/* " + s + " */"
|
91
|
+
sources.append(s)
|
92
|
+
# file name
|
93
|
+
s = "url(\"%s\")" % urllib.quote(fileName) # XXX: format(\"woff\")
|
94
|
+
sources.append(s)
|
95
|
+
# write
|
96
|
+
sources = "\n\t".join(sources)
|
97
|
+
descriptor = "src: %s;" % sources
|
98
|
+
return descriptor
|
99
|
+
|
100
|
+
def makeFontFaceFontWeight(font):
|
101
|
+
os2 = font["OS/2"]
|
102
|
+
value = os2.usWeightClass
|
103
|
+
descriptor = "font-weight: %d;" % value
|
104
|
+
if value < 100 or value > 900:
|
105
|
+
descriptor += " /* ERROR! Weight value is out of the 100-900 value range. */"
|
106
|
+
elif value % 100:
|
107
|
+
descriptor += " /* ERROR! Weight value is not a multiple of 100. */"
|
108
|
+
return descriptor
|
109
|
+
|
110
|
+
def makeFontFaceFontStretch(font):
|
111
|
+
os2 = font["OS/2"]
|
112
|
+
value = os2.usWidthClass
|
113
|
+
options = "ultra-condensed extra-condensed condensed semi-condensed normal semi-expanded expanded extra-expanded ultra-expanded".split(" ")
|
114
|
+
try:
|
115
|
+
value = options[value-1]
|
116
|
+
except IndexError:
|
117
|
+
value = "normal; /* ERROR! The value in the OS/2 table usWidthClass is not valid! */"
|
118
|
+
descriptor = "font-stretch: %s;" % value
|
119
|
+
return descriptor
|
120
|
+
|
121
|
+
def makeFontFaceFontStyle(font):
|
122
|
+
os2 = font["OS/2"]
|
123
|
+
if os2.fsSelection & 1:
|
124
|
+
value = "italic"
|
125
|
+
else:
|
126
|
+
value = "normal"
|
127
|
+
descriptor = "font-style: %s;" % value
|
128
|
+
return descriptor
|
129
|
+
|
130
|
+
def makeFontFaceUnicodeRange(font):
|
131
|
+
# compile ranges
|
132
|
+
cmap = font["cmap"]
|
133
|
+
table = cmap.getcmap(3, 1)
|
134
|
+
mapping = table.cmap
|
135
|
+
ranges = []
|
136
|
+
for value in sorted(mapping.keys()):
|
137
|
+
newRanges = []
|
138
|
+
handled = False
|
139
|
+
for rangeMin, rangeMax in ranges:
|
140
|
+
if value >= rangeMin and value <= rangeMax:
|
141
|
+
handled = True
|
142
|
+
elif rangeMin - 1 == value:
|
143
|
+
rangeMin = value
|
144
|
+
handled = True
|
145
|
+
elif rangeMax + 1 == value:
|
146
|
+
rangeMax = value
|
147
|
+
handled = True
|
148
|
+
newRanges.append((rangeMin, rangeMax))
|
149
|
+
if not handled:
|
150
|
+
newRanges.append((value, value))
|
151
|
+
ranges = sorted(newRanges)
|
152
|
+
# convert ints to proper hexk values
|
153
|
+
formatted = []
|
154
|
+
for minValue, maxValue in ranges:
|
155
|
+
if minValue == maxValue:
|
156
|
+
uniRange = hex(minValue)[2:].upper()
|
157
|
+
else:
|
158
|
+
minCode = hex(minValue)[2:].upper()
|
159
|
+
maxCode = hex(maxValue)[2:].upper()
|
160
|
+
uniRange = "-".join((minCode, maxCode))
|
161
|
+
formatted.append("U+%s" % uniRange)
|
162
|
+
# break into nice lines
|
163
|
+
perLine = 4
|
164
|
+
chunks = []
|
165
|
+
while formatted:
|
166
|
+
if len(formatted) > perLine:
|
167
|
+
chunks.append(formatted[:perLine])
|
168
|
+
formatted = formatted[perLine:]
|
169
|
+
else:
|
170
|
+
chunks.append(formatted)
|
171
|
+
formatted = []
|
172
|
+
formatted = []
|
173
|
+
for index, chunk in enumerate(chunks):
|
174
|
+
s = ", ".join(chunk)
|
175
|
+
if index < len(chunks) - 1:
|
176
|
+
s += ","
|
177
|
+
formatted.append(s)
|
178
|
+
formatted = "\n\t".join(formatted)
|
179
|
+
# write
|
180
|
+
descriptor = "unicode-range: %s;" % formatted
|
181
|
+
return descriptor
|
182
|
+
|
183
|
+
# -------
|
184
|
+
# Helpers
|
185
|
+
# -------
|
186
|
+
|
187
|
+
def _skimNameIDs(font, priority):
|
188
|
+
nameIDs = {}
|
189
|
+
for nameRecord in font["name"].names:
|
190
|
+
nameID = nameRecord.nameID
|
191
|
+
platformID = nameRecord.platformID
|
192
|
+
platEncID = nameRecord.platEncID
|
193
|
+
langID = nameRecord.langID
|
194
|
+
text = nameRecord.string
|
195
|
+
nameIDs[nameID, platformID, platEncID, langID] = text
|
196
|
+
for (nameID, platformID, platEncID, langID) in priority:
|
197
|
+
for (nID, pID, pEID, lID), text in nameIDs.items():
|
198
|
+
if nID != nameID:
|
199
|
+
continue
|
200
|
+
if pID != platformID and platformID is not None:
|
201
|
+
continue
|
202
|
+
if pEID != platEncID and platEncID is not None:
|
203
|
+
continue
|
204
|
+
if lID != langID and langID is not None:
|
205
|
+
continue
|
206
|
+
text = "".join([i for i in text if i != "\x00"])
|
207
|
+
return text
|
208
|
+
|
209
|
+
# ---------------
|
210
|
+
# Public Function
|
211
|
+
# ---------------
|
212
|
+
|
213
|
+
def makeFontFaceRule(font, fontPath, doLocalSrc=True):
|
214
|
+
"""
|
215
|
+
Create a CSS @font-face rule from the given font. This always
|
216
|
+
returns the CSS text.
|
217
|
+
|
218
|
+
Arguments
|
219
|
+
|
220
|
+
**font** - A *WOFFFont* object from *woffLib*.
|
221
|
+
**fontPath** - The location of the font file. At the least, this should be the file name for the font.
|
222
|
+
**doLocalSrc** - Generate "local" references as part of the "src" descriptor.
|
223
|
+
"""
|
224
|
+
# create text
|
225
|
+
sections = [
|
226
|
+
makeFontFaceFontFamily(font),
|
227
|
+
makeFontFaceSrc(font, os.path.basename(fontPath), doLocalSrc=doLocalSrc),
|
228
|
+
makeFontFaceFontWeight(font),
|
229
|
+
makeFontFaceFontStretch(font),
|
230
|
+
makeFontFaceFontStyle(font),
|
231
|
+
makeFontFaceUnicodeRange(font)
|
232
|
+
]
|
233
|
+
lines = []
|
234
|
+
for section in sections:
|
235
|
+
lines += ["\t" + line for line in section.splitlines()]
|
236
|
+
rule = ["/* Automatically generated from: %s %d.%d */" % (os.path.basename(fontPath), font.majorVersion, font.minorVersion)]
|
237
|
+
rule += [""]
|
238
|
+
rule += ["@font-face {"] + lines + ["}"]
|
239
|
+
rule = "\n".join(rule)
|
240
|
+
return rule
|
241
|
+
|
242
|
+
# --------------------
|
243
|
+
# Command Line Behvior
|
244
|
+
# --------------------
|
245
|
+
|
246
|
+
usage = "%prog [options] fontpath1 fontpath2"
|
247
|
+
|
248
|
+
description = """This tool examines the contents of a WOFF
|
249
|
+
file and attempts to generate a CSS @font-face rule based
|
250
|
+
on the data found in the WOFF file. The results of this
|
251
|
+
tool should always be carefully checked.
|
252
|
+
"""
|
253
|
+
|
254
|
+
def main():
|
255
|
+
parser = optparse.OptionParser(usage=usage, description=description, version="%prog 0.1beta")
|
256
|
+
parser.add_option("-d", dest="outputDirectory", help="Output directory. The default is to output the CSS into the same directory as the font file.")
|
257
|
+
parser.add_option("-o", dest="outputFileName", help="Output file name. The default is \"fontfilename.css\". If this file already exists a time stamp will be added to the file name.")
|
258
|
+
parser.add_option("-l", action="store_true", dest="doLocalSrc", help="Write \"local\" instructions as part of the \"src\" descriptor.")
|
259
|
+
(options, args) = parser.parse_args()
|
260
|
+
outputDirectory = options.outputDirectory
|
261
|
+
if outputDirectory is not None and not os.path.exists(outputDirectory):
|
262
|
+
print "Directory does not exist:", outputDirectory
|
263
|
+
sys.exit()
|
264
|
+
for fontPath in args:
|
265
|
+
if not os.path.exists(fontPath):
|
266
|
+
print "File does not exist:", fontPath
|
267
|
+
sys.exit()
|
268
|
+
else:
|
269
|
+
print "Creating CSS: %s..." % fontPath
|
270
|
+
fontPath = fontPath.decode("utf-8")
|
271
|
+
font = WOFFFont(fontPath)
|
272
|
+
css = makeFontFaceRule(font, fontPath, doLocalSrc=options.doLocalSrc)
|
273
|
+
# make the output file name
|
274
|
+
if options.outputFileName is not None:
|
275
|
+
fileName = options.outputFileName
|
276
|
+
else:
|
277
|
+
fileName = os.path.splitext(os.path.basename(fontPath))[0]
|
278
|
+
fileName += ".css"
|
279
|
+
# make the output directory
|
280
|
+
if options.outputDirectory is not None:
|
281
|
+
directory = options.outputDirectory
|
282
|
+
else:
|
283
|
+
directory = os.path.dirname(fontPath)
|
284
|
+
# write the file
|
285
|
+
path = os.path.join(directory, fileName)
|
286
|
+
path = findUniqueFileName(path)
|
287
|
+
f = open(path, "wb")
|
288
|
+
f.write(css)
|
289
|
+
f.close()
|
290
|
+
|
291
|
+
if __name__ == "__main__":
|
292
|
+
main()
|
@@ -0,0 +1,296 @@
|
|
1
|
+
"""
|
2
|
+
A module for reporting information about the contents of
|
3
|
+
WOFF files. *reportInfo* is the only public function.
|
4
|
+
|
5
|
+
This can also be used as a command line tool.
|
6
|
+
"""
|
7
|
+
|
8
|
+
# import test
|
9
|
+
|
10
|
+
importErrors = []
|
11
|
+
try:
|
12
|
+
import numpy
|
13
|
+
except:
|
14
|
+
importErrors.append("numpy")
|
15
|
+
try:
|
16
|
+
import fontTools
|
17
|
+
except ImportError:
|
18
|
+
importErrors.append("fontTools")
|
19
|
+
try:
|
20
|
+
import woffTools
|
21
|
+
except ImportError:
|
22
|
+
importErrors.append("woffTools")
|
23
|
+
|
24
|
+
if importErrors:
|
25
|
+
import sys
|
26
|
+
print "Could not import needed module(s):", ", ".join(importErrors)
|
27
|
+
sys.exit()
|
28
|
+
|
29
|
+
# import
|
30
|
+
|
31
|
+
import os
|
32
|
+
import optparse
|
33
|
+
from woffTools import WOFFFont
|
34
|
+
from woffTools.tools.support import startHTML, finishHTML, findUniqueFileName
|
35
|
+
from woffTools.tools.css import makeFontFaceRule
|
36
|
+
|
37
|
+
|
38
|
+
# ----------------
|
39
|
+
# Report Functions
|
40
|
+
# ----------------
|
41
|
+
|
42
|
+
def writeFileInfo(font, fontPath, writer):
|
43
|
+
# start the block
|
44
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
45
|
+
# title
|
46
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
47
|
+
writer.write("File Information")
|
48
|
+
writer.endtag("h3")
|
49
|
+
# table
|
50
|
+
writer.begintag("table", c_l_a_s_s="report")
|
51
|
+
writeFileInfoRow("FILE", os.path.basename(fontPath), writer)
|
52
|
+
writeFileInfoRow("DIRECTORY", os.path.dirname(fontPath), writer)
|
53
|
+
writeFileInfoRow("FILE SIZE", str(font.reader.length) + " bytes", writer)
|
54
|
+
writeFileInfoRow("VERSION", "%d.%d" % (font.majorVersion, font.minorVersion), writer)
|
55
|
+
writer.endtag("table")
|
56
|
+
## close the container
|
57
|
+
writer.endtag("div")
|
58
|
+
|
59
|
+
def writeFileInfoRow(title, value, writer):
|
60
|
+
# row
|
61
|
+
writer.begintag("tr")
|
62
|
+
# title
|
63
|
+
writer.begintag("td", c_l_a_s_s="title")
|
64
|
+
writer.write(title)
|
65
|
+
writer.endtag("td")
|
66
|
+
# message
|
67
|
+
writer.begintag("td")
|
68
|
+
writer.write(value)
|
69
|
+
writer.endtag("td")
|
70
|
+
# close row
|
71
|
+
writer.endtag("tr")
|
72
|
+
|
73
|
+
def writeSFNTInfo(font, writer):
|
74
|
+
# start the block
|
75
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
76
|
+
# title
|
77
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
78
|
+
writer.write("sfnt Tables")
|
79
|
+
writer.endtag("h3")
|
80
|
+
# tables
|
81
|
+
writer.begintag("table", c_l_a_s_s="sfntTableData")
|
82
|
+
writer.begintag("tr")
|
83
|
+
columns = "tag offset compLength origLength origChecksum".split()
|
84
|
+
for c in columns:
|
85
|
+
writer.begintag("th")
|
86
|
+
writer.write(c)
|
87
|
+
writer.endtag("th")
|
88
|
+
writer.endtag("tr")
|
89
|
+
for tag, entry in sorted(font.reader.tables.items()):
|
90
|
+
if entry.compLength == entry.origLength:
|
91
|
+
writer.begintag("tr", c_l_a_s_s="uncompressed")
|
92
|
+
else:
|
93
|
+
writer.begintag("tr")
|
94
|
+
for attr in columns:
|
95
|
+
v = getattr(entry, attr)
|
96
|
+
if attr == "origChecksum":
|
97
|
+
v = hex(v)
|
98
|
+
else:
|
99
|
+
v = str(v)
|
100
|
+
writer.begintag("td")
|
101
|
+
writer.write(v)
|
102
|
+
writer.endtag("td")
|
103
|
+
writer.endtag("tr")
|
104
|
+
writer.endtag("table")
|
105
|
+
## close the block
|
106
|
+
writer.endtag("div")
|
107
|
+
|
108
|
+
def writeMetadata(font, writer):
|
109
|
+
# start the block
|
110
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
111
|
+
# title
|
112
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
113
|
+
writer.write("Metadata")
|
114
|
+
writer.endtag("h3")
|
115
|
+
# content
|
116
|
+
if font.metadata is not None:
|
117
|
+
for element in font.metadata:
|
118
|
+
writeMetadataElement(element, writer)
|
119
|
+
# close the block
|
120
|
+
writer.endtag("div")
|
121
|
+
|
122
|
+
def writeMetadataElement(element, writer):
|
123
|
+
writer.begintag("div", c_l_a_s_s="metadataElement")
|
124
|
+
# tag
|
125
|
+
writer.begintag("h5", c_l_a_s_s="metadata")
|
126
|
+
writer.write(element.tag)
|
127
|
+
writer.endtag("h5")
|
128
|
+
# attributes
|
129
|
+
if len(element.attrib):
|
130
|
+
writer.begintag("h6", c_l_a_s_s="metadata")
|
131
|
+
writer.write("Attributes:")
|
132
|
+
writer.endtag("h6")
|
133
|
+
# key, value pairs
|
134
|
+
writer.begintag("table", c_l_a_s_s="metadata")
|
135
|
+
for key, value in sorted(element.attrib.items()):
|
136
|
+
writer.begintag("tr")
|
137
|
+
writer.begintag("td", c_l_a_s_s="key")
|
138
|
+
writer.write(key)
|
139
|
+
writer.endtag("td")
|
140
|
+
writer.begintag("td", c_l_a_s_s="value")
|
141
|
+
writer.write(value)
|
142
|
+
writer.endtag("td")
|
143
|
+
writer.endtag("tr")
|
144
|
+
writer.endtag("table")
|
145
|
+
# text
|
146
|
+
if element.text is not None and element.text.strip():
|
147
|
+
writer.begintag("h6", c_l_a_s_s="metadata")
|
148
|
+
writer.write("Text:")
|
149
|
+
writer.endtag("h6")
|
150
|
+
writer.begintag("p", c_l_a_s_s="metadata")
|
151
|
+
writer.write(element.text)
|
152
|
+
writer.endtag("p")
|
153
|
+
# child elements
|
154
|
+
if len(element):
|
155
|
+
writer.begintag("h6", c_l_a_s_s="metadata")
|
156
|
+
writer.write("Child Elements:")
|
157
|
+
writer.endtag("h6")
|
158
|
+
for child in element:
|
159
|
+
writeMetadataElement(child, writer)
|
160
|
+
# close
|
161
|
+
writer.endtag("div")
|
162
|
+
|
163
|
+
hexFilter = "".join([(len(repr(chr(x))) == 3) and chr(x) or "." for x in range(256)])
|
164
|
+
|
165
|
+
def writePrivateData(font, writer):
|
166
|
+
# start the block
|
167
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
168
|
+
# title
|
169
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
170
|
+
writer.write("Private Data")
|
171
|
+
writer.endtag("h3")
|
172
|
+
# content
|
173
|
+
if font.privateData:
|
174
|
+
# adapted from http://code.activestate.com/recipes/142812/
|
175
|
+
src = font.privateData
|
176
|
+
length = 16
|
177
|
+
result = []
|
178
|
+
for i in xrange(0, len(src), length):
|
179
|
+
s = src[i:i+length]
|
180
|
+
hexa = []
|
181
|
+
c = []
|
182
|
+
for x in s:
|
183
|
+
x = "%02X" % ord(x)
|
184
|
+
c.append(x)
|
185
|
+
if len(c) == 4:
|
186
|
+
hexa.append("".join(c))
|
187
|
+
c = []
|
188
|
+
if c:
|
189
|
+
hexa.append("".join(c))
|
190
|
+
hexa = " ".join(hexa)
|
191
|
+
if len(hexa) != 35:
|
192
|
+
hexa += " " * (35 - len(hexa))
|
193
|
+
printable = s.translate(hexFilter)
|
194
|
+
result.append("%04X %s %s\n" % (i, hexa, printable))
|
195
|
+
privateData = "".join(result)
|
196
|
+
writer.begintag("pre", c_l_a_s_s="privateData")
|
197
|
+
writer.write(privateData)
|
198
|
+
writer.endtag("pre")
|
199
|
+
# close the block
|
200
|
+
writer.endtag("div")
|
201
|
+
|
202
|
+
def writeFontFaceRule(font, fontPath, writer):
|
203
|
+
# start the block
|
204
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
205
|
+
# title
|
206
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
207
|
+
writer.write("@font-face")
|
208
|
+
writer.endtag("h3")
|
209
|
+
# the text
|
210
|
+
fontFaceRule = makeFontFaceRule(font, fontPath, doLocalSrc=False)
|
211
|
+
writer.begintag("pre", c_l_a_s_s="fontFaceRule")
|
212
|
+
writer.write(fontFaceRule)
|
213
|
+
writer.endtag("pre")
|
214
|
+
# close the container
|
215
|
+
writer.endtag("div")
|
216
|
+
|
217
|
+
# ---------------
|
218
|
+
# Public Function
|
219
|
+
# ---------------
|
220
|
+
|
221
|
+
def reportInfo(font, fontPath):
|
222
|
+
"""
|
223
|
+
Create a report about the contents of font. This returns HTML.
|
224
|
+
|
225
|
+
Arguments
|
226
|
+
|
227
|
+
**font** - A *WOFFFont* object from *woffLib*.
|
228
|
+
**fontPath** - The location of the font file. At the least, this should be the file name for the font.
|
229
|
+
"""
|
230
|
+
# start the html
|
231
|
+
title = "Info: %s" % os.path.basename(fontPath)
|
232
|
+
writer = startHTML(title=title)
|
233
|
+
# file info
|
234
|
+
writeFileInfo(font, fontPath, writer)
|
235
|
+
# SFNT tables
|
236
|
+
writeSFNTInfo(font, writer)
|
237
|
+
# metadata
|
238
|
+
writeMetadata(font, writer)
|
239
|
+
# private data
|
240
|
+
writePrivateData(font, writer)
|
241
|
+
# @font-face
|
242
|
+
writeFontFaceRule(font, fontPath, writer)
|
243
|
+
# finish the html
|
244
|
+
text = finishHTML(writer)
|
245
|
+
# return
|
246
|
+
return text
|
247
|
+
|
248
|
+
# --------------------
|
249
|
+
# Command Line Behvior
|
250
|
+
# --------------------
|
251
|
+
|
252
|
+
usage = "%prog [options] fontpath1 fontpath2"
|
253
|
+
|
254
|
+
description = """This tool displays information about the
|
255
|
+
contents of one or more WOFF files.
|
256
|
+
"""
|
257
|
+
|
258
|
+
def main():
|
259
|
+
parser = optparse.OptionParser(usage=usage, description=description, version="%prog 0.1beta")
|
260
|
+
parser.add_option("-d", dest="outputDirectory", help="Output directory. The default is to output the report into the same directory as the font file.")
|
261
|
+
parser.add_option("-o", dest="outputFileName", help="Output file name. The default is \"fontfilename_info.html\".")
|
262
|
+
parser.set_defaults(excludeTests=[])
|
263
|
+
(options, args) = parser.parse_args()
|
264
|
+
outputDirectory = options.outputDirectory
|
265
|
+
if outputDirectory is not None and not os.path.exists(outputDirectory):
|
266
|
+
print "Directory does not exist:", outputDirectory
|
267
|
+
sys.exit()
|
268
|
+
for fontPath in args:
|
269
|
+
if not os.path.exists(fontPath):
|
270
|
+
print "File does not exist:", fontPath
|
271
|
+
sys.exit()
|
272
|
+
else:
|
273
|
+
print "Creating Info Report: %s..." % fontPath
|
274
|
+
fontPath = fontPath.decode("utf-8")
|
275
|
+
font = WOFFFont(fontPath)
|
276
|
+
html = reportInfo(font, fontPath)
|
277
|
+
# make the output file name
|
278
|
+
if options.outputFileName is not None:
|
279
|
+
fileName = options.outputFileName
|
280
|
+
else:
|
281
|
+
fileName = os.path.splitext(os.path.basename(fontPath))[0]
|
282
|
+
fileName += "_info.html"
|
283
|
+
# make the output directory
|
284
|
+
if options.outputDirectory is not None:
|
285
|
+
directory = options.outputDirectory
|
286
|
+
else:
|
287
|
+
directory = os.path.dirname(fontPath)
|
288
|
+
# write the file
|
289
|
+
path = os.path.join(directory, fileName)
|
290
|
+
path = findUniqueFileName(path)
|
291
|
+
f = open(path, "wb")
|
292
|
+
f.write(html)
|
293
|
+
f.close()
|
294
|
+
|
295
|
+
if __name__ == "__main__":
|
296
|
+
main()
|