summaryrefslogtreecommitdiff
path: root/misc/pylib/fontbuild
diff options
context:
space:
mode:
Diffstat (limited to 'misc/pylib/fontbuild')
-rw-r--r--misc/pylib/fontbuild/.gitignore1
-rw-r--r--misc/pylib/fontbuild/Build.pyx365
-rw-r--r--misc/pylib/fontbuild/LICENSE201
-rw-r--r--misc/pylib/fontbuild/ORIGIN.txt1
-rw-r--r--misc/pylib/fontbuild/__init__.py6
-rw-r--r--misc/pylib/fontbuild/alignpoints.pyx178
-rw-r--r--misc/pylib/fontbuild/anchors.py77
-rw-r--r--misc/pylib/fontbuild/convertCurves.pyx102
-rw-r--r--misc/pylib/fontbuild/curveFitPen.pyx422
-rw-r--r--misc/pylib/fontbuild/decomposeGlyph.pyx23
-rwxr-xr-xmisc/pylib/fontbuild/features.py189
-rw-r--r--misc/pylib/fontbuild/generateGlyph.py97
-rw-r--r--misc/pylib/fontbuild/instanceNames.py248
-rw-r--r--misc/pylib/fontbuild/italics.pyx308
-rwxr-xr-xmisc/pylib/fontbuild/markFeature.py52
-rw-r--r--misc/pylib/fontbuild/mitreGlyph.pyx111
-rw-r--r--misc/pylib/fontbuild/mix.pyx358
-rw-r--r--misc/pylib/fontbuild/setup.py20
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
-)