├── test-requirements.txt ├── requirements.txt ├── .codecov.yml ├── Lib └── cu2qu │ ├── __main__.py │ ├── __init__.py │ ├── errors.py │ └── cli.py ├── .pyup.yml ├── tests ├── data │ ├── RobotoSubset-Regular.ufo │ │ ├── glyphs │ │ │ ├── space.glif │ │ │ ├── I_.glif │ │ │ ├── l.glif │ │ │ ├── L_.glif │ │ │ ├── T_.glif │ │ │ ├── Y_.glif │ │ │ ├── N_.glif │ │ │ ├── v.glif │ │ │ ├── V_.glif │ │ │ ├── x.glif │ │ │ ├── X_.glif │ │ │ ├── F_.glif │ │ │ ├── H_.glif │ │ │ ├── J_.glif │ │ │ ├── i.glif │ │ │ ├── z.glif │ │ │ ├── K_.glif │ │ │ ├── k.glif │ │ │ ├── A_.glif │ │ │ ├── Z_.glif │ │ │ ├── U_.glif │ │ │ ├── r.glif │ │ │ ├── P_.glif │ │ │ ├── E_.glif │ │ │ ├── M_.glif │ │ │ ├── t.glif │ │ │ ├── f.glif │ │ │ ├── h.glif │ │ │ ├── u.glif │ │ │ ├── n.glif │ │ │ ├── y.glif │ │ │ ├── D_.glif │ │ │ ├── w.glif │ │ │ ├── W_.glif │ │ │ ├── c.glif │ │ │ ├── o.glif │ │ │ ├── C_.glif │ │ │ ├── R_.glif │ │ │ ├── O_.glif │ │ │ ├── j.glif │ │ │ ├── e.glif │ │ │ ├── G_.glif │ │ │ ├── Q_.glif │ │ │ ├── d.glif │ │ │ ├── b.glif │ │ │ ├── q.glif │ │ │ ├── p.glif │ │ │ ├── S_.glif │ │ │ ├── s.glif │ │ │ ├── m.glif │ │ │ ├── B_.glif │ │ │ ├── a.glif │ │ │ ├── g.glif │ │ │ └── contents.plist │ │ ├── layercontents.plist │ │ ├── metainfo.plist │ │ ├── lib.plist │ │ └── fontinfo.plist │ ├── cubic │ │ ├── A_acute.glif │ │ ├── acute.glif │ │ ├── contents.plist │ │ ├── A_.glif │ │ ├── E_acute.glif │ │ └── a.glif │ ├── RobotoSubset-Bold.ufo │ │ ├── layercontents.plist │ │ ├── metainfo.plist │ │ ├── glyphs │ │ │ ├── I_.glif │ │ │ ├── l.glif │ │ │ ├── L_.glif │ │ │ ├── T_.glif │ │ │ ├── Y_.glif │ │ │ ├── N_.glif │ │ │ ├── V_.glif │ │ │ ├── v.glif │ │ │ ├── X_.glif │ │ │ ├── x.glif │ │ │ ├── H_.glif │ │ │ ├── F_.glif │ │ │ ├── J_.glif │ │ │ ├── z.glif │ │ │ ├── K_.glif │ │ │ ├── k.glif │ │ │ ├── A_.glif │ │ │ ├── Z_.glif │ │ │ ├── space.glif │ │ │ ├── i.glif │ │ │ ├── U_.glif │ │ │ ├── r.glif │ │ │ ├── P_.glif │ │ │ ├── E_.glif │ │ │ ├── M_.glif │ │ │ ├── t.glif │ │ │ ├── f.glif │ │ │ ├── h.glif │ │ │ ├── n.glif │ │ │ ├── u.glif │ │ │ ├── y.glif │ │ │ ├── D_.glif │ │ │ ├── w.glif │ │ │ ├── W_.glif │ │ │ ├── c.glif │ │ │ ├── C_.glif │ │ │ ├── o.glif │ │ │ ├── R_.glif │ │ │ ├── O_.glif │ │ │ ├── j.glif │ │ │ ├── G_.glif │ │ │ ├── e.glif │ │ │ ├── Q_.glif │ │ │ ├── d.glif │ │ │ ├── b.glif │ │ │ ├── q.glif │ │ │ ├── p.glif │ │ │ ├── s.glif │ │ │ ├── S_.glif │ │ │ ├── m.glif │ │ │ ├── B_.glif │ │ │ ├── a.glif │ │ │ ├── g.glif │ │ │ └── contents.plist │ │ ├── lib.plist │ │ └── fontinfo.plist │ └── quadratic │ │ ├── acute.glif │ │ ├── contents.plist │ │ ├── E_acute.glif │ │ ├── A_.glif │ │ └── a.glif ├── __init__.py ├── cli_test.py └── cu2qu_test.py ├── README.rst ├── pyproject.toml ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── tools ├── update_cython_shadow.py ├── ufo_benchmark.py └── benchmark.py ├── .coveragerc ├── CONTRIBUTING.md ├── tox.ini ├── .github └── workflows │ └── ci.yml └── setup.py /test-requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | pytest 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fonttools[ufo]==3.32.0 2 | defcon==0.6.0 3 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | coverage: 3 | status: 4 | project: off 5 | patch: off 6 | -------------------------------------------------------------------------------- /Lib/cu2qu/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from cu2qu.cli import main 3 | 4 | 5 | if __name__ == "__main__": 6 | sys.exit(main()) 7 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # controls the frequency of updates (undocumented beta feature) 2 | schedule: every week 3 | 4 | # do not pin dependencies unless they have explicit version specifiers 5 | pin: False 6 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/space.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/data/cubic/A_acute.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | cu2qu 2 | ===== 3 | 4 | This library provides functions which take in UFO objects (Defcon Fonts 5 | or Robofab RFonts) and converts any cubic curves to quadratic. The most 6 | useful function is probably ``fonts_to_quadratic``. 7 | 8 | This library is now maintained as part of https://github.com/fonttools/fonttools 9 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools", 4 | "wheel", 5 | "setuptools_scm", 6 | "cython", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [tool.cibuildwheel] 11 | test-requires = "pytest" 12 | before-test = "pip install -r requirements.txt" 13 | test-command = "pytest {project}/tests" 14 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/layercontents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | public.default 7 | glyphs 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/layercontents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | public.default 7 | glyphs 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 3 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 3 9 | 10 | 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include CONTRIBUTING.md 4 | include requirements.txt 5 | include test-requirements.txt 6 | include tox.ini 7 | include .coveragerc 8 | recursive-include tests *.py 9 | recursive-include tests/data *.json 10 | recursive-include tests/data */*.glif 11 | recursive-include tests/data */*.plist */*/*.glif */*/*.plist 12 | recursive-include tools *.py 13 | -------------------------------------------------------------------------------- /tests/data/cubic/acute.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/data/quadratic/acute.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/I_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/l.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/I_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/l.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/data/quadratic/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A 6 | A_.glif 7 | Eacute 8 | E_acute.glif 9 | a 10 | a.glif 11 | acute 12 | acute.glif 13 | 14 | 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [sdist] 5 | formats = zip 6 | 7 | [aliases] 8 | test = pytest 9 | 10 | [metadata] 11 | license_file = LICENSE 12 | 13 | [tool:pytest] 14 | minversion = 3.0 15 | testpaths = 16 | tests 17 | python_files = 18 | *_test.py 19 | python_classes = 20 | *Test 21 | addopts = 22 | -s 23 | -v 24 | -r a 25 | --doctest-modules 26 | --doctest-ignore-import-errors 27 | filterwarnings: 28 | ignore:.*bytes:DeprecationWarning:fs.base 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled and optimized files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | 7 | # cython generated C/HTML files 8 | Lib/cu2qu/*.c 9 | Lib/cu2qu/*.html 10 | 11 | # Packaging 12 | *.egg-info 13 | *.eggs 14 | build 15 | dist 16 | 17 | # Unit test and coverage files 18 | .cache 19 | .coverage 20 | .coverage.* 21 | .tox 22 | htmlcov 23 | .pytest_cache/ 24 | 25 | # OS X Finder 26 | .DS_Store 27 | 28 | # auto-generated version file 29 | Lib/cu2qu/_version.py 30 | -------------------------------------------------------------------------------- /tests/data/cubic/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A 6 | A_.glif 7 | Aacute 8 | A_acute.glif 9 | Eacute 10 | E_acute.glif 11 | a 12 | a.glif 13 | acute 14 | acute.glif 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fontTools.ufoLib.glifLib import GlyphSet 3 | import pkg_resources 4 | 5 | DATADIR = os.path.join(os.path.dirname(__file__), 'data') 6 | CUBIC_GLYPHS = GlyphSet(os.path.join(DATADIR, 'cubic')) 7 | QUAD_GLYPHS = GlyphSet(os.path.join(DATADIR, 'quadratic')) 8 | 9 | import unittest 10 | # Python 3 renamed 'assertRaisesRegexp' to 'assertRaisesRegex', and fires 11 | # deprecation warnings if a program uses the old name. 12 | if not hasattr(unittest.TestCase, 'assertRaisesRegex'): 13 | unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp 14 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/L_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/T_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/L_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/T_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/Y_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/Y_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/N_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/N_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/V_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/v.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/v.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/V_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/X_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/x.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/x.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/X_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/H_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/F_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/F_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/H_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/J_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/J_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tools/update_cython_shadow.py: -------------------------------------------------------------------------------- 1 | """ Update the embedded Lib/cu2qu/cython.py module with the contents of 2 | the latest cython repository. 3 | 4 | Usage: 5 | $ python tools/update_cython_shadow.py 0.28.5 6 | """ 7 | 8 | import requests 9 | import sys 10 | 11 | 12 | header = b'''\ 13 | """ This module is copied verbatim from the "Cython.Shadow" module: 14 | https://github.com/cython/cython/blob/master/Cython/Shadow.py 15 | 16 | Cython is licensed under the Apache 2.0 Software License. 17 | """ 18 | ''' 19 | 20 | try: 21 | version = sys.argv[1] 22 | except IndexError: 23 | version = "master" 24 | 25 | CYTHON_SHADOW_URL = ( 26 | "https://raw.githubusercontent.com/cython/cython/%s/Cython/Shadow.py" 27 | ) % version 28 | 29 | r = requests.get(CYTHON_SHADOW_URL, allow_redirects=True) 30 | with open("Lib/cu2qu/cython.py", "wb") as f: 31 | f.write(header) 32 | f.write(r.content) 33 | -------------------------------------------------------------------------------- /Lib/cu2qu/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from __future__ import print_function, division, absolute_import 17 | 18 | try: 19 | from ._version import version as __version__ 20 | except ImportError: 21 | __version__ = "0.0.0+unknown" 22 | 23 | from .cu2qu import * 24 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/i.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/z.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/K_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/k.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/z.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/A_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/Z_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/K_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/k.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/A_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/Z_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/space.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | com.typemytype.robofont.guides 10 | 11 | 12 | angle 13 | 0 14 | isGlobal 15 | 16 | magnetic 17 | 5 18 | x 19 | 0 20 | y 21 | 901 22 | 23 | 24 | angle 25 | 0 26 | isGlobal 27 | 28 | magnetic 29 | 5 30 | x 31 | 0 32 | y 33 | 555 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/i.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/U_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/U_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/r.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/r.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/P_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/P_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/E_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/E_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | # measure 'branch' coverage in addition to 'statement' coverage 3 | # See: http://coverage.readthedocs.org/en/coverage-4.0.3/branch.html#branch 4 | branch = True 5 | 6 | # list of directories or packages to measure 7 | source = cu2qu 8 | 9 | # this is simply vendored, no need to include in coverage report 10 | omit = 11 | */cu2qu/cython.py 12 | 13 | # these are treated as equivalent when combining data 14 | [paths] 15 | source = 16 | Lib/cu2qu 17 | .tox/*/lib/python*/site-packages/cu2qu 18 | .tox/pypy*/site-packages/cu2qu 19 | 20 | [report] 21 | # Regexes for lines to exclude from consideration 22 | exclude_lines = 23 | # keywords to use in inline comments to skip coverage 24 | pragma: no cover 25 | 26 | # don't complain if tests don't hit defensive assertion code 27 | raise AssertionError 28 | raise NotImplementedError 29 | 30 | # don't complain if non-runnable code isn't run 31 | if 0: 32 | if __name__ == .__main__.: 33 | 34 | # ignore source code that can’t be found 35 | ignore_errors = True 36 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/M_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/M_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/t.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/t.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/f.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/f.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/h.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/h.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/n.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/u.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/u.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/n.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/y.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/y.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/D_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/D_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/w.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/W_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/w.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/W_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/c.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/c.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tools/ufo_benchmark.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from __future__ import print_function, division, absolute_import 17 | 18 | import os 19 | import random 20 | 21 | from benchmark import run_benchmark 22 | 23 | MAX_ERR_EM = 0.002 24 | DATADIR = os.path.join( 25 | os.path.dirname(__file__), os.path.pardir, 'tests', 'data') 26 | 27 | 28 | def setup_fonts_to_quadratic_defcon(): 29 | from defcon import Font 30 | return [[Font(os.path.join(DATADIR, 'RobotoSubset-Regular.ufo'))], 31 | MAX_ERR_EM] 32 | 33 | 34 | def main(): 35 | run_benchmark( 36 | 'ufo_benchmark', 'cu2qu.ufo', 'fonts_to_quadratic', 37 | setup_suffix='defcon', repeat=10) 38 | 39 | 40 | if __name__ == '__main__': 41 | random.seed(1) 42 | main() 43 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/C_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/o.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/o.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/C_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/R_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/R_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/O_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/j.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/O_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/j.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print 2 | at the end). 3 | 4 | ### Before you contribute 5 | Before we can use your code, you must sign the 6 | [Google Individual Contributor License 7 | Agreement](https://cla.developers.google.com/about/google-individual) 8 | (CLA), which you can do online. The CLA is necessary mainly because you own the 9 | copyright to your changes, even after your contribution becomes part of our 10 | codebase, so we need your permission to use and distribute your code. We also 11 | need to be sure of various other things—for instance that you'll tell us if you 12 | know that your code infringes on other people's patents. You don't have to sign 13 | the CLA until after you've submitted your code for review and a member has 14 | approved it, but you must do it before we can put your code into our codebase. 15 | Before you start working on a larger contribution, you should get in touch with 16 | us first through the issue tracker with your idea so that we can help out and 17 | possibly guide you. Coordinating up front makes it much easier to avoid 18 | frustration later on. 19 | 20 | ### Code reviews 21 | All submissions, including submissions by project members, require review. We 22 | use Github pull requests for this purpose. 23 | 24 | ### The small print 25 | Contributions made by corporations are covered by a different agreement than 26 | the one above, the 27 | [Software Grant and Corporate Contributor License 28 | Agreement](https://cla.developers.google.com/about/google-corporate). 29 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/e.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/G_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/G_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/e.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/Q_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/Q_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/d.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/b.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/q.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/p.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/d.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/b.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/q.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/p.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/lib.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | public.glyphOrder 6 | 7 | space 8 | A 9 | B 10 | C 11 | D 12 | E 13 | F 14 | G 15 | H 16 | I 17 | J 18 | K 19 | L 20 | M 21 | N 22 | O 23 | P 24 | Q 25 | R 26 | S 27 | T 28 | U 29 | V 30 | W 31 | X 32 | Y 33 | Z 34 | a 35 | b 36 | c 37 | d 38 | e 39 | f 40 | g 41 | h 42 | i 43 | j 44 | k 45 | l 46 | m 47 | n 48 | o 49 | p 50 | q 51 | r 52 | s 53 | t 54 | u 55 | v 56 | w 57 | x 58 | y 59 | z 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/lib.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | public.glyphOrder 6 | 7 | space 8 | A 9 | B 10 | C 11 | D 12 | E 13 | F 14 | G 15 | H 16 | I 17 | J 18 | K 19 | L 20 | M 21 | N 22 | O 23 | P 24 | Q 25 | R 26 | S 27 | T 28 | U 29 | V 30 | W 31 | X 32 | Y 33 | Z 34 | a 35 | b 36 | c 37 | d 38 | e 39 | f 40 | g 41 | h 42 | i 43 | j 44 | k 45 | l 46 | m 47 | n 48 | o 49 | p 50 | q 51 | r 52 | s 53 | t 54 | u 55 | v 56 | w 57 | x 58 | y 59 | z 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/s.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/S_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/s.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/S_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/m.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/m.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/B_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/B_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/data/cubic/A_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/a.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/a.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/data/cubic/E_acute.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/g.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/g.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Lib/cu2qu/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, absolute_import, division 2 | 3 | 4 | class Error(Exception): 5 | """Base Cu2Qu exception class for all other errors.""" 6 | 7 | 8 | class ApproxNotFoundError(Error): 9 | def __init__(self, curve): 10 | message = "no approximation found: %s" % curve 11 | super(Error, self).__init__(message) 12 | self.curve = curve 13 | 14 | 15 | class UnequalZipLengthsError(Error): 16 | pass 17 | 18 | 19 | class IncompatibleGlyphsError(Error): 20 | def __init__(self, glyphs): 21 | assert len(glyphs) > 1 22 | self.glyphs = glyphs 23 | names = set(repr(g.name) for g in glyphs) 24 | if len(names) > 1: 25 | self.combined_name = "{%s}" % ", ".join(sorted(names)) 26 | else: 27 | self.combined_name = names.pop() 28 | 29 | def __repr__(self): 30 | return "<%s %s>" % (type(self).__name__, self.combined_name) 31 | 32 | 33 | class IncompatibleSegmentNumberError(IncompatibleGlyphsError): 34 | def __str__(self): 35 | return "Glyphs named %s have different number of segments" % ( 36 | self.combined_name 37 | ) 38 | 39 | 40 | class IncompatibleSegmentTypesError(IncompatibleGlyphsError): 41 | def __init__(self, glyphs, segments): 42 | IncompatibleGlyphsError.__init__(self, glyphs) 43 | self.segments = segments 44 | 45 | def __str__(self): 46 | lines = [] 47 | ndigits = len(str(max(self.segments))) 48 | for i, tags in sorted(self.segments.items()): 49 | lines.append( 50 | "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags)) 51 | ) 52 | return "Glyphs named %s have incompatible segment types:\n %s" % ( 53 | self.combined_name, 54 | "\n ".join(lines), 55 | ) 56 | 57 | 58 | class IncompatibleFontsError(Error): 59 | def __init__(self, glyph_errors): 60 | self.glyph_errors = glyph_errors 61 | 62 | def __str__(self): 63 | return "fonts contains incompatible glyphs: %s" % ( 64 | ", ".join(repr(g) for g in sorted(self.glyph_errors.keys())) 65 | ) 66 | -------------------------------------------------------------------------------- /tools/benchmark.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from __future__ import print_function, division, absolute_import 17 | 18 | import random 19 | import timeit 20 | 21 | MAX_ERR = 5 22 | 23 | SETUP_CODE = ''' 24 | from %(module)s import %(function)s 25 | from %(benchmark_module)s import %(setup_function)s 26 | args = %(setup_function)s() 27 | ''' 28 | 29 | 30 | def generate_curve(): 31 | return [ 32 | tuple(float(random.randint(0, 2048)) for coord in range(2)) 33 | for point in range(4)] 34 | 35 | 36 | def setup_curve_to_quadratic(): 37 | return generate_curve(), MAX_ERR 38 | 39 | 40 | def setup_curves_to_quadratic(): 41 | num_curves = 3 42 | return ( 43 | [generate_curve() for curve in range(num_curves)], 44 | [MAX_ERR] * num_curves) 45 | 46 | 47 | def run_benchmark( 48 | benchmark_module, module, function, setup_suffix='', repeat=1000): 49 | setup_func = 'setup_' + function 50 | if setup_suffix: 51 | print('%s with %s:' % (function, setup_suffix), end='') 52 | setup_func += '_' + setup_suffix 53 | else: 54 | print('%s:' % function, end='') 55 | results = timeit.repeat( 56 | '%s(*args)' % function, 57 | setup=(SETUP_CODE % { 58 | 'benchmark_module': benchmark_module, 'setup_function': setup_func, 59 | 'module': module, 'function': function}), 60 | repeat=repeat, number=1) 61 | print('\tavg=%dus' % (sum(results) / len(results) * 1000000.), 62 | '\tmin=%dus' % (min(results) * 1000000.)) 63 | 64 | 65 | def main(): 66 | run_benchmark('benchmark', 'cu2qu', 'curve_to_quadratic') 67 | run_benchmark('benchmark', 'cu2qu', 'curves_to_quadratic') 68 | 69 | 70 | if __name__ == '__main__': 71 | random.seed(1) 72 | main() 73 | -------------------------------------------------------------------------------- /tests/data/quadratic/E_acute.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /tests/data/cubic/a.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{27,37}-{cy,nocy}, htmlcov 3 | package_name = cu2qu 4 | ; we skip tox's own sdist generation as we need to pass different environment 5 | ; variables for testing buiding with and without cython 6 | skipsdist = true 7 | 8 | [testenv] 9 | setenv = 10 | nocy: CU2QU_WITH_CYTHON=0 11 | cy: CU2QU_WITH_CYTHON=1 12 | cy: CYTHON_TRACE=1 13 | cy: CYTHON_ANNOTATE=1 14 | ; download the latest pip, setuptools and wheel when creating the venv 15 | download = true 16 | deps = 17 | -rtest-requirements.txt 18 | -rrequirements.txt 19 | cy: cython 20 | changedir = {toxinidir} 21 | commands = 22 | # create source distribution in a temp dir 23 | python setup.py --quiet sdist --dist-dir {envtmpdir} 24 | 25 | # install from sdist 26 | python -m pip install --no-build-isolation --ignore-installed --pre --no-deps --no-cache-dir --find-links {envtmpdir} {[tox]package_name} 27 | 28 | # ensure we are running the requested cu2qu version (compiled vs interpreted) 29 | nocy: python -c "import sys, cu2qu.cu2qu; cu2qu.cu2qu.COMPILED and sys.exit(1)" 30 | cy: python -c "import sys, cu2qu.cu2qu; cu2qu.cu2qu.COMPILED or sys.exit(1)" 31 | 32 | # run tests with code coverage enabled 33 | coverage run --parallel-mode -m pytest {posargs} 34 | 35 | [testenv:htmlcov] 36 | deps = 37 | coverage 38 | changedir = {toxinidir} 39 | commands = 40 | coverage combine 41 | coverage report 42 | coverage html 43 | 44 | [testenv:codecov] 45 | passenv = * 46 | deps = 47 | coverage 48 | codecov 49 | ignore_outcome = true 50 | changedir = {toxinidir} 51 | commands = 52 | coverage combine 53 | codecov --env TRAVIS_PYTHON_VERSION 54 | 55 | [testenv:update-cython] 56 | deps = requests 57 | changedir = {toxinidir} 58 | commands = 59 | python tools/update_cython_shadow.py {posargs} 60 | 61 | [testenv:sdist] 62 | deps = 63 | setuptools 64 | cython 65 | changedir = {toxinidir} 66 | commands = 67 | python -c 'import shutil; shutil.rmtree("dist", ignore_errors=True)' 68 | python setup.py --with-cython sdist --dist-dir dist 69 | 70 | [testenv:pure-wheel] 71 | deps = 72 | {[testenv:sdist]deps} 73 | pip 74 | wheel 75 | setenv = CU2QU_WITH_CYTHON=0 76 | changedir = {toxinidir} 77 | commands = 78 | {[testenv:sdist]commands} 79 | pip wheel --pre --no-deps --no-cache-dir --wheel-dir dist --find-links dist \ 80 | --no-binary {[tox]package_name} {[tox]package_name} 81 | 82 | [testenv:native-wheel] 83 | deps = {[testenv:pure-wheel]deps} 84 | setenv = CU2QU_WITH_CYTHON=1 85 | changedir = {toxinidir} 86 | commands = {[testenv:pure-wheel]commands} 87 | 88 | ; we only upload the source distribution to PyPI (for now) 89 | [testenv:pypi] 90 | deps = 91 | {[testenv:sdist]deps} 92 | twine 93 | passenv = TWINE_USERNAME TWINE_PASSWORD 94 | changedir = {toxinidir} 95 | commands = 96 | {[testenv:sdist]commands} 97 | twine upload dist/*.zip 98 | -------------------------------------------------------------------------------- /tests/cli_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | 3 | import defcon 4 | 5 | from . import DATADIR 6 | import pytest 7 | import py 8 | 9 | from cu2qu.ufo import CURVE_TYPE_LIB_KEY 10 | from cu2qu.cli import main 11 | 12 | 13 | TEST_UFOS = [ 14 | py.path.local(DATADIR).join("RobotoSubset-Regular.ufo"), 15 | py.path.local(DATADIR).join("RobotoSubset-Bold.ufo"), 16 | ] 17 | 18 | 19 | @pytest.fixture 20 | def test_paths(tmpdir): 21 | result = [] 22 | for path in TEST_UFOS: 23 | new_path = tmpdir / path.basename 24 | path.copy(new_path) 25 | result.append(new_path) 26 | return result 27 | 28 | 29 | class MainTest(object): 30 | 31 | @staticmethod 32 | def run_main(*args): 33 | main([str(p) for p in args if p]) 34 | 35 | def test_single_input_no_output(self, test_paths): 36 | ufo_path = test_paths[0] 37 | 38 | self.run_main(ufo_path) 39 | 40 | font = defcon.Font(str(ufo_path)) 41 | assert font.lib[CURVE_TYPE_LIB_KEY] == "quadratic" 42 | 43 | def test_single_input_output_file(self, tmpdir): 44 | input_path = TEST_UFOS[0] 45 | output_path = tmpdir / input_path.basename 46 | self.run_main('-o', output_path, input_path) 47 | 48 | assert output_path.check(dir=1) 49 | 50 | def test_multiple_inputs_output_dir(self, tmpdir): 51 | output_dir = tmpdir / "output_dir" 52 | self.run_main('-d', output_dir, *TEST_UFOS) 53 | 54 | assert output_dir.check(dir=1) 55 | outputs = set(p.basename for p in output_dir.listdir()) 56 | assert "RobotoSubset-Regular.ufo" in outputs 57 | assert "RobotoSubset-Bold.ufo" in outputs 58 | 59 | def test_interpolatable_inplace(self, test_paths): 60 | self.run_main('-i', *test_paths) 61 | self.run_main('-i', *test_paths) # idempotent 62 | 63 | @pytest.mark.parametrize( 64 | "mode", ["", "-i"], ids=["normal", "interpolatable"]) 65 | def test_copytree(self, mode, tmpdir): 66 | output_dir = tmpdir / "output_dir" 67 | self.run_main(mode, '-d', output_dir, *TEST_UFOS) 68 | 69 | output_dir_2 = tmpdir / "output_dir_2" 70 | # no conversion when curves are already quadratic, just copy 71 | self.run_main(mode, '-d', output_dir_2, *output_dir.listdir()) 72 | # running again overwrites existing with the copy 73 | self.run_main(mode, '-d', output_dir_2, *output_dir.listdir()) 74 | 75 | def test_multiprocessing(self, tmpdir, test_paths): 76 | self.run_main(*(test_paths + ["-j"])) 77 | 78 | def test_keep_direction(self, test_paths): 79 | self.run_main('--keep-direction', *test_paths) 80 | 81 | def test_conversion_error(self, test_paths): 82 | self.run_main('--conversion-error', 0.002, *test_paths) 83 | 84 | def test_conversion_error_short(self, test_paths): 85 | self.run_main('-e', 0.003, test_paths[0]) 86 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A 6 | A_.glif 7 | B 8 | B_.glif 9 | C 10 | C_.glif 11 | D 12 | D_.glif 13 | E 14 | E_.glif 15 | F 16 | F_.glif 17 | G 18 | G_.glif 19 | H 20 | H_.glif 21 | I 22 | I_.glif 23 | J 24 | J_.glif 25 | K 26 | K_.glif 27 | L 28 | L_.glif 29 | M 30 | M_.glif 31 | N 32 | N_.glif 33 | O 34 | O_.glif 35 | P 36 | P_.glif 37 | Q 38 | Q_.glif 39 | R 40 | R_.glif 41 | S 42 | S_.glif 43 | T 44 | T_.glif 45 | U 46 | U_.glif 47 | V 48 | V_.glif 49 | W 50 | W_.glif 51 | X 52 | X_.glif 53 | Y 54 | Y_.glif 55 | Z 56 | Z_.glif 57 | a 58 | a.glif 59 | b 60 | b.glif 61 | c 62 | c.glif 63 | d 64 | d.glif 65 | e 66 | e.glif 67 | f 68 | f.glif 69 | g 70 | g.glif 71 | h 72 | h.glif 73 | i 74 | i.glif 75 | j 76 | j.glif 77 | k 78 | k.glif 79 | l 80 | l.glif 81 | m 82 | m.glif 83 | n 84 | n.glif 85 | o 86 | o.glif 87 | p 88 | p.glif 89 | q 90 | q.glif 91 | r 92 | r.glif 93 | s 94 | s.glif 95 | space 96 | space.glif 97 | t 98 | t.glif 99 | u 100 | u.glif 101 | v 102 | v.glif 103 | w 104 | w.glif 105 | x 106 | x.glif 107 | y 108 | y.glif 109 | z 110 | z.glif 111 | 112 | 113 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A 6 | A_.glif 7 | B 8 | B_.glif 9 | C 10 | C_.glif 11 | D 12 | D_.glif 13 | E 14 | E_.glif 15 | F 16 | F_.glif 17 | G 18 | G_.glif 19 | H 20 | H_.glif 21 | I 22 | I_.glif 23 | J 24 | J_.glif 25 | K 26 | K_.glif 27 | L 28 | L_.glif 29 | M 30 | M_.glif 31 | N 32 | N_.glif 33 | O 34 | O_.glif 35 | P 36 | P_.glif 37 | Q 38 | Q_.glif 39 | R 40 | R_.glif 41 | S 42 | S_.glif 43 | T 44 | T_.glif 45 | U 46 | U_.glif 47 | V 48 | V_.glif 49 | W 50 | W_.glif 51 | X 52 | X_.glif 53 | Y 54 | Y_.glif 55 | Z 56 | Z_.glif 57 | a 58 | a.glif 59 | b 60 | b.glif 61 | c 62 | c.glif 63 | d 64 | d.glif 65 | e 66 | e.glif 67 | f 68 | f.glif 69 | g 70 | g.glif 71 | h 72 | h.glif 73 | i 74 | i.glif 75 | j 76 | j.glif 77 | k 78 | k.glif 79 | l 80 | l.glif 81 | m 82 | m.glif 83 | n 84 | n.glif 85 | o 86 | o.glif 87 | p 88 | p.glif 89 | q 90 | q.glif 91 | r 92 | r.glif 93 | s 94 | s.glif 95 | space 96 | space.glif 97 | t 98 | t.glif 99 | u 100 | u.glif 101 | v 102 | v.glif 103 | w 104 | w.glif 105 | x 106 | x.glif 107 | y 108 | y.glif 109 | z 110 | z.glif 111 | 112 | 113 | -------------------------------------------------------------------------------- /tests/data/quadratic/A_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/data/quadratic/a.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Wheels 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | tags: ["v*.*.*"] 7 | pull_request: 8 | branches: [main] 9 | 10 | 11 | jobs: 12 | build-binaries: 13 | name: Build wheel ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ "ubuntu-latest", "windows-latest", "macos-latest" ] 19 | env: 20 | CIBW_ARCHS: auto64 21 | # macOS: Explicitly list ARM variants here for cross-compilation. 22 | CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" 23 | CIBW_SKIP: pp* 24 | CIBW_ENVIRONMENT: CU2QU_WITH_CYTHON="true" 25 | steps: 26 | - uses: actions/checkout@v2 27 | with: 28 | submodules: recursive 29 | fetch-depth: 0 30 | - uses: actions/setup-python@v2 31 | with: 32 | python-version: "3.x" 33 | - name: Install cibuildwheel 34 | run: python -m pip install -U pip cibuildwheel 35 | - name: Build wheels 36 | run: python -m cibuildwheel --output-dir dist 37 | - uses: actions/upload-artifact@v2 38 | with: 39 | name: wheels-${{ matrix.os }} 40 | path: dist/*.whl 41 | 42 | build-pure: 43 | name: Build sdist and pure wheel 44 | runs-on: "ubuntu-latest" 45 | steps: 46 | - uses: actions/checkout@v2 47 | with: 48 | submodules: recursive 49 | fetch-depth: 0 50 | # Use PyPy just so we can test the package on it. 51 | - uses: actions/setup-python@v2 52 | with: 53 | python-version: 'pypy-3.9' 54 | - name: Install packaging tooling 55 | run: python -m pip install -U pip setuptools wheel 56 | - name: Build sdist and pure wheel 57 | run: python setup.py sdist bdist_wheel 58 | - name: Prepare for testing 59 | run: python -m pip install pytest -r requirements.txt dist/*.whl 60 | - name: Test 61 | run: pytest 62 | - uses: actions/upload-artifact@v2 63 | with: 64 | name: wheels-pure 65 | path: | 66 | dist/*.whl 67 | dist/*.zip 68 | 69 | deploy: 70 | # only run if the commit is tagged... 71 | if: startsWith(github.ref, 'refs/tags/v') 72 | # ... and all build jobs completed successfully 73 | needs: [build-binaries, build-pure] 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v2 77 | with: 78 | submodules: recursive 79 | - name: Set up Python 80 | uses: actions/setup-python@v2 81 | with: 82 | python-version: "3.x" 83 | - name: Install dependencies 84 | run: | 85 | python -m pip install --upgrade pip 86 | pip install --upgrade setuptools wheel twine cython 87 | - name: Download artifacts from build jobs 88 | uses: actions/download-artifact@v2 89 | with: 90 | path: dist 91 | - name: Extract release notes from annotated tag message 92 | id: release_notes 93 | env: 94 | # e.g. v0.1.0a1, v1.2.0b2 or v2.3.0rc3, but not v1.0.0 95 | PRERELEASE_TAG_PATTERN: "v[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+([ab]|rc)[[:digit:]]+" 96 | run: | 97 | # GH checkout action doesn't preserve tag annotations, we must fetch them 98 | # https://github.com/actions/checkout/issues/290 99 | git fetch --tags --force 100 | # strip leading 'refs/tags/' to get the tag name 101 | TAG_NAME="${GITHUB_REF##*/}" 102 | # Dump tag message to temporary .md file (excluding the PGP signature at the bottom) 103 | TAG_MESSAGE=$(git tag -l --format='%(contents)' $TAG_NAME | sed -n '/-----BEGIN PGP SIGNATURE-----/q;p') 104 | echo "$TAG_MESSAGE" > "${{ runner.temp }}/release_notes.md" 105 | # if the tag has a pre-release suffix mark the Github Release accordingly 106 | if egrep -q "$PRERELEASE_TAG_PATTERN" <<< "$TAG_NAME"; then 107 | echo "Tag contains a pre-release suffix" 108 | echo "IS_PRERELEASE=true" >> "$GITHUB_ENV" 109 | else 110 | echo "Tag does not contain pre-release suffix" 111 | echo "IS_PRERELEASE=false" >> "$GITHUB_ENV" 112 | fi 113 | - name: Create GitHub release 114 | id: create_release 115 | uses: actions/create-release@v1 116 | env: 117 | # This token is provided by Actions, you do not need to create your own token 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | with: 120 | tag_name: ${{ github.ref }} 121 | release_name: ${{ github.ref }} 122 | body_path: "${{ runner.temp }}/release_notes.md" 123 | draft: false 124 | prerelease: ${{ env.IS_PRERELEASE }} 125 | - name: Build and publish 126 | env: 127 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 128 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 129 | run: | 130 | if [ "$IS_PRERELEASE" == true ]; then 131 | echo "DEBUG: This is a pre-release" 132 | else 133 | echo "DEBUG: This is a final release" 134 | fi 135 | twine upload dist/wheels-*/*.whl dist/wheels-*/*.zip 136 | -------------------------------------------------------------------------------- /Lib/cu2qu/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division, absolute_import 2 | import os 3 | import argparse 4 | import logging 5 | import shutil 6 | import multiprocessing as mp 7 | from contextlib import closing 8 | from functools import partial 9 | 10 | import cu2qu 11 | from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic 12 | 13 | import defcon 14 | 15 | logger = logging.getLogger("cu2qu") 16 | 17 | 18 | def _cpu_count(): 19 | try: 20 | return mp.cpu_count() 21 | except NotImplementedError: # pragma: no cover 22 | return 1 23 | 24 | 25 | def _font_to_quadratic(zipped_paths, **kwargs): 26 | input_path, output_path = zipped_paths 27 | ufo = defcon.Font(input_path) 28 | logger.info('Converting curves for %s', input_path) 29 | if font_to_quadratic(ufo, **kwargs): 30 | logger.info("Saving %s", output_path) 31 | ufo.save(output_path) 32 | else: 33 | _copytree(input_path, output_path) 34 | 35 | 36 | def _samepath(path1, path2): 37 | # TODO on python3+, there's os.path.samefile 38 | path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1))) 39 | path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2))) 40 | return path1 == path2 41 | 42 | 43 | def _copytree(input_path, output_path): 44 | if _samepath(input_path, output_path): 45 | logger.debug("input and output paths are the same file; skipped copy") 46 | return 47 | if os.path.exists(output_path): 48 | shutil.rmtree(output_path) 49 | shutil.copytree(input_path, output_path) 50 | 51 | 52 | def main(args=None): 53 | parser = argparse.ArgumentParser(prog="cu2qu") 54 | parser.add_argument( 55 | "--version", action="version", version=cu2qu.__version__) 56 | parser.add_argument( 57 | "infiles", 58 | nargs="+", 59 | metavar="INPUT", 60 | help="one or more input UFO source file(s).") 61 | parser.add_argument("-v", "--verbose", action="count", default=0) 62 | parser.add_argument( 63 | "-e", 64 | "--conversion-error", 65 | type=float, 66 | metavar="ERROR", 67 | default=None, 68 | help="maxiumum approximation error measured in EM (default: 0.001)") 69 | parser.add_argument( 70 | "--keep-direction", 71 | dest="reverse_direction", 72 | action="store_false", 73 | help="do not reverse the contour direction") 74 | 75 | mode_parser = parser.add_mutually_exclusive_group() 76 | mode_parser.add_argument( 77 | "-i", 78 | "--interpolatable", 79 | action="store_true", 80 | help="whether curve conversion should keep interpolation compatibility" 81 | ) 82 | mode_parser.add_argument( 83 | "-j", 84 | "--jobs", 85 | type=int, 86 | nargs="?", 87 | default=1, 88 | const=_cpu_count(), 89 | metavar="N", 90 | help="Convert using N multiple processes (default: %(default)s)") 91 | 92 | output_parser = parser.add_mutually_exclusive_group() 93 | output_parser.add_argument( 94 | "-o", 95 | "--output-file", 96 | default=None, 97 | metavar="OUTPUT", 98 | help=("output filename for the converted UFO. By default fonts are " 99 | "modified in place. This only works with a single input.")) 100 | output_parser.add_argument( 101 | "-d", 102 | "--output-dir", 103 | default=None, 104 | metavar="DIRECTORY", 105 | help="output directory where to save converted UFOs") 106 | 107 | options = parser.parse_args(args) 108 | 109 | if not options.verbose: 110 | level = "WARNING" 111 | elif options.verbose == 1: 112 | level = "INFO" 113 | else: 114 | level = "DEBUG" 115 | logging.basicConfig(level=level) 116 | 117 | if len(options.infiles) > 1 and options.output_file: 118 | parser.error("-o/--output-file can't be used with multile inputs") 119 | 120 | if options.output_dir: 121 | output_dir = options.output_dir 122 | if not os.path.exists(output_dir): 123 | os.mkdir(output_dir) 124 | elif not os.path.isdir(output_dir): 125 | parser.error("'%s' is not a directory" % output_dir) 126 | output_paths = [ 127 | os.path.join(output_dir, os.path.basename(p)) 128 | for p in options.infiles 129 | ] 130 | elif options.output_file: 131 | output_paths = [options.output_file] 132 | else: 133 | # save in-place 134 | output_paths = list(options.infiles) 135 | 136 | kwargs = dict(dump_stats=options.verbose > 0, 137 | max_err_em=options.conversion_error, 138 | reverse_direction=options.reverse_direction) 139 | 140 | if options.interpolatable: 141 | logger.info('Converting curves compatibly') 142 | ufos = [defcon.Font(infile) for infile in options.infiles] 143 | if fonts_to_quadratic(ufos, **kwargs): 144 | for ufo, output_path in zip(ufos, output_paths): 145 | logger.info("Saving %s", output_path) 146 | ufo.save(output_path) 147 | else: 148 | for input_path, output_path in zip(options.infiles, output_paths): 149 | _copytree(input_path, output_path) 150 | else: 151 | jobs = min(len(options.infiles), 152 | options.jobs) if options.jobs > 1 else 1 153 | if jobs > 1: 154 | func = partial(_font_to_quadratic, **kwargs) 155 | logger.info('Running %d parallel processes', jobs) 156 | with closing(mp.Pool(jobs)) as pool: 157 | # can't use Pool.starmap as it's 3.3+ only 158 | pool.map(func, zip(options.infiles, output_paths)) 159 | else: 160 | for paths in zip(options.infiles, output_paths): 161 | _font_to_quadratic(paths, **kwargs) 162 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Bold.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 2146 7 | capHeight 8 | 1456 9 | copyright 10 | Copyright 2011 Google Inc. All Rights Reserved. 11 | descender 12 | -555 13 | familyName 14 | RobotoSubset 15 | italicAngle 16 | 0 17 | openTypeHeadCreated 18 | 2008/09/12 12:29:34 19 | openTypeHeadFlags 20 | 21 | 0 22 | 1 23 | 3 24 | 4 25 | 26 | openTypeHeadLowestRecPPEM 27 | 9 28 | openTypeHheaAscender 29 | 1900 30 | openTypeHheaDescender 31 | -500 32 | openTypeHheaLineGap 33 | 0 34 | openTypeNameDescription 35 | 36 | openTypeNameDesigner 37 | 38 | openTypeNameDesignerURL 39 | 40 | openTypeNameLicense 41 | 42 | openTypeNameLicenseURL 43 | 44 | openTypeNameManufacturer 45 | 46 | openTypeNameManufacturerURL 47 | 48 | openTypeNameSampleText 49 | 50 | openTypeOS2CodePageRanges 51 | 52 | 0 53 | 1 54 | 2 55 | 3 56 | 4 57 | 7 58 | 8 59 | 29 60 | 61 | openTypeOS2FamilyClass 62 | 63 | 0 64 | 0 65 | 66 | openTypeOS2Panose 67 | 68 | 0 69 | 0 70 | 0 71 | 0 72 | 0 73 | 0 74 | 0 75 | 0 76 | 0 77 | 0 78 | 79 | openTypeOS2Selection 80 | 81 | 82 | openTypeOS2StrikeoutPosition 83 | 512 84 | openTypeOS2StrikeoutSize 85 | 102 86 | openTypeOS2SubscriptXOffset 87 | 0 88 | openTypeOS2SubscriptXSize 89 | 1434 90 | openTypeOS2SubscriptYOffset 91 | 287 92 | openTypeOS2SubscriptYSize 93 | 1331 94 | openTypeOS2SuperscriptXOffset 95 | 0 96 | openTypeOS2SuperscriptXSize 97 | 1434 98 | openTypeOS2SuperscriptYOffset 99 | 977 100 | openTypeOS2SuperscriptYSize 101 | 1331 102 | openTypeOS2Type 103 | 104 | 105 | openTypeOS2TypoAscender 106 | 2146 107 | openTypeOS2TypoDescender 108 | -555 109 | openTypeOS2TypoLineGap 110 | 0 111 | openTypeOS2UnicodeRanges 112 | 113 | 0 114 | 1 115 | 2 116 | 3 117 | 4 118 | 5 119 | 6 120 | 7 121 | 9 122 | 11 123 | 29 124 | 30 125 | 31 126 | 32 127 | 33 128 | 34 129 | 35 130 | 36 131 | 37 132 | 38 133 | 40 134 | 45 135 | 60 136 | 62 137 | 64 138 | 69 139 | 140 | openTypeOS2VendorID 141 | GOOG 142 | openTypeOS2WeightClass 143 | 700 144 | openTypeOS2WidthClass 145 | 5 146 | openTypeOS2WinAscent 147 | 2146 148 | openTypeOS2WinDescent 149 | 555 150 | postscriptBlueFuzz 151 | 1 152 | postscriptBlueScale 153 | 0.039625 154 | postscriptBlueShift 155 | 7 156 | postscriptBlueValues 157 | 158 | -20 159 | 0 160 | 1082 161 | 1102 162 | 1456 163 | 1476 164 | 165 | postscriptDefaultCharacter 166 | space 167 | postscriptForceBold 168 | 169 | postscriptIsFixedPitch 170 | 171 | postscriptOtherBlues 172 | 173 | -436 174 | -416 175 | 176 | postscriptStemSnapH 177 | 178 | 250 179 | 180 | postscriptStemSnapV 181 | 182 | 316 183 | 184 | postscriptUnderlinePosition 185 | -150 186 | postscriptUnderlineThickness 187 | 100 188 | postscriptUniqueID 189 | -1 190 | styleName 191 | Bold 192 | trademark 193 | 194 | unitsPerEm 195 | 2048 196 | versionMajor 197 | 1 198 | versionMinor 199 | 0 200 | xHeight 201 | 1082 202 | year 203 | 2017 204 | 205 | 206 | -------------------------------------------------------------------------------- /tests/data/RobotoSubset-Regular.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 2146 7 | capHeight 8 | 1456 9 | copyright 10 | Copyright 2011 Google Inc. All Rights Reserved. 11 | descender 12 | -555 13 | familyName 14 | RobotoSubset 15 | guidelines 16 | 17 | 18 | italicAngle 19 | 0 20 | openTypeHeadCreated 21 | 2008/09/12 12:29:34 22 | openTypeHeadFlags 23 | 24 | 0 25 | 1 26 | 3 27 | 4 28 | 29 | openTypeHeadLowestRecPPEM 30 | 9 31 | openTypeHheaAscender 32 | 1900 33 | openTypeHheaDescender 34 | -500 35 | openTypeHheaLineGap 36 | 0 37 | openTypeNameDescription 38 | 39 | openTypeNameDesigner 40 | 41 | openTypeNameDesignerURL 42 | 43 | openTypeNameLicense 44 | 45 | openTypeNameLicenseURL 46 | 47 | openTypeNameManufacturer 48 | 49 | openTypeNameManufacturerURL 50 | 51 | openTypeNameSampleText 52 | 53 | openTypeOS2CodePageRanges 54 | 55 | 0 56 | 1 57 | 2 58 | 3 59 | 4 60 | 7 61 | 8 62 | 29 63 | 64 | openTypeOS2FamilyClass 65 | 66 | 0 67 | 0 68 | 69 | openTypeOS2Panose 70 | 71 | 2 72 | 0 73 | 0 74 | 0 75 | 0 76 | 0 77 | 0 78 | 0 79 | 0 80 | 0 81 | 82 | openTypeOS2Selection 83 | 84 | 85 | openTypeOS2StrikeoutPosition 86 | 512 87 | openTypeOS2StrikeoutSize 88 | 102 89 | openTypeOS2SubscriptXOffset 90 | 0 91 | openTypeOS2SubscriptXSize 92 | 1434 93 | openTypeOS2SubscriptYOffset 94 | 287 95 | openTypeOS2SubscriptYSize 96 | 1331 97 | openTypeOS2SuperscriptXOffset 98 | 0 99 | openTypeOS2SuperscriptXSize 100 | 1434 101 | openTypeOS2SuperscriptYOffset 102 | 977 103 | openTypeOS2SuperscriptYSize 104 | 1331 105 | openTypeOS2Type 106 | 107 | 108 | openTypeOS2TypoAscender 109 | 2146 110 | openTypeOS2TypoDescender 111 | -555 112 | openTypeOS2TypoLineGap 113 | 0 114 | openTypeOS2UnicodeRanges 115 | 116 | 0 117 | 1 118 | 2 119 | 3 120 | 4 121 | 5 122 | 6 123 | 7 124 | 9 125 | 11 126 | 29 127 | 30 128 | 31 129 | 32 130 | 33 131 | 34 132 | 35 133 | 36 134 | 37 135 | 38 136 | 40 137 | 45 138 | 60 139 | 62 140 | 64 141 | 69 142 | 143 | openTypeOS2VendorID 144 | GOOG 145 | openTypeOS2WeightClass 146 | 400 147 | openTypeOS2WidthClass 148 | 5 149 | openTypeOS2WinAscent 150 | 2146 151 | openTypeOS2WinDescent 152 | 555 153 | postscriptBlueFuzz 154 | 1 155 | postscriptBlueScale 156 | 0.039625 157 | postscriptBlueShift 158 | 7 159 | postscriptBlueValues 160 | 161 | -20 162 | 0 163 | 1082 164 | 1102 165 | 1456 166 | 1476 167 | 168 | postscriptDefaultCharacter 169 | space 170 | postscriptFamilyBlues 171 | 172 | 173 | postscriptFamilyOtherBlues 174 | 175 | 176 | postscriptForceBold 177 | 178 | postscriptIsFixedPitch 179 | 180 | postscriptOtherBlues 181 | 182 | -436 183 | -416 184 | 185 | postscriptStemSnapH 186 | 187 | 154 188 | 189 | postscriptStemSnapV 190 | 191 | 196 192 | 193 | postscriptUnderlinePosition 194 | -150 195 | postscriptUnderlineThickness 196 | 100 197 | postscriptUniqueID 198 | -1 199 | styleName 200 | Regular 201 | trademark 202 | 203 | unitsPerEm 204 | 2048 205 | versionMajor 206 | 1 207 | versionMinor 208 | 0 209 | xHeight 210 | 1082 211 | year 212 | 2017 213 | 214 | 215 | -------------------------------------------------------------------------------- /tests/cu2qu_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from __future__ import print_function, division, absolute_import 17 | 18 | import collections 19 | import math 20 | import unittest 21 | import os 22 | import json 23 | 24 | from cu2qu import curve_to_quadratic, curves_to_quadratic 25 | from . import DATADIR 26 | 27 | 28 | MAX_ERR = 5 29 | 30 | 31 | class CurveToQuadraticTest(unittest.TestCase): 32 | 33 | @classmethod 34 | def setUpClass(cls): 35 | """Do the curve conversion ahead of time, and run tests on results.""" 36 | with open(os.path.join(DATADIR, "curves.json"), "r") as fp: 37 | curves = json.load(fp) 38 | 39 | cls.single_splines = [ 40 | curve_to_quadratic(c, MAX_ERR) for c in curves] 41 | cls.single_errors = [ 42 | cls.curve_spline_dist(c, s) 43 | for c, s in zip(curves, cls.single_splines)] 44 | 45 | curve_groups = [curves[i:i + 3] for i in range(0, 300, 3)] 46 | cls.compat_splines = [ 47 | curves_to_quadratic(c, [MAX_ERR] * 3) for c in curve_groups] 48 | cls.compat_errors = [ 49 | [cls.curve_spline_dist(c, s) for c, s in zip(curve_group, splines)] 50 | for curve_group, splines in zip(curve_groups, cls.compat_splines)] 51 | 52 | cls.results = [] 53 | 54 | @classmethod 55 | def tearDownClass(cls): 56 | """Print stats from conversion, as determined during tests.""" 57 | 58 | for tag, results in cls.results: 59 | print('\n%s\n%s' % ( 60 | tag, '\n'.join( 61 | '%s: %s (%d)' % (k, '#' * (v // 10 + 1), v) 62 | for k, v in sorted(results.items())))) 63 | 64 | def test_results_unchanged(self): 65 | """Tests that the results of conversion haven't changed since the time 66 | of this test's writing. Useful as a quick check whenever one modifies 67 | the conversion algorithm. 68 | """ 69 | 70 | expected = { 71 | 2: 6, 72 | 3: 26, 73 | 4: 82, 74 | 5: 232, 75 | 6: 360, 76 | 7: 266, 77 | 8: 28} 78 | 79 | results = collections.defaultdict(int) 80 | for spline in self.single_splines: 81 | n = len(spline) - 2 82 | results[n] += 1 83 | self.assertEqual(results, expected) 84 | self.results.append(('single spline lengths', results)) 85 | 86 | def test_results_unchanged_multiple(self): 87 | """Test that conversion results are unchanged for multiple curves.""" 88 | 89 | expected = { 90 | 5: 11, 91 | 6: 35, 92 | 7: 49, 93 | 8: 5} 94 | 95 | results = collections.defaultdict(int) 96 | for splines in self.compat_splines: 97 | n = len(splines[0]) - 2 98 | for spline in splines[1:]: 99 | self.assertEqual(len(spline) - 2, n, 100 | 'Got incompatible conversion results') 101 | results[n] += 1 102 | self.assertEqual(results, expected) 103 | self.results.append(('compatible spline lengths', results)) 104 | 105 | def test_does_not_exceed_tolerance(self): 106 | """Test that conversion results do not exceed given error tolerance.""" 107 | 108 | results = collections.defaultdict(int) 109 | for error in self.single_errors: 110 | results[round(error, 1)] += 1 111 | self.assertLessEqual(error, MAX_ERR) 112 | self.results.append(('single errors', results)) 113 | 114 | def test_does_not_exceed_tolerance_multiple(self): 115 | """Test that error tolerance isn't exceeded for multiple curves.""" 116 | 117 | results = collections.defaultdict(int) 118 | for errors in self.compat_errors: 119 | for error in errors: 120 | results[round(error, 1)] += 1 121 | self.assertLessEqual(error, MAX_ERR) 122 | self.results.append(('compatible errors', results)) 123 | 124 | @classmethod 125 | def curve_spline_dist(cls, bezier, spline, total_steps=20): 126 | """Max distance between a bezier and quadratic spline at sampled points.""" 127 | 128 | error = 0 129 | n = len(spline) - 2 130 | steps = total_steps // n 131 | for i in range(0, n - 1): 132 | p1 = spline[0] if i == 0 else p3 133 | p2 = spline[i + 1] 134 | if i < n - 1: 135 | p3 = cls.lerp(spline[i + 1], spline[i + 2], 0.5) 136 | else: 137 | p3 = spline[n + 2] 138 | segment = p1, p2, p3 139 | for j in range(steps): 140 | error = max(error, cls.dist( 141 | cls.cubic_bezier_at(bezier, (j / steps + i) / n), 142 | cls.quadratic_bezier_at(segment, j / steps))) 143 | return error 144 | 145 | @classmethod 146 | def lerp(cls, p1, p2, t): 147 | (x1, y1), (x2, y2) = p1, p2 148 | return x1 + (x2 - x1) * t, y1 + (y2 - y1) * t 149 | 150 | @classmethod 151 | def dist(cls, p1, p2): 152 | (x1, y1), (x2, y2) = p1, p2 153 | return math.hypot(x1 - x2, y1 - y2) 154 | 155 | @classmethod 156 | def quadratic_bezier_at(cls, b, t): 157 | (x1, y1), (x2, y2), (x3, y3) = b 158 | _t = 1 - t 159 | t2 = t * t 160 | _t2 = _t * _t 161 | _2_t_t = 2 * t * _t 162 | return (_t2 * x1 + _2_t_t * x2 + t2 * x3, 163 | _t2 * y1 + _2_t_t * y2 + t2 * y3) 164 | 165 | @classmethod 166 | def cubic_bezier_at(cls, b, t): 167 | (x1, y1), (x2, y2), (x3, y3), (x4, y4) = b 168 | _t = 1 - t 169 | t2 = t * t 170 | _t2 = _t * _t 171 | t3 = t * t2 172 | _t3 = _t * _t2 173 | _3_t2_t = 3 * t2 * _t 174 | _3_t_t2 = 3 * t * _t2 175 | return (_t3 * x1 + _3_t_t2 * x2 + _3_t2_t * x3 + t3 * x4, 176 | _t3 * y1 + _3_t_t2 * y2 + _3_t2_t * y3 + t3 * y4) 177 | 178 | 179 | if __name__ == '__main__': 180 | unittest.main() 181 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from setuptools import setup, find_packages, Extension 17 | from setuptools.command.build_ext import build_ext as _build_ext 18 | from setuptools.command.sdist import sdist as _sdist 19 | import pkg_resources 20 | from distutils import log 21 | import sys 22 | import os 23 | import re 24 | from io import open 25 | 26 | 27 | needs_pytest = {'pytest', 'test'}.intersection(sys.argv) 28 | pytest_runner = ['pytest_runner'] if needs_pytest else [] 29 | needs_wheel = {'bdist_wheel'}.intersection(sys.argv) 30 | wheel = ['wheel'] if needs_wheel else [] 31 | 32 | # Check if minimum required Cython is available. 33 | # For consistency, we require the same as our vendored Cython.Shadow module 34 | cymod = "Lib/cu2qu/cython.py" 35 | cython_version_re = re.compile('__version__ = ["\']([0-9][0-9\w\.]+)["\']') 36 | with open(cymod, "r", encoding="utf-8") as fp: 37 | for line in fp: 38 | m = cython_version_re.match(line) 39 | if m: 40 | cython_min_version = m.group(1) 41 | break 42 | else: 43 | sys.exit("error: failed to parse cython version in '%s'" % cymod) 44 | 45 | required_cython = "cython >= %s" % cython_min_version 46 | try: 47 | pkg_resources.require(required_cython) 48 | except pkg_resources.ResolutionError: 49 | has_cython = False 50 | else: 51 | has_cython = True 52 | 53 | # First, check if the CU2QU_WITH_CYTHON environment variable is set. 54 | # Values "1", "true" or "yes" mean that Cython is required and will be used 55 | # to regenerate the *.c sources from which the native extension is built; 56 | # "0", "false" or "no" mean that Cython is not required and no extension 57 | # module will be compiled (i.e. the wheel is pure-python and universal). 58 | # If the variable is not set, then the pre-generated *.c sources that 59 | # are included in the sdist package will be used to try build the extension. 60 | # However, if any error occurs during compilation (e.g. the host 61 | # machine doesn't have the required compiler toolchain installed), the 62 | # installation proceeds without the compiled extensions, but will only have 63 | # the pure-python module. 64 | env_with_cython = os.environ.get("CU2QU_WITH_CYTHON") 65 | with_cython = ( 66 | True if env_with_cython in {"1", "true", "yes"} 67 | else False if env_with_cython in {"0", "false", "no"} 68 | else None 69 | ) 70 | 71 | # command line options --with-cython and --without-cython are also supported. 72 | # They override the environment variable 73 | opt_with_cython = {'--with-cython'}.intersection(sys.argv) 74 | opt_without_cython = {'--without-cython'}.intersection(sys.argv) 75 | if opt_with_cython and opt_without_cython: 76 | sys.exit( 77 | "error: the options '--with-cython' and '--without-cython' are " 78 | "mutually exclusive" 79 | ) 80 | elif opt_with_cython: 81 | sys.argv.remove("--with-cython") 82 | with_cython = True 83 | elif opt_without_cython: 84 | sys.argv.remove("--without-cython") 85 | with_cython = False 86 | 87 | 88 | class cython_build_ext(_build_ext): 89 | """Compile *.pyx source files to *.c using cythonize if Cython is 90 | installed, else use the pre-generated *.c sources. 91 | """ 92 | 93 | def finalize_options(self): 94 | if with_cython: 95 | if not has_cython: 96 | from distutils.errors import DistutilsSetupError 97 | 98 | raise DistutilsSetupError( 99 | "%s is required when using --with-cython" % required_cython 100 | ) 101 | 102 | from Cython.Build import cythonize 103 | 104 | # optionally enable line tracing for test coverage support 105 | linetrace = os.environ.get("CYTHON_TRACE") == "1" 106 | 107 | self.distribution.ext_modules[:] = cythonize( 108 | self.distribution.ext_modules, 109 | force=linetrace or self.force, 110 | annotate=os.environ.get("CYTHON_ANNOTATE") == "1", 111 | quiet=not self.verbose, 112 | compiler_directives={ 113 | "linetrace": linetrace, 114 | "language_level": 3, 115 | "embedsignature": True, 116 | }, 117 | ) 118 | else: 119 | # replace *.py/.pyx sources with their pre-generated *.c versions 120 | for ext in self.distribution.ext_modules: 121 | ext.sources = [re.sub("\.pyx?$", ".c", n) for n in ext.sources] 122 | 123 | _build_ext.finalize_options(self) 124 | 125 | def build_extensions(self): 126 | if not has_cython: 127 | log.info( 128 | "%s is not installed. Pre-generated *.c sources will be " 129 | "will be used to build the extensions." % required_cython 130 | ) 131 | 132 | try: 133 | _build_ext.build_extensions(self) 134 | except Exception as e: 135 | if with_cython: 136 | raise 137 | from distutils.errors import DistutilsModuleError 138 | 139 | # optional compilation failed: we delete 'ext_modules' and make sure 140 | # the generated wheel is 'pure' 141 | del self.distribution.ext_modules[:] 142 | try: 143 | bdist_wheel = self.get_finalized_command("bdist_wheel") 144 | except DistutilsModuleError: 145 | # 'bdist_wheel' command not available as wheel is not installed 146 | pass 147 | else: 148 | bdist_wheel.root_is_pure = True 149 | log.error('error: building extensions failed: %s' % e) 150 | 151 | def get_source_files(self): 152 | filenames = _build_ext.get_source_files(self) 153 | 154 | # include pre-generated *.c sources inside sdist, but only if cython is 155 | # installed (and hence they will be updated upon making the sdist) 156 | if has_cython: 157 | for ext in self.extensions: 158 | filenames.extend( 159 | [re.sub("\.pyx?$", ".c", n) for n in ext.sources] 160 | ) 161 | return filenames 162 | 163 | 164 | class cython_sdist(_sdist): 165 | """ Run 'cythonize' on *.pyx sources to ensure the *.c files included 166 | in the source distribution are up-to-date. 167 | """ 168 | 169 | def run(self): 170 | if with_cython and not has_cython: 171 | from distutils.errors import DistutilsSetupError 172 | 173 | raise DistutilsSetupError( 174 | "%s is required when creating sdist --with-cython" 175 | % required_cython 176 | ) 177 | 178 | if has_cython: 179 | from Cython.Build import cythonize 180 | 181 | cythonize( 182 | self.distribution.ext_modules, 183 | force=True, # always regenerate *.c sources 184 | quiet=not self.verbose, 185 | compiler_directives={ 186 | "language_level": 3, 187 | "embedsignature": True 188 | }, 189 | ) 190 | 191 | _sdist.run(self) 192 | 193 | 194 | # don't build extensions if user explicitly requested --without-cython or we're 195 | # building for PyPy. 196 | is_pypy = hasattr(sys, "pypy_version_info") 197 | if with_cython is False or (with_cython is None and is_pypy): 198 | extensions = [] 199 | else: 200 | extensions = [ 201 | Extension("cu2qu.cu2qu", ["Lib/cu2qu/cu2qu.py"]), 202 | ] 203 | 204 | with open('README.rst', 'r') as f: 205 | long_description = f.read() 206 | 207 | setup( 208 | name='cu2qu', 209 | use_scm_version={"write_to": "Lib/cu2qu/_version.py"}, 210 | description='Cubic-to-quadratic bezier curve conversion', 211 | author="James Godfrey-Kittle, Behdad Esfahbod", 212 | author_email="jamesgk@google.com", 213 | url="https://github.com/googlefonts", 214 | license="Apache License, Version 2.0", 215 | long_description=long_description, 216 | packages=find_packages('Lib'), 217 | package_dir={'': 'Lib'}, 218 | ext_modules=extensions, 219 | include_package_data=True, 220 | setup_requires=pytest_runner + wheel + ["setuptools_scm"], 221 | tests_require=[ 222 | 'pytest>=2.8', 223 | ], 224 | install_requires=[ 225 | "fonttools[ufo] >= 3.32.0", 226 | ], 227 | extras_require={"cli": ["defcon >= 0.6.0"]}, 228 | entry_points={"console_scripts": ["cu2qu = cu2qu.cli:main [cli]"]}, 229 | classifiers=[ 230 | 'Development Status :: 4 - Beta', 231 | 'Intended Audience :: Developers', 232 | 'License :: OSI Approved :: Apache Software License', 233 | 'Operating System :: OS Independent', 234 | 'Programming Language :: Python', 235 | 'Programming Language :: Python :: 2', 236 | 'Programming Language :: Python :: 3', 237 | 'Topic :: Scientific/Engineering :: Mathematics', 238 | 'Topic :: Multimedia :: Graphics :: Graphics Conversion', 239 | 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', 240 | 'Topic :: Software Development :: Libraries :: Python Modules', 241 | ], 242 | cmdclass={"build_ext": cython_build_ext, "sdist": cython_sdist}, 243 | ) 244 | --------------------------------------------------------------------------------