Merge pull request #3156 from fonttools/drop-before-and-after-round

Drop impliable oncurves either before or after rounding
This commit is contained in:
Cosimo Lupo 2023-06-08 11:57:30 +01:00 committed by GitHub
commit f5c43cec49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 48 additions and 14 deletions

View File

@ -134,6 +134,11 @@ class Vector(tuple):
"can't set attribute, the 'values' attribute has been deprecated", "can't set attribute, the 'values' attribute has been deprecated",
) )
def isclose(self, other: "Vector", **kwargs) -> bool:
"""Return True if the vector is close to another Vector."""
assert len(self) == len(other)
return all(math.isclose(a, b, **kwargs) for a, b in zip(self, other))
def _operator_rsub(a, b): def _operator_rsub(a, b):
return operator.sub(b, a) return operator.sub(b, a)

View File

@ -1,5 +1,5 @@
from array import array from array import array
from typing import Any, Dict, Optional, Tuple from typing import Any, Callable, Dict, Optional, Tuple
from fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat from fontTools.misc.fixedTools import MAX_F2DOT14, floatToFixedToFloat
from fontTools.misc.loggingTools import LogMixin from fontTools.misc.loggingTools import LogMixin
from fontTools.pens.pointPen import AbstractPointPen from fontTools.pens.pointPen import AbstractPointPen
@ -127,7 +127,13 @@ class _TTGlyphBasePen:
components.append(component) components.append(component)
return components return components
def glyph(self, componentFlags: int = 0x04, dropImpliedOnCurves=False) -> Glyph: def glyph(
self,
componentFlags: int = 0x04,
dropImpliedOnCurves: bool = False,
*,
round: Callable[[float], int] = otRound,
) -> Glyph:
""" """
Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph. Returns a :py:class:`~._g_l_y_f.Glyph` object representing the glyph.
@ -144,8 +150,6 @@ class _TTGlyphBasePen:
glyph.coordinates = GlyphCoordinates(self.points) glyph.coordinates = GlyphCoordinates(self.points)
glyph.endPtsOfContours = self.endPts glyph.endPtsOfContours = self.endPts
glyph.flags = array("B", self.types) glyph.flags = array("B", self.types)
glyph.coordinates.toInt()
self.init() self.init()
if components: if components:
@ -159,6 +163,7 @@ class _TTGlyphBasePen:
glyph.program.fromBytecode(b"") glyph.program.fromBytecode(b"")
if dropImpliedOnCurves: if dropImpliedOnCurves:
dropImpliedOnCurvePoints(glyph) dropImpliedOnCurvePoints(glyph)
glyph.coordinates.toInt(round=round)
return glyph return glyph

View File

