woff 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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()
|