Merge pull request #2912 from fonttools/cu2quMultiPen

[cu2quPen] Add Cu2QuMultiPen
This commit is contained in:
Cosimo Lupo 2022-12-13 11:00:58 +00:00 committed by GitHub
commit 164d6ead03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from fontTools.cu2qu import curve_to_quadratic
from fontTools.cu2qu import curve_to_quadratic, curves_to_quadratic
from fontTools.pens.basePen import AbstractPen, decomposeSuperBezierSegment
from fontTools.pens.reverseContourPen import ReverseContourPen
from fontTools.pens.pointPen import BasePointToSegmentPen
@ -258,3 +258,102 @@ class Cu2QuPointPen(BasePointToSegmentPen):
def addComponent(self, baseGlyphName, transformation):
assert self.currentPath is None
self.pen.addComponent(baseGlyphName, transformation)
class Cu2QuMultiPen:
"""A filter multi-pen to convert cubic bezier curves to quadratic b-splines
in a interpolation-compatible manner, using the FontTools SegmentPen protocol.
Args:
other_pens: list of SegmentPens used to draw the transformed outlines.
max_err: maximum approximation error in font units. For optimal results,
if you know the UPEM of the font, we recommend setting this to a
value equal, or close to UPEM / 1000.
reverse_direction: flip the contours' direction but keep starting point.
This pen does not follow the normal SegmentPen protocol. Instead, its
moveTo/lineTo/qCurveTo/curveTo methods take a list of tuples that are
arguments that would normally be passed to a SegmentPen, one item for
each of the pens in other_pens.
"""
def __init__(self, other_pens, max_err, reverse_direction=False):
if reverse_direction:
other_pens = [ReverseContourPen(pen, outputImpliedClosingLine=True) for pen in other_pens]
self.pens = other_pens
self.max_err = max_err
self.start_pts = None
self.current_pts = None
def _check_contour_is_open(self):
if self.current_pts is None:
raise AssertionError("moveTo is required")
def _check_contour_is_closed(self):
if self.current_pts is not None:
raise AssertionError("closePath or endPath is required")
def _add_moveTo(self):
if self.start_pts is not None:
for pt,pen in zip(self.start_pts, self.pens):
pen.moveTo(*pt)
self.start_pts = None
def moveTo(self, pts):
self._check_contour_is_closed()
self.start_pts = self.current_pts = pts
self._add_moveTo()
def lineTo(self, pts):
self._check_contour_is_open()
self._add_moveTo()
for pt,pen in zip(pts, self.pens):
pen.lineTo(*pt)
self.current_pts = pts
def qCurveTo(self, pointsList):
self._check_contour_is_open()
if len(pointsList[0]) == 1:
self.lineTo([points[0] for points in pointsList])
return
self._add_moveTo()
current_pts = []
for points,pen in zip(pointsList, self.pens):
pen.qCurveTo(*points)
current_pts.append((points[-1],))
self.current_pts = current_pts
def _curves_to_quadratic(self, pointsList):
curves = []
for current_pt,points in zip(self.current_pts, pointsList):
curves.append(current_pt + points)
quadratics = curves_to_quadratic(curves, [self.max_err] * len(curves))
pointsList = []
for quadratic in quadratics:
pointsList.append(quadratic[1:])
self.qCurveTo(pointsList)
def curveTo(self, pointsList):
self._check_contour_is_open()
self._curves_to_quadratic(pointsList)
def closePath(self):
self._check_contour_is_open()
if self.start_pts is None:
for pen in self.pens:
pen.closePath()
self.current_pts = self.start_pts = None
def endPath(self):
self._check_contour_is_open()
if self.start_pts is None:
for pen in self.pens:
pen.endPath()
self.current_pts = self.start_pts = None
def addComponent(self, glyphName, transformations):
self._check_contour_is_closed()
for trans,pen in zip(transformations, self.pens):
pen.addComponent(glyphName, trans)