├── tests
├── test_files
│ ├── text
│ │ ├── byte.txt
│ │ ├── test.txt
│ │ └── kb.txt
│ └── fonts
│ │ ├── OpenSans-VF.ttf
│ │ ├── Roboto-Regular.ttf
│ │ ├── Ubuntu-Regular.ttf
│ │ ├── NotoSans-Regular.ttf
│ │ ├── NotoSans-Regular-dehinted.ttf
│ │ ├── Roboto-Regular-dehinted.ttf
│ │ ├── README.md
│ │ └── licenses
│ │ ├── OpenSans-License.txt
│ │ ├── Noto-License.txt
│ │ ├── Ubuntu-License.txt
│ │ └── Roboto-License.txt
├── test_bitops.py
├── test_system.py
├── test_paths.py
├── test_font.py
└── test_main.py
├── MANIFEST.in
├── tox.ini
├── .github
├── dependabot.yml
└── workflows
│ ├── py-typecheck.yml
│ ├── py-lint.yml
│ ├── publish-release.yml
│ ├── py-ci.yml
│ ├── py-coverage.yml
│ └── codeql-analysis.yml
├── requirements.txt
├── docs
└── DEVDOCS.md
├── setup.cfg
├── codecov.yml
├── lib
└── dehinter
│ ├── __init__.py
│ ├── bitops.py
│ ├── paths.py
│ ├── system.py
│ ├── __main__.py
│ └── font.py
├── Makefile
├── .gitignore
├── setup.py
├── CHANGELOG.md
├── README.md
└── LICENSE
/tests/test_files/text/byte.txt:
--------------------------------------------------------------------------------
1 | c
--------------------------------------------------------------------------------
/tests/test_files/text/test.txt:
--------------------------------------------------------------------------------
1 | test file
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG.md
2 | include LICENSE
3 | include README.md
4 |
5 | include *requirements.txt
--------------------------------------------------------------------------------
/tests/test_files/fonts/OpenSans-VF.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/OpenSans-VF.ttf
--------------------------------------------------------------------------------
/tests/test_files/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/tests/test_files/fonts/Ubuntu-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/Ubuntu-Regular.ttf
--------------------------------------------------------------------------------
/tests/test_files/fonts/NotoSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/NotoSans-Regular.ttf
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py310
3 |
4 | [testenv]
5 | commands =
6 | py.test
7 | deps =
8 | -rrequirements.txt
9 | pytest
10 |
--------------------------------------------------------------------------------
/tests/test_files/fonts/NotoSans-Regular-dehinted.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/NotoSans-Regular-dehinted.ttf
--------------------------------------------------------------------------------
/tests/test_files/fonts/Roboto-Regular-dehinted.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-foundry/dehinter/HEAD/tests/test_files/fonts/Roboto-Regular-dehinted.ttf
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: pip
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This file is autogenerated by pip-compile
3 | # To update, run:
4 | #
5 | # pip-compile
6 | #
7 | fonttools==4.38.0 # via dehinter (setup.py)
8 |
--------------------------------------------------------------------------------
/docs/DEVDOCS.md:
--------------------------------------------------------------------------------
1 | # Developer Documentation
2 |
3 | ## Versioning
4 |
5 | Version numbers are set in the `dehinter.__init__.py` file and read into the `setup.py` file and the executable from this file.
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 0
3 |
4 | [tool:pytest]
5 | filterwarnings =
6 | ignore:fromstring:DeprecationWarning
7 | ignore:tostring:DeprecationWarning
8 |
9 | [flake8]
10 | max-line-length = 90
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | max_report_age: off
3 |
4 | coverage:
5 | status:
6 | project:
7 | default:
8 | # basic
9 | target: auto
10 | threshold: 2%
11 | base: auto
12 |
13 | comment: off
14 |
--------------------------------------------------------------------------------
/tests/test_bitops.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from dehinter.bitops import is_bit_k_set, clear_bit_k, set_bit_k
4 |
5 |
6 | def test_is_bit_k_set_true():
7 | assert is_bit_k_set(15, 1) is True
8 |
9 |
10 | def test_is_bit_k_set_false():
11 | assert is_bit_k_set(13, 1) is False
12 |
13 |
14 | def test_clear_bit_k():
15 | # clearing bit 1 on 0b1111 is 0b1101 = 13
16 | assert clear_bit_k(15, 1) == 13
17 |
18 |
19 | def test_set_bit_k():
20 | # setting bit 1 on 0b1101 is 0b1111 = 15
21 | assert set_bit_k(13, 1) == 15
22 |
--------------------------------------------------------------------------------
/tests/test_system.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dehinter.system import get_filesize
4 |
5 |
6 | def test_get_filesize_bytes():
7 | byte_file = os.path.join("tests", "test_files", "text", "byte.txt")
8 | assert get_filesize(byte_file) == ("1.00", "B")
9 |
10 |
11 | def test_get_filesize_kilobytes():
12 | kilobyte_file = os.path.join("tests", "test_files", "text", "kb.txt")
13 | assert get_filesize(kilobyte_file) == ("1.00", "KB")
14 |
15 |
16 | def test_get_filesize_megabytes():
17 | megabyte_file = os.path.join("tests", "test_files", "text", "mb.txt")
18 | assert get_filesize(megabyte_file) == ("1.00", "MB")
19 |
--------------------------------------------------------------------------------
/.github/workflows/py-typecheck.yml:
--------------------------------------------------------------------------------
1 | name: Python Type Checks
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os: [ubuntu-latest]
11 | python-version: ["3.10"]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install Python testing dependencies
20 | run: pip install --upgrade mypy
21 | - name: Static type checks
22 | run: mypy lib/dehinter
23 |
--------------------------------------------------------------------------------
/lib/dehinter/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | version = __version__ = "4.0.0"
16 |
--------------------------------------------------------------------------------
/.github/workflows/py-lint.yml:
--------------------------------------------------------------------------------
1 | name: Python Lints
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os: [ubuntu-latest]
11 | python-version: ["3.10"]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install Python testing dependencies
20 | run: pip install --upgrade flake8
21 | - name: flake8 Lint
22 | uses: py-actions/flake8@v1
23 | with:
24 | max-line-length: "90"
25 | path: "lib/dehinter"
26 |
--------------------------------------------------------------------------------
/tests/test_files/fonts/README.md:
--------------------------------------------------------------------------------
1 | ## Test Font Licenses
2 |
3 | The fonts used in the testing of this project include the following:
4 |
5 | 1. OpenSans-VF.ttf
6 | 2. Roboto-Regular.ttf
7 | 3. Roboto-Regular-dehinted.ttf (ttfautohint dehinted derivative of Roboto-Regular.ttf)
8 | 4. NotoSans-Regular.ttf
9 | 5. NotoSans-Regular-dehinted.ttf (ttfautohint dehinted derivative of NotoSans-Regular.ttf)
10 | 6. Ubuntu-Regular.ttf
11 |
12 | Open Sans is licensed under the [SIL Open Font License 1.1](https://github.com/googlefonts/opensans/blob/main/OFL.txt)
13 |
14 | Roboto is licensed under the [Apache License v2.0](https://github.com/google/roboto/blob/master/LICENSE).
15 |
16 | Noto Sans is licensed under the [SIL Open Font License 1.1](https://github.com/googlefonts/noto-fonts/blob/master/LICENSE).
17 |
18 | Ubuntu is licensed under the [Ubuntu Font License](https://ubuntu.com/legal/font-licence).
19 |
20 | You may find the full text of these licenses in the `licenses` subdirectory.
21 |
--------------------------------------------------------------------------------
/tests/test_paths.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dehinter.paths import filepath_exists, get_default_out_path
4 |
5 | import pytest
6 |
7 |
8 | def test_filepath_exists_good_filepath():
9 | good_path = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
10 | assert filepath_exists(good_path) is True
11 |
12 |
13 | def test_filepath_exists_bad_filepath():
14 | bad_path = "bogus_file.txt"
15 | assert filepath_exists(bad_path) is False
16 |
17 |
18 | def test_get_default_filepath_without_dir():
19 | path = "Roboto-Regular.ttf"
20 | default_path = get_default_out_path(path)
21 | assert default_path == "Roboto-Regular-dehinted.ttf"
22 |
23 |
24 | def test_get_default_filepath_with_dir():
25 | path = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
26 | default_path = get_default_out_path(path)
27 | assert default_path == os.path.join(
28 | "tests", "test_files", "fonts", "Roboto-Regular-dehinted.ttf"
29 | )
30 |
--------------------------------------------------------------------------------
/tests/test_files/text/kb.txt:
--------------------------------------------------------------------------------
1 | ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: install
2 |
3 | black:
4 | black lib/dehinter/*.py
5 |
6 | import-sort:
7 | isort lib/dehinter/*.py
8 |
9 | format: import-sort black
10 |
11 | clean:
12 | - rm dist/*.whl dist/*.tar.gz dist/*.zip
13 |
14 | dist-build: clean
15 | python3 setup.py sdist bdist_wheel
16 |
17 | dist-push:
18 | twine upload dist/*.whl dist/*.tar.gz
19 |
20 | install:
21 | pip3 install --ignore-installed -r requirements.txt .
22 |
23 | install-dev:
24 | pip3 install --ignore-installed -r requirements.txt -e ".[dev]"
25 |
26 | install-user:
27 | pip3 install --ignore-installed --user .
28 |
29 | test: test-lint test-type-check test-unit
30 |
31 | test-coverage:
32 | coverage run --source dehinter -m py.test
33 | coverage report -m
34 | # coverage html
35 |
36 | test-lint:
37 | flake8 --ignore=E501,W50 lib/dehinter
38 |
39 | test-type-check:
40 | mypy lib/dehinter
41 |
42 | test-unit:
43 | tox
44 |
45 | uninstall:
46 | pip3 uninstall --yes dehinter
47 |
48 | .PHONY: all black clean dist-build dist-push install install-dev install-user test test-lint test-type-check test-unit uninstall
--------------------------------------------------------------------------------
/lib/dehinter/bitops.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
16 | def is_bit_k_set(int_val: int, k: int) -> bool:
17 | """Tests if the value of bit at offset k in an integer is set"""
18 | return (int_val & (1 << k)) != 0
19 |
20 |
21 | def clear_bit_k(int_val: int, k: int) -> int:
22 | """Clears the bit at offset k"""
23 | return int_val & ~(1 << k)
24 |
25 |
26 | def set_bit_k(int_val: int, k: int) -> int:
27 | """Sets the bit at offset k"""
28 | return int_val | (1 << k)
29 |
--------------------------------------------------------------------------------
/lib/dehinter/paths.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | from typing import Union
17 |
18 |
19 | def filepath_exists(filepath: Union[bytes, str, "os.PathLike[str]"]) -> bool:
20 | """Tests a file path string to confirm that the file exists on the file system"""
21 | return os.path.isfile(filepath)
22 |
23 |
24 | def get_default_out_path(
25 | filepath: os.PathLike,
26 | ) -> Union[str, bytes]:
27 | """Returns an updated file path that is used as dehinted file default when user
28 | does not specify an out file path."""
29 | dir_path, file_path = os.path.split(filepath)
30 | file_name, file_extension = os.path.splitext(file_path)
31 | default_file_name = file_name + "-dehinted" + file_extension
32 | return os.path.join(dir_path, default_file_name)
33 |
--------------------------------------------------------------------------------
/lib/dehinter/system.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | from typing import Tuple, Union
17 |
18 |
19 | def get_filesize(filepath: Union[str, bytes, "os.PathLike[str]"]) -> Tuple[str, str]:
20 | """Returns formatted file size tuple fit for printing to end user."""
21 | filesize = os.path.getsize(filepath)
22 | kb_factor = 1 << 10
23 | mb_factor = 1 << 20
24 |
25 | if filesize < kb_factor:
26 | size_string = "B"
27 | formatted_filesize = float(filesize)
28 | elif kb_factor <= filesize < mb_factor:
29 | size_string = "KB"
30 | formatted_filesize = filesize / float(kb_factor)
31 | else:
32 | size_string = "MB"
33 | formatted_filesize = filesize / float(mb_factor)
34 |
35 | return "{0:.2f}".format(formatted_filesize), size_string
36 |
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | # Sequence of patterns matched against refs/tags
4 | tags:
5 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
6 |
7 | name: Create and Publish Release
8 |
9 | jobs:
10 | build:
11 | name: Create and Publish Release
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | python-version: ["3.10"]
16 | steps:
17 | - uses: actions/checkout@v2
18 | - name: Set up Python ${{ matrix.python-version }}
19 | uses: actions/setup-python@v2
20 | with:
21 | python-version: ${{ matrix.python-version }}
22 | - name: Install dependencies
23 | run: |
24 | python -m pip install --upgrade pip
25 | pip install --upgrade setuptools wheel twine
26 | - name: Create GitHub release
27 | id: create_release
28 | uses: actions/create-release@v1
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | with:
32 | tag_name: ${{ github.ref }}
33 | release_name: ${{ github.ref }}
34 | body: |
35 | Please see the root of the repository for the CHANGELOG.md
36 | draft: false
37 | prerelease: false
38 | - name: Build and publish to PyPI
39 | env:
40 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
41 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
42 | run: |
43 | make dist-build
44 | twine upload dist/*
45 |
--------------------------------------------------------------------------------
/.github/workflows/py-ci.yml:
--------------------------------------------------------------------------------
1 | name: Python CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os: [ubuntu-latest, macos-latest, windows-latest]
11 | python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7"]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Display Python version & architecture
20 | run: |
21 | python -c "import sys; print(sys.version)"
22 | python -c "import struct; print(struct.calcsize('P') * 8)"
23 | - name: Get pip cache dir
24 | id: pip-cache
25 | run: |
26 | pip install --upgrade pip
27 | echo "::set-output name=dir::$(pip cache dir)"
28 | - name: pip cache
29 | uses: actions/cache@v2
30 | with:
31 | path: ${{ steps.pip-cache.outputs.dir }}
32 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
33 | restore-keys: |
34 | ${{ runner.os }}-pip-
35 | - name: Install Python testing dependencies
36 | run: pip install --upgrade pytest
37 | - name: Install Python project dependencies
38 | uses: py-actions/py-dependency-install@v2
39 | with:
40 | update-pip: "true"
41 | update-setuptools: "true"
42 | update-wheel: "true"
43 | - name: Install project
44 | run: pip install -r requirements.txt .
45 | - name: Python unit tests
46 | run: pytest
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | wheels/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 | MANIFEST
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *.cover
46 | .hypothesis/
47 | .pytest_cache/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 | db.sqlite3
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # Environments
84 | .env
85 | .venv
86 | env/
87 | venv/
88 | ENV/
89 | env.bak/
90 | venv.bak/
91 |
92 | # Spyder project settings
93 | .spyderproject
94 | .spyproject
95 |
96 | # Rope project settings
97 | .ropeproject
98 |
99 | # mkdocs documentation
100 | /site
101 |
102 | # mypy
103 | .mypy_cache/
104 |
105 | # macOS specific
106 | .DS_Store
107 |
108 | # PyCharm
109 | .idea
110 |
111 | # pytype
112 | .pytype
113 |
114 | .vscode
--------------------------------------------------------------------------------
/.github/workflows/py-coverage.yml:
--------------------------------------------------------------------------------
1 | name: Coverage
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os: [ubuntu-latest]
11 | python-version: ["3.10"]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v2
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Display Python version & architecture
20 | run: |
21 | python -c "import sys; print(sys.version)"
22 | python -c "import struct; print(struct.calcsize('P') * 8)"
23 | - name: Get pip cache dir
24 | id: pip-cache
25 | run: |
26 | pip install --upgrade pip
27 | echo "::set-output name=dir::$(pip cache dir)"
28 | - name: pip cache
29 | uses: actions/cache@v2
30 | with:
31 | path: ${{ steps.pip-cache.outputs.dir }}
32 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
33 | restore-keys: |
34 | ${{ runner.os }}-pip-
35 | - name: Install Python testing dependencies
36 | run: pip install --upgrade pytest
37 | - name: Install Python project dependencies
38 | uses: py-actions/py-dependency-install@v2
39 | with:
40 | update-pip: "true"
41 | update-setuptools: "true"
42 | update-wheel: "true"
43 | - name: Install project
44 | run: pip install .
45 | - name: Generate coverage report
46 | run: |
47 | pip install --upgrade coverage
48 | coverage run --source dehinter -m py.test
49 | coverage report -m
50 | coverage xml
51 | - name: Upload coverage to Codecov
52 | uses: codecov/codecov-action@v1
53 | with:
54 | file: ./coverage.xml
55 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '39 23 * * 2'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import sys
4 | from setuptools import setup, find_packages
5 |
6 | # Package meta-data.
7 | NAME = "dehinter"
8 | DESCRIPTION = "A tool for the removal of TrueType instruction sets (hints) in fonts"
9 | LICENSE = "Apache License v2.0"
10 | URL = "https://github.com/source-foundry/dehinter"
11 | EMAIL = "chris@sourcefoundry.org"
12 | AUTHOR = "Source Foundry Authors and Contributors"
13 | REQUIRES_PYTHON = ">=3.7.0"
14 |
15 | INSTALL_REQUIRES = [
16 | "fontTools",
17 | ]
18 | # Optional packages
19 | EXTRAS_REQUIRES = {
20 | # for developer installs
21 | "dev": ["coverage", "pytest", "tox", "flake8", "mypy", "isort"],
22 | # for maintainer installs
23 | "maintain": ["wheel", "setuptools", "twine"],
24 | }
25 |
26 | this_file_path = os.path.abspath(os.path.dirname(__file__))
27 |
28 | # Version
29 | main_namespace = {}
30 | version_fp = os.path.join(this_file_path, "lib", "dehinter", "__init__.py")
31 | try:
32 | with io.open(version_fp) as v:
33 | exec(v.read(), main_namespace)
34 | except IOError as version_e:
35 | sys.stderr.write(
36 | "[ERROR] setup.py: Failed to read the version data for the version definition: {}".format(
37 | str(version_e)
38 | )
39 | )
40 | raise version_e
41 |
42 | # Use repository Markdown README.md for PyPI long description
43 | try:
44 | with io.open("README.md", encoding="utf-8") as f:
45 | readme = f.read()
46 | except IOError as readme_e:
47 | sys.stderr.write(
48 | "[ERROR] setup.py: Failed to read the README.md file for the long description definition: {}".format(
49 | str(readme_e)
50 | )
51 | )
52 | raise readme_e
53 |
54 | setup(
55 | name=NAME,
56 | version=main_namespace["__version__"],
57 | description=DESCRIPTION,
58 | author=AUTHOR,
59 | author_email=EMAIL,
60 | url=URL,
61 | license=LICENSE,
62 | platforms=["Any"],
63 | long_description=readme,
64 | long_description_content_type="text/markdown",
65 | package_dir={"": "lib"},
66 | packages=find_packages("lib"),
67 | include_package_data=True,
68 | install_requires=INSTALL_REQUIRES,
69 | extras_require=EXTRAS_REQUIRES,
70 | python_requires=REQUIRES_PYTHON,
71 | entry_points={"console_scripts": ["dehinter = dehinter.__main__:main"]},
72 | classifiers=[
73 | "Development Status :: 5 - Production/Stable",
74 | "Environment :: Console",
75 | "Environment :: Other Environment",
76 | "Intended Audience :: Developers",
77 | "Intended Audience :: End Users/Desktop",
78 | "License :: OSI Approved :: Apache Software License",
79 | "Natural Language :: English",
80 | "Operating System :: OS Independent",
81 | "Programming Language :: Python",
82 | "Programming Language :: Python :: 3",
83 | "Programming Language :: Python :: 3.7",
84 | "Programming Language :: Python :: 3.8",
85 | "Programming Language :: Python :: 3.9",
86 | "Programming Language :: Python :: 3.10",
87 | "Topic :: Text Processing :: Fonts",
88 | "Topic :: Multimedia :: Graphics",
89 | "Topic :: Multimedia :: Graphics :: Graphics Conversion",
90 | ],
91 | )
92 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v4.0.0
4 |
5 | - remove Python 3.6 support (this was eliminated in our fontTools dependency)
6 | - update fonttools dependency to v4.28.1
7 | - update GitHub Actions CI workflow to include Python 3.10 testing
8 | - update GitHub Actions workflows to use cPython 3.10 runner by default
9 |
10 | ## v3.1.0
11 |
12 | - add a `dehinter.font.dehint` function to be used by programs that import dehinter as a module
13 | - add `--quiet` flag to suppress standard output reporting
14 | - update fonttools dependency to v4.22.1
15 |
16 | ## v3.0.0
17 |
18 | - add support for cvar table removal in variable fonts (backward incompatible change)
19 | - add new `--keep-cvar` option to toggle cvar table removal off
20 | - modify default gasp approach to add a gasp table when it is not available in a font (backward incompatible change)
21 | - add new source formatting Makefile targets
22 | - add isort package to setup.py extras_requires dev dependencies
23 | - source import statement formatting
24 | - update fonttools dependency to v4.22.0
25 | - update GitHub Actions workflows to use cPython 3.9 (from cPython 3.8)
26 |
27 | ## v2.0.4
28 |
29 | - dependency update patch
30 | - update fonttools dependency to v4.17.1
31 |
32 | ## v2.0.3
33 |
34 | - add cPython 3.9 interpreter testing
35 | - add CodeQL static source testing
36 | - update fonttools dependency to v4.16.1
37 |
38 | ## v2.0.2
39 |
40 | - refactor string formatting to f-strings
41 | - added GitHub Action CI workflows
42 | - removed Travis CI testing
43 | - removed Appveyor CI testing
44 |
45 | ## v2.0.1
46 |
47 | - update setup.py classifiers to properly define this project as status 5: Production / Stable
48 | - update README.md with transition to `mypy` as our static type check tool
49 |
50 | ## v2.0.0
51 |
52 | Backwards incompatible gasp table change introduced in this release
53 |
54 | - modified dehinted gasp table definition to grayscale, symmetric smoothing behavior (bit flag 0x000A). The previous default was bit flag 0x000f which defines gridfit, grayscale, symmetric gridfit, symmetric smoothing. Our previous default is the *default* behavior in `ttfautohint -d` to our knowledge. This change is a departure from the `ttfautohint -d` default behavior. (pull request #39, thanks Aaron!)
55 | - added type hints and mypy static type checking
56 | - updated fontTools dependency to v4.14.0
57 | - black source formatting applied to Python sources
58 |
59 | ## v1.0.0
60 |
61 | - updated fontTools and associated dependencies to v4.6.0 release
62 | - this update adds Unicode 13 support
63 | - add Python3.8 CI testing support
64 |
65 | ## v0.4.3
66 |
67 | - escalated fontTools dependency version to v4.2.4
68 |
69 | ## v0.4.2
70 |
71 | - fix Travis CI testing error on macOS platform with move to `pip3` from `pip`
72 | - update fontTools dependency to v4.0.1
73 |
74 | ## v0.4.1
75 |
76 | - fix Makefile uninstall target error
77 | - PEP8 source formatting edits
78 |
79 | ## v0.4.0
80 |
81 | - changed min Python version to Python 3.6+ interpreters
82 | - added support for default VDMX table removal
83 | - added `--keep-vdmx` option to keep original VDMX table
84 | - updated fontTools dependency to v4.0.0
85 | - changed configuration to build wheels for Py3 only
86 | - setup.py file updated with new classifiers
87 | - added new Ubuntu-Regular.ttf testing file with the UFL license
88 | - added the Roboto testing file license
89 |
90 | ## v0.3.0
91 |
92 | - modified `glyf` table instruction set bytecode removal approach for composite glyphs
93 | - fixed: `head` table flags bit 4 is now cleared only when `hdmx` and `LTSH` tables are removed (or were not present and bit is set in the font)
94 |
95 | ## v0.2.0
96 |
97 | - add `--keep-cvt` option to keep original cvt table
98 | - add `--keep-fpgm` option to keep original fpgm table
99 | - add `--keep-gasp` option to keep original gasp table
100 | - add `--keep-glyf` option to keep original glyf table
101 | - add `--keep-hdmx` option to keep original hdmx table
102 | - add `--keep-head` option to keep original head table
103 | - add `--keep-ltsh` option to keep original LTSH table
104 | - add `--keep-maxp` option to keep original maxp table
105 | - add `--keep-prep` option to keep original prep table
106 | - add `--keep-ttfa` option to keep original TTFA table
107 |
108 | ## v0.1.0
109 |
110 | - initial beta release
111 |
112 | ## v0.0.1
113 |
114 | - pre-release for PyPI naming
115 |
--------------------------------------------------------------------------------
/tests/test_files/fonts/licenses/OpenSans-License.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
2 |
3 | -----------------------------------------------------------
4 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
5 | -----------------------------------------------------------
6 |
7 | PREAMBLE
8 | The goals of the Open Font License (OFL) are to stimulate worldwide
9 | development of collaborative font projects, to support the font
10 | creation efforts of academic and linguistic communities, and to
11 | provide a free and open framework in which fonts may be shared and
12 | improved in partnership with others.
13 |
14 | The OFL allows the licensed fonts to be used, studied, modified and
15 | redistributed freely as long as they are not sold by themselves. The
16 | fonts, including any derivative works, can be bundled, embedded,
17 | redistributed and/or sold with any software provided that any reserved
18 | names are not used by derivative works. The fonts and derivatives,
19 | however, cannot be released under any other type of license. The
20 | requirement for fonts to remain under this license does not apply to
21 | any document created using the fonts or their derivatives.
22 |
23 | DEFINITIONS
24 | "Font Software" refers to the set of files released by the Copyright
25 | Holder(s) under this license and clearly marked as such. This may
26 | include source files, build scripts and documentation.
27 |
28 | "Reserved Font Name" refers to any names specified as such after the
29 | copyright statement(s).
30 |
31 | "Original Version" refers to the collection of Font Software
32 | components as distributed by the Copyright Holder(s).
33 |
34 | "Modified Version" refers to any derivative made by adding to,
35 | deleting, or substituting -- in part or in whole -- any of the
36 | components of the Original Version, by changing formats or by porting
37 | the Font Software to a new environment.
38 |
39 | "Author" refers to any designer, engineer, programmer, technical
40 | writer or other person who contributed to the Font Software.
41 |
42 | PERMISSION & CONDITIONS
43 | Permission is hereby granted, free of charge, to any person obtaining
44 | a copy of the Font Software, to use, study, copy, merge, embed,
45 | modify, redistribute, and sell modified and unmodified copies of the
46 | Font Software, subject to the following conditions:
47 |
48 | 1) Neither the Font Software nor any of its individual components, in
49 | Original or Modified Versions, may be sold by itself.
50 |
51 | 2) Original or Modified Versions of the Font Software may be bundled,
52 | redistributed and/or sold with any software, provided that each copy
53 | contains the above copyright notice and this license. These can be
54 | included either as stand-alone text files, human-readable headers or
55 | in the appropriate machine-readable metadata fields within text or
56 | binary files as long as those fields can be easily viewed by the user.
57 |
58 | 3) No Modified Version of the Font Software may use the Reserved Font
59 | Name(s) unless explicit written permission is granted by the
60 | corresponding Copyright Holder. This restriction only applies to the
61 | primary font name as presented to the users.
62 |
63 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
64 | Software shall not be used to promote, endorse or advertise any
65 | Modified Version, except to acknowledge the contribution(s) of the
66 | Copyright Holder(s) and the Author(s) or with their explicit written
67 | permission.
68 |
69 | 5) The Font Software, modified or unmodified, in part or in whole,
70 | must be distributed entirely under this license, and must not be
71 | distributed under any other license. The requirement for fonts to
72 | remain under this license does not apply to any document created using
73 | the Font Software.
74 |
75 | TERMINATION
76 | This license becomes null and void if any of the above conditions are
77 | not met.
78 |
79 | DISCLAIMER
80 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
81 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
82 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
83 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
84 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
85 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
86 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
87 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
88 | OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
/tests/test_files/fonts/licenses/Noto-License.txt:
--------------------------------------------------------------------------------
1 | Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts)
2 |
3 | This Font Software is licensed under the SIL Open Font License,
4 | Version 1.1.
5 |
6 | This license is copied below, and is also available with a FAQ at:
7 | http://scripts.sil.org/OFL
8 |
9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 |
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font
16 | creation efforts of academic and linguistic communities, and to
17 | provide a free and open framework in which fonts may be shared and
18 | improved in partnership with others.
19 |
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded,
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply to
27 | any document created using the fonts or their derivatives.
28 |
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 |
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 |
37 | "Original Version" refers to the collection of Font Software
38 | components as distributed by the Copyright Holder(s).
39 |
40 | "Modified Version" refers to any derivative made by adding to,
41 | deleting, or substituting -- in part or in whole -- any of the
42 | components of the Original Version, by changing formats or by porting
43 | the Font Software to a new environment.
44 |
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 |
48 | PERMISSION & CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed,
51 | modify, redistribute, and sell modified and unmodified copies of the
52 | Font Software, subject to the following conditions:
53 |
54 | 1) Neither the Font Software nor any of its individual components, in
55 | Original or Modified Versions, may be sold by itself.
56 |
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 |
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the
66 | corresponding Copyright Holder. This restriction only applies to the
67 | primary font name as presented to the users.
68 |
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 |
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created using
79 | the Font Software.
80 |
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 |
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
--------------------------------------------------------------------------------
/tests/test_files/fonts/licenses/Ubuntu-License.txt:
--------------------------------------------------------------------------------
1 | -------------------------------
2 | UBUNTU FONT LICENCE Version 1.0
3 | -------------------------------
4 |
5 | PREAMBLE
6 | This licence allows the licensed fonts to be used, studied, modified and
7 | redistributed freely. The fonts, including any derivative works, can be
8 | bundled, embedded, and redistributed provided the terms of this licence
9 | are met. The fonts and derivatives, however, cannot be released under
10 | any other licence. The requirement for fonts to remain under this
11 | licence does not require any document created using the fonts or their
12 | derivatives to be published under this licence, as long as the primary
13 | purpose of the document is not to be a vehicle for the distribution of
14 | the fonts.
15 |
16 | DEFINITIONS
17 | "Font Software" refers to the set of files released by the Copyright
18 | Holder(s) under this licence and clearly marked as such. This may
19 | include source files, build scripts and documentation.
20 |
21 | "Original Version" refers to the collection of Font Software components
22 | as received under this licence.
23 |
24 | "Modified Version" refers to any derivative made by adding to, deleting,
25 | or substituting -- in part or in whole -- any of the components of the
26 | Original Version, by changing formats or by porting the Font Software to
27 | a new environment.
28 |
29 | "Copyright Holder(s)" refers to all individuals and companies who have a
30 | copyright ownership of the Font Software.
31 |
32 | "Substantially Changed" refers to Modified Versions which can be easily
33 | identified as dissimilar to the Font Software by users of the Font
34 | Software comparing the Original Version with the Modified Version.
35 |
36 | To "Propagate" a work means to do anything with it that, without
37 | permission, would make you directly or secondarily liable for
38 | infringement under applicable copyright law, except executing it on a
39 | computer or modifying a private copy. Propagation includes copying,
40 | distribution (with or without modification and with or without charging
41 | a redistribution fee), making available to the public, and in some
42 | countries other activities as well.
43 |
44 | PERMISSION & CONDITIONS
45 | This licence does not grant any rights under trademark law and all such
46 | rights are reserved.
47 |
48 | Permission is hereby granted, free of charge, to any person obtaining a
49 | copy of the Font Software, to propagate the Font Software, subject to
50 | the below conditions:
51 |
52 | 1) Each copy of the Font Software must contain the above copyright
53 | notice and this licence. These can be included either as stand-alone
54 | text files, human-readable headers or in the appropriate machine-
55 | readable metadata fields within text or binary files as long as those
56 | fields can be easily viewed by the user.
57 |
58 | 2) The font name complies with the following:
59 | (a) The Original Version must retain its name, unmodified.
60 | (b) Modified Versions which are Substantially Changed must be renamed to
61 | avoid use of the name of the Original Version or similar names entirely.
62 | (c) Modified Versions which are not Substantially Changed must be
63 | renamed to both (i) retain the name of the Original Version and (ii) add
64 | additional naming elements to distinguish the Modified Version from the
65 | Original Version. The name of such Modified Versions must be the name of
66 | the Original Version, with "derivative X" where X represents the name of
67 | the new work, appended to that name.
68 |
69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the
70 | Font Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except (i) as required by this licence, (ii) to
72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
73 | their explicit written permission.
74 |
75 | 4) The Font Software, modified or unmodified, in part or in whole, must
76 | be distributed entirely under this licence, and must not be distributed
77 | under any other licence. The requirement for fonts to remain under this
78 | licence does not affect any document created using the Font Software,
79 | except any version of the Font Software extracted from a document
80 | created using the Font Software may only be distributed under this
81 | licence.
82 |
83 | TERMINATION
84 | This licence becomes null and void if any of the above conditions are
85 | not met.
86 |
87 | DISCLAIMER
88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
96 | DEALINGS IN THE FONT SOFTWARE.
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [](https://pypi.org/project/dehinter/)
5 | 
6 | 
7 | 
8 | [](https://codecov.io/gh/source-foundry/dehinter)
9 | [](https://www.codacy.com/app/SourceFoundry/dehinter?utm_source=github.com&utm_medium=referral&utm_content=source-foundry/dehinter&utm_campaign=Badge_Grade)
10 |
11 | ## About
12 |
13 | `dehinter` is a Python command line application that removes TrueType instruction sets, global hinting tables, and other associated OpenType table data in font files. The tool provides cross-platform support on macOS, Windows, and Linux systems with a Python v3.7+ interpreter.
14 |
15 | ## What it does
16 |
17 | - Removes OpenType [glyf table](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) instruction set bytecode data
18 | - Removes OpenType and other TTF hinting related tables - [cvt table](https://docs.microsoft.com/en-us/typography/opentype/spec/cvt) - [fpgm table](https://docs.microsoft.com/en-us/typography/opentype/spec/fpgm) - [hdmx table](https://docs.microsoft.com/en-us/typography/opentype/spec/hdmx) - [LTSH table](https://docs.microsoft.com/en-us/typography/opentype/spec/ltsh) - [prep table](https://docs.microsoft.com/en-us/typography/opentype/spec/prep) - [TTFA table](https://www.freetype.org/ttfautohint/doc/ttfautohint.html#add-ttfa-info-table) (not part of the OpenType specification) - [VDMX table](https://docs.microsoft.com/en-us/typography/opentype/spec/vdmx)
19 | - Removes OpenType [cvar table](https://docs.microsoft.com/en-us/typography/opentype/spec/cvar) from variable fonts
20 | - Updates [gasp table](https://docs.microsoft.com/en-us/typography/opentype/spec/gasp) values
21 | - Updates [maxp table](https://docs.microsoft.com/en-us/typography/opentype/spec/maxp) values
22 | - Updates [head table](https://docs.microsoft.com/en-us/typography/opentype/spec/head) bit flags
23 | - Displays file sizes of the hinted and dehinted versions of the fonts
24 |
25 | Options allow you to maintain the original version of any of these tables.
26 |
27 | ## Installation
28 |
29 | `dehinter` requires a Python 3.7+ interpreter.
30 |
31 | Installation in a [Python3 virtual environment](https://docs.python.org/3/library/venv.html) is recommended as dependencies are pinned to versions that are confirmed to work with this project.
32 |
33 | Use any of the following installation approaches:
34 |
35 | ### pip install from PyPI
36 |
37 | ```
38 | $ pip3 install dehinter
39 | ```
40 |
41 | ### pip install from source
42 |
43 | ```
44 | $ git clone https://github.com/source-foundry/dehinter.git
45 | $ cd dehinter
46 | $ pip3 install .
47 | ```
48 |
49 | ### Developer install from source
50 |
51 | The following approach installs the project and associated optional developer dependencies so that source changes are available without the need for re-installation.
52 |
53 | ```
54 | $ git clone https://github.com/source-foundry/dehinter.git
55 | $ cd dehinter
56 | $ pip3 install --ignore-installed -r requirements.txt -e ".[dev]"
57 | ```
58 |
59 | ## Usage
60 |
61 | ```
62 | $ dehinter [OPTIONS] [HINTED FILE PATH]
63 | ```
64 |
65 | By default, a new dehinted font build write occurs on the path `[ORIGINAL HINTED FONT NAME]-dehinted.ttf` in the `[HINTED FILE PATH]` directory.
66 |
67 | Use `dehinter -h` to view available options.
68 |
69 | ## Issues
70 |
71 | Please report issues on the [project issue tracker](https://github.com/source-foundry/dehinter/issues).
72 |
73 | ## Contributing
74 |
75 | Contributions are warmly welcomed. A development dependency environment can be installed in editable mode with the developer installation documentation above.
76 |
77 | Please use the standard Github pull request approach to propose source changes.
78 |
79 | ### Source file linting
80 |
81 | Python source files are linted with `flake8`. See the Makefile `test-lint` target for details.
82 |
83 | ### Source file static type checks
84 |
85 | Static type checks are performed on Python source files with `mypy` and are based on type annotations in the Python source files. See the Makefile `test-type-check` target for details.
86 |
87 | ### Testing
88 |
89 | The project runs continuous integration testing on GitHub Actions runners with the `pytest` testing toolchain. Test modules are located in the `tests` directory of the repository.
90 |
91 | Local testing by Python interpreter version can be performed with the following command executed from the root of the repository:
92 |
93 | ```
94 | $ tox -e [PYTHON INTERPRETER VERSION]
95 | ```
96 |
97 | Please see the `tox` documentation for additional details.
98 |
99 | ### Test coverage
100 |
101 | Unit test coverage is executed with the `coverage` tool. See the Makefile `test-coverage` target for details.
102 |
103 | ## Acknowledgments
104 |
105 | `dehinter` is built with the fantastic [fontTools free software library](https://github.com/fonttools/fonttools) and is based on the dehinting approach used in the [`ttfautohint` free software project](https://www.freetype.org/ttfautohint/).
106 |
107 | ## License
108 |
109 | Copyright 2019 Source Foundry Authors and Contributors
110 |
111 | Licensed under the Apache License, Version 2.0 (the "License");
112 | you may not use this file except in compliance with the License.
113 | You may obtain a copy of the License at
114 |
115 | http://www.apache.org/licenses/LICENSE-2.0
116 |
117 | Unless required by applicable law or agreed to in writing, software
118 | distributed under the License is distributed on an "AS IS" BASIS,
119 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120 | See the License for the specific language governing permissions and
121 | limitations under the License.
122 |
--------------------------------------------------------------------------------
/lib/dehinter/__main__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import argparse
16 | import os
17 | import sys
18 | from typing import List
19 |
20 | from fontTools.ttLib import TTFont # type: ignore
21 |
22 | from dehinter import __version__
23 | from dehinter.font import dehint, is_truetype_font
24 | from dehinter.paths import filepath_exists, get_default_out_path
25 | from dehinter.system import get_filesize
26 |
27 |
28 | def main() -> None: # pragma: no cover
29 | run(sys.argv[1:])
30 |
31 |
32 | def run(argv: List[str]) -> None:
33 | # ===========================================================
34 | # argparse command line argument definitions
35 | # ===========================================================
36 | parser = argparse.ArgumentParser(
37 | description="A tool for the removal of TrueType instruction sets (hints) in fonts"
38 | )
39 | parser.add_argument(
40 | "--version", action="version", version="dehinter v{}".format(__version__)
41 | )
42 | parser.add_argument("-o", "--out", help="out file path (dehinted font)")
43 | parser.add_argument("--keep-cvar", help="keep cvar table", action="store_true")
44 | parser.add_argument("--keep-cvt", help="keep cvt table", action="store_true")
45 | parser.add_argument("--keep-fpgm", help="keep fpgm table", action="store_true")
46 | parser.add_argument("--keep-hdmx", help="keep hdmx table", action="store_true")
47 | parser.add_argument("--keep-ltsh", help="keep LTSH table", action="store_true")
48 | parser.add_argument("--keep-prep", help="keep prep table", action="store_true")
49 | parser.add_argument("--keep-ttfa", help="keep TTFA table", action="store_true")
50 | parser.add_argument("--keep-vdmx", help="keep VDMX table", action="store_true")
51 | parser.add_argument(
52 | "--keep-glyf", help="do not modify glyf table", action="store_true"
53 | )
54 | parser.add_argument(
55 | "--keep-gasp", help="do not modify gasp table", action="store_true"
56 | )
57 | parser.add_argument(
58 | "--keep-maxp", help="do not modify maxp table", action="store_true"
59 | )
60 | parser.add_argument(
61 | "--keep-head", help="do not modify head table", action="store_true"
62 | )
63 | parser.add_argument("--quiet", help="silence standard output", action="store_true")
64 | parser.add_argument("INFILE", help="in file path (hinted font)")
65 |
66 | args = parser.parse_args(argv)
67 |
68 | # ===========================================================
69 | # Command line logic
70 | # ===========================================================
71 | #
72 | # Validations
73 | # -----------
74 | # (1) file path request is a file
75 | if not filepath_exists(args.INFILE):
76 | sys.stderr.write(
77 | f"[!] Error: '{args.INFILE}' is not a valid file path.{os.linesep}"
78 | )
79 | sys.stderr.write(f"[!] Request canceled.{os.linesep}")
80 | sys.exit(1)
81 | # (2) the file is a ttf font file (based on the 4 byte file signature
82 | if not is_truetype_font(args.INFILE):
83 | sys.stderr.write(
84 | f"[!] Error: '{args.INFILE}' does not appear to be a TrueType font "
85 | f"file.{os.linesep}"
86 | )
87 | sys.stderr.write(f"[!] Request canceled.{os.linesep}")
88 | sys.exit(1)
89 | # (3) confirm that out path is not the same as in path
90 | # This tool does not support writing dehinted files in place over hinted version
91 | if args.INFILE == args.out:
92 | sys.stderr.write(
93 | f"[!] Error: You are attempting to overwrite the hinted file with the "
94 | f"dehinted file. This is not supported. Please choose a different file "
95 | f"path for the dehinted file.{os.linesep}"
96 | )
97 | sys.exit(1)
98 | # Execution
99 | # ---------
100 | # (1) OpenType table removal
101 | try:
102 | tt = TTFont(args.INFILE)
103 | except Exception as e:
104 | sys.stderr.write(
105 | f"[!] Error: Unable to create font object with '{args.INFILE}' -> {str(e)}"
106 | )
107 | sys.exit(1)
108 |
109 | use_verbose_output = not args.quiet
110 |
111 | dehint(
112 | tt,
113 | keep_cvar=args.keep_cvar,
114 | keep_cvt=args.keep_cvt,
115 | keep_fpgm=args.keep_fpgm,
116 | keep_gasp=args.keep_gasp,
117 | keep_glyf=args.keep_glyf,
118 | keep_hdmx=args.keep_hdmx,
119 | keep_head=args.keep_head,
120 | keep_ltsh=args.keep_ltsh,
121 | keep_maxp=args.keep_maxp,
122 | keep_prep=args.keep_prep,
123 | keep_ttfa=args.keep_ttfa,
124 | keep_vdmx=args.keep_vdmx,
125 | verbose=use_verbose_output,
126 | )
127 |
128 | # File write
129 | # ----------
130 | if args.out:
131 | # validation performed above to prevent this file path definition from
132 | # being the same as the in file path. Write in place over a hinted
133 | # file is not supported
134 | outpath = args.out
135 | else:
136 | outpath = get_default_out_path(args.INFILE)
137 |
138 | try:
139 | tt.save(outpath)
140 | if use_verbose_output:
141 | print(f"{os.linesep}[+] Saved dehinted font as '{outpath}'")
142 | except Exception as e: # pragma: no cover
143 | sys.stderr.write(
144 | f"[!] Error: Unable to save dehinted font file: {str(e)}{os.linesep}"
145 | )
146 |
147 | # File size comparison
148 | # --------------------
149 | if use_verbose_output:
150 | infile_size_tuple = get_filesize(args.INFILE)
151 | outfile_size_tuple = get_filesize(
152 | outpath
153 | ) # depends on outpath definition defined during file write
154 | print(f"{os.linesep}[*] File sizes:")
155 | print(f" {infile_size_tuple[0]}{infile_size_tuple[1]} (hinted)")
156 | print(f" {outfile_size_tuple[0]}{outfile_size_tuple[1]} (dehinted)")
157 |
--------------------------------------------------------------------------------
/tests/test_font.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dehinter.font import (
4 | is_truetype_font,
5 | has_cvar_table,
6 | has_cvt_table,
7 | has_fpgm_table,
8 | has_gasp_table,
9 | has_hdmx_table,
10 | has_ltsh_table,
11 | has_prep_table,
12 | has_ttfa_table,
13 | has_vdmx_table,
14 | )
15 | from dehinter.font import (
16 | remove_cvar_table,
17 | remove_cvt_table,
18 | remove_fpgm_table,
19 | remove_hdmx_table,
20 | remove_ltsh_table,
21 | remove_prep_table,
22 | remove_ttfa_table,
23 | remove_vdmx_table,
24 | remove_glyf_instructions,
25 | )
26 | from dehinter.font import update_gasp_table, update_head_table_flags, update_maxp_table
27 |
28 | import pytest
29 | from fontTools.ttLib import TTFont
30 |
31 | FILEPATH_TEST_TEXT = os.path.join("tests", "test_files", "text", "test.txt")
32 | FILEPATH_HINTED_TTF = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
33 | FILEPATH_DEHINTED_TTF = os.path.join(
34 | "tests", "test_files", "fonts", "Roboto-Regular-dehinted.ttf"
35 | )
36 | FILEPATH_HINTED_TTF_2 = os.path.join(
37 | "tests", "test_files", "fonts", "NotoSans-Regular.ttf"
38 | )
39 | FILEPATH_DEHINTED_TTF_2 = os.path.join(
40 | "tests", "test_files", "fonts", "NotoSans-Regular-dehinted.ttf"
41 | )
42 |
43 | FILEPATH_HINTED_TTF_3 = os.path.join("tests", "test_files", "fonts", "Ubuntu-Regular.ttf")
44 |
45 | FILEPATH_HINTED_TTF_VF = os.path.join("tests", "test_files", "fonts", "OpenSans-VF.ttf")
46 |
47 | # ========================================================
48 | # Utilities
49 | # ========================================================
50 |
51 |
52 | def test_has_cvar_table_true():
53 | tt = TTFont(FILEPATH_HINTED_TTF_VF)
54 | assert has_cvar_table(tt) is True
55 |
56 |
57 | def test_has_cvar_table_false():
58 | tt = TTFont(FILEPATH_HINTED_TTF)
59 | assert has_cvar_table(tt) is False
60 |
61 |
62 | def test_has_cvt_table_true():
63 | tt = TTFont(FILEPATH_HINTED_TTF)
64 | assert has_cvt_table(tt) is True
65 |
66 |
67 | def test_has_cvt_table_false():
68 | tt = TTFont(FILEPATH_DEHINTED_TTF)
69 | assert has_cvt_table(tt) is False
70 |
71 |
72 | def test_has_fpgm_table_true():
73 | tt = TTFont(FILEPATH_HINTED_TTF)
74 | assert has_fpgm_table(tt) is True
75 |
76 |
77 | def test_has_fpgm_table_false():
78 | tt = TTFont(FILEPATH_DEHINTED_TTF)
79 | assert has_fpgm_table(tt) is False
80 |
81 |
82 | def test_has_gasp_table_true():
83 | tt = TTFont(FILEPATH_HINTED_TTF)
84 | assert has_gasp_table(tt) is True
85 |
86 |
87 | def test_has_hdmx_table_true():
88 | tt = TTFont(FILEPATH_HINTED_TTF)
89 | assert has_hdmx_table(tt) is True
90 |
91 |
92 | def test_has_hdmx_table_false():
93 | tt = TTFont(FILEPATH_DEHINTED_TTF)
94 | assert has_hdmx_table(tt) is False
95 |
96 |
97 | def test_has_ltsh_table_true():
98 | tt = TTFont(FILEPATH_HINTED_TTF)
99 | assert has_ltsh_table(tt) is True
100 |
101 |
102 | def test_has_ltsh_table_false():
103 | tt = TTFont(FILEPATH_DEHINTED_TTF)
104 | assert has_ltsh_table(tt) is False
105 |
106 |
107 | def test_has_prep_table_true():
108 | tt = TTFont(FILEPATH_HINTED_TTF)
109 | assert has_prep_table(tt) is True
110 |
111 |
112 | def test_has_prep_table_false():
113 | tt = TTFont(FILEPATH_DEHINTED_TTF)
114 | assert has_prep_table(tt) is False
115 |
116 |
117 | def test_has_ttfa_table_true():
118 | tt = TTFont(FILEPATH_HINTED_TTF_2) # tested in Noto Sans font
119 | assert has_ttfa_table(tt) is True
120 |
121 |
122 | def test_has_ttfa_table_false():
123 | tt = TTFont(FILEPATH_DEHINTED_TTF_2) # tested in Noto Sans font
124 | assert has_ttfa_table(tt) is False
125 |
126 |
127 | def test_has_vdmx_table_true():
128 | tt = TTFont(FILEPATH_HINTED_TTF_3)
129 | assert has_vdmx_table(tt) is True
130 |
131 |
132 | def test_has_vdmx_table_false():
133 | tt = TTFont(FILEPATH_HINTED_TTF)
134 | assert has_vdmx_table(tt) is False
135 |
136 |
137 | def test_is_truetype_font_for_ttf():
138 | assert is_truetype_font(FILEPATH_HINTED_TTF) is True
139 |
140 |
141 | def test_is_truetype_font_for_not_ttf():
142 | assert is_truetype_font(FILEPATH_TEST_TEXT) is False
143 |
144 |
145 | # ========================================================
146 | # OpenType table removal
147 | # ========================================================
148 | def test_delete_cvar_table():
149 | tt = TTFont(FILEPATH_HINTED_TTF_VF)
150 | assert ("cvar" in tt) is True
151 | remove_cvar_table(tt)
152 | assert ("cvar" in tt) is False
153 |
154 |
155 | def test_delete_cvar_table_when_not_vf():
156 | tt = TTFont(FILEPATH_HINTED_TTF)
157 | assert ("cvar" in tt) is False
158 | remove_cvar_table(tt)
159 | assert ("cvar" in tt) is False
160 |
161 |
162 | def test_delete_cvt_table():
163 | tt = TTFont(FILEPATH_HINTED_TTF)
164 | assert ("cvt " in tt) is True
165 | remove_cvt_table(tt)
166 | assert ("cvt " in tt) is False
167 |
168 |
169 | def test_delete_cvt_table_missing_table():
170 | tt = TTFont(FILEPATH_DEHINTED_TTF)
171 | assert ("cvt " in tt) is False
172 | remove_cvt_table(tt)
173 | assert ("cvt " in tt) is False
174 |
175 |
176 | def test_delete_fpgm_table():
177 | tt = TTFont(FILEPATH_HINTED_TTF)
178 | assert ("fpgm" in tt) is True
179 | remove_fpgm_table(tt)
180 | assert ("fpgm" in tt) is False
181 |
182 |
183 | def test_delete_fpgm_table_missing_table():
184 | tt = TTFont(FILEPATH_DEHINTED_TTF)
185 | assert ("fpgm" in tt) is False
186 | remove_fpgm_table(tt)
187 | assert ("fpgm" in tt) is False
188 |
189 |
190 | def test_delete_hdmx_table():
191 | tt = TTFont(FILEPATH_HINTED_TTF)
192 | assert ("hdmx" in tt) is True
193 | remove_hdmx_table(tt)
194 | assert ("hdmx" in tt) is False
195 |
196 |
197 | def test_delete_hdmtx_table_missing_table():
198 | tt = TTFont(FILEPATH_DEHINTED_TTF)
199 | assert ("hdmx" in tt) is False
200 | remove_hdmx_table(tt)
201 | assert ("hdmx" in tt) is False
202 |
203 |
204 | def test_delete_ltsh_table():
205 | tt = TTFont(FILEPATH_HINTED_TTF)
206 | assert ("LTSH" in tt) is True
207 | remove_ltsh_table(tt)
208 | assert ("LTSH" in tt) is False
209 |
210 |
211 | def test_delete_ltsh_table_missing_table():
212 | tt = TTFont(FILEPATH_DEHINTED_TTF)
213 | assert ("LTSH" in tt) is False
214 | remove_ltsh_table(tt)
215 | assert ("LTSH" in tt) is False
216 |
217 |
218 | def test_delete_prep_table():
219 | tt = TTFont(FILEPATH_HINTED_TTF)
220 | assert ("prep" in tt) is True
221 | remove_prep_table(tt)
222 | assert ("prep" in tt) is False
223 |
224 |
225 | def test_delete_prep_table_missing_table():
226 | tt = TTFont(FILEPATH_DEHINTED_TTF)
227 | assert ("prep" in tt) is False
228 | remove_prep_table(tt)
229 | assert ("prep" in tt) is False
230 |
231 |
232 | def test_delete_ttfa_table():
233 | tt = TTFont(FILEPATH_HINTED_TTF_2) # tested in Noto Sans
234 | assert ("TTFA" in tt) is True
235 | remove_ttfa_table(tt)
236 | assert ("TTFA" in tt) is False
237 |
238 |
239 | def test_delete_ttfa_table_missing_table():
240 | tt = TTFont(FILEPATH_DEHINTED_TTF_2) # tested in Noto Sans
241 | assert ("TTFA" in tt) is False
242 | remove_ttfa_table(tt)
243 | assert ("TTFA" in tt) is False
244 |
245 |
246 | def test_delete_vdmx_table():
247 | tt = TTFont(FILEPATH_HINTED_TTF_3) # tested in Ubuntu
248 | assert ("VDMX" in tt) is True
249 | remove_vdmx_table(tt)
250 | assert ("VDMX" in tt) is False
251 |
252 |
253 | def test_delete_vdmx_table_missing_table():
254 | tt = TTFont(FILEPATH_DEHINTED_TTF) # tested in Roboto
255 | assert ("VDMX" in tt) is False
256 | remove_vdmx_table(tt)
257 | assert ("VDMX" in tt) is False
258 |
259 |
260 | # ========================================================
261 | # glyf table instruction set bytecode removal
262 | # ========================================================
263 | def test_remove_glyf_instructions_hinted_font():
264 | tt = TTFont(FILEPATH_HINTED_TTF)
265 | number_removed = remove_glyf_instructions(tt)
266 | assert number_removed == 2717
267 |
268 |
269 | def test_remove_glyf_instructions_dehinted_font():
270 | tt = TTFont(FILEPATH_DEHINTED_TTF)
271 | number_removed = remove_glyf_instructions(tt)
272 | assert number_removed == 0
273 |
274 |
275 | # ========================================================
276 | # gasp table edits
277 | # ========================================================
278 | def test_update_gasp_table():
279 | tt = TTFont(FILEPATH_HINTED_TTF)
280 | assert update_gasp_table(tt) is True
281 | assert tt["gasp"].gaspRange == {65535: 0x000A}
282 |
283 |
284 | def test_update_gasp_table_no_gasp():
285 | # the Open Sans var font does not have a gasp table
286 | # execution here adds one when not present
287 | tt = TTFont(FILEPATH_HINTED_TTF_VF)
288 | assert update_gasp_table(tt) is True
289 | assert tt["gasp"].gaspRange == {65535: 0x000A}
290 |
291 |
292 | def test_update_gasp_table_previous_correct_definition():
293 | tt = TTFont(FILEPATH_DEHINTED_TTF_2)
294 | assert update_gasp_table(tt) is False
295 | assert tt["gasp"].gaspRange == {65535: 0x000A}
296 |
297 |
298 | # =========================================
299 | # maxp table edits
300 | # =========================================
301 | def test_update_maxp_table():
302 | tt = TTFont(
303 | FILEPATH_HINTED_TTF_2
304 | ) # test in Noto Sans as all values are modified there
305 | assert update_maxp_table(tt) is True
306 | assert tt["maxp"].maxZones == 0
307 | assert tt["maxp"].maxTwilightPoints == 0
308 | assert tt["maxp"].maxStorage == 0
309 | assert tt["maxp"].maxFunctionDefs == 0
310 | assert tt["maxp"].maxStackElements == 0
311 | assert tt["maxp"].maxSizeOfInstructions == 0
312 |
313 |
314 | # =========================================
315 | # head table edits
316 | # =========================================
317 | def test_update_head_table_flags_without_ltsh_hdmx():
318 | tt = TTFont(FILEPATH_HINTED_TTF)
319 | assert (tt["head"].flags & (1 << 4)) != 0
320 | remove_hdmx_table(tt)
321 | remove_ltsh_table(tt)
322 | response = update_head_table_flags(tt)
323 | assert response is True
324 | assert (tt["head"].flags & (1 << 4)) == 0
325 |
326 |
327 | def test_update_head_table_flags_with_ltsh_hdmx():
328 | tt = TTFont(FILEPATH_HINTED_TTF)
329 | assert (tt["head"].flags & (1 << 4)) != 0
330 | response = update_head_table_flags(tt)
331 | assert response is False
332 | assert (tt["head"].flags & (1 << 4)) != 0
333 |
334 |
335 | def test_update_head_table_flags_with_ltsh():
336 | tt = TTFont(FILEPATH_HINTED_TTF)
337 | assert (tt["head"].flags & (1 << 4)) != 0
338 | remove_hdmx_table(tt)
339 | response = update_head_table_flags(tt)
340 | assert response is False
341 | assert (tt["head"].flags & (1 << 4)) != 0
342 |
343 |
344 | def test_update_head_table_flags_with_hdmx():
345 | tt = TTFont(FILEPATH_HINTED_TTF)
346 | assert (tt["head"].flags & (1 << 4)) != 0
347 | remove_ltsh_table(tt)
348 | response = update_head_table_flags(tt)
349 | assert response is False
350 | assert (tt["head"].flags & (1 << 4)) != 0
351 |
352 |
353 | def test_update_head_table_flags_previously_cleared():
354 | tt = TTFont(FILEPATH_HINTED_TTF_2)
355 | assert (tt["head"].flags & (1 << 4)) == 0
356 | response = update_head_table_flags(tt)
357 | assert response is False
358 | assert (tt["head"].flags & (1 << 4)) == 0
359 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/tests/test_files/fonts/licenses/Roboto-License.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/lib/dehinter/font.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Source Foundry Authors and Contributors
2 |
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 |
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import array
16 | import os
17 | import pprint
18 | import sys
19 | from typing import Union
20 |
21 | from fontTools import ttLib # type: ignore
22 |
23 | from dehinter.bitops import clear_bit_k, is_bit_k_set
24 |
25 | # instantiate pretty printer
26 | pp = pprint.PrettyPrinter(indent=4)
27 |
28 |
29 | def _report_actions(table, has_table):
30 | if not has_table:
31 | print(f"[-] Removed {table} table")
32 | else: # pragma: no cover
33 | sys.stderr.write(
34 | f"[!] Error: failed to remove {table} table from font{os.linesep}"
35 | )
36 |
37 |
38 | # ========================================================
39 | # Core dehinting routine
40 | # ========================================================
41 | def dehint(
42 | tt,
43 | keep_cvar=False,
44 | keep_cvt=False,
45 | keep_fpgm=False,
46 | keep_gasp=False,
47 | keep_glyf=False,
48 | keep_hdmx=False,
49 | keep_head=False,
50 | keep_ltsh=False,
51 | keep_maxp=False,
52 | keep_prep=False,
53 | keep_ttfa=False,
54 | keep_vdmx=False,
55 | verbose=True,
56 | ):
57 |
58 | if is_variable_font(tt) and not keep_cvar:
59 | if has_cvar_table(tt):
60 | remove_cvar_table(tt)
61 | if verbose:
62 | _report_actions("cvar", has_cvar_table(tt))
63 |
64 | if not keep_cvt:
65 | if has_cvt_table(tt):
66 | remove_cvt_table(tt)
67 | if verbose:
68 | _report_actions("cvt", has_cvt_table(tt))
69 |
70 | if not keep_fpgm:
71 | if has_fpgm_table(tt):
72 | remove_fpgm_table(tt)
73 | if verbose:
74 | _report_actions("fpgm", has_fpgm_table(tt))
75 |
76 | if not keep_hdmx:
77 | if has_hdmx_table(tt):
78 | remove_hdmx_table(tt)
79 | if verbose:
80 | _report_actions("hdmx", has_hdmx_table(tt))
81 |
82 | if not keep_ltsh:
83 | if has_ltsh_table(tt):
84 | remove_ltsh_table(tt)
85 | if verbose:
86 | _report_actions("LTSH", has_ltsh_table(tt))
87 |
88 | if not keep_prep:
89 | if has_prep_table(tt):
90 | remove_prep_table(tt)
91 | if verbose:
92 | _report_actions("prep", has_prep_table(tt))
93 |
94 | if not keep_ttfa:
95 | if has_ttfa_table(tt):
96 | remove_ttfa_table(tt)
97 | if verbose:
98 | _report_actions("ttfa", has_ttfa_table(tt))
99 |
100 | if not keep_vdmx:
101 | if has_vdmx_table(tt):
102 | remove_vdmx_table(tt)
103 | if verbose:
104 | _report_actions("VDMX", has_vdmx_table(tt))
105 |
106 | # (2) Remove glyf table instruction set bytecode
107 | if not keep_glyf:
108 | number_glyfs_edited = remove_glyf_instructions(tt)
109 | if number_glyfs_edited > 0:
110 | if verbose:
111 | print(
112 | f"[-] Removed glyf table instruction bytecode from "
113 | f"{number_glyfs_edited} glyphs"
114 | )
115 |
116 | # (3) Edit gasp table
117 | if not keep_gasp:
118 | if update_gasp_table(tt):
119 | gasp_string = pp.pformat(tt["gasp"].__dict__)
120 | if verbose:
121 | print(f"[Δ] New gasp table values:{os.linesep} {gasp_string}")
122 |
123 | # (4) Edit maxp table
124 | if not keep_maxp:
125 | if update_maxp_table(tt):
126 | maxp_string = pp.pformat(tt["maxp"].__dict__)
127 | if verbose:
128 | print(f"[Δ] New maxp table values:{os.linesep} {maxp_string}")
129 |
130 | # (5) Edit head table flags to clear bit 4
131 | if not keep_head:
132 | if update_head_table_flags(tt):
133 | if verbose:
134 | print("[Δ] Cleared bit 4 in head table flags")
135 |
136 |
137 | # ========================================================
138 | # Utilities
139 | # ========================================================
140 | def has_cvar_table(tt) -> bool:
141 | """Tests for the presence of a cvat table in a TrueType variable font."""
142 | return "cvar" in tt
143 |
144 |
145 | def has_cvt_table(tt) -> bool:
146 | """Tests for the presence of a cvt table in a TrueType font."""
147 | return "cvt " in tt
148 |
149 |
150 | def has_fpgm_table(tt) -> bool:
151 | """Tests for the presence of a fpgm table in a TrueType font."""
152 | return "fpgm" in tt
153 |
154 |
155 | def has_gasp_table(tt) -> bool:
156 | """Tests for the presence of a gasp table in a TrueType font."""
157 | return "gasp" in tt
158 |
159 |
160 | def has_hdmx_table(tt) -> bool:
161 | """Tests for the presence of a hdmx table in a TrueType font."""
162 | return "hdmx" in tt
163 |
164 |
165 | def has_ltsh_table(tt) -> bool:
166 | """Tests for the presence of a LTSH table in a TrueType font."""
167 | return "LTSH" in tt
168 |
169 |
170 | def has_prep_table(tt) -> bool:
171 | """Tests for the presence of a prep table in a TrueType font."""
172 | return "prep" in tt
173 |
174 |
175 | def has_ttfa_table(tt) -> bool:
176 | """Tests for the presence of a TTFA table in a TrueType font."""
177 | return "TTFA" in tt
178 |
179 |
180 | def has_vdmx_table(tt) -> bool:
181 | """Tests for the presence of a VDMX table in a TrueType font."""
182 | return "VDMX" in tt
183 |
184 |
185 | def is_truetype_font(filepath: Union[bytes, str, "os.PathLike[str]"]) -> bool:
186 | """Tests that a font has the TrueType file signature of either:
187 | 1) b'\x00\x01\x00\x00'
188 | 2) b'\x74\x72\x75\x65' == 'true'"""
189 | with open(filepath, "rb") as f:
190 | file_signature: bytes = f.read(4)
191 |
192 | return file_signature in (b"\x00\x01\x00\x00", b"\x74\x72\x75\x65")
193 |
194 |
195 | def is_variable_font(tt) -> bool:
196 | """Tests for the presence of a fvar table to confirm that a file is
197 | a variable font."""
198 | return "fvar" in tt
199 |
200 |
201 | # ========================================================
202 | # OpenType table removal
203 | # ========================================================
204 | def remove_cvar_table(tt) -> None:
205 | """Removes cvt table from a fontTools.ttLib.TTFont object"""
206 | try:
207 | del tt["cvar"]
208 | except KeyError:
209 | # do nothing if table is not present in the font
210 | pass
211 |
212 |
213 | def remove_cvt_table(tt) -> None:
214 | """Removes cvt table from a fontTools.ttLib.TTFont object"""
215 | try:
216 | del tt["cvt "]
217 | except KeyError:
218 | # do nothing if table is not present in the font
219 | pass
220 |
221 |
222 | def remove_fpgm_table(tt) -> None:
223 | """Removes fpgm table from a fontTools.ttLib.TTFont object"""
224 | try:
225 | del tt["fpgm"]
226 | except KeyError:
227 | # do nothing if table is not present in the font
228 | pass
229 |
230 |
231 | def remove_hdmx_table(tt) -> None:
232 | """Removes hdmx table from a fontTools.ttLib.TTFont object"""
233 | try:
234 | del tt["hdmx"]
235 | except KeyError:
236 | # do nothing if table is not present in the font
237 | pass
238 |
239 |
240 | def remove_ltsh_table(tt) -> None:
241 | """Removes LTSH table from a fontTools.ttLib.TTFont object."""
242 | try:
243 | del tt["LTSH"]
244 | except KeyError:
245 | # do nothing if table is not present in the font
246 | pass
247 |
248 |
249 | def remove_prep_table(tt) -> None:
250 | """Removes prep table from a fontTools.ttLib.TTFont object"""
251 | try:
252 | del tt["prep"]
253 | except KeyError:
254 | # do nothing if table is not present in the font
255 | pass
256 |
257 |
258 | def remove_ttfa_table(tt) -> None:
259 | """Removes TTFA table from a fontTools.ttLib.TTFont object"""
260 | try:
261 | del tt["TTFA"]
262 | except KeyError:
263 | # do nothing if table is not present in the font
264 | pass
265 |
266 |
267 | def remove_vdmx_table(tt) -> None:
268 | """Removes TTFA table from a fontTools.ttLib.TTFont object"""
269 | try:
270 | del tt["VDMX"]
271 | except KeyError:
272 | # do nothing if table is not present in the font
273 | pass
274 |
275 |
276 | # ========================================================
277 | # glyf table instruction set bytecode removal
278 | # ========================================================
279 | def remove_glyf_instructions(tt) -> int:
280 | """Removes instruction set bytecode from glyph definitions in the glyf table."""
281 | glyph_number: int = 0
282 | for glyph in tt["glyf"].glyphs.values():
283 | glyph.expand(tt["glyf"])
284 | if hasattr(glyph, "program") and glyph.program.bytecode != array.array("B", []):
285 | if glyph.isComposite():
286 | del glyph.program
287 | glyph_number += 1
288 | else:
289 | glyph.program.bytecode = array.array("B", [])
290 | glyph_number += 1
291 | return glyph_number
292 |
293 |
294 | # ========================================================
295 | # gasp table edit
296 | # ========================================================
297 | def update_gasp_table(tt) -> bool:
298 | """Modifies the following gasp table fields:
299 | 1) rangeMaxPPEM changed to 65535
300 | 2) rangeGaspBehavior changed to 0x000a (symmetric grayscale, no gridfit)"""
301 | if "gasp" not in tt:
302 | tt["gasp"] = ttLib.newTable("gasp")
303 | tt["gasp"].gaspRange = {}
304 | if tt["gasp"].gaspRange != {65535: 0x000A}:
305 | tt["gasp"].gaspRange = {65535: 0x000A}
306 | return True
307 | else:
308 | return False
309 |
310 |
311 | # =========================================
312 | # maxp table edits
313 | # =========================================
314 | def update_maxp_table(tt) -> bool:
315 | """Update the maxp table with new values based on elimination of instruction sets."""
316 | changed: bool = False
317 | if tt["maxp"].maxZones != 0:
318 | tt["maxp"].maxZones = 0
319 | changed = True
320 | if tt["maxp"].maxTwilightPoints != 0:
321 | tt["maxp"].maxTwilightPoints = 0
322 | changed = True
323 | if tt["maxp"].maxStorage != 0:
324 | tt["maxp"].maxStorage = 0
325 | changed = True
326 | if tt["maxp"].maxFunctionDefs != 0:
327 | tt["maxp"].maxFunctionDefs = 0
328 | changed = True
329 | if tt["maxp"].maxStackElements != 0:
330 | tt["maxp"].maxStackElements = 0
331 | changed = True
332 | if tt["maxp"].maxSizeOfInstructions != 0:
333 | tt["maxp"].maxSizeOfInstructions = 0
334 | changed = True
335 | return changed
336 |
337 |
338 | # =========================================
339 | # head table edits
340 | # =========================================
341 | def update_head_table_flags(tt) -> bool:
342 | if is_bit_k_set(tt["head"].flags, 4):
343 | # confirm that there is no LTSH or hdmx table
344 | # bit 4 should be set if either of these tables are present in font
345 | if has_hdmx_table(tt) or has_ltsh_table(tt):
346 | return False
347 | else:
348 | new_flags = clear_bit_k(tt["head"].flags, 4)
349 | tt["head"].flags = new_flags
350 | return True
351 | else:
352 | return False
353 |
--------------------------------------------------------------------------------
/tests/test_main.py:
--------------------------------------------------------------------------------
1 | import array
2 | import os
3 | import shutil
4 |
5 | from fontTools.ttLib import TTFont
6 |
7 | from dehinter.__main__ import run
8 |
9 | import pytest
10 |
11 |
12 | #
13 | # Integration tests
14 | #
15 |
16 |
17 | def font_validator(filepath):
18 | assert os.path.exists(filepath)
19 | tt = TTFont(filepath)
20 | assert "cvt " not in tt
21 | assert "fpgm" not in tt
22 | assert "hdmx" not in tt
23 | assert "LTSH" not in tt
24 | assert "prep" not in tt
25 | assert "TTFA" not in tt
26 | assert "VDMX" not in tt
27 | for glyph in tt["glyf"].glyphs.values():
28 | glyph.expand(tt["glyf"])
29 | if glyph.isComposite():
30 | assert not hasattr(glyph, "program")
31 | if hasattr(glyph, "program"):
32 | assert glyph.program.bytecode == array.array("B", [])
33 | assert tt["gasp"].gaspRange == {65535: 0x000A}
34 | assert tt["maxp"].maxZones == 0
35 | assert tt["maxp"].maxTwilightPoints == 0
36 | assert tt["maxp"].maxStorage == 0
37 | assert tt["maxp"].maxStackElements == 0
38 | assert tt["maxp"].maxSizeOfInstructions == 0
39 | assert (tt["head"].flags & 1 << 4) == 0
40 |
41 |
42 | def test_default_run_roboto(capsys):
43 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
44 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
45 | test_inpath = os.path.join(
46 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
47 | )
48 | test_outpath = os.path.join(
49 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
50 | )
51 | test_args = [test_inpath]
52 |
53 | # setup
54 | if os.path.isdir(test_dir):
55 | shutil.rmtree(test_dir)
56 | os.mkdir(test_dir)
57 | shutil.copyfile(notouch_inpath, test_inpath)
58 |
59 | # execute
60 | run(test_args)
61 | captured = capsys.readouterr()
62 | assert "Saved dehinted font as" in captured.out
63 |
64 | # test
65 | font_validator(test_outpath)
66 |
67 | # tear down
68 | shutil.rmtree(test_dir)
69 |
70 |
71 | def test_default_run_noto():
72 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
73 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "NotoSans-Regular.ttf")
74 | test_inpath = os.path.join(
75 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
76 | )
77 | test_outpath = os.path.join(
78 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular-dehinted.ttf"
79 | )
80 | test_args = [test_inpath]
81 |
82 | # setup
83 | if os.path.isdir(test_dir):
84 | shutil.rmtree(test_dir)
85 | os.mkdir(test_dir)
86 | shutil.copyfile(notouch_inpath, test_inpath)
87 |
88 | # execute
89 | run(test_args)
90 |
91 | # test
92 | font_validator(test_outpath)
93 |
94 | # tear down
95 | shutil.rmtree(test_dir)
96 |
97 |
98 | def test_default_run_ubuntu():
99 | """This is used to test VDMX table removal"""
100 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
101 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Ubuntu-Regular.ttf")
102 | test_inpath = os.path.join(
103 | "tests", "test_files", "fonts", "temp", "Ubuntu-Regular.ttf"
104 | )
105 | test_outpath = os.path.join(
106 | "tests", "test_files", "fonts", "temp", "Ubuntu-Regular-dehinted.ttf"
107 | )
108 | test_args = [test_inpath]
109 |
110 | # setup
111 | if os.path.isdir(test_dir):
112 | shutil.rmtree(test_dir)
113 | os.mkdir(test_dir)
114 | shutil.copyfile(notouch_inpath, test_inpath)
115 |
116 | # execute
117 | run(test_args)
118 |
119 | # test
120 | font_validator(test_outpath)
121 |
122 | # tear down
123 | shutil.rmtree(test_dir)
124 |
125 |
126 | def test_default_run_opensans_vf():
127 | """This is used to test cvar table removal in a var font"""
128 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
129 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "OpenSans-VF.ttf")
130 | test_inpath = os.path.join("tests", "test_files", "fonts", "temp", "OpenSans.ttf")
131 | test_outpath = os.path.join(
132 | "tests", "test_files", "fonts", "temp", "OpenSans-dehinted.ttf"
133 | )
134 | test_args = [test_inpath]
135 |
136 | # setup
137 | if os.path.isdir(test_dir):
138 | shutil.rmtree(test_dir)
139 | os.mkdir(test_dir)
140 | shutil.copyfile(notouch_inpath, test_inpath)
141 |
142 | # execute
143 | run(test_args)
144 |
145 | # test
146 | font_validator(test_outpath)
147 | assert "cvar" not in TTFont(test_outpath)
148 |
149 | # tear down
150 | shutil.rmtree(test_dir)
151 |
152 |
153 | def test_default_run_opensans_vf_keep_cvar():
154 | """This is used to test cvar table removal in a var font"""
155 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
156 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "OpenSans-VF.ttf")
157 | test_inpath = os.path.join("tests", "test_files", "fonts", "temp", "OpenSans.ttf")
158 | test_outpath = os.path.join(
159 | "tests", "test_files", "fonts", "temp", "OpenSans-dehinted.ttf"
160 | )
161 | test_args = [test_inpath, "--keep-cvar"]
162 |
163 | # setup
164 | if os.path.isdir(test_dir):
165 | shutil.rmtree(test_dir)
166 | os.mkdir(test_dir)
167 | shutil.copyfile(notouch_inpath, test_inpath)
168 |
169 | # execute
170 | run(test_args)
171 |
172 | # test
173 | font_validator(test_outpath)
174 | assert "cvar" in TTFont(test_outpath)
175 |
176 | # tear down
177 | shutil.rmtree(test_dir)
178 |
179 |
180 | def test_run_roboto_keep_cvt():
181 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
182 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
183 | test_inpath = os.path.join(
184 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
185 | )
186 | test_outpath = os.path.join(
187 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
188 | )
189 | test_args = [test_inpath, "--keep-cvt"]
190 |
191 | # setup
192 | if os.path.isdir(test_dir):
193 | shutil.rmtree(test_dir)
194 | os.mkdir(test_dir)
195 | shutil.copyfile(notouch_inpath, test_inpath)
196 |
197 | # execute
198 | run(test_args)
199 |
200 | # test
201 | tt = TTFont(test_outpath)
202 | assert "cvt " in tt
203 |
204 | # tear down
205 | shutil.rmtree(test_dir)
206 |
207 |
208 | def test_run_roboto_keep_fpgm():
209 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
210 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
211 | test_inpath = os.path.join(
212 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
213 | )
214 | test_outpath = os.path.join(
215 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
216 | )
217 | test_args = [test_inpath, "--keep-fpgm"]
218 |
219 | # setup
220 | if os.path.isdir(test_dir):
221 | shutil.rmtree(test_dir)
222 | os.mkdir(test_dir)
223 | shutil.copyfile(notouch_inpath, test_inpath)
224 |
225 | # execute
226 | run(test_args)
227 |
228 | # test
229 | tt = TTFont(test_outpath)
230 | assert "fpgm" in tt
231 |
232 | # tear down
233 | shutil.rmtree(test_dir)
234 |
235 |
236 | def test_run_roboto_keep_hdmx():
237 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
238 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
239 | test_inpath = os.path.join(
240 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
241 | )
242 | test_outpath = os.path.join(
243 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
244 | )
245 | test_args = [test_inpath, "--keep-hdmx"]
246 |
247 | # setup
248 | if os.path.isdir(test_dir):
249 | shutil.rmtree(test_dir)
250 | os.mkdir(test_dir)
251 | shutil.copyfile(notouch_inpath, test_inpath)
252 |
253 | # execute
254 | run(test_args)
255 |
256 | # test
257 | tt = TTFont(test_outpath)
258 | assert "hdmx" in tt
259 |
260 | # tear down
261 | shutil.rmtree(test_dir)
262 |
263 |
264 | def test_run_roboto_keep_ltsh():
265 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
266 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
267 | test_inpath = os.path.join(
268 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
269 | )
270 | test_outpath = os.path.join(
271 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
272 | )
273 | test_args = [test_inpath, "--keep-ltsh"]
274 |
275 | # setup
276 | if os.path.isdir(test_dir):
277 | shutil.rmtree(test_dir)
278 | os.mkdir(test_dir)
279 | shutil.copyfile(notouch_inpath, test_inpath)
280 |
281 | # execute
282 | run(test_args)
283 |
284 | # test
285 | tt = TTFont(test_outpath)
286 | assert "LTSH" in tt
287 |
288 | # tear down
289 | shutil.rmtree(test_dir)
290 |
291 |
292 | def test_run_roboto_keep_prep():
293 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
294 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
295 | test_inpath = os.path.join(
296 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
297 | )
298 | test_outpath = os.path.join(
299 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
300 | )
301 | test_args = [test_inpath, "--keep-prep"]
302 |
303 | # setup
304 | if os.path.isdir(test_dir):
305 | shutil.rmtree(test_dir)
306 | os.mkdir(test_dir)
307 | shutil.copyfile(notouch_inpath, test_inpath)
308 |
309 | # execute
310 | run(test_args)
311 |
312 | # test
313 | tt = TTFont(test_outpath)
314 | assert "prep" in tt
315 |
316 | # tear down
317 | shutil.rmtree(test_dir)
318 |
319 |
320 | def test_run_noto_keep_ttfa(): # this has to be tested in Noto as it contains a TTFA table
321 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
322 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "NotoSans-Regular.ttf")
323 | test_inpath = os.path.join(
324 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
325 | )
326 | test_outpath = os.path.join(
327 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular-dehinted.ttf"
328 | )
329 | test_args = [test_inpath, "--keep-ttfa"]
330 |
331 | # setup
332 | if os.path.isdir(test_dir):
333 | shutil.rmtree(test_dir)
334 | os.mkdir(test_dir)
335 | shutil.copyfile(notouch_inpath, test_inpath)
336 |
337 | # execute
338 | run(test_args)
339 |
340 | # test
341 | tt = TTFont(test_outpath)
342 | assert "TTFA" in tt
343 |
344 | # tear down
345 | shutil.rmtree(test_dir)
346 |
347 |
348 | def test_default_run_ubuntu_keep_vdmx():
349 | """This is used to test VDMX table removal"""
350 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
351 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Ubuntu-Regular.ttf")
352 | test_inpath = os.path.join(
353 | "tests", "test_files", "fonts", "temp", "Ubuntu-Regular.ttf"
354 | )
355 | test_outpath = os.path.join(
356 | "tests", "test_files", "fonts", "temp", "Ubuntu-Regular-dehinted.ttf"
357 | )
358 | test_args = [test_inpath, "--keep-vdmx"]
359 |
360 | # setup
361 | if os.path.isdir(test_dir):
362 | shutil.rmtree(test_dir)
363 | os.mkdir(test_dir)
364 | shutil.copyfile(notouch_inpath, test_inpath)
365 |
366 | # execute
367 | run(test_args)
368 |
369 | # test
370 | tt = TTFont(test_outpath)
371 | assert "VDMX" in tt
372 |
373 | # tear down
374 | shutil.rmtree(test_dir)
375 |
376 |
377 | def test_run_roboto_keep_glyf():
378 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
379 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
380 | test_inpath = os.path.join(
381 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
382 | )
383 | test_outpath = os.path.join(
384 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
385 | )
386 | test_args = [test_inpath, "--keep-glyf"]
387 |
388 | # setup
389 | if os.path.isdir(test_dir):
390 | shutil.rmtree(test_dir)
391 | os.mkdir(test_dir)
392 | shutil.copyfile(notouch_inpath, test_inpath)
393 |
394 | # execute
395 | run(test_args)
396 |
397 | # test
398 | tt_pre = TTFont(test_inpath)
399 | tt_post = TTFont(test_outpath)
400 | assert tt_pre["glyf"] == tt_post["glyf"]
401 |
402 | # tear down
403 | shutil.rmtree(test_dir)
404 |
405 |
406 | def test_run_roboto_keep_gasp():
407 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
408 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
409 | test_inpath = os.path.join(
410 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
411 | )
412 | test_outpath = os.path.join(
413 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
414 | )
415 | test_args = [test_inpath, "--keep-gasp"]
416 |
417 | # setup
418 | if os.path.isdir(test_dir):
419 | shutil.rmtree(test_dir)
420 | os.mkdir(test_dir)
421 | shutil.copyfile(notouch_inpath, test_inpath)
422 |
423 | # execute
424 | run(test_args)
425 |
426 | # test
427 | tt = TTFont(test_outpath)
428 | assert tt["gasp"].gaspRange == {8: 2, 65535: 15} # unmodified value in Roboto
429 |
430 | # tear down
431 | shutil.rmtree(test_dir)
432 |
433 |
434 | def test_run_noto_keep_maxp():
435 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
436 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "NotoSans-Regular.ttf")
437 | test_inpath = os.path.join(
438 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
439 | )
440 | test_outpath = os.path.join(
441 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular-dehinted.ttf"
442 | )
443 | test_args = [test_inpath, "--keep-maxp"]
444 |
445 | # setup
446 | if os.path.isdir(test_dir):
447 | shutil.rmtree(test_dir)
448 | os.mkdir(test_dir)
449 | shutil.copyfile(notouch_inpath, test_inpath)
450 |
451 | # execute
452 | run(test_args)
453 |
454 | # test
455 | tt = TTFont(test_outpath)
456 | assert tt["maxp"].maxZones != 0
457 | assert tt["maxp"].maxTwilightPoints != 0
458 | assert tt["maxp"].maxStorage != 0
459 | assert tt["maxp"].maxFunctionDefs != 0
460 | assert tt["maxp"].maxStackElements != 0
461 | assert tt["maxp"].maxSizeOfInstructions != 0
462 |
463 | # tear down
464 | shutil.rmtree(test_dir)
465 |
466 |
467 | def test_run_roboto_keep_head():
468 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
469 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
470 | test_inpath = os.path.join(
471 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
472 | )
473 | test_outpath = os.path.join(
474 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
475 | )
476 | test_args = [test_inpath, "--keep-head"]
477 |
478 | # setup
479 | if os.path.isdir(test_dir):
480 | shutil.rmtree(test_dir)
481 | os.mkdir(test_dir)
482 | shutil.copyfile(notouch_inpath, test_inpath)
483 |
484 | # execute
485 | run(test_args)
486 |
487 | # test
488 | tt = TTFont(test_outpath)
489 | assert (tt["head"].flags & 1 << 4) != 0
490 |
491 | # tear down
492 | shutil.rmtree(test_dir)
493 |
494 |
495 | def test_run_with_outfile_path_roboto():
496 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
497 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
498 | test_inpath = os.path.join(
499 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
500 | )
501 | test_outpath = os.path.join(
502 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehintilio.ttf"
503 | )
504 | test_args = [test_inpath, "--out", test_outpath]
505 |
506 | # setup
507 | if os.path.isdir(test_dir):
508 | shutil.rmtree(test_dir)
509 | os.mkdir(test_dir)
510 | shutil.copyfile(notouch_inpath, test_inpath)
511 |
512 | # execute
513 | run(test_args)
514 |
515 | # test
516 | font_validator(test_outpath)
517 |
518 | # tear down
519 | shutil.rmtree(test_dir)
520 |
521 |
522 | def test_run_with_outfile_path_noto():
523 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
524 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "NotoSans-Regular.ttf")
525 | test_inpath = os.path.join(
526 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
527 | )
528 | test_outpath = os.path.join(
529 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular-dehintilio.ttf"
530 | )
531 | test_args = [test_inpath, "-o", test_outpath]
532 |
533 | # setup
534 | if os.path.isdir(test_dir):
535 | shutil.rmtree(test_dir)
536 | os.mkdir(test_dir)
537 | shutil.copyfile(notouch_inpath, test_inpath)
538 |
539 | # execute
540 | run(test_args)
541 |
542 | # test
543 | font_validator(test_outpath)
544 |
545 | # tear down
546 | shutil.rmtree(test_dir)
547 |
548 |
549 | def test_default_run_roboto_quiet_flag_stdout_test(capsys):
550 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
551 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "Roboto-Regular.ttf")
552 | test_inpath = os.path.join(
553 | "tests", "test_files", "fonts", "temp", "Roboto-Regular.ttf"
554 | )
555 | test_outpath = os.path.join(
556 | "tests", "test_files", "fonts", "temp", "Roboto-Regular-dehinted.ttf"
557 | )
558 | test_args = ["--quiet", test_inpath]
559 |
560 | # setup
561 | if os.path.isdir(test_dir):
562 | shutil.rmtree(test_dir)
563 | os.mkdir(test_dir)
564 | shutil.copyfile(notouch_inpath, test_inpath)
565 |
566 | # execute
567 | run(test_args)
568 | captured = capsys.readouterr()
569 | assert captured.out == ""
570 |
571 | # test
572 | font_validator(test_outpath)
573 |
574 | # tear down
575 | shutil.rmtree(test_dir)
576 |
577 |
578 | #
579 | # Validation error testing
580 | #
581 |
582 |
583 | def test_run_with_invalid_filepath():
584 | with pytest.raises(SystemExit):
585 | run(["bogusfile.txt"])
586 |
587 |
588 | def test_run_with_non_font_file():
589 | with pytest.raises(SystemExit):
590 | run([os.path.join("tests", "test_files", "text", "test.txt")])
591 |
592 |
593 | def test_run_dehinted_file_write_inplace():
594 | test_dir = os.path.join("tests", "test_files", "fonts", "temp")
595 | notouch_inpath = os.path.join("tests", "test_files", "fonts", "NotoSans-Regular.ttf")
596 | test_inpath = os.path.join(
597 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
598 | )
599 | test_outpath = os.path.join(
600 | "tests", "test_files", "fonts", "temp", "NotoSans-Regular.ttf"
601 | )
602 | test_args = [test_inpath, "-o", test_outpath]
603 |
604 | # setup
605 | if os.path.isdir(test_dir):
606 | shutil.rmtree(test_dir)
607 | os.mkdir(test_dir)
608 | shutil.copyfile(notouch_inpath, test_inpath)
609 |
610 | # execute
611 | with pytest.raises(SystemExit):
612 | run(test_args)
613 |
614 | # tear down
615 | shutil.rmtree(test_dir)
616 |
--------------------------------------------------------------------------------