123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- from fontTools.misc import sstruct
- from fontTools.misc.fixedTools import (
- fixedToFloat as fi2fl,
- floatToFixed as fl2fi,
- floatToFixedToStr as fl2str,
- strToFixedToFloat as str2fl,
- )
- from fontTools.misc.textTools import Tag, bytesjoin, safeEval
- from fontTools.ttLib import TTLibError
- from . import DefaultTable
- import struct
- # Apple's documentation of 'fvar':
- # https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html
- FVAR_HEADER_FORMAT = """
- > # big endian
- version: L
- offsetToData: H
- countSizePairs: H
- axisCount: H
- axisSize: H
- instanceCount: H
- instanceSize: H
- """
- FVAR_AXIS_FORMAT = """
- > # big endian
- axisTag: 4s
- minValue: 16.16F
- defaultValue: 16.16F
- maxValue: 16.16F
- flags: H
- axisNameID: H
- """
- FVAR_INSTANCE_FORMAT = """
- > # big endian
- subfamilyNameID: H
- flags: H
- """
- class table__f_v_a_r(DefaultTable.DefaultTable):
- dependencies = ["name"]
- def __init__(self, tag=None):
- DefaultTable.DefaultTable.__init__(self, tag)
- self.axes = []
- self.instances = []
- def compile(self, ttFont):
- instanceSize = sstruct.calcsize(FVAR_INSTANCE_FORMAT) + (len(self.axes) * 4)
- includePostScriptNames = any(
- instance.postscriptNameID != 0xFFFF for instance in self.instances
- )
- if includePostScriptNames:
- instanceSize += 2
- header = {
- "version": 0x00010000,
- "offsetToData": sstruct.calcsize(FVAR_HEADER_FORMAT),
- "countSizePairs": 2,
- "axisCount": len(self.axes),
- "axisSize": sstruct.calcsize(FVAR_AXIS_FORMAT),
- "instanceCount": len(self.instances),
- "instanceSize": instanceSize,
- }
- result = [sstruct.pack(FVAR_HEADER_FORMAT, header)]
- result.extend([axis.compile() for axis in self.axes])
- axisTags = [axis.axisTag for axis in self.axes]
- for instance in self.instances:
- result.append(instance.compile(axisTags, includePostScriptNames))
- return bytesjoin(result)
- def decompile(self, data, ttFont):
- header = {}
- headerSize = sstruct.calcsize(FVAR_HEADER_FORMAT)
- header = sstruct.unpack(FVAR_HEADER_FORMAT, data[0:headerSize])
- if header["version"] != 0x00010000:
- raise TTLibError("unsupported 'fvar' version %04x" % header["version"])
- pos = header["offsetToData"]
- axisSize = header["axisSize"]
- for _ in range(header["axisCount"]):
- axis = Axis()
- axis.decompile(data[pos : pos + axisSize])
- self.axes.append(axis)
- pos += axisSize
- instanceSize = header["instanceSize"]
- axisTags = [axis.axisTag for axis in self.axes]
- for _ in range(header["instanceCount"]):
- instance = NamedInstance()
- instance.decompile(data[pos : pos + instanceSize], axisTags)
- self.instances.append(instance)
- pos += instanceSize
- def toXML(self, writer, ttFont):
- for axis in self.axes:
- axis.toXML(writer, ttFont)
- for instance in self.instances:
- instance.toXML(writer, ttFont)
- def fromXML(self, name, attrs, content, ttFont):
- if name == "Axis":
- axis = Axis()
- axis.fromXML(name, attrs, content, ttFont)
- self.axes.append(axis)
- elif name == "NamedInstance":
- instance = NamedInstance()
- instance.fromXML(name, attrs, content, ttFont)
- self.instances.append(instance)
- class Axis(object):
- def __init__(self):
- self.axisTag = None
- self.axisNameID = 0
- self.flags = 0
- self.minValue = -1.0
- self.defaultValue = 0.0
- self.maxValue = 1.0
- def compile(self):
- return sstruct.pack(FVAR_AXIS_FORMAT, self)
- def decompile(self, data):
- sstruct.unpack2(FVAR_AXIS_FORMAT, data, self)
- def toXML(self, writer, ttFont):
- name = (
- ttFont["name"].getDebugName(self.axisNameID) if "name" in ttFont else None
- )
- if name is not None:
- writer.newline()
- writer.comment(name)
- writer.newline()
- writer.begintag("Axis")
- writer.newline()
- for tag, value in [
- ("AxisTag", self.axisTag),
- ("Flags", "0x%X" % self.flags),
- ("MinValue", fl2str(self.minValue, 16)),
- ("DefaultValue", fl2str(self.defaultValue, 16)),
- ("MaxValue", fl2str(self.maxValue, 16)),
- ("AxisNameID", str(self.axisNameID)),
- ]:
- writer.begintag(tag)
- writer.write(value)
- writer.endtag(tag)
- writer.newline()
- writer.endtag("Axis")
- writer.newline()
- def fromXML(self, name, _attrs, content, ttFont):
- assert name == "Axis"
- for tag, _, value in filter(lambda t: type(t) is tuple, content):
- value = "".join(value)
- if tag == "AxisTag":
- self.axisTag = Tag(value)
- elif tag in {"Flags", "MinValue", "DefaultValue", "MaxValue", "AxisNameID"}:
- setattr(
- self,
- tag[0].lower() + tag[1:],
- str2fl(value, 16) if tag.endswith("Value") else safeEval(value),
- )
- class NamedInstance(object):
- def __init__(self):
- self.subfamilyNameID = 0
- self.postscriptNameID = 0xFFFF
- self.flags = 0
- self.coordinates = {}
- def compile(self, axisTags, includePostScriptName):
- result = [sstruct.pack(FVAR_INSTANCE_FORMAT, self)]
- for axis in axisTags:
- fixedCoord = fl2fi(self.coordinates[axis], 16)
- result.append(struct.pack(">l", fixedCoord))
- if includePostScriptName:
- result.append(struct.pack(">H", self.postscriptNameID))
- return bytesjoin(result)
- def decompile(self, data, axisTags):
- sstruct.unpack2(FVAR_INSTANCE_FORMAT, data, self)
- pos = sstruct.calcsize(FVAR_INSTANCE_FORMAT)
- for axis in axisTags:
- value = struct.unpack(">l", data[pos : pos + 4])[0]
- self.coordinates[axis] = fi2fl(value, 16)
- pos += 4
- if pos + 2 <= len(data):
- self.postscriptNameID = struct.unpack(">H", data[pos : pos + 2])[0]
- else:
- self.postscriptNameID = 0xFFFF
- def toXML(self, writer, ttFont):
- name = (
- ttFont["name"].getDebugName(self.subfamilyNameID)
- if "name" in ttFont
- else None
- )
- if name is not None:
- writer.newline()
- writer.comment(name)
- writer.newline()
- psname = (
- ttFont["name"].getDebugName(self.postscriptNameID)
- if "name" in ttFont
- else None
- )
- if psname is not None:
- writer.comment("PostScript: " + psname)
- writer.newline()
- if self.postscriptNameID == 0xFFFF:
- writer.begintag(
- "NamedInstance",
- flags=("0x%X" % self.flags),
- subfamilyNameID=self.subfamilyNameID,
- )
- else:
- writer.begintag(
- "NamedInstance",
- flags=("0x%X" % self.flags),
- subfamilyNameID=self.subfamilyNameID,
- postscriptNameID=self.postscriptNameID,
- )
- writer.newline()
- for axis in ttFont["fvar"].axes:
- writer.simpletag(
- "coord",
- axis=axis.axisTag,
- value=fl2str(self.coordinates[axis.axisTag], 16),
- )
- writer.newline()
- writer.endtag("NamedInstance")
- writer.newline()
- def fromXML(self, name, attrs, content, ttFont):
- assert name == "NamedInstance"
- self.subfamilyNameID = safeEval(attrs["subfamilyNameID"])
- self.flags = safeEval(attrs.get("flags", "0"))
- if "postscriptNameID" in attrs:
- self.postscriptNameID = safeEval(attrs["postscriptNameID"])
- else:
- self.postscriptNameID = 0xFFFF
- for tag, elementAttrs, _ in filter(lambda t: type(t) is tuple, content):
- if tag == "coord":
- value = str2fl(elementAttrs["value"], 16)
- self.coordinates[elementAttrs["axis"]] = value
|