Add transformRoundFunc parameter to RoundingPens (#3426)
* Add optional transformRoundFunc to RoundingPen and RoundingPointPen * Add tests * Add doc about comparing UFO to TTF glyphs * Use floatToFixedToFloat for example with rounding
This commit is contained in:
parent
306f40a2ae
commit
7cdac78423
@ -31,6 +31,20 @@ class HashPointPen(AbstractPointPen):
|
|||||||
> # The hash values are identical, the outline has not changed.
|
> # The hash values are identical, the outline has not changed.
|
||||||
> # Compile the hinting code ...
|
> # Compile the hinting code ...
|
||||||
> pass
|
> pass
|
||||||
|
|
||||||
|
If you want to compare a glyph from a source format which supports floating point
|
||||||
|
coordinates and transformations against a glyph from a format which has restrictions
|
||||||
|
on the precision of floats, e.g. UFO vs. TTF, you must use an appropriate rounding
|
||||||
|
function to make the values comparable. For TTF fonts with composites, this
|
||||||
|
construct can be used to make the transform values conform to F2Dot14:
|
||||||
|
|
||||||
|
> ttf_hash_pen = HashPointPen(ttf_glyph_width, ttFont.getGlyphSet())
|
||||||
|
> ttf_round_pen = RoundingPointPen(ttf_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
|
||||||
|
> ufo_hash_pen = HashPointPen(ufo_glyph.width, ufo)
|
||||||
|
> ttf_glyph.drawPoints(ttf_round_pen, ttFont["glyf"])
|
||||||
|
> ufo_round_pen = RoundingPointPen(ufo_hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14))
|
||||||
|
> ufo_glyph.drawPoints(ufo_round_pen)
|
||||||
|
> assert ttf_hash_pen.hash == ufo_hash_pen.hash
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, glyphWidth=0, glyphSet=None):
|
def __init__(self, glyphWidth=0, glyphSet=None):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from fontTools.misc.roundTools import otRound
|
from fontTools.misc.roundTools import noRound, otRound
|
||||||
from fontTools.misc.transform import Transform
|
from fontTools.misc.transform import Transform
|
||||||
from fontTools.pens.filterPen import FilterPen, FilterPointPen
|
from fontTools.pens.filterPen import FilterPen, FilterPointPen
|
||||||
|
|
||||||
@ -8,7 +8,9 @@ __all__ = ["RoundingPen", "RoundingPointPen"]
|
|||||||
|
|
||||||
class RoundingPen(FilterPen):
|
class RoundingPen(FilterPen):
|
||||||
"""
|
"""
|
||||||
Filter pen that rounds point coordinates and component XY offsets to integer.
|
Filter pen that rounds point coordinates and component XY offsets to integer. For
|
||||||
|
rounding the component transform values, a separate round function can be passed to
|
||||||
|
the pen.
|
||||||
|
|
||||||
>>> from fontTools.pens.recordingPen import RecordingPen
|
>>> from fontTools.pens.recordingPen import RecordingPen
|
||||||
>>> recpen = RecordingPen()
|
>>> recpen = RecordingPen()
|
||||||
@ -28,9 +30,10 @@ class RoundingPen(FilterPen):
|
|||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, outPen, roundFunc=otRound):
|
def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
|
||||||
super().__init__(outPen)
|
super().__init__(outPen)
|
||||||
self.roundFunc = roundFunc
|
self.roundFunc = roundFunc
|
||||||
|
self.transformRoundFunc = transformRoundFunc
|
||||||
|
|
||||||
def moveTo(self, pt):
|
def moveTo(self, pt):
|
||||||
self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
|
self._outPen.moveTo((self.roundFunc(pt[0]), self.roundFunc(pt[1])))
|
||||||
@ -49,12 +52,16 @@ class RoundingPen(FilterPen):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def addComponent(self, glyphName, transformation):
|
def addComponent(self, glyphName, transformation):
|
||||||
|
xx, xy, yx, yy, dx, dy = transformation
|
||||||
self._outPen.addComponent(
|
self._outPen.addComponent(
|
||||||
glyphName,
|
glyphName,
|
||||||
Transform(
|
Transform(
|
||||||
*transformation[:4],
|
self.transformRoundFunc(xx),
|
||||||
self.roundFunc(transformation[4]),
|
self.transformRoundFunc(xy),
|
||||||
self.roundFunc(transformation[5]),
|
self.transformRoundFunc(yx),
|
||||||
|
self.transformRoundFunc(yy),
|
||||||
|
self.roundFunc(dx),
|
||||||
|
self.roundFunc(dy),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,6 +69,8 @@ class RoundingPen(FilterPen):
|
|||||||
class RoundingPointPen(FilterPointPen):
|
class RoundingPointPen(FilterPointPen):
|
||||||
"""
|
"""
|
||||||
Filter point pen that rounds point coordinates and component XY offsets to integer.
|
Filter point pen that rounds point coordinates and component XY offsets to integer.
|
||||||
|
For rounding the component scale values, a separate round function can be passed to
|
||||||
|
the pen.
|
||||||
|
|
||||||
>>> from fontTools.pens.recordingPen import RecordingPointPen
|
>>> from fontTools.pens.recordingPen import RecordingPointPen
|
||||||
>>> recpen = RecordingPointPen()
|
>>> recpen = RecordingPointPen()
|
||||||
@ -87,26 +96,35 @@ class RoundingPointPen(FilterPointPen):
|
|||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, outPen, roundFunc=otRound):
|
def __init__(self, outPen, roundFunc=otRound, transformRoundFunc=noRound):
|
||||||
super().__init__(outPen)
|
super().__init__(outPen)
|
||||||
self.roundFunc = roundFunc
|
self.roundFunc = roundFunc
|
||||||
|
self.transformRoundFunc = transformRoundFunc
|
||||||
|
|
||||||
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
|
def addPoint(
|
||||||
|
self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
|
||||||
|
):
|
||||||
self._outPen.addPoint(
|
self._outPen.addPoint(
|
||||||
(self.roundFunc(pt[0]), self.roundFunc(pt[1])),
|
(self.roundFunc(pt[0]), self.roundFunc(pt[1])),
|
||||||
segmentType=segmentType,
|
segmentType=segmentType,
|
||||||
smooth=smooth,
|
smooth=smooth,
|
||||||
name=name,
|
name=name,
|
||||||
|
identifier=identifier,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
def addComponent(self, baseGlyphName, transformation, **kwargs):
|
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
||||||
|
xx, xy, yx, yy, dx, dy = transformation
|
||||||
self._outPen.addComponent(
|
self._outPen.addComponent(
|
||||||
baseGlyphName,
|
baseGlyphName=baseGlyphName,
|
||||||
Transform(
|
transformation=Transform(
|
||||||
*transformation[:4],
|
self.transformRoundFunc(xx),
|
||||||
self.roundFunc(transformation[4]),
|
self.transformRoundFunc(xy),
|
||||||
self.roundFunc(transformation[5]),
|
self.transformRoundFunc(yx),
|
||||||
|
self.transformRoundFunc(yy),
|
||||||
|
self.roundFunc(dx),
|
||||||
|
self.roundFunc(dy),
|
||||||
),
|
),
|
||||||
|
identifier=identifier,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
69
Tests/pens/roundingPen_test.py
Normal file
69
Tests/pens/roundingPen_test.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from fontTools.misc.fixedTools import floatToFixedToFloat
|
||||||
|
from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
|
||||||
|
from fontTools.pens.roundingPen import RoundingPen, RoundingPointPen
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
|
||||||
|
tt_scale_round = partial(floatToFixedToFloat, precisionBits=14)
|
||||||
|
|
||||||
|
|
||||||
|
class RoundingPenTest(object):
|
||||||
|
def test_general(self):
|
||||||
|
recpen = RecordingPen()
|
||||||
|
roundpen = RoundingPen(recpen)
|
||||||
|
roundpen.moveTo((0.4, 0.6))
|
||||||
|
roundpen.lineTo((1.6, 2.5))
|
||||||
|
roundpen.qCurveTo((2.4, 4.6), (3.3, 5.7), (4.9, 6.1))
|
||||||
|
roundpen.curveTo((6.4, 8.6), (7.3, 9.7), (8.9, 10.1))
|
||||||
|
roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
|
||||||
|
assert recpen.value == [
|
||||||
|
("moveTo", ((0, 1),)),
|
||||||
|
("lineTo", ((2, 3),)),
|
||||||
|
("qCurveTo", ((2, 5), (3, 6), (5, 6))),
|
||||||
|
("curveTo", ((6, 9), (7, 10), (9, 10))),
|
||||||
|
("addComponent", ("a", (1.5, 0, 0, 1.5, 11, -10))),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_transform_round(self):
|
||||||
|
recpen = RecordingPen()
|
||||||
|
roundpen = RoundingPen(recpen, transformRoundFunc=tt_scale_round)
|
||||||
|
# The 0.913 is equal to 91.3% scale in a source editor
|
||||||
|
roundpen.addComponent("a", (0.9130000305, 0, 0, -1, 10.5, -10.5))
|
||||||
|
# The value should compare equal to its F2Dot14 representation
|
||||||
|
assert recpen.value == [
|
||||||
|
("addComponent", ("a", (0.91302490234375, 0, 0, -1, 11, -10))),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RoundingPointPenTest(object):
|
||||||
|
def test_general(self):
|
||||||
|
recpen = RecordingPointPen()
|
||||||
|
roundpen = RoundingPointPen(recpen)
|
||||||
|
roundpen.beginPath()
|
||||||
|
roundpen.addPoint((0.4, 0.6), "line")
|
||||||
|
roundpen.addPoint((1.6, 2.5), "line")
|
||||||
|
roundpen.addPoint((2.4, 4.6))
|
||||||
|
roundpen.addPoint((3.3, 5.7))
|
||||||
|
roundpen.addPoint((4.9, 6.1), "qcurve")
|
||||||
|
roundpen.endPath()
|
||||||
|
roundpen.addComponent("a", (1.5, 0, 0, 1.5, 10.5, -10.5))
|
||||||
|
assert recpen.value == [
|
||||||
|
("beginPath", (), {}),
|
||||||
|
("addPoint", ((0, 1), "line", False, None), {}),
|
||||||
|
("addPoint", ((2, 3), "line", False, None), {}),
|
||||||
|
("addPoint", ((2, 5), None, False, None), {}),
|
||||||
|
("addPoint", ((3, 6), None, False, None), {}),
|
||||||
|
("addPoint", ((5, 6), "qcurve", False, None), {}),
|
||||||
|
("endPath", (), {}),
|
||||||
|
("addComponent", ("a", (1.5, 0, 0, 1.5, 11, -10)), {}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_transform_round(self):
|
||||||
|
recpen = RecordingPointPen()
|
||||||
|
roundpen = RoundingPointPen(recpen, transformRoundFunc=tt_scale_round)
|
||||||
|
# The 0.913 is equal to 91.3% scale in a source editor
|
||||||
|
roundpen.addComponent("a", (0.913, 0, 0, -1, 10.5, -10.5))
|
||||||
|
# The value should compare equal to its F2Dot14 representation
|
||||||
|
assert recpen.value == [
|
||||||
|
("addComponent", ("a", (0.91302490234375, 0, 0, -1, 11, -10)), {}),
|
||||||
|
]
|
Loading…
x
Reference in New Issue
Block a user