Merge branch 'main' into HEAD
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 853d3f4..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-environment:
-  matrix:
-    - JOB: "2.7.13 32-bit"
-      PYTHON_HOME: "C:\\Python27"
-      TOXENV: "py27-cov"
-      TOXPYTHON: "C:\\Python27\\python.exe"
-
-    - JOB: "3.5.2 32-bit"
-      PYTHON_HOME: "C:\\Python35"
-      TOXENV: "py35-cov"
-      TOXPYTHON: "C:\\Python35\\python.exe"
-
-    - JOB: "3.6.0 32-bit"
-      PYTHON_HOME: "C:\\Python36"
-      TOXENV: "py36-cov"
-      TOXPYTHON: "C:\\Python36\\python.exe"
-
-    - JOB: "2.7.13 64-bit"
-      PYTHON_HOME: "C:\\Python27-x64"
-      TOXENV: "py27-cov"
-      TOXPYTHON: "C:\\Python27-x64\\python.exe"
-
-    - JOB: "3.5.2 64-bit"
-      PYTHON_HOME: "C:\\Python35-x64"
-      TOXENV: "py35-cov"
-      TOXPYTHON: "C:\\Python35-x64\\python.exe"
-
-    - JOB: "3.6.0 64-bit"
-      PYTHON_HOME: "C:\\Python36-x64"
-      TOXENV: "py36-cov"
-      TOXPYTHON: "C:\\Python36-x64\\python.exe"
-
-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"
-
-  # install the dependencies to run the tests
-  - "python -m pip install tox"
- 
-
-build: false
-
-test_script:
-  - "tox"
-
-after_test:
-  - "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 0000000..b3b2b25
--- /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 0000000..ec6abe2
--- /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:
+      - '*.*.*' # e.g. 1.0.0 or 20.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 0000000..89d668d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,91 @@
+name: Test
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    # https://github.community/t/github-actions-does-not-respect-skip-ci/17325/8
+    if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+    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 }}
+    if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+    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
+    if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+    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
+    if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
+    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 73b2713..eba633e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,59 @@
-# Byte-compiled / optimized files
+# Byte-compiled / optimized / DLL files
 __pycache__/
-*.py[co]
+*.py[cod]
 *$py.class
 
-# Distribution / Packaging
-*.egg
-*.egg-info
-*.eggs
-MANIFEST
-build
-dist
+# C extensions
+*.so
 
-# Unit test / coverage files
-.tox/*
-.cache/
+# Distribution / packaging
+.Python
+build/
+dist/
+.eggs/
+*.egg-info/
+*.egg
+MANIFEST
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
 .coverage
 .coverage.*
-htmlcov/
+.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 0000000..6ea2065
--- /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 0000000..928d658
--- /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 2598ccd..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-sudo: false
-
-language: python
-
-matrix:
-  fast_finish: true
-  include:
-    - python: 2.7
-      env: TOXENV=py27-cov
-    - python: 3.4
-      env: TOXENV=py34-cov
-    - python: 3.5
-      env: TOXENV=py35-cov
-    - python: 3.6
-      env:
-        - TOXENV=py36-cov
-        - BUILD_DIST=true
-    - python: pypy2.7-5.8.0
-      # disable coverage.py on pypy because of performance problems
-      env: TOXENV=pypy-nocov
-    - language: generic
-      os: osx
-      env: TOXENV=py27-cov
-    - language: generic
-      os: osx
-      env:
-        - TOXENV=py36-cov
-        - HOMEBREW_NO_AUTO_UPDATE=1
-    - env:
-        - TOXENV=py27-nocov
-        - PYENV_VERSION='2.7.6'
-        - PYENV_VERSION_STRING='Python 2.7.6'
-        - PYENV_ROOT=$HOME/.travis-pyenv
-        - TRAVIS_PYENV_VERSION='0.4.0'
-
-cache:
-  - pip
-  - directories:
-    - $HOME/.pyenv_cache
-
-before_install:
-  - source ./.travis/before_install.sh
-
-install:
-  - ./.travis/install.sh
-
-script:
-  - ./.travis/run.sh
-
-after_success:
-  - ./.travis/after_success.sh
-
-before_deploy:
-  - ./.travis/before_deploy.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"
-  # deploy to PyPI on tags
-  - provider: pypi
-    server: https://upload.pypi.org/legacy/
-    user: anthrotype
-    password:
-      secure: Dz3x8kh4ergBV6qZUgcGVDOEzjoCEFzzQiO5WVw4Zfi04DD8+d1ghmMz2BY4UvoVKSsFrfKDuEB3MCWyqewJsf/zoZQczk/vnWVFjERROieyO1Ckzpz/WkCvbjtniIE0lxzB7zorSV+kGI9VigGAaRlXJyU7mCFojeAFqD6cjS4=
-    skip_cleanup: true
-    distributions: pass
-    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 d113fe7..0000000
--- a/.travis/after_success.sh
+++ /dev/null
@@ -1,11 +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
diff --git a/.travis/before_deploy.sh b/.travis/before_deploy.sh
deleted file mode 100755
index 1ded8f0..0000000
--- a/.travis/before_deploy.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-# build sdist and wheel distribution packages in ./dist folder.
-# Travis runs the `before_deploy` stage before each deployment, but
-# we only want to build them once, as we want to use the same
-# files for both Github and PyPI
-if $(ls ./dist/fonttools*.zip > /dev/null 2>&1) && \
-		$(ls ./dist/fonttools*.whl > /dev/null 2>&1); then
-	echo "Distribution packages already exists; skipping"
-else
-	tox -e bdist
-fi
diff --git a/.travis/before_install.sh b/.travis/before_install.sh
deleted file mode 100755
index 8cc4edb..0000000
--- 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 03cc0b3..0000000
--- a/.travis/install.sh
+++ /dev/null
@@ -1,33 +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
-        # install virtualenv and create virtual environment
-        python -m pip install --user virtualenv
-        python -m virtualenv .venv/
-    elif [[ ${TOXENV} == *"py3"* ]]; then
-        # install/upgrade current python3 with homebrew
-        if brew list --versions python3 > /dev/null; then
-            brew upgrade python3
-        else
-            brew install python3
-        fi
-        # create virtual environment
-        python3 -m venv .venv/
-    else
-        echo "unsupported $TOXENV: "${TOXENV}
-        exit 1
-    fi
-    # activate virtual environment
-    source .venv/bin/activate
-fi
-
-python -m pip install $ci_requirements
diff --git a/.travis/run.sh b/.travis/run.sh
deleted file mode 100755
index 6804f7d..0000000
--- a/.travis/run.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-if [ "$TRAVIS_OS_NAME" == "osx" ]; then
-    source .venv/bin/activate
-fi
-
-tox
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..30f4976
--- /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 <cosimo@anthrotype.com>. 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 0000000..ea116c4
--- /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 <behdad@behdad.org>
+
+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/README.md b/Doc/README.md
new file mode 100644
index 0000000..26641b7
--- /dev/null
+++ b/Doc/README.md
@@ -0,0 +1,121 @@
+# fontTools Documentation
+
+The fontTools project documentation updates continuously on Read the Docs as the project source changes.  
+
+The documentation is hosted at https://fonttools.readthedocs.io/.
+
+## Contents
+
+- [How to Build Local Documentation](#how-to-build-local-documentation)
+- [Contributing to the fontTools Documentation](#contributing-to-the-documentation)
+- [Documentation License](#documentation-license)
+
+## How to Build Local Documentation
+
+### Install Dependencies
+
+You must have a Python 3 interpreter and the `pip` Python package manager installed on your system to build the fontTools documentation.
+
+Pull the fontTools project source files, create a Python virtual environment, and then install fontTools and the documentation build dependencies by executing the following commands in the root of the fontTools source repository:
+
+```
+$ pip install -e . [all]
+$ pip install -r Doc/docs-requirements.txt
+```
+
+### Build Documentation
+
+**With `make`**: execute the following command in the root of the repository:
+
+```
+$ make docs
+```
+
+**Without `make`**: execute the following command in the **`Doc` directory**:
+
+```
+$ sphinx-build -b html source build
+```
+
+Open the `Doc/build/html/index.html` file in your browser to view the documentation home page.
+
+## Contributing to the Documentation
+
+We highly encourage contributions!  Please follow the instructions below to improve the documentation.
+
+### Python Docstring Style
+
+We recommend the use of Python docstrings that follow [the Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#381-docstrings).  Our documentation build approach parses appropriately formatted docstrings into formatted documentation files.
+
+#### Function Documentation Example
+
+```python
+def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
+    """Fetches rows from a Bigtable.
+
+    Retrieves rows pertaining to the given keys from the Table instance
+    represented by big_table.  Silly things may happen if
+    other_silly_variable is not None.
+
+    Args:
+        big_table: An open Bigtable Table instance.
+        keys: A sequence of strings representing the key of each table row
+            to fetch.
+        other_silly_variable: Another optional variable, that has a much
+            longer name than the other args, and which does nothing.
+
+    Returns:
+        A dict mapping keys to the corresponding table row data
+        fetched. Each row is represented as a tuple of strings. For
+        example:
+
+        {'Serak': ('Rigel VII', 'Preparer'),
+         'Zim': ('Irk', 'Invader'),
+         'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+        If a key from the keys argument is missing from the dictionary,
+        then that row was not found in the table.
+
+    Raises:
+        IOError: An error occurred accessing the bigtable.Table object.
+    """
+```
+*Source: [Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md) (CC BY-SA 3.0)*
+
+#### Class Documentation Example
+
+```python
+class SampleClass(object):
+    """Summary of class here.
+
+    Longer class information....
+    Longer class information....
+
+    Attributes:
+        likes_spam: A boolean indicating if we like SPAM or not.
+        eggs: An integer count of the eggs we have laid.
+    """
+
+    def __init__(self, likes_spam=False):
+        """Inits SampleClass with blah."""
+        self.likes_spam = likes_spam
+        self.eggs = 0
+
+    def public_method(self):
+        """Performs operation blah."""
+```
+*Source: [Google Style Guide](https://github.com/google/styleguide/blob/gh-pages/pyguide.md) (CC BY-SA 3.0)*
+
+### Build Local Documentation and Review Your Changes
+
+Build a local set of HTML documentation files with the instructions above and review your changes.
+
+### Submit a Pull Request
+
+Submit a Github pull request with your proposed improvements to the documentation.  
+
+Thanks for your contribution!
+
+## Documentation License
+
+The fontTools documentation is released under a [CC BY-SA 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
diff --git a/Doc/docs-requirements.txt b/Doc/docs-requirements.txt
new file mode 100644
index 0000000..c62c8d1
--- /dev/null
+++ b/Doc/docs-requirements.txt
@@ -0,0 +1,3 @@
+sphinx==3.3.1
+sphinx_rtd_theme==0.5.0
+reportlab==3.5.55
diff --git a/Doc/source/afmLib.rst b/Doc/source/afmLib.rst
index f56d3c1..ab9f356 100644
--- a/Doc/source/afmLib.rst
+++ b/Doc/source/afmLib.rst
@@ -1,7 +1,8 @@
-######
-afmLib
-######
+###########################################
+afmLib: Read/write Adobe Font Metrics files
+###########################################
 
 .. automodule:: fontTools.afmLib
+
+.. autoclass:: fontTools.afmLib.AFM
    :members:
-   :undoc-members:
diff --git a/Doc/source/agl.rst b/Doc/source/agl.rst
index 0ecf14d..6e89857 100644
--- a/Doc/source/agl.rst
+++ b/Doc/source/agl.rst
@@ -1,7 +1,6 @@
-###
-agl
-###
+######################################
+agl: Interface to the Adobe Glyph List
+######################################
 
 .. automodule:: fontTools.agl
-   :members:
-   :undoc-members:
+   :members: toUnicode, UV2AGL, AGL2UV
diff --git a/Doc/source/assets/img/favicon.ico b/Doc/source/assets/img/favicon.ico
new file mode 100755
index 0000000..f55a275
--- /dev/null
+++ b/Doc/source/assets/img/favicon.ico
Binary files differ
diff --git a/Doc/source/cffLib.rst b/Doc/source/cffLib.rst
deleted file mode 100644
index 364824f..0000000
--- a/Doc/source/cffLib.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-######
-cffLib
-######
-
-.. automodule:: fontTools.cffLib
-   :members:
-   :undoc-members:
diff --git a/Doc/source/cffLib/index.rst b/Doc/source/cffLib/index.rst
new file mode 100644
index 0000000..281a0b1
--- /dev/null
+++ b/Doc/source/cffLib/index.rst
@@ -0,0 +1,53 @@
+##################################
+cffLib: read/write Adobe CFF fonts
+##################################
+
+.. automodule:: fontTools.cffLib
+
+This package also contains two modules for manipulating CFF format glyphs:
+
+.. toctree::
+   :maxdepth: 1
+
+   specializer
+   width
+
+.. autoclass:: fontTools.cffLib.CFFFontSet
+   :inherited-members:
+   :members:
+
+.. autoclass:: fontTools.cffLib.TopDict
+   :members:
+
+.. autoclass:: fontTools.cffLib.CharStrings
+   :members:
+
+.. autoclass:: fontTools.cffLib.Index
+   :members:
+
+.. autoclass:: fontTools.cffLib.GlobalSubrsIndex
+   :members:
+
+.. autoclass:: fontTools.cffLib.TopDictIndex
+   :members:
+
+.. autoclass:: fontTools.cffLib.CFFWriter
+   :members:
+
+.. autoclass:: fontTools.cffLib.IndexCompiler
+   :members:
+
+.. autoclass:: fontTools.cffLib.TopDictIndexCompiler
+   :members:
+
+.. autoclass:: fontTools.cffLib.FDArrayIndexCompiler
+   :members:
+
+.. autoclass:: fontTools.cffLib.GlobalSubrsCompiler
+   :members:
+
+.. autoclass:: fontTools.cffLib.SubrsCompiler
+   :members:
+
+.. autoclass:: fontTools.cffLib.CharStringsCompiler
+   :members:
diff --git a/Doc/source/cffLib/specializer.rst b/Doc/source/cffLib/specializer.rst
new file mode 100644
index 0000000..016a896
--- /dev/null
+++ b/Doc/source/cffLib/specializer.rst
@@ -0,0 +1,8 @@
+##############################################################
+specializer: T2CharString operator specializer and generalizer
+##############################################################
+
+.. automodule:: fontTools.cffLib.specializer
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/cffLib/width.rst b/Doc/source/cffLib/width.rst
new file mode 100644
index 0000000..68944da
--- /dev/null
+++ b/Doc/source/cffLib/width.rst
@@ -0,0 +1,6 @@
+#########################################
+width: T2CharString glyph width optimizer
+#########################################
+
+.. automodule:: fontTools.cffLib.width
+   :members: optimizeWidths, optimizeWidthsBruteforce
diff --git a/Doc/source/colorLib/index.rst b/Doc/source/colorLib/index.rst
new file mode 100644
index 0000000..d4eb9f8
--- /dev/null
+++ b/Doc/source/colorLib/index.rst
@@ -0,0 +1,11 @@
+#####################################################
+colorLib.builder: Build COLR/CPAL tables from scratch
+#####################################################
+
+.. automodule:: fontTools.colorLib.builder
+   :members: buildCPAL, buildCOLR, populateCOLRv0
+
+.. autoclass:: fontTools.colorLib.builder.ColorPaletteType
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/conf.py b/Doc/source/conf.py
index a3b2be2..82a5d57 100644
--- a/Doc/source/conf.py
+++ b/Doc/source/conf.py
@@ -25,40 +25,43 @@
 
 # If your documentation needs a minimal Sphinx version, state it here.
 #
-needs_sphinx = '1.3'
+needs_sphinx = "1.3"
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
+extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.coverage"]
 
-autodoc_mock_imports = ['gtk']
+autodoc_mock_imports = ["gtk"]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
 #
 # source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'fontTools'
-copyright = u'2017, Just van Rossum, Behdad Esfahbod et al.'
-author = u'Just van Rossum, Behdad Esfahbod et al.'
+project = u"fontTools"
+copyright = u"2020, Just van Rossum, Behdad Esfahbod, and the fontTools Authors. CC BY-SA 4.0"
+author = u"Just van Rossum, Behdad Esfahbod, and the fontTools Authors"
+
+# HTML page title
+html_title = "fontTools Documentation"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = u'3.10'
+version = u"4.0"
 # The full version, including alpha/beta/rc tags.
-release = u'3.10'
+release = u"4.0"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -73,10 +76,11 @@
 exclude_patterns = []
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+# pygments_style = "sphinx" (the default sphinx docs style on RTD)
+pygments_style = "default"
 
 # If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
+todo_include_todos = True
 
 
 # -- Options for HTML output ----------------------------------------------
@@ -84,24 +88,29 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'classic'
+html_theme = "sphinx_rtd_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
 #
-# html_theme_options = {}
+html_theme_options = {"display_version": False}
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = ["_static"]
+
+html_favicon = "assets/img/favicon.ico"
+
+# display the Sphinx attribution in the footer
+html_show_sphinx = False
 
 
 # -- Options for HTMLHelp output ------------------------------------------
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'fontToolsDoc'
+htmlhelp_basename = "fontToolsDoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
@@ -110,15 +119,12 @@
     # The paper size ('letterpaper' or 'a4paper').
     #
     # 'papersize': 'letterpaper',
-
     # The font size ('10pt', '11pt' or '12pt').
     #
     # 'pointsize': '10pt',
-
     # Additional stuff for the LaTeX preamble.
     #
     # 'preamble': '',
-
     # Latex figure (float) alignment
     #
     # 'figure_align': 'htbp',
@@ -128,8 +134,13 @@
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, 'fontTools.tex', u'fontTools Documentation',
-     u'Just van Rossum, Behdad Esfahbod et al.', 'manual'),
+    (
+        master_doc,
+        "fontTools.tex",
+        u"fontTools Documentation",
+        u"Just van Rossum, Behdad Esfahbod et al.",
+        "manual",
+    )
 ]
 
 
@@ -137,10 +148,7 @@
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'fonttools', u'fontTools Documentation',
-     [author], 1)
-]
+man_pages = [(master_doc, "fonttools", u"fontTools Documentation", [author], 1)]
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -149,8 +157,13 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    (master_doc, 'fontTools', u'fontTools Documentation',
-     author, 'fontTools', 'A library for manipulating fonts, written in Python.',
-     'Typography'),
+    (
+        master_doc,
+        "fontTools",
+        u"fontTools Documentation",
+        author,
+        "fontTools",
+        "A library for manipulating fonts, written in Python.",
+        "Typography",
+    )
 ]
-
diff --git a/Doc/source/cu2qu/index.rst b/Doc/source/cu2qu/index.rst
new file mode 100644
index 0000000..41730e5
--- /dev/null
+++ b/Doc/source/cu2qu/index.rst
@@ -0,0 +1,38 @@
+##########################################
+cu2qu: Cubic to quadratic curve conversion
+##########################################
+
+Routines for converting cubic curves to quadratic splines, suitable for use
+in OpenType to TrueType outline conversion.
+
+Conversion is carried out to a degree of tolerance provided by the user. While
+it is relatively easy to find the best *single* quadratic curve to represent a
+given cubic (see for example `this method from CAGD <https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/>`_),
+the best-fit method may not be sufficiently accurate for type design.
+
+Instead, this method chops the cubic curve into multiple segments before
+converting each cubic segment to a quadratic, in order to ensure that the
+resulting spline fits within the given tolerance.
+
+The basic curve conversion routines are implemented in the
+:mod:`fontTools.cu2qu.cu2qu` module; the :mod:`fontTools.cu2qu.ufo` module
+applies these routines to all of the curves in a UFO file or files; while the
+:mod:`fontTools.cu2qu.cli` module implements the ``fonttools cu2qu`` command
+for converting a UFO format font with cubic curves into one with quadratic
+curves.
+
+fontTools.cu2qu.cu2qu
+---------------------
+
+.. automodule:: fontTools.cu2qu.cu2qu
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+fontTools.cu2qu.ufo
+-------------------
+
+.. automodule:: fontTools.cu2qu.ufo
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/designspaceLib/index.rst b/Doc/source/designspaceLib/index.rst
new file mode 100644
index 0000000..2c33df7
--- /dev/null
+++ b/Doc/source/designspaceLib/index.rst
@@ -0,0 +1,18 @@
+##############
+designspaceLib
+##############
+
+MutatorMath started out with its own reader and writer for designspaces.
+Since then the use of designspace has broadened and it would be useful
+to have a reader and writer that are independent of a specific system.
+
+.. toctree::
+   :maxdepth: 1
+
+   readme
+   scripting
+
+.. automodule:: fontTools.designspaceLib
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/designspaceLib/readme.rst b/Doc/source/designspaceLib/readme.rst
new file mode 100644
index 0000000..c5757a6
--- /dev/null
+++ b/Doc/source/designspaceLib/readme.rst
@@ -0,0 +1,1150 @@
+#################################
+DesignSpaceDocument Specification
+#################################
+
+An object to read, write and edit interpolation systems for typefaces. Define sources, axes, rules and instances.
+
+-  `The Python API of the objects <#python-api>`_
+-  `The document XML structure <#document-xml-structure>`_
+
+
+**********
+Python API
+**********
+
+
+
+.. _designspacedocument-object:
+
+DesignSpaceDocument object
+==========================
+
+The DesignSpaceDocument object can read and write ``.designspace`` data.
+It imports the axes, sources and instances to very basic **descriptor**
+objects that store the data in attributes. Data is added to the document
+by creating such descriptor objects, filling them with data and then
+adding them to the document. This makes it easy to integrate this object
+in different contexts.
+
+The **DesignSpaceDocument** object can be subclassed to work with
+different objects, as long as they have the same attributes. Reader and
+Writer objects can be subclassed as well.
+
+**Note:** Python attribute names are usually camelCased, the
+corresponding `XML <#document-xml-structure>`_ attributes are usually
+all lowercase.
+
+.. example-1:
+
+.. code:: python
+
+    from fontTools.designspaceLib import DesignSpaceDocument
+    doc = DesignSpaceDocument()
+    doc.read("some/path/to/my.designspace")
+    doc.axes
+    doc.sources
+    doc.instances
+
+Attributes
+----------
+
+-  ``axes``: list of axisDescriptors
+-  ``sources``: list of sourceDescriptors
+-  ``instances``: list of instanceDescriptors
+-  ``rules``: list if ruleDescriptors
+-  ``readerClass``: class of the reader object
+-  ``writerClass``: class of the writer object
+-  ``lib``: dict for user defined, custom data that needs to be stored
+   in the designspace. Use reverse-DNS notation to identify your own data.
+   Respect the data stored by others.
+-  ``rulesProcessingLast``: This flag indicates whether the substitution rules should be applied before or after other glyph substitution features. False: before, True: after.
+
+Methods
+-------
+
+-  ``read(path)``: read a designspace file from ``path``
+-  ``write(path)``: write this designspace to ``path``
+-  ``addSource(aSourceDescriptor)``: add this sourceDescriptor to 
+   ``doc.sources``.
+-  ``addInstance(anInstanceDescriptor)``: add this instanceDescriptor
+   to ``doc.instances``.
+-  ``addAxis(anAxisDescriptor)``: add this instanceDescriptor to ``doc.axes``.
+-  ``newDefaultLocation()``: returns a dict with the default location
+   in designspace coordinates.
+-  ``updateFilenameFromPath(masters=True, instances=True, force=False)``:
+   set a descriptor filename attr from the path and this document.
+-  ``newAxisDescriptor()``: return a new axisDescriptor object.
+-  ``newSourceDescriptor()``: return a new sourceDescriptor object.
+-  ``newInstanceDescriptor()``: return a new instanceDescriptor object.
+-  ``getAxisOrder()``: return a list of axisnames
+-  ``findDefault()``: return the sourceDescriptor that is on the default
+   location. Returns None if there isn't one.
+-  ``normalizeLocation(aLocation)``: return a dict with normalized axis values.
+-  ``normalize()``: normalize the geometry of this designspace: scale all the
+   locations of all masters and instances to the ``-1 - 0 - 1`` value.
+-  ``loadSourceFonts()``: Ensure SourceDescriptor.font attributes are loaded,
+   and return list of fonts.
+-  ``tostring(encoding=None)``: Returns the designspace as a string. Default 
+   encoding `utf-8`.
+
+Class Methods
+-------------
+- ``fromfile(path)``
+- ``fromstring(string)``
+
+
+
+
+
+
+SourceDescriptor object
+=======================
+
+Attributes
+----------
+
+-  ``filename``: string. A relative path to the source file, **as it is
+   in the document**. MutatorMath + Varlib.
+-  ``path``: string. Absolute path to the source file, calculated from
+   the document path and the string in the filename attr. MutatorMath +
+   Varlib.
+-  ``layerName``: string. The name of the layer in the source to look for
+   outline data. Default ``None`` which means ``foreground``.
+-  ``font``: Any Python object. Optional. Points to a representation of
+   this source font that is loaded in memory, as a Python object
+   (e.g. a ``defcon.Font`` or a ``fontTools.ttFont.TTFont``). The default
+   document reader will not fill-in this attribute, and the default
+   writer will not use this attribute. It is up to the user of
+   ``designspaceLib`` to either load the resource identified by ``filename``
+   and store it in this field, or write the contents of this field to the
+   disk and make ``filename`` point to that.
+-  ``name``: string. Optional. Unique identifier name for this source,
+   if there is one or more ``instance.glyph`` elements in the document.
+   MutatorMath.
+-  ``location``: dict. Axis values for this source. MutatorMath + Varlib
+-  ``copyLib``: bool. Indicates if the contents of the font.lib need to
+   be copied to the instances. MutatorMath.
+-  ``copyInfo`` bool. Indicates if the non-interpolating font.info needs
+   to be copied to the instances. MutatorMath
+-  ``copyGroups`` bool. Indicates if the groups need to be copied to the
+   instances. MutatorMath.
+-  ``copyFeatures`` bool. Indicates if the feature text needs to be
+   copied to the instances. MutatorMath.
+-  ``muteKerning``: bool. Indicates if the kerning data from this source
+   needs to be muted (i.e. not be part of the calculations).
+   MutatorMath.
+-  ``muteInfo``: bool. Indicated if the interpolating font.info data for
+   this source needs to be muted. MutatorMath.
+-  ``mutedGlyphNames``: list. Glyphnames that need to be muted in the
+   instances. MutatorMath.
+-  ``familyName``: string. Family name of this source. Though this data
+   can be extracted from the font, it can be efficient to have it right
+   here. Varlib.
+-  ``styleName``: string. Style name of this source. Though this data
+   can be extracted from the font, it can be efficient to have it right
+   here. Varlib.
+
+.. code:: python
+
+    doc = DesignSpaceDocument()
+    s1 = SourceDescriptor()
+    s1.path = masterPath1
+    s1.name = "master.ufo1"
+    s1.font = defcon.Font("master.ufo1")
+    s1.copyLib = True
+    s1.copyInfo = True
+    s1.copyFeatures = True
+    s1.location = dict(weight=0)
+    s1.familyName = "MasterFamilyName"
+    s1.styleName = "MasterStyleNameOne"
+    s1.mutedGlyphNames.append("A")
+    s1.mutedGlyphNames.append("Z")
+    doc.addSource(s1)
+
+.. _instance-descriptor-object:
+
+InstanceDescriptor object
+=========================
+
+.. attributes-1:
+
+
+Attributes
+----------
+
+-  ``filename``: string. Relative path to the instance file, **as it is
+   in the document**. The file may or may not exist. MutatorMath.
+-  ``path``: string. Absolute path to the source file, calculated from
+   the document path and the string in the filename attr. The file may
+   or may not exist. MutatorMath.
+-  ``name``: string. Unique identifier name of the instance, used to
+   identify it if it needs to be referenced from elsewhere in the
+   document.
+-  ``location``: dict. Axis values for this source. MutatorMath +
+   Varlib.
+-  ``familyName``: string. Family name of this instance. MutatorMath +
+   Varlib.
+-  ``localisedFamilyName``: dict. A dictionary of localised family name
+   strings, keyed by language code.
+-  ``styleName``: string. Style name of this source. MutatorMath +
+   Varlib.
+-  ``localisedStyleName``: dict. A dictionary of localised stylename
+   strings, keyed by language code.
+-  ``postScriptFontName``: string. Postscript fontname for this
+   instance. MutatorMath.
+-  ``styleMapFamilyName``: string. StyleMap familyname for this
+   instance. MutatorMath.
+-  ``localisedStyleMapFamilyName``: A dictionary of localised style map
+   familyname strings, keyed by language code.
+-  ``localisedStyleMapStyleName``: A dictionary of localised style map
+   stylename strings, keyed by language code.
+-  ``styleMapStyleName``: string. StyleMap stylename for this instance.
+   MutatorMath.
+-  ``glyphs``: dict for special master definitions for glyphs. If glyphs
+   need special masters (to record the results of executed rules for
+   example). MutatorMath.
+-  ``kerning``: bool. Indicates if this instance needs its kerning
+   calculated. MutatorMath.
+-  ``info``: bool. Indicated if this instance needs the interpolating
+   font.info calculated.
+-  ``lib``: dict. Custom data associated with this instance.
+
+Methods
+-------
+
+These methods give easier access to the localised names.
+
+-  ``setStyleName(styleName, languageCode="en")``
+-  ``getStyleName(languageCode="en")``
+-  ``setFamilyName(familyName, languageCode="en")``
+-  ``getFamilyName(self, languageCode="en")``
+-  ``setStyleMapStyleName(styleMapStyleName, languageCode="en")``
+-  ``getStyleMapStyleName(languageCode="en")``
+-  ``setStyleMapFamilyName(styleMapFamilyName, languageCode="en")``
+-  ``getStyleMapFamilyName(languageCode="en")``
+
+Example
+-------
+
+.. code:: python
+
+    i2 = InstanceDescriptor()
+    i2.path = instancePath2
+    i2.familyName = "InstanceFamilyName"
+    i2.styleName = "InstanceStyleName"
+    i2.name = "instance.ufo2"
+    # anisotropic location
+    i2.location = dict(weight=500, width=(400,300))
+    i2.postScriptFontName = "InstancePostscriptName"
+    i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
+    i2.styleMapStyleName = "InstanceStyleMapStyleName"
+    glyphMasters = [dict(font="master.ufo1", glyphName="BB", location=dict(width=20,weight=20)), dict(font="master.ufo2", glyphName="CC", location=dict(width=900,weight=900))]
+    glyphData = dict(name="arrow", unicodeValue=1234)
+    glyphData['masters'] = glyphMasters
+    glyphData['note'] = "A note about this glyph"
+    glyphData['instanceLocation'] = dict(width=100, weight=120)
+    i2.glyphs['arrow'] = glyphData
+    i2.glyphs['arrow2'] = dict(mute=False)
+    i2.lib['com.coolDesignspaceApp.specimenText'] = 'Hamburgerwhatever'
+    doc.addInstance(i2)
+
+.. _axis-descriptor-object:
+
+AxisDescriptor object
+=====================
+
+-  ``tag``: string. Four letter tag for this axis. Some might be
+   registered at the `OpenType
+   specification <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__.
+   Privately-defined axis tags must begin with an uppercase letter and
+   use only uppercase letters or digits.
+-  ``name``: string. Name of the axis as it is used in the location
+   dicts. MutatorMath + Varlib.
+-  ``labelNames``: dict. When defining a non-registered axis, it will be
+   necessary to define user-facing readable names for the axis. Keyed by
+   xml:lang code. Values are required to be ``unicode`` strings, even if
+   they only contain ASCII characters.
+-  ``minimum``: number. The minimum value for this axis in user space.
+   MutatorMath + Varlib.
+-  ``maximum``: number. The maximum value for this axis in user space.
+   MutatorMath + Varlib.
+-  ``default``: number. The default value for this axis, i.e. when a new
+   location is created, this is the value this axis will get in user
+   space. MutatorMath + Varlib.
+-  ``map``: list of input / output values that can describe a warp
+   of user space to design space coordinates. If no map values are present, it is assumed user space is the same as design space, as
+   in [(minimum, minimum), (maximum, maximum)]. Varlib.
+
+.. code:: python
+
+    a1 = AxisDescriptor()
+    a1.minimum = 1
+    a1.maximum = 1000
+    a1.default = 400
+    a1.name = "weight"
+    a1.tag = "wght"
+    a1.labelNames[u'fa-IR'] = u"قطر"
+    a1.labelNames[u'en'] = u"Wéíght"
+    a1.map = [(1.0, 10.0), (400.0, 66.0), (1000.0, 990.0)]
+
+RuleDescriptor object
+=====================
+
+-  ``name``: string. Unique name for this rule. Can be used to
+   reference this rule data.
+-  ``conditionSets``: a list of conditionsets
+-  Each conditionset is a list of conditions.
+-  Each condition is a dict with ``name``, ``minimum`` and ``maximum`` keys.
+-  ``subs``: list of substitutions
+-  Each substitution is stored as tuples of glyphnames, e.g. ("a", "a.alt").
+-  Note: By default, rules are applied first, before other text shaping/OpenType layout, as they are part of the `Required Variation Alternates OpenType feature <https://docs.microsoft.com/en-us/typography/opentype/spec/features_pt#-tag-rvrn>`_. See `5.0 rules element`_ § Attributes.
+
+Evaluating rules
+----------------
+    
+-  ``evaluateRule(rule, location)``: Return True if any of the rule's conditionsets 
+   matches the given location.
+-  ``evaluateConditions(conditions, location)``: Return True if all the conditions
+   matches the given location. 
+-  ``processRules(rules, location, glyphNames)``: Apply all the rules to the list
+   of glyphNames. Return a new list of glyphNames with substitutions applied.
+
+.. code:: python
+
+    r1 = RuleDescriptor()
+    r1.name = "unique.rule.name"
+    r1.conditionSets.append([dict(name="weight", minimum=-10, maximum=10), dict(...)])
+    r1.conditionSets.append([dict(...), dict(...)])
+    r1.subs.append(("a", "a.alt"))
+
+
+.. _subclassing-descriptors:
+
+Subclassing descriptors
+=======================
+
+The DesignSpaceDocument can take subclassed Reader and Writer objects.
+This allows you to work with your own descriptors. You could subclass
+the descriptors. But as long as they have the basic attributes the
+descriptor does not need to be a subclass.
+
+.. code:: python
+
+    class MyDocReader(BaseDocReader):
+        ruleDescriptorClass = MyRuleDescriptor
+        axisDescriptorClass = MyAxisDescriptor
+        sourceDescriptorClass = MySourceDescriptor
+        instanceDescriptorClass = MyInstanceDescriptor
+
+    class MyDocWriter(BaseDocWriter):
+        ruleDescriptorClass = MyRuleDescriptor
+        axisDescriptorClass = MyAxisDescriptor
+        sourceDescriptorClass = MySourceDescriptor
+        instanceDescriptorClass = MyInstanceDescriptor
+
+    myDoc = DesignSpaceDocument(KeyedDocReader, KeyedDocWriter)
+
+**********************
+Document xml structure
+**********************
+
+-  The ``axes`` element contains one or more ``axis`` elements.
+-  The ``sources`` element contains one or more ``source`` elements.
+-  The ``instances`` element contains one or more ``instance`` elements.
+-  The ``rules`` element contains one or more ``rule`` elements.
+-  The ``lib`` element contains arbitrary data.
+
+.. code:: xml
+
+    <?xml version='1.0' encoding='utf-8'?>
+    <designspace format="3">
+        <axes>
+            <!-- define axes here -->
+            <axis../>
+        </axes>
+        <sources>
+            <!-- define masters here -->
+            <source../>
+        </sources>
+        <instances>
+            <!-- define instances here -->
+            <instance../>
+        </instances>
+        <rules>
+            <!-- define rules here -->
+            <rule../>
+        </rules>
+        <lib>
+            <dict>
+                <!-- store custom data here -->
+            </dict>
+        </lib>
+    </designspace>
+
+.. 1-axis-element:
+
+1. axis element
+===============
+
+-  Define a single axis
+-  Child element of ``axes``
+
+.. attributes-2:
+
+Attributes
+----------
+
+-  ``name``: required, string. Name of the axis that is used in the
+   location elements.
+-  ``tag``: required, string, 4 letters. Some axis tags are registered
+   in the OpenType Specification.
+-  ``minimum``: required, number. The minimum value for this axis, in user space coordinates.
+-  ``maximum``: required, number. The maximum value for this axis, in user space coordinates.
+-  ``default``: required, number. The default value for this axis, in user space coordinates.
+-  ``hidden``: optional, 0 or 1. Records whether this axis needs to be
+   hidden in interfaces.
+
+.. code:: xml
+
+    <axis name="weight" tag="wght" minimum="1" maximum="1000" default="400">
+
+.. 11-labelname-element:
+
+1.1 labelname element
+=====================
+
+-  Defines a human readable name for UI use.
+-  Optional for non-registered axis names.
+-  Can be localised with ``xml:lang``
+-  Child element of ``axis``
+
+.. attributes-3:
+
+Attributes
+----------
+
+-  ``xml:lang``: required, string. `XML language
+   definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
+
+Value
+-----
+
+-  The natural language name of this axis.
+
+.. example-2:
+
+Example
+-------
+
+.. code:: xml
+
+    <labelname xml:lang="fa-IR">قطر</labelname>
+    <labelname xml:lang="en">Wéíght</labelname>
+
+.. 12-map-element:
+
+1.2 map element
+===============
+
+-  Defines a single node in a series of input value (user space coordinate)
+   to output value (designspace coordinate) pairs.
+-  Together these values transform the designspace.
+-  Child of ``axis`` element.
+
+.. example-3:
+
+Example
+-------
+
+.. code:: xml
+
+    <map input="1.0" output="10.0" />
+    <map input="400.0" output="66.0" />
+    <map input="1000.0" output="990.0" />
+
+Example of all axis elements together:
+--------------------------------------
+
+.. code:: xml
+
+        <axes>
+            <axis default="1" maximum="1000" minimum="0" name="weight" tag="wght">
+                <labelname xml:lang="fa-IR">قطر</labelname>
+                <labelname xml:lang="en">Wéíght</labelname>
+            </axis>
+            <axis default="100" maximum="200" minimum="50" name="width" tag="wdth">
+                <map input="50.0" output="10.0" />
+                <map input="100.0" output="66.0" />
+                <map input="200.0" output="990.0" />
+            </axis>
+        </axes>
+
+.. 2-location-element:
+
+2. location element
+===================
+
+-  Defines a coordinate in the design space.
+-  Dictionary of axisname: axisvalue
+-  Used in ``source``, ``instance`` and ``glyph`` elements.
+
+.. 21-dimension-element:
+
+2.1 dimension element
+=====================
+
+-  Child element of ``location``
+
+.. attributes-4:
+
+Attributes
+----------
+
+-  ``name``: required, string. Name of the axis.
+-  ``xvalue``: required, number. The value on this axis.
+-  ``yvalue``: optional, number. Separate value for anisotropic
+   interpolations.
+
+.. example-4:
+
+Example
+-------
+
+.. code:: xml
+
+    <location>
+        <dimension name="width" xvalue="0.000000" />
+        <dimension name="weight" xvalue="0.000000" yvalue="0.003" />
+    </location>
+
+.. 3-source-element:
+
+3. source element
+=================
+
+-  Defines a single font or layer that contributes to the designspace.
+-  Child element of ``sources``
+-  Location in designspace coordinates.
+
+.. attributes-5:
+
+Attributes
+----------
+
+-  ``familyname``: optional, string. The family name of the source font.
+   While this could be extracted from the font data itself, it can be
+   more efficient to add it here.
+-  ``stylename``: optional, string. The style name of the source font.
+-  ``name``: required, string. A unique name that can be used to
+   identify this font if it needs to be referenced elsewhere.
+-  ``filename``: required, string. A path to the source file, relative
+   to the root path of this document. The path can be at the same level
+   as the document or lower.
+-  ``layer``: optional, string. The name of the layer in the source file.
+   If no layer attribute is given assume the foreground layer should be used.
+
+.. 31-lib-element:
+
+3.1 lib element
+===============
+
+There are two meanings for the ``lib`` element:
+
+1. Source lib
+    -  Example: ``<lib copy="1" />``
+    -  Child element of ``source``
+    -  Defines if the instances can inherit the data in the lib of this
+       source.
+    -  MutatorMath only
+
+2. Document and instance lib
+    - Example:
+
+      .. code:: xml
+
+        <lib>
+            <dict>
+                <key>...</key>
+                <string>The contents use the PLIST format.</string>
+            </dict>
+        </lib>
+
+    - Child element of ``designspace`` and ``instance``
+    - Contains arbitrary data about the whole document or about a specific
+      instance.
+    - Items in the dict need to use **reverse domain name notation** <https://en.wikipedia.org/wiki/Reverse_domain_name_notation>__
+
+.. 32-info-element:
+
+3.2 info element
+================
+
+-  ``<info copy="1" />``
+-  Child element of ``source``
+-  Defines if the instances can inherit the non-interpolating font info
+   from this source.
+-  MutatorMath
+
+.. 33-features-element:
+
+3.3 features element
+====================
+
+-  ``<features copy="1" />``
+-  Defines if the instances can inherit opentype feature text from this
+   source.
+-  Child element of ``source``
+-  MutatorMath only
+
+.. 34-glyph-element:
+
+3.4 glyph element
+=================
+
+-  Can appear in ``source`` as well as in ``instance`` elements.
+-  In a ``source`` element this states if a glyph is to be excluded from
+   the calculation.
+-  MutatorMath only
+
+.. attributes-6:
+
+Attributes
+----------
+
+-  ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
+   should be ignored as a master.
+-  ``<glyph mute="1" name="A"/>``
+-  MutatorMath only
+
+.. 35-kerning-element:
+
+3.5 kerning element
+===================
+
+-  ``<kerning mute="1" />``
+-  Can appear in ``source`` as well as in ``instance`` elements.
+
+.. attributes-7:
+
+Attributes
+----------
+
+-  ``mute``: required attribute, number 1 or 0. Indicate if the kerning
+   data from this source is to be excluded from the calculation.
+-  If the kerning element is not present, assume ``mute=0``, yes,
+   include the kerning of this source in the calculation.
+-  MutatorMath only
+
+.. example-5:
+
+Example
+-------
+
+.. code:: xml
+
+    <source familyname="MasterFamilyName" filename="masters/masterTest1.ufo" name="master.ufo1" stylename="MasterStyleNameOne">
+        <lib copy="1" />
+        <features copy="1" />
+        <info copy="1" />
+        <glyph mute="1" name="A" />
+        <glyph mute="1" name="Z" />
+        <location>
+            <dimension name="width" xvalue="0.000000" />
+            <dimension name="weight" xvalue="0.000000" />
+        </location>
+    </source>
+
+.. 4-instance-element:
+
+4. instance element
+===================
+
+-  Defines a single font that can be calculated with the designspace.
+-  Child element of ``instances``
+-  For use in Varlib the instance element really only needs the names
+   and the location. The ``glyphs`` element is not required.
+-  MutatorMath uses the ``glyphs`` element to describe how certain
+   glyphs need different masters, mainly to describe the effects of
+   conditional rules in Superpolator.
+-  Location in designspace coordinates.
+
+.. attributes-8:
+
+Attributes
+----------
+
+-  ``familyname``: required, string. The family name of the instance
+   font. Corresponds with ``font.info.familyName``
+-  ``stylename``: required, string. The style name of the instance font.
+   Corresponds with ``font.info.styleName``
+-  ``name``: required, string. A unique name that can be used to
+   identify this font if it needs to be referenced elsewhere.
+-  ``filename``: string. Required for MutatorMath. A path to the
+   instance file, relative to the root path of this document. The path
+   can be at the same level as the document or lower.
+-  ``postscriptfontname``: string. Optional for MutatorMath. Corresponds
+   with ``font.info.postscriptFontName``
+-  ``stylemapfamilyname``: string. Optional for MutatorMath. Corresponds
+   with ``styleMapFamilyName``
+-  ``stylemapstylename``: string. Optional for MutatorMath. Corresponds
+   with ``styleMapStyleName``
+
+Example for varlib
+------------------
+
+.. code:: xml
+
+    <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
+    <location>
+        <dimension name="width" xvalue="400" yvalue="300" />
+        <dimension name="weight" xvalue="66" />
+    </location>
+    <kerning />
+    <info />
+    <lib>
+        <dict>
+            <key>com.coolDesignspaceApp.specimenText</key>
+            <string>Hamburgerwhatever</string>
+        </dict>
+    </lib>
+    </instance>
+
+.. 41-glyphs-element:
+
+4.1 glyphs element
+==================
+
+-  Container for ``glyph`` elements.
+-  Optional
+-  MutatorMath only.
+
+.. 42-glyph-element:
+
+4.2 glyph element
+=================
+
+-  Child element of ``glyphs``
+-  May contain a ``location`` element.
+
+.. attributes-9:
+
+Attributes
+----------
+
+-  ``name``: string. The name of the glyph.
+-  ``unicode``: string. Unicode values for this glyph, in hexadecimal.
+   Multiple values should be separated with a space.
+-  ``mute``: optional attribute, number 1 or 0. Indicate if this glyph
+   should be supressed in the output.
+
+.. 421-note-element:
+
+4.2.1 note element
+==================
+
+-  String. The value corresponds to glyph.note in UFO.
+
+.. 422-masters-element:
+
+4.2.2 masters element
+=====================
+
+-  Container for ``master`` elements
+-  These ``master`` elements define an alternative set of glyph masters
+   for this glyph.
+
+.. 4221-master-element:
+
+4.2.2.1 master element
+======================
+
+-  Defines a single alternative master for this glyph.
+
+4.3 Localised names for instances
+=================================
+
+Localised names for instances can be included with these simple elements
+with an ``xml:lang`` attribute:
+`XML language definition <https://www.w3.org/International/questions/qa-when-xmllang.en>`__
+
+-  stylename
+-  familyname
+-  stylemapstylename
+-  stylemapfamilyname
+
+.. example-6:
+
+Example
+-------
+
+.. code:: xml
+
+    <stylename xml:lang="fr">Demigras</stylename>
+    <stylename xml:lang="ja">半ば</stylename>
+    <familyname xml:lang="fr">Montserrat</familyname>
+    <familyname xml:lang="ja">モンセラート</familyname>
+    <stylemapstylename xml:lang="de">Standard</stylemapstylename>
+    <stylemapfamilyname xml:lang="de">Montserrat Halbfett</stylemapfamilyname>
+    <stylemapfamilyname xml:lang="ja">モンセラート SemiBold</stylemapfamilyname>
+
+.. attributes-10:
+
+Attributes
+----------
+
+-  ``glyphname``: the name of the alternate master glyph.
+-  ``source``: the identifier name of the source this master glyph needs
+   to be loaded from
+
+.. example-7:
+
+Example
+-------
+
+.. code:: xml
+
+    <instance familyname="InstanceFamilyName" filename="instances/instanceTest2.ufo" name="instance.ufo2" postscriptfontname="InstancePostscriptName" stylemapfamilyname="InstanceStyleMapFamilyName" stylemapstylename="InstanceStyleMapStyleName" stylename="InstanceStyleName">
+    <location>
+        <dimension name="width" xvalue="400" yvalue="300" />
+        <dimension name="weight" xvalue="66" />
+    </location>
+    <glyphs>
+        <glyph name="arrow2" />
+        <glyph name="arrow" unicode="0x4d2 0x4d3">
+        <location>
+            <dimension name="width" xvalue="100" />
+            <dimension name="weight" xvalue="120" />
+        </location>
+        <note>A note about this glyph</note>
+        <masters>
+            <master glyphname="BB" source="master.ufo1">
+            <location>
+                <dimension name="width" xvalue="20" />
+                <dimension name="weight" xvalue="20" />
+            </location>
+            </master>
+        </masters>
+        </glyph>
+    </glyphs>
+    <kerning />
+    <info />
+    <lib>
+        <dict>
+            <key>com.coolDesignspaceApp.specimenText</key>
+            <string>Hamburgerwhatever</string>
+        </dict>
+    </lib>
+    </instance>
+
+.. 50-rules-element:
+
+5.0 rules element
+=================
+
+-  Container for ``rule`` elements
+-  The rules are evaluated in this order.
+
+Rules describe designspace areas in which one glyph should be replaced by another.
+A rule has a name and a number of conditionsets. The rule also contains a list of
+glyphname pairs: the glyphs that need to be substituted. For a rule to be triggered
+**only one** of the conditionsets needs to be true, ``OR``. Within a conditionset 
+**all** conditions need to be true, ``AND``.
+
+.. attributes-11:
+
+Attributes
+----------
+
+-  ``processing``: flag, optional. Valid values are [``first``, ``last``]. This flag indicates whether the substitution rules should be applied before or after other glyph substitution features.
+-  If no ``processing`` attribute is given, interpret as ``first``, and put the substitution rule in the `rvrn` feature.
+-  If ``processing`` is ``last``, put it in `rclt`.
+
+.. 51-rule-element:
+
+5.1 rule element
+================
+
+-  Defines a named rule.
+-  Each ``rule`` element contains one or more ``conditionset`` elements.
+-  **Only one** ``conditionset`` needs to be true to trigger the rule.
+-  **All** conditions in a ``conditionset`` must be true to make the ``conditionset`` true.
+-  For backwards compatibility a ``rule`` can contain ``condition`` elements outside of a conditionset. These are then understood to be part of a single, implied, ``conditionset``. Note: these conditions should be written wrapped in a conditionset.
+-  A rule element needs to contain one or more ``sub`` elements in order to be compiled to a variable font.
+-  Rules without sub elements should be ignored when compiling a font.
+-  For authoring tools it might be necessary to save designspace files without ``sub`` elements just because the work is incomplete.
+
+.. attributes-11:
+
+Attributes
+----------
+
+-  ``name``: optional, string. A unique name that can be used to
+   identify this rule if it needs to be referenced elsewhere. The name
+   is not important for compiling variable fonts.
+
+5.1.1 conditionset element
+=======================
+
+-  Child element of ``rule``
+-  Contains one or more ``condition`` elements.
+
+.. 512-condition-element:
+
+5.1.2 condition element
+=======================
+
+-  Child element of ``conditionset``
+-  Between the ``minimum`` and ``maximum`` this condition is ``True``.
+-  ``minimum`` and ``maximum`` are in designspace coordinates.
+-  If ``minimum`` is not available, assume it is ``axis.minimum``, mapped to designspace coordinates.
+-  If ``maximum`` is not available, assume it is ``axis.maximum``, mapped to designspace coordinates.
+-  The condition must contain at least a minimum or maximum or both.
+
+.. attributes-12:
+
+Attributes
+----------
+
+-  ``name``: string, required. Must match one of the defined ``axis``
+   name attributes.
+-  ``minimum``: number, required*. The low value, in designspace coordinates.
+-  ``maximum``: number, required*. The high value, in designspace coordinates.
+
+.. 513-sub-element:
+
+5.1.3 sub element
+=================
+
+-  Child element of ``rule``.
+-  Defines which glyph to replace when the rule evaluates to **True**.
+-  The ``sub`` element contains a pair of glyphnames. The ``name`` attribute is the glyph that should be visible when the rule evaluates to **False**. The ``with`` attribute is the glyph that should be visible when the rule evaluates to **True**.
+
+Axis values in Conditions are in designspace coordinates.
+
+.. attributes-13:
+
+Attributes
+----------
+
+-  ``name``: string, required. The name of the glyph this rule looks
+   for.
+-  ``with``: string, required. The name of the glyph it is replaced
+   with.
+
+.. example-8:
+
+Example
+-------
+
+Example with an implied ``conditionset``. Here the conditions are not
+contained in a conditionset. 
+
+.. code:: xml
+
+    <rules processing="last">
+        <rule name="named.rule.1">
+            <condition minimum="250" maximum="750" name="weight" />
+            <condition minimum="50" maximum="100" name="width" />
+            <sub name="dollar" with="dollar.alt"/>
+        </rule>
+    </rules>
+
+Example with ``conditionsets``. All conditions in a conditionset must be true.
+
+.. code:: xml
+
+    <rules>
+        <rule name="named.rule.2">
+            <conditionset>
+                <condition minimum="250" maximum="750" name="weight" />
+                <condition minimum="50" maximum="100" name="width" />
+            </conditionset>
+            <conditionset>
+                <condition ... />
+                <condition ... />
+            </conditionset>
+            <sub name="dollar" with="dollar.alt"/>
+        </rule>
+    </rules>
+
+.. 6-notes:
+
+6 Notes
+=======
+
+Paths and filenames
+-------------------
+
+A designspace file needs to store many references to UFO files.
+
+-  designspace files can be part of versioning systems and appear on
+   different computers. This means it is not possible to store absolute
+   paths.
+-  So, all paths are relative to the designspace document path.
+-  Using relative paths allows designspace files and UFO files to be
+   **near** each other, and that they can be **found** without enforcing
+   one particular structure.
+-  The **filename** attribute in the ``SourceDescriptor`` and
+   ``InstanceDescriptor`` classes stores the preferred relative path.
+-  The **path** attribute in these objects stores the absolute path. It
+   is calculated from the document path and the relative path in the
+   filename attribute when the object is created.
+-  Only the **filename** attribute is written to file.
+-  Both **filename** and **path** must use forward slashes (``/``) as
+   path separators, even on Windows.
+
+Right before we save we need to identify and respond to the following
+situations:
+
+In each descriptor, we have to do the right thing for the filename
+attribute. Before writing to file, the ``documentObject.updatePaths()``
+method prepares the paths as follows:
+
+**Case 1**
+
+::
+
+    descriptor.filename == None
+    descriptor.path == None
+
+**Action**
+
+-  write as is, descriptors will not have a filename attr. Useless, but
+   no reason to interfere.
+
+**Case 2**
+
+::
+
+    descriptor.filename == "../something"
+    descriptor.path == None
+
+**Action**
+
+-  write as is. The filename attr should not be touched.
+
+**Case 3**
+
+::
+
+    descriptor.filename == None
+    descriptor.path == "~/absolute/path/there"
+
+**Action**
+
+-  calculate the relative path for filename. We're not overwriting some
+   other value for filename, it should be fine.
+
+**Case 4**
+
+::
+
+    descriptor.filename == '../somewhere'
+    descriptor.path == "~/absolute/path/there"
+
+**Action**
+
+-  There is a conflict between the given filename, and the path. The
+   difference could have happened for any number of reasons. Assuming
+   the values were not in conflict when the object was created, either
+   could have changed. We can't guess.
+-  Assume the path attribute is more up to date. Calculate a new value
+   for filename based on the path and the document path.
+
+Recommendation for editors
+--------------------------
+
+-  If you want to explicitly set the **filename** attribute, leave the
+   path attribute empty.
+-  If you want to explicitly set the **path** attribute, leave the
+   filename attribute empty. It will be recalculated.
+-  Use ``documentObject.updateFilenameFromPath()`` to explicitly set the
+   **filename** attributes for all instance and source descriptors.
+
+.. 7-common-lib-key-registry:
+
+7 Common Lib Key Registry
+=========================
+
+public.skipExportGlyphs
+-----------------------
+
+This lib key works the same as the UFO lib key with the same name. The
+difference is that applications using a Designspace as the corner stone of the
+font compilation process should use the lib key in that Designspace instead of
+any of the UFOs. If the lib key is empty or not present in the Designspace, all
+glyphs should be exported, regardless of what the same lib key in any of the
+UFOs says.
+
+.. 8-implementation-and-differences:
+
+
+8 Implementation and differences
+================================
+
+The designspace format has gone through considerable development. 
+
+ -  the format was originally written for MutatorMath.
+ -  the format is now also used in fontTools.varlib.
+ -  not all values are be required by all implementations.
+
+8.1 Varlib vs. MutatorMath
+--------------------------
+
+There are some differences between the way MutatorMath and fontTools.varlib handle designspaces.
+
+ -  Varlib does not support anisotropic interpolations.
+ -  MutatorMath will extrapolate over the boundaries of
+    the axes. Varlib can not (at the moment).
+ -  Varlib requires much less data to define an instance than
+    MutatorMath.
+ -  The goals of Varlib and MutatorMath are different, so not all
+    attributes are always needed.
+
+8.2 Older versions
+------------------
+
+-  In some implementations that preceed Variable Fonts, the `copyInfo`
+   flag in a source indicated the source was to be treated as the default.
+   This is no longer compatible with the assumption that the default font
+   is located on the default value of each axis.
+-  Older implementations did not require axis records to be present in
+   the designspace file. The axis extremes for instance were generated 
+   from the locations used in the sources. This is no longer possible.
+
+8.3 Rules and generating static UFO instances
+---------------------------------------------
+
+When making instances as UFOs from a designspace with rules, it can
+be useful to evaluate the rules so that the characterset of the ufo 
+reflects, as much as possible, the state of a variable font when seen
+at the same location. This can be done by some swapping and renaming of
+glyphs.
+
+While useful for proofing or development work, it should be noted that
+swapping and renaming leaves the UFOs with glyphnames that are no longer
+descriptive. For instance, after a swap `dollar.bar` could contain a shape
+without a bar. Also, when the swapped glyphs are part of other GSUB variations
+it can become complex very quickly. So proceed with caution.
+
+ -  Assuming `rulesProcessingLast = True`:
+ -  We need to swap the glyphs so that the original shape is still available. 
+    For instance, if a rule swaps ``a`` for ``a.alt``, a glyph
+    that references ``a`` in a component would then show the new ``a.alt``.
+ -  But that can lead to unexpected results, the two glyphs may have different
+    widths or height. So, glyphs that are not specifically referenced in a rule
+    **should not change appearance**. That means that the implementation that swaps
+    ``a`` and ``a.alt`` also swap all components that reference these
+    glyphs in order to preserve their appearance.
+ -  The swap function also needs to take care of swapping the names in
+    kerning data and any GPOS code.
+
+
+.. 9-this-document
+
+9 This document
+===============
+
+-  Changes are to be expected.
+
+
diff --git a/Doc/source/designspaceLib/scripting.rst b/Doc/source/designspaceLib/scripting.rst
new file mode 100644
index 0000000..5a17816
--- /dev/null
+++ b/Doc/source/designspaceLib/scripting.rst
@@ -0,0 +1,253 @@
+#######################
+Scripting a designspace
+#######################
+
+It can be useful to build a designspace with a script rather than
+construct one with an interface like
+`Superpolator <http://superpolator.com>`__ or
+`DesignSpaceEditor <https://github.com/LettError/designSpaceRoboFontExtension>`__.
+
+`fontTools.designspaceLib` offers a some tools for building designspaces in
+Python. This document shows an example.
+
+********************************
+Filling-in a DesignSpaceDocument
+********************************
+
+So, suppose you installed the `fontTools` package through your favorite
+``git`` client.
+
+The ``DesignSpaceDocument`` object represents the document, whether it
+already exists or not. Make a new one:
+
+.. code:: python
+
+    from fontTools.designspaceLib import (DesignSpaceDocument, AxisDescriptor,
+                                          SourceDescriptor, InstanceDescriptor)
+    doc = DesignSpaceDocument()
+
+We want to create definitions for axes, sources and instances. That
+means there are a lot of attributes to set. The **DesignSpaceDocument
+object** uses objects to describe the axes, sources and instances. These
+are relatively simple objects, think of these as collections of
+attributes.
+
+-  Attributes of the :ref:`source-descriptor-object`
+-  Attributes of the :ref:`instance-descriptor-object`
+-  Attributes of the :ref:`axis-descriptor-object`
+-  Read about :ref:`subclassing-descriptors`
+
+Make an axis object
+===================
+
+Make a descriptor object and add it to the document.
+
+.. code:: python
+
+    a1 = AxisDescriptor()
+    a1.maximum = 1000
+    a1.minimum = 0
+    a1.default = 0
+    a1.name = "weight"
+    a1.tag = "wght"
+    doc.addAxis(a1)
+
+-  You can add as many axes as you need. OpenType has a maximum of
+   around 64K. DesignSpaceEditor has a maximum of 5.
+-  The ``name`` attribute is the name you'll be using as the axis name
+   in the locations.
+-  The ``tag`` attribute is the one of the registered `OpenType
+   Variation Axis
+   Tags <https://www.microsoft.com/typography/otspec/fvar.htm#VAT>`__
+-  The default master is expected at the intersection of all
+   default values of all axes. 
+
+Option: add label names
+-----------------------
+
+The **labelnames** attribute is intended to store localisable, human
+readable names for this axis if this is not an axis that is registered
+by OpenType. Think "The label next to the slider". The attribute is a
+dictionary. The key is the `xml language
+tag <https://www.w3.org/International/articles/language-tags/>`__, the
+value is a ``unicode`` string with the name. Whether or not this attribute is
+used depends on the font building tool, the operating system and the
+authoring software. This, at least, is the place to record it.
+
+.. code:: python
+
+    a1.labelNames['fa-IR'] = u"قطر"
+    a1.labelNames['en'] = u"Wéíght"
+
+Option: add a map
+-----------------
+
+The **map** attribute is a list of (input, output) mapping values
+intended for `axis variations table of
+OpenType <https://www.microsoft.com/typography/otspec/avar.htm>`__.
+
+.. code:: python
+
+    a1.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
+
+Make a source object
+====================
+
+A **source** is an object that points to a UFO file. It provides the
+outline geometry, kerning and font.info that we want to work with.
+
+.. code:: python
+
+    s0 = SourceDescriptor()
+    s0.path = "my/path/to/thin.ufo"
+    s0.name = "master.thin"
+    s0.location = dict(weight=0)
+    doc.addSource(s0)
+
+-  You'll need to have at least 2 sources in your document, so go ahead
+   and add another one.
+-  The **location** attribute is a dictionary with the designspace
+   location for this master.
+-  The axis names in the location have to match one of the ``axis.name``
+   values you defined before.
+-  The **path** attribute is the absolute path to an existing UFO.
+-  The **name** attribute is a unique name for this source used to keep
+   track it.
+-  The **layerName** attribute is the name of the UFO3 layer. Default None for ``foreground``.
+
+So go ahead and add another master:
+
+.. code:: python
+
+    s1 = SourceDescriptor()
+    s1.path = "my/path/to/bold.ufo"
+    s1.name = "master.bold"
+    s1.location = dict(weight=1000)
+    doc.addSource(s1)
+    
+
+Option: exclude glyphs
+----------------------
+
+By default all glyphs in a source will be processed. If you want to
+exclude certain glyphs, add their names to the ``mutedGlyphNames`` list.
+
+.. code:: python
+
+    s1.mutedGlyphNames = ["A.test", "A.old"]
+
+Make an instance object
+=======================
+
+An **instance** is description of a UFO that you want to generate with
+the designspace. For an instance you can define more things. If you want
+to generate UFO instances with MutatorMath then you can define different
+names and set flags for if you want to generate kerning and font info
+and so on. You can also set a path where to generate the instance.
+
+.. code:: python
+
+    i0 = InstanceDescriptor()
+    i0.familyName = "MyVariableFontPrototype"
+    i0.styleName = "Medium"
+    i0.path = os.path.join(root, "instances","MyVariableFontPrototype-Medium.ufo")
+    i0.location = dict(weight=500)
+    i0.kerning = True
+    i0.info = True
+    doc.addInstance(i0)
+
+-  The ``path`` attribute needs to be the absolute (real or intended)
+   path for the instance. When the document is saved this path will
+   written as relative to the path of the document.
+-  instance paths should be on the same level as the document, or in a
+   level below.
+-  Instances for MutatorMath will generate to UFO.
+-  Instances for variable fonts become **named instances**.
+
+Option: add more names
+----------------------
+
+If you want you can add a PostScript font name, a stylemap familyName
+and a stylemap styleName.
+
+.. code:: python
+
+    i0.postScriptFontName = "MyVariableFontPrototype-Medium"
+    i0.styleMapFamilyName = "MyVarProtoMedium"
+    i0.styleMapStyleName = "regular"
+
+Option: add glyph specific masters
+----------------------------------
+
+This bit is not supported by OpenType variable fonts, but it is needed
+for some designspaces intended for generating instances with
+MutatorMath. The code becomes a bit verbose, so you're invited to wrap
+this into something clever.
+
+.. code:: python
+
+    # we're making a dict with all sorts of
+    #(optional) settings for a glyph.
+    #In this example: the dollar.
+    glyphData = dict(name="dollar", unicodeValue=0x24)
+
+    # you can specify a different location for a glyph
+    glyphData['instanceLocation'] = dict(weight=500)
+
+    # You can specify different masters
+    # for this specific glyph.
+    # You can also give those masters new
+    # locations. It's a miniature designspace.
+    # Remember the "name" attribute we assigned to the sources?
+    glyphData['masters'] = [
+        dict(font="master.thin",
+            glyphName="dollar.nostroke",
+            location=dict(weight=0)),
+        dict(font="master.bold",
+            glyphName="dollar.nostroke",
+            location=dict(weight=1000)),
+        ]
+
+    # With all of that set up, store it in the instance.
+    i4.glyphs['dollar'] = glyphData
+
+******
+Saving
+******
+
+.. code:: python
+
+    path = "myprototype.designspace"
+    doc.write(path)
+
+************************
+Reading old designspaces
+************************
+
+Old designspace files might not contain ``axes`` definitions. This is
+how you reconstruct the axes from the extremes of the source locations
+
+.. code:: python
+
+    doc.checkAxes()
+
+This is how you check the default font.
+
+.. code:: python
+
+    doc.checkDefault()
+
+***********
+Generating?
+***********
+
+You can generate the UFO's with MutatorMath:
+
+.. code:: python
+
+    from mutatorMath.ufo import build
+    build("whatevs/myprototype.designspace")
+
+-  Assuming the outline data in the masters is compatible.
+
+Or you can use the file in making a **variable font** with varlib.
diff --git a/Doc/source/developer.rst b/Doc/source/developer.rst
new file mode 100644
index 0000000..3e259f0
--- /dev/null
+++ b/Doc/source/developer.rst
@@ -0,0 +1,115 @@
+.. _developerinfo:
+.. image:: ../../Icons/FontToolsIconGreenCircle.png
+   :width: 200px
+   :height: 200px
+   :alt: Font Tools
+   :align: center
+
+
+fontTools Developer Information
+===============================
+
+If you would like to contribute to the development of fontTools, you can clone the repository from GitHub, install the package in 'editable' mode and modify the source code in place. We recommend creating a virtual environment, using the Python 3 `venv <https://docs.python.org/3/library/venv.html>`_ module::
+
+    # download the source code to 'fonttools' folder
+    git clone https://github.com/fonttools/fonttools.git
+    cd fonttools
+
+    # create new virtual environment called e.g. 'fonttools-venv', or anything you like
+    python -m venv fonttools-venv
+
+    # source the `activate` shell script to enter the environment (Un*x)
+    . fonttools-venv/bin/activate
+
+    # to activate the virtual environment in Windows `cmd.exe`, do
+    fonttools-venv\Scripts\activate.bat
+
+    # install in 'editable' mode
+    pip install -e .
+
+
+.. note::
+
+    To exit a Python virtual environment, enter the command ``deactivate``.
+
+Testing
+-------
+
+To run the test suite, you need to install `pytest <http://docs.pytest.org/en/latest/>`__.
+When you run the ``pytest`` command, the tests will run against the
+installed fontTools package, or the first one found in the
+``PYTHONPATH``.
+
+You can also use `tox <https://tox.readthedocs.io/en/latest/>`__ to
+automatically run tests on different Python versions in isolated virtual
+environments::
+
+    pip install tox
+    tox
+
+
+.. note::
+
+    When you run ``tox`` without arguments, the tests are executed for all the environments listed in the ``tox.ini`` ``envlist``. The Python versions that are not available on your system ``PATH`` will be skipped.
+
+You can specify a particular testing environment list via the ``-e`` option, or the ``TOXENV`` environment variable::
+
+    tox -e py36
+    TOXENV="py36-cov,htmlcov" tox
+
+
+Development Community
+---------------------
+
+fontTools development is ongoing in an active community of developers that includes professional developers employed at major software corporations and type foundries as well as hobbyists.
+
+Feature requests and bug reports are always welcome at https://github.com/fonttools/fonttools/issues/
+
+The best place for end-user and developer discussion about the fontTools project is the `fontTools gitter channel <https://gitter.im/fonttools-dev/Lobby>`_. There is also a development https://groups.google.com/d/forum/fonttools-dev mailing list for continuous integration notifications.
+
+
+History
+-------
+
+The fontTools project was started by Just van Rossum in 1999, and was
+maintained as an open source project at
+http://sourceforge.net/projects/fonttools/. In 2008, Paul Wise (pabs3)
+began helping Just with stability maintenance. In 2013 Behdad Esfahbod
+began a friendly fork, thoroughly reviewing the codebase and making
+changes at https://github.com/behdad/fonttools to add new features and
+support for new font formats.
+
+
+Acknowledgments
+---------------
+
+In alphabetical order:
+
+Olivier Berten, Samyak Bhuta, Erik van Blokland, Petr van Blokland,
+Jelle Bosma, Sascha Brawer, Tom Byrer, Frédéric Coiffier, Vincent
+Connare, Dave Crossland, Simon Daniels, Peter Dekkers, Behdad Esfahbod,
+Behnam Esfahbod, Hannes Famira, Sam Fishman, Matt Fontaine, Yannis
+Haralambous, Greg Hitchcock, Jeremie Hornus, Khaled Hosny, John Hudson,
+Denis Moyogo Jacquerye, Jack Jansen, Tom Kacvinsky, Jens Kutilek,
+Antoine Leca, Werner Lemberg, Tal Leming, Peter Lofting, Cosimo Lupo,
+Masaya Nakamura, Dave Opstad, Laurence Penney, Roozbeh Pournader, Garret
+Rieger, Read Roberts, Guido van Rossum, Just van Rossum, Andreas Seidel,
+Georg Seifert, Chris Simpkins, Miguel Sousa, Adam Twardoch, Adrien Tétar, Vitaly Volkov,
+Paul Wise.
+
+License
+-------
+
+`MIT license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_.  See the full text of the license for details.
+
+.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
+   :target: https://travis-ci.org/fonttools/fonttools
+.. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
+   :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
+.. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
+   :target: https://codecov.io/gh/fonttools/fonttools
+.. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
+   :target: https://pypi.org/project/FontTools
+.. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
+   :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
+   :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
diff --git a/Doc/source/encodings.rst b/Doc/source/encodings.rst
deleted file mode 100644
index 8bcd38a..0000000
--- a/Doc/source/encodings.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-#########
-encodings
-#########
-
-.. automodule:: fontTools.encodings
-   :members:
-   :undoc-members:
-
-codecs
-------
-
-.. automodule:: fontTools.encodings.codecs
-   :members:
-   :undoc-members:
diff --git a/Doc/source/encodings/index.rst b/Doc/source/encodings/index.rst
new file mode 100644
index 0000000..32d13c7
--- /dev/null
+++ b/Doc/source/encodings/index.rst
@@ -0,0 +1,21 @@
+##################################################
+encodings: Support for OpenType-specific encodings
+##################################################
+
+fontTools includes support for some character encodings found in legacy Mac
+TrueType fonts. Many of these legacy encodings have found their way into the
+standard Python ``encodings`` library, but others still remain unimplemented.
+Importing ``fontTools.encodings.codecs`` will therefore add string ``encode``
+and ``decode`` support for the following encodings:
+
+* ``x_mac_japanese_ttx``
+* ``x_mac_trad_chinese_ttx``
+* ``x_mac_korean_ttx``
+* ``x_mac_simp_chinese_ttx``
+
+fontTools also includes a package (``fontTools.encodings.MacRoman``) which
+contains a mapping of glyph IDs to glyph names in the MacRoman character set::
+
+		>>> from fontTools.encodings.MacRoman import MacRoman
+		>>> MacRoman[26]
+		'twosuperior'
diff --git a/Doc/source/feaLib.rst b/Doc/source/feaLib.rst
deleted file mode 100644
index ad69217..0000000
--- a/Doc/source/feaLib.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-######
-feaLib
-######
-
-.. automodule:: fontTools.feaLib
-   :members:
-   :undoc-members:
-
-ast
----
-
-.. automodule:: fontTools.feaLib.ast
-   :members:
-   :undoc-members:
-
-builder
--------
-
-.. automodule:: fontTools.feaLib.builder
-   :members:
-   :undoc-members:
-
-error
------
-
-.. automodule:: fontTools.feaLib.parser
-   :members:
-   :undoc-members:
-
-lexer
------
-
-.. automodule:: fontTools.feaLib.lexer
-   :members:
-   :undoc-members:
-
-parser
-------
-
-.. automodule:: fontTools.feaLib.parser
-   :members:
-   :undoc-members:
-
diff --git a/Doc/source/feaLib/index.rst b/Doc/source/feaLib/index.rst
new file mode 100644
index 0000000..61ac31f
--- /dev/null
+++ b/Doc/source/feaLib/index.rst
@@ -0,0 +1,40 @@
+#########################################
+feaLib: Read/write OpenType feature files
+#########################################
+
+fontTools' ``feaLib`` allows for the creation and parsing of Adobe
+Font Development Kit for OpenType feature (``.fea``) files. The syntax
+of these files is described `here <https://adobe-type-tools.github.io/afdko/OpenTypeFeatureFileSpecification.html>`_.
+
+The :class:`fontTools.feaLib.parser.Parser` class can be used to parse files
+into an abstract syntax tree, and from there the
+:class:`fontTools.feaLib.builder.Builder` class can add features to an existing
+font file. You can inspect the parsed syntax tree, walk the tree and do clever
+things with it, and also generate your own feature files programmatically, by
+using the classes in the :mod:`fontTools.feaLib.ast` module.
+
+Parsing
+-------
+
+.. autoclass:: fontTools.feaLib.parser.Parser
+   :members: parse
+   :member-order: bysource
+
+Building
+---------
+
+.. automodule:: fontTools.feaLib.builder
+   :members: addOpenTypeFeatures, addOpenTypeFeaturesFromString
+
+Generation/Interrogation
+------------------------
+
+.. _`glyph-containing object`:
+.. _`glyph-containing objects`:
+
+In the below, a **glyph-containing object** is an object of one of the following
+classes: :class:`GlyphName`, :class:`GlyphClass`, :class:`GlyphClassName`.
+
+.. automodule:: fontTools.feaLib.ast
+   :member-order: bysource
+   :members:
diff --git a/Doc/source/index.rst b/Doc/source/index.rst
index a64dbe8..83bb850 100644
--- a/Doc/source/index.rst
+++ b/Doc/source/index.rst
@@ -1,28 +1,154 @@
+.. image:: ../../Icons/FontToolsIconGreenCircle.png
+   :width: 200px
+   :height: 200px
+   :alt: Font Tools
+   :align: center
+
+
 fontTools Docs
 ==============
 
+About
+-----
+
+fontTools is a family of libraries and utilities for manipulating fonts in Python.
+
+The project has an `MIT open-source license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_. Among other things this means you can use it free of charge.
+
+Installation
+------------
+
+.. note::
+
+    fontTools requires `Python <http://www.python.org/download/>`_ 3.6 or later.
+
+The package is listed in the Python Package Index (PyPI), so you can install it with `pip <https://pip.pypa.io/>`_::
+
+    pip install fonttools
+
+See the Optional Requirements section below for details about module-specific dependencies that must be installed in select cases.
+
+Utilities
+---------
+
+fontTools installs four command-line utilities:
+
+- ``pyftmerge``, a tool for merging fonts; see :py:mod:`fontTools.merge`
+- ``pyftsubset``, a tool for subsetting fonts; see :py:mod:`fontTools.subset`
+- ``ttx``, a tool for converting between OpenType binary fonts (OTF) and an XML representation (TTX); see :py:mod:`fontTools.ttx`
+- ``fonttools``, a "meta-tool" for accessing other components of the fontTools family.
+
+This last utility takes a subcommand, which could be one of:
+
+- ``cffLib.width``: Calculate optimum defaultWidthX/nominalWidthX values
+- ``cu2qu``: Convert a UFO font from cubic to quadratic curves
+- ``feaLib``: Add features from a feature file (.fea) into a OTF font
+- ``help``: Show this help
+- ``merge``: Merge multiple fonts into one
+- ``mtiLib``: Convert a FontDame OTL file to TTX XML
+- ``subset``: OpenType font subsetter and optimizer
+- ``ttLib.woff2``: Compress and decompress WOFF2 fonts
+- ``ttx``: Convert OpenType fonts to XML and back
+- ``varLib``: Build a variable font from a designspace file and masters
+- ``varLib.instancer``: Partially instantiate a variable font.
+- ``varLib.interpolatable``: Test for interpolatability issues between fonts
+- ``varLib.interpolate_layout``: Interpolate GDEF/GPOS/GSUB tables for a point on a designspace
+- ``varLib.models``: Normalize locations on a given designspace
+- ``varLib.mutator``: Instantiate a variation font
+- ``varLib.varStore``: Optimize a font's GDEF variation store
+
+Libraries
+---------
+
+The main library you will want to access when using fontTools for font
+engineering is likely to be :py:mod:`fontTools.ttLib`, which is the package
+for handling TrueType/OpenType fonts. However, there are many other
+libraries in the fontTools suite:
+
+- :py:mod:`fontTools.afmLib`: Module for reading and writing AFM files
+- :py:mod:`fontTools.agl`: Access to the Adobe Glyph List
+- :py:mod:`fontTools.cffLib`: Read/write tools for Adobe CFF fonts
+- :py:mod:`fontTools.colorLib`: Module for handling colors in CPAL/COLR fonts
+- :py:mod:`fontTools.cu2qu`: Module for cubic to quadratic conversion
+- :py:mod:`fontTools.designspaceLib`: Read and write designspace files
+- :py:mod:`fontTools.encodings`: Support for font-related character encodings
+- :py:mod:`fontTools.feaLib`: Read and read AFDKO feature files
+- :py:mod:`fontTools.fontBuilder`: Construct TTF/OTF fonts from scratch
+- :py:mod:`fontTools.merge`: Tools for merging font files
+- :py:mod:`fontTools.pens`: Various classes for manipulating glyph outlines
+- :py:mod:`fontTools.subset`: OpenType font subsetting and optimization
+- :py:mod:`fontTools.svgLib.path`: Library for drawing SVG paths onto glyphs
+- :py:mod:`fontTools.t1Lib`: Tools for PostScript Type 1 fonts (Python2 only)
+- :py:mod:`fontTools.tfmLib`: Module for reading TFM files
+- :py:mod:`fontTools.ttx`: Module for converting between OTF and XML representation
+- :py:mod:`fontTools.ufoLib`: Module for reading and writing UFO files
+- :py:mod:`fontTools.unicodedata`: Convert between Unicode and OpenType script information
+- :py:mod:`fontTools.varLib`: Module for dealing with 'gvar'-style font variations
+- :py:mod:`fontTools.voltLib`: Module for dealing with Visual OpenType Layout Tool (VOLT) files
+
+A selection of sample Python programs using these libaries can be found in the `Snippets directory <https://github.com/fonttools/fonttools/blob/master/Snippets/>`_ of the fontTools repository.
+
+Optional Dependencies
+---------------------
+
+The fontTools package currently has no (required) external dependencies
+besides the modules included in the Python Standard Library.
+However, a few extra dependencies are required to unlock optional features
+in some of the library modules. See the :doc:`optional requirements <./optional>`
+page for more information.
+
+Developer information
+---------------------
+
+Information for developers can be found :doc:`here <./developer>`.
+
+License
+-------
+
+`MIT license <https://github.com/fonttools/fonttools/blob/master/LICENSE>`_.  See the full text of the license for details.
+
+
+Table of Contents
+-----------------
+
 .. toctree::
-   :maxdepth: 1
+   :maxdepth: 2
+   :caption: Library
 
    afmLib
    agl
-   cffLib
-   inspect
-   encodings
-   feaLib
+   cffLib/index
+   colorLib/index
+   cu2qu/index
+   designspaceLib/index
+   encodings/index
+   feaLib/index
    merge
    misc/index
+   mtiLib
+   otlLib/index
    pens/index
-   subset
+   subset/index
+   svgLib/index
    t1Lib
+   tfmLib
    ttLib/index
    ttx
+   ufoLib/index
+   unicode
+   unicodedata/index
    varLib/index
    voltLib
 
-Indices and tables
-==================
 
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+.. |Travis Build Status| image:: https://travis-ci.org/fonttools/fonttools.svg
+   :target: https://travis-ci.org/fonttools/fonttools
+.. |Appveyor Build status| image:: https://ci.appveyor.com/api/projects/status/0f7fmee9as744sl7/branch/master?svg=true
+   :target: https://ci.appveyor.com/project/fonttools/fonttools/branch/master
+.. |Coverage Status| image:: https://codecov.io/gh/fonttools/fonttools/branch/master/graph/badge.svg
+   :target: https://codecov.io/gh/fonttools/fonttools
+.. |PyPI| image:: https://img.shields.io/pypi/v/fonttools.svg
+   :target: https://pypi.org/project/FontTools
+.. |Gitter Chat| image:: https://badges.gitter.im/fonttools-dev/Lobby.svg
+   :alt: Join the chat at https://gitter.im/fonttools-dev/Lobby
+   :target: https://gitter.im/fonttools-dev/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
\ No newline at end of file
diff --git a/Doc/source/inspect.rst b/Doc/source/inspect.rst
deleted file mode 100644
index e0f65c2..0000000
--- a/Doc/source/inspect.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-#######
-inspect
-#######
-
-.. automodule:: fontTools.inspect
-   :members:
-   :undoc-members:
diff --git a/Doc/source/merge.rst b/Doc/source/merge.rst
index 74cf9ad..3114615 100644
--- a/Doc/source/merge.rst
+++ b/Doc/source/merge.rst
@@ -1,7 +1,10 @@
-#####
-merge
-#####
+####################################
+merge: Merge multiple fonts into one
+####################################
 
-.. automodule:: fontTools.merge
+``fontTools.merge`` provides both a library and a command line interface
+(``fonttools merge``) for merging multiple fonts together.
+
+.. autoclass:: fontTools.merge.Merger
+   :inherited-members:
    :members:
-   :undoc-members:
diff --git a/Doc/source/misc/arrayTools.rst b/Doc/source/misc/arrayTools.rst
index acb2a51..d996cc2 100644
--- a/Doc/source/misc/arrayTools.rst
+++ b/Doc/source/misc/arrayTools.rst
@@ -1,7 +1,9 @@
-##########
-arrayTools
-##########
+#############################################
+arrayTools: Various array and rectangle tools
+#############################################
 
 .. automodule:: fontTools.misc.arrayTools
+   :member-order: bysource
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/bezierTools.rst b/Doc/source/misc/bezierTools.rst
index c5b4d2a..10ddc3a 100644
--- a/Doc/source/misc/bezierTools.rst
+++ b/Doc/source/misc/bezierTools.rst
@@ -1,7 +1,8 @@
-###########
-bezierTools
-###########
+####################################################
+bezierTools: Routines for working with Bezier curves
+####################################################
 
 .. automodule:: fontTools.misc.bezierTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/classifyTools.rst b/Doc/source/misc/classifyTools.rst
index b02b350..38c35d4 100644
--- a/Doc/source/misc/classifyTools.rst
+++ b/Doc/source/misc/classifyTools.rst
@@ -3,5 +3,6 @@
 #############
 
 .. automodule:: fontTools.misc.classifyTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/cliTools.rst b/Doc/source/misc/cliTools.rst
new file mode 100644
index 0000000..36b2aeb
--- /dev/null
+++ b/Doc/source/misc/cliTools.rst
@@ -0,0 +1,8 @@
+###################################################################
+cliTools: Utilities for command-line interfaces and console scripts
+###################################################################
+
+.. automodule:: fontTools.misc.cliTools
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/misc/eexec.rst b/Doc/source/misc/eexec.rst
index 8506f86..b229d58 100644
--- a/Doc/source/misc/eexec.rst
+++ b/Doc/source/misc/eexec.rst
@@ -1,7 +1,8 @@
-#####
-eexec
-#####
+###############################################################
+eexec: PostScript charstring encryption and decryption routines
+###############################################################
 
 .. automodule:: fontTools.misc.eexec
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/encodingTools.rst b/Doc/source/misc/encodingTools.rst
index ff29f66..4e4b719 100644
--- a/Doc/source/misc/encodingTools.rst
+++ b/Doc/source/misc/encodingTools.rst
@@ -3,5 +3,6 @@
 #############
 
 .. automodule:: fontTools.misc.encodingTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/etree.rst b/Doc/source/misc/etree.rst
new file mode 100644
index 0000000..4679a1d
--- /dev/null
+++ b/Doc/source/misc/etree.rst
@@ -0,0 +1,8 @@
+#####
+etree
+#####
+
+.. automodule:: fontTools.misc.etree
+   :inherited-members:
+   :members:
+   :undoc-members:
\ No newline at end of file
diff --git a/Doc/source/misc/filenames.rst b/Doc/source/misc/filenames.rst
new file mode 100644
index 0000000..2ebef35
--- /dev/null
+++ b/Doc/source/misc/filenames.rst
@@ -0,0 +1,7 @@
+##########################################################
+filenames: Implements UFO User Name to File Name Algorithm
+##########################################################
+
+.. automodule:: fontTools.misc.filenames
+   :members: userNameToFileName
+   :undoc-members:
diff --git a/Doc/source/misc/fixedTools.rst b/Doc/source/misc/fixedTools.rst
index 30a1f02..d3785f4 100644
--- a/Doc/source/misc/fixedTools.rst
+++ b/Doc/source/misc/fixedTools.rst
@@ -1,7 +1,8 @@
-##########
-fixedTools
-##########
+######################################################
+fixedTools: Tools for working with fixed-point numbers
+######################################################
 
 .. automodule:: fontTools.misc.fixedTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/index.rst b/Doc/source/misc/index.rst
index 29a7245..bd7db09 100644
--- a/Doc/source/misc/index.rst
+++ b/Doc/source/misc/index.rst
@@ -1,6 +1,9 @@
-####
-misc
-####
+##########################################################
+misc: Miscellaneous libraries helpful for font engineering
+##########################################################
+
+This is a collection of packages, most of which are used as internal support
+utilities by fontTools, but some of which may be more generally useful.
 
 .. toctree::
    :maxdepth: 2
@@ -8,16 +11,26 @@
    arrayTools
    bezierTools
    classifyTools
+   cliTools
    eexec
    encodingTools
+   etree
+   filenames
    fixedTools
+   intTools
    loggingTools
-   sstruct
+   macCreatorType
+   macRes
+   plistlib
    psCharStrings
+   psLib
+   psOperators
+   py23
+   sstruct
+   symfont
    testTools
    textTools
    timeTools
    transform
    xmlReader
    xmlWriter
-
diff --git a/Doc/source/misc/intTools.rst b/Doc/source/misc/intTools.rst
new file mode 100644
index 0000000..24ea231
--- /dev/null
+++ b/Doc/source/misc/intTools.rst
@@ -0,0 +1,6 @@
+###############################################
+intTools: Tools for working with integer values
+###############################################
+
+.. automodule:: fontTools.misc.intTools
+   :members:
diff --git a/Doc/source/misc/loggingTools.rst b/Doc/source/misc/loggingTools.rst
index fb8eab5..157e020 100644
--- a/Doc/source/misc/loggingTools.rst
+++ b/Doc/source/misc/loggingTools.rst
@@ -1,6 +1,6 @@
-############
-loggingTools
-############
+###################################################################
+loggingTools: tools for interfacing with the Python logging package
+###################################################################
 
 .. automodule:: fontTools.misc.loggingTools
    :members:
diff --git a/Doc/source/misc/macCreatorType.rst b/Doc/source/misc/macCreatorType.rst
new file mode 100644
index 0000000..809098d
--- /dev/null
+++ b/Doc/source/misc/macCreatorType.rst
@@ -0,0 +1,9 @@
+##############################################################
+macCreatorType: Functions for working with Mac file attributes
+##############################################################
+
+This module requires the `xattr <https://pypi.org/project/xattr/>`_ module
+to be installed in order to function correctly.
+
+.. automodule:: fontTools.misc.macCreatorType
+   :members:
diff --git a/Doc/source/misc/macRes.rst b/Doc/source/misc/macRes.rst
new file mode 100644
index 0000000..6fce8e2
--- /dev/null
+++ b/Doc/source/misc/macRes.rst
@@ -0,0 +1,11 @@
+############################################
+macRes: Tools for reading Mac resource forks
+############################################
+
+Classic Mac OS files are made up of two parts - the "data fork" which contains the file contents proper, and the "resource fork" which contains a number of structured data items called "resources". Some fonts, such as Mac "font suitcases" and Type 1 LWFN fonts, still use the resource fork for this kind of structured data, and so to read them, fontTools needs to have access to resource forks.
+
+The Inside Macintosh volume `More Macintosh Toolbox <https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf#page=34>`_ explains the structure of resource and data forks.
+
+.. automodule:: fontTools.misc.macRes
+   :members: ResourceReader, Resource
+   :member-order: bysource
\ No newline at end of file
diff --git a/Doc/source/misc/plistlib.rst b/Doc/source/misc/plistlib.rst
new file mode 100644
index 0000000..6857096
--- /dev/null
+++ b/Doc/source/misc/plistlib.rst
@@ -0,0 +1,9 @@
+#########################################
+plistlib: Tools for handling .plist files
+#########################################
+
+.. automodule:: fontTools.misc.plistlib
+   :members: totree, fromtree, load, loads, dump, dumps
+
+.. autoclass:: fontTools.misc.plistlib.Data
+   :members:
diff --git a/Doc/source/misc/psCharStrings.rst b/Doc/source/misc/psCharStrings.rst
index 3dc0dce..58497f6 100644
--- a/Doc/source/misc/psCharStrings.rst
+++ b/Doc/source/misc/psCharStrings.rst
@@ -3,5 +3,6 @@
 #############
 
 .. automodule:: fontTools.misc.psCharStrings
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/psLib.rst b/Doc/source/misc/psLib.rst
new file mode 100644
index 0000000..f3afa8b
--- /dev/null
+++ b/Doc/source/misc/psLib.rst
@@ -0,0 +1,8 @@
+#####
+psLib
+#####
+
+.. automodule:: fontTools.misc.psLib
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/misc/psOperators.rst b/Doc/source/misc/psOperators.rst
new file mode 100644
index 0000000..432274e
--- /dev/null
+++ b/Doc/source/misc/psOperators.rst
@@ -0,0 +1,8 @@
+###########
+psOperators
+###########
+
+.. automodule:: fontTools.misc.psOperators
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/misc/py23.rst b/Doc/source/misc/py23.rst
new file mode 100644
index 0000000..49a76bf
--- /dev/null
+++ b/Doc/source/misc/py23.rst
@@ -0,0 +1,8 @@
+####
+py23
+####
+
+.. automodule:: fontTools.misc.py23
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/misc/sstruct.rst b/Doc/source/misc/sstruct.rst
index 482aa23..0544795 100644
--- a/Doc/source/misc/sstruct.rst
+++ b/Doc/source/misc/sstruct.rst
@@ -3,5 +3,6 @@
 #######
 
 .. automodule:: fontTools.misc.sstruct
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/symfont.rst b/Doc/source/misc/symfont.rst
new file mode 100644
index 0000000..c189f3d
--- /dev/null
+++ b/Doc/source/misc/symfont.rst
@@ -0,0 +1,8 @@
+#######
+symfont
+#######
+
+.. automodule:: fontTools.misc.symfont
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/misc/testTools.rst b/Doc/source/misc/testTools.rst
index b3215ac..0019765 100644
--- a/Doc/source/misc/testTools.rst
+++ b/Doc/source/misc/testTools.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.testTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/textTools.rst b/Doc/source/misc/textTools.rst
index 754eb67..0044c08 100644
--- a/Doc/source/misc/textTools.rst
+++ b/Doc/source/misc/textTools.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.textTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/timeTools.rst b/Doc/source/misc/timeTools.rst
index f8d1508..c0a7199 100644
--- a/Doc/source/misc/timeTools.rst
+++ b/Doc/source/misc/timeTools.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.timeTools
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/transform.rst b/Doc/source/misc/transform.rst
index 9517fe0..44a3dbd 100644
--- a/Doc/source/misc/transform.rst
+++ b/Doc/source/misc/transform.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.transform
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/xmlReader.rst b/Doc/source/misc/xmlReader.rst
index 6e09354..d0b80f5 100644
--- a/Doc/source/misc/xmlReader.rst
+++ b/Doc/source/misc/xmlReader.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.xmlReader
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/misc/xmlWriter.rst b/Doc/source/misc/xmlWriter.rst
index f488183..5f7aaef 100644
--- a/Doc/source/misc/xmlWriter.rst
+++ b/Doc/source/misc/xmlWriter.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.misc.xmlWriter
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/mtiLib.rst b/Doc/source/mtiLib.rst
new file mode 100644
index 0000000..1bf74e1
--- /dev/null
+++ b/Doc/source/mtiLib.rst
@@ -0,0 +1,14 @@
+###########################################
+mtiLib: Read Monotype FontDame source files
+###########################################
+
+FontTools provides support for reading the OpenType layout tables produced by
+Monotype's FontDame and Font Chef font editors. These tables are written in a
+simple textual format. The ``mtiLib`` library parses these text files and creates
+table objects representing their contents.
+
+Additionally, ``fonttools mtiLib`` will convert a text file to TTX XML.
+
+
+.. automodule:: fontTools.mtiLib
+   :members: build, main
diff --git a/Doc/source/optional.rst b/Doc/source/optional.rst
new file mode 100644
index 0000000..09376a2
--- /dev/null
+++ b/Doc/source/optional.rst
@@ -0,0 +1,140 @@
+Optional Dependencies
+=====================
+
+The fonttools PyPI distribution also supports so-called "extras", i.e. a
+set of keywords that describe a group of additional dependencies, which can be
+used when installing via pip, or when specifying a requirement.
+For example:
+
+.. code:: sh
+
+    pip install fonttools[ufo,lxml,woff,unicode]
+
+This command will install fonttools, as well as the optional dependencies that
+are required to unlock the extra features named "ufo", etc.
+
+.. note::
+
+    Optional dependencies are detailed by module in the list below with the ``Extra`` setting that automates ``pip`` dependency installation when this is supported.
+
+
+
+:py:mod:`fontTools.misc.etree`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The module exports a ElementTree-like API for reading/writing XML files, and allows to use as the backend either the built-in ``xml.etree`` module or `lxml <https://lxml.de>`__. The latter is preferred whenever present, as it is generally faster and more secure.
+
+*Extra:* ``lxml``
+
+
+:py:mod:`fontTools.ufoLib`
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Package for reading and writing UFO source files; it requires:
+
+* `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem abstraction layer.
+
+* `enum34 <https://pypi.org/pypi/enum34>`__: backport for the built-in ``enum`` module (only required on Python < 3.4).
+
+*Extra:* ``ufo``
+
+
+:py:mod:`fontTools.ttLib.woff2`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module to compress/decompress WOFF 2.0 web fonts; it requires:
+
+* `brotli <https://pypi.python.org/pypi/Brotli>`__: Python bindings of the Brotli compression library.
+
+*Extra:* ``woff``
+
+
+:py:mod:`fontTools.unicode`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To display the Unicode character names when dumping the ``cmap`` table
+with ``ttx`` we use the ``unicodedata`` module in the Standard Library.
+The version included in there varies between different Python versions.
+To use the latest available data, you can install:
+
+* `unicodedata2 <https://pypi.python.org/pypi/unicodedata2>`__: ``unicodedata`` backport for Python 2.7
+  and 3.x updated to the latest Unicode version 12.0. Note this is not necessary if you use Python 3.8
+  as the latter already comes with an up-to-date ``unicodedata``.
+
+*Extra:* ``unicode``
+
+
+:py:mod:`fontTools.varLib.interpolatable`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module for finding wrong contour/component order between different masters.
+It requires one of the following packages in order to solve the so-called
+"minimum weight perfect matching problem in bipartite graphs", or
+the Assignment problem:
+
+* `scipy <https://pypi.python.org/pypi/scipy>`__: the Scientific Library for Python, which internally
+  uses `NumPy <https://pypi.python.org/pypi/numpy>`__ arrays and hence is very fast;
+* `munkres <https://pypi.python.org/pypi/munkres>`__: a pure-Python module that implements the Hungarian
+  or Kuhn-Munkres algorithm.
+
+*Extra:* ``interpolatable``
+
+
+:py:mod:`fontTools.varLib.plot`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Module for visualizing DesignSpaceDocument and resulting VariationModel.
+
+* `matplotlib <https://pypi.org/pypi/matplotlib>`__: 2D plotting library.
+
+*Extra:* ``plot``
+
+
+:py:mod:`fontTools.misc.symfont`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Advanced module for symbolic font statistics analysis; it requires:
+
+* `sympy <https://pypi.python.org/pypi/sympy>`__: the Python library for symbolic mathematics.
+
+*Extra:* ``symfont``
+
+
+:py:mod:`fontTools.t1Lib`
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To get the file creator and type of Macintosh PostScript Type 1 fonts
+on Python 3 you need to install the following module, as the old ``MacOS``
+module is no longer included in Mac Python:
+
+* `xattr <https://pypi.python.org/pypi/xattr>`__: Python wrapper for extended filesystem attributes
+  (macOS platform only).
+
+*Extra:* ``type1``
+
+
+:py:mod:`fontTools.pens.cocoaPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen for drawing glyphs with Cocoa ``NSBezierPath``, requires:
+
+* `PyObjC <https://pypi.python.org/pypi/pyobjc>`__: the bridge between Python and the Objective-C
+  runtime (macOS platform only).
+
+
+:py:mod:`fontTools.pens.qtPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen for drawing glyphs with Qt's ``QPainterPath``, requires:
+
+* `PyQt5 <https://pypi.python.org/pypi/PyQt5>`__: Python bindings for the Qt cross platform UI and
+  application toolkit.
+
+
+:py:mod:`fontTools.pens.reportLabPen`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pen to drawing glyphs as PNG images, requires:
+
+* `reportlab <https://pypi.python.org/pypi/reportlab>`__: Python toolkit for generating PDFs and
+  graphics.
diff --git a/Doc/source/otlLib/index.rst b/Doc/source/otlLib/index.rst
new file mode 100644
index 0000000..1984914
--- /dev/null
+++ b/Doc/source/otlLib/index.rst
@@ -0,0 +1,74 @@
+#################################################
+otlLib: Routines for working with OpenType Layout
+#################################################
+
+The ``fontTools.otlLib`` library provides routines to help you create the
+subtables and other data structures you need when you are editing a font's
+``GSUB`` and ``GPOS`` tables: substitution and positioning rules, anchors,
+lookups, coverage tables and so on.
+
+------------------------------------------
+High-level OpenType Layout Lookup Builders
+------------------------------------------
+
+.. automodule:: fontTools.otlLib.builder
+   :members: AlternateSubstBuilder, ChainContextPosBuilder, ChainContextSubstBuilder, LigatureSubstBuilder, MultipleSubstBuilder, CursivePosBuilder, MarkBasePosBuilder, MarkLigPosBuilder, MarkMarkPosBuilder, ReverseChainSingleSubstBuilder, SingleSubstBuilder, ClassPairPosSubtableBuilder, PairPosBuilder, SinglePosBuilder
+   :member-order: bysource
+
+--------------------------------------
+Common OpenType Layout Data Structures
+--------------------------------------
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildCoverage, buildLookup
+
+------------------------------------
+Low-level GSUB Table Lookup Builders
+------------------------------------
+
+These functions deal with the "simple" lookup types. See above for classes to
+help build more complex lookups (contextual and chaining lookups).
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildSingleSubstSubtable, buildMultipleSubstSubtable, buildAlternateSubstSubtable, buildLigatureSubstSubtable
+
+--------------------------
+GPOS Shared Table Builders
+--------------------------
+
+The functions help build the `GPOS shared tables <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#shared-tables-value-record-anchor-table-and-mark-array-table>`_
+as defined in the OpenType spec: value records, anchors, mark arrays and
+mark record tables.
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildValue, buildAnchor, buildMarkArray, buildDevice, buildBaseArray, buildComponentRecord, buildMarkArray, buildValue
+   :member-order: bysource
+
+------------------------------------
+Low-level GPOS Table Lookup Builders
+------------------------------------
+
+These functions deal with the "simple" lookup types. See above for classes to
+help build more complex lookups (contextual and chaining lookups).
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildCursivePosSubtable, buildLigatureArray, buildMarkBasePos, buildMarkBasePosSubtable, buildMarkLigPos, buildMarkLigPosSubtable, buildPairPosClassesSubtable, buildPairPosGlyphs, buildPairPosGlyphsSubtable, buildSinglePos, buildSinglePosSubtable
+   :member-order: bysource
+
+----------------------------
+GDEF Table Subtable Builders
+----------------------------
+
+These functions build subtables for elements of the ``GDEF`` table.
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildAttachList, buildLigCaretList, buildMarkGlyphSetsDef
+   :member-order: bysource
+
+------------------
+STAT Table Builder
+------------------
+
+.. automodule:: fontTools.otlLib.builder
+   :members: buildStatTable
+   :member-order: bysource
diff --git a/Doc/source/pens/areaPen.rst b/Doc/source/pens/areaPen.rst
index 03a751b..f3f21bb 100644
--- a/Doc/source/pens/areaPen.rst
+++ b/Doc/source/pens/areaPen.rst
@@ -3,5 +3,6 @@
 #######
 
 .. automodule:: fontTools.pens.areaPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/basePen.rst b/Doc/source/pens/basePen.rst
index f5965b1..87bf832 100644
--- a/Doc/source/pens/basePen.rst
+++ b/Doc/source/pens/basePen.rst
@@ -3,5 +3,6 @@
 #######
 
 .. automodule:: fontTools.pens.basePen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/boundsPen.rst b/Doc/source/pens/boundsPen.rst
index 8de5620..a0d9ab4 100644
--- a/Doc/source/pens/boundsPen.rst
+++ b/Doc/source/pens/boundsPen.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.pens.boundsPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/cocoaPen.rst b/Doc/source/pens/cocoaPen.rst
new file mode 100644
index 0000000..bbe8050
--- /dev/null
+++ b/Doc/source/pens/cocoaPen.rst
@@ -0,0 +1,8 @@
+########
+cocoaPen
+########
+
+.. automodule:: fontTools.pens.cocoaPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/cu2quPen.rst b/Doc/source/pens/cu2quPen.rst
new file mode 100644
index 0000000..4ae0249
--- /dev/null
+++ b/Doc/source/pens/cu2quPen.rst
@@ -0,0 +1,8 @@
+########
+cu2quPen
+########
+
+.. automodule:: fontTools.pens.cu2quPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/filterPen.rst b/Doc/source/pens/filterPen.rst
index 0b484a4..c79b944 100644
--- a/Doc/source/pens/filterPen.rst
+++ b/Doc/source/pens/filterPen.rst
@@ -3,5 +3,6 @@
 #########
 
 .. automodule:: fontTools.pens.filterPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/index.rst b/Doc/source/pens/index.rst
index 7e5a192..91175cf 100644
--- a/Doc/source/pens/index.rst
+++ b/Doc/source/pens/index.rst
@@ -5,14 +5,26 @@
 .. toctree::
    :maxdepth: 1
 
+   areaPen
    basePen
    boundsPen
-   pointInsidePen
+   cocoaPen
+   cu2quPen
    filterPen
-   transformPen
-   t2CharStringPen
-   statisticsPen
-   recordingPen
-   teePen
-   areaPen
+   momentsPen
    perimeterPen
+   pointInsidePen
+   pointPen
+   qtPen
+   recordingPen
+   reportLabPen
+   reverseContourPen
+   roundingPen
+   statisticsPen
+   svgPathPen
+   t2CharStringPen
+   teePen
+   transformPen
+   ttGlyphPen
+   wxPen
+
diff --git a/Doc/source/pens/momentsPen.rst b/Doc/source/pens/momentsPen.rst
new file mode 100644
index 0000000..4587f75
--- /dev/null
+++ b/Doc/source/pens/momentsPen.rst
@@ -0,0 +1,8 @@
+##########
+momentsPen
+##########
+
+.. automodule:: fontTools.pens.momentsPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/perimeterPen.rst b/Doc/source/pens/perimeterPen.rst
index 97fecca..c625a3d 100644
--- a/Doc/source/pens/perimeterPen.rst
+++ b/Doc/source/pens/perimeterPen.rst
@@ -3,5 +3,6 @@
 ############
 
 .. automodule:: fontTools.pens.perimeterPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/pointInsidePen.rst b/Doc/source/pens/pointInsidePen.rst
index 9954e47..81a4b2e 100644
--- a/Doc/source/pens/pointInsidePen.rst
+++ b/Doc/source/pens/pointInsidePen.rst
@@ -3,5 +3,6 @@
 ##############
 
 .. automodule:: fontTools.pens.pointInsidePen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/pointPen.rst b/Doc/source/pens/pointPen.rst
new file mode 100644
index 0000000..09b9897
--- /dev/null
+++ b/Doc/source/pens/pointPen.rst
@@ -0,0 +1,8 @@
+########
+pointPen
+########
+
+.. automodule:: fontTools.pens.pointPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/qtPen.rst b/Doc/source/pens/qtPen.rst
new file mode 100644
index 0000000..bfaa9b5
--- /dev/null
+++ b/Doc/source/pens/qtPen.rst
@@ -0,0 +1,8 @@
+#####
+qtPen
+#####
+
+.. automodule:: fontTools.pens.qtPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/recordingPen.rst b/Doc/source/pens/recordingPen.rst
index 69e7ceb..ee9178c 100644
--- a/Doc/source/pens/recordingPen.rst
+++ b/Doc/source/pens/recordingPen.rst
@@ -3,5 +3,6 @@
 ############
 
 .. automodule:: fontTools.pens.recordingPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/reportLabPen.rst b/Doc/source/pens/reportLabPen.rst
new file mode 100644
index 0000000..7fe8784
--- /dev/null
+++ b/Doc/source/pens/reportLabPen.rst
@@ -0,0 +1,8 @@
+############
+reportLabPen
+############
+
+.. automodule:: fontTools.pens.reportLabPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/reverseContourPen.rst b/Doc/source/pens/reverseContourPen.rst
new file mode 100644
index 0000000..8178e2c
--- /dev/null
+++ b/Doc/source/pens/reverseContourPen.rst
@@ -0,0 +1,8 @@
+#################
+reverseContourPen
+#################
+
+.. automodule:: fontTools.pens.reverseContourPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/roundingPen.rst b/Doc/source/pens/roundingPen.rst
new file mode 100644
index 0000000..7eb4214
--- /dev/null
+++ b/Doc/source/pens/roundingPen.rst
@@ -0,0 +1,8 @@
+###########
+roundingPen
+###########
+
+.. automodule:: fontTools.pens.roundingPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/statisticsPen.rst b/Doc/source/pens/statisticsPen.rst
index efa1608..e06e322 100644
--- a/Doc/source/pens/statisticsPen.rst
+++ b/Doc/source/pens/statisticsPen.rst
@@ -3,5 +3,6 @@
 #############
 
 .. automodule:: fontTools.pens.statisticsPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/svgPathPen.rst b/Doc/source/pens/svgPathPen.rst
new file mode 100644
index 0000000..45bf151
--- /dev/null
+++ b/Doc/source/pens/svgPathPen.rst
@@ -0,0 +1,8 @@
+##########
+svgPathPen
+##########
+
+.. automodule:: fontTools.pens.svgPathPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/t2CharStringPen.rst b/Doc/source/pens/t2CharStringPen.rst
index d58c67a..9d55391 100644
--- a/Doc/source/pens/t2CharStringPen.rst
+++ b/Doc/source/pens/t2CharStringPen.rst
@@ -3,5 +3,6 @@
 ###############
 
 .. automodule:: fontTools.pens.t2CharStringPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/teePen.rst b/Doc/source/pens/teePen.rst
index 7a0313c..2a4558d 100644
--- a/Doc/source/pens/teePen.rst
+++ b/Doc/source/pens/teePen.rst
@@ -3,5 +3,6 @@
 ######
 
 .. automodule:: fontTools.pens.teePen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/transformPen.rst b/Doc/source/pens/transformPen.rst
index 5b414f8..3bb802a 100644
--- a/Doc/source/pens/transformPen.rst
+++ b/Doc/source/pens/transformPen.rst
@@ -3,5 +3,6 @@
 ############
 
 .. automodule:: fontTools.pens.transformPen
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/pens/ttGlyphPen.rst b/Doc/source/pens/ttGlyphPen.rst
new file mode 100644
index 0000000..e1bf701
--- /dev/null
+++ b/Doc/source/pens/ttGlyphPen.rst
@@ -0,0 +1,8 @@
+##########
+ttGlyphPen
+##########
+
+.. automodule:: fontTools.pens.ttGlyphPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/pens/wxPen.rst b/Doc/source/pens/wxPen.rst
new file mode 100644
index 0000000..37ce50a
--- /dev/null
+++ b/Doc/source/pens/wxPen.rst
@@ -0,0 +1,8 @@
+#####
+wxPen
+#####
+
+.. automodule:: fontTools.pens.wxPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/subset.rst b/Doc/source/subset.rst
deleted file mode 100644
index a8fff95..0000000
--- a/Doc/source/subset.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-######
-subset
-######
-
-.. automodule:: fontTools.subset
-   :members:
-   :undoc-members:
diff --git a/Doc/source/subset/cff.rst b/Doc/source/subset/cff.rst
new file mode 100644
index 0000000..8c21c39
--- /dev/null
+++ b/Doc/source/subset/cff.rst
@@ -0,0 +1,8 @@
+###
+cff
+###
+
+.. automodule:: fontTools.subset.cff
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/subset/index.rst b/Doc/source/subset/index.rst
new file mode 100644
index 0000000..f32e0d4
--- /dev/null
+++ b/Doc/source/subset/index.rst
@@ -0,0 +1,13 @@
+######
+subset
+######
+
+.. toctree::
+   :maxdepth: 1
+
+   cff
+
+.. automodule:: fontTools.subset
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/svgLib/index.rst b/Doc/source/svgLib/index.rst
new file mode 100644
index 0000000..f86ff0a
--- /dev/null
+++ b/Doc/source/svgLib/index.rst
@@ -0,0 +1,8 @@
+######
+svgLib
+######
+
+.. toctree::
+   :maxdepth: 1
+
+   path/index
diff --git a/Doc/source/svgLib/path/index.rst b/Doc/source/svgLib/path/index.rst
new file mode 100644
index 0000000..0d7551b
--- /dev/null
+++ b/Doc/source/svgLib/path/index.rst
@@ -0,0 +1,13 @@
+####
+path
+####
+
+.. toctree::
+   :maxdepth: 1
+
+   parser
+
+.. automodule:: fontTools.svgLib.path
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/svgLib/path/parser.rst b/Doc/source/svgLib/path/parser.rst
new file mode 100644
index 0000000..fc7e7ff
--- /dev/null
+++ b/Doc/source/svgLib/path/parser.rst
@@ -0,0 +1,8 @@
+######
+parser
+######
+
+.. automodule:: fontTools.svgLib.path.parser
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/t1Lib.rst b/Doc/source/t1Lib.rst
index dcc0438..4436086 100644
--- a/Doc/source/t1Lib.rst
+++ b/Doc/source/t1Lib.rst
@@ -3,5 +3,6 @@
 #####
 
 .. automodule:: fontTools.t1Lib
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/tfmLib.rst b/Doc/source/tfmLib.rst
new file mode 100644
index 0000000..daa4125
--- /dev/null
+++ b/Doc/source/tfmLib.rst
@@ -0,0 +1,8 @@
+###########################################
+tfmLib: Read TeX Font Metrics files
+###########################################
+
+.. automodule:: fontTools.tfmLib
+
+.. autoclass:: fontTools.tfmLib.TFM
+   :members:
diff --git a/Doc/source/ttLib/index.rst b/Doc/source/ttLib/index.rst
index d273934..4dfa2d6 100644
--- a/Doc/source/ttLib/index.rst
+++ b/Doc/source/ttLib/index.rst
@@ -7,9 +7,13 @@
 
    macUtils
    sfnt
+   standardGlyphOrder
    tables
+   ttCollection
+   ttFont
    woff2
 
 .. automodule:: fontTools.ttLib
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/ttLib/macUtils.rst b/Doc/source/ttLib/macUtils.rst
index cb014d7..356a911 100644
--- a/Doc/source/ttLib/macUtils.rst
+++ b/Doc/source/ttLib/macUtils.rst
@@ -3,5 +3,6 @@
 ########
 
 .. automodule:: fontTools.ttLib.macUtils
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/ttLib/sfnt.rst b/Doc/source/ttLib/sfnt.rst
index 41e3032..29566ab 100644
--- a/Doc/source/ttLib/sfnt.rst
+++ b/Doc/source/ttLib/sfnt.rst
@@ -3,5 +3,6 @@
 ####
 
 .. automodule:: fontTools.ttLib.sfnt
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/ttLib/standardGlyphOrder.rst b/Doc/source/ttLib/standardGlyphOrder.rst
new file mode 100644
index 0000000..ca2557b
--- /dev/null
+++ b/Doc/source/ttLib/standardGlyphOrder.rst
@@ -0,0 +1,12 @@
+##################
+standardGlyphOrder
+##################
+
+.. automodule:: fontTools.ttLib.standardGlyphOrder
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.ttLib.standardGlyphOrder.standardGlyphOrder
+
+A Python list of "standard" glyphs required by post table formats 1.0 and 2.0.
diff --git a/Doc/source/ttLib/tables.rst b/Doc/source/ttLib/tables.rst
index 6ae705f..45c76b8 100644
--- a/Doc/source/ttLib/tables.rst
+++ b/Doc/source/ttLib/tables.rst
@@ -2,7 +2,114 @@
 tables
 ######
 
+This folder is a subpackage of :py:mod:`fontTools.ttLib`. Each module here is a
+specialized TT/OT table converter: they can convert raw data
+to Python objects and vice versa. Usually you don't need to
+use the modules directly: they are imported and used
+automatically when needed by :py:mod:`fontTools.ttLib`.
+
+If you are writing you own table converter the following is
+important.
+
+The modules here have pretty strange names: this is due to the
+fact that we need to map TT table tags (which are case sensitive)
+to filenames (which on Mac and Win aren't case sensitive) as well
+as to Python identifiers. The latter means it can only contain
+[A-Za-z0-9_] and cannot start with a number.
+
+:py:mod:`fontTools.ttLib` provides functions to expand a tag into the format used here::
+
+    >>> from fontTools import ttLib
+    >>> ttLib.tagToIdentifier("FOO ")
+    'F_O_O_'
+    >>> ttLib.tagToIdentifier("cvt ")
+    '_c_v_t'
+    >>> ttLib.tagToIdentifier("OS/2")
+    'O_S_2f_2'
+    >>> ttLib.tagToIdentifier("glyf")
+    '_g_l_y_f'
+    >>>
+
+And vice versa::
+
+    >>> ttLib.identifierToTag("F_O_O_")
+    'FOO '
+    >>> ttLib.identifierToTag("_c_v_t")
+    'cvt '
+    >>> ttLib.identifierToTag("O_S_2f_2")
+    'OS/2'
+    >>> ttLib.identifierToTag("_g_l_y_f")
+    'glyf'
+    >>>
+
+Eg. the 'glyf' table converter lives in a Python file called::
+
+	_g_l_y_f.py
+
+The converter itself is a class, named "table_" + expandedtag. Eg::
+
+
+	class table__g_l_y_f:
+		etc.
+
+
+Note that if you _do_ need to use such modules or classes manually,
+there are two convenient API functions that let you find them by tag::
+
+    >>> ttLib.getTableModule('glyf')
+    <module 'ttLib.tables._g_l_y_f'>
+    >>> ttLib.getTableClass('glyf')
+    <class ttLib.tables._g_l_y_f.table__g_l_y_f at 645f400>
+    >>
+
+You must subclass from :py:mod:`fontTools.ttLib.tables.DefaultTable.DefaultTable`. It provides some default
+behavior, as well as a constructor method (__init__) that you don't need to
+override.
+
+Your converter should minimally provide two methods::
+
+
+    class table_F_O_O_(DefaultTable.DefaultTable): # converter for table 'FOO '
+
+        def decompile(self, data, ttFont):
+            # 'data' is the raw table data. Unpack it into a
+            # Python data structure.
+            # 'ttFont' is a ttLib.TTfile instance, enabling you to
+            # refer to other tables. Do ***not*** keep a reference to
+            # it: it will cause a circular reference (ttFont saves
+            # a reference to us), and that means we'll be leaking
+            # memory. If you need to use it in other methods, just
+            # pass it around as a method argument.
+
+        def compile(self, ttFont):
+            # Return the raw data, as converted from the Python
+            # data structure.
+            # Again, 'ttFont' is there so you can access other tables.
+            # Same warning applies.
+
+
+If you want to support TTX import/export as well, you need to provide two
+additional methods::
+
+
+	def toXML(self, writer, ttFont):
+		# XXX
+
+	def fromXML(self, (name, attrs, content), ttFont):
+		# XXX
+
+
+
 .. automodule:: fontTools.ttLib.tables
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_a_n_k_r
+--------
+
+.. automodule:: fontTools.ttLib.tables._a_n_k_r
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -10,6 +117,23 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._a_v_a_r
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_b_s_l_n
+--------
+
+.. automodule:: fontTools.ttLib.tables._b_s_l_n
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_c_i_d_g
+--------
+
+.. automodule:: fontTools.ttLib.tables._c_i_d_g
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -17,6 +141,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._c_m_a_p
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -24,6 +149,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._c_v_a_r
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -31,6 +157,7 @@
 ------
 
 .. automodule:: fontTools.ttLib.tables._c_v_t
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -38,6 +165,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._f_e_a_t
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -45,6 +173,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._f_p_g_m
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -52,6 +181,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._f_v_a_r
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -59,6 +189,16 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._g_a_s_p
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+
+_g_c_i_d
+--------
+
+.. automodule:: fontTools.ttLib.tables._g_c_i_d
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -66,6 +206,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._g_l_y_f
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -73,6 +214,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._g_v_a_r
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -80,6 +222,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._h_d_m_x
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -87,6 +230,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._h_e_a_d
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -94,6 +238,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._h_h_e_a
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -101,6 +246,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._h_m_t_x
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -108,6 +254,15 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._k_e_r_n
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_l_c_a_r
+--------
+
+.. automodule:: fontTools.ttLib.tables._l_c_a_r
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -115,6 +270,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._l_o_c_a
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -122,6 +278,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._l_t_a_g
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -129,6 +286,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._m_a_x_p
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -136,6 +294,24 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._m_e_t_a
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_m_o_r_t
+--------
+
+.. automodule:: fontTools.ttLib.tables._m_o_r_t
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+
+_m_o_r_x
+--------
+
+.. automodule:: fontTools.ttLib.tables._m_o_r_x
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -143,6 +319,15 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._n_a_m_e
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+_o_p_b_d
+--------
+
+.. automodule:: fontTools.ttLib.tables._o_p_b_d
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -150,6 +335,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._p_o_s_t
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -157,6 +343,16 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._p_r_e_p
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+
+_p_r_o_p
+--------
+
+.. automodule:: fontTools.ttLib.tables._p_r_o_p
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -164,6 +360,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._s_b_i_x
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -171,6 +368,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._t_r_a_k
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -178,6 +376,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._v_h_e_a
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -185,6 +384,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables._v_m_t_x
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -192,6 +392,7 @@
 ----------
 
 .. automodule:: fontTools.ttLib.tables.asciiTable
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -199,6 +400,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.B_A_S_E_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -206,6 +408,7 @@
 ------------------
 
 .. automodule:: fontTools.ttLib.tables.BitmapGlyphMetrics
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -213,6 +416,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.C_B_D_T_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -220,6 +424,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.C_B_L_C_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -227,6 +432,7 @@
 ------
 
 .. automodule:: fontTools.ttLib.tables.C_F_F_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -234,6 +440,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.C_F_F__2
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -241,6 +448,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.C_O_L_R_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -248,6 +456,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.C_P_A_L_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -255,6 +464,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.D_S_I_G_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -262,6 +472,7 @@
 ------------
 
 .. automodule:: fontTools.ttLib.tables.DefaultTable
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -269,6 +480,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.E_B_D_T_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -276,13 +488,41 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.E_B_L_C_
+   :inherited-members:
    :members:
    :undoc-members:
 
+F__e_a_t
+--------
+
+.. automodule:: fontTools.ttLib.tables.F__e_a_t
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+
 F_F_T_M_
 --------
 
 .. automodule:: fontTools.ttLib.tables.F_F_T_M_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+
+G__l_a_t
+--------
+
+.. automodule:: fontTools.ttLib.tables.G__l_a_t
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+G__l_o_c
+--------
+
+.. automodule:: fontTools.ttLib.tables.G__l_o_c
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -290,6 +530,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.G_D_E_F_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -297,6 +538,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.G_M_A_P_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -304,6 +546,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.G_P_K_G_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -311,6 +554,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.G_P_O_S_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -318,6 +562,15 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.G_S_U_B_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+grUtils
+-------
+
+.. automodule:: fontTools.ttLib.tables.grUtils
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -325,6 +578,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.H_V_A_R_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -332,6 +586,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.J_S_T_F_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -339,6 +594,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.L_T_S_H_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -346,6 +602,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.M_A_T_H_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -353,6 +610,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.M_E_T_A_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -360,6 +618,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.M_V_A_R_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -367,6 +626,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.O_S_2f_2
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -374,6 +634,7 @@
 ------
 
 .. automodule:: fontTools.ttLib.tables.otBase
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -381,6 +642,7 @@
 ------------
 
 .. automodule:: fontTools.ttLib.tables.otConverters
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -388,6 +650,7 @@
 ------
 
 .. automodule:: fontTools.ttLib.tables.otData
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -395,6 +658,23 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.otTables
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+S__i_l_f
+--------
+
+.. automodule:: fontTools.ttLib.tables.S__i_l_f
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+S__i_l_l
+--------
+
+.. automodule:: fontTools.ttLib.tables.S__i_l_l
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -402,6 +682,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.S_I_N_G_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -409,6 +690,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.S_T_A_T_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -416,6 +698,7 @@
 ------
 
 .. automodule:: fontTools.ttLib.tables.S_V_G_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -423,6 +706,7 @@
 ---------
 
 .. automodule:: fontTools.ttLib.tables.sbixGlyph
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -430,6 +714,7 @@
 ----------
 
 .. automodule:: fontTools.ttLib.tables.sbixStrike
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -437,6 +722,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.T_S_I__0
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -444,6 +730,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.T_S_I__1
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -451,6 +738,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.T_S_I__2
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -458,6 +746,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.T_S_I__3
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -465,6 +754,71 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.T_S_I__5
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_B_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_B_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_C_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_C_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_D_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_D_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_J_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_J_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_P_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_P_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_S_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_S_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_S_I_V_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_S_I_V_
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+T_T_F_A_
+--------
+
+.. automodule:: fontTools.ttLib.tables.T_T_F_A_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -472,6 +826,7 @@
 ---------
 
 .. automodule:: fontTools.ttLib.tables.ttProgram
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -479,6 +834,7 @@
 --------------
 
 .. automodule:: fontTools.ttLib.tables.TupleVariation
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -486,6 +842,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.V_D_M_X_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -493,6 +850,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.V_O_R_G_
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -500,6 +858,7 @@
 --------
 
 .. automodule:: fontTools.ttLib.tables.V_V_A_R_
+   :inherited-members:
    :members:
    :undoc-members:
 
diff --git a/Doc/source/ttLib/ttCollection.rst b/Doc/source/ttLib/ttCollection.rst
new file mode 100644
index 0000000..0ca4ebd
--- /dev/null
+++ b/Doc/source/ttLib/ttCollection.rst
@@ -0,0 +1,8 @@
+############
+ttCollection
+############
+
+.. automodule:: fontTools.ttLib.ttCollection
+   :inherited-members:
+   :members:
+   :undoc-members:
\ No newline at end of file
diff --git a/Doc/source/ttLib/ttFont.rst b/Doc/source/ttLib/ttFont.rst
new file mode 100644
index 0000000..a571050
--- /dev/null
+++ b/Doc/source/ttLib/ttFont.rst
@@ -0,0 +1,9 @@
+######
+ttFont
+######
+
+.. automodule:: fontTools.ttLib.ttFont
+   :inherited-members:
+   :members:
+   :undoc-members:
+   :private-members:
diff --git a/Doc/source/ttLib/woff2.rst b/Doc/source/ttLib/woff2.rst
index 0c464be..327bb5b 100644
--- a/Doc/source/ttLib/woff2.rst
+++ b/Doc/source/ttLib/woff2.rst
@@ -3,5 +3,6 @@
 #####
 
 .. automodule:: fontTools.ttLib.woff2
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/ttx.rst b/Doc/source/ttx.rst
index 1c90901..b6e43f5 100644
--- a/Doc/source/ttx.rst
+++ b/Doc/source/ttx.rst
@@ -2,6 +2,61 @@
 ttx
 ###
 
+
+TTX – From OpenType and TrueType to XML and Back
+------------------------------------------------
+
+Once installed you can use the ttx command to convert binary font files (.otf, .ttf, etc) to the TTX XML format, edit them, and convert them back to binary format. TTX files have a .ttx file extension::
+
+    ttx /path/to/font.otf
+    ttx /path/to/font.ttx
+
+The TTX application can be used in two ways, depending on what platform you run it on:
+
+* As a command line tool (Windows/DOS, Unix, macOS)
+* By dropping files onto the application (Windows, macOS)
+
+TTX detects what kind of files it is fed: it will output a ``.ttx`` file when it sees a ``.ttf`` or ``.otf``, and it will compile a ``.ttf`` or ``.otf`` when the input file is a ``.ttx`` file. By default, the output file is created in the same folder as the input file, and will have the same name as the input file but with a different extension. TTX will never overwrite existing files, but if necessary will append a unique number to the output filename (before the extension) such as ``Arial#1.ttf``.
+
+When using TTX from the command line there are a bunch of extra options. These are explained in the help text, as displayed when typing ``ttx -h`` at the command prompt. These additional options include:
+
+
+* specifying the folder where the output files are created
+* specifying which tables to dump or which tables to exclude
+* merging partial .ttx files with existing .ttf or .otf files
+* listing brief table info instead of dumping to .ttx
+* splitting tables to separate .ttx files
+* disabling TrueType instruction disassembly
+
+The TTX file format
+^^^^^^^^^^^^^^^^^^^
+
+.. begin table list
+
+The following tables are currently supported::
+
+    BASE, CBDT, CBLC, CFF, CFF2, COLR, CPAL, DSIG, Debg, EBDT, EBLC,
+    FFTM, Feat, GDEF, GMAP, GPKG, GPOS, GSUB, Glat, Gloc, HVAR, JSTF,
+    LTSH, MATH, META, MVAR, OS/2, SING, STAT, SVG, Silf, Sill, TSI0,
+    TSI1, TSI2, TSI3, TSI5, TSIB, TSIC, TSID, TSIJ, TSIP, TSIS, TSIV,
+    TTFA, VDMX, VORG, VVAR, ankr, avar, bsln, cidg, cmap, cvar, cvt,
+    feat, fpgm, fvar, gasp, gcid, glyf, gvar, hdmx, head, hhea, hmtx,
+    kern, lcar, loca, ltag, maxp, meta, mort, morx, name, opbd, post,
+    prep, prop, sbix, trak, vhea and vmtx
+
+.. end table list
+
+Other tables are dumped as hexadecimal data.
+
+TrueType fonts use glyph indices (GlyphIDs) to refer to glyphs in most places. While this is fine in binary form, it is really hard to work with for humans. Therefore we use names instead.
+
+The glyph names are either extracted from the ``CFF`` table or the ``post`` table, or are derived from a Unicode ``cmap`` table. In the latter case the Adobe Glyph List is used to calculate names based on Unicode values. If all of these methods fail, names are invented based on GlyphID (eg ``glyph00142``)
+
+It is possible that different glyphs use the same name. If this happens, we force the names to be unique by appending #n to the name (n being an integer number.) The original names are being kept, so this has no influence on a "round tripped" font.
+
+Because the order in which glyphs are stored inside the binary font is important, we maintain an ordered list of glyph names in the font.
+
 .. automodule:: fontTools.ttx
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/ufoLib/converters.rst b/Doc/source/ufoLib/converters.rst
new file mode 100644
index 0000000..2b37e56
--- /dev/null
+++ b/Doc/source/ufoLib/converters.rst
@@ -0,0 +1,9 @@
+
+##########
+converters
+##########
+
+.. automodule:: fontTools.ufoLib.converters
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/errors.rst b/Doc/source/ufoLib/errors.rst
new file mode 100644
index 0000000..ba079ab
--- /dev/null
+++ b/Doc/source/ufoLib/errors.rst
@@ -0,0 +1,9 @@
+
+######
+errors
+######
+
+.. automodule:: fontTools.ufoLib.errors
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/filenames.rst b/Doc/source/ufoLib/filenames.rst
new file mode 100644
index 0000000..33a3c1b
--- /dev/null
+++ b/Doc/source/ufoLib/filenames.rst
@@ -0,0 +1,9 @@
+
+#########
+filenames
+#########
+
+.. automodule:: fontTools.ufoLib.filenames
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/glifLib.rst b/Doc/source/ufoLib/glifLib.rst
new file mode 100644
index 0000000..15bde5a
--- /dev/null
+++ b/Doc/source/ufoLib/glifLib.rst
@@ -0,0 +1,9 @@
+
+#######
+glifLib
+#######
+
+.. automodule:: fontTools.ufoLib.glifLib
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/index.rst b/Doc/source/ufoLib/index.rst
new file mode 100644
index 0000000..514c5c9
--- /dev/null
+++ b/Doc/source/ufoLib/index.rst
@@ -0,0 +1,22 @@
+
+######
+ufoLib
+######
+
+.. toctree::
+   :maxdepth: 1
+
+   converters
+   errors
+   filenames
+   glifLib
+   kerning
+   plistlib
+   pointpen
+   utils
+   validators
+
+.. automodule:: fontTools.ufoLib
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/kerning.rst b/Doc/source/ufoLib/kerning.rst
new file mode 100644
index 0000000..4d9d0c1
--- /dev/null
+++ b/Doc/source/ufoLib/kerning.rst
@@ -0,0 +1,9 @@
+
+#######
+kerning
+#######
+
+.. automodule:: fontTools.ufoLib.kerning
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/plistlib.rst b/Doc/source/ufoLib/plistlib.rst
new file mode 100644
index 0000000..d639947
--- /dev/null
+++ b/Doc/source/ufoLib/plistlib.rst
@@ -0,0 +1,9 @@
+
+########
+plistlib
+########
+
+.. automodule:: fontTools.ufoLib.plistlib
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/pointpen.rst b/Doc/source/ufoLib/pointpen.rst
new file mode 100644
index 0000000..5fb8c1c
--- /dev/null
+++ b/Doc/source/ufoLib/pointpen.rst
@@ -0,0 +1,9 @@
+
+########
+pointPen
+########
+
+.. automodule:: fontTools.ufoLib.pointPen
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/utils.rst b/Doc/source/ufoLib/utils.rst
new file mode 100644
index 0000000..d5ab143
--- /dev/null
+++ b/Doc/source/ufoLib/utils.rst
@@ -0,0 +1,9 @@
+
+#####
+utils
+#####
+
+.. automodule:: fontTools.ufoLib.utils
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/ufoLib/validators.rst b/Doc/source/ufoLib/validators.rst
new file mode 100644
index 0000000..363d864
--- /dev/null
+++ b/Doc/source/ufoLib/validators.rst
@@ -0,0 +1,9 @@
+
+##########
+validators
+##########
+
+.. automodule:: fontTools.ufoLib.validators
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/unicode.rst b/Doc/source/unicode.rst
new file mode 100644
index 0000000..65481d5
--- /dev/null
+++ b/Doc/source/unicode.rst
@@ -0,0 +1,8 @@
+#######
+unicode
+#######
+
+.. automodule:: fontTools.unicode
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/unicodedata/Blocks.rst b/Doc/source/unicodedata/Blocks.rst
new file mode 100644
index 0000000..5d01da7
--- /dev/null
+++ b/Doc/source/unicodedata/Blocks.rst
@@ -0,0 +1,13 @@
+######
+Blocks
+######
+
+.. automodule:: fontTools.unicodedata.Blocks
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.unicodedata.Blocks.RANGES
+
+.. data:: fontTools.unicodedata.Blocks.VALUES
+
diff --git a/Doc/source/unicodedata/OTTags.rst b/Doc/source/unicodedata/OTTags.rst
new file mode 100644
index 0000000..a436bdc
--- /dev/null
+++ b/Doc/source/unicodedata/OTTags.rst
@@ -0,0 +1,17 @@
+######
+OTTags
+######
+
+.. automodule:: fontTools.unicodedata.OTTags
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.unicodedata.OTTags.DEFAULT_SCRIPT
+
+.. data:: fontTools.unicodedata.OTTags.SCRIPT_EXCEPTIONS
+
+.. data:: fontTools.unicodedata.OTTags.NEW_SCRIPT_TAGS
+
+.. data:: fontTools.unicodedata.OTTags.NEW_SCRIPT_TAGS_REVERSED
+
diff --git a/Doc/source/unicodedata/ScriptExtensions.rst b/Doc/source/unicodedata/ScriptExtensions.rst
new file mode 100644
index 0000000..dce2bbc
--- /dev/null
+++ b/Doc/source/unicodedata/ScriptExtensions.rst
@@ -0,0 +1,12 @@
+################
+ScriptExtensions
+################
+
+.. automodule:: fontTools.unicodedata.ScriptExtensions
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.unicodedata.ScriptExtensions.RANGES
+
+.. data:: fontTools.unicodedata.ScriptExtensions.VALUES
diff --git a/Doc/source/unicodedata/Scripts.rst b/Doc/source/unicodedata/Scripts.rst
new file mode 100644
index 0000000..2ec6e34
--- /dev/null
+++ b/Doc/source/unicodedata/Scripts.rst
@@ -0,0 +1,16 @@
+#######
+Scripts
+#######
+
+.. automodule:: fontTools.unicodedata.Scripts
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.unicodedata.Scripts.NAMES
+
+.. data:: fontTools.unicodedata.Scripts.RANGES
+
+.. data:: fontTools.unicodedata.Scripts.VALUES
+
+
diff --git a/Doc/source/unicodedata/index.rst b/Doc/source/unicodedata/index.rst
new file mode 100644
index 0000000..811d65d
--- /dev/null
+++ b/Doc/source/unicodedata/index.rst
@@ -0,0 +1,16 @@
+###########
+unicodedata
+###########
+
+.. toctree::
+   :maxdepth: 1
+
+   Blocks
+   OTTags
+   ScriptExtensions
+   Scripts
+
+.. automodule:: fontTools.unicodedata
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/builder.rst b/Doc/source/varLib/builder.rst
new file mode 100644
index 0000000..3da3d32
--- /dev/null
+++ b/Doc/source/varLib/builder.rst
@@ -0,0 +1,8 @@
+#######
+builder
+#######
+
+.. automodule:: fontTools.varLib.builder
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/cff.rst b/Doc/source/varLib/cff.rst
new file mode 100644
index 0000000..62e11c7
--- /dev/null
+++ b/Doc/source/varLib/cff.rst
@@ -0,0 +1,8 @@
+###
+cff
+###
+
+.. automodule:: fontTools.varLib.cff
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/designspace.rst b/Doc/source/varLib/designspace.rst
deleted file mode 100644
index e3bbdf7..0000000
--- a/Doc/source/varLib/designspace.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-###########
-designspace
-###########
-
-.. automodule:: fontTools.varLib.designspace
-   :members:
-   :undoc-members:
diff --git a/Doc/source/varLib/errors.rst b/Doc/source/varLib/errors.rst
new file mode 100644
index 0000000..b761854
--- /dev/null
+++ b/Doc/source/varLib/errors.rst
@@ -0,0 +1,8 @@
+######
+errors
+######
+
+.. automodule:: fontTools.varLib.errors
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/featureVars.rst b/Doc/source/varLib/featureVars.rst
new file mode 100644
index 0000000..da73560
--- /dev/null
+++ b/Doc/source/varLib/featureVars.rst
@@ -0,0 +1,8 @@
+###########
+featureVars
+###########
+
+.. automodule:: fontTools.varLib.featureVars
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/index.rst b/Doc/source/varLib/index.rst
index 0e9f17b..7b22496 100644
--- a/Doc/source/varLib/index.rst
+++ b/Doc/source/varLib/index.rst
@@ -1,17 +1,114 @@
-######
-varLib
-######
+##################################
+varLib: OpenType Variation Support
+##################################
+
+The ``fontTools.varLib`` package contains a number of classes and routines
+for handling, building and interpolating variable font data. These routines
+rely on a common set of concepts, many of which are equivalent to concepts
+in the OpenType Specification, but some of which are unique to ``varLib``.
+
+Terminology
+-----------
+
+axis
+   "A designer-determined variable in a font face design that can be used to
+   derive multiple, variant designs within a family." (OpenType Specification)
+   An axis has a minimum value, a maximum value and a default value.
+
+designspace
+   The n-dimensional space formed by the font's axes. (OpenType Specification
+   calls this the "design-variation space")
+
+scalar
+   A value which is able to be varied at different points in the designspace:
+   for example, the horizontal advance width of the glyph "a" is a scalar.
+   However, see also *support scalar* below.
+
+default location
+   A point in the designspace whose coordinates are the default value of
+   all axes.
+
+location
+   A point in the designspace, specified as a set of coordinates on one or
+   more axes. In the context of ``varLib``, a location is a dictionary with
+   the keys being the axis tags and the values being the coordinates on the
+   respective axis. A ``varLib`` location dictionary may be "sparse", in the
+   sense that axes defined in the font may be omitted from the location's
+   coordinates, in which case the default value of the axis is assumed.
+   For example, given a font having a ``wght`` axis ranging from 200-1000
+   with default 400, and a ``wdth`` axis ranging 100-300 with default 150,
+   the location ``{"wdth": 200}`` represents the point ``wght=400,wdth=200``.
+
+master
+   The value of a scalar at a given location. **Note that this is a
+   considerably more general concept than the usual type design sense of
+   the term "master".**
+
+normalized location
+   While the range of an axis is determined by its minimum and maximum values
+   as set by the designer, locations are specified internally to the font binary
+   in the range -1 to 1, with 0 being the default, -1 being the minimum and
+   1 being the maximum. A normalized location is one which is scaled to the
+   range (-1,1) on all of its axes. Note that as the range from minimum to
+   default and from default to maximum on a given axis may differ (for
+   example, given ``wght min=200 default=500 max=1000``, the difference
+   between a normalized location -1 of a normalized location of 0 represents a
+   difference of 300 units while the difference between a normalized location
+   of 0 and a normalized location of 1 represents a difference of 700 units),
+   a location is scaled by a different factor depending on whether it is above
+   or below the axis' default value.
+
+support
+   While designers tend to think in terms of masters - that is, a precise
+   location having a particular value - OpenType Variations specifies the
+   variation of scalars in terms of deltas which are themselves composed of
+   the combined contributions of a set of triangular regions, each having
+   a contribution value of 0 at its minimum value, rising linearly to its
+   full contribution at the *peak* and falling linearly to zero from the
+   peak to the maximum value. The OpenType Specification calls these "regions",
+   while ``varLib`` calls them "supports" (a mathematical term used in real
+   analysis) and expresses them as a dictionary mapping each axis tag to a
+   tuple ``(min, peak, max)``.
+
+box
+   ``varLib`` uses the term "box" to denote the minimum and maximum "corners" of
+   a support, ignoring its peak value.
+
+delta
+   The term "delta" is used in OpenType Variations in two senses. In the
+   more general sense, a delta is the difference between a scalar at a
+   given location and its value at the default location. Additionally, inside
+   the font, variation data is stored as a mapping between supports and deltas.
+   The delta (in the first sense) is computed by summing the product of the
+   delta of each support by a factor representing the support's contribution
+   at this location (see "support scalar" below).
+
+support scalar
+   When interpolating a set of variation data, the support scalar represents
+   the scalar multiplier of the support's contribution at this location. For
+   example, the support scalar will be 1 at the support's peak location, and
+   0 below its minimum or above its maximum.
+
 
 .. toctree::
    :maxdepth: 2
 
-   designspace
+   builder
+   cff
+   errors
+   featureVars
+   instancer
    interpolatable
    interpolate_layout
+   iup
    merger
    models
    mutator
+   mvar
+   plot
+   varStore
 
 .. automodule:: fontTools.varLib
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/instancer.rst b/Doc/source/varLib/instancer.rst
new file mode 100644
index 0000000..8776de3
--- /dev/null
+++ b/Doc/source/varLib/instancer.rst
@@ -0,0 +1,8 @@
+#########
+instancer
+#########
+
+.. automodule:: fontTools.varLib.instancer
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/interpolatable.rst b/Doc/source/varLib/interpolatable.rst
index 969fb61..1120a98 100644
--- a/Doc/source/varLib/interpolatable.rst
+++ b/Doc/source/varLib/interpolatable.rst
@@ -3,5 +3,6 @@
 ##############
 
 .. automodule:: fontTools.varLib.interpolatable
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/interpolate_layout.rst b/Doc/source/varLib/interpolate_layout.rst
index 752f748..a9655b5 100644
--- a/Doc/source/varLib/interpolate_layout.rst
+++ b/Doc/source/varLib/interpolate_layout.rst
@@ -3,5 +3,6 @@
 ##################
 
 .. automodule:: fontTools.varLib.interpolate_layout
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/iup.rst b/Doc/source/varLib/iup.rst
new file mode 100644
index 0000000..b096788
--- /dev/null
+++ b/Doc/source/varLib/iup.rst
@@ -0,0 +1,8 @@
+###
+iup
+###
+
+.. automodule:: fontTools.varLib.iup
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/merger.rst b/Doc/source/varLib/merger.rst
index 37383aa..cf0a5a1 100644
--- a/Doc/source/varLib/merger.rst
+++ b/Doc/source/varLib/merger.rst
@@ -3,5 +3,6 @@
 ######
 
 .. automodule:: fontTools.varLib.merger
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/models.rst b/Doc/source/varLib/models.rst
index e6c7fa8..f59f0b8 100644
--- a/Doc/source/varLib/models.rst
+++ b/Doc/source/varLib/models.rst
@@ -3,5 +3,6 @@
 ######
 
 .. automodule:: fontTools.varLib.models
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/mutator.rst b/Doc/source/varLib/mutator.rst
index e606ab8..fffa803 100644
--- a/Doc/source/varLib/mutator.rst
+++ b/Doc/source/varLib/mutator.rst
@@ -3,5 +3,6 @@
 #######
 
 .. automodule:: fontTools.varLib.mutator
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Doc/source/varLib/mvar.rst b/Doc/source/varLib/mvar.rst
new file mode 100644
index 0000000..8c59a31
--- /dev/null
+++ b/Doc/source/varLib/mvar.rst
@@ -0,0 +1,10 @@
+####
+mvar
+####
+
+.. automodule:: fontTools.varLib.mvar
+   :inherited-members:
+   :members:
+   :undoc-members:
+
+.. data:: fontTools.varLib.mvar.MVAR_ENTRIES
\ No newline at end of file
diff --git a/Doc/source/varLib/plot.rst b/Doc/source/varLib/plot.rst
new file mode 100644
index 0000000..a722a2d
--- /dev/null
+++ b/Doc/source/varLib/plot.rst
@@ -0,0 +1,8 @@
+####
+plot
+####
+
+.. automodule:: fontTools.varLib.plot
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/varLib/varStore.rst b/Doc/source/varLib/varStore.rst
new file mode 100644
index 0000000..cc91101
--- /dev/null
+++ b/Doc/source/varLib/varStore.rst
@@ -0,0 +1,8 @@
+########
+varStore
+########
+
+.. automodule:: fontTools.varLib.varStore
+   :inherited-members:
+   :members:
+   :undoc-members:
diff --git a/Doc/source/voltLib.rst b/Doc/source/voltLib.rst
index 5906c4c..7695db7 100644
--- a/Doc/source/voltLib.rst
+++ b/Doc/source/voltLib.rst
@@ -3,6 +3,7 @@
 #######
 
 .. automodule:: fontTools.voltLib
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -10,6 +11,7 @@
 ---
 
 .. automodule:: fontTools.voltLib.ast
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -17,6 +19,7 @@
 -----
 
 .. automodule:: fontTools.voltLib.parser
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -24,6 +27,7 @@
 -----
 
 .. automodule:: fontTools.voltLib.lexer
+   :inherited-members:
    :members:
    :undoc-members:
 
@@ -31,5 +35,6 @@
 ------
 
 .. automodule:: fontTools.voltLib.parser
+   :inherited-members:
    :members:
    :undoc-members:
diff --git a/Icons/FontToolsIconGreenCircle.pdf b/Icons/FontToolsIconGreenCircle.pdf
new file mode 100644
index 0000000..f36817e
--- /dev/null
+++ b/Icons/FontToolsIconGreenCircle.pdf
Binary files differ
diff --git a/Icons/FontToolsIconGreenCircle.png b/Icons/FontToolsIconGreenCircle.png
new file mode 100644
index 0000000..47d5765
--- /dev/null
+++ b/Icons/FontToolsIconGreenCircle.png
Binary files differ
diff --git a/Icons/FontToolsIconGreenSquare.pdf b/Icons/FontToolsIconGreenSquare.pdf
new file mode 100644
index 0000000..b95057f
--- /dev/null
+++ b/Icons/FontToolsIconGreenSquare.pdf
Binary files differ
diff --git a/Icons/FontToolsIconGreenSquare.png b/Icons/FontToolsIconGreenSquare.png
new file mode 100644
index 0000000..a686561
--- /dev/null
+++ b/Icons/FontToolsIconGreenSquare.png
Binary files differ
diff --git a/Icons/FontToolsIconWhiteCircle.pdf b/Icons/FontToolsIconWhiteCircle.pdf
new file mode 100644
index 0000000..c53b81c
--- /dev/null
+++ b/Icons/FontToolsIconWhiteCircle.pdf
Binary files differ
diff --git a/Icons/FontToolsIconWhiteCircle.png b/Icons/FontToolsIconWhiteCircle.png
new file mode 100644
index 0000000..33601a5
--- /dev/null
+++ b/Icons/FontToolsIconWhiteCircle.png
Binary files differ
diff --git a/Icons/FontToolsIconWhiteSquare.pdf b/Icons/FontToolsIconWhiteSquare.pdf
new file mode 100644
index 0000000..3692e3a
--- /dev/null
+++ b/Icons/FontToolsIconWhiteSquare.pdf
Binary files differ
diff --git a/Icons/FontToolsIconWhiteSquare.png b/Icons/FontToolsIconWhiteSquare.png
new file mode 100644
index 0000000..2a2d797
--- /dev/null
+++ b/Icons/FontToolsIconWhiteSquare.png
Binary files differ
diff --git a/LICENSE b/LICENSE
index 2c90134..cc63390 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,25 +1,21 @@
-Copyright 1999-2004
-by Just van Rossum, Letterror, The Netherlands.
+MIT License
 
-                        All Rights Reserved
+Copyright (c) 2017 Just van Rossum
 
-Permission to use, copy, modify, and distribute this software and 
-its documentation for any purpose and without fee is hereby granted,
-provided that the above copyright notice appear in all copies and 
-that both that copyright notice and this permission notice appear 
-in supporting documentation, and that the names of Just van Rossum 
-or Letterror not be used in advertising or publicity pertaining to
-distribution of the software without specific, written prior
-permission.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-JUST VAN ROSSUM AND LETTERROR DISCLAIM ALL WARRANTIES WITH
-REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL JUST VAN ROSSUM OR 
-LETTERROR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
-DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
-PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
-TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THIS SOFTWARE.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
 
-
-just@letterror.com
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LICENSE.external b/LICENSE.external
index 4abc7d5..2bc4dab 100644
--- a/LICENSE.external
+++ b/LICENSE.external
@@ -26,6 +26,10 @@
 
   This Font Software is licensed under the SIL Open Font License, Version 1.1.
 
+Iosevka
+  Copyright (c) 2015-2020 Belleve Invis (belleve@typeof.net).
+  This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
 This license is copied below, and is also available with a FAQ at:
 http://scripts.sil.org/OFL
 
@@ -146,3 +150,210 @@
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+=====
+
+FontTools includes cu2qu, which is Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0, a copy of which is reproduced below:
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Lib/fontTools/__init__.py b/Lib/fontTools/__init__.py
index d777e34..95292d6 100644
--- a/Lib/fontTools/__init__.py
+++ b/Lib/fontTools/__init__.py
@@ -1,10 +1,8 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
 import logging
 from fontTools.misc.loggingTools import configLogger
 
 log = logging.getLogger(__name__)
 
-version = __version__ = "3.21.0.dev0"
+version = __version__ = "4.26.3.dev0"
 
 __all__ = ["version", "log", "configLogger"]
diff --git a/Lib/fontTools/__main__.py b/Lib/fontTools/__main__.py
index 7d45751..9b978aa 100644
--- a/Lib/fontTools/__main__.py
+++ b/Lib/fontTools/__main__.py
@@ -1,4 +1,3 @@
-from __future__ import print_function, division, absolute_import
 import sys
 
 
@@ -6,8 +5,6 @@
 	if args is None:
 		args = sys.argv[1:]
 
-	# TODO Add help output, --help, etc.
-
 	# TODO Handle library-wide options. Eg.:
 	# --unicodedata
 	# --verbose / other logging stuff
@@ -21,6 +18,10 @@
 	# can be added.  Should we just try importing the fonttools
 	# module first and try without if it fails?
 
+	if len(sys.argv) < 2:
+		sys.argv.append("help")
+	if sys.argv[1] == "-h" or sys.argv[1] == "--help":
+		sys.argv[1] = "help"
 	mod = 'fontTools.'+sys.argv[1]
 	sys.argv[1] = sys.argv[0] + ' ' + sys.argv[1]
 	del sys.argv[0]
diff --git a/Lib/fontTools/afmLib.py b/Lib/fontTools/afmLib.py
index e0ccafe..49d9951 100644
--- a/Lib/fontTools/afmLib.py
+++ b/Lib/fontTools/afmLib.py
@@ -1,60 +1,100 @@
-"""Module for reading and writing AFM files."""
+"""Module for reading and writing AFM (Adobe Font Metrics) files.
 
-# XXX reads AFM's generated by Fog, not tested with much else.
-# It does not implement the full spec (Adobe Technote 5004, Adobe Font Metrics
-# File Format Specification). Still, it should read most "common" AFM files.
+Note that this has been designed to read in AFM files generated by Fontographer
+and has not been tested on many other files. In particular, it does not
+implement the whole Adobe AFM specification [#f1]_ but, it should read most
+"common" AFM files.
 
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+Here is an example of using `afmLib` to read, modify and write an AFM file:
+
+	>>> from fontTools.afmLib import AFM
+	>>> f = AFM("Tests/afmLib/data/TestAFM.afm")
+	>>>
+	>>> # Accessing a pair gets you the kern value
+	>>> f[("V","A")]
+	-60
+	>>>
+	>>> # Accessing a glyph name gets you metrics
+	>>> f["A"]
+	(65, 668, (8, -25, 660, 666))
+	>>> # (charnum, width, bounding box)
+	>>>
+	>>> # Accessing an attribute gets you metadata
+	>>> f.FontName
+	'TestFont-Regular'
+	>>> f.FamilyName
+	'TestFont'
+	>>> f.Weight
+	'Regular'
+	>>> f.XHeight
+	500
+	>>> f.Ascender
+	750
+	>>>
+	>>> # Attributes and items can also be set
+	>>> f[("A","V")] = -150 # Tighten kerning
+	>>> f.FontName = "TestFont Squished"
+	>>>
+	>>> # And the font written out again (remove the # in front)
+	>>> #f.write("testfont-squished.afm")
+
+.. rubric:: Footnotes
+
+.. [#f1] `Adobe Technote 5004 <https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5004.AFM_Spec.pdf>`_,
+   Adobe Font Metrics File Format Specification.
+
+"""
+
+
 import re
 
 # every single line starts with a "word"
-identifierRE = re.compile("^([A-Za-z]+).*")
+identifierRE = re.compile(r"^([A-Za-z]+).*")
 
 # regular expression to parse char lines
 charRE = re.compile(
-		"(-?\d+)"			# charnum
-		"\s*;\s*WX\s+"			# ; WX
-		"(-?\d+)"			# width
-		"\s*;\s*N\s+"			# ; N
-		"([.A-Za-z0-9_]+)"		# charname
-		"\s*;\s*B\s+"			# ; B
-		"(-?\d+)"			# left
-		"\s+"
-		"(-?\d+)"			# bottom
-		"\s+"
-		"(-?\d+)"			# right
-		"\s+"
-		"(-?\d+)"			# top
-		"\s*;\s*"			# ;
+		r"(-?\d+)"			# charnum
+		r"\s*;\s*WX\s+"			# ; WX
+		r"(-?\d+)"			# width
+		r"\s*;\s*N\s+"			# ; N
+		r"([.A-Za-z0-9_]+)"		# charname
+		r"\s*;\s*B\s+"			# ; B
+		r"(-?\d+)"			# left
+		r"\s+"
+		r"(-?\d+)"			# bottom
+		r"\s+"
+		r"(-?\d+)"			# right
+		r"\s+"
+		r"(-?\d+)"			# top
+		r"\s*;\s*"			# ;
 		)
 
 # regular expression to parse kerning lines
 kernRE = re.compile(
-		"([.A-Za-z0-9_]+)"		# leftchar
-		"\s+"
-		"([.A-Za-z0-9_]+)"		# rightchar
-		"\s+"
-		"(-?\d+)"			# value
-		"\s*"
+		r"([.A-Za-z0-9_]+)"		# leftchar
+		r"\s+"
+		r"([.A-Za-z0-9_]+)"		# rightchar
+		r"\s+"
+		r"(-?\d+)"			# value
+		r"\s*"
 		)
 
 # regular expressions to parse composite info lines of the form:
 # Aacute 2 ; PCC A 0 0 ; PCC acute 182 211 ;
 compositeRE = re.compile(
-		"([.A-Za-z0-9_]+)"		# char name
-		"\s+"
-		"(\d+)"				# number of parts
-		"\s*;\s*"
+		r"([.A-Za-z0-9_]+)"		# char name
+		r"\s+"
+		r"(\d+)"				# number of parts
+		r"\s*;\s*"
 		)
 componentRE = re.compile(
-		"PCC\s+"			# PPC
-		"([.A-Za-z0-9_]+)"		# base char name
-		"\s+"
-		"(-?\d+)"			# x offset
-		"\s+"
-		"(-?\d+)"			# y offset
-		"\s*;\s*"
+		r"PCC\s+"			# PPC
+		r"([.A-Za-z0-9_]+)"		# base char name
+		r"\s+"
+		r"(-?\d+)"			# x offset
+		r"\s+"
+		r"(-?\d+)"			# y offset
+		r"\s*;\s*"
 		)
 
 preferredAttributeOrder = [
@@ -98,6 +138,11 @@
 			]
 
 	def __init__(self, path=None):
+		"""AFM file reader.
+
+		Instantiating an object with a path name will cause the file to be opened,
+		read, and parsed. Alternatively the path can be left unspecified, and a
+		file can be parsed later with the :meth:`read` method."""
 		self._attrs = {}
 		self._chars = {}
 		self._kerning = {}
@@ -108,6 +153,7 @@
 			self.read(path)
 
 	def read(self, path):
+		"""Opens, reads and parses a file."""
 		lines = readlines(path)
 		for line in lines:
 			if not line.strip():
@@ -190,6 +236,7 @@
 		self._composites[charname] = components
 
 	def write(self, path, sep='\r'):
+		"""Writes out an AFM font to the given path."""
 		import time
 		lines = [	"StartFontMetrics 2.0",
 				"Comment Generated by afmLib; at %s" % (
@@ -259,24 +306,40 @@
 		writelines(path, lines, sep)
 
 	def has_kernpair(self, pair):
+		"""Returns `True` if the given glyph pair (specified as a tuple) exists
+		in the kerning dictionary."""
 		return pair in self._kerning
 
 	def kernpairs(self):
+		"""Returns a list of all kern pairs in the kerning dictionary."""
 		return list(self._kerning.keys())
 
 	def has_char(self, char):
+		"""Returns `True` if the given glyph exists in the font."""
 		return char in self._chars
 
 	def chars(self):
+		"""Returns a list of all glyph names in the font."""
 		return list(self._chars.keys())
 
 	def comments(self):
+		"""Returns all comments from the file."""
 		return self._comments
 
 	def addComment(self, comment):
+		"""Adds a new comment to the file."""
 		self._comments.append(comment)
 
 	def addComposite(self, glyphName, components):
+		"""Specifies that the glyph `glyphName` is made up of the given components.
+		The components list should be of the following form::
+
+			[
+				(glyphname, xOffset, yOffset),
+				...
+			]
+		
+		"""
 		self._composites[glyphName] = components
 
 	def __getattr__(self, attr):
diff --git a/Lib/fontTools/agl.py b/Lib/fontTools/agl.py
index ec1a1b0..4f7ff92 100644
--- a/Lib/fontTools/agl.py
+++ b/Lib/fontTools/agl.py
@@ -1,17 +1,38 @@
 # -*- coding: utf-8 -*-
-# The table below is taken from
-# http://www.adobe.com/devnet/opentype/archives/aglfn.txt
+# The tables below are taken from
+# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/glyphlist.txt
+# and
+# https://github.com/adobe-type-tools/agl-aglfn/raw/4036a9ca80a62f64f9de4f7321a9a045ad0ecfd6/aglfn.txt
+"""
+Interface to the Adobe Glyph List
 
-from __future__ import (print_function, division, absolute_import,
-                        unicode_literals)
-from fontTools.misc.py23 import *
+This module exists to convert glyph names from the Adobe Glyph List
+to their Unicode equivalents. Example usage:
+
+	>>> from fontTools.agl import toUnicode
+	>>> toUnicode("nahiragana")
+	'な'
+
+It also contains two dictionaries, ``UV2AGL`` and ``AGL2UV``, which map from
+Unicode codepoints to AGL names and vice versa:
+
+	>>> import fontTools
+	>>> fontTools.agl.UV2AGL[ord("?")]
+	'question'
+	>>> fontTools.agl.AGL2UV["wcircumflex"]
+	373
+
+This is used by fontTools when it has to construct glyph names for a font which
+doesn't include any (e.g. format 3.0 post tables).
+"""
+
+from fontTools.misc.py23 import tostr
 import re
 
 
 _aglText = """\
 # -----------------------------------------------------------
-# Copyright 2003, 2005-2008, 2010 Adobe Systems Incorporated.
-# All rights reserved.
+# Copyright 2002-2019 Adobe (http://www.adobe.com/).
 #
 # Redistribution and use in source and binary forms, with or
 # without modification, are permitted provided that the
@@ -26,10 +47,4338 @@
 # disclaimer in the documentation and/or other materials
 # provided with the distribution.
 #
-# Neither the name of Adobe Systems Incorporated nor the names
-# of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
+# Neither the name of Adobe nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------
+# Name:          Adobe Glyph List
+# Table version: 2.0
+# Date:          September 20, 2002
+# URL:           https://github.com/adobe-type-tools/agl-aglfn
+#
+# Format: two semicolon-delimited fields:
+#   (1) glyph name--upper/lowercase letters and digits
+#   (2) Unicode scalar value--four uppercase hexadecimal digits
+#
+A;0041
+AE;00C6
+AEacute;01FC
+AEmacron;01E2
+AEsmall;F7E6
+Aacute;00C1
+Aacutesmall;F7E1
+Abreve;0102
+Abreveacute;1EAE
+Abrevecyrillic;04D0
+Abrevedotbelow;1EB6
+Abrevegrave;1EB0
+Abrevehookabove;1EB2
+Abrevetilde;1EB4
+Acaron;01CD
+Acircle;24B6
+Acircumflex;00C2
+Acircumflexacute;1EA4
+Acircumflexdotbelow;1EAC
+Acircumflexgrave;1EA6
+Acircumflexhookabove;1EA8
+Acircumflexsmall;F7E2
+Acircumflextilde;1EAA
+Acute;F6C9
+Acutesmall;F7B4
+Acyrillic;0410
+Adblgrave;0200
+Adieresis;00C4
+Adieresiscyrillic;04D2
+Adieresismacron;01DE
+Adieresissmall;F7E4
+Adotbelow;1EA0
+Adotmacron;01E0
+Agrave;00C0
+Agravesmall;F7E0
+Ahookabove;1EA2
+Aiecyrillic;04D4
+Ainvertedbreve;0202
+Alpha;0391
+Alphatonos;0386
+Amacron;0100
+Amonospace;FF21
+Aogonek;0104
+Aring;00C5
+Aringacute;01FA
+Aringbelow;1E00
+Aringsmall;F7E5
+Asmall;F761
+Atilde;00C3
+Atildesmall;F7E3
+Aybarmenian;0531
+B;0042
+Bcircle;24B7
+Bdotaccent;1E02
+Bdotbelow;1E04
+Becyrillic;0411
+Benarmenian;0532
+Beta;0392
+Bhook;0181
+Blinebelow;1E06
+Bmonospace;FF22
+Brevesmall;F6F4
+Bsmall;F762
+Btopbar;0182
+C;0043
+Caarmenian;053E
+Cacute;0106
+Caron;F6CA
+Caronsmall;F6F5
+Ccaron;010C
+Ccedilla;00C7
+Ccedillaacute;1E08
+Ccedillasmall;F7E7
+Ccircle;24B8
+Ccircumflex;0108
+Cdot;010A
+Cdotaccent;010A
+Cedillasmall;F7B8
+Chaarmenian;0549
+Cheabkhasiancyrillic;04BC
+Checyrillic;0427
+Chedescenderabkhasiancyrillic;04BE
+Chedescendercyrillic;04B6
+Chedieresiscyrillic;04F4
+Cheharmenian;0543
+Chekhakassiancyrillic;04CB
+Cheverticalstrokecyrillic;04B8
+Chi;03A7
+Chook;0187
+Circumflexsmall;F6F6
+Cmonospace;FF23
+Coarmenian;0551
+Csmall;F763
+D;0044
+DZ;01F1
+DZcaron;01C4
+Daarmenian;0534
+Dafrican;0189
+Dcaron;010E
+Dcedilla;1E10
+Dcircle;24B9
+Dcircumflexbelow;1E12
+Dcroat;0110
+Ddotaccent;1E0A
+Ddotbelow;1E0C
+Decyrillic;0414
+Deicoptic;03EE
+Delta;2206
+Deltagreek;0394
+Dhook;018A
+Dieresis;F6CB
+DieresisAcute;F6CC
+DieresisGrave;F6CD
+Dieresissmall;F7A8
+Digammagreek;03DC
+Djecyrillic;0402
+Dlinebelow;1E0E
+Dmonospace;FF24
+Dotaccentsmall;F6F7
+Dslash;0110
+Dsmall;F764
+Dtopbar;018B
+Dz;01F2
+Dzcaron;01C5
+Dzeabkhasiancyrillic;04E0
+Dzecyrillic;0405
+Dzhecyrillic;040F
+E;0045
+Eacute;00C9
+Eacutesmall;F7E9
+Ebreve;0114
+Ecaron;011A
+Ecedillabreve;1E1C
+Echarmenian;0535
+Ecircle;24BA
+Ecircumflex;00CA
+Ecircumflexacute;1EBE
+Ecircumflexbelow;1E18
+Ecircumflexdotbelow;1EC6
+Ecircumflexgrave;1EC0
+Ecircumflexhookabove;1EC2
+Ecircumflexsmall;F7EA
+Ecircumflextilde;1EC4
+Ecyrillic;0404
+Edblgrave;0204
+Edieresis;00CB
+Edieresissmall;F7EB
+Edot;0116
+Edotaccent;0116
+Edotbelow;1EB8
+Efcyrillic;0424
+Egrave;00C8
+Egravesmall;F7E8
+Eharmenian;0537
+Ehookabove;1EBA
+Eightroman;2167
+Einvertedbreve;0206
+Eiotifiedcyrillic;0464
+Elcyrillic;041B
+Elevenroman;216A
+Emacron;0112
+Emacronacute;1E16
+Emacrongrave;1E14
+Emcyrillic;041C
+Emonospace;FF25
+Encyrillic;041D
+Endescendercyrillic;04A2
+Eng;014A
+Enghecyrillic;04A4
+Enhookcyrillic;04C7
+Eogonek;0118
+Eopen;0190
+Epsilon;0395
+Epsilontonos;0388
+Ercyrillic;0420
+Ereversed;018E
+Ereversedcyrillic;042D
+Escyrillic;0421
+Esdescendercyrillic;04AA
+Esh;01A9
+Esmall;F765
+Eta;0397
+Etarmenian;0538
+Etatonos;0389
+Eth;00D0
+Ethsmall;F7F0
+Etilde;1EBC
+Etildebelow;1E1A
+Euro;20AC
+Ezh;01B7
+Ezhcaron;01EE
+Ezhreversed;01B8
+F;0046
+Fcircle;24BB
+Fdotaccent;1E1E
+Feharmenian;0556
+Feicoptic;03E4
+Fhook;0191
+Fitacyrillic;0472
+Fiveroman;2164
+Fmonospace;FF26
+Fourroman;2163
+Fsmall;F766
+G;0047
+GBsquare;3387
+Gacute;01F4
+Gamma;0393
+Gammaafrican;0194
+Gangiacoptic;03EA
+Gbreve;011E
+Gcaron;01E6
+Gcedilla;0122
+Gcircle;24BC
+Gcircumflex;011C
+Gcommaaccent;0122
+Gdot;0120
+Gdotaccent;0120
+Gecyrillic;0413
+Ghadarmenian;0542
+Ghemiddlehookcyrillic;0494
+Ghestrokecyrillic;0492
+Gheupturncyrillic;0490
+Ghook;0193
+Gimarmenian;0533
+Gjecyrillic;0403
+Gmacron;1E20
+Gmonospace;FF27
+Grave;F6CE
+Gravesmall;F760
+Gsmall;F767
+Gsmallhook;029B
+Gstroke;01E4
+H;0048
+H18533;25CF
+H18543;25AA
+H18551;25AB
+H22073;25A1
+HPsquare;33CB
+Haabkhasiancyrillic;04A8
+Hadescendercyrillic;04B2
+Hardsigncyrillic;042A
+Hbar;0126
+Hbrevebelow;1E2A
+Hcedilla;1E28
+Hcircle;24BD
+Hcircumflex;0124
+Hdieresis;1E26
+Hdotaccent;1E22
+Hdotbelow;1E24
+Hmonospace;FF28
+Hoarmenian;0540
+Horicoptic;03E8
+Hsmall;F768
+Hungarumlaut;F6CF
+Hungarumlautsmall;F6F8
+Hzsquare;3390
+I;0049
+IAcyrillic;042F
+IJ;0132
+IUcyrillic;042E
+Iacute;00CD
+Iacutesmall;F7ED
+Ibreve;012C
+Icaron;01CF
+Icircle;24BE
+Icircumflex;00CE
+Icircumflexsmall;F7EE
+Icyrillic;0406
+Idblgrave;0208
+Idieresis;00CF
+Idieresisacute;1E2E
+Idieresiscyrillic;04E4
+Idieresissmall;F7EF
+Idot;0130
+Idotaccent;0130
+Idotbelow;1ECA
+Iebrevecyrillic;04D6
+Iecyrillic;0415
+Ifraktur;2111
+Igrave;00CC
+Igravesmall;F7EC
+Ihookabove;1EC8
+Iicyrillic;0418
+Iinvertedbreve;020A
+Iishortcyrillic;0419
+Imacron;012A
+Imacroncyrillic;04E2
+Imonospace;FF29
+Iniarmenian;053B
+Iocyrillic;0401
+Iogonek;012E
+Iota;0399
+Iotaafrican;0196
+Iotadieresis;03AA
+Iotatonos;038A
+Ismall;F769
+Istroke;0197
+Itilde;0128
+Itildebelow;1E2C
+Izhitsacyrillic;0474
+Izhitsadblgravecyrillic;0476
+J;004A
+Jaarmenian;0541
+Jcircle;24BF
+Jcircumflex;0134
+Jecyrillic;0408
+Jheharmenian;054B
+Jmonospace;FF2A
+Jsmall;F76A
+K;004B
+KBsquare;3385
+KKsquare;33CD
+Kabashkircyrillic;04A0
+Kacute;1E30
+Kacyrillic;041A
+Kadescendercyrillic;049A
+Kahookcyrillic;04C3
+Kappa;039A
+Kastrokecyrillic;049E
+Kaverticalstrokecyrillic;049C
+Kcaron;01E8
+Kcedilla;0136
+Kcircle;24C0
+Kcommaaccent;0136
+Kdotbelow;1E32
+Keharmenian;0554
+Kenarmenian;053F
+Khacyrillic;0425
+Kheicoptic;03E6
+Khook;0198
+Kjecyrillic;040C
+Klinebelow;1E34
+Kmonospace;FF2B
+Koppacyrillic;0480
+Koppagreek;03DE
+Ksicyrillic;046E
+Ksmall;F76B
+L;004C
+LJ;01C7
+LL;F6BF
+Lacute;0139
+Lambda;039B
+Lcaron;013D
+Lcedilla;013B
+Lcircle;24C1
+Lcircumflexbelow;1E3C
+Lcommaaccent;013B
+Ldot;013F
+Ldotaccent;013F
+Ldotbelow;1E36
+Ldotbelowmacron;1E38
+Liwnarmenian;053C
+Lj;01C8
+Ljecyrillic;0409
+Llinebelow;1E3A
+Lmonospace;FF2C
+Lslash;0141
+Lslashsmall;F6F9
+Lsmall;F76C
+M;004D
+MBsquare;3386
+Macron;F6D0
+Macronsmall;F7AF
+Macute;1E3E
+Mcircle;24C2
+Mdotaccent;1E40
+Mdotbelow;1E42
+Menarmenian;0544
+Mmonospace;FF2D
+Msmall;F76D
+Mturned;019C
+Mu;039C
+N;004E
+NJ;01CA
+Nacute;0143
+Ncaron;0147
+Ncedilla;0145
+Ncircle;24C3
+Ncircumflexbelow;1E4A
+Ncommaaccent;0145
+Ndotaccent;1E44
+Ndotbelow;1E46
+Nhookleft;019D
+Nineroman;2168
+Nj;01CB
+Njecyrillic;040A
+Nlinebelow;1E48
+Nmonospace;FF2E
+Nowarmenian;0546
+Nsmall;F76E
+Ntilde;00D1
+Ntildesmall;F7F1
+Nu;039D
+O;004F
+OE;0152
+OEsmall;F6FA
+Oacute;00D3
+Oacutesmall;F7F3
+Obarredcyrillic;04E8
+Obarreddieresiscyrillic;04EA
+Obreve;014E
+Ocaron;01D1
+Ocenteredtilde;019F
+Ocircle;24C4
+Ocircumflex;00D4
+Ocircumflexacute;1ED0
+Ocircumflexdotbelow;1ED8
+Ocircumflexgrave;1ED2
+Ocircumflexhookabove;1ED4
+Ocircumflexsmall;F7F4
+Ocircumflextilde;1ED6
+Ocyrillic;041E
+Odblacute;0150
+Odblgrave;020C
+Odieresis;00D6
+Odieresiscyrillic;04E6
+Odieresissmall;F7F6
+Odotbelow;1ECC
+Ogoneksmall;F6FB
+Ograve;00D2
+Ogravesmall;F7F2
+Oharmenian;0555
+Ohm;2126
+Ohookabove;1ECE
+Ohorn;01A0
+Ohornacute;1EDA
+Ohorndotbelow;1EE2
+Ohorngrave;1EDC
+Ohornhookabove;1EDE
+Ohorntilde;1EE0
+Ohungarumlaut;0150
+Oi;01A2
+Oinvertedbreve;020E
+Omacron;014C
+Omacronacute;1E52
+Omacrongrave;1E50
+Omega;2126
+Omegacyrillic;0460
+Omegagreek;03A9
+Omegaroundcyrillic;047A
+Omegatitlocyrillic;047C
+Omegatonos;038F
+Omicron;039F
+Omicrontonos;038C
+Omonospace;FF2F
+Oneroman;2160
+Oogonek;01EA
+Oogonekmacron;01EC
+Oopen;0186
+Oslash;00D8
+Oslashacute;01FE
+Oslashsmall;F7F8
+Osmall;F76F
+Ostrokeacute;01FE
+Otcyrillic;047E
+Otilde;00D5
+Otildeacute;1E4C
+Otildedieresis;1E4E
+Otildesmall;F7F5
+P;0050
+Pacute;1E54
+Pcircle;24C5
+Pdotaccent;1E56
+Pecyrillic;041F
+Peharmenian;054A
+Pemiddlehookcyrillic;04A6
+Phi;03A6
+Phook;01A4
+Pi;03A0
+Piwrarmenian;0553
+Pmonospace;FF30
+Psi;03A8
+Psicyrillic;0470
+Psmall;F770
+Q;0051
+Qcircle;24C6
+Qmonospace;FF31
+Qsmall;F771
+R;0052
+Raarmenian;054C
+Racute;0154
+Rcaron;0158
+Rcedilla;0156
+Rcircle;24C7
+Rcommaaccent;0156
+Rdblgrave;0210
+Rdotaccent;1E58
+Rdotbelow;1E5A
+Rdotbelowmacron;1E5C
+Reharmenian;0550
+Rfraktur;211C
+Rho;03A1
+Ringsmall;F6FC
+Rinvertedbreve;0212
+Rlinebelow;1E5E
+Rmonospace;FF32
+Rsmall;F772
+Rsmallinverted;0281
+Rsmallinvertedsuperior;02B6
+S;0053
+SF010000;250C
+SF020000;2514
+SF030000;2510
+SF040000;2518
+SF050000;253C
+SF060000;252C
+SF070000;2534
+SF080000;251C
+SF090000;2524
+SF100000;2500
+SF110000;2502
+SF190000;2561
+SF200000;2562
+SF210000;2556
+SF220000;2555
+SF230000;2563
+SF240000;2551
+SF250000;2557
+SF260000;255D
+SF270000;255C
+SF280000;255B
+SF360000;255E
+SF370000;255F
+SF380000;255A
+SF390000;2554
+SF400000;2569
+SF410000;2566
+SF420000;2560
+SF430000;2550
+SF440000;256C
+SF450000;2567
+SF460000;2568
+SF470000;2564
+SF480000;2565
+SF490000;2559
+SF500000;2558
+SF510000;2552
+SF520000;2553
+SF530000;256B
+SF540000;256A
+Sacute;015A
+Sacutedotaccent;1E64
+Sampigreek;03E0
+Scaron;0160
+Scarondotaccent;1E66
+Scaronsmall;F6FD
+Scedilla;015E
+Schwa;018F
+Schwacyrillic;04D8
+Schwadieresiscyrillic;04DA
+Scircle;24C8
+Scircumflex;015C
+Scommaaccent;0218
+Sdotaccent;1E60
+Sdotbelow;1E62
+Sdotbelowdotaccent;1E68
+Seharmenian;054D
+Sevenroman;2166
+Shaarmenian;0547
+Shacyrillic;0428
+Shchacyrillic;0429
+Sheicoptic;03E2
+Shhacyrillic;04BA
+Shimacoptic;03EC
+Sigma;03A3
+Sixroman;2165
+Smonospace;FF33
+Softsigncyrillic;042C
+Ssmall;F773
+Stigmagreek;03DA
+T;0054
+Tau;03A4
+Tbar;0166
+Tcaron;0164
+Tcedilla;0162
+Tcircle;24C9
+Tcircumflexbelow;1E70
+Tcommaaccent;0162
+Tdotaccent;1E6A
+Tdotbelow;1E6C
+Tecyrillic;0422
+Tedescendercyrillic;04AC
+Tenroman;2169
+Tetsecyrillic;04B4
+Theta;0398
+Thook;01AC
+Thorn;00DE
+Thornsmall;F7FE
+Threeroman;2162
+Tildesmall;F6FE
+Tiwnarmenian;054F
+Tlinebelow;1E6E
+Tmonospace;FF34
+Toarmenian;0539
+Tonefive;01BC
+Tonesix;0184
+Tonetwo;01A7
+Tretroflexhook;01AE
+Tsecyrillic;0426
+Tshecyrillic;040B
+Tsmall;F774
+Twelveroman;216B
+Tworoman;2161
+U;0055
+Uacute;00DA
+Uacutesmall;F7FA
+Ubreve;016C
+Ucaron;01D3
+Ucircle;24CA
+Ucircumflex;00DB
+Ucircumflexbelow;1E76
+Ucircumflexsmall;F7FB
+Ucyrillic;0423
+Udblacute;0170
+Udblgrave;0214
+Udieresis;00DC
+Udieresisacute;01D7
+Udieresisbelow;1E72
+Udieresiscaron;01D9
+Udieresiscyrillic;04F0
+Udieresisgrave;01DB
+Udieresismacron;01D5
+Udieresissmall;F7FC
+Udotbelow;1EE4
+Ugrave;00D9
+Ugravesmall;F7F9
+Uhookabove;1EE6
+Uhorn;01AF
+Uhornacute;1EE8
+Uhorndotbelow;1EF0
+Uhorngrave;1EEA
+Uhornhookabove;1EEC
+Uhorntilde;1EEE
+Uhungarumlaut;0170
+Uhungarumlautcyrillic;04F2
+Uinvertedbreve;0216
+Ukcyrillic;0478
+Umacron;016A
+Umacroncyrillic;04EE
+Umacrondieresis;1E7A
+Umonospace;FF35
+Uogonek;0172
+Upsilon;03A5
+Upsilon1;03D2
+Upsilonacutehooksymbolgreek;03D3
+Upsilonafrican;01B1
+Upsilondieresis;03AB
+Upsilondieresishooksymbolgreek;03D4
+Upsilonhooksymbol;03D2
+Upsilontonos;038E
+Uring;016E
+Ushortcyrillic;040E
+Usmall;F775
+Ustraightcyrillic;04AE
+Ustraightstrokecyrillic;04B0
+Utilde;0168
+Utildeacute;1E78
+Utildebelow;1E74
+V;0056
+Vcircle;24CB
+Vdotbelow;1E7E
+Vecyrillic;0412
+Vewarmenian;054E
+Vhook;01B2
+Vmonospace;FF36
+Voarmenian;0548
+Vsmall;F776
+Vtilde;1E7C
+W;0057
+Wacute;1E82
+Wcircle;24CC
+Wcircumflex;0174
+Wdieresis;1E84
+Wdotaccent;1E86
+Wdotbelow;1E88
+Wgrave;1E80
+Wmonospace;FF37
+Wsmall;F777
+X;0058
+Xcircle;24CD
+Xdieresis;1E8C
+Xdotaccent;1E8A
+Xeharmenian;053D
+Xi;039E
+Xmonospace;FF38
+Xsmall;F778
+Y;0059
+Yacute;00DD
+Yacutesmall;F7FD
+Yatcyrillic;0462
+Ycircle;24CE
+Ycircumflex;0176
+Ydieresis;0178
+Ydieresissmall;F7FF
+Ydotaccent;1E8E
+Ydotbelow;1EF4
+Yericyrillic;042B
+Yerudieresiscyrillic;04F8
+Ygrave;1EF2
+Yhook;01B3
+Yhookabove;1EF6
+Yiarmenian;0545
+Yicyrillic;0407
+Yiwnarmenian;0552
+Ymonospace;FF39
+Ysmall;F779
+Ytilde;1EF8
+Yusbigcyrillic;046A
+Yusbigiotifiedcyrillic;046C
+Yuslittlecyrillic;0466
+Yuslittleiotifiedcyrillic;0468
+Z;005A
+Zaarmenian;0536
+Zacute;0179
+Zcaron;017D
+Zcaronsmall;F6FF
+Zcircle;24CF
+Zcircumflex;1E90
+Zdot;017B
+Zdotaccent;017B
+Zdotbelow;1E92
+Zecyrillic;0417
+Zedescendercyrillic;0498
+Zedieresiscyrillic;04DE
+Zeta;0396
+Zhearmenian;053A
+Zhebrevecyrillic;04C1
+Zhecyrillic;0416
+Zhedescendercyrillic;0496
+Zhedieresiscyrillic;04DC
+Zlinebelow;1E94
+Zmonospace;FF3A
+Zsmall;F77A
+Zstroke;01B5
+a;0061
+aabengali;0986
+aacute;00E1
+aadeva;0906
+aagujarati;0A86
+aagurmukhi;0A06
+aamatragurmukhi;0A3E
+aarusquare;3303
+aavowelsignbengali;09BE
+aavowelsigndeva;093E
+aavowelsigngujarati;0ABE
+abbreviationmarkarmenian;055F
+abbreviationsigndeva;0970
+abengali;0985
+abopomofo;311A
+abreve;0103
+abreveacute;1EAF
+abrevecyrillic;04D1
+abrevedotbelow;1EB7
+abrevegrave;1EB1
+abrevehookabove;1EB3
+abrevetilde;1EB5
+acaron;01CE
+acircle;24D0
+acircumflex;00E2
+acircumflexacute;1EA5
+acircumflexdotbelow;1EAD
+acircumflexgrave;1EA7
+acircumflexhookabove;1EA9
+acircumflextilde;1EAB
+acute;00B4
+acutebelowcmb;0317
+acutecmb;0301
+acutecomb;0301
+acutedeva;0954
+acutelowmod;02CF
+acutetonecmb;0341
+acyrillic;0430
+adblgrave;0201
+addakgurmukhi;0A71
+adeva;0905
+adieresis;00E4
+adieresiscyrillic;04D3
+adieresismacron;01DF
+adotbelow;1EA1
+adotmacron;01E1
+ae;00E6
+aeacute;01FD
+aekorean;3150
+aemacron;01E3
+afii00208;2015
+afii08941;20A4
+afii10017;0410
+afii10018;0411
+afii10019;0412
+afii10020;0413
+afii10021;0414
+afii10022;0415
+afii10023;0401
+afii10024;0416
+afii10025;0417
+afii10026;0418
+afii10027;0419
+afii10028;041A
+afii10029;041B
+afii10030;041C
+afii10031;041D
+afii10032;041E
+afii10033;041F
+afii10034;0420
+afii10035;0421
+afii10036;0422
+afii10037;0423
+afii10038;0424
+afii10039;0425
+afii10040;0426
+afii10041;0427
+afii10042;0428
+afii10043;0429
+afii10044;042A
+afii10045;042B
+afii10046;042C
+afii10047;042D
+afii10048;042E
+afii10049;042F
+afii10050;0490
+afii10051;0402
+afii10052;0403
+afii10053;0404
+afii10054;0405
+afii10055;0406
+afii10056;0407
+afii10057;0408
+afii10058;0409
+afii10059;040A
+afii10060;040B
+afii10061;040C
+afii10062;040E
+afii10063;F6C4
+afii10064;F6C5
+afii10065;0430
+afii10066;0431
+afii10067;0432
+afii10068;0433
+afii10069;0434
+afii10070;0435
+afii10071;0451
+afii10072;0436
+afii10073;0437
+afii10074;0438
+afii10075;0439
+afii10076;043A
+afii10077;043B
+afii10078;043C
+afii10079;043D
+afii10080;043E
+afii10081;043F
+afii10082;0440
+afii10083;0441
+afii10084;0442
+afii10085;0443
+afii10086;0444
+afii10087;0445
+afii10088;0446
+afii10089;0447
+afii10090;0448
+afii10091;0449
+afii10092;044A
+afii10093;044B
+afii10094;044C
+afii10095;044D
+afii10096;044E
+afii10097;044F
+afii10098;0491
+afii10099;0452
+afii10100;0453
+afii10101;0454
+afii10102;0455
+afii10103;0456
+afii10104;0457
+afii10105;0458
+afii10106;0459
+afii10107;045A
+afii10108;045B
+afii10109;045C
+afii10110;045E
+afii10145;040F
+afii10146;0462
+afii10147;0472
+afii10148;0474
+afii10192;F6C6
+afii10193;045F
+afii10194;0463
+afii10195;0473
+afii10196;0475
+afii10831;F6C7
+afii10832;F6C8
+afii10846;04D9
+afii299;200E
+afii300;200F
+afii301;200D
+afii57381;066A
+afii57388;060C
+afii57392;0660
+afii57393;0661
+afii57394;0662
+afii57395;0663
+afii57396;0664
+afii57397;0665
+afii57398;0666
+afii57399;0667
+afii57400;0668
+afii57401;0669
+afii57403;061B
+afii57407;061F
+afii57409;0621
+afii57410;0622
+afii57411;0623
+afii57412;0624
+afii57413;0625
+afii57414;0626
+afii57415;0627
+afii57416;0628
+afii57417;0629
+afii57418;062A
+afii57419;062B
+afii57420;062C
+afii57421;062D
+afii57422;062E
+afii57423;062F
+afii57424;0630
+afii57425;0631
+afii57426;0632
+afii57427;0633
+afii57428;0634
+afii57429;0635
+afii57430;0636
+afii57431;0637
+afii57432;0638
+afii57433;0639
+afii57434;063A
+afii57440;0640
+afii57441;0641
+afii57442;0642
+afii57443;0643
+afii57444;0644
+afii57445;0645
+afii57446;0646
+afii57448;0648
+afii57449;0649
+afii57450;064A
+afii57451;064B
+afii57452;064C
+afii57453;064D
+afii57454;064E
+afii57455;064F
+afii57456;0650
+afii57457;0651
+afii57458;0652
+afii57470;0647
+afii57505;06A4
+afii57506;067E
+afii57507;0686
+afii57508;0698
+afii57509;06AF
+afii57511;0679
+afii57512;0688
+afii57513;0691
+afii57514;06BA
+afii57519;06D2
+afii57534;06D5
+afii57636;20AA
+afii57645;05BE
+afii57658;05C3
+afii57664;05D0
+afii57665;05D1
+afii57666;05D2
+afii57667;05D3
+afii57668;05D4
+afii57669;05D5
+afii57670;05D6
+afii57671;05D7
+afii57672;05D8
+afii57673;05D9
+afii57674;05DA
+afii57675;05DB
+afii57676;05DC
+afii57677;05DD
+afii57678;05DE
+afii57679;05DF
+afii57680;05E0
+afii57681;05E1
+afii57682;05E2
+afii57683;05E3
+afii57684;05E4
+afii57685;05E5
+afii57686;05E6
+afii57687;05E7
+afii57688;05E8
+afii57689;05E9
+afii57690;05EA
+afii57694;FB2A
+afii57695;FB2B
+afii57700;FB4B
+afii57705;FB1F
+afii57716;05F0
+afii57717;05F1
+afii57718;05F2
+afii57723;FB35
+afii57793;05B4
+afii57794;05B5
+afii57795;05B6
+afii57796;05BB
+afii57797;05B8
+afii57798;05B7
+afii57799;05B0
+afii57800;05B2
+afii57801;05B1
+afii57802;05B3
+afii57803;05C2
+afii57804;05C1
+afii57806;05B9
+afii57807;05BC
+afii57839;05BD
+afii57841;05BF
+afii57842;05C0
+afii57929;02BC
+afii61248;2105
+afii61289;2113
+afii61352;2116
+afii61573;202C
+afii61574;202D
+afii61575;202E
+afii61664;200C
+afii63167;066D
+afii64937;02BD
+agrave;00E0
+agujarati;0A85
+agurmukhi;0A05
+ahiragana;3042
+ahookabove;1EA3
+aibengali;0990
+aibopomofo;311E
+aideva;0910
+aiecyrillic;04D5
+aigujarati;0A90
+aigurmukhi;0A10
+aimatragurmukhi;0A48
+ainarabic;0639
+ainfinalarabic;FECA
+aininitialarabic;FECB
+ainmedialarabic;FECC
+ainvertedbreve;0203
+aivowelsignbengali;09C8
+aivowelsigndeva;0948
+aivowelsigngujarati;0AC8
+akatakana;30A2
+akatakanahalfwidth;FF71
+akorean;314F
+alef;05D0
+alefarabic;0627
+alefdageshhebrew;FB30
+aleffinalarabic;FE8E
+alefhamzaabovearabic;0623
+alefhamzaabovefinalarabic;FE84
+alefhamzabelowarabic;0625
+alefhamzabelowfinalarabic;FE88
+alefhebrew;05D0
+aleflamedhebrew;FB4F
+alefmaddaabovearabic;0622
+alefmaddaabovefinalarabic;FE82
+alefmaksuraarabic;0649
+alefmaksurafinalarabic;FEF0
+alefmaksurainitialarabic;FEF3
+alefmaksuramedialarabic;FEF4
+alefpatahhebrew;FB2E
+alefqamatshebrew;FB2F
+aleph;2135
+allequal;224C
+alpha;03B1
+alphatonos;03AC
+amacron;0101
+amonospace;FF41
+ampersand;0026
+ampersandmonospace;FF06
+ampersandsmall;F726
+amsquare;33C2
+anbopomofo;3122
+angbopomofo;3124
+angkhankhuthai;0E5A
+angle;2220
+anglebracketleft;3008
+anglebracketleftvertical;FE3F
+anglebracketright;3009
+anglebracketrightvertical;FE40
+angleleft;2329
+angleright;232A
+angstrom;212B
+anoteleia;0387
+anudattadeva;0952
+anusvarabengali;0982
+anusvaradeva;0902
+anusvaragujarati;0A82
+aogonek;0105
+apaatosquare;3300
+aparen;249C
+apostrophearmenian;055A
+apostrophemod;02BC
+apple;F8FF
+approaches;2250
+approxequal;2248
+approxequalorimage;2252
+approximatelyequal;2245
+araeaekorean;318E
+araeakorean;318D
+arc;2312
+arighthalfring;1E9A
+aring;00E5
+aringacute;01FB
+aringbelow;1E01
+arrowboth;2194
+arrowdashdown;21E3
+arrowdashleft;21E0
+arrowdashright;21E2
+arrowdashup;21E1
+arrowdblboth;21D4
+arrowdbldown;21D3
+arrowdblleft;21D0
+arrowdblright;21D2
+arrowdblup;21D1
+arrowdown;2193
+arrowdownleft;2199
+arrowdownright;2198
+arrowdownwhite;21E9
+arrowheaddownmod;02C5
+arrowheadleftmod;02C2
+arrowheadrightmod;02C3
+arrowheadupmod;02C4
+arrowhorizex;F8E7
+arrowleft;2190
+arrowleftdbl;21D0
+arrowleftdblstroke;21CD
+arrowleftoverright;21C6
+arrowleftwhite;21E6
+arrowright;2192
+arrowrightdblstroke;21CF
+arrowrightheavy;279E
+arrowrightoverleft;21C4
+arrowrightwhite;21E8
+arrowtableft;21E4
+arrowtabright;21E5
+arrowup;2191
+arrowupdn;2195
+arrowupdnbse;21A8
+arrowupdownbase;21A8
+arrowupleft;2196
+arrowupleftofdown;21C5
+arrowupright;2197
+arrowupwhite;21E7
+arrowvertex;F8E6
+asciicircum;005E
+asciicircummonospace;FF3E
+asciitilde;007E
+asciitildemonospace;FF5E
+ascript;0251
+ascriptturned;0252
+asmallhiragana;3041
+asmallkatakana;30A1
+asmallkatakanahalfwidth;FF67
+asterisk;002A
+asteriskaltonearabic;066D
+asteriskarabic;066D
+asteriskmath;2217
+asteriskmonospace;FF0A
+asterisksmall;FE61
+asterism;2042
+asuperior;F6E9
+asymptoticallyequal;2243
+at;0040
+atilde;00E3
+atmonospace;FF20
+atsmall;FE6B
+aturned;0250
+aubengali;0994
+aubopomofo;3120
+audeva;0914
+augujarati;0A94
+augurmukhi;0A14
+aulengthmarkbengali;09D7
+aumatragurmukhi;0A4C
+auvowelsignbengali;09CC
+auvowelsigndeva;094C
+auvowelsigngujarati;0ACC
+avagrahadeva;093D
+aybarmenian;0561
+ayin;05E2
+ayinaltonehebrew;FB20
+ayinhebrew;05E2
+b;0062
+babengali;09AC
+backslash;005C
+backslashmonospace;FF3C
+badeva;092C
+bagujarati;0AAC
+bagurmukhi;0A2C
+bahiragana;3070
+bahtthai;0E3F
+bakatakana;30D0
+bar;007C
+barmonospace;FF5C
+bbopomofo;3105
+bcircle;24D1
+bdotaccent;1E03
+bdotbelow;1E05
+beamedsixteenthnotes;266C
+because;2235
+becyrillic;0431
+beharabic;0628
+behfinalarabic;FE90
+behinitialarabic;FE91
+behiragana;3079
+behmedialarabic;FE92
+behmeeminitialarabic;FC9F
+behmeemisolatedarabic;FC08
+behnoonfinalarabic;FC6D
+bekatakana;30D9
+benarmenian;0562
+bet;05D1
+beta;03B2
+betasymbolgreek;03D0
+betdagesh;FB31
+betdageshhebrew;FB31
+bethebrew;05D1
+betrafehebrew;FB4C
+bhabengali;09AD
+bhadeva;092D
+bhagujarati;0AAD
+bhagurmukhi;0A2D
+bhook;0253
+bihiragana;3073
+bikatakana;30D3
+bilabialclick;0298
+bindigurmukhi;0A02
+birusquare;3331
+blackcircle;25CF
+blackdiamond;25C6
+blackdownpointingtriangle;25BC
+blackleftpointingpointer;25C4
+blackleftpointingtriangle;25C0
+blacklenticularbracketleft;3010
+blacklenticularbracketleftvertical;FE3B
+blacklenticularbracketright;3011
+blacklenticularbracketrightvertical;FE3C
+blacklowerlefttriangle;25E3
+blacklowerrighttriangle;25E2
+blackrectangle;25AC
+blackrightpointingpointer;25BA
+blackrightpointingtriangle;25B6
+blacksmallsquare;25AA
+blacksmilingface;263B
+blacksquare;25A0
+blackstar;2605
+blackupperlefttriangle;25E4
+blackupperrighttriangle;25E5
+blackuppointingsmalltriangle;25B4
+blackuppointingtriangle;25B2
+blank;2423
+blinebelow;1E07
+block;2588
+bmonospace;FF42
+bobaimaithai;0E1A
+bohiragana;307C
+bokatakana;30DC
+bparen;249D
+bqsquare;33C3
+braceex;F8F4
+braceleft;007B
+braceleftbt;F8F3
+braceleftmid;F8F2
+braceleftmonospace;FF5B
+braceleftsmall;FE5B
+bracelefttp;F8F1
+braceleftvertical;FE37
+braceright;007D
+bracerightbt;F8FE
+bracerightmid;F8FD
+bracerightmonospace;FF5D
+bracerightsmall;FE5C
+bracerighttp;F8FC
+bracerightvertical;FE38
+bracketleft;005B
+bracketleftbt;F8F0
+bracketleftex;F8EF
+bracketleftmonospace;FF3B
+bracketlefttp;F8EE
+bracketright;005D
+bracketrightbt;F8FB
+bracketrightex;F8FA
+bracketrightmonospace;FF3D
+bracketrighttp;F8F9
+breve;02D8
+brevebelowcmb;032E
+brevecmb;0306
+breveinvertedbelowcmb;032F
+breveinvertedcmb;0311
+breveinverteddoublecmb;0361
+bridgebelowcmb;032A
+bridgeinvertedbelowcmb;033A
+brokenbar;00A6
+bstroke;0180
+bsuperior;F6EA
+btopbar;0183
+buhiragana;3076
+bukatakana;30D6
+bullet;2022
+bulletinverse;25D8
+bulletoperator;2219
+bullseye;25CE
+c;0063
+caarmenian;056E
+cabengali;099A
+cacute;0107
+cadeva;091A
+cagujarati;0A9A
+cagurmukhi;0A1A
+calsquare;3388
+candrabindubengali;0981
+candrabinducmb;0310
+candrabindudeva;0901
+candrabindugujarati;0A81
+capslock;21EA
+careof;2105
+caron;02C7
+caronbelowcmb;032C
+caroncmb;030C
+carriagereturn;21B5
+cbopomofo;3118
+ccaron;010D
+ccedilla;00E7
+ccedillaacute;1E09
+ccircle;24D2
+ccircumflex;0109
+ccurl;0255
+cdot;010B
+cdotaccent;010B
+cdsquare;33C5
+cedilla;00B8
+cedillacmb;0327
+cent;00A2
+centigrade;2103
+centinferior;F6DF
+centmonospace;FFE0
+centoldstyle;F7A2
+centsuperior;F6E0
+chaarmenian;0579
+chabengali;099B
+chadeva;091B
+chagujarati;0A9B
+chagurmukhi;0A1B
+chbopomofo;3114
+cheabkhasiancyrillic;04BD
+checkmark;2713
+checyrillic;0447
+chedescenderabkhasiancyrillic;04BF
+chedescendercyrillic;04B7
+chedieresiscyrillic;04F5
+cheharmenian;0573
+chekhakassiancyrillic;04CC
+cheverticalstrokecyrillic;04B9
+chi;03C7
+chieuchacirclekorean;3277
+chieuchaparenkorean;3217
+chieuchcirclekorean;3269
+chieuchkorean;314A
+chieuchparenkorean;3209
+chochangthai;0E0A
+chochanthai;0E08
+chochingthai;0E09
+chochoethai;0E0C
+chook;0188
+cieucacirclekorean;3276
+cieucaparenkorean;3216
+cieuccirclekorean;3268
+cieuckorean;3148
+cieucparenkorean;3208
+cieucuparenkorean;321C
+circle;25CB
+circlemultiply;2297
+circleot;2299
+circleplus;2295
+circlepostalmark;3036
+circlewithlefthalfblack;25D0
+circlewithrighthalfblack;25D1
+circumflex;02C6
+circumflexbelowcmb;032D
+circumflexcmb;0302
+clear;2327
+clickalveolar;01C2
+clickdental;01C0
+clicklateral;01C1
+clickretroflex;01C3
+club;2663
+clubsuitblack;2663
+clubsuitwhite;2667
+cmcubedsquare;33A4
+cmonospace;FF43
+cmsquaredsquare;33A0
+coarmenian;0581
+colon;003A
+colonmonetary;20A1
+colonmonospace;FF1A
+colonsign;20A1
+colonsmall;FE55
+colontriangularhalfmod;02D1
+colontriangularmod;02D0
+comma;002C
+commaabovecmb;0313
+commaaboverightcmb;0315
+commaaccent;F6C3
+commaarabic;060C
+commaarmenian;055D
+commainferior;F6E1
+commamonospace;FF0C
+commareversedabovecmb;0314
+commareversedmod;02BD
+commasmall;FE50
+commasuperior;F6E2
+commaturnedabovecmb;0312
+commaturnedmod;02BB
+compass;263C
+congruent;2245
+contourintegral;222E
+control;2303
+controlACK;0006
+controlBEL;0007
+controlBS;0008
+controlCAN;0018
+controlCR;000D
+controlDC1;0011
+controlDC2;0012
+controlDC3;0013
+controlDC4;0014
+controlDEL;007F
+controlDLE;0010
+controlEM;0019
+controlENQ;0005
+controlEOT;0004
+controlESC;001B
+controlETB;0017
+controlETX;0003
+controlFF;000C
+controlFS;001C
+controlGS;001D
+controlHT;0009
+controlLF;000A
+controlNAK;0015
+controlRS;001E
+controlSI;000F
+controlSO;000E
+controlSOT;0002
+controlSTX;0001
+controlSUB;001A
+controlSYN;0016
+controlUS;001F
+controlVT;000B
+copyright;00A9
+copyrightsans;F8E9
+copyrightserif;F6D9
+cornerbracketleft;300C
+cornerbracketlefthalfwidth;FF62
+cornerbracketleftvertical;FE41
+cornerbracketright;300D
+cornerbracketrighthalfwidth;FF63
+cornerbracketrightvertical;FE42
+corporationsquare;337F
+cosquare;33C7
+coverkgsquare;33C6
+cparen;249E
+cruzeiro;20A2
+cstretched;0297
+curlyand;22CF
+curlyor;22CE
+currency;00A4
+cyrBreve;F6D1
+cyrFlex;F6D2
+cyrbreve;F6D4
+cyrflex;F6D5
+d;0064
+daarmenian;0564
+dabengali;09A6
+dadarabic;0636
+dadeva;0926
+dadfinalarabic;FEBE
+dadinitialarabic;FEBF
+dadmedialarabic;FEC0
+dagesh;05BC
+dageshhebrew;05BC
+dagger;2020
+daggerdbl;2021
+dagujarati;0AA6
+dagurmukhi;0A26
+dahiragana;3060
+dakatakana;30C0
+dalarabic;062F
+dalet;05D3
+daletdagesh;FB33
+daletdageshhebrew;FB33
+dalethatafpatah;05D3 05B2
+dalethatafpatahhebrew;05D3 05B2
+dalethatafsegol;05D3 05B1
+dalethatafsegolhebrew;05D3 05B1
+dalethebrew;05D3
+dalethiriq;05D3 05B4
+dalethiriqhebrew;05D3 05B4
+daletholam;05D3 05B9
+daletholamhebrew;05D3 05B9
+daletpatah;05D3 05B7
+daletpatahhebrew;05D3 05B7
+daletqamats;05D3 05B8
+daletqamatshebrew;05D3 05B8
+daletqubuts;05D3 05BB
+daletqubutshebrew;05D3 05BB
+daletsegol;05D3 05B6
+daletsegolhebrew;05D3 05B6
+daletsheva;05D3 05B0
+daletshevahebrew;05D3 05B0
+dalettsere;05D3 05B5
+dalettserehebrew;05D3 05B5
+dalfinalarabic;FEAA
+dammaarabic;064F
+dammalowarabic;064F
+dammatanaltonearabic;064C
+dammatanarabic;064C
+danda;0964
+dargahebrew;05A7
+dargalefthebrew;05A7
+dasiapneumatacyrilliccmb;0485
+dblGrave;F6D3
+dblanglebracketleft;300A
+dblanglebracketleftvertical;FE3D
+dblanglebracketright;300B
+dblanglebracketrightvertical;FE3E
+dblarchinvertedbelowcmb;032B
+dblarrowleft;21D4
+dblarrowright;21D2
+dbldanda;0965
+dblgrave;F6D6
+dblgravecmb;030F
+dblintegral;222C
+dbllowline;2017
+dbllowlinecmb;0333
+dbloverlinecmb;033F
+dblprimemod;02BA
+dblverticalbar;2016
+dblverticallineabovecmb;030E
+dbopomofo;3109
+dbsquare;33C8
+dcaron;010F
+dcedilla;1E11
+dcircle;24D3
+dcircumflexbelow;1E13
+dcroat;0111
+ddabengali;09A1
+ddadeva;0921
+ddagujarati;0AA1
+ddagurmukhi;0A21
+ddalarabic;0688
+ddalfinalarabic;FB89
+dddhadeva;095C
+ddhabengali;09A2
+ddhadeva;0922
+ddhagujarati;0AA2
+ddhagurmukhi;0A22
+ddotaccent;1E0B
+ddotbelow;1E0D
+decimalseparatorarabic;066B
+decimalseparatorpersian;066B
+decyrillic;0434
+degree;00B0
+dehihebrew;05AD
+dehiragana;3067
+deicoptic;03EF
+dekatakana;30C7
+deleteleft;232B
+deleteright;2326
+delta;03B4
+deltaturned;018D
+denominatorminusonenumeratorbengali;09F8
+dezh;02A4
+dhabengali;09A7
+dhadeva;0927
+dhagujarati;0AA7
+dhagurmukhi;0A27
+dhook;0257
+dialytikatonos;0385
+dialytikatonoscmb;0344
+diamond;2666
+diamondsuitwhite;2662
+dieresis;00A8
+dieresisacute;F6D7
+dieresisbelowcmb;0324
+dieresiscmb;0308
+dieresisgrave;F6D8
+dieresistonos;0385
+dihiragana;3062
+dikatakana;30C2
+dittomark;3003
+divide;00F7
+divides;2223
+divisionslash;2215
+djecyrillic;0452
+dkshade;2593
+dlinebelow;1E0F
+dlsquare;3397
+dmacron;0111
+dmonospace;FF44
+dnblock;2584
+dochadathai;0E0E
+dodekthai;0E14
+dohiragana;3069
+dokatakana;30C9
+dollar;0024
+dollarinferior;F6E3
+dollarmonospace;FF04
+dollaroldstyle;F724
+dollarsmall;FE69
+dollarsuperior;F6E4
+dong;20AB
+dorusquare;3326
+dotaccent;02D9
+dotaccentcmb;0307
+dotbelowcmb;0323
+dotbelowcomb;0323
+dotkatakana;30FB
+dotlessi;0131
+dotlessj;F6BE
+dotlessjstrokehook;0284
+dotmath;22C5
+dottedcircle;25CC
+doubleyodpatah;FB1F
+doubleyodpatahhebrew;FB1F
+downtackbelowcmb;031E
+downtackmod;02D5
+dparen;249F
+dsuperior;F6EB
+dtail;0256
+dtopbar;018C
+duhiragana;3065
+dukatakana;30C5
+dz;01F3
+dzaltone;02A3
+dzcaron;01C6
+dzcurl;02A5
+dzeabkhasiancyrillic;04E1
+dzecyrillic;0455
+dzhecyrillic;045F
+e;0065
+eacute;00E9
+earth;2641
+ebengali;098F
+ebopomofo;311C
+ebreve;0115
+ecandradeva;090D
+ecandragujarati;0A8D
+ecandravowelsigndeva;0945
+ecandravowelsigngujarati;0AC5
+ecaron;011B
+ecedillabreve;1E1D
+echarmenian;0565
+echyiwnarmenian;0587
+ecircle;24D4
+ecircumflex;00EA
+ecircumflexacute;1EBF
+ecircumflexbelow;1E19
+ecircumflexdotbelow;1EC7
+ecircumflexgrave;1EC1
+ecircumflexhookabove;1EC3
+ecircumflextilde;1EC5
+ecyrillic;0454
+edblgrave;0205
+edeva;090F
+edieresis;00EB
+edot;0117
+edotaccent;0117
+edotbelow;1EB9
+eegurmukhi;0A0F
+eematragurmukhi;0A47
+efcyrillic;0444
+egrave;00E8
+egujarati;0A8F
+eharmenian;0567
+ehbopomofo;311D
+ehiragana;3048
+ehookabove;1EBB
+eibopomofo;311F
+eight;0038
+eightarabic;0668
+eightbengali;09EE
+eightcircle;2467
+eightcircleinversesansserif;2791
+eightdeva;096E
+eighteencircle;2471
+eighteenparen;2485
+eighteenperiod;2499
+eightgujarati;0AEE
+eightgurmukhi;0A6E
+eighthackarabic;0668
+eighthangzhou;3028
+eighthnotebeamed;266B
+eightideographicparen;3227
+eightinferior;2088
+eightmonospace;FF18
+eightoldstyle;F738
+eightparen;247B
+eightperiod;248F
+eightpersian;06F8
+eightroman;2177
+eightsuperior;2078
+eightthai;0E58
+einvertedbreve;0207
+eiotifiedcyrillic;0465
+ekatakana;30A8
+ekatakanahalfwidth;FF74
+ekonkargurmukhi;0A74
+ekorean;3154
+elcyrillic;043B
+element;2208
+elevencircle;246A
+elevenparen;247E
+elevenperiod;2492
+elevenroman;217A
+ellipsis;2026
+ellipsisvertical;22EE
+emacron;0113
+emacronacute;1E17
+emacrongrave;1E15
+emcyrillic;043C
+emdash;2014
+emdashvertical;FE31
+emonospace;FF45
+emphasismarkarmenian;055B
+emptyset;2205
+enbopomofo;3123
+encyrillic;043D
+endash;2013
+endashvertical;FE32
+endescendercyrillic;04A3
+eng;014B
+engbopomofo;3125
+enghecyrillic;04A5
+enhookcyrillic;04C8
+enspace;2002
+eogonek;0119
+eokorean;3153
+eopen;025B
+eopenclosed;029A
+eopenreversed;025C
+eopenreversedclosed;025E
+eopenreversedhook;025D
+eparen;24A0
+epsilon;03B5
+epsilontonos;03AD
+equal;003D
+equalmonospace;FF1D
+equalsmall;FE66
+equalsuperior;207C
+equivalence;2261
+erbopomofo;3126
+ercyrillic;0440
+ereversed;0258
+ereversedcyrillic;044D
+escyrillic;0441
+esdescendercyrillic;04AB
+esh;0283
+eshcurl;0286
+eshortdeva;090E
+eshortvowelsigndeva;0946
+eshreversedloop;01AA
+eshsquatreversed;0285
+esmallhiragana;3047
+esmallkatakana;30A7
+esmallkatakanahalfwidth;FF6A
+estimated;212E
+esuperior;F6EC
+eta;03B7
+etarmenian;0568
+etatonos;03AE
+eth;00F0
+etilde;1EBD
+etildebelow;1E1B
+etnahtafoukhhebrew;0591
+etnahtafoukhlefthebrew;0591
+etnahtahebrew;0591
+etnahtalefthebrew;0591
+eturned;01DD
+eukorean;3161
+euro;20AC
+evowelsignbengali;09C7
+evowelsigndeva;0947
+evowelsigngujarati;0AC7
+exclam;0021
+exclamarmenian;055C
+exclamdbl;203C
+exclamdown;00A1
+exclamdownsmall;F7A1
+exclammonospace;FF01
+exclamsmall;F721
+existential;2203
+ezh;0292
+ezhcaron;01EF
+ezhcurl;0293
+ezhreversed;01B9
+ezhtail;01BA
+f;0066
+fadeva;095E
+fagurmukhi;0A5E
+fahrenheit;2109
+fathaarabic;064E
+fathalowarabic;064E
+fathatanarabic;064B
+fbopomofo;3108
+fcircle;24D5
+fdotaccent;1E1F
+feharabic;0641
+feharmenian;0586
+fehfinalarabic;FED2
+fehinitialarabic;FED3
+fehmedialarabic;FED4
+feicoptic;03E5
+female;2640
+ff;FB00
+ffi;FB03
+ffl;FB04
+fi;FB01
+fifteencircle;246E
+fifteenparen;2482
+fifteenperiod;2496
+figuredash;2012
+filledbox;25A0
+filledrect;25AC
+finalkaf;05DA
+finalkafdagesh;FB3A
+finalkafdageshhebrew;FB3A
+finalkafhebrew;05DA
+finalkafqamats;05DA 05B8
+finalkafqamatshebrew;05DA 05B8
+finalkafsheva;05DA 05B0
+finalkafshevahebrew;05DA 05B0
+finalmem;05DD
+finalmemhebrew;05DD
+finalnun;05DF
+finalnunhebrew;05DF
+finalpe;05E3
+finalpehebrew;05E3
+finaltsadi;05E5
+finaltsadihebrew;05E5
+firsttonechinese;02C9
+fisheye;25C9
+fitacyrillic;0473
+five;0035
+fivearabic;0665
+fivebengali;09EB
+fivecircle;2464
+fivecircleinversesansserif;278E
+fivedeva;096B
+fiveeighths;215D
+fivegujarati;0AEB
+fivegurmukhi;0A6B
+fivehackarabic;0665
+fivehangzhou;3025
+fiveideographicparen;3224
+fiveinferior;2085
+fivemonospace;FF15
+fiveoldstyle;F735
+fiveparen;2478
+fiveperiod;248C
+fivepersian;06F5
+fiveroman;2174
+fivesuperior;2075
+fivethai;0E55
+fl;FB02
+florin;0192
+fmonospace;FF46
+fmsquare;3399
+fofanthai;0E1F
+fofathai;0E1D
+fongmanthai;0E4F
+forall;2200
+four;0034
+fourarabic;0664
+fourbengali;09EA
+fourcircle;2463
+fourcircleinversesansserif;278D
+fourdeva;096A
+fourgujarati;0AEA
+fourgurmukhi;0A6A
+fourhackarabic;0664
+fourhangzhou;3024
+fourideographicparen;3223
+fourinferior;2084
+fourmonospace;FF14
+fournumeratorbengali;09F7
+fouroldstyle;F734
+fourparen;2477
+fourperiod;248B
+fourpersian;06F4
+fourroman;2173
+foursuperior;2074
+fourteencircle;246D
+fourteenparen;2481
+fourteenperiod;2495
+fourthai;0E54
+fourthtonechinese;02CB
+fparen;24A1
+fraction;2044
+franc;20A3
+g;0067
+gabengali;0997
+gacute;01F5
+gadeva;0917
+gafarabic;06AF
+gaffinalarabic;FB93
+gafinitialarabic;FB94
+gafmedialarabic;FB95
+gagujarati;0A97
+gagurmukhi;0A17
+gahiragana;304C
+gakatakana;30AC
+gamma;03B3
+gammalatinsmall;0263
+gammasuperior;02E0
+gangiacoptic;03EB
+gbopomofo;310D
+gbreve;011F
+gcaron;01E7
+gcedilla;0123
+gcircle;24D6
+gcircumflex;011D
+gcommaaccent;0123
+gdot;0121
+gdotaccent;0121
+gecyrillic;0433
+gehiragana;3052
+gekatakana;30B2
+geometricallyequal;2251
+gereshaccenthebrew;059C
+gereshhebrew;05F3
+gereshmuqdamhebrew;059D
+germandbls;00DF
+gershayimaccenthebrew;059E
+gershayimhebrew;05F4
+getamark;3013
+ghabengali;0998
+ghadarmenian;0572
+ghadeva;0918
+ghagujarati;0A98
+ghagurmukhi;0A18
+ghainarabic;063A
+ghainfinalarabic;FECE
+ghaininitialarabic;FECF
+ghainmedialarabic;FED0
+ghemiddlehookcyrillic;0495
+ghestrokecyrillic;0493
+gheupturncyrillic;0491
+ghhadeva;095A
+ghhagurmukhi;0A5A
+ghook;0260
+ghzsquare;3393
+gihiragana;304E
+gikatakana;30AE
+gimarmenian;0563
+gimel;05D2
+gimeldagesh;FB32
+gimeldageshhebrew;FB32
+gimelhebrew;05D2
+gjecyrillic;0453
+glottalinvertedstroke;01BE
+glottalstop;0294
+glottalstopinverted;0296
+glottalstopmod;02C0
+glottalstopreversed;0295
+glottalstopreversedmod;02C1
+glottalstopreversedsuperior;02E4
+glottalstopstroke;02A1
+glottalstopstrokereversed;02A2
+gmacron;1E21
+gmonospace;FF47
+gohiragana;3054
+gokatakana;30B4
+gparen;24A2
+gpasquare;33AC
+gradient;2207
+grave;0060
+gravebelowcmb;0316
+gravecmb;0300
+gravecomb;0300
+gravedeva;0953
+gravelowmod;02CE
+gravemonospace;FF40
+gravetonecmb;0340
+greater;003E
+greaterequal;2265
+greaterequalorless;22DB
+greatermonospace;FF1E
+greaterorequivalent;2273
+greaterorless;2277
+greateroverequal;2267
+greatersmall;FE65
+gscript;0261
+gstroke;01E5
+guhiragana;3050
+guillemotleft;00AB
+guillemotright;00BB
+guilsinglleft;2039
+guilsinglright;203A
+gukatakana;30B0
+guramusquare;3318
+gysquare;33C9
+h;0068
+haabkhasiancyrillic;04A9
+haaltonearabic;06C1
+habengali;09B9
+hadescendercyrillic;04B3
+hadeva;0939
+hagujarati;0AB9
+hagurmukhi;0A39
+haharabic;062D
+hahfinalarabic;FEA2
+hahinitialarabic;FEA3
+hahiragana;306F
+hahmedialarabic;FEA4
+haitusquare;332A
+hakatakana;30CF
+hakatakanahalfwidth;FF8A
+halantgurmukhi;0A4D
+hamzaarabic;0621
+hamzadammaarabic;0621 064F
+hamzadammatanarabic;0621 064C
+hamzafathaarabic;0621 064E
+hamzafathatanarabic;0621 064B
+hamzalowarabic;0621
+hamzalowkasraarabic;0621 0650
+hamzalowkasratanarabic;0621 064D
+hamzasukunarabic;0621 0652
+hangulfiller;3164
+hardsigncyrillic;044A
+harpoonleftbarbup;21BC
+harpoonrightbarbup;21C0
+hasquare;33CA
+hatafpatah;05B2
+hatafpatah16;05B2
+hatafpatah23;05B2
+hatafpatah2f;05B2
+hatafpatahhebrew;05B2
+hatafpatahnarrowhebrew;05B2
+hatafpatahquarterhebrew;05B2
+hatafpatahwidehebrew;05B2
+hatafqamats;05B3
+hatafqamats1b;05B3
+hatafqamats28;05B3
+hatafqamats34;05B3
+hatafqamatshebrew;05B3
+hatafqamatsnarrowhebrew;05B3
+hatafqamatsquarterhebrew;05B3
+hatafqamatswidehebrew;05B3
+hatafsegol;05B1
+hatafsegol17;05B1
+hatafsegol24;05B1
+hatafsegol30;05B1
+hatafsegolhebrew;05B1
+hatafsegolnarrowhebrew;05B1
+hatafsegolquarterhebrew;05B1
+hatafsegolwidehebrew;05B1
+hbar;0127
+hbopomofo;310F
+hbrevebelow;1E2B
+hcedilla;1E29
+hcircle;24D7
+hcircumflex;0125
+hdieresis;1E27
+hdotaccent;1E23
+hdotbelow;1E25
+he;05D4
+heart;2665
+heartsuitblack;2665
+heartsuitwhite;2661
+hedagesh;FB34
+hedageshhebrew;FB34
+hehaltonearabic;06C1
+heharabic;0647
+hehebrew;05D4
+hehfinalaltonearabic;FBA7
+hehfinalalttwoarabic;FEEA
+hehfinalarabic;FEEA
+hehhamzaabovefinalarabic;FBA5
+hehhamzaaboveisolatedarabic;FBA4
+hehinitialaltonearabic;FBA8
+hehinitialarabic;FEEB
+hehiragana;3078
+hehmedialaltonearabic;FBA9
+hehmedialarabic;FEEC
+heiseierasquare;337B
+hekatakana;30D8
+hekatakanahalfwidth;FF8D
+hekutaarusquare;3336
+henghook;0267
+herutusquare;3339
+het;05D7
+hethebrew;05D7
+hhook;0266
+hhooksuperior;02B1
+hieuhacirclekorean;327B
+hieuhaparenkorean;321B
+hieuhcirclekorean;326D
+hieuhkorean;314E
+hieuhparenkorean;320D
+hihiragana;3072
+hikatakana;30D2
+hikatakanahalfwidth;FF8B
+hiriq;05B4
+hiriq14;05B4
+hiriq21;05B4
+hiriq2d;05B4
+hiriqhebrew;05B4
+hiriqnarrowhebrew;05B4
+hiriqquarterhebrew;05B4
+hiriqwidehebrew;05B4
+hlinebelow;1E96
+hmonospace;FF48
+hoarmenian;0570
+hohipthai;0E2B
+hohiragana;307B
+hokatakana;30DB
+hokatakanahalfwidth;FF8E
+holam;05B9
+holam19;05B9
+holam26;05B9
+holam32;05B9
+holamhebrew;05B9
+holamnarrowhebrew;05B9
+holamquarterhebrew;05B9
+holamwidehebrew;05B9
+honokhukthai;0E2E
+hookabovecomb;0309
+hookcmb;0309
+hookpalatalizedbelowcmb;0321
+hookretroflexbelowcmb;0322
+hoonsquare;3342
+horicoptic;03E9
+horizontalbar;2015
+horncmb;031B
+hotsprings;2668
+house;2302
+hparen;24A3
+hsuperior;02B0
+hturned;0265
+huhiragana;3075
+huiitosquare;3333
+hukatakana;30D5
+hukatakanahalfwidth;FF8C
+hungarumlaut;02DD
+hungarumlautcmb;030B
+hv;0195
+hyphen;002D
+hypheninferior;F6E5
+hyphenmonospace;FF0D
+hyphensmall;FE63
+hyphensuperior;F6E6
+hyphentwo;2010
+i;0069
+iacute;00ED
+iacyrillic;044F
+ibengali;0987
+ibopomofo;3127
+ibreve;012D
+icaron;01D0
+icircle;24D8
+icircumflex;00EE
+icyrillic;0456
+idblgrave;0209
+ideographearthcircle;328F
+ideographfirecircle;328B
+ideographicallianceparen;323F
+ideographiccallparen;323A
+ideographiccentrecircle;32A5
+ideographicclose;3006
+ideographiccomma;3001
+ideographiccommaleft;FF64
+ideographiccongratulationparen;3237
+ideographiccorrectcircle;32A3
+ideographicearthparen;322F
+ideographicenterpriseparen;323D
+ideographicexcellentcircle;329D
+ideographicfestivalparen;3240
+ideographicfinancialcircle;3296
+ideographicfinancialparen;3236
+ideographicfireparen;322B
+ideographichaveparen;3232
+ideographichighcircle;32A4
+ideographiciterationmark;3005
+ideographiclaborcircle;3298
+ideographiclaborparen;3238
+ideographicleftcircle;32A7
+ideographiclowcircle;32A6
+ideographicmedicinecircle;32A9
+ideographicmetalparen;322E
+ideographicmoonparen;322A
+ideographicnameparen;3234
+ideographicperiod;3002
+ideographicprintcircle;329E
+ideographicreachparen;3243
+ideographicrepresentparen;3239
+ideographicresourceparen;323E
+ideographicrightcircle;32A8
+ideographicsecretcircle;3299
+ideographicselfparen;3242
+ideographicsocietyparen;3233
+ideographicspace;3000
+ideographicspecialparen;3235
+ideographicstockparen;3231
+ideographicstudyparen;323B
+ideographicsunparen;3230
+ideographicsuperviseparen;323C
+ideographicwaterparen;322C
+ideographicwoodparen;322D
+ideographiczero;3007
+ideographmetalcircle;328E
+ideographmooncircle;328A
+ideographnamecircle;3294
+ideographsuncircle;3290
+ideographwatercircle;328C
+ideographwoodcircle;328D
+ideva;0907
+idieresis;00EF
+idieresisacute;1E2F
+idieresiscyrillic;04E5
+idotbelow;1ECB
+iebrevecyrillic;04D7
+iecyrillic;0435
+ieungacirclekorean;3275
+ieungaparenkorean;3215
+ieungcirclekorean;3267
+ieungkorean;3147
+ieungparenkorean;3207
+igrave;00EC
+igujarati;0A87
+igurmukhi;0A07
+ihiragana;3044
+ihookabove;1EC9
+iibengali;0988
+iicyrillic;0438
+iideva;0908
+iigujarati;0A88
+iigurmukhi;0A08
+iimatragurmukhi;0A40
+iinvertedbreve;020B
+iishortcyrillic;0439
+iivowelsignbengali;09C0
+iivowelsigndeva;0940
+iivowelsigngujarati;0AC0
+ij;0133
+ikatakana;30A4
+ikatakanahalfwidth;FF72
+ikorean;3163
+ilde;02DC
+iluyhebrew;05AC
+imacron;012B
+imacroncyrillic;04E3
+imageorapproximatelyequal;2253
+imatragurmukhi;0A3F
+imonospace;FF49
+increment;2206
+infinity;221E
+iniarmenian;056B
+integral;222B
+integralbottom;2321
+integralbt;2321
+integralex;F8F5
+integraltop;2320
+integraltp;2320
+intersection;2229
+intisquare;3305
+invbullet;25D8
+invcircle;25D9
+invsmileface;263B
+iocyrillic;0451
+iogonek;012F
+iota;03B9
+iotadieresis;03CA
+iotadieresistonos;0390
+iotalatin;0269
+iotatonos;03AF
+iparen;24A4
+irigurmukhi;0A72
+ismallhiragana;3043
+ismallkatakana;30A3
+ismallkatakanahalfwidth;FF68
+issharbengali;09FA
+istroke;0268
+isuperior;F6ED
+iterationhiragana;309D
+iterationkatakana;30FD
+itilde;0129
+itildebelow;1E2D
+iubopomofo;3129
+iucyrillic;044E
+ivowelsignbengali;09BF
+ivowelsigndeva;093F
+ivowelsigngujarati;0ABF
+izhitsacyrillic;0475
+izhitsadblgravecyrillic;0477
+j;006A
+jaarmenian;0571
+jabengali;099C
+jadeva;091C
+jagujarati;0A9C
+jagurmukhi;0A1C
+jbopomofo;3110
+jcaron;01F0
+jcircle;24D9
+jcircumflex;0135
+jcrossedtail;029D
+jdotlessstroke;025F
+jecyrillic;0458
+jeemarabic;062C
+jeemfinalarabic;FE9E
+jeeminitialarabic;FE9F
+jeemmedialarabic;FEA0
+jeharabic;0698
+jehfinalarabic;FB8B
+jhabengali;099D
+jhadeva;091D
+jhagujarati;0A9D
+jhagurmukhi;0A1D
+jheharmenian;057B
+jis;3004
+jmonospace;FF4A
+jparen;24A5
+jsuperior;02B2
+k;006B
+kabashkircyrillic;04A1
+kabengali;0995
+kacute;1E31
+kacyrillic;043A
+kadescendercyrillic;049B
+kadeva;0915
+kaf;05DB
+kafarabic;0643
+kafdagesh;FB3B
+kafdageshhebrew;FB3B
+kaffinalarabic;FEDA
+kafhebrew;05DB
+kafinitialarabic;FEDB
+kafmedialarabic;FEDC
+kafrafehebrew;FB4D
+kagujarati;0A95
+kagurmukhi;0A15
+kahiragana;304B
+kahookcyrillic;04C4
+kakatakana;30AB
+kakatakanahalfwidth;FF76
+kappa;03BA
+kappasymbolgreek;03F0
+kapyeounmieumkorean;3171
+kapyeounphieuphkorean;3184
+kapyeounpieupkorean;3178
+kapyeounssangpieupkorean;3179
+karoriisquare;330D
+kashidaautoarabic;0640
+kashidaautonosidebearingarabic;0640
+kasmallkatakana;30F5
+kasquare;3384
+kasraarabic;0650
+kasratanarabic;064D
+kastrokecyrillic;049F
+katahiraprolongmarkhalfwidth;FF70
+kaverticalstrokecyrillic;049D
+kbopomofo;310E
+kcalsquare;3389
+kcaron;01E9
+kcedilla;0137
+kcircle;24DA
+kcommaaccent;0137
+kdotbelow;1E33
+keharmenian;0584
+kehiragana;3051
+kekatakana;30B1
+kekatakanahalfwidth;FF79
+kenarmenian;056F
+kesmallkatakana;30F6
+kgreenlandic;0138
+khabengali;0996
+khacyrillic;0445
+khadeva;0916
+khagujarati;0A96
+khagurmukhi;0A16
+khaharabic;062E
+khahfinalarabic;FEA6
+khahinitialarabic;FEA7
+khahmedialarabic;FEA8
+kheicoptic;03E7
+khhadeva;0959
+khhagurmukhi;0A59
+khieukhacirclekorean;3278
+khieukhaparenkorean;3218
+khieukhcirclekorean;326A
+khieukhkorean;314B
+khieukhparenkorean;320A
+khokhaithai;0E02
+khokhonthai;0E05
+khokhuatthai;0E03
+khokhwaithai;0E04
+khomutthai;0E5B
+khook;0199
+khorakhangthai;0E06
+khzsquare;3391
+kihiragana;304D
+kikatakana;30AD
+kikatakanahalfwidth;FF77
+kiroguramusquare;3315
+kiromeetorusquare;3316
+kirosquare;3314
+kiyeokacirclekorean;326E
+kiyeokaparenkorean;320E
+kiyeokcirclekorean;3260
+kiyeokkorean;3131
+kiyeokparenkorean;3200
+kiyeoksioskorean;3133
+kjecyrillic;045C
+klinebelow;1E35
+klsquare;3398
+kmcubedsquare;33A6
+kmonospace;FF4B
+kmsquaredsquare;33A2
+kohiragana;3053
+kohmsquare;33C0
+kokaithai;0E01
+kokatakana;30B3
+kokatakanahalfwidth;FF7A
+kooposquare;331E
+koppacyrillic;0481
+koreanstandardsymbol;327F
+koroniscmb;0343
+kparen;24A6
+kpasquare;33AA
+ksicyrillic;046F
+ktsquare;33CF
+kturned;029E
+kuhiragana;304F
+kukatakana;30AF
+kukatakanahalfwidth;FF78
+kvsquare;33B8
+kwsquare;33BE
+l;006C
+labengali;09B2
+lacute;013A
+ladeva;0932
+lagujarati;0AB2
+lagurmukhi;0A32
+lakkhangyaothai;0E45
+lamaleffinalarabic;FEFC
+lamalefhamzaabovefinalarabic;FEF8
+lamalefhamzaaboveisolatedarabic;FEF7
+lamalefhamzabelowfinalarabic;FEFA
+lamalefhamzabelowisolatedarabic;FEF9
+lamalefisolatedarabic;FEFB
+lamalefmaddaabovefinalarabic;FEF6
+lamalefmaddaaboveisolatedarabic;FEF5
+lamarabic;0644
+lambda;03BB
+lambdastroke;019B
+lamed;05DC
+lameddagesh;FB3C
+lameddageshhebrew;FB3C
+lamedhebrew;05DC
+lamedholam;05DC 05B9
+lamedholamdagesh;05DC 05B9 05BC
+lamedholamdageshhebrew;05DC 05B9 05BC
+lamedholamhebrew;05DC 05B9
+lamfinalarabic;FEDE
+lamhahinitialarabic;FCCA
+laminitialarabic;FEDF
+lamjeeminitialarabic;FCC9
+lamkhahinitialarabic;FCCB
+lamlamhehisolatedarabic;FDF2
+lammedialarabic;FEE0
+lammeemhahinitialarabic;FD88
+lammeeminitialarabic;FCCC
+lammeemjeeminitialarabic;FEDF FEE4 FEA0
+lammeemkhahinitialarabic;FEDF FEE4 FEA8
+largecircle;25EF
+lbar;019A
+lbelt;026C
+lbopomofo;310C
+lcaron;013E
+lcedilla;013C
+lcircle;24DB
+lcircumflexbelow;1E3D
+lcommaaccent;013C
+ldot;0140
+ldotaccent;0140
+ldotbelow;1E37
+ldotbelowmacron;1E39
+leftangleabovecmb;031A
+lefttackbelowcmb;0318
+less;003C
+lessequal;2264
+lessequalorgreater;22DA
+lessmonospace;FF1C
+lessorequivalent;2272
+lessorgreater;2276
+lessoverequal;2266
+lesssmall;FE64
+lezh;026E
+lfblock;258C
+lhookretroflex;026D
+lira;20A4
+liwnarmenian;056C
+lj;01C9
+ljecyrillic;0459
+ll;F6C0
+lladeva;0933
+llagujarati;0AB3
+llinebelow;1E3B
+llladeva;0934
+llvocalicbengali;09E1
+llvocalicdeva;0961
+llvocalicvowelsignbengali;09E3
+llvocalicvowelsigndeva;0963
+lmiddletilde;026B
+lmonospace;FF4C
+lmsquare;33D0
+lochulathai;0E2C
+logicaland;2227
+logicalnot;00AC
+logicalnotreversed;2310
+logicalor;2228
+lolingthai;0E25
+longs;017F
+lowlinecenterline;FE4E
+lowlinecmb;0332
+lowlinedashed;FE4D
+lozenge;25CA
+lparen;24A7
+lslash;0142
+lsquare;2113
+lsuperior;F6EE
+ltshade;2591
+luthai;0E26
+lvocalicbengali;098C
+lvocalicdeva;090C
+lvocalicvowelsignbengali;09E2
+lvocalicvowelsigndeva;0962
+lxsquare;33D3
+m;006D
+mabengali;09AE
+macron;00AF
+macronbelowcmb;0331
+macroncmb;0304
+macronlowmod;02CD
+macronmonospace;FFE3
+macute;1E3F
+madeva;092E
+magujarati;0AAE
+magurmukhi;0A2E
+mahapakhhebrew;05A4
+mahapakhlefthebrew;05A4
+mahiragana;307E
+maichattawalowleftthai;F895
+maichattawalowrightthai;F894
+maichattawathai;0E4B
+maichattawaupperleftthai;F893
+maieklowleftthai;F88C
+maieklowrightthai;F88B
+maiekthai;0E48
+maiekupperleftthai;F88A
+maihanakatleftthai;F884
+maihanakatthai;0E31
+maitaikhuleftthai;F889
+maitaikhuthai;0E47
+maitholowleftthai;F88F
+maitholowrightthai;F88E
+maithothai;0E49
+maithoupperleftthai;F88D
+maitrilowleftthai;F892
+maitrilowrightthai;F891
+maitrithai;0E4A
+maitriupperleftthai;F890
+maiyamokthai;0E46
+makatakana;30DE
+makatakanahalfwidth;FF8F
+male;2642
+mansyonsquare;3347
+maqafhebrew;05BE
+mars;2642
+masoracirclehebrew;05AF
+masquare;3383
+mbopomofo;3107
+mbsquare;33D4
+mcircle;24DC
+mcubedsquare;33A5
+mdotaccent;1E41
+mdotbelow;1E43
+meemarabic;0645
+meemfinalarabic;FEE2
+meeminitialarabic;FEE3
+meemmedialarabic;FEE4
+meemmeeminitialarabic;FCD1
+meemmeemisolatedarabic;FC48
+meetorusquare;334D
+mehiragana;3081
+meizierasquare;337E
+mekatakana;30E1
+mekatakanahalfwidth;FF92
+mem;05DE
+memdagesh;FB3E
+memdageshhebrew;FB3E
+memhebrew;05DE
+menarmenian;0574
+merkhahebrew;05A5
+merkhakefulahebrew;05A6
+merkhakefulalefthebrew;05A6
+merkhalefthebrew;05A5
+mhook;0271
+mhzsquare;3392
+middledotkatakanahalfwidth;FF65
+middot;00B7
+mieumacirclekorean;3272
+mieumaparenkorean;3212
+mieumcirclekorean;3264
+mieumkorean;3141
+mieumpansioskorean;3170
+mieumparenkorean;3204
+mieumpieupkorean;316E
+mieumsioskorean;316F
+mihiragana;307F
+mikatakana;30DF
+mikatakanahalfwidth;FF90
+minus;2212
+minusbelowcmb;0320
+minuscircle;2296
+minusmod;02D7
+minusplus;2213
+minute;2032
+miribaarusquare;334A
+mirisquare;3349
+mlonglegturned;0270
+mlsquare;3396
+mmcubedsquare;33A3
+mmonospace;FF4D
+mmsquaredsquare;339F
+mohiragana;3082
+mohmsquare;33C1
+mokatakana;30E2
+mokatakanahalfwidth;FF93
+molsquare;33D6
+momathai;0E21
+moverssquare;33A7
+moverssquaredsquare;33A8
+mparen;24A8
+mpasquare;33AB
+mssquare;33B3
+msuperior;F6EF
+mturned;026F
+mu;00B5
+mu1;00B5
+muasquare;3382
+muchgreater;226B
+muchless;226A
+mufsquare;338C
+mugreek;03BC
+mugsquare;338D
+muhiragana;3080
+mukatakana;30E0
+mukatakanahalfwidth;FF91
+mulsquare;3395
+multiply;00D7
+mumsquare;339B
+munahhebrew;05A3
+munahlefthebrew;05A3
+musicalnote;266A
+musicalnotedbl;266B
+musicflatsign;266D
+musicsharpsign;266F
+mussquare;33B2
+muvsquare;33B6
+muwsquare;33BC
+mvmegasquare;33B9
+mvsquare;33B7
+mwmegasquare;33BF
+mwsquare;33BD
+n;006E
+nabengali;09A8
+nabla;2207
+nacute;0144
+nadeva;0928
+nagujarati;0AA8
+nagurmukhi;0A28
+nahiragana;306A
+nakatakana;30CA
+nakatakanahalfwidth;FF85
+napostrophe;0149
+nasquare;3381
+nbopomofo;310B
+nbspace;00A0
+ncaron;0148
+ncedilla;0146
+ncircle;24DD
+ncircumflexbelow;1E4B
+ncommaaccent;0146
+ndotaccent;1E45
+ndotbelow;1E47
+nehiragana;306D
+nekatakana;30CD
+nekatakanahalfwidth;FF88
+newsheqelsign;20AA
+nfsquare;338B
+ngabengali;0999
+ngadeva;0919
+ngagujarati;0A99
+ngagurmukhi;0A19
+ngonguthai;0E07
+nhiragana;3093
+nhookleft;0272
+nhookretroflex;0273
+nieunacirclekorean;326F
+nieunaparenkorean;320F
+nieuncieuckorean;3135
+nieuncirclekorean;3261
+nieunhieuhkorean;3136
+nieunkorean;3134
+nieunpansioskorean;3168
+nieunparenkorean;3201
+nieunsioskorean;3167
+nieuntikeutkorean;3166
+nihiragana;306B
+nikatakana;30CB
+nikatakanahalfwidth;FF86
+nikhahitleftthai;F899
+nikhahitthai;0E4D
+nine;0039
+ninearabic;0669
+ninebengali;09EF
+ninecircle;2468
+ninecircleinversesansserif;2792
+ninedeva;096F
+ninegujarati;0AEF
+ninegurmukhi;0A6F
+ninehackarabic;0669
+ninehangzhou;3029
+nineideographicparen;3228
+nineinferior;2089
+ninemonospace;FF19
+nineoldstyle;F739
+nineparen;247C
+nineperiod;2490
+ninepersian;06F9
+nineroman;2178
+ninesuperior;2079
+nineteencircle;2472
+nineteenparen;2486
+nineteenperiod;249A
+ninethai;0E59
+nj;01CC
+njecyrillic;045A
+nkatakana;30F3
+nkatakanahalfwidth;FF9D
+nlegrightlong;019E
+nlinebelow;1E49
+nmonospace;FF4E
+nmsquare;339A
+nnabengali;09A3
+nnadeva;0923
+nnagujarati;0AA3
+nnagurmukhi;0A23
+nnnadeva;0929
+nohiragana;306E
+nokatakana;30CE
+nokatakanahalfwidth;FF89
+nonbreakingspace;00A0
+nonenthai;0E13
+nonuthai;0E19
+noonarabic;0646
+noonfinalarabic;FEE6
+noonghunnaarabic;06BA
+noonghunnafinalarabic;FB9F
+noonhehinitialarabic;FEE7 FEEC
+nooninitialarabic;FEE7
+noonjeeminitialarabic;FCD2
+noonjeemisolatedarabic;FC4B
+noonmedialarabic;FEE8
+noonmeeminitialarabic;FCD5
+noonmeemisolatedarabic;FC4E
+noonnoonfinalarabic;FC8D
+notcontains;220C
+notelement;2209
+notelementof;2209
+notequal;2260
+notgreater;226F
+notgreaternorequal;2271
+notgreaternorless;2279
+notidentical;2262
+notless;226E
+notlessnorequal;2270
+notparallel;2226
+notprecedes;2280
+notsubset;2284
+notsucceeds;2281
+notsuperset;2285
+nowarmenian;0576
+nparen;24A9
+nssquare;33B1
+nsuperior;207F
+ntilde;00F1
+nu;03BD
+nuhiragana;306C
+nukatakana;30CC
+nukatakanahalfwidth;FF87
+nuktabengali;09BC
+nuktadeva;093C
+nuktagujarati;0ABC
+nuktagurmukhi;0A3C
+numbersign;0023
+numbersignmonospace;FF03
+numbersignsmall;FE5F
+numeralsigngreek;0374
+numeralsignlowergreek;0375
+numero;2116
+nun;05E0
+nundagesh;FB40
+nundageshhebrew;FB40
+nunhebrew;05E0
+nvsquare;33B5
+nwsquare;33BB
+nyabengali;099E
+nyadeva;091E
+nyagujarati;0A9E
+nyagurmukhi;0A1E
+o;006F
+oacute;00F3
+oangthai;0E2D
+obarred;0275
+obarredcyrillic;04E9
+obarreddieresiscyrillic;04EB
+obengali;0993
+obopomofo;311B
+obreve;014F
+ocandradeva;0911
+ocandragujarati;0A91
+ocandravowelsigndeva;0949
+ocandravowelsigngujarati;0AC9
+ocaron;01D2
+ocircle;24DE
+ocircumflex;00F4
+ocircumflexacute;1ED1
+ocircumflexdotbelow;1ED9
+ocircumflexgrave;1ED3
+ocircumflexhookabove;1ED5
+ocircumflextilde;1ED7
+ocyrillic;043E
+odblacute;0151
+odblgrave;020D
+odeva;0913
+odieresis;00F6
+odieresiscyrillic;04E7
+odotbelow;1ECD
+oe;0153
+oekorean;315A
+ogonek;02DB
+ogonekcmb;0328
+ograve;00F2
+ogujarati;0A93
+oharmenian;0585
+ohiragana;304A
+ohookabove;1ECF
+ohorn;01A1
+ohornacute;1EDB
+ohorndotbelow;1EE3
+ohorngrave;1EDD
+ohornhookabove;1EDF
+ohorntilde;1EE1
+ohungarumlaut;0151
+oi;01A3
+oinvertedbreve;020F
+okatakana;30AA
+okatakanahalfwidth;FF75
+okorean;3157
+olehebrew;05AB
+omacron;014D
+omacronacute;1E53
+omacrongrave;1E51
+omdeva;0950
+omega;03C9
+omega1;03D6
+omegacyrillic;0461
+omegalatinclosed;0277
+omegaroundcyrillic;047B
+omegatitlocyrillic;047D
+omegatonos;03CE
+omgujarati;0AD0
+omicron;03BF
+omicrontonos;03CC
+omonospace;FF4F
+one;0031
+onearabic;0661
+onebengali;09E7
+onecircle;2460
+onecircleinversesansserif;278A
+onedeva;0967
+onedotenleader;2024
+oneeighth;215B
+onefitted;F6DC
+onegujarati;0AE7
+onegurmukhi;0A67
+onehackarabic;0661
+onehalf;00BD
+onehangzhou;3021
+oneideographicparen;3220
+oneinferior;2081
+onemonospace;FF11
+onenumeratorbengali;09F4
+oneoldstyle;F731
+oneparen;2474
+oneperiod;2488
+onepersian;06F1
+onequarter;00BC
+oneroman;2170
+onesuperior;00B9
+onethai;0E51
+onethird;2153
+oogonek;01EB
+oogonekmacron;01ED
+oogurmukhi;0A13
+oomatragurmukhi;0A4B
+oopen;0254
+oparen;24AA
+openbullet;25E6
+option;2325
+ordfeminine;00AA
+ordmasculine;00BA
+orthogonal;221F
+oshortdeva;0912
+oshortvowelsigndeva;094A
+oslash;00F8
+oslashacute;01FF
+osmallhiragana;3049
+osmallkatakana;30A9
+osmallkatakanahalfwidth;FF6B
+ostrokeacute;01FF
+osuperior;F6F0
+otcyrillic;047F
+otilde;00F5
+otildeacute;1E4D
+otildedieresis;1E4F
+oubopomofo;3121
+overline;203E
+overlinecenterline;FE4A
+overlinecmb;0305
+overlinedashed;FE49
+overlinedblwavy;FE4C
+overlinewavy;FE4B
+overscore;00AF
+ovowelsignbengali;09CB
+ovowelsigndeva;094B
+ovowelsigngujarati;0ACB
+p;0070
+paampssquare;3380
+paasentosquare;332B
+pabengali;09AA
+pacute;1E55
+padeva;092A
+pagedown;21DF
+pageup;21DE
+pagujarati;0AAA
+pagurmukhi;0A2A
+pahiragana;3071
+paiyannoithai;0E2F
+pakatakana;30D1
+palatalizationcyrilliccmb;0484
+palochkacyrillic;04C0
+pansioskorean;317F
+paragraph;00B6
+parallel;2225
+parenleft;0028
+parenleftaltonearabic;FD3E
+parenleftbt;F8ED
+parenleftex;F8EC
+parenleftinferior;208D
+parenleftmonospace;FF08
+parenleftsmall;FE59
+parenleftsuperior;207D
+parenlefttp;F8EB
+parenleftvertical;FE35
+parenright;0029
+parenrightaltonearabic;FD3F
+parenrightbt;F8F8
+parenrightex;F8F7
+parenrightinferior;208E
+parenrightmonospace;FF09
+parenrightsmall;FE5A
+parenrightsuperior;207E
+parenrighttp;F8F6
+parenrightvertical;FE36
+partialdiff;2202
+paseqhebrew;05C0
+pashtahebrew;0599
+pasquare;33A9
+patah;05B7
+patah11;05B7
+patah1d;05B7
+patah2a;05B7
+patahhebrew;05B7
+patahnarrowhebrew;05B7
+patahquarterhebrew;05B7
+patahwidehebrew;05B7
+pazerhebrew;05A1
+pbopomofo;3106
+pcircle;24DF
+pdotaccent;1E57
+pe;05E4
+pecyrillic;043F
+pedagesh;FB44
+pedageshhebrew;FB44
+peezisquare;333B
+pefinaldageshhebrew;FB43
+peharabic;067E
+peharmenian;057A
+pehebrew;05E4
+pehfinalarabic;FB57
+pehinitialarabic;FB58
+pehiragana;307A
+pehmedialarabic;FB59
+pekatakana;30DA
+pemiddlehookcyrillic;04A7
+perafehebrew;FB4E
+percent;0025
+percentarabic;066A
+percentmonospace;FF05
+percentsmall;FE6A
+period;002E
+periodarmenian;0589
+periodcentered;00B7
+periodhalfwidth;FF61
+periodinferior;F6E7
+periodmonospace;FF0E
+periodsmall;FE52
+periodsuperior;F6E8
+perispomenigreekcmb;0342
+perpendicular;22A5
+perthousand;2030
+peseta;20A7
+pfsquare;338A
+phabengali;09AB
+phadeva;092B
+phagujarati;0AAB
+phagurmukhi;0A2B
+phi;03C6
+phi1;03D5
+phieuphacirclekorean;327A
+phieuphaparenkorean;321A
+phieuphcirclekorean;326C
+phieuphkorean;314D
+phieuphparenkorean;320C
+philatin;0278
+phinthuthai;0E3A
+phisymbolgreek;03D5
+phook;01A5
+phophanthai;0E1E
+phophungthai;0E1C
+phosamphaothai;0E20
+pi;03C0
+pieupacirclekorean;3273
+pieupaparenkorean;3213
+pieupcieuckorean;3176
+pieupcirclekorean;3265
+pieupkiyeokkorean;3172
+pieupkorean;3142
+pieupparenkorean;3205
+pieupsioskiyeokkorean;3174
+pieupsioskorean;3144
+pieupsiostikeutkorean;3175
+pieupthieuthkorean;3177
+pieuptikeutkorean;3173
+pihiragana;3074
+pikatakana;30D4
+pisymbolgreek;03D6
+piwrarmenian;0583
+plus;002B
+plusbelowcmb;031F
+pluscircle;2295
+plusminus;00B1
+plusmod;02D6
+plusmonospace;FF0B
+plussmall;FE62
+plussuperior;207A
+pmonospace;FF50
+pmsquare;33D8
+pohiragana;307D
+pointingindexdownwhite;261F
+pointingindexleftwhite;261C
+pointingindexrightwhite;261E
+pointingindexupwhite;261D
+pokatakana;30DD
+poplathai;0E1B
+postalmark;3012
+postalmarkface;3020
+pparen;24AB
+precedes;227A
+prescription;211E
+primemod;02B9
+primereversed;2035
+product;220F
+projective;2305
+prolongedkana;30FC
+propellor;2318
+propersubset;2282
+propersuperset;2283
+proportion;2237
+proportional;221D
+psi;03C8
+psicyrillic;0471
+psilipneumatacyrilliccmb;0486
+pssquare;33B0
+puhiragana;3077
+pukatakana;30D7
+pvsquare;33B4
+pwsquare;33BA
+q;0071
+qadeva;0958
+qadmahebrew;05A8
+qafarabic;0642
+qaffinalarabic;FED6
+qafinitialarabic;FED7
+qafmedialarabic;FED8
+qamats;05B8
+qamats10;05B8
+qamats1a;05B8
+qamats1c;05B8
+qamats27;05B8
+qamats29;05B8
+qamats33;05B8
+qamatsde;05B8
+qamatshebrew;05B8
+qamatsnarrowhebrew;05B8
+qamatsqatanhebrew;05B8
+qamatsqatannarrowhebrew;05B8
+qamatsqatanquarterhebrew;05B8
+qamatsqatanwidehebrew;05B8
+qamatsquarterhebrew;05B8
+qamatswidehebrew;05B8
+qarneyparahebrew;059F
+qbopomofo;3111
+qcircle;24E0
+qhook;02A0
+qmonospace;FF51
+qof;05E7
+qofdagesh;FB47
+qofdageshhebrew;FB47
+qofhatafpatah;05E7 05B2
+qofhatafpatahhebrew;05E7 05B2
+qofhatafsegol;05E7 05B1
+qofhatafsegolhebrew;05E7 05B1
+qofhebrew;05E7
+qofhiriq;05E7 05B4
+qofhiriqhebrew;05E7 05B4
+qofholam;05E7 05B9
+qofholamhebrew;05E7 05B9
+qofpatah;05E7 05B7
+qofpatahhebrew;05E7 05B7
+qofqamats;05E7 05B8
+qofqamatshebrew;05E7 05B8
+qofqubuts;05E7 05BB
+qofqubutshebrew;05E7 05BB
+qofsegol;05E7 05B6
+qofsegolhebrew;05E7 05B6
+qofsheva;05E7 05B0
+qofshevahebrew;05E7 05B0
+qoftsere;05E7 05B5
+qoftserehebrew;05E7 05B5
+qparen;24AC
+quarternote;2669
+qubuts;05BB
+qubuts18;05BB
+qubuts25;05BB
+qubuts31;05BB
+qubutshebrew;05BB
+qubutsnarrowhebrew;05BB
+qubutsquarterhebrew;05BB
+qubutswidehebrew;05BB
+question;003F
+questionarabic;061F
+questionarmenian;055E
+questiondown;00BF
+questiondownsmall;F7BF
+questiongreek;037E
+questionmonospace;FF1F
+questionsmall;F73F
+quotedbl;0022
+quotedblbase;201E
+quotedblleft;201C
+quotedblmonospace;FF02
+quotedblprime;301E
+quotedblprimereversed;301D
+quotedblright;201D
+quoteleft;2018
+quoteleftreversed;201B
+quotereversed;201B
+quoteright;2019
+quoterightn;0149
+quotesinglbase;201A
+quotesingle;0027
+quotesinglemonospace;FF07
+r;0072
+raarmenian;057C
+rabengali;09B0
+racute;0155
+radeva;0930
+radical;221A
+radicalex;F8E5
+radoverssquare;33AE
+radoverssquaredsquare;33AF
+radsquare;33AD
+rafe;05BF
+rafehebrew;05BF
+ragujarati;0AB0
+ragurmukhi;0A30
+rahiragana;3089
+rakatakana;30E9
+rakatakanahalfwidth;FF97
+ralowerdiagonalbengali;09F1
+ramiddlediagonalbengali;09F0
+ramshorn;0264
+ratio;2236
+rbopomofo;3116
+rcaron;0159
+rcedilla;0157
+rcircle;24E1
+rcommaaccent;0157
+rdblgrave;0211
+rdotaccent;1E59
+rdotbelow;1E5B
+rdotbelowmacron;1E5D
+referencemark;203B
+reflexsubset;2286
+reflexsuperset;2287
+registered;00AE
+registersans;F8E8
+registerserif;F6DA
+reharabic;0631
+reharmenian;0580
+rehfinalarabic;FEAE
+rehiragana;308C
+rehyehaleflamarabic;0631 FEF3 FE8E 0644
+rekatakana;30EC
+rekatakanahalfwidth;FF9A
+resh;05E8
+reshdageshhebrew;FB48
+reshhatafpatah;05E8 05B2
+reshhatafpatahhebrew;05E8 05B2
+reshhatafsegol;05E8 05B1
+reshhatafsegolhebrew;05E8 05B1
+reshhebrew;05E8
+reshhiriq;05E8 05B4
+reshhiriqhebrew;05E8 05B4
+reshholam;05E8 05B9
+reshholamhebrew;05E8 05B9
+reshpatah;05E8 05B7
+reshpatahhebrew;05E8 05B7
+reshqamats;05E8 05B8
+reshqamatshebrew;05E8 05B8
+reshqubuts;05E8 05BB
+reshqubutshebrew;05E8 05BB
+reshsegol;05E8 05B6
+reshsegolhebrew;05E8 05B6
+reshsheva;05E8 05B0
+reshshevahebrew;05E8 05B0
+reshtsere;05E8 05B5
+reshtserehebrew;05E8 05B5
+reversedtilde;223D
+reviahebrew;0597
+reviamugrashhebrew;0597
+revlogicalnot;2310
+rfishhook;027E
+rfishhookreversed;027F
+rhabengali;09DD
+rhadeva;095D
+rho;03C1
+rhook;027D
+rhookturned;027B
+rhookturnedsuperior;02B5
+rhosymbolgreek;03F1
+rhotichookmod;02DE
+rieulacirclekorean;3271
+rieulaparenkorean;3211
+rieulcirclekorean;3263
+rieulhieuhkorean;3140
+rieulkiyeokkorean;313A
+rieulkiyeoksioskorean;3169
+rieulkorean;3139
+rieulmieumkorean;313B
+rieulpansioskorean;316C
+rieulparenkorean;3203
+rieulphieuphkorean;313F
+rieulpieupkorean;313C
+rieulpieupsioskorean;316B
+rieulsioskorean;313D
+rieulthieuthkorean;313E
+rieultikeutkorean;316A
+rieulyeorinhieuhkorean;316D
+rightangle;221F
+righttackbelowcmb;0319
+righttriangle;22BF
+rihiragana;308A
+rikatakana;30EA
+rikatakanahalfwidth;FF98
+ring;02DA
+ringbelowcmb;0325
+ringcmb;030A
+ringhalfleft;02BF
+ringhalfleftarmenian;0559
+ringhalfleftbelowcmb;031C
+ringhalfleftcentered;02D3
+ringhalfright;02BE
+ringhalfrightbelowcmb;0339
+ringhalfrightcentered;02D2
+rinvertedbreve;0213
+rittorusquare;3351
+rlinebelow;1E5F
+rlongleg;027C
+rlonglegturned;027A
+rmonospace;FF52
+rohiragana;308D
+rokatakana;30ED
+rokatakanahalfwidth;FF9B
+roruathai;0E23
+rparen;24AD
+rrabengali;09DC
+rradeva;0931
+rragurmukhi;0A5C
+rreharabic;0691
+rrehfinalarabic;FB8D
+rrvocalicbengali;09E0
+rrvocalicdeva;0960
+rrvocalicgujarati;0AE0
+rrvocalicvowelsignbengali;09C4
+rrvocalicvowelsigndeva;0944
+rrvocalicvowelsigngujarati;0AC4
+rsuperior;F6F1
+rtblock;2590
+rturned;0279
+rturnedsuperior;02B4
+ruhiragana;308B
+rukatakana;30EB
+rukatakanahalfwidth;FF99
+rupeemarkbengali;09F2
+rupeesignbengali;09F3
+rupiah;F6DD
+ruthai;0E24
+rvocalicbengali;098B
+rvocalicdeva;090B
+rvocalicgujarati;0A8B
+rvocalicvowelsignbengali;09C3
+rvocalicvowelsigndeva;0943
+rvocalicvowelsigngujarati;0AC3
+s;0073
+sabengali;09B8
+sacute;015B
+sacutedotaccent;1E65
+sadarabic;0635
+sadeva;0938
+sadfinalarabic;FEBA
+sadinitialarabic;FEBB
+sadmedialarabic;FEBC
+sagujarati;0AB8
+sagurmukhi;0A38
+sahiragana;3055
+sakatakana;30B5
+sakatakanahalfwidth;FF7B
+sallallahoualayhewasallamarabic;FDFA
+samekh;05E1
+samekhdagesh;FB41
+samekhdageshhebrew;FB41
+samekhhebrew;05E1
+saraaathai;0E32
+saraaethai;0E41
+saraaimaimalaithai;0E44
+saraaimaimuanthai;0E43
+saraamthai;0E33
+saraathai;0E30
+saraethai;0E40
+saraiileftthai;F886
+saraiithai;0E35
+saraileftthai;F885
+saraithai;0E34
+saraothai;0E42
+saraueeleftthai;F888
+saraueethai;0E37
+saraueleftthai;F887
+sarauethai;0E36
+sarauthai;0E38
+sarauuthai;0E39
+sbopomofo;3119
+scaron;0161
+scarondotaccent;1E67
+scedilla;015F
+schwa;0259
+schwacyrillic;04D9
+schwadieresiscyrillic;04DB
+schwahook;025A
+scircle;24E2
+scircumflex;015D
+scommaaccent;0219
+sdotaccent;1E61
+sdotbelow;1E63
+sdotbelowdotaccent;1E69
+seagullbelowcmb;033C
+second;2033
+secondtonechinese;02CA
+section;00A7
+seenarabic;0633
+seenfinalarabic;FEB2
+seeninitialarabic;FEB3
+seenmedialarabic;FEB4
+segol;05B6
+segol13;05B6
+segol1f;05B6
+segol2c;05B6
+segolhebrew;05B6
+segolnarrowhebrew;05B6
+segolquarterhebrew;05B6
+segoltahebrew;0592
+segolwidehebrew;05B6
+seharmenian;057D
+sehiragana;305B
+sekatakana;30BB
+sekatakanahalfwidth;FF7E
+semicolon;003B
+semicolonarabic;061B
+semicolonmonospace;FF1B
+semicolonsmall;FE54
+semivoicedmarkkana;309C
+semivoicedmarkkanahalfwidth;FF9F
+sentisquare;3322
+sentosquare;3323
+seven;0037
+sevenarabic;0667
+sevenbengali;09ED
+sevencircle;2466
+sevencircleinversesansserif;2790
+sevendeva;096D
+seveneighths;215E
+sevengujarati;0AED
+sevengurmukhi;0A6D
+sevenhackarabic;0667
+sevenhangzhou;3027
+sevenideographicparen;3226
+seveninferior;2087
+sevenmonospace;FF17
+sevenoldstyle;F737
+sevenparen;247A
+sevenperiod;248E
+sevenpersian;06F7
+sevenroman;2176
+sevensuperior;2077
+seventeencircle;2470
+seventeenparen;2484
+seventeenperiod;2498
+seventhai;0E57
+sfthyphen;00AD
+shaarmenian;0577
+shabengali;09B6
+shacyrillic;0448
+shaddaarabic;0651
+shaddadammaarabic;FC61
+shaddadammatanarabic;FC5E
+shaddafathaarabic;FC60
+shaddafathatanarabic;0651 064B
+shaddakasraarabic;FC62
+shaddakasratanarabic;FC5F
+shade;2592
+shadedark;2593
+shadelight;2591
+shademedium;2592
+shadeva;0936
+shagujarati;0AB6
+shagurmukhi;0A36
+shalshelethebrew;0593
+shbopomofo;3115
+shchacyrillic;0449
+sheenarabic;0634
+sheenfinalarabic;FEB6
+sheeninitialarabic;FEB7
+sheenmedialarabic;FEB8
+sheicoptic;03E3
+sheqel;20AA
+sheqelhebrew;20AA
+sheva;05B0
+sheva115;05B0
+sheva15;05B0
+sheva22;05B0
+sheva2e;05B0
+shevahebrew;05B0
+shevanarrowhebrew;05B0
+shevaquarterhebrew;05B0
+shevawidehebrew;05B0
+shhacyrillic;04BB
+shimacoptic;03ED
+shin;05E9
+shindagesh;FB49
+shindageshhebrew;FB49
+shindageshshindot;FB2C
+shindageshshindothebrew;FB2C
+shindageshsindot;FB2D
+shindageshsindothebrew;FB2D
+shindothebrew;05C1
+shinhebrew;05E9
+shinshindot;FB2A
+shinshindothebrew;FB2A
+shinsindot;FB2B
+shinsindothebrew;FB2B
+shook;0282
+sigma;03C3
+sigma1;03C2
+sigmafinal;03C2
+sigmalunatesymbolgreek;03F2
+sihiragana;3057
+sikatakana;30B7
+sikatakanahalfwidth;FF7C
+siluqhebrew;05BD
+siluqlefthebrew;05BD
+similar;223C
+sindothebrew;05C2
+siosacirclekorean;3274
+siosaparenkorean;3214
+sioscieuckorean;317E
+sioscirclekorean;3266
+sioskiyeokkorean;317A
+sioskorean;3145
+siosnieunkorean;317B
+siosparenkorean;3206
+siospieupkorean;317D
+siostikeutkorean;317C
+six;0036
+sixarabic;0666
+sixbengali;09EC
+sixcircle;2465
+sixcircleinversesansserif;278F
+sixdeva;096C
+sixgujarati;0AEC
+sixgurmukhi;0A6C
+sixhackarabic;0666
+sixhangzhou;3026
+sixideographicparen;3225
+sixinferior;2086
+sixmonospace;FF16
+sixoldstyle;F736
+sixparen;2479
+sixperiod;248D
+sixpersian;06F6
+sixroman;2175
+sixsuperior;2076
+sixteencircle;246F
+sixteencurrencydenominatorbengali;09F9
+sixteenparen;2483
+sixteenperiod;2497
+sixthai;0E56
+slash;002F
+slashmonospace;FF0F
+slong;017F
+slongdotaccent;1E9B
+smileface;263A
+smonospace;FF53
+sofpasuqhebrew;05C3
+softhyphen;00AD
+softsigncyrillic;044C
+sohiragana;305D
+sokatakana;30BD
+sokatakanahalfwidth;FF7F
+soliduslongoverlaycmb;0338
+solidusshortoverlaycmb;0337
+sorusithai;0E29
+sosalathai;0E28
+sosothai;0E0B
+sosuathai;0E2A
+space;0020
+spacehackarabic;0020
+spade;2660
+spadesuitblack;2660
+spadesuitwhite;2664
+sparen;24AE
+squarebelowcmb;033B
+squarecc;33C4
+squarecm;339D
+squarediagonalcrosshatchfill;25A9
+squarehorizontalfill;25A4
+squarekg;338F
+squarekm;339E
+squarekmcapital;33CE
+squareln;33D1
+squarelog;33D2
+squaremg;338E
+squaremil;33D5
+squaremm;339C
+squaremsquared;33A1
+squareorthogonalcrosshatchfill;25A6
+squareupperlefttolowerrightfill;25A7
+squareupperrighttolowerleftfill;25A8
+squareverticalfill;25A5
+squarewhitewithsmallblack;25A3
+srsquare;33DB
+ssabengali;09B7
+ssadeva;0937
+ssagujarati;0AB7
+ssangcieuckorean;3149
+ssanghieuhkorean;3185
+ssangieungkorean;3180
+ssangkiyeokkorean;3132
+ssangnieunkorean;3165
+ssangpieupkorean;3143
+ssangsioskorean;3146
+ssangtikeutkorean;3138
+ssuperior;F6F2
+sterling;00A3
+sterlingmonospace;FFE1
+strokelongoverlaycmb;0336
+strokeshortoverlaycmb;0335
+subset;2282
+subsetnotequal;228A
+subsetorequal;2286
+succeeds;227B
+suchthat;220B
+suhiragana;3059
+sukatakana;30B9
+sukatakanahalfwidth;FF7D
+sukunarabic;0652
+summation;2211
+sun;263C
+superset;2283
+supersetnotequal;228B
+supersetorequal;2287
+svsquare;33DC
+syouwaerasquare;337C
+t;0074
+tabengali;09A4
+tackdown;22A4
+tackleft;22A3
+tadeva;0924
+tagujarati;0AA4
+tagurmukhi;0A24
+taharabic;0637
+tahfinalarabic;FEC2
+tahinitialarabic;FEC3
+tahiragana;305F
+tahmedialarabic;FEC4
+taisyouerasquare;337D
+takatakana;30BF
+takatakanahalfwidth;FF80
+tatweelarabic;0640
+tau;03C4
+tav;05EA
+tavdages;FB4A
+tavdagesh;FB4A
+tavdageshhebrew;FB4A
+tavhebrew;05EA
+tbar;0167
+tbopomofo;310A
+tcaron;0165
+tccurl;02A8
+tcedilla;0163
+tcheharabic;0686
+tchehfinalarabic;FB7B
+tchehinitialarabic;FB7C
+tchehmedialarabic;FB7D
+tchehmeeminitialarabic;FB7C FEE4
+tcircle;24E3
+tcircumflexbelow;1E71
+tcommaaccent;0163
+tdieresis;1E97
+tdotaccent;1E6B
+tdotbelow;1E6D
+tecyrillic;0442
+tedescendercyrillic;04AD
+teharabic;062A
+tehfinalarabic;FE96
+tehhahinitialarabic;FCA2
+tehhahisolatedarabic;FC0C
+tehinitialarabic;FE97
+tehiragana;3066
+tehjeeminitialarabic;FCA1
+tehjeemisolatedarabic;FC0B
+tehmarbutaarabic;0629
+tehmarbutafinalarabic;FE94
+tehmedialarabic;FE98
+tehmeeminitialarabic;FCA4
+tehmeemisolatedarabic;FC0E
+tehnoonfinalarabic;FC73
+tekatakana;30C6
+tekatakanahalfwidth;FF83
+telephone;2121
+telephoneblack;260E
+telishagedolahebrew;05A0
+telishaqetanahebrew;05A9
+tencircle;2469
+tenideographicparen;3229
+tenparen;247D
+tenperiod;2491
+tenroman;2179
+tesh;02A7
+tet;05D8
+tetdagesh;FB38
+tetdageshhebrew;FB38
+tethebrew;05D8
+tetsecyrillic;04B5
+tevirhebrew;059B
+tevirlefthebrew;059B
+thabengali;09A5
+thadeva;0925
+thagujarati;0AA5
+thagurmukhi;0A25
+thalarabic;0630
+thalfinalarabic;FEAC
+thanthakhatlowleftthai;F898
+thanthakhatlowrightthai;F897
+thanthakhatthai;0E4C
+thanthakhatupperleftthai;F896
+theharabic;062B
+thehfinalarabic;FE9A
+thehinitialarabic;FE9B
+thehmedialarabic;FE9C
+thereexists;2203
+therefore;2234
+theta;03B8
+theta1;03D1
+thetasymbolgreek;03D1
+thieuthacirclekorean;3279
+thieuthaparenkorean;3219
+thieuthcirclekorean;326B
+thieuthkorean;314C
+thieuthparenkorean;320B
+thirteencircle;246C
+thirteenparen;2480
+thirteenperiod;2494
+thonangmonthothai;0E11
+thook;01AD
+thophuthaothai;0E12
+thorn;00FE
+thothahanthai;0E17
+thothanthai;0E10
+thothongthai;0E18
+thothungthai;0E16
+thousandcyrillic;0482
+thousandsseparatorarabic;066C
+thousandsseparatorpersian;066C
+three;0033
+threearabic;0663
+threebengali;09E9
+threecircle;2462
+threecircleinversesansserif;278C
+threedeva;0969
+threeeighths;215C
+threegujarati;0AE9
+threegurmukhi;0A69
+threehackarabic;0663
+threehangzhou;3023
+threeideographicparen;3222
+threeinferior;2083
+threemonospace;FF13
+threenumeratorbengali;09F6
+threeoldstyle;F733
+threeparen;2476
+threeperiod;248A
+threepersian;06F3
+threequarters;00BE
+threequartersemdash;F6DE
+threeroman;2172
+threesuperior;00B3
+threethai;0E53
+thzsquare;3394
+tihiragana;3061
+tikatakana;30C1
+tikatakanahalfwidth;FF81
+tikeutacirclekorean;3270
+tikeutaparenkorean;3210
+tikeutcirclekorean;3262
+tikeutkorean;3137
+tikeutparenkorean;3202
+tilde;02DC
+tildebelowcmb;0330
+tildecmb;0303
+tildecomb;0303
+tildedoublecmb;0360
+tildeoperator;223C
+tildeoverlaycmb;0334
+tildeverticalcmb;033E
+timescircle;2297
+tipehahebrew;0596
+tipehalefthebrew;0596
+tippigurmukhi;0A70
+titlocyrilliccmb;0483
+tiwnarmenian;057F
+tlinebelow;1E6F
+tmonospace;FF54
+toarmenian;0569
+tohiragana;3068
+tokatakana;30C8
+tokatakanahalfwidth;FF84
+tonebarextrahighmod;02E5
+tonebarextralowmod;02E9
+tonebarhighmod;02E6
+tonebarlowmod;02E8
+tonebarmidmod;02E7
+tonefive;01BD
+tonesix;0185
+tonetwo;01A8
+tonos;0384
+tonsquare;3327
+topatakthai;0E0F
+tortoiseshellbracketleft;3014
+tortoiseshellbracketleftsmall;FE5D
+tortoiseshellbracketleftvertical;FE39
+tortoiseshellbracketright;3015
+tortoiseshellbracketrightsmall;FE5E
+tortoiseshellbracketrightvertical;FE3A
+totaothai;0E15
+tpalatalhook;01AB
+tparen;24AF
+trademark;2122
+trademarksans;F8EA
+trademarkserif;F6DB
+tretroflexhook;0288
+triagdn;25BC
+triaglf;25C4
+triagrt;25BA
+triagup;25B2
+ts;02A6
+tsadi;05E6
+tsadidagesh;FB46
+tsadidageshhebrew;FB46
+tsadihebrew;05E6
+tsecyrillic;0446
+tsere;05B5
+tsere12;05B5
+tsere1e;05B5
+tsere2b;05B5
+tserehebrew;05B5
+tserenarrowhebrew;05B5
+tserequarterhebrew;05B5
+tserewidehebrew;05B5
+tshecyrillic;045B
+tsuperior;F6F3
+ttabengali;099F
+ttadeva;091F
+ttagujarati;0A9F
+ttagurmukhi;0A1F
+tteharabic;0679
+ttehfinalarabic;FB67
+ttehinitialarabic;FB68
+ttehmedialarabic;FB69
+tthabengali;09A0
+tthadeva;0920
+tthagujarati;0AA0
+tthagurmukhi;0A20
+tturned;0287
+tuhiragana;3064
+tukatakana;30C4
+tukatakanahalfwidth;FF82
+tusmallhiragana;3063
+tusmallkatakana;30C3
+tusmallkatakanahalfwidth;FF6F
+twelvecircle;246B
+twelveparen;247F
+twelveperiod;2493
+twelveroman;217B
+twentycircle;2473
+twentyhangzhou;5344
+twentyparen;2487
+twentyperiod;249B
+two;0032
+twoarabic;0662
+twobengali;09E8
+twocircle;2461
+twocircleinversesansserif;278B
+twodeva;0968
+twodotenleader;2025
+twodotleader;2025
+twodotleadervertical;FE30
+twogujarati;0AE8
+twogurmukhi;0A68
+twohackarabic;0662
+twohangzhou;3022
+twoideographicparen;3221
+twoinferior;2082
+twomonospace;FF12
+twonumeratorbengali;09F5
+twooldstyle;F732
+twoparen;2475
+twoperiod;2489
+twopersian;06F2
+tworoman;2171
+twostroke;01BB
+twosuperior;00B2
+twothai;0E52
+twothirds;2154
+u;0075
+uacute;00FA
+ubar;0289
+ubengali;0989
+ubopomofo;3128
+ubreve;016D
+ucaron;01D4
+ucircle;24E4
+ucircumflex;00FB
+ucircumflexbelow;1E77
+ucyrillic;0443
+udattadeva;0951
+udblacute;0171
+udblgrave;0215
+udeva;0909
+udieresis;00FC
+udieresisacute;01D8
+udieresisbelow;1E73
+udieresiscaron;01DA
+udieresiscyrillic;04F1
+udieresisgrave;01DC
+udieresismacron;01D6
+udotbelow;1EE5
+ugrave;00F9
+ugujarati;0A89
+ugurmukhi;0A09
+uhiragana;3046
+uhookabove;1EE7
+uhorn;01B0
+uhornacute;1EE9
+uhorndotbelow;1EF1
+uhorngrave;1EEB
+uhornhookabove;1EED
+uhorntilde;1EEF
+uhungarumlaut;0171
+uhungarumlautcyrillic;04F3
+uinvertedbreve;0217
+ukatakana;30A6
+ukatakanahalfwidth;FF73
+ukcyrillic;0479
+ukorean;315C
+umacron;016B
+umacroncyrillic;04EF
+umacrondieresis;1E7B
+umatragurmukhi;0A41
+umonospace;FF55
+underscore;005F
+underscoredbl;2017
+underscoremonospace;FF3F
+underscorevertical;FE33
+underscorewavy;FE4F
+union;222A
+universal;2200
+uogonek;0173
+uparen;24B0
+upblock;2580
+upperdothebrew;05C4
+upsilon;03C5
+upsilondieresis;03CB
+upsilondieresistonos;03B0
+upsilonlatin;028A
+upsilontonos;03CD
+uptackbelowcmb;031D
+uptackmod;02D4
+uragurmukhi;0A73
+uring;016F
+ushortcyrillic;045E
+usmallhiragana;3045
+usmallkatakana;30A5
+usmallkatakanahalfwidth;FF69
+ustraightcyrillic;04AF
+ustraightstrokecyrillic;04B1
+utilde;0169
+utildeacute;1E79
+utildebelow;1E75
+uubengali;098A
+uudeva;090A
+uugujarati;0A8A
+uugurmukhi;0A0A
+uumatragurmukhi;0A42
+uuvowelsignbengali;09C2
+uuvowelsigndeva;0942
+uuvowelsigngujarati;0AC2
+uvowelsignbengali;09C1
+uvowelsigndeva;0941
+uvowelsigngujarati;0AC1
+v;0076
+vadeva;0935
+vagujarati;0AB5
+vagurmukhi;0A35
+vakatakana;30F7
+vav;05D5
+vavdagesh;FB35
+vavdagesh65;FB35
+vavdageshhebrew;FB35
+vavhebrew;05D5
+vavholam;FB4B
+vavholamhebrew;FB4B
+vavvavhebrew;05F0
+vavyodhebrew;05F1
+vcircle;24E5
+vdotbelow;1E7F
+vecyrillic;0432
+veharabic;06A4
+vehfinalarabic;FB6B
+vehinitialarabic;FB6C
+vehmedialarabic;FB6D
+vekatakana;30F9
+venus;2640
+verticalbar;007C
+verticallineabovecmb;030D
+verticallinebelowcmb;0329
+verticallinelowmod;02CC
+verticallinemod;02C8
+vewarmenian;057E
+vhook;028B
+vikatakana;30F8
+viramabengali;09CD
+viramadeva;094D
+viramagujarati;0ACD
+visargabengali;0983
+visargadeva;0903
+visargagujarati;0A83
+vmonospace;FF56
+voarmenian;0578
+voicediterationhiragana;309E
+voicediterationkatakana;30FE
+voicedmarkkana;309B
+voicedmarkkanahalfwidth;FF9E
+vokatakana;30FA
+vparen;24B1
+vtilde;1E7D
+vturned;028C
+vuhiragana;3094
+vukatakana;30F4
+w;0077
+wacute;1E83
+waekorean;3159
+wahiragana;308F
+wakatakana;30EF
+wakatakanahalfwidth;FF9C
+wakorean;3158
+wasmallhiragana;308E
+wasmallkatakana;30EE
+wattosquare;3357
+wavedash;301C
+wavyunderscorevertical;FE34
+wawarabic;0648
+wawfinalarabic;FEEE
+wawhamzaabovearabic;0624
+wawhamzaabovefinalarabic;FE86
+wbsquare;33DD
+wcircle;24E6
+wcircumflex;0175
+wdieresis;1E85
+wdotaccent;1E87
+wdotbelow;1E89
+wehiragana;3091
+weierstrass;2118
+wekatakana;30F1
+wekorean;315E
+weokorean;315D
+wgrave;1E81
+whitebullet;25E6
+whitecircle;25CB
+whitecircleinverse;25D9
+whitecornerbracketleft;300E
+whitecornerbracketleftvertical;FE43
+whitecornerbracketright;300F
+whitecornerbracketrightvertical;FE44
+whitediamond;25C7
+whitediamondcontainingblacksmalldiamond;25C8
+whitedownpointingsmalltriangle;25BF
+whitedownpointingtriangle;25BD
+whiteleftpointingsmalltriangle;25C3
+whiteleftpointingtriangle;25C1
+whitelenticularbracketleft;3016
+whitelenticularbracketright;3017
+whiterightpointingsmalltriangle;25B9
+whiterightpointingtriangle;25B7
+whitesmallsquare;25AB
+whitesmilingface;263A
+whitesquare;25A1
+whitestar;2606
+whitetelephone;260F
+whitetortoiseshellbracketleft;3018
+whitetortoiseshellbracketright;3019
+whiteuppointingsmalltriangle;25B5
+whiteuppointingtriangle;25B3
+wihiragana;3090
+wikatakana;30F0
+wikorean;315F
+wmonospace;FF57
+wohiragana;3092
+wokatakana;30F2
+wokatakanahalfwidth;FF66
+won;20A9
+wonmonospace;FFE6
+wowaenthai;0E27
+wparen;24B2
+wring;1E98
+wsuperior;02B7
+wturned;028D
+wynn;01BF
+x;0078
+xabovecmb;033D
+xbopomofo;3112
+xcircle;24E7
+xdieresis;1E8D
+xdotaccent;1E8B
+xeharmenian;056D
+xi;03BE
+xmonospace;FF58
+xparen;24B3
+xsuperior;02E3
+y;0079
+yaadosquare;334E
+yabengali;09AF
+yacute;00FD
+yadeva;092F
+yaekorean;3152
+yagujarati;0AAF
+yagurmukhi;0A2F
+yahiragana;3084
+yakatakana;30E4
+yakatakanahalfwidth;FF94
+yakorean;3151
+yamakkanthai;0E4E
+yasmallhiragana;3083
+yasmallkatakana;30E3
+yasmallkatakanahalfwidth;FF6C
+yatcyrillic;0463
+ycircle;24E8
+ycircumflex;0177
+ydieresis;00FF
+ydotaccent;1E8F
+ydotbelow;1EF5
+yeharabic;064A
+yehbarreearabic;06D2
+yehbarreefinalarabic;FBAF
+yehfinalarabic;FEF2
+yehhamzaabovearabic;0626
+yehhamzaabovefinalarabic;FE8A
+yehhamzaaboveinitialarabic;FE8B
+yehhamzaabovemedialarabic;FE8C
+yehinitialarabic;FEF3
+yehmedialarabic;FEF4
+yehmeeminitialarabic;FCDD
+yehmeemisolatedarabic;FC58
+yehnoonfinalarabic;FC94
+yehthreedotsbelowarabic;06D1
+yekorean;3156
+yen;00A5
+yenmonospace;FFE5
+yeokorean;3155
+yeorinhieuhkorean;3186
+yerahbenyomohebrew;05AA
+yerahbenyomolefthebrew;05AA
+yericyrillic;044B
+yerudieresiscyrillic;04F9
+yesieungkorean;3181
+yesieungpansioskorean;3183
+yesieungsioskorean;3182
+yetivhebrew;059A
+ygrave;1EF3
+yhook;01B4
+yhookabove;1EF7
+yiarmenian;0575
+yicyrillic;0457
+yikorean;3162
+yinyang;262F
+yiwnarmenian;0582
+ymonospace;FF59
+yod;05D9
+yoddagesh;FB39
+yoddageshhebrew;FB39
+yodhebrew;05D9
+yodyodhebrew;05F2
+yodyodpatahhebrew;FB1F
+yohiragana;3088
+yoikorean;3189
+yokatakana;30E8
+yokatakanahalfwidth;FF96
+yokorean;315B
+yosmallhiragana;3087
+yosmallkatakana;30E7
+yosmallkatakanahalfwidth;FF6E
+yotgreek;03F3
+yoyaekorean;3188
+yoyakorean;3187
+yoyakthai;0E22
+yoyingthai;0E0D
+yparen;24B4
+ypogegrammeni;037A
+ypogegrammenigreekcmb;0345
+yr;01A6
+yring;1E99
+ysuperior;02B8
+ytilde;1EF9
+yturned;028E
+yuhiragana;3086
+yuikorean;318C
+yukatakana;30E6
+yukatakanahalfwidth;FF95
+yukorean;3160
+yusbigcyrillic;046B
+yusbigiotifiedcyrillic;046D
+yuslittlecyrillic;0467
+yuslittleiotifiedcyrillic;0469
+yusmallhiragana;3085
+yusmallkatakana;30E5
+yusmallkatakanahalfwidth;FF6D
+yuyekorean;318B
+yuyeokorean;318A
+yyabengali;09DF
+yyadeva;095F
+z;007A
+zaarmenian;0566
+zacute;017A
+zadeva;095B
+zagurmukhi;0A5B
+zaharabic;0638
+zahfinalarabic;FEC6
+zahinitialarabic;FEC7
+zahiragana;3056
+zahmedialarabic;FEC8
+zainarabic;0632
+zainfinalarabic;FEB0
+zakatakana;30B6
+zaqefgadolhebrew;0595
+zaqefqatanhebrew;0594
+zarqahebrew;0598
+zayin;05D6
+zayindagesh;FB36
+zayindageshhebrew;FB36
+zayinhebrew;05D6
+zbopomofo;3117
+zcaron;017E
+zcircle;24E9
+zcircumflex;1E91
+zcurl;0291
+zdot;017C
+zdotaccent;017C
+zdotbelow;1E93
+zecyrillic;0437
+zedescendercyrillic;0499
+zedieresiscyrillic;04DF
+zehiragana;305C
+zekatakana;30BC
+zero;0030
+zeroarabic;0660
+zerobengali;09E6
+zerodeva;0966
+zerogujarati;0AE6
+zerogurmukhi;0A66
+zerohackarabic;0660
+zeroinferior;2080
+zeromonospace;FF10
+zerooldstyle;F730
+zeropersian;06F0
+zerosuperior;2070
+zerothai;0E50
+zerowidthjoiner;FEFF
+zerowidthnonjoiner;200C
+zerowidthspace;200B
+zeta;03B6
+zhbopomofo;3113
+zhearmenian;056A
+zhebrevecyrillic;04C2
+zhecyrillic;0436
+zhedescendercyrillic;0497
+zhedieresiscyrillic;04DD
+zihiragana;3058
+zikatakana;30B8
+zinorhebrew;05AE
+zlinebelow;1E95
+zmonospace;FF5A
+zohiragana;305E
+zokatakana;30BE
+zparen;24B5
+zretroflexhook;0290
+zstroke;01B6
+zuhiragana;305A
+zukatakana;30BA
+# END
+"""
+
+
+_aglfnText = """\
+# -----------------------------------------------------------
+# Copyright 2002-2019 Adobe (http://www.adobe.com/).
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the
+# following conditions are met:
+#
+# Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# Neither the name of Adobe nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
 #
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
@@ -48,7 +4397,7 @@
 # Name:          Adobe Glyph List For New Fonts
 # Table version: 1.7
 # Date:          November 6, 2008
-# URL:           http://sourceforge.net/adobe/aglfn/
+# URL:           https://github.com/adobe-type-tools/agl-aglfn
 #
 # Description:
 #
@@ -705,13 +5054,14 @@
 017C;zdotaccent;LATIN SMALL LETTER Z WITH DOT ABOVE
 0030;zero;DIGIT ZERO
 03B6;zeta;GREEK SMALL LETTER ZETA
-#END
+# END
 """
 
 
 class AGLError(Exception):
 	pass
 
+LEGACY_AGL2UV = {}
 AGL2UV = {}
 UV2AGL = {}
 
@@ -720,7 +5070,7 @@
 
 	lines = _aglText.splitlines()
 
-	parseAGL_RE = re.compile("([0-9A-F]{4});([A-Za-z_0-9.]+);.*?$")
+	parseAGL_RE = re.compile("([A-Za-z0-9]+);((?:[0-9A-F]{4})(?: (?:[0-9A-F]{4}))*)$")
 
 	for line in lines:
 		if not line or line[:1] == '#':
@@ -728,24 +5078,36 @@
 		m = parseAGL_RE.match(line)
 		if not m:
 			raise AGLError("syntax error in glyphlist.txt: %s" % repr(line[:20]))
+		unicodes = m.group(2)
+		assert len(unicodes) % 5 == 4
+		unicodes = [int(unicode, 16) for unicode in unicodes.split()]
+		glyphName = tostr(m.group(1))
+		LEGACY_AGL2UV[glyphName] = unicodes
+
+	lines = _aglfnText.splitlines()
+
+	parseAGLFN_RE = re.compile("([0-9A-F]{4});([A-Za-z0-9]+);.*?$")
+
+	for line in lines:
+		if not line or line[:1] == '#':
+			continue
+		m = parseAGLFN_RE.match(line)
+		if not m:
+			raise AGLError("syntax error in aglfn.txt: %s" % repr(line[:20]))
 		unicode = m.group(1)
 		assert len(unicode) == 4
 		unicode = int(unicode, 16)
 		glyphName = tostr(m.group(2))
-		if glyphName in AGL2UV:
-			# the above table contains identical duplicates
-			assert AGL2UV[glyphName] == unicode
-		else:
-			AGL2UV[glyphName] = unicode
+		AGL2UV[glyphName] = unicode
 		UV2AGL[unicode] = glyphName
 
 _builddicts()
 
 
 def toUnicode(glyph, isZapfDingbats=False):
-	"""Convert glyph names to Unicode, such as 'longs_t.oldstyle' --> u'ſt'
+	"""Convert glyph names to Unicode, such as ``'longs_t.oldstyle'`` --> ``u'ſt'``
 
-	If isZapfDingbats is True, the implementation recognizes additional
+	If ``isZapfDingbats`` is ``True``, the implementation recognizes additional
 	glyph names (as required by the AGL specification).
 	"""
 	# https://github.com/adobe-type-tools/agl-specification#2-the-mapping
@@ -776,14 +5138,9 @@
 
 	# Otherwise, if the component is in AGL, then map it
 	# to the corresponding character in that list.
-	#
-	# TODO: We currently use the AGLFN (Adobe glyph list for new fonts),
-	# although the spec actually mandates the legacy AGL which is
-	# a superset of the AGLFN.
-	# https://github.com/fonttools/fonttools/issues/775
-	uchar = AGL2UV.get(component)
-	if uchar:
-		return unichr(uchar)
+	uchars = LEGACY_AGL2UV.get(component)
+	if uchars:
+		return "".join(map(chr, uchars))
 
 	# Otherwise, if the component is of the form "uni" (U+0075,
 	# U+006E, and U+0069) followed by a sequence of uppercase
@@ -853,7 +5210,7 @@
 	if any(c >= 0xD800 and c <= 0xDFFF for c in chars):
 		# The AGL specification explicitly excluded surrogate pairs.
 		return None
-	return ''.join([unichr(c) for c in chars])
+	return ''.join([chr(c) for c in chars])
 
 
 _re_u = re.compile("^u([0-9A-F]{4,6})$")
@@ -871,5 +5228,5 @@
 		return None
 	if ((value >= 0x0000 and value <= 0xD7FF) or
 	    (value >= 0xE000 and value <= 0x10FFFF)):
-		return unichr(value)
+		return chr(value)
 	return None
diff --git a/Lib/fontTools/cffLib/__init__.py b/Lib/fontTools/cffLib/__init__.py
index 59f5ab5..d4cd7a1 100644
--- a/Lib/fontTools/cffLib/__init__.py
+++ b/Lib/fontTools/cffLib/__init__.py
@@ -1,7 +1,17 @@
-"""cffLib.py -- read/write tools for Adobe CFF fonts."""
+"""cffLib: read/write Adobe CFF fonts
 
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+OpenType fonts with PostScript outlines contain a completely independent
+font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts
+requires also dealing with CFF. This module allows you to read and write
+fonts written in the CFF format.
+
+In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
+format which, along with other changes, extended the CFF format to deal with
+the demands of variable fonts. This module parses both original CFF and CFF2.
+
+"""
+
+from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tobytes, tostr
 from fontTools.misc import sstruct
 from fontTools.misc import psCharStrings
 from fontTools.misc.arrayTools import unionRect, intRect
@@ -10,6 +20,7 @@
 from fontTools.ttLib.tables.otBase import OTTableWriter
 from fontTools.ttLib.tables.otBase import OTTableReader
 from fontTools.ttLib.tables import otTables as ot
+from io import BytesIO
 import struct
 import logging
 import re
@@ -29,8 +40,37 @@
 
 
 class CFFFontSet(object):
+	"""A CFF font "file" can contain more than one font, although this is
+	extremely rare (and not allowed within OpenType fonts).
+
+	This class is the entry point for parsing a CFF table. To actually
+	manipulate the data inside the CFF font, you will want to access the
+	``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
+	object can either be treated as a dictionary (with appropriate
+	``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
+	objects, or as a list.
+
+	.. code:: python
+
+		from fontTools import ttLib
+		tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
+		tt["CFF "].cff
+		# <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
+		tt["CFF "].cff[0] # Here's your actual font data
+		# <fontTools.cffLib.TopDict object at 0x1020f1fd0>
+	
+	"""
 
 	def decompile(self, file, otFont, isCFF2=None):
+		"""Parse a binary CFF file into an internal representation. ``file``
+		should be a file handle object. ``otFont`` is the top-level
+		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
+
+		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
+		library makes an assertion that the CFF header is of the appropriate
+		version.
+		"""
+
 		self.otFont = otFont
 		sstruct.unpack(cffHeaderFormat, file.read(3), self)
 		if isCFF2 is not None:
@@ -79,7 +119,7 @@
 		"""
 		if hasattr(nameOrIndex, "__index__"):
 			index = nameOrIndex.__index__()
-		elif isinstance(nameOrIndex, basestring):
+		elif isinstance(nameOrIndex, str):
 			name = nameOrIndex
 			try:
 				index = self.fontNames.index(name)
@@ -90,6 +130,14 @@
 		return self.topDictIndex[index]
 
 	def compile(self, file, otFont, isCFF2=None):
+		"""Write the object back into binary representation onto the given file.
+		``file`` should be a file handle object. ``otFont`` is the top-level
+		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
+
+		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
+		library makes an assertion that the CFF header is of the appropriate
+		version.
+		"""
 		self.otFont = otFont
 		if isCFF2 is not None:
 			# called from ttLib: assert 'major' value matches expected version
@@ -144,7 +192,17 @@
 
 		writer.toFile(file)
 
-	def toXML(self, xmlWriter, progress=None):
+	def toXML(self, xmlWriter):
+		"""Write the object into XML representation onto the given
+		:class:`fontTools.misc.xmlWriter.XMLWriter`.
+
+		.. code:: python
+
+			writer = xmlWriter.XMLWriter(sys.stdout)
+			tt["CFF "].cff.toXML(writer)
+
+		"""
+
 		xmlWriter.simpletag("major", value=self.major)
 		xmlWriter.newline()
 		xmlWriter.simpletag("minor", value=self.minor)
@@ -153,17 +211,18 @@
 			xmlWriter.begintag("CFFFont", name=tostr(fontName))
 			xmlWriter.newline()
 			font = self[fontName]
-			font.toXML(xmlWriter, progress)
+			font.toXML(xmlWriter)
 			xmlWriter.endtag("CFFFont")
 			xmlWriter.newline()
 		xmlWriter.newline()
 		xmlWriter.begintag("GlobalSubrs")
 		xmlWriter.newline()
-		self.GlobalSubrs.toXML(xmlWriter, progress)
+		self.GlobalSubrs.toXML(xmlWriter)
 		xmlWriter.endtag("GlobalSubrs")
 		xmlWriter.newline()
 
 	def fromXML(self, name, attrs, content, otFont=None):
+		"""Reads data from the XML element into the ``CFFFontSet`` object."""
 		self.otFont = otFont
 
 		# set defaults. These will be replaced if there are entries for them
@@ -203,16 +262,23 @@
 				self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
 			self.topDictIndex.append(topDict)
 			for element in content:
-				if isinstance(element, basestring):
+				if isinstance(element, str):
 					continue
 				name, attrs, content = element
 				topDict.fromXML(name, attrs, content)
+
+			if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
+				fdArray = topDict.FDArray
+				for fontDict in fdArray:
+					if hasattr(fontDict, "Private"):
+						fontDict.Private.vstore = topDict.VarStore
+
 		elif name == "GlobalSubrs":
 			subrCharStringClass = psCharStrings.T2CharString
 			if not hasattr(self, "GlobalSubrs"):
 				self.GlobalSubrs = GlobalSubrsIndex()
 			for element in content:
-				if isinstance(element, basestring):
+				if isinstance(element, str):
 					continue
 				name, attrs, content = element
 				subr = subrCharStringClass()
@@ -224,7 +290,11 @@
 			self.minor = int(attrs['value'])
 
 	def convertCFFToCFF2(self, otFont):
-		# This assumes a decompiled CFF table.
+		"""Converts this object from CFF format to CFF2 format. This conversion
+		is done 'in-place'. The conversion cannot be reversed.
+
+		This assumes a decompiled CFF table. (i.e. that the object has been
+		filled via :meth:`decompile`.)"""
 		self.major = 2
 		cff2GetGlyphOrder = self.otFont.getGlyphOrder
 		topDictData = TopDictIndex(None, cff2GetGlyphOrder, None)
@@ -244,7 +314,7 @@
 				if key in topDict.rawDict:
 					del topDict.rawDict[key]
 				if hasattr(topDict, key):
-					exec("del topDict.%s" % (key))
+					delattr(topDict, key)
 
 		if not hasattr(topDict, "FDArray"):
 			fdArray = topDict.FDArray = FDArrayIndex()
@@ -257,6 +327,7 @@
 			else:
 				charStrings.fdArray = fdArray
 			fontDict = FontDict()
+			fontDict.setCFF2(True)
 			fdArray.append(fontDict)
 			fontDict.Private = privateDict
 			privateOpOrder = buildOrder(privateDictOperators2)
@@ -267,12 +338,20 @@
 						# print "Removing private dict", key
 						del privateDict.rawDict[key]
 					if hasattr(privateDict, key):
-						exec("del privateDict.%s" % (key))
+						delattr(privateDict, key)
 						# print "Removing privateDict attr", key
 		else:
 			# clean up the PrivateDicts in the fdArray
+			fdArray = topDict.FDArray
 			privateOpOrder = buildOrder(privateDictOperators2)
 			for fontDict in fdArray:
+				fontDict.setCFF2(True)
+				for key in fontDict.rawDict.keys():
+					if key not in fontDict.order:
+						del fontDict.rawDict[key]
+						if hasattr(fontDict, key):
+							delattr(fontDict, key)
+
 				privateDict = fontDict.Private
 				for entry in privateDictOperators:
 					key = entry[1]
@@ -281,7 +360,7 @@
 							# print "Removing private dict", key
 							del privateDict.rawDict[key]
 						if hasattr(privateDict, key):
-							exec("del privateDict.%s" % (key))
+							delattr(privateDict, key)
 							# print "Removing privateDict attr", key
 		# At this point, the Subrs and Charstrings are all still T2Charstring class
 		# easiest to fix this by compiling, then decompiling again
@@ -292,7 +371,8 @@
 
 
 class CFFWriter(object):
-
+	"""Helper class for serializing CFF data to binary. Used by
+	:meth:`CFFFontSet.compile`."""
 	def __init__(self, isCFF2):
 		self.data = []
 		self.isCFF2 = isCFF2
@@ -352,6 +432,8 @@
 
 
 class IndexCompiler(object):
+	"""Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
+	to binary."""
 
 	def __init__(self, items, strings, parent, isCFF2=None):
 		if isCFF2 is None and hasattr(parent, "isCFF2"):
@@ -431,6 +513,7 @@
 
 
 class TopDictIndexCompiler(IndexCompiler):
+	"""Helper class for writing the TopDict to binary."""
 
 	def getItems(self, items, strings):
 		out = []
@@ -466,6 +549,9 @@
 
 
 class FDArrayIndexCompiler(IndexCompiler):
+	"""Helper class for writing the
+	`Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
+	to binary."""
 
 	def getItems(self, items, strings):
 		out = []
@@ -504,6 +590,8 @@
 
 
 class GlobalSubrsCompiler(IndexCompiler):
+	"""Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+	to binary."""
 
 	def getItems(self, items, strings):
 		out = []
@@ -514,14 +602,17 @@
 
 
 class SubrsCompiler(GlobalSubrsCompiler):
-
+	"""Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+	to binary."""
+	
 	def setPos(self, pos, endPos):
 		offset = pos - self.parent.pos
 		self.parent.rawDict["Subrs"] = offset
 
 
 class CharStringsCompiler(GlobalSubrsCompiler):
-
+	"""Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
+	to binary."""
 	def getItems(self, items, strings):
 		out = []
 		for cs in items:
@@ -534,8 +625,9 @@
 
 
 class Index(object):
-
-	"""This class represents what the CFF spec calls an INDEX."""
+	"""This class represents what the CFF spec calls an INDEX (an array of
+	variable-sized objects). `Index` items can be addressed and set using
+	Python list indexing."""
 
 	compilerClass = IndexCompiler
 
@@ -593,13 +685,50 @@
 		return data
 
 	def append(self, item):
+		"""Add an item to an INDEX."""
 		self.items.append(item)
 
 	def getCompiler(self, strings, parent, isCFF2=None):
 		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
 
+	def clear(self):
+		"""Empty the INDEX."""
+		del self.items[:]
+
 
 class GlobalSubrsIndex(Index):
+	"""This index contains all the global subroutines in the font. A global
+	subroutine is a set of ``CharString`` data which is accessible to any
+	glyph in the font, and are used to store repeated instructions - for
+	example, components may be encoded as global subroutines, but so could
+	hinting instructions.
+
+	Remember that when interpreting a ``callgsubr`` instruction (or indeed
+	a ``callsubr`` instruction) that you will need to add the "subroutine
+	number bias" to number given:
+
+	.. code:: python
+
+		tt = ttLib.TTFont("Almendra-Bold.otf")
+		u = tt["CFF "].cff[0].CharStrings["udieresis"]
+		u.decompile()
+
+		u.toXML(XMLWriter(sys.stdout))
+		# <some stuff>
+		# -64 callgsubr <-- Subroutine which implements the dieresis mark
+		# <other stuff>
+
+		tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
+		# <T2CharString (bytecode) at 103451d10>
+
+		tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
+		# <T2CharString (source) at 103451390>
+
+	("The bias applied depends on the number of subrs (gsubrs). If the number of
+	subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
+	than 33900, it is 1131; otherwise it is 32768.",
+	`Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
+	"""
 
 	compilerClass = GlobalSubrsCompiler
 	subrClass = psCharStrings.T2CharString
@@ -614,11 +743,6 @@
 			self.fdSelect = fdSelect
 		if fdArray:
 			self.fdArray = fdArray
-		if isCFF2:
-			# CFF2Subr's can have numeric arguments on the stack after the last operator.
-			self.subrClass = psCharStrings.CFF2Subr
-			self.charStringClass = psCharStrings.CFF2Subr
-			
 
 	def produceItem(self, index, data, file, offset):
 		if self.private is not None:
@@ -633,7 +757,16 @@
 			private = None
 		return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
+		"""Write the subroutines index into XML representation onto the given
+		:class:`fontTools.misc.xmlWriter.XMLWriter`.
+
+		.. code:: python
+
+			writer = xmlWriter.XMLWriter(sys.stdout)
+			tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
+
+		"""
 		xmlWriter.comment(
 			"The 'index' attribute is only for humans; "
 			"it is ignored when parsed.")
@@ -664,10 +797,26 @@
 
 
 class SubrsIndex(GlobalSubrsIndex):
+	"""This index contains a glyph's local subroutines. A local subroutine is a
+	private set of ``CharString`` data which is accessible only to the glyph to
+	which the index is attached."""
+
 	compilerClass = SubrsCompiler
 
 
 class TopDictIndex(Index):
+	"""This index represents the array of ``TopDict`` structures in the font
+	(again, usually only one entry is present). Hence the following calls are
+	equivalent:
+
+	.. code:: python
+
+		tt["CFF "].cff[0]
+		# <fontTools.cffLib.TopDict object at 0x102ed6e50>
+		tt["CFF "].cff.topDictIndex[0]
+		# <fontTools.cffLib.TopDict object at 0x102ed6e50>
+
+	"""
 
 	compilerClass = TopDictIndexCompiler
 
@@ -698,11 +847,11 @@
 		top.decompile(data)
 		return top
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
 		for i in range(len(self)):
 			xmlWriter.begintag("FontDict", index=i)
 			xmlWriter.newline()
-			self[i].toXML(xmlWriter, progress)
+			self[i].toXML(xmlWriter)
 			xmlWriter.endtag("FontDict")
 			xmlWriter.newline()
 
@@ -711,11 +860,11 @@
 
 	compilerClass = FDArrayIndexCompiler
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
 		for i in range(len(self)):
 			xmlWriter.begintag("FontDict", index=i)
 			xmlWriter.newline()
-			self[i].toXML(xmlWriter, progress)
+			self[i].toXML(xmlWriter)
 			xmlWriter.endtag("FontDict")
 			xmlWriter.newline()
 
@@ -731,7 +880,7 @@
 			return
 		fontDict = FontDict()
 		for element in content:
-			if isinstance(element, basestring):
+			if isinstance(element, str):
 				continue
 			name, attrs, content = element
 			fontDict.fromXML(name, attrs, content)
@@ -817,6 +966,23 @@
 					for glyphID in range(prev, first):
 						gidArray[glyphID] = fd
 				self.gidArray = gidArray
+			elif self.format == 4:
+				gidArray = [None] * numGlyphs
+				nRanges = readCard32(file)
+				fd = None
+				prev = None
+				for i in range(nRanges):
+					first = readCard32(file)
+					if prev is not None:
+						for glyphID in range(prev, first):
+							gidArray[glyphID] = fd
+					prev = first
+					fd = readCard16(file)
+				if prev is not None:
+					first = readCard32(file)
+					for glyphID in range(prev, first):
+						gidArray[glyphID] = fd
+				self.gidArray = gidArray
 			else:
 				assert False, "unsupported FDSelect format: %s" % format
 		else:
@@ -839,6 +1005,20 @@
 
 
 class CharStrings(object):
+	"""The ``CharStrings`` in the font represent the instructions for drawing 
+	each glyph. This object presents a dictionary interface to the font's
+	CharStrings, indexed by glyph name:
+
+	.. code:: python
+	
+		tt["CFF "].cff[0].CharStrings["a"]
+		# <T2CharString (bytecode) at 103451e90>
+
+	See :class:`fontTools.misc.psCharStrings.T1CharString` and
+	:class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
+	compile and interpret the glyph drawing instructions in the returned objects.
+
+	"""
 
 	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
 			isCFF2=None):
@@ -906,11 +1086,8 @@
 				sel = None
 			return self.charStrings[name], sel
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
 		names = sorted(self.keys())
-		i = 0
-		step = 10
-		numGlyphs = len(names)
 		for name in names:
 			charStr, fdSelectIndex = self.getItemAndSelector(name)
 			if charStr.needsDecompilation():
@@ -927,14 +1104,10 @@
 			charStr.toXML(xmlWriter)
 			xmlWriter.endtag("CharString")
 			xmlWriter.newline()
-			if not i % step and progress is not None:
-				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
-				progress.increment(step / numGlyphs)
-			i = i + 1
 
 	def fromXML(self, name, attrs, content):
 		for element in content:
-			if isinstance(element, basestring):
+			if isinstance(element, str):
 				continue
 			name, attrs, content = element
 			if name != "CharString":
@@ -994,6 +1167,10 @@
 	return struct.pack(">H", value)
 
 
+def packCard32(value):
+	return struct.pack(">L", value)
+
+
 def buildOperatorDict(table):
 	d = {}
 	for op, name, arg, default, conv in table:
@@ -1037,12 +1214,22 @@
 class SimpleConverter(object):
 
 	def read(self, parent, value):
+		if not hasattr(parent, "file"):
+			return self._read(parent, value)
+		file = parent.file
+		pos = file.tell()
+		try:
+			return self._read(parent, value)
+		finally:
+			file.seek(pos)
+
+	def _read(self, parent, value):
 		return value
 
 	def write(self, parent, value):
 		return value
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		xmlWriter.simpletag(name, value=value)
 		xmlWriter.newline()
 
@@ -1052,14 +1239,14 @@
 
 class ASCIIConverter(SimpleConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		return tostr(value, encoding='ascii')
 
 	def write(self, parent, value):
 		return tobytes(value, encoding='ascii')
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
-		xmlWriter.simpletag(name, value=tounicode(value, encoding="ascii"))
+	def xmlWrite(self, xmlWriter, name, value):
+		xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
 		xmlWriter.newline()
 
 	def xmlRead(self, name, attrs, content, parent):
@@ -1068,14 +1255,14 @@
 
 class Latin1Converter(SimpleConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		return tostr(value, encoding='latin1')
 
 	def write(self, parent, value):
 		return tobytes(value, encoding='latin1')
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
-		value = tounicode(value, encoding="latin1")
+	def xmlWrite(self, xmlWriter, name, value):
+		value = tostr(value, encoding="latin1")
 		if name in ['Notice', 'Copyright']:
 			value = re.sub(r"[\r\n]\s+", " ", value)
 		xmlWriter.simpletag(name, value=value)
@@ -1096,7 +1283,7 @@
 def parseBlendList(s):
 	valueList = []
 	for element in s:
-		if isinstance(element, basestring):
+		if isinstance(element, str):
 			continue
 		name, attrs, content = element
 		blendList = attrs["value"].split()
@@ -1108,7 +1295,7 @@
 
 
 class NumberConverter(SimpleConverter):
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		if isinstance(value, list):
 			xmlWriter.begintag(name)
 			xmlWriter.newline()
@@ -1133,7 +1320,7 @@
 
 
 class ArrayConverter(SimpleConverter):
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		if value and isinstance(value[0], list):
 			xmlWriter.begintag(name)
 			xmlWriter.newline()
@@ -1162,17 +1349,17 @@
 
 class TableConverter(SimpleConverter):
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		xmlWriter.begintag(name)
 		xmlWriter.newline()
-		value.toXML(xmlWriter, progress)
+		value.toXML(xmlWriter)
 		xmlWriter.endtag(name)
 		xmlWriter.newline()
 
 	def xmlRead(self, name, attrs, content, parent):
 		ob = self.getClass()()
 		for element in content:
-			if isinstance(element, basestring):
+			if isinstance(element, str):
 				continue
 			name, attrs, content = element
 			ob.fromXML(name, attrs, content)
@@ -1184,7 +1371,7 @@
 	def getClass(self):
 		return PrivateDict
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		size, offset = value
 		file = parent.file
 		isCFF2 = parent._isCFF2
@@ -1209,7 +1396,7 @@
 	def getClass(self):
 		return SubrsIndex
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		file = parent.file
 		isCFF2 = parent._isCFF2
 		file.seek(parent.offset + value)  # Offset(self)
@@ -1221,7 +1408,7 @@
 
 class CharStringsConverter(TableConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		file = parent.file
 		isCFF2 = parent._isCFF2
 		charset = parent.charset
@@ -1265,8 +1452,8 @@
 		return charStrings
 
 
-class CharsetConverter(object):
-	def read(self, parent, value):
+class CharsetConverter(SimpleConverter):
+	def _read(self, parent, value):
 		isCID = hasattr(parent, "ROS")
 		if value > 2:
 			numGlyphs = parent.numGlyphs
@@ -1282,6 +1469,20 @@
 				raise NotImplementedError
 			assert len(charset) == numGlyphs
 			log.log(DEBUG, "    charset end at %s", file.tell())
+			# make sure glyph names are unique
+			allNames = {}
+			newCharset = []
+			for glyphName in charset:
+				if glyphName in allNames:
+					# make up a new glyphName that's unique
+					n = allNames[glyphName]
+					while (glyphName + "#" + str(n)) in allNames:
+						n += 1
+					allNames[glyphName] = n + 1
+					glyphName = glyphName + "#" + str(n)
+				allNames[glyphName] = 1
+				newCharset.append(glyphName)
+			charset = newCharset
 		else:  # offset == 0 -> no charset data.
 			if isCID or "CharStrings" not in parent.rawDict:
 				# We get here only when processing fontDicts from the FDArray of
@@ -1301,7 +1502,7 @@
 	def write(self, parent, value):
 		return 0  # dummy value
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		# XXX only write charset when not in OT/TTX context, where we
 		# dump charset as a separate "GlyphOrder" table.
 		# # xmlWriter.simpletag("charset")
@@ -1450,7 +1651,7 @@
 class EncodingCompiler(object):
 
 	def __init__(self, strings, encoding, parent):
-		assert not isinstance(encoding, basestring)
+		assert not isinstance(encoding, str)
 		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
 		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
 		if len(data0) < len(data1):
@@ -1471,7 +1672,7 @@
 
 class EncodingConverter(SimpleConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		if value == 0:
 			return "StandardEncoding"
 		elif value == 1:
@@ -1501,7 +1702,7 @@
 			return 1
 		return 0  # dummy value
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		if value in ("StandardEncoding", "ExpertEncoding"):
 			xmlWriter.simpletag(name, name=value)
 			xmlWriter.newline()
@@ -1521,7 +1722,7 @@
 			return attrs["name"]
 		encoding = [".notdef"] * 256
 		for element in content:
-			if isinstance(element, basestring):
+			if isinstance(element, str):
 				continue
 			name, attrs, content = element
 			code = safeEval(attrs["code"])
@@ -1613,7 +1814,7 @@
 
 class FDArrayConverter(TableConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		try:
 			vstore = parent.VarStore
 		except AttributeError:
@@ -1633,16 +1834,16 @@
 	def xmlRead(self, name, attrs, content, parent):
 		fdArray = FDArrayIndex()
 		for element in content:
-			if isinstance(element, basestring):
+			if isinstance(element, str):
 				continue
 			name, attrs, content = element
 			fdArray.fromXML(name, attrs, content)
 		return fdArray
 
 
-class FDSelectConverter(object):
+class FDSelectConverter(SimpleConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		file = parent.file
 		file.seek(value)
 		fdSelect = FDSelect(file, parent.numGlyphs)
@@ -1653,7 +1854,7 @@
 
 	# The FDSelect glyph data is written out to XML in the charstring keys,
 	# so we write out only the format selector
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		xmlWriter.simpletag(name, [('format', value.format)])
 		xmlWriter.newline()
 
@@ -1667,7 +1868,7 @@
 
 class VarStoreConverter(SimpleConverter):
 
-	def read(self, parent, value):
+	def _read(self, parent, value):
 		file = parent.file
 		file.seek(value)
 		varStore = VarStoreData(file)
@@ -1677,7 +1878,7 @@
 	def write(self, parent, value):
 		return 0  # dummy value
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		value.writeXML(xmlWriter, name)
 
 	def xmlRead(self, name, attrs, content, parent):
@@ -1715,6 +1916,27 @@
 	return bytesjoin(data)
 
 
+def packFDSelect4(fdSelectArray):
+	fmt = 4
+	fdRanges = []
+	lenArray = len(fdSelectArray)
+	lastFDIndex = -1
+	for i in range(lenArray):
+		fdIndex = fdSelectArray[i]
+		if lastFDIndex != fdIndex:
+			fdRanges.append([i, fdIndex])
+			lastFDIndex = fdIndex
+	sentinelGID = i + 1
+
+	data = [packCard8(fmt)]
+	data.append(packCard32(len(fdRanges)))
+	for fdRange in fdRanges:
+		data.append(packCard32(fdRange[0]))
+		data.append(packCard16(fdRange[1]))
+	data.append(packCard32(sentinelGID))
+	return bytesjoin(data)
+
+
 class FDSelectCompiler(object):
 
 	def __init__(self, fdSelect, parent):
@@ -1724,6 +1946,8 @@
 			self.data = packFDSelect0(fdSelectArray)
 		elif fmt == 3:
 			self.data = packFDSelect3(fdSelectArray)
+		elif fmt == 4:
+			self.data = packFDSelect4(fdSelectArray)
 		else:
 			# choose smaller of the two formats
 			data0 = packFDSelect0(fdSelectArray)
@@ -1771,7 +1995,7 @@
 
 class ROSConverter(SimpleConverter):
 
-	def xmlWrite(self, xmlWriter, name, value, progress):
+	def xmlWrite(self, xmlWriter, name, value):
 		registry, order, supplement = value
 		xmlWriter.simpletag(
 			name,
@@ -1882,6 +2106,8 @@
 	(11,		'StdVW',		'number',	None,	None),
 	((12, 12),	'StemSnapH',		'delta',	None,	None),
 	((12, 13),	'StemSnapV',		'delta',	None,	None),
+	((12, 17),	'LanguageGroup',	'number',	0,	None),
+	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
 	(19,		'Subrs',		'number',	None,	SubrsConverter()),
 ]
 
@@ -2013,27 +2239,33 @@
 
 
 	def arg_delta_blend(self, value):
-		""" A delta list with blend lists has to be *all* blend lists.
-		The value is a list is arranged as follows.
-		[
-		   [V0, d0..dn] 
-		   [V1, d0..dn]
-		   ...
-		   [Vm, d0..dn]
-		]
-		V is the absolute coordinate value from the default font, and d0-dn are
-		the delta values from the n regions. Each V is an absolute coordinate
-		from the default font.
-		We want to return a list:
-		[
-		   [v0, v1..vm] 
-		   [d0..dn]
-		   ...
-		   [d0..dn]
-		   numBlends
-		   blendOp
-		]
-		where each v is relative to the previous default font value.
+		"""A delta list with blend lists has to be *all* blend lists.
+
+		The value is a list is arranged as follows::
+
+			[
+				[V0, d0..dn]
+				[V1, d0..dn]
+				...
+				[Vm, d0..dn]
+			]
+
+		``V`` is the absolute coordinate value from the default font, and ``d0-dn``
+		are the delta values from the *n* regions. Each ``V`` is an absolute
+		coordinate from the default font.
+
+		We want to return a list::
+
+			[
+				[v0, v1..vm]
+				[d0..dn]
+				...
+				[d0..dn]
+				numBlends
+				blendOp
+			]
+
+		where each ``v`` is relative to the previous default font value.
 		"""
 		numMasters = len(value[0])
 		numBlends = len(value)
@@ -2103,7 +2335,7 @@
 					self.rawDict["charset"] = charsetCode
 			if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
 				encoding = self.dictObj.Encoding
-				if not isinstance(encoding, basestring):
+				if not isinstance(encoding, str):
 					children.append(EncodingCompiler(strings, encoding, self))
 		else:
 			if hasattr(self.dictObj, "VarStore"):
@@ -2235,6 +2467,12 @@
 		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
 
 	def __getattr__(self, name):
+		if name[:2] == name[-2:] == "__":
+			# to make deepcopy() and pickle.load() work, we need to signal with
+			# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
+			# aren't implemented. For more details, see:
+			# https://github.com/fonttools/fonttools/pull/1488
+			raise AttributeError(name)
 		value = self.rawDict.get(name, None)
 		if value is None:
 			value = self.defaults.get(name)
@@ -2245,7 +2483,7 @@
 		setattr(self, name, value)
 		return value
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
 		for name in self.order:
 			if name in self.skipNames:
 				continue
@@ -2262,7 +2500,7 @@
 			if value is None and name != "charset":
 				continue
 			conv = self.converters[name]
-			conv.xmlWrite(xmlWriter, name, value, progress)
+			conv.xmlWrite(xmlWriter, name, value)
 		ignoredNames = set(self.rawDict) - set(self.order)
 		if ignoredNames:
 			xmlWriter.comment(
@@ -2276,6 +2514,30 @@
 
 
 class TopDict(BaseDict):
+	"""The ``TopDict`` represents the top-level dictionary holding font
+	information. CFF2 tables contain a restricted set of top-level entries
+	as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
+	but CFF tables may contain a wider range of information. This information
+	can be accessed through attributes or through the dictionary returned
+	through the ``rawDict`` property:
+
+	.. code:: python
+
+		font = tt["CFF "].cff[0]
+		font.FamilyName
+		# 'Linux Libertine O'
+		font.rawDict["FamilyName"]
+		# 'Linux Libertine O'
+
+	More information is available in the CFF file's private dictionary, accessed
+	via the ``Private`` property:
+
+	.. code:: python
+
+		tt["CFF "].cff[0].Private.BlueValues
+		# [-15, 0, 515, 515, 666, 666]
+	
+	"""
 
 	defaults = buildDefaults(topDictOperators)
 	converters = buildConverters(topDictOperators)
@@ -2297,6 +2559,7 @@
 			self.order = buildOrder(topDictOperators)
 
 	def getGlyphOrder(self):
+		"""Returns a list of glyph names in the CFF font."""
 		return self.charset
 
 	def postDecompile(self):
@@ -2310,9 +2573,9 @@
 		else:
 			self.numGlyphs = readCard16(self.file)
 
-	def toXML(self, xmlWriter, progress):
+	def toXML(self, xmlWriter):
 		if hasattr(self, "CharStrings"):
-			self.decompileAllCharStrings(progress)
+			self.decompileAllCharStrings()
 		if hasattr(self, "ROS"):
 			self.skipNames = ['Encoding']
 		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
@@ -2320,25 +2583,21 @@
 			# in CID fonts.
 			self.skipNames = [
 				'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount']
-		BaseDict.toXML(self, xmlWriter, progress)
+		BaseDict.toXML(self, xmlWriter)
 
-	def decompileAllCharStrings(self, progress):
+	def decompileAllCharStrings(self):
 		# Make sure that all the Private Dicts have been instantiated.
-		i = 0
-		for charString in self.CharStrings.values():
+		for i, charString in enumerate(self.CharStrings.values()):
 			try:
 				charString.decompile()
 			except:
 				log.error("Error in charstring %s", i)
 				raise
-			if not i % 30 and progress:
-				progress.increment(0)  # update
-			i = i + 1
 
 	def recalcFontBBox(self):
 		fontBBox = None
 		for charString in self.CharStrings.values():
-			bounds = charString.calcBounds()
+			bounds = charString.calcBounds(self.CharStrings)
 			if bounds is not None:
 				if fontBBox is not None:
 					fontBBox = unionRect(fontBBox, bounds)
@@ -2382,13 +2641,24 @@
 	defaults = {}
 	converters = buildConverters(topDictOperators)
 	compilerClass = FontDictCompiler
-	order = ['FontName', 'FontMatrix', 'Weight', 'Private']
+	orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private']
+	orderCFF2 = ['Private']
 	decompilerClass = TopDictDecompiler
 
 	def __init__(self, strings=None, file=None, offset=None,
 			GlobalSubrs=None, isCFF2=None, vstore=None):
 		super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
 		self.vstore = vstore
+		self.setCFF2(isCFF2)
+
+	def setCFF2(self, isCFF2):
+		# isCFF2 may be None.
+		if isCFF2:
+			self.order = self.orderCFF2
+			self._isCFF2 = True
+		else:
+			self.order = self.orderCFF
+			self._isCFF2 = False
 
 
 class PrivateDict(BaseDict):
@@ -2405,10 +2675,17 @@
 		if isCFF2:
 			self.defaults = buildDefaults(privateDictOperators2)
 			self.order = buildOrder(privateDictOperators2)
+			# Provide dummy values. This avoids needing to provide
+			# an isCFF2 state in a lot of places.
+			self.nominalWidthX = self.defaultWidthX = None
 		else:
 			self.defaults = buildDefaults(privateDictOperators)
 			self.order = buildOrder(privateDictOperators)
 
+	@property
+	def in_cff2(self):
+		return self._isCFF2
+
 	def getNumRegions(self, vi=None):  # called from misc/psCharStrings.py
 		# if getNumRegions is being called, we can assume that VarStore exists.
 		if vi is None:
diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py
index b1244bc..fbfefa9 100644
--- a/Lib/fontTools/cffLib/specializer.py
+++ b/Lib/fontTools/cffLib/specializer.py
@@ -1,13 +1,23 @@
 # -*- coding: utf-8 -*-
 
-"""T2CharString operator specializer and generalizer."""
+"""T2CharString operator specializer and generalizer.
 
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
+PostScript glyph drawing operations can be expressed in multiple different
+ways. For example, as well as the ``lineto`` operator, there is also a
+``hlineto`` operator which draws a horizontal line, removing the need to
+specify a ``dx`` coordinate, and a ``vlineto`` operator which draws a
+vertical line, removing the need to specify a ``dy`` coordinate. As well
+as decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects
+into lists of operations, this module allows for conversion between general
+and specific forms of the operation.
+
+"""
+
+from fontTools.cffLib import maxStackLimit
 
 
 def stringToProgram(string):
-	if isinstance(string, basestring):
+	if isinstance(string, str):
 		string = string.split()
 	program = []
 	for token in string:
@@ -21,32 +31,80 @@
 		program.append(token)
 	return program
 
+
 def programToString(program):
 	return ' '.join(str(x) for x in program)
 
 
-def programToCommands(program):
+def programToCommands(program, getNumRegions=None):
 	"""Takes a T2CharString program list and returns list of commands.
 	Each command is a two-tuple of commandname,arg-list.  The commandname might
 	be empty string if no commandname shall be emitted (used for glyph width,
 	hintmask/cntrmask argument, as well as stray arguments at the end of the
-	program (¯\_(ツ)_/¯)."""
+	program (¯\_(ツ)_/¯).
+	'getNumRegions' may be None, or a callable object. It must return the
+	number of regions. 'getNumRegions' takes a single argument, vsindex. If
+	the vsindex argument is None, getNumRegions returns the default number
+	of regions for the charstring, else it returns the numRegions for
+	the vsindex.
+	The Charstring may or may not start with a width value. If the first
+	non-blend operator has an odd number of arguments, then the first argument is
+	a width, and is popped off. This is complicated with blend operators, as
+	there may be more than one before the first hint or moveto operator, and each
+	one reduces several arguments to just one list argument. We have to sum the
+	number of arguments that are not part of the blend arguments, and all the
+	'numBlends' values. We could instead have said that by definition, if there
+	is a blend operator, there is no width value, since CFF2 Charstrings don't
+	have width values. I discussed this with Behdad, and we are allowing for an
+	initial width value in this case because developers may assemble a CFF2
+	charstring from CFF Charstrings, which could have width values.
+	"""
 
-	width = None
+	seenWidthOp = False
+	vsIndex = None
+	lenBlendStack = 0
+	lastBlendIndex = 0
 	commands = []
 	stack = []
 	it = iter(program)
+
 	for token in it:
-		if not isinstance(token, basestring):
+		if not isinstance(token, str):
 			stack.append(token)
 			continue
 
-		if width is None and token in {'hstem', 'hstemhm', 'vstem', 'vstemhm',
-					       'cntrmask', 'hintmask',
-					       'hmoveto', 'vmoveto', 'rmoveto',
-					       'endchar'}:
+		if token == 'blend':
+			assert getNumRegions is not None
+			numSourceFonts = 1 + getNumRegions(vsIndex)
+			# replace the blend op args on the stack with a single list
+			# containing all the blend op args.
+			numBlends = stack[-1]
+			numBlendArgs = numBlends * numSourceFonts + 1
+			# replace first blend op by a list of the blend ops.
+			stack[-numBlendArgs:] = [stack[-numBlendArgs:]]
+			lenBlendStack += numBlends + len(stack) - 1
+			lastBlendIndex = len(stack)
+			# if a blend op exists, this is or will be a CFF2 charstring.
+			continue
+
+		elif token == 'vsindex':
+			vsIndex = stack[-1]
+			assert type(vsIndex) is int
+
+		elif (not seenWidthOp) and token in {'hstem', 'hstemhm', 'vstem', 'vstemhm',
+			'cntrmask', 'hintmask',
+			'hmoveto', 'vmoveto', 'rmoveto',
+			'endchar'}:
+			seenWidthOp = True
 			parity = token in {'hmoveto', 'vmoveto'}
-			if stack and (len(stack) % 2) ^ parity:
+			if lenBlendStack:
+				# lenBlendStack has the number of args represented by the last blend
+				# arg and all the preceding args. We need to now add the number of
+				# args following the last blend arg.
+				numArgs = lenBlendStack + len(stack[lastBlendIndex:])
+			else:
+				numArgs = len(stack)
+			if numArgs and (numArgs % 2) ^ parity:
 				width = stack.pop(0)
 				commands.append(('', [width]))
 
@@ -56,17 +114,30 @@
 			commands.append((token, []))
 			commands.append(('', [next(it)]))
 		else:
-			commands.append((token,stack))
+			commands.append((token, stack))
 		stack = []
 	if stack:
 		commands.append(('', stack))
 	return commands
 
+
+def _flattenBlendArgs(args):
+	token_list = []
+	for arg in args:
+		if isinstance(arg, list):
+			token_list.extend(arg)
+			token_list.append('blend')
+		else:
+			token_list.append(arg)
+	return token_list
+
 def commandsToProgram(commands):
 	"""Takes a commands list as returned by programToCommands() and converts
 	it back to a T2CharString program list."""
 	program = []
 	for op,args in commands:
+		if any(isinstance(arg, list) for arg in args):
+			args = _flattenBlendArgs(args)
 		program.extend(args)
 		if op:
 			program.append(op)
@@ -201,11 +272,58 @@
 			yield ('rlineto', args)
 		yield ('rrcurveto', last_args)
 
+def _convertBlendOpToArgs(blendList):
+	# args is list of blend op args. Since we are supporting
+	# recursive blend op calls, some of these args may also
+	# be a list of blend op args, and need to be converted before
+	# we convert the current list.
+	if any([isinstance(arg, list) for arg in blendList]):
+		args =  [i for e in blendList for i in 
+					(_convertBlendOpToArgs(e) if isinstance(e,list) else [e]) ]
+	else:
+		args = blendList
+
+	# We now know that blendList contains a blend op argument list, even if
+	# some of the args are lists that each contain a blend op argument list.
+	# 	Convert from:
+	# 		[default font arg sequence x0,...,xn] + [delta tuple for x0] + ... + [delta tuple for xn]
+	# 	to:
+	# 		[ [x0] + [delta tuple for x0],
+	#                 ...,
+	#          [xn] + [delta tuple for xn] ]
+	numBlends = args[-1]
+	# Can't use args.pop() when the args are being used in a nested list
+	# comprehension. See calling context
+	args = args[:-1]
+
+	numRegions = len(args)//numBlends - 1
+	if not (numBlends*(numRegions + 1) == len(args)):
+		raise ValueError(blendList)
+
+	defaultArgs = [[arg] for arg in args[:numBlends]]
+	deltaArgs = args[numBlends:]
+	numDeltaValues = len(deltaArgs)
+	deltaList = [ deltaArgs[i:i + numRegions] for i in range(0, numDeltaValues, numRegions) ]
+	blend_args = [ a + b for a, b in zip(defaultArgs,deltaList)]
+	return blend_args
 
 def generalizeCommands(commands, ignoreErrors=False):
 	result = []
 	mapping = _GeneralizerDecombinerCommandsMap
-	for op,args in commands:
+	for op, args in commands:
+		# First, generalize any blend args in the arg list.
+		if any([isinstance(arg, list) for arg in args]):
+			try:
+				args = [n for arg in args for n in (_convertBlendOpToArgs(arg) if isinstance(arg, list) else [arg])]
+			except ValueError:
+				if ignoreErrors:
+					# Store op as data, such that consumers of commands do not have to
+					# deal with incorrect number of arguments.
+					result.append(('', args))
+					result.append(('', [op]))
+				else:
+					raise
+
 		func = getattr(mapping, op, None)
 		if not func:
 			result.append((op,args))
@@ -223,8 +341,8 @@
 				raise
 	return result
 
-def generalizeProgram(program, **kwargs):
-	return commandsToProgram(generalizeCommands(programToCommands(program), **kwargs))
+def generalizeProgram(program, getNumRegions=None, **kwargs):
+	return commandsToProgram(generalizeCommands(programToCommands(program, getNumRegions), **kwargs))
 
 
 def _categorizeVector(v):
@@ -265,6 +383,70 @@
 	assert a in '0r'
 	return a
 
+def _convertToBlendCmds(args):
+	# return a list of blend commands, and
+	# the remaining non-blended args, if any.
+	num_args = len(args)
+	stack_use = 0
+	new_args = []
+	i = 0
+	while i < num_args:
+		arg = args[i]
+		if not isinstance(arg, list):
+			new_args.append(arg)
+			i += 1
+			stack_use += 1
+		else:
+			prev_stack_use = stack_use
+			# The arg is a tuple of blend values.
+			# These are each (master 0,delta 1..delta n)
+			# Combine as many successive tuples as we can,
+			# up to the max stack limit.
+			num_sources = len(arg)
+			blendlist = [arg]
+			i += 1
+			stack_use += 1 + num_sources  # 1 for the num_blends arg
+			while (i < num_args) and isinstance(args[i], list):
+				blendlist.append(args[i])
+				i += 1
+				stack_use += num_sources
+				if stack_use + num_sources > maxStackLimit:
+					# if we are here, max stack is the CFF2 max stack.
+					# I use the CFF2 max stack limit here rather than
+					# the 'maxstack' chosen by the client, as the default
+					#  maxstack may have been used unintentionally. For all
+					# the other operators, this just produces a little less
+					# optimization, but here it puts a hard (and low) limit
+					# on the number of source fonts that can be used.
+					break
+			# blendList now contains as many single blend tuples as can be
+			# combined without exceeding the CFF2 stack limit.
+			num_blends = len(blendlist)
+			# append the 'num_blends' default font values
+			blend_args = []
+			for arg in blendlist:
+				blend_args.append(arg[0])
+			for arg in blendlist:
+				blend_args.extend(arg[1:])
+			blend_args.append(num_blends)
+			new_args.append(blend_args)
+			stack_use = prev_stack_use + num_blends
+
+	return new_args
+
+def _addArgs(a, b):
+	if isinstance(b, list):
+		if isinstance(a, list):
+			if len(a) != len(b):
+				raise ValueError()
+			return [_addArgs(va, vb) for va,vb in zip(a, b)]
+		else:
+			a, b = b, a
+	if isinstance(a, list):
+		return [_addArgs(a[0], b)] + a[1:]
+	return a + b
+
+
 def specializeCommands(commands,
 		       ignoreErrors=False,
 		       generalizeFirst=True,
@@ -300,6 +482,8 @@
 	# I have convinced myself that this produces optimal bytecode (except for, possibly
 	# one byte each time maxstack size prohibits combining.)  YMMV, but you'd be wrong. :-)
 	# A dynamic-programming approach can do the same but would be significantly slower.
+	#
+	# 7. For any args which are blend lists, convert them to a blend command.
 
 
 	# 0. Generalize commands.
@@ -415,10 +599,18 @@
 				continue
 
 			# Merge adjacent hlineto's and vlineto's.
-			if i and op in {'hlineto', 'vlineto'} and op == commands[i-1][0]:
+			# In CFF2 charstrings from variable fonts, each
+			# arg item may be a list of blendable values, one from
+			# each source font.
+			if (i and op in {'hlineto', 'vlineto'} and
+							(op == commands[i-1][0])):
 				_, other_args = commands[i-1]
 				assert len(args) == 1 and len(other_args) == 1
-				commands[i-1] = (op, [other_args[0]+args[0]])
+				try:
+					new_args = [_addArgs(args[0], other_args[0])]
+				except ValueError:
+					continue
+				commands[i-1] = (op, new_args)
 				del commands[i]
 				continue
 
@@ -436,7 +628,6 @@
 
 		if op[2:] == 'curveto' and len(args) == 5 and prv == nxt == 'rrcurveto':
 			assert (op[0] == 'r') ^ (op[1] == 'r')
-			args = list(args)
 			if op[0] == 'v':
 				pos = 0
 			elif op[0] != 'r':
@@ -445,7 +636,8 @@
 				pos = 4
 			else:
 				pos = 5
-			args.insert(pos, 0)
+			# Insert, while maintaining the type of args (can be tuple or list).
+			args = args[:pos] + type(args)((0,)) + args[pos:]
 			commands[i] = ('rrcurveto', args)
 			continue
 
@@ -493,7 +685,9 @@
 				if d0 is None: continue
 				new_op = d0+d+'curveto'
 
-		if new_op and len(args1) + len(args2) <= maxstack:
+		# Make sure the stack depth does not exceed (maxstack - 1), so
+		# that subroutinizer can insert subroutine calls at any point.
+		if new_op and len(args1) + len(args2) < maxstack:
 			commands[i-1] = (new_op, args1+args2)
 			del commands[i]
 
@@ -528,10 +722,16 @@
 			commands[i] = op0+op1+'curveto', args
 			continue
 
+	# 7. For any series of args which are blend lists, convert the series to a single blend arg.
+	for i in range(len(commands)):
+		op, args = commands[i]
+		if any(isinstance(arg, list) for arg in args):
+			commands[i] = op, _convertToBlendCmds(args)
+
 	return commands
 
-def specializeProgram(program, **kwargs):
-	return commandsToProgram(specializeCommands(programToCommands(program), **kwargs))
+def specializeProgram(program, getNumRegions=None, **kwargs):
+	return commandsToProgram(specializeCommands(programToCommands(program, getNumRegions), **kwargs))
 
 
 if __name__ == '__main__':
@@ -548,4 +748,3 @@
 	assert program == program2
 	print("Generalized program:"); print(programToString(generalizeProgram(program)))
 	print("Specialized program:"); print(programToString(specializeProgram(program)))
-
diff --git a/Lib/fontTools/cffLib/width.py b/Lib/fontTools/cffLib/width.py
new file mode 100644
index 0000000..00b859b
--- /dev/null
+++ b/Lib/fontTools/cffLib/width.py
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+
+"""T2CharString glyph width optimizer.
+
+CFF glyphs whose width equals the CFF Private dictionary's ``defaultWidthX``
+value do not need to specify their width in their charstring, saving bytes.
+This module determines the optimum ``defaultWidthX`` and ``nominalWidthX``
+values for a font, when provided with a list of glyph widths."""
+
+from fontTools.ttLib import TTFont
+from collections import defaultdict
+from operator import add
+from functools import reduce
+
+
+class missingdict(dict):
+	def __init__(self, missing_func):
+		self.missing_func = missing_func
+	def __missing__(self, v):
+		return self.missing_func(v)
+
+def cumSum(f, op=add, start=0, decreasing=False):
+	
+	keys = sorted(f.keys())
+	minx, maxx = keys[0], keys[-1]
+
+	total = reduce(op, f.values(), start)
+
+	if decreasing:
+		missing = lambda x: start if x > maxx else total
+		domain = range(maxx, minx - 1, -1)
+	else:
+		missing = lambda x: start if x < minx else total
+		domain = range(minx, maxx + 1)
+
+	out = missingdict(missing)
+
+	v = start
+	for x in domain:
+		v = op(v, f[x])
+		out[x] = v
+
+	return out
+
+def byteCost(widths, default, nominal):
+
+	if not hasattr(widths, 'items'):
+		d = defaultdict(int)
+		for w in widths:
+			d[w] += 1
+		widths = d
+
+	cost = 0
+	for w,freq in widths.items():
+		if w == default: continue
+		diff = abs(w - nominal)
+		if diff <= 107:
+			cost += freq
+		elif diff <= 1131:
+			cost += freq * 2
+		else:
+			cost += freq * 5
+	return cost
+
+
+def optimizeWidthsBruteforce(widths):
+	"""Bruteforce version.  Veeeeeeeeeeeeeeeeery slow.  Only works for smallests of fonts."""
+
+	d = defaultdict(int)
+	for w in widths:
+		d[w] += 1
+
+	# Maximum number of bytes using default can possibly save
+	maxDefaultAdvantage = 5 * max(d.values())
+
+	minw, maxw = min(widths), max(widths)
+	domain = list(range(minw, maxw+1))
+
+	bestCostWithoutDefault = min(byteCost(widths, None, nominal) for nominal in domain)
+
+	bestCost = len(widths) * 5 + 1
+	for nominal in domain:
+		if byteCost(widths, None, nominal) > bestCost + maxDefaultAdvantage:
+			continue
+		for default in domain:
+			cost = byteCost(widths, default, nominal)
+			if cost < bestCost:
+				bestCost = cost
+				bestDefault = default
+				bestNominal = nominal
+
+	return bestDefault, bestNominal
+
+
+def optimizeWidths(widths):
+	"""Given a list of glyph widths, or dictionary mapping glyph width to number of
+	glyphs having that, returns a tuple of best CFF default and nominal glyph widths.
+
+	This algorithm is linear in UPEM+numGlyphs."""
+
+	if not hasattr(widths, 'items'):
+		d = defaultdict(int)
+		for w in widths:
+			d[w] += 1
+		widths = d
+	
+	keys = sorted(widths.keys())
+	minw, maxw = keys[0], keys[-1]
+	domain = list(range(minw, maxw+1))
+
+	# Cumulative sum/max forward/backward.
+	cumFrqU = cumSum(widths, op=add)
+	cumMaxU = cumSum(widths, op=max)
+	cumFrqD = cumSum(widths, op=add, decreasing=True)
+	cumMaxD = cumSum(widths, op=max, decreasing=True)
+
+	# Cost per nominal choice, without default consideration.
+	nomnCostU = missingdict(lambda x: cumFrqU[x] + cumFrqU[x-108] + cumFrqU[x-1132]*3)
+	nomnCostD = missingdict(lambda x: cumFrqD[x] + cumFrqD[x+108] + cumFrqD[x+1132]*3)
+	nomnCost  = missingdict(lambda x: nomnCostU[x] + nomnCostD[x] - widths[x])
+
+	# Cost-saving per nominal choice, by best default choice.
+	dfltCostU = missingdict(lambda x: max(cumMaxU[x], cumMaxU[x-108]*2, cumMaxU[x-1132]*5))
+	dfltCostD = missingdict(lambda x: max(cumMaxD[x], cumMaxD[x+108]*2, cumMaxD[x+1132]*5))
+	dfltCost  = missingdict(lambda x: max(dfltCostU[x], dfltCostD[x]))
+
+	# Combined cost per nominal choice.
+	bestCost  = missingdict(lambda x: nomnCost[x] - dfltCost[x])
+
+	# Best nominal.
+	nominal = min(domain, key=lambda x: bestCost[x])
+
+	# Work back the best default.
+	bestC = bestCost[nominal]
+	dfltC = nomnCost[nominal] - bestCost[nominal]
+	ends = []
+	if dfltC == dfltCostU[nominal]:
+		starts = [nominal, nominal-108, nominal-1131]
+		for start in starts:
+			while cumMaxU[start] and cumMaxU[start] == cumMaxU[start-1]:
+				start -= 1
+			ends.append(start)
+	else:
+		starts = [nominal, nominal+108, nominal+1131]
+		for start in starts:
+			while cumMaxD[start] and cumMaxD[start] == cumMaxD[start+1]:
+				start += 1
+			ends.append(start)
+	default = min(ends, key=lambda default: byteCost(widths, default, nominal))
+
+	return default, nominal
+
+def main(args=None):
+	"""Calculate optimum defaultWidthX/nominalWidthX values"""
+
+	import argparse
+	parser = argparse.ArgumentParser(
+		"fonttools cffLib.width",
+		description=main.__doc__,
+	)
+	parser.add_argument('inputs', metavar='FILE', type=str, nargs='+',
+		help="Input TTF files")
+	parser.add_argument('-b', '--brute-force', dest="brute", action="store_true",
+		help="Use brute-force approach (VERY slow)")
+
+	args = parser.parse_args(args)
+
+	for fontfile in args.inputs:
+		font = TTFont(fontfile)
+		hmtx = font['hmtx']
+		widths = [m[0] for m in hmtx.metrics.values()]
+		if args.brute:
+			default, nominal = optimizeWidthsBruteforce(widths)
+		else:
+			default, nominal = optimizeWidths(widths)
+		print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal)))
+
+if __name__ == '__main__':
+	import sys
+	if len(sys.argv) == 1:
+		import doctest
+		sys.exit(doctest.testmod().failed)
+	main()
diff --git a/Lib/fontTools/colorLib/__init__.py b/Lib/fontTools/colorLib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/fontTools/colorLib/__init__.py
diff --git a/Lib/fontTools/colorLib/builder.py b/Lib/fontTools/colorLib/builder.py
new file mode 100644
index 0000000..d2e35d8
--- /dev/null
+++ b/Lib/fontTools/colorLib/builder.py
@@ -0,0 +1,673 @@
+"""
+colorLib.builder: Build COLR/CPAL tables from scratch
+
+"""
+import collections
+import copy
+import enum
+from functools import partial
+from math import ceil, log
+from typing import (
+    Any,
+    Dict,
+    Generator,
+    Iterable,
+    List,
+    Mapping,
+    Optional,
+    Sequence,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+)
+from fontTools.misc.arrayTools import intRect
+from fontTools.misc.fixedTools import fixedToFloat
+from fontTools.ttLib.tables import C_O_L_R_
+from fontTools.ttLib.tables import C_P_A_L_
+from fontTools.ttLib.tables import _n_a_m_e
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.ttLib.tables.otTables import ExtendMode, CompositeMode
+from .errors import ColorLibError
+from .geometry import round_start_circle_stable_containment
+from .table_builder import BuildCallback, TableBuilder
+
+
+# TODO move type aliases to colorLib.types?
+T = TypeVar("T")
+_Kwargs = Mapping[str, Any]
+_PaintInput = Union[int, _Kwargs, ot.Paint, Tuple[str, "_PaintInput"]]
+_PaintInputList = Sequence[_PaintInput]
+_ColorGlyphsDict = Dict[str, Union[_PaintInputList, _PaintInput]]
+_ColorGlyphsV0Dict = Dict[str, Sequence[Tuple[str, int]]]
+_ClipBoxInput = Union[
+    Tuple[int, int, int, int, int],  # format 1, variable
+    Tuple[int, int, int, int],  # format 0, non-variable
+    ot.ClipBox,
+]
+
+
+MAX_PAINT_COLR_LAYER_COUNT = 255
+_DEFAULT_ALPHA = 1.0
+_MAX_REUSE_LEN = 32
+
+
+def _beforeBuildPaintRadialGradient(paint, source):
+    x0 = source["x0"]
+    y0 = source["y0"]
+    r0 = source["r0"]
+    x1 = source["x1"]
+    y1 = source["y1"]
+    r1 = source["r1"]
+
+    # TODO apparently no builder_test confirms this works (?)
+
+    # avoid abrupt change after rounding when c0 is near c1's perimeter
+    c = round_start_circle_stable_containment((x0, y0), r0, (x1, y1), r1)
+    x0, y0 = c.centre
+    r0 = c.radius
+
+    # update source to ensure paint is built with corrected values
+    source["x0"] = x0
+    source["y0"] = y0
+    source["r0"] = r0
+    source["x1"] = x1
+    source["y1"] = y1
+    source["r1"] = r1
+
+    return paint, source
+
+
+def _defaultColorStop():
+    colorStop = ot.ColorStop()
+    colorStop.Alpha = _DEFAULT_ALPHA
+    return colorStop
+
+
+def _defaultVarColorStop():
+    colorStop = ot.VarColorStop()
+    colorStop.Alpha = _DEFAULT_ALPHA
+    return colorStop
+
+
+def _defaultColorLine():
+    colorLine = ot.ColorLine()
+    colorLine.Extend = ExtendMode.PAD
+    return colorLine
+
+
+def _defaultVarColorLine():
+    colorLine = ot.VarColorLine()
+    colorLine.Extend = ExtendMode.PAD
+    return colorLine
+
+
+def _defaultPaintSolid():
+    paint = ot.Paint()
+    paint.Alpha = _DEFAULT_ALPHA
+    return paint
+
+
+def _buildPaintCallbacks():
+    return {
+        (
+            BuildCallback.BEFORE_BUILD,
+            ot.Paint,
+            ot.PaintFormat.PaintRadialGradient,
+        ): _beforeBuildPaintRadialGradient,
+        (
+            BuildCallback.BEFORE_BUILD,
+            ot.Paint,
+            ot.PaintFormat.PaintVarRadialGradient,
+        ): _beforeBuildPaintRadialGradient,
+        (BuildCallback.CREATE_DEFAULT, ot.ColorStop): _defaultColorStop,
+        (BuildCallback.CREATE_DEFAULT, ot.VarColorStop): _defaultVarColorStop,
+        (BuildCallback.CREATE_DEFAULT, ot.ColorLine): _defaultColorLine,
+        (BuildCallback.CREATE_DEFAULT, ot.VarColorLine): _defaultVarColorLine,
+        (
+            BuildCallback.CREATE_DEFAULT,
+            ot.Paint,
+            ot.PaintFormat.PaintSolid,
+        ): _defaultPaintSolid,
+        (
+            BuildCallback.CREATE_DEFAULT,
+            ot.Paint,
+            ot.PaintFormat.PaintVarSolid,
+        ): _defaultPaintSolid,
+    }
+
+
+def populateCOLRv0(
+    table: ot.COLR,
+    colorGlyphsV0: _ColorGlyphsV0Dict,
+    glyphMap: Optional[Mapping[str, int]] = None,
+):
+    """Build v0 color layers and add to existing COLR table.
+
+    Args:
+        table: a raw otTables.COLR() object (not ttLib's table_C_O_L_R_).
+        colorGlyphsV0: map of base glyph names to lists of (layer glyph names,
+            color palette index) tuples. Can be empty.
+        glyphMap: a map from glyph names to glyph indices, as returned from
+            TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
+    """
+    if glyphMap is not None:
+        colorGlyphItems = sorted(
+            colorGlyphsV0.items(), key=lambda item: glyphMap[item[0]]
+        )
+    else:
+        colorGlyphItems = colorGlyphsV0.items()
+    baseGlyphRecords = []
+    layerRecords = []
+    for baseGlyph, layers in colorGlyphItems:
+        baseRec = ot.BaseGlyphRecord()
+        baseRec.BaseGlyph = baseGlyph
+        baseRec.FirstLayerIndex = len(layerRecords)
+        baseRec.NumLayers = len(layers)
+        baseGlyphRecords.append(baseRec)
+
+        for layerGlyph, paletteIndex in layers:
+            layerRec = ot.LayerRecord()
+            layerRec.LayerGlyph = layerGlyph
+            layerRec.PaletteIndex = paletteIndex
+            layerRecords.append(layerRec)
+
+    table.BaseGlyphRecordArray = table.LayerRecordArray = None
+    if baseGlyphRecords:
+        table.BaseGlyphRecordArray = ot.BaseGlyphRecordArray()
+        table.BaseGlyphRecordArray.BaseGlyphRecord = baseGlyphRecords
+    if layerRecords:
+        table.LayerRecordArray = ot.LayerRecordArray()
+        table.LayerRecordArray.LayerRecord = layerRecords
+    table.BaseGlyphRecordCount = len(baseGlyphRecords)
+    table.LayerRecordCount = len(layerRecords)
+
+
+def buildCOLR(
+    colorGlyphs: _ColorGlyphsDict,
+    version: Optional[int] = None,
+    glyphMap: Optional[Mapping[str, int]] = None,
+    varStore: Optional[ot.VarStore] = None,
+    varIndexMap: Optional[ot.DeltaSetIndexMap] = None,
+    clipBoxes: Optional[Dict[str, _ClipBoxInput]] = None,
+) -> C_O_L_R_.table_C_O_L_R_:
+    """Build COLR table from color layers mapping.
+    Args:
+        colorGlyphs: map of base glyph name to, either list of (layer glyph name,
+            color palette index) tuples for COLRv0; or a single Paint (dict) or
+            list of Paint for COLRv1.
+        version: the version of COLR table. If None, the version is determined
+            by the presence of COLRv1 paints or variation data (varStore), which
+            require version 1; otherwise, if all base glyphs use only simple color
+            layers, version 0 is used.
+        glyphMap: a map from glyph names to glyph indices, as returned from
+            TTFont.getReverseGlyphMap(), to optionally sort base records by GID.
+        varStore: Optional ItemVarationStore for deltas associated with v1 layer.
+        varIndexMap: Optional DeltaSetIndexMap for deltas associated with v1 layer.
+        clipBoxes: Optional map of base glyph name to clip box 4- or 5-tuples:
+            (xMin, yMin, xMax, yMax) or (xMin, yMin, xMax, yMax, varIndexBase).
+    Return:
+        A new COLR table.
+    """
+    self = C_O_L_R_.table_C_O_L_R_()
+
+    if varStore is not None and version == 0:
+        raise ValueError("Can't add VarStore to COLRv0")
+
+    if version in (None, 0) and not varStore:
+        # split color glyphs into v0 and v1 and encode separately
+        colorGlyphsV0, colorGlyphsV1 = _split_color_glyphs_by_version(colorGlyphs)
+        if version == 0 and colorGlyphsV1:
+            raise ValueError("Can't encode COLRv1 glyphs in COLRv0")
+    else:
+        # unless explicitly requested for v1 or have variations, in which case
+        # we encode all color glyph as v1
+        colorGlyphsV0, colorGlyphsV1 = {}, colorGlyphs
+
+    colr = ot.COLR()
+
+    populateCOLRv0(colr, colorGlyphsV0, glyphMap)
+
+    colr.LayerList, colr.BaseGlyphList = buildColrV1(colorGlyphsV1, glyphMap)
+
+    if version is None:
+        version = 1 if (varStore or colorGlyphsV1) else 0
+    elif version not in (0, 1):
+        raise NotImplementedError(version)
+    self.version = colr.Version = version
+
+    if version == 0:
+        self.ColorLayers = self._decompileColorLayersV0(colr)
+    else:
+        clipBoxes = {
+            name: clipBoxes[name] for name in clipBoxes or {} if name in colorGlyphsV1
+        }
+        colr.ClipList = buildClipList(clipBoxes) if clipBoxes else None
+        colr.VarIndexMap = varIndexMap
+        colr.VarStore = varStore
+        self.table = colr
+
+    return self
+
+
+def buildClipList(clipBoxes: Dict[str, _ClipBoxInput]) -> ot.ClipList:
+    clipList = ot.ClipList()
+    clipList.Format = 1
+    clipList.clips = {name: buildClipBox(box) for name, box in clipBoxes.items()}
+    return clipList
+
+
+def buildClipBox(clipBox: _ClipBoxInput) -> ot.ClipBox:
+    if isinstance(clipBox, ot.ClipBox):
+        return clipBox
+    n = len(clipBox)
+    clip = ot.ClipBox()
+    if n not in (4, 5):
+        raise ValueError(f"Invalid ClipBox: expected 4 or 5 values, found {n}")
+    clip.xMin, clip.yMin, clip.xMax, clip.yMax = intRect(clipBox[:4])
+    clip.Format = int(n == 5) + 1
+    if n == 5:
+        clip.VarIndexBase = int(clipBox[4])
+    return clip
+
+
+class ColorPaletteType(enum.IntFlag):
+    USABLE_WITH_LIGHT_BACKGROUND = 0x0001
+    USABLE_WITH_DARK_BACKGROUND = 0x0002
+
+    @classmethod
+    def _missing_(cls, value):
+        # enforce reserved bits
+        if isinstance(value, int) and (value < 0 or value & 0xFFFC != 0):
+            raise ValueError(f"{value} is not a valid {cls.__name__}")
+        return super()._missing_(value)
+
+
+# None, 'abc' or {'en': 'abc', 'de': 'xyz'}
+_OptionalLocalizedString = Union[None, str, Dict[str, str]]
+
+
+def buildPaletteLabels(
+    labels: Iterable[_OptionalLocalizedString], nameTable: _n_a_m_e.table__n_a_m_e
+) -> List[Optional[int]]:
+    return [
+        nameTable.addMultilingualName(l, mac=False)
+        if isinstance(l, dict)
+        else C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
+        if l is None
+        else nameTable.addMultilingualName({"en": l}, mac=False)
+        for l in labels
+    ]
+
+
+def buildCPAL(
+    palettes: Sequence[Sequence[Tuple[float, float, float, float]]],
+    paletteTypes: Optional[Sequence[ColorPaletteType]] = None,
+    paletteLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
+    paletteEntryLabels: Optional[Sequence[_OptionalLocalizedString]] = None,
+    nameTable: Optional[_n_a_m_e.table__n_a_m_e] = None,
+) -> C_P_A_L_.table_C_P_A_L_:
+    """Build CPAL table from list of color palettes.
+
+    Args:
+        palettes: list of lists of colors encoded as tuples of (R, G, B, A) floats
+            in the range [0..1].
+        paletteTypes: optional list of ColorPaletteType, one for each palette.
+        paletteLabels: optional list of palette labels. Each lable can be either:
+            None (no label), a string (for for default English labels), or a
+            localized string (as a dict keyed with BCP47 language codes).
+        paletteEntryLabels: optional list of palette entry labels, one for each
+            palette entry (see paletteLabels).
+        nameTable: optional name table where to store palette and palette entry
+            labels. Required if either paletteLabels or paletteEntryLabels is set.
+
+    Return:
+        A new CPAL v0 or v1 table, if custom palette types or labels are specified.
+    """
+    if len({len(p) for p in palettes}) != 1:
+        raise ColorLibError("color palettes have different lengths")
+
+    if (paletteLabels or paletteEntryLabels) and not nameTable:
+        raise TypeError(
+            "nameTable is required if palette or palette entries have labels"
+        )
+
+    cpal = C_P_A_L_.table_C_P_A_L_()
+    cpal.numPaletteEntries = len(palettes[0])
+
+    cpal.palettes = []
+    for i, palette in enumerate(palettes):
+        colors = []
+        for j, color in enumerate(palette):
+            if not isinstance(color, tuple) or len(color) != 4:
+                raise ColorLibError(
+                    f"In palette[{i}][{j}]: expected (R, G, B, A) tuple, got {color!r}"
+                )
+            if any(v > 1 or v < 0 for v in color):
+                raise ColorLibError(
+                    f"palette[{i}][{j}] has invalid out-of-range [0..1] color: {color!r}"
+                )
+            # input colors are RGBA, CPAL encodes them as BGRA
+            red, green, blue, alpha = color
+            colors.append(
+                C_P_A_L_.Color(*(round(v * 255) for v in (blue, green, red, alpha)))
+            )
+        cpal.palettes.append(colors)
+
+    if any(v is not None for v in (paletteTypes, paletteLabels, paletteEntryLabels)):
+        cpal.version = 1
+
+        if paletteTypes is not None:
+            if len(paletteTypes) != len(palettes):
+                raise ColorLibError(
+                    f"Expected {len(palettes)} paletteTypes, got {len(paletteTypes)}"
+                )
+            cpal.paletteTypes = [ColorPaletteType(t).value for t in paletteTypes]
+        else:
+            cpal.paletteTypes = [C_P_A_L_.table_C_P_A_L_.DEFAULT_PALETTE_TYPE] * len(
+                palettes
+            )
+
+        if paletteLabels is not None:
+            if len(paletteLabels) != len(palettes):
+                raise ColorLibError(
+                    f"Expected {len(palettes)} paletteLabels, got {len(paletteLabels)}"
+                )
+            cpal.paletteLabels = buildPaletteLabels(paletteLabels, nameTable)
+        else:
+            cpal.paletteLabels = [C_P_A_L_.table_C_P_A_L_.NO_NAME_ID] * len(palettes)
+
+        if paletteEntryLabels is not None:
+            if len(paletteEntryLabels) != cpal.numPaletteEntries:
+                raise ColorLibError(
+                    f"Expected {cpal.numPaletteEntries} paletteEntryLabels, "
+                    f"got {len(paletteEntryLabels)}"
+                )
+            cpal.paletteEntryLabels = buildPaletteLabels(paletteEntryLabels, nameTable)
+        else:
+            cpal.paletteEntryLabels = [
+                C_P_A_L_.table_C_P_A_L_.NO_NAME_ID
+            ] * cpal.numPaletteEntries
+    else:
+        cpal.version = 0
+
+    return cpal
+
+
+# COLR v1 tables
+# See draft proposal at: https://github.com/googlefonts/colr-gradients-spec
+
+
+def _is_colrv0_layer(layer: Any) -> bool:
+    # Consider as COLRv0 layer any sequence of length 2 (be it tuple or list) in which
+    # the first element is a str (the layerGlyph) and the second element is an int
+    # (CPAL paletteIndex).
+    # https://github.com/googlefonts/ufo2ft/issues/426
+    try:
+        layerGlyph, paletteIndex = layer
+    except (TypeError, ValueError):
+        return False
+    else:
+        return isinstance(layerGlyph, str) and isinstance(paletteIndex, int)
+
+
+def _split_color_glyphs_by_version(
+    colorGlyphs: _ColorGlyphsDict,
+) -> Tuple[_ColorGlyphsV0Dict, _ColorGlyphsDict]:
+    colorGlyphsV0 = {}
+    colorGlyphsV1 = {}
+    for baseGlyph, layers in colorGlyphs.items():
+        if all(_is_colrv0_layer(l) for l in layers):
+            colorGlyphsV0[baseGlyph] = layers
+        else:
+            colorGlyphsV1[baseGlyph] = layers
+
+    # sanity check
+    assert set(colorGlyphs) == (set(colorGlyphsV0) | set(colorGlyphsV1))
+
+    return colorGlyphsV0, colorGlyphsV1
+
+
+def _reuse_ranges(num_layers: int) -> Generator[Tuple[int, int], None, None]:
+    # TODO feels like something itertools might have already
+    for lbound in range(num_layers):
+        # Reuse of very large #s of layers is relatively unlikely
+        # +2: we want sequences of at least 2
+        # otData handles single-record duplication
+        for ubound in range(
+            lbound + 2, min(num_layers + 1, lbound + 2 + _MAX_REUSE_LEN)
+        ):
+            yield (lbound, ubound)
+
+
+class LayerListBuilder:
+    slices: List[ot.Paint]
+    layers: List[ot.Paint]
+    reusePool: Mapping[Tuple[Any, ...], int]
+    tuples: Mapping[int, Tuple[Any, ...]]
+    keepAlive: List[ot.Paint]  # we need id to remain valid
+
+    def __init__(self):
+        self.slices = []
+        self.layers = []
+        self.reusePool = {}
+        self.tuples = {}
+        self.keepAlive = []
+
+        # We need to intercept construction of PaintColrLayers
+        callbacks = _buildPaintCallbacks()
+        callbacks[
+            (
+                BuildCallback.BEFORE_BUILD,
+                ot.Paint,
+                ot.PaintFormat.PaintColrLayers,
+            )
+        ] = self._beforeBuildPaintColrLayers
+        self.tableBuilder = TableBuilder(callbacks)
+
+    def _paint_tuple(self, paint: ot.Paint):
+        # start simple, who even cares about cyclic graphs or interesting field types
+        def _tuple_safe(value):
+            if isinstance(value, enum.Enum):
+                return value
+            elif hasattr(value, "__dict__"):
+                return tuple(
+                    (k, _tuple_safe(v)) for k, v in sorted(value.__dict__.items())
+                )
+            elif isinstance(value, collections.abc.MutableSequence):
+                return tuple(_tuple_safe(e) for e in value)
+            return value
+
+        # Cache the tuples for individual Paint instead of the whole sequence
+        # because the seq could be a transient slice
+        result = self.tuples.get(id(paint), None)
+        if result is None:
+            result = _tuple_safe(paint)
+            self.tuples[id(paint)] = result
+            self.keepAlive.append(paint)
+        return result
+
+    def _as_tuple(self, paints: Sequence[ot.Paint]) -> Tuple[Any, ...]:
+        return tuple(self._paint_tuple(p) for p in paints)
+
+    # COLR layers is unusual in that it modifies shared state
+    # so we need a callback into an object
+    def _beforeBuildPaintColrLayers(self, dest, source):
+        paint = ot.Paint()
+        paint.Format = int(ot.PaintFormat.PaintColrLayers)
+        self.slices.append(paint)
+
+        # Sketchy gymnastics: a sequence input will have dropped it's layers
+        # into NumLayers; get it back
+        if isinstance(source.get("NumLayers", None), collections.abc.Sequence):
+            layers = source["NumLayers"]
+        else:
+            layers = source["Layers"]
+
+        # Convert maps seqs or whatever into typed objects
+        layers = [self.buildPaint(l) for l in layers]
+
+        # No reason to have a colr layers with just one entry
+        if len(layers) == 1:
+            return layers[0], {}
+
+        # Look for reuse, with preference to longer sequences
+        # This may make the layer list smaller
+        found_reuse = True
+        while found_reuse:
+            found_reuse = False
+
+            ranges = sorted(
+                _reuse_ranges(len(layers)),
+                key=lambda t: (t[1] - t[0], t[1], t[0]),
+                reverse=True,
+            )
+            for lbound, ubound in ranges:
+                reuse_lbound = self.reusePool.get(
+                    self._as_tuple(layers[lbound:ubound]), -1
+                )
+                if reuse_lbound == -1:
+                    continue
+                new_slice = ot.Paint()
+                new_slice.Format = int(ot.PaintFormat.PaintColrLayers)
+                new_slice.NumLayers = ubound - lbound
+                new_slice.FirstLayerIndex = reuse_lbound
+                layers = layers[:lbound] + [new_slice] + layers[ubound:]
+                found_reuse = True
+                break
+
+        # The layer list is now final; if it's too big we need to tree it
+        is_tree = len(layers) > MAX_PAINT_COLR_LAYER_COUNT
+        layers = _build_n_ary_tree(layers, n=MAX_PAINT_COLR_LAYER_COUNT)
+
+        # We now have a tree of sequences with Paint leaves.
+        # Convert the sequences into PaintColrLayers.
+        def listToColrLayers(layer):
+            if isinstance(layer, collections.abc.Sequence):
+                return self.buildPaint(
+                    {
+                        "Format": ot.PaintFormat.PaintColrLayers,
+                        "Layers": [listToColrLayers(l) for l in layer],
+                    }
+                )
+            return layer
+
+        layers = [listToColrLayers(l) for l in layers]
+
+        paint.NumLayers = len(layers)
+        paint.FirstLayerIndex = len(self.layers)
+        self.layers.extend(layers)
+
+        # Register our parts for reuse provided we aren't a tree
+        # If we are a tree the leaves registered for reuse and that will suffice
+        if not is_tree:
+            for lbound, ubound in _reuse_ranges(len(layers)):
+                self.reusePool[self._as_tuple(layers[lbound:ubound])] = (
+                    lbound + paint.FirstLayerIndex
+                )
+
+        # we've fully built dest; empty source prevents generalized build from kicking in
+        return paint, {}
+
+    def buildPaint(self, paint: _PaintInput) -> ot.Paint:
+        return self.tableBuilder.build(ot.Paint, paint)
+
+    def build(self) -> Optional[ot.LayerList]:
+        if not self.layers:
+            return None
+        layers = ot.LayerList()
+        layers.LayerCount = len(self.layers)
+        layers.Paint = self.layers
+        return layers
+
+
+def buildBaseGlyphPaintRecord(
+    baseGlyph: str, layerBuilder: LayerListBuilder, paint: _PaintInput
+) -> ot.BaseGlyphList:
+    self = ot.BaseGlyphPaintRecord()
+    self.BaseGlyph = baseGlyph
+    self.Paint = layerBuilder.buildPaint(paint)
+    return self
+
+
+def _format_glyph_errors(errors: Mapping[str, Exception]) -> str:
+    lines = []
+    for baseGlyph, error in sorted(errors.items()):
+        lines.append(f"    {baseGlyph} => {type(error).__name__}: {error}")
+    return "\n".join(lines)
+
+
+def buildColrV1(
+    colorGlyphs: _ColorGlyphsDict,
+    glyphMap: Optional[Mapping[str, int]] = None,
+) -> Tuple[Optional[ot.LayerList], ot.BaseGlyphList]:
+    if glyphMap is not None:
+        colorGlyphItems = sorted(
+            colorGlyphs.items(), key=lambda item: glyphMap[item[0]]
+        )
+    else:
+        colorGlyphItems = colorGlyphs.items()
+
+    errors = {}
+    baseGlyphs = []
+    layerBuilder = LayerListBuilder()
+    for baseGlyph, paint in colorGlyphItems:
+        try:
+            baseGlyphs.append(buildBaseGlyphPaintRecord(baseGlyph, layerBuilder, paint))
+
+        except (ColorLibError, OverflowError, ValueError, TypeError) as e:
+            errors[baseGlyph] = e
+
+    if errors:
+        failed_glyphs = _format_glyph_errors(errors)
+        exc = ColorLibError(f"Failed to build BaseGlyphList:\n{failed_glyphs}")
+        exc.errors = errors
+        raise exc from next(iter(errors.values()))
+
+    layers = layerBuilder.build()
+    glyphs = ot.BaseGlyphList()
+    glyphs.BaseGlyphCount = len(baseGlyphs)
+    glyphs.BaseGlyphPaintRecord = baseGlyphs
+    return (layers, glyphs)
+
+
+def _build_n_ary_tree(leaves, n):
+    """Build N-ary tree from sequence of leaf nodes.
+
+    Return a list of lists where each non-leaf node is a list containing
+    max n nodes.
+    """
+    if not leaves:
+        return []
+
+    assert n > 1
+
+    depth = ceil(log(len(leaves), n))
+
+    if depth <= 1:
+        return list(leaves)
+
+    # Fully populate complete subtrees of root until we have enough leaves left
+    root = []
+    unassigned = None
+    full_step = n ** (depth - 1)
+    for i in range(0, len(leaves), full_step):
+        subtree = leaves[i : i + full_step]
+        if len(subtree) < full_step:
+            unassigned = subtree
+            break
+        while len(subtree) > n:
+            subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
+        root.append(subtree)
+
+    if unassigned:
+        # Recurse to fill the last subtree, which is the only partially populated one
+        subtree = _build_n_ary_tree(unassigned, n)
+        if len(subtree) <= n - len(root):
+            # replace last subtree with its children if they can still fit
+            root.extend(subtree)
+        else:
+            root.append(subtree)
+        assert len(root) <= n
+
+    return root
diff --git a/Lib/fontTools/colorLib/errors.py b/Lib/fontTools/colorLib/errors.py
new file mode 100644
index 0000000..a0bdda1
--- /dev/null
+++ b/Lib/fontTools/colorLib/errors.py
@@ -0,0 +1,3 @@
+
+class ColorLibError(Exception):
+    pass
diff --git a/Lib/fontTools/colorLib/geometry.py b/Lib/fontTools/colorLib/geometry.py
new file mode 100644
index 0000000..e62aead
--- /dev/null
+++ b/Lib/fontTools/colorLib/geometry.py
@@ -0,0 +1,145 @@
+"""Helpers for manipulating 2D points and vectors in COLR table."""
+
+from math import copysign, cos, hypot, pi
+from fontTools.misc.roundTools import otRound
+
+
+def _vector_between(origin, target):
+    return (target[0] - origin[0], target[1] - origin[1])
+
+
+def _round_point(pt):
+    return (otRound(pt[0]), otRound(pt[1]))
+
+
+def _unit_vector(vec):
+    length = hypot(*vec)
+    if length == 0:
+        return None
+    return (vec[0] / length, vec[1] / length)
+
+
+# This is the same tolerance used by Skia's SkTwoPointConicalGradient.cpp to detect
+# when a radial gradient's focal point lies on the end circle.
+_NEARLY_ZERO = 1 / (1 << 12)  # 0.000244140625
+
+
+# The unit vector's X and Y components are respectively
+#   U = (cos(α), sin(α))
+# where α is the angle between the unit vector and the positive x axis.
+_UNIT_VECTOR_THRESHOLD = cos(3 / 8 * pi)  # == sin(1/8 * pi) == 0.38268343236508984
+
+
+def _rounding_offset(direction):
+    # Return 2-tuple of -/+ 1.0 or 0.0 approximately based on the direction vector.
+    # We divide the unit circle in 8 equal slices oriented towards the cardinal
+    # (N, E, S, W) and intermediate (NE, SE, SW, NW) directions. To each slice we
+    # map one of the possible cases: -1, 0, +1 for either X and Y coordinate.
+    # E.g. Return (+1.0, -1.0) if unit vector is oriented towards SE, or
+    # (-1.0, 0.0) if it's pointing West, etc.
+    uv = _unit_vector(direction)
+    if not uv:
+        return (0, 0)
+
+    result = []
+    for uv_component in uv:
+        if -_UNIT_VECTOR_THRESHOLD <= uv_component < _UNIT_VECTOR_THRESHOLD:
+            # unit vector component near 0: direction almost orthogonal to the
+            # direction of the current axis, thus keep coordinate unchanged
+            result.append(0)
+        else:
+            # nudge coord by +/- 1.0 in direction of unit vector
+            result.append(copysign(1.0, uv_component))
+    return tuple(result)
+
+
+class Circle:
+    def __init__(self, centre, radius):
+        self.centre = centre
+        self.radius = radius
+
+    def __repr__(self):
+        return f"Circle(centre={self.centre}, radius={self.radius})"
+
+    def round(self):
+        return Circle(_round_point(self.centre), otRound(self.radius))
+
+    def inside(self, outer_circle):
+        dist = self.radius + hypot(*_vector_between(self.centre, outer_circle.centre))
+        return (
+            abs(outer_circle.radius - dist) <= _NEARLY_ZERO
+            or outer_circle.radius > dist
+        )
+
+    def concentric(self, other):
+        return self.centre == other.centre
+
+    def move(self, dx, dy):
+        self.centre = (self.centre[0] + dx, self.centre[1] + dy)
+
+
+def round_start_circle_stable_containment(c0, r0, c1, r1):
+    """Round start circle so that it stays inside/outside end circle after rounding.
+
+    The rounding of circle coordinates to integers may cause an abrupt change
+    if the start circle c0 is so close to the end circle c1's perimiter that
+    it ends up falling outside (or inside) as a result of the rounding.
+    To keep the gradient unchanged, we nudge it in the right direction.
+
+    See:
+    https://github.com/googlefonts/colr-gradients-spec/issues/204
+    https://github.com/googlefonts/picosvg/issues/158
+    """
+    start, end = Circle(c0, r0), Circle(c1, r1)
+
+    inside_before_round = start.inside(end)
+
+    round_start = start.round()
+    round_end = end.round()
+    inside_after_round = round_start.inside(round_end)
+
+    if inside_before_round == inside_after_round:
+        return round_start
+    elif inside_after_round:
+        # start was outside before rounding: we need to push start away from end
+        direction = _vector_between(round_end.centre, round_start.centre)
+        radius_delta = +1.0
+    else:
+        # start was inside before rounding: we need to push start towards end
+        direction = _vector_between(round_start.centre, round_end.centre)
+        radius_delta = -1.0
+    dx, dy = _rounding_offset(direction)
+
+    # At most 2 iterations ought to be enough to converge. Before the loop, we
+    # know the start circle didn't keep containment after normal rounding; thus
+    # we continue adjusting by -/+ 1.0 until containment is restored.
+    # Normal rounding can at most move each coordinates -/+0.5; in the worst case
+    # both the start and end circle's centres and radii will be rounded in opposite
+    # directions, e.g. when they move along a 45 degree diagonal:
+    #   c0 = (1.5, 1.5) ===> (2.0, 2.0)
+    #   r0 = 0.5 ===> 1.0
+    #   c1 = (0.499, 0.499) ===> (0.0, 0.0)
+    #   r1 = 2.499 ===> 2.0
+    # In this example, the relative distance between the circles, calculated
+    # as r1 - (r0 + distance(c0, c1)) is initially 0.57437 (c0 is inside c1), and
+    # -1.82842 after rounding (c0 is now outside c1). Nudging c0 by -1.0 on both
+    # x and y axes moves it towards c1 by hypot(-1.0, -1.0) = 1.41421. Two of these
+    # moves cover twice that distance, which is enough to restore containment.
+    max_attempts = 2
+    for _ in range(max_attempts):
+        if round_start.concentric(round_end):
+            # can't move c0 towards c1 (they are the same), so we change the radius
+            round_start.radius += radius_delta
+            assert round_start.radius >= 0
+        else:
+            round_start.move(dx, dy)
+        if inside_before_round == round_start.inside(round_end):
+            break
+    else:  # likely a bug
+        raise AssertionError(
+            f"Rounding circle {start} "
+            f"{'inside' if inside_before_round else 'outside'} "
+            f"{end} failed after {max_attempts} attempts!"
+        )
+
+    return round_start
diff --git a/Lib/fontTools/colorLib/table_builder.py b/Lib/fontTools/colorLib/table_builder.py
new file mode 100644
index 0000000..763115b
--- /dev/null
+++ b/Lib/fontTools/colorLib/table_builder.py
@@ -0,0 +1,225 @@
+"""
+colorLib.table_builder: Generic helper for filling in BaseTable derivatives from tuples and maps and such.
+
+"""
+
+import collections
+import enum
+from fontTools.ttLib.tables.otBase import (
+    BaseTable,
+    FormatSwitchingBaseTable,
+    UInt8FormatSwitchingBaseTable,
+)
+from fontTools.ttLib.tables.otConverters import (
+    ComputedInt,
+    SimpleValue,
+    Struct,
+    Short,
+    UInt8,
+    UShort,
+    IntValue,
+    FloatValue,
+    OptionalValue,
+)
+from fontTools.misc.roundTools import otRound
+
+
+class BuildCallback(enum.Enum):
+    """Keyed on (BEFORE_BUILD, class[, Format if available]).
+    Receives (dest, source).
+    Should return (dest, source), which can be new objects.
+    """
+
+    BEFORE_BUILD = enum.auto()
+
+    """Keyed on (AFTER_BUILD, class[, Format if available]).
+    Receives (dest).
+    Should return dest, which can be a new object.
+    """
+    AFTER_BUILD = enum.auto()
+
+    """Keyed on (CREATE_DEFAULT, class[, Format if available]).
+    Receives no arguments.
+    Should return a new instance of class.
+    """
+    CREATE_DEFAULT = enum.auto()
+
+
+def _assignable(convertersByName):
+    return {k: v for k, v in convertersByName.items() if not isinstance(v, ComputedInt)}
+
+
+def _isNonStrSequence(value):
+    return isinstance(value, collections.abc.Sequence) and not isinstance(value, str)
+
+
+def _split_format(cls, source):
+    if _isNonStrSequence(source):
+        assert len(source) > 0, f"{cls} needs at least format from {source}"
+        fmt, remainder = source[0], source[1:]
+    elif isinstance(source, collections.abc.Mapping):
+        assert "Format" in source, f"{cls} needs at least Format from {source}"
+        remainder = source.copy()
+        fmt = remainder.pop("Format")
+    else:
+        raise ValueError(f"Not sure how to populate {cls} from {source}")
+
+    assert isinstance(
+        fmt, collections.abc.Hashable
+    ), f"{cls} Format is not hashable: {fmt!r}"
+    assert (
+        fmt in cls.convertersByName
+    ), f"{cls} invalid Format: {fmt!r}"
+
+    return fmt, remainder
+
+
+class TableBuilder:
+    """
+    Helps to populate things derived from BaseTable from maps, tuples, etc.
+
+    A table of lifecycle callbacks may be provided to add logic beyond what is possible
+    based on otData info for the target class. See BuildCallbacks.
+    """
+
+    def __init__(self, callbackTable=None):
+        if callbackTable is None:
+            callbackTable = {}
+        self._callbackTable = callbackTable
+
+    def _convert(self, dest, field, converter, value):
+        enumClass = getattr(converter, "enumClass", None)
+
+        if enumClass:
+            if isinstance(value, enumClass):
+                pass
+            elif isinstance(value, str):
+                try:
+                    value = getattr(enumClass, value.upper())
+                except AttributeError:
+                    raise ValueError(f"{value} is not a valid {enumClass}")
+            else:
+                value = enumClass(value)
+
+        elif isinstance(converter, IntValue):
+            value = otRound(value)
+        elif isinstance(converter, FloatValue):
+            value = float(value)
+
+        elif isinstance(converter, Struct):
+            if converter.repeat:
+                if _isNonStrSequence(value):
+                    value = [self.build(converter.tableClass, v) for v in value]
+                else:
+                    value = [self.build(converter.tableClass, value)]
+                setattr(dest, converter.repeat, len(value))
+            else:
+                value = self.build(converter.tableClass, value)
+        elif callable(converter):
+            value = converter(value)
+
+        setattr(dest, field, value)
+
+    def build(self, cls, source):
+        assert issubclass(cls, BaseTable)
+
+        if isinstance(source, cls):
+            return source
+
+        callbackKey = (cls,)
+        fmt = None
+        if issubclass(cls, FormatSwitchingBaseTable):
+            fmt, source = _split_format(cls, source)
+            callbackKey = (cls, fmt)
+
+        dest = self._callbackTable.get(
+            (BuildCallback.CREATE_DEFAULT,) + callbackKey, lambda: cls()
+        )()
+        assert isinstance(dest, cls)
+
+        convByName = _assignable(cls.convertersByName)
+        skippedFields = set()
+
+        # For format switchers we need to resolve converters based on format
+        if issubclass(cls, FormatSwitchingBaseTable):
+            dest.Format = fmt
+            convByName = _assignable(convByName[dest.Format])
+            skippedFields.add("Format")
+
+        # Convert sequence => mapping so before thunk only has to handle one format
+        if _isNonStrSequence(source):
+            # Sequence (typically list or tuple) assumed to match fields in declaration order
+            assert len(source) <= len(
+                convByName
+            ), f"Sequence of {len(source)} too long for {cls}; expected <= {len(convByName)} values"
+            source = dict(zip(convByName.keys(), source))
+
+        dest, source = self._callbackTable.get(
+            (BuildCallback.BEFORE_BUILD,) + callbackKey, lambda d, s: (d, s)
+        )(dest, source)
+
+        if isinstance(source, collections.abc.Mapping):
+            for field, value in source.items():
+                if field in skippedFields:
+                    continue
+                converter = convByName.get(field, None)
+                if not converter:
+                    raise ValueError(
+                        f"Unrecognized field {field} for {cls}; expected one of {sorted(convByName.keys())}"
+                    )
+                self._convert(dest, field, converter, value)
+        else:
+            # let's try as a 1-tuple
+            dest = self.build(cls, (source,))
+
+        for field, conv in convByName.items():
+            if not hasattr(dest, field) and isinstance(conv, OptionalValue):
+                setattr(dest, field, conv.DEFAULT)
+
+        dest = self._callbackTable.get(
+            (BuildCallback.AFTER_BUILD,) + callbackKey, lambda d: d
+        )(dest)
+
+        return dest
+
+
+class TableUnbuilder:
+    def __init__(self, callbackTable=None):
+        if callbackTable is None:
+            callbackTable = {}
+        self._callbackTable = callbackTable
+
+    def unbuild(self, table):
+        assert isinstance(table, BaseTable)
+
+        source = {}
+
+        callbackKey = (type(table),)
+        if isinstance(table, FormatSwitchingBaseTable):
+            source["Format"] = int(table.Format)
+            callbackKey += (table.Format,)
+
+        for converter in table.getConverters():
+            if isinstance(converter, ComputedInt):
+                continue
+            value = getattr(table, converter.name)
+
+            enumClass = getattr(converter, "enumClass", None)
+            if enumClass:
+                source[converter.name] = value.name.lower()
+            elif isinstance(converter, Struct):
+                if converter.repeat:
+                    source[converter.name] = [self.unbuild(v) for v in value]
+                else:
+                    source[converter.name] = self.unbuild(value)
+            elif isinstance(converter, SimpleValue):
+                # "simple" values (e.g. int, float, str) need no further un-building
+                source[converter.name] = value
+            else:
+                raise NotImplementedError(
+                    "Don't know how unbuild {value!r} with {converter!r}"
+                )
+
+        source = self._callbackTable.get(callbackKey, lambda s: s)(source)
+
+        return source
diff --git a/Lib/fontTools/colorLib/unbuilder.py b/Lib/fontTools/colorLib/unbuilder.py
new file mode 100644
index 0000000..0d10cff
--- /dev/null
+++ b/Lib/fontTools/colorLib/unbuilder.py
@@ -0,0 +1,79 @@
+from fontTools.ttLib.tables import otTables as ot
+from .table_builder import TableUnbuilder
+
+
+def unbuildColrV1(layerV1List, baseGlyphV1List):
+    unbuilder = LayerListUnbuilder(layerV1List.Paint)
+    return {
+        rec.BaseGlyph: unbuilder.unbuildPaint(rec.Paint)
+        for rec in baseGlyphV1List.BaseGlyphPaintRecord
+    }
+
+
+def _flatten(lst):
+    for el in lst:
+        if isinstance(el, list):
+            yield from _flatten(el)
+        else:
+            yield el
+
+
+class LayerListUnbuilder:
+    def __init__(self, layers):
+        self.layers = layers
+
+        callbacks = {
+            (
+                ot.Paint,
+                ot.PaintFormat.PaintColrLayers,
+            ): self._unbuildPaintColrLayers,
+        }
+        self.tableUnbuilder = TableUnbuilder(callbacks)
+
+    def unbuildPaint(self, paint):
+        assert isinstance(paint, ot.Paint)
+        return self.tableUnbuilder.unbuild(paint)
+
+    def _unbuildPaintColrLayers(self, source):
+        assert source["Format"] == ot.PaintFormat.PaintColrLayers
+
+        layers = list(
+            _flatten(
+                [
+                    self.unbuildPaint(childPaint)
+                    for childPaint in self.layers[
+                        source["FirstLayerIndex"] : source["FirstLayerIndex"]
+                        + source["NumLayers"]
+                    ]
+                ]
+            )
+        )
+
+        if len(layers) == 1:
+            return layers[0]
+
+        return {"Format": source["Format"], "Layers": layers}
+
+
+if __name__ == "__main__":
+    from pprint import pprint
+    import sys
+    from fontTools.ttLib import TTFont
+
+    try:
+        fontfile = sys.argv[1]
+    except IndexError:
+        sys.exit("usage: fonttools colorLib.unbuilder FONTFILE")
+
+    font = TTFont(fontfile)
+    colr = font["COLR"]
+    if colr.version < 1:
+        sys.exit(f"error: No COLR table version=1 found in {fontfile}")
+
+    colorGlyphs = unbuildColrV1(
+        colr.table.LayerList,
+        colr.table.BaseGlyphList,
+        ignoreVarIdx=not colr.table.VarStore,
+    )
+
+    pprint(colorGlyphs)
diff --git a/Lib/fontTools/cu2qu/__init__.py b/Lib/fontTools/cu2qu/__init__.py
new file mode 100644
index 0000000..4ae6356
--- /dev/null
+++ b/Lib/fontTools/cu2qu/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .cu2qu import *
diff --git a/Lib/fontTools/cu2qu/__main__.py b/Lib/fontTools/cu2qu/__main__.py
new file mode 100644
index 0000000..084bf8f
--- /dev/null
+++ b/Lib/fontTools/cu2qu/__main__.py
@@ -0,0 +1,6 @@
+import sys
+from .cli import main
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/Lib/fontTools/cu2qu/cli.py b/Lib/fontTools/cu2qu/cli.py
new file mode 100644
index 0000000..d4e83b8
--- /dev/null
+++ b/Lib/fontTools/cu2qu/cli.py
@@ -0,0 +1,177 @@
+import os
+import argparse
+import logging
+import shutil
+import multiprocessing as mp
+from contextlib import closing
+from functools import partial
+
+import fontTools
+from .ufo import font_to_quadratic, fonts_to_quadratic
+
+ufo_module = None
+try:
+    import ufoLib2 as ufo_module
+except ImportError:
+    try:
+        import defcon as ufo_module
+    except ImportError as e:
+        pass
+
+
+logger = logging.getLogger("fontTools.cu2qu")
+
+
+def _cpu_count():
+    try:
+        return mp.cpu_count()
+    except NotImplementedError:  # pragma: no cover
+        return 1
+
+
+def _font_to_quadratic(input_path, output_path=None, **kwargs):
+    ufo = ufo_module.Font(input_path)
+    logger.info('Converting curves for %s', input_path)
+    if font_to_quadratic(ufo, **kwargs):
+        logger.info("Saving %s", output_path)
+        if output_path:
+            ufo.save(output_path)
+        else:
+            ufo.save()  # save in-place
+    elif output_path:
+        _copytree(input_path, output_path)
+
+
+def _samepath(path1, path2):
+    # TODO on python3+, there's os.path.samefile
+    path1 = os.path.normcase(os.path.abspath(os.path.realpath(path1)))
+    path2 = os.path.normcase(os.path.abspath(os.path.realpath(path2)))
+    return path1 == path2
+
+
+def _copytree(input_path, output_path):
+    if _samepath(input_path, output_path):
+        logger.debug("input and output paths are the same file; skipped copy")
+        return
+    if os.path.exists(output_path):
+        shutil.rmtree(output_path)
+    shutil.copytree(input_path, output_path)
+
+
+def main(args=None):
+    """Convert a UFO font from cubic to quadratic curves"""
+    parser = argparse.ArgumentParser(prog="cu2qu")
+    parser.add_argument(
+        "--version", action="version", version=fontTools.__version__)
+    parser.add_argument(
+        "infiles",
+        nargs="+",
+        metavar="INPUT",
+        help="one or more input UFO source file(s).")
+    parser.add_argument("-v", "--verbose", action="count", default=0)
+    parser.add_argument(
+        "-e",
+        "--conversion-error",
+        type=float,
+        metavar="ERROR",
+        default=None,
+        help="maxiumum approximation error measured in EM (default: 0.001)")
+    parser.add_argument(
+        "--keep-direction",
+        dest="reverse_direction",
+        action="store_false",
+        help="do not reverse the contour direction")
+
+    mode_parser = parser.add_mutually_exclusive_group()
+    mode_parser.add_argument(
+        "-i",
+        "--interpolatable",
+        action="store_true",
+        help="whether curve conversion should keep interpolation compatibility"
+    )
+    mode_parser.add_argument(
+        "-j",
+        "--jobs",
+        type=int,
+        nargs="?",
+        default=1,
+        const=_cpu_count(),
+        metavar="N",
+        help="Convert using N multiple processes (default: %(default)s)")
+
+    output_parser = parser.add_mutually_exclusive_group()
+    output_parser.add_argument(
+        "-o",
+        "--output-file",
+        default=None,
+        metavar="OUTPUT",
+        help=("output filename for the converted UFO. By default fonts are "
+              "modified in place. This only works with a single input."))
+    output_parser.add_argument(
+        "-d",
+        "--output-dir",
+        default=None,
+        metavar="DIRECTORY",
+        help="output directory where to save converted UFOs")
+
+    options = parser.parse_args(args)
+
+    if ufo_module is None:
+        parser.error("Either ufoLib2 or defcon are required to run this script.")
+
+    if not options.verbose:
+        level = "WARNING"
+    elif options.verbose == 1:
+        level = "INFO"
+    else:
+        level = "DEBUG"
+    logging.basicConfig(level=level)
+
+    if len(options.infiles) > 1 and options.output_file:
+        parser.error("-o/--output-file can't be used with multile inputs")
+
+    if options.output_dir:
+        output_dir = options.output_dir
+        if not os.path.exists(output_dir):
+            os.mkdir(output_dir)
+        elif not os.path.isdir(output_dir):
+            parser.error("'%s' is not a directory" % output_dir)
+        output_paths = [
+            os.path.join(output_dir, os.path.basename(p))
+            for p in options.infiles
+        ]
+    elif options.output_file:
+        output_paths = [options.output_file]
+    else:
+        # save in-place
+        output_paths = [None] * len(options.infiles)
+
+    kwargs = dict(dump_stats=options.verbose > 0,
+                  max_err_em=options.conversion_error,
+                  reverse_direction=options.reverse_direction)
+
+    if options.interpolatable:
+        logger.info('Converting curves compatibly')
+        ufos = [ufo_module.Font(infile) for infile in options.infiles]
+        if fonts_to_quadratic(ufos, **kwargs):
+            for ufo, output_path in zip(ufos, output_paths):
+                logger.info("Saving %s", output_path)
+                if output_path:
+                    ufo.save(output_path)
+                else:
+                    ufo.save()
+        else:
+            for input_path, output_path in zip(options.infiles, output_paths):
+                if output_path:
+                    _copytree(input_path, output_path)
+    else:
+        jobs = min(len(options.infiles),
+                   options.jobs) if options.jobs > 1 else 1
+        if jobs > 1:
+            func = partial(_font_to_quadratic, **kwargs)
+            logger.info('Running %d parallel processes', jobs)
+            with closing(mp.Pool(jobs)) as pool:
+                pool.starmap(func, zip(options.infiles, output_paths))
+        else:
+            for input_path, output_path in zip(options.infiles, output_paths):
+                _font_to_quadratic(input_path, output_path, **kwargs)
diff --git a/Lib/fontTools/cu2qu/cu2qu.py b/Lib/fontTools/cu2qu/cu2qu.py
new file mode 100644
index 0000000..c9ce93a
--- /dev/null
+++ b/Lib/fontTools/cu2qu/cu2qu.py
@@ -0,0 +1,496 @@
+#cython: language_level=3
+#distutils: define_macros=CYTHON_TRACE_NOGIL=1
+
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+try:
+    import cython
+except ImportError:
+    # if cython not installed, use mock module with no-op decorators and types
+    from fontTools.misc import cython
+
+import math
+
+from .errors import Error as Cu2QuError, ApproxNotFoundError
+
+
+__all__ = ['curve_to_quadratic', 'curves_to_quadratic']
+
+MAX_N = 100
+
+NAN = float("NaN")
+
+
+if cython.compiled:
+    # Yep, I'm compiled.
+    COMPILED = True
+else:
+    # Just a lowly interpreted script.
+    COMPILED = False
+
+
+@cython.cfunc
+@cython.inline
+@cython.returns(cython.double)
+@cython.locals(v1=cython.complex, v2=cython.complex)
+def dot(v1, v2):
+    """Return the dot product of two vectors.
+
+    Args:
+        v1 (complex): First vector.
+        v2 (complex): Second vector.
+
+    Returns:
+        double: Dot product.
+    """
+    return (v1 * v2.conjugate()).real
+
+
+@cython.cfunc
+@cython.inline
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(_1=cython.complex, _2=cython.complex, _3=cython.complex, _4=cython.complex)
+def calc_cubic_points(a, b, c, d):
+    _1 = d
+    _2 = (c / 3.0) + d
+    _3 = (b + c) / 3.0 + _2
+    _4 = a + d + c + b
+    return _1, _2, _3, _4
+
+
+@cython.cfunc
+@cython.inline
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+def calc_cubic_parameters(p0, p1, p2, p3):
+    c = (p1 - p0) * 3.0
+    b = (p2 - p1) * 3.0 - c
+    d = p0
+    a = p3 - d - c - b
+    return a, b, c, d
+
+
+@cython.cfunc
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+def split_cubic_into_n_iter(p0, p1, p2, p3, n):
+    """Split a cubic Bezier into n equal parts.
+
+    Splits the curve into `n` equal parts by curve time.
+    (t=0..1/n, t=1/n..2/n, ...)
+
+    Args:
+        p0 (complex): Start point of curve.
+        p1 (complex): First handle of curve.
+        p2 (complex): Second handle of curve.
+        p3 (complex): End point of curve.
+
+    Returns:
+        An iterator yielding the control points (four complex values) of the
+        subcurves.
+    """
+    # Hand-coded special-cases
+    if n == 2:
+        return iter(split_cubic_into_two(p0, p1, p2, p3))
+    if n == 3:
+        return iter(split_cubic_into_three(p0, p1, p2, p3))
+    if n == 4:
+        a, b = split_cubic_into_two(p0, p1, p2, p3)
+        return iter(split_cubic_into_two(*a) + split_cubic_into_two(*b))
+    if n == 6:
+        a, b = split_cubic_into_two(p0, p1, p2, p3)
+        return iter(split_cubic_into_three(*a) + split_cubic_into_three(*b))
+
+    return _split_cubic_into_n_gen(p0,p1,p2,p3,n)
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, n=cython.int)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(dt=cython.double, delta_2=cython.double, delta_3=cython.double, i=cython.int)
+@cython.locals(a1=cython.complex, b1=cython.complex, c1=cython.complex, d1=cython.complex)
+def _split_cubic_into_n_gen(p0, p1, p2, p3, n):
+    a, b, c, d = calc_cubic_parameters(p0, p1, p2, p3)
+    dt = 1 / n
+    delta_2 = dt * dt
+    delta_3 = dt * delta_2
+    for i in range(n):
+        t1 = i * dt
+        t1_2 = t1 * t1
+        # calc new a, b, c and d
+        a1 = a * delta_3
+        b1 = (3*a*t1 + b) * delta_2
+        c1 = (2*b*t1 + c + 3*a*t1_2) * dt
+        d1 = a*t1*t1_2 + b*t1_2 + c*t1 + d
+        yield calc_cubic_points(a1, b1, c1, d1)
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(mid=cython.complex, deriv3=cython.complex)
+def split_cubic_into_two(p0, p1, p2, p3):
+    """Split a cubic Bezier into two equal parts.
+
+    Splits the curve into two equal parts at t = 0.5
+
+    Args:
+        p0 (complex): Start point of curve.
+        p1 (complex): First handle of curve.
+        p2 (complex): Second handle of curve.
+        p3 (complex): End point of curve.
+
+    Returns:
+        tuple: Two cubic Beziers (each expressed as a tuple of four complex
+        values).
+    """
+    mid = (p0 + 3 * (p1 + p2) + p3) * .125
+    deriv3 = (p3 + p2 - p1 - p0) * .125
+    return ((p0, (p0 + p1) * .5, mid - deriv3, mid),
+            (mid, mid + deriv3, (p2 + p3) * .5, p3))
+
+
+@cython.locals(p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex, _27=cython.double)
+@cython.locals(mid1=cython.complex, deriv1=cython.complex, mid2=cython.complex, deriv2=cython.complex)
+def split_cubic_into_three(p0, p1, p2, p3, _27=1/27):
+    """Split a cubic Bezier into three equal parts.
+
+    Splits the curve into three equal parts at t = 1/3 and t = 2/3
+
+    Args:
+        p0 (complex): Start point of curve.
+        p1 (complex): First handle of curve.
+        p2 (complex): Second handle of curve.
+        p3 (complex): End point of curve.
+
+    Returns:
+        tuple: Three cubic Beziers (each expressed as a tuple of four complex
+        values).
+    """
+    # we define 1/27 as a keyword argument so that it will be evaluated only
+    # once but still in the scope of this function
+    mid1 = (8*p0 + 12*p1 + 6*p2 + p3) * _27
+    deriv1 = (p3 + 3*p2 - 4*p0) * _27
+    mid2 = (p0 + 6*p1 + 12*p2 + 8*p3) * _27
+    deriv2 = (4*p3 - 3*p1 - p0) * _27
+    return ((p0, (2*p0 + p1) / 3.0, mid1 - deriv1, mid1),
+            (mid1, mid1 + deriv1, mid2 - deriv2, mid2),
+            (mid2, mid2 + deriv2, (p2 + 2*p3) / 3.0, p3))
+
+
+@cython.returns(cython.complex)
+@cython.locals(t=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(_p1=cython.complex, _p2=cython.complex)
+def cubic_approx_control(t, p0, p1, p2, p3):
+    """Approximate a cubic Bezier using a quadratic one.
+
+    Args:
+        t (double): Position of control point.
+        p0 (complex): Start point of curve.
+        p1 (complex): First handle of curve.
+        p2 (complex): Second handle of curve.
+        p3 (complex): End point of curve.
+
+    Returns:
+        complex: Location of candidate control point on quadratic curve.
+    """
+    _p1 = p0 + (p1 - p0) * 1.5
+    _p2 = p3 + (p2 - p3) * 1.5
+    return _p1 + (_p2 - _p1) * t
+
+
+@cython.returns(cython.complex)
+@cython.locals(a=cython.complex, b=cython.complex, c=cython.complex, d=cython.complex)
+@cython.locals(ab=cython.complex, cd=cython.complex, p=cython.complex, h=cython.double)
+def calc_intersect(a, b, c, d):
+    """Calculate the intersection of two lines.
+
+    Args:
+        a (complex): Start point of first line.
+        b (complex): End point of first line.
+        c (complex): Start point of second line.
+        d (complex): End point of second line.
+
+    Returns:
+        complex: Location of intersection if one present, ``complex(NaN,NaN)``
+        if no intersection was found.
+    """
+    ab = b - a
+    cd = d - c
+    p = ab * 1j
+    try:
+        h = dot(p, a - c) / dot(p, cd)
+    except ZeroDivisionError:
+        return complex(NAN, NAN)
+    return c + cd * h
+
+
+@cython.cfunc
+@cython.returns(cython.int)
+@cython.locals(tolerance=cython.double, p0=cython.complex, p1=cython.complex, p2=cython.complex, p3=cython.complex)
+@cython.locals(mid=cython.complex, deriv3=cython.complex)
+def cubic_farthest_fit_inside(p0, p1, p2, p3, tolerance):
+    """Check if a cubic Bezier lies within a given distance of the origin.
+
+    "Origin" means *the* origin (0,0), not the start of the curve. Note that no
+    checks are made on the start and end positions of the curve; this function
+    only checks the inside of the curve.
+
+    Args:
+        p0 (complex): Start point of curve.
+        p1 (complex): First handle of curve.
+        p2 (complex): Second handle of curve.
+        p3 (complex): End point of curve.
+        tolerance (double): Distance from origin.
+
+    Returns:
+        bool: True if the cubic Bezier ``p`` entirely lies within a distance
+        ``tolerance`` of the origin, False otherwise.
+    """
+    # First check p2 then p1, as p2 has higher error early on.
+    if abs(p2) <= tolerance and abs(p1) <= tolerance:
+        return True
+
+    # Split.
+    mid = (p0 + 3 * (p1 + p2) + p3) * .125
+    if abs(mid) > tolerance:
+        return False
+    deriv3 = (p3 + p2 - p1 - p0) * .125
+    return (cubic_farthest_fit_inside(p0, (p0+p1)*.5, mid-deriv3, mid, tolerance) and
+            cubic_farthest_fit_inside(mid, mid+deriv3, (p2+p3)*.5, p3, tolerance))
+
+
+@cython.cfunc
+@cython.locals(tolerance=cython.double, _2_3=cython.double)
+@cython.locals(q1=cython.complex, c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex)
+def cubic_approx_quadratic(cubic, tolerance, _2_3=2/3):
+    """Approximate a cubic Bezier with a single quadratic within a given tolerance.
+
+    Args:
+        cubic (sequence): Four complex numbers representing control points of
+            the cubic Bezier curve.
+        tolerance (double): Permitted deviation from the original curve.
+
+    Returns:
+        Three complex numbers representing control points of the quadratic
+        curve if it fits within the given tolerance, or ``None`` if no suitable
+        curve could be calculated.
+    """
+    # we define 2/3 as a keyword argument so that it will be evaluated only
+    # once but still in the scope of this function
+
+    q1 = calc_intersect(*cubic)
+    if math.isnan(q1.imag):
+        return None
+    c0 = cubic[0]
+    c3 = cubic[3]
+    c1 = c0 + (q1 - c0) * _2_3
+    c2 = c3 + (q1 - c3) * _2_3
+    if not cubic_farthest_fit_inside(0,
+                                     c1 - cubic[1],
+                                     c2 - cubic[2],
+                                     0, tolerance):
+        return None
+    return c0, q1, c3
+
+
+@cython.cfunc
+@cython.locals(n=cython.int, tolerance=cython.double, _2_3=cython.double)
+@cython.locals(i=cython.int)
+@cython.locals(c0=cython.complex, c1=cython.complex, c2=cython.complex, c3=cython.complex)
+@cython.locals(q0=cython.complex, q1=cython.complex, next_q1=cython.complex, q2=cython.complex, d1=cython.complex)
+def cubic_approx_spline(cubic, n, tolerance, _2_3=2/3):
+    """Approximate a cubic Bezier curve with a spline of n quadratics.
+
+    Args:
+        cubic (sequence): Four complex numbers representing control points of
+            the cubic Bezier curve.
+        n (int): Number of quadratic Bezier curves in the spline.
+        tolerance (double): Permitted deviation from the original curve.
+
+    Returns:
+        A list of ``n+2`` complex numbers, representing control points of the
+        quadratic spline if it fits within the given tolerance, or ``None`` if
+        no suitable spline could be calculated.
+    """
+    # we define 2/3 as a keyword argument so that it will be evaluated only
+    # once but still in the scope of this function
+
+    if n == 1:
+        return cubic_approx_quadratic(cubic, tolerance)
+
+    cubics = split_cubic_into_n_iter(cubic[0], cubic[1], cubic[2], cubic[3], n)
+
+    # calculate the spline of quadratics and check errors at the same time.
+    next_cubic = next(cubics)
+    next_q1 = cubic_approx_control(0, *next_cubic)
+    q2 = cubic[0]
+    d1 = 0j
+    spline = [cubic[0], next_q1]
+    for i in range(1, n+1):
+
+        # Current cubic to convert
+        c0, c1, c2, c3 = next_cubic
+
+        # Current quadratic approximation of current cubic
+        q0 = q2
+        q1 = next_q1
+        if i < n:
+            next_cubic = next(cubics)
+            next_q1 = cubic_approx_control(i / (n-1), *next_cubic)
+            spline.append(next_q1)
+            q2 = (q1 + next_q1) * .5
+        else:
+            q2 = c3
+
+        # End-point deltas
+        d0 = d1
+        d1 = q2 - c3
+
+        if (abs(d1) > tolerance or
+            not cubic_farthest_fit_inside(d0,
+                                          q0 + (q1 - q0) * _2_3 - c1,
+                                          q2 + (q1 - q2) * _2_3 - c2,
+                                          d1,
+                                          tolerance)):
+            return None
+    spline.append(cubic[3])
+
+    return spline
+
+
+@cython.locals(max_err=cython.double)
+@cython.locals(n=cython.int)
+def curve_to_quadratic(curve, max_err):
+    """Approximate a cubic Bezier curve with a spline of n quadratics.
+
+    Args:
+        cubic (sequence): Four 2D tuples representing control points of
+            the cubic Bezier curve.
+        max_err (double): Permitted deviation from the original curve.
+
+    Returns:
+        A list of 2D tuples, representing control points of the quadratic
+        spline if it fits within the given tolerance, or ``None`` if no
+        suitable spline could be calculated.
+    """
+
+    curve = [complex(*p) for p in curve]
+
+    for n in range(1, MAX_N + 1):
+        spline = cubic_approx_spline(curve, n, max_err)
+        if spline is not None:
+            # done. go home
+            return [(s.real, s.imag) for s in spline]
+
+    raise ApproxNotFoundError(curve)
+
+
+
+@cython.locals(l=cython.int, last_i=cython.int, i=cython.int)
+def curves_to_quadratic(curves, max_errors):
+    """Return quadratic Bezier splines approximating the input cubic Beziers.
+
+    Args:
+        curves: A sequence of *n* curves, each curve being a sequence of four
+            2D tuples.
+        max_errors: A sequence of *n* floats representing the maximum permissible
+            deviation from each of the cubic Bezier curves.
+
+    Example::
+
+        >>> curves_to_quadratic( [
+        ...   [ (50,50), (100,100), (150,100), (200,50) ],
+        ...   [ (75,50), (120,100), (150,75),  (200,60) ]
+        ... ], [1,1] )
+        [[(50.0, 50.0), (75.0, 75.0), (125.0, 91.66666666666666), (175.0, 75.0), (200.0, 50.0)], [(75.0, 50.0), (97.5, 75.0), (135.41666666666666, 82.08333333333333), (175.0, 67.5), (200.0, 60.0)]]
+
+    The returned splines have "implied oncurve points" suitable for use in
+    TrueType ``glif`` outlines - i.e. in the first spline returned above,
+    the first quadratic segment runs from (50,50) to
+    ( (75 + 125)/2 , (120 + 91.666..)/2 ) = (100, 83.333...).
+
+    Returns:
+        A list of splines, each spline being a list of 2D tuples.
+
+    Raises:
+        fontTools.cu2qu.Errors.ApproxNotFoundError: if no suitable approximation
+        can be found for all curves with the given parameters.
+    """
+
+    curves = [[complex(*p) for p in curve] for curve in curves]
+    assert len(max_errors) == len(curves)
+
+    l = len(curves)
+    splines = [None] * l
+    last_i = i = 0
+    n = 1
+    while True:
+        spline = cubic_approx_spline(curves[i], n, max_errors[i])
+        if spline is None:
+            if n == MAX_N:
+                break
+            n += 1
+            last_i = i
+            continue
+        splines[i] = spline
+        i = (i + 1) % l
+        if i == last_i:
+            # done. go home
+            return [[(s.real, s.imag) for s in spline] for spline in splines]
+
+    raise ApproxNotFoundError(curves)
+
+
+if __name__ == '__main__':
+    import random
+    import timeit
+
+    MAX_ERR = 5
+
+    def generate_curve():
+        return [
+            tuple(float(random.randint(0, 2048)) for coord in range(2))
+            for point in range(4)]
+
+    def setup_curve_to_quadratic():
+        return generate_curve(), MAX_ERR
+
+    def setup_curves_to_quadratic():
+        num_curves = 3
+        return (
+            [generate_curve() for curve in range(num_curves)],
+            [MAX_ERR] * num_curves)
+
+    def run_benchmark(
+            benchmark_module, module, function, setup_suffix='', repeat=5, number=1000):
+        setup_func = 'setup_' + function
+        if setup_suffix:
+            print('%s with %s:' % (function, setup_suffix), end='')
+            setup_func += '_' + setup_suffix
+        else:
+            print('%s:' % function, end='')
+
+        def wrapper(function, setup_func):
+            function = globals()[function]
+            setup_func = globals()[setup_func]
+            def wrapped():
+                return function(*setup_func())
+            return wrapped
+        results = timeit.repeat(wrapper(function, setup_func), repeat=repeat, number=number)
+        print('\t%5.1fus' % (min(results) * 1000000. / number))
+
+    def main():
+        run_benchmark('cu2qu.benchmark', 'cu2qu', 'curve_to_quadratic')
+        run_benchmark('cu2qu.benchmark', 'cu2qu', 'curves_to_quadratic')
+
+    random.seed(1)
+    main()
diff --git a/Lib/fontTools/cu2qu/errors.py b/Lib/fontTools/cu2qu/errors.py
new file mode 100644
index 0000000..74c4c22
--- /dev/null
+++ b/Lib/fontTools/cu2qu/errors.py
@@ -0,0 +1,76 @@
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Error(Exception):
+    """Base Cu2Qu exception class for all other errors."""
+
+
+class ApproxNotFoundError(Error):
+    def __init__(self, curve):
+        message = "no approximation found: %s" % curve
+        super().__init__(message)
+        self.curve = curve
+
+
+class UnequalZipLengthsError(Error):
+    pass
+
+
+class IncompatibleGlyphsError(Error):
+    def __init__(self, glyphs):
+        assert len(glyphs) > 1
+        self.glyphs = glyphs
+        names = set(repr(g.name) for g in glyphs)
+        if len(names) > 1:
+            self.combined_name = "{%s}" % ", ".join(sorted(names))
+        else:
+            self.combined_name = names.pop()
+
+    def __repr__(self):
+        return "<%s %s>" % (type(self).__name__, self.combined_name)
+
+
+class IncompatibleSegmentNumberError(IncompatibleGlyphsError):
+    def __str__(self):
+        return "Glyphs named %s have different number of segments" % (
+            self.combined_name
+        )
+
+
+class IncompatibleSegmentTypesError(IncompatibleGlyphsError):
+    def __init__(self, glyphs, segments):
+        IncompatibleGlyphsError.__init__(self, glyphs)
+        self.segments = segments
+
+    def __str__(self):
+        lines = []
+        ndigits = len(str(max(self.segments)))
+        for i, tags in sorted(self.segments.items()):
+            lines.append(
+                "%s: (%s)" % (str(i).rjust(ndigits), ", ".join(repr(t) for t in tags))
+            )
+        return "Glyphs named %s have incompatible segment types:\n  %s" % (
+            self.combined_name,
+            "\n  ".join(lines),
+        )
+
+
+class IncompatibleFontsError(Error):
+    def __init__(self, glyph_errors):
+        self.glyph_errors = glyph_errors
+
+    def __str__(self):
+        return "fonts contains incompatible glyphs: %s" % (
+            ", ".join(repr(g) for g in sorted(self.glyph_errors.keys()))
+        )
diff --git a/Lib/fontTools/cu2qu/ufo.py b/Lib/fontTools/cu2qu/ufo.py
new file mode 100644
index 0000000..447de7b
--- /dev/null
+++ b/Lib/fontTools/cu2qu/ufo.py
@@ -0,0 +1,324 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Converts cubic bezier curves to quadratic splines.
+
+Conversion is performed such that the quadratic splines keep the same end-curve
+tangents as the original cubics. The approach is iterative, increasing the
+number of segments for a spline until the error gets below a bound.
+
+Respective curves from multiple fonts will be converted at once to ensure that
+the resulting splines are interpolation-compatible.
+"""
+
+import logging
+from fontTools.pens.basePen import AbstractPen
+from fontTools.pens.pointPen import PointToSegmentPen
+from fontTools.pens.reverseContourPen import ReverseContourPen
+
+from . import curves_to_quadratic
+from .errors import (
+    UnequalZipLengthsError, IncompatibleSegmentNumberError,
+    IncompatibleSegmentTypesError, IncompatibleGlyphsError,
+    IncompatibleFontsError)
+
+
+__all__ = ['fonts_to_quadratic', 'font_to_quadratic']
+
+# The default approximation error below is a relative value (1/1000 of the EM square).
+# Later on, we convert it to absolute font units by multiplying it by a font's UPEM
+# (see fonts_to_quadratic).
+DEFAULT_MAX_ERR = 0.001
+CURVE_TYPE_LIB_KEY = "com.github.googlei18n.cu2qu.curve_type"
+
+logger = logging.getLogger(__name__)
+
+
+_zip = zip
+def zip(*args):
+    """Ensure each argument to zip has the same length. Also make sure a list is
+    returned for python 2/3 compatibility.
+    """
+
+    if len(set(len(a) for a in args)) != 1:
+        raise UnequalZipLengthsError(*args)
+    return list(_zip(*args))
+
+
+class GetSegmentsPen(AbstractPen):
+    """Pen to collect segments into lists of points for conversion.
+
+    Curves always include their initial on-curve point, so some points are
+    duplicated between segments.
+    """
+
+    def __init__(self):
+        self._last_pt = None
+        self.segments = []
+
+    def _add_segment(self, tag, *args):
+        if tag in ['move', 'line', 'qcurve', 'curve']:
+            self._last_pt = args[-1]
+        self.segments.append((tag, args))
+
+    def moveTo(self, pt):
+        self._add_segment('move', pt)
+
+    def lineTo(self, pt):
+        self._add_segment('line', pt)
+
+    def qCurveTo(self, *points):
+        self._add_segment('qcurve', self._last_pt, *points)
+
+    def curveTo(self, *points):
+        self._add_segment('curve', self._last_pt, *points)
+
+    def closePath(self):
+        self._add_segment('close')
+
+    def endPath(self):
+        self._add_segment('end')
+
+    def addComponent(self, glyphName, transformation):
+        pass
+
+
+def _get_segments(glyph):
+    """Get a glyph's segments as extracted by GetSegmentsPen."""
+
+    pen = GetSegmentsPen()
+    # glyph.draw(pen)
+    # We can't simply draw the glyph with the pen, but we must initialize the
+    # PointToSegmentPen explicitly with outputImpliedClosingLine=True.
+    # By default PointToSegmentPen does not outputImpliedClosingLine -- unless
+    # last and first point on closed contour are duplicated. Because we are
+    # converting multiple glyphs at the same time, we want to make sure
+    # this function returns the same number of segments, whether or not
+    # the last and first point overlap.
+    # https://github.com/googlefonts/fontmake/issues/572
+    # https://github.com/fonttools/fonttools/pull/1720
+    pointPen = PointToSegmentPen(pen, outputImpliedClosingLine=True)
+    glyph.drawPoints(pointPen)
+    return pen.segments
+
+
+def _set_segments(glyph, segments, reverse_direction):
+    """Draw segments as extracted by GetSegmentsPen back to a glyph."""
+
+    glyph.clearContours()
+    pen = glyph.getPen()
+    if reverse_direction:
+        pen = ReverseContourPen(pen)
+    for tag, args in segments:
+        if tag == 'move':
+            pen.moveTo(*args)
+        elif tag == 'line':
+            pen.lineTo(*args)
+        elif tag == 'curve':
+            pen.curveTo(*args[1:])
+        elif tag == 'qcurve':
+            pen.qCurveTo(*args[1:])
+        elif tag == 'close':
+            pen.closePath()
+        elif tag == 'end':
+            pen.endPath()
+        else:
+            raise AssertionError('Unhandled segment type "%s"' % tag)
+
+
+def _segments_to_quadratic(segments, max_err, stats):
+    """Return quadratic approximations of cubic segments."""
+
+    assert all(s[0] == 'curve' for s in segments), 'Non-cubic given to convert'
+
+    new_points = curves_to_quadratic([s[1] for s in segments], max_err)
+    n = len(new_points[0])
+    assert all(len(s) == n for s in new_points[1:]), 'Converted incompatibly'
+
+    spline_length = str(n - 2)
+    stats[spline_length] = stats.get(spline_length, 0) + 1
+
+    return [('qcurve', p) for p in new_points]
+
+
+def _glyphs_to_quadratic(glyphs, max_err, reverse_direction, stats):
+    """Do the actual conversion of a set of compatible glyphs, after arguments
+    have been set up.
+
+    Return True if the glyphs were modified, else return False.
+    """
+
+    try:
+        segments_by_location = zip(*[_get_segments(g) for g in glyphs])
+    except UnequalZipLengthsError:
+        raise IncompatibleSegmentNumberError(glyphs)
+    if not any(segments_by_location):
+        return False
+
+    # always modify input glyphs if reverse_direction is True
+    glyphs_modified = reverse_direction
+
+    new_segments_by_location = []
+    incompatible = {}
+    for i, segments in enumerate(segments_by_location):
+        tag = segments[0][0]
+        if not all(s[0] == tag for s in segments[1:]):
+            incompatible[i] = [s[0] for s in segments]
+        elif tag == 'curve':
+            segments = _segments_to_quadratic(segments, max_err, stats)
+            glyphs_modified = True
+        new_segments_by_location.append(segments)
+
+    if glyphs_modified:
+        new_segments_by_glyph = zip(*new_segments_by_location)
+        for glyph, new_segments in zip(glyphs, new_segments_by_glyph):
+            _set_segments(glyph, new_segments, reverse_direction)
+
+    if incompatible:
+        raise IncompatibleSegmentTypesError(glyphs, segments=incompatible)
+    return glyphs_modified
+
+
+def glyphs_to_quadratic(
+        glyphs, max_err=None, reverse_direction=False, stats=None):
+    """Convert the curves of a set of compatible of glyphs to quadratic.
+
+    All curves will be converted to quadratic at once, ensuring interpolation
+    compatibility. If this is not required, calling glyphs_to_quadratic with one
+    glyph at a time may yield slightly more optimized results.
+
+    Return True if glyphs were modified, else return False.
+
+    Raises IncompatibleGlyphsError if glyphs have non-interpolatable outlines.
+    """
+    if stats is None:
+        stats = {}
+
+    if not max_err:
+        # assume 1000 is the default UPEM
+        max_err = DEFAULT_MAX_ERR * 1000
+
+    if isinstance(max_err, (list, tuple)):
+        max_errors = max_err
+    else:
+        max_errors = [max_err] * len(glyphs)
+    assert len(max_errors) == len(glyphs)
+
+    return _glyphs_to_quadratic(glyphs, max_errors, reverse_direction, stats)
+
+
+def fonts_to_quadratic(
+        fonts, max_err_em=None, max_err=None, reverse_direction=False,
+        stats=None, dump_stats=False, remember_curve_type=True):
+    """Convert the curves of a collection of fonts to quadratic.
+
+    All curves will be converted to quadratic at once, ensuring interpolation
+    compatibility. If this is not required, calling fonts_to_quadratic with one
+    font at a time may yield slightly more optimized results.
+
+    Return True if fonts were modified, else return False.
+
+    By default, cu2qu stores the curve type in the fonts' lib, under a private
+    key "com.github.googlei18n.cu2qu.curve_type", and will not try to convert
+    them again if the curve type is already set to "quadratic".
+    Setting 'remember_curve_type' to False disables this optimization.
+
+    Raises IncompatibleFontsError if same-named glyphs from different fonts
+    have non-interpolatable outlines.
+    """
+
+    if remember_curve_type:
+        curve_types = {f.lib.get(CURVE_TYPE_LIB_KEY, "cubic") for f in fonts}
+        if len(curve_types) == 1:
+            curve_type = next(iter(curve_types))
+            if curve_type == "quadratic":
+                logger.info("Curves already converted to quadratic")
+                return False
+            elif curve_type == "cubic":
+                pass  # keep converting
+            else:
+                raise NotImplementedError(curve_type)
+        elif len(curve_types) > 1:
+            # going to crash later if they do differ
+            logger.warning("fonts may contain different curve types")
+
+    if stats is None:
+        stats = {}
+
+    if max_err_em and max_err:
+        raise TypeError('Only one of max_err and max_err_em can be specified.')
+    if not (max_err_em or max_err):
+        max_err_em = DEFAULT_MAX_ERR
+
+    if isinstance(max_err, (list, tuple)):
+        assert len(max_err) == len(fonts)
+        max_errors = max_err
+    elif max_err:
+        max_errors = [max_err] * len(fonts)
+
+    if isinstance(max_err_em, (list, tuple)):
+        assert len(fonts) == len(max_err_em)
+        max_errors = [f.info.unitsPerEm * e
+                      for f, e in zip(fonts, max_err_em)]
+    elif max_err_em:
+        max_errors = [f.info.unitsPerEm * max_err_em for f in fonts]
+
+    modified = False
+    glyph_errors = {}
+    for name in set().union(*(f.keys() for f in fonts)):
+        glyphs = []
+        cur_max_errors = []
+        for font, error in zip(fonts, max_errors):
+            if name in font:
+                glyphs.append(font[name])
+                cur_max_errors.append(error)
+        try:
+            modified |= _glyphs_to_quadratic(
+                glyphs, cur_max_errors, reverse_direction, stats)
+        except IncompatibleGlyphsError as exc:
+            logger.error(exc)
+            glyph_errors[name] = exc
+
+    if glyph_errors:
+        raise IncompatibleFontsError(glyph_errors)
+
+    if modified and dump_stats:
+        spline_lengths = sorted(stats.keys())
+        logger.info('New spline lengths: %s' % (', '.join(
+                    '%s: %d' % (l, stats[l]) for l in spline_lengths)))
+
+    if remember_curve_type:
+        for font in fonts:
+            curve_type = font.lib.get(CURVE_TYPE_LIB_KEY, "cubic")
+            if curve_type != "quadratic":
+                font.lib[CURVE_TYPE_LIB_KEY] = "quadratic"
+                modified = True
+    return modified
+
+
+def glyph_to_quadratic(glyph, **kwargs):
+    """Convenience wrapper around glyphs_to_quadratic, for just one glyph.
+    Return True if the glyph was modified, else return False.
+    """
+
+    return glyphs_to_quadratic([glyph], **kwargs)
+
+
+def font_to_quadratic(font, **kwargs):
+    """Convenience wrapper around fonts_to_quadratic, for just one font.
+    Return True if the font was modified, else return False.
+    """
+
+    return fonts_to_quadratic([font], **kwargs)
diff --git a/Lib/fontTools/designspaceLib/__init__.py b/Lib/fontTools/designspaceLib/__init__.py
new file mode 100644
index 0000000..3bb2bbf
--- /dev/null
+++ b/Lib/fontTools/designspaceLib/__init__.py
@@ -0,0 +1,1407 @@
+# -*- coding: utf-8 -*-
+
+from fontTools.misc.py23 import tobytes, tostr
+from fontTools.misc.loggingTools import LogMixin
+import collections
+from io import BytesIO, StringIO
+import os
+import posixpath
+from fontTools.misc import etree as ET
+from fontTools.misc import plistlib
+
+"""
+    designSpaceDocument
+
+    - read and write designspace files
+"""
+
+__all__ = [
+    'DesignSpaceDocumentError', 'DesignSpaceDocument', 'SourceDescriptor',
+    'InstanceDescriptor', 'AxisDescriptor', 'RuleDescriptor', 'BaseDocReader',
+    'BaseDocWriter'
+]
+
+# ElementTree allows to find namespace-prefixed elements, but not attributes
+# so we have to do it ourselves for 'xml:lang'
+XML_NS = "{http://www.w3.org/XML/1998/namespace}"
+XML_LANG = XML_NS + "lang"
+
+
+def posix(path):
+    """Normalize paths using forward slash to work also on Windows."""
+    new_path = posixpath.join(*path.split(os.path.sep))
+    if path.startswith('/'):
+        # The above transformation loses absolute paths
+        new_path = '/' + new_path
+    elif path.startswith(r'\\'):
+        # The above transformation loses leading slashes of UNC path mounts
+        new_path = '//' + new_path
+    return new_path
+
+
+def posixpath_property(private_name):
+    def getter(self):
+        # Normal getter
+        return getattr(self, private_name)
+
+    def setter(self, value):
+        # The setter rewrites paths using forward slashes
+        if value is not None:
+            value = posix(value)
+        setattr(self, private_name, value)
+
+    return property(getter, setter)
+
+
+class DesignSpaceDocumentError(Exception):
+    def __init__(self, msg, obj=None):
+        self.msg = msg
+        self.obj = obj
+
+    def __str__(self):
+        return str(self.msg) + (
+            ": %r" % self.obj if self.obj is not None else "")
+
+
+class AsDictMixin(object):
+
+    def asdict(self):
+        d = {}
+        for attr, value in self.__dict__.items():
+            if attr.startswith("_"):
+                continue
+            if hasattr(value, "asdict"):
+                value = value.asdict()
+            elif isinstance(value, list):
+                value = [
+                    v.asdict() if hasattr(v, "asdict") else v for v in value
+                ]
+            d[attr] = value
+        return d
+
+
+class SimpleDescriptor(AsDictMixin):
+    """ Containers for a bunch of attributes"""
+
+    # XXX this is ugly. The 'print' is inappropriate here, and instead of
+    # assert, it should simply return True/False
+    def compare(self, other):
+        # test if this object contains the same data as the other
+        for attr in self._attrs:
+            try:
+                assert(getattr(self, attr) == getattr(other, attr))
+            except AssertionError:
+                print("failed attribute", attr, getattr(self, attr), "!=", getattr(other, attr))
+
+
+class SourceDescriptor(SimpleDescriptor):
+    """Simple container for data related to the source"""
+    flavor = "source"
+    _attrs = ['filename', 'path', 'name', 'layerName',
+              'location', 'copyLib',
+              'copyGroups', 'copyFeatures',
+              'muteKerning', 'muteInfo',
+              'mutedGlyphNames',
+              'familyName', 'styleName']
+
+    def __init__(
+        self,
+        *,
+        filename=None,
+        path=None,
+        font=None,
+        name=None,
+        location=None,
+        layerName=None,
+        familyName=None,
+        styleName=None,
+        copyLib=False,
+        copyInfo=False,
+        copyGroups=False,
+        copyFeatures=False,
+        muteKerning=False,
+        muteInfo=False,
+        mutedGlyphNames=None,
+    ):
+        self.filename = filename
+        """The original path as found in the document."""
+
+        self.path = path
+        """The absolute path, calculated from filename."""
+
+        self.font = font
+        """Any Python object. Optional. Points to a representation of this
+        source font that is loaded in memory, as a Python object (e.g. a
+        ``defcon.Font`` or a ``fontTools.ttFont.TTFont``).
+
+        The default document reader will not fill-in this attribute, and the
+        default writer will not use this attribute. It is up to the user of
+        ``designspaceLib`` to either load the resource identified by
+        ``filename`` and store it in this field, or write the contents of
+        this field to the disk and make ```filename`` point to that.
+        """
+
+        self.name = name
+        self.location = location
+        self.layerName = layerName
+        self.familyName = familyName
+        self.styleName = styleName
+
+        self.copyLib = copyLib
+        self.copyInfo = copyInfo
+        self.copyGroups = copyGroups
+        self.copyFeatures = copyFeatures
+        self.muteKerning = muteKerning
+        self.muteInfo = muteInfo
+        self.mutedGlyphNames = mutedGlyphNames or []
+
+    path = posixpath_property("_path")
+    filename = posixpath_property("_filename")
+
+
+class RuleDescriptor(SimpleDescriptor):
+    """<!-- optional: list of substitution rules -->
+    <rules>
+        <rule name="vertical.bars">
+            <conditionset>
+                <condition minimum="250.000000" maximum="750.000000" name="weight"/>
+                <condition minimum="100" name="width"/>
+                <condition minimum="10" maximum="40" name="optical"/>
+            </conditionset>
+            <sub name="cent" with="cent.alt"/>
+            <sub name="dollar" with="dollar.alt"/>
+        </rule>
+    </rules>
+    """
+    _attrs = ['name', 'conditionSets', 'subs']   # what do we need here
+
+    def __init__(self, *, name=None, conditionSets=None, subs=None):
+        self.name = name
+        # list of lists of dict(name='aaaa', minimum=0, maximum=1000)
+        self.conditionSets = conditionSets or []
+        # list of substitutions stored as tuples of glyphnames ("a", "a.alt")
+        self.subs = subs or []
+
+
+def evaluateRule(rule, location):
+    """ Return True if any of the rule's conditionsets matches the given location."""
+    return any(evaluateConditions(c, location) for c in rule.conditionSets)
+
+
+def evaluateConditions(conditions, location):
+    """ Return True if all the conditions matches the given location.
+        If a condition has no minimum, check for < maximum.
+        If a condition has no maximum, check for > minimum.
+    """
+    for cd in conditions:
+        value = location[cd['name']]
+        if cd.get('minimum') is None:
+            if value > cd['maximum']:
+                return False
+        elif cd.get('maximum') is None:
+            if cd['minimum'] > value:
+                return False
+        elif not cd['minimum'] <= value <= cd['maximum']:
+            return False
+    return True
+
+
+def processRules(rules, location, glyphNames):
+    """ Apply these rules at this location to these glyphnames
+        - rule order matters
+    """
+    newNames = []
+    for rule in rules:
+        if evaluateRule(rule, location):
+            for name in glyphNames:
+                swap = False
+                for a, b in rule.subs:
+                    if name == a:
+                        swap = True
+                        break
+                if swap:
+                    newNames.append(b)
+                else:
+                    newNames.append(name)
+            glyphNames = newNames
+            newNames = []
+    return glyphNames
+
+
+class InstanceDescriptor(SimpleDescriptor):
+    """Simple container for data related to the instance"""
+    flavor = "instance"
+    _defaultLanguageCode = "en"
+    _attrs = ['path',
+              'name',
+              'location',
+              'familyName',
+              'styleName',
+              'postScriptFontName',
+              'styleMapFamilyName',
+              'styleMapStyleName',
+              'kerning',
+              'info',
+              'lib']
+
+    def __init__(
+        self,
+        *,
+        filename=None,
+        path=None,
+        font=None,
+        name=None,
+        location=None,
+        familyName=None,
+        styleName=None,
+        postScriptFontName=None,
+        styleMapFamilyName=None,
+        styleMapStyleName=None,
+        localisedFamilyName=None,
+        localisedStyleName=None,
+        localisedStyleMapFamilyName=None,
+        localisedStyleMapStyleName=None,
+        glyphs=None,
+        kerning=True,
+        info=True,
+        lib=None,
+    ):
+        # the original path as found in the document
+        self.filename = filename
+        # the absolute path, calculated from filename
+        self.path = path
+        # Same as in SourceDescriptor.
+        self.font = font
+        self.name = name
+        self.location = location
+        self.familyName = familyName
+        self.styleName = styleName
+        self.postScriptFontName = postScriptFontName
+        self.styleMapFamilyName = styleMapFamilyName
+        self.styleMapStyleName = styleMapStyleName
+        self.localisedFamilyName = localisedFamilyName or {}
+        self.localisedStyleName = localisedStyleName or {}
+        self.localisedStyleMapFamilyName = localisedStyleMapFamilyName or {}
+        self.localisedStyleMapStyleName = localisedStyleMapStyleName or {}
+        self.glyphs = glyphs or {}
+        self.kerning = kerning
+        self.info = info
+
+        self.lib = lib or {}
+        """Custom data associated with this instance."""
+
+    path = posixpath_property("_path")
+    filename = posixpath_property("_filename")
+
+    def setStyleName(self, styleName, languageCode="en"):
+        self.localisedStyleName[languageCode] = tostr(styleName)
+
+    def getStyleName(self, languageCode="en"):
+        return self.localisedStyleName.get(languageCode)
+
+    def setFamilyName(self, familyName, languageCode="en"):
+        self.localisedFamilyName[languageCode] = tostr(familyName)
+
+    def getFamilyName(self, languageCode="en"):
+        return self.localisedFamilyName.get(languageCode)
+
+    def setStyleMapStyleName(self, styleMapStyleName, languageCode="en"):
+        self.localisedStyleMapStyleName[languageCode] = tostr(styleMapStyleName)
+
+    def getStyleMapStyleName(self, languageCode="en"):
+        return self.localisedStyleMapStyleName.get(languageCode)
+
+    def setStyleMapFamilyName(self, styleMapFamilyName, languageCode="en"):
+        self.localisedStyleMapFamilyName[languageCode] = tostr(styleMapFamilyName)
+
+    def getStyleMapFamilyName(self, languageCode="en"):
+        return self.localisedStyleMapFamilyName.get(languageCode)
+
+
+def tagForAxisName(name):
+    # try to find or make a tag name for this axis name
+    names = {
+        'weight':   ('wght', dict(en = 'Weight')),
+        'width':    ('wdth', dict(en = 'Width')),
+        'optical':  ('opsz', dict(en = 'Optical Size')),
+        'slant':    ('slnt', dict(en = 'Slant')),
+        'italic':   ('ital', dict(en = 'Italic')),
+    }
+    if name.lower() in names:
+        return names[name.lower()]
+    if len(name) < 4:
+        tag = name + "*" * (4 - len(name))
+    else:
+        tag = name[:4]
+    return tag, dict(en=name)
+
+
+class AxisDescriptor(SimpleDescriptor):
+    """ Simple container for the axis data
+        Add more localisations?
+    """
+    flavor = "axis"
+    _attrs = ['tag', 'name', 'maximum', 'minimum', 'default', 'map']
+
+    def __init__(
+        self,
+        *,
+        tag=None,
+        name=None,
+        labelNames=None,
+        minimum=None,
+        default=None,
+        maximum=None,
+        hidden=False,
+        map=None,
+    ):
+        # opentype tag for this axis
+        self.tag = tag
+        # name of the axis used in locations
+        self.name = name
+        # names for UI purposes, if this is not a standard axis,
+        self.labelNames = labelNames or {}
+        self.minimum = minimum
+        self.maximum = maximum
+        self.default = default
+        self.hidden = hidden
+        self.map = map or []
+
+    def serialize(self):
+        # output to a dict, used in testing
+        return dict(
+            tag=self.tag,
+            name=self.name,
+            labelNames=self.labelNames,
+            maximum=self.maximum,
+            minimum=self.minimum,
+            default=self.default,
+            hidden=self.hidden,
+            map=self.map,
+        )
+
+    def map_forward(self, v):
+        from fontTools.varLib.models import piecewiseLinearMap
+
+        if not self.map:
+            return v
+        return piecewiseLinearMap(v, {k: v for k, v in self.map})
+
+    def map_backward(self, v):
+        from fontTools.varLib.models import piecewiseLinearMap
+
+        if not self.map:
+            return v
+        return piecewiseLinearMap(v, {v: k for k, v in self.map})
+
+
+class BaseDocWriter(object):
+    _whiteSpace = "    "
+    ruleDescriptorClass = RuleDescriptor
+    axisDescriptorClass = AxisDescriptor
+    sourceDescriptorClass = SourceDescriptor
+    instanceDescriptorClass = InstanceDescriptor
+
+    @classmethod
+    def getAxisDecriptor(cls):
+        return cls.axisDescriptorClass()
+
+    @classmethod
+    def getSourceDescriptor(cls):
+        return cls.sourceDescriptorClass()
+
+    @classmethod
+    def getInstanceDescriptor(cls):
+        return cls.instanceDescriptorClass()
+
+    @classmethod
+    def getRuleDescriptor(cls):
+        return cls.ruleDescriptorClass()
+
+    def __init__(self, documentPath, documentObject):
+        self.path = documentPath
+        self.documentObject = documentObject
+        self.documentVersion = "4.1"
+        self.root = ET.Element("designspace")
+        self.root.attrib['format'] = self.documentVersion
+        self._axes = []     # for use by the writer only
+        self._rules = []    # for use by the writer only
+
+    def write(self, pretty=True, encoding="UTF-8", xml_declaration=True):
+        if self.documentObject.axes:
+            self.root.append(ET.Element("axes"))
+        for axisObject in self.documentObject.axes:
+            self._addAxis(axisObject)
+
+        if self.documentObject.rules:
+            if getattr(self.documentObject, "rulesProcessingLast", False):
+                attributes = {"processing": "last"}
+            else:
+                attributes = {}
+            self.root.append(ET.Element("rules", attributes))
+        for ruleObject in self.documentObject.rules:
+            self._addRule(ruleObject)
+
+        if self.documentObject.sources:
+            self.root.append(ET.Element("sources"))
+        for sourceObject in self.documentObject.sources:
+            self._addSource(sourceObject)
+
+        if self.documentObject.instances:
+            self.root.append(ET.Element("instances"))
+        for instanceObject in self.documentObject.instances:
+            self._addInstance(instanceObject)
+
+        if self.documentObject.lib:
+            self._addLib(self.documentObject.lib)
+
+        tree = ET.ElementTree(self.root)
+        tree.write(
+            self.path,
+            encoding=encoding,
+            method='xml',
+            xml_declaration=xml_declaration,
+            pretty_print=pretty,
+        )
+
+    def _makeLocationElement(self, locationObject, name=None):
+        """ Convert Location dict to a locationElement."""
+        locElement = ET.Element("location")
+        if name is not None:
+            locElement.attrib['name'] = name
+        validatedLocation = self.documentObject.newDefaultLocation()
+        for axisName, axisValue in locationObject.items():
+            if axisName in validatedLocation:
+                # only accept values we know
+                validatedLocation[axisName] = axisValue
+        for dimensionName, dimensionValue in validatedLocation.items():
+            dimElement = ET.Element('dimension')
+            dimElement.attrib['name'] = dimensionName
+            if type(dimensionValue) == tuple:
+                dimElement.attrib['xvalue'] = self.intOrFloat(dimensionValue[0])
+                dimElement.attrib['yvalue'] = self.intOrFloat(dimensionValue[1])
+            else:
+                dimElement.attrib['xvalue'] = self.intOrFloat(dimensionValue)
+            locElement.append(dimElement)
+        return locElement, validatedLocation
+
+    def intOrFloat(self, num):
+        if int(num) == num:
+            return "%d" % num
+        return "%f" % num
+
+    def _addRule(self, ruleObject):
+        # if none of the conditions have minimum or maximum values, do not add the rule.
+        self._rules.append(ruleObject)
+        ruleElement = ET.Element('rule')
+        if ruleObject.name is not None:
+            ruleElement.attrib['name'] = ruleObject.name
+        for conditions in ruleObject.conditionSets:
+            conditionsetElement = ET.Element('conditionset')
+            for cond in conditions:
+                if cond.get('minimum') is None and cond.get('maximum') is None:
+                    # neither is defined, don't add this condition
+                    continue
+                conditionElement = ET.Element('condition')
+                conditionElement.attrib['name'] = cond.get('name')
+                if cond.get('minimum') is not None:
+                    conditionElement.attrib['minimum'] = self.intOrFloat(cond.get('minimum'))
+                if cond.get('maximum') is not None:
+                    conditionElement.attrib['maximum'] = self.intOrFloat(cond.get('maximum'))
+                conditionsetElement.append(conditionElement)
+            if len(conditionsetElement):
+                ruleElement.append(conditionsetElement)
+        for sub in ruleObject.subs:
+            subElement = ET.Element('sub')
+            subElement.attrib['name'] = sub[0]
+            subElement.attrib['with'] = sub[1]
+            ruleElement.append(subElement)
+        if len(ruleElement):
+            self.root.findall('.rules')[0].append(ruleElement)
+
+    def _addAxis(self, axisObject):
+        self._axes.append(axisObject)
+        axisElement = ET.Element('axis')
+        axisElement.attrib['tag'] = axisObject.tag
+        axisElement.attrib['name'] = axisObject.name
+        axisElement.attrib['minimum'] = self.intOrFloat(axisObject.minimum)
+        axisElement.attrib['maximum'] = self.intOrFloat(axisObject.maximum)
+        axisElement.attrib['default'] = self.intOrFloat(axisObject.default)
+        if axisObject.hidden:
+            axisElement.attrib['hidden'] = "1"
+        for languageCode, labelName in sorted(axisObject.labelNames.items()):
+            languageElement = ET.Element('labelname')
+            languageElement.attrib[XML_LANG] = languageCode
+            languageElement.text = labelName
+            axisElement.append(languageElement)
+        if axisObject.map:
+            for inputValue, outputValue in axisObject.map:
+                mapElement = ET.Element('map')
+                mapElement.attrib['input'] = self.intOrFloat(inputValue)
+                mapElement.attrib['output'] = self.intOrFloat(outputValue)
+                axisElement.append(mapElement)
+        self.root.findall('.axes')[0].append(axisElement)
+
+    def _addInstance(self, instanceObject):
+        instanceElement = ET.Element('instance')
+        if instanceObject.name is not None:
+            instanceElement.attrib['name'] = instanceObject.name
+        if instanceObject.familyName is not None:
+            instanceElement.attrib['familyname'] = instanceObject.familyName
+        if instanceObject.styleName is not None:
+            instanceElement.attrib['stylename'] = instanceObject.styleName
+        # add localisations
+        if instanceObject.localisedStyleName:
+            languageCodes = list(instanceObject.localisedStyleName.keys())
+            languageCodes.sort()
+            for code in languageCodes:
+                if code == "en":
+                    continue  # already stored in the element attribute
+                localisedStyleNameElement = ET.Element('stylename')
+                localisedStyleNameElement.attrib[XML_LANG] = code
+                localisedStyleNameElement.text = instanceObject.getStyleName(code)
+                instanceElement.append(localisedStyleNameElement)
+        if instanceObject.localisedFamilyName:
+            languageCodes = list(instanceObject.localisedFamilyName.keys())
+            languageCodes.sort()
+            for code in languageCodes:
+                if code == "en":
+                    continue  # already stored in the element attribute
+                localisedFamilyNameElement = ET.Element('familyname')
+                localisedFamilyNameElement.attrib[XML_LANG] = code
+                localisedFamilyNameElement.text = instanceObject.getFamilyName(code)
+                instanceElement.append(localisedFamilyNameElement)
+        if instanceObject.localisedStyleMapStyleName:
+            languageCodes = list(instanceObject.localisedStyleMapStyleName.keys())
+            languageCodes.sort()
+            for code in languageCodes:
+                if code == "en":
+                    continue
+                localisedStyleMapStyleNameElement = ET.Element('stylemapstylename')
+                localisedStyleMapStyleNameElement.attrib[XML_LANG] = code
+                localisedStyleMapStyleNameElement.text = instanceObject.getStyleMapStyleName(code)
+                instanceElement.append(localisedStyleMapStyleNameElement)
+        if instanceObject.localisedStyleMapFamilyName:
+            languageCodes = list(instanceObject.localisedStyleMapFamilyName.keys())
+            languageCodes.sort()
+            for code in languageCodes:
+                if code == "en":
+                    continue
+                localisedStyleMapFamilyNameElement = ET.Element('stylemapfamilyname')
+                localisedStyleMapFamilyNameElement.attrib[XML_LANG] = code
+                localisedStyleMapFamilyNameElement.text = instanceObject.getStyleMapFamilyName(code)
+                instanceElement.append(localisedStyleMapFamilyNameElement)
+
+        if instanceObject.location is not None:
+            locationElement, instanceObject.location = self._makeLocationElement(instanceObject.location)
+            instanceElement.append(locationElement)
+        if instanceObject.filename is not None:
+            instanceElement.attrib['filename'] = instanceObject.filename
+        if instanceObject.postScriptFontName is not None:
+            instanceElement.attrib['postscriptfontname'] = instanceObject.postScriptFontName
+        if instanceObject.styleMapFamilyName is not None:
+            instanceElement.attrib['stylemapfamilyname'] = instanceObject.styleMapFamilyName
+        if instanceObject.styleMapStyleName is not None:
+            instanceElement.attrib['stylemapstylename'] = instanceObject.styleMapStyleName
+        if instanceObject.glyphs:
+            if instanceElement.findall('.glyphs') == []:
+                glyphsElement = ET.Element('glyphs')
+                instanceElement.append(glyphsElement)
+            glyphsElement = instanceElement.findall('.glyphs')[0]
+            for glyphName, data in sorted(instanceObject.glyphs.items()):
+                glyphElement = self._writeGlyphElement(instanceElement, instanceObject, glyphName, data)
+                glyphsElement.append(glyphElement)
+        if instanceObject.kerning:
+            kerningElement = ET.Element('kerning')
+            instanceElement.append(kerningElement)
+        if instanceObject.info:
+            infoElement = ET.Element('info')
+            instanceElement.append(infoElement)
+        if instanceObject.lib:
+            libElement = ET.Element('lib')
+            libElement.append(plistlib.totree(instanceObject.lib, indent_level=4))
+            instanceElement.append(libElement)
+        self.root.findall('.instances')[0].append(instanceElement)
+
+    def _addSource(self, sourceObject):
+        sourceElement = ET.Element("source")
+        if sourceObject.filename is not None:
+            sourceElement.attrib['filename'] = sourceObject.filename
+        if sourceObject.name is not None:
+            if sourceObject.name.find("temp_master") != 0:
+                # do not save temporary source names
+                sourceElement.attrib['name'] = sourceObject.name
+        if sourceObject.familyName is not None:
+            sourceElement.attrib['familyname'] = sourceObject.familyName
+        if sourceObject.styleName is not None:
+            sourceElement.attrib['stylename'] = sourceObject.styleName
+        if sourceObject.layerName is not None:
+            sourceElement.attrib['layer'] = sourceObject.layerName
+        if sourceObject.copyLib:
+            libElement = ET.Element('lib')
+            libElement.attrib['copy'] = "1"
+            sourceElement.append(libElement)
+        if sourceObject.copyGroups:
+            groupsElement = ET.Element('groups')
+            groupsElement.attrib['copy'] = "1"
+            sourceElement.append(groupsElement)
+        if sourceObject.copyFeatures:
+            featuresElement = ET.Element('features')
+            featuresElement.attrib['copy'] = "1"
+            sourceElement.append(featuresElement)
+        if sourceObject.copyInfo or sourceObject.muteInfo:
+            infoElement = ET.Element('info')
+            if sourceObject.copyInfo:
+                infoElement.attrib['copy'] = "1"
+            if sourceObject.muteInfo:
+                infoElement.attrib['mute'] = "1"
+            sourceElement.append(infoElement)
+        if sourceObject.muteKerning:
+            kerningElement = ET.Element("kerning")
+            kerningElement.attrib["mute"] = '1'
+            sourceElement.append(kerningElement)
+        if sourceObject.mutedGlyphNames:
+            for name in sourceObject.mutedGlyphNames:
+                glyphElement = ET.Element("glyph")
+                glyphElement.attrib["name"] = name
+                glyphElement.attrib["mute"] = '1'
+                sourceElement.append(glyphElement)
+        locationElement, sourceObject.location = self._makeLocationElement(sourceObject.location)
+        sourceElement.append(locationElement)
+        self.root.findall('.sources')[0].append(sourceElement)
+
+    def _addLib(self, dict):
+        libElement = ET.Element('lib')
+        libElement.append(plistlib.totree(dict, indent_level=2))
+        self.root.append(libElement)
+
+    def _writeGlyphElement(self, instanceElement, instanceObject, glyphName, data):
+        glyphElement = ET.Element('glyph')
+        if data.get('mute'):
+            glyphElement.attrib['mute'] = "1"
+        if data.get('unicodes') is not None:
+            glyphElement.attrib['unicode'] = " ".join([hex(u) for u in data.get('unicodes')])
+        if data.get('instanceLocation') is not None:
+            locationElement, data['instanceLocation'] = self._makeLocationElement(data.get('instanceLocation'))
+            glyphElement.append(locationElement)
+        if glyphName is not None:
+            glyphElement.attrib['name'] = glyphName
+        if data.get('note') is not None:
+            noteElement = ET.Element('note')
+            noteElement.text = data.get('note')
+            glyphElement.append(noteElement)
+        if data.get('masters') is not None:
+            mastersElement = ET.Element("masters")
+            for m in data.get('masters'):
+                masterElement = ET.Element("master")
+                if m.get('glyphName') is not None:
+                    masterElement.attrib['glyphname'] = m.get('glyphName')
+                if m.get('font') is not None:
+                    masterElement.attrib['source'] = m.get('font')
+                if m.get('location') is not None:
+                    locationElement, m['location'] = self._makeLocationElement(m.get('location'))
+                    masterElement.append(locationElement)
+                mastersElement.append(masterElement)
+            glyphElement.append(mastersElement)
+        return glyphElement
+
+
+class BaseDocReader(LogMixin):
+    ruleDescriptorClass = RuleDescriptor
+    axisDescriptorClass = AxisDescriptor
+    sourceDescriptorClass = SourceDescriptor
+    instanceDescriptorClass = InstanceDescriptor
+
+    def __init__(self, documentPath, documentObject):
+        self.path = documentPath
+        self.documentObject = documentObject
+        tree = ET.parse(self.path)
+        self.root = tree.getroot()
+        self.documentObject.formatVersion = self.root.attrib.get("format", "3.0")
+        self._axes = []
+        self.rules = []
+        self.sources = []
+        self.instances = []
+        self.axisDefaults = {}
+        self._strictAxisNames = True
+
+    @classmethod
+    def fromstring(cls, string, documentObject):
+        f = BytesIO(tobytes(string, encoding="utf-8"))
+        self = cls(f, documentObject)
+        self.path = None
+        return self
+
+    def read(self):
+        self.readAxes()
+        self.readRules()
+        self.readSources()
+        self.readInstances()
+        self.readLib()
+
+    def readRules(self):
+        # we also need to read any conditions that are outside of a condition set.
+        rules = []
+        rulesElement = self.root.find(".rules")
+        if rulesElement is not None:
+            processingValue = rulesElement.attrib.get("processing", "first")
+            if processingValue not in {"first", "last"}:
+                raise DesignSpaceDocumentError(
+                    "<rules> processing attribute value is not valid: %r, "
+                    "expected 'first' or 'last'" % processingValue)
+            self.documentObject.rulesProcessingLast = processingValue == "last"
+        for ruleElement in self.root.findall(".rules/rule"):
+            ruleObject = self.ruleDescriptorClass()
+            ruleName = ruleObject.name = ruleElement.attrib.get("name")
+            # read any stray conditions outside a condition set
+            externalConditions = self._readConditionElements(
+                ruleElement,
+                ruleName,
+            )
+            if externalConditions:
+                ruleObject.conditionSets.append(externalConditions)
+                self.log.info(
+                    "Found stray rule conditions outside a conditionset. "
+                    "Wrapped them in a new conditionset."
+                )
+            # read the conditionsets
+            for conditionSetElement in ruleElement.findall('.conditionset'):
+                conditionSet = self._readConditionElements(
+                    conditionSetElement,
+                    ruleName,
+                )
+                if conditionSet is not None:
+                    ruleObject.conditionSets.append(conditionSet)
+            for subElement in ruleElement.findall('.sub'):
+                a = subElement.attrib['name']
+                b = subElement.attrib['with']
+                ruleObject.subs.append((a, b))
+            rules.append(ruleObject)
+        self.documentObject.rules = rules
+
+    def _readConditionElements(self, parentElement, ruleName=None):
+        cds = []
+        for conditionElement in parentElement.findall('.condition'):
+            cd = {}
+            cdMin = conditionElement.attrib.get("minimum")
+            if cdMin is not None:
+                cd['minimum'] = float(cdMin)
+            else:
+                # will allow these to be None, assume axis.minimum
+                cd['minimum'] = None
+            cdMax = conditionElement.attrib.get("maximum")
+            if cdMax is not None:
+                cd['maximum'] = float(cdMax)
+            else:
+                # will allow these to be None, assume axis.maximum
+                cd['maximum'] = None
+            cd['name'] = conditionElement.attrib.get("name")
+            # # test for things
+            if cd.get('minimum') is None and cd.get('maximum') is None:
+                raise DesignSpaceDocumentError(
+                    "condition missing required minimum or maximum in rule" +
+                    (" '%s'" % ruleName if ruleName is not None else ""))
+            cds.append(cd)
+        return cds
+
+    def readAxes(self):
+        # read the axes elements, including the warp map.
+        axisElements = self.root.findall(".axes/axis")
+        if not axisElements:
+            return
+        for axisElement in axisElements:
+            axisObject = self.axisDescriptorClass()
+            axisObject.name = axisElement.attrib.get("name")
+            axisObject.minimum = float(axisElement.attrib.get("minimum"))
+            axisObject.maximum = float(axisElement.attrib.get("maximum"))
+            if axisElement.attrib.get('hidden', False):
+                axisObject.hidden = True
+            axisObject.default = float(axisElement.attrib.get("default"))
+            axisObject.tag = axisElement.attrib.get("tag")
+            for mapElement in axisElement.findall('map'):
+                a = float(mapElement.attrib['input'])
+                b = float(mapElement.attrib['output'])
+                axisObject.map.append((a, b))
+            for labelNameElement in axisElement.findall('labelname'):
+                # Note: elementtree reads the "xml:lang" attribute name as
+                # '{http://www.w3.org/XML/1998/namespace}lang'
+                for key, lang in labelNameElement.items():
+                    if key == XML_LANG:
+                        axisObject.labelNames[lang] = tostr(labelNameElement.text)
+            self.documentObject.axes.append(axisObject)
+            self.axisDefaults[axisObject.name] = axisObject.default
+
+    def readSources(self):
+        for sourceCount, sourceElement in enumerate(self.root.findall(".sources/source")):
+            filename = sourceElement.attrib.get('filename')
+            if filename is not None and self.path is not None:
+                sourcePath = os.path.abspath(os.path.join(os.path.dirname(self.path), filename))
+            else:
+                sourcePath = None
+            sourceName = sourceElement.attrib.get('name')
+            if sourceName is None:
+                # add a temporary source name
+                sourceName = "temp_master.%d" % (sourceCount)
+            sourceObject = self.sourceDescriptorClass()
+            sourceObject.path = sourcePath        # absolute path to the ufo source
+            sourceObject.filename = filename      # path as it is stored in the document
+            sourceObject.name = sourceName
+            familyName = sourceElement.attrib.get("familyname")
+            if familyName is not None:
+                sourceObject.familyName = familyName
+            styleName = sourceElement.attrib.get("stylename")
+            if styleName is not None:
+                sourceObject.styleName = styleName
+            sourceObject.location = self.locationFromElement(sourceElement)
+            layerName = sourceElement.attrib.get('layer')
+            if layerName is not None:
+                sourceObject.layerName = layerName
+            for libElement in sourceElement.findall('.lib'):
+                if libElement.attrib.get('copy') == '1':
+                    sourceObject.copyLib = True
+            for groupsElement in sourceElement.findall('.groups'):
+                if groupsElement.attrib.get('copy') == '1':
+                    sourceObject.copyGroups = True
+            for infoElement in sourceElement.findall(".info"):
+                if infoElement.attrib.get('copy') == '1':
+                    sourceObject.copyInfo = True
+                if infoElement.attrib.get('mute') == '1':
+                    sourceObject.muteInfo = True
+            for featuresElement in sourceElement.findall(".features"):
+                if featuresElement.attrib.get('copy') == '1':
+                    sourceObject.copyFeatures = True
+            for glyphElement in sourceElement.findall(".glyph"):
+                glyphName = glyphElement.attrib.get('name')
+                if glyphName is None:
+                    continue
+                if glyphElement.attrib.get('mute') == '1':
+                    sourceObject.mutedGlyphNames.append(glyphName)
+            for kerningElement in sourceElement.findall(".kerning"):
+                if kerningElement.attrib.get('mute') == '1':
+                    sourceObject.muteKerning = True
+            self.documentObject.sources.append(sourceObject)
+
+    def locationFromElement(self, element):
+        elementLocation = None
+        for locationElement in element.findall('.location'):
+            elementLocation = self.readLocationElement(locationElement)
+            break
+        return elementLocation
+
+    def readLocationElement(self, locationElement):
+        """ Format 0 location reader """
+        if self._strictAxisNames and not self.documentObject.axes:
+            raise DesignSpaceDocumentError("No axes defined")
+        loc = {}
+        for dimensionElement in locationElement.findall(".dimension"):
+            dimName = dimensionElement.attrib.get("name")
+            if self._strictAxisNames and dimName not in self.axisDefaults:
+                # In case the document contains no axis definitions,
+                self.log.warning("Location with undefined axis: \"%s\".", dimName)
+                continue
+            xValue = yValue = None
+            try:
+                xValue = dimensionElement.attrib.get('xvalue')
+                xValue = float(xValue)
+            except ValueError:
+                self.log.warning("KeyError in readLocation xValue %3.3f", xValue)
+            try:
+                yValue = dimensionElement.attrib.get('yvalue')
+                if yValue is not None:
+                    yValue = float(yValue)
+            except ValueError:
+                pass
+            if yValue is not None:
+                loc[dimName] = (xValue, yValue)
+            else:
+                loc[dimName] = xValue
+        return loc
+
+    def readInstances(self, makeGlyphs=True, makeKerning=True, makeInfo=True):
+        instanceElements = self.root.findall('.instances/instance')
+        for instanceElement in instanceElements:
+            self._readSingleInstanceElement(instanceElement, makeGlyphs=makeGlyphs, makeKerning=makeKerning, makeInfo=makeInfo)
+
+    def _readSingleInstanceElement(self, instanceElement, makeGlyphs=True, makeKerning=True, makeInfo=True):
+        filename = instanceElement.attrib.get('filename')
+        if filename is not None and self.documentObject.path is not None:
+            instancePath = os.path.join(os.path.dirname(self.documentObject.path), filename)
+        else:
+            instancePath = None
+        instanceObject = self.instanceDescriptorClass()
+        instanceObject.path = instancePath    # absolute path to the instance
+        instanceObject.filename = filename    # path as it is stored in the document
+        name = instanceElement.attrib.get("name")
+        if name is not None:
+            instanceObject.name = name
+        familyname = instanceElement.attrib.get('familyname')
+        if familyname is not None:
+            instanceObject.familyName = familyname
+        stylename = instanceElement.attrib.get('stylename')
+        if stylename is not None:
+            instanceObject.styleName = stylename
+        postScriptFontName = instanceElement.attrib.get('postscriptfontname')
+        if postScriptFontName is not None:
+            instanceObject.postScriptFontName = postScriptFontName
+        styleMapFamilyName = instanceElement.attrib.get('stylemapfamilyname')
+        if styleMapFamilyName is not None:
+            instanceObject.styleMapFamilyName = styleMapFamilyName
+        styleMapStyleName = instanceElement.attrib.get('stylemapstylename')
+        if styleMapStyleName is not None:
+            instanceObject.styleMapStyleName = styleMapStyleName
+        # read localised names
+        for styleNameElement in instanceElement.findall('stylename'):
+            for key, lang in styleNameElement.items():
+                if key == XML_LANG:
+                    styleName = styleNameElement.text
+                    instanceObject.setStyleName(styleName, lang)
+        for familyNameElement in instanceElement.findall('familyname'):
+            for key, lang in familyNameElement.items():
+                if key == XML_LANG:
+                    familyName = familyNameElement.text
+                    instanceObject.setFamilyName(familyName, lang)
+        for styleMapStyleNameElement in instanceElement.findall('stylemapstylename'):
+            for key, lang in styleMapStyleNameElement.items():
+                if key == XML_LANG:
+                    styleMapStyleName = styleMapStyleNameElement.text
+                    instanceObject.setStyleMapStyleName(styleMapStyleName, lang)
+        for styleMapFamilyNameElement in instanceElement.findall('stylemapfamilyname'):
+            for key, lang in styleMapFamilyNameElement.items():
+                if key == XML_LANG:
+                    styleMapFamilyName = styleMapFamilyNameElement.text
+                    instanceObject.setStyleMapFamilyName(styleMapFamilyName, lang)
+        instanceLocation = self.locationFromElement(instanceElement)
+        if instanceLocation is not None:
+            instanceObject.location = instanceLocation
+        for glyphElement in instanceElement.findall('.glyphs/glyph'):
+            self.readGlyphElement(glyphElement, instanceObject)
+        for infoElement in instanceElement.findall("info"):
+            self.readInfoElement(infoElement, instanceObject)
+        for libElement in instanceElement.findall('lib'):
+            self.readLibElement(libElement, instanceObject)
+        self.documentObject.instances.append(instanceObject)
+
+    def readLibElement(self, libElement, instanceObject):
+        """Read the lib element for the given instance."""
+        instanceObject.lib = plistlib.fromtree(libElement[0])
+
+    def readInfoElement(self, infoElement, instanceObject):
+        """ Read the info element."""
+        instanceObject.info = True
+
+    def readKerningElement(self, kerningElement, instanceObject):
+        """ Read the kerning element."""
+        kerningLocation = self.locationFromElement(kerningElement)
+        instanceObject.addKerning(kerningLocation)
+
+    def readGlyphElement(self, glyphElement, instanceObject):
+        """
+        Read the glyph element.
+            <glyph name="b" unicode="0x62"/>
+            <glyph name="b"/>
+            <glyph name="b">
+                <master location="location-token-bbb" source="master-token-aaa2"/>
+                <master glyphname="b.alt1" location="location-token-ccc" source="master-token-aaa3"/>
+                <note>
+                    This is an instance from an anisotropic interpolation.
+                </note>
+            </glyph>
+        """
+        glyphData = {}
+        glyphName = glyphElement.attrib.get('name')
+        if glyphName is None:
+            raise DesignSpaceDocumentError("Glyph object without name attribute")
+        mute = glyphElement.attrib.get("mute")
+        if mute == "1":
+            glyphData['mute'] = True
+        # unicode
+        unicodes = glyphElement.attrib.get('unicode')
+        if unicodes is not None:
+            try:
+                unicodes = [int(u, 16) for u in unicodes.split(" ")]
+                glyphData['unicodes'] = unicodes
+            except ValueError:
+                raise DesignSpaceDocumentError("unicode values %s are not integers" % unicodes)
+
+        for noteElement in glyphElement.findall('.note'):
+            glyphData['note'] = noteElement.text
+            break
+        instanceLocation = self.locationFromElement(glyphElement)
+        if instanceLocation is not None:
+            glyphData['instanceLocation'] = instanceLocation
+        glyphSources = None
+        for masterElement in glyphElement.findall('.masters/master'):
+            fontSourceName = masterElement.attrib.get('source')
+            sourceLocation = self.locationFromElement(masterElement)
+            masterGlyphName = masterElement.attrib.get('glyphname')
+            if masterGlyphName is None:
+                # if we don't read a glyphname, use the one we have
+                masterGlyphName = glyphName
+            d = dict(font=fontSourceName,
+                     location=sourceLocation,
+                     glyphName=masterGlyphName)
+            if glyphSources is None:
+                glyphSources = []
+            glyphSources.append(d)
+        if glyphSources is not None:
+            glyphData['masters'] = glyphSources
+        instanceObject.glyphs[glyphName] = glyphData
+
+    def readLib(self):
+        """Read the lib element for the whole document."""
+        for libElement in self.root.findall(".lib"):
+            self.documentObject.lib = plistlib.fromtree(libElement[0])
+
+
+class DesignSpaceDocument(LogMixin, AsDictMixin):
+    """ Read, write data from the designspace file"""
+    def __init__(self, readerClass=None, writerClass=None):
+        self.path = None
+        self.filename = None
+        """String, optional. When the document is read from the disk, this is
+        its original file name, i.e. the last part of its path.
+
+        When the document is produced by a Python script and still only exists
+        in memory, the producing script can write here an indication of a
+        possible "good" filename, in case one wants to save the file somewhere.
+        """
+
+        self.formatVersion = None
+        self.sources = []
+        self.instances = []
+        self.axes = []
+        self.rules = []
+        self.rulesProcessingLast = False
+        self.default = None         # name of the default master
+
+        self.lib = {}
+        """Custom data associated with the whole document."""
+
+        #
+        if readerClass is not None:
+            self.readerClass = readerClass
+        else:
+            self.readerClass = BaseDocReader
+        if writerClass is not None:
+            self.writerClass = writerClass
+        else:
+            self.writerClass = BaseDocWriter
+
+    @classmethod
+    def fromfile(cls, path, readerClass=None, writerClass=None):
+        self = cls(readerClass=readerClass, writerClass=writerClass)
+        self.read(path)
+        return self
+
+    @classmethod
+    def fromstring(cls, string, readerClass=None, writerClass=None):
+        self = cls(readerClass=readerClass, writerClass=writerClass)
+        reader = self.readerClass.fromstring(string, self)
+        reader.read()
+        if self.sources:
+            self.findDefault()
+        return self
+
+    def tostring(self, encoding=None):
+        if encoding is str or (
+            encoding is not None and encoding.lower() == "unicode"
+        ):
+            f = StringIO()
+            xml_declaration = False
+        elif encoding is None or encoding == "utf-8":
+            f = BytesIO()
+            encoding = "UTF-8"
+            xml_declaration = True
+        else:
+            raise ValueError("unsupported encoding: '%s'" % encoding)
+        writer = self.writerClass(f, self)
+        writer.write(encoding=encoding, xml_declaration=xml_declaration)
+        return f.getvalue()
+
+    def read(self, path):
+        if hasattr(path, "__fspath__"):  # support os.PathLike objects
+            path = path.__fspath__()
+        self.path = path
+        self.filename = os.path.basename(path)
+        reader = self.readerClass(path, self)
+        reader.read()
+        if self.sources:
+            self.findDefault()
+
+    def write(self, path):
+        if hasattr(path, "__fspath__"):  # support os.PathLike objects
+            path = path.__fspath__()
+        self.path = path
+        self.filename = os.path.basename(path)
+        self.updatePaths()
+        writer = self.writerClass(path, self)
+        writer.write()
+
+    def _posixRelativePath(self, otherPath):
+        relative = os.path.relpath(otherPath, os.path.dirname(self.path))
+        return posix(relative)
+
+    def updatePaths(self):
+        """
+            Right before we save we need to identify and respond to the following situations:
+            In each descriptor, we have to do the right thing for the filename attribute.
+
+            case 1.
+            descriptor.filename == None
+            descriptor.path == None
+
+            -- action:
+            write as is, descriptors will not have a filename attr.
+            useless, but no reason to interfere.
+
+
+            case 2.
+            descriptor.filename == "../something"
+            descriptor.path == None
+
+            -- action:
+            write as is. The filename attr should not be touched.
+
+
+            case 3.
+            descriptor.filename == None
+            descriptor.path == "~/absolute/path/there"
+
+            -- action:
+            calculate the relative path for filename.
+            We're not overwriting some other value for filename, it should be fine
+
+
+            case 4.
+            descriptor.filename == '../somewhere'
+            descriptor.path == "~/absolute/path/there"
+
+            -- action:
+            there is a conflict between the given filename, and the path.
+            So we know where the file is relative to the document.
+            Can't guess why they're different, we just choose for path to be correct and update filename.
+
+
+        """
+        assert self.path is not None
+        for descriptor in self.sources + self.instances:
+            if descriptor.path is not None:
+                # case 3 and 4: filename gets updated and relativized
+                descriptor.filename = self._posixRelativePath(descriptor.path)
+
+    def addSource(self, sourceDescriptor):
+        self.sources.append(sourceDescriptor)
+
+    def addSourceDescriptor(self, **kwargs):
+        source = self.writerClass.sourceDescriptorClass(**kwargs)
+        self.addSource(source)
+        return source
+
+    def addInstance(self, instanceDescriptor):
+        self.instances.append(instanceDescriptor)
+
+    def addInstanceDescriptor(self, **kwargs):
+        instance = self.writerClass.instanceDescriptorClass(**kwargs)
+        self.addInstance(instance)
+        return instance
+
+    def addAxis(self, axisDescriptor):
+        self.axes.append(axisDescriptor)
+
+    def addAxisDescriptor(self, **kwargs):
+        axis = self.writerClass.axisDescriptorClass(**kwargs)
+        self.addAxis(axis)
+        return axis
+
+    def addRule(self, ruleDescriptor):
+        self.rules.append(ruleDescriptor)
+
+    def addRuleDescriptor(self, **kwargs):
+        rule = self.writerClass.ruleDescriptorClass(**kwargs)
+        self.addRule(rule)
+        return rule
+
+    def newDefaultLocation(self):
+        """Return default location in design space."""
+        # Without OrderedDict, output XML would be non-deterministic.
+        # https://github.com/LettError/designSpaceDocument/issues/10
+        loc = collections.OrderedDict()
+        for axisDescriptor in self.axes:
+            loc[axisDescriptor.name] = axisDescriptor.map_forward(
+                axisDescriptor.default
+            )
+        return loc
+
+    def updateFilenameFromPath(self, masters=True, instances=True, force=False):
+        # set a descriptor filename attr from the path and this document path
+        # if the filename attribute is not None: skip it.
+        if masters:
+            for descriptor in self.sources:
+                if descriptor.filename is not None and not force:
+                    continue
+                if self.path is not None:
+                    descriptor.filename = self._posixRelativePath(descriptor.path)
+        if instances:
+            for descriptor in self.instances:
+                if descriptor.filename is not None and not force:
+                    continue
+                if self.path is not None:
+                    descriptor.filename = self._posixRelativePath(descriptor.path)
+
+    def newAxisDescriptor(self):
+        # Ask the writer class to make us a new axisDescriptor
+        return self.writerClass.getAxisDecriptor()
+
+    def newSourceDescriptor(self):
+        # Ask the writer class to make us a new sourceDescriptor
+        return self.writerClass.getSourceDescriptor()
+
+    def newInstanceDescriptor(self):
+        # Ask the writer class to make us a new instanceDescriptor
+        return self.writerClass.getInstanceDescriptor()
+
+    def getAxisOrder(self):
+        names = []
+        for axisDescriptor in self.axes:
+            names.append(axisDescriptor.name)
+        return names
+
+    def getAxis(self, name):
+        for axisDescriptor in self.axes:
+            if axisDescriptor.name == name:
+                return axisDescriptor
+        return None
+
+    def findDefault(self):
+        """Set and return SourceDescriptor at the default location or None.
+
+        The default location is the set of all `default` values in user space
+        of all axes.
+        """
+        self.default = None
+
+        # Convert the default location from user space to design space before comparing
+        # it against the SourceDescriptor locations (always in design space).
+        default_location_design = self.newDefaultLocation()
+
+        for sourceDescriptor in self.sources:
+            if sourceDescriptor.location == default_location_design:
+                self.default = sourceDescriptor
+                return sourceDescriptor
+
+        return None
+
+    def normalizeLocation(self, location):
+        from fontTools.varLib.models import normalizeValue
+
+        new = {}
+        for axis in self.axes:
+            if axis.name not in location:
+                # skipping this dimension it seems
+                continue
+            value = location[axis.name]
+            # 'anisotropic' location, take first coord only
+            if isinstance(value, tuple):
+                value = value[0]
+            triple = [
+                axis.map_forward(v) for v in (axis.minimum, axis.default, axis.maximum)
+            ]
+            new[axis.name] = normalizeValue(value, triple)
+        return new
+
+    def normalize(self):
+        # Normalise the geometry of this designspace:
+        #   scale all the locations of all masters and instances to the -1 - 0 - 1 value.
+        #   we need the axis data to do the scaling, so we do those last.
+        # masters
+        for item in self.sources:
+            item.location = self.normalizeLocation(item.location)
+        # instances
+        for item in self.instances:
+            # glyph masters for this instance
+            for _, glyphData in item.glyphs.items():
+                glyphData['instanceLocation'] = self.normalizeLocation(glyphData['instanceLocation'])
+                for glyphMaster in glyphData['masters']:
+                    glyphMaster['location'] = self.normalizeLocation(glyphMaster['location'])
+            item.location = self.normalizeLocation(item.location)
+        # the axes
+        for axis in self.axes:
+            # scale the map first
+            newMap = []
+            for inputValue, outputValue in axis.map:
+                newOutputValue = self.normalizeLocation({axis.name: outputValue}).get(axis.name)
+                newMap.append((inputValue, newOutputValue))
+            if newMap:
+                axis.map = newMap
+            # finally the axis values
+            minimum = self.normalizeLocation({axis.name: axis.minimum}).get(axis.name)
+            maximum = self.normalizeLocation({axis.name: axis.maximum}).get(axis.name)
+            default = self.normalizeLocation({axis.name: axis.default}).get(axis.name)
+            # and set them in the axis.minimum
+            axis.minimum = minimum
+            axis.maximum = maximum
+            axis.default = default
+        # now the rules
+        for rule in self.rules:
+            newConditionSets = []
+            for conditions in rule.conditionSets:
+                newConditions = []
+                for cond in conditions:
+                    if cond.get('minimum') is not None:
+                        minimum = self.normalizeLocation({cond['name']: cond['minimum']}).get(cond['name'])
+                    else:
+                        minimum = None
+                    if cond.get('maximum') is not None:
+                        maximum = self.normalizeLocation({cond['name']: cond['maximum']}).get(cond['name'])
+                    else:
+                        maximum = None
+                    newConditions.append(dict(name=cond['name'], minimum=minimum, maximum=maximum))
+                newConditionSets.append(newConditions)
+            rule.conditionSets = newConditionSets
+
+    def loadSourceFonts(self, opener, **kwargs):
+        """Ensure SourceDescriptor.font attributes are loaded, and return list of fonts.
+
+        Takes a callable which initializes a new font object (e.g. TTFont, or
+        defcon.Font, etc.) from the SourceDescriptor.path, and sets the
+        SourceDescriptor.font attribute.
+        If the font attribute is already not None, it is not loaded again.
+        Fonts with the same path are only loaded once and shared among SourceDescriptors.
+
+        For example, to load UFO sources using defcon:
+
+            designspace = DesignSpaceDocument.fromfile("path/to/my.designspace")
+            designspace.loadSourceFonts(defcon.Font)
+
+        Or to load masters as FontTools binary fonts, including extra options:
+
+            designspace.loadSourceFonts(ttLib.TTFont, recalcBBoxes=False)
+
+        Args:
+            opener (Callable): takes one required positional argument, the source.path,
+                and an optional list of keyword arguments, and returns a new font object
+                loaded from the path.
+            **kwargs: extra options passed on to the opener function.
+
+        Returns:
+            List of font objects in the order they appear in the sources list.
+        """
+        # we load fonts with the same source.path only once
+        loaded = {}
+        fonts = []
+        for source in self.sources:
+            if source.font is not None:  # font already loaded
+                fonts.append(source.font)
+                continue
+            if source.path in loaded:
+                source.font = loaded[source.path]
+            else:
+                if source.path is None:
+                    raise DesignSpaceDocumentError(
+                        "Designspace source '%s' has no 'path' attribute"
+                        % (source.name or "<Unknown>")
+                    )
+                source.font = opener(source.path, **kwargs)
+                loaded[source.path] = source.font
+            fonts.append(source.font)
+        return fonts
diff --git a/Lib/fontTools/encodings/MacRoman.py b/Lib/fontTools/encodings/MacRoman.py
index 43c58eb..25232d3 100644
--- a/Lib/fontTools/encodings/MacRoman.py
+++ b/Lib/fontTools/encodings/MacRoman.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
 MacRoman = [
 		'NUL', 'Eth', 'eth', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Yacute',
 		'yacute', 'HT', 'LF', 'Thorn', 'thorn', 'CR', 'Zcaron', 'zcaron', 'DLE', 'DC1',
diff --git a/Lib/fontTools/encodings/StandardEncoding.py b/Lib/fontTools/encodings/StandardEncoding.py
index dc01ef8..810b2a0 100644
--- a/Lib/fontTools/encodings/StandardEncoding.py
+++ b/Lib/fontTools/encodings/StandardEncoding.py
@@ -1,6 +1,3 @@
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
-
 StandardEncoding = [
 		'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
 		'.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
diff --git a/Lib/fontTools/encodings/__init__.py b/Lib/fontTools/encodings/__init__.py
index 3f9abc9..156cb23 100644
--- a/Lib/fontTools/encodings/__init__.py
+++ b/Lib/fontTools/encodings/__init__.py
@@ -1,4 +1 @@
 """Empty __init__.py file to signal Python this directory is a package."""
-
-from __future__ import print_function, division, absolute_import
-from fontTools.misc.py23 import *
diff --git a/Lib/fontTools/encodings/codecs.py b/Lib/fontTools/encodings/codecs.py
index 30e4691..3b1a825 100644
--- a/Lib/fontTools/encodings/codecs.py
+++ b/Lib/fontTools/encodings/codecs.py
@@ -1,8 +1,6 @@
 """Extend the Python codecs module with a few encodings that are used in OpenType (name table)
-but missing from Python.  See https://github.com/behdad/fonttool