Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mangled contours if start point is offcurve #5

Open
frankrolf opened this issue Sep 27, 2019 · 6 comments
Open

Mangled contours if start point is offcurve #5

frankrolf opened this issue Sep 27, 2019 · 6 comments

Comments

@frankrolf
Copy link
Contributor

frankrolf commented Sep 27, 2019

I noticed that an interpolation with scaleFast may mangle curves if the starting point is inconsistent across masters – one contour may start with an offcurve, the other with an oncurve. While this doesn’t cause any problems in many interpolation scenarios, scaleFast isn’t that forgiving:

Screen Shot 2019-09-26 at 12 11 10

I solved the issue by pre-processing my UFOs with this little script first, which makes sure all contours start with oncurve points:

from fontTools.ufoLib.pointPen import PointToSegmentPen


def redraw_glyph(g):
    contours = []

    for contour in g.contours:
        points = [p for p in contour.points]
        while points[0].type == 'offcurve':
            points.append(points.pop(0))
        contours.append(points)

    g.prepareUndo('redraw')
    rg = RGlyph()
    ppen = PointToSegmentPen(rg.getPen())

    for contour in contours:
        ppen.beginPath()
        for point in contour:
            if point.type == 'offcurve':
                ptype = None
            else:
                ptype = point.type
            ppen.addPoint((point.x, point.y), ptype)
        ppen.endPath()

    g.clearContours()
    g.appendGlyph(rg)
    g.performUndo()

f = CurrentFont()
glyphs_with_contours = [g for g in f if len(g.contours)]
glyphs_with_offcurve_start = []

for g in glyphs_with_contours:
    for contour in g.contours:
        first_point = contour.points[0]
        first_bPoint = contour.bPoints[0]
        first_point_coords = (first_point.x, first_point.y)
        if first_point_coords != first_bPoint.anchor:
            glyphs_with_offcurve_start.append(g)

for g in glyphs_with_offcurve_start:
    print(g.name)
    redraw_glyph(g)

(for a modernized version of this script, which processes all UFOs at once, see https://gist.github.com/frankrolf/e75980e3895df26615cc8ed1e60f9c5b )

@frankrolf frankrolf changed the title Mangled contours if startpoint is offcurve Mangled contours if start point is offcurve Sep 27, 2019
@frankrolf
Copy link
Contributor Author

I prepared an example for this. Consider the two attached UFOs, which contain the same single glyph, one starting with oncurve, the other with offcurve.
Here is a script to interpolate the two:

import os
f_all = sorted([f for f in AllFonts() if f.path.endswith('curve.ufo')], key=lambda f: f.path)
f_off, f_on = f_all
g_off = f_off['a']
g_on = f_on['a']

inter_result = g_off + (g_off - g_on) * 0.5
f_target = RFont()
f_target.glyphOrder = ['a']
ng = f_target.newGlyph('a', clear=True)
ng.appendGlyph(inter_result)
ng.width = inter_result.width
f_target.save(os.path.expanduser('~/Desktop/target.ufo'))

As expected, the interpolation result looks like this:
Screen Shot 2020-06-02 at 14 43 23

However, ScaleFast does that:
Screen Shot 2020-06-02 at 14 32 13

Might this simply be an issue of interpolating points vs interpolating bPoints?

oncurve vs offcurve start.zip

@justvanrossum
Copy link

As expected

I don't agree with this. I think it only accidentally works for some reason (perhaps the same reason as why RF doesn't show the startpoint correctly in the offcurve ufo?). These outlines are obviously not compatible, and the fact that it accidentally "works" in one environment is no reason for this to work everywhere.

(Sorry to interfere – Frank and I just had a private discussion about this.)

@justvanrossum
Copy link

justvanrossum commented Jun 2, 2020

In other words: IMO GlyphMath/FontMath (which I assume is responsible for the "working" example) is buggy for not complaining about this incompatibility.

ScaleFast is perhaps not designed to catch problems like this (the "Fast" in the name suggests as much), so it can be argued it's actually doing the right thing (the number of points is the same in both masters, it's just the point types that don't match).

@justvanrossum
Copy link

Looks like RF thinks point 2 is the starting point instead of point 0...
image

@arrowtype
Copy link

@frankrolf I have found your script to fix this very helpful, a number of times.

Would it be alright with you if I were to include it in an open, OFL font project repo? I will include credit to you where I use these functions.

@frankrolf
Copy link
Contributor Author

Umm … sure. I don’t know if a band-aid like approach like this really is the solution we’re after, but go ahead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants