Add support for Microsoft COLR/CPAL layered color glyphs

This commit is contained in:
Behdad Esfahbod 2013-07-24 12:31:50 -04:00
parent 7baa13689c
commit 50d9a44e58
4 changed files with 258 additions and 4 deletions

View File

@ -42,11 +42,7 @@ When using TTX from the command line there are a bunch of extra options, these a
The following tables are currently supported:
<BLOCKQUOTE><TT>
<!-- begin table list -->
<<<<<<< HEAD
BASE, CFF, DSIG, EBDT, EBLC, GDEF, GMAP, GPKG, GPOS, GSUB, JSTF, LTSH, META, OS/2, SING, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, VORG, cmap, cvt, fpgm, gasp, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, post, prep, vhea and vmtx
=======
BASE, CBDT, CBLC, CFF, COLR, CPAL, DSIG, EBDT, EBLC, GDEF, GMAP, GPKG, GPOS, GSUB, JSTF, LTSH, META, OS/2, SING, TSI0, TSI1, TSI2, TSI3, TSI5, TSIB, TSID, TSIJ, TSIP, TSIS, TSIV, VORG, cmap, cvt, fpgm, gasp, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, post, prep, vhea and vmtx
>>>>>>> 305bad8... Add support for Google CBLC/CBDT color bitmaps
<!-- end table list -->
</TT></BLOCKQUOTE>
Other tables are dumped as hexadecimal data.

View File

@ -0,0 +1,159 @@
import operator
import DefaultTable
import struct
from fontTools.ttLib import sfnt
from fontTools.misc.textTools import safeEval, readHex
from types import IntType, StringType
class table_C_O_L_R_(DefaultTable.DefaultTable):
""" This table is structured so that you can treat it like a dictionary keyed by glyph name.
ttFont['COLR'][<glyphName>] will return the color layers for any glyph
ttFont['COLR'][<glyphName>] = <value> will set the color layers for any glyph.
"""
def decompile(self, data, ttFont):
self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
self.version, numBaseGlyphRecords, offsetBaseGlyphRecord, offsetLayerRecord, numLayerRecords = struct.unpack(">HHLLH", data[:14])
assert (self.version == 0), "Version of COLR table is higher than I know how to handle"
glyphOrder = ttFont.getGlyphOrder()
gids = []
layerLists = []
glyphPos = offsetBaseGlyphRecord
for i in range(numBaseGlyphRecords):
gid, firstLayerIndex, numLayers = struct.unpack(">HHH", data[glyphPos:glyphPos+6])
glyphPos += 6
gids.append(gid)
assert (firstLayerIndex + numLayers <= numLayerRecords)
layerPos = offsetLayerRecord + firstLayerIndex * 4
layers = []
for j in range(numLayers):
layerGid, colorID = struct.unpack(">HH", data[layerPos:layerPos+4])
try:
layerName = glyphOrder[layerGid]
except IndexError:
layerName = self.getGlyphName(layerGid)
layerPos += 4
layers.append(LayerRecord(layerName, colorID))
layerLists.append(layers)
self.ColorLayers = colorLayerLists = {}
try:
names = map(operator.getitem, [glyphOrder]*numBaseGlyphRecords, gids)
except IndexError:
getGlyphName = self.getGlyphName
names = map(getGlyphName, gids )
map(operator.setitem, [colorLayerLists]*numBaseGlyphRecords, names, layerLists)
def compile(self, ttFont):
ordered = []
ttFont.getReverseGlyphMap(rebuild=1)
glyphNames = self.ColorLayers.keys()
for glyphName in glyphNames:
try:
gid = ttFont.getGlyphID(glyphName)
except:
assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
ordered.sort()
glyphMap = []
layerMap = []
for (gid, glyphName, layers) in ordered:
glyphMap.append(struct.pack(">HHH", gid, len(layerMap), len(layers)))
for layer in layers:
layerMap.append(struct.pack(">HH", ttFont.getGlyphID(layer.name), layer.colorID))
dataList = [struct.pack(">HHLLH", self.version, len(glyphMap), 14, 14+6*len(glyphMap), len(layerMap))]
dataList.extend(glyphMap)
dataList.extend(layerMap)
data = "".join(dataList)
return data
def toXML(self, writer, ttFont):
writer.simpletag("version", value=self.version)
writer.newline()
ordered = []
glyphNames = self.ColorLayers.keys()
for glyphName in glyphNames:
try:
gid = ttFont.getGlyphID(glyphName)
except:
assert 0, "COLR table contains a glyph name not in ttFont.getGlyphNames(): " + str(glyphName)
ordered.append([gid, glyphName, self.ColorLayers[glyphName]])
ordered.sort()
for entry in ordered:
writer.begintag("ColorGlyph", name=entry[1])
writer.newline()
for layer in entry[2]:
layer.toXML(writer, ttFont)
writer.endtag("ColorGlyph")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "ColorLayers"):
self.ColorLayers = {}
self.getGlyphName = ttFont.getGlyphName # for use in get/set item functions, for access by GID
if name == "ColorGlyph":
glyphName = attrs["name"]
for element in content:
if isinstance(element, StringType):
continue
layers = []
for element in content:
if isinstance(element, StringType):
continue
layer = LayerRecord()
layer.fromXML(element, ttFont)
layers.append (layer)
operator.setitem(self, glyphName, layers)
elif attrs.has_key("value"):
value = safeEval(attrs["value"])
setattr(self, name, value)
def __getitem__(self, glyphSelector):
if type(glyphSelector) == IntType:
# its a gid, convert to glyph name
glyphSelector = self.getGlyphName(glyphSelector)
if not self.ColorLayers.has_key(glyphSelector):
return None
return self.ColorLayers[glyphSelector]
def __setitem__(self, glyphSelector, value):
if type(glyphSelector) == IntType:
# its a gid, convert to glyph name
glyphSelector = self.getGlyphName(glyphSelector)
if value:
self.ColorLayers[glyphSelector] = value
elif self.ColorLayers.has_key(glyphSelector):
del self.ColorLayers[glyphSelector]
class LayerRecord:
def __init__(self, name = None, colorID = None):
self.name = name
self.colorID = colorID
def toXML(self, writer, ttFont):
writer.simpletag("layer", name=self.name, colorID=self.colorID)
writer.newline()
def fromXML(self, (eltname, attrs, content), ttFont):
for (name, value) in attrs.items():
if name == "name":
if type(value) == IntType:
value = ttFont.getGlyphName(value)
setattr(self, name, value)
else:
try:
value = safeEval(value)
except OverflowError:
value = long(value)
setattr(self, name, value)

