From 7de6799fd482434c6d752d22bceb2d139931b42b Mon Sep 17 00:00:00 2001 From: Haibo Huang Date: Fri, 4 Dec 2020 18:55:08 -0800 Subject: Upgrade fonttools to 4.18.0 Test: make Change-Id: I680f435ae3589363be8363d2ba6d2e48e951d71a --- .appveyor.yml | 56 - .gitattributes | 11 + .github/workflows/publish.yml | 31 + .github/workflows/test.yml | 86 + .gitignore | 60 +- .pyup.yml | 4 + .readthedocs.yml | 29 + .travis.yml | 90 - .travis/after_success.sh | 16 - .travis/before_install.sh | 6 - .travis/install.sh | 30 - .travis/run.sh | 20 - CODE_OF_CONDUCT.md | 76 + CONTRIBUTING.md | 26 + Doc/docs-requirements.txt | 2 +- Lib/fontTools/__init__.py | 2 +- Lib/fontTools/colorLib/builder.py | 32 + Lib/fontTools/feaLib/builder.py | 9 +- Lib/fontTools/feaLib/lookupDebugInfo.py | 1 + Lib/fontTools/otlLib/builder.py | 216 +- Lib/fontTools/otlLib/builder.py.sketch | 105 + Lib/fontTools/pens/momentsPen.py | 9 +- Lib/fontTools/pens/quartzPen.py | 46 + Lib/fontTools/subset/__init__.py | 23 +- Lib/fontTools/ttLib/tables/otData.py | 17 + Lib/fontTools/ttLib/tables/otTables.py | 4 +- Lib/fontTools/ttLib/woff2.py | 5 +- Lib/fontTools/ufoLib/__init__.py | 34 +- Lib/fontTools/ufoLib/glifLib.py | 6 + Lib/fontTools/varLib/interpolatable.py | 518 +++-- Lib/fonttools.egg-info/PKG-INFO | 2106 -------------------- Lib/fonttools.egg-info/SOURCES.txt | 2093 ------------------- Lib/fonttools.egg-info/dependency_links.txt | 1 - Lib/fonttools.egg-info/entry_points.txt | 6 - Lib/fonttools.egg-info/requires.txt | 68 - Lib/fonttools.egg-info/top_level.txt | 1 - METADATA | 8 +- NEWS.rst | 21 + PKG-INFO | 2106 -------------------- README.rst | 25 +- Snippets/fontTools | 1 + Snippets/name-viewer.ipynb | 117 ++ Tests/colorLib/builder_test.py | 40 +- Tests/cu2qu/data/curves.json | 1 + Tests/feaLib/builder_test.py | 3 +- Tests/feaLib/data/ChainPosSubtable.ttx | 36 +- Tests/feaLib/data/GPOS_8.ttx | 68 +- Tests/feaLib/data/GSUB_5_formats.fea | 20 + Tests/feaLib/data/GSUB_5_formats.ttx | 197 ++ Tests/feaLib/data/GSUB_6.ttx | 52 +- Tests/feaLib/data/GSUB_6_formats.fea | 20 + Tests/feaLib/data/GSUB_6_formats.ttx | 256 +++ .../data/ZeroValue_ChainSinglePos_horizontal.ttx | 69 +- .../data/ZeroValue_ChainSinglePos_vertical.ttx | 69 +- Tests/feaLib/data/bug512.ttx | 83 +- Tests/feaLib/data/spec5f_ii_3.ttx | 212 +- Tests/pens/cocoaPen_test.py | 59 + Tests/pens/quartzPen_test.py | 79 + Tests/subset/data/expect_layout_scripts.ttx | 129 ++ Tests/subset/data/layout_scripts.ttx | 997 +++++++++ Tests/subset/subset_test.py | 10 + Tests/ttLib/tables/C_O_L_R_test.py | 396 ++-- Tests/ttLib/woff2_test.py | 5 +- Tests/ttx/ttx_test.py | 5 +- Tests/ufoLib/UFO3_test.py | 20 + Tests/ufoLib/glifLib_test.py | 10 + Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz | Bin 0 -> 10577 bytes .../test_results/InterpolateLayoutGPOS_7_diff.ttx | 38 +- .../test_results/InterpolateLayoutGPOS_7_same.ttx | 38 +- Tests/varLib/varLib_test.py | 53 +- mypy.ini | 21 + requirements.txt | 1 + setup.cfg | 7 +- setup.py | 6 +- tox.ini | 2 +- 75 files changed, 3787 insertions(+), 7338 deletions(-) delete mode 100644 .appveyor.yml create mode 100644 .gitattributes create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml create mode 100644 .pyup.yml create mode 100644 .readthedocs.yml delete mode 100644 .travis.yml delete mode 100755 .travis/after_success.sh delete mode 100755 .travis/before_install.sh delete mode 100755 .travis/install.sh delete mode 100755 .travis/run.sh create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Lib/fontTools/otlLib/builder.py.sketch create mode 100644 Lib/fontTools/pens/quartzPen.py delete mode 100644 Lib/fonttools.egg-info/PKG-INFO delete mode 100644 Lib/fonttools.egg-info/SOURCES.txt delete mode 100644 Lib/fonttools.egg-info/dependency_links.txt delete mode 100644 Lib/fonttools.egg-info/entry_points.txt delete mode 100644 Lib/fonttools.egg-info/requires.txt delete mode 100644 Lib/fonttools.egg-info/top_level.txt delete mode 100644 PKG-INFO create mode 100755 Snippets/fontTools create mode 100644 Snippets/name-viewer.ipynb create mode 100644 Tests/cu2qu/data/curves.json create mode 100644 Tests/feaLib/data/GSUB_5_formats.fea create mode 100644 Tests/feaLib/data/GSUB_5_formats.ttx create mode 100644 Tests/feaLib/data/GSUB_6_formats.fea create mode 100644 Tests/feaLib/data/GSUB_6_formats.ttx create mode 100644 Tests/pens/cocoaPen_test.py create mode 100644 Tests/pens/quartzPen_test.py create mode 100644 Tests/subset/data/expect_layout_scripts.ttx create mode 100644 Tests/subset/data/layout_scripts.ttx create mode 100644 Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz create mode 100644 mypy.ini diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index be60ab52..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,56 +0,0 @@ -environment: - matrix: - - JOB: "3.7 64-bit" - PYTHON_HOME: "C:\\Python37-x64" - - JOB: "3.8 64-bit" - PYTHON_HOME: "C:\\Python38-x64" - -branches: - only: - - master - # We want to build wip/* branches since these are not usually used for PRs - - /^wip\/.*$/ - # We want to build version tags as well. - - /^\d+\.\d+.*$/ - -install: - # If there is a newer build queued for the same PR, cancel this one. - # The AppVeyor 'rollout builds' option is supposed to serve the same - # purpose but it is problematic because it tends to cancel builds pushed - # directly to master instead of just PR builds (or the converse). - # credits: JuliaLang developers. - - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` - https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` - Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` - throw "There are newer queued builds for this pull request, failing early." } - - # Prepend Python to the PATH of this build - - "SET PATH=%PYTHON_HOME%;%PYTHON_HOME%\\Scripts;%PATH%" - - # check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" - - # upgrade pip and setuptools to avoid out-of-date warnings - - "python -m pip install --disable-pip-version-check --user --upgrade pip setuptools virtualenv" - - # install the dependencies to run the tests - - "python -m pip install tox" - -build: false - -test_script: - # run tests with the current 'python' in %PATH%, and measure test coverage - - "tox -e py-cov" - -after_test: - # upload test coverage to Codecov.io - - "tox -e codecov" - -notifications: - - provider: Email - to: - - fonttools-dev@googlegroups.com - on_build_success: false - on_build_failure: true - on_build_status_changed: true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..b3b2b25e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.py text + +# Font files are binary (so that autocrlf doesn't muck with them) +*.lwfn binary +*.pfa binary +*.pfb binary diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..d43c8408 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,31 @@ +# This workflows will upload a Python Package using Twine when a tag is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..7c302587 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,86 @@ +name: Test + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install packages + run: pip install tox + - name: Run Tox + run: tox -e mypy,package_readme + + test: + runs-on: ${{ matrix.platform }} + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + platform: [ubuntu-latest, macos-latest, windows-latest] + exclude: # Only test on the oldest and latest supported stable Python on macOS and Windows. + - platform: macos-latest + python-version: 3.7 + - platform: macos-latest + python-version: 3.8 + - platform: windows-latest + python-version: 3.7 + - platform: windows-latest + python-version: 3.8 + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages + run: pip install tox coverage + - name: Run Tox + run: tox -e py-cov + - name: Run Tox without lxml + run: tox -e py-cov-nolxml + - name: Produce coverage files + run: | + coverage combine + coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + file: coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true + + test-cython: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install packages + run: pip install tox + - name: Run Tox + run: tox -e py-cy-nolxml + + test-pypy3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python pypy3 + uses: actions/setup-python@v2 + with: + python-version: "pypy3" + - name: Install packages + run: pip install tox + - name: Run Tox + run: tox -e pypy3-nolxml diff --git a/.gitignore b/.gitignore index 4fca027d..eba633ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,59 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +dist/ +.eggs/ +*.egg-info/ +*.egg MANIFEST -build -dist + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +coverage.xml +*.cover +.pytest_cache/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# emacs backup files +*~ + +# OSX Finder +.DS_Store + +# VSCode +.vscode + +# Cython sources (e.g. cu2qu) +Lib/**/*.c + +# Ctags +tags diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 00000000..6ea20650 --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,4 @@ +# autogenerated pyup.io config file +# see https://pyup.io/docs/configuration/ for all available options + +schedule: every week diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..928d6587 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,29 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +build: + image: latest + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: Doc/source/conf.py + fail_on_warning: false + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - htmlzip + - epub + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.8 + install: + - requirements: Doc/docs-requirements.txt + - method: pip + path: . + extra_requirements: + - all diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 389d3372..00000000 --- a/.travis.yml +++ /dev/null @@ -1,90 +0,0 @@ -dist: xenial -language: python -python: 3.6 - -env: - global: - - TWINE_USERNAME="anthrotype" - - secure: PJuCmlDuwnojiw3QuDhfNAaU4f/yeJcEcRzJAudA66bwZK7hvxV7Tiy9A17Bm6yO0HbJmmyjsIr8h2e7/PyY6QCaV8RqcMDkQ0UraU16pRsihp0giVXJoWscj2sCP4cNDOBVwSaGAX8yZ2OONc5srESywghzcy8xmgw6O+XFqx4= - -branches: - only: - - master - # We want to build wip/* branches since these are not usually used for PRs - - /^wip\/.*$/ - # We want to build version tags as well. - - /^\d+\.\d+.*$/ - -matrix: - fast_finish: true - include: - - python: 3.6 - env: - - TOXENV=mypy - - python: 3.6 - env: - - TOXENV=py36-cov,package_readme - - BUILD_DIST=true - - python: 3.7 - env: TOXENV=py37-cov - - python: 3.8 - env: TOXENV=py38-cov - - python: 3.8 - env: TOXENV=py38-cy - - python: pypy3 - # disable coverage.py on pypy because of performance problems - env: TOXENV=pypy3 - dist: xenial - - language: generic - os: osx - env: - - TOXENV=py3-cov - - HOMEBREW_NO_AUTO_UPDATE=1 - allow_failures: - # We use fast_finish + allow_failures because OSX builds take forever - # https://blog.travis-ci.com/2013-11-27-fast-finishing-builds - - language: generic - os: osx - env: - - TOXENV=py3-cov - - HOMEBREW_NO_AUTO_UPDATE=1 - -cache: - - pip - - directories: - - $HOME/.pyenv_cache - -addons: - apt: - packages: - - language-pack-de - -before_install: - - source ./.travis/before_install.sh - -install: - - ./.travis/install.sh - -script: - - ./.travis/run.sh - -after_success: - - ./.travis/after_success.sh - -notifications: - irc: "irc.freenode.org##fonts" - email: fonttools-dev@googlegroups.com - -deploy: - # deploy to Github Releases on tags - - provider: releases - api_key: - secure: KEcWhJxMcnKay7wmWJCpg2W5GWHTQ+LaRbqGM11IKGcQuEOFxWuG7W1xjGpVdKPj/MQ+cG0b9hGUFpls1hwseOA1HANMv4xjCgYkuvT1OdpX/KOcZ7gfe/qaovzVxHyP9xwohnHSJMb790t37fmDfFUSROx3iEexIX09LLoDjO8= - skip_cleanup: true - file_glob: true - file: "dist/*" - on: - tags: true - repo: fonttools/fonttools - all_branches: true - condition: "$BUILD_DIST == true" diff --git a/.travis/after_success.sh b/.travis/after_success.sh deleted file mode 100755 index 07bcab5e..00000000 --- a/.travis/after_success.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -e -set -x - -if [ "$TRAVIS_OS_NAME" == "osx" ]; then - source .venv/bin/activate -fi - -# upload coverage data to Codecov.io -[[ ${TOXENV} == *"-cov"* ]] && tox -e codecov - -# if tagged commit, create distribution packages and deploy to PyPI -if [ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_REPO_SLUG" == "fonttools/fonttools" ] && [ "$BUILD_DIST" == true ]; then - tox -e pypi -fi diff --git a/.travis/before_install.sh b/.travis/before_install.sh deleted file mode 100755 index 8cc4edba..00000000 --- a/.travis/before_install.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -if [[ -n "$PYENV_VERSION" ]]; then - wget https://github.com/praekeltfoundation/travis-pyenv/releases/download/${TRAVIS_PYENV_VERSION}/setup-pyenv.sh - source setup-pyenv.sh -fi diff --git a/.travis/install.sh b/.travis/install.sh deleted file mode 100755 index a62b2365..00000000 --- a/.travis/install.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -set -e -set -x - -ci_requirements="pip setuptools tox" - -if [ "$TRAVIS_OS_NAME" == "osx" ]; then - if [[ ${TOXENV} == *"py27"* ]]; then - # install pip on the system python - curl -O https://bootstrap.pypa.io/get-pip.py - python get-pip.py --user - python -m pip install --user virtualenv - python -m virtualenv .venv/ - elif [[ ${TOXENV} == *"py3"* ]]; then - # install current python3 with homebrew - # NOTE: the formula is now named just "python" - brew install python - command -v python3 - python3 --version - python3 -m pip install virtualenv - python3 -m virtualenv .venv/ - else - echo "unsupported $TOXENV: "${TOXENV} - exit 1 - fi - source .venv/bin/activate -fi - -python -m pip install --upgrade $ci_requirements diff --git a/.travis/run.sh b/.travis/run.sh deleted file mode 100755 index e947d850..00000000 --- a/.travis/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -e -set -x - -if [ "$TRAVIS_OS_NAME" == "osx" ]; then - source .venv/bin/activate -fi - -tox --skip-missing-interpreters false - -# re-run all the XML-related tests, this time without lxml but using the -# built-in ElementTree library. -if [ -z "$TOXENV" ]; then - TOXENV="py-nolxml" -else - # strip additional tox envs after the comma, add -nolxml factor - TOXENV="${TOXENV%,*}-nolxml" -fi -tox --skip-missing-interpreters false -e $TOXENV -- Tests/ufoLib Tests/misc/etree_test.py Tests/misc/plistlib_test.py diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..30f4976b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ea116c41 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,26 @@ +## How to Contribute + +FontTools development is on-going in an active community of developers, that includes professional developers employed at major software corporations and at type foundries, as well as hobbyists. + +The project is run on Github, in the typical free/libre/open-source software way. +If you are unfamiliar with that, check out [opensource.guide](https://opensource.guide) and [producingoss.com](http://producingoss.com). + +We use Github's Issue Tracker to report, discuss and track bugs, map out future improvements, set priorities, and self-assign issues. +If you find a bug, have an idea for a new feature, then please [create a new issue](https://github.com/fonttools/fonttools/issues) and we'll be happy to work with you on it! + +If you have a question or want to discuss usage from an end-user perspective, there is a mailing list at [groups.google.com/d/forum/fonttools](https://groups.google.com/d/forum/fonttools) mailing list. + +If you would like to speak to someone directly, you can also email the project lead, Behdad Esfahbod, privately at + +If you make a pull request, you (or the organization that owns your copyrights) should be listed in the [README](https://github.com/fonttools/fonttools#copyrights). + +(There is also a development [groups.google.com/d/forum/fonttools-dev](https://groups.google.com/d/forum/fonttools-dev) mailing list for Continuous Integration notifications.) + +## Code reviews + +All submissions, including submissions by project members, go through a review process using GitHub Pull Requests. +Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on making Pull Requests. + +## Code of Conduct + +This project has a [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) diff --git a/Doc/docs-requirements.txt b/Doc/docs-requirements.txt index bf77512f..c62c8d1e 100644 --- a/Doc/docs-requirements.txt +++ b/Doc/docs-requirements.txt @@ -1,3 +1,3 @@ -sphinx==3.3.0 +sphinx==3.3.1 sphinx_rtd_theme==0.5.0 reportlab==3.5.55 diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py index c4ee2653..8a8121dd 100644 --- a/Lib/fontTools/__init__.py +++ b/Lib/fontTools/__init__.py @@ -4,6 +4,6 @@ from fontTools.misc.loggingTools import configLogger log = logging.getLogger(__name__) -version = __version__ = "4.17.1" +version = __version__ = "4.18.0" __all__ = ["version", "log", "configLogger"] diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py index 5e7d8c6e..d5084f45 100644 --- a/Lib/fontTools/colorLib/builder.py +++ b/Lib/fontTools/colorLib/builder.py @@ -542,6 +542,38 @@ class LayerV1ListBuilder: ot_paint.Paint = self.buildPaint(paint) return ot_paint + def buildPaintRotate( + self, + paint: _PaintInput, + angle: _ScalarInput, + centerX: _ScalarInput, + centerY: _ScalarInput, + ) -> ot.Paint: + ot_paint = ot.Paint() + ot_paint.Format = int(ot.Paint.Format.PaintRotate) + ot_paint.Paint = self.buildPaint(paint) + ot_paint.angle = _to_variable_f16dot16_float(angle) + ot_paint.centerX = _to_variable_f16dot16_float(centerX) + ot_paint.centerY = _to_variable_f16dot16_float(centerY) + return ot_paint + + def buildPaintSkew( + self, + paint: _PaintInput, + xSkewAngle: _ScalarInput, + ySkewAngle: _ScalarInput, + centerX: _ScalarInput, + centerY: _ScalarInput, + ) -> ot.Paint: + ot_paint = ot.Paint() + ot_paint.Format = int(ot.Paint.Format.PaintSkew) + ot_paint.Paint = self.buildPaint(paint) + ot_paint.xSkewAngle = _to_variable_f16dot16_float(xSkewAngle) + ot_paint.ySkewAngle = _to_variable_f16dot16_float(ySkewAngle) + ot_paint.centerX = _to_variable_f16dot16_float(centerX) + ot_paint.centerY = _to_variable_f16dot16_float(centerY) + return ot_paint + def buildPaintComposite( self, mode: _CompositeInput, diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 6baaeeb2..30046bda 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -2,7 +2,11 @@ from fontTools.misc.py23 import * from fontTools.misc import sstruct from fontTools.misc.textTools import binary2num, safeEval from fontTools.feaLib.error import FeatureLibError -from fontTools.feaLib.lookupDebugInfo import LookupDebugInfo, LOOKUP_DEBUG_INFO_KEY +from fontTools.feaLib.lookupDebugInfo import ( + LookupDebugInfo, + LOOKUP_DEBUG_INFO_KEY, + LOOKUP_DEBUG_ENV_VAR, +) from fontTools.feaLib.parser import Parser from fontTools.feaLib.ast import FeatureFile from fontTools.otlLib import builder as otl @@ -31,6 +35,7 @@ from collections import defaultdict import itertools import logging import warnings +import os log = logging.getLogger(__name__) @@ -210,7 +215,7 @@ class Builder(object): self.font["BASE"] = base elif "BASE" in self.font: del self.font["BASE"] - if debug: + if debug or os.environ.get(LOOKUP_DEBUG_ENV_VAR): self.buildDebg() def get_chained_lookup_(self, location, builder_class): diff --git a/Lib/fontTools/feaLib/lookupDebugInfo.py b/Lib/fontTools/feaLib/lookupDebugInfo.py index 3b711f64..876cadff 100644 --- a/Lib/fontTools/feaLib/lookupDebugInfo.py +++ b/Lib/fontTools/feaLib/lookupDebugInfo.py @@ -1,6 +1,7 @@ from typing import NamedTuple LOOKUP_DEBUG_INFO_KEY = "com.github.fonttools.feaLib" +LOOKUP_DEBUG_ENV_VAR = "FONTTOOLS_LOOKUP_DEBUGGING" class LookupDebugInfo(NamedTuple): """Information about where a lookup came from, to be embedded in a font""" diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index a9d13ec6..1ba63c35 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -2,10 +2,16 @@ from collections import namedtuple, OrderedDict from fontTools.misc.fixedTools import fixedToFloat from fontTools import ttLib from fontTools.ttLib.tables import otTables as ot -from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict +from fontTools.ttLib.tables.otBase import ( + ValueRecord, + valueRecordFormatDict, + OTTableWriter, + CountReference, +) from fontTools.ttLib.tables import otBase from fontTools.otlLib.error import OpenTypeLibError import logging +import copy log = logging.getLogger(__name__) @@ -330,6 +336,19 @@ class ChainContextualBuilder(LookupBuilder): # Squish any empty subtables return [x for x in ruleset if len(x.rules) > 0] + def getCompiledSize_(self, subtables): + size = 0 + for st in subtables: + w = OTTableWriter() + w["LookupType"] = CountReference( + {"LookupType": st.LookupType}, "LookupType" + ) + # We need to make a copy here because compiling + # modifies the subtable (finalizing formats etc.) + copy.deepcopy(st).compile(w, self.font) + size += len(w.getAllData()) + return size + def build(self): """Build the lookup. @@ -342,12 +361,153 @@ class ChainContextualBuilder(LookupBuilder): rulesets = self.rulesets() chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets) for ruleset in rulesets: + # Determine format strategy. We try to build formats 1, 2 and 3 + # subtables and then work out which is best. candidates list holds + # the subtables in each format for this ruleset (including a dummy + # "format 0" to make the addressing match the format numbers). + + # We can always build a format 3 lookup by accumulating each of + # the rules into a list, so start with that. + candidates = [None, None, None, []] for rule in ruleset.rules: - subtables.append(self.buildFormat3Subtable(rule, chaining)) + candidates[3].append(self.buildFormat3Subtable(rule, chaining)) + + # Can we express the whole ruleset as a format 2 subtable? + classdefs = ruleset.format2ClassDefs() + if classdefs: + candidates[2] = [ + self.buildFormat2Subtable(ruleset, classdefs, chaining) + ] + + if not ruleset.hasAnyGlyphClasses: + candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)] + + candidates = [x for x in candidates if x is not None] + winner = min(candidates, key=self.getCompiledSize_) + subtables.extend(winner) + # If we are not chaining, lookup type will be automatically fixed by # buildLookup_ return self.buildLookup_(subtables) + def buildFormat1Subtable(self, ruleset, chaining=True): + st = self.newSubtable_(chaining=chaining) + st.Format = 1 + st.populateDefaults() + coverage = set() + rulesetsByFirstGlyph = {} + ruleAttr = self.ruleAttr_(format=1, chaining=chaining) + + for rule in ruleset.rules: + ruleAsSubtable = self.newRule_(format=1, chaining=chaining) + + if chaining: + ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix) + ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix) + ruleAsSubtable.Backtrack = [list(x)[0] for x in reversed(rule.prefix)] + ruleAsSubtable.LookAhead = [list(x)[0] for x in rule.suffix] + + ruleAsSubtable.InputGlyphCount = len(rule.glyphs) + else: + ruleAsSubtable.GlyphCount = len(rule.glyphs) + + ruleAsSubtable.Input = [list(x)[0] for x in rule.glyphs[1:]] + + self.buildLookupList(rule, ruleAsSubtable) + + firstGlyph = list(rule.glyphs[0])[0] + if firstGlyph not in rulesetsByFirstGlyph: + coverage.add(firstGlyph) + rulesetsByFirstGlyph[firstGlyph] = [] + rulesetsByFirstGlyph[firstGlyph].append(ruleAsSubtable) + + st.Coverage = buildCoverage(coverage, self.glyphMap) + ruleSets = [] + for g in st.Coverage.glyphs: + ruleSet = self.newRuleSet_(format=1, chaining=chaining) + setattr(ruleSet, ruleAttr, rulesetsByFirstGlyph[g]) + setattr(ruleSet, f"{ruleAttr}Count", len(rulesetsByFirstGlyph[g])) + ruleSets.append(ruleSet) + + setattr(st, self.ruleSetAttr_(format=1, chaining=chaining), ruleSets) + setattr( + st, self.ruleSetAttr_(format=1, chaining=chaining) + "Count", len(ruleSets) + ) + + return st + + def buildFormat2Subtable(self, ruleset, classdefs, chaining=True): + st = self.newSubtable_(chaining=chaining) + st.Format = 2 + st.populateDefaults() + + if chaining: + ( + st.BacktrackClassDef, + st.InputClassDef, + st.LookAheadClassDef, + ) = [c.build() for c in classdefs] + else: + st.ClassDef = classdefs[1].build() + + inClasses = classdefs[1].classes() + + classSets = [] + for _ in inClasses: + classSet = self.newRuleSet_(format=2, chaining=chaining) + classSets.append(classSet) + + coverage = set() + classRuleAttr = self.ruleAttr_(format=2, chaining=chaining) + + for rule in ruleset.rules: + ruleAsSubtable = self.newRule_(format=2, chaining=chaining) + if chaining: + ruleAsSubtable.BacktrackGlyphCount = len(rule.prefix) + ruleAsSubtable.LookAheadGlyphCount = len(rule.suffix) + # The glyphs in the rule may be list, tuple, odict_keys... + # Order is not important anyway because they are guaranteed + # to be members of the same class. + ruleAsSubtable.Backtrack = [ + st.BacktrackClassDef.classDefs[list(x)[0]] + for x in reversed(rule.prefix) + ] + ruleAsSubtable.LookAhead = [ + st.LookAheadClassDef.classDefs[list(x)[0]] for x in rule.suffix + ] + + ruleAsSubtable.InputGlyphCount = len(rule.glyphs) + ruleAsSubtable.Input = [ + st.InputClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:] + ] + setForThisRule = classSets[ + st.InputClassDef.classDefs[list(rule.glyphs[0])[0]] + ] + else: + ruleAsSubtable.GlyphCount = len(rule.glyphs) + ruleAsSubtable.Class = [ # The spec calls this InputSequence + st.ClassDef.classDefs[list(x)[0]] for x in rule.glyphs[1:] + ] + setForThisRule = classSets[ + st.ClassDef.classDefs[list(rule.glyphs[0])[0]] + ] + + self.buildLookupList(rule, ruleAsSubtable) + coverage |= set(rule.glyphs[0]) + + getattr(setForThisRule, classRuleAttr).append(ruleAsSubtable) + setattr( + setForThisRule, + f"{classRuleAttr}Count", + getattr(setForThisRule, f"{classRuleAttr}Count") + 1, + ) + setattr(st, self.ruleSetAttr_(format=2, chaining=chaining), classSets) + setattr( + st, self.ruleSetAttr_(format=2, chaining=chaining) + "Count", len(classSets) + ) + st.Coverage = buildCoverage(coverage, self.glyphMap) + return st + def buildFormat3Subtable(self, rule, chaining=True): st = self.newSubtable_(chaining=chaining) st.Format = 3 @@ -357,7 +517,10 @@ class ChainContextualBuilder(LookupBuilder): self.setInputCoverage_(rule.glyphs, st) else: self.setCoverage_(rule.glyphs, st) + self.buildLookupList(rule, st) + return st + def buildLookupList(self, rule, st): for sequenceIndex, lookupList in enumerate(rule.lookups): if lookupList is not None: if not isinstance(lookupList, list): @@ -377,7 +540,6 @@ class ChainContextualBuilder(LookupBuilder): rec = self.newLookupRecord_(st) rec.SequenceIndex = sequenceIndex rec.LookupListIndex = l.lookup_index - return st def add_subtable_break(self, location): self.rules.append( @@ -398,6 +560,54 @@ class ChainContextualBuilder(LookupBuilder): setattr(st, f"{self.subtable_type}LookupRecord", []) return st + # Format 1 and format 2 GSUB5/GSUB6/GPOS7/GPOS8 rulesets and rules form a family: + # + # format 1 ruleset format 1 rule format 2 ruleset format 2 rule + # GSUB5 SubRuleSet SubRule SubClassSet SubClassRule + # GSUB6 ChainSubRuleSet ChainSubRule ChainSubClassSet ChainSubClassRule + # GPOS7 PosRuleSet PosRule PosClassSet PosClassRule + # GPOS8 ChainPosRuleSet ChainPosRule ChainPosClassSet ChainPosClassRule + # + # The following functions generate the attribute names and subtables according + # to this naming convention. + def ruleSetAttr_(self, format=1, chaining=True): + if format == 1: + formatType = "Rule" + elif format == 2: + formatType = "Class" + else: + raise AssertionError(formatType) + subtablename = f"{self.subtable_type[0:3]}{formatType}Set" # Sub, not Subst. + if chaining: + subtablename = "Chain" + subtablename + return subtablename + + def ruleAttr_(self, format=1, chaining=True): + if format == 1: + formatType = "" + elif format == 2: + formatType = "Class" + else: + raise AssertionError(formatType) + subtablename = f"{self.subtable_type[0:3]}{formatType}Rule" # Sub, not Subst. + if chaining: + subtablename = "Chain" + subtablename + return subtablename + + def newRuleSet_(self, format=1, chaining=True): + st = getattr( + ot, self.ruleSetAttr_(format, chaining) + )() # ot.ChainPosRuleSet()/ot.SubRuleSet()/etc. + st.populateDefaults() + return st + + def newRule_(self, format=1, chaining=True): + st = getattr( + ot, self.ruleAttr_(format, chaining) + )() # ot.ChainPosClassRule()/ot.SubClassRule()/etc. + st.populateDefaults() + return st + def attachSubtableWithCount_( self, st, subtable_name, count_name, existing=None, index=None, chaining=False ): diff --git a/Lib/fontTools/otlLib/builder.py.sketch b/Lib/fontTools/otlLib/builder.py.sketch new file mode 100644 index 00000000..9addf8c8 --- /dev/null +++ b/Lib/fontTools/otlLib/builder.py.sketch @@ -0,0 +1,105 @@ + +from fontTools.otlLib import builder as builder + +GDEF::mark filtering sets +name:: + +lookup_flags = builder.LOOKUP_FLAG_IGNORE_MARKS | builder.LOOKUP_FLAG_RTL +smcp_subtable = builder.buildSingleSubstitute({'a':'a.scmp'}) +smcp_lookup = builder.buildLookup([smcp_subtable], lookup_flags=lookup_flags, mark_filter_set=int) + +lookups = [smcp_lookup, ...] + +scmp_feature = builder.buildFeature('smcp', [scmp_lookup], lookup_list=lookups) +scmp_feature = builder.buildFeature('smcp', [0]) + +features = [smcp_feature] + +default_langsys = builder.buildLangSys(set([scmp_feature]), requiredFeature=None, featureOrder=features) +default_langsys = builder.buildLangSys(set([0]), requiredFeature=None) + +script = + + +#GSUB: + +builder.buildSingleSubst({'a':'a.scmp'}) +builder.buildLigatureSubst({('f','i'):'fi'}) +builder.buildMultipleSubst({'a':('a0','a1')}) +builder.buildAlternateSubst({'a':('a.0','a.1')}) + + +class ChainSequence : namedtuple(['backtrack', 'input', 'lookahead')]) + pass + +ChainSequence(backtrack=..., input=..., lookahead=...) + +klass0 = frozenset() + +builder.buildChainContextGlyphs( + [ + ( (None, ('f','f','i'), (,)), ( (1,lookup_fi), (1,lookup_2) ) ), + ], + glyphMap +) +builder.buildChainContextClass( + [ + ( (None, (2,0,1), (,)), ( (1,lookup_fi), (1,lookup_2) ) ), + ], + klasses = ( backtrackClass, ... ), + glyphMap +) +builder.buildChainContextCoverage( + ( (None, (frozenset('f'),frozenset('f'),frozenset('i')), (,)), ( (1,lookup_fi), (1,lookup_2) ) ), + glyphMap +) +builder.buildExtension(...) + +#GPOS: +device = builder.buildDevice() +builder.buildAnchor(100, -200) or (100,-200) +builder.buildAnchor(100, -200, device=device) +builder.buildAnchor(100, -200, point=2) + +valueRecord = builder.buildValue({'XAdvance':-200, ...}) + +builder.buildSinglePos({'a':valueRecord}) +builder.buildPairPosGlyphs( + { + ('a','b'): (valueRecord1,valueRecord2), + }, + glyphMap, + , valueFormat1=None, valueFormat2=None +) +builder.buildPairPosClasses( + { + (frozenset(['a']),frozenset(['b'])): (valueRecord1,valueRecord2), + }, + glyphMap, + , valueFormat1=None, valueFormat2=None +) + +builder.buildCursivePos( + { + 'alef': (entry,exit), + } + glyphMap +) +builder.buildMarkBasePos( + marks = { + 'mark1': (klass, anchor), + }, + bases = { + 'base0': [anchor0, anchor1, anchor2], + }, + glyphMap +) +builder.buildMarkBasePos( + marks = { + 'mark1': (name, anchor), + }, + bases = { + 'base0': {'top':anchor0, 'left':anchor1}, + }, + glyphMap +) diff --git a/Lib/fontTools/pens/momentsPen.py b/Lib/fontTools/pens/momentsPen.py index bc1df94c..694d6b02 100644 --- a/Lib/fontTools/pens/momentsPen.py +++ b/Lib/fontTools/pens/momentsPen.py @@ -7,6 +7,10 @@ from fontTools.pens.basePen import BasePen __all__ = ["MomentsPen"] +class OpenContourError(NotImplementedError): + pass + + class MomentsPen(BasePen): def __init__(self, glyphset=None): @@ -30,8 +34,9 @@ class MomentsPen(BasePen): def _endPath(self): p0 = self._getCurrentPoint() if p0 != self.__startPoint: - # Green theorem is not defined on open contours. - raise NotImplementedError + raise OpenContourError( + "Green theorem is not defined on open contours." + ) def _lineTo(self, p1): x0,y0 = self._getCurrentPoint() diff --git a/Lib/fontTools/pens/quartzPen.py b/Lib/fontTools/pens/quartzPen.py new file mode 100644 index 00000000..d35a993b --- /dev/null +++ b/Lib/fontTools/pens/quartzPen.py @@ -0,0 +1,46 @@ +from fontTools.misc.py23 import * +from fontTools.pens.basePen import BasePen + +from Quartz.CoreGraphics import CGPathCreateMutable, CGPathMoveToPoint +from Quartz.CoreGraphics import CGPathAddLineToPoint, CGPathAddCurveToPoint +from Quartz.CoreGraphics import CGPathAddQuadCurveToPoint, CGPathCloseSubpath + + +__all__ = ["QuartzPen"] + + +class QuartzPen(BasePen): + + """A pen that creates a CGPath + + Parameters + - path: an optional CGPath to add to + - xform: an optional CGAffineTransform to apply to the path + """ + + def __init__(self, glyphSet, path=None, xform=None): + BasePen.__init__(self, glyphSet) + if path is None: + path = CGPathCreateMutable() + self.path = path + self.xform = xform + + def _moveTo(self, pt): + x, y = pt + CGPathMoveToPoint(self.path, self.xform, x, y) + + def _lineTo(self, pt): + x, y = pt + CGPathAddLineToPoint(self.path, self.xform, x, y) + + def _curveToOne(self, p1, p2, p3): + (x1, y1), (x2, y2), (x3, y3) = p1, p2, p3 + CGPathAddCurveToPoint(self.path, self.xform, x1, y1, x2, y2, x3, y3) + + def _qCurveToOne(self, p1, p2): + (x1, y1), (x2, y2) = p1, p2 + CGPathAddQuadCurveToPoint(self.path, self.xform, x1, y1, x2, y2) + + def _closePath(self): + CGPathCloseSubpath(self.path) + diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 4aa97610..d78aa8a8 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -175,8 +175,9 @@ Glyph set expansion: * Keep default set of features plus 'aalt', but drop 'vrt2'. --layout-scripts[+|-]= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/GSUB_6.ttx b/Tests/feaLib/data/GSUB_6.ttx index f32e47d9..18405d25 100644 --- a/Tests/feaLib/data/GSUB_6.ttx +++ b/Tests/feaLib/data/GSUB_6.ttx @@ -229,36 +229,30 @@ - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/GSUB_6_formats.fea b/Tests/feaLib/data/GSUB_6_formats.fea new file mode 100644 index 00000000..44135d24 --- /dev/null +++ b/Tests/feaLib/data/GSUB_6_formats.fea @@ -0,0 +1,20 @@ +lookup GSUB6f1 { + ignore sub one two three' four' five six seven; + ignore sub two one three' four' six five seven; +} GSUB6f1; + +lookup GSUB6f2 { + ignore sub [A - H] [I - Z] [a - z]' [A - H]' [I - Z]'; + ignore sub [I - Z] [A - H] [a - z]' [A - H]' [I - Z]'; + ignore sub [A - H] [I - Z] [a - z]' [I - Z]' [A - H]'; +} GSUB6f2; + +lookup GSUB6f3 { + ignore sub [space comma semicolon] e'; +} GSUB6f3; + +feature test { + lookup GSUB6f1; + lookup GSUB6f2; + lookup GSUB6f3; +} test; diff --git a/Tests/feaLib/data/GSUB_6_formats.ttx b/Tests/feaLib/data/GSUB_6_formats.ttx new file mode 100644 index 00000000..ad2a1c5e --- /dev/null +++ b/Tests/feaLib/data/GSUB_6_formats.ttx @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx b/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx index ce76370e..98f7aa9e 100644 --- a/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx +++ b/Tests/feaLib/data/ZeroValue_ChainSinglePos_horizontal.ttx @@ -32,44 +32,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx b/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx index e1cb5d61..973cb4f6 100644 --- a/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx +++ b/Tests/feaLib/data/ZeroValue_ChainSinglePos_vertical.ttx @@ -32,44 +32,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/bug512.ttx b/Tests/feaLib/data/bug512.ttx index 50b76b46..693ebeb7 100644 --- a/Tests/feaLib/data/bug512.ttx +++ b/Tests/feaLib/data/bug512.ttx @@ -32,50 +32,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/feaLib/data/spec5f_ii_3.ttx b/Tests/feaLib/data/spec5f_ii_3.ttx index a550f0b6..a94efcea 100644 --- a/Tests/feaLib/data/spec5f_ii_3.ttx +++ b/Tests/feaLib/data/spec5f_ii_3.ttx @@ -32,111 +32,115 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/pens/cocoaPen_test.py b/Tests/pens/cocoaPen_test.py new file mode 100644 index 00000000..51795e12 --- /dev/null +++ b/Tests/pens/cocoaPen_test.py @@ -0,0 +1,59 @@ +from fontTools.misc.py23 import * +import unittest + +try: + from fontTools.pens.cocoaPen import CocoaPen + from AppKit import NSBezierPathElementMoveTo, NSBezierPathElementLineTo + from AppKit import NSBezierPathElementCurveTo, NSBezierPathElementClosePath + + PATH_ELEMENTS = { + # NSBezierPathElement key desc + NSBezierPathElementMoveTo: 'moveto', + NSBezierPathElementLineTo: 'lineto', + NSBezierPathElementCurveTo: 'curveto', + NSBezierPathElementClosePath: 'close', + } + + PYOBJC_AVAILABLE = True +except ImportError: + PYOBJC_AVAILABLE = False + + +def draw(pen): + pen.moveTo((50, 0)) + pen.lineTo((50, 500)) + pen.lineTo((200, 500)) + pen.curveTo((350, 500), (450, 400), (450, 250)) + pen.curveTo((450, 100), (350, 0), (200, 0)) + pen.closePath() + + +def cocoaPathToString(path): + num_elements = path.elementCount() + output = [] + for i in range(num_elements - 1): + elem_type, elem_points = path.elementAtIndex_associatedPoints_(i) + elem_type = PATH_ELEMENTS[elem_type] + path_points = " ".join([f"{p.x} {p.y}" for p in elem_points]) + output.append(f"{elem_type} {path_points}") + return " ".join(output) + + +@unittest.skipUnless(PYOBJC_AVAILABLE, "pyobjc not installed") +class CocoaPenTest(unittest.TestCase): + def test_draw(self): + pen = CocoaPen(None) + draw(pen) + self.assertEqual( + "moveto 50.0 0.0 lineto 50.0 500.0 lineto 200.0 500.0 curveto 350.0 500.0 450.0 400.0 450.0 250.0 curveto 450.0 100.0 350.0 0.0 200.0 0.0 close ", + cocoaPathToString(pen.path) + ) + + def test_empty(self): + pen = CocoaPen(None) + self.assertEqual("", cocoaPathToString(pen.path)) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main()) diff --git a/Tests/pens/quartzPen_test.py b/Tests/pens/quartzPen_test.py new file mode 100644 index 00000000..12fbd292 --- /dev/null +++ b/Tests/pens/quartzPen_test.py @@ -0,0 +1,79 @@ +from fontTools.misc.py23 import * +import unittest + +try: + from fontTools.pens.quartzPen import QuartzPen + + from Quartz.CoreGraphics import CGPathApply + from Quartz.CoreGraphics import kCGPathElementMoveToPoint + from Quartz.CoreGraphics import kCGPathElementAddLineToPoint + from Quartz.CoreGraphics import kCGPathElementAddQuadCurveToPoint + from Quartz.CoreGraphics import kCGPathElementAddCurveToPoint + from Quartz.CoreGraphics import kCGPathElementCloseSubpath + + PATH_ELEMENTS = { + # CG constant key desc num_points + kCGPathElementMoveToPoint: ('moveto', 1), + kCGPathElementAddLineToPoint: ('lineto', 1), + kCGPathElementAddCurveToPoint: ('curveto', 3), + kCGPathElementAddQuadCurveToPoint: ('qcurveto', 2), + kCGPathElementCloseSubpath: ('close', 0), + } + + PYOBJC_AVAILABLE = True +except ImportError: + PYOBJC_AVAILABLE = False + + +def draw(pen): + pen.moveTo((50, 0)) + pen.lineTo((50, 500)) + pen.lineTo((200, 500)) + pen.curveTo((350, 500), (450, 400), (450, 250)) + pen.curveTo((450, 100), (350, 0), (200, 0)) + pen.closePath() + + +def quartzPathApplier(elements, element): + num_points = 0 + elem_type = None + if element.type in PATH_ELEMENTS: + num_points = PATH_ELEMENTS[element.type][1] + elem_type = PATH_ELEMENTS[element.type][0] + elements.append((elem_type, element.points.as_tuple(num_points))) + + +def quartzPathElements(path): + elements = [] + CGPathApply(path, elements, quartzPathApplier) + return elements + + +def quartzPathToString(path): + elements = quartzPathElements(path) + output = [] + for element in elements: + elem_type, elem_points = element + path_points = " ".join([f"{p.x} {p.y}" for p in elem_points]) + output.append(f"{elem_type} {path_points}") + return " ".join(output) + + +@unittest.skipUnless(PYOBJC_AVAILABLE, "pyobjc not installed") +class QuartzPenTest(unittest.TestCase): + def test_draw(self): + pen = QuartzPen(None) + draw(pen) + self.assertEqual( + "moveto 50.0 0.0 lineto 50.0 500.0 lineto 200.0 500.0 curveto 350.0 500.0 450.0 400.0 450.0 250.0 curveto 450.0 100.0 350.0 0.0 200.0 0.0 close ", + quartzPathToString(pen.path) + ) + + def test_empty(self): + pen = QuartzPen(None) + self.assertEqual("", quartzPathToString(pen.path)) + + +if __name__ == '__main__': + import sys + sys.exit(unittest.main()) diff --git a/Tests/subset/data/expect_layout_scripts.ttx b/Tests/subset/data/expect_layout_scripts.ttx new file mode 100644 index 00000000..f3eea822 --- /dev/null +++ b/Tests/subset/data/expect_layout_scripts.ttx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/subset/data/layout_scripts.ttx b/Tests/subset/data/layout_scripts.ttx new file mode 100644 index 00000000..37b2fd81 --- /dev/null +++ b/Tests/subset/data/layout_scripts.ttx @@ -0,0 +1,997 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright 2010-2020 The Amiri Project Authors (https://github.com/alif-type/amiri). + + + Amiri + + + Regular + + + 0.114;ALIF;Amiri-Regular + + + Amiri Regular + + + Version 0.114 + + + Amiri-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 2 3 1 vhcurveto + endchar + + + 177 113 -106 callgsubr + return + + + 487 531 rmoveto + -5 -82 -21 -36 -38 10 -36 10 -16 18 5 25 5 25 1 18 -2 8 -2 8 -5 5 -8 1 -8 1 -4 -7 -3 -13 -14 -69 -26 -36 -40 -4 -29 -3 -23 4 -19 11 rrcurveto + -8 5 -24 31 -39 57 -17 26 -12 0 -6 -23 -25 -101 rcurveline + -3 -13 5 -16 14 -20 79 -117 45 -148 11 -178 1 -8 2 -6 4 -3 4 -3 4 0 6 2 6 2 3 4 2 7 20 79 -11 123 -42 169 39 -3 35 16 32 35 rrcurveto + 14 -11 17 -9 20 -6 63 -20 40 23 18 65 13 49 7 46 2 41 rrcurveto + 20 1 -6 9 -10 hhcurveto + -10 -5 -6 -12 -1 hvcurveto + endchar + + + rmoveto + -45 13 -33 20 -23 28 -3 4 0 3 3 3 27 30 33 15 38 -1 rrcurveto + 28 20 -8 -13 13 hvcurveto + 5 -5 3 0 4 2 3 1 0 3 -1 6 -6 13 -11 16 -15 16 rrcurveto + 14 -13 -14 7 -14 hhcurveto + -30 -33 -21 -43 -37 hvcurveto + -15 -19 -12 -20 -7 -23 -3 -7 1 -9 3 -8 11 -25 31 -23 53 -19 -51 -45 -38 -53 -24 -60 -5 -12 -1 -6 5 -1 6 -3 7 4 7 10 26 40 32 36 34 30 rrcurveto + 37 33 37 24 37 16 10 4 7 8 4 13 16 50 rcurveline + 4 10 -5 3 -12 -4 -18 -7 -23 -14 -28 -22 -7 -5 -9 -2 -9 3 rrcurveto + endchar + + + 311 -77 rmoveto + 21 89 26 81 29 73 29 73 39 74 45 76 rrcurveto + 6 11 2 9 8 vvcurveto + -6 97 rlineto + 9 -1 -2 4 -5 hhcurveto + -5 -5 -6 -10 -5 hvcurveto + -79 -149 -53 -135 -27 -121 -4 -19 -7 2 -7 21 -55 159 -61 140 -70 124 -11 20 -8 -8 -5 -35 -13 -81 rcurveline + -2 -10 1 -8 3 -6 57 -95 46 -93 38 -90 38 -90 20 -68 2 -45 rrcurveto + -7 3 -4 4 -3 vhcurveto + 4 -3 4 0 5 2 5 2 2 5 2 7 rrcurveto + endchar + + + rmoveto + 19 69 -12 87 -41 106 -10 24 -4 14 2 3 rrcurveto + 10 10 11 5 12 hhcurveto + 31 25 -26 -51 20 hvcurveto + 2 -3 1 -2 3 -1 rrcurveto + 2 2 1 3 2 hvcurveto + 12 34 21 34 28 35 2 2 2 4 1 4 14 51 rcurveline + 3 0 2 -3 1 vhcurveto + -3 1 -2 -1 -4 -3 -24 -22 -21 -32 -16 -43 -22 49 -28 24 -36 -2 -32 -1 -21 -19 -12 -38 -12 -39 2 -39 13 -38 29 -80 17 -68 4 -55 rrcurveto + -6 3 -2 4 5 -107 callsubr + + + 158 296 -95 callgsubr + + + 136 657 rmoveto + -13 -53 -16 -49 -17 -44 -18 -43 -22 -44 -27 -46 rrcurveto + -4 -6 -1 -6 -5 vvcurveto + 3 -58 rlineto + -6 2 -2 3 3 3 3 6 3 vhcurveto + 48 90 31 81 17 72 2 12 4 -1 4 -13 33 -95 37 -84 42 -75 7 -12 4 5 3 21 8 49 rcurveline + 1 6 0 4 -2 4 -34 57 -28 56 -23 54 -22 54 -12 41 -2 27 rrcurveto + 4 -1 3 -3 2 vhcurveto + -2 1 -3 0 -3 -1 -3 -1 -1 -4 -1 -4 rrcurveto + endchar + + + 220 500 rmoveto + -67 -86 -32 -66 2 -47 4 -86 75 -35 146 14 5 -82 22 -87 39 -91 15 -35 9 -2 6 33 18 105 rcurveline + 3 15 -3 16 -10 18 -32 59 -18 73 -3 86 -3 87 -16 66 -32 45 -39 56 -43 0 -46 -56 rrcurveto + 102 -152 rmoveto + 6 -19 -1 -13 -6 -6 rrcurveto + -2 -3 -3 -2 -5 hhcurveto + -50 -3 -36 6 -22 14 -16 10 -5 16 7 21 12 39 24 15 36 -10 30 -9 21 -22 11 -35 rrcurveto + endchar + + + 53 654 -94 callgsubr + + + 38 655 -88 callgsubr + + + hhcurveto + 54 3 49 -4 45 -12 5 -1 4 0 2 1 54 31 rcurveline + 10 6 0 4 -10 3 -61 14 -61 3 -60 -9 57 83 42 104 25 126 1 7 0 5 -2 2 -2 2 -3 -1 -4 -4 -40 -41 rcurveline + -4 -3 -3 -6 -1 -7 -25 -113 -35 -98 -45 -84 rrcurveto + endchar + + + 119 655 -85 callgsubr + + + 4 -4 5 0 6 2 6 2 3 5 1 7 20 return + + + -3 -13 4 -15 12 -19 return + + + + + + 111 endchar + + + 10 -106 callsubr + -94 466 -80 callgsubr + + + 10 -106 callsubr + -94 466 -80 callgsubr + + + 332 -84 callgsubr + + + 39 -82 callgsubr + + + 332 -87 callgsubr + + + 39 -95 callsubr + + + 332 -97 callgsubr + + + 39 -98 callsubr + + + 332 -105 callsubr + + + 39 263 662 -107 callgsubr + + + 332 175 515 rmoveto + -12 19 -10 8 -7 -1 -4 -2 -4 -5 -2 -10 -25 -105 rcurveline + -93 callsubr + 83 -128 46 -149 7 -168 rrcurveto + -7 3 -5 4 -4 vhcurveto + -94 callsubr + 98 -4 110 -26 122 26 -13 33 -5 38 3 63 5 49 28 34 54 9 14 1 10 -7 7 -6 5 -9 -3 -13 -11 rrcurveto + -21 -18 -39 -7 -56 3 -38 2 -39 18 -40 34 20 66 50 34 83 1 rrcurveto + 26 35 -15 -31 42 hvcurveto + 10 -7 6 0 5 7 3 5 -2 9 -7 14 -35 66 -40 33 -44 -3 -76 -5 -57 -50 -40 -93 -4 -11 -5 0 -5 11 -11 28 -19 35 -27 42 rrcurveto + endchar + + + 39 76 651 rmoveto + -12 19 -8 1 -3 -15 -15 -63 rcurveline + -2 -7 2 -9 7 -12 50 -77 28 -89 4 -101 rrcurveto + -4 2 -3 2 -2 vhcurveto + 3 -3 3 0 3 1 rrcurveto + 4 2 2 3 4 vvcurveto + 12 59 -2 66 -16 73 16 -8 20 -3 22 2 38 3 30 17 20 32 5 8 1 6 -4 5 -4 3 -5 -2 -8 -7 -13 -11 -23 -4 -34 2 -22 1 -24 11 -24 20 rrcurveto + 12 40 30 20 50 1 rrcurveto + 16 21 -9 -19 25 hvcurveto + 6 -4 3 0 3 4 2 3 -1 6 -4 8 -21 40 -24 20 -27 -2 -45 -3 -34 -30 -24 -56 -3 -7 -3 0 -3 7 -6 17 -12 21 -16 25 rrcurveto + endchar + + + 332 229 -83 rmoveto + 31 115 -20 145 -69 177 -16 40 -6 23 3 4 rrcurveto + 18 16 19 8 19 hhcurveto + 52 -1 42 -43 34 -84 2 -6 3 -3 5 -1 3 -1 3 2 3 5 21 57 34 57 47 57 3 4 3 6 2 7 23 85 rcurveline + 1 6 -1 3 -5 2 -4 1 -4 -2 -6 -5 -41 -36 -34 -53 -27 -73 -36 82 -48 40 -59 -3 -53 -2 -36 -32 -19 -62 -20 -65 2 -65 22 -64 49 -133 28 -114 6 -92 rrcurveto + -9 1 4 -4 8 hhcurveto + 8 5 3 6 2 hvcurveto + endchar + + + 39 108 303 -102 callsubr + + + 332 235 526 rmoveto + -12 12 -8 -2 -2 -16 -17 -121 rcurveline + -2 -15 3 -8 7 -4 8 -4 8 -5 7 -5 -75 -103 -42 -88 -8 -76 -11 -105 29 -55 68 -6 39 -3 34 17 31 36 16 -26 28 -10 40 7 54 9 33 60 13 110 rrcurveto + 5 39 -17 59 -39 78 -29 58 -63 75 -98 92 rrcurveto + 16 -184 rmoveto + 76 -52 58 -68 42 -83 6 -12 2 -10 -4 -10 -10 -27 -17 -15 -23 -3 -17 -2 -14 3 -12 8 -1 7 1 6 3 5 -1 6 -3 4 -4 2 -5 2 -5 -3 -6 -6 rrcurveto + -64 -62 -52 -8 -40 45 -20 23 2 44 25 64 15 38 27 48 41 56 rrcurveto + endchar + + + 39 112 656 -99 callgsubr + + + 332 331 285 rmoveto + -74 21 -56 34 -37 47 -5 6 0 5 5 5 45 50 54 25 63 -1 47 -1 34 -12 22 -23 7 -7 6 -1 6 3 5 3 1 5 -3 9 -9 22 -18 26 -26 28 rrcurveto + 23 -22 -22 11 -24 hhcurveto + -50 -55 -35 -72 -61 hvcurveto + -26 -31 -20 -33 -12 -38 -4 -13 1 -14 5 -13 18 -43 52 -37 88 -32 -85 -75 -62 -88 -41 -100 -8 -20 -1 -10 8 -3 10 -4 12 7 11 16 44 66 52 60 58 51 rrcurveto + 61 54 61 41 62 26 17 7 12 14 7 21 26 83 rcurveline + 6 17 -8 5 -20 -7 -30 -11 -38 -24 -47 -36 -12 -9 -14 -3 -16 5 rrcurveto + endchar + + + 39 170 512 -104 callsubr + + + 332 -90 callgsubr + + + 39 -97 callsubr + + + 332 -103 callsubr + + + 39 -101 callsubr + + + 332 104 -50 rmoveto + -3 -6 -1 -5 2 -3 2 -3 4 -2 8 1 90 5 83 -7 75 -20 8 -2 6 0 4 2 89 51 rcurveline + 18 10 0 8 -18 4 -101 23 -101 5 -100 -15 95 139 69 174 42 210 2 11 0 8 -3 3 -4 4 -5 -2 -6 -6 -68 -68 rcurveline + -6 -6 -5 -10 -2 -12 -42 -188 -58 -163 -74 -140 rrcurveto + endchar + + + 39 33 312 rmoveto + -8 -4 3 -3 9 -96 callsubr + + + 332 -98 callgsubr + + + 39 -100 callsubr + + + 332 -99 callsubr + + + 39 -93 callgsubr + + + + + + + + rmoveto + -3 -50 -12 -21 -23 6 -22 6 -9 11 3 15 3 15 0 10 -1 5 -1 5 -3 3 -5 1 rrcurveto + -5 -2 -4 -8 -2 hvcurveto + -8 -41 -16 -22 -24 -2 -17 -2 -14 2 -12 7 -4 3 -15 19 -23 34 -10 15 -8 0 -3 -13 -15 -61 rcurveline + -2 -8 3 -9 8 -12 48 -71 27 -88 6 -107 1 -5 1 -4 3 -1 2 -2 2 0 4 1 4 1 1 3 2 4 12 47 -7 74 -25 101 23 -1 21 9 19 21 rrcurveto + 9 -6 10 -6 12 -3 38 -12 24 13 11 39 7 30 5 27 1 25 rrcurveto + 12 -3 5 -6 -6 -3 -3 -7 -1 vhcurveto + endchar + + + rmoveto + 237 vlineto + -92 callgsubr + return + + + vlineto + -29 0 -27 -5 -16 -102 callgsubr + -46 -77 callgsubr + vvcurveto + return + + + rmoveto + -100 callgsubr + return + + + -81 callgsubr + 49 0 -17 7 hvcurveto + 5 -12 0 return + + + vhcurveto + -11 -3 -47 -74 callgsubr + 26 -79 callgsubr + 26 -78 callgsubr + return + + + 5 5 -1 18 -4 5 rrcurveto + -12 return + + + -27 22 -22 27 27 22 22 27 27 -22 22 -27 -27 -22 -22 -27 vhcurveto + return + + + rmoveto + -7 7 -5 -1 -1 -10 -10 -73 rcurveline + -2 -9 2 -4 4 -3 5 -2 5 -3 4 -3 -45 -62 -25 -53 -5 -45 -6 -63 17 -33 41 -4 23 -2 21 10 18 22 10 -16 17 -6 24 5 32 5 20 36 8 66 rrcurveto + 3 23 -11 36 -23 47 -17 34 -38 45 -59 56 rrcurveto + 10 -111 rmoveto + 45 -31 35 -41 25 -50 4 -7 1 -6 -2 -6 -6 -16 -11 -9 -13 -2 -11 -1 -8 2 -7 5 -1 4 1 3 2 3 -1 4 -2 2 -2 2 -3 1 -3 -2 -4 -4 rrcurveto + -38 -37 -31 -5 -24 27 -12 14 1 27 15 38 9 23 16 29 25 33 rrcurveto + endchar + + + 275 527 rmoveto + -21 -89 -27 -81 -29 -73 -29 -73 -38 -73 -45 -76 rrcurveto + -6 -11 -2 -10 -8 vvcurveto + 5 -97 rlineto + -9 1 3 -3 5 hhcurveto + 5 5 5 10 5 hvcurveto + 79 149 53 135 27 121 4 19 7 -2 7 -21 55 -159 61 -140 70 -124 11 -20 8 8 5 35 13 81 rcurveline + 2 10 -1 8 -3 6 -57 95 -46 93 -38 90 -38 90 -20 68 -2 45 rrcurveto + 7 -3 5 -4 3 vhcurveto + -4 3 -4 0 -5 -2 -5 -2 -2 -6 -2 -7 rrcurveto + endchar + + + 137 -96 callgsubr + + + 521 rmoveto + -25 -102 -3 -13 4 -15 12 -19 rlinecurve + 79 -124 46 -149 11 -172 1 -7 2 -5 4 -4 4 -4 5 0 6 2 6 2 3 5 1 7 20 99 -12 128 -42 158 79 -13 57 18 33 49 21 30 13 33 6 37 rrcurveto + 6 37 3 24 -3 7 rrcurveto + 7 -3 -6 4 -9 hhcurveto + -9 -6 -6 -12 -2 hvcurveto + -69 -9 -43 -36 -79 hhcurveto + -55 -44 32 62 -33 hvcurveto + -11 21 -9 10 -7 -2 -5 -1 -4 -6 -3 -13 rrcurveto + endchar + + + rmoveto + 12 53 16 49 17 44 18 43 23 45 27 45 rrcurveto + 4 7 1 5 5 vvcurveto + -4 58 rlineto + 6 -2 2 -3 -3 -3 -3 -6 -3 vhcurveto + -47 -90 -32 -81 -16 -72 -2 -12 -5 1 -4 13 -33 95 -36 84 -42 75 -7 12 -5 -5 -3 -21 -8 -49 rcurveline + -1 -6 1 -4 2 -4 34 -57 27 -56 23 -54 23 -54 12 -41 1 -27 rrcurveto + -4 2 -2 2 -2 vhcurveto + 3 -2 2 0 3 1 3 2 1 3 2 4 rrcurveto + endchar + + + rmoveto + -15 -62 -2 -7 3 -9 7 -12 rlinecurve + 47 -74 28 -90 7 -103 rrcurveto + -4 1 -3 3 -2 vhcurveto + 2 -3 3 0 4 1 3 2 2 3 1 4 12 59 -7 77 -26 95 48 -8 34 11 20 29 12 18 8 20 4 22 3 22 2 15 -2 4 rrcurveto + 4 -1 -4 3 -5 hhcurveto + -6 -3 -4 -7 -2 hvcurveto + -42 -5 -26 -21 -47 hhcurveto + -33 -27 19 37 -19 hvcurveto + -7 13 -5 6 -5 -1 -3 -1 -2 -4 -2 -7 rrcurveto + endchar + + + 103 641 -91 callgsubr + + + 10 6 47 1 7 vhcurveto + 5 -6 11 -5 vhcurveto + -39 -21 -50 -103 callgsubr + -26 -13 vvcurveto + -182 -105 callgsubr + return + + + rmoveto + -40 -52 -19 -39 1 -28 2 -52 45 -21 88 8 3 -49 13 -52 23 -55 9 -21 6 -1 3 20 11 63 rcurveline + 2 9 -2 10 -6 10 -19 36 -11 44 -2 51 -1 52 -10 40 -19 27 -24 34 -25 0 -28 -34 rrcurveto + 61 -91 rmoveto + -16 5 -2 -9 -10 hhcurveto + -30 -2 -22 3 -13 9 -9 6 -3 9 4 13 7 23 14 9 22 -6 18 -5 13 -13 6 -21 rrcurveto + endchar + + + 111 -89 callgsubr + + + 522 rmoveto + -15 -79 -3 -13 4 -12 9 -10 rlinecurve + 50 -56 78 -14 105 27 rrcurveto + 1 1 0 0 hvcurveto + 3 -1 1 -4 -7 vvcurveto + -5 -150 31 -142 70 -132 15 -29 11 3 6 32 17 95 rcurveline + 3 17 -4 18 -9 17 -71 128 -29 128 13 130 2 16 -8 6 -18 -5 -109 -33 -75 12 -38 57 -19 28 -12 0 -5 -27 rrcurveto + endchar + + + rmoveto + -9 -47 -2 -8 2 -7 6 -6 rlinecurve + 30 -34 46 -8 63 16 rrcurveto + 3 1 1 -3 -5 vvcurveto + -3 -90 19 -85 42 -80 9 -17 6 2 4 19 10 57 rcurveline + 2 10 -3 11 -5 10 -43 77 -17 77 8 78 1 9 -5 4 -11 -3 -65 -20 -45 7 -23 35 -11 16 -7 0 -3 -16 rrcurveto + endchar + + + 246 -86 callgsubr + + + 524 rmoveto + -34 -100 -5 -14 3 -17 11 -20 rlinecurve + 71 -126 35 -148 -2 -171 rrcurveto + -12 4 -6 9 -2 vhcurveto + 9 -2 5 5 4 10 23 69 4 99 -18 129 -13 92 -27 96 -39 100 -17 40 -14 6 -9 -28 rrcurveto + endchar + + + rmoveto + -21 -60 -3 -8 2 -10 7 -12 rlinecurve + 42 -76 21 -89 -1 -102 rrcurveto + -7 2 -4 6 -1 vhcurveto + 5 -1 3 3 3 6 13 41 3 59 -11 78 -8 55 -16 58 -23 60 -11 24 -8 3 -5 -17 rrcurveto + endchar + + + 251 -83 callgsubr + + + 312 rmoveto + -38 -106 -5 -15 1 -10 8 -5 rlinecurve + 91 -53 17 -10 11 2 6 17 rlinecurve + 32 99 5 15 -3 11 -12 8 rlinecurve + -83 53 -9 6 -7 2 -5 -3 rlinecurve + -4 -1 -3 -4 -2 -6 rrcurveto + endchar + + + 122 527 rmoveto + -23 -63 -3 -9 0 -6 5 -3 rlinecurve + 55 -32 10 -6 7 1 3 10 rlinecurve + 19 60 3 9 -1 6 -8 5 rlinecurve + -49 32 -9 6 -6 -1 -3 -9 rlinecurve + endchar + + + -13 -50 -7 -4 -5 2 -19 2 -2 rrcurveto + 7 return + + + -104 callgsubr + endchar + + + 3 38 hhcurveto + 38 return + + + -3 -2 40 hvcurveto + -101 callgsubr + return + + + 5 11 -3 hvcurveto + -5 16 0 27 29 return + + + 5 -5 rrcurveto + 2 40 return + + + -5 -12 hhcurveto + -4 -5 -1 return + + + -75 callgsubr + -18 -76 callgsubr + return + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index 2cce9baf..0d2f9fe2 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -78,6 +78,16 @@ class SubsetTest(unittest.TestCase): # Tests # ----- + def test_layout_scripts(self): + _, fontpath = self.compile_font(self.getpath("layout_scripts.ttx"), ".otf") + subsetpath = self.temp_path(".otf") + subset.main([fontpath, "--glyphs=*", "--layout-features=*", + "--layout-scripts=latn,arab.URD,arab.dflt", + "--output-file=%s" % subsetpath]) + subsetfont = TTFont(subsetpath) + self.expect_ttx(subsetfont, self.getpath("expect_layout_scripts.ttx"), + ["GPOS", "GSUB"]) + def test_no_notdef_outline_otf(self): _, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf") subsetpath = self.temp_path(".otf") diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py index 0a1d9df9..76e9e61a 100644 --- a/Tests/ttLib/tables/C_O_L_R_test.py +++ b/Tests/ttLib/tables/C_O_L_R_test.py @@ -2,26 +2,32 @@ from fontTools import ttLib from fontTools.misc.testTools import getXML, parseXML from fontTools.ttLib.tables.C_O_L_R_ import table_C_O_L_R_ +import binascii import pytest -COLR_V0_DATA = ( - b"\x00\x00" # Version (0) - b"\x00\x01" # BaseGlyphRecordCount (1) - b"\x00\x00\x00\x0e" # Offset to BaseGlyphRecordArray from beginning of table (14) - b"\x00\x00\x00\x14" # Offset to LayerRecordArray from beginning of table (20) - b"\x00\x03" # LayerRecordCount (3) - b"\x00\x06" # BaseGlyphRecord[0].BaseGlyph (6) - b"\x00\x00" # BaseGlyphRecord[0].FirstLayerIndex (0) - b"\x00\x03" # BaseGlyphRecord[0].NumLayers (3) - b"\x00\x07" # LayerRecord[0].LayerGlyph (7) - b"\x00\x00" # LayerRecord[0].PaletteIndex (0) - b"\x00\x08" # LayerRecord[1].LayerGlyph (8) - b"\x00\x01" # LayerRecord[1].PaletteIndex (1) - b"\x00\t" # LayerRecord[2].LayerGlyph (9) - b"\x00\x02" # LayerRecord[3].PaletteIndex (2) +COLR_V0_SAMPLE = ( + (b"\x00\x00", "Version (0)"), + (b"\x00\x01", "BaseGlyphRecordCount (1)"), + ( + b"\x00\x00\x00\x0e", + "Offset to BaseGlyphRecordArray from beginning of table (14)", + ), + (b"\x00\x00\x00\x14", "Offset to LayerRecordArray from beginning of table (20)"), + (b"\x00\x03", "LayerRecordCount (3)"), + (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"), + (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"), + (b"\x00\x03", "BaseGlyphRecord[0].NumLayers (3)"), + (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"), + (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"), + (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"), + (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"), + (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"), + (b"\x00\x02", "LayerRecord[3].PaletteIndex (2)"), ) +COLR_V0_DATA = b"".join(t[0] for t in COLR_V0_SAMPLE) + COLR_V0_XML = [ '', @@ -37,6 +43,21 @@ def dump(table, ttFont=None): print("\n".join(getXML(table.toXML, ttFont))) +def diff_binary_fragments(font_bytes, expected_fragments): + pos = 0 + prev_desc = "" + for expected_bytes, description in expected_fragments: + actual_bytes = font_bytes[pos : pos + len(expected_bytes)] + assert ( + actual_bytes == expected_bytes + ), f'{description} (previous "{prev_desc}", bytes: {str(font_bytes[pos:pos+16])}' + pos += len(expected_bytes) + prev_desc = description + assert pos == len( + font_bytes + ), f"Leftover font bytes, used {pos} of {len(font_bytes)}" + + @pytest.fixture def font(): font = ttLib.TTFont() @@ -48,7 +69,7 @@ class COLR_V0_Test(object): def test_decompile_and_compile(self, font): colr = table_C_O_L_R_() colr.decompile(COLR_V0_DATA, font) - assert colr.compile(font) == COLR_V0_DATA + diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE) def test_decompile_and_dump_xml(self, font): colr = table_C_O_L_R_() @@ -62,145 +83,177 @@ class COLR_V0_Test(object): for name, attrs, content in parseXML(COLR_V0_XML): colr.fromXML(name, attrs, content, font) - assert colr.compile(font) == COLR_V0_DATA + diff_binary_fragments(colr.compile(font), COLR_V0_SAMPLE) + def test_round_trip_xml(self, font): + colr = table_C_O_L_R_() + for name, attrs, content in parseXML(COLR_V0_XML): + colr.fromXML(name, attrs, content, font) + compiled = colr.compile(font) -COLR_V1_DATA = ( - b"\x00\x01" # Version (1) - b"\x00\x01" # BaseGlyphRecordCount (1) - b"\x00\x00\x00\x1a" # Offset to BaseGlyphRecordArray from beginning of table (26) - b"\x00\x00\x00 " # Offset to LayerRecordArray from beginning of table (32) - b"\x00\x03" # LayerRecordCount (3) - b"\x00\x00\x00," # Offset to BaseGlyphV1List from beginning of table (44) - b"\x00\x00\x00\x81" # Offset to LayerV1List from beginning of table (129) - b"\x00\x00\x00\x00" # Offset to VarStore (NULL) - b"\x00\x06" # BaseGlyphRecord[0].BaseGlyph (6) - b"\x00\x00" # BaseGlyphRecord[0].FirstLayerIndex (0) - b"\x00\x03" # BaseGlyphRecord[0].NumLayers (3) - b"\x00\x07" # LayerRecord[0].LayerGlyph (7) - b"\x00\x00" # LayerRecord[0].PaletteIndex (0) - b"\x00\x08" # LayerRecord[1].LayerGlyph (8) - b"\x00\x01" # LayerRecord[1].PaletteIndex (1) - b"\x00\t" # LayerRecord[2].LayerGlyph (9) - b"\x00\x02" # LayerRecord[2].PaletteIndex (2) - b"\x00\x00\x00\x02" # BaseGlyphV1List.BaseGlyphCount (2) - b"\x00\n" # BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph (10) - b"\x00\x00\x00\x10" # Offset to Paint table from beginning of BaseGlyphV1List (16) - b"\x00\x0e" # BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14) - b"\x00\x00\x00\x16" # Offset to Paint table from beginning of BaseGlyphV1List (22) - b"\x01" # BaseGlyphV1Record[0].Paint.Format (1) - b"\x03" # BaseGlyphV1Record[0].Paint.NumLayers (3) - b"\x00\x00\x00\x00" # BaseGlyphV1Record[0].Paint.FirstLayerIndex (0) - b"\x08" # BaseGlyphV1Record[1].Paint.Format (8) - b"\x00\x00<" # Offset to SourcePaint from beginning of PaintComposite (60) - b"\x03" # BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3) - b"\x00\x00\x08" # Offset to BackdropPaint from beginning of PaintComposite (8) - b"\x07" # BaseGlyphV1Record[1].Paint.BackdropPaint.Format (7) - b"\x00\x004" # Offset to Paint from beginning of PaintTransform (52) - b"\x00\x01\x00\x00" # Affine2x3.xx.value (1.0) - b"\x00\x00\x00\x00" - b"\x00\x00\x00\x00" # Affine2x3.xy.value (0.0) - b"\x00\x00\x00\x00" - b"\x00\x00\x00\x00" # Affine2x3.yx.value (0.0) - b"\x00\x00\x00\x00" - b"\x00\x01\x00\x00" # Affine2x3.yy.value (1.0) - b"\x00\x00\x00\x00" - b"\x01,\x00\x00" # Affine2x3.dx.value (300.0) - b"\x00\x00\x00\x00" - b"\x00\x00\x00\x00" # Affine2x3.dy.value (0.0) - b"\x00\x00\x00\x00" - b"\x06" # BaseGlyphV1Record[1].Paint.SourcePaint.Format (6) - b"\x00\n" # BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10) - b"\x00\x00\x00\x03" # LayerV1List.LayerCount (3) - b"\x00\x00\x00\x10" # Offset to Paint table from beginning of LayerV1List (16) - b"\x00\x00\x00\x1f" # Offset to Paint table from beginning of LayerV1List (31) - b"\x00\x00\x00z" # Offset to Paint table from beginning of LayerV1List (122) - b"\x05" # LayerV1List.Paint[0].Format (5) - b"\x00\x00\x06" # Offset to Paint subtable from beginning of PaintGlyph (6) - b"\x00\x0b" # LayerV1List.Paint[0].Glyph (11) - b"\x02" # LayerV1List.Paint[0].Paint.Format (2) - b"\x00\x02" # Paint.Color.PaletteIndex (2) - b" \x00" # Paint.Color.Alpha.value (0.5) - b"\x00\x00\x00\x00" # Paint.Color.Alpha.varIdx (0) - b"\x05" # LayerV1List.Paint[1].Format (5) - b"\x00\x00\x06" # Offset to Paint subtable from beginning of PaintGlyph (6) - b"\x00\x0c" # LayerV1List.Paint[1].Glyph (12) - b"\x03" # LayerV1List.Paint[1].Paint.Format (3) - b"\x00\x00(" # Offset to ColorLine from beginning of PaintLinearGradient (40) - b"\x00\x01" # Paint.x0.value (1) - b"\x00\x00\x00\x00" # Paint.x0.varIdx (0) - b"\x00\x02" # Paint.y0.value (2) - b"\x00\x00\x00\x00" # Paint.y0.varIdx (0) - b"\xff\xfd" # Paint.x1.value (-3) - b"\x00\x00\x00\x00" # Paint.x1.varIdx (0) - b"\xff\xfc" # Paint.y1.value (-4) - b"\x00\x00\x00\x00" # Paint.y1.varIdx (0) - b"\x00\x05" # Paint.x2.value (5) - b"\x00\x00\x00\x00" # Paint.x2.varIdx (0) - b"\x00\x06" # Paint.y2.value (6) - b"\x00\x00\x00\x00" # Paint.y2.varIdx (0) - b"\x01" # ColorLine.Extend (1 or "repeat") - b"\x00\x03" # ColorLine.StopCount (3) - b"\x00\x00" # ColorLine.ColorStop[0].StopOffset.value (0.0) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[0].StopOffset.varIdx (0) - b"\x00\x03" # ColorLine.ColorStop[0].Color.PaletteIndex (3) - b"@\x00" # ColorLine.ColorStop[0].Color.Alpha.value (1.0) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[0].Color.Alpha.varIdx (0) - b" \x00" # ColorLine.ColorStop[1].StopOffset.value (0.5) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[1].StopOffset.varIdx (0) - b"\x00\x04" # ColorLine.ColorStop[1].Color.PaletteIndex (4) - b"@\x00" # ColorLine.ColorStop[1].Color.Alpha.value (1.0) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[1].Color.Alpha.varIdx (0) - b"@\x00" # ColorLine.ColorStop[2].StopOffset.value (1.0) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[2].StopOffset.varIdx (0) - b"\x00\x05" # ColorLine.ColorStop[2].Color.PaletteIndex (5) - b"@\x00" # ColorLine.ColorStop[2].Color.Alpha.value (1.0) - b"\x00\x00\x00\x00" # ColorLine.ColorStop[2].Color.Alpha.varIdx (0) - b"\x05" # LayerV1List.Paint[2].Format (5) - b"\x00\x00\x06" # Offset to Paint subtable from beginning of PaintGlyph (6) - b"\x00\r" # LayerV1List.Paint[2].Glyph (13) - b"\x07" # LayerV1List.Paint[2].Paint.Format (5) - b"\x00\x004" # Offset to Paint subtable from beginning of PaintTransform (52) - b"\xff\xf3\x00\x00" # Affine2x3.xx.value (-13) - b"\x00\x00\x00\x00" - b"\x00\x0e\x00\x00" # Affine2x3.xy.value (14) - b"\x00\x00\x00\x00" - b"\x00\x0f\x00\x00" # Affine2x3.yx.value (15) - b"\x00\x00\x00\x00" - b"\xff\xef\x00\x00" # Affine2x3.yy.value (-17) - b"\x00\x00\x00\x00" - b"\x00\x12\x00\x00" # Affine2x3.yy.value (18) - b"\x00\x00\x00\x00" - b"\x00\x13\x00\x00" # Affine2x3.yy.value (19) - b"\x00\x00\x00\x00" - b"\x04" # LayerV1List.Paint[2].Paint.Paint.Format (4) - b"\x00\x00(" # Offset to ColorLine from beginning of PaintRadialGradient (40) - b"\x00\x07" # Paint.x0.value (7) - b"\x00\x00\x00\x00" - b"\x00\x08" # Paint.y0.value (8) - b"\x00\x00\x00\x00" - b"\x00\t" # Paint.r0.value (9) - b"\x00\x00\x00\x00" - b"\x00\n" # Paint.x1.value (10) - b"\x00\x00\x00\x00" - b"\x00\x0b" # Paint.y1.value (11) - b"\x00\x00\x00\x00" - b"\x00\x0c" # Paint.r1.value (12) - b"\x00\x00\x00\x00" - b"\x00" # ColorLine.Extend (0 or "pad") - b"\x00\x02" # ColorLine.StopCount (2) - b"\x00\x00" # ColorLine.ColorStop[0].StopOffset.value (0.0) - b"\x00\x00\x00\x00" - b"\x00\x06" # ColorLine.ColorStop[0].Color.PaletteIndex (6) - b"@\x00" # ColorLine.ColorStop[0].Color.Alpha.value (1.0) - b"\x00\x00\x00\x00" - b"@\x00" # ColorLine.ColorStop[1].StopOffset.value (1.0) - b"\x00\x00\x00\x00" - b"\x00\x07" # ColorLine.ColorStop[1].Color.PaletteIndex (7) - b"\x19\x9a" # ColorLine.ColorStop[1].Color.Alpha.value (0.4) - b"\x00\x00\x00\x00" + colr = table_C_O_L_R_() + colr.decompile(compiled, font) + assert getXML(colr.toXML, font) == COLR_V0_XML + + +COLR_V1_SAMPLE = ( + (b"\x00\x01", "Version (1)"), + (b"\x00\x01", "BaseGlyphRecordCount (1)"), + ( + b"\x00\x00\x00\x1a", + "Offset to BaseGlyphRecordArray from beginning of table (26)", + ), + (b"\x00\x00\x00 ", "Offset to LayerRecordArray from beginning of table (32)"), + (b"\x00\x03", "LayerRecordCount (3)"), + (b"\x00\x00\x00,", "Offset to BaseGlyphV1List from beginning of table (44)"), + (b"\x00\x00\x00\x81", "Offset to LayerV1List from beginning of table (129)"), + (b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"), + (b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"), + (b"\x00\x00", "BaseGlyphRecord[0].FirstLayerIndex (0)"), + (b"\x00\x04", "BaseGlyphRecord[0].NumLayers (4)"), + (b"\x00\x07", "LayerRecord[0].LayerGlyph (7)"), + (b"\x00\x00", "LayerRecord[0].PaletteIndex (0)"), + (b"\x00\x08", "LayerRecord[1].LayerGlyph (8)"), + (b"\x00\x01", "LayerRecord[1].PaletteIndex (1)"), + (b"\x00\t", "LayerRecord[2].LayerGlyph (9)"), + (b"\x00\x02", "LayerRecord[2].PaletteIndex (2)"), + (b"\x00\x00\x00\x02", "BaseGlyphV1List.BaseGlyphCount (2)"), + (b"\x00\n", "BaseGlyphV1List.BaseGlyphV1Record[0].BaseGlyph (10)"), + ( + b"\x00\x00\x00\x10", + "Offset to Paint table from beginning of BaseGlyphV1List (16)", + ), + (b"\x00\x0e", "BaseGlyphV1List.BaseGlyphV1Record[1].BaseGlyph (14)"), + ( + b"\x00\x00\x00\x16", + "Offset to Paint table from beginning of BaseGlyphV1List (22)", + ), + (b"\x01", "BaseGlyphV1Record[0].Paint.Format (1)"), + (b"\x04", "BaseGlyphV1Record[0].Paint.NumLayers (4)"), + (b"\x00\x00\x00\x00", "BaseGlyphV1Record[0].Paint.FirstLayerIndex (0)"), + (b"\x0A", "BaseGlyphV1Record[1].Paint.Format (10)"), + (b"\x00\x00<", "Offset to SourcePaint from beginning of PaintComposite (60)"), + (b"\x03", "BaseGlyphV1Record[1].Paint.CompositeMode [SRC_OVER] (3)"), + (b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"), + (b"\x07", "BaseGlyphV1Record[1].Paint.BackdropPaint.Format (7)"), + (b"\x00\x00\x34", "Offset to Paint from beginning of PaintTransform (52)"), + (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (1.0)"), + (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (0.0)"), + (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.yx.value (0.0)"), + (b"\x00\x01\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (1.0)"), + (b"\x01\x2c\x00\x00\x00\x00\x00\x00", "Affine2x3.dx.value (300.0)"), + (b"\x00\x00\x00\x00\x00\x00\x00\x00", "Affine2x3.dy.value (0.0)"), + (b"\x06", "BaseGlyphV1Record[1].Paint.SourcePaint.Format (6)"), + (b"\x00\n", "BaseGlyphV1Record[1].Paint.SourcePaint.Glyph (10)"), + (b"\x00\x00\x00\x04", "LayerV1List.LayerCount (4)"), + ( + b"\x00\x00\x00\x14", + "First Offset to Paint table from beginning of LayerV1List (20)", + ), + ( + b"\x00\x00\x00\x1a", + "Second Offset to Paint table from beginning of LayerV1List (26)", + ), + ( + b"\x00\x00\x00u", + "Third Offset to Paint table from beginning of LayerV1List (117)", + ), + ( + b"\x00\x00\x00\xf6", + "Fourth Offset to Paint table from beginning of LayerV1List (246)", + ), + # PaintGlyph glyph00011 + (b"\x05", "LayerV1List.Paint[0].Format (5)"), + (b"\x00\x01\x28", "Offset24 to Paint subtable from beginning of PaintGlyph (296)"), + (b"\x00\x0b", "LayerV1List.Paint[0].Glyph (glyph00011)"), + # PaintGlyph glyph00012 + (b"\x05", "LayerV1List.Paint[1].Format (5)"), + (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), + (b"\x00\x0c", "LayerV1List.Paint[1].Glyph (glyph00012)"), + (b"\x03", "LayerV1List.Paint[1].Paint.Format (3)"), + (b"\x00\x00(", "Offset to ColorLine from beginning of PaintLinearGradient (40)"), + (b"\x00\x01", "Paint.x0.value (1)"), + (b"\x00\x00\x00\x00", "Paint.x0.varIdx (0)"), + (b"\x00\x02", "Paint.y0.value (2)"), + (b"\x00\x00\x00\x00", "Paint.y0.varIdx (0)"), + (b"\xff\xfd", "Paint.x1.value (-3)"), + (b"\x00\x00\x00\x00", "Paint.x1.varIdx (0)"), + (b"\xff\xfc", "Paint.y1.value (-4)"), + (b"\x00\x00\x00\x00", "Paint.y1.varIdx (0)"), + (b"\x00\x05", "Paint.x2.value (5)"), + (b"\x00\x00\x00\x00", "Paint.x2.varIdx (0)"), + (b"\x00\x06", "Paint.y2.value (6)"), + (b"\x00\x00\x00\x00", "Paint.y2.varIdx (0)"), + (b"\x01", "ColorLine.Extend (1; repeat)"), + (b"\x00\x03", "ColorLine.StopCount (3)"), + (b"\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].StopOffset.varIdx (0)"), + (b"\x00\x03", "ColorLine.ColorStop[0].Color.PaletteIndex (3)"), + (b"@\x00", "ColorLine.ColorStop[0].Color.Alpha.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[0].Color.Alpha.varIdx (0)"), + (b" \x00", "ColorLine.ColorStop[1].StopOffset.value (0.5)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].StopOffset.varIdx (0)"), + (b"\x00\x04", "ColorLine.ColorStop[1].Color.PaletteIndex (4)"), + (b"@\x00", "ColorLine.ColorStop[1].Color.Alpha.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.varIdx (0)"), + (b"@\x00", "ColorLine.ColorStop[2].StopOffset.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[2].StopOffset.varIdx (0)"), + (b"\x00\x05", "ColorLine.ColorStop[2].Color.PaletteIndex (5)"), + (b"@\x00", "ColorLine.ColorStop[2].Color.Alpha.value (1.0)"), + (b"\x00\x00\x00\x00", "ColorLine.ColorStop[2].Color.Alpha.varIdx (0)"), + # PaintGlyph glyph00013 + (b"\x05", "LayerV1List.Paint[2].Format (5)"), + (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), + (b"\x00\r", "LayerV1List.Paint[2].Glyph (13)"), + (b"\x07", "LayerV1List.Paint[2].Paint.Format (5)"), + (b"\x00\x00\x34", "Offset to Paint subtable from beginning of PaintTransform (52)"), + (b"\xff\xf3\x00\x00\x00\x00\x00\x00", "Affine2x3.xx.value (-13)"), + (b"\x00\x0e\x00\x00\x00\x00\x00\x00", "Affine2x3.xy.value (14)"), + (b"\x00\x0f\x00\x00\x00\x00\x00\x00", "Affine2x3.yx.value (15)"), + (b"\xff\xef\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (-17)"), + (b"\x00\x12\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (18)"), + (b"\x00\x13\x00\x00\x00\x00\x00\x00", "Affine2x3.yy.value (19)"), + (b"\x04", "LayerV1List.Paint[2].Paint.Paint.Format (4)"), + (b"\x00\x00(", "Offset to ColorLine from beginning of PaintRadialGradient (40)"), + (b"\x00\x07\x00\x00\x00\x00", "Paint.x0.value (7)"), + (b"\x00\x08\x00\x00\x00\x00", "Paint.y0.value (8)"), + (b"\x00\t\x00\x00\x00\x00", "Paint.r0.value (9)"), + (b"\x00\n\x00\x00\x00\x00", "Paint.x1.value (10)"), + (b"\x00\x0b\x00\x00\x00\x00", "Paint.y1.value (11)"), + (b"\x00\x0c\x00\x00\x00\x00", "Paint.r1.value (12)"), + (b"\x00", "ColorLine.Extend (0; pad)"), + (b"\x00\x02", "ColorLine.StopCount (2)"), + (b"\x00\x00\x00\x00\x00\x00", "ColorLine.ColorStop[0].StopOffset.value (0.0)"), + (b"\x00\x06", "ColorLine.ColorStop[0].Color.PaletteIndex (6)"), + (b"@\x00\x00\x00\x00\x00", "ColorLine.ColorStop[0].Color.Alpha.value (1.0)"), + (b"@\x00\x00\x00\x00\x00", "ColorLine.ColorStop[1].StopOffset.value (1.0)"), + (b"\x00\x07", "ColorLine.ColorStop[1].Color.PaletteIndex (7)"), + (b"\x19\x9a\x00\x00\x00\x00", "ColorLine.ColorStop[1].Color.Alpha.value (0.4)"), + # PaintRotate + (b"\x08", "LayerV1List.Paint[3].Format (8)"), + (b"\x00\x00\x1c", "Offset to Paint subtable from beginning of PaintRotate (28)"), + (b"\x00\x2d\x00\x00\x00\x00\x00\x00", "angle.value (45)"), + (b"\x00\xff\x00\x00\x00\x00\x00\x00", "centerX.value (255)"), + (b"\x01\x00\x00\x00\x00\x00\x00\x00", "centerY.value (256)"), + # PaintSkew + (b"\x09", "LayerV1List.Paint[3].Format (9)"), + (b"\x00\x00\x24", "Offset to Paint subtable from beginning of PaintSkew (36)"), + (b"\xff\xf5\x00\x00\x00\x00\x00\x00", "xSkewAngle (-11)"), + (b"\x00\x05\x00\x00\x00\x00\x00\x00", "ySkewAngle (5)"), + (b"\x00\xfd\x00\x00\x00\x00\x00\x00", "centerX.value (253)"), + (b"\x00\xfe\x00\x00\x00\x00\x00\x00", "centerY.value (254)"), + # PaintGlyph + (b"\x05", "LayerV1List.Paint[2].Format (5)"), + (b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"), + (b"\x00\x0b", "LayerV1List.Paint[2].Glyph (11)"), + # PaintSolid + (b"\x02", "LayerV1List.Paint[0].Paint.Format (2)"), + (b"\x00\x02", "Paint.Color.PaletteIndex (2)"), + (b" \x00", "Paint.Color.Alpha.value (0.5)"), + (b"\x00\x00\x00\x00", "Paint.Color.Alpha.varIdx (0)"), ) +COLR_V1_DATA = b"".join(t[0] for t in COLR_V1_SAMPLE) COLR_V1_XML = [ '', @@ -209,7 +262,7 @@ COLR_V1_XML = [ ' ', ' ', ' ', - ' ', + ' ', " ", "", "", @@ -232,13 +285,13 @@ COLR_V1_XML = [ ' ', ' ', ' ', - ' ', + ' ', ' ', " ", " ", ' ', ' ', - ' ', + ' ', ' ', ' ', " ", @@ -260,7 +313,7 @@ COLR_V1_XML = [ " ", "", "", - " ", + " ", ' ', ' ', " ", @@ -345,6 +398,26 @@ COLR_V1_XML = [ " ", ' ', " ", + ' ', + ' ', + ' ', + ' ', + " ", + ' ', + ' ', + " ", + " ", + ' ', + " ", + ' ', + ' ', + ' ', + ' ', + " ", + ' ', + ' ', + ' ', + " ", "", ] @@ -353,7 +426,7 @@ class COLR_V1_Test(object): def test_decompile_and_compile(self, font): colr = table_C_O_L_R_() colr.decompile(COLR_V1_DATA, font) - assert colr.compile(font) == COLR_V1_DATA + diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE) def test_decompile_and_dump_xml(self, font): colr = table_C_O_L_R_() @@ -366,5 +439,14 @@ class COLR_V1_Test(object): colr = table_C_O_L_R_() for name, attrs, content in parseXML(COLR_V1_XML): colr.fromXML(name, attrs, content, font) + diff_binary_fragments(colr.compile(font), COLR_V1_SAMPLE) - assert colr.compile(font) == COLR_V1_DATA + def test_round_trip_xml(self, font): + colr = table_C_O_L_R_() + for name, attrs, content in parseXML(COLR_V1_XML): + colr.fromXML(name, attrs, content, font) + compiled = colr.compile(font) + + colr = table_C_O_L_R_() + colr.decompile(compiled, font) + assert getXML(colr.toXML, font) == COLR_V1_XML diff --git a/Tests/ttLib/woff2_test.py b/Tests/ttLib/woff2_test.py index 2651e808..661fd448 100644 --- a/Tests/ttLib/woff2_test.py +++ b/Tests/ttLib/woff2_test.py @@ -21,7 +21,10 @@ import pytest haveBrotli = False try: - import brotli + try: + import brotlicffi as brotli + except ImportError: + import brotli haveBrotli = True except ImportError: pass diff --git a/Tests/ttx/ttx_test.py b/Tests/ttx/ttx_test.py index 753cc9ce..3d4c3f92 100644 --- a/Tests/ttx/ttx_test.py +++ b/Tests/ttx/ttx_test.py @@ -18,7 +18,10 @@ try: except ImportError: zopfli = None try: - import brotli + try: + import brotlicffi as brotli + except ImportError: + import brotli except ImportError: brotli = None diff --git a/Tests/ufoLib/UFO3_test.py b/Tests/ufoLib/UFO3_test.py index bed527db..c4218023 100644 --- a/Tests/ufoLib/UFO3_test.py +++ b/Tests/ufoLib/UFO3_test.py @@ -3940,6 +3940,26 @@ class UFO3WriteLayersTestCase(unittest.TestCase): result = list(writer.getGlyphSet("layer 2", defaultLayer=False).keys()) self.assertEqual(expected, result) + def testGetGlyphSetNoContents(self): + self.makeUFO() + os.remove(os.path.join(self.ufoPath, "glyphs.layer 1", "contents.plist")) + + reader = UFOReader(self.ufoPath, validate=True) + with self.assertRaises(GlifLibError): + reader.getGlyphSet("layer 1") + + writer = UFOWriter(self.ufoPath, validate=True) + with self.assertRaises(GlifLibError): + writer.getGlyphSet("layer 1", defaultLayer=False, expectContentsFile=True) + + # There's a separate code path for < v3 UFOs. + with open(os.path.join(self.ufoPath, "metainfo.plist"), "wb") as f: + plistlib.dump(dict(creator="test", formatVersion=2), f) + os.remove(os.path.join(self.ufoPath, "glyphs", "contents.plist")) + writer = UFOWriter(self.ufoPath, validate=True, formatVersion=2) + with self.assertRaises(GlifLibError): + writer.getGlyphSet(expectContentsFile=True) + # make a new font with two layers def testNewFontOneLayer(self): diff --git a/Tests/ufoLib/glifLib_test.py b/Tests/ufoLib/glifLib_test.py index 4c8d587c..3af0256c 100644 --- a/Tests/ufoLib/glifLib_test.py +++ b/Tests/ufoLib/glifLib_test.py @@ -54,6 +54,16 @@ class GlyphSetTests(unittest.TestCase): added, removed, "%s.glif file differs after round tripping" % glyphName) + def testContentsExist(self): + with self.assertRaises(GlifLibError): + GlyphSet( + self.dstDir, + ufoFormatVersion=2, + validateRead=True, + validateWrite=True, + expectContentsFile=True, + ) + def testRebuildContents(self): gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True) contents = gset.contents diff --git a/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz b/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz new file mode 100644 index 00000000..d78d494f Binary files /dev/null and b/Tests/ufoLib/testdata/TestFont1 (UFO3).ufoz differ diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx index 8112d0f6..ea1fa7e6 100644 --- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx +++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx @@ -86,26 +86,28 @@ - - - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx index f56b0503..1ad81185 100644 --- a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx +++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx @@ -86,26 +86,28 @@ - - - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index dfc10c34..da1f94f4 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -45,10 +45,15 @@ class BuildTest(unittest.TestCase): if self.tempdir: shutil.rmtree(self.tempdir) - @staticmethod - def get_test_input(test_file_or_folder): - path, _ = os.path.split(__file__) - return os.path.join(path, "data", test_file_or_folder) + def get_test_input(self, test_file_or_folder, copy=False): + parent_dir = os.path.dirname(__file__) + path = os.path.join(parent_dir, "data", test_file_or_folder) + if copy: + copied_path = os.path.join(self.tempdir, test_file_or_folder) + shutil.copy2(path, copied_path) + return copied_path + else: + return path @staticmethod def get_test_output(test_file_or_folder): @@ -314,11 +319,12 @@ class BuildTest(unittest.TestCase): ) def test_varlib_nonmarking_CFF2(self): - ds_path = self.get_test_input('TestNonMarkingCFF2.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestNonMarkingCFF2.designspace', copy=True) ttx_dir = self.get_test_input("master_non_marking_cff2") expected_ttx_path = self.get_test_output("TestNonMarkingCFF2.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestNonMarkingCFF2_'): self.compile_font(path, ".otf", self.tempdir) @@ -336,11 +342,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_CFF2(self): - ds_path = self.get_test_input('TestCFF2.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestCFF2.designspace', copy=True) ttx_dir = self.get_test_input("master_cff2") expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'): self.compile_font(path, ".otf", self.tempdir) @@ -358,11 +365,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_CFF2_from_CFF2(self): - ds_path = self.get_test_input('TestCFF2Input.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestCFF2Input.designspace', copy=True) ttx_dir = self.get_test_input("master_cff2_input") expected_ttx_path = self.get_test_output("BuildTestCFF2.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestCFF2_'): self.compile_font(path, ".otf", self.tempdir) @@ -380,11 +388,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_sparse_CFF2(self): - ds_path = self.get_test_input('TestSparseCFF2VF.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestSparseCFF2VF.designspace', copy=True) ttx_dir = self.get_test_input("master_sparse_cff2") expected_ttx_path = self.get_test_output("TestSparseCFF2VF.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'MasterSet_Kanji-'): self.compile_font(path, ".otf", self.tempdir) @@ -402,11 +411,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_vpal(self): - ds_path = self.get_test_input('test_vpal.designspace') + self.temp_dir() + + ds_path = self.get_test_input('test_vpal.designspace', copy=True) ttx_dir = self.get_test_input("master_vpal_test") expected_ttx_path = self.get_test_output("test_vpal.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'): self.compile_font(path, ".otf", self.tempdir) @@ -494,11 +504,12 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) def test_varlib_build_from_ttf_paths(self): - ds_path = self.get_test_input("Build.designspace") + self.temp_dir() + + ds_path = self.get_test_input("Build.designspace", copy=True) ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf") expected_ttx_path = self.get_test_output("BuildMain.ttx") - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'): self.compile_font(path, ".ttf", self.tempdir) @@ -643,12 +654,13 @@ class BuildTest(unittest.TestCase): assert all(tag in mvar_tags for tag in fontTools.varLib.mvar.MVAR_ENTRIES) def test_varlib_build_VVAR_CFF2(self): - ds_path = self.get_test_input('TestVVAR.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestVVAR.designspace', copy=True) ttx_dir = self.get_test_input("master_vvar_cff2") expected_ttx_name = 'TestVVAR' suffix = '.otf' - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'): font, savepath = self.compile_font(path, suffix, self.tempdir) @@ -668,12 +680,13 @@ class BuildTest(unittest.TestCase): self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix) def test_varlib_build_BASE(self): - ds_path = self.get_test_input('TestBASE.designspace') + self.temp_dir() + + ds_path = self.get_test_input('TestBASE.designspace', copy=True) ttx_dir = self.get_test_input("master_base_test") expected_ttx_name = 'TestBASE' suffix = '.otf' - self.temp_dir() for path in self.get_file_list(ttx_dir, '.ttx', 'TestBASE'): font, savepath = self.compile_font(path, suffix, self.tempdir) diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..7e37b03f --- /dev/null +++ b/mypy.ini @@ -0,0 +1,21 @@ +[mypy] +python_version = 3.6 +files = Lib/fontTools/misc/plistlib +follow_imports = silent +ignore_missing_imports = True +warn_redundant_casts = True +warn_unused_configs = True +warn_unused_ignores = True + +[mypy-fontTools.misc.plistlib] +check_untyped_defs = True +disallow_any_generics = True +disallow_incomplete_defs = True +disallow_subclassing_any = True +disallow_untyped_decorators = True +disallow_untyped_calls = False +disallow_untyped_defs = True +no_implicit_optional = True +no_implicit_reexport = True +strict_equality = True +warn_return_any = True diff --git a/requirements.txt b/requirements.txt index 940e2d4a..680ffbb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ fs==2.4.11 skia-pathops==0.5.1.post1; platform_python_implementation != "PyPy" # this is only required to run Tests/cu2qu/{ufo,cli}_test.py ufoLib2==0.6.2 +pyobjc==6.2.2; sys_platform == "darwin" diff --git a/setup.cfg b/setup.cfg index d00d4959..db6ac55a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 4.17.1 +current_version = 4.18.0 commit = True tag = False tag_name = {new_version} @@ -52,8 +52,3 @@ filterwarnings = ignore:writePlist:DeprecationWarning:plistlib_test ignore:some_function:DeprecationWarning:fontTools.ufoLib.utils ignore::DeprecationWarning:fontTools.varLib.designspace - -[egg_info] -tag_build = -tag_date = 0 - diff --git a/setup.py b/setup.py index 4d705a0f..df010048 100755 --- a/setup.py +++ b/setup.py @@ -82,8 +82,8 @@ extras_require = { # for fontTools.sfnt and fontTools.woff2: to compress/uncompress # WOFF 1.0 and WOFF 2.0 webfonts. "woff": [ - "brotli >= 1.0.1; platform_python_implementation != 'PyPy'", - "brotlipy >= 0.7.0; platform_python_implementation == 'PyPy'", + "brotli >= 1.0.1; platform_python_implementation == 'CPython'", + "brotlicffi >= 0.8.0; platform_python_implementation != 'CPython'", "zopfli >= 0.1.4", ], # for fontTools.unicode and fontTools.unicodedata: to use the latest version @@ -441,7 +441,7 @@ if ext_modules: setup_params = dict( name="fonttools", - version="4.17.1", + version="4.18.0", description="Tools to manipulate font files", author="Just van Rossum", author_email="just@letterror.com", diff --git a/tox.ini b/tox.ini index b2d1033f..8ced886b 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ deps = pytest pytest-randomly -rrequirements.txt - !nolxml: lxml==4.5.0 + !nolxml: lxml==4.6.1 extras = ufo woff -- cgit v1.2.3