• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.textTools import safeEval
3from .sbixGlyph import Glyph
4import struct
5
6sbixStrikeHeaderFormat = """
7	>
8	ppem:          H	# The PPEM for which this strike was designed (e.g., 9,
9						# 12, 24)
10	resolution:    H	# The screen resolution (in dpi) for which this strike
11						# was designed (e.g., 72)
12"""
13
14sbixGlyphDataOffsetFormat = """
15	>
16	glyphDataOffset:   L	# Offset from the beginning of the strike data record
17							# to data for the individual glyph
18"""
19
20sbixStrikeHeaderFormatSize = sstruct.calcsize(sbixStrikeHeaderFormat)
21sbixGlyphDataOffsetFormatSize = sstruct.calcsize(sbixGlyphDataOffsetFormat)
22
23
24class Strike(object):
25	def __init__(self, rawdata=None, ppem=0, resolution=72):
26		self.data = rawdata
27		self.ppem = ppem
28		self.resolution = resolution
29		self.glyphs = {}
30
31	def decompile(self, ttFont):
32		if self.data is None:
33			from fontTools import ttLib
34			raise ttLib.TTLibError
35		if len(self.data) < sbixStrikeHeaderFormatSize:
36			from fontTools import ttLib
37			raise(ttLib.TTLibError, "Strike header too short: Expected %x, got %x.") \
38				% (sbixStrikeHeaderFormatSize, len(self.data))
39
40		# read Strike header from raw data
41		sstruct.unpack(sbixStrikeHeaderFormat, self.data[:sbixStrikeHeaderFormatSize], self)
42
43		# calculate number of glyphs
44		firstGlyphDataOffset, = struct.unpack(">L", \
45			self.data[sbixStrikeHeaderFormatSize:sbixStrikeHeaderFormatSize + sbixGlyphDataOffsetFormatSize])
46		self.numGlyphs = (firstGlyphDataOffset - sbixStrikeHeaderFormatSize) // sbixGlyphDataOffsetFormatSize - 1
47		# ^ -1 because there's one more offset than glyphs
48
49		# build offset list for single glyph data offsets
50		self.glyphDataOffsets = []
51		for i in range(self.numGlyphs + 1): # + 1 because there's one more offset than glyphs
52			start = i * sbixGlyphDataOffsetFormatSize + sbixStrikeHeaderFormatSize
53			current_offset, = struct.unpack(">L", self.data[start:start + sbixGlyphDataOffsetFormatSize])
54			self.glyphDataOffsets.append(current_offset)
55
56		# iterate through offset list and slice raw data into glyph data records
57		for i in range(self.numGlyphs):
58			current_glyph = Glyph(rawdata=self.data[self.glyphDataOffsets[i]:self.glyphDataOffsets[i+1]], gid=i)
59			current_glyph.decompile(ttFont)
60			self.glyphs[current_glyph.glyphName] = current_glyph
61		del self.glyphDataOffsets
62		del self.numGlyphs
63		del self.data
64
65	def compile(self, ttFont):
66		self.glyphDataOffsets = b""
67		self.bitmapData = b""
68
69		glyphOrder = ttFont.getGlyphOrder()
70
71		# first glyph starts right after the header
72		currentGlyphDataOffset = sbixStrikeHeaderFormatSize + sbixGlyphDataOffsetFormatSize * (len(glyphOrder) + 1)
73		for glyphName in glyphOrder:
74			if glyphName in self.glyphs:
75				# we have glyph data for this glyph
76				current_glyph = self.glyphs[glyphName]
77			else:
78				# must add empty glyph data record for this glyph
79				current_glyph = Glyph(glyphName=glyphName)
80			current_glyph.compile(ttFont)
81			current_glyph.glyphDataOffset = currentGlyphDataOffset
82			self.bitmapData += current_glyph.rawdata
83			currentGlyphDataOffset += len(current_glyph.rawdata)
84			self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, current_glyph)
85
86		# add last "offset", really the end address of the last glyph data record
87		dummy = Glyph()
88		dummy.glyphDataOffset = currentGlyphDataOffset
89		self.glyphDataOffsets += sstruct.pack(sbixGlyphDataOffsetFormat, dummy)
90
91		# pack header
92		self.data = sstruct.pack(sbixStrikeHeaderFormat, self)
93		# add offsets and image data after header
94		self.data += self.glyphDataOffsets + self.bitmapData
95
96	def toXML(self, xmlWriter, ttFont):
97		xmlWriter.begintag("strike")
98		xmlWriter.newline()
99		xmlWriter.simpletag("ppem", value=self.ppem)
100		xmlWriter.newline()
101		xmlWriter.simpletag("resolution", value=self.resolution)
102		xmlWriter.newline()
103		glyphOrder = ttFont.getGlyphOrder()
104		for i in range(len(glyphOrder)):
105			if glyphOrder[i] in self.glyphs:
106				self.glyphs[glyphOrder[i]].toXML(xmlWriter, ttFont)
107				# TODO: what if there are more glyph data records than (glyf table) glyphs?
108		xmlWriter.endtag("strike")
109		xmlWriter.newline()
110
111	def fromXML(self, name, attrs, content, ttFont):
112		if name in ["ppem", "resolution"]:
113			setattr(self, name, safeEval(attrs["value"]))
114		elif name == "glyph":
115			if "graphicType" in attrs:
116				myFormat = safeEval("'''" + attrs["graphicType"] + "'''")
117			else:
118				myFormat = None
119			if "glyphname" in attrs:
120				myGlyphName = safeEval("'''" + attrs["glyphname"] + "'''")
121			elif "name" in attrs:
122				myGlyphName = safeEval("'''" + attrs["name"] + "'''")
123			else:
124				from fontTools import ttLib
125				raise ttLib.TTLibError("Glyph must have a glyph name.")
126			if "originOffsetX" in attrs:
127				myOffsetX = safeEval(attrs["originOffsetX"])
128			else:
129				myOffsetX = 0
130			if "originOffsetY" in attrs:
131				myOffsetY = safeEval(attrs["originOffsetY"])
132			else:
133				myOffsetY = 0
134			current_glyph = Glyph(
135				glyphName=myGlyphName,
136				graphicType=myFormat,
137				originOffsetX=myOffsetX,
138				originOffsetY=myOffsetY,
139			)
140			for element in content:
141				if isinstance(element, tuple):
142					name, attrs, content = element
143					current_glyph.fromXML(name, attrs, content, ttFont)
144					current_glyph.compile(ttFont)
145			self.glyphs[current_glyph.glyphName] = current_glyph
146		else:
147			from fontTools import ttLib
148			raise ttLib.TTLibError("can't handle '%s' element" % name)
149