pax_global_header00006660000000000000000000000064132273643150014520gustar00rootroot0000000000000052 comment=213d3627410afd82ddd2b2b6ca5f61dff815fbaa fontmake-1.4.0/000077500000000000000000000000001322736431500133265ustar00rootroot00000000000000fontmake-1.4.0/.gitignore000066400000000000000000000001501322736431500153120ustar00rootroot00000000000000# Byte-compiled files *.pyc # Packaging *.egg *.egg-info/ *.eggs build dist # Unit test .cache/ .tox/ fontmake-1.4.0/.pyup.yml000066400000000000000000000002501322736431500151210ustar00rootroot00000000000000# controls the frequency of updates (undocumented beta feature) schedule: every week # do not pin dependencies unless they have explicit version specifiers pin: False fontmake-1.4.0/.travis.yml000066400000000000000000000040461322736431500154430ustar00rootroot00000000000000sudo: false language: python python: - "2.7" - "3.6" install: - pip install --upgrade pip - pip install -r test_requirements.txt . script: - (cd test && ./run.sh) deploy: # deploy sdist and wheel packages to PyPI on tags - provider: pypi server: https://upload.pypi.org/legacy/ on: repo: googlei18n/fontmake tags: true all_branches: true python: 2.7 user: anthrotype password: secure: PPf1+LkeIOnuY0rQ4pEmNYphl5J93u26EHf8KrNciWAaUlXKopde43bfXI1nkKItYgedq5pVcdOeUrct+WNtbWFSmqDAedBKKgjWDwnvcUd/Hwe6uwFN/uwwk+LXooAGQs4az/kbdKb3H6TWszVArzsl+R7IWCDHwOkRkrBc293Lf3bl/bi8nBuAIijO8CJU8/LYTncs9k/sbo9wxu5usRlYr3D+IBse2vmT23vNYNMSEyd0ISWThdTjD79nDjuo/i05D20HyIUeOcz9HN5s/PhizZRqoN85LHtCV1QrHZ+JjAzy9TXFB1rPdnAwSaTROdipPLw7unkN4kDcLrL8ktaQAmg3ixUIlG7NDZKH9lM0RLF1NK1lB/mUpHT80gFgPgyLjHAEODynbW6VLbJCe84ux1gjiQWqks/PZb99IERyuGSJhdU7q9slB5DBiXdIgCh12Eb/wmJfPNgkDBXPb8tCd7A9AR0a+wTnJgp+RccotBn6CRwyt26bsAt3eE3LMM0quhFNARWkDySCq+ZAc4yIlc9lXhjWnqugZz/TBS5xth7+P1GY4N2Dm7m/gGjyCTGerWkNWoQHsPcWufhbmmsY6KN4LWMi4rzK40DTNgjgrSY+nBfYjLq2zKrr3yfuiFv07YK2NBv1kwXFa8U71y9qWLkhHQNBCj4J54dopuM= distributions: sdist bdist_wheel skip_upload_docs: true # also create a Github Release with the current tag - provider: releases api_key: secure: vfsMIz6Un0QZbS6uisUEUVO4db6b74COS5cu7ipmE13EGY0KtcIGQqgd+J71KxY7yQlnk/cm5Tp9Aei3L1UPEK6G4uC65kVFjq4hJRjOzm1QT6H/wyw6DpC5qUcr/MEZDQaPouVqvK2A30pNmMK+4Kohp61EgeMCgy+gcaTfbMao8o5Ao3FJvSrl31VWZEd/2pwFC9zi0kRmf2QtOVN+nnlCuU22GC4d78HmvjUF70p9XmgL+H73vjum4djd1O0kdw9gVYOolnRDlH8bR39jztKemOz6DnWDoTyB46RZ31Jyt5tsEdLOwWrO+fL2fDKgyg2716F+RaM9bqvM09BRbxHlO2ws+Mmn4g0kZs2DnBmdUac4V4tW5COQ0Ww3W4QaFRkR/caqjISlGujxifXJD/fisqwE+pnLUMQEcel39/UslgAWCtrPH7Bp0dDlopaEnugvQ8AX9hqe9AYaS9ySowcr6UfCWfjalXTbrCcTpH+Sj9r3JjEw1AZBecySwOUZCxZBejltAVdNsN8pnBbUR8oQN3ahLjIiUMDMxDCZnw8S2mQaUZoFqJiTz5SA9ED7yaciiehTLbW6M1cvTS/oqHFb+GGfn4D1CCF7HC9ML3oau82pHU6GcZqOj+zJr2v2YIJBf5cBDK54QBA8ERwNh+7Tq3IEhYQDX7HPtxsIvWI= on: repo: googlei18n/fontmake tags: true all_branches: true python: 2.7 fontmake-1.4.0/CONTRIBUTING.md000066400000000000000000000026521322736431500155640ustar00rootroot00000000000000Want to contribute? Great! First, read this page (including the small print at the end). ### Before you contribute Before we can use your code, you must sign the [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) (CLA), which you can do online. The CLA is necessary mainly because you own the copyright to your changes, even after your contribution becomes part of our codebase, so we need your permission to use and distribute your code. We also need to be sure of various other things—for instance that you'll tell us if you know that your code infringes on other people's patents. You don't have to sign the CLA until after you've submitted your code for review and a member has approved it, but you must do it before we can put your code into our codebase. Before you start working on a larger contribution, you should get in touch with us first through the issue tracker with your idea so that we can help out and possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ### Code reviews All submissions, including submissions by project members, require review. We use Github pull requests for this purpose. ### The small print Contributions made by corporations are covered by a different agreement than the one above, the [Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate). fontmake-1.4.0/LICENSE000066400000000000000000000261351322736431500143420ustar00rootroot00000000000000 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. fontmake-1.4.0/Lib/000077500000000000000000000000001322736431500140345ustar00rootroot00000000000000fontmake-1.4.0/Lib/fontmake/000077500000000000000000000000001322736431500156405ustar00rootroot00000000000000fontmake-1.4.0/Lib/fontmake/__init__.py000066400000000000000000000000261322736431500177470ustar00rootroot00000000000000__version__ = "1.4.0" fontmake-1.4.0/Lib/fontmake/__main__.py000066400000000000000000000204361322736431500177370ustar00rootroot00000000000000# Copyright 2015 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from argparse import ArgumentParser, ArgumentTypeError from fontmake import __version__ from fontmake.font_project import FontProject class PyClassType(object): """ Callable object which returns a Python class defined in named module. It can be passed as type= argument to ArgumentParser.add_argument(). """ def __init__(self, class_name): self.class_name = class_name def __call__(self, module_name): import importlib import inspect try: mod = importlib.import_module(module_name) except ImportError: raise ArgumentTypeError("No module named %r" % module_name) try: klass = getattr(mod, self.class_name) except AttributeError as e: raise ArgumentTypeError("Module %r has no attribute %r" % (module_name, self.class_name)) if not inspect.isclass(klass): raise ArgumentTypeError("%r is not a class: %r" % (self.class_name, type(klass))) return klass def exclude_args(parser, args, excluded_args, source_name): msg = '"%s" argument only available for %s source' for excluded in excluded_args: if args[excluded]: parser.error(msg % (excluded, source_name)) del args[excluded] def main(args=None): parser = ArgumentParser() parser.add_argument('--version', action='version', version=__version__) inputGroup = parser.add_argument_group( title='Input arguments', description='The following arguments are mutually exclusive.') xInputGroup = inputGroup.add_mutually_exclusive_group(required=True) xInputGroup.add_argument( '-g', '--glyphs-path', metavar='GLYPHS', help='Path to .glyphs source file') xInputGroup.add_argument( '-u', '--ufo-paths', nargs='+', metavar='UFO', help='One or more paths to UFO files') xInputGroup.add_argument( '-m', '--mm-designspace', metavar='DESIGNSPACE', help='Path to .designspace file') outputGroup = parser.add_argument_group(title='Output arguments') outputGroup.add_argument( '-o', '--output', nargs='+', default=('otf', 'ttf'), metavar="FORMAT", help='Output font formats. Choose between: %(choices)s. ' 'Default: otf, ttf', choices=('ufo', 'otf', 'ttf', 'ttf-interpolatable', 'variable')) outputGroup.add_argument( '-i', '--interpolate', action='store_true', help='Interpolate masters (for Glyphs or MutatorMath sources only)') outputGroup.add_argument( '-M', '--masters-as-instances', action='store_true', help='Output masters as instances') outputGroup.add_argument( '--family-name', help='Family name to use for masters, and to filter output instances') outputGroup.add_argument( '--round-instances', dest='round_instances', action='store_true', help='Apply integer rounding to all geometry when interpolating') contourGroup = parser.add_argument_group(title='Handling of contours') contourGroup.add_argument( '--keep-overlaps', dest='remove_overlaps', action='store_false', help='Do not remove any overlap.') contourGroup.add_argument( '--keep-direction', dest='reverse_direction', action='store_false', help='Do not reverse contour direction when output is ttf or ' 'ttf-interpolatable') contourGroup.add_argument( '-e', '--conversion-error', type=float, default=None, metavar='ERROR', help='Maximum approximation error for cubic to quadratic conversion ' 'measured in EM') contourGroup.add_argument( '-a', '--autohint', nargs='?', const='', help='Run ttfautohint. Can provide arguments, quoted') layoutGroup = parser.add_argument_group(title='Handling of OpenType Layout') layoutGroup.add_argument( '--interpolate-binary-layout', action='store_true', help='Interpolate layout tables from compiled master binaries. ' 'Requires Glyphs or MutatorMath source.') layoutGroup.add_argument( '--kern-writer-module', metavar="MODULE", dest='kern_writer_class', type=PyClassType('KernFeatureWriter'), help='Module containing a custom `KernFeatureWriter` class.') layoutGroup.add_argument( '--mark-writer-module', metavar="MODULE", dest='mark_writer_class', type=PyClassType('MarkFeatureWriter'), help='Module containing a custom `MarkFeatureWriter` class.') feaCompilerGroup = layoutGroup.add_mutually_exclusive_group(required=False) feaCompilerGroup.add_argument( '--use-afdko', action='store_true', help='Use makeOTF instead of feaLib to compile FEA.') feaCompilerGroup.add_argument( '--mti-source', help='Path to mtiLib .txt feature definitions (use instead of FEA)') glyphnamesGroup = parser.add_mutually_exclusive_group(required=False) glyphnamesGroup.add_argument( '--production-names', dest='use_production_names', action='store_true', help='Rename glyphs with production names if available otherwise use ' 'uninames.') glyphnamesGroup.add_argument( '--no-production-names', dest='use_production_names', action='store_false') subsetGroup = parser.add_mutually_exclusive_group(required=False) subsetGroup.add_argument( '--subset', dest='subset', action='store_true', help='Subset font using export flags set by glyphsLib') subsetGroup.add_argument( '--no-subset', dest='subset', action='store_false') subroutinizeGroup = parser.add_mutually_exclusive_group(required=False) subroutinizeGroup.add_argument( '-s', '--subroutinize', action='store_true', help='Optimize CFF table using compreffor (default)') subroutinizeGroup.add_argument( '-S', '--no-subroutinize', dest='subroutinize', action='store_false') parser.set_defaults(use_production_names=None, subset=None, subroutinize=True) logGroup = parser.add_argument_group(title='Logging arguments') logGroup.add_argument( '--timing', action='store_true', help="Print the elapsed time for each steps") logGroup.add_argument( '--verbose', default='INFO', metavar='LEVEL', choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'), help='Configure the logger verbosity level. Choose between: ' '%(choices)s. Default: INFO') args = vars(parser.parse_args(args)) glyphs_path = args.pop('glyphs_path') ufo_paths = args.pop('ufo_paths') designspace_path = args.pop('mm_designspace') if 'variable' in args['output']: if not (glyphs_path or designspace_path): parser.error( 'Glyphs or designspace source required for variable font') for argname in ('interpolate', 'masters_as_instances', 'interpolate_binary_layout'): if args[argname]: parser.error('--%s option invalid for variable font' % argname.replace("_", "-")) project = FontProject(timing=args.pop('timing'), verbose=args.pop('verbose')) if glyphs_path: project.run_from_glyphs(glyphs_path, **args) return exclude_args(parser, args, ['family_name', 'mti_source'], 'Glyphs') if designspace_path: project.run_from_designspace(designspace_path, **args) return exclude_args( parser, args, ['interpolate', 'interpolate_binary_layout', 'round_instances'], 'Glyphs or MutatorMath') project.run_from_ufos( ufo_paths, is_instance=args.pop('masters_as_instances'), **args) if __name__ == '__main__': main() fontmake-1.4.0/Lib/fontmake/font_project.py000066400000000000000000000623341322736431500207160ustar00rootroot00000000000000# Copyright 2015 Google Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function, division, absolute_import from __future__ import unicode_literals import glob import logging import math import os import shutil import tempfile try: from plistlib import load as readPlist # PY3 except ImportError: from plistlib import readPlist # PY2 from cu2qu.pens import ReverseContourPen from cu2qu.ufo import font_to_quadratic, fonts_to_quadratic from defcon import Font from fontTools import subset from fontTools.misc.py23 import tobytes, basestring from fontTools.misc.loggingTools import configLogger, Timer from fontTools.misc.transform import Transform from fontTools.pens.transformPen import TransformPen from fontTools.ttLib import TTFont from fontTools import varLib from fontTools.varLib.interpolate_layout import interpolate_layout from ufo2ft import compileOTF, compileTTF from ufo2ft.featureCompiler import FeatureCompiler from fontmake.ttfautohint import ttfautohint logger = logging.getLogger(__name__) timer = Timer(logging.getLogger('fontmake.timer'), level=logging.DEBUG) PUBLIC_PREFIX = 'public.' GLYPHS_PREFIX = 'com.schriftgestaltung.' class FontProject(object): """Provides methods for building fonts.""" def __init__(self, timing=False, verbose='INFO'): logging.basicConfig(level=getattr(logging, verbose.upper())) logging.getLogger('fontTools.subset').setLevel(logging.WARNING) if timing: configLogger(logger=timer.logger, level=logging.DEBUG) @timer() def build_master_ufos(self, glyphs_path, family_name=None, mti_source=None): """Build UFOs and MutatorMath designspace from Glyphs source.""" import glyphsLib master_dir = self._output_dir('ufo') instance_dir = self._output_dir('ufo', is_instance=True) masters, designspace_path, instances = glyphsLib.build_masters( glyphs_path, master_dir, designspace_instance_dir=instance_dir, family_name=family_name) if mti_source: self.add_mti_features_to_master_ufos(mti_source, masters) return designspace_path, instances @timer() def add_mti_features_to_master_ufos(self, mti_source, masters): mti_dir = os.path.dirname(mti_source) with open(mti_source, 'rb') as mti_file: mti_paths = readPlist(mti_file) for master in masters: key = os.path.basename(master.path).rstrip('.ufo') for table, path in mti_paths[key].items(): with open(os.path.join(mti_dir, path), "rb") as mti_source: ufo_path = ( 'com.github.googlei18n.ufo2ft.mtiFeatures/%s.mti' % table.strip()) master.data[ufo_path] = mti_source.read() # If we have MTI sources, any Adobe feature files derived from # the Glyphs file should be ignored. We clear it here because # it only contains junk information anyway. master.features.text = "" master.save() @timer() def remove_overlaps(self, ufos, glyph_filter=lambda g: len(g)): """Remove overlaps in UFOs' glyphs' contours.""" from booleanOperations import union, BooleanOperationsError for ufo in ufos: font_name = self._font_name(ufo) logger.info('Removing overlaps for ' + font_name) for glyph in ufo: if not glyph_filter(glyph): continue contours = list(glyph) glyph.clearContours() try: union(contours, glyph.getPointPen()) except BooleanOperationsError: logger.error("Failed to remove overlaps for %s: %r", font_name, glyph.name) raise @timer() def decompose_glyphs(self, ufos, glyph_filter=lambda g: True): """Move components of UFOs' glyphs to their outlines.""" for ufo in ufos: logger.info('Decomposing glyphs for ' + self._font_name(ufo)) for glyph in ufo: if not glyph.components or not glyph_filter(glyph): continue self._deep_copy_contours(ufo, glyph, glyph, Transform()) glyph.clearComponents() def _deep_copy_contours(self, ufo, parent, component, transformation): """Copy contours from component to parent, including nested components.""" for nested in component.components: self._deep_copy_contours( ufo, parent, ufo[nested.baseGlyph], transformation.transform(nested.transformation)) if component != parent: pen = TransformPen(parent.getPen(), transformation) # if the transformation has a negative determinant, it will reverse # the contour direction of the component xx, xy, yx, yy = transformation[:4] if xx*yy - xy*yx < 0: pen = ReverseContourPen(pen) component.draw(pen) @timer() def convert_curves(self, ufos, compatible=False, reverse_direction=True, conversion_error=None): if compatible: logger.info('Converting curves compatibly') fonts_to_quadratic( ufos, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True) else: for ufo in ufos: logger.info('Converting curves for ' + self._font_name(ufo)) font_to_quadratic( ufo, max_err_em=conversion_error, reverse_direction=reverse_direction, dump_stats=True) def build_otfs(self, ufos, remove_overlaps=True, **kwargs): """Build OpenType binaries with CFF outlines.""" logger.info('Building OTFs') self.decompose_glyphs(ufos) if remove_overlaps: self.remove_overlaps(ufos) self.save_otfs(ufos, **kwargs) def build_ttfs( self, ufos, remove_overlaps=True, reverse_direction=True, conversion_error=None, **kwargs): """Build OpenType binaries with TrueType outlines.""" logger.info('Building TTFs') # decompose glyphs with mixed contours and components, since they're # decomposed anyways when compiled into glyf tables. # NOTE: bool(glyph) is True when len(glyph) != 0, i.e. if the glyph # instance has any contours. self.decompose_glyphs(ufos, glyph_filter=lambda g: g) if remove_overlaps: self.remove_overlaps(ufos) self.convert_curves(ufos, reverse_direction=reverse_direction, conversion_error=conversion_error) self.save_otfs(ufos, ttf=True, **kwargs) def build_interpolatable_ttfs( self, ufos, reverse_direction=True, conversion_error=None, **kwargs): """Build OpenType binaries with interpolatable TrueType outlines.""" logger.info('Building interpolation-compatible TTFs') self.convert_curves(ufos, compatible=True, reverse_direction=reverse_direction, conversion_error=conversion_error) self.save_otfs(ufos, ttf=True, interpolatable=True, **kwargs) def build_variable_font(self, designspace_path): """Build OpenType variable font from masters in a designspace.""" # TODO: make output filename user configurable outfile = os.path.splitext(os.path.basename(designspace_path))[0] + '-VF' outfile = self._output_path(outfile, 'ttf', is_variable=True) logger.info('Building variable font ' + outfile) master_locations, _ = self._designspace_locations(designspace_path) ufo_paths = list(master_locations.keys()) ufodir = os.path.dirname(ufo_paths[0]) assert all(p.startswith(ufodir) for p in ufo_paths) ttfdir = self._output_dir('ttf', interpolatable=True) if ufodir: finder = lambda s: s.replace(ufodir, ttfdir).replace('.ufo', '.ttf') else: finder = lambda s: os.path.join(ttfdir, s).replace('.ufo', '.ttf') font, _, _ = varLib.build(designspace_path, finder) font.save(outfile) @timer() def save_otfs( self, ufos, ttf=False, is_instance=False, interpolatable=False, use_afdko=False, autohint=None, subset=None, use_production_names=None, subroutinize=False, interpolate_layout_from=None, kern_writer_class=None, mark_writer_class=None, inplace=True): """Build OpenType binaries from UFOs. Args: ufos: Font objects to compile. ttf: If True, build fonts with TrueType outlines and .ttf extension. is_instance: If output fonts are instances, for generating paths. interpolatable: If output is interpolatable, for generating paths. use_afdko: If True, use AFDKO to compile feature source. autohint: Parameters to provide to ttfautohint. If not provided, the autohinting step is skipped. subset: Whether to subset the output according to data in the UFOs. If not provided, also determined by flags in the UFOs. use_production_names: Whether to use production glyph names in the output. If not provided, determined by flags in the UFOs. subroutinize: If True, subroutinize CFF outlines in output. interpolate_layout_from: A designspace path to give varLib for interpolating layout tables to use in output. kern_writer_class: Class overriding ufo2ft's KernFeatureWriter. mark_writer_class: Class overriding ufo2ft's MarkFeatureWriter. """ ext = 'ttf' if ttf else 'otf' fea_compiler = FDKFeatureCompiler if use_afdko else FeatureCompiler if kern_writer_class is not None: logger.info("Using %r", kern_writer_class.__module__) if mark_writer_class is not None: logger.info("Using %r", mark_writer_class.__module__) if interpolate_layout_from is not None: master_locations, instance_locations = self._designspace_locations( interpolate_layout_from) ufod = self._output_dir('ufo', False, interpolatable) otfd = self._output_dir(ext, False, interpolatable) finder = lambda s: s.replace(ufod, otfd).replace('.ufo', '.' + ext) for ufo in ufos: name = self._font_name(ufo) logger.info('Saving %s for %s' % (ext.upper(), name)) otf_path = self._output_path(ufo, ext, is_instance, interpolatable) if use_production_names is None: use_production_names = not ufo.lib.get( GLYPHS_PREFIX + "Don't use Production Names") compiler_options = dict( featureCompilerClass=fea_compiler, kernWriterClass=kern_writer_class, markWriterClass=mark_writer_class, glyphOrder=ufo.lib.get(PUBLIC_PREFIX + 'glyphOrder'), useProductionNames=use_production_names, inplace=True, # avoid extra copy ) if ttf: font = compileTTF(ufo, convertCubics=False, **compiler_options) else: font = compileOTF(ufo, optimizeCFF=subroutinize, **compiler_options) if interpolate_layout_from is not None: loc = instance_locations[ufo.path] gpos_src = interpolate_layout( interpolate_layout_from, loc, finder, mapped=True) font['GPOS'] = gpos_src['GPOS'] gsub_src = TTFont( finder(self._closest_location(master_locations, loc))) font['GDEF'] = gsub_src['GDEF'] font['GSUB'] = gsub_src['GSUB'] font.save(otf_path) if subset is None: export_key = GLYPHS_PREFIX + 'Glyphs.Export' subset = ((GLYPHS_PREFIX + 'Keep Glyphs') in ufo.lib or any(glyph.lib.get(export_key, True) is False for glyph in ufo)) if subset: self.subset_otf_from_ufo(otf_path, ufo) if ttf and autohint is not None: hinted_otf_path = self._output_path( ufo, ext, is_instance, interpolatable, autohinted=True) ttfautohint(otf_path, hinted_otf_path, args=autohint) def subset_otf_from_ufo(self, otf_path, ufo): """Subset a font using export flags set by glyphsLib.""" keep_glyphs = set(ufo.lib.get(GLYPHS_PREFIX + 'Keep Glyphs', [])) include = [] ufo_order = [glyph_name for glyph_name in ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] if glyph_name in ufo] for old_name, new_name in zip( ufo_order, TTFont(otf_path).getGlyphOrder()): glyph = ufo[old_name] if ((keep_glyphs and old_name not in keep_glyphs) or not glyph.lib.get(GLYPHS_PREFIX + 'Glyphs.Export', True)): continue include.append(new_name) # copied from nototools.subset opt = subset.Options() opt.name_IDs = ['*'] opt.name_legacy = True opt.name_languages = ['*'] opt.layout_features = ['*'] opt.notdef_outline = True opt.recalc_bounds = True opt.recalc_timestamp = True opt.canonical_order = True opt.glyph_names = True font = subset.load_font(otf_path, opt, lazy=False) subsetter = subset.Subsetter(options=opt) subsetter.populate(glyphs=include) subsetter.subset(font) subset.save_font(font, otf_path, opt) def run_from_glyphs( self, glyphs_path, family_name=None, mti_source=None, **kwargs): """Run toolchain from Glyphs source. Args: glyphs_path: Path to source file. preprocess: If True, check source file for un-compilable content. family_name: If provided, uses this family name in the output. mti_source: Path to property list file containing a dictionary mapping UFO masters to dictionaries mapping layout table tags to MTI source paths which should be compiled into those tables. kwargs: Arguments passed along to run_from_designspace. """ logger.info('Building master UFOs and designspace from Glyphs source') designspace_path, instance_data = self.build_master_ufos( glyphs_path, family_name, mti_source=mti_source) self.run_from_designspace( designspace_path, instance_data=instance_data, **kwargs) def run_from_designspace( self, designspace_path, interpolate=False, masters_as_instances=False, instance_data=None, interpolate_binary_layout=False, round_instances=False, **kwargs): """Run toolchain from a MutatorMath design space document. Args: designspace_path: Path to designspace document. interpolate: If True output instance fonts, otherwise just masters. masters_as_instances: If True, output master fonts as instances. instance_data: Data to be applied to instance UFOs, as returned from glyphsLib's parsing function (ignored unless interpolate is True). interpolate_binary_layout: Interpolate layout tables from compiled master binaries. round_instances: apply integer rounding when interpolating with MutatorMath. kwargs: Arguments passed along to run_from_ufos. Raises: TypeError: "variable" output is incompatible with arguments "interpolate", "masters_as_instances", and "interpolate_binary_layout". """ if "variable" in kwargs.get("output", ()): for argname in ("interpolate", "masters_as_instances", "interpolate_binary_layout"): if locals()[argname]: raise TypeError( '"%s" argument incompatible with "variable" output' % argname) from glyphsLib.interpolation import apply_instance_data from mutatorMath.ufo import build as build_designspace from mutatorMath.ufo.document import DesignSpaceDocumentReader ufos = [] if not interpolate or masters_as_instances: reader = DesignSpaceDocumentReader(designspace_path, ufoVersion=3) ufos.extend(reader.getSourcePaths()) if interpolate: logger.info('Interpolating master UFOs from designspace') _, ilocs = self._designspace_locations(designspace_path) for ipath in ilocs.keys(): # mutatorMath does not remove the existing instance UFOs # so we do it ourselves, or else strange things happen... # https://github.com/googlei18n/fontmake/issues/372 if ipath.endswith(".ufo") and os.path.isdir(ipath): logger.debug("Removing %s" % ipath) shutil.rmtree(ipath) results = build_designspace( designspace_path, outputUFOFormatVersion=3, roundGeometry=round_instances) if instance_data is not None: ufos.extend(apply_instance_data(instance_data)) else: for result in results: ufos.extend(result.values()) interpolate_layout_from = ( designspace_path if interpolate_binary_layout else None) self.run_from_ufos( ufos, designspace_path=designspace_path, is_instance=(interpolate or masters_as_instances), interpolate_layout_from=interpolate_layout_from, **kwargs) def run_from_ufos( self, ufos, output=(), designspace_path=None, remove_overlaps=True, reverse_direction=True, conversion_error=None, **kwargs): """Run toolchain from UFO sources. Args: ufos: List of UFO sources, as either paths or opened objects. output: List of output formats to generate. designspace_path: Path to a MutatorMath designspace, used to generate variable font if requested. remove_overlaps: If True, remove overlaps in glyph shapes. reverse_direction: If True, reverse contour directions when compiling TrueType outlines. conversion_error: Error to allow when converting cubic CFF contours to quadratic TrueType contours. kwargs: Arguments passed along to save_otfs. Raises: TypeError: 'variable' specified in output formats but designspace path not provided. """ if set(output) == set(['ufo']): return if hasattr(ufos[0], 'path'): ufo_paths = [ufo.path for ufo in ufos] else: if isinstance(ufos, str): ufo_paths = glob.glob(ufos) else: ufo_paths = ufos ufos = [Font(path) for path in ufo_paths] need_reload = False if 'otf' in output: self.build_otfs( ufos, remove_overlaps, **kwargs) need_reload = True if 'ttf' in output: if need_reload: ufos = [Font(path) for path in ufo_paths] self.build_ttfs( ufos, remove_overlaps, reverse_direction, conversion_error, **kwargs) need_reload = True if 'ttf-interpolatable' in output or 'variable' in output: if need_reload: ufos = [Font(path) for path in ufo_paths] self.build_interpolatable_ttfs( ufos, reverse_direction, conversion_error, **kwargs) if 'variable' in output: if designspace_path is None: raise TypeError('Need designspace to build variable font.') self.build_variable_font(designspace_path) def _font_name(self, ufo): """Generate a postscript-style font name.""" return '%s-%s' % (ufo.info.familyName.replace(' ', ''), ufo.info.styleName.replace(' ', '')) def _output_dir(self, ext, is_instance=False, interpolatable=False, autohinted=False, is_variable=False): """Generate an output directory. Args: ext: extension string. is_instance: The output is instance font or not. interpolatable: The output is interpolatable or not. autohinted: The output is autohinted or not. is_variable: The output is variable font or not. Return: output directory string. """ assert not (is_variable and any([is_instance, interpolatable])) # FIXME? Use user configurable destination folders. if is_variable: dir_prefix = 'variable_' elif is_instance: dir_prefix = 'instance_' else: dir_prefix = 'master_' dir_suffix = '_interpolatable' if interpolatable else '' output_dir = dir_prefix + ext + dir_suffix if autohinted: output_dir = os.path.join('autohinted', output_dir) return output_dir def _output_path(self, ufo_or_font_name, ext, is_instance=False, interpolatable=False, autohinted=False, is_variable=False): """Generate output path for a font file with given extension.""" if isinstance(ufo_or_font_name, basestring): font_name = ufo_or_font_name elif ufo_or_font_name.path: font_name = os.path.splitext(os.path.basename( ufo_or_font_name.path))[0] else: font_name = self._font_name(ufo_or_font_name) out_dir = self._output_dir( ext, is_instance, interpolatable, autohinted, is_variable) if not os.path.exists(out_dir): os.makedirs(out_dir) return os.path.join(out_dir, '%s.%s' % (font_name, ext)) def _designspace_locations(self, designspace_path): """Map font filenames to their locations in a designspace.""" maps = [] ds = varLib.designspace.load(designspace_path) for location_list in (ds['sources'], ds.get('instances', [])): location_map = {} for loc in location_list: abspath = os.path.normpath(os.path.join( os.path.dirname(designspace_path), loc['filename'])) location_map[abspath] = loc['location'] maps.append(location_map) return maps def _closest_location(self, location_map, target): """Return path of font whose location is closest to target.""" dist = lambda a, b: math.sqrt(sum((a[k] - b[k]) ** 2 for k in a.keys())) paths = iter(location_map.keys()) closest = next(paths) closest_dist = dist(target, location_map[closest]) for path in paths: cur_dist = dist(target, location_map[path]) if cur_dist < closest_dist: closest = path closest_dist = cur_dist return closest class FDKFeatureCompiler(FeatureCompiler): """An OTF compiler which uses the AFDKO to compile feature syntax.""" def __init__(self, *args, **kwargs): super(FDKFeatureCompiler, self).__init__(*args, **kwargs) if hasattr(self, 'mtiFeatures') and self.mtiFeatures is not None: raise TypeError("MTI features not supported by makeotf") def setupFile_featureTables(self): if not self.features.strip(): return import subprocess from fontTools.misc.py23 import tostr fd, outline_path = tempfile.mkstemp() os.close(fd) self.outline.save(outline_path) fd, feasrc_path = tempfile.mkstemp() os.close(fd) fd, fea_path = tempfile.mkstemp() os.write(fd, tobytes(self.features, encoding='utf-8')) os.close(fd) report = tostr(subprocess.check_output([ "makeotf", "-o", feasrc_path, "-f", outline_path, "-ff", fea_path], shell=True)) os.remove(outline_path) os.remove(fea_path) logger.info(report) success = "Done." in report if success: feasrc = TTFont(feasrc_path) for table in ["GDEF", "GPOS", "GSUB"]: if table in feasrc: self.outline[table] = feasrc[table] feasrc.close() os.remove(feasrc_path) if not success: raise ValueError("Feature syntax compilation failed.") fontmake-1.4.0/Lib/fontmake/ttfautohint.py000066400000000000000000000040361322736431500205660ustar00rootroot00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import subprocess def ttfautohint(in_file, out_file, args=None, **kwargs): """Thin wrapper around the ttfautohint command line tool. Can take in command line arguments directly as a string, or spelled out as Python keyword arguments. """ arg_list = ['ttfautohint'] file_args = [in_file, out_file] if args is not None: if kwargs: raise TypeError('Should not provide both cmd args and kwargs.') return subprocess.call(arg_list + args.split() + file_args) boolean_options = ( 'debug', 'composites', 'dehint', 'help', 'ignore_restrictions', 'detailed_info', 'no_info', 'adjust_subglyphs', 'symbol', 'ttfa_table', 'verbose', 'version', 'windows_compatibility') other_options = ( 'default_script', 'fallback_script', 'family_suffix', 'hinting_limit', 'fallback_stem_width', 'hinting_range_min', 'control_file', 'hinting_range_max', 'strong_stem_width', 'increase_x_height', 'x_height_snapping_exceptions') for option in boolean_options: if kwargs.pop(option, False): arg_list.append('--' + option.replace('_', '-')) for option in other_options: arg = kwargs.pop(option, None) if arg is not None: arg_list.append('--%s=%s' % (option.replace('_', '-'), arg)) if kwargs: raise TypeError('Unexpected argument(s): ' + ', '.join(kwargs.keys())) return subprocess.call(arg_list + file_args) fontmake-1.4.0/MANIFEST.in000066400000000000000000000002161322736431500150630ustar00rootroot00000000000000include README.rst include CONTRIBUTING.md include LICENSE include *requirements.txt recursive-include tests *.glyphs *.designspace *.plist fontmake-1.4.0/README.rst000066400000000000000000000106441322736431500150220ustar00rootroot00000000000000|Travis Build Status| |Python Versions| |PyPI Version| fontmake ======== This library provides a wrapper for several other Python libraries which together compile fonts from various sources (.glyphs, .ufo) into binaries (.otf, .ttf). Installation ~~~~~~~~~~~~ Fontmake requires Python 2.7, 3.5 or later. Releases are available on `PyPI`_ and can be installed with `pip`_. .. code:: bash pip install fontmake Use the ``-U``, ``--upgrade`` option to update fontmake and its dependencies to the newest available release: .. code:: bash pip install -U fontmake Alternatively, you can download the git repository and install from source: .. code:: bash git clone https://github.com/googlei18n/fontmake cd fontmake pip install . Developers who want to quickly test changes to the source code without re-installing, can use the "--editable" option when installing from a local source checkout: .. code:: bash pip install -e . However, even with an editable installation, it is recommended to always reinstall fontmake after pulling the latest changes from the upstream repo: .. code:: bash git pull pip install -e . This makes sure that the requirements are still met, i.e. updating old ones to new minimum required versions, or installing new ones as needed. It also ensures that the package metadata is updated, e.g. when displaying the installed version with ``pip list`` or ``pip show fontmake``. Virtual environments -------------------- It is recommended to install fontmake inside a "virtual environment" to prevent conflicts between its dependencies and other modules installed globally. You can either install `virtualenv`_ (``pip install --user virtualenv``), or use the Python 3 `venv`_ module. - To create a new virtual environment, e.g. inside the 'env' directory: .. code:: bash python -m virtualenv env Similarly, if you are using the ``venv`` module: .. code:: bash python3 -m venv env - To "activate" a virtual environment, i.e. temporarily place the folder containing the executable scripts on the shell's ``$PATH`` so they can be run from anywhere, run this from the Bash shell (e.g., Linux, Mac): .. code:: bash source env/bin/activate If you are using the Windows Command Prompt: .. code:: bash env/bin/activate.bat - To deactivate the virtual environment and restore the original environment, just do: .. code:: bash deactivate Dependencies and requirements files ----------------------------------- Fontmake is mostly the front-end interface for a number of Python libraries. These are automatically installed or updated to the minimum required version whenever you install a given fontmake version. Pip also allows to specify a set of packages that work together in text files. These can be used with the ``-r`` option to recreate a particular environment. There are two such requirements files in fontmake repository: - ``dev_requirements.txt``: contains the URLs of the git repositories for all fontmake's dependencies. - ``requirements.txt``: contains the current released versions of the direct dependencies which fontmake is tested against. To install from the latest development versions, or upgrade an existing environment to the current ``HEAD`` commit of the respective ``master`` branches, you can do: .. code:: bash pip install -r dev_requirements.txt For more information on requirements files, see `pip documentation`_. Usage ~~~~~ After installation, you can use the ``fontmake`` console script. For example: .. code:: bash fontmake -g MyFont.glyphs # outputs binary font files for masters only Use ``fontmake -h`` to see options for specifying different types of input and output. You can also use fontmake as a module to run intermediate steps in the build process, via methods of the ``fontmake.font_project.FontProject`` class. .. _virtualenv: https://virtualenv.pypa.io .. _venv: https://docs.python.org/3/library/venv.html .. _pip: https://pip.pypa.io .. _pip documentation: https://pip.readthedocs.io/en/stable/user_guide/#requirements-files .. _PyPI: https://pypi.org/project/fontmake .. |Travis Build Status| image:: https://travis-ci.org/googlei18n/fontmake.svg :target: https://travis-ci.org/googlei18n/fontmake .. |Python Versions| image:: https://img.shields.io/badge/python-2.7%2C%203.6-blue.svg .. |PyPI Version| image:: https://img.shields.io/pypi/v/fontmake.svg :target: https://pypi.org/project/fontmake/ fontmake-1.4.0/dev_requirements.txt000066400000000000000000000014011322736431500174440ustar00rootroot00000000000000# for MutatorMath -e git+https://github.com/typesupply/fontMath.git#egg=fontMath -e git+https://github.com/unified-font-object/ufoLib.git#egg=ufoLib # for ufo2ft -e git+https://github.com/fonttools/fonttools.git#egg=fonttools -e git+https://github.com/googlei18n/compreffor.git#egg=compreffor # direct dependencies -e git+https://github.com/googlei18n/cu2qu.git#egg=cu2qu -e git+https://github.com/googlei18n/glyphsLib.git#egg=glyphsLib -e git+https://github.com/googlei18n/ufo2ft.git#egg=ufo2ft -e git+https://github.com/LettError/MutatorMath.git#egg=MutatorMath -e git+https://github.com/typemytype/booleanOperations.git#egg=booleanOperations -e git+https://github.com/typesupply/defcon.git#egg=defcon # install fontmake from source folder in "editable" mode -e . fontmake-1.4.0/requirements.txt000066400000000000000000000001701322736431500166100ustar00rootroot00000000000000fonttools==3.21.2 cu2qu==1.3.0 glyphsLib==2.2.1 ufo2ft==1.1.0 MutatorMath==2.1.0 defcon==0.3.5 booleanOperations==0.8.0 fontmake-1.4.0/setup.cfg000066400000000000000000000012321322736431500151450ustar00rootroot00000000000000[bumpversion] current_version = 1.4.0 commit = True tag = False tag_name = v{new_version} parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? serialize = {major}.{minor}.{patch}.{release}{dev} {major}.{minor}.{patch} [bumpversion:part:release] optional_value = final values = dev final [bumpversion:part:dev] [bumpversion:file:Lib/fontmake/__init__.py] search = __version__ = "{current_version}" replace = __version__ = "{new_version}" [bumpversion:file:setup.py] search = version="{current_version}" replace = version="{new_version}" [wheel] universal = 1 [sdist] formats = zip [metadata] license_file = LICENSE fontmake-1.4.0/setup.py000066400000000000000000000156701322736431500150510ustar00rootroot00000000000000# Copyright 2015 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from setuptools import setup, find_packages, Command from distutils import log from io import open class bump_version(Command): description = "increment the package version and commit the changes" user_options = [ ("major", None, "bump the first digit, for incompatible API changes"), ("minor", None, "bump the second digit, for new backward-compatible features"), ("patch", None, "bump the third digit, for bug fixes (default)"), ] def initialize_options(self): self.minor = False self.major = False self.patch = False def finalize_options(self): part = None for attr in ("major", "minor", "patch"): if getattr(self, attr, False): if part is None: part = attr else: from distutils.errors import DistutilsOptionError raise DistutilsOptionError( "version part options are mutually exclusive") self.part = part or "patch" def bumpversion(self, part, **kwargs): """ Run bumpversion.main() with the specified arguments. """ import bumpversion args = ['--verbose'] if self.verbose > 1 else [] for k, v in kwargs.items(): k = "--{}".format(k.replace("_", "-")) is_bool = isinstance(v, bool) and v is True args.extend([k] if is_bool else [k, str(v)]) args.append(part) log.debug( "$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) bumpversion.main(args) def run(self): log.info("bumping '%s' version" % self.part) self.bumpversion(self.part) class release(bump_version): """Drop the developmental release '.devN' suffix from the package version, open the default text $EDITOR to write release notes, commit the changes and generate a git tag. Release notes can also be set with the -m/--message option, or by reading from standard input. """ description = "tag a new release" user_options = [ ("message=", 'm', "message containing the release notes"), ("sign", "s", "make a GPG-signed tag, using the default key"), ] def initialize_options(self): self.message = None self.sign = False def finalize_options(self): import re current_version = self.distribution.metadata.get_version() if not re.search(r"\.dev[0-9]+", current_version): from distutils.errors import DistutilsSetupError raise DistutilsSetupError( "current version (%s) has no '.devN' suffix.\n " "Run 'setup.py bump_version' with any of " "--major, --minor, --patch options" % current_version) message = self.message if message is None: if sys.stdin.isatty(): # stdin is interactive, use editor to write release notes message = self.edit_release_notes() else: # read release notes from stdin pipe message = sys.stdin.read() if not message.strip(): from distutils.errors import DistutilsSetupError raise DistutilsSetupError("release notes message is empty") self.message = u"v{new_version}\n\n%s" % message self.sign = bool(self.sign) @staticmethod def edit_release_notes(): """Use the default text $EDITOR to write release notes. If $EDITOR is not set, use 'nano'.""" from tempfile import mkstemp import os import shlex import subprocess text_editor = shlex.split(os.environ.get('EDITOR', 'nano')) fd, tmp = mkstemp(prefix='bumpversion-') try: os.close(fd) with open(tmp, 'w', encoding='utf-8') as f: f.write(u"\n\n# Write release notes.\n" "# Lines starting with '#' will be ignored.") subprocess.check_call(text_editor + [tmp]) with open(tmp, 'r', encoding='utf-8') as f: changes = u"".join( l for l in f.readlines() if not l.startswith('#')) finally: os.remove(tmp) return changes def run(self): log.info("stripping developmental release suffix") # drop '.dev0' suffix, commit with given message and create git tag self.bumpversion("release", tag=True, message="Release {new_version}", tag_message=self.message, sign_tags=self.sign) needs_wheel = {'bdist_wheel'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] needs_bump2version = {'release', 'bump_version'}.intersection(sys.argv) bump2version = ['bump2version >= 0.5.7'] if needs_bump2version else [] with open('README.rst', 'r', encoding='utf-8') as f: long_description = f.read() setup( name="fontmake", version="1.4.0", description=("Compile fonts from sources (UFO, Glyphs) to binary " "(OpenType, TrueType)."), long_description=long_description, url="https://github.com/googlei18n/fontmake", license="Apache Software License 2.0", packages=find_packages("Lib"), package_dir={'': 'Lib'}, entry_points={ 'console_scripts': [ 'fontmake = fontmake.__main__:main' ] }, setup_requires=wheel + bump2version, install_requires=[ "fonttools>=3.21.2", "cu2qu>=1.3.0", "glyphsLib>=2.2.1", "ufo2ft>=1.1.0", "MutatorMath>=2.1.0", "defcon>=0.3.5", "booleanOperations>=0.8.0", ], cmdclass={ "release": release, "bump_version": bump_version, }, 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 :: Multimedia :: Graphics", "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Editors :: Vector-Based", "Topic :: Text Processing :: Fonts", ], ) fontmake-1.4.0/test/000077500000000000000000000000001322736431500143055ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/000077500000000000000000000000001322736431500202045ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/AvarDesignspaceTest.designspace000066400000000000000000000022221322736431500263100ustar00rootroot00000000000000 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/000077500000000000000000000000001322736431500230665ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/fontinfo.plist000066400000000000000000000006311322736431500257650ustar00rootroot00000000000000 ascender 0 capHeight 0 descender 0 unitsPerEm 1000 xHeight 0 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/glyphs/000077500000000000000000000000001322736431500243745ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/glyphs/contents.plist000066400000000000000000000003431322736431500273060ustar00rootroot00000000000000 l l.glif fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/glyphs/l.glif000066400000000000000000000005311322736431500254710ustar00rootroot00000000000000 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/layercontents.plist000066400000000000000000000004151322736431500270350ustar00rootroot00000000000000 public.default glyphs fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Bold.ufo/metainfo.plist000066400000000000000000000004451322736431500257500ustar00rootroot00000000000000 creator org.robofab.ufoLib formatVersion 3 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/000077500000000000000000000000001322736431500232555ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/fontinfo.plist000066400000000000000000000006311322736431500261540ustar00rootroot00000000000000 ascender 0 capHeight 0 descender 0 unitsPerEm 1000 xHeight 0 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/glyphs/000077500000000000000000000000001322736431500245635ustar00rootroot00000000000000fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/glyphs/contents.plist000066400000000000000000000003431322736431500274750ustar00rootroot00000000000000 l l.glif fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/glyphs/l.glif000066400000000000000000000005271322736431500256650ustar00rootroot00000000000000 fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/layercontents.plist000066400000000000000000000004151322736431500272240ustar00rootroot00000000000000 public.default glyphs fontmake-1.4.0/test/AvarDesignspaceTest/MyFont-Light.ufo/metainfo.plist000066400000000000000000000004451322736431500261370ustar00rootroot00000000000000 creator org.robofab.ufoLib formatVersion 3 fontmake-1.4.0/test/DesignspaceTest/000077500000000000000000000000001322736431500173725ustar00rootroot00000000000000fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/000077500000000000000000000000001322736431500243145ustar00rootroot00000000000000fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/fontinfo.plist000066400000000000000000000015501322736431500272140ustar00rootroot00000000000000 ascender 0 capHeight 0 descender 0 familyName Designspace Test guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Light unitsPerEm 1000 xHeight 0 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/glyphs/000077500000000000000000000000001322736431500256225ustar00rootroot00000000000000fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/glyphs/contents.plist000066400000000000000000000002741322736431500305370ustar00rootroot00000000000000 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/glyphs/layerinfo.plist000066400000000000000000000002741322736431500306720ustar00rootroot00000000000000 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/layercontents.plist000066400000000000000000000004151322736431500302630ustar00rootroot00000000000000 public.default glyphs fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Light.ufo/metainfo.plist000066400000000000000000000004451322736431500271760ustar00rootroot00000000000000 creator org.robofab.ufoLib formatVersion 3 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/000077500000000000000000000000001322736431500246465ustar00rootroot00000000000000fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/fontinfo.plist000066400000000000000000000015521322736431500275500ustar00rootroot00000000000000 ascender 0 capHeight 0 descender 0 familyName Designspace Test guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Regular unitsPerEm 1000 xHeight 0 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/glyphs/000077500000000000000000000000001322736431500261545ustar00rootroot00000000000000fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/glyphs/contents.plist000066400000000000000000000002741322736431500310710ustar00rootroot00000000000000 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/glyphs/layerinfo.plist000066400000000000000000000002741322736431500312240ustar00rootroot00000000000000 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/layercontents.plist000066400000000000000000000004151322736431500306150ustar00rootroot00000000000000 public.default glyphs fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest-Regular.ufo/metainfo.plist000066400000000000000000000004451322736431500275300ustar00rootroot00000000000000 creator org.robofab.ufoLib formatVersion 3 fontmake-1.4.0/test/DesignspaceTest/DesignspaceTest.designspace000066400000000000000000000021131322736431500246630ustar00rootroot00000000000000 fontmake-1.4.0/test/GuidelineTest.glyphs000066400000000000000000000022071322736431500203030ustar00rootroot00000000000000{ .appVersion = "895"; date = "2016-08-18 19:32:38 +0000"; familyName = GuidelineTest; fontMaster = ( { ascender = 800; capHeight = 700; descender = -200; id = "DE990F7B-AF5B-426E-B4C9-8534B0E19FAB"; xHeight = 500; }, { ascender = 800; capHeight = 700; descender = -200; id = "3437EDCC-B9F2-4034-BEBC-1F1548EA7CD5"; weight = Bold; weightValue = 200; xHeight = 500; } ); glyphs = ( { glyphname = A; lastChange = "2016-08-18 20:42:26 +0000"; layers = ( { guideLines = ( { position = "{100, 200}"; } ); layerId = "DE990F7B-AF5B-426E-B4C9-8534B0E19FAB"; width = 600; }, { guideLines = ( { angle = 90; position = "{100, 200}"; } ); layerId = "3437EDCC-B9F2-4034-BEBC-1F1548EA7CD5"; width = 600; } ); unicode = 0041; }, { glyphname = space; lastChange = "2016-08-18 20:54:22 +0000"; layers = ( { layerId = "DE990F7B-AF5B-426E-B4C9-8534B0E19FAB"; width = 600; }, { layerId = "3437EDCC-B9F2-4034-BEBC-1F1548EA7CD5"; width = 600; } ); unicode = 0020; } ); instances = ( { interpolationWeight = 200; instanceInterpolations = { "3437EDCC-B9F2-4034-BEBC-1F1548EA7CD5" = 1; }; name = Bold; weightClass = Bold; } ); unitsPerEm = 1000; versionMajor = 1; versionMinor = 0; } fontmake-1.4.0/test/InterpolateLayoutTest/000077500000000000000000000000001322736431500206315ustar00rootroot00000000000000fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest 0 GPOS.txt000066400000000000000000000003661322736431500265740ustar00rootroot00000000000000FontDame GPOS table % InterpolateLayoutTest - weight:0 EM 1000 script table begin DFLT default 0 latn default 0 script table end feature table begin 0 kern kern-latn feature table end lookup kern-latn pair left x advance V a -12 lookup end fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest 1000 GPOS.txt000066400000000000000000000003711322736431500270110ustar00rootroot00000000000000FontDame GPOS table % InterpolateLayoutTest - weight:1000 EM 1000 script table begin DFLT default 0 latn default 0 script table end feature table begin 0 kern kern-latn feature table end lookup kern-latn pair left x advance V a -40 lookup end fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest GDEF.txt000066400000000000000000000001111322736431500263750ustar00rootroot00000000000000FontDame GDEF table class definition begin V 1 a 1 class definition end fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest GSUB.txt000066400000000000000000000001341322736431500264350ustar00rootroot00000000000000FontDame GSUB table EM 1000 script table begin DFLT default latn default script table end fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest.glyphs000066400000000000000000000153701322736431500264130ustar00rootroot00000000000000{ .appVersion = "1008"; customParameters = ( { name = vendorID; value = ADBO; } ); date = "2017-04-11 12:06:06 +0000"; designer = "Paul D. Hunt"; disablesAutomaticAlignment = 1; disablesNiceNames = 1; familyName = InterpolateLayoutTest; featurePrefixes = ( { code = ""; name = Prefix; } ); fontMaster = ( { ascender = 722; capHeight = 660; customParameters = ( { name = typoAscender; value = 750; }, { name = typoDescender; value = -250; }, { name = typoLineGap; value = 0; }, { name = hheaAscender; value = 984; }, { name = hheaDescender; value = -273; }, { name = hheaLineGap; value = 0; }, { name = winAscent; value = 984; }, { name = winDescent; value = 273; }, { name = underlineThickness; value = 50; }, { name = underlinePosition; value = -75; } ); descender = -222; horizontalStems = ( 28, 40 ); id = "F391A38D-09EE-4697-93B1-BD76E14F8F30"; verticalStems = ( 32, 48 ); weight = Light; weightValue = 0; xHeight = 478; }, { ascender = 696; capHeight = 650; customParameters = ( { name = typoAscender; value = 750; }, { name = typoDescender; value = -250; }, { name = typoLineGap; value = 0; }, { name = hheaAscender; value = 984; }, { name = hheaDescender; value = -273; }, { name = hheaLineGap; value = 0; }, { name = winAscent; value = 984; }, { name = winDescent; value = 273; }, { name = underlineThickness; value = 50; }, { name = underlinePosition; value = -75; } ); descender = -176; horizontalStems = ( 134, 144 ); id = "AF818D18-A4CE-4FD4-8AD5-FA156C77929B"; verticalStems = ( 172, 176 ); weight = Bold; weightValue = 1000; xHeight = 500; } ); glyphs = ( { glyphname = V; lastChange = "2017-04-11 12:29:38 +0000"; layers = ( { anchors = ( { name = aboveUC; position = "{240, 682}"; }, { name = belowLC; position = "{244, -22}"; } ); layerId = "F391A38D-09EE-4697-93B1-BD76E14F8F30"; paths = ( { closed = 1; nodes = ( "226 0 LINE", "258 0 LINE", "476 660 LINE", "444 660 LINE", "316 264 LINE SMOOTH", "290 182 OFFCURVE", "272 122 OFFCURVE", "244 41 CURVE", "240 41 LINE", "212 122 OFFCURVE", "194 182 OFFCURVE", "168 264 CURVE SMOOTH", "40 660 LINE", "6 660 LINE" ); } ); width = 482; }, { anchors = ( { name = aboveUC; position = "{286, 672}"; }, { name = belowLC; position = "{282, -22}"; } ); layerId = "AF818D18-A4CE-4FD4-8AD5-FA156C77929B"; paths = ( { closed = 1; nodes = ( "182 0 LINE", "390 0 LINE", "582 650 LINE", "406 650 LINE", "340 366 LINE SMOOTH", "323 297 OFFCURVE", "310 230 OFFCURVE", "292 160 CURVE", "288 160 LINE", "270 230 OFFCURVE", "258 297 OFFCURVE", "240 366 CURVE SMOOTH", "172 650 LINE", "-10 650 LINE" ); } ); width = 572; } ); unicode = 0056; }, { glyphname = a; lastChange = "2017-04-11 12:07:34 +0000"; layers = ( { layerId = "F391A38D-09EE-4697-93B1-BD76E14F8F30"; paths = ( { closed = 1; nodes = ( "262 -12 OFFCURVE", "322 24 OFFCURVE", "372 64 CURVE", "374 64 LINE", "378 0 LINE", "404 0 LINE", "404 310 LINE SMOOTH", "404 406 OFFCURVE", "370 490 OFFCURVE", "258 490 CURVE SMOOTH", "180 490 OFFCURVE", "114 450 OFFCURVE", "84 428 CURVE", "100 404 LINE", "130 428 OFFCURVE", "188 462 OFFCURVE", "256 462 CURVE SMOOTH", "356 462 OFFCURVE", "376 376 OFFCURVE", "374 298 CURVE", "158 274 OFFCURVE", "60 224 OFFCURVE", "60 117 CURVE SMOOTH", "60 26 OFFCURVE", "124 -12 OFFCURVE", "198 -12 CURVE SMOOTH" ); }, { closed = 1; nodes = ( "142 16 OFFCURVE", "92 44 OFFCURVE", "92 118 CURVE SMOOTH", "92 200 OFFCURVE", "164 248 OFFCURVE", "374 272 CURVE", "374 98 LINE", "310 44 OFFCURVE", "258 16 OFFCURVE", "200 16 CURVE SMOOTH" ); } ); width = 486; }, { layerId = "AF818D18-A4CE-4FD4-8AD5-FA156C77929B"; paths = ( { closed = 1; nodes = ( "242 -12 OFFCURVE", "286 12 OFFCURVE", "326 48 CURVE", "330 48 LINE", "342 0 LINE", "482 0 LINE", "482 278 LINE SMOOTH", "482 442 OFFCURVE", "404 512 OFFCURVE", "274 512 CURVE SMOOTH", "196 512 OFFCURVE", "124 488 OFFCURVE", "54 446 CURVE", "114 334 LINE", "166 362 OFFCURVE", "204 376 OFFCURVE", "240 376 CURVE SMOOTH", "284 376 OFFCURVE", "306 360 OFFCURVE", "310 324 CURVE", "118 304 OFFCURVE", "38 246 OFFCURVE", "38 142 CURVE SMOOTH", "38 60 OFFCURVE", "94 -12 OFFCURVE", "188 -12 CURVE SMOOTH" ); }, { closed = 1; nodes = ( "218 120 OFFCURVE", "202 133 OFFCURVE", "202 156 CURVE SMOOTH", "202 184 OFFCURVE", "228 210 OFFCURVE", "310 222 CURVE", "310 154 LINE", "292 134 OFFCURVE", "276 120 OFFCURVE", "248 120 CURVE SMOOTH" ); } ); width = 536; } ); unicode = 0061; }, { glyphname = space; lastChange = "2017-04-11 12:07:34 +0000"; layers = ( { layerId = "F391A38D-09EE-4697-93B1-BD76E14F8F30"; width = 200; }, { layerId = "AF818D18-A4CE-4FD4-8AD5-FA156C77929B"; width = 200; } ); unicode = 0020; }, { glyphname = .notdef; lastChange = "2017-04-11 12:07:34 +0000"; layers = ( { layerId = "F391A38D-09EE-4697-93B1-BD76E14F8F30"; paths = ( { closed = 1; nodes = ( "96 0 LINE", "528 0 LINE", "528 660 LINE", "96 660 LINE" ); }, { closed = 1; nodes = ( "144 32 LINE", "246 208 LINE", "310 314 LINE", "314 314 LINE", "376 208 LINE", "476 32 LINE" ); }, { closed = 1; nodes = ( "310 366 LINE", "254 458 LINE", "160 626 LINE", "462 626 LINE", "368 458 LINE", "314 366 LINE" ); }, { closed = 1; nodes = ( "134 74 LINE", "134 610 LINE", "288 340 LINE" ); }, { closed = 1; nodes = ( "488 74 LINE", "336 340 LINE", "488 610 LINE" ); } ); width = 624; }, { layerId = "AF818D18-A4CE-4FD4-8AD5-FA156C77929B"; paths = ( { closed = 1; nodes = ( "76 0 LINE", "628 0 LINE", "628 660 LINE", "76 660 LINE" ); }, { closed = 1; nodes = ( "288 104 LINE", "314 160 LINE", "350 256 LINE", "354 256 LINE", "390 160 LINE", "416 104 LINE" ); }, { closed = 1; nodes = ( "350 424 LINE", "310 520 LINE", "292 556 LINE", "412 556 LINE", "394 520 LINE", "354 424 LINE" ); }, { closed = 1; nodes = ( "188 172 LINE", "188 508 LINE", "270 340 LINE" ); }, { closed = 1; nodes = ( "516 172 LINE", "434 340 LINE", "516 508 LINE" ); } ); width = 704; } ); } ); instances = ( { interpolationWeight = 0; instanceInterpolations = { "F391A38D-09EE-4697-93B1-BD76E14F8F30" = 1; }; name = ExtraLight; weightClass = ExtraLight; }, { instanceInterpolations = { "F391A38D-09EE-4697-93B1-BD76E14F8F30" = 0.9; "AF818D18-A4CE-4FD4-8AD5-FA156C77929B" = 0.1; }; name = Light; weightClass = Light; }, { interpolationWeight = 368; instanceInterpolations = { "F391A38D-09EE-4697-93B1-BD76E14F8F30" = 0.632; "AF818D18-A4CE-4FD4-8AD5-FA156C77929B" = 0.368; }; name = Regular; }, { interpolationWeight = 600; instanceInterpolations = { "F391A38D-09EE-4697-93B1-BD76E14F8F30" = 0.4; "AF818D18-A4CE-4FD4-8AD5-FA156C77929B" = 0.6; }; name = SemiBold; weightClass = SemiBold; }, { interpolationWeight = 824; instanceInterpolations = { "F391A38D-09EE-4697-93B1-BD76E14F8F30" = 0.176; "AF818D18-A4CE-4FD4-8AD5-FA156C77929B" = 0.824; }; isBold = 1; name = Bold; weightClass = Bold; }, { interpolationWeight = 1000; instanceInterpolations = { "AF818D18-A4CE-4FD4-8AD5-FA156C77929B" = 1; }; name = Black; weightClass = Black; } ); unitsPerEm = 1000; versionMajor = 2; versionMinor = 20; } fontmake-1.4.0/test/InterpolateLayoutTest/InterpolateLayoutTest.plist000066400000000000000000000013141322736431500262310ustar00rootroot00000000000000 InterpolateLayoutTest-Light GDEF InterpolateLayoutTest GDEF.txt GPOS InterpolateLayoutTest 0 GPOS.txt GSUB InterpolateLayoutTest GSUB.txt InterpolateLayoutTest-Bold GDEF InterpolateLayoutTest GDEF.txt GPOS InterpolateLayoutTest 1000 GPOS.txt GSUB InterpolateLayoutTest GSUB.txt fontmake-1.4.0/test/run.sh000077500000000000000000000025301322736431500154500ustar00rootroot00000000000000#/usr/bin/env bash # # Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function check_failure() { if [[ $? = 1 ]]; then echo $1 exit 1 fi } for src in 'DesignspaceTest' 'AvarDesignspaceTest'; do cd "${src}" fontmake -i -m "${src}.designspace" check_failure "${src} failed to build" cd .. done for src in 'InterpolateLayoutTest'; do cd "${src}" fontmake -g "${src}.glyphs" --mti-source "${src}.plist" --no-production-names fontmake -g "${src}.glyphs" -i --interpolate-binary-layout --no-production-names check_failure "${src} failed to build" cd .. done for src in 'GuidelineTest'; do fontmake -i -g "${src}.glyphs" check_failure "${src} failed to build" done python test_output.py python test_arguments.py check_failure 'fontmake output incorrect' fontmake-1.4.0/test/test_arguments.py000066400000000000000000000074521322736431500177330ustar00rootroot00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import unittest try: # unittest.mock is only available for python 3+ from unittest import mock from unittest.mock import patch except ImportError: import mock from mock import patch from fontTools.ttLib import TTFont from fontmake.font_project import FontProject import fontmake.__main__ as entry import fontTools class TestFunctionsAreCalledByArguments(unittest.TestCase): @patch('fontmake.font_project.FontProject.run_from_glyphs') def test_run_from_glyphs(self, mock): self.assertFalse(mock.called) entry.main(['-g', 'someGlyphs.glyph']) self.assertTrue(mock.called) @patch('fontmake.font_project.FontProject.run_from_designspace') def test_run_from_designspace(self, mock): self.assertFalse(mock.called) entry.main(['-m', 'someDesignspace.designspace']) self.assertTrue(mock.called) @patch('fontmake.font_project.FontProject.run_from_ufos') def test_run_from_ufos(self, mock): self.assertFalse(mock.called) entry.main(['-u', 'someUfo.ufo']) self.assertTrue(mock.called) # When you nest patch decorators the mocks are passed in to the decorated # function in the same order they applied (the normal python order that # decorators are applied). This means from the bottom up. So mock_build_ttfs # is the first parameter, then mock_build_otfs. @patch('fontmake.font_project.FontProject.build_otfs') @patch('fontmake.font_project.FontProject.build_ttfs') def test_build_otfs(self, mock_build_ttfs, mock_build_otfs): project = FontProject() self.assertFalse(mock_build_otfs.called) self.assertFalse(mock_build_ttfs.called) project.run_from_ufos("path to ufo", output=('otf', 'ttf')) self.assertTrue(mock_build_ttfs.called) self.assertTrue(mock_build_otfs.called) @patch('fontmake.font_project.FontProject.build_interpolatable_ttfs') @patch('fontmake.font_project.FontProject.build_variable_font') def test_run_from_ufos(self, mock_build_variable_font, mock_build_interpolatable_ttfs): project = FontProject() self.assertFalse(mock_build_interpolatable_ttfs.called) self.assertFalse(mock_build_variable_font.called) project.run_from_ufos("path to ufo", output=('variable'), designspace_path="design space") self.assertTrue(mock_build_interpolatable_ttfs.called) self.assertTrue(mock_build_variable_font.called) class TestOutputFileName(unittest.TestCase): @patch('fontTools.varLib.build') @patch('fontTools.ttLib.TTFont.save') @patch('fontmake.font_project.FontProject._designspace_locations') def test_variable_output_filename(self, mock_designspace_locations, mock_TTFont_save, mock_varLib_build): project = FontProject() mock_designspace_locations.return_value = {'master1': 'location1'}, None mock_varLib_build.return_value = TTFont(), None, None project.build_variable_font('path/to/designspace.designspace') self.assertTrue(mock_TTFont_save.called) self.assertTrue(mock_TTFont_save.call_count == 1) self.assertEqual(mock_TTFont_save.call_args, mock.call('variable_ttf/designspace-VF.ttf')) if __name__ == '__main__': unittest.main() fontmake-1.4.0/test/test_output.py000066400000000000000000000023361322736431500172620ustar00rootroot00000000000000# Copyright 2016 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import unittest from fontTools.ttLib import TTFont class TestAvarOutput(unittest.TestCase): def _get_font(self): return TTFont(os.path.join( 'AvarDesignspaceTest', 'instance_ttf', 'MyFont-Regular.ttf')) def test_weight_classes(self): font = self._get_font() self.assertEqual(font['OS/2'].usWeightClass, 400) def test_interpolation(self): font = self._get_font() glyph_set = font.getGlyphSet() glyph = glyph_set['uni006C']._glyph self.assertEqual(glyph.xMin, 50) self.assertEqual(glyph.xMax, 170) if __name__ == '__main__': unittest.main() fontmake-1.4.0/test_requirements.txt000066400000000000000000000000701322736431500176460ustar00rootroot00000000000000-r requirements.txt mock==2.0.0; python_version < "3.3"