diff -Nru cu2qu-1.6.7/.coveragerc cu2qu-1.3.0/.coveragerc --- cu2qu-1.6.7/.coveragerc 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/.coveragerc 2017-10-31 19:57:00.000000000 +0000 @@ -6,10 +6,6 @@ # list of directories or packages to measure source = cu2qu -# this is simply vendored, no need to include in coverage report -omit = - */cu2qu/cython.py - # these are treated as equivalent when combining data [paths] source = diff -Nru cu2qu-1.6.7/debian/changelog cu2qu-1.3.0/debian/changelog --- cu2qu-1.6.7/debian/changelog 2020-12-07 17:20:03.000000000 +0000 +++ cu2qu-1.3.0/debian/changelog 2021-08-23 02:12:28.000000000 +0000 @@ -1,91 +1,8 @@ -cu2qu (1.6.7-1build2) hirsute; urgency=medium +cu2qu (1.3.0-1~16.04.sav0) xenial; urgency=medium - * No-change rebuild to drop python3.8 extensions. + * Backport to Xenial - -- Matthias Klose Mon, 07 Dec 2020 18:20:03 +0100 - -cu2qu (1.6.7-1build1) hirsute; urgency=medium - - * No-change rebuild to build with python3.9 as supported. - - -- Matthias Klose Mon, 26 Oct 2020 10:56:38 +0100 - -cu2qu (1.6.7-1) unstable; urgency=medium - - * New upstream version 1.6.7 - * d/control: Bump Standards-Version to 4.5.0 - * d/patches: Delete patches that applied upstream - - -- Yao Wei (魏銘廷) Sun, 01 Mar 2020 23:24:49 +0800 - -cu2qu (1.6.6-5) unstable; urgency=medium - - * Fix Python upgrade breakage (Closes: #943995) - * Add Salsa CI script - - -- Yao Wei (魏銘廷) Sun, 03 Nov 2019 12:50:49 +0800 - -cu2qu (1.6.6-4) unstable; urgency=medium - - * Team upload. - - [ Yao Wei (魏銘廷) ] - * Add patch from upstream to fix i386 test precision - - -- Boyuan Yang Fri, 01 Nov 2019 11:19:40 -0400 - -cu2qu (1.6.6-3) unstable; urgency=medium - - * Use cython3 to build native binary module - * Fix package test dependency - - -- Yao Wei (魏銘廷) Wed, 23 Oct 2019 10:18:12 +0800 - -cu2qu (1.6.6-2) unstable; urgency=medium - - * Team upload. - * No-change source-only upload to allow testing migration. - - -- Boyuan Yang Tue, 22 Oct 2019 11:14:18 -0400 - -cu2qu (1.6.6-1) unstable; urgency=medium - - * New upstream version 1.6.6 - * Separate Python Programs to another package per Python Policy - * Bump debhelper-compat to 12 - * Bump Standards-Version to 4.4.1 - * Use pytest-3 as autopkgtest - - -- Yao Wei (魏銘廷) Sun, 13 Oct 2019 13:16:51 +0800 - -cu2qu (1.6.5-1) unstable; urgency=medium - - [ Yao Wei (魏銘廷) ] - * New upstream release 1.6.5 - * debian/control: - - Update maintainer address - - Bump Standards-Version to 4.2.1 - - Bump debhelper version to 11 - - Update dependencies, remove ufolib dependency - * debian/rules: - - Add workaround for setuptools - * add dep for setuptools-scm - - [ Jeremy Bicha ] - * Add Testsuite: autopkgtest-pkg-python - - -- Yao Wei (魏銘廷) Thu, 13 Dec 2018 09:12:51 -0500 - -cu2qu (1.5.0-1) unstable; urgency=medium - - * New upstream release 1.5.0 - * debian/control: - - Update team mailing list address - - Bump Standards-Version to 4.1.4 - - Change Vcs fields to salsa.debian.org - - Update requirements - - -- Yao Wei (魏銘廷) Wed, 05 Sep 2018 11:53:56 +0800 + -- Rob Savoury Sun, 22 Aug 2021 19:12:28 -0700 cu2qu (1.3.0-1) unstable; urgency=medium diff -Nru cu2qu-1.6.7/debian/compat cu2qu-1.3.0/debian/compat --- cu2qu-1.6.7/debian/compat 1970-01-01 00:00:00.000000000 +0000 +++ cu2qu-1.3.0/debian/compat 2017-11-18 21:59:26.000000000 +0000 @@ -0,0 +1 @@ +10 diff -Nru cu2qu-1.6.7/debian/control cu2qu-1.3.0/debian/control --- cu2qu-1.6.7/debian/control 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/control 2017-11-18 21:59:26.000000000 +0000 @@ -1,49 +1,28 @@ Source: cu2qu Section: fonts Priority: optional -Maintainer: Debian Fonts Task Force -Uploaders: - Jeremy Bicha , - Yao Wei (魏銘廷) -Build-Depends: debhelper-compat (= 12), +Maintainer: Debian Fonts Task Force +Uploaders: Jeremy Bicha +Build-Depends: debhelper (>= 10), dh-python, python3-all, - python3-all-dev, - cython3 (>= 0.28.5), - python3-defcon (>= 0.6.0), - python3-fonttools (>= 3.32.0), + python3-defcon , + python3-fonttools (>= 3.18.0), python3-pytest , - python3-importlib-metadata , python3-setuptools, - python3-setuptools-scm -Standards-Version: 4.5.0 + python3-ufolib (>= 2.1.1) +Standards-Version: 4.1.1 Homepage: https://github.com/googlei18n/cu2qu -Vcs-Git: https://salsa.debian.org/fonts-team/cu2qu.git -Vcs-Browser: https://salsa.debian.org/fonts-team/cu2qu +Vcs-Git: https://anonscm.debian.org/git/pkg-fonts/cu2qu.git +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-fonts/cu2qu.git Package: python3-cu2qu -Architecture: any +Architecture: all Section: python Depends: ${misc:Depends}, ${python3:Depends}, - ${shlibs:Depends}, - python3-fonttools (>= 3.32.0), - python3-defcon (>= 0.6.0) -Description: Cubic-to-quadratic bezier curve conversion (Python 3 Library) - cu2qu is a library that approximates cubic bezier curves with quadratic - splines. This has general utility, but is especially useful for generating - fonts. - -Package: cu2qu -Architecture: all -Multi-Arch: foreign -Depends: ${python3:Depends}, - python3-cu2qu (>= ${source:Version}), - python3-cu2qu (<< ${source:Upstream-Version}.0~), - ${misc:Depends} -Description: Cubic-to-quadratic bezier curve conversion (Executable) + python3-ufolib (>= 2.1.1) +Description: Python library for cubic-to-quadratic bezier curve conversion cu2qu is a library that approximates cubic bezier curves with quadratic splines. This has general utility, but is especially useful for generating fonts. - . - This provides the command-line utility of cu2qu package diff -Nru cu2qu-1.6.7/debian/copyright cu2qu-1.3.0/debian/copyright --- cu2qu-1.6.7/debian/copyright 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/copyright 2017-11-18 21:59:26.000000000 +0000 @@ -1,4 +1,4 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: cu2qu Source: https://github.com/googlei18n/cu2qu diff -Nru cu2qu-1.6.7/debian/cu2qu.install cu2qu-1.3.0/debian/cu2qu.install --- cu2qu-1.6.7/debian/cu2qu.install 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/cu2qu.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -debian/tmp/usr/bin/* usr/bin/ diff -Nru cu2qu-1.6.7/debian/docs cu2qu-1.3.0/debian/docs --- cu2qu-1.6.7/debian/docs 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/docs 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -README.rst diff -Nru cu2qu-1.6.7/debian/python3-cu2qu.install cu2qu-1.3.0/debian/python3-cu2qu.install --- cu2qu-1.6.7/debian/python3-cu2qu.install 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/python3-cu2qu.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -debian/tmp/usr/lib/python3* usr/lib/ diff -Nru cu2qu-1.6.7/debian/rules cu2qu-1.3.0/debian/rules --- cu2qu-1.6.7/debian/rules 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/rules 2017-11-18 21:59:26.000000000 +0000 @@ -1,10 +1,6 @@ #!/usr/bin/make -f -export PYBUILD_BUILD_ARGS="--with-cython" +export PYBUILD_NAME=cu2qu %: dh $@ --with python3 --buildsystem=pybuild - -override_dh_auto_clean: - dh_auto_clean - rm Lib/cu2qu/_version.py diff -Nru cu2qu-1.6.7/debian/salsa-ci.yml cu2qu-1.3.0/debian/salsa-ci.yml --- cu2qu-1.6.7/debian/salsa-ci.yml 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ ---- -include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff -Nru cu2qu-1.6.7/debian/tests/control cu2qu-1.3.0/debian/tests/control --- cu2qu-1.6.7/debian/tests/control 2020-03-01 15:24:49.000000000 +0000 +++ cu2qu-1.3.0/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -Test-Command: pytest-3 -Depends: python3-all, - python3-cu2qu, - python3-pytest, - python3-coverage, - python3-importlib-metadata diff -Nru cu2qu-1.6.7/.gitignore cu2qu-1.3.0/.gitignore --- cu2qu-1.6.7/.gitignore 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/.gitignore 2017-10-31 19:57:00.000000000 +0000 @@ -1,12 +1,7 @@ # Byte-compiled and optimized files __pycache__/ -*.py[cod] +*.py[co] *$py.class -*.so - -# cython generated C/HTML files -Lib/cu2qu/*.c -Lib/cu2qu/*.html # Packaging *.egg-info @@ -20,10 +15,6 @@ .coverage.* .tox htmlcov -.pytest_cache/ # OS X Finder .DS_Store - -# auto-generated version file -Lib/cu2qu/_version.py diff -Nru cu2qu-1.6.7/Lib/cu2qu/cli.py cu2qu-1.3.0/Lib/cu2qu/cli.py --- cu2qu-1.6.7/Lib/cu2qu/cli.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/cli.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,161 +0,0 @@ -from __future__ import print_function, division, absolute_import -import os -import argparse -import logging -import shutil -import multiprocessing as mp -from contextlib import closing -from functools import partial - -import cu2qu -from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic - -import defcon - -logger = logging.getLogger("cu2qu") - - -def _cpu_count(): - try: - return mp.cpu_count() - except NotImplementedError: # pragma: no cover - return 1 - - -def _font_to_quadratic(zipped_paths, **kwargs): - input_path, output_path = zipped_paths - ufo = defcon.Font(input_path) - logger.info('Converting curves for %s', input_path) - if font_to_quadratic(ufo, **kwargs): - logger.info("Saving %s", output_path) - ufo.save(output_path) - else: - _copytree(input_path, output_path) - - -def _samepath(path1, path2): - # TODO on python3+, there's os.path.samefile - path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1))) - path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2))) - return path1 == path2 - - -def _copytree(input_path, output_path): - if _samepath(input_path, output_path): - logger.debug("input and output paths are the same file; skipped copy") - return - if os.path.exists(output_path): - shutil.rmtree(output_path) - shutil.copytree(input_path, output_path) - - -def main(args=None): - parser = argparse.ArgumentParser(prog="cu2qu") - parser.add_argument( - "--version", action="version", version=cu2qu.__version__) - parser.add_argument( - "infiles", - nargs="+", - metavar="INPUT", - help="one or more input UFO source file(s).") - parser.add_argument("-v", "--verbose", action="count", default=0) - parser.add_argument( - "-e", - "--conversion-error", - type=float, - metavar="ERROR", - default=None, - help="maxiumum approximation error measured in EM (default: 0.001)") - parser.add_argument( - "--keep-direction", - dest="reverse_direction", - action="store_false", - help="do not reverse the contour direction") - - mode_parser = parser.add_mutually_exclusive_group() - mode_parser.add_argument( - "-i", - "--interpolatable", - action="store_true", - help="whether curve conversion should keep interpolation compatibility" - ) - mode_parser.add_argument( - "-j", - "--jobs", - type=int, - nargs="?", - default=1, - const=_cpu_count(), - metavar="N", - help="Convert using N multiple processes (default: %(default)s)") - - output_parser = parser.add_mutually_exclusive_group() - output_parser.add_argument( - "-o", - "--output-file", - default=None, - metavar="OUTPUT", - help=("output filename for the converted UFO. By default fonts are " - "modified in place. This only works with a single input.")) - output_parser.add_argument( - "-d", - "--output-dir", - default=None, - metavar="DIRECTORY", - help="output directory where to save converted UFOs") - - options = parser.parse_args(args) - - if not options.verbose: - level = "WARNING" - elif options.verbose == 1: - level = "INFO" - else: - level = "DEBUG" - logging.basicConfig(level=level) - - if len(options.infiles) > 1 and options.output_file: - parser.error("-o/--output-file can't be used with multile inputs") - - if options.output_dir: - output_dir = options.output_dir - if not os.path.exists(output_dir): - os.mkdir(output_dir) - elif not os.path.isdir(output_dir): - parser.error("'%s' is not a directory" % output_dir) - output_paths = [ - os.path.join(output_dir, os.path.basename(p)) - for p in options.infiles - ] - elif options.output_file: - output_paths = [options.output_file] - else: - # save in-place - output_paths = list(options.infiles) - - kwargs = dict(dump_stats=options.verbose > 0, - max_err_em=options.conversion_error, - reverse_direction=options.reverse_direction) - - if options.interpolatable: - logger.info('Converting curves compatibly') - ufos = [defcon.Font(infile) for infile in options.infiles] - if fonts_to_quadratic(ufos, **kwargs): - for ufo, output_path in zip(ufos, output_paths): - logger.info("Saving %s", output_path) - ufo.save(output_path) - else: - for input_path, output_path in zip(options.infiles, output_paths): - _copytree(input_path, output_path) - else: - jobs = min(len(options.infiles), - options.jobs) if options.jobs > 1 else 1 - if jobs > 1: - func = partial(_font_to_quadratic, **kwargs) - logger.info('Running %d parallel processes', jobs) - with closing(mp.Pool(jobs)) as pool: - # can't use Pool.starmap as it's 3.3+ only - pool.map(func, zip(options.infiles, output_paths)) - else: - for paths in zip(options.infiles, output_paths): - _font_to_quadratic(paths, **kwargs) diff -Nru cu2qu-1.6.7/Lib/cu2qu/cu2qu.py cu2qu-1.3.0/Lib/cu2qu/cu2qu.py --- cu2qu-1.6.7/Lib/cu2qu/cu2qu.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/cu2qu.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,364 +0,0 @@ -#cython: language_level=3 -#distutils: define_macros=CYTHON_TRACE_NOGIL=1 - -# Copyright 2015 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from __future__ import print_function, division, absolute_import - -try: - import cython -except ImportError: - # if not installed, use the embedded (no-op) copy of Cython.Shadow - from . import cython - -import math - -from .errors import Error as Cu2QuError, ApproxNotFoundError - -__all__ = ['curve_to_quadratic', 'curves_to_quadratic'] - -MAX_N = 100 - -NAN = float("NaN") - - -if cython.compiled: - # Yep, I'm compiled. - COMPILED = True -else: - # Just a lowly interpreted script. - COMPILED = False - - -@cython.cfunc -@cython.inline -@cython.returns(cython.double) -@cython.locals(v1=cython.complex, v2=cython.complex) -def dot(v1, v2): - """Return the dot product of two vectors.""" - return (v1 * v2.conjugate()).real - - -@cython.cfunc -@cython.inline -@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) -@cython.locals(_1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex) -def calc_cubic_points(a, b, c, d): - _1 = d - _2 = (c / 3.0) + d - _3 = (b + c) / 3.0 + _2 - _4 = a + d + c + b - return _1, _2, _3, _4 - - -@cython.cfunc -@cython.inline -@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex) -@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) -def calc_cubic_parameters(p0, p1, p2, p3): - c = (p1 - p0) * 3.0 - b = (p2 - p1) * 3.0 - c - d = p0 - a = p3 - d - c - b - return a, b, c, d - - -@cython.cfunc -@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex) -def split_cubic_into_n_iter(p0, p1, p2, p3, n): - # Hand-coded special-cases - if n == 2: - return iter(split_cubic_into_two(p0, p1, p2, p3)) - if n == 3: - return iter(split_cubic_into_three(p0, p1, p2, p3)) - if n == 4: - a, b = split_cubic_into_two(p0, p1, p2, p3) - return iter(split_cubic_into_two(*a) + split_cubic_into_two(*b)) - if n == 6: - a, b = split_cubic_into_two(p0, p1, p2, p3) - return iter(split_cubic_into_three(*a) + split_cubic_into_three(*b)) - - return _split_cubic_into_n_gen(p0,p1,p2,p3,n) - - -@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, n=cython.int) -@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) -@cython.locals(dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int) -@cython.locals(a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex) -def _split_cubic_into_n_gen(p0, p1, p2, p3, n): - a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3) - dt = 1 / n - delta_2 = dt * dt - delta_3 = dt * delta_2 - for i in range(n): - t1 = i * dt - t1_2 = t1 * t1 - # calc new a, b, c and d - a1 = a * delta_3 - b1 = (3*a*t1 + b) * delta_2 - c1 = (2*b*t1 + c + 3*a*t1_2) * dt - d1 = a*t1*t1_2 + b*t1_2 + c*t1 + d - yield calc_cubic_points(a1, b1, c1, d1) - - -@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex) -@cython.locals(mid=cython.complex, deriv3=cython.complex) -def split_cubic_into_two(p0, p1, p2, p3): - mid = (p0 + 3 * (p1 + p2) + p3) * .125 - deriv3 = (p3 + p2 - p1 - p0) * .125 - return ((p0, (p0 + p1) * .5, mid - deriv3, mid), - (mid, mid + deriv3, (p2 + p3) * .5, p3)) - - -@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, _27=cython.double) -@cython.locals(mid1=cython.complex, deriv1=cython.complex, mid2=cython.complex, deriv2=cython.complex) -def split_cubic_into_three(p0, p1, p2, p3, _27=1/27): - # we define 1/27 as a keyword argument so that it will be evaluated only - # once but still in the scope of this function - mid1 = (8*p0 + 12*p1 + 6*p2 + p3) * _27 - deriv1 = (p3 + 3*p2 - 4*p0) * _27 - mid2 = (p0 + 6*p1 + 12*p2 + 8*p3) * _27 - deriv2 = (4*p3 - 3*p1 - p0) * _27 - return ((p0, (2*p0 + p1) / 3.0, mid1 - deriv1, mid1), - (mid1, mid1 + deriv1, mid2 - deriv2, mid2), - (mid2, mid2 + deriv2, (p2 + 2*p3) / 3.0, p3)) - - -@cython.returns(cython.complex) -@cython.locals(t=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex) -@cython.locals(_p1=cython.complex, _p2=cython.complex) -def cubic_approx_control(t, p0, p1, p2, p3): - """Approximate a cubic bezier curve with a quadratic one. - Returns the candidate control point.""" - _p1 = p0 + (p1 - p0) * 1.5 - _p2 = p3 + (p2 - p3) * 1.5 - return _p1 + (_p2 - _p1) * t - - -@cython.returns(cython.complex) -@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex) -@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double) -def calc_intersect(a, b, c, d): - """Calculate the intersection of ab and cd, given a, b, c, d.""" - - ab = b - a - cd = d - c - p = ab * 1j - try: - h = dot(p, a - c) / dot(p, cd) - except ZeroDivisionError: - return complex(NAN, NAN) - return c + cd * h - - -@cython.cfunc -@cython.returns(cython.int) -@cython.locals(tolerance=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex) -@cython.locals(mid=cython.complex, deriv3=cython.complex) -def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance): - """Returns True if the cubic Bezier p entirely lies within a distance - tolerance of origin, False otherwise. Assumes that p0 and p3 do fit - within tolerance of origin, and just checks the inside of the curve.""" - - # First check p2 then p1, as p2 has higher error early on. - if abs(p2) <= tolerance and abs(p1) <= tolerance: - return True - - # Split. - mid = (p0 + 3 * (p1 + p2) + p3) * .125 - if abs(mid) > tolerance: - return False - deriv3 = (p3 + p2 - p1 - p0) * .125 - return (cubic_farthest_fit_inside(p0, (p0+p1)*.5, mid-deriv3, mid, tolerance) and - cubic_farthest_fit_inside(mid, mid+deriv3, (p2+p3)*.5, p3, tolerance)) - - -@cython.cfunc -@cython.locals(tolerance=cython.double, _2_3=cython.double) -@cython.locals(q1=cython.complex, c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex) -def cubic_approx_quadratic(cubic, tolerance, _2_3=2/3): - """Return the uniq quadratic approximating cubic that maintains - endpoint tangents if that is within tolerance, None otherwise.""" - # we define 2/3 as a keyword argument so that it will be evaluated only - # once but still in the scope of this function - - q1 = calc_intersect(*cubic) - if math.isnan(q1.imag): - return None - c0 = cubic[0] - c3 = cubic[3] - c1 = c0 + (q1 - c0) * _2_3 - c2 = c3 + (q1 - c3) * _2_3 - if not cubic_farthest_fit_inside(0, - c1 - cubic[1], - c2 - cubic[2], - 0, tolerance): - return None - return c0, q1, c3 - - -@cython.cfunc -@cython.locals(n=cython.int, tolerance=cython.double, _2_3=cython.double) -@cython.locals(i=cython.int) -@cython.locals(c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex) -@cython.locals(q0=cython.complex, q1=cython.complex, next_q1=cython.complex, q2=cython.complex, d1=cython.complex) -def cubic_approx_spline(cubic, n, tolerance, _2_3=2/3): - """Approximate a cubic bezier curve with a spline of n quadratics. - - Returns None if no quadratic approximation is found which lies entirely - within a distance `tolerance` from the original curve. - """ - # we define 2/3 as a keyword argument so that it will be evaluated only - # once but still in the scope of this function - - if n == 1: - return cubic_approx_quadratic(cubic, tolerance) - - cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n) - - # calculate the spline of quadratics and check errors at the same time. - next_cubic = next(cubics) - next_q1 = cubic_approx_control(0, *next_cubic) - q2 = cubic[0] - d1 = 0j - spline = [cubic[0], next_q1] - for i in range(1, n+1): - - # Current cubic to convert - c0, c1, c2, c3 = next_cubic - - # Current quadratic approximation of current cubic - q0 = q2 - q1 = next_q1 - if i < n: - next_cubic = next(cubics) - next_q1 = cubic_approx_control(i / (n-1), *next_cubic) - spline.append(next_q1) - q2 = (q1 + next_q1) * .5 - else: - q2 = c3 - - # End-point deltas - d0 = d1 - d1 = q2 - c3 - - if (abs(d1) > tolerance or - not cubic_farthest_fit_inside(d0, - q0 + (q1 - q0) * _2_3 - c1, - q2 + (q1 - q2) * _2_3 - c2, - d1, - tolerance)): - return None - spline.append(cubic[3]) - - return spline - - -@cython.locals(max_err=cython.double) -@cython.locals(n=cython.int) -def curve_to_quadratic(curve, max_err): - """Return a quadratic spline approximating this cubic bezier. - Raise 'ApproxNotFoundError' if no suitable approximation can be found - with the given parameters. - """ - - curve = [complex(*p) for p in curve] - - for n in range(1, MAX_N + 1): - spline = cubic_approx_spline(curve, n, max_err) - if spline is not None: - # done. go home - return [(s.real, s.imag) for s in spline] - - raise ApproxNotFoundError(curve) - - - -@cython.locals(l=cython.int, last_i=cython.int, i=cython.int) -def curves_to_quadratic(curves, max_errors): - """Return quadratic splines approximating these cubic beziers. - Raise 'ApproxNotFoundError' if no suitable approximation can be found - for all curves with the given parameters. - """ - - curves = [[complex(*p) for p in curve] for curve in curves] - assert len(max_errors) == len(curves) - - l = len(curves) - splines = [None] * l - last_i = i = 0 - n = 1 - while True: - spline = cubic_approx_spline(curves[i], n, max_errors[i]) - if spline is None: - if n == MAX_N: - break - n += 1 - last_i = i - continue - splines[i] = spline - i = (i + 1) % l - if i == last_i: - # done. go home - return [[(s.real, s.imag) for s in spline] for spline in splines] - - raise ApproxNotFoundError(curves) - - -if __name__ == '__main__': - import random - import timeit - - MAX_ERR = 5 - - def generate_curve(): - return [ - tuple(float(random.randint(0, 2048)) for coord in range(2)) - for point in range(4)] - - def setup_curve_to_quadratic(): - return generate_curve(), MAX_ERR - - def setup_curves_to_quadratic(): - num_curves = 3 - return ( - [generate_curve() for curve in range(num_curves)], - [MAX_ERR] * num_curves) - - def run_benchmark( - benchmark_module, module, function, setup_suffix='', repeat=5, number=1000): - setup_func = 'setup_' + function - if setup_suffix: - print('%s with %s:' % (function, setup_suffix), end='') - setup_func += '_' + setup_suffix - else: - print('%s:' % function, end='') - - def wrapper(function, setup_func): - function = globals()[function] - setup_func = globals()[setup_func] - def wrapped(): - return function(*setup_func()) - return wrapped - results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number) - print('\t%5.1fus' % (min(results) * 1000000. / number)) - - def main(): - run_benchmark('cu2qu.benchmark', 'cu2qu', 'curve_to_quadratic') - run_benchmark('cu2qu.benchmark', 'cu2qu', 'curves_to_quadratic') - - random.seed(1) - main() diff -Nru cu2qu-1.6.7/Lib/cu2qu/cython.py cu2qu-1.3.0/Lib/cu2qu/cython.py --- cu2qu-1.6.7/Lib/cu2qu/cython.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/cython.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,477 +0,0 @@ -""" This module is copied verbatim from the "Cython.Shadow" module: -https://github.com/cython/cython/blob/master/Cython/Shadow.py - -Cython is licensed under the Apache 2.0 Software License. -""" -# cython.* namespace for pure mode. -from __future__ import absolute_import - -__version__ = "0.29.14" - -try: - from __builtin__ import basestring -except ImportError: - basestring = str - - -# BEGIN shameless copy from Cython/minivect/minitypes.py - -class _ArrayType(object): - - is_array = True - subtypes = ['dtype'] - - def __init__(self, dtype, ndim, is_c_contig=False, is_f_contig=False, - inner_contig=False, broadcasting=None): - self.dtype = dtype - self.ndim = ndim - self.is_c_contig = is_c_contig - self.is_f_contig = is_f_contig - self.inner_contig = inner_contig or is_c_contig or is_f_contig - self.broadcasting = broadcasting - - def __repr__(self): - axes = [":"] * self.ndim - if self.is_c_contig: - axes[-1] = "::1" - elif self.is_f_contig: - axes[0] = "::1" - - return "%s[%s]" % (self.dtype, ", ".join(axes)) - - -def index_type(base_type, item): - """ - Support array type creation by slicing, e.g. double[:, :] specifies - a 2D strided array of doubles. The syntax is the same as for - Cython memoryviews. - """ - class InvalidTypeSpecification(Exception): - pass - - def verify_slice(s): - if s.start or s.stop or s.step not in (None, 1): - raise InvalidTypeSpecification( - "Only a step of 1 may be provided to indicate C or " - "Fortran contiguity") - - if isinstance(item, tuple): - step_idx = None - for idx, s in enumerate(item): - verify_slice(s) - if s.step and (step_idx or idx not in (0, len(item) - 1)): - raise InvalidTypeSpecification( - "Step may only be provided once, and only in the " - "first or last dimension.") - - if s.step == 1: - step_idx = idx - - return _ArrayType(base_type, len(item), - is_c_contig=step_idx == len(item) - 1, - is_f_contig=step_idx == 0) - elif isinstance(item, slice): - verify_slice(item) - return _ArrayType(base_type, 1, is_c_contig=bool(item.step)) - else: - # int[8] etc. - assert int(item) == item # array size must be a plain integer - array(base_type, item) - -# END shameless copy - - -compiled = False - -_Unspecified = object() - -# Function decorators - -def _empty_decorator(x): - return x - -def locals(**arg_types): - return _empty_decorator - -def test_assert_path_exists(*paths): - return _empty_decorator - -def test_fail_if_path_exists(*paths): - return _empty_decorator - -class _EmptyDecoratorAndManager(object): - def __call__(self, x): - return x - def __enter__(self): - pass - def __exit__(self, exc_type, exc_value, traceback): - pass - -class _Optimization(object): - pass - -cclass = ccall = cfunc = _EmptyDecoratorAndManager() - -returns = wraparound = boundscheck = initializedcheck = nonecheck = \ - embedsignature = cdivision = cdivision_warnings = \ - always_allows_keywords = profile = linetrace = infer_types = \ - unraisable_tracebacks = freelist = \ - lambda _: _EmptyDecoratorAndManager() - -exceptval = lambda _=None, check=True: _EmptyDecoratorAndManager() - -overflowcheck = lambda _: _EmptyDecoratorAndManager() -optimization = _Optimization() - -overflowcheck.fold = optimization.use_switch = \ - optimization.unpack_method_calls = lambda arg: _EmptyDecoratorAndManager() - -final = internal = type_version_tag = no_gc_clear = no_gc = _empty_decorator - - -_cython_inline = None -def inline(f, *args, **kwds): - if isinstance(f, basestring): - global _cython_inline - if _cython_inline is None: - from Cython.Build.Inline import cython_inline as _cython_inline - return _cython_inline(f, *args, **kwds) - else: - assert len(args) == len(kwds) == 0 - return f - - -def compile(f): - from Cython.Build.Inline import RuntimeCompiledFunction - return RuntimeCompiledFunction(f) - - -# Special functions - -def cdiv(a, b): - q = a / b - if q < 0: - q += 1 - return q - -def cmod(a, b): - r = a % b - if (a*b) < 0: - r -= b - return r - - -# Emulated language constructs - -def cast(type, *args, **kwargs): - kwargs.pop('typecheck', None) - assert not kwargs - if hasattr(type, '__call__'): - return type(*args) - else: - return args[0] - -def sizeof(arg): - return 1 - -def typeof(arg): - return arg.__class__.__name__ - # return type(arg) - -def address(arg): - return pointer(type(arg))([arg]) - -def declare(type=None, value=_Unspecified, **kwds): - if type not in (None, object) and hasattr(type, '__call__'): - if value is not _Unspecified: - return type(value) - else: - return type() - else: - return value - -class _nogil(object): - """Support for 'with nogil' statement and @nogil decorator. - """ - def __call__(self, x): - if callable(x): - # Used as function decorator => return the function unchanged. - return x - # Used as conditional context manager or to create an "@nogil(True/False)" decorator => keep going. - return self - - def __enter__(self): - pass - def __exit__(self, exc_class, exc, tb): - return exc_class is None - -nogil = _nogil() -gil = _nogil() -del _nogil - - -# Emulated types - -class CythonMetaType(type): - - def __getitem__(type, ix): - return array(type, ix) - -CythonTypeObject = CythonMetaType('CythonTypeObject', (object,), {}) - -class CythonType(CythonTypeObject): - - def _pointer(self, n=1): - for i in range(n): - self = pointer(self) - return self - -class PointerType(CythonType): - - def __init__(self, value=None): - if isinstance(value, (ArrayType, PointerType)): - self._items = [cast(self._basetype, a) for a in value._items] - elif isinstance(value, list): - self._items = [cast(self._basetype, a) for a in value] - elif value is None or value == 0: - self._items = [] - else: - raise ValueError - - def __getitem__(self, ix): - if ix < 0: - raise IndexError("negative indexing not allowed in C") - return self._items[ix] - - def __setitem__(self, ix, value): - if ix < 0: - raise IndexError("negative indexing not allowed in C") - self._items[ix] = cast(self._basetype, value) - - def __eq__(self, value): - if value is None and not self._items: - return True - elif type(self) != type(value): - return False - else: - return not self._items and not value._items - - def __repr__(self): - return "%s *" % (self._basetype,) - -class ArrayType(PointerType): - - def __init__(self): - self._items = [None] * self._n - - -class StructType(CythonType): - - def __init__(self, cast_from=_Unspecified, **data): - if cast_from is not _Unspecified: - # do cast - if len(data) > 0: - raise ValueError('Cannot accept keyword arguments when casting.') - if type(cast_from) is not type(self): - raise ValueError('Cannot cast from %s'%cast_from) - for key, value in cast_from.__dict__.items(): - setattr(self, key, value) - else: - for key, value in data.items(): - setattr(self, key, value) - - def __setattr__(self, key, value): - if key in self._members: - self.__dict__[key] = cast(self._members[key], value) - else: - raise AttributeError("Struct has no member '%s'" % key) - - -class UnionType(CythonType): - - def __init__(self, cast_from=_Unspecified, **data): - if cast_from is not _Unspecified: - # do type cast - if len(data) > 0: - raise ValueError('Cannot accept keyword arguments when casting.') - if isinstance(cast_from, dict): - datadict = cast_from - elif type(cast_from) is type(self): - datadict = cast_from.__dict__ - else: - raise ValueError('Cannot cast from %s'%cast_from) - else: - datadict = data - if len(datadict) > 1: - raise AttributeError("Union can only store one field at a time.") - for key, value in datadict.items(): - setattr(self, key, value) - - def __setattr__(self, key, value): - if key in '__dict__': - CythonType.__setattr__(self, key, value) - elif key in self._members: - self.__dict__ = {key: cast(self._members[key], value)} - else: - raise AttributeError("Union has no member '%s'" % key) - -def pointer(basetype): - class PointerInstance(PointerType): - _basetype = basetype - return PointerInstance - -def array(basetype, n): - class ArrayInstance(ArrayType): - _basetype = basetype - _n = n - return ArrayInstance - -def struct(**members): - class StructInstance(StructType): - _members = members - for key in members: - setattr(StructInstance, key, None) - return StructInstance - -def union(**members): - class UnionInstance(UnionType): - _members = members - for key in members: - setattr(UnionInstance, key, None) - return UnionInstance - -class typedef(CythonType): - - def __init__(self, type, name=None): - self._basetype = type - self.name = name - - def __call__(self, *arg): - value = cast(self._basetype, *arg) - return value - - def __repr__(self): - return self.name or str(self._basetype) - - __getitem__ = index_type - -class _FusedType(CythonType): - pass - - -def fused_type(*args): - if not args: - raise TypeError("Expected at least one type as argument") - - # Find the numeric type with biggest rank if all types are numeric - rank = -1 - for type in args: - if type not in (py_int, py_long, py_float, py_complex): - break - - if type_ordering.index(type) > rank: - result_type = type - else: - return result_type - - # Not a simple numeric type, return a fused type instance. The result - # isn't really meant to be used, as we can't keep track of the context in - # pure-mode. Casting won't do anything in this case. - return _FusedType() - - -def _specialized_from_args(signatures, args, kwargs): - "Perhaps this should be implemented in a TreeFragment in Cython code" - raise Exception("yet to be implemented") - - -py_int = typedef(int, "int") -try: - py_long = typedef(long, "long") -except NameError: # Py3 - py_long = typedef(int, "long") -py_float = typedef(float, "float") -py_complex = typedef(complex, "double complex") - - -# Predefined types - -int_types = ['char', 'short', 'Py_UNICODE', 'int', 'Py_UCS4', 'long', 'longlong', 'Py_ssize_t', 'size_t'] -float_types = ['longdouble', 'double', 'float'] -complex_types = ['longdoublecomplex', 'doublecomplex', 'floatcomplex', 'complex'] -other_types = ['bint', 'void', 'Py_tss_t'] - -to_repr = { - 'longlong': 'long long', - 'longdouble': 'long double', - 'longdoublecomplex': 'long double complex', - 'doublecomplex': 'double complex', - 'floatcomplex': 'float complex', -}.get - -gs = globals() - -# note: cannot simply name the unicode type here as 2to3 gets in the way and replaces it by str -try: - import __builtin__ as builtins -except ImportError: # Py3 - import builtins - -gs['unicode'] = typedef(getattr(builtins, 'unicode', str), 'unicode') -del builtins - -for name in int_types: - reprname = to_repr(name, name) - gs[name] = typedef(py_int, reprname) - if name not in ('Py_UNICODE', 'Py_UCS4') and not name.endswith('size_t'): - gs['u'+name] = typedef(py_int, "unsigned " + reprname) - gs['s'+name] = typedef(py_int, "signed " + reprname) - -for name in float_types: - gs[name] = typedef(py_float, to_repr(name, name)) - -for name in complex_types: - gs[name] = typedef(py_complex, to_repr(name, name)) - -bint = typedef(bool, "bint") -void = typedef(None, "void") -Py_tss_t = typedef(None, "Py_tss_t") - -for t in int_types + float_types + complex_types + other_types: - for i in range(1, 4): - gs["%s_%s" % ('p'*i, t)] = gs[t]._pointer(i) - -NULL = gs['p_void'](0) - -# looks like 'gs' has some users out there by now... -#del gs - -integral = floating = numeric = _FusedType() - -type_ordering = [py_int, py_long, py_float, py_complex] - -class CythonDotParallel(object): - """ - The cython.parallel module. - """ - - __all__ = ['parallel', 'prange', 'threadid'] - - def parallel(self, num_threads=None): - return nogil - - def prange(self, start=0, stop=None, step=1, nogil=False, schedule=None, chunksize=None, num_threads=None): - if stop is None: - stop = start - start = 0 - return range(start, stop, step) - - def threadid(self): - return 0 - - # def threadsavailable(self): - # return 1 - -import sys -sys.modules['cython.parallel'] = CythonDotParallel() -del sys diff -Nru cu2qu-1.6.7/Lib/cu2qu/errors.py cu2qu-1.3.0/Lib/cu2qu/errors.py --- cu2qu-1.6.7/Lib/cu2qu/errors.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/errors.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -from __future__ import print_function, absolute_import, division - - -class Error(Exception): - """Base Cu2Qu exception class for all other errors.""" - - -class ApproxNotFoundError(Error): - def __init__(self, curve): - message = "no approximation found: %s" % curve - super(Error, self).__init__(message) - self.curve = curve - - -class UnequalZipLengthsError(Error): - pass - - -class IncompatibleGlyphsError(Error): - def __init__(self, glyphs): - assert len(glyphs) > 1 - self.glyphs = glyphs - names = set(repr(g.name) for g in glyphs) - if len(names) > 1: - self.combined_name = "{%s}" % ", ".join(sorted(names)) - else: - self.combined_name = names.pop() - - def __repr__(self): - return "<%s %s>" % (type(self).__name__, self.combined_name) - - -class IncompatibleSegmentNumberError(IncompatibleGlyphsError): - def __str__(self): - return "Glyphs named %s have different number of segments" % ( - self.combined_name - ) - - -class IncompatibleSegmentTypesError(IncompatibleGlyphsError): - def __init__(self, glyphs, segments): - IncompatibleGlyphsError.__init__(self, glyphs) - self.segments = segments - - def __str__(self): - lines = [] - ndigits = len(str(max(self.segments))) - for i, tags in sorted(self.segments.items()): - lines.append( - "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags)) - ) - return "Glyphs named %s have incompatible segment types:\n %s" % ( - self.combined_name, - "\n ".join(lines), - ) - - -class IncompatibleFontsError(Error): - def __init__(self, glyph_errors): - self.glyph_errors = glyph_errors - - def __str__(self): - return "fonts contains incompatible glyphs: %s" % ( - ", ".join(repr(g) for g in sorted(self.glyph_errors.keys())) - ) diff -Nru cu2qu-1.6.7/Lib/cu2qu/__init__.py cu2qu-1.3.0/Lib/cu2qu/__init__.py --- cu2qu-1.6.7/Lib/cu2qu/__init__.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/__init__.py 2017-10-31 19:57:00.000000000 +0000 @@ -15,9 +15,253 @@ from __future__ import print_function, division, absolute_import -try: - from ._version import version as __version__ -except ImportError: - __version__ = "0.0.0+unknown" +__version__ = "1.3.0" + +__all__ = ['curve_to_quadratic', 'curves_to_quadratic'] + +MAX_N = 100 + + +class Cu2QuError(Exception): + pass + + +class ApproxNotFoundError(Cu2QuError): + def __init__(self, curve): + message = "no approximation found: %s" % curve + super(Cu2QuError, self).__init__(message) + self.curve = curve + + +def dot(v1, v2): + """Return the dot product of two vectors.""" + return (v1 * v2.conjugate()).real + + +def calc_cubic_points(a, b, c, d): + _1 = d + _2 = (c / 3.0) + d + _3 = (b + c) / 3.0 + _2 + _4 = a + d + c + b + return _1, _2, _3, _4 + + +def calc_cubic_parameters(p0, p1, p2, p3): + c = (p1 - p0) * 3.0 + b = (p2 - p1) * 3.0 - c + d = p0 + a = p3 - d - c - b + return a, b, c, d + + +def split_cubic_into_n_iter(p0, p1, p2, p3, n): + # Hand-coded special-cases + if n == 2: + return iter(split_cubic_into_two(p0, p1, p2, p3)) + if n == 3: + return iter(split_cubic_into_three(p0, p1, p2, p3)) + if n == 4: + a, b = split_cubic_into_two(p0, p1, p2, p3) + return iter(split_cubic_into_two(*a) + split_cubic_into_two(*b)) + if n == 6: + a, b = split_cubic_into_two(p0, p1, p2, p3) + return iter(split_cubic_into_three(*a) + split_cubic_into_three(*b)) + + return _split_cubic_into_n_gen(p0,p1,p2,p3,n) + + +def _split_cubic_into_n_gen(p0, p1, p2, p3, n): + a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3) + dt = 1 / n + delta_2 = dt * dt + delta_3 = dt * delta_2 + for i in range(n): + t1 = i * dt + t1_2 = t1 * t1 + # calc new a, b, c and d + a1 = a * delta_3 + b1 = (3*a*t1 + b) * delta_2 + c1 = (2*b*t1 + c + 3*a*t1_2) * dt + d1 = a*t1*t1_2 + b*t1_2 + c*t1 + d + yield calc_cubic_points(a1, b1, c1, d1) + + +def split_cubic_into_two(p0, p1, p2, p3): + mid = (p0 + 3 * (p1 + p2) + p3) * .125 + deriv3 = (p3 + p2 - p1 - p0) * .125 + return ((p0, (p0 + p1) * .5, mid - deriv3, mid), + (mid, mid + deriv3, (p2 + p3) * .5, p3)) + + +def split_cubic_into_three(p0, p1, p2, p3, _27=1/27): + # we define 1/27 as a keyword argument so that it will be evaluated only + # once but still in the scope of this function + mid1 = (8*p0 + 12*p1 + 6*p2 + p3) * _27 + deriv1 = (p3 + 3*p2 - 4*p0) * _27 + mid2 = (p0 + 6*p1 + 12*p2 + 8*p3) * _27 + deriv2 = (4*p3 - 3*p1 - p0) * _27 + return ((p0, (2*p0 + p1) / 3, mid1 - deriv1, mid1), + (mid1, mid1 + deriv1, mid2 - deriv2, mid2), + (mid2, mid2 + deriv2, (p2 + 2*p3) / 3, p3)) + + +def cubic_approx_control(p, t): + """Approximate a cubic bezier curve with a quadratic one. + Returns the candidate control point.""" + + p1 = p[0] + (p[1] - p[0]) * 1.5 + p2 = p[3] + (p[2] - p[3]) * 1.5 + return p1 + (p2 - p1) * t + + +def calc_intersect(a, b, c, d): + """Calculate the intersection of ab and cd, given a, b, c, d.""" + + ab = b - a + cd = d - c + p = ab * 1j + try: + h = dot(p, a - c) / dot(p, cd) + except ZeroDivisionError: + return None + return c + cd * h + + +def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance): + """Returns True if the cubic Bezier p entirely lies within a distance + tolerance of origin, False otherwise. Assumes that p0 and p3 do fit + within tolerance of origin, and just checks the inside of the curve.""" + + # First check p2 then p1, as p2 has higher error early on. + if abs(p2) <= tolerance and abs(p1) <= tolerance: + return True + + # Split. + mid = (p0 + 3 * (p1 + p2) + p3) * .125 + if abs(mid) > tolerance: + return False + deriv3 = (p3 + p2 - p1 - p0) * .125 + return (cubic_farthest_fit_inside(p0, (p0+p1)*.5, mid-deriv3, mid, tolerance) and + cubic_farthest_fit_inside(mid, mid+deriv3, (p2+p3)*.5, p3, tolerance)) + + +def cubic_approx_quadratic(cubic, tolerance, _2_3=2/3): + """Return the uniq quadratic approximating cubic that maintains + endpoint tangents if that is within tolerance, None otherwise.""" + # we define 2/3 as a keyword argument so that it will be evaluated only + # once but still in the scope of this function + + q1 = calc_intersect(*cubic) + if q1 is None: + return None + c0 = cubic[0] + c3 = cubic[3] + c1 = c0 + (q1 - c0) * _2_3 + c2 = c3 + (q1 - c3) * _2_3 + if not cubic_farthest_fit_inside(0, + c1 - cubic[1], + c2 - cubic[2], + 0, tolerance): + return None + return c0, q1, c3 + + +def cubic_approx_spline(cubic, n, tolerance, _2_3=2/3): + """Approximate a cubic bezier curve with a spline of n quadratics. + + Returns None if no quadratic approximation is found which lies entirely + within a distance `tolerance` from the original curve. + """ + # we define 2/3 as a keyword argument so that it will be evaluated only + # once but still in the scope of this function + + if n == 1: + return cubic_approx_quadratic(cubic, tolerance) + + cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n) + + # calculate the spline of quadratics and check errors at the same time. + next_cubic = next(cubics) + next_q1 = cubic_approx_control(next_cubic, 0) + q2 = cubic[0] + d1 = 0j + spline = [cubic[0], next_q1] + for i in range(1, n+1): + + # Current cubic to convert + c0, c1, c2, c3 = next_cubic + + # Current quadratic approximation of current cubic + q0 = q2 + q1 = next_q1 + if i < n: + next_cubic = next(cubics) + next_q1 = cubic_approx_control(next_cubic, i / (n-1)) + spline.append(next_q1) + q2 = (q1 + next_q1) * .5 + else: + q2 = c3 + + # End-point deltas + d0 = d1 + d1 = q2 - c3 + + if (abs(d1) > tolerance or + not cubic_farthest_fit_inside(d0, + q0 + (q1 - q0) * _2_3 - c1, + q2 + (q1 - q2) * _2_3 - c2, + d1, + tolerance)): + return None + spline.append(cubic[3]) + + return spline + + +def curve_to_quadratic(curve, max_err): + """Return a quadratic spline approximating this cubic bezier. + Raise 'ApproxNotFoundError' if no suitable approximation can be found + with the given parameters. + """ + + curve = [complex(*p) for p in curve] + + for n in range(1, MAX_N + 1): + spline = cubic_approx_spline(curve, n, max_err) + if spline is not None: + # done. go home + return [(s.real, s.imag) for s in spline] + + raise ApproxNotFoundError(curve) + + + +def curves_to_quadratic(curves, max_errors): + """Return quadratic splines approximating these cubic beziers. + Raise 'ApproxNotFoundError' if no suitable approximation can be found + for all curves with the given parameters. + """ + + curves = [[complex(*p) for p in curve] for curve in curves] + assert len(max_errors) == len(curves) + + l = len(curves) + splines = [None] * l + last_i = i = 0 + n = 1 + while True: + spline = cubic_approx_spline(curves[i], n, max_errors[i]) + if spline is None: + if n == MAX_N: + break + n += 1 + last_i = i + continue + splines[i] = spline + i = (i + 1) % l + if i == last_i: + # done. go home + return [[(s.real, s.imag) for s in spline] for spline in splines] + + raise ApproxNotFoundError(curves) -from .cu2qu import * diff -Nru cu2qu-1.6.7/Lib/cu2qu/__main__.py cu2qu-1.3.0/Lib/cu2qu/__main__.py --- cu2qu-1.6.7/Lib/cu2qu/__main__.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/__main__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -import sys -from cu2qu.cli import main - - -if __name__ == "__main__": - sys.exit(main()) diff -Nru cu2qu-1.6.7/Lib/cu2qu/pens.py cu2qu-1.3.0/Lib/cu2qu/pens.py --- cu2qu-1.6.7/Lib/cu2qu/pens.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/pens.py 2017-10-31 19:57:00.000000000 +0000 @@ -2,8 +2,8 @@ from cu2qu import curve_to_quadratic from fontTools.pens.basePen import AbstractPen, decomposeSuperBezierSegment from fontTools.pens.reverseContourPen import ReverseContourPen -from fontTools.pens.pointPen import BasePointToSegmentPen -from fontTools.pens.pointPen import ReverseContourPointPen +from ufoLib.pointPen import BasePointToSegmentPen +from ufoLib.pointPen import ReverseContourPointPen class Cu2QuPen(AbstractPen): @@ -220,13 +220,7 @@ for (pt, smooth, name, kwargs) in offcurves: pen.addPoint(pt, None, smooth, name, **kwargs) pt, smooth, name, kwargs = points[-1] - if pt is None: - # special quadratic contour with no on-curve points: - # we need to skip the "None" point. See also the Pen - # protocol's qCurveTo() method and fontTools.pens.basePen - pass - else: - pen.addPoint(pt, segment_type, smooth, name, **kwargs) + pen.addPoint(pt, segment_type, smooth, name, **kwargs) else: # 'curve' segments must have been converted to 'qcurve' by now raise AssertionError( diff -Nru cu2qu-1.6.7/Lib/cu2qu/ufo.py cu2qu-1.3.0/Lib/cu2qu/ufo.py --- cu2qu-1.6.7/Lib/cu2qu/ufo.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/Lib/cu2qu/ufo.py 2017-10-31 19:57:00.000000000 +0000 @@ -28,24 +28,27 @@ import logging from fontTools.pens.basePen import AbstractPen -from fontTools.pens.pointPen import PointToSegmentPen -from fontTools.pens.reverseContourPen import ReverseContourPen from cu2qu import curves_to_quadratic -from cu2qu.errors import ( - UnequalZipLengthsError, IncompatibleSegmentNumberError, - IncompatibleSegmentTypesError, IncompatibleGlyphsError, - IncompatibleFontsError) - +from cu2qu.pens import ReverseContourPen __all__ = ['fonts_to_quadratic', 'font_to_quadratic'] DEFAULT_MAX_ERR = 0.001 -CURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type" logger = logging.getLogger(__name__) +class IncompatibleGlyphsError(ValueError): + + def __str__(self): + return ", ".join(set(repr(glyph.name) for glyph in self.args)) + + +class UnequalZipLengthsError(ValueError): + pass + + _zip = zip def zip(*args): """Ensure each argument to zip has the same length. Also make sure a list is @@ -99,18 +102,7 @@ """Get a glyph's segments as extracted by GetSegmentsPen.""" pen = GetSegmentsPen() - # glyph.draw(pen) - # We can't simply draw the glyph with the pen, but we must initialize the - # PointToSegmentPen explicitly with outputImpliedClosingLine=True. - # By default PointToSegmentPen does not outputImpliedClosingLine -- unless - # last and first point on closed contour are duplicated. Because we are - # converting multiple glyphs at the same time, we want to make sure - # this function returns the same number of segments, whether or not - # the last and first point overlap. - # https://github.com/googlefonts/fontmake/issues/572 - # https://github.com/fonttools/fonttools/pull/1720 - pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True) - glyph.drawPoints(pointPen) + glyph.draw(pen) return pen.segments @@ -126,8 +118,6 @@ pen.moveTo(*args) elif tag == 'line': pen.lineTo(*args) - elif tag == 'curve': - pen.curveTo(*args[1:]) elif tag == 'qcurve': pen.qCurveTo(*args[1:]) elif tag == 'close': @@ -163,7 +153,7 @@ try: segments_by_location = zip(*[_get_segments(g) for g in glyphs]) except UnequalZipLengthsError: - raise IncompatibleSegmentNumberError(glyphs) + raise IncompatibleGlyphsError(*glyphs) if not any(segments_by_location): return False @@ -171,12 +161,11 @@ glyphs_modified = reverse_direction new_segments_by_location = [] - incompatible = {} - for i, segments in enumerate(segments_by_location): + for segments in segments_by_location: tag = segments[0][0] if not all(s[0] == tag for s in segments[1:]): - incompatible[i] = [s[0] for s in segments] - elif tag == 'curve': + raise IncompatibleGlyphsError(*glyphs) + if tag == 'curve': segments = _segments_to_quadratic(segments, max_err, stats) glyphs_modified = True new_segments_by_location.append(segments) @@ -186,8 +175,6 @@ for glyph, new_segments in zip(glyphs, new_segments_by_glyph): _set_segments(glyph, new_segments, reverse_direction) - if incompatible: - raise IncompatibleSegmentTypesError(glyphs, segments=incompatible) return glyphs_modified @@ -221,7 +208,7 @@ def fonts_to_quadratic( fonts, max_err_em=None, max_err=None, reverse_direction=False, - stats=None, dump_stats=False, remember_curve_type=True): + stats=None, dump_stats=False): """Convert the curves of a collection of fonts to quadratic. All curves will be converted to quadratic at once, ensuring interpolation @@ -230,30 +217,10 @@ Return True if fonts were modified, else return False. - By default, cu2qu stores the curve type in the fonts' lib, under a private - key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert - them again if the curve type is already set to "quadratic". - Setting 'remember_curve_type' to False disables this optimization. - - Raises IncompatibleFontsError if same-named glyphs from different fonts + Raises IncompatibleGlyphsError if same-named glyphs from different fonts have non-interpolatable outlines. """ - if remember_curve_type: - curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts} - if len(curve_types) == 1: - curve_type = next(iter(curve_types)) - if curve_type == "quadratic": - logger.info("Curves already converted to quadratic") - return False - elif curve_type == "cubic": - pass # keep converting - else: - raise NotImplementedError(curve_type) - elif len(curve_types) > 1: - # going to crash later if they do differ - logger.warning("fonts may contain different curve types") - if stats is None: stats = {} @@ -276,7 +243,6 @@ max_errors = [f.info.unitsPerEm * max_err_em for f in fonts] modified = False - glyph_errors = {} for name in set().union(*(f.keys() for f in fonts)): glyphs = [] cur_max_errors = [] @@ -284,27 +250,13 @@ if name in font: glyphs.append(font[name]) cur_max_errors.append(error) - try: - modified |= _glyphs_to_quadratic( - glyphs, cur_max_errors, reverse_direction, stats) - except IncompatibleGlyphsError as exc: - logger.error(exc) - glyph_errors[name] = exc - - if glyph_errors: - raise IncompatibleFontsError(glyph_errors) + modified |= _glyphs_to_quadratic( + glyphs, cur_max_errors, reverse_direction, stats) if modified and dump_stats: spline_lengths = sorted(stats.keys()) logger.info('New spline lengths: %s' % (', '.join( '%s: %d' % (l, stats[l]) for l in spline_lengths))) - - if remember_curve_type: - for font in fonts: - curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic") - if curve_type != "quadratic": - font.lib[CURVE_TYPE_LIB_KEY] = "quadratic" - modified = True return modified diff -Nru cu2qu-1.6.7/pyproject.toml cu2qu-1.3.0/pyproject.toml --- cu2qu-1.6.7/pyproject.toml 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/pyproject.toml 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -[build-system] -requires = [ - "setuptools", - "wheel", - "setuptools_scm", - "cython", -] -build-backend = "setuptools.build_meta" diff -Nru cu2qu-1.6.7/README.rst cu2qu-1.3.0/README.rst --- cu2qu-1.6.7/README.rst 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/README.rst 2017-10-31 19:57:00.000000000 +0000 @@ -58,65 +58,9 @@ fonts_to_quadratic([font], stats=stats) # "stats" will report combined statistics for both fonts -The library also provides a command-line script also named ``cu2qu``. -Check its ``--help`` to see all the options. - -Installation ------------- - -You can install/upgrade cu2qu using pip, like any other Python package. - -.. code:: sh - - $ pip install --upgrade cu2qu - -This will download the latest stable version available from the Python -Package Index (PyPI). - -If you wish to modify the sources in-place, you can clone the git repository -from Github and install in ``--editable`` (or ``-e``) mode: - -.. code:: sh - - $ git clone https://github.com/googlefonts/cu2qu - $ cd cu2qu - $ pip install --editable . - -Optionally, you can build an optimized version of cu2qu which uses Cython_ -to compile Python to C. The extension module thus created is *more than -twice as fast* than its pure-Python equivalent. - -When installing cu2qu from PyPI using pip, as long as you have a C compiler -available, the cu2qu setup script will automatically attempt to build a -C/Python extension module. If the compilation fails for any reasons, an error -is printed and cu2qu will be installed as pure-Python, without the optimized -extension. - -If you have cloned the git repository, the C source files are not present and -need to be regenerated. To do that, you need to install the latest Cython -(as usual, ``pip install -U cython``), and then use the global option -``--with-cython`` when invoking the ``setup.py`` script. You can also export -a ``CU2QU_WITH_CYTHON=1`` environment variable if you prefer. - -For example, to build the cu2qu extension module in-place (i.e. in the same -source directory): - -.. code:: sh - - $ python setup.py --with-cython build_ext --inplace - -You can also pass ``--global-option`` when installing with pip from a local -source checkout, like so: - -.. code:: sh - - $ pip install --global-option="--with-cython" -e . - - -.. _Cython: https://github.com/cython/cython -.. |Build Status| image:: https://travis-ci.org/googlefonts/cu2qu.svg - :target: https://travis-ci.org/googlefonts/cu2qu +.. |Build Status| image:: https://travis-ci.org/googlei18n/cu2qu.svg + :target: https://travis-ci.org/googlei18n/cu2qu .. |PyPI Version| image:: https://img.shields.io/pypi/v/cu2qu.svg :target: https://pypi.org/project/cu2qu/ -.. |Coverage| image:: https://codecov.io/gh/googlefonts/cu2qu/branch/master/graph/badge.svg - :target: https://codecov.io/gh/googlefonts/cu2qu +.. |Coverage| image:: https://codecov.io/gh/googlei18n/cu2qu/branch/master/graph/badge.svg + :target: https://codecov.io/gh/googlei18n/cu2qu diff -Nru cu2qu-1.6.7/requirements.txt cu2qu-1.3.0/requirements.txt --- cu2qu-1.6.7/requirements.txt 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/requirements.txt 2017-10-31 19:57:00.000000000 +0000 @@ -1,2 +1,2 @@ -fonttools[ufo]==3.32.0 -defcon==0.6.0 +fonttools==3.18.0 +ufoLib==2.1.1 diff -Nru cu2qu-1.6.7/setup.cfg cu2qu-1.3.0/setup.cfg --- cu2qu-1.6.7/setup.cfg 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/setup.cfg 2017-10-31 19:57:00.000000000 +0000 @@ -1,4 +1,30 @@ -[bdist_wheel] +[bumpversion] +current_version = 1.3.0 +commit = True +tag = False +tag_name = v{new_version} +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}.{release}{dev} + {major}.{minor}.{patch} + +[bumpversion:part:release] +optional_value = final +values = + dev + final + +[bumpversion:part:dev] + +[bumpversion:file:Lib/cu2qu/__init__.py] +search = __version__ = "{current_version}" +replace = __version__ = "{new_version}" + +[bumpversion:file:setup.py] +search = version="{current_version}" +replace = version="{new_version}" + +[wheel] universal = 1 [sdist] @@ -12,17 +38,16 @@ [tool:pytest] minversion = 3.0 -testpaths = +testpaths = tests -python_files = +python_files = *_test.py -python_classes = +python_classes = *Test -addopts = +addopts = -s -v -r a --doctest-modules --doctest-ignore-import-errors -filterwarnings: - ignore:.*bytes:DeprecationWarning:fs.base + diff -Nru cu2qu-1.6.7/setup.py cu2qu-1.3.0/setup.py --- cu2qu-1.6.7/setup.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/setup.py 2017-10-31 19:57:00.000000000 +0000 @@ -13,217 +13,172 @@ # limitations under the License. -from setuptools import setup, find_packages, Extension -from setuptools.command.build_ext import build_ext as _build_ext -from setuptools.command.sdist import sdist as _sdist -import pkg_resources -from distutils import log +from setuptools import setup, find_packages, Command import sys -import os -import re -from io import open +from distutils import log -needs_pytest = {'pytest', 'test'}.intersection(sys.argv) -pytest_runner = ['pytest_runner'] if needs_pytest else [] -needs_wheel = {'bdist_wheel'}.intersection(sys.argv) -wheel = ['wheel'] if needs_wheel else [] +class bump_version(Command): -# Check if minimum required Cython is available. -# For consistency, we require the same as our vendored Cython.Shadow module -cymod = "Lib/cu2qu/cython.py" -cython_version_re = re.compile('__version__ = ["\']([0-9][0-9\w\.]+)["\']') -with open(cymod, "r", encoding="utf-8") as fp: - for line in fp: - m = cython_version_re.match(line) - if m: - cython_min_version = m.group(1) - break - else: - sys.exit("error: failed to parse cython version in '%s'" % cymod) - -required_cython = "cython >= %s" % cython_min_version -try: - pkg_resources.require(required_cython) -except pkg_resources.ResolutionError: - has_cython = False -else: - has_cython = True - -# First, check if the CU2QU_WITH_CYTHON environment variable is set. -# Values "1", "true" or "yes" mean that Cython is required and will be used -# to regenerate the *.c sources from which the native extension is built; -# "0", "false" or "no" mean that Cython is not required and no extension -# module will be compiled (i.e. the wheel is pure-python and universal). -# If the variable is not set, then the pre-generated *.c sources that -# are included in the sdist package will be used to try build the extension. -# However, if any error occurs during compilation (e.g. the host -# machine doesn't have the required compiler toolchain installed), the -# installation proceeds without the compiled extensions, but will only have -# the pure-python module. -env_with_cython = os.environ.get("CU2QU_WITH_CYTHON") -with_cython = ( - True if env_with_cython in {"1", "true", "yes"} - else False if env_with_cython in {"0", "false", "no"} - else None -) + description = "increment the package version and commit the changes" + + user_options = [ + ("major", None, "bump the first digit, for incompatible API changes"), + ("minor", None, "bump the second digit, for new backward-compatible features"), + ("patch", None, "bump the third digit, for bug fixes (default)"), + ] + + def initialize_options(self): + self.minor = False + self.major = False + self.patch = False -# command line options --with-cython and --without-cython are also supported. -# They override the environment variable -opt_with_cython = {'--with-cython'}.intersection(sys.argv) -opt_without_cython = {'--without-cython'}.intersection(sys.argv) -if opt_with_cython and opt_without_cython: - sys.exit( - "error: the options '--with-cython' and '--without-cython' are " - "mutually exclusive" - ) -elif opt_with_cython: - sys.argv.remove("--with-cython") - with_cython = True -elif opt_without_cython: - sys.argv.remove("--without-cython") - with_cython = False - - -class cython_build_ext(_build_ext): - """Compile *.pyx source files to *.c using cythonize if Cython is - installed, else use the pre-generated *.c sources. + def finalize_options(self): + part = None + for attr in ("major", "minor", "patch"): + if getattr(self, attr, False): + if part is None: + part = attr + else: + from distutils.errors import DistutilsOptionError + raise DistutilsOptionError( + "version part options are mutually exclusive") + self.part = part or "patch" + + def bumpversion(self, part, **kwargs): + """ Run bumpversion.main() with the specified arguments. + """ + import bumpversion + + args = ['--verbose'] if self.verbose > 1 else [] + for k, v in kwargs.items(): + k = "--{}".format(k.replace("_", "-")) + is_bool = isinstance(v, bool) and v is True + args.extend([k] if is_bool else [k, str(v)]) + args.append(part) + + log.debug( + "$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) + + bumpversion.main(args) + + def run(self): + log.info("bumping '%s' version" % self.part) + self.bumpversion(self.part) + + +class release(bump_version): + """Drop the developmental release '.devN' suffix from the package version, + open the default text $EDITOR to write release notes, commit the changes + and generate a git tag. + + Release notes can also be set with the -m/--message option, or by reading + from standard input. """ + description = "tag a new release" + + user_options = [ + ("message=", 'm', "message containing the release notes"), + ] + + def initialize_options(self): + self.message = None + def finalize_options(self): - if with_cython: - if not has_cython: - from distutils.errors import DistutilsSetupError - - raise DistutilsSetupError( - "%s is required when using --with-cython" % required_cython - ) - - from Cython.Build import cythonize - - # optionally enable line tracing for test coverage support - linetrace = os.environ.get("CYTHON_TRACE") == "1" - - self.distribution.ext_modules[:] = cythonize( - self.distribution.ext_modules, - force=linetrace or self.force, - annotate=os.environ.get("CYTHON_ANNOTATE") == "1", - quiet=not self.verbose, - compiler_directives={ - "linetrace": linetrace, - "language_level": 3, - "embedsignature": True, - }, - ) - else: - # replace *.py/.pyx sources with their pre-generated *.c versions - for ext in self.distribution.ext_modules: - ext.sources = [re.sub("\.pyx?$", ".c", n) for n in ext.sources] - - _build_ext.finalize_options(self) - - def build_extensions(self): - if not has_cython: - log.info( - "%s is not installed. Pre-generated *.c sources will be " - "will be used to build the extensions." % required_cython - ) + import re - try: - _build_ext.build_extensions(self) - except Exception as e: - if with_cython: - raise - from distutils.errors import DistutilsModuleError - - # optional compilation failed: we delete 'ext_modules' and make sure - # the generated wheel is 'pure' - del self.distribution.ext_modules[:] - try: - bdist_wheel = self.get_finalized_command("bdist_wheel") - except DistutilsModuleError: - # 'bdist_wheel' command not available as wheel is not installed - pass + current_version = self.distribution.metadata.get_version() + if not re.search(r"\.dev[0-9]+", current_version): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError( + "current version (%s) has no '.devN' suffix.\n " + "Run 'setup.py bump_version' with any of " + "--major, --minor, --patch options" % current_version) + + message = self.message + if message is None: + if sys.stdin.isatty(): + # stdin is interactive, use editor to write release notes + message = self.edit_release_notes() else: - bdist_wheel.root_is_pure = True - log.error('error: building extensions failed: %s' % e) + # read release notes from stdin pipe + message = sys.stdin.read() - def get_source_files(self): - filenames = _build_ext.get_source_files(self) + if not message.strip(): + from distutils.errors import DistutilsSetupError + raise DistutilsSetupError("release notes message is empty") - # include pre-generated *.c sources inside sdist, but only if cython is - # installed (and hence they will be updated upon making the sdist) - if has_cython: - for ext in self.extensions: - filenames.extend( - [re.sub("\.pyx?$", ".c", n) for n in ext.sources] - ) - return filenames - - -class cython_sdist(_sdist): - """ Run 'cythonize' on *.pyx sources to ensure the *.c files included - in the source distribution are up-to-date. - """ + self.message = "v{new_version}\n\n%s" % message + + @staticmethod + def edit_release_notes(): + """Use the default text $EDITOR to write release notes. + If $EDITOR is not set, use 'nano'.""" + from tempfile import mkstemp + import os + import shlex + import subprocess + + text_editor = shlex.split(os.environ.get('EDITOR', 'nano')) + + fd, tmp = mkstemp(prefix='bumpversion-') + try: + os.close(fd) + with open(tmp, 'w') as f: + f.write("\n\n# Write release notes.\n" + "# Lines starting with '#' will be ignored.") + subprocess.check_call(text_editor + [tmp]) + with open(tmp, 'r') as f: + changes = "".join( + l for l in f.readlines() if not l.startswith('#')) + finally: + os.remove(tmp) + return changes def run(self): - if with_cython and not has_cython: - from distutils.errors import DistutilsSetupError + log.info("stripping developmental release suffix") + # drop '.dev0' suffix, commit with given message and create git tag + self.bumpversion("release", + tag=True, + message="Release {new_version}", + tag_message=self.message) - raise DistutilsSetupError( - "%s is required when creating sdist --with-cython" - % required_cython - ) - - if has_cython: - from Cython.Build import cythonize - - cythonize( - self.distribution.ext_modules, - force=True, # always regenerate *.c sources - quiet=not self.verbose, - compiler_directives={ - "language_level": 3, - "embedsignature": True - }, - ) - - _sdist.run(self) - - -# don't build extensions if user explicitly requested --without-cython -if with_cython is False: - extensions = [] -else: - extensions = [ - Extension("cu2qu.cu2qu", ["Lib/cu2qu/cu2qu.py"]), - ] + +needs_pytest = {'pytest', 'test'}.intersection(sys.argv) +pytest_runner = ['pytest_runner'] if needs_pytest else [] +needs_wheel = {'bdist_wheel'}.intersection(sys.argv) +wheel = ['wheel'] if needs_wheel else [] +needs_bump2version = {'release', 'bump_version'}.intersection(sys.argv) +bump2version = ['bump2version'] if needs_bump2version else [] with open('README.rst', 'r') as f: long_description = f.read() setup( name='cu2qu', - use_scm_version={"write_to": "Lib/cu2qu/_version.py"}, + version="1.3.0", description='Cubic-to-quadratic bezier curve conversion', author="James Godfrey-Kittle, Behdad Esfahbod", author_email="jamesgk@google.com", - url="https://github.com/googlefonts", + url="https://github.com/googlei18n", license="Apache License, Version 2.0", long_description=long_description, packages=find_packages('Lib'), package_dir={'': 'Lib'}, - ext_modules=extensions, include_package_data=True, - setup_requires=pytest_runner + wheel + ["setuptools_scm"], + setup_requires=pytest_runner + wheel + bump2version, tests_require=[ 'pytest>=2.8', ], install_requires=[ - "fonttools[ufo] >= 3.32.0", + "fonttools>=3.18.0", + "ufoLib>=2.1.1", ], - extras_require={"cli": ["defcon >= 0.6.0"]}, - entry_points={"console_scripts": ["cu2qu = cu2qu.cli:main [cli]"]}, + cmdclass={ + "release": release, + "bump_version": bump_version, + }, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', @@ -237,5 +192,4 @@ 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', 'Topic :: Software Development :: Libraries :: Python Modules', ], - cmdclass={"build_ext": cython_build_ext, "sdist": cython_sdist}, ) diff -Nru cu2qu-1.6.7/test-requirements.txt cu2qu-1.3.0/test-requirements.txt --- cu2qu-1.6.7/test-requirements.txt 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/test-requirements.txt 2017-10-31 19:57:00.000000000 +0000 @@ -1,2 +1,3 @@ coverage pytest +defcon==0.3.5 diff -Nru cu2qu-1.6.7/tests/cli_test.py cu2qu-1.3.0/tests/cli_test.py --- cu2qu-1.6.7/tests/cli_test.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tests/cli_test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -from __future__ import print_function, division, absolute_import - -import defcon - -from . import DATADIR -import pytest -import py - -from cu2qu.ufo import CURVE_TYPE_LIB_KEY -from cu2qu.cli import main - - -TEST_UFOS = [ - py.path.local(DATADIR).join("RobotoSubset-Regular.ufo"), - py.path.local(DATADIR).join("RobotoSubset-Bold.ufo"), -] - - -@pytest.fixture -def test_paths(tmpdir): - result = [] - for path in TEST_UFOS: - new_path = tmpdir / path.basename - path.copy(new_path) - result.append(new_path) - return result - - -class MainTest(object): - - @staticmethod - def run_main(*args): - main([str(p) for p in args if p]) - - def test_single_input_no_output(self, test_paths): - ufo_path = test_paths[0] - - self.run_main(ufo_path) - - font = defcon.Font(str(ufo_path)) - assert font.lib[CURVE_TYPE_LIB_KEY] == "quadratic" - - def test_single_input_output_file(self, tmpdir): - input_path = TEST_UFOS[0] - output_path = tmpdir / input_path.basename - self.run_main('-o', output_path, input_path) - - assert output_path.check(dir=1) - - def test_multiple_inputs_output_dir(self, tmpdir): - output_dir = tmpdir / "output_dir" - self.run_main('-d', output_dir, *TEST_UFOS) - - assert output_dir.check(dir=1) - outputs = set(p.basename for p in output_dir.listdir()) - assert "RobotoSubset-Regular.ufo" in outputs - assert "RobotoSubset-Bold.ufo" in outputs - - def test_interpolatable_inplace(self, test_paths): - self.run_main('-i', *test_paths) - self.run_main('-i', *test_paths) # idempotent - - @pytest.mark.parametrize( - "mode", ["", "-i"], ids=["normal", "interpolatable"]) - def test_copytree(self, mode, tmpdir): - output_dir = tmpdir / "output_dir" - self.run_main(mode, '-d', output_dir, *TEST_UFOS) - - output_dir_2 = tmpdir / "output_dir_2" - # no conversion when curves are already quadratic, just copy - self.run_main(mode, '-d', output_dir_2, *output_dir.listdir()) - # running again overwrites existing with the copy - self.run_main(mode, '-d', output_dir_2, *output_dir.listdir()) - - def test_multiprocessing(self, tmpdir, test_paths): - self.run_main(*(test_paths + ["-j"])) - - def test_keep_direction(self, test_paths): - self.run_main('--keep-direction', *test_paths) - - def test_conversion_error(self, test_paths): - self.run_main('--conversion-error', 0.002, *test_paths) - - def test_conversion_error_short(self, test_paths): - self.run_main('-e', 0.003, test_paths[0]) diff -Nru cu2qu-1.6.7/tests/__init__.py cu2qu-1.3.0/tests/__init__.py --- cu2qu-1.6.7/tests/__init__.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tests/__init__.py 2017-10-31 19:57:00.000000000 +0000 @@ -1,5 +1,5 @@ import os -from fontTools.ufoLib.glifLib import GlyphSet +from ufoLib.glifLib import GlyphSet import pkg_resources DATADIR = os.path.join(os.path.dirname(__file__), 'data') diff -Nru cu2qu-1.6.7/tests/pens_test.py cu2qu-1.3.0/tests/pens_test.py --- cu2qu-1.6.7/tests/pens_test.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tests/pens_test.py 2017-10-31 19:57:00.000000000 +0000 @@ -6,7 +6,6 @@ from .utils import DummyGlyph, DummyPointGlyph from .utils import DummyPen, DummyPointPen from fontTools.misc.loggingTools import CapturingLogHandler -from textwrap import dedent import logging @@ -18,8 +17,6 @@ PointPen test cases, plus some helper methods. """ - maxDiff = None - def diff(self, expected, actual): import difflib expected = str(self.Glyph(expected)).splitlines(True) @@ -39,7 +36,7 @@ def expect_glyph(self, source, expected): converted = self.convert_glyph(source) self.assertNotEqual(converted, source) - if not converted.approx(expected): + if converted != expected: print(self.diff(expected, converted)) self.fail("converted glyph is different from expected") @@ -346,32 +343,6 @@ self.assertEqual(new_segments[0][1][-1][0], (0, 0)) self.assertEqual(new_segments[-1][1][-1][0], (3, 3)) - def test_quad_no_oncurve(self): - """When passed a contour which has no on-curve points, the - Cu2QuPointPen will treat it as a special quadratic contour whose - first point has 'None' coordinates. - """ - self.maxDiff = None - pen = DummyPointPen() - quadpen = Cu2QuPointPen(pen, MAX_ERR) - quadpen.beginPath() - quadpen.addPoint((1, 1)) - quadpen.addPoint((2, 2)) - quadpen.addPoint((3, 3)) - quadpen.endPath() - - self.assertEqual( - str(pen), - dedent( - """\ - pen.beginPath() - pen.addPoint((1, 1), name=None, segmentType=None, smooth=False) - pen.addPoint((2, 2), name=None, segmentType=None, smooth=False) - pen.addPoint((3, 3), name=None, segmentType=None, smooth=False) - pen.endPath()""" - ) - ) - if __name__ == "__main__": unittest.main() diff -Nru cu2qu-1.6.7/tests/ufo_test.py cu2qu-1.3.0/tests/ufo_test.py --- cu2qu-1.6.7/tests/ufo_test.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tests/ufo_test.py 2017-10-31 19:57:00.000000000 +0000 @@ -9,12 +9,7 @@ glyphs_to_quadratic, glyph_to_quadratic, logger, - CURVE_TYPE_LIB_KEY, -) -from cu2qu.errors import ( - IncompatibleSegmentNumberError, - IncompatibleSegmentTypesError, - IncompatibleFontsError, + IncompatibleGlyphsError ) from . import DATADIR @@ -48,18 +43,6 @@ fonts_to_quadratic(fonts, dump_stats=True) assert captor.assertRegex("New spline lengths:") - def test_remember_curve_type(self, fonts): - fonts_to_quadratic(fonts, remember_curve_type=True) - assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "quadratic" - with CapturingLogHandler(logger, "INFO") as captor: - fonts_to_quadratic(fonts, remember_curve_type=True) - assert captor.assertRegex("already converted") - - def test_no_remember_curve_type(self, fonts): - assert CURVE_TYPE_LIB_KEY not in fonts[0].lib - fonts_to_quadratic(fonts, remember_curve_type=False) - assert CURVE_TYPE_LIB_KEY not in fonts[0].lib - def test_different_glyphsets(self, fonts): del fonts[0]['a'] assert 'a' not in fonts[0] @@ -133,49 +116,40 @@ reverse_direction=True) @pytest.mark.parametrize( - ["outlines", "exception", "message"], + "outlines", [ [ [ - [ - ('moveTo', ((0, 0),)), - ('curveTo', ((1, 1), (2, 2), (3, 3))), - ('curveTo', ((4, 4), (5, 5), (6, 6))), - ('closePath', ()), - ], - [ - ('moveTo', ((7, 7),)), - ('curveTo', ((8, 8), (9, 9), (10, 10))), - ('closePath', ()), - ] + ('moveTo', ((0, 0),)), + ('curveTo', ((1, 1), (2, 2), (3, 3))), + ('curveTo', ((4, 4), (5, 5), (6, 6))), + ('closePath', ()), ], - IncompatibleSegmentNumberError, - "have different number of segments", + [ + ('moveTo', ((7, 7),)), + ('curveTo', ((8, 8), (9, 9), (10, 10))), + ('closePath', ()), + ] ], [ [ - - [ - ('moveTo', ((0, 0),)), - ('curveTo', ((1, 1), (2, 2), (3, 3))), - ('closePath', ()), - ], - [ - ('moveTo', ((4, 4),)), - ('lineTo', ((5, 5),)), - ('closePath', ()), - ], + ('moveTo', ((0, 0),)), + ('curveTo', ((1, 1), (2, 2), (3, 3))), + ('closePath', ()), ], - IncompatibleSegmentTypesError, - "have incompatible segment types", - ], + [ + ('moveTo', ((4, 4),)), + ('lineTo', ((5, 5),)), + ('closePath', ()), + ], + ] ], ids=[ "unequal-length", "different-segment-types", ] ) - def test_incompatible_glyphs(self, outlines, exception, message): + def test_incompatible(self, outlines): glyphs = [] for i, outline in enumerate(outlines): glyph = Glyph() @@ -184,37 +158,9 @@ for operator, args in outline: getattr(pen, operator)(*args) glyphs.append(glyph) - with pytest.raises(exception) as excinfo: + with pytest.raises(IncompatibleGlyphsError) as excinfo: glyphs_to_quadratic(glyphs) - assert excinfo.match(message) - - def test_incompatible_fonts(self): - font1 = Font() - font1.info.unitsPerEm = 1000 - glyph1 = font1.newGlyph("a") - pen1 = glyph1.getPen() - for operator, args in [("moveTo", ((0, 0),)), - ("lineTo", ((1, 1),)), - ("endPath", ())]: - getattr(pen1, operator)(*args) - - font2 = Font() - font2.info.unitsPerEm = 1000 - glyph2 = font2.newGlyph("a") - pen2 = glyph2.getPen() - for operator, args in [("moveTo", ((0, 0),)), - ("curveTo", ((1, 1), (2, 2), (3, 3))), - ("endPath", ())]: - getattr(pen2, operator)(*args) - - with pytest.raises(IncompatibleFontsError) as excinfo: - fonts_to_quadratic([font1, font2]) - assert excinfo.match("fonts contains incompatible glyphs: 'a'") - - assert hasattr(excinfo.value, "glyph_errors") - error = excinfo.value.glyph_errors['a'] - assert isinstance(error, IncompatibleSegmentTypesError) - assert error.segments == {1: ["line", "curve"]} + assert excinfo.match("^'glyph[0-9]+'(, 'glyph[0-9]+')*$") def test_already_quadratic(self): glyph = Glyph() @@ -244,42 +190,3 @@ pen.closePath() assert glyph_to_quadratic(glyph) assert len(glyph.components) == 1 - - def test_overlapping_start_end_points(self): - # https://github.com/googlefonts/fontmake/issues/572 - glyph1 = Glyph() - pen = glyph1.getPointPen() - pen.beginPath() - pen.addPoint((0, 651), segmentType="line") - pen.addPoint((0, 101), segmentType="line") - pen.addPoint((0, 101), segmentType="line") - pen.addPoint((0, 651), segmentType="line") - pen.endPath() - - glyph2 = Glyph() - pen = glyph2.getPointPen() - pen.beginPath() - pen.addPoint((1, 651), segmentType="line") - pen.addPoint((2, 101), segmentType="line") - pen.addPoint((3, 101), segmentType="line") - pen.addPoint((4, 651), segmentType="line") - pen.endPath() - - glyphs = [glyph1, glyph2] - - assert glyphs_to_quadratic(glyphs, reverse_direction=True) - - assert [[(p.x, p.y) for p in glyph[0]] for glyph in glyphs] == [ - [ - (0, 651), - (0, 651), - (0, 101), - (0, 101), - ], - [ - (1, 651), - (4, 651), - (3, 101), - (2, 101) - ], - ] diff -Nru cu2qu-1.6.7/tests/utils.py cu2qu-1.3.0/tests/utils.py --- cu2qu-1.6.7/tests/utils.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tests/utils.py 2017-10-31 19:57:00.000000000 +0000 @@ -1,7 +1,6 @@ from __future__ import print_function, division, absolute_import from . import CUBIC_GLYPHS -from fontTools.pens.pointPen import PointToSegmentPen, SegmentToPointPen -from fontTools.misc.py23 import isclose +from ufoLib.pointPen import PointToSegmentPen, SegmentToPointPen import unittest @@ -51,7 +50,7 @@ self.commands.append(('endPath', tuple(), {})) def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs): - kwargs['segmentType'] = str(segmentType) if segmentType else None + kwargs['segmentType'] = segmentType kwargs['smooth'] = smooth kwargs['name'] = name self.commands.append(('addPoint', (pt,), kwargs)) @@ -112,37 +111,6 @@ """Return True if 'other' glyph's outline is different from self.""" return not (self == other) - def approx(self, other, rel_tol=1e-12): - if hasattr(other, 'outline'): - outline2 == other.outline - elif hasattr(other, 'draw'): - outline2 = self.__class__(other).outline - else: - raise TypeError(type(other).__name__) - outline1 = self.outline - if len(outline1) != len(outline2): - return False - for (cmd1, arg1, kwd1), (cmd2, arg2, kwd2) in zip(outline1, outline2): - if cmd1 != cmd2: - return False - if kwd1 != kwd2: - return False - if arg1: - if isinstance(arg1[0], tuple): - if not arg2 or not isinstance(arg2[0], tuple): - return False - for (x1, y1), (x2, y2) in zip(arg1, arg2): - if ( - not isclose(x1, x2, rel_tol=rel_tol) or - not isclose(y1, y2, rel_tol=rel_tol) - ): - return False - elif arg1 != arg2: - return False - elif arg2: - return False - return True - def __str__(self): """Return commands making up the glyph's outline as a string.""" return str(self._pen) @@ -211,12 +179,9 @@ for cmd, args, kwargs in commands: if args: if isinstance(args[0], tuple): - # cast float to int if there're no digits after decimal point, - # and round floats to 12 decimal digits (more than enough) - args = [ - tuple((int(v) if int(v) == v else round(v, 12)) for v in pt) - for pt in args - ] + # cast float to int if there're no digits after decimal point + args = [tuple((int(v) if int(v) == v else v) for v in pt) + for pt in args] args = ", ".join(repr(a) for a in args) if kwargs: kwargs = ", ".join("%s=%r" % (k, v) diff -Nru cu2qu-1.6.7/tools/update_cython_shadow.py cu2qu-1.3.0/tools/update_cython_shadow.py --- cu2qu-1.6.7/tools/update_cython_shadow.py 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tools/update_cython_shadow.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -""" Update the embedded Lib/cu2qu/cython.py module with the contents of -the latest cython repository. - -Usage: - $ python tools/update_cython_shadow.py 0.28.5 -""" - -import requests -import sys - - -header = b'''\ -""" This module is copied verbatim from the "Cython.Shadow" module: -https://github.com/cython/cython/blob/master/Cython/Shadow.py - -Cython is licensed under the Apache 2.0 Software License. -""" -''' - -try: - version = sys.argv[1] -except IndexError: - version = "master" - -CYTHON_SHADOW_URL = ( - "https://raw.githubusercontent.com/cython/cython/%s/Cython/Shadow.py" -) % version - -r = requests.get(CYTHON_SHADOW_URL, allow_redirects=True) -with open("Lib/cu2qu/cython.py", "wb") as f: - f.write(header) - f.write(r.content) diff -Nru cu2qu-1.6.7/tox.ini cu2qu-1.3.0/tox.ini --- cu2qu-1.6.7/tox.ini 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/tox.ini 2017-10-31 19:57:00.000000000 +0000 @@ -1,41 +1,18 @@ [tox] -envlist = py{27,37}-{cy,nocy}, htmlcov -package_name = cu2qu -; we skip tox's own sdist generation as we need to pass different environment -; variables for testing buiding with and without cython -skipsdist = true +envlist = py27, py36, htmlcov [testenv] -setenv = - nocy: CU2QU_WITH_CYTHON=0 - cy: CU2QU_WITH_CYTHON=1 - cy: CYTHON_TRACE=1 - cy: CYTHON_ANNOTATE=1 -; download the latest pip, setuptools and wheel when creating the venv -download = true deps = -rtest-requirements.txt -rrequirements.txt - cy: cython -changedir = {toxinidir} commands = - # create source distribution in a temp dir - python setup.py --quiet sdist --dist-dir {envtmpdir} - - # install from sdist - python -m pip install --no-build-isolation --ignore-installed --pre --no-deps --no-cache-dir --find-links {envtmpdir} {[tox]package_name} - - # ensure we are running the requested cu2qu version (compiled vs interpreted) - nocy: python -c "import sys, cu2qu.cu2qu; cu2qu.cu2qu.COMPILED and sys.exit(1)" - cy: python -c "import sys, cu2qu.cu2qu; cu2qu.cu2qu.COMPILED or sys.exit(1)" - - # run tests with code coverage enabled coverage run --parallel-mode -m pytest {posargs} [testenv:htmlcov] +basepython = python3.6 deps = coverage -changedir = {toxinidir} +skip_install = true commands = coverage combine coverage report @@ -46,52 +23,8 @@ deps = coverage codecov +skip_install = true ignore_outcome = true -changedir = {toxinidir} commands = coverage combine codecov --env TRAVIS_PYTHON_VERSION - -[testenv:update-cython] -deps = requests -changedir = {toxinidir} -commands = - python tools/update_cython_shadow.py {posargs} - -[testenv:sdist] -deps = - setuptools - cython -changedir = {toxinidir} -commands = - python -c 'import shutil; shutil.rmtree("dist", ignore_errors=True)' - python setup.py --with-cython sdist --dist-dir dist - -[testenv:pure-wheel] -deps = - {[testenv:sdist]deps} - pip - wheel -setenv = CU2QU_WITH_CYTHON=0 -changedir = {toxinidir} -commands = - {[testenv:sdist]commands} - pip wheel --pre --no-deps --no-cache-dir --wheel-dir dist --find-links dist \ - --no-binary {[tox]package_name} {[tox]package_name} - -[testenv:native-wheel] -deps = {[testenv:pure-wheel]deps} -setenv = CU2QU_WITH_CYTHON=1 -changedir = {toxinidir} -commands = {[testenv:pure-wheel]commands} - -; we only upload the source distribution to PyPI (for now) -[testenv:pypi] -deps = - {[testenv:sdist]deps} - twine -passenv = TWINE_USERNAME TWINE_PASSWORD -changedir = {toxinidir} -commands = - {[testenv:sdist]commands} - twine upload dist/*.zip diff -Nru cu2qu-1.6.7/.travis.yml cu2qu-1.3.0/.travis.yml --- cu2qu-1.6.7/.travis.yml 2020-02-07 13:23:43.000000000 +0000 +++ cu2qu-1.3.0/.travis.yml 2017-10-31 19:57:00.000000000 +0000 @@ -1,37 +1,29 @@ sudo: false language: python - -env: - global: - - TWINE_USERNAME="anthrotype" - - secure: uIlWYz63F0Y/pDZawW2mS5DolWghdIodM8VtJtGbyIYB3fL3/edwTG5z+FWKaNeRtNfTAGDMs0y2dF45IJ7ZqTzw4yotgHz0uzLqjGjQ1/MOu99tppoXA6wQocZvmrdZpUcRbvBzJQzpAUcjldPLAP200mV9cG8+LfODn8Di2eJ329Ts3aA140pjUF8791jRLHBhUTpxK5RDPn1Q7OlugjryS7yNVIfT1/DaNDXAu4OZ8oNkygioRcyZ9QiFLjv5yBZ7uHB4UXWxw89RYPyz4NfHwyzDR38X/A6vfP19w2V0kecAK5BVBUE+WI7d26XjQzxDuH6Z0phB3x6MFuCXrX/pdrvNr7hs5kTAdQ7R1YA6MH4lPa+7oXha1/j353StzDMUKByXGVHyLAv7Ct2RSXOHUM6hAB4T+JbyJp/YkPWh968GKpl1lwvziKTi7K1qpngbXCdYIYKJ78IbmDcxzmQ+3j+fsXt9+gArZW7ICLgWrs+Lr1FiJsBOKLmqigOSmqrjHa+ef+wjieFSgzCVIfr9zvibzCEtYeqkuJuDQcSBIS0JG/heOfBGQ6FIxSXzwvICyWpldmfP67nBjVOVzPcyEAT8w+45LOM0HPCm6+Xjn7mKstc7x9TD7dVjWeyKJzZuKSmuAFA4UtGGKDQ93YQlAY2SCW4irYsusj80LHU= - -matrix: - include: - - env: TOXENV=py27-nocy - python: 2.7 - - env: TOXENV=py36-nocy - python: 3.6 - - env: TOXENV=py27-cy - python: 2.7 - - env: - - TOXENV=py36-cy - - BUILD_DIST=true - python: 3.6 - +python: + - "2.7" + - "3.6" branches: only: - master - /^v\d+\.\d+.*$/ -install: pip install tox +install: pip install tox-travis script: tox -after_success: - - if [ -z "$TRAVIS_TAG" ]; then tox -e codecov; fi +after_success: tox -e codecov + +deploy: # deploy to PyPI on tags - - | - if [ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_REPO_SLUG" == "googlefonts/cu2qu" ] && [ "$BUILD_DIST" == true ]; then - tox -e pypi - fi + provider: pypi + server: https://upload.pypi.org/legacy/ + on: + repo: googlei18n/cu2qu + tags: true + all_branches: true + python: 3.6 + user: anthrotype + password: + secure: LdEsho2OyiHKOsqjdwa1s6LQNuzbiHJGpk7L+Qn6XV8bmzznI15Z5yaC4kO8vE0ZfE0dwTcdA4BrjUBxFZnmWZtGp/la9pcwUF5fX8LnKwRsw8oPHJZNvvi9IEgnIg68VUJ4X787+hJKilQKhyE+J3UKDrJ0on6UJjpTciO8Tsins80EMD5wB000VriCXiZ3wvaCm/yaXDeGKkb8Us3NWT8pshgZ2SpoQyIJ1pik7p4UtjcZM2tPKbCPkim17UCOYQ/0II4KmoT19JGceC0xWbD0cssZDM2rd0vIQ+OMQTD7fkoTE2pY9L3dLDyHamJCECq/ZX0rNgFUBylEJq1+gin+8g81vXsewzBZA5Zc4/D+ER0INdLF8LbLAZOqu40eMa2X6bu3w++Vo8dK0wZibVlrA3EnBKcvTePTFuXnlAPuE252lsq+zn1nO8SO6xfzvB4JF3iB7GO7dajnR+8C+m9ctO/Lx043+FoxH607N1E/WOsFvOCPXQhpeNJZHyPtA4no++O0fp3KcKkvvJrt5nVxs55AT+p5uAXvfOATwwjaZYivayeZj2ICeFhh3A2LciMOvaqqXAJ64zFmjnmgVYyJPc7Xh30cnsvdKOR5QR35gzAZfEAyBeYIckIyIwpF9N+ogtGMQvHSprkbf1Bm6Hpmhq/XTkNzW+SM8+3/cfI= + distributions: sdist bdist_wheel