diff options
author | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2018-09-03 22:55:49 +0300 |
commit | c833e252c925e8dd68108660710ca835d95daa6f (patch) | |
tree | 6b2e28264ed45efd7f054e453b622098d0d875b8 /misc/pylib/fontbuild | |
parent | 8c1a4c181ef12000179dfec541f1af87e9b03122 (diff) | |
download | inter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz |
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc/pylib/fontbuild')
-rw-r--r-- | misc/pylib/fontbuild/.gitignore | 1 | ||||
-rw-r--r-- | misc/pylib/fontbuild/Build.pyx | 365 | ||||
-rw-r--r-- | misc/pylib/fontbuild/LICENSE | 201 | ||||
-rw-r--r-- | misc/pylib/fontbuild/ORIGIN.txt | 1 | ||||
-rw-r--r-- | misc/pylib/fontbuild/__init__.py | 6 | ||||
-rw-r--r-- | misc/pylib/fontbuild/alignpoints.pyx | 178 | ||||
-rw-r--r-- | misc/pylib/fontbuild/anchors.py | 77 | ||||
-rw-r--r-- | misc/pylib/fontbuild/convertCurves.pyx | 102 | ||||
-rw-r--r-- | misc/pylib/fontbuild/curveFitPen.pyx | 422 | ||||
-rw-r--r-- | misc/pylib/fontbuild/decomposeGlyph.pyx | 23 | ||||
-rwxr-xr-x | misc/pylib/fontbuild/features.py | 189 | ||||
-rw-r--r-- | misc/pylib/fontbuild/generateGlyph.py | 97 | ||||
-rw-r--r-- | misc/pylib/fontbuild/instanceNames.py | 248 | ||||
-rw-r--r-- | misc/pylib/fontbuild/italics.pyx | 308 | ||||
-rwxr-xr-x | misc/pylib/fontbuild/markFeature.py | 52 | ||||
-rw-r--r-- | misc/pylib/fontbuild/mitreGlyph.pyx | 111 | ||||
-rw-r--r-- | misc/pylib/fontbuild/mix.pyx | 358 | ||||
-rw-r--r-- | misc/pylib/fontbuild/setup.py | 20 |
18 files changed, 0 insertions, 2759 deletions
diff --git a/misc/pylib/fontbuild/.gitignore b/misc/pylib/fontbuild/.gitignore deleted file mode 100644 index 064a8d8ef..000000000 --- a/misc/pylib/fontbuild/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.c diff --git a/misc/pylib/fontbuild/Build.pyx b/misc/pylib/fontbuild/Build.pyx deleted file mode 100644 index d4fbd91d7..000000000 --- a/misc/pylib/fontbuild/Build.pyx +++ /dev/null @@ -1,365 +0,0 @@ -# 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 ConfigParser -import os -import sys -import math -import warnings - -from booleanOperations import BooleanOperationManager - -from cu2qu.ufo import fonts_to_quadratic -from fontTools.misc.transform import Transform -from robofab.world import OpenFont -from ufo2ft import compileOTF, compileTTF - -from fontbuild.decomposeGlyph import decomposeGlyph -from fontbuild.features import readFeatureFile, writeFeatureFile -from fontbuild.generateGlyph import generateGlyph -from fontbuild.instanceNames import setInfoRF -from fontbuild.italics import italicizeGlyph -from fontbuild.markFeature import RobotoFeatureCompiler, RobotoKernWriter -from fontbuild.mitreGlyph import mitreGlyph -from fontbuild.mix import Mix,Master,narrowFLGlyph - - -class FontProject: - - def __init__(self, basefont, basedir, configfile, buildTag=''): - self.basefont = basefont - self.basedir = basedir - self.config = ConfigParser.RawConfigParser() - self.configfile = os.path.join(self.basedir, configfile) - self.config.read(self.configfile) - self.buildTag = buildTag - - self.diacriticList = [ - line.strip() for line in self.openResource("diacriticfile") - if not line.startswith("#")] - self.adobeGlyphList = dict( - line.split(";") for line in self.openResource("agl_glyphlistfile") - if not line.startswith("#")) - self.glyphOrder = self.openResource("glyphorder") - - # map exceptional glyph names in Roboto to names in the AGL - roboNames = ( - ('Obar', 'Ocenteredtilde'), ('obar', 'obarred'), - ('eturn', 'eturned'), ('Iota1', 'Iotaafrican')) - for roboName, aglName in roboNames: - self.adobeGlyphList[roboName] = self.adobeGlyphList[aglName] - - self.builddir = "out" - self.decompose = self.config.get("glyphs","decompose").split() - self.predecompose = set(self.config.get("glyphs","predecompose").split()) - self.predecompose_auto = 1 # unless 0, automatically populate predecompose - self.lessItalic = set(self.config.get("glyphs","lessitalic").split()) - self.deleteList = set(self.config.get("glyphs","delete").split()) - self.noItalic = set(self.config.get("glyphs","noitalic").split()) - - self.buildOTF = False - self.compatible = False - self.generatedFonts = [] - self.justCompileUFO = False - - def openResource(self, name): - with open(os.path.join( - self.basedir, self.config.get("res", name))) as resourceFile: - resource = resourceFile.read() - return resource.splitlines() - - def generateOutputPath(self, font, ext): - family = font.info.familyName.replace(" ", "") - style = font.info.styleName.replace(" ", "") - path = os.path.join(self.basedir, self.builddir, family + ext.upper()) - if not os.path.exists(path): - os.makedirs(path) - return os.path.join(path, "%s-%s.%s" % (family, style, ext)) - - def generateFont(self, mix, names, italic=False, swapSuffixes=None, stemWidth=185, - italicMeanYCenter=-825, scaleX=1, panose=[]): - - n = names.split("/") - log("---------------------\n%s, %s\n----------------------" %(n[0],n[1])) - - if isinstance( mix, Mix): - log(">> Mixing masters") - f = mix.generateFont(self.basefont, self.deleteList) - else: - f = mix.copy() - if not self.justCompileUFO: - deleteGlyphs(f, self.deleteList) - - if italic == True: - log(">> Italicizing") - i = 0 - for g in f: - decomposeGlyph(f, g) - removeGlyphOverlap(g) - - for g in f: - i += 1 - if i % 10 == 0: print g.name - - if g.name not in self.noItalic: - if g.name in self.lessItalic: - italicizeGlyph(f, g, 10, stemWidth=stemWidth, - meanYCenter=italicMeanYCenter, - scaleX=scaleX) - else: - italicizeGlyph(f, g, 12, stemWidth=stemWidth, - meanYCenter=italicMeanYCenter, - scaleX=scaleX) - - # set the oblique flag in fsSelection - f.info.openTypeOS2Selection.append(9) - - if swapSuffixes != None: - for swap in swapSuffixes: - swapList = [g.name for g in f if g.name.endswith(swap)] - for gname in swapList: - print gname - swapContours(f, gname.replace(swap,""), gname) - - if self.predecompose_auto == 1: - self.predecompose_auto = 2 - for g in self.basefont: - if len(g.components) > 0: - self.predecompose.add(g.name) - - if not self.justCompileUFO: - for gname in self.predecompose: - if f.has_key(gname): - decomposeGlyph(f, f[gname]) - - log(">> Generating glyphs") - self.generateGlyphs(f, self.diacriticList, self.adobeGlyphList) - # log(">> Reading features") - # readFeatureFile(f, f.features.text) - - # adjust width of italic glyphs - if italic == True: - widthAdjustment = -8 - if widthAdjustment != 0: - leftAdjustment = math.floor(widthAdjustment / 2) - rightAdjustment = math.ceil(widthAdjustment / 2) - for g in f: - if g.name not in self.noItalic: - if g.width != 0: - if g.box is None: - g.width += widthAdjustment - else: - newLeftMargin = int(g.leftMargin + leftAdjustment) - newRightMargin = int(g.rightMargin + rightAdjustment) - g.leftMargin = newLeftMargin - g.rightMargin = newRightMargin - - if not self.justCompileUFO: - log(">> Decomposing") - # for g in f: - # if len(g.components) > 0: - # decomposeGlyph(f, g) - for gname in self.decompose: - if f.has_key(gname): - decomposeGlyph(f, f[gname]) - - copyrightHolderName = '' - if self.config.has_option('main', 'copyrightHolderName'): - copyrightHolderName = self.config.get('main', 'copyrightHolderName') - - def getcfg(name, fallback=''): - if self.config.has_option('main', name): - return self.config.get('main', name) - else: - return fallback - - setInfoRF(f, n, { - 'foundry': getcfg('foundry'), - 'foundryURL': getcfg('foundryURL'), - 'designer': getcfg('designer'), - 'copyrightHolderName': getcfg('copyrightHolderName'), - 'build': self.buildTag, - 'version': getcfg('version'), - 'license': getcfg('license'), - 'licenseURL': getcfg('licenseURL'), - 'italicAngle': float(getcfg('italicAngle', '-12')), - }, panose) - - if not self.compatible and not self.justCompileUFO: - # Remove overlaps etc - cleanCurves(f) - # deleteGlyphs(f, self.deleteList) - - log(">> Generating font files") - ufoName = self.generateOutputPath(f, "ufo") - f.save(ufoName) - self.generatedFonts.append(ufoName) - - if self.justCompileUFO: - print("wrote %s" % ufoName) - return - - # filter glyphorder -- only include glyphs that exists in font - glyphOrder = [] - seenGlyphNames = set() - missingGlyphs = [] - for glyphName in self.glyphOrder: - if glyphName in f: - if glyphName in seenGlyphNames: - raise Exception('Duplicate glyphname %r in glyphorder' % glyphName) - seenGlyphNames.add(glyphName) - glyphOrder.append(glyphName) - - if self.buildOTF: - newFont = OpenFont(ufoName) - otfName = self.generateOutputPath(f, "otf") - log(">> Generating OTF file %s" % otfName) - saveOTF(newFont, otfName, glyphOrder) - - def generateTTFs(self): - """Build TTF for each font generated since last call to generateTTFs.""" - - fonts = [OpenFont(ufo) for ufo in self.generatedFonts] - self.generatedFonts = [] - - log(">> Converting curves to quadratic") - # using a slightly higher max error (e.g. 0.0025 em), dots will have - # fewer control points and look noticeably different - max_err = 0.001 - if self.compatible: - fonts_to_quadratic(fonts, max_err_em=max_err, dump_stats=True, reverse_direction=True) - else: - for font in fonts: - fonts_to_quadratic([font], max_err_em=max_err, dump_stats=True, reverse_direction=True) - - for font in fonts: - ttfName = self.generateOutputPath(font, "ttf") - log(">> Generating TTF file %s" % ttfName) - log(os.path.basename(ttfName)) - glyphOrder = [n for n in self.glyphOrder if n in font] - saveOTF(font, ttfName, glyphOrder, truetype=True) - - def generateGlyphs(self, f, glyphNames, glyphList={}): - log(">> Generating diacritics") - glyphnames = [gname for gname in glyphNames if not gname.startswith("#") and gname != ""] - for glyphName in glyphNames: - generateGlyph(f, glyphName, glyphList) - - -# def transformGlyphMembers(g, m): -# g.width = int(g.width * m.a) -# g.Transform(m) -# for a in g.anchors: -# p = Point(a.p) -# p.Transform(m) -# a.p = p -# for c in g.components: -# # Assumes that components have also been individually transformed -# p = Point(0,0) -# d = Point(c.deltas[0]) -# d.Transform(m) -# p.Transform(m) -# d1 = d - p -# c.deltas[0].x = d1.x -# c.deltas[0].y = d1.y -# s = Point(c.scale) -# s.Transform(m) -# #c.scale = s - - -def swapContours(f,gName1,gName2): - try: - g1 = f[gName1] - g2 = f[gName2] - except KeyError: - log("swapGlyphs failed for %s %s" % (gName1, gName2)) - return - g3 = g1.copy() - - while g1.contours: - g1.removeContour(0) - for contour in g2.contours: - g1.appendContour(contour) - g1.width = g2.width - - while g2.contours: - g2.removeContour(0) - for contour in g3.contours: - g2.appendContour(contour) - g2.width = g3.width - - -def log(msg): - print msg - - -def deleteGlyphs(f, deleteList): - for name in deleteList: - if f.has_key(name): - f.removeGlyph(name) - - -def cleanCurves(f): - log(">> Removing overlaps") - for g in f: - if len(g.components) > 0: - decomposeGlyph(f, g) - removeGlyphOverlap(g) - - # log(">> Mitring sharp corners") - # for g in f: - # mitreGlyph(g, 3., .7) - - # log(">> Converting curves to quadratic") - # for g in f: - # glyphCurvesToQuadratic(g) - - -def removeGlyphOverlap(g): - """Remove overlaps in contours from a glyph.""" - # Note: Although it theoretically would be more efficient to first check - # if contours has overlap before applying clipping, boolean ops and - # re-drawing the shapes, the booleanOperations library's getIntersections - # function adds more overhead in the real world, compared to bluntly - # computing the union for every single glyph. - # - # If we can find a really cheap/efficient way to check if there's any - # overlap, then we should do that before going ahead and computing the - # union. Keep in mind that some glyphs have just a single contour that - # intersects itself, for instance "e". - - contours = g.contours - g.clearContours() - BooleanOperationManager.union(contours, g.getPointPen()) - - -def saveOTF(font, destFile, glyphOrder, truetype=False): - """Save a RoboFab font as an OTF binary using ufo2fdk.""" - with warnings.catch_warnings(): - # Note: ignore warnings produced by compreffor, used by ufo2ft: - # 'Compreffor' class is deprecated; use 'compress' function instead - # which is triggered by ufo2ft which we can't update because newer - # versions completely break the API of ufo2ft. - warnings.simplefilter("ignore") - if truetype: - otf = compileTTF(font, featureCompilerClass=RobotoFeatureCompiler, - kernWriter=RobotoKernWriter, glyphOrder=glyphOrder, - convertCubics=False, - useProductionNames=False) - else: - otf = compileOTF(font, featureCompilerClass=RobotoFeatureCompiler, - kernWriter=RobotoKernWriter, glyphOrder=glyphOrder, - useProductionNames=False) - otf.save(destFile) diff --git a/misc/pylib/fontbuild/LICENSE b/misc/pylib/fontbuild/LICENSE deleted file mode 100644 index 261eeb9e9..000000000 --- a/misc/pylib/fontbuild/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/misc/pylib/fontbuild/ORIGIN.txt b/misc/pylib/fontbuild/ORIGIN.txt deleted file mode 100644 index 1b0a3cf79..000000000 --- a/misc/pylib/fontbuild/ORIGIN.txt +++ /dev/null @@ -1 +0,0 @@ -https://github.com/google/roboto/tree/master/scripts/lib/fontbuild diff --git a/misc/pylib/fontbuild/__init__.py b/misc/pylib/fontbuild/__init__.py deleted file mode 100644 index 4ed720308..000000000 --- a/misc/pylib/fontbuild/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -fontbuild - -A collection of font production tools written for FontLab -""" -version = "0.1"
\ No newline at end of file diff --git a/misc/pylib/fontbuild/alignpoints.pyx b/misc/pylib/fontbuild/alignpoints.pyx deleted file mode 100644 index 363aeef85..000000000 --- a/misc/pylib/fontbuild/alignpoints.pyx +++ /dev/null @@ -1,178 +0,0 @@ -# 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 math - -import numpy as np -from numpy.linalg import lstsq - - -def alignCorners(glyph, va, subsegments): - out = va.copy() - # for i,c in enumerate(subsegments): - # segmentCount = len(glyph.contours[i].segments) - 1 - # n = len(c) - # for j,s in enumerate(c): - # if j < segmentCount: - # seg = glyph.contours[i].segments[j] - # if seg.type == "line": - # subIndex = subsegmentIndex(i,j,subsegments) - # out[subIndex] = alignPoints(va[subIndex]) - - for i,c in enumerate(subsegments): - segmentCount = len(glyph.contours[i].segments) - n = len(c) - for j,s in enumerate(c): - if j < segmentCount - 1: - segType = glyph.contours[i].segments[j].type - segnextType = glyph.contours[i].segments[j+1].type - next = j+1 - elif j == segmentCount -1 and s[1] > 3: - segType = glyph.contours[i].segments[j].type - segNextType = "line" - next = j+1 - elif j == segmentCount: - segType = "line" - segnextType = glyph.contours[i].segments[1].type - if glyph.name == "J": - print s[1] - print segnextType - next = 1 - else: - break - if segType == "line" and segnextType == "line": - subIndex = subsegmentIndex(i,j,subsegments) - pts = va[subIndex] - ptsnext = va[subsegmentIndex(i,next,subsegments)] - # out[subIndex[-1]] = (out[subIndex[-1]] - 500) * 3 + 500 #findCorner(pts, ptsnext) - # print subIndex[-1], subIndex, subsegmentIndex(i,next,subsegments) - try: - out[subIndex[-1]] = findCorner(pts, ptsnext) - except: - pass - # print glyph.name, "Can't find corner: parallel lines" - return out - - -def subsegmentIndex(contourIndex, segmentIndex, subsegments): - # This whole thing is so dumb. Need a better data model for subsegments - - contourOffset = 0 - for i,c in enumerate(subsegments): - if i == contourIndex: - break - contourOffset += c[-1][0] - n = subsegments[contourIndex][-1][0] - # print contourIndex, contourOffset, n - startIndex = subsegments[contourIndex][segmentIndex-1][0] - segmentCount = subsegments[contourIndex][segmentIndex][1] - endIndex = (startIndex + segmentCount + 1) % (n) - - indices = np.array([(startIndex + i) % (n) + contourOffset for i in range(segmentCount + 1)]) - return indices - - -def alignPoints(pts, start=None, end=None): - if start == None or end == None: - start, end = fitLine(pts) - out = pts.copy() - for i,p in enumerate(pts): - out[i] = nearestPoint(start, end, p) - return out - - -def findCorner(pp, nn): - if len(pp) < 4 or len(nn) < 4: - assert 0, "line too short to fit" - pStart,pEnd = fitLine(pp) - nStart,nEnd = fitLine(nn) - prev = pEnd - pStart - next = nEnd - nStart - # print int(np.arctan2(prev[1],prev[0]) / math.pi * 180), - # print int(np.arctan2(next[1],next[0]) / math.pi * 180) - # if lines are parallel, return simple average of end and start points - if np.dot(prev / np.linalg.norm(prev), - next / np.linalg.norm(next)) > .999999: - # print "parallel lines", np.arctan2(prev[1],prev[0]), np.arctan2(next[1],next[0]) - # print prev, next - assert 0, "parallel lines" - # if glyph.name is None: - # # Never happens, but here to fix a bug in Python 2.7 with -OO - # print '' - return lineIntersect(pStart, pEnd, nStart, nEnd) - - -def lineIntersect(p1, p2, p3, p4): - x1, y1 = p1 - x2, y2 = p2 - x3, y3 = p3 - x4, y4 = p4 - - x12 = x1 - x2 - x34 = x3 - x4 - y12 = y1 - y2 - y34 = y3 - y4 - - det = x12 * y34 - y12 * x34 - if det == 0: - print "parallel!" - - a = x1 * y2 - y1 * x2 - b = x3 * y4 - y3 * x4 - - x = (a * x34 - b * x12) / det - y = (a * y34 - b * y12) / det - - return (x,y) - - -def fitLineLSQ(pts): - "returns a line fit with least squares. Fails for vertical lines" - n = len(pts) - a = np.ones((n,2)) - for i in range(n): - a[i,0] = pts[i,0] - line = lstsq(a,pts[:,1])[0] - return line - - -def fitLine(pts): - """returns a start vector and direction vector - Assumes points segments that already form a somewhat smooth line - """ - n = len(pts) - if n < 1: - return (0,0),(0,0) - a = np.zeros((n-1,2)) - for i in range(n-1): - v = pts[i] - pts[i+1] - a[i] = v / np.linalg.norm(v) - direction = np.mean(a[1:-1], axis=0) - start = np.mean(pts[1:-1], axis=0) - return start, start+direction - - -def nearestPoint(a,b,c): - "nearest point to point c on line a_b" - magnitude = np.linalg.norm(b-a) - if magnitude == 0: - raise Exception, "Line segment cannot be 0 length" - return (b-a) * np.dot((c-a) / magnitude, (b-a) / magnitude) + a - - -# pts = np.array([[1,1],[2,2],[3,3],[4,4]]) -# pts2 = np.array([[1,0],[2,0],[3,0],[4,0]]) -# print alignPoints(pts2, start = pts[0], end = pts[0]+pts[0]) -# # print findCorner(pts,pts2) diff --git a/misc/pylib/fontbuild/anchors.py b/misc/pylib/fontbuild/anchors.py deleted file mode 100644 index a617b2f51..000000000 --- a/misc/pylib/fontbuild/anchors.py +++ /dev/null @@ -1,77 +0,0 @@ -# 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. - - -def getGlyph(gname, font): - return font[gname] if font.has_key(gname) else None - - -def getComponentByName(f, g, componentName): - for c in g.components: - if c.baseGlyph == componentName: - return c - -def getAnchorByName(g,anchorName): - for a in g.anchors: - if a.name == anchorName: - return a - -def moveMarkAnchors(f, g, anchorName, accentName, dx, dy): - if "top"==anchorName: - anchors = f[accentName].anchors - for anchor in anchors: - if "mkmktop_acc" == anchor.name: - for anc in g.anchors: - if anc.name == "top": - g.removeAnchor(anc) - break - g.appendAnchor("top", (anchor.x + int(dx), anchor.y + int(dy))) - - elif anchorName in ["bottom", "bottomu"]: - anchors = f[accentName].anchors - for anchor in anchors: - if "mkmkbottom_acc" == anchor.name: - for anc in g.anchors: - if anc.name == "bottom": - g.removeAnchor(anc) - break - x = anchor.x + int(dx) - for anc in anchors: - if "top" == anc.name: - x = anc.x + int(dx) - g.appendAnchor("bottom", (x, anchor.y + int(dy))) - - -def alignComponentToAnchor(f,glyphName,baseName,accentName,anchorName): - g = getGlyph(glyphName,f) - base = getGlyph(baseName,f) - accent = getGlyph(accentName,f) - if g == None or base == None or accent == None: - return - a1 = getAnchorByName(base,anchorName) - a2 = getAnchorByName(accent,"_" + anchorName) - if a1 == None or a2 == None: - return - offset = (a1.x - a2.x, a1.y - a2.y) - c = getComponentByName(f, g, accentName) - c.offset = offset - moveMarkAnchors(f, g, anchorName, accentName, offset[0], offset[1]) - - -def alignComponentsToAnchors(f,glyphName,baseName,accentNames): - for a in accentNames: - if len(a) == 1: - continue - alignComponentToAnchor(f,glyphName,baseName,a[0],a[1]) - diff --git a/misc/pylib/fontbuild/convertCurves.pyx b/misc/pylib/fontbuild/convertCurves.pyx deleted file mode 100644 index b6efd5ca2..000000000 --- a/misc/pylib/fontbuild/convertCurves.pyx +++ /dev/null @@ -1,102 +0,0 @@ -#! /usr/bin/env python -# -# Copyright 2015 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Converts a cubic bezier curve to a quadratic spline with -exactly two off curve points. - -""" - -import numpy -from numpy import array,cross,dot -from fontTools.misc import bezierTools -from robofab.objects.objectsRF import RSegment - -def replaceSegments(contour, segments): - while len(contour): - contour.removeSegment(0) - for s in segments: - contour.appendSegment(s.type, [(p.x, p.y) for p in s.points], s.smooth) - -def calcIntersect(a,b,c,d): - numpy.seterr(all='raise') - e = b-a - f = d-c - p = array([-e[1], e[0]]) - try: - h = dot((a-c),p) / dot(f,p) - except: - print a,b,c,d - raise - return c + dot(f,h) - -def simpleConvertToQuadratic(p0,p1,p2,p3): - p = [array(i.x,i.y) for i in [p0,p1,p2,p3]] - off = calcIntersect(p[0],p[1],p[2],p[3]) - -# OFFCURVE_VECTOR_CORRECTION = -.015 -OFFCURVE_VECTOR_CORRECTION = 0 - -def convertToQuadratic(p0,p1,p2,p3): - # TODO: test for accuracy and subdivide further if needed - p = [(i.x,i.y) for i in [p0,p1,p2,p3]] - # if p[0][0] == p[1][0] and p[0][0] == p[2][0] and p[0][0] == p[2][0] and p[0][0] == p[3][0]: - # return (p[0],p[1],p[2],p[3]) - # if p[0][1] == p[1][1] and p[0][1] == p[2][1] and p[0][1] == p[2][1] and p[0][1] == p[3][1]: - # return (p[0],p[1],p[2],p[3]) - seg1,seg2 = bezierTools.splitCubicAtT(p[0], p[1], p[2], p[3], .5) - pts1 = [array([i[0], i[1]]) for i in seg1] - pts2 = [array([i[0], i[1]]) for i in seg2] - on1 = seg1[0] - on2 = seg2[3] - try: - off1 = calcIntersect(pts1[0], pts1[1], pts1[2], pts1[3]) - off2 = calcIntersect(pts2[0], pts2[1], pts2[2], pts2[3]) - except: - return (p[0],p[1],p[2],p[3]) - off1 = (on1 - off1) * OFFCURVE_VECTOR_CORRECTION + off1 - off2 = (on2 - off2) * OFFCURVE_VECTOR_CORRECTION + off2 - return (on1,off1,off2,on2) - -def cubicSegmentToQuadratic(c,sid): - - segment = c[sid] - if (segment.type != "curve"): - print "Segment type not curve" - return - - #pSegment,junk = getPrevAnchor(c,sid) - pSegment = c[sid-1] #assumes that a curve type will always be proceeded by another point on the same contour - points = convertToQuadratic(pSegment.points[-1],segment.points[0], - segment.points[1],segment.points[2]) - return RSegment( - 'qcurve', [[int(i) for i in p] for p in points[1:]], segment.smooth) - -def glyphCurvesToQuadratic(g): - - for c in g: - segments = [] - for i in range(len(c)): - s = c[i] - if s.type == "curve": - try: - segments.append(cubicSegmentToQuadratic(c, i)) - except Exception: - print g.name, i - raise - else: - segments.append(s) - replaceSegments(c, segments) diff --git a/misc/pylib/fontbuild/curveFitPen.pyx b/misc/pylib/fontbuild/curveFitPen.pyx deleted file mode 100644 index 4b2ee4b34..000000000 --- a/misc/pylib/fontbuild/curveFitPen.pyx +++ /dev/null @@ -1,422 +0,0 @@ -#! /opt/local/bin/pythonw2.7 -# -# 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. - - -__all__ = ["SubsegmentPen","SubsegmentsToCurvesPen", "segmentGlyph", "fitGlyph"] - - -from fontTools.pens.basePen import BasePen -import numpy as np -from numpy import array as v -from numpy.linalg import norm -from robofab.pens.adapterPens import GuessSmoothPointPen -from robofab.pens.pointPen import BasePointToSegmentPen - - -class SubsegmentsToCurvesPointPen(BasePointToSegmentPen): - def __init__(self, glyph, subsegmentGlyph, subsegments): - BasePointToSegmentPen.__init__(self) - self.glyph = glyph - self.subPen = SubsegmentsToCurvesPen(None, glyph.getPen(), subsegmentGlyph, subsegments) - - def setMatchTangents(self, b): - self.subPen.matchTangents = b - - def _flushContour(self, segments): - # - # adapted from robofab.pens.adapterPens.rfUFOPointPen - # - assert len(segments) >= 1 - # if we only have one point and it has a name, we must have an anchor - first = segments[0] - segmentType, points = first - pt, smooth, name, kwargs = points[0] - if len(segments) == 1 and name != None: - self.glyph.appendAnchor(name, pt) - return - else: - segmentType, points = segments[-1] - movePt, smooth, name, kwargs = points[-1] - if smooth: - # last point is smooth, set pen to start smooth - self.subPen.setLastSmooth(True) - if segmentType == 'line': - del segments[-1] - - self.subPen.moveTo(movePt) - - # do the rest of the segments - for segmentType, points in segments: - isSmooth = True in [smooth for pt, smooth, name, kwargs in points] - pp = [pt for pt, smooth, name, kwargs in points] - if segmentType == "line": - assert len(pp) == 1 - if isSmooth: - self.subPen.smoothLineTo(pp[0]) - else: - self.subPen.lineTo(pp[0]) - elif segmentType == "curve": - assert len(pp) == 3 - if isSmooth: - self.subPen.smoothCurveTo(*pp) - else: - self.subPen.curveTo(*pp) - elif segmentType == "qcurve": - assert 0, "qcurve not supported" - else: - assert 0, "illegal segmentType: %s" % segmentType - self.subPen.closePath() - - def addComponent(self, glyphName, transform): - self.subPen.addComponent(glyphName, transform) - - -class SubsegmentsToCurvesPen(BasePen): - def __init__(self, glyphSet, otherPen, subsegmentGlyph, subsegments): - BasePen.__init__(self, None) - self.otherPen = otherPen - self.ssglyph = subsegmentGlyph - self.subsegments = subsegments - self.contourIndex = -1 - self.segmentIndex = -1 - self.lastPoint = (0,0) - self.lastSmooth = False - self.nextSmooth = False - - def setLastSmooth(self, b): - self.lastSmooth = b - - def _moveTo(self, a): - self.contourIndex += 1 - self.segmentIndex = 0 - self.startPoint = a - p = self.ssglyph.contours[self.contourIndex][0].points[0] - self.otherPen.moveTo((p.x, p.y)) - self.lastPoint = a - - def _lineTo(self, a): - self.segmentIndex += 1 - index = self.subsegments[self.contourIndex][self.segmentIndex][0] - p = self.ssglyph.contours[self.contourIndex][index].points[0] - self.otherPen.lineTo((p.x, p.y)) - self.lastPoint = a - self.lastSmooth = False - - def smoothLineTo(self, a): - self.lineTo(a) - self.lastSmooth = True - - def smoothCurveTo(self, a, b, c): - self.nextSmooth = True - self.curveTo(a, b, c) - self.nextSmooth = False - self.lastSmooth = True - - def _curveToOne(self, a, b, c): - self.segmentIndex += 1 - c = self.ssglyph.contours[self.contourIndex] - n = len(c) - startIndex = (self.subsegments[self.contourIndex][self.segmentIndex-1][0]) - segmentCount = (self.subsegments[self.contourIndex][self.segmentIndex][1]) - endIndex = (startIndex + segmentCount + 1) % (n) - - indices = [(startIndex + i) % (n) for i in range(segmentCount + 1)] - points = np.array([(c[i].points[0].x, c[i].points[0].y) for i in indices]) - prevPoint = (c[(startIndex - 1)].points[0].x, c[(startIndex - 1)].points[0].y) - nextPoint = (c[(endIndex) % n].points[0].x, c[(endIndex) % n].points[0].y) - prevTangent = prevPoint - points[0] - nextTangent = nextPoint - points[-1] - - tangent1 = points[1] - points[0] - tangent3 = points[-2] - points[-1] - prevTangent /= np.linalg.norm(prevTangent) - nextTangent /= np.linalg.norm(nextTangent) - tangent1 /= np.linalg.norm(tangent1) - tangent3 /= np.linalg.norm(tangent3) - - tangent1, junk = self.smoothTangents(tangent1, prevTangent, self.lastSmooth) - tangent3, junk = self.smoothTangents(tangent3, nextTangent, self.nextSmooth) - if self.matchTangents == True: - cp = fitBezier(points, tangent1, tangent3) - cp[1] = norm(cp[1] - cp[0]) * tangent1 / norm(tangent1) + cp[0] - cp[2] = norm(cp[2] - cp[3]) * tangent3 / norm(tangent3) + cp[3] - else: - cp = fitBezier(points) - # if self.ssglyph.name == 'r': - # print "-----------" - # print self.lastSmooth, self.nextSmooth - # print "%i %i : %i %i \n %i %i : %i %i \n %i %i : %i %i"%(x1,y1, cp[1,0], cp[1,1], x2,y2, cp[2,0], cp[2,1], x3,y3, cp[3,0], cp[3,1]) - self.otherPen.curveTo((cp[1,0], cp[1,1]), (cp[2,0], cp[2,1]), (cp[3,0], cp[3,1])) - self.lastPoint = c - self.lastSmooth = False - - def smoothTangents(self,t1,t2,forceSmooth = False): - if forceSmooth or (abs(t1.dot(t2)) > .95 and norm(t1-t2) > 1): - # print t1,t2, - t1 = (t1 - t2) / 2 - t2 = -t1 - # print t1,t2 - return t1 / norm(t1), t2 / norm(t2) - - def _closePath(self): - self.otherPen.closePath() - - def _endPath(self): - self.otherPen.endPath() - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - -class SubsegmentPointPen(BasePointToSegmentPen): - def __init__(self, glyph, resolution): - BasePointToSegmentPen.__init__(self) - self.glyph = glyph - self.resolution = resolution - self.subPen = SubsegmentPen(None, glyph.getPen()) - - def getSubsegments(self): - return self.subPen.subsegments[:] - - def _flushContour(self, segments): - # - # adapted from robofab.pens.adapterPens.rfUFOPointPen - # - assert len(segments) >= 1 - # if we only have one point and it has a name, we must have an anchor - first = segments[0] - segmentType, points = first - pt, smooth, name, kwargs = points[0] - if len(segments) == 1 and name != None: - self.glyph.appendAnchor(name, pt) - return - else: - segmentType, points = segments[-1] - movePt, smooth, name, kwargs = points[-1] - if segmentType == 'line': - del segments[-1] - - self.subPen.moveTo(movePt) - - # do the rest of the segments - for segmentType, points in segments: - points = [pt for pt, smooth, name, kwargs in points] - if segmentType == "line": - assert len(points) == 1 - self.subPen.lineTo(points[0]) - elif segmentType == "curve": - assert len(points) == 3 - self.subPen.curveTo(*points) - elif segmentType == "qcurve": - assert 0, "qcurve not supported" - else: - assert 0, "illegal segmentType: %s" % segmentType - self.subPen.closePath() - - def addComponent(self, glyphName, transform): - self.subPen.addComponent(glyphName, transform) - - -class SubsegmentPen(BasePen): - - def __init__(self, glyphSet, otherPen, resolution=25): - BasePen.__init__(self,glyphSet) - self.resolution = resolution - self.otherPen = otherPen - self.subsegments = [] - self.startContour = (0,0) - self.contourIndex = -1 - - def _moveTo(self, a): - self.contourIndex += 1 - self.segmentIndex = 0 - self.subsegments.append([]) - self.subsegmentCount = 0 - self.subsegments[self.contourIndex].append([self.subsegmentCount, 0]) - self.startContour = a - self.lastPoint = a - self.otherPen.moveTo(a) - - def _lineTo(self, a): - count = self.stepsForSegment(a,self.lastPoint) - if count < 1: - count = 1 - self.subsegmentCount += count - self.subsegments[self.contourIndex].append([self.subsegmentCount, count]) - for i in range(1,count+1): - x1 = self.lastPoint[0] + (a[0] - self.lastPoint[0]) * i/float(count) - y1 = self.lastPoint[1] + (a[1] - self.lastPoint[1]) * i/float(count) - self.otherPen.lineTo((x1,y1)) - self.lastPoint = a - - def _curveToOne(self, a, b, c): - count = self.stepsForSegment(c, self.lastPoint) - if count < 2: - count = 2 - self.subsegmentCount += count - self.subsegments[self.contourIndex].append([self.subsegmentCount,count]) - x = self.renderCurve((self.lastPoint[0],a[0],b[0],c[0]),count) - y = self.renderCurve((self.lastPoint[1],a[1],b[1],c[1]),count) - assert len(x) == count - if (c[0] == self.startContour[0] and c[1] == self.startContour[1]): - count -= 1 - for i in range(count): - self.otherPen.lineTo((x[i], y[i])) - self.lastPoint = c - - def _closePath(self): - if not (self.lastPoint[0] == self.startContour[0] and self.lastPoint[1] == self.startContour[1]): - self._lineTo(self.startContour) - - # round values used by otherPen (a RoboFab SegmentToPointPen) to decide - # whether to delete duplicate points at start and end of contour - #TODO(jamesgk) figure out why we have to do this hack, then remove it - c = self.otherPen.contour - for i in [0, -1]: - c[i] = [[round(n, 5) for n in c[i][0]]] + list(c[i][1:]) - - self.otherPen.closePath() - - def _endPath(self): - self.otherPen.endPath() - - def addComponent(self, glyphName, transformation): - self.otherPen.addComponent(glyphName, transformation) - - def stepsForSegment(self, p1, p2): - dist = np.linalg.norm(v(p1) - v(p2)) - out = int(dist / self.resolution) - return out - - def renderCurve(self,p,count): - curvePoints = [] - t = 1.0 / float(count) - temp = t * t - - f = p[0] - fd = 3 * (p[1] - p[0]) * t - fdd_per_2 = 3 * (p[0] - 2 * p[1] + p[2]) * temp - fddd_per_2 = 3 * (3 * (p[1] - p[2]) + p[3] - p[0]) * temp * t - - fddd = fddd_per_2 + fddd_per_2 - fdd = fdd_per_2 + fdd_per_2 - fddd_per_6 = fddd_per_2 * (1.0 / 3) - - for i in range(count): - f = f + fd + fdd_per_2 + fddd_per_6 - fd = fd + fdd + fddd_per_2 - fdd = fdd + fddd - fdd_per_2 = fdd_per_2 + fddd_per_2 - curvePoints.append(f) - - return curvePoints - - -def fitBezierSimple(pts): - T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))] - tsum = np.sum(T) - T = [0] + T - T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))] - T = [[t**3, t**2, t, 1] for t in T] - T = np.array(T) - M = np.array([[-1, 3, -3, 1], - [ 3, -6, 3, 0], - [-3, 3, 0, 0], - [ 1, 0, 0, 0]]) - T = T.dot(M) - T = np.concatenate((T, np.array([[100,0,0,0], [0,0,0,100]]))) - # pts = np.vstack((pts, pts[0] * 100, pts[-1] * 100)) - C = np.linalg.lstsq(T, pts, rcond=-1) - return C[0] - - -def subdivideLineSegment(pts): - out = [pts[0]] - for i in range(1, len(pts)): - out.append(pts[i-1] + (pts[i] - pts[i-1]) * .5) - out.append(pts[i]) - return np.array(out) - - -def fitBezier(pts, tangent0=None, tangent3=None): - if len(pts < 4): - pts = subdivideLineSegment(pts) - T = [np.linalg.norm(pts[i]-pts[i-1]) for i in range(1,len(pts))] - tsum = np.sum(T) - T = [0] + T - T = [np.sum(T[0:i+1])/tsum for i in range(len(pts))] - T = [[t**3, t**2, t, 1] for t in T] - T = np.array(T) - M = np.array([[-1, 3, -3, 1], - [ 3, -6, 3, 0], - [-3, 3, 0, 0], - [ 1, 0, 0, 0]]) - T = T.dot(M) - n = len(pts) - pout = pts.copy() - pout[:,0] -= (T[:,0] * pts[0,0]) + (T[:,3] * pts[-1,0]) - pout[:,1] -= (T[:,0] * pts[0,1]) + (T[:,3] * pts[-1,1]) - - TT = np.zeros((n*2,4)) - for i in range(n): - for j in range(2): - TT[i*2,j*2] = T[i,j+1] - TT[i*2+1,j*2+1] = T[i,j+1] - pout = pout.reshape((n*2,1),order="C") - - if tangent0 is not None and tangent3 is not None: - tangentConstraintsT = np.array([ - [tangent0[1], -tangent0[0], 0, 0], - [0, 0, tangent3[1], -tangent3[0]] - ]) - tangentConstraintsP = np.array([ - [pts[0][1] * -tangent0[0] + pts[0][0] * tangent0[1]], - [pts[-1][1] * -tangent3[0] + pts[-1][0] * tangent3[1]] - ]) - TT = np.concatenate((TT, tangentConstraintsT * 1000)) - pout = np.concatenate((pout, tangentConstraintsP * 1000)) - C = np.linalg.lstsq(TT, pout, rcond=-1)[0].reshape((2,2)) - return np.array([pts[0], C[0], C[1], pts[-1]]) - - -def segmentGlyph(glyph, resolution=50): - g1 = glyph.copy() - g1.clear() - dp = SubsegmentPointPen(g1, resolution) - glyph.drawPoints(dp) - return g1, dp.getSubsegments() - - -def fitGlyph(glyph, subsegmentGlyph, subsegmentIndices, matchTangents=True): - outGlyph = glyph.copy() - outGlyph.clear() - fitPen = SubsegmentsToCurvesPointPen(outGlyph, subsegmentGlyph, subsegmentIndices) - fitPen.setMatchTangents(matchTangents) - # smoothPen = GuessSmoothPointPen(fitPen) - glyph.drawPoints(fitPen) - outGlyph.width = subsegmentGlyph.width - return outGlyph - - -if __name__ == '__main__': - p = SubsegmentPen(None, None) - pts = np.array([ - [0,0], - [.5,.5], - [.5,.5], - [1,1] - ]) - print np.array(p.renderCurve(pts,10)) * 10 diff --git a/misc/pylib/fontbuild/decomposeGlyph.pyx b/misc/pylib/fontbuild/decomposeGlyph.pyx deleted file mode 100644 index 0470fa60b..000000000 --- a/misc/pylib/fontbuild/decomposeGlyph.pyx +++ /dev/null @@ -1,23 +0,0 @@ -def decomposeGlyph(font, glyph): - """Moves the components of a glyph to its outline.""" - if len(glyph.components): - deepCopyContours(font, glyph, glyph, (0, 0), (1, 1)) - glyph.clearComponents() - - -def deepCopyContours(font, parent, component, offset, scale): - """Copy contours to parent from component, including nested components.""" - - for nested in component.components: - deepCopyContours( - font, parent, font[nested.baseGlyph], - (offset[0] + nested.offset[0], offset[1] + nested.offset[1]), - (scale[0] * nested.scale[0], scale[1] * nested.scale[1])) - - if component == parent: - return - for contour in component: - contour = contour.copy() - contour.scale(scale) - contour.move(offset) - parent.appendContour(contour) diff --git a/misc/pylib/fontbuild/features.py b/misc/pylib/fontbuild/features.py deleted file mode 100755 index fe6eca012..000000000 --- a/misc/pylib/fontbuild/features.py +++ /dev/null @@ -1,189 +0,0 @@ -# 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 re - -from feaTools import parser -from feaTools.writers.fdkSyntaxWriter import FDKSyntaxFeatureWriter - - -class FilterFeatureWriter(FDKSyntaxFeatureWriter): - """Feature writer to detect invalid references and duplicate definitions.""" - - def __init__(self, refs=set(), name=None, isFeature=False): - """Initializes the set of known references, empty by default.""" - self.refs = refs - self.featureNames = set() - self.lookupNames = set() - self.tableNames = set() - self.languageSystems = set() - super(FilterFeatureWriter, self).__init__( - name=name, isFeature=isFeature) - - # error to print when undefined reference is found in glyph class - self.classErr = ('Undefined reference "%s" removed from glyph class ' - 'definition %s.') - - # error to print when undefined reference is found in sub or pos rule - subErr = ['Substitution rule with undefined reference "%s" removed'] - if self._name: - subErr.append(" from ") - subErr.append("feature" if self._isFeature else "lookup") - subErr.append(' "%s"' % self._name) - subErr.append(".") - self.subErr = "".join(subErr) - self.posErr = self.subErr.replace("Substitution", "Positioning") - - def _subwriter(self, name, isFeature): - """Use this class for nested expressions e.g. in feature definitions.""" - return FilterFeatureWriter(self.refs, name, isFeature) - - def _flattenRefs(self, refs, flatRefs): - """Flatten a list of references.""" - for ref in refs: - if type(ref) == list: - self._flattenRefs(ref, flatRefs) - elif ref != "'": # ignore contextual class markings - flatRefs.append(ref) - - def _checkRefs(self, refs, errorMsg): - """Check a list of references found in a sub or pos rule.""" - flatRefs = [] - self._flattenRefs(refs, flatRefs) - for ref in flatRefs: - # trailing apostrophes should be ignored - if ref[-1] == "'": - ref = ref[:-1] - if ref not in self.refs: - print errorMsg % ref - # insert an empty instruction so that we can't end up with an - # empty block, which is illegal syntax - super(FilterFeatureWriter, self).rawText(";") - return False - return True - - def classDefinition(self, name, contents): - """Check that contents are valid, then add name to known references.""" - if name in self.refs: - return - newContents = [] - for ref in contents: - if ref not in self.refs and ref != "-": - print self.classErr % (ref, name) - else: - newContents.append(ref) - self.refs.add(name) - super(FilterFeatureWriter, self).classDefinition(name, newContents) - - def gsubType1(self, target, replacement): - """Check a sub rule with one-to-one replacement.""" - if self._checkRefs([target, replacement], self.subErr): - super(FilterFeatureWriter, self).gsubType1(target, replacement) - - def gsubType4(self, target, replacement): - """Check a sub rule with many-to-one replacement.""" - if self._checkRefs([target, replacement], self.subErr): - super(FilterFeatureWriter, self).gsubType4(target, replacement) - - def gsubType6(self, precedingContext, target, trailingContext, replacement): - """Check a sub rule with contextual replacement.""" - refs = [precedingContext, target, trailingContext, replacement] - if self._checkRefs(refs, self.subErr): - super(FilterFeatureWriter, self).gsubType6( - precedingContext, target, trailingContext, replacement) - - def gposType1(self, target, value): - """Check a single positioning rule.""" - if self._checkRefs([target], self.posErr): - super(FilterFeatureWriter, self).gposType1(target, value) - - def gposType2(self, target, value, needEnum=False): - """Check a pair positioning rule.""" - if self._checkRefs(target, self.posErr): - super(FilterFeatureWriter, self).gposType2(target, value, needEnum) - - # these rules may contain references, but they aren't present in Roboto - def gsubType3(self, target, replacement): - raise NotImplementedError - - def feature(self, name): - """Adds a feature definition only once.""" - if name not in self.featureNames: - self.featureNames.add(name) - return super(FilterFeatureWriter, self).feature(name) - # we must return a new writer even if we don't add it to this one - return FDKSyntaxFeatureWriter(name, True) - - def lookup(self, name): - """Adds a lookup block only once.""" - if name not in self.lookupNames: - self.lookupNames.add(name) - return super(FilterFeatureWriter, self).lookup(name) - # we must return a new writer even if we don't add it to this one - return FDKSyntaxFeatureWriter(name, False) - - def languageSystem(self, langTag, scriptTag): - """Adds a language system instruction only once.""" - system = (langTag, scriptTag) - if system not in self.languageSystems: - self.languageSystems.add(system) - super(FilterFeatureWriter, self).languageSystem(langTag, scriptTag) - - def table(self, name, data): - """Adds a table only once.""" - if name in self.tableNames: - return - self.tableNames.add(name) - self._instructions.append("table %s {" % name) - self._instructions.extend([" %s %s;" % line for line in data]) - self._instructions.append("} %s;" % name) - - -def compileFeatureRE(name): - """Compiles a feature-matching regex.""" - - # this is the pattern used internally by feaTools: - # https://github.com/typesupply/feaTools/blob/master/Lib/feaTools/parser.py - featureRE = list(parser.featureContentRE) - featureRE.insert(2, name) - featureRE.insert(6, name) - return re.compile("".join(featureRE)) - - -def updateFeature(font, name, value): - """Add a feature definition, or replace existing one.""" - featureRE = compileFeatureRE(name) - if featureRE.search(font.features.text): - font.features.text = featureRE.sub(value, font.features.text) - else: - font.features.text += "\n" + value - - -def readFeatureFile(font, text, prepend=True): - """Incorporate valid definitions from feature text into font.""" - writer = FilterFeatureWriter(set(font.keys())) - if prepend: - text += font.features.text - else: - text = font.features.text + text - parser.parseFeatures(writer, text) - font.features.text = writer.write() - - -def writeFeatureFile(font, path): - """Write the font's features to an external file.""" - fout = open(path, "w") - fout.write(font.features.text) - fout.close() diff --git a/misc/pylib/fontbuild/generateGlyph.py b/misc/pylib/fontbuild/generateGlyph.py deleted file mode 100644 index 465f940a9..000000000 --- a/misc/pylib/fontbuild/generateGlyph.py +++ /dev/null @@ -1,97 +0,0 @@ -# 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 re -from string import find - -from anchors import alignComponentsToAnchors, getAnchorByName - - -def parseComposite(composite): - c = composite.split("=") - d = c[1].split("/") - glyphName = d[0] - if len(d) == 1: - offset = [0, 0] - else: - offset = [int(i) for i in d[1].split(",")] - accentString = c[0] - accents = accentString.split("+") - baseName = accents.pop(0) - accentNames = [i.split(":") for i in accents] - return (glyphName, baseName, accentNames, offset) - - -def copyMarkAnchors(f, g, srcname, width): - for anchor in f[srcname].anchors: - if anchor.name in ("top_dd", "bottom_dd", "top0315"): - g.appendAnchor(anchor.name, (anchor.x + width, anchor.y)) - - if ("top" == anchor.name and - not any(a.name == "parent_top" for a in g.anchors)): - g.appendAnchor("parent_top", anchor.position) - - if ("bottom" == anchor.name and - not any(a.name == "bottom" for a in g.anchors)): - g.appendAnchor("bottom", anchor.position) - - if any(a.name == "top" for a in g.anchors): - return - - anchor_parent_top = getAnchorByName(g, "parent_top") - if anchor_parent_top is not None: - g.appendAnchor("top", anchor_parent_top.position) - - -def generateGlyph(f,gname,glyphList={}): - glyphName, baseName, accentNames, offset = parseComposite(gname) - if f.has_key(glyphName): - print('Existing glyph "%s" found in font, ignoring composition rule ' - '"%s"' % (glyphName, gname)) - return - - if baseName.find("_") != -1: - g = f.newGlyph(glyphName) - for componentName in baseName.split("_"): - g.appendComponent(componentName, (g.width, 0)) - g.width += f[componentName].width - setUnicodeValue(g, glyphList) - - else: - try: - f.compileGlyph(glyphName, baseName, accentNames) - except KeyError as e: - print('KeyError raised for composition rule "%s", likely "%s" ' - 'anchor not found in glyph "%s"' % (gname, e, baseName)) - return - g = f[glyphName] - setUnicodeValue(g, glyphList) - copyMarkAnchors(f, g, baseName, offset[1] + offset[0]) - if len(accentNames) > 0: - alignComponentsToAnchors(f, glyphName, baseName, accentNames) - if offset[0] != 0 or offset[1] != 0: - g.width += offset[1] + offset[0] - g.move((offset[0], 0), anchors=False) - - -def setUnicodeValue(glyph, glyphList): - """Try to ensure glyph has a unicode value -- used by FDK to make OTFs.""" - - if glyph.name in glyphList: - glyph.unicode = int(glyphList[glyph.name], 16) - else: - uvNameMatch = re.match("uni([\dA-F]{4})$", glyph.name) - if uvNameMatch: - glyph.unicode = int(uvNameMatch.group(1), 16) diff --git a/misc/pylib/fontbuild/instanceNames.py b/misc/pylib/fontbuild/instanceNames.py deleted file mode 100644 index 0ee59f55a..000000000 --- a/misc/pylib/fontbuild/instanceNames.py +++ /dev/null @@ -1,248 +0,0 @@ -# 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 datetime import date -import re -from random import randint -import string - -class InstanceNames: - "Class that allows easy setting of FontLab name fields. TODO: Add proper italic flags" - - foundry = "" - foundryURL = "" - copyrightHolderName = "" - build = "" - version = "1.0" - year = date.today().year - designer = "" - designerURL = "" - license = "" - licenseURL = "" - italicAngle = -12 - - def __init__(self,names): - if type(names) == type(" "): - names = names.split("/") - #print names - self.longfamily = names[0] - self.longstyle = names[1] - self.shortstyle = names[2] - self.subfamilyAbbrev = names[3] - - self.width = self._getWidth() - self.italic = self._getItalic() - self.weight = self._getWeight() - self.fullname = "%s %s" %(self.longfamily, self.longstyle) - self.postscript = re.sub(' ','', self.longfamily) + "-" + re.sub(' ','',self.longstyle) - - if self.subfamilyAbbrev != "" and self.subfamilyAbbrev != None and self.subfamilyAbbrev != "Rg": - self.shortfamily = "%s %s" %(self.longfamily, self.longstyle.split()[0]) - else: - self.shortfamily = self.longfamily - - def setRFNames(self,f, version=1, versionMinor=0, panose={}): - f.info.familyName = self.longfamily - f.info.styleName = self.longstyle - f.info.styleMapFamilyName = self.shortfamily - f.info.styleMapStyleName = self.shortstyle.lower() - f.info.versionMajor = version - f.info.versionMinor = versionMinor - f.info.year = self.year - if len(self.copyrightHolderName) > 0: - f.info.copyright = "Copyright %s %s" % (self.year, self.copyrightHolderName) - f.info.trademark = "%s is a trademark of %s." %(self.longfamily, self.foundry.rstrip('.')) - - if len(self.designer) > 0: - f.info.openTypeNameDesigner = self.designer - if len(self.designerURL) > 0: - f.info.openTypeNameDesignerURL = self.designerURL - f.info.openTypeNameManufacturer = self.foundry - f.info.openTypeNameManufacturerURL = self.foundryURL - f.info.openTypeNameLicense = self.license - f.info.openTypeNameLicenseURL = self.licenseURL - - if self.build is not None and len(self.build): - f.info.openTypeNameVersion = "%i.%i;%s" %(version, versionMinor, self.build) - f.info.openTypeNameUniqueID = "%s:%s:%s" %(self.fullname, self.year, self.build) - else: - f.info.openTypeNameVersion = "%i.%i" %(version, versionMinor) - f.info.openTypeNameUniqueID = "%s:%s" %(self.fullname, self.year) - - # f.info.openTypeNameDescription = "" - # f.info.openTypeNameCompatibleFullName = "" - # f.info.openTypeNameSampleText = "" - if (self.subfamilyAbbrev != "Rg"): - f.info.openTypeNamePreferredFamilyName = self.longfamily - f.info.openTypeNamePreferredSubfamilyName = self.longstyle - - f.info.openTypeOS2WeightClass = self._getWeightCode(self.weight) - f.info.macintoshFONDName = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle) - f.info.postscriptFontName = f.info.macintoshFONDName.replace(" ", "-") - if self.italic: - f.info.italicAngle = self.italicAngle - - if len(panose) > 0: - f.info.openTypeOS2Panose = [ - panose.get('bFamilyType', panose.get('familyType', 0)), - panose.get('bSerifStyle', panose.get('serifStyle', 0)), - panose.get('bWeight', panose.get('weight', 0)), - panose.get('bProportion', panose.get('proportion', 0)), - panose.get('bContrast', panose.get('contrast', 0)), - panose.get('bStrokeVariation', panose.get('strokeVariation', 0)), - panose.get('bArmStyle', panose.get('armStyle', 0)), - panose.get('bLetterform', panose.get('letterform', 0)), - panose.get('bMidline', panose.get('midline', 0)), - panose.get('bXHeight', panose.get('xHeight', 0)), - ] - - - def setFLNames(self,flFont): - - from FL import NameRecord - - flFont.family_name = self.shortfamily - flFont.mac_compatible = self.fullname - flFont.style_name = self.longstyle - flFont.full_name = self.fullname - flFont.font_name = self.postscript - flFont.font_style = self._getStyleCode() - flFont.menu_name = self.shortfamily - flFont.apple_name = re.sub(' ','',self.longfamily) + " " + re.sub(' ','',self.longstyle) - flFont.fond_id = randint(1000,9999) - flFont.pref_family_name = self.longfamily - flFont.pref_style_name = self.longstyle - flFont.weight = self.weight - flFont.weight_code = self._getWeightCode(self.weight) - flFont.width = self.width - if len(self.italic): - flFont.italic_angle = -12 - - fn = flFont.fontnames - fn.clean() - #fn.append(NameRecord(0,1,0,0, "Font data copyright %s %s" %(self.foundry, self.year) )) - #fn.append(NameRecord(0,3,1,1033, "Font data copyright %s %s" %(self.foundry, self.year) )) - copyrightHolderName = self.copyrightHolderName if len(self.copyrightHolderName) > 0 else self.foundry - fn.append(NameRecord(0,1,0,0, "Copyright %s %s" %(self.year, copyrightHolderName) )) - fn.append(NameRecord(0,3,1,1033, "Copyright %s %s" %(self.year, copyrightHolderName) )) - fn.append(NameRecord(1,1,0,0, self.longfamily )) - fn.append(NameRecord(1,3,1,1033, self.shortfamily )) - fn.append(NameRecord(2,1,0,0, self.longstyle )) - fn.append(NameRecord(2,3,1,1033, self.longstyle )) - #fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) )) - #fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.longfamily, self.year) )) - fn.append(NameRecord(3,1,0,0, "%s:%s:%s" %(self.foundry, self.fullname, self.year) )) - fn.append(NameRecord(3,3,1,1033, "%s:%s:%s" %(self.foundry, self.fullname, self.year) )) - fn.append(NameRecord(4,1,0,0, self.fullname )) - fn.append(NameRecord(4,3,1,1033, self.fullname )) - if len(self.build) > 0: - fn.append(NameRecord(5,1,0,0, "Version %s%s; %s" %(self.version, self.build, self.year) )) - fn.append(NameRecord(5,3,1,1033, "Version %s%s; %s" %(self.version, self.build, self.year) )) - else: - fn.append(NameRecord(5,1,0,0, "Version %s; %s" %(self.version, self.year) )) - fn.append(NameRecord(5,3,1,1033, "Version %s; %s" %(self.version, self.year) )) - fn.append(NameRecord(6,1,0,0, self.postscript )) - fn.append(NameRecord(6,3,1,1033, self.postscript )) - fn.append(NameRecord(7,1,0,0, "%s is a trademark of %s." %(self.longfamily, self.foundry) )) - fn.append(NameRecord(7,3,1,1033, "%s is a trademark of %s." %(self.longfamily, self.foundry) )) - fn.append(NameRecord(9,1,0,0, self.foundry )) - fn.append(NameRecord(9,3,1,1033, self.foundry )) - fn.append(NameRecord(11,1,0,0, self.foundryURL )) - fn.append(NameRecord(11,3,1,1033, self.foundryURL )) - fn.append(NameRecord(12,1,0,0, self.designer )) - fn.append(NameRecord(12,3,1,1033, self.designer )) - fn.append(NameRecord(13,1,0,0, self.license )) - fn.append(NameRecord(13,3,1,1033, self.license )) - fn.append(NameRecord(14,1,0,0, self.licenseURL )) - fn.append(NameRecord(14,3,1,1033, self.licenseURL )) - if (self.subfamilyAbbrev != "Rg"): - fn.append(NameRecord(16,3,1,1033, self.longfamily )) - fn.append(NameRecord(17,3,1,1033, self.longstyle)) - #else: - #fn.append(NameRecord(17,3,1,1033,"")) - #fn.append(NameRecord(18,1,0,0, re.sub("Italic","It", self.fullname))) - - def _getSubstyle(self, regex): - substyle = re.findall(regex, self.longstyle) - if len(substyle) > 0: - return substyle[0] - else: - return "" - - def _getItalic(self): - return self._getSubstyle(r"Italic|Oblique|Obliq") - - def _getWeight(self): - w = self._getSubstyle(r"Extrabold|Superbold|Super|Fat|Black|Bold|Semibold|Demibold|Medium|Light|Thin") - if w == "": - w = "Regular" - return w - - def _getWidth(self): - w = self._getSubstyle(r"Condensed|Extended|Narrow|Wide") - if w == "": - w = "Normal" - return w - - def _getStyleCode(self): - #print "shortstyle:", self.shortstyle - styleCode = 0 - if self.shortstyle == "Bold": - styleCode = 32 - if self.shortstyle == "Italic": - styleCode = 1 - if self.shortstyle == "Bold Italic": - styleCode = 33 - if self.longstyle == "Regular": - styleCode = 64 - return styleCode - - def _getWeightCode(self,weight): - if weight == "Thin": - return 250 - elif weight == "Light": - return 300 - elif weight == "Bold": - return 700 - elif weight == "Medium": - return 500 - elif weight == "Semibold": - return 600 - elif weight == "Black": - return 900 - elif weight == "Fat": - return 900 - - return 400 - -def setNames(f,names,foundry="",version="1.0",build=""): - InstanceNames.foundry = foundry - InstanceNames.version = version - InstanceNames.build = build - i = InstanceNames(names) - i.setFLNames(f) - - -def setInfoRF(f, names, attrs={}, panose={}): - i = InstanceNames(names) - version, versionMinor = (1, 0) - for k,v in attrs.iteritems(): - if k == 'version': - if v.find('.') != -1: - version, versionMinor = [int(num) for num in v.split(".")] - else: - version = int(v) - setattr(i, k, v) - i.setRFNames(f, version=version, versionMinor=versionMinor, panose=panose) diff --git a/misc/pylib/fontbuild/italics.pyx b/misc/pylib/fontbuild/italics.pyx deleted file mode 100644 index 769586f40..000000000 --- a/misc/pylib/fontbuild/italics.pyx +++ /dev/null @@ -1,308 +0,0 @@ -# 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 math - -from fontTools.misc.transform import Transform -import numpy as np -from numpy.linalg import norm -from scipy.sparse.linalg import cg -from scipy.ndimage.filters import gaussian_filter1d as gaussian -from scipy.cluster.vq import vq, whiten - -from fontbuild.alignpoints import alignCorners -from fontbuild.curveFitPen import fitGlyph, segmentGlyph - - -def italicizeGlyph(f, g, angle=10, stemWidth=185, meanYCenter=-825, scaleX=1): - unic = g.unicode #save unicode - - glyph = f[g.name] - slope = np.tanh(math.pi * angle / 180) - - # determine how far on the x axis the glyph should slide - # to compensate for the slant. - # meanYCenter: - # -600 is a magic number that assumes a 2048 unit em square, - # and -825 for a 2816 unit em square. (UPM*0.29296875) - m = Transform(1, 0, slope, 1, 0, 0) - xoffset, junk = m.transformPoint((0, meanYCenter)) - m = Transform(scaleX, 0, slope, 1, xoffset, 0) - - if len(glyph) > 0: - g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth) - f.insertGlyph(g2, g.name) - - transformFLGlyphMembers(f[g.name], m) - - if unic > 0xFFFF: #restore unicode - g.unicode = unic - - -def italicize(glyph, angle=12, stemWidth=180, xoffset=-50): - CURVE_CORRECTION_WEIGHT = .03 - CORNER_WEIGHT = 10 - - # decompose the glyph into smaller segments - ga, subsegments = segmentGlyph(glyph,25) - va, e = glyphToMesh(ga) - n = len(va) - grad = mapEdges(lambda a, pn: normalize(pn[0]-a), va, e) - cornerWeights = mapEdges(lambda a, pn: normalize(pn[0]-a).dot(normalize(a-pn[1])), grad, e)[:,0].reshape((-1,1)) - smooth = np.ones((n,1)) * CURVE_CORRECTION_WEIGHT - - controlPoints = findControlPointsInMesh(glyph, va, subsegments) - smooth[controlPoints > 0] = 1 - smooth[cornerWeights < .6] = CORNER_WEIGHT - # smooth[cornerWeights >= .9999] = 1 - - out = va.copy() - hascurves = False - for c in glyph.contours: - for s in c.segments: - if s.type == "curve": - hascurves = True - break - if hascurves: - break - if stemWidth > 100: - outCorrected = skewMesh(recompose(skewMesh(out, angle * 1.6), grad, e, smooth=smooth), -angle * 1.6) - # out = copyMeshDetails(va, out, e, 6) - else: - outCorrected = out - - # create a transform for italicizing - normals = edgeNormals(out, e) - center = va + normals * stemWidth * .4 - if stemWidth > 130: - center[:, 0] = va[:, 0] * .7 + center[:,0] * .3 - centerSkew = skewMesh(center.dot(np.array([[.97,0],[0,1]])), angle * .9) - - # apply the transform - out = outCorrected + (centerSkew - center) - out[:,1] = outCorrected[:,1] - - # make some corrections - smooth = np.ones((n,1)) * .1 - out = alignCorners(glyph, out, subsegments) - out = copyMeshDetails(skewMesh(va, angle), out, e, 7, smooth=smooth) - # grad = mapEdges(lambda a,(p,n): normalize(p-a), skewMesh(outCorrected, angle*.9), e) - # out = recompose(out, grad, e, smooth=smooth) - - out = skewMesh(out, angle * .1) - out[:,0] += xoffset - # out[:,1] = outCorrected[:,1] - out[va[:,1] == 0, 1] = 0 - gOut = meshToGlyph(out, ga) - # gOut.width *= .97 - # gOut.width += 10 - # return gOut - - # recompose the glyph into original segments - return fitGlyph(glyph, gOut, subsegments) - - -def transformFLGlyphMembers(g, m, transformAnchors = True): - # g.transform(m) - g.width = g.width * m[0] - p = m.transformPoint((0,0)) - for c in g.components: - d = m.transformPoint(c.offset) - c.offset = (d[0] - p[0], d[1] - p[1]) - if transformAnchors: - for a in g.anchors: - aa = m.transformPoint((a.x,a.y)) - a.x = aa[0] - # a.x,a.y = (aa[0] - p[0], aa[1] - p[1]) - # a.x = a.x - m[4] - - -def glyphToMesh(g): - points = [] - edges = {} - offset = 0 - for c in g.contours: - if len(c) < 2: - continue - for i,prev,next in rangePrevNext(len(c)): - points.append((c[i].points[0].x, c[i].points[0].y)) - edges[i + offset] = np.array([prev + offset, next + offset], dtype=int) - offset += len(c) - return np.array(points), edges - - -def meshToGlyph(points, g): - g1 = g.copy() - j = 0 - for c in g1.contours: - if len(c) < 2: - continue - for i in range(len(c)): - c[i].points[0].x = points[j][0] - c[i].points[0].y = points[j][1] - j += 1 - return g1 - - -def quantizeGradient(grad, book=None): - if book == None: - book = np.array([(1,0),(0,1),(0,-1),(-1,0)]) - indexArray = vq(whiten(grad), book)[0] - out = book[indexArray] - for i,v in enumerate(out): - out[i] = normalize(v) - return out - - -def findControlPointsInMesh(glyph, va, subsegments): - controlPointIndices = np.zeros((len(va),1)) - index = 0 - for i,c in enumerate(subsegments): - segmentCount = len(glyph.contours[i].segments) - 1 - for j,s in enumerate(c): - if j < segmentCount: - if glyph.contours[i].segments[j].type == "line": - controlPointIndices[index] = 1 - index += s[1] - return controlPointIndices - - -def recompose(v, grad, e, smooth=1, P=None, distance=None): - n = len(v) - if distance == None: - distance = mapEdges(lambda a, pn: norm(pn[0] - a), v, e) - if (P == None): - P = mP(v,e) - P += np.identity(n) * smooth - f = v.copy() - for i,(prev,next) in e.iteritems(): - f[i] = (grad[next] * distance[next] - grad[i] * distance[i]) - out = v.copy() - f += v * smooth - for i in range(len(out[0,:])): - out[:,i] = cg(P, f[:,i])[0] - return out - - -def mP(v,e): - n = len(v) - M = np.zeros((n,n)) - for i, edges in e.iteritems(): - w = -2 / float(len(edges)) - for index in edges: - M[i,index] = w - M[i,i] = 2 - return M - - -def normalize(v): - n = np.linalg.norm(v) - if n == 0: - return v - return v/n - - -def mapEdges(func,v,e,*args): - b = v.copy() - for i, edges in e.iteritems(): - b[i] = func(v[i], [v[j] for j in edges], *args) - return b - - -def getNormal(a,b,c): - "Assumes TT winding direction" - p = np.roll(normalize(b - a), 1) - n = -np.roll(normalize(c - a), 1) - p[1] *= -1 - n[1] *= -1 - # print p, n, normalize((p + n) * .5) - return normalize((p + n) * .5) - - -def edgeNormals(v,e): - "Assumes a mesh where each vertex has exactly least two edges" - return mapEdges(lambda a, pn : getNormal(a,pn[0],pn[1]),v,e) - - -def rangePrevNext(count): - c = np.arange(count,dtype=int) - r = np.vstack((c, np.roll(c, 1), np.roll(c, -1))) - return r.T - - -def skewMesh(v,angle): - slope = np.tanh([math.pi * angle / 180]) - return v.dot(np.array([[1,0],[slope,1]])) - - -def labelConnected(e): - label = 0 - labels = np.zeros((len(e),1)) - for i,(prev,next) in e.iteritems(): - labels[i] = label - if next <= i: - label += 1 - return labels - - -def copyGradDetails(a,b,e,scale=15): - n = len(a) - labels = labelConnected(e) - out = a.astype(float).copy() - for i in range(labels[-1]+1): - mask = (labels==i).flatten() - out[mask,:] = gaussian(b[mask,:], scale, mode="wrap", axis=0) + a[mask,:] - gaussian(a[mask,:], scale, mode="wrap", axis=0) - return out - - -def copyMeshDetails(va,vb,e,scale=5,smooth=.01): - gradA = mapEdges(lambda a, pn: normalize(pn[0]-a), va, e) - gradB = mapEdges(lambda a, pn: normalize(pn[0]-a), vb, e) - grad = copyGradDetails(gradA, gradB, e, scale) - grad = mapEdges(lambda a, pn: normalize(a), grad, e) - return recompose(vb, grad, e, smooth=smooth) - - -def condenseGlyph(glyph, scale=.8, stemWidth=185): - ga, subsegments = segmentGlyph(glyph, 25) - va, e = glyphToMesh(ga) - n = len(va) - - normals = edgeNormals(va,e) - cn = va.dot(np.array([[scale, 0],[0,1]])) - grad = mapEdges(lambda a, pn: normalize(pn[0]-a), cn, e) - # ograd = mapEdges(lambda a,(p,n): normalize(p-a), va, e) - - cn[:,0] -= normals[:,0] * stemWidth * .5 * (1 - scale) - out = recompose(cn, grad, e, smooth=.5) - # out = recompose(out, grad, e, smooth=.1) - out = recompose(out, grad, e, smooth=.01) - - # cornerWeights = mapEdges(lambda a,(p,n): normalize(p-a).dot(normalize(a-n)), grad, e)[:,0].reshape((-1,1)) - # smooth = np.ones((n,1)) * .1 - # smooth[cornerWeights < .6] = 10 - # - # grad2 = quantizeGradient(grad).astype(float) - # grad2 = copyGradDetails(grad, grad2, e, scale=10) - # grad2 = mapEdges(lambda a,e: normalize(a), grad2, e) - # out = recompose(out, grad2, e, smooth=smooth) - out[:,0] += 15 - out[:,1] = va[:,1] - # out = recompose(out, grad, e, smooth=.5) - gOut = meshToGlyph(out, ga) - gOut = fitGlyph(glyph, gOut, subsegments) - for i,seg in enumerate(gOut): - gOut[i].points[0].y = glyph[i].points[0].y - return gOut diff --git a/misc/pylib/fontbuild/markFeature.py b/misc/pylib/fontbuild/markFeature.py deleted file mode 100755 index 914a75a67..000000000 --- a/misc/pylib/fontbuild/markFeature.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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 ufo2ft.kernFeatureWriter import KernFeatureWriter -from ufo2ft.makeotfParts import FeatureOTFCompiler - - -class RobotoFeatureCompiler(FeatureOTFCompiler): - def precompile(self): - self.overwriteFeatures = True - - def setupAnchorPairs(self): - self.anchorPairs = [ - ["top", "_marktop"], - ["bottom", "_markbottom"], - ["top_dd", "_marktop_dd"], - ["bottom_dd", "_markbottom_dd"], - ["rhotichook", "_markrhotichook"], - ["top0315", "_marktop0315"], - ] - - self.mkmkAnchorPairs = [ - ["mkmktop", "_marktop"], - ["mkmkbottom_acc", "_markbottom"], - - # By providing a pair with accent anchor _bottom and no base anchor, - # we designate all glyphs with _bottom as accents (so that they will - # be used as base glyphs for mkmk features) without generating any - # positioning rules actually using this anchor (which is instead - # used to generate composite glyphs). This is all for consistency - # with older roboto versions. - ["", "_bottom"], - ] - - self.ligaAnchorPairs = [] - - -class RobotoKernWriter(KernFeatureWriter): - leftFeaClassRe = r"@_(.+)_L$" - rightFeaClassRe = r"@_(.+)_R$" diff --git a/misc/pylib/fontbuild/mitreGlyph.pyx b/misc/pylib/fontbuild/mitreGlyph.pyx deleted file mode 100644 index d0834ed84..000000000 --- a/misc/pylib/fontbuild/mitreGlyph.pyx +++ /dev/null @@ -1,111 +0,0 @@ -# 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. - - -"""Mitre Glyph: - -mitreSize : Length of the segment created by the mitre. The default is 4. -maxAngle : Maximum angle in radians at which segments will be mitred. The default is .9 (about 50 degrees). - Works for both inside and outside angles - -""" - -import math -from robofab.objects.objectsRF import RPoint, RSegment -from fontbuild.convertCurves import replaceSegments - -def getTangents(contours): - tmap = [] - for c in contours: - clen = len(c) - for i in range(clen): - s = c[i] - p = s.points[-1] - ns = c[(i + 1) % clen] - ps = c[(clen + i - 1) % clen] - np = ns.points[1] if ns.type == 'curve' else ns.points[-1] - pp = s.points[2] if s.type == 'curve' else ps.points[-1] - tmap.append((pp - p, np - p)) - return tmap - -def normalizeVector(p): - m = getMagnitude(p); - if m != 0: - return p*(1/m) - else: - return RPoint(0,0) - -def getMagnitude(p): - return math.sqrt(p.x*p.x + p.y*p.y) - -def getDistance(v1,v2): - return getMagnitude(RPoint(v1.x - v2.x, v1.y - v2.y)) - -def getAngle(v1,v2): - angle = math.atan2(v1.y,v1.x) - math.atan2(v2.y,v2.x) - return (angle + (2*math.pi)) % (2*math.pi) - -def angleDiff(a,b): - return math.pi - abs((abs(a - b) % (math.pi*2)) - math.pi) - -def getAngle2(v1,v2): - return abs(angleDiff(math.atan2(v1.y, v1.x), math.atan2(v2.y, v2.x))) - -def getMitreOffset(n,v1,v2,mitreSize=4,maxAngle=.9): - - # dont mitre if segment is too short - if abs(getMagnitude(v1)) < mitreSize * 2 or abs(getMagnitude(v2)) < mitreSize * 2: - return - angle = getAngle2(v2,v1) - v1 = normalizeVector(v1) - v2 = normalizeVector(v2) - if v1.x == v2.x and v1.y == v2.y: - return - - - # only mitre corners sharper than maxAngle - if angle > maxAngle: - return - - radius = mitreSize / abs(getDistance(v1,v2)) - offset1 = RPoint(round(v1.x * radius), round(v1.y * radius)) - offset2 = RPoint(round(v2.x * radius), round(v2.y * radius)) - return offset1, offset2 - -def mitreGlyph(g,mitreSize,maxAngle): - if g == None: - return - - tangents = getTangents(g.contours) - sid = -1 - for c in g.contours: - segments = [] - needsMitring = False - for s in c: - sid += 1 - v1, v2 = tangents[sid] - off = getMitreOffset(s,v1,v2,mitreSize,maxAngle) - s1 = s.copy() - if off != None: - offset1, offset2 = off - p2 = s.points[-1] + offset2 - s2 = RSegment('line', [(p2.x, p2.y)]) - s1.points[0] += offset1 - segments.append(s1) - segments.append(s2) - needsMitring = True - else: - segments.append(s1) - if needsMitring: - replaceSegments(c, segments) diff --git a/misc/pylib/fontbuild/mix.pyx b/misc/pylib/fontbuild/mix.pyx deleted file mode 100644 index 9be434b6a..000000000 --- a/misc/pylib/fontbuild/mix.pyx +++ /dev/null @@ -1,358 +0,0 @@ -# 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 numpy import array, append -import copy -import json -from robofab.objects.objectsRF import RGlyph -from robofab.world import OpenFont -from decomposeGlyph import decomposeGlyph - - -class Vec2: - def __init__(self, x, y): - self.x = x - self.y = y - - -class FFont: - "Font wrapper for floating point operations" - - def __init__(self,f=None): - self.glyphs = {} - self.hstems = [] - self.vstems = [] - self.kerning = {} - if isinstance(f,FFont): - #self.glyphs = [g.copy() for g in f.glyphs] - for key,g in f.glyphs.iteritems(): - self.glyphs[key] = g.copy() - self.hstems = list(f.hstems) - self.vstems = list(f.vstems) - self.kerning = dict(f.kerning) - elif f != None: - self.copyFromFont(f) - - def copyFromFont(self, f): - for g in f: - self.glyphs[g.name] = FGlyph(g) - self.hstems = [s for s in f.info.postscriptStemSnapH] - self.vstems = [s for s in f.info.postscriptStemSnapV] - self.kerning = f.kerning.asDict() - - - def copyToFont(self, f): - for g in f: - try: - gF = self.glyphs[g.name] - gF.copyToGlyph(g) - except: - print "Copy to glyph failed for" + g.name - f.info.postscriptStemSnapH = self.hstems - f.info.postscriptStemSnapV = self.vstems - for pair in self.kerning: - f.kerning[pair] = self.kerning[pair] - - def getGlyph(self, gname): - try: - return self.glyphs[gname] - except: - return None - - def setGlyph(self, gname, glyph): - self.glyphs[gname] = glyph - - def addDiff(self,b,c): - newFont = FFont(self) - for key,g in newFont.glyphs.iteritems(): - gB = b.getGlyph(key) - gC = c.getGlyph(key) - try: - newFont.glyphs[key] = g.addDiff(gB,gC) - except: - print "Add diff failed for '%s'" %key - return newFont - -class FGlyph: - "provides a temporary floating point compatible glyph data structure" - - def __init__(self, g=None): - self.contours = [] - self.width = 0. - self.components = [] - self.anchors = [] - if g != None: - self.copyFromGlyph(g) - - def copyFromGlyph(self,g): - self.name = g.name - valuesX = [] - valuesY = [] - self.width = len(valuesX) - valuesX.append(g.width) - for c in g.components: - self.components.append((len(valuesX), len(valuesY))) - valuesX.append(c.scale[0]) - valuesY.append(c.scale[1]) - valuesX.append(c.offset[0]) - valuesY.append(c.offset[1]) - - for a in g.anchors: - self.anchors.append((len(valuesX), len(valuesY))) - valuesX.append(a.x) - valuesY.append(a.y) - - for i in range(len(g)): - self.contours.append([]) - for j in range (len(g[i].points)): - self.contours[i].append((len(valuesX), len(valuesY))) - valuesX.append(g[i].points[j].x) - valuesY.append(g[i].points[j].y) - - self.dataX = array(valuesX, dtype=float) - self.dataY = array(valuesY, dtype=float) - - def copyToGlyph(self,g): - g.width = self._derefX(self.width) - if len(g.components) == len(self.components): - for i in range(len(self.components)): - g.components[i].scale = (self._derefX(self.components[i][0] + 0, asInt=False), - self._derefY(self.components[i][1] + 0, asInt=False)) - g.components[i].offset = (self._derefX(self.components[i][0] + 1), - self._derefY(self.components[i][1] + 1)) - if len(g.anchors) == len(self.anchors): - for i in range(len(self.anchors)): - g.anchors[i].x = self._derefX( self.anchors[i][0]) - g.anchors[i].y = self._derefY( self.anchors[i][1]) - for i in range(len(g)) : - for j in range (len(g[i].points)): - g[i].points[j].x = self._derefX(self.contours[i][j][0]) - g[i].points[j].y = self._derefY(self.contours[i][j][1]) - - def isCompatible(self, g): - return (len(self.dataX) == len(g.dataX) and - len(self.dataY) == len(g.dataY) and - len(g.contours) == len(self.contours)) - - def __add__(self,g): - if self.isCompatible(g): - newGlyph = self.copy() - newGlyph.dataX = self.dataX + g.dataX - newGlyph.dataY = self.dataY + g.dataY - return newGlyph - else: - print "Add failed for '%s'" %(self.name) - raise Exception - - def __sub__(self,g): - if self.isCompatible(g): - newGlyph = self.copy() - newGlyph.dataX = self.dataX - g.dataX - newGlyph.dataY = self.dataY - g.dataY - return newGlyph - else: - print "Subtract failed for '%s'" %(self.name) - raise Exception - - def __mul__(self,scalar): - newGlyph = self.copy() - newGlyph.dataX = self.dataX * scalar - newGlyph.dataY = self.dataY * scalar - return newGlyph - - def scaleX(self,scalar): - newGlyph = self.copy() - if len(self.dataX) > 0: - newGlyph.dataX = self.dataX * scalar - for i in range(len(newGlyph.components)): - newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]] - return newGlyph - - def shift(self,ammount): - newGlyph = self.copy() - newGlyph.dataX = self.dataX + ammount - for i in range(len(newGlyph.components)): - newGlyph.dataX[newGlyph.components[i][0]] = self.dataX[newGlyph.components[i][0]] - return newGlyph - - def interp(self, g, v): - gF = self.copy() - if not self.isCompatible(g): - print "Interpolate failed for '%s'; outlines incompatible" %(self.name) - raise Exception - - gF.dataX += (g.dataX - gF.dataX) * v.x - gF.dataY += (g.dataY - gF.dataY) * v.y - return gF - - def copy(self): - ng = FGlyph() - ng.contours = list(self.contours) - ng.width = self.width - ng.components = list(self.components) - ng.anchors = list(self.anchors) - ng.dataX = self.dataX.copy() - ng.dataY = self.dataY.copy() - ng.name = self.name - return ng - - def _derefX(self,id, asInt=True): - val = self.dataX[id] - return int(round(val)) if asInt else val - - def _derefY(self,id, asInt=True): - val = self.dataY[id] - return int(round(val)) if asInt else val - - def addDiff(self,gB,gC): - newGlyph = self + (gB - gC) - return newGlyph - - - -class Master: - - def __init__(self, font=None, v=0, kernlist=None, overlay=None): - if isinstance(font, FFont): - self.font = None - self.ffont = font - elif isinstance(font,str): - self.openFont(font,overlay) - elif isinstance(font,Mix): - self.font = font - else: - self.font = font - self.ffont = FFont(font) - if isinstance(v,float) or isinstance(v,int): - self.v = Vec2(v, v) - else: - self.v = v - if kernlist != None: - kerns = [i.strip().split() for i in open(kernlist).readlines()] - - self.kernlist = [{'left':k[0], 'right':k[1], 'value': k[2]} - for k in kerns - if not k[0].startswith("#") - and not k[0] == ""] - #TODO implement class based kerning / external kerning file - - def openFont(self, path, overlayPath=None): - self.font = OpenFont(path) - for g in self.font: - size = len(g) - csize = len(g.components) - if (size > 0 and csize > 0): - decomposeGlyph(self.font, self.font[g.name]) - - if overlayPath != None: - overlayFont = OpenFont(overlayPath) - font = self.font - for overlayGlyph in overlayFont: - font.insertGlyph(overlayGlyph) - - self.ffont = FFont(self.font) - - -class Mix: - def __init__(self,masters,v): - self.masters = masters - if isinstance(v,float) or isinstance(v,int): - self.v = Vec2(v,v) - else: - self.v = v - - def getFGlyph(self, master, gname): - if isinstance(master.font, Mix): - return master.font.mixGlyphs(gname) - return master.ffont.getGlyph(gname) - - def getGlyphMasters(self,gname): - masters = self.masters - if len(masters) <= 2: - return self.getFGlyph(masters[0], gname), self.getFGlyph(masters[-1], gname) - - def generateFFont(self): - ffont = FFont(self.masters[0].ffont) - for key,g in ffont.glyphs.iteritems(): - ffont.glyphs[key] = self.mixGlyphs(key) - ffont.kerning = self.mixKerns() - return ffont - - def generateFont(self, baseFont, ignoreGlyphs=None): - newFont = baseFont.copy() - #self.mixStems(newFont) todo _ fix stems code - for g in newFont: - if not ignoreGlyphs or g.name not in ignoreGlyphs: - gF = self.mixGlyphs(g.name) - if gF == None: - g.mark = True - elif isinstance(gF, RGlyph): - newFont[g.name] = gF.copy() - else: - gF.copyToGlyph(g) - - newFont.kerning.clear() - newFont.kerning.update(self.mixKerns() or {}) - return newFont - - def mixGlyphs(self,gname): - gA,gB = self.getGlyphMasters(gname) - try: - return gA.interp(gB,self.v) - except: - print "mixglyph failed for %s" %(gname) - if gA != None: - return gA.copy() - - def getKerning(self, master): - if isinstance(master.font, Mix): - return master.font.mixKerns() - return master.ffont.kerning - - def mixKerns(self): - masters = self.masters - kA, kB = self.getKerning(masters[0]), self.getKerning(masters[-1]) - return interpolateKerns(kA, kB, self.v) - - -def narrowFLGlyph(g, gThin, factor=.75): - gF = FGlyph(g) - if not isinstance(gThin,FGlyph): - gThin = FGlyph(gThin) - gCondensed = gThin.scaleX(factor) - try: - gNarrow = gF + (gCondensed - gThin) - gNarrow.copyToGlyph(g) - except: - print "No dice for: " + g.name - -def interpolate(a,b,v,e=0): - if e == 0: - return a+(b-a)*v - qe = (b-a)*v*v*v + a #cubic easing - le = a+(b-a)*v # linear easing - return le + (qe-le) * e - -def interpolateKerns(kA, kB, v): - kerns = {} - for pair, val in kA.items(): - kerns[pair] = interpolate(val, kB.get(pair, 0), v.x) - for pair, val in kB.items(): - lerped_val = interpolate(val, kA.get(pair, 0), 1 - v.x) - if pair in kerns: - assert abs(kerns[pair] - lerped_val) < 1e-6 - else: - kerns[pair] = lerped_val - return kerns diff --git a/misc/pylib/fontbuild/setup.py b/misc/pylib/fontbuild/setup.py deleted file mode 100644 index b39f8e8a1..000000000 --- a/misc/pylib/fontbuild/setup.py +++ /dev/null @@ -1,20 +0,0 @@ -from distutils.core import setup -from distutils.extension import Extension -from Cython.Distutils import build_ext - -ext_modules = [ - Extension("decomposeGlyph", ["decomposeGlyph.pyx"]), - Extension("alignpoints", ["alignpoints.pyx"]), - Extension("Build", ["Build.pyx"]), - Extension("convertCurves", ["convertCurves.pyx"]), - Extension("mitreGlyph", ["mitreGlyph.pyx"]), - Extension("mix", ["mix.pyx"]), - Extension("italics", ["italics.pyx"]), - Extension("curveFitPen", ["curveFitPen.pyx"]), -] - -setup( - name = 'copy', - cmdclass = {'build_ext': build_ext}, - ext_modules = ext_modules -) |