pax_global_header00006660000000000000000000000064132401570630014513gustar00rootroot0000000000000052 comment=3d20521dc7ea826d168e8bc11852a47cdc9f85ca psautohint-1.1.0/000077500000000000000000000000001324015706300137105ustar00rootroot00000000000000psautohint-1.1.0/.appveyor.yml000066400000000000000000000002471324015706300163610ustar00rootroot00000000000000install: - git submodule update --init - set PATH=%PATH%;C:\cygwin\bin - pip install -r requirements.txt build_script: - make test_script: - make -j check psautohint-1.1.0/.clang-format000066400000000000000000000053541324015706300162720ustar00rootroot00000000000000Language: 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-1.1.0/.gitignore000066400000000000000000000001621324015706300156770ustar00rootroot00000000000000__pycache__/ *.py[cod] *.so *.o *.a *.swp autohintexe build *.egg *.egg-info *.eggs MANIFEST build dist .DS_Store psautohint-1.1.0/.gitmodules000066400000000000000000000001771324015706300160720ustar00rootroot00000000000000[submodule "tests/data"] path = tests/data url = https://github.com/adobe-type-tools/psautohint-testdata.git ignore = dirty psautohint-1.1.0/.travis.yml000066400000000000000000000012461324015706300160240ustar00rootroot00000000000000dist: trusty sudo: false language: python matrix: include: - python: 2.7 - python: 3.6 # The 'osx' environment uses the python2.7 by default. # TODO: maybe also tests python3 on macOS - language: generic os: osx install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then curl -O https://bootstrap.pypa.io/get-pip.py; python get-pip.py --user; python -m pip install --user virtualenv; python -m virtualenv .venv/; source .venv/bin/activate; fi - pip install -r requirements.txt script: - make -j4 check - make dist - unzip dist/psautohint-$(python setup.py -V).zip - make -C psautohint-$(python setup.py -V) psautohint-1.1.0/COPYING000066400000000000000000000011471324015706300147460ustar00rootroot00000000000000Copyright 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-1.1.0/LICENSE000066400000000000000000000261361324015706300147250ustar00rootroot00000000000000 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-1.1.0/MANIFEST.in000066400000000000000000000002011324015706300154370ustar00rootroot00000000000000include README.md COPYING LICENSE include Makefile source/Makefile include requirements.txt recursive-include libpsautohint *.h psautohint-1.1.0/Makefile000066400000000000000000000020231324015706300153450ustar00rootroot00000000000000.PHONY: build install check dist clean format ROOT_DIR = . SRC_DIR = $(ROOT_DIR)/libpsautohint TST_DIR = $(ROOT_DIR)/tests PYTHON ?= python # --user does not work from inside a virtual environment # PIP_OPTIONS ?= --user PIP_OPTIONS ?= # Get the platform/version-specific build/lib.* folder define GET_BUILD_DIR import os import sys import sysconfig root = sys.argv[1] plat_lib_name = "lib.{platform}-{version[0]}.{version[1]}".format( platform=sysconfig.get_platform(), version=sys.version_info) build_dir = os.path.abspath(os.path.join(root, "build", plat_lib_name)) print(build_dir) endef BUILD_DIR := $(shell $(PYTHON) -c '$(GET_BUILD_DIR)' $(ROOT_DIR)) build: $(PYTHON) setup.py build install: $(PYTHON) -m pip install -r requirements.txt -v $(PIP_OPTIONS) . autohintexe: make -C $(SRC_DIR) dist: clean $(PYTHON) setup.py sdist bdist_wheel clean: $(PYTHON) setup.py clean --all make -C $(SRC_DIR) clean check: build make -C $(TST_DIR) PYTHONPATH="$(BUILD_DIR)" format: clang-format -i `find $(SRC_DIR) -name '*.c'` psautohint-1.1.0/README.md000066400000000000000000000020111324015706300151610ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/khaledhosny/psautohint.svg?branch=master)](https://travis-ci.org/khaledhosny/psautohint) [![Build status](https://ci.appveyor.com/api/projects/status/0xy2iyc6wsl5ag4e?svg=true)](https://ci.appveyor.com/project/khaledhosny/psautohint) # PSAutoHint A standalone version of [AFDKO](https://github.com/adobe-type-tools/afdko)'s autohinter. Still a work in progress. Use at your own risk! ## 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: make build To install the C extension and the helper scripts globally: make install Alternatively to install them for the current user: make PIP_OPTIONS=--user install The authinter can be used by running: psautohint The old `autohintexe` binary can also be built by running: make autohintexe ## Testing We have a very primitive test suite that can be run with: make check psautohint-1.1.0/doc/000077500000000000000000000000001324015706300144555ustar00rootroot00000000000000psautohint-1.1.0/doc/AC.md000066400000000000000000000177631324015706300153000ustar00rootroot00000000000000# 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-1.1.0/doc/bezfileformat.txt000066400000000000000000000031051324015706300200460ustar00rootroot00000000000000PROPOSAL FOR BEZIER FILE FORMATS (revised) The Standard Bezier format is an file consisting of the following operators: (width) -- character width (* this may be added at a later date) sc -- start of character outline rmt -- relative move (same as T1 rmoveto operator) vmt -- relative move (same as T1 vmoveto operator) hmt -- relative move (same as T1 hmoveto operator) rdt -- relative line/draw (same as T1 rlineto operator) vdt -- relative line/draw (same as T1 vlineto operator) hdt -- relative line/draw (same as T1 hlineto operator) rct -- optimized relative curve (same as T1 rrcurveto operator) vhct -- optimized relative curve (same as T1 vhcurveto operator) hvct -- optimized relative curve (same as T1 hvcurveto operator) cp -- end of sub-path (same as T1 closepath operator) ed -- end of character outline (same as T1 endchar operator) preflx1 -- marks start of flex op, expressed as T1 path ops preflx2 -- marks end of flex op flx -- flex op beginsubr snc -- marks start of new block of hint values endsubr encnewcolors -- marks end of hint values rb -- horizontal stem hint ry -- vertical stem hint rm -- vertical counter hints rv -- horizontal counter hints div -- same as PostScript div operator Notes: The preflx1/preflx2 sequence provides the same info as the flex sequence; the difference is that the preflx1/preflx2 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. psautohint-1.1.0/libpsautohint/000077500000000000000000000000001324015706300165755ustar00rootroot00000000000000psautohint-1.1.0/libpsautohint/.lvimrc000066400000000000000000000000771324015706300200760ustar00rootroot00000000000000set expandtab set shiftwidth=4 set softtabstop=4 set tabstop=4 psautohint-1.1.0/libpsautohint/Makefile000066400000000000000000000034731324015706300202440ustar00rootroot00000000000000NULL = # Configuration OBJ_DIR = . SRC_DIR = . # Program PRG_OBJS = $(OBJ_DIR)/autohintexe.o PRG_TARGET = $(OBJ_DIR)/autohintexe$(EXE) PRG_LIBS = -L. -lpsautohint # Library LIB_TARGET = $(OBJ_DIR)/libpsautohint.a CFLAGS = \ -I$(SRC_DIR)/include \ -I$(SRC_DIR)/src \ -Wall \ -Wextra \ -Wpedantic \ -Wdeclaration-after-statement \ -Wstrict-prototypes \ -Wunused-macros \ -Wmissing-prototypes \ -O3 \ -fno-tree-vectorize \ $(NULL) ifeq ($(firstword $(shell $(CC) --version)),clang) CFLAGS += \ -Wunreachable-code-break \ -Wmissing-variable-declarations \ $(NULL) endif ifeq ($(OS),Windows_NT) EXE = .exe else PRG_LIBS += -lm endif LIB_OBJS = \ $(OBJ_DIR)/src/acfixed.o \ $(OBJ_DIR)/src/ac.o \ $(OBJ_DIR)/src/auto.o \ $(OBJ_DIR)/src/bbox.o \ $(OBJ_DIR)/src/charpathpriv.o \ $(OBJ_DIR)/src/charprop.o \ $(OBJ_DIR)/src/check.o \ $(OBJ_DIR)/src/control.o \ $(OBJ_DIR)/src/eval.o \ $(OBJ_DIR)/src/fix.o \ $(OBJ_DIR)/src/flat.o \ $(OBJ_DIR)/src/fontinfo.o \ $(OBJ_DIR)/src/gen.o \ $(OBJ_DIR)/src/head.o \ $(OBJ_DIR)/src/logging.o \ $(OBJ_DIR)/src/memory.o \ $(OBJ_DIR)/src/merge.o \ $(OBJ_DIR)/src/misc.o \ $(OBJ_DIR)/src/pick.o \ $(OBJ_DIR)/src/psautohint.o \ $(OBJ_DIR)/src/read.o \ $(OBJ_DIR)/src/report.o \ $(OBJ_DIR)/src/shuffle.o \ $(OBJ_DIR)/src/stemreport.o \ $(OBJ_DIR)/src/write.o \ $(NULL) default: $(PRG_TARGET) clean: rm -f $(PRG_OBJS) rm -f $(LIB_OBJS) rm -f $(PRG_TARGET) rm -f $(LIB_TARGET) COMPILE = $(if $(filter $V,1),,@echo " CC $< ";)$(CC) LINK = $(if $(filter $V,1),,@echo " LD $@ ";)$(CC) ARCHIVE = $(if $(filter $V,1),,@echo " AR $@ ";)$(AR) # Program $(PRG_TARGET): $(PRG_OBJS) $(LIB_TARGET) $(LINK) $(CFLAGS) -o $@ $(PRG_OBJS) $(PRG_LIBS) # Library $(LIB_TARGET): $(LIB_OBJS) $(ARCHIVE) -rs $@ $? $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c $(COMPILE) $(CFLAGS) -c $< -o $@ psautohint-1.1.0/libpsautohint/autohintexe.c000066400000000000000000000330611324015706300213010ustar00rootroot00000000000000/* * 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.65240"; static const char* reportExt = ".rpt"; static const char* dfltExt = ".new"; static char* bezName = NULL; static char* fileSuffix = NULL; static FILE* reportFile = NULL; static bool verbose = true; /* if true don't number of characters processed. */ static void openReportFile(char* name, char* fSuffix); 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 coloring\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(int top, int bottom, char* glyphName) { if (reportFile) fprintf(reportFile, "charZone %s top %f bottom %f\n", glyphName, top / 256.0, bottom / 256.0); } static void stemZoneCB(int top, int bottom, char* glyphName) { if (reportFile) fprintf(reportFile, "stemZone %s top %f bottom %f\n", glyphName, top / 256.0, bottom / 256.0); } static void hstemCB(int top, int bottom, char* glyphName) { if (reportFile) fprintf(reportFile, "HStem %s top %f bottom %f\n", glyphName, top / 256.0, bottom / 256.0); } static void vstemCB(int right, int left, char* glyphName) { if (reportFile) fprintf(reportFile, "VStem %s right %f left %f\n", glyphName, right / 256.0, left / 256.0); } static void reportCB(char* msg) { fprintf(stdout, "%s", msg); } static void reportRetry(void) { if (reportFile != NULL) { fclose(reportFile); openReportFile(bezName, fileSuffix); } } static char* getFileData(char* name) { char* data; struct stat filestat; if ((stat(name, &filestat)) < 0) { fprintf(stdout, "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(stdout, "Error. File '%s' has zero size.\n", name); exit(AC_FatalError); } data = malloc(filestat.st_size + 1); if (data == NULL) { fprintf(stdout, "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(stdout, "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, char* fSuffix) { FILE* fp; size_t nameSize = 1 + strlen(name); char* savedName; int usedNewName = 0; if ((fSuffix != NULL) && (fSuffix[0] != '\0')) { nameSize += strlen(fSuffix); savedName = malloc(nameSize); savedName[0] = '\0'; strcat(savedName, name); strcat(savedName, fSuffix); usedNewName = 1; } else savedName = malloc(nameSize); fp = fopen(savedName, "w"); fwrite(output, 1, strlen(output), fp); fclose(fp); if (usedNewName) free(savedName); } static void openReportFile(char* name, char* fSuffix) { size_t nameSize = 1 + strlen(name); char* savedName; int usedNewName = 0; if ((fSuffix != NULL) && (fSuffix[0] != '\0')) { nameSize += strlen(fSuffix); savedName = malloc(nameSize); savedName[0] = '\0'; strcat(savedName, name); strcat(savedName, fSuffix); usedNewName = 1; } else savedName = malloc(nameSize); reportFile = fopen(savedName, "w"); if (usedNewName) free(savedName); } static void closeReportFile(void) { if (reportFile != NULL) fclose(reportFile); } 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, debug, badParam, allStems; bool argumentIsBezData = false; bool report = 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. */ char* current_arg; int16_t total_files = 0; int result, argi; badParam = false; debug = false; allStems = false; allowEdit = allowHintSub = roundCoords = true; fileSuffix = (char*)dfltExt; /* read in options */ argi = 0; while (++argi < argc) { 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(stdout, "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(stdout, "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(stdout, "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(stdout, "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(stdout, "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(stdout, "Error. Illegal command line. \"-s\" " "option must be followed by a string, " "and the string must not begin with " "'-'.\n"); exit(1); } 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 = (char*)reportExt; switch (current_arg[2]) { case 'a': AC_SetReportRetryCB(reportRetry); AC_SetReportZonesCB(charZoneCB, stemZoneCB); report = true; break; case 's': AC_SetReportRetryCB(reportRetry); AC_SetReportStemsCB(hstemCB, vstemCB, allStems); report = true; break; default: fprintf(stdout, "Error. %s is an invalid parameter.\n", current_arg); badParam = true; break; } break; case 'v': printVersions(); exit(0); default: fprintf(stdout, "Error. %s is an invalid parameter.\n", current_arg); badParam = true; break; } } if (firstFileNameIndex == -1) { fprintf(stdout, "Error. Illegal command line. Must provide bez file name.\n"); badParam = true; } if (fontInfoFileName == NULL) { fprintf( stdout, "Error. Illegal command line. Must provide font info file name.\n"); badParam = true; } if (badParam) exit(AC_InvalidParameterError); AC_SetReportCB(reportCB, verbose); argi = firstFileNameIndex - 1; while (++argi < argc) { char* bezdata; char* output; size_t outputsize = 0; bezName = argv[argi]; if (!argumentIsBezData) { bezdata = getFileData(bezName); } else { bezdata = bezName; } outputsize = 4 * strlen(bezdata); output = malloc(outputsize); if (!argumentIsBezData && report) { openReportFile(bezName, fileSuffix); } result = AutoColorString(bezdata, fontinfo, output, &outputsize, allowEdit, allowHintSub, roundCoords, debug); if (result == AC_DestBuffOfloError) { if (reportFile != NULL) { closeReportFile(); if (!argumentIsBezData && report) { openReportFile(bezName, fileSuffix); } } free(output); output = malloc(outputsize); /* printf("NOTE: trying again. Input size %d output size %d.\n", * strlen(bezdata), outputsize); */ AC_SetReportCB(reportCB, false); result = AutoColorString(bezdata, fontinfo, output, &outputsize, allowEdit, allowHintSub, roundCoords, debug); AC_SetReportCB(reportCB, verbose); } if (reportFile != NULL) { closeReportFile(); } else { if ((outputsize != 0) && (result == AC_Success)) { if (!argumentIsBezData) { writeFileData(bezName, output, fileSuffix); } else { printf("%s", output); } } } free(output); if (result != AC_Success) exit(result); } return 0; } /* end of main */ psautohint-1.1.0/libpsautohint/include/000077500000000000000000000000001324015706300202205ustar00rootroot00000000000000psautohint-1.1.0/libpsautohint/include/psautohint.h000066400000000000000000000111421324015706300225660ustar00rootroot00000000000000/* * 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) #else #if __GNUC__ && __MACH__ #define ACLIB_API __attribute__((visibility("default"))) #endif #endif #else #define ACLIB_API #endif enum { AC_Success, AC_FontinfoParseFail, AC_FatalError, AC_MemoryError, AC_UnknownError, AC_DestBuffOfloError, AC_InvalidParameterError }; /* * 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 and verbose is set to true, then the AC lib will write * (many!) text status messages to this file. * * If verbose is set false, then only error messages are written. */ typedef void (*AC_REPORTFUNCPTR)(char* msg); ACLIB_API void AC_SetReportCB(AC_REPORTFUNCPTR reportCB, int verbose); /* * 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. * * 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)(int top, int bottom, char* glyphName); ACLIB_API void AC_SetReportStemsCB(AC_REPORTSTEMPTR hstemCB, AC_REPORTSTEMPTR vstemCB, unsigned int allStems); /* * Function: AC_SetReportZonesCB * * If this is called , then the AC lib will write all the alignment zones it * encounters. * * 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)(int top, int bottom, char* glyphName); ACLIB_API void AC_SetReportZonesCB(AC_REPORTZONEPTR charCB, AC_REPORTZONEPTR stemCB); /* * 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); ACLIB_API void AC_SetReportRetryCB(AC_RETRYPTR retryCB); /* * Function: AutoColorString * * 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, an error will be returned and length * will be set to the desired size. */ ACLIB_API int AutoColorString(const char* srcbezdata, const char* fontinfo, char* dstbezdata, size_t* length, int allowEdit, int allowHintSub, int roundCoords, int debug); /* * 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-1.1.0/libpsautohint/src/000077500000000000000000000000001324015706300173645ustar00rootroot00000000000000psautohint-1.1.0/libpsautohint/src/ac.c000066400000000000000000000137001324015706300201140ustar00rootroot00000000000000/* * 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 */ PPathElt gPathStart, gPathEnd; bool gYgoesUp; bool gUseV, gUseH, gAutoVFix, gAutoHFix, gAutoLinearCurveFix, gEditChar; bool gAutoExtraDebug, gDebugColorPath, gDebug, gLogging; bool gShowVs, gShowHs, gListClrInfo; bool gReportErrors, gHasFlex, gFlexOK, gFlexStrict, gShowClrInfo, gBandError; Fixed gHBigDist, gVBigDist, gInitBigDist, gMinDist, gGhostWidth, gGhostLength, gBendLength, gBandMargin, gMaxFlare, gMaxBendMerge, gMaxMerge, gMinColorElementLength, 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; PClrVal gVColoring, gHColoring, gVPrimary, gHPrimary, gValList; PClrSeg gSegLists[4]; Fixed gVStems[MAXSTEMS], gHStems[MAXSTEMS]; int32_t gNumVStems, gNumHStems; Fixed gTopBands[MAXBLUES], gBotBands[MAXBLUES], gSerifs[MAXSERIFS]; PClrPoint gPointList, *gPtLstArray; int32_t gPtLstIndex, gNumPtLsts, gMaxPtLsts; bool gWriteColoredBez = true; Fixed gBlueFuzz; bool gDoAligns = false, gDoStems = false; bool gIdInFile; bool gRoundToInt; static int maxStemDist = MAXSTEMDIST; AC_REPORTFUNCPTR gLibReportCB = NULL; AC_REPORTFUNCPTR gLibErrorReportCB = NULL; /* 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 gAddCharExtremesCB = NULL; AC_REPORTZONEPTR gAddStemExtremesCB = NULL; AC_RETRYPTR gReportRetryCB = NULL; #define VMSIZE (1000000) static unsigned char *vmfree, *vmlast, vm[VMSIZE]; /* sub allocator */ unsigned char* 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 in glyph: %s.\n", gGlyphName); } return s; } void InitData(const ACFontInfo* fontinfo, int32_t reason) { char* s; float tmp, origEmSquare; switch (reason) { case STARTUP: gDebug = false; gDMin = 50; gDelta = 0; gYgoesUp = (dtfmy(FixOne) > 0) ? true : false; 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 coloring */ gBandMargin = PSDist(30); gMaxFlare = PSDist(10); gMaxBendMerge = PSDist(6); gMaxMerge = PSDist(2); /* must be < 3 for Cushing-BookItalic z */ gMinColorElementLength = PSDist(12); gFlexCand = PSDist(4); gSCurveTan = 25; gMaxVal = 8000000.0; gMinVal = 1.0 / (float)(FixOne); gAutoHFix = gAutoVFix = false; gEditChar = true; gRoundToInt = true; /* Default is to change a curve with collinear points into a line. */ gAutoLinearCurveFix = true; gFlexOK = false; gFlexStrict = true; gAutoExtraDebug = gDebug; gLogging = gDebug; gDebugColorPath = false; gShowClrInfo = gDebug; gShowHs = gShowVs = gDebug; gListClrInfo = gDebug; if (gScalingHints) { s = GetFontInfo(fontinfo, "OrigEmSqUnits", MANDATORY); origEmSquare = strtod(s, NULL); gBlueFuzz = (Fixed)(origEmSquare / 2000.0); /* .5 pixel */ } else { gBlueFuzz = DEFAULTBLUEFUZZ; } /* fall through */ case RESTART: memset((void*)vm, 0x0, VMSIZE); vmfree = vm; vmlast = vm + VMSIZE; /* ?? Does this cause a leak ?? */ gPointList = NULL; gMaxPtLsts = 5; gPtLstArray = (PClrPoint*)Alloc(gMaxPtLsts * sizeof(PClrPoint)); gPtLstIndex = 0; gPtLstArray[0] = NULL; gNumPtLsts = 1; /* if (glyphName != NULL && glyphName[0] == 'g') showClrInfo = showHs = showVs = listClrInfo = true; */ } } /* Returns whether coloring was successful. */ bool AutoColor(const ACFontInfo* fontinfo, const char* srcbezdata, bool fixStems, bool debug, bool extracolor, bool changeChar, bool roundCoords) { InitAll(fontinfo, STARTUP); if (!ReadFontInfo(fontinfo)) return false; gEditChar = changeChar; gRoundToInt = roundCoords; gAutoLinearCurveFix = gEditChar; if (gEditChar && fixStems) gAutoVFix = gAutoHFix = fixStems; if (debug) gDebug = gShowClrInfo = gShowHs = gShowVs = gListClrInfo = true; return AutoColorGlyph(fontinfo, srcbezdata, extracolor); } #if defined(_MSC_VER) && _MSC_VER < 1800 float roundf(float x) { return (float)((x < 0) ? (ceil((x)-0.5)) : (floor((x) + 0.5))); } #endif psautohint-1.1.0/libpsautohint/src/ac.h000066400000000000000000000373431324015706300201320ustar00rootroot00000000000000/* * 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 */ /* number of default entries in counter color character list. */ #define COUNTERDEFAULTENTRIES 4 #define COUNTERLISTSIZE 64 /* values for ClrSeg.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 */ /* character point coordinates */ typedef struct { int32_t x, y; } Cd, *CdPtr; typedef struct { int16_t limit; Fixed feps; void (*report)(Cd); Cd ll, ur; Fixed llx, lly; } FltnRec, *PFltnRec; typedef struct _clrseg { struct _clrseg *sNxt; /* points to next ClrSeg 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 _clrval *sLnk; /* points to the best ClrVal that uses this ClrSeg */ /* set by FindBestValForSegs in pick.c */ struct _pthelt *sElt; /* points to the path element that generated this ClrSeg */ /* set by AddSegment in gen.c */ int16_t sType; /* tells what type of segment this is: sLINE sBEND sCURVE or sGHOST */ } ClrSeg, *PClrSeg; typedef struct _seglnk { PClrSeg seg; } SegLnk, *PSegLnk; typedef struct _seglnklst { struct _seglnklst *next; PSegLnk lnk; } SegLnkLst; typedef SegLnkLst *PSegLnkLst; #if 0 typedef struct _clrrep { Fixed vVal, vSpc, vLoc1, vLoc2; struct _clrval *vBst; } ClrRep, *PClrRep; typedef struct _clrval { struct _clrval *vNxt; Fixed vVal, vSpc, initVal; Fixed vLoc1, vLoc2; /* vBot=vLoc1, vTop=vLoc2, vLft=vLoc1, vRght=vLoc2 */ int16_t vGhst:8; int16_t pruned:8; PClrSeg vSeg1, vSeg2; struct _clrval *vBst; PClrRep vRep; } ClrVal, *PClrVal; #else typedef struct _clrval { struct _clrval *vNxt; /* points to next ClrVal in list */ Fixed vVal, vSpc, initVal; /* vVal is value given in eval.c */ /* vSpc is nonzero for "special" ClrVals */ /* 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 ClrVal, vBot=vLoc1 and vTop=vLoc2 */ /* for vertical ClrVal, vLft=vLoc1 and vRght=vLoc2 */ unsigned int vGhst:1; /* true iff one of the ClrSegs 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; PClrSeg vSeg1, vSeg2; /* vSeg1 points to the left ClrSeg in a vertical, bottom in a horizontal */ /* vSeg2 points to the right ClrSeg in a vertical, top in a horizontal */ struct _clrval *vBst; /* points to another ClrVal if this one has been merged or replaced */ } ClrVal, *PClrVal; #endif typedef struct _pthelt { struct _pthelt *prev, *next, *conflict; int16_t type; PSegLnkLst Hs, Vs; bool Hcopy:1, Vcopy:1, isFlex:1, yFlex:1, newCP:1, sol:1, eol:1; int unused:9; int16_t count, newcolors; Fixed x, y, x1, y1, x2, y2, x3, y3; } PathElt, *PPathElt; typedef struct _clrpnt { struct _clrpnt *next; Fixed x0, y0, x1, y1; /* for vstem, only interested in x0 and x1 */ /* for hstem, only interested in y0 and y1 */ PPathElt p0, p1; /* p0 is source of x0,y0; p1 is source of x1,y1 */ char c; /* tells what kind of coloring: 'b' 'y' 'm' or 'v' */ bool done; } ClrPoint, *PClrPoint; typedef struct { char *key, *value; } FFEntry; typedef struct { FFEntry *entries; /* font information entries */ size_t length; /* number of the entries */ } ACFontInfo; typedef struct { char* data; /* character data held in the buffer */ size_t length; /* actual length of the data */ size_t capacity; /* allocated memory size */ } ACBuffer; /* global data */ extern ACBuffer* gBezOutput; extern PPathElt gPathStart, gPathEnd; extern bool gYgoesUp; extern bool gUseV, gUseH, gAutoVFix, gAutoHFix, gAutoLinearCurveFix; extern bool gAutoExtraDebug, gDebugColorPath, gDebug, gLogging; extern bool gEditChar; /* whether character can be modified when adding hints */ extern bool gShowHs, gShowVs, gBandError, gListClrInfo; extern bool gReportErrors, gHasFlex, gFlexOK, gFlexStrict, gShowClrInfo; extern Fixed gHBigDist, gVBigDist, gInitBigDist, gMinDist, gGhostWidth, gGhostLength, gBendLength, gBandMargin, gMaxFlare, gMaxBendMerge, gMaxMerge, gMinColorElementLength, 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 PClrVal gVColoring, gHColoring, gVPrimary, gHPrimary, gValList; extern PClrSeg gSegLists[4]; /* left, right, top, bot */ extern PClrPoint gPointList, *gPtLstArray; extern int32_t gPtLstIndex, gNumPtLsts, gMaxPtLsts; extern bool gScalingHints; /* global callbacks */ /* global log function which is supplied by the following */ extern AC_REPORTFUNCPTR gLibReportCB; /* global error log function which is supplied by the following */ extern AC_REPORTFUNCPTR gLibErrorReportCB; /* 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 gAddCharExtremesCB; extern AC_REPORTZONEPTR gAddStemExtremesCB; void AddStemExtremes(Fixed bot, Fixed top); extern AC_RETRYPTR gReportRetryCB; #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 *gHColorList[], *gVColorList[]; extern int32_t gNumHColors, gNumVColors; extern bool gWriteColoredBez; extern Fixed gBlueFuzz; extern bool gDoAligns, gDoStems; extern bool gIdInFile; extern bool gRoundToInt; #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 FixedPosInf INT32_MAX #define FixedNegInf INT32_MIN #define FixShift (8) #define FixInt(i) (((int32_t)(i)) << FixShift) #define FixReal(i) ((int32_t)((i) *256.0)) 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))>>FixShift) #define FIXED2FLOAT(x) (x/256.0) #define FixOne (0x100) #define FixTwo (0x200) #define FixHalf (0x80) #define FixQuarter (0x40) #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 tfmx(x) ((x)) #define tfmy(y) (-(y)) #define itfmx(x) ((x)) #define itfmy(y) (-(y)) #define dtfmx(x) ((x)) #define dtfmy(y) (-(y)) #define idtfmx(x) ((x)) #define idtfmy(y) (-(y)) #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));} #define DEBUG_ROUND4(val) { val = ( val >=0 ) ? (4*(val/4)) : (4*((val - 1)/4));} /* 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); unsigned char* Alloc(int32_t sz); /* Sub-allocator */ int AddCounterColorChars(char* charlist, char* ColorList[]); bool FindNameInList(char* nm, char** lst); void PruneElementColorSegs(void); int TestColorLst(PSegLnkLst lst, PClrVal colorList, bool flg, bool doLst); PClrVal CopyClrs(PClrVal lst); void AutoExtraColors(bool movetoNewClrs, bool soleol, int32_t solWhere); int32_t SpecialCharType(void); bool VColorChar(void); bool HColorChar(void); bool NoBlueChar(void); int32_t SolEolCharCode(void); bool SpecialSolEol(void); bool MoveToNewClrs(void); void CheckSmooth(void); void CheckBBoxEdge(PPathElt 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 AddColorPoint(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch, PPathElt p0, PPathElt p1); void AddHPair(PClrVal v, char ch); void AddVPair(PClrVal v, char ch); void XtraClrs(PPathElt e); bool AutoColorGlyph(const ACFontInfo* fontinfo, const char* srcglyph, bool extracolor); void EvalV(void); void EvalH(void); void GenVPts(int32_t specialCharType); void CheckVal(PClrVal val, bool vert); void CheckTfmVal(PClrSeg hSegList, Fixed* bandList, int32_t length); void CheckVals(PClrVal vlst, bool vert); bool DoFixes(void); bool FindLineSeg(Fixed loc, PClrSeg sL); void FltnCurve(Cd c0, Cd c1, Cd c2, Cd c3, PFltnRec pfr); bool InBlueBand(Fixed loc, int32_t n, Fixed* p); void GenHPts(void); void PreGenPts(void); PPathElt GetDest(PPathElt cldest); PPathElt GetClosedBy(PPathElt clsdby); void GetEndPoint(PPathElt e, Fixed* x1p, Fixed* y1p); void GetEndPoints(PPathElt 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(PPathElt e); bool IsShort(PPathElt e); PPathElt NxtForBend(PPathElt p, Fixed* px2, Fixed* py2, Fixed* px3, Fixed* py3); PPathElt PrvForBend(PPathElt p, Fixed* px2, Fixed* py2); bool IsLower(PPathElt p); bool IsUpper(PPathElt p); bool CloseSegs(PClrSeg s1, PClrSeg s2, bool vert); void DoPrune(void); void PruneVVals(void); void PruneHVals(void); void MergeVals(bool vert); void MergeFromMainColors(char ch); void RoundPathCoords(void); void MoveSubpathToEnd(PPathElt e); void AddSolEol(void); void InitAuto(int32_t reason); void InitData(const ACFontInfo* fontinfo, int32_t reason); void InitFix(int32_t reason); void InitGen(int32_t reason); void InitPick(int32_t reason); void AutoAddFlex(void); bool SameColors(int32_t cn1, int32_t cn2); bool PreCheckForColoring(void); int32_t CountSubPaths(void); void PickVVals(PClrVal gValList); void PickHVals(PClrVal gValList); void FindBestHVals(void); void FindBestVVals(void); void PrintMessage(char* format, ...); void ReportError(char* format, ...); void ReportSmoothError(Fixed x, Fixed y); void ReportAddFlex(void); void ReportClipSharpAngle(Fixed x, Fixed y); void ReportSharpAngle(Fixed x, Fixed y); void ReportLinearCurve(PPathElt 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(PPathElt e); void ReportMissingClosePath(void); void ReportTryFlexNearMiss(Fixed x0, Fixed y0, Fixed x2, Fixed y2); void ReportTryFlexError(bool CPflg, Fixed x, Fixed y); void AskForSplit(PPathElt e); void ReportSplit(PPathElt e); void ReportConflictCheck(PPathElt e, PPathElt conflict, PPathElt cp); void ReportConflictCnt(PPathElt e, int32_t cnt); void ReportMoveSubpath(PPathElt e, char* s); void ReportRemFlare(PPathElt e, PPathElt e2, bool hFlg, int32_t i); void ReportRemConflict(PPathElt e); void ReportRotateSubpath(PPathElt e); void ReportRemShortColors(Fixed ex, Fixed ey); bool ResolveConflictBySplit(PPathElt e, bool Hflg, PSegLnkLst lnk1, PSegLnkLst lnk2); void ReportPossibleLoop(PPathElt e); void ShowHVal(PClrVal val); void ShowHVals(PClrVal lst); void ReportAddHVal(PClrVal val); void ShowVVal(PClrVal val); void ShowVVals(PClrVal lst); void ReportAddVVal(PClrVal val); void ReportFndBstVal(PClrSeg seg, PClrVal val, bool hFlg); void ReportCarry(Fixed l0, Fixed l1, Fixed loc, PClrVal clrs, bool vert); void ReportBestCP(PPathElt e, PPathElt cp); void LogColorInfo(PClrPoint pl); void ReportAddVSeg(Fixed from, Fixed to, Fixed loc, int32_t i); void ReportAddHSeg(Fixed from, Fixed to, Fixed loc, int32_t i); void ReportBandNearMiss(char* str, Fixed loc, Fixed blu); void ReportStemNearMiss(bool vert, Fixed w, Fixed minW, Fixed b, Fixed t, bool curve); void ReportColorConflict(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(PClrVal val, PClrVal v, int32_t i); void ReportRemVSeg(Fixed from, Fixed to, Fixed loc); void ReportRemHSeg(Fixed from, Fixed to, Fixed loc); void ReportPruneVVal(PClrVal val, PClrVal v, int32_t i); Fixed ScaleAbs(const ACFontInfo* fontinfo, Fixed unscaled); Fixed UnScaleAbs(const ACFontInfo* fontinfo, Fixed scaled); void InitShuffleSubpaths(void); void MarkLinks(PClrVal vL, bool hFlg); void DoShuffleSubpaths(void); void CopyMainH(void); void CopyMainV(void); void RMovePoint(Fixed dx, Fixed dy, int32_t whichcp, PPathElt e); void AddVSegment(Fixed from, Fixed to, Fixed loc, PPathElt p1, PPathElt p2, int32_t typ, int32_t i); void AddHSegment(Fixed from, Fixed to, Fixed loc, PPathElt p1, PPathElt p2, int32_t typ, int32_t i); void Delete(PPathElt e); bool ReadGlyph(const ACFontInfo* fontinfo, const char* srcglyph, bool forBlendData, bool readHints); double FixToDbl(Fixed f); bool CompareValues(PClrVal val1, PClrVal val2, int32_t factor, int32_t ghstshift); void SaveFile(const ACFontInfo* fontinfo); void CheckForMultiMoveTo(void); #define STARTUP (0) #define RESTART (1) void ListClrInfo(void); void InitAll(const ACFontInfo* fontinfo, int32_t reason); void AddVStem(Fixed top, Fixed bottom, bool curved); void AddHStem(Fixed right, Fixed left, bool curved); void AddCharExtremes(Fixed bot, Fixed top); bool AutoColor(const ACFontInfo* fontinfo, const char* srcbezdata, bool fixStems, bool debug, bool extracolor, bool changeChar, bool roundCoords); #endif /* AC_AC_H_ */ psautohint-1.1.0/libpsautohint/src/acfixed.c000066400000000000000000000011531324015706300211330ustar00rootroot00000000000000/* * 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 >= FixedPosInf / FIXEDSCALE) return FixedPosInf; if (f <= FixedNegInf / FIXEDSCALE) return FixedNegInf; return (Fixed)(f * FIXEDSCALE); } psautohint-1.1.0/libpsautohint/src/auto.c000066400000000000000000000746061324015706300205150ustar00rootroot00000000000000/* * 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 clrBBox, clrHBounds, clrVBounds, haveHBnds, haveVBnds, mergeMain; void InitAuto(int32_t reason) { switch (reason) { case STARTUP: case RESTART: clrBBox = clrHBounds = clrVBounds = haveHBnds = haveVBnds = false; } } static PPathElt GetSubPathNxt(PPathElt e) { if (e->type == CLOSEPATH) return GetDest(e); return e->next; } static PPathElt GetSubPathPrv(PPathElt e) { if (e->type == MOVETO) e = GetClosedBy(e); return e->prev; } static PClrVal FindClosestVal(PClrVal sLst, Fixed loc) { Fixed dist = FixInt(10000), bot, top, d; PClrVal best = NULL; while (sLst != NULL) { 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 CpyHClr(PPathElt e) { Fixed x1, y1; PClrVal best; GetEndPoint(e, &x1, &y1); best = FindClosestVal(gHPrimary, y1); if (best != NULL) AddHPair(best, 'b'); } static void CpyVClr(PPathElt e) { Fixed x1, y1; PClrVal best; GetEndPoint(e, &x1, &y1); best = FindClosestVal(gVPrimary, x1); if (best != NULL) AddVPair(best, 'y'); } static void PruneColorSegs(PPathElt e, bool hFlg) { PSegLnkLst lst, nxt, prv; PSegLnk lnk; PClrSeg seg; PClrVal val; lst = hFlg ? e->Hs : e->Vs; prv = NULL; while (lst != NULL) { val = NULL; 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 PruneElementColorSegs(void) { PPathElt e; e = gPathStart; while (e != NULL) { PruneColorSegs(e, true); PruneColorSegs(e, false); e = e->next; } } #define ElmntClrSegLst(e, hFlg) (hFlg) ? (e)->Hs : (e)->Vs static void RemLnk(PPathElt e, bool hFlg, PSegLnkLst rm) { PSegLnkLst 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 in glyph: %s.\n", gGlyphName); } static bool AlreadyOnList(PClrVal v, PClrVal lst) { while (lst != NULL) { if (v == lst) return true; lst = lst->vNxt; } return false; } static void AutoVSeg(PClrVal sLst) { AddVPair(sLst, 'y'); } static void AutoHSeg(PClrVal sLst) { AddHPair(sLst, 'b'); } static void AddHColoring(PClrVal h) { if (gUseH || AlreadyOnList(h, gHColoring)) return; h->vNxt = gHColoring; gHColoring = h; AutoHSeg(h); } static void AddVColoring(PClrVal v) { if (gUseV || AlreadyOnList(v, gVColoring)) return; v->vNxt = gVColoring; gVColoring = v; AutoVSeg(v); } static int32_t TestColor(PClrSeg s, PClrVal colorList, bool flg, bool doLst) { /* -1 means already in colorList; 0 means conflicts; 1 means ok to add */ PClrVal v, clst; Fixed top, bot, cTop, cBot, vT, vB, loc; bool loc1; 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; } if (gDebug) { int32_t cnt = 0; clst = colorList; while (clst != NULL) { if (++cnt > 100) { LogMsg(WARNING, OK, "Loop in hintlist for TestHint\n\007"); return 0; } clst = clst->vNxt; } } if (v->vGhst) { /* if best value for segment uses a ghost, and segment loc is already in the colorList, then return -1 */ clst = colorList; 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 (colorList != NULL) { /* check for conflict */ cTop = colorList->vLoc2; cBot = colorList->vLoc1; if (vB == cBot && vT == cTop) { return -1; } if (colorList->vGhst) { /* collapse width for conflict test */ if (colorList->vSeg1->sType == sGHOST) cBot = cTop; else cTop = cBot; } if ((flg && (cBot <= top) && (cTop >= bot)) || (!flg && (cBot >= top) && (cTop <= bot))) { return 0; } colorList = colorList->vNxt; if (!doLst) break; } return 1; } #define TestHColorLst(h) TestColorLst(h, gHColoring, gYgoesUp, true) #define TestVColorLst(v) TestColorLst(v, gVColoring, true, true) int TestColorLst(PSegLnkLst lst, PClrVal colorList, bool flg, bool doLst) { /* -1 means already in colorList; 0 means conflicts; 1 means ok to add */ int result, i, cnt; result = -1; cnt = 0; while (lst != NULL) { i = TestColor(lst->lnk->seg, colorList, 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\007\n"); 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(PPathElt e, bool Hflg, PSegLnkLst lnk1, PSegLnkLst 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 */ PPathElt new; Cd d0, d1, d2, d3, d4, d5, d6, d7; if (e->type != CURVETO || e->isFlex) return false; ReportSplit(e); new = (PPathElt)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(PPathElt e, bool Hflg) { PSegLnkLst 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 colored. */ static bool TryResolveConflict(PPathElt e, bool Hflg) { int32_t typ; PSegLnkLst lst, lnk1, lnk2; PClrSeg seg, seg1, seg2; PClrVal 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; seg2 = lst->lnk->seg; lc2 = seg2->sLoc; lnk2 = lst; if (lc1 == loc1 || lc2 == loc2) { } 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); if (gShowClrInfo) ReportRemConflict(e); return true; } if (val2->vVal < FixInt(50) && val1->vVal > val2->vVal * 20 && OkToRemLnk(loc2, Hflg, val2->vSpc)) { RemLnk(e, Hflg, lnk2); if (gShowClrInfo) 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); if (gShowClrInfo) 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); if (gShowClrInfo) ReportRemConflict(e); return true; } GetEndPoint(GetSubPathNxt(e), &x1, &y1); loc3 = Hflg ? y1 : x1; if (ProdLt0(loc3 - loc2, loc1 - loc2)) { RemLnk(e, Hflg, lnk2); if (gShowClrInfo) ReportRemConflict(e); return true; } if ((loc2 == val2->vLoc1 || loc2 == val2->vLoc2) && loc1 != val1->vLoc1 && loc1 != val1->vLoc2) { RemLnk(e, Hflg, lnk1); if (gShowClrInfo) ReportRemConflict(e); return true; } if ((loc1 == val1->vLoc1 || loc1 == val1->vLoc2) && loc2 != val2->vLoc1 && loc2 != val2->vLoc2) { RemLnk(e, Hflg, lnk2); if (gShowClrInfo) ReportRemConflict(e); return true; } if (gEditChar && ResolveConflictBySplit(e, Hflg, lnk1, lnk2)) return true; else return false; } static bool CheckColorSegs(PPathElt e, bool flg, bool Hflg) { PSegLnkLst lst; PSegLnkLst lst2; PClrSeg seg; PClrVal 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 && TestColorLst(lst2, val, flg, false) == 0) { if (TryResolveConflict(e, Hflg)) return CheckColorSegs(e, flg, Hflg); AskForSplit(e); if (Hflg) e->Hs = NULL; else e->Vs = NULL; return true; } } lst = lst2; } return false; } static void CheckElmntClrSegs(void) { PPathElt e; e = gPathStart; while (e != NULL) { if (!CheckColorSegs(e, gYgoesUp, true)) (void)CheckColorSegs(e, true, false); e = e->next; } } static bool ClrLstsClash(PSegLnkLst lst1, PSegLnkLst lst2, bool flg) { PClrSeg seg; PClrVal val; PSegLnkLst lst; while (lst1 != NULL) { seg = lst1->lnk->seg; val = seg->sLnk; if (val != NULL) { lst = lst2; while (lst != NULL) { if (TestColorLst(lst, val, flg, false) == 0) { return true; } lst = lst->next; } } lst1 = lst1->next; } return false; } static PSegLnkLst BestFromLsts(PSegLnkLst lst1, PSegLnkLst lst2) { PSegLnkLst lst, bst; PClrSeg seg; PClrVal val; Fixed bstval; int32_t i; bst = NULL; bstval = 0; for (i = 0; i < 2; i++) { lst = i ? lst1 : lst2; while (lst != NULL) { seg = lst->lnk->seg; val = seg->sLnk; if (val != NULL && val->vVal > bstval) { bst = lst; bstval = val->vVal; } lst = lst->next; } } return bst; } static bool ClrsClash(PPathElt e, PPathElt p, PSegLnkLst* hLst, PSegLnkLst* vLst, PSegLnkLst* phLst, PSegLnkLst* pvLst) { bool clash = false; PSegLnkLst bst, new; if (ClrLstsClash(*hLst, *phLst, gYgoesUp)) { clash = true; bst = BestFromLsts(*hLst, *phLst); if (bst) { new = (PSegLnkLst)Alloc(sizeof(SegLnkLst)); new->next = NULL; new->lnk = bst->lnk; } else new = NULL; e->Hs = p->Hs = *hLst = *phLst = new; } if (ClrLstsClash(*vLst, *pvLst, true)) { clash = true; bst = BestFromLsts(*vLst, *pvLst); if (bst) { new = (PSegLnkLst)Alloc(sizeof(SegLnkLst)); new->next = NULL; new->lnk = bst->lnk; } else new = NULL; e->Vs = p->Vs = *vLst = *pvLst = new; } return clash; } static void GetColorLsts(PPathElt e, PSegLnkLst* phLst, PSegLnkLst* pvLst, int32_t* ph, int32_t* pv) { PSegLnkLst hLst, vLst; int32_t h, v; if (gUseH) { hLst = NULL; h = -1; } else { hLst = e->Hs; if (hLst == NULL) h = -1; else h = TestHColorLst(hLst); } if (gUseV) { vLst = NULL; v = -1; } else { vLst = e->Vs; if (vLst == NULL) v = -1; else v = TestVColorLst(vLst); } *pvLst = vLst; *phLst = hLst; *ph = h; *pv = v; } static void ReClrBounds(PPathElt e) { if (!gUseH) { if (clrHBounds && gHColoring == NULL && !haveHBnds) ReClrHBnds(); else if (!clrBBox) { if (gHColoring == NULL) CpyHClr(e); if (mergeMain) MergeFromMainColors('b'); } } if (!gUseV) { if (clrVBounds && gVColoring == NULL && !haveVBnds) ReClrVBnds(); else if (!clrBBox) { if (gVColoring == NULL) CpyVClr(e); if (mergeMain) MergeFromMainColors('y'); } } } static void AddColorLst(PSegLnkLst lst, bool vert) { PClrVal val; PClrSeg seg; while (lst != NULL) { seg = lst->lnk->seg; val = seg->sLnk; if (vert) AddVColoring(val); else AddHColoring(val); lst = lst->next; } } static void StartNewColoring(PPathElt e, PSegLnkLst hLst, PSegLnkLst vLst) { ReClrBounds(e); if (e->newcolors != 0) { LogMsg(LOGERROR, NONFATALERROR, "Uninitialized extra hints list in glyph: %s.\n", gGlyphName); } XtraClrs(e); clrBBox = false; if (gUseV) CopyMainV(); if (gUseH) CopyMainH(); gHColoring = gVColoring = NULL; if (!gUseH) AddColorLst(hLst, false); if (!gUseV) AddColorLst(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) \ AddColorLst(vLst, true) #define AddIfNeedH(h, hLst) \ if (!gUseH && h == 1) \ AddColorLst(hLst, false) static void SetHColors(PClrVal lst) { if (gUseH) return; gHColoring = lst; while (lst != NULL) { AutoHSeg(lst); lst = lst->vNxt; } } static void SetVColors(PClrVal lst) { if (gUseV) return; gVColoring = lst; while (lst != NULL) { AutoVSeg(lst); lst = lst->vNxt; } } PClrVal CopyClrs(PClrVal lst) { PClrVal v, vlst; int cnt; vlst = NULL; cnt = 0; while (lst != NULL) { v = (PClrVal)Alloc(sizeof(ClrVal)); *v = *lst; v->vNxt = vlst; vlst = v; if (++cnt > 100) { LogMsg(WARNING, OK, "Loop in CopyClrs\007\n"); return vlst; } lst = lst->vNxt; } return vlst; } static PPathElt ColorBBox(PPathElt e) { e = FindSubpathBBox(e); ClrBBox(); clrBBox = true; return e; } static bool IsFlare(Fixed loc, PPathElt e, PPathElt 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(PPathElt e, bool hFlg, PSegLnkLst rm, PPathElt e2, int32_t i) { RemLnk(e, hFlg, rm); if (gShowClrInfo) ReportRemFlare(e, e2, hFlg, i); } bool CompareValues(PClrVal val1, PClrVal val2, int32_t factor, int32_t ghstshift) { Fixed v1 = val1->vVal, v2 = val2->vVal, mx; mx = v1 > v2 ? v1 : v2; mx <<= 1; while (mx > 0) { mx <<= 1; v1 <<= 1; v2 <<= 1; } 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 < FixedPosInf / factor) ? (v1 * factor > v2) : (v1 > v2 / factor); return (v2 < FixedPosInf / factor) ? (v1 > v2 * factor) : (v1 / factor > v2); } static void RemFlares(bool Hflg) { PSegLnkLst lst1, lst2, nxt1, nxt2; PPathElt e, n; PClrSeg seg1, seg2; PClrVal 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 = ElmntClrSegLst(e, Nm1); while (lst1 != NULL) { seg1 = lst1->lnk->seg; nxt1 = lst1->next; lst2 = ElmntClrSegLst(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, PClrVal clrs) { PClrSeg seg; PClrVal 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 (clrs != NULL) { seg = clrs->vSeg1; if (clrs->vGhst && seg->sType == sGHOST) seg = clrs->vSeg2; if (seg == NULL) goto Nxt; l0 = clrs->vLoc2; l1 = clrs->vLoc1; if (l0 > l1) { tmp = l1; l1 = l0; l0 = tmp; } l0 -= halfMargin; l1 += halfMargin; if (loc > l0 && loc < l1) { seglnk = seg->sLnk; seg->sLnk = clrs; if (vert) { if (TestColor(seg, gVColoring, true, true) == 1) { if (gShowClrInfo) ReportCarry(l0, l1, loc, clrs, vert); AddVColoring(clrs); seg->sLnk = seglnk; break; } } else if (TestColor(seg, gHColoring, gYgoesUp, true) == 1) { if (gShowClrInfo) ReportCarry(l0, l1, loc, clrs, vert); AddHColoring(clrs); seg->sLnk = seglnk; break; } seg->sLnk = seglnk; } Nxt: clrs = clrs->vNxt; } } #define PRODIST \ (FixInt(100)) /* DEBUG 8 BIT. Needed to double test from 50 to 100 for \ change in coordinate system */ static void ProClrs(PPathElt e, bool hFlg, Fixed loc) { PSegLnkLst lst, plst; PPathElt prv; Fixed cx, cy, dst; lst = ElmntClrSegLst(e, hFlg); if (lst == NULL) return; if (hFlg ? e->Hcopy : e->Vcopy) return; prv = e; while (true) { prv = GetSubPathPrv(prv); plst = ElmntClrSegLst(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 PromoteColors(void) { PPathElt e; Fixed cx, cy; e = gPathStart; while (e != NULL) { GetEndPoint(e, &cx, &cy); ProClrs(e, true, cy); ProClrs(e, false, cx); e = e->next; } } static void RemPromotedClrs(void) { PPathElt 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 RemShortColors(void) { /* Must not change colors at a short element. */ PPathElt e; Fixed cx, cy, ex, ey; e = gPathStart; cx = 0; cy = 0; while (e != NULL) { GetEndPoint(e, &ex, &ey); if (abs(cx - ex) < gMinColorElementLength && abs(cy - ey) < gMinColorElementLength) { if (gShowClrInfo) ReportRemShortColors(ex, ey); e->Hs = NULL; e->Vs = NULL; } e = e->next; cx = ex; cy = ey; } } void AutoExtraColors(bool movetoNewClrs, bool soleol, int32_t solWhere) { int32_t h, v, ph, pv; PPathElt e, cp, p; int32_t etype; PSegLnkLst hLst, vLst, phLst, pvLst; PClrVal mtVclrs, mtHclrs, prvHclrs, prvVclrs; bool (*Tst)(int32_t, int32_t), newClrs = true; bool isSpc; Fixed x, y; isSpc = clrBBox = clrVBounds = clrHBounds = false; mergeMain = (CountSubPaths() <= 5); e = gPathStart; if (gAutoExtraDebug) PrintMessage("RemFlares"); RemFlares(true); RemFlares(false); if (gAutoExtraDebug) PrintMessage("CheckElmntClrSegs"); CheckElmntClrSegs(); if (gAutoExtraDebug) PrintMessage("PromoteColors"); PromoteColors(); if (gAutoExtraDebug) PrintMessage("RemShortColors"); RemShortColors(); haveVBnds = clrVBounds; haveHBnds = clrHBounds; p = NULL; Tst = IsOk; /* it is ok to add to primary coloring */ if (gAutoExtraDebug) PrintMessage("color loop"); mtVclrs = mtHclrs = NULL; while (e != NULL) { etype = e->type; if (movetoNewClrs && etype == MOVETO) { StartNewColoring(e, (PSegLnkLst)NULL, (PSegLnkLst)NULL); Tst = IsOk; } if (soleol && etype == MOVETO) { /* start new coloring on soleol mt */ if ((solWhere == 1 && IsUpper(e)) || (solWhere == -1 && IsLower(e)) || (solWhere == 0)) { /* color bbox of next subpath */ StartNewColoring(e, (PSegLnkLst)NULL, (PSegLnkLst)NULL); Tst = IsOk; haveHBnds = haveVBnds = isSpc = true; e = ColorBBox(e); continue; } else if (isSpc) { /* new coloring after the special */ StartNewColoring(e, (PSegLnkLst)NULL, (PSegLnkLst)NULL); Tst = IsOk; haveHBnds = haveVBnds = isSpc = false; } } if (newClrs && e == p) { StartNewColoring(e, (PSegLnkLst)NULL, (PSegLnkLst)NULL); SetHColors(mtHclrs); SetVColors(mtVclrs); Tst = IsIn; } GetColorLsts(e, &hLst, &vLst, &h, &v); if (etype == MOVETO && IsShort(cp = GetClosedBy(e))) { GetColorLsts(p = cp->prev, &phLst, &pvLst, &ph, &pv); if (ClrsClash(e, p, &hLst, &vLst, &phLst, &pvLst)) { GetColorLsts(e, &hLst, &vLst, &h, &v); GetColorLsts(p, &phLst, &pvLst, &ph, &pv); } if (!(*Tst)(ph, pv) || !(*Tst)(h, v)) { StartNewColoring(e, hLst, vLst); Tst = IsOk; ph = pv = 1; /* force add of colors for p also */ } else { AddIfNeedH(h, hLst); AddIfNeedV(v, vLst); } AddIfNeedH(ph, phLst); AddIfNeedV(pv, pvLst); newClrs = false; /* so can tell if added new colors in subpath */ } else if (!(*Tst)(h, v)) { /* e needs new coloring */ if (etype == CLOSEPATH) { /* do not attach extra colors to closepath */ e = e->prev; GetColorLsts(e, &hLst, &vLst, &h, &v); } prvHclrs = CopyClrs(gHColoring); prvVclrs = CopyClrs(gVColoring); if (!newClrs) { /* this is the first extra since mt */ newClrs = true; mtVclrs = CopyClrs(prvVclrs); mtHclrs = CopyClrs(prvHclrs); } StartNewColoring(e, hLst, vLst); Tst = IsOk; if (etype == CURVETO) { x = e->x1; y = e->y1; } else GetEndPoint(e, &x, &y); CarryIfNeed(y, false, prvHclrs); CarryIfNeed(x, true, prvVclrs); } else { /* do not need to start new coloring */ AddIfNeedH(h, hLst); AddIfNeedV(v, vLst); } e = e->next; } ReClrBounds(gPathEnd); if (gAutoExtraDebug) PrintMessage("RemPromotedClrs"); RemPromotedClrs(); if (gAutoExtraDebug) PrintMessage("done autoextracolors"); } psautohint-1.1.0/libpsautohint/src/basic.h000066400000000000000000000022101324015706300206110ustar00rootroot00000000000000/* * 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 #if !defined(_MSC_VER) || _MSC_VER >= 1800 #include #include #else #include "winstdint.h" typedef unsigned char bool; #define true 1 #define false 0 #endif #ifdef _MSC_VER #if _MSC_VER < 1900 #define snprintf(buf, size, ...) _snprintf_s(buf, size, _TRUNCATE, __VA_ARGS__) #endif /* _MSC_VER < 1900 */ #if _MSC_VER < 1800 float roundf(float x); #endif /* _MSC_VER < 1800 */ #endif /* _MSC_VER */ 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)) #endif /*BASIC_H*/ psautohint-1.1.0/libpsautohint/src/bbox.c000066400000000000000000000244341324015706300204710ustar00rootroot00000000000000/* * 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 PPathElt 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; PPathElt e; Cd c0, c1, c2, c3; 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 in %s glyph.\n", gGlyphName); } } e = e->next; } xmin = FHalfRnd(xmin); ymin = FHalfRnd(ymin); xmax = FHalfRnd(xmax); ymax = FHalfRnd(ymax); } PPathElt FindSubpathBBox(PPathElt e) { FltnRec fr; Cd c0, c1, c2, c3; 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 coloring in characters in the SolEol lists. These are supposed to have subpath bbox colored, but were getting path bbox colored 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 in %s glyph.\n", gGlyphName); } } 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 ClrVBnds(void) { Fixed tmp; PPathElt p; if (gPathStart == NULL || VColorChar()) return; FindPathBBox(); vMn = itfmx(xmin); vMx = itfmx(xmax); pvMn = pxmn; pvMx = pxmx; if (vMn > vMx) { tmp = vMn; vMn = vMx; vMx = tmp; p = pvMn; pvMn = pvMx; pvMx = p; } AddColorPoint(vMn, 0, vMx, 0, 'y', pvMn, pvMx); } void ReClrVBnds(void) { AddColorPoint(vMn, 0, vMx, 0, 'y', pvMn, pvMx); } void ClrHBnds(void) { Fixed tmp; PPathElt p; if (gPathStart == NULL || HColorChar()) return; FindPathBBox(); hMn = itfmy(ymin); hMx = itfmy(ymax); phMn = pymn; phMx = pymx; if (hMn > hMx) { tmp = hMn; hMn = hMx; hMx = tmp; p = phMn; phMn = phMx; phMx = p; } AddColorPoint(0, hMn, 0, hMx, 'b', phMn, phMx); } void ReClrHBnds(void) { AddColorPoint(0, hMn, 0, hMx, 'b', phMn, phMx); } static bool CheckValOverlaps(Fixed lft, Fixed rht, PClrVal lst, bool xflg) { Fixed lft2, rht2, tmp; if (xflg) { lft = itfmx(lft); rht = itfmx(rht); } else { lft = itfmy(lft); rht = itfmy(rht); } if (lft > rht) { tmp = lft; lft = rht; rht = tmp; } while (lst != NULL) { lft2 = lst->vLoc1; rht2 = lst->vLoc2; if (xflg) { lft2 = itfmx(lft2); rht2 = itfmx(rht2); } else { lft2 = itfmy(lft2); rht2 = itfmy(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) { PPathElt e; PClrVal val; PClrSeg seg1, seg2; e = gPathStart; while (e != NULL) { if (subs) e = FindSubpathBBox(e); else { FindPathBBox(); e = NULL; } if (!Hflg) { if (!CheckValOverlaps(xmin, xmax, gVColoring, true)) { val = (PClrVal)Alloc(sizeof(ClrVal)); seg1 = (PClrSeg)Alloc(sizeof(ClrSeg)); 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 = (PClrSeg)Alloc(sizeof(ClrSeg)); 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 = gVColoring; val->vBst = val; gVColoring = val; } } else { if (!CheckValOverlaps(ymin, ymax, gHColoring, false)) { val = (PClrVal)Alloc(sizeof(ClrVal)); seg1 = (PClrSeg)Alloc(sizeof(ClrSeg)); 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 = (PClrSeg)Alloc(sizeof(ClrSeg)); 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 = gHColoring; val->vBst = val; gHColoring = val; } } } } void ClrBBox(void) { Fixed llx, lly, urx, ury, tmp; PPathElt p, p0, p1; if (!gUseV) { llx = itfmx(xmin); urx = itfmx(xmax); p0 = pxmn; p1 = pxmx; if (llx > urx) { tmp = llx; llx = urx; urx = tmp; p = p0; p0 = p1; p1 = p; } AddColorPoint(llx, 0, urx, 0, 'y', p0, p1); } if (!gUseH) { lly = itfmy(ymax); ury = itfmy(ymin); p0 = pymx; p1 = pymn; if (lly > ury) { tmp = lly; lly = ury; ury = tmp; p = p0; p0 = p1; p1 = p; } AddColorPoint(0, lly, 0, ury, 'b', p0, p1); } } void CheckPathBBox(void) { Fixed llx, lly, urx, ury, tmp; FindPathBBox(); llx = itfmx(xmin); urx = itfmx(xmax); if (llx > urx) { tmp = llx; llx = urx; urx = tmp; } lly = itfmy(ymax); ury = itfmy(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(PPathElt e1, PPathElt 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-1.1.0/libpsautohint/src/bbox.h000066400000000000000000000014341324015706300204710ustar00rootroot00000000000000/* * 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" PPathElt FindSubpathBBox(PPathElt 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 ClrVBnds(void); void ReClrVBnds(void); void ClrHBnds(void); void ReClrHBnds(void); void AddBBoxHV(bool Hflg, bool subs); void ClrBBox(void); void CheckPathBBox(void); bool CheckBBoxes(PPathElt e1, PPathElt e2); #endif /* AC_BBOX_H_ */ psautohint-1.1.0/libpsautohint/src/charpath.h000066400000000000000000000022241324015706300213270ustar00rootroot00000000000000/* * 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, *PHintElt; typedef struct _t_charpathelt { int16_t type; /* RMT, RDT, RCT, CP */ /* the following fields must be cleared in charpathpriv.c/CheckPath */ bool isFlex:1, sol:1, eol:1, remove:1; int unused:12; PHintElt hints; Fixed x, y, x1, y1, x2, y2, x3, y3; /* absolute coordinates */ int32_t rx, ry, rx1, ry1, rx2, ry2, rx3, ry3; /* relative coordinates */ } CharPathElt, *PCharPathElt; typedef struct _t_pathlist { PCharPathElt path; PHintElt mainhints; int32_t sb; int16_t width; } PathList, *PPathList; PCharPathElt AppendCharPathElement(int); void SetHintsElt(int16_t, CdPtr, int32_t, int32_t, bool); void SetNoHints(void); #endif /*CHARPATH_H*/ psautohint-1.1.0/libpsautohint/src/charpathpriv.c000066400000000000000000000066051324015706300222320ustar00rootroot00000000000000/* * 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" #define MAXPATHELT 100 /* initial maximum number of path elements */ static int32_t maxPathEntries = 0; static PPathList currPathList = NULL; static int32_t path_entries = 0; /* number of elements in a character path */ static bool addHints = true; /* whether to include hints in the font */ static void CheckPath(void); static void CheckPath(void) { if (currPathList->path == NULL) { currPathList->path = (CharPathElt*)AllocateMem( maxPathEntries, sizeof(CharPathElt), "path element array"); } if (path_entries >= maxPathEntries) { int i; maxPathEntries += MAXPATHELT; currPathList->path = (PCharPathElt)ReallocateMem( (char*)currPathList->path, maxPathEntries * sizeof(CharPathElt), "path element array"); /* Initialize certain fields in CharPathElt, since realloc'ed memory */ /* may be non-zero. */ for (i = path_entries; i < maxPathEntries; i++) { currPathList->path[i].hints = NULL; currPathList->path[i].isFlex = false; currPathList->path[i].sol = false; currPathList->path[i].eol = false; currPathList->path[i].remove = false; } } } PCharPathElt AppendCharPathElement(int pathtype) { CheckPath(); currPathList->path[path_entries].type = pathtype; path_entries++; return (&currPathList->path[path_entries - 1]); } void SetHintsElt(int16_t hinttype, CdPtr coord, int32_t elt1, int32_t elt2, bool mainhints) { PHintElt* hintEntry; PHintElt lastHintEntry = NULL; if (!addHints) return; if (mainhints) /* define main hints for character */ hintEntry = &currPathList->mainhints; else { CheckPath(); hintEntry = &currPathList->path[path_entries].hints; } lastHintEntry = (PHintElt)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; } /* Called when character file contains hinting operators, but not the path element information needed for making blended fonts. */ void SetNoHints(void) { addHints = false; } /* According to Bill Paxton the offset locking commands should be replaced by hint substitution and is not necessary to use for blended fonts. This means characters that should have these commands may not look as good on Classic LW's. */ /* void SetOffsetLocking(locktype) char *locktype; { if (strcmp(locktype, "sol") == 0) currPathList[path_entries-1].sol = true; else currPathList[path_entries-1].eol = true; } */ psautohint-1.1.0/libpsautohint/src/charprop.c000066400000000000000000000155301324015706300213520ustar00rootroot00000000000000/* * 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" char* gVColorList[] = { "m", "M", "T", "ellipsis", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; char* gHColorList[] = { "element", "equivalence", "notelement", "divide", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static char* UpperSpecialChars[] = { "questiondown", "exclamdown", "semicolon", NULL }; static char* LowerSpecialChars[] = { "question", "exclam", "colon", NULL }; static char* NoBlueList[] = { "at", "bullet", "copyright", "currency", "registered", NULL }; static char* SolEol0List[] = { "asciitilde", "asterisk", "bullet", "period", "periodcentered", "colon", "dieresis", "divide", "ellipsis", "exclam", "exclamdown", "guillemotleft", "guillemotright", "guilsinglleft", "guilsinglright", "quotesingle", "quotedbl", "quotedblbase", "quotesinglbase", "quoteleft", "quoteright", "quotedblleft", "quotedblright", "tilde", NULL }; static char* SolEol1List[] = { "i", "j", "questiondown", "semicolon", NULL }; static char* SolEolNeg1List[] = { "question", NULL }; static bool StrEqual(char* s1, char* s2) { unsigned char c1, c2; while (true) { c1 = *s1++; c2 = *s2++; if (c1 != c2) return false; if (c1 == 0 && c2 == 0) return true; if (c1 == 0 || c2 == 0) return false; } } bool FindNameInList(char* nm, char** lst) { char **l, *lnm; l = lst; while (true) { lnm = *l; if (lnm == NULL) return false; if (StrEqual(lnm, nm)) return true; l++; } } /* Adds specified characters to CounterColorList array. */ int AddCounterColorChars(char* charlist, char* ColorList[]) { 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, ColorList)) continue; /* Currently, ColorList must end with a NULL pointer. */ if (ListEntries == (COUNTERLISTSIZE - 1)) { LogMsg(WARNING, OK, "Exceeded counter hints list size. (maximum is " "%d.)\n Cannot add %s or subsequent " "characters.\n", (int)COUNTERLISTSIZE, token); break; } ColorList[ListEntries] = AllocateMem(1, strlen(token) + 1, "counter hints list"); strcpy(ColorList[ListEntries++], token); } return (ListEntries - COUNTERDEFAULTENTRIES); } int32_t SpecialCharType(void) { /* 1 = upper; -1 = lower; 0 = neither */ if (FindNameInList(gGlyphName, UpperSpecialChars)) return 1; if (FindNameInList(gGlyphName, LowerSpecialChars)) return -1; return 0; } bool HColorChar(void) { return FindNameInList(gGlyphName, gHColorList); } bool VColorChar(void) { return FindNameInList(gGlyphName, gVColorList); } bool NoBlueChar(void) { return FindNameInList(gGlyphName, NoBlueList); } int32_t SolEolCharCode(void) { if (FindNameInList(gGlyphName, SolEol0List)) return 0; if (FindNameInList(gGlyphName, SolEol1List)) return 1; if (FindNameInList(gGlyphName, SolEolNeg1List)) return -1; return 2; } /* This change was made to prevent bogus sol-eol's. And to prevent adding sol-eol if there are a lot of subpaths. */ bool SpecialSolEol(void) { int32_t code = SolEolCharCode(); int32_t count; if (code == 2) return false; count = CountSubPaths(); if (code != 0 && count != 2) return false; if (code == 0 && count > 3) return false; return true; } static PPathElt SubpathEnd(PPathElt e) { while (true) { e = e->next; if (e == NULL) return gPathEnd; if (e->type == MOVETO) return e->prev; } } static PPathElt SubpathStart(PPathElt e) { while (e != gPathStart) { if (e->type == MOVETO) break; e = e->prev; } return e; } static PPathElt SolEol(PPathElt e) { e = SubpathStart(e); e->sol = true; e = SubpathEnd(e); e->eol = true; return e; } static void SolEolAll(void) { PPathElt e; e = gPathStart->next; while (e != NULL) { e = SolEol(e); e = e->next; } } static void SolEolUpperOrLower(bool upper) { PPathElt e, s1, s2; Fixed x1, y1, s1y, s2y; bool s1Upper; if (gPathStart == NULL) return; e = s1 = gPathStart->next; GetEndPoint(e, &x1, &y1); s1y = itfmy(y1); e = SubpathEnd(e); e = e->next; if (e == NULL) return; s2 = e; GetEndPoint(e, &x1, &y1); s2y = itfmy(y1); s1Upper = (s1y > s2y); if ((upper && s1Upper) || (!upper && !s1Upper)) (void)SolEol(s1); else (void)SolEol(s2); } /* This change was made to prevent bogus sol-eol's. And to prevent adding sol-eol if there are a lot of subpaths. */ void AddSolEol(void) { if (gPathStart == NULL) return; if (!SpecialSolEol()) return; switch (SolEolCharCode()) { /* 1 means upper, -1 means lower, 0 means all */ case 0: SolEolAll(); break; case 1: SolEolUpperOrLower(true); break; case -1: SolEolUpperOrLower(false); break; } } bool MoveToNewClrs(void) { return StrEqual(gGlyphName, "percent") || StrEqual(gGlyphName, "perthousand"); } psautohint-1.1.0/libpsautohint/src/check.c000066400000000000000000000362271324015706300206170ustar00rootroot00000000000000/* * 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 xflat, yflat, xdone, ydone, bbquit; static int32_t xstate, ystate, xstart, ystart; static Fixed x0, cy0, x1, cy1, xloc, yloc; static Fixed x, y, xnxt, ynxt; static Fixed yflatstartx, yflatstarty, yflatendx, yflatendy; static Fixed xflatstarty, xflatstartx, xflatendx, xflatendy; static bool vert, started, reCheckSmooth; static Fixed loc, frst, lst, fltnvalue; static PPathElt e; static bool forMultiMaster = false, 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) { reCheckSmooth = ResolveConflictBySplit(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 (y > yloc) { /* going up */ if (ystate == goingUP) return; if (ystate == STARTING) ystart = ystate = goingUP; else /*if (ystate == goingDOWN)*/ { if (ystart == goingUP) { yflatendx = xloc; yflatendy = yloc; } else if (!yflat) { yflatstartx = xloc; yflatstarty = yloc; yflat = true; } ystate = goingUP; } } else if (y < yloc) { /* going down */ if (ystate == goingDOWN) return; if (ystate == STARTING) ystart = ystate = goingDOWN; else /*if (ystate == goingUP)*/ { if (ystart == goingDOWN) { yflatendx = xloc; yflatendy = yloc; } else if (!yflat) { yflatstartx = xloc; yflatstarty = yloc; yflat = true; } ystate = goingDOWN; } } } static void chkYFLAT(void) { if (!yflat) { if (LsTan(y - yloc, x - xloc)) { yflat = true; yflatstartx = xloc; yflatstarty = yloc; } return; } if (ystate != ystart) return; if (GrTan(y - yloc, x - xloc)) { yflatendx = xloc; yflatendy = yloc; ydone = true; } } static void chkXFLAT(void) { if (!xflat) { if (LsTan(x - xloc, y - yloc)) { xflat = true; xflatstartx = xloc; xflatstarty = yloc; } return; } if (xstate != xstart) return; if (GrTan(x - xloc, y - yloc)) { xflatendx = xloc; xflatendy = yloc; xdone = true; } } static void chkXDIR(void) { if (x > xloc) { /* going up */ if (xstate == goingUP) return; if (xstate == STARTING) xstart = xstate = goingUP; else /*if (xstate == goingDOWN)*/ { if (xstart == goingUP) { xflatendx = xloc; xflatendy = yloc; } else if (!xflat) { xflatstartx = xloc; xflatstarty = yloc; xflat = true; } xstate = goingUP; } } else if (x < xloc) { if (xstate == goingDOWN) return; if (xstate == STARTING) xstart = xstate = goingDOWN; else /*if (xstate == goingUP)*/ { if (xstart == goingDOWN) { xflatendx = xloc; xflatendy = yloc; } else if (!xflat) { xflatstartx = xloc; xflatstarty = yloc; xflat = true; } xstate = goingDOWN; } } } static void chkDT(Cd c) { Fixed loc; x = c.x, y = c.y; ynxt = y; xnxt = x; if (!ydone) { chkYDIR(); chkYFLAT(); if (ydone && yflat && abs(yflatstarty - cy0) > SDELTA && abs(cy1 - yflatendy) > SDELTA) { if ((ystart == goingUP && yflatstarty - yflatendy > SDELTA) || (ystart == goingDOWN && yflatendy - yflatstarty > SDELTA)) { if (gEditChar && !forMultiMaster) chkBad(); return; } if (abs(yflatstartx - yflatendx) > SDELTA3) { DEBUG_ROUND(yflatstartx); DEBUG_ROUND(yflatendx); DEBUG_ROUND(yflatstarty); DEBUG_ROUND(yflatendy); loc = (yflatstarty + yflatendy) / 2; DEBUG_ROUND(loc); if (!forMultiMaster) { AddHSegment(yflatstartx, yflatendx, loc, e, (PPathElt)NULL, sCURVE, 13); } else { inflPtFound = true; fltnvalue = itfmy(loc); } } } } if (!xdone) { chkXDIR(); chkXFLAT(); if (xdone && xflat && abs(xflatstartx - x0) > SDELTA && abs(x1 - xflatendx) > SDELTA) { if ((xstart == goingUP && xflatstartx - xflatendx > SDELTA) || (xstart == goingDOWN && xflatendx - xflatstartx > SDELTA)) { if (gEditChar && !forMultiMaster) chkBad(); return; } if (abs(xflatstarty - xflatendy) > SDELTA3) { DEBUG_ROUND(xflatstarty); DEBUG_ROUND(xflatendy); DEBUG_ROUND(xflatstartx); DEBUG_ROUND(xflatendx); loc = (xflatstartx + xflatendx) / 2; DEBUG_ROUND(loc); if (!forMultiMaster) { AddVSegment(xflatstarty, xflatendy, loc, e, (PPathElt)NULL, sCURVE, 13); } else { inflPtFound = true; fltnvalue = itfmx(loc); } } } } xloc = xnxt; yloc = 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, PPathElt 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 in %s.\n", gGlyphName); } void Delete(PPathElt e) { PPathElt 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; } static void CheckSCurve(PPathElt ee) { FltnRec fr; Cd c0, c1, c2, c3; if (ee->type != CURVETO) { LogMsg(LOGERROR, NONFATALERROR, "Malformed path list in %s.\n", gGlyphName); } 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; xstate = ystate = STARTING; xdone = ydone = xflat = yflat = false; x0 = c0.x; cy0 = c0.y; x1 = c3.x; cy1 = c3.y; xloc = x0; yloc = cy0; e = ee; forMultiMaster = false; FltnCurve(c0, c1, c2, c3, &fr); } static void CheckZeroLength(void) { PPathElt e, NxtE; Fixed x0, cy0, x1, cy1, x2, y2, x3, y3; 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) { PPathElt e, nxt, NxtE; bool recheck; Fixed x0, cy0, x1, cy1, x2, y2, x3, y3, smdiff, xx, yy; CheckZeroLength(); restart: 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)) ReportSmoothError(x1, cy1); if (smdiff > FixInt(140)) { /* trim off sharp angle */ /* As of version 2.21 angle blunting will not occur. */ /* if (editChar && ConsiderClipSharpPoint(x0, cy0, x1, cy1, x2, y2, e)) { ReportClipSharpAngle(x1, cy1); recheck = true; } else */ if (smdiff > FixInt(160)) ReportSharpAngle(x1, cy1); } Nxt: e = NxtE; } if (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 (bbquit) return; if (vert) { lst = y; if (!started && abs(x - loc) <= BBdist) { started = true; frst = y; } else if (started && abs(x - loc) > BBdist) bbquit = true; } else { lst = x; if (!started && abs(y - loc) <= BBdist) { started = true; frst = x; } else if (started && abs(y - loc) > BBdist) bbquit = true; } } void CheckForMultiMoveTo(void) { PPathElt 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(PPathElt 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 in %s.\n", gGlyphName); } GetEndPoint(e->prev, &c0.x, &c0.y); fr.report = chkBBDT; 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; loc = lc; vert = vrt; started = false; chkBBDT(c0); FltnCurve(c0, c1, c2, c3, &fr); *pf = frst; *pl = 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.0; 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) { PPathElt 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: x = itfmx(x); y = itfmy(y); ReportDuplicates(x, y); } void MoveSubpathToEnd(PPathElt e) { PPathElt 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-1.1.0/libpsautohint/src/control.c000066400000000000000000000654041324015706300212210ustar00rootroot00000000000000/* * 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(const ACFontInfo* fontinfo, PClrVal sLst1); static void DoVStems(PClrVal sLst); static bool CounterFailed; void InitAll(const ACFontInfo* fontinfo, int32_t reason) { InitData(fontinfo, reason); /* must be first */ InitAuto(reason); InitFix(reason); InitGen(reason); InitPick(reason); } static int32_t PtLstLen(PClrPoint lst) { int32_t cnt = 0; while (lst != NULL) { cnt++; lst = lst->next; } return cnt; } static int32_t PointListCheck(PClrPoint new, PClrPoint 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 in %s.\n", gGlyphName); } } if (n1 > n2) { tmp = n1; n1 = n2; n2 = tmp; } while (true) { if (lst == NULL) { return -1; } if (lst->c == ch) { /* same kind of color */ 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 newcolors. 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 SameColorLists(PClrPoint lst1, PClrPoint 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 SameColors(int32_t cn1, int32_t cn2) { if (cn1 == cn2) { return true; } return SameColorLists(gPtLstArray[cn1], gPtLstArray[cn2]); } void MergeFromMainColors(char ch) { PClrPoint lst; for (lst = gPtLstArray[0]; lst != NULL; lst = lst->next) { if (lst->c != ch) { continue; } if (PointListCheck(lst, gPointList) == -1) { if (ch == 'b') { AddColorPoint(0, lst->y0, 0, lst->y1, ch, lst->p0, lst->p1); } else { AddColorPoint(lst->x0, 0, lst->x1, 0, ch, lst->p0, lst->p1); } } } } void AddColorPoint(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch, PPathElt p0, PPathElt p1) { PClrPoint pt; int32_t chk; pt = (PClrPoint)Alloc(sizeof(ClrPoint)); 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 && gShowClrInfo) { ReportColorConflict(x0, y0, x1, y1, ch); } if (chk == -1) { pt->next = gPointList; gPointList = pt; if (gLogging) { LogColorInfo(gPointList); } } } static void CopyClrFromLst(char clr, PClrPoint lst) { bool bvflg = (clr == 'b' || clr == 'v'); while (lst != NULL) { if (lst->c == clr) { if (bvflg) { AddColorPoint(0, lst->y0, 0, lst->y1, clr, lst->p0, lst->p1); } else { AddColorPoint(lst->x0, 0, lst->x1, 0, clr, lst->p0, lst->p1); } } lst = lst->next; } } void CopyMainV(void) { CopyClrFromLst('m', gPtLstArray[0]); } void CopyMainH(void) { CopyClrFromLst('v', gPtLstArray[0]); } void AddHPair(PClrVal v, char ch) { Fixed bot, top, tmp; PPathElt p0, p1, p; bot = itfmy(v->vLoc1); top = itfmy(v->vLoc2); p0 = v->vBst->vSeg1->sElt; p1 = v->vBst->vSeg2->sElt; if (top < bot) { 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 */ } } AddColorPoint(0, bot, 0, top, ch, p0, p1); } void AddVPair(PClrVal v, char ch) { Fixed lft, rght, tmp; PPathElt p0, p1, p; lft = itfmx(v->vLoc1); rght = itfmx(v->vLoc2); p0 = v->vBst->vSeg1->sElt; p1 = v->vBst->vSeg2->sElt; if (lft > rght) { tmp = lft; lft = rght; rght = tmp; p = p0; p0 = p1; p1 = p; } AddColorPoint(lft, 0, rght, 0, ch, p0, p1); } static bool UseCounter(PClrVal sLst, bool mclr) { int32_t cnt = 0; Fixed minLoc, midLoc, maxLoc, prevBstVal, bestVal; Fixed minDelta, midDelta, maxDelta, loc, delta, th; PClrVal 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) { loc = sLst->vLoc1; 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 (mclr) { gVColoring = newLst; } else { gHColoring = newLst; } return true; } if (abs(minDelta - maxDelta) < FixInt(3) && abs((maxLoc - midLoc) - (midLoc - minLoc)) < FixInt(3)) { ReportError(mclr ? "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 */ PClrPoint* newArray; int32_t i; gMaxPtLsts += 5; newArray = (PClrPoint*)Alloc(gMaxPtLsts * sizeof(PClrPoint)); for (i = 0; i < gMaxPtLsts - 5; i++) { newArray[i] = gPtLstArray[i]; } gPtLstArray = newArray; } gPtLstIndex = gNumPtLsts; gNumPtLsts++; gPointList = NULL; gPtLstArray[gPtLstIndex] = NULL; } void XtraClrs(PPathElt e) { /* this can be simplified for standalone coloring */ gPtLstArray[gPtLstIndex] = gPointList; if (e->newcolors == 0) { GetNewPtLst(); e->newcolors = (int16_t)gPtLstIndex; } gPtLstIndex = e->newcolors; gPointList = gPtLstArray[gPtLstIndex]; } static void Blues(const ACFontInfo* fontinfo) { Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0; PClrVal sLst; /* Top alignment zones are in the global 'topBands', bottom in 'botBands'. This function looks through the path, as defined by the linked list of 'elts', starting at the global 'pathStart', and adds to several lists. Coordinates are stored in the elt.(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. HStems, Vstems are global lists of Fixed 24.8 numbers.. segLists is an array of 4 ClrSeg 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. NoBlueChar() consults a hard-coded list of glyph names, If the glyph is in this list, set the alignment zones (top and botBands) to empty. 1) gen.c:GenHPts() . Buid the raw list of stem segments in global 'topList' and 'botList'. gen.c:GenHPts() steps through the liked list of path segments, starting at 'pathStart' It decides if a path is mostly H, and if so, adds it to a linked list of hstem candidates in segLists, 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 'ClrSeg' for the parent path elt , fills it in, adds it to appropriate list of the 4 segLists, and then sorts by hstem location. seg->sElt is the parent elt seg->sType is the type seg->sLoc is the location in Fixed 18.14: right shift 7 to get integer value. If the current path elt is a Closepath, It also calls LinkSegment() to add the current stem segment to the list of stem segments referenced by this elt. e->Hs/Vs. Note that a hint segment is created for each nearly vertical or horioztonal path elt. Ths 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(). Evaluate 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 widt ( top - bot) Calculcate 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 HStems 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 HStems, set weight to 0. Calculate weight as related to length of the segments squared divied 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 HStems, decrease the value scale weight by of *(largest stem hint in HStems)/ dy)**3. AddHValue() decides whether add a (bottom, top) pair of color 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 ClrVal 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 color segment item->vSeg2 = tSeg; # top color 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 */ if (gShowClrInfo) { PrintMessage("generate blues"); } if (NoBlueChar()) { gLenTopBands = gLenBotBands = 0; } GenHPts(); if (gShowClrInfo) { PrintMessage("evaluate"); } if (!CounterFailed && HColorChar()) { 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); if (gShowClrInfo) { ShowHVals(gValList); PrintMessage("pick best"); } MarkLinks(gValList, true); CheckVals(gValList, false); DoHStems(fontinfo, gValList); /* Report stems and alignment zones, if this has been requested. */ PickHVals(gValList); /* Moves best ClrVal items from valList to Hcoloring list. (? Choose from set of ClrVals for the samte stem values.) */ if (!CounterFailed && HColorChar()) { gPruneValue = pv; gPruneD = pd; gPruneC = pc; gPruneB = pb; gPruneA = pa; gUseH = UseCounter(gHColoring, false); if (!gUseH) { /* try to fix */ AddBBoxHV(true, true); gUseH = UseCounter(gHColoring, false); if (!gUseH) { /* still bad news */ ReportError("Note: Glyph is in list for using H counter hints, " "but didn't find any candidates."); CounterFailed = true; } } } else { gUseH = false; } if (gHColoring == NULL) { AddBBoxHV(true, false); } if (gShowClrInfo) { PrintMessage("results"); PrintMessage(gUseH ? "rv" : "rb"); ShowHVals(gHColoring); } if (gUseH) { PrintMessage("Using H counter hints."); } sLst = gHColoring; while (sLst != NULL) { AddHPair(sLst, gUseH ? 'v' : 'b'); /* actually adds hint */ sLst = sLst->vNxt; } } static void DoHStems(const ACFontInfo* fontinfo, PClrVal sLst1) { Fixed bot, top; Fixed charTop = INT32_MIN, charBot = INT32_MAX; bool curved; if (!gDoAligns && !gDoStems) { return; } while (sLst1 != NULL) { bot = itfmy(sLst1->vLoc1); top = itfmy(sLst1->vLoc2); if (top < bot) { Fixed tmp = top; top = bot; bot = tmp; } if (top > charTop) { charTop = top; } if (bot < charBot) { charBot = bot; } /* skip if ghost or not a line on top or bottom */ if (sLst1->vGhst) { sLst1 = sLst1->vNxt; continue; } curved = !FindLineSeg(sLst1->vLoc1, botList) && !FindLineSeg(sLst1->vLoc2, topList); AddHStem(top, bot, curved); sLst1 = sLst1->vNxt; if (top != INT32_MIN || bot != INT32_MAX) { AddStemExtremes(UnScaleAbs(fontinfo, bot), UnScaleAbs(fontinfo, top)); } } if (charTop != INT32_MIN || charBot != INT32_MAX) { AddCharExtremes(UnScaleAbs(fontinfo, charBot), UnScaleAbs(fontinfo, charTop)); } } static void Yellows(void) { Fixed pv = 0, pd = 0, pc = 0, pb = 0, pa = 0; PClrVal sLst; if (gShowClrInfo) { PrintMessage("generate yellows"); } GenVPts(SpecialCharType()); if (gShowClrInfo) { PrintMessage("evaluate"); } if (!CounterFailed && VColorChar()) { 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); if (gShowClrInfo) { ShowVVals(gValList); PrintMessage("pick best"); } MarkLinks(gValList, false); CheckVals(gValList, true); DoVStems(gValList); PickVVals(gValList); if (!CounterFailed && VColorChar()) { gPruneValue = pv; gPruneD = pd; gPruneC = pc; gPruneB = pb; gPruneA = pa; gUseV = UseCounter(gVColoring, true); if (!gUseV) { /* try to fix */ AddBBoxHV(false, true); gUseV = UseCounter(gVColoring, true); if (!gUseV) { /* still bad news */ ReportError("Note: Glyph is in list for using V counter hints, " "but didn't find any candidates."); CounterFailed = true; } } } else { gUseV = false; } if (gVColoring == NULL) { AddBBoxHV(false, false); } if (gShowClrInfo) { PrintMessage("results"); PrintMessage(gUseV ? "rm" : "ry"); ShowVVals(gVColoring); } if (gUseV) { PrintMessage("Using V counter hints."); } sLst = gVColoring; while (sLst != NULL) { AddVPair(sLst, gUseV ? 'm' : 'y'); sLst = sLst->vNxt; } } static void DoVStems(PClrVal sLst) { Fixed lft, rght; bool curved; if (!gDoAligns && !gDoStems) { return; } while (sLst != NULL) { curved = !FindLineSeg(sLst->vLoc1, leftList) && !FindLineSeg(sLst->vLoc2, rightList); lft = itfmx(sLst->vLoc1); rght = itfmx(sLst->vLoc2); if (lft > rght) { Fixed tmp = lft; lft = rght; rght = tmp; } AddVStem(rght, lft, curved); sLst = sLst->vNxt; } } static void RemoveRedundantFirstColors(void) { PPathElt e; if (gNumPtLsts < 2 || !SameColors(0, 1)) { return; } e = gPathStart; while (e != NULL) { if (e->newcolors == 1) { e->newcolors = 0; return; } e = e->next; } } static void AddColorsSetup(void) { int i; gVBigDist = 0; for (i = 0; i < gNumVStems; i++) { if (gVStems[i] > gVBigDist) { gVBigDist = gVStems[i]; } } gVBigDist = dtfmx(gVBigDist); 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(dtfmy(gHBigDist)); if (gHBigDist < gInitBigDist) { gHBigDist = gInitBigDist; } gHBigDist = (gHBigDist * 23) / 20; acfixtopflt(gHBigDist, &gHBigDistR); if ((!gScalingHints) && (gRoundToInt)) { RoundPathCoords(); } CheckForMultiMoveTo(); /* PreCheckForSolEol(); */ } /* If extracolor is true then it is ok to have multi-level coloring. */ static void AddColorsInnerLoop(const ACFontInfo* fontinfo, const char* srcglyph, bool extracolor) { int32_t solEolCode = 2, retryColoring = 0; bool isSolEol = false; while (true) { PreGenPts(); CheckSmooth(); InitShuffleSubpaths(); Blues(fontinfo); if (!gDoAligns) { Yellows(); } if (gEditChar) { DoShuffleSubpaths(); } gHPrimary = CopyClrs(gHColoring); gVPrimary = CopyClrs(gVColoring); /* isSolEol = SpecialSolEol() && !useV && !useH; solEolCode = isSolEol? SolEolCharCode() : 2; */ PruneElementColorSegs(); if (gListClrInfo) { ListClrInfo(); } if (extracolor) { AutoExtraColors(MoveToNewClrs(), isSolEol, solEolCode); } gPtLstArray[gPtLstIndex] = gPointList; if (isSolEol) { break; } retryColoring++; /* we want to retry coloring if `1) CounterFailed or 2) doFixes changed something, but in both cases, only on the first pass. */ if (CounterFailed && retryColoring == 1) { goto retry; } if (!DoFixes()) { break; } if (retryColoring > 1) { break; } retry: /* if we are doing the stem and zones reporting, we need to discard the * reported. */ if (gReportRetryCB != NULL) { gReportRetryCB(); } if (gPathStart == NULL || gPathStart == gPathEnd) { LogMsg(LOGERROR, NONFATALERROR, "No glyph path in %s.\n", gGlyphName); } /* SaveFile(); SaveFile is always called in AddColorsCleanup, so this is * a duplciate */ InitAll(fontinfo, RESTART); if (gWriteColoredBez && !ReadGlyph(fontinfo, srcglyph, false, false)) { break; } AddColorsSetup(); if (!PreCheckForColoring()) { break; } if (gFlexOK) { gHasFlex = false; AutoAddFlex(); } gReportErrors = false; } } static void AddColorsCleanup(const ACFontInfo* fontinfo) { RemoveRedundantFirstColors(); gReportErrors = true; if (gWriteColoredBez) { if (gPathStart == NULL || gPathStart == gPathEnd) { LogMsg(LOGERROR, NONFATALERROR, "The %s glyph path vanished while adding " "hints.\n", gGlyphName); } else { SaveFile(fontinfo); } } InitAll(fontinfo, RESTART); } static void AddColors(const ACFontInfo* fontinfo, const char* srcglyph, bool extracolor) { if (gPathStart == NULL || gPathStart == gPathEnd) { PrintMessage("No character path, so no hints."); SaveFile(fontinfo); /* make sure it gets saved with no coloring */ return; } gReportErrors = true; CounterFailed = gBandError = false; CheckPathBBox(); CheckForDups(); AddColorsSetup(); if (!PreCheckForColoring()) { return; } if (gFlexOK) { gHasFlex = false; AutoAddFlex(); } AddColorsInnerLoop(fontinfo, srcglyph, extracolor); AddColorsCleanup(fontinfo); } bool AutoColorGlyph(const ACFontInfo* fontinfo, const char* srcglyph, bool extracolor) { int32_t lentop = gLenTopBands, lenbot = gLenBotBands; if (!ReadGlyph(fontinfo, srcglyph, false, false)) { LogMsg(LOGERROR, NONFATALERROR, "Cannot parse %s glyph.\n", gGlyphName); } PrintMessage(""); /* Just print the file name. */ AddColors(fontinfo, srcglyph, extracolor); gLenTopBands = lentop; gLenBotBands = lenbot; return true; } psautohint-1.1.0/libpsautohint/src/eval.c000066400000000000000000000432331324015706300204640ustar00rootroot00000000000000/* * 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.0 * 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.5) { 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.0 && 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.0 + 0.4 * (1.0 - 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(PClrSeg botSeg, PClrSeg topSeg, Fixed* pspc, Fixed* pv) { Fixed brght, blft, bloc, tloc, trght, tlft, ldst, rdst; Fixed mndist, dist, dx, dy, minlen, overlaplen; bool inBotBand, inTopBand; int i; *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 */ overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft); minlen = NUMMIN(trght - tlft, brght - blft); if (minlen == overlaplen) dist = dy; else dist = CalcOverlapDist(dy, overlaplen, minlen); } else { /* no overlap; take closer ends */ ldst = abs(tlft - brght); rdst = abs(trght - blft); dx = NUMMIN(ldst, rdst); dist = GapDist(dx); dist += (7 * dy) / 5; /* 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. */ DEBUG_ROUND(dist) /* DEBUG 8 BIT */ if (dx > dy) dist *= dx / dy; } mndist = FixTwoMul(gMinDist); dist = NUMMAX(dist, mndist); if (gNumHStems > 0) { Fixed w = idtfmy(dy); w = abs(w); 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(PClrSeg botSeg, PClrSeg topSeg) { Fixed brght, blft, bloc, tloc, trght, tlft; Fixed mndist, dist, dy, minlen, overlaplen; Fixed b, t, diff, minDiff, minW, w, sw; 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 */ overlaplen = NUMMIN(trght, brght) - NUMMAX(tlft, blft); 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 = itfmy(bloc); t = itfmy(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++) { sw = gHStems[i]; 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(PClrSeg leftSeg, PClrSeg rightSeg, Fixed* pspc, Fixed* pv) { Fixed ltop, lbot, lloc, rloc, rtop, rbot, tdst, bdst; Fixed mndist, dx, dy, dist, overlaplen, minlen; Fixed bonus, lbonus, rbonus; int i; *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 */ overlaplen = NUMMIN(ltop, rtop) - NUMMAX(lbot, rbot); minlen = NUMMIN(ltop - lbot, rtop - rbot); if (minlen == overlaplen) dist = dx; else dist = CalcOverlapDist(dx, overlaplen, minlen); } else { /* no overlap; take closer ends */ tdst = abs(ltop - rbot); bdst = abs(lbot - rtop); 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) { Fixed w = idtfmx(dx); w = abs(w); 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(PClrSeg leftSeg, PClrSeg rightSeg) { Fixed ltop, lbot, lloc, rloc, rtop, rbot; Fixed mndist, dx, dist, overlaplen, minlen; Fixed l, r, diff, minDiff, minW, w, sw; 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)) { /* overlap */ overlaplen = NUMMIN(ltop, rtop) - NUMMAX(lbot, rbot); minlen = NUMMIN(ltop - lbot, rtop - rbot); if (minlen == overlaplen) dist = dx; else dist = CalcOverlapDist(dx, overlaplen, minlen); } else return; mndist = FixTwoMul(gMinDist); dist = NUMMAX(dist, mndist); l = itfmx(lloc); r = itfmx(rloc); w = abs(r - l); minDiff = FixInt(1000); minW = 0; for (i = 0; i < gNumVStems; i++) { sw = gVStems[i]; 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, PClrSeg lSeg, PClrSeg rSeg) { PClrVal item, vlist, vprev; item = (PClrVal)Alloc(sizeof(ClrVal)); 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; if (gShowClrInfo && gShowVs) ReportAddVVal(item); } #define LePruneValue(val) ((val) < FixOne && ((val) << 10) <= gPruneValue) static void AddVValue(Fixed lft, Fixed rght, Fixed val, Fixed spc, PClrSeg lSeg, PClrSeg 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; } InsertVValue(lft, rght, val, spc, lSeg, rSeg); } static void InsertHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, PClrSeg bSeg, PClrSeg tSeg, bool ghst) { PClrVal 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 = (PClrVal)Alloc(sizeof(ClrVal)); 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; if (gShowClrInfo && gShowHs) ReportAddHVal(item); } static void AddHValue(Fixed bot, Fixed top, Fixed val, Fixed spc, PClrSeg bSeg, PClrSeg 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; 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.0000001) 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 */ PClrVal vlist, v1; Fixed loc1, loc2; Fixed val; bool match; vlist = gValList; while (vlist != NULL) { v1 = vlist->vNxt; loc1 = vlist->vLoc1; loc2 = vlist->vLoc2; val = vlist->vVal; 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) { PClrSeg 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) { PClrSeg 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 && gYgoesUp) || (bot > top && !gYgoesUp)) { EvalHPair(bList, tList, &spc, &val); HStemMiss(bList, tList); AddHValue(bot, top, val, spc, bList, tList); } tList = tList->sNxt; } bList = bList->sNxt; } ghostSeg = (PClrSeg)Alloc(sizeof(ClrSeg)); 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; if (gYgoesUp) tempLoc += gGhostWidth; else tempLoc -= 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; if (!gYgoesUp) tempLoc += gGhostWidth; else tempLoc -= 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-1.1.0/libpsautohint/src/fix.c000066400000000000000000000211701324015706300203170ustar00rootroot00000000000000/* * 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 maxFixes (100) static Fixed HFixYs[maxFixes], HFixDYs[maxFixes]; static Fixed VFixXs[maxFixes], VFixDXs[maxFixes]; static int32_t HFixCount, VFixCount; static Fixed bPrev, tPrev; void InitFix(int32_t reason) { switch (reason) { case STARTUP: case RESTART: HFixCount = VFixCount = 0; bPrev = tPrev = FixedPosInf; } } static void RecordHFix(Fixed y, Fixed dy) { HFixYs[HFixCount] = y; HFixDYs[HFixCount] = dy; HFixCount++; } static void RecordVFix(Fixed x, Fixed dx) { VFixXs[VFixCount] = x; VFixDXs[VFixCount] = dx; VFixCount++; } static void RecordForFix(bool vert, Fixed w, Fixed minW, Fixed b, Fixed t) { Fixed mn, mx, delta; if (b < t) { mn = b; mx = t; } else { mn = t; mx = b; } if (!vert && HFixCount + 4 < maxFixes && gAutoHFix) { Fixed fixdy = w - minW; if (abs(fixdy) <= FixOne) { RecordHFix(mn, fixdy); RecordHFix(mx - fixdy, fixdy); } else { delta = FixHalfMul(fixdy); RecordHFix(mn, delta); RecordHFix(mn + fixdy, -delta); RecordHFix(mx, -delta); RecordHFix(mx - fixdy, delta); } } else if (vert && VFixCount + 4 < maxFixes && gAutoVFix) { Fixed fixdx = w - minW; if (abs(fixdx) <= FixOne) { RecordVFix(mn, fixdx); RecordVFix(mx - fixdx, fixdx); } else { delta = FixHalfMul(fixdx); RecordVFix(mn, delta); RecordVFix(mn + fixdx, -delta); RecordVFix(mx, -delta); RecordVFix(mx - fixdx, delta); } } } 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])) { ReportBandNearMiss(bottom ? "below" : "above", loc, blues[i]); } bottom = !bottom; } } bool FindLineSeg(Fixed loc, PClrSeg 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(PClrSeg hSegList, Fixed* bandList, int32_t length) { Fixed tfmval; PClrSeg sList = hSegList; while (sList != NULL) { tfmval = itfmy(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 void CheckVal(PClrVal val, bool vert) { Fixed* stems; int32_t numstems, i; Fixed wd, diff, minDiff, minW, b, t, w; bool curve = false; if (vert) { stems = gVStems; numstems = gNumVStems; b = itfmx(val->vLoc1); t = itfmx(val->vLoc2); } else { stems = gHStems; numstems = gNumHStems; b = itfmy(val->vLoc1); t = itfmy(val->vLoc2); } w = abs(t - b); minDiff = FixInt(1000); minW = 0; for (i = 0; i < numstems; i++) { wd = stems[i]; 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) { 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; if ((vert && gAutoVFix) || (!vert && gAutoHFix)) RecordForFix(vert, w, minW, b, t); } void CheckVals(PClrVal vlst, bool vert) { while (vlst != NULL) { CheckVal(vlst, vert); vlst = vlst->vNxt; } } static void FixH(PPathElt e, Fixed fixy, Fixed fixdy) { PPathElt prev, nxt; RMovePoint(0, fixdy, cpStart, e); RMovePoint(0, fixdy, cpEnd, e); prev = e->prev; if (prev != NULL && prev->type == CURVETO && prev->y2 == fixy) RMovePoint(0, fixdy, cpCurve2, prev); if (e->type == CLOSEPATH) e = GetDest(e); nxt = e->next; if (nxt != NULL && nxt->type == CURVETO && nxt->y1 == fixy) RMovePoint(0, fixdy, cpCurve1, nxt); } static void FixHs(Fixed fixy, Fixed fixdy) { /* y dy in user space */ PPathElt e; Fixed xlst = 0, ylst = 0, xinit = 0, yinit = 0; fixy = tfmy(fixy); fixdy = dtfmy(fixdy); e = gPathStart; while (e != NULL) { switch (e->type) { case MOVETO: xlst = xinit = e->x; ylst = yinit = e->y; break; case LINETO: if (e->y == fixy && ylst == fixy) FixH(e, fixy, fixdy); xlst = e->x; ylst = e->y; break; case CURVETO: xlst = e->x3; ylst = e->y3; break; case CLOSEPATH: if (yinit == fixy && ylst == fixy && xinit != xlst) FixH(e, fixy, fixdy); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal operator in path list in %s.\n", gGlyphName); } } e = e->next; } } static void FixV(PPathElt e, Fixed fixx, Fixed fixdx) { PPathElt prev, nxt; RMovePoint(fixdx, 0, cpStart, e); RMovePoint(fixdx, 0, cpEnd, e); prev = e->prev; if (prev != NULL && prev->type == CURVETO && prev->x2 == fixx) RMovePoint(fixdx, 0, cpCurve2, prev); if (e->type == CLOSEPATH) e = GetDest(e); nxt = e->next; if (nxt != NULL && nxt->type == CURVETO && nxt->x1 == fixx) RMovePoint(fixdx, 0, cpCurve1, nxt); } static void FixVs(Fixed fixx, Fixed fixdx) { /* x dx in user space */ PPathElt e; Fixed xlst = 0, ylst = 0, xinit = 0, yinit = 0; fixx = tfmx(fixx); fixdx = dtfmx(fixdx); e = gPathStart; while (e != NULL) { switch (e->type) { case MOVETO: xlst = xinit = e->x; ylst = yinit = e->y; break; case LINETO: if (e->x == fixx && xlst == fixx) FixV(e, fixx, fixdx); xlst = e->x; ylst = e->y; break; case CURVETO: xlst = e->x3; ylst = e->y3; break; case CLOSEPATH: if (xinit == fixx && xlst == fixx && yinit != ylst) FixV(e, fixx, fixdx); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal operator in point list in %s.\n", gGlyphName); } } e = e->next; } } bool DoFixes(void) { bool didfixes = false; int32_t i; if (HFixCount > 0 && gAutoHFix) { PrintMessage("Fixing horizontal near misses."); didfixes = true; for (i = 0; i < HFixCount; i++) FixHs(HFixYs[i], HFixDYs[i]); } if (VFixCount > 0 && gAutoVFix) { PrintMessage("Fixing vertical near misses."); didfixes = true; for (i = 0; i < VFixCount; i++) FixVs(VFixXs[i], VFixDXs[i]); } return didfixes; } psautohint-1.1.0/libpsautohint/src/flat.c000066400000000000000000000306201324015706300204570ustar00rootroot00000000000000/* * 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, PFltnRec pfr, bool inside) { /* Like FFltnCurve, but assumes abs(deltas) <= 127 pixels */ /* 8 bits of fraction gives enough precision for splitting curves */ #define MFix(f) (f) #define UnMFix(f) (f) #define MFixInt(f) ((f) << 8) #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) (((int32_t)(a) + (int32_t)(b)) >> 1) int32_t cds[MiniBlkSz * MiniFltnMaxDepth], dpth, eps; int32_t bbLLX = 0, bbLLY = 0, bbURX = 0, bbURY = 0; int32_t* p; p = cds; dpth = 1; *(p++) = inside; /* initial value of inrect2. Set to True by caller, and is never set false. */ *(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++) = (int32_t)MFix(f0.x - llx); *(p++) = (int32_t)MFix(f0.y - lly); *(p++) = (int32_t)MFix(f1.x - llx); *(p++) = (int32_t)MFix(f1.y - lly); *(p++) = (int32_t)MFix(f2.x - llx); *(p++) = (int32_t)MFix(f2.y - lly); *(p++) = (int32_t)MFix(f3.x - llx); *(p++) = (int32_t)MFix(f3.y - lly); } if (!inrect) { Fixed c, f128; c = (int32_t)pfr->ll.x; bbLLX = (c <= 0) ? 0 : (int32_t)MFix(c); c = (int32_t)pfr->ll.y; bbLLY = (c <= 0) ? 0 : (int32_t)MFix(c); f128 = FixInt(128); c = (int32_t)pfr->ur.x; bbURX = (c >= f128) ? 0x7fff : (int32_t)MFix(c); c = (int32_t)pfr->ur.y; bbURY = (c >= f128) ? 0x7fff : (int32_t)MFix(c); } eps = (int32_t)MFix(pfr->feps); // if (eps < 8) // eps = 8; /* Brotz patch */ 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) { int32_t 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 = MFixInt(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 = MFixInt(128) - 1; c = c1y; if (c > ll && c < ur) { c = c2y; if (c > ll && c < ur) inbbox = true; } } } } if (inbbox) { int32_t eqa, eqb, x, y; Fixed EPS, d; x = c0x; y = c0y; eqa = c3y - y; eqb = x - c3x; if (eqa == 0 && eqb == 0) goto ReportC3; EPS = ((abs(eqa) > abs(eqb)) ? (int32_t)eqa : (int32_t)eqb) * (int32_t)eps; if (EPS < 0) EPS = -EPS; d = (int32_t)eqa * (int32_t)(c1x - x); d += (int32_t)eqb * (int32_t)(c1y - y); if (abs(d) < EPS) { d = (int32_t)eqa * (int32_t)(c2x - x); d += (int32_t)eqb * (int32_t)(c2y - y); if (abs(d) < EPS) goto ReportC3; } } { /* Bezier divide */ int32_t c0, c1, c2, d1, d2, d3; d0x = c0 = c0x; c1 = c1x; c2 = c2x; d1x = d1 = (int32_t)mdpt(c0, c1); d3 = (int32_t)mdpt(c1, c2); d2x = d2 = (int32_t)mdpt(d1, d3); c2x = c2 = (int32_t)mdpt(c2, c3x); c1x = c1 = (int32_t)mdpt(d3, c2); c0x = d3x = (int32_t)mdpt(d2, c1); d0y = c0 = c0y; c1 = c1y; c2 = c2y; d1y = d1 = (int32_t)mdpt(c0, c1); d3 = (int32_t)mdpt(c1, c2); d2y = d2 = (int32_t)mdpt(d1, d3); c2y = c2 = (int32_t)mdpt(c2, c3y); c1y = c1 = (int32_t)mdpt(d3, c2); c0y = d3y = (int32_t)mdpt(d2, c1); bbox2 = inbbox; inrect2 = inrect; p += MiniBlkSz; dpth++; continue; } ReportC3 : { Cd c; if (--dpth == 0) c = f3; else { c.x = UnMFix(c3x) + pfr->llx; c.y = UnMFix(c3y) + pfr->lly; } (*pfr->report)(c); // call FPBBoxPt() to reset bbox. if (dpth == 0) return; p -= MiniBlkSz; } } } /* end of FMiniFltn */ #undef MFix #undef UnMFix #undef MFixInt #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, PFltnRec pfr, bool inrect) { 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 (!inrect) { if (urx < pfr->ll.x || llx > pfr->ur.x || ury < pfr->ll.y || lly > pfr->ur.y) goto ReportC3; if (urx <= pfr->ur.x && ury <= pfr->ur.y && llx >= pfr->ll.x && lly >= pfr->ll.y) inrect = true; } { /* 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; if (!inrect) { pfr->ll.x -= llx; pfr->ur.x -= llx; pfr->ll.y -= lly; pfr->ur.y -= lly; } FMiniFltn(c0, c1, c2, c3, pfr, inrect); if (!inrect) { pfr->ll.x += llx; pfr->ur.x += llx; pfr->ll.y += lly; pfr->ur.y += lly; } 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, inrect); FFltnCurve(d0, d1, d2, d3, pfr, inrect); pfr->limit++; return; ReportC3: (*pfr->report)(c3); } void FltnCurve(Cd c0, Cd c1, Cd c2, Cd c3, PFltnRec pfr) { pfr->limit = 6; /* limit on how many times a bez curve can be split in half by recursive calls to FFltnCurve() */ // pfr->feps = FixHalf; pfr->feps = FixOne; /* DEBUG 8 BIT FIX */ FFltnCurve(c0, c1, c2, c3, pfr, true); } psautohint-1.1.0/libpsautohint/src/fontinfo.c000066400000000000000000000276071324015706300213660ustar00rootroot00000000000000/* * 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 gNumHColors, gNumVColors; static void ParseIntStems(const ACFontInfo* fontinfo, char* kw, bool optional, int32_t maxstems, int* stems, int32_t* pnum); static void ParseStems(const ACFontInfo* fontinfo, char* kw, Fixed* stems, int32_t* pnum) { int istems[MAXSTEMS], i; ParseIntStems(fontinfo, kw, ACOPTIONAL, 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; fontinfostr = GetFontInfo(fontinfo, keyword, optional); if ((fontinfostr != NULL) && (fontinfostr[0] != 0)) { *value = atol(fontinfostr); } return; } static void GetKeyFixedValue(const ACFontInfo* fontinfo, char* keyword, bool optional, Fixed* value) { char* fontinfostr; float tempValue; fontinfostr = GetFontInfo(fontinfo, keyword, optional); if ((fontinfostr != NULL) && (fontinfostr[0] != 0)) { tempValue = strtod(fontinfostr, NULL); *value = (Fixed)tempValue * (1 << FixShift); } return; } 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 ORDINARYCOLORING = !gScalingHints && gWriteColoredBez; 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; gNumHColors = gNumVColors = 0; gLenBotBands = gLenTopBands = 0; /* check for FlexOK, AuxHStems, AuxVStems */ /* for intelligent scaling, it's too hard to check these */ if (!gScalingHints) { 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", !ORDINARYCOLORING); gFlexOK = (fontinfostr != NULL) && (fontinfostr[0] != '\0') && strcmp(fontinfostr, "false"); fontinfostr = GetFontInfo(fontinfo, "FlexStrict", true); if (fontinfostr != NULL) 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", ACOPTIONAL, &gBlueFuzz); /* Check for counter coloring characters. */ fontinfostr = GetFontInfo(fontinfo, "VCounterChars", ACOPTIONAL); if (fontinfostr != NULL) gNumVColors = AddCounterColorChars(fontinfostr, gVColorList); fontinfostr = GetFontInfo(fontinfo, "HCounterChars", ACOPTIONAL); if (fontinfostr != NULL) gNumHColors = AddCounterColorChars(fontinfostr, gHColorList); GetKeyValue(fontinfo, "AscenderHeight", ACOPTIONAL, &AscenderHeight); GetKeyValue(fontinfo, "AscenderOvershoot", ACOPTIONAL, &AscenderOvershoot); GetKeyValue(fontinfo, "BaselineYCoord", !ORDINARYCOLORING, &BaselineYCoord); GetKeyValue(fontinfo, "BaselineOvershoot", !ORDINARYCOLORING, &BaselineOvershoot); GetKeyValue(fontinfo, "Baseline5", ACOPTIONAL, &Baseline5); GetKeyValue(fontinfo, "Baseline5Overshoot", ACOPTIONAL, &Baseline5Overshoot); GetKeyValue(fontinfo, "Baseline6", ACOPTIONAL, &Baseline6); GetKeyValue(fontinfo, "Baseline6Overshoot", ACOPTIONAL, &Baseline6Overshoot); GetKeyValue(fontinfo, "CapHeight", !ORDINARYCOLORING, &CapHeight); GetKeyValue(fontinfo, "CapOvershoot", !ORDINARYCOLORING, &CapOvershoot); GetKeyValue(fontinfo, "DescenderHeight", ACOPTIONAL, &DescenderHeight); GetKeyValue(fontinfo, "DescenderOvershoot", ACOPTIONAL, &DescenderOvershoot); GetKeyValue(fontinfo, "FigHeight", ACOPTIONAL, &FigHeight); GetKeyValue(fontinfo, "FigOvershoot", ACOPTIONAL, &FigOvershoot); GetKeyValue(fontinfo, "Height5", ACOPTIONAL, &Height5); GetKeyValue(fontinfo, "Height5Overshoot", ACOPTIONAL, &Height5Overshoot); GetKeyValue(fontinfo, "Height6", ACOPTIONAL, &Height6); GetKeyValue(fontinfo, "Height6Overshoot", ACOPTIONAL, &Height6Overshoot); GetKeyValue(fontinfo, "LcHeight", ACOPTIONAL, &LcHeight); GetKeyValue(fontinfo, "LcOvershoot", ACOPTIONAL, &LcOvershoot); GetKeyValue(fontinfo, "OrdinalBaseline", ACOPTIONAL, &OrdinalBaseline); GetKeyValue(fontinfo, "OrdinalOvershoot", ACOPTIONAL, &OrdinalOvershoot); GetKeyValue(fontinfo, "SuperiorBaseline", ACOPTIONAL, &SuperiorBaseline); GetKeyValue(fontinfo, "SuperiorOvershoot", ACOPTIONAL, &SuperiorOvershoot); gLenBotBands = gLenTopBands = 0; if (BaselineYCoord != UNDEFINED && BaselineOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(BaselineYCoord + BaselineOvershoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(BaselineYCoord)); } if (Baseline5 != UNDEFINED && Baseline5Overshoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(Baseline5 + Baseline5Overshoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(Baseline5)); } if (Baseline6 != UNDEFINED && Baseline6Overshoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(Baseline6 + Baseline6Overshoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(Baseline6)); } if (SuperiorBaseline != UNDEFINED && SuperiorOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(SuperiorBaseline + SuperiorOvershoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(SuperiorBaseline)); } if (OrdinalBaseline != UNDEFINED && OrdinalOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(OrdinalBaseline + OrdinalOvershoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(OrdinalBaseline)); } if (DescenderHeight != UNDEFINED && DescenderOvershoot != UNDEFINED) { gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(DescenderHeight + DescenderOvershoot)); gBotBands[gLenBotBands++] = ScaleAbs(fontinfo, FixInt(DescenderHeight)); } if (CapHeight != UNDEFINED && CapOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(CapHeight)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(CapHeight + CapOvershoot)); } if (LcHeight != UNDEFINED && LcOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(LcHeight)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(LcHeight + LcOvershoot)); } if (AscenderHeight != UNDEFINED && AscenderOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(AscenderHeight)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(AscenderHeight + AscenderOvershoot)); } if (FigHeight != UNDEFINED && FigOvershoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(FigHeight)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(FigHeight + FigOvershoot)); } if (Height5 != UNDEFINED && Height5Overshoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(Height5)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(Height5 + Height5Overshoot)); } if (Height6 != UNDEFINED && Height6Overshoot != UNDEFINED) { gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, FixInt(Height6)); gTopBands[gLenTopBands++] = ScaleAbs(fontinfo, 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 a NULL. Otherwise, returns the value string. */ char* GetFontInfo(const ACFontInfo* fontinfo, char* keyword, bool optional) { size_t i; if (!fontinfo) { LogMsg(LOGERROR, NONFATALERROR, "Fontinfo is NULL"); return NULL; } for (i = 0; i < fontinfo->length; i++) { if (fontinfo->entries[i].key && !strcmp(fontinfo->entries[i].key, keyword)) { return fontinfo->entries[i].value; } } if (!optional) { LogMsg(LOGERROR, NONFATALERROR, "ERROR: Fontinfo: Couldn't find fontinfo for %s\n", keyword); } return NULL; } /* * 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 (initline == NULL || 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) == EOF) break; if (count >= maxstems) { LogMsg(LOGERROR, NONFATALERROR, "Cannot have more than %d values in fontinfo array:\n %s\n", (int)maxstems, initline); } if (val < 1) { LogMsg(LOGERROR, NONFATALERROR, "Cannot have a value < 1 in fontinfo file array: \n %s\n", 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; } psautohint-1.1.0/libpsautohint/src/fontinfo.h000066400000000000000000000020141324015706300213540ustar00rootroot00000000000000/* * 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 ACOPTIONAL 1 #define MANDATORY 0 /* Default value used by PS interpreter and Adobe's fonts to extend the range * of alignment zones. */ #define DEFAULTBLUEFUZZ FixOne bool ReadFontInfo(const ACFontInfo* fontinfo); /* 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 a NULL. Otherwise, returns the value string. */ char* GetFontInfo(const ACFontInfo*, char*, bool); #endif /* AC_FONTINFO_H_ */ psautohint-1.1.0/libpsautohint/src/gen.c000066400000000000000000001021711324015706300203030ustar00rootroot00000000000000/* * 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 PSegLnkLst 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(PPathElt e, bool Hflg, PClrSeg seg) { PSegLnk newlnk; PSegLnkLst newlst, globlst; newlnk = (PSegLnk)Alloc(sizeof(SegLnk)); newlnk->seg = seg; newlst = (PSegLnkLst)Alloc(sizeof(SegLnkLst)); globlst = (PSegLnkLst)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(PPathElt e1, PPathElt e2, bool Hflg) { /* copy reference to first link from e1 to e2 */ PSegLnkLst newlst; newlst = (PSegLnkLst)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, PPathElt e1, PPathElt e2, bool Hflg, int32_t typ) { PClrSeg seg, segList, prevSeg; int32_t segNm; seg = (PClrSeg)Alloc(sizeof(ClrSeg)); 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); 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, PPathElt p1, PPathElt p2, int32_t typ, int32_t i) { if (gDebug) ReportAddVSeg(from, to, loc, i); if (gYgoesUp) AddSegment(from, to, loc, 0, 1, p1, p2, false, typ); else AddSegment(from, to, loc, 1, 0, p1, p2, false, typ); } void AddHSegment(Fixed from, Fixed to, Fixed loc, PPathElt p1, PPathElt p2, int32_t typ, int32_t i) { if (gDebug) ReportAddHSeg(from, to, 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 character 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); if (!gYgoesUp) { dy0 = -dy0; dy1 = -dy1; } ccw = (dx0 * dy1) >= (dx1 * dy0); return ccw; } static void DoHBendsNxt(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PPathElt p) { Fixed x2, y2, delta, strt, end, x3, y3; bool ysame, ccw, above, doboth; if (y0 == y1) return; (void)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))))) { delta = FixHalfMul(gBendLength); doboth = false; if ((x0 <= x1 && x1 < x2) || (x0 < x1 && x1 <= x2)) { } else if ((x2 < x1 && x1 <= x0) || (x2 <= x1 && x1 < x0)) delta = -delta; else if (ysame) { above = y0 > y1; if (!gYgoesUp) above = !above; 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, (PPathElt)NULL, sBEND, 0); if (doboth) AddHSegment(end, strt, y1, p, (PPathElt)NULL, sBEND, 1); } } static void DoHBendsPrv(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PPathElt p) { Fixed x2, y2, delta, strt, end; bool ysame, ccw, above, doboth; if (y0 == y1) return; (void)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))))) { delta = FixHalfMul(gBendLength); doboth = false; if ((x2 < x0 && x0 <= x1) || (x2 <= x0 && x0 < x1)) { } else if ((x1 < x0 && x0 <= x2) || (x1 <= x0 && x0 < x2)) delta = -delta; else if (ysame) { above = (y2 > y0); if (!gYgoesUp) above = !above; 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, (PPathElt)NULL, sBEND, 2); if (doboth) AddHSegment(end, strt, y0, p->prev, (PPathElt)NULL, sBEND, 3); } } static void DoVBendsNxt(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PPathElt p) { Fixed x2, y2, delta, strt, end, x3, y3; bool xsame, ccw, right, doboth; if (x0 == x1) return; (void)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))))) { delta = FixHalfMul(gBendLength); doboth = false; if ((y0 <= y1 && y1 < y2) || (y0 < y1 && y1 <= y2)) { } else if ((y2 < y1 && y1 <= y0) || (y2 <= y1 && y1 < y0)) delta = -delta; else if (xsame) { right = x0 > x1; ccw = IsCCW(x0, y0, x1, y1, x2, y2); if (right != ccw) delta = -delta; if (!gYgoesUp) delta = -delta; } else doboth = true; strt = y1 - delta; end = y1 + delta; AddVSegment(strt, end, x1, p, (PPathElt)NULL, sBEND, 0); if (doboth) AddVSegment(end, strt, x1, p, (PPathElt)NULL, sBEND, 1); } } static void DoVBendsPrv(Fixed x0, Fixed y0, Fixed x1, Fixed y1, PPathElt p) { Fixed x2, y2, delta, strt, end; bool xsame, ccw, right, doboth; if (x0 == x1) return; (void)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))))) { delta = FixHalfMul(gBendLength); doboth = false; if ((y2 < y0 && y0 <= y1) || (y2 <= y0 && y0 < y1)) { } else if ((y1 < y0 && y0 <= y2) || (y1 <= y0 && y0 < y2)) delta = -delta; else if (xsame) { right = x0 > x1; ccw = IsCCW(x2, y2, x0, y0, x1, y1); if (right != ccw) delta = -delta; if (!gYgoesUp) delta = -delta; } strt = y0 - delta; end = y0 + delta; AddVSegment(strt, end, x0, p->prev, (PPathElt)NULL, sBEND, 2); if (doboth) AddVSegment(end, strt, x0, p->prev, (PPathElt)NULL, sBEND, 3); } } static void MergeLnkSegs(PClrSeg seg1, PClrSeg seg2, PSegLnkLst lst) { /* replace lnk refs to seg1 by seg2 */ PSegLnk lnk; while (lst != NULL) { lnk = lst->lnk; if (lnk->seg == seg1) lnk->seg = seg2; lst = lst->next; } } static void MergeHSegs(PClrSeg seg1, PClrSeg seg2) { MergeLnkSegs(seg1, seg2, Hlnks); } static void MergeVSegs(PClrSeg seg1, PClrSeg seg2) { MergeLnkSegs(seg1, seg2, Vlnks); } static void ReportRemSeg(int32_t l, PClrSeg 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: ReportRemVSeg(from, to, loc); break; case 2: case 3: ReportRemHSeg(from, to, loc); break; } } /* Filters out bogus bend segments. */ static void RemExtraBends(int32_t l0, int32_t l1) { PClrSeg lst0, lst, n, p; PClrSeg nxt, prv; Fixed loc0, loc; lst0 = gSegLists[l0]; prv = NULL; while (lst0 != NULL) { nxt = lst0->sNxt; loc0 = lst0->sLoc; lst = gSegLists[l1]; p = NULL; while (lst != NULL) { n = lst->sNxt; 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; if (gDebug) 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; if (gDebug) ReportRemSeg(l1, lst); lst = p; } } p = lst; lst = n; } prv = lst0; lst0 = nxt; } } static void CompactList(int32_t i, void (*nm)(PClrSeg, PClrSeg)) { PClrSeg lst, prv, nxtprv, nxt; Fixed lstmin, lstmax, nxtmin, nxtmax; bool flg; lst = gSegLists[i]; prv = NULL; while (lst != NULL) { nxt = lst->sNxt; nxtprv = lst; while (true) { 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 character 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, PPathElt p) { Fixed x2, y2, x3, y3; p = NxtForBend(p, &x2, &y2, &x3, &y3); return TstFlat(y2 - y, x2 - x); } static bool PrvHorz(Fixed x, Fixed y, PPathElt p) { Fixed x2, y2; p = PrvForBend(p, &x2, &y2); return TstFlat(y2 - y, x2 - x); } static bool NxtVert(Fixed x, Fixed y, PPathElt p) { Fixed x2, y2, x3, y3; p = NxtForBend(p, &x2, &y2, &x3, &y3); return TstFlat(x2 - x, y2 - y); } static bool PrvVert(Fixed x, Fixed y, PPathElt p) { Fixed x2, y2; p = 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, PPathElt 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, PPathElt 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 specialCharType) { /* specialCharType 1 = upper; -1 = lower; 0 = neither */ PPathElt 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); (void)PrvForBend(p, &prvx, &prvy); (void)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, (PPathElt)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, (PPathElt)NULL, sCURVE, 8); } else AddVSegment(y1 - ydist, y1, x1, p, (PPathElt)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, (PPathElt)NULL, sCURVE, 10); } } } } else if (p->type == MOVETO) { gBonus = 0; if (specialCharType == -1) { if (IsLower(p)) gBonus = FixInt(200); } else if (specialCharType == 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); (void)PrvForBend(p, &prvx, &prvy); (void)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, (PPathElt)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 = itfmy(loc); /* Augment the blue band by bluefuzz in each direction. This will result in "near misses" being colored 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; Fixed upper, lower; 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) { if (y0 > y1) { upper = y0; lower = y1; } else { upper = y1; lower = y0; } if (!gYgoesUp) { Fixed tmp = lower; lower = upper; upper = tmp; } 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) { PPathElt 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); (void)PrvForBend(p, &prvx, &prvy); (void)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, (PPathElt)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, (PPathElt)NULL, sCURVE, 8); } else AddHSegment(x1 - xdist, x1, y1, p, (PPathElt)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, (PPathElt)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); (void)PrvForBend(p, &prvx, &prvy); (void)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-1.1.0/libpsautohint/src/head.c000066400000000000000000000162161324015706300204370ustar00rootroot00000000000000/* * 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" PPathElt GetDest(PPathElt cldest) { if (cldest == NULL) return NULL; while (true) { cldest = cldest->prev; if (cldest == NULL) return gPathStart; if (cldest->type == MOVETO) return cldest; } } PPathElt GetClosedBy(PPathElt 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(PPathElt e, Fixed* x1p, Fixed* y1p) { if (e == NULL) { *x1p = 0; *y1p = 0; return; } retry: 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 character description file: %s.\n", gGlyphName); } goto retry; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal operator in character file: %s.\n", gGlyphName); } } } void GetEndPoints(PPathElt 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 < .25) result = (float)Interpolate(q, 1.0, 0.0, .841, .25); else if (q < .5) result = (float)Interpolate(q, .841, .25, .707, .5); else if (q < 1) result = (float)Interpolate(q, .707, .5, .5, 1.0); else if (q < 2) result = (float)Interpolate(q, .5, 1.0, .25, 2.0); else if (q < 4) result = (float)Interpolate(q, .25, 2.0, 0.0, 4.0); 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(PPathElt 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(PPathElt 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. */ } PPathElt NxtForBend(PPathElt p, Fixed* px2, Fixed* py2, Fixed* px3, Fixed* py3) { PPathElt nxt, nxtMT = NULL; Fixed x = 0, y = 0, x2 = 0, y2 = 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) { x2 = nxt->x1; 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; } PPathElt PrvForBend(PPathElt p, Fixed* px2, Fixed* py2) { PPathElt 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, PPathElt p) { PPathElt ee; Fixed y, yy; ee = gPathStart; y = itfmy(p->y); while (ee != NULL) { if (ee->type == MOVETO && ee != p) { yy = itfmy(ee->y); if ((upperFlag && yy > y) || (!upperFlag && yy < y)) return false; } ee = ee->next; } return true; } bool IsLower(PPathElt p) { return CheckHeight(false, p); } bool IsUpper(PPathElt p) { return CheckHeight(true, p); } psautohint-1.1.0/libpsautohint/src/logging.c000066400000000000000000000062371324015706300211660ustar00rootroot00000000000000/* * 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" /* proc to be called from LogMsg if error occurs */ static int (*errorproc)(int16_t); /* used for cacheing of log messages */ static char lastLogStr[MAXMSGLEN + 1] = ""; static int16_t lastLogLevel = -1; static int logCount = 0; static void LogMsg1(char* str, int16_t level, int16_t code); #define Write(s) \ { \ if (gLibReportCB != NULL) \ gLibReportCB(s); \ } #define WriteWarnorErr(f, s) \ { \ if (gLibErrorReportCB != NULL) \ gLibErrorReportCB(s); \ } void set_errorproc(int (*userproc)(int16_t)) { errorproc = userproc; } /* called by LogMsg and when exiting (tidyup) */ static void FlushLogMsg(void) { /* if message happened exactly 2 times, don't treat it specially */ if (logCount == 1) { LogMsg1(lastLogStr, lastLogLevel, OK); } else if (logCount > 1) { char newStr[MAXMSGLEN + 1]; snprintf(newStr, MAXMSGLEN, "The last message (%.20s...) repeated %d more times.\n", lastLogStr, logCount); LogMsg1(newStr, lastLogLevel, OK); } logCount = 0; } void LogMsg(int16_t level, /* error, warning, info */ int16_t code, /* exit value - if !OK, this proc will not return */ char* format, /* message string */ ...) { char str[MAXMSGLEN + 1]; va_list va; va_start(va, format); vsnprintf(str, MAXMSGLEN, format, va); va_end(va); if (!strcmp(str, lastLogStr) && level == lastLogLevel) { ++logCount; /* same message */ } else { /* new message */ if (logCount) /* messages pending */ FlushLogMsg(); LogMsg1(str, level, code); /* won't return if LOGERROR */ strncpy(lastLogStr, str, MAXMSGLEN); lastLogLevel = level; } } static void LogMsg1(char* str, int16_t level, int16_t code) { switch (level) { case INFO: Write(str); Write("\n"); break; case WARNING: WriteWarnorErr(stderr, "WARNING: "); WriteWarnorErr(stderr, str); break; case LOGERROR: WriteWarnorErr(stderr, "ERROR: "); WriteWarnorErr(stderr, str); break; default: WriteWarnorErr(stderr, "ERROR - log level not recognized: "); WriteWarnorErr(stderr, str); break; } if (level == LOGERROR && (code == NONFATALERROR || code == FATALERROR)) { (*errorproc)(code); } } psautohint-1.1.0/libpsautohint/src/logging.h000066400000000000000000000012521324015706300211630ustar00rootroot00000000000000/* * 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 "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 INFO 0 #define WARNING 1 #define LOGERROR 2 /* maximum message length */ #define MAXMSGLEN 500 void LogMsg(int16_t, int16_t, char *, ...); void set_errorproc( int (*)(int16_t) ); #endif /* BF_LOGGING_H_ */ psautohint-1.1.0/libpsautohint/src/memory.c000066400000000000000000000035151324015706300210440ustar00rootroot00000000000000/* * 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, NONFATALERROR, "Cannot allocate %d bytes of memory for %s.\n", (int)(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, NONFATALERROR, "Cannot allocate %d bytes of memory for %s.\n", (int)size, description); } return (newptr); } void UnallocateMem(void* ptr) { /* free(ptr) */ AC_memmanageFuncPtr(AC_memmanageCtxPtr, ptr, 0); } psautohint-1.1.0/libpsautohint/src/memory.h000066400000000000000000000011011324015706300210360ustar00rootroot00000000000000/* * 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-1.1.0/libpsautohint/src/merge.c000066400000000000000000000423001324015706300206260ustar00rootroot00000000000000/* * 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(PPathElt e1, PPathElt e2, Fixed loc1, Fixed loc2, bool vert) { Fixed tmp; Fixed x, y; PPathElt 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(PClrSeg s1, PClrSeg s2, bool vert) { /* true if the elements for these segs are "close" in the path */ PPathElt e1, e2; Fixed loc1, loc2; 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. */ PClrVal 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 PClrVal PruneOne(PClrVal sLst, bool hFlg, PClrVal 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 PruneLt(val, v) \ (((v) < (FixedPosInf / 10) && (val) < (FixedPosInf / PRNFCTR)) \ ? ((val)*PRNFCTR < (v)*10) \ : ((val) / 10 < (v) / PRNFCTR)) #define PruneLe(val, v) \ (((val) < (FixedPosInf / PRNFCTR)) ? ((v) <= (val)*PRNFCTR) \ : ((v) / PRNFCTR <= (val))) #define PruneGt(val, v) \ (((val) < (FixedPosInf / PRNFCTR)) ? ((v) > (val)*PRNFCTR) \ : ((v) / PRNFCTR > (val))) #define MUCHFCTR (50) #define PruneMuchGt(val, v) \ (((val) < (FixedPosInf / MUCHFCTR)) ? ((v) > (val)*MUCHFCTR) \ : ((v) / MUCHFCTR > (val))) #define VERYMUCHFCTR (100) #define PruneVeryMuchGt(val, v) \ (((val) < (FixedPosInf / VERYMUCHFCTR)) ? ((v) > (val)*VERYMUCHFCTR) \ : ((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 colored. */ void PruneVVals(void) { PClrVal sLst, sL; PClrSeg 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) { PClrVal sLst, sL; PClrSeg 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 */ ((gYgoesUp && top + prndist >= t && bot - prndist <= b) || (!gYgoesUp && 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(PClrVal vL) { Fixed bV, bS; Fixed t, b; PClrVal 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 coloring and extra newcolors. */ static void ReplaceVals(Fixed oldB, Fixed oldT, Fixed newB, Fixed newT, PClrVal newBst, bool vert) { PClrVal vL; for (vL = gValList; vL != NULL; vL = vL->vNxt) { if (vL->vLoc1 != oldB || vL->vLoc2 != oldT || vL->merge) continue; if (gShowClrInfo) { 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) { PClrVal vLst, vL; PClrVal bstV, bV; PClrSeg 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) { } 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 ((gYgoesUp && t > top) || (!gYgoesUp && t < top)) goto replace; } else if (InBlueBand(b, gLenBotBands, gBotBands)) { if ((gYgoesUp && b < bot) || (!gYgoesUp && 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-1.1.0/libpsautohint/src/misc.c000066400000000000000000000175221324015706300204720ustar00rootroot00000000000000/* * 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) { PPathElt e = gPathStart; int32_t cnt = 0; while (e != NULL) { if (e->type == MOVETO) cnt++; e = e->next; } return cnt; } void RoundPathCoords(void) { PPathElt 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 CheckForClr(void) { PPathElt 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 PreCheckForColoring(void) { PPathElt e, nxt; int32_t cnt = 0; int32_t chk; 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) { 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) { chk = CheckForClr(); if (chk == -1) return false; if (chk == 0) break; if (++cnt > 10) { LogMsg(WARNING, OK, "Looping in PreCheckForHints!\n"); break; } } return true; } static PPathElt GetSubpathNext(PPathElt e) { while (true) { e = e->next; if (e == NULL) break; if (e->type == CLOSEPATH) break; if (!IsTiny(e)) break; } return e; } static PPathElt GetSubpathPrev(PPathElt 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(PPathElt e, bool yflag) { PPathElt e0 = e, e1 = e->next; if (e0->type != CURVETO || e1->type != CURVETO) { LogMsg(LOGERROR, NONFATALERROR, "Illegal input in glyph: %s.\n", gGlyphName); } /* 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(PPathElt e, PPathElt n, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { Fixed x2, y2, x3, y3, x4, y4; PPathElt p, q; bool top, dwn; 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) { 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; if (gYgoesUp) dwn = (y1 < y0) ? true : false; else 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(PPathElt e, PPathElt n, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { Fixed x2, y2, x3, y3, x4, y4; PPathElt p, q; bool lft; 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) { 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 */ if (gYgoesUp) lft = (y0 > y2) ? true : false; else 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) { PPathElt 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-1.1.0/libpsautohint/src/opcodes.h000066400000000000000000000134641324015706300212010ustar00rootroot00000000000000/* * 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 character. 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 coloring */ #define RB 1 /* y dy RB -- add horizontal coloring 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 coloring 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 coloring pairs */ #define RV 2 /* y0 dy0 y1 dy1 y2 dy2 RV -- add 3 equal spaced horizontal coloring 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) */ psautohint-1.1.0/libpsautohint/src/pick.c000066400000000000000000000311431324015706300204600ustar00rootroot00000000000000/* * 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 PClrVal 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, PClrVal colorList, Fixed prevBestVal) { if (bestSpc > 0) return true; if (colorList == NULL) return bestVal >= gPruneD; if (bestVal > gPruneA) return true; if (LtPruneB(bestVal)) return false; return (bestVal < FixedPosInf / gPruneC) ? (prevBestVal <= bestVal * gPruneC) : (prevBestVal / gPruneC <= bestVal); } void PickVVals(PClrVal valList) { PClrVal colorList, rejectList, vlist, prev, best, bestPrev, nxt; Fixed bestVal, prevBestVal; Fixed lft, rght, vlft, vrght; colorList = 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, colorList, 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 = colorList; /* add best to front of list */ colorList = 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) { vlft = vlist->vLoc1; 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 (colorList == NULL) ClrVBnds(); gVColoring = colorList; Vrejects = rejectList; } static bool InSerifBand(Fixed y0, Fixed y1, int32_t n, Fixed* p) { int32_t i; if (n <= 0) return false; y0 = itfmy(y0); y1 = itfmy(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(PClrVal val, PClrSeg 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 PClrVal FndBstVal(PClrSeg seg, bool seg1Flg, PClrVal cList, PClrVal rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool locFlg, bool hFlg) { Fixed loc, vloc; PClrVal best, vList, initLst; PClrSeg vseg; best = NULL; loc = seg->sLoc; vList = cList; while (true) { 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; } if (gShowClrInfo) ReportFndBstVal(seg, best, hFlg); return best; } #define FixSixteenth (0x10) static PClrVal FindBestValForSeg(PClrSeg seg, bool seg1Flg, PClrVal cList, PClrVal rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool hFlg) { PClrVal 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(PClrVal val, PClrVal vList) { while (vList != NULL) { if (val == vList) return true; vList = vList->vNxt; } return false; } static PClrVal PrevVal(PClrVal val, PClrVal vList) { PClrVal prev; if (val == vList) return NULL; prev = vList; while (true) { vList = vList->vNxt; if (vList == NULL) { LogMsg(LOGERROR, NONFATALERROR, "Malformed value list in %s.\n", gGlyphName); } if (vList == val) return prev; prev = vList; } } static void FindRealVal(PClrVal vlist, Fixed top, Fixed bot, PClrSeg* pseg1, PClrSeg* 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(PClrVal valList) { PClrVal vlist, colorList, rejectList, bestPrev, prev, best, nxt; Fixed bestVal, prevBestVal; Fixed bot, top, vtop, vbot; PClrVal newBst; PClrSeg seg1, seg2; colorList = 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, colorList, 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, (PClrVal)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, (PClrVal)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 = colorList; colorList = 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; } if (gYgoesUp) { bot -= gBandMargin; top += gBandMargin; } else { 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 ((gYgoesUp && (vbot <= top) && (vtop >= bot)) || ((!gYgoesUp && (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 (colorList == NULL) ClrHBnds(); gHColoring = colorList; Hrejects = rejectList; } static void FindBestValForSegs(PClrSeg sList, bool seg1Flg, PClrVal cList, PClrVal rList, int32_t nb, Fixed* b, int32_t ns, Fixed* s, bool hFlg) { PClrVal 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) { PClrVal vL = gValList; while (vL != NULL) { vL->pruned = true; vL = vL->vNxt; } } void FindBestHVals(void) { SetPruned(); FindBestValForSegs(topList, false, gValList, NULL, gLenTopBands, gTopBands, 0, (Fixed*)NULL, true); FindBestValForSegs(botList, true, gValList, NULL, gLenBotBands, gBotBands, 0, (Fixed*)NULL, true); DoPrune(); } void FindBestVVals(void) { SetPruned(); FindBestValForSegs(leftList, true, gValList, NULL, 0, (Fixed*)NULL, gNumSerifs, gSerifs, false); FindBestValForSegs(rightList, false, gValList, NULL, 0, (Fixed*)NULL, gNumSerifs, gSerifs, false); DoPrune(); } psautohint-1.1.0/libpsautohint/src/psautohint.c000066400000000000000000000245071324015706300217360ustar00rootroot00000000000000/* * 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 "setjmp.h" #include "ac.h" #include "psautohint.h" static const char* libversion = "1.6.0"; ACBuffer* gBezOutput = NULL; bool gScalingHints = false; static jmp_buf aclibmark; /* to handle exit() calls in the library version*/ #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); } static void FreeFontInfo(ACFontInfo* fontinfo) { size_t i; if (!fontinfo) return; for (i = 0; i < fontinfo->length; i++) { if (fontinfo->entries[i].value[0]) { UnallocateMem(fontinfo->entries[i].value); } } UnallocateMem(fontinfo->entries); UnallocateMem(fontinfo); } static ACFontInfo* NewFontInfo(size_t length) { ACFontInfo* fontinfo; if (length <= 0) return NULL; fontinfo = (ACFontInfo*)AllocateMem(1, sizeof(ACFontInfo), "fontinfo"); if (!fontinfo) return NULL; fontinfo->entries = (FFEntry*)AllocateMem(length, sizeof(FFEntry), "fontinfo entry"); if (!fontinfo->entries) { UnallocateMem(fontinfo); return NULL; } fontinfo->length = length; return fontinfo; } static ACBuffer* NewBuffer(size_t size) { ACBuffer* buffer; if (size <= 0) return NULL; buffer = (ACBuffer*)AllocateMem(1, sizeof(ACBuffer), "out buffer"); if (!buffer) return NULL; buffer->data = AllocateMem(size, 1, "out buffer data"); if (!buffer->data) { UnallocateMem(buffer); return NULL; } buffer->data[0] = '\0'; buffer->capacity = size; buffer->length = 0; return buffer; } static void FreeBuffer(ACBuffer* buffer) { if (!buffer) return; UnallocateMem(buffer->data); UnallocateMem(buffer); } static int ParseFontInfo(const char* data, ACFontInfo** fontinfo) { const char *kwstart, *kwend, *tkstart, *current; size_t i; ACFontInfo* info = *fontinfo = NewFontInfo(34); if (!info) return AC_MemoryError; info->entries[0].key = "OrigEmSqUnits"; info->entries[1].key = "FontName"; info->entries[2].key = "FlexOK"; /* Blue Values */ info->entries[3].key = "BaselineOvershoot"; info->entries[4].key = "BaselineYCoord"; info->entries[5].key = "CapHeight"; info->entries[6].key = "CapOvershoot"; info->entries[7].key = "LcHeight"; info->entries[8].key = "LcOvershoot"; info->entries[9].key = "AscenderHeight"; info->entries[10].key = "AscenderOvershoot"; info->entries[11].key = "FigHeight"; info->entries[12].key = "FigOvershoot"; info->entries[13].key = "Height5"; info->entries[14].key = "Height5Overshoot"; info->entries[15].key = "Height6"; info->entries[16].key = "Height6Overshoot"; /* Other Values */ info->entries[17].key = "Baseline5Overshoot"; info->entries[18].key = "Baseline5"; info->entries[19].key = "Baseline6Overshoot"; info->entries[20].key = "Baseline6"; info->entries[21].key = "SuperiorOvershoot"; info->entries[22].key = "SuperiorBaseline"; info->entries[23].key = "OrdinalOvershoot"; info->entries[24].key = "OrdinalBaseline"; info->entries[25].key = "DescenderOvershoot"; info->entries[26].key = "DescenderHeight"; info->entries[27].key = "DominantV"; info->entries[28].key = "StemSnapV"; info->entries[29].key = "DominantH"; info->entries[30].key = "StemSnapH"; info->entries[31].key = "VCounterChars"; info->entries[32].key = "HCounterChars"; /* later addenda */ info->entries[33].key = "BlueFuzz"; for (i = 0; i < info->length; i++) { info->entries[i].value = ""; } if (!data) return AC_Success; current = data; while (*current) { size_t kwLen; 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->entries[i].key)); if (!strncmp(info->entries[i].key, kwstart, matchLen)) { info->entries[i].value = AllocateMem(current - tkstart + 1, 1, "fontinfo entry value"); if (!info->entries[i].value) { FreeFontInfo(info); return AC_MemoryError; } strncpy(info->entries[i].value, tkstart, current - tkstart); info->entries[i].value[current - tkstart] = '\0'; break; } } if (i == info->length) { char* temp; temp = AllocateMem(tkstart - kwstart + 1, 1, "no idea!"); if (!temp) { FreeFontInfo(info); return AC_MemoryError; } strncpy(temp, kwstart, tkstart - kwstart); temp[tkstart - kwstart] = '\0'; /*fprintf(stderr, "Ignoring fileinfo %s...\n", temp);*/ UnallocateMem(temp); } skipblanks(); } return AC_Success; } ACLIB_API void AC_SetMemManager(void* ctxptr, AC_MEMMANAGEFUNCPTR func) { setAC_memoryManager(ctxptr, func); } ACLIB_API void AC_SetReportCB(AC_REPORTFUNCPTR reportCB, int verbose) { if (verbose) gLibReportCB = reportCB; else gLibReportCB = NULL; gLibErrorReportCB = reportCB; } ACLIB_API void AC_SetReportStemsCB(AC_REPORTSTEMPTR hstemCB, AC_REPORTSTEMPTR vstemCB, unsigned int allStems) { gAllStems = allStems; gAddHStemCB = hstemCB; gAddVStemCB = vstemCB; gDoStems = true; gAddCharExtremesCB = NULL; gAddStemExtremesCB = NULL; gDoAligns = false; } ACLIB_API void AC_SetReportZonesCB(AC_REPORTZONEPTR charCB, AC_REPORTZONEPTR stemCB) { gAddCharExtremesCB = charCB; gAddStemExtremesCB = stemCB; gDoAligns = true; gAddHStemCB = NULL; gAddVStemCB = NULL; gDoStems = false; } ACLIB_API void AC_SetReportRetryCB(AC_RETRYPTR retryCB) { gReportRetryCB = retryCB; } /* * 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 AutoColorString(). */ 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 AutoColorString(const char* srcbezdata, const char* fontinfodata, char* dstbezdata, size_t* length, int allowEdit, int allowHintSub, int roundCoords, int debug) { int value, result; ACFontInfo* fontinfo = NULL; if (!srcbezdata) return AC_InvalidParameterError; if (ParseFontInfo(fontinfodata, &fontinfo)) return AC_FontinfoParseFail; set_errorproc(error_handler); value = setjmp(aclibmark); /* We will return here whenever an error occurs during the execution of * AutoColor(), 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) { /* AutoColor was called successfully */ FreeFontInfo(fontinfo); if (gBezOutput->length < *length) { *length = gBezOutput->length + 1; strncpy(dstbezdata, gBezOutput->data, *length); FreeBuffer(gBezOutput); return AC_Success; } else { *length = gBezOutput->length + 1; FreeBuffer(gBezOutput); return AC_DestBuffOfloError; } } gBezOutput = NewBuffer(*length); if (!gBezOutput) { FreeFontInfo(fontinfo); return AC_MemoryError; } result = AutoColor(fontinfo, /* font info */ srcbezdata, /* input glyph */ false, /* fixStems */ debug, /* debug */ allowHintSub, /* extracolor*/ allowEdit, /* editChars */ 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 void AC_initCallGlobals(void) { gLibReportCB = NULL; gLibErrorReportCB = NULL; gAddCharExtremesCB = NULL; gAddStemExtremesCB = NULL; gDoAligns = false; gAddHStemCB = NULL; gAddVStemCB = NULL; gDoStems = false; } ACLIB_API const char* AC_getVersion(void) { return libversion; } psautohint-1.1.0/libpsautohint/src/read.c000066400000000000000000000465531324015706300204600ustar00rootroot00000000000000/* * 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 "fontinfo.h" #include "opcodes.h" #define ESCVAL 100 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 PCharPathElt structure instead of PPathElt. */ static float origEmSquare = 0.0; Fixed ScaleAbs(const ACFontInfo* fontinfo, Fixed unscaled) { Fixed temp1; if (!gScalingHints) return unscaled; if (origEmSquare == 0.0) { char* fistr = GetFontInfo(fontinfo, "OrigEmSqUnits", ACOPTIONAL); if (fistr) origEmSquare = strtod(fistr, NULL); else origEmSquare = 1000.0; } temp1 = (Fixed)(1000.0 / origEmSquare * ((float)unscaled)); return temp1; } Fixed UnScaleAbs(const ACFontInfo* fontinfo, Fixed scaled) { Fixed temp1; if (!gScalingHints) return scaled; if (origEmSquare == 0.0) { char* fistr = GetFontInfo(fontinfo, "OrigEmSqUnits", ACOPTIONAL); if (fistr) origEmSquare = strtod(fistr, NULL); else origEmSquare = 1000.0; } temp1 = (Fixed)(origEmSquare / 1000.0 * ((float)scaled)); temp1 = FRnd(temp1); return (temp1); } static Fixed Pop(void) { if (stkindex <= 0) { LogMsg(LOGERROR, NONFATALERROR, "Stack underflow while reading %s glyph.\n", gGlyphName); } stkindex--; return stk[stkindex]; } static void Push(Fixed r) { if (stkindex >= STKMAX) { LogMsg(LOGERROR, NONFATALERROR, "Stack underflow while reading %s glyph.\n", gGlyphName); } stk[stkindex] = r; stkindex++; } static void Pop2(void) { (void)Pop(); (void)Pop(); } static void PopPCd(Cd* pcd) { pcd->y = Pop(); pcd->x = Pop(); } #define DoDelta(dx, dy) \ currentx += (dx); \ currenty += (dy) static PPathElt AppendElement(int32_t etype) { PPathElt e; e = (PPathElt)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 psDIV(void) { Fixed x, y; y = Pop(); x = Pop(); if (y == FixInt(100)) x /= 100; /* this will usually be the case */ else x = (x * FixOne) / y; Push(x); } static void RDcurveto(const ACFontInfo* fontinfo, Cd c1, Cd c2, Cd c3) { if (!forMultiMaster) { PPathElt new; new = AppendElement(CURVETO); new->x1 = tfmx(ScaleAbs(fontinfo, c1.x)); new->y1 = tfmy(ScaleAbs(fontinfo, c1.y)); new->x2 = tfmx(ScaleAbs(fontinfo, c2.x)); new->y2 = tfmy(ScaleAbs(fontinfo, c2.y)); new->x3 = tfmx(ScaleAbs(fontinfo, c3.x)); new->y3 = tfmy(ScaleAbs(fontinfo, c3.y)); } else { PCharPathElt new; new = AppendCharPathElement(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(const ACFontInfo* fontinfo, int32_t etype) { if (!forMultiMaster) { PPathElt new; new = AppendElement(etype); new->x = tfmx(ScaleAbs(fontinfo, currentx)); new->y = tfmy(ScaleAbs(fontinfo, currenty)); return; } else { PCharPathElt new; new = AppendCharPathElement(etype == LINETO ? RDT : RMT); new->x = currentx; new->y = currenty; new->rx = tempx; new->ry = tempy; } } #define RDlineto() RDmtlt(fontinfo, LINETO) #define RDmoveto() RDmtlt(fontinfo, MOVETO) static void psRDT(const ACFontInfo* fontinfo) { Cd c; PopPCd(&c); tempx = c.x; tempy = c.y; DoDelta(c.x, c.y); RDlineto(); } static void psHDT(const ACFontInfo* fontinfo) { Fixed dx; tempy = 0; dx = tempx = Pop(); currentx += dx; RDlineto(); } static void psVDT(const ACFontInfo* fontinfo) { Fixed dy; tempx = 0; dy = tempy = Pop(); currenty += dy; RDlineto(); } static void psRMT(const ACFontInfo* fontinfo) { Cd c; PopPCd(&c); if (flex) return; tempx = c.x; tempy = c.y; DoDelta(c.x, c.y); RDmoveto(); } static void psHMT(const ACFontInfo* fontinfo) { Fixed dx; tempy = 0; dx = tempx = Pop(); currentx += dx; RDmoveto(); } static void psVMT(const ACFontInfo* fontinfo) { Fixed dy; tempx = 0; dy = tempy = Pop(); currenty += dy; RDmoveto(); } static void Rct(const ACFontInfo* fontinfo, 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(fontinfo, c1, c2, c3); } static void psRCT(const ACFontInfo* fontinfo) { Cd c1, c2, c3; PopPCd(&c3); PopPCd(&c2); PopPCd(&c1); Rct(fontinfo, c1, c2, c3); } static void psVHCT(const ACFontInfo* fontinfo) { Cd c1, c2, c3; c3.y = 0; c3.x = Pop(); PopPCd(&c2); c1.y = Pop(); c1.x = 0; Rct(fontinfo, c1, c2, c3); } static void psHVCT(const ACFontInfo* fontinfo) { Cd c1, c2, c3; c3.y = Pop(); c3.x = 0; PopPCd(&c2); c1.y = 0; c1.x = Pop(); Rct(fontinfo, c1, c2, c3); } static void psCP(void) { if (!forMultiMaster) AppendElement(CLOSEPATH); else AppendCharPathElement(CP); } static void psMT(const ACFontInfo* fontinfo) { 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(const ACFontInfo* fontinfo) { 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(const ACFontInfo* fontinfo) { Cd c1, c2, c3; tempx = currentx; tempy = currenty; PopPCd(&c3); PopPCd(&c2); PopPCd(&c1); RDcurveto(fontinfo, c1, c2, c3); } static void psFLX(const ACFontInfo* fontinfo) { Cd c0, c1, c2, c3, c4, c5; int32_t i; for (i = 0; i < 5; i++) (void)Pop(); PopPCd(&c5); PopPCd(&c4); PopPCd(&c3); PopPCd(&c2); PopPCd(&c1); PopPCd(&c0); Rct(fontinfo, c0, c1, c2); Rct(fontinfo, 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\n glyph: %s. Please re-hint using " "the latest software.\n Hints will not be included " "in this font.\n", gGlyphName); SetNoHints(); 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 ACFontInfo* fontinfo, 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(fontinfo); break; case 'p': psCP(); break; default: goto badFile; } break; case 'm': /* mt */ if (nm[1] != 't') goto badFile; psMT(fontinfo); break; case 'd': /* dt */ if (nm[1] != 't') goto badFile; psDT(fontinfo); 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; case 'i': /* id */ if (nm[1] != 'd') goto badFile; Pop(); gIdInFile = true; break; default: goto badFile; } break; case 3: switch (nm[0]) { case 'r': /* rdt, rmt, rct */ if (nm[2] != 't') goto badFile; switch (nm[1]) { case 'd': psRDT(fontinfo); break; case 'm': psRMT(fontinfo); break; case 'c': psRCT(fontinfo); break; default: goto badFile; } break; case 'h': /* hdt, hmt */ if (nm[2] != 't') goto badFile; switch (nm[1]) { case 'd': psHDT(fontinfo); break; case 'm': psHMT(fontinfo); break; default: goto badFile; } break; case 'v': /* vdt, vmt */ if (nm[2] != 't') goto badFile; switch (nm[1]) { case 'd': psVDT(fontinfo); break; case 'm': psVMT(fontinfo); break; default: goto badFile; } break; case 's': /* sol, snc */ case 'e': /* eol, enc */ switch (nm[1]) { case 'o': if (nm[2] != 'l') goto badFile; break; case 'n': if (nm[2] != 'c') goto badFile; break; default: goto badFile; } break; case 'f': /* flx */ if (nm[1] == 'l' && nm[2] == 'x') psFLX(fontinfo); else goto badFile; break; case 'd': /* div */ if (nm[1] == 'i' && nm[2] == 'v') psDIV(); else goto badFile; break; default: goto badFile; } break; case 4: if (nm[2] != 'c' || nm[3] != 't') goto badFile; switch (nm[0]) { case 'v': /* vhct */ if (nm[1] != 'h') goto badFile; psVHCT(fontinfo); break; case 'h': /* hvct */ if (nm[1] != 'v') goto badFile; psHVCT(fontinfo); 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': /* newcolors */ 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 in %s character.\n", op, gGlyphName); } } static void ParseString(const ACFontInfo* fontinfo, const char* s) { const char* s0; char c; 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] != '\r') && (s[end] != '\n')) end++; strncpy(gGlyphName, s, end); if (end < MAX_GLYPHNAME_LEN) gGlyphName[end] = '\0'; else { gGlyphName[MAX_GLYPHNAME_LEN - 1] = '\0'; LogMsg(LOGERROR, NONFATALERROR, "Bad input data. Glyph name %s is " "greater than %d chars.\n", gGlyphName, MAX_GLYPHNAME_LEN); } } while ((*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 %s file.\n", gGlyphName); } 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(fontinfo, s0, s, s - s0 - 1); if (c == '\0') s--; continue; } LogMsg(LOGERROR, NONFATALERROR, "Unexpected character in %s glyph.\n", gGlyphName); } rdnum: isReal = false; c0 = (char*)(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); rval = roundf(rval * 100) / 100; // Autohint can only // support 2 digits of // decimal precision. /* 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); */ if (stkindex >= STKMAX) { LogMsg(LOGERROR, NONFATALERROR, "Stack overflow while reading %s glyph.\n", gGlyphName); return; } stk[stkindex] = r; stkindex++; goto nxtChar; } else { LogMsg(LOGERROR, NONFATALERROR, "Illegal number terminator while reading %s glyph.\n", gGlyphName); return; } } /*end while true */ } } bool ReadGlyph(const ACFontInfo* fontinfo, const char* srcglyph, bool forBlendData, bool readHints) { if (!srcglyph) return false; currentx = currenty = tempx = tempy = stkindex = 0; flex = gIdInFile = startchar = false; forMultiMaster = forBlendData; includeHints = readHints; ParseString(fontinfo, srcglyph); return true; } psautohint-1.1.0/libpsautohint/src/report.c000066400000000000000000000371611324015706300210530ustar00rootroot00000000000000/* * 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 r; } void PrintMessage(char* format, ...) { if ((gLibReportCB != NULL) && (strlen(format) > 0)) { char msgBuffer[MAXMSGLEN + 1]; va_list va; msgBuffer[0] = '\t'; va_start(va, format); vsnprintf(msgBuffer + 1, MAXMSGLEN - 1, format, va); va_end(va); gLibReportCB(msgBuffer); gLibReportCB("\n"); } } void ReportError(char* format, ...) { if (gReportErrors) { char msgBuffer[MAXMSGLEN + 1]; va_list va; va_start(va, format); vsnprintf(msgBuffer, MAXMSGLEN, format, va); va_end(va); PrintMessage(msgBuffer); } } void ReportSmoothError(Fixed x, Fixed y) { ReportError("Junction at %g %g may need smoothing.", FixToDbl(itfmx(x)), FixToDbl(itfmy(y))); } void ReportAddFlex(void) { if (gHasFlex) return; gHasFlex = true; PrintMessage("FYI: added flex operators to this character."); } void ReportClipSharpAngle(Fixed x, Fixed y) { PrintMessage("FYI: Too sharp angle at %g %g has been clipped.", FixToDbl(itfmx(x)), FixToDbl(itfmy(y))); } void ReportSharpAngle(Fixed x, Fixed y) { PrintMessage("FYI: angle at %g %g is very sharp. Please check.", FixToDbl(itfmx(x)), FixToDbl(itfmy(y))); } void ReportLinearCurve(PPathElt e, Fixed x0, Fixed y0, Fixed x1, Fixed y1) { if (gAutoLinearCurveFix) { e->type = LINETO; e->x = e->x3; e->y = e->y3; PrintMessage("Curve from %g %g to %g %g was changed to a line.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x1)), FixToDbl(itfmy(y1))); } else { PrintMessage("Curve from %g %g to %g %g should be changed to a line.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x1)), FixToDbl(itfmy(y1))); } } static void ReportNonHVError(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char* s) { Fixed dx, dy; x0 = itfmx(x0); y0 = itfmy(y0); x1 = itfmx(x1); y1 = itfmy(y1); dx = x0 - x1; dy = y0 - y1; if (abs(dx) > FixInt(10) || abs(dy) > FixInt(10) || FTrunc(dx * dx) + FTrunc(dy * dy) > FixInt(100)) { ReportError("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(PPathElt 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.\n"); return; } LogMsg(LOGERROR, NONFATALERROR, "Path for %s character has a %s where a moveto was " "expected.\n The data are probably truncated.", gGlyphName, s); } void ReportMissingClosePath(void) { LogMsg(LOGERROR, NONFATALERROR, "Missing closepath in %s character.\n" " The data are probably truncated.", gGlyphName); } void ReportTryFlexNearMiss(Fixed x0, Fixed y0, Fixed x2, Fixed y2) { ReportError("Curves from %g %g to %g %g near miss for adding flex.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x2)), FixToDbl(itfmy(y2))); } void ReportTryFlexError(bool CPflg, Fixed x, Fixed y) { ReportError( CPflg ? "Please move closepath from %g %g so can add flex." : "Please remove zero length element at %g %g so can add flex.", FixToDbl(itfmx(x)), FixToDbl(itfmy(y))); } void ReportSplit(PPathElt e) { Fixed x0, y0, x1, y1; GetEndPoints(e, &x0, &y0, &x1, &y1); PrintMessage( "FYI: the element that goes from %g %g to %g %g has been split.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x1)), FixToDbl(itfmy(y1))); } void AskForSplit(PPathElt e) { Fixed x0, y0, x1, y1; if (e->type == MOVETO) e = GetClosedBy(e); GetEndPoints(e, &x0, &y0, &x1, &y1); ReportError("Please split the element that goes from %g %g to %g %g.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x1)), FixToDbl(itfmy(y1))); } void ReportPossibleLoop(PPathElt e) { Fixed x0, y0, x1, y1; if (e->type == MOVETO) e = GetClosedBy(e); GetEndPoints(e, &x0, &y0, &x1, &y1); ReportError("Possible loop in element that goes from %g %g to %g %g." " Please check.", FixToDbl(itfmx(x0)), FixToDbl(itfmy(y0)), FixToDbl(itfmx(x1)), FixToDbl(itfmy(y1))); } void ReportConflictCheck(PPathElt e, PPathElt conflict, PPathElt cp) { Fixed ex, ey, cx, cy, cpx, cpy; GetEndPoint(e, &ex, &ey); GetEndPoint(conflict, &cx, &cy); GetEndPoint(cp, &cpx, &cpy); ReportError("Check e %g %g conflict %g %g cp %g %g.", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey)), FixToDbl(itfmx(cx)), FixToDbl(itfmy(cy)), FixToDbl(itfmx(cpx)), FixToDbl(itfmy(cpy))); } void ReportConflictCnt(PPathElt e, int32_t cnt) { Fixed ex, ey; GetEndPoint(e, &ex, &ey); ReportError("%g %g conflict count = %d", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey)), cnt); } void ReportRemFlare(PPathElt e, PPathElt e2, bool hFlg, int32_t i) { Fixed ex1, ey1, ex2, ey2; if (!gShowClrInfo) return; GetEndPoint(e, &ex1, &ey1); GetEndPoint(e2, &ex2, &ey2); PrintMessage("Removed %s flare at %g %g by %g %g : %d.", hFlg ? "horizontal" : "vertical", FixToDbl(itfmx(ex1)), FixToDbl(itfmy(ey1)), FixToDbl(itfmx(ex2)), FixToDbl(itfmy(ey2)), i); } void ReportRemConflict(PPathElt e) { Fixed ex, ey; if (!gShowClrInfo) return; GetEndPoint(e, &ex, &ey); ReportError("Removed conflicting hints at %g %g.", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey))); } void ReportRotateSubpath(PPathElt e) { Fixed ex, ey; if (!gShowClrInfo) return; GetEndPoint(e, &ex, &ey); PrintMessage("FYI: changed closepath to %g %g.", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey))); } void ReportRemShortColors(Fixed ex, Fixed ey) { if (!gShowClrInfo) return; PrintMessage("Removed hints from short element at %g %g.", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey))); } static void PrntVal(Fixed v) { if (v >= FixInt(100000)) PrintMessage("%d", FTrunc(v)); else PrintMessage("%g", FixToDbl(v)); } static void ShwHV(PClrVal val) { Fixed bot, top; bot = itfmy(val->vLoc1); top = itfmy(val->vLoc2); PrintMessage("b %g t %g v ", FixToDbl(bot), FixToDbl(top)); PrntVal(val->vVal); PrintMessage(" s %g", FixToDbl(val->vSpc)); if (val->vGhst) PrintMessage(" G"); } void ShowHVal(PClrVal val) { Fixed l, r; PClrSeg seg; ShwHV(val); seg = val->vSeg1; if (seg == NULL) return; l = itfmx(seg->sMin); r = itfmx(seg->sMax); PrintMessage(" l1 %g r1 %g ", FixToDbl(l), FixToDbl(r)); seg = val->vSeg2; l = itfmx(seg->sMin); r = itfmx(seg->sMax); PrintMessage(" l2 %g r2 %g", FixToDbl(l), FixToDbl(r)); } void ShowHVals(PClrVal lst) { while (lst != NULL) { ShowHVal(lst); lst = lst->vNxt; } } void ReportAddHVal(PClrVal val) { ShowHVal(val); } static void ShwVV(PClrVal val) { Fixed lft, rht; lft = itfmx(val->vLoc1); rht = itfmx(val->vLoc2); PrintMessage("l %g r %g v ", FixToDbl(lft), FixToDbl(rht)); PrntVal(val->vVal); PrintMessage(" s %g", FixToDbl(val->vSpc)); } void ShowVVal(PClrVal val) { Fixed b, t; PClrSeg seg; ShwVV(val); seg = val->vSeg1; if (seg == NULL) return; b = itfmy(seg->sMin); t = itfmy(seg->sMax); PrintMessage(" b1 %g t1 %g ", FixToDbl(b), FixToDbl(t)); seg = val->vSeg2; b = itfmy(seg->sMin); t = itfmy(seg->sMax); PrintMessage(" b2 %g t2 %g", FixToDbl(b), FixToDbl(t)); } void ShowVVals(PClrVal lst) { while (lst != NULL) { ShowVVal(lst); lst = lst->vNxt; } } void ReportAddVVal(PClrVal val) { ShowVVal(val); } void ReportFndBstVal(PClrSeg seg, PClrVal val, bool hFlg) { if (hFlg) { PrintMessage("FndBstVal: sLoc %g sLft %g sRght %g ", FixToDbl(itfmy(seg->sLoc)), FixToDbl(itfmx(seg->sMin)), FixToDbl(itfmx(seg->sMax))); if (val) ShwHV(val); else PrintMessage("NULL"); } else { PrintMessage("FndBstVal: sLoc %g sBot %g sTop %g ", FixToDbl(itfmx(seg->sLoc)), FixToDbl(itfmy(seg->sMin)), FixToDbl(itfmy(seg->sMax))); if (val) ShwVV(val); else PrintMessage("NULL"); } } void ReportCarry(Fixed l0, Fixed l1, Fixed loc, PClrVal clrs, bool vert) { if (!gShowClrInfo) return; if (vert) { ShowVVal(clrs); loc = itfmx(loc); l0 = itfmx(l0); l1 = itfmx(l1); } else { ShowHVal(clrs); loc = itfmy(loc); l0 = itfmy(l0); l1 = itfmy(l1); } PrintMessage(" carry to %g in [%g..%g]", FixToDbl(loc), FixToDbl(l0), FixToDbl(l1)); } void ReportBestCP(PPathElt e, PPathElt cp) { Fixed ex, ey, px, py; GetEndPoint(e, &ex, &ey); if (cp != NULL) { GetEndPoint(cp, &px, &py); PrintMessage("%g %g best cp at %g %g", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey)), FixToDbl(itfmx(px)), FixToDbl(itfmy(py))); } else { PrintMessage("%g %g no best cp", FixToDbl(itfmx(ex)), FixToDbl(itfmy(ey))); } } void LogColorInfo(PClrPoint pl) { char c = pl->c; Fixed lft, rht, top, bot, wdth; if (c == 'y' || c == 'm') { /* vertical lines */ lft = pl->x0; rht = pl->x1; PrintMessage("%4g %-30s%5g%5g\n", FixToDbl(rht - lft), gGlyphName, FixToDbl(lft), FixToDbl(rht)); } else { bot = pl->y0; top = pl->y1; wdth = top - bot; if (wdth == -FixInt(21) || wdth == -FixInt(20)) return; /* ghost pair */ PrintMessage("%4g %-30s%5g%5g\n", FixToDbl(wdth), gGlyphName, FixToDbl(bot), FixToDbl(top)); } } static void LstHVal(PClrVal val) { PrintMessage("\t"); ShowHVal(val); PrintMessage(" "); } static void LstVVal(PClrVal val) { PrintMessage("\t"); ShowVVal(val); PrintMessage(" "); } void ListClrInfo(void) { /* debugging routine */ PPathElt e; PSegLnkLst hLst, vLst; PClrSeg seg; Fixed x, y; e = gPathStart; while (e != NULL) { hLst = e->Hs; vLst = e->Vs; if ((hLst != NULL) || (vLst != NULL)) { GetEndPoint(e, &x, &y); x = itfmx(x); y = itfmy(y); PrintMessage("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 ReportAddVSeg(Fixed from, Fixed to, Fixed loc, int32_t i) { if (!gShowClrInfo || !gShowVs) return; PrintMessage("add vseg %g %g to %g %g %d", FixToDbl(itfmx(loc)), FixToDbl(itfmy(from)), FixToDbl(itfmx(loc)), FixToDbl(itfmy(to)), i); } void ReportAddHSeg(Fixed from, Fixed to, Fixed loc, int32_t i) { if (!gShowClrInfo || !gShowHs) return; PrintMessage("add hseg %g %g to %g %g %d", FixToDbl(itfmx(from)), FixToDbl(itfmy(loc)), FixToDbl(itfmx(to)), FixToDbl(itfmy(loc)), i); } void ReportRemVSeg(Fixed from, Fixed to, Fixed loc) { if (!gShowClrInfo || !gShowVs) return; PrintMessage("rem vseg %g %g to %g %g", FixToDbl(itfmx(loc)), FixToDbl(itfmy(from)), FixToDbl(itfmx(loc)), FixToDbl(itfmy(to))); } void ReportRemHSeg(Fixed from, Fixed to, Fixed loc) { if (!gShowClrInfo || !gShowHs) return; PrintMessage("rem hseg %g %g to %g %g", FixToDbl(itfmx(from)), FixToDbl(itfmy(loc)), FixToDbl(itfmx(to)), FixToDbl(itfmy(loc))); } void ReportBandNearMiss(char* str, Fixed loc, Fixed blu) { ReportError("Near miss %s horizontal zone at %g instead of %g.", str, FixToDbl(loc), FixToDbl(blu)); } void ReportStemNearMiss(bool vert, Fixed w, Fixed minW, Fixed b, Fixed t, bool curve) { ReportError("%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 ReportColorConflict(Fixed x0, Fixed y0, Fixed x1, Fixed y1, char ch) { unsigned char s[2]; s[0] = ch; s[1] = 0; ReportError(" Conflicts with current hints: %g %g %g %g %s.", FixToDbl(x0), FixToDbl(y0), FixToDbl(x1), FixToDbl(y1), s); } void ReportDuplicates(Fixed x, Fixed y) { ReportError("Check for duplicate subpath at %g %g.", FixToDbl(x), FixToDbl(y)); } void ReportBBoxBogus(Fixed llx, Fixed lly, Fixed urx, Fixed ury) { ReportError("Character 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) { if (!gShowClrInfo) return; PrintMessage( "Replace H hints pair at %g %g by %g %g\n\told value ", FixToDbl(itfmy(b0)), FixToDbl(itfmy(t0)), FixToDbl(itfmy(b1)), FixToDbl(itfmy(t1))); PrntVal(v0); PrintMessage(" %g new value ", FixToDbl(s0)); PrntVal(v1); PrintMessage(" %g", FixToDbl(s1)); } void ReportMergeVVal(Fixed l0, Fixed r0, Fixed l1, Fixed r1, Fixed v0, Fixed s0, Fixed v1, Fixed s1) { if (!gShowClrInfo) return; PrintMessage("Replace V hints pair at %g %g by %g %g\n\told value ", FixToDbl(itfmx(l0)), FixToDbl(itfmx(r0)), FixToDbl(itfmx(l1)), FixToDbl(itfmx(r1))); PrntVal(v0); PrintMessage(" %g new value ", FixToDbl(s0)); PrntVal(v1); PrintMessage(" %g", FixToDbl(s1)); } void ReportPruneHVal(PClrVal val, PClrVal v, int32_t i) { if (!gShowClrInfo) return; PrintMessage("PruneHVal: %d\n\t", i); ShowHVal(val); PrintMessage("\n\t"); ShowHVal(v); } void ReportPruneVVal(PClrVal val, PClrVal v, int32_t i) { if (!gShowClrInfo) return; PrintMessage("PruneVVal: %d\n\t", i); ShowVVal(val); PrintMessage("\n\t"); ShowVVal(v); } void ReportMoveSubpath(PPathElt e, char* s) { Fixed x, y; GetEndPoint(e, &x, &y); PrintMessage("FYI: Moving subpath %g %g to %s.", FixToDbl(itfmx(x)), FixToDbl(itfmy(y)), s); } psautohint-1.1.0/libpsautohint/src/shuffle.c000066400000000000000000000126321324015706300211700ustar00rootroot00000000000000/* * 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 unsigned char* links; static int32_t rowcnt; void InitShuffleSubpaths(void) { int32_t cnt = -1; PPathElt e = gPathStart; while (e != NULL) { /* every element is marked with its subpath count */ if (e->type == MOVETO) cnt++; if (gDebug) { if (e->type == MOVETO) { /* DEBUG */ PrintMessage("subpath %d starts at %g %g\n", cnt, FixToDbl(itfmx(e->x)), FixToDbl(itfmy(e->y))); } } e->count = (int16_t)cnt; e = e->next; } cnt++; rowcnt = cnt; links = (cnt < 4 || cnt >= MAXCNT) ? NULL : (unsigned char*)Alloc(cnt * cnt); } static void PrintLinks(void) { int32_t i, j; PrintMessage("Links "); for (i = 0; i < rowcnt; i++) { PrintMessage("%d ", i); if (i < 10) PrintMessage(" "); } PrintMessage("\n"); for (i = 0; i < rowcnt; i++) { PrintMessage(" %d ", i); if (i < 10) PrintMessage(" "); for (j = 0; j < rowcnt; j++) { PrintMessage("%d ", links[rowcnt * i + j]); } PrintMessage("\n"); } } static void PrintSumLinks(char* sumlinks) { int32_t i; PrintMessage("Sumlinks "); for (i = 0; i < rowcnt; i++) { PrintMessage("%d ", i); if (i < 10) PrintMessage(" "); } PrintMessage("\n"); PrintMessage(" "); for (i = 0; i < rowcnt; i++) { PrintMessage("%d ", sumlinks[i]); } PrintMessage("\n"); } static void PrintOutLinks(unsigned char* outlinks) { int32_t i; PrintMessage("Outlinks "); for (i = 0; i < rowcnt; i++) { PrintMessage("%d ", i); if (i < 10) PrintMessage(" "); } PrintMessage("\n"); PrintMessage(" "); for (i = 0; i < rowcnt; i++) { PrintMessage("%d ", outlinks[i]); } PrintMessage("\n"); } void MarkLinks(PClrVal vL, bool hFlg) { int32_t i, j; PClrSeg seg; PPathElt e; if (links == NULL) return; for (; vL != NULL; vL = vL->vNxt) { if (vL == NULL) continue; 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 (gDebug) { if (hFlg) ShowHVal(vL); else ShowVVal(vL); PrintMessage(" : %d <-> %d\n", 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; PPathElt e = gPathStart; while (e != NULL) { if (e->count == i) break; e = e->next; } MoveSubpathToEnd(e); if (gDebug) { PrintMessage("move subpath %d to end\n", bst); /* DEBUG */ } output[bst] = 1; lnks = &links[bst * rowcnt]; outlnks = outlinks; for (i = 0; i < rowcnt; i++) *outlnks++ += *lnks++; if (gDebug) 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 characters had the most problems with this which caused huge files to be created. */ void DoShuffleSubpaths(void) { unsigned char sumlinks[MAXCNT], output[MAXCNT], outlinks[MAXCNT]; unsigned char* lnks; int32_t i, j, bst, bstsum, bstlnks; if (links == NULL) return; if (gDebug) PrintLinks(); 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]++; } } if (gDebug) PrintSumLinks((char*)sumlinks); while (true) { bst = -1; 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) { 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-1.1.0/libpsautohint/src/stemreport.c000066400000000000000000000016551324015706300217430ustar00rootroot00000000000000/* * 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 top, Fixed bottom, bool curved) { if (curved && !gAllStems) return; if (gAddVStemCB != NULL) { gAddVStemCB(top, bottom, gGlyphName); } } void AddHStem(Fixed right, Fixed left, bool curved) { if (curved && !gAllStems) return; if (gAddHStemCB != NULL) { gAddHStemCB(right, left, gGlyphName); } } void AddCharExtremes(Fixed bot, Fixed top) { if (gAddCharExtremesCB != NULL) { gAddCharExtremesCB(top, bot, gGlyphName); } } void AddStemExtremes(Fixed bot, Fixed top) { if (gAddStemExtremesCB != NULL) { gAddStemExtremesCB(top, bot, gGlyphName); } } psautohint-1.1.0/libpsautohint/src/winstdint.h000066400000000000000000000170601324015706300215640ustar00rootroot00000000000000// ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2008 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. The name of the author may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////////// #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 #pragma once #endif #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include #ifdef __cplusplus } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants #define INTMAX_C INT64_C #define UINTMAX_C UINT64_C #endif // __STDC_CONSTANT_MACROS ] #endif // _MSC_STDINT_H_ ] psautohint-1.1.0/libpsautohint/src/write.c000066400000000000000000000407541324015706300206740ustar00rootroot00000000000000/* * 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, wrtColorInfo; #define MAXS0LEN 127 static char S0[MAXS0LEN + 1]; static PClrPoint bst; static char bch; static Fixed bx, by; static bool bstB; static int16_t subpathcount; static int writeAbsolute = 1; 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; } static void WriteString(char* str) { if (!gBezOutput) { LogMsg(LOGERROR, FATALERROR, "NULL output buffer while writing glyph: %s", gGlyphName); return; } if ((gBezOutput->length + strlen(str)) >= gBezOutput->capacity) { size_t desiredsize = NUMMAX(gBezOutput->capacity * 2, gBezOutput->capacity + strlen(str)); gBezOutput->data = ReallocateMem(gBezOutput->data, desiredsize, "output bez data"); if (gBezOutput->data) gBezOutput->capacity = desiredsize; else return; /*FATAL ERROR*/ } strcat(gBezOutput->data, str); gBezOutput->length += strlen(str); } /* Note: The 8 bit fixed fraction cannot support more than 2 decimal places. */ #define WRTNUM(i) \ { \ snprintf(S0, MAXS0LEN, "%d ", (int32_t)(i)); \ WriteString(S0); \ } #define WRTRNUM(i) \ { \ snprintf(S0, MAXS0LEN, "%0.2f ", roundf((float)(i)*100) / 100); \ WriteString(S0); \ } static void wrtx(Fixed x) { Fixed i; Fixed dx; if ((gRoundToInt) || (FracPart(x) == 0)) { i = FRnd(x); dx = i - currentx; WRTNUM(FTrunc(dx)); currentx = i; } else { float r; i = x - currentx; currentx = x; r = (float)FIXED2FLOAT(i); WRTRNUM(r); } } static void wrtxa(Fixed x) { Fixed i; if ((gRoundToInt) || (FracPart(x) == 0)) { i = FRnd(x); WRTNUM(FTrunc(i)); currentx = i; } else { float r; currentx = x; r = (float)FIXED2FLOAT(x); WRTRNUM(r); } } static void wrty(Fixed y) { Fixed i; Fixed dy; if ((gRoundToInt) || (FracPart(y) == 0)) { i = FRnd(y); dy = i - currenty; WRTNUM(FTrunc(dy)); currenty = i; } else { float r; i = y - currenty; currenty = y; r = (float)FIXED2FLOAT(i); WRTRNUM(r); } } static void wrtya(Fixed y) { Fixed i; if ((gRoundToInt) || (FracPart(y) == 0)) { i = FRnd(y); WRTNUM(FTrunc(i)); currenty = i; } else { float r; currenty = y; r = (float)FIXED2FLOAT(y); WRTRNUM(r); } } #define wrtcd(c) \ wrtx(c.x); \ wrty(c.y) #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, "ERROR: Hint information overflowing buffer: %s\n", gGlyphName); } else { strcat(s1, s2); } } #define sws(str) safestrcat(hintmaskstr, (char*)str) #define SWRTNUM(i) \ { \ snprintf(S0, MAXS0LEN, "%d ", (int32_t)(i)); \ sws(S0); \ } #define SWRTNUMA(i) \ { \ snprintf(S0, MAXS0LEN, "%0.2f ", roundf((float)(i)*100) / 100); \ sws(S0); \ } static void NewBest(PClrPoint lst) { Fixed x0, x1, y0, y1; bst = lst; bch = lst->c; if (bch == 'y' || bch == 'm') { bstB = true; x0 = lst->x0; x1 = lst->x1; bx = NUMMIN(x0, x1); } else { bstB = false; y0 = lst->y0; y1 = lst->y1; by = NUMMIN(y0, y1); } } static void WriteOne(const ACFontInfo* fontinfo, Fixed s) { /* write s to output file */ Fixed r = UnScaleAbs(fontinfo, s); if (gScalingHints) { r = FRnd(r); } if (FracPart(r) == 0) { SWRTNUM(FTrunc(r)) } else { float d = (float)FIXED2FLOAT(r); if (writeAbsolute) { SWRTNUMA(d); } else { d = (float)((d + 0.005) * 100); SWRTNUM(d); sws("100 div "); } } } static void WritePointItem(const ACFontInfo* fontinfo, PClrPoint lst) { switch (lst->c) { case 'b': case 'v': WriteOne(fontinfo, lst->y0); WriteOne(fontinfo, lst->y1 - lst->y0); sws(((lst->c == 'b') ? "rb" : "rv")); break; case 'y': case 'm': WriteOne(fontinfo, lst->x0); WriteOne(fontinfo, lst->x1 - lst->x0); sws(((lst->c == 'y') ? "ry" : "rm")); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal point list data for glyph: %s.\n", gGlyphName); } } sws(" % "); SWRTNUM(lst->p0 != NULL ? lst->p0->count : 0); SWRTNUM(lst->p1 != NULL ? lst->p1->count : 0); sws("\n"); } static void WrtPntLst(const ACFontInfo* fontinfo, PClrPoint lst) { PClrPoint 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(fontinfo, bst); } } static void wrtnewclrs(const ACFontInfo* fontinfo, PPathElt e) { if (!wrtColorInfo) { return; } hintmaskstr[0] = '\0'; WrtPntLst(fontinfo, gPtLstArray[e->newcolors]); if (strcmp(prevhintmaskstr, hintmaskstr)) { WriteString("beginsubr snc\n"); WriteString(hintmaskstr); WriteString("endsubr enc\nnewcolors\n"); strcpy(prevhintmaskstr, hintmaskstr); } } static bool IsFlex(PPathElt e) { PPathElt 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(const ACFontInfo* fontinfo, Cd c, PPathElt e) { if (e->newcolors != 0) { wrtnewclrs(fontinfo, e); } if (writeAbsolute) { wrtcda(c); WriteString("mt\n"); } else { if (FRnd(c.y) == currenty) { wrtx(c.x); WriteString("hmt\n"); } else if (FRnd(c.x) == currentx) { wrty(c.y); WriteString("vmt\n"); } else { wrtcd(c); WriteString("rmt\n"); } } if (e->eol) { WriteString("eol\n"); } if (e->sol) { WriteString("sol\n"); } } static void dt(const ACFontInfo* fontinfo, Cd c, PPathElt e) { if (e->newcolors != 0) { wrtnewclrs(fontinfo, e); } if (writeAbsolute) { wrtcda(c); WriteString("dt\n"); } else { if (FRnd(c.y) == currenty) { wrtx(c.x); WriteString("hdt\n"); } else if (FRnd(c.x) == currentx) { wrty(c.y); WriteString("vdt\n"); } else { wrtcd(c); WriteString("rdt\n"); } } if (e->eol) { WriteString("eol\n"); } if (e->sol) { WriteString("sol\n"); } } static Fixed flX, flY; static Cd fc1, fc2, fc3; #define wrtpreflx2(c) \ wrtcd(c); \ WriteString("rmt\npreflx2\n") #define wrtpreflx2a(c) \ wrtcda(c); \ WriteString("rmt\npreflx2a\n") static void wrtflex(Cd c1, Cd c2, Cd c3, PPathElt 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.0) { 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.0) { shrink = -shrink; } acfixtopflt(fc3.x - c3.x, &r1); r1 *= shrink; acfixtopflt(c3.x, &r2); r1 += r2; c13.x = acpflttofix(&r1); } c13.y = fc3.y; } if (writeAbsolute) { 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"); } else { wrtpreflx2(c13); wrtpreflx2(fc1); wrtpreflx2(fc2); wrtpreflx2(fc3); wrtpreflx2(c1); wrtpreflx2(c2); wrtpreflx2(c3); currentx = flX; currenty = flY; wrtcd(fc1); wrtcd(fc2); wrtcd(fc3); wrtcd(c1); wrtcd(c2); wrtcd(c3); WRTNUM(dmin); WRTNUM(delta); WRTNUM(yflag); WRTNUM(FTrunc(FRnd(currentx))); WRTNUM(FTrunc(FRnd(currenty))); WriteString("flx\n"); } firstFlex = true; } static void ct(const ACFontInfo* fontinfo, Cd c1, Cd c2, Cd c3, PPathElt e) { if (e->newcolors != 0) { wrtnewclrs(fontinfo, e); } if (e->isFlex && IsFlex(e)) { wrtflex(c1, c2, c3, e); } else if (writeAbsolute) { wrtcda(c1); wrtcda(c2); wrtcda(c3); WriteString("ct\n"); } else { if ((FRnd(c1.x) == currentx) && (c2.y == c3.y)) { wrty(c1.y); wrtcd(c2); wrtx(c3.x); WriteString("vhct\n"); } else if ((FRnd(c1.y) == currenty) && (c2.x == c3.x)) { wrtx(c1.x); wrtcd(c2); wrty(c3.y); WriteString("hvct\n"); } else { wrtcd(c1); wrtcd(c2); wrtcd(c3); WriteString("rct\n"); } } if (e->eol) { WriteString("eol\n"); } if (e->sol) { WriteString("sol\n"); } } static void cp(const ACFontInfo* fontinfo, PPathElt e) { if (e->newcolors != 0) { wrtnewclrs(fontinfo, e); } if (gIdInFile) { WRTNUM(subpathcount++); WriteString("id\n"); } WriteString("cp\n"); if (e->eol) { WriteString("eol\n"); } if (e->sol) { WriteString("sol\n"); } } static void NumberPath(void) { int16_t cnt; PPathElt e; e = gPathStart; cnt = 1; while (e != NULL) { e->count = cnt++; e = e->next; } } void SaveFile(const ACFontInfo* fontinfo) { PPathElt e = gPathStart; Cd c1, c2, c3; /* AddSolEol(); */ WriteString("% "); WriteString(gGlyphName); WriteString("\n"); wrtColorInfo = (gPathStart != NULL && gPathStart != gPathEnd); NumberPath(); prevhintmaskstr[0] = '\0'; if (wrtColorInfo && (!e->newcolors)) { hintmaskstr[0] = '\0'; WrtPntLst(fontinfo, gPtLstArray[0]); WriteString(hintmaskstr); strcpy(prevhintmaskstr, hintmaskstr); } WriteString("sc\n"); firstFlex = true; currentx = currenty = 0; while (e != NULL) { switch (e->type) { case CURVETO: c1.x = UnScaleAbs(fontinfo, itfmx(e->x1)); c1.y = UnScaleAbs(fontinfo, itfmy(e->y1)); c2.x = UnScaleAbs(fontinfo, itfmx(e->x2)); c2.y = UnScaleAbs(fontinfo, itfmy(e->y2)); c3.x = UnScaleAbs(fontinfo, itfmx(e->x3)); c3.y = UnScaleAbs(fontinfo, itfmy(e->y3)); ct(fontinfo, c1, c2, c3, e); break; case LINETO: c1.x = UnScaleAbs(fontinfo, itfmx(e->x)); c1.y = UnScaleAbs(fontinfo, itfmy(e->y)); dt(fontinfo, c1, e); break; case MOVETO: c1.x = UnScaleAbs(fontinfo, itfmx(e->x)); c1.y = UnScaleAbs(fontinfo, itfmy(e->y)); mt(fontinfo, c1, e); break; case CLOSEPATH: cp(fontinfo, e); break; default: { LogMsg(LOGERROR, NONFATALERROR, "Illegal path list for glyph: %s.\n", gGlyphName); } } #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-1.1.0/python/000077500000000000000000000000001324015706300152315ustar00rootroot00000000000000psautohint-1.1.0/python/.lvimrc000066400000000000000000000001011324015706300165160ustar00rootroot00000000000000set noexpandtab set shiftwidth=4 set softtabstop=4 set tabstop=4 psautohint-1.1.0/python/psautohint/000077500000000000000000000000001324015706300174275ustar00rootroot00000000000000psautohint-1.1.0/python/psautohint/__init__.py000066400000000000000000000000001324015706300215260ustar00rootroot00000000000000psautohint-1.1.0/python/psautohint/__main__.py000066400000000000000000000612401324015706300215240ustar00rootroot00000000000000from __future__ import print_function, absolute_import import os import re import sys from psautohint import _psautohint, autohint, ufoFont __copyright__ = """\ Copyright 2014-2017 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved. """ __usage__ = """ Auto-hinting program for PostScript and OpenType/CFF fonts. autohint.py v1.49 April 27 2016 autohint -h autohint -u autohint -hfd autohint -pfd autohint [-g ] [-gf ] [-xg ] [-xgf ] [-cf path] [-a] [-logOnly] [-log ] [-r] [-q] [-qq] [-c] [-nf] [-ns] [-nb] [-wd] [-o ] font-path """ __help__ = __usage__ + """ Takes a list fonts, and an optional list of glyphs, and hints the fonts. If the list of glyphs is supplied, the hinting is limited to the specified glyphs. Note that the hinting is better if the font's global alignment zones are set usefully; at the very least, you should have entered values that capture capital height, x-height, ascender and descender heights, and ascender and descender overshoot. The reports provided by the stemHist tool are useful for choosing these. By default, autothint will hint all glyphs in the font. Options allow you to specify a subset of the glyphs for hinting. Options: -h ... Print help. -u ... Print usage. -hfd . Print a description of the format for defining a set of alternate alignment zones in an "fontinfo" file. -pfd . Print the default FDDict values for the source font: the alignment zone, stem width, and other global values. This is useful as a starting point for building FDDict defintions (see -hfd). -pfdl Print the list of user-defined FDDict values, and which glyphs are included in each. This is useful for checking your FDDict definitions and glyph search terms. (see -hfd). -g ,,..., Hint only the specified list of glyphs. Note that all glyphs will be written to the output file. The list must be comma-delimited. The glyph references may be glyphID's, glyph names, or glyph CID's. If the latter, the CID value must be prefixed with the string "/". There must be no white-space in the glyph list. Examples: autohint -g A,B,C,68 myFont autohint -g /1030,/434,/1535,68 myCIDFont A range of glyphs may be specified by providing two names separated only by a hyphen: autohint -g zero-nine,onehalf myFont Note that the range will be resolved by expanding the glyph indices (GID)'s, not by alphabetic names. -gf Hint only the list of glyphs contained in the specified file. The file must contain a comma-delimited list of glyph identifiers. Any number of space, tab, and new-line characters are permitted between glyph names and commas. -xg, -xgf Same as -g and -gf, but will exclude the specified glyphs from hinting. -cf AC 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". Counter hints help to keep the space between stems open and equal in size. To extend this list, use the option -cf followed by a path to a text file. The text file must contain one record per line. A record references one glyph, and should consist of a single letter V or H, to indicate whether the counters should be vertical or horizontal, followd by a space or tab, followed by a glyph name. A maximum of 64 glyph names can be added to either the vertical or horizontal list. Example: V ffi V ffl V f_f_j Alternatively, if there is a file named "fontinfo" in the same directory as the source font, this script will look in that file for lines with the format: VCounterChar ( ...) HCounterChar ( ...) and add the referenced glyphs to the counter hint lists. Example: VCounterChar (ffi ffl f_f_j) -logOnly Do not change any outlines, but report warnings for all selected glyphs, including those already hinted. The option -q is ignored. The option -a is implied. -q ... Quiet mode. Will suppress comments from the auto-hinting library about recommended glyph outline changes. -qq .. Really quiet mode. Will suppress all normal messages. -c ... Permit changes to the glyph outline. When this is turned on, the autohint program will fix a few issues: if there are many hint substitutions, it will try and shuffle the order of paths to reduce this, and it will flatten nearly straight curves. It no longer blunts sharp angles. That is better done with checkOutlines. -nf .. Suppress generation of flex commands. -ns .. Suppress hint substitution. Do this only if you really want the smallest possible font file. This will use only one set of hints for the entire glyph. -nb .. Allow the font to have to no stem widths or blue values specified. Without this option, autohint will complain and quit. -o If not specified, autohint will write the hinted output to the original font path name. -log . Write output to a log file. -all . Hint all glyphs, even if the source glyphs is unchanged and the glyph has been hinted before. Applies only to UFO fonts. -hf .. Use history file. Will create it if it does not already exist. Should not be used with UFO fonts, where another mechanism is employed. -a ... Hint all glyphs that are in the history file, or are unhinted. Has effect only if the history file is being used. -r ... Re-hint glyphs. Glyphs not in the history file will be hinted even if they already have hints. However, glyphs will not be hinted if they both have not changed and are in the history file. -decimal Use decimal coordinates, instead of rounding to the nearest integer value. -wd .. Write changed glyphs to default layer instead of '%s'. autohint can also apply different sets of alignment zones while hinting a particular set of glyphs. This is useful for name-keyed fonts, which, unlike CID fonts, only have one set of global alignment zones and stem widths. By default, autohint uses the font's global alignment zones and stem widths for each glyph. However, if there is a file named "fontinfo" in the same directory as the input font file, autohint will check the "fontinfo" file for definitions of alternate sets of alignment zones, and the matching lists of glyphs to which they should be applied. To see the format for these entries, use the option "-hfd". This allows one set of glyphs to be hinted using a different set of zones and stem widths than other glyphs. This isn't as useful as having real multiple hint dictionaries 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 baselines. autohint can maintain a history file, which allows you to avoid hinting glyphs that have already been auto-hinted or manually hinted. When this is in use, autohint will by default hint only those glyphs that are not already hinted, and also those glyphs which are hinted, but whose outlines have changed since the last time autohint was run. autohint knows whether an outline has changed by storing the outline in the history file whenever the glyph is hinted, and then consulting the history file when it is asked to hint the glyph again. By default, autohint does not maintain or use the history file, but this can be turned on with an option. When used, the history file is named ".plist", in the same location as the parent font file. For each glyph, autohint stores a simplified version of the outline coordinates. If this entry is missing for a glyph and the glyph has hints, then autohint assumes it was manually hinted, and will by default not hint it again. If the file is missing, autohint will assume that all the glyphs were manually hinted, and you will have to use the option -a or -r to hint any glyphs. """ % (ufoFont.kProcessedGlyphsLayerName) __FDDoc__ = """ By default, autohint 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, autohint will check the "fontinfo" file for definitions of sets of alignment zones (a "FDDict"), and the matching lists of glyphs to which they should be applied. This allows one set of glyphs to be hinted using a different set of zones and stem widths than other glyphs. This isn't as useful as having real multiple hint dictionaries 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, autohint will synthesize an dummy FDDict, where the zones are set outside of the the font's bounding box, so they will not affect hinting. This is desirable for glyphs that have no features that need to be aligned. If autohint finds an FDDict named "FinalFont", then it will write that set of values to the output font. Otherwise, it will merge all the alignment zones and stem widths in the union of all the FDDict definitions. If this merge fails because some of the alignment zones are stem widths overlap, then you have to provide a "FinalFont" FDDict that explicitly defines which stems and zones to use in the hinted output font. To use a dictionary of alignment zones and stem widths, you need to define both the dictionary of alternate values, 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. If you run autohint with the option "-pfd", it will print out the list of FDDict values for the source font. You can use this text as a starting point for your FDDict definitions. You can also run autohint with the option "-pfdl". This will print the user-defined FDDict defintions, and the list of glyphs associated with each FDDict. You can use this to check your values, and to check which glyphs are assigned to which FDDict. In particular, check the glyph list for the first FDDict "No Alignment Zones": this list exists because these glyphs did not match in the search terms for any user-defined FDDict. 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 comprising set set of 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 ".s04" Example FDDict and GlyphSet definitions. ******************************** begin FDDict ST_Smallcaps # I like 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 AachenStd-Bold # Alignment zones. # The first is a bottom zone, the rest are top zones. See below. 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 FontName AachenStd-Bold BaselineOvershoot -25 BaselineYCoord 0 CapHeight 950 CapOvershoot 25 LcHeight 750 LcOvershoot 21 DominantV [236 267] DominantH [141 152] FlexOK true 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. "#" is a comment character: any occurrence of "#" and all following text on a line is skipped. GlyphSet and FDDict definitions may be intermixed, as long as any FDDict is defined before the GlyphSet which refers to it. You must provide at least two BlueValue pairs (the 'BaselineYCoord' bottom zone and any top zone), and you must provide the DominantH and DominantV keywords. All other keywords are optional. The full set of recognized FDDict keywords are: 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: # These Baseline5Overshoot Baseline5 Baseline6Overshoot Baseline6 SuperiorOvershoot SuperiorBaseline OrdinalOvershoot OrdinalBaseline DescenderOvershoot DescenderHeight For zones which capture the bottom of a feature in the glyph, (BaselineYCoord and all the OtherBlues), 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 the glyph, (the rest of the BlueValue zones), 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 of the values used in another FontDict. There is no inheritance from one definition to the next. All FontDicts must specify at least the BaselineYCoord and one top zone. 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 characters to which counter hints may be applied, in the form [ ...] HCounterChars List of characters to which counter hints may be applied, in the form [ ...] Note for cognoscenti: the autohint program code ignores StdHW and StdVW entries if DominantV and DominantH entries are present, so I omit writing the Std[HV]W keywords to fontinfo file. Also, autohint 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 I also write the full stem list for the Dominant[HV] keywords, and do not write the StemSnap[HV] keywords, to the fontinfo file. This is technically not right, as DominantHV array is supposed to hold only two values, but the autohint program doesn't care, and I can write fewer entries this way. """ class OptionParseError(Exception): pass def expandNames(glyphName, nameAliases): glyphRange = glyphName.split("-") if len(glyphRange) > 1: g1 = expandNames(glyphRange[0], nameAliases) g2 = expandNames(glyphRange[1], nameAliases) glyphName = "%s-%s" % (g1, g2) elif glyphName[0] == "/": glyphName = "cid" + glyphName[1:].zfill(5) if glyphName == "cid00000": glyphName = ".notdef" nameAliases[glyphName] = "cid00000" elif glyphName.startswith("cid") and (len(glyphName) < 8): glyphName = "cid" + glyphName[3:].zfill(5) if glyphName == "cid00000": glyphName = ".notdef" nameAliases[glyphName] = "cid00000" return glyphName def parseGlyphListArg(glyphString, nameAliases): glyphString = re.sub(r"[ \t\r\n,]+", ",", glyphString) glyphList = glyphString.split(",") glyphList = [expandNames(n, nameAliases) for n in glyphList] glyphList = filter(None, glyphList) return glyphList def parseCounterHintData(path): hCounterGlyphList = [] vCounterGlyphList = [] with open(path, "rt") as gf: data = gf.read() lines = re.findall(r"([^\r\n]+)", data) # strip blank and comment lines lines = filter(lambda line: re.sub(r"#.+", "", line), lines) lines = filter(lambda line: line.strip(), lines) for line in lines: fields = line.split() if (len(fields) != 2) or (fields[0] not in ["V", "v", "H", "h"]) : print("\tError: could not process counter hint line '%s' in file %s. Doesn't look like V or H followed by a tab or space, and then a glyph name." % (line, path)) elif fields[0] in ["V", "v"]: vCounterGlyphList.append(fields[1]) else: hCounterGlyphList.append(fields[1]) return hCounterGlyphList, vCounterGlyphList def checkFontinfoFile(options): """Check if there ia a makeotf fontinfo file in the input font directory. If so, get any Vcounter or HCouunter glyphs from it.""" srcFontInfo = os.path.dirname(options.inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") if os.path.exists(srcFontInfo): with open(srcFontInfo, "rU") as fi: data = fi.read() data = re.sub(r"#[^\r\n]+", "", data) counterGlyphLists = re.findall(r"([VH])CounterChars\s+\(\s*([^\)\r\n]+)\)", data) for entry in counterGlyphLists: glyphList = entry[1].split() if glyphList: if entry[0] == "V": options.vCounterGlyphs.extend(glyphList) else: options.hCounterGlyphs.extend(glyphList) if options.vCounterGlyphs or options.hCounterGlyphs: options.counterHintFile = srcFontInfo def getOptions(args): options = autohint.ACOptions() i = 0 numOptions = len(args) while i < numOptions: arg = args[i] if options.inputPath: raise OptionParseError("Option Error: All options must preceed the input font path <%s>." % arg) if arg == "-h": print(__help__) print("Lib version:", _psautohint.version) return elif arg == "-u": print(__usage__) print("Lib version:", _psautohint.version) return elif arg == "-hfd": print(__FDDoc__) return elif arg == "-pfd": options.printDefaultFDDict = True elif arg == "-pfdl": options.printFDDictList = True elif arg == "-hf": options.usePlistFile = True elif arg == "-a": options.hintAll = True elif arg == "-all": options.hintAll = True elif arg == "-r": options.rehint = True elif arg == "-q": options.verbose = False elif arg == "-qq": options.quiet = True options.verbose = False elif arg == "-c": options.allowChanges = True elif arg == "-nf": options.noFlex = True elif arg == "-ns": options.noHintSub = True elif arg == "-nb": options.allow_no_blues = True elif arg in ["-xg", "-g"]: if arg == "-xg": options.excludeGlyphList = True i += 1 glyphString = args[i] if glyphString[0] == "-": raise OptionParseError("Option Error: it looks like the first item in the glyph list following '-g' is another option.") options.glyphList += parseGlyphListArg(glyphString, options.nameAliases) elif arg in ["-xgf", "-gf"]: if arg == "-xgf": options.excludeGlyphList = True i += 1 filePath = args[i] if filePath[0] == "-": raise OptionParseError("Option Error: it looks like the the glyph list file following '-gf' is another option.") try: with open(filePath, "rt") as gf: glyphString = gf.read() except (IOError,OSError): raise OptionParseError("Option Error: could not open glyph list file <%s>." % filePath) options.glyphList += parseGlyphListArg(glyphString, options.nameAliases) elif arg == "-cf": i += 1 filePath = args[i] if filePath[0] == "-": raise OptionParseError("Option Error: it looks like the the counter hint glyph list file following '-cf' is another option.") try: options.counterHintFile = filePath options.hCounterGlyphs, options.vCounterGlyphs = parseCounterHintData(filePath) except (IOError,OSError): raise OptionParseError("Option Error: could not open counter hint glyph list file <%s>." % filePath) elif arg == "-logOnly": options.logOnly = True elif arg == "-log": i += 1 options.logFile = open(args[i], "wt") elif arg == "-o": i += 1 options.outputPath = args[i] elif arg == "-d": options.debug = True elif arg in ["-decimal", "-dec"]: options.allowDecimalCoords = True elif arg =="-wd": options.writeToDefaultLayer = True elif arg[0] == "-": raise OptionParseError("Option Error: Unknown option <%s>." % arg) else: options.inputPath = arg i += 1 if not options.inputPath: raise OptionParseError("Option Error: You must provide a font file path.") if not os.path.exists(options.inputPath): raise OptionParseError("Option Error: The input font file path %s' does not exist." % (options.inputPath)) # Might be a UFO font. # Auto completion in some shells adds a dir separator, # which then causes problems with os.path.dirname(). options.inputPath = options.inputPath.rstrip(os.sep) checkFontinfoFile(options) if options.logOnly: options.verbose = True options.hintAll = True return options def main(args=None): if args is None: args = sys.argv[1:] try: options = getOptions(args) if options is None: # Happens when one of the help arguments is given. return except OptionParseError as e: autohint.logMsg(e) return 1 # verify that all files exist. try: autohint.hintFile(options) except (autohint.ACFontError, autohint.ACHintError, ufoFont.UFOParseError) as e: autohint.logMsg("\t%s" % e) return 1 if options.logFile: options.logFile.close() if __name__=='__main__': sys.exit(main()) psautohint-1.1.0/python/psautohint/_psautohint.c000066400000000000000000000161151324015706300221340ustar00rootroot00000000000000/* * 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 #if !defined(_MSC_VER) || _MSC_VER >= 1800 #include #else typedef unsigned char bool; #define true 1 #define false 0 #endif #include "psautohint.h" static void reportCB(char* msg) { #if PY_MAJOR_VERSION >= 3 PySys_FormatStdout("%s", msg); #else /* Formatted string should not exceed 1000 bytes, see: * https://docs.python.org/2/c-api/sys.html#c.PySys_WriteStdout */ PySys_WriteStdout("%.1000s", msg); #endif } #if PY_MAJOR_VERSION >= 3 #define MEMNEW(size) PyMem_RawCalloc(1, size) #define MEMFREE(ptr) PyMem_RawFree(ptr) #define MEMRENEW(ptr, size) PyMem_RawRealloc(ptr, size) #else #define MEMNEW(size) PyMem_Malloc(size) #define MEMFREE(ptr) PyMem_Free(ptr) #define MEMRENEW(ptr, size) PyMem_Realloc(ptr, size) #endif static void* memoryManager(void* ctx, void* ptr, size_t size) { if (!ptr && !size) return NULL; if (ptr && size) ptr = MEMRENEW(ptr, size); else if (size) ptr = MEMNEW(size); else MEMFREE(ptr); return ptr; } static PyObject* PsAutoHintError; static char autohint_doc[] = "Autohint glyphs.\n" "\n" "Signature:\n" " autohint(font_info, glyphs[, verbose, no_edit, allow_hint_sub, " "round, debug])\n" "\n" "Args:\n" " font_info: font information.\n" " glyphs: sequence of glyph data in bez format.\n" " verbose: print verbose messages.\n" " allow_edit: allow editing (changing) the paths when hinting.\n" " allow_hint_sub: no multiple layers of coloring.\n" " round: round coordinates.\n" " debug: print debug messages.\n" "\n" "Output:\n" " Sequence of autohinted glyph data in bez format.\n" "\n" "Raises:\n" " psautohint.error: If authinting fails.\n"; static PyObject* autohint(PyObject* self, PyObject* args) { int allowEdit = true, roundCoords = true, allowHintSub = true; int verbose = true; int debug = false; PyObject* inSeq = NULL; PyObject* fontObj = NULL; PyObject* outSeq = NULL; int bezLen = 0; char* fontInfo = NULL; bool error = false; if (!PyArg_ParseTuple(args, "O!O|iiiii", &PyBytes_Type, &fontObj, &inSeq, &verbose, &allowEdit, &allowHintSub, &roundCoords, &debug)) return NULL; inSeq = PySequence_Fast(inSeq, "argument must be sequence"); if (!inSeq) return NULL; fontInfo = PyBytes_AsString(fontObj); AC_SetMemManager(NULL, memoryManager); AC_SetReportCB(reportCB, verbose); bezLen = PySequence_Fast_GET_SIZE(inSeq); outSeq = PyTuple_New(bezLen); if (!outSeq) { error = true; } else { int i = 0; for (i = 0; i < bezLen; i++) { char* bezData = NULL; char* output = NULL; size_t outputSize = 0; int result; PyObject* itemObj = PySequence_Fast_GET_ITEM(inSeq, i); bezData = PyBytes_AsString(itemObj); if (!bezData) { error = true; break; } outputSize = 4 * strlen(bezData); output = MEMNEW(outputSize); result = AutoColorString(bezData, fontInfo, output, &outputSize, allowEdit, allowHintSub, roundCoords, debug); if (result == AC_DestBuffOfloError) { output = MEMRENEW(output, outputSize); AC_SetReportCB(reportCB, false); result = AutoColorString(bezData, fontInfo, output, &outputSize, allowEdit, allowHintSub, roundCoords, debug); AC_SetReportCB(reportCB, verbose); } if (outputSize != 0 && result == AC_Success) { PyObject* bezObj = PyBytes_FromString(output); PyTuple_SET_ITEM(outSeq, i, bezObj); } MEMFREE(output); if (result != AC_Success) { switch (result) { case AC_FontinfoParseFail: PyErr_SetString(PsAutoHintError, "Parsing font info failed"); break; case AC_FatalError: PyErr_SetString(PsAutoHintError, "Fatal error"); break; case AC_MemoryError: PyErr_NoMemory(); break; case AC_UnknownError: PyErr_SetString(PsAutoHintError, "Hinting failed"); break; case AC_DestBuffOfloError: PyErr_SetString(PsAutoHintError, "Dest buffer small"); break; case AC_InvalidParameterError: PyErr_SetString(PyExc_ValueError, "Invalid glyph data"); break; } error = true; break; } } } Py_XDECREF(inSeq); if (error) { Py_XDECREF(outSeq); return NULL; } return outSeq; } /* clang-format off */ static PyMethodDef psautohint_methods[] = { { "autohint", autohint, METH_VARARGS, autohint_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); #if PY_MAJOR_VERSION >= 3 /* 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; } #else /* Python < 3 */ PyMODINIT_FUNC init_psautohint(void) { PyObject* m; m = Py_InitModule3("_psautohint", psautohint_methods, psautohint_doc); if (m == NULL) return; SETUPMODULE return; } #endif psautohint-1.1.0/python/psautohint/autohint.py000066400000000000000000000513341324015706300216420ustar00rootroot00000000000000#!/bin/env python from __future__ import print_function, absolute_import __copyright__ = """\ Copyright 2016 Adobe Systems Incorporated (http://www.adobe.com/). 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 sys import os import re import time import plistlib import warnings import traceback import shutil from psautohint import psautohint kACIDKey = "AutoHintKey" gLogFile = None kFontPlistSuffix = ".plist" kTempCFFSuffix = ".temp.ac.cff" class ACOptions: def __init__(self): self.inputPath = None self.outputPath = None self.glyphList = [] self.nameAliases = {} self.excludeGlyphList = False self.usePlistFile = False self.hintAll = False self.rehint = False self.verbose = True self.quiet = False self.allowChanges = False self.noFlex = False self.noHintSub = False self.allow_no_blues = False self.hCounterGlyphs = [] self.vCounterGlyphs = [] self.counterHintFile = None self.logOnly = False self.logFile = None self.printDefaultFDDict = False self.printFDDictList = False self.debug = False self.allowDecimalCoords = False self.writeToDefaultLayer = False class ACFontInfoParseError(Exception): pass class ACFontError(Exception): pass class ACHintError(Exception): pass kProgressChar = "." def logMsg(*args): for arg in args: msg = str(arg).strip() if not msg: print sys.stdout.flush() if gLogFile: gLogFile.write("\n") gLogFile.flush() return if msg[-1] == ",": msg = msg[:-1] if msg == kProgressChar: sys.stdout.write(msg) # avoid space, which is added by 'print' else: print(msg,) sys.stdout.flush() if gLogFile: gLogFile.write(msg) gLogFile.flush() else: print(msg) sys.stdout.flush() if gLogFile: gLogFile.write(msg + "\n") gLogFile.flush() def getGlyphID(glyphTag, fontGlyphList): glyphID = None try: glyphID = int(glyphTag) glyphName = fontGlyphList[glyphID] except IndexError: pass except ValueError: try: glyphID = fontGlyphList.index(glyphTag) except IndexError: pass except ValueError: pass return glyphID def getGlyphNames(glyphTag, fontGlyphList, fontFileName): glyphNameList = [] rangeList = glyphTag.split("-") prevGID = getGlyphID(rangeList[0], fontGlyphList) if prevGID == None: if len(rangeList) > 1: logMsg("\tWarning: glyph ID <%s> in range %s from glyph selection list option is not in font. <%s>." % (rangeList[0], glyphTag, fontFileName)) else: logMsg("\tWarning: 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 == None: logMsg("\tWarning: 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 != None: glyphList.extend(glyphNames) if options.excludeGlyphList: newList = filter(lambda name: name not in glyphList, fontGlyphList) glyphList = newList return glyphList def openFontPlistFile(psName, dirPath): # Find or create the plist file. # This holds a Python dictionary in repr() form, # key: glyph name # value: outline point list # This is used to determine which glyphs are manually hinted, # and which have changed since the last hint pass. fontPlist = None filePath = None isNewPlistFile = True pPath1 = os.path.join(dirPath, psName + kFontPlistSuffix) if os.path.exists(pPath1): filePath = pPath1 else: # Crude approach to file length limitations. # Since Adobe keeps face info in separate directories, I don't worry # about name collisions. pPath2 = os.path.join(dirPath, psName[:-len(kFontPlistSuffix)] + kFontPlistSuffix) if os.path.exists(pPath2): filePath = pPath2 if not filePath: filePath = pPath1 else: try: fontPlist = plistlib.Plist.fromFile(filePath) isNewPlistFile = False except (IOError, OSError): raise ACFontError("\tError: font plist file exists, but could not be read <%s>." % filePath) except: raise ACFontError("\tError: font plist file exists, but could not be parsed <%s>." % filePath) if fontPlist == None: fontPlist = plistlib.Plist() if kACIDKey not in fontPlist: fontPlist[kACIDKey] = {} return fontPlist, filePath, isNewPlistFile 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', ] integerPattern = """ -?\d+""" arrayPattern = """ \[[ ,0-9]+\]""" stringPattern = """ \S+""" counterPattern = """ \([\S ]+\)""" def printFontInfo(fontInfoString): for item in fontInfoKeywordList: if item in ['FontName', 'FlexOK']: matchingExp = item + stringPattern elif item in ['VCounterChars', 'HCounterChars']: matchingExp = item + counterPattern elif item in ['DominantV', 'DominantH']: matchingExp = item + arrayPattern else: matchingExp = item + integerPattern try: print('\t%s' % re.search(matchingExp, fontInfoString).group()) except: pass flexPatthern = re.compile(r"preflx1[^f]+preflx2[\r\n](-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+)(-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+-*\d+\s+).+?flx([\r\n])", re.DOTALL) commentPattern = re.compile(r"[^\r\n]*%[^\r\n]*[\r\n]") hintGroupPattern = re.compile(r"beginsubr.+?newcolors[\r\n]", re.DOTALL) whiteSpacePattern = re.compile(r"\s+", re.DOTALL) def makeACIdentifier(bezText): # Get rid of all the hint operators and their args # collapse flex to just the two rct's bezText = commentPattern.sub("", bezText) bezText = hintGroupPattern.sub("", bezText) bezText = flexPatthern.sub("\1 rct\3\2 rct\3", bezText) bezText = whiteSpacePattern.sub("", bezText) return bezText def openFile(path, outFilePath, useHashMap, options): if os.path.isfile(path): font = openOpenTypeFile(path, outFilePath, options) else: # maybe it is a a UFO font. # We always use the hash map to skip glyphs that have been previously # processed, unless the user has report only, not make changes. font = openUFOFile(path, outFilePath, useHashMap, options) return font def openUFOFile(path, outFilePath, useHashMap, options): from psautohint import ufoFont # Check if has glyphs/contents.plist contentsPath = os.path.join(path, "glyphs", "contents.plist") if not os.path.exists(contentsPath): msg = "Font file must be a CFF, OTF, or ufo font file: %s." % (path) logMsg(msg) raise ACFontError(msg) # If user has specified a path other than the source font path, # then copy the entire UFO font, and operate on the copy. if (outFilePath != None) and (os.path.abspath(path) != os.path.abspath(outFilePath)): if not options.quiet: msg = "Copying from source UFO font to output UFO font before processing..." logMsg(msg) if os.path.exists(outFilePath): shutil.rmtree(outFilePath) shutil.copytree(path , outFilePath) path = outFilePath font = ufoFont.UFOFontData(path, useHashMap, ufoFont.kAutohintName) font.useProcessedLayer = True # Programs in this list must be run before autohint, # if the outlines have been edited. font.requiredHistory.append(ufoFont.kCheckOutlineName) return font def openOpenTypeFile(path, outFilePath, options): from psautohint.otfFont import CFFFontData from fontTools.ttLib import TTFont, getTableModule # If input font is CFF, build a dummy ttFont in memory. # Return ttFont, and flag if is a real OTF font. # Return flag is 0 if OTF, and 1 if CFF. fontType = 0 # OTF try: with open(path, "rb") as ff: data = ff.read(10) except (IOError, OSError): logMsg("Failed to open and read font file %s." % path) if data[:4] == b"OTTO": # it is an OTF font, can process file directly try: ttFont = TTFont(path) except (IOError, OSError): raise ACFontError("Error opening or reading from font file <%s>." % path) except TTLibError: raise ACFontError("Error parsing font file <%s>." % path) try: cffTable = ttFont["CFF "] except KeyError: raise ACFontError("Error: font is not a CFF font <%s>." % fontFileName) else: # It is not an OTF file. if (data[0] == b'\1') and (data[1] == b'\0'): # CFF file fontType = 1 else: logMsg("Font file must be a CFF or OTF fontfile: %s." % path) raise ACFontError("Font file must be CFF or OTF file: %s." % path) # now package the CFF font as an OTF font. with open(path, "rb") as ff: data = ff.read() try: ttFont = TTFont() cffModule = getTableModule('CFF ') cffTable = cffModule.table_C_F_F_('CFF ') ttFont['CFF '] = cffTable cffTable.decompile(data, ttFont) except: logMsg("\t%s" %(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1])) logMsg("Attempted to read font %s as CFF." % path) raise ACFontError("Error parsing font file <%s>." % path) fontData = CFFFontData(ttFont, path, outFilePath, fontType, logMsg) return fontData def removeTempFiles(fileList): for filePath in fileList: if os.path.exists(filePath): os.remove(filePath) def cmpFDDictEntries(entry1, entry2): # entry = [glyphName, [fdIndex, glyphListIndex] ] if entry1[1][1] > entry2[1][1]: return 1 elif entry1[1][1] < entry2[1][1]: return -1 else: return 0 def hintFile(options): global gLogFile gLogFile = options.logFile nameAliases = options.nameAliases path = options.inputPath fontFileName = os.path.basename(path) if not options.quiet: logMsg("Hinting font %s. Start time: %s." % (path, time.asctime())) try: # For UFO fonts only. # We always use the hash map, unless the user requested only report issues. useHashMap = not options.logOnly fontData = openFile(path, options.outputPath, useHashMap, options) fontData.allowDecimalCoords = options.allowDecimalCoords if options.writeToDefaultLayer and hasattr(fontData, "setWriteToDefault"): # UFO fonts only fontData.setWriteToDefault() except (IOError, OSError): logMsg(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1]) raise ACFontError("Error opening or reading from font file <%s>." % fontFileName) except: logMsg(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1]) raise ACFontError("Error parsing font file <%s>." % fontFileName) # filter specified list, if any, with font list. fontGlyphList = fontData.getGlyphList() glyphList = filterGlyphList(options, fontGlyphList, fontFileName) if not glyphList: raise ACFontError("Error: selected glyph list is empty for font <%s>." % fontFileName) fontInfo = "" psName = fontData.getPSName() if (not options.logOnly) and options.usePlistFile: fontPlist, fontPlistFilePath, isNewPlistFile = openFontPlistFile(psName, os.path.dirname(path)) if isNewPlistFile and not (options.hintAll or options.rehint): logMsg("No hint info plist file was found, so all glyphs are unknown to autohint. To hint all glyphs, run autohint again with option -a to hint all glyphs unconditionally.") logMsg("Done with font %s. End time: %s." % (path, time.asctime())) fontData.close() return # Check counter glyphs, if any. if options.hCounterGlyphs or options.vCounterGlyphs: missingList = filter(lambda name: name not in fontGlyphList, options.hCounterGlyphs + options.vCounterGlyphs) if missingList: logMsg("\tError: glyph named in counter hint list file '%s' are not in font: %s" % (options.counterHintFile, missingList)) # Build alignment zone string if (options.printDefaultFDDict): logMsg("Showing default FDDict Values:") fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs) printFontInfo(str(fdDict)) fontData.close() return fdGlyphDict, fontDictList = fontData.getfdInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, glyphList) if options.printFDDictList: # Print the user defined FontDicts, and exit. if fdGlyphDict: logMsg("Showing user-defined FontDict Values:") for fi in range(len(fontDictList)): fontDict = fontDictList[fi] logMsg("") logMsg(fontDict.DictName) printFontInfo(str(fontDict)) gnameList = [] itemList = fdGlyphDict.items() itemList.sort(cmpFDDictEntries) for gName, entry in itemList: if entry[0] == fi: gnameList.append(gName) logMsg("%d glyphs:" % len(gnameList)) if len(gnameList) > 0: gTxt = " ".join(gnameList) else: gTxt = "None" logMsg(gTxt) else: logMsg("There are no user-defined FontDict Values.") fontData.close() return if fdGlyphDict == None: fdDict = fontDictList[0] fontInfo = fdDict.getFontInfo() else: if not options.verbose and not options.quiet: logMsg("Note: Using alternate FDDict global values from fontinfo file for some glyphs. Remove option '-q' to see which dict is used for which glyphs.") # Get charstring for identifier in glyph-list isCID = fontData.isCID() lastFDIndex = None anyGlyphChanged = False pListChanged = False if isCID: options.noFlex = True if not options.verbose: dotCount = 0 curTime = time.time() dotCount = 0 seenGlyphCount = 0 processedGlyphCount = 0 for name in glyphList: prevACIdentifier = None seenGlyphCount += 1 # Convert to bez format bezString, width, hasHints = fontData.convertToBez(name, options.verbose, options.hintAll) processedGlyphCount += 1 if bezString == None: continue if "mt" not in bezString: # skip empty glyphs. continue # get new fontinfo string if FDarray index has changed, # as each FontDict has different alignment zones. gid = fontData.getGlyphID(name) if isCID: # fdIndex = fontData.getfdIndex(gid) if not fdIndex == lastFDIndex: lastFDIndex = fdIndex fdDict = fontData.getFontInfo(psName, path, options.allow_no_blues, options.noFlex, options.vCounterGlyphs, options.hCounterGlyphs, fdIndex) fontInfo = fdDict.getFontInfo() else: if (fdGlyphDict != None): try: fdIndex = fdGlyphDict[name][0] except KeyError: # use default dict. fdIndex = 0 if lastFDIndex != fdIndex: lastFDIndex = fdIndex fdDict = fontDictList[fdIndex] fontInfo = fdDict.getFontInfo() # Build autohint point list identifier oldBezString = "" oldHintBezString = "" if (not options.logOnly) and options.usePlistFile: # If the glyph is not in the plist file, then we skip it unless # kReHintUnknown is set. # If the glyph is in the plist file and the outline has changed, # we hint it. ACidentifier = makeACIdentifier(bezString) try: (prevACIdentifier, ACtime, oldBezString, oldHintBezString) = fontPlist[kACIDKey][name] except ValueError: (prevACIdentifier, ACtime) = fontPlist[kACIDKey][name] oldBezString = oldHintBezString = "" except KeyError: # there wasn't an entry in tempList file, so we will add one. pListChanged = True if hasHints and not options.rehint: # Glyphs is hinted, but not referenced in the plist file. # Skip it unless options.rehint is seen if not isNewPlistFile: # Comment only if there is a plist file; otherwise, we'd # be complaining for almost every glyph. logMsg("%s Skipping glyph - it has hints, but it is not in the hint info plist file." % nameAliases.get(name, name)) dotCount = 0 continue # there's an entry in the plist file and it matches what's in the font if prevACIdentifier and (prevACIdentifier == ACidentifier): if hasHints and not (options.hintAll or options.rehint): continue else: pListChanged = True if options.verbose: if fdGlyphDict: logMsg("Hinting %s with fdDict %s." % (nameAliases.get(name, name), fdDict.DictName)) else: logMsg("Hinting %s." % nameAliases.get(name, name)) elif not options.quiet: logMsg(".,") dotCount += 1 if dotCount > 40: dotCount = 0 logMsg("") # I do this to never have more than 40 dots on a line. # This in turn give reasonable performance when calling autohint # in a subprocess and getting output with std.readline() # Call auto-hint library on bez string. #print("oldBezString", oldBezString) #print("") #print("bezString", bezString) if oldBezString != "" and oldBezString == bezString: newBezString = oldHintBezString else: newBezString = psautohint.autohint(fontInfo, [bezString], options.verbose, options.allowChanges, not options.noHintSub, options.allowDecimalCoords) newBezString = newBezString[0] if not newBezString: if not options.verbose and not options.quiet: logMsg("") raise ACHintError("%s Error - failure in processing outline data." % nameAliases.get(name, name)) if not (("ry" in newBezString[:200]) or ("rb" in newBezString[:200]) or ("rm" in newBezString[:200]) or ("rv" in newBezString[:200])): print("No hints added!") if options.logOnly: continue # Convert bez to charstring, and update CFF. anyGlyphChanged = True fontData.updateFromBez(newBezString, name, width, options.verbose) if options.usePlistFile: bezString = "%% %s\n%s" % (name, newBezString) ACidentifier = makeACIdentifier(bezString) # add glyph hint entry to plist file if options.allowChanges: if prevACIdentifier and (prevACIdentifier != ACidentifier): logMsg("\t%s Glyph outline changed" % nameAliases.get(name, name)) dotCount = 0 fontPlist[kACIDKey][name] = (ACidentifier, time.asctime(), bezString, newBezString) if not options.verbose and not options.quiet: print("") # print final new line after progress dots. if not options.logOnly: if anyGlyphChanged: if not options.quiet: logMsg("Saving font file with new hints..." + time.asctime()) fontData.saveChanges() else: fontData.close() if options.usePlistFile: if options.rehint: logMsg("No new hints. All glyphs had hints that matched the hint record file %s." % (fontPlistFilePath)) if options.hintAll: logMsg("No new hints. All glyphs had hints that matched the hint history file %s, or were not in the history file and already had hints." % (fontPlistFilePath)) else: logMsg("No new hints. All glyphs were already hinted.") else: logMsg("No glyphs were hinted.") if options.usePlistFile and (anyGlyphChanged or pListChanged): # save font plist file. fontPlist.write(fontPlistFilePath) if processedGlyphCount != seenGlyphCount: logMsg("Skipped %s of %s glyphs." % (seenGlyphCount - processedGlyphCount, seenGlyphCount)) if not options.quiet: logMsg("Done with font %s. End time: %s." % (path, time.asctime())) psautohint-1.1.0/python/psautohint/fdTools.py000066400000000000000000000400731324015706300214170ustar00rootroot00000000000000#!/bin/env python from __future__ import print_function, absolute_import __doc__ = """ fdTools.py. v 1.9 April 16 2016 Convert a Type 1 font to CID, given multiple hint dict defs in the "fontinfo" file. See AC.py help, with the "-hfd" option, 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. Note that this file makes a lot of temporary files, using the input font path as the base file path, so the parent directory needs to be read/write enabled. """ __copyright__ = """Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved. """ import re import types # 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"] kFontDictBluePairsName = "BlueValuesPairs" # We keep this in the FDDict, as it is easier to sort and validate as a list of pairs kFontDictOtherBluePairsName = "OtherBlueValuesPairs" kFontDictBluesName = "BlueValues" # Holds the actual string for the Type1 font dict kFontDictOtherBluesName = "OtherBlues" # Holds the actual string for the Type1 font dict kRunTimeFDDictKeys = ["DictName", kFontDictBluePairsName, kFontDictOtherBluePairsName, kFontDictBluesName, kFontDictOtherBluesName] kFDDictKeys = kOtherFDDictKeys + kBlueValueKeys + kOtherBlueValueKeys + kRunTimeFDDictKeys class FontInfoParseError(ValueError): pass class FDDict: def __init__(self): self.DictName=None for key in kFDDictKeys: exec("self.%s = None" % (key)) self.FlexOK="true" def getFontInfo(self): keys = dir(self) fiList = [] for key in keys: if key.startswith("_") or (key in kRunTimeFDDictKeys): continue value = eval("self.%s" % (key)) if type(value) == types.MethodType: continue if value != None: fiList.append("%s %s" % (key, value)) return " ".join(fiList) def buildBlueLists(self): if (self.BaselineOvershoot == None): print("Error: FDDict definition %s is missing the BaselineYCoord/BaselineOvershoot values. These are required." % (self.DictName)) elif (int(self.BaselineOvershoot) > 0): print("Error: 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 = [] keyIndex = 0 for key in keyList: if key.endswith("Overshoot"): width = eval("self.%s" % (key)) if width != None: width = int(width) baseName = key[:-len("Overshoot")] zonePos = None if key == "BaselineOvershoot": zonePos = eval("self.BaselineYCoord") zonePos = int(zonePos) tempKey = "BaselineYCoord" else: for posSuffix in ["", "Height", "Baseline"]: tempKey = "%s%s" % (baseName, posSuffix) try: zonePos = eval("self.%s" % (tempKey)) zonePos = int(zonePos) break except AttributeError: continue if zonePos == 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"): print("Error: 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): print("Error: 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*self.BlueFuzz + 1 for pair in bluePairList[1:]: if prevPair[0] > pair[1]: print("Error 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: print("Error 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 exec("self.%s = %s" % (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) ) #print(self.DictName, bluePairList) #print("\t", bluesList) exec("self.%s = \"%s\"" % (fieldName, bluesList)) return def __repr__(self): printStr = [] keys = dir(self) for key in keys: val = eval("self.%s" % (key)) # print(key, type(val)) if (val == None) or (type(val) == types.MethodType) or key.startswith("_"): continue printStr.append(key) printStr.append("%s" % (val)) return " ".join(printStr) def parseFontInfoFile(fontDictList, data, glyphList, maxY, minY, fontName, blueFuzz): # fontDictList may or may not already contain a font dict taken from the source font top FontDict. fdGlyphDict = {} # The map of glyph names to font dict: the index into fontDictList. finalFDict = None # 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. # 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) 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) exec("fdDict.%s = \"%s\"" % (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): fdGlyphDict[gname] = [fdIndexDict[setName], gi] # fdIndex value 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 == None: print("Warning: the FDDict '%s' in fontinfo has no DominantH value" % (dictName)) if fdDict.DominantV == None: print("Warning: the FDDict '%s' in fontinfo has no DominantV value" % (dictName)) if (fdDict.BlueFuzz == None): fdDict.BlueFuzz = blueFuzz fdDict.buildBlueLists() if fdDict.FontName == 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: exec("fdDict.%s = \"%s\"" % (token, value)) else: raise FontInfoParseError("FDDict key \"%s\" in fdDict named \"%s\" is not recognised." % ( 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 AC 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: exec("defaultFDDict.%s = None" % (key)) 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 = {} blueKeyList = [kBlueValueKeys, kOtherBlueValueKeys] bluePairListNames = [kFontDictBluePairsName, kFontDictOtherBluePairsName] zoneDictList = [blueZoneDict, otherBlueZoneDict] for prefDDict in prevDictList: for ki in [0,1]: zoneDict = zoneDictList[ki] bluePairName = bluePairListNames[ki] bluePairList = eval("prefDDict.%s" % (bluePairName)) if not bluePairList: continue for topPos, bottomPos, zoneName, dictName, 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 = eval("prefDDict.%s" % (stemFieldName)) stemDict = stemDictList[wi] if dList != 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]) seenProblem = 0 prev = zoneList[0] zoneBuffer = 2*prefDDict.BlueFuzz + 1 for zone in zoneList[1:]: if (ki == 0) and (len(zoneList) >= 14): print("Warning. For final FontDict, skipping BlueValues alignment zone %s from FDDict %s because there are already 7 zones." % (zoneName, fdDictName)) elif (ki == 1) and (len(zoneList) >= 5): print("Warning. For final FontDict, skipping OtherBlues alignment zone %s from FDDict %s because there are already 5 zones." % (zoneName, fdDictName)) if zone[1] < prevZone[0]: curEntry = blueZoneDict[zone] prevEntry = blueZoneDict[prevZone] zoneName = curEntry[1] fdDictName = curEntry[2] prevZoneName = prevEntry[1] prevFDictName = prevEntry[2] print("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: curEntry = blueZoneDict[zone] prevEntry = blueZoneDict[prevZone] zoneName = curEntry[1] fdDictName = curEntry[2] prevZoneName = prevEntry[1] prevFDictName = prevEntry[2] print("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: fdDictName = stemDict[stem] prevFDictName = stemDict[prevStem] print("Warning. For final FontDict, skipping stem width %s in FDDict %s because it overlaps in coverage with stem width %s in FDDict %s." % (stem, fdDictName, prevStem, prevFDictName)) 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-1.1.0/python/psautohint/otfFont.py000066400000000000000000001146141324015706300214270ustar00rootroot00000000000000from __future__ import print_function, absolute_import """ otfFont.py v1.13 July 11 2017 Utilities for converting between T2 charstrings and the bez data format. Used by AC and focus/CheckOutlines. """ __copyright__ = """\ Copyright 2014-2017 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved. """ import sys import re import os from fontTools.misc.psCharStrings import T2OutlineExtractor, SimpleT2Decompiler from fontTools.misc.py23 import * from psautohint import fdTools debug = False def debugMsg(*args): if debug: print(args) kStackLimit = 46 kStemLimit = 96 class ACFontError(Exception): pass class SEACError(Exception): pass 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 = byteord(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, allowDecimals=False): T2OutlineExtractor.__init__(self, None, localSubrs, globalSubrs, nominalWidthX, defaultWidthX) self.vhints = [] self.hhints = [] self.bezProgram = [] self.firstMarkingOpSeen = False self.closePathSeen = False self.subrLevel = 0 self.allowDecimals = allowDecimals 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 rMoveTo(self, point): point = self._nextPoint(point) if not self.firstMarkingOpSeen : self.firstMarkingOpSeen = True self.bezProgram.append("sc\n") debugMsg("moveto", point, "curpos", self.currentPoint) x = point[0] y = point[1] if (not self.allowDecimals): x = int(round(x)) y = int(round(y)) self.bezProgram.append("%s %s mt\n" % (x, y)) else: self.bezProgram.append("%.2f %.2f mt\n" % (x, y)) self.sawMoveTo = 1 def rLineTo(self, point): point = self._nextPoint(point) if not self.firstMarkingOpSeen : self.firstMarkingOpSeen = True self.bezProgram.append("sc\n") self.bezProgram.append("0 0 mt\n") debugMsg("lineto", point, "curpos", self.currentPoint) if not self.sawMoveTo: self.rMoveTo((0, 0)) x = point[0] y = point[1] if (not self.allowDecimals): x = int(round(x)) y = int(round(y)) self.bezProgram.append("%s %s dt\n" % (x, y)) else: self.bezProgram.append("%.2f %.2f dt\n" % (x, y)) def rCurveTo(self, pt1, pt2, pt3): pt1 = list(self._nextPoint(pt1)) pt2 = list(self._nextPoint(pt2)) pt3 = list(self._nextPoint(pt3)) if not self.firstMarkingOpSeen : self.firstMarkingOpSeen = True self.bezProgram.append("sc\n") self.bezProgram.append("0 0 mt\n") debugMsg("curveto", pt1, pt2, pt3, "curpos", self.currentPoint) if not self.sawMoveTo: self.rMoveTo((0, 0)) if (not self.allowDecimals): for pt in [pt1, pt2, pt3]: pt[0] = int(round(pt[0])) pt[1] = int(round(pt[1])) self.bezProgram.append("%s %s %s %s %s %s ct\n" % (pt1[0], pt1[1], pt2[0], pt2[1], pt3[0], pt3[1])) else: self.bezProgram.append("%.2f %.2f %.2f %.2f %.2f %.2f ct\n" % (pt1[0], pt1[1], pt2[0], pt2[1], pt3[0], pt3[1])) 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: debugMsg("endPath") self.bezProgram.append("cp\n") self.sawMoveTo = 0 def closePath(self): self.closePathSeen = True debugMsg("closePath") if self.bezProgram and self.bezProgram[-1] != "cp\n": self.bezProgram.append("cp\n") self.bezProgram.append("ed\n") def op_hstem(self, index): args = self.popallWidth() self.hhints = [] self.countHints(args) debugMsg("hstem", self.hhints) def op_vstem(self, index): args = self.popallWidth() self.vhints = [] self.countHints(args) debugMsg("vstem", self.vhints) def op_hstemhm(self, index): args = self.popallWidth() self.hhints = [] self.countHints(args) debugMsg("stemhm", self.hhints, args) def op_vstemhm(self, index): args = self.popallWidth() self.vhints = [] self.countHints(args) debugMsg("vstemhm", self.vhints, args) def doMask(self, index, bezCommand): args = [] if not self.hintMaskBytes: args = self.popallWidth() if args: self.vhints = [] self.countHints(args) self.hintMaskBytes = int((self.hintCount + 7) / 8) self.hintMaskString, index = self.callingStack[-1].getBytes(index, self.hintMaskBytes) 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, allowDecimals=False): # wrapper for T2ToBezExtractor which applies it to the supplied T2 charstring bezString = "" subrs = getattr(t2CharString.private, "Subrs", []) extractor = T2ToBezExtractor( subrs, t2CharString.globalSubrs, t2CharString.private.nominalWidthX, t2CharString.private.defaultWidthX, allowDecimals) extractor.execute(t2CharString) if extractor.gotWidth: t2Wdth = extractor.width - t2CharString.private.nominalWidthX else: t2Wdth = None return "".join(extractor.bezProgram), extractor.hintCount > 0, t2Wdth class HintMask: # class used to collect hints for the current hint mask when converting bez to T2. def __init__(self, listPos): self.listPos = listPos # The index into the t2list is kept so we can quickly find them later. self.hList = [] # These contain the actual hint values. self.vList = [] def maskByte(self, hHints, vHints): # return hintmask bytes for known hints. numHHints = len(hHints) numVHints = len(vHints) maskVal = 0 byteIndex = 0 self.byteLength = byteLength = int((7 + numHHints + numVHints)/8) mask = b"" self.hList.sort() for hint in self.hList: try: i = hHints.index(hint) except ValueError: continue # we get here if some hints have been dropped because of the stack limit newbyteIndex = int(i/8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i %8)) self.vList.sort() for hint in self.vList: try: i = numHHints + vHints.index(hint) except ValueError: continue # we get here if some hints have been dropped because of the stack limit newbyteIndex = int(i/8) if newbyteIndex != byteIndex: mask += bytechr(maskVal) byteIndex += 1 while byteIndex < newbyteIndex: mask += b"\0" byteIndex += 1 maskVal = 0 maskVal += 2**(7 - (i %8)) if maskVal: mask += bytechr(maskVal) if len(mask) < byteLength: mask += b"\0"*(byteLength - len(mask)) self.mask = mask return mask def makeHintList(hints, needHintMasks, isH): # Add the list of T2 tokens that make up the initial hint operators hintList = [] 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 (type(pos) == float) and (int(pos) == pos): pos = int(pos) hintList.append(pos) pos2 = hint[1] if (type(pos2) == float) and (int(pos2) == pos2): pos2 = int(pos2) lastPos = pos1 + pos2 hintList.append(pos2) if needHintMasks: if isH: op = "hstemhm" hintList.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 isH: op = "hstem" else: op = "vstem" hintList.append(op) return hintList bezToT2 = { "mt" : 'rmoveto', "rmt" : 'rmoveto', "hmt" : 'hmoveto', "vmt" : 'vmoveto', "dt" : 'rlineto', "rdt" : 'rlineto', "hdt" : 'hlineto', "vdt" : 'vlineto', "ct" : 'rrcurveto', "rct" : 'rrcurveto', "rcv" : 'rrcurveto', # Morisawa's alternate name for 'rct'. "vhct": 'vhcurveto', "hvct": 'hvcurveto', "cp" : '', "ed" : 'endchar' } def optimizeT2Program(t2List): # Assumes T2 operands are in a list with one entry per operand, and each # entry is a list of [argList, opToken]. # Matches logic in tx, and Adobe low level library. # Note that I am expecting only rlineto, vlineto, hlineto, vhcurveto, # hvcurveto, rrcurveto from AC. # The other optimized operators, rcurveline and rlinecurve are not supported # as input here. newT2List = [] arglist = [] kNoOp = "noop" pendingOp = kNoOp sequenceOp = kNoOp for entry in t2List: op = entry[1] args = entry[0] if op == "vlineto": dy = args[-1] if (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp == "hlineto"): arglist.append(dy) sequenceOp = "vlineto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-1], pendingOp]) arglist = [dy] pendingOp = "vlineto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dy] pendingOp = sequenceOp = "vlineto" elif op == "hlineto": dx = args[-1] if (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp == "vlineto"): arglist.append(dx) sequenceOp = "hlineto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-1], pendingOp]) arglist = [dx] pendingOp = "hlineto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dx] pendingOp = sequenceOp = "hlineto" elif op == "rlineto": dx = args[-2] dy = args[-1] if dx == 0: if (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp == "hlineto"): arglist.append(dy) sequenceOp = "vlineto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-1], pendingOp]) arglist = [dy] pendingOp = "vlineto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dy] pendingOp = sequenceOp = "vlineto" elif dy == 0: if (pendingOp in ["vlineto", "hlineto"]) and (sequenceOp == "vlineto"): arglist.append(dx) sequenceOp = "hlineto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-1], pendingOp]) arglist = [dx] pendingOp = "hlineto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dx] pendingOp = sequenceOp = "hlineto" elif pendingOp == "rrcurveto": arglist.extend([dx,dy]) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-2], pendingOp]) arglist = [dx, dy] pendingOp = sequenceOp = "rlineto" else: newT2List.append([arglist, "rcurveline"]) arglist = [] pendingOp = sequenceOp = kNoOp elif (pendingOp == op) and (sequenceOp == op): arglist.extend([dx,dy]) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-2], pendingOp]) arglist = [dx, dy] else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dx,dy] pendingOp = sequenceOp = op elif op == "vhcurveto": if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "hvcurveto"): sequenceOp = "vhcurveto" arglist.extend(args) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-len(args)], pendingOp]) arglist = args pendingOp = sequenceOp = op else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = args pendingOp = sequenceOp = "vhcurveto" if len(args) == 5: newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp elif op == "hvcurveto": if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "vhcurveto"): sequenceOp = "hvcurveto" arglist.extend(args) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-len(args)], pendingOp]) arglist = args pendingOp = sequenceOp = op else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = args pendingOp = sequenceOp = "hvcurveto" if len(args) == 5: newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp elif op == "rrcurveto": dx1 = args[0] dy1 = args[1] dx2 = args[2] dy2 = args[3] dx3 = args[4] dy3 = args[5] if dx1 == 0: if dy3 == 0: # - dy1 dx2 dy2 dx3 - vhcurveto if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "hvcurveto"): arglist.extend([dy1, dx2, dy2, dx3]) sequenceOp = "vhcurveto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-4], pendingOp]) arglist = [dy1, dx2, dy2, dx3] pendingOp = "vhcurveto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dy1, dx2, dy2, dx3] pendingOp = sequenceOp = "vhcurveto" elif dx3 == 0: # - dy1 dx2 dy2 - dy3 vvcurveto if pendingOp not in ["vvcurveto", kNoOp]: newT2List.append([arglist, pendingOp]) arglist = [] arglist.extend([dy1, dx2, dy2, dy3 ]) sequenceOp = "vvcurveto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-4], pendingOp]) arglist = [dy1, dx2, dy2, dy3] pendingOp = sequenceOp else: pendingOp = sequenceOp else: # - dy1 dx2 dy2 dx3 dy3 vhcurveto (odd number of args, can't concatenate any more ops.) if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "hvcurveto"): arglist.extend([dy1, dx2, dy2, dx3, dy3]) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-5], pendingOp]) arglist = [dy1, dx2, dy2, dx3, dy3] pendingOp = "vhcurveto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dy1, dx2, dy2, dx3, dy3] pendingOp = "vhcurveto" newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp elif dy1 == 0: if dx3 == 0: # dx1 - dx2 dy2 - dy3 hvcurveto if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "vhcurveto"): arglist.extend([dx1, dx2, dy2, dy3]) sequenceOp = "hvcurveto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-4], pendingOp]) arglist = [dx1, dx2, dy2, dy3] pendingOp = "hvcurveto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dx1, dx2, dy2, dy3] pendingOp = sequenceOp = "hvcurveto" elif dy3 == 0: # dx1 - dx2 dy2 dx3 - hhcurveto if pendingOp not in ["hhcurveto", kNoOp]: newT2List.append([arglist, pendingOp]) arglist = [] arglist.extend([dx1, dx2, dy2, dx3 ]) sequenceOp = "hhcurveto" if len(arglist) >= kStackLimit: newT2List.append([arglist[:-4], pendingOp]) # XXX Problem. Was vvcurveto arglist = [dx1, dx2, dy2, dx3] pendingOp = sequenceOp else: pendingOp = sequenceOp else: # dx1 - dx2 dy2 dy3 dx3 hvcurveto (odd number of args, can't concatenate any more ops.) if (pendingOp in ["vhcurveto", "hvcurveto"]) and (sequenceOp == "vhcurveto"): arglist.extend([dx1, dx2, dy2, dy3, dx3]) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-5], pendingOp]) arglist = [dx1, dx2, dy2, dy3, dx3] pendingOp = "hvcurveto" else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [dx1, dx2, dy2, dy3, dx3] pendingOp = "hvcurveto" newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp elif dx3 == 0: # dx1 dy1 dx2 dy2 - dy3 vvcurveto (odd args) if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [] arglist = [dx1, dy1, dx2, dy2, dy3] pendingOp = "vvcurveto" newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp elif dy3 == 0: # dx1 dy1 dx2 dy2 dx3 - hhcurveto (odd args) if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [] arglist = [dy1, dx1, dx2, dy2, dx3] # note arg order swap pendingOp = "hhcurveto" newT2List.append([arglist, pendingOp]) arglist = [] pendingOp = sequenceOp = kNoOp else: if pendingOp == "rlineto": arglist.extend(args) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-len(args)], pendingOp]) arglist = args pendingOp = sequenceOp = op else: newT2List.append([arglist, "rlinecurve"]) arglist = [] pendingOp = sequenceOp = kNoOp else: if pendingOp not in [kNoOp, "rrcurveto"]: newT2List.append([arglist, pendingOp]) arglist = [] arglist.extend(args) if len(arglist) >= kStackLimit: newT2List.append([arglist[:-len(args)], pendingOp]) arglist = args pendingOp = sequenceOp = op elif op == "flex": dx1 = args[0] dy1 = args[1] dx2 = args[2] dy2 = args[3] dx3 = args[4] dy3 = args[5] dx4 = args[6] dy4 = args[7] dx5 = args[8] dy5 = args[9] dx6 = args[10] dy6 = args[11] if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) arglist = [] noFlex = True if (dy3 == 0 == dy4): if (dy1 == dy6 == 0) and (dy2 == -dy5): newT2List.append([[dx1, dx2, dy2, dx3, dx4, dx5, dx6], "hflex"]) # the device pixel threshold is always 50 , when coming back from AC. noFlex = False else: dy = dy1 + dy2 + dy3 + dy4 + dy5 + dy6 if dy == 0: newT2List.append([[dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6], "hflex1"]) noFlex = False if noFlex: if 0: dx = dx1 + dx2 + dx3 + dx4 + dx5 dy = dy1 + dy2 + dy3 + dy4 + dy5 if ((dy + dy6) == 0) or ((dx+dx6) == 0): if abs(dx) > abs(dy): lastArg = dx6 else: lastArg = dy6 newT2List.append([args[:10] + [lastArg], "flex1"]) newLastArg = lastArg else: newT2List.append([args, "flex"]) else: newT2List.append([args, "flex"]) arglist = [] pendingOp = sequenceOp = kNoOp else: if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) newT2List.append([args, op]) arglist = [] pendingOp = sequenceOp = kNoOp if pendingOp != kNoOp: newT2List.append([arglist, pendingOp]) return newT2List kHintArgsNoOverlap = 0 kHintArgsOverLap = 1 kHintArgsMatch = 2 def checkStem3ArgsOverlap(argList, hintList): # status == 0 -> no overlap # status == 1 -> arg are the same # status = 2 -> args overlap, and are not the same status = kHintArgsNoOverlap for x0,x1 in argList: x1 = x0 + x1 for y0,y1 in hintList: 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 buildControlMaskList(hStem3List, vStem3List): """ The deal is that a charstring will use either counter hints, or stem 3 hints, but not both. We examine all the arglists. If any are not a multiple of 3, then we use all the arglists as is as the args to a counter hint. If all are a multiple of 3, then we divide them up into triplets, and add a separate conter mask for each unique arg set. """ vControlMask = HintMask(0) hControlMask = vControlMask controlMaskList = [hControlMask] for argList in hStem3List: for mask in controlMaskList: overlapStatus = kHintArgsNoOverlap if not mask.hList: mask.hList.extend(argList) overlapStatus = kHintArgsMatch break overlapStatus = checkStem3ArgsOverlap(argList, mask.hList) if overlapStatus == kHintArgsMatch: # The args match args in this control mask. break if overlapStatus != kHintArgsMatch: mask = HintMask(0) controlMaskList.append(mask) mask.hList.extend(argList) for argList in vStem3List: for mask in controlMaskList: overlapStatus = kHintArgsNoOverlap if not mask.vList: mask.vList.extend(argList) overlapStatus = kHintArgsMatch break overlapStatus = checkStem3ArgsOverlap(argList, mask.vList) if overlapStatus == kHintArgsMatch: # The args match args in this control mask. break if overlapStatus != kHintArgsMatch: mask = HintMask(0) controlMaskList.append(mask) mask.vList.extend(argList) return controlMaskList def makeRelativeCTArgs(argList, curX, curY): newCurX = argList[4] newCurY = argList[5] argList[5] -= argList[3] argList[4] -= argList[2] argList[3] -= argList[1] argList[2] -= argList[0] argList[0] -= curX argList[1] -= curY return argList, newCurX, newCurY def convertBezToT2(bezString): # 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. # 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. bezString = re.sub(r"%.+?\n", "", bezString) # suppress comments bezList = re.findall(r"(\S+)", bezString) if not bezList: return "" hhints = [] vhints = [] hintMask = HintMask(0) # Always assume a hint mask until proven otherwise. hintMaskList = [hintMask] vStem3Args = [] hStem3Args = [] vStem3List = [] hStem3List = [] argList = [] 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: argList.append(val2) else: argList.append("% 100 div" % (str(int(val1*100)))) except ValueError: argList.append(val1) continue except ValueError: pass if token == "newcolors": lastPathOp = token pass elif token in ["beginsubr", "endsubr"]: lastPathOp = token pass elif token in ["snc"]: lastPathOp = token hintMask = HintMask(len(t2List)) # The index into the t2list is kept so we can quickly find them later. t2List.append([hintMask]) hintMaskList.append(hintMask) elif token in ["enc"]: lastPathOp = token pass elif token == "div": # i specifically do NOT set lastPathOp for this. value = argList[-2]/float(argList[-1]) argList[-2:] =[value] elif token == "rb": lastPathOp = token try: i = hhints.index(argList) except ValueError: i = len(hhints) hhints.append(argList) if hintMask: if hhints[i] not in hintMask.hList: hintMask.hList.append(hhints[i]) argList = [] elif token == "ry": lastPathOp = token try: i = vhints.index(argList) except ValueError: i = len(vhints) vhints.append(argList) if hintMask: if vhints[i] not in hintMask.vList: hintMask.vList.append(vhints[i]) argList = [] elif token == "rm": # vstem3 hints are vhints try: i = vhints.index(argList) except ValueError: i = len(vhints) vhints.append(argList) if hintMask: if vhints[i] not in hintMask.vList: hintMask.vList.append(vhints[i]) 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. vStem3List.append(vStem3Args) vStem3Args = [] vStem3Args.append(argList) argList = [] lastPathOp = token elif token == "rv": # hstem3 are hhints try: i = hhints.index(argList) except ValueError: i = len(hhints) hhints.append(argList) if hintMask: if hhints[i] not in hintMask.hList: hintMask.hList.append(hhints[i]) if (lastPathOp != token) and hStem3Args: # first rv, must be start of a new h countermask hStem3List.append(hStem3Args) hStem3Args = [] hStem3Args.append(argList) argList = [] lastPathOp = token elif token == "preflx1": # The preflx1/preflx2 sequence provides the same 'i' as the flex # sequence. The difference is that the preflx1/preflx2 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 argList = [] elif token in ["preflx2", "preflx2a"]: lastPathOp = token del t2List[-1] argList = [] elif token == "flx": lastPathOp = token argList = argList[:12] t2List.append([argList + [50], "flex"]) argList = [] elif token == "flxa": lastPathOp = token argList1, curX, curY = makeRelativeCTArgs(argList[:6], curX, curY) argList2, curX, curY = makeRelativeCTArgs(argList[6:], curX, curY) argList = argList1 + argList2 t2List.append([argList[:12] + [50], "flex"]) argList = [] elif token == "sc": lastPathOp = token pass else: if token[-2:] in ["mt", "dt", "ct", "cv"]: lastPathOp = token t2Op = bezToT2.get(token,None) if token in ["mt", "dt"]: newList = [argList[0] - curX, argList[1]- curY] curX = argList[0] curY = argList[1] argList = newList elif token in ["ct", "cv"]: argList, curX, curY = makeRelativeCTArgs(argList, curX, curY) if t2Op: t2List.append([argList, t2Op]) elif t2Op == None: print("Unhandled operation", argList, token) raise KeyError argList = [] # 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. numHintMasks = len(hintMaskList) needHintMasks = numHintMasks > 1 if vStem3Args: vStem3List.append(vStem3Args) if hStem3Args : hStem3List.append(hStem3Args) t2Program = [] hhints.sort() vhints.sort() numHHints = len(hhints) numVHints = len(vhints) hintLimit = int((kStackLimit-2)/2) if numHHints >=hintLimit: hhints = hhints[:hintLimit] numHHints = hintLimit if numVHints >=hintLimit: vhints = vhints[:hintLimit] numVHints = hintLimit if hhints: isH = 1 t2Program = makeHintList(hhints, needHintMasks, isH) if vhints: isH = 0 t2Program += makeHintList(vhints, needHintMasks, isH) if vStem3List or hStem3List: controlMaskList = buildControlMaskList(hStem3List, vStem3List) for cMask in controlMaskList: hBytes = cMask.maskByte(hhints, vhints) t2Program.extend(["cntrmask", hBytes]) if needHintMasks: # If there is not a hintsub before any drawing operators, then # add an initial first hint mask to the t2Program. if hintMaskList[1].listPos != 0: hBytes = hintMaskList[0].maskByte(hhints, vhints) t2Program.extend(["hintmask", hBytes]) # Convert the rest of the hint masks to a hintmask op and hintmask bytes. for hintMask in hintMaskList[1:]: pos = hintMask.listPos t2List[pos] = [["hintmask"], hintMask.maskByte(hhints, vhints)] t2List = optimizeT2Program(t2List) for entry in t2List: try: t2Program.extend(entry[0]) t2Program.append(entry[1]) except: print("Failed to extend t2Program with entry", entry) raise KeyError return t2Program class CFFFontData: def __init__(self, ttFont, inputPath, outFilePath, fontType, logMsgCB): self.ttFont = ttFont self.inputPath = inputPath if (outFilePath == None): outFilePath = inputPath self.outFilePath = outFilePath self.fontType = fontType self.logMsg = logMsgCB try: self.cffTable = ttFont["CFF "] topDict = self.cffTable.cff.topDictIndex[0] except KeyError: raise focusFontError("Error: font is not a CFF font <%s>." % fontFileName) # for identifier in glyph-list: # Get charstring. self.topDict = topDict self.charStrings = topDict.CharStrings self.charStringIndex = self.charStrings.charStringsIndex self.allowDecimalCoords = False def getGlyphList(self): fontGlyphList = self.ttFont.getGlyphOrder() return fontGlyphList def getUnitsPerEm(self): unitsPerEm = "1000" if hasattr(self.topDict, "FontMatrix"): matrix = self.topDict.FontMatrix unitsPerEm = "%s" % (int(round(1.0/matrix[0]))) return unitsPerEm def getPSName(self): psName = self.cffTable.cff.fontNames[0] return psName def convertToBez(self, glyphName, beVerbose, doAll=False): hasHints = False t2Wdth = None gid = self.charStrings.charStrings[glyphName] t2CharString = self.charStringIndex[gid] try: bezString, hasHints, t2Wdth = convertT2GlyphToBez(t2CharString, self.allowDecimalCoords) # Note: the glyph name is important, as it is used by autohintexe # for various heuristics, including [hv]stem3 derivation. bezString = "% " + glyphName + "\n" + bezString except SEACError: if not beVerbose: dotCount = 0 self.logMsg("") # end series of "." self.logMsg("Checking %s -- ," % (glyphName)) # output message when SEAC glyph is found self.logMsg("Skipping %s: can't process SEAC composite glyphs." % (glyphName)) bezString = None return bezString, t2Wdth, hasHints def updateFromBez(self, bezData, glyphName, width, beVerbose): t2Program = [width] + convertBezToT2(bezData) if t2Program: gid = self.charStrings.charStrings[glyphName] t2CharString = self.charStringIndex[gid] t2CharString.program = t2Program else: if not beVerbose: dotCount = 0 self.logMsg("") # end series of "." self.logMsg("Checking %s -- ," % (aliasName(name))) self.logMsg("Skipping %s: error in processing fixed outline." % (aliasName(name))) def saveChanges(self): ttFont = self.ttFont fontType = self.fontType inputPath = self.inputPath outFilePath = self.outFilePath overwriteOriginal = False if inputPath == outFilePath: overwriteOriginal = True tempPath = inputPath + ".temp.ac" if fontType == 0: # OTF if overwriteOriginal: ttFont.save(tempPath) ttFont.close() if os.path.exists(inputPath): try: os.remove(inputPath) os.rename(tempPath, inputPath) except (OSError, IOError): self.logMsg("\t%s" %(traceback.format_exception_only(sys.exc_info()[0], sys.exc_info()[1])[-1])) self.logMsg("Error: could not overwrite original font file path '%s'. Hinted font file path is '%s'." % (inputPath, tempPath)) else: ttFont.save(outFilePath) ttFont.close() else: data = ttFont["CFF "].compile(ttFont) if fontType == 1: # CFF if overwriteOriginal: with open(inputPath, "wb") as tf: tf.write(data) else: with open(outFilePath, "wb") as tf: tf.write(data) if os.path.exists(tempPath): os.remove(tempPath) def close(self): self.ttFont.close() def getGlyphID(self, name): gid = self.ttFont.getGlyphID(name) return gid def isCID(self): isCID = hasattr(self.topDict, "FDSelect") return isCID def getFontInfo(self, fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs,fdIndex = 0): # The AC 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() if hasattr(privateDict, "LanguageGroup"): fdDict.LanguageGroup = privateDict.LanguageGroup else: fdDict.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) if hasattr(pTopDict, "FontName"): fdDict.FontName = pDict.FontName # FontName else: fdDict.FontName = fontPSName low = min(-upm*0.25, pTopDict.FontBBox[1] - 200) high = max (upm*1.25, pTopDict.FontBBox[3] + 200) # 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 hasattr(privateDict, "BlueValues"): blueValues = privateDict.BlueValues[:] numBlueValues = len(privateDict.BlueValues) blueValues.sort() if numBlueValues < 4: if allow_no_blues: blueValues = inactiveAlignmentValues numBlueValues = len(blueValues) else: raise ACFontError("Error: font must have at least four values in it's BlueValues array for AC to work!") else: if allow_no_blues: blueValues = inactiveAlignmentValues numBlueValues = len(blueValues) else: raise ACFontError("Error: font has no BlueValues array!") # 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] exec("fdDict.%s = %s" % (key, value)) #print(numBlueValues) #for i in range(0, len(fontinfo),2): # print(fontinfo[i], fontinfo[i+1]) 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() 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] exec("fdDict.%s = %s" % (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 ACFontError("Error: font has neither StemSnapV nor StdVW!") vstems.sort() if (len(vstems) == 0) or ((len(vstems) == 1) and (vstems[0] < 1)): vstems = [upm] # dummy value that will allow PyAC to run self.logMsg("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 ACFontError("Error: font has neither StemSnapH nor StdHW!") hstems.sort() if (len(hstems) == 0) or ((len(hstems) == 1) and (hstems[0] < 1)): hstems = [upm] # dummy value that will allow PyAC to run self.logMsg("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) if hasattr(privateDict, "BlueFuzz"): fdDict.BlueFuzz = privateDict.BlueFuzz else: fdDict.BlueFuzz = 1 return fdDict def getfdIndex(self, gid): fdIndex = self.topDict.FDSelect[gid] return fdIndex def getfdInfo(self, fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): topDict = self.topDict fontDictList = [] fdGlyphDict = None # Get the default fontinfo from the font's top dict. fdDict = self.getFontInfo(fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList.append(fdDict) # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") if os.path.exists(srcFontInfo): with open(srcFontInfo, "rU") 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] blueFuzz = fdDict.BlueFuzz fdGlyphDict, fontDictList, finalFDict = fdTools.parseFontInfoFile(fontDictList, fontInfoData, glyphList, maxY, minY, fontPSName, blueFuzz) if finalFDict == None: # If a font dict was not explicitly specified for the output font, # use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], topDict.Private) else: fdTools.mergeFDDicts([finalFDict], topDict.Private) return fdGlyphDict, fontDictList def test(): # Test program. # Takes first argument font file path, optional second argument = glyph name. # Use form "cid0769" for CID keys references. from fontTools.ttLib import TTFont path = sys.argv[1] ttFont = TTFont(path) if len(sys.argv) > 2: glyphNames = sys.argv[2:] else: glyphNames = ttFont.getGlyphOrder() cffTable = ttFont["CFF "] topDict = cffTable.cff.topDictIndex[0] charStrings = topDict.CharStrings for glyphName in glyphNames: print() print(glyphName) t2CharString = charStrings[glyphName] bezString, hasHints, t2Width = convertT2GlyphToBez(t2CharString) #print(bezString) t2Program = convertBezToT2(bezString) if t2Width != None: t2Program.insert(0,t2Width) #print(len(t2Program), ("t2Program",t2Program)) def test2(): # Test program. # Takes first argument = bez path, writes T2 string. # Use form "cid0769" for CID keys references. from fontTools.ttLib import TTFont path = sys.argv[1] with open(path, "rt") as fp: bezString = fp.read() t2Program = convertBezToT2(bezString) if __name__=='__main__': test2() psautohint-1.1.0/python/psautohint/psautohint.py000066400000000000000000000013101324015706300221720ustar00rootroot00000000000000from . import _psautohint def toBytes(o): if hasattr(o, "encode"): return o.encode("utf-8") if isinstance(o, (list, tuple)): return [toBytes(i) for i in o] return o def toStr(o): if hasattr(o, "decode"): return o.decode("utf-8") if isinstance(o, (list, tuple)): return [toStr(i) for i in o] return o def autohint(info, glyphs, verbose=True, allow_edit=True, allow_hint_sub=True, round_ccordinates=True, debug=False): hinted = _psautohint.autohint(toBytes(info), toBytes(glyphs), verbose, allow_edit, allow_hint_sub, round_ccordinates, debug) return toStr(hinted) psautohint-1.1.0/python/psautohint/ufoFont.py000066400000000000000000002136621324015706300214330ustar00rootroot00000000000000from __future__ import print_function, absolute_import """ ufoFont.py v1.30 May 2 2017 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. """ __copyright__ = """Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved. """ _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. ... ()* ()* * # where n1-5 are decimal values * # where n1-5 are decimal values * (* ( (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 64bf4987f05ced2a50195f971cd924984047eb1d79c8c43e6a0054f59cc85dea23a49deb20946a4ea84840534363f7a13cca31a81b1e7e33c832185173369086 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 """ import re import os import plistlib import hashlib try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET from psautohint import fdTools XML = ET.XML XMLElement = ET.Element xmlToString = ET.tostring debug = False def debugMsg(*args): if debug: print(args) #UFO names kDefaultGlyphsLayerName = "public.default" kDefaultGlyphsLayer = "glyphs" kProcessedGlyphsLayerName = "AFDKO ProcessedGlyphs" kProcessedGlyphsLayer = "glyphs.com.adobe.type.processedGlyphs" kFontInfoName = "fontinfo.plist" kContentsName = "contents.plist" kLibName = "lib.plist" kPublicGlyphOrderKey = "public.glyphOrder" kAdobeDomainPrefix = "com.adobe.type" kAdobHashMapName = "%s.processedHashMap" % (kAdobeDomainPrefix) kAdobHashMapVersionName = "hashMapVersion" kAdobHashMapVersion = (1,0) # If major version differs, do not use. kAutohintName = "autohint" kCheckOutlineName = "checkOutlines" kCheckOutlineNameUFO = "checkOutlines" kOutlinePattern = re.compile(r"", re.DOTALL) kStemHintsName = "stemhints" kStemListName = "stemList" kStemPosName = "pos" kStemWidthName = "width" kHStemName = "hstem" kVStemName = "vstem" kHStem3Name = "hstem3" kVStem3Name = "vstem3" kStem3Pos = "stem3List" kHintSetListName = "hintSetList" kFlexListName = "hintSetList" kHintSetName = "hintset" kBaseFlexName = "flexCurve" kPointTag = "pointTag" kStemIndexName = "stemindex" kFlexIndexListName = "flexList" kHintDomainName1 = "com.adobe.type.autohint" kHintDomainName2 = "com.adobe.type.autohint.v2" kPointName = "name" # Hint stuff kStackLimit = 46 kStemLimit = 96 kDefaultFMNDBPath = "FontMenuNameDB" kDefaultGOADBPath = "GlyphOrderAndAliasDB" class UFOParseError(ValueError): pass class BezParseError(ValueError): pass class UFOFontData: def __init__(self, parentPath, useHashMap, programName): self.parentPath = parentPath self.glyphMap = {} self.processedLayerGlyphMap = {} self.newGlyphMap = {} self.glyphList = [] self.fontInfo = None self.useHashMap = useHashMap # if False, will skip reading has map and testing to see if glyph can be skipped. # should be used only when calling program is running in report mdoe only, and not changing any glyph data. self.hashMap = {} # Used to skip getting glyph data when glyph hash matches hash of current glyph data. self.fontDict = None self.programName = programName self.curSrcDir = None self.hashMapChanged = False self.glyphDefaultDir = os.path.join(parentPath, "glyphs") self.glyphLayerDir = os.path.join(parentPath, kProcessedGlyphsLayer) self.glyphWriteDir = self.glyphLayerDir self.historyList = [] self.requiredHistory = [] # See documentation above. self.useProcessedLayer = False # If False, then read data only from the default layer; else read glyphs from processed layer, if it exists. self.writeToDefaultLayer = False # If True, then write data to the default layer self.doAll = False # if True, then do not skip any glyphs. self.deletedGlyph = False # track whether checkSkipGLyph has deleted a out of date glyph from the processed glyph layer self.allowDecimalCoords = False # if true, do NOT round x,y values when processing. def getUnitsPerEm(self): unitsPerEm = "1000" if self.fontInfo == None: self.loadFontInfo() if self.fontInfo: unitsPerEm = int(self.fontInfo["unitsPerEm"]) unitsPerEm = int(unitsPerEm) return unitsPerEm def getPSName(self): psName = "PSName-Undefined" if self.fontInfo == None: self.loadFontInfo() if self.fontInfo: psName = self.fontInfo.get("postscriptFontName", psName) return psName def isCID(self): return 0 def checkForHints(self, glyphName): hasHints = False glyphPath = self.getGlyphProcessedPath(glyphName) if glyphPath and os.path.exists(glyphPath): with open(glyphPath, "rt") as fp: data = fp.read() if "hintSetList" in data: hasHints = True return hasHints def convertToBez(self, glyphName, beVerbose, doAll=False): # convertGLIFToBez does not yet support hints - no need for removeHints arg. bezString, width = convertGLIFToBez(self, glyphName, beVerbose, doAll) hasHints = self.checkForHints(glyphName) return bezString, width, hasHints def updateFromBez(self, bezData, glyphName, width, beVerbose): # For UFO font, we don't use the width parameter: it is carried over from the input glif file. glifXML = convertBezToGLIF(self, glyphName, bezData ) self.newGlyphMap[glyphName] = glifXML def saveChanges(self): if self.hashMapChanged: self.writeHashMap() self.hashMapChanged = False if not os.path.exists(self.glyphWriteDir): os.makedirs(self.glyphWriteDir) for glyphName, glifXML in self.newGlyphMap.items(): glyphPath = self.getWriteGlyphPath(glyphName) #print("Saving file", glyphPath) et = ET.ElementTree(glifXML) with open(glyphPath, "wb") as fp: et.write(fp, encoding="UTF-8", xml_declaration=True) # Update the layer contents.plist file layerContentsFilePath = os.path.join(self.parentPath, "layercontents.plist") self.updateLayerContents(layerContentsFilePath) glyphContentsFilePath = os.path.join(self.glyphWriteDir, "contents.plist") self.updateLayerGlyphContents(glyphContentsFilePath, self.newGlyphMap) def getWriteGlyphPath(self, glyphName): if len(self.glyphMap) == 0: self.loadGlyphMap() glyphFileName = self.glyphMap[glyphName] if not self.writeToDefaultLayer: if self.processedLayerGlyphMap: try: glyphFileName = self.processedLayerGlyphMap[glyphName] except KeyError: pass glifFilePath = os.path.join(self.glyphWriteDir, glyphFileName) return glifFilePath def getGlyphMap(self): if len(self.glyphMap) == 0: self.loadGlyphMap() return self.glyphMap def readHashMap(self): hashPath = os.path.join(self.parentPath, "data", kAdobHashMapName) if os.path.exists(hashPath): with open(hashPath, "rt") as fp: data = fp.read() newMap = eval(data) else: newMap = {kAdobHashMapVersionName:kAdobHashMapVersion} try: version = newMap[kAdobHashMapVersionName] if version[0] > kAdobHashMapVersion[0]: raise UFOParseError("Hash map version is newer than program. Please update the FDK") elif version[0] < kAdobHashMapVersion[0]: print("Updating hash map: was older version") newMap = {kAdobHashMapVersionName:kAdobHashMapVersion} except KeyError: print("Updating hash map: was older version") newMap = {kAdobHashMapVersionName:kAdobHashMapVersion} self.hashMap = newMap return def writeHashMap(self): hashMap = self.hashMap if len(hashMap) == 0: return # no glyphs were processed. hashDir = os.path.join(self.parentPath, "data") if not os.path.exists(hashDir): os.makedirs(hashDir) hashPath = os.path.join(hashDir, kAdobHashMapName) hasMapKeys = hashMap.keys() hasMapKeys = sorted(hasMapKeys) data = ["{"] for gName in hasMapKeys: data.append("'%s': %s," % (gName, hashMap[gName])) data.append("}") data.append("") data = "\n".join(data) with open(hashPath, "wt") as fp: fp.write(data) return def getCurGlyphPath(self, glyphName): if self.curSrcDir == None: self.curSrcDir = self.glyphDefaultDir # Get the glyph file name. if len(self.glyphMap) == 0: self.loadGlyphMap() glyphFileName = self.glyphMap[glyphName] path = os.path.join(self.curSrcDir, glyphFileName) return path def getGlyphSrcPath(self, glyphName): if len(self.glyphMap) == 0: self.loadGlyphMap() glyphFileName = self.glyphMap[glyphName] if self.useProcessedLayer and self.processedLayerGlyphMap: # Try for processed layer first. try: glyphFileName = self.processedLayerGlyphMap[glyphName] self.curSrcDir = self.glyphLayerDir glyphPath = os.path.join(self.glyphLayerDir, glyphFileName) if os.path.exists(glyphPath): return glyphPath except KeyError: pass self.curSrcDir = self.glyphDefaultDir glyphPath = os.path.join(self.curSrcDir, glyphFileName) return glyphPath def getGlyphDefaultPath(self, glyphName): if len(self.glyphMap) == 0: self.loadGlyphMap() glyphFileName = self.glyphMap[glyphName] glyphPath = os.path.join(self.glyphDefaultDir, glyphFileName) return glyphPath def getGlyphProcessedPath(self, glyphName): if len(self.glyphMap) == 0: self.loadGlyphMap() if not self.processedLayerGlyphMap: return None try: glyphFileName = self.processedLayerGlyphMap[glyphName] glyphPath = os.path.join(self.glyphLayerDir, glyphFileName) except KeyError: glyphPath = None return glyphPath def updateHashEntry(self, glyphName, changed): # srcHarsh has already been set: we are fixing the history list. if not self.useHashMap: return # Get hash entry for glyph try: hashEntry = self.hashMap[glyphName] srcHash, historyList = hashEntry except KeyError: hashEntry = None self.hashMapChanged = True # If the program always reads data from the default layer, and we have just created a new glyph in the processed layer, then reset the history. if (not self.useProcessedLayer) and changed: self.hashMap[glyphName] = [srcHash, [self.programName] ] return else: try: programHistoryIndex = historyList.index(self.programName) except ValueError: #If the program is not in the history list, add it. historyList.append(self.programName) def checkSkipGlyph(self, glyphName, newSrcHash, doAll): skip = False if not self.useHashMap: return skip if len(self.hashMap) == 0: # Hash maps have not yet been read in. Get them. self.readHashMap() hashEntry = srcHash = None historyList = [] programHistoryIndex = -1 # not found in historyList # Get hash entry for glyph try: hashEntry = self.hashMap[glyphName] srcHash, historyList = hashEntry try: programHistoryIndex = historyList.index(self.programName) except ValueError: pass except KeyError: # Glyph is as yet untouched by any program. pass if (srcHash == newSrcHash): if (programHistoryIndex >= 0): # The glyph has already been processed by this program, and there have been no changes since. skip = True and (not doAll) if not skip: if not self.useProcessedLayer: # case for Checkoutlines self.hashMapChanged = True self.hashMap[glyphName] = [newSrcHash, [self.programName] ] glyphPath = self.getGlyphProcessedPath(glyphName) if glyphPath and os.path.exists(glyphPath): os.remove(glyphPath) else: if (programHistoryIndex < 0): historyList.append(self.programName) else: if self.useProcessedLayer: # case for autohint # default layer glyph and stored glyph hash differ,and useProcessedLayer is True # If any of the programs in requiredHistory in are in the historyList, we need to complain and skip. foundMatch = False if len(historyList) > 0: for programName in self.requiredHistory: if programName in historyList: foundMatch = True if foundMatch: print("Error. Glyph '%s' has been edited. You must first run '%s' before running '%s'. Skipping." % (glyphName, self.requiredHistory, self.programName)) skip = True # If the source hash has changed, we need to delete the processed layer glyph. self.hashMapChanged = True self.hashMap[glyphName] = [newSrcHash, [self.programName] ] glyphPath = self.getGlyphProcessedPath(glyphName) if glyphPath and os.path.exists(glyphPath): os.remove(glyphPath) self.deletedGlyph = True return skip def getGlyphXML(self, glyphDir, glyphFileName): glyphPath = os.path.join(glyphDir, glyphFileName) # default etRoot = ET.ElementTree() glifXML = etRoot.parse(glyphPath) outlineXML = glifXML.find("outline") try: widthXML = glifXML.find("advance") if widthXML != None: width = int(eval(widthXML.get("width"))) else: width = 1000 except UFOParseError as e: print("Error. skipping glyph '%s' because of parse error: %s" % (glyphName, e.message)) return None, None, None return width, glifXML, outlineXML def getOrSkipGlyphXML(self, glyphName, doAll=False): # 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. if len(self.glyphMap) == 0: self.loadGlyphMap() glyphFileName = self.glyphMap[glyphName] width, glifXML, outlineXML = self.getGlyphXML(self.glyphDefaultDir, glyphFileName) if glifXML == None: skip = True return None, None, skip useDefaultGlyphDir = True # Hash is always from the default glyph layer. newHash, dataList = self.buildGlyphHashValue(width, outlineXML, glyphName, useDefaultGlyphDir) skip = self.checkSkipGlyph(glyphName, newHash, doAll) # If self.useProcessedLayer and there is a glyph in the processed layer, get the outline from that. if self.useProcessedLayer and self.processedLayerGlyphMap: try: glyphFileName = self.processedLayerGlyphMap[glyphName] except KeyError: pass glyphPath = os.path.join(self.glyphLayerDir, glyphFileName) if os.path.exists(glyphPath): width, glifXML, outlineXML = self.getGlyphXML(self.glyphLayerDir, glyphFileName) if glifXML == None: skip = True return None, None, skip return width, outlineXML, skip def getGlyphList(self): if len(self.glyphMap) == 0: self.loadGlyphMap() return self.glyphList def loadGlyphMap(self): # Need to both get the list of glyphs from contents.plist, and also the glyph order. # the latter is take from the public.glyphOrder key in lib.py, if it exists, # else it is taken from the contents.plist file. Any glyphs in contents.plist # which are not named in the public.glyphOrder are sorted after all glyphs which are named # in the public.glyphOrder,, in the order that they occured in contents.plist. contentsPath = os.path.join(self.parentPath, "glyphs", kContentsName) self.glyphMap, self.glyphList = parsePList(contentsPath) orderPath = os.path.join(self.parentPath, kLibName) self.orderMap = parseGlyphOrder(orderPath) if self.orderMap != None: orderIndex = len(self.orderMap) orderList = [] # If there are glyphs in the font which are not named in the public.glyphOrder entry, add then in the order of the contents.plist file. for glyphName in self.glyphList: try: entry = [self.orderMap[glyphName], glyphName] except KeyError: entry = [orderIndex, glyphName] self.orderMap[glyphName] = orderIndex orderIndex += 1 orderList.append(entry) orderList.sort() self.glyphList = [] for entry in orderList: self.glyphList.append(entry[1]) else: self.orderMap = {} numGlyphs = len(self.glyphList) for i in range(numGlyphs): self.orderMap[self.glyphList[i]] = i # I also need to get the glyph map for the processed layer, # and use this when the glyph is read from the processed layer. # Because checkOutliensUFO used the defcon library, it can write glyph file names # that differ from what is in the default glyph layer. contentsPath = os.path.join(self.glyphLayerDir, kContentsName) if os.path.exists(contentsPath): self.processedLayerGlyphMap, self.processedLayerGlyphList = parsePList(contentsPath) def loadFontInfo(self): fontInfoPath = os.path.join(self.parentPath, "fontinfo.plist") if not os.path.exists(fontInfoPath): return self.fontInfo, tempList = parsePList(fontInfoPath) def updateLayerContents(self, contentsFilePath): if os.path.exists(contentsFilePath): contentsList= plistlib.readPlist(contentsFilePath) # If the layer name already exists, don't add a new one, or change the path seenPublicDefault = False seenProcessedGlyph = False for layerName, layerPath in contentsList: if (layerPath == kProcessedGlyphsLayer): seenProcessedGlyph = True if (layerPath == kDefaultGlyphsLayer): seenPublicDefault = True update = False if not seenPublicDefault: update = True contentsList = [[kDefaultGlyphsLayerName, kDefaultGlyphsLayer]] + contentsList if not seenProcessedGlyph: update = True contentsList.append([kProcessedGlyphsLayerName, kProcessedGlyphsLayer]) if update: plistlib.writePlist(contentsList, contentsFilePath) else: contentsList = [[kDefaultGlyphsLayerName, kDefaultGlyphsLayer]] contentsList.append([kProcessedGlyphsLayerName, kProcessedGlyphsLayer]) plistlib.writePlist(contentsList, contentsFilePath) def updateLayerGlyphContents(self, contentsFilePath, newGlyphData): if os.path.exists(contentsFilePath): contentsDict = plistlib.readPlist(contentsFilePath) else: contentsDict = {} for glyphName in newGlyphData.keys(): if self.useProcessedLayer and self.processedLayerGlyphMap: # Try for processed layer first. try: contentsDict[glyphName] = self.processedLayerGlyphMap[glyphName] except KeyError: contentsDict[glyphName] = self.glyphMap[glyphName] else: contentsDict[glyphName] = self.glyphMap[glyphName] plistlib.writePlist(contentsDict, contentsFilePath) def getFontInfo(self, fontPSName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex=0): if self.fontDict != None: return self.fontDict if self.fontInfo == None: self.loadFontInfo() fdDict = fdTools.FDDict() fdDict.LanguageGroup = self.fontInfo.get("languagegroup", "0") # should be 1 if the glyphs are ideographic, else 0. fdDict.OrigEmSqUnits = self.getUnitsPerEm() fdDict.FontName = self.getPSName() upm = self.getUnitsPerEm() low = min( -upm*0.25, float(self.fontInfo.get("openTypeOS2WinDescent", "0")) - 200) high = max ( upm*1.25, float(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 UFOParseError("ERROR: Font must have at least four values in its BlueValues array for AC 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] exec("fdDict.%s = %s" % (key, value)) otherBlues = self.fontInfo.get("postscriptOtherBlues", []) numBlueValues = len(otherBlues) 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] exec("fdDict.%s = %s" % (key, value)) vstems = self.fontInfo.get("postscriptStemSnapV", []) if len(vstems) == 0: if allow_no_blues: vstems = [fdDict.OrigEmSqUnits] # 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. else: raise UFOParseError("ERROR: Font does not have postscriptStemSnapV!") vstems.sort() if (len(vstems) == 0) or ((len(vstems) == 1) and (vstems[0] < 1) ): vstems = [fdDict.OrigEmSqUnits] # dummy value that will allow PyAC to run logMsg("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 len(hstems) == 0: if allow_no_blues: hstems = [fdDict.OrigEmSqUnits] # 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. else: raise UFOParseError("ERROR: Font does not have postscriptStemSnapH!") hstems.sort() if (len(hstems) == 0) or ((len(hstems) == 1) and (hstems[0] < 1) ): hstems = [fdDict.OrigEmSqUnits] # dummy value that will allow PyAC to run logMsg("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 getfdIndex(self, gid): fdIndex = 0 return fdIndex def getfdInfo(self, psName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): fontDictList = [] fdGlyphDict = None fdDict = self.getFontInfo(psName, inputPath, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList.append(fdDict) # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(inputPath) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") maxX = self.getUnitsPerEm()*2 maxY = maxX minY = -self.getUnitsPerEm() if os.path.exists(srcFontInfo): with open(srcFontInfo, "rU") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) if "FDDict" in fontInfoData: blueFuzz = fdDict.BlueFuzz fdGlyphDict, fontDictList, finalFDict = fdTools.parseFontInfoFile(fontDictList, fontInfoData, glyphList, maxY, minY, psName, blueFuzz) if finalFDict == 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], topDict ) return fdGlyphDict, fontDictList def getGlyphID(self, glyphName): try: gid = self.orderMap[glyphName] except IndexError: raise UFOParseError("Could not find glyph name '%s' in UFO font contents plist. '%s'. " % (glyphName, self.parentPath)) return gid def buildGlyphHashValue(self, width, outlineXML, glyphName, useDefaultGlyphDir, level = 0): """ glyphData must be the official XML from a GLIF. We skip contours with only one point. """ dataList = ["w%s" % (str(width))] if level > 10: raise UFOParseError("In parsing component, exceeded 10 levels of reference. '%s'. " % (glyphName)) # tag is optional per spec., e.g. space glyph does not necessarily have it. if outlineXML: for childContour in outlineXML: if childContour.tag == "contour": if len(childContour) < 2: continue else: for child in childContour: if child.tag == "point": try: pointType = child.attrib["type"][0] except KeyError: pointType = "" dataList.append("%s%s%s" % (pointType, child.attrib["x"], child.attrib["y"])) #print(dataList[-3:]) elif childContour.tag == "component": # append the component hash. try: compGlyphName = childContour.attrib["base"] dataList.append("%s%s" % ("base:", compGlyphName)) except KeyError: raise UFOParseError("'%s' is missing the 'base' attribute in a component. glyph '%s'." % (glyphName)) if useDefaultGlyphDir: try: componentPath = self.getGlyphDefaultPath(compGlyphName) except KeyError: raise UFOParseError("'%s' component glyph is missing from contents.plist." % (compGlyphName)) else: # If we are not necessarily using the default layer for the main glyph, then a missing component # may not have been processed, and may just be in the default layer. We need to look for component # glyphs in the src list first, then in the defualt layer. try: componentPath = self.getGlyphSrcPath(compGlyphName) if not os.path.exists(componentPath): componentPath = self.getGlyphDefaultPath(compGlyphName) except KeyError: try: componentPath = self.getGlyphDefaultPath(compGlyphName) except KeyError: raise UFOParseError("'%s' component glyph is missing from contents.plist." % (compGlyphName)) if not os.path.exists(componentPath): raise UFOParseError("'%s' component file is missing: '%s'." % (compGlyphName, componentPath)) etRoot = ET.ElementTree() # Collect transformm fields, if any. for transformTag in ["xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"]: try: value = childContour.attrib[transformTag] rval = eval(value) if int(rval) == rval: value = str(int(rval)) dataList.append(value) except KeyError: pass componentXML = etRoot.parse(componentPath) componentOutlineXML = componentXML.find("outline") componentHash, componentDataList = self.buildGlyphHashValue(width, componentOutlineXML, glyphName, useDefaultGlyphDir, level+1) dataList.extend(componentDataList) data = "".join(dataList) if len(data) < 128: hash = data else: hash = hashlib.sha512(data.encode("utf-8")).hexdigest() return hash, dataList def getComponentOutline(self, componentItem): try: compGlyphName = componentItem.attrib["base"] except KeyError: raise UFOParseError("'%s' attribute missing from component '%s'." % ("base", xmlToString(componentXML))) if not self.useProcessedLayer: try: compGlyphFilePath = self.getGlyphDefaultPath(compGlyphName) except KeyError: raise UFOParseError("'%s' component glyph is missing from contents.plist." % (compGlyphName)) else: # If we are not necessarily using the default layer for the main glyph, then a missing component # may not have been processed, and may just be in the default layer. We need to look for component # glyphs in the src list first, then in the defualt layer. try: compGlyphFilePath = self.getGlyphSrcPath(compGlyphName) if not os.path.exists(compGlyphFilePath): compGlyphFilePath = self.getGlyphDefaultPath(compGlyphName) except KeyError: try: compGlyphFilePath = self.getGlyphDefaultPath(compGlyphName) except KeyError: raise UFOParseError("'%s' component glyph is missing from contents.plist." % (compGlyphName)) if not os.path.exists(compGlyphFilePath): raise UFOParseError("'%s' component file is missing: '%s'." % (compGlyphName, compGlyphFilePath)) etRoot = ET.ElementTree() glifXML = etRoot.parse(compGlyphFilePath) outlineXML = glifXML.find("outline") return outlineXML def copyTo(self, dstPath): """ Copy UFO font to target path""" return def close(self): if self.hashMapChanged: self.writeHashMap() self.hashMapChanged = False return def clearHashMap(self): self.hashMap = {kAdobHashMapVersionName:kAdobHashMapVersion} hashDir = os.path.join(self.parentPath, "data") if not os.path.exists(hashDir): return hashPath = os.path.join(hashDir, kAdobHashMapName) if os.path.exists(hashPath): os.remove(hashPath) def setWriteToDefault(self): self.useProcessedLayer = False self.writeToDefaultLayer = True self.glyphWriteDir = self.glyphDefaultDir def parseGlyphOrder(filePath): orderMap = None if os.path.exists(filePath): publicOrderDict, temp = parsePList(filePath, kPublicGlyphOrderKey) if publicOrderDict != None: orderMap = {} glyphList = publicOrderDict[kPublicGlyphOrderKey] numGlyphs = len(glyphList) for i in range(numGlyphs): orderMap[glyphList[i]] = i return orderMap def parsePList(filePath, dictKey = None): # if dictKey is defined, parse and return only the data for that key. # This helps avoid needing to parse all plist types. I need to support only data for known keys amd lists. plistDict = {} plistKeys = [] # I uses this rather than the plistlib in order to get a list that allows preserving key order. with open(filePath, "r") as fp: data = fp.read() contents = XML(data) dict = contents.find("dict") if dict == None: raise UFOParseError("In '%s', failed to find dict. '%s'." % (filePath)) lastTag = "string" for child in dict: if child.tag == "key": if lastTag == "key": raise UFOParseError("In contents.plist, key name '%s' followed another key name '%s'." % (xmlToString(child), lastTag) ) skipKeyData = False lastName = child.text lastTag = "key" if dictKey != None: if lastName != dictKey: skipKeyData = True elif child.tag != "key": if lastTag != "key": raise UFOParseError("In contents.plist, key value '%s' followed a non-key tag '%s'." % (xmlToString(child), lastTag) ) lastTag = child.tag if skipKeyData: continue if lastName in plistDict: raise UFOParseError("Encountered duplicate key name '%s' in '%s'." % (lastName, filePath) ) if child.tag == "array": list = [] for listChild in child: val = listChild.text if listChild.tag == "integer": val= int(eval(val)) elif listChild.tag == "real": val = float(eval(val)) elif listChild.tag == "string": pass else: raise UFOParseError("In plist file, encountered unhandled key type '%s' in '%s' for parent key %s. %s." % (listChild.tag, child.tag, lastName, filePath)) list.append(val) plistDict[lastName] = list elif child.tag == "integer": plistDict[lastName] = int(eval(child.text)) elif child.tag == "real": plistDict[lastName] = float(eval(child.text)) elif child.tag == "false": plistDict[lastName] = 0 elif child.tag == "true": plistDict[lastName] = 1 elif child.tag == "string": plistDict[lastName] = child.text else: raise UFOParseError("In plist file, encountered unhandled key type '%s' for key %s. %s." % (child.tag, lastName, filePath)) plistKeys.append(lastName) else: raise UFOParseError("In plist file, encountered unexpected element '%s'. %s." % (xmlToString(child), filePath )) if len(plistDict) == 0: plistDict = None return plistDict, plistKeys class UFOTransform: kTransformTagList = ["xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"] def __init__(self, componentXML): self.transformFactors = [] self.isDefault = True self.isOffsetOnly = True hasScale = False hasOffset = False for tag in self.kTransformTagList: val = componentXML.attrib.get(tag, None) if tag in ["xScale", "yScale"]: if val == None: val = 1.0 else: val = float(val) if val != 1.0: hasScale = True else: if val == None: val = 0 else: val = float(val) if val != 0: self.isDefault = False if tag in ["xyScale", "yxScale"]: hasScale = True elif tag in ["xOffset", "yOffset"]: hasOffset = True else: raise UFOParseError("Unknown tag '%s' in component '%s'." % (tag, xmlToString(componentXML))) self.transformFactors.append(val) if hasScale or hasOffset: self.isDefault = False if hasScale: self.isOffsetOnly = False def concat(self, transform): if transform == None: return if transform.isDefault: return tfCur = self.transformFactors tfPrev = transform.transformFactors if transform.isOffsetOnly: tfCur[4] += tfPrev[4] tfCur[5] += tfPrev[5] self.isDefault = False else: tfNew = [0.0]*6 tfNew[0] = tfCur[0]*tfPrev[0] + tfCur[1]*tfPrev[2]; tfNew[1] = tfCur[0]*tfPrev[1] + tfCur[1]*tfPrev[3]; tfNew[2] = tfCur[2]*tfPrev[0] + tfCur[3]*tfPrev[2]; tfNew[3] = tfCur[2]*tfPrev[1] + tfCur[3]*tfPrev[3]; tfNew[4] = tfCur[4]*tfPrev[0] + tfCur[5]*tfPrev[2] + tfPrev[4]; tfNew[5] = tfCur[4]*tfPrev[1] + tfCur[5]*tfPrev[3] + tfPrev[5]; self.transformFactors = tfNew self.isOffsetOnly = self.isOffsetOnly and transform.isOffsetOnly self.isDefault = False def apply(self, x, y): tfCur = self.transformFactors if self.isOffsetOnly: x += tfCur[4] y += tfCur[5] else: x, y = x*tfCur[0] + y*tfCur[2] + tfCur[4], \ x*tfCur[1] + y*tfCur[3] + tfCur[5] return x,y def convertGlyphOutlineToBezString(outlineXML, ufoFontData, transform = None, level = 0): """convert XML outline element containing contours and components to a bez string. Since xml.etree.CElementTree is compiled code, this will run faster than tokenizing and parsing in regular Python. glyphMap is a dict mapping glyph names to component file names. If it has a length of 1, it needs to be filled from the contents.plist file. It must have a key/value [UFO_FONTPATH] = path to parent UFO font. transformList is None, or a list of floats in the order: ["xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset"] Build a list of outlines and components. For each item: if is outline: get list of points for each point: if transform matrix: apply transform to coordinate if off line push coord on stack if online: add operator if is component: if any scale, skew, or offset factors: set transform call convertGlyphOutlineToBezString with transform add to bez string I don't bother adding in any hinting info, as this is used only for making temp bez files as input to checkOutlines or autohint, which invalidate hinting data by changing the outlines data, or at best ignore hinting data. bez ops output: ["rrmt", "dt", "rct", "cp" , "ed"] """ if level > 10: raise UFOParseError("In parsing component, exceeded 10 levels of reference. '%s'. " % (outlineXML)) allowDecimals = ufoFontData.allowDecimalCoords bezStringList = [] if outlineXML == None: bezstring = "" else: for outlineItem in outlineXML: if outlineItem.tag == "component": newTransform = UFOTransform(outlineItem) if newTransform.isDefault: if transform != None: newTransform.concat(transform) else: newTransform = None else: if transform != None: newTransform.concat(transform) componentOutline = ufoFontData.getComponentOutline(outlineItem) if componentOutline: outlineBezString = convertGlyphOutlineToBezString(componentOutline, ufoFontData, newTransform, level+1) bezStringList.append(outlineBezString) continue if outlineItem.tag != "contour": continue # May be an empty contour. if len(outlineItem) == 0: continue # We have a regular contour. Iterate over points. argStack = [] # Deal with setting up move-to. lastItem = outlineItem[0] try: type = lastItem.attrib["type"] except KeyError: type = "offcurve" if type in ["curve","line","qccurve"]: outlineItem = outlineItem[1:] if type != "line": outlineItem.append(lastItem) # I don't do this for line, as AC behaves differently when a final line-to is explicit. x = float(lastItem.attrib["x"]) y = float(lastItem.attrib["y"]) if transform != None: x,y = transform.apply(x,y) if (not allowDecimals): x = int(round(x)) y = int(round(y)) if (allowDecimals): op = "%.3f %.3f mt" % (x , y) else: op = "%s %s mt" % (x, y) bezStringList.append(op) elif type == "move": # first op is a move-to. if len(outlineItem) == 1: # this is a move, and is the only op in this outline. Don't pass it through. # this is most likely left over from a GLIF format 1 anchor. argStack = [] continue elif type == "offcurve": # We should only see an off curve point as the first point when the first op is a curve and the last op is a line. # In this case, insert a move-to to the line's coords, then omit the line. # Breaking news! In rare cases, a first off-curve point can occur when the first AND last op is a curve. curLastItem = outlineItem[-1] # In this case, insert a move-to to the last op's end pos. try: lastType = curLastItem.attrib["type"] x = float(curLastItem.attrib["x"]) y = float(curLastItem.attrib["y"]) if transform != None: x,y = transform.apply(x,y) if (not allowDecimals): x = int(round(x)) y = int(round(y)) if lastType == "line": if (allowDecimals): op = "%.3f %.3f mt" % (x , y) else: op = "%s %s mt" % (x, y) bezStringList.append(op) outlineItem = outlineItem[:-1] elif lastType == "curve": if (allowDecimals): op = "%.3f %.3f mt" % (x , y) else: op = "%s %s mt" % (x, y) bezStringList.append(op) except KeyError: raise UFOParseError("Unhandled case for first and last points in outline '%s'." % (xmlToString(outlineItem))) else: raise UFOParseError("Unhandled case for first point in outline '%s'." % (xmlToString(outlineItem))) for contourItem in outlineItem: if contourItem.tag != "point": continue x = float(contourItem.attrib["x"]) y = float(contourItem.attrib["y"]) if transform != None: x,y = transform.apply(x,y) if (not allowDecimals): x = int(round(x)) y = int(round(y)) try: type = contourItem.attrib["type"] except KeyError: type = "offcurve" if type == "offcurve": argStack.append(x) argStack.append(y) elif type == "move": if (allowDecimals): op = "%.3f %.3f mt" % (x , y) else: op = "%s %s mt" % (x, y) bezStringList.append(op) argStack = [] elif type == "line": if (allowDecimals): op = "%.3f %.3f dt" % (x, y) else: op = "%s %s dt" % (x, y) bezStringList.append(op) argStack = [] elif type == "curve": if len(argStack) != 4: raise UFOParseError("Argument stack error seen for curve point '%s'." % (xmlToString(contourItem))) if (allowDecimals): op = "%.3f %.3f %.3f %.3f %.3f %.3f ct" % ( argStack[0], argStack[1], argStack[2], argStack[3], x, y) else: op = "%s %s %s %s %s %s ct" % ( argStack[0], argStack[1], argStack[2], argStack[3], x, y) argStack = [] bezStringList.append(op) elif type == "qccurve": raise UFOParseError("Point type not supported '%s'." % (xmlToString(contourItem))) else: raise UFOParseError("Unknown Point type not supported '%s'." % (xmlToString(contourItem))) # we get here only if there was at least a move. bezStringList.append("cp") bezstring = "\n".join(bezStringList) return bezstring def convertGLIFToBez(ufoFontData, glyphName, beVerbose, doAll=False): width, outlineXML, skip = ufoFontData.getOrSkipGlyphXML(glyphName, doAll) if skip: return None, width if outlineXML == None: return None, width bezString = convertGlyphOutlineToBezString(outlineXML, ufoFontData) bezString = "\n".join(["% " + glyphName, "sc", bezString, "ed", ""]) return bezString, width class HintMask: # class used to collect hints for the current hint mask when converting bez to T2. def __init__(self, listPos): self.listPos = listPos # The index into the pointList is kept so we can quickly find them later. self.hList = [] # These contain the actual hint values. self.vList = [] self.hstem3List = [] self.vstem3List = [] self.pointName = "hintSet" + str(listPos).zfill(4) # The name attribute of the point which follows the new hint set. def addHintSet(self, hintSetList): # Add the hint set to hintSetList newHintSetDict = XMLElement("dict") hintSetList.append(newHintSetDict) newHintSetKey = XMLElement("key") newHintSetKey.text = kPointTag newHintSetDict.append(newHintSetKey) newHintSetValue = XMLElement("string") newHintSetValue.text = self.pointName newHintSetDict.append(newHintSetValue) stemKey = XMLElement("key") stemKey.text = "stems" newHintSetDict.append(stemKey) newHintSetArray = XMLElement("array") newHintSetDict.append(newHintSetArray) newHintSet = [] if (len(self.hList) > 0) or (len(self.vstem3List)): isH = True addHintList(self.hList, self.hstem3List, newHintSetArray, isH) if (len(self.vList) > 0) or (len(self.vstem3List)): isH = False addHintList(self.vList, self.vstem3List, newHintSetArray, isH) def makeStemHintList(hintsStem3, stemList, isH): stem3Args = [] lastPos = 0 # In bez terms, the first coordinate in each pair is absolute, second is relative, and hence is the width. if isH: op = kHStem3Name else: op = kVStem3Name newStem = XMLElement("string") posList = [op] for stem3 in hintsStem3: for pos, width in stem3: if (type(pos) == type(0.0)) and (int(pos) == pos): pos = int(pos) if (type(width) == type(0.0)) and (int(width) == width): width = int(width) posList.append("%s %s" % (pos, width)) posString = " ".join(posList) newStem.text = posString stemList.append(newStem) def makeHintList(hints, newHintSetArray, isH): # Add the list of hint operators hintList = [] lastPos = 0 # In bez terms, the first coordinate in each pair is absolute, second is relative, and hence is the width. for hint in hints: if not hint: continue pos = hint[0] if (type(pos) == type(0.0)) and (int(pos) == pos): pos = int(pos) width = hint[1] if (type(width) == type(0.0)) and (int(width) == width): width = int(width) if isH: op = kHStemName else: op = kVStemName newStem = XMLElement("string") newStem.text = "%s %s %s" % (op, pos, width) newHintSetArray.append(newStem) def addFlexHint(flexList, flexArray): for pointTag in flexList: newFlexTag = XMLElement("string") newFlexTag.text = pointTag flexArray.append(newFlexTag) def fixStartPoint(outlineItem, opList): # For the GLIF format, the idea of first/last point is funky, because the format avoids identifying a # 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. firstOp, firstX, firstY = opList[0] lastOp, lastX, lastY = opList[-1] firstPointElement = outlineItem[0] if (firstX == lastX) and (firstY == lastY): del outlineItem[-1] firstPointElement.set("type", lastOp) else: # we have an implied final line to. All we need to do is convert the inital moveto to a lineto. firstPointElement.set("type", "line") bezToUFOPoint = { "mt" : 'move', "rmt" : 'move', "hmt" : 'move', "vmt" : 'move', "rdt" : 'line', "dt" : 'line', "hdt" : "line", "vdt" : "line", "rct" : 'curve', "ct" : 'curve', "rcv" : 'curve', # Morisawa's alternate name for 'rct'. "vhct": 'curve', "hvct": 'curve', } def convertCoords(curX, curY): showX = int(curX) if showX != curX: showX = curX showY = int(curY) if showY != curY: showY = curY return showX, showY def convertBezToOutline(ufoFontData, glyphName, 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 bezList = re.findall(r"(\S+)", bezString) if not bezList: return "", None, None flexList = [] hintMask = HintMask(0) # Create an initial hint mask. We use this if there is no explicit initial hint sub. hintMaskList = [hintMask] vStem3Args = [] hStem3Args = [] vStem3List = [] hStem3List = [] argList = [] opList = [] newHintMaskName = None inPreFlex = False hintInfoDict = None opIndex = 0 lastPathOp = None curX = 0 curY = 0 newOutline = XMLElement("outline") outlineItem = None seenHints = False for token in bezList: try: val = float(token) argList.append(val) continue except ValueError: pass if token == "newcolors": lastPathOp = token pass elif token in ["beginsubr", "endsubr"]: lastPathOp = token pass elif token in ["snc"]: lastPathOp = token hintMask = HintMask(opIndex) if opIndex == 0: # If the new colors 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. hintMaskList = [hintMask] else: hintMaskList.append(hintMask) newHintMaskName = hintMask.pointName elif token in ["enc"]: lastPathOp = token pass elif token == "div": # I specifically do NOT set lastPathOp for this. value = argList[-2]/float(argList[-1]) argList[-2:] =[value] elif token == "rb": if newHintMaskName == None: newHintMaskName = hintMask.pointName lastPathOp = token hintMask.hList.append(argList) argList = [] seenHints = True elif token == "ry": if newHintMaskName == None: newHintMaskName = hintMask.pointName lastPathOp = token hintMask.vList.append(argList) argList = [] seenHints = True elif token == "rm": # vstem3's are vhints if newHintMaskName == None: newHintMaskName = hintMask.pointName seenHints = True vStem3Args.append(argList) argList = [] lastPathOp = token if len(vStem3Args) == 3: hintMask.vstem3List.append(vStem3Args) vStem3Args = [] elif token == "rv": # hstem3's are hhints seenHints = True hStem3Args.append(argList) argList = [] lastPathOp = token if len(hStem3Args) == 3: hintMask.hstem3List.append(hStem3Args) hStem3Args = [] elif token == "preflx1": # the preflx1/preflx2 sequence provides the same i as the flex sequence; the difference is that the # preflx1/preflx2 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. lastPathOp = token argList = [] inPreFlex = True # need to skip all move-tos until we see the "flex" operator. elif token == "preflx2a": lastPathOp = token argList = [] elif token == "preflx2": lastPathOp = token argList = [] elif token == "flxa": # flex with absolute coords. inPreFlex = False flexPointName = kBaseFlexName + str(opIndex).zfill(4) flexList.append(flexPointName) curveCnt = 2 i = 0 # The first 12 args are the 6 args for each of the two curves that make up the flex feature. while i < curveCnt: curX = argList[0] curY = argList[1] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX = argList[2] curY = argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX = argList[4] curY = argList[5] showX, showY = convertCoords(curX, curY) opName = 'curve' newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": opName } ) outlineItem.append(newPoint) opList.append([opName,curX, curY]) opIndex += 1 if i == 0: argList = argList[6:12] i += 1 outlineItem[-6].set(kPointName, flexPointName) # attach the point name to the first point of the first curve. if (newHintMaskName != 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 = flexPointName newHintMaskName = None lastPathOp = token argList = [] elif token == "flx": inPreFlex = False flexPointName = kBaseFlexName + str(opIndex).zfill(4) flexList.append(flexPointName) curveCnt = 2 i = 0 # The first 12 args are the 6 args for each of the two curves that make up the flex feature. while i < curveCnt: curX += argList[0] curY += argList[1] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[2] curY += argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[4] curY += argList[5] showX, showY = convertCoords(curX, curY) opName = 'curve' newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": opName } ) outlineItem.append(newPoint) opList.append([opName,curX, curY]) opIndex += 1 if i == 0: argList = argList[6:12] i += 1 outlineItem[-6].set(kPointName, flexPointName) # attach the point name to the first point of the first curve. if (newHintMaskName != 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 = flexPointName newHintMaskName = None lastPathOp = token argList = [] elif token == "sc": lastPathOp = token pass elif token == "cp": pass elif token == "ed": pass else: if inPreFlex and (token[-2:] == "mt"): continue if token[-2:] in ["mt", "dt", "ct", "cv"]: lastPathOp = token opIndex += 1 else: print("Unhandled operation", argList, token) raise BezParseError("Unhandled operation: '%s' '%s'.", argList, token) dx = dy = 0 opName = bezToUFOPoint[token] if token[-2:] in ["mt", "dt"]: if token in ["mt", "dt"]: curX = argList[0] curY = argList[1] else: if token in ["rmt", "rdt"]: dx = argList[0] dy = argList[1] elif token in ["hmt", "hdt"]: dx = argList[0] elif token in ["vmt", "vdt"]: dy = argList[0] curX += dx curY += dy showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": "%s" % (opName) } ) if opName == "move": if outlineItem != None: if len(outlineItem) == 1: # Just in case we see two moves in a row, delete the previous outlineItem if it has only the move-to'' print("Deleting moveto:", xmlToString(newOutline[-1]), "adding", xmlToString(outlineItem)) del newOutline[-1] else: fixStartPoint(outlineItem, opList) # Fix the start/implied end path of the previous path. opList = [] outlineItem = XMLElement('contour') newOutline.append(outlineItem) if (newHintMaskName != None): newPoint.set(kPointName, newHintMaskName) newHintMaskName = None outlineItem.append(newPoint) opList.append([opName,curX, curY]) else: if token in ["ct", "cv"]: curX = argList[0] curY = argList[1] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX = argList[2] curY = argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX = argList[4] curY = argList[5] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": "%s" % (opName) } ) outlineItem.append(newPoint) else: if token in ["rct", "rcv"]: curX += argList[0] curY += argList[1] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[2] curY += argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[4] curY += argList[5] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": "%s" % (opName) } ) outlineItem.append(newPoint) elif token == "vhct": curY += argList[0] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[1] curY += argList[2] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": "%s" % (opName) } ) outlineItem.append(newPoint) elif token == "hvct": curX += argList[0] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curX += argList[1] curY += argList[2] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY) } ) outlineItem.append(newPoint) curY += argList[3] showX, showY = convertCoords(curX, curY) newPoint = XMLElement("point", {"x": "%s" % (showX), "y": "%s" % (showY), "type": "%s" % (opName) } ) outlineItem.append(newPoint) if (newHintMaskName != None): outlineItem[-3].set(kPointName, newHintMaskName) # attach the pointName to the first point of the curve. newHintMaskName = None opList.append([opName,curX, curY]) argList = [] if outlineItem != None: if len(outlineItem) == 1: # Just in case we see two moves in a row, delete the previous outlineItem if it has zero length. del newOutline[-1] else: fixStartPoint(outlineItem, opList) # 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 opList by the point name. # We will follow the T1 spec: a glyph may have stem3 counter hints or regular hints, but not both. hintSetList = None if (seenHints) or (len(flexList) > 0): hintInfoDict = XMLElement("dict") idItem = XMLElement("key") idItem.text = "id" hintInfoDict.append(idItem) idString = XMLElement("string") idString.text = "id" hintInfoDict.append(idString) hintSetListItem = XMLElement("key") hintSetListItem.text = kHintSetListName hintInfoDict.append(hintSetListItem) hintSetListArray = XMLElement("array") hintInfoDict.append(hintSetListArray) # Convert the rest of the hint masks to a hintmask op and hintmask bytes. for hintMask in hintMaskList: hintMask.addHintSet(hintSetListArray) if len(flexList) > 0: hintSetListItem = XMLElement("key") hintSetListItem.text = kFlexIndexListName hintInfoDict.append(hintSetListItem) flexArray = XMLElement("array") hintInfoDict.append(flexArray) addFlexHint(flexList, flexArray) return newOutline, hintInfoDict def addHintList(hints, hintsStem3, newHintSetArray, isH): # A charstring may have regular v stem hints or vstem3 hints, but not both. # Same for h stem hints and hstem3 hints. if len(hintsStem3) > 0: hintsStem3.sort() numHints = len(hintsStem3) hintLimit = (kStackLimit-2)/2 if numHints >=hintLimit: hintsStem3 = hintsStem3[:hintLimit] numHints = hintLimit makeStemHintList(hintsStem3, newHintSetArray, isH) else: hints.sort() numHints = len(hints) hintLimit = (kStackLimit-2)/2 if numHints >=hintLimit: hints = hints[:hintLimit] numHints = hintLimit makeHintList(hints, newHintSetArray, isH) def addWhiteSpace(parent, level): child = None childIndent = "\n" + (" "*(level +1)) prentIndent = "\n" + (" "*(level)) #print("parent Tag", parent.tag, repr(parent.text), repr(parent.tail)) for child in parent: child.tail = childIndent addWhiteSpace(child, level +1) if child != None: if parent.text == None: parent.text = childIndent child.tail = prentIndent #print("lastChild Tag", child.tag, repr(child.text), repr(child.tail), "parent Tag", parent.tag) def convertBezToGLIF(ufoFontData, glyphName, bezString, hintsOnly = False): # I need to replace the contours with data from the bez string. glyphPath = ufoFontData.getGlyphSrcPath(glyphName) with open(glyphPath, "r") as fp: data = fp.read() glifXML = XML(data) outlineItem = None libIndex = outlineIndex = -1 outlineIndex = outlineIndex = -1 childIndex = 0 for childElement in glifXML: if childElement.tag == "outline": outlineItem = childElement outlineIndex = childIndex if childElement.tag == "lib": libIndex = childIndex childIndex += 1 newOutlineElement, hintInfoDict = convertBezToOutline(ufoFontData, glyphName, bezString ) #print(xmlToString(stemHints)) if not hintsOnly: if outlineItem == None: # need to add it. Add it before the lib item, if any. if libIndex > 0: glifXML.insert(libIndex, newOutlineElement) else: glifXML.append(newOutlineElement) else: # remove the old one and add the new one. glifXML.remove(outlineItem) glifXML.insert(outlineIndex, newOutlineElement) # convertBezToGLIF is called only if the GLIF has been edited by a tool. We need to update the edit status # in the has map entry. # I assume that convertGLIFToBez has ben run before, which will add an entry for this glyph. ufoFontData.updateHashEntry(glyphName, changed=True) # Add the stem hints. if (hintInfoDict != None): widthXML = glifXML.find("advance") if widthXML != None: width = int(eval(widthXML.get("width"))) else: width = 1000 useDefaultGlyphDir = False newGlyphHash, dataList = ufoFontData.buildGlyphHashValue(width, newOutlineElement, glyphName, useDefaultGlyphDir) # We add this hash to the T1 data, as it is the hash which matches the output outline data. # This is not necessarily the same as the the hash of the source data - autohint can be used to change outlines. if libIndex > 0: libItem = glifXML[libIndex] else: libItem = XMLElement("lib") glifXML.append(libItem) dictItem = libItem.find("dict") if dictItem == None: dictItem = XMLElement("dict") libItem.append(dictItem) # Remove any existing hint data. i = 0 childList = list(dictItem) for childItem in childList: i += 1 if (childItem.tag == "key") and ((childItem.text == kHintDomainName1) or (childItem.text == kHintDomainName2)): dictItem.remove(childItem) # remove key dictItem.remove(childList[i]) # remove data item. glyphDictItem = dictItem key = XMLElement("key") key.text = kHintDomainName2 glyphDictItem.append(key) glyphDictItem.append(hintInfoDict) childList = list(hintInfoDict) idValue = childList[1] idValue.text = newGlyphHash addWhiteSpace(glifXML, 0) return glifXML psautohint-1.1.0/requirements.txt000066400000000000000000000000221324015706300171660ustar00rootroot00000000000000fonttools==3.13.1 psautohint-1.1.0/setup.cfg000066400000000000000000000000711324015706300155270ustar00rootroot00000000000000[sdist] formats = zip [metadata] license_file = LICENSE psautohint-1.1.0/setup.py000066400000000000000000000100741324015706300154240ustar00rootroot00000000000000import platform from setuptools import setup, Extension from setuptools.command.build_ext import build_ext class CustomBuildExt(build_ext): def build_extension(self, ext): compiler_type = self.compiler.compiler_type if compiler_type == "unix": if ext.extra_compile_args is None: ext.extra_compile_args = [] # fixes segmentation fault when python (and thus the extension # module) is compiled with -O3 and tree vectorize: # https://github.com/khaledhosny/psautohint/issues/16 ext.extra_compile_args.append("-fno-tree-vectorize") build_ext.build_extension(self, ext) module1 = Extension("psautohint._psautohint", include_dirs=[ "libpsautohint/include", ], sources=[ "python/psautohint/_psautohint.c", "libpsautohint/src/ac.c", "libpsautohint/src/acfixed.c", "libpsautohint/src/auto.c", "libpsautohint/src/bbox.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/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", ], depends=[ "libpsautohint/include/psautohint.h", "libpsautohint/src/ac.h", "libpsautohint/src/bbox.h", "libpsautohint/src/fontinfo.h", "libpsautohint/src/memory.h", "libpsautohint/src/winstdint.h", "libpsautohint/src/basic.h", "libpsautohint/src/charpath.h", "libpsautohint/src/logging.h", "libpsautohint/src/opcodes.h", ], ) setup(name="psautohint", version="1.1.0", description="Python wrapper for Adobe's PostScript autohinter", url='https://github.com/khaledhosny/psautohint', author='Khaled Hosny', author_email='khaledhosny@eglug.org', license='Apache License, Version 2.0', package_dir={'': 'python'}, packages=['psautohint'], ext_modules=[module1], entry_points={ 'console_scripts': [ "psautohint = psautohint.__main__:main", ], }, install_requires=[ 'fonttools>=3.1.2', ], cmdclass={ 'build_ext': CustomBuildExt, }, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Text Processing :: Fonts', 'Topic :: Multimedia :: Graphics', 'Topic :: Multimedia :: Graphics :: Graphics Conversion', ], ) psautohint-1.1.0/tests/000077500000000000000000000000001324015706300150525ustar00rootroot00000000000000psautohint-1.1.0/tests/Makefile000066400000000000000000000017521324015706300165170ustar00rootroot00000000000000.PHONY: all FORCE PYTHON ?= python UFO:=$(wildcard data/*/*/font.ufo) OTF:=$(wildcard data/*/*/font.otf) TESTS:=$(UFO:%.ufo=%.tst) $(OTF:%.otf=%.pst) all: $(TESTS) # diff options # http://www.gnu.org/software/diffutils/manual/html_node/diff-Options.html # -N If one file is missing, treat it as present but empty # -a Treat all files as text and compare them line-by-line # -u Use the unified output format, showing three lines of context # -r When comparing directories, recursively compare any subdirectories found # -x When comparing directories, ignore files and subdirectories whose basenames # match the pattern %.tst: %.ufo FORCE @echo " Testing $<" @rm -rf $@ @$(PYTHON) -m psautohint -all -qq -o $@ $< @rm -f $@/.DS_Store @diff -Naur --strip-trailing-cr $< $@ @rm -rf $@ %.pst: %.otf FORCE @echo " Testing $<" @rm -rf $@ @$(PYTHON) -m psautohint -qq -o $@ $< @ttx -q -t CFF -o $<.ttx $< @ttx -q -t CFF -o $@.ttx $@ @diff -u $<.ttx $@.ttx @rm -rf $@ $<.ttx $@.ttx FORCE: psautohint-1.1.0/tests/data/000077500000000000000000000000001324015706300157635ustar00rootroot00000000000000