robotools / fontParts

The replacement for RoboFab
MIT License
132 stars 44 forks source link

Proposal: BPointPen #726

Open typesupply opened 4 months ago

typesupply commented 4 months ago

Pens are great. They have been a part of our standard toolkit since before RoboFab existed and they shaped the contour structures inside of the original RoboFab objects. We can look at our contours as points, corresponding to the PointPen protocol, and segments, corresponding to the Pen protocol.

However, there has always been a third way to look at contours: bPoints. bPoints have an API that is very similar to the RoboFog API. I added bPoints to the RoboFab objects because, well, the API mirrors the visual representation of Bezier points in font editors so it just fits better with how I see a problem than points or segments when writing a quick script. I thought they were kind of hacky and have always been a bit embarrassed by my clinging to them, but they have stuck around. Recently I’ve noticed other people using them and that was delightful. Also, I recently added a feature to Glyph Nanny that uses the bPoint model to make the code easier to read. While I was writing that, I started to wish that there was a common abstraction for bPoints. Something like the way Pen abstracts segments and PointPen abstracts points. So...

I propose a new pen protocol for fontParts: BPointPen. Here’s my implementation. This includes functions that would be added to BaseGlyph and BaseContour, an abstract BPointPen, adapters that convert between the BPointPen and PointPen protocols, and some base pens for recording, filtering and printing.

Any thoughts?

typesupply commented 4 months ago

cc: @LettError @typemytype @benkiel @connordavenport @ryanbugden

justvanrossum commented 4 months ago

Opinionated opinions:

typesupply commented 4 months ago

Generally I don't like how BPoints can't distinguish between lines and curves that have their handles coincide with their end points. This implicit "a line is just a flat curve" is ambiguous,

This is an entirely optional way to look at the data and it is only for fontParts. I wouldn't propose this for fontTools or anywhere else because, among many reasons, it's very specific to the designer oriented API of fontParts. I agree that it's not always the best way to look at contour data. The same goes for points and segments. For example, in Glyph Nanny the various tests use points, segments or bPoints depending on the needs of the test. If the points model was best the Pen protocol wouldn't be needed. If the segments model was best the PointPen protocol wouldn't be needed. Soometimes neither of those is best. 😉

and caused me many headaches with MathGlyph in the past. I know this is intended as a feature (thanks Fontographer), but I've only experienced it as a bug.

The MathGlyph comparison isn't relevant. MathGlyph predates the modern implementation of variable fonts and the point count requirements by over a decade. When I wrote MathGlyph there were a significant number of outlines that purposely did not have off curve compatibility. These incompatibilities caused significant work delays for designers when interpolating. MathGlyph was written for the time it was written in. Besides, it now has a strict flag that turns off the curve compatibility mode and new tools are turning that on by default.

BPoints do not work for quadratics where the number of off-curve points can be != 2.

Correct. This is only for cubics, just like the bPoint objects in RoboFab/fontParts have been.

10 arguments is a bit much for a method.

Do you have any suggestions? There is a lot of data to be transferred. I considered something like this:

addBPoint(
    bcpIn={},
    anchor={},
    bcpOut={},
    **kwargs
}

where each of the dicts would have this structure:

{
    point : (x, y)
    name : None | string (optional, None by default)
    identifier : None | string (optional, None by default)
}

I ditched this idea because it would require every implementation of addBPoint to have the same dict unpacking logic and that would be a lot of duplicated code. Inconsistencies in implementations could arise from that.

benkiel commented 4 months ago

Agee that 10 is a lot, but the usual case will be the first 4; the defaults work well for 90% (completely made up percentage) of the use cases, so can be ignored by scripters. We could note this in the docs.

I think this would be a good addition to fontParts, +1