From 59418656a48ec0cff9d9eb62076c5130b7efe50b Mon Sep 17 00:00:00 2001 From: Behdad Esfahbod Date: Thu, 2 Feb 2023 14:41:57 -0700 Subject: [PATCH] [VarComposite] Add VarTransform and use --- Lib/fontTools/misc/transform.py | 25 +++++++++++++ Lib/fontTools/ttLib/scaleUpem.py | 5 ++- Lib/fontTools/ttLib/tables/_g_l_y_f.py | 49 ++++++++++++++------------ Lib/fontTools/ttLib/ttGlyphSet.py | 14 +------- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/Lib/fontTools/misc/transform.py b/Lib/fontTools/misc/transform.py index ea659ea69..4accaea9d 100644 --- a/Lib/fontTools/misc/transform.py +++ b/Lib/fontTools/misc/transform.py @@ -49,6 +49,7 @@ Scale >>> """ +import math from typing import NamedTuple @@ -398,6 +399,30 @@ def Scale(x, y=None): return Transform(x, 0, 0, y, 0, 0) +class VarTransform: + + translateX: float = 0 + translateY: float = 0 + rotation: float = 0 # in degrees, counter-clockwise + scaleX: float = 1 + scaleY: float = 1 + skewX: float = 0 # in degrees, counter-clockwise + skewY: float = 0 # in degrees, counter-clockwise + tCenterX: float = 0 + tCenterY: float = 0 + + def toTransform(self): + t = Transform() + t = t.translate( + self.translateX + self.tCenterX, self.translateY + self.tCenterY + ) + t = t.rotate(math.radians(self.rotation)) + t = t.scale(self.scaleX, self.scaleY) + t = t.skew(-math.radians(self.skewX), math.radians(self.skewY)) + t = t.translate(-self.tCenterX, -self.tCenterY) + return t + + if __name__ == "__main__": import sys import doctest diff --git a/Lib/fontTools/ttLib/scaleUpem.py b/Lib/fontTools/ttLib/scaleUpem.py index 37c678abc..7018f27a7 100644 --- a/Lib/fontTools/ttLib/scaleUpem.py +++ b/Lib/fontTools/ttLib/scaleUpem.py @@ -127,9 +127,8 @@ def visit(visitor, obj, attr, glyphs): if g.isVarComposite(): for component in g.components: for attr in ("translateX", "translateY", "tCenterX", "tCenterY"): - v = getattr(component, attr, None) - if v is not None: - setattr(component, attr, visitor.scale(v)) + v = getattr(component.transform, attr) + setattr(component.transform, attr, visitor.scale(v)) continue if hasattr(g, "coordinates"): diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index 9dfae63da..c178125a3 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -4,6 +4,7 @@ from collections import namedtuple from fontTools.misc import sstruct from fontTools import ttLib from fontTools import version +from fontTools.misc.transform import VarTransform from fontTools.misc.textTools import tostr, safeEval, pad from fontTools.misc.arrayTools import calcIntBounds, pointInRect from fontTools.misc.bezierTools import calcQuadraticBounds @@ -1749,7 +1750,8 @@ VAR_COMPONENT_TRANSFORM_MAPPING = { class GlyphVarComponent(object): def __init__(self): - pass + self.location = {} + self.transform = VarTransform() def decompile(self, data, glyfTable): flags = struct.unpack(">H", data[:2])[0] @@ -1803,13 +1805,13 @@ class GlyphVarComponent(object): for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items(): data, value = read_transform_component(data, mapping_values) - setattr(self, attr_name, value) + setattr(self.transform, attr_name, value) if flags & VarComponentFlags.UNIFORM_SCALE: if flags & VarComponentFlags.HAVE_SCALE_X and not ( flags & VarComponentFlags.HAVE_SCALE_Y ): - self.scaleY = self.scaleX + self.transform.scaleY = self.transform.scaleX flags |= VarComponentFlags.HAVE_SCALE_Y flags ^= VarComponentFlags.UNIFORM_SCALE @@ -1822,7 +1824,7 @@ class GlyphVarComponent(object): flags = 0 # Calculate optimal transform component flags for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items(): - value = getattr(self, attr_name, mapping.defaultValue) + value = getattr(self.transform, attr_name) if fl2fi(value / mapping.scale, mapping.fractionalBits) != fl2fi( mapping.defaultValue / mapping.scale, mapping.fractionalBits ): @@ -1886,7 +1888,7 @@ class GlyphVarComponent(object): attrs = attrs + [("flags", hex(self.flags))] for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items(): - v = getattr(self, attr_name, mapping.defaultValue) + v = getattr(self.transform, attr_name) if v != mapping.defaultValue: attrs.append((attr_name, fl2str(v, mapping.fractionalBits))) @@ -1911,14 +1913,11 @@ class GlyphVarComponent(object): self.flags = safeEval(attrs["flags"]) for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items(): - v = ( - str2fl(safeEval(attrs[attr_name]), mapping.fractionalBits) - if attr_name in attrs - else mapping.defaultValue - ) - setattr(self, attr_name, v) + if attr_name not in attrs: + continue + v = str2fl(safeEval(attrs[attr_name]), mapping.fractionalBits) + setattr(self.transform, attr_name, v) - self.location = {} for c in content: if not isinstance(c, tuple): continue @@ -1974,28 +1973,30 @@ class GlyphVarComponent(object): VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y ): controls.append("translate") - coords.append((self.translateX, self.translateY)) + coords.append((self.transform.translateX, self.transform.translateY)) if self.flags & VarComponentFlags.HAVE_ROTATION: controls.append("rotation") - coords.append((fl2fi(self.rotation / 180, 12), 0)) + coords.append((fl2fi(self.transform.rotation / 180, 12), 0)) if self.flags & ( VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y ): controls.append("scale") - coords.append((fl2fi(self.scaleX, 10), fl2fi(self.scaleY, 10))) + coords.append( + (fl2fi(self.transform.scaleX, 10), fl2fi(self.transform.scaleY, 10)) + ) if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y): controls.append("skew") coords.append( ( - fl2fi(self.skewX / 180, 12), - fl2fi(self.skewY / 180, 12), + fl2fi(self.transform.skewX / 180, 12), + fl2fi(self.transform.skewY / 180, 12), ) ) if self.flags & ( VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y ): controls.append("tCenter") - coords.append((self.tCenterX, self.tCenterY)) + coords.append((self.transform.tCenterX, self.transform.tCenterY)) return coords, controls @@ -2012,18 +2013,20 @@ class GlyphVarComponent(object): if self.flags & ( VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y ): - self.translateX, self.translateY = coords[i] + self.transform.translateX, self.transform.translateY = coords[i] i += 1 if self.flags & VarComponentFlags.HAVE_ROTATION: - self.rotation = fi2fl(coords[i][0], 12) * 180 + self.transform.rotation = fi2fl(coords[i][0], 12) * 180 i += 1 if self.flags & ( VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y ): - self.scaleX, self.scaleY = fi2fl(coords[i][0], 10), fi2fl(coords[i][1], 10) + self.transform.scaleX, self.transform.scaleY = fi2fl( + coords[i][0], 10 + ), fi2fl(coords[i][1], 10) i += 1 if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y): - self.skewX, self.skewY = ( + self.transform.skewX, self.transform.skewY = ( fi2fl(coords[i][0], 12) * 180, fi2fl(coords[i][1], 12) * 180, ) @@ -2031,7 +2034,7 @@ class GlyphVarComponent(object): if self.flags & ( VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y ): - self.tCenterX, self.tCenterY = coords[i] + self.transform.tCenterX, self.transform.tCenterY = coords[i] i += 1 return coords[i:] diff --git a/Lib/fontTools/ttLib/ttGlyphSet.py b/Lib/fontTools/ttLib/ttGlyphSet.py index 6ae2ea29c..444548aff 100644 --- a/Lib/fontTools/ttLib/ttGlyphSet.py +++ b/Lib/fontTools/ttLib/ttGlyphSet.py @@ -1,6 +1,5 @@ """GlyphSets returned by a TTFont.""" -import math from abc import ABC, abstractmethod from collections.abc import Mapping from contextlib import contextmanager @@ -181,18 +180,7 @@ class _TTGlyphGlyf(_TTGlyph): for comp in glyph.components: - # Create a transform filled in from the component and defaults - ct = SimpleNamespace() - for key, values in VAR_COMPONENT_TRANSFORM_MAPPING.items(): - setattr(ct, key, getattr(comp, key, values.defaultValue)) - - t = Transform() - t = t.translate(ct.translateX + ct.tCenterX, ct.translateY + ct.tCenterY) - t = t.rotate(math.radians(ct.rotation)) - t = t.scale(ct.scaleX, ct.scaleY) - t = t.skew(-math.radians(ct.skewX), math.radians(ct.skewY)) - t = t.translate(-ct.tCenterX, -ct.tCenterY) - + t = comp.transform.toTransform() if isPointPen: tPen = TransformPointPen(pen, t) else: