pax_global_header00006660000000000000000000000064140152321560014511gustar00rootroot0000000000000052 comment=122aa65274056ef8f75a9f526f71d04fbd87722d psautohint-2.3.0/000077500000000000000000000000001401523215600137115ustar00rootroot00000000000000psautohint-2.3.0/.clang-format000066400000000000000000000053541401523215600162730ustar00rootroot00000000000000Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: TopLevel AlwaysBreakAfterReturnType: TopLevelDefinitions AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum: true AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: true BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Mozilla BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: true IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: false PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true SortIncludes: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never psautohint-2.3.0/.codecov.yml000066400000000000000000000001031401523215600161260ustar00rootroot00000000000000comment: false coverage: status: project: off patch: off psautohint-2.3.0/.coveragerc000066400000000000000000000016621401523215600160370ustar00rootroot00000000000000[run] # measure 'branch' coverage in addition to 'statement' coverage # See: http://coverage.readthedocs.io/en/coverage-4.5.1/branch.html branch = True # list of directories or packages to measure source = psautohint # these are treated as equivalent when combining data [paths] source = python/psautohint .tox/*/lib/python*/site-packages/psautohint .tox/*/Lib/site-packages/psautohint .tox/pypy*/site-packages/psautohint [report] # Regexes for lines to exclude from consideration exclude_lines = # keywords to use in inline comments to skip coverage pragma: no cover # don't complain if tests don't hit defensive assertion code raise AssertionError raise NotImplementedError # don't complain if non-runnable code isn't run if 0: if __name__ == .__main__.: # ignore source code that can't be found ignore_errors = True # when running a summary report, show missing lines show_missing = True psautohint-2.3.0/.github/000077500000000000000000000000001401523215600152515ustar00rootroot00000000000000psautohint-2.3.0/.github/workflows/000077500000000000000000000000001401523215600173065ustar00rootroot00000000000000psautohint-2.3.0/.github/workflows/coverage.yml000066400000000000000000000021701401523215600216240ustar00rootroot00000000000000name: Coverage on: push: branches: - '*' repository_dispatch: types: manual-trigger jobs: run_coverage: if: contains(toJson(github.event.commits), '[skip ci]') == false && contains(toJson(github.event.commits), '[skip github]') == false runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v1 with: submodules: true - name: Set up Python 3.7 uses: actions/setup-python@v1 with: python-version: 3.7 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r dev-requirements.txt pip list echo "PYTHONPATH=home/runner/work/psautohint/build/lib" >> $GITHUB_ENV - name: Generate and upload Python and C coverage env: CFLAGS: '--coverage' run: | pip install pytest==5.4.3 pytest-cov pytest-xdist==1.34.0 python setup.py build --build-base build --build-platlib build/lib python setup.py install python -m pytest --cov=psautohint --cov-report=xml -n auto bash <(curl -s https://codecov.io/bash) -y .codecov.yml psautohint-2.3.0/.github/workflows/pythonpackage.yml000066400000000000000000000101121401523215600226610ustar00rootroot00000000000000name: Test and Build on: push: branches: - '*' tags: - 'v*' pull_request: branches: [master] repository_dispatch: types: manual-trigger jobs: run_tests: if: contains(toJson(github.event.commits), '[skip ci]') == false && contains(toJson(github.event.commits), '[skip github]') == false runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.6, 3.7, 3.8, 3.9] exclude: - os: macos-latest python-version: 3.6 - os: macos-latest python-version: 3.7 - os: macos-latest python-version: 3.8 - os: ubuntu-latest python-version: 3.7 - os: windows-latest python-version: 3.6 - os: windows-latest python-version: 3.7 - os: windows-latest python-version: 3.8 steps: - uses: actions/checkout@v1 with: submodules: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt -r dev-requirements.txt - name: Lint with flake8 (only Ubuntu/Python 3.9) if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' run: | pip install flake8 flake8 --config=setup.cfg --count --show-source --statistics - name: Test with tox (except Ubuntu/Python 3.7) # not in love with the following syntax, but it works, # whereas "! (condition)" does NOT if: (matrix.os == 'ubuntu-latest' && matrix.python-version == '3.7') == false run: | tox -e py-cov build_wheels: needs: run_tests if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Use MSBuild (Windows only) uses: microsoft/setup-msbuild@v1.0.2 if: matrix.os == 'windows-latest' - name: Install Python 3.7 uses: actions/setup-python@v2 with: python-version: '3.7' - name: Install cibuildwheel run: | python -m pip install cibuildwheel - name: Build wheel run: | python -m cibuildwheel --output-dir dist env: CIBW_BUILD: "cp3?-*" CIBW_MANYLINUX_X86_64_IMAGE: manylinux1 CIBW_SKIP: "pp* cp35* cp*win32 cp*manylinux_i686 cp*manylinux_aarch64 cp*manylinux_ppc64le cp*manylinux_s390x" CIBW_ENVIRONMENT: "CFLAGS='-g0'" - name: Build sdist run: | python setup.py sdist if: matrix.os == 'ubuntu-latest' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - name: Publish package to PyPI run: | pip install twine twine upload dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.pypi_password }} if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') - name: Create release if: matrix.os == 'ubuntu-latest' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: ${{ github.ref }} draft: false prerelease: true - name: Attach sdist to release if: matrix.os == 'ubuntu-latest' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tag_name="${GITHUB_REF##*/}" hub release edit $(find dist -type f -name "*.zip" -printf "-a %p ") -m "" "$tag_name" psautohint-2.3.0/.gitignore000066400000000000000000000003731401523215600157040ustar00rootroot00000000000000__pycache__/ .idea/ *.py[cod] *.so *.o *.a *.swp autohintexe build *.egg *.egg-info *.eggs MANIFEST build dist .DS_Store .tox/ .pytest_cache/ .coverage* htmlcov/ libpsautohint/src/version.h pip-wheel-metadata/psautohint.dist-info *.xcodeproj .vscode/psautohint-2.3.0/.gitmodules000066400000000000000000000002131401523215600160620ustar00rootroot00000000000000[submodule "tests/data"] path = tests/integration/data url = https://github.com/adobe-type-tools/psautohint-testdata.git ignore = dirty psautohint-2.3.0/.lgtm.yml000066400000000000000000000003721401523215600154570ustar00rootroot00000000000000extraction: python: python_setup: requirements: "cython>=0.29" cpp: after_prepare: - "pip3 install --upgrade --user cython" - "export PATH=\"$HOME/.local/bin:$PATH\"" index: build_command: "python3 setup.py build" psautohint-2.3.0/.pyup.yml000066400000000000000000000001771401523215600155140ustar00rootroot00000000000000# autogenerated pyup.io config file # see https://pyup.io/docs/configuration/ for all available options schedule: every week psautohint-2.3.0/COPYING000066400000000000000000000011471401523215600147470ustar00rootroot00000000000000Copyright 2014 Adobe Systems Incorporated. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.psautohint-2.3.0/LICENSE000066400000000000000000000261361401523215600147260ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. psautohint-2.3.0/MANIFEST.in000066400000000000000000000001401401523215600154420ustar00rootroot00000000000000include README.md COPYING LICENSE include requirements.txt recursive-include libpsautohint *.h psautohint-2.3.0/NEWS.md000066400000000000000000000072261401523215600150160ustar00rootroot00000000000000Changelog # v2.3.0 - February 22, 2021 - Fixed a major performance issue when hinting VFs (thanks, @madig!) ([#289](https://github.com/adobe-type-tools/psautohint/pull/289), [#221](https://github.com/adobe-type-tools/psautohint/issues/221)) - Updated dependencies ([#290](https://github.com/adobe-type-tools/psautohint/pull/290), [#291](https://github.com/adobe-type-tools/psautohint/pull/291), [#292](https://github.com/adobe-type-tools/psautohint/pull/292)) # v2.2.0 - December 15, 2020 - Removed unused third-party CI configs and related code - Updated dependencies - Removed deprecated CLI option flags that were temporarily in place for the autohint -> psautohint transition - Removed `autohintexe` from Python wheels. It can still be built using `python setup.py build_exe`. - Cleaned up Python code # v2.1.2 - November 6, 2020 - Move remaining CI workflows (building wheels, deploying) to GitHub Actions including building of Python 3.9 wheels for supported platforms. - Updated dependencies - Fixed some [minor formatting issues](https://github.com/adobe-type-tools/psautohint/pull/272) # v2.1.1 - September 24, 2020 - Mute ['unhinted ' messages from tx](https://github.com/adobe-type-tools/psautohint/issues/231) - Updated dependencies - Generate coverage reports from GitHub Actions - Fix [NULL pointer access when processing taito glyph](https://github.com/adobe-type-tools/psautohint/pull/263) - Fix [dump-font.py utility](https://github.com/adobe-type-tools/psautohint/commit/e04a11844738584bb7a666fbb69ffb840b2d19ef) (thanks @khaledhosny!) # v2.0.1 - March 25, 2020 - Fixed [a recursion error with `-x` option](https://github.com/adobe-type-tools/psautohint/issues/223) (thanks @kontur!) - Fixed [an error and other problems with `--print-dflt-fddict`](https://github.com/adobe-type-tools/psautohint/issues/222) (thanks again @kontur!) - Changed [logging level of "Conflicts with current hints" message to DEBUG](https://github.com/adobe-type-tools/psautohint/pull/235/commits/69bab0df4eac8c4a88d9ac4dce94c2d6c61aba99) instead of ERROR (thanks _again_ @kontur!) - Added [Python 3.8 wheels](https://github.com/adobe-type-tools/psautohint/pull/242) to the distribution set (thanks @miguelsousa!) # v2.0.0 - December 6, 2019 - Drop Python 2.7 support – **this version supports Python 3.6+ ONLY** - [modify CLI argument parsing](https://github.com/adobe-type-tools/psautohint/issues/176) to allow the same options as AFDKO autohint and [improve `-o` option](https://github.com/adobe-type-tools/psautohint/issues/129) - use `fonttools` 4.0.2 - fix some mysterious memory issues - [clean up and improve the formatting of `psstemhist` reports](https://github.com/adobe-type-tools/psautohint/issues/153) - add [LGTM.com/Semmle](https://lgtm.com/projects/g/adobe-type-tools/psautohint/?mode=tree) config. `psautohint` Pull Requests are now automatically analyzed for a variety of problems (security, coding style, etc.) - [*tons* of fixes to both Python and C++ code based on ongoing LGTM.com reports](https://lgtm.com/projects/g/adobe-type-tools/psautohint/history/) - refactoring to allow sharing paths between hinting source fonts for MM fonts, and [hinting a CFF2 variable font](https://github.com/adobe-type-tools/psautohint/issues/105) - removed unused code - dropped support for using `autohintexe` (it is still _built_, just not used by `psautohint`...it will eventually be removed from builds also) - fix [bug with compatible hinting (-r option)](https://github.com/adobe-type-tools/psautohint/issues/189) - remove Codacy analysis and related badge - implemented workaround for [a nagging crash with -O3 optimization under Linux](https://github.com/adobe-type-tools/psautohint/issues/103) psautohint-2.3.0/README.md000066400000000000000000000047771401523215600152070ustar00rootroot00000000000000![Test and Build](https://github.com/adobe-type-tools/psautohint/workflows/Test%20and%20Build/badge.svg) [![Codecov](https://codecov.io/gh/adobe-type-tools/psautohint/branch/master/graph/badge.svg)](https://codecov.io/gh/adobe-type-tools/psautohint) [![PyPI](https://img.shields.io/pypi/v/psautohint.svg)](https://pypi.org/project/psautohint) [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/adobe-type-tools/psautohint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe-type-tools/psautohint/context:cpp) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/adobe-type-tools/psautohint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe-type-tools/psautohint/context:python) [![Total alerts](https://img.shields.io/lgtm/alerts/g/adobe-type-tools/psautohint.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe-type-tools/psautohint/alerts/) PSAutoHint ========== A standalone version of [AFDKO](https://github.com/adobe-type-tools/afdko)’s autohinter. **NOTE**: as of August 2019, only Python 3.6 or later is supported. Building and running -------------------- This repository currently consists of a core autohinter written in C, a Python C extension providing an interface to it, and helper Python code. To build the C extension: python setup.py build To install the C extension and the helper scripts globally: pip install -r requirements.txt . Alternatively to install them for the current user: pip install -r requirements.txt --user . The autohinter can be used by running: psautohint To build just the `autohintexe` binary: python setup.py build_exe Testing ------- We have a test suite that can be run with: tox Debugging --------- For standard debugging, build with: python setup.py build --debug It is also possible to build a debug version with [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) ("ASan") support (currently _for Mac OS X only_) with: python setup.py build --asan pip install . Once it is installed, you can use the `util/launch-asan.sh` shell script to launch a Python process that invokes the ASan libraries needed for debugging. Attach Xcode the launched process, then execute code in the process that triggers memory usage problems and wait for ASan to do its magic. NOTE: be sure to build and install `psautohint` as described above; using other techniques such as `python setup.py install` will cause a re-build _without_ ASan and debug support, which won't work. psautohint-2.3.0/dev-requirements.txt000066400000000000000000000000471401523215600177520ustar00rootroot00000000000000tox<3.8 # pyup: ignore coverage>=5.0.1 psautohint-2.3.0/doc/000077500000000000000000000000001401523215600144565ustar00rootroot00000000000000psautohint-2.3.0/doc/AC.md000066400000000000000000000177631401523215600153010ustar00rootroot00000000000000# AC - Automatic Coloring (Hinting) ## Background AC was written by Bill Paxton. Originally, it was integrated with the font editor, FE, but Bill extracted the hinting code so it could run independently and would be easier to maintain. ## Input AC reads a glyph outline in bez format. The fontinfo data is also read to get alignment zone information, the list of H,V counter glyphs, the list of auxiliary H,V stem values, and whether or not flex can be added to a glyph. As the bez data is read a doubly linked-list is created that contains the path element information, e.g. coordinates, path type (moveto, curveto...), etc. ## Setup The following initial setup and error checking is done after a glyph is read: 1. Calculate the glyph bounding box to find the minimum and maximum x, y values. Supposedly, the very minimum hinting a glyph will get is its bounding box values. 2. Check for duplicate subpaths. 3. Check for consecutive movetos and only keep the last one. 4. Check that the path ends with a single closepath and that there are matching movetos and closepaths. 5. Initialize the value of the largest vertical and horizontal stem value allowed. The largest vertical stem value is the larger of 86.25 and the largest value in the auxiliary V stem array. The largest horizontal stem value is the larger of 86.25 and the largest value in the auxiliary H stem array. 6. If flex is allowed add flex to the glyph. The current flex algorithm is very lax and flex can occur where you least expect it. Almost anything that conforms to page 72 of the black book is flexed. However, the last line on page 72 says the flex height must be 20 units or less and this should really say 10 units or less. 7. Check for smooth curves. If the direction arms tangent to the curve is between 0 and 30 degrees the points are forced to be colinear. 8. If there is a sharp angle, greater than 140 degrees, the angle will be blunted by adding another point. There’s a comment that says as of version 2.21 this blunting will not occur. 9. Count and save number of subpaths for each path element. ## Hinting Generate possible hstem and vstem values. These values are saved as a linked list (the coloring segment list) in the path element data structure. There are four segment lists, one for top, bottom, left, and right segments. The basic progression is: path → segments → values → hints, where a segment is a horizontal or vertical feature, a value is a pair of segments (top and bottom or left and right) that is assigned a priority based on the length and width of a stem, and hints are non-overlapping pairs with the highest priority. ### Generating {H,V}Stems The path element is traversed and possible coordinates are added to the segment list. The x or y coordinate is saved depending on if it’s a top/bottom or left/right segment and the minimum and maximum extent for a vertical or horizontal segment. These coordinates are included in the list: a) the coordinates of horizontal/vertical lines, b) if this is a curve find the bends (bend angle must be 135 degrees or less to be included); don’t add a curve’s coordinate if the point is not at an extreme and is going in the same direction as the previous path, c) add points at extremes, d) add bands for s-curves or where an inflection point occurs, e) checks are made to see if the curve is in a blue zone and a coordinate added at the appropriate place. Compact the coloring segment lists by removing any pairs that are completely contained in another. Filter out bogus bend segments and report any near misses to the horizontal alignment zones. ### Evaluating Stems Form all top and bottom, left and right pairs from segment list and generate ghost pairs for horizontal segments in alignment zones. A priority value is assigned to each pair. The value is a function of the length of the segments, the length of any overlap, and the distance apart. A higher priority value means it is a likely candidate to be included in the hints and this is given to pairs that are closer together, longer in length, and have a clean overlap. All pairs with 0 priority are discarded. Report any near misses (1 or 2 units) to the values in the H or V stems array. ### Pruning Values Prune non-relevant stem pairs and keep the best of any overlapping pairs. This is done by looking at the priority values. ### Finding the Best Values After pruning, the best pair is found for each top, bottom or left, right segment using the priority values. Pairs that are at the same location and are “similar” enough are merged by replacing the lower priority pair with the higher priority pair. The pairs are again checked for near misses (1 or 2 units) to the values in the H or V stems array, but this time the information is saved in an array. If fixing these pairs is allowed the pairs saved in the array are changed to match the value it was “close” to in the H or V stem array. Check to see if H or V counter hints (hstem3, vstem3) should be used instead of hstem or vstem. Create the main hints that are included at the beginning of a glyph. These are created by picking, in order of priority, from the segment lists. If no good hints are found use bounding box hints. ## Shuffling Subpaths The glyph’s subpaths are reordered so that the hints will not need to change constantly because it is jumping from one subpath to another. Kanji glyphs had the most problems with this which caused huge files to be created. ## Hint Substitution Remove “flares” from the segment list. Flares usually occur at the top of a serif where a hint is added at an endpoint, but it’s not at the extreme and there is another endpoint very nearby. This causes a blip at the top of the serif at low resolutions. Flares are not removed if they are in an overshoot band. Copy hints to earlier elements in the path, if possible, so hint substitution can start sooner. Don’t allow hint substitution at short elements, so remove any if they exist. Short is considered less than 6 units. Go through path element looking for pairs. If this pair is compatible with the currently active hints then add it otherwise start hint substitution. ## Special Cases When generating stems a procedure is called to allow lines that are not completely horizontal or vertical to be included in the coloring segment list. This was specifically included because the serifs of ITCGaramond Ultra have points that are not quite horizontal according to the comment int the program. When generating hstem values the threshold is ≤ 2 for delta y and ≥ 25 for delta x. The reverse is true when generating vstem values. There are many thresholds used when evaluating stems, pruning, and finding the best values. The thresholds used throughout the program are just “best guesses” according to Bill Paxton. There are various comments in the code explaining why some of these thresholds were changed and specifically for which fonts. The following glyphs attempt to have H counter hints added. > "element", "equivalence", "notelement", "divide" in addition to any glyphs listed in the HCounterChars keyword of the fontinfo file. The following glyphs attempt to have V counter hints added. > "m", "M", "T", "ellipsis" in addition to any glyphs listed in the VCounterChars keyword of the fontinfo file. There used to be a special set of glyphs that received dotsection hints. This was to handle hinting on older PS interpreters that could not perform hint replacement. This feature has been commented out of the current version of AC. AC uses a 24.8 Fixed type number rather than the more widely used 16.16. ## Output AC writes out glyphs bez format that includes the hinting information. Along with each hint it writes a comment that specifies which path element was used to create this hint. This comment is used by BuildFont for hinting multiple master fonts. ## Platforms AC should run on Unix and Unix-like operating systems, Mac OS X and Microsoft Windows, both 32-bit and 64-bit. The code is written in portable C. AC consists of one header file and about 28 C files. psautohint-2.3.0/doc/bezformat.md000066400000000000000000000034621401523215600167760ustar00rootroot00000000000000The Standard Bezier format ============================ The Standard Bezier format is an plain text format consisting of the following operators: | Operator | Description | |-----------|------------------------------------------------------------------| | `sc` | start of glyph outline | | `rmt` | relative move (same as Type 1 `rmoveto` operator) | | `dt` | absolute line/draw (same as Type 1 `lineto` operator) | | `ct` | absolute curve (same as Type 1 `curveto` operator) | | `cp` | end of sub-path (same as Type 1 `closepath` operator) | | `ed` | end of glyph outline (same as Type 1 `endchar` operator) | | `preflx1` | marks start of flex operator, expressed as Type 1 path operators | | `preflx2` | marks end of flex operator | | `flx` | flex operator | | `beginsubr snc` | marks start of new block of hint values | | `endsubr enc\nnewcolors` | marks end of hint values | | `rb` | horizontal stem hint | | `ry` | vertical stem hint | | `rm` | vertical counter hints | | `rv` | horizontal counter hints | Notes ----- The `preflx1`/`preflx2` sequence provides the same info as the `flx` sequence; the difference is that the `preflx1`/`preflx2` sequence provides the argument values needed for building a Type 1 string while the `flx` sequence is simply the 6 `rcurveto` points. Both sequences are always provided. psautohint-2.3.0/libpsautohint/000077500000000000000000000000001401523215600165765ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/autohintexe.c000066400000000000000000000376761401523215600213220ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include #include #include #include #include #include "psautohint.h" static const char* C_ProgramVersion = "1.8"; static const char* reportExt = ".rpt"; static const char* dfltExt = ".new"; static bool verbose = true; /* if true don't number of characters processed. */ static bool debug = false; static void printVersions(void) { fprintf(stdout, "C program version %s. lib version %s.\n", C_ProgramVersion, AC_getVersion()); } static void printUsage(void) { fprintf(stdout, "Usage: autohintexe [-u] [-h]\n"); fprintf(stdout, " autohintexe -f [-e] [-n] " "[-q] [-s ] [-ra] [-rs] -a] [ " "... ]\n"); printVersions(); } static void printHelp(void) { printUsage(); fprintf(stdout, " -u usage\n"); fprintf(stdout, " -h help message\n"); fprintf(stdout, " -e do not edit (change) the paths when hinting\n"); fprintf(stdout, " -n no multiple layers of hinting\n"); fprintf(stdout, " -q quiet\n"); fprintf(stdout, " -f path to font info file\n"); fprintf(stdout, " -i This can be used instead of " "the -f parameter for data input \n"); fprintf(stdout, " [name2]..[nameN] paths to glyph bez files\n"); fprintf(stdout, " -b the last argument is bez data instead of a file " "name and the result will go to stdout\n"); fprintf( stdout, " -s Write output data to 'file name' + 'suffix', rather\n"); fprintf( stdout, " than writing it to the same file name as the input file.\n"); fprintf(stdout, " -ra Write alignment zones data. Does not hint or " "change glyph. Default extension is '.rpt'\n"); fprintf(stdout, " -rs Write stem widths data. Does not hint or " "change glyph. Default extension is '.rpt'\n"); fprintf(stdout, " -a Modifies -ra and -rs: Includes stems between " "curved lines: default is to omit these.\n"); fprintf(stdout, " -v print versions.\n"); } static void charZoneCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "charZone %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void stemZoneCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "stemZone %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void hstemCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "HStem %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void vstemCB(float right, float left, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "VStem %s right %f left %f\n", glyphName, (double)right, (double)left); } static void reportCB(char* msg, int level) { switch (level) { case AC_LogDebug: if (debug) fprintf(stderr, "DEBUG: %s\n", msg); break; case AC_LogInfo: if (verbose) fprintf(stderr, "INFO: %s\n", msg); break; case AC_LogWarning: if (verbose) fprintf(stderr, "WARNING: %s\n", msg); break; case AC_LogError: fprintf(stderr, "ERROR: %s\n", msg); break; default: break; } } static void reportRetry(void* userData) { if (userData) { ACBuffer* buffer = (ACBuffer*)userData; ACBufferReset(buffer); } } static char* getFileData(char* name) { char* data; struct stat filestat; if ((stat(name, &filestat)) < 0) { fprintf(stderr, "ERROR: Could not open file '%s'. Please check " "that it exists and is not write-protected.\n", name); exit(AC_FatalError); } if (filestat.st_size == 0) { fprintf(stderr, "ERROR: File '%s' has zero size.\n", name); exit(AC_FatalError); } data = malloc(filestat.st_size + 1); if (data == NULL) { fprintf(stderr, "ERROR: Could not allcoate memory for contents of file %s.\n", name); exit(AC_FatalError); } else { size_t fileSize = 0; FILE* fp = fopen(name, "r"); if (fp == NULL) { fprintf(stderr, "ERROR: Could not open file '%s'. Please check " "that it exists and is not write-protected.\n", name); exit(AC_FatalError); } fileSize = fread(data, 1, filestat.st_size, fp); data[fileSize] = 0; fclose(fp); } return data; } static void writeFileData(char* name, char* output, size_t outputsize, const char* fSuffix) { FILE* fp; if ((fSuffix != NULL) && (fSuffix[0] != '\0')) { char* savedName; size_t nameSize; nameSize = strlen(name) + strlen(fSuffix) + 1; savedName = malloc(nameSize); savedName[0] = '\0'; strcat(savedName, name); strcat(savedName, fSuffix); fp = fopen(savedName, "w"); free(savedName); } else fp = fopen(name, "w"); fwrite(output, 1, outputsize, fp); fclose(fp); } static FILE* openReportFile(char* name, const char* fSuffix) { FILE* file; if (fSuffix != NULL && fSuffix[0] != '\0') { char* savedName; size_t nameSize; nameSize = strlen(name) + strlen(fSuffix) + 1; savedName = malloc(nameSize); savedName[0] = '\0'; strcat(savedName, name); strcat(savedName, fSuffix); file = fopen(savedName, "w"); free(savedName); } else file = fopen(name, "w"); return file; } int main(int argc, char* argv[]) { /* See the discussion in the function definition for: autohintlib:control.c:Blues() static void Blues() */ bool allowEdit, roundCoords, allowHintSub, badParam, allStems; bool argumentIsBezData = false; bool doMM = false; bool report_zones = false, report_stems = false; char* fontInfoFileName = NULL; /* font info file name, or suffix of environment variable holding the fontfino string. */ char* fontinfo = NULL; /* the string of fontinfo data */ int firstFileNameIndex = -1; /* arg index for first bez file name, or suffix of environment variable holding the bez string. */ const char* fileSuffix = dfltExt; int total_files = 0; int result, argi; ACBuffer* reportBuffer = NULL; badParam = false; allStems = false; allowEdit = allowHintSub = roundCoords = true; /* read in options */ argi = 0; while (++argi < argc) { char* current_arg = argv[argi]; if (current_arg[0] == '\0') { continue; } else if (current_arg[0] != '-') { if (firstFileNameIndex == -1) { firstFileNameIndex = argi; } total_files++; continue; } else if (firstFileNameIndex != -1) { fprintf(stderr, "ERROR: Illegal command line. \"-\" option " "found after first file name.\n"); exit(1); } switch (current_arg[1]) { case '\0': badParam = true; break; case 'u': printUsage(); exit(0); case 'h': printHelp(); exit(0); case 'e': allowEdit = false; break; case 'd': roundCoords = false; break; case 'b': argumentIsBezData = true; break; case 'f': if (fontinfo != NULL) { fprintf(stderr, "ERROR: Illegal command line. \"-f\" " "can’t be used together with the " "\"-i\" command.\n"); exit(1); } fontInfoFileName = argv[++argi]; if ((fontInfoFileName[0] == '\0') || (fontInfoFileName[0] == '-')) { fprintf(stderr, "ERROR: Illegal command line. \"-f\" " "option must be followed by a file " "name.\n"); exit(1); } fontinfo = getFileData(fontInfoFileName); break; case 'i': if (fontinfo != NULL) { fprintf(stderr, "ERROR: Illegal command line. \"-i\" " "can’t be used together with the " "\"-f\" command.\n"); exit(1); } fontinfo = argv[++argi]; if ((fontinfo[0] == '\0') || (fontinfo[0] == '-')) { fprintf(stderr, "ERROR: Illegal command line. \"-i\" " "option must be followed by a font " "info string.\n"); exit(1); } break; case 's': fileSuffix = argv[++argi]; if ((fileSuffix[0] == '\0') || (fileSuffix[0] == '-')) { fprintf(stderr, "ERROR: Illegal command line. \"-s\" " "option must be followed by a string, " "and the string must not begin with " "'-'.\n"); exit(1); } break; case 'm': doMM = true; break; case 'n': allowHintSub = false; break; case 'q': verbose = false; break; case 'D': debug = true; break; case 'a': allStems = true; break; case 'r': allowEdit = allowHintSub = false; fileSuffix = reportExt; switch (current_arg[2]) { case 'a': report_zones = true; break; case 's': report_stems = true; break; default: fprintf(stderr, "ERROR: %s is an invalid parameter.\n", current_arg); badParam = true; break; } break; case 'v': printVersions(); exit(0); default: fprintf(stderr, "ERROR: %s is an invalid parameter.\n", current_arg); badParam = true; break; } } if (report_zones || report_stems) { reportBuffer = ACBufferNew(150); AC_SetReportRetryCB(reportRetry, (void*)reportBuffer); } if (report_zones) AC_SetReportZonesCB(charZoneCB, stemZoneCB, (void*)reportBuffer); if (report_stems) AC_SetReportStemsCB(hstemCB, vstemCB, allStems, (void*)reportBuffer); if (firstFileNameIndex == -1) { fprintf(stderr, "ERROR: Illegal command line. Must provide bez file name.\n"); badParam = true; } if (fontinfo == NULL) { fprintf(stderr, "ERROR: Illegal command line. Must provide font info.\n"); badParam = true; } if (badParam) exit(AC_InvalidParameterError); AC_SetReportCB(reportCB); argi = firstFileNameIndex - 1; if (!doMM) { while (++argi < argc) { char* bezdata; ACBuffer* output; char* bezName = argv[argi]; if (!argumentIsBezData) { bezdata = getFileData(bezName); } else { bezdata = bezName; } output = ACBufferNew(4 * strlen(bezdata)); if (reportBuffer) ACBufferReset(reportBuffer); result = AutoHintString(bezdata, fontinfo, output, allowEdit, allowHintSub, roundCoords); if (!argumentIsBezData) free(bezdata); if (result == AC_Success) { char* data; size_t len; if (reportBuffer) { ACBufferRead(reportBuffer, &data, &len); if (!argumentIsBezData) { FILE* file = openReportFile(bezName, fileSuffix); fwrite(data, 1, len, file); fclose(file); } else { fwrite(data, 1, len, stdout); } } else { ACBufferRead(output, &data, &len); if (!argumentIsBezData) writeFileData(bezName, data, len, fileSuffix); else fwrite(data, 1, len, stdout); } } ACBufferFree(output); output=NULL; if (result != AC_Success) exit(result); } } else /* assume files are MM bez files */ { /** MM support */ char** masters; /* master names - here, harwired to 001 - -1 */ char** inGlyphs; /* Input bez data */ ACBuffer** outGlyphs; /* output bez data */ ACBuffer* hintedGlyph; /* reference hinted glyph */ int i; masters = malloc(sizeof(char*) * total_files); inGlyphs = malloc(sizeof(char*) * total_files); outGlyphs = malloc(sizeof(ACBuffer*) * total_files); argi = firstFileNameIndex - 1; for (i = 0; i < total_files; i++) { char* bezName; argi++; bezName = argv[argi]; masters[i] = malloc(strlen(bezName) + 1); strcpy(masters[i], bezName); inGlyphs[i] = getFileData(bezName); outGlyphs[i] = ACBufferNew(4 * strlen(inGlyphs[i])); } hintedGlyph = ACBufferNew(4 * strlen(inGlyphs[0])); result = AutoHintString(inGlyphs[0], fontinfo, hintedGlyph, allowEdit, allowHintSub, roundCoords); if (result != AC_Success) exit(result); free(inGlyphs[0]); { char* data; size_t len; ACBufferRead(hintedGlyph, &data, &len); strncpy(inGlyphs[0], data, len); } result = AutoHintStringMM((const char**)inGlyphs, total_files, (const char**)masters, outGlyphs); for (i = 0; i < total_files; i++) { char* data; size_t len; ACBufferRead(outGlyphs[i], &data, &len); writeFileData(masters[i], data, len, "new"); free(masters[i]); free(inGlyphs[i]); ACBufferFree(outGlyphs[i]); outGlyphs[i] = NULL; } free(inGlyphs); free(outGlyphs); free(masters); if (result != AC_Success) exit(result); } if (fontInfoFileName) free(fontinfo); ACBufferFree(reportBuffer); reportBuffer = NULL; AC_initCallGlobals(); /* clear out references to reportBuffer */ return 0; } /* end of main */ psautohint-2.3.0/libpsautohint/include/000077500000000000000000000000001401523215600202215ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/include/psautohint.h000066400000000000000000000127201401523215600225720ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* The following ifdef block is the standard way of creating macros which make * exporting from a DLL simpler. All files within this DLL are compiled with * the ACLIB_API symbol defined on the command line. This symbol should not be * defined on any project that uses this DLL. This way any other project whose * source files include this file see ACLIB_API functions as being imported * from a DLL, wheras this DLL sees symbols defined with this macro as being * exported. */ #ifndef PSAUTOHINT_H_ #define PSAUTOHINT_H_ #include #ifdef __cplusplus extern "C" { #endif #ifdef AC_C_LIB_EXPORTS #ifdef _WIN32 #define ACLIB_API __declspec(dllexport) #elif __GNUC__ && __MACH__ #define ACLIB_API __attribute__((visibility("default"))) #else #define ACLIB_API #endif #else #define ACLIB_API #endif enum { AC_Success, AC_FatalError, AC_UnknownError, AC_InvalidParameterError }; enum { AC_LogDebug = -1, AC_LogInfo, AC_LogWarning, AC_LogError }; typedef struct ACBuffer ACBuffer; ACLIB_API ACBuffer* ACBufferNew(size_t size); ACLIB_API void ACBufferFree(ACBuffer* buffer); ACLIB_API void ACBufferReset(ACBuffer* buffer); ACLIB_API void ACBufferWrite(ACBuffer* buffer, char* data, size_t length); ACLIB_API void ACBufferWriteF(ACBuffer* buffer, char* format, ...); ACLIB_API void ACBufferRead(ACBuffer* buffer, char** data, size_t* length); /* * Function: AC_getVersion * * Returns AC library version. */ ACLIB_API const char* AC_getVersion(void); /* * Function: AC_SetMemManager * * If this is supplied, then the AC lib will call this function for all memory * allocations. Otherwise it will use alloc/malloc/free. */ typedef void* (*AC_MEMMANAGEFUNCPTR)(void* ctxptr, void* old, size_t size); ACLIB_API void AC_SetMemManager(void* ctxptr, AC_MEMMANAGEFUNCPTR func); /* * Function: AC_SetReportCB * * If this is supplied, then the AC lib will use this call back to report * messages. * */ typedef void (*AC_REPORTFUNCPTR)(char* msg, int level); ACLIB_API void AC_SetReportCB(AC_REPORTFUNCPTR reportCB); /* * Function: AC_SetReportStemsCB * * If this is called, then the AC lib will write all the stem widths it * encounters. * * If allStems is false, then stems defined by curves are excluded from the * reporting. * * userData is a pointer provided by the client, and will be passed to report * callback functions. * * Note that the callbacks should not dispose of the glyphName memory; that * belongs to the AC lib. It should be copied immediately - it may may last * past the return of the callback. */ typedef void (*AC_REPORTSTEMPTR)(float top, float bottom, char* glyphName, void* userData); ACLIB_API void AC_SetReportStemsCB(AC_REPORTSTEMPTR hstemCB, AC_REPORTSTEMPTR vstemCB, unsigned int allStems, void* userData); /* * Function: AC_SetReportZonesCB * * If this is called , then the AC lib will write all the alignment zones it * encounters. * * userData is a pointer provided by the client, and will be passed to report * callback functions. * * Note that the callbacks should not dispose of the glyphName memory; that * belongs to the AC lib. It should be copied immediately - it may may last * past the return of the callback. */ typedef void (*AC_REPORTZONEPTR)(float top, float bottom, char* glyphName, void* userData); ACLIB_API void AC_SetReportZonesCB(AC_REPORTZONEPTR charCB, AC_REPORTZONEPTR stemCB, void* userData); /* * Function: AC_SetReportRetryCB * * If this is called, then the AC lib will call this function when it wants to * discard the previous log content and start from scratch. * * This is to be used when AC_SetReportZonesCB or AC_SetReportStemsCB are used. */ typedef void (*AC_RETRYPTR)(void* userData); ACLIB_API void AC_SetReportRetryCB(AC_RETRYPTR retryCB, void* userData); /* * Function: AutoHintString * * This function takes srcbezdata, a pointer to null terminated C string * containing glyph data in the bez format (see bez spec) and fontinfo, a * pointer to null terminated C string containing fontinfo for the bez glyph. * * Hint information is added to the bez data and returned to the caller through * the buffer dstbezdata. dstbezdata must be allocated before the call and a * pointer to its length passed as *length. If the space allocated is * insufficient for the target bezdata, it will be reallocated as needed. */ ACLIB_API int AutoHintString(const char* srcbezdata, const char* fontinfo, ACBuffer* outbuffer, int allowEdit, int allowHintSub, int roundCoords); /* * Function: AutoHintStringMM * */ ACLIB_API int AutoHintStringMM(const char** srcbezdata, int nmasters, const char** masters, ACBuffer** outbuffers); /* * Function: AC_initCallGlobals * * This function must be called in the case where the program is switching * between any of the auto-hinting and stem reporting modes while running. */ ACLIB_API void AC_initCallGlobals(void); #ifdef __cplusplus } #endif #endif /* PSAUTOHINT_H_ */ psautohint-2.3.0/libpsautohint/meson.build000066400000000000000000000035671401523215600207530ustar00rootroot00000000000000project('psautohint', 'c', version : '1.7.0', default_options : 'c_std=c99') pkg = import('pkgconfig') cc = meson.get_compiler('c') version_h = vcs_tag(input : 'src/version.h.in', output : 'version.h') libm = cc.find_library('m', required : false) cflags = [ '-Wcast-align', '-Wdeclaration-after-statement', '-Wdouble-promotion', '-Wextra', '-Wimplicit-fallthrough', '-Wmissing-prototypes', '-Wmissing-variable-declarations', '-Wpedantic', '-Wstrict-prototypes', '-Wstringop-truncation', '-Wunreachable-code-break', '-Wunused-macros', '-Werror', ] add_global_arguments(cc.get_supported_arguments(cflags), language : 'c') libpsautohint = library( 'psautohint', 'include/psautohint.h', 'src/ac.c', 'src/acfixed.c', 'src/ac.h', 'src/auto.c', 'src/basic.h', 'src/bbox.c', 'src/bbox.h', 'src/buffer.c', 'src/charpath.c', 'src/charpath.h', 'src/charpathpriv.c', 'src/charprop.c', 'src/check.c', 'src/control.c', 'src/eval.c', 'src/fix.c', 'src/flat.c', 'src/fontinfo.c', 'src/fontinfo.h', 'src/gen.c', 'src/head.c', 'src/logging.c', 'src/logging.h', 'src/memory.c', 'src/memory.h', 'src/merge.c', 'src/misc.c', 'src/opcodes.h', 'src/optable.c', 'src/optable.h', 'src/pick.c', 'src/psautohint.c', 'src/read.c', 'src/report.c', 'src/shuffle.c', 'src/stemreport.c', 'src/write.c', version_h, include_directories : include_directories(['include']), c_args : '-DAC_C_LIB_EXPORTS', dependencies : libm, install : true, ) autohintexe = executable( 'autohintexe', 'autohintexe.c', include_directories : include_directories(['include']), link_with : libpsautohint, install : true, ) pkg.generate( libraries : libpsautohint, version : meson.project_version(), name : meson.project_name(), filebase : meson.project_name(), description : 'A library for autohinting PostScript fonts', ) psautohint-2.3.0/libpsautohint/psautohintexe/000077500000000000000000000000001401523215600214765ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/psautohintexe/libpsautohint/000077500000000000000000000000001401523215600243635ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/psautohintexe/libpsautohint/libpsautohint.xcodeproj/000077500000000000000000000000001401523215600312445ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/psautohintexe/libpsautohint/libpsautohint.xcodeproj/project.pbxproj000066400000000000000000000560241401523215600343270ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 56BD964E226E09C700452646 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 56BD964D226E09C700452646 /* buffer.c */; }; BD2C1182203DF53500D922B6 /* ac.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1165203DF53500D922B6 /* ac.c */; }; BD2C1183203DF53500D922B6 /* acfixed.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1166203DF53500D922B6 /* acfixed.c */; }; BD2C1184203DF53500D922B6 /* auto.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1167203DF53500D922B6 /* auto.c */; }; BD2C1185203DF53500D922B6 /* bbox.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1168203DF53500D922B6 /* bbox.c */; }; BD2C1186203DF53500D922B6 /* charpath.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1169203DF53500D922B6 /* charpath.c */; }; BD2C1187203DF53500D922B6 /* charpathpriv.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116A203DF53500D922B6 /* charpathpriv.c */; }; BD2C1188203DF53500D922B6 /* charprop.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116B203DF53500D922B6 /* charprop.c */; }; BD2C1189203DF53500D922B6 /* check.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116C203DF53500D922B6 /* check.c */; }; BD2C118A203DF53500D922B6 /* control.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116D203DF53500D922B6 /* control.c */; }; BD2C118B203DF53500D922B6 /* eval.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116E203DF53500D922B6 /* eval.c */; }; BD2C118C203DF53500D922B6 /* fix.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C116F203DF53500D922B6 /* fix.c */; }; BD2C118D203DF53500D922B6 /* flat.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1170203DF53500D922B6 /* flat.c */; }; BD2C118E203DF53500D922B6 /* fontinfo.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1171203DF53500D922B6 /* fontinfo.c */; }; BD2C118F203DF53500D922B6 /* gen.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1172203DF53500D922B6 /* gen.c */; }; BD2C1190203DF53500D922B6 /* head.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1173203DF53500D922B6 /* head.c */; }; BD2C1191203DF53500D922B6 /* logging.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1174203DF53500D922B6 /* logging.c */; }; BD2C1192203DF53500D922B6 /* memory.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1175203DF53500D922B6 /* memory.c */; }; BD2C1194203DF53500D922B6 /* merge.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1177203DF53500D922B6 /* merge.c */; }; BD2C1195203DF53500D922B6 /* misc.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1178203DF53500D922B6 /* misc.c */; }; BD2C1196203DF53500D922B6 /* optable.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1179203DF53500D922B6 /* optable.c */; }; BD2C1197203DF53500D922B6 /* pick.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117A203DF53500D922B6 /* pick.c */; }; BD2C1198203DF53500D922B6 /* psautohint.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117B203DF53500D922B6 /* psautohint.c */; }; BD2C1199203DF53500D922B6 /* read.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117C203DF53500D922B6 /* read.c */; }; BD2C119A203DF53500D922B6 /* report.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117D203DF53500D922B6 /* report.c */; }; BD2C119B203DF53500D922B6 /* shuffle.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117E203DF53500D922B6 /* shuffle.c */; }; BD2C119C203DF53500D922B6 /* stemreport.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C117F203DF53500D922B6 /* stemreport.c */; }; BD2C119E203DF53500D922B6 /* write.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C1181203DF53500D922B6 /* write.c */; }; BD2C11A0203DF55300D922B6 /* psautohint.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C119F203DF55300D922B6 /* psautohint.h */; settings = {ATTRIBUTES = (Public, ); }; }; BD2C11AB203DF56F00D922B6 /* ac.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A1203DF56F00D922B6 /* ac.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11AC203DF56F00D922B6 /* basic.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A2203DF56F00D922B6 /* basic.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11AD203DF56F00D922B6 /* bbox.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A3203DF56F00D922B6 /* bbox.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11AE203DF56F00D922B6 /* charpath.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A4203DF56F00D922B6 /* charpath.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11AF203DF56F00D922B6 /* fontinfo.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A5203DF56F00D922B6 /* fontinfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11B0203DF56F00D922B6 /* logging.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A6203DF56F00D922B6 /* logging.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11B1203DF56F00D922B6 /* memory.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A7203DF56F00D922B6 /* memory.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11B2203DF56F00D922B6 /* opcodes.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A8203DF56F00D922B6 /* opcodes.h */; settings = {ATTRIBUTES = (Private, ); }; }; BD2C11B3203DF56F00D922B6 /* optable.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2C11A9203DF56F00D922B6 /* optable.h */; settings = {ATTRIBUTES = (Private, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 56BD964D226E09C700452646 /* buffer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = buffer.c; sourceTree = ""; }; BD2C115E203DF50800D922B6 /* libpsautohint.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libpsautohint.a; sourceTree = BUILT_PRODUCTS_DIR; }; BD2C1165203DF53500D922B6 /* ac.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ac.c; sourceTree = ""; }; BD2C1166203DF53500D922B6 /* acfixed.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = acfixed.c; sourceTree = ""; }; BD2C1167203DF53500D922B6 /* auto.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = auto.c; sourceTree = ""; }; BD2C1168203DF53500D922B6 /* bbox.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = bbox.c; sourceTree = ""; }; BD2C1169203DF53500D922B6 /* charpath.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = charpath.c; sourceTree = ""; }; BD2C116A203DF53500D922B6 /* charpathpriv.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = charpathpriv.c; sourceTree = ""; }; BD2C116B203DF53500D922B6 /* charprop.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = charprop.c; sourceTree = ""; }; BD2C116C203DF53500D922B6 /* check.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = check.c; sourceTree = ""; }; BD2C116D203DF53500D922B6 /* control.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = control.c; sourceTree = ""; }; BD2C116E203DF53500D922B6 /* eval.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = eval.c; sourceTree = ""; }; BD2C116F203DF53500D922B6 /* fix.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fix.c; sourceTree = ""; }; BD2C1170203DF53500D922B6 /* flat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = flat.c; sourceTree = ""; }; BD2C1171203DF53500D922B6 /* fontinfo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fontinfo.c; sourceTree = ""; }; BD2C1172203DF53500D922B6 /* gen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = gen.c; sourceTree = ""; }; BD2C1173203DF53500D922B6 /* head.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = head.c; sourceTree = ""; }; BD2C1174203DF53500D922B6 /* logging.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = logging.c; sourceTree = ""; }; BD2C1175203DF53500D922B6 /* memory.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = memory.c; sourceTree = ""; }; BD2C1177203DF53500D922B6 /* merge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = merge.c; sourceTree = ""; }; BD2C1178203DF53500D922B6 /* misc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = misc.c; sourceTree = ""; }; BD2C1179203DF53500D922B6 /* optable.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = optable.c; sourceTree = ""; }; BD2C117A203DF53500D922B6 /* pick.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pick.c; sourceTree = ""; }; BD2C117B203DF53500D922B6 /* psautohint.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = psautohint.c; sourceTree = ""; }; BD2C117C203DF53500D922B6 /* read.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = read.c; sourceTree = ""; }; BD2C117D203DF53500D922B6 /* report.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = report.c; sourceTree = ""; }; BD2C117E203DF53500D922B6 /* shuffle.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = shuffle.c; sourceTree = ""; }; BD2C117F203DF53500D922B6 /* stemreport.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = stemreport.c; sourceTree = ""; }; BD2C1181203DF53500D922B6 /* write.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = write.c; sourceTree = ""; }; BD2C119F203DF55300D922B6 /* psautohint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = psautohint.h; path = ../include/psautohint.h; sourceTree = ""; }; BD2C11A1203DF56F00D922B6 /* ac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ac.h; sourceTree = ""; }; BD2C11A2203DF56F00D922B6 /* basic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = basic.h; sourceTree = ""; }; BD2C11A3203DF56F00D922B6 /* bbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bbox.h; sourceTree = ""; }; BD2C11A4203DF56F00D922B6 /* charpath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = charpath.h; sourceTree = ""; }; BD2C11A5203DF56F00D922B6 /* fontinfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fontinfo.h; sourceTree = ""; }; BD2C11A6203DF56F00D922B6 /* logging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = logging.h; sourceTree = ""; }; BD2C11A7203DF56F00D922B6 /* memory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = memory.h; sourceTree = ""; }; BD2C11A8203DF56F00D922B6 /* opcodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = opcodes.h; sourceTree = ""; }; BD2C11A9203DF56F00D922B6 /* optable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = optable.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ BD2C115B203DF50800D922B6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ BD2C1155203DF50800D922B6 = { isa = PBXGroup; children = ( BD2C11B5203DF57F00D922B6 /* include */, BD2C11B6203DF58B00D922B6 /* src */, BD2C115F203DF50800D922B6 /* Products */, ); sourceTree = ""; usesTabs = 0; }; BD2C115F203DF50800D922B6 /* Products */ = { isa = PBXGroup; children = ( BD2C115E203DF50800D922B6 /* libpsautohint.a */, ); name = Products; sourceTree = ""; }; BD2C11B5203DF57F00D922B6 /* include */ = { isa = PBXGroup; children = ( BD2C11A1203DF56F00D922B6 /* ac.h */, BD2C11A2203DF56F00D922B6 /* basic.h */, BD2C11A3203DF56F00D922B6 /* bbox.h */, BD2C11A4203DF56F00D922B6 /* charpath.h */, BD2C11A5203DF56F00D922B6 /* fontinfo.h */, BD2C11A6203DF56F00D922B6 /* logging.h */, BD2C11A7203DF56F00D922B6 /* memory.h */, BD2C11A8203DF56F00D922B6 /* opcodes.h */, BD2C11A9203DF56F00D922B6 /* optable.h */, BD2C119F203DF55300D922B6 /* psautohint.h */, ); name = include; path = ../../src; sourceTree = ""; }; BD2C11B6203DF58B00D922B6 /* src */ = { isa = PBXGroup; children = ( BD2C1165203DF53500D922B6 /* ac.c */, BD2C1166203DF53500D922B6 /* acfixed.c */, BD2C1167203DF53500D922B6 /* auto.c */, BD2C1168203DF53500D922B6 /* bbox.c */, 56BD964D226E09C700452646 /* buffer.c */, BD2C1169203DF53500D922B6 /* charpath.c */, BD2C116A203DF53500D922B6 /* charpathpriv.c */, BD2C116B203DF53500D922B6 /* charprop.c */, BD2C116C203DF53500D922B6 /* check.c */, BD2C116D203DF53500D922B6 /* control.c */, BD2C116E203DF53500D922B6 /* eval.c */, BD2C116F203DF53500D922B6 /* fix.c */, BD2C1170203DF53500D922B6 /* flat.c */, BD2C1171203DF53500D922B6 /* fontinfo.c */, BD2C1172203DF53500D922B6 /* gen.c */, BD2C1173203DF53500D922B6 /* head.c */, BD2C1174203DF53500D922B6 /* logging.c */, BD2C1175203DF53500D922B6 /* memory.c */, BD2C1177203DF53500D922B6 /* merge.c */, BD2C1178203DF53500D922B6 /* misc.c */, BD2C1179203DF53500D922B6 /* optable.c */, BD2C117A203DF53500D922B6 /* pick.c */, BD2C117B203DF53500D922B6 /* psautohint.c */, BD2C117C203DF53500D922B6 /* read.c */, BD2C117D203DF53500D922B6 /* report.c */, BD2C117E203DF53500D922B6 /* shuffle.c */, BD2C117F203DF53500D922B6 /* stemreport.c */, BD2C1181203DF53500D922B6 /* write.c */, ); name = src; path = ../../src; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ BD2C115C203DF50800D922B6 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( BD2C11A0203DF55300D922B6 /* psautohint.h in Headers */, BD2C11AB203DF56F00D922B6 /* ac.h in Headers */, BD2C11AC203DF56F00D922B6 /* basic.h in Headers */, BD2C11AD203DF56F00D922B6 /* bbox.h in Headers */, BD2C11AE203DF56F00D922B6 /* charpath.h in Headers */, BD2C11AF203DF56F00D922B6 /* fontinfo.h in Headers */, BD2C11B0203DF56F00D922B6 /* logging.h in Headers */, BD2C11B1203DF56F00D922B6 /* memory.h in Headers */, BD2C11B2203DF56F00D922B6 /* opcodes.h in Headers */, BD2C11B3203DF56F00D922B6 /* optable.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ BD2C115D203DF50800D922B6 /* libpsautohint */ = { isa = PBXNativeTarget; buildConfigurationList = BD2C1162203DF50800D922B6 /* Build configuration list for PBXNativeTarget "libpsautohint" */; buildPhases = ( 56BD964C226E098800452646 /* Set version number */, BD2C115A203DF50800D922B6 /* Sources */, BD2C115B203DF50800D922B6 /* Frameworks */, BD2C115C203DF50800D922B6 /* Headers */, ); buildRules = ( ); dependencies = ( ); name = libpsautohint; productName = libpsautohint; productReference = BD2C115E203DF50800D922B6 /* libpsautohint.a */; productType = "com.apple.product-type.library.static"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BD2C1156203DF50800D922B6 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0820; ORGANIZATIONNAME = rroberts; TargetAttributes = { BD2C115D203DF50800D922B6 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = BD2C1159203DF50800D922B6 /* Build configuration list for PBXProject "libpsautohint" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = BD2C1155203DF50800D922B6; productRefGroup = BD2C115F203DF50800D922B6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( BD2C115D203DF50800D922B6 /* libpsautohint */, ); }; /* End PBXProject section */ /* Begin PBXShellScriptBuildPhase section */ 56BD964C226E098800452646 /* Set version number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Set version number"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#!/bin/bash\n\ngit=$(sh /etc/profile; which git)\nnumber_of_commits=$(\"$git\" rev-list HEAD --count)\ngit_release_version=$(\"$git\" describe --tags)\n\ntarget_file=\"$SRCROOT/../../src/version.h\"\n/bin/cat <$target_file\n/* file generated by Xcode\n don't change, don't track in version control */\n#define PSAUTOHINT_VERSION \"$git_release_version\"\nEOM\n# echo \"warning: finished\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ BD2C115A203DF50800D922B6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BD2C1182203DF53500D922B6 /* ac.c in Sources */, BD2C1183203DF53500D922B6 /* acfixed.c in Sources */, BD2C1184203DF53500D922B6 /* auto.c in Sources */, BD2C1185203DF53500D922B6 /* bbox.c in Sources */, BD2C1186203DF53500D922B6 /* charpath.c in Sources */, BD2C1187203DF53500D922B6 /* charpathpriv.c in Sources */, BD2C1188203DF53500D922B6 /* charprop.c in Sources */, 56BD964E226E09C700452646 /* buffer.c in Sources */, BD2C1189203DF53500D922B6 /* check.c in Sources */, BD2C118A203DF53500D922B6 /* control.c in Sources */, BD2C118B203DF53500D922B6 /* eval.c in Sources */, BD2C118C203DF53500D922B6 /* fix.c in Sources */, BD2C118D203DF53500D922B6 /* flat.c in Sources */, BD2C118E203DF53500D922B6 /* fontinfo.c in Sources */, BD2C118F203DF53500D922B6 /* gen.c in Sources */, BD2C1190203DF53500D922B6 /* head.c in Sources */, BD2C1191203DF53500D922B6 /* logging.c in Sources */, BD2C1192203DF53500D922B6 /* memory.c in Sources */, BD2C1194203DF53500D922B6 /* merge.c in Sources */, BD2C1195203DF53500D922B6 /* misc.c in Sources */, BD2C1196203DF53500D922B6 /* optable.c in Sources */, BD2C1197203DF53500D922B6 /* pick.c in Sources */, BD2C1199203DF53500D922B6 /* read.c in Sources */, BD2C119A203DF53500D922B6 /* report.c in Sources */, BD2C119B203DF53500D922B6 /* shuffle.c in Sources */, BD2C119C203DF53500D922B6 /* stemreport.c in Sources */, BD2C119E203DF53500D922B6 /* write.c in Sources */, BD2C1198203DF53500D922B6 /* psautohint.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ BD2C1160203DF50800D922B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; BD2C1161203DF50800D922B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; name = Release; }; BD2C1163203DF50800D922B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { EXECUTABLE_PREFIX = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; BD2C1164203DF50800D922B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { EXECUTABLE_PREFIX = ""; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ BD2C1159203DF50800D922B6 /* Build configuration list for PBXProject "libpsautohint" */ = { isa = XCConfigurationList; buildConfigurations = ( BD2C1160203DF50800D922B6 /* Debug */, BD2C1161203DF50800D922B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BD2C1162203DF50800D922B6 /* Build configuration list for PBXNativeTarget "libpsautohint" */ = { isa = XCConfigurationList; buildConfigurations = ( BD2C1163203DF50800D922B6 /* Debug */, BD2C1164203DF50800D922B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BD2C1156203DF50800D922B6 /* Project object */; } psautohint-2.3.0/libpsautohint/psautohintexe/psautohintexe.xcodeproj/000077500000000000000000000000001401523215600263725ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/psautohintexe/psautohintexe.xcodeproj/project.pbxproj000066400000000000000000000263131401523215600314530ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ BD2C11C8203DF81200D922B6 /* autohintexe.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2C11C7203DF81200D922B6 /* autohintexe.c */; }; BD2C11DC203DFA8200D922B6 /* libpsautohint.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BD2C11D9203DFA4900D922B6 /* libpsautohint.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ BD2C11D8203DFA4900D922B6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BD2C11D4203DFA4900D922B6 /* libpsautohint.xcodeproj */; proxyType = 2; remoteGlobalIDString = BD2C115E203DF50800D922B6; remoteInfo = libpsautohint; }; BD2C11DA203DFA7800D922B6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BD2C11D4203DFA4900D922B6 /* libpsautohint.xcodeproj */; proxyType = 1; remoteGlobalIDString = BD2C115D203DF50800D922B6; remoteInfo = libpsautohint; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ BD2C1145203DF47200D922B6 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ BD2C1147203DF47200D922B6 /* psautohintexe */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = psautohintexe; sourceTree = BUILT_PRODUCTS_DIR; }; BD2C11B8203DF77000D922B6 /* libpsautohint.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpsautohint.a; path = "../../../../../../Users/rroberts/Library/Developer/Xcode/DerivedData/libpsautohint-adnprwhqznqfblergcjoongqpnfu/Build/Products/Debug/libpsautohint.a"; sourceTree = ""; }; BD2C11C6203DF7F200D922B6 /* psautohint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = psautohint.h; sourceTree = ""; }; BD2C11C7203DF81200D922B6 /* autohintexe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = autohintexe.c; path = ../autohintexe.c; sourceTree = ""; }; BD2C11D4203DFA4900D922B6 /* libpsautohint.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libpsautohint.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ BD2C1144203DF47200D922B6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( BD2C11DC203DFA8200D922B6 /* libpsautohint.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ BD2C113E203DF47200D922B6 = { isa = PBXGroup; children = ( BD2C11D3203DFA4900D922B6 /* libpsautohint */, BD2C11C9203DF81500D922B6 /* src */, BD2C11C5203DF7F200D922B6 /* include */, BD2C11CA203DF84000D922B6 /* lib */, BD2C1148203DF47200D922B6 /* Products */, BD2C11B7203DF77000D922B6 /* Frameworks */, ); sourceTree = ""; usesTabs = 0; }; BD2C1148203DF47200D922B6 /* Products */ = { isa = PBXGroup; children = ( BD2C1147203DF47200D922B6 /* psautohintexe */, ); name = Products; sourceTree = ""; }; BD2C11B7203DF77000D922B6 /* Frameworks */ = { isa = PBXGroup; children = ( BD2C11B8203DF77000D922B6 /* libpsautohint.a */, ); name = Frameworks; sourceTree = ""; }; BD2C11C5203DF7F200D922B6 /* include */ = { isa = PBXGroup; children = ( BD2C11C6203DF7F200D922B6 /* psautohint.h */, ); name = include; path = ../include; sourceTree = ""; }; BD2C11C9203DF81500D922B6 /* src */ = { isa = PBXGroup; children = ( BD2C11C7203DF81200D922B6 /* autohintexe.c */, ); name = src; sourceTree = ""; }; BD2C11CA203DF84000D922B6 /* lib */ = { isa = PBXGroup; children = ( ); name = lib; sourceTree = ""; }; BD2C11D3203DFA4900D922B6 /* libpsautohint */ = { isa = PBXGroup; children = ( BD2C11D4203DFA4900D922B6 /* libpsautohint.xcodeproj */, ); path = libpsautohint; sourceTree = ""; }; BD2C11D5203DFA4900D922B6 /* Products */ = { isa = PBXGroup; children = ( BD2C11D9203DFA4900D922B6 /* libpsautohint.a */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ BD2C1146203DF47200D922B6 /* psautohintexe */ = { isa = PBXNativeTarget; buildConfigurationList = BD2C114E203DF47200D922B6 /* Build configuration list for PBXNativeTarget "psautohintexe" */; buildPhases = ( BD2C1143203DF47200D922B6 /* Sources */, BD2C1144203DF47200D922B6 /* Frameworks */, BD2C1145203DF47200D922B6 /* CopyFiles */, ); buildRules = ( ); dependencies = ( BD2C11DB203DFA7800D922B6 /* PBXTargetDependency */, ); name = psautohintexe; productName = psautohintexe; productReference = BD2C1147203DF47200D922B6 /* psautohintexe */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ BD2C113F203DF47200D922B6 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0820; ORGANIZATIONNAME = rroberts; TargetAttributes = { BD2C1146203DF47200D922B6 = { CreatedOnToolsVersion = 8.2.1; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = BD2C1142203DF47200D922B6 /* Build configuration list for PBXProject "psautohintexe" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = BD2C113E203DF47200D922B6; productRefGroup = BD2C1148203DF47200D922B6 /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = BD2C11D5203DFA4900D922B6 /* Products */; ProjectRef = BD2C11D4203DFA4900D922B6 /* libpsautohint.xcodeproj */; }, ); projectRoot = ""; targets = ( BD2C1146203DF47200D922B6 /* psautohintexe */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ BD2C11D9203DFA4900D922B6 /* libpsautohint.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libpsautohint.a; remoteRef = BD2C11D8203DFA4900D922B6 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXSourcesBuildPhase section */ BD2C1143203DF47200D922B6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BD2C11C8203DF81200D922B6 /* autohintexe.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ BD2C11DB203DFA7800D922B6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = libpsautohint; targetProxy = BD2C11DA203DFA7800D922B6 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ BD2C114C203DF47200D922B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; BD2C114D203DF47200D922B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; name = Release; }; BD2C114F203DF47200D922B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; BD2C1150203DF47200D922B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ BD2C1142203DF47200D922B6 /* Build configuration list for PBXProject "psautohintexe" */ = { isa = XCConfigurationList; buildConfigurations = ( BD2C114C203DF47200D922B6 /* Debug */, BD2C114D203DF47200D922B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; BD2C114E203DF47200D922B6 /* Build configuration list for PBXNativeTarget "psautohintexe" */ = { isa = XCConfigurationList; buildConfigurations = ( BD2C114F203DF47200D922B6 /* Debug */, BD2C1150203DF47200D922B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = BD2C113F203DF47200D922B6 /* Project object */; } psautohint-2.3.0/libpsautohint/src/000077500000000000000000000000001401523215600173655ustar00rootroot00000000000000psautohint-2.3.0/libpsautohint/src/ac.c000066400000000000000000000115661401523215600201250ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "fontinfo.h" #define MAXSTEMDIST 150 /* initial maximum stem width allowed for hints */ PathElt *gPathStart, *gPathEnd; bool gUseV, gUseH, gAutoLinearCurveFix, gEditGlyph; bool gHasFlex, gFlexOK, gFlexStrict, gBandError; Fixed gHBigDist, gVBigDist, gInitBigDist, gMinDist, gGhostWidth, gGhostLength, gBendLength, gBandMargin, gMaxFlare, gMaxBendMerge, gMaxMerge, gMinHintElementLength, gFlexCand; Fixed gPruneA, gPruneB, gPruneC, gPruneD, gPruneValue, gBonus; float gTheta, gHBigDistR, gVBigDistR, gMaxVal, gMinVal; int32_t gLenTopBands, gLenBotBands, gNumSerifs, gDMin, gDelta, gCPpercent; int32_t gBendTan, gSCurveTan; HintVal *gVHinting, *gHHinting, *gVPrimary, *gHPrimary, *gValList; HintSeg* gSegLists[4]; Fixed gVStems[MAXSTEMS], gHStems[MAXSTEMS]; int32_t gNumVStems, gNumHStems; Fixed gTopBands[MAXBLUES], gBotBands[MAXBLUES], gSerifs[MAXSERIFS]; HintPoint *gPointList, **gPtLstArray; int32_t gPtLstIndex, gNumPtLsts, gMaxPtLsts; bool gWriteHintedBez = true; Fixed gBlueFuzz; bool gDoAligns = false, gDoStems = false; bool gRoundToInt; static int maxStemDist = MAXSTEMDIST; /* if false, then stems defined by curves are excluded from the reporting */ unsigned int gAllStems = false; AC_REPORTSTEMPTR gAddHStemCB = NULL; AC_REPORTSTEMPTR gAddVStemCB = NULL; AC_REPORTZONEPTR gAddGlyphExtremesCB = NULL; AC_REPORTZONEPTR gAddStemExtremesCB = NULL; AC_RETRYPTR gReportRetryCB = NULL; void* gAddStemUserData = NULL; void* gAddExtremesUserData = NULL; void* gReportRetryUserData = NULL; #define VMSIZE (1000000) static unsigned char *vmfree, *vmlast, vm[VMSIZE]; /* sub allocator */ void* Alloc(int32_t sz) { unsigned char* s; sz = (sz + 3) & ~3; /* make size a multiple of 4 */ s = vmfree; vmfree += sz; if (vmfree > vmlast) /* Error! need to make VMSIZE bigger */ { LogMsg(LOGERROR, FATALERROR, "Exceeded VM size for hints."); } return s; } void InitData(int32_t reason) { float tmp; gGlyphName[0] = '\0'; switch (reason) { case STARTUP: gDMin = 50; gDelta = 0; gInitBigDist = PSDist(maxStemDist); /* must be <= 168 for ITC Garamond Book Italic p, q, thorn */ gMinDist = PSDist(7); gGhostWidth = PSDist(20); gGhostLength = PSDist(4); gBendLength = PSDist(2); gBendTan = 577; /* 30 sin 30 cos div abs == .57735 */ gTheta = (float).38; /* must be <= .38 for Ryumin-Light-32 c49*/ gPruneA = FixInt(50); gPruneC = 100; gPruneD = FixOne; tmp = (float)10.24; /* set to 1024 times the threshold value */ gPruneValue = gPruneB = acpflttofix(&tmp); /* pruneB must be <= .01 for Yakout/Light/heM */ /* pruneValue must be <= .01 for Yakout/Light/heM */ gCPpercent = 40; /* must be < 46 for Americana-Bold d bowl vs stem hinting */ gBandMargin = PSDist(30); gMaxFlare = PSDist(10); gMaxBendMerge = PSDist(6); gMaxMerge = PSDist(2); /* must be < 3 for Cushing-BookItalic z */ gMinHintElementLength = PSDist(12); gFlexCand = PSDist(4); gSCurveTan = 25; gMaxVal = 8000000.0; gMinVal = 1.0f / (float)(FixOne); gEditGlyph = true; gRoundToInt = true; /* Default is to change a curve with collinear points into a line. */ gAutoLinearCurveFix = true; gFlexOK = false; gFlexStrict = true; gBlueFuzz = DEFAULTBLUEFUZZ; /* fall through */ case RESTART: memset((void*)vm, 0x0, VMSIZE); vmfree = vm; vmlast = vm + VMSIZE; /* ?? Does this cause a leak ?? */ gPointList = NULL; gMaxPtLsts = 128; gPtLstArray = (HintPoint**)Alloc(gMaxPtLsts * sizeof(HintPoint*)); gPtLstIndex = 0; gPtLstArray[0] = NULL; gNumPtLsts = 1; gAddHints = true; gVHinting = NULL; gHHinting = NULL; /* if (glyphName != NULL && glyphName[0] == 'g') showHintInfo = showHs = showVs = listHintInfo = true; */ } } /* Returns whether hinting was successful. */ bool AutoHint(const ACFontInfo* fontinfo, const char* srcbezdata, bool extrahint, bool changeGlyph, bool roundCoords) { InitAll(STARTUP); if (!ReadFontInfo(fontinfo)) return false; gEditGlyph = changeGlyph; gRoundToInt = roundCoords; gAutoLinearCurveFix = gEditGlyph; return AutoHintGlyph(srcbezdata, extrahint); } psautohint-2.3.0/libpsautohint/src/ac.h000066400000000000000000000333631401523215600201310ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* See the discussion in the function definition for: control.c:Blues() static void Blues() */ #ifndef AC_AC_H_ #define AC_AC_H_ #include "psautohint.h" #include "logging.h" #include "memory.h" /* widely used definitions */ /* values for HintSeg.sType */ #define sLINE (0) #define sBEND (1) #define sCURVE (2) #define sGHOST (3) /* values for PathElt.type */ #define MOVETO (0) #define LINETO (1) #define CURVETO (2) #define CLOSEPATH (3) /* values for pathelt control points */ #define cpStart (0) #define cpCurve1 (1) #define cpCurve2 (2) #define cpEnd (3) /* widths of ghost bands */ #define botGhst (-21) #define topGhst (-20) /* structures */ /* glyph point coordinates */ typedef struct { Fixed x, y; } Cd; typedef struct { int16_t limit; Fixed feps; void (*report)(Cd); Cd ll, ur; Fixed llx, lly; } FltnRec; typedef struct _hintseg { struct _hintseg *sNxt; /* points to next HintSeg in list */ /* separate lists for top, bottom, left, and right segments */ Fixed sLoc, sMax, sMin; /* sLoc is X loc for vertical seg, Y loc for horizontal seg */ /* sMax and sMin give Y extent for vertical seg, X extent for horizontal */ /* i.e., sTop=sMax, sBot=sMin, sLft=sMin, sRght=sMax. */ Fixed sBonus; /* nonzero for segments in sol-eol subpaths */ /* (probably a leftover that is no longer needed) */ struct _hintval *sLnk; /* points to the best HintVal that uses this HintSeg */ /* set by FindBestValForSegs in pick.c */ struct _pthelt *sElt; /* points to the path element that generated this HintSeg */ /* set by AddSegment in gen.c */ int16_t sType; /* tells what type of segment this is: sLINE sBEND sCURVE or sGHOST */ } HintSeg; typedef struct { HintSeg* seg; } SegLnk; typedef struct _seglnklst { struct _seglnklst *next; SegLnk* lnk; } SegLnkLst; #if 0 typedef struct _hintrep { Fixed vVal, vSpc, vLoc1, vLoc2; struct _hintval *vBst; } HintRep; typedef struct _hintval { struct _hintval *vNxt; Fixed vVal, vSpc, initVal; Fixed vLoc1, vLoc2; /* vBot=vLoc1, vTop=vLoc2, vLft=vLoc1, vRght=vLoc2 */ int16_t vGhst:8; int16_t pruned:8; HintSeg* vSeg1, *vSeg2; struct _hintval *vBst; HintRep* vRep; } HintVal; #else typedef struct _hintval { struct _hintval *vNxt; /* points to next HintVal in list */ Fixed vVal, vSpc, initVal; /* vVal is value given in eval.c */ /* vSpc is nonzero for "special" HintVals */ /* such as those with a segment in a blue zone */ /* initVal is the initially assigned value */ /* used by FndBstVal in pick.c */ Fixed vLoc1, vLoc2; /* vLoc1 is location corresponding to vSeg1 */ /* vLoc2 is location corresponding to vSeg2 */ /* for horizontal HintVal, vBot=vLoc1 and vTop=vLoc2 */ /* for vertical HintVal, vLft=vLoc1 and vRght=vLoc2 */ unsigned int vGhst:1; /* true iff one of the HintSegs is a sGHOST seg */ unsigned int pruned:1; /* flag used by FindBestHVals and FindBestVVals */ /* and by PruneVVals and PruneHVals */ unsigned int merge:1; /* flag used by ReplaceVals in merge.c */ unsigned int unused:13; HintSeg *vSeg1, *vSeg2; /* vSeg1 points to the left HintSeg in a vertical, bottom in a horizontal */ /* vSeg2 points to the right HintSeg in a vertical, top in a horizontal */ struct _hintval *vBst; /* points to another HintVal if this one has been merged or replaced */ } HintVal; #endif typedef struct _pthelt { struct _pthelt *prev, *next, *conflict; int16_t type; SegLnkLst *Hs, *Vs; bool Hcopy:1, Vcopy:1, isFlex:1, yFlex:1, newCP:1; unsigned int unused:9; int16_t count, newhints; Fixed x, y, x1, y1, x2, y2, x3, y3; } PathElt; typedef struct _hintpnt { struct _hintpnt *next; Fixed x0, y0, x1, y1; /* for vstem, only interested in x0 and x1 */ /* for hstem, only interested in y0 and y1 */ PathElt *p0, *p1; /* p0 is source of x0,y0; p1 is source of x1,y1 */ char c; /* tells what kind of hinting: 'b' 'y' 'm' or 'v' */ bool done; } HintPoint; typedef struct { char** keys; /* font information keys */ char** values; /* font information values */ size_t length; /* number of the entries */ } ACFontInfo; /* global data */ extern ACBuffer* gBezOutput; extern PathElt* gPathStart, *gPathEnd; extern bool gUseV, gUseH, gAutoLinearCurveFix; extern bool gEditGlyph; /* whether glyph can be modified when adding hints */ extern bool gBandError; extern bool gHasFlex, gFlexOK, gFlexStrict; extern Fixed gHBigDist, gVBigDist, gInitBigDist, gMinDist, gGhostWidth, gGhostLength, gBendLength, gBandMargin, gMaxFlare, gMaxBendMerge, gMaxMerge, gMinHintElementLength, gFlexCand; extern Fixed gPruneA, gPruneB, gPruneC, gPruneD, gPruneValue, gBonus; extern float gTheta, gHBigDistR, gVBigDistR, gMaxVal, gMinVal; extern int32_t gDMin, gDelta, gCPpercent, gBendTan, gSCurveTan; extern HintVal *gVHinting, *gHHinting, *gVPrimary, *gHPrimary, *gValList; extern HintSeg* gSegLists[4]; /* left, right, top, bot */ extern HintPoint* gPointList, **gPtLstArray; extern int32_t gPtLstIndex, gNumPtLsts, gMaxPtLsts; /* global callbacks */ /* if false, then stems defined by curves are excluded from the reporting */ extern unsigned int gAllStems; extern AC_REPORTSTEMPTR gAddHStemCB; extern AC_REPORTSTEMPTR gAddVStemCB; extern AC_REPORTZONEPTR gAddGlyphExtremesCB; extern AC_REPORTZONEPTR gAddStemExtremesCB; extern AC_RETRYPTR gReportRetryCB; extern void* gAddStemUserData; extern void* gAddExtremesUserData; extern void* gReportRetryUserData; void AddStemExtremes(Fixed bot, Fixed top); #define leftList (gSegLists[0]) #define rightList (gSegLists[1]) #define topList (gSegLists[2]) #define botList (gSegLists[3]) #define MAXFLEX (PSDist(20)) #define MAXBLUES (20) #define MAXSERIFS (5) extern Fixed gTopBands[MAXBLUES], gBotBands[MAXBLUES], gSerifs[MAXSERIFS]; extern int32_t gLenTopBands, gLenBotBands, gNumSerifs; #define MAXSTEMS (20) extern Fixed gVStems[MAXSTEMS], gHStems[MAXSTEMS]; extern int32_t gNumVStems, gNumHStems; extern char *gHHintList[], *gVHintList[]; extern int32_t gNumHHints, gNumVHints; extern bool gWriteHintedBez; extern Fixed gBlueFuzz; extern bool gDoAligns, gDoStems; extern bool gRoundToInt; extern bool gAddHints; #define MAX_GLYPHNAME_LEN 64 /* defined in read.c; set from the glyph name at the start of the bex file. */ extern char gGlyphName[MAX_GLYPHNAME_LEN]; /* macros */ #define FixOne (0x100) #define FixTwo (0x200) #define FixHalf (0x80) #define FixQuarter (0x40) #define FIXED_MAX INT32_MAX #define FIXED_MIN INT32_MIN #define FixInt(i) (((int32_t)(i)) * FixOne) #define FixReal(i) ((int32_t)((i) * (float)FixOne)) int32_t FRnd(int32_t x); #define FHalfRnd(x) ((int32_t)(((x)+(1<<7)) & ~0xFF)) #define FracPart(x) ((int32_t)(x) & 0xFF) #define FTrunc(x) (((int32_t)(x)) / FixOne) #define FIXED2FLOAT(x) (x / (float)FixOne) #define FixHalfMul(f) (2*((f) >> 2)) /* DEBUG 8 BIT. Revert this to ((f) >>1) once I am confident that there are not bugs from the update to 8 bits for the Fixed fraction. */ #define FixTwoMul(f) ((f) << 1) #define PSDist(d) ((FixInt(d))) #define IsVertical(x1,y1,x2,y2) (VertQuo(x1,y1,x2,y2) > 0) #define IsHorizontal(x1,y1,x2,y2) (HorzQuo(x1,y1,x2,y2) > 0) #define SFACTOR (20) /* SFACTOR must be < 25 for Gothic-Medium-22 c08 */ #define spcBonus (1000) #define ProdLt0(f0, f1) (((f0) < 0 && (f1) > 0) || ((f0) > 0 && (f1) < 0)) #define ProdGe0(f0, f1) (!ProdLt0(f0, f1)) #define DEBUG_ROUND(val) { val = ( val >=0 ) ? (2*(val/2)) : (2*((val - 1)/2));} /* DEBUG_ROUND is used to force calculations to come out the same as the previous version, where coordinates used 7 bits for the Fixed fraction, rather than the current 8 bits. Once I am confident that there are no bugs in the update, I will remove all occurences of this macro, and accept the differences due to more exact division */ /* procedures */ /* The fix to float and float to fixed procs are different for ac because it uses 24 bit of int32_t and 8 bits of fraction. */ void acfixtopflt(Fixed x, float* pf); Fixed acpflttofix(float* pf); void *Alloc(int32_t sz); /* Sub-allocator */ int AddCounterHintGlyphs(char* charlist, char* HintList[]); bool FindNameInList(char* nm, char** lst); void PruneElementHintSegs(void); int TestHintLst(SegLnkLst* lst, HintVal* hintList, bool flg, bool doLst); HintVal* CopyHints(HintVal* lst); void AutoExtraHints(bool movetoNewHints); int32_t SpecialGlyphType(void); bool VHintGlyph(void); bool HHintGlyph(void); bool NoBlueGlyph(void); bool MoveToNewHints(void); bool GetInflectionPoint(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed, Fixed, Fixed, Fixed *); void CheckSmooth(void); void CheckBBoxEdge(PathElt* e, bool vrt, Fixed lc, Fixed* pf, Fixed* pl); bool CheckSmoothness(Fixed x0, Fixed cy0, Fixed x1, Fixed cy1, Fixed x2, Fixed y2, Fixed* pd); void CheckForDups(void); void AddHintPoint(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch, PathElt* p0, PathElt* p1); void AddHPair(HintVal* v, char ch); void AddVPair(HintVal* v, char ch); void XtraHints(PathElt* e); bool AutoHintGlyph(const char* srcglyph, bool extrahint); void EvalV(void); void EvalH(void); void GenVPts(int32_t specialGlyphType); void CheckTfmVal(HintSeg* hSegList, Fixed* bandList, int32_t length); void CheckVals(HintVal* vlst, bool vert); bool FindLineSeg(Fixed loc, HintSeg* sL); void FltnCurve(Cd c0, Cd c1, Cd c2, Cd c3, FltnRec* pfr); bool InBlueBand(Fixed loc, int32_t n, Fixed* p); void GenHPts(void); void PreGenPts(void); PathElt* GetDest(PathElt* cldest); PathElt* GetClosedBy(PathElt* clsdby); void GetEndPoint(PathElt* e, Fixed* x1p, Fixed* y1p); void GetEndPoints(PathElt* p, Fixed* px0, Fixed* py0, Fixed* px1, Fixed* py1); Fixed VertQuo(Fixed xk, Fixed yk, Fixed xl, Fixed yl); Fixed HorzQuo(Fixed xk, Fixed yk, Fixed xl, Fixed yl); bool IsTiny(PathElt* e); bool IsShort(PathElt* e); PathElt* NxtForBend(PathElt* p, Fixed* px2, Fixed* py2, Fixed* px3, Fixed* py3); PathElt* PrvForBend(PathElt* p, Fixed* px2, Fixed* py2); bool IsLower(PathElt* p); bool IsUpper(PathElt* p); bool CloseSegs(HintSeg* s1, HintSeg* s2, bool vert); void DoPrune(void); void PruneVVals(void); void PruneHVals(void); void MergeVals(bool vert); void MergeFromMainHints(char ch); void RoundPathCoords(void); void MoveSubpathToEnd(PathElt* e); void InitData(int32_t reason); void InitFix(int32_t reason); void InitGen(int32_t reason); void InitPick(int32_t reason); void AutoAddFlex(void); bool SameHints(int32_t cn1, int32_t cn2); bool PreCheckForHinting(void); int32_t CountSubPaths(void); void PickVVals(HintVal* gValList); void PickHVals(HintVal* gValList); void FindBestHVals(void); void FindBestVVals(void); void ReportAddFlex(void); void ReportLinearCurve(PathElt* e, Fixed x0, Fixed y0, Fixed x1, Fixed y1); void ReportNonHError(Fixed x0, Fixed y0, Fixed x1, Fixed y1); void ReportNonVError(Fixed x0, Fixed y0, Fixed x1, Fixed y1); void ExpectedMoveTo(PathElt* e); void ReportMissingClosePath(void); void ReportTryFlexNearMiss(Fixed x0, Fixed y0, Fixed x2, Fixed y2); void ReportTryFlexError(bool CPflg, Fixed x, Fixed y); void ReportSplit(PathElt* e); void ReportRemFlare(PathElt* e, PathElt* e2, bool hFlg, int32_t i); void ReportRemConflict(PathElt* e); void ReportRemShortHints(Fixed ex, Fixed ey); bool ResolveConflictBySplit(PathElt* e, bool Hflg, SegLnkLst* lnk1, SegLnkLst* lnk2); void ReportPossibleLoop(PathElt* e); void ShowHVal(HintVal* val); void ShowHVals(HintVal* lst); void ReportAddHVal(HintVal* val); void ShowVVal(HintVal* val); void ShowVVals(HintVal* lst); void ReportAddVVal(HintVal* val); void ReportFndBstVal(HintSeg* seg, HintVal* val, bool hFlg); void ReportCarry(Fixed l0, Fixed l1, Fixed loc, HintVal* hints, bool vert); void LogHintInfo(HintPoint* pl); void ReportStemNearMiss(bool vert, Fixed w, Fixed minW, Fixed b, Fixed t, bool curve); void ReportHintConflict(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch); void ReportDuplicates(Fixed x, Fixed y); void ReportBBoxBogus(Fixed llx, Fixed lly, Fixed urx, Fixed ury); void ReportMergeHVal(Fixed b0, Fixed t0, Fixed b1, Fixed t1, Fixed v0, Fixed s0, Fixed v1, Fixed s1); void ReportMergeVVal(Fixed l0, Fixed r0, Fixed l1, Fixed r1, Fixed v0, Fixed s0, Fixed v1, Fixed s1); void ReportPruneHVal(HintVal* val, HintVal* v, int32_t i); void ReportPruneVVal(HintVal* val, HintVal* v, int32_t i); unsigned char* InitShuffleSubpaths(void); void MarkLinks(HintVal* vL, bool hFlg, unsigned char* links); void DoShuffleSubpaths(unsigned char* links); void CopyMainH(void); void CopyMainV(void); void RMovePoint(Fixed dx, Fixed dy, int32_t whichcp, PathElt* e); void AddVSegment(Fixed from, Fixed to, Fixed loc, PathElt* p1, PathElt* p2, int32_t typ, int32_t i); void AddHSegment(Fixed from, Fixed to, Fixed loc, PathElt* p1, PathElt* p2, int32_t typ, int32_t i); void Delete(PathElt* e); bool ReadGlyph(const char* srcglyph, bool forBlendData, bool readHints); double FixToDbl(Fixed f); bool CompareValues(HintVal* val1, HintVal* val2, int32_t factor, int32_t ghstshift); void SaveFile(void); void CheckForMultiMoveTo(void); #define STARTUP (0) #define RESTART (1) void ListHintInfo(void); void InitAll(int32_t reason); void AddVStem(Fixed right, Fixed left, bool curved); void AddHStem(Fixed top, Fixed bottom, bool curved); void AddGlyphExtremes(Fixed bot, Fixed top); bool AutoHint(const ACFontInfo* fontinfo, const char* srcbezdata, bool extrahint, bool changeGlyph, bool roundCoords); bool MergeGlyphPaths(const char** srcglyphs, int nmasters, const char** masters, ACBuffer** outbuffers); #endif /* AC_AC_H_ */ psautohint-2.3.0/libpsautohint/src/acfixed.c000066400000000000000000000011431401523215600211330ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #define FIXEDSCALE ((float)(FixOne)) void acfixtopflt(Fixed x, float* pf) { *pf = (float)x / FIXEDSCALE; } Fixed acpflttofix(float* pf) { float f = *pf; if (f >= FIXED_MAX / FIXEDSCALE) return FIXED_MAX; if (f <= FIXED_MIN / FIXEDSCALE) return FIXED_MIN; return (Fixed)(f * FIXEDSCALE); } psautohint-2.3.0/libpsautohint/src/auto.c000066400000000000000000000704031401523215600205050ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "bbox.h" static bool mergeMain; static PathElt* GetSubPathNxt(PathElt* e) { if (e->type == CLOSEPATH) return GetDest(e); return e->next; } static PathElt* GetSubPathPrv(PathElt* e) { if (e->type == MOVETO) e = GetClosedBy(e); return e->prev; } static HintVal* FindClosestVal(HintVal* sLst, Fixed loc) { Fixed dist = FixInt(10000); HintVal* best = NULL; while (sLst != NULL) { Fixed bot, top, d; bot = sLst->vLoc1; top = sLst->vLoc2; if (bot > top) { Fixed tmp = bot; bot = top; top = tmp; } if (loc >= bot && loc <= top) { best = sLst; break; } if (loc < bot) d = bot - loc; else d = loc - top; if (d < dist) { dist = d; best = sLst; } sLst = sLst->vNxt; } return best; } static void CpyHHint(PathElt* e) { Fixed x1, y1; HintVal* best; GetEndPoint(e, &x1, &y1); best = FindClosestVal(gHPrimary, y1); if (best != NULL) AddHPair(best, 'b'); } static void CpyVHint(PathElt* e) { Fixed x1, y1; HintVal* best; GetEndPoint(e, &x1, &y1); best = FindClosestVal(gVPrimary, x1); if (best != NULL) AddVPair(best, 'y'); } static void PruneHintSegs(PathElt* e, bool hFlg) { SegLnkLst *lst, *nxt, *prv; HintSeg* seg; lst = hFlg ? e->Hs : e->Vs; prv = NULL; while (lst != NULL) { HintVal* val = NULL; SegLnk* lnk = lst->lnk; if (lnk != NULL) { seg = lnk->seg; if (seg != NULL) val = seg->sLnk; } nxt = lst->next; if (val == NULL) { /* prune this one */ if (prv == NULL) { if (hFlg) e->Hs = nxt; else e->Vs = nxt; } else prv->next = nxt; lst = nxt; } else { prv = lst; lst = nxt; } } } void PruneElementHintSegs(void) { PathElt* e; e = gPathStart; while (e != NULL) { PruneHintSegs(e, true); PruneHintSegs(e, false); e = e->next; } } #define ElmntHintSegLst(e, hFlg) (hFlg) ? (e)->Hs : (e)->Vs static void RemLnk(PathElt* e, bool hFlg, SegLnkLst* rm) { SegLnkLst *lst, *prv, *nxt; lst = hFlg ? e->Hs : e->Vs; prv = NULL; while (lst != NULL) { nxt = lst->next; if (lst == rm) { if (prv == NULL) { if (hFlg) e->Hs = nxt; else e->Vs = nxt; } else prv->next = nxt; return; } prv = lst; lst = nxt; } LogMsg(LOGERROR, NONFATALERROR, "Badly formatted segment list."); } static bool AlreadyOnList(HintVal* v, HintVal* lst) { while (lst != NULL) { if (v == lst) return true; lst = lst->vNxt; } return false; } static void AutoVSeg(HintVal* sLst) { AddVPair(sLst, 'y'); } static void AutoHSeg(HintVal* sLst) { AddHPair(sLst, 'b'); } static void AddHHinting(HintVal* h) { if (gUseH || AlreadyOnList(h, gHHinting)) return; h->vNxt = gHHinting; gHHinting = h; AutoHSeg(h); } static void AddVHinting(HintVal* v) { if (gUseV || AlreadyOnList(v, gVHinting)) return; v->vNxt = gVHinting; gVHinting = v; AutoVSeg(v); } static int32_t TestHint(HintSeg* s, HintVal* hintList, bool flg, bool doLst) { /* -1 means already in hintList; 0 means conflicts; 1 means ok to add */ HintVal *v, *clst; Fixed top, bot, vT, vB, loc; if (s == NULL) return -1; v = s->sLnk; loc = s->sLoc; if (v == NULL) return -1; vT = top = v->vLoc2; vB = bot = v->vLoc1; if (v->vGhst) { /* collapse width for conflict test */ if (v->vSeg1->sType == sGHOST) bot = top; else top = bot; } { int32_t cnt = 0; clst = hintList; while (clst != NULL) { if (++cnt > 100) { LogMsg(LOGDEBUG, OK, "Loop in hintlist for TestHint."); return 0; } clst = clst->vNxt; } } if (v->vGhst) { bool loc1; /* if best value for segment uses a ghost, and segment loc is already in the hintList, then return -1 */ clst = hintList; if (abs(loc - vT) < abs(loc - vB)) { loc1 = false; loc = vT; } else { loc1 = true; loc = vB; } while (clst != NULL) { if ((loc1 ? clst->vLoc1 : clst->vLoc2) == loc) return -1; clst = clst->vNxt; if (!doLst) break; } } if (flg) { top += gBandMargin; bot -= gBandMargin; } else { top -= gBandMargin; bot += gBandMargin; } while (hintList != NULL) { /* check for conflict */ Fixed cTop = hintList->vLoc2; Fixed cBot = hintList->vLoc1; if (vB == cBot && vT == cTop) { return -1; } if (hintList->vGhst) { /* collapse width for conflict test */ if (hintList->vSeg1->sType == sGHOST) cBot = cTop; else cTop = cBot; } if ((flg && (cBot <= top) && (cTop >= bot)) || (!flg && (cBot >= top) && (cTop <= bot))) { return 0; } hintList = hintList->vNxt; if (!doLst) break; } return 1; } #define TestHHintLst(h) TestHintLst(h, gHHinting, false, true) #define TestVHintLst(v) TestHintLst(v, gVHinting, true, true) int TestHintLst(SegLnkLst* lst, HintVal* hintList, bool flg, bool doLst) { /* -1 means already in hintList; 0 means conflicts; 1 means ok to add */ int result, cnt; result = -1; cnt = 0; while (lst != NULL) { int i = TestHint(lst->lnk->seg, hintList, flg, doLst); if (i == 0) { result = 0; break; } if (i == 1) result = 1; lst = lst->next; if (++cnt > 100) { LogMsg(WARNING, OK, "Looping in TestHintLst."); return 0; } } return result; } #define FixedMidPoint(m, a, b) \ (m).x = ((a).x + (b).x) >> 1; \ (m).y = ((a).y + (b).y) >> 1 #define FixedBezDiv(a0, a1, a2, a3, b0, b1, b2, b3) \ FixedMidPoint(b2, a2, a3); \ FixedMidPoint(a3, a1, a2); \ FixedMidPoint(a1, a0, a1); \ FixedMidPoint(a2, a1, a3); \ FixedMidPoint(b1, a3, b2); \ FixedMidPoint(a3, a2, b1); bool ResolveConflictBySplit(PathElt* e, bool Hflg, SegLnkLst* lnk1, SegLnkLst* lnk2) { /* insert new pathelt immediately following e */ /* e gets first half of split; new gets second */ /* e gets lnk1 in Hs or Vs; new gets lnk2 */ PathElt* new; Cd d0, d1, d2, d3, d4, d5, d6, d7; if (e->type != CURVETO || e->isFlex) return false; ReportSplit(e); new = (PathElt*)Alloc(sizeof(PathElt)); new->next = e->next; e->next = new; new->prev = e; if (new->next == NULL) gPathEnd = new; else new->next->prev = new; if (Hflg) { e->Hs = lnk1; new->Hs = lnk2; } else { e->Vs = lnk1; new->Vs = lnk2; } if (lnk1 != NULL) lnk1->next = NULL; if (lnk2 != NULL) lnk2->next = NULL; new->type = CURVETO; GetEndPoint(e->prev, &d0.x, &d0.y); d1.x = e->x1; d1.y = e->y1; d2.x = e->x2; d2.y = e->y2; d3.x = e->x3; d3.y = e->y3; d4 = d0; d5 = d1; d6 = d2; d7 = d3; new->x3 = d3.x; new->y3 = d3.y; FixedBezDiv(d4, d5, d6, d7, d0, d1, d2, d3); e->x1 = d5.x; e->y1 = d5.y; e->x2 = d6.x; e->y2 = d6.y; e->x3 = d7.x; e->y3 = d7.y; new->x1 = d1.x; new->y1 = d1.y; new->x2 = d2.x; new->y2 = d2.y; return true; } static void RemDupLnks(PathElt* e, bool Hflg) { SegLnkLst *l1, *l2, *l2nxt; l1 = Hflg ? e->Hs : e->Vs; while (l1 != NULL) { l2 = l1->next; while (l2 != NULL) { l2nxt = l2->next; if (l1->lnk->seg == l2->lnk->seg) RemLnk(e, Hflg, l2); l2 = l2nxt; } l1 = l1->next; } } #define OkToRemLnk(loc, Hflg, spc) \ (!(Hflg) || (spc) == 0 || \ (!InBlueBand((loc), gLenTopBands, gTopBands) && \ !InBlueBand((loc), gLenBotBands, gBotBands))) /* The changes made here were to fix a problem in MinisterLight/E. The top left point was not getting hinted. */ static bool TryResolveConflict(PathElt* e, bool Hflg) { int32_t typ; SegLnkLst *lst, *lnk1, *lnk2; HintSeg *seg, *seg1, *seg2; HintVal *val1, *val2; Fixed lc1, lc2, loc0, loc1, loc2, loc3, x0, y0, x1, y1; RemDupLnks(e, Hflg); typ = e->type; if (typ == MOVETO) GetEndPoints(GetClosedBy(e), &x0, &y0, &x1, &y1); else if (typ == CURVETO) { x0 = e->x1; y0 = e->y1; x1 = e->x3; y1 = e->y3; } else GetEndPoints(e, &x0, &y0, &x1, &y1); loc1 = Hflg ? y0 : x0; loc2 = Hflg ? y1 : x1; lst = Hflg ? e->Hs : e->Vs; seg1 = lst->lnk->seg; lc1 = seg1->sLoc; lnk1 = lst; lst = lst->next; if (lst == NULL) return false; seg2 = lst->lnk->seg; lc2 = seg2->sLoc; lnk2 = lst; if (lc1 == loc1 || lc2 == loc2) { /* do nothing */ } else if (abs(lc1 - loc1) > abs(lc1 - loc2) || abs(lc2 - loc2) > abs(lc2 - loc1)) { seg = seg1; seg1 = seg2; seg2 = seg; lst = lnk1; lnk1 = lnk2; lnk2 = lst; } val1 = seg1->sLnk; val2 = seg2->sLnk; if (val1->vVal < FixInt(50) && OkToRemLnk(loc1, Hflg, val1->vSpc)) { RemLnk(e, Hflg, lnk1); ReportRemConflict(e); return true; } if (val2->vVal < FixInt(50) && val1->vVal > val2->vVal * 20 && OkToRemLnk(loc2, Hflg, val2->vSpc)) { RemLnk(e, Hflg, lnk2); ReportRemConflict(e); return true; } if (typ != CURVETO || ((((Hflg && IsHorizontal(x0, y0, x1, y1)) || (!Hflg && IsVertical(x0, y0, x1, y1)))) && OkToRemLnk(loc1, Hflg, val1->vSpc))) { RemLnk(e, Hflg, lnk1); ReportRemConflict(e); return true; } GetEndPoints(GetSubPathPrv(e), &x0, &y0, &x1, &y1); loc0 = Hflg ? y0 : x0; if (ProdLt0(loc2 - loc1, loc0 - loc1)) { RemLnk(e, Hflg, lnk1); ReportRemConflict(e); return true; } GetEndPoint(GetSubPathNxt(e), &x1, &y1); loc3 = Hflg ? y1 : x1; if (ProdLt0(loc3 - loc2, loc1 - loc2)) { RemLnk(e, Hflg, lnk2); ReportRemConflict(e); return true; } if ((loc2 == val2->vLoc1 || loc2 == val2->vLoc2) && loc1 != val1->vLoc1 && loc1 != val1->vLoc2) { RemLnk(e, Hflg, lnk1); ReportRemConflict(e); return true; } if ((loc1 == val1->vLoc1 || loc1 == val1->vLoc2) && loc2 != val2->vLoc1 && loc2 != val2->vLoc2) { RemLnk(e, Hflg, lnk2); ReportRemConflict(e); return true; } if (gEditGlyph && ResolveConflictBySplit(e, Hflg, lnk1, lnk2)) return true; else return false; } static bool CheckHintSegs(PathElt* e, bool flg, bool Hflg) { SegLnkLst* lst; SegLnkLst* lst2; HintSeg* seg; HintVal* val; lst = Hflg ? e->Hs : e->Vs; while (lst != NULL) { lst2 = lst->next; if (lst2 != NULL) { seg = lst->lnk->seg; val = seg->sLnk; if (val != NULL && TestHintLst(lst2, val, flg, false) == 0) { if (TryResolveConflict(e, Hflg)) return CheckHintSegs(e, flg, Hflg); if (Hflg) e->Hs = NULL; else e->Vs = NULL; return true; } } lst = lst2; } return false; } static void CheckElmntHintSegs(void) { PathElt* e; e = gPathStart; while (e != NULL) { if (!CheckHintSegs(e, false, true)) CheckHintSegs(e, true, false); e = e->next; } } static bool HintLstsClash(SegLnkLst* lst1, SegLnkLst* lst2, bool flg) { while (lst1 != NULL) { HintSeg* seg = lst1->lnk->seg; HintVal* val = seg->sLnk; if (val != NULL) { SegLnkLst* lst = lst2; while (lst != NULL) { if (TestHintLst(lst, val, flg, false) == 0) { return true; } lst = lst->next; } } lst1 = lst1->next; } return false; } static SegLnkLst* BestFromLsts(SegLnkLst* lst1, SegLnkLst* lst2) { SegLnkLst* bst = NULL; Fixed bstval = 0; int32_t i; for (i = 0; i < 2; i++) { SegLnkLst* lst = i ? lst1 : lst2; while (lst != NULL) { HintSeg* seg = lst->lnk->seg; HintVal* val = seg->sLnk; if (val != NULL && val->vVal > bstval) { bst = lst; bstval = val->vVal; } lst = lst->next; } } return bst; } static bool HintsClash(PathElt* e, PathElt* p, SegLnkLst** hLst, SegLnkLst** vLst, SegLnkLst** phLst, SegLnkLst** pvLst) { bool clash = false; SegLnkLst *bst, *new; if (HintLstsClash(*hLst, *phLst, false)) { clash = true; bst = BestFromLsts(*hLst, *phLst); if (bst) { new = (SegLnkLst*)Alloc(sizeof(SegLnkLst)); new->next = NULL; new->lnk = bst->lnk; } else new = NULL; e->Hs = p->Hs = *hLst = *phLst = new; } if (HintLstsClash(*vLst, *pvLst, true)) { clash = true; bst = BestFromLsts(*vLst, *pvLst); if (bst) { new = (SegLnkLst*)Alloc(sizeof(SegLnkLst)); new->next = NULL; new->lnk = bst->lnk; } else new = NULL; e->Vs = p->Vs = *vLst = *pvLst = new; } return clash; } static void GetHintLsts(PathElt* e, SegLnkLst** phLst, SegLnkLst** pvLst, int32_t* ph, int32_t* pv) { SegLnkLst *hLst, *vLst; int32_t h, v; if (gUseH) { hLst = NULL; h = -1; } else { hLst = e->Hs; if (hLst == NULL) h = -1; else h = TestHHintLst(hLst); } if (gUseV) { vLst = NULL; v = -1; } else { vLst = e->Vs; if (vLst == NULL) v = -1; else v = TestVHintLst(vLst); } *pvLst = vLst; *phLst = hLst; *ph = h; *pv = v; } static void ReHintBounds(PathElt* e) { if (!gUseH) { if (gHHinting == NULL) CpyHHint(e); if (mergeMain) MergeFromMainHints('b'); } if (!gUseV) { if (gVHinting == NULL) CpyVHint(e); if (mergeMain) MergeFromMainHints('y'); } } static void AddHintLst(SegLnkLst* lst, bool vert) { while (lst != NULL) { HintSeg* seg = lst->lnk->seg; HintVal* val = seg->sLnk; if (vert) AddVHinting(val); else AddHHinting(val); lst = lst->next; } } static void StartNewHinting(PathElt* e, SegLnkLst* hLst, SegLnkLst* vLst) { ReHintBounds(e); if (e->newhints != 0) { LogMsg(LOGERROR, NONFATALERROR, "Uninitialized extra hints list."); } XtraHints(e); if (gUseV) CopyMainV(); if (gUseH) CopyMainH(); gHHinting = gVHinting = NULL; if (!gUseH) AddHintLst(hLst, false); if (!gUseV) AddHintLst(vLst, true); } static bool IsIn(int32_t h, int32_t v) { return (h == -1 && v == -1); } static bool IsOk(int32_t h, int32_t v) { return (h != 0 && v != 0); } #define AddIfNeedV(v, vLst) \ if (!gUseV && v == 1) \ AddHintLst(vLst, true) #define AddIfNeedH(h, hLst) \ if (!gUseH && h == 1) \ AddHintLst(hLst, false) static void SetHHints(HintVal* lst) { if (gUseH) return; gHHinting = lst; while (lst != NULL) { AutoHSeg(lst); lst = lst->vNxt; } } static void SetVHints(HintVal* lst) { if (gUseV) return; gVHinting = lst; while (lst != NULL) { AutoVSeg(lst); lst = lst->vNxt; } } HintVal* CopyHints(HintVal* lst) { HintVal* vlst; int cnt; vlst = NULL; cnt = 0; while (lst != NULL) { HintVal* v = (HintVal*)Alloc(sizeof(HintVal)); *v = *lst; v->vNxt = vlst; vlst = v; if (++cnt > 100) { LogMsg(WARNING, OK, "Loop in CopyHints."); return vlst; } lst = lst->vNxt; } return vlst; } static bool IsFlare(Fixed loc, PathElt* e, PathElt* n, bool Hflg) { Fixed x, y; while (e != n) { GetEndPoint(e, &x, &y); if ((Hflg && abs(y - loc) > gMaxFlare) || (!Hflg && abs(x - loc) > gMaxFlare)) return false; e = GetSubPathNxt(e); } return true; } static bool IsTopSegOfVal(Fixed loc, Fixed top, Fixed bot) { Fixed d1, d2; d1 = top - loc; d2 = bot - loc; return (abs(d1) <= abs(d2)) ? true : false; } static void RemFlareLnk(PathElt* e, bool hFlg, SegLnkLst* rm, PathElt* e2, int32_t i) { RemLnk(e, hFlg, rm); ReportRemFlare(e, e2, hFlg, i); } bool CompareValues(HintVal* val1, HintVal* val2, int32_t factor, int32_t ghstshift) { Fixed v1 = val1->vVal, v2 = val2->vVal, mx; mx = NUMMAX(v1, v2); while (mx < FIXED_MAX / 2) { mx *= 2; v1 *= 2; v2 *= 2; } if (ghstshift > 0 && val1->vGhst != val2->vGhst) { if (val1->vGhst) v1 >>= ghstshift; if (val2->vGhst) v2 >>= ghstshift; } if ((val1->vSpc > 0 && val2->vSpc > 0) || (val1->vSpc == 0 && val2->vSpc == 0)) return v1 > v2; if (val1->vSpc > 0) return (v1 < FIXED_MAX / factor) ? (v1 * factor > v2) : (v1 > v2 / factor); return (v2 < FIXED_MAX / factor) ? (v1 > v2 * factor) : (v1 / factor > v2); } static void RemFlares(bool Hflg) { SegLnkLst *lst1, *lst2, *nxt1, *nxt2; PathElt *e, *n; HintSeg *seg1, *seg2; HintVal *val1, *val2; Fixed diff; bool nxtE; bool Nm1, Nm2; if (Hflg) { Nm1 = true; Nm2 = false; } else { Nm1 = false; Nm2 = true; } e = gPathStart; while (e != NULL) { if (Nm1 ? e->Hs == NULL : e->Vs == NULL) { e = e->next; continue; } /* e now is an element with Nm1 prop */ n = GetSubPathNxt(e); nxtE = false; while (n != e && !nxtE) { if (Nm1 ? n->Hs != NULL : n->Vs != NULL) { lst1 = ElmntHintSegLst(e, Nm1); while (lst1 != NULL) { seg1 = lst1->lnk->seg; nxt1 = lst1->next; lst2 = ElmntHintSegLst(n, Nm1); while (lst2 != NULL) { seg2 = lst2->lnk->seg; nxt2 = lst2->next; if (seg1 != NULL && seg2 != NULL) { diff = seg1->sLoc - seg2->sLoc; if (abs(diff) > gMaxFlare) { nxtE = true; goto Nxt2; } if (!IsFlare(seg1->sLoc, e, n, Hflg)) { nxtE = true; goto Nxt2; } val1 = seg1->sLnk; val2 = seg2->sLnk; if (diff != 0 && IsTopSegOfVal(seg1->sLoc, val1->vLoc2, val1->vLoc1) == IsTopSegOfVal(seg2->sLoc, val2->vLoc2, val2->vLoc1)) { if (CompareValues(val1, val2, spcBonus, 0)) { /* This change was made to fix flares in * Bodoni2. */ if (val2->vSpc == 0 && val2->vVal < FixInt(1000)) RemFlareLnk(n, Nm1, lst2, e, 1); } else if (val1->vSpc == 0 && val1->vVal < FixInt(1000)) { RemFlareLnk(e, Nm1, lst1, n, 2); goto Nxt1; } } } Nxt2: lst2 = nxt2; } Nxt1: lst1 = nxt1; } } if (Nm2 ? n->Hs != NULL : n->Vs != NULL) break; n = GetSubPathNxt(n); } e = e->next; } } static void CarryIfNeed(Fixed loc, bool vert, HintVal* hints) { HintSeg* seg; HintVal* seglnk; Fixed l0, l1, tmp, halfMargin; if ((vert && gUseV) || (!vert && gUseH)) return; halfMargin = FixHalfMul(gBandMargin); /* DEBUG 8 BIT. Needed to double test from 10 to 20 for change in coordinate * system */ if (halfMargin > FixInt(20)) halfMargin = FixInt(20); while (hints != NULL) { seg = hints->vSeg1; if (hints->vGhst && seg->sType == sGHOST) seg = hints->vSeg2; if (seg == NULL) goto Nxt; l0 = hints->vLoc2; l1 = hints->vLoc1; if (l0 > l1) { tmp = l1; l1 = l0; l0 = tmp; } l0 -= halfMargin; l1 += halfMargin; if (loc > l0 && loc < l1) { seglnk = seg->sLnk; seg->sLnk = hints; if (vert) { if (TestHint(seg, gVHinting, true, true) == 1) { ReportCarry(l0, l1, loc, hints, vert); AddVHinting(hints); seg->sLnk = seglnk; break; } } else if (TestHint(seg, gHHinting, false, true) == 1) { ReportCarry(l0, l1, loc, hints, vert); AddHHinting(hints); seg->sLnk = seglnk; break; } seg->sLnk = seglnk; } Nxt: hints = hints->vNxt; } } #define PRODIST \ (FixInt(100)) /* DEBUG 8 BIT. Needed to double test from 50 to 100 for \ change in coordinate system */ static void ProHints(PathElt* e, bool hFlg, Fixed loc) { SegLnkLst* lst; PathElt* prv; lst = ElmntHintSegLst(e, hFlg); if (lst == NULL) return; if (hFlg ? e->Hcopy : e->Vcopy) return; prv = e; while (true) { Fixed cx, cy, dst; SegLnkLst* plst; prv = GetSubPathPrv(prv); plst = ElmntHintSegLst(prv, hFlg); if (plst != NULL) return; GetEndPoint(prv, &cx, &cy); dst = (hFlg ? cy : cx) - loc; if (abs(dst) > PRODIST) return; if (hFlg) { prv->Hs = lst; prv->Hcopy = true; } else { prv->Vs = lst; prv->Vcopy = true; } } } static void PromoteHints(void) { PathElt* e; e = gPathStart; while (e != NULL) { Fixed cx, cy; GetEndPoint(e, &cx, &cy); ProHints(e, true, cy); ProHints(e, false, cx); e = e->next; } } static void RemPromotedHints(void) { PathElt* e; e = gPathStart; while (e != NULL) { if (e->Hcopy) { e->Hs = NULL; e->Hcopy = false; } if (e->Vcopy) { e->Vs = NULL; e->Vcopy = false; } e = e->next; } } static void RemShortHints(void) { /* Must not change hints at a short element. */ PathElt* e; Fixed cx, cy, ex, ey; e = gPathStart; cx = 0; cy = 0; while (e != NULL) { GetEndPoint(e, &ex, &ey); if (abs(cx - ex) < gMinHintElementLength && abs(cy - ey) < gMinHintElementLength) { ReportRemShortHints(ex, ey); e->Hs = NULL; e->Vs = NULL; } e = e->next; cx = ex; cy = ey; } } void AutoExtraHints(bool movetoNewHints) { int32_t h, v, ph, pv; PathElt *e, *cp, *p; SegLnkLst *hLst, *vLst, *phLst, *pvLst; HintVal *mtVhints, *mtHhints, *prvHhints, *prvVhints; bool (*Tst)(int32_t, int32_t), newHints = true; Fixed x, y; mergeMain = (CountSubPaths() <= 5); e = gPathStart; LogMsg(LOGDEBUG, OK, "RemFlares"); RemFlares(true); RemFlares(false); LogMsg(LOGDEBUG, OK, "CheckElmntHintSegs"); CheckElmntHintSegs(); LogMsg(LOGDEBUG, OK, "PromoteHints"); PromoteHints(); LogMsg(LOGDEBUG, OK, "RemShortHints"); RemShortHints(); p = NULL; Tst = IsOk; /* it is ok to add to primary hinting */ LogMsg(LOGDEBUG, OK, "hint loop"); mtVhints = mtHhints = NULL; while (e != NULL) { int32_t etype = e->type; if (movetoNewHints && etype == MOVETO) { StartNewHinting(e, NULL, NULL); Tst = IsOk; } if (newHints && e == p) { StartNewHinting(e, NULL, NULL); SetHHints(mtHhints); SetVHints(mtVhints); Tst = IsIn; } GetHintLsts(e, &hLst, &vLst, &h, &v); if (etype == MOVETO && IsShort(cp = GetClosedBy(e))) { GetHintLsts(p = cp->prev, &phLst, &pvLst, &ph, &pv); if (HintsClash(e, p, &hLst, &vLst, &phLst, &pvLst)) { GetHintLsts(e, &hLst, &vLst, &h, &v); GetHintLsts(p, &phLst, &pvLst, &ph, &pv); } if (!(*Tst)(ph, pv) || !(*Tst)(h, v)) { StartNewHinting(e, hLst, vLst); Tst = IsOk; ph = pv = 1; /* force add of hints for p also */ } else { AddIfNeedH(h, hLst); AddIfNeedV(v, vLst); } AddIfNeedH(ph, phLst); AddIfNeedV(pv, pvLst); newHints = false; /* so can tell if added new hints in subpath */ } else if (!(*Tst)(h, v)) { /* e needs new hinting */ if (etype == CLOSEPATH) { /* do not attach extra hints to closepath */ e = e->prev; GetHintLsts(e, &hLst, &vLst, &h, &v); } prvHhints = CopyHints(gHHinting); prvVhints = CopyHints(gVHinting); if (!newHints) { /* this is the first extra since mt */ newHints = true; mtVhints = CopyHints(prvVhints); mtHhints = CopyHints(prvHhints); } StartNewHinting(e, hLst, vLst); Tst = IsOk; if (etype == CURVETO) { x = e->x1; y = e->y1; } else GetEndPoint(e, &x, &y); CarryIfNeed(y, false, prvHhints); CarryIfNeed(x, true, prvVhints); } else { /* do not need to start new hinting */ AddIfNeedH(h, hLst); AddIfNeedV(v, vLst); } e = e->next; } ReHintBounds(gPathEnd); LogMsg(LOGDEBUG, OK, "RemPromotedHints"); RemPromotedHints(); LogMsg(LOGDEBUG, OK, "done AutoExtraHints"); } psautohint-2.3.0/libpsautohint/src/basic.h000066400000000000000000000017621401523215600206250ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #ifndef BASIC_H #define BASIC_H #include #include #include #include #include #include #include #include #include #include typedef int32_t Fixed; typedef int indx; /* for indexes that could be either short or long - let the compiler decide */ /* macro definitions */ #define NUMMIN(a, b) ((a) <= (b) ? (a) : (b)) #define NUMMAX(a, b) ((a) >= (b) ? (a) : (b)) /* Round the same way as PS. i.e. -6.5 ==> -6.0 */ #define LROUND(a) ((a > 0) ? (int32_t)(a + .5f) : ((a + (int32_t)(-a)) == -.5f) ? (int32_t) a : (int32_t)(a - .5f)) #ifndef MAXINT #define MAXINT 32767 #endif #endif /*BASIC_H*/ psautohint-2.3.0/libpsautohint/src/bbox.c000066400000000000000000000222201401523215600204610ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "bbox.h" #include "ac.h" static Fixed xmin, ymin, xmax, ymax, vMn, vMx, hMn, hMx; static PathElt *pxmn, *pxmx, *pymn, *pymx, *pe, *pvMn, *pvMx, *phMn, *phMx; static void FPBBoxPt(Cd c) { if (c.x < xmin) { xmin = c.x; pxmn = pe; } if (c.x > xmax) { xmax = c.x; pxmx = pe; } if (c.y < ymin) { ymin = c.y; pymn = pe; } if (c.y > ymax) { ymax = c.y; pymx = pe; } } static void FindPathBBox(void) { FltnRec fr; PathElt* e; Cd c0, c1, c2, c3; memset(&c0, 0, sizeof(Cd)); if (gPathStart == NULL) { xmin = ymin = xmax = ymax = 0; pxmn = pxmx = pymn = pymx = NULL; return; } fr.report = FPBBoxPt; xmin = ymin = FixInt(10000); xmax = ymax = -xmin; e = gPathStart; while (e != NULL) { switch (e->type) { case MOVETO: case LINETO: c0.x = e->x; c0.y = e->y; pe = e; FPBBoxPt(c0); break; case CURVETO: c1.x = e->x1; c1.y = e->y1; c2.x = e->x2; c2.y = e->y2; c3.x = e->x3; c3.y = e->y3; pe = e; FltnCurve(c0, c1, c2, c3, &fr); c0 = c3; break; case CLOSEPATH: break; default: { LogMsg(LOGERROR, NONFATALERROR, "Undefined operator."); } } e = e->next; } xmin = FHalfRnd(xmin); ymin = FHalfRnd(ymin); xmax = FHalfRnd(xmax); ymax = FHalfRnd(ymax); } PathElt* FindSubpathBBox(PathElt* e) { FltnRec fr; Cd c0, c1, c2, c3; memset(&c0, 0, sizeof(Cd)); if (e == NULL) { xmin = ymin = xmax = ymax = 0; pxmn = pxmx = pymn = pymx = NULL; return NULL; } fr.report = FPBBoxPt; xmin = ymin = FixInt(10000); xmax = ymax = -xmin; #if 0 e = GetDest(e); /* back up to moveto */ #else /* This and the following change (in the next else clause) were made to fix the hinting in glyphs in the SolEol lists. These are supposed to have subpath bbox hinted, but were getting path bbox hinted instead. */ if (e->type != MOVETO) e = GetDest(e); /* back up to moveto */ #endif while (e != NULL) { switch (e->type) { case MOVETO: case LINETO: c0.x = e->x; c0.y = e->y; pe = e; FPBBoxPt(c0); break; case CURVETO: c1.x = e->x1; c1.y = e->y1; c2.x = e->x2; c2.y = e->y2; c3.x = e->x3; c3.y = e->y3; pe = e; FltnCurve(c0, c1, c2, c3, &fr); c0 = c3; break; case CLOSEPATH: #if 0 break; #else e = e->next; goto done; #endif default: { LogMsg(LOGERROR, NONFATALERROR, "Undefined operator."); } } e = e->next; } #if 1 done: #endif xmin = FHalfRnd(xmin); ymin = FHalfRnd(ymin); xmax = FHalfRnd(xmax); ymax = FHalfRnd(ymax); return e; } void FindCurveBBox(Fixed x0, Fixed y0, Fixed px1, Fixed py1, Fixed px2, Fixed py2, Fixed x1, Fixed y1, Fixed* pllx, Fixed* plly, Fixed* purx, Fixed* pury) { FltnRec fr; Cd c0, c1, c2, c3; fr.report = FPBBoxPt; xmin = ymin = FixInt(10000); xmax = ymax = -xmin; c0.x = x0; c0.y = y0; c1.x = px1; c1.y = py1; c2.x = px2; c2.y = py2; c3.x = x1; c3.y = y1; FPBBoxPt(c0); FltnCurve(c0, c1, c2, c3, &fr); *pllx = FHalfRnd(xmin); *plly = FHalfRnd(ymin); *purx = FHalfRnd(xmax); *pury = FHalfRnd(ymax); } void HintVBnds(void) { PathElt* p; if (gPathStart == NULL || VHintGlyph()) return; FindPathBBox(); vMn = xmin; vMx = xmax; pvMn = pxmn; pvMx = pxmx; if (vMn > vMx) { Fixed tmp = vMn; vMn = vMx; vMx = tmp; p = pvMn; pvMn = pvMx; pvMx = p; } AddHintPoint(vMn, 0, vMx, 0, 'y', pvMn, pvMx); } void HintHBnds(void) { if (gPathStart == NULL || HHintGlyph()) return; FindPathBBox(); hMn = -ymin; hMx = -ymax; phMn = pymn; phMx = pymx; if (hMn > hMx) { PathElt* p; Fixed tmp = hMn; hMn = hMx; hMx = tmp; p = phMn; phMn = phMx; phMx = p; } AddHintPoint(0, hMn, 0, hMx, 'b', phMn, phMx); } static bool CheckValOverlaps(Fixed lft, Fixed rht, HintVal* lst, bool xflg) { Fixed tmp; if (!xflg) { lft = -lft; rht = -rht; } if (lft > rht) { tmp = lft; lft = rht; rht = tmp; } while (lst != NULL) { Fixed lft2 = lst->vLoc1; Fixed rht2 = lst->vLoc2; if (!xflg) { lft2 = -lft2; rht2 = -rht2; } if (lft2 > rht2) { tmp = lft2; lft2 = rht2; rht2 = tmp; } if (lft2 <= rht && lft <= rht2) return true; lst = lst->vNxt; } return false; } void AddBBoxHV(bool Hflg, bool subs) { PathElt* e; HintVal* val; HintSeg *seg1, *seg2; e = gPathStart; while (e != NULL) { if (subs) e = FindSubpathBBox(e); else { FindPathBBox(); e = NULL; } if (!Hflg) { if (!CheckValOverlaps(xmin, xmax, gVHinting, true)) { val = (HintVal*)Alloc(sizeof(HintVal)); seg1 = (HintSeg*)Alloc(sizeof(HintSeg)); seg1->sLoc = xmin; seg1->sElt = pxmn; seg1->sBonus = 0; seg1->sType = sLINE; seg1->sMin = ymin; seg1->sMax = ymax; seg1->sNxt = NULL; seg1->sLnk = NULL; seg2 = (HintSeg*)Alloc(sizeof(HintSeg)); seg2->sLoc = xmax; seg2->sElt = pxmx; seg2->sBonus = 0; seg2->sType = sLINE; seg2->sMin = ymin; seg2->sMax = ymax; seg2->sNxt = NULL; seg2->sLnk = NULL; val->vVal = 100; val->vSpc = 0; val->vLoc1 = xmin; val->vLoc2 = xmax; val->vSeg1 = seg1; val->vSeg2 = seg2; val->vGhst = false; val->vNxt = gVHinting; val->vBst = val; gVHinting = val; } } else { if (!CheckValOverlaps(ymin, ymax, gHHinting, false)) { val = (HintVal*)Alloc(sizeof(HintVal)); seg1 = (HintSeg*)Alloc(sizeof(HintSeg)); seg1->sLoc = ymax; seg1->sElt = pymx; seg1->sBonus = 0; seg1->sType = sLINE; seg1->sMin = xmin; seg1->sMax = xmax; seg1->sNxt = NULL; seg1->sLnk = NULL; seg2 = (HintSeg*)Alloc(sizeof(HintSeg)); seg2->sLoc = ymin; seg2->sElt = pymn; seg2->sBonus = 0; seg2->sType = sLINE; seg2->sMin = xmin; seg2->sMax = xmax; seg2->sNxt = NULL; seg2->sLnk = NULL; val->vVal = 100; val->vSpc = 0; val->vLoc1 = ymax; /* bot is > top because y axis is reversed */ val->vLoc2 = ymin; val->vSeg1 = seg1; val->vSeg2 = seg2; val->vGhst = false; val->vNxt = gHHinting; val->vBst = val; gHHinting = val; } } } } void CheckPathBBox(void) { Fixed llx, lly, urx, ury, tmp; FindPathBBox(); llx = xmin; urx = xmax; if (llx > urx) { tmp = llx; llx = urx; urx = tmp; } lly = -ymax; ury = -ymin; if (lly > ury) { tmp = lly; lly = ury; ury = tmp; } if (llx < -FixInt(600) || lly < -FixInt(600) || urx > FixInt(1600) || ury > FixInt(1600)) ReportBBoxBogus(llx, lly, urx, ury); } bool CheckBBoxes(PathElt* e1, PathElt* e2) { /* return true if e1 and e2 in same subpath or i the bbox for one is inside the bbox of the other */ Fixed xmn, xmx, ymn, ymx; e1 = GetDest(e1); e2 = GetDest(e2); if (e1 == e2) return true; /* same subpath */ FindSubpathBBox(e1); xmn = xmin; xmx = xmax; ymn = ymin; ymx = ymax; FindSubpathBBox(e2); return ((xmn <= xmin && xmax <= xmx && ymn <= ymin && ymax <= ymx) || (xmn >= xmin && xmax >= xmx && ymn >= ymin && ymax >= ymx)); } psautohint-2.3.0/libpsautohint/src/bbox.h000066400000000000000000000013341401523215600204710ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #ifndef AC_BBOX_H_ #define AC_BBOX_H_ #include "ac.h" PathElt* FindSubpathBBox(PathElt* e); void FindCurveBBox(Fixed x0, Fixed y0, Fixed px1, Fixed py1, Fixed px2, Fixed py2, Fixed x1, Fixed y1, Fixed* pllx, Fixed* plly, Fixed* purx, Fixed* pury); void HintVBnds(void); void HintHBnds(void); void AddBBoxHV(bool Hflg, bool subs); void CheckPathBBox(void); bool CheckBBoxes(PathElt* e1, PathElt* e2); #endif /* AC_BBOX_H_ */ psautohint-2.3.0/libpsautohint/src/buffer.c000066400000000000000000000047311401523215600210070ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "logging.h" #include "memory.h" #include "psautohint.h" struct ACBuffer { char* data; /* buffer data, NOT null-terminated */ size_t len; /* actual length of the data */ size_t capacity; /* allocated memory size */ }; ACLIB_API ACBuffer* ACBufferNew(size_t size) { ACBuffer* buffer; if (!size) return NULL; buffer = (ACBuffer*)AllocateMem(1, sizeof(ACBuffer), "buffer"); buffer->data = AllocateMem(size, 1, "buffer data"); buffer->data[0] = '\0'; buffer->capacity = size; buffer->len = 0; return buffer; } ACLIB_API void ACBufferFree(ACBuffer* buffer) { if (!buffer) return; UnallocateMem(buffer->data); UnallocateMem(buffer); } ACLIB_API void ACBufferReset(ACBuffer* buffer) { if (!buffer) return; buffer->len = 0; } ACLIB_API void ACBufferWrite(ACBuffer* buffer, char* data, size_t length) { if (!buffer) return; if ((buffer->len + length) >= buffer->capacity) { size_t size = NUMMAX(buffer->capacity * 2, buffer->capacity + length); buffer->data = ReallocateMem(buffer->data, size, "buffer data"); buffer->capacity = size; } memcpy(buffer->data + buffer->len, data, length); buffer->len += length; } #define STRLEN 1000 ACLIB_API void ACBufferWriteF(ACBuffer* buffer, char* format, ...) { char outstr[STRLEN]; int len; va_list va; if (!buffer) return; va_start(va, format); len = vsnprintf(outstr, STRLEN, format, va); va_end(va); if (len > 0 && len <= STRLEN) { ACBufferWrite(buffer, outstr, len); } else { char* outstr = AllocateMem(1, len + 1, "Temporary buffer"); va_start(va, format); len = vsnprintf(outstr, len + 1, format, va); va_end(va); if (len > 0) ACBufferWrite(buffer, outstr, len); else LogMsg(LOGERROR, FATALERROR, "Failed to write string to ACBuffer."); UnallocateMem(outstr); } } ACLIB_API void ACBufferRead(ACBuffer* buffer, char** data, size_t* length) { if (buffer) { *data = buffer->data; *length = buffer->len; } else { *data = NULL; *length = 0; } } psautohint-2.3.0/libpsautohint/src/charpath.c000066400000000000000000001606461401523215600213400ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "bbox.h" #include "charpath.h" #include "opcodes.h" #include "optable.h" #define DONT_COMBINE_PATHS 1 #define DMIN 50 /* device minimum (one-half of a device pixel) */ #define FONTSTKLIMIT 22 #define MAINHINTS -1 /* The following definitions are used when determining if a hinting operator used the start, end, average or flattened coordinate values. */ /* Segments are numbered starting with 1. The "ghost" segment is 0. */ #define STARTPT 0 #define ENDPT 1 #define AVERAGE 2 #define CURVEBBOX 3 #define FLATTEN 4 #define GHOST 5 static bool firstMT; static Cd* refPtArray = NULL; static ACBuffer* outbuff; static int masterCount; static const char** masterNames; static PathList* pathlist = NULL; static indx hintsMasterIx = 0; /* The index of the master we read hints from */ /* Prototypes */ static void GetRelativePosition(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed*); static int16_t GetOperandCount(int16_t); static void GetLengthandSubrIx(int16_t, int16_t*, int16_t*); /* macros */ #define WriteToBuffer(...) ACBufferWriteF(outbuff, __VA_ARGS__) #define WRTNUM(i) WriteToBuffer("%d ", (int)(i)) #define WRTNUMA(i) WriteToBuffer("%0.2f ", round((double)(i)*100) / 100) #define WriteSubr(val) WriteToBuffer("%d subr ", val) static void WriteX(Fixed x) { Fixed i = FRnd(x); WRTNUM(FTrunc(i)); } static void WriteY(Fixed y) { Fixed i = FRnd(y); WRTNUM(FTrunc(i)); } #define WriteCd(c) \ { \ WriteX(c.x); \ WriteY(c.y); \ } static void WriteOneHintVal(Fixed val) { if (FracPart(val) == 0) WRTNUM(FTrunc(val)); else WRTNUMA(FIXED2FLOAT(val)); } /* Locates the first CP following the given path element. */ static int32_t GetCPIx(indx mIx, int32_t pathIx) { indx ix; for (ix = pathIx; ix < gPathEntries; ix++) if (pathlist[mIx].path[ix].type == CP) return ix; LogMsg(LOGERROR, NONFATALERROR, "No closepath."); return (-1); } static void GetEndPoint1(indx mIx, int32_t pathIx, Fixed* ptX, Fixed* ptY) { GlyphPathElt* pathElt = &pathlist[mIx].path[pathIx]; retry: switch (pathElt->type) { case RMT: case RDT: *ptX = pathElt->x; *ptY = pathElt->y; break; case RCT: *ptX = pathElt->x3; *ptY = pathElt->y3; break; case CP: while (--pathIx >= 0) { pathElt = &pathlist[mIx].path[pathIx]; if (pathElt->type == RMT) goto retry; } LogMsg(LOGERROR, NONFATALERROR, "Bad description."); break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal operator."); } } static void GetEndPoints1(indx mIx, int32_t pathIx, Cd* start, Cd* end) { if (pathlist[mIx].path[pathIx].type == RMT) { int32_t cpIx; GetEndPoint1(mIx, pathIx, &start->x, &start->y); /* Get index for closepath associated with this moveto. */ cpIx = GetCPIx(mIx, pathIx + 1); GetEndPoint1(mIx, cpIx - 1, &end->x, &end->y); } else { GetEndPoint1(mIx, pathIx - 1, &start->x, &start->y); GetEndPoint1(mIx, pathIx, &end->x, &end->y); } } static void GetCoordFromType(int16_t pathtype, Cd* coord, indx mIx, indx eltno) { switch (pathtype) { case RMT: case RDT: (*coord).x = FTrunc(FRnd(pathlist[mIx].path[eltno].x)); (*coord).y = FTrunc(FRnd(pathlist[mIx].path[eltno].y)); break; case RCT: (*coord).x = FTrunc(FRnd(pathlist[mIx].path[eltno].x3)); (*coord).y = FTrunc(FRnd(pathlist[mIx].path[eltno].y3)); break; case CP: GetCoordFromType(pathlist[mIx].path[eltno - 1].type, coord, mIx, eltno - 1); break; default: LogMsg(LOGERROR, NONFATALERROR, "Unrecognized path type"); memset(coord, 0, sizeof(Cd)); break; } } static const char* const pathTypes[] = { "moveto", "lineto", "curveto", "closepath" }; static const char* GetPathType(int16_t pathtype) { switch (pathtype) { case RMT: return pathTypes[0]; case RDT: return pathTypes[1]; case RCT: return pathTypes[2]; case CP: return pathTypes[3]; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal path type: %d.", pathtype); return NULL; } } static void FreeHints(HintElt* hints) { HintElt* next; while (hints != NULL) { next = hints->next; UnallocateMem(hints); hints = next; } } static void FreePathElements(indx stopix) { indx i, j; for (j = 0; j < stopix; j++) { if (pathlist[j].path != NULL) { /* Before we can free hint elements will need to know gPathEntries value for char in each master because this proc can be called when glyphs are inconsistent. */ for (i = 0; i < gPathEntries; i++) FreeHints(pathlist[j].path[i].hints); } FreeHints(pathlist[j].mainhints); UnallocateMem(pathlist[j].path); } UnallocateMem(pathlist); pathlist = NULL; } static void InconsistentPointCount(indx ix, int entries1, int entries2) { LogMsg(WARNING, OK, "Glyph will not be included in the font " "because the version in %s has a total of %d elements " "and the one in %s has %d elements.", masterNames[0], entries1, masterNames[ix], entries2); } static void InconsistentPathType(indx ix, int16_t type1, int16_t type2, indx eltno) { Cd coord1, coord2; GetCoordFromType(type1, &coord1, 0, eltno); GetCoordFromType(type2, &coord2, ix, eltno); LogMsg(WARNING, OK, "Glyph will not be included in the font " "because the version in %s has path type %s at coord: %d " "%d and the one in %s has type %s at coord %d %d.", masterNames[0], GetPathType(type1), coord1.x, coord1.y, masterNames[ix], GetPathType(type2), coord2.x, coord2.y); } /* Returns whether changing the line to a curve is successful. */ static bool ChangetoCurve(indx mIx, indx pathIx) { Cd start = { 0, 0 }, end = { 0, 0 }, ctl1, ctl2; GlyphPathElt* pathElt = &pathlist[mIx].path[pathIx]; if (pathElt->type == RCT) return true; /* Use the 1/3 rule to convert a line to a curve, i.e. put the control points 1/3 of the total distance from each end point. */ GetEndPoints1(mIx, pathIx, &start, &end); ctl1.x = FRnd((start.x * 2 + end.x + FixHalf) / 3); ctl1.y = FRnd((start.y * 2 + end.y + FixHalf) / 3); ctl2.x = FRnd((end.x * 2 + start.x + FixHalf) / 3); ctl2.y = FRnd((end.y * 2 + start.y + FixHalf) / 3); pathElt->type = RCT; pathElt->x1 = ctl1.x; pathElt->y1 = ctl1.y; pathElt->x2 = ctl2.x; pathElt->y2 = ctl2.y; pathElt->x3 = end.x; pathElt->y3 = end.y; pathElt->rx1 = ctl1.x - start.x; pathElt->ry1 = ctl1.y - start.y; pathElt->rx2 = pathElt->x2 - pathElt->x1; pathElt->ry2 = pathElt->y2 - pathElt->y1; pathElt->rx3 = pathElt->x3 - pathElt->x2; pathElt->ry3 = pathElt->y3 - pathElt->y2; return true; } /* Checks that glyph paths for multiple masters have the same number of points and in the same path order. If this isn't the case the glyph is not included in the font. */ static bool CompareGlyphPaths(const char** glyphs) { indx mIx, ix, i; int32_t totalPathElt, minPathLen; bool ok = true; int16_t type1, type2; totalPathElt = minPathLen = MAXINT; if (pathlist == NULL) { pathlist = (PathList*)AllocateMem(masterCount, sizeof(PathList), "glyph path list"); } for (mIx = 0; mIx < masterCount; mIx++) { ResetMaxPathEntries(); SetCurrPathList(&pathlist[mIx]); gPathEntries = 0; if (hintsMasterIx == mIx) { /* read char data and hints from bez file */ if (!ReadGlyph(glyphs[mIx], true, gAddHints)) return false; } else { /* read char data only */ if (!ReadGlyph(glyphs[mIx], true, false)) return false; } if (mIx == 0) totalPathElt = gPathEntries; else if (gPathEntries != totalPathElt) { InconsistentPointCount(mIx, totalPathElt, gPathEntries); ok = false; } minPathLen = NUMMIN(NUMMIN(gPathEntries, totalPathElt), minPathLen); } for (mIx = 1; mIx < masterCount; mIx++) { for (i = 0; i < minPathLen; i++) { if ((type1 = pathlist[0].path[i].type) != (type2 = pathlist[mIx].path[i].type)) { if ((type1 == RDT) && (type2 == RCT)) { /* Change this element in all previous masters to a curve. */ ix = mIx - 1; do { ok = ok && ChangetoCurve(ix, i); ix--; } while (ix >= 0); } else if ((type1 == RCT) && (type2 == RDT)) ok = ok && ChangetoCurve(mIx, i); else { InconsistentPathType(mIx, pathlist[0].path[i].type, pathlist[mIx].path[i].type, i); ok = false; /* skip to next subpath */ while (++i < minPathLen && (pathlist[0].path[i].type != CP)) ; } } } } return ok; } static void SetSbandWidth(void) { indx mIx; for (mIx = 0; mIx < masterCount; mIx++) { pathlist[mIx].sb = 0; pathlist[mIx].width = 1000; } } static void WriteSbandWidth(void) { int16_t subrix, length, opcount = GetOperandCount(SBX); indx ix, j, startix = 0; bool writeSubrOnce, sbsame = true, wsame = true; for (ix = 1; ix < masterCount; ix++) { sbsame = sbsame && (pathlist[ix].sb == pathlist[ix - 1].sb); wsame = wsame && (pathlist[ix].width == pathlist[ix - 1].width); } if (sbsame && wsame) { WriteToBuffer("%d %d ", pathlist[0].sb, pathlist[0].width); } else if (sbsame) { WriteToBuffer("%d ", pathlist[0].sb); for (j = 0; j < masterCount; j++) { WriteToBuffer("%d ", (j == 0) ? pathlist[j].width : pathlist[j].width - pathlist[0].width); } GetLengthandSubrIx(1, &length, &subrix); WriteSubr(subrix); } else if (wsame) { for (j = 0; j < masterCount; j++) { WriteToBuffer("%d ", (j == 0) ? pathlist[j].sb : pathlist[j].sb - pathlist[0].sb); } GetLengthandSubrIx(1, &length, &subrix); WriteSubr(subrix); WriteToBuffer("%d ", pathlist[0].width); } else { GetLengthandSubrIx(opcount, &length, &subrix); if ((writeSubrOnce = (length == opcount))) { WriteToBuffer("%d %d ", pathlist[0].sb, pathlist[0].width); length = startix = 1; } for (ix = 0; ix < opcount; ix += length) { for (j = startix; j < masterCount; j++) { WriteToBuffer( "%d ", (ix == 0) ? (j == 0) ? pathlist[j].sb : pathlist[j].sb - pathlist[0].sb : (j == 0) ? (int32_t)pathlist[j].width : (int32_t)(pathlist[j].width - pathlist[0].width)); } if (!writeSubrOnce || (ix == (opcount - 1))) WriteSubr(subrix); } } WriteToBuffer("sbx\n"); } static bool CurveBBox(indx mIx, int16_t hinttype, int32_t pathIx, Fixed* value) { Cd startPt, endPt; Fixed llx, lly, urx, ury, minval = 0, maxval = 0; Fixed p1 = 0, p2 = 0, *minbx = 0, *maxbx = 0; GlyphPathElt pathElt; *value = FixInt(10000); pathElt = pathlist[mIx].path[pathIx]; GetEndPoints1(mIx, pathIx, &startPt, &endPt); switch (hinttype) { case RB: case RV + ESCVAL: minval = -NUMMIN(startPt.y, endPt.y); maxval = -NUMMAX(startPt.y, endPt.y); p1 = -pathElt.y1; p2 = -pathElt.y2; minbx = &lly; maxbx = &ury; break; case RY: case RM + ESCVAL: minval = NUMMIN(startPt.x, endPt.x); maxval = NUMMAX(startPt.x, endPt.x); p1 = pathElt.x1; p2 = pathElt.x2; minbx = &llx; maxbx = &urx; break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal hint type."); return false; } if (p1 - maxval >= FixOne || p2 - maxval >= FixOne || p1 - minval <= FixOne || p2 - minval <= FixOne) { /* Transform coordinates so I get the same value that AC would give. */ FindCurveBBox(startPt.x, -startPt.y, pathElt.x1, -pathElt.y1, pathElt.x2, -pathElt.y2, endPt.x, -endPt.y, &llx, &lly, &urx, &ury); if (*maxbx > maxval || minval > *minbx) { if (minval - *minbx > *maxbx - maxval) *value = (hinttype == RB || hinttype == RV + ESCVAL) ? -*minbx : *minbx; else *value = (hinttype == RB || hinttype == RV + ESCVAL) ? -*maxbx : *maxbx; return true; } } return false; } static bool nearlyequal_(Fixed a, Fixed b, Fixed tolerance) { return (abs(a - b) <= tolerance); } /* Returns whether the hint values are derived from the start, average, end or flattened curve with an inflection point of the specified path element. Since path element numbers in glyph files start from one and the path array starts from zero we need to subtract one from the path index. */ static int16_t GetPointType(int16_t hinttype, Fixed value, int32_t* pathEltIx) { Cd startPt, endPt; Fixed startval = 0, endval = 0, loc; int16_t pathtype; bool tryAgain = true; int32_t pathIx = *pathEltIx - 1; retry: GetEndPoints1(hintsMasterIx, pathIx, &startPt, &endPt); switch (hinttype) { case RB: case RV + ESCVAL: startval = startPt.y; endval = endPt.y; break; case RY: case RM + ESCVAL: startval = startPt.x; endval = endPt.x; break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal hint type."); } /* Check for exactly equal first, in case endval = startval + 1. * Certain cases are still ambiguous. */ if (value == startval) return STARTPT; else if (value == endval) return ENDPT; else if (nearlyequal_(value, startval, FixOne)) return STARTPT; else if (nearlyequal_(value, endval, FixOne)) return ENDPT; else if (value == (loc = FixHalfMul(startval + endval)) || nearlyequal_(value, loc, FixOne)) return AVERAGE; pathtype = pathlist[hintsMasterIx].path[pathIx].type; /* try looking at other end of line or curve */ if (tryAgain && (pathIx + 1 < gPathEntries) && (pathtype != CP)) { pathIx++; *pathEltIx += 1; tryAgain = false; goto retry; } /* reset pathEltIx to original value */ if (!tryAgain) *pathEltIx -= 1; if (CurveBBox(hintsMasterIx, hinttype, *pathEltIx - 1, &loc) && nearlyequal_(value, loc, FixOne)) return CURVEBBOX; return FLATTEN; } static void GetRelPos(int32_t pathIx, int16_t hinttype, Fixed hintVal, Cd* startPt, Cd* endPt, Fixed* val) { Cd origStart, origEnd; GetEndPoints1(hintsMasterIx, pathIx, &origStart, &origEnd); if (hinttype == RB || hinttype == (RV + ESCVAL)) GetRelativePosition(endPt->y, startPt->y, origEnd.y, origStart.y, hintVal, val); else GetRelativePosition(endPt->x, startPt->x, origEnd.x, origStart.x, hintVal, val); } /* Calculates the relative position of hintVal between its endpoints and gets new hint value between currEnd and currStart. */ static void GetRelativePosition(Fixed currEnd, Fixed currStart, Fixed end, Fixed start, Fixed hintVal, Fixed* fixedRelValue) { if ((end - start) == 0) *fixedRelValue = (Fixed)LROUND((float)(hintVal - start) + currStart); else { float relVal = (float)(hintVal - start) / (float)(end - start); *fixedRelValue = (Fixed)LROUND(((currEnd - currStart) * relVal) + currStart); } } /* For each base design, excluding hints master, include the hint information at the specified path element. type1 and type2 indicates whether to use the start, end, avg., curvebbox or flattened curve. If a curve is to be flattened check if this is an "s" curve and use the inflection point. If not then use the same relative position between the two endpoints as in the main hints master. hinttype is either RB, RY, RM or RV. pathEltIx is the index into the path array where the new hint should be stored. pathIx is the index of the path segment used to calculate this particular hint. */ static void InsertHint(HintElt* currHintElt, indx pathEltIx, int16_t type1, int16_t type2) { indx ix, j; Cd startPt, endPt; HintElt **hintElt, *newEntry; GlyphPathElt pathElt; int32_t pathIx; int16_t pathtype, hinttype = currHintElt->type; Fixed *value, ghostVal = 0, tempVal; if (type1 == GHOST || type2 == GHOST) /* ghostVal should be -20 or -21 */ ghostVal = currHintElt->rightortop - currHintElt->leftorbot; for (ix = 0; ix < masterCount; ix++) { if (ix == hintsMasterIx) continue; newEntry = (HintElt*)AllocateMem(1, sizeof(HintElt), "hint element"); newEntry->type = hinttype; hintElt = (pathEltIx == MAINHINTS ? &pathlist[ix].mainhints : &pathlist[ix].path[pathEltIx].hints); while (*hintElt != NULL && (*hintElt)->next != NULL) hintElt = &(*hintElt)->next; if (*hintElt == NULL) *hintElt = newEntry; else (*hintElt)->next = newEntry; for (j = 0; j < 2; j++) { if (j == 0) { pathIx = currHintElt->pathix1 - 1; pathtype = type1; value = &newEntry->leftorbot; } else { pathIx = currHintElt->pathix2 - 1; pathtype = type2; value = &newEntry->rightortop; } if (pathtype != GHOST) GetEndPoints1(ix, pathIx, &startPt, &endPt); switch (pathtype) { case AVERAGE: *value = ((hinttype == RB || hinttype == (RV + ESCVAL)) ? FixHalfMul(startPt.y + endPt.y) : FixHalfMul(startPt.x + endPt.x)); break; case CURVEBBOX: if (!CurveBBox(ix, hinttype, pathIx, value)) { GetRelPos(pathIx, hinttype, ((j == 0) ? currHintElt->leftorbot : currHintElt->rightortop), &startPt, &endPt, &tempVal); *value = FRnd(tempVal); } break; case ENDPT: *value = ((hinttype == RB || hinttype == (RV + ESCVAL)) ? endPt.y : endPt.x); break; case FLATTEN: pathElt = pathlist[ix].path[pathIx]; if (pathElt.type != RCT) { LogMsg(LOGERROR, NONFATALERROR, "Malformed path list in master: %s, " "element: %d, type: %s != curveto.", masterNames[ix], pathIx, GetPathType(pathElt.type)); } if (!GetInflectionPoint(startPt.x, startPt.y, pathElt.x1, pathElt.y1, pathElt.x2, pathElt.y2, pathElt.x3, pathElt.y3, value)) { /* no flat spot found */ /* Get relative position of value in currHintElt. */ GetRelPos(pathIx, hinttype, ((j == 0) ? currHintElt->leftorbot : currHintElt->rightortop), &startPt, &endPt, &tempVal); *value = FRnd(tempVal); } break; case GHOST: if (j == 1) *value = newEntry->leftorbot + ghostVal; break; case STARTPT: *value = ((hinttype == RB || hinttype == (RV + ESCVAL)) ? startPt.y : startPt.x); break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal point type."); } /* Assign correct value for bottom band if first path element is a ghost band. */ if (j == 1 && type1 == GHOST) newEntry->leftorbot = newEntry->rightortop - ghostVal; } } } static void ReadHints(HintElt* hintElt, indx pathEltIx) { HintElt* currElt = hintElt; int16_t pointtype1, pointtype2; while (currElt != NULL) { if (currElt->pathix1 != 0) pointtype1 = GetPointType(currElt->type, currElt->leftorbot, &(currElt->pathix1)); else pointtype1 = GHOST; if (currElt->pathix2 != 0) pointtype2 = GetPointType(currElt->type, currElt->rightortop, &(currElt->pathix2)); else pointtype2 = GHOST; InsertHint(currElt, pathEltIx, pointtype1, pointtype2); currElt = currElt->next; } } /* Reads hints from hints master path list and assigns corresponding hints to other designs. */ static bool ReadandAssignHints(void) { indx ix; /* Check for main hints first, i.e. global to glyph. */ if (pathlist[hintsMasterIx].mainhints != NULL) ReadHints(pathlist[hintsMasterIx].mainhints, MAINHINTS); /* Now check for local hint values. */ for (ix = 0; ix < gPathEntries; ix++) { if (pathlist[hintsMasterIx].path == NULL) return false; if (pathlist[hintsMasterIx].path[ix].hints != NULL) ReadHints(pathlist[hintsMasterIx].path[ix].hints, ix); } return true; } static bool DoubleCheckFlexVals(indx masternum, indx eltix, indx hintmasternum) { bool vert = (pathlist[hintmasternum].path[eltix].x == pathlist[hintmasternum].path[eltix + 1].x3); if (vert) { return (pathlist[masternum].path[eltix].x == pathlist[masternum].path[eltix + 1].x3); } else { return (pathlist[masternum].path[eltix].y == pathlist[masternum].path[eltix + 1].y3); } } static bool CheckFlexOK(indx ix) { indx i; bool flexOK = pathlist[hintsMasterIx].path[ix].isFlex; GlyphPathElt* end; for (i = 0; i < masterCount; i++) { if (i == hintsMasterIx) continue; if (flexOK && (!pathlist[i].path[ix].isFlex)) { if (!DoubleCheckFlexVals(i, ix, hintsMasterIx)) { end = &pathlist[i].path[ix]; LogMsg(WARNING, OK, "Flex will not be included in the glyph, " "in '%s' at element %d near (%d, %d) because " "the glyph does not have flex in each " "design.", masterNames[i], (int)ix, FTrunc(end->x), FTrunc(end->y)); return false; } else { pathlist[i].path[ix].isFlex = flexOK; } } } return flexOK; } static void OptimizeCT(indx ix) { int16_t newtype = 0; bool vhct = true, hvct = true; indx i; for (i = 0; i < masterCount; i++) if (pathlist[i].path[ix].rx1 != 0 || pathlist[i].path[ix].ry3 != 0) { vhct = false; break; } for (i = 0; i < masterCount; i++) if (pathlist[i].path[ix].ry1 != 0 || pathlist[i].path[ix].rx3 != 0) { hvct = false; break; } if (vhct) newtype = VHCT; else if (hvct) newtype = HVCT; if (vhct || hvct) for (i = 0; i < masterCount; i++) pathlist[i].path[ix].type = newtype; } static void MtorDt(Cd coord, indx startix, int16_t length) { if (length == 2) { WriteCd(coord); } else if (startix == 0) WriteX(coord.x); else WriteY(coord.y); } static void Hvct(Cd coord1, Cd coord2, Cd coord3, indx startix, int16_t length) { indx ix; indx lastix = startix + length; for (ix = startix; ix < lastix; ix++) switch (ix) { case 0: WriteX(coord1.x); break; case 1: WriteX(coord2.x); break; case 2: WriteY(coord2.y); break; case 3: WriteY(coord3.y); break; default: LogMsg(LOGERROR, NONFATALERROR, "Invalid index value: %d defined for curveto " "command1.", (int)ix); break; } } static void Vhct(Cd coord1, Cd coord2, Cd coord3, indx startix, int16_t length) { indx ix; indx lastix = startix + length; for (ix = startix; ix < lastix; ix++) switch (ix) { case 0: WriteY(coord1.y); break; case 1: WriteX(coord2.x); break; case 2: WriteY(coord2.y); break; case 3: WriteX(coord3.x); break; default: LogMsg(LOGERROR, NONFATALERROR, "Invalid index value: %d defined for curveto " "command2.", (int)ix); break; } } /* length can only be 1, 2, 3 or 6 */ static void Ct(Cd coord1, Cd coord2, Cd coord3, indx startix, int16_t length) { indx ix; indx lastix = startix + length; for (ix = startix; ix < lastix; ix++) switch (ix) { case 0: WriteX(coord1.x); break; case 1: WriteY(coord1.y); break; case 2: WriteX(coord2.x); break; case 3: WriteY(coord2.y); break; case 4: WriteX(coord3.x); break; case 5: WriteY(coord3.y); break; default: LogMsg(LOGERROR, NONFATALERROR, "Invalid index value: %d defined for curveto " "command3.", (int)ix); break; } } static void CheckFlexValues(int16_t* operator, indx eltix, indx flexix, bool* xequal, bool* yequal) { indx ix; Cd coord = { 0, 0 }; *operator= RMT; if (flexix < 2) return; *xequal = *yequal = true; for (ix = 1; ix < masterCount; ix++) switch (flexix) { case 2: if ((coord.x = pathlist[ix].path[eltix].rx2) != pathlist[ix - 1].path[eltix].rx2) *xequal = false; if ((coord.y = pathlist[ix].path[eltix].ry2) != pathlist[ix - 1].path[eltix].ry2) *yequal = false; break; case 3: if ((coord.x = pathlist[ix].path[eltix].rx3) != pathlist[ix - 1].path[eltix].rx3) *xequal = false; if ((coord.y = pathlist[ix].path[eltix].ry3) != pathlist[ix - 1].path[eltix].ry3) *yequal = false; break; case 4: if ((coord.x = pathlist[ix].path[eltix + 1].rx1) != pathlist[ix - 1].path[eltix + 1].rx1) *xequal = false; if ((coord.y = pathlist[ix].path[eltix + 1].ry1) != pathlist[ix - 1].path[eltix + 1].ry1) *yequal = false; break; case 5: if ((coord.x = pathlist[ix].path[eltix + 1].rx2) != pathlist[ix - 1].path[eltix + 1].rx2) *xequal = false; if ((coord.y = pathlist[ix].path[eltix + 1].ry2) != pathlist[ix - 1].path[eltix + 1].ry2) *yequal = false; break; case 6: if ((coord.x = pathlist[ix].path[eltix + 1].rx3) != pathlist[ix - 1].path[eltix + 1].rx3) *xequal = false; if ((coord.y = pathlist[ix].path[eltix + 1].ry3) != pathlist[ix - 1].path[eltix + 1].ry3) *yequal = false; break; case 7: if ((coord.x = pathlist[ix].path[eltix + 1].x3) != pathlist[ix - 1].path[eltix + 1].x3) *xequal = false; if ((coord.y = pathlist[ix].path[eltix + 1].y3) != pathlist[ix - 1].path[eltix + 1].y3) *yequal = false; break; } if (!(*xequal) && !(*yequal)) return; if (*xequal && (coord.x == 0)) { *operator= VMT; *xequal = false; } if (*yequal && (coord.y == 0)) { *operator= HMT; *yequal = false; } } static void GetFlexCoord(indx rmtCt, indx mIx, indx eltix, Cd* coord) { switch (rmtCt) { case 0: (*coord).x = refPtArray[mIx].x - pathlist[mIx].path[eltix].x; (*coord).y = refPtArray[mIx].y - pathlist[mIx].path[eltix].y; break; case 1: (*coord).x = pathlist[mIx].path[eltix].x1 - refPtArray[mIx].x; (*coord).y = pathlist[mIx].path[eltix].y1 - refPtArray[mIx].y; break; case 2: (*coord).x = pathlist[mIx].path[eltix].rx2; (*coord).y = pathlist[mIx].path[eltix].ry2; break; case 3: (*coord).x = pathlist[mIx].path[eltix].rx3; (*coord).y = pathlist[mIx].path[eltix].ry3; break; case 4: (*coord).x = pathlist[mIx].path[eltix + 1].rx1; (*coord).y = pathlist[mIx].path[eltix + 1].ry1; break; case 5: (*coord).x = pathlist[mIx].path[eltix + 1].rx2; (*coord).y = pathlist[mIx].path[eltix + 1].ry2; break; case 6: (*coord).x = pathlist[mIx].path[eltix + 1].rx3; (*coord).y = pathlist[mIx].path[eltix + 1].ry3; break; case 7: (*coord).x = pathlist[mIx].path[eltix + 1].x3; (*coord).y = pathlist[mIx].path[eltix + 1].y3; break; } } /* NOTE: Mathematical transformations are applied to the multi-master data in order to decrease the computation during font execution. See /user/foley/atm/blendfont1.910123 for the definition of this transformation. This transformation is applied whenever charstring data is written (i.e. sb and width, flex, hints, dt, ct, mt) AND an OtherSubr 7 - 11 will be called. */ static void WriteFlex(indx eltix) { bool vert = (pathlist[hintsMasterIx].path[eltix].x == pathlist[hintsMasterIx].path[eltix + 1].x3); Cd coord, coord0; /* array of reference points */ bool xsame, ysame, writeSubrOnce; int16_t optype; indx ix, j, opix, startix; int16_t subrIx, length; refPtArray = (Cd*)AllocateMem(masterCount, sizeof(Cd), "reference point array"); for (ix = 0; ix < masterCount; ix++) { refPtArray[ix].x = (vert ? pathlist[ix].path[eltix].x : pathlist[ix].path[eltix].x3); refPtArray[ix].y = (vert ? pathlist[ix].path[eltix].y3 : pathlist[ix].path[eltix].y); } WriteToBuffer("1 subr\n"); for (j = 0; j < 8; j++) { int16_t opcount; if (j == 7) WRTNUM(DMIN); xsame = ysame = false; CheckFlexValues(&optype, eltix, j, &xsame, &ysame); opcount = GetOperandCount(optype); if ((xsame && !ysame) || (!xsame && ysame)) GetLengthandSubrIx((opcount = 1), &length, &subrIx); else GetLengthandSubrIx(opcount, &length, &subrIx); GetFlexCoord(j, 0, eltix, &coord); coord0.x = coord.x; coord0.y = coord.y; if (j == 7) { if (!xsame && (optype == VMT)) WriteX(coord.x); /* usually=0. cf bottom of "CheckFlexValues" */ } if (xsame && ysame) { WriteCd(coord); } else if (xsame) { WriteX(coord.x); if (optype != HMT) { for (ix = 0; ix < masterCount; ix++) { GetFlexCoord(j, ix, eltix, &coord); WriteY((ix == 0 ? coord.y : coord.y - coord0.y)); } WriteSubr(subrIx); } } else if (ysame) { if (optype != VMT) { for (ix = 0; ix < masterCount; ix++) { GetFlexCoord(j, ix, eltix, &coord); WriteX((ix == 0 ? coord.x : coord.x - coord0.x)); } WriteSubr(subrIx); } WriteY(coord.y); } else { startix = 0; if ((writeSubrOnce = (length == opcount))) { if (optype == HMT) WriteX(coord.x); else if (optype == VMT) WriteY(coord.y); else WriteCd(coord); length = startix = 1; } for (opix = 0; opix < opcount; opix += length) { for (ix = startix; ix < masterCount; ix++) { GetFlexCoord(j, ix, eltix, &coord); if (ix != 0) { coord.x -= coord0.x; coord.y -= coord0.y; } switch (optype) { case HMT: WriteX(coord.x); break; case VMT: WriteY(coord.y); break; case RMT: MtorDt(coord, opix, length); break; } } if (!writeSubrOnce || (opix == (opcount - 1))) WriteSubr(subrIx); } /* end of for opix */ } /* end of last else clause */ if (j != 7) { WriteToBuffer("%s 2 subr\n", GetOperator(optype)); } if (j == 7) { if (!ysame && (optype == HMT)) WriteY(coord.y); /* usually=0. cf bottom of "CheckFlexValues" */ } } /* end of j for loop */ WriteToBuffer("0 subr\n"); UnallocateMem(refPtArray); } static void WriteUnmergedHints(indx pathEltIx, indx mIx) { HintElt* hintList; /* hintArray contains the pointers to the beginning of the linked list of * hints for each design at pathEltIx. */ /* Initialize hint list. */ if (pathEltIx == MAINHINTS) hintList = pathlist[mIx].mainhints; else hintList = pathlist[mIx].path[pathEltIx].hints; if (pathEltIx != MAINHINTS) WriteToBuffer("beginsubr snc\n"); while (hintList != NULL) { int16_t hinttype = hintList->type; hintList->rightortop -= hintList->leftorbot; /* relativize */ if ((hinttype == RY || hinttype == (RM + ESCVAL))) /* If it is a cube library, sidebearings are considered to be * zero. for normal fonts, translate vstem hints left by * sidebearing. */ hintList->leftorbot -= FixInt(pathlist[mIx].sb); WriteOneHintVal(hintList->leftorbot); WriteOneHintVal(hintList->rightortop); switch (hinttype) { case RB: WriteToBuffer("rb\n"); break; case RV + ESCVAL: WriteToBuffer("rv\n"); break; case RY: WriteToBuffer("ry\n"); break; case RM + ESCVAL: WriteToBuffer("rm\n"); break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal hint type: %d", hinttype); } if (hintList->next == NULL) hintList = NULL; else hintList = hintList->next; } /* end of while */ if (pathEltIx != MAINHINTS) WriteToBuffer("endsubr enc\nnewcolors\n"); UnallocateMem(hintList); } #define BREAK_ON_NULL_HINTARRAY_IX if(hintArray[ix] == NULL) break static void WriteHints(indx pathEltIx) { indx ix, opix; int16_t opcount, subrIx, length; HintElt** hintArray; bool writeSubrOnce; /* hintArray contains the pointers to the beginning of the linked list of hints for each design at pathEltIx. */ hintArray = (HintElt**)AllocateMem(masterCount, sizeof(HintElt*), "hint element array"); /* Initialize hint array. */ for (ix = 0; ix < masterCount; ix++) hintArray[ix] = (pathEltIx == MAINHINTS ? pathlist[ix].mainhints : pathlist[ix].path[pathEltIx].hints); if (pathEltIx != MAINHINTS) WriteToBuffer("beginsubr snc\n"); while (hintArray[0] != NULL) { bool lbsame, rtsame; indx startix = 0; int16_t hinttype = hintArray[hintsMasterIx]->type; for (ix = 0; ix < masterCount; ix++) { BREAK_ON_NULL_HINTARRAY_IX; hintArray[ix]->rightortop -= hintArray[ix]->leftorbot; /* relativize */ if ((hinttype == RY || hinttype == (RM + ESCVAL))) /* if it is a cube library, sidebearings are considered to be * zero */ /* for normal fonts, translate vstem hints left by sidebearing */ hintArray[ix]->leftorbot -= FixInt(pathlist[ix].sb); } lbsame = rtsame = true; for (ix = 1; ix < masterCount; ix++) { BREAK_ON_NULL_HINTARRAY_IX; if (hintArray[ix]->leftorbot != hintArray[ix - 1]->leftorbot) lbsame = false; if (hintArray[ix]->rightortop != hintArray[ix - 1]->rightortop) rtsame = false; } if (lbsame && rtsame) { WriteOneHintVal(hintArray[0]->leftorbot); WriteOneHintVal(hintArray[0]->rightortop); } else if (lbsame) { WriteOneHintVal(hintArray[0]->leftorbot); for (ix = 0; ix < masterCount; ix++) { BREAK_ON_NULL_HINTARRAY_IX; WriteOneHintVal((ix == 0 ? hintArray[ix]->rightortop : hintArray[ix]->rightortop - hintArray[0]->rightortop)); } GetLengthandSubrIx(1, &length, &subrIx); WriteSubr(subrIx); } else if (rtsame) { for (ix = 0; ix < masterCount; ix++) { BREAK_ON_NULL_HINTARRAY_IX; WriteOneHintVal((ix == 0 ? hintArray[ix]->leftorbot : hintArray[ix]->leftorbot - hintArray[0]->leftorbot)); } GetLengthandSubrIx(1, &length, &subrIx); WriteSubr(subrIx); WriteOneHintVal(hintArray[0]->rightortop); } else { opcount = GetOperandCount(hinttype); GetLengthandSubrIx(opcount, &length, &subrIx); if ((writeSubrOnce = (length == opcount))) { WriteOneHintVal(hintArray[0]->leftorbot); WriteOneHintVal(hintArray[0]->rightortop); length = startix = 1; } for (opix = 0; opix < opcount; opix += length) { for (ix = startix; ix < masterCount; ix++) { BREAK_ON_NULL_HINTARRAY_IX; if (opix == 0) WriteOneHintVal((ix == 0 ? hintArray[ix]->leftorbot : hintArray[ix]->leftorbot - hintArray[0]->leftorbot)); else WriteOneHintVal((ix == 0 ? hintArray[ix]->rightortop : hintArray[ix]->rightortop - hintArray[0]->rightortop)); } if (!writeSubrOnce || (opix == (opcount - 1))) WriteSubr(subrIx); } } switch (hinttype) { case RB: WriteToBuffer("rb\n"); break; case RV + ESCVAL: WriteToBuffer("rv\n"); break; case RY: WriteToBuffer("ry\n"); break; case RM + ESCVAL: WriteToBuffer("rm\n"); break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal hint type: %d.", hinttype); } for (ix = 0; ix < masterCount; ix++) hintArray[ix] = (hintArray[ix]->next == NULL) ? NULL : hintArray[ix]->next; } /* end of while */ if (pathEltIx != MAINHINTS) WriteToBuffer("endsubr enc\nnewcolors\n"); UnallocateMem(hintArray); } static void WritePathElt(indx mIx, indx eltIx, int16_t pathType, indx startix, int16_t length) { Cd c1, c2, c3; GlyphPathElt *path, *path0; path = &pathlist[mIx].path[eltIx]; path0 = &pathlist[0].path[eltIx]; switch (pathType) { case HDT: case HMT: WriteX((mIx == 0 ? path->rx : path->rx - path0->rx)); break; case VDT: case VMT: WriteY((mIx == 0 ? path->ry : path->ry - path0->ry)); break; case RDT: case RMT: case DT: case MT: if (pathType == DT || pathType == MT) { c1.x = path->x; c1.y = path->y; } else { c1.x = (mIx == 0 ? path->rx : path->rx - path0->rx); c1.y = (mIx == 0 ? path->ry : path->ry - path0->ry); } MtorDt(c1, startix, length); break; case HVCT: case VHCT: case RCT: case CT: if (pathType == CT) { c1.x = path->x1; c1.y = path->y1; c2.x = path->x2; c2.y = path->y2; c3.x = path->x3; c3.y = path->y3; } else if (mIx == 0) { c1.x = path->rx1; c1.y = path->ry1; c2.x = path->rx2; c2.y = path->ry2; c3.x = path->rx3; c3.y = path->ry3; } else { c1.x = path->rx1 - path0->rx1; c1.y = path->ry1 - path0->ry1; c2.x = path->rx2 - path0->rx2; c2.y = path->ry2 - path0->ry2; c3.x = path->rx3 - path0->rx3; c3.y = path->ry3 - path0->ry3; } if (pathType == RCT || pathType == CT) Ct(c1, c2, c3, startix, length); else if (pathType == HVCT) Hvct(c1, c2, c3, startix, length); else Vhct(c1, c2, c3, startix, length); break; case CP: break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal path operator %d found.", pathType); } } } static void OptimizeMtorDt(indx eltix, int16_t* op, bool* xequal, bool* yequal) { indx ix; *xequal = *yequal = true; for (ix = 1; ix < masterCount; ix++) { *xequal = *xequal && (pathlist[ix].path[eltix].rx == pathlist[ix - 1].path[eltix].rx); *yequal = *yequal && (pathlist[ix].path[eltix].ry == pathlist[ix - 1].path[eltix].ry); } if (*xequal && pathlist[0].path[eltix].rx == 0) { *op = (*op == RMT) ? VMT : VDT; *xequal = false; } else if (*yequal && pathlist[0].path[eltix].ry == 0) { *op = (*op == RMT) ? HMT : HDT; *yequal = false; } } static bool CoordsEqual(indx master1, indx master2, indx opIx, indx eltIx, int16_t op) { GlyphPathElt *path1 = &pathlist[master1].path[eltIx], *path2 = &pathlist[master2].path[eltIx]; switch (opIx) { case 0: if (op == RCT || op == HVCT) return (path1->rx1 == path2->rx1); else /* op == VHCT */ return (path1->ry1 == path2->ry1); case 1: if (op == RCT) return (path1->ry1 == path2->ry1); else return (path1->rx2 == path2->rx2); case 2: if (op == RCT) return (path1->rx2 == path2->rx2); else return (path1->ry2 == path2->ry2); case 3: if (op == RCT) return (path1->ry2 == path2->ry2); else if (op == HVCT) return (path1->ry3 == path2->ry3); else /* op == VHCT */ return (path1->rx3 == path2->rx3); case 4: return (path1->rx3 == path2->rx3); case 5: return (path1->ry3 == path2->ry3); default: LogMsg(LOGERROR, NONFATALERROR, "Invalid index value: %d defined for curveto " "command4. Op=%d, master=%s near " "(%d %d).", (int)opIx, (int)op, masterNames[master1], FTrunc(path1->x), FTrunc(path1->y)); break; } return 0; } /* Checks if path element values are the same for the RCT, HVCT and VHCT operators in each master master between operands startIx to startIx + length. Returns true if they are the same and false otherwise. */ static bool SamePathValues(indx eltIx, int16_t op, indx startIx, int16_t length) { indx ix, mIx; /* GlyphPathElt* path0 = &pathlist[0].path[eltIx]; */ bool same = true; for (ix = 0; ix < length; ix++) { for (mIx = 1; mIx < masterCount; mIx++) if (!(same = same && CoordsEqual(mIx, 0, startIx, eltIx, op))) return false; startIx++; } return true; } /* Takes multiple path descriptions for the same glyph name and combines them into a single path description using new subroutine calls 7 - 11. */ static void WritePaths(ACBuffer** outBuffers) { indx ix, eltix, opix, startIx, mIx; int16_t length, subrIx, opcount, op; bool xequal, yequal; #if DONT_COMBINE_PATHS for (mIx = 0; mIx < masterCount; mIx++) { PathList path = pathlist[mIx]; outbuff = outBuffers[mIx]; WriteToBuffer("%% %s\n", gGlyphName); if (gAddHints && (pathlist[hintsMasterIx].mainhints != NULL)) WriteUnmergedHints(MAINHINTS, mIx); WriteToBuffer("sc\n"); for (eltix = 0; eltix < gPathEntries; eltix++) { GlyphPathElt elt = path.path[eltix]; op = elt.type; /* Use non-relative operators for easy comparison with input, * I don’t think it is really required. */ if (op == RMT) op = MT; else if (op == RCT) op = CT; else if (op == RDT) op = DT; if (gAddHints && elt.hints != NULL) WriteUnmergedHints(eltix, mIx); opcount = GetOperandCount(op); GetLengthandSubrIx(opcount, &length, &subrIx); WritePathElt(mIx, eltix, op, 0, opcount); WriteToBuffer("%s\n", GetOperator(op)); } WriteToBuffer("ed\n"); } return; #endif /* DONT_COMBINE_PATHS */ /* The code below is not used, but were are not ifdef'ing it and the code * it calls so it keep compiling and does not bitrot. */ WriteToBuffer("%% %s\n", gGlyphName); WriteSbandWidth(); if (gAddHints && (pathlist[hintsMasterIx].mainhints != NULL)) WriteHints(MAINHINTS); WriteToBuffer("sc\n"); firstMT = true; for (eltix = 0; eltix < gPathEntries; eltix++) { xequal = yequal = false; if (gAddHints && (pathlist[hintsMasterIx].path[eltix].hints != NULL)) WriteHints(eltix); switch (pathlist[0].path[eltix].type) { case RMT: /* translate by sidebearing value */ if (firstMT) { for (ix = 0; ix < masterCount; ix++) pathlist[ix].path[eltix].rx -= FixInt(pathlist[ix].sb); } firstMT = false; case RDT: case CP: break; case RCT: if (CheckFlexOK(eltix)) { WriteFlex(eltix); /* Since we know the next element is a flexed curve and has been written out we skip it. */ eltix++; continue; } /* Try to use optimized operators. */ if ((pathlist[0].path[eltix].rx1 == 0 && pathlist[0].path[eltix].ry3 == 0) || (pathlist[0].path[eltix].ry1 == 0 && pathlist[0].path[eltix].rx3 == 0)) OptimizeCT(eltix); break; default: LogMsg(LOGERROR, NONFATALERROR, "Unknown operator."); } op = pathlist[0].path[eltix].type; if (op != RCT && op != HVCT && op != VHCT && op != CP) /* Try to use optimized operators. */ OptimizeMtorDt(eltix, &op, &xequal, &yequal); startIx = 0; opcount = GetOperandCount(op); GetLengthandSubrIx(opcount, &length, &subrIx); if (xequal && yequal) { WritePathElt(0, eltix, op, 0, opcount); } else if (xequal) { WriteX(pathlist[0].path[eltix].rx); if (op != HMT && op != HDT) { for (ix = 0; ix < masterCount; ix++) WriteY(((ix == 0) ? pathlist[ix].path[eltix].ry : pathlist[ix].path[eltix].ry - pathlist[0].path[eltix].ry)); GetLengthandSubrIx(1, &length, &subrIx); WriteSubr(subrIx); } } else if (yequal) { if (op != VMT && op != VDT) { for (ix = 0; ix < masterCount; ix++) WriteX(((ix == 0) ? pathlist[ix].path[eltix].rx : pathlist[ix].path[eltix].rx - pathlist[0].path[eltix].rx)); GetLengthandSubrIx(1, &length, &subrIx); WriteSubr(subrIx); } WriteY(pathlist[0].path[eltix].ry); } else for (opix = 0; opix < opcount; opix += length) { WritePathElt(0, eltix, op, opix, length); startIx = opix; if (op == RCT || op == HVCT || op == VHCT) if (SamePathValues(eltix, op, startIx, length)) continue; for (ix = 0; ix < length; ix++) { for (mIx = 1; mIx < masterCount; mIx++) WritePathElt(mIx, eltix, op, startIx, 1); startIx++; } if (subrIx >= 0 && op != CP) WriteSubr(subrIx); } /* end of for opix */ WriteToBuffer("%s\n", GetOperator(op)); } /* end of for eltix */ WriteToBuffer("ed\n"); } /* Returns number of operands for the given operator. */ static int16_t GetOperandCount(int16_t op) { int16_t count = 0; if (op < ESCVAL) switch (op) { case CP: case HDT: case HMT: case VDT: case VMT: count = 1; break; case RMT: case MT: case RDT: case DT: case RB: case RY: case SBX: count = 2; break; case HVCT: case VHCT: count = 4; break; case RCT: case CT: count = 6; break; default: LogMsg(LOGERROR, NONFATALERROR, "Unknown operator."); break; } else /* handle escape operators */ switch (op - ESCVAL) { case RM: case RV: count = 2; break; } return count; } /* Returns the subr number to use for a given operator in subrIx and checks that the argument length of each subr call does not exceed the font interpreter stack limit. */ static void GetLengthandSubrIx(int16_t opcount, int16_t* length, int16_t* subrIx) { if (((opcount * masterCount) > FONTSTKLIMIT) && opcount != 1) if ((opcount / 2 * masterCount) > FONTSTKLIMIT) if ((2 * masterCount) > FONTSTKLIMIT) *length = 1; else *length = 2; else *length = opcount / 2; else *length = opcount; if (((*length) * masterCount) > FONTSTKLIMIT) { LogMsg(LOGERROR, NONFATALERROR, "Font stack limit exceeded"); } switch (*length) { case 1: *subrIx = 7; break; case 2: *subrIx = 8; break; case 3: *subrIx = 9; break; case 4: *subrIx = 10; break; case 6: *subrIx = 11; break; default: LogMsg(LOGERROR, NONFATALERROR, "Illegal operand length."); break; } } /********** Normal MM fonts have their dimensionality wired into the subrs. That is, the contents of subr 7-11 are computed on a per-font basis. Cube fonts can be of 1-4 dimensions on a per-glyph basis. But there are only a few possible combinations of these numbers because we are limited by the stack size: dimensions arguments values subr# 1 1 2 7 1 2 4 8 1 3 6 9 1 4 8 10 1 6 12 11 2 1 4 12 2 2 8 13 2 3 12 14 2 4 16 15 3 1 8 16 3 2 16 17 4 1 16 18 *************/ bool MergeGlyphPaths(const char** srcglyphs, int nmasters, const char** masters, ACBuffer** outbuffers) { bool ok; /* This requires that master hintsMasterIx has already been hinted with * AutoHint(). See comments in psautohint,c::AutoHintStringMM() */ masterCount = nmasters; masterNames = masters; ok = CompareGlyphPaths(srcglyphs); if (ok) { SetSbandWidth(); if (gAddHints && hintsMasterIx >= 0 && gPathEntries > 0) { if (!ReadandAssignHints()) { LogMsg(LOGERROR, FATALERROR, "Path problem in ReadAndAssignHints"); } } WritePaths(outbuffers); } FreePathElements(masterCount); return ok; } psautohint-2.3.0/libpsautohint/src/charpath.h000066400000000000000000000023721401523215600213340ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #ifndef CHARPATH_H #define CHARPATH_H #include "ac.h" typedef struct _t_hintelt { struct _t_hintelt *next; int16_t type; /* RB, RY, RM, RV */ Fixed leftorbot, rightortop; int32_t pathix1, pathix2; } HintElt; typedef struct { int16_t type; /* RMT, RDT, RCT, CP */ /* the following fields must be cleared in charpathpriv.c/CheckPath */ bool isFlex:1; HintElt* hints; Fixed x, y, x1, y1, x2, y2, x3, y3; /* absolute coordinates */ int32_t rx, ry, rx1, ry1, rx2, ry2, rx3, ry3; /* relative coordinates */ } GlyphPathElt; typedef struct { GlyphPathElt* path; HintElt* mainhints; int32_t sb; int16_t width; } PathList; extern int32_t gPathEntries; /* number of elements in a glyph path */ extern bool gAddHints; /* whether to include hints in the font */ GlyphPathElt* AppendGlyphPathElement(int); void ResetMaxPathEntries(void); void SetCurrPathList(PathList*); void SetHintsElt(int16_t, Cd*, int32_t, int32_t, bool); void SetNoHints(void); #endif /*CHARPATH_H*/ psautohint-2.3.0/libpsautohint/src/charpathpriv.c000066400000000000000000000055021401523215600222260ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* This source file should contain any procedure(s) that are called from AC. It should not contain calls to procedures implemented in object files that are not bound into AC. */ #include "charpath.h" #include "memory.h" int32_t gPathEntries = 0; /* number of elements in a glyph path */ bool gAddHints = true; /* whether to include hints in the font */ #define MAXPATHELT 100 /* initial maximum number of path elements */ static int32_t maxPathEntries = 0; static PathList* currPathList = NULL; static void CheckPath(void); static void CheckPath(void) { if (currPathList->path == NULL) { currPathList->path = (GlyphPathElt*)AllocateMem( maxPathEntries, sizeof(GlyphPathElt), "path element array"); } if (gPathEntries >= maxPathEntries) { int i; maxPathEntries += MAXPATHELT; currPathList->path = (GlyphPathElt*)ReallocateMem( (char*)currPathList->path, maxPathEntries * sizeof(GlyphPathElt), "path element array"); /* Initialize certain fields in GlyphPathElt, since realloc'ed memory */ /* may be non-zero. */ for (i = gPathEntries; i < maxPathEntries; i++) { currPathList->path[i].hints = NULL; currPathList->path[i].isFlex = false; } } } GlyphPathElt* AppendGlyphPathElement(int pathtype) { CheckPath(); currPathList->path[gPathEntries].type = pathtype; gPathEntries++; return (&currPathList->path[gPathEntries - 1]); } /* Called from CompareGlyphPaths when a new glyph is being read. */ void ResetMaxPathEntries(void) { maxPathEntries = MAXPATHELT; } void SetCurrPathList(PathList* plist) { currPathList = plist; } void SetHintsElt(int16_t hinttype, Cd* coord, int32_t elt1, int32_t elt2, bool mainhints) { HintElt** hintEntry; HintElt* lastHintEntry = NULL; if (!gAddHints) return; if (mainhints) /* define main hints for glyph */ hintEntry = &currPathList->mainhints; else { CheckPath(); hintEntry = &currPathList->path[gPathEntries].hints; } lastHintEntry = (HintElt*)AllocateMem(1, sizeof(HintElt), "hint element"); lastHintEntry->type = hinttype; lastHintEntry->leftorbot = coord->x; lastHintEntry->rightortop = coord->y; /* absolute coordinates */ lastHintEntry->pathix1 = elt1; lastHintEntry->pathix2 = elt2; while (*hintEntry != NULL && (*hintEntry)->next != NULL) hintEntry = &(*hintEntry)->next; if (*hintEntry == NULL) *hintEntry = lastHintEntry; else (*hintEntry)->next = lastHintEntry; } psautohint-2.3.0/libpsautohint/src/charprop.c000066400000000000000000000062531401523215600213550ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" /* number of default entries in counter hint glyph list. */ #define COUNTERDEFAULTENTRIES 4 #define COUNTERLISTSIZE 20 char* gVHintList[] = { "m", "M", "T", "ellipsis", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; char* gHHintList[] = { "element", "equivalence", "notelement", "divide", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static char* UpperSpecialGlyphs[] = { "questiondown", "exclamdown", "semicolon", NULL }; static char* LowerSpecialGlyphs[] = { "question", "exclam", "colon", NULL }; static char* NoBlueList[] = { "at", "bullet", "copyright", "currency", "registered", NULL }; bool FindNameInList(char* nm, char** lst) { char** l = lst; while (true) { char* lnm = *l; if (lnm == NULL) return false; if (strcmp(lnm, nm) == 0) return true; l++; } } /* Adds specified glyphs to CounterHintList array. */ int AddCounterHintGlyphs(char* charlist, char* HintList[]) { const char* setList = "(), \t\n\r"; char* token; bool firstTime = true; int16_t ListEntries = COUNTERDEFAULTENTRIES; while (true) { if (firstTime) { token = (char*)strtok(charlist, setList); firstTime = false; } else token = (char*)strtok(NULL, setList); if (token == NULL) break; if (FindNameInList(token, HintList)) continue; /* Currently, HintList must end with a NULL pointer. */ if (ListEntries == (COUNTERLISTSIZE - 1)) { LogMsg(WARNING, OK, "Exceeded counter hints list size. (maximum is %d.) " "Cannot add %s or subsequent characters.", (int)COUNTERLISTSIZE, token); break; } HintList[ListEntries] = AllocateMem(1, strlen(token) + 1, "counter hints list"); strcpy(HintList[ListEntries++], token); } return (ListEntries - COUNTERDEFAULTENTRIES); } int32_t SpecialGlyphType(void) { /* 1 = upper; -1 = lower; 0 = neither */ if (FindNameInList(gGlyphName, UpperSpecialGlyphs)) return 1; if (FindNameInList(gGlyphName, LowerSpecialGlyphs)) return -1; return 0; } bool HHintGlyph(void) { return FindNameInList(gGlyphName, gHHintList); } bool VHintGlyph(void) { return FindNameInList(gGlyphName, gVHintList); } bool NoBlueGlyph(void) { return FindNameInList(gGlyphName, NoBlueList); } bool MoveToNewHints(void) { return strcmp(gGlyphName, "percent") == 0 || strcmp(gGlyphName, "perthousand") == 0; } psautohint-2.3.0/libpsautohint/src/check.c000066400000000000000000000405241401523215600206130ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" static bool g_xflat, g_yflat, g_xdone, g_ydone, g_bbquit; static int32_t g_xstate, g_ystate, g_xstart, g_ystart; static Fixed g_x0, g_cy0, g_x1, g_cy1, g_xloc, g_yloc; static Fixed g_x, g_y, g_xnxt, g_ynxt; static Fixed g_yflatstartx, g_yflatstarty, g_yflatendx, g_yflatendy; static Fixed g_xflatstarty, g_xflatstartx, g_xflatendx, g_xflatendy; static bool g_vert, g_started, g_reCheckSmooth; static Fixed g_loc, g_frst, g_lst, g_fltnvalue; static PathElt* g_e; static bool g_forMultiMaster = false, g_inflPtFound = false; #define STARTING (0) #define goingUP (1) #define goingDOWN (2) /* DEBUG 8 BIT. The SDELTA value must tbe increased by 2 due to change in * coordinate system from 7 to 8 bit FIXED fraction. */ #define SDELTA (FixInt(8)) #define SDELTA3 (FixInt(10)) static void chkBad(void) { g_reCheckSmooth = ResolveConflictBySplit(g_e, false, NULL, NULL); ; } #define GrTan(n, d) (abs(n) * 100 > abs(d) * gSCurveTan) #define LsTan(n, d) (abs(n) * 100 < abs(d) * gSCurveTan) static void chkYDIR(void) { if (g_y > g_yloc) { /* going up */ if (g_ystate == goingUP) return; if (g_ystate == STARTING) g_ystart = g_ystate = goingUP; else /*if (ystate == goingDOWN)*/ { if (g_ystart == goingUP) { g_yflatendx = g_xloc; g_yflatendy = g_yloc; } else if (!g_yflat) { g_yflatstartx = g_xloc; g_yflatstarty = g_yloc; g_yflat = true; } g_ystate = goingUP; } } else if (g_y < g_yloc) { /* going down */ if (g_ystate == goingDOWN) return; if (g_ystate == STARTING) g_ystart = g_ystate = goingDOWN; else /*if (ystate == goingUP)*/ { if (g_ystart == goingDOWN) { g_yflatendx = g_xloc; g_yflatendy = g_yloc; } else if (!g_yflat) { g_yflatstartx = g_xloc; g_yflatstarty = g_yloc; g_yflat = true; } g_ystate = goingDOWN; } } } static void chkYFLAT(void) { if (!g_yflat) { if (LsTan(g_y - g_yloc, g_x - g_xloc)) { g_yflat = true; g_yflatstartx = g_xloc; g_yflatstarty = g_yloc; } return; } if (g_ystate != g_ystart) return; if (GrTan(g_y - g_yloc, g_x - g_xloc)) { g_yflatendx = g_xloc; g_yflatendy = g_yloc; g_ydone = true; } } static void chkXFLAT(void) { if (!g_xflat) { if (LsTan(g_x - g_xloc, g_y - g_yloc)) { g_xflat = true; g_xflatstartx = g_xloc; g_xflatstarty = g_yloc; } return; } if (g_xstate != g_xstart) return; if (GrTan(g_x - g_xloc, g_y - g_yloc)) { g_xflatendx = g_xloc; g_xflatendy = g_yloc; g_xdone = true; } } static void chkXDIR(void) { if (g_x > g_xloc) { /* going up */ if (g_xstate == goingUP) return; if (g_xstate == STARTING) g_xstart = g_xstate = goingUP; else /*if (xstate == goingDOWN)*/ { if (g_xstart == goingUP) { g_xflatendx = g_xloc; g_xflatendy = g_yloc; } else if (!g_xflat) { g_xflatstartx = g_xloc; g_xflatstarty = g_yloc; g_xflat = true; } g_xstate = goingUP; } } else if (g_x < g_xloc) { if (g_xstate == goingDOWN) return; if (g_xstate == STARTING) g_xstart = g_xstate = goingDOWN; else /*if (xstate == goingUP)*/ { if (g_xstart == goingDOWN) { g_xflatendx = g_xloc; g_xflatendy = g_yloc; } else if (!g_xflat) { g_xflatstartx = g_xloc; g_xflatstarty = g_yloc; g_xflat = true; } g_xstate = goingDOWN; } } } static void chkDT(Cd c) { Fixed loc; g_x = c.x; g_y = c.y; g_ynxt = g_y; g_xnxt = g_x; if (!g_ydone) { chkYDIR(); chkYFLAT(); if (g_ydone && g_yflat && abs(g_yflatstarty - g_cy0) > SDELTA && abs(g_cy1 - g_yflatendy) > SDELTA) { if ((g_ystart == goingUP && g_yflatstarty - g_yflatendy > SDELTA) || (g_ystart == goingDOWN && g_yflatendy - g_yflatstarty > SDELTA)) { if (gEditGlyph && !g_forMultiMaster) chkBad(); return; } if (abs(g_yflatstartx - g_yflatendx) > SDELTA3) { DEBUG_ROUND(g_yflatstartx); DEBUG_ROUND(g_yflatendx); DEBUG_ROUND(g_yflatstarty); DEBUG_ROUND(g_yflatendy); loc = (g_yflatstarty + g_yflatendy) / 2; DEBUG_ROUND(loc); if (!g_forMultiMaster) { AddHSegment(g_yflatstartx, g_yflatendx, loc, g_e, NULL, sCURVE, 13); } else { g_inflPtFound = true; g_fltnvalue = -loc; } } } } if (!g_xdone) { chkXDIR(); chkXFLAT(); if (g_xdone && g_xflat && abs(g_xflatstartx - g_x0) > SDELTA && abs(g_x1 - g_xflatendx) > SDELTA) { if ((g_xstart == goingUP && g_xflatstartx - g_xflatendx > SDELTA) || (g_xstart == goingDOWN && g_xflatendx - g_xflatstartx > SDELTA)) { if (gEditGlyph && !g_forMultiMaster) chkBad(); return; } if (abs(g_xflatstarty - g_xflatendy) > SDELTA3) { DEBUG_ROUND(g_xflatstarty); DEBUG_ROUND(g_xflatendy); DEBUG_ROUND(g_xflatstartx); DEBUG_ROUND(g_xflatendx); loc = (g_xflatstartx + g_xflatendx) / 2; DEBUG_ROUND(loc); if (!g_forMultiMaster) { AddVSegment(g_xflatstarty, g_xflatendy, loc, g_e, NULL, sCURVE, 13); } else { g_inflPtFound = true; g_fltnvalue = loc; } } } } g_xloc = g_xnxt; g_yloc = g_ynxt; } #define FQ(x) ((int32_t)((x) >> 6)) static int32_t CPDirection(Fixed x1, Fixed cy1, Fixed x2, Fixed y2, Fixed x3, Fixed y3) { int32_t q, q1, q2, q3; q1 = FQ(x2) * FQ(y3 - cy1); q2 = FQ(x1) * FQ(y2 - y3); q3 = FQ(x3) * FQ(cy1 - y2); q = q1 + q2 + q3; if (q > 0) return 1; if (q < 0) return -1; return 0; } void RMovePoint(Fixed dx, Fixed dy, int32_t whichcp, PathElt* e) { if (whichcp == cpStart) { e = e->prev; whichcp = cpEnd; } if (whichcp == cpEnd) { if (e->type == CLOSEPATH) e = GetDest(e); if (e->type == CURVETO) { e->x3 += dx; e->y3 += dy; } else { e->x += dx; e->y += dy; } return; } if (whichcp == cpCurve1) { e->x1 += dx; e->y1 += dy; return; } if (whichcp == cpCurve2) { e->x2 += dx; e->y2 += dy; return; } LogMsg(LOGERROR, NONFATALERROR, "Malformed path list."); } void Delete(PathElt* e) { PathElt *nxt, *prv; nxt = e->next; prv = e->prev; if (nxt != NULL) nxt->prev = prv; else gPathEnd = prv; if (prv != NULL) prv->next = nxt; else gPathStart = nxt; } /* This procedure is called from BuildFont when adding hints to base designs of a multi-master font. */ bool GetInflectionPoint(Fixed px, Fixed py, Fixed px1, Fixed pcy1, Fixed px2, Fixed py2, Fixed px3, Fixed py3, Fixed* inflPt) { FltnRec fltnrec; Cd c0, c1, c2, c3; fltnrec.report = chkDT; c0.x = px; c0.y = -py; c1.x = px1; c1.y = -pcy1; c2.x = px2; c2.y = -py2; c3.x = px3; c3.y = -py3; g_xstate = g_ystate = STARTING; g_xdone = g_ydone = g_xflat = g_yflat = g_inflPtFound = false; g_x0 = c0.x; g_cy0 = c0.y; g_x1 = c3.x; g_cy1 = c3.y; g_xloc = g_x0; g_yloc = g_cy0; g_forMultiMaster = true; FltnCurve(c0, c1, c2, c3, &fltnrec); if (g_inflPtFound) *inflPt = g_fltnvalue; return g_inflPtFound; } static void CheckSCurve(PathElt* ee) { FltnRec fr; Cd c0, c1, c2, c3; if (ee->type != CURVETO) { LogMsg(LOGERROR, NONFATALERROR, "Malformed path list."); } GetEndPoint(ee->prev, &c0.x, &c0.y); fr.report = chkDT; c1.x = ee->x1; c1.y = ee->y1; c2.x = ee->x2; c2.y = ee->y2; c3.x = ee->x3; c3.y = ee->y3; g_xstate = g_ystate = STARTING; g_xdone = g_ydone = g_xflat = g_yflat = false; g_x0 = c0.x; g_cy0 = c0.y; g_x1 = c3.x; g_cy1 = c3.y; g_xloc = g_x0; g_yloc = g_cy0; g_e = ee; g_forMultiMaster = false; FltnCurve(c0, c1, c2, c3, &fr); } static void CheckZeroLength(void) { PathElt *e, *NxtE; Fixed x0, cy0, x1, cy1, x2, y2, x3, y3; if ((!gEditGlyph) || g_forMultiMaster) { /* Do not change topology when hinting MM fonts, and do not edit glyphs if not requested */ return; } e = gPathStart; while (e != NULL) { /* delete zero length elements */ NxtE = e->next; GetEndPoints(e, &x0, &cy0, &x1, &cy1); if (e->type == LINETO && x0 == x1 && cy0 == cy1) { Delete(e); goto Nxt1; } if (e->type == CURVETO) { x2 = e->x1; y2 = e->y1; x3 = e->x2; y3 = e->y2; if (x0 == x1 && cy0 == cy1 && x2 == x1 && x3 == x1 && y2 == cy1 && y3 == cy1) { Delete(e); goto Nxt1; } } Nxt1: e = NxtE; } } void CheckSmooth(void) { PathElt *e, *nxt, *NxtE; bool recheck; Fixed x0, cy0, x1, cy1, x2, y2, x3, y3, smdiff, xx, yy; CheckZeroLength(); restart: g_reCheckSmooth = false; recheck = false; e = gPathStart; while (e != NULL) { NxtE = e->next; if (e->type == MOVETO || IsTiny(e) || e->isFlex) goto Nxt; GetEndPoint(e, &x1, &cy1); if (e->type == CURVETO) { int32_t cpd0, cpd1; x2 = e->x1; y2 = e->y1; x3 = e->x2; y3 = e->y2; GetEndPoint(e->prev, &x0, &cy0); cpd0 = CPDirection(x0, cy0, x2, y2, x3, y3); cpd1 = CPDirection(x2, y2, x3, y3, x1, cy1); if (ProdLt0(cpd0, cpd1)) CheckSCurve(e); } nxt = NxtForBend(e, &x2, &y2, &xx, &yy); if (nxt->isFlex) goto Nxt; PrvForBend(nxt, &x0, &cy0); if (!CheckSmoothness(x0, cy0, x1, cy1, x2, y2, &smdiff)) LogMsg(INFO, OK, "Junction at %g %g may need smoothing.", FixToDbl(x1), FixToDbl(-cy1)); if (smdiff > FixInt(160)) LogMsg(INFO, OK, "Too sharp angle at %g %g has been clipped.", FixToDbl(x1), FixToDbl(-cy1)); Nxt: e = NxtE; } if (g_reCheckSmooth) goto restart; if (!recheck) return; CheckZeroLength(); /* in certain cases clip sharp point can produce a zero length line */ } #define BBdist \ (FixInt(20)) /* DEBUG 8 BIT. DOuble value from 10 to 20 for change in \ coordinate system. */ static void chkBBDT(Cd c) { Fixed x = c.x, y = c.y; if (g_bbquit) return; if (g_vert) { g_lst = y; if (!g_started && abs(x - g_loc) <= BBdist) { g_started = true; g_frst = y; } else if (g_started && abs(x - g_loc) > BBdist) g_bbquit = true; } else { g_lst = x; if (!g_started && abs(y - g_loc) <= BBdist) { g_started = true; g_frst = x; } else if (g_started && abs(y - g_loc) > BBdist) g_bbquit = true; } } void CheckForMultiMoveTo(void) { PathElt* e = gPathStart; bool moveto; moveto = false; while (e != NULL) { if (e->type != MOVETO) moveto = false; else if (!moveto) moveto = true; else Delete(e->prev); /* delete previous moveto */ e = e->next; } } void CheckBBoxEdge(PathElt* e, bool vrt, Fixed lc, Fixed* pf, Fixed* pl) { FltnRec fr; Cd c0, c1, c2, c3; if (e->type != CURVETO) { LogMsg(LOGERROR, NONFATALERROR, "Malformed path list."); } GetEndPoint(e->prev, &c0.x, &c0.y); fr.report = chkBBDT; g_bbquit = false; c1.x = e->x1; c1.y = e->y1; c2.x = e->x2; c2.y = e->y2; c3.x = e->x3; c3.y = e->y3; g_loc = lc; g_vert = vrt; g_started = false; chkBBDT(c0); FltnCurve(c0, c1, c2, c3, &fr); *pf = g_frst; *pl = g_lst; } static void MakeColinear(Fixed tx, Fixed ty, Fixed x0, Fixed cy0, Fixed x1, Fixed cy1, Fixed* xptr, Fixed* yptr) { Fixed dx, dy; float rdx, rdy, dxdy, dxsq, dysq, dsq, xi, yi, rx, ry, rx0, ry0; dx = x1 - x0; dy = cy1 - cy0; if (dx == 0 && dy == 0) { *xptr = tx; *yptr = ty; return; } if (dx == 0) { *xptr = x0; *yptr = ty; return; } if (dy == 0) { *xptr = tx; *yptr = cy0; return; } acfixtopflt(dx, &rdx); acfixtopflt(dy, &rdy); acfixtopflt(x0, &rx0); acfixtopflt(cy0, &ry0); acfixtopflt(tx, &rx); acfixtopflt(ty, &ry); dxdy = rdx * rdy; dxsq = rdx * rdx; dysq = rdy * rdy; dsq = dxsq + dysq; xi = (rx * dxsq + rx0 * dysq + (ry - ry0) * dxdy) / dsq; yi = ry0 + ((xi - rx0) * rdy) / rdx; *xptr = acpflttofix(&xi); *yptr = acpflttofix(&yi); } #define DEG(x) ((x)*57.29577951308232088) static Fixed ATan(Fixed a, Fixed b) { float aa, bb, cc; acfixtopflt(a, &aa); acfixtopflt(b, &bb); cc = (float)DEG(atan2((double)aa, (double)bb)); while (cc < 0) cc += 360.0f; return acpflttofix(&cc); } bool CheckSmoothness(Fixed x0, Fixed cy0, Fixed x1, Fixed cy1, Fixed x2, Fixed y2, Fixed* pd) { Fixed dx, dy, smdiff, smx, smy, at0, at1; dx = x0 - x1; dy = cy0 - cy1; *pd = 0; if (dx == 0 && dy == 0) return true; at0 = ATan(dx, dy); dx = x1 - x2; dy = cy1 - y2; if (dx == 0 && dy == 0) return true; at1 = ATan(dx, dy); smdiff = at0 - at1; if (smdiff < 0) smdiff = -smdiff; if (smdiff >= FixInt(180)) smdiff = FixInt(360) - smdiff; *pd = smdiff; if (smdiff == 0 || smdiff > FixInt(30)) return true; MakeColinear(x1, cy1, x0, cy0, x2, y2, &smx, &smy); smx = FHalfRnd(smx); smy = FHalfRnd(smy); /* DEBUG 8 BIT. Double hard coded distance values, for change from 7 to 8 * bits for fractions. */ return abs(smx - x1) < FixInt(4) && abs(smy - cy1) < FixInt(4); } void CheckForDups(void) { PathElt *ob, *nxt; Fixed x, y; ob = gPathStart; while (ob != NULL) { nxt = ob->next; if (ob->type == MOVETO) { x = ob->x; y = ob->y; ob = nxt; while (ob != NULL) { if (ob->type == MOVETO && x == ob->x && y == ob->y) goto foundMatch; ob = ob->next; } } ob = nxt; } return; foundMatch: y = -y; ReportDuplicates(x, y); } void MoveSubpathToEnd(PathElt* e) { PathElt *subEnd, *subStart, *subNext, *subPrev; subEnd = (e->type == CLOSEPATH) ? e : GetClosedBy(e); subStart = GetDest(subEnd); if (subEnd == gPathEnd) return; /* already at end */ subNext = subEnd->next; if (subStart == gPathStart) { gPathStart = subNext; subNext->prev = NULL; } else { subPrev = subStart->prev; subPrev->next = subNext; subNext->prev = subPrev; } gPathEnd->next = subStart; subStart->prev = gPathEnd; subEnd->next = NULL; gPathEnd = subEnd; } psautohint-2.3.0/libpsautohint/src/control.c000066400000000000000000000634361401523215600212250ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "bbox.h" static void DoHStems(HintVal* sLst1); static void DoVStems(HintVal* sLst); static bool CounterFailed; void InitAll(int32_t reason) { InitData(reason); /* must be first */ InitFix(reason); InitGen(reason); InitPick(reason); } static int32_t PtLstLen(HintPoint* lst) { int32_t cnt = 0; while (lst != NULL) { cnt++; lst = lst->next; } return cnt; } static int32_t PointListCheck(HintPoint* new, HintPoint* lst) { /* -1 means not a member, 1 means already a member, 0 means conflicts */ Fixed l1 = 0, l2 = 0, n1 = 0, n2 = 0, tmp, halfMargin; char ch = new->c; halfMargin = FixHalfMul(gBandMargin); halfMargin = FixHalfMul(halfMargin); /* DEBUG 8 BIT. In the previous version, with 7 bit fraction coordinates instead of the current 8 bit, bandMargin is declared as 30, but scaled by half to match the 7 bit fraction coordinate -> a value of 15. In the current version this scaling doesn't happen. However, in this part of the code, the hint values are scaled up to 8 bits of fraction even in the original version, but topBand is applied without correcting for the scaling difference. In this version I need to divide by half again in order to get to the same value. I think the original is a bug, but it has been working for 30 years, so I am not going to change the test now. */ switch (ch) { case 'y': case 'm': { n1 = new->x0; n2 = new->x1; break; } case 'b': case 'v': { n1 = new->y0; n2 = new->y1; break; } default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal character in point list."); } } if (n1 > n2) { tmp = n1; n1 = n2; n2 = tmp; } while (true) { if (lst == NULL) { return -1; } if (lst->c == ch) { /* same kind of hint */ switch (ch) { case 'y': case 'm': { l1 = lst->x0; l2 = lst->x1; break; } case 'b': case 'v': { l1 = lst->y0; l2 = lst->y1; break; } } if (l1 > l2) { tmp = l1; l1 = l2; l2 = tmp; } if (l1 == n1 && l2 == n2) { return 1; } /* Add this extra margin to the band to fix a problem in TimesEuropa/Italic/v,w,y where a main hstem hint was being merged with newhints. This main hstem caused problems in rasterization so it shouldn't be included. */ l1 -= halfMargin; l2 += halfMargin; if (l1 <= n2 && n1 <= l2) { return 0; } } lst = lst->next; } } static bool SameHintLists(HintPoint* lst1, HintPoint* lst2) { if (PtLstLen(lst1) != PtLstLen(lst2)) { return false; } while (lst1 != NULL) { /* go through lst1 */ if (PointListCheck(lst1, lst2) != 1) { return false; } lst1 = lst1->next; } return true; } bool SameHints(int32_t cn1, int32_t cn2) { if (cn1 == cn2) { return true; } return SameHintLists(gPtLstArray[cn1], gPtLstArray[cn2]); } void MergeFromMainHints(char ch) { HintPoint* lst; for (lst = gPtLstArray[0]; lst != NULL; lst = lst->next) { if (lst->c != ch) { continue; } if (PointListCheck(lst, gPointList) == -1) { if (ch == 'b') { AddHintPoint(0, lst->y0, 0, lst->y1, ch, lst->p0, lst->p1); } else { AddHintPoint(lst->x0, 0, lst->x1, 0, ch, lst->p0, lst->p1); } } } } void AddHintPoint(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch, PathElt* p0, PathElt* p1) { HintPoint* pt; int32_t chk; pt = (HintPoint*)Alloc(sizeof(HintPoint)); pt->x0 = x0; pt->y0 = y0; pt->x1 = x1; pt->y1 = y1; pt->c = ch; pt->done = false; pt->next = NULL; pt->p0 = p0; pt->p1 = p1; chk = PointListCheck(pt, gPointList); if (chk == 0) { ReportHintConflict(x0, y0, x1, y1, ch); } if (chk == -1) { pt->next = gPointList; gPointList = pt; LogHintInfo(gPointList); } } static void CopyHintFromLst(char hint, HintPoint* lst) { bool bvflg = (hint == 'b' || hint == 'v'); while (lst != NULL) { if (lst->c == hint) { if (bvflg) { AddHintPoint(0, lst->y0, 0, lst->y1, hint, lst->p0, lst->p1); } else { AddHintPoint(lst->x0, 0, lst->x1, 0, hint, lst->p0, lst->p1); } } lst = lst->next; } } void CopyMainV(void) { CopyHintFromLst('m', gPtLstArray[0]); } void CopyMainH(void) { CopyHintFromLst('v', gPtLstArray[0]); } void AddHPair(HintVal* v, char ch) { Fixed bot, top; PathElt *p0, *p1, *p; bot = -v->vLoc1; top = -v->vLoc2; p0 = v->vBst->vSeg1->sElt; p1 = v->vBst->vSeg2->sElt; if (top < bot) { Fixed tmp = top; top = bot; bot = tmp; p = p0; p0 = p1; p1 = p; } if (v->vGhst) { if (v->vSeg1->sType == sGHOST) { bot = top; p0 = p1; p1 = NULL; top = bot - FixInt(20); /* width == -20 iff bottom seg is ghost */ } else { top = bot; p1 = p0; p0 = NULL; bot = top + FixInt(21); /* width == -21 iff top seg is ghost */ } } AddHintPoint(0, bot, 0, top, ch, p0, p1); } void AddVPair(HintVal* v, char ch) { Fixed lft, rght; PathElt *p0, *p1, *p; lft = v->vLoc1; rght = v->vLoc2; p0 = v->vBst->vSeg1->sElt; p1 = v->vBst->vSeg2->sElt; if (lft > rght) { Fixed tmp = lft; lft = rght; rght = tmp; p = p0; p0 = p1; p1 = p; } AddHintPoint(lft, 0, rght, 0, ch, p0, p1); } static bool UseCounter(HintVal* sLst, bool mhint) { int32_t cnt = 0; Fixed minLoc, midLoc, maxLoc, prevBstVal, bestVal; Fixed minDelta, midDelta, maxDelta, th; HintVal *lst, *newLst; minLoc = midLoc = maxLoc = FixInt(20000); minDelta = midDelta = maxDelta = 0; lst = sLst; while (lst != NULL) { cnt++; lst = lst->vNxt; } if (cnt < 3) { return false; } cnt -= 3; prevBstVal = 0; while (cnt > 0) { cnt--; if (cnt == 0) { prevBstVal = sLst->vVal; } sLst = sLst->vNxt; } bestVal = sLst->vVal; if (prevBstVal > FixInt(1000) || bestVal < prevBstVal * 10) { return false; } newLst = sLst; while (sLst != NULL) { Fixed loc = sLst->vLoc1; Fixed delta = sLst->vLoc2 - loc; loc += FixHalfMul(delta); if (loc < minLoc) { maxLoc = midLoc; maxDelta = midDelta; midLoc = minLoc; midDelta = minDelta; minLoc = loc; minDelta = delta; } else if (loc < midLoc) { maxLoc = midLoc; maxDelta = midDelta; midLoc = loc; midDelta = delta; } else { maxLoc = loc; maxDelta = delta; } sLst = sLst->vNxt; } th = FixInt(5) / 100; if (abs(minDelta - maxDelta) < th && abs((maxLoc - midLoc) - (midLoc - minLoc)) < th) { if (mhint) { gVHinting = newLst; } else { gHHinting = newLst; } return true; } if (abs(minDelta - maxDelta) < FixInt(3) && abs((maxLoc - midLoc) - (midLoc - minLoc)) < FixInt(3)) { LogMsg(INFO, OK, mhint ? "Near miss for using V counter hinting." : "Near miss for using H counter hinting."); } return false; } static void GetNewPtLst(void) { if (gNumPtLsts >= gMaxPtLsts) { /* increase size */ HintPoint** newArray; int32_t i; int32_t newSize = gMaxPtLsts * 2; newArray = (HintPoint**)Alloc(newSize * sizeof(HintPoint*)); for (i = 0; i < gMaxPtLsts; i++) { newArray[i] = gPtLstArray[i]; } gPtLstArray = newArray; gMaxPtLsts = newSize; } gPtLstIndex = gNumPtLsts; gNumPtLsts++; gPointList = NULL; gPtLstArray[gPtLstIndex] = NULL; } void XtraHints(PathElt* e) { /* this can be simplified for standalone hinting */ gPtLstArray[gPtLstIndex] = gPointList; if (e->newhints == 0) { GetNewPtLst(); e->newhints = (int16_t)gPtLstIndex; } gPtLstIndex = e->newhints; gPointList = gPtLstArray[gPtLstIndex]; } static void Blues(unsigned char* links) { Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0; HintVal* sLst; /* * Top alignment zones are in the global 'gTopBands', bottom in * 'gBotBands'. * * This function looks through the path, as defined by the linked list of * PathElt's, starting at the global 'gPathStart', and adds to several * lists. Coordinates are stored in the PathElt.(x,y) as (original * value)/2.0, aka right shifted by 1 bit from the original 24.8 Fixed. I * suspect that is to allow a larger integer portion - when this program * was written, an int was 16 bits. * * 'gHStems' and 'gVStems' are global arrays of Fixed 24.8 numbers.. * * 'gSegLists' is an array of 4 HintSeg linked lists. List 0 and 1 are * respectively up and down vertical segments. Lists 2 and 3 are * respectively left pointing and right pointing horizontal segments. On a * counter-clockwise path, this is the same as selecting top and bottom * stem locations. * * NoBlueGlyph() consults a hard-coded list of glyph names, if the glyph is * in this list, set the alignment zones ('gTopBands' and 'gBotBands') to * empty. * * 1) gen.c:GenHPts() * Builds the raw list of stem segments in global * 'topList' and 'botList'. It steps through the liked list of path * segments, starting at 'gPathStart'. It decides if a path is mostly H, * and if so, adds it to a linked list of hstem candidates in gSegLists, * by calling gen.c:AddHSegment(). This calls ReportAddHSeg() (useful in * debugging), and then gen.c:AddSegment(). * * If the path segment is in fact entirely vertical and is followed by a * sharp bend, gen.c:GenHPts() adds two new path segments just 1 unit * long, after the segment end point, called H/VBends (segment type * sBend=1). I have no idea what these are for. * * AddSegment() is pretty simple. It creates a new hint segment * (HintSeg) for the parent PathElt, fills it in, adds it to appropriate * list of the 4 gSegLists, and then sorts by hstem location. seg->sElt * is the parent PathElt, seg->sType is the type, seg->sLoc is the * location in Fixed 18.14: right shift 7 to get integer value. * * If the current PathElt is a Closepath, It also calls LinkSegment() to * add the current stem segment to the list of stem segments referenced * by this elt's e->Hs/Vs. * * Note that a hint segment is created for each nearly vertical or * horizontal PathElt. This means that in an H, there will be two hint * segments created for the bottom and top of the H, as there are two * horizontal paths with the same Y at the top and bottom of the H. * * Assign the top and bottom Hstem location lists. * topList = segLists[2] * botList = segLists[3]; * * 2) eval.c::EvalH() * Evaluates every combination of botList and topList, and assign a * priority value and a 'Q' value. * * For each bottom stem * for each top stem * 1) assign priority (spc) and weight (val) values with EvalHPair() * 2) report stem near misses in the 'HStems' list with HStemMiss() * 3) decide whether to add pair to 'HStems' list with AddHValue() * * Add ghost hints. * For each bottom stem segment and then for each top stem segment: * if it is in an alignment zone, make a ghost hint segment and add it * with AddHValue(). * * EvalHPair() sets priority (spc) and weight (val) values. * Omit pair by setting value to 0 if: * bottom is in bottom alignment zone, and top is in top alignment * zone. (otherwise, these will override the ghost hints). * * Boost priority by +2 if either the bot or top segment is in an * alignment zone. * * dy = stem width ( top - bot) * * Calculate dist. Dist is set to a fudge factor * dy. * if bottom segment xo->x1 overlaps top x0->x1, the fudge factor is * 1.0. The less the overlap, the larger the fduge factor. * if bottom segment xo->x1 overlaps top x0->x1:. * if top and bottom overlap exactly, dist = dy * if they barely overlap, dist = 1.4*dy * in between, interpolate. * else, look at closest ends betwen bottom and top segments. * dx = min X separation between top and bottom segments. * dist = 1.4 *dy * dist += dx*dx * if dx > dy: * dist *= dx / dy; * * Look through the gHStems global list. For each match to dy, boost * priority by +1. * * Calculate weight with gen.c:AdjustVal() * if dy is more than twice the 1.1.5* the largest hint in gHStems, * set weight to 0. * Calculate weight as related to length of the segments squared * divided by the distance squared. * Basically, the greater the ratio segment overlap to stem width, * the higher the value. * if dy is greater than the largest stem hint in gHStems, decrease * the value scale weight by of * (largest stem hint in * gHStems)/dy)**3. * * AddHValue() decides whether add a (bottom, top) pair of hint segments. * Do not add the pair if: * if weight (val) is 0, * if both are sBEND segments * if neither are a ghost hint, and weight <= pruneD and priority (spc) * is <= 0: * if either is an sBEND: skip * if the BBox for one segment is the same or inside the BBox for the * other: skip * * else add it with eval.c:InsertHValue() * add new HintVal to global valList. * item->vVal = val; # weight * item->initVal = val; # originl weight from EvalHPair() * item->vSpc = spc; # priority * item->vLoc1 = bot; # bottom Y value in Fixed 18.14 * item->vLoc2 = top; # top Y value in Fixed 18.14 * item->vSeg1 = bSeg; # bottom hint segment * item->vSeg2 = tSeg; # top hint segment * item->vGhst = ghst; # if it is a ghost segment. * The new item is inserted after the first element where vlist->vLoc2 >= top * and vlist->vLoc1 >= bottom * * 3) merge.c:PruneHVals(); * * item2 in the list knocks out item1 if: * 1) (item2 val is more than 3* greater than item1 val) and * (val 1 is less than FixedInt(100)) and * (item2 top and bottom is within item 1 top and bottom) and * (if val1 is more than 50* less than val2 and either top * seg1 is close to top seg 2, or bottom seg1 is close to * bottom seg 2) and * (val 1 < FixInt(16)) or * ((item1 top not in blue zone, or top1 = top2) and * (item1 bottom not in blue zone, or top1 = bottom2)) * "Close to" for the bottom segment means you can get to the bottom elt for * item 2 from bottom elt for 1 within the same path, by * stepping either forward or back from item 1's elt, and without going * outside the bounds between * location 1 and location 2. Same for top segments. * * 4) pick.c:FindBestHVals(); * When a hint segment */ LogMsg(LOGDEBUG, OK, "generate blues"); if (NoBlueGlyph()) { gLenTopBands = gLenBotBands = 0; } GenHPts(); LogMsg(LOGDEBUG, OK, "evaluate"); if (!CounterFailed && HHintGlyph()) { pv = gPruneValue; gPruneValue = (Fixed)gMinVal; pa = gPruneA; gPruneA = (Fixed)gMinVal; pd = gPruneD; gPruneD = (Fixed)gMinVal; pc = gPruneC; gPruneC = (Fixed)gMaxVal; pb = gPruneB; gPruneB = (Fixed)gMinVal; } EvalH(); PruneHVals(); FindBestHVals(); MergeVals(false); ShowHVals(gValList); LogMsg(LOGDEBUG, OK, "pick best"); MarkLinks(gValList, true, links); CheckVals(gValList, false); /* Report stems and alignment zones, if this has been requested. */ if (gDoAligns || gDoStems) DoHStems(gValList); /* Moves best HintVal items from valList to Hhinting list. * (? Choose from set of HintVals for the samte stem values.) */ PickHVals(gValList); if (!CounterFailed && HHintGlyph()) { gPruneValue = pv; gPruneD = pd; gPruneC = pc; gPruneB = pb; gPruneA = pa; gUseH = UseCounter(gHHinting, false); if (!gUseH) { /* try to fix */ AddBBoxHV(true, true); gUseH = UseCounter(gHHinting, false); if (!gUseH) { /* still bad news */ LogMsg(INFO, OK, "Glyph is in list for using H counter hints, " "but didn't find any candidates."); CounterFailed = true; } } } else { gUseH = false; } if (gHHinting == NULL) { AddBBoxHV(true, false); } LogMsg(LOGDEBUG, OK, "results"); LogMsg(LOGDEBUG, OK, gUseH ? "rv" : "rb"); ShowHVals(gHHinting); if (gUseH) { LogMsg(INFO, OK, "Using H counter hints."); } sLst = gHHinting; while (sLst != NULL) { AddHPair(sLst, gUseH ? 'v' : 'b'); /* actually adds hint */ sLst = sLst->vNxt; } } static void DoHStems(HintVal* sLst1) { Fixed glyphTop = INT32_MIN, glyphBot = INT32_MAX; bool curved; while (sLst1 != NULL) { Fixed bot = -sLst1->vLoc1; Fixed top = -sLst1->vLoc2; if (top < bot) { Fixed tmp = top; top = bot; bot = tmp; } if (top > glyphTop) glyphTop = top; if (bot < glyphBot) glyphBot = bot; /* skip if ghost or not a line on top or bottom */ if (!sLst1->vGhst) { curved = !FindLineSeg(sLst1->vLoc1, botList) && !FindLineSeg(sLst1->vLoc2, topList); AddHStem(top, bot, curved); if (top != INT32_MIN || bot != INT32_MAX) AddStemExtremes(bot, top); } sLst1 = sLst1->vNxt; } if (glyphTop != INT32_MIN || glyphBot != INT32_MAX) AddGlyphExtremes(glyphBot, glyphTop); } static void Yellows(unsigned char* links) { Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0; HintVal* sLst; LogMsg(LOGDEBUG, OK, "generate yellows"); GenVPts(SpecialGlyphType()); LogMsg(LOGDEBUG, OK, "evaluate"); if (!CounterFailed && VHintGlyph()) { pv = gPruneValue; gPruneValue = (Fixed)gMinVal; pa = gPruneA; gPruneA = (Fixed)gMinVal; pd = gPruneD; gPruneD = (Fixed)gMinVal; pc = gPruneC; gPruneC = (Fixed)gMaxVal; pb = gPruneB; gPruneB = (Fixed)gMinVal; } EvalV(); PruneVVals(); FindBestVVals(); MergeVals(true); ShowVVals(gValList); LogMsg(LOGDEBUG, OK, "pick best"); MarkLinks(gValList, false, links); CheckVals(gValList, true); if (gDoAligns || gDoStems) DoVStems(gValList); PickVVals(gValList); if (!CounterFailed && VHintGlyph()) { gPruneValue = pv; gPruneD = pd; gPruneC = pc; gPruneB = pb; gPruneA = pa; gUseV = UseCounter(gVHinting, true); if (!gUseV) { /* try to fix */ AddBBoxHV(false, true); gUseV = UseCounter(gVHinting, true); if (!gUseV) { /* still bad news */ LogMsg(INFO, OK, "Glyph is in list for using V counter hints, " "but didn't find any candidates."); CounterFailed = true; } } } else { gUseV = false; } if (gVHinting == NULL) { AddBBoxHV(false, false); } LogMsg(LOGDEBUG, OK, "results"); LogMsg(LOGDEBUG, OK, gUseV ? "rm" : "ry"); ShowVVals(gVHinting); if (gUseV) { LogMsg(INFO, OK, "Using V counter hints."); } sLst = gVHinting; while (sLst != NULL) { AddVPair(sLst, gUseV ? 'm' : 'y'); sLst = sLst->vNxt; } } static void DoVStems(HintVal* sLst) { while (sLst != NULL) { Fixed lft, rght; bool curved; curved = !FindLineSeg(sLst->vLoc1, leftList) && !FindLineSeg(sLst->vLoc2, rightList); lft = sLst->vLoc1; rght = sLst->vLoc2; if (lft > rght) { Fixed tmp = lft; lft = rght; rght = tmp; } AddVStem(rght, lft, curved); sLst = sLst->vNxt; } } static void RemoveRedundantFirstHints(void) { PathElt* e; if (gNumPtLsts < 2 || !SameHints(0, 1)) { return; } e = gPathStart; while (e != NULL) { if (e->newhints == 1) { e->newhints = 0; return; } e = e->next; } } static void AddHintsSetup(void) { int i; gVBigDist = 0; for (i = 0; i < gNumVStems; i++) { if (gVStems[i] > gVBigDist) { gVBigDist = gVStems[i]; } } if (gVBigDist < gInitBigDist) { gVBigDist = gInitBigDist; } gVBigDist = (gVBigDist * 23) / 20; acfixtopflt(gVBigDist, &gVBigDistR); gHBigDist = 0; for (i = 0; i < gNumHStems; i++) { if (gHStems[i] > gHBigDist) { gHBigDist = gHStems[i]; } } gHBigDist = abs(gHBigDist); if (gHBigDist < gInitBigDist) { gHBigDist = gInitBigDist; } gHBigDist = (gHBigDist * 23) / 20; acfixtopflt(gHBigDist, &gHBigDistR); if (gRoundToInt) { RoundPathCoords(); } CheckForMultiMoveTo(); /* PreCheckForSolEol(); */ } /* If extrahint is true then it is ok to have multi-level hinting. */ static void AddHintsInnerLoop(const char* srcglyph, bool extrahint) { int32_t retryHinting = 0; unsigned char* links; while (true) { PreGenPts(); CheckSmooth(); links = InitShuffleSubpaths(); Blues(links); if (!gDoAligns) { Yellows(links); } if (gEditGlyph) { DoShuffleSubpaths(links); } gHPrimary = CopyHints(gHHinting); gVPrimary = CopyHints(gVHinting); PruneElementHintSegs(); ListHintInfo(); if (extrahint) { AutoExtraHints(MoveToNewHints()); } gPtLstArray[gPtLstIndex] = gPointList; retryHinting++; /* we want to retry hinting if `1) CounterFailed or 2) doFixes changed something, but in both cases, only on the first pass. */ if (CounterFailed && retryHinting == 1) { goto retry; } if (retryHinting > 1) { break; } retry: /* if we are doing the stem and zones reporting, we need to discard the * reported. */ if (gReportRetryCB != NULL) { gReportRetryCB(gReportRetryUserData); } if (gPathStart == NULL || gPathStart == gPathEnd) { LogMsg(LOGERROR, NONFATALERROR, "No glyph path."); } /* SaveFile(); SaveFile is always called in AddHintsCleanup, so this is * a duplciate */ InitAll(RESTART); if (gWriteHintedBez && !ReadGlyph(srcglyph, false, false)) { break; } AddHintsSetup(); if (!PreCheckForHinting()) { break; } if (gFlexOK) { gHasFlex = false; AutoAddFlex(); } } } static void AddHintsCleanup(void) { RemoveRedundantFirstHints(); if (gWriteHintedBez) { if (gPathStart == NULL || gPathStart == gPathEnd) { LogMsg(LOGERROR, NONFATALERROR, "The glyph path vanished while adding hints."); } else { SaveFile(); } } InitAll(RESTART); } static void AddHints(const char* srcglyph, bool extrahint) { if (gPathStart == NULL || gPathStart == gPathEnd) { LogMsg(INFO, OK, "No glyph path, so no hints."); SaveFile(); /* make sure it gets saved with no hinting */ return; } CounterFailed = gBandError = false; CheckPathBBox(); CheckForDups(); AddHintsSetup(); if (!PreCheckForHinting()) { return; } if (gFlexOK) { gHasFlex = false; AutoAddFlex(); } AddHintsInnerLoop(srcglyph, extrahint); AddHintsCleanup(); } bool AutoHintGlyph(const char* srcglyph, bool extrahint) { int32_t lentop = gLenTopBands, lenbot = gLenBotBands; if (!ReadGlyph(srcglyph, false, false)) { LogMsg(LOGERROR, NONFATALERROR, "Cannot parse glyph."); } AddHints(srcglyph, extrahint); gLenTopBands = lentop; gLenBotBands = lenbot; return true; } psautohint-2.3.0/libpsautohint/src/eval.c000066400000000000000000000410371401523215600204650ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "bbox.h" #define MAXF (1 << 15) static void AdjustVal(Fixed* pv, Fixed l1, Fixed l2, Fixed dist, Fixed d, bool hFlg) { float v, q, r1, r2, rd; /* DEBUG 8 BIT. To get the saem result as the old auothint, had to change from FixedOne to FixedTwo. Since the returned weight is proportional to the square of l1 and l2, these need to be clamped to twice the old clamped value, else when the clamped values are used, the weight comes out as 1/4 of the original value. */ if (dist < FixTwo) dist = FixTwo; if (l1 < FixTwo) l1 = FixTwo; if (l2 < FixTwo) l2 = FixTwo; if (abs(l1) < MAXF) r1 = (float)(l1 * l1); else { r1 = (float)l1; r1 = r1 * r1; } if (abs(l2) < MAXF) r2 = (float)(l2 * l2); else { r2 = (float)l2; r2 = r2 * r2; } if (abs(dist) < MAXF) q = (float)(dist * dist); else { q = (float)dist; q = q * q; } v = (float)((1000.0f * r1 * r2) / (q * q)); if (d <= (hFlg ? gHBigDist : gVBigDist)) goto done; acfixtopflt(d, &rd); q = (hFlg ? gHBigDistR : gVBigDistR) / rd; /* 0 < q < 1.0 */ if (q <= 0.5f) { v = 0.0; goto done; } q *= q; q *= q; q *= q; /* raise q to 8th power */ v = v * q; /* if d is twice bigDist, value goes down by factor of 256 */ done: if (v > gMaxVal) v = gMaxVal; else if (v > 0.0f && v < gMinVal) v = gMinVal; *pv = acpflttofix(&v); } static Fixed CalcOverlapDist(Fixed d, Fixed overlaplen, Fixed minlen) { float r = (float)d, ro = (float)overlaplen, rm = (float)minlen; r = r * ((float)(1.0f + 0.4f * (1.0f - ro / rm))); d = (Fixed)r; return d; } #define GapDist(d) \ (((d) < FixInt(127)) ? FTrunc(((d) * (d)) / 40) \ : ((int32_t)(((double)(d)) * (d) / (40 * 256)))) /* if d is >= 127.0 Fixed, then d*d will overflow the signed int 16 bit value. */ /* DEBUG 8 BIT. No idea why d*d was divided by 20, but we need to divide it by 2 more to get a dist that is only 2* the old autohint value. With the 8.8 fixed coordinate system, we still overflow a int32_t with d*(d/40), so rather than casting this to a int32_t and then doing >>8, we need to divide by 256, then cast to int32_t. I also fail to understand why the original used FTrunc, which right shifts by 256. For the current coordinate space, which has a fractional part of 8 bits, you do need to divide by 256 after doing a simple int multiply, but the previous coordinate space has a 7 bit Fixed fraction, and should be dividing by 128. I suspect that there was a yet earlier version which used a 8 bit fraction, and this is a bug. */ static void EvalHPair(HintSeg* botSeg, HintSeg* topSeg, Fixed* pspc, Fixed* pv) { Fixed brght, blft, bloc, tloc, trght, tlft; Fixed mndist, dist, dy; bool inBotBand, inTopBand; *pspc = 0; brght = botSeg->sMax; blft = botSeg->sMin; trght = topSeg->sMax; tlft = topSeg->sMin; bloc = botSeg->sLoc; tloc = topSeg->sLoc; dy = abs(bloc - tloc); if (dy < gMinDist) { *pv = 0; return; } inBotBand = InBlueBand(bloc, gLenBotBands, gBotBands); inTopBand = InBlueBand(tloc, gLenTopBands, gTopBands); if (inBotBand && inTopBand) { /* delete these */ *pv = 0; return; } if (inBotBand || inTopBand) /* up the priority of these */ *pspc = FixInt(2); /* left is always < right */ if ((tlft <= brght) && (trght >= blft)) { /* overlap */ Fixed overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft); Fixed minlen = NUMMIN(trght - tlft, brght - blft); if (minlen == overlaplen) dist = dy; else dist = CalcOverlapDist(dy, overlaplen, minlen); } else { /* no overlap; take closer ends */ Fixed dx; Fixed ldst = abs(tlft - brght); Fixed rdst = abs(trght - blft); dx = NUMMIN(ldst, rdst); dist = GapDist(dx); /* extra penalty for nonoverlap changed from 7/5 to 12/5 for * Perpetua/Regular/ n, r ,m and other lowercase serifs; undid change * for Berthold/AkzidenzGrotesk 9/16/91; this did not make Perpetua any * worse. */ dist += (7 * dy) / 5; DEBUG_ROUND(dist) /* DEBUG 8 BIT */ if (dx > dy) dist *= dx / dy; } mndist = FixTwoMul(gMinDist); dist = NUMMAX(dist, mndist); if (gNumHStems > 0) { int i; Fixed w = abs(dy); for (i = 0; i < gNumHStems; i++) if (w == gHStems[i]) { *pspc += FixOne; break; } } AdjustVal(pv, brght - blft, trght - tlft, dist, dy, true); } static void HStemMiss(HintSeg* botSeg, HintSeg* topSeg) { Fixed brght, blft, bloc, tloc, trght, tlft; Fixed mndist, dist, dy; Fixed b, t, minDiff, minW, w; int i; if (gNumHStems == 0) return; brght = botSeg->sMax; blft = botSeg->sMin; trght = topSeg->sMax; tlft = topSeg->sMin; bloc = botSeg->sLoc; tloc = topSeg->sLoc; dy = abs(bloc - tloc); if (dy < gMinDist) return; /* left is always < right */ if ((tlft <= brght) && (trght >= blft)) { /* overlap */ Fixed overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft); Fixed minlen = NUMMIN(trght - tlft, brght - blft); if (minlen == overlaplen) dist = dy; else dist = CalcOverlapDist(dy, overlaplen, minlen); } else return; mndist = FixTwoMul(gMinDist); if (dist < mndist) return; minDiff = FixInt(1000); minW = 0; b = -bloc; t = -tloc; w = t - b; /* don't check ghost bands for near misses */ if (((w = t - b) == botGhst) || (w == topGhst)) return; w = abs(w); for (i = 0; i < gNumHStems; i++) { Fixed sw = gHStems[i]; Fixed diff = abs(sw - w); if (diff == 0) return; if (diff < minDiff) { minDiff = diff; minW = sw; } } if (minDiff > FixInt(2)) return; ReportStemNearMiss(false, w, minW, b, t, (botSeg->sType == sCURVE) || (topSeg->sType == sCURVE)); } static void EvalVPair(HintSeg* leftSeg, HintSeg* rightSeg, Fixed* pspc, Fixed* pv) { Fixed ltop, lbot, lloc, rloc, rtop, rbot; Fixed mndist, dx, dist; Fixed bonus, lbonus, rbonus; *pspc = 0; ltop = leftSeg->sMax; lbot = leftSeg->sMin; rtop = rightSeg->sMax; rbot = rightSeg->sMin; lloc = leftSeg->sLoc; rloc = rightSeg->sLoc; dx = abs(lloc - rloc); if (dx < gMinDist) { *pv = 0; return; } /* top is always > bot, independent of YgoesUp */ if ((ltop >= rbot) && (lbot <= rtop)) { /* overlap */ Fixed overlaplen = NUMMIN(ltop, rtop) - NUMMAX(lbot, rbot); Fixed minlen = NUMMIN(ltop - lbot, rtop - rbot); if (minlen == overlaplen) dist = dx; else dist = CalcOverlapDist(dx, overlaplen, minlen); } else { /* no overlap; take closer ends */ Fixed tdst = abs(ltop - rbot); Fixed bdst = abs(lbot - rtop); Fixed dy = NUMMIN(tdst, bdst); dist = (7 * dx) / 5 + GapDist(dy); /* extra penalty for nonoverlap */ DEBUG_ROUND(dist) /* DEBUG 8 BIT */ if (dy > dx) dist *= dy / dx; } mndist = FixTwoMul(gMinDist); dist = NUMMAX(dist, mndist); lbonus = leftSeg->sBonus; rbonus = rightSeg->sBonus; bonus = NUMMIN(lbonus, rbonus); *pspc = (bonus > 0) ? FixInt(2) : 0; /* this is for sol-eol characters */ if (gNumVStems > 0) { int i; Fixed w = abs(dx); for (i = 0; i < gNumVStems; i++) if (w == gVStems[i]) { *pspc = *pspc + FixOne; break; } } AdjustVal(pv, ltop - lbot, rtop - rbot, dist, dx, false); } static void VStemMiss(HintSeg* leftSeg, HintSeg* rightSeg) { Fixed ltop, lbot, lloc, rloc, rtop, rbot; Fixed dx, l, r, minDiff, minW, w; int i; if (gNumVStems == 0) return; ltop = leftSeg->sMax; lbot = leftSeg->sMin; rtop = rightSeg->sMax; rbot = rightSeg->sMin; lloc = leftSeg->sLoc; rloc = rightSeg->sLoc; dx = abs(lloc - rloc); if (dx < gMinDist) return; /* top is always > bot, independent of YgoesUp */ if ((ltop < rbot) || (lbot > rtop)) /* does not overlap */ return; l = lloc; r = rloc; w = abs(r - l); minDiff = FixInt(1000); minW = 0; for (i = 0; i < gNumVStems; i++) { Fixed sw = gVStems[i]; Fixed diff = abs(sw - w); if (diff < minDiff) { minDiff = diff; minW = sw; } if (minDiff == 0) return; } if (minDiff > FixInt(2)) return; ReportStemNearMiss(true, w, minW, l, r, (leftSeg->sType == sCURVE) || (rightSeg->sType == sCURVE)); } static void InsertVValue(Fixed lft, Fixed rght, Fixed val, Fixed spc, HintSeg* lSeg, HintSeg* rSeg) { HintVal *item, *vlist, *vprev; item = (HintVal*)Alloc(sizeof(HintVal)); item->vVal = val; item->initVal = val; item->vLoc1 = lft; item->vLoc2 = rght; item->vSpc = spc; item->vSeg1 = lSeg; item->vSeg2 = rSeg; item->vGhst = false; vlist = gValList; vprev = NULL; while (vlist != NULL) { if (vlist->vLoc1 >= lft) break; vprev = vlist; vlist = vlist->vNxt; } while (vlist != NULL && vlist->vLoc1 == lft) { if (vlist->vLoc2 >= rght) break; vprev = vlist; vlist = vlist->vNxt; } if (vprev == NULL) gValList = item; else vprev->vNxt = item; item->vNxt = vlist; ReportAddVVal(item); } #define LePruneValue(val) ((val) < FixOne && ((val) << 10) <= gPruneValue) static void AddVValue(Fixed lft, Fixed rght, Fixed val, Fixed spc, HintSeg* lSeg, HintSeg* rSeg) { if (val == 0) return; if (LePruneValue(val) && spc <= 0) return; if (lSeg != NULL && lSeg->sType == sBEND && rSeg != NULL && rSeg->sType == sBEND) return; if (val <= gPruneD && spc <= 0 && lSeg != NULL && rSeg != NULL) { if (lSeg->sType == sBEND || rSeg->sType == sBEND || !CheckBBoxes(lSeg->sElt, rSeg->sElt)) return; } if (rSeg == NULL) return; InsertVValue(lft, rght, val, spc, lSeg, rSeg); } static void InsertHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, HintSeg* bSeg, HintSeg* tSeg, bool ghst) { HintVal *item, *vlist, *vprev, *vl; vlist = gValList; vprev = NULL; while (vlist != NULL) { if (vlist->vLoc2 >= top) break; vprev = vlist; vlist = vlist->vNxt; } while (vlist != NULL && vlist->vLoc2 == top) { if (vlist->vLoc1 >= bot) break; vprev = vlist; vlist = vlist->vNxt; } /* prune ghost pair that is same as non ghost pair for same segment only if val for ghost is less than an existing val with same top and bottom segment (vl) */ vl = vlist; while (ghst && vl != NULL && vl->vLoc2 == top && vl->vLoc1 == bot) { if (!vl->vGhst && (vl->vSeg1 == bSeg || vl->vSeg2 == tSeg) && vl->vVal > val) return; vl = vl->vNxt; } item = (HintVal*)Alloc(sizeof(HintVal)); item->vVal = val; item->initVal = val; item->vSpc = spc; item->vLoc1 = bot; item->vLoc2 = top; item->vSeg1 = bSeg; item->vSeg2 = tSeg; item->vGhst = ghst; if (vprev == NULL) gValList = item; else vprev->vNxt = item; item->vNxt = vlist; ReportAddHVal(item); } static void AddHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, HintSeg* bSeg, HintSeg* tSeg) { bool ghst; if (val == 0) return; if (LePruneValue(val) && spc <= 0) return; if (bSeg->sType == sBEND && tSeg->sType == sBEND) return; ghst = bSeg->sType == sGHOST || tSeg->sType == sGHOST; if (!ghst && val <= gPruneD && spc <= 0) { if (bSeg->sType == sBEND || tSeg->sType == sBEND || !CheckBBoxes(bSeg->sElt, tSeg->sElt)) return; } InsertHValue(bot, top, val, spc, bSeg, tSeg, ghst); } static float mfabs(float in) { if (in > 0) return in; return -in; } static Fixed CombVals(Fixed v1, Fixed v2) { int32_t i; float r1, r2; float x, a, xx = 0; acfixtopflt(v1, &r1); acfixtopflt(v2, &r2); /* home brew sqrt */ a = r1 * r2; x = a; for (i = 0; i < 16; i++) { xx = ((float)0.5) * (x + a / x); if (i >= 8 && mfabs(xx - x) <= mfabs(xx) * 0.0000001f) break; x = xx; } r1 += r2 + ((float)2.0) * xx; if (r1 > gMaxVal) r1 = gMaxVal; else if (r1 > 0 && r1 < gMinVal) r1 = gMinVal; return acpflttofix(&r1); } static void CombineValues(void) { /* works for both H and V */ HintVal* vlist = gValList; while (vlist != NULL) { HintVal* v1 = vlist->vNxt; Fixed loc1 = vlist->vLoc1; Fixed loc2 = vlist->vLoc2; Fixed val = vlist->vVal; bool match = false; while (v1 != NULL && v1->vLoc1 == loc1 && v1->vLoc2 == loc2) { if (v1->vGhst) val = v1->vVal; else val = CombVals(val, v1->vVal); /* increase value to compensate for length squared effect */ match = true; v1 = v1->vNxt; } if (match) { while (vlist != v1) { vlist->vVal = val; vlist = vlist->vNxt; } } else vlist = v1; } } void EvalV(void) { HintSeg *lList, *rList; Fixed lft, rght; Fixed val, spc; gValList = NULL; lList = leftList; while (lList != NULL) { rList = rightList; while (rList != NULL) { lft = lList->sLoc; rght = rList->sLoc; if (lft < rght) { EvalVPair(lList, rList, &spc, &val); VStemMiss(lList, rList); AddVValue(lft, rght, val, spc, lList, rList); } rList = rList->sNxt; } lList = lList->sNxt; } CombineValues(); } void EvalH(void) { HintSeg *bList, *tList, *lst, *ghostSeg; Fixed lstLoc, tempLoc, cntr; Fixed val, spc; gValList = NULL; bList = botList; while (bList != NULL) { tList = topList; while (tList != NULL) { Fixed bot, top; bot = bList->sLoc; top = tList->sLoc; if (bot > top) { EvalHPair(bList, tList, &spc, &val); HStemMiss(bList, tList); AddHValue(bot, top, val, spc, bList, tList); } tList = tList->sNxt; } bList = bList->sNxt; } ghostSeg = (HintSeg*)Alloc(sizeof(HintSeg)); ghostSeg->sType = sGHOST; ghostSeg->sElt = NULL; if (gLenBotBands < 2 && gLenTopBands < 2) goto done; lst = botList; while (lst != NULL) { lstLoc = lst->sLoc; if (InBlueBand(lstLoc, gLenBotBands, gBotBands)) { tempLoc = lstLoc - gGhostWidth; ghostSeg->sLoc = tempLoc; cntr = (lst->sMax + lst->sMin) / 2; ghostSeg->sMax = cntr + gGhostLength / 2; ghostSeg->sMin = cntr - gGhostLength / 2; DEBUG_ROUND(ghostSeg->sMax) /* DEBUG 8 BIT */ DEBUG_ROUND(ghostSeg->sMin) /* DEBUG 8 BIT */ spc = FixInt(2); val = FixInt(20); AddHValue(lstLoc, tempLoc, val, spc, lst, ghostSeg); } lst = lst->sNxt; } lst = topList; while (lst != NULL) { lstLoc = lst->sLoc; if (InBlueBand(lstLoc, gLenTopBands, gTopBands)) { tempLoc = lstLoc + gGhostWidth; ghostSeg->sLoc = tempLoc; cntr = (lst->sMin + lst->sMax) / 2; ghostSeg->sMax = cntr + gGhostLength / 2; ghostSeg->sMin = cntr - gGhostLength / 2; spc = FixInt(2); val = FixInt(20); AddHValue(tempLoc, lstLoc, val, spc, ghostSeg, lst); } lst = lst->sNxt; } done: CombineValues(); } psautohint-2.3.0/libpsautohint/src/fix.c000066400000000000000000000072361401523215600203270ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" static Fixed bPrev, tPrev; void InitFix(int32_t reason) { switch (reason) { case STARTUP: case RESTART: bPrev = tPrev = FIXED_MAX; } } static bool CheckForInsideBands(Fixed loc, Fixed* blues, int32_t numblues) { int32_t i; for (i = 0; i < numblues; i += 2) { if (loc >= blues[i] && loc <= blues[i + 1]) return true; } return false; } #define bFuzz (FixInt(6)) static void CheckForNearBands(Fixed loc, Fixed* blues, int32_t numblues) { int32_t i; bool bottom = true; for (i = 0; i < numblues; i++) { if ((bottom && loc >= blues[i] - bFuzz && loc < blues[i]) || (!bottom && loc <= blues[i] + bFuzz && loc > blues[i])) { LogMsg( INFO, OK, "Near miss %s horizontal zone at %g instead of %g.", bottom ? "below" : "above", FixToDbl(loc), FixToDbl(blues[i])); } bottom = !bottom; } } bool FindLineSeg(Fixed loc, HintSeg* sL) { while (sL != NULL) { if (sL->sLoc == loc && sL->sType == sLINE) return true; sL = sL->sNxt; } return false; } #if 1 /* Traverses hSegList to check for near misses to the horizontal alignment zones. The list contains segments that may or may not have hints added. */ void CheckTfmVal(HintSeg* hSegList, Fixed* bandList, int32_t length) { HintSeg* sList = hSegList; while (sList != NULL) { Fixed tfmval = -sList->sLoc; if ((length >= 2) && !gBandError && !CheckForInsideBands(tfmval, bandList, length)) CheckForNearBands(tfmval, bandList, length); sList = sList->sNxt; } } #else void CheckTfmVal(Fixed b, Fixed t, bool vert) { if (t < b) { Fixed tmp; tmp = t; t = b; b = tmp; } if (!vert && (lenTopBands >= 2 || lenBotBands >= 2) && !bandError && !CheckForInsideBands(t, topBands, lenTopBands) && !CheckForInsideBands(b, botBands, lenBotBands)) { CheckForNearBands(t, topBands, lenTopBands); CheckForNearBands(b, botBands, lenBotBands); } } #endif static void CheckVal(HintVal* val, bool vert) { Fixed* stems; int32_t numstems, i; Fixed minDiff, minW, b, t, w; if (vert) { stems = gVStems; numstems = gNumVStems; b = val->vLoc1; t = val->vLoc2; } else { stems = gHStems; numstems = gNumHStems; b = -val->vLoc1; t = -val->vLoc2; } w = abs(t - b); minDiff = FixInt(1000); minW = 0; for (i = 0; i < numstems; i++) { Fixed wd = stems[i]; Fixed diff = abs(wd - w); if (diff < minDiff) { minDiff = diff; minW = wd; if (minDiff == 0) break; } } if (minDiff == 0 || minDiff > FixInt(2)) return; if (b != bPrev || t != tPrev) { bool curve = false; if ((vert && (!FindLineSeg(val->vLoc1, leftList) || !FindLineSeg(val->vLoc2, rightList))) || (!vert && (!FindLineSeg(val->vLoc1, botList) || !FindLineSeg(val->vLoc2, topList)))) curve = true; if (!val->vGhst) ReportStemNearMiss(vert, w, minW, b, t, curve); } bPrev = b; tPrev = t; } void CheckVals(HintVal* vlst, bool vert) { while (vlst != NULL) { CheckVal(vlst, vert); vlst = vlst->vNxt; } } psautohint-2.3.0/libpsautohint/src/flat.c000066400000000000000000000263711401523215600204700ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" static void FMiniFltn(Cd f0, Cd f1, Cd f2, Cd f3, FltnRec* pfr) { /* Like FFltnCurve, but assumes abs(deltas) <= 127 pixels */ /* 8 bits of fraction gives enough precision for splitting curves */ #define MiniFltnMaxDepth (6) #define inrect (p[-10]) #define inbbox (p[-9]) #define c0x (p[-8]) #define c0y (p[-7]) #define c1x (p[-6]) #define c1y (p[-5]) #define c2x (p[-4]) #define c2y (p[-3]) #define c3x (p[-2]) #define c3y (p[-1]) #define inrect2 (p[0]) #define bbox2 (p[1]) #define d0x (p[2]) #define d0y (p[3]) #define d1x (p[4]) #define d1y (p[5]) #define d2x (p[6]) #define d2y (p[7]) #define d3x (p[8]) #define d3y (p[9]) #define MiniBlkSz (10) #define mdpt(a, b) (((a) + (b)) >> 1) Fixed cds[MiniBlkSz * MiniFltnMaxDepth], dpth, eps; Fixed bbLLX = 0, bbLLY = 0, bbURX = 0, bbURY = 0; Fixed* p; p = cds; dpth = 1; *(p++) = true; /* inrect2 starts out true */ *(p++) = false; /* inbbox2 starts out false */ /* shift coordinates so that lower left of BBox is at (0,0)*/ /* This fills the first MiniBlkSz series of ints with the start point, control point, end end point (x,y) values for the curve, minus the lower left (x,y) for the curve. One each pass, it splits the curve in two, replacing the current MiniBlkSz series of ints with the first of the two split curves, and the next MiniBlkSz series of ints with the second of the curves. It then sets the pointer p so that the second MiniBlkSz series of ints becomes the current set, and iterates, thereby splitting the second curve in two parts. This continues until the control points get very close to the start point, or we reach the limit of MiniFltnMaxDepth iterations. At that time, the PathBBox update function is called with the end point of the first of the most recently split curves. Once the the current set of points meets the test that one of the control points is very close to the start point, then the algoithm iteratively steps back to the previous set. If thsi does not meet the test, the algorith iterates forward again */ /*DEBUG 8 BIT. AFter chaning the fractinoal part to 8 bits form 7 bits, had * to change all the int16_t values to int32_t in order to not overflow math * operations. The only reason these were all shorts was speed and memory * issues in 1986. */ { Fixed llx, lly; llx = pfr->llx; lly = pfr->lly; *(p++) = f0.x - llx; *(p++) = f0.y - lly; *(p++) = f1.x - llx; *(p++) = f1.y - lly; *(p++) = f2.x - llx; *(p++) = f2.y - lly; *(p++) = f3.x - llx; *(p++) = f3.y - lly; } if (!inrect) { Fixed c, f128; c = pfr->ll.x; bbLLX = (c <= 0) ? 0 : c; c = pfr->ll.y; bbLLY = (c <= 0) ? 0 : c; f128 = FixInt(128); c = pfr->ur.x; bbURX = (c >= f128) ? 0x7fff : c; c = pfr->ur.y; bbURY = (c >= f128) ? 0x7fff : c; } eps = pfr->feps; if (eps < 16) /* DEBUG 8 BIT FIX */ eps = 16; /* Brotz patch */ while (true) { /* Iterate until curve has been flattened into MiniFltnMaxDepth segments */ if (dpth == MiniFltnMaxDepth) goto ReportC3; if (!inrect) { Fixed llx, lly, urx, ury, c; llx = urx = c0x; if ((c = c1x) < llx) llx = c; else if (c > urx) urx = c; if ((c = c2x) < llx) llx = c; else if (c > urx) urx = c; if ((c = c3x) < llx) llx = c; else if (c > urx) urx = c; if (urx < bbLLX || llx > bbURX) goto ReportC3; lly = ury = c0y; if ((c = c1y) < lly) lly = c; else if (c > ury) ury = c; if ((c = c2y) < lly) lly = c; else if (c > ury) ury = c; if ((c = c3y) < lly) lly = c; else if (c > ury) ury = c; if (ury < bbLLY || lly > bbURY) goto ReportC3; if (urx <= bbURX && ury <= bbURY && llx >= bbLLX && lly >= bbLLY) inrect = true; } if (!inbbox) { int32_t mrgn = eps, r0, r3, ll, ur, c; r0 = c0x; r3 = c3x; if (r0 < r3) { ll = r0 - mrgn; ur = r3 + mrgn; } else { ll = r3 - mrgn; ur = r0 + mrgn; } if (ur < 0) ur = FixInt(128) - 1; c = c1x; if (c > ll && c < ur) { c = c2x; if (c > ll && c < ur) { r0 = c0y; r3 = c3y; if (r0 < r3) { ll = r0 - mrgn; ur = r3 + mrgn; } else { ll = r3 - mrgn; ur = r0 + mrgn; } if (ur < 0) ur = FixInt(128) - 1; c = c1y; if (c > ll && c < ur) { c = c2y; if (c > ll && c < ur) inbbox = true; } } } } if (inbbox) { Fixed eqa, eqb, x, y; Fixed EPS; /* int64_t instead of Fixed to avoid integer overflow below */ int64_t d; x = c0x; y = c0y; eqa = c3y - y; eqb = x - c3x; if (eqa == 0 && eqb == 0) goto ReportC3; EPS = ((abs(eqa) > abs(eqb)) ? eqa : eqb) * eps; if (EPS < 0) EPS = -EPS; /* The casts are needed to prevent integer overflow */ d = (int64_t)eqa * (c1x - x); d += (int64_t)eqb * (c1y - y); if (labs(d) < EPS) { d = (int64_t)eqa * (c2x - x); d += (int64_t)eqb * (c2y - y); if (labs(d) < EPS) goto ReportC3; } } { /* Bezier divide */ Fixed c0, c1, c2, d1, d2, d3; d0x = c0 = c0x; c1 = c1x; c2 = c2x; d1x = d1 = mdpt(c0, c1); d3 = mdpt(c1, c2); d2x = d2 = mdpt(d1, d3); c2x = c2 = mdpt(c2, c3x); c1x = c1 = mdpt(d3, c2); c0x = d3x = mdpt(d2, c1); d0y = c0 = c0y; c1 = c1y; c2 = c2y; d1y = d1 = mdpt(c0, c1); d3 = mdpt(c1, c2); d2y = d2 = mdpt(d1, d3); c2y = c2 = mdpt(c2, c3y); c1y = c1 = mdpt(d3, c2); c0y = d3y = mdpt(d2, c1); bbox2 = inbbox; inrect2 = inrect; p += MiniBlkSz; dpth++; continue; } ReportC3 : { Cd c; if (--dpth == 0) c = f3; else { c.x = c3x + pfr->llx; c.y = c3y + pfr->lly; } (*pfr->report)(c); /* call FPBBoxPt() to reset bbox. */ if (dpth == 0) return; p -= MiniBlkSz; } } } /* end of FMiniFltn */ #undef MiniFltnMaxDepth #undef inrect #undef inbbox #undef c0x #undef c0y #undef c1x #undef c1y #undef c2x #undef c2y #undef c3x #undef c3y #undef inrect2 #undef bbox2 #undef d0x #undef d0y #undef d1x #undef d1y #undef d2x #undef d2y #undef d3x #undef d3y #undef MiniBlkSz #undef mdpt #define FixedMidPoint(m, a, b) \ (m).x = ((a).x + (b).x) >> 1; \ (m).y = ((a).y + (b).y) >> 1 #define FixedBezDiv(a0, a1, a2, a3, b0, b1, b2, b3) \ b3 = a3; \ FixedMidPoint(b2, a2, a3); \ FixedMidPoint(a3, a1, a2); \ FixedMidPoint(a1, a0, a1); \ FixedMidPoint(a2, a1, a3); \ FixedMidPoint(b1, a3, b2); \ FixedMidPoint(b0, a2, b1); \ a3 = b0 /* inrect = !testRect */ /* Like FltnCurve, but works in the Fixed domain. */ /* abs values of coords must be < 2^14 so will not overflow when find midpoint by add and shift */ static void FFltnCurve(Cd c0, Cd c1, Cd c2, Cd c3, FltnRec* pfr) { Cd d0, d1, d2, d3; Fixed llx, lly, urx, ury; if (c0.x == c1.x && c0.y == c1.y && c2.x == c3.x && c2.y == c3.y) goto ReportC3; /* it is a flat curve - do not need to flatten. */ if (pfr->limit <= 0) goto ReportC3; { /* set initial bbox of llx,lly, urx, ury from bez control and end points */ Fixed c; llx = urx = c0.x; if ((c = c1.x) < llx) llx = c; else if (c > urx) urx = c; if ((c = c2.x) < llx) llx = c; else if (c > urx) urx = c; if ((c = c3.x) < llx) llx = c; else if (c > urx) urx = c; lly = ury = c0.y; if ((c = c1.y) < lly) lly = c; else if (c > ury) ury = c; if ((c = c2.y) < lly) lly = c; else if (c > ury) ury = c; if ((c = c3.y) < lly) lly = c; else if (c > ury) ury = c; } { /* if the height or width of the initial bbox is > 256, split it, and this function on the two parts. */ Fixed th; th = FixInt(256); /* DEBUG 8 Bit */ /* delta threshhold of 127 pixels */ /* The reason we split this is that the FMiniFltn function uses and 8.8 Fixed to hold coordindate data - so the max intger part must be no more than 127, to allow for signed values. This made sense when an int was 16 bits, but is no longer an optimization. I still subdivide, so that FMiniFltn, which subdivides into a maximum of 6 curve segments, will be owkring with short segments. */ if (urx - llx >= th || ury - lly >= th) { goto Split; } } pfr->llx = llx; pfr->lly = lly; FMiniFltn(c0, c1, c2, c3, pfr); return; Split: /* Split the bez curve in half */ FixedBezDiv(c0, c1, c2, c3, d0, d1, d2, d3); pfr->limit--; FFltnCurve(c0, c1, c2, c3, pfr); FFltnCurve(d0, d1, d2, d3, pfr); pfr->limit++; return; ReportC3: (*pfr->report)(c3); } void FltnCurve(Cd c0, Cd c1, Cd c2, Cd c3, FltnRec* pfr) { pfr->limit = 6; /* limit on how many times a bez curve can be split in half by recursive calls to FFltnCurve() */ pfr->feps = FixOne; /* DEBUG 8 BIT FIX */ FFltnCurve(c0, c1, c2, c3, pfr); } psautohint-2.3.0/libpsautohint/src/fontinfo.c000066400000000000000000000351231401523215600213570ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "fontinfo.h" #include "ac.h" #define UNDEFINED (INT32_MAX) int32_t gNumHHints, gNumVHints; static void ParseIntStems(const ACFontInfo* fontinfo, char* kw, bool optional, int32_t maxstems, int* stems, int32_t* pnum); static char* GetFontInfo(const ACFontInfo*, char*, bool); static void ParseStems(const ACFontInfo* fontinfo, char* kw, Fixed* stems, int32_t* pnum) { int istems[MAXSTEMS], i; memset(istems, 0, MAXSTEMS * sizeof(int)); ParseIntStems(fontinfo, kw, OPTIONAL, MAXSTEMS, istems, pnum); for (i = 0; i < *pnum; i++) stems[i] = FixInt(istems[i]); } static void GetKeyValue(const ACFontInfo* fontinfo, char* keyword, bool optional, int32_t* value) { char* fontinfostr = GetFontInfo(fontinfo, keyword, optional); if (strlen(fontinfostr) != 0) { *value = (int32_t)atol(fontinfostr); } return; } static void GetKeyFixedValue(const ACFontInfo* fontinfo, char* keyword, bool optional, Fixed* value) { char* fontinfostr = GetFontInfo(fontinfo, keyword, optional); if (strlen(fontinfostr) != 0) *value = FixInt(strtod(fontinfostr, NULL)); } bool ReadFontInfo(const ACFontInfo* fontinfo) { char* fontinfostr; int32_t AscenderHeight, AscenderOvershoot, BaselineYCoord, BaselineOvershoot, Baseline5, Baseline5Overshoot, Baseline6, Baseline6Overshoot, CapHeight, CapOvershoot, DescenderHeight, DescenderOvershoot, FigHeight, FigOvershoot, Height5, Height5Overshoot, Height6, Height6Overshoot, LcHeight, LcOvershoot, OrdinalBaseline, OrdinalOvershoot, SuperiorBaseline, SuperiorOvershoot; bool ORDINARYHINTING = gWriteHintedBez; AscenderHeight = AscenderOvershoot = BaselineYCoord = BaselineOvershoot = Baseline5 = Baseline5Overshoot = Baseline6 = Baseline6Overshoot = CapHeight = CapOvershoot = DescenderHeight = DescenderOvershoot = FigHeight = FigOvershoot = Height5 = Height5Overshoot = Height6 = Height6Overshoot = LcHeight = LcOvershoot = OrdinalBaseline = OrdinalOvershoot = SuperiorBaseline = SuperiorOvershoot = UNDEFINED; /* mark as undefined */ gNumHStems = gNumVStems = 0; gNumHHints = gNumVHints = 0; gLenBotBands = gLenTopBands = 0; /* check for FlexOK, AuxHStems, AuxVStems */ /* for intelligent scaling, it's too hard to check these */ ParseStems(fontinfo, "StemSnapH", gHStems, &gNumHStems); ParseStems(fontinfo, "StemSnapV", gVStems, &gNumVStems); if (gNumHStems == 0) { ParseStems(fontinfo, "DominantH", gHStems, &gNumHStems); ParseStems(fontinfo, "DominantV", gVStems, &gNumVStems); } fontinfostr = GetFontInfo(fontinfo, "FlexOK", !ORDINARYHINTING); gFlexOK = strcmp(fontinfostr, "false"); fontinfostr = GetFontInfo(fontinfo, "FlexStrict", true); gFlexStrict = strcmp(fontinfostr, "false"); /* get bluefuzz. It is already set to its default value in ac.c::InitData(). GetKeyFixedValue does not change the value if it's not present in fontinfo. */ GetKeyFixedValue(fontinfo, "BlueFuzz", OPTIONAL, &gBlueFuzz); /* Check for counter hinting glyphs. */ fontinfostr = GetFontInfo(fontinfo, "VCounterChars", OPTIONAL); gNumVHints = AddCounterHintGlyphs(fontinfostr, gVHintList); fontinfostr = GetFontInfo(fontinfo, "HCounterChars", OPTIONAL); gNumHHints = AddCounterHintGlyphs(fontinfostr, gHHintList); GetKeyValue(fontinfo, "AscenderHeight", OPTIONAL, &AscenderHeight); GetKeyValue(fontinfo, "AscenderOvershoot", OPTIONAL, &AscenderOvershoot); GetKeyValue(fontinfo, "BaselineYCoord", !ORDINARYHINTING, &BaselineYCoord); GetKeyValue(fontinfo, "BaselineOvershoot", !ORDINARYHINTING, &BaselineOvershoot); GetKeyValue(fontinfo, "Baseline5", OPTIONAL, &Baseline5); GetKeyValue(fontinfo, "Baseline5Overshoot", OPTIONAL, &Baseline5Overshoot); GetKeyValue(fontinfo, "Baseline6", OPTIONAL, &Baseline6); GetKeyValue(fontinfo, "Baseline6Overshoot", OPTIONAL, &Baseline6Overshoot); GetKeyValue(fontinfo, "CapHeight", !ORDINARYHINTING, &CapHeight); GetKeyValue(fontinfo, "CapOvershoot", !ORDINARYHINTING, &CapOvershoot); GetKeyValue(fontinfo, "DescenderHeight", OPTIONAL, &DescenderHeight); GetKeyValue(fontinfo, "DescenderOvershoot", OPTIONAL, &DescenderOvershoot); GetKeyValue(fontinfo, "FigHeight", OPTIONAL, &FigHeight); GetKeyValue(fontinfo, "FigOvershoot", OPTIONAL, &FigOvershoot); GetKeyValue(fontinfo, "Height5", OPTIONAL, &Height5); GetKeyValue(fontinfo, "Height5Overshoot", OPTIONAL, &Height5Overshoot); GetKeyValue(fontinfo, "Height6", OPTIONAL, &Height6); GetKeyValue(fontinfo, "Height6Overshoot", OPTIONAL, &Height6Overshoot); GetKeyValue(fontinfo, "LcHeight", OPTIONAL, &LcHeight); GetKeyValue(fontinfo, "LcOvershoot", OPTIONAL, &LcOvershoot); GetKeyValue(fontinfo, "OrdinalBaseline", OPTIONAL, &OrdinalBaseline); GetKeyValue(fontinfo, "OrdinalOvershoot", OPTIONAL, &OrdinalOvershoot); GetKeyValue(fontinfo, "SuperiorBaseline", OPTIONAL, &SuperiorBaseline); GetKeyValue(fontinfo, "SuperiorOvershoot", OPTIONAL, &SuperiorOvershoot); gLenBotBands = gLenTopBands = 0; if (BaselineYCoord != UNDEFINED && BaselineOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(BaselineYCoord + BaselineOvershoot); gBotBands[gLenBotBands++] = FixInt(BaselineYCoord); } if (Baseline5 != UNDEFINED && Baseline5Overshoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(Baseline5 + Baseline5Overshoot); gBotBands[gLenBotBands++] = FixInt(Baseline5); } if (Baseline6 != UNDEFINED && Baseline6Overshoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(Baseline6 + Baseline6Overshoot); gBotBands[gLenBotBands++] = FixInt(Baseline6); } if (SuperiorBaseline != UNDEFINED && SuperiorOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(SuperiorBaseline + SuperiorOvershoot); gBotBands[gLenBotBands++] = FixInt(SuperiorBaseline); } if (OrdinalBaseline != UNDEFINED && OrdinalOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(OrdinalBaseline + OrdinalOvershoot); gBotBands[gLenBotBands++] = FixInt(OrdinalBaseline); } if (DescenderHeight != UNDEFINED && DescenderOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = FixInt(DescenderHeight + DescenderOvershoot); gBotBands[gLenBotBands++] = FixInt(DescenderHeight); } if (CapHeight != UNDEFINED && CapOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(CapHeight); gTopBands[gLenTopBands++] = FixInt(CapHeight + CapOvershoot); } if (LcHeight != UNDEFINED && LcOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(LcHeight); gTopBands[gLenTopBands++] = FixInt(LcHeight + LcOvershoot); } if (AscenderHeight != UNDEFINED && AscenderOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(AscenderHeight); gTopBands[gLenTopBands++] = FixInt(AscenderHeight + AscenderOvershoot); } if (FigHeight != UNDEFINED && FigOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(FigHeight); gTopBands[gLenTopBands++] = FixInt(FigHeight + FigOvershoot); } if (Height5 != UNDEFINED && Height5Overshoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(Height5); gTopBands[gLenTopBands++] = FixInt(Height5 + Height5Overshoot); } if (Height6 != UNDEFINED && Height6Overshoot != UNDEFINED) { gTopBands[gLenTopBands++] = FixInt(Height6); gTopBands[gLenTopBands++] = FixInt(Height6 + Height6Overshoot); } return true; } static int misspace(int c) { if (c == ' ' || c == '\n' || c == '\r' || c == '\t') return 1; return 0; } static int misdigit(int c) { return c >= '0' && c <= '9'; } /* Looks up the value of the specified keyword in the fontinfo file. If the * keyword doesn't exist and this is an optional key, returns an empty string. * Otherwise, returns the value string. */ static char* GetFontInfo(const ACFontInfo* fontinfo, char* keyword, bool optional) { size_t i; if (!fontinfo) { LogMsg(LOGERROR, NONFATALERROR, "Fontinfo is NULL"); return ""; } for (i = 0; i < fontinfo->length; i++) { if (fontinfo->keys[i] && !strcmp(fontinfo->keys[i], keyword)) { return fontinfo->values[i]; } } if (!optional) { LogMsg(LOGERROR, NONFATALERROR, "Fontinfo: Couldn't find fontinfo for %s.", keyword); } return ""; } /* * This procedure parses the various fontinfo file stem keywords: * StemSnap{H,V}, Dominant{H,V}. * ParseIntStems guarantees that stem values are unique and in ascending order. */ static void ParseIntStems(const ACFontInfo* fontinfo, char* kw, bool optional, int32_t maxstems, int* stems, int32_t* pnum) { int i, j, count = 0; bool singleint = false; char* initline; char* line; *pnum = 0; initline = GetFontInfo(fontinfo, kw, optional); if (strlen(initline) == 0) return; /* optional keyword not found */ line = initline; if (strchr(line, '[') == NULL) singleint = true; /* A single integer instead of a matrix. */ else line = strchr(line, '[') + 1; /* A matrix, skip past first "[". */ while (*line != ']') { int val; while (misspace(*line)) line++; /* skip past any blanks */ if (sscanf(line, " %d", &val) < 1) break; if (count >= maxstems) { LogMsg(LOGERROR, NONFATALERROR, "Cannot have more than %d values in fontinfo array: %s", (int)maxstems, initline); } if (val < 1) { LogMsg(LOGERROR, NONFATALERROR, "Cannot have a value < 1 in fontinfo file array: %s", line); } stems[count++] = val; if (singleint) break; while (misdigit(*line)) line++; /* skip past the number */ } /* ensure they are in order */ for (i = 0; i < count; i++) { for (j = i + 1; j < count; j++) { if (stems[i] > stems[j]) { int temp = stems[i]; stems[i] = stems[j]; stems[j] = temp; } } } /* ensure they are unique - note: complaint for too many might precede guarantee of uniqueness */ for (i = 0; i < count - 1; i++) { if (stems[i] == stems[i + 1]) { for (j = (i + 2); j < count; j++) stems[j - 1] = stems[j]; count--; } } *pnum = count; } void FreeFontInfo(ACFontInfo* fontinfo) { size_t i; if (!fontinfo) return; if(fontinfo->values) { for (i = 0; i < fontinfo->length; i++) { if (fontinfo->values[i][0]) { UnallocateMem(fontinfo->values[i]); } } UnallocateMem(fontinfo->values); } UnallocateMem(fontinfo); } static char* fontinfo_keys[] = { "OrigEmSqUnits", "FontName", "FlexOK", /* Blue Values */ "BaselineOvershoot", "BaselineYCoord", "CapHeight", "CapOvershoot", "LcHeight", "LcOvershoot", "AscenderHeight", "AscenderOvershoot", "FigHeight", "FigOvershoot", "Height5", "Height5Overshoot", "Height6", "Height6Overshoot", /* Other Values */ "Baseline5Overshoot", "Baseline5", "Baseline6Overshoot", "Baseline6", "SuperiorOvershoot", "SuperiorBaseline", "OrdinalOvershoot", "OrdinalBaseline", "DescenderOvershoot", "DescenderHeight", "DominantV", "StemSnapV", "DominantH", "StemSnapH", "VCounterChars", "HCounterChars", /* later addenda */ "BlueFuzz", NULL }; static ACFontInfo* NewFontInfo(void) { size_t i; ACFontInfo* fontinfo; fontinfo = (ACFontInfo*)AllocateMem(1, sizeof(ACFontInfo), "fontinfo"); fontinfo->length = 0; while (fontinfo_keys[fontinfo->length] != NULL) fontinfo->length++; fontinfo->values = (char**)AllocateMem(fontinfo->length, sizeof(char*), "fontinfo values"); fontinfo->keys = fontinfo_keys; for (i = 0; i < fontinfo->length; i++) fontinfo->values[i] = ""; return fontinfo; } #define skipblanks() \ while (*current == '\t' || *current == '\n' || *current == ' ' || \ *current == '\r') \ current++ #define skipnonblanks() \ while (*current != '\t' && *current != '\n' && *current != ' ' && \ *current != '\r' && *current != '\0') \ current++ #define skipmatrix() \ while (*current != '\0' && *current != ']') \ current++ static void skippsstring(const char** current) { int parencount = 0; do { if (**current == '(') parencount++; else if (**current == ')') parencount--; else if (**current == '\0') return; (*current)++; } while (parencount > 0); } ACFontInfo* ParseFontInfo(const char* data) { const char* current; size_t i; ACFontInfo* info = NewFontInfo(); if (!data) return info; current = data; while (*current) { size_t kwLen; const char *kwstart, *kwend, *tkstart; skipblanks(); kwstart = current; skipnonblanks(); kwend = current; skipblanks(); tkstart = current; if (*tkstart == '(') { skippsstring(¤t); if (*tkstart) current++; } else if (*tkstart == '[') { skipmatrix(); if (*tkstart) current++; } else skipnonblanks(); kwLen = (size_t)(kwend - kwstart); for (i = 0; i < info->length; i++) { size_t matchLen = NUMMAX(kwLen, strlen(info->keys[i])); if (!strncmp(info->keys[i], kwstart, matchLen)) { info->values[i] = AllocateMem(current - tkstart + 1, 1, "fontinfo entry value"); strncpy(info->values[i], tkstart, current - tkstart); info->values[i][current - tkstart] = '\0'; break; } } skipblanks(); } return info; } psautohint-2.3.0/libpsautohint/src/fontinfo.h000066400000000000000000000015651401523215600213670ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* This interface is for C-program callers of the font info lookup * proc. The caller of filookup is responsible for also calling * fiptrfree to get rid of the storage returned. */ #ifndef AC_FONTINFO_H_ #define AC_FONTINFO_H_ #include "ac.h" #include "basic.h" #define OPTIONAL true #define MANDATORY false /* Default value used by PS interpreter and Adobe's fonts to extend the range * of alignment zones. */ #define DEFAULTBLUEFUZZ FixOne ACFontInfo* ParseFontInfo(const char* data); void FreeFontInfo(ACFontInfo* fontinfo); bool ReadFontInfo(const ACFontInfo* fontinfo); #endif /* AC_FONTINFO_H_ */ psautohint-2.3.0/libpsautohint/src/gen.c000066400000000000000000001014161401523215600203050ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" #include "bbox.h" static SegLnkLst *Hlnks, *Vlnks; static int32_t cpFrom, cpTo; void InitGen(int32_t reason) { int32_t i; switch (reason) { case STARTUP: case RESTART: for (i = 0; i < 4; i++) gSegLists[i] = NULL; Hlnks = Vlnks = NULL; } } static void LinkSegment(PathElt* e, bool Hflg, HintSeg* seg) { SegLnk* newlnk; SegLnkLst *newlst, *globlst; newlnk = (SegLnk*)Alloc(sizeof(SegLnk)); newlnk->seg = seg; newlst = (SegLnkLst*)Alloc(sizeof(SegLnkLst)); globlst = (SegLnkLst*)Alloc(sizeof(SegLnkLst)); globlst->lnk = newlnk; newlst->lnk = newlnk; if (Hflg) { newlst->next = e->Hs; e->Hs = newlst; globlst->next = Hlnks; Hlnks = globlst; } else { newlst->next = e->Vs; e->Vs = newlst; globlst->next = Vlnks; Vlnks = globlst; } } static void CopySegmentLink(PathElt* e1, PathElt* e2, bool Hflg) { /* copy reference to first link from e1 to e2 */ SegLnkLst* newlst; newlst = (SegLnkLst*)Alloc(sizeof(SegLnkLst)); if (Hflg) { newlst->lnk = e1->Hs->lnk; newlst->next = e2->Hs; e2->Hs = newlst; } else { newlst->lnk = e1->Vs->lnk; newlst->next = e2->Vs; e2->Vs = newlst; } } static void AddSegment(Fixed from, Fixed to, Fixed loc, int32_t lftLstNm, int32_t rghtLstNm, PathElt* e1, PathElt* e2, bool Hflg, int32_t typ) { HintSeg *seg, *segList, *prevSeg; int32_t segNm; seg = (HintSeg*)Alloc(sizeof(HintSeg)); seg->sLoc = loc; if (from > to) { seg->sMax = from; seg->sMin = to; } else { seg->sMax = to; seg->sMin = from; } seg->sBonus = gBonus; seg->sType = (int16_t)typ; if (e1 != NULL) { if (e1->type == CLOSEPATH) e1 = GetDest(e1); LinkSegment(e1, Hflg, seg); seg->sElt = e1; } if (e2 != NULL) { if (e2->type == CLOSEPATH) e2 = GetDest(e2); if (e1 != NULL) CopySegmentLink(e1, e2, Hflg); if (e1 == NULL || e2 == e1->prev) seg->sElt = e2; } segNm = (from > to) ? lftLstNm : rghtLstNm; segList = gSegLists[segNm]; prevSeg = NULL; while (true) { /* keep list in increasing order by sLoc */ if (segList == NULL) { /* at end of list */ if (prevSeg == NULL) { gSegLists[segNm] = seg; break; } prevSeg->sNxt = seg; break; } if (segList->sLoc >= loc) { /* insert before this one */ if (prevSeg == NULL) gSegLists[segNm] = seg; else prevSeg->sNxt = seg; seg->sNxt = segList; break; } prevSeg = segList; segList = segList->sNxt; } } void AddVSegment(Fixed from, Fixed to, Fixed loc, PathElt* p1, PathElt* p2, int32_t typ, int32_t i) { LogMsg(LOGDEBUG, OK, "add vseg %g %g to %g %g %d", FixToDbl(loc), FixToDbl(-from), FixToDbl(loc), FixToDbl(-to), i); AddSegment(from, to, loc, 1, 0, p1, p2, false, typ); } void AddHSegment(Fixed from, Fixed to, Fixed loc, PathElt* p1, PathElt* p2, int32_t typ, int32_t i) { LogMsg(LOGDEBUG, OK, "add hseg %g %g to %g %g %d", FixToDbl(from), FixToDbl(-loc), FixToDbl(to), FixToDbl(-loc), i); AddSegment(from, to, loc, 2, 3, p1, p2, true, typ); } static Fixed CPFrom(Fixed cp2, Fixed cp3) { Fixed val = 2 * (((cp3 - cp2) * cpFrom) / 200); /*DEBUG 8 BIT: hack to get same rounding as old version */ val += cp2; DEBUG_ROUND(val) return val; /* DEBUG 8 BIT to match results with 7 bit fractions */ } static Fixed CPTo(Fixed cp0, Fixed cp1) { Fixed val = 2 * (((cp1 - cp0) * cpTo) / 200); /*DEBUG 8 BIT: hack to get same rounding as old version */ val += cp0; DEBUG_ROUND(val) return val; /* DEBUG 8 BIT to match results with 7 bit fractions */ } static bool TestBend(Fixed x0, Fixed y0, Fixed x1, Fixed y1, Fixed x2, Fixed y2) { /* return true if bend angle is sharp enough (135 degrees or less) */ float dx1, dy1, dx2, dy2, dotprod, lensqprod; acfixtopflt(x1 - x0, &dx1); acfixtopflt(y1 - y0, &dy1); acfixtopflt(x2 - x1, &dx2); acfixtopflt(y2 - y1, &dy2); dotprod = dx1 * dx2 + dy1 * dy2; lensqprod = (dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2); return roundf((dotprod * dotprod / lensqprod) * 1000) / 1000 <= .5f; } #define TestTan(d1, d2) (abs(d1) > (abs(d2) * gBendTan) / 1000) #define FRound(x) FTrunc(FRnd(x)) static bool IsCCW(Fixed x0, Fixed y0, Fixed x1, Fixed y1, Fixed x2, Fixed y2) { /* returns true if (x0,y0) -> (x1,y1) -> (x2,y2) is counter clockwise in glyph space */ int32_t dx0, dy0, dx1, dy1; bool ccw; dx0 = FRound(x1 - x0); dy0 = -FRound(y1 - y0); dx1 = FRound(x2 - x1); dy1 = -FRound(y2 - y1); ccw = (dx0 * dy1) >= (dx1 * dy0); return ccw; } static void DoHBendsNxt(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2, x3, y3; bool ysame; if (y0 == y1) return; NxtForBend(p, &x2, &y2, &x3, &y3); ysame = ProdLt0(y2 - y1, y1 - y0); /* y0 and y2 on same side of y1 */ if (ysame || (TestTan(x1 - x2, y1 - y2) && (ProdLt0(x2 - x1, x1 - x0) || (IsVertical(x0, y0, x1, y1) && TestBend(x0, y0, x1, y1, x2, y2))))) { Fixed strt, end; Fixed delta = FixHalfMul(gBendLength); bool doboth = false; if ((x0 <= x1 && x1 < x2) || (x0 < x1 && x1 <= x2)) { /* do nothing */ } else if ((x2 < x1 && x1 <= x0) || (x2 <= x1 && x1 < x0)) delta = -delta; else if (ysame) { bool ccw; bool above = y0 < y1; ccw = IsCCW(x0, y0, x1, y1, x2, y2); if (above != ccw) delta = -delta; } else doboth = true; strt = x1 - delta; end = x1 + delta; AddHSegment(strt, end, y1, p, NULL, sBEND, 0); if (doboth) AddHSegment(end, strt, y1, p, NULL, sBEND, 1); } } static void DoHBendsPrv(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2; bool ysame; if (y0 == y1) return; PrvForBend(p, &x2, &y2); ysame = ProdLt0(y2 - y0, y0 - y1); if (ysame || (TestTan(x0 - x2, y0 - y2) && (ProdLt0(x2 - x0, x0 - x1) || (IsVertical(x0, y0, x1, y1) && TestBend(x2, y2, x0, y0, x1, y1))))) { Fixed strt, end; Fixed delta = FixHalfMul(gBendLength); bool doboth = false; if ((x2 < x0 && x0 <= x1) || (x2 <= x0 && x0 < x1)) { /* do nothing */ } else if ((x1 < x0 && x0 <= x2) || (x1 <= x0 && x0 < x2)) delta = -delta; else if (ysame) { bool ccw; bool above = y2 < y0; ccw = IsCCW(x2, y2, x0, y0, x1, y1); if (above != ccw) delta = -delta; } strt = x0 - delta; end = x0 + delta; AddHSegment(strt, end, y0, p->prev, NULL, sBEND, 2); if (doboth) AddHSegment(end, strt, y0, p->prev, NULL, sBEND, 3); } } static void DoVBendsNxt(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2, x3, y3; bool xsame; if (x0 == x1) return; NxtForBend(p, &x2, &y2, &x3, &y3); xsame = ProdLt0(x2 - x1, x1 - x0); if (xsame || (TestTan(y1 - y2, x1 - x2) && (ProdLt0(y2 - y1, y1 - y0) || (IsHorizontal(x0, y0, x1, y1) && TestBend(x0, y0, x1, y1, x2, y2))))) { Fixed strt, end; Fixed delta = FixHalfMul(gBendLength); bool doboth = false; if ((y0 <= y1 && y1 < y2) || (y0 < y1 && y1 <= y2)) { /* do nothing */ } else if ((y2 < y1 && y1 <= y0) || (y2 <= y1 && y1 < y0)) delta = -delta; else if (xsame) { bool right = x0 > x1; bool ccw = IsCCW(x0, y0, x1, y1, x2, y2); if (right != ccw) delta = -delta; delta = -delta; } else doboth = true; strt = y1 - delta; end = y1 + delta; AddVSegment(strt, end, x1, p, NULL, sBEND, 0); if (doboth) AddVSegment(end, strt, x1, p, NULL, sBEND, 1); } } static void DoVBendsPrv(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2; bool xsame; if (x0 == x1) return; PrvForBend(p, &x2, &y2); xsame = ProdLt0(x2 - x0, x0 - x1); if (xsame || (TestTan(y0 - y2, x0 - x2) && (ProdLt0(y2 - y0, y0 - y1) || (IsHorizontal(x0, y0, x1, y1) && TestBend(x2, y2, x0, y0, x1, y1))))) { Fixed strt, end; Fixed delta = FixHalfMul(gBendLength); bool doboth = false; if ((y2 < y0 && y0 <= y1) || (y2 <= y0 && y0 < y1)) { /* do nothing */ } else if ((y1 < y0 && y0 <= y2) || (y1 <= y0 && y0 < y2)) delta = -delta; else if (xsame) { bool right = x0 > x1; bool ccw = IsCCW(x2, y2, x0, y0, x1, y1); if (right != ccw) delta = -delta; delta = -delta; } strt = y0 - delta; end = y0 + delta; AddVSegment(strt, end, x0, p->prev, NULL, sBEND, 2); if (doboth) AddVSegment(end, strt, x0, p->prev, NULL, sBEND, 3); } } static void MergeLnkSegs(HintSeg* seg1, HintSeg* seg2, SegLnkLst* lst) { /* replace lnk refs to seg1 by seg2 */ while (lst != NULL) { SegLnk* lnk = lst->lnk; if (lnk->seg == seg1) lnk->seg = seg2; lst = lst->next; } } static void MergeHSegs(HintSeg* seg1, HintSeg* seg2) { MergeLnkSegs(seg1, seg2, Hlnks); } static void MergeVSegs(HintSeg* seg1, HintSeg* seg2) { MergeLnkSegs(seg1, seg2, Vlnks); } static void ReportRemSeg(int32_t l, HintSeg* lst) { Fixed from = 0, to = 0, loc = 0; /* this assumes !YgoesUp */ switch (l) { case 1: case 2: from = lst->sMax; to = lst->sMin; break; case 0: case 3: from = lst->sMin; to = lst->sMax; break; } loc = lst->sLoc; switch (l) { case 0: case 1: LogMsg(LOGDEBUG, OK, "rem vseg %g %g to %g %g", FixToDbl(loc), FixToDbl(-from), FixToDbl(loc), FixToDbl(-to)); break; case 2: case 3: LogMsg(LOGDEBUG, OK, "rem hseg %g %g to %g %g", FixToDbl(from), FixToDbl(-loc), FixToDbl(to), FixToDbl(-loc)); break; } } /* Filters out bogus bend segments. */ static void RemExtraBends(int32_t l0, int32_t l1) { HintSeg* lst0 = gSegLists[l0]; HintSeg* prv = NULL; while (lst0 != NULL) { HintSeg* nxt = lst0->sNxt; Fixed loc0 = lst0->sLoc; HintSeg* lst = gSegLists[l1]; HintSeg* p = NULL; while (lst != NULL) { HintSeg* n = lst->sNxt; Fixed loc = lst->sLoc; if (loc > loc0) break; /* list in increasing order by sLoc */ if (loc == loc0 && lst->sMin < lst0->sMax && lst->sMax > lst0->sMin) { if (lst0->sType == sBEND && lst->sType != sBEND && lst->sType != sGHOST && (lst->sMax - lst->sMin) > (lst0->sMax - lst0->sMin) * 3) { /* delete lst0 */ if (prv == NULL) gSegLists[l0] = nxt; else prv->sNxt = nxt; ReportRemSeg(l0, lst0); lst0 = prv; break; } if (lst->sType == sBEND && lst0->sType != sBEND && lst0->sType != sGHOST && (lst0->sMax - lst0->sMin) > (lst->sMax - lst->sMin) * 3) { /* delete lst */ if (p == NULL) gSegLists[l1] = n; else p->sNxt = n; ReportRemSeg(l1, lst); lst = p; } } p = lst; lst = n; } prv = lst0; lst0 = nxt; } } static void CompactList(int32_t i, void (*nm)(HintSeg*, HintSeg*)) { HintSeg* lst = gSegLists[i]; HintSeg* prv = NULL; while (lst != NULL) { bool flg; HintSeg* nxt = lst->sNxt; HintSeg* nxtprv = lst; while (true) { Fixed lstmin, lstmax, nxtmin, nxtmax; if ((nxt == NULL) || (nxt->sLoc > lst->sLoc)) { flg = true; break; } lstmin = lst->sMin; lstmax = lst->sMax; nxtmin = nxt->sMin; nxtmax = nxt->sMax; if (lstmax >= nxtmin && lstmin <= nxtmax) { /* do not worry about YgoesUp since "sMax" is really max in device space, not in glyph space */ if (abs(lstmax - lstmin) > abs(nxtmax - nxtmin)) { /* merge into lst and remove nxt */ (*nm)(nxt, lst); lst->sMin = NUMMIN(lstmin, nxtmin); lst->sMax = NUMMAX(lstmax, nxtmax); lst->sBonus = NUMMAX(lst->sBonus, nxt->sBonus); nxtprv->sNxt = nxt->sNxt; } else { /* merge into nxt and remove lst */ (*nm)(lst, nxt); nxt->sMin = NUMMIN(lstmin, nxtmin); nxt->sMax = NUMMAX(lstmax, nxtmax); nxt->sBonus = NUMMAX(lst->sBonus, nxt->sBonus); lst = lst->sNxt; if (prv == NULL) gSegLists[i] = lst; else prv->sNxt = lst; } flg = false; break; } nxtprv = nxt; nxt = nxt->sNxt; } if (flg) { prv = lst; lst = lst->sNxt; } } } static Fixed PickVSpot(Fixed x0, Fixed y0, Fixed x1, Fixed y1, Fixed px1, Fixed py1, Fixed px2, Fixed py2, Fixed prvx, Fixed prvy, Fixed nxtx, Fixed nxty) { Fixed a1, a2; if (x0 == px1 && x1 != px2) return x0; if (x0 != px1 && x1 == px2) return x1; if (x0 == prvx && x1 != nxtx) return x0; if (x0 != prvx && x1 == nxtx) return x1; a1 = abs(py1 - y0); a2 = abs(py2 - y1); if (a1 > a2) return x0; a1 = abs(py2 - y1); a2 = abs(py1 - y0); if (a1 > a2) return x1; if (x0 == prvx && x1 == nxtx) { a1 = abs(y0 - prvy); a2 = abs(y1 - nxty); if (a1 > a2) return x0; return x1; } return FixHalfMul(x0 + x1); } static Fixed AdjDist(Fixed d, Fixed q) { Fixed val; if (q == FixOne) { DEBUG_ROUND(d) /* DEBUG 8 BIT */ return d; } val = (d * q) >> 8; DEBUG_ROUND(val) /* DEBUG 8 BIT */ return val; } /* serifs of ITCGaramond Ultra have points that are not quite horizontal e.g., in H: (53,51)(74,52)(116,54) the following was added to let these through */ static bool TstFlat(Fixed dmn, Fixed dmx) { if (dmn < 0) dmn = -dmn; if (dmx < 0) dmx = -dmx; return (dmx >= PSDist(50) && dmn <= PSDist(4)); } static bool NxtHorz(Fixed x, Fixed y, PathElt* p) { Fixed x2, y2, x3, y3; NxtForBend(p, &x2, &y2, &x3, &y3); return TstFlat(y2 - y, x2 - x); } static bool PrvHorz(Fixed x, Fixed y, PathElt* p) { Fixed x2, y2; PrvForBend(p, &x2, &y2); return TstFlat(y2 - y, x2 - x); } static bool NxtVert(Fixed x, Fixed y, PathElt* p) { Fixed x2, y2, x3, y3; NxtForBend(p, &x2, &y2, &x3, &y3); return TstFlat(x2 - x, y2 - y); } static bool PrvVert(Fixed x, Fixed y, PathElt* p) { Fixed x2, y2; PrvForBend(p, &x2, &y2); return TstFlat(x2 - x, y2 - y); } /* PrvSameDir and NxtSameDir were added to check the direction of a path and not add a band if the point is not at an extreme and is going in the same direction as the previous path. */ static bool TstSameDir(Fixed x0, Fixed y0, Fixed x1, Fixed y1, Fixed x2, Fixed y2) { if (ProdLt0(y0 - y1, y1 - y2) || ProdLt0(x0 - x1, x1 - x2)) return false; return !TestBend(x0, y0, x1, y1, x2, y2); } static bool PrvSameDir(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2; p = PrvForBend(p, &x2, &y2); if (p != NULL && p->type == CURVETO && p->prev != NULL) GetEndPoint(p->prev, &x2, &y2); return TstSameDir(x0, y0, x1, y1, x2, y2); } static bool NxtSameDir(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PathElt* p) { Fixed x2, y2, x3, y3; p = NxtForBend(p, &x2, &y2, &x3, &y3); if (p != NULL && p->type == CURVETO) { x2 = p->x3; y2 = p->y3; } return TstSameDir(x0, y0, x1, y1, x2, y2); } void GenVPts(int32_t specialGlyphType) { /* specialGlyphType 1 = upper; -1 = lower; 0 = neither */ PathElt *p, *fl; bool isVert, flex1, flex2; Fixed flx0, fly0, llx, lly, urx, ury, yavg, yend, ydist, q, q2; Fixed prvx, prvy, nxtx, nxty, xx, yy, yd2; p = gPathStart; flex1 = flex2 = false; cpTo = gCPpercent; cpFrom = 100 - cpTo; flx0 = fly0 = 0; fl = NULL; while (p != NULL) { Fixed x0, y0, x1, y1; GetEndPoints(p, &x0, &y0, &x1, &y1); if (p->type == CURVETO) { Fixed px1, py1, px2, py2; isVert = false; if (p->isFlex) { if (flex1) { if (IsVertical(flx0, fly0, x1, y1)) AddVSegment(fly0, y1, x1, fl->prev, p, sLINE, 4); flex1 = false; flex2 = true; } else { flex1 = true; flex2 = false; flx0 = x0; fly0 = y0; fl = p; } } else flex1 = flex2 = false; px1 = p->x1; py1 = p->y1; px2 = p->x2; py2 = p->y2; if (!flex2) { if ((q = VertQuo(px1, py1, x0, y0)) == 0) /* first two not vertical */ DoVBendsPrv(x0, y0, px1, py1, p); else { isVert = true; if (px1 == x0 || (px2 != x1 && (PrvVert(px1, py1, p) || !PrvSameDir(x1, y1, x0, y0, p)))) { if ((q2 = VertQuo(px2, py2, x0, y0)) > 0 && ProdGe0(py1 - y0, py2 - y0) && abs(py2 - y0) > abs(py1 - y0)) { ydist = AdjDist(CPTo(py1, py2) - y0, q2); yend = AdjDist(CPTo(y0, py1) - y0, q); if (abs(yend) > abs(ydist)) ydist = yend; AddVSegment(y0, y0 + ydist, x0, p->prev, p, sCURVE, 5); } else { ydist = AdjDist(CPTo(y0, py1) - y0, q); AddVSegment(y0, CPTo(y0, py1), x0, p->prev, p, sCURVE, 6); } } } } if (!flex1) { if ((q = VertQuo(px2, py2, x1, y1)) == 0) /* last 2 not vertical */ DoVBendsNxt(px2, py2, x1, y1, p); else if (px2 == x1 || (px1 != x0 && (NxtVert(px2, py2, p) || !NxtSameDir(x0, y0, x1, y1, p)))) { ydist = AdjDist(y1 - CPFrom(py2, y1), q); isVert = true; q2 = VertQuo(x0, y0, x1, y1); yd2 = (q2 > 0) ? AdjDist(y1 - y0, q2) : 0; if (isVert && q2 > 0 && abs(yd2) > abs(ydist)) { if (x0 == px1 && px1 == px2 && px2 == x1) ReportLinearCurve(p, x0, y0, x1, y1); ydist = FixHalfMul(yd2); yavg = FixHalfMul(y0 + y1); PrvForBend(p, &prvx, &prvy); NxtForBend(p, &nxtx, &nxty, &xx, &yy); AddVSegment(yavg - ydist, yavg + ydist, PickVSpot(x0, y0, x1, y1, px1, py1, px2, py2, prvx, prvy, nxtx, nxty), p, NULL, sCURVE, 7); } else { q2 = VertQuo(px1, py1, x1, y1); if (q2 > 0 && ProdGe0(py1 - y1, py2 - y1) && abs(py2 - y1) < abs(py1 - y1)) { yend = AdjDist(y1 - CPFrom(py1, py2), q2); if (abs(yend) > abs(ydist)) ydist = yend; AddVSegment(y1 - ydist, y1, x1, p, NULL, sCURVE, 8); } else AddVSegment(y1 - ydist, y1, x1, p, NULL, sCURVE, 9); } } } if (!flex1 && !flex2) { Fixed minx, maxx; maxx = NUMMAX(x0, x1); minx = NUMMIN(x0, x1); if (px1 - maxx >= FixTwo || px2 - maxx >= FixTwo || px1 - minx <= FixTwo || px2 - minx <= FixTwo) { FindCurveBBox(x0, y0, px1, py1, px2, py2, x1, y1, &llx, &lly, &urx, &ury); if (urx - maxx > FixTwo || minx - llx > FixTwo) { Fixed loc, frst, lst; loc = (minx - llx > urx - maxx) ? llx : urx; CheckBBoxEdge(p, true, loc, &frst, &lst); yavg = FixHalfMul(frst + lst); ydist = (frst == lst) ? (y1 - y0) / 10 : FixHalfMul(lst - frst); if (abs(ydist) < gBendLength) ydist = (ydist > 0) ? FixHalfMul(gBendLength) : FixHalfMul(-gBendLength); AddVSegment(yavg - ydist, yavg + ydist, loc, p, NULL, sCURVE, 10); } } } } else if (p->type == MOVETO) { gBonus = 0; if (specialGlyphType == -1) { if (IsLower(p)) gBonus = FixInt(200); } else if (specialGlyphType == 1) { if (IsUpper(p)) gBonus = FixInt(200); } } else if (!IsTiny(p)) { if ((q = VertQuo(x0, y0, x1, y1)) > 0) { if (x0 == x1) AddVSegment(y0, y1, x0, p->prev, p, sLINE, 11); else { if (q < FixQuarter) q = FixQuarter; ydist = FixHalfMul(AdjDist(y1 - y0, q)); yavg = FixHalfMul(y0 + y1); PrvForBend(p, &prvx, &prvy); NxtForBend(p, &nxtx, &nxty, &xx, &yy); AddVSegment(yavg - ydist, yavg + ydist, PickVSpot(x0, y0, x1, y1, x0, y0, x1, y1, prvx, prvy, nxtx, nxty), p, NULL, sLINE, 12); if (abs(x0 - x1) <= FixTwo) ReportNonVError(x0, y0, x1, y1); } } else { DoVBendsNxt(x0, y0, x1, y1, p); DoVBendsPrv(x0, y0, x1, y1, p); } } p = p->next; } CompactList(0, MergeVSegs); CompactList(1, MergeVSegs); RemExtraBends(0, 1); leftList = gSegLists[0]; rightList = gSegLists[1]; } bool InBlueBand(Fixed loc, int32_t n, Fixed* p) { int i; Fixed y; if (n <= 0) return false; y = -loc; /* Augment the blue band by bluefuzz in each direction. This will result in "near misses" being hinted and so adjusted by the PS interpreter. */ for (i = 0; i < n; i += 2) if ((p[i] - gBlueFuzz) <= y && (p[i + 1] + gBlueFuzz) >= y) return true; return false; } static Fixed PickHSpot(Fixed x0, Fixed y0, Fixed x1, Fixed y1, Fixed xdist, Fixed px1, Fixed py1, Fixed px2, Fixed py2, Fixed prvx, Fixed prvy, Fixed nxtx, Fixed nxty) { bool topSeg = (xdist < 0) ? true : false; bool inBlue0, inBlue1; if (topSeg) { inBlue0 = InBlueBand(y0, gLenTopBands, gTopBands); inBlue1 = InBlueBand(y1, gLenTopBands, gTopBands); } else { inBlue0 = InBlueBand(y0, gLenBotBands, gBotBands); inBlue1 = InBlueBand(y1, gLenBotBands, gBotBands); } if (inBlue0 && !inBlue1) return y0; if (inBlue1 && !inBlue0) return y1; if (y0 == py1 && y1 != py2) return y0; if (y0 != py1 && y1 == py2) return y1; if (y0 == prvy && y1 != nxty) return y0; if (y0 != prvy && y1 == nxty) return y1; if (inBlue0 && inBlue1) { Fixed upper, lower; if (y0 > y1) { upper = y1; lower = y0; } else { upper = y0; lower = y1; } return topSeg ? upper : lower; } if (abs(px1 - x0) > abs(px2 - x1)) return y0; if (abs(px2 - x1) > abs(px1 - x0)) return y1; if (y0 == prvy && y1 == nxty) { if (abs(x0 - prvx) > abs(x1 - nxtx)) return y0; return y1; } return FixHalfMul(y0 + y1); } void GenHPts(void) { PathElt *p, *fl; bool isHoriz, flex1, flex2; Fixed flx0, fly0, llx, lly, urx, ury, xavg, xend, xdist, q, q2; Fixed prvx, prvy, nxtx, nxty, xx, yy, xd2; p = gPathStart; gBonus = 0; flx0 = fly0 = 0; fl = NULL; flex1 = flex2 = false; cpTo = gCPpercent; cpFrom = 100 - cpTo; while (p != NULL) { Fixed x0, y0, x1, y1; GetEndPoints(p, &x0, &y0, &x1, &y1); if (p->type == CURVETO) { Fixed px1, py1, px2, py2; isHoriz = false; if (p->isFlex) { if (flex1) { flex1 = false; flex2 = true; if (IsHorizontal(flx0, fly0, x1, y1)) AddHSegment(flx0, x1, y1, fl->prev, p, sLINE, 4); } else { flex1 = true; flex2 = false; flx0 = x0; fly0 = y0; fl = p; } } else flex1 = flex2 = false; px1 = p->x1; py1 = p->y1; px2 = p->x2; py2 = p->y2; if (!flex2) { if ((q = HorzQuo(px1, py1, x0, y0)) == 0) DoHBendsPrv(x0, y0, px1, py1, p); else { isHoriz = true; if (py1 == y0 || (py2 != y1 && (PrvHorz(px1, py1, p) || !PrvSameDir(x1, y1, x0, y0, p)))) { if ((q2 = HorzQuo(px2, py2, x0, y0)) > 0 && ProdGe0(px1 - x0, px2 - x0) && abs(px2 - x0) > abs(px1 - x0)) { xdist = AdjDist(CPTo(px1, px2) - x0, q2); xend = AdjDist(CPTo(x0, px1) - x0, q); if (abs(xend) > abs(xdist)) xdist = xend; AddHSegment(x0, x0 + xdist, y0, p->prev, p, sCURVE, 5); } else { xdist = AdjDist(CPTo(x0, px1) - x0, q); AddHSegment(x0, x0 + xdist, y0, p->prev, p, sCURVE, 6); } } } } if (!flex1) { if ((q = HorzQuo(px2, py2, x1, y1)) == 0) DoHBendsNxt(px2, py2, x1, y1, p); else if (py2 == y1 || (py1 != y0 && (NxtHorz(px2, py2, p) || !NxtSameDir(x0, y0, x1, y1, p)))) { xdist = AdjDist(x1 - CPFrom(px2, x1), q); q2 = HorzQuo(x0, y0, x1, y1); isHoriz = true; xd2 = (q2 > 0) ? AdjDist(x1 - x0, q2) : 0; if (isHoriz && q2 > 0 && abs(xd2) > abs(xdist)) { Fixed hspot; if (y0 == py1 && py1 == py2 && py2 == y1) ReportLinearCurve(p, x0, y0, x1, y1); PrvForBend(p, &prvx, &prvy); NxtForBend(p, &nxtx, &nxty, &xx, &yy); xdist = FixHalfMul(xd2); xavg = FixHalfMul(x0 + x1); hspot = PickHSpot(x0, y0, x1, y1, xdist, px1, py1, px2, py2, prvx, prvy, nxtx, nxty); AddHSegment(xavg - xdist, xavg + xdist, hspot, p, NULL, sCURVE, 7); } else { q2 = HorzQuo(px1, py1, x1, y1); if (q2 > 0 && ProdGe0(px1 - x1, px2 - x1) && abs(px2 - x1) < abs(px1 - x1)) { xend = AdjDist(x1 - CPFrom(px1, px2), q2); if (abs(xend) > abs(xdist)) xdist = xend; AddHSegment(x1 - xdist, x1, y1, p, NULL, sCURVE, 8); } else AddHSegment(x1 - xdist, x1, y1, p, NULL, sCURVE, 9); } } } if (!flex1 && !flex2) { Fixed miny, maxy; maxy = NUMMAX(y0, y1); miny = NUMMIN(y0, y1); if (py1 - maxy >= FixTwo || py2 - maxy >= FixTwo || py1 - miny <= FixTwo || py2 - miny <= FixTwo) { FindCurveBBox(x0, y0, px1, py1, px2, py2, x1, y1, &llx, &lly, &urx, &ury); if (ury - maxy > FixTwo || miny - lly > FixTwo) { Fixed loc, frst, lst; loc = (miny - lly > ury - maxy) ? lly : ury; CheckBBoxEdge(p, false, loc, &frst, &lst); xavg = FixHalfMul(frst + lst); xdist = (frst == lst) ? (x1 - x0) / 10 : FixHalfMul(lst - frst); if (abs(xdist) < gBendLength) xdist = (xdist > 0.0) ? FixHalfMul(gBendLength) : FixHalfMul(-gBendLength); AddHSegment(xavg - xdist, xavg + xdist, loc, p, NULL, sCURVE, 10); } } } } else if (p->type != MOVETO && !IsTiny(p)) { if ((q = HorzQuo(x0, y0, x1, y1)) > 0) { if (y0 == y1) AddHSegment(x0, x1, y0, p->prev, p, sLINE, 11); else { if (q < FixQuarter) q = FixQuarter; xdist = FixHalfMul(AdjDist(x1 - x0, q)); xavg = FixHalfMul(x0 + x1); PrvForBend(p, &prvx, &prvy); NxtForBend(p, &nxtx, &nxty, &xx, &yy); yy = PickHSpot(x0, y0, x1, y1, xdist, x0, y0, x1, y1, prvx, prvy, nxtx, nxty); AddHSegment(xavg - xdist, xavg + xdist, yy, p->prev, p, sLINE, 12); if (abs(y0 - y1) <= FixTwo) ReportNonHError(x0, y0, x1, y1); } } else { DoHBendsNxt(x0, y0, x1, y1, p); DoHBendsPrv(x0, y0, x1, y1, p); } } p = p->next; } CompactList(2, MergeHSegs); CompactList(3, MergeHSegs); RemExtraBends(2, 3); topList = gSegLists[2]; /* this is probably unnecessary */ botList = gSegLists[3]; CheckTfmVal(topList, gTopBands, gLenTopBands); CheckTfmVal(botList, gBotBands, gLenBotBands); } void PreGenPts(void) { Hlnks = Vlnks = NULL; gSegLists[0] = NULL; gSegLists[1] = NULL; gSegLists[2] = NULL; gSegLists[3] = NULL; } psautohint-2.3.0/libpsautohint/src/head.c000066400000000000000000000160471401523215600204420ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" PathElt* GetDest(PathElt* cldest) { if (cldest == NULL) return NULL; while (true) { cldest = cldest->prev; if (cldest == NULL) return gPathStart; if (cldest->type == MOVETO) return cldest; } } PathElt* GetClosedBy(PathElt* clsdby) { if (clsdby == NULL) return NULL; if (clsdby->type == CLOSEPATH) return clsdby; while (true) { clsdby = clsdby->next; if (clsdby == NULL) return NULL; if (clsdby->type == MOVETO) return NULL; if (clsdby->type == CLOSEPATH) return clsdby; } } void GetEndPoint(PathElt* e, Fixed* x1p, Fixed* y1p) { retry: if (e == NULL) { *x1p = 0; *y1p = 0; return; } switch (e->type) { case MOVETO: case LINETO: *x1p = e->x; *y1p = e->y; break; case CURVETO: *x1p = e->x3; *y1p = e->y3; break; case CLOSEPATH: e = GetDest(e); if (e == NULL || e->type == CLOSEPATH) { LogMsg(LOGERROR, NONFATALERROR, "Bad description."); } goto retry; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal operator."); } } } void GetEndPoints(PathElt* p, Fixed* px0, Fixed* py0, Fixed* px1, Fixed* py1) { GetEndPoint(p, px1, py1); GetEndPoint(p->prev, px0, py0); } #define Interpolate(q, v0, q0, v1, q1) (v0 + (q - q0) * ((v1 - v0) / (q1 - q0))) static Fixed HVness(float* pq) { float q; float result; /* approximately == 2 q neg exp */ /* as q -> 0, result goes to 1.0 */ /* as q -> inf, result goes to 0.0 */ q = *pq; if (q < .25f) result = (float)Interpolate(q, 1.0f, 0.0f, .841f, .25f); else if (q < .5f) result = (float)Interpolate(q, .841f, .25f, .707f, .5f); else if (q < 1) result = (float)Interpolate(q, .707f, .5f, .5f, 1.0f); else if (q < 2) result = (float)Interpolate(q, .5f, 1.0f, .25f, 2.0f); else if (q < 4) result = (float)Interpolate(q, .25f, 2.0f, 0.0f, 4.0f); else result = 0.0; return acpflttofix(&result); } Fixed VertQuo(Fixed xk, Fixed yk, Fixed xl, Fixed yl) { /* FixOne means exactly vertical. 0 means not vertical */ /* intermediate values mean almost vertical */ Fixed xabs, yabs; float rx, ry, q; xabs = xk - xl; if (xabs < 0) xabs = -xabs; if (xabs == 0) return FixOne; yabs = yk - yl; if (yabs < 0) yabs = -yabs; if (yabs == 0) return 0; acfixtopflt(xabs, &rx); acfixtopflt(yabs, &ry); q = (float)(rx * rx) / (gTheta * ry); /* DEBUG 8 BIT. Used to by 2*(rx*rx)/(theta*ry). Don't need thsi with the 8 bits of Fixed fraction. */ return HVness(&q); } Fixed HorzQuo(Fixed xk, Fixed yk, Fixed xl, Fixed yl) { Fixed xabs, yabs; float rx, ry, q; yabs = yk - yl; if (yabs < 0) yabs = -yabs; if (yabs == 0) return FixOne; xabs = xk - xl; if (xabs < 0) xabs = -xabs; if (xabs == 0) return 0; acfixtopflt(xabs, &rx); acfixtopflt(yabs, &ry); q = (float)(ry * ry) / (gTheta * rx); /* DEBUG 8 BIT. Used to by 2*(ry*ry)/(theta*ry). Don't need thsi with the 8 bits of Fixed fraction. */ return HVness(&q); } bool IsTiny(PathElt* e) { Fixed x0 = 0, y0 = 0, x1 = 0, y1 = 0; GetEndPoints(e, &x0, &y0, &x1, &y1); return ((abs(x0 - x1) < FixTwo) && (abs(y0 - y1) < FixTwo)) ? true : false; } bool IsShort(PathElt* e) { Fixed x0 = 0, y0 = 0, x1 = 0, y1 = 0, dx = 0, dy = 0, mn = 0, mx = 0; GetEndPoints(e, &x0, &y0, &x1, &y1); dx = abs(x0 - x1); dy = abs(y0 - y1); if (dx > dy) { mn = dy; mx = dx; } else { mn = dx; mx = dy; } return ((mx + (mn * 42) / 125) < FixInt(6)) ? true : false; /* DEBUG 8 BIT. Increased threshold from 3 to 6, for change in coordinare system. */ } PathElt* NxtForBend(PathElt* p, Fixed* px2, Fixed* py2, Fixed* px3, Fixed* py3) { PathElt *nxt, *nxtMT = NULL; Fixed x = 0, y = 0; nxt = p; GetEndPoint(p, &x, &y); while (true) { if (nxt->type == CLOSEPATH) { nxt = GetDest(nxt); /* The following test was added to prevent an infinite loop. */ if (nxtMT != NULL && nxtMT == nxt) { ReportPossibleLoop(p); nxt = NULL; } else { nxtMT = nxt; nxt = nxt->next; } } else nxt = nxt->next; if (nxt == NULL) { /* forget it */ *px2 = *py2 = *px3 = *py3 = -FixInt(9999); return nxt; } if (!IsTiny(nxt)) break; } if (nxt->type == CURVETO) { Fixed x2 = nxt->x1; Fixed y2 = nxt->y1; if (x2 == x && y2 == y) { x2 = nxt->x2; y2 = nxt->y2; } *px2 = x2; *py2 = y2; } else GetEndPoint(nxt, px2, py2); GetEndPoint(nxt, px3, py3); return nxt; } PathElt* PrvForBend(PathElt* p, Fixed* px2, Fixed* py2) { PathElt *prv, *prvCP = NULL; Fixed x2, y2; prv = p; while (true) { prv = prv->prev; if (prv == NULL) goto Bogus; if (prv->type == MOVETO) { prv = GetClosedBy(prv); /* The following test was added to prevent an infinite loop. */ if (prv == NULL || (prvCP != NULL && prvCP == prv)) goto Bogus; prvCP = prv; } if (!IsTiny(prv)) break; } if (prv->type == CURVETO) { x2 = prv->x2; y2 = prv->y2; if (x2 == prv->x3 && y2 == prv->y3) { x2 = prv->x1; y2 = prv->y1; } *px2 = x2; *py2 = y2; } else { p = prv->prev; if (p == NULL) goto Bogus; GetEndPoint(p, px2, py2); } return prv; Bogus: *px2 = *py2 = -FixInt(9999); return prv; } static bool CheckHeight(bool upperFlag, PathElt* p) { PathElt* ee; Fixed y, yy; ee = gPathStart; y = -p->y; while (ee != NULL) { if (ee->type == MOVETO && ee != p) { yy = -ee->y; if ((upperFlag && yy > y) || (!upperFlag && yy < y)) return false; } ee = ee->next; } return true; } bool IsLower(PathElt* p) { return CheckHeight(false, p); } bool IsUpper(PathElt* p) { return CheckHeight(true, p); } psautohint-2.3.0/libpsautohint/src/logging.c000066400000000000000000000022351401523215600211610ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" AC_REPORTFUNCPTR gLibReportCB = NULL; /* proc to be called from LogMsg if error occurs */ static int (*errorproc)(int16_t); void set_errorproc(int (*userproc)(int16_t)) { errorproc = userproc; } void LogMsg(int16_t level, /* error, warning, info */ int16_t code, /* exit value - if !OK, this proc will not return */ char* format, /* message string */ ...) { /* "glyphname: message" */ char str[MAX_GLYPHNAME_LEN + 2 + MAXMSGLEN + 1] = { 0 }; va_list va; if (strlen(gGlyphName) > 0) snprintf(str, strlen(gGlyphName) + 3, "%s: ", gGlyphName); va_start(va, format); vsnprintf(str + strlen(str), MAXMSGLEN, format, va); va_end(va); if (gLibReportCB != NULL) gLibReportCB(str, level); if (level == LOGERROR && (code == NONFATALERROR || code == FATALERROR)) { (*errorproc)(code); } } psautohint-2.3.0/libpsautohint/src/logging.h000066400000000000000000000015541401523215600211710ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "psautohint.h" #include "basic.h" #ifndef BF_LOGGING_H_ #define BF_LOGGING_H_ /* defines for LogMsg code param */ #define OK 0 #define NONFATALERROR 1 #define FATALERROR 2 /* defines for LogMsg level param */ #define LOGDEBUG AC_LogDebug #define INFO AC_LogInfo #define WARNING AC_LogWarning #define LOGERROR AC_LogError /* maximum message length */ #define MAXMSGLEN 500 /* global log function which is supplied by the following */ extern AC_REPORTFUNCPTR gLibReportCB; void LogMsg(int16_t, int16_t, char *, ...); void set_errorproc( int (*)(int16_t) ); #endif /* BF_LOGGING_H_ */ psautohint-2.3.0/libpsautohint/src/memory.c000066400000000000000000000034731401523215600210500ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "memory.h" #include "logging.h" static void* defaultAC_memmanage(void* ctxptr, void* old, size_t size) { (void)ctxptr; if (size > 0) { if (NULL == old) { return malloc(size); } else { return realloc(old, size); } } else { if (NULL == old) return NULL; else { free(old); return NULL; } } } static AC_MEMMANAGEFUNCPTR AC_memmanageFuncPtr = defaultAC_memmanage; static void* AC_memmanageCtxPtr = NULL; void setAC_memoryManager(void* ctxptr, AC_MEMMANAGEFUNCPTR func) { AC_memmanageFuncPtr = func; AC_memmanageCtxPtr = ctxptr; } void* AllocateMem(size_t nelem, size_t elsize, const char* description) { /* calloc(nelem, elsize) */ void* ptr = AC_memmanageFuncPtr(AC_memmanageCtxPtr, NULL, nelem * elsize); if (NULL != ptr) memset(ptr, 0x0, nelem * elsize); if (ptr == NULL) { LogMsg(LOGERROR, FATALERROR, "Cannot allocate %zu bytes of memory for %s.", nelem * elsize, description); } return (ptr); } void* ReallocateMem(void* ptr, size_t size, const char* description) { /* realloc(ptr, size) */ void* newptr = AC_memmanageFuncPtr(AC_memmanageCtxPtr, ptr, size); if (newptr == NULL) { LogMsg(LOGERROR, FATALERROR, "Cannot reallocate %zu bytes of memory for %s.", size, description); } return (newptr); } void UnallocateMem(void* ptr) { /* free(ptr) */ AC_memmanageFuncPtr(AC_memmanageCtxPtr, ptr, 0); } psautohint-2.3.0/libpsautohint/src/memory.h000066400000000000000000000011011401523215600210370ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "psautohint.h" #include "basic.h" #ifndef AC_MEMORY_H_ #define AC_MEMORY_H_ void setAC_memoryManager(void* ctxptr, AC_MEMMANAGEFUNCPTR func); void* AllocateMem(size_t, size_t, const char*); void* ReallocateMem(void*, size_t, const char*); void UnallocateMem(void* ptr); #endif /* AC_MEMORY_H_ */ psautohint-2.3.0/libpsautohint/src/merge.c000066400000000000000000000412731401523215600206370ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #define CLSMRG (PSDist(20)) /* true iff you can go from e1 to e2 without going out of band loc1..loc2 * if vert is true, then band is vert (test x values) * else band is horizontal (test y values) * band is expanded by CLSMRG in each direction */ static bool CloseElements(PathElt* e1, PathElt* e2, Fixed loc1, Fixed loc2, bool vert) { Fixed tmp; Fixed x, y; PathElt* e; if (e1 == e2) return true; if (loc1 < loc2) { if ((loc2 - loc1) > 5 * CLSMRG) return false; loc1 -= CLSMRG; loc2 += CLSMRG; } else { if ((loc1 - loc2) > 5 * CLSMRG) return false; tmp = loc1; loc1 = loc2 - CLSMRG; loc2 = tmp + CLSMRG; } e = e1; while (true) { if (e == e2) return true; GetEndPoint(e, &x, &y); tmp = vert ? x : y; if (tmp > loc2 || tmp < loc1) return false; if (e->type == CLOSEPATH) e = GetDest(e); else e = e->next; if (e == e1) return false; } } bool CloseSegs(HintSeg* s1, HintSeg* s2, bool vert) { /* true if the elements for these segs are "close" in the path */ PathElt *e1, *e2; Fixed loc1, loc2; if ((s1 == NULL) || (s2 == NULL)) return false; if (s1 == s2) return true; e1 = s1->sElt; e2 = s2->sElt; if (e1 == NULL || e2 == NULL) return true; loc1 = s1->sLoc; loc2 = s2->sLoc; return (CloseElements(e1, e2, loc1, loc2, vert) || CloseElements(e2, e1, loc2, loc1, vert)) ? true : false; } void DoPrune(void) { /* Step through valList to the first item which is not pruned; set that to be the head of the list. Then remove from the list any subsequent element for which 'pruned' is true. */ HintVal *vL = gValList, *vPrv; while (vL != NULL && vL->pruned) vL = vL->vNxt; gValList = vL; if (vL == NULL) return; vPrv = vL; vL = vL->vNxt; while (vL != NULL) { if (vL->pruned) vPrv->vNxt = vL = vL->vNxt; else { vPrv = vL; vL = vL->vNxt; } } } static HintVal* PruneOne(HintVal* sLst, bool hFlg, HintVal* sL, int32_t i) { /* Simply set the 'pruned' field to True for sLst. */ if (hFlg) ReportPruneHVal(sLst, sL, i); else ReportPruneVVal(sLst, sL, i); sLst->pruned = true; return sLst->vNxt; } #define PRNDIST (PSDist(10)) #define PRNFCTR (3) #define MUCHFCTR (50) #define VERYMUCHFCTR (100) static bool PruneLt(Fixed val, Fixed v) { if (v < (FIXED_MAX / 10) && val < (FIXED_MAX / PRNFCTR)) return (val * PRNFCTR) < (v * 10); return (val / 10) < (v / PRNFCTR); } static bool PruneLe(Fixed val, Fixed v) { if (val < (FIXED_MAX / PRNFCTR)) return v <= (val * PRNFCTR); return (v / PRNFCTR) <= val; } static bool PruneGt(Fixed val, Fixed v) { if (val < (FIXED_MAX / PRNFCTR)) return v > (val * PRNFCTR); return (v / PRNFCTR) > val; } static bool PruneMuchGt(Fixed val, Fixed v) { if (val < (FIXED_MAX / MUCHFCTR)) return v > (val * MUCHFCTR); return (v / MUCHFCTR) > val; } static bool PruneVeryMuchGt(Fixed val, Fixed v) { if (val < (FIXED_MAX / VERYMUCHFCTR)) return v > (val * VERYMUCHFCTR); return (v / VERYMUCHFCTR) > val; } /* The changes made here and in PruneHVals are to fix a bug in MinisterLight/E where the top left point was not getting hinted. */ void PruneVVals(void) { HintVal *sLst, *sL; HintSeg *seg1, *seg2, *sg1, *sg2; Fixed lft, rht, l, r, prndist; Fixed val, v; bool flg, otherLft, otherRht; sLst = gValList; prndist = PRNDIST; while (sLst != NULL) { flg = true; otherLft = otherRht = false; val = sLst->vVal; lft = sLst->vLoc1; rht = sLst->vLoc2; seg1 = sLst->vSeg1; seg2 = sLst->vSeg2; sL = gValList; while (sL != NULL) { v = sL->vVal; sg1 = sL->vSeg1; sg2 = sL->vSeg2; l = sL->vLoc1; r = sL->vLoc2; if ((l == lft && r == rht) || PruneLe(val, v)) goto NxtSL; if (rht + prndist >= r && lft - prndist <= l && (val < FixInt(100) && PruneMuchGt(val, v) ? (CloseSegs(seg1, sg1, true) || CloseSegs(seg2, sg2, true)) : (CloseSegs(seg1, sg1, true) && CloseSegs(seg2, sg2, true)))) { sLst = PruneOne(sLst, false, sL, 1); flg = false; break; } if (seg1 != NULL && seg2 != NULL) { if (abs(l - lft) < FixOne) { if (!otherLft && PruneLt(val, v) && abs(l - r) < abs(lft - rht) && CloseSegs(seg1, sg1, true)) otherLft = true; if (seg2->sType == sBEND && CloseSegs(seg1, sg1, true)) { sLst = PruneOne(sLst, false, sL, 2); flg = false; break; } } if (abs(r - rht) < FixOne) { if (!otherRht && PruneLt(val, v) && abs(l - r) < abs(lft - rht) && CloseSegs(seg2, sg2, true)) otherRht = true; if (seg1->sType == sBEND && CloseSegs(seg2, sg2, true)) { sLst = PruneOne(sLst, false, sL, 3); flg = false; break; } } if (otherLft && otherRht) { sLst = PruneOne(sLst, false, sL, 4); flg = false; break; } } NxtSL: sL = sL->vNxt; } if (flg) { sLst = sLst->vNxt; } } DoPrune(); } #define Fix16 (FixOne << 4) void PruneHVals(void) { HintVal *sLst, *sL; HintSeg *seg1, *seg2, *sg1, *sg2; Fixed bot, top, t, b; Fixed val, v, prndist; bool flg, otherTop, otherBot, topInBlue, botInBlue, ghst; sLst = gValList; prndist = PRNDIST; while (sLst != NULL) { flg = true; otherTop = otherBot = false; seg1 = sLst->vSeg1; seg2 = sLst->vSeg2; /* seg1 is bottom, seg2 is top */ ghst = sLst->vGhst; val = sLst->vVal; bot = sLst->vLoc1; top = sLst->vLoc2; topInBlue = InBlueBand(top, gLenTopBands, gTopBands); botInBlue = InBlueBand(bot, gLenBotBands, gBotBands); sL = gValList; while (sL != NULL) { if ((sL->pruned) && (gDoAligns || !gDoStems)) goto NxtSL; sg1 = sL->vSeg1; sg2 = sL->vSeg2; /* sg1 is b, sg2 is t */ v = sL->vVal; if (!ghst && sL->vGhst && !PruneVeryMuchGt(val, v)) goto NxtSL; /* Do not bother checking if we should prune, if slSt is not ghost hint, sL is ghost hint, and not (sL->vVal is more than 50* bigger than sLst->vVal. Basically, we prefer non-ghost hints over ghost unless vVal is really low. */ b = sL->vLoc1; t = sL->vLoc2; if (t == top && b == bot) goto NxtSL; /* Don't compare two valList elements that have the same top and bot. */ if (/* Prune sLst if the following are all true */ PruneGt(val, v) && /* v is more than 3* val */ (top - prndist <= t && bot + prndist >= b) && /* The sL hint is within the sLst hint */ (val < FixInt(100) && PruneMuchGt(val, v) ? (CloseSegs(seg1, sg1, false) || CloseSegs(seg2, sg2, false)) : (CloseSegs(seg1, sg1, false) && CloseSegs(seg2, sg2, false))) && /* val is less than 100, and the segments are close to each other.*/ (val < Fix16 || /* needs to be greater than FixOne << 3 for HelveticaNeue 95 Black G has val == 2.125 Poetica/ItalicOne H has val == .66 */ ((!topInBlue || top == t) && (!botInBlue || bot == b))) /* either val is small ( < Fixed 16) or, for both bot and top, the value is the same as SL, and not in a blue zone. */ ) { sLst = PruneOne(sLst, true, sL, 5); flg = false; break; } if (seg1 == NULL || seg2 == NULL) goto NxtSL; /* If the sLst is aghost hint, skip */ if (abs(b - bot) < FixOne) { /* If the bottoms of the stems are within 1 unit */ if (PruneGt(val, v) && /* If v is more than 3* val) */ !topInBlue && seg2->sType == sBEND && CloseSegs(seg1, sg1, false) /* and the tops are close */ ) { sLst = PruneOne(sLst, true, sL, 6); flg = false; break; } if (!otherBot && PruneLt(val, v) && abs(t - b) < abs(top - bot)) { if (CloseSegs(seg1, sg1, false)) otherBot = true; } } if (abs(t - top) < FixOne) { /* If the tops of the stems are within 1 unit */ if (PruneGt(val, v) && /* If v is more than 3* val) */ !botInBlue && seg2->sType == sBEND && CloseSegs(seg1, sg1, false)) /* and the tops are close */ { sLst = PruneOne(sLst, true, sL, 7); flg = false; break; } if (!otherTop && PruneLt(val, v) && abs(t - b) < abs(top - bot)) { if (CloseSegs(seg2, sg2, false)) otherTop = true; } } if (otherBot && otherTop) { /* if v less than val by a factor of 3, and the sl stem width is less than the sLst stem width, and the tops and bottoms are close */ sLst = PruneOne(sLst, true, sL, 8); flg = false; break; } NxtSL: sL = sL->vNxt; } if (flg) { sLst = sLst->vNxt; } } DoPrune(); } static void FindBestVals(HintVal* vL) { Fixed bV, bS; Fixed t, b; HintVal *vL2, *vPrv, *bstV; for (; vL != NULL; vL = vL->vNxt) { if (vL->vBst != NULL) continue; /* already assigned */ bV = vL->vVal; bS = vL->vSpc; bstV = vL; b = vL->vLoc1; t = vL->vLoc2; vL2 = vL->vNxt; vPrv = vL; for (; vL2 != NULL; vL2 = vL2->vNxt) { if (vL2->vBst != NULL || vL2->vLoc1 != b || vL2->vLoc2 != t) continue; if ((vL2->vSpc == bS && vL2->vVal > bV) || (vL2->vSpc > bS)) { bS = vL2->vSpc; bV = vL2->vVal; bstV = vL2; } vL2->vBst = vPrv; vPrv = vL2; } while (vPrv != NULL) { vL2 = vPrv->vBst; vPrv->vBst = bstV; vPrv = vL2; } } } /* The following changes were made to fix a problem in Ryumin-Light and possibly other fonts as well. The old version causes bogus hinting and extra newhints. */ static void ReplaceVals(Fixed oldB, Fixed oldT, Fixed newB, Fixed newT, HintVal* newBst, bool vert) { HintVal* vL; for (vL = gValList; vL != NULL; vL = vL->vNxt) { if (vL->vLoc1 != oldB || vL->vLoc2 != oldT || vL->merge) continue; if (vert) ReportMergeVVal(oldB, oldT, newB, newT, vL->vVal, vL->vSpc, newBst->vVal, newBst->vSpc); else ReportMergeHVal(oldB, oldT, newB, newT, vL->vVal, vL->vSpc, newBst->vVal, newBst->vSpc); vL->vLoc1 = newB; vL->vLoc2 = newT; vL->vVal = newBst->vVal; vL->vSpc = newBst->vSpc; vL->vBst = newBst; vL->merge = true; } } void MergeVals(bool vert) { HintVal *vLst, *vL; HintVal *bstV, *bV; HintSeg *seg1, *seg2, *sg1, *sg2; Fixed bot, top, b, t; Fixed val, v, spc, s; bool ghst; FindBestVals(gValList); /* We want to get rid of wider hstems in favor or overlapping smaller hstems * only if we are NOT reporting all possible alignment zones. */ if (gAddStemExtremesCB == NULL) return; for (vL = gValList; vL != NULL; vL = vL->vNxt) vL->merge = false; while (true) { /* pick best from valList with merge field still set to false */ vLst = gValList; vL = NULL; while (vLst != NULL) { if (vLst->merge) { /* do nothing */ } else if (vL == NULL || CompareValues(vLst->vBst, vL->vBst, SFACTOR, 0)) vL = vLst; vLst = vLst->vNxt; } if (vL == NULL) break; vL->merge = true; ghst = vL->vGhst; b = vL->vLoc1; t = vL->vLoc2; sg1 = vL->vSeg1; /* left or bottom */ sg2 = vL->vSeg2; /* right or top */ vLst = gValList; bV = vL->vBst; v = bV->vVal; s = bV->vSpc; while (vLst != NULL) { /* consider replacing vLst by vL */ if (vLst->merge || ghst != vLst->vGhst) goto NxtVL; bot = vLst->vLoc1; top = vLst->vLoc2; if (bot == b && top == t) goto NxtVL; bstV = vLst->vBst; val = bstV->vVal; spc = bstV->vSpc; if ((top == t && CloseSegs(sg2, vLst->vSeg2, vert) && (vert || (!InBlueBand(t, gLenTopBands, gTopBands) && !InBlueBand(bot, gLenBotBands, gBotBands) && !InBlueBand(b, gLenBotBands, gBotBands)))) || (bot == b && CloseSegs(sg1, vLst->vSeg1, vert) && (vert || (!InBlueBand(b, gLenBotBands, gBotBands) && !InBlueBand(t, gLenTopBands, gTopBands) && !InBlueBand(top, gLenTopBands, gTopBands)))) || (abs(top - t) <= gMaxMerge && abs(bot - b) <= gMaxMerge && (vert || (t == top || !InBlueBand(top, gLenTopBands, gTopBands))) && (vert || (b == bot || !InBlueBand(bot, gLenBotBands, gBotBands))))) { if (s == spc && val == v && !vert) { if (InBlueBand(t, gLenTopBands, gTopBands)) { if (t < top) goto replace; } else if (InBlueBand(b, gLenBotBands, gBotBands)) { if (b > bot) goto replace; } } else goto replace; } else if (s == spc && sg1 != NULL && sg2 != NULL) { seg1 = vLst->vSeg1; seg2 = vLst->vSeg2; if (seg1 != NULL && seg2 != NULL) { if (abs(bot - b) <= FixOne && abs(top - t) <= gMaxBendMerge) { if (seg2->sType == sBEND && (vert || !InBlueBand(top, gLenTopBands, gTopBands))) goto replace; } else if (abs(top - t) <= FixOne && abs(bot - b) <= gMaxBendMerge) { if (v > val && seg1->sType == sBEND && (vert || !InBlueBand(bot, gLenBotBands, gBotBands))) goto replace; } } } goto NxtVL; replace: ReplaceVals(bot, top, b, t, bV, vert); NxtVL: vLst = vLst->vNxt; } vL = vL->vNxt; } } psautohint-2.3.0/libpsautohint/src/misc.c000066400000000000000000000172431401523215600204730ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" int32_t CountSubPaths(void) { PathElt* e = gPathStart; int32_t cnt = 0; while (e != NULL) { if (e->type == MOVETO) cnt++; e = e->next; } return cnt; } void RoundPathCoords(void) { PathElt* e; e = gPathStart; while (e != NULL) { if (e->type == CURVETO) { e->x1 = FHalfRnd(e->x1); e->y1 = FHalfRnd(e->y1); e->x2 = FHalfRnd(e->x2); e->y2 = FHalfRnd(e->y2); e->x3 = FHalfRnd(e->x3); e->y3 = FHalfRnd(e->y3); } else if (e->type == LINETO || e->type == MOVETO) { e->x = FHalfRnd(e->x); e->y = FHalfRnd(e->y); } e = e->next; } } static int32_t CheckForHint(void) { PathElt *mt, *cp; mt = gPathStart; while (mt != NULL) { if (mt->type != MOVETO) { ExpectedMoveTo(mt); return -1; } cp = GetClosedBy(mt); if (cp == NULL) { ReportMissingClosePath(); return -1; } mt = cp->next; } return 0; } bool PreCheckForHinting(void) { PathElt* e; int32_t cnt = 0; while (gPathEnd != NULL) { if (gPathEnd->type == MOVETO) Delete(gPathEnd); else if (gPathEnd->type != CLOSEPATH) { ReportMissingClosePath(); return false; } else break; } e = gPathStart; while (e != NULL) { if (e->type == CLOSEPATH) { PathElt* nxt; if (e == gPathEnd) break; nxt = e->next; if (nxt->type == MOVETO) { e = nxt; continue; } if (nxt->type == CLOSEPATH) { /* remove double closepath */ Delete(nxt); continue; } } e = e->next; } while (true) { int32_t chk = CheckForHint(); if (chk == -1) return false; if (chk == 0) break; if (++cnt > 10) { LogMsg(WARNING, OK, "Looping in PreCheckForHints!."); break; } } return true; } static PathElt* GetSubpathNext(PathElt* e) { while (true) { e = e->next; if (e == NULL) break; if (e->type == CLOSEPATH) break; if (!IsTiny(e)) break; } return e; } static PathElt* GetSubpathPrev(PathElt* e) { while (true) { e = e->prev; if (e == NULL) break; if (e->type == MOVETO) e = GetClosedBy(e); if (!IsTiny(e)) break; } return e; } static bool AddAutoFlexProp(PathElt* e, bool yflag) { PathElt *e0 = e, *e1 = e->next; if (e0->type != CURVETO || e1->type != CURVETO) { LogMsg(LOGERROR, NONFATALERROR, "Illegal input."); } /* Don't add flex to linear curves. */ if (yflag && e0->y3 == e1->y1 && e1->y1 == e1->y2 && e1->y2 == e1->y3) return false; else if (e0->x3 == e1->x1 && e1->x1 == e1->x2 && e1->x2 == e1->x3) return false; e0->yFlex = yflag; e1->yFlex = yflag; e0->isFlex = true; e1->isFlex = true; return true; } #define LENGTHRATIOCUTOFF \ 0.11 /* 0.33^2 : two curves must be in approximate length ratio of 1:3 or \ better */ static void TryYFlex(PathElt* e, PathElt* n, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { Fixed x2, y2, x3, y3, x4, y4; double d0sq, d1sq, quot, dx, dy; GetEndPoint(n, &x2, &y2); dy = abs(y0 - y2); if (dy > gFlexCand) return; /* too big diff in bases. If dy is within flexCand, flex will fail , but we will report it as a candidate. */ dx = abs(x0 - x2); if (dx < MAXFLEX) return; /* Let's not add flex to features less than MAXFLEX wide. */ if (dx < (3 * abs(y0 - y2))) return; /* We want the width to be at least three times the height. */ if (ProdLt0(y1 - y0, y1 - y2)) return; /* y0 and y2 not on same side of y1 */ /* check the ratios of the "lengths" of 'e' and 'n' */ dx = (x1 - x0); dy = (y1 - y0); d0sq = dx * dx + dy * dy; dx = (x2 - x1); dy = (y2 - y1); d1sq = dx * dx + dy * dy; quot = (d0sq > d1sq) ? (d1sq / d0sq) : (d0sq / d1sq); if (quot < LENGTHRATIOCUTOFF) return; if (gFlexStrict) { bool top, dwn; PathElt *p, *q; q = GetSubpathNext(n); GetEndPoint(q, &x3, &y3); if (ProdLt0(y3 - y2, y1 - y2)) return; /* y1 and y3 not on same side of y2 */ p = GetSubpathPrev(e); GetEndPoint(p->prev, &x4, &y4); if (ProdLt0(y4 - y0, y1 - y0)) return; /* y1 and y4 not on same side of y0 */ top = (x0 > x1) ? true : false; dwn = (y1 > y0) ? true : false; if ((top && !dwn) || (!top && dwn)) return; /* concave */ } if (n != e->next) { /* something in the way */ n = e->next; ReportTryFlexError(n->type == CLOSEPATH, x1, y1); return; } if (y0 != y2) { ReportTryFlexNearMiss(x0, y0, x2, y2); return; } if (AddAutoFlexProp(e, true)) ReportAddFlex(); } static void TryXFlex(PathElt* e, PathElt* n, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { Fixed x2, y2, x3, y3, x4, y4; double d0sq, d1sq, quot, dx, dy; GetEndPoint(n, &x2, &y2); dx = abs(y0 - y2); if (dx > gFlexCand) return; /* too big diff in bases */ dy = abs(x0 - x2); if (dy < MAXFLEX) return; /* Let's not add flex to features less than MAXFLEX wide. */ if (dy < (3 * abs(x0 - x2))) return; /* We want the width to be at least three times the height. */ if (ProdLt0(x1 - x0, x1 - x2)) return; /* x0 and x2 not on same side of x1 */ /* check the ratios of the "lengths" of 'e' and 'n' */ dx = (x1 - x0); dy = (y1 - y0); d0sq = dx * dx + dy * dy; dx = (x2 - x1); dy = (y2 - y1); d1sq = dx * dx + dy * dy; quot = (d0sq > d1sq) ? (d1sq / d0sq) : (d0sq / d1sq); if (quot < LENGTHRATIOCUTOFF) return; if (gFlexStrict) { PathElt *p, *q; bool lft; q = GetSubpathNext(n); GetEndPoint(q, &x3, &y3); if (ProdLt0(x3 - x2, x1 - x2)) return; /* x1 and x3 not on same side of x2 */ p = GetSubpathPrev(e); GetEndPoint(p->prev, &x4, &y4); if (ProdLt0(x4 - x0, x1 - x0)) return; /* x1 and x4 not on same side of x0 */ lft = (y0 < y2) ? true : false; if ((lft && x0 > x1) || (!lft && x0 < x1)) return; /* concave */ } if (n != e->next) { /* something in the way */ n = e->next; ReportTryFlexError(n->type == CLOSEPATH, x1, y1); return; } if (x0 != x2) { ReportTryFlexNearMiss(x0, y0, x2, y2); return; } if (AddAutoFlexProp(e, false)) ReportAddFlex(); } void AutoAddFlex(void) { PathElt *e, *n; Fixed x0, y0, x1, y1; e = gPathStart; while (e != NULL) { if (e->type != CURVETO || e->isFlex) goto Nxt; n = GetSubpathNext(e); if (n->type != CURVETO) goto Nxt; GetEndPoints(e, &x0, &y0, &x1, &y1); if (abs(y0 - y1) <= MAXFLEX) TryYFlex(e, n, x0, y0, x1, y1); if (abs(x0 - x1) <= MAXFLEX) TryXFlex(e, n, x0, y0, x1, y1); Nxt: e = e->next; } } psautohint-2.3.0/libpsautohint/src/opcodes.h000066400000000000000000000135001401523215600211710ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* opcodes for PS operators */ /* The assignment of mnemonics to constants in this file has two purposes: 1) establish the value of encoded operators that are put in the CharString of a glyph. This purpose requires that the values of such operators must correspond to the values in fontbuild.c in the PS interpreter. 2) provide an index into a lookup table for buildfont's use. Thus, some of the mnemonics are never put in a CharString, but are here just so the table lookup is possible. For such operators, the values don't matter as long as they don't conflict with any of the PS interpreter values. */ /* Thu May 12 22:56:28 PDT 1994 jvz SETWV, COMPOSE */ #define COURIERB 0 /* y COURIERB - Courier hinting */ #define RB 1 /* y dy RB -- add horizontal hinting pair at y and y+dy */ #define COMPOSE 2 /* subr# COMPOSE -- draw a library element. Pops only one arg from stack. */ #define RY 3 /* x dx RY -- add vertical hinting pair at x and x+dx */ #define VMT 4 /* dy VMT -- equivalent to 0 dy RMT */ #define RDT 5 /* dx dy RDT -- relative lineto */ #define HDT 6 /* dx HDT -- equivalent to dx 0 RDT */ #define VDT 7 /* dy VDT -- equivalent to 0 dy RDT */ #define RCT 8 /* dx1 dy1 dx2 dy2 dx3 dy3 RCT -- relative curveto */ #define CP 9 /* closepath */ #define DOSUB 10 /* i DOSUB -- execute Subrs[i] */ #define RET 11 /* RET -- return from DOSUB call */ #define ESC 12 /* escape - see special operators below */ #define SBX 13 /* SBX */ #define ED 14 /* end of a character */ #define MT 15 /* x y MT - non-relative - Courier and some space chars only */ #define DT 16 /* x y DT - non-relative - Courier only */ #define CT 17 /* x1 y1 x2 y2 x3 y3 CT - non-relative - Courier only */ #define OMIN 18 /* MIN is a macro in THINK C */ /* a b MIN -> (min(a,b)) */ #define ST 19 /* ST -- stroke (graphics state operator) */ #define NP 20 /* NP - Courier newpath */ #define RMT 21 /* dx dy RMT -- relative moveto */ #define HMT 22 /* dx HMT -- equivalent to dx 0 RMT */ #define SLC 23 /* i SLC -- setlinecap (graphics state operator) */ #define MUL 24 /* a b MUL -> (a*b) */ #define STKWDTH 25 /* font constant for strokewidth */ #define BSLN 26 /* font constant for baseline */ #define CPHGHT 27 /* font constant for cap height */ #define BOVER 28 /* font constant for baseline overshoot */ #define XHGHT 29 /* font constant for xheight */ #define VHCT 30 /* dy1 dx2 dy2 dx3 VHCT -- equivalent to 0 dy1 dx2 dy2 dx3 0 RCT */ #define HVCT 31 /* dx1 dx2 dy2 dy3 HVCT -- equivalent to dx1 0 dx2 dy2 0 dy3 RCT */ /* the operators above can be used in the charstring and thus evaluated by the PS interpreter. The following operators never are placed in the charstring, but are found in bez files. */ #define SNC 38 /* start new colors */ #define ENC 39 /* end new colors */ #define SC 40 /* start character */ #define FLX 41 /* flex => translated to a 0 DOSUB in CharString */ #define SOL 42 /* start of loop = subpath => translated to FL CharString op */ #define EOL 43 /* end of loop = subpath => translated to FL CharString op */ #define ID 44 /* path id number */ /* escape operators - all of these can go into the charstrings */ #define FL 0 /* FL -- flip switch for "offset locking" - e.g. insuring > 0 pixel separation between two paths of an i */ #define RM 1 /* x0 dx0 x1 dx1 x2 dx2 RM -- add 3 equal spaced vertical hinting pairs */ #define RV 2 /* y0 dy0 y1 dy1 y2 dy2 RV -- add 3 equal spaced horizontal hinting pairs */ #define FI 3 /* FI -- fill (graphics state operator) */ #define ARC 4 /* cx cy r a1 a2 ARC - Courier only */ #define SLW 5 /* w SLW -- setlinewidth (graphics state operator) */ #define CC 6 /* asb dx dy ccode acode CC -- composite character definition */ #define SB 7 /* */ #define SSPC 8 /* llx lly urx ury SSPC -- start self painting character - Courier only */ #define ESPC 9 /* ESPC -- end self painting character - Courier only */ #define ADD 10 /*a b ADD -> (a+b) */ #define SUB 11 /* a b SUB -> (a-b) */ #define DIV 12 /* a b DIV -> (a / b), used when need a non-integer value */ #define OMAX 13 /* MAX is a macro in THINK C */ /* a b MAX -> (max(a,b)) */ #define NEG 14 /* a NEG -> (-a) */ #define IFGTADD 15 /* a b c d IFGTADD -> (b > c)? (a+d) : (a) */ #define DO 16 /* a1 ... an n i DO -- push an on PS stack; ... push a1 on PS stack; begin systemdict; begin fontdict; execute OtherSubrs[i]; end; end; */ #define POP 17 /* pop a number from top of PS stack and push it on fontbuild stack used in communicating with OtherSubrs */ #define DSCND 18 /* font constant for descender */ #define SETWV 19 /* set weight vector */ #define OVRSHT 20 /* font constant for overshoot */ #define SLJ 21 /* i SLJ -- setlinejoin (graphics state operator) */ #define XOVR 22 /* font constant for xheight overshoot */ #define CAPOVR 23 /* font constant for cap overshoot */ #define AOVR 24 /* font constant for ascender overshoot */ #define HLFSW 25 /* font constant for half strokewidth */ #define ROUNDSW 26 /* w RNDSW -> (RoundSW(w)) -- round stroke width */ #define ARCN 27 /* cx cy r a1 a2 ARCN - Courier only */ #define EXCH 28 /* a b EXCH -> b a */ #define INDEX 29 /* an ... a0 i INDX -> an ... a0 ai */ #define CRB 30 /* y dy CRB - Courier rb */ #define CRY 31 /* x dx CRY - Courier ry */ #define PUSHCP 32 /* PUSHCP -> cx cy -- push the current position onto font stack (graphics state operator) */ #define POPCP 33 /* cx cy POPCP -- set current position from font stack (graphics state operator) */ #define ESCVAL 100 psautohint-2.3.0/libpsautohint/src/optable.c000066400000000000000000000036331401523215600211640ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ /* optable.c - initializes table of known PostScript operators. */ #include "optable.h" #include "ac.h" #include "opcodes.h" /* Not all of the operators from opcodes.h are initialized here; in particular, some that seemed to be needed just for Courier were ignored. Operators for which the encoding value has been incremented by ESCVAL must be preceded by an escape when written to the output charstring. */ /* This defines an element of the table used to translate ASCII operand names to the binary encoded equivalents. */ static struct { int16_t op; char* operator; } op_table[] = { { VMT, "vmt" }, { RDT, "rdt" }, { HDT, "hdt" }, { VDT, "vdt" }, { RCT, "rct" }, { CP, "cp" }, { RET, "ret" }, { ESC, "esc" }, { SBX, "sbx" }, { ED, "ed" }, { MT, "mt" }, { CT, "ct" }, { DT, "dt" }, { RMT, "rmt" }, { HMT, "hmt" }, { VHCT, "vhct" }, { HVCT, "hvct" }, /* special non-charstring perators start here */ { SC, "sc" }, { ID, "id" }, /* escape perators start here */ { CC + ESCVAL, "cc" }, { 0, NULL } }; char* GetOperator(int16_t op) { indx ix; for (ix = 0; op_table[ix].operator!= NULL; ix++) { if (op == op_table[ix].op) { return op_table[ix].operator; } } LogMsg(LOGERROR, NONFATALERROR, "The opcode: %d is invalid.\n", op); return ""; } psautohint-2.3.0/libpsautohint/src/optable.h000066400000000000000000000007061401523215600211670ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #ifndef LIBPSAUTOHINT_SRC_OPTABLE_H_ #define LIBPSAUTOHINT_SRC_OPTABLE_H_ #include "ac.h" #include "opcodes.h" char* GetOperator(int16_t op); #endif /* LIBPSAUTOHINT_SRC_OPTABLE_H_ */ psautohint-2.3.0/libpsautohint/src/pick.c000066400000000000000000000306341401523215600204650ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #include "bbox.h" static HintVal *Vrejects, *Hrejects; void InitPick(int32_t reason) { switch (reason) { case STARTUP: case RESTART: Vrejects = Hrejects = NULL; } } #define LtPruneB(val) ((val) < FixOne && ((val) << 10) < gPruneB) static bool ConsiderPicking(Fixed bestSpc, Fixed bestVal, HintVal* hintList, Fixed prevBestVal) { if (bestSpc > 0) return true; if (hintList == NULL) return bestVal >= gPruneD; if (bestVal > gPruneA) return true; if (LtPruneB(bestVal)) return false; return (bestVal < FIXED_MAX / gPruneC) ? (prevBestVal <= bestVal * gPruneC) : (prevBestVal / gPruneC <= bestVal); } void PickVVals(HintVal* valList) { HintVal *hintList, *rejectList, *vlist, *nxt; Fixed bestVal = 0, prevBestVal; hintList = rejectList = NULL; prevBestVal = 0; while (true) { HintVal *prev, *bestPrev, *best; Fixed lft, rght; vlist = valList; prev = bestPrev = best = NULL; while (vlist != NULL) { if ((best == NULL || CompareValues(vlist, best, spcBonus, 0)) && ConsiderPicking(vlist->vSpc, vlist->vVal, hintList, prevBestVal)) { best = vlist; bestPrev = prev; bestVal = vlist->vVal; } prev = vlist; vlist = vlist->vNxt; } if (best == NULL) break; /* no more */ if (bestPrev == NULL) valList = best->vNxt; else bestPrev->vNxt = best->vNxt; /* have now removed best from valList */ best->vNxt = hintList; /* add best to front of list */ hintList = best; prevBestVal = bestVal; lft = best->vLoc1 - gBandMargin; rght = best->vLoc2 + gBandMargin; /* remove segments from valList that overlap lft..rght */ vlist = valList; prev = NULL; while (vlist != NULL) { Fixed vlft = vlist->vLoc1; Fixed vrght = vlist->vLoc2; if ((vlft <= rght) && (vrght >= lft)) { nxt = vlist->vNxt; vlist->vNxt = rejectList; rejectList = vlist; vlist = nxt; if (prev == NULL) valList = vlist; else prev->vNxt = vlist; } else { prev = vlist; vlist = vlist->vNxt; } } } vlist = valList; /* move rest of valList to rejectList */ while (vlist != NULL) { nxt = vlist->vNxt; vlist->vNxt = rejectList; rejectList = vlist; vlist = nxt; } if (hintList == NULL) HintVBnds(); gVHinting = hintList; Vrejects = rejectList; } static bool InSerifBand(Fixed y0, Fixed y1, int32_t n, Fixed* p) { int32_t i; if (n <= 0) return false; y0 = -y0; y1 = -y1; if (y0 > y1) { Fixed tmp = y1; y1 = y0; y0 = tmp; } for (i = 0; i < n; i += 2) if (p[i] <= y0 && p[i + 1] >= y1) return true; return false; } static bool ConsiderValForSeg(HintVal* val, HintSeg* seg, Fixed loc, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool primary) { if (primary && val->vSpc > 0.0) return true; if (InBlueBand(loc, nb, b)) return true; if (val->vSpc <= 0.0 && InSerifBand(seg->sMax, seg->sMin, ns, s)) return false; if (LtPruneB(val->vVal)) return false; return true; } static HintVal* FndBstVal(HintSeg* seg, bool seg1Flg, HintVal* cList, HintVal* rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool locFlg, bool hFlg) { Fixed loc, vloc; HintVal *best, *vList; HintSeg* vseg; best = NULL; loc = seg->sLoc; vList = cList; while (true) { HintVal* initLst = vList; while (vList != NULL) { if (seg1Flg) { vseg = vList->vSeg1; vloc = vList->vLoc1; } else { vseg = vList->vSeg2; vloc = vList->vLoc2; } if (abs(loc - vloc) <= gMaxMerge && (locFlg ? !vList->vGhst : (vseg == seg || CloseSegs(seg, vseg, !hFlg))) && (best == NULL || (vList->vVal == best->vVal && vList->vSpc == best->vSpc && vList->initVal > best->initVal) || CompareValues(vList, best, spcBonus, 3)) && /* last arg is "ghostshift" that penalizes ghost values */ /* ghost values are set to 20 */ /* so ghostshift of 3 means prefer nonghost if its value is > (20 >> 3) */ ConsiderValForSeg(vList, seg, loc, nb, b, ns, s, true)) best = vList; vList = vList->vNxt; } if (initLst == rList) break; vList = rList; } ReportFndBstVal(seg, best, hFlg); return best; } #define FixSixteenth (0x10) static HintVal* FindBestValForSeg(HintSeg* seg, bool seg1Flg, HintVal* cList, HintVal* rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool hFlg) { HintVal *best, *nonghst, *ghst = NULL; best = FndBstVal(seg, seg1Flg, cList, rList, nb, b, ns, s, false, hFlg); if (best != NULL && best->vGhst) { nonghst = FndBstVal(seg, seg1Flg, cList, rList, nb, b, ns, s, true, hFlg); /* If nonghst hints are "better" use it instead of ghost band. */ if (nonghst != NULL && nonghst->vVal >= FixInt(2)) { /* threshold must be greater than 1.004 for ITC Garamond Ultra "q" */ ghst = best; best = nonghst; } } if (best != NULL) { if (best->vVal < FixSixteenth && (ghst == NULL || ghst->vVal < FixSixteenth)) best = NULL; /* threshold must be > .035 for Monotype/Plantin/Bold Thorn and < .08 for Bookman2/Italic asterisk */ else best->pruned = false; } return best; } static bool MembValList(HintVal* val, HintVal* vList) { while (vList != NULL) { if (val == vList) return true; vList = vList->vNxt; } return false; } static HintVal* PrevVal(HintVal* val, HintVal* vList) { HintVal* prev; if (val == vList) return NULL; prev = vList; while (true) { vList = vList->vNxt; if (vList == NULL) { LogMsg(LOGERROR, NONFATALERROR, "Malformed value list."); return NULL; } if (vList == val) return prev; prev = vList; } return NULL; } static void FindRealVal(HintVal* vlist, Fixed top, Fixed bot, HintSeg** pseg1, HintSeg** pseg2) { while (vlist != NULL) { if (vlist->vLoc2 == top && vlist->vLoc1 == bot && !vlist->vGhst) { *pseg1 = vlist->vSeg1; *pseg2 = vlist->vSeg2; return; } vlist = vlist->vNxt; } } void PickHVals(HintVal* valList) { HintVal *vlist, *hintList, *rejectList, *bestPrev, *prev, *best, *nxt; Fixed bestVal = 0, prevBestVal; Fixed bot, top, vtop, vbot; HintVal* newBst; HintSeg *seg1, *seg2; hintList = rejectList = NULL; prevBestVal = 0; while (true) { vlist = valList; prev = bestPrev = best = NULL; while (vlist != NULL) { if ((best == NULL || CompareValues(vlist, best, spcBonus, 0)) && ConsiderPicking(vlist->vSpc, vlist->vVal, hintList, prevBestVal)) { best = vlist; bestPrev = prev; bestVal = vlist->vVal; } prev = vlist; vlist = vlist->vNxt; } if (best != NULL) { seg1 = best->vSeg1; seg2 = best->vSeg2; if (best->vGhst) { /* find float segments at same loc as best */ FindRealVal(valList, best->vLoc2, best->vLoc1, &seg1, &seg2); } if (seg1->sType == sGHOST) { /*newBst = FindBestValForSeg(seg2, false, valList, NULL, 0, (Fixed *)NIL, 0, (Fixed *)NIL, true);*/ newBst = seg2->sLnk; if (newBst != NULL && newBst != best && MembValList(newBst, valList)) { best = newBst; bestPrev = PrevVal(best, valList); } } else if (seg2->sType == sGHOST) { /*newBst = FindBestValForSeg(seg1, true, valList, NULL, 0, (Fixed *)NIL, 0, (Fixed *)NIL, true); */ newBst = seg2->sLnk; if (newBst != NULL && newBst != best && MembValList(newBst, valList)) { best = newBst; bestPrev = PrevVal(best, valList); } } } if (best == NULL) goto noMore; prevBestVal = bestVal; if (bestPrev == NULL) valList = best->vNxt; else bestPrev->vNxt = best->vNxt; /* have now removed best from valList */ best->vNxt = hintList; hintList = best; /* add best to front of list */ bot = best->vLoc1; top = best->vLoc2; /* The next if statement was added so that ghost bands are given 0 width for doing the conflict tests for bands too close together. This was a problem in Minion/DisplayItalic onequarter and onehalf. */ if (best->vGhst) { /* collapse width */ if (best->vSeg1->sType == sGHOST) bot = top; else top = bot; } bot += gBandMargin; top -= gBandMargin; /* remove segments from valList that overlap bot..top */ vlist = valList; prev = NULL; while (vlist != NULL) { vbot = vlist->vLoc1; vtop = vlist->vLoc2; /* The next if statement was added so that ghost bands are given 0 width for doing the conflict tests for bands too close together. */ if (vlist->vGhst) { /* collapse width */ if (vlist->vSeg1->sType == sGHOST) vbot = vtop; else vtop = vbot; } if ((vbot >= top) && (vtop <= bot)) { nxt = vlist->vNxt; vlist->vNxt = rejectList; rejectList = vlist; vlist = nxt; if (prev == NULL) valList = vlist; else prev->vNxt = vlist; } else { prev = vlist; vlist = vlist->vNxt; } } } noMore: vlist = valList; /* move rest of valList to rejectList */ while (vlist != NULL) { nxt = vlist->vNxt; vlist->vNxt = rejectList; rejectList = vlist; vlist = nxt; } if (hintList == NULL) HintHBnds(); gHHinting = hintList; Hrejects = rejectList; } static void FindBestValForSegs(HintSeg* sList, bool seg1Flg, HintVal* cList, HintVal* rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool hFlg) { HintVal* best; while (sList != NULL) { best = FindBestValForSeg(sList, seg1Flg, cList, rList, nb, b, ns, s, hFlg); sList->sLnk = best; sList = sList->sNxt; } } static void SetPruned(void) { HintVal* vL = gValList; while (vL != NULL) { vL->pruned = true; vL = vL->vNxt; } } void FindBestHVals(void) { SetPruned(); FindBestValForSegs(topList, false, gValList, NULL, gLenTopBands, gTopBands, 0, NULL, true); FindBestValForSegs(botList, true, gValList, NULL, gLenBotBands, gBotBands, 0, NULL, true); DoPrune(); } void FindBestVVals(void) { SetPruned(); FindBestValForSegs(leftList, true, gValList, NULL, 0, NULL, gNumSerifs, gSerifs, false); FindBestValForSegs(rightList, false, gValList, NULL, 0, NULL, gNumSerifs, gSerifs, false); DoPrune(); } psautohint-2.3.0/libpsautohint/src/psautohint.c000066400000000000000000000147301401523215600217340ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" #include "fontinfo.h" #include "psautohint.h" #include "version.h" ACBuffer* gBezOutput = NULL; static jmp_buf aclibmark; /* to handle exit() calls in the library version*/ ACLIB_API void AC_SetMemManager(void* ctxptr, AC_MEMMANAGEFUNCPTR func) { setAC_memoryManager(ctxptr, func); } ACLIB_API void AC_SetReportCB(AC_REPORTFUNCPTR reportCB) { gLibReportCB = reportCB; } ACLIB_API void AC_SetReportStemsCB(AC_REPORTSTEMPTR hstemCB, AC_REPORTSTEMPTR vstemCB, unsigned int allStems, void* userData) { gAllStems = allStems; gAddHStemCB = hstemCB; gAddVStemCB = vstemCB; gAddStemUserData = userData; gDoStems = true; gAddGlyphExtremesCB = NULL; gAddStemExtremesCB = NULL; gDoAligns = false; } ACLIB_API void AC_SetReportZonesCB(AC_REPORTZONEPTR glyphCB, AC_REPORTZONEPTR stemCB, void* userData) { gAddGlyphExtremesCB = glyphCB; gAddStemExtremesCB = stemCB; gAddExtremesUserData = userData; gDoAligns = true; gAddHStemCB = NULL; gAddVStemCB = NULL; gDoStems = false; } ACLIB_API void AC_SetReportRetryCB(AC_RETRYPTR retryCB, void* userData) { gReportRetryCB = retryCB; gReportRetryUserData = userData; } /* * This is our error handler, it gets called by LogMsg() whenever the log level * is LOGERROR (see logging.c for the exact condition). The call to longjmp() * will transfer the control to the point where setjmp() is called below. So * effectively whenever LogMsg() is called for an error the execution of the * calling function will end and we will return back to AutoHintString(). */ static int error_handler(int16_t code) { if (code == FATALERROR || code == NONFATALERROR) longjmp(aclibmark, -1); else longjmp(aclibmark, 1); return 0; /* we don't actually ever get here */ } ACLIB_API int AutoHintString(const char* srcbezdata, const char* fontinfodata, ACBuffer* outbuffer, int allowEdit, int allowHintSub, int roundCoords) { int value, result; ACFontInfo* fontinfo = NULL; if (!srcbezdata) return AC_InvalidParameterError; fontinfo = ParseFontInfo(fontinfodata); set_errorproc(error_handler); value = setjmp(aclibmark); /* We will return here whenever an error occurs during the execution of * AutoHint(), or after it finishes execution. See the error_handler * comments above and below. */ if (value == -1) { /* a fatal error occurred somewhere. */ FreeFontInfo(fontinfo); return AC_FatalError; } else if (value == 1) { /* AutoHint was called successfully */ FreeFontInfo(fontinfo); return AC_Success; } gBezOutput = outbuffer; result = AutoHint(fontinfo, /* font info */ srcbezdata, /* input glyph */ allowHintSub, /* extrahint */ allowEdit, /* changeGlyphs */ roundCoords); /* result == true is good */ /* The following call to error_handler() always returns control to just * after the setjmp() function call above, but with value set to 1 if * success, or -1 if not */ error_handler((result == true) ? OK : NONFATALERROR); /* Shouldn't get here */ return AC_UnknownError; } ACLIB_API int AutoHintStringMM(const char** srcbezdata, int nmasters, const char** masters, ACBuffer** outbuffers) { /* Only the master with index 'hintsMasterIx' needs to be hinted. * This function expects that the master with index 'hintsMasterIx' has * already been hinted with AutoHint(). * * The hints for the others masters are derived a very simple process. When * the first master was hinted, the logic recorded the path element index * for the path element which sets the inner or outer edge of each hint, * and whether it is the endpoint or startpoint which sets the edge. For * each other master, the logic gets the path element for the current * master using the same path element index as in the first master, and * uses the end or start point of that path element to set the edge in the * current master. * * Some code notes: The original hinting pass in AutoHintString() on the * first master records the path indicies for each path element that sets a * hint edge, and whether it is a start or end point; this stored in the * hintElt structures in the first master. There is a hintElt for the main * (default) hints at the start of the charstring, and there is hintElt * structure attached to each path element which triggers a new hint set. * The function charpath.c::ReadandAssignHints(), called from * charpath.c::MergeGlyphPaths(), then copies all the hintElts to the * current master main or path elements. (This actually happens in * charpath.c::InsertHint().) */ int value, result; if (!srcbezdata) return AC_InvalidParameterError; set_errorproc(error_handler); value = setjmp(aclibmark); /* We will return here whenever an error occurs during the execution of * AutoHint(), or after it finishes execution. See the error_handler * comments above and below. */ if (value == -1) { /* a fatal error occurred somewhere. */ return AC_FatalError; } else if (value == 1) { /* AutoHint was called successfully */ return AC_Success; } /* result == true is good */ result = MergeGlyphPaths(srcbezdata, nmasters, masters, outbuffers); /* The following call to error_handler() always returns control to just * after the setjmp() function call above, but with value set to 1 if * success, or -1 if not */ error_handler((result == true) ? OK : NONFATALERROR); /* Shouldn't get here */ return AC_UnknownError; } ACLIB_API void AC_initCallGlobals(void) { gLibReportCB = NULL; gAddGlyphExtremesCB = NULL; gAddStemExtremesCB = NULL; gDoAligns = false; gAddHStemCB = NULL; gAddVStemCB = NULL; gDoStems = false; gAddStemUserData = NULL; gAllStems = 0; gReportRetryCB = NULL; gReportRetryUserData = NULL; } ACLIB_API const char* AC_getVersion(void) { return PSAUTOHINT_VERSION; } psautohint-2.3.0/libpsautohint/src/read.c000066400000000000000000000322141401523215600204460ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" #include "charpath.h" #include "opcodes.h" char gGlyphName[MAX_GLYPHNAME_LEN]; static Fixed currentx, currenty; /* used to calculate absolute coordinates */ static Fixed tempx, tempy; /* used to calculate relative coordinates */ #define STKMAX (20) static Fixed stk[STKMAX]; static int32_t stkindex; static bool flex, startchar; static bool forMultiMaster, includeHints; /* Reading file for comparison of multiple master data and hint information. Reads into GlyphPathElt structure instead of PathElt. */ static Fixed Pop(void) { if (stkindex <= 0) { LogMsg(LOGERROR, NONFATALERROR, "Stack underflow while reading glyph."); } stkindex--; return stk[stkindex]; } static void Push(Fixed r) { if (stkindex >= STKMAX) { LogMsg(LOGERROR, NONFATALERROR, "Stack overflow while reading glyph."); return; } stk[stkindex] = r; stkindex++; } static void Pop2(void) { Pop(); Pop(); } static void PopPCd(Cd* pcd) { pcd->y = Pop(); pcd->x = Pop(); } #define DoDelta(dx, dy) \ currentx += (dx); \ currenty += (dy) static PathElt* AppendElement(int32_t etype) { PathElt* e; e = (PathElt*)Alloc(sizeof(PathElt)); e->type = (int16_t)etype; if (gPathEnd != NULL) { gPathEnd->next = e; e->prev = gPathEnd; } else gPathStart = e; gPathEnd = e; return e; } static void RDcurveto(Cd c1, Cd c2, Cd c3) { if (!forMultiMaster) { PathElt* new; new = AppendElement(CURVETO); new->x1 = c1.x; new->y1 = -c1.y; new->x2 = c2.x; new->y2 = -c2.y; new->x3 = c3.x; new->y3 = -c3.y; } else { GlyphPathElt* new; new = AppendGlyphPathElement(RCT); new->x = tempx; new->y = tempy; new->x1 = c1.x; new->y1 = c1.y; new->x2 = c2.x; new->y2 = c2.y; new->x3 = c3.x; new->y3 = c3.y; new->rx1 = c1.x - tempx; new->ry1 = c1.y - tempy; new->rx2 = c2.x - c1.x; new->ry2 = c2.y - c1.y; new->rx3 = c3.x - c2.x; new->ry3 = c3.y - c2.y; if (flex) new->isFlex = true; } } static void RDmtlt(int32_t etype) { if (!forMultiMaster) { PathElt* new; new = AppendElement(etype); new->x = currentx; new->y = -currenty; return; } else { GlyphPathElt* new; new = AppendGlyphPathElement(etype == LINETO ? RDT : RMT); new->x = currentx; new->y = currenty; new->rx = tempx; new->ry = tempy; } } #define RDlineto() RDmtlt(LINETO) #define RDmoveto() RDmtlt(MOVETO) static void psRMT(void) { Cd c; PopPCd(&c); if (flex) return; tempx = c.x; tempy = c.y; DoDelta(c.x, c.y); RDmoveto(); } static void Rct(Cd c1, Cd c2, Cd c3) { tempx = currentx; tempy = currenty; DoDelta(c1.x, c1.y); c1.x = currentx; c1.y = currenty; DoDelta(c2.x, c2.y); c2.x = currentx; c2.y = currenty; DoDelta(c3.x, c3.y); c3.x = currentx; c3.y = currenty; RDcurveto(c1, c2, c3); } static void psCP(void) { if (!forMultiMaster) AppendElement(CLOSEPATH); else AppendGlyphPathElement(CP); } static void psMT(void) { Cd c; c.y = Pop(); c.x = Pop(); tempx = c.x - currentx; tempy = c.y - currenty; currenty = c.y; currentx = c.x; RDmoveto(); } static void psDT(void) { Cd c; c.y = Pop(); c.x = Pop(); tempx = c.x - currentx; tempy = c.y - currenty; currenty = c.y; currentx = c.x; RDlineto(); } static void psCT(void) { Cd c1, c2, c3; tempx = currentx; tempy = currenty; PopPCd(&c3); PopPCd(&c2); PopPCd(&c1); RDcurveto(c1, c2, c3); } static void psFLX(void) { Cd c0, c1, c2, c3, c4, c5; int32_t i; for (i = 0; i < 5; i++) Pop(); PopPCd(&c5); PopPCd(&c4); PopPCd(&c3); PopPCd(&c2); PopPCd(&c1); PopPCd(&c0); Rct(c0, c1, c2); Rct(c3, c4, c5); flex = false; } static void ReadHintInfo(char nm, const char* str) { Cd c0; int16_t hinttype = nm == 'y' ? RY : nm == 'b' ? RB : nm == 'm' ? RM + ESCVAL : RV + ESCVAL; int32_t elt1, elt2; PopPCd(&c0); c0.y += c0.x; /* make absolute */ /* Look for comment of path elements used to determine this band. */ if (sscanf(str, " %% %d %d", &elt1, &elt2) != 2) { LogMsg(WARNING, NONFATALERROR, "Extra hint information required for blended fonts is " "not in glyph. Please re-hint using the latest software. " "Hints will not be included in this glyph."); gAddHints = false; includeHints = false; } else SetHintsElt(hinttype, &c0, elt1, elt2, (bool)!startchar); } /*Used instead of StringEqual to keep ac from cloberring source string*/ static int isPrefix(const char* s, const char* pref) { while (*pref) { if (*pref != *s) return 0; pref++; s++; } return 1; } static void DoName(const char* nm, const char* buff, int len) { switch (len) { case 2: switch (nm[0]) { case 'c': /* ct, cp */ switch (nm[1]) { case 't': psCT(); break; case 'p': psCP(); break; default: goto badFile; } break; case 'm': /* mt */ if (nm[1] != 't') goto badFile; psMT(); break; case 'd': /* dt */ if (nm[1] != 't') goto badFile; psDT(); break; case 's': /* sc */ if (nm[1] != 'c') goto badFile; startchar = true; break; case 'e': /* ed */ if (nm[1] != 'd') goto badFile; break; case 'r': /* rm, rv, ry, rb */ if (includeHints) ReadHintInfo(nm[1], buff); else Pop2(); break; default: goto badFile; } break; case 3: switch (nm[0]) { case 'r': /* rmt */ if (nm[1] != 'm' || nm[2] != 't') goto badFile; psRMT(); break; case 's': /* snc */ case 'e': /* enc */ switch (nm[1]) { case 'n': if (nm[2] != 'c') goto badFile; break; default: goto badFile; } break; case 'f': /* flx */ if (nm[1] == 'l' && nm[2] == 'x') psFLX(); else goto badFile; break; default: goto badFile; } break; case 7: switch (nm[6]) { case '1': /* preflx1 */ case '2': /* preflx2 */ if (nm[0] != 'p' || nm[1] != 'r' || nm[2] != 'e' || nm[3] != 'f' || nm[4] != 'l' || nm[5] != 'x') goto badFile; flex = true; break; case 'r': /* endsubr */ if (!isPrefix(nm, "endsubr")) goto badFile; break; default: goto badFile; } break; case 9: switch (nm[0]) { case 'b': /* beginsubr */ if (!isPrefix(nm, "beginsubr")) goto badFile; break; case 'n': /* newhints */ if (!isPrefix(nm, "newcolors")) goto badFile; break; default: goto badFile; } break; default: goto badFile; } return; badFile : { char op[80]; if (len > 79) len = 79; strncpy(op, nm, len); op[len] = 0; LogMsg(LOGERROR, NONFATALERROR, "Bad file format. Unknown operator: %s.", op); } } static void ParseString(const char* s) { const char* s0; char c; const char* c0; bool neg = false; bool isReal; float rval; int32_t val = 0; Fixed r; gPathStart = gPathEnd = NULL; gGlyphName[0] = '\0'; while (true) { c = *s++; nxtChar: switch (c) { case '-': /* negative number */ neg = true; val = 0; goto rdnum; case '%': /* comment */ if (gGlyphName[0] == '\0') { unsigned int end = 0; while (*s == ' ') s++; while (s[end] && (s[end] != ' ') && (s[end] != '\r') && (s[end] != '\n')) end++; if (end >= MAX_GLYPHNAME_LEN) { LogMsg(LOGERROR, NONFATALERROR, "Bad input data. Glyph name is greater than " "%d chars.", MAX_GLYPHNAME_LEN); end = MAX_GLYPHNAME_LEN - 1; } strncpy(gGlyphName, s, end); gGlyphName[end] = '\0'; } while (*s && (*s != '\n') && (*s != '\r')) { s++; } continue; case ' ': continue; case '\t': continue; case '\n': continue; case '\r': continue; case 0: /* end of file */ if (stkindex != 0) { LogMsg(LOGERROR, NONFATALERROR, "Bad input data. Numbers left on stack " "at end of glyph."); } return; default: if (c >= '0' && c <= '9') { neg = false; val = c - '0'; goto rdnum; } if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { s0 = s - 1; while (true) { c = *s++; if ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\0')) break; if (c == 0) break; } DoName(s0, s, (int)(s - s0 - 1)); if (c == '\0') s--; continue; } LogMsg(LOGERROR, NONFATALERROR, "Unexpected character: %c", c); } rdnum: isReal = false; c0 = s - 1; while (true) { c = *s++; if (c == '.') isReal = true; else if (c >= '0' && c <= '9') val = val * 10 + (c - '0'); else if ((c == ' ') || (c == '\t')) { if (isReal) { rval = strtod(c0, NULL); /* Autohint only supports 2 digits of decimal precision. */ rval = roundf(rval * 100) / 100; /* do not need to use 'neg' to negate the value, as c0 * string includes the minus sign.*/ r = FixReal(rval); /* convert to Fixed */ } else { if (neg) val = -val; r = FixInt(val); /* convert to Fixed */ } Push(r); goto nxtChar; } else { LogMsg(LOGERROR, NONFATALERROR, "Illegal number terminator while reading glyph."); return; } } /*end while true */ } } bool ReadGlyph(const char* srcglyph, bool forBlendData, bool readHints) { if (!srcglyph) return false; currentx = currenty = tempx = tempy = stkindex = 0; flex = startchar = false; forMultiMaster = forBlendData; includeHints = readHints; ParseString(srcglyph); return true; } psautohint-2.3.0/libpsautohint/src/report.c000066400000000000000000000247251401523215600210560ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" double FixToDbl(Fixed f) { float r; acfixtopflt(f, &r); return (double)r; } void ReportAddFlex(void) { if (gHasFlex) return; gHasFlex = true; LogMsg(INFO, OK, "added flex operators to this glyph."); } void ReportLinearCurve(PathElt* e, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { if (gAutoLinearCurveFix) { e->type = LINETO; e->x = e->x3; e->y = e->y3; LogMsg(INFO, OK, "Curve from %g %g to %g %g was changed to a line.", FixToDbl(x0), FixToDbl(-y0), FixToDbl(x1), FixToDbl(-y1)); } else { LogMsg(INFO, OK, "Curve from %g %g to %g %g should be changed to a line.", FixToDbl(x0), FixToDbl(-y0), FixToDbl(x1), FixToDbl(-y1)); } } static void ReportNonHVError(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char* s) { Fixed dx, dy; y0 = -y0; y1 = -y1; dx = x0 - x1; dy = y0 - y1; if (abs(dx) > FixInt(10) || abs(dy) > FixInt(10) || FTrunc(dx * dx) + FTrunc(dy * dy) > FixInt(100)) { LogMsg(INFO, OK, "The line from %g %g to %g %g is not exactly %s.", FixToDbl(x0), FixToDbl(y0), FixToDbl(x1), FixToDbl(y1), s); } } void ReportNonHError(Fixed x0, Fixed y0, Fixed x1, Fixed y1) { ReportNonHVError(x0, y0, x1, y1, "horizontal"); } void ReportNonVError(Fixed x0, Fixed y0, Fixed x1, Fixed y1) { ReportNonHVError(x0, y0, x1, y1, "vertical"); } void ExpectedMoveTo(PathElt* e) { char* s; switch (e->type) { case LINETO: s = (char*)"lineto"; break; case CURVETO: s = (char*)"curveto"; break; case CLOSEPATH: s = (char*)"closepath"; break; default: LogMsg(LOGERROR, NONFATALERROR, "Malformed path list."); return; } LogMsg(LOGERROR, NONFATALERROR, "Glyph path has a %s where a moveto was expected. " "The data is probably truncated.", s); } void ReportMissingClosePath(void) { LogMsg(LOGERROR, NONFATALERROR, "Missing closepath. The data is probably truncated."); } void ReportTryFlexNearMiss(Fixed x0, Fixed y0, Fixed x2, Fixed y2) { LogMsg(WARNING, OK, "Curves from %g %g to %g %g near miss for adding flex.", FixToDbl(x0), FixToDbl(-y0), FixToDbl(x2), FixToDbl(-y2)); } void ReportTryFlexError(bool CPflg, Fixed x, Fixed y) { LogMsg(LOGERROR, OK, CPflg ? "Please move closepath from %g %g so can add flex." : "Please remove zero length element at %g %g so can add flex.", FixToDbl(x), FixToDbl(-y)); } void ReportSplit(PathElt* e) { Fixed x0, y0, x1, y1; GetEndPoints(e, &x0, &y0, &x1, &y1); LogMsg(INFO, OK, "the element that goes from %g %g to %g %g has been split.", FixToDbl(x0), FixToDbl(-y0), FixToDbl(x1), FixToDbl(-y1)); } void ReportPossibleLoop(PathElt* e) { Fixed x0, y0, x1, y1; if (e->type == MOVETO) e = GetClosedBy(e); GetEndPoints(e, &x0, &y0, &x1, &y1); LogMsg(LOGERROR, OK, "Possible loop in element that goes from %g %g to %g %g." " Please check.", FixToDbl(x0), FixToDbl(-y0), FixToDbl(x1), FixToDbl(-y1)); } void ReportRemFlare(PathElt* e, PathElt* e2, bool hFlg, int32_t i) { Fixed ex1, ey1, ex2, ey2; GetEndPoint(e, &ex1, &ey1); GetEndPoint(e2, &ex2, &ey2); LogMsg(INFO, OK, "Removed %s flare at %g %g by %g %g : %d.", hFlg ? "horizontal" : "vertical", FixToDbl(ex1), FixToDbl(-ey1), FixToDbl(ex2), FixToDbl(-ey2), i); } void ReportRemConflict(PathElt* e) { Fixed ex, ey; GetEndPoint(e, &ex, &ey); LogMsg(INFO, OK, "Removed conflicting hints at %g %g.", FixToDbl(ex), FixToDbl(-ey)); } void ReportRemShortHints(Fixed ex, Fixed ey) { LogMsg(INFO, OK, "Removed hints from short element at %g %g.", FixToDbl(ex), FixToDbl(-ey)); } #define VAL(v) ((v) >= FixInt(100000) ? FTrunc(v) : FixToDbl(v)) static void ShwHV(HintVal* val) { Fixed bot, top; bot = -val->vLoc1; top = -val->vLoc2; LogMsg(LOGDEBUG, OK, "b %g t %g v %g s %g%s", FixToDbl(bot), FixToDbl(top), VAL(val->vVal), FixToDbl(val->vSpc), val->vGhst ? " G" : ""); } void ShowHVal(HintVal* val) { Fixed l1, l2, r1, r2; Fixed bot, top; HintSeg* seg = val->vSeg1; if (seg == NULL) { ShwHV(val); return; } bot = -val->vLoc1; top = -val->vLoc2; l1 = seg->sMin; r1 = seg->sMax; seg = val->vSeg2; l2 = seg->sMin; r2 = seg->sMax; LogMsg(LOGDEBUG, OK, "b %g t %g v %g s %g%s l1 %g r1 %g l2 %g r2 %g", FixToDbl(bot), FixToDbl(top), VAL(val->vVal), FixToDbl(val->vSpc), val->vGhst ? " G" : "", FixToDbl(l1), FixToDbl(r1), FixToDbl(l2), FixToDbl(r2)); } void ShowHVals(HintVal* lst) { while (lst != NULL) { ShowHVal(lst); lst = lst->vNxt; } } void ReportAddHVal(HintVal* val) { ShowHVal(val); } static void ShwVV(HintVal* val) { Fixed lft, rht; lft = val->vLoc1; rht = val->vLoc2; LogMsg(LOGDEBUG, OK, "l %g r %g v %g s %g", FixToDbl(lft), FixToDbl(rht), VAL(val->vVal), FixToDbl(val->vSpc)); } void ShowVVal(HintVal* val) { Fixed b1, b2, t1, t2; Fixed lft, rht; HintSeg* seg = val->vSeg1; if (seg == NULL) { ShwVV(val); return; } lft = val->vLoc1; rht = val->vLoc2; b1 = -seg->sMin; t1 = -seg->sMax; seg = val->vSeg2; b2 = -seg->sMin; t2 = -seg->sMax; LogMsg(LOGDEBUG, OK, "l %g r %g v %g s %g b1 %g t1 %g b2 %g t2 %g", FixToDbl(lft), FixToDbl(rht), VAL(val->vVal), FixToDbl(val->vSpc), FixToDbl(b1), FixToDbl(t1), FixToDbl(b2), FixToDbl(t2)); } void ShowVVals(HintVal* lst) { while (lst != NULL) { ShowVVal(lst); lst = lst->vNxt; } } void ReportAddVVal(HintVal* val) { ShowVVal(val); } void ReportFndBstVal(HintSeg* seg, HintVal* val, bool hFlg) { if (hFlg) { LogMsg(LOGDEBUG, OK, "FndBstVal: sLoc %g sLft %g sRght %g ", FixToDbl(-seg->sLoc), FixToDbl(seg->sMin), FixToDbl(seg->sMax)); if (val) ShwHV(val); else LogMsg(LOGDEBUG, OK, "NULL"); } else { LogMsg(LOGDEBUG, OK, "FndBstVal: sLoc %g sBot %g sTop %g ", FixToDbl(seg->sLoc), FixToDbl(-seg->sMin), FixToDbl(-seg->sMax)); if (val) ShwVV(val); else LogMsg(LOGDEBUG, OK, "NULL"); } } void ReportCarry(Fixed l0, Fixed l1, Fixed loc, HintVal* hints, bool vert) { if (vert) { ShowVVal(hints); } else { ShowHVal(hints); loc = -loc; l0 = -l0; l1 = -l1; } LogMsg(LOGDEBUG, OK, " carry to %g in [%g..%g]", FixToDbl(loc), FixToDbl(l0), FixToDbl(l1)); } void LogHintInfo(HintPoint* pl) { char c = pl->c; if (c == 'y' || c == 'm') { /* vertical lines */ Fixed lft = pl->x0; Fixed rht = pl->x1; LogMsg(LOGDEBUG, OK, "%4g %-30s%5g%5g", FixToDbl(rht - lft), gGlyphName, FixToDbl(lft), FixToDbl(rht)); } else { Fixed bot = pl->y0; Fixed top = pl->y1; Fixed wdth = top - bot; if (wdth == -FixInt(21) || wdth == -FixInt(20)) return; /* ghost pair */ LogMsg(LOGDEBUG, OK, "%4g %-30s%5g%5g", FixToDbl(wdth), gGlyphName, FixToDbl(bot), FixToDbl(top)); } } static void LstHVal(HintVal* val) { ShowHVal(val); } static void LstVVal(HintVal* val) { ShowVVal(val); } void ListHintInfo(void) { /* debugging routine */ PathElt* e; SegLnkLst *hLst, *vLst; HintSeg* seg; Fixed x, y; e = gPathStart; while (e != NULL) { hLst = e->Hs; vLst = e->Vs; if ((hLst != NULL) || (vLst != NULL)) { GetEndPoint(e, &x, &y); y = -y; LogMsg(LOGDEBUG, OK, "x %g y %g ", FixToDbl(x), FixToDbl(y)); while (hLst != NULL) { seg = hLst->lnk->seg; LstHVal(seg->sLnk); hLst = hLst->next; } while (vLst != NULL) { seg = vLst->lnk->seg; LstVVal(seg->sLnk); vLst = vLst->next; } } e = e->next; } } void ReportStemNearMiss(bool vert, Fixed w, Fixed minW, Fixed b, Fixed t, bool curve) { LogMsg(INFO, OK, "%s %s stem near miss: %g instead of %g at %g to %g.", vert ? "Vertical" : "Horizontal", curve ? "curve" : "linear", FixToDbl(w), FixToDbl(minW), FixToDbl(NUMMIN(b, t)), FixToDbl(NUMMAX(b, t))); } void ReportHintConflict(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch) { char s[2]; s[0] = ch; s[1] = 0; LogMsg(LOGDEBUG, OK, " Conflicts with current hints: %g %g %g %g %s.", FixToDbl(x0), FixToDbl(y0), FixToDbl(x1), FixToDbl(y1), s); } void ReportDuplicates(Fixed x, Fixed y) { LogMsg(LOGERROR, OK, "Check for duplicate subpath at %g %g.", FixToDbl(x), FixToDbl(y)); } void ReportBBoxBogus(Fixed llx, Fixed lly, Fixed urx, Fixed ury) { LogMsg(INFO, OK, "Glyph bounding box looks bogus: %g %g %g %g.", FixToDbl(llx), FixToDbl(lly), FixToDbl(urx), FixToDbl(ury)); } void ReportMergeHVal(Fixed b0, Fixed t0, Fixed b1, Fixed t1, Fixed v0, Fixed s0, Fixed v1, Fixed s1) { LogMsg(LOGDEBUG, OK, "Replace H hints pair at %g %g by %g %g", FixToDbl(-b0), FixToDbl(-t0), FixToDbl(-b1), FixToDbl(-t1)); LogMsg(LOGDEBUG, OK, "\told value %g %g new value %g %g", VAL(v0), FixToDbl(s0), VAL(v1), FixToDbl(s1)); } void ReportMergeVVal(Fixed l0, Fixed r0, Fixed l1, Fixed r1, Fixed v0, Fixed s0, Fixed v1, Fixed s1) { LogMsg(LOGDEBUG, OK, "Replace V hints pair at %g %g by %g %g", FixToDbl(l0), FixToDbl(r0), FixToDbl(l1), FixToDbl(r1)); LogMsg(LOGDEBUG, OK, "\told value %g %g new value %g %g", VAL(v0), FixToDbl(s0), VAL(v1), FixToDbl(s1)); } void ReportPruneHVal(HintVal* val, HintVal* v, int32_t i) { LogMsg(LOGDEBUG, OK, "PruneHVal: %d", i); ShowHVal(val); ShowHVal(v); } void ReportPruneVVal(HintVal* val, HintVal* v, int32_t i) { LogMsg(LOGDEBUG, OK, "PruneVVal: %d", i); ShowVVal(val); ShowVVal(v); } psautohint-2.3.0/libpsautohint/src/shuffle.c000066400000000000000000000131161401523215600211670ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" #define MAXCNT (100) static int32_t rowcnt; unsigned char* InitShuffleSubpaths(void) { int32_t cnt = -1; PathElt* e = gPathStart; while (e != NULL) { /* every element is marked with its subpath count */ if (e->type == MOVETO) cnt++; if (e->type == MOVETO) { LogMsg(LOGDEBUG, OK, "subpath %d starts at %g %g.", cnt, FixToDbl(e->x), FixToDbl(-e->y)); } e->count = (int16_t)cnt; e = e->next; } cnt++; rowcnt = cnt; if (cnt < 4 || cnt >= MAXCNT) return NULL; return Alloc(cnt * cnt); } static void PrintLinks(unsigned char* links) { int32_t i, j; LogMsg(LOGDEBUG, OK, "Links "); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, "%d ", i); if (i < 10) LogMsg(LOGDEBUG, OK, " "); } LogMsg(LOGDEBUG, OK, "\n"); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, " %d ", i); if (i < 10) LogMsg(LOGDEBUG, OK, " "); for (j = 0; j < rowcnt; j++) { LogMsg(LOGDEBUG, OK, "%d ", links[rowcnt * i + j]); } LogMsg(LOGDEBUG, OK, "\n"); } } static void PrintSumLinks(char* sumlinks) { int32_t i; LogMsg(LOGDEBUG, OK, "Sumlinks "); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, "%d ", i); if (i < 10) LogMsg(LOGDEBUG, OK, " "); } LogMsg(LOGDEBUG, OK, "\n"); LogMsg(LOGDEBUG, OK, " "); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, "%d ", sumlinks[i]); } LogMsg(LOGDEBUG, OK, "\n"); } static void PrintOutLinks(unsigned char* outlinks) { int32_t i; LogMsg(LOGDEBUG, OK, "Outlinks "); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, "%d ", i); if (i < 10) LogMsg(LOGDEBUG, OK, " "); } LogMsg(LOGDEBUG, OK, "\n"); LogMsg(LOGDEBUG, OK, " "); for (i = 0; i < rowcnt; i++) { LogMsg(LOGDEBUG, OK, "%d ", outlinks[i]); } LogMsg(LOGDEBUG, OK, "\n"); } void MarkLinks(HintVal* vL, bool hFlg, unsigned char* links) { int32_t i, j; HintSeg* seg; PathElt* e; if (links == NULL) return; for (; vL != NULL; vL = vL->vNxt) { seg = vL->vSeg1; if (seg == NULL) continue; e = seg->sElt; if (e == NULL) continue; i = e->count; seg = vL->vSeg2; if (seg == NULL) continue; e = seg->sElt; if (e == NULL) continue; j = e->count; if (i == j) continue; if (hFlg) ShowHVal(vL); else ShowVVal(vL); LogMsg(LOGDEBUG, OK, " : %d <-> %d", i, j); links[rowcnt * i + j] = 1; links[rowcnt * j + i] = 1; } } static void Outpath(unsigned char* links, unsigned char* outlinks, unsigned char* output, int32_t bst) { unsigned char *lnks, *outlnks; int32_t i = bst; PathElt* e = gPathStart; while (e != NULL) { if (e->count == i) break; e = e->next; } if (e == NULL) return; MoveSubpathToEnd(e); LogMsg(LOGDEBUG, OK, "move subpath %d to end.", bst); output[bst] = 1; lnks = &links[bst * rowcnt]; outlnks = outlinks; for (i = 0; i < rowcnt; i++) *outlnks++ += *lnks++; PrintOutLinks(outlinks); } /* The intent of this code is to order the subpaths so that the hints will not need to change constantly because it is jumping from one subpath to another. Kanji glyphs had the most problems with this which caused huge files to be created. */ void DoShuffleSubpaths(unsigned char* links) { unsigned char sumlinks[MAXCNT], output[MAXCNT], outlinks[MAXCNT]; unsigned char* lnks; int32_t i, j; memset(sumlinks, 0, MAXCNT * sizeof(unsigned char)); memset(output, 0, MAXCNT * sizeof(unsigned char)); memset(outlinks, 0, MAXCNT * sizeof(unsigned char)); if (links == NULL) return; PrintLinks(links); for (i = 0; i < rowcnt; i++) output[i] = sumlinks[i] = outlinks[i] = 0; lnks = links; for (i = 0; i < rowcnt; i++) { for (j = 0; j < rowcnt; j++) { if (*lnks++ != 0) sumlinks[i]++; } } PrintSumLinks((char*)sumlinks); while (true) { int32_t bst = -1; int32_t bstsum = 0; for (i = 0; i < rowcnt; i++) { if (output[i] == 0 && (bst == -1 || sumlinks[i] > bstsum)) { bstsum = sumlinks[i]; bst = i; } } if (bst == -1) break; Outpath(links, outlinks, output, bst); while (true) { int32_t bstlnks; bst = -1; bstsum = 0; bstlnks = 0; for (i = 0; i < rowcnt; i++) { if (output[i] == 0 && outlinks[i] >= bstlnks) { if (outlinks[i] > 0 && (bst == -1 || outlinks[i] > bstlnks || (outlinks[i] == bstlnks && sumlinks[i] > bstsum))) { bstlnks = outlinks[i]; bst = i; bstsum = sumlinks[i]; } } } if (bst == -1) break; Outpath(links, outlinks, output, bst); } } } psautohint-2.3.0/libpsautohint/src/stemreport.c000066400000000000000000000022071401523215600217360ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include "ac.h" void AddVStem(Fixed right, Fixed left, bool curved) { if (curved && !gAllStems) return; if (gAddVStemCB) gAddVStemCB(FIXED2FLOAT(right), FIXED2FLOAT(left), gGlyphName, gAddStemUserData); } void AddHStem(Fixed top, Fixed bottom, bool curved) { if (curved && !gAllStems) return; if (gAddHStemCB) gAddHStemCB(FIXED2FLOAT(top), FIXED2FLOAT(bottom), gGlyphName, gAddStemUserData); } void AddGlyphExtremes(Fixed bot, Fixed top) { if (gAddGlyphExtremesCB) gAddGlyphExtremesCB(FIXED2FLOAT(top), FIXED2FLOAT(bot), gGlyphName, gAddExtremesUserData); } void AddStemExtremes(Fixed bot, Fixed top) { if (gAddStemExtremesCB) gAddStemExtremesCB(FIXED2FLOAT(top), FIXED2FLOAT(bot), gGlyphName, gAddExtremesUserData); } psautohint-2.3.0/libpsautohint/src/version.h.in000066400000000000000000000001141401523215600216240ustar00rootroot00000000000000/* auto generated file don't edit */ #define PSAUTOHINT_VERSION "@VCS_TAG@" psautohint-2.3.0/libpsautohint/src/write.c000066400000000000000000000255701401523215600206740ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). * All Rights Reserved. * * This software is licensed as OpenSource, under the Apache License, Version * 2.0. * This license is available at: http://opensource.org/licenses/Apache-2.0. */ #include #include "ac.h" #define WRTABS_COMMENT (0) static Fixed currentx, currenty; static bool firstFlex, wrtHintInfo; #define MAXBUFFLEN 127 static char S0[MAXBUFFLEN + 1]; static HintPoint* bst; static char bch; static Fixed bx, by; static bool bstB; int32_t FRnd(int32_t x) { /* This is meant to work on Fixed 24.8 values, not the elt path (x,y) which * are 25.7 */ int32_t r; r = x; if (gRoundToInt) { r = r + (1 << 7); r = r & ~0xFF; } return r; } #define WriteString(...) ACBufferWriteF(gBezOutput, __VA_ARGS__) /* Note: The 8 bit fixed fraction cannot support more than 2 decimal places. */ #define WRTNUM(i) WriteString("%d ", (int32_t)(i)) #define WRTRNUM(i) WriteString("%0.2f ", round((double)(i)*100) / 100) static void wrtxa(Fixed x) { if ((gRoundToInt) || (FracPart(x) == 0)) { Fixed i = FRnd(x); WRTNUM(FTrunc(i)); currentx = i; } else { float r; currentx = x; r = (float)FIXED2FLOAT(x); WRTRNUM(r); } } static void wrtya(Fixed y) { if ((gRoundToInt) || (FracPart(y) == 0)) { Fixed i = FRnd(y); WRTNUM(FTrunc(i)); currenty = i; } else { float r; currenty = y; r = (float)FIXED2FLOAT(y); WRTRNUM(r); } } #define wrtcda(c) \ wrtxa(c.x); \ wrtya(c.y) /*To avoid pointless hint subs*/ #define HINTMAXSTR 2048 static char hintmaskstr[HINTMAXSTR]; static char prevhintmaskstr[HINTMAXSTR]; static void safestrcat(char* s1, char* s2) { if (strlen(s1) + strlen(s2) + 1 > HINTMAXSTR) { LogMsg(LOGERROR, FATALERROR, "Hint information overflowing buffer."); } else { strcat(s1, s2); } } #define sws(str) safestrcat(hintmaskstr, (char*)str) #define SWRTNUM(i) \ { \ snprintf(S0, MAXBUFFLEN, "%d ", (int32_t)(i)); \ sws(S0); \ } #define SWRTNUMA(i) \ { \ snprintf(S0, MAXBUFFLEN, "%0.2f ", round((double)(i)*100) / 100); \ sws(S0); \ } static void NewBest(HintPoint* lst) { bst = lst; bch = lst->c; if (bch == 'y' || bch == 'm') { Fixed x0, x1; bstB = true; x0 = lst->x0; x1 = lst->x1; bx = NUMMIN(x0, x1); } else { Fixed y0, y1; bstB = false; y0 = lst->y0; y1 = lst->y1; by = NUMMIN(y0, y1); } } static void WriteOne(Fixed s) { /* write s to output file */ if (FracPart(s) == 0) { SWRTNUM(FTrunc(s)) } else { float d = (float)FIXED2FLOAT(s); SWRTNUMA(d); } } static void WritePointItem(HintPoint* lst) { switch (lst->c) { case 'b': case 'v': WriteOne(lst->y0); WriteOne(lst->y1 - lst->y0); sws(((lst->c == 'b') ? "rb" : "rv")); break; case 'y': case 'm': WriteOne(lst->x0); WriteOne(lst->x1 - lst->x0); sws(((lst->c == 'y') ? "ry" : "rm")); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal point list data."); } } sws(" % "); SWRTNUM(lst->p0 != NULL ? lst->p0->count : 0); SWRTNUM(lst->p1 != NULL ? lst->p1->count : 0); sws("\n"); } static void WrtPntLst(HintPoint* lst) { HintPoint* ptLst; char ch; Fixed x0, x1, y0, y1; ptLst = lst; while (lst != NULL) { /* mark all as not yet done */ lst->done = false; lst = lst->next; } while (true) { /* write in sort order */ lst = ptLst; bst = NULL; while (lst != NULL) { /* find first not yet done as init best */ if (!lst->done) { NewBest(lst); break; } lst = lst->next; } if (bst == NULL) { break; /* finished with entire list */ } lst = bst->next; while (lst != NULL) { /* search for best */ if (!lst->done) { ch = lst->c; if (ch > bch) { NewBest(lst); } else if (ch == bch) { if (bstB) { x0 = lst->x0; x1 = lst->x1; if (NUMMIN(x0, x1) < bx) { NewBest(lst); } } else { y0 = lst->y0; y1 = lst->y1; if (NUMMIN(y0, y1) < by) { NewBest(lst); } } } } lst = lst->next; } bst->done = true; /* mark as having been done */ WritePointItem(bst); } } static void wrtnewhints(PathElt* e) { if (!wrtHintInfo) { return; } hintmaskstr[0] = '\0'; WrtPntLst(gPtLstArray[e->newhints]); if (strcmp(prevhintmaskstr, hintmaskstr)) { WriteString("beginsubr snc\n%sendsubr enc\nnewcolors\n", hintmaskstr); strcpy(prevhintmaskstr, hintmaskstr); } } static bool IsFlex(PathElt* e) { PathElt *e0, *e1; if (firstFlex) { e0 = e; e1 = e->next; } else { e0 = e->prev; e1 = e; } return (e0 != NULL && e0->isFlex && e1 != NULL && e1->isFlex); } static void mt(Cd c, PathElt* e) { if (e->newhints != 0) { wrtnewhints(e); } wrtcda(c); WriteString("mt\n"); } static void dt(Cd c, PathElt* e) { if (e->newhints != 0) { wrtnewhints(e); } wrtcda(c); WriteString("dt\n"); } static Fixed flX, flY; static Cd fc1, fc2, fc3; #define wrtpreflx2a(c) \ wrtcda(c); \ WriteString("rmt\npreflx2a\n") static void wrtflex(Cd c1, Cd c2, Cd c3, PathElt* e) { int32_t dmin, delta; bool yflag; Cd c13; float shrink, r1, r2; if (firstFlex) { flX = currentx; flY = currenty; fc1 = c1; fc2 = c2; fc3 = c3; firstFlex = false; return; } yflag = e->yFlex; dmin = gDMin; delta = gDelta; WriteString("preflx1\n"); if (yflag) { if (fc3.y == c3.y) { c13.y = c3.y; } else { acfixtopflt(fc3.y - c3.y, &shrink); shrink = (float)delta / shrink; if (shrink < 0.0f) { shrink = -shrink; } acfixtopflt(fc3.y - c3.y, &r1); r1 *= shrink; acfixtopflt(c3.y, &r2); r1 += r2; c13.y = acpflttofix(&r1); } c13.x = fc3.x; } else { if (fc3.x == c3.x) { c13.x = c3.x; } else { acfixtopflt(fc3.x - c3.x, &shrink); shrink = (float)delta / shrink; if (shrink < 0.0f) { shrink = -shrink; } acfixtopflt(fc3.x - c3.x, &r1); r1 *= shrink; acfixtopflt(c3.x, &r2); r1 += r2; c13.x = acpflttofix(&r1); } c13.y = fc3.y; } wrtpreflx2a(c13); wrtpreflx2a(fc1); wrtpreflx2a(fc2); wrtpreflx2a(fc3); wrtpreflx2a(c1); wrtpreflx2a(c2); wrtpreflx2a(c3); currentx = flX; currenty = flY; wrtcda(fc1); wrtcda(fc2); wrtcda(fc3); wrtcda(c1); wrtcda(c2); wrtcda(c3); WRTNUM(dmin); WRTNUM(delta); WRTNUM(yflag); WRTNUM(FTrunc(FRnd(currentx))); WRTNUM(FTrunc(FRnd(currenty))); WriteString("flxa\n"); firstFlex = true; } static void ct(Cd c1, Cd c2, Cd c3, PathElt* e) { if (e->newhints != 0) { wrtnewhints(e); } if (e->isFlex && IsFlex(e)) { wrtflex(c1, c2, c3, e); } else { wrtcda(c1); wrtcda(c2); wrtcda(c3); WriteString("ct\n"); } } static void cp(PathElt* e) { if (e->newhints != 0) { wrtnewhints(e); } WriteString("cp\n"); } static void NumberPath(void) { int16_t cnt; PathElt* e; e = gPathStart; cnt = 1; while (e != NULL) { e->count = cnt++; e = e->next; } } void SaveFile(void) { PathElt* e = gPathStart; Cd c1, c2, c3; WriteString("%% %s\n", gGlyphName); wrtHintInfo = (gPathStart != NULL && gPathStart != gPathEnd); NumberPath(); prevhintmaskstr[0] = '\0'; if (wrtHintInfo && (!e->newhints)) { hintmaskstr[0] = '\0'; WrtPntLst(gPtLstArray[0]); WriteString("%s", hintmaskstr); strcpy(prevhintmaskstr, hintmaskstr); } WriteString("sc\n"); firstFlex = true; currentx = currenty = 0; while (e != NULL) { switch (e->type) { case CURVETO: c1.x = e->x1; c1.y = -e->y1; c2.x = e->x2; c2.y = -e->y2; c3.x = e->x3; c3.y = -e->y3; ct(c1, c2, c3, e); break; case LINETO: c1.x = e->x; c1.y = -e->y; dt(c1, e); break; case MOVETO: c1.x = e->x; c1.y = -e->y; mt(c1, e); break; case CLOSEPATH: cp(e); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal path list."); } } #if WRTABS_COMMENT WriteString(" %% "); WRTNUM(e->count) switch (e->type) { case CURVETO: wrtfx(c1.x); wrtfx(c1.y); wrtfx(c2.x); wrtfx(c2.y); wrtfx(c3.x); wrtfx(c3.y); WriteString("ct"); break; case LINETO: wrtfx(c1.x); wrtfx(c1.y); WriteString("dt"); break; case MOVETO: wrtfx(c1.x); wrtfx(c1.y); WriteString("mt"); break; case CLOSEPATH: WriteString("cp"); break; } WriteString("\n"); #endif e = e->next; } WriteString("ed\n"); } psautohint-2.3.0/pyproject.toml000066400000000000000000000001511401523215600166220ustar00rootroot00000000000000[build-system] requires = [ "setuptools >= 36.4", "wheel >= 0.31", "setuptools_scm >= 2.1" ] psautohint-2.3.0/python/000077500000000000000000000000001401523215600152325ustar00rootroot00000000000000psautohint-2.3.0/python/psautohint/000077500000000000000000000000001401523215600174305ustar00rootroot00000000000000psautohint-2.3.0/python/psautohint/__init__.py000066400000000000000000000045071401523215600215470ustar00rootroot00000000000000import os import logging from . import _psautohint log = logging.getLogger(__name__) __version__ = _psautohint.version class FontParseError(Exception): pass def _font_is_ufo(path): from fontTools.ufoLib import UFOReader from fontTools.ufoLib.errors import UFOLibError try: UFOReader(path, validate=False) return True except (UFOLibError, KeyError, TypeError): return False def get_font_format(font_file_path): if _font_is_ufo(font_file_path): return "UFO" elif os.path.isfile(font_file_path): with open(font_file_path, 'rb') as f: head = f.read(4) if head == b'OTTO': return 'OTF' elif head[0:2] == b'\x01\x00': return 'CFF' elif head[0:2] == b'\x80\x01': return 'PFB' elif head in (b'%!PS', b'%!Fo'): for fullhead in (b'%!PS-AdobeFont', b'%!FontType1', b'%!PS-Adobe-3.0 Resource-CIDFont'): f.seek(0) if f.read(len(fullhead)) == fullhead: if b"CID" not in fullhead: return 'PFA' else: return 'PFC' return None else: return None def hint_bez_glyph(info, glyph, allow_edit=True, allow_hint_sub=True, round_coordinates=True, report_zones=False, report_stems=False, report_all_stems=False): report = 0 if report_zones: report = 1 elif report_stems: report = 2 # In/out of C code is bytes. In/out of Python code is str. hinted_b = _psautohint.autohint(info.encode('ascii'), glyph.encode('ascii'), allow_edit, allow_hint_sub, round_coordinates, report, report_all_stems) hinted = hinted_b.decode('ascii') return hinted def hint_compatible_bez_glyphs(info, glyphs, masters): hinted = _psautohint.autohintmm(tuple(g.encode('ascii') for g in glyphs), tuple(m.encode('ascii') for m in masters)) return [g.decode('ascii') for g in hinted] psautohint-2.3.0/python/psautohint/__main__.py000066400000000000000000000770471401523215600215410ustar00rootroot00000000000000# Copyright 2014 Adobe. All rights reserved. """ Auto-hinting program for PostScript, OpenType/CFF and UFO fonts. """ import argparse import logging import os import re import subprocess import sys import textwrap from . import __version__, get_font_format from .autohint import ACOptions, hintFiles FONTINFO_FILE_NAME = 'fontinfo' GENERAL_INFO = """ psautohint is a tool for hinting fonts. It supports the following formats: Type 1 (PFA and PFB), CFF (a.k.a. Type 2), OTF (name- and CID-keyed), and UFO. By default, psautohint will hint all glyphs in the input font. Options are available to specify a subset of the glyphs for hinting and other settings. Note that the hinting results are better if the font's alignment zones are set properly. For a font supporting a bicameral script (e.g. Latin, Greek, or Cyrillic) the zones should capture the baseline, the x-height and the capital height, and respective overshoots of the glyphs, at the very least. Additionally, ascender, descender, small cap, and figures heights may also be considered. The reports provided by the stemHist tool can be useful for choosing alignment zone and stem width values. """ FDDICT_DOC = r""" By default, psautohint uses the font's global alignment zones and stem widths when hinting each glyph. However, if there is a file named 'fontinfo' in the same directory as the input font file, psautohint will search it for definitions of sets of alignment zones (a.k.a 'FDDict'), and for matching lists of glyphs to which each FDDict should be applied to. This approach allows a group of glyphs to be hinted using a different set of zones and stem widths than other glyphs. This solution isn't as robust as having multiple hint dictionaries --supported in CID fonts-- in the font, as the final name-keyed font can only have one set of alignment zones, but it does allow for improved hinting when different sets of glyphs need different alignment zones. If FDDict definitions are used, then the global alignment zones and stem widths in the source font will be ignored. For any glyphs not covered by an explicit FDDict definition, psautohint will synthesize a dummy FDDict, where the zones are set outside of the font's bounding box, so not to influence the hinting. This is desirable for glyphs that have no features that need to be aligned. If psautohint finds an FDDict named 'FinalFont', it will write that set of values to the output font. Otherwise, it will merge all the alignment zones and stem widths as the union of all FDDict definitions. If this merging fails because some of the alignment zones and stem widths overlap, then the font developer will have to provide a 'FinalFont' FDDict that explicitly defines which stems and zones to use in the hinted output font. To use a FDDict, one must define both the values of alignment zones and stem widths, and the set of glyphs to apply it to. The FDDict must be defined in the file before the set of glyphs which belong to it. Both the FDDict and the glyph set define a name; an FDDict is applied to the glyph set with the same name. Running psautohint with the option --print-dflt-fddict will provide the list of default FDDict values for the source font. These can be used as a starting point for alternate FDDict definitions. Running psautohint with the option --print-list-fddict will provide all the user-defined FDDict defintions, as well as the list of glyphs associated with each of them. One can use these to confirm the values, and to check which glyphs are assigned to which FDDict. In particular, one should check the glyph list of the FDDict named 'No Alignment Zones'; this list contains the glyphs that did not match any of the search terms of the user-defined FDDicts. The definitions use the following syntax: begin FDDict ... end FDDict begin GlyphSet ... end GlyphSet The glyph names may be either a real glyph name, or a regular expression designed to match several names. An abbreviated regex primer: ^ ..... Matches at the start of the glyph name $ ..... Matches at the end [aABb] Matches any one character in the set a, A, b, B [A-Z] Matches any one character in the set comprising the range from A-Z [abA-Z] Matches any one character in the set a, b, and the characters in the range from A-Z . ..... Matches any single character + ..... Maches whatever preceded it one or more times * ..... Matches whatever preceded it none or more times. \ ..... An escape character that includes the following character without the second one being interpreted as a regex special character Examples: ^[A-Z]$ Matches names with one character in the range from A-Z. ^[A-Z].+ Matches any name where the first character is in the range A-Z, and it is followed by one or more characters. [A-Z].+ Matches any name with a character that is in the range A-Z and which is followed by one or more characters. ^[A-Z].* Matches any name with one or more characters, and the first character is in the range A-Z ^.+\.smallcaps Matches any name that contains ".smallcaps" ^.+\.smallcaps$ Matches any name that ends with ".smallcaps" ^.+\.s[0-24]0$ Matches any name that ends with ".s00",".s10",".s20" or ".s40" Example FDDict and GlyphSet definitions ======================================= begin FDDict ST_Smallcaps # It's good practice to put the non-hint stuff first OrigEmSqUnits 1000 FlexOK true # This gets used as the hint dict name if the font # is eventually built as a CID font. FontName MyFont-Bold # Alignment zones # The first is a bottom zone, the rest are top zones BaselineOvershoot -20 BaselineYCoord 0 CapHeight 900 CapOvershoot 20 LcHeight 700 LcOvershoot 15 # Stem widths DominantV [236 267] DominantH [141 152] end FDDict ST_Smallcaps begin FDDict LM_Smallcaps OrigEmSqUnits 1000 FlexOK true FontName MyFont-Bold BaselineOvershoot -25 BaselineYCoord 0 CapHeight 950 CapOvershoot 25 LcHeight 750 LcOvershoot 21 DominantV [236 267] DominantH [141 152] end FDDict LM_Smallcaps begin GlyphSet LM_Smallcaps [Ll]\S+\.smallcap [Mm]\S+\.smallcap end GlyphSet LM_Smallcaps begin GlyphSet ST_Smallcaps [Tt]\S+\.smallcap [Ss]\S+\.smallcap end GlyphSet ST_Smallcaps *************************************** Note that whitespace must exist between keywords and values, but is otherwise ignored. '#' marks the start of a comment; all the text after this character on a line is ignored. GlyphSet and FDDict definitions may be intermixed, as long as any FDDict is defined before the GlyphSet which refers to it. At least two BlueValue pairs (the 'BaselineYCoord' bottom zone and any top zone), and DominantH and DominantV values must be provided. All other keywords are optional. The full set of recognized FDDict keywords is: BlueValue pairs: # BaselineOvershoot is a bottom zone, the rest are top zones. BaselineYCoord BaselineOvershoot CapHeight CapOvershoot LcHeight LcOvershoot AscenderHeight AscenderOvershoot FigHeight FigOvershoot Height5 Height5Overshoot Height6 Height6Overshoot OtherBlues pairs: Baseline5Overshoot Baseline5 Baseline6Overshoot Baseline6 SuperiorOvershoot SuperiorBaseline OrdinalOvershoot OrdinalBaseline DescenderOvershoot DescenderHeight For zones which capture the bottom of a feature in a glyph --BaselineYCoord and all OtherBlues values-- the value specifies the top of the zone, and the 'Overshoot' is a negative value which specifes the offset to the bottom of the zone, e.g. BaselineYCoord 0 BaselineOvershoot -12 For zones which capture the top of a feature in a glyph --all BlueValue values except BaselineYCoord-- the value specifies the bottom of the zone, and the 'Overshoot' is a positive value which specifes the offset to the top of the zone, e.g. Height6 800 Height6Overshoot 20 Note also that there is no implied sequential order of values. Height6 may have a value less than or equal to CapHeight. The values for keywords in one FontDict definiton are completely independent from the values used in another FontDict. There is no inheritance from one definition to the next. Miscellaneous values: FontName ..... PostScript font name. Only used by makeotf when building a CID font. OrigEmSqUnits Single value: size of em-square. Only used by makeotf when building a CID font. LanguageGroup 0 or 1. Specifies whether counter hints for ideographic glyphs should be applied. Only used by makeotf when building a CID font. DominantV .... List of dominant vertical stems, in the form [ ...] DominantH .... List of dominant horizontal stems, in the form [ ...] FlexOK ....... true or false. VCounterChars List of glyphs to which counter hints may be applied, in the form [ ...] HCounterChars List of glyphs to which counter hints may be applied, in the form [ ...] Counter hints help to keep the space between stems open and equal in size. psautohint will try and add counter hints to only a short hard-coded list of glyphs, V-counters: m, M, T, ellipsis H-counters: element, equivalence, notelement, divide To extend this list, use the VCounterChars and HCounterChars keywords. A maximum of 64 glyph names can be added to either the vertical or horizontal list. --- Note for cognoscenti: the psautohint program code ignores StdHW and StdVW entries if DominantV and DominantH entries are present, so it omits writing the Std[HV]W keywords to fontinfo file. Also, psautohint will add any non-duplicate stem width values for StemSnap[HV] to the Dominant[HV] stem width list, but the StemSnap[HV] entries are not necessary if the full list of stem widths are supplied as values for the Dominant[HV] keywords. Hence it also writes the full stem list for the Dominant[HV] keywords, and does not write the StemSnap[HV] keywords, to the fontinfo file. This is technically not right, as Dominant[HV] array is supposed to hold only two values, but the psautohint program is not affected, and it can write fewer entries this way. """ def _read_txt_file(file_path): with open(file_path, encoding='utf-8') as f: return f.read() def _expand_cid_name(glyph_name, name_aliases): glyphRange = glyph_name.split("-") if len(glyphRange) > 1: g1 = _expand_cid_name(glyphRange[0], name_aliases) g2 = _expand_cid_name(glyphRange[1], name_aliases) glyph_name = "%s-%s" % (g1, g2) elif glyph_name[0] == "/": glyph_name = "cid" + glyph_name[1:].zfill(5) if glyph_name == "cid00000": glyph_name = ".notdef" name_aliases[glyph_name] = "cid00000" elif glyph_name.startswith("cid") and (len(glyph_name) < 8): glyph_name = "cid" + glyph_name[3:].zfill(5) if glyph_name == "cid00000": glyph_name = ".notdef" name_aliases[glyph_name] = "cid00000" return glyph_name def _process_glyph_list_arg(glyph_list, name_aliases): return [_expand_cid_name(n, name_aliases) for n in glyph_list] class HintOptions(ACOptions): def __init__(self, pargs): super(HintOptions, self).__init__() self.inputPaths = pargs.font_paths self.outputPaths = pargs.output_paths self.reference_font = pargs.reference_font self.hintAll = pargs.hint_all_ufo self.allowChanges = pargs.allow_changes self.noFlex = pargs.no_flex self.noHintSub = pargs.no_hint_sub self.allow_no_blues = pargs.no_zones_stems self.logOnly = pargs.report_only self.printDefaultFDDict = pargs.print_dflt_fddict self.printFDDictList = pargs.print_list_fddict self.round_coords = not pargs.decimal self.writeToDefaultLayer = pargs.write_to_default_layer class _CustomHelpFormatter(argparse.RawDescriptionHelpFormatter): """ Adds extra line between options """ @staticmethod def __add_whitespace(i, i_wtsp, arg): if i == 0: return arg return (" " * i_wtsp) + arg def _split_lines(self, arg, width): arg_rows = arg.splitlines() for i, line in enumerate(arg_rows): search = re.search(r'\s*[0-9\-]{0,}\.?\s*', line) if line.strip() == "": arg_rows[i] = " " elif search: line_wtsp = search.end() lines = [self.__add_whitespace(j, line_wtsp, x) for j, x in enumerate(textwrap.wrap(line, width))] arg_rows[i] = lines # [''] adds the extra line between args return [item for sublist in arg_rows for item in sublist] + [''] class _AdditionalHelpAction(argparse.Action): """ Based on argparse's _HelpAction and _VersionAction. """ def __init__(self, option_strings, addl_help=None, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None): super(_AdditionalHelpAction, self).__init__( option_strings=option_strings, dest=dest, default=default, nargs=0, help=help) self.addl_help = addl_help def __call__(self, parser, namespace, values, option_string=None): formatter = parser._get_formatter() formatter.add_text(self.addl_help) parser.exit(message=formatter.format_help()) class DuplicateMessageFilter(logging.Filter): """ Suppresses any log message that was reported before in the same module and for the same logging level. We check for module and level number in addition to the message just in case, though checking the message only is probably enough. """ def __init__(self): super(DuplicateMessageFilter, self).__init__() self.logs = set() def filter(self, record): current = (record.module, record.levelno, record.getMessage()) if current in self.logs: return False self.logs.add(current) return True def _split_comma_sequence(comma_str): return [item.strip() for item in comma_str.split(',')] def _check_save_path(path_str): test_path = os.path.abspath(os.path.realpath(path_str)) del_test_file = True check_path = test_path if os.path.isdir(test_path): check_path = os.path.join(test_path, "dummy") try: if os.path.exists(check_path): del_test_file = False open(check_path, 'a').close() if del_test_file: os.remove(check_path) except (IOError, OSError): raise argparse.ArgumentTypeError( f"{test_path} is not a valid path to write to.") return test_path def _check_tx(): try: subprocess.check_output(["tx", "-h"], stderr=subprocess.STDOUT) return True except (subprocess.CalledProcessError, OSError): return False def _validate_font_paths(path_lst, parser): """ Checks that all input paths are fonts, and that all are the same format. Returns the font format string. """ has_tx = None format_set = set() for path in path_lst: font_format = get_font_format(path) base_name = os.path.basename(path) if font_format is None: parser.error(f"{base_name} is not a supported font format") if font_format in ("PFA", "PFB", "PFC"): if has_tx is None: has_tx = _check_tx() if not has_tx: parser.error(f"{base_name} font format requires 'tx'. " "Please install 'afdko'.") format_set.add(font_format) if len(format_set) > 1: parser.error("the input fonts must be all of the same format") # get the set's single-item via tuple unpacking ft_format_str, = format_set return ft_format_str def _validate_path(path_str): valid_path = os.path.abspath(os.path.realpath(path_str)) if not os.path.exists(valid_path): raise argparse.ArgumentTypeError( f"{path_str} is not a valid path.") return valid_path RE_COUNTER_CHARS = re.compile( r"([VH])CounterChars\s+[\(\[]\s*([^\)\r\n]+)[\)\]]") RE_COMMENT_LINE = re.compile(r"#[^\r\n]+") def _parse_fontinfo_file(options, fontinfo_path): """ Collects VCounterChars and HCounterChars values from a fontinfo file, and updates the ACOptions class. """ data = _read_txt_file(fontinfo_path) data = re.sub(RE_COMMENT_LINE, "", data) counterGlyphLists = RE_COUNTER_CHARS.findall(data) for entry in counterGlyphLists: glyphList = entry[1].split() if glyphList: if entry[0] == "V": options.vCounterGlyphs.extend(glyphList) else: options.hCounterGlyphs.extend(glyphList) def get_options(args): parser = argparse.ArgumentParser( formatter_class=_CustomHelpFormatter, description=__doc__ ) parser_fonts = parser.add_argument( 'font_paths', metavar='FONT', nargs='*', type=_validate_path, help='Type1/CFF/OTF/UFO font files' ) parser.add_argument( '-v', '--verbose', action='count', default=0, help='verbose mode\n' 'Use -vv for extra-verbose mode.' ) parser.add_argument( '-o', '--output', metavar='PATH', nargs='+', dest='output_paths', type=_check_save_path, help='specify a file to write the hints to\n' 'The hints are written to a new font instead of modifying the ' 'original one.' ) parser.add_argument( '-r', '--reference-font', metavar='PATH', type=_validate_path, help='reference font\n' 'Font to be used as reference, when hinting multiple fonts ' 'compatibily.' ) parser.add_argument( '-a', '--all', action='store_true', dest='hint_all_ufo', help='hint all glyphs\n' 'All glyphs get hinted even if:\n' '- they have been hinted before, or\n' '- the outlines in the default layer have not changed\n' 'This is a UFO-only option.' ) parser.add_argument( '-w', '--write-to-default-layer', action='store_true', help='write hints to default layer. This is a UFO-only option.' ) glyphs_parser = parser.add_mutually_exclusive_group() glyphs_parser.add_argument( '-g', '--glyphs', metavar='GLYPH_LIST', dest='glyphs_to_hint', type=_split_comma_sequence, default=[], help='comma-separated sequence of glyphs to hint\n' 'The glyph identifiers may be glyph indexes, glyph names, or ' 'glyph CIDs. CID values must be prefixed with a forward slash.\n' 'Examples:\n' ' psautohint -g A,B,C,69 MyFont.ufo\n' ' psautohint -g /103,/434,68 MyCIDFont' ) glyphs_parser.add_argument( '--glyphs-file', metavar='PATH', dest='glyphs_to_hint_file', type=_validate_path, help='file containing a list of glyphs to hint\n' 'The file must contain a comma-separated sequence of glyph ' 'identifiers.' ) glyphs_parser.add_argument( '-x', '--exclude-glyphs', metavar='GLYPH_LIST', dest='glyphs_to_not_hint', type=_split_comma_sequence, default=[], help='comma-separated sequence of glyphs to NOT hint\n' "Counterpart to '--glyphs' option." ) glyphs_parser.add_argument( '--exclude-glyphs-file', metavar='PATH', dest='glyphs_to_not_hint_file', type=_validate_path, help='file containing a list of glyphs to NOT hint\n' "Counterpart to '--glyphs-file' option." ) report_parser = parser.add_mutually_exclusive_group() report_parser.add_argument( '-c', '--allow-changes', action='store_true', help='allow changes to the glyph outlines\n' 'Paths are reordered to reduce hint substitution, and nearly ' 'straight curves are flattened.' ) report_parser.add_argument( '--report-only', action='store_true', help='process the font without modifying it' ) parser.add_argument( '--log', metavar='PATH', dest='log_path', type=_check_save_path, help='write output messages to a file' ) parser.add_argument( '-d', '--decimal', action='store_true', help='use decimal coordinates' ) parser.add_argument( '--no-flex', action='store_true', help='suppress generation of flex commands' ) parser.add_argument( '--no-hint-sub', action='store_true', help='suppress hint substitution' ) parser.add_argument( '--no-zones-stems', action='store_true', help='allow the font to have no alignment zones nor stem widths' ) parser.add_argument( '--fontinfo-file', metavar='PATH', type=_validate_path, help='file containing hinting parameters\n' f"Default: '{FONTINFO_FILE_NAME}'" ) parser.add_argument( '--print-dflt-fddict', action='store_true', help="print the font's default FDDict set\n" 'This can be used as a starting point for building FDDict ' "sets. Use '--doc-fddict' option for more info." ) parser.add_argument( '--print-list-fddict', action='store_true', help='print the list of user-defined FDDict sets, and the glyphs ' 'associated with each of them\n' 'This is useful for checking the FDDict sets and their glyph-' "matching definitions. Use '--doc-fddict' option for more info." ) parser.add_argument( '--doc-fddict', action=_AdditionalHelpAction, help="show a description of the format for defining sets of " "alternate alignment zones in a 'fontinfo' file and exit", addl_help=FDDICT_DOC ) parser.add_argument( '--info', action=_AdditionalHelpAction, help="show program's general info and exit", addl_help=GENERAL_INFO ) parser.add_argument( '--version', action='version', version=__version__ ) parser.add_argument( '--traceback', action='store_true', help="show traceback for exceptions.", ) parsed_args = parser.parse_args(args) if parsed_args.verbose == 0: log_level = logging.WARNING elif parsed_args.verbose == 1: log_level = logging.INFO else: log_level = logging.DEBUG logging.basicConfig(format="%(levelname)s: %(message)s", level=log_level, filename=parsed_args.log_path) for handler in logging.root.handlers: handler.addFilter(DuplicateMessageFilter()) if (not len(parsed_args.font_paths or []) and len(parsed_args.output_paths or [])): # allow "psautohint -o outputpath inputpath" # see https://github.com/adobe-type-tools/psautohint/issues/129 half = len(parsed_args.output_paths) // 2 parsed_args.font_paths = [ parsed_args.output_paths.pop(half) for _ in range(half)] if not len(parsed_args.font_paths): parser.error( f"the following arguments are required: {parser_fonts.metavar}") if (parsed_args.output_paths and len(parsed_args.font_paths) != len(parsed_args.output_paths)): parser.error("number of input and output fonts differ") if parsed_args.reference_font in parsed_args.font_paths: parser.error("the reference font cannot also be a font input") if len(parsed_args.font_paths) != len(set(parsed_args.font_paths)): parser.error("found duplicate font inputs") if parsed_args.reference_font: all_font_paths = parsed_args.font_paths + [parsed_args.reference_font] else: all_font_paths = parsed_args.font_paths options = HintOptions(parsed_args) options.font_format = _validate_font_paths(all_font_paths, parser) if parsed_args.glyphs_to_hint: options.glyphList = _process_glyph_list_arg( parsed_args.glyphs_to_hint, options.nameAliases) elif parsed_args.glyphs_to_not_hint: options.excludeGlyphList = True options.glyphList = _process_glyph_list_arg( parsed_args.glyphs_to_not_hint, options.nameAliases) elif parsed_args.glyphs_to_hint_file: options.glyphList = _process_glyph_list_arg( _read_txt_file(parsed_args.glyphs_to_hint_file), options.nameAliases) elif parsed_args.glyphs_to_not_hint_file: options.excludeGlyphList = True options.glyphList = _process_glyph_list_arg( _read_txt_file(parsed_args.glyphs_to_not_hint_file), options.nameAliases) if not parsed_args.fontinfo_file: fontinfo_path = os.path.join(os.path.dirname(all_font_paths[0]), FONTINFO_FILE_NAME) if os.path.isfile(fontinfo_path): _parse_fontinfo_file(options, fontinfo_path) else: _parse_fontinfo_file(options, parsed_args.fontinfo_file) return options, parsed_args def main(args=None): options, pargs = get_options(args) try: hintFiles(options) except Exception as ex: if pargs.traceback: raise logging.error(ex) return 1 class ReportOptions(ACOptions): def __init__(self, pargs): super(ReportOptions, self).__init__() self.hintAll = True self.noFlex = True self.allow_no_blues = True self.logOnly = True self.inputPaths = pargs.font_paths self.outputPaths = pargs.output_paths self.report_stems = not pargs.alignment_zones self.report_zones = pargs.alignment_zones self.report_all_stems = pargs.all_stems def get_stemhist_options(args): parser = argparse.ArgumentParser( formatter_class=_CustomHelpFormatter, description='Stem and Alignment zones report for PostScript, ' 'OpenType/CFF and UFO fonts.' ) parser.add_argument( 'font_paths', metavar='FONT', nargs='+', type=_validate_path, help='Type1/CFF/OTF/UFO font files' ) parser.add_argument( '-o', '--output', metavar='PATH', nargs='+', dest='output_paths', type=_check_save_path, help='When this is specified, the path argument is used as the base ' 'path for the reports. Otherwise, the font file path is used as ' 'the base path. Several reports are written, using the base path ' 'name plus an extension.\n' 'Without the -z option, the report files are:\n' ' .hstm.txt The horizontal stems.\n' ' .vstm.txt The vertical stems.\n' 'With the -z option, the report files are:\n' ' .top.txt The top zones.\n' ' .bot.txt The bottom zones.' ) parser.add_argument( '-z', '--alignment-zones', action='store_true', dest='alignment_zones', help='Print alignment zone report rather than stem report.' ) parser.add_argument( '-a', '--all', action='store_true', dest='all_stems', help='Include stems formed by curved line segments; by default, ' 'includes only stems formed by straight line segments.' ) glyphs_parser = parser.add_mutually_exclusive_group() glyphs_parser.add_argument( '-g', '--glyphs', metavar='GLYPH_LIST', dest='glyphs_to_hint', type=_split_comma_sequence, default=[], help='comma-separated sequence of glyphs to process\n' 'The glyph identifiers may be glyph indexes, glyph names, or ' 'glyph CIDs. CID values must be prefixed with a forward slash.\n' 'Examples:\n' ' psautohint -g A,B,C,69 MyFont.ufo\n' ' psautohint -g /103,/434,68 MyCIDFont' ) glyphs_parser.add_argument( '--glyphs-file', metavar='PATH', dest='glyphs_to_hint_file', type=_validate_path, help='file containing a list of glyphs to process\n' 'The file must contain a comma-separated sequence of glyph ' 'identifiers.' ) glyphs_parser.add_argument( '-x', '--exclude-glyphs', metavar='GLYPH_LIST', dest='glyphs_to_not_hint', type=_split_comma_sequence, default=[], help='comma-separated sequence of glyphs to NOT process\n' "Counterpart to '--glyphs' option." ) glyphs_parser.add_argument( '--exclude-glyphs-file', metavar='PATH', dest='glyphs_to_not_hint_file', type=_validate_path, help='file containing a list of glyphs to NOT process\n' "Counterpart to '--glyphs-file' option." ) parser.add_argument( '-v', '--verbose', action='count', default=0, help='verbose mode\n' 'Use -vv for extra-verbose mode.' ) parser.add_argument( '--version', action='version', version=__version__ ) parser.add_argument( '--traceback', action='store_true', help="show traceback for exceptions.", ) parsed_args = parser.parse_args(args) if parsed_args.verbose == 0: log_level = logging.WARNING elif parsed_args.verbose == 1: log_level = logging.INFO else: log_level = logging.DEBUG logging.basicConfig(format="%(levelname)s: %(message)s", level=log_level) for handler in logging.root.handlers: handler.addFilter(DuplicateMessageFilter()) if (parsed_args.output_paths and len(parsed_args.font_paths) != len(parsed_args.output_paths)): parser.error("number of input and output fonts differ") if len(parsed_args.font_paths) != len(set(parsed_args.font_paths)): parser.error("found duplicate font inputs") options = ReportOptions(parsed_args) options.font_format = _validate_font_paths(parsed_args.font_paths, parser) if parsed_args.glyphs_to_hint: options.glyphList = _process_glyph_list_arg( parsed_args.glyphs_to_hint, options.nameAliases) elif parsed_args.glyphs_to_not_hint: options.excludeGlyphList = True options.glyphList = _process_glyph_list_arg( parsed_args.glyphs_to_not_hint, options.nameAliases) elif parsed_args.glyphs_to_hint_file: options.glyphList = _process_glyph_list_arg( _read_txt_file(parsed_args.glyphs_to_hint_file), options.nameAliases) elif parsed_args.glyphs_to_not_hint_file: options.excludeGlyphList = True options.glyphList = _process_glyph_list_arg( _read_txt_file(parsed_args.glyphs_to_not_hint_file), options.nameAliases) return options, parsed_args def stemhist(args=None): options, pargs = get_stemhist_options(args) try: hintFiles(options) except Exception as ex: if pargs.traceback: raise logging.error(ex) return 1 if __name__ == '__main__': sys.exit(main()) psautohint-2.3.0/python/psautohint/_psautohint.c000066400000000000000000000263361401523215600221430ustar00rootroot00000000000000/* * Copyright 2014 Adobe Systems Incorporated. All rights reserved. * Copyright 2017 Khaled Hosny * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use these files except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define PY_SSIZE_T_CLEAN 1 #include #include #include #include #include #include "psautohint.h" static void reportCB(char* msg, int level) { static PyObject* logger = NULL; if (logger == NULL) { PyObject* logging = PyImport_ImportModule("logging"); if (logging == NULL) return; logger = PyObject_CallMethod(logging, "getLogger", "s", "_psautohint"); if (logger == NULL) return; } switch (level) { case AC_LogDebug: PyObject_CallMethod(logger, "debug", "s", msg); break; case AC_LogInfo: PyObject_CallMethod(logger, "info", "s", msg); break; case AC_LogWarning: PyObject_CallMethod(logger, "warning", "s", msg); break; case AC_LogError: PyObject_CallMethod(logger, "error", "s", msg); break; default: break; } } static void charZoneCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "charZone %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void stemZoneCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "stemZone %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void hstemCB(float top, float bottom, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "HStem %s top %f bottom %f\n", glyphName, (double)top, (double)bottom); } static void vstemCB(float right, float left, char* glyphName, void* userData) { ACBufferWriteF((ACBuffer*)userData, "VStem %s right %f left %f\n", glyphName, (double)right, (double)left); } static void reportRetry(void* userData) { ACBufferReset((ACBuffer*)userData); } static void* memoryManager(void* ctx, void* ptr, size_t size) { if (!ptr && !size) return NULL; if (ptr && size) ptr = PyMem_RawRealloc(ptr, size); else if (size) ptr = PyMem_RawCalloc(1, size); else PyMem_RawFree(ptr); return ptr; } static PyObject* PsAutoHintError; static char autohint_doc[] = "Autohint glyphs.\n" "\n" "Signature:\n" " autohint(font_info, glyphs[, no_edit, allow_hint_sub, round])\n" "\n" "Args:\n" " font_info: font information.\n" " glyph: glyph data in bez format.\n" " allow_edit: allow editing (changing) the paths when hinting.\n" " allow_hint_sub: no multiple layers of coloring.\n" " round: round coordinates.\n" "\n" "Output:\n" " Autohinted glyph data in bez format.\n" "\n" "Raises:\n" " psautohint.error: If autohinting fails.\n"; static PyObject* autohint(PyObject* self, PyObject* args) { int allowEdit = true, roundCoords = true, allowHintSub = true; int report = 0, allStems = false; PyObject* fontObj = NULL; PyObject* inObj = NULL; PyObject* outObj = NULL; char* inData = NULL; char* fontInfo = NULL; bool error = true; ACBuffer* reportBuffer = NULL; if (!PyArg_ParseTuple(args, "O!O!|iiiii", &PyBytes_Type, &fontObj, &PyBytes_Type, &inObj, &allowEdit, &allowHintSub, &roundCoords, &report, &allStems)) return NULL; if (report) { reportBuffer = ACBufferNew(150); allowEdit = allowHintSub = false; switch (report) { case 1: AC_SetReportRetryCB(reportRetry, (void*)reportBuffer); AC_SetReportZonesCB(charZoneCB, stemZoneCB, (void*)reportBuffer); break; case 2: AC_SetReportRetryCB(reportRetry, (void*)reportBuffer); AC_SetReportStemsCB(hstemCB, vstemCB, allStems, (void*)reportBuffer); break; default: PyErr_SetString(PyExc_ValueError, "Invalid \"report\" argument, must be 1 or 2"); goto done; } } AC_SetMemManager(NULL, memoryManager); AC_SetReportCB(reportCB); fontInfo = PyBytes_AsString(fontObj); inData = PyBytes_AsString(inObj); if (inData && fontInfo) { int result = -1; ACBuffer* output = ACBufferNew(4 * strlen(inData)); if (output) { result = AutoHintString(inData, fontInfo, output, allowEdit, allowHintSub, roundCoords); if (result == AC_Success) { char* data; size_t len; error = false; if (reportBuffer) ACBufferRead(reportBuffer, &data, &len); else ACBufferRead(output, &data, &len); outObj = PyBytes_FromStringAndSize(data, len); } } ACBufferFree(output); output=NULL; if (result != AC_Success) { switch (result) { case -1: /* Do nothing, we already called PyErr_* */ break; case AC_FatalError: PyErr_SetString(PsAutoHintError, "Fatal error"); break; case AC_InvalidParameterError: PyErr_SetString(PyExc_ValueError, "Invalid glyph data"); break; case AC_UnknownError: default: PyErr_SetString(PsAutoHintError, "Hinting failed"); break; } } } done: ACBufferFree(reportBuffer); reportBuffer = NULL; AC_initCallGlobals(); /* clear out references to reportBuffer */ if (error) return NULL; return outObj; } static char autohintmm_doc[] = "Autohint glyphs.\n" "\n" "Signature:\n" " autohintm(font_info, glyphs)\n" "\n" "Args:\n" " glyphs: sequence of glyph data in bez format.\n" " masters: sequence of master names.\n" "\n" "Output:\n" " Sequence of autohinted glyph data in bez format.\n" "\n" "Raises:\n" " psautohint.error: If autohinting fails.\n"; static PyObject* autohintmm(PyObject* self, PyObject* args) { PyObject* inObj = NULL; Py_ssize_t inCount = 0; PyObject* mastersObj = NULL; Py_ssize_t mastersCount = 0; PyObject* outSeq = NULL; const char** masters; bool error = true; Py_ssize_t i; if (!PyArg_ParseTuple(args, "O!O!", &PyTuple_Type, &inObj, &PyTuple_Type, &mastersObj)) return NULL; inCount = PyTuple_GET_SIZE(inObj); mastersCount = PyTuple_GET_SIZE(mastersObj); if (inCount != mastersCount) { PyErr_SetString( PyExc_TypeError, "Length of \"glyphs\" must equal length of \"masters\"."); return NULL; } if (inCount <= 1) { PyErr_SetString(PyExc_TypeError, "Length of input glyphs must be > 1"); return NULL; } masters = PyMem_RawCalloc(mastersCount, sizeof(char*)); if (!masters) { PyErr_NoMemory(); return NULL; } for (i = 0; i < mastersCount; i++) { PyObject* obj = PyTuple_GET_ITEM(mastersObj, i); masters[i] = PyBytes_AsString(obj); if (!masters[i]) goto done; } AC_SetMemManager(NULL, memoryManager); AC_SetReportCB(reportCB); outSeq = PyTuple_New(inCount); if (outSeq) { int result = -1; const char** inGlyphs = PyMem_RawCalloc(inCount, sizeof(char*)); ACBuffer** outGlyphs = PyMem_RawCalloc(inCount, sizeof(ACBuffer*)); if (!inGlyphs || !outGlyphs) { PyErr_NoMemory(); goto finish; } for (i = 0; i < inCount; i++) { PyObject* glyphObj = PyTuple_GET_ITEM(inObj, i); inGlyphs[i] = PyBytes_AsString(glyphObj); if (!inGlyphs[i]) goto finish; outGlyphs[i] = ACBufferNew(4 * strlen(inGlyphs[i])); } result = AutoHintStringMM(inGlyphs, mastersCount, masters, outGlyphs); if (result == AC_Success) { error = false; for (i = 0; i < inCount; i++) { PyObject* outObj; char* data; size_t len; ACBufferRead(outGlyphs[i], &data, &len); outObj = PyBytes_FromStringAndSize(data, len); PyTuple_SET_ITEM(outSeq, i, outObj); } } finish: if (outGlyphs) { for (i = 0; i < inCount; i++) { ACBufferFree(outGlyphs[i]); outGlyphs[i] = NULL; } } PyMem_RawFree(inGlyphs); PyMem_RawFree(outGlyphs); if (result != AC_Success) { switch (result) { case -1: /* Do nothing, we already called PyErr_* */ break; case AC_FatalError: PyErr_SetString(PsAutoHintError, "Fatal error"); break; case AC_InvalidParameterError: PyErr_SetString(PyExc_ValueError, "Invalid glyph data"); break; case AC_UnknownError: default: PyErr_SetString(PsAutoHintError, "Hinting failed"); break; } } } done: PyMem_RawFree(masters); if (error) { Py_XDECREF(outSeq); return NULL; } return outSeq; } /* clang-format off */ static PyMethodDef psautohint_methods[] = { { "autohint", autohint, METH_VARARGS, autohint_doc }, { "autohintmm", autohintmm, METH_VARARGS, autohintmm_doc }, { NULL, NULL, 0, NULL } }; /* clang-format on */ static char psautohint_doc[] = "Python wrapper for Adobe's PostScrupt autohinter.\n" "\n" "autohint() -- Autohint glyphs.\n"; #define SETUPMODULE \ PyModule_AddStringConstant(m, "version", AC_getVersion()); \ PsAutoHintError = PyErr_NewException("psautohint.error", NULL, NULL); \ Py_INCREF(PsAutoHintError); \ PyModule_AddObject(m, "error", PsAutoHintError); /* clang-format off */ static struct PyModuleDef psautohint_module = { PyModuleDef_HEAD_INIT, "_psautohint", psautohint_doc, 0, psautohint_methods, NULL, NULL, NULL, NULL }; /* clang-format on */ PyMODINIT_FUNC PyInit__psautohint(void) { PyObject* m; m = PyModule_Create(&psautohint_module); if (m == NULL) return NULL; SETUPMODULE return m; } psautohint-2.3.0/python/psautohint/autohint.py000066400000000000000000000765301401523215600216500ustar00rootroot00000000000000# Copyright 2016 Adobe. All rights reserved. # Methods: # Parse args. If glyphlist is from file, read in entire file as single string, # and remove all white space, then parse out glyph-names and GID's. # For each font name: # Use fontTools library to open font and extract CFF table. # If error, skip font and report error. # Filter specified glyph list, if any, with list of glyphs in the font. # Open font plist file, if any. If not, create empty font plist. # Build alignment zone string # For identifier in glyph-list: # Get T2 charstring for glyph from parent font CFF table. If not present, # report and skip. # Get new alignment zone string if FDarray index (which font dict is used) # has changed. # Convert to bez # Build autohint point list string; this is used to tell if glyph has been # changed since the last time it was hinted. # If requested, check against plist dict, and skip if glyph is already # hinted or is manually hinted. # Call autohint library on bez string. # If change to the point list is permitted and happened, rebuild. # Autohint point list string. # Convert bez string to T2 charstring, and update parent font CFF. # Add glyph hint entry to plist file # Save font plist file. import ast import logging import os import sys import time from collections import defaultdict, namedtuple from .otfFont import CFFFontData from .ufoFont import UFOFontData from ._psautohint import error as PsAutoHintCError from . import (get_font_format, hint_bez_glyph, hint_compatible_bez_glyphs, FontParseError) log = logging.getLogger(__name__) class ACOptions(object): def __init__(self): self.inputPaths = [] self.outputPaths = [] self.reference_font = None self.glyphList = [] self.nameAliases = {} self.excludeGlyphList = False self.hintAll = False self.read_hints = False self.allowChanges = False self.noFlex = False self.noHintSub = False self.allow_no_blues = False self.hCounterGlyphs = [] self.vCounterGlyphs = [] self.logOnly = False self.printDefaultFDDict = False self.printFDDictList = False self.round_coords = True self.writeToDefaultLayer = False self.baseMaster = {} self.font_format = None self.report_zones = False self.report_stems = False self.report_all_stems = False def __str__(self): # used only when debugging. import inspect data = [] methodList = inspect.getmembers(self) for fname, fvalue in methodList: if fname[0] == "_": continue data.append(str((fname, fvalue))) data.append("") return os.linesep.join(data) class ACHintError(Exception): pass class GlyphReports: def __init__(self): self.glyphs = {} def addGlyphReport(self, glyphName, reportString): hstems = {} vstems = {} hstems_pos = {} vstems_pos = {} char_zones = {} stem_zone_stems = {} self.glyphs[glyphName] = [hstems, vstems, char_zones, stem_zone_stems] lines = reportString.splitlines() for line in lines: tokens = line.split() key = tokens[0] x = ast.literal_eval(tokens[3]) y = ast.literal_eval(tokens[5]) hintpos = "%s %s" % (x, y) if key == "charZone": char_zones[hintpos] = (x, y) elif key == "stemZone": stem_zone_stems[hintpos] = (x, y) elif key == "HStem": width = x - y # avoid counting duplicates if hintpos not in hstems_pos: count = hstems.get(width, 0) hstems[width] = count + 1 hstems_pos[hintpos] = width elif key == "VStem": width = x - y # avoid counting duplicates if hintpos not in vstems_pos: count = vstems.get(width, 0) vstems[width] = count + 1 vstems_pos[hintpos] = width else: raise FontParseError("Found unknown keyword %s in report file " "for glyph %s." % (key, glyphName)) @staticmethod def round_value(val): if val >= 0: return int(val + 0.5) else: return int(val - 0.5) def parse_stem_dict(self, stem_dict): """ stem_dict: {45.5: 1, 47.0: 2} """ # key: stem width # value: stem count width_dict = defaultdict(int) for width, count in stem_dict.items(): width = self.round_value(width) width_dict[width] += count return width_dict def parse_zone_dicts(self, char_dict, stem_dict): all_zones_dict = char_dict.copy() all_zones_dict.update(stem_dict) # key: zone height # value: zone count top_dict = defaultdict(int) bot_dict = defaultdict(int) for top, bot in all_zones_dict.values(): top = self.round_value(top) top_dict[top] += 1 bot = self.round_value(bot) bot_dict[bot] += 1 return top_dict, bot_dict def assemble_rep_list(self, items_dict, count_dict): # item 0: stem/zone count # item 1: stem width/zone height # item 2: list of glyph names gorder = list(self.glyphs.keys()) rep_list = [] for item in items_dict: gnames = list(items_dict[item]) # sort the names by the font's glyph order if len(gnames) > 1: gindexes = [gorder.index(gname) for gname in gnames] gnames = [x for _, x in sorted(zip(gindexes, gnames))] rep_list.append((count_dict[item], item, gnames)) return rep_list def _get_lists(self): """ self.glyphs is a dictionary: key: glyph name value: list of 4 dictionaries hstems vstems char_zones stem_zone_stems { 'A': [{45.5: 1, 47.0: 2}, {229.0: 1}, {}, {}], 'B': [{46.0: 2, 46.5: 2, 47.0: 1}, {94.0: 1, 100.0: 1}, {}, {}], 'C': [{50.0: 2}, {109.0: 1}, {}, {}], 'D': [{46.0: 1, 46.5: 2, 47.0: 1}, {95.0: 1, 109.0: 1}, {}, {}], 'E': [{46.5: 2, 47.0: 1, 50.0: 2, 177.0: 1, 178.0: 1}, {46.0: 1, 75.5: 2, 95.0: 1}, {}, {}], 'F': [{46.5: 2, 47.0: 1, 50.0: 1, 177.0: 1}, {46.0: 1, 60.0: 1, 75.5: 1, 95.0: 1}, {}, {}], 'G': [{43.0: 1, 44.5: 1, 50.0: 1}, {94.0: 1, 109.0: 1}, {}, {}] } """ h_stem_items_dict = defaultdict(set) h_stem_count_dict = defaultdict(int) v_stem_items_dict = defaultdict(set) v_stem_count_dict = defaultdict(int) top_zone_items_dict = defaultdict(set) top_zone_count_dict = defaultdict(int) bot_zone_items_dict = defaultdict(set) bot_zone_count_dict = defaultdict(int) for gName, dicts in self.glyphs.items(): hStemDict, vStemDict, charZoneDict, stemZoneStemDict = dicts glyph_h_stem_dict = self.parse_stem_dict(hStemDict) glyph_v_stem_dict = self.parse_stem_dict(vStemDict) for stem_width, stem_count in glyph_h_stem_dict.items(): h_stem_items_dict[stem_width].add(gName) h_stem_count_dict[stem_width] += stem_count for stem_width, stem_count in glyph_v_stem_dict.items(): v_stem_items_dict[stem_width].add(gName) v_stem_count_dict[stem_width] += stem_count glyph_top_zone_dict, glyph_bot_zone_dict = self.parse_zone_dicts( charZoneDict, stemZoneStemDict) for zone_height, zone_count in glyph_top_zone_dict.items(): top_zone_items_dict[zone_height].add(gName) top_zone_count_dict[zone_height] += zone_count for zone_height, zone_count in glyph_bot_zone_dict.items(): bot_zone_items_dict[zone_height].add(gName) bot_zone_count_dict[zone_height] += zone_count # item 0: stem count # item 1: stem width # item 2: list of glyph names h_stem_list = self.assemble_rep_list( h_stem_items_dict, h_stem_count_dict) v_stem_list = self.assemble_rep_list( v_stem_items_dict, v_stem_count_dict) # item 0: zone count # item 1: zone height # item 2: list of glyph names top_zone_list = self.assemble_rep_list( top_zone_items_dict, top_zone_count_dict) bot_zone_list = self.assemble_rep_list( bot_zone_items_dict, bot_zone_count_dict) return h_stem_list, v_stem_list, top_zone_list, bot_zone_list @staticmethod def _sort_count(t): """ sort by: count (1st item), value (2nd item), list of glyph names (3rd item) """ return (-t[0], -t[1], t[2]) @staticmethod def _sort_val(t): """ sort by: value (2nd item), count (1st item), list of glyph names (3rd item) """ return (t[1], -t[0], t[2]) @staticmethod def _sort_val_reversed(t): """ sort by: value (2nd item), count (1st item), list of glyph names (3rd item) """ return (-t[1], -t[0], t[2]) def save(self, path): h_stems, v_stems, top_zones, bot_zones = self._get_lists() items = ([h_stems, self._sort_count], [v_stems, self._sort_count], [top_zones, self._sort_val_reversed], [bot_zones, self._sort_val]) atime = time.asctime() suffixes = (".hstm.txt", ".vstm.txt", ".top.txt", ".bot.txt") titles = ("Horizontal Stem List for %s on %s\n" % (path, atime), "Vertical Stem List for %s on %s\n" % (path, atime), "Top Zone List for %s on %s\n" % (path, atime), "Bottom Zone List for %s on %s\n" % (path, atime), ) headers = (["count width glyphs\n"] * 2 + ["count height glyphs\n"] * 2) for i, item in enumerate(items): reps, sortFunc = item if not reps: continue fName = f'{path}{suffixes[i]}' title = titles[i] header = headers[i] with open(fName, "w") as fp: fp.write(title) fp.write(header) reps.sort(key=sortFunc) for rep in reps: gnames = ' '.join(rep[2]) fp.write(f"{rep[0]:5} {rep[1]:5} [{gnames}]\n") log.info("Wrote %s" % fName) def getGlyphID(glyphTag, fontGlyphList): if glyphTag in fontGlyphList: return fontGlyphList.index(glyphTag) return None def getGlyphNames(glyphTag, fontGlyphList, fontFileName): glyphNameList = [] rangeList = glyphTag.split("-") prevGID = getGlyphID(rangeList[0], fontGlyphList) if prevGID is None: if len(rangeList) > 1: log.warning("glyph ID <%s> in range %s from glyph selection " "list option is not in font. <%s>.", rangeList[0], glyphTag, fontFileName) else: log.warning("glyph ID <%s> from glyph selection list option " "is not in font. <%s>.", rangeList[0], fontFileName) return None glyphNameList.append(fontGlyphList[prevGID]) for glyphTag2 in rangeList[1:]: gid = getGlyphID(glyphTag2, fontGlyphList) if gid is None: log.warning("glyph ID <%s> in range %s from glyph selection " "list option is not in font. <%s>.", glyphTag2, glyphTag, fontFileName) return None for i in range(prevGID + 1, gid + 1): glyphNameList.append(fontGlyphList[i]) prevGID = gid return glyphNameList def filterGlyphList(options, fontGlyphList, fontFileName): # Return the list of glyphs which are in the intersection of the argument # list and the glyphs in the font. # Complain about glyphs in the argument list which are not in the font. if not options.glyphList: glyphList = fontGlyphList else: # expand ranges: glyphList = [] for glyphTag in options.glyphList: glyphNames = getGlyphNames(glyphTag, fontGlyphList, fontFileName) if glyphNames is not None: glyphList.extend(glyphNames) if options.excludeGlyphList: glyphList = [n for n in fontGlyphList if n not in glyphList] return glyphList fontInfoKeywordList = [ 'FontName', # string 'OrigEmSqUnits', 'LanguageGroup', 'DominantV', # array 'DominantH', # array 'FlexOK', # string 'BlueFuzz', 'VCounterChars', # counter 'HCounterChars', # counter 'BaselineYCoord', 'BaselineOvershoot', 'CapHeight', 'CapOvershoot', 'LcHeight', 'LcOvershoot', 'AscenderHeight', 'AscenderOvershoot', 'FigHeight', 'FigOvershoot', 'Height5', 'Height5Overshoot', 'Height6', 'Height6Overshoot', 'DescenderOvershoot', 'DescenderHeight', 'SuperiorOvershoot', 'SuperiorBaseline', 'OrdinalOvershoot', 'OrdinalBaseline', 'Baseline5Overshoot', 'Baseline5', 'Baseline6Overshoot', 'Baseline6', ] def openFile(path, options): font_format = get_font_format(path) if font_format is None: raise FontParseError(f"{path} is not a supported font format") if font_format == "UFO": font = UFOFontData(path, options.logOnly, options.writeToDefaultLayer) else: font = CFFFontData(path, font_format) return font def get_glyph_list(options, font, path): filename = os.path.basename(path) # filter specified list, if any, with font list. glyph_list = filterGlyphList(options, font.getGlyphList(), filename) if not glyph_list: raise FontParseError("Selected glyph list is empty for font <%s>." % filename) return glyph_list def get_bez_glyphs(options, font, glyph_list): glyphs = {} for name in glyph_list: # Convert to bez format try: bez_glyph = font.convertToBez(name, options.read_hints, options.round_coords, options.hintAll) if bez_glyph is None or "mt" not in bez_glyph: # skip empty glyphs. continue except KeyError: # Source fonts may be sparse, e.g. be a subset of the # reference font. bez_glyph = None glyphs[name] = GlyphEntry(bez_glyph, font) total = len(glyph_list) processed = len(glyphs) if processed != total: log.info("Skipped %s of %s glyphs.", total - processed, total) return glyphs def get_fontinfo_list(options, font, glyph_list, is_var=False): # Check counter glyphs, if any. counter_glyphs = options.hCounterGlyphs + options.vCounterGlyphs if counter_glyphs: missing = [n for n in counter_glyphs if n not in font.getGlyphList()] if missing: log.error("H/VCounterChars glyph named in fontinfo is " "not in font: %s", missing) # For Type1 name keyed fonts, psautohint supports defining # different alignment zones for different glyphs by FontDict # entries in the fontinfo file. This is NOT supported for CID # or CFF2 fonts, as these have FDArrays, can can truly support # different Font.Dict.Private Dicts for different groups of glyphs. if font.hasFDArray(): return get_fontinfo_list_withFDArray(options, font, glyph_list, is_var) else: return get_fontinfo_list_withFontInfo(options, font, glyph_list) def get_fontinfo_list_withFDArray(options, font, glyph_list, is_var=False): lastFDIndex = None fontinfo_list = {} for name in glyph_list: # get new fontinfo string if FDarray index has changed, # as each FontDict has different alignment zones. fdIndex = font.getfdIndex(name) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fddict = font.getFontInfo(options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fontinfo = fddict.getFontInfo() fontinfo_list[name] = (fontinfo, None, None) return fontinfo_list def get_fontinfo_list_withFontInfo(options, font, glyph_list): # Build alignment zone string if options.printDefaultFDDict: print("Showing default FDDict Values:") fddict = font.getFontInfo(options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs) # Exit by printing default FDDict with all lines indented by one tab sys.exit("\t" + "\n\t".join(fddict.getFontInfo().split("\n"))) fdglyphdict, fontDictList = font.getfdInfo(options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, glyph_list) if options.printFDDictList: # Print the user defined FontDicts, and exit. if fdglyphdict: print("Showing user-defined FontDict Values:\n") for fi, fontDict in enumerate(fontDictList): print(fontDict.DictName) print(fontDict.getFontInfo()) gnameList = [] # item = [glyphName, [fdIndex, glyphListIndex]] itemList = sorted(fdglyphdict.items(), key=lambda x: x[1][1]) for gName, entry in itemList: if entry[0] == fi: gnameList.append(gName) print("%d glyphs:" % len(gnameList)) if len(gnameList) > 0: gTxt = " ".join(gnameList) else: gTxt = "None" print(gTxt + "\n") else: print("There are no user-defined FontDict Values.") return if fdglyphdict is None: fddict = fontDictList[0] fontinfo = fddict.getFontInfo() else: log.info("Using alternate FDDict global values from fontinfo " "file for some glyphs.") lastFDIndex = None fontinfo_list = {} for name in glyph_list: if fdglyphdict is not None: fdIndex = fdglyphdict[name][0] if lastFDIndex != fdIndex: lastFDIndex = fdIndex fddict = fontDictList[fdIndex] fontinfo = fddict.getFontInfo() fontinfo_list[name] = (fontinfo, fddict, fdglyphdict) return fontinfo_list class MMHintInfo: def __init__(self, glyph_name=None): self.defined = False self.h_order = None self.v_order = None self.hint_masks = [] self.glyph_name = glyph_name # bad_hint_idxs contains the hint pair indices for all the bad # hint pairs in any of the fonts for the current glyph. self.bad_hint_idxs = set() self.cntr_masks = [] self.new_cntr_masks = [] self.glyph_programs = None @property def needs_fix(self): return len(self.bad_hint_idxs) > 0 def hint_glyph(options, name, bez_glyph, fontinfo): try: hinted = hint_bez_glyph(fontinfo, bez_glyph, options.allowChanges, not options.noHintSub, options.round_coords, options.report_zones, options.report_stems, options.report_all_stems) except PsAutoHintCError: raise ACHintError("%s: Failure in processing outline data." % options.nameAliases.get(name, name)) return hinted def hint_compatible_glyphs(options, name, bez_glyphs, masters, fontinfo): # This function is used by both # hint_with_reference_font->hint_compatible_fonts # and hint_vf_font. try: ref_master = masters[0] # ************************************************************* # *********** DO NOT DELETE THIS COMMENTED-OUT CODE *********** # If you're tempted to "clean up", work on solving # https://github.com/adobe-type-tools/psautohint/issues/202 # first, then you can uncomment the "hint_compatible_bez_glyphs" # line and remove this and other related comments, as well as # the workaround block following "# else:", below. Thanks. # ************************************************************* # # if False: # # This is disabled because it causes crashes on the CI servers # # which are not reproducible locally. The branch below is a hack # # to avoid the crash and should be dropped once the crash is # # fixed, https://github.com/adobe-type-tools/psautohint/pull/131 # hinted = hint_compatible_bez_glyphs( # fontinfo, bez_glyphs, masters) # *** see https://github.com/adobe-type-tools/psautohint/issues/202 *** # else: hinted = [] hinted_ref_bez = hint_glyph(options, name, bez_glyphs[0], fontinfo) for i, bez in enumerate(bez_glyphs[1:]): if bez is None: out = [hinted_ref_bez, None] else: in_bez = [hinted_ref_bez, bez] in_masters = [ref_master, masters[i + 1]] out = hint_compatible_bez_glyphs(fontinfo, in_bez, in_masters) # out is [hinted_ref_bez, new_hinted_region_bez] if i == 0: hinted = out else: hinted.append(out[1]) except PsAutoHintCError: raise ACHintError("%s: Failure in processing outline data." % options.nameAliases.get(name, name)) return hinted def get_glyph_reports(options, font, glyph_list, fontinfo_list): reports = GlyphReports() glyphs = get_bez_glyphs(options, font, glyph_list) for name in glyphs: if name == ".notdef": continue bez_glyph = glyphs[name][0] fontinfo = fontinfo_list[name][0] report = hint_glyph(options, name, bez_glyph, fontinfo) reports.addGlyphReport(name, report.strip()) return reports GlyphEntry = namedtuple("GlyphEntry", "bez_data,font") def hint_font(options, font, glyph_list, fontinfo_list): aliases = options.nameAliases hinted = {} glyphs = get_bez_glyphs(options, font, glyph_list) for name in glyphs: g_entry = glyphs[name] fontinfo, fddict, fdglyphdict = fontinfo_list[name] if fdglyphdict: log.info("%s: Begin hinting (using fdDict %s).", aliases.get(name, name), fddict.DictName) else: log.info("%s: Begin hinting.", aliases.get(name, name)) # Call auto-hint library on bez string. new_bez_glyph = hint_glyph(options, name, g_entry.bez_data, fontinfo) options.baseMaster[name] = new_bez_glyph if not ("ry" in new_bez_glyph or "rb" in new_bez_glyph or "rm" in new_bez_glyph or "rv" in new_bez_glyph): log.info("%s: No hints added!", aliases.get(name, name)) continue if options.logOnly: continue hinted[name] = GlyphEntry(new_bez_glyph, font) return hinted def hint_compatible_fonts(options, paths, glyphs, fontinfo_list): # glyphs is a list of dicts, one per font. Each dict is keyed by glyph name # and references a tuple of (src bez file, font) aliases = options.nameAliases hinted_glyphs = set() reference_font = None for name in glyphs[0]: fontinfo, _, _ = fontinfo_list[name] log.info("%s: Begin hinting.", aliases.get(name, name)) masters = [os.path.basename(path) for path in paths] bez_glyphs = [g[name].bez_data for g in glyphs] new_bez_glyphs = hint_compatible_glyphs(options, name, bez_glyphs, masters, fontinfo) if options.logOnly: continue if reference_font is None: fonts = [g[name].font for g in glyphs] reference_font = fonts[0] mm_hint_info = MMHintInfo() for i, new_bez_glyph in enumerate(new_bez_glyphs): if new_bez_glyph is not None: g_entry = glyphs[i][name] g_entry.font.updateFromBez(new_bez_glyph, name, mm_hint_info) hinted_glyphs.add(name) # Now check if we need to fix any hint lists. if mm_hint_info.needs_fix: reference_font.fix_glyph_hints(name, mm_hint_info, is_reference_font=True) for font in fonts[1:]: font.fix_glyph_hints(name, mm_hint_info, is_reference_font=False) return len(hinted_glyphs) > 0 def hint_vf_font(options, font_path, out_path): font = openFile(font_path, options) options.noFlex = True # work around for incompatibel flex args. aliases = options.nameAliases glyph_names = get_glyph_list(options, font, font_path) log.info("Hinting font %s. Start time: %s.", font_path, time.asctime()) fontinfo_list = get_fontinfo_list(options, font, glyph_names, True) hinted_glyphs = set() for name in glyph_names: fontinfo, _, _ = fontinfo_list[name] log.info("%s: Begin hinting.", aliases.get(name, name)) bez_glyphs = font.get_vf_bez_glyphs(name) num_masters = len(bez_glyphs) masters = [f"Master-{i}" for i in range(num_masters)] new_bez_glyphs = hint_compatible_glyphs(options, name, bez_glyphs, masters, fontinfo) if None in new_bez_glyphs: log.info(f"Error while hinting glyph {name}.") continue if options.logOnly: continue hinted_glyphs.add(name) # First, convert bez to fontTools T2 programs, # and check if any hints conflict. mm_hint_info = MMHintInfo() for i, new_bez_glyph in enumerate(new_bez_glyphs): if new_bez_glyph is not None: font.updateFromBez(new_bez_glyph, name, mm_hint_info) # Now check if we need to fix any hint lists. if mm_hint_info.needs_fix: font.fix_glyph_hints(name, mm_hint_info) # Now merge the programs into a singel CFF2 charstring program font.merge_hinted_glyphs(name) if hinted_glyphs: log.info(f"Saving font file {out_path} with new hints...") font.save(out_path) else: log.info("No glyphs were hinted.") font.close() log.info("Done with font %s. End time: %s.", font_path, time.asctime()) def hint_with_reference_font(options, fonts, paths, outpaths): # We are doing compatible, AKA multiple master, hinting. log.info("Start time: %s.", time.asctime()) options.noFlex = True # work-around for mm-hinting # Get the glyphs and font info of the reference font. We assume the # fonts have the same glyph set, glyph dict and in general are # compatible. If not bad things will happen. glyph_names = get_glyph_list(options, fonts[0], paths[0]) fontinfo_list = get_fontinfo_list(options, fonts[0], glyph_names) glyphs = [] for i, font in enumerate(fonts): glyphs.append(get_bez_glyphs(options, font, glyph_names)) have_hinted_glyphs = hint_compatible_fonts(options, paths, glyphs, fontinfo_list) if have_hinted_glyphs: log.info("Saving font files with new hints...") for i, font in enumerate(fonts): font.save(outpaths[i]) else: log.info("No glyphs were hinted.") font.close() log.info("End time: %s.", time.asctime()) def hint_regular_fonts(options, fonts, paths, outpaths): # Regular fonts, just iterate over the list and hint each one. for i, font in enumerate(fonts): path = paths[i] outpath = outpaths[i] glyph_names = get_glyph_list(options, font, path) fontinfo_list = get_fontinfo_list(options, font, glyph_names) log.info("Hinting font %s. Start time: %s.", path, time.asctime()) if options.report_zones or options.report_stems: reports = get_glyph_reports(options, font, glyph_names, fontinfo_list) reports.save(outpath) else: hinted = hint_font(options, font, glyph_names, fontinfo_list) if hinted: log.info("Saving font file with new hints...") for name in hinted: g_entry = hinted[name] font.updateFromBez(g_entry.bez_data, name) font.save(outpath) else: log.info("No glyphs were hinted.") font.close() log.info("Done with font %s. End time: %s.", path, time.asctime()) def get_outpath(options, font_path, i): if options.outputPaths is not None and i < len(options.outputPaths): outpath = options.outputPaths[i] else: outpath = font_path return outpath def hintFiles(options): fonts = [] paths = [] outpaths = [] # If there is a reference font, prepend it to font paths. # It must be the first font in the list, code below assumes that. if options.reference_font: font = openFile(options.reference_font, options) fonts.append(font) paths.append(options.reference_font) outpaths.append(options.reference_font) if hasattr(font, 'ttFont'): assert 'fvar' not in font.ttFont, ("Can't use a CFF2 VF font as a " "default font in a set of MM " "fonts.") # Open the rest of the fonts and handle output paths. for i, path in enumerate(options.inputPaths): font = openFile(path, options) out_path = get_outpath(options, path, i) if hasattr(font, 'ttFont') and 'fvar' in font.ttFont: assert not options.report_zones or options.report_stems # Certainly not supported now, also I think it only makes sense # to ask for zone reports for the source fonts for the VF font. # You can't easily change blue values in a VF font. hint_vf_font(options, path, out_path) else: fonts.append(font) paths.append(path) outpaths.append(out_path) if fonts: if fonts[0].isCID(): options.noFlex = True # Flex hinting in CJK fonts doed bad things. # For CFF fonts, being a CID font is a good indicator of being CJK. if options.reference_font: hint_with_reference_font(options, fonts, paths, outpaths) else: hint_regular_fonts(options, fonts, paths, outpaths) psautohint-2.3.0/python/psautohint/fdTools.py000066400000000000000000000467301401523215600214260ustar00rootroot00000000000000# Copyright 2014 Adobe. All rights reserved. """ Tools for processing "fontinfo" files. See "psautohint --doc-fddict", or the MakeOTF user guide for details on this format. The "fontinfo" file is expected to be in the same directory as the input font file. """ import logging import re log = logging.getLogger(__name__) # Tokens seen in font info file that are not # part of a FDDict or GlyphSet definition. kBeginToken = "begin" kEndToken = "end" kFDDictToken = "FDDict" kGlyphSetToken = "GlyphSet" kFinalDictName = "FinalFont" kDefaultDictName = "No Alignment Zones" kBaseStateTokens = [ "FontName", "FullName", "IsBoldStyle", "IsItalicStyle", "ConvertToCID", "PreferOS/2TypoMetrics", "IsOS/2WidthWeigthSlopeOnly", "IsOS/2OBLIQUE", "UseOldNameID4", "LicenseCode" ] kBlueValueKeys = [ "BaselineOvershoot", # 0 "BaselineYCoord", # 1 "CapHeight", # 2 "CapOvershoot", # 3 "LcHeight", # 4 "LcOvershoot", # 5 "AscenderHeight", # 6 "AscenderOvershoot", # 7 "FigHeight", # 8 "FigOvershoot", # 9 "Height5", # 10 "Height5Overshoot", # 11 "Height6", # 12 "Height6Overshoot", # 13 ] kOtherBlueValueKeys = [ "Baseline5Overshoot", # 0 "Baseline5", # 1 "Baseline6Overshoot", # 2 "Baseline6", # 3 "SuperiorOvershoot", # 4 "SuperiorBaseline", # 5 "OrdinalOvershoot", # 6 "OrdinalBaseline", # 7 "DescenderOvershoot", # 8 "DescenderHeight", # 9 ] kOtherFDDictKeys = [ "FontName", "OrigEmSqUnits", "LanguageGroup", "DominantV", "DominantH", "FlexOK", "VCounterChars", "HCounterChars", "BlueFuzz" ] # We keep this in the FDDict, as it is easier # to sort and validate as a list of pairs kFontDictBluePairsName = "BlueValuesPairs" kFontDictOtherBluePairsName = "OtherBlueValuesPairs" # Holds the actual string for the Type1 font dict kFontDictBluesName = "BlueValues" # Holds the actual string for the Type1 font dict kFontDictOtherBluesName = "OtherBlues" kRunTimeFDDictKeys = [ "DictName", kFontDictBluePairsName, kFontDictOtherBluePairsName, kFontDictBluesName, kFontDictOtherBluesName ] kFDDictKeys = (kOtherFDDictKeys + kBlueValueKeys + kOtherBlueValueKeys + kRunTimeFDDictKeys) kFontInfoKeys = (kOtherFDDictKeys + kBlueValueKeys + kOtherBlueValueKeys + ["StemSnapH", "StemSnapV"]) class FontInfoParseError(ValueError): pass class FDDict: def __init__(self): self.DictName = None for key in kFDDictKeys: setattr(self, key, None) self.FlexOK = "true" def getFontInfo(self): fontinfo = [] for key, value in vars(self).items(): if key not in kFontInfoKeys or value is None: continue fontinfo.append("%s %s" % (key, value)) return "\n".join(fontinfo) def buildBlueLists(self): if self.BaselineOvershoot is None: raise FontInfoParseError( "FDDict definition %s is missing the BaselineYCoord/" "BaselineOvershoot values. These are required." % self.DictName) elif int(self.BaselineOvershoot) > 0: raise FontInfoParseError( "The BaselineYCoord/BaselineOvershoot in FDDict definition %s " "must be a bottom zone - the BaselineOvershoot must be " "negative, not positive." % self.DictName) blueKeyList = [kBlueValueKeys, kOtherBlueValueKeys] bluePairListNames = [kFontDictBluePairsName, kFontDictOtherBluePairsName] blueFieldNames = [kFontDictBluesName, kFontDictOtherBluesName] for i in [0, 1]: keyList = blueKeyList[i] fieldName = blueFieldNames[i] pairFieldName = bluePairListNames[i] bluePairList = [] for key in keyList: if key.endswith("Overshoot"): width = getattr(self, key) if width is not None: width = int(width) baseName = key[:-len("Overshoot")] zonePos = None if key == "BaselineOvershoot": zonePos = int(self.BaselineYCoord) tempKey = "BaselineYCoord" else: for posSuffix in ["", "Height", "Baseline"]: tempKey = "%s%s" % (baseName, posSuffix) value = getattr(self, tempKey, None) if value is not None: zonePos = int(value) break if zonePos is None: raise FontInfoParseError( "Failed to find fontinfo FDDict %s top/bottom " "zone name %s to match the zone width key '%s'" "." % (self.DictName, tempKey, key)) if width < 0: topPos = zonePos bottomPos = zonePos + width isBottomZone = 1 if (i == 0) and (key != "BaselineOvershoot"): raise FontInfoParseError( "FontDict %s. Zone %s is a top zone, and " "the width (%s) must be positive." % (self.DictName, tempKey, width)) else: bottomPos = zonePos topPos = zonePos + width isBottomZone = 0 if i == 1: raise FontInfoParseError( "FontDict %s. Zone %s is a bottom zone, " "and so the width (%s) must be negative." % (self.DictName, tempKey, width)) bluePairList.append((topPos, bottomPos, tempKey, self.DictName, isBottomZone)) if bluePairList: bluePairList = sorted(bluePairList) prevPair = bluePairList[0] zoneBuffer = 2 * int(self.BlueFuzz) + 1 for pair in bluePairList[1:]: if prevPair[0] > pair[1]: raise FontInfoParseError( "In FDDict %s. The top of zone %s at %s overlaps " "zone %s with the bottom at %s." % (self.DictName, prevPair[2], prevPair[0], pair[2], pair[1])) elif abs(pair[1] - prevPair[0]) <= zoneBuffer: raise FontInfoParseError( "In FDDict %s. The top of zone %s at %s is within " "the min spearation limit (%s units) of zone %s " "with the bottom at %s." % (self.DictName, prevPair[2], prevPair[0], zoneBuffer, pair[2], pair[1])) prevPair = pair setattr(self, pairFieldName, bluePairList) bluesList = [] for pairEntry in bluePairList: bluesList.append(pairEntry[1]) bluesList.append(pairEntry[0]) bluesList = [str(v) for v in bluesList] bluesList = "[%s]" % (" ".join(bluesList)) setattr(self, fieldName, bluesList) return def __repr__(self): fddict = {} for key, val in vars(self).items(): if val is None: continue fddict[key] = val return "<%s '%s' %s>" % ( self.__class__.__name__, fddict.get('DictName', 'no name'), fddict) def parseFontInfoFile(fontDictList, data, glyphList, maxY, minY, fontName): # fontDictList may or may not already contain a # font dict taken from the source font top FontDict. # The map of glyph names to font dict: the index into fontDictList. fdGlyphDict = {} # The user-specified set of blue values to write into the output font, # some sort of merge of the individual font dicts. May not be supplied. finalFDict = None blueFuzz = fontDictList[0].BlueFuzz # Get rid of comments. data = re.sub(r"#[^\r\n]+[\r\n]", "", data) # We assume that no items contain whitespace. tokenList = data.split() numTokens = len(tokenList) i = 0 baseState = 0 inValue = 1 inDictValue = 2 dictState = 3 glyphSetState = 4 fdIndexDict = {} lenSrcFontDictList = len(fontDictList) dictValueList = [] dictKeyWord = '' state = baseState while i < numTokens: token = tokenList[i] i += 1 if state == baseState: if token == kBeginToken: token = tokenList[i] i += 1 if token == kFDDictToken: state = dictState dictName = tokenList[i] i += 1 fdDict = FDDict() fdDict.DictName = dictName if dictName == kFinalDictName: # This is dict is NOT used to hint any glyphs; it is # used to supply the merged alignment zones and stem # widths for the final font. finalFDict = fdDict else: # save dict and FDIndex. fdIndexDict[dictName] = len(fontDictList) fontDictList.append(fdDict) elif token == kGlyphSetToken: state = glyphSetState setName = tokenList[i] i += 1 else: raise FontInfoParseError( "Unrecognized token after \"begin\" keyword: %s" % token) elif token in kBaseStateTokens: # Discard value for base token. token = tokenList[i] i += 1 if (token[0] in ["[", "("]) and (not token[-1] in ["]", ")"]): state = inValue else: raise FontInfoParseError( "Unrecognized token in base state: %s" % token) elif state == inValue: # We are processing a list value for a base state token. if token[-1] in ["]", ")"]: state = baseState # found the last token in the list value. elif state == inDictValue: dictValueList.append(token) if token[-1] in ["]", ")"]: value = " ".join(dictValueList) setattr(fdDict, dictKeyWord, value) state = dictState # found the last token in the list value. elif state == glyphSetState: # "end GlyphSet" marks end of set, # else we are adding a new glyph name. if (token == kEndToken) and tokenList[i] == kGlyphSetToken: if tokenList[i + 1] != setName: raise FontInfoParseError( "End glyph set name \"%s\" does not match begin glyph " "set name \"%s\"." % (tokenList[i + 1], setName)) state = baseState i += 2 setName = None else: # Need to add matching glyphs. gi = 0 for gname in glyphList: if re.search(token, gname): # fdIndex value fdGlyphDict[gname] = [fdIndexDict[setName], gi] gi += 1 elif state == dictState: # "end FDDict" marks end of set, # else we are adding a new glyph name. if (token == kEndToken) and tokenList[i] == kFDDictToken: if tokenList[i + 1] != dictName: raise FontInfoParseError( "End FDDict name \"%s\" does not match begin FDDict " "name \"%s\"." % (tokenList[i + 1], dictName)) if fdDict.DominantH is None: log.warning("The FDDict '%s' in fontinfo has no " "DominantH value", dictName) if fdDict.DominantV is None: log.warning("The FDDict '%s' in fontinfo has no " "DominantV value", dictName) if fdDict.BlueFuzz is None: fdDict.BlueFuzz = blueFuzz fdDict.buildBlueLists() if fdDict.FontName is None: fdDict.FontName = fontName state = baseState i += 2 dictName = None fdDict = None else: if token in kFDDictKeys: value = tokenList[i] i += 1 if (value[0] in ["[", "("]) and ( not value[-1] in ["]", ")"]): state = inDictValue dictValueList = [value] dictKeyWord = token else: setattr(fdDict, token, value) else: raise FontInfoParseError( "FDDict key \"%s\" in fdDict named \"%s\" is not " "recognized." % (token, dictName)) if lenSrcFontDictList != len(fontDictList): # There are some FDDict definitions. This means that we need # to fix the default fontDict, inherited from the source font, # so that it has blues zones that will not affect hinting, # e.g outside of the Font BBox. We do this becuase if there are # glyphs which are not assigned toa user specified font dict, # it is becuase it doesn't make sense to provide alignment zones # for the glyph. Since psautohint does require at least one bottom zone # and one top zone, we add one bottom and one top zone that are # outside the font BBox, so that hinting won't be affected by them. defaultFDDict = fontDictList[0] for key in kBlueValueKeys + kOtherBlueValueKeys: setattr(defaultFDDict, key, None) defaultFDDict.BaselineYCoord = minY - 100 defaultFDDict.BaselineOvershoot = 0 defaultFDDict.CapHeight = maxY + 100 defaultFDDict.CapOvershoot = 0 defaultFDDict.BlueFuzz = 0 defaultFDDict.DictName = kDefaultDictName # "No Alignment Zones" defaultFDDict.FontName = fontName defaultFDDict.buildBlueLists() gi = 0 for gname in glyphList: if gname not in fdGlyphDict: fdGlyphDict[gname] = [0, gi] gi += 1 return fdGlyphDict, fontDictList, finalFDict def mergeFDDicts(prevDictList, privateDict): # Extract the union of the stem widths and zones from the list # of FDDicts, and replace the current values in the topDict. blueZoneDict = {} otherBlueZoneDict = {} dominantHDict = {} dominantVDict = {} bluePairListNames = [kFontDictBluePairsName, kFontDictOtherBluePairsName] zoneDictList = [blueZoneDict, otherBlueZoneDict] for prefDDict in prevDictList: for ki in [0, 1]: zoneDict = zoneDictList[ki] bluePairName = bluePairListNames[ki] bluePairList = getattr(prefDDict, bluePairName) if not bluePairList: continue for topPos, bottomPos, zoneName, _, isBottomZone in bluePairList: zoneDict[(topPos, bottomPos)] = (isBottomZone, zoneName, prefDDict.DictName) # Now for the stem widths. stemNameList = ["DominantH", "DominantV"] stemDictList = [dominantHDict, dominantVDict] for wi in (0, 1): stemFieldName = stemNameList[wi] dList = getattr(prefDDict, stemFieldName) stemDict = stemDictList[wi] if dList is not None: dList = dList[1:-1] # remove the braces dList = dList.split() dList = [int(d) for d in dList] for width in dList: stemDict[width] = prefDDict.DictName # Now we have collected all the stem widths and zones # from all the dicts. See if we can merge them. goodBlueZoneList = [] goodOtherBlueZoneList = [] goodHStemList = [] goodVStemList = [] zoneDictList = [blueZoneDict, otherBlueZoneDict] goodZoneLists = [goodBlueZoneList, goodOtherBlueZoneList] stemDictList = [dominantHDict, dominantVDict] goodStemLists = [goodHStemList, goodVStemList] for ki in [0, 1]: zoneDict = zoneDictList[ki] goodZoneList = goodZoneLists[ki] stemDict = stemDictList[ki] goodStemList = goodStemLists[ki] # Zones first. zoneList = zoneDict.keys() if not zoneList: continue zoneList = sorted(zoneList) # Now check for conflicts. prevZone = zoneList[0] goodZoneList.append(prevZone[1]) goodZoneList.append(prevZone[0]) zoneBuffer = 2 * prefDDict.BlueFuzz + 1 for zone in zoneList[1:]: curEntry = blueZoneDict[zone] prevEntry = blueZoneDict[prevZone] zoneName = curEntry[1] fdDictName = curEntry[2] prevZoneName = prevEntry[1] prevFDictName = prevEntry[2] if zone[1] < prevZone[0]: log.warning("For final FontDict, skipping zone %s in FDDict %s" " because it overlaps with zone %s in FDDict %s.", zoneName, fdDictName, prevZoneName, prevFDictName) elif abs(zone[1] - prevZone[0]) <= zoneBuffer: log.warning("For final FontDict, skipping zone %s in FDDict %s" " because it is within the minimum separation " "allowed (%s units) of %s in FDDict %s.", zoneName, fdDictName, zoneBuffer, prevZoneName, prevFDictName) else: goodZoneList.append(zone[1]) goodZoneList.append(zone[0]) prevZone = zone stemList = stemDict.keys() if not stemList: continue stemList = sorted(stemList) # Now check for conflicts. prevStem = stemList[0] goodStemList.append(prevStem) for stem in stemList[1:]: if abs(stem - prevStem) < 2: log.warning("For final FontDict, skipping stem width %s in " "FDDict %s because it overlaps in coverage with " "stem width %s in FDDict %s.", stem, stemDict[stem], prevStem, stemDict[prevStem]) else: goodStemList.append(stem) prevStem = stem if goodBlueZoneList: privateDict.BlueValues = goodBlueZoneList if goodOtherBlueZoneList: privateDict.OtherBlues = goodOtherBlueZoneList else: privateDict.OtherBlues = None if goodHStemList: privateDict.StemSnapH = goodHStemList else: privateDict.StemSnapH = None if goodVStemList: privateDict.StemSnapV = goodVStemList else: privateDict.StemSnapV = None return psautohint-2.3.0/python/psautohint/otfFont.py000066400000000000000000001604141401523215600214270ustar00rootroot00000000000000# Copyright 2014 Adobe. All rights reserved. """ Utilities for converting between T2 charstrings and the bez data format. """ import copy import logging import os import re import subprocess import tempfile import itertools from fontTools.misc.psCharStrings import (T2OutlineExtractor, SimpleT2Decompiler) from fontTools.ttLib import TTFont, newTable from fontTools.misc.fixedTools import otRound from fontTools.varLib.varStore import VarStoreInstancer from fontTools.varLib.cff import CFF2CharStringMergePen, MergeOutlineExtractor # import subset.cff is needed to load the implementation for # CFF.desubroutinize: the module adds this class method to the CFF and CFF2 # classes. import fontTools.subset.cff from . import fdTools, FontParseError # keep linting tools quiet about unused import assert fontTools.subset.cff is not None log = logging.getLogger(__name__) kStackLimit = 46 kStemLimit = 96 class SEACError(Exception): pass def _add_method(*clazzes): """Returns a decorator function that adds a new method to one or more classes.""" def wrapper(method): done = [] for clazz in clazzes: if clazz in done: continue # Support multiple names of a clazz done.append(clazz) assert clazz.__name__ != 'DefaultTable', \ 'Oops, table class not found.' assert not hasattr(clazz, method.__name__), \ "Oops, class '%s' has method '%s'." % (clazz.__name__, method.__name__) setattr(clazz, method.__name__, method) return None return wrapper def hintOn(i, hintMaskBytes): # used to add the active hints to the bez string, # when a T2 hintmask operator is encountered. byteIndex = int(i / 8) byteValue = hintMaskBytes[byteIndex] offset = 7 - (i % 8) return ((2**offset) & byteValue) > 0 class T2ToBezExtractor(T2OutlineExtractor): # The T2OutlineExtractor class calls a class method as the handler for each # T2 operator. # I use this to convert the T2 operands and arguments to bez operators. # Note: flex is converted to regular rrcurveto's. # cntrmasks just map to hint replacement blocks with the specified stems. def __init__(self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, read_hints=True, round_coords=True): T2OutlineExtractor.__init__(self, None, localSubrs, globalSubrs, nominalWidthX, defaultWidthX) self.vhints = [] self.hhints = [] self.bezProgram = [] self.read_hints = read_hints self.firstMarkingOpSeen = False self.closePathSeen = False self.subrLevel = 0 self.round_coords = round_coords self.hintMaskBytes = None def execute(self, charString): self.subrLevel += 1 SimpleT2Decompiler.execute(self, charString) self.subrLevel -= 1 if (not self.closePathSeen) and (self.subrLevel == 0): self.closePath() def _point(self, point): if self.round_coords: return " ".join("%d" % round(pt) for pt in point) return " ".join("%3f" % pt for pt in point) def rMoveTo(self, point): point = self._nextPoint(point) if not self.firstMarkingOpSeen: self.firstMarkingOpSeen = True self.bezProgram.append("sc\n") log.debug("moveto %s, curpos %s", point, self.currentPoint) self.bezProgram.append("%s mt\n" % self._point(point)) self.sawMoveTo = True def rLineTo(self, point): if not self.sawMoveTo: self.rMoveTo((0, 0)) point = self._nextPoint(point) log.debug("lineto %s, curpos %s", point, self.currentPoint) self.bezProgram.append("%s dt\n" % self._point(point)) def rCurveTo(self, pt1, pt2, pt3): if not self.sawMoveTo: self.rMoveTo((0, 0)) pt1 = list(self._nextPoint(pt1)) pt2 = list(self._nextPoint(pt2)) pt3 = list(self._nextPoint(pt3)) log.debug("curveto %s %s %s, curpos %s", pt1, pt2, pt3, self.currentPoint) self.bezProgram.append("%s ct\n" % self._point(pt1 + pt2 + pt3)) def op_endchar(self, index): self.endPath() args = self.popallWidth() if args: # It is a 'seac' composite character. Don't process raise SEACError def endPath(self): # In T2 there are no open paths, so always do a closePath when # finishing a sub path. if self.sawMoveTo: log.debug("endPath") self.bezProgram.append("cp\n") self.sawMoveTo = False def closePath(self): self.closePathSeen = True log.debug("closePath") if self.bezProgram and self.bezProgram[-1] != "cp\n": self.bezProgram.append("cp\n") self.bezProgram.append("ed\n") def updateHints(self, args, hint_list, bezCommand): self.countHints(args) # first hint value is absolute hint coordinate, second is hint width if not self.read_hints: return lastval = args[0] arg = str(lastval) hint_list.append(arg) self.bezProgram.append(arg + " ") for i in range(len(args))[1:]: val = args[i] lastval += val if i % 2: arg = str(val) hint_list.append(arg) self.bezProgram.append("%s %s\n" % (arg, bezCommand)) else: arg = str(lastval) hint_list.append(arg) self.bezProgram.append(arg + " ") def op_hstem(self, index): args = self.popallWidth() self.hhints = [] self.updateHints(args, self.hhints, "rb") log.debug("hstem %s", self.hhints) def op_vstem(self, index): args = self.popallWidth() self.vhints = [] self.updateHints(args, self.vhints, "ry") log.debug("vstem %s", self.vhints) def op_hstemhm(self, index): args = self.popallWidth() self.hhints = [] self.updateHints(args, self.hhints, "rb") log.debug("stemhm %s %s", self.hhints, args) def op_vstemhm(self, index): args = self.popallWidth() self.vhints = [] self.updateHints(args, self.vhints, "ry") log.debug("vstemhm %s %s", self.vhints, args) def getCurHints(self, hintMaskBytes): curhhints = [] curvhints = [] numhhints = len(self.hhints) for i in range(int(numhhints / 2)): if hintOn(i, hintMaskBytes): curhhints.extend(self.hhints[2 * i:2 * i + 2]) numvhints = len(self.vhints) for i in range(int(numvhints / 2)): if hintOn(i + int(numhhints / 2), hintMaskBytes): curvhints.extend(self.vhints[2 * i:2 * i + 2]) return curhhints, curvhints def doMask(self, index, bezCommand): args = [] if not self.hintMaskBytes: args = self.popallWidth() if args: self.vhints = [] self.updateHints(args, self.vhints, "ry") self.hintMaskBytes = int((self.hintCount + 7) / 8) self.hintMaskString, index = self.callingStack[-1].getBytes( index, self.hintMaskBytes) if self.read_hints: curhhints, curvhints = self.getCurHints(self.hintMaskString) strout = "" mask = [strout + hex(ch) for ch in self.hintMaskString] log.debug("%s %s %s %s %s", bezCommand, mask, curhhints, curvhints, args) self.bezProgram.append("beginsubr snc\n") for i, hint in enumerate(curhhints): self.bezProgram.append("%s " % hint) if i % 2: self.bezProgram.append("rb\n") for i, hint in enumerate(curvhints): self.bezProgram.append("%s " % hint) if i % 2: self.bezProgram.append("ry\n") self.bezProgram.extend(["endsubr enc\n", "newcolors\n"]) return self.hintMaskString, index def op_hintmask(self, index): hintMaskString, index = self.doMask(index, "hintmask") return hintMaskString, index def op_cntrmask(self, index): hintMaskString, index = self.doMask(index, "cntrmask") return hintMaskString, index def countHints(self, args): self.hintCount = self.hintCount + int(len(args) / 2) def convertT2GlyphToBez(t2CharString, read_hints=True, round_coords=True): # wrapper for T2ToBezExtractor which # applies it to the supplied T2 charstring subrs = getattr(t2CharString.private, "Subrs", []) extractor = T2ToBezExtractor(subrs, t2CharString.globalSubrs, t2CharString.private.nominalWidthX, t2CharString.private.defaultWidthX, read_hints, round_coords) extractor.execute(t2CharString) t2_width_arg = None if extractor.gotWidth and (extractor.width is not None): t2_width_arg = extractor.width - t2CharString.private.nominalWidthX return "".join(extractor.bezProgram), t2_width_arg class HintMask: # class used to collect hints for the current # hint mask when converting bez to T2. def __init__(self, listPos): # The index into the t2list is kept so we can quickly find them later. # Note that t2list has one item per operator, and does not include the # initial hint operators - first op is always [rhv]moveto or endchar. self.listPos = listPos # These contain the actual hint values. self.h_list = [] self.v_list = [] self.mask = None def maskByte(self, hHints, vHints): # return hintmask bytes for known hints. num_hhints = len(hHints) num_vhints = len(vHints) self.byteLength = byteLength = int((7 + num_hhints + num_vhints) / 8) maskVal = 0 byteIndex = 0 mask = b"" if self.h_list: mask, maskVal, byteIndex = self.addMaskBits( hHints, self.h_list, 0, mask, maskVal, byteIndex) if self.v_list: mask, maskVal, byteIndex = self.addMaskBits( vHints, self.v_list, num_hhints, mask, maskVal, byteIndex) if maskVal: mask += bytes([maskVal]) if len(mask) < byteLength: mask += b"\0" * (byteLength - len(mask)) self.mask = mask return mask @staticmethod def addMaskBits(allHints, maskHints, numPriorHints, mask, maskVal, byteIndex): # sort in allhints order. sort_list = [[allHints.index(hint) + numPriorHints, hint] for hint in maskHints if hint in allHints] if not sort_list: # we get here if some hints have been dropped # because of # the stack limit, so that none of the items in maskHints are # not in allHints return mask, maskVal, byteIndex sort_list.sort() (idx_list, maskHints) = zip(*sort_list) for i in idx_list: newbyteIndex = int(i / 8) if newbyteIndex != byteIndex: mask += bytes([maskVal]) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i % 8)) return mask, maskVal, byteIndex @property def num_bits(self): count = sum( [bin(mask_byte).count('1') for mask_byte in bytearray(self.mask)]) return count def make_hint_list(hints, need_hint_masks, is_h): # Add the list of T2 tokens that make up the initial hint operators hint_list = [] lastPos = 0 # In bez terms, the first coordinate in each pair is absolute, # second is relative. # In T2, each term is relative to the previous one. for hint in hints: if not hint: continue pos1 = hint[0] pos = pos1 - lastPos if pos % 1 == 0: pos = int(pos) hint_list.append(pos) pos2 = hint[1] if pos2 % 1 == 0: pos2 = int(pos2) lastPos = pos1 + pos2 hint_list.append(pos2) if need_hint_masks: if is_h: op = "hstemhm" hint_list.append(op) # never need to append vstemhm: if we are using it, it is followed # by a mask command and vstemhm is inferred. else: if is_h: op = "hstem" else: op = "vstem" hint_list.append(op) return hint_list bezToT2 = { "mt": 'rmoveto', "rmt": 'rmoveto', "dt": 'rlineto', "ct": 'rrcurveto', "cp": '', "ed": 'endchar' } kHintArgsNoOverlap = 0 kHintArgsOverLap = 1 kHintArgsMatch = 2 def checkStem3ArgsOverlap(arg_list, hint_list): status = kHintArgsNoOverlap for x0, x1 in arg_list: x1 = x0 + x1 for y0, y1 in hint_list: y1 = y0 + y1 if x0 == y0: if x1 == y1: status = kHintArgsMatch else: return kHintArgsOverLap elif x1 == y1: return kHintArgsOverLap else: if (x0 > y0) and (x0 < y1): return kHintArgsOverLap if (x1 > y0) and (x1 < y1): return kHintArgsOverLap return status def _add_cntr_maskHints(counter_mask_list, src_hints, is_h): for arg_list in src_hints: for mask in counter_mask_list: dst_hints = mask.h_list if is_h else mask.v_list if not dst_hints: dst_hints.extend(arg_list) overlap_status = kHintArgsMatch break overlap_status = checkStem3ArgsOverlap(arg_list, dst_hints) # The args match args in this control mask. if overlap_status == kHintArgsMatch: break if overlap_status != kHintArgsMatch: mask = HintMask(0) counter_mask_list.append(mask) dst_hints.extend(arg_list) def build_counter_mask_list(h_stem3_list, v_stem3_list): v_counter_mask = HintMask(0) h_counter_mask = v_counter_mask counter_mask_list = [h_counter_mask] _add_cntr_maskHints(counter_mask_list, h_stem3_list, is_h=True) _add_cntr_maskHints(counter_mask_list, v_stem3_list, is_h=False) return counter_mask_list def makeRelativeCTArgs(arg_list, curX, curY): newCurX = arg_list[4] newCurY = arg_list[5] arg_list[5] -= arg_list[3] arg_list[4] -= arg_list[2] arg_list[3] -= arg_list[1] arg_list[2] -= arg_list[0] arg_list[0] -= curX arg_list[1] -= curY return arg_list, newCurX, newCurY def build_hint_order(hints): # MM hints have duplicate hints. We want to return a list of indices into # the original unsorted and unfiltered list. The list should be sorted, and # should filter out duplicates num_hints = len(hints) index_list = list(range(num_hints)) hint_list = list(zip(hints, index_list)) hint_list.sort() new_hints = [hint_list[i] for i in range(1, num_hints) if hint_list[i][0] != hint_list[i - 1][0]] new_hints = [hint_list[0]] + new_hints hints, hint_order = list(zip(*new_hints)) # hints is now a list of hint pairs, sorted by increasing bottom edge. # hint_order is now a list of the hint indices from the bez file, but # sorted in the order of the hint pairs. return hints, hint_order def make_abs(hint_pair): bottom_edge, delta = hint_pair new_hint_pair = [bottom_edge, delta] if delta in [-20, -21]: # It is a ghost hint! # We use this only in comparing overlap and order: # pretend the delta is 0, as it isn't a real value. new_hint_pair[1] = bottom_edge else: new_hint_pair[1] = bottom_edge + delta return new_hint_pair def check_hint_overlap(hint_list, last_idx, bad_hint_idxs): # return True if there is an overlap. prev = hint_list[0] for i, hint_pair in enumerate(hint_list[1:], 1): if prev[1] >= hint_pair[0]: bad_hint_idxs.add(i + last_idx - 1) prev = hint_pair def check_hint_pairs(hint_pairs, mm_hint_info, last_idx=0): # pairs must be in ascending order by bottom (or left) edge, # and pairs in a hint group must not overlap. # check order first bad_hint_idxs = set() prev = hint_pairs[0] for i, hint_pair in enumerate(hint_pairs[1:], 1): if prev[0] > hint_pair[0]: # If there is a conflict, we drop the previous hint bad_hint_idxs.add(i + last_idx - 1) prev = hint_pair # check for overlap in hint groups. if mm_hint_info.hint_masks: for hint_mask in mm_hint_info.hint_masks: if last_idx == 0: hint_list = hint_mask.h_list else: hint_list = hint_mask.v_list hint_list = [make_abs(hint_pair) for hint_pair in hint_list] check_hint_overlap(hint_list, last_idx, bad_hint_idxs) else: hint_list = [make_abs(hint_pair) for hint_pair in hint_pairs] check_hint_overlap(hint_list, last_idx, bad_hint_idxs) if bad_hint_idxs: mm_hint_info.bad_hint_idxs |= bad_hint_idxs def update_hints(in_mm_hints, arg_list, hints, hint_mask, is_v=False): if in_mm_hints: hints.append(arg_list) i = len(hints) - 1 else: try: i = hints.index(arg_list) except ValueError: i = len(hints) hints.append(arg_list) if hint_mask: hint_list = hint_mask.v_list if is_v else hint_mask.h_list if hints[i] not in hint_list: hint_list.append(hints[i]) return i def convertBezToT2(bezString, mm_hint_info=None): # convert bez data to a T2 outline program, a list of operator tokens. # # Convert all bez ops to simplest T2 equivalent. # Add all hints to vertical and horizontal hint lists as encountered. # Insert a HintMask class whenever a new set of hints is encountered. # Add all hints as prefix to t2Program # After all operators have been processed, convert HintMask items into # hintmask ops and hintmask bytes. # Review operator list to optimize T2 operators. # # If doing MM-hinting, extra work is needed to maintain merge # compatibility between the reference font and the region fonts. # Although hints are generated for exactly the same outline features # in all fonts, they will have different values. Consequently, the # hints in a region font may not sort to the same order as in the # reference font. In addition, they may be filtered differently. Only # unique hints are added from the bez file to the hint list. Two hint # pairs may differ in one font, but not in another. # We work around these problems by first not filtering the hint # pairs for uniqueness when accumulating the hint lists. For the # reference font, once we have collected all the hints, we remove any # duplicate pairs, but keep a list of the retained hint pair indices # into the unfiltered hint pair list. For the region fonts, we # select hints from the unfiltered hint pair lists by using the selected # index list from the reference font. # Note that this breaks the CFF spec for snapshotted instances of the # CFF2 VF variable font, as hints may not be in ascending order, and the # hint list may contain duplicate hints. in_mm_hints = mm_hint_info is not None bezString = re.sub(r"%.+?\n", "", bezString) # suppress comments bezList = re.findall(r"(\S+)", bezString) if not bezList: return "" hhints = [] vhints = [] # Always assume a hint mask exists until proven # otherwise - make an initial HintMask. hint_mask = HintMask(0) hintMaskList = [hint_mask] vStem3Args = [] hStem3Args = [] v_stem3_list = [] h_stem3_list = [] arg_list = [] t2List = [] lastPathOp = None curX = 0 curY = 0 for token in bezList: try: val1 = round(float(token), 2) try: val2 = int(token) if int(val1) == val2: arg_list.append(val2) else: arg_list.append("%s 100 div" % int(val1 * 100)) except ValueError: arg_list.append(val1) continue except ValueError: pass if token == "newcolors": lastPathOp = token elif token in ["beginsubr", "endsubr"]: lastPathOp = token elif token == "snc": lastPathOp = token # The index into the t2list is kept # so we can quickly find them later. hint_mask = HintMask(len(t2List)) t2List.append([hint_mask]) hintMaskList.append(hint_mask) elif token == "enc": lastPathOp = token elif token == "rb": update_hints(in_mm_hints, arg_list, hhints, hint_mask, False) arg_list = [] lastPathOp = token elif token == "ry": update_hints(in_mm_hints, arg_list, vhints, hint_mask, True) arg_list = [] lastPathOp = token elif token == "rm": # vstem3 hints are vhints update_hints(in_mm_hints, arg_list, vhints, hint_mask, True) if (lastPathOp != token) and vStem3Args: # first rm, must be start of a new vstem3 # if we already have a set of vstems in vStem3Args, save them, # and then clear the vStem3Args so we can add the new set. v_stem3_list.append(vStem3Args) vStem3Args = [] vStem3Args.append(arg_list) arg_list = [] lastPathOp = token elif token == "rv": # hstem3 are hhints update_hints(in_mm_hints, arg_list, hhints, hint_mask, False) if (lastPathOp != token) and hStem3Args: # first rv, must be start of a new h countermask h_stem3_list.append(hStem3Args) hStem3Args = [] hStem3Args.append(arg_list) arg_list = [] lastPathOp = token elif token == "preflx1": # The preflx1/preflx2a sequence provides the same 'i' as the flex # sequence. The difference is that the preflx1/preflx2a sequence # provides the argument values needed for building a Type1 string # while the flex sequence is simply the 6 rrcurveto points. # Both sequences are always provided. lastPathOp = token arg_list = [] elif token == "preflx2a": lastPathOp = token del t2List[-1] arg_list = [] elif token == "flxa": lastPathOp = token argList1, curX, curY = makeRelativeCTArgs(arg_list[:6], curX, curY) argList2, curX, curY = makeRelativeCTArgs(arg_list[6:], curX, curY) arg_list = argList1 + argList2 t2List.append([arg_list[:12] + [50], "flex"]) arg_list = [] elif token == "sc": lastPathOp = token else: if token in ["rmt", "mt", "dt", "ct"]: lastPathOp = token t2Op = bezToT2.get(token, None) if token in ["mt", "dt"]: newList = [arg_list[0] - curX, arg_list[1] - curY] curX = arg_list[0] curY = arg_list[1] arg_list = newList elif token == "ct": arg_list, curX, curY = makeRelativeCTArgs(arg_list, curX, curY) if t2Op: t2List.append([arg_list, t2Op]) elif t2Op is None: raise KeyError("Unhandled operation %s %s" % (arg_list, token)) arg_list = [] # Add hints, if any. Must be done at the end of op processing to make sure # we have seen all the hints in the bez string. Note that the hintmask are # identified in the t2List by an index into the list; be careful NOT to # change the t2List length until the hintmasks have been converted. need_hint_masks = len(hintMaskList) > 1 if vStem3Args: v_stem3_list.append(vStem3Args) if hStem3Args: h_stem3_list.append(hStem3Args) t2Program = [] if hhints or vhints: if mm_hint_info is None: hhints.sort() vhints.sort() elif mm_hint_info.defined: # Apply hint order from reference font in MM hinting hhints = [hhints[j] for j in mm_hint_info.h_order] vhints = [vhints[j] for j in mm_hint_info.v_order] else: # Define hint order from reference font in MM hinting hhints, mm_hint_info.h_order = build_hint_order(hhints) vhints, mm_hint_info.v_order = build_hint_order(vhints) num_hhints = len(hhints) num_vhints = len(vhints) hint_limit = int((kStackLimit - 2) / 2) if num_hhints >= hint_limit: hhints = hhints[:hint_limit] if num_vhints >= hint_limit: vhints = vhints[:hint_limit] if mm_hint_info and mm_hint_info.defined: check_hint_pairs(hhints, mm_hint_info) last_idx = len(hhints) check_hint_pairs(vhints, mm_hint_info, last_idx) if hhints: t2Program = make_hint_list(hhints, need_hint_masks, is_h=True) if vhints: t2Program += make_hint_list(vhints, need_hint_masks, is_h=False) cntrmask_progam = None if mm_hint_info is None: if v_stem3_list or h_stem3_list: counter_mask_list = build_counter_mask_list(h_stem3_list, v_stem3_list) cntrmask_progam = [['cntrmask', cMask.maskByte(hhints, vhints)] for cMask in counter_mask_list] elif (not mm_hint_info.defined): if v_stem3_list or h_stem3_list: # this is the reference font - we need to build the list. counter_mask_list = build_counter_mask_list(h_stem3_list, v_stem3_list) cntrmask_progam = [['cntrmask', cMask.maskByte(hhints, vhints)] for cMask in counter_mask_list] mm_hint_info.cntr_masks = counter_mask_list else: # This is a region font - we need to used the reference font list. counter_mask_list = mm_hint_info.cntr_masks cntrmask_progam = [['cntrmask', cMask.mask] for cMask in counter_mask_list] if cntrmask_progam: cntrmask_progam = itertools.chain(*cntrmask_progam) t2Program.extend(cntrmask_progam) if need_hint_masks: # If there is not a hintsub before any drawing operators, then # add an initial first hint mask to the t2Program. if (mm_hint_info is None) or (not mm_hint_info.defined): # a single font and a reference font for mm hinting are # processed the same way if hintMaskList[1].listPos != 0: hBytes = hintMaskList[0].maskByte(hhints, vhints) t2Program.extend(["hintmask", hBytes]) if in_mm_hints: mm_hint_info.hint_masks.append(hintMaskList[0]) # Convert the rest of the hint masks # to a hintmask op and hintmask bytes. for hint_mask in hintMaskList[1:]: pos = hint_mask.listPos hBytes = hint_mask.maskByte(hhints, vhints) t2List[pos] = [["hintmask"], hBytes] if in_mm_hints: mm_hint_info.hint_masks.append(hint_mask) elif (mm_hint_info is not None): # This is a MM region font: # apply hint masks from reference font. try: hm0_mask = mm_hint_info.hint_masks[0].mask except IndexError: import pdb pdb.set_trace() if isinstance(t2List[0][0], HintMask): t2List[0] = [["hintmask"], hm0_mask] else: t2Program.extend(["hintmask", hm0_mask]) for hm in mm_hint_info.hint_masks[1:]: t2List[hm.listPos] = [["hintmask"], hm.mask] for entry in t2List: try: t2Program.extend(entry[0]) t2Program.append(entry[1]) except Exception: raise KeyError("Failed to extend t2Program with entry %s" % entry) if in_mm_hints: mm_hint_info.defined = True return t2Program def _run_tx(args): try: subprocess.check_call(["tx"] + args, stderr=subprocess.DEVNULL) except (subprocess.CalledProcessError, OSError) as e: raise FontParseError(e) class FixHintWidthDecompiler(SimpleT2Decompiler): # If we are using this class, we know the charstring has hints. def __init__(self, localSubrs, globalSubrs, private=None): self.hintMaskBytes = 0 # to silence false Codacy error. SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, private) self.has_explicit_width = None self.h_hint_args = self.v_hint_args = None self.last_stem_index = None def op_hstem(self, index): self.countHints(is_vert=False) self.last_stem_index = index op_hstemhm = op_hstem def op_vstem(self, index): self.countHints(is_vert=True) self.last_stem_index = index op_vstemhm = op_vstem def op_hintmask(self, index): if not self.hintMaskBytes: # Note that I am assuming that there is never an op_vstemhm # followed by an op_hintmask. Since this is applied after saving # the font with fontTools, this is safe. self.countHints(is_vert=True) self.hintMaskBytes = (self.hintCount + 7) // 8 cs = self.callingStack[-1] hintMaskBytes, index = cs.getBytes(index, self.hintMaskBytes) return hintMaskBytes, index op_cntrmask = op_hintmask def countHints(self, is_vert): args = self.popall() if self.has_explicit_width is None: if (len(args) % 2) == 0: self.has_explicit_width = False else: self.has_explicit_width = True self.width_arg = args[0] args = args[1:] self.hintCount = self.hintCount + len(args) // 2 if is_vert: self.v_hint_args = args else: self.h_hint_args = args class CFFFontData: def __init__(self, path, font_format): self.inputPath = path self.font_format = font_format self.mm_hint_info_dict = {} self.t2_widths = {} self.is_cff2 = False self.is_vf = False self.vs_data_models = None if font_format == "OTF": # It is an OTF font, we can process it directly. font = TTFont(path) if "CFF " in font: cff_format = "CFF " elif "CFF2" in font: cff_format = "CFF2" self.is_cff2 = True else: raise FontParseError("OTF font has no CFF table <%s>." % path) else: # Else, package it in an OTF font. cff_format = "CFF " if font_format == "CFF": with open(path, "rb") as fp: data = fp.read() else: fd, temp_path = tempfile.mkstemp() os.close(fd) try: _run_tx(["-cff", "+b", "-std", path, temp_path]) with open(temp_path, "rb") as fp: data = fp.read() finally: os.remove(temp_path) font = TTFont() font['CFF '] = newTable('CFF ') font['CFF '].decompile(data, font) self.ttFont = font self.cffTable = font[cff_format] # for identifier in glyph-list: # Get charstring. self.topDict = self.cffTable.cff.topDictIndex[0] self.charStrings = self.topDict.CharStrings if 'fvar' in self.ttFont: # have not yet collected VF global data. self.is_vf = True fvar = self.ttFont['fvar'] CFF2 = self.cffTable CFF2.desubroutinize() topDict = CFF2.cff.topDictIndex[0] # We need a new charstring object into which we can save the # hinted CFF2 program data. Copying an existing charstring is a # little easier than creating a new one and making sure that all # properties are set correctly. self.temp_cs = copy.deepcopy(self.charStrings['.notdef']) self.vs_data_models = self.get_vs_data_models(topDict, fvar) def getGlyphList(self): return self.ttFont.getGlyphOrder() def getPSName(self): if self.is_cff2 and 'name' in self.ttFont: psName = next((name_rec.string for name_rec in self.ttFont[ 'name'].names if (name_rec.nameID == 6) and ( name_rec.platformID == 3))) psName = psName.decode('utf-16be') else: psName = self.cffTable.cff.fontNames[0] return psName def get_min_max(self, pTopDict, upm): if self.is_cff2 and 'hhea' in self.ttFont: font_max = self.ttFont['hhea'].ascent font_min = self.ttFont['hhea'].descent elif hasattr(pTopDict, 'FontBBox'): font_max = pTopDict.FontBBox[3] font_min = pTopDict.FontBBox[1] else: font_max = upm * 1.25 font_min = -upm * 0.25 alignment_min = min(-upm * 0.25, font_min) alignment_max = max(upm * 1.25, font_max) return alignment_min, alignment_max def convertToBez(self, glyphName, read_hints, round_coords, doAll=False): t2Wdth = None t2CharString = self.charStrings[glyphName] try: bezString, t2Wdth = convertT2GlyphToBez(t2CharString, read_hints, round_coords) # Note: the glyph name is important, as it is used by the C-code # for various heuristics, including [hv]stem3 derivation. bezString = "% " + glyphName + "\n" + bezString except SEACError: log.warning("Skipping %s: can't process SEAC composite glyphs.", glyphName) bezString = None self.t2_widths[glyphName] = t2Wdth return bezString def updateFromBez(self, bezData, glyphName, mm_hint_info=None): t2Program = convertBezToT2(bezData, mm_hint_info) if not self.is_cff2: t2_width_arg = self.t2_widths[glyphName] if t2_width_arg is not None: t2Program = [t2_width_arg] + t2Program if self.vs_data_models is not None: # It is a variable font. Accumulate the charstrings. self.glyph_programs.append(t2Program) else: # This is an MM source font. Update the font's charstring directly. t2CharString = self.charStrings[glyphName] t2CharString.program = t2Program def save(self, path): if path is None: path = self.inputPath if self.font_format == "OTF": self.ttFont.save(path) self.ttFont.close() else: data = self.ttFont["CFF "].compile(self.ttFont) if self.font_format == "CFF": with open(path, "wb") as fp: fp.write(data) else: fd, temp_path = tempfile.mkstemp() os.write(fd, data) os.close(fd) try: args = ["-t1", "-std"] if self.font_format == "PFB": args.append("-pfb") _run_tx(args + [temp_path, path]) finally: os.remove(temp_path) def close(self): self.ttFont.close() def isCID(self): return hasattr(self.topDict, "FDSelect") def hasFDArray(self): return self.is_cff2 or hasattr(self.topDict, "FDSelect") def flattenBlends(self, blendList): if type(blendList[0]) is list: flatList = [blendList[i][0] for i in range(len(blendList))] else: flatList = blendList return flatList def getFontInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex=0): # The psautohint library needs the global font hint zones # and standard stem widths. # Format them into a single text string. # The text format is arbitrary, inherited from very old software, # but there is no real need to change it. pTopDict = self.topDict if hasattr(pTopDict, "FDArray"): pDict = pTopDict.FDArray[fdIndex] else: pDict = pTopDict privateDict = pDict.Private fdDict = fdTools.FDDict() fdDict.LanguageGroup = getattr(privateDict, "LanguageGroup", "0") if hasattr(pDict, "FontMatrix"): fdDict.FontMatrix = pDict.FontMatrix else: fdDict.FontMatrix = pTopDict.FontMatrix upm = int(1 / fdDict.FontMatrix[0]) fdDict.OrigEmSqUnits = str(upm) fdDict.FontName = getattr(pTopDict, "FontName", self.getPSName()) blueValues = getattr(privateDict, "BlueValues", [])[:] numBlueValues = len(blueValues) if numBlueValues < 4: low, high = self.get_min_max(pTopDict, upm) # Make a set of inactive alignment zones: zones outside of the # font BBox so as not to affect hinting. Used when source font has # no BlueValues or has invalid BlueValues. Some fonts have bad BBox # values, so I don't let this be smaller than -upm*0.25, upm*1.25. inactiveAlignmentValues = [low, low, high, high] if allow_no_blues: blueValues = inactiveAlignmentValues numBlueValues = len(blueValues) else: raise FontParseError("Font must have at least four values in " "its BlueValues array for PSAutoHint to " "work!") blueValues.sort() # The first pair only is a bottom zone, where the first value is the # overshoot position. The rest are top zones, and second value of the # pair is the overshoot position. blueValues = self.flattenBlends(blueValues) blueValues[0] = blueValues[0] - blueValues[1] for i in range(3, numBlueValues, 2): blueValues[i] = blueValues[i] - blueValues[i - 1] blueValues = [str(v) for v in blueValues] numBlueValues = min(numBlueValues, len(fdTools.kBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kBlueValueKeys[i] value = blueValues[i] setattr(fdDict, key, value) if hasattr(privateDict, "OtherBlues"): # For all OtherBlues, the pairs are bottom zones, and # the first value of each pair is the overshoot position. i = 0 numBlueValues = len(privateDict.OtherBlues) blueValues = privateDict.OtherBlues[:] blueValues.sort() blueValues = self.flattenBlends(blueValues) for i in range(0, numBlueValues, 2): blueValues[i] = blueValues[i] - blueValues[i + 1] blueValues = [str(v) for v in blueValues] numBlueValues = min(numBlueValues, len(fdTools.kOtherBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kOtherBlueValueKeys[i] value = blueValues[i] setattr(fdDict, key, value) if hasattr(privateDict, "StemSnapV"): vstems = privateDict.StemSnapV elif hasattr(privateDict, "StdVW"): vstems = [privateDict.StdVW] else: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. vstems = [upm] else: raise FontParseError("Font has neither StemSnapV nor StdVW!") vstems.sort() vstems = self.flattenBlends(vstems) if (len(vstems) == 0) or ((len(vstems) == 1) and (vstems[0] < 1)): vstems = [upm] # dummy value that will allow PyAC to run log.warning("There is no value or 0 value for DominantV.") fdDict.DominantV = "[" + " ".join([str(v) for v in vstems]) + "]" if hasattr(privateDict, "StemSnapH"): hstems = privateDict.StemSnapH elif hasattr(privateDict, "StdHW"): hstems = [privateDict.StdHW] else: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. hstems = [upm] else: raise FontParseError("Font has neither StemSnapH nor StdHW!") hstems.sort() hstems = self.flattenBlends(hstems) if (len(hstems) == 0) or ((len(hstems) == 1) and (hstems[0] < 1)): hstems = [upm] # dummy value that will allow PyAC to run log.warning("There is no value or 0 value for DominantH.") fdDict.DominantH = "[" + " ".join([str(v) for v in hstems]) + "]" if noFlex: fdDict.FlexOK = "false" else: fdDict.FlexOK = "true" # Add candidate lists for counter hints, if any. if vCounterGlyphs: temp = " ".join(vCounterGlyphs) fdDict.VCounterChars = "( %s )" % (temp) if hCounterGlyphs: temp = " ".join(hCounterGlyphs) fdDict.HCounterChars = "( %s )" % (temp) fdDict.BlueFuzz = getattr(privateDict, "BlueFuzz", 1) return fdDict def getfdIndex(self, name): gid = self.ttFont.getGlyphID(name) if hasattr(self.topDict, "FDSelect"): fdIndex = self.topDict.FDSelect[gid] else: fdIndex = 0 return fdIndex def getfdInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): topDict = self.topDict fdGlyphDict = None # Get the default fontinfo from the font's top dict. fdDict = self.getFontInfo( allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList = [fdDict] # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(self.inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") if os.path.exists(srcFontInfo): with open(srcFontInfo, "r", encoding="utf-8") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) else: return fdGlyphDict, fontDictList if "FDDict" in fontInfoData: maxY = topDict.FontBBox[3] minY = topDict.FontBBox[1] fdGlyphDict, fontDictList, finalFDict = fdTools.parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, self.getPSName()) if hasattr(topDict, "FDArray"): private = topDict.FDArray[fdIndex].Private else: private = topDict.Private if finalFDict is None: # If a font dict was not explicitly specified for the # output font, use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], private) else: fdTools.mergeFDDicts([finalFDict], private) return fdGlyphDict, fontDictList @staticmethod def args_to_hints(hint_args): hints = [hint_args[0:2]] prev = hints[0] for i in range(2, len(hint_args), 2): bottom = hint_args[i] + prev[0] + prev[1] hints.append([bottom, hint_args[i + 1]]) prev = hints[-1] return hints @staticmethod def extract_hint_args(program): width = None h_hint_args = [] v_hint_args = [] for i, token in enumerate(program): if type(token) is str: if i % 2 != 0: width = program[0] del program[0] idx = i - 1 else: idx = i if (token[:4] == 'vstem') or token[-3:] == 'mask': h_hint_args = [] v_hint_args = program[:idx] elif token[:5] == 'hstem': h_hint_args = program[:idx] v_program = program[idx + 1:] for j, vtoken in enumerate(v_program): if type(vtoken) is str: if (vtoken[:5] == 'vstem') or vtoken[-4:] == \ 'mask': v_hint_args = v_program[:j] break break return width, h_hint_args, v_hint_args def fix_t2_program_hints(self, program, mm_hint_info, is_reference_font): width_arg, h_hint_args, v_hint_args = self.extract_hint_args(program) # 1. Build list of good [vh]hints. bad_hint_idxs = list(mm_hint_info.bad_hint_idxs) bad_hint_idxs.sort() num_hhint_pairs = len(h_hint_args) // 2 for idx in reversed(bad_hint_idxs): if idx < num_hhint_pairs: hint_args = h_hint_args bottom_idx = idx * 2 else: hint_args = v_hint_args bottom_idx = (idx - num_hhint_pairs) * 2 delta = hint_args[bottom_idx] + hint_args[bottom_idx + 1] del hint_args[bottom_idx:bottom_idx + 2] if len(hint_args) > bottom_idx: hint_args[bottom_idx] += delta # delete old hints from program if mm_hint_info.cntr_masks: last_hint_idx = program.index('cntrmask') elif mm_hint_info.hint_masks: last_hint_idx = program.index('hintmask') else: for op in ['vstem', 'hstem']: try: last_hint_idx = program.index(op) break except IndexError: last_hint_idx = None if last_hint_idx is not None: del program[:last_hint_idx] # If there were v_hint_args, but they have now all been # deleted, the first token will still be 'vstem[hm]'. Delete it. if ((not v_hint_args) and program[0].startswith('vstem')): del program[0] # Add width and updated hints back. if width_arg is not None: hint_program = [width_arg] else: hint_program = [] if h_hint_args: op_hstem = 'hstemhm' if mm_hint_info.hint_masks else 'hstem' hint_program.extend(h_hint_args) hint_program.append(op_hstem) if v_hint_args: hint_program.extend(v_hint_args) # Don't need to append op_vstem, as this is still in hint_program. program = hint_program + program # Re-calculate the hint masks. if is_reference_font: hhints = self.args_to_hints(h_hint_args) vhints = self.args_to_hints(v_hint_args) for hm in mm_hint_info.hint_masks: hm.maskByte(hhints, vhints) # Apply fixed hint masks if mm_hint_info.hint_masks: hm_pos_list = [i for i, token in enumerate(program) if token == 'hintmask'] for i, hm in enumerate(mm_hint_info.hint_masks): pos = hm_pos_list[i] program[pos + 1] = hm.mask # Now fix the control masks. We will weed out a control mask # if it ends up with fewer than 3 hints. cntr_masks = mm_hint_info.cntr_masks if is_reference_font and cntr_masks: # Update mask bytes, # and remove control masks with fewer than 3 bits. mask_byte_list = [cm.mask for cm in cntr_masks] for cm in cntr_masks: cm.maskByte(hhints, vhints) new_cm_list = [cm for cm in cntr_masks if cm.num_bits >= 3] new_mask_byte_list = [cm.mask for cm in new_cm_list] if new_mask_byte_list != mask_byte_list: mm_hint_info.new_cntr_masks = new_cm_list if mm_hint_info.new_cntr_masks: # Remove all the old cntrmask ops num_old_cm = len(cntr_masks) idx = program.index('cntrmask') del program[idx:idx + num_old_cm * 2] cm_progam = [['cntrmask', cm.mask] for cm in mm_hint_info.new_cntr_masks] cm_progam = list(itertools.chain(*cm_progam)) program[idx:idx] = cm_progam return program def fix_glyph_hints(self, glyph_name, mm_hint_info, is_reference_font=None): # 1. Delete any bad hints. # 2. If reference font, recalculate the hint mask byte strings # 3. Replace hint masks. # 3. Fix cntr masks. if self.is_vf: # We get called once, and fix all the charstring programs. for i, t2_program in enumerate(self.glyph_programs): self.glyph_programs[i] = self.fix_t2_program_hints( t2_program, mm_hint_info, is_reference_font=(i == 0)) else: # we are called for each font in turn try: t2CharString = self.charStrings[glyph_name] except KeyError: return # Happens with sparse sources - just skip the glyph. program = self.fix_t2_program_hints(t2CharString.program, mm_hint_info, is_reference_font) t2CharString.program = program def get_vf_bez_glyphs(self, glyph_name): charstring = self.charStrings[glyph_name] if 'vsindex' in charstring.program: op_index = charstring.program.index('vsindex') vsindex = charstring.program[op_index - 1] else: vsindex = 0 self.vsindex = vsindex self.glyph_programs = [] vs_data_model = self.vs_data_model = self.vs_data_models[vsindex] bez_list = [] for vsi in vs_data_model.master_vsi_list: t2_program = interpolate_cff2_charstring(charstring, glyph_name, vsi.interpolateFromDeltas, vsindex) self.temp_cs.program = t2_program bezString, _ = convertT2GlyphToBez(self.temp_cs, True, True) # DBG Adding glyph name is useful only for debugging. bezString = "% {}\n".format(glyph_name) + bezString bez_list.append(bezString) return bez_list @staticmethod def get_vs_data_models(topDict, fvar): otvs = topDict.VarStore.otVarStore region_list = otvs.VarRegionList.Region axis_tags = [axis_entry.axisTag for axis_entry in fvar.axes] vs_data_models = [] for vsindex, var_data in enumerate(otvs.VarData): vsi = VarStoreInstancer(topDict.VarStore.otVarStore, fvar.axes, {}) master_vsi_list = [vsi] for region_idx in var_data.VarRegionIndex: region = region_list[region_idx] loc = {} for i, axis in enumerate(region.VarRegionAxis): loc[axis_tags[i]] = axis.PeakCoord vsi = VarStoreInstancer(topDict.VarStore.otVarStore, fvar.axes, loc) master_vsi_list.append(vsi) vdm = VarDataModel(var_data, vsindex, master_vsi_list) vs_data_models.append(vdm) return vs_data_models def merge_hinted_glyphs(self, name): new_t2cs = merge_hinted_programs(self.temp_cs, self.glyph_programs, name, self.vs_data_model) if self.vsindex: new_t2cs.program = [self.vsindex, 'vsindex'] + new_t2cs.program self.charStrings[name] = new_t2cs def interpolate_cff2_charstring(charstring, gname, interpolateFromDeltas, vsindex): # Interpolate charstring # e.g replace blend op args with regular args, # and discard vsindex op. new_program = [] last_i = 0 program = charstring.program for i, token in enumerate(program): if token == 'vsindex': if last_i != 0: new_program.extend(program[last_i:i - 1]) last_i = i + 1 elif token == 'blend': num_regions = charstring.getNumRegions(vsindex) numMasters = 1 + num_regions num_args = program[i - 1] # The program list starting at program[i] is now: # ..args for following operations # num_args values from the default font # num_args tuples, each with numMasters-1 delta values # num_blend_args # 'blend' argi = i - (num_args * numMasters + 1) if last_i != argi: new_program.extend(program[last_i:argi]) end_args = tuplei = argi + num_args master_args = [] while argi < end_args: next_ti = tuplei + num_regions deltas = program[tuplei:next_ti] val = interpolateFromDeltas(vsindex, deltas) master_val = program[argi] master_val += otRound(val) master_args.append(master_val) tuplei = next_ti argi += 1 new_program.extend(master_args) last_i = i + 1 if last_i != 0: new_program.extend(program[last_i:]) return new_program def merge_hinted_programs(charstring, t2_programs, gname, vs_data_model): num_masters = vs_data_model.num_masters var_pen = CFF2CharStringMergePen([], gname, num_masters, 0) charstring.outlineExtractor = MergeOutlineExtractor for i, t2_program in enumerate(t2_programs): var_pen.restart(i) charstring.program = t2_program charstring.draw(var_pen) new_charstring = var_pen.getCharString( private=charstring.private, globalSubrs=charstring.globalSubrs, var_model=vs_data_model, optimize=True) return new_charstring @_add_method(VarStoreInstancer) def get_scalars(self, vsindex, region_idx): varData = self._varData # The index key needs to be the master value index, which includes # the default font value. VarRegionIndex provides the region indices. scalars = {0: 1.0} # The default font always has a weight of 1.0 region_index = varData[vsindex].VarRegionIndex for idx in range(region_idx): # omit the scalar for the region. scalar = self._getScalar(region_index[idx]) if scalar: scalars[idx + 1] = scalar return scalars class VarDataModel(object): def __init__(self, var_data, vsindex, master_vsi_list): self.master_vsi_list = master_vsi_list self.var_data = var_data self._num_masters = len(master_vsi_list) self.delta_weights = [{}] # for default font value for region_idx, vsi in enumerate(master_vsi_list[1:]): scalars = vsi.get_scalars(vsindex, region_idx) self.delta_weights.append(scalars) @property def num_masters(self): return self._num_masters def getDeltas(self, master_values): assert len(master_values) == len(self.delta_weights) out = [] for i, scalars in enumerate(self.delta_weights): delta = master_values[i] for j, scalar in scalars.items(): if scalar: delta -= out[j] * scalar out.append(delta) return out psautohint-2.3.0/python/psautohint/ufoFont.py000066400000000000000000001356431401523215600214360ustar00rootroot00000000000000# Copyright 2014 Adobe. All rights reserved. """ This module supports using the Adobe FDK tools which operate on 'bez' files with UFO fonts. It provides low level utilities to manipulate UFO data without fully parsing and instantiating UFO objects, and without requiring that the AFDKO contain the robofab libraries. Modified in Nov 2014, when AFDKO added the robofab libraries. It can now be used with UFO fonts only to support the hash mechanism. Developed in order to support checkOutlines and autohint, the code supports two main functions: - convert between UFO GLIF and bez formats - keep a history of processing in a hash map, so that the (lengthy) processing by autohint and checkOutlines can be avoided if the glyph has already been processed, and the source data has not changed. The basic model is: - read GLIF file - transform GLIF XML element to bez file - call FDK tool on bez file - transform new bez file to GLIF XML element with new data, and save in list After all glyphs are done save all the new GLIF XML elements to GLIF files, and update the hash map. A complication in the Adobe UFO workflow comes from the fact we want to make sure that checkOutlines and autohint have been run on each glyph in a UFO font, when building an OTF font from the UFO font. We need to run checkOutlines, because we no longer remove overlaps from source UFO font data, because this can make revising a glyph much easier. We need to run autohint, because the glyphs must be hinted after checkOutlines is run, and in any case we want all glyphs to have been autohinted. The problem with this is that it can take a minute or two to run autohint or checkOutlines on a 2K-glyph font. The way we avoid this is to make and keep a hash of the source glyph drawing operators for each tool. When the tool is run, it calculates a hash of the source glyph, and compares it to the saved hash. If these are the same, the tool can skip the glyph. This saves a lot of time: if checkOutlines and autohint are run on all glyphs in a font, then a second pass is under 2 seconds. Another issue is that since we no longer remove overlaps from the source glyph files, checkOutlines must write any edited glyph data to a different layer in order to not destroy the source data. The ufoFont defines an Adobe-specific glyph layer for processed glyphs, named "glyphs.com.adobe.type.processedGlyphs". checkOutlines writes new glyph files to the processed glyphs layer only when it makes a change to the glyph data. When the autohint program is run, the ufoFont must be able to tell whether checkOutlines has been run and has altered a glyph: if so, the input file needs to be from the processed glyph layer, else it needs to be from the default glyph layer. The way the hashmap works is that we keep an entry for every glyph that has been processed, identified by a hash of its marking path data. Each entry contains: - a hash of the glyph point coordinates, from the default layer. This is set after a program has been run. - a history list: a list of the names of each program that has been run, in order. - an editStatus flag. Altered GLIF data is always written to the Adobe processed glyph layer. The program may or may not have altered the outline data. For example, autohint adds private hint data, and adds names to points, but does not change any paths. If the stored hash for the glyph does not exist, the ufoFont lib will save the new hash in the hash map entry and will set the history list to contain just the current program. The program will read the glyph from the default layer. If the stored hash matches the hash for the current glyph file in the default layer, and the current program name is in the history list,then ufoFont will return "skip=1", and the calling program may skip the glyph. If the stored hash matches the hash for the current glyph file in the default layer, and the current program name is not in the history list, then the ufoFont will return "skip=0". If the font object field 'usedProcessedLayer' is set True, the program will read the glyph from the from the Adobe processed layer, if it exists, else it will always read from the default layer. If the hash differs between the hash map entry and the current glyph in the default layer, and usedProcessedLayer is False, then ufoFont will return "skip=0". If usedProcessedLayer is True, then the program will consult the list of required programs. If any of these are in the history list, then the program will report an error and return skip =1, else it will return skip=1. The program will then save the new hash in the hash map entry and reset the history list to contain just the current program. If the old and new hash match, but the program name is not in the history list, then the ufoFont will not skip the glyph, and will add the program name to the history list. The only tools using this are, at the moment, checkOutlines, checkOutlinesUFO and autohint. checkOutlines and checkOutlinesUFO use the hash map to skip processing only when being used to edit glyphs, not when reporting them. checkOutlines necessarily flattens any components in the source glyph file to actual outlines. autohint adds point names, and saves the hint data as a private data in the new GLIF file. autohint saves the hint data in the GLIF private data area, /lib/dict, as a key and data pair. See below for the format. autohint started with _hintFormat1_, a reasonably compact XML representation of the data. In Sep 2105, autohhint switched to _hintFormat2_ in order to be plist compatible. This was necessary in order to be compatible with the UFO spec, by was driven more immediately by the fact the the UFO font file normalization tools stripped out the _hintFormat1_ hint data as invalid elements. """ import ast import hashlib import logging import os import re import shutil from collections import OrderedDict from types import SimpleNamespace from fontTools.pens.basePen import BasePen from fontTools.pens.pointPen import AbstractPointPen from fontTools.ufoLib import UFOReader, UFOWriter from fontTools.ufoLib.errors import UFOLibError from . import fdTools, FontParseError log = logging.getLogger(__name__) _hintFormat1_ = """ Deprecated. See _hintFormat2_ below. A element identifies a specific point by its name, and describes a new set of stem hints which should be applied before the specific point. A element identifies a specific point by its name. The point is the first point of a curve. The presence of the element is a processing suggestion, that the curve and its successor curve should be converted to a flex operator. One challenge in applying the hintset and flex elements is that in the GLIF format, there is no explicit start and end operator: the first path operator is both the end and the start of the path. I have chosen to convert this to T1 by taking the first path operator, and making it a move-to. I then also use it as the last path operator. An exception is a line-to; in T1, this is omitted, as it is implied by the need to close the path. Hence, if a hintset references the first operator, there is a potential ambiguity: should it be applied before the T1 move-to, or before the final T1 path operator? The logic here applies it before the move-to only. ... ()* ()* * * * (* ( (positive integer)+ )+ )* * Example from "B" in SourceCodePro-Regular """ _hintFormat2_ = """ A element in the hintSetList array identifies a specific point by its name, and describes a new set of stem hints which should be applied before the specific point. A element in the flexList identifies a specific point by its name. The point is the first point of a curve. The presence of the element is a processing suggestion, that the curve and its successor curve should be converted to a flex operator. One challenge in applying the hintSetList and flexList elements is that in the GLIF format, there is no explicit start and end operator: the first path operator is both the end and the start of the path. I have chosen to convert this to T1 by taking the first path operator, and making it a move-to. I then also use it as the last path operator. An exception is a line-to; in T1, this is omitted, as it is implied by the need to close the path. Hence, if a hintset references the first operator, there is a potential ambiguity: should it be applied before the T1 move-to, or before the final T1 path operator? The logic here applies it before the move-to only. ... id hintSetList pointTag stems hstem * vstem * hstem3 ... * vstem3 ... * * flexList* + Example from "B" in SourceCodePro-Regular id 64bf4987f05ced2a50195f971cd924984047eb1d79c8c43e6a0054f59cc85dea23 a49deb20946a4ea84840534363f7a13cca31a81b1e7e33c832185173369086 hintSetList pointTag hintSet0000 stems hstem 338 28 hstem 632 28 hstem 100 32 hstem 496 32 pointTag hintSet0005 stems hstem 0 28 hstem 338 28 hstem 632 28 hstem 100 32 hstem 454 32 hstem 496 32 pointTag hintSet0016 stems hstem 0 28 hstem 338 28 hstem 632 28 hstem 100 32 hstem 496 32 """ # UFO names PUBLIC_GLYPH_ORDER = "public.glyphOrder" ADOBE_DOMAIN_PREFIX = "com.adobe.type" PROCESSED_LAYER_NAME = "%s.processedglyphs" % ADOBE_DOMAIN_PREFIX PROCESSED_GLYPHS_DIRNAME = "glyphs.%s" % PROCESSED_LAYER_NAME HASHMAP_NAME = "%s.processedHashMap" % ADOBE_DOMAIN_PREFIX HASHMAP_VERSION_NAME = "hashMapVersion" HASHMAP_VERSION = (1, 0) # If major version differs, do not use. AUTOHINT_NAME = "autohint" CHECKOUTLINE_NAME = "checkOutlines" BASE_FLEX_NAME = "flexCurve" FLEX_INDEX_LIST_NAME = "flexList" HINT_DOMAIN_NAME1 = "com.adobe.type.autohint" HINT_DOMAIN_NAME2 = "com.adobe.type.autohint.v2" HINT_SET_LIST_NAME = "hintSetList" HSTEM3_NAME = "hstem3" HSTEM_NAME = "hstem" POINT_NAME = "name" POINT_TAG = "pointTag" STEMS_NAME = "stems" VSTEM3_NAME = "vstem3" VSTEM_NAME = "vstem" STACK_LIMIT = 46 class BezParseError(ValueError): pass class UFOFontData: def __init__(self, path, log_only, write_to_default_layer): self._reader = UFOReader(path, validate=False) self.path = path self._glyphmap = None self._processed_layer_glyphmap = None self.newGlyphMap = {} self._fontInfo = None self._glyphsets = {} # If True, we are running in report mode and not doing any changes, so # we skip the hash map and process all glyphs. self.log_only = log_only # Used to store the hash of glyph data of already processed glyphs. If # the stored hash matches the calculated one, we skip the glyph. self._hashmap = None self.fontDict = None self.hashMapChanged = False # If True, then write data to the default layer self.writeToDefaultLayer = write_to_default_layer def getUnitsPerEm(self): return self.fontInfo.get("unitsPerEm", 1000) def getPSName(self): return self.fontInfo.get("postscriptFontName", "PSName-Undefined") @staticmethod def isCID(): return False @staticmethod def hasFDArray(): return False def convertToBez(self, name, read_hints, round_coords, doAll=False): # We do not yet support reading hints, so read_hints is ignored. width, bez, skip = self._get_or_skip_glyph(name, round_coords, doAll) if skip: return None bezString = "\n".join(bez) bezString = "\n".join(["% " + name, "sc", bezString, "ed", ""]) return bezString def updateFromBez(self, bezData, name, mm_hint_info=None): layer = None if name in self.processedLayerGlyphMap: layer = PROCESSED_LAYER_NAME glyphset = self._get_glyphset(layer) glyph = BezGlyph(bezData) glyphset.readGlyph(name, glyph) if hasattr(glyph, 'width'): glyph.width = norm_float(glyph.width) self.newGlyphMap[name] = glyph # updateFromBez is called only if the glyph has been autohinted which # might also change its outline data. We need to update the edit status # in the hash map entry. I assume that convertToBez has been run # before, which will add an entry for this glyph. self.updateHashEntry(name) def save(self, path): if path is None: path = self.path if os.path.abspath(self.path) != os.path.abspath(path): # If user has specified a path other than the source font path, # then copy the entire UFO font, and operate on the copy. log.info("Copying from source UFO font to output UFO font before " "processing...") if os.path.exists(path): shutil.rmtree(path) shutil.copytree(self.path, path) writer = UFOWriter(path, self._reader.formatVersionTuple, validate=False) layer = PROCESSED_LAYER_NAME if self.writeToDefaultLayer: layer = None # Write layer contents. layers = writer.layerContents.copy() if self.writeToDefaultLayer and PROCESSED_LAYER_NAME in layers: # Delete processed glyphs directory writer.deleteGlyphSet(PROCESSED_LAYER_NAME) # Remove entry from 'layercontents.plist' file del layers[PROCESSED_LAYER_NAME] elif self.processedLayerGlyphMap or not self.writeToDefaultLayer: layers[PROCESSED_LAYER_NAME] = PROCESSED_GLYPHS_DIRNAME writer.layerContents.update(layers) writer.writeLayerContents() # Write glyphs. glyphset = writer.getGlyphSet(layer, defaultLayer=layer is None) for name, glyph in self.newGlyphMap.items(): filename = self.glyphMap[name] if not self.writeToDefaultLayer and \ name in self.processedLayerGlyphMap: filename = self.processedLayerGlyphMap[name] # Recalculate glyph hashes if self.writeToDefaultLayer: self.recalcHashEntry(name, glyph) glyphset.contents[name] = filename glyphset.writeGlyph(name, glyph, glyph.drawPoints) glyphset.writeContents() # Write hashmap if self.hashMapChanged: self.writeHashMap(writer) @property def hashMap(self): if self._hashmap is None: try: data = self._reader.readData(HASHMAP_NAME) except UFOLibError: data = None if data: hashmap = ast.literal_eval(data.decode("utf-8")) else: hashmap = {HASHMAP_VERSION_NAME: HASHMAP_VERSION} version = (0, 0) if HASHMAP_VERSION_NAME in hashmap: version = hashmap[HASHMAP_VERSION_NAME] if version[0] > HASHMAP_VERSION[0]: raise FontParseError("Hash map version is newer than " "psautohint. Please update.") elif version[0] < HASHMAP_VERSION[0]: log.info("Updating hash map: was older version") hashmap = {HASHMAP_VERSION_NAME: HASHMAP_VERSION} self._hashmap = hashmap return self._hashmap def writeHashMap(self, writer): hashMap = self.hashMap if not hashMap: return # no glyphs were processed. data = ["{"] for gName in sorted(hashMap.keys()): data.append("'%s': %s," % (gName, hashMap[gName])) data.append("}") data.append("") data = "\n".join(data) writer.writeData(HASHMAP_NAME, data.encode("utf-8")) def updateHashEntry(self, glyphName): # srcHash has already been set: we are fixing the history list. # Get hash entry for glyph srcHash, historyList = self.hashMap[glyphName] self.hashMapChanged = True # If the program is not in the history list, add it. if AUTOHINT_NAME not in historyList: historyList.append(AUTOHINT_NAME) def recalcHashEntry(self, glyphName, glyph): hashBefore, historyList = self.hashMap[glyphName] hash_pen = HashPointPen(glyph) glyph.drawPoints(hash_pen) hashAfter = hash_pen.getHash() if hashAfter != hashBefore: self.hashMap[glyphName] = [hashAfter, historyList] self.hashMapChanged = True def checkSkipGlyph(self, glyphName, newSrcHash, doAll): skip = False if self.log_only: return skip srcHash = None historyList = [] # Get hash entry for glyph if glyphName in self.hashMap: srcHash, historyList = self.hashMap[glyphName] if srcHash == newSrcHash: if AUTOHINT_NAME in historyList: # The glyph has already been autohinted, and there have been no # changes since. skip = not doAll if not skip and AUTOHINT_NAME not in historyList: historyList.append(AUTOHINT_NAME) else: if CHECKOUTLINE_NAME in historyList: log.error("Glyph '%s' has been edited. You must first " "run '%s' before running '%s'. Skipping.", glyphName, CHECKOUTLINE_NAME, AUTOHINT_NAME) skip = True # If the source hash has changed, we need to delete the processed # layer glyph. self.hashMapChanged = True self.hashMap[glyphName] = [newSrcHash, [AUTOHINT_NAME]] if glyphName in self.processedLayerGlyphMap: del self.processedLayerGlyphMap[glyphName] return skip def _get_glyphset(self, layer_name=None): if layer_name not in self._glyphsets: glyphset = None try: glyphset = self._reader.getGlyphSet(layer_name) except UFOLibError: pass self._glyphsets[layer_name] = glyphset return self._glyphsets[layer_name] @staticmethod def get_glyph_bez(glyph, round_coords): pen = BezPen(glyph.glyphSet, round_coords) glyph.draw(pen) if not hasattr(glyph, "width"): glyph.width = 0 return pen.bez def _get_or_skip_glyph(self, name, round_coords, doAll): # Get default glyph layer data, so we can check if the glyph # has been edited since this program was last run. # If the program name is in the history list, and the srcHash # matches the default glyph layer data, we can skip. glyphset = self._get_glyphset() glyph = glyphset[name] bez = self.get_glyph_bez(glyph, round_coords) # Hash is always from the default glyph layer. hash_pen = HashPointPen(glyph) glyph.drawPoints(hash_pen) skip = self.checkSkipGlyph(name, hash_pen.getHash(), doAll) # If there is a glyph in the processed layer, get the outline from it. if name in self.processedLayerGlyphMap: glyphset = self._get_glyphset(PROCESSED_LAYER_NAME) glyph = glyphset[name] bez = self.get_glyph_bez(glyph, round_coords) return glyph.width, bez, skip def getGlyphList(self): glyphOrder = self._reader.readLib().get(PUBLIC_GLYPH_ORDER, []) glyphList = list(self._get_glyphset().keys()) # Sort the returned glyph list by the glyph order as we depend in the # order for expanding glyph ranges. def key_fn(v): if v in glyphOrder: return glyphOrder.index(v) return len(glyphOrder) return sorted(glyphList, key=key_fn) @property def glyphMap(self): if self._glyphmap is None: glyphset = self._get_glyphset() self._glyphmap = glyphset.contents return self._glyphmap @property def processedLayerGlyphMap(self): if self._processed_layer_glyphmap is None: self._processed_layer_glyphmap = {} glyphset = self._get_glyphset(PROCESSED_LAYER_NAME) if glyphset is not None: self._processed_layer_glyphmap = glyphset.contents return self._processed_layer_glyphmap @property def fontInfo(self): if self._fontInfo is None: info = SimpleNamespace() self._reader.readInfo(info) self._fontInfo = vars(info) return self._fontInfo def getFontInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex=0): if self.fontDict is not None: return self.fontDict fdDict = fdTools.FDDict() # should be 1 if the glyphs are ideographic, else 0. fdDict.LanguageGroup = self.fontInfo.get("languagegroup", "0") fdDict.OrigEmSqUnits = self.getUnitsPerEm() fdDict.FontName = self.getPSName() upm = self.getUnitsPerEm() low = min(-upm * 0.25, self.fontInfo.get("openTypeOS2WinDescent", 0) - 200) high = max(upm * 1.25, self.fontInfo.get("openTypeOS2WinAscent", 0) + 200) # Make a set of inactive alignment zones: zones outside of the font # bbox so as not to affect hinting. Used when src font has no # BlueValues or has invalid BlueValues. Some fonts have bad BBox # values, so I don't let this be smaller than -upm*0.25, upm*1.25. inactiveAlignmentValues = [low, low, high, high] blueValues = self.fontInfo.get("postscriptBlueValues", []) numBlueValues = len(blueValues) if numBlueValues < 4: if allow_no_blues: blueValues = inactiveAlignmentValues numBlueValues = len(blueValues) else: raise FontParseError( "Font must have at least four values in its " "BlueValues array for PSAutoHint to work!") blueValues.sort() # The first pair only is a bottom zone, where the first value is the # overshoot position; the rest are top zones, and second value of the # pair is the overshoot position. blueValues[0] = blueValues[0] - blueValues[1] for i in range(3, numBlueValues, 2): blueValues[i] = blueValues[i] - blueValues[i - 1] blueValues = [str(v) for v in blueValues] numBlueValues = min(numBlueValues, len(fdTools.kBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kBlueValueKeys[i] value = blueValues[i] setattr(fdDict, key, value) otherBlues = self.fontInfo.get("postscriptOtherBlues", []) if len(otherBlues) > 0: i = 0 numBlueValues = len(otherBlues) otherBlues.sort() for i in range(0, numBlueValues, 2): otherBlues[i] = otherBlues[i] - otherBlues[i + 1] otherBlues = [str(v) for v in otherBlues] numBlueValues = min(numBlueValues, len(fdTools.kOtherBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kOtherBlueValueKeys[i] value = otherBlues[i] setattr(fdDict, key, value) vstems = self.fontInfo.get("postscriptStemSnapV", []) if not vstems: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. vstems = [fdDict.OrigEmSqUnits] else: raise FontParseError("Font does not have postscriptStemSnapV!") vstems.sort() if not vstems or (len(vstems) == 1 and vstems[0] < 1): # dummy value that will allow PyAC to run vstems = [fdDict.OrigEmSqUnits] log.warning("There is no value or 0 value for DominantV.") fdDict.DominantV = "[" + " ".join([str(v) for v in vstems]) + "]" hstems = self.fontInfo.get("postscriptStemSnapH", []) if not hstems: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. hstems = [fdDict.OrigEmSqUnits] else: raise FontParseError("Font does not have postscriptStemSnapH!") hstems.sort() if not hstems or (len(hstems) == 1 and hstems[0] < 1): # dummy value that will allow PyAC to run hstems = [fdDict.OrigEmSqUnits] log.warning("There is no value or 0 value for DominantH.") fdDict.DominantH = "[" + " ".join([str(v) for v in hstems]) + "]" if noFlex: fdDict.FlexOK = "false" else: fdDict.FlexOK = "true" # Add candidate lists for counter hints, if any. if vCounterGlyphs: temp = " ".join(vCounterGlyphs) fdDict.VCounterChars = "( %s )" % (temp) if hCounterGlyphs: temp = " ".join(hCounterGlyphs) fdDict.HCounterChars = "( %s )" % (temp) fdDict.BlueFuzz = self.fontInfo.get("postscriptBlueFuzz", 1) # postscriptBlueShift # postscriptBlueScale self.fontDict = fdDict return fdDict def getfdInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): fdGlyphDict = None fdDict = self.getFontInfo(allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList = [fdDict] # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(self.path) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") maxX = self.getUnitsPerEm() * 2 maxY = maxX minY = -self.getUnitsPerEm() if os.path.exists(srcFontInfo): with open(srcFontInfo, "r", encoding="utf-8") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) if "FDDict" in fontInfoData: fdGlyphDict, fontDictList, finalFDict = \ fdTools.parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, self.getPSName()) if finalFDict is None: # If a font dict was not explicitly specified for the # output font, use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], self.fontDict) else: fdTools.mergeFDDicts([finalFDict], self.fontDict) return fdGlyphDict, fontDictList @staticmethod def close(): return class BezPen(BasePen): def __init__(self, glyph_set, round_coords): super(BezPen, self).__init__(glyph_set) self.round_coords = round_coords self.bez = [] def _point(self, point): if self.round_coords: return " ".join("%d" % round(pt) for pt in point) return " ".join("%3f" % pt for pt in point) def _moveTo(self, pt): self.bez.append("%s mt" % self._point(pt)) def _lineTo(self, pt): self.bez.append("%s dt" % self._point(pt)) def _curveToOne(self, pt1, pt2, pt3): self.bez.append("%s ct" % self._point(pt1 + pt2 + pt3)) @staticmethod def _qCurveToOne(pt1, pt2): raise FontParseError("Quadratic curves are not supported") def _closePath(self): self.bez.append("cp") class HashPointPen(AbstractPointPen): def __init__(self, glyph): self.glyphset = getattr(glyph, "glyphSet", None) self.width = norm_float(round(getattr(glyph, "width", 0), 9)) self.data = ["w%s" % self.width] def getHash(self): data = "".join(self.data) if len(data) >= 128: data = hashlib.sha512(data.encode("ascii")).hexdigest() return data def beginPath(self, identifier=None, **kwargs): pass def endPath(self): pass def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): if segmentType is None: pt_type = "" else: pt_type = segmentType[0] self.data.append("%s%s%s" % (pt_type, repr(norm_float(round(pt[0], 9))), repr(norm_float(round(pt[1], 9))))) def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): self.data.append("base:%s" % baseGlyphName) for v in transformation: self.data.append(str(norm_float(round(v, 9)))) self.data.append("w%s" % self.width) glyph = self.glyphset[baseGlyphName] glyph.drawPoints(self) class BezGlyph(object): def __init__(self, bez): self._bez = bez self.lib = {} @staticmethod def _draw(contours, pen): for contour in contours: pen.beginPath() for point in contour: x = point.get("x") y = point.get("y") segmentType = point.get("type", None) name = point.get("name", None) pen.addPoint((x, y), segmentType=segmentType, name=name) pen.endPath() def drawPoints(self, pen): contours, hints = convertBezToOutline(self._bez) self._draw(contours, pen) # Add the stem hints. if hints is not None: # Add this hash to the glyph data, as it is the hash which matches # the output outline data. This is not necessarily the same as the # hash of the source data; autohint can be used to change outlines. hash_pen = HashPointPen(self) self._draw(contours, hash_pen) hints["id"] = hash_pen.getHash() # Remove any existing hint data. for key in (HINT_DOMAIN_NAME1, HINT_DOMAIN_NAME2): if key in self.lib: del self.lib[key] self.lib[HINT_DOMAIN_NAME2] = hints class HintMask: # class used to collect hints for the current # hint mask when converting bez to T2. def __init__(self, listPos): # The index into the pointList is kept # so we can quickly find them later. self.listPos = listPos self.hList = [] # These contain the actual hint values. self.vList = [] self.hstem3List = [] self.vstem3List = [] # The name attribute of the point which follows the new hint set. self.pointName = "hintSet" + str(listPos).zfill(4) def getHintSet(self): hintset = OrderedDict() hintset[POINT_TAG] = self.pointName hintset[STEMS_NAME] = [] if len(self.hList) > 0 or len(self.hstem3List): hintset[STEMS_NAME].extend( makeHintSet(self.hList, self.hstem3List, isH=True)) if len(self.vList) > 0 or len(self.vstem3List): hintset[STEMS_NAME].extend( makeHintSet(self.vList, self.vstem3List, isH=False)) return hintset def norm_float(value): """Converts a float (whose decimal part is zero) to integer""" if isinstance(value, float) and value.is_integer(): return int(value) return value def makeStemHintList(hintsStem3, isH): # In bez terms, the first coordinate in each pair is # absolute, second is relative, and hence is the width. if isH: op = HSTEM3_NAME else: op = VSTEM3_NAME posList = [op] for stem3 in hintsStem3: for pos, width in stem3: posList.append("%s %s" % (norm_float(pos), norm_float(width))) return " ".join(posList) def makeHintList(hints, isH): # Add the list of hint operators # In bez terms, the first coordinate in each pair is # absolute, second is relative, and hence is the width. hintset = [] for hint in hints: if not hint: continue pos = hint[0] width = hint[1] if isH: op = HSTEM_NAME else: op = VSTEM_NAME hintset.append("%s %s %s" % (op, norm_float(pos), norm_float(width))) return hintset def fixStartPoint(contour, operators): # For the GLIF format, the idea of first/last point is funky, because # the format avoids identifying a start point. This means there is no # implied close-path line-to. If the last implied or explicit path-close # operator is a line-to, then replace the "mt" with linto, and remove # the last explicit path-closing line-to, if any. If the last op is a # curve, then leave the first two point args on the stack at the end of # the point list, and move the last curveto to the first op, replacing # the move-to. _, firstX, firstY = operators[0] lastOp, lastX, lastY = operators[-1] point = contour[0] if (firstX == lastX) and (firstY == lastY): del contour[-1] point["type"] = lastOp else: # we have an implied final line to. All we need to do # is convert the inital moveto to a lineto. point["type"] = "line" bezToUFOPoint = { "mt": 'move', "rmt": 'move', "dt": 'line', "ct": 'curve', } def convertCoords(current_x, current_y): return norm_float(current_x), norm_float(current_y) def convertBezToOutline(bezString): """ Since the UFO outline element as no attributes to preserve, I can just make a new one. """ # convert bez data to a UFO glif XML representation # # Convert all bez ops to simplest UFO equivalent # Add all hints to vertical and horizontal hint lists as encountered; # insert a HintMask class whenever a new set of hints is encountered # after all operators have been processed, convert HintMask items into # hintmask ops and hintmask bytes add all hints as prefix review operator # list to optimize T2 operators. # if useStem3 == 1, then any counter hints must be processed as stem3 # hints, else the opposite. # Counter hints are used only in LanguageGroup 1 glyphs, aka ideographs bezString = re.sub(r"%.+?\n", "", bezString) # supress comments bez = re.findall(r"(\S+)", bezString) flexes = [] # Create an initial hint mask. We use this if # there is no explicit initial hint sub. hintmask = HintMask(0) hintmasks = [hintmask] vstem3_args = [] hstem3_args = [] args = [] operators = [] hintmask_name = None in_preflex = False hints = None op_index = 0 current_x = 0 current_y = 0 contours = [] contour = None has_hints = False for token in bez: try: val = float(token) args.append(val) continue except ValueError: pass if token == "newcolors": pass elif token in ["beginsubr", "endsubr"]: pass elif token == "snc": hintmask = HintMask(op_index) # If the new hints precedes any marking operator, # then we want throw away the initial hint mask we # made, and use the new one as the first hint mask. if op_index == 0: hintmasks = [hintmask] else: hintmasks.append(hintmask) hintmask_name = hintmask.pointName elif token == "enc": pass elif token == "rb": if hintmask_name is None: hintmask_name = hintmask.pointName hintmask.hList.append(args) args = [] has_hints = True elif token == "ry": if hintmask_name is None: hintmask_name = hintmask.pointName hintmask.vList.append(args) args = [] has_hints = True elif token == "rm": # vstem3's are vhints if hintmask_name is None: hintmask_name = hintmask.pointName has_hints = True vstem3_args.append(args) args = [] if len(vstem3_args) == 3: hintmask.vstem3List.append(vstem3_args) vstem3_args = [] elif token == "rv": # hstem3's are hhints has_hints = True hstem3_args.append(args) args = [] if len(hstem3_args) == 3: hintmask.hstem3List.append(hstem3_args) hstem3_args = [] elif token == "preflx1": # the preflx1/preflx2a sequence provides the same i as the flex # sequence; the difference is that the preflx1/preflx2a sequence # provides the argument values needed for building a Type1 string # while the flex sequence is simply the 6 rcurveto points. Both # sequences are always provided. args = [] # need to skip all move-tos until we see the "flex" operator. in_preflex = True elif token == "preflx2a": args = [] elif token == "flxa": # flex with absolute coords. in_preflex = False flex_point_name = BASE_FLEX_NAME + str(op_index).zfill(4) flexes.append(flex_point_name) # The first 12 args are the 6 args for each of # the two curves that make up the flex feature. i = 0 while i < 2: current_x = args[0] current_y = args[1] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y} contour.append(point) current_x = args[2] current_y = args[3] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y} contour.append(point) current_x = args[4] current_y = args[5] x, y = convertCoords(current_x, current_y) point_type = 'curve' point = {"x": x, "y": y, "type": point_type} contour.append(point) operators.append([point_type, current_x, current_y]) op_index += 1 if i == 0: args = args[6:12] i += 1 # attach the point name to the first point of the first curve. contour[-6][POINT_NAME] = flex_point_name if hintmask_name is not None: # We have a hint mask that we want to attach to the first # point of the flex op. However, there is already a flex # name in that attribute. What we do is set the flex point # name into the hint mask. hintmask.pointName = flex_point_name hintmask_name = None args = [] elif token == "sc": pass elif token == "cp": pass elif token == "ed": pass else: if in_preflex and token in ["rmt", "mt"]: continue if token in ["rmt", "mt", "dt", "ct"]: op_index += 1 else: raise BezParseError( "Unhandled operation: '%s' '%s'." % (args, token)) point_type = bezToUFOPoint[token] if token in ["rmt", "mt", "dt"]: if token in ["mt", "dt"]: current_x = args[0] current_y = args[1] else: current_x += args[0] current_y += args[1] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y, "type": point_type} if point_type == "move": if contour is not None: if len(contour) == 1: # Just in case we see two moves in a row, # delete the previous contour if it has # only the move-to log.info("Deleting moveto: %s adding %s", contours[-1], contour) del contours[-1] else: # Fix the start/implied end path # of the previous path. fixStartPoint(contour, operators) operators = [] contour = [] contours.append(contour) if hintmask_name is not None: point[POINT_NAME] = hintmask_name hintmask_name = None contour.append(point) operators.append([point_type, current_x, current_y]) else: # "ct" current_x = args[0] current_y = args[1] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y} contour.append(point) current_x = args[2] current_y = args[3] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y} contour.append(point) current_x = args[4] current_y = args[5] x, y = convertCoords(current_x, current_y) point = {"x": x, "y": y, "type": point_type} contour.append(point) if hintmask_name is not None: # attach the pointName to the first point of the curve. contour[-3][POINT_NAME] = hintmask_name hintmask_name = None operators.append([point_type, current_x, current_y]) args = [] if contour is not None: if len(contour) == 1: # Just in case we see two moves in a row, delete # the previous contour if it has zero length. del contours[-1] else: fixStartPoint(contour, operators) # Add hints, if any. # Must be done at the end of op processing to make sure we have seen all # the hints in the bez string. # Note that the hintmasks are identified in the operators by the point # name. We will follow the T1 spec: a glyph may have stem3 counter hints # or regular hints, but not both. if has_hints or len(flexes) > 0: hints = OrderedDict() hints["id"] = "" # Convert the rest of the hint masks to a hintmask op and hintmask # bytes. hints[HINT_SET_LIST_NAME] = [] for hintmask in hintmasks: hints[HINT_SET_LIST_NAME].append(hintmask.getHintSet()) if len(flexes) > 0: hints[FLEX_INDEX_LIST_NAME] = [] for pointTag in flexes: hints[FLEX_INDEX_LIST_NAME].append(pointTag) return contours, hints def makeHintSet(hints, hintsStem3, isH): # A charstring may have regular v stem hints or vstem3 hints, but not both. # Same for h stem hints and hstem3 hints. hintset = [] if len(hintsStem3) > 0: hintsStem3.sort() numHints = len(hintsStem3) hintLimit = int((STACK_LIMIT - 2) / 2) if numHints >= hintLimit: hintsStem3 = hintsStem3[:hintLimit] hintset.append(makeStemHintList(hintsStem3, isH)) else: hints.sort() numHints = len(hints) hintLimit = int((STACK_LIMIT - 2) / 2) if numHints >= hintLimit: hints = hints[:hintLimit] hintset.extend(makeHintList(hints, isH)) return hintset psautohint-2.3.0/requirements.txt000066400000000000000000000003251401523215600171750ustar00rootroot00000000000000# 'lxml' is not a primary requirement of psautohint but it's listed here because we # want to have control over the version and guarantee that the XML output remains stable lxml==4.6.2 fonttools[ufo,lxml]==4.20.0 psautohint-2.3.0/setup.cfg000066400000000000000000000002611401523215600155310ustar00rootroot00000000000000[sdist] formats = zip [metadata] license_file = LICENSE [flake8] select = E,F,W ignore = W504 exclude = .eggs, .git, .pytest_cache, .tox, build, dist, psautohint-2.3.0/setup.py000066400000000000000000000551541401523215600154350ustar00rootroot00000000000000import io import os from distutils import log from setuptools import setup, Extension, Command from setuptools.dist import Distribution from distutils.errors import DistutilsSetupError from distutils.sysconfig import customize_compiler as _customize_compiler from distutils.dep_util import newer_group from distutils.ccompiler import show_compilers from setuptools.command.build_clib import build_clib as _build_clib from setuptools.command.build_ext import build_ext as _build_ext class Executable(Extension): pass def customize_compiler(compiler): """The default distutils' customize_compiler does not forward CFLAGS, LDFLAGS and CPPFLAGS when linking executables, so we need to do it here. """ _customize_compiler(compiler) if compiler.compiler_type == "unix": linker_exe = " ".join(compiler.linker_exe) if 'LDFLAGS' in os.environ: linker_exe += ' ' + os.environ['LDFLAGS'] if 'CFLAGS' in os.environ: linker_exe += ' ' + os.environ['CFLAGS'] if 'CPPFLAGS' in os.environ: linker_exe += ' ' + os.environ['CPPFLAGS'] compiler.set_executable("linker_exe", linker_exe) class CustomDistribution(Distribution): """ Adds an 'executables' setup keyword which must be a list of Exectutable instances. Pass it to 'distclass' setup keyword to replace the default setuptools Distribution class. """ def __init__(self, attrs=None): self.executables = None if attrs: executables = attrs.get("executables") if executables: del attrs["executables"] self.executables = executables Distribution.__init__(self, attrs) def has_executables(self): return self.executables and len(self.executables) > 0 # this code is mostly derived from distutils' build_ext, only that it links a # native executable (independent of python) instead of an extension module. class build_exe(Command): description = "build C/C++ executables (compile/link to build directory)" sep_by = " (separated by '%s')" % os.pathsep user_options = [ ('build-lib=', 'b', "directory for compiled executables"), ('build-temp=', 't', "directory for temporary files (build by-products)"), ('inplace', 'i', "ignore build-lib and put compiled executables into the source " + "directory alongside your pure Python modules"), ('include-dirs=', 'I', "list of directories to search for header files" + sep_by), ('define=', 'D', "C preprocessor macros to define"), ('undef=', 'U', "C preprocessor macros to undefine"), ('libraries=', 'l', "external C libraries to link with"), ('library-dirs=', 'L', "directories to search for external C libraries" + sep_by), ('rpath=', 'R', "directories to search for shared C libraries at runtime"), ('link-objects=', 'O', "extra explicit link objects to include in the link"), ('debug', 'g', "compile/link with debugging information"), ('force', 'f', "forcibly build everything (ignore file timestamps)"), ('compiler=', 'c', "specify the compiler type"), ('asan', None, 'debug + Address Sanitizer'), ] boolean_options = ['inplace', 'debug', 'force', 'asan'] help_options = [ ('help-compiler', None, "list available compilers", show_compilers), ] def initialize_options(self): self.executables = None self.build_lib = None self.plat_name = None self.build_temp = None self.inplace = 0 self.include_dirs = None self.define = None self.undef = None self.libraries = None self.library_dirs = None self.rpath = None self.link_objects = None self.debug = None self.force = None self.compiler = None self.asan = None def finalize_options(self): self.set_undefined_options('build', ('build_lib', 'build_lib'), ('build_temp', 'build_temp'), ('compiler', 'compiler'), ('debug', 'debug'), ('force', 'force'), ) self.executables = self.distribution.executables self.ensure_string_list('libraries') self.ensure_string_list('link_objects') if self.libraries is None: self.libraries = [] if self.library_dirs is None: self.library_dirs = [] elif isinstance(self.library_dirs, str): self.library_dirs = self.library_dirs.split(os.pathsep) if self.rpath is None: self.rpath = [] elif isinstance(self.rpath, str): self.rpath = self.rpath.split(os.pathsep) if self.asan: # implies debug self.debug = 1 asancflags = " -O0 -g -fsanitize=address" cflags = os.environ.get('CFLAGS', '') + asancflags os.environ['CFLAGS'] = cflags asanldflags = " -fsanitize=address -shared-libasan" ldflags = os.environ.get('LDFLAGS', '') + asanldflags os.environ['LDFLAGS'] = ldflags # for executables under windows use different directories # for Release and Debug builds. if os.name == 'nt': if self.debug: self.build_temp = os.path.join(self.build_temp, "Debug") else: self.build_temp = os.path.join(self.build_temp, "Release") # The argument parsing will result in self.define being a string, but # it has to be a list of 2-tuples. All the preprocessor symbols # specified by the 'define' option will be set to '1'. Multiple # symbols can be separated with commas. if self.define: defines = self.define.split(',') self.define = [(symbol, '1') for symbol in defines] # The option for macros to undefine is also a string from the # option parsing, but has to be a list. Multiple symbols can also # be separated with commas here. if self.undef: self.undef = self.undef.split(',') def run(self): from distutils.ccompiler import new_compiler # 'self.executables', as supplied by setup.py, is a list of # Executable instances. if not self.executables: return # If we were asked to build any C/C++ libraries, make sure that the # directory where we put them is in the library search path for # linking executables. if self.distribution.has_c_libraries(): build_clib = self.get_finalized_command('build_clib') self.libraries.extend(build_clib.get_library_names() or []) self.library_dirs.append(build_clib.build_clib) # make sure build_clib is run before build_exe (no-op if command # has already run) self.run_command("build_clib") # Setup the CCompiler object that we'll use to do all the # compiling and linking self.compiler = new_compiler(compiler=self.compiler, verbose=self.verbose, dry_run=self.dry_run, force=self.force) customize_compiler(self.compiler) # And make sure that any compile/link-related options (which might # come from the command-line or from the setup script) are set in # that CCompiler object -- that way, they automatically apply to # all compiling and linking done here. if self.include_dirs is not None: self.compiler.set_include_dirs(self.include_dirs) if self.define is not None: # 'define' option is a list of (name,value) tuples for (name, value) in self.define: self.compiler.define_macro(name, value) if self.undef is not None: for macro in self.undef: self.compiler.undefine_macro(macro) if self.libraries is not None: self.compiler.set_libraries(self.libraries) if self.library_dirs is not None: self.compiler.set_library_dirs(self.library_dirs) if self.rpath is not None: self.compiler.set_runtime_library_dirs(self.rpath) if self.link_objects is not None: self.compiler.set_link_objects(self.link_objects) # Now actually compile and link everything. self.build_executables() def check_executables_list(self, executables): """Ensure that the list of executables is valid, i.e. it is a list of Executable objects. Raise DistutilsSetupError if the structure is invalid anywhere; just returns otherwise. """ if (not isinstance(executables, list) or not all(isinstance(e, Executable) for e in executables)): raise DistutilsSetupError( "'executables' option must be a list of Extension instances") def get_source_files(self): self.check_executables_list(self.executables) filenames = [] for exe in self.executables: filenames.extend(exe.sources) filenames.extend(exe.depends) return filenames def get_outputs(self): self.check_executables_list(self.executables) outputs = [] for exe in self.executables: outputs.append(self.get_exe_fullpath(exe.name)) return outputs def build_executables(self): self.check_executables_list(self.executables) for exe in self.executables: self.build_executable(exe) def build_executable(self, exe): sources = exe.sources if sources is None or not isinstance(sources, (list, tuple)): raise DistutilsSetupError( "in 'executables' option (executable '%s'), " "'sources' must be present and must be " "a list of source filenames" % exe.name) sources = list(sources) exe_path = self.get_exe_fullpath(exe.name) depends = sources + exe.depends if not (self.force or newer_group(depends, exe_path, 'newer')): log.debug("skipping '%s' executable (up-to-date)", exe.name) return else: log.info("building '%s' executable", exe.name) extra_args = exe.extra_compile_args or [] macros = exe.define_macros[:] for undef in exe.undef_macros: macros.append((undef,)) objects = self.compiler.compile(sources, output_dir=self.build_temp, macros=macros, include_dirs=exe.include_dirs, debug=self.debug, extra_postargs=extra_args, depends=exe.depends) if exe.extra_objects: objects.extend(exe.extra_objects) extra_args = exe.extra_link_args or [] language = exe.language or self.compiler.detect_language(sources) pkg_dir, exe_filename = os.path.split(self.get_exe_filename(exe.name)) # '.exe' suffix is added automatically as needed by the CCompiler exe_name, _ = os.path.splitext(exe_filename) # we compile the executable into a subfolder of build_temp and # finally copy only the executable file to the destination, ignoring # the other build by-products build_temp = os.path.join(self.build_temp, pkg_dir) self.compiler.link_executable( objects, exe_name, output_dir=build_temp, libraries=exe.libraries, library_dirs=exe.library_dirs, runtime_library_dirs=exe.runtime_library_dirs, extra_postargs=extra_args, debug=self.debug, target_lang=language) self.mkpath(os.path.dirname(exe_path)) self.copy_file(os.path.join(build_temp, exe_filename), exe_path) # -- Name generators ----------------------------------------------- # (executable names, filenames, whatever) def get_exe_fullpath(self, exe_name): """Returns the path of the filename for a given executable. The file is located in `build_lib` or directly in the package (inplace option). """ modpath = exe_name.split('.') filename = self.get_exe_filename(modpath[-1]) if not self.inplace: # no further work needed # returning : # build_dir/package/path/filename filename = os.path.join(*modpath[:-1] + [filename]) return os.path.join(self.build_lib, filename) # the inplace option requires to find the package directory # using the build_py command for that package = '.'.join(modpath[0:-1]) build_py = self.get_finalized_command('build_py') package_dir = os.path.abspath(build_py.get_package_dir(package)) # returning # package_dir/filename return os.path.join(package_dir, filename) def get_exe_filename(self, exe_name): r"""Convert the name of an executable (eg. "foo.bar") into the name of the file from which it will be loaded (eg. "foo/bar", or "foo\bar.exe"). """ from distutils.sysconfig import get_config_var exe_path = exe_name.split('.') exe_suffix = get_config_var('EXE') return os.path.join(*exe_path) + exe_suffix class CustomBuildClib(_build_clib): """Includes in the sdist all the libraries' headers (filenames specified in the 'obj_deps' dict of a library's build_info dict). """ def get_source_files(self): filenames = _build_clib.get_source_files(self) msg = ( "in 'libraries' option (library '%s'), " "'obj_deps' must be a dictionary of " "type 'source: list'" ) for (lib_name, build_info) in self.libraries: sources = build_info['sources'] obj_deps = build_info.get('obj_deps', dict()) if not isinstance(obj_deps, dict): raise DistutilsSetupError(msg % lib_name) global_deps = obj_deps.get('', []) if not isinstance(global_deps, (list, tuple)): raise DistutilsSetupError(msg % lib_name) filenames.extend(global_deps) for source in sources: extra_deps = obj_deps.get(source, []) if not isinstance(extra_deps, (list, tuple)): raise DistutilsSetupError(msg % lib_name) filenames.extend(extra_deps) return filenames def build_libraries(self, libraries): for (lib_name, build_info) in libraries: sources = build_info.get('sources') if sources is None or not isinstance(sources, (list, tuple)): raise DistutilsSetupError( "in 'libraries' option (library '%s'), " "'sources' must be present and must be " "a list of source filenames" % lib_name) sources = list(sources) log.info("building '%s' library", lib_name) # First, compile the source code to object files in the library # directory. (This should probably change to putting object # files in a temporary build directory.) macros = build_info.get('macros') include_dirs = build_info.get('include_dirs') objects = self.compiler.compile(sources, output_dir=self.build_temp, macros=macros, include_dirs=include_dirs, debug=self.debug) # Now "link" the object files together into a static library. # (On Unix at least, this isn't really linking -- it just # builds an archive. Whatever.) self.compiler.create_static_lib(objects, lib_name, output_dir=self.build_clib, debug=self.debug) class CustomBuildExt(_build_ext): def run(self): if self.distribution.has_c_libraries(): self.run_command("build_clib") _build_ext.run(self) cmdclass = { 'build_clib': CustomBuildClib, 'build_ext': CustomBuildExt, 'build_exe': build_exe, } libraries = [ ( "psautohint", { "sources": [ "libpsautohint/src/ac.c", "libpsautohint/src/acfixed.c", "libpsautohint/src/auto.c", "libpsautohint/src/bbox.c", "libpsautohint/src/buffer.c", "libpsautohint/src/charpath.c", "libpsautohint/src/charpathpriv.c", "libpsautohint/src/charprop.c", "libpsautohint/src/check.c", "libpsautohint/src/control.c", "libpsautohint/src/eval.c", "libpsautohint/src/fix.c", "libpsautohint/src/flat.c", "libpsautohint/src/fontinfo.c", "libpsautohint/src/gen.c", "libpsautohint/src/head.c", "libpsautohint/src/logging.c", "libpsautohint/src/memory.c", "libpsautohint/src/merge.c", "libpsautohint/src/misc.c", "libpsautohint/src/optable.c", "libpsautohint/src/pick.c", "libpsautohint/src/psautohint.c", "libpsautohint/src/read.c", "libpsautohint/src/report.c", "libpsautohint/src/shuffle.c", "libpsautohint/src/stemreport.c", "libpsautohint/src/write.c", ], "include_dirs": [ "libpsautohint/include", ], "obj_deps": { # TODO: define per-file dependecies instead of global ones # so that only the modified object files are recompiled "": [ "libpsautohint/include/psautohint.h", "libpsautohint/src/ac.h", "libpsautohint/src/basic.h", "libpsautohint/src/bbox.h", "libpsautohint/src/charpath.h", "libpsautohint/src/fontinfo.h", "libpsautohint/src/logging.h", "libpsautohint/src/memory.h", "libpsautohint/src/opcodes.h", "libpsautohint/src/optable.h", # "libpsautohint/src/version.h", # autogenerated ], }, "macros": [ ("AC_C_LIB_EXPORTS", None), ], "cflags": [ # fixes segmentation fault when python (and thus the extension # module) is compiled with -O3 and tree vectorize: # https://github.com/khaledhosny/psautohint/issues/16 "-fno-tree-vectorize", ] if os.name != "nt" else [], } ), ] ext_modules = [ Extension( "psautohint._psautohint", include_dirs=[ "libpsautohint/include", ], sources=[ "python/psautohint/_psautohint.c", ], ), ] executables = [ Executable( "psautohint.autohintexe", sources=[ "libpsautohint/autohintexe.c" ], include_dirs=[ "libpsautohint/include", ], # when building with MSVC on Windows, don't link the C math library. # XXX what about MinGW or Cygwin? # Normally we wouldn't need to pass '-lpsautohint' explicitly here as # that's done by default automatically for all the libraries built by # build_clib. The problem is Distutils CCompiler places the global # linker options after the ones specific to the extension/executable # being linked, and when linking libpsautohint in the standalone # executable, we want '-lpsautohint' to be placed before '-lm' in the # linker command line, otherwise we get linker errors (strangely, only # when running gcc with --coverage): # https://travis-ci.org/adobe-type-tools/psautohint/jobs/423944212#L581 # It should be ok if '-lpsautohint' is mentioned twice. libraries=["psautohint"] + (["m"] if os.name != "nt" else []), ), ] with io.open("README.md", encoding="utf-8") as readme: long_description = readme.read() VERSION_TEMPLATE = """\ /* file generated by setuptools_scm don't change, don't track in version control */ #define PSAUTOHINT_VERSION "{version}" """ setup(name="psautohint", use_scm_version={ "write_to": "libpsautohint/src/version.h", "write_to_template": VERSION_TEMPLATE, }, description="Python wrapper for Adobe's PostScript autohinter", long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/adobe-type-tools/psautohint', author='Adobe Type team & friends', author_email='afdko@adobe.com', license='Apache License, Version 2.0', package_dir={'': 'python'}, packages=['psautohint'], libraries=libraries, ext_modules=ext_modules, executables=executables, entry_points={ 'console_scripts': [ "psautohint = psautohint.__main__:main", "psstemhist = psautohint.__main__:stemhist", ], }, python_requires='>3.6', setup_requires=["setuptools_scm"], install_requires=[ 'fonttools[ufo]>=3.32.0', ], extras_require={ "testing": [ "pytest >= 3.0.0, <4", "pytest-cov >= 2.5.1, <3", "pytest-xdist >= 1.22.2, <1.28.0", "pytest-randomly >= 1.2.3, <2", ], }, cmdclass=cmdclass, distclass=CustomDistribution, zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Topic :: Text Processing :: Fonts', 'Topic :: Multimedia :: Graphics', 'Topic :: Multimedia :: Graphics :: Graphics Conversion', ], ) psautohint-2.3.0/tests/000077500000000000000000000000001401523215600150535ustar00rootroot00000000000000psautohint-2.3.0/tests/integration/000077500000000000000000000000001401523215600173765ustar00rootroot00000000000000psautohint-2.3.0/tests/integration/__init__.py000066400000000000000000000004521401523215600215100ustar00rootroot00000000000000import os import py.path here = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) DATA_DIR = os.path.relpath(os.path.join(here, "data"), os.getcwd()) def make_temp_copy(tmpdir, path): src = py.path.local(path) dst = tmpdir / src.basename src.copy(dst) return str(dst) psautohint-2.3.0/tests/integration/data/000077500000000000000000000000001401523215600203075ustar00rootroot00000000000000psautohint-2.3.0/tests/integration/differ.py000077500000000000000000000317571401523215600212270ustar00rootroot00000000000000# Copyright 2018 Adobe. All rights reserved. """ Helper script for diff'ing files. Used as part of the integration tests. """ import argparse import difflib import filecmp import logging import os import re import sys __version__ = '0.3.3' logger = logging.getLogger('differ') TXT_MODE = 'txt' BIN_MODE = 'bin' SPLIT_MARKER = '_+:+_' DFLT_ENC = 'utf-8' class Differ(object): def __init__(self, opts): self.mode = opts.mode self.path1 = opts.path1 self.path2 = opts.path2 # tuple of strings containing the starts of lines to skip self.skip_strings = opts.skip_strings # tuple of integers for the line numbers to skip self.skip_lines = opts.skip_lines # regex pattern matching the beginning of line self.skip_regex = opts.skip_regex self.encoding = opts.encoding def diff_paths(self): """ Diffs the contents of two paths using the parameters provided. Returns True if the contents match, and False if they don't. """ if os.path.isdir(self.path1): return self._diff_dirs() else: # paths are files if self.mode == TXT_MODE: return self._diff_txt_files(self.path1, self.path2) elif self.mode == BIN_MODE: return filecmp.cmp(self.path1, self.path2) def _diff_txt_files(self, path1, path2): """ Diffs two text-based files using difflib. Returns True if the contents match, and False if they don't. NOTE: This method CANNOT use self.path1 and self.path2 because it gets called from _diff_dirs(). """ expected = self._read_txt_file(path1) actual = self._read_txt_file(path2) if actual != expected: for line in difflib.unified_diff(expected, actual, fromfile=path1, tofile=path2, n=1): sys.stdout.write(line) return False return True def _read_txt_file(self, path): """ Reads a text file and returns a list of its lines. """ # Hard code a first line; this way the difflib results start # from 1 instead of zero, thus matching the file's line numbers lines = [''] try: with open(path, "r", encoding=self.encoding) as f: for i, line in enumerate(f.readlines(), 1): # Skip lines that change, such as timestamps if self._line_to_skip(line): logger.debug(f"Matched begin of line. " f"Skipped: {line.rstrip()}") # Blank the line instead of actually skipping (via # 'continue'); this way the difflib results show the # correct line numbers line = '' # Skip specific lines, referenced by number elif i in self.skip_lines: logger.debug(f"Matched line #{i}. " f"Skipped: {line.rstrip()}") line = '' # Skip lines that match regex elif self.skip_regex and self.skip_regex.match(line): logger.debug(f"Matched regex begin of line. " f"Skipped: {line.rstrip()}") line = '' # Use os-native line separator to enable running difflib lines.append(line.rstrip() + os.linesep) except UnicodeDecodeError: logger.error(f"Couldn't read text file using '{self.encoding}' " f"encoding.\n File path: {path}") sys.exit(1) return lines def _line_to_skip(self, line): """ Loops over the skip items. Returns True if the beginning of the line matches a skip item. """ for item in self.skip_strings: if line.startswith(item): return True return False def _diff_dirs(self): """ Diffs two folders containing files. Returns True if all files match. Returns False if the folders' contents don't match, or as soon as one non-matching file is found. """ all_rel_file_paths = self._compare_dir_contents() if all_rel_file_paths is None: return False for rel_file_path in all_rel_file_paths: path1 = self.path1 + rel_file_path assert os.path.exists(path1), f"Not a valid path1: {path1}" path2 = self.path2 + rel_file_path assert os.path.exists(path2), f"Not a valid path2: {path2}" if self.mode == BIN_MODE: diff_result = filecmp.cmp(path1, path2) else: diff_result = self._diff_txt_files(path1, path2) if not diff_result: logger.debug(f"Non-matching file: {rel_file_path}") return False return True def _report_dir_diffs(self, all_paths1, all_paths2): """ Returns a string listing the paths that exist in folder 1 but not in 2, and vice-versa. """ diffs_str = '' set_1st = set(all_paths1) set_2nd = set(all_paths2) diff1 = sorted(set_1st - set_2nd) diff2 = sorted(set_2nd - set_1st) if diff1: dir1 = os.path.basename(self.path1) dj1 = '\n '.join(diff1) diffs_str += (f"\n In 1st folder ({dir1}) but not in 2nd:" f"\n {dj1}") if diff2: dir2 = os.path.basename(self.path2) dj2 = '\n '.join(diff2) diffs_str += (f"\n In 2nd folder ({dir2}) but not in 1st:" f"\n {dj2}") return diffs_str def _compare_dir_contents(self): """ Checks if two directory trees have the same files and folders. Returns a list of relative paths to all files if the dirs' contents match, and None if they don't. """ all_paths1 = self._get_all_file_paths_in_dir_tree(self.path1) all_paths2 = self._get_all_file_paths_in_dir_tree(self.path2) if all_paths1 != all_paths2: dd = self._report_dir_diffs(all_paths1, all_paths2) logger.info(f"Folders' contents don't match.{dd}") return None return all_paths1 @staticmethod def _get_all_file_paths_in_dir_tree(start_path): """ Returns a list of relative paths of all files in a directory tree. The list's items are ordered top-down according to the tree. """ all_paths = [] for dir_name, _, file_names in os.walk(start_path): all_paths.extend( [os.path.join(dir_name, f_name) for f_name in file_names]) # Make the paths relative, and enforce order. all_paths = sorted( [path.replace(start_path, '') for path in all_paths]) logger.debug(f"All paths: {all_paths}") return all_paths def _get_path_kind(pth): """ Returns a string describing the kind of path. Possible values are 'file', 'folder', and 'invalid'. """ try: if os.path.isfile(pth): return 'file' elif os.path.isdir(pth): return 'folder' elif not os.path.exists(pth): return 'invalid' except TypeError: return 'invalid' def _paths_are_same_kind(path1, path2): """ Checks that both paths are either files or folders. Returns boolean. """ if all([os.path.isfile(path) for path in (path1, path2)]): return True elif all([os.path.isdir(path) for path in (path1, path2)]): return True return False def _validate_path(path_str): valid_path = os.path.abspath(os.path.realpath(path_str)) if not os.path.exists(valid_path): raise argparse.ArgumentTypeError( f"{path_str} is not a valid path.") return valid_path def _split_string_sequence(str_seq): return tuple(str_seq.split(SPLIT_MARKER)) def _split_num_range_or_delta(num_str): num_range = num_str.split('-') + ['-'] num_delta = num_str.split('+') + ['+'] if len(num_range) == 3: return num_range elif len(num_delta) == 3: return num_delta elif num_str.isnumeric(): return num_str else: raise argparse.ArgumentTypeError( f"Invalid number range or delta: {num_str}") def _convert_to_int(num_str): try: return int(num_str) except ValueError: raise argparse.ArgumentTypeError(f"Not a number: {num_str}") def _expand_num_range_or_delta(num_str_lst): start_num = _convert_to_int(num_str_lst[0]) rng_dlt_num = _convert_to_int(num_str_lst[1]) sign = num_str_lst[2] if sign == '+': return list(range(start_num, start_num + rng_dlt_num + 1)) else: # sign == '-' if not (rng_dlt_num >= start_num): raise argparse.ArgumentTypeError( f"The start of range value is larger than the end of range " f"value: {start_num}-{rng_dlt_num}") return list(range(start_num, rng_dlt_num + 1)) def _convert_seq_to_ints(num_seq): seq = [] for item in num_seq: if isinstance(item, list): seq.extend(_expand_num_range_or_delta(item)) else: seq.append(_convert_to_int(item)) return sorted(set(seq)) def _split_linenumber_sequence(str_seq): num_seq = [_split_num_range_or_delta(item) for item in str_seq.split(',')] return tuple(_convert_seq_to_ints(num_seq)) def _compile_regex(str_seq): if not str_seq.startswith('^'): raise argparse.ArgumentTypeError( "The expression must start with the caret '^' character") try: return re.compile(str_seq) except re.error as err: raise argparse.ArgumentTypeError( f'The expression is invalid: {err}') def get_options(args): parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description=__doc__ ) parser.add_argument( '--version', action='version', version=__version__ ) parser.add_argument( '-v', '--verbose', action='count', default=0, help='verbose mode\n' 'Use -vv for debug mode' ) parser.add_argument( '-m', '--mode', default=TXT_MODE, choices=(TXT_MODE, BIN_MODE), help='diff mode (default: %(default)s)' ) parser.add_argument( '-s', '--string', dest='skip_strings', type=_split_string_sequence, default=(), help=f'string for matching the beginning of a line to skip\n' f'For multiple strings, separate them with {SPLIT_MARKER}' ) parser.add_argument( '-l', '--line', dest='skip_lines', type=_split_linenumber_sequence, default=(), help='number of a line to skip\n' 'For multiple line numbers, separate them with a comma (,).\n' 'For ranges of lines, use a minus (-) between two numbers.\n' 'For a line delta, use a plus (+) between two numbers.' ) parser.add_argument( '-r', '--regex', dest='skip_regex', type=_compile_regex, help='regular expression matching the beginning of a line to skip\n' "The expression must start with the caret '^' character, " 'and characters such as backslash, semicolon, and space need ' 'to be escaped.' ) parser.add_argument( '-e', '--encoding', default=DFLT_ENC, choices=(DFLT_ENC, 'macroman'), help='encoding to use when opening text files (default: %(default)s)' ) parser.add_argument( 'path1', metavar='PATH1', type=_validate_path, help='1st path for comparison' ) parser.add_argument( 'path2', metavar='PATH2', type=_validate_path, help='2nd path for comparison' ) options = parser.parse_args(args) if not options.verbose: level = "WARNING" elif options.verbose == 1: level = "INFO" else: level = "DEBUG" logging.basicConfig(level=level) if not _paths_are_same_kind(options.path1, options.path2): kp1 = _get_path_kind(options.path1) kp2 = _get_path_kind(options.path2) parser.error(f"The paths are not of the same kind. " f"Path1's kind is {kp1}. " f"Path2's kind is {kp2}.") logger.debug(f"Line numbers: {options.skip_lines}") regexpat = getattr(options.skip_regex, 'pattern', None) logger.debug(f"Regular expression: {regexpat}") return options def main(args=None): """ Returns True if the inputs match, and False if they don't. """ opts = get_options(args) differ = Differ(opts) return differ.diff_paths() if __name__ == "__main__": sys.exit(0 if main() else 1) psautohint-2.3.0/tests/integration/test_cli.py000066400000000000000000000237211401523215600215630ustar00rootroot00000000000000import glob import os from os.path import basename import subprocess import pytest from psautohint import FontParseError from psautohint.__main__ import main as psautohint from psautohint.__main__ import stemhist from .differ import main as differ from . import make_temp_copy, DATA_DIR # font.otf, font.cff, font.ufo FONTS = glob.glob("%s/dummy/font.[ocu][tf][fo]" % DATA_DIR) def autohint(args): return psautohint(["--all"] + args) @pytest.mark.parametrize("path", FONTS) def test_basic(path, tmpdir): # the input font is modified in-place, make a temp copy first autohint([make_temp_copy(tmpdir, path)]) @pytest.mark.parametrize("path", FONTS) def test_outpath(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out]) def test_multi_outpath(tmpdir): """Test handling multiple output paths.""" paths = sorted(glob.glob("%s/dummy/mm0/*.ufo" % DATA_DIR)) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) for p in inpaths] autohint(inpaths + ['-o'] + outpaths + ['-r', reference]) def test_multi_outpath_unequal(tmpdir): """Test that we exit if output paths don't match number of input paths.""" paths = sorted(glob.glob("%s/dummy/mm0/*.ufo" % DATA_DIR)) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) for p in inpaths][1:] with pytest.raises(SystemExit): autohint(inpaths + ['-o'] + outpaths + ['-r', reference]) def test_multi_different_formats(tmpdir): """Test that we exit if input paths are of different formats.""" base = "%s/dummy/mm0" % DATA_DIR paths = sorted(glob.glob(base + "/*.ufo")) otfs = sorted(glob.glob(base + "/*.otf")) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, otfs[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) for p in inpaths] with pytest.raises(SystemExit): autohint(inpaths + ['-o'] + outpaths + ['-r', reference]) def test_multi_reference_is_input(tmpdir): """Test that we exit if reference font is also one of the input paths.""" paths = sorted(glob.glob("%s/dummy/mm0/*.ufo" % DATA_DIR)) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = [reference] + paths[1:] outpaths = [str(tmpdir / basename(p)) for p in inpaths] with pytest.raises(SystemExit): autohint(inpaths + ['-o'] + outpaths + ['-r', reference]) def test_multi_reference_is_duplicated(tmpdir): """Test that we exit if one of the input paths is duplicated.""" paths = sorted(glob.glob("%s/dummy/mm0/*.ufo" % DATA_DIR)) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] + [paths[1]] outpaths = [str(tmpdir / basename(p)) for p in inpaths] with pytest.raises(SystemExit): autohint(inpaths + ['-o'] + outpaths + ['-r', reference]) tx_found = False try: subprocess.check_call(["tx", "-h"]) tx_found = True except (subprocess.CalledProcessError, OSError): pass @pytest.mark.parametrize("path", glob.glob("%s/dummy/font.p*" % DATA_DIR)) @pytest.mark.skipif(tx_found, reason="'tx' is found") def test_type1_raises(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" with pytest.raises(SystemExit): autohint([path, '-o', out]) @pytest.mark.parametrize("path", glob.glob("%s/dummy/font.p*" % DATA_DIR)) @pytest.mark.skipif(tx_found is False, reason="'tx' is missing") def test_type1_supported(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out]) @pytest.mark.parametrize("glyphs", [ 'a,b,c', # Glyph List 'a-z', # Glyph range 'FOO,BAR,a', # Some glyphs in the list do not exist. ]) def test_glyph_list(glyphs, tmpdir): path = "%s/dummy/font.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, '-g', glyphs]) @pytest.mark.parametrize("glyphs", [ '/0,/1,/2', '/0-/10', 'cid0,cid1,cid2', 'cid0-cid10', ]) def test_cid_glyph_list(glyphs, tmpdir): path = "%s/source-code-pro/CID/font.otf" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, '-g', glyphs]) @pytest.mark.parametrize("glyphs", [ 'a,b,c', 'a-z', ]) def test_exclude_glyph_list(glyphs, tmpdir): path = "%s/dummy/font.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, '-x', glyphs]) @pytest.mark.parametrize("glyphs", [ 'FOO,BAR', 'FOO-BAR', 'FOO-a', 'a-BAR', ]) def test_missing_glyph_list(glyphs, tmpdir): path = "%s/dummy/font.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" with pytest.raises(FontParseError): autohint([path, '--traceback', '-o', out, '-g', glyphs]) @pytest.mark.parametrize("path", ["%s/dummy/fontinfo" % DATA_DIR, DATA_DIR]) def test_unsupported_format(path): with pytest.raises(SystemExit): autohint([path]) def test_missing_cff_table1(tmpdir): path = "%s/dummy/nocff.otf" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" assert autohint([path, '-o', out]) == 1 def test_missing_cff_table2(tmpdir): path = "%s/dummy/nocff.otf" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" with pytest.raises(FontParseError): autohint([path, '-o', out, '--traceback']) @pytest.mark.parametrize("option,argument", [ ("--exclude-glyphs-file", "glyphs.txt"), ("--fontinfo-file", "fontinfo"), ("--glyphs-file", "glyphs.txt"), ]) @pytest.mark.parametrize("path", ["font.ufo", "font.otf"]) def test_option(path, option, argument, tmpdir): path = "%s/dummy/%s" % (DATA_DIR, path) out = str(tmpdir / basename(path)) + ".out" argument = "%s/dummy/%s" % (DATA_DIR, argument) autohint([path, '-o', out, option, argument]) @pytest.mark.parametrize("option", [ "--allow-changes", "--decimal", "--no-flex", "--no-hint-sub", "--no-zones-stems", "--print-list-fddict", "--report-only", "--verbose", "--write-to-default-layer", "-vv", ]) @pytest.mark.parametrize("path", ["font.ufo", "font.otf"]) def test_argumentless_option(path, option, tmpdir): path = "%s/dummy/%s" % (DATA_DIR, path) out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, option]) def test_print_fddict(capsys): dummypath = os.path.join(DATA_DIR, "dummy") fontpath = os.path.join(dummypath, "font.otf") fipath = os.path.join(dummypath, "fontinfo_fdd") exp_path = os.path.join(dummypath, 'print_dflt_fddict_expected.txt') with open(exp_path, 'r') as exp_f: expected = exp_f.read() with pytest.raises(SystemExit) as wrapped_exc: autohint([fontpath, '--print-dflt-fddict', '--fontinfo-file', fipath]) str(wrapped_exc) == expected @pytest.mark.parametrize("option", [ "--doc-fddict", "--help", "--info", "--version", ]) def test_doc_option(option): with pytest.raises(SystemExit) as e: autohint([option]) assert e.type == SystemExit assert e.value.code == 0 def test_no_fddict(tmpdir): path = "%s/dummy/mm0/font0.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, "--print-list-fddict"]) @pytest.mark.parametrize("path", [ "%s/dummy/font.ufo" % DATA_DIR, "%s/dummy/font.otf" % DATA_DIR, "%s/dummy/font.cff" % DATA_DIR, ]) def test_overwrite_font(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" autohint([path, '-o', out, '-g', 'a,b,c']) autohint([path, '-o', out, '-g', 'a,b,c']) def test_invalid_input_path(tmpdir): path = str(tmpdir / "foo") + ".otf" with pytest.raises(SystemExit): autohint([path]) def test_invalid_save_path(tmpdir): path = "%s/dummy/font.otf" % DATA_DIR out = str(tmpdir / basename(path) / "foo") + ".out" with pytest.raises(SystemExit): autohint([path, '-o', out]) @pytest.mark.parametrize("args", [ pytest.param(['-z'], id="report_zones"), pytest.param([], id="report_stems"), pytest.param(['-a'], id="report_stems,all_stems"), pytest.param(['-g', 'a-z,A-Z,zero-nine'], id="report_stems,glyphs"), ]) def test_stemhist(args, tmpdir): path = "%s/dummy/font.otf" % DATA_DIR out = str(tmpdir / basename(path)) stemhist([path, '-o', out] + args) if '-z' in args: suffixes = ['.top.txt', '.bot.txt'] else: suffixes = ['.hstm.txt', '.vstm.txt'] for suffix in suffixes: exp_suffix = suffix if '-a' in args: exp_suffix = '.all' + suffix if '-g' in args: g = args[args.index('-g') + 1] exp_suffix = '.' + g + exp_suffix assert differ([path + exp_suffix, out + suffix, '-l', '1']) @pytest.mark.parametrize("path", FONTS) def test_outpath_order(path, tmpdir): """ e.g. psautohint -o outfile infile""" out = str(tmpdir / basename(path)) + ".out" autohint(['-o', out, path]) def test_multi_order(tmpdir): """ e.g. psautohint -o outfile1 outfile2 infile1 infile2""" in1 = "%s/dummy/font.ufo" % DATA_DIR in2 = "%s/dummy/big_glyph.ufo" % DATA_DIR out1 = str(tmpdir / basename(in1)) + ".out" out2 = str(tmpdir / basename(in2)) + ".out" autohint(['-o', out1, out2, in1, in2]) def test_multi_order_unequal(tmpdir): """ e.g. psautohint -o outfile1 outfile2 infile""" in1 = "%s/dummy/font.ufo" % DATA_DIR out1 = str(tmpdir / basename(in1)) + ".out" out2 = str(tmpdir / basename(in1)) + "X.out" with pytest.raises(SystemExit): autohint(['-o', out1, out2, in1]) def test_lack_of_input_raises(tmpdir): with pytest.raises(SystemExit): autohint(['--report-only']) psautohint-2.3.0/tests/integration/test_hint.py000066400000000000000000000265011401523215600217550ustar00rootroot00000000000000import glob from os.path import basename import pytest import logging import subprocess from fontTools.misc.xmlWriter import XMLWriter from fontTools.cffLib import CFFFontSet from fontTools.ttLib import TTFont from psautohint.autohint import ACOptions, ACHintError, hintFiles from psautohint import FontParseError from .differ import main as differ from . import DATA_DIR parametrize = pytest.mark.parametrize class Options(ACOptions): def __init__(self, inpath, outpath): super(Options, self).__init__() self.inputPaths = [inpath] self.outputPaths = [outpath] self.hintAll = True self.verbose = False self.read_hints = True @parametrize("ufo", glob.glob("%s/*/*/font.ufo" % DATA_DIR)) def test_ufo(ufo, tmpdir): out = str(tmpdir / basename(ufo)) options = Options(ufo, out) hintFiles(options) assert differ([ufo, out]) @parametrize("otf", glob.glob("%s/*/*/font.otf" % DATA_DIR)) def test_otf(otf, tmpdir): out = str(tmpdir / basename(otf)) + ".out" options = Options(otf, out) hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) @parametrize("cff", glob.glob("%s/dummy/*.cff" % DATA_DIR)) def test_cff(cff, tmpdir): out = str(tmpdir / basename(cff)) + ".out" options = Options(cff, out) hintFiles(options) for path in (cff, out): font = CFFFontSet() writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") with open(path, "rb") as fp: font.decompile(fp, None) font.toXML(writer) writer.close() assert differ([str(tmpdir / basename(cff)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) tx_found = False try: subprocess.check_call(["tx", "-h"]) tx_found = True except (subprocess.CalledProcessError, OSError): pass @parametrize("path", glob.glob("%s/dummy/font.p*" % DATA_DIR)) @pytest.mark.skipif(tx_found, reason="'tx' is found") def test_type1_raises(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) @parametrize("path", glob.glob("%s/dummy/font.p*" % DATA_DIR)) @pytest.mark.skipif(tx_found is False, reason="'tx' is missing") def test_type1_supported(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) path_dump = str(tmpdir / basename(path)) + ".txt" out_dump = str(tmpdir / basename(out)) + ".txt" subprocess.check_call(["tx", "-dump", "-6", path, path_dump]) subprocess.check_call(["tx", "-dump", "-6", out, out_dump]) assert differ([path_dump, out_dump]) def test_unsupported_format(tmpdir): path = "%s/dummy/fontinfo" % DATA_DIR out = str(tmpdir / basename(path)) options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) def test_missing_cff_table(tmpdir): path = "%s/dummy/nocff.otf" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) def test_ufo_write_to_default_layer(tmpdir): path = "%s/dummy/defaultlayer.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.writeToDefaultLayer = True hintFiles(options) assert differ([path, out]) @parametrize("path", glob.glob("%s/dummy/*_metainfo.ufo" % DATA_DIR)) def test_ufo_bad(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) @parametrize("path", glob.glob("%s/dummy/bad_*.p*" % DATA_DIR)) def test_type1_bad(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) def test_counter_glyphs(tmpdir): path = "%s/dummy/font.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.vCounterGlyphs = ["m", "M", "T"] hintFiles(options) def test_seac_op(tmpdir, caplog): path = "%s/dummy/seac.otf" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) msgs = [r.getMessage() for r in caplog.records] assert "Skipping Aacute: can't process SEAC composite glyphs." in msgs @pytest.mark.skipif(tx_found is False, reason="'tx' is missing") def test_mute_tx_msgs(tmpdir, capfd): path = "%s/dummy/nohints.pfa" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) captured = capfd.readouterr() assert "(cfw) unhinted <.notdef>" not in captured.err @parametrize("path", glob.glob("%s/dummy/bad_privatedict_*" % DATA_DIR)) def test_bad_privatedict(path, tmpdir): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) @parametrize("path", glob.glob("%s/dummy/bad_privatedict_*" % DATA_DIR)) def test_bad_privatedict_accept(path, tmpdir): """Same as above test, but PrivateDict is accepted because of `allow_no_blues` option.""" out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.allow_no_blues = True hintFiles(options) @parametrize("path", glob.glob("%s/dummy/ok_privatedict_*" % DATA_DIR)) def test_ok_privatedict_accept(path, tmpdir, caplog): out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) msg = "There is no value or 0 value for Dominant" assert any(r.getMessage().startswith(msg) for r in caplog.records) @parametrize("otf", glob.glob("%s/libertinus-*/*/font.otf" % DATA_DIR)) def test_flex_otf(otf, tmpdir): out = str(tmpdir / basename(otf)) + ".out" options = Options(otf, out) options.noFlex = False hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) @parametrize("ufo", glob.glob("%s/libertinus-*/*/font.ufo" % DATA_DIR)) def test_flex_ufo(ufo, tmpdir): out = str(tmpdir / basename(ufo)) + ".out" options = Options(ufo, out) options.noFlex = False hintFiles(options) assert differ([ufo, out]) def test_too_long_glyph_name(tmpdir): path = "%s/dummy/too_long_glyph_name.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(ACHintError): hintFiles(options) def test_hashmap_glyph_changed(tmpdir, caplog): path = "%s/dummy/hashmap_glyph_changed.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) msgs = [r.getMessage() for r in caplog.records] assert "Glyph 'a' has been edited. You must first run 'checkOutlines' " \ "before running 'autohint'. Skipping." in msgs def test_hashmap_processed_no_autohint(tmpdir, caplog): path = "%s/dummy/hashmap_processed_no_autohint.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) assert not differ([path, out]) def test_hashmap_no_version(tmpdir, caplog): caplog.set_level(logging.INFO) path = "%s/dummy/hashmap_no_version.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) msgs = [r.getMessage() for r in caplog.records] assert "Updating hash map: was older version" in msgs assert not differ([path, out]) def test_hashmap_old_version(tmpdir, caplog): caplog.set_level(logging.INFO) path = "%s/dummy/hashmap_old_version.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) msgs = [r.getMessage() for r in caplog.records] assert "Updating hash map: was older version" in msgs assert not differ([path, out]) def test_hashmap_new_version(tmpdir, caplog): path = "%s/dummy/hashmap_new_version.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) with pytest.raises(FontParseError): hintFiles(options) def test_hashmap_advance(tmpdir): path = "%s/dummy/hashmap_advance.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) assert differ([path, out]) def test_hashmap_dflt_layer(tmpdir): path = "%s/dummy/hashmap_dflt_layer.ufo" % DATA_DIR rslt = "%s/dummy/hashmap_dflt_layer_hinted.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.writeToDefaultLayer = True hintFiles(options) assert differ([rslt, out]) def test_hashmap_dflt_layer_rehint(tmpdir): path = "%s/dummy/hashmap_dflt_layer.ufo" % DATA_DIR rslt = "%s/dummy/hashmap_dflt_layer_rehinted.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.writeToDefaultLayer = False hintFiles(options) options2 = Options(out, out) options2.writeToDefaultLayer = True hintFiles(options2) assert differ([rslt, out]) def test_hashmap_transform(tmpdir): path = "%s/dummy/hashmap_transform.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) assert differ([path, out]) def test_hashmap_unnormalized_floats(tmpdir): path = "%s/dummy/hashmap_unnormalized_floats.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) assert differ([path, out]) def test_decimals_ufo(tmpdir): path = "%s/dummy/decimals.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.round_coords = False hintFiles(options) assert differ([path, out]) def test_decimals_otf(tmpdir): otf = "%s/dummy/decimals.otf" % DATA_DIR out = str(tmpdir / basename(otf)) + ".out" options = Options(otf, out) options.round_coords = False hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) def test_layers(tmpdir): path = "%s/dummy/layers.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) options.allow_no_blues = True hintFiles(options) assert differ([path, out]) def test_big_glyph(tmpdir): path = "%s/dummy/big_glyph.ufo" % DATA_DIR out = str(tmpdir / basename(path)) + ".out" options = Options(path, out) hintFiles(options) assert differ([path, out]) psautohint-2.3.0/tests/integration/test_mmhint.py000066400000000000000000000077051401523215600223140ustar00rootroot00000000000000import glob from os.path import basename import pytest from fontTools.misc.xmlWriter import XMLWriter from fontTools.ttLib import TTFont from psautohint.autohint import ACOptions, ACHintError, hintFiles from .differ import main as differ from . import make_temp_copy, DATA_DIR class Options(ACOptions): def __init__(self, reference, inpaths, outpaths): super(Options, self).__init__() self.inputPaths = inpaths self.outputPaths = outpaths self.reference_font = reference self.hintAll = True self.verbose = False self.verbose = False @pytest.mark.parametrize("base", glob.glob("%s/*/Masters" % DATA_DIR)) def test_mmufo(base, tmpdir): paths = sorted(glob.glob(base + "/*.ufo")) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) + ".out" for p in inpaths] options = Options(reference, inpaths, outpaths) hintFiles(options) for inpath, outpath in zip(inpaths, outpaths): assert differ([inpath, outpath]) @pytest.mark.parametrize("base", glob.glob("%s/*/OTFMasters" % DATA_DIR)) def test_mmotf(base, tmpdir): paths = sorted(glob.glob(base + "/*.otf")) # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) + ".out" for p in inpaths] options = Options(reference, inpaths, outpaths) hintFiles(options) refs = [p + ".ref" for p in paths] for ref, out in zip(refs, [reference] + outpaths): for path in (ref, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(ref)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) @pytest.mark.parametrize("otf", glob.glob("%s/vf_tests/*.otf" % DATA_DIR)) def test_vfotf(otf, tmpdir): out = str(tmpdir / basename(otf)) + ".out" options = Options(None, [otf], [out]) options.allow_no_blues = True hintFiles(options) for path in (otf, out): font = TTFont(path) assert "CFF2" in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF2"].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(otf)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) def test_incompatible_masters(tmpdir): base = "%s/source-serif-pro/" % DATA_DIR paths = [base + "Light/font.ufo", base + "Black/font.ufo"] # the reference font is modified in-place, make a temp copy first reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / p) for p in inpaths] options = Options(reference, inpaths, outpaths) with pytest.raises(ACHintError): hintFiles(options) def test_sparse_mmotf(tmpdir): base = "%s/sparse_masters" % DATA_DIR paths = sorted(glob.glob(base + "/*.otf")) # the reference font is modified in-place, make a temp copy first # MasterSet_Kanji-w0.00.otf has to be the reference font. reference = make_temp_copy(tmpdir, paths[0]) inpaths = paths[1:] outpaths = [str(tmpdir / basename(p)) + ".out" for p in inpaths] options = Options(reference, inpaths, outpaths) options.allow_no_blues = True hintFiles(options) refs = [p + ".ref" for p in paths] for ref, out in zip(refs, [reference] + outpaths): for path in (ref, out): font = TTFont(path) assert "CFF " in font writer = XMLWriter(str(tmpdir / basename(path)) + ".xml") font["CFF "].toXML(writer, font) writer.close() assert differ([str(tmpdir / basename(ref)) + ".xml", str(tmpdir / basename(out)) + ".xml"]) psautohint-2.3.0/tests/integration/test_stemhist.py000066400000000000000000000024331401523215600226510ustar00rootroot00000000000000from os.path import basename import pytest from psautohint.autohint import ACOptions, hintFiles from .differ import main as differ from . import DATA_DIR class Options(ACOptions): def __init__(self, inpath, outpath, zones, stems, all_stems): super(Options, self).__init__() self.inputPaths = [inpath] self.outputPaths = [outpath] self.logOnly = True self.hintAll = True self.verbose = False self.report_zones = zones self.report_stems = stems self.report_all_stems = all_stems @pytest.mark.parametrize("zones,stems,all_stems", [ pytest.param(True, False, False, id="report_zones"), pytest.param(False, True, False, id="report_stems"), pytest.param(False, True, True, id="report_stems,all_stems"), ]) def test_otf(zones, stems, all_stems, tmpdir): path = "%s/dummy/font.otf" % DATA_DIR out = str(tmpdir / basename(path)) options = Options(path, out, zones, stems, all_stems) hintFiles(options) if zones: suffixes = ['.top.txt', '.bot.txt'] else: suffixes = ['.hstm.txt', '.vstm.txt'] for suffix in suffixes: exp_suffix = suffix if all_stems: exp_suffix = '.all' + suffix assert differ([path + exp_suffix, out + suffix, '-l', '1']) psautohint-2.3.0/tests/integration/test_ufo.py000066400000000000000000000004451401523215600216030ustar00rootroot00000000000000from psautohint.ufoFont import UFOFontData from . import DATA_DIR def test_incomplete_glyphorder(): path = "%s/dummy/incomplete_glyphorder.ufo" % DATA_DIR font = UFOFontData(path, False, True) assert len(font.getGlyphList()) == 95 assert "ampersand" in font.getGlyphList() psautohint-2.3.0/tests/unittests/000077500000000000000000000000001401523215600171155ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/__init__.py000066400000000000000000000002231401523215600212230ustar00rootroot00000000000000import os here = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) DATA_DIR = os.path.relpath(os.path.join(here, "data"), os.getcwd()) psautohint-2.3.0/tests/unittests/data/000077500000000000000000000000001401523215600200265ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/fontinfo/000077500000000000000000000000001401523215600216505ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/fontinfo/bad_base_token000066400000000000000000000001661401523215600245160ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE BaselineOvershoot -12 psautohint-2.3.0/tests/unittests/data/fontinfo/bad_begin000066400000000000000000000003521401523215600234650ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FOO BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 DominantV [86] DominantH [68] FlexOK false end FOO psautohint-2.3.0/tests/unittests/data/fontinfo/bad_end_fddict000066400000000000000000000004041401523215600244620ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FDDict UPPERCASE BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 DominantV [86] DominantH [68] FlexOK false end FDDict LOWERCASE psautohint-2.3.0/tests/unittests/data/fontinfo/bad_end_glyphset000066400000000000000000000007151401523215600250710ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin GlyphSet UPPERCASE ^([A-Z]|AE|OE|Germandbls|Eth|Thorn|Schwa|Eng|IJ)(acute|grave|circumflex|tilde|dieresis|macron|breve|ring|ringacute|caron|ogonek|dotbelow|hoi|stroke|linebelow|cedilla|dotaccent|croat|commaaccent|hook|brevebelow|bar|dotbelowmacron|slash|hungarumlaut|horn|cat|dot)?(acute|grave|hoi|tilde|dotbelow|macron|caron|.a)?$ end GlyphSet LOWERCASE psautohint-2.3.0/tests/unittests/data/fontinfo/bad_fddict_key000066400000000000000000000004131401523215600245040ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FDDict UPPERCASE BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 DominantV [86] DominantH [68] FlexOK false FOO 1 end FDDict UPPERCASE psautohint-2.3.0/tests/unittests/data/fontinfo/base_token_list000066400000000000000000000001501401523215600247340ustar00rootroot00000000000000IsItalicStyle [ false ] IsBoldStyle ( false ) IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE psautohint-2.3.0/tests/unittests/data/fontinfo/bluefuzz_fontname000066400000000000000000000012131401523215600253250ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FDDict UPPERCASE FontName Bar BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 DominantV [86] DominantH [68] FlexOK false BlueFuzz 10 end FDDict UPPERCASE begin GlyphSet UPPERCASE ^([A-Z]|AE|OE|Germandbls|Eth|Thorn|Schwa|Eng|IJ)(acute|grave|circumflex|tilde|dieresis|macron|breve|ring|ringacute|caron|ogonek|dotbelow|hoi|stroke|linebelow|cedilla|dotaccent|croat|commaaccent|hook|brevebelow|bar|dotbelowmacron|slash|hungarumlaut|horn|cat|dot)?(acute|grave|hoi|tilde|dotbelow|macron|caron|.a)?$ end GlyphSet UPPERCASE psautohint-2.3.0/tests/unittests/data/fontinfo/finalfont000066400000000000000000000004041401523215600235510ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FDDict FinalFont BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 DominantV [86] DominantH [68] FlexOK false end FDDict FinalFont psautohint-2.3.0/tests/unittests/data/fontinfo/no_dominant_h_v000066400000000000000000000003441401523215600247350ustar00rootroot00000000000000IsItalicStyle false IsBoldStyle false IsOS/2OBLIQUE false UseOldNameID4 false LicenseCode ADOBE begin FDDict UPPERCASE BaselineYCoord 0 BaselineOvershoot -12 CapHeight 656 CapOvershoot 12 FlexOK false end FDDict UPPERCASE psautohint-2.3.0/tests/unittests/data/hinted/000077500000000000000000000000001401523215600213015ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/000077500000000000000000000000001401523215600245045ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/.notdef.bez000066400000000000000000000000151401523215600265370ustar00rootroot00000000000000% .notdef ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/circle.bez000066400000000000000000000002361401523215600264500ustar00rootroot00000000000000% circle 21 -21 rb 500 -20 rb 60 500 ry sc 60 250 mt 60 112 172 0 310 0 ct 448 0 560 112 560 250 ct 560 388 448 500 310 500 ct 172 500 60 388 60 250 ct cp ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/circle2.bez000066400000000000000000000002261401523215600265310ustar00rootroot00000000000000% circle2 -250 500 rb 0 500 ry sc 0 0 mt 0 -138 112 -250 250 -250 ct 388 -250 500 -138 500 0 ct 500 138 388 250 250 250 ct 112 250 0 138 0 0 ct cp ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/fontinfo000066400000000000000000000002701401523215600262500ustar00rootroot00000000000000BaselineOvershoot -20 BaselineYCoord 0 BlueFuzz 1 CapHeight 500 CapOvershoot 0 DominantH [10] DominantV [10] FlexOK true FontName BasicShapes-Regular LanguageGroup 0 OrigEmSqUnits 1000psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/square.bez000066400000000000000000000001131401523215600265010ustar00rootroot00000000000000% square 0 500 rb 60 500 ry sc 560 500 mt 560 0 dt 60 0 dt 60 500 dt cp ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/square2.bez000066400000000000000000000001111401523215600265610ustar00rootroot00000000000000% square2 0 500 rb 0 500 ry sc 0 0 mt 0 500 dt 500 500 dt 500 0 dt cp ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.bez/triangle.bez000066400000000000000000000001031401523215600270050ustar00rootroot00000000000000% triangle 0 500 rb 60 500 ry sc 310 500 mt 560 0 dt 60 0 dt cp ed psautohint-2.3.0/tests/unittests/data/hinted/basic_shapes.otf000066400000000000000000000024341401523215600244420ustar00rootroot00000000000000OTTO CFF UzOS/2Wn^z`cmap &L$headwnv6hhea$hmtx x maxpPnameQ`post3p /#_< ׂ=+ץl0Zl<<0P1 PfEd 8Z  #% HW &j   F   &Copyright (c) 2018,BasicShapesRegularFontForge : BasicShapes : 30-7-2018Version 001.000BasicShapes-RegularCopyright (c) 2018,BasicShapesRegularFontForge : BasicShapes : 30-7-2018Version 001.000BasicShapes-Regular 2BasicShapes-Regular% !! (,#8Ccirclecircle2trianglesquaresquare2Copyright (c) 2018, BasicShapes?zvww l<<<<<psautohint-2.3.0/tests/unittests/data/unhinted/000077500000000000000000000000001401523215600216445ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/000077500000000000000000000000001401523215600250475ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/.notdef.bez000066400000000000000000000000151401523215600271020ustar00rootroot00000000000000% .notdef ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/circle.bez000066400000000000000000000001771401523215600270170ustar00rootroot00000000000000% circle sc 60 250 mt 60 112 172 0 310 0 ct 448 0 560 112 560 250 ct 560 388 448 500 310 500 ct 172 500 60 388 60 250 ct cp ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/circle2.bez000066400000000000000000000002011401523215600270650ustar00rootroot00000000000000% circle2 sc 0 0 mt 0 -138 112 -250 250 -250 ct 388 -250 500 -138 500 0 ct 500 138 388 250 250 250 ct 112 250 0 138 0 0 ct cp ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/fontinfo000066400000000000000000000002701401523215600266130ustar00rootroot00000000000000BaselineOvershoot -20 BaselineYCoord 0 BlueFuzz 1 CapHeight 500 CapOvershoot 0 DominantH [10] DominantV [10] FlexOK true FontName BasicShapes-Regular LanguageGroup 0 OrigEmSqUnits 1000psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/square.bez000066400000000000000000000000701401523215600270460ustar00rootroot00000000000000% square sc 560 500 mt 560 0 dt 60 0 dt 60 500 dt cp ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/square2.bez000066400000000000000000000000671401523215600271360ustar00rootroot00000000000000% square2 sc 0 0 mt 0 500 dt 500 500 dt 500 0 dt cp ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.bez/triangle.bez000066400000000000000000000000601401523215600273520ustar00rootroot00000000000000% triangle sc 310 500 mt 560 0 dt 60 0 dt cp ed psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.otf000066400000000000000000000023001401523215600247750ustar00rootroot00000000000000OTTO CFF FOS/2Wn^z`cmap &L$headwe6hhea$hmtx xmaxpPnameQ`post3p $4q-_< ׂ=+ץ0Zl<<0P1 PfEd 8Z  #% HW &j   F   &Copyright (c) 2018,BasicShapesRegularFontForge : BasicShapes : 30-7-2018Version 001.000BasicShapes-RegularCopyright (c) 2018,BasicShapesRegularFontForge : BasicShapes : 30-7-2018Version 001.000BasicShapes-Regular 2BasicShapes-Regular% !! (,#8Ccirclecircle2trianglesquaresquare2Copyright (c) 2018, BasicShapes(JXemw l<<<<<psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/000077500000000000000000000000001401523215600250605ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/fontinfo.plist000066400000000000000000000111241401523215600277560ustar00rootroot00000000000000 ascender 800 capHeight 500 copyright Copyright (c) 2018, descender -200 familyName BasicShapes italicAngle 0.0 openTypeHeadCreated 2018/07/28 15:32:59 openTypeHeadFlags 0 1 3 openTypeHeadLowestRecPPEM 8 openTypeHheaAscender 500 openTypeHheaCaretOffset 0 openTypeHheaCaretSlopeRise 1 openTypeHheaCaretSlopeRun 0 openTypeHheaDescender 0 openTypeHheaLineGap 90 openTypeNameUniqueID FontForge : BasicShapes : 30-7-2018 openTypeNameVersion Version 001.000 openTypeOS2CodePageRanges 0 openTypeOS2Panose 2 0 5 9 0 0 0 0 0 0 openTypeOS2Selection openTypeOS2StrikeoutPosition 258 openTypeOS2StrikeoutSize 49 openTypeOS2SubscriptXOffset 0 openTypeOS2SubscriptXSize 650 openTypeOS2SubscriptYOffset 140 openTypeOS2SubscriptYSize 700 openTypeOS2SuperscriptXOffset 0 openTypeOS2SuperscriptXSize 650 openTypeOS2SuperscriptYOffset 480 openTypeOS2SuperscriptYSize 700 openTypeOS2Type openTypeOS2TypoAscender 800 openTypeOS2TypoDescender -200 openTypeOS2TypoLineGap 90 openTypeOS2UnicodeRanges 0 openTypeOS2VendorID PfEd openTypeOS2WeightClass 400 openTypeOS2WidthClass 5 openTypeOS2WinAscent 500 openTypeOS2WinDescent 0 postscriptBlueShift 0 postscriptBlueValues -20 0 500 500 postscriptDefaultWidthX 620 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptFontName BasicShapes-Regular postscriptForceBold postscriptFullName BasicShapes postscriptIsFixedPitch postscriptNominalWidthX 0 postscriptOtherBlues postscriptStemSnapH 10 postscriptStemSnapV 10 postscriptUnderlinePosition -125 postscriptUnderlineThickness 50 postscriptWeightName Regular styleMapFamilyName BasicShapes styleMapStyleName regular styleName Regular unitsPerEm 1000 versionMajor 1 versionMinor 0 xHeight 0 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/000077500000000000000000000000001401523215600263665ustar00rootroot00000000000000psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/_notdef.glif000066400000000000000000000002041401523215600306430ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/circle.glif000066400000000000000000000011631401523215600304730ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/circle2.glif000066400000000000000000000011701401523215600305530ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/contents.plist000066400000000000000000000010241401523215600312750ustar00rootroot00000000000000 .notdef _notdef.glif circle circle.glif circle2 circle2.glif square square.glif square2 square2.glif triangle triangle.glif psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/square.glif000066400000000000000000000005061401523215600305320ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/square2.glif000066400000000000000000000005051401523215600306130ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/glyphs/triangle.glif000066400000000000000000000004361401523215600310410ustar00rootroot00000000000000 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/lib.plist000066400000000000000000000006631401523215600267100ustar00rootroot00000000000000 public.glyphOrder .notdef circle circle2 triangle square square2 psautohint-2.3.0/tests/unittests/data/unhinted/basic_shapes.ufo/metainfo.plist000066400000000000000000000004651401523215600277440ustar00rootroot00000000000000 creator org.robofab.ufoLib formatVersion 2 psautohint-2.3.0/tests/unittests/test_extension.py000066400000000000000000000062651401523215600225530ustar00rootroot00000000000000import pytest from psautohint import _psautohint INFO = b"FontName Foo" NAME = b"Foo" GLYPH = b"""% square 0 500 rb 60 500 ry sc 560 500 mt 560 0 dt 60 0 dt 60 500 dt cp ed """ def test_autohint_good_args(): _psautohint.autohint(INFO, GLYPH) def test_autohintmm_good_args(): _psautohint.autohintmm((GLYPH, GLYPH), (NAME, NAME)) @pytest.mark.parametrize("args", [ [], # no arguments [INFO], # 1 argument [INFO.decode('ascii'), GLYPH], # 1st is string not bytes [INFO, GLYPH.decode('ascii')], # 2nd is string not bytes [[INFO], GLYPH.decode('ascii')], # 1st is a list [INFO, [GLYPH.decode('ascii')]], # 2nd is a list ]) def test_autohint_bad_args(args): with pytest.raises(TypeError): _psautohint.autohint(*args) @pytest.mark.parametrize("args", [ [], # no arguments [(GLYPH, GLYPH)], # 1 argument [GLYPH, (NAME, NAME)], # 1st is not a tuple [(GLYPH, GLYPH), NAME], # 2nd is not a tuple [(GLYPH, GLYPH), tuple()], # 2nd is an empty tuple [(GLYPH, GLYPH), (NAME,)], # 2nd is shorter than 1st [(GLYPH,), (NAME,)], # 1st is one glyph ]) def test_autohintmm_bad_args(args): with pytest.raises(TypeError): _psautohint.autohintmm(*args) @pytest.mark.parametrize("args", [ [(GLYPH.decode('ascii'), GLYPH), (NAME, NAME)], # 1st should be bytes [(GLYPH, GLYPH), (NAME.decode('ascii'), NAME)], # 2nd should be bytes ]) def test_autohintmm_unicode(args): with pytest.raises(TypeError): _psautohint.autohintmm(*args) @pytest.mark.parametrize("glyph", [ b"% foo\ned", # ending comment with newline b"% foo\red", # ending comment with linefeed b"\t% foo\nsc\ted", # separating tokens with tab b"% foo\nsc ed", # separating tokens with space b"% foo\nsc\ned", # separating tokens with newline b"% foo\nsc\red", # separating tokens with linefeed b"% foo", # glyph name only b"% foo bar", # extra data after glyph name ]) def test_autohint_good_glyph(glyph): result = _psautohint.autohint(INFO, glyph) assert result == b"% foo\nsc\ned\n" @pytest.mark.parametrize("glyph", [ b"% foo\ncf", # unknown operator b"% foo\n" + 80 * b"f", # too long unknown operator b"% " + 65 * b"A", # too long glyph name b"% foo\n10 ", # number left on stack at end of glyph b"% foo\n0a 0 rm\ned", # bad number terminator b"% foo\nry", # stack underflow b"% foo\n$", # unexpected character ]) def test_autohint_bad_glyph(glyph): with pytest.raises(_psautohint.error): _psautohint.autohint(INFO, glyph) @pytest.mark.parametrize("glyphs", [ (b"cf", b"cf"), ]) def test_autohintmm_bad_glyphs(glyphs): with pytest.raises(_psautohint.error): _psautohint.autohintmm(glyphs, (NAME, NAME)) @pytest.mark.parametrize("info", [ b"HCounterChars [" + b" ".join(b"A" * i for i in range(16)) + b"]", b"VCounterChars [" + b" ".join(b"A" * i for i in range(16)) + b"]", ]) def test_autohint_too_many_counter_glyphs(info): _psautohint.autohint(info, GLYPH) psautohint-2.3.0/tests/unittests/test_fdtools.py000066400000000000000000000053621401523215600222060ustar00rootroot00000000000000import glob import pytest from psautohint.fdTools import (mergeFDDicts, parseFontInfoFile, FDDict, FontInfoParseError) from . import DATA_DIR def parse(path, glyphs=None): with open(path) as fp: data = fp.read() if glyphs is None: glyphs = [] fddict = FDDict() fddict.BlueFuzz = 0 return parseFontInfoFile([fddict], data, glyphs, 2000, -1000, "Foo") def test_finalfont(): path = "%s/fontinfo/finalfont" % DATA_DIR _, _, final_dict = parse(path) assert final_dict is not None def test_base_token_value_is_a_list(): path = "%s/fontinfo/base_token_list" % DATA_DIR _, font_dicts, _ = parse(path) assert len(font_dicts) > 0 def test_no_dominant_h_or_v(caplog): path = "%s/fontinfo/no_dominant_h_v" % DATA_DIR _, font_dicts, _ = parse(path) assert len(font_dicts) > 0 msgs = [r.getMessage() for r in caplog.records] assert "The FDDict 'UPPERCASE' in fontinfo has no DominantH value" in msgs assert "The FDDict 'UPPERCASE' in fontinfo has no DominantV value" in msgs def test_bluefuzz_and_fontname(): path = "%s/fontinfo/bluefuzz_fontname" % DATA_DIR _, font_dicts, _ = parse(path, ["A", "B", "C", "D"]) assert len(font_dicts) > 1 assert font_dicts[1].BlueFuzz == "10" assert font_dicts[1].FontName == "Bar" @pytest.mark.parametrize("path", glob.glob("%s/fontinfo/bad_*" % DATA_DIR)) def test_bad_fontinfo(path): with pytest.raises(FontInfoParseError): parse(path) @pytest.mark.parametrize("attributes", [ {"CapOvershoot": 5, "CapHeight": 5, "AscenderOvershoot": 0, "AscenderHeight": 8}, {"BaselineOvershoot": None}, {"BaselineOvershoot": 10}, {"CapOvershoot": 0}, {"AscenderHeight": -10, "AscenderOvershoot": 0}, {"AscenderHeight": 0, "AscenderOvershoot": -10}, {"Baseline5Overshoot": 10, "Baseline5": 0}, ]) def test_fddict_bad_zone(attributes): fddict = FDDict() fddict.BlueFuzz = 0 fddict.BaselineYCoord = 0 fddict.BaselineOvershoot = -10 for key in attributes: setattr(fddict, key, attributes[key]) with pytest.raises(FontInfoParseError): fddict.buildBlueLists() def test_merge_empty_fddicts(): mergeFDDicts([FDDict(), FDDict()], FDDict()) @pytest.mark.parametrize("stemdict", ["DominantH", "DominantV"]) def test_merge_fddicts_with_stemdicts(stemdict): fddict0 = FDDict() fddict1 = FDDict() fddict2 = FDDict() for i, fddict in enumerate([fddict1, fddict2]): fddict.BlueFuzz = 0 fddict.BaselineYCoord = 0 fddict.BaselineOvershoot = -10 setattr(fddict, stemdict, "[" + " ".join([str(i * 10), str(i * 20)]) + "]") fddict.buildBlueLists() mergeFDDicts([fddict1, fddict2], fddict0) psautohint-2.3.0/tests/unittests/test_hint.py000066400000000000000000000075311401523215600214760ustar00rootroot00000000000000import glob import os import pytest from psautohint.autohint import ACOptions, openFile from psautohint import hint_bez_glyph from . import DATA_DIR class BezFontData: def __init__(self, path): self._path = path self._info = None self._glyphs = {} class FontInfo: def __init__(self, info): self._info = info def getFontInfo(self): return self._info def getFontInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs): if self._info is None: with open(os.path.join(self._path, "fontinfo")) as fp: self._info = self.FontInfo(fp.read()) return self._info def convertToBez(self, glyphName, read_hints, doAll=False): if glyphName not in self._glyphs: with open(os.path.join(self._path, glyphName + ".bez")) as fp: self._glyphs[glyphName] = fp.read() return self._glyphs[glyphName] def getGlyphList(self): files = glob.glob(self._path + "/*.bez") files += glob.glob(self._path + "/.*.bez") return [os.path.splitext(os.path.basename(f))[0] for f in files] def open_font(path): if not path.endswith(".bez"): font = openFile(path, ACOptions()) else: font = BezFontData(path) return font def get_font_info(font, path): info = font.getFontInfo(False, False, [], []) # Sort to normalize the order. info = sorted(info.getFontInfo().split("\n")) return "\n".join(info) def normalize_glyph(glyph, name): """Dump Bez format normalizer.""" # strip comments hhints = [] vhints = [] path = [] for line in glyph.split("\n"): line = line.split("%")[0].strip() if not line: pass elif line.endswith("rb"): hhints.append(line) elif line.endswith("ry"): vhints.append(line) else: path.append(line) hhints.sort() vhints.sort() return "\n".join(["% " + name] + hhints + vhints + path) def get_glyph(font, name): glyph = font.convertToBez(name, True, True) return normalize_glyph(glyph, name) @pytest.mark.parametrize("unhinted,hinted", zip( glob.glob("%s/unhinted/*.[uo][ft][of]" % DATA_DIR), glob.glob("%s/hinted/*.[uo][ft][of]" % DATA_DIR), )) def test_bez(unhinted, hinted): unhinted_base = os.path.splitext(unhinted)[0] hinted_base = os.path.splitext(hinted)[0] bez_font = open_font(unhinted_base + ".bez") otf_font = open_font(unhinted_base + ".otf") ufo_font = open_font(unhinted_base + ".ufo") hinted_bez_font = open_font(hinted_base + ".bez") hinted_otf_font = open_font(hinted_base + ".otf") bez_info = get_font_info(bez_font, unhinted_base + ".bez") otf_info = get_font_info(otf_font, unhinted_base + ".otf") ufo_info = get_font_info(ufo_font, unhinted_base + ".ufo") assert otf_info == bez_info == ufo_info names = sorted(otf_font.getGlyphList()) assert all(sorted(f.getGlyphList()) == names for f in [bez_font, otf_font, ufo_font, hinted_bez_font, hinted_otf_font]) for name in names: if name == ".notdef": continue bez_glyph = get_glyph(bez_font, name) otf_glyph = get_glyph(otf_font, name) ufo_glyph = get_glyph(ufo_font, name) assert otf_glyph == bez_glyph == ufo_glyph hinted_bez_glyph = get_glyph(hinted_bez_font, name) hinted_otf_glyph = get_glyph(hinted_otf_font, name) assert hinted_otf_glyph == hinted_bez_glyph result = hint_bez_glyph(bez_info, bez_glyph) assert normalize_glyph(result, name) == hinted_bez_glyph psautohint-2.3.0/tox.ini000066400000000000000000000053531401523215600152320ustar00rootroot00000000000000[tox] envlist = py{36,37,38}-cov,coverage minversion = 2.9.1 skip_missing_interpreters = true [pytest] norecursedirs = data testpaths = tests addopts = -v -r a --exitfirst [testenv] description = run the tests with pytest under {basepython} setenv = COVERAGE_FILE={toxinidir}/.coverage.{envname} deps = -rrequirements.txt tx: afdko extras = testing commands = nocov: pytest -n {env:PYTEST_NUM_PROCESSES:auto} {posargs} cov: pytest --cov="{envsitepackagesdir}/psautohint" --cov-config={toxinidir}/.coveragerc -n {env:PYTEST_NUM_PROCESSES:auto} {posargs} [testenv:coverage] description = run locally after tests to combine coverage data and create reports; generates a diff coverage against origin/master (or DIFF_AGAINST env var) deps = coverage >= 5.0.1, < 5.1.0 diff_cover skip_install = true setenv = COVERAGE_FILE={toxinidir}/.coverage passenv = DIFF_AGAINST changedir = {toxinidir} commands = coverage erase coverage combine coverage report coverage xml -o {toxworkdir}/coverage.xml coverage html diff-cover --compare-branch {env:DIFF_AGAINST:origin/master} {toxworkdir}/coverage.xml [testenv:coverage-c] description = generate coverage for C library deps = -rrequirements.txt pytest >= 5.3.0, < 6.0.0 pytest-xdist < 2.0.0 skip_install = true setenv = CFLAGS = --coverage BUILD_DIR = {envdir}/build LIB_DIR = {env:BUILD_DIR}/lib PYTHONPATH = {env:PYTHONPATH:}{:}{env:LIB_DIR} passenv = TOXENV CI CODECOV_* changedir = {toxinidir} commands = python setup.py build --build-base {env:BUILD_DIR} --build-platlib {env:LIB_DIR} pytest -n {env:PYTEST_NUM_PROCESSES:auto} {posargs} [testenv:codecov] description = upload Python coverage data to codecov (only run on CI) deps = {[testenv:coverage]deps} codecov skip_install = true setenv = {[testenv:coverage]setenv} passenv = TOXENV CI CODECOV_* changedir = {toxinidir} commands = coverage combine codecov --env TOXENV [testenv:coverage-codecov-c] description = generate C coverage data and upload to codecov (only run on CI) deps = {[testenv:coverage-c]deps} codecov skip_install = true setenv = {[testenv:coverage-c]setenv} passenv = {[testenv:coverage-c]passenv} changedir = {toxinidir} commands = {[testenv:coverage-c]commands} codecov --env {envname} [testenv:sdist] description = build sdist to be uploaded to PyPI skip_install = true deps = coverage >= 5.0.1, < 5.1.0 setuptools >= 36.4.0 wheel >= 0.31.0 changedir = {toxinidir} commands = python -c 'import shutil; shutil.rmtree("dist", ignore_errors=True)' python setup.py sdist --dist-dir dist [testenv:wheel] description = build wheel package for upload to PyPI skip_install = true deps = {[testenv:sdist]deps} changedir = {toxinidir} commands = python setup.py clean -a bdist_wheel psautohint-2.3.0/util/000077500000000000000000000000001401523215600146665ustar00rootroot00000000000000psautohint-2.3.0/util/dump-font.py000066400000000000000000000037511401523215600171570ustar00rootroot00000000000000import argparse import os import sys from psautohint.autohint import ACOptions, openFile def open_font(path): font = openFile(path, ACOptions()) return font def get_font_info(font): info = font.getFontInfo(False, False, [], []) return info.getFontInfo() def split_at_comma(comma_str): return [item.strip() for item in comma_str.split(',')] def main(): parser = argparse.ArgumentParser(description="Dump font in bez format.") parser.add_argument("path", metavar="FILE", help="input font to process") parser.add_argument("-o", "--out", metavar="FILE", help="output directory, default is inputh path " "+ '.bez'") parser.add_argument("-n", "--no-fontinfo", action="store_true", help="don't output fontinfo") parser.add_argument("-m", "--no-metainfo", action="store_true", help="don't output metainfo") parser.add_argument("-g", "--glyphs", metavar="NAMES", type=split_at_comma, help="comma-separated list of glyphs to dump, " "all glyphs will be dumped by default") args = parser.parse_args() path = args.path outpath = args.out if not outpath: outpath = os.path.splitext(os.path.abspath(path))[0] + ".bez" os.makedirs(outpath) print("Writing output to:", outpath) font = open_font(path) names = args.glyphs if not names: names = font.getGlyphList() if not args.no_fontinfo: info = get_font_info(font) with open(os.path.join(outpath, "fontinfo"), "x") as fp: fp.write(info) glyphs = [font.convertToBez(n, True, True) for n in names] for name, glyph in zip(names, glyphs): if glyph is None: glyph = "" glyph_path = name.lower() + ".bez" with open(os.path.join(outpath, glyph_path), "x") as fp: fp.write(glyph) return 0 if __name__ == "__main__": sys.exit(main()) psautohint-2.3.0/util/launch-asan.sh000077500000000000000000000003061401523215600174160ustar00rootroot00000000000000#!/bin/bash DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib $(which python3)