View File

@ -0,0 +1,97 @@
import operator
import DefaultTable
import struct
from fontTools.ttLib import sfnt
from fontTools.misc.textTools import safeEval, readHex
from types import IntType, StringType
class table_C_P_A_L_(DefaultTable.DefaultTable):
def decompile(self, data, ttFont):
self.version, self.numPaletteEntries, numPalettes, numColorRecords, goffsetFirstColorRecord = struct.unpack(">HHHHL", data[:12])
assert (self.version == 0), "Version of COLR table is higher than I know how to handle"
self.palettes = []
pos = 12
for i in range(numPalettes):
startIndex = struct.unpack(">H", data[pos:pos+2])[0]
assert (startIndex + self.numPaletteEntries <= numColorRecords)
pos += 2
palette = []
ppos = goffsetFirstColorRecord + startIndex * 4
for j in range(self.numPaletteEntries):
palette.append( Color(*struct.unpack(">BBBB", data[ppos:ppos+4])) )
ppos += 4
self.palettes.append(palette)
def compile(self, ttFont):
dataList = [struct.pack(">HHHHL", self.version, self.numPaletteEntries, len(self.palettes), self.numPaletteEntries * len(self.palettes), 12+2*len(self.palettes))]
for i in range(len(self.palettes)):
dataList.append(struct.pack(">H", i*self.numPaletteEntries))
for palette in self.palettes:
assert(len(palette) == self.numPaletteEntries)
for color in palette:
dataList.append(struct.pack(">BBBB", color.blue,color.green,color.red,color.alpha))
data = "".join(dataList)
return data
def toXML(self, writer, ttFont):
writer.simpletag("version", value=self.version)
writer.newline()
writer.simpletag("numPaletteEntries", value=self.numPaletteEntries)
writer.newline()
for index, palette in enumerate(self.palettes):
writer.begintag("palette", index=index)
writer.newline()
assert(len(palette) == self.numPaletteEntries)
for cindex, color in enumerate(palette):
color.toXML(writer, ttFont, cindex)
writer.endtag("palette")
writer.newline()
def fromXML(self, (name, attrs, content), ttFont):
if not hasattr(self, "palettes"):
self.palettes = []
if name == "palette":
palette = []
for element in content:
if isinstance(element, StringType):
continue
palette = []
for element in content:
if isinstance(element, StringType):
continue
color = Color()
color.fromXML(element, ttFont)
palette.append (color)
self.palettes.append(palette)
elif attrs.has_key("value"):
value = safeEval(attrs["value"])
setattr(self, name, value)
class Color:
def __init__(self, blue=None, green=None, red=None, alpha=None):
self.blue = blue
self.green = green
self.red = red
self.alpha = alpha
def hex(self):
return "#%02X%02X%02X%02X" % (self.red, self.green, self.blue, self.alpha)
def __repr__(self):
return self.hex()
def toXML(self, writer, ttFont, index=None):
writer.simpletag("color", value=self.hex(), index=index)
writer.newline()
def fromXML(self, (eltname, attrs, content), ttFont):
value = attrs["value"]
if value[0] == '#':
value = value[1:]
self.red = int(value[0:2], 16)
self.green = int(value[2:4], 16)
self.blue = int(value[4:6], 16)
self.alpha = int(value[6:8], 16) if len (value) >= 8 else 0xFF

View File

@ -7,6 +7,8 @@ def _moduleFinderHint():
import C_B_D_T_
import C_B_L_C_
import C_F_F_
import C_O_L_R_
import C_P_A_L_
import D_S_I_G_
import E_B_D_T_
import E_B_L_C_