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