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
165
166
167
168
169
170
171
172
173
174
175
176
177
|
from fontTools.misc import sstruct
from fontTools.misc.textTools import safeEval
from .sbixGlyph import Glyph
import struct
sbixStrikeHeaderFormat = """
>
ppem: H # The PPEM for which this strike was designed (e.g., 9,
# 12, 24)
resolution: H # The screen resolution (in dpi) for which this strike
# was designed (e.g., 72)
"""
sbixGlyphDataOffsetFormat = """
>
glyphDataOffset: L # Offset from the beginning of the strike data record
# to data for the individual glyph
"""
sbixStrikeHeaderFormatSize = sstruct.calcsize(sbixStrikeHeaderFormat)
sbixGlyphDataOffsetFormatSize = sstruct.calcsize(sbixGlyphDataOffsetFormat)
class Strike(object):
def __init__(self, rawdata=None, ppem=0, resolution=72):
self.data = rawdata
self.ppem = ppem
self.resolution = resolution
self.glyphs = {}
def decompile(self, ttFont):
if self.data is None:
from fontTools import ttLib
raise ttLib.TTLibError
if len(self.data) < sbixStrikeHeaderFormatSize:
from fontTools import ttLib
raise (
ttLib.TTLibError,
"Strike header too short: Expected %x, got %x.",
) % (sbixStrikeHeaderFormatSize, len(self.data))
# read Strike header from raw data
sstruct.unpack(
sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self
)
# calculate number of glyphs
(firstGlyphDataOffset,) = struct.unpack(
">L",
self.data[
sbixStrikeHeaderFormatSize : sbixStrikeHeaderFormatSize
+ sbixGlyphDataOffsetFormatSize
],
)
self.numGlyphs = (
firstGlyphDataOffset - sbixStrikeHeaderFormatSize
) // sbixGlyphDataOffsetFormatSize - 1
# ^ -1 because there's one more offset than glyphs
# build offset list for single glyph data offsets
self.glyphDataOffsets = []
for i in range(
self.numGlyphs + 1
): # + 1 because there's one more offset than glyphs
start = i * sbixGlyphDataOffsetFormatSize + sbixStrikeHeaderFormatSize
(current_offset,) = struct.unpack(
">L", self.data[start : start + sbixGlyphDataOffsetFormatSize]
)
self.glyphDataOffsets.append(current_offset)
# iterate through offset list and slice raw data into glyph data records
for i in range(self.numGlyphs):
current_glyph = Glyph(
rawdata=self.data[
self.glyphDataOffsets[i] : self.glyphDataOffsets[i + 1]
],
gid=i,
)
current_glyph.decompile(ttFont)
self.glyphs[current_glyph.glyphName] = current_glyph
del self.glyphDataOffsets
del self.numGlyphs
del self.data
def compile(self, ttFont):
self.glyphDataOffsets = b""
self.bitmapData = b""
glyphOrder = ttFont.getGlyphOrder()
# first glyph starts right after the header
currentGlyphDataOffset = (
sbixStrikeHeaderFormatSize
+ sbixGlyphDataOffsetFormatSize * (len(glyphOrder) + 1)
)
for glyphName in glyphOrder:
if glyphName in self.glyphs:
# we have glyph data for this glyph
current_glyph = self.glyphs[glyphName]
else:
# must add empty glyph data record for this glyph
current_glyph = Glyph(glyphName=glyphName)
current_glyph.compile(ttFont)
current_glyph.glyphDataOffset = currentGlyphDataOffset
self.bitmapData += current_glyph.rawdata
currentGlyphDataOffset += len(current_glyph.rawdata)
self.glyphDataOffsets += sstruct.pack(
sbixGlyphDataOffsetFormat, current_glyph
)
# add last "offset", really the end address of the last glyph data record
dummy = Glyph()
dummy.glyphDataOffset = currentGlyphDataOffset
self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, dummy)
# pack header
self.data = sstruct.pack(sbixStrikeHeaderFormat, self)
# add offsets and image data after header
self.data += self.glyphDataOffsets + self.bitmapData
def toXML(self, xmlWriter, ttFont):
xmlWriter.begintag("strike")
xmlWriter.newline()
xmlWriter.simpletag("ppem", value=self.ppem)
xmlWriter.newline()
xmlWriter.simpletag("resolution", value=self.resolution)
xmlWriter.newline()
glyphOrder = ttFont.getGlyphOrder()
for i in range(len(glyphOrder)):
if glyphOrder[i] in self.glyphs:
self.glyphs[glyphOrder[i]].toXML(xmlWriter, ttFont)
# TODO: what if there are more glyph data records than (glyf table) glyphs?
xmlWriter.endtag("strike")
xmlWriter.newline()
def fromXML(self, name, attrs, content, ttFont):
if name in ["ppem", "resolution"]:
setattr(self, name, safeEval(attrs["value"]))
elif name == "glyph":
if "graphicType" in attrs:
myFormat = safeEval("'''" + attrs["graphicType"] + "'''")
else:
myFormat = None
if "glyphname" in attrs:
myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''")
elif "name" in attrs:
myGlyphName = safeEval("'''" + attrs["name"] + "'''")
else:
from fontTools import ttLib
raise ttLib.TTLibError("Glyph must have a glyph name.")
if "originOffsetX" in attrs:
myOffsetX = safeEval(attrs["originOffsetX"])
else:
myOffsetX = 0
if "originOffsetY" in attrs:
myOffsetY = safeEval(attrs["originOffsetY"])
else:
myOffsetY = 0
current_glyph = Glyph(
glyphName=myGlyphName,
graphicType=myFormat,
originOffsetX=myOffsetX,
originOffsetY=myOffsetY,
)
for element in content:
if isinstance(element, tuple):
name, attrs, content = element
current_glyph.fromXML(name, attrs, content, ttFont)
current_glyph.compile(ttFont)
self.glyphs[current_glyph.glyphName] = current_glyph
else:
from fontTools import ttLib
raise ttLib.TTLibError("can't handle '%s' element" % name)
|