bugThe FreeType Project - Bugs: bug #57701, FT_Outline_Decompose documentation...


You are not allowed to post comments on this tracker with your current authentication level.

bug #57701: FT_Outline_Decompose documentation isn't entirely correct.

Submitted by:  bungeman <bungeman>
Submitted on:  Thu 30 Jan 2020 06:31:21 PM UTC
Severity:  3 - Normal Item Group:  None
Status:  None Privacy:  Public
Assigned to:  None Open/Closed:  Open
Planned Release:  None
* Mandatory Fields

Add a New Comment (Rich Markup)

Comment Type & Canned Response:
       No canned response available


Tue 17 Mar 2020 04:52:02 PM UTC, comment #5: 

Thanks a lot for your very detailed analysis and thoughts (and sorry for not answering earlier).

What actions do you recommend?  Maybe you even have a patch in mind?

I'm currently a bit out of FreeType business, so I would be really glad for help by volunteers...


Werner LEMBERG <wl>
Project Administrator
Mon 16 Mar 2020 11:41:18 PM UTC, comment #4: 

As a note ReactOS [0] reproduces a good bit of FreeType code to walk the outline in order to avoid contours with a single point (in order to reflect GDI behavior).

[0] https://github.com/reactos/reactos/blob/2c6747aff0d33987465188f13e2ac0eb0549aedd/win32ss/gdi/ntgdi/freetype.c#L3388

bungeman <bungeman>
Wed 05 Feb 2020 10:30:18 PM UTC, comment #3: 

I suppose that OpenType CFF fonts aren't supposed to use attachment points (or at least afdko doesn't support it) [0], so speculation in the previous comment isn't really relevant. However, the fact that ps_builder_close_contour is dropping the points is still interesting.

[0] http://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html#2evi-contour-point

bungeman <bungeman>
Wed 05 Feb 2020 10:04:06 PM UTC, comment #2: 

As one more data point the current implementation of ps_builder_close_contour goes out of its way to drop empty contours (a moveto followed by a moveto), so they don't even end up in the FT_Outline to begin with.

Maybe a separate issue (and maybe not even a FreeType specific issue) but I'm not sure what the above means for point numbering. Do OpenType CFF fonts use attachment points from GPOS? If so, I'm not sure the the FT_Outline always has the right point numbers in some cases, since it seems the point numbers in CFF (if defined) would be each point as it is processed by the path construction operators. Then again, this doesn't seem to be specified anywhere and I haven't taken a hard look at what other systems do for CFF point numbering. Note that if CFF fonts do use attachment points in GPOS, I think that would affect HarfBuzz's FreeType functions, as for attachment points those functions just index into the FT_Outline. While fonts probably don't normally have multiple movetos in a row, it doesn't seem impossible that a font would specify the final point on a contour to close the contour explicitly instead of letting the last point always be implied.

bungeman <bungeman>
Fri 31 Jan 2020 09:57:31 PM UTC, comment #1: 

In relation to FT_Stroker_ParseOutline, I noticed that when I view the Stroked.ttf in ftview outline mode (key 4) that I don't get the round caps on the degenerate two point contours. This appears to be because FT_StrokerXxxTo check if the command is degenerate and bail without clearing stoker->first_point. In the end FT_Stroker_EndSubPath is only called if !stoker->first_point.

This leads to two interesting results. The first that FT_Stroker_EndSubPath is not always called for each FT_Stroker_BeginSubPath (which would not be good behavior for a generic vistor, instead the 'skipping' logic if needed would go inside FT_Stroker_EndSubPath). The second effect of checking !stoker->first_point here (even if done inside FT_Stroker_EndSubPath) is that degenerate open subpaths don't get end caps applied (closed subpaths never have end caps applied).

As a result, I would suggest that FT_Stroker_EndSubPath just start the curve if stoker->first_point. This fixes the situation for open curves. It would affect closed curves a bit since degenerate closed curves would be reported as empty areas around the degenerate point instead of as empty outlines.

I wanted to make a note of this in relation to this issue because SVG wants "zero length closed subpaths should render the same as zero length open subpath using the stroke-linecap to determine the result"[0] but also that zero length open subpaths should get caps. Because of these two requirements the result of viewing Stroked.ttf in ftview outline mode will not be the same as stroking the same logical outline in SVG which uses slightly different rules. Using the SVG rules the degenerate closed contours at the center of some of these glyphs will get end caps, under the FT_Stroker rules they do not.

Note that all of this is (at least here) a clarification, since I was trying to figure out a way to demonstrate this issue in one of the demo apps and wondered why ftview doesn't reproduce exactly what I'm seeing when stroking the 'same' outline with SVG. As an aside, what SVG really wanted was for strokes scaled through a singular matrix to be stroked as if all the SVD Σ zeros of that matrix were slightly not zero (so circles sent to degeneracy would stroke as round things, squares sent to degeneracy would stroke as square things, and rectangles with one dimension zero but the other not would stroke as empty). If they could have figured out how to spec that, I bet that actual degenerate closed contours would just not get caps, as expected, and everyone would be happy.

