Merge pull request #2912 from fonttools/cu2quMultiPen
[cu2quPen] Add Cu2QuMultiPen
This commit is contained in:
commit
164d6ead03
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user