Add support for Microsoft COLR/CPAL layered color glyphs
This commit is contained in:
parent
7baa13689c
commit
50d9a44e58
@ -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.
|
||||
|
159
Lib/fontTools/ttLib/tables/C_O_L_R_.py
Normal file
159
Lib/fontTools/ttLib/tables/C_O_L_R_.py
Normal 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)
|
97
Lib/fontTools/ttLib/tables/C_P_A_L_.py
Normal file
97
Lib/fontTools/ttLib/tables/C_P_A_L_.py
Normal 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
|
@ -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_
|
||||
|
Loading…
x
Reference in New Issue
Block a user