robotools / fontMath

A collection of objects that implement fast font, glyph, etc. math.
MIT License
42 stars 17 forks source link

FilterRedundantPointPen and start points #276

Closed arialcrime closed 2 years ago

arialcrime commented 2 years ago

I came across a situation where depending on the start point, FilterRedundantPointPen is not able to catch redundant offcurve points.

In a glyph like the one here, the offcurve points stay in place after running the pen.

<?xml version='1.0' encoding='UTF-8'?>
<glyph name="C" format="2">
  <advance width="500"/>
  <outline>
    <contour>
      <point x="298" y="132"/>
      <point x="298" y="80"/>
      <point x="298" y="80" type="curve"/>
      <point x="600" y="80" type="line"/>
      <point x="600" y="132" type="line"/>
      <point x="298" y="132" type="line"/>
    </contour>
  </outline>
</glyph>

In the case below, FilterRedundantPointPen works as expected by getting rid of the 2 points without a type.

<?xml version='1.0' encoding='UTF-8'?>
<glyph name="C" format="2">
  <advance width="500"/>
  <outline>
    <contour>
      <point x="298" y="80" type="curve"/>
      <point x="600" y="80" type="line"/>
      <point x="600" y="132" type="line"/>
      <point x="298" y="132" type="line"/>
      <point x="298" y="132"/>
      <point x="298" y="80"/>
    </contour>
  </outline>
</glyph>
typemytype commented 2 years ago

will make a PR

from fontTools.pens.pointPen import AbstractPointPen

class FilterRedundantPointPen(AbstractPointPen):

    def __init__(self, anotherPointPen):
        self._pen = anotherPointPen
        self._points = []

    def _flushContour(self):
        # convert to a list of mutable dicts
        # keep the point order
        # keep a flag if the point will be removed
        points = [
            dict(
                pt=pt, 
                segmentType=segmentType, 
                smooth=smooth, 
                name=name, 
                identifier=identifier, 
                removed=False
                ) 
                    for pt, segmentType, smooth, name, identifier in self._points
            ]

        for index, data in enumerate(points):
            if data["segmentType"] == "curve":
                prevOnCurve = points[index - 3]
                prevOffCurve1 = points[index -2]
                prevOffCurve2 = points[index - 1]

                if prevOnCurve["pt"] == prevOffCurve1["pt"] and prevOffCurve2["pt"] == data["pt"]:
                    # the off curves are on top of the on curve point
                    # change the segmentType
                    data["segmentType"] = "line"
                    # flag the off curves to be removed
                    prevOffCurve1["removed"] = True
                    prevOffCurve2["removed"] = True

        for data in points:
            if not data["removed"]:
                self._pen.addPoint(
                    data["pt"], 
                    data["segmentType"], 
                    smooth=data["smooth"], 
                    name=data["name"], 
                    identifier=data["identifier"]
                )

    def beginPath(self, identifier=None, **kwargs):
        self._points = []
        self._pen.beginPath(identifier=identifier)

    def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs):
        self._points.append((pt, segmentType, smooth, name, identifier))

    def endPath(self):
        self._flushContour()
        self._pen.endPath()

    def addComponent(self, baseGlyph, transformation, identifier=None, **kwargs):
        self._pen.addComponent(baseGlyph, transformation, identifier)

from fontPens.printPointPen import PrintPointPen

pen = FilterRedundantPointPen(PrintPointPen())

pen.beginPath()
pen.addPoint((298, 132))
pen.addPoint((298, 80))
pen.addPoint((298, 80), segmentType="curve")
pen.addPoint((600, 80), segmentType="line")
pen.addPoint((600, 132), segmentType="line")
pen.addPoint((298, 132), segmentType="line")
pen.endPath()

pen.beginPath()
pen.addPoint((298, 80), segmentType="curve")
pen.addPoint((600, 80), segmentType="line")
pen.addPoint((600, 132), segmentType="line")
pen.addPoint((298, 132), segmentType="line")
pen.addPoint((298, 132))
pen.addPoint((298, 80))
pen.endPath()

pen.beginPath()
pen.addPoint((298, 80))
pen.addPoint((298, 80), segmentType="curve")
pen.addPoint((600, 80), segmentType="line")
pen.addPoint((600, 132), segmentType="line")
pen.addPoint((298, 132), segmentType="line")
pen.addPoint((298, 132))

pen.endPath()

pen.beginPath()
pen.addPoint((298, 132))
pen.addPoint((298, 81))
pen.addPoint((298, 80), segmentType="curve")
pen.addPoint((600, 80), segmentType="line")
pen.addPoint((600, 132), segmentType="line")
pen.addPoint((298, 132), segmentType="line")
pen.endPath()

pen.beginPath()
pen.addPoint((298, 80), segmentType="curve")
pen.addPoint((600, 80), segmentType="line")
pen.addPoint((600, 132), segmentType="line")
pen.addPoint((298, 132), segmentType="line")
pen.addPoint((298, 132))
pen.addPoint((298, 81))
pen.endPath()