Initial sbix support from Jens Kutilek
This commit is contained in:
parent
d0a85754fe
commit
f1394f3fdd
139
Lib/fontTools/ttLib/tables/_s_b_i_x.py
Normal file
139
Lib/fontTools/ttLib/tables/_s_b_i_x.py
Normal file
@ -0,0 +1,139 @@
|
||||
import DefaultTable
|
||||
import struct, sstruct
|
||||
#from fontTools.misc.textTools import readHex
|
||||
from types import TupleType
|
||||
from sbixBitmapSet import *
|
||||
from sbixBitmap import *
|
||||
|
||||
"""
|
||||
sbix Table organization:
|
||||
|
||||
USHORT version?
|
||||
USHORT version?
|
||||
USHORT count number of bitmap sets
|
||||
offsetEntry offsetEntry[count] offsetEntries
|
||||
(Variable) storage for bitmap sets
|
||||
|
||||
|
||||
offsetEntry:
|
||||
|
||||
ULONG offset offset from table start to bitmap set
|
||||
|
||||
|
||||
bitmap set:
|
||||
|
||||
USHORT size height and width in pixels
|
||||
USHORT resolution ?
|
||||
offsetRecord offsetRecord[]
|
||||
(Variable) storage for bitmaps
|
||||
|
||||
|
||||
offsetRecord:
|
||||
|
||||
ULONG bitmapOffset offset from start of bitmap set to individual bitmap
|
||||
|
||||
|
||||
bitmap:
|
||||
|
||||
ULONG reserved 00 00 00 00
|
||||
char[4] format data type, e.g. "png "
|
||||
(Variable) bitmap data
|
||||
"""
|
||||
|
||||
sbixHeaderFormat = """
|
||||
>
|
||||
usVal1: H # 00 01
|
||||
usVal2: H # 00 01
|
||||
numSets: L # 00 00 00 02 # number of bitmap sets
|
||||
"""
|
||||
sbixHeaderFormatSize = sstruct.calcsize(sbixHeaderFormat)
|
||||
|
||||
|
||||
sbixBitmapSetOffsetFormat = """
|
||||
>
|
||||
offset: L # 00 00 00 10 # offset from table start to each bitmap set
|
||||
"""
|
||||
sbixBitmapSetOffsetFormatSize = sstruct.calcsize(sbixBitmapSetOffsetFormat)
|
||||
|
||||
|
||||
class table__s_b_i_x(DefaultTable.DefaultTable):
|
||||
def __init__(self, tag):
|
||||
self.tableTag = tag
|
||||
self.usVal1 = 1
|
||||
self.usVal2 = 1
|
||||
self.numSets = 0
|
||||
self.bitmapSets = {}
|
||||
self.bitmapSetOffsets = []
|
||||
|
||||
def decompile(self, data, ttFont):
|
||||
# read table header
|
||||
sstruct.unpack(sbixHeaderFormat, data[ : sbixHeaderFormatSize], self)
|
||||
# collect offsets to individual bitmap sets in self.bitmapSetOffsets
|
||||
for i in range(self.numSets):
|
||||
myOffset = sbixHeaderFormatSize + i * sbixBitmapSetOffsetFormatSize
|
||||
offsetEntry = sbixBitmapSetOffset()
|
||||
sstruct.unpack(sbixBitmapSetOffsetFormat, \
|
||||
data[myOffset : myOffset+sbixBitmapSetOffsetFormatSize], \
|
||||
offsetEntry)
|
||||
self.bitmapSetOffsets.append(offsetEntry.offset)
|
||||
|
||||
# decompile BitmapSets
|
||||
for i in range(self.numSets-1, -1, -1):
|
||||
myBitmapSet = BitmapSet(rawdata=data[self.bitmapSetOffsets[i]:])
|
||||
data = data[:self.bitmapSetOffsets[i]]
|
||||
myBitmapSet.decompile(ttFont)
|
||||
#print " BitmapSet length: %xh" % len(bitmapSetData)
|
||||
#print "Number of Bitmaps:", myBitmapSet.numBitmaps
|
||||
if myBitmapSet.size in self.bitmapSets:
|
||||
from fontTools import ttLib
|
||||
raise(ttLib.TTLibError, "Pixel 'size' must be unique for each BitmapSet")
|
||||
self.bitmapSets[myBitmapSet.size] = myBitmapSet
|
||||
|
||||
# after the bitmaps have been extracted, we don't need the offsets anymore
|
||||
del self.bitmapSetOffsets
|
||||
|
||||
def compile(self, ttFont):
|
||||
sbixData = ""
|
||||
self.numSets = len(self.bitmapSets)
|
||||
sbixHeader = sstruct.pack(sbixHeaderFormat, self)
|
||||
|
||||
# calculate offset to start of first bitmap set
|
||||
setOffset = sbixHeaderFormatSize + sbixBitmapSetOffsetFormatSize * self.numSets
|
||||
|
||||
for si in sorted(self.bitmapSets.keys()):
|
||||
myBitmapSet = self.bitmapSets[si]
|
||||
myBitmapSet.compile(ttFont)
|
||||
# append offset to this bitmap set to table header
|
||||
myBitmapSet.offset = setOffset
|
||||
sbixHeader += sstruct.pack(sbixBitmapSetOffsetFormat, myBitmapSet)
|
||||
setOffset += sbixBitmapSetHeaderFormatSize + len(myBitmapSet.data)
|
||||
sbixData += myBitmapSet.data
|
||||
|
||||
return sbixHeader + sbixData
|
||||
|
||||
def toXML(self, xmlWriter, ttFont):
|
||||
xmlWriter.simpletag("usVal1", value=self.usVal1)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.simpletag("usVal2", value=self.usVal2)
|
||||
xmlWriter.newline()
|
||||
for i in sorted(self.bitmapSets.keys()):
|
||||
self.bitmapSets[i].toXML(xmlWriter, ttFont)
|
||||
|
||||
def fromXML(self, (name, attrs, content), ttFont):
|
||||
if name in ["usVal1", "usVal2"]:
|
||||
setattr(self, name, int(attrs["value"]))
|
||||
elif name == "bitmapSet":
|
||||
myBitmapSet = BitmapSet()
|
||||
for element in content:
|
||||
if type(element) == TupleType:
|
||||
myBitmapSet.fromXML(element, ttFont)
|
||||
self.bitmapSets[myBitmapSet.size] = myBitmapSet
|
||||
else:
|
||||
from fontTools import ttLib
|
||||
raise ttLib.TTLibError, "can't handle '%s' element" % name
|
||||
|
||||
|
||||
# Helper classes
|
||||
|
||||
class sbixBitmapSetOffset(object):
|
||||
pass
|
100
Lib/fontTools/ttLib/tables/sbixBitmap.py
Normal file
100
Lib/fontTools/ttLib/tables/sbixBitmap.py
Normal file
@ -0,0 +1,100 @@
|
||||
import struct, sstruct
|
||||
from fontTools.misc.textTools import readHex
|
||||
|
||||
sbixBitmapHeaderFormat = """
|
||||
>
|
||||
usReserved1: H # 00 00
|
||||
usReserved2: H # 00 00
|
||||
imageFormatTag: 4s # e.g. "png "
|
||||
"""
|
||||
|
||||
sbixBitmapHeaderFormatSize = sstruct.calcsize(sbixBitmapHeaderFormat)
|
||||
|
||||
|
||||
class Bitmap(object):
|
||||
def __init__(self, glyphName=None, referenceGlyphName=None, usReserved1=0, usReserved2=0, imageFormatTag=None, imageData=None, rawdata=None, gid=0):
|
||||
self.gid = gid
|
||||
self.glyphName = glyphName
|
||||
self.referenceGlyphName = referenceGlyphName
|
||||
self.usReserved1 = usReserved1
|
||||
self.usReserved2 = usReserved2
|
||||
self.rawdata = rawdata
|
||||
self.imageFormatTag = imageFormatTag
|
||||
self.imageData = imageData
|
||||
|
||||
def decompile(self, ttFont):
|
||||
self.glyphName = ttFont.getGlyphName(self.gid)
|
||||
if self.rawdata is None:
|
||||
from fontTools import ttLib
|
||||
raise(ttLib.TTLibError, "No table data to decompile.")
|
||||
if len(self.rawdata) > 0:
|
||||
if len(self.rawdata) < sbixBitmapHeaderFormatSize:
|
||||
from fontTools import ttLib
|
||||
#print "Bitmap %i header too short: Expected %x, got %x." % (self.gid, sbixBitmapHeaderFormatSize, len(self.rawdata))
|
||||
raise(ttLib.TTLibError, "Bitmap header too short.")
|
||||
|
||||
sstruct.unpack(sbixBitmapHeaderFormat, self.rawdata[:sbixBitmapHeaderFormatSize], self)
|
||||
|
||||
if self.imageFormatTag == "dupe":
|
||||
# bitmap is a reference to another glyph's bitmap
|
||||
gid, = struct.unpack(">H", self.rawdata[sbixBitmapHeaderFormatSize:])
|
||||
self.referenceGlyphName = ttFont.getGlyphName(gid)
|
||||
else:
|
||||
self.imageData = self.rawdata[sbixBitmapHeaderFormatSize:]
|
||||
self.referenceGlyphName = None
|
||||
# clean up
|
||||
del self.rawdata
|
||||
del self.gid
|
||||
|
||||
def compile(self, ttFont):
|
||||
if self.glyphName is None:
|
||||
from fontTools import ttLib
|
||||
raise ttLib.TTLibError, "Can't compile bitmap without glyph name"
|
||||
# TODO: if ttFont has no maxp, cmap etc., ignore glyph names and compile by index?
|
||||
# (needed if you just want to compile the sbix table on its own)
|
||||
self.gid = struct.pack(">H", ttFont.getGlyphID(self.glyphName))
|
||||
if self.imageFormatTag is None:
|
||||
self.rawdata = ""
|
||||
else:
|
||||
self.rawdata = sstruct.pack(sbixBitmapHeaderFormat, self) + self.imageData
|
||||
|
||||
def toXML(self, xmlWriter, ttFont):
|
||||
if self.imageFormatTag == None:
|
||||
# TODO: ignore empty bitmaps?
|
||||
# a bitmap entry is required for each glyph,
|
||||
# but empty ones can be calculated at compile time
|
||||
xmlWriter.simpletag("bitmap", glyphname=self.glyphName)
|
||||
xmlWriter.newline()
|
||||
return
|
||||
xmlWriter.begintag("bitmap", format=self.imageFormatTag, glyphname=self.glyphName)
|
||||
xmlWriter.newline()
|
||||
#xmlWriter.simpletag("usReserved1", value=self.usReserved1)
|
||||
#xmlWriter.newline()
|
||||
#xmlWriter.simpletag("usReserved2", value=self.usReserved2)
|
||||
#xmlWriter.newline()
|
||||
if self.imageFormatTag == "dupe":
|
||||
# format == "dupe" is apparently a reference to another glyph id.
|
||||
xmlWriter.simpletag("ref", glyphname=self.referenceGlyphName)
|
||||
else:
|
||||
xmlWriter.begintag("hexdata")
|
||||
xmlWriter.newline()
|
||||
xmlWriter.dumphex(self.imageData)
|
||||
xmlWriter.endtag("hexdata")
|
||||
xmlWriter.newline()
|
||||
xmlWriter.endtag("bitmap")
|
||||
xmlWriter.newline()
|
||||
|
||||
def fromXML(self, (name, attrs, content), ttFont):
|
||||
#if name in ["usReserved1", "usReserved2"]:
|
||||
# setattr(self, name, int(attrs["value"]))
|
||||
#elif
|
||||
if name == "ref":
|
||||
# bitmap is a "dupe", i.e. a reference to another bitmap.
|
||||
# in this case imageData contains the glyph id of the reference glyph
|
||||
# get glyph id from glyphname
|
||||
self.imageData = struct.pack(">H", ttFont.getGlyphID(attrs["glyphname"]))
|
||||
elif name == "hexdata":
|
||||
self.imageData = readHex(content)
|
||||
else:
|
||||
from fontTools import ttLib
|
||||
raise ttLib.TTLibError, "can't handle '%s' element" % name
|
135
Lib/fontTools/ttLib/tables/sbixBitmapSet.py
Normal file
135
Lib/fontTools/ttLib/tables/sbixBitmapSet.py
Normal file
@ -0,0 +1,135 @@
|
||||
import struct, sstruct
|
||||
from types import TupleType
|
||||
from sbixBitmap import *
|
||||
#from fontTools.misc.textTools import hexStr
|
||||
|
||||
sbixBitmapSetHeaderFormat = """
|
||||
>
|
||||
size: H # 00 28
|
||||
resolution: H # 00 48
|
||||
"""
|
||||
|
||||
sbixBitmapOffsetEntryFormat = """
|
||||
>
|
||||
ulOffset: L # 00 00 07 E0 # Offset from start of first offset entry to each bitmap
|
||||
"""
|
||||
|
||||
sbixBitmapSetHeaderFormatSize = sstruct.calcsize(sbixBitmapSetHeaderFormat)
|
||||
sbixBitmapOffsetEntryFormatSize = sstruct.calcsize(sbixBitmapOffsetEntryFormat)
|
||||
|
||||
|
||||
class BitmapSet(object):
|
||||
def __init__(self, rawdata=None, size=0, resolution=72):
|
||||
self.data = rawdata
|
||||
self.size = size
|
||||
self.resolution = resolution
|
||||
self.bitmaps = {}
|
||||
|
||||
def decompile(self, ttFont):
|
||||
if self.data is None:
|
||||
from fontTools import ttLib
|
||||
raise(ttLib.TTLibError, "No table data to decompile.")
|
||||
if len(self.data) < sbixBitmapSetHeaderFormatSize:
|
||||
from fontTools import ttLib
|
||||
raise(ttLib.TTLibError, "BitmapSet header too short: Expected %x, got %x.") \
|
||||
% (sbixBitmapSetHeaderFormatSize, len(self.data))
|
||||
|
||||
# read BitmapSet header from raw data
|
||||
sstruct.unpack(sbixBitmapSetHeaderFormat, self.data[:sbixBitmapSetHeaderFormatSize], self)
|
||||
|
||||
# calculate number of bitmaps
|
||||
firstBitmapOffset, = struct.unpack(">L", \
|
||||
self.data[sbixBitmapSetHeaderFormatSize : sbixBitmapSetHeaderFormatSize + sbixBitmapOffsetEntryFormatSize])
|
||||
self.numBitmaps = (firstBitmapOffset - sbixBitmapSetHeaderFormatSize) / sbixBitmapOffsetEntryFormatSize - 1
|
||||
# ^ -1 because there's one more offset than bitmaps
|
||||
|
||||
# build offset list for single bitmap offsets
|
||||
self.bitmapOffsets = []
|
||||
for i in range(self.numBitmaps + 1): # + 1 because there's one more offset than bitmaps
|
||||
start = i * sbixBitmapOffsetEntryFormatSize + sbixBitmapSetHeaderFormatSize
|
||||
myOffset, = struct.unpack(">L", self.data[start : start + sbixBitmapOffsetEntryFormatSize])
|
||||
self.bitmapOffsets.append(myOffset)
|
||||
|
||||
# iterate through offset list and slice raw data into bitmaps
|
||||
for i in range(self.numBitmaps):
|
||||
myBitmap = Bitmap(rawdata=self.data[self.bitmapOffsets[i] : self.bitmapOffsets[i+1]], gid=i)
|
||||
myBitmap.decompile(ttFont)
|
||||
self.bitmaps[myBitmap.glyphName] = myBitmap
|
||||
del self.bitmapOffsets
|
||||
del self.data
|
||||
|
||||
def compile(self, ttFont):
|
||||
self.bitmapOffsets = ""
|
||||
self.bitmapData = ""
|
||||
|
||||
glyphOrder = ttFont.getGlyphOrder()
|
||||
|
||||
# first bitmap starts right after the header
|
||||
bitmapOffset = sbixBitmapSetHeaderFormatSize + sbixBitmapOffsetEntryFormatSize * (len(glyphOrder) + 1)
|
||||
for glyphName in glyphOrder:
|
||||
if glyphName in self.bitmaps:
|
||||
# we have a bitmap for this glyph
|
||||
myBitmap = self.bitmaps[glyphName]
|
||||
else:
|
||||
# must add empty bitmap for this glyph
|
||||
myBitmap = Bitmap(glyphName=glyphName)
|
||||
myBitmap.compile(ttFont)
|
||||
myBitmap.ulOffset = bitmapOffset
|
||||
self.bitmapData += myBitmap.rawdata
|
||||
bitmapOffset += len(myBitmap.rawdata)
|
||||
self.bitmapOffsets += sstruct.pack(sbixBitmapOffsetEntryFormat, myBitmap)
|
||||
|
||||
# add last "offset", really the end address of the last bitmap
|
||||
dummy = Bitmap()
|
||||
dummy.ulOffset = bitmapOffset
|
||||
self.bitmapOffsets += sstruct.pack(sbixBitmapOffsetEntryFormat, dummy)
|
||||
|
||||
# bitmap sets are padded to 4 byte boundaries
|
||||
dataLength = len(self.bitmapOffsets) + len(self.bitmapData)
|
||||
if dataLength % 4 != 0:
|
||||
padding = 4 - (dataLength % 4)
|
||||
else:
|
||||
padding = 0
|
||||
|
||||
# pack header
|
||||
self.data = sstruct.pack(sbixBitmapSetHeaderFormat, self)
|
||||
# add offset, image data and padding after header
|
||||
self.data += self.bitmapOffsets + self.bitmapData + "\0" * padding
|
||||
|
||||
def toXML(self, xmlWriter, ttFont):
|
||||
xmlWriter.begintag("bitmapSet")
|
||||
xmlWriter.newline()
|
||||
xmlWriter.simpletag("size", value=self.size)
|
||||
xmlWriter.newline()
|
||||
xmlWriter.simpletag("resolution", value=self.resolution)
|
||||
xmlWriter.newline()
|
||||
glyphOrder = ttFont.getGlyphOrder()
|
||||
for i in range(len(glyphOrder)):
|
||||
if glyphOrder[i] in self.bitmaps:
|
||||
self.bitmaps[glyphOrder[i]].toXML(xmlWriter, ttFont)
|
||||
# TODO: what if there are more bitmaps than glyphs?
|
||||
xmlWriter.endtag("bitmapSet")
|
||||
xmlWriter.newline()
|
||||
|
||||
def fromXML(self, (name, attrs, content), ttFont):
|
||||
if name in ["size", "resolution"]:
|
||||
setattr(self, name, int(attrs["value"]))
|
||||
elif name == "bitmap":
|
||||
if attrs.has_key("format"):
|
||||
myFormat = attrs["format"]
|
||||
else:
|
||||
myFormat = None
|
||||
if attrs.has_key("glyphname"):
|
||||
myGlyphName = attrs["glyphname"]
|
||||
else:
|
||||
from fontTools import ttLib
|
||||
raise ttLib.TTLibError, "Bitmap must have a glyph name."
|
||||
myBitmap = Bitmap(glyphName=myGlyphName, imageFormatTag=myFormat)
|
||||
for element in content:
|
||||
if type(element) == TupleType:
|
||||
myBitmap.fromXML(element, ttFont)
|
||||
myBitmap.compile(ttFont)
|
||||
self.bitmaps[myBitmap.glyphName] = myBitmap
|
||||
else:
|
||||
from fontTools import ttLib
|
||||
raise ttLib.TTLibError, "can't handle '%s' element" % name
|
Loading…
x
Reference in New Issue
Block a user