All of that to explain why I want degenerate multi-point contours to be able to be drawn as end caps (and why ftview doesn't draw them that way and that's fine, just different).

[0] https://www.w3.org/2016/02/18-svg-minutes.html#item01

bungeman <bungeman>
Thu 30 Jan 2020 06:31:21 PM UTC, original submission:  

Fonts want to have lone points so that such points can be hinted and used as attachment points but there is no way to mark such points as not part of the outline. Ideally TrueType would have provided a means of marking points or contours as phantom, allowing the font designer to have these points but mark them as not part of the outline. Unfortunately, this wasn't considered at the time, and wouldn't help much now for existing fonts. As a result, the convention seems to be that lone points should be left out of a reported user level outline.

Some time ago there was an issue about the behavior of FT_Outline_Decompose with respect to contours with a single point [0]. The comments there suggest that if a strong argument could be made the behavior of FT_Outline_Decompose might be modified. However, no one at the time had a strong argument so instead the documentation for FT_Outline_Decompose was updated [1].

However, this documentation is not entirely correct. Currently it states "A contour that contains a single point only is represented by a `move to' operation followed by `line to' to the same point." However, this is only true if the contour contains a single on curve point. A contour with a single off curve point will be reported as a move_to followed by a conic_to. Unfortunately, this is indistinguishable from a contour with two points the first off and the second on at the same location. As a result there is no way to filter only single point contours using FT_Outline_Decompose as it is currently written.

To test how various systems handle such degenerate contours I created Stroked.ttf [3] for a test. The result of this test is that GDI/DirectWrite drop (do not report) single point contours, but do report all other contours. I think this is the desired behavior. (CoreText/CoreGraphics does something strange which is probably buggy, so there isn't much to be learned there.)

Generally contours with zero/no area and zero/no length don't make a difference except when stroking and end caps are applied. Not surprisingly, FT_Stroker_ParseOutline (which is notably like FT_Outline_Decompose) specifically skips contours with a single point [2] for this reason. Indeed the structure of FT_Stroker_ParseOutline is almost exactly what I would prefer from FT_Outline_Decompose, skipping single point contours, calling an additional 'close' callback, and not explicitly calling line_to to close when implied. This is how the callbacks in CoreGraphics CGPathApplierFunction and DirectWrite IDWriteGeometrySink work. (In fact if FT_Outline_Decompose worked that way, FT_Stroker_ParseOutline could be implemented as a call to FT_Outline_Decompose with the appropriate FT_Outline_Funcs.)

Of course, it isn't really possible to add a 'close' to FT_Outline_Funcs at this point and dropping the implicit line_to to close is likely to cause issues. However, skipping single point contours should be fine. Any code which previously tried to skip single point contours will be fine as that code simply won't be triggered. While this could change the behavior of the plotter case mentioned in [0] it would actually be for the better, as GDI/DirectWrite wouldn't have output that contour anyway. Any code which actually needs to know about the contour points directly (like placing a dot at the points) should probably directly inspect the contents of the FT_Outline anyway (as ftgrid does). There are internal uses in calculating the bounding box and rendering antialiased, which may slightly alter metrics (but probably not rendering) but not in ways that seem incorrect.

Note that it is of course possible for users to work around this by simply making a copy of FT_Outline_Decompose and making it more like FT_Stroker_ParseOutline or (equivalently) taking FT_Stroker_ParseOutline and replacing the 'callbacks', since the members of FT_Outline are public and documented. However, this would be unfortunate since the point of FT_Outline_Decompose was to prevent the user from needing to do this, since properly iterating over the outline is quite subtle.

All of this is to make the strong argument requested in [0] that FT_Outline_Decompose should skip contours with a single point (and the documentation be updated appropriately).

[0] https://savannah.nongnu.org/bugs/?39905
[1] https://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=504f5b4a8d83d21e906aea619b25965b658839fd "Fix Savannah bug #39905 (documentation only)."
[2] https://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=0634a876d9cccff49212e514aec81e36a3a7754e "* src/base/ftstroke (FT_Stroker_ParseOutline): single points"
[3] https://skia-review.googlesource.com/c/skia/+/266945

bungeman <bungeman>


(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

Attach Files:

No files currently attached


Depends on the following items: None found

Items that depend on this one: None found


Carbon-Copy List
  • -email is unavailable- added by wl (Posted a comment)
  • -email is unavailable- added by bungeman (Submitted the item)

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only logged-in users can vote.





    No changes have been made to this item

    Back to the top

    Powered by Savane 3.9