Merge pull request #2679 from fonttools/avar2

Avar2
This commit is contained in:
Behdad Esfahbod 2023-03-15 11:56:30 -06:00 committed by GitHub
commit 05872d6878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 237 additions and 53 deletions

View File

@ -5,29 +5,20 @@ from fontTools.misc.fixedTools import (
floatToFixedToStr as fl2str,
strToFixedToFloat as str2fl,
)
from fontTools.misc.textTools import bytesjoin
from fontTools.misc.textTools import bytesjoin, safeEval
from fontTools.ttLib import TTLibError
from . import DefaultTable
from . import otTables
import struct
import logging
log = logging.getLogger(__name__)
# Apple's documentation of 'avar':
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html
AVAR_HEADER_FORMAT = """
> # big endian
majorVersion: H
minorVersion: H
reserved: H
axisCount: H
"""
assert sstruct.calcsize(AVAR_HEADER_FORMAT) == 8, sstruct.calcsize(AVAR_HEADER_FORMAT)
from .otBase import BaseTTXConverter
class table__a_v_a_r(DefaultTable.DefaultTable):
class table__a_v_a_r(BaseTTXConverter):
"""Axis Variations Table
This class represents the ``avar`` table of a variable font. The object has one
@ -54,46 +45,53 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
dependencies = ["fvar"]
def __init__(self, tag=None):
DefaultTable.DefaultTable.__init__(self, tag)
super().__init__(tag)
self.segments = {}
def compile(self, ttFont):
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
header = {
"majorVersion": 1,
"minorVersion": 0,
"reserved": 0,
"axisCount": len(axisTags),
}
result = [sstruct.pack(AVAR_HEADER_FORMAT, header)]
if not hasattr(self, "table"):
self.table = otTables.avar()
if not hasattr(self.table, "Reserved"):
self.table.Reserved = 0
self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
self, "minorVersion", 0
)
self.table.AxisCount = len(axisTags)
self.table.AxisSegmentMap = []
for axis in axisTags:
mappings = sorted(self.segments[axis].items())
result.append(struct.pack(">H", len(mappings)))
for key, value in mappings:
fixedKey = fl2fi(key, 14)
fixedValue = fl2fi(value, 14)
result.append(struct.pack(">hh", fixedKey, fixedValue))
return bytesjoin(result)
mappings = self.segments[axis]
segmentMap = otTables.AxisSegmentMap()
segmentMap.PositionMapCount = len(mappings)
segmentMap.AxisValueMap = []
for key, value in sorted(mappings.items()):
valueMap = otTables.AxisValueMap()
valueMap.FromCoordinate = key
valueMap.ToCoordinate = value
segmentMap.AxisValueMap.append(valueMap)
self.table.AxisSegmentMap.append(segmentMap)
return super().compile(ttFont)
def decompile(self, data, ttFont):
super().decompile(data, ttFont)
assert self.table.Version >= 0x00010000
self.majorVersion = self.table.Version >> 16
self.minorVersion = self.table.Version & 0xFFFF
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
header = {}
headerSize = sstruct.calcsize(AVAR_HEADER_FORMAT)
header = sstruct.unpack(AVAR_HEADER_FORMAT, data[0:headerSize])
majorVersion = header["majorVersion"]
if majorVersion != 1:
raise TTLibError("unsupported 'avar' version %d" % majorVersion)
pos = headerSize
for axis in axisTags:
self.segments[axis] = {}
for axis, segmentMap in zip(axisTags, self.table.AxisSegmentMap):
segments = self.segments[axis] = {}
numPairs = struct.unpack(">H", data[pos : pos + 2])[0]
pos = pos + 2
for _ in range(numPairs):
fromValue, toValue = struct.unpack(">hh", data[pos : pos + 4])
segments[fi2fl(fromValue, 14)] = fi2fl(toValue, 14)
pos = pos + 4
for segment in segmentMap.AxisValueMap:
segments[segment.FromCoordinate] = segment.ToCoordinate
def toXML(self, writer, ttFont):
writer.simpletag(
"version",
major=getattr(self, "majorVersion", 1),
minor=getattr(self, "minorVersion", 0),
)
writer.newline()
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
for axis in axisTags:
writer.begintag("segment", axis=axis)
@ -105,9 +103,24 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
writer.newline()
writer.endtag("segment")
writer.newline()
if getattr(self, "majorVersion", 1) >= 2:
if self.table.VarIdxMap:
self.table.VarIdxMap.toXML(writer, ttFont, name="VarIdxMap")
if self.table.VarStore:
self.table.VarStore.toXML(writer, ttFont)
def fromXML(self, name, attrs, content, ttFont):
if name == "segment":
if not hasattr(self, "table"):
self.table = otTables.avar()
if not hasattr(self.table, "Reserved"):
self.table.Reserved = 0
if name == "version":
self.majorVersion = safeEval(attrs["major"])
self.minorVersion = safeEval(attrs["minor"])
self.table.Version = (getattr(self, "majorVersion", 1) << 16) | getattr(
self, "minorVersion", 0
)
elif name == "segment":
axis = attrs["axis"]
segment = self.segments[axis] = {}
for element in content:
@ -121,3 +134,5 @@ class table__a_v_a_r(DefaultTable.DefaultTable):
"duplicate entry for %s in axis '%s'", fromValue, axis
)
segment[fromValue] = toValue
else:
super().fromXML(name, attrs, content, ttFont)

View File

@ -571,12 +571,10 @@ class Version(SimpleValue):
def read(self, reader, font, tableDict):
value = reader.readLong()
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
return value
def write(self, writer, font, tableDict, value, repeatIndex=None):
value = fi2ve(value)
assert (value >> 16) == 1, "Unsupported version 0x%08x" % value
writer.writeLong(value)
@staticmethod

76
Lib/fontTools/ttLib/tables/otData.py Executable file → Normal file
View File

@ -6157,4 +6157,80 @@ otData = [
),
],
),
#
# avar
#
(
"AxisValueMap",
[
(
"F2Dot14",
"FromCoordinate",
None,
None,
"A normalized coordinate value obtained using default normalization",
),
(
"F2Dot14",
"ToCoordinate",
None,
None,
"The modified, normalized coordinate value",
),
],
),
(
"AxisSegmentMap",
[
(
"uint16",
"PositionMapCount",
None,
None,
"The number of correspondence pairs for this axis",
),
(
"AxisValueMap",
"AxisValueMap",
"PositionMapCount",
0,
"The array of axis value map records for this axis",
),
],
),
(
"avar",
[
(
"Version",
"Version",
None,
None,
"Version of the avar table- 0x00010000 or 0x00020000",
),
("uint16", "Reserved", None, None, "Permanently reserved; set to zero"),
(
"uint16",
"AxisCount",
None,
None,
'The number of variation axes for this font. This must be the same number as axisCount in the "fvar" table',
),
(
"AxisSegmentMap",
"AxisSegmentMap",
"AxisCount",
0,
'The segment maps array — one segment map for each axis, in the order of axes specified in the "fvar" table',
),
(
"LOffsetTo(DeltaSetIndexMap)",
"VarIdxMap",
None,
"Version >= 0x00020000",
"",
),
("LOffset", "VarStore", None, "Version >= 0x00020000", ""),
],
),
]

View File

@ -637,6 +637,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -1809,6 +1809,7 @@
</VORG>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -378,6 +378,7 @@
</GSUB>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -406,6 +406,7 @@
</VVAR>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -129,6 +129,7 @@
</VVAR>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -140,6 +140,7 @@
</VVAR>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -9,6 +9,7 @@
</GlyphOrder>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -8,6 +8,7 @@
</GlyphOrder>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -2668,6 +2668,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.36">
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.5" to="-0.75"/>

View File

@ -417,6 +417,7 @@
</post>
<avar>
<version major="1" minor="0"/>
<segment axis="0000">
</segment>
<segment axis="0001">

View File

@ -1,9 +1,13 @@
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTLibError
from fontTools.misc.fixedTools import floatToFixed as fl2fi
from fontTools.ttLib import TTFont, TTLibError
import fontTools.ttLib.tables.otTables as otTables
from fontTools.ttLib.tables._a_v_a_r import table__a_v_a_r
from fontTools.ttLib.tables._f_v_a_r import table__f_v_a_r, Axis
import fontTools.varLib.models as models
import fontTools.varLib.varStore as varStore
from io import BytesIO
import unittest
@ -44,13 +48,6 @@ class AxisVariationTableTest(unittest.TestCase):
avar.segments,
)
def test_decompile_unsupportedVersion(self):
avar = table__a_v_a_r()
font = self.makeFont(["wdth", "wght"])
self.assertRaises(
TTLibError, avar.decompile, deHexStr("02 01 03 06 00 00 00 00"), font
)
def test_toXML(self):
avar = table__a_v_a_r()
avar.segments["opsz"] = {-1.0: -1.0, 0.0: 0.0, 0.2999878: 0.7999878, 1.0: 1.0}
@ -58,6 +55,7 @@ class AxisVariationTableTest(unittest.TestCase):
avar.toXML(writer, self.makeFont(["opsz"]))
self.assertEqual(
[
'<version major="1" minor="0"/>',
'<segment axis="opsz">',
'<mapping from="-1.0" to="-1.0"/>',
'<mapping from="0.0" to="0.0"/>',
@ -91,7 +89,9 @@ class AxisVariationTableTest(unittest.TestCase):
axis = Axis()
axis.axisTag = tag
fvar.axes.append(axis)
return {"fvar": fvar}
font = TTFont()
font["fvar"] = fvar
return font
@staticmethod
def xml_lines(writer):
@ -99,6 +99,82 @@ class AxisVariationTableTest(unittest.TestCase):
return [line.strip() for line in content.splitlines()][1:]
class Avar2Test(unittest.TestCase):
def test(self):
axisTags = ["wght", "wdth"]
fvar = table__f_v_a_r()
for tag in axisTags:
axis = Axis()
axis.axisTag = tag
fvar.axes.append(axis)
master_locations_normalized = [
{},
{"wght": 1, "wdth": -1},
]
data = [
{},
{"wdth": -0.8},
]
model = models.VariationModel(master_locations_normalized, axisTags)
store_builder = varStore.OnlineVarStoreBuilder(axisTags)
store_builder.setModel(model)
varIdxes = {}
for axis in axisTags:
masters = [fl2fi(m.get(axis, 0), 14) for m in data]
varIdxes[axis] = store_builder.storeMasters(masters)[1]
store = store_builder.finish()
mapping = store.optimize()
varIdxes = {axis: mapping[value] for axis, value in varIdxes.items()}
del model, store_builder, mapping
varIdxMap = otTables.DeltaSetIndexMap()
varIdxMap.Format = 1
varIdxMap.mapping = []
for tag in axisTags:
varIdxMap.mapping.append(varIdxes[tag])
avar = table__a_v_a_r()
avar.segments["wght"] = {}
avar.segments["wdth"] = {-1.0: -1.0, 0.0: 0.0, 0.4: 0.5, 1.0: 1.0}
avar.majorVersion = 2
avar.table = otTables.avar()
avar.table.VarIdxMap = varIdxMap
avar.table.VarStore = store
font = TTFont()
font["fvar"] = fvar
font["avar"] = avar
b = BytesIO()
font.save(b)
b.seek(0)
font2 = TTFont(b)
assert font2["avar"].table.VarStore.VarRegionList.RegionAxisCount == 2
assert font2["avar"].table.VarStore.VarRegionList.RegionCount == 1
xml1 = BytesIO()
writer = XMLWriter(xml1)
font["avar"].toXML(writer, font)
xml2 = BytesIO()
writer = XMLWriter(xml2)
font2["avar"].toXML(writer, font2)
assert xml1.getvalue() == xml2.getvalue(), (xml1.getvalue(), xml2.getvalue())
avar = table__a_v_a_r()
xml = b"".join(xml2.getvalue().splitlines()[1:])
for name, attrs, content in parseXML(xml):
avar.fromXML(name, attrs, content, ttFont=TTFont())
assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2
assert avar.table.VarStore.VarRegionList.RegionCount == 1
if __name__ == "__main__":
import sys

View File

@ -761,6 +761,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.5" to="-0.7283"/>

View File

@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>

View File

@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>

View File

@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>

View File

@ -787,6 +787,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>

View File

@ -1139,6 +1139,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>

View File

@ -1336,6 +1336,7 @@
</STAT>
<avar>
<version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.74194"/>

View File

@ -213,6 +213,7 @@
</GPOS>
<avar>
<version major="1" minor="0"/>
<segment axis="opsz">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.01" to="-0.9"/>