@ -13,8 +13,9 @@ from fontTools.misc.fixedTools import (
floatToFixed as fl2fi, floatToFixed as fl2fi,
floatToFixedToStr as fl2str, floatToFixedToStr as fl2str,
strToFixedToFloat as str2fl, strToFixedToFloat as str2fl,
otRound,
) )
from fontTools.misc.roundTools import noRound, otRound
from fontTools.misc.vector import Vector
from numbers import Number from numbers import Number
from . import DefaultTable from . import DefaultTable
from . import ttProgram from . import ttProgram
@ -28,6 +29,7 @@ from fontTools.misc import xmlWriter
from fontTools.misc.filenames import userNameToFileName from fontTools.misc.filenames import userNameToFileName
from fontTools.misc.loggingTools import deprecateFunction from fontTools.misc.loggingTools import deprecateFunction
from enum import IntFlag from enum import IntFlag
from functools import partial
from types import SimpleNamespace from types import SimpleNamespace
from typing import Set from typing import Set
@ -369,7 +371,9 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
(0, bottomSideY), (0, bottomSideY),
] ]
def _getCoordinatesAndControls(self, glyphName, hMetrics, vMetrics=None): def _getCoordinatesAndControls(
self, glyphName, hMetrics, vMetrics=None, *, round=otRound
):
"""Return glyph coordinates and controls as expected by "gvar" table. """Return glyph coordinates and controls as expected by "gvar" table.
The coordinates includes four "phantom points" for the glyph metrics, The coordinates includes four "phantom points" for the glyph metrics,
@ -442,6 +446,7 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
# Add phantom points for (left, right, top, bottom) positions. # Add phantom points for (left, right, top, bottom) positions.
phantomPoints = self._getPhantomPoints(glyphName, hMetrics, vMetrics) phantomPoints = self._getPhantomPoints(glyphName, hMetrics, vMetrics)
coords.extend(phantomPoints) coords.extend(phantomPoints)
coords.toInt(round=round)
return coords, controls return coords, controls
def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None): def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None):
@ -1533,6 +1538,19 @@ class Glyph(object):
return result if result is NotImplemented else not result return result if result is NotImplemented else not result
# Vector.__round__ uses the built-in (Banker's) `round` but we want
# to use otRound below
_roundv = partial(Vector.__round__, round=otRound)
def _is_mid_point(p0: tuple, p1: tuple, p2: tuple) -> bool:
# True if p1 is in the middle of p0 and p2, either before or after rounding
p0 = Vector(p0)
p1 = Vector(p1)
p2 = Vector(p2)
return ((p0 + p2) * 0.5).isclose(p1) or _roundv(p0) + _roundv(p2) == _roundv(p1) * 2
def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]: def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
"""Drop impliable on-curve points from the (simple) glyph or glyphs. """Drop impliable on-curve points from the (simple) glyph or glyphs.
@ -1593,12 +1611,8 @@ def dropImpliedOnCurvePoints(*interpolatable_glyphs: Glyph) -> Set[int]:
nxt = i + 1 if i < last else start nxt = i + 1 if i < last else start
if (flags[prv] & flagOnCurve) or flags[prv] != flags[nxt]: if (flags[prv] & flagOnCurve) or flags[prv] != flags[nxt]:
continue continue
p0 = coords[prv] # we may drop the ith on-curve if halfway between previous/next off-curves
p1 = coords[i] if not _is_mid_point(coords[prv], coords[i], coords[nxt]):
p2 = coords[nxt]
if not math.isclose(p1[0] - p0[0], p2[0] - p1[0]) or not math.isclose(
p1[1] - p0[1], p2[1] - p1[1]
):
continue continue
may_drop.add(i) may_drop.add(i)
@ -2307,6 +2321,8 @@ class GlyphCoordinates(object):
self._a.extend(p) self._a.extend(p)
def toInt(self, *, round=otRound): def toInt(self, *, round=otRound):
if round is noRound:
return
a = self._a a = self._a
for i in range(len(a)): for i in range(len(a)):
a[i] = round(a[i]) a[i] = round(a[i])

View File

@ -1,4 +1,5 @@
from fontTools.misc.fixedTools import floatToFixedToFloat from fontTools.misc.fixedTools import floatToFixedToFloat
from fontTools.misc.roundTools import noRound
from fontTools.misc.testTools import stripVariableItemsFromTTX from fontTools.misc.testTools import stripVariableItemsFromTTX
from fontTools.misc.textTools import Tag from fontTools.misc.textTools import Tag
from fontTools import ttLib from fontTools import ttLib
@ -51,8 +52,15 @@ def fvarAxes():
def _get_coordinates(varfont, glyphname): def _get_coordinates(varfont, glyphname):
# converts GlyphCoordinates to a list of (x, y) tuples, so that pytest's # converts GlyphCoordinates to a list of (x, y) tuples, so that pytest's
# assert will give us a nicer diff # assert will give us a nicer diff
with pytest.deprecated_call(): return list(
return list(varfont["glyf"].getCoordinatesAndControls(glyphname, varfont)[0]) varfont["glyf"]._getCoordinatesAndControls(
glyphname,
varfont["hmtx"].metrics,
varfont["vmtx"].metrics,
# the tests expect float coordinates
round=noRound,
)[0]
)
class InstantiateGvarTest(object): class InstantiateGvarTest(object):