aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/ttLib/tables/sbixStrike.py
blob: 7614af4c7b325c363c0b30edfc85a478aa15f01b (plain)
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)