236 lines
8.4 KiB
Python
236 lines
8.4 KiB
Python
from fontTools.misc import sstruct
|
|
from fontTools.misc.fixedTools import floatToFixedToStr
|
|
from fontTools.misc.textTools import safeEval
|
|
|
|
# from itertools import *
|
|
from functools import partial
|
|
from . import DefaultTable
|
|
from . import grUtils
|
|
import struct
|
|
|
|
|
|
Glat_format_0 = """
|
|
> # big endian
|
|
version: 16.16F
|
|
"""
|
|
|
|
Glat_format_3 = """
|
|
>
|
|
version: 16.16F
|
|
compression:L # compression scheme or reserved
|
|
"""
|
|
|
|
Glat_format_1_entry = """
|
|
>
|
|
attNum: B # Attribute number of first attribute
|
|
num: B # Number of attributes in this run
|
|
"""
|
|
Glat_format_23_entry = """
|
|
>
|
|
attNum: H # Attribute number of first attribute
|
|
num: H # Number of attributes in this run
|
|
"""
|
|
|
|
Glat_format_3_octabox_metrics = """
|
|
>
|
|
subboxBitmap: H # Which subboxes exist on 4x4 grid
|
|
diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
|
|
diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
|
|
diagPosMin: B # Defines minimum positively-sloped diagonal (di)
|
|
diagPosMax: B # Defines maximum positively-sloped diagonal (da)
|
|
"""
|
|
|
|
Glat_format_3_subbox_entry = """
|
|
>
|
|
left: B # xi
|
|
right: B # xa
|
|
bottom: B # yi
|
|
top: B # ya
|
|
diagNegMin: B # Defines minimum negatively-sloped diagonal (si)
|
|
diagNegMax: B # Defines maximum negatively-sloped diagonal (sa)
|
|
diagPosMin: B # Defines minimum positively-sloped diagonal (di)
|
|
diagPosMax: B # Defines maximum positively-sloped diagonal (da)
|
|
"""
|
|
|
|
|
|
class _Object:
|
|
pass
|
|
|
|
|
|
class _Dict(dict):
|
|
pass
|
|
|
|
|
|
class table_G__l_a_t(DefaultTable.DefaultTable):
|
|
"""Graphite Glyph Attributes table
|
|
|
|
See also https://graphite.sil.org/graphite_techAbout#graphite-font-tables
|
|
"""
|
|
|
|
def __init__(self, tag=None):
|
|
DefaultTable.DefaultTable.__init__(self, tag)
|
|
self.scheme = 0
|
|
|
|
def decompile(self, data, ttFont):
|
|
sstruct.unpack2(Glat_format_0, data, self)
|
|
self.version = float(floatToFixedToStr(self.version, precisionBits=16))
|
|
if self.version <= 1.9:
|
|
decoder = partial(self.decompileAttributes12, fmt=Glat_format_1_entry)
|
|
elif self.version <= 2.9:
|
|
decoder = partial(self.decompileAttributes12, fmt=Glat_format_23_entry)
|
|
elif self.version >= 3.0:
|
|
(data, self.scheme) = grUtils.decompress(data)
|
|
sstruct.unpack2(Glat_format_3, data, self)
|
|
self.hasOctaboxes = (self.compression & 1) == 1
|
|
decoder = self.decompileAttributes3
|
|
|
|
gloc = ttFont["Gloc"]
|
|
self.attributes = {}
|
|
count = 0
|
|
for s, e in zip(gloc, gloc[1:]):
|
|
self.attributes[ttFont.getGlyphName(count)] = decoder(data[s:e])
|
|
count += 1
|
|
|
|
def decompileAttributes12(self, data, fmt):
|
|
attributes = _Dict()
|
|
while len(data) > 3:
|
|
e, data = sstruct.unpack2(fmt, data, _Object())
|
|
keys = range(e.attNum, e.attNum + e.num)
|
|
if len(data) >= 2 * e.num:
|
|
vals = struct.unpack_from((">%dh" % e.num), data)
|
|
attributes.update(zip(keys, vals))
|
|
data = data[2 * e.num :]
|
|
return attributes
|
|
|
|
def decompileAttributes3(self, data):
|
|
if self.hasOctaboxes:
|
|
o, data = sstruct.unpack2(Glat_format_3_octabox_metrics, data, _Object())
|
|
numsub = bin(o.subboxBitmap).count("1")
|
|
o.subboxes = []
|
|
for b in range(numsub):
|
|
if len(data) >= 8:
|
|
subbox, data = sstruct.unpack2(
|
|
Glat_format_3_subbox_entry, data, _Object()
|
|
)
|
|
o.subboxes.append(subbox)
|
|
attrs = self.decompileAttributes12(data, Glat_format_23_entry)
|
|
if self.hasOctaboxes:
|
|
attrs.octabox = o
|
|
return attrs
|
|
|
|
def compile(self, ttFont):
|
|
data = sstruct.pack(Glat_format_0, self)
|
|
if self.version <= 1.9:
|
|
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
|
|
elif self.version <= 2.9:
|
|
encoder = partial(self.compileAttributes12, fmt=Glat_format_1_entry)
|
|
elif self.version >= 3.0:
|
|
self.compression = (self.scheme << 27) + (1 if self.hasOctaboxes else 0)
|
|
data = sstruct.pack(Glat_format_3, self)
|
|
encoder = self.compileAttributes3
|
|
|
|
glocs = []
|
|
for n in range(len(self.attributes)):
|
|
glocs.append(len(data))
|
|
data += encoder(self.attributes[ttFont.getGlyphName(n)])
|
|
glocs.append(len(data))
|
|
ttFont["Gloc"].set(glocs)
|
|
|
|
if self.version >= 3.0:
|
|
data = grUtils.compress(self.scheme, data)
|
|
return data
|
|
|
|
def compileAttributes12(self, attrs, fmt):
|
|
data = b""
|
|
for e in grUtils.entries(attrs):
|
|
data += sstruct.pack(fmt, {"attNum": e[0], "num": e[1]}) + struct.pack(
|
|
(">%dh" % len(e[2])), *e[2]
|
|
)
|
|
return data
|
|
|
|
def compileAttributes3(self, attrs):
|
|
if self.hasOctaboxes:
|
|
o = attrs.octabox
|
|
data = sstruct.pack(Glat_format_3_octabox_metrics, o)
|
|
numsub = bin(o.subboxBitmap).count("1")
|
|
for b in range(numsub):
|
|
data += sstruct.pack(Glat_format_3_subbox_entry, o.subboxes[b])
|
|
else:
|
|
data = ""
|
|
return data + self.compileAttributes12(attrs, Glat_format_23_entry)
|
|
|
|
def toXML(self, writer, ttFont):
|
|
writer.simpletag("version", version=self.version, compressionScheme=self.scheme)
|
|
writer.newline()
|
|
for n, a in sorted(
|
|
self.attributes.items(), key=lambda x: ttFont.getGlyphID(x[0])
|
|
):
|
|
writer.begintag("glyph", name=n)
|
|
writer.newline()
|
|
if hasattr(a, "octabox"):
|
|
o = a.octabox
|
|
formatstring, names, fixes = sstruct.getformat(
|
|
Glat_format_3_octabox_metrics
|
|
)
|
|
vals = {}
|
|
for k in names:
|
|
if k == "subboxBitmap":
|
|
continue
|
|
vals[k] = "{:.3f}%".format(getattr(o, k) * 100.0 / 255)
|
|
vals["bitmap"] = "{:0X}".format(o.subboxBitmap)
|
|
writer.begintag("octaboxes", **vals)
|
|
writer.newline()
|
|
formatstring, names, fixes = sstruct.getformat(
|
|
Glat_format_3_subbox_entry
|
|
)
|
|
for s in o.subboxes:
|
|
vals = {}
|
|
for k in names:
|
|
vals[k] = "{:.3f}%".format(getattr(s, k) * 100.0 / 255)
|
|
writer.simpletag("octabox", **vals)
|
|
writer.newline()
|
|
writer.endtag("octaboxes")
|
|
writer.newline()
|
|
for k, v in sorted(a.items()):
|
|
writer.simpletag("attribute", index=k, value=v)
|
|
writer.newline()
|
|
writer.endtag("glyph")
|
|
writer.newline()
|
|
|
|
def fromXML(self, name, attrs, content, ttFont):
|
|
if name == "version":
|
|
self.version = float(safeEval(attrs["version"]))
|
|
self.scheme = int(safeEval(attrs["compressionScheme"]))
|
|
if name != "glyph":
|
|
return
|
|
if not hasattr(self, "attributes"):
|
|
self.attributes = {}
|
|
gname = attrs["name"]
|
|
attributes = _Dict()
|
|
for element in content:
|
|
if not isinstance(element, tuple):
|
|
continue
|
|
tag, attrs, subcontent = element
|
|
if tag == "attribute":
|
|
k = int(safeEval(attrs["index"]))
|
|
v = int(safeEval(attrs["value"]))
|
|
attributes[k] = v
|
|
elif tag == "octaboxes":
|
|
self.hasOctaboxes = True
|
|
o = _Object()
|
|
o.subboxBitmap = int(attrs["bitmap"], 16)
|
|
o.subboxes = []
|
|
del attrs["bitmap"]
|
|
for k, v in attrs.items():
|
|
setattr(o, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
|
|
for element in subcontent:
|
|
if not isinstance(element, tuple):
|
|
continue
|
|
(tag, attrs, subcontent) = element
|
|
so = _Object()
|
|
for k, v in attrs.items():
|
|
setattr(so, k, int(float(v[:-1]) * 255.0 / 100.0 + 0.5))
|
|
o.subboxes.append(so)
|
|
attributes.octabox = o
|
|
self.attributes[gname] = attributes
|