C_O_L_R_.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. # Copyright 2013 Google, Inc. All Rights Reserved.
  2. #
  3. # Google Author(s): Behdad Esfahbod
  4. from fontTools.misc.textTools import safeEval
  5. from . import DefaultTable
  6. class table_C_O_L_R_(DefaultTable.DefaultTable):
  7. """This table is structured so that you can treat it like a dictionary keyed by glyph name.
  8. ``ttFont['COLR'][<glyphName>]`` will return the color layers for any glyph.
  9. ``ttFont['COLR'][<glyphName>] = <value>`` will set the color layers for any glyph.
  10. """
  11. @staticmethod
  12. def _decompileColorLayersV0(table):
  13. if not table.LayerRecordArray:
  14. return {}
  15. colorLayerLists = {}
  16. layerRecords = table.LayerRecordArray.LayerRecord
  17. numLayerRecords = len(layerRecords)
  18. for baseRec in table.BaseGlyphRecordArray.BaseGlyphRecord:
  19. baseGlyph = baseRec.BaseGlyph
  20. firstLayerIndex = baseRec.FirstLayerIndex
  21. numLayers = baseRec.NumLayers
  22. assert firstLayerIndex + numLayers <= numLayerRecords
  23. layers = []
  24. for i in range(firstLayerIndex, firstLayerIndex + numLayers):
  25. layerRec = layerRecords[i]
  26. layers.append(LayerRecord(layerRec.LayerGlyph, layerRec.PaletteIndex))
  27. colorLayerLists[baseGlyph] = layers
  28. return colorLayerLists
  29. def _toOTTable(self, ttFont):
  30. from . import otTables
  31. from fontTools.colorLib.builder import populateCOLRv0
  32. tableClass = getattr(otTables, self.tableTag)
  33. table = tableClass()
  34. table.Version = self.version
  35. populateCOLRv0(
  36. table,
  37. {
  38. baseGlyph: [(layer.name, layer.colorID) for layer in layers]
  39. for baseGlyph, layers in self.ColorLayers.items()
  40. },
  41. glyphMap=ttFont.getReverseGlyphMap(rebuild=True),
  42. )
  43. return table
  44. def decompile(self, data, ttFont):
  45. from .otBase import OTTableReader
  46. from . import otTables
  47. # We use otData to decompile, but we adapt the decompiled otTables to the
  48. # existing COLR v0 API for backward compatibility.
  49. reader = OTTableReader(data, tableTag=self.tableTag)
  50. tableClass = getattr(otTables, self.tableTag)
  51. table = tableClass()
  52. table.decompile(reader, ttFont)
  53. self.version = table.Version
  54. if self.version == 0:
  55. self.ColorLayers = self._decompileColorLayersV0(table)
  56. else:
  57. # for new versions, keep the raw otTables around
  58. self.table = table
  59. def compile(self, ttFont):
  60. from .otBase import OTTableWriter
  61. if hasattr(self, "table"):
  62. table = self.table
  63. else:
  64. table = self._toOTTable(ttFont)
  65. writer = OTTableWriter(tableTag=self.tableTag)
  66. table.compile(writer, ttFont)
  67. return writer.getAllData()
  68. def toXML(self, writer, ttFont):
  69. if hasattr(self, "table"):
  70. self.table.toXML2(writer, ttFont)
  71. else:
  72. writer.simpletag("version", value=self.version)
  73. writer.newline()
  74. for baseGlyph in sorted(self.ColorLayers.keys(), key=ttFont.getGlyphID):
  75. writer.begintag("ColorGlyph", name=baseGlyph)
  76. writer.newline()
  77. for layer in self.ColorLayers[baseGlyph]:
  78. layer.toXML(writer, ttFont)
  79. writer.endtag("ColorGlyph")
  80. writer.newline()
  81. def fromXML(self, name, attrs, content, ttFont):
  82. if name == "version": # old COLR v0 API
  83. setattr(self, name, safeEval(attrs["value"]))
  84. elif name == "ColorGlyph":
  85. if not hasattr(self, "ColorLayers"):
  86. self.ColorLayers = {}
  87. glyphName = attrs["name"]
  88. for element in content:
  89. if isinstance(element, str):
  90. continue
  91. layers = []
  92. for element in content:
  93. if isinstance(element, str):
  94. continue
  95. layer = LayerRecord()
  96. layer.fromXML(element[0], element[1], element[2], ttFont)
  97. layers.append(layer)
  98. self.ColorLayers[glyphName] = layers
  99. else: # new COLR v1 API
  100. from . import otTables
  101. if not hasattr(self, "table"):
  102. tableClass = getattr(otTables, self.tableTag)
  103. self.table = tableClass()
  104. self.table.fromXML(name, attrs, content, ttFont)
  105. self.table.populateDefaults()
  106. self.version = self.table.Version
  107. def __getitem__(self, glyphName):
  108. if not isinstance(glyphName, str):
  109. raise TypeError(f"expected str, found {type(glyphName).__name__}")
  110. return self.ColorLayers[glyphName]
  111. def __setitem__(self, glyphName, value):
  112. if not isinstance(glyphName, str):
  113. raise TypeError(f"expected str, found {type(glyphName).__name__}")
  114. if value is not None:
  115. self.ColorLayers[glyphName] = value
  116. elif glyphName in self.ColorLayers:
  117. del self.ColorLayers[glyphName]
  118. def __delitem__(self, glyphName):
  119. del self.ColorLayers[glyphName]
  120. class LayerRecord(object):
  121. def __init__(self, name=None, colorID=None):
  122. self.name = name
  123. self.colorID = colorID
  124. def toXML(self, writer, ttFont):
  125. writer.simpletag("layer", name=self.name, colorID=self.colorID)
  126. writer.newline()
  127. def fromXML(self, eltname, attrs, content, ttFont):
  128. for name, value in attrs.items():
  129. if name == "name":
  130. setattr(self, name, value)
  131. else:
  132. setattr(self, name, safeEval(value))