* Support sbix Glyphs with graphicType = "flip" Since iOS 17.4 beta, Apple introduced the "flip" graphic type for the sbix glyphs to reference the another glyph to be flipped by their render engine. Their use case is for directional emojis. Example emoji: https://emojipedia.org/woman_walking_facing_right#technical This is the example of the output XML after this PR: ``` <glyph graphicType="flip" name="u1F3C3.0.M.u27A1" originOffsetX="0" originOffsetY="0"> <ref glyphname="u1F3C3.0.M" /> </glyph> ``` Before, it was just `<hexdata>` with some data in it.
150 lines
5.7 KiB
Python
150 lines
5.7 KiB
Python
from fontTools.misc import sstruct
|
|
from fontTools.misc.textTools import readHex, safeEval
|
|
import struct
|
|
|
|
|
|
sbixGlyphHeaderFormat = """
|
|
>
|
|
originOffsetX: h # The x-value of the point in the glyph relative to its
|
|
# lower-left corner which corresponds to the origin of
|
|
# the glyph on the screen, that is the point on the
|
|
# baseline at the left edge of the glyph.
|
|
originOffsetY: h # The y-value of the point in the glyph relative to its
|
|
# lower-left corner which corresponds to the origin of
|
|
# the glyph on the screen, that is the point on the
|
|
# baseline at the left edge of the glyph.
|
|
graphicType: 4s # e.g. "png "
|
|
"""
|
|
|
|
sbixGlyphHeaderFormatSize = sstruct.calcsize(sbixGlyphHeaderFormat)
|
|
|
|
|
|
class Glyph(object):
|
|
def __init__(
|
|
self,
|
|
glyphName=None,
|
|
referenceGlyphName=None,
|
|
originOffsetX=0,
|
|
originOffsetY=0,
|
|
graphicType=None,
|
|
imageData=None,
|
|
rawdata=None,
|
|
gid=0,
|
|
):
|
|
self.gid = gid
|
|
self.glyphName = glyphName
|
|
self.referenceGlyphName = referenceGlyphName
|
|
self.originOffsetX = originOffsetX
|
|
self.originOffsetY = originOffsetY
|
|
self.rawdata = rawdata
|
|
self.graphicType = graphicType
|
|
self.imageData = imageData
|
|
|
|
# fix self.graphicType if it is null terminated or too short
|
|
if self.graphicType is not None:
|
|
if self.graphicType[-1] == "\0":
|
|
self.graphicType = self.graphicType[:-1]
|
|
if len(self.graphicType) > 4:
|
|
from fontTools import ttLib
|
|
|
|
raise ttLib.TTLibError(
|
|
"Glyph.graphicType must not be longer than 4 characters."
|
|
)
|
|
elif len(self.graphicType) < 4:
|
|
# pad with spaces
|
|
self.graphicType += " "[: (4 - len(self.graphicType))]
|
|
|
|
def is_reference_type(self):
|
|
"""Returns True if this glyph is a reference to another glyph's image data."""
|
|
return self.graphicType == "dupe" or self.graphicType == "flip"
|
|
|
|
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) < sbixGlyphHeaderFormatSize:
|
|
from fontTools import ttLib
|
|
|
|
# print "Glyph %i header too short: Expected %x, got %x." % (self.gid, sbixGlyphHeaderFormatSize, len(self.rawdata))
|
|
raise ttLib.TTLibError("Glyph header too short.")
|
|
|
|
sstruct.unpack(
|
|
sbixGlyphHeaderFormat, self.rawdata[:sbixGlyphHeaderFormatSize], self
|
|
)
|
|
|
|
if self.is_reference_type():
|
|
# this glyph is a reference to another glyph's image data
|
|
(gid,) = struct.unpack(">H", self.rawdata[sbixGlyphHeaderFormatSize:])
|
|
self.referenceGlyphName = ttFont.getGlyphName(gid)
|
|
else:
|
|
self.imageData = self.rawdata[sbixGlyphHeaderFormatSize:]
|
|
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 Glyph 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.graphicType is None:
|
|
rawdata = b""
|
|
else:
|
|
rawdata = sstruct.pack(sbixGlyphHeaderFormat, self)
|
|
if self.is_reference_type():
|
|
rawdata += struct.pack(">H", ttFont.getGlyphID(self.referenceGlyphName))
|
|
else:
|
|
assert self.imageData is not None
|
|
rawdata += self.imageData
|
|
self.rawdata = rawdata
|
|
|
|
def toXML(self, xmlWriter, ttFont):
|
|
if self.graphicType is None:
|
|
# TODO: ignore empty glyphs?
|
|
# a glyph data entry is required for each glyph,
|
|
# but empty ones can be calculated at compile time
|
|
xmlWriter.simpletag("glyph", name=self.glyphName)
|
|
xmlWriter.newline()
|
|
return
|
|
xmlWriter.begintag(
|
|
"glyph",
|
|
graphicType=self.graphicType,
|
|
name=self.glyphName,
|
|
originOffsetX=self.originOffsetX,
|
|
originOffsetY=self.originOffsetY,
|
|
)
|
|
xmlWriter.newline()
|
|
if self.is_reference_type():
|
|
# this glyph is 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("glyph")
|
|
xmlWriter.newline()
|
|
|
|
def fromXML(self, name, attrs, content, ttFont):
|
|
if name == "ref":
|
|
# this glyph i.e. a reference to another glyph's image data.
|
|
# in this case imageData contains the glyph id of the reference glyph
|
|
# get glyph id from glyphname
|
|
glyphname = safeEval("'''" + attrs["glyphname"] + "'''")
|
|
self.imageData = struct.pack(">H", ttFont.getGlyphID(glyphname))
|
|
self.referenceGlyphName = glyphname
|
|
elif name == "hexdata":
|
|
self.imageData = readHex(content)
|
|
else:
|
|
from fontTools import ttLib
|
|
|
|
raise ttLib.TTLibError("can't handle '%s' element" % name)
|