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
@@ -0,0 +1,210 @@
|
|
1
|
+
"""
|
2
|
+
A module for automatically creating simple proof files from
|
3
|
+
WOFF files. *proofFont* is the only public function.
|
4
|
+
|
5
|
+
This can also be used as a command line tool for generating
|
6
|
+
proofs 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 optparse
|
34
|
+
import unicodedata
|
35
|
+
from woffTools import WOFFFont
|
36
|
+
from woffTools.tools.css import makeFontFaceRule, makeFontFaceFontFamily
|
37
|
+
from woffTools.tools.support import startHTML, finishHTML, findUniqueFileName
|
38
|
+
|
39
|
+
|
40
|
+
# ----------------
|
41
|
+
# Report Functions
|
42
|
+
# ----------------
|
43
|
+
|
44
|
+
def writeFileInfo(font, fontPath, writer):
|
45
|
+
# start the block
|
46
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
47
|
+
# title
|
48
|
+
writer.begintag("h3", c_l_a_s_s="infoBlockTitle")
|
49
|
+
writer.write("File Information")
|
50
|
+
writer.endtag("h3")
|
51
|
+
# table
|
52
|
+
writer.begintag("table", c_l_a_s_s="report")
|
53
|
+
writeFileInfoRow("FILE", os.path.basename(fontPath), writer)
|
54
|
+
writeFileInfoRow("DIRECTORY", os.path.dirname(fontPath), writer)
|
55
|
+
writeFileInfoRow("VERSION", "%d.%d" % (font.majorVersion, font.minorVersion), writer)
|
56
|
+
writer.endtag("table")
|
57
|
+
# close the container
|
58
|
+
writer.endtag("div")
|
59
|
+
|
60
|
+
def writeFileInfoRow(title, value, writer):
|
61
|
+
# row
|
62
|
+
writer.begintag("tr")
|
63
|
+
# title
|
64
|
+
writer.begintag("td", c_l_a_s_s="title")
|
65
|
+
writer.write(title)
|
66
|
+
writer.endtag("td")
|
67
|
+
# message
|
68
|
+
writer.begintag("td")
|
69
|
+
writer.write(value)
|
70
|
+
writer.endtag("td")
|
71
|
+
# close row
|
72
|
+
writer.endtag("tr")
|
73
|
+
|
74
|
+
def writeCharacterSet(font, writer, pointSizes=[9, 10, 11, 12, 14, 18, 24, 36, 48, 72], sampleText=None):
|
75
|
+
characterSet = makeCharacterSet(font)
|
76
|
+
for size in pointSizes:
|
77
|
+
# start the block
|
78
|
+
writer.begintag("div", c_l_a_s_s="infoBlock")
|
79
|
+
# title
|
80
|
+
writer.begintag("h4", c_l_a_s_s="infoBlockTitle")
|
81
|
+
writer.write("%dpx" % size)
|
82
|
+
writer.endtag("h4")
|
83
|
+
# character set
|
84
|
+
writer.begintag("p", style="font-size: %dpx;" % size, c_l_a_s_s="characterSet")
|
85
|
+
writer.write(characterSet)
|
86
|
+
writer.endtag("p")
|
87
|
+
# sample text
|
88
|
+
if sampleText:
|
89
|
+
writer.begintag("p", style="font-size: %dpx;" % size, c_l_a_s_s="sampleText")
|
90
|
+
writer.write(sampleText)
|
91
|
+
writer.endtag("p")
|
92
|
+
# close the container
|
93
|
+
writer.endtag("div")
|
94
|
+
|
95
|
+
# -------------
|
96
|
+
# Character Set
|
97
|
+
# -------------
|
98
|
+
|
99
|
+
def makeCharacterSet(font):
|
100
|
+
cmap = font["cmap"]
|
101
|
+
table = cmap.getcmap(3, 1)
|
102
|
+
mapping = table.cmap
|
103
|
+
categorizedCharacters = {}
|
104
|
+
glyphNameToCharacter = {}
|
105
|
+
for value, glyphName in sorted(mapping.items()):
|
106
|
+
character = unichr(value)
|
107
|
+
# skip whitespace
|
108
|
+
if not character.strip():
|
109
|
+
continue
|
110
|
+
if glyphName not in glyphNameToCharacter:
|
111
|
+
glyphNameToCharacter[glyphName] = []
|
112
|
+
glyphNameToCharacter[glyphName].append(character)
|
113
|
+
# use the glyph order defined in the font
|
114
|
+
sortedCharacters = []
|
115
|
+
for glyphName in font.getGlyphOrder():
|
116
|
+
if glyphName in glyphNameToCharacter:
|
117
|
+
sortedCharacters += glyphNameToCharacter[glyphName]
|
118
|
+
return u"".join(sortedCharacters)
|
119
|
+
|
120
|
+
# ---------------
|
121
|
+
# Public Function
|
122
|
+
# ---------------
|
123
|
+
|
124
|
+
def proofFont(font, fontPath, sampleText=None):
|
125
|
+
"""
|
126
|
+
Create a proof file from the given font. This always
|
127
|
+
returns HTML.
|
128
|
+
|
129
|
+
Arguments:
|
130
|
+
**font** - A *WOFFFont* object from *woffLib*.
|
131
|
+
**fontPath** - The location of the font file. At the least, this should be the file name for the font.
|
132
|
+
**sampleText** - A string of text to display. If not provided, no text will be displayed.
|
133
|
+
"""
|
134
|
+
# start the html
|
135
|
+
title = "Proof: %s" % os.path.basename(fontPath)
|
136
|
+
cssReplacements = {
|
137
|
+
"/* proof: @font-face rule */" : makeFontFaceRule(font, fontPath, doLocalSrc=False),
|
138
|
+
"/* proof: @font-face font-family */" : makeFontFaceFontFamily(font)
|
139
|
+
}
|
140
|
+
writer = startHTML(title=title, cssReplacements=cssReplacements)
|
141
|
+
# file info
|
142
|
+
writeFileInfo(font, fontPath, writer)
|
143
|
+
# character set
|
144
|
+
writeCharacterSet(font, writer, sampleText=sampleText)
|
145
|
+
# finish the html
|
146
|
+
text = finishHTML(writer)
|
147
|
+
text = text.replace("%%break%%", "</br>")
|
148
|
+
# return
|
149
|
+
return text
|
150
|
+
|
151
|
+
# --------------------
|
152
|
+
# Command Line Behvior
|
153
|
+
# --------------------
|
154
|
+
|
155
|
+
usage = "%prog [options] fontpath1 fontpath2"
|
156
|
+
|
157
|
+
description = """This tool displays information about the
|
158
|
+
contents of one or more WOFF files.
|
159
|
+
"""
|
160
|
+
|
161
|
+
defaultSampleText = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. The quick brown fox jumps over the lazy dog."
|
162
|
+
|
163
|
+
def main():
|
164
|
+
parser = optparse.OptionParser(usage=usage, description=description, version="%prog 0.1beta")
|
165
|
+
parser.add_option("-d", dest="outputDirectory", help="Output directory. The default is to output the proof into the same directory as the font file.")
|
166
|
+
parser.add_option("-o", dest="outputFileName", help="Output file name. The default is \"fontfilename_proof.html\".")
|
167
|
+
parser.add_option("-t", dest="sampleTextFile", help="Sample text file. A file containing sample text to display. If not file is provided, The quick brown fox... will be used.")
|
168
|
+
parser.set_defaults(excludeTests=[])
|
169
|
+
(options, args) = parser.parse_args()
|
170
|
+
outputDirectory = options.outputDirectory
|
171
|
+
if outputDirectory is not None and not os.path.exists(outputDirectory):
|
172
|
+
print "Directory does not exist:", outputDirectory
|
173
|
+
sys.exit()
|
174
|
+
sampleText = defaultSampleText
|
175
|
+
if options.sampleTextFile:
|
176
|
+
if not os.path.exists(options.sampleTextFile):
|
177
|
+
print "Sample text file does not exist:", options.sampleTextFile
|
178
|
+
sys.exit()
|
179
|
+
f = open(options.sampleTextFile, "r")
|
180
|
+
sampleText = f.read()
|
181
|
+
f.close()
|
182
|
+
for fontPath in args:
|
183
|
+
if not os.path.exists(fontPath):
|
184
|
+
print "File does not exist:", fontPath
|
185
|
+
sys.exit()
|
186
|
+
else:
|
187
|
+
print "Creating Proof: %s..." % fontPath
|
188
|
+
fontPath = fontPath.decode("utf-8")
|
189
|
+
font = WOFFFont(fontPath)
|
190
|
+
html = proofFont(font, fontPath, sampleText=sampleText)
|
191
|
+
# make the output file name
|
192
|
+
if options.outputFileName is not None:
|
193
|
+
fileName = options.outputFileName
|
194
|
+
else:
|
195
|
+
fileName = os.path.splitext(os.path.basename(fontPath))[0]
|
196
|
+
fileName += "_proof.html"
|
197
|
+
# make the output directory
|
198
|
+
if options.outputDirectory is not None:
|
199
|
+
directory = options.outputDirectory
|
200
|
+
else:
|
201
|
+
directory = os.path.dirname(fontPath)
|
202
|
+
# write the file
|
203
|
+
path = os.path.join(directory, fileName)
|
204
|
+
path = findUniqueFileName(path)
|
205
|
+
f = open(path, "wb")
|
206
|
+
f.write(html)
|
207
|
+
f.close()
|
208
|
+
|
209
|
+
if __name__ == "__main__":
|
210
|
+
main()
|
@@ -0,0 +1,417 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
from cStringIO import StringIO
|
5
|
+
|
6
|
+
# ----------------------
|
7
|
+
# Very Simple XML Writer
|
8
|
+
# ----------------------
|
9
|
+
|
10
|
+
class XMLWriter(object):
|
11
|
+
|
12
|
+
def __init__(self):
|
13
|
+
self._root = None
|
14
|
+
self._elements = []
|
15
|
+
|
16
|
+
def simpletag(self, tag, **kwargs):
|
17
|
+
ElementTree.SubElement(self._elements[-1], tag, **kwargs)
|
18
|
+
|
19
|
+
def begintag(self, tag, **kwargs):
|
20
|
+
if self._elements:
|
21
|
+
s = ElementTree.SubElement(self._elements[-1], tag, **kwargs)
|
22
|
+
else:
|
23
|
+
s = ElementTree.Element(tag, **kwargs)
|
24
|
+
if self._root is None:
|
25
|
+
self._root = s
|
26
|
+
self._elements.append(s)
|
27
|
+
|
28
|
+
def endtag(self, tag):
|
29
|
+
assert self._elements[-1].tag == tag
|
30
|
+
del self._elements[-1]
|
31
|
+
|
32
|
+
def write(self, text):
|
33
|
+
if self._elements[-1].text is None:
|
34
|
+
self._elements[-1].text = text
|
35
|
+
else:
|
36
|
+
self._elements[-1].text += text
|
37
|
+
|
38
|
+
def compile(self, encoding="utf-8"):
|
39
|
+
f = StringIO()
|
40
|
+
tree = ElementTree.ElementTree(self._root)
|
41
|
+
indent(tree.getroot())
|
42
|
+
tree.write(f, encoding=encoding)
|
43
|
+
text = f.getvalue()
|
44
|
+
del f
|
45
|
+
return text
|
46
|
+
|
47
|
+
def indent(elem, level=0):
|
48
|
+
# this is from http://effbot.python-hosting.com/file/effbotlib/ElementTree.py
|
49
|
+
i = "\n" + level * "\t"
|
50
|
+
if len(elem):
|
51
|
+
if not elem.text or not elem.text.strip():
|
52
|
+
elem.text = i + "\t"
|
53
|
+
for e in elem:
|
54
|
+
indent(e, level + 1)
|
55
|
+
if not e.tail or not e.tail.strip():
|
56
|
+
e.tail = i
|
57
|
+
if level and (not elem.tail or not elem.tail.strip()):
|
58
|
+
elem.tail = i
|
59
|
+
|
60
|
+
# ------------
|
61
|
+
# HTML Helpers
|
62
|
+
# ------------
|
63
|
+
|
64
|
+
defaultCSS = """
|
65
|
+
body {
|
66
|
+
background-color: #e5e5e5;
|
67
|
+
padding: 15px 15px 0px 15px;
|
68
|
+
margin: 0px;
|
69
|
+
font-family: Helvetica, Verdana, Arial, sans-serif;
|
70
|
+
}
|
71
|
+
|
72
|
+
h2.readError {
|
73
|
+
background-color: red;
|
74
|
+
color: white;
|
75
|
+
margin: 20px 15px 20px 15px;
|
76
|
+
padding: 10px;
|
77
|
+
border-radius: 5px;
|
78
|
+
-webkit-border-radius: 5px;
|
79
|
+
-moz-border-radius: 5px;
|
80
|
+
-webkit-box-shadow: #999 0 2px 5px;
|
81
|
+
-moz-box-shadow: #999 0 2px 5px;
|
82
|
+
font-size: 25px;
|
83
|
+
}
|
84
|
+
|
85
|
+
/* info blocks */
|
86
|
+
|
87
|
+
.infoBlock {
|
88
|
+
background-color: white;
|
89
|
+
margin: 0px 0px 15px 0px;
|
90
|
+
padding: 15px;
|
91
|
+
border-radius: 5px;
|
92
|
+
-webkit-border-radius: 5px;
|
93
|
+
-moz-border-radius: 5px;
|
94
|
+
-webkit-box-shadow: rgba(0, 0, 0, .3) 0 2px 5px;
|
95
|
+
-moz-box-shadow: rgba(0, 0, 0, .3) 0 2px 5px;
|
96
|
+
}
|
97
|
+
|
98
|
+
h3.infoBlockTitle {
|
99
|
+
font-size: 20px;
|
100
|
+
margin: 0px 0px 15px 0px;
|
101
|
+
padding: 0px 0px 10px 0px;
|
102
|
+
border-bottom: 1px solid #e5e5e5;
|
103
|
+
}
|
104
|
+
|
105
|
+
h4.infoBlockTitle {
|
106
|
+
font-size: 17px;
|
107
|
+
margin: 0px 0px 15px 0px;
|
108
|
+
padding: 0px 0px 10px 0px;
|
109
|
+
border-bottom: 1px solid #e5e5e5;
|
110
|
+
}
|
111
|
+
|
112
|
+
table.report {
|
113
|
+
border-collapse: collapse;
|
114
|
+
width: 100%;
|
115
|
+
font-size: 14px;
|
116
|
+
}
|
117
|
+
|
118
|
+
table.report tr {
|
119
|
+
border-top: 1px solid white;
|
120
|
+
}
|
121
|
+
|
122
|
+
table.report tr.testPass, table.report tr.testReportPass {
|
123
|
+
background-color: #c8ffaf;
|
124
|
+
}
|
125
|
+
|
126
|
+
table.report tr.testError, table.report tr.testReportError {
|
127
|
+
background-color: #ffc3af;
|
128
|
+
}
|
129
|
+
|
130
|
+
table.report tr.testWarning, table.report tr.testReportWarning {
|
131
|
+
background-color: #ffe1af;
|
132
|
+
}
|
133
|
+
|
134
|
+
table.report tr.testNote, table.report tr.testReportNote {
|
135
|
+
background-color: #96e1ff;
|
136
|
+
}
|
137
|
+
|
138
|
+
table.report tr.testTraceback, table.report tr.testReportTraceback {
|
139
|
+
background-color: red;
|
140
|
+
color: white;
|
141
|
+
}
|
142
|
+
|
143
|
+
table.report td {
|
144
|
+
padding: 7px 5px 7px 5px;
|
145
|
+
vertical-align: top;
|
146
|
+
}
|
147
|
+
|
148
|
+
table.report td.title {
|
149
|
+
width: 80px;
|
150
|
+
text-align: right;
|
151
|
+
font-weight: bold;
|
152
|
+
text-transform: uppercase;
|
153
|
+
}
|
154
|
+
|
155
|
+
table.report td.testReportResultCount {
|
156
|
+
width: 100px;
|
157
|
+
}
|
158
|
+
|
159
|
+
table.report td.toggleButton {
|
160
|
+
text-align: center;
|
161
|
+
width: 50px;
|
162
|
+
border-left: 1px solid white;
|
163
|
+
cursor: pointer;
|
164
|
+
}
|
165
|
+
|
166
|
+
.infoBlock td p.info {
|
167
|
+
font-size: 12px;
|
168
|
+
font-style: italic;
|
169
|
+
margin: 5px 0px 0px 0px;
|
170
|
+
}
|
171
|
+
|
172
|
+
/* SFNT table */
|
173
|
+
|
174
|
+
table.sfntTableData {
|
175
|
+
font-size: 14px;
|
176
|
+
width: 100%;
|
177
|
+
border-collapse: collapse;
|
178
|
+
padding: 0px;
|
179
|
+
}
|
180
|
+
|
181
|
+
table.sfntTableData th {
|
182
|
+
padding: 5px 0px 5px 0px;
|
183
|
+
text-align: left
|
184
|
+
}
|
185
|
+
|
186
|
+
table.sfntTableData tr.uncompressed {
|
187
|
+
background-color: #ffc3af;
|
188
|
+
}
|
189
|
+
|
190
|
+
table.sfntTableData td {
|
191
|
+
width: 20%;
|
192
|
+
padding: 5px 0px 5px 0px;
|
193
|
+
border: 1px solid #e5e5e5;
|
194
|
+
border-left: none;
|
195
|
+
border-right: none;
|
196
|
+
font-family: Consolas, Menlo, "Vera Mono", Monaco, monospace;
|
197
|
+
}
|
198
|
+
|
199
|
+
pre {
|
200
|
+
font-size: 12px;
|
201
|
+
font-family: Consolas, Menlo, "Vera Mono", Monaco, monospace;
|
202
|
+
margin: 0px;
|
203
|
+
padding: 0px;
|
204
|
+
}
|
205
|
+
|
206
|
+
/* Metadata */
|
207
|
+
|
208
|
+
.metadataElement {
|
209
|
+
background: rgba(0, 0, 0, 0.03);
|
210
|
+
margin: 10px 0px 10px 0px;
|
211
|
+
border: 2px solid #d8d8d8;
|
212
|
+
padding: 10px;
|
213
|
+
}
|
214
|
+
|
215
|
+
h5.metadata {
|
216
|
+
font-size: 14px;
|
217
|
+
margin: 5px 0px 10px 0px;
|
218
|
+
padding: 0px 0px 5px 0px;
|
219
|
+
border-bottom: 1px solid #d8d8d8;
|
220
|
+
}
|
221
|
+
|
222
|
+
h6.metadata {
|
223
|
+
font-size: 12px;
|
224
|
+
font-weight: normal;
|
225
|
+
margin: 10px 0px 10px 0px;
|
226
|
+
padding: 0px 0px 5px 0px;
|
227
|
+
border-bottom: 1px solid #d8d8d8;
|
228
|
+
}
|
229
|
+
|
230
|
+
table.metadata {
|
231
|
+
font-size: 12px;
|
232
|
+
width: 100%;
|
233
|
+
border-collapse: collapse;
|
234
|
+
padding: 0px;
|
235
|
+
}
|
236
|
+
|
237
|
+
table.metadata td.key {
|
238
|
+
width: 5em;
|
239
|
+
padding: 5px 5px 5px 0px;
|
240
|
+
border-right: 1px solid #d8d8d8;
|
241
|
+
text-align: right;
|
242
|
+
vertical-align: top;
|
243
|
+
}
|
244
|
+
|
245
|
+
table.metadata td.value {
|
246
|
+
padding: 5px 0px 5px 5px;
|
247
|
+
border-left: 1px solid #d8d8d8;
|
248
|
+
text-align: left;
|
249
|
+
vertical-align: top;
|
250
|
+
}
|
251
|
+
|
252
|
+
p.metadata {
|
253
|
+
font-size: 12px;
|
254
|
+
font-style: italic;
|
255
|
+
}
|
256
|
+
|
257
|
+
/* Proof */
|
258
|
+
|
259
|
+
/* proof: @font-face rule */
|
260
|
+
|
261
|
+
p.characterSet {
|
262
|
+
/* proof: @font-face font-family */
|
263
|
+
line-height: 135%;
|
264
|
+
word-wrap: break-word;
|
265
|
+
margin: 0px;
|
266
|
+
padding: 0px;
|
267
|
+
}
|
268
|
+
|
269
|
+
p.sampleText {
|
270
|
+
/* proof: @font-face font-family */
|
271
|
+
line-height: 135%;
|
272
|
+
margin: .5em 0px 0px 0px;
|
273
|
+
padding: .5em 0px 0px 0px;
|
274
|
+
border-top: 1px solid #e5e5e5;
|
275
|
+
}
|
276
|
+
"""
|
277
|
+
|
278
|
+
defaultJavascript = """
|
279
|
+
|
280
|
+
//<![CDATA[
|
281
|
+
function testResultToggleButtonHit(buttonID, className) {
|
282
|
+
// change the button title
|
283
|
+
var element = document.getElementById(buttonID);
|
284
|
+
if (element.innerHTML == "Show" ) {
|
285
|
+
element.innerHTML = "Hide";
|
286
|
+
}
|
287
|
+
else {
|
288
|
+
element.innerHTML = "Show";
|
289
|
+
}
|
290
|
+
// toggle the elements
|
291
|
+
var elements = getTestResults(className);
|
292
|
+
for (var e = 0; e < elements.length; ++e) {
|
293
|
+
toggleElement(elements[e]);
|
294
|
+
}
|
295
|
+
// toggle the info blocks
|
296
|
+
toggleInfoBlocks();
|
297
|
+
}
|
298
|
+
|
299
|
+
function getTestResults(className) {
|
300
|
+
var rows = document.getElementsByTagName("tr");
|
301
|
+
var found = Array();
|
302
|
+
for (var r = 0; r < rows.length; ++r) {
|
303
|
+
var row = rows[r];
|
304
|
+
if (row.className == className) {
|
305
|
+
found[found.length] = row;
|
306
|
+
}
|
307
|
+
}
|
308
|
+
return found;
|
309
|
+
}
|
310
|
+
|
311
|
+
function toggleElement(element) {
|
312
|
+
if (element.style.display != "none" ) {
|
313
|
+
element.style.display = "none";
|
314
|
+
}
|
315
|
+
else {
|
316
|
+
element.style.display = "";
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
function toggleInfoBlocks() {
|
321
|
+
var tables = document.getElementsByTagName("table")
|
322
|
+
for (var t = 0; t < tables.length; ++t) {
|
323
|
+
var table = tables[t];
|
324
|
+
if (table.className == "report") {
|
325
|
+
var haveVisibleRow = false;
|
326
|
+
var rows = table.rows;
|
327
|
+
for (var r = 0; r < rows.length; ++r) {
|
328
|
+
var row = rows[r];
|
329
|
+
if (row.style.display == "none") {
|
330
|
+
var i = 0;
|
331
|
+
}
|
332
|
+
else {
|
333
|
+
haveVisibleRow = true;
|
334
|
+
}
|
335
|
+
}
|
336
|
+
var div = table.parentNode;
|
337
|
+
if (haveVisibleRow == true) {
|
338
|
+
div.style.display = "";
|
339
|
+
}
|
340
|
+
else {
|
341
|
+
div.style.display = "none";
|
342
|
+
}
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
//]]>
|
347
|
+
"""
|
348
|
+
|
349
|
+
def startHTML(title=None, cssReplacements={}):
|
350
|
+
writer = XMLWriter()
|
351
|
+
# start the html
|
352
|
+
writer.begintag("html", xmlns="http://www.w3.org/1999/xhtml", lang="en")
|
353
|
+
# start the head
|
354
|
+
writer.begintag("head")
|
355
|
+
writer.simpletag("meta", http_equiv="Content-Type", content="text/html; charset=utf-8")
|
356
|
+
# title
|
357
|
+
if title is not None:
|
358
|
+
writer.begintag("title")
|
359
|
+
writer.write(title)
|
360
|
+
writer.endtag("title")
|
361
|
+
# write the css
|
362
|
+
writer.begintag("style", type="text/css")
|
363
|
+
css = defaultCSS
|
364
|
+
for before, after in cssReplacements.items():
|
365
|
+
css = css.replace(before, after)
|
366
|
+
writer.write(css)
|
367
|
+
writer.endtag("style")
|
368
|
+
# write the javascript
|
369
|
+
writer.begintag("script", type="text/javascript")
|
370
|
+
javascript = defaultJavascript
|
371
|
+
## hack around some ElementTree escaping
|
372
|
+
javascript = javascript.replace("<", "l_e_s_s")
|
373
|
+
javascript = javascript.replace(">", "g_r_e_a_t_e_r")
|
374
|
+
writer.write(javascript)
|
375
|
+
writer.endtag("script")
|
376
|
+
# close the head
|
377
|
+
writer.endtag("head")
|
378
|
+
# start the body
|
379
|
+
writer.begintag("body")
|
380
|
+
# return the writer
|
381
|
+
return writer
|
382
|
+
|
383
|
+
def finishHTML(writer):
|
384
|
+
# close the body
|
385
|
+
writer.endtag("body")
|
386
|
+
# close the html
|
387
|
+
writer.endtag("html")
|
388
|
+
# get the text
|
389
|
+
text = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
|
390
|
+
text += writer.compile()
|
391
|
+
text = text.replace("c_l_a_s_s", "class")
|
392
|
+
text = text.replace("a_p_o_s_t_r_o_p_h_e", "'")
|
393
|
+
text = text.replace("l_e_s_s", "<")
|
394
|
+
text = text.replace("g_r_e_a_t_e_r", ">")
|
395
|
+
text = text.replace("http_equiv", "http-equiv")
|
396
|
+
# return
|
397
|
+
return text
|
398
|
+
|
399
|
+
# ---------
|
400
|
+
# File Name
|
401
|
+
# ---------
|
402
|
+
|
403
|
+
def findUniqueFileName(path):
|
404
|
+
if not os.path.exists(path):
|
405
|
+
return path
|
406
|
+
folder = os.path.dirname(path)
|
407
|
+
fileName = os.path.basename(path)
|
408
|
+
fileName, extension = os.path.splitext(fileName)
|
409
|
+
stamp = time.strftime("%Y-%m-%d %H-%M-%S %Z")
|
410
|
+
newFileName = "%s (%s)%s" % (fileName, stamp, extension)
|
411
|
+
newPath = os.path.join(folder, newFileName)
|
412
|
+
# intentionally break to prevent a file overwrite.
|
413
|
+
# this could happen if the user has a directory full
|
414
|
+
# of files with future time stamped file names.
|
415
|
+
# not likely, but avoid it all the same.
|
416
|
+
assert not os.path.exists(newPath)
|
417
|
+
return newPath
|