Giter VIP home page Giter VIP logo

cu2qu's Introduction

cu2qu

This library provides functions which take in UFO objects (Defcon Fonts or Robofab RFonts) and converts any cubic curves to quadratic. The most useful function is probably fonts_to_quadratic.

This library is now maintained as part of https://github.com/fonttools/fonttools

cu2qu's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cu2qu's Issues

Change main converter function's return value

Currently main converter function returns a dictionary of conversion statistics. I suggest change the function signature to return nothing or something else (for example, whether we managed to convert everything within the requested error), and return statistics into a caller-provided named-argument dictionary:

stats = {}
fonts_convert_...(..., stats=stats)

The nice thing about this, is makes it easy to compare stats of converting a set of fonts compatibly, or doing separately. You can pass the same stats dict to multiple calls of the function and it will keep accumulating.

Support a conversion precision key

One of our designers recently did an outline font and needed to set it to 4000 upM. The conversion precision conversely needed to be higher. This can be done on the command line, but may be better kept inside the UFO?

Better reporting on ValueError in ufo.py

Not a bug, but a request. For debugging purposes of font data it would be helpful to see which glyph trips the ValueError in ufo.py line 47.
ValueError: Args to zip in cu2qu should have equal lengths: [('move', ((244, 489),)), ('curve', ((244, 489)..[etc]

module 'cu2qu' has no attribute 'ufo'

Hello –

I was trying to use this on my Open Sans work but it looks like the skeleton of a script provided is incorrect. @felipesanches was helping me with the script and it looks like that the module isn't as documented in the readme.

My script:

from defcon import Font
import cu2qu
import ipdb; ipdb.set_trace()


light_font = Font('source/Open Sans-Light.ufo')
bold_font = Font('source/Open Sans-Bold.ufo')
extrabold_font = Font('source/Open Sans-ExtraBold.ufo')
lightcond_font = Font('source/Open Sans-Light Condensed.ufo')
boldcond_font = Font('source/Open Sans-Bold Condensed.ufo')
extraboldcond_font = Font('source/Open Sans-ExtraBold Condensed.ufo')

cu2qu.ufo.fonts_to_quadratic([light_font, bold_font, extrabold_font, lightcond_font, boldcond_font, extraboldcond_font])

running in ipdb:

screenshot_2019-04-11_23-11-59

Consider inserting inflexion points

Cubics with implicit inflexion points can look kinda bad once converted into quads... It would probably be wise to insert them.

Doing this and keeping compatibility might be a bit of a head scratch, I reckon.

change the default approximation error?

The current DEFAULT_MAX_ERR value is set to 0.0025 of the UPEM, e.g. 2.5 units in 1000 UPEM.

Our designers at DaMa feel that this value is a bit too generous, thus we opted for a stricter value of 0.001 which produces more accurate results, of course with the greater cost in terms of file size.

I would like to hear from others who use cu2qu what they think, if you use the default value or a custom one, and whether you would agree on changing the default conversion tolerance.

ReverseContourPen drops points

The following test will fail:

class TestCu2QuReverseContourPen(unittest.TestCase):
    def _draw_test_contour(self, pen):
        pen.moveTo((848, 348))
        pen.lineTo((848, 348))
        pen.qCurveTo((848, 526), (649, 704), (449, 704))
        pen.qCurveTo((449, 704), (248, 704), (50, 526), (50, 348))
        pen.lineTo((50, 348))
        pen.qCurveTo((50, 348), (50, 171), (248, -3), (449, -3))
        pen.qCurveTo((449, -3), (649, -3), (848, 171), (848, 348))
        pen.closePath()

    def test_duplicate_points_remain(self):
        pen1 = DummyPen()
        pen2 = DummyPen()
        self._draw_test_contour(pen1)
        self._draw_test_contour(ReverseContourPen(pen2))
        self.assertEqual(
            len(pen1.commands), len(pen2.commands),
            '\n%s\nreversed to:\n%s;\nsome points dropped.' % (pen1, pen2))

cu2qu script doesn't convert sparse layers; take a designspace as input?

The cu2qu console script currently only accepts single UFOs and will convert their respective default layers to quadratic.
It would be nice if the cu2qu script also accepted a Designspace document as input. This would allow the cu2qu script to also convert the non-default "sparse" layers (or "brace layers" using Glyphs.app's jargon) that may be referenced in the designspace sources (i.e. the additional sources that reference the same UFO filename as a default source but have a layer attribute to specify a non-default layer within the same UFO, used to provide additional masters for specific glyphs).

Note that fonts_to_quadratic function is already been used by ufo2ft to convert default and sparse layers compatibly when compiling interpolatable TTFs.
Here I am talking about the cu2qu console script that converts UFOs from cubic to quadratic, which one can use if interested in obtaining a set of UFOs with quadratic curves (rather than just converting curves in memory while compiling TTFs which is already supported).

Speed up conversion

I'm guessing that handcoding cubic_bezier_at() and quadratic_bezier_at() will give a good boost.

Why have an empty module?

The geometry submodule looks redundant to me. In the general API overhaul, I think we can move them up into init directly.

Specify max error in em, not font units

Right now the max error is in font units (ie 10). This is problematic, since it means very different things with different fonts.

Change default and meaning of the parameter to be a multiple of unitsPerEm. Ie. default 0.005, to mean half-percent-of-an-EM error.

Add documentation of the algorithm

Here's the original sketch of the algorithm I wrote to James in July:

Hey,

Here are my thoughts of a new, IMO much more elegant approach:

- Convince yourself that if we cut the curve into n segments with equal
parameter lengths (ie, cut at t=i/n), then each of the new points are placed
in the middle of the control points on their two sides,

- For each of the resulting cubic's, we are going to choose a control point
that will be the one used for the quadratic; let's call this new point the
corner.   For sub-curve i, use the two existing control points to come up with
two candidates for the corner.  The candidates are chosen such that the
resulting quadratic curve will have the same tangent as the cubic at the
corresponding endpoints.  Ie, for a sub-curve with p0,p1,p2,p3, we find q0 and
q1 such that:

  (d Cubic(p0,p1,p2,p3) / dt)[t=0] = (d Quadratic(p0,q0,p3) / dt)[t=0]
  (d Cubic(p0,p1,p2,p3) / dt)[t=1] = (d Quadratic(p0,q1,p3) / dt)[t=1]

Then, we choose the corner q to be ((n-i)*q0 + i*q1) / n where i is the index
of the segment.

This ensures that the resulting quadratic spline has the same tangents as the
original curve at the endpoints.

  - After choosing corners, position the on-curve points to lie in the middle
of the neighboring corners.  Ie, don't encode them in the glyf encoding.

The only part I have not figured out is the error function.  After error
function is figured out, just increase n until feasible solution is found.

Do you think you can experiment with this?

Cheers,
behdad

to which James responded:

Okay, well I just wanted to make an observation that I thought was interesting. Not sure if you noticed but I think your comparison

(d Cubic(p0,p1,p2,p3) / dt)[t=0] = (d Quadratic(p0,q0,p3) / dt)[t=0]

comes out to

2 (q0 - p0) = 3 (p1 - p0)

or

q0 = p0 + 3/2 (p1 - p0)

with a symmetric equality using p3 & p2 for the other end of the curve. This is cool because it's basically the method I was using originally to patch the intersection method, except now we interpolate over time between q0 and q1 instead of always taking the midpoint.

and I replied:

Yes, I came up with this as a way of generalizing your method :)

tests fail

======================================================================
FAIL: test_results_unchanged (cu2qu.test.cu2qu_test.CurveToQuadraticTest)
Tests that the results of conversion haven't changed since the time
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/behdad/SSD/distillery/cu2qu/Lib/cu2qu/test/cu2qu_test.py", line 74, in test_results_unchanged
    self.assertEqual(results, expected)
AssertionError: defaultdict(<type 'int'>, {2: 1, 3: 6, 4: 17, 5: 88, 6: 244, 7: 412, 8: 231, 9: 1}) != {3: 5, 4: 31, 5: 74, 6: 228, 7: 416, 8: 242, 9: 4}

======================================================================
FAIL: test_results_unchanged_multiple (cu2qu.test.cu2qu_test.CurveToQuadraticTest)
Test that conversion results are unchanged for multiple curves.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/behdad/SSD/distillery/cu2qu/Lib/cu2qu/test/cu2qu_test.py", line 93, in test_results_unchanged_multiple
    self.assertEqual(results, expected)
AssertionError: defaultdict(<type 'int'>, {8: 51, 6: 5, 7: 44}) != {8: 62, 9: 1, 6: 3, 7: 34}

----------------------------------------------------------------------

fonts_to_quadratic([thin_font, bold_font])

Not really an issue.
I wanted to give try to your cu2qu curves converter.
Could you please give a tip how to save the converted:
fonts_to_quadratic([thin_font])
to a new UFO file. Quick test showed that original UFO is not modified by the script.

I tried to pass it to Robofab UFOwriter, but apparently fonts_to_quadratic returns a dictionary with curve stats.
Thanks,
Anton

API overhaul

@anthrotype can you list what you needed from cu2qu that wasn't in the module already? I see you implemented a pen. Lets design the API we want for cu2qu, agree on it, and then make the change.

Cheers

Possible false fail in cu2qu: "Glyphs named <x> have different number of segments"

I'm working on a variable font that has quite a few masters. I'm drawing in RoboFont with cubic-bezier UFOs, and using FontMake to build these into a variable font.

I have been working on this for quite awhile, so I'm fairly familiar with the general usage of FontMake, and I am routinely really impressed with how good a job cu2qu does in converting my cubic drawings to quadratics – even when I'm doing some fairly experimental stuff.

However, I'm butting up against a strange blocker: a few glyphs refused to convert to quadratics, but I can't find anything incompatible about them, let alone anything that supports the error message cu2qu is giving me.

INFO:fontmake.font_project:Building variable font font-betas/work-in-progress/Recursive-mono-full--w_ital_slnt-2019_07_23.ttf
INFO:ufo2ft:Pre-processing glyphs
ERROR:cu2qu.ufo:Glyphs named 'colonmonetary' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'B' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'currency' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'n' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'a' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'bitcoin' have different number of segments
Traceback (most recent call last):
  File "/Users/stephennixon/type-repos/recursive/venv/bin/fontmake", line 10, in <module>
    sys.exit(main())
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/__main__.py", line 407, in main
    project.run_from_designspace(designspace_path, **args)
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 914, in run_from_designspace
    **kwargs
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 969, in _run_from_designspace_interpolatable
    designspace, output_path=output_path, output_dir=output_dir, **kwargs
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/fontmake/font_project.py", line 411, in build_variable_font
    inplace=True,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 542, in compileVariableTTF
    inplace=inplace,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 388, in compileInterpolatableTTFsFromDS
    for source, ttf in zip(result.sources, ttfs):
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/__init__.py", line 274, in compileInterpolatableTTFs
    glyphSets = preProcessor.process()
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/ufo2ft/preProcessor.py", line 220, in process
    remember_curve_type=self._rememberCurveType and self.inplace,
  File "/Users/stephennixon/type-repos/recursive/venv/lib/python3.7/site-packages/cu2qu/ufo.py", line 283, in fonts_to_quadratic
    raise IncompatibleFontsError(glyph_errors)
cu2qu.errors.IncompatibleFontsError: fonts contains incompatible glyphs: 'B', 'a', 'bitcoin', 'colonmonetary', 'currency', 'n'

I've found quite a few ways to make sure glyphs are compatible for FontMake & cu2qu:

  • I prep UFOs so that they all have the same set of glyphs, eliminating unique glyphs or ones that are clearly not compatible.
  • I give all UFOs the same feature code.
  • I test and refine to make sure that for every glyph:
    • the oncurve and offcurve point numbers are all the same.
    • the segment types all match.
    • there are the same number of segments.

I have also confirmed that the anchors match in the /n and the /B (the order of them is different in the /a, but based on /n failing, this probably isn't what's triggering the failure).

However, I'm unable to find anything incompatible or unique about the glyphs that are failing this test. I suspect that there must be something I've missed because most glyphs I have checked are building just fine.

Still, because I have confirmed that the number of segments in these glyphs is exactly the same, I think it's likely that the wrong error message is getting triggered by something in my drawings, and this is probably a bug that could be fixed in cu2qu.

Reproducing the issue

Here's a permalink to a set of UFOs and a designspace that gives me this issue:

https://github.com/thundernixon/recursive/tree/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono

UPDATE: here is the correct permalink: https://github.com/thundernixon/recursive/tree/a149f28374805800c24168621ce5f52e8283e58c/src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09

If you clone the repo and checkout commit (a149f283), the designspace giving this issue can be built with the following command:

fontmake -m src/masters/mono/recursive_mono-varfontprep-2019_07_23-19_14_09/Recursive-mono-full--w_ital_slnt.designspace -o variable

(You may need to install FontMake if you don't already have it, but I'm guessing if you're checking this issue, you do).

Thanks for any pointers, questions, and suggestions!

drop robofab support?

I would like to get rid of the try: import ufoLib; except ImportError: import robofab stuff and only require ufoLib.

Robofab is py2 only, and is no longer actively maintained. Even though it might be slightly faster than defcon+ufoLib in the cu2qu.ufo_benchmark, I think we can't keep supporting both backends for long.

It'd be just easier if we only support ufoLib, so I can specify the latter as install_requires in the setup.py.

Alternatively, we could have robofab as an optional extras_require, so people who wishes to use cu2qu with robofab could do pip install cu2qu[robofab]. But then we will have to maintain robofab (e.g. run tests against it on the CI).

I suggest we drop it.

cu2qu-1.6.2 Error reporting is superior to latest version

While the new version was unable to notify me of some issues downgrading to 1.6.2, pinpointed the issues.

ERROR:cu2qu.ufo:Glyphs named 'Etatonos' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'Epsilontonos' have different number of segments
ERROR:cu2qu.ufo:Glyphs named 'Iotatonos' have different number of segments

Even though the component mixup between "Eta" and "H" having exactly the same contour, the error in "Etatonos" at least came up, even though is shouldn't be "different number of segments", but "different component base"

Thank you for your efforts!

Some tests don't pass on i386 architecture

As I am packaging cu2qu on Debian, I found that tests with cython compiled cu2qu cannot pass tests on i386, and it seems to be the issue related to the compiler emitting instructions for 387 instead of SSE. Should I use SSE instructions instead to build i386 builds?

Build status is listed here: https://buildd.debian.org/status/package.php?p=cu2qu

One failed excerpt of the log is shown below:

tests/pens_test.py::TestCu2QuPen::test_convert_mixed_glyph --- expected
+++ actual
@@ -12,7 +12,7 @@
 pen.qCurveTo((302, 752), (221.84375, 714.4114583333334), (163.15625, 651.8385416666666), (131, 575.625), (131, 537))
 pen.qCurveTo((131, 503.25), (159.75, 440.25), (192, 417))
 pen.qCurveTo((134.5, 403), (51.58333333333333, 319.58333333333326), (7, 210.5), (7, 158))
-pen.qCurveTo((7, 111), (45.66666666666667, 30.83333333333333), (127.00000000000001, -18), (191, -18))
-pen.qCurveTo((250.5, -18), (365.4166666666667, 26.833333333333336), (458, 110.99999999999999), (484, 170))
+pen.qCurveTo((7, 111), (45.666666666666664, 30.833333333333332), (127.00000000000001, -18), (191, -18))
+pen.qCurveTo((250.5, -18), (365.41666666666663, 26.833333333333336), (458, 110.99999999999999), (484, 170))
 pen.closePath()
 pen.addComponent('acute', (1, 0, 0, 1, 210, 250))
FAILED

Print the name of the layer where the incompatible glyphs are?

As described in issue #182, I ended up being quite confused by a failure of cu2qu to make glyphs compatible, when I knew their foreground layers were compatible. I eventually found that a support layer, used for other characters in the designspace, had empty versions of these failing glyphs, and this was blocking the conversion.

@anthrotype suggested the cu2qu might be able to print the name of the layer where the incompatible glyphs are. As a user, I expect this could really be a helpful addition! Just leaving this issue here to document the idea.

Handle cusps

If any of the curves have a cusp, we should chop at the cusp and convert sides separately.

Bounding box calculation

Using cu2qu through otf2ttf on a number of OTFs doesn't give perfect results in some cases. It appears to happen most when curves do not have points on extremum. In the attached fonts the C and o are clear examples. Advanced width appears to be fine, but the bounding box is off.

A visual diff:
cu2qu_diff

AFa724a4c6ad991aa.zip

ufo.py: hook for converting a single glyph

_fonts_to_quadratic converts the whole font.
_segments_to_quadratic converts a set of segments.

I'd be interested in something in the middle, like glyphs_to_quadratic that I can feed a list of robofab / defcon glyphs. It could be derived from _fonts_to_quadratic which needs to iterate through the objects in the font and then call glyphs_to_quadratic and return the converted glyphs.

Handle degenerate curves

If all points are colinear, we should detect that and generate curves to span the same footprint. This is much lower priority than the cusp issue though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.