summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorRasmus Andersson <rasmus@notion.se>2018-09-03 22:55:49 +0300
committerRasmus Andersson <rasmus@notion.se>2018-09-03 22:55:49 +0300
commitc833e252c925e8dd68108660710ca835d95daa6f (patch)
tree6b2e28264ed45efd7f054e453b622098d0d875b8 /misc
parent8c1a4c181ef12000179dfec541f1af87e9b03122 (diff)
downloadinter-c833e252c925e8dd68108660710ca835d95daa6f.tar.xz
Major overhaul, moving from UFO2 to Glyphs and UFO3, plus a brand new and much simpler fontbuild
Diffstat (limited to 'misc')
-rwxr-xr-xmisc/check-font.py34
-rwxr-xr-xmisc/fontbuild462
-rwxr-xr-xmisc/mac-tmp-disk-mount.sh25
-rwxr-xr-xmisc/mac-tmp-disk-unmount.sh5
-rw-r--r--misc/patches/woff2.patch33
-rw-r--r--misc/pylib/booleanOperations/.gitignore2
-rw-r--r--misc/pylib/booleanOperations/LICENSE20
-rw-r--r--misc/pylib/booleanOperations/__init__.py11
-rw-r--r--misc/pylib/booleanOperations/booleanGlyph.pyx257
-rw-r--r--misc/pylib/booleanOperations/booleanOperationManager.pyx137
-rw-r--r--misc/pylib/booleanOperations/exceptions.py25
-rw-r--r--misc/pylib/booleanOperations/flatten.pyx1248
-rw-r--r--misc/pylib/booleanOperations/requirements.txt3
-rw-r--r--misc/pylib/booleanOperations/setup.py15
-rw-r--r--misc/pylib/booleanOperations/version.py4
-rw-r--r--misc/pylib/copy/.gitignore2
-rw-r--r--misc/pylib/copy/LICENSE.txt254
-rw-r--r--misc/pylib/copy/__init__.py2
-rw-r--r--misc/pylib/copy/copy.pyx433
-rw-r--r--misc/pylib/copy/setup.py13
-rwxr-xr-xmisc/pylib/extractor/License.txt21
-rwxr-xr-xmisc/pylib/extractor/README.rst50
-rwxr-xr-xmisc/pylib/extractor/__init__.py45
-rwxr-xr-xmisc/pylib/extractor/exceptions.py1
-rwxr-xr-xmisc/pylib/extractor/formats/__init__.py0
-rwxr-xr-xmisc/pylib/extractor/formats/opentype.py806
-rwxr-xr-xmisc/pylib/extractor/formats/ttx.py28
-rwxr-xr-xmisc/pylib/extractor/formats/type1.py164
-rwxr-xr-xmisc/pylib/extractor/formats/woff.py222
-rwxr-xr-xmisc/pylib/extractor/requirements.txt2
-rwxr-xr-xmisc/pylib/extractor/tools.py32
-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
-rw-r--r--misc/pylib/robofab/.gitignore2
-rw-r--r--misc/pylib/robofab/LICENSE.txt22
-rwxr-xr-xmisc/pylib/robofab/__init__.py82
-rwxr-xr-xmisc/pylib/robofab/contrib/__init__.py11
-rw-r--r--misc/pylib/robofab/exceptions.py3
-rwxr-xr-xmisc/pylib/robofab/gString.py625
-rwxr-xr-xmisc/pylib/robofab/glifLib.pyx718
-rwxr-xr-xmisc/pylib/robofab/glifLib2.py747
-rwxr-xr-xmisc/pylib/robofab/interface/__init__.py14
-rwxr-xr-xmisc/pylib/robofab/interface/all/__init__.py14
-rwxr-xr-xmisc/pylib/robofab/interface/all/dialogs.py278
-rw-r--r--misc/pylib/robofab/interface/all/dialogs_default.py76
-rw-r--r--misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py73
-rw-r--r--misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py373
-rwxr-xr-xmisc/pylib/robofab/interface/all/dialogs_legacy.py737
-rw-r--r--misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py267
-rwxr-xr-xmisc/pylib/robofab/interface/mac/__init__.py10
-rwxr-xr-xmisc/pylib/robofab/interface/mac/getFileOrFolder.py80
-rwxr-xr-xmisc/pylib/robofab/interface/win/__init__.py10
-rwxr-xr-xmisc/pylib/robofab/misc/__init__.py13
-rw-r--r--misc/pylib/robofab/misc/arrayTools.pyx160
-rw-r--r--misc/pylib/robofab/misc/bezierTools.py416
-rw-r--r--misc/pylib/robofab/misc/speedTestCase.py99
-rw-r--r--misc/pylib/robofab/misc/test.py119
-rwxr-xr-xmisc/pylib/robofab/objects/__init__.py15
-rwxr-xr-xmisc/pylib/robofab/objects/objectsBase.pyx3426
-rw-r--r--misc/pylib/robofab/objects/objectsFF.py1253
-rwxr-xr-xmisc/pylib/robofab/objects/objectsFL.py3112
-rwxr-xr-xmisc/pylib/robofab/objects/objectsRF.pyx1233
-rwxr-xr-xmisc/pylib/robofab/path/__init__.py12
-rw-r--r--misc/pylib/robofab/path/intersect.py108
-rwxr-xr-xmisc/pylib/robofab/pens/__init__.py11
-rw-r--r--misc/pylib/robofab/pens/adapterPens.py293
-rw-r--r--misc/pylib/robofab/pens/angledMarginPen.py132
-rw-r--r--misc/pylib/robofab/pens/boundsPen.pyx95
-rwxr-xr-xmisc/pylib/robofab/pens/digestPen.py106
-rwxr-xr-xmisc/pylib/robofab/pens/filterPen.py407
-rwxr-xr-xmisc/pylib/robofab/pens/flPen.py274
-rw-r--r--misc/pylib/robofab/pens/marginPen.py155
-rwxr-xr-xmisc/pylib/robofab/pens/mathPens.py185
-rw-r--r--misc/pylib/robofab/pens/pointPen.py173
-rw-r--r--misc/pylib/robofab/pens/quartzPen.py21
-rwxr-xr-xmisc/pylib/robofab/pens/reverseContourPointPen.py125
-rwxr-xr-xmisc/pylib/robofab/pens/rfUFOPen.pyx103
-rwxr-xr-xmisc/pylib/robofab/plistFromTree.py43
-rwxr-xr-xmisc/pylib/robofab/plistlib.py495
-rwxr-xr-xmisc/pylib/robofab/setup.py19
-rwxr-xr-xmisc/pylib/robofab/test/__init__.py8
-rw-r--r--misc/pylib/robofab/test/runAll.py27
-rwxr-xr-xmisc/pylib/robofab/test/testSupport.py278
-rw-r--r--misc/pylib/robofab/test/test_RInfoFL.py111
-rw-r--r--misc/pylib/robofab/test/test_RInfoRF.py56
-rw-r--r--misc/pylib/robofab/test/test_dialogs.py218
-rw-r--r--misc/pylib/robofab/test/test_fontLabUFOReadWrite.py565
-rw-r--r--misc/pylib/robofab/test/test_glifLib.py150
-rw-r--r--misc/pylib/robofab/test/test_noneLabUFOReadWrite.py321
-rwxr-xr-xmisc/pylib/robofab/test/test_objectsFL.py54
-rwxr-xr-xmisc/pylib/robofab/test/test_objectsUFO.py203
-rwxr-xr-xmisc/pylib/robofab/test/test_pens.py149
-rw-r--r--misc/pylib/robofab/test/test_psHints.py110
-rw-r--r--misc/pylib/robofab/test/test_ufoLib.py1659
-rwxr-xr-xmisc/pylib/robofab/tools/__init__.py12
-rwxr-xr-xmisc/pylib/robofab/tools/accentBuilder.py348
-rw-r--r--misc/pylib/robofab/tools/fontlabFeatureSplitter.py85
-rwxr-xr-xmisc/pylib/robofab/tools/glifExport.py95
-rwxr-xr-xmisc/pylib/robofab/tools/glifImport.py74
-rw-r--r--misc/pylib/robofab/tools/glyphConstruction.py565
-rwxr-xr-xmisc/pylib/robofab/tools/glyphNameSchemes.py41
-rwxr-xr-xmisc/pylib/robofab/tools/objectDumper.py55
-rwxr-xr-xmisc/pylib/robofab/tools/otFeatures.py190
-rwxr-xr-xmisc/pylib/robofab/tools/proof.py119
-rwxr-xr-xmisc/pylib/robofab/tools/remote.py175
-rwxr-xr-xmisc/pylib/robofab/tools/rfPrefs.py122
-rwxr-xr-xmisc/pylib/robofab/tools/toolsAll.py145
-rwxr-xr-xmisc/pylib/robofab/tools/toolsFL.py339
-rwxr-xr-xmisc/pylib/robofab/tools/toolsRF.py6
-rwxr-xr-xmisc/pylib/robofab/ufoLib.py1084
-rw-r--r--misc/pylib/robofab/world.py108
-rw-r--r--misc/pylib/robofab/xmlTreeBuilder.pyx116
-rwxr-xr-xmisc/tools/cleanup_kerning.py (renamed from misc/cleanup_kerning.py)0
-rwxr-xr-xmisc/tools/download-count.py (renamed from misc/download-count.py)0
-rwxr-xr-xmisc/tools/enrich-glypnames.py (renamed from misc/enrich-glypnames.py)0
-rwxr-xr-xmisc/tools/fixup-diacritics.py (renamed from misc/fixup-diacritics.py)0
-rwxr-xr-xmisc/tools/fixup-features.py (renamed from misc/fixup-features.py)0
-rwxr-xr-xmisc/tools/fixup-kerning.py (renamed from misc/fixup-kerning.py)0
-rwxr-xr-xmisc/tools/fontinfo.py (renamed from misc/fontinfo.py)0
-rwxr-xr-xmisc/tools/gen-glyphinfo.py (renamed from misc/gen-glyphinfo.py)0
-rwxr-xr-xmisc/tools/gen-glyphorder.py (renamed from misc/gen-glyphorder.py)0
-rw-r--r--misc/tools/gen-kern.py (renamed from misc/gen-kern.py)0
-rwxr-xr-xmisc/tools/gen-metrics-and-svgs.py (renamed from misc/gen-metrics-and-svgs.py)0
-rw-r--r--misc/tools/gen-num-pairs.js (renamed from misc/gen-num-pairs.js)0
-rwxr-xr-xmisc/tools/gen-tnum.py (renamed from misc/gen-tnum.py)0
-rwxr-xr-xmisc/tools/glyf-props.py (renamed from misc/glyf-props.py)0
-rwxr-xr-xmisc/tools/glyphcheck.py (renamed from misc/glyphcheck.py)0
-rwxr-xr-xmisc/tools/kernsample.py (renamed from misc/kernsample.py)0
-rw-r--r--misc/tools/restore-diacritics-kerning.py (renamed from misc/restore-diacritics-kerning.py)0
-rwxr-xr-xmisc/tools/rewrite-glyphorder.py (renamed from misc/rewrite-glyphorder.py)0
-rwxr-xr-xmisc/tools/rmglyph.py (renamed from misc/rmglyph.py)0
-rwxr-xr-xmisc/tools/show-changes.py (renamed from misc/show-changes.py)0
-rwxr-xr-xmisc/tools/svgsync.py (renamed from misc/svgsync.py)0
-rwxr-xr-xmisc/tools/svgsync2.py (renamed from misc/svgsync2.py)0
-rwxr-xr-xmisc/tools/ufo-color-glyphs.py (renamed from misc/ufo-color-glyphs.py)0
-rw-r--r--misc/tools/unicode_util.py (renamed from misc/unicode_util.py)0
-rwxr-xr-xmisc/tools/versionize-css.py (renamed from misc/versionize-css.py)0
-rwxr-xr-xmisc/ufocompile253
-rwxr-xr-xmisc/version.py16
155 files changed, 462 insertions, 31255 deletions
diff --git a/misc/check-font.py b/misc/check-font.py
deleted file mode 100755
index e7c432cad..000000000
--- a/misc/check-font.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf8
-from __future__ import print_function
-import os, sys
-from argparse import ArgumentParser
-from multiprocessing import Pool
-import extractor, defcon
-
-
-def check_font(filename):
- print('check %s' % filename)
- ufo = defcon.Font()
- extractor.extractUFO(filename, ufo, doGlyphs=True, doInfo=True, doKerning=True)
-
-
-def main(argv=None):
- opts = ArgumentParser(description='Check')
-
- opts.add_argument(
- 'fontFiles', metavar='<file>', type=str, nargs='+',
- help='Font files (otf, ttf, woff, woff2, pfa, pfb, ttx)')
-
- args = opts.parse_args(argv)
-
- if len(args.fontFiles) == 1:
- check_font(args.fontFiles[0])
- else:
- p = Pool(8)
- p.map(check_font, args.fontFiles)
- p.terminate()
-
-
-if __name__ == '__main__':
- main()
diff --git a/misc/fontbuild b/misc/fontbuild
new file mode 100755
index 000000000..b2dad33c4
--- /dev/null
+++ b/misc/fontbuild
@@ -0,0 +1,462 @@
+#!/usr/bin/env python
+from __future__ import print_function
+
+import sys, os
+# patch PYTHONPATH to include $BASEDIR/build/venv/python/site-packages
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+VENVDIR = os.path.join(BASEDIR, 'build', 'venv')
+sys.path.append(os.path.join(VENVDIR, 'lib', 'python', 'site-packages'))
+
+import argparse
+import datetime
+import glyphsLib
+import logging
+import re
+import signal
+import subprocess
+from fontmake.font_project import FontProject
+from fontTools import designspaceLib
+from glyphsLib.interpolation import apply_instance_data
+from mutatorMath.ufo.document import DesignSpaceDocumentReader
+
+
+BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+
+_gitHash = None
+def getGitHash():
+ global _gitHash
+ if _gitHash is None:
+ _gitHash = ''
+ try:
+ _gitHash = subprocess.check_output(
+ ['git', '-C', BASEDIR, 'rev-parse', '--short', 'HEAD'],
+ shell=False
+ ).strip()
+ except:
+ pass
+ return _gitHash
+
+
+_version = None
+def getVersion():
+ global _version
+ if _version is None:
+ _version = open(os.path.join(BASEDIR, 'version.txt'), 'r').read().strip()
+ return _version
+
+
+subfamily_re = re.compile(r'^\s*([^\s]+)(?:\s*italic|)\s*$', re.I | re.U)
+
+
+def sighandler(signum, frame):
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+ sys.exit(1)
+
+
+def mkdirs(path):
+ if not os.access(path, os.F_OK):
+ os.makedirs(path)
+
+
+# setFontInfo patches font.info
+#
+def setFontInfo(font, weight):
+ #
+ # For UFO3 names, see
+ # https://github.com/unified-font-object/ufo-spec/blob/gh-pages/versions/
+ # ufo3/fontinfo.plist.md
+ # For OpenType NAME table IDs, see
+ # https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids
+ #
+ version = getVersion()
+ buildtag = getGitHash()
+ versionMajor, versionMinor = [int(num) for num in version.split(".")]
+ now = datetime.datetime.utcnow()
+ family = font.info.familyName # i.e. "Inter UI"
+ style = font.info.styleName # e.g. "Medium Italic"
+ isitalic = font.info.italicAngle != 0
+
+ # weight
+ font.info.openTypeOS2WeightClass = weight
+
+ # creation date & time (YYYY/MM/DD HH:MM:SS)
+ font.info.openTypeHeadCreated = now.strftime("%Y/%m/%d %H:%M:%S")
+
+ # version
+ font.info.version = version
+ font.info.versionMajor = versionMajor
+ font.info.versionMinor = versionMinor
+ font.info.year = now.year
+ font.info.openTypeNameVersion = "%s;%s" % (version, buildtag)
+ font.info.openTypeNameUniqueID = "%s %s:%d:%s" % (family, style, now.year, buildtag)
+
+ # Names
+ family_nosp = re.sub(r'\s', '', family)
+ style_nosp = re.sub(r'\s', '', style)
+ font.info.macintoshFONDName = "%s %s" % (family_nosp, style_nosp)
+ font.info.postscriptFontName = "%s-%s" % (family_nosp, style_nosp)
+
+ # name ID 16 "Typographic Family name"
+ font.info.openTypeNamePreferredFamilyName = family
+
+ # name ID 17 "Typographic Subfamily name"
+ subfamily = subfamily_re.sub('\\1', style) # "A Italic" => "A", "A" => "A"
+ if len(subfamily) == 0:
+ subfamily = "Regular"
+ font.info.openTypeNamePreferredSubfamilyName = subfamily
+
+ # Legacy family name (full name except "italic")
+ subfamily_lc = subfamily.lower()
+ if subfamily_lc != "regular" and subfamily_lc != "bold":
+ font.info.styleMapFamilyName = "%s %s" % (family, subfamily)
+ else:
+ font.info.styleMapFamilyName = family
+
+ # Legacy style name. Must be one of these case-sensitive strings:
+ # "regular", "italic", "bold", "bold italic"
+ font.info.styleMapStyleName = "regular"
+ if style.strip().lower().find('bold') != -1:
+ if isitalic:
+ font.info.styleMapStyleName = "bold italic"
+ else:
+ font.info.styleMapStyleName = "bold"
+ elif isitalic:
+ font.info.styleMapStyleName = "italic"
+
+
+class Main(object):
+
+ def __init__(self):
+ self.tmpdir = os.path.join(BASEDIR,'build','tmp')
+
+
+ def main(self, argv):
+ # make ^C instantly exit program
+ signal.signal(signal.SIGINT, sighandler)
+
+ # update environment
+ os.environ['PATH'] = '%s:%s' % (
+ os.path.join(VENVDIR, 'bin'), os.environ['PATH'])
+
+ argparser = argparse.ArgumentParser(
+ description='',
+ usage='''
+ %(prog)s [options] <command> [<args>]
+
+ Commands:
+ compile Build font files
+ glyphsync Generate designspace and UFOs from Glyphs file
+ instancegen Generate instance UFOs for designspace
+ '''.strip().replace('\n ', '\n'))
+
+ argparser.add_argument('-v', '--verbose', action='store_true',
+ help='Print more details')
+
+ argparser.add_argument('command', metavar='<command>')
+
+ args = argparser.parse_args(argv[1:2])
+ if args.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+ else:
+ logging.basicConfig(level=logging.ERROR)
+ cmd = 'cmd_' + args.command.replace('-', '_')
+ if not hasattr(self, cmd):
+ print('Unrecognized command %s. Try --help' % args.command,
+ file=sys.stderr)
+ exit(1)
+ getattr(self, cmd)(argv[2:])
+
+
+
+ def cmd_compile(self, argv):
+ argparser = argparse.ArgumentParser(
+ usage='%(prog)s compile [-h] [-o <file>] <ufo>',
+ description='Compile font files')
+
+ argparser.add_argument('ufo', metavar='<ufo>',
+ help='UFO source file')
+
+ argparser.add_argument('-o', '--output', metavar='<fontfile>',
+ help='Output font file (.otf, .ttf, .ufo or .gx)')
+
+ argparser.add_argument('--validate', action='store_true',
+ help='Enable ufoLib validation on reading/writing UFO files')
+
+ # argparser.add_argument('-f', '--formats', nargs='+', metavar='<format>',
+ # help='Output formats. Any of %(choices)s. Defaults to "otf".',
+ # choices=tuple(all_formats),
+ # default=('otf'))
+
+ args = argparser.parse_args(argv)
+
+ ext_to_format = {
+ '.ufo': 'ufo',
+ '.otf': 'otf',
+ '.ttf': 'ttf',
+ # 'ttf-interpolatable',
+ '.gx': 'variable',
+ }
+
+ formats = []
+ filename = args.output
+ if filename is None or filename == '':
+ ufoname = os.path.basename(args.ufo)
+ name, ext = os.path.splitext(ufoname)
+ filename = name + '.otf'
+ logging.info('setting --output %r' % filename)
+ # for filename in args.output:
+ ext = os.path.splitext(filename)[1]
+ ext_lc = ext.lower()
+ if ext_lc in ext_to_format:
+ formats.append(ext_to_format[ext_lc])
+ else:
+ print('Unsupported output format %s' % ext, file=sys.stderr)
+ exit(1)
+
+ mkdirs(self.tmpdir)
+
+ project = FontProject(
+ timing=None,
+ verbose='WARNING',
+ validate_ufo=args.validate
+ )
+
+ project.run_from_ufos(
+ [args.ufo],
+ output_dir=self.tmpdir,
+ output=formats
+ )
+
+ # run through ots-sanitize
+ # for filename in args.output:
+ tmpfile = os.path.join(self.tmpdir, os.path.basename(filename))
+ mkdirs(os.path.dirname(filename))
+ success = True
+ try:
+ otssan_res = subprocess.check_output(
+ ['ots-sanitize', tmpfile, filename],
+ shell=False
+ ).strip()
+ # Note: ots-sanitize does not exit with an error in many cases where
+ # it fails to sanitize the font.
+ success = otssan_res.find('Failed') == -1
+ except:
+ success = False
+ otssan_res = 'error'
+
+ if success:
+ os.unlink(tmpfile)
+ else:
+ print('ots-sanitize failed for %s: %s' % (
+ tmpfile, otssan_res), file=sys.stderr)
+ exit(1)
+
+
+
+ def cmd_glyphsync(self, argv):
+ argparser = argparse.ArgumentParser(
+ usage='%(prog)s glyphsync <glyphsfile> [options]',
+ description='Generates designspace and UFOs from Glyphs file')
+
+ argparser.add_argument('glyphsfile', metavar='<glyphsfile>',
+ help='Glyphs source file')
+
+ argparser.add_argument('-o', '--outdir', metavar='<dir>',
+ help='''Write output to <dir>. If omitted, designspace and UFOs are
+ written to the directory of the glyphs file.
+ '''.strip().replace('\n ', ''))
+
+ args = argparser.parse_args(argv)
+
+ outdir = args.outdir
+ if outdir is None:
+ outdir = os.path.dirname(args.glyphsfile)
+
+ # files
+ master_dir = outdir
+ glyphsfile = args.glyphsfile
+ designspace_file = os.path.join(outdir, 'Inter-UI.designspace')
+ instance_dir = os.path.join(BASEDIR, 'build', 'ufo')
+
+ # load glyphs project file
+ print("generating %s from %s" % (
+ os.path.relpath(designspace_file, os.getcwd()),
+ os.path.relpath(glyphsfile, os.getcwd())
+ ))
+ font = glyphsLib.GSFont(glyphsfile)
+
+ # generate designspace from glyphs project
+ designspace = glyphsLib.to_designspace(
+ font,
+ propagate_anchors=False,
+ instance_dir=os.path.relpath(instance_dir, master_dir)
+ )
+
+ # strip lib data
+ designspace.lib.clear()
+
+ # fixup axes
+ for axis in designspace.axes:
+ if axis.tag == "wght":
+ axis.map = []
+ axis.minimum = 100
+ axis.maximum = 900
+ axis.default = 400
+
+ # patch and write UFO files
+ # TODO: Only write out-of-date UFOs
+ for source in designspace.sources:
+ # source : fontTools.designspaceLib.SourceDescriptor
+ # source.font : defcon.objects.font.Font
+ ufo_path = os.path.join(master_dir, source.filename.replace('InterUI', 'Inter-UI'))
+ # no need to also set the relative 'filename' attribute as that
+ # will be auto-updated on writing the designspace document
+
+ # name "Inter UI Black" => "black"
+ source.name = source.styleName.lower().replace(' ', '')
+
+ # fixup font info
+ weight = int(source.location['Weight'])
+ setFontInfo(source.font, weight)
+
+ # cleanup lib
+ lib = dict()
+ for key, value in source.font.lib.iteritems():
+ if key.startswith('com.schriftgestaltung'):
+ continue
+ if key == 'public.postscriptNames':
+ continue
+ lib[key] = value
+ source.font.lib.clear()
+ source.font.lib.update(lib)
+
+ # write UFO file
+ source.path = ufo_path
+ print("write %s" % os.path.relpath(ufo_path, os.getcwd()))
+ source.font.save(ufo_path)
+
+ # patch instance names
+ for instance in designspace.instances:
+ # name "Inter UI Black Italic" => "blackitalic"
+ instance.name = instance.styleName.lower().replace(' ', '')
+ instance.filename = instance.filename.replace('InterUI', 'Inter-UI')
+
+ print("write %s" % os.path.relpath(designspace_file, os.getcwd()))
+ designspace.write(designspace_file)
+
+
+
+ def cmd_instancegen(self, argv):
+ argparser = argparse.ArgumentParser(
+ description='Generate UFO instances from designspace')
+
+ argparser.add_argument('designspacefile', metavar='<designspace>',
+ help='Designspace file')
+
+ argparser.add_argument('instances', metavar='<instance-name>', nargs='*',
+ help='Style instances to generate. Omit to generate all.')
+
+ args = argparser.parse_args(argv)
+
+ instances = set([s.lower() for s in args.instances])
+ all_instances = len(instances) == 0
+
+ # files
+ designspace_file = args.designspacefile
+ instance_dir = os.path.join(BASEDIR, 'build', 'ufo')
+
+ # DesignSpaceDocumentReader generates UFOs
+ gen = DesignSpaceDocumentReader(
+ designspace_file,
+ ufoVersion=3,
+ roundGeometry=True,
+ verbose=True
+ )
+
+ designspace = designspaceLib.DesignSpaceDocument()
+ designspace.read(designspace_file)
+
+ # Generate UFOs for instances
+ instance_weight = dict()
+ instance_files = set()
+ for instance in designspace.instances:
+ if all_instances or instance.name in instances:
+ filebase = os.path.basename(instance.filename)
+ relname = os.path.relpath(
+ os.path.join(os.path.dirname(designspace_file), instance.filename),
+ os.getcwd()
+ )
+ print('generating %s' % relname)
+ gen.readInstance(("name", instance.name))
+ instance_files.add(instance.filename)
+ instance_weight[filebase] = int(instance.location['Weight'])
+ if not all_instances:
+ instances.remove(instance.name)
+
+ if len(instances) > 0:
+ print('unknown style(s): %s' % ', '.join(list(instances)), file=sys.stderr)
+ sys.exit(1)
+
+ ufos = apply_instance_data(designspace_file, instance_files)
+
+ # patch ufos (list of defcon.Font instances)
+ italicAngleKey = 'com.schriftgestaltung.customParameter.' +\
+ 'InstanceDescriptorAsGSInstance.italicAngle'
+ for font in ufos:
+ # move italicAngle from lib to info
+ italicAngle = font.lib.get(italicAngleKey)
+ if italicAngle != None:
+ italicAngle = float(italicAngle)
+ del font.lib[italicAngleKey]
+ font.info.italicAngle = italicAngle
+
+ # update font info
+ weight = instance_weight[os.path.basename(font.path)]
+ setFontInfo(font, weight)
+
+ font.save()
+
+
+
+ def checkfont(self, fontfile):
+ try:
+ res = subprocess.check_output(
+ ['ots-idempotent', fontfile],
+ shell=False
+ ).strip()
+ # Note: ots-idempotent does not exit with an error in many cases where
+ # it fails to sanitize the font.
+ if res.find('Failed') != -1:
+ logging.error('[checkfont] ots-idempotent failed for %r: %s' % (
+ fontfile, res))
+ return False
+ except:
+ logging.error('[checkfont] ots-idempotent failed for %r' % fontfile)
+ return False
+ return True
+
+
+ def cmd_checkfont(self, argv):
+ argparser = argparse.ArgumentParser(
+ usage='%(prog)s checkfont <file> ...',
+ description='Verify integrity of font files')
+
+ argparser.add_argument('files', metavar='<file>', nargs='+',
+ help='Font files')
+
+ args = argparser.parse_args(argv)
+
+ for fontfile in args.files:
+ if not self.checkfont(fontfile):
+ sys.exit(1)
+
+ # could use from multiprocessing import Pool
+ # p = Pool(8)
+ # p.map(self.checkfont, args.files)
+ # p.terminate()
+
+
+if __name__ == '__main__':
+ Main().main(sys.argv)
diff --git a/misc/mac-tmp-disk-mount.sh b/misc/mac-tmp-disk-mount.sh
deleted file mode 100755
index f752c54f6..000000000
--- a/misc/mac-tmp-disk-mount.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-set -e
-cd "$(dirname "$0")/.."
-
-# Create if needed
-if [[ ! -f build/tmp.sparseimage ]]; then
- echo "Creating sparse disk image with case-sensitive file system build/tmp.sparseimage"
- mkdir -p build
- hdiutil create build/tmp.sparseimage \
- -size 1g \
- -type SPARSE \
- -fs JHFS+X \
- -volname tmp
-fi
-
-# Mount if needed
-if ! (diskutil info build/tmp >/dev/null); then
- echo "Mounting sparse disk image with case-sensitive file system at build/tmp"
- hdiutil attach build/tmp.sparseimage \
- -readwrite \
- -mountpoint "$(pwd)/build/tmp" \
- -nobrowse \
- -noautoopen \
- -noidmereveal
-fi
diff --git a/misc/mac-tmp-disk-unmount.sh b/misc/mac-tmp-disk-unmount.sh
deleted file mode 100755
index d0dcff925..000000000
--- a/misc/mac-tmp-disk-unmount.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-set -e
-cd "$(dirname "$0")/.."
-
-diskutil unmount build/tmp
diff --git a/misc/patches/woff2.patch b/misc/patches/woff2.patch
deleted file mode 100644
index cd4a4c3cc..000000000
--- a/misc/patches/woff2.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 25319cecf58faf9d92f489ad52323fe4762b8087 Mon Sep 17 00:00:00 2001
-From: Rasmus Andersson <rasmus@notion.se>
-Date: Mon, 21 Aug 2017 07:53:23 -0700
-Subject: [PATCH] Exclude invalid ar flag in makefile on macOS
-
----
- Makefile | 4 +++-
- 1 file changed, 3 insertions(+), 1 deletion(-)
-
-diff --git a/Makefile b/Makefile
-index 4b3bd7b..5bf878f 100644
---- a/Makefile
-+++ b/Makefile
-@@ -11,13 +11,15 @@ CANONICAL_PREFIXES ?= -no-canonical-prefixes
- NOISY_LOGGING ?= -DFONT_COMPRESSION_BIN
- COMMON_FLAGS = -fno-omit-frame-pointer $(CANONICAL_PREFIXES) $(NOISY_LOGGING) -D __STDC_FORMAT_MACROS
-
-+ARFLAGS = cr
-+
- ifeq ($(OS), Darwin)
- CPPFLAGS += -DOS_MACOSX
- else
- COMMON_FLAGS += -fno-tree-vrp
-+ ARFLAGS += f
- endif
-
--ARFLAGS = crf
- CFLAGS += $(COMMON_FLAGS)
- CXXFLAGS += $(COMMON_FLAGS) -std=c++11
-
---
-2.11.0
-
diff --git a/misc/pylib/booleanOperations/.gitignore b/misc/pylib/booleanOperations/.gitignore
deleted file mode 100644
index e35ddbd94..000000000
--- a/misc/pylib/booleanOperations/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.c
-build
diff --git a/misc/pylib/booleanOperations/LICENSE b/misc/pylib/booleanOperations/LICENSE
deleted file mode 100644
index 90104d98c..000000000
--- a/misc/pylib/booleanOperations/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2013 Frederik Berlaen
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/misc/pylib/booleanOperations/__init__.py b/misc/pylib/booleanOperations/__init__.py
deleted file mode 100644
index 55a9ff571..000000000
--- a/misc/pylib/booleanOperations/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from __future__ import print_function, division, absolute_import
-from .booleanOperationManager import BooleanOperationManager
-from .exceptions import BooleanOperationsError
-from .version import __version__
-
-# export BooleanOperationManager static methods
-union = BooleanOperationManager.union
-difference = BooleanOperationManager.difference
-intersection = BooleanOperationManager.intersection
-xor = BooleanOperationManager.xor
-getIntersections = BooleanOperationManager.getIntersections
diff --git a/misc/pylib/booleanOperations/booleanGlyph.pyx b/misc/pylib/booleanOperations/booleanGlyph.pyx
deleted file mode 100644
index af3979f05..000000000
--- a/misc/pylib/booleanOperations/booleanGlyph.pyx
+++ /dev/null
@@ -1,257 +0,0 @@
-from __future__ import print_function, division, absolute_import
-import weakref
-from copy import deepcopy
-
-try:
- from robofab.pens.pointPen import AbstractPointPen
- from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen
- from robofab.pens.boundsPen import BoundsPen
-except:
- from ufoLib.pointPen import (
- AbstractPointPen, PointToSegmentPen, SegmentToPointPen)
- from fontTools.pens.boundsPen import BoundsPen
-
-from fontTools.pens.areaPen import AreaPen
-from .booleanOperationManager import BooleanOperationManager
-
-manager = BooleanOperationManager()
-
-
-class BooleanGlyphDataPointPen(AbstractPointPen):
-
- def __init__(self, glyph):
- self._glyph = glyph
- self._points = []
- self.copyContourData = True
-
- def _flushContour(self):
- points = self._points
- if len(points) == 1 and points[0][0] == "move":
- # it's an anchor
- segmentType, pt, smooth, name = points[0]
- self._glyph.anchors.append((pt, name))
- elif self.copyContourData:
- # ignore double points on start and end
- firstPoint = points[0]
- if firstPoint[0] == "move":
- # remove trailing off curves in an open path
- while points[-1][0] is None:
- points.pop()
- lastPoint = points[-1]
- if firstPoint[0] is not None and lastPoint[0] is not None:
- if firstPoint[1] == lastPoint[1]:
- if firstPoint[0] in ("line", "move"):
- del points[0]
- else:
- raise AssertionError("Unhandled point type sequence")
- elif firstPoint[0] == "move":
- # auto close the path
- _, pt, smooth, name = firstPoint
- points[0] = "line", pt, smooth, name
-
- contour = self._glyph.contourClass()
- contour._points = points
- self._glyph.contours.append(contour)
-
- def beginPath(self):
- self._points = []
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self._points.append((segmentType, pt, smooth, name))
-
- def endPath(self):
- self._flushContour()
-
- def addComponent(self, baseGlyphName, transformation):
- self._glyph.components.append((baseGlyphName, transformation))
-
-
-class BooleanContour(object):
-
- """
- Contour like object.
- """
-
- def __init__(self):
- self._points = []
- self._clockwise = None
- self._bounds = None
-
- def __len__(self):
- return len(self._points)
-
- # shallow contour API
-
- def draw(self, pen):
- pointPen = PointToSegmentPen(pen)
- self.drawPoints(pointPen)
-
- def drawPoints(self, pointPen):
- pointPen.beginPath()
- for segmentType, pt, smooth, name in self._points:
- pointPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name)
- pointPen.endPath()
-
- def _get_clockwise(self):
- if self._clockwise is None:
- pen = AreaPen()
- pen.endPath = pen.closePath
- self.draw(pen)
- self._clockwise = pen.value < 0
- return self._clockwise
-
- clockwise = property(_get_clockwise)
-
- def _get_bounds(self):
- if self._bounds is None:
- pen = BoundsPen(None)
- self.draw(pen)
- self._bounds = pen.bounds
- return self._bounds
-
- bounds = property(_get_bounds)
-
-
-class BooleanGlyph(object):
-
- """
- Glyph like object handling boolean operations.
-
- union:
- result = BooleanGlyph(glyph).union(BooleanGlyph(glyph2))
- result = BooleanGlyph(glyph) | BooleanGlyph(glyph2)
-
- difference:
- result = BooleanGlyph(glyph).difference(BooleanGlyph(glyph2))
- result = BooleanGlyph(glyph) % BooleanGlyph(glyph2)
-
- intersection:
- result = BooleanGlyph(glyph).intersection(BooleanGlyph(glyph2))
- result = BooleanGlyph(glyph) & BooleanGlyph(glyph2)
-
- xor:
- result = BooleanGlyph(glyph).xor(BooleanGlyph(glyph2))
- result = BooleanGlyph(glyph) ^ BooleanGlyph(glyph2)
-
- """
-
- contourClass = BooleanContour
-
- def __init__(self, glyph=None, copyContourData=True):
- self.contours = []
- self.components = []
- self.anchors = []
-
- self.name = None
- self.unicodes = None
- self.width = None
- self.lib = {}
- self.note = None
-
- if glyph:
- pen = self.getPointPen()
- pen.copyContourData = copyContourData
- glyph.drawPoints(pen)
-
- self.name = glyph.name
- self.unicodes = glyph.unicodes
- self.width = glyph.width
- self.lib = deepcopy(glyph.lib)
- self.note = glyph.note
-
- if not isinstance(glyph, self.__class__):
- self.getSourceGlyph = weakref.ref(glyph)
-
- def __repr__(self):
- return "<BooleanGlyph %s>" % self.name
-
- def __len__(self):
- return len(self.contours)
-
- def __getitem__(self, index):
- return self.contours[index]
-
- def getSourceGlyph(self):
- return None
-
- def getParent(self):
- source = self.getSourceGlyph()
- if source:
- return source.getParent()
- return None
-
- # shalllow glyph API
-
- def draw(self, pen):
- pointPen = PointToSegmentPen(pen)
- self.drawPoints(pointPen)
-
- def drawPoints(self, pointPen):
- for contour in self.contours:
- contour.drawPoints(pointPen)
- for baseName, transformation in self.components:
- pointPen.addComponent(baseName, transformation)
- for pt, name in self.anchors:
- pointPen.beginPath()
- pointPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name)
- pointPen.endPath()
-
- def getPen(self):
- return SegmentToPointPen(self.getPointPen())
-
- def getPointPen(self):
- return BooleanGlyphDataPointPen(self)
-
- # boolean operations
-
- def _booleanMath(self, operation, other):
- if not isinstance(other, self.__class__):
- other = self.__class__(other)
- destination = self.__class__(self, copyContourData=False)
- func = getattr(manager, operation)
-
- if operation == "union":
- contours = self.contours
- if other is not None:
- contours += other.contours
- func(contours, destination.getPointPen())
- else:
- subjectContours = self.contours
- clipContours = other.contours
- func(subjectContours, clipContours, destination.getPointPen())
- return destination
-
- def __or__(self, other):
- return self.union(other)
-
- __ror__ = __ior__ = __or__
-
- def __mod__(self, other):
- return self.difference(other)
-
- __rmod__ = __imod__ = __mod__
-
- def __and__(self, other):
- return self.intersection(other)
-
- __rand__ = __iand__ = __and__
-
- def __xor__(self, other):
- return self.xor(other)
-
- __rxor__ = __ixor__ = __xor__
-
- def union(self, other):
- return self._booleanMath("union", other)
-
- def difference(self, other):
- return self._booleanMath("difference", other)
-
- def intersection(self, other):
- return self._booleanMath("intersection", other)
-
- def xor(self, other):
- return self._booleanMath("xor", other)
-
- def removeOverlap(self):
- return self._booleanMath("union", None)
diff --git a/misc/pylib/booleanOperations/booleanOperationManager.pyx b/misc/pylib/booleanOperations/booleanOperationManager.pyx
deleted file mode 100644
index 15e2def1e..000000000
--- a/misc/pylib/booleanOperations/booleanOperationManager.pyx
+++ /dev/null
@@ -1,137 +0,0 @@
-from __future__ import print_function, division, absolute_import
-from .flatten import InputContour, OutputContour
-from .exceptions import (
- InvalidSubjectContourError, InvalidClippingContourError, ExecutionError)
-import pyclipper
-
-
-"""
-General Suggestions:
-- Contours should only be sent here if they actually overlap.
- This can be checked easily using contour bounds.
-- Only perform operations on closed contours.
-- contours must have an on curve point
-- some kind of a log
-"""
-
-
-_operationMap = {
- "union": pyclipper.CT_UNION,
- "intersection": pyclipper.CT_INTERSECTION,
- "difference": pyclipper.CT_DIFFERENCE,
- "xor": pyclipper.CT_XOR,
-}
-
-_fillTypeMap = {
- "evenOdd": pyclipper.PFT_EVENODD,
- "nonZero": pyclipper.PFT_NONZERO,
- # we keep the misspelling for compatibility with earlier versions
- "noneZero": pyclipper.PFT_NONZERO,
-}
-
-
-def clipExecute(subjectContours, clipContours, operation, subjectFillType="nonZero",
- clipFillType="nonZero"):
- pc = pyclipper.Pyclipper()
-
- for i, subjectContour in enumerate(subjectContours):
- # ignore paths with no area
- if pyclipper.Area(subjectContour):
- try:
- pc.AddPath(subjectContour, pyclipper.PT_SUBJECT)
- except pyclipper.ClipperException:
- raise InvalidSubjectContourError("contour %d is invalid for clipping" % i)
- for j, clipContour in enumerate(clipContours):
- # ignore paths with no area
- if pyclipper.Area(clipContour):
- try:
- pc.AddPath(clipContour, pyclipper.PT_CLIP)
- except pyclipper.ClipperException:
- raise InvalidClippingContourError("contour %d is invalid for clipping" % j)
-
- try:
- solution = pc.Execute(_operationMap[operation],
- _fillTypeMap[subjectFillType],
- _fillTypeMap[clipFillType])
- except pyclipper.ClipperException as exc:
- raise ExecutionError(exc)
-
- return [[tuple(p) for p in path] for path in solution]
-
-
-def _performOperation(operation, subjectContours, clipContours, outPen):
- # prep the contours
- subjectInputContours = [InputContour(contour) for contour in subjectContours if contour and len(contour) > 1]
- clipInputContours = [InputContour(contour) for contour in clipContours if contour and len(contour) > 1]
- inputContours = subjectInputContours + clipInputContours
-
- resultContours = clipExecute([subjectInputContour.originalFlat for subjectInputContour in subjectInputContours],
- [clipInputContour.originalFlat for clipInputContour in clipInputContours],
- operation, subjectFillType="nonZero", clipFillType="nonZero")
- # convert to output contours
- outputContours = [OutputContour(contour) for contour in resultContours]
- # re-curve entire contour
- for inputContour in inputContours:
- for outputContour in outputContours:
- if outputContour.final:
- continue
- if outputContour.reCurveFromEntireInputContour(inputContour):
- # the input is expired if a match was made,
- # so stop passing it to the outputs
- break
- # re-curve segments
- for inputContour in inputContours:
- # skip contours that were comppletely used in the previous step
- if inputContour.used:
- continue
- # XXX this could be expensive if an input becomes completely used
- # it doesn't stop from being passed to the output
- for outputContour in outputContours:
- outputContour.reCurveFromInputContourSegments(inputContour)
- # curve fit
- for outputContour in outputContours:
- outputContour.reCurveSubSegments(inputContours)
- # output the results
- for outputContour in outputContours:
- outputContour.drawPoints(outPen)
- return outputContours
-
-
-class BooleanOperationManager(object):
-
- @staticmethod
- def union(contours, outPen):
- return _performOperation("union", contours, [], outPen)
-
- @staticmethod
- def difference(subjectContours, clipContours, outPen):
- return _performOperation("difference", subjectContours, clipContours, outPen)
-
- @staticmethod
- def intersection(subjectContours, clipContours, outPen):
- return _performOperation("intersection", subjectContours, clipContours, outPen)
-
- @staticmethod
- def xor(subjectContours, clipContours, outPen):
- return _performOperation("xor", subjectContours, clipContours, outPen)
-
- @staticmethod
- def getIntersections(contours):
- from .flatten import _scalePoints, inverseClipperScale
- # prep the contours
- inputContours = [InputContour(contour) for contour in contours if contour and len(contour) > 1]
-
- inputFlatPoints = set()
- for contour in inputContours:
- inputFlatPoints.update(contour.originalFlat)
-
- resultContours = clipExecute(
- [inputContour.originalFlat for inputContour in inputContours], [],
- "union", subjectFillType="nonZero", clipFillType="nonZero")
-
- resultFlatPoints = set()
- for contour in resultContours:
- resultFlatPoints.update(contour)
-
- intersections = resultFlatPoints - inputFlatPoints
- return _scalePoints(intersections, inverseClipperScale)
diff --git a/misc/pylib/booleanOperations/exceptions.py b/misc/pylib/booleanOperations/exceptions.py
deleted file mode 100644
index 38b72e0b2..000000000
--- a/misc/pylib/booleanOperations/exceptions.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import print_function, division, absolute_import
-
-
-class BooleanOperationsError(Exception):
- """Base BooleanOperations exception"""
-
-
-class InvalidContourError(BooleanOperationsError):
- """Rased when any input contour is invalid"""
-
-
-class InvalidSubjectContourError(InvalidContourError):
- """Rased when a 'subject' contour is not valid"""
-
-
-class InvalidClippingContourError(InvalidContourError):
- """Rased when a 'clipping' contour is not valid"""
-
-
-class ExecutionError(BooleanOperationsError):
- """Raised when clipping execution fails"""
-
-
-class OpenContourError(BooleanOperationsError):
- """Raised when any input contour is open"""
diff --git a/misc/pylib/booleanOperations/flatten.pyx b/misc/pylib/booleanOperations/flatten.pyx
deleted file mode 100644
index 606b2005a..000000000
--- a/misc/pylib/booleanOperations/flatten.pyx
+++ /dev/null
@@ -1,1248 +0,0 @@
-from __future__ import print_function, division, absolute_import
-import math
-from fontTools.misc import bezierTools
-from fontTools.pens.basePen import decomposeQuadraticSegment
-import pyclipper
-from .exceptions import OpenContourError
-
-"""
-To Do:
-- the stuff listed below
-- need to know what kind of curves should be used for
- curve fit--curve or qcurve
-- false curves and duplicate points need to be filtered early on
-
-notes:
-- the flattened segments *must* be cyclical.
- if they aren't, matching is almost impossible.
-
-
-optimization ideas:
-- the flattening of the output segment in the full contour
- matching is probably expensive.
-- there should be a way to flag an input contour as
- entirely used so that it isn't tried and tried and
- tried for segment matches.
-- do a faster test when matching segments: when a end
- match is found, jump back input length and grab the
- output segment. test for match with the input.
-- cache input contour objects. matching these to incoming
- will be a little difficult because of point names and
- identifiers. alternatively, deal with those after the fact.
-- some tests on input before conversion to input objects
- could yield significant speedups. would need to check
- each contour for self intersection and each
- non-self-intersectingcontour for collision with other
- contours. and contours that don't have a hit could be
- skipped. this cound be done roughly with bounds.
- this should probably be done by extenal callers.
-- set a proper starting points of the output segments based on known points
- known points are:
- input oncurve points
- if nothing found intersection points (only use this is in the final curve fitting stage)
-
-test cases:
-- untouched contour: make clockwise and counter-clockwise tests
- of the same contour
-"""
-epsilon = 1e-8
-
-# factors for transferring coordinates to and from Clipper
-clipperScale = 1 << 17
-inverseClipperScale = 1.0 / clipperScale
-
-# approximateSegmentLength setting
-_approximateSegmentLength = 5.3
-
-
-# -------------
-# Input Objects
-# -------------
-
-# Input
-
-class InputContour(object):
-
- def __init__(self, contour):
- # gather the point data
- pointPen = ContourPointDataPen()
- contour.drawPoints(pointPen)
- points = pointPen.getData()
- reversedPoints = _reversePoints(points)
- # gather segments
- self.segments = _convertPointsToSegments(points)
- # only calculate once all the flat points.
- # it seems to have some tiny difference and its a lot faster
- # if the flat points are calculated from the reversed input points.
- self.reversedSegments = _convertPointsToSegments(reversedPoints, willBeReversed=True)
- # simple reverse the flat points and store them in the reversedSegments
- index = 0
- for segment in self.segments:
- otherSegment = self.reversedSegments[index]
- otherSegment.flat = segment.getReversedFlatPoints()
- index -= 1
- # get the direction; returns True if counter-clockwise, False otherwise
- self.clockwise = not pyclipper.Orientation(points)
- # store the gathered data
- if self.clockwise:
- self.clockwiseSegments = self.segments
- self.counterClockwiseSegments = self.reversedSegments
- else:
- self.clockwiseSegments = self.reversedSegments
- self.counterClockwiseSegments = self.segments
- # flag indicating if the contour has been used
- self.used = False
-
- # ----------
- # Attributes
- # ----------
-
- # the original direction in flat segments
-
- def _get_originalFlat(self):
- if self.clockwise:
- return self.clockwiseFlat
- else:
- return self.counterClockwiseFlat
-
- originalFlat = property(_get_originalFlat)
-
- # the clockwise direction in flat segments
-
- def _get_clockwiseFlat(self):
- flat = []
- segments = self.clockwiseSegments
- for segment in segments:
- flat.extend(segment.flat)
- return flat
-
- clockwiseFlat = property(_get_clockwiseFlat)
-
- # the counter-clockwise direction in flat segments
-
- def _get_counterClockwiseFlat(self):
- flat = []
- segments = self.counterClockwiseSegments
- for segment in segments:
- flat.extend(segment.flat)
- return flat
-
- counterClockwiseFlat = property(_get_counterClockwiseFlat)
-
- def hasOnCurve(self):
- for inputSegment in self.segments:
- if not inputSegment.used and inputSegment.segmentType != "line":
- return True
- return False
-
-
-class InputSegment(object):
-
- # __slots__ = ["points", "previousOnCurve", "scaledPreviousOnCurve", "flat", "used"]
-
- def __init__(self, points=None, previousOnCurve=None, willBeReversed=False):
- if points is None:
- points = []
- self.points = points
- self.previousOnCurve = previousOnCurve
- self.scaledPreviousOnCurve = _scaleSinglePoint(previousOnCurve, scale=clipperScale)
- self.used = False
- self.flat = []
- # if the bcps are equal to the oncurves convert the segment to a line segment.
- # otherwise this causes an error when flattening.
- if self.segmentType == "curve":
- if previousOnCurve == points[0].coordinates and points[1].coordinates == points[-1].coordinates:
- oncurve = points[-1]
- oncurve.segmentType = "line"
- self.points = points = [oncurve]
- elif previousOnCurve[0] == points[0].coordinates[0] == points[1].coordinates[0] == points[-1].coordinates[0]:
- oncurve = points[-1]
- oncurve.segmentType = "line"
- self.points = points = [oncurve]
- elif previousOnCurve[1] == points[0].coordinates[1] == points[1].coordinates[1] == points[-1].coordinates[1]:
- oncurve = points[-1]
- oncurve.segmentType = "line"
- self.points = points = [oncurve]
- # its a reversed segment the flat points will be set later on in the InputContour
- if willBeReversed:
- return
- pointsToFlatten = []
- if self.segmentType == "qcurve":
- assert len(points) >= 0
- flat = []
- currentOnCurve = previousOnCurve
- pointCoordinates = [point.coordinates for point in points]
- for pt1, pt2 in decomposeQuadraticSegment(pointCoordinates[1:]):
- pt0x, pt0y = currentOnCurve
- pt1x, pt1y = pt1
- pt2x, pt2y = pt2
- mid1x = pt0x + 0.66666666666666667 * (pt1x - pt0x)
- mid1y = pt0y + 0.66666666666666667 * (pt1y - pt0y)
- mid2x = pt2x + 0.66666666666666667 * (pt1x - pt2x)
- mid2y = pt2y + 0.66666666666666667 * (pt1y - pt2y)
-
- convertedQuadPointToFlatten = [currentOnCurve, (mid1x, mid1y), (mid2x, mid2y), pt2]
- flat.extend(_flattenSegment(convertedQuadPointToFlatten))
- currentOnCurve = pt2
- self.flat = flat
- # this shoudl be easy.
- # copy the quad to cubic from fontTools.pens.basePen
- elif self.segmentType == "curve":
- pointsToFlatten = [previousOnCurve] + [point.coordinates for point in points]
- else:
- assert len(points) == 1
- self.flat = [point.coordinates for point in points]
- if pointsToFlatten:
- self.flat = _flattenSegment(pointsToFlatten)
- # if len(self.flat) == 1 and self.segmentType == "curve":
- # oncurve = self.points[-1]
- # oncurve.segmentType = "line"
- # self.points = [oncurve]
- self.flat = _scalePoints(self.flat, scale=clipperScale)
- self.flat = _checkFlatPoints(self.flat)
- self.used = False
-
- def _get_segmentType(self):
- return self.points[-1].segmentType
-
- segmentType = property(_get_segmentType)
-
- def getReversedFlatPoints(self):
- reversedFlatPoints = [self.scaledPreviousOnCurve] + self.flat[:-1]
- reversedFlatPoints.reverse()
- return reversedFlatPoints
-
- def split(self, tValues):
- """
- Split the segment according the t values
- """
- if self.segmentType == "curve":
- on1 = self.previousOnCurve
- off1 = self.points[0].coordinates
- off2 = self.points[1].coordinates
- on2 = self.points[2].coordinates
- return bezierTools.splitCubicAtT(on1, off1, off2, on2, *tValues)
- elif self.segmentType == "line":
- segments = []
- x1, y1 = self.previousOnCurve
- x2, y2 = self.points[0].coordinates
- dx = x2 - x1
- dy = y2 - y1
- pp = x1, y1
- for t in tValues:
- np = (x1+dx*t, y1+dy*t)
- segments.append([pp, np])
- pp = np
- segments.append([pp, (x2, y2)])
- return segments
- elif self.segmentType == "qcurve":
- raise NotImplementedError
- else:
- raise NotImplementedError
-
- def tValueForPoint(self, point):
- """
- get a t values for a given point
-
- required:
- the point must be a point on the curve.
- in an overlap cause the point will be an intersection points wich is alwasy a point on the curve
- """
- if self.segmentType == "curve":
- on1 = self.previousOnCurve
- off1 = self.points[0].coordinates
- off2 = self.points[1].coordinates
- on2 = self.points[2].coordinates
- return _tValueForPointOnCubicCurve(point, (on1, off1, off2, on2))
- elif self.segmentType == "line":
- return _tValueForPointOnLine(point, (self.previousOnCurve, self.points[0].coordinates))
- elif self.segmentType == "qcurve":
- raise NotImplementedError
- else:
- raise NotImplementedError
-
-
-class InputPoint(object):
-
- __slots__ = ["coordinates", "segmentType", "smooth", "name", "kwargs"]
-
- def __init__(self, coordinates, segmentType=None, smooth=False, name=None, kwargs=None):
- x, y = coordinates
- self.coordinates = coordinates
- self.segmentType = segmentType
- self.smooth = smooth
- self.name = name
- self.kwargs = kwargs
-
- def __getitem__(self, i):
- return self.coordinates[i]
-
- def copy(self):
- copy = self.__class__(
- coordinates=self.coordinates,
- segmentType=self.segmentType,
- smooth=self.smooth,
- name=self.name,
- kwargs=self.kwargs
- )
- return copy
-
- def __str__(self):
- return "%s %s" % (self.segmentType, self.coordinates)
-
- def __repr__(self):
- return self.__str__()
-
-
-# -------------
-# Input Support
-# -------------
-
-class ContourPointDataPen:
-
- """
- Point pen for gathering raw contour data.
- An instance of this pen may only be used for one contour.
- """
-
- def __init__(self):
- self._points = None
- self._foundStartingPoint = False
-
- def getData(self):
- """
- Return a list of normalized InputPoint objects
- for the contour drawn with this pen.
- """
- # organize the points into segments
- # 1. make sure there is an on curve
- haveOnCurve = False
- for point in self._points:
- if point.segmentType is not None:
- haveOnCurve = True
- break
- # 2. move the off curves to front of the list
- if haveOnCurve:
- _prepPointsForSegments(self._points)
- # 3. ignore double points on start and end
- firstPoint = self._points[0]
- lastPoint = self._points[-1]
- if firstPoint.segmentType is not None and lastPoint.segmentType is not None:
- if firstPoint.coordinates == lastPoint.coordinates:
- if (firstPoint.segmentType in ["line", "move"]):
- del self._points[0]
- else:
- raise AssertionError("Unhandled point type sequence")
- # done
- return self._points
-
- def beginPath(self):
- assert self._points is None
- self._points = []
-
- def endPath(self):
- pass
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- if segmentType == "move":
- raise OpenContourError("Unhandled open contour")
- if not self._foundStartingPoint and segmentType is not None:
- kwargs['startingPoint'] = self._foundStartingPoint = True
- data = InputPoint(
- coordinates=pt,
- segmentType=segmentType,
- smooth=smooth,
- name=name,
- kwargs=kwargs
- )
- self._points.append(data)
-
- def addComponent(self, baseGlyphName, transformation):
- raise NotImplementedError
-
-
-def _prepPointsForSegments(points):
- """
- Move any off curves at the end of the contour
- to the beginning of the contour. This makes
- segmentation easier.
- """
- while 1:
- point = points[-1]
- if point.segmentType:
- break
- else:
- point = points.pop()
- points.insert(0, point)
- continue
-
-
-def _copyPoints(points):
- """
- Make a shallow copy of the points.
- """
- copied = [point.copy() for point in points]
- return copied
-
-
-def _reversePoints(points):
- """
- Reverse the points. This differs from the
- reversal point pen in RoboFab in that it doesn't
- worry about maintaing the start point position.
- That has no benefit within the context of this module.
- """
- # copy the points
- points = _copyPoints(points)
- # find the first on curve type and recycle
- # it for the last on curve type
- firstOnCurve = None
- for index, point in enumerate(points):
- if point.segmentType is not None:
- firstOnCurve = index
- break
- lastSegmentType = points[firstOnCurve].segmentType
- # reverse the points
- points = reversed(points)
- # work through the reversed remaining points
- final = []
- for point in points:
- segmentType = point.segmentType
- if segmentType is not None:
- point.segmentType = lastSegmentType
- lastSegmentType = segmentType
- final.append(point)
- # move any offcurves at the end of the points
- # to the start of the points
- _prepPointsForSegments(final)
- # done
- return final
-
-
-def _convertPointsToSegments(points, willBeReversed=False):
- """
- Compile points into InputSegment objects.
- """
- # get the last on curve
- previousOnCurve = None
- for point in reversed(points):
- if point.segmentType is not None:
- previousOnCurve = point.coordinates
- break
- assert previousOnCurve is not None
- # gather the segments
- offCurves = []
- segments = []
- for point in points:
- # off curve, hold.
- if point.segmentType is None:
- offCurves.append(point)
- else:
- segment = InputSegment(
- points=offCurves + [point],
- previousOnCurve=previousOnCurve,
- willBeReversed=willBeReversed
- )
- segments.append(segment)
- offCurves = []
- previousOnCurve = point.coordinates
- assert not offCurves
- return segments
-
-
-# --------------
-# Output Objects
-# --------------
-
-class OutputContour(object):
-
- def __init__(self, pointList):
- if pointList[0] == pointList[-1]:
- del pointList[-1]
- self.clockwise = not pyclipper.Orientation(pointList)
- self.segments = [
- OutputSegment(
- segmentType="flat",
- points=[point]
- ) for point in pointList
- ]
-
- def _scalePoint(self, point):
- x, y = point
- x = x * inverseClipperScale
- if int(x) == x:
- x = int(x)
- y = y * inverseClipperScale
- if int(y) == y:
- y = int(y)
- return x, y
-
- # ----------
- # Attributes
- # ----------
-
- def _get_final(self):
- # XXX this could be optimized:
- # store a fixed value after teh contour is finalized
- # don't do the dymanic searching if that flag is set to True
- for segment in self.segments:
- if not segment.final:
- return False
- return True
-
- final = property(_get_final)
-
- # --------------------------
- # Re-Curve and Curve Fitting
- # --------------------------
-
- def reCurveFromEntireInputContour(self, inputContour):
- """
- Match if entire input contour matches entire output contour,
- allowing for different start point.
- """
- if self.clockwise:
- inputFlat = inputContour.clockwiseFlat
- else:
- inputFlat = inputContour.counterClockwiseFlat
- outputFlat = []
- for segment in self.segments:
- # XXX this could be expensive
- assert segment.segmentType == "flat"
- outputFlat += segment.points
- # test lengths
- haveMatch = False
- if len(inputFlat) == len(outputFlat):
- if inputFlat == outputFlat:
- haveMatch = True
- else:
- inputStart = inputFlat[0]
- if inputStart in outputFlat:
- # there should be only one occurance of the point
- # but handle it just in case
- if outputFlat.count(inputStart) > 1:
- startIndexes = [index for index, point in enumerate(outputFlat) if point == inputStart]
- else:
- startIndexes = [outputFlat.index(inputStart)]
- # slice and dice to test possible orders
- for startIndex in startIndexes:
- test = outputFlat[startIndex:] + outputFlat[:startIndex]
- if inputFlat == test:
- haveMatch = True
- break
- if haveMatch:
- # clear out the flat points
- self.segments = []
- # replace with the appropriate points from the input
- if self.clockwise:
- inputSegments = inputContour.clockwiseSegments
- else:
- inputSegments = inputContour.counterClockwiseSegments
- for inputSegment in inputSegments:
- self.segments.append(
- OutputSegment(
- segmentType=inputSegment.segmentType,
- points=[
- OutputPoint(
- coordinates=point.coordinates,
- segmentType=point.segmentType,
- smooth=point.smooth,
- name=point.name,
- kwargs=point.kwargs
- )
- for point in inputSegment.points
- ],
- final=True
- )
- )
- inputSegment.used = True
- # reset the direction of the final contour
- self.clockwise = inputContour.clockwise
- return True
- return False
-
- def reCurveFromInputContourSegments(self, inputContour):
- return
- # match individual segments
- if self.clockwise:
- inputSegments = inputContour.clockwiseSegments
- else:
- inputSegments = inputContour.counterClockwiseSegments
- for inputSegment in inputSegments:
- # skip used
- if inputSegment.used:
- continue
- # skip if the input contains more points than the entire output contour
- if len(inputSegment.flat) > len(self.segments):
- continue
- # skip if the input end is not in the contour
- inputSegmentLastPoint = inputSegment.flat[-1]
- outputFlat = [segment.points[-1] for segment in self.segments]
- if inputSegmentLastPoint not in outputFlat:
- continue
- # work through all output segments
- for outputSegmentIndex, outputSegment in enumerate(self.segments):
- # skip finalized
- if outputSegment.final:
- continue
- # skip if the output point doesn't match the input end
- if outputSegment.points[-1] != inputSegmentLastPoint:
- continue
- # make a set of ranges for slicing the output into a testable list of points
- inputLength = len(inputSegment.flat)
- outputRanges = []
- outputSegmentIndex += 1
- if outputSegmentIndex - inputLength < 0:
- r1 = (len(self.segments) + outputSegmentIndex - inputLength, len(self.segments))
- outputRanges.append(r1)
- r2 = (0, outputSegmentIndex)
- outputRanges.append(r2)
- else:
- outputRanges.append((outputSegmentIndex - inputLength, outputSegmentIndex))
- # gather the output segments
- testableOutputSegments = []
- for start, end in outputRanges:
- testableOutputSegments += self.segments[start:end]
- # create a list of points
- test = []
- for s in testableOutputSegments:
- # stop if a segment is final
- if s.final:
- test = None
- break
- test.append(s.points[-1])
- if test == inputSegment.flat and inputSegment.segmentType != "line":
- # insert new segment
- newSegment = OutputSegment(
- segmentType=inputSegment.segmentType,
- points=[
- OutputPoint(
- coordinates=point.coordinates,
- segmentType=point.segmentType,
- smooth=point.smooth,
- name=point.name,
- kwargs=point.kwargs
- )
- for point in inputSegment.points
- ],
- final=True
- )
- self.segments.insert(outputSegmentIndex, newSegment)
- # remove old segments
- # XXX this is sloppy
- for start, end in outputRanges:
- if start > outputSegmentIndex:
- start += 1
- end += 1
- del self.segments[start:end]
- # flag the original as used
- inputSegment.used = True
- break
- # ? match line start points (to prevent curve fit in shortened line)
- return False
-
- def reCurveSubSegmentsCheckInputContoursOnHasCurve(self, inputContours):
- # test is the remaining input contours contains only lineTo points
- # XXX could be cached
- return True
- # for inputContour in inputContours:
- # if inputContour.used:
- # continue
- # if inputContour.hasOnCurve():
- # return True
- # return False
-
- def reCurveSubSegments(self, inputContours):
- if not self.segments:
- # its all done
- return
- # the inputContours has some curved segments
- # if not it all the segments will be converted at the end
- if self.reCurveSubSegmentsCheckInputContoursOnHasCurve(inputContours):
- # collect all flat points in a dict of unused inputContours
- # collect both clockwise segment and counterClockwise segments
- # it happens a lot that the directions turns around
- # the clockwise attribute can help but testing the directions is always needed
- # collect all oncurve points as well
- flatInputPointsSegmentDict = dict()
- reversedFlatInputPointsSegmentDict = dict()
- flatIntputOncurves = set()
- for inputContour in inputContours:
- if inputContour.used:
- continue
- if self.clockwise:
- inputSegments = inputContour.clockwiseSegments
- reversedSegments = inputContour.counterClockwiseSegments
- else:
- inputSegments = inputContour.counterClockwiseSegments
- reversedSegments = inputContour.clockwiseSegments
- for inputSegment in inputSegments:
- if inputSegment.used:
- continue
- for p in inputSegment.flat:
- flatInputPointsSegmentDict[p] = inputSegment
- flatIntputOncurves.add(inputSegment.scaledPreviousOnCurve)
-
- for inputSegment in reversedSegments:
- if inputSegment.used:
- continue
- for p in inputSegment.flat:
- reversedFlatInputPointsSegmentDict[p] = inputSegment
- flatIntputOncurves.add(inputSegment.scaledPreviousOnCurve)
- flatInputPoints = set(flatInputPointsSegmentDict.keys())
- # reset the starting point to a known point.
- # not somewhere in the middle of a flatten point list
- firstSegment = self.segments[0]
- foundStartingPoint = True
- if firstSegment.segmentType == "flat":
- foundStartingPoint = False
- for index, segment in enumerate(self.segments):
- if segment.segmentType in ["line", "curve", "qcurve"]:
- foundStartingPoint = True
- break
- if foundStartingPoint:
- # if found re index the segments
- # if there is no known starting point found do it later based on the intersection points
- self.segments = self.segments[index:] + self.segments[:index]
- # collect all flat points in a intersect segment
- remainingSubSegment = OutputSegment(segmentType="intersect", points=[])
- # store all segments in one big temp list
- newSegments = []
- for index, segment in enumerate(self.segments):
- if segment.segmentType != "flat":
- # when the segment contains only one points its a line cause it is a single intersection point
- if len(remainingSubSegment.points) == 1:
- remainingSubSegment.segmentType = "line"
- remainingSubSegment.final = True
- remainingSubSegment.points = [
- OutputPoint(
- coordinates=self._scalePoint(point),
- segmentType="line",
- smooth=point.smooth,
- name=point.name,
- kwargs=point.kwargs
- )
- for point in remainingSubSegment.points
- ]
- newSegments.append(remainingSubSegment)
- remainingSubSegment = OutputSegment(segmentType="intersect", points=[])
- newSegments.append(segment)
- continue
- remainingSubSegment.points.extend(segment.points)
- newSegments.append(remainingSubSegment)
- # loop over all segments
- for segment in newSegments:
- # handle only segments tagged as intersect
- if segment.segmentType != "intersect":
- continue
- # skip empty segments
- if not segment.points:
- continue
- # get al inputSegments, this is an unorderd list of all points no in the the flatInputPoints
- segmentPointsSet = set(segment.points)
- intersectionPoints = segmentPointsSet - flatInputPoints
- # merge both oncurves and intersectionPoints as known points
- possibleStartingPoints = flatIntputOncurves | intersectionPoints
- hasOncurvePoints = segmentPointsSet & flatIntputOncurves
- # if not starting point is found earlier do it here
- foundStartingPointIndex = None
- if not foundStartingPoint:
- for index, p in enumerate(segment.points):
- if p in flatIntputOncurves:
- foundStartingPointIndex = index
- break
- if foundStartingPointIndex is None:
- for index, p in enumerate(segment.points):
- if p in intersectionPoints:
- foundStartingPointIndex = index
- break
- segment.points = segment.points[foundStartingPointIndex:] + segment.points[:foundStartingPointIndex]
- # split list based on oncurvepoints and intersection points, aka possibleStartingPoints.
- segmentedFlatPoints = [[]]
- for p in segment.points:
- segmentedFlatPoints[-1].append(p)
- if p in possibleStartingPoints:
- segmentedFlatPoints.append([])
- if not segmentedFlatPoints[-1]:
- segmentedFlatPoints.pop(-1)
- if len(segmentedFlatPoints) > 1 and len(segmentedFlatPoints[0]) == 1:
- # if last segment is a curve, the start point may be last point on the last segment. If so, merge them.
- # check if they both have the same inputSegment or reversedInputSegment
- fp = segmentedFlatPoints[0][0]
- lp = segmentedFlatPoints[-1][-1]
- mergeFirstSegments = False
- if fp in flatInputPoints and lp in flatInputPoints:
- firstInputSegment = flatInputPointsSegmentDict[fp]
- lastInputSegment = flatInputPointsSegmentDict[lp]
- reversedFirstInputSegment = reversedFlatInputPointsSegmentDict[fp]
- reversedLastInputSegment = reversedFlatInputPointsSegmentDict[lp]
- if (firstInputSegment.segmentType == reversedFirstInputSegment.segmentType == "curve") or (lastInputSegment.segmentType == reversedLastInputSegment.segmentType == "curve"):
- if firstInputSegment == lastInputSegment or reversedFirstInputSegment == reversedLastInputSegment:
- mergeFirstSegments = True
- # elif len(firstInputSegment.points) > 1 and len(lastInputSegment.points) > 1:
- elif fp == lastInputSegment.scaledPreviousOnCurve:
- mergeFirstSegments = True
- elif lp == firstInputSegment.scaledPreviousOnCurve:
- mergeFirstSegments = True
- elif fp == reversedLastInputSegment.scaledPreviousOnCurve:
- mergeFirstSegments = True
- elif lp == reversedFirstInputSegment.scaledPreviousOnCurve:
- mergeFirstSegments = True
- elif not hasOncurvePoints and _distance(fp, lp):
- # Merge last segment with first segment if the distance between the last point and the first
- # point is less than the step distance between the last two points. _approximateSegmentLength
- # can be significantly smaller than this step size.
- if len(segmentedFlatPoints[-1]) > 1:
- f1 = segmentedFlatPoints[-1][-2]
- f2 = segmentedFlatPoints[-1][-1]
- stepLen = _distance(f1, f2)
- else:
- stepLen = _approximateSegmentLength*clipperScale
-
- if _distance(fp, lp) <= stepLen:
- mergeFirstSegments = True
- if mergeFirstSegments:
- segmentedFlatPoints[0] = segmentedFlatPoints[-1] + segmentedFlatPoints[0]
- segmentedFlatPoints.pop(-1)
- mergeFirstSegments = False
- convertedSegments = []
- previousIntersectionPoint = None
- if segmentedFlatPoints[-1][-1] in intersectionPoints:
- previousIntersectionPoint = self._scalePoint(segmentedFlatPoints[-1][-1])
- elif segmentedFlatPoints[0][0] in intersectionPoints:
- previousIntersectionPoint = self._scalePoint(segmentedFlatPoints[0][0])
-
- for flatSegment in segmentedFlatPoints:
- # search two points in the flat segment that is not an inputOncurve or intersection point
- # to get a proper direction of the flatSegment
- # based on these two points pick a inputSegment
- fp = ep = None
- for p in flatSegment:
- if p in possibleStartingPoints:
- continue
- elif fp is None:
- fp = p
- elif ep is None:
- ep = p
- else:
- break
- canDoFastLine = True
- if ep is None and ((fp is None) or (len(flatSegment) == 2)):
- # if fp is not None, then it is a flattened part of a curve, and should be used to derive the input segment.
- # It may be either the first or second point.
- # If fp is None, I use the original logic.
- if fp is None:
- fp = flatSegment[-1]
- # flat segment only contains two intersection points or one intersection point and one input oncurve point
- # this can be ignored cause this is a very small line
- # and will be converted to a simple line
- if self.clockwise:
- inputSegment = reversedFlatInputPointsSegmentDict.get(fp)
- else:
- inputSegment = flatInputPointsSegmentDict.get(fp)
- else:
- # get inputSegment based on the clockwise settings
- inputSegment = flatInputPointsSegmentDict[fp]
- if ep is not None and ep in inputSegment.flat:
- # if two points are found get indexes
- fi = inputSegment.flat.index(fp)
- ei = inputSegment.flat.index(ep)
- if fi > ei:
- # if the start index is bigger
- # get the reversed inputSegment
- inputSegment = reversedFlatInputPointsSegmentDict[fp]
- else:
- # if flat segment is short and has only one point not in intersections and input oncurves
- # test it against the reversed inputSegment
- reversedInputSegment = reversedFlatInputPointsSegmentDict[fp]
- if flatSegment[0] == reversedInputSegment.flat[0] and flatSegment[-1] == reversedInputSegment.flat[-1]:
- inputSegment = reversedInputSegment
- elif flatSegment[0] in intersectionPoints and flatSegment[-1] == reversedInputSegment.flat[-1]:
- inputSegment = reversedInputSegment
- elif flatSegment[-1] in intersectionPoints and flatSegment[0] == reversedInputSegment.flat[0]:
- inputSegment = reversedInputSegment
- canDoFastLine = False
- # if there is only one point in a flat segment
- # this is a single intersection points (two crossing lineTo's)
- if inputSegment.segmentType == "curve":
- canDoFastLine = False
- if (len(flatSegment) == 1 or inputSegment is None) and canDoFastLine:
- # p = flatSegment[0]
- for p in flatSegment:
- previousIntersectionPoint = self._scalePoint(p)
- pointInfo = dict()
- kwargs = dict()
- if p in flatInputPointsSegmentDict:
- lineSegment = flatInputPointsSegmentDict[p]
- segmentPoint = lineSegment.points[-1]
- pointInfo["smooth"] = segmentPoint.smooth
- pointInfo["name"] = segmentPoint.name
- kwargs.update(segmentPoint.kwargs)
- convertedSegments.append(OutputPoint(coordinates=previousIntersectionPoint, segmentType="line", kwargs=kwargs, **pointInfo))
- continue
- tValues = None
- lastPointWithAttributes = None
- if flatSegment[0] == inputSegment.flat[0] and flatSegment[-1] != inputSegment.flat[-1]:
- # needed the first part of the segment
- # if previousIntersectionPoint is None:
- # previousIntersectionPoint = self._scalePoint(flatSegment[-1])
- searchPoint = self._scalePoint(flatSegment[-1])
- tValues = inputSegment.tValueForPoint(searchPoint)
- curveNeeded = 0
- replacePointOnNewCurve = [(3, searchPoint)]
- previousIntersectionPoint = searchPoint
- elif flatSegment[-1] == inputSegment.flat[-1] and flatSegment[0] != inputSegment.flat[0]:
- # needed the end of the segment
- if previousIntersectionPoint is None:
- previousIntersectionPoint = self._scalePoint(flatSegment[0])
- convertedSegments.append(OutputPoint(
- coordinates=previousIntersectionPoint,
- segmentType="line",
- ))
- tValues = inputSegment.tValueForPoint(previousIntersectionPoint)
- curveNeeded = -1
- replacePointOnNewCurve = [(0, previousIntersectionPoint)]
- previousIntersectionPoint = None
- lastPointWithAttributes = inputSegment.points[-1]
- elif flatSegment[0] != inputSegment.flat[0] and flatSegment[-1] != inputSegment.flat[-1]:
- # needed the a middle part of the segment
- if previousIntersectionPoint is None:
- previousIntersectionPoint = self._scalePoint(flatSegment[0])
- tValues = inputSegment.tValueForPoint(previousIntersectionPoint)
- searchPoint = self._scalePoint(flatSegment[-1])
- tValues.extend(inputSegment.tValueForPoint(searchPoint))
- curveNeeded = 1
- replacePointOnNewCurve = [(0, previousIntersectionPoint), (3, searchPoint)]
- previousIntersectionPoint = searchPoint
- else:
- # take the whole segments as is
- newCurve = [
- OutputPoint(
- coordinates=point.coordinates,
- segmentType=point.segmentType,
- smooth=point.smooth,
- name=point.name,
- kwargs=point.kwargs
- )
- for point in inputSegment.points
- ]
- convertedSegments.extend(newCurve)
- previousIntersectionPoint = None
- # if we found some tvalue, split the curve and get the requested parts of the splitted curves
- if tValues:
- newCurve = inputSegment.split(tValues)
- newCurve = list(newCurve[curveNeeded])
- for i, replace in replacePointOnNewCurve:
- newCurve[i] = replace
- newCurve = [OutputPoint(coordinates=p, segmentType=None) for p in newCurve[1:]]
- newCurve[-1].segmentType = inputSegment.segmentType
- if lastPointWithAttributes is not None:
- newCurve[-1].smooth = lastPointWithAttributes.smooth
- newCurve[-1].name = lastPointWithAttributes.name
- newCurve[-1].kwargs = lastPointWithAttributes.kwargs
- convertedSegments.extend(newCurve)
- # replace the the points with the converted segments
- segment.points = convertedSegments
- segment.segmentType = "reCurved"
- self.segments = newSegments
- # XXX convert all of the remaining segments to lines
- for segment in self.segments:
- if not segment.points:
- continue
- if segment.segmentType not in ["intersect", "flat"]:
- continue
- segment.segmentType = "line"
- segment.points = [
- OutputPoint(
- coordinates=self._scalePoint(point),
- segmentType="line",
- # smooth=point.smooth,
- # name=point.name,
- # kwargs=point.kwargs
- )
- for point in segment.points
- ]
-
- # ----
- # Draw
- # ----
-
- def drawPoints(self, pointPen):
- pointPen.beginPath()
- points = []
- for segment in self.segments:
- points.extend(segment.points)
-
- hasOnCurve = False
- originalStartingPoints = []
- for index, point in enumerate(points):
- if point.segmentType is not None:
- hasOnCurve = True
- if point.kwargs is not None and point.kwargs.get("startingPoint"):
- distanceFromOrigin = math.hypot(*point)
- originalStartingPoints.append((distanceFromOrigin, index))
- if originalStartingPoints:
- # use the original starting point that is closest to the origin
- startingPointIndex = sorted(originalStartingPoints)[0][1]
- points = points[startingPointIndex:] + points[:startingPointIndex]
- elif hasOnCurve:
- while points[0].segmentType is None:
- p = points.pop(0)
- points.append(p)
- previousPointCoordinates = None
- for point in points:
- if previousPointCoordinates is not None and point.segmentType and tuple(point.coordinates) == previousPointCoordinates:
- continue
- kwargs = {}
- if point.kwargs is not None:
- kwargs = point.kwargs
- pointPen.addPoint(
- point.coordinates,
- segmentType=point.segmentType,
- smooth=point.smooth,
- name=point.name,
- **kwargs
- )
- if point.segmentType:
- previousPointCoordinates = tuple(point.coordinates)
- else:
- previousPointCoordinates = None
- pointPen.endPath()
-
-
-class OutputSegment(object):
-
- __slots__ = ["segmentType", "points", "final"]
-
- def __init__(self, segmentType=None, points=None, final=False):
- self.segmentType = segmentType
- if points is None:
- points = []
- self.points = points
- self.final = final
-
-
-class OutputPoint(InputPoint):
- pass
-
-
-# ----------
-# Misc. Math
-# ----------
-
-def _tValueForPointOnCubicCurve(point, cubicCurve, isHorizontal=0):
- """
- Finds a t value on a curve from a point.
- The points must be originaly be a point on the curve.
- This will only back trace the t value, needed to split the curve in parts
- """
- pt1, pt2, pt3, pt4 = cubicCurve
- a, b, c, d = bezierTools.calcCubicParameters(pt1, pt2, pt3, pt4)
- solutions = bezierTools.solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
- d[isHorizontal] - point[isHorizontal])
- solutions = [t for t in solutions if 0 <= t < 1]
- if not solutions and not isHorizontal:
- # can happen that a horizontal line doens intersect, try the vertical
- return _tValueForPointOnCubicCurve(point, (pt1, pt2, pt3, pt4), isHorizontal=1)
- if len(solutions) > 1:
- intersectionLenghts = {}
- for t in solutions:
- tp = _getCubicPoint(t, pt1, pt2, pt3, pt4)
- dist = _distance(tp, point)
- intersectionLenghts[dist] = t
- minDist = min(intersectionLenghts.keys())
- solutions = [intersectionLenghts[minDist]]
- return solutions
-
-
-def _tValueForPointOnQuadCurve(point, pts, isHorizontal=0):
- quadSegments = decomposeQuadraticSegment(pts[1:])
- previousOnCurve = pts[0]
- solutionsDict = dict()
- for index, (pt1, pt2) in enumerate(quadSegments):
- a, b, c = bezierTools.calcQuadraticParameters(previousOnCurve, pt1, pt2)
- subSolutions = bezierTools.solveQuadratic(a[isHorizontal], b[isHorizontal], c[isHorizontal] - point[isHorizontal])
- subSolutions = [t for t in subSolutions if 0 <= t < 1]
- for t in subSolutions:
- solutionsDict[(t, index)] = _getQuadPoint(t, previousOnCurve, pt1, pt2)
- previousOnCurve = pt2
- solutions = list(solutionsDict.keys())
- if not solutions and not isHorizontal:
- return _tValueForPointOnQuadCurve(point, pts, isHorizontal=1)
- if len(solutions) > 1:
- intersectionLenghts = {}
- for t in solutions:
- tp = solutionsDict[t]
- dist = _distance(tp, point)
- intersectionLenghts[dist] = t
- minDist = min(intersectionLenghts.keys())
- solutions = [intersectionLenghts[minDist]]
- return solutions
-
-
-def _tValueForPointOnLine(point, line):
- pt1, pt2 = line
- dist = _distance(pt1, point)
- totalDist = _distance(pt1, pt2)
- return [dist / totalDist]
-
-
-def _scalePoints(points, scale=1, convertToInteger=True):
- """
- Scale points and optionally convert them to integers.
- """
- if convertToInteger:
- points = [
- (int(round(x * scale)), int(round(y * scale)))
- for (x, y) in points
- ]
- else:
- points = [(x * scale, y * scale) for (x, y) in points]
- return points
-
-
-def _scaleSinglePoint(point, scale=1, convertToInteger=True):
- """
- Scale a single point
- """
- x, y = point
- if convertToInteger:
- return int(round(x * scale)), int(round(y * scale))
- else:
- return (x * scale, y * scale)
-
-
-def _intPoint(pt):
- return int(round(pt[0])), int(round(pt[1]))
-
-
-def _checkFlatPoints(points):
- _points = []
- previousX = previousY = None
- for x, y in points:
- if x == previousX:
- continue
- elif y == previousY:
- continue
- if (x, y) not in _points:
- # is it possible that two flat point are on top of eachother???
- _points.append((x, y))
- previousX, previousY = x, y
- if _points[-1] != points[-1]:
- _points[-1] = points[-1]
- return _points
-
-
-"""
-The curve flattening code was forked and modified from RoboFab's FilterPen.
-That code was written by Erik van Blokland.
-"""
-
-
-def _flattenSegment(segment, approximateSegmentLength=_approximateSegmentLength):
- """
- Flatten the curve segment int a list of points.
- The first and last points in the segment must be
- on curves. The returned list of points will not
- include the first on curve point.
-
- false curves (where the off curves are not any
- different from the on curves) must not be sent here.
- duplicate points must not be sent here.
- """
- onCurve1, offCurve1, offCurve2, onCurve2 = segment
- if _pointOnLine(onCurve1, onCurve2, offCurve1) and _pointOnLine(onCurve1, onCurve2, offCurve2):
- return [onCurve2]
- est = _estimateCubicCurveLength(onCurve1, offCurve1, offCurve2, onCurve2) / approximateSegmentLength
- flat = []
- minStep = 0.1564
- step = 1.0 / est
- if step > .3:
- step = minStep
- t = step
- while t < 1:
- pt = _getCubicPoint(t, onCurve1, offCurve1, offCurve2, onCurve2)
- # ignore when point is in the same direction as the on - off curve line
- if not _pointOnLine(offCurve2, onCurve2, pt) and not _pointOnLine(onCurve1, offCurve1, pt):
- flat.append(pt)
- t += step
- flat.append(onCurve2)
- return flat
-
-
-def _distance(pt1, pt2):
- return math.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
-
-
-def _pointOnLine(pt1, pt2, a):
- return abs(_distance(pt1, a) + _distance(a, pt2) - _distance(pt1, pt2)) < epsilon
-
-
-def _estimateCubicCurveLength(pt0, pt1, pt2, pt3, precision=10):
- """
- Estimate the length of this curve by iterating
- through it and averaging the length of the flat bits.
- """
- points = []
- length = 0
- step = 1.0 / precision
- factors = range(0, precision + 1)
- for i in factors:
- points.append(_getCubicPoint(i * step, pt0, pt1, pt2, pt3))
- for i in range(len(points) - 1):
- pta = points[i]
- ptb = points[i + 1]
- length += _distance(pta, ptb)
- return length
-
-
-def _mid(pt1, pt2):
- """
- (Point, Point) -> Point
- Return the point that lies in between the two input points.
- """
- (x0, y0), (x1, y1) = pt1, pt2
- return 0.5 * (x0 + x1), 0.5 * (y0 + y1)
-
-
-def _getCubicPoint(t, pt0, pt1, pt2, pt3):
- if t == 0:
- return pt0
- if t == 1:
- return pt3
- if t == 0.5:
- a = _mid(pt0, pt1)
- b = _mid(pt1, pt2)
- c = _mid(pt2, pt3)
- d = _mid(a, b)
- e = _mid(b, c)
- return _mid(d, e)
- else:
- cx = (pt1[0] - pt0[0]) * 3.0
- cy = (pt1[1] - pt0[1]) * 3.0
- bx = (pt2[0] - pt1[0]) * 3.0 - cx
- by = (pt2[1] - pt1[1]) * 3.0 - cy
- ax = pt3[0] - pt0[0] - cx - bx
- ay = pt3[1] - pt0[1] - cy - by
- t3 = t ** 3
- t2 = t * t
- x = ax * t3 + bx * t2 + cx * t + pt0[0]
- y = ay * t3 + by * t2 + cy * t + pt0[1]
- return x, y
-
-
-def _getQuadPoint(t, pt0, pt1, pt2):
- if t == 0:
- return pt0
- if t == 1:
- return pt2
- else:
- cx = pt0[0]
- cy = pt0[1]
- bx = (pt1[0] - cx) * 2.0
- by = (pt1[1] - cy) * 2.0
- ax = pt2[0] - cx - bx
- ay = pt2[1] - cy - by
- x = ax * t**2 + bx * t + cx
- y = ay * t**2 + by * t + cy
- return x, y
diff --git a/misc/pylib/booleanOperations/requirements.txt b/misc/pylib/booleanOperations/requirements.txt
deleted file mode 100644
index 4f1d1b49a..000000000
--- a/misc/pylib/booleanOperations/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# pyclipper==1.0.5
-# fonttools==3.1.2
-# ufoLib==2.0.0
diff --git a/misc/pylib/booleanOperations/setup.py b/misc/pylib/booleanOperations/setup.py
deleted file mode 100644
index ce98414ce..000000000
--- a/misc/pylib/booleanOperations/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from distutils.core import setup
-from distutils.extension import Extension
-from Cython.Distutils import build_ext
-
-ext_modules = [
- Extension("booleanGlyph", ["booleanGlyph.pyx"]),
- Extension("booleanOperationManager", ["booleanOperationManager.pyx"]),
- Extension("flatten", ["flatten.pyx"]),
-]
-
-setup(
- name = 'booleanOperations',
- cmdclass = {'build_ext': build_ext},
- ext_modules = ext_modules
-)
diff --git a/misc/pylib/booleanOperations/version.py b/misc/pylib/booleanOperations/version.py
deleted file mode 100644
index a44d7b1bf..000000000
--- a/misc/pylib/booleanOperations/version.py
+++ /dev/null
@@ -1,4 +0,0 @@
-try:
- __version__ = __import__('pkg_resources').require('booleanOperations')[0].version
-except Exception:
- __version__ = 'unknown'
diff --git a/misc/pylib/copy/.gitignore b/misc/pylib/copy/.gitignore
deleted file mode 100644
index e35ddbd94..000000000
--- a/misc/pylib/copy/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.c
-build
diff --git a/misc/pylib/copy/LICENSE.txt b/misc/pylib/copy/LICENSE.txt
deleted file mode 100644
index 529349e4b..000000000
--- a/misc/pylib/copy/LICENSE.txt
+++ /dev/null
@@ -1,254 +0,0 @@
-A. HISTORY OF THE SOFTWARE
-==========================
-
-Python was created in the early 1990s by Guido van Rossum at Stichting
-Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
-as a successor of a language called ABC. Guido remains Python's
-principal author, although it includes many contributions from others.
-
-In 1995, Guido continued his work on Python at the Corporation for
-National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
-in Reston, Virginia where he released several versions of the
-software.
-
-In May 2000, Guido and the Python core development team moved to
-BeOpen.com to form the BeOpen PythonLabs team. In October of the same
-year, the PythonLabs team moved to Digital Creations, which became
-Zope Corporation. In 2001, the Python Software Foundation (PSF, see
-https://www.python.org/psf/) was formed, a non-profit organization
-created specifically to own Python-related Intellectual Property.
-Zope Corporation was a sponsoring member of the PSF.
-
-All Python releases are Open Source (see http://www.opensource.org for
-the Open Source Definition). Historically, most, but not all, Python
-releases have also been GPL-compatible; the table below summarizes
-the various releases.
-
- Release Derived Year Owner GPL-
- from compatible? (1)
-
- 0.9.0 thru 1.2 1991-1995 CWI yes
- 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
- 1.6 1.5.2 2000 CNRI no
- 2.0 1.6 2000 BeOpen.com no
- 1.6.1 1.6 2001 CNRI yes (2)
- 2.1 2.0+1.6.1 2001 PSF no
- 2.0.1 2.0+1.6.1 2001 PSF yes
- 2.1.1 2.1+2.0.1 2001 PSF yes
- 2.1.2 2.1.1 2002 PSF yes
- 2.1.3 2.1.2 2002 PSF yes
- 2.2 and above 2.1.1 2001-now PSF yes
-
-Footnotes:
-
-(1) GPL-compatible doesn't mean that we're distributing Python under
- the GPL. All Python licenses, unlike the GPL, let you distribute
- a modified version without making your changes open source. The
- GPL-compatible licenses make it possible to combine Python with
- other software that is released under the GPL; the others don't.
-
-(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
- because its license has a choice of law clause. According to
- CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
- is "not incompatible" with the GPL.
-
-Thanks to the many outside volunteers who have worked under Guido's
-direction to make these releases possible.
-
-
-B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
-===============================================================
-
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF hereby
-grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
-analyze, test, perform and/or display publicly, prepare derivative works,
-distribute, and otherwise use Python alone or in any derivative version,
-provided, however, that PSF's License Agreement and PSF's notice of copyright,
-i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
-2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights
-Reserved" are retained in Python alone or in any derivative version prepared by
-Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
-
-4. PSF is making Python available to Licensee on an "AS IS"
-basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee. This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
-
-
-BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
--------------------------------------------
-
-BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
-
-1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
-office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
-Individual or Organization ("Licensee") accessing and otherwise using
-this software in source or binary form and its associated
-documentation ("the Software").
-
-2. Subject to the terms and conditions of this BeOpen Python License
-Agreement, BeOpen hereby grants Licensee a non-exclusive,
-royalty-free, world-wide license to reproduce, analyze, test, perform
-and/or display publicly, prepare derivative works, distribute, and
-otherwise use the Software alone or in any derivative version,
-provided, however, that the BeOpen Python License is retained in the
-Software, alone or in any derivative version prepared by Licensee.
-
-3. BeOpen is making the Software available to Licensee on an "AS IS"
-basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
-SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
-AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
-DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-5. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-6. This License Agreement shall be governed by and interpreted in all
-respects by the law of the State of California, excluding conflict of
-law provisions. Nothing in this License Agreement shall be deemed to
-create any relationship of agency, partnership, or joint venture
-between BeOpen and Licensee. This License Agreement does not grant
-permission to use BeOpen trademarks or trade names in a trademark
-sense to endorse or promote products or services of Licensee, or any
-third party. As an exception, the "BeOpen Python" logos available at
-http://www.pythonlabs.com/logos.html may be used according to the
-permissions granted on that web page.
-
-7. By copying, installing or otherwise using the software, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
-
-
-CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
----------------------------------------
-
-1. This LICENSE AGREEMENT is between the Corporation for National
-Research Initiatives, having an office at 1895 Preston White Drive,
-Reston, VA 20191 ("CNRI"), and the Individual or Organization
-("Licensee") accessing and otherwise using Python 1.6.1 software in
-source or binary form and its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, CNRI
-hereby grants Licensee a nonexclusive, royalty-free, world-wide
-license to reproduce, analyze, test, perform and/or display publicly,
-prepare derivative works, distribute, and otherwise use Python 1.6.1
-alone or in any derivative version, provided, however, that CNRI's
-License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
-1995-2001 Corporation for National Research Initiatives; All Rights
-Reserved" are retained in Python 1.6.1 alone or in any derivative
-version prepared by Licensee. Alternately, in lieu of CNRI's License
-Agreement, Licensee may substitute the following text (omitting the
-quotes): "Python 1.6.1 is made available subject to the terms and
-conditions in CNRI's License Agreement. This Agreement together with
-Python 1.6.1 may be located on the Internet using the following
-unique, persistent identifier (known as a handle): 1895.22/1013. This
-Agreement may also be obtained from a proxy server on the Internet
-using the following URL: http://hdl.handle.net/1895.22/1013".
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python 1.6.1 or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python 1.6.1.
-
-4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
-basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. This License Agreement shall be governed by the federal
-intellectual property law of the United States, including without
-limitation the federal copyright law, and, to the extent such
-U.S. federal law does not apply, by the law of the Commonwealth of
-Virginia, excluding Virginia's conflict of law provisions.
-Notwithstanding the foregoing, with regard to derivative works based
-on Python 1.6.1 that incorporate non-separable material that was
-previously distributed under the GNU General Public License (GPL), the
-law of the Commonwealth of Virginia shall govern this License
-Agreement only as to issues arising under or with respect to
-Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
-License Agreement shall be deemed to create any relationship of
-agency, partnership, or joint venture between CNRI and Licensee. This
-License Agreement does not grant permission to use CNRI trademarks or
-trade name in a trademark sense to endorse or promote products or
-services of Licensee, or any third party.
-
-8. By clicking on the "ACCEPT" button where indicated, or by copying,
-installing or otherwise using Python 1.6.1, Licensee agrees to be
-bound by the terms and conditions of this License Agreement.
-
- ACCEPT
-
-
-CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
---------------------------------------------------
-
-Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
-The Netherlands. All rights reserved.
-
-Permission to use, copy, modify, and distribute this software and its
-documentation for any purpose and without fee is hereby granted,
-provided that the above copyright notice appear in all copies and that
-both that copyright notice and this permission notice appear in
-supporting documentation, and that the name of Stichting Mathematisch
-Centrum or CWI not be used in advertising or publicity pertaining to
-distribution of the software without specific, written prior
-permission.
-
-STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
-THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
-FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
-OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/misc/pylib/copy/__init__.py b/misc/pylib/copy/__init__.py
deleted file mode 100644
index de7d17673..000000000
--- a/misc/pylib/copy/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from __future__ import absolute_import
-from .copy import copy, deepcopy, Error
diff --git a/misc/pylib/copy/copy.pyx b/misc/pylib/copy/copy.pyx
deleted file mode 100644
index daf81a3ff..000000000
--- a/misc/pylib/copy/copy.pyx
+++ /dev/null
@@ -1,433 +0,0 @@
-"""Generic (shallow and deep) copying operations.
-
-Interface summary:
-
- import copy
-
- x = copy.copy(y) # make a shallow copy of y
- x = copy.deepcopy(y) # make a deep copy of y
-
-For module specific errors, copy.Error is raised.
-
-The difference between shallow and deep copying is only relevant for
-compound objects (objects that contain other objects, like lists or
-class instances).
-
-- A shallow copy constructs a new compound object and then (to the
- extent possible) inserts *the same objects* into it that the
- original contains.
-
-- A deep copy constructs a new compound object and then, recursively,
- inserts *copies* into it of the objects found in the original.
-
-Two problems often exist with deep copy operations that don't exist
-with shallow copy operations:
-
- a) recursive objects (compound objects that, directly or indirectly,
- contain a reference to themselves) may cause a recursive loop
-
- b) because deep copy copies *everything* it may copy too much, e.g.
- administrative data structures that should be shared even between
- copies
-
-Python's deep copy operation avoids these problems by:
-
- a) keeping a table of objects already copied during the current
- copying pass
-
- b) letting user-defined classes override the copying operation or the
- set of components copied
-
-This version does not copy types like module, class, function, method,
-nor stack trace, stack frame, nor file, socket, window, nor array, nor
-any similar types.
-
-Classes can use the same interfaces to control copying that they use
-to control pickling: they can define methods called __getinitargs__(),
-__getstate__() and __setstate__(). See the documentation for module
-"pickle" for information on these methods.
-"""
-
-import types
-import weakref
-from copy_reg import dispatch_table
-
-class Error(Exception):
- pass
-error = Error # backward compatibility
-
-try:
- from org.python.core import PyStringMap
-except ImportError:
- PyStringMap = None
-
-__all__ = ["Error", "copy", "deepcopy"]
-
-def copy(x):
- """Shallow copy operation on arbitrary Python objects.
-
- See the module's __doc__ string for more info.
- """
-
- cls = type(x)
-
- copier = _copy_dispatch.get(cls)
- if copier:
- return copier(x)
-
- copier = getattr(cls, "__copy__", None)
- if copier:
- return copier(x)
-
- reductor = dispatch_table.get(cls)
- if reductor:
- rv = reductor(x)
- else:
- reductor = getattr(x, "__reduce_ex__", None)
- if reductor:
- rv = reductor(2)
- else:
- reductor = getattr(x, "__reduce__", None)
- if reductor:
- rv = reductor()
- else:
- raise Error("un(shallow)copyable object of type %s" % cls)
-
- return _reconstruct(x, rv, 0)
-
-
-_copy_dispatch = d = {}
-
-def _copy_immutable(x):
- return x
-for t in (type(None), int, long, float, bool, str, tuple,
- frozenset, type, xrange, types.ClassType,
- types.BuiltinFunctionType, type(Ellipsis),
- types.FunctionType, weakref.ref):
- d[t] = _copy_immutable
-for name in ("ComplexType", "UnicodeType", "CodeType"):
- t = getattr(types, name, None)
- if t is not None:
- d[t] = _copy_immutable
-
-def _copy_with_constructor(x):
- return type(x)(x)
-for t in (list, dict, set):
- d[t] = _copy_with_constructor
-
-def _copy_with_copy_method(x):
- return x.copy()
-if PyStringMap is not None:
- d[PyStringMap] = _copy_with_copy_method
-
-def _copy_inst(x):
- if hasattr(x, '__copy__'):
- return x.__copy__()
- if hasattr(x, '__getinitargs__'):
- args = x.__getinitargs__()
- y = x.__class__(*args)
- else:
- y = _EmptyClass()
- y.__class__ = x.__class__
- if hasattr(x, '__getstate__'):
- state = x.__getstate__()
- else:
- state = x.__dict__
- if hasattr(y, '__setstate__'):
- y.__setstate__(state)
- else:
- y.__dict__.update(state)
- return y
-d[types.InstanceType] = _copy_inst
-
-del d
-
-def deepcopy(x, memo=None, _nil=[]):
- """Deep copy operation on arbitrary Python objects.
-
- See the module's __doc__ string for more info.
- """
-
- if memo is None:
- memo = {}
-
- d = id(x)
- y = memo.get(d, _nil)
- if y is not _nil:
- return y
-
- cls = type(x)
-
- copier = _deepcopy_dispatch.get(cls)
- if copier:
- y = copier(x, memo)
- else:
- try:
- issc = issubclass(cls, type)
- except TypeError: # cls is not a class (old Boost; see SF #502085)
- issc = 0
- if issc:
- y = _deepcopy_atomic(x, memo)
- else:
- copier = getattr(x, "__deepcopy__", None)
- if copier:
- y = copier(memo)
- else:
- reductor = dispatch_table.get(cls)
- if reductor:
- rv = reductor(x)
- else:
- reductor = getattr(x, "__reduce_ex__", None)
- if reductor:
- rv = reductor(2)
- else:
- reductor = getattr(x, "__reduce__", None)
- if reductor:
- rv = reductor()
- else:
- raise Error(
- "un(deep)copyable object of type %s" % cls)
- y = _reconstruct(x, rv, 1, memo)
-
- memo[d] = y
- _keep_alive(x, memo) # Make sure x lives at least as long as d
- return y
-
-_deepcopy_dispatch = d = {}
-
-def _deepcopy_atomic(x, memo):
- return x
-d[type(None)] = _deepcopy_atomic
-d[type(Ellipsis)] = _deepcopy_atomic
-d[int] = _deepcopy_atomic
-d[long] = _deepcopy_atomic
-d[float] = _deepcopy_atomic
-d[bool] = _deepcopy_atomic
-try:
- d[complex] = _deepcopy_atomic
-except NameError:
- pass
-d[str] = _deepcopy_atomic
-try:
- d[unicode] = _deepcopy_atomic
-except NameError:
- pass
-try:
- d[types.CodeType] = _deepcopy_atomic
-except AttributeError:
- pass
-d[type] = _deepcopy_atomic
-d[xrange] = _deepcopy_atomic
-d[types.ClassType] = _deepcopy_atomic
-d[types.BuiltinFunctionType] = _deepcopy_atomic
-d[types.FunctionType] = _deepcopy_atomic
-d[weakref.ref] = _deepcopy_atomic
-
-def _deepcopy_list(x, memo):
- y = []
- memo[id(x)] = y
- for a in x:
- y.append(deepcopy(a, memo))
- return y
-d[list] = _deepcopy_list
-
-def _deepcopy_tuple(x, memo):
- y = []
- for a in x:
- y.append(deepcopy(a, memo))
- d = id(x)
- try:
- return memo[d]
- except KeyError:
- pass
- for i in range(len(x)):
- if x[i] is not y[i]:
- y = tuple(y)
- break
- else:
- y = x
- memo[d] = y
- return y
-d[tuple] = _deepcopy_tuple
-
-def _deepcopy_dict(x, memo):
- y = {}
- memo[id(x)] = y
- for key, value in x.iteritems():
- y[deepcopy(key, memo)] = deepcopy(value, memo)
- return y
-d[dict] = _deepcopy_dict
-if PyStringMap is not None:
- d[PyStringMap] = _deepcopy_dict
-
-def _deepcopy_method(x, memo): # Copy instance methods
- return type(x)(x.im_func, deepcopy(x.im_self, memo), x.im_class)
-_deepcopy_dispatch[types.MethodType] = _deepcopy_method
-
-def _keep_alive(x, memo):
- """Keeps a reference to the object x in the memo.
-
- Because we remember objects by their id, we have
- to assure that possibly temporary objects are kept
- alive by referencing them.
- We store a reference at the id of the memo, which should
- normally not be used unless someone tries to deepcopy
- the memo itself...
- """
- try:
- memo[id(memo)].append(x)
- except KeyError:
- # aha, this is the first one :-)
- memo[id(memo)]=[x]
-
-def _deepcopy_inst(x, memo):
- if hasattr(x, '__deepcopy__'):
- return x.__deepcopy__(memo)
- if hasattr(x, '__getinitargs__'):
- args = x.__getinitargs__()
- args = deepcopy(args, memo)
- y = x.__class__(*args)
- else:
- y = _EmptyClass()
- y.__class__ = x.__class__
- memo[id(x)] = y
- if hasattr(x, '__getstate__'):
- state = x.__getstate__()
- else:
- state = x.__dict__
- state = deepcopy(state, memo)
- if hasattr(y, '__setstate__'):
- y.__setstate__(state)
- else:
- y.__dict__.update(state)
- return y
-d[types.InstanceType] = _deepcopy_inst
-
-def _reconstruct(x, info, deep, memo=None):
- if isinstance(info, str):
- return x
- assert isinstance(info, tuple)
- if memo is None:
- memo = {}
- n = len(info)
- assert n in (2, 3, 4, 5)
- callable, args = info[:2]
- if n > 2:
- state = info[2]
- else:
- state = None
- if n > 3:
- listiter = info[3]
- else:
- listiter = None
- if n > 4:
- dictiter = info[4]
- else:
- dictiter = None
- if deep:
- args = deepcopy(args, memo)
- y = callable(*args)
- memo[id(x)] = y
-
- if state is not None:
- if deep:
- state = deepcopy(state, memo)
- if hasattr(y, '__setstate__'):
- y.__setstate__(state)
- else:
- if isinstance(state, tuple) and len(state) == 2:
- state, slotstate = state
- else:
- slotstate = None
- if state is not None:
- y.__dict__.update(state)
- if slotstate is not None:
- for key, value in slotstate.iteritems():
- setattr(y, key, value)
-
- if listiter is not None:
- for item in listiter:
- if deep:
- item = deepcopy(item, memo)
- y.append(item)
- if dictiter is not None:
- for key, value in dictiter:
- if deep:
- key = deepcopy(key, memo)
- value = deepcopy(value, memo)
- y[key] = value
- return y
-
-del d
-
-del types
-
-# Helper for instance creation without calling __init__
-class _EmptyClass:
- pass
-
-def _test():
- l = [None, 1, 2L, 3.14, 'xyzzy', (1, 2L), [3.14, 'abc'],
- {'abc': 'ABC'}, (), [], {}]
- l1 = copy(l)
- print l1==l
- l1 = map(copy, l)
- print l1==l
- l1 = deepcopy(l)
- print l1==l
- class C:
- def __init__(self, arg=None):
- self.a = 1
- self.arg = arg
- if __name__ == '__main__':
- import sys
- file = sys.argv[0]
- else:
- file = __file__
- self.fp = open(file)
- self.fp.close()
- def __getstate__(self):
- return {'a': self.a, 'arg': self.arg}
- def __setstate__(self, state):
- for key, value in state.iteritems():
- setattr(self, key, value)
- def __deepcopy__(self, memo=None):
- new = self.__class__(deepcopy(self.arg, memo))
- new.a = self.a
- return new
- c = C('argument sketch')
- l.append(c)
- l2 = copy(l)
- print l == l2
- print l
- print l2
- l2 = deepcopy(l)
- print l == l2
- print l
- print l2
- l.append({l[1]: l, 'xyz': l[2]})
- l3 = copy(l)
- import repr
- print map(repr.repr, l)
- print map(repr.repr, l1)
- print map(repr.repr, l2)
- print map(repr.repr, l3)
- l3 = deepcopy(l)
- import repr
- print map(repr.repr, l)
- print map(repr.repr, l1)
- print map(repr.repr, l2)
- print map(repr.repr, l3)
- class odict(dict):
- def __init__(self, d = {}):
- self.a = 99
- dict.__init__(self, d)
- def __setitem__(self, k, i):
- dict.__setitem__(self, k, i)
- self.a
- o = odict({"A" : "B"})
- x = deepcopy(o)
- print(o, x)
-
-if __name__ == '__main__':
- _test()
diff --git a/misc/pylib/copy/setup.py b/misc/pylib/copy/setup.py
deleted file mode 100644
index 180beb160..000000000
--- a/misc/pylib/copy/setup.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from distutils.core import setup
-from distutils.extension import Extension
-from Cython.Distutils import build_ext
-
-ext_modules = [
- Extension("copy", ["copy.pyx"]),
-]
-
-setup(
- name = 'copy',
- cmdclass = {'build_ext': build_ext},
- ext_modules = ext_modules
-)
diff --git a/misc/pylib/extractor/License.txt b/misc/pylib/extractor/License.txt
deleted file mode 100755
index c564676ab..000000000
--- a/misc/pylib/extractor/License.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License
-
-Copyright (c) 2010 Type Supply LLC
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE. \ No newline at end of file
diff --git a/misc/pylib/extractor/README.rst b/misc/pylib/extractor/README.rst
deleted file mode 100755
index 7dd9ab196..000000000
--- a/misc/pylib/extractor/README.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-|Travis Build Status| |PyPI Version| |Python Versions|
-
-
-UFO Extractor
-=============
-
-Tools for extracting data from font binaries into UFO objects.
-
-Features
---------
-
-Import data into a `Defcon <https://github.com/typesupply/defcon>`__ ``Font``
-instance:
-
-.. code:: python
-
- >>> import extractor
- >>> import defcon
- >>> ufo = defcon.Font()
- >>> extractor.extractUFO("/path/to/MyFont.ttf", ufo)
- >>> ufo.save("/path/to/MyFont.ufo")
-
-Supported input formats:
-
-- CFF or TrueType-flavored OpenType fonts (``*.otf``, ``*.ttf``)
-- `FontTools <https://github.com/fonttools/fonttools>`__ TTX files
- (``*.ttx``)
-- WOFF 1.0/2.0 (``*.woff``, ``*.woff2``)
-- PostScript Type1 fonts (``*.pfa``, ``*.pfb``, etc.)
-
-Installation
-------------
-
-You can install ``extractor`` with ``pip``:
-
-.. code::
-
- $ pip install ufo-extractor
-
-Note that, for historical reasons, the package is listed on the
-`Python Package Index <https://travis-ci.org/typesupply/extractor>`__ under the name
-``ufo-extractor``, to disambiguate it from another package also called "extractor".
-However, the import name for the package remains ``extractor``, without prefix.
-
-
-.. |Travis Build Status| image:: https://travis-ci.org/typesupply/extractor.svg?branch=master
- :target: https://travis-ci.org/typesupply/extractor
-.. |PyPI Version| image:: https://img.shields.io/pypi/v/ufo-extractor.svg
- :target: https://pypi.org/project/ufo-extractor/
-.. |Python Versions| image:: https://img.shields.io/badge/python-2.7%2C%203.5-blue.svg
diff --git a/misc/pylib/extractor/__init__.py b/misc/pylib/extractor/__init__.py
deleted file mode 100755
index 121ae275e..000000000
--- a/misc/pylib/extractor/__init__.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from extractor.exceptions import ExtractorError
-from extractor.formats.opentype import isOpenType, extractFontFromOpenType
-from extractor.formats.woff import isWOFF, extractFontFromWOFF
-from extractor.formats.type1 import isType1, extractFontFromType1
-from extractor.formats.ttx import isTTX, extractFontFromTTX
-
-
-__version__ = "0.2.1.dev0"
-
-_extractFunctions = dict(
- OTF=extractFontFromOpenType,
- Type1=extractFontFromType1,
- WOFF=extractFontFromWOFF,
- ttx=extractFontFromTTX,
-)
-
-def extractFormat(pathOrFile):
- if isType1(pathOrFile):
- return "Type1"
- elif isWOFF(pathOrFile):
- return "WOFF"
- elif isOpenType(pathOrFile):
- return "OTF"
- elif isTTX(pathOrFile):
- return "ttx"
- return None
-
-def extractUFO(pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, format=None, customFunctions={}):
- if format is None:
- format = extractFormat(pathOrFile)
- if format not in _extractFunctions:
- raise ExtractorError("Unknown file format.")
- func = _extractFunctions[format]
- # wrap the extraction in a try: except: so that
- # callers don't need to worry about lower level
- # (fontTools, etc.) errors. if an error
- # occurs, print the traceback for debugging and
- # raise an ExtractorError.
- try:
- func(pathOrFile, destination, doGlyphs=doGlyphs, doInfo=doInfo, doKerning=doKerning, customFunctions=customFunctions.get(format, []))
- except:
- import sys
- import traceback
- traceback.print_exc(file=sys.stdout)
- raise ExtractorError("There was an error reading the %s file." % format)
diff --git a/misc/pylib/extractor/exceptions.py b/misc/pylib/extractor/exceptions.py
deleted file mode 100755
index 823a88ace..000000000
--- a/misc/pylib/extractor/exceptions.py
+++ /dev/null
@@ -1 +0,0 @@
-class ExtractorError(Exception): pass
diff --git a/misc/pylib/extractor/formats/__init__.py b/misc/pylib/extractor/formats/__init__.py
deleted file mode 100755
index e69de29bb..000000000
--- a/misc/pylib/extractor/formats/__init__.py
+++ /dev/null
diff --git a/misc/pylib/extractor/formats/opentype.py b/misc/pylib/extractor/formats/opentype.py
deleted file mode 100755
index 3cf0e06ec..000000000
--- a/misc/pylib/extractor/formats/opentype.py
+++ /dev/null
@@ -1,806 +0,0 @@
-import time
-from fontTools.ttLib import TTFont, TTLibError
-from fontTools.ttLib.tables._h_e_a_d import mac_epoch_diff
-from fontTools.misc.textTools import num2binary
-from fontTools.pens.boundsPen import ControlBoundsPen
-from extractor.exceptions import ExtractorError
-from extractor.tools import RelaxedInfo, copyAttr
-
-# ----------------
-# Public Functions
-# ----------------
-
-def isOpenType(pathOrFile):
- try:
- font = TTFont(pathOrFile)
- del font
- except TTLibError:
- return False
- return True
-
-def extractFontFromOpenType(pathOrFile, destination, doGlyphOrder=True, doGlyphs=True, doInfo=True, doKerning=True, customFunctions=[]):
- source = TTFont(pathOrFile)
- if doInfo:
- extractOpenTypeInfo(source, destination)
- if doGlyphs:
- extractOpenTypeGlyphs(source, destination)
- if doGlyphOrder:
- extractGlyphOrder(source, destination)
- if doKerning:
- kerning, groups = extractOpenTypeKerning(source, destination)
- destination.groups.update(groups)
- destination.kerning.clear()
- destination.kerning.update(kerning)
- for function in customFunctions:
- function(source, destination)
- source.close()
-
-def extractGlyphOrder(source, destination):
- glyphOrder = source.getGlyphOrder()
- if len(glyphOrder):
- destination.lib["public.glyphOrder"] = glyphOrder
-
-# ----
-# Info
-# ----
-
-def extractOpenTypeInfo(source, destination):
- info = RelaxedInfo(destination.info)
- _extractInfoHead(source, info)
- _extractInfoName(source, info)
- _extracInfoOS2(source, info)
- _extractInfoHhea(source, info)
- _extractInfoVhea(source, info)
- _extractInfoPost(source, info)
- _extractInfoCFF(source, info)
- _extractInfoGasp(source, info)
-
-def _extractInfoHead(source, info):
- head = source["head"]
- # version
- version = str(round(head.fontRevision, 3))
- versionMajor, versionMinor = version.split(".")
- info.versionMajor = int(versionMajor)
- info.versionMinor = int(versionMinor)
- # upm
- info.unitsPerEm = head.unitsPerEm
- # created
- format = "%Y/%m/%d %H:%M:%S"
- created = head.created
- created = time.gmtime(max(0, created + mac_epoch_diff))
- info.openTypeHeadCreated = time.strftime(format, created)
- # lowestRecPPEM
- info.openTypeHeadLowestRecPPEM = head.lowestRecPPEM
- # flags
- info.openTypeHeadFlags = binaryToIntList(head.flags)
- # styleMapStyleName
- macStyle = binaryToIntList(head.macStyle)
- styleMapStyleName = "regular"
- if 0 in macStyle and 1 in macStyle:
- styleMapStyleName = "bold italic"
- elif 0 in macStyle:
- styleMapStyleName = "bold"
- elif 1 in macStyle:
- styleMapStyleName = "italic"
- info.styleMapStyleName = styleMapStyleName
-
-def _extractInfoName(source, info):
- records = []
- nameIDs = {}
- for record in source["name"].names:
- nameID = record.nameID
- platformID = record.platformID
- encodingID = record.platEncID
- languageID = record.langID
- string = record.toUnicode()
- nameIDs[nameID, platformID, encodingID, languageID] = string
- records.append((nameID, platformID, encodingID, languageID,
- dict(
- nameID=nameID,
- platformID=platformID,
- encodingID=encodingID,
- languageID=languageID,
- string=string
- )
- ))
- attributes = dict(
- familyName=_priorityOrder(16) + _priorityOrder(1),
- styleName=_priorityOrder(17) + _priorityOrder(2),
- styleMapFamilyName=_priorityOrder(1),
- # styleMapStyleName will be handled in head extraction
- copyright=_priorityOrder(0),
- trademark=_priorityOrder(7),
- openTypeNameDesigner=_priorityOrder(9),
- openTypeNameDesignerURL=_priorityOrder(12),
- openTypeNameManufacturer=_priorityOrder(8),
- openTypeNameManufacturerURL=_priorityOrder(11),
- openTypeNameLicense=_priorityOrder(13),
- openTypeNameLicenseURL=_priorityOrder(14),
- openTypeNameVersion=_priorityOrder(5),
- openTypeNameUniqueID=_priorityOrder(3),
- openTypeNameDescription=_priorityOrder(10),
- openTypeNamePreferredFamilyName=_priorityOrder(16),
- openTypeNamePreferredSubfamilyName=_priorityOrder(17),
- openTypeNameCompatibleFullName=_priorityOrder(18),
- openTypeNameSampleText=_priorityOrder(20),
- openTypeNameWWSFamilyName=_priorityOrder(21),
- openTypeNameWWSSubfamilyName=_priorityOrder(22)
- )
- for attr, priority in attributes.items():
- value = _skimNameIDs(nameIDs, priority)
- if value is not None:
- setattr(info, attr, value)
- info.openTypeNameRecords = [record[-1] for record in sorted(records)]
-
-def _priorityOrder(nameID):
- priority = [
- (nameID, 1, 0, 0),
- (nameID, 1, None, None),
- (nameID, None, None, None)
- ]
- return priority
-
-def _skimNameIDs(nameIDs, priority):
- for (nameID, platformID, platEncID, langID) in priority:
- for (nID, pID, pEID, lID), text in nameIDs.items():
- if nID != nameID:
- continue
- if pID != platformID and platformID is not None:
- continue
- if pEID != platEncID and platEncID is not None:
- continue
- if lID != langID and langID is not None:
- continue
- return text
-
-def _extracInfoOS2(source, info):
- os2 = source["OS/2"]
- # openTypeOS2WidthClass
- copyAttr(os2, "usWidthClass", info, "openTypeOS2WidthClass")
- # openTypeOS2WeightClass
- copyAttr(os2, "usWeightClass", info, "openTypeOS2WeightClass")
- # openTypeOS2Selection
- if hasattr(os2, "fsSelection"):
- fsSelection = binaryToIntList(os2.fsSelection)
- fsSelection = [i for i in fsSelection if i in (1, 2, 3, 4)]
- info.openTypeOS2Selection = fsSelection
- # openTypeOS2VendorID
- copyAttr(os2, "achVendID", info, "openTypeOS2VendorID")
- ## the string could be padded with null bytes. strip those.
- if info.openTypeOS2VendorID.endswith("\x00"):
- r = []
- for c in reversed(info.openTypeOS2VendorID):
- if r or c != "\x00":
- r.insert(0, c)
- info.openTypeOS2VendorID = "".join(r)
- # openTypeOS2Panose
- if hasattr(os2, "panose"):
- panose = os2.panose
- info.openTypeOS2Panose = [
- os2.panose.bFamilyType,
- os2.panose.bSerifStyle,
- os2.panose.bWeight,
- os2.panose.bProportion,
- os2.panose.bContrast,
- os2.panose.bStrokeVariation,
- os2.panose.bArmStyle,
- os2.panose.bLetterForm,
- os2.panose.bMidline,
- os2.panose.bXHeight
- ]
- # openTypeOS2FamilyClass
- # XXX info.openTypeOS2FamilyClass
- if hasattr(os2, "ulUnicodeRange1"):
- info.openTypeOS2UnicodeRanges = binaryToIntList(os2.ulUnicodeRange1) + binaryToIntList(os2.ulUnicodeRange2, 32) + binaryToIntList(os2.ulUnicodeRange3, 64) + binaryToIntList(os2.ulUnicodeRange4, 96)
- if hasattr(os2, "ulCodePageRange1"):
- info.openTypeOS2CodePageRanges = binaryToIntList(os2.ulCodePageRange1) + binaryToIntList(os2.ulCodePageRange2, 32)
- copyAttr(os2, "sxHeight", info, "xHeight")
- copyAttr(os2, "sCapHeight", info, "capHeight")
- copyAttr(os2, "sTypoAscender", info, "ascender")
- copyAttr(os2, "sTypoDescender", info, "descender")
- copyAttr(os2, "sTypoAscender", info, "openTypeOS2TypoAscender")
- copyAttr(os2, "sTypoDescender", info, "openTypeOS2TypoDescender")
- copyAttr(os2, "sTypoLineGap", info, "openTypeOS2TypoLineGap")
- copyAttr(os2, "usWinAscent", info, "openTypeOS2WinAscent")
- copyAttr(os2, "usWinDescent", info, "openTypeOS2WinDescent")
- if hasattr(os2, "fsType"):
- info.openTypeOS2Type = binaryToIntList(os2.fsType)
- copyAttr(os2, "ySubscriptXSize", info, "openTypeOS2SubscriptXSize")
- copyAttr(os2, "ySubscriptYSize", info, "openTypeOS2SubscriptYSize")
- copyAttr(os2, "ySubscriptXOffset", info, "openTypeOS2SubscriptXOffset")
- copyAttr(os2, "ySubscriptYOffset", info, "openTypeOS2SubscriptYOffset")
- copyAttr(os2, "ySuperscriptXSize", info, "openTypeOS2SuperscriptXSize")
- copyAttr(os2, "ySuperscriptYSize", info, "openTypeOS2SuperscriptYSize")
- copyAttr(os2, "ySuperscriptXOffset", info, "openTypeOS2SuperscriptXOffset")
- copyAttr(os2, "ySuperscriptYOffset", info, "openTypeOS2SuperscriptYOffset")
- copyAttr(os2, "yStrikeoutSize", info, "openTypeOS2StrikeoutSize")
- copyAttr(os2, "yStrikeoutPosition", info, "openTypeOS2StrikeoutPosition")
-
-def _extractInfoHhea(source, info):
- if "hhea" not in source:
- return
- hhea = source["hhea"]
- info.openTypeHheaAscender = hhea.ascent
- info.openTypeHheaDescender = hhea.descent
- info.openTypeHheaLineGap = hhea.lineGap
- info.openTypeHheaCaretSlopeRise = hhea.caretSlopeRise
- info.openTypeHheaCaretSlopeRun = hhea.caretSlopeRun
- info.openTypeHheaCaretOffset = hhea.caretOffset
-
-def _extractInfoVhea(source, info):
- if "vhea" not in source:
- return
- vhea = source["vhea"]
- info.openTypeVheaVertTypoAscender = vhea.ascent
- info.openTypeVheaVertTypoDescender = vhea.descent
- info.openTypeVheaVertTypoLineGap = vhea.lineGap
- info.openTypeVheaCaretSlopeRise = vhea.caretSlopeRise
- info.openTypeVheaCaretSlopeRun = vhea.caretSlopeRun
- if hasattr(vhea, "caretOffset"):
- info.openTypeVheaCaretOffset = vhea.caretOffset
-
-def _extractInfoPost(source, info):
- post = source["post"]
- info.italicAngle = post.italicAngle
- info.postscriptUnderlineThickness = post.underlineThickness
- info.postscriptUnderlinePosition = post.underlinePosition
- info.postscriptIsFixedPitch = bool(post.isFixedPitch)
-
-def _extractInfoCFF(source, info):
- if "CFF " not in source:
- return
- cff = source["CFF "].cff
- info.postscriptFontName = cff.fontNames[0]
- # TopDict
- topDict = cff.topDictIndex[0]
- info.postscriptFullName = topDict.rawDict.get("FullName", None)
- info.postscriptWeightName = topDict.rawDict.get("Weight", None)
- # Private
- # CID doesn't have this, so safely extract.
- if hasattr(topDict, "Private"):
- private = topDict.Private
- info.postscriptBlueValues = private.rawDict.get("BlueValues", [])
- info.postscriptOtherBlues = private.rawDict.get("OtherBlues", [])
- info.postscriptFamilyBlues = private.rawDict.get("FamilyBlues", [])
- info.postscriptFamilyOtherBlues = private.rawDict.get("FamilyOtherBlues", [])
- info.postscriptStemSnapH = private.rawDict.get("StemSnapH", [])
- info.postscriptStemSnapV = private.rawDict.get("StemSnapV", [])
- info.postscriptBlueFuzz = private.rawDict.get("BlueFuzz", None)
- info.postscriptBlueShift = private.rawDict.get("BlueShift", None)
- info.postscriptBlueScale = private.rawDict.get("BlueScale", None)
- info.postscriptForceBold = bool(private.rawDict.get("ForceBold", None))
- info.postscriptNominalWidthX = private.rawDict.get("nominalWidthX", None)
- info.postscriptDefaultWidthX = private.rawDict.get("defaultWidthX", None)
- # XXX postscriptSlantAngle
- # XXX postscriptUniqueID
-
-def _extractInfoGasp(source, info):
- if "gasp" not in source:
- return
- gasp = source["gasp"]
- records = []
- for size, bits in sorted(gasp.gaspRange.items()):
- behavior = []
- if bits & 0x0001:
- behavior.append(0)
- if bits & 0x0002:
- behavior.append(1)
- if bits & 0x0004:
- behavior.append(2)
- if bits & 0x0008:
- behavior.append(3)
- record = dict(
- rangeMaxPPEM=size,
- rangeGaspBehavior=behavior
- )
- records.append(record)
- info.openTypeGaspRangeRecords = records
-
-# Tools
-
-def binaryToIntList(value, start=0):
- intList = []
- counter = start
- while value:
- if value & 1:
- intList.append(counter)
- value >>= 1
- counter += 1
- return intList
-
-# --------
-# Outlines
-# --------
-
-def extractOpenTypeGlyphs(source, destination):
- # grab the cmap
- cmap = source["cmap"]
- vmtx = source.get("vmtx")
- vorg = source.get("VORG")
- preferred = [
- (3, 10, 12),
- (3, 10, 4),
- (3, 1, 12),
- (3, 1, 4),
- (0, 3, 12),
- (0, 3, 4),
- (3, 0, 12),
- (3, 0, 4),
- (1, 0, 12),
- (1, 0, 4)
- ]
- found = {}
- for table in cmap.tables:
- found[table.platformID, table.platEncID, table.format] = table
- table = None
- for key in preferred:
- if key not in found:
- continue
- table = found[key]
- break
- reversedMapping = {}
- if table is not None:
- for uniValue, glyphName in table.cmap.items():
- reversedMapping[glyphName] = uniValue
- # grab the glyphs
- glyphSet = source.getGlyphSet()
- for glyphName in glyphSet.keys():
- sourceGlyph = glyphSet[glyphName]
- # make the new glyph
- destination.newGlyph(glyphName)
- destinationGlyph = destination[glyphName]
- # outlines
- pen = destinationGlyph.getPen()
- sourceGlyph.draw(pen)
- # width
- destinationGlyph.width = sourceGlyph.width
- # height and vertical origin
- if vmtx is not None and glyphName in vmtx.metrics:
- destinationGlyph.height = vmtx[glyphName][0]
- if vorg is not None:
- if glyphName in vorg.VOriginRecords:
- destinationGlyph.verticalOrigin = vorg[glyphName]
- else:
- destinationGlyph.verticalOrigin = vorg.defaultVertOriginY
- else:
- tsb = vmtx[glyphName][1]
- bounds_pen = ControlBoundsPen(glyphSet)
- sourceGlyph.draw(bounds_pen)
- if bounds_pen.bounds is None:
- continue
- xMin, yMin, xMax, yMax = bounds_pen.bounds
- destinationGlyph.verticalOrigin = tsb + yMax
- # unicode
- destinationGlyph.unicode = reversedMapping.get(glyphName)
-
-# -------
-# Kerning
-# -------
-
-def extractOpenTypeKerning(source, destination):
- kerning = {}
- groups = {}
- if "GPOS" in source:
- kerning, groups = _extractOpenTypeKerningFromGPOS(source)
- elif "kern" in source:
- kerning = _extractOpenTypeKerningFromKern(source)
- groups = {}
- for name, group in groups.items():
- groups[name] = list(sorted(group))
- return kerning, groups
-
-def _extractOpenTypeKerningFromGPOS(source):
- gpos = source["GPOS"].table
- # get an ordered list of scripts
- scriptOrder = _makeScriptOrder(gpos)
- # extract kerning and classes from each applicable lookup
- kerningDictionaries, leftClassDictionaries, rightClassDictionaries = _gatherDataFromLookups(gpos, scriptOrder)
- # merge all kerning pairs
- kerning = _mergeKerningDictionaries(kerningDictionaries)
- # get rid of groups that have only one member
- leftSingleMemberGroups = _findSingleMemberGroups(leftClassDictionaries)
- rightSingleMemberGroups = _findSingleMemberGroups(rightClassDictionaries)
- # filter out the single glyph groups from the kerning
- kerning = _removeSingleMemberGroupReferences(kerning, leftSingleMemberGroups, rightSingleMemberGroups)
- # merge groups that have the exact same member list
- leftClasses, leftClassRename = _mergeClasses(leftClassDictionaries)
- rightClasses, rightClassRename = _mergeClasses(rightClassDictionaries)
- # search for overlapping groups and raise an error if any were found
- _validateClasses(leftClasses)
- _validateClasses(rightClasses)
- # populate the class marging into the kerning
- kerning = _replaceRenamedPairMembers(kerning, leftClassRename, rightClassRename)
- # rename the groups to final names
- leftClassRename = _renameClasses(leftClasses, "public.kern1.")
- rightClassRename = _renameClasses(rightClasses, "public.kern2.")
- # populate the final group names
- kerning = _replaceRenamedPairMembers(kerning, leftClassRename, rightClassRename)
- leftGroups = _setGroupNames(leftClasses, leftClassRename)
- rightGroups = _setGroupNames(rightClasses, rightClassRename)
- # combine the side groups
- groups = {}
- groups.update(leftGroups)
- groups.update(rightGroups)
- # done.
- return kerning, groups
-
-def _makeScriptOrder(gpos):
- """
- Run therough GPOS and make an alphabetically
- ordered list of scripts. If DFLT is in the list,
- move it to the front.
- """
- scripts = []
- for scriptRecord in gpos.ScriptList.ScriptRecord:
- scripts.append(scriptRecord.ScriptTag)
- if "DFLT" in scripts:
- scripts.remove("DFLT")
- scripts.insert(0, "DFLT")
- return sorted(scripts)
-
-def _gatherDataFromLookups(gpos, scriptOrder):
- """
- Gather kerning and classes from the applicable lookups
- and return them in script order.
- """
- lookupIndexes = _gatherLookupIndexes(gpos)
- seenLookups = set()
- kerningDictionaries = []
- leftClassDictionaries = []
- rightClassDictionaries = []
- for script in scriptOrder:
- kerning = []
- leftClasses = []
- rightClasses = []
- for lookupIndex in lookupIndexes[script]:
- if lookupIndex in seenLookups:
- continue
- seenLookups.add(lookupIndex)
- result = _gatherKerningForLookup(gpos, lookupIndex)
- if result is None:
- continue
- k, lG, rG = result
- kerning.append(k)
- leftClasses.append(lG)
- rightClasses.append(rG)
- if kerning:
- kerningDictionaries.append(kerning)
- leftClassDictionaries.append(leftClasses)
- rightClassDictionaries.append(rightClasses)
- return kerningDictionaries, leftClassDictionaries, rightClassDictionaries
-
-def _gatherLookupIndexes(gpos):
- """
- Gather a mapping of script to lookup indexes
- referenced by the kern feature for each script.
- Returns a dictionary of this structure:
- {
- "latn" : [0],
- "DFLT" : [0]
- }
- """
- # gather the indexes of the kern features
- kernFeatureIndexes = [index for index, featureRecord in enumerate(gpos.FeatureList.FeatureRecord) if featureRecord.FeatureTag == "kern"]
- # find scripts and languages that have kern features
- scriptKernFeatureIndexes = {}
- for scriptRecord in gpos.ScriptList.ScriptRecord:
- script = scriptRecord.ScriptTag
- thisScriptKernFeatureIndexes = []
- defaultLangSysRecord = scriptRecord.Script.DefaultLangSys
- if defaultLangSysRecord is not None:
- f = []
- for featureIndex in defaultLangSysRecord.FeatureIndex:
- if featureIndex not in kernFeatureIndexes:
- continue
- f.append(featureIndex)
- if f:
- thisScriptKernFeatureIndexes.append((None, f))
- if scriptRecord.Script.LangSysRecord is not None:
- for langSysRecord in scriptRecord.Script.LangSysRecord:
- langSys = langSysRecord.LangSysTag
- f = []
- for featureIndex in langSysRecord.LangSys.FeatureIndex:
- if featureIndex not in kernFeatureIndexes:
- continue
- f.append(featureIndex)
- if f:
- thisScriptKernFeatureIndexes.append((langSys, f))
- scriptKernFeatureIndexes[script] = thisScriptKernFeatureIndexes
- # convert the feature indexes to lookup indexes
- scriptLookupIndexes = {}
- for script, featureDefinitions in scriptKernFeatureIndexes.items():
- lookupIndexes = scriptLookupIndexes[script] = []
- for language, featureIndexes in featureDefinitions:
- for featureIndex in featureIndexes:
- featureRecord = gpos.FeatureList.FeatureRecord[featureIndex]
- for lookupIndex in featureRecord.Feature.LookupListIndex:
- if lookupIndex not in lookupIndexes:
- lookupIndexes.append(lookupIndex)
- # done
- return scriptLookupIndexes
-
-def _gatherKerningForLookup(gpos, lookupIndex):
- """
- Gather the kerning and class data for a particular lookup.
- Returns kerning, left clases, right classes.
- The kerning dictionary is of this structure:
- {
- ("a", "a") : 10,
- ((1, 1, 3), "a") : -20
- }
- The class dictionaries have this structure:
- {
- (1, 1, 3) : ["x", "y", "z"]
- }
- Where the tuple means this:
- (lookup index, subtable index, class index)
- """
- allKerning = {}
- allLeftClasses = {}
- allRightClasses = {}
- lookup = gpos.LookupList.Lookup[lookupIndex]
- # only handle pair positioning and extension
- if lookup.LookupType not in (2, 9):
- return
- for subtableIndex, subtable in enumerate(lookup.SubTable):
- if lookup.LookupType == 2:
- format = subtable.Format
- lookupType = subtable.LookupType
- if (lookupType, format) == (2, 1):
- kerning = _handleLookupType2Format1(subtable)
- allKerning.update(kerning)
- elif (lookupType, format) == (2, 2):
- kerning, leftClasses, rightClasses = _handleLookupType2Format2(subtable, lookupIndex, subtableIndex)
- allKerning.update(kerning)
- allLeftClasses.update(leftClasses)
- allRightClasses.update(rightClasses)
- elif lookup.LookupType == 9:
- extSubtable = subtable.ExtSubTable
- format = extSubtable.Format
- lookupType = extSubtable.LookupType
- if (lookupType, format) == (2, 1):
- kerning = _handleLookupType2Format1(extSubtable)
- allKerning.update(kerning)
- elif (lookupType, format) == (2, 2):
- kerning, leftClasses, rightClasses = _handleLookupType2Format2(extSubtable, lookupIndex, subtableIndex)
- allKerning.update(kerning)
- allLeftClasses.update(leftClasses)
- allRightClasses.update(rightClasses)
- # done
- return allKerning, allLeftClasses, allRightClasses
-
-def _handleLookupType2Format1(subtable):
- """
- Extract a kerning dictionary from a Lookup Type 2 Format 1.
- """
- kerning = {}
- coverage = subtable.Coverage.glyphs
- valueFormat1 = subtable.ValueFormat1
- pairSets = subtable.PairSet
- for index, leftGlyphName in enumerate(coverage):
- pairSet = pairSets[index]
- for pairValueRecord in pairSet.PairValueRecord:
- rightGlyphName = pairValueRecord.SecondGlyph
- if valueFormat1:
- value = pairValueRecord.Value1
- else:
- value = pairValueRecord.Value2
- if hasattr(value, "XAdvance"):
- value = value.XAdvance
- kerning[leftGlyphName, rightGlyphName] = value
- return kerning
-
-def _handleLookupType2Format2(subtable, lookupIndex, subtableIndex):
- """
- Extract kerning, left class and right class dictionaries from a Lookup Type 2 Format 2.
- """
- # extract the classes
- leftClasses = _extractFeatureClasses(lookupIndex=lookupIndex, subtableIndex=subtableIndex, classDefs=subtable.ClassDef1.classDefs, coverage=subtable.Coverage.glyphs)
- rightClasses = _extractFeatureClasses(lookupIndex=lookupIndex, subtableIndex=subtableIndex, classDefs=subtable.ClassDef2.classDefs)
- # extract the pairs
- kerning = {}
- for class1RecordIndex, class1Record in enumerate(subtable.Class1Record):
- for class2RecordIndex, class2Record in enumerate(class1Record.Class2Record):
- leftClass = (lookupIndex, subtableIndex, class1RecordIndex)
- rightClass = (lookupIndex, subtableIndex, class2RecordIndex)
- valueFormat1 = subtable.ValueFormat1
- if valueFormat1:
- value = class2Record.Value1
- else:
- value = class2Record.Value2
- if hasattr(value, "XAdvance") and value.XAdvance != 0:
- value = value.XAdvance
- kerning[leftClass, rightClass] = value
- return kerning, leftClasses, rightClasses
-
-def _mergeKerningDictionaries(kerningDictionaries):
- """
- Merge all of the kerning dictionaries found into
- one flat dictionary.
- """
- # work through the dictionaries backwards since
- # this uses an update to load the kerning. this
- # will ensure that the script order is honored.
- kerning = {}
- for dictionaryGroup in reversed(kerningDictionaries):
- for dictionary in dictionaryGroup:
- kerning.update(dictionary)
- # done.
- return kerning
-
-def _findSingleMemberGroups(classDictionaries):
- """
- Find all classes that have only one member.
- """
- toRemove = {}
- for classDictionaryGroup in classDictionaries:
- for classDictionary in classDictionaryGroup:
- for name, members in list(classDictionary.items()):
- if len(members) == 1:
- toRemove[name] = list(members)[0]
- del classDictionary[name]
- return toRemove
-
-def _removeSingleMemberGroupReferences(kerning, leftGroups, rightGroups):
- """
- Translate group names into glyph names in pairs
- if the group only contains one glyph.
- """
- new = {}
- for (left, right), value in kerning.items():
- left = leftGroups.get(left, left)
- right = rightGroups.get(right, right)
- new[left, right] = value
- return new
-
-def _mergeClasses(classDictionaries):
- """
- Look for classes that have the exact same list
- of members and flag them for removal.
- This returns left classes, left rename map,
- right classes and right rename map.
- The classes have the standard class structure.
- The rename maps have this structure:
- {
- (1, 1, 3) : (2, 3, 4),
- old name : new name
- }
- Where the key is the class that should be
- preserved and the value is a list of classes
- that should be removed.
- """
- # build a mapping of members to names
- memberTree = {}
- for classDictionaryGroup in classDictionaries:
- for classDictionary in classDictionaryGroup:
- for name, members in classDictionary.items():
- if members not in memberTree:
- memberTree[members] = set()
- memberTree[members].add(name)
- # find members that have more than one name
- classes = {}
- rename = {}
- for members, names in memberTree.items():
- name = names.pop()
- if len(names) > 0:
- for otherName in names:
- rename[otherName] = name
- classes[name] = members
- return classes, rename
-
-def _setGroupNames(classes, classRename):
- """
- Set the final names into the groups.
- """
- groups = {}
- for groupName, glyphList in classes.items():
- groupName = classRename.get(groupName, groupName)
- # if the glyph list has only one member,
- # the glyph name will be used in the pairs.
- # no group is needed.
- if len(glyphList) == 1:
- continue
- groups[groupName] = glyphList
- return groups
-
-def _validateClasses(classes):
- """
- Check to make sure that a glyph is not part of more than
- one class. If this is found, an ExtractorError is raised.
- """
- glyphToClass = {}
- for className, glyphList in classes.items():
- for glyphName in glyphList:
- if glyphName not in glyphToClass:
- glyphToClass[glyphName] = set()
- glyphToClass[glyphName].add(className)
- conflicts = 0
- for glyphName, groupList in glyphToClass.items():
- if len(groupList) > 1:
- print('Conflicting kerning classes for %s:' % glyphName)
- for groupId in groupList:
- group = classes[groupId]
- print(' %r => %s' % (groupId, ', '.join(group)))
- conflicts += 1
- if conflicts > 0:
- raise ExtractorError("Kerning classes are in an conflicting state")
-
-def _replaceRenamedPairMembers(kerning, leftRename, rightRename):
- """
- Populate the renamed pair members into the kerning.
- """
- renamedKerning = {}
- for (left, right), value in kerning.items():
- left = leftRename.get(left, left)
- right = rightRename.get(right, right)
- renamedKerning[left, right] = value
- return renamedKerning
-
-def _renameClasses(classes, prefix):
- """
- Replace class IDs with nice strings.
- """
- renameMap = {}
- for classID, glyphList in classes.items():
- if len(glyphList) == 0:
- groupName = "%s_empty_lu.%d_st.%d_cl.%d" % (prefix, classID[0], classID[1], classID[2])
- elif len(glyphList) == 1:
- groupName = list(glyphList)[0]
- else:
- glyphList = list(sorted(glyphList))
- groupName = prefix + glyphList[0]
- renameMap[classID] = groupName
- return renameMap
-
-def _extractFeatureClasses(lookupIndex, subtableIndex, classDefs, coverage=None):
- """
- Extract classes for a specific lookup in a specific subtable.
- This is relatively straightforward, except for class 0 interpretation.
- Some fonts don't have class 0. Some fonts have a list of class
- members that are clearly not all to be used in kerning pairs.
- In the case of a missing class 0, the coverage is used as a basis
- for the class and glyph names used in classed 1+ are filtered out.
- In the case of class 0 having glyph names that are not part of the
- kerning pairs, the coverage is used to filter out the unnecessary
- glyph names.
- """
- # gather the class members
- classDict = {}
- for glyphName, classIndex in classDefs.items():
- if classIndex not in classDict:
- classDict[classIndex] = set()
- classDict[classIndex].add(glyphName)
- # specially handle class index 0
- revisedClass0 = set()
- if coverage is not None and 0 in classDict:
- for glyphName in classDict[0]:
- if glyphName in coverage:
- revisedClass0.add(glyphName)
- elif coverage is not None and 0 not in classDict:
- revisedClass0 = set(coverage)
- for glyphList in classDict.values():
- revisedClass0 = revisedClass0 - glyphList
- classDict[0] = revisedClass0
- # flip the class map around
- classes = {}
- for classIndex, glyphList in classDict.items():
- classes[lookupIndex, subtableIndex, classIndex] = frozenset(glyphList)
- return classes
-
-def _extractOpenTypeKerningFromKern(source):
- kern = source["kern"]
- kerning = {}
- for subtable in kern.kernTables:
- if subtable.version != 0:
- raise ExtractorError("Unknown kern table formst: %d" % subtable.version)
- # XXX the spec defines coverage values for
- # kerning direction (horizontal or vertical)
- # minimum (some sort of kerning restriction)
- # cross-stream (direction of the kerns within the direction of the table. odd.)
- # override (if the values in this subtable should override the values of others)
- # however, it is vague about how these should be stored.
- # as such, we just assume that the direction is horizontal,
- # that the values of all subtables are additive and that
- # there are no minimum values.
- kerning.update(subtable.kernTable)
- return kerning
diff --git a/misc/pylib/extractor/formats/ttx.py b/misc/pylib/extractor/formats/ttx.py
deleted file mode 100755
index fac4a98a0..000000000
--- a/misc/pylib/extractor/formats/ttx.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from extractor.formats.opentype import extractOpenTypeInfo, extractOpenTypeGlyphs, extractOpenTypeKerning
-
-def isTTX(pathOrFile):
- from fontTools.ttLib import TTFont, TTLibError
- try:
- font = TTFont()
- font.importXML(pathOrFile)
- del font
- except TTLibError:
- return False
- return True
-
-def extractFontFromTTX(pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, customFunctions=[]):
- from fontTools.ttLib import TTFont, TTLibError
- source = TTFont()
- source.importXML(pathOrFile)
- if doInfo:
- extractOpenTypeInfo(source, destination)
- if doGlyphs:
- extractOpenTypeGlyphs(source, destination)
- if doKerning:
- kerning, groups = extractOpenTypeKerning(source, destination)
- destination.groups.update(groups)
- destination.kerning.clear()
- destination.kerning.update(kerning)
- for function in customFunctions:
- function(source, destination)
- source.close()
diff --git a/misc/pylib/extractor/formats/type1.py b/misc/pylib/extractor/formats/type1.py
deleted file mode 100755
index ca3f76741..000000000
--- a/misc/pylib/extractor/formats/type1.py
+++ /dev/null
@@ -1,164 +0,0 @@
-from fontTools.t1Lib import T1Font, T1Error
-from fontTools.agl import AGL2UV
-from fontTools.misc.psLib import PSInterpreter
-from fontTools.misc.transform import Transform
-from extractor.tools import RelaxedInfo
-
-# specification: http://partners.adobe.com/public/developer/en/font/T1_SPEC.PDF
-
-# ----------------
-# Public Functions
-# ----------------
-
-def isType1(pathOrFile):
- try:
- font = T1Font(pathOrFile)
- del font
- except T1Error:
- return False
- return True
-
-def extractFontFromType1(pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, customFunctions=[]):
- source = T1Font(pathOrFile)
- destination.lib["public.glyphOrder"] = _extractType1GlyphOrder(source)
- if doInfo:
- extractType1Info(source, destination)
- if doGlyphs:
- extractType1Glyphs(source, destination)
- if doKerning:
- # kerning extraction is not supported yet.
- # in theory, it could be retried from an AFM.
- # we need to find the AFM naming rules so that we can sniff for the file.
- pass
- for function in customFunctions:
- function(source, destination)
-
-def extractType1Info(source, destination):
- info = RelaxedInfo(destination.info)
- _extractType1FontInfo(source, info)
- _extractType1Private(source, info)
- _extractType1FontMatrix(source, info)
-
-# ----
-# Info
-# ----
-
-def _extractType1FontInfo(source, info):
- sourceInfo = source["FontInfo"]
- # FontName
- info.postscriptFontName = source["FontName"]
- # version
- version = sourceInfo.get("version")
- if version is not None:
- # the spec says that version will be a string and no formatting info is given.
- # so, only move forward if the string can actually be parsed.
- try:
- # 1. convert to a float
- version = float(version)
- # 2. convert it back to a string
- version = "%.3f" % version
- # 3. split.
- versionMajor, versionMinor = version.split(".")
- # 4. convert.
- versionMajor = int(versionMajor)
- versionMinor = int(versionMinor)
- # 5. set.
- info.versionMajor = int(versionMajor)
- info.versionMinor = int(versionMinor)
- except ValueError:
- # couldn't parse. leve the object with the default values.
- pass
- # Notice
- notice = sourceInfo.get("Notice")
- if notice:
- info.copyright = notice
- # FullName
- fullName = sourceInfo.get("FullName")
- if fullName:
- info.postscriptFullName = fullName
- # FamilyName
- familyName = sourceInfo.get("FamilyName")
- if familyName:
- info.familyName = familyName
- # Weight
- postscriptWeightName = sourceInfo.get("Weight")
- if postscriptWeightName:
- info.postscriptWeightName = postscriptWeightName
- # ItalicAngle
- info.italicAngle = sourceInfo.get("ItalicAngle")
- # IsFixedPitch
- info.postscriptIsFixedPitch = sourceInfo.get("isFixedPitch")
- # UnderlinePosition/Thickness
- info.postscriptUnderlinePosition = sourceInfo.get("UnderlinePosition")
- info.postscriptUnderlineThickness = sourceInfo.get("UnderlineThickness")
-
-def _extractType1FontMatrix(source, info):
- # units per em
- matrix = source["FontMatrix"]
- matrix = Transform(*matrix).inverse()
- info.unitsPerEm = int(round(matrix[3]))
-
-def _extractType1Private(source, info):
- private = source["Private"]
- # UniqueID
- info.openTypeNameUniqueID = private.get("UniqueID", None)
- # BlueValues and OtherBlues
- info.postscriptBlueValues = private.get("BlueValues", [])
- info.postscriptOtherBlues = private.get("OtherBlues", [])
- # FamilyBlues and FamilyOtherBlues
- info.postscriptFamilyBlues = private.get("FamilyBlues", [])
- info.postscriptFamilyOtherBlues = private.get("FamilyOtherBlues", [])
- # BlueScale/Shift/Fuzz
- info.postscriptBlueScale = private.get("BlueScale", None)
- info.postscriptBlueShift = private.get("BlueShift", None)
- info.postscriptBlueFuzz = private.get("BlueFuzz", None)
- # StemSnapH/V
- info.postscriptStemSnapH = private.get("StemSnapH", [])
- info.postscriptStemSnapV = private.get("StemSnapV", [])
- # ForceBold
- info.postscriptForceBold = bool(private.get("ForceBold", None))
-
-# --------
-# Outlines
-# --------
-
-def extractType1Glyphs(source, destination):
- glyphSet = source.getGlyphSet()
- for glyphName in sorted(glyphSet.keys()):
- sourceGlyph = glyphSet[glyphName]
- # make the new glyph
- destination.newGlyph(glyphName)
- destinationGlyph = destination[glyphName]
- # outlines
- pen = destinationGlyph.getPen()
- sourceGlyph.draw(pen)
- # width
- destinationGlyph.width = sourceGlyph.width
- # synthesize the unicode value
- destinationGlyph.unicode = AGL2UV.get(glyphName)
-
-# -----------
-# Glyph order
-# -----------
-
-class GlyphOrderPSInterpreter(PSInterpreter):
-
- def __init__(self):
- PSInterpreter.__init__(self)
- self.glyphOrder = []
- self.collectTokenForGlyphOrder = False
-
- def do_literal(self, token):
- result = PSInterpreter.do_literal(self, token)
- if token == "/FontName":
- self.collectTokenForGlyphOrder = False
- if self.collectTokenForGlyphOrder:
- self.glyphOrder.append(result.value)
- if token == "/CharStrings":
- self.collectTokenForGlyphOrder = True
- return result
-
-def _extractType1GlyphOrder(t1Font):
- interpreter = GlyphOrderPSInterpreter()
- interpreter.interpret(t1Font.data)
- return interpreter.glyphOrder
diff --git a/misc/pylib/extractor/formats/woff.py b/misc/pylib/extractor/formats/woff.py
deleted file mode 100755
index b641232f5..000000000
--- a/misc/pylib/extractor/formats/woff.py
+++ /dev/null
@@ -1,222 +0,0 @@
-from xml.sax.saxutils import quoteattr
-from fontTools.ttLib import TTFont, TTLibError
-from extractor.tools import RelaxedInfo
-from extractor.formats.opentype import extractOpenTypeInfo, extractOpenTypeGlyphs, extractOpenTypeKerning
-
-try:
- from xml.etree import cElementTree as ElementTree
-except ImportError:
- from xml.etree import ElementTree
-
-# ----------------
-# Public Functions
-# ----------------
-
-def isWOFF(pathOrFile):
- flavor = None
- try:
- font = TTFont(pathOrFile)
- flavor = font.flavor
- del font
- except TTLibError:
- return False
- return flavor in ("woff", "woff2")
-
-def extractFontFromWOFF(pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, customFunctions=[]):
- source = TTFont(pathOrFile)
- if doInfo:
- extractWOFFInfo(source, destination)
- if doGlyphs:
- extractWOFFGlyphs(source, destination)
- if doKerning:
- kerning, groups = extractWOFFKerning(source, destination)
- destination.groups.update(groups)
- destination.kerning.clear()
- destination.kerning.update(kerning)
- for function in customFunctions:
- function(source, destination)
- source.close()
-
-# ----------------
-# Specific Imports
-# ----------------
-
-def extractWOFFInfo(source, destination):
- info = RelaxedInfo(destination.info)
- info.woffMajorVersion = source.flavorData.majorVersion
- info.woffMinorVersion = source.flavorData.minorVersion
- _extractWOFFMetadata(source.flavorData, info)
- return extractOpenTypeInfo(source, destination)
-
-def extractWOFFGlyphs(source, destination):
- return extractOpenTypeGlyphs(source, destination)
-
-def extractWOFFKerning(source, destination):
- return extractOpenTypeKerning(source, destination)
-
-# --------
-# Metadata
-# --------
-
-def _extractWOFFMetadata(source, destination):
- if source.metaData is None:
- return
- metadata = ElementTree.fromstring(source.metaData)
- for element in metadata:
- if element.tag == "uniqueid":
- _extractWOFFMetadataUniqueID(element, destination)
- elif element.tag == "vendor":
- _extractWOFFMetadataVendor(element, destination)
- elif element.tag == "credits":
- _extractWOFFMetadataCredits(element, destination)
- elif element.tag == "description":
- _extractWOFFMetadataDescription(element, destination)
- elif element.tag == "license":
- _extractWOFFMetadataLicense(element, destination)
- elif element.tag == "copyright":
- _extractWOFFMetadataCopyright(element, destination)
- elif element.tag == "trademark":
- _extractWOFFMetadataTrademark(element, destination)
- elif element.tag == "licensee":
- _extractWOFFMetadataLicensee(element, destination)
- elif element.tag == "extension":
- _extractWOFFMetadataExtension(element, destination)
-
-def _extractWOFFMetadataUniqueID(element, destination):
- destination.woffMetadataUniqueID = _extractWOFFMetadataDict(element, ("id",))
-
-def _extractWOFFMetadataVendor(element, destination):
- attributes = ("name", "url", "dir", "class")
- record = _extractWOFFMetadataDict(element, attributes)
- destination.woffMetadataVendor = record
-
-def _extractWOFFMetadataCredits(element, destination):
- attributes = ("name", "url", "role", "dir", "class")
- credits = []
- for subElement in element:
- if subElement.tag == "credit":
- record = _extractWOFFMetadataDict(subElement, attributes)
- credits.append(record)
- destination.woffMetadataCredits = dict(credits=credits)
-
-def _extractWOFFMetadataDescription(element, destination):
- description = _extractWOFFMetadataDict(element, ("url",))
- textRecords = _extractWOFFMetadataText(element)
- if textRecords:
- description["text"] = textRecords
- destination.woffMetadataDescription = description
-
-def _extractWOFFMetadataLicense(element, destination):
- license = _extractWOFFMetadataDict(element, ("url", "id"))
- textRecords = _extractWOFFMetadataText(element)
- if textRecords:
- license["text"] = textRecords
- destination.woffMetadataLicense = license
-
-def _extractWOFFMetadataCopyright(element, destination):
- copyright = {}
- textRecords = _extractWOFFMetadataText(element)
- if textRecords:
- copyright["text"] = textRecords
- destination.woffMetadataCopyright = copyright
-
-def _extractWOFFMetadataTrademark(element, destination):
- trademark = {}
- textRecords = _extractWOFFMetadataText(element)
- if textRecords:
- trademark["text"] = textRecords
- destination.woffMetadataTrademark = trademark
-
-def _extractWOFFMetadataLicensee(element, destination):
- destination.woffMetadataLicensee = _extractWOFFMetadataDict(element, ("name", "dir", "class"))
-
-def _extractWOFFMetadataExtension(element, destination):
- extension = _extractWOFFMetadataDict(element, ("id",))
- for subElement in element:
- if subElement.tag == "name":
- if "names" not in extension:
- extension["names"] = []
- name = _extractWOFFMetadataExtensionName(subElement)
- extension["names"].append(name)
- elif subElement.tag == "item":
- if "items" not in extension:
- extension["items"] = []
- item = _extractWOFFMetadataExtensionItem(subElement)
- extension["items"].append(item)
- extensions = []
- if destination.woffMetadataExtensions:
- extensions.extend(destination.woffMetadataExtensions)
- destination.woffMetadataExtensions = extensions + [extension]
-
-def _extractWOFFMetadataExtensionItem(element):
- item = _extractWOFFMetadataDict(element, ("id",))
- for subElement in element:
- if subElement.tag == "name":
- if "names" not in item:
- item["names"] = []
- name = _extractWOFFMetadataExtensionName(subElement)
- item["names"].append(name)
- elif subElement.tag == "value":
- if "values" not in item:
- item["values"] = []
- name = _extractWOFFMetadataExtensionValue(subElement)
- item["values"].append(name)
- return item
-
-def _extractWOFFMetadataExtensionName(element):
- name = _extractWOFFMetadataDict(element, ("dir", "class"))
- language = _extractWOFFMetadataLanguage(element)
- if language is not None:
- name["language"] = language
- name["text"] = _flattenWOFFMetadataString(element)
- return name
-
-def _extractWOFFMetadataExtensionValue(element):
- return _extractWOFFMetadataExtensionName(element)
-
-# support
-
-def _extractWOFFMetadataDict(element, attributes):
- record = {}
- for attribute in attributes:
- value = element.attrib.get(attribute)
- if value is not None:
- record[attribute] = value
- return record
-
-def _extractWOFFMetadataText(element):
- records = []
- attributes = ("dir", "class")
- for subElement in element:
- record = _extractWOFFMetadataDict(subElement, attributes)
- # text
- record["text"] = _flattenWOFFMetadataString(subElement)
- # language
- language = _extractWOFFMetadataLanguage(subElement)
- if language is not None:
- record["language"] = language
- records.append(record)
- return records
-
-def _extractWOFFMetadataLanguage(element):
- language = element.attrib.get("{http://www.w3.org/XML/1998/namespace}lang")
- if language is None:
- language = element.attrib.get("lang")
- return language
-
-def _flattenWOFFMetadataString(element, sub=False):
- text = element.text.strip()
- for subElement in element:
- text += _flattenWOFFMetadataString(subElement, sub=True)
- if element.tail:
- text += element.tail.strip()
- if sub:
- attrib = ["%s=%s" % (key, quoteattr(value)) for key, value in element.attrib.items()]
- attrib = " ".join(attrib)
- if attrib:
- start = "<%s %s>" % (element.tag, attrib)
- else:
- start = "<%s>" % (element.tag)
- end = "</%s>" % (element.tag)
- text = start + text + end
- return text
diff --git a/misc/pylib/extractor/requirements.txt b/misc/pylib/extractor/requirements.txt
deleted file mode 100755
index f87221064..000000000
--- a/misc/pylib/extractor/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-fonttools==3.3.1
-ufoLib==2.0.0
diff --git a/misc/pylib/extractor/tools.py b/misc/pylib/extractor/tools.py
deleted file mode 100755
index f0750d7c3..000000000
--- a/misc/pylib/extractor/tools.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from ufoLib import fontInfoAttributesVersion3, validateFontInfoVersion3ValueForAttribute
-
-
-class RelaxedInfo(object):
-
- """
- This object that sets only valid info values
- into the given info object.
- """
-
- def __init__(self, info):
- self._object = info
-
- def __getattr__(self, attr):
- if attr in fontInfoAttributesVersion3:
- return getattr(self._object, attr)
- else:
- return super(RelaxedInfo, self).__getattr__(attr)
-
- def __setattr__(self, attr, value):
- if attr in fontInfoAttributesVersion3:
- if validateFontInfoVersion3ValueForAttribute(attr, value):
- setattr(self._object, attr, value)
- else:
- super(RelaxedInfo, self).__setattr__(attr, value)
-
-
-def copyAttr(src, srcAttr, dest, destAttr):
- if not hasattr(src, srcAttr):
- return
- value = getattr(src, srcAttr)
- setattr(dest, destAttr, value)
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
-)
diff --git a/misc/pylib/robofab/.gitignore b/misc/pylib/robofab/.gitignore
deleted file mode 100644
index e35ddbd94..000000000
--- a/misc/pylib/robofab/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.c
-build
diff --git a/misc/pylib/robofab/LICENSE.txt b/misc/pylib/robofab/LICENSE.txt
deleted file mode 100644
index df0945ab8..000000000
--- a/misc/pylib/robofab/LICENSE.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-RoboFab License Agreement
-
-Copyright (c) 2003-2013, The RoboFab Developers:
- Erik van Blokland
- Tal Leming
- Just van Rossum
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-Neither the name of the The RoboFab Developers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-Up to date info on RoboFab:
- http://robofab.com/
-
-This is the BSD license:
- http://www.opensource.org/licenses/BSD-3-Clause
-
diff --git a/misc/pylib/robofab/__init__.py b/misc/pylib/robofab/__init__.py
deleted file mode 100755
index d16579ff6..000000000
--- a/misc/pylib/robofab/__init__.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-ROBOFAB
-RoboFab is a Python library with objects
-that deal with data usually associated
-with fonts and type design.
-
-DEVELOPERS
-RoboFab is developed and maintained by
- Tal Leming
- Erik van Blokland
- Just van Rossum
- (in no particular order)
-
-MORE INFO
-The RoboFab homepage, documentation etc.
-http://robofab.com
-
-SVN REPOSITORY
-http://svn.robofab.com
-TRAC
-http://code.robofab.com
-
-RoboFab License Agreement
-
-Copyright (c) 2003-2013, The RoboFab Developers:
- Erik van Blokland
- Tal Leming
- Just van Rossum
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-Neither the name of the The RoboFab Developers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-Up to date info on RoboFab:
- http://robofab.com/
-
-This is the BSD license:
- http://www.opensource.org/licenses/BSD-3-Clause
-
-
-HISTORY
-RoboFab starts somewhere during the
-TypoTechnica in Heidelberg, 2003.
-
-DEPENDENCIES
-RoboFab expects fontTools to be installed.
-http://sourceforge.net/projects/fonttools/
-Some of the RoboFab modules require data files
-that are included in the source directory.
-RoboFab likes to be able to calculate paths
-to these data files all by itself, so keep them
-together with the source files.
-
-QUOTES
-Yuri Yarmola:
-"If data is somehow available to other programs
-via some standard data-exchange interface which
-can be accessed by some library in Python, you
-can make a Python script that uses that library
-to apply data to a font opened in FontLab."
-
-W.A. Dwiggins:
-"You will understand that I am not trying to
-short-circuit any of your shop operations in
-sending drawings of this kind. The closer I can
-get to the machine the better the result.
-Subtleties of curves are important, as you know,
-and if I can make drawings that can be used in
-the large size I have got one step closer to the
-machine that cuts the punches." [1932]
-
-"""
-
-from .exceptions import RoboFabError, RoboFabWarning
-
-numberVersion = (1, 2, "release", 1)
-version = "1.2.1"
diff --git a/misc/pylib/robofab/contrib/__init__.py b/misc/pylib/robofab/contrib/__init__.py
deleted file mode 100755
index 15f284762..000000000
--- a/misc/pylib/robofab/contrib/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-
-Directory for contributed packages.
-Packages stored here can be imported from
- robofab.contrib.<packagename>
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/exceptions.py b/misc/pylib/robofab/exceptions.py
deleted file mode 100644
index 21ca07507..000000000
--- a/misc/pylib/robofab/exceptions.py
+++ /dev/null
@@ -1,3 +0,0 @@
-class RoboFabError(Exception): pass
-
-class RoboFabWarning(Warning): pass
diff --git a/misc/pylib/robofab/gString.py b/misc/pylib/robofab/gString.py
deleted file mode 100755
index 01f319da3..000000000
--- a/misc/pylib/robofab/gString.py
+++ /dev/null
@@ -1,625 +0,0 @@
-"""A bunch of stuff useful for glyph name comparisons and such.
-
-1. A group of sorted glyph name lists that can be called directly:
-2. Some tools to work with glyph names to do things like build control strings."""
-
-import string
-
-######################################################
-# THE LISTS
-######################################################
-
-uppercase_plain = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AE', 'OE', 'Oslash', 'Thorn', 'Eth',]
-
-uppercase_accents = ['Aacute', 'Abreve', 'Acaron', 'Acircumflex', 'Adblgrave', 'Adieresis', 'Agrave', 'Amacron', 'Aogonek', 'Aring', 'Aringacute', 'Atilde', 'Bdotaccent', 'Cacute', 'Ccaron', 'Ccircumflex', 'Cdotaccent', 'Dcaron', 'Dcedilla', 'Ddotaccent', 'Eacute', 'Ebreve', 'Ecaron', 'Ecircumflex', 'Edblgrave', 'Edieresis', 'Edotaccent', 'Egrave', 'Emacron', 'Eogonek', 'Etilde', 'Fdotaccent', 'Gacute', 'Gbreve', 'Gcaron', 'Gcedilla', 'Gcircumflex', 'Gcommaaccent', 'Gdotaccent', 'Gmacron', 'Hcedilla', 'Hcircumflex', 'Hdieresis', 'Hdotaccent', 'Iacute', 'Ibreve', 'Icaron', 'Icircumflex', 'Idblgrave', 'Idieresis', 'Idieresisacute', 'Idieresisacute', 'Idotaccent', 'Igrave', 'Imacron', 'Iogonek', 'Itilde', 'Jcircumflex', 'Kacute', 'Kcaron', 'Kcedilla', 'Kcommaaccent', 'Lacute', 'Lcaron', 'Lcedilla', 'Lcommaaccent', 'Ldotaccent', 'Macute', 'Mdotaccent', 'Nacute', 'Ncaron', 'Ncedilla', 'Ncommaaccent', 'Ndotaccent', 'Ntilde', 'Oacute', 'Obreve', 'Ocaron', 'Ocircumflex', 'Odblgrave', 'Odieresis', 'Ograve', 'Ohorn', 'Ohungarumlaut', 'Omacron', 'Oogonek', 'Otilde', 'Pacute', 'Pdotaccent', 'Racute', 'Rcaron', 'Rcedilla', 'Rcommaaccent', 'Rdblgrave', 'Rdotaccent', 'Sacute', 'Scaron', 'Scedilla', 'Scircumflex', 'Scommaaccent', 'Sdotaccent', 'Tcaron', 'Tcedilla', 'Tcommaaccent', 'Tdotaccent', 'Uacute', 'Ubreve', 'Ucaron', 'Ucircumflex', 'Udblgrave', 'Udieresis', 'Udieresisacute', 'Udieresisacute', 'Udieresisgrave', 'Udieresisgrave', 'Ugrave', 'Uhorn', 'Uhungarumlaut', 'Umacron', 'Uogonek', 'Uring', 'Utilde', 'Vtilde', 'Wacute', 'Wcircumflex', 'Wdieresis', 'Wdotaccent', 'Wgrave', 'Xdieresis', 'Xdotaccent', 'Yacute', 'Ycircumflex', 'Ydieresis', 'Ydotaccent', 'Ygrave', 'Ytilde', 'Zacute', 'Zcaron', 'Zcircumflex', 'Zdotaccent', 'AEacute', 'Ccedilla', 'Oslashacute', 'Ldot']
-
-uppercase_special_accents = ['Dcroat', 'Lslash', 'Hbar', 'Tbar', 'LL', 'Eng']
-
-uppercase_ligatures = ['IJ']
-
-uppercase = uppercase_plain+uppercase_accents+uppercase_special_accents+uppercase_ligatures
-
-lowercase_plain = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'dotlessi', 'dotlessj', 'ae', 'oe', 'oslash', 'thorn', 'eth', 'germandbls', 'longs',]
-
-lowercase_accents = ['aacute', 'abreve', 'acaron', 'acircumflex', 'adblgrave', 'adieresis', 'agrave', 'amacron', 'aogonek', 'aring', 'aringacute', 'atilde', 'bdotaccent', 'cacute', 'ccaron', 'ccircumflex', 'cdotaccent', 'dcaron', 'dcedilla', 'ddotaccent', 'dmacron', 'eacute', 'ebreve', 'ecaron', 'ecircumflex', 'edblgrave', 'edieresis', 'edotaccent', 'egrave', 'emacron', 'eogonek', 'etilde', 'fdotaccent', 'gacute', 'gbreve', 'gcaron', 'gcedilla', 'gcircumflex', 'gcommaaccent', 'gdotaccent', 'gmacron', 'hcedilla', 'hcircumflex', 'hdieresis', 'hdotaccent', 'iacute', 'ibreve', 'icaron', 'icircumflex', 'idblgrave', 'idieresis', 'idieresisacute', 'idieresisacute', 'igrave', 'imacron', 'iogonek', 'itilde', 'jcaron', 'jcircumflex', 'kacute', 'kcaron', 'kcedilla', 'kcommaaccent', 'lacute', 'lcaron', 'lcedilla', 'lcommaaccent', 'ldotaccent', 'macute', 'mdotaccent', 'nacute', 'ncaron', 'ncedilla', 'ncommaaccent', 'ndotaccent', 'ntilde', 'oacute', 'obreve', 'ocaron', 'ocircumflex', 'odblgrave', 'odieresis', 'ograve', 'ohorn', 'ohungarumlaut', 'omacron', 'oogonek', 'otilde', 'pacute', 'pdotaccent', 'racute', 'rcaron', 'rcedilla', 'rcommaaccent', 'rdblgrave', 'rdotaccent', 'sacute', 'scaron', 'scedilla', 'scircumflex', 'scommaaccent', 'sdotaccent', 'tcaron', 'tcedilla', 'tcommaaccent', 'tdieresis', 'tdotaccent', 'uacute', 'ubreve', 'ucaron', 'ucircumflex', 'udblgrave', 'udieresis', 'udieresisacute', 'udieresisacute', 'udieresisgrave', 'udieresisgrave', 'ugrave', 'uhorn', 'uhungarumlaut', 'umacron', 'uogonek', 'uring', 'utilde', 'vtilde', 'wacute', 'wcircumflex', 'wdieresis', 'wdotaccent', 'wgrave', 'wring', 'xdieresis', 'xdotaccent', 'yacute', 'ycircumflex', 'ydieresis', 'ydotaccent', 'ygrave', 'yring', 'ytilde', 'zacute', 'zcaron', 'zcircumflex', 'zdotaccent', 'aeacute', 'ccedilla', 'oslashacute', 'ldot', ]
-
-lowercase_special_accents = ['dcroat', 'lslash', 'hbar', 'tbar', 'kgreenlandic', 'longs', 'll', 'eng']
-
-lowercase_ligatures = ['fi', 'fl', 'ff', 'ffi', 'ffl', 'ij']
-
-lowercase = lowercase_plain+lowercase_accents+lowercase_special_accents+lowercase_ligatures
-
-smallcaps_plain = ['A.sc', 'B.sc', 'C.sc', 'D.sc', 'E.sc', 'F.sc', 'G.sc', 'H.sc', 'I.sc', 'J.sc', 'K.sc', 'L.sc', 'M.sc', 'N.sc', 'O.sc', 'P.sc', 'Q.sc', 'R.sc', 'S.sc', 'T.sc', 'U.sc', 'V.sc', 'W.sc', 'X.sc', 'Y.sc', 'Z.sc', 'AE.sc', 'OE.sc', 'Oslash.sc', 'Thorn.sc', 'Eth.sc', ]
-
-smallcaps_accents = ['Aacute.sc', 'Acircumflex.sc', 'Adieresis.sc', 'Agrave.sc', 'Aring.sc', 'Atilde.sc', 'Ccedilla.sc', 'Eacute.sc', 'Ecircumflex.sc', 'Edieresis.sc', 'Egrave.sc', 'Iacute.sc', 'Icircumflex.sc', 'Idieresis.sc', 'Igrave.sc', 'Ntilde.sc', 'Oacute.sc', 'Ocircumflex.sc', 'Odieresis.sc', 'Ograve.sc', 'Otilde.sc', 'Scaron.sc', 'Uacute.sc', 'Ucircumflex.sc', 'Udieresis.sc', 'Ugrave.sc', 'Yacute.sc', 'Ydieresis.sc', 'Zcaron.sc', 'Ccedilla.sc', 'Lslash.sc', ]
-
-smallcaps_special_accents = ['Dcroat.sc', 'Lslash.sc', 'Hbar.sc', 'Tbar.sc', 'LL.sc', 'Eng.sc']
-
-smallcaps_ligatures = ['IJ.sc']
-
-smallcaps = smallcaps_plain + smallcaps_accents + smallcaps_special_accents + smallcaps_ligatures
-
-all_accents = uppercase_accents + uppercase_special_accents + lowercase_accents +lowercase_special_accents + smallcaps_accents + smallcaps_special_accents
-
-digits = ['one', 'onefitted', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'zero']
-
-digits_oldstyle = ['eight.oldstyle', 'five.oldstyle', 'four.oldstyle', 'nine.oldstyle', 'one.oldstyle', 'seven.oldstyle', 'six.oldstyle', 'three.oldstyle', 'two.oldstyle', 'zero.oldstyle']
-
-digits_superior = ['eight.superior', 'five.superior', 'four.superior', 'nine.superior', 'one.superior', 'seven.superior', 'six.superior', 'three.superior', 'two.superior', 'zero.superior']
-
-digits_inferior = ['eight.inferior', 'five.inferior', 'four.inferior', 'nine.inferior', 'one.inferior', 'seven.inferior', 'three.inferior', 'two.inferior', 'zero.inferior']
-
-fractions = ['oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onequarter', 'threequarters', 'onethird', 'twothirds', 'onehalf']
-
-currency = ['dollar', 'cent', 'currency', 'Euro', 'sterling', 'yen', 'florin', 'franc', 'lira']
-
-currency_oldstyle = ['cent.oldstyle', 'dollar.oldstyle']
-
-currency_superior = ['cent.superior', 'dollar.superior']
-
-currency_inferior = ['cent.inferior', 'dollar.inferior']
-
-inferior = ['eight.inferior', 'five.inferior', 'four.inferior', 'nine.inferior', 'one.inferior', 'seven.inferior', 'three.inferior', 'two.inferior', 'zero.inferior', 'cent.inferior', 'dollar.inferior', 'comma.inferior', 'hyphen.inferior', 'parenleft.inferior', 'parenright.inferior', 'period.inferior']
-
-superior = ['eight.superior', 'five.superior', 'four.superior', 'nine.superior', 'one.superior', 'seven.superior', 'six.superior', 'three.superior', 'two.superior', 'zero.superior', 'cent.superior', 'dollar.superior', 'Rsmallinverted.superior', 'a.superior', 'b.superior', 'comma.superior', 'd.superior', 'equal.superior', 'e.superior', 'glottalstopreversed.superior', 'hhook.superior', 'h.superior', 'hyphen.superior', 'i.superior', 'j.superior', 'l.superior', 'm.superior', 'n.superior', 'o.superior', 'parenleft.superior', 'parenright.superior', 'period.superior', 'plus.superior', 'r.superior', 'rturned.superior', 's.superior', 't.superior', 'w.superior', 'x.superior', 'y.superior']
-
-accents = ['acute', 'acutecomb', 'breve', 'caron', 'cedilla', 'circumflex', 'commaaccent', 'dblgrave', 'dieresis', 'dieresisacute', 'dieresisacute', 'dieresisgrave', 'dieresisgrave', 'dotaccent', 'grave', 'dblgrave', 'gravecomb', 'hungarumlaut', 'macron', 'ogonek', 'ring', 'ringacute', 'tilde', 'tildecomb', 'horn', 'Acute.sc', 'Breve.sc', 'Caron.sc', 'Cedilla.sc', 'Circumflex.sc', 'Dieresis.sc', 'Dotaccent.sc', 'Grave.sc', 'Hungarumlaut.sc', 'Macron.sc', 'Ogonek.sc', 'Ring.sc', 'Tilde.sc']
-
-dashes = ['hyphen', 'endash', 'emdash', 'threequartersemdash', 'underscore', 'underscoredbl', 'figuredash']
-
-legal = ['trademark', 'trademarksans', 'trademarkserif', 'copyright', 'copyrightsans', 'copyrightserif', 'registered', 'registersans', 'registerserif']
-
-ligatures = ['fi', 'fl', 'ff', 'ffi', 'ffl', 'ij', 'IJ']
-
-punctuation = ['period', 'periodcentered', 'comma', 'colon', 'semicolon', 'ellipsis', 'exclam', 'exclamdown', 'exclamdbl', 'question', 'questiondown']
-
-numerical = ['percent', 'perthousand', 'infinity', 'numbersign', 'degree', 'colonmonetary', 'dotmath']
-
-slashes = ['slash', 'backslash', 'bar', 'brokenbar', 'fraction']
-
-special = ['ampersand', 'paragraph', 'section', 'bullet', 'dagger', 'daggerdbl', 'asterisk', 'at', 'asciicircum', 'asciitilde']
-
-
-dependencies = {
- 'A': ['Aacute', 'Abreve', 'Acaron', 'Acircumflex', 'Adblgrave', 'Adieresis', 'Agrave', 'Amacron', 'Aogonek', 'Aring', 'Aringacute', 'Atilde'],
- 'B': ['Bdotaccent'],
- 'C': ['Cacute', 'Ccaron', 'Ccircumflex', 'Cdotaccent', 'Ccedilla'],
- 'D': ['Dcaron', 'Dcedilla', 'Ddotaccent'],
- 'E': ['Eacute', 'Ebreve', 'Ecaron', 'Ecircumflex', 'Edblgrave', 'Edieresis', 'Edotaccent', 'Egrave', 'Emacron', 'Eogonek', 'Etilde'],
- 'F': ['Fdotaccent'],
- 'G': ['Gacute', 'Gbreve', 'Gcaron', 'Gcedilla', 'Gcircumflex', 'Gcommaaccent', 'Gdotaccent', 'Gmacron'],
- 'H': ['Hcedilla', 'Hcircumflex', 'Hdieresis', 'Hdotaccent'],
- 'I': ['Iacute', 'Ibreve', 'Icaron', 'Icircumflex', 'Idblgrave', 'Idieresis', 'Idieresisacute', 'Idieresisacute', 'Idotaccent', 'Igrave', 'Imacron', 'Iogonek', 'Itilde'],
- 'J': ['Jcircumflex'],
- 'K': ['Kacute', 'Kcaron', 'Kcedilla', 'Kcommaaccent'],
- 'L': ['Lacute', 'Lcaron', 'Lcedilla', 'Lcommaaccent', 'Ldotaccent', 'Ldot'],
- 'M': ['Macute', 'Mdotaccent'],
- 'N': ['Nacute', 'Ncaron', 'Ncedilla', 'Ncommaaccent', 'Ndotaccent', 'Ntilde'],
- 'O': ['Oacute', 'Obreve', 'Ocaron', 'Ocircumflex', 'Odblgrave', 'Odieresis', 'Ograve', 'Ohorn', 'Ohungarumlaut', 'Omacron', 'Oogonek', 'Otilde'],
- 'P': ['Pacute', 'Pdotaccent'],
- 'R': ['Racute', 'Rcaron', 'Rcedilla', 'Rcommaaccent', 'Rdblgrave', 'Rdotaccent'],
- 'S': ['Sacute', 'Scaron', 'Scedilla', 'Scircumflex', 'Scommaaccent', 'Sdotaccent'],
- 'T': ['Tcaron', 'Tcedilla', 'Tcommaaccent', 'Tdotaccent'],
- 'U': ['Uacute', 'Ubreve', 'Ucaron', 'Ucircumflex', 'Udblgrave', 'Udieresis', 'Udieresisacute', 'Udieresisacute', 'Udieresisgrave', 'Udieresisgrave', 'Ugrave', 'Uhorn', 'Uhungarumlaut', 'Umacron', 'Uogonek', 'Uring', 'Utilde'],
- 'V': ['Vtilde'],
- 'W': ['Wacute', 'Wcircumflex', 'Wdieresis', 'Wdotaccent', 'Wgrave'],
- 'X': ['Xdieresis', 'Xdotaccent'],
- 'Y': ['Yacute', 'Ycircumflex', 'Ydieresis', 'Ydotaccent', 'Ygrave', 'Ytilde'],
- 'Z': ['Zacute', 'Zcaron', 'Zcircumflex', 'Zdotaccent'],
- 'AE': ['AEacute'],
- 'Oslash': ['Oslashacute'],
-
- 'a': ['aacute', 'abreve', 'acaron', 'acircumflex', 'adblgrave', 'adieresis', 'agrave', 'amacron', 'aogonek', 'aring', 'aringacute', 'atilde'],
- 'b': ['bdotaccent'],
- 'c': ['cacute', 'ccaron', 'ccircumflex', 'cdotaccent', 'ccedilla'],
- 'd': ['dcaron', 'dcedilla', 'ddotaccent', 'dmacron'],
- 'e': ['eacute', 'ebreve', 'ecaron', 'ecircumflex', 'edblgrave', 'edieresis', 'edotaccent', 'egrave', 'emacron', 'eogonek', 'etilde'],
- 'f': ['fdotaccent'],
- 'g': ['gacute', 'gbreve', 'gcaron', 'gcedilla', 'gcircumflex', 'gcommaaccent', 'gdotaccent', 'gmacron'],
- 'h': ['hcedilla', 'hcircumflex', 'hdieresis', 'hdotaccent'],
- 'i': ['iacute', 'ibreve', 'icaron', 'icircumflex', 'idblgrave', 'idieresis', 'idieresisacute', 'idieresisacute', 'igrave', 'imacron', 'iogonek', 'itilde'],
- 'j': ['jcaron', 'jcircumflex'],
- 'k': ['kacute', 'kcaron', 'kcedilla', 'kcommaaccent'],
- 'l': ['lacute', 'lcaron', 'lcedilla', 'lcommaaccent', 'ldotaccent', 'ldot'],
- 'm': ['macute', 'mdotaccent'],
- 'n': ['nacute', 'ncaron', 'ncedilla', 'ncommaaccent', 'ndotaccent', 'ntilde'],
- 'o': ['oacute', 'obreve', 'ocaron', 'ocircumflex', 'odblgrave', 'odieresis', 'ograve', 'ohorn', 'ohungarumlaut', 'omacron', 'oogonek', 'otilde'],
- 'p': ['pacute', 'pdotaccent'],
- 'r': ['racute', 'rcaron', 'rcedilla', 'rcommaaccent', 'rdblgrave', 'rdotaccent'],
- 's': ['sacute', 'scaron', 'scedilla', 'scircumflex', 'scommaaccent', 'sdotaccent'],
- 't': ['tcaron', 'tcedilla', 'tcommaaccent', 'tdieresis', 'tdotaccent'],
- 'u': ['uacute', 'ubreve', 'ucaron', 'ucircumflex', 'udblgrave', 'udieresis', 'udieresisacute', 'udieresisacute', 'udieresisgrave', 'udieresisgrave', 'ugrave', 'uhorn', 'uhungarumlaut', 'umacron', 'uogonek', 'uring', 'utilde'],
- 'v': ['vtilde'],
- 'w': ['wacute', 'wcircumflex', 'wdieresis', 'wdotaccent', 'wgrave', 'wring'],
- 'x': ['xdieresis', 'xdotaccent'],
- 'y': ['yacute', 'ycircumflex', 'ydieresis', 'ydotaccent', 'ygrave', 'yring', 'ytilde'],
- 'z': ['zacute', 'zcaron', 'zcircumflex', 'zdotaccent'],
- 'ae': ['aeacute'],
- 'oslash': ['oslashacute'],
- }
-######################################################
-# MISC TOOLS
-######################################################
-
-def breakSuffix(glyphname):
- """
- Breaks the glyphname into a two item list
- 0: glyphname
- 1: suffix
-
- if a suffix is not found it returns None
- """
- if glyphname.find('.') != -1:
- split = glyphname.split('.')
- return split
- else:
- return None
-
-def findAccentBase(accentglyph):
- """Return the base glyph of an accented glyph
- for example: Ugrave.sc returns U.sc"""
- base = splitAccent(accentglyph)[0]
- return base
-
-def splitAccent(accentglyph):
- """
- Split an accented glyph into a two items
- 0: base glyph
- 1: accent list
-
- for example: Yacute.scalt45 returns: (Y.scalt45, [acute])
- and: aacutetilde.alt45 returns (a.alt45, [acute, tilde])
- """
- base = None
- suffix = ''
- accentList=[]
- broken = breakSuffix(accentglyph)
- if broken is not None:
- suffix = broken[1]
- base = broken[0]
- else:
- base=accentglyph
- ogbase=base
- temp_special = lowercase_special_accents + uppercase_special_accents
- if base in lowercase_plain + uppercase_plain + smallcaps_plain:
- pass
- elif base not in temp_special:
- for accent in accents:
- if base.find(accent) != -1:
- base = base.replace(accent, '')
- accentList.append(accent)
- counter={}
- for accent in accentList:
- counter[ogbase.find(accent)] = accent
- counterList = counter.keys()
- counterList.sort()
- finalAccents = []
- for i in counterList:
- finalAccents.append(counter[i])
- accentList = finalAccents
- if len(suffix) != 0:
- base = '.'.join([base, suffix])
- return base, accentList
-
-
-######################################################
-# UPPER, LOWER, SMALL
-######################################################
-
-casedict = {
- 'germandbls' : 'S/S',
- 'dotlessi' : 'I',
- 'dotlessj' : 'J',
- 'ae' : 'AE',
- 'aeacute' : 'AEacute',
- 'oe' : 'OE',
- 'll' : 'LL'
- }
-
-casedictflip = {}
-
-smallcapscasedict = {
- 'germandbls' : 'S.sc/S.sc',
- 'question' : 'question.sc',
- 'questiondown' : 'questiondown.sc',
- 'exclam' : 'exclam.sc',
- 'exclamdown' : 'exclamdown.sc',
- 'ampersand' : 'ampersand.sc'
- }
-
-class _InternalCaseFunctions:
- """internal functions for doing gymnastics with the casedicts"""
-
- def expandsmallcapscasedict(self):
- for i in casedict.values():
- if i not in smallcapscasedict.keys():
- if len(i) > 1:
- if i[:1].upper() == i[:1]:
- smallcapscasedict[i] = i[:1] + i[1:] + '.sc'
-
- for i in uppercase:
- if i + '.sc' in smallcaps:
- if i not in smallcapscasedict.keys():
- smallcapscasedict[i] = i + '.sc'
-
- def flipcasedict(self):
- for i in casedict.keys():
- if i.find('dotless') != -1:
- i = i.replace('dotless', '')
- casedictflip[casedict[i]] = i
-
- def expandcasedict(self):
- for i in lowercase_ligatures:
- casedict[i] = i.upper()
- for i in lowercase:
- if i not in casedict.keys():
- if string.capitalize(i) in uppercase:
- casedict[i] = string.capitalize(i)
-
-
-def upper(glyphstring):
- """Convert all possible characters to uppercase in a glyph string."""
-
- _InternalCaseFunctions().expandcasedict()
- uc = []
- for i in glyphstring.split('/'):
- if i.find('.sc') != -1:
- if i[-3] != '.sc':
- x = i.replace('.sc', '.')
- else:
- x = i.replace('.sc', '')
- i = x
- suffix = ''
- bS = breakSuffix(i)
- if bS is not None:
- suffix = bS[1]
- i = bS[0]
- if i in casedict.keys():
- i = casedict[i]
- if len(suffix) != 0:
- i = '.'.join([i, suffix])
- uc.append(i)
- return '/'.join(uc)
-
-def lower(glyphstring):
- """Convert all possible characters to lowercase in a glyph string."""
-
- _InternalCaseFunctions().expandcasedict()
- _InternalCaseFunctions().flipcasedict()
- lc = []
- for i in glyphstring.split('/'):
- if i.find('.sc') != -1:
- if i[-3] != '.sc':
- x = i.replace('.sc', '.')
- else:
- x = i.replace('.sc', '')
- i = x
- suffix = ''
- bS = breakSuffix(i)
- if breakSuffix(i) is not None:
- suffix = bS[1]
- i = bS[0]
- if i in casedictflip.keys():
- i = casedictflip[i]
- if len(suffix) != 0:
- i = '.'.join([i, suffix])
- lc.append(i)
- return '/'.join(lc)
-
-def small(glyphstring):
- """Convert all possible characters to smallcaps in a glyph string."""
-
- _InternalCaseFunctions().expandcasedict()
- _InternalCaseFunctions().expandsmallcapscasedict()
- sc = []
- for i in glyphstring.split('/'):
- suffix = ''
- bS = breakSuffix(i)
- if bS is not None:
- suffix = bS[1]
- if suffix == 'sc':
- suffix = ''
- i = bS[0]
- if i in lowercase:
- if i not in smallcapscasedict.keys():
- i = casedict[i]
- if i in smallcapscasedict.keys():
- i = smallcapscasedict[i]
- if i != 'S.sc/S.sc':
- if len(suffix) != 0:
- if i[-3:] == '.sc':
- i = ''.join([i, suffix])
- else:
- i = '.'.join([i, suffix])
- sc.append(i)
- return '/'.join(sc)
-
-
-######################################################
-# CONTROL STRING TOOLS
-######################################################
-
-
-controldict = {
- 'UC' : ['/H/H', '/H/O/H/O', '/O/O'],
- 'LC' : ['/n/n', '/n/o/n/o', '/o/o'],
- 'SC' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc', '/O.sc/O.sc'],
- 'DIGITS' : ['/one/one', '/one/zero/one/zero', '/zero/zero'],
- }
-
-
-def controls(glyphname):
- """Send this a glyph name and get a control string
- with all glyphs separated by slashes."""
- controlslist = []
- for value in controldict.values():
- for v in value:
- for i in v.split('/'):
- if len(i) > 0:
- if i not in controlslist:
- controlslist.append(i)
- cs = ''
- if glyphname in controlslist:
- for key in controldict.keys():
- for v in controldict[key]:
- if glyphname in v.split('/'):
- con = controldict[key]
- striptriple = []
- hold1 = ''
- hold2 = ''
- for i in ''.join(con).split('/'):
- if len(i) != 0:
- if i == hold1 and i == hold2:
- pass
- else:
- striptriple.append(i)
- hold1 = hold2
- hold2 = i
- constr = '/' + '/'.join(striptriple)
- # this is a bit of a hack since FL seems to have trouble
- # when it encounters the same string more than once.
- # so, let's stick the glyph at the end to differentiate it.
- # for example: HHOHOOH and HHOHOOO
- cs = constr + '/' + glyphname
- else:
- suffix = ''
- bS = breakSuffix(glyphname)
- if bS is not None:
- suffix = bS[1]
- glyphname = bS[0]
- if suffix[:2] == 'sc':
- controls = controldict['SC']
- elif glyphname in uppercase:
- controls = controldict['UC']
- elif glyphname in lowercase:
- controls = controldict['LC']
- elif glyphname in digits:
- controls = controldict['DIGITS']
- else:
- controls = controldict['UC']
- if len(suffix) != 0:
- glyphname = '.'.join([glyphname, suffix])
- cs = controls[0] + '/' + glyphname + controls[1] + '/' + glyphname + controls[2]
- return cs
-
-
-def sortControlList(list):
- """Roughly sort a list of control strings."""
-
- controls = []
- for v in controldict.values():
- for w in v:
- for x in w.split('/'):
- if len(x) is not None:
- if x not in controls:
- controls.append(x)
- temp_digits = digits + digits_oldstyle + fractions
- temp_currency = currency + currency_oldstyle
- ss_uppercase = []
- ss_lowercase = []
- ss_smallcaps = []
- ss_digits = []
- ss_currency = []
- ss_other = []
- for i in list:
- glyphs = i.split('/')
- c = glyphs[2]
- for glyph in glyphs:
- if len(glyph) is not None:
- if glyph not in controls:
- c = glyph
- if c in uppercase:
- ss_uppercase.append(i)
- elif c in lowercase:
- ss_lowercase.append(i)
- elif c in smallcaps:
- ss_smallcaps.append(i)
- elif c in temp_digits:
- ss_digits.append(i)
- elif c in temp_currency:
- ss_currency.append(i)
- else:
- ss_other.append(i)
- ss_uppercase.sort()
- ss_lowercase.sort()
- ss_smallcaps.sort()
- ss_digits.sort()
- ss_currency.sort()
- ss_other.sort()
- return ss_uppercase + ss_lowercase + ss_smallcaps + ss_digits + ss_currency + ss_other
-
-
-# under contruction!
-kerncontroldict = {
- 'UC/UC' : ['/H/H', '/H/O/H/O/O'],
- 'UC/LC' : ['', '/n/n/o/n/e/r/s'],
- 'UC/SORTS' : ['/H/H', '/H/O/H/O/O'],
- 'UC/DIGITS' : ['/H/H', '/H/O/H/O/O'],
- 'LC/LC' : ['/n/n', '/n/o/n/o/o'],
- 'LC/SORTS' : ['/n/n', '/n/o/n/o/o'],
- 'LC/DIGITS' : ['', '/n/n/o/n/e/r/s'],
- 'SC/SC' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc/O.sc'],
- 'UC/SC' : ['', '/H.sc/H.sc/O.sc/H.sc/O.sc/O.sc'],
- 'SC/SORTS' : ['/H.sc/H.sc', '/H.sc/O.sc/H.sc/O.sc/O.sc'],
- 'SC/DIGITS' : ['', '/H.sc/H.sc/O.sc/H.sc/O.sc/O.sc'],
- 'DIGITS/DIGITS' : ['/H/H', '/H/O/H/O/O'],
- 'DIGITS/SORTS' : ['/H/H', '/H/O/H/O/O'],
- 'SORTS/SORTS' : ['/H/H', '/H/O/H/O/O'],
- }
-
-def kernControls(leftglyphname, rightglyphname):
- """build a control string based on the left glyph and right glyph"""
-
- sorts = currency + accents + dashes + legal + numerical + slashes + special
-
- l = leftglyphname
- r = rightglyphname
- lSuffix = ''
- rSuffix = ''
- bSL = breakSuffix(l)
- if bSL is not None:
- lSuffix = bSL[1]
- l = bSL[0]
- bSR = breakSuffix(r)
- if bSR is not None:
- rSuffix = bSR[1]
- r = bSR[0]
- if lSuffix[:2] == 'sc' or rSuffix[:2] == 'sc':
- if l in uppercase or r in uppercase:
- controls = kerncontroldict['UC/SC']
- elif l in digits or r in digits:
- controls = kerncontroldict['SC/DIGITS']
- elif l in sorts or r in sorts:
- controls = kerncontroldict['SC/SORTS']
- else:
- controls = kerncontroldict['SC/SC']
- elif l in uppercase or r in uppercase:
- if l in lowercase or r in lowercase:
- controls = kerncontroldict['UC/LC']
- elif l in digits or r in digits:
- controls = kerncontroldict['UC/DIGITS']
- elif l in sorts or r in sorts:
- controls = kerncontroldict['UC/SORTS']
- else:
- controls = kerncontroldict['UC/UC']
- elif l in lowercase or r in lowercase:
- if l in uppercase or r in uppercase:
- controls = kerncontroldict['UC/LC']
- elif l in digits or r in digits:
- controls = kerncontroldict['LC/DIGITS']
- elif l in sorts or r in sorts:
- controls = kerncontroldict['LC/SORTS']
- else:
- controls = kerncontroldict['LC/LC']
- elif l in digits or r in digits:
- if l in uppercase or r in uppercase:
- controls = kerncontroldict['UC/DIGITS']
- elif l in lowercase or r in lowercase:
- controls = kerncontroldict['LC/DIGITS']
- elif l in sorts or r in sorts:
- controls = kerncontroldict['DIGITS/SORTS']
- else:
- controls = kerncontroldict['DIGITS/DIGITS']
- elif l in sorts and r in sorts:
- controls = kerncontroldict['SORTS/SORTS']
- else:
- controls = kerncontroldict['UC/UC']
-
- if len(lSuffix) != 0:
- l = '.'.join([l, lSuffix])
- if len(rSuffix) != 0:
- r = '.'.join([r, rSuffix])
-
- cs = controls[0] + '/' + l + '/' + r + controls[1]
-
- return cs
-
-
-######################################################
-
-class _testing:
- def __init__(self):
- print
- print '##### testing!'
- # self.listtest()
- # self.accentbasetest()
- # self.controlstest()
- self.upperlowersmalltest()
- # self.stringsorttest()
-
- def listtest(self):
- testlist = [
- uppercase,
- uppercase_accents,
- lowercase,
- lowercase_accents,
- smallcaps,
- smallcaps_accents,
- digits,
- digits_oldstyle,
- digits_superior,
- digits_inferior,
- fractions,
- currency,
- currency_oldstyle,
- currency_superior,
- currency_inferior,
- inferior,
- superior,
- accents,
- dashes,
- legal,
- ligatures,
- punctuation,
- numerical,
- slashes,
- special
- ]
- for i in testlist:
- print i
-
-
- def accentbasetest(self):
- print findAccentBase('Adieresis')
- print findAccentBase('Adieresis.sc')
- print findAccentBase('Thorn.sc')
- print findAccentBase('notaralglyphname')
-
-
- def controlstest(self):
- print kernControls('A', 'a.swash')
- print kernControls('A.sc', '1')
- print kernControls('bracket.sc', 'germandbls')
- print kernControls('2', 'X')
- print kernControls('Y', 'X')
- print kernControls('Y.alt', 'X')
- print kernControls('Y.scalt', 'X')
- #print controls('x')
- #print controls('germandbls')
- #print controls('L')
- #print controls('L.sc')
- #print controls('Z.sc')
- #print controls('seven')
- #print controls('question')
- #print controls('unknown')
-
- def upperlowersmalltest(self):
- u = upper('/H/i/Z.sc/ampersand.sc/dotlessi/germandbls/four.superior/LL')
- l = lower('/H/I/Z.sc/ampersand.sc/dotlessi/germandbls/four.superior/LL')
- s = small('/H/i/Z.sc/ampersand.alt/dotlessi/germandbls/four.superior/LL')
- print u
- print l
- print s
- print lower(u)
- print upper(l)
- print upper(s)
- print lower(s)
-
- def stringsorttest(self):
- sample = "/H/H/Euro/H/O/H/O/Euro/O/O /H/H/R/H/O/H/O/R/O/O /H/H/question/H/O/H/O/question/O/O /H/H/sterling/H/O/H/O/sterling/O/O /n/n/r/n/o/n/o/r/o/o"
- list = string.split(sample, ' ')
- x = sortControlList(list)
- print x
-
-if __name__ == '__main__':
- _testing() \ No newline at end of file
diff --git a/misc/pylib/robofab/glifLib.pyx b/misc/pylib/robofab/glifLib.pyx
deleted file mode 100755
index 8a447fff4..000000000
--- a/misc/pylib/robofab/glifLib.pyx
+++ /dev/null
@@ -1,718 +0,0 @@
-# -*- coding: utf-8 -*-
-"""glifLib.py -- Generic module for reading and writing the .glif format.
-
-More info about the .glif format (GLyphInterchangeFormat) can be found here:
-
- http://robofab.com/ufo/glif.html
-
-The main class in this module is GlyphSet. It manages a set of .glif files
-in a folder. It offers two ways to read glyph data, and one way to write
-glyph data. See the class doc string for details.
-"""
-
-__all__ = ["GlyphSet", "GlifLibError",
- "readGlyphFromString", "writeGlyphToString",
- "glyphNameToFileName"]
-
-import os
-from robofab.xmlTreeBuilder import buildTree, stripCharacterData
-from robofab.pens.pointPen import AbstractPointPen
-from cStringIO import StringIO
-
-
-class GlifLibError(Exception): pass
-
-
-if os.name == "mac":
- WRITE_MODE = "wb" # use unix line endings, even with Classic MacPython
- READ_MODE = "rb"
-else:
- WRITE_MODE = "w"
- READ_MODE = "r"
-
-
-class Glyph:
-
- """Minimal glyph object. It has no glyph attributes until either
- the draw() or the drawPoint() method has been called.
- """
-
- def __init__(self, glyphName, glyphSet):
- self.glyphName = glyphName
- self.glyphSet = glyphSet
-
- def draw(self, pen):
- """Draw this glyph onto a *FontTools* Pen."""
- from robofab.pens.adapterPens import PointToSegmentPen
- pointPen = PointToSegmentPen(pen)
- self.drawPoints(pointPen)
-
- def drawPoints(self, pointPen):
- """Draw this glyph onto a PointPen."""
- self.glyphSet.readGlyph(self.glyphName, self, pointPen)
-
-
-def glyphNameToFileName(glyphName, glyphSet):
- """Default algorithm for making a file name out of a glyph name.
- This one has limited support for case insensitive file systems:
- it assumes glyph names are not case sensitive apart from the first
- character:
- 'a' -> 'a.glif'
- 'A' -> 'A_.glif'
- 'A.alt' -> 'A_.alt.glif'
- 'A.Alt' -> 'A_.Alt.glif'
- 'T_H' -> 'T__H_.glif'
- 'T_h' -> 'T__h.glif'
- 't_h' -> 't_h.glif'
- 'F_F_I' -> 'F__F__I_.glif'
- 'f_f_i' -> 'f_f_i.glif'
-
- """
- if glyphName.startswith("."):
- # some OSes consider filenames such as .notdef "hidden"
- glyphName = "_" + glyphName[1:]
- parts = glyphName.split(".")
- if parts[0].find("_")!=-1:
- # it is a compound name, check the separate parts
- bits = []
- for p in parts[0].split("_"):
- if p != p.lower():
- bits.append(p+"_")
- continue
- bits.append(p)
- parts[0] = "_".join(bits)
- else:
- # it is a single name
- if parts[0] != parts[0].lower():
- parts[0] += "_"
- for i in range(1, len(parts)):
- # resolve additional, period separated parts, like alt / Alt
- if parts[i] != parts[i].lower():
- parts[i] += "_"
- return ".".join(parts) + ".glif"
-
-
-
-class GlyphSet:
-
- """GlyphSet manages a set of .glif files inside one directory.
-
- GlyphSet's constructor takes a path to an existing directory as it's
- first argument. Reading glyph data can either be done through the
- readGlyph() method, or by using GlyphSet's dictionary interface, where
- the keys are glyph names and the values are (very) simple glyph objects.
-
- To write a glyph to the glyph set, you use the writeGlyph() method.
- The simple glyph objects returned through the dict interface do not
- support writing, they are just means as a convenient way to get at
- the glyph data.
- """
-
- glyphClass = Glyph
-
- def __init__(self, dirName, glyphNameToFileNameFunc=None):
- """'dirName' should be a path to an existing directory.
-
- The optional 'glyphNameToFileNameFunc' argument must be a callback
- function that takes two arguments: a glyph name and the GlyphSet
- instance. It should return a file name (including the .glif
- extension). The glyphNameToFileName function is called whenever
- a file name is created for a given glyph name.
- """
- self.dirName = dirName
- if glyphNameToFileNameFunc is None:
- glyphNameToFileNameFunc = glyphNameToFileName
- self.glyphNameToFileName = glyphNameToFileNameFunc
- self.contents = self._findContents()
- self._reverseContents = None
-
- def rebuildContents(self):
- """Rebuild the contents dict by checking what glyphs are available
- on disk.
- """
- self.contents = self._findContents(forceRebuild=True)
- self._reverseContents = None
-
- def getReverseContents(self):
- """Return a reversed dict of self.contents, mapping file names to
- glyph names. This is primarily an aid for custom glyph name to file
- name schemes that want to make sure they don't generate duplicate
- file names. The file names are converted to lowercase so we can
- reliably check for duplicates that only differ in case, which is
- important for case-insensitive file systems.
- """
- if self._reverseContents is None:
- d = {}
- for k, v in self.contents.iteritems():
- d[v.lower()] = k
- self._reverseContents = d
- return self._reverseContents
-
- def writeContents(self):
- """Write the contents.plist file out to disk. Call this method when
- you're done writing glyphs.
- """
- from plistlib import writePlistToString
- contentsPath = os.path.join(self.dirName, "contents.plist")
- # We need to force Unix line endings, even in OS9 MacPython in FL,
- # so we do the writing to file ourselves.
- plist = writePlistToString(self.contents)
- f = open(contentsPath, WRITE_MODE)
- f.write(plist)
- f.close()
-
- # reading/writing API
-
- def readGlyph(self, glyphName, glyphObject=None, pointPen=None):
- """Read a .glif file for 'glyphName' from the glyph set. The
- 'glyphObject' argument can be any kind of object (even None);
- the readGlyph() method will attempt to set the following
- attributes on it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional, in two ways:
- 1) An attribute *won't* be set if the .glif file doesn't
- contain data for it. 'glyphObject' will have to deal
- with default values itself.
- 2) If setting the attribute fails with an AttributeError
- (for example if the 'glyphObject' attribute is read-
- only), readGlyph() will not propagate that exception,
- but ignore that attribute.
-
- To retrieve outline information, you need to pass an object
- conforming to the PointPen protocol as the 'pointPen' argument.
- This argument may be None if you don't need the outline data.
-
- readGlyph() will raise KeyError if the glyph is not present in
- the glyph set.
- """
- tree = self._getXMLTree(glyphName)
- _readGlyphFromTree(tree, glyphObject, pointPen)
-
- def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None):
- """Write a .glif file for 'glyphName' to the glyph set. The
- 'glyphObject' argument can be any kind of object (even None);
- the writeGlyph() method will attempt to get the following
- attributes from it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional: if 'glyphObject' doesn't
- have the attribute, it will simply be skipped.
-
- To write outline data to the .glif file, writeGlyph() needs
- a function (any callable object actually) that will take one
- argument: an object that conforms to the PointPen protocol.
- The function will be called by writeGlyph(); it has to call the
- proper PointPen methods to transfer the outline to the .glif file.
- """
- data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc)
-
- fileName = self.contents.get(glyphName)
- if fileName is None:
- fileName = self.glyphNameToFileName(glyphName, self)
- self.contents[glyphName] = fileName
- if self._reverseContents is not None:
- self._reverseContents[fileName.lower()] = glyphName
- path = os.path.join(self.dirName, fileName)
- if os.path.exists(path):
- f = open(path, READ_MODE)
- oldData = f.read()
- f.close()
- if data == oldData:
- return
- f = open(path, WRITE_MODE)
- f.write(data)
- f.close()
-
- def deleteGlyph(self, glyphName):
- """Permanently delete the glyph from the glyph set on disk. Will
- raise KeyError if the glyph is not present in the glyph set.
- """
- fileName = self.contents[glyphName]
- os.remove(os.path.join(self.dirName, fileName))
- if self._reverseContents is not None:
- del self._reverseContents[self.contents[glyphName].lower()]
- del self.contents[glyphName]
-
- # dict-like support
-
- def keys(self):
- return self.contents.keys()
-
- def has_key(self, glyphName):
- return glyphName in self.contents
-
- __contains__ = has_key
-
- def __len__(self):
- return len(self.contents)
-
- def __getitem__(self, glyphName):
- if glyphName not in self.contents:
- raise KeyError, glyphName
- return self.glyphClass(glyphName, self)
-
- # quickly fetching unicode values
-
- def getUnicodes(self):
- """Return a dictionary that maps all glyph names to lists containing
- the unicode value[s] for that glyph, if any. This parses the .glif
- files partially, so is a lot faster than parsing all files completely.
- """
- # XXX: This method is quite wasteful if we've already parsed many .glif
- # files completely. We could collect unicodes values in readGlyph,
- # and only do _fetchUnicodes() for those we haven't seen yet.
- unicodes = {}
- for glyphName, fileName in self.contents.iteritems():
- path = os.path.join(self.dirName, fileName)
- unicodes[glyphName] = _fetchUnicodes(path)
- return unicodes
-
- # internal methods
-
- def _findContents(self, forceRebuild=False):
- contentsPath = os.path.join(self.dirName, "contents.plist")
- if forceRebuild or not os.path.exists(contentsPath):
- fileNames = os.listdir(self.dirName)
- fileNames = [n for n in fileNames if n.endswith(".glif")]
- contents = {}
- for n in fileNames:
- glyphPath = os.path.join(self.dirName, n)
- contents[_fetchGlyphName(glyphPath)] = n
- else:
- from plistlib import readPlist
- contents = readPlist(contentsPath)
- return contents
-
- def _getXMLTree(self, glyphName):
- fileName = self.contents[glyphName]
- path = os.path.join(self.dirName, fileName)
- if not os.path.exists(path):
- raise KeyError, glyphName
- return _glifTreeFromFile(path)
-
-
-def readGlyphFromString(aString, glyphObject=None, pointPen=None):
- """Read .glif data from a string into a glyph object.
-
- The 'glyphObject' argument can be any kind of object (even None);
- the readGlyphFromString() method will attempt to set the following
- attributes on it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional, in two ways:
- 1) An attribute *won't* be set if the .glif file doesn't
- contain data for it. 'glyphObject' will have to deal
- with default values itself.
- 2) If setting the attribute fails with an AttributeError
- (for example if the 'glyphObject' attribute is read-
- only), readGlyphFromString() will not propagate that
- exception, but ignore that attribute.
-
- To retrieve outline information, you need to pass an object
- conforming to the PointPen protocol as the 'pointPen' argument.
- This argument may be None if you don't need the outline data.
- """
- tree = _glifTreeFromFile(StringIO(aString))
- _readGlyphFromTree(tree, glyphObject, pointPen)
-
-
-def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=None):
- """Return .glif data for a glyph as a UTF-8 encoded string.
- The 'glyphObject' argument can be any kind of object (even None);
- the writeGlyphToString() method will attempt to get the following
- attributes from it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional: if 'glyphObject' doesn't
- have the attribute, it will simply be skipped.
-
- To write outline data to the .glif file, writeGlyphToString() needs
- a function (any callable object actually) that will take one
- argument: an object that conforms to the PointPen protocol.
- The function will be called by writeGlyphToString(); it has to call the
- proper PointPen methods to transfer the outline to the .glif file.
- """
- if writer is None:
- try:
- from xmlWriter import XMLWriter
- except ImportError:
- # try the other location
- from fontTools.misc.xmlWriter import XMLWriter
- aFile = StringIO()
- writer = XMLWriter(aFile, encoding="UTF-8")
- else:
- aFile = None
- writer.begintag("glyph", [("name", glyphName), ("format", "1")])
- writer.newline()
-
- width = getattr(glyphObject, "width", None)
- if width is not None:
- if not isinstance(width, (int, float)):
- raise GlifLibError, "width attribute must be int or float"
- writer.simpletag("advance", width=repr(width))
- writer.newline()
-
- unicodes = getattr(glyphObject, "unicodes", None)
- if unicodes:
- if isinstance(unicodes, int):
- unicodes = [unicodes]
- for code in unicodes:
- if not isinstance(code, int):
- raise GlifLibError, "unicode values must be int"
- hexCode = hex(code)[2:].upper()
- if len(hexCode) < 4:
- hexCode = "0" * (4 - len(hexCode)) + hexCode
- writer.simpletag("unicode", hex=hexCode)
- writer.newline()
-
- note = getattr(glyphObject, "note", None)
- if note is not None:
- if not isinstance(note, (str, unicode)):
- raise GlifLibError, "note attribute must be str or unicode"
- note = note.encode('utf-8')
- writer.begintag("note")
- writer.newline()
- for line in note.splitlines():
- writer.write(line.strip())
- writer.newline()
- writer.endtag("note")
- writer.newline()
-
- if drawPointsFunc is not None:
- writer.begintag("outline")
- writer.newline()
- pen = GLIFPointPen(writer)
- drawPointsFunc(pen)
- writer.endtag("outline")
- writer.newline()
-
- lib = getattr(glyphObject, "lib", None)
- if lib:
- from robofab.plistlib import PlistWriter
- if not isinstance(lib, dict):
- lib = dict(lib)
- writer.begintag("lib")
- writer.newline()
- plistWriter = PlistWriter(writer.file, indentLevel=writer.indentlevel,
- indent=writer.indentwhite, writeHeader=False)
- plistWriter.writeValue(lib)
- writer.endtag("lib")
- writer.newline()
-
- writer.endtag("glyph")
- writer.newline()
- if aFile is not None:
- return aFile.getvalue()
- else:
- return None
-
-
-# misc helper functions
-
-def _stripGlyphXMLTree(nodes):
- for element, attrs, children in nodes:
- # "lib" is formatted as a plist, so we need unstripped
- # character data so we can support strings with leading or
- # trailing whitespace. Do strip everything else.
- recursive = (element != "lib")
- stripCharacterData(children, recursive=recursive)
-
-
-def _glifTreeFromFile(aFile):
- try:
- tree = buildTree(aFile, stripData=False)
- stripCharacterData(tree[2], recursive=False)
- assert tree[0] == "glyph"
- _stripGlyphXMLTree(tree[2])
- return tree
- except:
- print "Problem with glif file", aFile
- raise
- return None
-
-
-def _relaxedSetattr(object, attr, value):
- try:
- setattr(object, attr, value)
- except AttributeError:
- pass
-
-
-def _number(s):
- """Given a numeric string, return an integer or a float, whichever
- the string indicates. _number("1") will return the integer 1,
- _number("1.0") will return the float 1.0.
- """
- try:
- n = int(s)
- except ValueError:
- n = float(s)
- return n
-
-
-
-def _readGlyphFromTree(tree, glyphObject=None, pointPen=None):
- unicodes = []
- assert tree[0] == "glyph"
- formatVersion = int(tree[1].get("format", "0"))
- if formatVersion not in (0, 1):
- raise GlifLibError, "unsupported glif format version: %s" % formatVersion
- glyphName = tree[1].get("name")
- if glyphName and glyphObject is not None:
- _relaxedSetattr(glyphObject, "name", glyphName)
- for element, attrs, children in tree[2]:
- if element == "outline":
- if pointPen is not None:
- if formatVersion == 0:
- buildOutline_Format0(pointPen, children)
- else:
- buildOutline_Format1(pointPen, children)
- elif glyphObject is None:
- continue
- elif element == "advance":
- width = _number(attrs["width"])
- _relaxedSetattr(glyphObject, "width", width)
- elif element == "unicode":
- unicodes.append(int(attrs["hex"], 16))
- elif element == "note":
- rawNote = "\n".join(children)
- lines = rawNote.split("\n")
- lines = [line.strip() for line in lines]
- note = "\n".join(lines)
- _relaxedSetattr(glyphObject, "note", note)
- elif element == "lib":
- from plistFromTree import readPlistFromTree
- assert len(children) == 1
- lib = readPlistFromTree(children[0])
- _relaxedSetattr(glyphObject, "lib", lib)
- if unicodes:
- _relaxedSetattr(glyphObject, "unicodes", unicodes)
-
-
-class _DoneParsing(Exception): pass
-
-def _startElementHandler(tagName, attrs):
- if tagName != "glyph":
- # the top level element of any .glif file must be <glyph>
- raise _DoneParsing(None)
- glyphName = attrs["name"]
- raise _DoneParsing(glyphName)
-
-def _fetchGlyphName(glyphPath):
- # Given a path to an existing .glif file, get the glyph name
- # from the XML data.
- from xml.parsers.expat import ParserCreate
-
- p = ParserCreate()
- p.StartElementHandler = _startElementHandler
- p.returns_unicode = True
- f = open(glyphPath)
- try:
- p.ParseFile(f)
- except _DoneParsing, why:
- glyphName = why.args[0]
- if glyphName is None:
- raise ValueError, (".glif file doen't have a <glyph> top-level "
- "element: %r" % glyphPath)
- else:
- assert 0, "it's not expected that parsing the file ends normally"
- return glyphName
-
-
-def _fetchUnicodes(glyphPath):
- # Given a path to an existing .glif file, get a list of all
- # unicode values from the XML data.
- # NOTE: this assumes .glif files written by glifLib, since
- # we simply stop parsing as soon as we see anything else than
- # <glyph>, <advance> or <unicode>. glifLib always writes those
- # elements in that order, before anything else.
- from xml.parsers.expat import ParserCreate
-
- unicodes = []
- def _startElementHandler(tagName, attrs, _unicodes=unicodes):
- if tagName == "unicode":
- _unicodes.append(int(attrs["hex"], 16))
- elif tagName not in ("glyph", "advance"):
- raise _DoneParsing()
-
- p = ParserCreate()
- p.StartElementHandler = _startElementHandler
- p.returns_unicode = True
- f = open(glyphPath)
- try:
- p.ParseFile(f)
- except _DoneParsing:
- pass
- return unicodes
-
-
-def buildOutline_Format0(pen, xmlNodes):
- # This reads the "old" .glif format, retroactively named "format 0",
- # later formats have a "format" attribute in the <glyph> element.
- for element, attrs, children in xmlNodes:
- if element == "contour":
- pen.beginPath()
- currentSegmentType = None
- for subElement, attrs, dummy in children:
- if subElement != "point":
- continue
- x = _number(attrs["x"])
- y = _number(attrs["y"])
- pointType = attrs.get("type", "onCurve")
- if pointType == "bcp":
- currentSegmentType = "curve"
- elif pointType == "offCurve":
- currentSegmentType = "qcurve"
- elif currentSegmentType is None and pointType == "onCurve":
- currentSegmentType = "line"
- if pointType == "onCurve":
- segmentType = currentSegmentType
- currentSegmentType = None
- else:
- segmentType = None
- smooth = attrs.get("smooth") == "yes"
- pen.addPoint((x, y), segmentType=segmentType, smooth=smooth)
- pen.endPath()
- elif element == "component":
- baseGlyphName = attrs["base"]
- transformation = []
- for attr, default in _transformationInfo:
- value = attrs.get(attr)
- if value is None:
- value = default
- else:
- value = _number(value)
- transformation.append(value)
- pen.addComponent(baseGlyphName, tuple(transformation))
- elif element == "anchor":
- name, x, y = attrs["name"], _number(attrs["x"]), _number(attrs["y"])
- pen.beginPath()
- pen.addPoint((x, y), segmentType="move", name=name)
- pen.endPath()
-
-
-def buildOutline_Format1(pen, xmlNodes):
- for element, attrs, children in xmlNodes:
- if element == "contour":
- pen.beginPath()
- for subElement, attrs, dummy in children:
- if subElement != "point":
- continue
- x = _number(attrs["x"])
- y = _number(attrs["y"])
- segmentType = attrs.get("type", "offcurve")
- if segmentType == "offcurve":
- segmentType = None
- smooth = attrs.get("smooth") == "yes"
- name = attrs.get("name")
- pen.addPoint((x, y), segmentType=segmentType, smooth=smooth, name=name)
- pen.endPath()
- elif element == "component":
- baseGlyphName = attrs["base"]
- transformation = []
- for attr, default in _transformationInfo:
- value = attrs.get(attr)
- if value is None:
- value = default
- else:
- value = _number(value)
- transformation.append(value)
- pen.addComponent(baseGlyphName, tuple(transformation))
-
-
-_transformationInfo = [
- # field name, default value
- ("xScale", 1),
- ("xyScale", 0),
- ("yxScale", 0),
- ("yScale", 1),
- ("xOffset", 0),
- ("yOffset", 0),
-]
-
-class GLIFPointPen(AbstractPointPen):
-
- """Helper class using the PointPen protocol to write the <outline>
- part of .glif files.
- """
-
- def __init__(self, xmlWriter):
- self.writer = xmlWriter
-
- def beginPath(self):
- self.writer.begintag("contour")
- self.writer.newline()
-
- def endPath(self):
- self.writer.endtag("contour")
- self.writer.newline()
-
- def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs):
- attrs = []
- if pt is not None:
- for coord in pt:
- if not isinstance(coord, (int, float)):
- raise GlifLibError, "coordinates must be int or float"
- attrs.append(("x", repr(pt[0])))
- attrs.append(("y", repr(pt[1])))
- if segmentType is not None:
- attrs.append(("type", segmentType))
- if smooth:
- attrs.append(("smooth", "yes"))
- if name is not None:
- attrs.append(("name", name))
- self.writer.simpletag("point", attrs)
- self.writer.newline()
-
- def addComponent(self, glyphName, transformation):
- attrs = [("base", glyphName)]
- for (attr, default), value in zip(_transformationInfo, transformation):
- if not isinstance(value, (int, float)):
- raise GlifLibError, "transformation values must be int or float"
- if value != default:
- attrs.append((attr, repr(value)))
- self.writer.simpletag("component", attrs)
- self.writer.newline()
-
-
-if __name__ == "__main__":
- from pprint import pprint
- from robofab.pens.pointPen import PrintingPointPen
- class TestGlyph: pass
- gs = GlyphSet(".")
- def drawPoints(pen):
- pen.beginPath()
- pen.addPoint((100, 200), name="foo")
- pen.addPoint((200, 250), segmentType="curve", smooth=True)
- pen.endPath()
- pen.addComponent("a", (1, 0, 0, 1, 20, 30))
- glyph = TestGlyph()
- glyph.width = 120
- glyph.unicodes = [1, 2, 3, 43215, 66666]
- glyph.lib = {"a": "b", "c": [1, 2, 3, True]}
- glyph.note = " hallo! "
- if 0:
- gs.writeGlyph("a", glyph, drawPoints)
- g2 = TestGlyph()
- gs.readGlyph("a", g2, PrintingPointPen())
- pprint(g2.__dict__)
- else:
- s = writeGlyphToString("a", glyph, drawPoints)
- print s
- g2 = TestGlyph()
- readGlyphFromString(s, g2, PrintingPointPen())
- pprint(g2.__dict__)
-
diff --git a/misc/pylib/robofab/glifLib2.py b/misc/pylib/robofab/glifLib2.py
deleted file mode 100755
index a3c3c92d3..000000000
--- a/misc/pylib/robofab/glifLib2.py
+++ /dev/null
@@ -1,747 +0,0 @@
-# -*- coding: utf-8 -*-
-"""glifLib.py -- Generic module for reading and writing the .glif format.
-
-More info about the .glif format (GLyphInterchangeFormat) can be found here:
-
- http://unifiedfontobject.org
-
-The main class in this module is GlyphSet. It manages a set of .glif files
-in a folder. It offers two ways to read glyph data, and one way to write
-glyph data. See the class doc string for details.
-"""
-
-__all__ = ["GlyphSet", "GlifLibError",
- "readGlyphFromString", "writeGlyphToString",
- "glyphNameToFileName"]
-
-import os
-from robofab.xmlTreeBuilder import buildTree, stripCharacterData
-from robofab.pens.pointPen import AbstractPointPen
-from cStringIO import StringIO
-
-
-class GlifLibError(Exception): pass
-
-
-if os.name == "mac":
- WRITE_MODE = "wb" # use unix line endings, even with Classic MacPython
- READ_MODE = "rb"
-else:
- WRITE_MODE = "w"
- READ_MODE = "r"
-
-
-class Glyph:
-
- """Minimal glyph object. It has no glyph attributes until either
- the draw() or the drawPoint() method has been called.
- """
-
- def __init__(self, glyphName, glyphSet):
- self.glyphName = glyphName
- self.glyphSet = glyphSet
-
- def draw(self, pen):
- """Draw this glyph onto a *FontTools* Pen."""
- from robofab.pens.adapterPens import PointToSegmentPen
- pointPen = PointToSegmentPen(pen)
- self.drawPoints(pointPen)
-
- def drawPoints(self, pointPen):
- """Draw this glyph onto a PointPen."""
- self.glyphSet.readGlyph(self.glyphName, self, pointPen)
-
-
-def glyphNameToFileName(glyphName, glyphSet):
- """Default algorithm for making a file name out of a glyph name.
- This one has limited support for case insensitive file systems:
- it assumes glyph names are not case sensitive apart from the first
- character:
- 'a' -> 'a.glif'
- 'A' -> 'A_.glif'
- 'A.alt' -> 'A_.alt.glif'
- 'A.Alt' -> 'A_.Alt.glif'
- 'T_H' -> 'T__H_.glif'
- 'T_h' -> 'T__h.glif'
- 't_h' -> 't_h.glif'
- 'F_F_I' -> 'F__F__I_.glif'
- 'f_f_i' -> 'f_f_i.glif'
-
- """
- if glyphName.startswith("."):
- # some OSes consider filenames such as .notdef "hidden"
- glyphName = "_" + glyphName[1:]
- parts = glyphName.split(".")
- if parts[0].find("_")!=-1:
- # it is a compound name, check the separate parts
- bits = []
- for p in parts[0].split("_"):
- if p != p.lower():
- bits.append(p+"_")
- continue
- bits.append(p)
- parts[0] = "_".join(bits)
- else:
- # it is a single name
- if parts[0] != parts[0].lower():
- parts[0] += "_"
- for i in range(1, len(parts)):
- # resolve additional, period separated parts, like alt / Alt
- if parts[i] != parts[i].lower():
- parts[i] += "_"
- return ".".join(parts) + ".glif"
-
-
-class GlyphSet:
-
- """GlyphSet manages a set of .glif files inside one directory.
-
- GlyphSet's constructor takes a path to an existing directory as it's
- first argument. Reading glyph data can either be done through the
- readGlyph() method, or by using GlyphSet's dictionary interface, where
- the keys are glyph names and the values are (very) simple glyph objects.
-
- To write a glyph to the glyph set, you use the writeGlyph() method.
- The simple glyph objects returned through the dict interface do not
- support writing, they are just a convenient way to get at the glyph data.
- """
-
- glyphClass = Glyph
-
- def __init__(self, dirName, glyphNameToFileNameFunc=None):
- """'dirName' should be a path to an existing directory.
-
- The optional 'glyphNameToFileNameFunc' argument must be a callback
- function that takes two arguments: a glyph name and the GlyphSet
- instance. It should return a file name (including the .glif
- extension). The glyphNameToFileName function is called whenever
- a file name is created for a given glyph name.
- """
- self.dirName = dirName
- if glyphNameToFileNameFunc is None:
- glyphNameToFileNameFunc = glyphNameToFileName
- self.glyphNameToFileName = glyphNameToFileNameFunc
- self.contents = self._findContents()
- self._reverseContents = None
- self._glifCache = {}
-
- def rebuildContents(self):
- """Rebuild the contents dict by checking what glyphs are available
- on disk.
- """
- self.contents = self._findContents(forceRebuild=True)
- self._reverseContents = None
-
- def getReverseContents(self):
- """Return a reversed dict of self.contents, mapping file names to
- glyph names. This is primarily an aid for custom glyph name to file
- name schemes that want to make sure they don't generate duplicate
- file names. The file names are converted to lowercase so we can
- reliably check for duplicates that only differ in case, which is
- important for case-insensitive file systems.
- """
- if self._reverseContents is None:
- d = {}
- for k, v in self.contents.iteritems():
- d[v.lower()] = k
- self._reverseContents = d
- return self._reverseContents
-
- def writeContents(self):
- """Write the contents.plist file out to disk. Call this method when
- you're done writing glyphs.
- """
- from plistlib import writePlistToString
- contentsPath = os.path.join(self.dirName, "contents.plist")
- # We need to force Unix line endings, even in OS9 MacPython in FL,
- # so we do the writing to file ourselves.
- plist = writePlistToString(self.contents)
- f = open(contentsPath, WRITE_MODE)
- f.write(plist)
- f.close()
-
- # read caching
-
- def getGLIF(self, glyphName):
- """Get the raw GLIF text for a given glyph name. This only works
- for GLIF files that are already on disk.
-
- This method is useful in situations when the raw XML needs to be
- read from a glyph set for a particular glyph before fully parsing
- it into an object structure via the readGlyph method.
-
- Internally, this method will load a GLIF the first time it is
- called and then cache it. The next time this method is called
- the GLIF will be pulled from the cache if the file's modification
- time has not changed since the GLIF was cached. For memory
- efficiency, the cached GLIF will be purged by various other methods
- such as readGlyph.
- """
- needRead = False
- fileName = self.contents.get(glyphName)
- path = None
- if fileName is not None:
- path = os.path.join(self.dirName, fileName)
- if glyphName not in self._glifCache:
- needRead = True
- elif fileName is not None and os.path.getmtime(path) != self._glifCache[glyphName][1]:
- needRead = True
- if needRead:
- fileName = self.contents[glyphName]
- if not os.path.exists(path):
- raise KeyError, glyphName
- f = open(path, "rb")
- text = f.read()
- f.close()
- self._glifCache[glyphName] = (text, os.path.getmtime(path))
- return self._glifCache[glyphName][0]
-
- def _purgeCachedGLIF(self, glyphName):
- if glyphName in self._glifCache:
- del self._glifCache[glyphName]
-
- # reading/writing API
-
- def readGlyph(self, glyphName, glyphObject=None, pointPen=None):
- """Read a .glif file for 'glyphName' from the glyph set. The
- 'glyphObject' argument can be any kind of object (even None);
- the readGlyph() method will attempt to set the following
- attributes on it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional, in two ways:
- 1) An attribute *won't* be set if the .glif file doesn't
- contain data for it. 'glyphObject' will have to deal
- with default values itself.
- 2) If setting the attribute fails with an AttributeError
- (for example if the 'glyphObject' attribute is read-
- only), readGlyph() will not propagate that exception,
- but ignore that attribute.
-
- To retrieve outline information, you need to pass an object
- conforming to the PointPen protocol as the 'pointPen' argument.
- This argument may be None if you don't need the outline data.
-
- readGlyph() will raise KeyError if the glyph is not present in
- the glyph set.
- """
- text = self.getGLIF(glyphName)
- self._purgeCachedGLIF(glyphName)
- tree = _glifTreeFromFile(StringIO(text))
- _readGlyphFromTree(tree, glyphObject, pointPen)
-
- def writeGlyph(self, glyphName, glyphObject=None, drawPointsFunc=None):
- """Write a .glif file for 'glyphName' to the glyph set. The
- 'glyphObject' argument can be any kind of object (even None);
- the writeGlyph() method will attempt to get the following
- attributes from it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional: if 'glyphObject' doesn't
- have the attribute, it will simply be skipped.
-
- To write outline data to the .glif file, writeGlyph() needs
- a function (any callable object actually) that will take one
- argument: an object that conforms to the PointPen protocol.
- The function will be called by writeGlyph(); it has to call the
- proper PointPen methods to transfer the outline to the .glif file.
- """
- self._purgeCachedGLIF(glyphName)
- data = writeGlyphToString(glyphName, glyphObject, drawPointsFunc)
- fileName = self.contents.get(glyphName)
- if fileName is None:
- fileName = self.glyphNameToFileName(glyphName, self)
- self.contents[glyphName] = fileName
- if self._reverseContents is not None:
- self._reverseContents[fileName.lower()] = glyphName
- path = os.path.join(self.dirName, fileName)
- if os.path.exists(path):
- f = open(path, READ_MODE)
- oldData = f.read()
- f.close()
- if data == oldData:
- return
- f = open(path, WRITE_MODE)
- f.write(data)
- f.close()
-
- def deleteGlyph(self, glyphName):
- """Permanently delete the glyph from the glyph set on disk. Will
- raise KeyError if the glyph is not present in the glyph set.
- """
- self._purgeCachedGLIF(glyphName)
- fileName = self.contents[glyphName]
- os.remove(os.path.join(self.dirName, fileName))
- if self._reverseContents is not None:
- del self._reverseContents[self.contents[glyphName].lower()]
- del self.contents[glyphName]
-
- # dict-like support
-
- def keys(self):
- return self.contents.keys()
-
- def has_key(self, glyphName):
- return glyphName in self.contents
-
- __contains__ = has_key
-
- def __len__(self):
- return len(self.contents)
-
- def __getitem__(self, glyphName):
- if glyphName not in self.contents:
- raise KeyError, glyphName
- return self.glyphClass(glyphName, self)
-
- # quickly fetching unicode values
-
- def getUnicodes(self):
- """Return a dictionary that maps all glyph names to lists containing
- the unicode value[s] for that glyph, if any. This parses the .glif
- files partially, so is a lot faster than parsing all files completely.
- """
- unicodes = {}
- for glyphName in self.contents.keys():
- text = self.getGLIF(glyphName)
- unicodes[glyphName] = _fetchUnicodes(text)
- return unicodes
-
- # internal methods
-
- def _findContents(self, forceRebuild=False):
- contentsPath = os.path.join(self.dirName, "contents.plist")
- if forceRebuild or not os.path.exists(contentsPath):
- fileNames = os.listdir(self.dirName)
- fileNames = [n for n in fileNames if n.endswith(".glif")]
- contents = {}
- for n in fileNames:
- glyphPath = os.path.join(self.dirName, n)
- contents[_fetchGlyphName(glyphPath)] = n
- else:
- from plistlib import readPlist
- contents = readPlist(contentsPath)
- return contents
-
-
-def readGlyphFromString(aString, glyphObject=None, pointPen=None):
- """Read .glif data from a string into a glyph object.
-
- The 'glyphObject' argument can be any kind of object (even None);
- the readGlyphFromString() method will attempt to set the following
- attributes on it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional, in two ways:
- 1) An attribute *won't* be set if the .glif file doesn't
- contain data for it. 'glyphObject' will have to deal
- with default values itself.
- 2) If setting the attribute fails with an AttributeError
- (for example if the 'glyphObject' attribute is read-
- only), readGlyphFromString() will not propagate that
- exception, but ignore that attribute.
-
- To retrieve outline information, you need to pass an object
- conforming to the PointPen protocol as the 'pointPen' argument.
- This argument may be None if you don't need the outline data.
- """
- tree = _glifTreeFromFile(StringIO(aString))
- _readGlyphFromTree(tree, glyphObject, pointPen)
-
-
-def writeGlyphToString(glyphName, glyphObject=None, drawPointsFunc=None, writer=None):
- """Return .glif data for a glyph as a UTF-8 encoded string.
- The 'glyphObject' argument can be any kind of object (even None);
- the writeGlyphToString() method will attempt to get the following
- attributes from it:
- "width" the advance with of the glyph
- "unicodes" a list of unicode values for this glyph
- "note" a string
- "lib" a dictionary containing custom data
-
- All attributes are optional: if 'glyphObject' doesn't
- have the attribute, it will simply be skipped.
-
- To write outline data to the .glif file, writeGlyphToString() needs
- a function (any callable object actually) that will take one
- argument: an object that conforms to the PointPen protocol.
- The function will be called by writeGlyphToString(); it has to call the
- proper PointPen methods to transfer the outline to the .glif file.
- """
- if writer is None:
- try:
- from xmlWriter import XMLWriter
- except ImportError:
- # try the other location
- from fontTools.misc.xmlWriter import XMLWriter
- aFile = StringIO()
- writer = XMLWriter(aFile, encoding="UTF-8")
- else:
- aFile = None
- writer.begintag("glyph", [("name", glyphName), ("format", "1")])
- writer.newline()
-
- width = getattr(glyphObject, "width", None)
- if width is not None:
- if not isinstance(width, (int, float)):
- raise GlifLibError, "width attribute must be int or float"
- writer.simpletag("advance", width=repr(width))
- writer.newline()
-
- unicodes = getattr(glyphObject, "unicodes", None)
- if unicodes:
- if isinstance(unicodes, int):
- unicodes = [unicodes]
- for code in unicodes:
- if not isinstance(code, int):
- raise GlifLibError, "unicode values must be int"
- hexCode = hex(code)[2:].upper()
- if len(hexCode) < 4:
- hexCode = "0" * (4 - len(hexCode)) + hexCode
- writer.simpletag("unicode", hex=hexCode)
- writer.newline()
-
- note = getattr(glyphObject, "note", None)
- if note is not None:
- if not isinstance(note, (str, unicode)):
- raise GlifLibError, "note attribute must be str or unicode"
- note = note.encode('utf-8')
- writer.begintag("note")
- writer.newline()
- for line in note.splitlines():
- writer.write(line.strip())
- writer.newline()
- writer.endtag("note")
- writer.newline()
-
- if drawPointsFunc is not None:
- writer.begintag("outline")
- writer.newline()
- pen = GLIFPointPen(writer)
- drawPointsFunc(pen)
- writer.endtag("outline")
- writer.newline()
-
- lib = getattr(glyphObject, "lib", None)
- if lib:
- from robofab.plistlib import PlistWriter
- if not isinstance(lib, dict):
- lib = dict(lib)
- writer.begintag("lib")
- writer.newline()
- plistWriter = PlistWriter(writer.file, indentLevel=writer.indentlevel,
- indent=writer.indentwhite, writeHeader=False)
- plistWriter.writeValue(lib)
- writer.endtag("lib")
- writer.newline()
-
- writer.endtag("glyph")
- writer.newline()
- if aFile is not None:
- return aFile.getvalue()
- else:
- return None
-
-
-# misc helper functions
-
-def _stripGlyphXMLTree(nodes):
- for element, attrs, children in nodes:
- # "lib" is formatted as a plist, so we need unstripped
- # character data so we can support strings with leading or
- # trailing whitespace. Do strip everything else.
- recursive = (element != "lib")
- stripCharacterData(children, recursive=recursive)
-
-
-def _glifTreeFromFile(aFile):
- tree = buildTree(aFile, stripData=False)
- stripCharacterData(tree[2], recursive=False)
- assert tree[0] == "glyph"
- _stripGlyphXMLTree(tree[2])
- return tree
-
-
-def _relaxedSetattr(object, attr, value):
- try:
- setattr(object, attr, value)
- except AttributeError:
- pass
-
-
-def _number(s):
- """Given a numeric string, return an integer or a float, whichever
- the string indicates. _number("1") will return the integer 1,
- _number("1.0") will return the float 1.0.
- """
- try:
- n = int(s)
- except ValueError:
- n = float(s)
- return n
-
-
-
-def _readGlyphFromTree(tree, glyphObject=None, pointPen=None):
- unicodes = []
- assert tree[0] == "glyph"
- formatVersion = int(tree[1].get("format", "0"))
- if formatVersion not in (0, 1):
- raise GlifLibError, "unsupported glif format version: %s" % formatVersion
- glyphName = tree[1].get("name")
- if glyphName and glyphObject is not None:
- _relaxedSetattr(glyphObject, "name", glyphName)
- for element, attrs, children in tree[2]:
- if element == "outline":
- if pointPen is not None:
- if formatVersion == 0:
- buildOutline_Format0(pointPen, children)
- else:
- buildOutline_Format1(pointPen, children)
- elif glyphObject is None:
- continue
- elif element == "advance":
- width = _number(attrs["width"])
- _relaxedSetattr(glyphObject, "width", width)
- elif element == "unicode":
- unicodes.append(int(attrs["hex"], 16))
- elif element == "note":
- rawNote = "\n".join(children)
- lines = rawNote.split("\n")
- lines = [line.strip() for line in lines]
- note = "\n".join(lines)
- _relaxedSetattr(glyphObject, "note", note)
- elif element == "lib":
- from plistFromTree import readPlistFromTree
- assert len(children) == 1
- lib = readPlistFromTree(children[0])
- _relaxedSetattr(glyphObject, "lib", lib)
- if unicodes:
- _relaxedSetattr(glyphObject, "unicodes", unicodes)
-
-
-class _DoneParsing(Exception): pass
-
-def _startElementHandler(tagName, attrs):
- if tagName != "glyph":
- # the top level element of any .glif file must be <glyph>
- raise _DoneParsing(None)
- glyphName = attrs["name"]
- raise _DoneParsing(glyphName)
-
-def _fetchGlyphName(glyphPath):
- # Given a path to an existing .glif file, get the glyph name
- # from the XML data.
- from xml.parsers.expat import ParserCreate
-
- p = ParserCreate()
- p.StartElementHandler = _startElementHandler
- p.returns_unicode = True
- f = open(glyphPath)
- try:
- p.ParseFile(f)
- except _DoneParsing, why:
- glyphName = why.args[0]
- if glyphName is None:
- raise ValueError, (".glif file doen't have a <glyph> top-level "
- "element: %r" % glyphPath)
- else:
- assert 0, "it's not expected that parsing the file ends normally"
- return glyphName
-
-
-def _fetchUnicodes(text):
- # Given GLIF text, get a list of all unicode values from the XML data.
- parser = _FetchUnicodesParser(text)
- return parser.unicodes
-
-class _FetchUnicodesParser(object):
-
- def __init__(self, text):
- from xml.parsers.expat import ParserCreate
- self.unicodes = []
- self._elementStack = []
- parser = ParserCreate()
- parser.returns_unicode = 0 # XXX, Don't remember why. It sucks, though.
- parser.StartElementHandler = self.startElementHandler
- parser.EndElementHandler = self.endElementHandler
- parser.Parse(text)
-
- def startElementHandler(self, name, attrs):
- if name == "unicode" and len(self._elementStack) == 1 and self._elementStack[0] == "glyph":
- value = attrs.get("hex")
- value = int(value, 16)
- self.unicodes.append(value)
- self._elementStack.append(name)
-
- def endElementHandler(self, name):
- other = self._elementStack.pop(-1)
- assert other == name
-
-
-def buildOutline_Format0(pen, xmlNodes):
- # This reads the "old" .glif format, retroactively named "format 0",
- # later formats have a "format" attribute in the <glyph> element.
- for element, attrs, children in xmlNodes:
- if element == "contour":
- pen.beginPath()
- currentSegmentType = None
- for subElement, attrs, dummy in children:
- if subElement != "point":
- continue
- x = _number(attrs["x"])
- y = _number(attrs["y"])
- pointType = attrs.get("type", "onCurve")
- if pointType == "bcp":
- currentSegmentType = "curve"
- elif pointType == "offCurve":
- currentSegmentType = "qcurve"
- elif currentSegmentType is None and pointType == "onCurve":
- currentSegmentType = "line"
- if pointType == "onCurve":
- segmentType = currentSegmentType
- currentSegmentType = None
- else:
- segmentType = None
- smooth = attrs.get("smooth") == "yes"
- pen.addPoint((x, y), segmentType=segmentType, smooth=smooth)
- pen.endPath()
- elif element == "component":
- baseGlyphName = attrs["base"]
- transformation = []
- for attr, default in _transformationInfo:
- value = attrs.get(attr)
- if value is None:
- value = default
- else:
- value = _number(value)
- transformation.append(value)
- pen.addComponent(baseGlyphName, tuple(transformation))
- elif element == "anchor":
- name, x, y = attrs["name"], _number(attrs["x"]), _number(attrs["y"])
- pen.beginPath()
- pen.addPoint((x, y), segmentType="move", name=name)
- pen.endPath()
-
-
-def buildOutline_Format1(pen, xmlNodes):
- for element, attrs, children in xmlNodes:
- if element == "contour":
- pen.beginPath()
- for subElement, attrs, dummy in children:
- if subElement != "point":
- continue
- x = _number(attrs["x"])
- y = _number(attrs["y"])
- segmentType = attrs.get("type", "offcurve")
- if segmentType == "offcurve":
- segmentType = None
- smooth = attrs.get("smooth") == "yes"
- name = attrs.get("name")
- pen.addPoint((x, y), segmentType=segmentType, smooth=smooth, name=name)
- pen.endPath()
- elif element == "component":
- baseGlyphName = attrs["base"]
- transformation = []
- for attr, default in _transformationInfo:
- value = attrs.get(attr)
- if value is None:
- value = default
- else:
- value = _number(value)
- transformation.append(value)
- pen.addComponent(baseGlyphName, tuple(transformation))
-
-
-_transformationInfo = [
- # field name, default value
- ("xScale", 1),
- ("xyScale", 0),
- ("yxScale", 0),
- ("yScale", 1),
- ("xOffset", 0),
- ("yOffset", 0),
-]
-
-class GLIFPointPen(AbstractPointPen):
-
- """Helper class using the PointPen protocol to write the <outline>
- part of .glif files.
- """
-
- def __init__(self, xmlWriter):
- self.writer = xmlWriter
-
- def beginPath(self):
- self.writer.begintag("contour")
- self.writer.newline()
-
- def endPath(self):
- self.writer.endtag("contour")
- self.writer.newline()
-
- def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs):
- attrs = []
- if pt is not None:
- for coord in pt:
- if not isinstance(coord, (int, float)):
- raise GlifLibError, "coordinates must be int or float"
- attrs.append(("x", repr(pt[0])))
- attrs.append(("y", repr(pt[1])))
- if segmentType is not None:
- attrs.append(("type", segmentType))
- if smooth:
- attrs.append(("smooth", "yes"))
- if name is not None:
- attrs.append(("name", name))
- self.writer.simpletag("point", attrs)
- self.writer.newline()
-
- def addComponent(self, glyphName, transformation):
- attrs = [("base", glyphName)]
- for (attr, default), value in zip(_transformationInfo, transformation):
- if not isinstance(value, (int, float)):
- raise GlifLibError, "transformation values must be int or float"
- if value != default:
- attrs.append((attr, repr(value)))
- self.writer.simpletag("component", attrs)
- self.writer.newline()
-
-
-if __name__ == "__main__":
- from pprint import pprint
- from robofab.pens.pointPen import PrintingPointPen
- class TestGlyph: pass
- gs = GlyphSet(".")
- def drawPoints(pen):
- pen.beginPath()
- pen.addPoint((100, 200), name="foo")
- pen.addPoint((200, 250), segmentType="curve", smooth=True)
- pen.endPath()
- pen.addComponent("a", (1, 0, 0, 1, 20, 30))
- glyph = TestGlyph()
- glyph.width = 120
- glyph.unicodes = [1, 2, 3, 43215, 66666]
- glyph.lib = {"a": "b", "c": [1, 2, 3, True]}
- glyph.note = " hallo! "
- if 0:
- gs.writeGlyph("a", glyph, drawPoints)
- g2 = TestGlyph()
- gs.readGlyph("a", g2, PrintingPointPen())
- pprint(g2.__dict__)
- else:
- s = writeGlyphToString("a", glyph, drawPoints)
- print s
- g2 = TestGlyph()
- readGlyphFromString(s, g2, PrintingPointPen())
- pprint(g2.__dict__)
-
diff --git a/misc/pylib/robofab/interface/__init__.py b/misc/pylib/robofab/interface/__init__.py
deleted file mode 100755
index 154c42b70..000000000
--- a/misc/pylib/robofab/interface/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-
-Directory for interface related modules. Stuff like widgets,
-dialog modules. Please keep them sorted by platform.
-
-interfaces/all : modules that are platform independent
-interfaces/mac : modules that are mac specific
-interfaces/win : modules that are windows specific
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/interface/all/__init__.py b/misc/pylib/robofab/interface/all/__init__.py
deleted file mode 100755
index 154c42b70..000000000
--- a/misc/pylib/robofab/interface/all/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-
-Directory for interface related modules. Stuff like widgets,
-dialog modules. Please keep them sorted by platform.
-
-interfaces/all : modules that are platform independent
-interfaces/mac : modules that are mac specific
-interfaces/win : modules that are windows specific
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/interface/all/dialogs.py b/misc/pylib/robofab/interface/all/dialogs.py
deleted file mode 100755
index 8fdfe847b..000000000
--- a/misc/pylib/robofab/interface/all/dialogs.py
+++ /dev/null
@@ -1,278 +0,0 @@
-"""
-
-
- Restructured dialogs for Robofab
-
- dialog file dialogs
-
-* FontLab 5.04 10.6 dialogKit fl internal * theoretically that should work under windows as well, untested
-* FontLab 5.1 10.6 dialogKit fl internal
-* FontLab 5.1 10.7 raw cocao fl internal
- Glyphs any vanilla vanilla
- RoboFont any vanilla vanilla
-
- This module does a fair amount of sniffing in order to guess
- which dialogs to load. Linux and Windows environments are
- underrepresented at the moment. Following the prototypes in dialogs_default.py
- it is possible (with knowledge of the platform) to extend support.
-
- The platformApplicationSupport table contains very specific
- versions with which certain apps need to work. Moving forward,
- it is likely that these versions will change and need to be updated.
-
- # this calls the new dialogs infrastructure:
- from robofab.interface.all.dialogs import Message
-
- # this calls the old original legacy dialogs infrastructure:
- from robofab.interface.all.dialogs_legacy import Message
-
-"""
-
-
-
-# determine platform and application
-import sys, os
-import platform as _platform
-
-__verbose__ = False
-
-platform = None
-platformVersion = None
-application = None
-applicationVersion = None
-
-if sys.platform in (
- 'mac',
- 'darwin',
- ):
- platform = "mac"
- v = _platform.mac_ver()[0]
- platformVersion = float('.'.join(v.split('.')[:2]))
-elif sys.platform in (
- 'linux1',
- 'linux2', # Ubuntu = others?
- ):
- platform = "linux"
-elif os.name == 'nt':
- platform = "win"
-
-# determine application
-
-try:
- # FontLab
- # alternative syntax to cheat on the FL import filtering in RF
- __import__("FL")
- application = "fontlab"
- #applicationVersion = fl.version
-except ImportError:
- pass
-
-if application is None:
- try:
- # RoboFont
- import mojo
- application = 'robofont'
- try:
- from AppKit import NSBundle
- b = NSBundle.mainBundle()
- applicationVersion = b.infoDictionary()["CFBundleVersion"]
- except ImportError:
- pass
- except ImportError:
- pass
-
-if application is None:
- try:
- # Glyphs
- import GlyphsApp
- application = "glyphs"
- except ImportError:
- pass
-
-if application is None:
- try:
- # fontforge
- # note that in some configurations, fontforge can be imported in other pythons as well
- # so the availability of the fontforge module is no garuantee that we are in fontforge.
- import fontforge
- application = "fontforge"
- except ImportError:
- pass
-
-pyVersion = sys.version_info[:3]
-
-# with that out of the way, perhaps we can have a look at where we are
-# and which modules we have available. This maps any number of platform / application
-# combinations so an independent list of module names. That should make it
-# possible to map multiple things to one module.
-
-platformApplicationSupport = [
- #
- # switchboard for platform, application, python version -> dialog implementations
- # platform applicatiom python sub module
- # | | | |
- ('mac', 'fontlab', (2,3,5), "dialogs_fontlab_legacy1"),
- # because FontLab 5.01 and earlier on 2.3.5 can run EasyDialogs
- # | | | |
- # because FontLab 5.1 on mac 10.6 should theoretically be able to run cocoa dialogs,
- # but they are very unreliable. So until we know what's going on, FL5.1 on 10.6
- # is going to have to live with DialogKit dialogs.
- # | | | |
- ('mac', 'fontlab', None, "dialogs_fontlab_legacy2"),
- # because FontLab 5.1 on mac, 10.7+ should run cocoa / vanilla
- # | | | |
- ('mac', None, None, "dialogs_mac_vanilla"),
- # perhaps nonelab scripts can run vanilla as well?
- # | | | |
- ('win', None, None, "dialogs_legacy"),
- # older windows stuff might be able to use the legacy dialogs
-]
-
-platformModule = None
-foundPlatformModule = False
-dialogs = {}
-
-if __verbose__:
- print "robofab.interface.all __init__ - finding out where we were."
-
-# do we have a support module?
-for pl, app, py, platformApplicationModuleName in platformApplicationSupport:
- if __verbose__:
- print "looking at", pl, app, py, platformApplicationModuleName
- if pl is None or pl == platform:
- if app is None or app == application:
- if py is None or py == pyVersion:
- break
- if __verbose__:
- print "nope"
-
-if __verbose__:
- print "searched for", pl, app, py, platformApplicationModuleName
-
-# preload the namespace with default functions that do nothing but raise NotImplementedError
-from robofab.interface.all.dialogs_default import *
-
-# now import the module we selected.
-if platformApplicationModuleName == "dialogs_fontlab_legacy1":
- try:
- from robofab.interface.all.dialogs_fontlab_legacy1 import *
- foundPlatformModule = True
- if __verbose__:
- print "loaded robofab.interface.all.dialogs_fontlab_legacy1"
- if platform == "mac":
- from robofab.interface.mac.getFileOrFolder import GetFile, GetFileOrFolder
- except ImportError:
- print "can't import", platformApplicationModuleName
-
-elif platformApplicationModuleName == "dialogs_fontlab_legacy2":
- try:
- from robofab.interface.all.dialogs_fontlab_legacy2 import *
- foundPlatformModule = True
- if __verbose__:
- print "loaded robofab.interface.all.dialogs_fontlab_legacy2"
- if platform == "mac":
- #
- #
- #
- #
- #
- from robofab.interface.all.dialogs_legacy import AskString, TwoChecks, TwoFields, SelectGlyph, FindGlyph, OneList, SearchList, SelectFont, SelectGlyph
- except ImportError:
- print "can't import", platformApplicationModuleName
-
-elif platformApplicationModuleName == "dialogs_mac_vanilla":
- try:
- from robofab.interface.all.dialogs_mac_vanilla import *
- foundPlatformModule = True
- if __verbose__:
- print "loaded robofab.interface.all.dialogs_mac_vanilla"
- except ImportError:
- print "can't import", platformApplicationModuleName
-
-elif platformApplicationModuleName == "dialogs_legacy":
- try:
- from robofab.interface.all.dialogs_legacy import *
- foundPlatformModule = True
- if __verbose__:
- print "loaded robofab.interface.all.dialogs_legacy"
- except ImportError:
- print "can't import", platformApplicationModuleName
-
-
-__all__ = [
- "AskString",
- "AskYesNoCancel",
- "FindGlyph",
- "GetFile",
- "GetFolder",
- "GetFileOrFolder",
- "Message",
- "OneList",
- "PutFile",
- "SearchList",
- "SelectFont",
- "SelectGlyph",
- "TwoChecks",
- "TwoFields",
- "ProgressBar",
-]
-
-
-def test():
- """ This is a test that prints the available functions and where they're imported from.
- The report can be useful for debugging.
-
- For instance:
-
- from robofab.interface.all.dialogs import test
- test()
-
- testing RoboFab Dialogs:
- python version: (2, 7, 1)
- platform: mac
- application: None
- applicationVersion: None
- platformVersion: 10.7
- looking for module: dialogs_mac_vanilla
- did we find it? True
-
- Available dialogs and source:
- AskString robofab.interface.all.dialogs_mac_vanilla
- AskYesNoCancel robofab.interface.all.dialogs_mac_vanilla
- FindGlyph robofab.interface.all.dialogs_mac_vanilla
- GetFile robofab.interface.all.dialogs_mac_vanilla
- GetFolder robofab.interface.all.dialogs_mac_vanilla
- GetFileOrFolder robofab.interface.all.dialogs_mac_vanilla
- Message robofab.interface.all.dialogs_mac_vanilla
- OneList robofab.interface.all.dialogs_mac_vanilla
- PutFile robofab.interface.all.dialogs_mac_vanilla
- SearchList robofab.interface.all.dialogs_mac_vanilla
- SelectFont robofab.interface.all.dialogs_mac_vanilla
- SelectGlyph robofab.interface.all.dialogs_mac_vanilla
- TwoChecks robofab.interface.all.dialogs_default
- TwoFields robofab.interface.all.dialogs_default
- ProgressBar robofab.interface.all.dialogs_mac_vanilla
-
- """
-
- print
- print "testing RoboFab Dialogs:"
- print "\tpython version:", pyVersion
- print "\tplatform:", platform
- print "\tapplication:", application
- print "\tapplicationVersion:", applicationVersion
- print "\tplatformVersion:", platformVersion
- print "\tlooking for module:", platformApplicationModuleName
- print "\t\tdid we find it?", foundPlatformModule
-
- print
- print "Available dialogs and source:"
- for name in __all__:
- if name in globals().keys():
- print "\t", name, "\t", globals()[name].__module__
- else:
- print "\t", name, "\t not loaded."
-
-if __name__ == "__main__":
- test()
-
diff --git a/misc/pylib/robofab/interface/all/dialogs_default.py b/misc/pylib/robofab/interface/all/dialogs_default.py
deleted file mode 100644
index c513aa9b7..000000000
--- a/misc/pylib/robofab/interface/all/dialogs_default.py
+++ /dev/null
@@ -1,76 +0,0 @@
-"""
-
- Dialog prototypes.
-
- These are loaded before any others. So if a specific platform implementation doesn't
- have all functions, these will make sure a NotImplemtedError is raised.
-
- http://www.robofab.org/tools/dialogs.html
-
-"""
-
-__all__ = [
- "AskString",
- "AskYesNoCancel",
- "FindGlyph",
- "GetFile",
- "GetFolder",
- "GetFileOrFolder",
- "Message",
- "OneList",
- "PutFile",
- "SearchList",
- "SelectFont",
- "SelectGlyph",
- "TwoChecks",
- "TwoFields",
- "ProgressBar",
-]
-
-# start with all the defaults.
-
-def AskString(message, value='', title='RoboFab'):
- raise NotImplementedError
-
-def AskYesNoCancel(message, title='RoboFab', default=0):
- raise NotImplementedError
-
-def FindGlyph(font, message="Search for a glyph:", title='RoboFab'):
- raise NotImplementedError
-
-def GetFile(message=None):
- raise NotImplementedError
-
-def GetFolder(message=None):
- raise NotImplementedError
-
-def GetFileOrFolder(message=None):
- raise NotImplementedError
-
-def Message(message, title='RoboFab'):
- raise NotImplementedError
-
-def OneList(list, message="Select an item:", title='RoboFab'):
- raise PendingDeprecationWarning
-
-def PutFile(message=None, fileName=None):
- raise NotImplementedError
-
-def SearchList(list, message="Select an item:", title='RoboFab'):
- raise NotImplementedError
-
-def SelectFont(message="Select a font:", title='RoboFab'):
- raise NotImplementedError
-
-def SelectGlyph(font, message="Select a glyph:", title='RoboFab'):
- raise NotImplementedError
-
-def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'):
- raise PendingDeprecationWarning
-
-def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
- raise PendingDeprecationWarning
-
-class ProgressBar(object):
- pass
-
diff --git a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py b/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py
deleted file mode 100644
index 73c3d7a15..000000000
--- a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy1.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
-
- Dialogs for FontLab < 5.1.
-
- This one should be loaded for various platforms, using dialogKit
- http://www.robofab.org/tools/dialogs.html
-
-"""
-
-from FL import *
-from dialogKit import ModalDialog, Button, TextBox, EditText
-
-__all__ = [
- #"AskString",
- #"AskYesNoCancel",
- #"FindGlyph",
- "GetFile",
- "GetFolder",
- #"Message",
- #"OneList",
- #"PutFile",
- #"SearchList",
- #"SelectFont",
- #"SelectGlyph",
- #"TwoChecks",
- #"TwoFields",
- "ProgressBar",
-]
-
-
-def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- strFilter = "All Files (*.*)|*.*|"
- defaultExt = ""
- # using fontlab's internal file dialogs
- return fl.GetFileName(1, defaultExt, message, strFilter)
-
-def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False):
- # using fontlab's internal file dialogs
- if message is None:
- message = ""
- return fl.GetPathName(message)
-
-def PutFile(message=None, fileName=None):
- # using fontlab's internal file dialogs
- # message is not used
- if message is None:
- message = ""
- if fileName is None:
- fileName = ""
- defaultExt = ""
- return fl.GetFileName(0, defaultExt, fileName, '')
-
-class ProgressBar(object):
-
- def __init__(self, title="RoboFab...", ticks=0, label=""):
- self._tickValue = 1
- fl.BeginProgress(title, ticks)
-
- def getCurrentTick(self):
- return self._tickValue
-
- def tick(self, tickValue=None):
- if not tickValue:
- tickValue = self._tickValue
- fl.TickProgress(tickValue)
- self._tickValue = tickValue + 1
-
- def label(self, label):
- pass
-
- def close(self):
- fl.EndProgress()
-
diff --git a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py b/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py
deleted file mode 100644
index 460b73f1e..000000000
--- a/misc/pylib/robofab/interface/all/dialogs_fontlab_legacy2.py
+++ /dev/null
@@ -1,373 +0,0 @@
-"""
-
- Dialogs for FontLab 5.1.
- This might work in future versions of FontLab as well.
- This is basically a butchered version of vanilla.dialogs.
- No direct import of, or dependency on Vanilla
-
- March 7 2012
- It seems only the dialogs that deal with the file system
- need to be replaced, the other dialogs still work.
- As we're not entirely sure whether it is worth to maintain
- these dialogs, let's fix the imports in dialogs.py.
-
- This is the phenolic aldehyde version of dialogs.
-
-"""
-
-#__import__("FL")
-from FL import *
-
-from Foundation import NSObject
-from AppKit import NSApplication, NSInformationalAlertStyle, objc, NSAlert, NSAlertFirstButtonReturn, NSAlertSecondButtonReturn, NSAlertThirdButtonReturn, NSSavePanel, NSOKButton, NSOpenPanel
-
-NSApplication.sharedApplication()
-
-__all__ = [
-# "AskString",
- "AskYesNoCancel",
-# "FindGlyph",
- "GetFile",
- "GetFolder",
- "GetFileOrFolder",
- "Message",
-# "OneList",
- "PutFile",
-# "SearchList",
-# "SelectFont",
-# "SelectGlyph",
-# "TwoChecks",
-# "TwoFields",
- "ProgressBar",
-]
-
-
-class BaseMessageDialog(NSObject):
-
- def initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_(self,
- messageText="",
- informativeText="",
- alertStyle=NSInformationalAlertStyle,
- buttonTitlesValues=None,
- parentWindow=None,
- resultCallback=None):
- if buttonTitlesValues is None:
- buttonTitlesValues = []
- self = super(BaseMessageDialog, self).init()
- self.retain()
- self._resultCallback = resultCallback
- self._buttonTitlesValues = buttonTitlesValues
- #
- alert = NSAlert.alloc().init()
- alert.setMessageText_(messageText)
- alert.setInformativeText_(informativeText)
- alert.setAlertStyle_(alertStyle)
- for buttonTitle, value in buttonTitlesValues:
- alert.addButtonWithTitle_(buttonTitle)
- self._value = None
- code = alert.runModal()
- self._translateValue(code)
- return self
-
- def _translateValue(self, code):
- if code == NSAlertFirstButtonReturn:
- value = 1
- elif code == NSAlertSecondButtonReturn:
- value = 2
- elif code == NSAlertThirdButtonReturn:
- value = 3
- else:
- value = code - NSAlertThirdButtonReturn + 3
- self._value = self._buttonTitlesValues[value-1][1]
-
- def windowWillClose_(self, notification):
- self.autorelease()
-
-
-class BasePutGetPanel(NSObject):
-
- def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None):
- self = super(BasePutGetPanel, self).init()
- self.retain()
- self._parentWindow = parentWindow
- self._resultCallback = resultCallback
- return self
-
- def windowWillClose_(self, notification):
- self.autorelease()
-
-
-class PutFilePanel(BasePutGetPanel):
-
- def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None):
- self = super(PutFilePanel, self).initWithWindow_resultCallback_(parentWindow, resultCallback)
- self.messageText = None
- self.title = None
- self.fileTypes = None
- self.directory = None
- self.fileName = None
- self.canCreateDirectories = True
- self.accessoryView = None
- self._result = None
- return self
-
- def run(self):
- panel = NSSavePanel.alloc().init()
- if self.messageText:
- panel.setMessage_(self.messageText)
- if self.title:
- panel.setTitle_(self.title)
- if self.directory:
- panel.setDirectory_(self.directory)
- if self.fileTypes:
- panel.setAllowedFileTypes_(self.fileTypes)
- panel.setCanCreateDirectories_(self.canCreateDirectories)
- panel.setCanSelectHiddenExtension_(True)
- panel.setAccessoryView_(self.accessoryView)
- if self._parentWindow is not None:
- panel.beginSheetForDirectory_file_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
- self.directory, self.fileName, self._parentWindow, self, "savePanelDidEnd:returnCode:contextInfo:", 0)
- else:
- isOK = panel.runModalForDirectory_file_(self.directory, self.fileName)
- if isOK == NSOKButton:
- self._result = panel.filename()
-
- def savePanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context):
- panel.close()
- if returnCode:
- self._result = panel.filename()
- if self._resultCallback is not None:
- self._resultCallback(self._result)
-
- savePanelDidEnd_returnCode_contextInfo_ = objc.selector(savePanelDidEnd_returnCode_contextInfo_, signature="v@:@ii")
-
-
-class GetFileOrFolderPanel(BasePutGetPanel):
-
- def initWithWindow_resultCallback_(self, parentWindow=None, resultCallback=None):
- self = super(GetFileOrFolderPanel, self).initWithWindow_resultCallback_(parentWindow, resultCallback)
- self.messageText = None
- self.title = None
- self.directory = None
- self.fileName = None
- self.fileTypes = None
- self.allowsMultipleSelection = False
- self.canChooseDirectories = True
- self.canChooseFiles = True
- self.resolvesAliases = True
- self._result = None
- return self
-
- def run(self):
- panel = NSOpenPanel.alloc().init()
- if self.messageText:
- panel.setMessage_(self.messageText)
- if self.title:
- panel.setTitle_(self.title)
- if self.directory:
- panel.setDirectory_(self.directory)
- if self.fileTypes:
- panel.setAllowedFileTypes_(self.fileTypes)
- panel.setCanChooseDirectories_(self.canChooseDirectories)
- panel.setCanChooseFiles_(self.canChooseFiles)
- panel.setAllowsMultipleSelection_(self.allowsMultipleSelection)
- panel.setResolvesAliases_(self.resolvesAliases)
- if self._parentWindow is not None:
- panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
- self.directory, self.fileName, self.fileTypes, self._parentWindow, self, "openPanelDidEnd:returnCode:contextInfo:", 0)
- else:
- isOK = panel.runModalForDirectory_file_types_(self.directory, self.fileName, self.fileTypes)
- if isOK == NSOKButton:
- self._result = panel.filenames()
-
- def openPanelDidEnd_returnCode_contextInfo_(self, panel, returnCode, context):
- panel.close()
- if returnCode:
- self._result = panel.filenames()
- if self._resultCallback is not None:
- self._resultCallback(self._result)
-
- openPanelDidEnd_returnCode_contextInfo_ = objc.selector(openPanelDidEnd_returnCode_contextInfo_, signature="v@:@ii")
-
-
-def Message(message="", title='noLongerUsed', informativeText=""):
- """Legacy robofab dialog compatible wrapper."""
- #def _message(messageText="", informativeText="", alertStyle=NSInformationalAlertStyle, parentWindow=None, resultCallback=None):
- resultCallback = None
- alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_(
- messageText=message,
- informativeText=informativeText,
- alertStyle=NSInformationalAlertStyle,
- buttonTitlesValues=[("OK", 1)],
- parentWindow=None,
- resultCallback=None)
- if resultCallback is None:
- return 1
-
-
-def AskYesNoCancel(message, title='noLongerUsed', default=None, informativeText=""):
- """
- AskYesNoCancel Dialog
-
- message the string
- title* a title of the window
- (may not be supported everywhere)
- default* index number of which button should be default
- (i.e. respond to return)
- informativeText* A string with secundary information
-
- * may not be supported everywhere
- """
- parentWindow = None
- alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_(
- messageText=message,
- informativeText=informativeText,
- alertStyle=NSInformationalAlertStyle,
- buttonTitlesValues=[("Cancel", -1), ("Yes", 1), ("No", 0)],
- parentWindow=None,
- resultCallback=None)
- return alert._value
-
-def _askYesNo(messageText="", informativeText="", alertStyle=NSInformationalAlertStyle, parentWindow=None, resultCallback=None):
- parentWindow = None
- alert = BaseMessageDialog.alloc().initWithMessageText_informativeText_alertStyle_buttonTitlesValues_window_resultCallback_(
- messageText=messageText, informativeText=informativeText, alertStyle=alertStyle, buttonTitlesValues=[("Yes", 1), ("No", 0)], parentWindow=parentWindow, resultCallback=resultCallback)
- if resultCallback is None:
- return alert._value
-
-def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- """ Legacy robofab dialog compatible wrapper.
- This will select UFO on OSX 10.7, FL5.1
- """
- parentWindow = None
- resultCallback=None
- basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback)
- basePanel.messageText = message
- basePanel.title = title
- basePanel.directory = directory
- basePanel.fileName = fileName
- basePanel.fileTypes = fileTypes
- basePanel.allowsMultipleSelection = allowsMultipleSelection
- basePanel.canChooseDirectories = False
- basePanel.canChooseFiles = True
- basePanel.run()
- if basePanel._result is None:
- return None
- if not allowsMultipleSelection:
- # compatibly return only one as we expect
- return str(list(basePanel._result)[0])
- else:
- # return more if we explicitly expect
- return [str(n) for n in list(basePanel._result)]
-
-def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False):
- parentWindow = None
- resultCallback = None
- basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback)
- basePanel.messageText = message
- basePanel.title = title
- basePanel.directory = directory
- basePanel.allowsMultipleSelection = allowsMultipleSelection
- basePanel.canChooseDirectories = True
- basePanel.canChooseFiles = False
- basePanel.run()
- if basePanel._result is None:
- return None
- if not allowsMultipleSelection:
- # compatibly return only one as we expect
- return str(list(basePanel._result)[0])
- else:
- # return more if we explicitly expect
- return [str(n) for n in list(basePanel._result)]
-
-def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None, parentWindow=None, resultCallback=None):
- parentWindow = None
- basePanel = GetFileOrFolderPanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback)
- basePanel.messageText = message
- basePanel.title = title
- basePanel.directory = directory
- basePanel.fileName = fileName
- basePanel.fileTypes = fileTypes
- basePanel.allowsMultipleSelection = allowsMultipleSelection
- basePanel.canChooseDirectories = True
- basePanel.canChooseFiles = True
- basePanel.run()
- if basePanel._result is None:
- return None
- if not allowsMultipleSelection:
- # compatibly return only one as we expect
- return str(list(basePanel._result)[0])
- else:
- # return more if we explicitly expect
- return [str(n) for n in list(basePanel._result)]
-
-def PutFile(message=None, title=None, directory=None, fileName=None, canCreateDirectories=True, fileTypes=None):
- parentWindow = None
- resultCallback=None
- accessoryView=None
- basePanel = PutFilePanel.alloc().initWithWindow_resultCallback_(parentWindow, resultCallback)
- basePanel.messageText = message
- basePanel.title = title
- basePanel.directory = directory
- basePanel.fileName = fileName
- basePanel.fileTypes = fileTypes
- basePanel.canCreateDirectories = canCreateDirectories
- basePanel.accessoryView = accessoryView
- basePanel.run()
- return str(basePanel._result)
-
-
-class ProgressBar(object):
-
- def __init__(self, title="RoboFab...", ticks=0, label=""):
- self._tickValue = 1
- fl.BeginProgress(title, ticks)
-
- def getCurrentTick(self):
- return self._tickValue
-
- def tick(self, tickValue=None):
- if not tickValue:
- tickValue = self._tickValue
- fl.TickProgress(tickValue)
- self._tickValue = tickValue + 1
-
- def label(self, label):
- pass
-
- def close(self):
- fl.EndProgress()
-
-
-# we seem to have problems importing from here.
-# so let's see what happens if we make the robofab compatible wrappers here as well.
-
-# start with all the defaults.
-
-#def AskString(message, value='', title='RoboFab'):
-# raise NotImplementedError
-
-#def FindGlyph(aFont, message="Search for a glyph:", title='RoboFab'):
-# raise NotImplementedError
-
-#def OneList(list, message="Select an item:", title='RoboFab'):
-# raise NotImplementedError
-
-#def PutFile(message=None, fileName=None):
-# raise NotImplementedError
-
-#def SearchList(list, message="Select an item:", title='RoboFab'):
-# raise NotImplementedError
-
-#def SelectFont(message="Select a font:", title='RoboFab'):
-# raise NotImplementedError
-
-#def SelectGlyph(font, message="Select a glyph:", title='RoboFab'):
-# raise NotImplementedError
-
-#def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'):
-# raise NotImplementedError
-
-#def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
-# raise NotImplementedError
-
diff --git a/misc/pylib/robofab/interface/all/dialogs_legacy.py b/misc/pylib/robofab/interface/all/dialogs_legacy.py
deleted file mode 100755
index 8223a7d64..000000000
--- a/misc/pylib/robofab/interface/all/dialogs_legacy.py
+++ /dev/null
@@ -1,737 +0,0 @@
-
-"""
-
-Dialogs.
-Cross-platform and cross-application compatible. Some of them anyway.
-(Not all dialogs work on PCs outside of FontLab. Some dialogs are for FontLab only. Sorry.)
-
-Mac and FontLab implementation written by the RoboFab development team.
-PC implementation by Eigi Eigendorf and is (C)2002 Eigi Eigendorf.
-
-"""
-
-import os
-import sys
-from robofab import RoboFabError
-from warnings import warn
-
-MAC = False
-PC = False
-haveMacfs = False
-
-if sys.platform in ('mac', 'darwin'):
- MAC = True
-elif os.name == 'nt':
- PC = True
-else:
- warn("dialogs.py only supports Mac and PC platforms.")
-pyVersion = sys.version_info[:3]
-
-inFontLab = False
-try:
- from FL import *
- inFontLab = True
-except ImportError: pass
-
-
-try:
- import W
- hasW = True
-except ImportError:
- hasW = False
-
-try:
- import dialogKit
- hasDialogKit = True
-except ImportError:
- hasDialogKit = False
-
-try:
- import EasyDialogs
- hasEasyDialogs = True
-except:
- hasEasyDialogs = False
-
-if MAC:
- if pyVersion < (2, 3, 0):
- import macfs
- haveMacfs = True
-elif PC and not inFontLab:
- from win32com.shell import shell
- import win32ui
- import win32con
-
-
-def _raisePlatformError(dialog):
- """error raiser"""
- if MAC:
- p = 'Macintosh'
- elif PC:
- p = 'PC'
- else:
- p = sys.platform
- raise RoboFabError("%s is not currently available on the %s platform"%(dialog, p))
-
-
-class _FontLabDialogOneList:
- """A one list dialog for FontLab. This class should not be called directly. Use the OneList function."""
-
- def __init__(self, list, message, title='RoboFab'):
- self.message = message
- self.selected = None
- self.list = list
- self.d = Dialog(self)
- self.d.size = Point(250, 250)
- self.d.title = title
- self.d.Center()
- self.d.AddControl(LISTCONTROL, Rect(12, 30, 238, 190), "list", STYLE_LIST, self.message)
- self.list_index = 0
-
- def Run(self):
- return self.d.Run()
-
- def on_cancel(self, code):
- self.selected = None
-
- def on_ok(self, code):
- self.d.GetValue('list')
- # Since FLS v5.2, the GetValue() method of the Dialog() class returns
- # a 'wrong' index value from the specified LISTCONTROL.
- # If the selected index is n, it will return n-1. For example, when
- # the index is 1, it returns 0; when it's 2, it returns 1, and so on.
- # If the selection is empty, FLS v5.2 returns -2, while the old v5.0
- # returned None.
- # See also:
- # - http://forum.fontlab.com/index.php?topic=8807.0
- # - http://forum.fontlab.com/index.php?topic=9003.0
- #
- # Edited based on feedback from Adam Twardoch
- if fl.buildnumber > 4600 and sys.platform == 'win32':
- if self.list_index == -2:
- self.selected = None
- else:
- self.selected = self.list_index + 1
- else:
- self.selected = self.list_index
-
-
-class _FontLabDialogSearchList:
- """A dialog for searching through a list. It contains a text field and a results list FontLab. This class should not be called directly. Use the SearchList function."""
-
- def __init__(self, aList, message, title="RoboFab"):
- self.d = Dialog(self)
- self.d.size = Point(250, 290)
- self.d.title = title
- self.d.Center()
-
- self.message = message
- self._fullContent = aList
- self.possibleHits = list(aList)
- self.possibleHits.sort()
- self.possibleHits_index = 0
- self.entryField = ""
- self.selected = None
-
- self.d.AddControl(STATICCONTROL, Rect(10, 10, 240, 30), "message", STYLE_LABEL, message)
- self.d.AddControl(EDITCONTROL, Rect(10, 30, 240, aAUTO), "entryField", STYLE_EDIT, "")
- self.d.AddControl(LISTCONTROL, Rect(12, 60, 238, 230), "possibleHits", STYLE_LIST, "")
-
-
- def run(self):
- self.d.Run()
-
- def on_entryField(self, code):
- self.d.GetValue("entryField")
- entry = self.entryField
- count = len(entry)
- possibleHits = [
- i for i in self._fullContent
- if len(i) >= count
- and i[:count] == entry
- ]
- possibleHits.sort()
- self.possibleHits = possibleHits
- self.possibleHits_index = 0
- self.d.PutValue("possibleHits")
-
- def on_ok(self, code):
- self.d.GetValue("possibleHits")
- sel = self.possibleHits_index
- if sel == -1:
- self.selected = None
- else:
- self.selected = self.possibleHits[sel]
-
- def on_cancel(self, code):
- self.selected = None
-
-
-class _FontLabDialogTwoFields:
- """A two field dialog for FontLab. This class should not be called directly. Use the TwoFields function."""
-
- def __init__(self, title_1, value_1, title_2, value_2, title='RoboFab'):
- self.d = Dialog(self)
- self.d.size = Point(200, 125)
- self.d.title = title
- self.d.Center()
- self.d.AddControl(EDITCONTROL, Rect(120, 10, aIDENT2, aAUTO), "v1edit", STYLE_EDIT, title_1)
- self.d.AddControl(EDITCONTROL, Rect(120, 40, aIDENT2, aAUTO), "v2edit", STYLE_EDIT, title_2)
- self.v1edit = value_1
- self.v2edit = value_2
-
- def Run(self):
- return self.d.Run()
-
- def on_cancel(self, code):
- self.v1edit = None
- self.v2edit = None
-
- def on_ok(self, code):
- self.d.GetValue("v1edit")
- self.d.GetValue("v2edit")
- self.v1 = self.v1edit
- self.v2 = self.v2edit
-
-class _FontLabDialogTwoChecks:
- """A two check box dialog for FontLab. This class should not be called directly. Use the TwoChecks function."""
-
- def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'):
- self.d = Dialog(self)
- self.d.size = Point(200, 105)
- self.d.title = title
- self.d.Center()
- self.d.AddControl(CHECKBOXCONTROL, Rect(10, 10, aIDENT2, aAUTO), "check1", STYLE_CHECKBOX, title_1)
- self.d.AddControl(CHECKBOXCONTROL, Rect(10, 30, aIDENT2, aAUTO), "check2", STYLE_CHECKBOX, title_2)
- self.check1 = value1
- self.check2 = value2
-
- def Run(self):
- return self.d.Run()
-
- def on_cancel(self, code):
- self.check1 = None
- self.check2 = None
-
- def on_ok(self, code):
- self.d.GetValue("check1")
- self.d.GetValue("check2")
-
-
-class _FontLabDialogAskString:
- """A one simple string prompt dialog for FontLab. This class should not be called directly. Use the GetString function."""
-
- def __init__(self, message, value, title='RoboFab'):
- self.d = Dialog(self)
- self.d.size = Point(350, 130)
- self.d.title = title
- self.d.Center()
- self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, aAUTO), "label", STYLE_LABEL, message)
- self.d.AddControl(EDITCONTROL, Rect(aIDENT, 40, aIDENT, aAUTO), "value", STYLE_EDIT, '')
- self.value=value
-
- def Run(self):
- return self.d.Run()
-
- def on_cancel(self, code):
- self.value = None
-
- def on_ok(self, code):
- self.d.GetValue("value")
-
-class _FontLabDialogMessage:
- """A simple message dialog for FontLab. This class should not be called directly. Use the SimpleMessage function."""
-
- def __init__(self, message, title='RoboFab'):
- self.d = Dialog(self)
- self.d.size = Point(350, 130)
- self.d.title = title
- self.d.Center()
- self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message)
-
- def Run(self):
- return self.d.Run()
-
-class _FontLabDialogGetYesNoCancel:
- """A yes no cancel message dialog for FontLab. This class should not be called directly. Use the YesNoCancel function."""
-
- def __init__(self, message, title='RoboFab'):
- self.d = Dialog(self)
- self.d.size = Point(350, 130)
- self.d.title = title
- self.d.Center()
- self.d.ok = 'Yes'
- self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, 80), "label", STYLE_LABEL, message)
- self.d.AddControl(BUTTONCONTROL, Rect(100, 95, 172, 115), "button", STYLE_BUTTON, "No")
- self.value = 0
-
- def Run(self):
- return self.d.Run()
-
- def on_ok(self, code):
- self.value = 1
-
- def on_cancel(self, code):
- self.value = -1
-
- def on_button(self, code):
- self.value = 0
- self.d.End()
-
-
-class _MacOneListW:
- """A one list dialog for Macintosh. This class should not be called directly. Use the OneList function."""
-
- def __init__(self, list, message='Make a selection'):
- import W
- self.list = list
- self.selected = None
- self.w = W.ModalDialog((200, 240))
- self.w.message = W.TextBox((10, 10, -10, 30), message)
- self.w.list = W.List((10, 35, -10, -50), list)
- self.w.l = W.HorizontalLine((10, -40, -10, 1), 1)
- self.w.cancel = W.Button((10, -30, 87, -10), 'Cancel', self.cancel)
- self.w.ok = W.Button((102, -30, 88, -10), 'OK', self.ok)
- self.w.setdefaultbutton(self.w.ok)
- self.w.bind('cmd.', self.w.cancel.push)
- self.w.open()
-
- def ok(self):
- if len(self.w.list.getselection()) == 1:
- self.selected = self.w.list.getselection()[0]
- self.w.close()
-
- def cancel(self):
- self.selected = None
- self.w.close()
-
-class _MacTwoChecksW:
- """ Version using W """
-
- def __init__(self, title_1, title_2, value1=1, value2=1, title='RoboFab'):
- import W
- self.check1 = value1
- self.check2 = value2
- self.w = W.ModalDialog((200, 100))
- self.w.check1 = W.CheckBox((10, 10, -10, 16), title_1, value=value1)
- self.w.check2 = W.CheckBox((10, 35, -10, 16), title_2, value=value2)
- self.w.l = W.HorizontalLine((10, 60, -10, 1), 1)
- self.w.cancel = W.Button((10, 70, 85, 20), 'Cancel', self.cancel)
- self.w.ok = W.Button((105, 70, 85, 20), 'OK', self.ok)
- self.w.setdefaultbutton(self.w.ok)
- self.w.bind('cmd.', self.w.cancel.push)
- self.w.open()
-
- def ok(self):
- self.check1 = self.w.check1.get()
- self.check2 = self.w.check2.get()
- self.w.close()
-
- def cancel(self):
- self.check1 = None
- self.check2 = None
- self.w.close()
-
-
-class ProgressBar:
- def __init__(self, title='RoboFab...', ticks=0, label=''):
- """
- A progress bar.
- Availability: FontLab, Mac
- """
- self._tickValue = 1
-
- if inFontLab:
- fl.BeginProgress(title, ticks)
- elif MAC and hasEasyDialogs:
- import EasyDialogs
- self._bar = EasyDialogs.ProgressBar(title, maxval=ticks, label=label)
- else:
- _raisePlatformError('Progress')
-
- def getCurrentTick(self):
- return self._tickValue
-
-
- def tick(self, tickValue=None):
- """
- Tick the progress bar.
- Availability: FontLab, Mac
- """
- if not tickValue:
- tickValue = self._tickValue
-
- if inFontLab:
- fl.TickProgress(tickValue)
- elif MAC:
- self._bar.set(tickValue)
- else:
- pass
-
- self._tickValue = tickValue + 1
-
- def label(self, label):
- """
- Set the label on the progress bar.
- Availability: Mac
- """
- if inFontLab:
- pass
- elif MAC:
- self._bar.label(label)
- else:
- pass
-
-
- def close(self):
- """
- Close the progressbar.
- Availability: FontLab, Mac
- """
- if inFontLab:
- fl.EndProgress()
- elif MAC:
- del self._bar
- else:
- pass
-
-
-def SelectFont(message="Select a font:", title='RoboFab'):
- """
- Returns font instance if there is one, otherwise it returns None.
- Availability: FontLab
- """
- from robofab.world import RFont
- if inFontLab:
- list = []
- for i in range(fl.count):
- list.append(fl[i].full_name)
- name = OneList(list, message, title)
- if name is None:
- return None
- else:
- return RFont(fl[list.index(name)])
- else:
- _raisePlatformError('SelectFont')
-
-def SelectGlyph(font, message="Select a glyph:", title='RoboFab'):
- """
- Returns glyph instance if there is one, otherwise it returns None.
- Availability: FontLab
- """
- from fontTools.misc.textTools import caselessSort
-
- if inFontLab:
- tl = font.keys()
- list = caselessSort(tl)
- glyphname = OneList(list, message, title)
- if glyphname is None:
- return None
- else:
- return font[glyphname]
- else:
- _raisePlatformError('SelectGlyph')
-
-def FindGlyph(font, message="Search for a glyph:", title='RoboFab'):
- """
- Returns glyph instance if there is one, otherwise it returns None.
- Availability: FontLab
- """
-
- if inFontLab:
- glyphname = SearchList(font.keys(), message, title)
- if glyphname is None:
- return None
- else:
- return font[glyphname]
- else:
- _raisePlatformError('SelectGlyph')
-
-def OneList(list, message="Select an item:", title='RoboFab'):
- """
- Returns selected item, otherwise it returns None.
- Availability: FontLab, Macintosh
- """
- if inFontLab:
- ol = _FontLabDialogOneList(list, message)
- ol.Run()
- selected = ol.selected
- if selected is None:
- return None
- else:
- try:
- return list[selected]
- except:
- return None
- elif MAC:
- if hasW:
- d = _MacOneListW(list, message)
- sel = d.selected
- if sel is None:
- return None
- else:
- return list[sel]
- else:
- _raisePlatformError('OneList')
- elif PC:
- _raisePlatformError('OneList')
-
-def SearchList(list, message="Select an item:", title='RoboFab'):
- """
- Returns selected item, otherwise it returns None.
- Availability: FontLab
- """
- if inFontLab:
- sl = _FontLabDialogSearchList(list, message, title)
- sl.run()
- selected = sl.selected
- if selected is None:
- return None
- else:
- return selected
- else:
- _raisePlatformError('SearchList')
-
-def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
- """
- Returns (value 1, value 2).
- Availability: FontLab
- """
- if inFontLab:
- tf = _FontLabDialogTwoFields(title_1, value_1, title_2, value_2, title)
- tf.Run()
- try:
- v1 = tf.v1
- v2 = tf.v2
- return (v1, v2)
- except:
- return None
- else:
- _raisePlatformError('TwoFields')
-
-def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'):
- """
- Returns check value:
- 1 if check box 1 is checked
- 2 if check box 2 is checked
- 3 if both are checked
- 0 if neither are checked
- None if cancel is clicked.
-
- Availability: FontLab, Macintosh
- """
- tc = None
- if inFontLab:
- tc = _FontLabDialogTwoChecks(title_1, title_2, value1, value2, title)
- tc.Run()
- elif MAC:
- if hasW:
- tc = _MacTwoChecksW(title_1, title_2, value1, value2, title)
- else:
- _raisePlatformError('TwoChecks')
- else:
- _raisePlatformError('TwoChecks')
- c1 = tc.check1
- c2 = tc.check2
- if c1 == 1 and c2 == 0:
- return 1
- elif c1 == 0 and c2 == 1:
- return 2
- elif c1 == 1 and c2 == 1:
- return 3
- elif c1 == 0 and c2 == 0:
- return 0
- else:
- return None
-
-def Message(message, title='RoboFab'):
- """
- A simple message dialog.
- Availability: FontLab, Macintosh
- """
- if inFontLab:
- _FontLabDialogMessage(message, title).Run()
- elif MAC:
- import EasyDialogs
- EasyDialogs.Message(message)
- else:
- _raisePlatformError('Message')
-
-def AskString(message, value='', title='RoboFab'):
- """
- Returns entered string.
- Availability: FontLab, Macintosh
- """
- if inFontLab:
- askString = _FontLabDialogAskString(message, value, title)
- askString.Run()
- v = askString.value
- if v is None:
- return None
- else:
- return v
- elif MAC:
- import EasyDialogs
- askString = EasyDialogs.AskString(message)
- if askString is None:
- return None
- if len(askString) == 0:
- return None
- else:
- return askString
- else:
- _raisePlatformError('GetString')
-
-def AskYesNoCancel(message, title='RoboFab', default=0):
- """
- Returns 1 for 'Yes', 0 for 'No' and -1 for 'Cancel'.
- Availability: FontLab, Macintosh
- ("default" argument only available on Macintosh)
- """
- if inFontLab:
- gync = _FontLabDialogGetYesNoCancel(message, title)
- gync.Run()
- v = gync.value
- return v
- elif MAC:
- import EasyDialogs
- gync = EasyDialogs.AskYesNoCancel(message, default=default)
- return gync
- else:
- _raisePlatformError('GetYesNoCancel')
-
-def GetFile(message=None):
- """
- Select file dialog. Returns path if one is selected. Otherwise it returns None.
- Availability: FontLab, Macintosh, PC
- """
- path = None
- if MAC:
- if haveMacfs:
- fss, ok = macfs.PromptGetFile(message)
- if ok:
- path = fss.as_pathname()
- else:
- from robofab.interface.mac.getFileOrFolder import GetFile
- path = GetFile(message)
- elif PC:
- if inFontLab:
- if not message:
- message = ''
- path = fl.GetFileName(1, message, '', '')
- else:
- openFlags = win32con.OFN_FILEMUSTEXIST|win32con.OFN_EXPLORER
- mode_open = 1
- myDialog = win32ui.CreateFileDialog(mode_open,None,None,openFlags)
- myDialog.SetOFNTitle(message)
- is_OK = myDialog.DoModal()
- if is_OK == 1:
- path = myDialog.GetPathName()
- else:
- _raisePlatformError('GetFile')
- return path
-
-def GetFolder(message=None):
- """
- Select folder dialog. Returns path if one is selected. Otherwise it returns None.
- Availability: FontLab, Macintosh, PC
- """
- path = None
- if MAC:
- if haveMacfs:
- fss, ok = macfs.GetDirectory(message)
- if ok:
- path = fss.as_pathname()
- else:
- from robofab.interface.mac.getFileOrFolder import GetFileOrFolder
- # This _also_ allows the user to select _files_, but given the
- # package/folder dichotomy, I think we have no other choice.
- path = GetFileOrFolder(message)
- elif PC:
- if inFontLab:
- if not message:
- message = ''
- path = fl.GetPathName('', message)
- else:
- myTuple = shell.SHBrowseForFolder(0, None, message, 64)
- try:
- path = shell.SHGetPathFromIDList(myTuple[0])
- except:
- pass
- else:
- _raisePlatformError('GetFile')
- return path
-
-GetDirectory = GetFolder
-
-def PutFile(message=None, fileName=None):
- """
- Save file dialog. Returns path if one is entered. Otherwise it returns None.
- Availability: FontLab, Macintosh, PC
- """
- path = None
- if MAC:
- if haveMacfs:
- fss, ok = macfs.StandardPutFile(message, fileName)
- if ok:
- path = fss.as_pathname()
- else:
- import EasyDialogs
- path = EasyDialogs.AskFileForSave(message, savedFileName=fileName)
- elif PC:
- if inFontLab:
- if not message:
- message = ''
- if not fileName:
- fileName = ''
- path = fl.GetFileName(0, message, fileName, '')
- else:
- openFlags = win32con.OFN_OVERWRITEPROMPT|win32con.OFN_EXPLORER
- mode_save = 0
- myDialog = win32ui.CreateFileDialog(mode_save, None, fileName, openFlags)
- myDialog.SetOFNTitle(message)
- is_OK = myDialog.DoModal()
- if is_OK == 1:
- path = myDialog.GetPathName()
- else:
- _raisePlatformError('GetFile')
- return path
-
-
-if __name__=='__main__':
- import traceback
-
- print "dialogs hasW", hasW
- print "dialogs hasDialogKit", hasDialogKit
- print "dialogs MAC", MAC
- print "dialogs PC", PC
- print "dialogs inFontLab", inFontLab
- print "dialogs hasEasyDialogs", hasEasyDialogs
-
- def tryDialog(dialogClass, args=None):
- print
- print "tryDialog:", dialogClass, "with args:", args
- try:
- if args is not None:
- apply(dialogClass, args)
- else:
- apply(dialogClass)
- except:
- traceback.print_exc(limit=0)
-
- tryDialog(TwoChecks, ('hello', 'world', 1, 0, 'ugh'))
- tryDialog(TwoFields)
- tryDialog(TwoChecks, ('hello', 'world', 1, 0, 'ugh'))
- tryDialog(OneList, (['a', 'b', 'c'], 'hello world'))
- tryDialog(Message, ('hello world',))
- tryDialog(AskString, ('hello world',))
- tryDialog(AskYesNoCancel, ('hello world',))
-
- try:
- b = ProgressBar('hello', 50, 'world')
- for i in range(50):
- if i == 25:
- b.label('ugh.')
- b.tick(i)
- b.close()
- except:
- traceback.print_exc(limit=0)
diff --git a/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py b/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py
deleted file mode 100644
index d4b76f9c7..000000000
--- a/misc/pylib/robofab/interface/all/dialogs_mac_vanilla.py
+++ /dev/null
@@ -1,267 +0,0 @@
-"""
-
- Dialogs for environments that support cocao / vanilla.
-
-"""
-
-import vanilla.dialogs
-from AppKit import NSApp, NSModalPanelWindowLevel, NSWindowCloseButton, NSWindowZoomButton, NSWindowMiniaturizeButton
-
-__all__ = [
- "AskString",
- "AskYesNoCancel",
- "FindGlyph",
- "GetFile",
- "GetFileOrFolder",
- "GetFolder",
- "Message",
- "OneList",
- "PutFile",
- "SearchList",
- "SelectFont",
- "SelectGlyph",
-# "TwoChecks",
-# "TwoFields",
- "ProgressBar",
-]
-
-class _ModalWindow(vanilla.Window):
-
- nsWindowLevel = NSModalPanelWindowLevel
-
- def __init__(self, *args, **kwargs):
- super(_ModalWindow, self).__init__(*args, **kwargs)
- self._window.standardWindowButton_(NSWindowCloseButton).setHidden_(True)
- self._window.standardWindowButton_(NSWindowZoomButton).setHidden_(True)
- self._window.standardWindowButton_(NSWindowMiniaturizeButton).setHidden_(True)
-
- def open(self):
- super(_ModalWindow, self).open()
- self.center()
- NSApp().runModalForWindow_(self._window)
-
- def windowWillClose_(self, notification):
- super(_ModalWindow, self).windowWillClose_(notification)
- NSApp().stopModal()
-
-
-class _baseWindowController(object):
-
- def setUpBaseWindowBehavior(self):
- self._getValue = None
-
- self.w.okButton = vanilla.Button((-70, -30, -15, 20), "OK", callback=self.okCallback, sizeStyle="small")
- self.w.setDefaultButton(self.w.okButton)
-
- self.w.closeButton = vanilla.Button((-150, -30, -80, 20), "Cancel", callback=self.closeCallback, sizeStyle="small")
- self.w.closeButton.bind(".", ["command"])
- self.w.closeButton.bind(unichr(27), [])
-
- self.cancelled = False
-
- def okCallback(self, sender):
- self.w.close()
-
- def closeCallback(self, sender):
- self.cancelled = True
- self.w.close()
-
- def get(self):
- raise NotImplementedError
-
-
-class _AskStringController(_baseWindowController):
-
- def __init__(self, message, value, title):
- self.w = _ModalWindow((370, 110), title)
-
- self.w.infoText = vanilla.TextBox((15, 10, -15, 22), message)
- self.w.input = vanilla.EditText((15, 40, -15, 22))
- self.w.input.set(value)
-
- self.setUpBaseWindowBehavior()
- self.w.open()
-
- def get(self):
- if self.cancelled:
- return None
- return self.w.input.get()
-
-
-class _listController(_baseWindowController):
-
- def __init__(self, items, message, title, showSearch=False):
-
- self.items = items
-
- self.w = _ModalWindow((350, 300), title)
- y = 10
- self.w.infoText = vanilla.TextBox((15, y, -15, 22), message)
- y += 25
- if showSearch:
- self.w.search = vanilla.SearchBox((15, y, -15, 22), callback=self.searchCallback)
- y += 25
- self.w.itemList = vanilla.List((15, y, -15, -40), self.items, allowsMultipleSelection=False)
-
- self.setUpBaseWindowBehavior()
- self.w.open()
-
- def searchCallback(self, sender):
- search = sender.get()
-
- newItems = [item for item in self.items if repr(item).startswith(search)]
- self.w.itemList.set(newItems)
- if newItems:
- self.w.itemList.setSelection([0])
-
- def get(self):
- index = self.w.itemList.getSelection()
- if index:
- index = index[0]
- return self.w.itemList[index]
- return None
-
-
-def AskString(message, value='', title='RoboFab'):
- """
- AskString Dialog
-
- message the string
- value a default value
- title a title of the window (may not be supported everywhere)
- """
- w = _AskStringController(message, value, title)
- return w.get()
-
-def AskYesNoCancel(message, title='RoboFab', default=0, informativeText=""):
- """
- AskYesNoCancel Dialog
-
- message the string
- title* a title of the window
- (may not be supported everywhere)
- default* index number of which button should be default
- (i.e. respond to return)
- informativeText* A string with secundary information
-
- * may not be supported everywhere
- """
- return vanilla.dialogs.askYesNoCancel(messageText=message, informativeText=informativeText)
-
-def FindGlyph(aFont, message="Search for a glyph:", title='RoboFab'):
- items = aFont.keys()
- items.sort()
- w = _listController(items, message, title, showSearch=True)
- glyphName = w.get()
- if glyphName is not None:
- return aFont[glyphName]
- return None
-
-def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- result = vanilla.dialogs.getFile(messageText=message, title=title, directory=directory, fileName=fileName, allowsMultipleSelection=allowsMultipleSelection, fileTypes=fileTypes)
- if result is None:
- return None
- if not allowsMultipleSelection:
- return str(list(result)[0])
- else:
- return [str(n) for n in list(result)]
-
-def GetFolder(message=None, title=None, directory=None, allowsMultipleSelection=False):
- result = vanilla.dialogs.getFolder(messageText=message, title=title, directory=directory, allowsMultipleSelection=allowsMultipleSelection)
- if result is None:
- return None
- if not allowsMultipleSelection:
- return str(list(result)[0])
- else:
- return [str(n) for n in list(result)]
-
-def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- result = vanilla.dialogs.getFileOrFolder(messageText=message, title=title, directory=directory, fileName=fileName, allowsMultipleSelection=allowsMultipleSelection, fileTypes=fileTypes)
- if result is None:
- return None
- if not allowsMultipleSelection:
- return str(list(result)[0])
- else:
- return [str(n) for n in list(result)]
-
-def Message(message, title='RoboFab', informativeText=""):
- vanilla.dialogs.message(messageText=message, informativeText=informativeText)
-
-def OneList(items, message="Select an item:", title='RoboFab'):
- w = _listController(items, message, title, showSearch=False)
- return w.get()
-
-def PutFile(message=None, fileName=None):
- return vanilla.dialogs.putFile(messageText=message, fileName=fileName)
-
-def SearchList(list, message="Select an item:", title='RoboFab'):
- w = _listController(list, message, title, showSearch=True)
- return w.get()
-
-def SelectFont(message="Select a font:", title='RoboFab', allFonts=None):
- if allFonts is None:
- from robofab.world import AllFonts
- fonts = AllFonts()
- else:
- fonts = allFonts
-
- data = dict()
- for font in fonts:
- data["%s" %font] = font
-
- items = data.keys()
- items.sort()
- w = _listController(items, message, title, showSearch=False)
- value = w.get()
- return data.get(value, None)
-
-def SelectGlyph(aFont, message="Select a glyph:", title='RoboFab'):
- items = aFont.keys()
- items.sort()
- w = _listController(items, message, title, showSearch=False)
- glyphName = w.get()
- if glyphName is not None:
- return aFont[glyphName]
- return None
-
-def TwoChecks(title_1="One", title_2="Two", value1=1, value2=1, title='RoboFab'):
- raise NotImplementedError
-
-def TwoFields(title_1="One:", value_1="0", title_2="Two:", value_2="0", title='RoboFab'):
- raise NotImplementedError
-
-
-class ProgressBar(object):
- def __init__(self, title="RoboFab...", ticks=None, label=""):
- self.w = vanilla.Window((250, 60), title)
- if ticks is None:
- isIndeterminate = True
- ticks = 0
- else:
- isIndeterminate = False
- self.w.progress = vanilla.ProgressBar((15, 15, -15, 12), maxValue=ticks, isIndeterminate=isIndeterminate, sizeStyle="small")
- self.w.text = vanilla.TextBox((15, 32, -15, 14), label, sizeStyle="small")
- self.w.progress.start()
- self.w.center()
- self.w.open()
-
- def close(self):
- self.w.progress.stop()
- self.w.close()
-
- def getCurrentTick(self):
- return self.w.progress.get()
-
- def label(self, label):
- self.w.text.set(label)
- self.w.text._nsObject.display()
-
- def tick(self, tickValue=None):
- if tickValue is None:
- self.w.progress.increment()
- else:
- self.w.progress.set(tickValue)
-
-
-if __name__ == "__main__":
- pass \ No newline at end of file
diff --git a/misc/pylib/robofab/interface/mac/__init__.py b/misc/pylib/robofab/interface/mac/__init__.py
deleted file mode 100755
index 15f7d59c5..000000000
--- a/misc/pylib/robofab/interface/mac/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
-
-Directory for interface related modules.
-Stuff for MacOSX, widgets, quartz
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/interface/mac/getFileOrFolder.py b/misc/pylib/robofab/interface/mac/getFileOrFolder.py
deleted file mode 100755
index da7edff61..000000000
--- a/misc/pylib/robofab/interface/mac/getFileOrFolder.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""This module provides two functions, very similar to
-EasyDialogs.AskFileForOpen() and EasyDialogs.AskFolder(): GetFile() and
-GetFileOrFolder(). The main difference is that the functions here fully
-support "packages" or "bundles", ie. folders that appear to be files in
-the finder and open/save dialogs. The second difference is that
-GetFileOrFolder() allows the user to select a file _or_ a folder.
-"""
-
-
-__all__ = ["GetFile", "GetFileOrFolder"]
-
-
-from EasyDialogs import _process_Nav_args, _interact
-import Nav
-import Carbon.File
-
-
-# Lots of copy/paste from EasyDialogs.py, for one because althought the
-# EasyDialogs counterparts take a million options, they don't take the
-# one option I need: the flag to support packages...
-
-kNavSupportPackages = 0x00001000
-
-
-def GetFile(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- """Ask the user to select a file.
-
- Some of these arguments are not supported:
- title, directory, fileName, allowsMultipleSelection and fileTypes are here for compatibility reasons.
- """
- default_flags = 0x56 | kNavSupportPackages
- args, tpwanted = _process_Nav_args(default_flags, message=message)
- _interact()
- try:
- rr = Nav.NavChooseFile(args)
- good = 1
- except Nav.error, arg:
- if arg[0] != -128: # userCancelledErr
- raise Nav.error, arg
- return None
- if not rr.validRecord or not rr.selection:
- return None
- if issubclass(tpwanted, Carbon.File.FSRef):
- return tpwanted(rr.selection_fsr[0])
- if issubclass(tpwanted, Carbon.File.FSSpec):
- return tpwanted(rr.selection[0])
- if issubclass(tpwanted, str):
- return tpwanted(rr.selection_fsr[0].as_pathname())
- if issubclass(tpwanted, unicode):
- return tpwanted(rr.selection_fsr[0].as_pathname(), 'utf8')
- raise TypeError, "Unknown value for argument 'wanted': %s" % repr(tpwanted)
-
-
-def GetFileOrFolder(message=None, title=None, directory=None, fileName=None, allowsMultipleSelection=False, fileTypes=None):
- """Ask the user to select a file or a folder.
-
- Some of these arguments are not supported:
- title, directory, fileName, allowsMultipleSelection and fileTypes are here for compatibility reasons.
- """
- default_flags = 0x17 | kNavSupportPackages
- args, tpwanted = _process_Nav_args(default_flags, message=message)
- _interact()
- try:
- rr = Nav.NavChooseObject(args)
- good = 1
- except Nav.error, arg:
- if arg[0] != -128: # userCancelledErr
- raise Nav.error, arg
- return None
- if not rr.validRecord or not rr.selection:
- return None
- if issubclass(tpwanted, Carbon.File.FSRef):
- return tpwanted(rr.selection_fsr[0])
- if issubclass(tpwanted, Carbon.File.FSSpec):
- return tpwanted(rr.selection[0])
- if issubclass(tpwanted, str):
- return tpwanted(rr.selection_fsr[0].as_pathname())
- if issubclass(tpwanted, unicode):
- return tpwanted(rr.selection_fsr[0].as_pathname(), 'utf8')
- raise TypeError, "Unknown value for argument 'wanted': %s" % repr(tpwanted)
diff --git a/misc/pylib/robofab/interface/win/__init__.py b/misc/pylib/robofab/interface/win/__init__.py
deleted file mode 100755
index fe360c4ea..000000000
--- a/misc/pylib/robofab/interface/win/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""
-
-Directory for interface related modules.
-Stuff for Windows
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/misc/__init__.py b/misc/pylib/robofab/misc/__init__.py
deleted file mode 100755
index 5ed66a4e4..000000000
--- a/misc/pylib/robofab/misc/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""
-
- arrayTools and bezierTools, originally from fontTools and using Numpy,
- now in a pure python implementation. This should ease the Numpy dependency
- for normal UFO input/output and basic scripting tasks.
-
- comparison test and speedtest provided.
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/misc/arrayTools.pyx b/misc/pylib/robofab/misc/arrayTools.pyx
deleted file mode 100644
index 95884aa20..000000000
--- a/misc/pylib/robofab/misc/arrayTools.pyx
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# Various array and rectangle tools, but mostly rectangles, hence the
-# name of this module (not).
-#
-
-"""
-Rewritten to elimate the numpy dependency
-"""
-
-import math
-
-def calcBounds(array):
- """Return the bounding rectangle of a 2D points array as a tuple:
- (xMin, yMin, xMax, yMax)
- """
- if len(array) == 0:
- return 0, 0, 0, 0
- xs = [x for x, y in array]
- ys = [y for x, y in array]
- return min(xs), min(ys), max(xs), max(ys)
-
-def updateBounds(bounds, pt, min=min, max=max):
- """Return the bounding recangle of rectangle bounds and point (x, y)."""
- xMin, yMin, xMax, yMax = bounds
- x, y = pt
- return min(xMin, x), min(yMin, y), max(xMax, x), max(yMax, y)
-
-def pointInRect(pt, rect):
- """Return True when point (x, y) is inside rect."""
- xMin, yMin, xMax, yMax = rect
- return (xMin <= pt[0] <= xMax) and (yMin <= pt[1] <= yMax)
-
-def pointsInRect(array, rect):
- """Find out which points or array are inside rect.
- Returns an array with a boolean for each point.
- """
- if len(array) < 1:
- return []
- xMin, yMin, xMax, yMax = rect
- return [(xMin <= x <= xMax) and (yMin <= y <= yMax) for x, y in array]
-
-def vectorLength(vector):
- """Return the length of the given vector."""
- x, y = vector
- return math.sqrt(x**2 + y**2)
-
-def asInt16(array):
- """Round and cast to 16 bit integer."""
- return [int(math.floor(i+0.5)) for i in array]
-
-
-def normRect(box):
- """Normalize the rectangle so that the following holds:
- xMin <= xMax and yMin <= yMax
- """
- return min(box[0], box[2]), min(box[1], box[3]), max(box[0], box[2]), max(box[1], box[3])
-
-def scaleRect(box, x, y):
- """Scale the rectangle by x, y."""
- return box[0] * x, box[1] * y, box[2] * x, box[3] * y
-
-def offsetRect(box, dx, dy):
- """Offset the rectangle by dx, dy."""
- return box[0]+dx, box[1]+dy, box[2]+dx, box[3]+dy
-
-def insetRect(box, dx, dy):
- """Inset the rectangle by dx, dy on all sides."""
- return box[0]+dx, box[1]+dy, box[2]-dx, box[3]-dy
-
-def sectRect(box1, box2):
- """Return a boolean and a rectangle. If the input rectangles intersect, return
- True and the intersecting rectangle. Return False and (0, 0, 0, 0) if the input
- rectangles don't intersect.
- """
- xMin, yMin, xMax, yMax = (max(box1[0], box2[0]), max(box1[1], box2[1]),
- min(box1[2], box2[2]), min(box1[3], box2[3]))
- if xMin >= xMax or yMin >= yMax:
- return 0, (0, 0, 0, 0)
- return 1, (xMin, yMin, xMax, yMax)
-
-def unionRect(box1, box2):
- """Return the smallest rectangle in which both input rectangles are fully
- enclosed. In other words, return the total bounding rectangle of both input
- rectangles.
- """
- return (max(box1[0], box2[0]), max(box1[1], box2[1]),
- min(box1[2], box2[2]), min(box1[3], box2[3]))
-
-def rectCenter(box):
- """Return the center of the rectangle as an (x, y) coordinate."""
- return (box[0]+box[2])/2, (box[1]+box[3])/2
-
-def intRect(box):
- """Return the rectangle, rounded off to integer values, but guaranteeing that
- the resulting rectangle is NOT smaller than the original.
- """
- xMin, yMin, xMax, yMax = box
- xMin = int(math.floor(xMin))
- yMin = int(math.floor(yMin))
- xMax = int(math.ceil(xMax))
- yMax = int(math.ceil(yMax))
- return (xMin, yMin, xMax, yMax)
-
-
-def _test():
- """
- >>> import math
- >>> calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)])
- (0, 10, 80, 100)
- >>> updateBounds((0, 0, 0, 0), (100, 100))
- (0, 0, 100, 100)
- >>> pointInRect((50, 50), (0, 0, 100, 100))
- True
- >>> pointInRect((0, 0), (0, 0, 100, 100))
- True
- >>> pointInRect((100, 100), (0, 0, 100, 100))
- True
- >>> not pointInRect((101, 100), (0, 0, 100, 100))
- True
- >>> list(pointsInRect([(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)))
- [True, True, True, False]
- >>> vectorLength((3, 4))
- 5.0
- >>> vectorLength((1, 1)) == math.sqrt(2)
- True
- >>> list(asInt16([0, 0.1, 0.5, 0.9]))
- [0, 0, 1, 1]
- >>> normRect((0, 10, 100, 200))
- (0, 10, 100, 200)
- >>> normRect((100, 200, 0, 10))
- (0, 10, 100, 200)
- >>> scaleRect((10, 20, 50, 150), 1.5, 2)
- (15.0, 40, 75.0, 300)
- >>> offsetRect((10, 20, 30, 40), 5, 6)
- (15, 26, 35, 46)
- >>> insetRect((10, 20, 50, 60), 5, 10)
- (15, 30, 45, 50)
- >>> insetRect((10, 20, 50, 60), -5, -10)
- (5, 10, 55, 70)
- >>> intersects, rect = sectRect((0, 10, 20, 30), (0, 40, 20, 50))
- >>> not intersects
- True
- >>> intersects, rect = sectRect((0, 10, 20, 30), (5, 20, 35, 50))
- >>> intersects
- 1
- >>> rect
- (5, 20, 20, 30)
- >>> unionRect((0, 10, 20, 30), (0, 40, 20, 50))
- (0, 10, 20, 50)
- >>> rectCenter((0, 0, 100, 200))
- (50, 100)
- >>> rectCenter((0, 0, 100, 199.0))
- (50, 99.5)
- >>> intRect((0.9, 2.9, 3.1, 4.1))
- (0, 2, 4, 5)
- """
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/misc/pylib/robofab/misc/bezierTools.py b/misc/pylib/robofab/misc/bezierTools.py
deleted file mode 100644
index 9872060b1..000000000
--- a/misc/pylib/robofab/misc/bezierTools.py
+++ /dev/null
@@ -1,416 +0,0 @@
-"""fontTools.misc.bezierTools.py -- tools for working with bezier path segments.
-Rewritten to elimate the numpy dependency
-"""
-
-
-__all__ = [
- "calcQuadraticBounds",
- "calcCubicBounds",
- "splitLine",
- "splitQuadratic",
- "splitCubic",
- "splitQuadraticAtT",
- "splitCubicAtT",
- "solveQuadratic",
- "solveCubic",
-]
-
-from robofab.misc.arrayTools import calcBounds
-
-epsilon = 1e-12
-
-
-def calcQuadraticBounds(pt1, pt2, pt3):
- """Return the bounding rectangle for a qudratic bezier segment.
- pt1 and pt3 are the "anchor" points, pt2 is the "handle".
-
- >>> calcQuadraticBounds((0, 0), (50, 100), (100, 0))
- (0, 0, 100, 50.0)
- >>> calcQuadraticBounds((0, 0), (100, 0), (100, 100))
- (0.0, 0.0, 100, 100)
- """
- (ax, ay), (bx, by), (cx, cy) = calcQuadraticParameters(pt1, pt2, pt3)
- ax2 = ax*2.0
- ay2 = ay*2.0
- roots = []
- if ax2 != 0:
- roots.append(-bx/ax2)
- if ay2 != 0:
- roots.append(-by/ay2)
- points = [(ax*t*t + bx*t + cx, ay*t*t + by*t + cy) for t in roots if 0 <= t < 1] + [pt1, pt3]
- return calcBounds(points)
-
-
-def calcCubicBounds(pt1, pt2, pt3, pt4):
- """Return the bounding rectangle for a cubic bezier segment.
- pt1 and pt4 are the "anchor" points, pt2 and pt3 are the "handles".
-
- >>> calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0))
- (0, 0, 100, 75.0)
- >>> calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100))
- (0.0, 0.0, 100, 100)
- >>> calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0))
- (35.566243270259356, 0, 64.43375672974068, 75.0)
- """
- (ax, ay), (bx, by), (cx, cy), (dx, dy) = calcCubicParameters(pt1, pt2, pt3, pt4)
- # calc first derivative
- ax3 = ax * 3.0
- ay3 = ay * 3.0
- bx2 = bx * 2.0
- by2 = by * 2.0
- xRoots = [t for t in solveQuadratic(ax3, bx2, cx) if 0 <= t < 1]
- yRoots = [t for t in solveQuadratic(ay3, by2, cy) if 0 <= t < 1]
- roots = xRoots + yRoots
-
- points = [(ax*t*t*t + bx*t*t + cx * t + dx, ay*t*t*t + by*t*t + cy * t + dy) for t in roots] + [pt1, pt4]
- return calcBounds(points)
-
-
-def splitLine(pt1, pt2, where, isHorizontal):
- """Split the line between pt1 and pt2 at position 'where', which
- is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of two line segments if the
- line was successfully split, or a list containing the original
- line.
-
- >>> printSegments(splitLine((0, 0), (100, 200), 50, False))
- ((0, 0), (50.0, 100.0))
- ((50.0, 100.0), (100, 200))
- >>> printSegments(splitLine((0, 0), (100, 200), 50, True))
- ((0, 0), (25.0, 50.0))
- ((25.0, 50.0), (100, 200))
- >>> printSegments(splitLine((0, 0), (100, 100), 50, True))
- ((0, 0), (50.0, 50.0))
- ((50.0, 50.0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 100, True))
- ((0, 0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 0, True))
- ((0, 0), (0.0, 0.0))
- ((0.0, 0.0), (100, 100))
- >>> printSegments(splitLine((0, 0), (100, 100), 0, False))
- ((0, 0), (0.0, 0.0))
- ((0.0, 0.0), (100, 100))
- """
- pt1x, pt1y = pt1
- pt2x, pt2y = pt2
-
- ax = (pt2x - pt1x)
- ay = (pt2y - pt1y)
-
- bx = pt1x
- by = pt1y
-
- ax1 = (ax, ay)[isHorizontal]
-
- if ax1 == 0:
- return [(pt1, pt2)]
-
- t = float(where - (bx, by)[isHorizontal]) / ax1
- if 0 <= t < 1:
- midPt = ax * t + bx, ay * t + by
- return [(pt1, midPt), (midPt, pt2)]
- else:
- return [(pt1, pt2)]
-
-
-def splitQuadratic(pt1, pt2, pt3, where, isHorizontal):
- """Split the quadratic curve between pt1, pt2 and pt3 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
-
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 150, False))
- ((0, 0), (50, 100), (100, 0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, False))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, False))
- ((0.0, 0.0), (12.5, 25.0), (25.0, 37.5))
- ((25.0, 37.5), (62.5, 75.0), (100.0, 0.0))
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 25, True))
- ((0.0, 0.0), (7.32233047034, 14.6446609407), (14.6446609407, 25.0))
- ((14.6446609407, 25.0), (50.0, 75.0), (85.3553390593, 25.0))
- ((85.3553390593, 25.0), (92.6776695297, 14.6446609407), (100.0, -7.1054273576e-15))
- >>> # XXX I'm not at all sure if the following behavior is desirable:
- >>> printSegments(splitQuadratic((0, 0), (50, 100), (100, 0), 50, True))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (50.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- """
- a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- solutions = solveQuadratic(a[isHorizontal], b[isHorizontal],
- c[isHorizontal] - where)
- solutions = [t for t in solutions if 0 <= t < 1]
- solutions.sort()
- if not solutions:
- return [(pt1, pt2, pt3)]
- return _splitQuadraticAtT(a, b, c, *solutions)
-
-
-def splitCubic(pt1, pt2, pt3, pt4, where, isHorizontal):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at position 'where',
- which is an x coordinate if isHorizontal is False, a y coordinate if
- isHorizontal is True. Return a list of curve segments.
-
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 150, False))
- ((0, 0), (25, 100), (75, 100), (100, 0))
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 50, False))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
- >>> printSegments(splitCubic((0, 0), (25, 100), (75, 100), (100, 0), 25, True))
- ((0.0, 0.0), (2.2937927384, 9.17517095361), (4.79804488188, 17.5085042869), (7.47413641001, 25.0))
- ((7.47413641001, 25.0), (31.2886200204, 91.6666666667), (68.7113799796, 91.6666666667), (92.52586359, 25.0))
- ((92.52586359, 25.0), (95.2019551181, 17.5085042869), (97.7062072616, 9.17517095361), (100.0, 1.7763568394e-15))
- """
- a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- solutions = solveCubic(a[isHorizontal], b[isHorizontal], c[isHorizontal],
- d[isHorizontal] - where)
- solutions = [t for t in solutions if 0 <= t < 1]
- solutions.sort()
- if not solutions:
- return [(pt1, pt2, pt3, pt4)]
- return _splitCubicAtT(a, b, c, d, *solutions)
-
-
-def splitQuadraticAtT(pt1, pt2, pt3, *ts):
- """Split the quadratic curve between pt1, pt2 and pt3 at one or more
- values of t. Return a list of curve segments.
-
- >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (75.0, 50.0), (100.0, 0.0))
- >>> printSegments(splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75))
- ((0.0, 0.0), (25.0, 50.0), (50.0, 50.0))
- ((50.0, 50.0), (62.5, 50.0), (75.0, 37.5))
- ((75.0, 37.5), (87.5, 25.0), (100.0, 0.0))
- """
- a, b, c = calcQuadraticParameters(pt1, pt2, pt3)
- return _splitQuadraticAtT(a, b, c, *ts)
-
-
-def splitCubicAtT(pt1, pt2, pt3, pt4, *ts):
- """Split the cubic curve between pt1, pt2, pt3 and pt4 at one or more
- values of t. Return a list of curve segments.
-
- >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (68.75, 75.0), (87.5, 50.0), (100.0, 0.0))
- >>> printSegments(splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75))
- ((0.0, 0.0), (12.5, 50.0), (31.25, 75.0), (50.0, 75.0))
- ((50.0, 75.0), (59.375, 75.0), (68.75, 68.75), (77.34375, 56.25))
- ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25.0), (100.0, 0.0))
- """
- a, b, c, d = calcCubicParameters(pt1, pt2, pt3, pt4)
- return _splitCubicAtT(a, b, c, d, *ts)
-
-
-def _splitQuadraticAtT(a, b, c, *ts):
- ts = list(ts)
- segments = []
- ts.insert(0, 0.0)
- ts.append(1.0)
- ax, ay = a
- bx, by = b
- cx, cy = c
- for i in range(len(ts) - 1):
- t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
- # calc new a, b and c
- a1x = ax * delta**2
- a1y = ay * delta**2
- b1x = (2*ax*t1 + bx) * delta
- b1y = (2*ay*t1 + by) * delta
- c1x = ax*t1**2 + bx*t1 + cx
- c1y = ay*t1**2 + by*t1 + cy
-
- pt1, pt2, pt3 = calcQuadraticPoints((a1x, a1y), (b1x, b1y), (c1x, c1y))
- segments.append((pt1, pt2, pt3))
- return segments
-
-
-def _splitCubicAtT(a, b, c, d, *ts):
- ts = list(ts)
- ts.insert(0, 0.0)
- ts.append(1.0)
- segments = []
- ax, ay = a
- bx, by = b
- cx, cy = c
- dx, dy = d
- for i in range(len(ts) - 1):
- t1 = ts[i]
- t2 = ts[i+1]
- delta = (t2 - t1)
- # calc new a, b, c and d
- a1x = ax * delta**3
- a1y = ay * delta**3
- b1x = (3*ax*t1 + bx) * delta**2
- b1y = (3*ay*t1 + by) * delta**2
- c1x = (2*bx*t1 + cx + 3*ax*t1**2) * delta
- c1y = (2*by*t1 + cy + 3*ay*t1**2) * delta
- d1x = ax*t1**3 + bx*t1**2 + cx*t1 + dx
- d1y = ay*t1**3 + by*t1**2 + cy*t1 + dy
- pt1, pt2, pt3, pt4 = calcCubicPoints((a1x, a1y), (b1x, b1y), (c1x, c1y), (d1x, d1y))
- segments.append((pt1, pt2, pt3, pt4))
- return segments
-
-
-#
-# Equation solvers.
-#
-
-from math import sqrt, acos, cos, pi
-
-
-def solveQuadratic(a, b, c,
- sqrt=sqrt):
- """Solve a quadratic equation where a, b and c are real.
- a*x*x + b*x + c = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
- """
- if abs(a) < epsilon:
- if abs(b) < epsilon:
- # We have a non-equation; therefore, we have no valid solution
- roots = []
- else:
- # We have a linear equation with 1 root.
- roots = [-c/b]
- else:
- # We have a true quadratic equation. Apply the quadratic formula to find two roots.
- DD = b*b - 4.0*a*c
- if DD >= 0.0:
- rDD = sqrt(DD)
- roots = [(-b+rDD)/2.0/a, (-b-rDD)/2.0/a]
- else:
- # complex roots, ignore
- roots = []
- return roots
-
-
-def solveCubic(a, b, c, d,
- abs=abs, pow=pow, sqrt=sqrt, cos=cos, acos=acos, pi=pi):
- """Solve a cubic equation where a, b, c and d are real.
- a*x*x*x + b*x*x + c*x + d = 0
- This function returns a list of roots. Note that the returned list
- is neither guaranteed to be sorted nor to contain unique values!
- """
- #
- # adapted from:
- # CUBIC.C - Solve a cubic polynomial
- # public domain by Ross Cottrell
- # found at: http://www.strangecreations.com/library/snippets/Cubic.C
- #
- if abs(a) < epsilon:
- # don't just test for zero; for very small values of 'a' solveCubic()
- # returns unreliable results, so we fall back to quad.
- return solveQuadratic(b, c, d)
- a = float(a)
- a1 = b/a
- a2 = c/a
- a3 = d/a
-
- Q = (a1*a1 - 3.0*a2)/9.0
- R = (2.0*a1*a1*a1 - 9.0*a1*a2 + 27.0*a3)/54.0
- R2_Q3 = R*R - Q*Q*Q
-
- if R2_Q3 < 0:
- theta = acos(R/sqrt(Q*Q*Q))
- rQ2 = -2.0*sqrt(Q)
- x0 = rQ2*cos(theta/3.0) - a1/3.0
- x1 = rQ2*cos((theta+2.0*pi)/3.0) - a1/3.0
- x2 = rQ2*cos((theta+4.0*pi)/3.0) - a1/3.0
- return [x0, x1, x2]
- else:
- if Q == 0 and R == 0:
- x = 0
- else:
- x = pow(sqrt(R2_Q3)+abs(R), 1/3.0)
- x = x + Q/x
- if R >= 0.0:
- x = -x
- x = x - a1/3.0
- return [x]
-
-
-#
-# Conversion routines for points to parameters and vice versa
-#
-
-def calcQuadraticParameters(pt1, pt2, pt3):
- x2, y2 = pt2
- x3, y3 = pt3
- cx, cy = pt1
- bx = (x2 - cx) * 2.0
- by = (y2 - cy) * 2.0
- ax = x3 - cx - bx
- ay = y3 - cy - by
- return (ax, ay), (bx, by), (cx, cy)
-
-
-def calcCubicParameters(pt1, pt2, pt3, pt4):
- x2, y2 = pt2
- x3, y3 = pt3
- x4, y4 = pt4
- dx, dy = pt1
- cx = (x2 -dx) * 3.0
- cy = (y2 -dy) * 3.0
- bx = (x3 - x2) * 3.0 - cx
- by = (y3 - y2) * 3.0 - cy
- ax = x4 - dx - cx - bx
- ay = y4 - dy - cy - by
- return (ax, ay), (bx, by), (cx, cy), (dx, dy)
-
-
-def calcQuadraticPoints(a, b, c):
- ax, ay = a
- bx, by = b
- cx, cy = c
- x1 = cx
- y1 = cy
- x2 = (bx * 0.5) + cx
- y2 = (by * 0.5) + cy
- x3 = ax + bx + cx
- y3 = ay + by + cy
- return (x1, y1), (x2, y2), (x3, y3)
-
-
-def calcCubicPoints(a, b, c, d):
- ax, ay = a
- bx, by = b
- cx, cy = c
- dx, dy = d
- x1 = dx
- y1 = dy
- x2 = (cx / 3.0) + dx
- y2 = (cy / 3.0) + dy
- x3 = (bx + cx) / 3.0 + x2
- y3 = (by + cy) / 3.0 + y2
- x4 = ax + dx + cx + bx
- y4 = ay + dy + cy + by
- return (x1, y1), (x2, y2), (x3, y3), (x4, y4)
-
-
-def _segmentrepr(obj):
- """
- >>> _segmentrepr([1, [2, 3], [], [[2, [3, 4], [0.1, 2.2]]]])
- '(1, (2, 3), (), ((2, (3, 4), (0.1, 2.2))))'
- """
- try:
- it = iter(obj)
- except TypeError:
- return str(obj)
- else:
- return "(%s)" % ", ".join([_segmentrepr(x) for x in it])
-
-
-def printSegments(segments):
- """Helper for the doctests, displaying each segment in a list of
- segments on a single line as a tuple.
- """
- for segment in segments:
- print _segmentrepr(segment)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/misc/pylib/robofab/misc/speedTestCase.py b/misc/pylib/robofab/misc/speedTestCase.py
deleted file mode 100644
index e5003ac41..000000000
--- a/misc/pylib/robofab/misc/speedTestCase.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-
- Speed comparison between the fontTools numpy based arrayTools and bezierTools,
- and the pure python implementation in robofab.path.arrayTools and robofab.path.bezierTools
-
-"""
-
-import time
-
-from fontTools.misc import arrayTools
-from fontTools.misc import bezierTools
-
-import numpy
-
-import robofab.misc.arrayTools as noNumpyArrayTools
-import robofab.misc.bezierTools as noNumpyBezierTools
-
-################
-
-pt1 = (100, 100)
-pt2 = (200, 20)
-pt3 = (30, 580)
-pt4 = (153, 654)
-rect = [20, 20, 100, 100]
-
-## loops
-c = 10000
-
-print "(loop %s)"%c
-
-
-print "with numpy:"
-print "calcQuadraticParameters\t\t",
-n = time.time()
-for i in range(c):
- bezierTools.calcQuadraticParameters(pt1, pt2, pt3)
-print time.time() - n
-
-print "calcBounds\t\t\t",
-n = time.time()
-for i in range(c):
- arrayTools.calcBounds([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3])
-print time.time() - n
-
-print "pointsInRect\t\t\t",
-n = time.time()
-for i in range(c):
- arrayTools.pointsInRect([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt4], rect)
-print time.time() - n
-
-print "calcQuadraticBounds\t\t",
-n = time.time()
-for i in range(c):
- bezierTools.calcQuadraticBounds(pt1, pt2, pt3)
-print time.time() - n
-
-print "calcCubicBounds\t\t\t",
-n = time.time()
-for i in range(c):
- bezierTools.calcCubicBounds(pt1, pt2, pt3, pt4)
-print time.time() - n
-
-print
-##############
-
-print "no-numpy"
-print "calcQuadraticParameters\t\t",
-n = time.time()
-for i in range(c):
- noNumpyBezierTools.calcQuadraticParameters(pt1, pt2, pt3)
-print time.time() - n
-
-print "calcBounds\t\t\t",
-n = time.time()
-for i in range(c):
- noNumpyArrayTools.calcBounds([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3])
-print time.time() - n
-
-print "pointsInRect\t\t\t",
-n = time.time()
-for i in range(c):
- noNumpyArrayTools.pointsInRect([pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt1, pt2, pt3, pt4], rect)
-print time.time() - n
-
-print "calcQuadraticBounds\t\t",
-n = time.time()
-for i in range(c):
- noNumpyBezierTools.calcQuadraticBounds(pt1, pt2, pt3)
-print time.time() - n
-
-print "calcCubicBounds\t\t\t",
-n = time.time()
-for i in range(c):
- noNumpyBezierTools.calcCubicBounds(pt1, pt2, pt3, pt4)
-print time.time() - n
-
-
-
-
diff --git a/misc/pylib/robofab/misc/test.py b/misc/pylib/robofab/misc/test.py
deleted file mode 100644
index 99a795c48..000000000
--- a/misc/pylib/robofab/misc/test.py
+++ /dev/null
@@ -1,119 +0,0 @@
-"""
-doc test requires fontTools to compare and defon to make the test font.
-"""
-
-import random
-from fontTools.pens.basePen import BasePen
-
-from fontTools.misc import arrayTools
-from fontTools.misc import bezierTools
-
-import robofab.misc.arrayTools as noNumpyArrayTools
-import robofab.misc.bezierTools as noNumpyBezierTools
-
-
-def drawMoveTo(pen, maxBox):
- pen.moveTo((maxBox*random.random(), maxBox*random.random()))
-
-def drawLineTo(pen, maxBox):
- pen.lineTo((maxBox*random.random(), maxBox*random.random()))
-
-def drawCurveTo(pen, maxBox):
- pen.curveTo((maxBox*random.random(), maxBox*random.random()),
- (maxBox*random.random(), maxBox*random.random()),
- (maxBox*random.random(), maxBox*random.random()))
-
-def drawClosePath(pen):
- pen.closePath()
-
-def createRandomFont():
- from defcon import Font
-
- maxGlyphs = 1000
- maxContours = 10
- maxSegments = 10
- maxBox = 700
- drawCallbacks = [drawLineTo, drawCurveTo]
- f = Font()
- for i in range(maxGlyphs):
- name = "%s" %i
- f.newGlyph(name)
- g = f[name]
- g.width = maxBox
- pen = g.getPen()
- for c in range(maxContours):
- drawMoveTo(pen, maxBox)
- for s in range(maxSegments):
- random.choice(drawCallbacks)(pen, maxBox)
- drawClosePath(pen)
- return f
-
-class BoundsPen(BasePen):
-
- def __init__(self, glyphSet, at, bt):
- BasePen.__init__(self, glyphSet)
- self.bounds = None
- self._start = None
- self._arrayTools = at
- self._bezierTools = bt
-
- def _moveTo(self, pt):
- self._start = pt
-
- def _addMoveTo(self):
- if self._start is None:
- return
- bounds = self.bounds
- if bounds:
- self.bounds = self._arrayTools.updateBounds(bounds, self._start)
- else:
- x, y = self._start
- self.bounds = (x, y, x, y)
- self._start = None
-
- def _lineTo(self, pt):
- self._addMoveTo()
- self.bounds = self._arrayTools.updateBounds(self.bounds, pt)
-
- def _curveToOne(self, bcp1, bcp2, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = self._arrayTools.updateBounds(bounds, pt)
- if not self._arrayTools.pointInRect(bcp1, bounds) or not self._arrayTools.pointInRect(bcp2, bounds):
- bounds = self._arrayTools.unionRect(bounds, self._bezierTools.calcCubicBounds(
- self._getCurrentPoint(), bcp1, bcp2, pt))
- self.bounds = bounds
-
- def _qCurveToOne(self, bcp, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = self._arrayTools.updateBounds(bounds, pt)
- if not self._arrayTools.pointInRect(bcp, bounds):
- bounds = self._arrayToolsunionRect(bounds, self._bezierTools.calcQuadraticBounds(
- self._getCurrentPoint(), bcp, pt))
- self.bounds = bounds
-
-
-
-def _testFont(font):
- succes = True
- for glyph in font:
- fontToolsBoundsPen = BoundsPen(font, arrayTools, bezierTools)
- glyph.draw(fontToolsBoundsPen)
- noNumpyBoundsPen = BoundsPen(font, noNumpyArrayTools, noNumpyBezierTools)
- glyph.draw(noNumpyBoundsPen)
- if fontToolsBoundsPen.bounds != noNumpyBoundsPen.bounds:
- succes = False
- return succes
-
-
-def testCompareAgainstFontTools():
- """
- >>> font = createRandomFont()
- >>> _testFont(font)
- True
- """
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod() \ No newline at end of file
diff --git a/misc/pylib/robofab/objects/__init__.py b/misc/pylib/robofab/objects/__init__.py
deleted file mode 100755
index ad85fd002..000000000
--- a/misc/pylib/robofab/objects/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-"""
-
-Directory for modules supporting
-
- Unified
-
- Font
-
- Objects
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/objects/objectsBase.pyx b/misc/pylib/robofab/objects/objectsBase.pyx
deleted file mode 100755
index 3154fb3f4..000000000
--- a/misc/pylib/robofab/objects/objectsBase.pyx
+++ /dev/null
@@ -1,3426 +0,0 @@
-"""
-Base classes for the Unified Font Objects (UFO),
-a series of classes that deal with fonts, glyphs,
-contours and related things.
-
-Unified Font Objects are:
-- platform independent
-- application independent
-
-About Object Inheritance:
-objectsFL and objectsRF objects inherit
-methods and attributes from these objects.
-In other words, if it is in here, you can
-do it with the objectsFL and objectsRF.
-"""
-
-
-from __future__ import generators
-from __future__ import division
-
-from warnings import warn
-import math
-import copy
-
-from robofab import ufoLib
-from robofab import RoboFabError
-from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect
-from fontTools.pens.basePen import AbstractPen
-from fontTools.pens.areaPen import AreaPen
-from ..exceptions import RoboFabError, RoboFabWarning
-
-try:
- set
-except NameError:
- from sets import Set as set
-
-#constants for dealing with segments, points and bPoints
-MOVE = 'move'
-LINE = 'line'
-CORNER = 'corner'
-CURVE = 'curve'
-QCURVE = 'qcurve'
-OFFCURVE = 'offcurve'
-
-DEGREE = 180 / math.pi
-
-
-
-# the key for the postscript hint data stored in the UFO
-postScriptHintDataLibKey = "org.robofab.postScriptHintData"
-
-# from http://svn.typesupply.com/packages/fontMath/mathFunctions.py
-
-def add(v1, v2):
- return v1 + v2
-
-def sub(v1, v2):
- return v1 - v2
-
-def mul(v, f):
- return v * f
-
-def div(v, f):
- return v / f
-
-def issequence(x):
- "Is x a sequence? We say it is if it has a __getitem__ method."
- return hasattr(x, '__getitem__')
-
-
-
-class BasePostScriptHintValues(object):
- """ Base class for postscript hinting information.
- """
-
- def __init__(self, data=None):
- if data is not None:
- self.fromDict(data)
- else:
- for name in self._attributeNames.keys():
- setattr(self, name, self._attributeNames[name]['default'])
-
- def getParent(self):
- """this method will be overwritten with a weakref if there is a parent."""
- return None
-
- def setParent(self, parent):
- import weakref
- self.getParent = weakref.ref(parent)
-
- def isEmpty(self):
- """Check all attrs and decide if they're all empty."""
- empty = True
- for name in self._attributeNames:
- if getattr(self, name):
- empty = False
- break
- return empty
-
- def clear(self):
- """Set all attributes to default / empty"""
- for name in self._attributeNames:
- setattr(self, name, self._attributeNames[name]['default'])
-
- def _loadFromLib(self, lib):
- data = lib.get(postScriptHintDataLibKey)
- if data is not None:
- self.fromDict(data)
-
- def _saveToLib(self, lib):
- parent = self.getParent()
- if parent is not None:
- parent.setChanged(True)
- hintsDict = self.asDict()
- if hintsDict:
- lib[postScriptHintDataLibKey] = hintsDict
-
- def fromDict(self, data):
- for name in self._attributeNames:
- if name in data:
- setattr(self, name, data[name])
-
- def asDict(self):
- d = {}
- for name in self._attributeNames:
- try:
- value = getattr(self, name)
- except AttributeError:
- print "%s attribute not supported"%name
- continue
- if value:
- d[name] = getattr(self, name)
- return d
-
- def update(self, other):
- assert isinstance(other, BasePostScriptHintValues)
- for name in self._attributeNames.keys():
- v = getattr(other, name)
- if v is not None:
- setattr(self, name, v)
-
- def __repr__(self):
- return "<Base PS Hint Data>"
-
- def copy(self, aParent=None):
- """Duplicate this object. Pass an object for parenting if you want."""
- n = self.__class__(data=self.asDict())
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['getParent']
- for k in self.__dict__.keys():
- if k in dont:
- continue
- dup = copy.deepcopy(self.__dict__[k])
- setattr(n, k, dup)
- return n
-
-class BasePostScriptGlyphHintValues(BasePostScriptHintValues):
- """ Base class for glyph-level postscript hinting information.
- vStems, hStems
- """
- _attributeNames = {
- # some of these values can have only a certain number of elements
- 'vHints': {'default': None, 'max':100, 'isVertical':True},
- 'hHints': {'default': None, 'max':100, 'isVertical':False},
- }
-
- def __init__(self, data=None):
- if data is not None:
- self.fromDict(data)
- else:
- for name in self._attributeNames.keys():
- setattr(self, name, self._attributeNames[name]['default'])
-
- def __repr__(self):
- return "<PostScript Glyph Hints Values>"
-
- def round(self):
- """Round the values to reasonable values.
- - stems are rounded to int
- """
- for name, values in self._attributeNames.items():
- v = getattr(self, name)
- if v is None:
- continue
- new = []
- for n in v:
- new.append((int(round(n[0])), int(round(n[1]))))
- setattr(self, name, new)
-
- # math operations for psHint object
- # Note: math operations can change integers to floats.
- def __add__(self, other):
- assert isinstance(other, BasePostScriptHintValues)
- copied = self.copy()
- self._processMathOne(copied, other, add)
- return copied
-
- def __sub__(self, other):
- assert isinstance(other, BasePostScriptHintValues)
- copied = self.copy()
- self._processMathOne(copied, other, sub)
- return copied
-
- def __mul__(self, factor):
- #if isinstance(factor, tuple):
- # factor = factor[0]
- copiedInfo = self.copy()
- self._processMathTwo(copiedInfo, factor, mul)
- return copiedInfo
-
- __rmul__ = __mul__
-
- def __div__(self, factor):
- #if isinstance(factor, tuple):
- # factor = factor[0]
- copiedInfo = self.copy()
- self._processMathTwo(copiedInfo, factor, div)
- return copiedInfo
-
- __rdiv__ = __div__
-
- def _processMathOne(self, copied, other, funct):
- for name, values in self._attributeNames.items():
- a = None
- b = None
- v = None
- if hasattr(copied, name):
- a = getattr(copied, name)
- if hasattr(other, name):
- b = getattr(other, name)
- if a is not None and b is not None:
- if len(a) != len(b):
- # can't do math with non matching zones
- continue
- l = len(a)
- for i in range(l):
- if v is None:
- v = []
- ai = a[i]
- bi = b[i]
- l2 = min(len(ai), len(bi))
- v2 = [funct(ai[j], bi[j]) for j in range(l2)]
- v.append(v2)
- if v is not None:
- setattr(copied, name, v)
-
- def _processMathTwo(self, copied, factor, funct):
- for name, values in self._attributeNames.items():
- a = None
- b = None
- v = None
- isVertical = self._attributeNames[name]['isVertical']
- splitFactor = factor
- if isinstance(factor, tuple):
- #print "mathtwo", name, funct, factor, isVertical
- if isVertical:
- splitFactor = factor[1]
- else:
- splitFactor = factor[0]
- if hasattr(copied, name):
- a = getattr(copied, name)
- if a is not None:
- for i in range(len(a)):
- if v is None:
- v = []
- v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))]
- v.append(v2)
- if v is not None:
- setattr(copied, name, v)
-
-
-class BasePostScriptFontHintValues(BasePostScriptHintValues):
- """ Base class for font-level postscript hinting information.
- Blues values, stem values.
- """
-
- _attributeNames = {
- # some of these values can have only a certain number of elements
- # default: what the value should be when initialised
- # max: the maximum number of items this attribute is allowed to have
- # isVertical: the vertical relevance
- 'blueFuzz': {'default': None, 'max':1, 'isVertical':True},
- 'blueScale': {'default': None, 'max':1, 'isVertical':True},
- 'blueShift': {'default': None, 'max':1, 'isVertical':True},
- 'forceBold': {'default': None, 'max':1, 'isVertical':False},
- 'blueValues': {'default': None, 'max':7, 'isVertical':True},
- 'otherBlues': {'default': None, 'max':5, 'isVertical':True},
- 'familyBlues': {'default': None, 'max':7, 'isVertical':True},
- 'familyOtherBlues': {'default': None, 'max':5, 'isVertical':True},
- 'vStems': {'default': None, 'max':6, 'isVertical':True},
- 'hStems': {'default': None, 'max':11, 'isVertical':False},
- }
-
- def __init__(self, data=None):
- if data is not None:
- self.fromDict(data)
-
- def __repr__(self):
- return "<PostScript Font Hints Values>"
-
- # route attribute calls to info object
-
- def _bluesToPairs(self, values):
- values.sort()
- finalValues = []
- for value in values:
- if not finalValues or len(finalValues[-1]) == 2:
- finalValues.append([])
- finalValues[-1].append(value)
- return finalValues
-
- def _bluesFromPairs(self, values):
- finalValues = []
- for value1, value2 in values:
- finalValues.append(value1)
- finalValues.append(value2)
- finalValues.sort()
- return finalValues
-
- def _get_blueValues(self):
- values = self.getParent().info.postscriptBlueValues
- if values is None:
- values = []
- values = self._bluesToPairs(values)
- return values
-
- def _set_blueValues(self, values):
- if values is None:
- values = []
- values = self._bluesFromPairs(values)
- self.getParent().info.postscriptBlueValues = values
-
- blueValues = property(_get_blueValues, _set_blueValues)
-
- def _get_otherBlues(self):
- values = self.getParent().info.postscriptOtherBlues
- if values is None:
- values = []
- values = self._bluesToPairs(values)
- return values
-
- def _set_otherBlues(self, values):
- if values is None:
- values = []
- values = self._bluesFromPairs(values)
- self.getParent().info.postscriptOtherBlues = values
-
- otherBlues = property(_get_otherBlues, _set_otherBlues)
-
- def _get_familyBlues(self):
- values = self.getParent().info.postscriptFamilyBlues
- if values is None:
- values = []
- values = self._bluesToPairs(values)
- return values
-
- def _set_familyBlues(self, values):
- if values is None:
- values = []
- values = self._bluesFromPairs(values)
- self.getParent().info.postscriptFamilyBlues = values
-
- familyBlues = property(_get_familyBlues, _set_familyBlues)
-
- def _get_familyOtherBlues(self):
- values = self.getParent().info.postscriptFamilyOtherBlues
- if values is None:
- values = []
- values = self._bluesToPairs(values)
- return values
-
- def _set_familyOtherBlues(self, values):
- if values is None:
- values = []
- values = self._bluesFromPairs(values)
- self.getParent().info.postscriptFamilyOtherBlues = values
-
- familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues)
-
- def _get_vStems(self):
- return self.getParent().info.postscriptStemSnapV
-
- def _set_vStems(self, value):
- if value is None:
- value = []
- self.getParent().info.postscriptStemSnapV = list(value)
-
- vStems = property(_get_vStems, _set_vStems)
-
- def _get_hStems(self):
- return self.getParent().info.postscriptStemSnapH
-
- def _set_hStems(self, value):
- if value is None:
- value = []
- self.getParent().info.postscriptStemSnapH = list(value)
-
- hStems = property(_get_hStems, _set_hStems)
-
- def _get_blueScale(self):
- return self.getParent().info.postscriptBlueScale
-
- def _set_blueScale(self, value):
- self.getParent().info.postscriptBlueScale = value
-
- blueScale = property(_get_blueScale, _set_blueScale)
-
- def _get_blueShift(self):
- return self.getParent().info.postscriptBlueShift
-
- def _set_blueShift(self, value):
- self.getParent().info.postscriptBlueShift = value
-
- blueShift = property(_get_blueShift, _set_blueShift)
-
- def _get_blueFuzz(self):
- return self.getParent().info.postscriptBlueFuzz
-
- def _set_blueFuzz(self, value):
- self.getParent().info.postscriptBlueFuzz = value
-
- blueFuzz = property(_get_blueFuzz, _set_blueFuzz)
-
- def _get_forceBold(self):
- return self.getParent().info.postscriptForceBold
-
- def _set_forceBold(self, value):
- self.getParent().info.postscriptForceBold = value
-
- forceBold = property(_get_forceBold, _set_forceBold)
-
- def round(self):
- """Round the values to reasonable values.
- - blueScale is not rounded, it is a float
- - forceBold is set to False if -0.5 < value < 0.5. Otherwise it will be True.
- - blueShift, blueFuzz are rounded to int
- - stems are rounded to int
- - blues are rounded to int
- """
- for name, values in self._attributeNames.items():
- if name == "blueScale":
- continue
- elif name == "forceBold":
- v = getattr(self, name)
- if v is None:
- continue
- if -0.5 <= v <= 0.5:
- setattr(self, name, False)
- else:
- setattr(self, name, True)
- elif name in ['blueFuzz', 'blueShift']:
- v = getattr(self, name)
- if v is None:
- continue
- setattr(self, name, int(round(v)))
- elif name in ['hStems', 'vStems']:
- v = getattr(self, name)
- if v is None:
- continue
- new = []
- for n in v:
- new.append(int(round(n)))
- setattr(self, name, new)
- else:
- v = getattr(self, name)
- if v is None:
- continue
- new = []
- for n in v:
- new.append([int(round(m)) for m in n])
- setattr(self, name, new)
-
-
-
-class RoboFabInterpolationError(Exception): pass
-
-
-def _interpolate(a,b,v):
- """interpolate values by factor v"""
- return a + (b-a) * v
-
-def _interpolatePt(a, b, v):
- """interpolate point by factor v"""
- xa, ya = a
- xb, yb = b
- if not isinstance(v, tuple):
- xv = v
- yv = v
- else:
- xv, yv = v
- return xa + (xb-xa) * xv, ya + (yb-ya) * yv
-
-def _scalePointFromCenter(pt, scale, center):
- """scale a point from a center point"""
- pointX, pointY = pt
- scaleX, scaleY = scale
- centerX, centerY = center
- ogCenter = center
- scaledCenter = (centerX * scaleX, centerY * scaleY)
- shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1])
- scaledPointX = (pointX * scaleX) - shiftVal[0]
- scaledPointY = (pointY * scaleY) - shiftVal[1]
- return (scaledPointX, scaledPointY)
-
-def _box(objectToMeasure, fontObject=None):
- """calculate the bounds of the object and return it as a (xMin, yMin, xMax, yMax)"""
- #from fontTools.pens.boundsPen import BoundsPen
- from robofab.pens.boundsPen import BoundsPen
- boundsPen = BoundsPen(glyphSet=fontObject)
- objectToMeasure.draw(boundsPen)
- bounds = boundsPen.bounds
- if bounds is None:
- bounds = (0, 0, 0, 0)
- return bounds
-
-def roundPt(pt):
- """Round a vector"""
- return int(round(pt[0])), int(round(pt[1]))
-
-def addPt(ptA, ptB):
- """Add two vectors"""
- return ptA[0] + ptB[0], ptA[1] + ptB[1]
-
-def subPt(ptA, ptB):
- """Substract two vectors"""
- return ptA[0] - ptB[0], ptA[1] - ptB[1]
-
-def mulPt(ptA, scalar):
- """Multiply a vector with scalar"""
- if not isinstance(scalar, tuple):
- f1 = scalar
- f2 = scalar
- else:
- f1, f2 = scalar
- return ptA[0]*f1, ptA[1]*f2
-
-def relativeBCPIn(anchor, BCPIn):
- """convert absolute incoming bcp value to a relative value"""
- return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1])
-
-def absoluteBCPIn(anchor, BCPIn):
- """convert relative incoming bcp value to an absolute value"""
- return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1])
-
-def relativeBCPOut(anchor, BCPOut):
- """convert absolute outgoing bcp value to a relative value"""
- return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1])
-
-def absoluteBCPOut(anchor, BCPOut):
- """convert relative outgoing bcp value to an absolute value"""
- return (BCPOut[0] + anchor[0], BCPOut[1] + anchor[1])
-
-class FuzzyNumber(object):
-
- def __init__(self, value, threshold):
- self.value = value
- self.threshold = threshold
-
- def __cmp__(self, other):
- if abs(self.value - other.value) < self.threshold:
- return 0
- else:
- return cmp(self.value, other.value)
-
-
-class RBaseObject(object):
-
- """Base class for wrapper objects"""
-
- attrMap= {}
- _title = "RoboFab Wrapper"
-
- def __init__(self):
- self._object = {}
- self.changed = False # if the object needs to be saved
- self.selected = False
-
- def __len__(self):
- return len(self._object)
-
- def __repr__(self):
- try:
- name = `self._object`
- except:
- name = "None"
- return "<%s for %s>" %(self._title, name)
-
- def copy(self, aParent=None):
- """Duplicate this object. Pass an object for parenting if you want."""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['getParent']
- for k in self.__dict__.keys():
- if k in dont:
- continue
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- setattr(n, k, dup)
- return n
-
- def round(self):
- pass
-
- def isRobofab(self):
- """Presence of this method indicates a Robofab object"""
- return 1
-
- def naked(self):
- """Return the wrapped object itself, in case it is needed for direct access."""
- return self._object
-
- def setChanged(self, state=True):
- self.changed = state
-
- def getParent(self):
- """this method will be overwritten with a weakref if there is a parent."""
- return None
-
- def setParent(self, parent):
- import weakref
- self.getParent = weakref.ref(parent)
-
- def _writeXML(self, writer):
- pass
-
- def dump(self, private=False):
- """Print a dump of this object to the std out."""
- from robofab.tools.objectDumper import dumpObject
- dumpObject(self, private)
-
-
-
-class BaseFont(RBaseObject):
-
- """Base class for all font objects."""
-
- _allFonts = []
-
- def __init__(self):
- import weakref
- RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self._allFonts.append(weakref.ref(self))
- self._supportHints = False
-
- def __repr__(self):
- try:
- name = self.info.postscriptFullName
- except AttributeError:
- name = "unnamed_font"
- return "<RFont font for %s>" %(name)
-
- def __eq__(self, other):
- #Compare this font with another, compare if they refer to the same file.
- return self._compare(other)
-
- def _compare(self, other):
- """Compare this font to other. RF and FL UFO implementations need
- slightly different ways of comparing fonts. This method does the
- basic stuff. Start with simple and quick comparisons, then move into
- detailed comparisons of glyphs."""
- if not hasattr(other, "fileName"):
- return False
- if self.fileName is not None and self.fileName == other.fileName:
- return True
- if self.fileName <> other.fileName:
- return False
- # this will falsely identify two distinct "Untitled" as equal
- # so test some more. A lot of work to please some dolt who
- # does not save his fonts while running scripts.
- try:
- if len(self) <> len(other):
- return False
- except TypeError:
- return False
- # same name and length. start comparing glyphs
- namesSelf = self.keys()
- namesOther = other.keys()
- namesSelf.sort()
- namesOther.sort()
- for i in range(len(namesSelf)):
- if namesSelf[i] <> namesOther[i]:
- return False
- for c in self:
- if not c == other[c.name]:
- return False
- return True
-
- def keys(self):
- # must be implemented by subclass
- raise NotImplementedError
-
- def __iter__(self):
- for glyphName in self.keys():
- yield self.getGlyph(glyphName)
-
- def __getitem__(self, glyphName):
- return self.getGlyph(glyphName)
-
- def __contains__(self, glyphName):
- return self.has_key(glyphName)
-
- def _hasChanged(self):
- #mark the object as changed
- self.setChanged(True)
-
- def update(self):
- """update the font"""
- pass
-
- def close(self, save=1):
- """Close the font, saving is optional."""
- pass
-
- def round(self):
- """round all of the points in all of the glyphs"""
- for glyph in self:
- glyph.round()
-
- def autoUnicodes(self):
- """Using fontTools.agl, assign Unicode lists to all glyphs in the font"""
- for glyph in self:
- glyph.autoUnicodes()
-
- def getCharacterMapping(self):
- """Create a dictionary of unicode -> [glyphname, ...] mappings.
- Note that this dict is created each time this method is called,
- which can make it expensive for larger fonts. All glyphs are loaded.
- Note that one glyph can have multiple unicode values,
- and a unicode value can have multiple glyphs pointing to it."""
- map = {}
- for glyph in self:
- for u in glyph.unicodes:
- if not map.has_key(u):
- map[u] = []
- map[u].append(glyph.name)
- return map
-
- def getReverseComponentMapping(self):
- """
- Get a reversed map of component references in the font.
- {
- 'A' : ['Aacute', 'Aring']
- 'acute' : ['Aacute']
- 'ring' : ['Aring']
- etc.
- }
- """
- map = {}
- for glyph in self:
- glyphName = glyph.name
- for component in glyph.components:
- baseGlyphName = component.baseGlyph
- if not map.has_key(baseGlyphName):
- map[baseGlyphName] = []
- map[baseGlyphName].append(glyphName)
- return map
-
- def compileGlyph(self, glyphName, baseName, accentNames, \
- adjustWidth=False, preflight=False, printErrors=True):
- """Compile components into a new glyph using components and anchorpoints.
- glyphName: the name of the glyph where it all needs to go
- baseName: the name of the base glyph
- accentNames: a list of accentName, anchorName tuples, [('acute', 'top'), etc]
- """
- anchors = {}
- errors = {}
- baseGlyph = self[baseName]
- for anchor in baseGlyph.getAnchors():
- anchors[anchor.name] = anchor.position
- destGlyph = self.newGlyph(glyphName, clear=True)
- destGlyph.appendComponent(baseName)
- destGlyph.width = baseGlyph.width
- for accentName, anchorName in accentNames:
- try:
- accent = self[accentName]
- except IndexError:
- errors["glyph '%s' is missing in font %s"%(accentName, self.info.fullName)] = 1
- continue
- shift = None
- for accentAnchor in accent.getAnchors():
- if '_'+anchorName == accentAnchor.name:
- shift = anchors[anchorName][0] - accentAnchor.position[0], anchors[anchorName][1] - accentAnchor.position[1]
- destGlyph.appendComponent(accentName, offset=shift)
- break
- if shift is not None:
- for accentAnchor in accent.getAnchors():
- if accentAnchor.name in anchors:
- anchors[accentAnchor.name] = shift[0]+accentAnchor.position[0], shift[1]+accentAnchor.position[1]
- if printErrors:
- for px in errors.keys():
- print px
- return destGlyph
-
- def generateGlyph(self, glyphName, replace=1, preflight=False, printErrors=True):
- """Generate a glyph and return it. Assembled from GlyphConstruction.txt"""
- from robofab.tools.toolsAll import readGlyphConstructions
- con = readGlyphConstructions()
- entry = con.get(glyphName, None)
- if not entry:
- print "glyph '%s' is not listed in the robofab/Data/GlyphConstruction.txt"%(glyphName)
- return
- baseName = con[glyphName][0]
- parts = con[glyphName][1:]
- return self.compileGlyph(glyphName, baseName, parts, adjustWidth=1, preflight=preflight, printErrors=printErrors)
-
- def interpolate(self, factor, minFont, maxFont, suppressError=True, analyzeOnly=False, doProgress=False):
- """Traditional interpolation method. Interpolates by factor between minFont and maxFont.
- suppressError will supress all tracebacks and analyze only will not perform the interpolation
- but it will analyze all glyphs and return a dict of problems."""
- errors = {}
- if not isinstance(factor, tuple):
- factor = factor, factor
- minGlyphNames = minFont.keys()
- maxGlyphNames = maxFont.keys()
- allGlyphNames = list(set(minGlyphNames) | set(maxGlyphNames))
- if doProgress:
- from robofab.interface.all.dialogs import ProgressBar
- progress = ProgressBar('Interpolating...', len(allGlyphNames))
- tickCount = 0
- # some dimensions and values
- self.info.ascender = _interpolate(minFont.info.ascender, maxFont.info.ascender, factor[1])
- self.info.descender = _interpolate(minFont.info.descender, maxFont.info.descender, factor[1])
- # check for the presence of the glyph in each of the fonts
- for glyphName in allGlyphNames:
- if doProgress:
- progress.label(glyphName)
- fatalError = False
- if glyphName not in minGlyphNames:
- fatalError = True
- if not errors.has_key('Missing Glyphs'):
- errors['Missing Glyphs'] = []
- errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName))
- if glyphName not in maxGlyphNames:
- fatalError = True
- if not errors.has_key('Missing Glyphs'):
- errors['Missing Glyphs'] = []
- errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName))
- # if no major problems, proceed.
- if not fatalError:
- # remove the glyph since FontLab has a problem with
- # interpolating an existing glyph that contains
- # some contour data.
- oldLib = {}
- oldMark = None
- oldNote = None
- if self.has_key(glyphName):
- glyph = self[glyphName]
- oldLib = dict(glyph.lib)
- oldMark = glyph.mark
- oldNote = glyph.note
- self.removeGlyph(glyphName)
- selfGlyph = self.newGlyph(glyphName)
- selfGlyph.lib.update(oldLib)
- if oldMark != None:
- selfGlyph.mark = oldMark
- selfGlyph.note = oldNote
- min = minFont[glyphName]
- max = maxFont[glyphName]
- ok, glyphErrors = selfGlyph.interpolate(factor, min, max, suppressError=suppressError, analyzeOnly=analyzeOnly)
- if not errors.has_key('Glyph Errors'):
- errors['Glyph Errors'] = {}
- errors['Glyph Errors'][glyphName] = glyphErrors
- if doProgress:
- progress.tick(tickCount)
- tickCount = tickCount + 1
- if doProgress:
- progress.close()
- return errors
-
- def getGlyphNameToFileNameFunc(self):
- funcName = self.lib.get("org.robofab.glyphNameToFileNameFuncName")
- if funcName is None:
- return None
- parts = funcName.split(".")
- module = ".".join(parts[:-1])
- try:
- item = __import__(module)
- for sub in parts[1:]:
- item = getattr(item, sub)
- except (ImportError, AttributeError):
- warn("Can't find glyph name to file name converter function, "
- "falling back to default scheme (%s)" % funcName, RoboFabWarning)
- return None
- else:
- return item
-
-
-class BaseGlyph(RBaseObject):
-
- """Base class for all glyph objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- #self.contours = []
- #self.components = []
- #self.anchors = []
- #self.width = 0
- #self.note = None
- ##self.unicodes = []
- #self.selected = None
- self.changed = False # if the object needs to be saved
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- fontParent = self.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError:
- pass
- try:
- glyph = self.name
- except AttributeError:
- pass
- return "<RGlyph for %s.%s>" %(font, glyph)
-
- #
- # Glyph Math
- #
-
- def _getMathData(self):
- from robofab.pens.mathPens import GetMathDataPointPen
- pen = GetMathDataPointPen()
- self.drawPoints(pen)
- data = pen.getData()
- return data
-
- def _setMathData(self, data, destination=None):
- from robofab.pens.mathPens import CurveSegmentFilterPointPen
- if destination is None:
- newGlyph = self._mathCopy()
- else:
- newGlyph = destination
- newGlyph.clear()
- #
- # draw the data onto the glyph
- pointPen = newGlyph.getPointPen()
- filterPen = CurveSegmentFilterPointPen(pointPen)
- for contour in data['contours']:
- filterPen.beginPath()
- for segmentType, pt, smooth, name in contour:
- filterPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name)
- filterPen.endPath()
- for baseName, transformation in data['components']:
- filterPen.addComponent(baseName, transformation)
- for pt, name in data['anchors']:
- filterPen.beginPath()
- filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name)
- filterPen.endPath()
- newGlyph.width = data['width']
- psHints = data.get('psHints')
- if psHints is not None:
- newGlyph.psHints.update(psHints)
- #
- return newGlyph
-
- def _getMathDestination(self):
- # make a new, empty glyph
- return self.__class__()
-
- def _mathCopy(self):
- # copy self without contour, component and anchor data
- glyph = self._getMathDestination()
- glyph.name = self.name
- glyph.unicodes = list(self.unicodes)
- glyph.width = self.width
- glyph.note = self.note
- glyph.lib = dict(self.lib)
- return glyph
-
- def _processMathOne(self, otherGlyph, funct):
- # used by: __add__, __sub__
- #
- newData = {
- 'contours':[],
- 'components':[],
- 'anchors':[],
- 'width':None
- }
- selfData = self._getMathData()
- otherData = otherGlyph._getMathData()
- #
- # contours
- selfContours = selfData['contours']
- otherContours = otherData['contours']
- newContours = newData['contours']
- if len(selfContours) > 0:
- for contourIndex in xrange(len(selfContours)):
- newContours.append([])
- selfContour = selfContours[contourIndex]
- otherContour = otherContours[contourIndex]
- for pointIndex in xrange(len(selfContour)):
- segType, pt, smooth, name = selfContour[pointIndex]
- newX, newY = funct(selfContour[pointIndex][1], otherContour[pointIndex][1])
- newContours[-1].append((segType, (newX, newY), smooth, name))
- # anchors
- selfAnchors = selfData['anchors']
- otherAnchors = otherData['anchors']
- newAnchors = newData['anchors']
- if len(selfAnchors) > 0:
- selfAnchors, otherAnchors = self._mathAnchorCompare(selfAnchors, otherAnchors)
- anchorNames = selfAnchors.keys()
- for anchorName in anchorNames:
- selfAnchorList = selfAnchors[anchorName]
- otherAnchorList = otherAnchors[anchorName]
- for i in range(len(selfAnchorList)):
- selfAnchor = selfAnchorList[i]
- otherAnchor = otherAnchorList[i]
- newAnchor = funct(selfAnchor, otherAnchor)
- newAnchors.append((newAnchor, anchorName))
- # components
- selfComponents = selfData['components']
- otherComponents = otherData['components']
- newComponents = newData['components']
- if len(selfComponents) > 0:
- selfComponents, otherComponents = self._mathComponentCompare(selfComponents, otherComponents)
- componentNames = selfComponents.keys()
- for componentName in componentNames:
- selfComponentList = selfComponents[componentName]
- otherComponentList = otherComponents[componentName]
- for i in range(len(selfComponentList)):
- # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset
- selfXScale, selfXYScale, selfYXScale, selfYScale, selfXOffset, selfYOffset = selfComponentList[i]
- otherXScale, otherXYScale, otherYXScale, otherYScale, otherXOffset, otherYOffset = otherComponentList[i]
- newXScale, newXYScale = funct((selfXScale, selfXYScale), (otherXScale, otherXYScale))
- newYXScale, newYScale = funct((selfYXScale, selfYScale), (otherYXScale, otherYScale))
- newXOffset, newYOffset = funct((selfXOffset, selfYOffset), (otherXOffset, otherYOffset))
- newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset)))
- return newData
-
- def _processMathTwo(self, factor, funct):
- # used by: __mul__, __div__
- #
- newData = {
- 'contours':[],
- 'components':[],
- 'anchors':[],
- 'width':None
- }
- selfData = self._getMathData()
- # contours
- selfContours = selfData['contours']
- newContours = newData['contours']
- for selfContour in selfContours:
- newContours.append([])
- for segType, pt, smooth, name in selfContour:
- newX, newY = funct(pt, factor)
- newContours[-1].append((segType, (newX, newY), smooth, name))
- # anchors
- selfAnchors = selfData['anchors']
- newAnchors = newData['anchors']
- for pt, anchorName in selfAnchors:
- newPt = funct(pt, factor)
- newAnchors.append((newPt, anchorName))
- # components
- selfComponents = selfData['components']
- newComponents = newData['components']
- for baseName, transformation in selfComponents:
- xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation
- newXOffset, newYOffset = funct((xOffset, yOffset), factor)
- newXScale, newYScale = funct((xScale, yScale), factor)
- newXYScale, newYXScale = funct((xyScale, yxScale), factor)
- newComponents.append((baseName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset)))
- # return the data
- return newData
-
- def _mathAnchorCompare(self, selfMathAnchors, otherMathAnchors):
- # collect compatible anchors
- selfAnchors = {}
- for pt, name in selfMathAnchors:
- if not selfAnchors.has_key(name):
- selfAnchors[name] = []
- selfAnchors[name].append(pt)
- otherAnchors = {}
- for pt, name in otherMathAnchors:
- if not otherAnchors.has_key(name):
- otherAnchors[name] = []
- otherAnchors[name].append(pt)
- compatAnchors = set(selfAnchors.keys()) & set(otherAnchors.keys())
- finalSelfAnchors = {}
- finalOtherAnchors = {}
- for name in compatAnchors:
- if not finalSelfAnchors.has_key(name):
- finalSelfAnchors[name] = []
- if not finalOtherAnchors.has_key(name):
- finalOtherAnchors[name] = []
- selfList = selfAnchors[name]
- otherList = otherAnchors[name]
- selfCount = len(selfList)
- otherCount = len(otherList)
- if selfCount != otherCount:
- r = range(min(selfCount, otherCount))
- else:
- r = range(selfCount)
- for i in r:
- finalSelfAnchors[name].append(selfList[i])
- finalOtherAnchors[name].append(otherList[i])
- return finalSelfAnchors, finalOtherAnchors
-
- def _mathComponentCompare(self, selfMathComponents, otherMathComponents):
- # collect compatible components
- selfComponents = {}
- for baseName, transformation in selfMathComponents:
- if not selfComponents.has_key(baseName):
- selfComponents[baseName] = []
- selfComponents[baseName].append(transformation)
- otherComponents = {}
- for baseName, transformation in otherMathComponents:
- if not otherComponents.has_key(baseName):
- otherComponents[baseName] = []
- otherComponents[baseName].append(transformation)
- compatComponents = set(selfComponents.keys()) & set(otherComponents.keys())
- finalSelfComponents = {}
- finalOtherComponents = {}
- for baseName in compatComponents:
- if not finalSelfComponents.has_key(baseName):
- finalSelfComponents[baseName] = []
- if not finalOtherComponents.has_key(baseName):
- finalOtherComponents[baseName] = []
- selfList = selfComponents[baseName]
- otherList = otherComponents[baseName]
- selfCount = len(selfList)
- otherCount = len(otherList)
- if selfCount != otherCount:
- r = range(min(selfCount, otherCount))
- else:
- r = range(selfCount)
- for i in r:
- finalSelfComponents[baseName].append(selfList[i])
- finalOtherComponents[baseName].append(otherList[i])
- return finalSelfComponents, finalOtherComponents
-
- def __mul__(self, factor):
- assert isinstance(factor, (int, float, tuple)), "Glyphs can only be multiplied by int, float or a 2-tuple."
- if not isinstance(factor, tuple):
- factor = (factor, factor)
- data = self._processMathTwo(factor, mulPt)
- data['width'] = self.width * factor[0]
- # psHints
- if not self.psHints.isEmpty():
- newPsHints = self.psHints * factor
- data['psHints'] = newPsHints
- return self._setMathData(data)
-
- __rmul__ = __mul__
-
- def __div__(self, factor):
- assert isinstance(factor, (int, float, tuple)), "Glyphs can only be divided by int, float or a 2-tuple."
- # calculate reverse factor, and cause nice ZeroDivisionError if it can't
- if isinstance(factor, tuple):
- reverse = 1.0/factor[0], 1.0/factor[1]
- else:
- reverse = 1.0/factor
- return self.__mul__(reverse)
-
- def __add__(self, other):
- assert isinstance(other, BaseGlyph), "Glyphs can only be added to other glyphs."
- data = self._processMathOne(other, addPt)
- data['width'] = self.width + other.width
- return self._setMathData(data)
-
- def __sub__(self, other):
- assert isinstance(other, BaseGlyph), "Glyphs can only be substracted from other glyphs."
- data = self._processMathOne(other, subPt)
- data['width'] = self.width + other.width
- return self._setMathData(data)
-
- #
- # Interpolation
- #
-
- def interpolate(self, factor, minGlyph, maxGlyph, suppressError=True, analyzeOnly=False):
- """Traditional interpolation method. Interpolates by factor between minGlyph and maxGlyph.
- suppressError will supress all tracebacks and analyze only will not perform the interpolation
- but it will analyze all glyphs and return a dict of problems."""
- if not isinstance(factor, tuple):
- factor = factor, factor
- fatalError = False
- if analyzeOnly:
- ok, errors = minGlyph.isCompatible(maxGlyph)
- return ok, errors
- minData = None
- maxData = None
- minName = minGlyph.name
- maxName = maxGlyph.name
- try:
- minData = minGlyph._getMathData()
- maxData = maxGlyph._getMathData()
- newContours = self._interpolateContours(factor, minData['contours'], maxData['contours'])
- newComponents = self._interpolateComponents(factor, minData['components'], maxData['components'])
- newAnchors = self._interpolateAnchors(factor, minData['anchors'], maxData['anchors'])
- newWidth = _interpolate(minGlyph.width, maxGlyph.width, factor[0])
- newData = {
- 'contours':newContours,
- 'components':newComponents,
- 'anchors':newAnchors,
- 'width':newWidth
- }
- self._setMathData(newData, self)
- except IndexError:
- if not suppressError:
- ok, errors = minGlyph.isCompatible(maxGlyph)
- ok = not ok
- return ok, errors
- self.update()
- return False, []
-
- def isCompatible(self, otherGlyph, report=True):
- """Return a bool value if the glyph is compatible with otherGlyph.
- With report = True, isCompatible will return a report of what's wrong.
- The interpolate method requires absolute equality between contour data.
- Absolute equality is preferred among component and anchor data, but
- it is NOT required. Interpolation between components and anchors
- will only deal with compatible data and incompatible data will be
- ignored. This method reflects this system."""
- selfName = self.name
- selfData = self._getMathData()
- otherName = otherGlyph.name
- otherData = otherGlyph._getMathData()
- compatible, errors = self._isCompatibleInternal(selfName, otherName, selfData, otherData)
- if report:
- return compatible, errors
- return compatible
-
- def _isCompatibleInternal(self, selfName, otherName, selfData, otherData):
- fatalError = False
- errors = []
- ## contours
- # any contour incompatibilities
- # result in fatal errors
- selfContours = selfData['contours']
- otherContours = otherData['contours']
- if len(selfContours) != len(otherContours):
- fatalError = True
- errors.append("Fatal error: glyph %s and glyph %s don't have the same number of contours." %(selfName, otherName))
- else:
- for contourIndex in xrange(len(selfContours)):
- selfContour = selfContours[contourIndex]
- otherContour = otherContours[contourIndex]
- if len(selfContour) != len(otherContour):
- fatalError = True
- errors.append("Fatal error: contour %d in glyph %s and glyph %s don't have the same number of segments." %(contourIndex, selfName, otherName))
- ## components
- # component incompatibilities
- # do not result in fatal errors
- selfComponents = selfData['components']
- otherComponents = otherData['components']
- if len(selfComponents) != len(otherComponents):
- errors.append("Error: glyph %s and glyph %s don't have the same number of components." %(selfName, otherName))
- for componentIndex in xrange(min(len(selfComponents), len(otherComponents))):
- selfBaseName, selfTransformation = selfComponents[componentIndex]
- otherBaseName, otherTransformation = otherComponents[componentIndex]
- if selfBaseName != otherBaseName:
- errors.append("Error: component %d in glyph %s and glyph %s don't have the same base glyph." %(componentIndex, selfName, otherName))
- ## anchors
- # anchor incompatibilities
- # do not result in fatal errors
- selfAnchors = selfData['anchors']
- otherAnchors = otherData['anchors']
- if len(selfAnchors) != len(otherAnchors):
- errors.append("Error: glyph %s and glyph %s don't have the same number of anchors." %(selfName, otherName))
- for anchorIndex in xrange(min(len(selfAnchors), len(otherAnchors))):
- selfPt, selfAnchorName = selfAnchors[anchorIndex]
- otherPt, otherAnchorName = otherAnchors[anchorIndex]
- if selfAnchorName != otherAnchorName:
- errors.append("Error: anchor %d in glyph %s and glyph %s don't have the same name." %(anchorIndex, selfName, otherName))
- return not fatalError, errors
-
- def _interpolateContours(self, factor, minContours, maxContours):
- newContours = []
- for contourIndex in xrange(len(minContours)):
- minContour = minContours[contourIndex]
- maxContour = maxContours[contourIndex]
- newContours.append([])
- for pointIndex in xrange(len(minContour)):
- segType, pt, smooth, name = minContour[pointIndex]
- minPoint = minContour[pointIndex][1]
- maxPoint = maxContour[pointIndex][1]
- newX, newY = _interpolatePt(minPoint, maxPoint, factor)
- newContours[-1].append((segType, (newX, newY), smooth, name))
- return newContours
-
- def _interpolateComponents(self, factor, minComponents, maxComponents):
- newComponents = []
- minComponents, maxComponents = self._mathComponentCompare(minComponents, maxComponents)
- componentNames = minComponents.keys()
- for componentName in componentNames:
- minComponentList = minComponents[componentName]
- maxComponentList = maxComponents[componentName]
- for i in xrange(len(minComponentList)):
- # transformation breakdown: xScale, xyScale, yxScale, yScale, xOffset, yOffset
- minXScale, minXYScale, minYXScale, minYScale, minXOffset, minYOffset = minComponentList[i]
- maxXScale, maxXYScale, maxYXScale, maxYScale, maxXOffset, maxYOffset = maxComponentList[i]
- newXScale, newXYScale = _interpolatePt((minXScale, minXYScale), (maxXScale, maxXYScale), factor)
- newYXScale, newYScale = _interpolatePt((minYXScale, minYScale), (maxYXScale, maxYScale), factor)
- newXOffset, newYOffset = _interpolatePt((minXOffset, minYOffset), (maxXOffset, maxYOffset), factor)
- newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset)))
- return newComponents
-
- def _interpolateAnchors(self, factor, minAnchors, maxAnchors):
- newAnchors = []
- minAnchors, maxAnchors = self._mathAnchorCompare(minAnchors, maxAnchors)
- anchorNames = minAnchors.keys()
- for anchorName in anchorNames:
- minAnchorList = minAnchors[anchorName]
- maxAnchorList = maxAnchors[anchorName]
- for i in range(len(minAnchorList)):
- minAnchor = minAnchorList[i]
- maxAnchor = maxAnchorList[i]
- newAnchor = _interpolatePt(minAnchor, maxAnchor, factor)
- newAnchors.append((newAnchor, anchorName))
- return newAnchors
-
- #
- # comparisons
- #
-
- def __eq__(self, other):
- if isinstance(other, BaseGlyph):
- return self._getDigest() == other._getDigest()
- return False
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def _getDigest(self, pointsOnly=False):
- """Calculate a digest of coordinates, points, things in this glyph.
- With pointsOnly == True the digest consists of a flat tuple of all
- coordinate pairs in the glyph, without the order of contours.
- """
- from robofab.pens.digestPen import DigestPointPen
- mp = DigestPointPen()
- self.drawPoints(mp)
- if pointsOnly:
- return "%s|%d|%s"%(mp.getDigestPointsOnly(), self.width, self.unicode)
- else:
- return "%s|%d|%s"%(mp.getDigest(), self.width, self.unicode)
-
- def _getStructure(self):
- """Calculate a digest of points, things in this glyph, but NOT coordinates."""
- from robofab.pens.digestPen import DigestPointStructurePen
- mp = DigestPointStructurePen()
- self.drawPoints(mp)
- return mp.getDigest()
-
- def _hasChanged(self):
- """mark the object and it's parent as changed"""
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def _get_box(self):
- bounds = _box(self, fontObject=self.getParent())
- return bounds
-
- box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)")
-
- def _get_leftMargin(self):
- if self.isEmpty():
- return 0
- xMin, yMin, xMax, yMax = self.box
- return xMin
-
- def _set_leftMargin(self, value):
- if self.isEmpty():
- self.width = self.width + value
- else:
- diff = value - self.leftMargin
- self.move((diff, 0))
- self.width = self.width + diff
-
- leftMargin = property(_get_leftMargin, _set_leftMargin, doc="the left margin")
-
- def _get_rightMargin(self):
- if self.isEmpty():
- return self.width
- xMin, yMin, xMax, yMax = self.box
- return self.width - xMax
-
- def _set_rightMargin(self, value):
- if self.isEmpty():
- self.width = value
- else:
- xMin, yMin, xMax, yMax = self.box
- self.width = xMax + value
-
- rightMargin = property(_get_rightMargin, _set_rightMargin, doc="the right margin")
-
- def copy(self, aParent=None):
- """Duplicate this glyph"""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- dont = ['_object', 'getParent']
- for k in self.__dict__.keys():
- ok = True
- if k in dont:
- continue
- elif k == "contours":
- dup = []
- for i in self.contours:
- dup.append(i.copy(n))
- elif k == "components":
- dup = []
- for i in self.components:
- dup.append(i.copy(n))
- elif k == "anchors":
- dup = []
- for i in self.anchors:
- dup.append(i.copy(n))
- elif k == "psHints":
- dup = self.psHints.copy()
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- if ok:
- setattr(n, k, dup)
- return n
-
- def _setParentTree(self):
- """Set the parents of all contained and dependent objects (and their dependents) right."""
- for item in self.contours:
- item.setParent(self)
- item._setParentTree()
- for item in self.components:
- item.setParent(self)
- for items in self.anchors:
- item.setParent(self)
-
- def getGlyph(self, glyphName):
- """Provided there is a font parent for this glyph, return a sibling glyph."""
- if glyphName == self.name:
- return self
- if self.getParent() is not None:
- return self.getParent()[glyphName]
- return None
-
- def getPen(self):
- """Return a Pen object for creating an outline in this glyph."""
- from robofab.pens.adapterPens import SegmentToPointPen
- return SegmentToPointPen(self.getPointPen())
-
- def getPointPen(self):
- """Return a PointPen object for creating an outline in this glyph."""
- raise NotImplementedError, "getPointPen() must be implemented by subclass"
-
- def deSelect(self):
- """Set all selected attrs in glyph to False: for the glyph, components, anchors, points."""
- for a in self.anchors:
- a.selected = False
- for a in self.components:
- a.selected = False
- for c in self.contours:
- for p in c.points:
- p.selected = False
- self.selected = False
-
- def isEmpty(self):
- """return true if the glyph has no contours or components"""
- if len(self.contours) + len(self.components) == 0:
- return True
- else:
- return False
-
- def _saveToGlyphSet(self, glyphSet, glyphName=None, force=False):
- """Save the glyph to GlyphSet, a private method that's part of the saving process."""
- # save stuff in the lib first
- if force or self.changed:
- if glyphName is None:
- glyphName = self.name
- glyphSet.writeGlyph(glyphName, self, self.drawPoints)
-
- def update(self):
- """update the glyph"""
- pass
-
- def draw(self, pen):
- """draw the object with a RoboFab segment pen"""
- try:
- pen.setWidth(self.width)
- if self.note is not None:
- pen.setNote(self.note)
- except AttributeError:
- # FontTools pens don't have these methods
- pass
- for a in self.anchors:
- a.draw(pen)
- for c in self.contours:
- c.draw(pen)
- for c in self.components:
- c.draw(pen)
- try:
- pen.doneDrawing()
- except AttributeError:
- # FontTools pens don't have a doneDrawing() method
- pass
-
- def drawPoints(self, pen):
- """draw the object with a point pen"""
- for a in self.anchors:
- a.drawPoints(pen)
- for c in self.contours:
- c.drawPoints(pen)
- for c in self.components:
- c.drawPoints(pen)
-
- def appendContour(self, aContour, offset=(0, 0)):
- """append a contour to the glyph"""
- x, y = offset
- pen = self.getPointPen()
- aContour.drawPoints(pen)
- self.contours[-1].move((x, y))
-
- def appendGlyph(self, aGlyph, offset=(0, 0)):
- """append another glyph to the glyph"""
- x, y = offset
- pen = self.getPointPen()
- #to handle the offsets, move the source glyph and then move it back!
- aGlyph.move((x, y))
- aGlyph.drawPoints(pen)
- aGlyph.move((-x, -y))
-
- def round(self):
- """round all coordinates in all contours, components and anchors"""
- for n in self.contours:
- n.round()
- for n in self.components:
- n.round()
- for n in self.anchors:
- n.round()
- self.width = int(round(self.width))
-
- def autoUnicodes(self):
- """Using fontTools.agl, assign Unicode list to the glyph"""
- from fontTools.agl import AGL2UV
- if AGL2UV.has_key(self.name):
- self.unicode = AGL2UV[self.name]
- self._hasChanged()
-
- def pointInside(self, pt, evenOdd=0):
- """determine if the point is in the black or white of the glyph"""
- x, y = pt
- from fontTools.pens.pointInsidePen import PointInsidePen
- font = self.getParent()
- piPen = PointInsidePen(glyphSet=font, testPoint=(x, y), evenOdd=evenOdd)
- self.draw(piPen)
- return piPen.getResult()
-
- def correctDirection(self, trueType=False):
- """corect the direction of the contours in the glyph."""
- #this is a bit slow, but i'm not sure how much more it can be optimized.
- #it also has a bug somewhere that is causeing some contours to be set incorrectly.
- #try to run it on the copyright symbol to see the problem. hm.
- #
- #establish the default direction that an outer contour should follow
- #i believe for TT this is clockwise and for PS it is counter
- #i could be wrong about this, i need to double check.
- from fontTools.pens.pointInsidePen import PointInsidePen
- baseDirection = 0
- if trueType:
- baseDirection = 1
- #we don't need to do all the work if the contour count is < 2
- count = len(self.contours)
- if count == 0:
- return
- elif count == 1:
- self.contours[0].clockwise = baseDirection
- return
- #store up needed before we start
- #i think the .box calls are eating a big chunk of the time
- contourDict = {}
- for contourIndex in range(len(self.contours)):
- contour = self.contours[contourIndex]
- contourDict[contourIndex] = {'box':contour.box, 'dir':contour.clockwise, 'hit':[], 'notHit':[]}
- #now, for every contour, determine which contours it intersects
- #as we go, we will also store contours that it doesn't intersct
- #and we store this value for both contours
- allIndexes = contourDict.keys()
- for contourIndex in allIndexes:
- for otherContourIndex in allIndexes:
- if otherContourIndex != contourIndex:
- if contourIndex not in contourDict[otherContourIndex]['hit'] and contourIndex not in contourDict[otherContourIndex]['notHit']:
- xMin1, yMin1, xMax1, yMax1 = contourDict[contourIndex]['box']
- xMin2, yMin2, xMax2, yMax2= contourDict[otherContourIndex]['box']
- hit, pos = sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2))
- if hit == 1:
- contourDict[contourIndex]['hit'].append(otherContourIndex)
- contourDict[otherContourIndex]['hit'].append(contourIndex)
- else:
- contourDict[contourIndex]['notHit'].append(otherContourIndex)
- contourDict[otherContourIndex]['notHit'].append(contourIndex)
- #set up the pen here to shave a bit of time
- font = self.getParent()
- piPen = PointInsidePen(glyphSet=font, testPoint=(0, 0), evenOdd=0)
- #now do the pointInside work
- for contourIndex in allIndexes:
- direction = baseDirection
- contour = self.contours[contourIndex]
- startPoint = contour.segments[0].onCurve
- if startPoint is not None: #skip TT paths with no onCurve
- if len(contourDict[contourIndex]['hit']) != 0:
- for otherContourIndex in contourDict[contourIndex]['hit']:
- piPen.setTestPoint(testPoint=(startPoint.x, startPoint.y))
- otherContour = self.contours[otherContourIndex]
- otherContour.draw(piPen)
- direction = direction + piPen.getResult()
- newDirection = direction % 2
- #now set the direction if we need to
- if newDirection != contourDict[contourIndex]['dir']:
- contour.reverseContour()
-
- def autoContourOrder(self):
- """attempt to sort the contours based on their centers"""
- # sort is based on (in this order):
- # - the (negative) point count
- # - the (negative) segment count
- # - fuzzy x value of the center of the contour
- # - fuzzy y value of the center of the contour
- # - the (negative) surface of the bounding box of the contour: width * height
- # the latter is a safety net for for instances like a very thin 'O' where the
- # x centers could be close enough to rely on the y for the sort which could
- # very well be the same for both contours. We use the _negative_ of the surface
- # to ensure that larger contours appear first, which seems more natural.
- tempContourList = []
- contourList = []
- xThreshold = None
- yThreshold = None
- for contour in self.contours:
- xMin, yMin, xMax, yMax = contour.box
- width = xMax - xMin
- height = yMax - yMin
- xC = 0.5 * (xMin + xMax)
- yC = 0.5 * (yMin + yMax)
- xTh = abs(width * .5)
- yTh = abs(height * .5)
- if xThreshold is None or xThreshold > xTh:
- xThreshold = xTh
- if yThreshold is None or yThreshold > yTh:
- yThreshold = yTh
- tempContourList.append((-len(contour.points), -len(contour.segments), xC, yC, -(width * height), contour))
- for points, segments, x, y, surface, contour in tempContourList:
- contourList.append((points, segments, FuzzyNumber(x, xThreshold), FuzzyNumber(y, yThreshold), surface, contour))
- contourList.sort()
- for i in range(len(contourList)):
- points, segments, xO, yO, surface, contour = contourList[i]
- contour.index = i
-
- def rasterize(self, cellSize=50, xMin=None, yMin=None, xMax=None, yMax=None):
- """
- Slice the glyph into a grid based on the cell size.
- It returns a list of lists containing bool values
- that indicate the black (True) or white (False)
- value of that particular cell. These lists are
- arranged from top to bottom of the glyph and
- proceed from left to right.
- This is an expensive operation!
- """
- from fontTools.pens.pointInsidePen import PointInsidePen
- piPen = PointInsidePen(glyphSet=self.getParent(), testPoint=(0, 0), evenOdd=0)
- if xMin is None or yMin is None or xMax is None or yMax is None:
- _xMin, _yMin, _xMax, _yMax = self.box
- if xMin is None:
- xMin = _xMin
- if yMin is None:
- yMin = _yMin
- if xMax is None:
- xMax = _xMax
- if yMax is None:
- yMax = _yMax
- #
- hitXMax = False
- hitYMin = False
- xSlice = 0
- ySlice = 0
- halfCellSize = cellSize / 2.0
- #
- map = []
- #
- while not hitYMin:
- map.append([])
- yScan = -(ySlice * cellSize) + yMax - halfCellSize
- if yScan < yMin:
- hitYMin = True
- while not hitXMax:
- xScan = (xSlice * cellSize) + xMin - halfCellSize
- if xScan > xMax:
- hitXMax = True
- piPen.setTestPoint((xScan, yScan))
- self.draw(piPen)
- test = piPen.getResult()
- if test:
- map[-1].append(True)
- else:
- map[-1].append(False)
- xSlice = xSlice + 1
- hitXMax = False
- xSlice = 0
- ySlice = ySlice + 1
- return map
-
- def move(self, pt, contours=True, components=True, anchors=True):
- """Move a glyph's items that are flagged as True"""
- x, y = roundPt(pt)
- if contours:
- for contour in self.contours:
- contour.move((x, y))
- if components:
- for component in self.components:
- component.move((x, y))
- if anchors:
- for anchor in self.anchors:
- anchor.move((x, y))
-
- def scale(self, pt, center=(0, 0)):
- """scale the glyph"""
- x, y = pt
- for contour in self.contours:
- contour.scale((x, y), center=center)
- for component in self.components:
- offset = component.offset
- component.offset = _scalePointFromCenter(offset, pt, center)
- sX, sY = component.scale
- component.scale = (sX*x, sY*y)
- for anchor in self.anchors:
- anchor.scale((x, y), center=center)
-
- def transform(self, matrix):
- """Transform this glyph.
- Use a Transform matrix object from
- robofab.transform"""
- n = []
- for c in self.contours:
- c.transform(matrix)
- for a in self.anchors:
- a.transform(matrix)
-
- def rotate(self, angle, offset=None):
- """rotate the glyph"""
- from fontTools.misc.transform import Identity
- radAngle = angle / DEGREE # convert from degrees to radians
- if offset is None:
- offset = (0,0)
- rT = Identity.translate(offset[0], offset[1])
- rT = rT.rotate(radAngle)
- rT = rT.translate(-offset[0], -offset[1])
- self.transform(rT)
-
- def skew(self, angle, offset=None):
- """skew the glyph"""
- from fontTools.misc.transform import Identity
- radAngle = angle / DEGREE # convert from degrees to radians
- if offset is None:
- offset = (0,0)
- rT = Identity.translate(offset[0], offset[1])
- rT = rT.skew(radAngle)
- self.transform(rT)
-
-
-class BaseContour(RBaseObject):
-
- """Base class for all contour objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- #self.index = None
- self.changed = False # if the object needs to be saved
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- glyphParent = self.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- try:
- idx = `self.index`
- except ValueError:
- # XXXX
- idx = "XXX"
- return "<RContour for %s.%s[%s]>"%(font, glyph, idx)
-
- def __len__(self):
- return len(self.segments)
-
- def __mul__(self, factor):
- warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.segments = []
- for i in range(len(self.segments)):
- n.segments.append(self.segments[i] * factor)
- n._setParentTree()
- return n
-
- __rmul__ = __mul__
-
- def __add__(self, other):
- warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.segments = []
- for i in range(len(self.segments)):
- n.segments.append(self.segments[i] + other.segments[i])
- n._setParentTree()
- return n
-
- def __sub__(self, other):
- warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.segments = []
- for i in range(len(self.segments)):
- n.segments.append(self.segments[i] - other.segments[i])
- n._setParentTree()
- return n
-
- def __getitem__(self, index):
- return self.segments[index]
-
- def _hasChanged(self):
- """mark the object and it's parent as changed"""
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def _nextSegment(self, segmentIndex):
- return self.segments[(segmentIndex + 1) % len(self.segments)]
-
- def _prevSegment(self, segmentIndex):
- segments = self.segments
- return self.segments[(segmentIndex - 1) % len(self.segments)]
-
- def _get_box(self):
- bounds = _box(self)
- return bounds
-
- box = property(_get_box, doc="the bounding box for the contour")
-
- def _set_clockwise(self, value):
- if self.clockwise != value:
- self.reverseContour()
-
- def _get_clockwise(self):
- pen = AreaPen(self)
- self.draw(pen)
- return pen.value < 0
-
- clockwise = property(_get_clockwise, _set_clockwise, doc="direction of contour: positive=counterclockwise negative=clockwise")
-
- def copy(self, aParent=None):
- """Duplicate this contour"""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['_object', 'points', 'bPoints', 'getParent']
- for k in self.__dict__.keys():
- ok = True
- if k in dont:
- continue
- elif k == "segments":
- dup = []
- for i in self.segments:
- dup.append(i.copy(n))
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- if ok:
- setattr(n, k, dup)
- return n
-
- def _setParentTree(self):
- """Set the parents of all contained and dependent objects (and their dependents) right."""
- for item in self.segments:
- item.setParent(self)
-
- def round(self):
- """round the value of all points in the contour"""
- for n in self.points:
- n.round()
-
- def draw(self, pen):
- """draw the object with a fontTools pen"""
- firstOn = self.segments[0].onCurve
- firstType = self.segments[0].type
- lastOn = self.segments[-1].onCurve
- # this is a special exception for FontLab
- # FL can have a contour that does not contain a move.
- # this will only happen if the contour begins with a qcurve.
- # in this case, we move to the segment's on curve,
- # then we iterate through the rest of the points,
- # then we add the first qcurve and finally we
- # close the path. after this, i say "ugh."
- if firstType == QCURVE:
- pen.moveTo((firstOn.x, firstOn.y))
- for segment in self.segments[1:]:
- segmentType = segment.type
- pt = segment.onCurve.x, segment.onCurve.y
- if segmentType == LINE:
- pen.lineTo(pt)
- elif segmentType == CURVE:
- pts = [(point.x, point.y) for point in segment.points]
- pen.curveTo(*pts)
- elif segmentType == QCURVE:
- pts = [(point.x, point.y) for point in segment.points]
- pen.qCurveTo(*pts)
- else:
- assert 0, "unsupported segment type"
- pts = [(point.x, point.y) for point in self.segments[0].points]
- pen.qCurveTo(*pts)
- pen.closePath()
- else:
- if firstType == MOVE and (firstOn.x, firstOn.y) == (lastOn.x, lastOn.y):
- closed = True
- else:
- closed = True
- for segment in self.segments:
- segmentType = segment.type
- pt = segment.onCurve.x, segment.onCurve.y
- if segmentType == MOVE:
- pen.moveTo(pt)
- elif segmentType == LINE:
- pen.lineTo(pt)
- elif segmentType == CURVE:
- pts = [(point.x, point.y) for point in segment.points]
- pen.curveTo(*pts)
- elif segmentType == QCURVE:
- pts = [(point.x, point.y) for point in segment.points]
- pen.qCurveTo(*pts)
- else:
- assert 0, "unsupported segment type"
- if closed:
- pen.closePath()
- else:
- pen.endPath()
-
- def drawPoints(self, pen):
- """draw the object with a point pen"""
- pen.beginPath()
- lastOn = self.segments[-1].onCurve
- didLastOn = False
- flQCurveException = False
- lastIndex = len(self.segments) - 1
- for i in range(len(self.segments)):
- segment = self.segments[i]
- segmentType = segment.type
- # the new protocol states that we start with an onCurve
- # so, if we have a move and a nd a last point overlapping,
- # add the last point to the beginning and skip the move
- if segmentType == MOVE and (segment.onCurve.x, segment.onCurve.y) == (lastOn.x, lastOn.y):
- point = self.segments[-1].onCurve
- name = getattr(segment.onCurve, 'name', None)
- pen.addPoint((point.x, point.y), point.type, smooth=self.segments[-1].smooth, name=name)
- didLastOn = True
- continue
- # this is an exception for objectsFL
- # the problem is that quad contours are
- # represented differently that they are in
- # objectsRF:
- # FL: [qcurve, qcurve, qcurve, qcurve]
- # RF: [move, qcurve, qcurve, qcurve, qcurve]
- # so, we need to catch this, and shift the offCurves to
- # to the end of the contour
- if i == 0 and segmentType == QCURVE:
- flQCurveException = True
- if segmentType == MOVE:
- segmentType = LINE
- ## the offCurves
- if i == 0 and flQCurveException:
- pass
- else:
- for point in segment.offCurve:
- name = getattr(point, 'name', None)
- pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected)
- ## the onCurve
- # skip the last onCurve if it was used as the move
- if i == lastIndex and didLastOn:
- continue
- point = segment.onCurve
- name = getattr(point, 'name', None)
- pen.addPoint((point.x, point.y), segmentType, smooth=segment.smooth, name=name, selected=point.selected)
- # if we have the special qCurve case with objectsFL
- # take care of the offCurves associated with the first contour
- if flQCurveException:
- for point in self.segments[0].offCurve:
- name = getattr(point, 'name', None)
- pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected)
- pen.endPath()
-
- def move(self, pt):
- """move the contour"""
- #this will be faster if we go straight to the points
- for point in self.points:
- point.move(pt)
-
- def scale(self, pt, center=(0, 0)):
- """scale the contour"""
- #this will be faster if we go straight to the points
- for point in self.points:
- point.scale(pt, center=center)
-
- def transform(self, matrix):
- """Transform this contour.
- Use a Transform matrix object from
- robofab.transform"""
- n = []
- for s in self.segments:
- s.transform(matrix)
-
- def rotate(self, angle, offset=None):
- """rotate the contour"""
- from fontTools.misc.transform import Identity
- radAngle = angle / DEGREE # convert from degrees to radians
- if offset is None:
- offset = (0,0)
- rT = Identity.translate(offset[0], offset[1])
- rT = rT.rotate(radAngle)
- self.transform(rT)
-
- def skew(self, angle, offset=None):
- """skew the contour"""
- from fontTools.misc.transform import Identity
- radAngle = angle / DEGREE # convert from degrees to radians
- if offset is None:
- offset = (0,0)
- rT = Identity.translate(offset[0], offset[1])
- rT = rT.skew(radAngle)
- self.transform(rT)
-
- def pointInside(self, pt, evenOdd=0):
- """determine if the point is inside or ouside of the contour"""
- from fontTools.pens.pointInsidePen import PointInsidePen
- glyph = self.getParent()
- font = glyph.getParent()
- piPen = PointInsidePen(glyphSet=font, testPoint=pt, evenOdd=evenOdd)
- self.draw(piPen)
- return piPen.getResult()
-
- def autoStartSegment(self):
- """automatically set the lower left point of the contour as the first point."""
- #adapted from robofog
- startIndex = 0
- startSegment = self.segments[0]
- for i in range(len(self.segments)):
- segment = self.segments[i]
- startOn = startSegment.onCurve
- on = segment.onCurve
- if on.y <= startOn.y:
- if on.y == startOn.y:
- if on.x < startOn.x:
- startSegment = segment
- startIndex = i
- else:
- startSegment = segment
- startIndex = i
- if startIndex != 0:
- self.setStartSegment(startIndex)
-
- def appendBPoint(self, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)):
- """append a bPoint to the contour"""
- self.insertBPoint(len(self.segments), pointType=pointType, anchor=anchor, bcpIn=bcpIn, bcpOut=bcpOut)
-
- def insertBPoint(self, index, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)):
- """insert a bPoint at index on the contour"""
- #insert a CURVE point that we can work with
- nextSegment = self._nextSegment(index-1)
- if nextSegment.type == QCURVE:
- return
- if nextSegment.type == MOVE:
- prevSegment = self.segments[index-1]
- prevOn = prevSegment.onCurve
- if bcpIn != (0, 0):
- new = self.appendSegment(CURVE, [(prevOn.x, prevOn.y), absoluteBCPIn(anchor, bcpIn), anchor], smooth=False)
- if pointType == CURVE:
- new.smooth = True
- else:
- new = self.appendSegment(LINE, [anchor], smooth=False)
- #if the user wants an outgoing bcp, we must add a CURVE ontop of the move
- if bcpOut != (0, 0):
- nextOn = nextSegment.onCurve
- self.appendSegment(CURVE, [absoluteBCPOut(anchor, bcpOut), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False)
- else:
- #handle the bcps
- if nextSegment.type != CURVE:
- prevSegment = self.segments[index-1]
- prevOn = prevSegment.onCurve
- prevOutX, prevOutY = (prevOn.x, prevOn.y)
- else:
- prevOut = nextSegment.offCurve[0]
- prevOutX, prevOutY = (prevOut.x, prevOut.y)
- self.insertSegment(index, segmentType=CURVE, points=[(prevOutX, prevOutY), anchor, anchor], smooth=False)
- newSegment = self.segments[index]
- prevSegment = self._prevSegment(index)
- nextSegment = self._nextSegment(index)
- if nextSegment.type == MOVE:
- raise RoboFabError, 'still working out curving at the end of a contour'
- elif nextSegment.type == QCURVE:
- return
- #set the new incoming bcp
- newIn = newSegment.offCurve[1]
- nIX, nIY = absoluteBCPIn(anchor, bcpIn)
- newIn.x = nIX
- newIn.y = nIY
- #set the new outgoing bcp
- hasCurve = True
- if nextSegment.type != CURVE:
- if bcpOut != (0, 0):
- nextSegment.type = CURVE
- hasCurve = True
- else:
- hasCurve = False
- if hasCurve:
- newOut = nextSegment.offCurve[0]
- nOX, nOY = absoluteBCPOut(anchor, bcpOut)
- newOut.x = nOX
- newOut.y = nOY
- #now check to see if we can convert the CURVE segment to a LINE segment
- newAnchor = newSegment.onCurve
- newA = newSegment.offCurve[0]
- newB = newSegment.offCurve[1]
- nextAnchor = nextSegment.onCurve
- prevAnchor = prevSegment.onCurve
- if (prevAnchor.x, prevAnchor.y) == (newA.x, newA.y) and (newAnchor.x, newAnchor.y) == (newB.x, newB.y):
- newSegment.type = LINE
- #the user wants a smooth segment
- if pointType == CURVE:
- newSegment.smooth = True
-
-
-class BaseSegment(RBaseObject):
-
- """Base class for all segment objects"""
-
- def __init__(self):
- self.changed = False
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- contourIndex = "unknown_contour"
- contourParent = self.getParent()
- if contourParent is not None:
- try:
- contourIndex = `contourParent.index`
- except AttributeError: pass
- glyphParent = contourParent.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- try:
- idx = `self.index`
- except ValueError:
- idx = "XXX"
- return "<RSegment for %s.%s[%s][%s]>"%(font, glyph, contourIndex, idx)
-
- def __mul__(self, factor):
- warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.points = []
- for i in range(len(self.points)):
- n.points.append(self.points[i] * factor)
- n._setParentTree()
- return n
-
- __rmul__ = __mul__
-
- def __add__(self, other):
- warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.points = []
- for i in range(len(self.points)):
- n.points.append(self.points[i] + other.points[i])
- return n
-
- def __sub__(self, other):
- warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning)
- n = self.copy()
- n.points = []
- for i in range(len(self.points)):
- n.points.append(self.points[i] - other.points[i])
- return n
-
- def _hasChanged(self):
- """mark the object and it's parent as changed"""
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def copy(self, aParent=None):
- """Duplicate this segment"""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['_object', 'getParent', 'offCurve', 'onCurve']
- for k in self.__dict__.keys():
- ok = True
- if k in dont:
- continue
- if k == "points":
- dup = []
- for i in self.points:
- dup.append(i.copy(n))
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- if ok:
- setattr(n, k, dup)
- return n
-
- def _setParentTree(self):
- """Set the parents of all contained and dependent objects (and their dependents) right."""
- for item in self.points:
- item.setParent(self)
-
- def round(self):
- """round all points in the segment"""
- for point in self.points:
- point.round()
-
- def move(self, pt):
- """move the segment"""
- for point in self.points:
- point.move(pt)
-
- def scale(self, pt, center=(0, 0)):
- """scale the segment"""
- for point in self.points:
- point.scale(pt, center=center)
-
- def transform(self, matrix):
- """Transform this segment.
- Use a Transform matrix object from
- robofab.transform"""
- n = []
- for p in self.points:
- p.transform(matrix)
-
- def _get_onCurve(self):
- return self.points[-1]
-
- def _get_offCurve(self):
- return self.points[:-1]
-
- offCurve = property(_get_offCurve, doc="on curve point for the segment")
- onCurve = property(_get_onCurve, doc="list of off curve points for the segment")
-
-
-
-class BasePoint(RBaseObject):
-
- """Base class for point objects."""
-
- def __init__(self):
- #RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self.selected = False
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- contourIndex = "unknown_contour"
- segmentIndex = "unknown_segment"
- segmentParent = self.getParent()
- if segmentParent is not None:
- try:
- segmentIndex = `segmentParent.index`
- except AttributeError: pass
- contourParent = self.getParent().getParent()
- if contourParent is not None:
- try:
- contourIndex = `contourParent.index`
- except AttributeError: pass
- glyphParent = contourParent.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex)
-
- def __add__(self, other):
- warn("Point math has been deprecated and is slated for removal.", DeprecationWarning)
- #Add one point to another
- n = self.copy()
- n.x, n.y = addPt((self.x, self.y), (other.x, other.y))
- return n
-
- def __sub__(self, other):
- warn("Point math has been deprecated and is slated for removal.", DeprecationWarning)
- #Subtract one point from another
- n = self.copy()
- n.x, n.y = subPt((self.x, self.y), (other.x, other.y))
- return n
-
- def __mul__(self, factor):
- warn("Point math has been deprecated and is slated for removal.", DeprecationWarning)
- #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2)
- n = self.copy()
- n.x, n.y = mulPt((self.x, self.y), factor)
- return n
-
- __rmul__ = __mul__
-
- def _hasChanged(self):
- #mark the object and it's parent as changed
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def copy(self, aParent=None):
- """Duplicate this point"""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['getParent', 'offCurve', 'onCurve']
- for k in self.__dict__.keys():
- ok = True
- if k in dont:
- continue
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- if ok:
- setattr(n, k, dup)
- return n
-
- def select(self, state=True):
- """Set the selection of this point.
- XXXX This method should be a lot more versatile, dealing with
- different kinds of selection, select the bcp's seperately etc.
- But that's for later when we need it more. For now it's just
- one flag for the entire thing."""
- self.selected = state
-
- def round(self):
- """round the values in the point"""
- self.x, self.y = roundPt((self.x, self.y))
-
- def move(self, pt):
- """Move the point"""
- self.x, self.y = addPt((self.x, self.y), pt)
-
- def scale(self, pt, center=(0, 0)):
- """scale the point"""
- nX, nY = _scalePointFromCenter((self.x, self.y), pt, center)
- self.x = nX
- self.y = nY
-
- def transform(self, matrix):
- """Transform this point. Use a Transform matrix
- object from fontTools.misc.transform"""
- self.x, self.y = matrix.transformPoint((self.x, self.y))
-
-
-class BaseBPoint(RBaseObject):
-
- """Base class for bPoints objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self.selected = False
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- contourIndex = "unknown_contour"
- segmentIndex = "unknown_segment"
- segmentParent = self.getParent()
- if segmentParent is not None:
- try:
- segmentIndex = `segmentParent.index`
- except AttributeError: pass
- contourParent = segmentParent.getParent()
- if contourParent is not None:
- try:
- contourIndex = `contourParent.index`
- except AttributeError: pass
- glyphParent = contourParent.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`)
-
-
- def __add__(self, other):
- warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning)
- #Add one bPoint to another
- n = self.copy()
- n.anchor = addPt(self.anchor, other.anchor)
- n.bcpIn = addPt(self.bcpIn, other.bcpIn)
- n.bcpOut = addPt(self.bcpOut, other.bcpOut)
- return n
-
- def __sub__(self, other):
- warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning)
- #Subtract one bPoint from another
- n = self.copy()
- n.anchor = subPt(self.anchor, other.anchor)
- n.bcpIn = subPt(self.bcpIn, other.bcpIn)
- n.bcpOut = subPt(self.bcpOut, other.bcpOut)
- return n
-
- def __mul__(self, factor):
- warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning)
- #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2)
- n = self.copy()
- n.anchor = mulPt(self.anchor, factor)
- n.bcpIn = mulPt(self.bcpIn, factor)
- n.bcpOut = mulPt(self.bcpOut, factor)
- return n
-
- __rmul__ = __mul__
-
- def _hasChanged(self):
- #mark the object and it's parent as changed
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def select(self, state=True):
- """Set the selection of this point.
- XXXX This method should be a lot more versatile, dealing with
- different kinds of selection, select the bcp's seperately etc.
- But that's for later when we need it more. For now it's just
- one flag for the entire thing."""
- self.selected = state
-
- def round(self):
- """Round the coordinates to integers"""
- self.anchor = roundPt(self.anchor)
- pSeg = self._parentSegment
- if pSeg.type != MOVE:
- self.bcpIn = roundPt(self.bcpIn)
- if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE:
- self.bcpOut = roundPt(self.bcpOut)
-
- def move(self, pt):
- """move the bPoint"""
- x, y = pt
- bcpIn = self.bcpIn
- bcpOut = self.bcpOut
- self.anchor = (self.anchor[0] + x, self.anchor[1] + y)
- pSeg = self._parentSegment
- if pSeg.type != MOVE:
- self.bcpIn = bcpIn
- if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE:
- self.bcpOut = bcpOut
-
- def scale(self, pt, center=(0, 0)):
- """scale the bPoint"""
- x, y = pt
- centerX, centerY = center
- ogCenter = (centerX, centerY)
- scaledCenter = (centerX * x, centerY * y)
- shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1])
- anchor = self.anchor
- bcpIn = self.bcpIn
- bcpOut = self.bcpOut
- self.anchor = ((anchor[0] * x) - shiftVal[0], (anchor[1] * y) - shiftVal[1])
- pSeg = self._parentSegment
- if pSeg.type != MOVE:
- self.bcpIn = ((bcpIn[0] * x), (bcpIn[1] * y))
- if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE:
- self.bcpOut = ((bcpOut[0] * x), (bcpOut[1] * y))
-
- def transform(self, matrix):
- """Transform this point. Use a Transform matrix
- object from fontTools.misc.transform"""
- self.anchor = matrix.transformPoint(self.anchor)
- pSeg = self._parentSegment
- if pSeg.type != MOVE:
- self.bcpIn = matrix.transformPoint(self.bcpIn)
- if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE:
- self.bcpOut = matrix.transformPoint(self.bcpOut)
-
- def _get__anchorPoint(self):
- return self._parentSegment.onCurve
-
- _anchorPoint = property(_get__anchorPoint, doc="the oncurve point in the parent segment")
-
- def _get_anchor(self):
- point = self._anchorPoint
- return (point.x, point.y)
-
- def _set_anchor(self, value):
- x, y = value
- point = self._anchorPoint
- point.x = x
- point.y = y
-
- anchor = property(_get_anchor, _set_anchor, doc="the position of the anchor")
-
- def _get_bcpIn(self):
- pSeg = self._parentSegment
- pCount = len(pSeg.offCurve)
- if pCount == 2:
- p = pSeg.offCurve[1]
- pOn = pSeg.onCurve
- return relativeBCPIn((pOn.x, pOn.y), (p.x, p.y))
- else:
- return (0, 0)
-
- def _set_bcpIn(self, value):
- x, y = (absoluteBCPIn(self.anchor, value))
- pSeg = self._parentSegment
- if pSeg.type == MOVE:
- #the user wants to have a bcp leading into the MOVE
- if value == (0, 0) and self.bcpOut == (0, 0):
- #we have a straight line between the two anchors
- pass
- else:
- #we need to insert a new CURVE segment ontop of the move
- contour = self._parentSegment.getParent()
- #set the prev segment outgoing bcp to the onCurve
- prevSeg = contour._prevSegment(self._parentSegment.index)
- prevOn = prevSeg.onCurve
- contour.appendSegment(CURVE, [(prevOn.x, prevOn.y), (x, y), self.anchor], smooth=False)
- else:
- pCount = len(pSeg.offCurve)
- if pCount == 2:
- #if the two points in the offCurvePoints list are located at the
- #anchor coordinates we can switch to a LINE segment type
- if value == (0, 0) and self.bcpOut == (0, 0):
- pSeg.type = LINE
- pSeg.smooth = False
- else:
- pSeg.offCurve[1].x = x
- pSeg.offCurve[1].y = y
- elif value != (0, 0):
- pSeg.type = CURVE
- pSeg.offCurve[1].x = x
- pSeg.offCurve[1].y = y
-
- bcpIn = property(_get_bcpIn, _set_bcpIn, doc="the (x,y) for the incoming bcp")
-
- def _get_bcpOut(self):
- pSeg = self._parentSegment
- nextSeg = pSeg.getParent()._nextSegment(pSeg.index)
- nsCount = len(nextSeg.offCurve)
- if nsCount == 2:
- p = nextSeg.offCurve[0]
- return relativeBCPOut(self.anchor, (p.x, p.y))
- else:
- return (0, 0)
-
- def _set_bcpOut(self, value):
- x, y = (absoluteBCPOut(self.anchor, value))
- pSeg = self._parentSegment
- nextSeg = pSeg.getParent()._nextSegment(pSeg.index)
- if nextSeg.type == MOVE:
- if value == (0, 0) and self.bcpIn == (0, 0):
- pass
- else:
- #we need to insert a new CURVE segment ontop of the move
- contour = self._parentSegment.getParent()
- nextOn = nextSeg.onCurve
- contour.appendSegment(CURVE, [(x, y), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False)
- else:
- nsCount = len(nextSeg.offCurve)
- if nsCount == 2:
- #if the two points in the offCurvePoints list are located at the
- #anchor coordinates we can switch to a LINE segment type
- if value == (0, 0) and self.bcpIn == (0, 0):
- nextSeg.type = LINE
- nextSeg.smooth = False
- else:
- nextSeg.offCurve[0].x = x
- nextSeg.offCurve[0].y = y
- elif value != (0, 0):
- nextSeg.type = CURVE
- nextSeg.offCurve[0].x = x
- nextSeg.offCurve[0].y = y
-
- bcpOut = property(_get_bcpOut, _set_bcpOut, doc="the (x,y) for the outgoing bcp")
-
- def _get_type(self):
- pType = self._parentSegment.type
- bpType = CORNER
- if pType == CURVE:
- if self._parentSegment.smooth:
- bpType = CURVE
- return bpType
-
- def _set_type(self, pointType):
- pSeg = self._parentSegment
- segType = pSeg.type
- #user wants a curve where there is a line
- if pointType == CURVE and segType == LINE:
- pSeg.type = CURVE
- pSeg.smooth = True
- #the anchor is a curve segment. so, all we need to do is turn the smooth off
- elif pointType == CORNER and segType == CURVE:
- pSeg.smooth = False
-
- type = property(_get_type, _set_type, doc="the type of bPoint, either 'corner' or 'curve'")
-
-
-class BaseComponent(RBaseObject):
-
- """Base class for all component objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self.selected = False
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- glyphParent = self.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`)
-
- def _hasChanged(self):
- """mark the object and it's parent as changed"""
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def copy(self, aParent=None):
- """Duplicate this component."""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['getParent', '_object']
- for k in self.__dict__.keys():
- if k in dont:
- continue
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- setattr(n, k, dup)
- return n
-
- def __add__(self, other):
- warn("Component math has been deprecated and is slated for removal.", DeprecationWarning)
- #Add one Component to another
- n = self.copy()
- n.offset = addPt(self.offset, other.offset)
- n.scale = addPt(self.scale, other.scale)
- return n
-
- def __sub__(self, other):
- warn("Component math has been deprecated and is slated for removal.", DeprecationWarning)
- #Subtract one Component from another
- n = self.copy()
- n.offset = subPt(self.offset, other.offset)
- n.scale = subPt(self.scale, other.scale)
- return n
-
- def __mul__(self, factor):
- warn("Component math has been deprecated and is slated for removal.", DeprecationWarning)
- #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2)
- n = self.copy()
- n.offset = mulPt(self.offset, factor)
- n.scale = mulPt(self.scale, factor)
- return n
-
- __rmul__ = __mul__
-
- def _get_box(self):
- parentGlyph = self.getParent()
- # the component is an orphan
- if parentGlyph is None:
- return None
- parentFont = parentGlyph.getParent()
- # the glyph that contains the component
- # does not hae a parent
- if parentFont is None:
- return None
- # the font does not have a glyph
- # that matches the glyph that
- # this component references
- if not parentFont.has_key(self.baseGlyph):
- return None
- return _box(self, parentFont)
-
- box = property(_get_box, doc="the bounding box of the component: (xMin, yMin, xMax, yMax)")
-
- def round(self):
- """round the offset values"""
- self.offset = roundPt(self.offset)
- self._hasChanged()
-
- def draw(self, pen):
- """Segment pen drawing method."""
- if isinstance(pen, AbstractPen):
- # It's a FontTools pen, which for addComponent is identical
- # to PointPen.
- self.drawPoints(pen)
- else:
- # It's an "old" 'Fab pen
- pen.addComponent(self.baseGlyph, self.offset, self.scale)
-
- def drawPoints(self, pen):
- """draw the object with a point pen"""
- oX, oY = self.offset
- sX, sY = self.scale
- #xScale, xyScale, yxScale, yScale, xOffset, yOffset
- pen.addComponent(self.baseGlyph, (sX, 0, 0, sY, oX, oY))
-
-
-class BaseAnchor(RBaseObject):
-
- """Base class for all anchor point objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self.selected = False
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- glyphParent = self.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`)
-
- def __add__(self, other):
- warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning)
- #Add one anchor to another
- n = self.copy()
- n.x, n.y = addPt((self.x, self.y), (other.x, other.y))
- return n
-
- def __sub__(self, other):
- warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning)
- #Substract one anchor from another
- n = self.copy()
- n.x, n.y = subPt((self.x, self.y), (other.x, other.y))
- return n
-
- def __mul__(self, factor):
- warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning)
- #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2)
- n = self.copy()
- n.x, n.y = mulPt((self.x, self.y), factor)
- return n
-
- __rmul__ = __mul__
-
- def _hasChanged(self):
- #mark the object and it's parent as changed
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def copy(self, aParent=None):
- """Duplicate this anchor."""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- dont = ['getParent', '_object']
- for k in self.__dict__.keys():
- if k in dont:
- continue
- elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)):
- dup = self.__dict__[k].copy(n)
- else:
- dup = copy.deepcopy(self.__dict__[k])
- setattr(n, k, dup)
- return n
-
- def round(self):
- """round the values in the anchor"""
- self.x, self.y = roundPt((self.x, self.y))
- self._hasChanged()
-
- def draw(self, pen):
- """Draw the object onto a segment pen"""
- if isinstance(pen, AbstractPen):
- # It's a FontTools pen
- pen.moveTo((self.x, self.y))
- pen.endPath()
- else:
- # It's an "old" 'Fab pen
- pen.addAnchor(self.name, (self.x, self.y))
-
- def drawPoints(self, pen):
- """draw the object with a point pen"""
- pen.beginPath()
- pen.addPoint((self.x, self.y), segmentType="move", smooth=False, name=self.name)
- pen.endPath()
-
- def move(self, pt):
- """Move the anchor"""
- x, y = pt
- pX, pY = self.position
- self.position = (pX+x, pY+y)
-
- def scale(self, pt, center=(0, 0)):
- """scale the anchor"""
- pos = self.position
- self.position = _scalePointFromCenter(pos, pt, center)
-
- def transform(self, matrix):
- """Transform this anchor. Use a Transform matrix
- object from fontTools.misc.transform"""
- self.x, self.y = matrix.transformPoint((self.x, self.y))
-
-
-class BaseGuide(RBaseObject):
-
- """Base class for all guide objects."""
-
- def __init__(self):
- RBaseObject.__init__(self)
- self.changed = False # if the object needs to be saved
- self.selected = False
-
-
-class BaseInfo(RBaseObject):
-
- _baseAttributes = ["_object", "changed", "selected", "getParent"]
- _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2
- _infoAttributes = ufoLib.fontInfoAttributesVersion2
- # subclasses may define a list of environment
- # specific attributes that can be retrieved or set.
- _environmentAttributes = []
- # subclasses may define a list of attributes
- # that should not follow the standard get/set
- # order provided by __setattr__ and __getattr__.
- # for these attributes, the environment specific
- # set and get methods must handle this value
- # without any pre-call validation.
- # (yeah. this is because of some FontLab dumbness.)
- _environmentOverrides = []
-
- def __setattr__(self, attr, value):
- # check to see if the attribute has been
- # deprecated. if so, warn the caller and
- # update the attribute and value.
- if attr in self._deprecatedAttributes:
- newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value)
- note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr)
- warn(note, DeprecationWarning)
- attr = newAttr
- value = newValue
- # setting a known attribute
- if attr in self._infoAttributes or attr in self._environmentAttributes:
- # lightly test the validity of the value
- if value is not None:
- isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value)
- if not isValidValue:
- raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr))
- # use the environment specific info attr set
- # method if it is defined.
- if hasattr(self, "_environmentSetAttr"):
- self._environmentSetAttr(attr, value)
- # fallback to super
- else:
- super(BaseInfo, self).__setattr__(attr, value)
- # unknown attribute, test to see if it is a python attr
- elif attr in self.__dict__ or attr in self._baseAttributes:
- super(BaseInfo, self).__setattr__(attr, value)
- # raise an attribute error
- else:
- raise AttributeError("Unknown attribute %s." % attr)
-
- # subclasses with environment specific attr setting can
- # implement this method. __setattr__ will call it if present.
- # def _environmentSetAttr(self, attr, value):
- # pass
-
- def __getattr__(self, attr):
- if attr in self._environmentOverrides:
- return self._environmentGetAttr(attr)
- # check to see if the attribute has been
- # deprecated. if so, warn the caller and
- # flag the value as needing conversion.
- needValueConversionTo1 = False
- if attr in self._deprecatedAttributes:
- oldAttr = attr
- oldValue = attr
- newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None)
- note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr)
- warn(note, DeprecationWarning)
- attr = newAttr
- needValueConversionTo1 = True
- # getting a known attribute
- if attr in self._infoAttributes or attr in self._environmentAttributes:
- # use the environment specific info attr get
- # method if it is defined.
- if hasattr(self, "_environmentGetAttr"):
- value = self._environmentGetAttr(attr)
- # fallback to super
- else:
- try:
- value = super(BaseInfo, self).__getattribute__(attr)
- except AttributeError:
- return None
- if needValueConversionTo1:
- oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value)
- return value
- # raise an attribute error
- else:
- raise AttributeError("Unknown attribute %s." % attr)
-
- # subclasses with environment specific attr retrieval can
- # implement this method. __getattr__ will call it if present.
- # it should return the requested value.
- # def _environmentGetAttr(self, attr):
- # pass
-
-class BaseFeatures(RBaseObject):
-
- def __init__(self):
- RBaseObject.__init__(self)
- self._text = ""
-
- def _get_text(self):
- return self._text
-
- def _set_text(self, value):
- assert isinstance(value, basestring)
- self._text = value
-
- text = property(_get_text, _set_text, doc="raw feature text.")
-
-
-class BaseGroups(dict):
-
- """Base class for all RFont.groups objects"""
-
- def __init__(self):
- pass
-
- def __repr__(self):
- font = "unnamed_font"
- fontParent = self.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RGroups for %s>"%font
-
- def getParent(self):
- """this method will be overwritten with a weakref if there is a parent."""
- pass
-
- def setParent(self, parent):
- import weakref
- self.__dict__['getParent'] = weakref.ref(parent)
-
- def __setitem__(self, key, value):
- #override base class to insure proper data is being stored
- if not isinstance(key, str):
- raise RoboFabError, 'key must be a string'
- if not isinstance(value, list):
- raise RoboFabError, 'group must be a list'
- super(BaseGroups, self).__setitem__(key, value)
-
- def findGlyph(self, glyphName):
- """return a list of all groups contianing glyphName"""
- found = []
- for i in self.keys():
- l = self[i]
- if glyphName in l:
- found.append(i)
- return found
-
-
-class BaseLib(dict):
-
- """Base class for all lib objects"""
-
- def __init__(self):
- pass
-
- def __repr__(self):
- #this is a doozy!
- parent = "unknown_parent"
- parentObject = self.getParent()
- if parentObject is not None:
- #do we have a font?
- try:
- parent = parentObject.info.postscriptFullName
- except AttributeError:
- #or do we have a glyph?
- try:
- parent = parentObject.name
- #we must be an orphan
- except AttributeError: pass
- return "<RLib for %s>"%parent
-
- def getParent(self):
- """this method will be overwritten with a weakref if there is a parent."""
- pass
-
- def setParent(self, parent):
- import weakref
- self.__dict__['getParent'] = weakref.ref(parent)
-
- def copy(self, aParent=None):
- """Duplicate this lib."""
- n = self.__class__()
- if aParent is not None:
- n.setParent(aParent)
- elif self.getParent() is not None:
- n.setParent(self.getParent())
- for k in self.keys():
- n[k] = copy.deepcopy(self[k])
- return n
-
-
-class BaseKerning(RBaseObject):
-
- """Base class for all kerning objects. Object behaves like a dict but has
- some special kerning specific tricks."""
-
- def __init__(self, kerningDict=None):
- if not kerningDict:
- kerningDict = {}
- self._kerning = kerningDict
- self.changed = False # if the object needs to be saved
-
- def __repr__(self):
- font = "unnamed_font"
- fontParent = self.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.postscriptFullName
- except AttributeError: pass
- return "<RKerning for %s>"%font
-
- def __getitem__(self, key):
- if isinstance(key, tuple):
- pair = key
- return self.get(pair)
- elif isinstance(key, str):
- raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
- else:
- keys = self.keys()
- if key > len(keys):
- raise IndexError
- keys.sort()
- pair = keys[key]
- if not self._kerning.has_key(pair):
- raise IndexError
- else:
- return pair
-
- def __setitem__(self, pair, value):
- if not isinstance(pair, tuple):
- raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
- else:
- if len(pair) != 2:
- raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
- else:
- if value == 0:
- if self._kerning.get(pair) is not None:
- del self._kerning[pair]
- else:
- self._kerning[pair] = value
- self._hasChanged()
-
- def __len__(self):
- return len(self._kerning.keys())
-
- def _hasChanged(self):
- """mark the object and it's parent as changed"""
- self.setChanged(True)
- if self.getParent() is not None:
- self.getParent()._hasChanged()
-
- def keys(self):
- """return list of kerning pairs"""
- return self._kerning.keys()
-
- def values(self):
- """return a list of kerning values"""
- return self._kerning.values()
-
- def items(self):
- """return a list of kerning items"""
- return self._kerning.items()
-
- def has_key(self, pair):
- return self._kerning.has_key(pair)
-
- def get(self, pair, default=None):
- """get a value. return None if the pair does not exist"""
- value = self._kerning.get(pair, default)
- return value
-
- def remove(self, pair):
- """remove a kerning pair"""
- self[pair] = 0
-
- def getAverage(self):
- """return average of all kerning pairs"""
- if len(self) == 0:
- return 0
- value = 0
- for i in self.values():
- value = value + i
- return value / float(len(self))
-
- def getExtremes(self):
- """return the lowest and highest kerning values"""
- if len(self) == 0:
- return 0
- values = self.values()
- values.append(0)
- values.sort()
- return (values[0], values[-1])
-
- def update(self, kerningDict):
- """replace kerning data with the data in the given kerningDict"""
- for pair in kerningDict.keys():
- self[pair] = kerningDict[pair]
-
- def clear(self):
- """clear all kerning"""
- self._kerning = {}
-
- def add(self, value):
- """add value to all kerning pairs"""
- for pair in self.keys():
- self[pair] = self[pair] + value
-
- def scale(self, value):
- """scale all kernng pairs by value"""
- for pair in self.keys():
- self[pair] = self[pair] * value
-
- def minimize(self, minimum=10):
- """eliminate pairs with value less than minimum"""
- for pair in self.keys():
- if abs(self[pair]) < minimum:
- self[pair] = 0
-
- def eliminate(self, leftGlyphsToEliminate=None, rightGlyphsToEliminate=None, analyzeOnly=False):
- """eliminate pairs containing a left glyph that is in the leftGlyphsToEliminate list
- or a right glyph that is in the rightGlyphsToELiminate list.
- sideGlyphsToEliminate can be a string: 'a' or list: ['a', 'b'].
- analyzeOnly will not remove pairs. it will return a count
- of all pairs that would be removed."""
- if analyzeOnly:
- count = 0
- lgte = leftGlyphsToEliminate
- rgte = rightGlyphsToEliminate
- if isinstance(lgte, str):
- lgte = [lgte]
- if isinstance(rgte, str):
- rgte = [rgte]
- for pair in self.keys():
- left, right = pair
- if left in lgte or right in rgte:
- if analyzeOnly:
- count = count + 1
- else:
- self[pair] = 0
- if analyzeOnly:
- return count
- else:
- return None
-
- def interpolate(self, sourceDictOne, sourceDictTwo, value, clearExisting=True):
- """interpolate the kerning between sourceDictOne
- and sourceDictTwo. clearExisting will clear existing
- kerning first."""
- if isinstance(value, tuple):
- # in case the value is a x, y tuple: use the x only.
- value = value[0]
- if clearExisting:
- self.clear()
- pairs = set(sourceDictOne.keys()) | set(sourceDictTwo.keys())
- for pair in pairs:
- s1 = sourceDictOne.get(pair, 0)
- s2 = sourceDictTwo.get(pair, 0)
- self[pair] = _interpolate(s1, s2, value)
-
- def round(self, multiple=10):
- """round the kerning pair values to increments of multiple"""
- for pair in self.keys():
- value = self[pair]
- self[pair] = int(round(value / float(multiple))) * multiple
-
- def occurrenceCount(self, glyphsToCount):
- """return a dict with glyphs as keys and the number of
- occurances of that glyph in the kerning pairs as the value
- glyphsToCount can be a string: 'a' or list: ['a', 'b']"""
- gtc = glyphsToCount
- if isinstance(gtc, str):
- gtc = [gtc]
- gtcDict = {}
- for glyph in gtc:
- gtcDict[glyph] = 0
- for pair in self.keys():
- left, right = pair
- if not gtcDict.get(left):
- gtcDict[left] = 0
- if not gtcDict.get(right):
- gtcDict[right] = 0
- gtcDict[left] = gtcDict[left] + 1
- gtcDict[right] = gtcDict[right] + 1
- found = {}
- for glyphName in gtc:
- found[glyphName] = gtcDict[glyphName]
- return found
-
- def getLeft(self, glyphName):
- """Return a list of kerns with glyphName as left character."""
- hits = []
- for k, v in self.items():
- if k[0] == glyphName:
- hits.append((k, v))
- return hits
-
- def getRight(self, glyphName):
- """Return a list of kerns with glyphName as left character."""
- hits = []
- for k, v in self.items():
- if k[1] == glyphName:
- hits.append((k, v))
- return hits
-
- def combine(self, kerningDicts, overwriteExisting=True):
- """combine two or more kerning dictionaries.
- overwrite exsisting duplicate pairs if overwriteExisting=True"""
- if isinstance(kerningDicts, dict):
- kerningDicts = [kerningDicts]
- for kd in kerningDicts:
- for pair in kd.keys():
- exists = self.has_key(pair)
- if exists and overwriteExisting:
- self[pair] = kd[pair]
- elif not exists:
- self[pair] = kd[pair]
-
- def swapNames(self, swapTable):
- """change glyph names in all kerning pairs based on swapTable.
- swapTable = {'BeforeName':'AfterName', ...}"""
- for pair in self.keys():
- foundInstance = False
- left, right = pair
- if swapTable.has_key(left):
- left = swapTable[left]
- foundInstance = True
- if swapTable.has_key(right):
- right = swapTable[right]
- foundInstance = True
- if foundInstance:
- self[(left, right)] = self[pair]
- self[pair] = 0
-
- def explodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False):
- """turn class kerns into real kerning pairs. classes should
- be defined in dicts: {'O':['C', 'G', 'Q'], 'H':['B', 'D', 'E', 'F', 'I']}.
- analyzeOnly will not remove pairs. it will return a count
- of all pairs that would be added"""
- if not leftClassDict:
- leftClassDict = {}
- if not rightClassDict:
- rightClassDict = {}
- if analyzeOnly:
- count = 0
- for pair in self.keys():
- left, right = pair
- value = self[pair]
- if leftClassDict.get(left) and rightClassDict.get(right):
- allLeft = leftClassDict[left] + [left]
- allRight = rightClassDict[right] + [right]
- for leftSub in allLeft:
- for rightSub in allRight:
- if analyzeOnly:
- count = count + 1
- else:
- self[(leftSub, rightSub)] = value
- elif leftClassDict.get(left) and not rightClassDict.get(right):
- allLeft = leftClassDict[left] + [left]
- for leftSub in allLeft:
- if analyzeOnly:
- count = count + 1
- else:
- self[(leftSub, right)] = value
- elif rightClassDict.get(right) and not leftClassDict.get(left):
- allRight = rightClassDict[right] + [right]
- for rightSub in allRight:
- if analyzeOnly:
- count = count + 1
- else:
- self[(left, rightSub)] = value
- if analyzeOnly:
- return count
- else:
- return None
-
- def implodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False):
- """condense the number of kerning pairs by applying classes.
- this will eliminate all pairs containg the classed glyphs leaving
- pairs that contain the key glyphs behind. analyzeOnly will not
- remove pairs. it will return a count of all pairs that would be removed."""
- if not leftClassDict:
- leftClassDict = {}
- if not rightClassDict:
- rightClassDict = {}
- leftImplode = []
- rightImplode = []
- for value in leftClassDict.values():
- leftImplode = leftImplode + value
- for value in rightClassDict.values():
- rightImplode = rightImplode + value
- analyzed = self.eliminate(leftGlyphsToEliminate=leftImplode, rightGlyphsToEliminate=rightImplode, analyzeOnly=analyzeOnly)
- if analyzeOnly:
- return analyzed
- else:
- return None
-
- def importAFM(self, path, clearExisting=True):
- """Import kerning pairs from an AFM file. clearExisting=True will
- clear all exising kerning"""
- from fontTools.afmLib import AFM
- #a nasty hack to fix line ending problems
- f = open(path, 'rb')
- text = f.read().replace('\r', '\n')
- f.close()
- f = open(path, 'wb')
- f.write(text)
- f.close()
- #/nasty hack
- kerning = AFM(path)._kerning
- if clearExisting:
- self.clear()
- for pair in kerning.keys():
- self[pair] = kerning[pair]
-
- def asDict(self, returnIntegers=True):
- """return the object as a dictionary"""
- if not returnIntegers:
- return self._kerning
- else:
- #duplicate the kerning dict so that we aren't destroying it
- kerning = {}
- for pair in self.keys():
- kerning[pair] = int(round(self[pair]))
- return kerning
-
- def __add__(self, other):
- new = self.__class__()
- k = set(self.keys()) | set(other.keys())
- for key in k:
- new[key] = self.get(key, 0) + other.get(key, 0)
- return new
-
- def __sub__(self, other):
- new = self.__class__()
- k = set(self.keys()) | set(other.keys())
- for key in k:
- new[key] = self.get(key, 0) - other.get(key, 0)
- return new
-
- def __mul__(self, factor):
- new = self.__class__()
- for name, value in self.items():
- new[name] = value * factor
- return new
-
- __rmul__ = __mul__
-
- def __div__(self, factor):
- if factor == 0:
- raise ZeroDivisionError
- return self.__mul__(1.0/factor)
-
diff --git a/misc/pylib/robofab/objects/objectsFF.py b/misc/pylib/robofab/objects/objectsFF.py
deleted file mode 100644
index 4ed6aae1e..000000000
--- a/misc/pylib/robofab/objects/objectsFF.py
+++ /dev/null
@@ -1,1253 +0,0 @@
-
-
-__DEBUG__ = True
-__version__ = "0.2"
-
-"""
- RoboFab API Objects for FontForge
- http://fontforge.sourceforge.net
-
- FontForge python docs:
- http://fontforge.sourceforge.net/python.html
-
- Note: This is dead. EvB: "objectsFF.py is very dead and should only serve as an example of "dead"
-
- History
- Version zero. May 2007. EvB
- Experiment to see how far the API can be made to work.
-
- 0.1 extended testing and comparisons for attributes.
- 0.2 checked into svn. Still quite raw. Lots of print statements and tests at the end.
-
- Notes
- This code is best used with fontforge compiled as a python extension.
-
- FontForge Python API:
- __doc__
- str(object) -> string
-
- Return a nice string representation of the object.
- If the argument is a string, the return value is the same object.
-
- __file__
- str(object) -> string
-
- Return a nice string representation of the object.
- If the argument is a string, the return value is the same object.
-
- __name__
- str(object) -> string
-
- Return a nice string representation of the object.
- If the argument is a string, the return value is the same object.
-
- activeFont
- If invoked from the UI, this returns the currently active font. When not in UI this returns None
-
- activeFontInUI
- If invoked from the UI, this returns the currently active font. When not in UI this returns None
-
- activeGlyph
- If invoked from the UI, this returns the currently active glyph (or None)
-
- ask
- Pops up a dialog asking the user a question and providing a set of buttons for the user to reply with
-
- askChoices
- Pops up a dialog asking the user a question and providing a scrolling list for the user to reply with
-
- askString
- Pops up a dialog asking the user a question and providing a textfield for the user to reply with
-
- contour
- fontforge Contour objects
-
- contouriter
- None
-
- cvt
- fontforge cvt objects
-
- defaultOtherSubrs
- Use FontForge's default "othersubrs" functions for Type1 fonts
-
- font
- FontForge Font object
-
- fontiter
- None
-
- fonts
- Returns a tuple of all loaded fonts
-
- fontsInFile
- Returns a tuple containing the names of any fonts in an external file
-
- getPrefs
- Get FontForge preference items
-
- glyph
- FontForge GlyphPen object
-
- glyphPen
- FontForge Glyph object
-
- hasSpiro
- Returns whether this fontforge has access to Raph Levien's spiro package
-
- hasUserInterface
- Returns whether this fontforge session has a user interface (True if it has opened windows) or is just running a script (False)
-
- hooks
- dict() -> new empty dictionary.
- dict(mapping) -> new dictionary initialized from a mapping object's
- (key, value) pairs.
- dict(seq) -> new dictionary initialized as if via:
- d = {}
- for k, v in seq:
- d[k] = v
- dict(**kwargs) -> new dictionary initialized with the name=value pairs
- in the keyword argument list. For example: dict(one=1, two=2)
-
- layer
- fontforge Layer objects
-
- layeriter
- None
-
- loadEncodingFile
- Load an encoding file into the list of encodings
-
- loadNamelist
- Load a namelist into the list of namelists
-
- loadNamelistDir
- Load a directory of namelist files into the list of namelists
-
- loadPlugin
- Load a FontForge plugin
-
- loadPluginDir
- Load a directory of FontForge plugin files
-
- loadPrefs
- Load FontForge preference items
-
- logWarning
- Adds a non-fatal message to the Warnings window
-
- open
- Opens a font and returns it
-
- openFilename
- Pops up a file picker dialog asking the user for a filename to open
-
- parseTTInstrs
- Takes a string and parses it into a tuple of truetype instruction bytes
-
- point
- fontforge Point objects
-
- postError
- Pops up an error dialog box with the given title and message
-
- postNotice
- Pops up an notice window with the given title and message
-
- preloadCidmap
- Load a cidmap file
-
- printSetup
- Prepare to print a font sample (select default printer or file, page size, etc.)
-
- private
- FontForge private dictionary
-
- privateiter
- None
-
- readOtherSubrsFile
- Read from a file, "othersubrs" functions for Type1 fonts
-
- registerImportExport
- Adds an import/export spline conversion module
-
- registerMenuItem
- Adds a menu item (which runs a python script) to the font or glyph (or both) windows -- in the Tools menu
-
- saveFilename
- Pops up a file picker dialog asking the user for a filename to use for saving
-
- savePrefs
- Save FontForge preference items
-
- selection
- fontforge selection objects
-
- setPrefs
- Set FontForge preference items
-
- spiroCorner
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- spiroG2
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- spiroG4
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- spiroLeft
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- spiroOpen
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- spiroRight
- int(x[, base]) -> integer
-
- Convert a string or number to an integer, if possible. A floating point
- argument will be truncated towards zero (this does not include a string
- representation of a floating point number!) When converting a string, use
- the optional base. It is an error to supply a base when converting a
- non-string. If the argument is outside the integer range a long object
- will be returned instead.
-
- unParseTTInstrs
- Takes a tuple of truetype instruction bytes and converts to a human readable string
-
- unicodeFromName
- Given a name, look it up in the namelists and find what unicode code point it maps to (returns -1 if not found)
-
- version
- Returns a string containing the current version of FontForge, as 20061116
-
-
-
-
-Problems:
- XXX: reading glif from UFO: is the contour order changed in some way?
-
-
-ToDo:
- - segments ?
-
-
-"""
-
-import os
-from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\
- BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\
- roundPt, addPt, _box,\
- MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
- relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut
-
-from robofab.objects.objectsRF import RGlyph as _RGlyph
-
-import fontforge
-import psMat
-
-
-# a list of attributes that are to be copied when copying a glyph.
-# this is used by glyph.copy and font.insertGlyph
-GLYPH_COPY_ATTRS = [
- "name",
- "width",
- "unicodes",
- "note",
- "lib",
- ]
-
-
-
-def CurrentFont():
- if fontforge.hasUserInterface():
- _font = fontforge.activeFontInUI()
- return RFont(_font)
- if __DEBUG__:
- print "CurrentFont(): fontforge not running with user interface,"
- return None
-
-def OpenFont(fontPath):
- obj = fontforge.open(fontPath)
- if __DEBUG__:
- print "OpenFont", fontPath
- print "result:", obj
- return RFont(obj)
-
-def NewFont(fontPath=None):
- _font = fontforge.font()
- if __DEBUG__:
- print "NewFont", fontPath
- print "result:", _font
- return RFont(_font)
-
-
-
-
-class RFont(BaseFont):
- def __init__(self, font=None):
- if font is None:
- # make a new font
- pass
- else:
- self._object = font
-
- # -----------------------------------------------------------------
- #
- # access
-
- def keys(self):
- """FF implements __iter__ for the font object - better?"""
- return [n.glyphname for n in self._object.glyphs()]
-
- def has_key(self, glyphName):
- return glyphName in self
-
- def _get_info(self):
- return RInfo(self._object)
-
- info = property(_get_info, doc="font info object")
-
- def __iter__(self):
- for glyphName in self.keys():
- yield self.getGlyph(glyphName)
-
-
- # -----------------------------------------------------------------
- #
- # file
-
- def _get_path(self):
- return self._object.path
-
- path = property(_get_path, doc="path of this file")
-
- def __contains__(self, glyphName):
- return glyphName in self.keys()
-
- def save(self, path=None):
- """Save this font as sfd file.
- XXX: how to set a sfd path if is none
- """
- if path is not None:
- # trying to save it somewhere else
- _path = path
- else:
- _path = self.path
- if os.path.splitext(_path)[-1] != ".sfd":
- _path = os.path.splitext(_path)[0]+".sfd"
- if __DEBUG__:
- print "RFont.save() to", _path
- self._object.save(_path)
-
- def naked(self):
- return self._object
-
- def close(self):
- if __DEBUG__:
- print "RFont.close()"
- self._object.close()
-
-
- # -----------------------------------------------------------------
- #
- # generate
-
- def dummyGeneratePreHook(self, *args):
- print "dummyGeneratePreHook", args
-
- def dummyGeneratePostHook(self, *args):
- print "dummyGeneratePostHook", args
-
- def generate(self, outputType, path=None):
- """
- generate the font. outputType is the type of font to ouput.
- --Ouput Types:
- 'pctype1' : PC Type 1 font (binary/PFB)
- 'pcmm' : PC MultipleMaster font (PFB)
- 'pctype1ascii' : PC Type 1 font (ASCII/PFA)
- 'pcmmascii' : PC MultipleMaster font (ASCII/PFA)
- 'unixascii' : UNIX ASCII font (ASCII/PFA)
- 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file)
- 'otfcff' : PS OpenType (CFF-based) font (OTF)
- 'otfttf' : PC TrueType/TT OpenType font (TTF)
- 'macttf' : Mac TrueType font (generates suitcase)
- 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork)
- (doc adapted from http://dev.fontlab.net/flpydoc/)
-
- path can be a directory or a directory file name combo:
- path="DirectoryA/DirectoryB"
- path="DirectoryA/DirectoryB/MyFontName"
- if no path is given, the file will be output in the same directory
- as the vfb file. if no file name is given, the filename will be the
- vfb file name with the appropriate suffix.
- """
-
- extensions = {
- 'pctype1': 'pfm',
- 'otfcff': 'otf',
- }
-
- if __DEBUG__:
- print "font.generate", outputType, path
-
- # set pre and post hooks (necessary?)
- temp = getattr(self._object, "temporary")
- if temp is None:
- self._object.temporary = {}
- else:
- if type(self._object.temporary)!=dict:
- self._object.temporary = {}
- self._object.temporary['generateFontPreHook'] = self.dummyGeneratePreHook
- self._object.temporary['generateFontPostHook'] = self.dummyGeneratePostHook
-
- # make a path for the destination
- if path is None:
- fileName = os.path.splitext(os.path.basename(self.path))[0]
- dirName = os.path.dirname(self.path)
- extension = extensions.get(outputType)
- if extension is not None:
- fileName = "%s.%s"%(fileName, extension)
- else:
- if __DEBUG__:
- print "can't generate font in %s format"%outputType
- return
- path = os.path.join(dirName, fileName)
-
- # prepare OTF fields
- generateFlags = []
- generateFlags.append('opentype')
- # generate
- self._object.generate(filename=path, flags=generateFlags)
- if __DEBUG__:
- print "font.generate():", path
- return path
-
-
- # -----------------------------------------------------------------
- #
- # kerning stuff
-
- def _get_kerning(self):
- kerning = {}
- f = self._object
- for g in f.glyphs:
- for p in g.kerning:
- try:
- key = (g.name, f[p.key].name)
- kerning[key] = p.value
- except AttributeError: pass #catch for TT exception
- rk = RKerning(kerning)
- rk.setParent(self)
- return rk
-
- kerning = property(_get_kerning, doc="a kerning object")
-
- # -----------------------------------------------------------------
- #
- # glyph stuff
-
- def getGlyph(self, glyphName):
- try:
- ffGlyph = self._object[glyphName]
- except TypeError:
- print "font.getGlyph, can't find glyphName, returning new glyph"
- return self.newGlyph(glyphName)
- glyph = RGlyph(ffGlyph)
- glyph.setParent(self)
- return glyph
-
- def newGlyph(self, glyphName, clear=True):
- """Make a new glyph
-
- Notes: not sure how to make a new glyph without an encoded name.
- createChar() seems to be intended for that, but when I pass it -1
- for the unicode, it complains that it wants -1. Perhaps a bug?
- """
- # is the glyph already there?
- glyph = None
- if glyphName in self:
- if clear:
- self._object[glyphName].clear()
- return self[glyphName]
- else:
- # is the glyph in an encodable place:
- slot = self._object.findEncodingSlot(glyphName)
- if slot == -1:
- # not encoded
- print "font.newGlyph: unencoded slot", slot, glyphName
- glyph = self._object.createChar(-1, glyphName)
- else:
- glyph = self._object.createMappedChar(glyphName)
- glyph = RGlyph(self._object[glyphName])
- glyph.setParent(self)
- return glyph
-
- def removeGlyph(self, glyphName):
- self._object.removeGlyph(glyphName)
-
-
-
-
-class RGlyph(BaseGlyph):
- """Fab wrapper for FF Glyph object"""
- def __init__(self, ffGlyph=None):
- if ffGlyph is None:
- raise RoboFabError
- self._object = ffGlyph
- # XX anchors seem to be supported, but in a different way
- # XX so, I will ignore them for now to get something working.
- self.anchors = []
- self.lib = {}
-
- def naked(self):
- return self._object
-
- def setChanged(self):
- self._object.changed()
-
-
- # -----------------------------------------------------------------
- #
- # attributes
-
- def _get_name(self):
- return self._object.glyphname
- def _set_name(self, value):
- self._object.glyphname = value
- name = property(_get_name, _set_name, doc="name")
-
- def _get_note(self):
- return self._object.comment
- def _set_note(self, note):
- self._object.comment = note
- note = property(_get_note, _set_note, doc="note")
-
- def _get_width(self):
- return self._object.width
- def _set_width(self, width):
- self._object.width = width
- width = property(_get_width, _set_width, doc="width")
-
- def _get_leftMargin(self):
- return self._object.left_side_bearing
- def _set_leftMargin(self, leftMargin):
- self._object.left_side_bearing = leftMargin
- leftMargin = property(_get_leftMargin, _set_leftMargin, doc="leftMargin")
-
- def _get_rightMargin(self):
- return self._object.right_side_bearing
- def _set_rightMargin(self, rightMargin):
- self._object.right_side_bearing = rightMargin
- rightMargin = property(_get_rightMargin, _set_rightMargin, doc="rightMargin")
-
- def _get_unicodes(self):
- return [self._object.unicode]
- def _set_unicodes(self, unicodes):
- assert len(unicodes)==1
- self._object.unicode = unicodes[0]
- unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes")
-
- def _get_unicode(self):
- return self._object.unicode
- def _set_unicode(self, unicode):
- self._object.unicode = unicode
- unicode = property(_get_unicode, _set_unicode, doc="unicode")
-
- def _get_box(self):
- bounds = self._object.boundingBox()
- return bounds
- box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)")
-
- def _get_mark(self):
- """color of the glyph box in the font view. This accepts a 6 hex digit number.
-
- XXX the FL implementation accepts a
- """
- import colorsys
- r = (self._object.color&0xff0000)>>16
- g = (self._object.color&0xff00)>>8
- g = (self._object.color&0xff)>>4
- return colorsys.rgb_to_hsv( r, g, b)[0]
-
- def _set_mark(self, markColor=-1):
- import colorsys
- self._object.color = colorSys.hsv_to_rgb(markColor, 1, 1)
-
- mark = property(_get_mark, _set_mark, doc="the color of the glyph box in the font view")
-
-
- # -----------------------------------------------------------------
- #
- # pen, drawing
-
- def getPen(self):
- return self._object.glyphPen()
-
- def __getPointPen(self):
- """Return a point pen.
-
- Note: FontForge doesn't support segment pen, so return an adapter.
- """
- from robofab.pens.adapterPens import PointToSegmentPen
- segmentPen = self._object.glyphPen()
- return PointToSegmentPen(segmentPen)
-
- def getPointPen(self):
- from robofab.pens.rfUFOPen import RFUFOPointPen
- pen = RFUFOPointPen(self)
- #print "getPointPen", pen, pen.__class__, dir(pen)
- return pen
-
- def draw(self, pen):
- """draw
-
- """
- self._object.draw(pen)
- pen = None
-
- def drawPoints(self, pen):
- """drawPoints
-
- Note: FontForge implements glyph.draw, but not glyph.drawPoints.
- """
- from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen
- adapter = SegmentToPointPen(pen)
- self._object.draw(adapter)
- pen = None
-
- def appendGlyph(self, other):
- pen = self.getPen()
- other.draw(pen)
-
- # -----------------------------------------------------------------
- #
- # glyphmath
-
- def round(self):
- self._object.round()
-
- def _getMathDestination(self):
- from robofab.objects.objectsRF import RGlyph as _RGlyph
- return _RGlyph()
-
- def _mathCopy(self):
- # copy self without contour, component and anchor data
- glyph = self._getMathDestination()
- glyph.name = self.name
- glyph.unicodes = list(self.unicodes)
- glyph.width = self.width
- glyph.note = self.note
- glyph.lib = dict(self.lib)
- return glyph
-
- def __mul__(self, factor):
- if __DEBUG__:
- print "glyphmath mul", factor
- return self.copy() *factor
-
- __rmul__ = __mul__
-
- def __sub__(self, other):
- if __DEBUG__:
- print "glyphmath sub", other, other.__class__
- return self.copy() - other.copy()
-
- def __add__(self, other):
- if __DEBUG__:
- print "glyphmath add", other, other.__class__
- return self.copy() + other.copy()
-
- def getParent(self):
- return self
-
- def copy(self, aParent=None):
- """Make a copy of this glyph.
- Note: the copy is not a duplicate fontlab glyph, but
- a RF RGlyph with the same outlines. The new glyph is
- not part of the fontlab font in any way. Use font.appendGlyph(glyph)
- to get it in a FontLab glyph again."""
- from robofab.objects.objectsRF import RGlyph as _RGlyph
- newGlyph = _RGlyph()
- newGlyph.appendGlyph(self)
- for attr in GLYPH_COPY_ATTRS:
- value = getattr(self, attr)
- setattr(newGlyph, attr, value)
- parent = self.getParent()
- if aParent is not None:
- newGlyph.setParent(aParent)
- elif self.getParent() is not None:
- newGlyph.setParent(self.getParent())
- return newGlyph
-
- def _get_contours(self):
- # find the contour data and wrap it
-
- """get the contours in this glyph"""
- contours = []
- for n in range(len(self._object.foreground)):
- item = self._object.foreground[n]
- rc = RContour(item, n)
- rc.setParent(self)
- contours.append(rc)
- #print contours
- return contours
-
- contours = property(_get_contours, doc="allow for iteration through glyph.contours")
-
- # -----------------------------------------------------------------
- #
- # transformations
-
- def move(self, (x, y)):
- matrix = psMat.translate((x,y))
- self._object.transform(matrix)
-
- def scale(self, (x, y), center=(0,0)):
- matrix = psMat.scale(x,y)
- self._object.transform(matrix)
-
- def transform(self, matrix):
- self._object.transform(matrix)
-
- def rotate(self, angle, offset=None):
- matrix = psMat.rotate(angle)
- self._object.transform(matrix)
-
- def skew(self, angle, offset=None):
- matrix = psMat.skew(angle)
- self._object.transform(matrix)
-
- # -----------------------------------------------------------------
- #
- # components stuff
-
- def decompose(self):
- self._object.unlinkRef()
-
- # -----------------------------------------------------------------
- #
- # unicode stuff
-
- def autoUnicodes(self):
- if __DEBUG__:
- print "objectsFF.RGlyph.autoUnicodes() not implemented yet."
-
- # -----------------------------------------------------------------
- #
- # contour stuff
-
- def removeOverlap(self):
- self._object.removeOverlap()
-
- def correctDirection(self, trueType=False):
- # no option for trueType, really.
- self._object.correctDirection()
-
- def clear(self):
- self._object.clear()
-
- def __getitem__(self, index):
- return self.contours[index]
-
-
-class RContour(BaseContour):
- def __init__(self, contour, index=None):
- self._object = contour
- self.index = index
-
- def _get_points(self):
- pts = []
- for pt in self._object:
- wpt = RPoint(pt)
- wpt.setParent(self)
- pts.append(wpt)
- return pts
-
- points = property(_get_points, doc="get contour points")
-
- def _get_box(self):
- return self._object.boundingBox()
-
- box = property(_get_box, doc="get contour bounding box")
-
- def __len__(self):
- return len(self._object)
-
- def __getitem__(self, index):
- return self.points[index]
-
-
-
-class RPoint(BasePoint):
-
- def __init__(self, pointObject):
- self._object = pointObject
-
- def _get_x(self):
- return self._object.x
-
- def _set_x(self, value):
- self._object.x = value
-
- x = property(_get_x, _set_x, doc="")
-
- def _get_y(self):
- return self._object.y
-
- def _set_y(self, value):
- self._object.y = value
-
- y = property(_get_y, _set_y, doc="")
-
- def _get_type(self):
- if self._object.on_curve == 0:
- return OFFCURVE
-
- # XXX not always curve
- return CURVE
-
- def _set_type(self, value):
- self._type = value
- self._hasChanged()
-
- type = property(_get_type, _set_type, doc="")
-
- def __repr__(self):
- font = "unnamed_font"
- glyph = "unnamed_glyph"
- contourIndex = "unknown_contour"
- contourParent = self.getParent()
- if contourParent is not None:
- try:
- contourIndex = `contourParent.index`
- except AttributeError: pass
- glyphParent = contourParent.getParent()
- if glyphParent is not None:
- try:
- glyph = glyphParent.name
- except AttributeError: pass
- fontParent = glyphParent.getParent()
- if fontParent is not None:
- try:
- font = fontParent.info.fullName
- except AttributeError: pass
- return "<RPoint for %s.%s[%s]>"%(font, glyph, contourIndex)
-
-
-class RInfo(BaseInfo):
- def __init__(self, font):
- BaseInfo.__init__(self)
- self._object = font
-
- def _get_familyName(self):
- return self._object.familyname
- def _set_familyName(self, value):
- self._object.familyname = value
- familyName = property(_get_familyName, _set_familyName, doc="familyname")
-
- def _get_fondName(self):
- return self._object.fondname
- def _set_fondName(self, value):
- self._object.fondname = value
- fondName = property(_get_fondName, _set_fondName, doc="fondname")
-
- def _get_fontName(self):
- return self._object.fontname
- def _set_fontName(self, value):
- self._object.fontname = value
- fontName = property(_get_fontName, _set_fontName, doc="fontname")
-
- # styleName doesn't have a specific field, FF has a whole sfnt dict.
- # implement fullName because a repr depends on it
- def _get_fullName(self):
- return self._object.fullname
- def _set_fullName(self, value):
- self._object.fullname = value
- fullName = property(_get_fullName, _set_fullName, doc="fullname")
-
- def _get_unitsPerEm(self):
- return self._object.em
- def _set_unitsPerEm(self, value):
- self._object.em = value
- unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value")
-
- def _get_ascender(self):
- return self._object.ascent
- def _set_ascender(self, value):
- self._object.ascent = value
- ascender = property(_get_ascender, _set_ascender, doc="ascender value")
-
- def _get_descender(self):
- return -self._object.descent
- def _set_descender(self, value):
- self._object.descent = -value
- descender = property(_get_descender, _set_descender, doc="descender value")
-
- def _get_copyright(self):
- return self._object.copyright
- def _set_copyright(self, value):
- self._object.copyright = value
- copyright = property(_get_copyright, _set_copyright, doc="copyright")
-
-
-
-class RKerning(BaseKerning):
-
- """ Object representing the kerning.
- This is going to need some thinking about.
- """
-
-
-__all__ = [ 'RFont', 'RGlyph', 'RContour', 'RPoint', 'RInfo',
- 'OpenFont', 'CurrentFont', 'NewFont', 'CurrentFont'
- ]
-
-
-
-if __name__ == "__main__":
- import os
- from robofab.objects.objectsRF import RFont as _RFont
- from sets import Set
-
- def dumpFontForgeAPI(testFontPath, printModule=False,
- printFont=False, printGlyph=False,
- printLayer=False, printContour=False, printPoint=False):
- def printAPI(item, name):
- print
- print "-"*80
- print "API of", item
- names = dir(item)
- names.sort()
- print
-
- if printAPI:
- for n in names:
- print
- print "%s.%s"%(name, n)
- try:
- print getattr(item, n).__doc__
- except:
- print "# error showing", n
- # module
- if printModule:
- print "module file:", fontforge.__file__
- print "version:", fontforge.version()
- print "module doc:", fontforge.__doc__
- print "has User Interface:", fontforge.hasUserInterface()
- print "has Spiro:", fontforge.hasSpiro()
- printAPI(fontforge, "fontforge")
-
- # font
- fontObj = fontforge.open(testFontPath)
- if printFont:
- printAPI(fontObj, "font")
-
- # glyph
- glyphObj = fontObj["A"]
- if printGlyph:
- printAPI(glyphObj, "glyph")
-
- # layer
- layerObj = glyphObj.foreground
- if printLayer:
- printAPI(layerObj, "layer")
-
- # contour
- contourObj = layerObj[0]
- if printContour:
- printAPI(contourObj, "contour")
-
- # point
- if printPoint:
- pointObj = contourObj[0]
- printAPI(pointObj, "point")
-
-
- # other objects
- penObj = glyphObj.glyphPen()
- printAPI(penObj, "glyphPen")
-
- # use your own paths here.
- demoRoot = "/Users/erik/Develop/Mess/FontForge/objectsFF_work/"
- UFOPath = os.path.join(demoRoot, "Test.ufo")
- SFDPath = os.path.join(demoRoot, "Test_realSFD2.sfd")
-
- #dumpFontForgeAPI(UFOPath, printPoint=True)
-
- # should return None
- CurrentFont()
-
- def compareAttr(obj1, obj2, attrName, isMethod=False):
- if isMethod:
- a = getattr(obj1, attrName)()
- b = getattr(obj2, attrName)()
- else:
- a = getattr(obj1, attrName)
- b = getattr(obj2, attrName)
- if a == b and a is not None and b is not None:
- print "\tattr %s ok"%attrName, a
- return True
- else:
- print "\t?\t%s error:"%attrName, "%s:"%obj1.__class__, a, "%s:"%obj2.__class__, b
- return False
-
- f = OpenFont(UFOPath)
- #f = OpenFont(SFDPath)
- ref = _RFont(UFOPath)
-
- if False:
- print
- print "test font attributes"
- compareAttr(f, ref, "path")
-
- a = Set(f.keys())
- b = Set(ref.keys())
- print "glyphs in ref, not in f", b.difference(a)
- print "glyphs in f, not in ref", a.difference(b)
-
- print "A" in f, "A" in ref
- print f.has_key("A"), ref.has_key("A")
-
- print
- print "test font info attributes"
- compareAttr(f.info, ref.info, "ascender")
- compareAttr(f.info, ref.info, "descender")
- compareAttr(f.info, ref.info, "unitsPerEm")
- compareAttr(f.info, ref.info, "copyright")
- compareAttr(f.info, ref.info, "fullName")
- compareAttr(f.info, ref.info, "familyName")
- compareAttr(f.info, ref.info, "fondName")
- compareAttr(f.info, ref.info, "fontName")
-
- # crash
- f.save()
-
- otfOutputPath = os.path.join(demoRoot, "test_ouput.otf")
- ufoOutputPath = os.path.join(demoRoot, "test_ouput.ufo")
- # generate without path, should end up in the source folder
-
- f['A'].removeOverlap()
- f.generate('otfcff') #, otfPath)
- f.generate('pctype1') #, otfPath)
-
- # generate with path. Type is taken from the extension.
- f.generate('otfcff', otfOutputPath) #, otfPath)
- f.generate(None, ufoOutputPath) #, otfPath)
-
- featurePath = os.path.join(demoRoot, "testFeatureOutput.fea")
- f.naked().generateFeatureFile(featurePath)
-
- if False:
- # new glyphs
- # unencoded
- print "new glyph: unencoded", f.newGlyph("test_unencoded_glyph")
- # encoded
- print "new glyph: encoded", f.newGlyph("Adieresis")
- # existing
- print "new glyph: existing", f.newGlyph("K")
-
- print
- print "test glyph attributes"
- compareAttr(f['A'], ref['A'], "width")
- compareAttr(f['A'], ref['A'], "unicode")
- compareAttr(f['A'], ref['A'], "name")
- compareAttr(f['A'], ref['A'], "box")
- compareAttr(f['A'], ref['A'], "leftMargin")
- compareAttr(f['A'], ref['A'], "rightMargin")
-
- if False:
- print
- print "comparing glyph digests"
- failed = []
- for n in f.keys():
- g1 = f[n]
- #g1.round()
- g2 = ref[n]
- #g2.round()
- d1 = g1._getDigest()
- d2 = g2._getDigest()
- if d1 != d2:
- failed.append(n)
- #print "f: ", d1
- #print "ref: ", d2
- print "digest failed for %s"%". ".join(failed)
-
- g3 = f['A'] *.333
- print g3
- print g3._getDigest()
- f.save()
-
- if False:
- print
- print "test contour attributes"
- compareAttr(f['A'].contours[0], ref['A'].contours[0], "index")
-
- #for c in f['A'].contours:
- # for p in c.points:
- # print p, p.type
-
- # test with a glyph with just 1 contour so we can be sure we're comparing the same thing
- compareAttr(f['C'].contours[0], ref['C'].contours[0], "box")
- compareAttr(f['C'].contours[0], ref['C'].contours[0], "__len__", isMethod=True)
-
- ptf = f['C'].contours[0].points[0]
- ptref = ref['C'].contours[0].points[0]
- print "x, y", (ptf.x, ptf.y) == (ptref.x, ptref.y), (ptref.x, ptref.y)
- print 'type', ptf.type, ptref.type
-
- print "point inside", f['A'].pointInside((50,10)), ref['A'].pointInside((50,10))
-
-
- print ref.kerning.keys()
-
- class GlyphLookupWrapper(dict):
- """A wrapper for the lookups / subtable data in a FF glyph.
- A lot of data is stored there, so it helps to have something to sort things out.
- """
- def __init__(self, ffGlyph):
- self._object = ffGlyph
- self.refresh()
-
- def __repr__(self):
- return "<GlyphLookupWrapper for %s, %d keys>"%(self._object.glyphname, len(self))
-
- def refresh(self):
- """Pick some of the values apart."""
- lookups = self._object.getPosSub('*')
- for t in lookups:
- print 'lookup', t
- lookupName = t[0]
- lookupType = t[1]
- if not lookupName in self:
- self[lookupName] = []
- self[lookupName].append(t[1:])
-
- def getKerning(self):
- """Get a regular kerning dict for this glyph"""
- d = {}
- left = self._object.glyphname
- for name in self.keys():
- for item in self[name]:
- print 'item', item
- if item[0]!="Pair":
- continue
- #print 'next glyph:', item[1]
- #print 'first glyph x Pos:', item[2]
- #print 'first glyph y Pos:', item[3]
- #print 'first glyph h Adv:', item[4]
- #print 'first glyph v Adv:', item[5]
-
- #print 'second glyph x Pos:', item[6]
- #print 'second glyph y Pos:', item[7]
- #print 'second glyph h Adv:', item[8]
- #print 'second glyph v Adv:', item[9]
- right = item[1]
- d[(left, right)] = item[4]
- return d
-
- def setKerning(self, kernDict):
- """Set the values of a regular kerning dict to the lookups in a FF glyph."""
- for left, right in kernDict.keys():
- if left != self._object.glyphname:
- # should we filter the dict before it gets here?
- # easier just to filter it here.
- continue
-
-
-
- # lets try to find the kerning
- A = f['A'].naked()
- positionTypes = [ "Position", "Pair", "Substitution", "AltSubs", "MultSubs","Ligature"]
- print A.getPosSub('*')
- #for t in A.getPosSub('*'):
- # print 'lookup subtable name:', t[0]
- # print 'positioning type:', t[1]
- # if t[1]in positionTypes:
- # print 'next glyph:', t[2]
- # print 'first glyph x Pos:', t[3]
- # print 'first glyph y Pos:', t[4]
- # print 'first glyph h Adv:', t[5]
- # print 'first glyph v Adv:', t[6]
-
- # print 'second glyph x Pos:', t[7]
- # print 'second glyph y Pos:', t[8]
- # print 'second glyph h Adv:', t[9]
- # print 'second glyph v Adv:', t[10]
-
- gw = GlyphLookupWrapper(A)
- print gw
- print gw.keys()
- print gw.getKerning()
-
- name = "'kern' Horizontal Kerning in Latin lookup 0 subtable"
- item = (name, 'quoteright', 0, 0, -200, 0, 0, 0, 0, 0)
-
- A.removePosSub(name)
- apply(A.addPosSub, item)
-
-
- print "after", A.getPosSub('*')
-
- fn = f.naked()
-
- name = "'kern' Horizontal Kerning in Latin lookup 0"
-
-
- print "before removing stuff", fn.gpos_lookups
- print "removing stuff", fn.removeLookup(name)
- print "after removing stuff", fn.gpos_lookups
-
- flags = ()
- feature_script_lang = (("kern",(("latn",("dflt")),)),)
- print fn.addLookup('kern', 'gpos_pair', flags, feature_script_lang)
- print fn.addLookupSubtable('kern', 'myKerning')
-
-
- print fn.gpos_lookups
- A.addPosSub('myKerning', 'A', 0, 0, -400, 0, 0, 0, 0, 0)
- A.addPosSub('myKerning', 'B', 0, 0, 200, 0, 0, 0, 0, 0)
- A.addPosSub('myKerning', 'C', 0, 0, 10, 0, 0, 0, 0, 0)
- A.addPosSub('myKerning', 'A', 0, 0, 77, 0, 0, 0, 0, 0)
-
-
- gw = GlyphLookupWrapper(A)
- print gw
- print gw.keys()
- print gw.getKerning()
-
diff --git a/misc/pylib/robofab/objects/objectsFL.py b/misc/pylib/robofab/objects/objectsFL.py
deleted file mode 100755
index 3b78ddc14..000000000
--- a/misc/pylib/robofab/objects/objectsFL.py
+++ /dev/null
@@ -1,3112 +0,0 @@
-"""UFO implementation for the objects as used by FontLab 4.5 and higher"""
-
-from FL import *
-
-from robofab.tools.toolsFL import GlyphIndexTable, NewGlyph
-from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\
- BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\
- roundPt, addPt, _box,\
- MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
- relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\
- BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues
-from robofab.misc import arrayTools
-from robofab.pens.flPen import FLPointPen, FLPointContourPen
-from robofab import RoboFabError
-import os
-from robofab.plistlib import Data, Dict, readPlist, writePlist
-from StringIO import StringIO
-from robofab import ufoLib
-from warnings import warn
-import datetime
-from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab
-
-
-try:
- set
-except NameError:
- from sets import Set as set
-
-# local encoding
-if os.name in ["mac", "posix"]:
- LOCAL_ENCODING = "macroman"
-else:
- LOCAL_ENCODING = "latin-1"
-
-_IN_UFO_EXPORT = False
-
-# a list of attributes that are to be copied when copying a glyph.
-# this is used by glyph.copy and font.insertGlyph
-GLYPH_COPY_ATTRS = [
- "name",
- "width",
- "unicodes",
- "note",
- "lib",
- ]
-
-# Generate Types
-PC_TYPE1 = 'pctype1'
-PC_MM = 'pcmm'
-PC_TYPE1_ASCII = 'pctype1ascii'
-PC_MM_ASCII = 'pcmmascii'
-UNIX_ASCII = 'unixascii'
-MAC_TYPE1 = 'mactype1'
-OTF_CFF = 'otfcff'
-OTF_TT = 'otfttf'
-MAC_TT = 'macttf'
-MAC_TT_DFONT = 'macttdfont'
-
-# doc for these functions taken from: http://dev.fontlab.net/flpydoc/
-# internal name (FontLab name, extension)
-_flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PFB)
- PC_MM : (ftTYPE1_MM, 'mm'), # PC MultipleMaster font (PFB)
- PC_TYPE1_ASCII : (ftTYPE1ASCII, 'pfa'), # PC Type 1 font (ASCII/PFA)
- PC_MM_ASCII : (ftTYPE1ASCII_MM, 'mm'), # PC MultipleMaster font (ASCII/PFA)
- UNIX_ASCII : (ftTYPE1ASCII, 'pfa'), # UNIX ASCII font (ASCII/PFA)
- OTF_TT : (ftTRUETYPE, 'ttf'), # PC TrueType/TT OpenType font (TTF)
- OTF_CFF : (ftOPENTYPE, 'otf'), # PS OpenType (CFF-based) font (OTF)
- MAC_TYPE1 : (ftMACTYPE1, 'suit'), # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM)
- MAC_TT : (ftMACTRUETYPE, 'ttf'), # Mac TrueType font (generates suitcase)
- MAC_TT_DFONT : (ftMACTRUETYPE_DFONT, 'dfont'), # Mac TrueType font (generates suitcase with resources in data fork)
- }
-
-## FL Hint stuff
-# this should not be referenced outside of this module
-# since we may be changing the way this works in the future.
-
-
-"""
-
- FontLab implementation of psHints objects
-
- Most of the FL methods relating to ps hints return a list of 16 items.
- These values are for the 16 corners of a 4 axis multiple master.
- The odd thing is that even single masters get these 16 values.
- RoboFab doesn't access the MM masters, so by default, the psHints
- object only works with the first element. If you want to access the other
- values in the list, give a value between 0 and 15 for impliedMasterIndex
- when creating the object.
-
- From the FontLab docs:
- http://dev.fontlab.net/flpydoc/
-
- blue_fuzz
- blue_scale
- blue_shift
-
- blue_values_num(integer) - number of defined blue values
- blue_values[integer[integer]] - two-dimentional array of BlueValues
- master index is top-level index
-
- other_blues_num(integer) - number of defined OtherBlues values
- other_blues[integer[integer]] - two-dimentional array of OtherBlues
- master index is top-level index
-
- family_blues_num(integer) - number of FamilyBlues records
- family_blues[integer[integer]] - two-dimentional array of FamilyBlues
- master index is top-level index
-
- family_other_blues_num(integer) - number of FamilyOtherBlues records
- family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues
- master index is top-level index
-
- force_bold[integer] - list of Force Bold values, one for
- each master
- stem_snap_h_num(integer)
- stem_snap_h
- stem_snap_v_num(integer)
- stem_snap_v
- """
-
-class PostScriptFontHintValues(BasePostScriptFontHintValues):
- """ Wrapper for font-level PostScript hinting information for FontLab.
- Blues values, stem values.
- """
- def __init__(self, font=None, impliedMasterIndex=0):
- self._object = font.naked()
- self._masterIndex = impliedMasterIndex
-
- def copy(self):
- from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues
- return _PostScriptFontHintValues(data=self.asDict())
-
-
-class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues):
- """ Wrapper for glyph-level PostScript hinting information for FontLab.
- vStems, hStems.
- """
- def __init__(self, glyph=None):
- self._object = glyph.naked()
-
- def copy(self):
- from robofab.objects.objectsRF import PostScriptGlyphHintValues as _PostScriptGlyphHintValues
- return _PostScriptGlyphHintValues(data=self.asDict())
-
- def _hintObjectsToList(self, item):
- data = []
- done = []
- for hint in item:
- p = (hint.position, hint.width)
- if p in done:
- continue
- data.append(p)
- done.append(p)
- data.sort()
- return data
-
- def _listToHintObjects(self, item):
- hints = []
- done = []
- for pos, width in item:
- if (pos, width) in done:
- # we don't want to set duplicates
- continue
- hints.append(Hint(pos, width))
- done.append((pos,width))
- return hints
-
- def _getVHints(self):
- return self._hintObjectsToList(self._object.vhints)
-
- def _setVHints(self, values):
- # 1 = horizontal hints and links,
- # 2 = vertical hints and links
- # 3 = all hints and links
- self._object.RemoveHints(2)
- if values is None:
- # just clearing it then
- return
- values.sort()
- for hint in self._listToHintObjects(values):
- self._object.vhints.append(hint)
-
- def _getHHints(self):
- return self._hintObjectsToList(self._object.hhints)
-
- def _setHHints(self, values):
- # 1 = horizontal hints and links,
- # 2 = vertical hints and links
- # 3 = all hints and links
- self._object.RemoveHints(1)
- if values is None:
- # just clearing it then
- return
- values.sort()
- for hint in self._listToHintObjects(values):
- self._object.hhints.append(hint)
-
- vHints = property(_getVHints, _setVHints, doc="postscript hints: vertical hint zones")
- hHints = property(_getHHints, _setHHints, doc="postscript hints: horizontal hint zones")
-
-
-
-def _glyphHintsToDict(glyph):
- data = {}
- ##
- ## horizontal and vertical hints
- ##
- # glyph.hhints and glyph.vhints returns a list of Hint objects.
- # Hint objects have position and width attributes.
- data['hHints'] = []
- for index in xrange(len(glyph.hhints)):
- hint = glyph.hhints[index]
- data['hHints'].append((hint.position, hint.width))
- if not data['hHints']:
- del data['hHints']
- data['vHints'] = []
- for index in xrange(len(glyph.vhints)):
- hint = glyph.vhints[index]
- data['vHints'].append((hint.position, hint.width))
- if not data['vHints']:
- del data['vHints']
- ##
- ## horizontal and vertical links
- ##
- # glyph.hlinks and glyph.vlinks returns a list of Link objects.
- # Link objects have node1 and node2 attributes.
- data['hLinks'] = []
- for index in xrange(len(glyph.hlinks)):
- link = glyph.hlinks[index]
- d = { 'node1' : link.node1,
- 'node2' : link.node2,
- }
- data['hLinks'].append(d)
- if not data['hLinks']:
- del data['hLinks']
- data['vLinks'] = []
- for index in xrange(len(glyph.vlinks)):
- link = glyph.vlinks[index]
- d = { 'node1' : link.node1,
- 'node2' : link.node2,
- }
- data['vLinks'].append(d)
- if not data['vLinks']:
- del data['vLinks']
- ##
- ## replacement table
- ##
- # glyph.replace_table returns a list of Replace objects.
- # Replace objects have type and index attributes.
- data['replaceTable'] = []
- for index in xrange(len(glyph.replace_table)):
- replace = glyph.replace_table[index]
- d = { 'type' : replace.type,
- 'index' : replace.index,
- }
- data['replaceTable'].append(d)
- if not data['replaceTable']:
- del data['replaceTable']
- # XXX
- # need to support glyph.instructions and glyph.hdmx?
- # they are not documented very well.
- return data
-
-def _dictHintsToGlyph(glyph, aDict):
- # clear existing hints first
- # RemoveHints requires an "integer mode" argument
- # but it is not documented. from some simple experiments
- # i deduced that
- # 1 = horizontal hints and links,
- # 2 = vertical hints and links
- # 3 = all hints and links
- glyph.RemoveHints(3)
- ##
- ## horizontal and vertical hints
- ##
- if aDict.has_key('hHints'):
- for d in aDict['hHints']:
- glyph.hhints.append(Hint(d[0], d[1]))
- if aDict.has_key('vHints'):
- for d in aDict['vHints']:
- glyph.vhints.append(Hint(d[0], d[1]))
- ##
- ## horizontal and vertical links
- ##
- if aDict.has_key('hLinks'):
- for d in aDict['hLinks']:
- glyph.hlinks.append(Link(d['node1'], d['node2']))
- if aDict.has_key('vLinks'):
- for d in aDict['vLinks']:
- glyph.vlinks.append(Link(d['node1'], d['node2']))
- ##
- ## replacement table
- ##
- if aDict.has_key('replaceTable'):
- for d in aDict['replaceTable']:
- glyph.replace_table.append(Replace(d['type'], d['index']))
-
-# FL Node Types
-flMOVE = 17
-flLINE = 1
-flCURVE = 35
-flOFFCURVE = 65
-flSHARP = 0
-# I have no idea what the difference between
-# "smooth" and "fixed" is, but booth values
-# are returned by FL
-flSMOOTH = 4096
-flFIXED = 12288
-
-
-_flToRFSegmentDict = { flMOVE : MOVE,
- flLINE : LINE,
- flCURVE : CURVE,
- flOFFCURVE : OFFCURVE
- }
-
-_rfToFLSegmentDict = {}
-for k, v in _flToRFSegmentDict.items():
- _rfToFLSegmentDict[v] = k
-
-def _flToRFSegmentType(segmentType):
- return _flToRFSegmentDict[segmentType]
-
-def _rfToFLSegmentType(segmentType):
- return _rfToFLSegmentDict[segmentType]
-
-def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)):
- ogCenter = (centerX, centerY)
- scaledCenter = (centerX * scaleX, centerY * scaleY)
- shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1])
- scaledPointX = (pointX * scaleX) - shiftVal[0]
- scaledPointY = (pointY * scaleY) - shiftVal[1]
- return (scaledPointX, scaledPointY)
-
-# Nostalgia code:
-def CurrentFont():
- """Return a RoboFab font object for the currently selected font."""
- f = fl.font
- if f is not None:
- return RFont(fl.font)
- return None
-
-def CurrentGlyph():
- """Return a RoboFab glyph object for the currently selected glyph."""
- currentPath = fl.font.file_name
- if fl.glyph is None:
- return None
- glyphName = fl.glyph.name
- currentFont = None
- # is this font already loaded as an RFont?
- for font in AllFonts():
- # ugh this won't work because AllFonts sees non RFonts as well....
- if font.path == currentPath:
- currentFont = font
- break
- xx = currentFont[glyphName]
- #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent()
- return xx
-
-def OpenFont(path=None, note=None):
- """Open a font from a path."""
- if path == None:
- from robofab.interface.all.dialogs import GetFile
- path = GetFile(note)
- if path:
- if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']:
- f = Font(path)
- fl.Add(f)
- return RFont(f)
- return None
-
-def NewFont(familyName=None, styleName=None):
- """Make a new font"""
- from FL import fl, Font
- f = Font()
- fl.Add(f)
- rf = RFont(f)
- if familyName is not None:
- rf.info.familyName = familyName
- if styleName is not None:
- rf.info.styleName = styleName
- return rf
-
-def AllFonts():
- """Return a list of all open fonts."""
- fontCount = len(fl)
- all = []
- for index in xrange(fontCount):
- naked = fl[index]
- all.append(RFont(naked))
- return all
-
- from robofab.world import CurrentGlyph
-
-def getGlyphFromMask(g):
- """Get a Fab glyph object for the data in the mask layer."""
- from robofab.objects.objectsFL import RGlyph as FL_RGlyph
- from robofab.objects.objectsRF import RGlyph as RF_RGlyph
- n = g.naked()
- mask = n.mask
- fg = FL_RGlyph(mask)
- rf = RF_RGlyph()
- pen = rf.getPointPen()
- fg.drawPoints(pen)
- rf.width = g.width # can we get to the mask glyph width without flipping the UI?
- return rf
-
-def setMaskToGlyph(maskGlyph, targetGlyph, clear=True):
- """Set the maskGlyph as a mask layer in targetGlyph.
- maskGlyph is a FontLab or RoboFab RGlyph, orphaned or not.
- targetGlyph is a FontLab RGLyph.
- clear is a bool. False: keep the existing mask data, True: clear the existing mask data.
- """
- from robofab.objects.objectsFL import RGlyph as FL_RGlyph
- from FL import Glyph as FL_NakedGlyph
- flGlyph = FL_NakedGlyph() # new, orphaned FL glyph
- wrapped = FL_RGlyph(flGlyph) # rf wrapper for FL glyph
- if not clear:
- # copy the existing mask data first
- existingMask = getGlyphFromMask(targetGlyph)
- if existingMask is not None:
- pen = FLPointContourPen(existingMask)
- existingMask.drawPoints(pen)
- pen = FLPointContourPen(wrapped)
- maskGlyph.drawPoints(pen) # draw the data
- targetGlyph.naked().mask = wrapped .naked()
- targetGlyph.update()
-
-# the lib getter and setter are shared by RFont and RGlyph
-def _get_lib(self):
- data = self._object.customdata
- if data:
- f = StringIO(data)
- try:
- pList = readPlist(f)
- except: # XXX ugh, plistlib can raise lots of things
- # Anyway, customdata does not contain valid plist data,
- # but we don't need to toss it!
- pList = {"org.robofab.fontlab.customdata": Data(data)}
- else:
- pList = {}
- # pass it along to the lib object
- l = RLib(pList)
- l.setParent(self)
- return l
-
-def _set_lib(self, aDict):
- l = RLib({})
- l.setParent(self)
- l.update(aDict)
-
-
-def _normalizeLineEndings(s):
- return s.replace("\r\n", "\n").replace("\r", "\n")
-
-
-class RFont(BaseFont):
- """RoboFab UFO wrapper for FL Font object"""
-
- _title = "FLFont"
-
- def __init__(self, font=None):
- BaseFont.__init__(self)
- if font is None:
- from FL import fl, Font
- # rather than raise an error we could just start a new font.
- font = Font()
- fl.Add(font)
- #raise RoboFabError, "RFont: there's nothing to wrap!?"
- self._object = font
- self._lib = {}
- self._supportHints = True
- self.psHints = PostScriptFontHintValues(self)
- self.psHints.setParent(self)
-
- def keys(self):
- keys = {}
- for glyph in self._object.glyphs:
- glyphName = glyph.name
- if glyphName in keys:
- n = 1
- while ("%s#%s" % (glyphName, n)) in keys:
- n += 1
- newGlyphName = "%s#%s" % (glyphName, n)
- print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName)
- glyphName = newGlyphName
- glyph.name = glyphName
- keys[glyphName] = None
- return keys.keys()
-
- def has_key(self, glyphName):
- glyph = self._object[glyphName]
- if glyph is None:
- return False
- else:
- return True
-
- __contains__ = has_key
-
- def __setitem__(self, glyphName, glyph):
- self._object[glyphName] = glyph.naked()
-
- def __cmp__(self, other):
- if not hasattr(other, '_object'):
- return -1
- return self._compare(other)
- # if self._object.file_name == other._object.file_name:
- # # so, names match.
- # # this will falsely identify two distinct "Untitled"
- # # let's check some more
- # return 0
- # else:
- # return -1
-
-
-# def _get_psHints(self):
-# h = PostScriptFontHintValues(self)
-# h.setParent(self)
-# return h
-#
-# psHints = property(_get_psHints, doc="font level postscript hint data")
-
- def _get_info(self):
- return RInfo(self._object)
-
- info = property(_get_info, doc="font info object")
-
- def _get_features(self):
- return RFeatures(self._object)
-
- features = property(_get_features, doc="features object")
-
- def _get_kerning(self):
- kerning = {}
- f = self._object
- for g in f.glyphs:
- for p in g.kerning:
- try:
- key = (g.name, f[p.key].name)
- kerning[key] = p.value
- except AttributeError: pass #catch for TT exception
- rk = RKerning(kerning)
- rk.setParent(self)
- return rk
-
- kerning = property(_get_kerning, doc="a kerning object")
-
- def _set_groups(self, aDict):
- g = RGroups({})
- g.setParent(self)
- g.update(aDict)
-
- def _get_groups(self):
- groups = {}
- for i in self._object.classes:
- # test to make sure that the class is properly formatted
- if i.find(':') == -1:
- continue
- key = i.split(':')[0]
- value = i.split(':')[1].lstrip().split(' ')
- groups[key] = value
- rg = RGroups(groups)
- rg.setParent(self)
- return rg
-
- groups = property(_get_groups, _set_groups, doc="a group object")
-
- lib = property(_get_lib, _set_lib, doc="font lib object")
-
- #
- # attributes
- #
-
- def _get_fontIndex(self):
- # find the index of the font
- # by comparing the file_name
- # to all open fonts. if the
- # font has no file_name, meaning
- # it is a new, unsaved font,
- # return the index of the first
- # font with no file_name.
- selfFileName = self._object.file_name
- fontCount = len(fl)
- for index in xrange(fontCount):
- other = fl[index]
- if other.file_name == selfFileName:
- return index
-
- fontIndex = property(_get_fontIndex, doc="the fontindex for this font")
-
- def _get_path(self):
- return self._object.file_name
-
- path = property(_get_path, doc="path to the font")
-
- def _get_fileName(self):
- if self.path is None:
- return None
- return os.path.split(self.path)
-
- fileName = property(_get_fileName, doc="the font's file name")
-
- def _get_selection(self):
- # return a list of glyph names for glyphs selected in the font window
- l=[]
- for i in range(len(self._object.glyphs)):
- if fl.Selected(i) == 1:
- l.append(self._object[i].name)
- return l
-
- def _set_selection(self, list):
- fl.Unselect()
- for i in list:
- fl.Select(i)
-
- selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window")
-
-
- def _makeGlyphlist(self):
- # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters.
- gl = []
- for c in self:
- gl.append(c)
- return gl
-
- def _get_glyphs(self):
- return self._makeGlyphlist()
-
- glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs")
-
- def update(self):
- """Don't forget to update the font when you are done."""
- fl.UpdateFont(self.fontIndex)
-
- def save(self, path=None):
- """Save the font, path is required."""
- if not path:
- if not self._object.file_name:
- raise RoboFabError, "No destination path specified."
- else:
- path = self._object.file_name
- fl.Save(self.fontIndex, path)
-
- def close(self, save=False):
- """Close the font, saving is optional."""
- if save:
- self.save()
- else:
- self._object.modified = 0
- fl.Close(self.fontIndex)
-
- def getGlyph(self, glyphName):
- # XXX may need to become private
- flGlyph = self._object[glyphName]
- if flGlyph is not None:
- glyph = RGlyph(flGlyph)
- glyph.setParent(self)
- return glyph
- return self.newGlyph(glyphName)
-
- def newGlyph(self, glyphName, clear=True):
- """Make a new glyph."""
- # the old implementation always updated the font.
- # that proved to be very slow. so, the updating is
- # now left up to the caller where it can be more
- # efficiently managed.
- g = NewGlyph(self._object, glyphName, clear, updateFont=False)
- return RGlyph(g)
-
- def insertGlyph(self, glyph, name=None):
- """Returns a new glyph that has been inserted into the font.
- name = another glyphname if you want to insert as with that."""
- from robofab.objects.objectsRF import RFont as _RFont
- from robofab.objects.objectsRF import RGlyph as _RGlyph
- oldGlyph = glyph
- if name is None:
- name = oldGlyph.name
- # clear the destination glyph if it exists.
- if self.has_key(name):
- self[name].clear()
- # get the parent for the glyph
- otherFont = oldGlyph.getParent()
- # in some cases we will use the native
- # FL method for appending a glyph.
- useNative = True
- testingNative = True
- while testingNative:
- # but, maybe it is an orphan glyph.
- # in that case we should not use the native method.
- if otherFont is None:
- useNative = False
- testingNative = False
- # or maybe the glyph is coming from a NoneLab font
- if otherFont is not None:
- if isinstance(otherFont, _RFont):
- useNative = False
- testingNative = False
- # but, it could be a copied FL glyph
- # which is a NoneLab glyph that
- # has a FontLab font as the parent
- elif isinstance(otherFont, RFont):
- useNative = False
- testingNative = False
- # or, maybe the glyph is being replaced, in which
- # case the native method should not be used
- # since FL will destroy any references to the glyph
- if self.has_key(name):
- useNative = False
- testingNative = False
- # if the glyph contains components the native
- # method should not be used since FL does
- # not reference glyphs in components by
- # name, but by index (!!!).
- if len(oldGlyph.components) != 0:
- useNative = False
- testingNative = False
- testingNative = False
- # finally, insert the glyph.
- if useNative:
- font = self.naked()
- otherFont = oldGlyph.getParent().naked()
- self.naked().glyphs.append(otherFont[name])
- newGlyph = self.getGlyph(name)
- else:
- newGlyph = self.newGlyph(name)
- newGlyph.appendGlyph(oldGlyph)
- for attr in GLYPH_COPY_ATTRS:
- if attr == "name":
- value = name
- else:
- value = getattr(oldGlyph, attr)
- setattr(newGlyph, attr, value)
- if self._supportHints:
- # now we need to transfer the hints from
- # the old glyph to the new glyph. we'll do this
- # via the dict to hint functions.
- hintDict = {}
- # if the glyph is a NoneLab glyph, then we need
- # to extract the ps hints from the lib
- if isinstance(oldGlyph, _RGlyph):
- hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {})
- # otherwise we need to extract the hint dict from the glyph
- else:
- hintDict = _glyphHintsToDict(oldGlyph.naked())
- # now apply the hint data
- if hintDict:
- _dictHintsToGlyph(newGlyph.naked(), hintDict)
- # delete any remaining hint data from the glyph lib
- if newGlyph.lib.has_key(postScriptHintDataLibKey):
- del newGlyph.lib[postScriptHintDataLibKey]
- return newGlyph
-
- def removeGlyph(self, glyphName):
- """remove a glyph from the font"""
- index = self._object.FindGlyph(glyphName)
- if index != -1:
- del self._object.glyphs[index]
-
- #
- # opentype
- #
-
- def getOTClasses(self):
- """Return all OpenType classes as a dict. Relies on properly formatted classes."""
- classes = {}
- c = self._object.ot_classes
- if c is None:
- return classes
- c = c.replace('\r', '').replace('\n', '').split(';')
- for i in c:
- if i.find('=') != -1:
- value = []
- i = i.replace(' = ', '=')
- name = i.split('=')[0]
- v = i.split('=')[1].replace('[', '').replace(']', '').split(' ')
- #catch double spaces?
- for j in v:
- if len(j) > 0:
- value.append(j)
- classes[name] = value
- return classes
-
- def setOTClasses(self, dict):
- """Set all OpenType classes."""
- l = []
- for i in dict.keys():
- l.append(''.join([i, ' = [', ' '.join(dict[i]), '];']))
- self._object.ot_classes = '\n'.join(l)
-
- def getOTClass(self, name):
- """Get a specific OpenType class."""
- classes = self.getOTClasses()
- return classes[name]
-
- def setOTClass(self, name, list):
- """Set a specific OpenType class."""
- classes = self.getOTClasses()
- classes[name] = list
- self.setOTClasses(classes)
-
- def getOTFeatures(self):
- """Return all OpenType features as a dict keyed by name.
- The value is a string of the text of the feature."""
- features = {}
- for i in self._object.features:
- v = []
- for j in i.value.replace('\r', '\n').split('\n'):
- if j.find(i.tag) == -1:
- v.append(j)
- features[i.tag] = '\n'.join(v)
- return features
-
- def setOTFeatures(self, dict):
- """Set all OpenType features in the font."""
- features= {}
- for i in dict.keys():
- f = []
- f.append('feature %s {'%i)
- f.append(dict[i])
- f.append('} %s;'%i)
- features[i] = '\n'.join(f)
- self._object.features.clean()
- for i in features.keys():
- self._object.features.append(Feature(i, features[i]))
-
- def getOTFeature(self, name):
- """return a specific OpenType feature."""
- features = self.getOTFeatures()
- return features[name]
-
- def setOTFeature(self, name, text):
- """Set a specific OpenType feature."""
- features = self.getOTFeatures()
- features[name] = text
- self.setOTFeatures(features)
-
- #
- # guides
- #
-
- def getVGuides(self):
- """Return a list of wrapped vertical guides in this RFont"""
- vguides=[]
- for i in range(len(self._object.vguides)):
- g = RGuide(self._object.vguides[i], i)
- g.setParent(self)
- vguides.append(g)
- return vguides
-
- def getHGuides(self):
- """Return a list of wrapped horizontal guides in this RFont"""
- hguides=[]
- for i in range(len(self._object.hguides)):
- g = RGuide(self._object.hguides[i], i)
- g.setParent(self)
- hguides.append(g)
- return hguides
-
- def appendHGuide(self, position, angle=0):
- """Append a horizontal guide"""
- position = int(round(position))
- angle = int(round(angle))
- g=Guide(position, angle)
- self._object.hguides.append(g)
-
- def appendVGuide(self, position, angle=0):
- """Append a horizontal guide"""
- position = int(round(position))
- angle = int(round(angle))
- g=Guide(position, angle)
- self._object.vguides.append(g)
-
- def removeHGuide(self, guide):
- """Remove a horizontal guide."""
- pos = (guide.position, guide.angle)
- for g in self.getHGuides():
- if (g.position, g.angle) == pos:
- del self._object.hguides[g.index]
- break
-
- def removeVGuide(self, guide):
- """Remove a vertical guide."""
- pos = (guide.position, guide.angle)
- for g in self.getVGuides():
- if (g.position, g.angle) == pos:
- del self._object.vguides[g.index]
- break
-
- def clearHGuides(self):
- """Clear all horizontal guides."""
- self._object.hguides.clean()
-
- def clearVGuides(self):
- """Clear all vertical guides."""
- self._object.vguides.clean()
-
-
- #
- # generators
- #
-
- def generate(self, outputType, path=None):
- """
- generate the font. outputType is the type of font to ouput.
- --Ouput Types:
- 'pctype1' : PC Type 1 font (binary/PFB)
- 'pcmm' : PC MultipleMaster font (PFB)
- 'pctype1ascii' : PC Type 1 font (ASCII/PFA)
- 'pcmmascii' : PC MultipleMaster font (ASCII/PFA)
- 'unixascii' : UNIX ASCII font (ASCII/PFA)
- 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file)
- 'otfcff' : PS OpenType (CFF-based) font (OTF)
- 'otfttf' : PC TrueType/TT OpenType font (TTF)
- 'macttf' : Mac TrueType font (generates suitcase)
- 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork)
- (doc adapted from http://dev.fontlab.net/flpydoc/)
-
- path can be a directory or a directory file name combo:
- path="DirectoryA/DirectoryB"
- path="DirectoryA/DirectoryB/MyFontName"
- if no path is given, the file will be output in the same directory
- as the vfb file. if no file name is given, the filename will be the
- vfb file name with the appropriate suffix.
- """
- outputType = outputType.lower()
- if not _flGenerateTypes.has_key(outputType):
- raise RoboFabError, "%s output type is not supported"%outputType
- flOutputType, suffix = _flGenerateTypes[outputType]
- if path is None:
- filePath, fileName = os.path.split(self.path)
- fileName = fileName.replace('.vfb', '')
- else:
- if os.path.isdir(path):
- filePath = path
- fileName = os.path.split(self.path)[1].replace('.vfb', '')
- else:
- filePath, fileName = os.path.split(path)
- if '.' in fileName:
- raise RoboFabError, "filename cannot contain periods.", fileName
- fileName = '.'.join([fileName, suffix])
- finalPath = os.path.join(filePath, fileName)
- if isinstance(finalPath, unicode):
- finalPath = finalPath.encode("utf-8")
- # generate is (oddly) an application level method
- # rather than a font level method. because of this,
- # the font must be the current font. so, make it so.
- fl.ifont = self.fontIndex
- fl.GenerateFont(flOutputType, finalPath)
-
- def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None,
- doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2):
- from robofab.interface.all.dialogs import ProgressBar, Message
- # special glyph name to file name conversion
- if glyphNameToFileNameFunc is None:
- glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
- if glyphNameToFileNameFunc is None:
- from robofab.tools.glyphNameSchemes import glyphNameToShortFileName
- glyphNameToFileNameFunc = glyphNameToShortFileName
- # get a valid path
- if not path:
- if self.path is None:
- Message("Please save this font first before exporting to UFO...")
- return
- else:
- path = ufoLib.makeUFOPath(self.path)
- # get the glyphs to export
- if glyphs is None:
- glyphs = self.keys()
- # if the file exists, check the format version.
- # if the format version being written is different
- # from the format version of the existing UFO
- # and only some files are set to be written
- # raise an error.
- if os.path.exists(path):
- if os.path.exists(os.path.join(path, "metainfo.plist")):
- reader = ufoLib.UFOReader(path)
- existingFormatVersion = reader.formatVersion
- if formatVersion != existingFormatVersion:
- if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]:
- Message("When overwriting an existing UFO with a different format version all files must be written.")
- return
- # the lib must be written if format version is 1
- if not doLib and formatVersion == 1:
- Message("The lib must be written when exporting format version 1.")
- return
- # set up the progress bar
- nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True)
- bar = None
- if doProgress:
- bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs))
- # try writing
- try:
- writer = ufoLib.UFOWriter(path, formatVersion=formatVersion)
- ## We make a shallow copy if lib, since we add some stuff for export
- ## that doesn't need to be retained in memory.
- fontLib = dict(self.lib)
- # write the font info
- if doInfo:
- global _IN_UFO_EXPORT
- _IN_UFO_EXPORT = True
- writer.writeInfo(self.info)
- _IN_UFO_EXPORT = False
- if bar:
- bar.tick()
- # write the kerning
- if doKerning:
- writer.writeKerning(self.kerning.asDict())
- if bar:
- bar.tick()
- # write the groups
- if doGroups:
- writer.writeGroups(self.groups)
- if bar:
- bar.tick()
- # write the features
- if doFeatures:
- if formatVersion == 2:
- writer.writeFeatures(self.features.text)
- else:
- self._writeOpenTypeFeaturesToLib(fontLib)
- if bar:
- bar.tick()
- # write the lib
- if doLib:
- ## Always export the postscript font hint values to the lib in format version 1
- if formatVersion == 1:
- d = self.psHints.asDict()
- fontLib[postScriptHintDataLibKey] = d
- ## Export the glyph order to the lib
- glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs]
- fontLib["public.glyphOrder"] = glyphOrder
- ## export the features
- if doFeatures and formatVersion == 1:
- self._writeOpenTypeFeaturesToLib(fontLib)
- if bar:
- bar.tick()
- writer.writeLib(fontLib)
- if bar:
- bar.tick()
- # write the glyphs
- if glyphs:
- glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc)
- count = nonGlyphCount
- for nakedGlyph in self.naked().glyphs:
- if nakedGlyph.name not in glyphs:
- continue
- glyph = RGlyph(nakedGlyph)
- if doHints:
- hintStuff = _glyphHintsToDict(glyph.naked())
- if hintStuff:
- glyph.lib[postScriptHintDataLibKey] = hintStuff
- glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints)
- # remove the hint dict from the lib
- if doHints and glyph.lib.has_key(postScriptHintDataLibKey):
- del glyph.lib[postScriptHintDataLibKey]
- if bar and not count % 10:
- bar.tick(count)
- count = count + 1
- glyphSet.writeContents()
- # only blindly stop if the user says to
- except KeyboardInterrupt:
- if bar:
- bar.close()
- bar = None
- # kill the bar
- if bar:
- bar.close()
-
- def _writeOpenTypeFeaturesToLib(self, fontLib):
- # this should only be used for UFO format version 1
- flFont = self.naked()
- cls = flFont.ot_classes
- if cls is not None:
- fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(cls).rstrip() + "\n"
- if flFont.features:
- features = {}
- order = []
- for feature in flFont.features:
- order.append(feature.tag)
- features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n"
- fontLib["org.robofab.opentype.features"] = features
- fontLib["org.robofab.opentype.featureorder"] = order
-
- def readUFO(self, path, doProgress=False,
- doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None):
- """read a .ufo into the font"""
- from robofab.pens.flPen import FLPointPen
- from robofab.interface.all.dialogs import ProgressBar
- # start up the reader
- reader = ufoLib.UFOReader(path)
- glyphSet = reader.getGlyphSet()
- # get a list of glyphs that should be imported
- if glyphs is None:
- glyphs = glyphSet.keys()
- # set up the progress bar
- nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True)
- bar = None
- if doProgress:
- bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs))
- # start reading
- try:
- fontLib = reader.readLib()
- # info
- if doInfo:
- reader.readInfo(self.info)
- if bar:
- bar.tick()
- # glyphs
- count = 1
- glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet)
- for glyphName in glyphOrder:
- if glyphName not in glyphs:
- continue
- glyph = self.newGlyph(glyphName, clear=True)
- pen = FLPointPen(glyph.naked())
- glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen)
- if doHints:
- hintData = glyph.lib.get(postScriptHintDataLibKey)
- if hintData:
- _dictHintsToGlyph(glyph.naked(), hintData)
- # now that the hints have been extracted from the glyph
- # there is no reason to keep the location in the lib.
- if glyph.lib.has_key(postScriptHintDataLibKey):
- del glyph.lib[postScriptHintDataLibKey]
- if bar and not count % 10:
- bar.tick(count)
- count = count + 1
- # features
- if doFeatures:
- if reader.formatVersion == 1:
- self._readOpenTypeFeaturesFromLib(fontLib)
- else:
- featureText = reader.readFeatures()
- self.features.text = featureText
- if bar:
- bar.tick()
- else:
- # remove features stored in the lib
- self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False)
- # kerning
- if doKerning:
- self.kerning.clear()
- self.kerning.update(reader.readKerning())
- if bar:
- bar.tick()
- # groups
- if doGroups:
- self.groups.clear()
- self.groups.update(reader.readGroups())
- if bar:
- bar.tick()
- # hints in format version 1
- if doHints and reader.formatVersion == 1:
- self.psHints._loadFromLib(fontLib)
- else:
- # remove hint data stored in the lib
- if fontLib.has_key(postScriptHintDataLibKey):
- del fontLib[postScriptHintDataLibKey]
- # lib
- if doLib:
- self.lib.clear()
- self.lib.update(fontLib)
- if bar:
- bar.tick()
- # update the font
- self.update()
- # only blindly stop if the user says to
- except KeyboardInterrupt:
- bar.close()
- bar = None
- # kill the bar
- if bar:
- bar.close()
-
- def _getGlyphOrderFromLib(self, fontLib, glyphSet):
- key = "public.glyphOrder"
- glyphOrder = fontLib.get(key)
- if glyphOrder is None:
- key = "org.robofab.glyphOrder"
- glyphOrder = fontLib.get(key)
- if glyphOrder is not None:
- # no need to keep track if the glyph order in lib once the font is loaded.
- del fontLib[key]
- glyphNames = []
- done = {}
- for glyphName in glyphOrder:
- if glyphName in glyphSet:
- glyphNames.append(glyphName)
- done[glyphName] = 1
- allGlyphNames = glyphSet.keys()
- allGlyphNames.sort()
- for glyphName in allGlyphNames:
- if glyphName not in done:
- glyphNames.append(glyphName)
- else:
- glyphNames = glyphSet.keys()
- glyphNames.sort()
- return glyphNames
-
- def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True):
- # setFeatures may be False. in this case, this method
- # should only clear the data from the lib.
- classes = fontLib.get("org.robofab.opentype.classes")
- if classes is not None:
- del fontLib["org.robofab.opentype.classes"]
- if setFeatures:
- self.naked().ot_classes = classes
- features = fontLib.get("org.robofab.opentype.features")
- if features is not None:
- order = fontLib.get("org.robofab.opentype.featureorder")
- if order is None:
- # for UFOs saved without the feature order, do the same as before.
- order = features.keys()
- order.sort()
- else:
- del fontLib["org.robofab.opentype.featureorder"]
- del fontLib["org.robofab.opentype.features"]
- #features = features.items()
- orderedFeatures = []
- for tag in order:
- oneFeature = features.get(tag)
- if oneFeature is not None:
- orderedFeatures.append((tag, oneFeature))
- if setFeatures:
- self.naked().features.clean()
- for tag, src in orderedFeatures:
- self.naked().features.append(Feature(tag, src))
-
-
-
-class RGlyph(BaseGlyph):
- """RoboFab wrapper for FL Glyph object"""
-
- _title = "FLGlyph"
-
- def __init__(self, flGlyph):
- #BaseGlyph.__init__(self)
- if flGlyph is None:
- raise RoboFabError, "RGlyph: there's nothing to wrap!?"
- self._object = flGlyph
- self._lib = {}
- self._contours = None
-
- def __getitem__(self, index):
- return self.contours[index]
-
- def __delitem__(self, index):
- self._object.DeleteContour(index)
- self._invalidateContours()
-
- def __len__(self):
- return len(self.contours)
-
- lib = property(_get_lib, _set_lib, doc="glyph lib object")
-
- def _invalidateContours(self):
- self._contours = None
-
- def _buildContours(self):
- self._contours = []
- for contourIndex in range(self._object.GetContoursNumber()):
- c = RContour(contourIndex)
- c.setParent(self)
- c._buildSegments()
- self._contours.append(c)
-
- #
- # attribute handlers
- #
-
- def _get_index(self):
- return self._object.parent.FindGlyph(self.name)
-
- index = property(_get_index, doc="return the index of the glyph in the font")
-
- def _get_name(self):
- return self._object.name
-
- def _set_name(self, value):
- self._object.name = value
-
- name = property(_get_name, _set_name, doc="name")
-
- def _get_psName(self):
- return self._object.name
-
- def _set_psName(self, value):
- self._object.name = value
-
- psName = property(_get_psName, _set_psName, doc="name")
-
- def _get_baseName(self):
- return self._object.name.split('.')[0]
-
- baseName = property(_get_baseName, doc="")
-
- def _get_unicode(self):
- return self._object.unicode
-
- def _set_unicode(self, value):
- self._object.unicode = value
-
- unicode = property(_get_unicode, _set_unicode, doc="unicode")
-
- def _get_unicodes(self):
- return self._object.unicodes
-
- def _set_unicodes(self, value):
- self._object.unicodes = value
-
- unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes")
-
- def _get_width(self):
- return self._object.width
-
- def _set_width(self, value):
- value = int(round(value))
- self._object.width = value
-
- width = property(_get_width, _set_width, doc="the width")
-
- def _get_box(self):
- if not len(self.contours) and not len(self.components):
- return (0, 0, 0, 0)
- r = self._object.GetBoundingRect()
- return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y)))
-
- box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)")
-
- def _get_selected(self):
- if fl.Selected(self._object.parent.FindGlyph(self._object.name)):
- return 1
- else:
- return 0
-
- def _set_selected(self, value):
- fl.Select(self._object.name, value)
-
- selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window")
-
- def _get_mark(self):
- return self._object.mark
-
- def _set_mark(self, value):
- self._object.mark = value
-
- mark = property(_get_mark, _set_mark, doc="mark")
-
- def _get_note(self):
- s = self._object.note
- if s is None:
- return s
- return unicode(s, LOCAL_ENCODING)
-
- def _set_note(self, value):
- if value is None:
- value = ''
- if type(value) == type(u""):
- value = value.encode(LOCAL_ENCODING)
- self._object.note = value
-
- note = property(_get_note, _set_note, doc="note")
-
- def _get_psHints(self):
- # get an object representing the postscript zone information
- return PostScriptGlyphHintValues(self)
-
- psHints = property(_get_psHints, doc="postscript hint data")
-
- #
- # necessary evil
- #
-
- def update(self):
- """Don't forget to update the glyph when you are done."""
- fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name))
-
- #
- # methods to make RGlyph compatible with FL.Glyph
- # ##are these still needed?
- #
-
- def GetBoundingRect(self, masterIndex):
- """FL compatibility"""
- return self._object.GetBoundingRect(masterIndex)
-
- def GetMetrics(self, masterIndex):
- """FL compatibility"""
- return self._object.GetMetrics(masterIndex)
-
- def SetMetrics(self, value, masterIndex):
- """FL compatibility"""
- return self._object.SetMetrics(value, masterIndex)
-
- #
- # object builders
- #
-
- def _get_anchors(self):
- return self.getAnchors()
-
- anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors")
-
- def _get_components(self):
- return self.getComponents()
-
- components = property(_get_components, doc="allow for iteration through glyph.components")
-
- def _get_contours(self):
- if self._contours is None:
- self._buildContours()
- return self._contours
-
- contours = property(_get_contours, doc="allow for iteration through glyph.contours")
-
- def getAnchors(self):
- """Return a list of wrapped anchors in this RGlyph."""
- anchors=[]
- for i in range(len(self._object.anchors)):
- a = RAnchor(self._object.anchors[i], i)
- a.setParent(self)
- anchors.append(a)
- return anchors
-
- def getComponents(self):
- """Return a list of wrapped components in this RGlyph."""
- components=[]
- for i in range(len(self._object.components)):
- c = RComponent(self._object.components[i], i)
- c.setParent(self)
- components.append(c)
- return components
-
- def getVGuides(self):
- """Return a list of wrapped vertical guides in this RGlyph"""
- vguides=[]
- for i in range(len(self._object.vguides)):
- g = RGuide(self._object.vguides[i], i)
- g.setParent(self)
- vguides.append(g)
- return vguides
-
- def getHGuides(self):
- """Return a list of wrapped horizontal guides in this RGlyph"""
- hguides=[]
- for i in range(len(self._object.hguides)):
- g = RGuide(self._object.hguides[i], i)
- g.setParent(self)
- hguides.append(g)
- return hguides
-
- #
- # tools
- #
-
- def getPointPen(self):
- self._invalidateContours()
- # Now just don't muck with glyph.contours before you're done drawing...
- return FLPointPen(self)
-
- def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)):
- """Append a component to the glyph. x and y are optional offset values"""
- offset = roundPt((offset[0], offset[1]))
- p = FLPointPen(self.naked())
- xx, yy = scale
- dx, dy = offset
- p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy))
-
- def appendAnchor(self, name, position):
- """Append an anchor to the glyph"""
- value = roundPt((position[0], position[1]))
- anchor = Anchor(name, value[0], value[1])
- self._object.anchors.append(anchor)
-
- def appendHGuide(self, position, angle=0):
- """Append a horizontal guide"""
- position = int(round(position))
- g = Guide(position, angle)
- self._object.hguides.append(g)
-
- def appendVGuide(self, position, angle=0):
- """Append a horizontal guide"""
- position = int(round(position))
- g = Guide(position, angle)
- self._object.vguides.append(g)
-
- def clearContours(self):
- self._object.Clear()
- self._invalidateContours()
-
- def clearComponents(self):
- """Clear all components."""
- self._object.components.clean()
-
- def clearAnchors(self):
- """Clear all anchors."""
- self._object.anchors.clean()
-
- def clearHGuides(self):
- """Clear all horizontal guides."""
- self._object.hguides.clean()
-
- def clearVGuides(self):
- """Clear all vertical guides."""
- self._object.vguides.clean()
-
- def removeComponent(self, component):
- """Remove a specific component from the glyph. This only works
- if the glyph does not have duplicate components in the same location."""
- pos = (component.baseGlyph, component.offset, component.scale)
- a = self.getComponents()
- found = []
- for i in a:
- if (i.baseGlyph, i.offset, i.scale) == pos:
- found.append(i)
- if len(found) > 1:
- raise RoboFabError, 'Found more than one possible component to remove'
- elif len(found) == 1:
- del self._object.components[found[0].index]
- else:
- raise RoboFabError, 'Component does not exist'
-
- def removeContour(self, index):
- """remove a specific contour from the glyph"""
- self._object.DeleteContour(index)
- self._invalidateContours()
-
- def removeAnchor(self, anchor):
- """Remove a specific anchor from the glyph. This only works
- if the glyph does not have anchors with duplicate names
- in exactly the same location with the same mark."""
- pos = (anchor.name, anchor.position, anchor.mark)
- a = self.getAnchors()
- found = []
- for i in a:
- if (i.name, i.position, i.mark) == pos:
- found.append(i)
- if len(found) > 1:
- raise RoboFabError, 'Found more than one possible anchor to remove'
- elif len(found) == 1:
- del self._object.anchors[found[0].index]
- else:
- raise RoboFabError, 'Anchor does not exist'
-
- def removeHGuide(self, guide):
- """Remove a horizontal guide."""
- pos = (guide.position, guide.angle)
- for g in self.getHGuides():
- if (g.position, g.angle) == pos:
- del self._object.hguides[g.index]
- break
-
- def removeVGuide(self, guide):
- """Remove a vertical guide."""
- pos = (guide.position, guide.angle)
- for g in self.getVGuides():
- if (g.position, g.angle) == pos:
- del self._object.vguides[g.index]
- break
-
- def center(self, padding=None):
- """Equalise sidebearings, set to padding if wanted."""
- left = self.leftMargin
- right = self.rightMargin
- if padding:
- e_left = e_right = padding
- else:
- e_left = (left + right)/2
- e_right = (left + right) - e_left
- self.leftMargin= e_left
- self.rightMargin= e_right
-
- def removeOverlap(self):
- """Remove overlap"""
- self._object.RemoveOverlap()
- self._invalidateContours()
-
- def decompose(self):
- """Decompose all components"""
- self._object.Decompose()
- self._invalidateContours()
-
- ##broken!
- #def removeHints(self):
- # """Remove the hints."""
- # self._object.RemoveHints()
-
- def autoHint(self):
- """Automatically generate type 1 hints."""
- self._object.Autohint()
-
- def move(self, (x, y), contours=True, components=True, anchors=True):
- """Move a glyph's items that are flagged as True"""
- x, y = roundPt((x, y))
- self._object.Shift(Point(x, y))
- for c in self.getComponents():
- c.move((x, y))
- for a in self.getAnchors():
- a.move((x, y))
-
- def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True):
- """Clear all items marked as true from the glyph"""
- if contours:
- self._object.Clear()
- self._invalidateContours()
- if components:
- self._object.components.clean()
- if anchors:
- self._object.anchors.clean()
- if guides:
- self._object.hguides.clean()
- self._object.vguides.clean()
- if hints:
- # RemoveHints requires an "integer mode" argument
- # but it is not documented. from some simple experiments
- # i deduced that
- # 1 = horizontal hints and links,
- # 2 = vertical hints and links
- # 3 = all hints and links
- self._object.RemoveHints(3)
-
- #
- # special treatment for GlyphMath support in FontLab
- #
-
- def _getMathDestination(self):
- from robofab.objects.objectsRF import RGlyph as _RGlyph
- return _RGlyph()
-
- def copy(self, aParent=None):
- """Make a copy of this glyph.
- Note: the copy is not a duplicate fontlab glyph, but
- a RF RGlyph with the same outlines. The new glyph is
- not part of the fontlab font in any way. Use font.appendGlyph(glyph)
- to get it in a FontLab glyph again."""
- from robofab.objects.objectsRF import RGlyph as _RGlyph
- newGlyph = _RGlyph()
- newGlyph.appendGlyph(self)
- for attr in GLYPH_COPY_ATTRS:
- value = getattr(self, attr)
- setattr(newGlyph, attr, value)
- # hints
- doHints = False
- parent = self.getParent()
- if parent is not None and parent._supportHints:
- hintStuff = _glyphHintsToDict(self.naked())
- if hintStuff:
- newGlyph.lib[postScriptHintDataLibKey] = hintStuff
- if aParent is not None:
- newGlyph.setParent(aParent)
- elif self.getParent() is not None:
- newGlyph.setParent(self.getParent())
- return newGlyph
-
- def __mul__(self, factor):
- return self.copy() *factor
-
- __rmul__ = __mul__
-
- def __sub__(self, other):
- return self.copy() - other.copy()
-
- def __add__(self, other):
- return self.copy() + other.copy()
-
-
-
-class RContour(BaseContour):
-
- """RoboFab wrapper for non FL contour object"""
-
- _title = "FLContour"
-
- def __init__(self, index):
- self._index = index
- self._parentGlyph = None
- self.segments = []
-
- def __len__(self):
- return len(self.points)
-
- def _buildSegments(self):
- #######################
- # Notes about FL node contour structure
- #######################
- # for TT curves, FL lists them as seperate nodes:
- # [move, off, off, off, line, off, off]
- # and, this list is sequential. after the last on curve,
- # it is possible (and likely) that there will be more offCurves
- # in our segment object, these should be associated with the
- # first segment in the contour.
- #
- # for PS curves, it is a very different scenerio.
- # curve nodes contain points:
- # [on, off, off]
- # and the list is not in sequential order. the first point in
- # the list is the on curve and the subsequent points are the off
- # curve points leading up to that on curve.
- #
- # it is very important to remember these structures when trying
- # to understand the code below
-
- self.segments = []
- offList = []
- nodes = self._nakedParent.nodes
- for index in range(self._nodeLength):
- x = index + self._startNodeIndex
- node = nodes[x]
- # we do have a loose off curve. deal with it.
- if node.type == flOFFCURVE:
- offList.append(x)
- # we are not dealing with a loose off curve
- else:
- s = RSegment(x)
- s.setParent(self)
- # but do we have a collection of loose off curves above?
- # if so, apply them to the segment, and clear the list
- if len(offList) != 0:
- s._looseOffCurve = offList
- offList = []
- self.segments.append(s)
- # do we have some off curves now that the contour is complete?
- if len(offList) != 0:
- # ugh. apply them to the first segment
- self.segments[0]._looseOffCurve = offList
-
- def setParent(self, parentGlyph):
- self._parentGlyph = parentGlyph
-
- def getParent(self):
- return self._parentGlyph
-
- def _get__nakedParent(self):
- return self._parentGlyph.naked()
-
- _nakedParent = property(_get__nakedParent, doc="")
-
- def _get__startNodeIndex(self):
- return self._nakedParent.GetContourBegin(self._index)
-
- _startNodeIndex = property(_get__startNodeIndex, doc="")
-
- def _get__nodeLength(self):
- return self._nakedParent.GetContourLength(self._index)
-
- _nodeLength = property(_get__nodeLength, doc="")
-
- def _get__lastNodeIndex(self):
- return self._startNodeIndex + self._nodeLength - 1
-
- _lastNodeIndex = property(_get__lastNodeIndex, doc="")
-
- def _previousNodeIndex(self, index):
- return (index - 1) % self._nodeLength
-
- def _nextNodeIndex(self, index):
- return (index + 1) % self._nodeLength
-
- def _getNode(self, index):
- return self._nodes[index]
-
- def _get__nodes(self):
- nodes = []
- for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]:
- nodes.append(node)
- return nodes
-
- _nodes = property(_get__nodes, doc="")
-
- def _get_points(self):
- points = []
- for segment in self.segments:
- for point in segment.points:
- points.append(point)
- return points
-
- points = property(_get_points, doc="")
-
- def _get_bPoints(self):
- bPoints = []
- for segment in self.segments:
- bp = RBPoint(segment.index)
- bp.setParent(self)
- bPoints.append(bp)
- return bPoints
-
- bPoints = property(_get_bPoints, doc="")
-
- def _get_index(self):
- return self._index
-
- def _set_index(self, index):
- if index != self._index:
- self._nakedParent.ReorderContour(self._index, index)
- # reorder and set the _index of the existing RContour objects
- # this will be a better solution than reconstructing all the objects
- # segment objects will still, sadly, have to be reconstructed
- contourList = self.getParent().contours
- contourList.insert(index, contourList.pop(self._index))
- for i in range(len(contourList)):
- contourList[i]._index = i
- contourList[i]._buildSegments()
-
-
- index = property(_get_index, _set_index, doc="the index of the contour")
-
- def _get_selected(self):
- selected = 0
- nodes = self._nodes
- for node in nodes:
- if node.selected == 1:
- selected = 1
- break
- return selected
-
- def _set_selected(self, value):
- if value == 1:
- self._nakedParent.SelectContour(self._index)
- else:
- for node in self._nodes:
- node.selected = value
-
- selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected")
-
- def appendSegment(self, segmentType, points, smooth=False):
- segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth)
- return segment
-
- def insertSegment(self, index, segmentType, points, smooth=False):
- """insert a seggment into the contour"""
- # do a qcurve insertion
- if segmentType == QCURVE:
- count = 0
- for point in points[:-1]:
- newNode = Node(flOFFCURVE, Point(point[0], point[1]))
- self._nakedParent.Insert(newNode, self._startNodeIndex + index + count)
- count = count + 1
- newNode = Node(flLINE, Point(points[-1][0], points[-1][1]))
- self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1)
- # do a regular insertion
- else:
- onX, onY = points[-1]
- newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY))
- # fix the off curves in case the user is inserting a curve
- # but is not specifying off curve points
- if segmentType == CURVE and len(points) == 1:
- pSeg = self._prevSegment(index)
- pOn = pSeg.onCurve
- newNode.points[1].Assign(Point(pOn.x, pOn.y))
- newNode.points[2].Assign(Point(onX, onY))
- for pointIndex in range(len(points[:-1])):
- x, y = points[pointIndex]
- newNode.points[1 + pointIndex].Assign(Point(x, y))
- if smooth:
- newNode.alignment = flSMOOTH
- self._nakedParent.Insert(newNode, self._startNodeIndex + index)
- self._buildSegments()
- return self.segments[index]
-
- def removeSegment(self, index):
- """remove a segment from the contour"""
- segment = self.segments[index]
- # we have a qcurve. umph.
- if segment.type == QCURVE:
- indexList = [segment._nodeIndex] + segment._looseOffCurve
- indexList.sort()
- indexList.reverse()
- parent = self._nakedParent
- for nodeIndex in indexList:
- parent.DeleteNode(nodeIndex)
- # we have a more sane structure to follow
- else:
- # store some info for later
- next = self._nextSegment(index)
- nextOffA = None
- nextOffB = None
- nextType = next.type
- if nextType != LINE and nextType != MOVE:
- pA = next.offCurve[0]
- nextOffA = (pA.x, pA.y)
- pB = next.offCurve[-1]
- nextOffB = (pB.x, pB.y)
- nodeIndex = segment._nodeIndex
- self._nakedParent.DeleteNode(nodeIndex)
- self._buildSegments()
- # now we must override FL guessing about offCurves
- next = self._nextSegment(index - 1)
- nextType = next.type
- if nextType != LINE and nextType != MOVE:
- pA = next.offCurve[0]
- pB = next.offCurve[-1]
- pA.x, pA.y = nextOffA
- pB.x, pB.y = nextOffB
-
- def reverseContour(self):
- """reverse contour direction"""
- self._nakedParent.ReverseContour(self._index)
- self._buildSegments()
-
- def setStartSegment(self, segmentIndex):
- """set the first node on the contour"""
- self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex)
- self.getParent()._invalidateContours()
- self.getParent()._buildContours()
-
- def copy(self, aParent=None):
- """Copy this object -- result is an ObjectsRF flavored object.
- There is no way to make this work using FontLab objects.
- Copy is mainly used for glyphmath.
- """
- raise RoboFabError, "copy() for objectsFL.RContour is not implemented."
-
-
-
-class RSegment(BaseSegment):
-
- _title = "FLSegment"
-
- def __init__(self, flNodeIndex):
- BaseSegment.__init__(self)
- self._nodeIndex = flNodeIndex
- self._looseOffCurve = [] #a list of indexes to loose off curve nodes
-
- def _get__node(self):
- glyph = self.getParent()._nakedParent
- return glyph.nodes[self._nodeIndex]
-
- _node = property(_get__node, doc="")
-
- def _get_qOffCurve(self):
- nodes = self.getParent()._nakedParent.nodes
- off = []
- for x in self._looseOffCurve:
- off.append(nodes[x])
- return off
-
- _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment")
-
- def _get_index(self):
- contour = self.getParent()
- return self._nodeIndex - contour._startNodeIndex
-
- index = property(_get_index, doc="")
-
- def _isQCurve(self):
- # loose off curves only appear in q curves
- if len(self._looseOffCurve) != 0:
- return True
- return False
-
- def _get_type(self):
- if self._isQCurve():
- return QCURVE
- return _flToRFSegmentType(self._node.type)
-
- def _set_type(self, segmentType):
- if self._isQCurve():
- raise RoboFabError, 'qcurve point types cannot be changed'
- oldNode = self._node
- oldType = oldNode.type
- oldPointType = _flToRFSegmentType(oldType)
- if oldPointType == MOVE:
- raise RoboFabError, '%s point types cannot be changed'%oldPointType
- if segmentType == MOVE or segmentType == OFFCURVE:
- raise RoboFabError, '%s point types cannot be assigned'%oldPointType
- if oldPointType == segmentType:
- return
- oldNode.type = _rfToFLSegmentType(segmentType)
-
- type = property(_get_type, _set_type, doc="")
-
- def _get_smooth(self):
- alignment = self._node.alignment
- if alignment == flSMOOTH or alignment == flFIXED:
- return True
- return False
-
- def _set_smooth(self, value):
- if value:
- self._node.alignment = flSMOOTH
- else:
- self._node.alignment = flSHARP
-
- smooth = property(_get_smooth, _set_smooth, doc="")
-
- def _get_points(self):
- points = []
- node = self._node
- # gather the off curves
- #
- # are we dealing with a qCurve? ugh.
- # gather the loose off curves
- if self._isQCurve():
- off = self._qOffCurve
- x = 0
- for n in off:
- p = RPoint(0)
- p.setParent(self)
- p._qOffIndex = x
- points.append(p)
- x = x + 1
- # otherwise get the points associated with the node
- else:
- index = 1
- for point in node.points[1:]:
- p = RPoint(index)
- p.setParent(self)
- points.append(p)
- index = index + 1
- # the last point should always be the on curve
- p = RPoint(0)
- p.setParent(self)
- points.append(p)
- return points
-
- points = property(_get_points, doc="")
-
- def _get_selected(self):
- return self._node.selected
-
- def _set_selected(self, value):
- self._node.selected = value
-
- selected = property(_get_selected, _set_selected, doc="")
-
- def move(self, (x, y)):
- x, y = roundPt((x, y))
- self._node.Shift(Point(x, y))
- if self._isQCurve():
- qOff = self._qOffCurve
- for node in qOff:
- node.Shift(Point(x, y))
-
- def copy(self, aParent=None):
- """Copy this object -- result is an ObjectsRF flavored object.
- There is no way to make this work using FontLab objects.
- Copy is mainly used for glyphmath.
- """
- raise RoboFabError, "copy() for objectsFL.RSegment is not implemented."
-
-
-
-class RPoint(BasePoint):
-
- _title = "FLPoint"
-
- def __init__(self, pointIndex):
- #BasePoint.__init__(self)
- self._pointIndex = pointIndex
- self._qOffIndex = None
-
- def _get__parentGlyph(self):
- return self._parentContour.getParent()
-
- _parentGlyph = property(_get__parentGlyph, doc="")
-
- def _get__parentContour(self):
- return self._parentSegment.getParent()
-
- _parentContour = property(_get__parentContour, doc="")
-
- def _get__parentSegment(self):
- return self.getParent()
-
- _parentSegment = property(_get__parentSegment, doc="")
-
- def _get__node(self):
- if self._qOffIndex is not None:
- return self.getParent()._qOffCurve[self._qOffIndex]
- return self.getParent()._node
-
- _node = property(_get__node, doc="")
-
- def _get__point(self):
- return self._node.points[self._pointIndex]
-
- _point = property(_get__point, doc="")
-
- def _get_x(self):
- return self._point.x
-
- def _set_x(self, value):
- value = int(round(value))
- self._point.x = value
-
- x = property(_get_x, _set_x, doc="")
-
- def _get_y(self):
- return self._point.y
-
- def _set_y(self, value):
- value = int(round(value))
- self._point.y = value
-
- y = property(_get_y, _set_y, doc="")
-
- def _get_type(self):
- if self._pointIndex == 0:
- # FL store quad contour data as a list of off curves and lines
- # (see note in RContour._buildSegments). So, we need to do
- # a bit of trickery to return a decent point type.
- # if the straight FL node type is off curve, it is a loose
- # quad off curve. return that.
- tp = _flToRFSegmentType(self._node.type)
- if tp == OFFCURVE:
- return OFFCURVE
- # otherwise we are dealing with an on curve. in this case,
- # we attempt to get the parent segment type and return it.
- segment = self.getParent()
- if segment is not None:
- return segment.type
- # we must not have a segment, fall back to straight conversion
- return tp
- return OFFCURVE
-
- type = property(_get_type, doc="")
-
- def _set_selected(self, value):
- if self._pointIndex == 0:
- self._node.selected = value
-
- def _get_selected(self):
- if self._pointIndex == 0:
- return self._node.selected
- return False
-
- selected = property(_get_selected, _set_selected, doc="")
-
- def move(self, (x, y)):
- x, y = roundPt((x, y))
- self._point.Shift(Point(x, y))
-
- def scale(self, (x, y), center=(0, 0)):
- centerX, centerY = roundPt(center)
- point = self._point
- point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY))
-
- def copy(self, aParent=None):
- """Copy this object -- result is an ObjectsRF flavored object.
- There is no way to make this work using FontLab objects.
- Copy is mainly used for glyphmath.
- """
- raise RoboFabError, "copy() for objectsFL.RPoint is not implemented."
-
-
-class RBPoint(BaseBPoint):
-
- _title = "FLBPoint"
-
- def __init__(self, segmentIndex):
- #BaseBPoint.__init__(self)
- self._segmentIndex = segmentIndex
-
- def _get__parentSegment(self):
- return self.getParent().segments[self._segmentIndex]
-
- _parentSegment = property(_get__parentSegment, doc="")
-
- def _get_index(self):
- return self._segmentIndex
-
- index = property(_get_index, doc="")
-
- def _get_selected(self):
- return self._parentSegment.selected
-
- def _set_selected(self, value):
- self._parentSegment.selected = value
-
- selected = property(_get_selected, _set_selected, doc="")
-
- def copy(self, aParent=None):
- """Copy this object -- result is an ObjectsRF flavored object.
- There is no way to make this work using FontLab objects.
- Copy is mainly used for glyphmath.
- """
- raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented."
-
-
-class RComponent(BaseComponent):
-
- """RoboFab wrapper for FL Component object"""
-
- _title = "FLComponent"
-
- def __init__(self, flComponent, index):
- BaseComponent.__init__(self)
- self._object = flComponent
- self._index=index
-
- def _get_index(self):
- return self._index
-
- index = property(_get_index, doc="index of component")
-
- def _get_baseGlyph(self):
- return self._object.parent.parent[self._object.index].name
-
- baseGlyph = property(_get_baseGlyph, doc="")
-
- def _get_offset(self):
- return (int(self._object.delta.x), int(self._object.delta.y))
-
- def _set_offset(self, value):
- value = roundPt((value[0], value[1]))
- self._object.delta=Point(value[0], value[1])
-
- offset = property(_get_offset, _set_offset, doc="the offset of the component")
-
- def _get_scale(self):
- return (self._object.scale.x, self._object.scale.y)
-
- def _set_scale(self, (x, y)):
- self._object.scale=Point(x, y)
-
- scale = property(_get_scale, _set_scale, doc="the scale of the component")
-
- def move(self, (x, y)):
- """Move the component"""
- x, y = roundPt((x, y))
- self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y)
-
- def decompose(self):
- """Decompose the component"""
- self._object.Paste()
-
- def copy(self, aParent=None):
- """Copy this object -- result is an ObjectsRF flavored object.
- There is no way to make this work using FontLab objects.
- Copy is mainly used for glyphmath.
- """
- raise RoboFabError, "copy() for objectsFL.RComponent is not implemented."
-
-
-
-class RAnchor(BaseAnchor):
- """RoboFab wrapper for FL Anchor object"""
-
- _title = "FLAnchor"
-
- def __init__(self, flAnchor, index):
- BaseAnchor.__init__(self)
- self._object = flAnchor
- self._index = index
-
- def _get_y(self):
- return self._object.y
-
- def _set_y(self, value):
- self._object.y = int(round(value))
-
- y = property(_get_y, _set_y, doc="y")
-
- def _get_x(self):
- return self._object.x
-
- def _set_x(self, value):
- self._object.x = int(round(value))
-
- x = property(_get_x, _set_x, doc="x")
-
- def _get_name(self):
- return self._object.name
-
- def _set_name(self, value):
- self._object.name = value
-
- name = property(_get_name, _set_name, doc="name")
-
- def _get_mark(self):
- return self._object.mark
-
- def _set_mark(self, value):
- self._object.mark = value
-
- mark = property(_get_mark, _set_mark, doc="mark")
-
- def _get_index(self):
- return self._index
-
- index = property(_get_index, doc="index of the anchor")
-
- def _get_position(self):
- return (self._object.x, self._object.y)
-
- def _set_position(self, value):
- value = roundPt((value[0], value[1]))
- self._object.x=value[0]
- self._object.y=value[1]
-
- position = property(_get_position, _set_position, doc="position of the anchor")
-
-
-
-class RGuide(BaseGuide):
-
- """RoboFab wrapper for FL Guide object"""
-
- _title = "FLGuide"
-
- def __init__(self, flGuide, index):
- BaseGuide.__init__(self)
- self._object = flGuide
- self._index = index
-
- def __repr__(self):
- # this is a doozy!
- parent = "unknown_parent"
- parentObject = self.getParent()
- if parentObject is not None:
- # do we have a font?
- try:
- parent = parentObject.info.postscriptFullName
- except AttributeError:
- # or do we have a glyph?
- try:
- parent = parentObject.name
- # we must be an orphan
- except AttributeError: pass
- return "<Robofab guide wrapper for %s>"%parent
-
- def _get_position(self):
- return self._object.position
-
- def _set_position(self, value):
- self._object.position = value
-
- position = property(_get_position, _set_position, doc="position")
-
- def _get_angle(self):
- return self._object.angle
-
- def _set_angle(self, value):
- self._object.angle = value
-
- angle = property(_get_angle, _set_angle, doc="angle")
-
- def _get_index(self):
- return self._index
-
- index = property(_get_index, doc="index of the guide")
-
-
-class RGroups(BaseGroups):
-
- """RoboFab wrapper for FL group data"""
-
- _title = "FLGroups"
-
- def __init__(self, aDict):
- self.update(aDict)
-
- def __setitem__(self, key, value):
- # override baseclass so that data is stored in FL classes
- if not isinstance(key, str):
- raise RoboFabError, 'key must be a string'
- if not isinstance(value, list):
- raise RoboFabError, 'group must be a list'
- super(RGroups, self).__setitem__(key, value)
- self._setFLGroups()
-
- def __delitem__(self, key):
- # override baseclass so that data is stored in FL classes
- super(RGroups, self).__delitem__(key)
- self._setFLGroups()
-
- def _setFLGroups(self):
- # set the group data into the font.
- if self.getParent() is not None:
- groups = []
- for i in self.keys():
- value = ' '.join(self[i])
- groups.append(': '.join([i, value]))
- groups.sort()
- self.getParent().naked().classes = groups
-
- def update(self, aDict):
- # override baseclass so that data is stored in FL classes
- super(RGroups, self).update(aDict)
- self._setFLGroups()
-
- def clear(self):
- # override baseclass so that data is stored in FL classes
- super(RGroups, self).clear()
- self._setFLGroups()
-
- def pop(self, key):
- # override baseclass so that data is stored in FL classes
- i = super(RGroups, self).pop(key)
- self._setFLGroups()
- return i
-
- def popitem(self):
- # override baseclass so that data is stored in FL classes
- i = super(RGroups, self).popitem()
- self._setFLGroups()
- return i
-
- def setdefault(self, key, value=None):
- # override baseclass so that data is stored in FL classes
- i = super(RGroups, self).setdefault(key, value)
- self._setFLGroups()
- return i
-
-
-class RKerning(BaseKerning):
-
- """RoboFab wrapper for FL Kerning data"""
-
- _title = "FLKerning"
-
- def __setitem__(self, pair, value):
- if not isinstance(pair, tuple):
- raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
- else:
- if len(pair) != 2:
- raise RoboFabError, 'kerning pair must be a tuple: (left, right)'
- else:
- if value == 0:
- if self._kerning.get(pair) is not None:
- #see note about setting kerning values to 0 below
- self._setFLKerning(pair, 0)
- del self._kerning[pair]
- else:
- #self._kerning[pair] = value
- self._setFLKerning(pair, value)
-
- def _setFLKerning(self, pair, value):
- # write a pair back into the font
- #
- # this is fairly speedy, but setting a pair to 0 is roughly
- # 2-3 times slower than setting a real value. this is because
- # of all the hoops that must be jumped through to keep FL
- # from storing kerning pairs with a value of 0.
- parentFont = self.getParent().naked()
- left = parentFont[pair[0]]
- right = parentFont.FindGlyph(pair[1])
- # the left glyph doesn not exist
- if left is None:
- return
- # the right glyph doesn not exist
- if right == -1:
- return
- self._kerning[pair] = value
- leftName = pair[0]
- value = int(round(value))
- # pairs set to 0 need to be handled carefully. FL will allow
- # for pairs to have a value of 0 (!?), so we must catch them
- # when they pop up and make sure that the pair is actually
- # removed from the font.
- if value == 0:
- foundPair = False
- # if the value is 0, we don't need to construct a pair
- # we just need to make sure that the pair is not in the list
- pairs = []
- # so, go through all the pairs and add them to a new list
- for flPair in left.kerning:
- # we have found the pair. flag it.
- if flPair.key == right:
- foundPair = True
- # not the pair. add it to the list.
- else:
- pairs.append((flPair.key, flPair.value))
- # if we found it, write it back to the glyph.
- if foundPair:
- left.kerning = []
- for p in pairs:
- new = KerningPair(p[0], p[1])
- left.kerning.append(new)
- else:
- # non-zero pairs are a bit easier to handle
- # we just need to look to see if the pair exists
- # if so, change the value and stop the loop.
- # if not, add a new pair to the glyph
- self._kerning[pair] = value
- foundPair = False
- for flPair in left.kerning:
- if flPair.key == right:
- flPair.value = value
- foundPair = True
- break
- if not foundPair:
- p = KerningPair(right, value)
- left.kerning.append(p)
-
- def update(self, kerningDict):
- """replace kerning data with the data in the given kerningDict"""
- # override base class here for speed
- parentFont = self.getParent().naked()
- # add existing data to the new kerning dict is not being replaced
- for pair in self.keys():
- if not kerningDict.has_key(pair):
- kerningDict[pair] = self._kerning[pair]
- # now clear the existing kerning to make sure that
- # all the kerning in residing in the glyphs is gone
- self.clear()
- self._kerning = kerningDict
- kDict = {}
- # nest the pairs into a dict keyed by the left glyph
- # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...}
- for left, right in kerningDict.keys():
- value = kerningDict[left, right]
- if not left in kDict:
- kDict[left] = {}
- kDict[left][right] = value
- for left in kDict.keys():
- leftGlyph = parentFont[left]
- if leftGlyph is not None:
- for right in kDict[left].keys():
- value = kDict[left][right]
- if value != 0:
- rightIndex = parentFont.FindGlyph(right)
- if rightIndex != -1:
- p = KerningPair(rightIndex, value)
- leftGlyph.kerning.append(p)
-
- def clear(self):
- """clear all kerning"""
- # override base class here for speed
- self._kerning = {}
- for glyph in self.getParent().naked().glyphs:
- glyph.kerning = []
-
- def __add__(self, other):
- """Math operations on FL Kerning objects return RF Kerning objects
- as they need to be orphaned objects and FL can't deal with that."""
- from sets import Set
- from robofab.objects.objectsRF import RKerning as _RKerning
- new = _RKerning()
- k = Set(self.keys()) | Set(other.keys())
- for key in k:
- new[key] = self.get(key, 0) + other.get(key, 0)
- return new
-
- def __sub__(self, other):
- """Math operations on FL Kerning objects return RF Kerning objects
- as they need to be orphaned objects and FL can't deal with that."""
- from sets import Set
- from robofab.objects.objectsRF import RKerning as _RKerning
- new = _RKerning()
- k = Set(self.keys()) | Set(other.keys())
- for key in k:
- new[key] = self.get(key, 0) - other.get(key, 0)
- return new
-
- def __mul__(self, factor):
- """Math operations on FL Kerning objects return RF Kerning objects
- as they need to be orphaned objects and FL can't deal with that."""
- from robofab.objects.objectsRF import RKerning as _RKerning
- new = _RKerning()
- for name, value in self.items():
- new[name] = value * factor
- return new
-
- __rmul__ = __mul__
-
- def __div__(self, factor):
- """Math operations on FL Kerning objects return RF Kerning objects
- as they need to be orphaned objects and FL can't deal with that."""
- if factor == 0:
- raise ZeroDivisionError
- return self.__mul__(1.0/factor)
-
-
-class RLib(BaseLib):
-
- """RoboFab wrapper for FL lib"""
-
- # XXX: As of FL 4.6 the customdata field in glyph objects is busted.
- # storing anything there causes the glyph to become uneditable.
- # however, the customdata field in font objects is stable.
-
- def __init__(self, aDict):
- self.update(aDict)
-
- def __setitem__(self, key, value):
- # override baseclass so that data is stored in customdata field
- super(RLib, self).__setitem__(key, value)
- self._stashLib()
-
- def __delitem__(self, key):
- # override baseclass so that data is stored in customdata field
- super(RLib, self).__delitem__(key)
- self._stashLib()
-
- def _stashLib(self):
- # write the plist into the customdata field of the FL object
- if self.getParent() is None:
- return
- if not self:
- data = None
- elif len(self) == 1 and "org.robofab.fontlab.customdata" in self:
- data = self["org.robofab.fontlab.customdata"].data
- else:
- f = StringIO()
- writePlist(self, f)
- data = f.getvalue()
- f.close()
- parent = self.getParent()
- parent.naked().customdata = data
-
- def update(self, aDict):
- # override baseclass so that data is stored in customdata field
- super(RLib, self).update(aDict)
- self._stashLib()
-
- def clear(self):
- # override baseclass so that data is stored in customdata field
- super(RLib, self).clear()
- self._stashLib()
-
- def pop(self, key):
- # override baseclass so that data is stored in customdata field
- i = super(RLib, self).pop(key)
- self._stashLib()
- return i
-
- def popitem(self):
- # override baseclass so that data is stored in customdata field
- i = super(RLib, self).popitem()
- self._stashLib()
- return i
-
- def setdefault(self, key, value=None):
- # override baseclass so that data is stored in customdata field
- i = super(RLib, self).setdefault(key, value)
- self._stashLib()
- return i
-
-
-def _infoMapDict(**kwargs):
- default = dict(
- nakedAttribute=None,
- type=None,
- requiresSetNum=False,
- masterSpecific=False,
- libLocation=None,
- specialGetSet=False
- )
- default.update(kwargs)
- return default
-
-def _flipDict(d):
- f = {}
- for k, v in d.items():
- f[v] = k
- return f
-
-_styleMapStyleName_fromFL = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
-}
-_styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL)
-
-_postscriptWindowsCharacterSet_fromFL = {
- 0 : 1,
- 1 : 2,
- 2 : 3,
- 77 : 4,
- 128 : 5,
- 129 : 6,
- 130 : 7,
- 134 : 8,
- 136 : 9,
- 161 : 10,
- 162 : 11,
- 163 : 12,
- 177 : 13,
- 178 : 14,
- 186 : 15,
- 200 : 16,
- 204 : 17,
- 222 : 18,
- 238 : 19,
- 255 : 20,
-}
-_postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL)
-
-_openTypeOS2Type_toFL = {
- 1 : 0x0002,
- 2 : 0x0004,
- 3 : 0x0008,
- 8 : 0x0100,
- 9 : 0x0200,
-}
-_openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL)
-
-_openTypeOS2WidthClass_fromFL = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9,
-}
-_openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL)
-
-_postscriptHintAttributes = set((
- "postscriptBlueValues",
- "postscriptOtherBlues",
- "postscriptFamilyBlues",
- "postscriptFamilyOtherBlues",
- "postscriptStemSnapH",
- "postscriptStemSnapV",
-))
-
-
-class RInfo(BaseInfo):
-
- """RoboFab wrapper for FL Font Info"""
-
- _title = "FLInfo"
-
- _ufoToFLAttrMapping = {
- "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"),
- "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"),
- "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"),
- "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True),
- "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"),
- "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"),
- "year" : _infoMapDict(valueType=int, nakedAttribute="year"),
- "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"),
- "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"),
- "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"),
- "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True),
- "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True),
- "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True),
- "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True),
- "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"),
- "note" : _infoMapDict(valueType=str, nakedAttribute="note"),
- "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values
- "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"),
- "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface.
- "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"),
- "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"),
- "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"),
- "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"),
- "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"),
- "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"),
- "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"),
- "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"),
- "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"),
- "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"),
- "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"),
- "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"),
- "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"),
- "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"),
- "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"),
- "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None),
- "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None),
- "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None),
- "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"),
- "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True),
- "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0
- "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"),
- "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True),
- "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True),
- "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"),
- "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"),
- "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"),
- "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"),
- "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"),
- "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"),
- "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True),
- "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True),
- "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"),
- "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"),
- "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"),
- "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"),
- "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"),
- "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"),
- "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"),
- "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"),
- "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"),
- "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"),
- "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None),
- "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None),
- "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"),
- "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"),
- "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"),
- "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"),
- "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"),
- "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"),
- "postscriptIsFixedPitch" : _infoMapDict(valueType="boolint", nakedAttribute="is_fixed_pitch"),
- "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True),
- "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True),
- "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True),
- "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True),
- "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True),
- "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True),
- "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True),
- "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True),
- "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True),
- "postscriptForceBold" : _infoMapDict(valueType="boolint", nakedAttribute="force_bold", masterSpecific=True),
- "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True),
- "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None),
- "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"),
- "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"),
- "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True),
- "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"),
- "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"),
- }
- _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh.
-
- def __init__(self, font):
- super(RInfo, self).__init__()
- self._object = font
-
- def _environmentSetAttr(self, attr, value):
- # special fontlab workarounds
- if attr == "width":
- warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning)
- attr = "openTypeOS2WidthClass"
- if attr == "openTypeOS2WidthClass":
- if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL:
- print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value
- self._object.width = value
- else:
- self._object.width = _openTypeOS2WidthClass_toFL[value]
- return
- # get the attribute data
- data = self._ufoToFLAttrMapping[attr]
- flAttr = data["nakedAttribute"]
- valueType = data["valueType"]
- masterSpecific = data["masterSpecific"]
- requiresSetNum = data["requiresSetNum"]
- specialGetSet = data["specialGetSet"]
- # warn about setting attributes not supported by FL
- if flAttr is None:
- print "The attribute %s is not supported by FontLab. This data will not be set." % attr
- return
- # make sure that the value is the proper type for FL
- if valueType == "intList":
- value = [int(i) for i in value]
- elif valueType == "boolint":
- value = int(bool(value))
- elif valueType == str:
- if value is None:
- value = ""
- value = value.encode(LOCAL_ENCODING)
- elif valueType == int and not isinstance(value, int):
- value = int(round(value))
- elif not isinstance(value, valueType):
- value = valueType(value)
- # handle postscript hint bug in FL
- if attr in _postscriptHintAttributes:
- value = self._handlePSHintBug(attr, value)
- # handle special cases
- if specialGetSet:
- attr = "_set_%s" % attr
- method = getattr(self, attr)
- return method(value)
- # set the value
- obj = self._object
- if len(flAttr.split(".")) > 1:
- flAttrList = flAttr.split(".")
- for i in flAttrList[:-1]:
- obj = getattr(obj, i)
- flAttr = flAttrList[-1]
- ## set the foo_num attribute if necessary
- if requiresSetNum:
- numAttr = flAttr + "_num"
- setattr(obj, numAttr, len(value))
- ## set master 0 if the data is master specific
- if masterSpecific:
- subObj = getattr(obj, flAttr)
- if valueType == "intList":
- for index, v in enumerate(value):
- subObj[0][index] = v
- else:
- subObj[0] = value
- ## otherwise use a regular set
- else:
- setattr(obj, flAttr, value)
-
- def _environmentGetAttr(self, attr):
- # special fontlab workarounds
- if attr == "width":
- warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning)
- attr = "openTypeOS2WidthClass"
- if attr == "openTypeOS2WidthClass":
- value = self._object.width
- if value not in _openTypeOS2WidthClass_fromFL:
- print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value
- return
- else:
- return _openTypeOS2WidthClass_fromFL[value]
- # get the attribute data
- data = self._ufoToFLAttrMapping[attr]
- flAttr = data["nakedAttribute"]
- valueType = data["valueType"]
- masterSpecific = data["masterSpecific"]
- specialGetSet = data["specialGetSet"]
- # warn about setting attributes not supported by FL
- if flAttr is None:
- if not _IN_UFO_EXPORT:
- print "The attribute %s is not supported by FontLab." % attr
- return
- # handle special cases
- if specialGetSet:
- attr = "_get_%s" % attr
- method = getattr(self, attr)
- return method()
- # get the value
- if len(flAttr.split(".")) > 1:
- flAttrList = flAttr.split(".")
- obj = self._object
- for i in flAttrList:
- obj = getattr(obj, i)
- value = obj
- else:
- value = getattr(self._object, flAttr)
- # grab the first master value if necessary
- if masterSpecific:
- value = value[0]
- # convert if necessary
- if valueType == "intList":
- value = [int(i) for i in value]
- elif valueType == "boolint":
- value = bool(value)
- elif valueType == str:
- if value is None:
- pass
- else:
- value = unicode(value, LOCAL_ENCODING)
- elif not isinstance(value, valueType):
- value = valueType(value)
- return value
-
- # ------------------------------
- # individual attribute overrides
- # ------------------------------
-
- # styleMapStyleName
-
- def _get_styleMapStyleName(self):
- return _styleMapStyleName_fromFL[self._object.font_style]
-
- def _set_styleMapStyleName(self, value):
- value = _styleMapStyleName_toFL[value]
- self._object.font_style = value
-
-# # openTypeHeadCreated
-#
-# # fontlab epoch: 1969-12-31 19:00:00
-#
-# def _get_openTypeHeadCreated(self):
-# value = self._object.ttinfo.head_creation
-# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0)
-# delta = datetime.timedelta(seconds=value[0])
-# t = epoch - delta
-# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2))
-# return string
-#
-# def _set_openTypeHeadCreated(self, value):
-# date, time = value.split(" ")
-# year, month, day = [int(i) for i in date.split("-")]
-# hour, minute, second = [int(i) for i in time.split(":")]
-# value = datetime.datetime(year, month, day, hour, minute, second)
-# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0)
-# delta = epoch - value
-# seconds = delta.seconds
-# self._object.ttinfo.head_creation[0] = seconds
-
- # openTypeOS2WeightClass
-
- def _get_openTypeOS2WeightClass(self):
- value = self._object.weight_code
- if value == -1:
- value = None
- return value
-
- def _set_openTypeOS2WeightClass(self, value):
- self._object.weight_code = value
-
- # openTypeOS2WinDescent
-
- def _get_openTypeOS2WinDescent(self):
- return self._object.ttinfo.os2_us_win_descent
-
- def _set_openTypeOS2WinDescent(self, value):
- if value < 0:
- warn("FontLab can only handle positive values for openTypeOS2WinDescent.")
- value = abs(value)
- self._object.ttinfo.os2_us_win_descent = value
-
- # openTypeOS2Type
-
- def _get_openTypeOS2Type(self):
- value = self._object.ttinfo.os2_fs_type
- intList = []
- for bit, bitNumber in _openTypeOS2Type_fromFL.items():
- if value & bit:
- intList.append(bitNumber)
- return intList
-
- def _set_openTypeOS2Type(self, values):
- value = 0
- for bitNumber in values:
- bit = _openTypeOS2Type_toFL[bitNumber]
- value = value | bit
- self._object.ttinfo.os2_fs_type = value
-
- # openTypeOS2Panose
-
- def _get_openTypeOS2Panose(self):
- return [i for i in self._object.panose]
-
- def _set_openTypeOS2Panose(self, values):
- for index, value in enumerate(values):
- self._object.panose[index] = value
-
- # openTypeOS2FamilyClass
-
- def _get_openTypeOS2FamilyClass(self):
- value = self._object.ttinfo.os2_s_family_class
- for classID in range(15):
- classValue = classID * 256
- if classValue > value:
- classID -= 1
- classValue = classID * 256
- break
- subclassID = value - classValue
- return [classID, subclassID]
-
- def _set_openTypeOS2FamilyClass(self, values):
- classID, subclassID = values
- classID = classID * 256
- value = classID + subclassID
- self._object.ttinfo.os2_s_family_class = value
-
- # postscriptWindowsCharacterSet
-
- def _get_postscriptWindowsCharacterSet(self):
- value = self._object.ms_charset
- value = _postscriptWindowsCharacterSet_fromFL[value]
- return value
-
- def _set_postscriptWindowsCharacterSet(self, value):
- value = _postscriptWindowsCharacterSet_toFL[value]
- self._object.ms_charset = value
-
- # -----------------
- # FL bug workaround
- # -----------------
-
- def _handlePSHintBug(self, attribute, values):
- """Function to handle problems with FontLab not allowing the max number of
- alignment zones to be set to the max number.
- Input: the name of the zones and the values to be set
- Output: a warning when there are too many values to be set
- and the max values which FontLab will allow.
- """
- originalValues = values
- truncatedLength = None
- if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"):
- if len(values) > 10:
- values = values[:10]
- truncatedLength = 10
- elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"):
- if len(values) > 12:
- values = values[:12]
- truncatedLength = 12
- elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"):
- if len(values) > 8:
- values = values[:8]
- truncatedLength = 8
- if truncatedLength is not None:
- print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:]))
- return values
-
-
-class RFeatures(BaseFeatures):
-
- _title = "FLFeatures"
-
- def __init__(self, font):
- super(RFeatures, self).__init__()
- self._object = font
-
- def _get_text(self):
- naked = self._object
- features = []
- if naked.ot_classes:
- features.append(_normalizeLineEndings(naked.ot_classes))
- for feature in naked.features:
- features.append(_normalizeLineEndings(feature.value))
- return "".join(features)
-
- def _set_text(self, value):
- classes, features = splitFeaturesForFontLab(value)
- naked = self._object
- naked.ot_classes = classes
- naked.features.clean()
- for featureName, featureText in features:
- f = Feature(featureName, featureText)
- naked.features.append(f)
-
- text = property(_get_text, _set_text, doc="raw feature text.")
-
diff --git a/misc/pylib/robofab/objects/objectsRF.pyx b/misc/pylib/robofab/objects/objectsRF.pyx
deleted file mode 100755
index 50e1f13a7..000000000
--- a/misc/pylib/robofab/objects/objectsRF.pyx
+++ /dev/null
@@ -1,1233 +0,0 @@
-"""UFO for GlifLib"""
-
-from robofab import RoboFabError, RoboFabWarning
-from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\
- BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \
- relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\
- _interpolate, _interpolatePt, roundPt, addPt,\
- MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
- BasePostScriptFontHintValues, postScriptHintDataLibKey, BasePostScriptGlyphHintValues
-
-import os
-
-
-__all__ = [ "CurrentFont",
- "CurrentGlyph", 'OpenFont',
- 'RFont', 'RGlyph', 'RContour',
- 'RPoint', 'RBPoint', 'RAnchor',
- 'RComponent'
- ]
-
-
-
-def CurrentFont():
- return None
-
-def CurrentGlyph():
- return None
-
-def OpenFont(path=None, note=None):
- """Open a font from a path. If path is not given, present the user with a dialog."""
- if not note:
- note = 'select a .ufo directory'
- if not path:
- from robofab.interface.all.dialogs import GetFolder
- path = GetFolder(note)
- if path:
- try:
- return RFont(path)
- except OSError:
- from robofab.interface.all.dialogs import Message
- Message("%s is not a valid .UFO font. But considering it's all XML, why don't you have a look inside with a simple text editor."%(path))
- else:
- return None
-
-def NewFont(familyName=None, styleName=None):
- """Make a new font"""
- new = RFont()
- if familyName is not None:
- new.info.familyName = familyName
- if styleName is not None:
- new.info.styleName = styleName
- return new
-
-def AllFonts():
- """AllFonts can't work in plain python usage. It's really up to some sort of application
- to keep track of which fonts are open."""
- raise NotImplementedError
-
-
-class PostScriptFontHintValues(BasePostScriptFontHintValues):
- """ Font level PostScript hints object for objectsRF usage.
- If there are values in the lib, use those.
- If there are no values in the lib, use defaults.
-
- The psHints attribute for objectsRF.RFont is basically just the
- data read from the Lib. When the object saves to UFO, the
- hints are written back to the lib, which is then saved.
-
- """
-
- def __init__(self, aFont=None, data=None):
- self.setParent(aFont)
- BasePostScriptFontHintValues.__init__(self)
- if aFont is not None:
- # in version 1, this data was stored in the lib
- # if it is still there, guess that it is correct
- # move it to font info and remove it from the lib.
- libData = aFont.lib.get(postScriptHintDataLibKey)
- if libData is not None:
- self.fromDict(libData)
- del libData[postScriptHintDataLibKey]
- if data is not None:
- self.fromDict(data)
-
-def getPostScriptHintDataFromLib(aFont, fontLib):
- hintData = fontLib.get(postScriptHintDataLibKey)
- psh = PostScriptFontHintValues(aFont)
- psh.fromDict(hintData)
- return psh
-
-class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues):
- """ Glyph level PostScript hints object for objectsRF usage.
- If there are values in the lib, use those.
- If there are no values in the lib, be empty.
-
- """
- def __init__(self, aGlyph=None, data=None):
- # read the data from the glyph.lib, it won't be anywhere else
- BasePostScriptGlyphHintValues.__init__(self)
- if aGlyph is not None:
- self.setParent(aGlyph)
- self._loadFromLib(aGlyph.lib)
- if data is not None:
- self.fromDict(data)
-
-
-class RFont(BaseFont):
- """UFO font object which reads and writes glif, and keeps the data in memory in between.
- Bahviour:
- - comparable to Font
- - comparable to GlyphSet so that it can be passed to Glif widgets
- """
-
- _title = "RoboFabFont"
-
- def __init__(self, path=None):
- BaseFont.__init__(self)
- if path is not None:
- self._path = os.path.normpath(os.path.abspath(path))
- else:
- self._path = None
- self._object = {}
-
- self._glyphSet = None
- self._scheduledForDeletion = [] # this is a place for storing glyphs that need to be removed when the font is saved
-
- self.kerning = RKerning()
- self.kerning.setParent(self)
- self.info = RInfo()
- self.info.setParent(self)
- self.features = RFeatures()
- self.features.setParent(self)
- self.groups = RGroups()
- self.groups.setParent(self)
- self.lib = RLib()
- self.lib.setParent(self)
- if path:
- self._loadData(path)
- else:
- self.psHints = PostScriptFontHintValues(self)
- self.psHints.setParent(self)
-
- def __setitem__(self, glyphName, glyph):
- """Set a glyph at key."""
- self._object[glyphName] = glyph
-
- def __cmp__(self, other):
- """Compare this font with another, compare if they refer to the same file."""
- if not hasattr(other, '_path'):
- return -1
- if self._object._path == other._object._path and self._object._path is not None:
- return 0
- else:
- return -1
-
- def __len__(self):
- if self._glyphSet is None:
- return 0
- return len(self._glyphSet)
-
- def _loadData(self, path):
- from robofab.ufoLib import UFOReader
- reader = UFOReader(path)
- fontLib = reader.readLib()
- # info
- reader.readInfo(self.info)
- # kerning
- self.kerning.update(reader.readKerning())
- self.kerning.setChanged(False)
- # groups
- self.groups.update(reader.readGroups())
- # features
- if reader.formatVersion == 1:
- # migrate features from the lib
- features = []
- classes = fontLib.get("org.robofab.opentype.classes")
- if classes is not None:
- del fontLib["org.robofab.opentype.classes"]
- features.append(classes)
- splitFeatures = fontLib.get("org.robofab.opentype.features")
- if splitFeatures is not None:
- order = fontLib.get("org.robofab.opentype.featureorder")
- if order is None:
- order = splitFeatures.keys()
- order.sort()
- else:
- del fontLib["org.robofab.opentype.featureorder"]
- del fontLib["org.robofab.opentype.features"]
- for tag in order:
- oneFeature = splitFeatures.get(tag)
- if oneFeature is not None:
- features.append(oneFeature)
- features = "\n".join(features)
- else:
- features = reader.readFeatures()
- self.features.text = features
- # hint data
- self.psHints = PostScriptFontHintValues(self)
- if postScriptHintDataLibKey in fontLib:
- del fontLib[postScriptHintDataLibKey]
- # lib
- self.lib.update(fontLib)
- # glyphs
- self._glyphSet = reader.getGlyphSet()
- self._hasNotChanged(doGlyphs=False)
-
- def _loadGlyph(self, glyphName):
- """Load a single glyph from the glyphSet, on request."""
- from robofab.pens.rfUFOPen import RFUFOPointPen
- g = RGlyph()
- g.name = glyphName
- pen = RFUFOPointPen(g)
- self._glyphSet.readGlyph(glyphName=glyphName, glyphObject=g, pointPen=pen)
- g.setParent(self)
- g.psHints._loadFromLib(g.lib)
- self._object[glyphName] = g
- self._object[glyphName]._hasNotChanged()
- return g
-
- #def _prepareSaveDir(self, dir):
- # path = os.path.join(dir, 'glyphs')
- # if not os.path.exists(path):
- # os.makedirs(path)
-
- def _hasNotChanged(self, doGlyphs=True):
- #set the changed state of the font
- if doGlyphs:
- for glyph in self:
- glyph._hasNotChanged()
- self.setChanged(False)
-
- #
- # attributes
- #
-
- def _get_path(self):
- return self._path
-
- path = property(_get_path, doc="path of the font")
-
- #
- # methods for imitating GlyphSet?
- #
-
- def keys(self):
- # the keys are the superset of self._objects.keys() and
- # self._glyphSet.keys(), minus self._scheduledForDeletion
- keys = self._object.keys()
- if self._glyphSet is not None:
- keys.extend(self._glyphSet.keys())
- d = dict()
- for glyphName in keys:
- d[glyphName] = None
- for glyphName in self._scheduledForDeletion:
- if glyphName in d:
- del d[glyphName]
- return d.keys()
-
- def has_key(self, glyphName):
- # XXX ditto, see above.
- if self._glyphSet is not None:
- hasGlyph = glyphName in self._object or glyphName in self._glyphSet
- else:
- hasGlyph = glyphName in self._object
- return hasGlyph and not glyphName in self._scheduledForDeletion
-
- __contains__ = has_key
-
- def getWidth(self, glyphName):
- if self._object.has_key(glyphName):
- return self._object[glyphName].width
- raise IndexError # or return None?
-
- def getReverseComponentMapping(self):
- """
- Get a reversed map of component references in the font.
- {
- 'A' : ['Aacute', 'Aring']
- 'acute' : ['Aacute']
- 'ring' : ['Aring']
- etc.
- }
- """
- # a NON-REVERESED map is stored in the lib.
- # this is done because a reveresed map could
- # contain faulty data. for example: "Aacute" contains
- # a component that references "A". Glyph "Aacute" is
- # then deleted. The reverse map would still say that
- # "A" is referenced by "Aacute" even though the
- # glyph has been deleted. So, the stored lib works like this:
- # {
- # 'Aacute' : [
- # # the last known mod time of the GLIF
- # 1098706856.75,
- # # component references in a glyph
- # ['A', 'acute']
- # ]
- # }
- import time
- import os
- import re
- componentSearch_RE = re.compile(
- "<component\s+" # <component
- "[^>]*?" # anything EXCEPT >
- "base\s*=\s*[\"\']" # base="
- "(.*?)" # foo
- "[\"\']" # "
- )
- rightNow = time.time()
- libKey = "org.robofab.componentMapping"
- previousMap = None
- if self.lib.has_key(libKey):
- previousMap = self.lib[libKey]
- basicMap = {}
- reverseMap = {}
- for glyphName in self.keys():
- componentsToMap = None
- modTime = None
- # get the previous bits of data
- previousModTime = None
- previousList = None
- if previousMap is not None and previousMap.has_key(glyphName):
- previousModTime, previousList = previousMap[glyphName]
- # the glyph has been loaded.
- # simply get the components from it.
- if self._object.has_key(glyphName):
- componentsToMap = [component.baseGlyph for component in self._object[glyphName].components]
- # the glyph has not been loaded.
- else:
- glyphPath = os.path.join(self._glyphSet.dirName, self._glyphSet.contents[glyphName])
- scanGlyph = True
- # test the modified time of the GLIF
- fileModTime = os.path.getmtime(glyphPath)
- if previousModTime is not None and fileModTime == previousModTime:
- # the GLIF almost* certianly has not changed.
- # *theoretically, a user could replace a GLIF
- # with another GLIF that has precisely the same
- # mod time.
- scanGlyph = False
- componentsToMap = previousList
- modTime = previousModTime
- else:
- # the GLIF is different
- modTime = fileModTime
- if scanGlyph:
- # use regex to extract component
- # base glyphs from the file
- f = open(glyphPath, 'rb')
- data = f.read()
- f.close()
- componentsToMap = componentSearch_RE.findall(data)
- if componentsToMap is not None:
- # store the non-reversed map
- basicMap[glyphName] = (modTime, componentsToMap)
- # reverse the map for the user
- if componentsToMap:
- for baseGlyphName in componentsToMap:
- if not reverseMap.has_key(baseGlyphName):
- reverseMap[baseGlyphName] = []
- reverseMap[baseGlyphName].append(glyphName)
- # if a glyph has been loaded, we do not store data about it in the lib.
- # this is done becuase there is not way to determine the proper mod time
- # for a loaded glyph.
- if modTime is None:
- del basicMap[glyphName]
- # store the map in the lib for re-use
- self.lib[libKey] = basicMap
- return reverseMap
-
-
- def save(self, destDir=None, doProgress=False, formatVersion=2):
- """Save the Font in UFO format."""
- # XXX note that when doing "save as" by specifying the destDir argument
- # _all_ glyphs get loaded into memory. This could be optimized by either
- # copying those .glif files that have not been edited or (not sure how
- # well that would work) by simply clearing out self._objects after the
- # save.
- from robofab.ufoLib import UFOWriter
- from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab
- # if no destination is given, or if
- # the given destination is the current
- # path, this is not a save as operation
- if destDir is None or destDir == self._path:
- saveAs = False
- destDir = self._path
- else:
- saveAs = True
- # start a progress bar
- nonGlyphCount = 5
- bar = None
- if doProgress:
- from robofab.interface.all.dialogs import ProgressBar
- bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys()))
- # write
- writer = UFOWriter(destDir, formatVersion=formatVersion)
- try:
- # make a shallow copy of the lib. stuff may be added to it.
- fontLib = dict(self.lib)
- # info
- if bar:
- bar.label("Saving info...")
- writer.writeInfo(self.info)
- if bar:
- bar.tick()
- # kerning
- if self.kerning.changed or saveAs:
- if bar:
- bar.label("Saving kerning...")
- writer.writeKerning(self.kerning.asDict())
- if bar:
- bar.tick()
- # groups
- if bar:
- bar.label("Saving groups...")
- writer.writeGroups(self.groups)
- if bar:
- bar.tick()
- # features
- if bar:
- bar.label("Saving features...")
- features = self.features.text
- if features is None:
- features = ""
- if formatVersion == 2:
- writer.writeFeatures(features)
- elif formatVersion == 1:
- classes, features = splitFeaturesForFontLab(features)
- if classes:
- fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n"
- if features:
- featureDict = {}
- for featureName, featureText in features:
- featureDict[featureName] = featureText.strip() + "\n"
- fontLib["org.robofab.opentype.features"] = featureDict
- fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features]
- if bar:
- bar.tick()
- # lib
- if formatVersion == 1:
- fontLib[postScriptHintDataLibKey] = self.psHints.asDict()
- if bar:
- bar.label("Saving lib...")
- writer.writeLib(fontLib)
- if bar:
- bar.tick()
- # glyphs
- glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc()
-
- glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc)
- if len(self._scheduledForDeletion) != 0:
- if bar:
- bar.label("Removing deleted glyphs...")
- for glyphName in self._scheduledForDeletion:
- if glyphSet.has_key(glyphName):
- glyphSet.deleteGlyph(glyphName)
- if bar:
- bar.tick()
- if bar:
- bar.label("Saving glyphs...")
- count = nonGlyphCount
- if saveAs:
- glyphNames = self.keys()
- else:
- glyphNames = self._object.keys()
- for glyphName in glyphNames:
- glyph = self[glyphName]
- glyph.psHints._saveToLib(glyph.lib)
- glyph._saveToGlyphSet(glyphSet, glyphName=glyphName, force=saveAs)
- if bar and not count % 10:
- bar.tick(count)
- count = count + 1
- glyphSet.writeContents()
- self._glyphSet = glyphSet
- # only blindly stop if the user says to
- except KeyboardInterrupt:
- bar.close()
- bar = None
- # kill the progress bar
- if bar:
- bar.close()
- # reset internal stuff
- self._path = destDir
- self._scheduledForDeletion = []
- self.setChanged(False)
-
- def newGlyph(self, glyphName, clear=True):
- """Make a new glyph with glyphName
- if the glyph exists and clear=True clear the glyph"""
- if clear and glyphName in self:
- g = self[glyphName]
- g.clear()
- w = self.info.postscriptDefaultWidthX
- if w is None:
- w = 0
- g.width = w
- return g
- g = RGlyph()
- g.setParent(self)
- g.name = glyphName
- w = self.info.postscriptDefaultWidthX
- if w is None:
- w = 0
- g.width = w
- g._hasChanged()
- self._object[glyphName] = g
- # is the user adding a glyph that has the same
- # name as one that was deleted earlier?
- if glyphName in self._scheduledForDeletion:
- self._scheduledForDeletion.remove(glyphName)
- return self.getGlyph(glyphName)
-
- def insertGlyph(self, glyph, name=None):
- """returns a new glyph that has been inserted into the font"""
- if name is None:
- name = glyph.name
- glyph = glyph.copy()
- glyph.name = name
- glyph.setParent(self)
- glyph._hasChanged()
- self._object[name] = glyph
- # is the user adding a glyph that has the same
- # name as one that was deleted earlier?
- if name in self._scheduledForDeletion:
- self._scheduledForDeletion.remove(name)
- return self.getGlyph(name)
-
- def removeGlyph(self, glyphName):
- """remove a glyph from the font"""
- # XXX! Potential issue with removing glyphs.
- # if a glyph is removed from a font, but it is still referenced
- # by a component, it will give pens some trouble.
- # where does the resposibility for catching this fall?
- # the removeGlyph method? the addComponent method
- # of the various pens? somewhere else? hm... tricky.
- #
- #we won't actually remove it, we will just store it for removal
- # but only if the glyph does exist
- if self.has_key(glyphName) and glyphName not in self._scheduledForDeletion:
- self._scheduledForDeletion.append(glyphName)
- # now delete the object
- if self._object.has_key(glyphName):
- del self._object[glyphName]
- self._hasChanged()
-
- def getGlyph(self, glyphName):
- # XXX getGlyph may have to become private, to avoid duplication
- # with __getitem__
- n = None
- if self._object.has_key(glyphName):
- # have we served this glyph before? it should be in _object
- n = self._object[glyphName]
- else:
- # haven't served it before, is it in the glyphSet then?
- if self._glyphSet is not None and glyphName in self._glyphSet:
- # yes, read the .glif file from disk
- n = self._loadGlyph(glyphName)
- if n is None:
- raise KeyError, glyphName
- return n
-
-
-class RGlyph(BaseGlyph):
-
- _title = "RGlyph"
-
- def __init__(self):
- BaseGlyph.__init__(self)
- self.contours = []
- self.components = []
- self.anchors = []
- self._unicodes = []
- self.width = 0
- self.note = None
- self._name = "Unnamed Glyph"
- self.selected = False
- self._properties = None
- self._lib = RLib()
- self._lib.setParent(self)
- self.psHints = PostScriptGlyphHintValues()
- self.psHints.setParent(self)
-
- def __len__(self):
- return len(self.contours)
-
- def __getitem__(self, index):
- if index < len(self.contours):
- return self.contours[index]
- raise IndexError
-
- def _hasNotChanged(self):
- for contour in self.contours:
- contour.setChanged(False)
- for segment in contour.segments:
- segment.setChanged(False)
- for point in segment.points:
- point.setChanged(False)
- for component in self.components:
- component.setChanged(False)
- for anchor in self.anchors:
- anchor.setChanged(False)
- self.setChanged(False)
-
- #
- # attributes
- #
-
- def _get_lib(self):
- return self._lib
-
- def _set_lib(self, obj):
- self._lib.clear()
- self._lib.update(obj)
-
- lib = property(_get_lib, _set_lib)
-
- def _get_name(self):
- return self._name
-
- def _set_name(self, value):
- prevName = self._name
- newName = value
- if newName == prevName:
- return
- self._name = newName
- self.setChanged(True)
- font = self.getParent()
- if font is not None:
- # but, this glyph could be linked to a
- # FontLab font, because objectsFL.RGlyph.copy()
- # creates an objectsRF.RGlyph with the parent
- # set to an objectsFL.RFont object. so, check to see
- # if this is a legitimate RFont before trying to
- # do the objectsRF.RFont glyph name change
- if isinstance(font, RFont):
- font._object[newName] = self
- # is the user changing a glyph's name to the
- # name of a glyph that was deleted earlier?
- if newName in font._scheduledForDeletion:
- font._scheduledForDeletion.remove(newName)
- font.removeGlyph(prevName)
-
- name = property(_get_name, _set_name)
-
- def _get_unicodes(self):
- return self._unicodes
-
- def _set_unicodes(self, value):
- if not isinstance(value, list):
- raise RoboFabError, "unicodes must be a list"
- self._unicodes = value
- self._hasChanged()
-
- unicodes = property(_get_unicodes, _set_unicodes, doc="all unicode values for the glyph")
-
- def _get_unicode(self):
- if len(self._unicodes) == 0:
- return None
- return self._unicodes[0]
-
- def _set_unicode(self, value):
- uni = self._unicodes
- if value is not None:
- if value not in uni:
- self.unicodes.insert(0, value)
- elif uni.index(value) != 0:
- uni.insert(0, uni.pop(uni.index(value)))
- self.unicodes = uni
-
- unicode = property(_get_unicode, _set_unicode, doc="first unicode value for the glyph")
-
- def getPointPen(self):
- from robofab.pens.rfUFOPen import RFUFOPointPen
- return RFUFOPointPen(self)
-
- def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)):
- """append a component to the glyph"""
- new = RComponent(baseGlyph, offset, scale)
- new.setParent(self)
- self.components.append(new)
- self._hasChanged()
-
- def appendAnchor(self, name, position, mark=None):
- """append an anchor to the glyph"""
- new = RAnchor(name, position, mark)
- new.setParent(self)
- self.anchors.append(new)
- self._hasChanged()
-
- def removeContour(self, index):
- """remove a specific contour from the glyph"""
- del self.contours[index]
- self._hasChanged()
-
- def removeAnchor(self, anchor):
- """remove a specific anchor from the glyph"""
- del self.anchors[anchor.index]
- self._hasChanged()
-
- def removeComponent(self, component):
- """remove a specific component from the glyph"""
- del self.components[component.index]
- self._hasChanged()
-
- def center(self, padding=None):
- """Equalise sidebearings, set to padding if wanted."""
- left = self.leftMargin
- right = self.rightMargin
- if padding:
- e_left = e_right = padding
- else:
- e_left = (left + right)/2
- e_right = (left + right) - e_left
- self.leftMargin = e_left
- self.rightMargin = e_right
-
- def decompose(self):
- """Decompose all components"""
- for i in range(len(self.components)):
- self.components[-1].decompose()
- self._hasChanged()
-
- def clear(self, contours=True, components=True, anchors=True, guides=True):
- """Clear all items marked as True from the glyph"""
- if contours:
- self.clearContours()
- if components:
- self.clearComponents()
- if anchors:
- self.clearAnchors()
- if guides:
- self.clearHGuides()
- self.clearVGuides()
-
- def clearContours(self):
- """clear all contours"""
- self.contours = []
- self._hasChanged()
-
- def clearComponents(self):
- """clear all components"""
- self.components = []
- self._hasChanged()
-
- def clearAnchors(self):
- """clear all anchors"""
- self.anchors = []
- self._hasChanged()
-
- def clearHGuides(self):
- """clear all horizontal guides"""
- self.hGuides = []
- self._hasChanged()
-
- def clearVGuides(self):
- """clear all vertical guides"""
- self.vGuides = []
- self._hasChanged()
-
- def getAnchors(self):
- return self.anchors
-
- def getComponents(self):
- return self.components
-
- #
- # stuff related to Glyph Properties
- #
-
-
-
-class RContour(BaseContour):
-
- _title = "RoboFabContour"
-
- def __init__(self, object=None):
- #BaseContour.__init__(self)
- self.segments = []
- self.selected = False
-
- def __len__(self):
- return len(self.segments)
-
- def __getitem__(self, index):
- if index < len(self.segments):
- return self.segments[index]
- raise IndexError
-
- def _get_index(self):
- return self.getParent().contours.index(self)
-
- def _set_index(self, index):
- ogIndex = self.index
- if index != ogIndex:
- contourList = self.getParent().contours
- contourList.insert(index, contourList.pop(ogIndex))
-
-
- index = property(_get_index, _set_index, doc="index of the contour")
-
- def _get_points(self):
- points = []
- for segment in self.segments:
- for point in segment.points:
- points.append(point)
- return points
-
- points = property(_get_points, doc="view the contour as a list of points")
-
- def _get_bPoints(self):
- bPoints = []
- for segment in self.segments:
- segType = segment.type
- if segType == MOVE:
- bType = CORNER
- elif segType == LINE:
- bType = CORNER
- elif segType == CURVE:
- if segment.smooth:
- bType = CURVE
- else:
- bType = CORNER
- else:
- raise RoboFabError, "encountered unknown segment type"
- b = RBPoint()
- b.setParent(segment)
- bPoints.append(b)
- return bPoints
-
- bPoints = property(_get_bPoints, doc="view the contour as a list of bPoints")
-
- def appendSegment(self, segmentType, points, smooth=False):
- """append a segment to the contour"""
- segment = self.insertSegment(index=len(self.segments), segmentType=segmentType, points=points, smooth=smooth)
- return segment
-
- def insertSegment(self, index, segmentType, points, smooth=False):
- """insert a segment into the contour"""
- segment = RSegment(segmentType, points, smooth)
- segment.setParent(self)
- self.segments.insert(index, segment)
- self._hasChanged()
- return segment
-
- def removeSegment(self, index):
- """remove a segment from the contour"""
- del self.segments[index]
- self._hasChanged()
-
- def reverseContour(self):
- """reverse the contour"""
- from robofab.pens.reverseContourPointPen import ReverseContourPointPen
- index = self.index
- glyph = self.getParent()
- pen = glyph.getPointPen()
- reversePen = ReverseContourPointPen(pen)
- self.drawPoints(reversePen)
- # we've drawn the reversed contour onto our parent glyph,
- # so it sits at the end of the contours list:
- newContour = glyph.contours.pop(-1)
- for segment in newContour.segments:
- segment.setParent(self)
- self.segments = newContour.segments
- self._hasChanged()
-
- def setStartSegment(self, segmentIndex):
- """set the first segment on the contour"""
- # this obviously does not support open contours
- if len(self.segments) < 2:
- return
- if segmentIndex == 0:
- return
- if segmentIndex > len(self.segments)-1:
- raise IndexError, 'segment index not in segments list'
- oldStart = self.segments[0]
- oldLast = self.segments[-1]
- #check to see if the contour ended with a curve on top of the move
- #if we find one delete it,
- if oldLast.type == CURVE or oldLast.type == QCURVE:
- startOn = oldStart.onCurve
- lastOn = oldLast.onCurve
- if startOn.x == lastOn.x and startOn.y == lastOn.y:
- del self.segments[0]
- # since we deleted the first contour, the segmentIndex needs to shift
- segmentIndex = segmentIndex - 1
- # if we DO have a move left over, we need to convert it to a line
- if self.segments[0].type == MOVE:
- self.segments[0].type = LINE
- # slice up the segments and reassign them to the contour
- segments = self.segments[segmentIndex:]
- self.segments = segments + self.segments[:segmentIndex]
- # now, draw the contour onto the parent glyph
- glyph = self.getParent()
- pen = glyph.getPointPen()
- self.drawPoints(pen)
- # we've drawn the new contour onto our parent glyph,
- # so it sits at the end of the contours list:
- newContour = glyph.contours.pop(-1)
- for segment in newContour.segments:
- segment.setParent(self)
- self.segments = newContour.segments
- self._hasChanged()
-
-
-class RSegment(BaseSegment):
-
- _title = "RoboFabSegment"
-
- def __init__(self, segmentType=None, points=[], smooth=False):
- BaseSegment.__init__(self)
- self.selected = False
- self.points = []
- self.smooth = smooth
- if points:
- #the points in the segment should be RPoints, so create those objects
- for point in points[:-1]:
- x, y = point
- p = RPoint(x, y, pointType=OFFCURVE)
- p.setParent(self)
- self.points.append(p)
- aX, aY = points[-1]
- p = RPoint(aX, aY, segmentType)
- p.setParent(self)
- self.points.append(p)
-
- def _get_type(self):
- return self.points[-1].type
-
- def _set_type(self, pointType):
- onCurve = self.points[-1]
- ocType = onCurve.type
- if ocType == pointType:
- return
- #we are converting a cubic line into a cubic curve
- if pointType == CURVE and ocType == LINE:
- onCurve.type = pointType
- parent = self.getParent()
- prev = parent._prevSegment(self.index)
- p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
- p1.setParent(self)
- p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
- p2.setParent(self)
- self.points.insert(0, p2)
- self.points.insert(0, p1)
- #we are converting a cubic move to a curve
- elif pointType == CURVE and ocType == MOVE:
- onCurve.type = pointType
- parent = self.getParent()
- prev = parent._prevSegment(self.index)
- p1 = RPoint(prev.onCurve.x, prev.onCurve.y, pointType=OFFCURVE)
- p1.setParent(self)
- p2 = RPoint(onCurve.x, onCurve.y, pointType=OFFCURVE)
- p2.setParent(self)
- self.points.insert(0, p2)
- self.points.insert(0, p1)
- #we are converting a quad curve to a cubic curve
- elif pointType == CURVE and ocType == QCURVE:
- onCurve.type == CURVE
- #we are converting a cubic curve into a cubic line
- elif pointType == LINE and ocType == CURVE:
- p = self.points.pop(-1)
- self.points = [p]
- onCurve.type = pointType
- self.smooth = False
- #we are converting a cubic move to a line
- elif pointType == LINE and ocType == MOVE:
- onCurve.type = pointType
- #we are converting a quad curve to a line:
- elif pointType == LINE and ocType == QCURVE:
- p = self.points.pop(-1)
- self.points = [p]
- onCurve.type = pointType
- self.smooth = False
- # we are converting to a quad curve where just about anything is legal
- elif pointType == QCURVE:
- onCurve.type = pointType
- else:
- raise RoboFabError, 'unknown segment type'
-
- type = property(_get_type, _set_type, doc="type of the segment")
-
- def _get_index(self):
- return self.getParent().segments.index(self)
-
- index = property(_get_index, doc="index of the segment")
-
- def insertPoint(self, index, pointType, point):
- x, y = point
- p = RPoint(x, y, pointType=pointType)
- p.setParent(self)
- self.points.insert(index, p)
- self._hasChanged()
-
- def removePoint(self, index):
- del self.points[index]
- self._hasChanged()
-
-
-class RBPoint(BaseBPoint):
-
- _title = "RoboFabBPoint"
-
- def _setAnchorChanged(self, value):
- self._anchorPoint.setChanged(value)
-
- def _setNextChanged(self, value):
- self._nextOnCurve.setChanged(value)
-
- def _get__parentSegment(self):
- return self.getParent()
-
- _parentSegment = property(_get__parentSegment, doc="")
-
- def _get__nextOnCurve(self):
- pSeg = self._parentSegment
- contour = pSeg.getParent()
- #could this potentially return an incorrect index? say, if two segments are exactly the same?
- return contour.segments[(contour.segments.index(pSeg) + 1) % len(contour.segments)]
-
- _nextOnCurve = property(_get__nextOnCurve, doc="")
-
- def _get_index(self):
- return self._parentSegment.index
-
- index = property(_get_index, doc="index of the bPoint on the contour")
-
-
-class RPoint(BasePoint):
-
- _title = "RoboFabPoint"
-
- def __init__(self, x=0, y=0, pointType=None, name=None):
- self.selected = False
- self._type = pointType
- self._x = x
- self._y = y
- self._name = name
-
- def _get_x(self):
- return self._x
-
- def _set_x(self, value):
- self._x = value
- self._hasChanged()
-
- x = property(_get_x, _set_x, doc="")
-
- def _get_y(self):
- return self._y
-
- def _set_y(self, value):
- self._y = value
- self._hasChanged()
-
- y = property(_get_y, _set_y, doc="")
-
- def _get_type(self):
- return self._type
-
- def _set_type(self, value):
- self._type = value
- self._hasChanged()
-
- type = property(_get_type, _set_type, doc="")
-
- def _get_name(self):
- return self._name
-
- def _set_name(self, value):
- self._name = value
- self._hasChanged()
-
- name = property(_get_name, _set_name, doc="")
-
-
-class RAnchor(BaseAnchor):
-
- _title = "RoboFabAnchor"
-
- def __init__(self, name=None, position=None, mark=None):
- BaseAnchor.__init__(self)
- self.selected = False
- self.name = name
- if position is None:
- self.x = self.y = None
- else:
- self.x, self.y = position
- self.mark = mark
-
- def _get_index(self):
- if self.getParent() is None: return None
- return self.getParent().anchors.index(self)
-
- index = property(_get_index, doc="index of the anchor")
-
- def _get_position(self):
- return (self.x, self.y)
-
- def _set_position(self, value):
- self.x = value[0]
- self.y = value[1]
- self._hasChanged()
-
- position = property(_get_position, _set_position, doc="position of the anchor")
-
- def move(self, pt):
- """Move the anchor"""
- self.x = self.x + pt[0]
- self.y = self.y + pt[0]
- self._hasChanged()
-
-
-class RComponent(BaseComponent):
-
- _title = "RoboFabComponent"
-
- def __init__(self, baseGlyphName=None, offset=(0,0), scale=(1,1), transform=None):
- BaseComponent.__init__(self)
- self.selected = False
- self._baseGlyph = baseGlyphName
- self._offset = offset
- self._scale = scale
- if transform is None:
- xx, yy = scale
- dx, dy = offset
- self.transformation = (xx, 0, 0, yy, dx, dy)
- else:
- self.transformation = transform
-
- def _get_index(self):
- if self.getParent() is None: return None
- return self.getParent().components.index(self)
-
- index = property(_get_index, doc="index of the component")
-
- def _get_baseGlyph(self):
- return self._baseGlyph
-
- def _set_baseGlyph(self, glyphName):
- # XXXX needs to be implemented in objectsFL for symmetricity's sake. Eventually.
- self._baseGlyph = glyphName
- self._hasChanged()
-
- baseGlyph = property(_get_baseGlyph, _set_baseGlyph, doc="")
-
- def _get_offset(self):
- """ Get the offset component of the transformation.="""
- (xx, xy, yx, yy, dx, dy) = self._transformation
- return dx, dy
-
- def _set_offset(self, value):
- """ Set the offset component of the transformation."""
- (xx, xy, yx, yy, dx, dy) = self._transformation
- self._transformation = (xx, xy, yx, yy, value[0], value[1])
- self._hasChanged()
-
- offset = property(_get_offset, _set_offset, doc="the offset of the component")
-
- def _get_scale(self):
- """ Return the scale components of the transformation."""
- (xx, xy, yx, yy, dx, dy) = self._transformation
- return xx, yy
-
- def _set_scale(self, scale):
- """ Set the scale component of the transformation.
- Note: setting this value effectively makes the xy and yx values meaningless.
- We're assuming that if you're setting the xy and yx values, you will use
- the transformation attribute rather than the scale and offset attributes.
- """
- xScale, yScale = scale
- (xx, xy, yx, yy, dx, dy) = self._transformation
- self._transformation = (xScale, xy, yx, yScale, dx, dy)
- self._hasChanged()
-
- scale = property(_get_scale, _set_scale, doc="the scale of the component")
-
- def _get_transformation(self):
- return self._transformation
-
- def _set_transformation(self, transformation):
- assert len(transformation)==6, "Transformation matrix must have 6 values"
- self._transformation = transformation
-
- transformation = property(_get_transformation, _set_transformation, doc="the transformation matrix of the component")
-
- def move(self, pt):
- """Move the component"""
- (xx, xy, yx, yy, dx, dy) = self._transformation
- self._transformation = (xx, xy, yx, yy, dx+pt[0], dy+pt[1])
- self._hasChanged()
-
- def decompose(self):
- """Decompose the component"""
- baseGlyphName = self.baseGlyph
- parentGlyph = self.getParent()
- # if there is no parent glyph, there is nothing to decompose to
- if baseGlyphName is not None and parentGlyph is not None:
- parentFont = parentGlyph.getParent()
- # we must have a parent glyph with the baseGlyph
- # if not, we will simply remove the component from
- # the parent glyph thereby decomposing the component
- # to nothing.
- if parentFont is not None and parentFont.has_key(baseGlyphName):
- from robofab.pens.adapterPens import TransformPointPen
- baseGlyph = parentFont[baseGlyphName]
- for contour in baseGlyph.contours:
- pointPen = parentGlyph.getPointPen()
- transPen = TransformPointPen(pointPen, self._transformation)
- contour.drawPoints(transPen)
- parentGlyph.components.remove(self)
-
-
-class RKerning(BaseKerning):
-
- _title = "RoboFabKerning"
-
-
-class RGroups(BaseGroups):
-
- _title = "RoboFabGroups"
-
-class RLib(BaseLib):
-
- _title = "RoboFabLib"
-
-
-class RInfo(BaseInfo):
-
- _title = "RoboFabFontInfo"
-
-class RFeatures(BaseFeatures):
-
- _title = "RoboFabFeatures"
-
diff --git a/misc/pylib/robofab/path/__init__.py b/misc/pylib/robofab/path/__init__.py
deleted file mode 100755
index f268df6c9..000000000
--- a/misc/pylib/robofab/path/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
-
-Directory for modules
-which do path stuff.
-Maybe it should move somewhere else.
-
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/path/intersect.py b/misc/pylib/robofab/path/intersect.py
deleted file mode 100644
index ea0600c19..000000000
--- a/misc/pylib/robofab/path/intersect.py
+++ /dev/null
@@ -1,108 +0,0 @@
-from robofab.pens.filterPen import flattenGlyph
-from robofab.objects.objectsRF import RGlyph as _RGlyph
-import math
-
-_EPSILON = 1e-15
-
-
-def normalise(a1, a2):
- """Normalise this vector to length 1"""
- n = math.sqrt((a1*a1)+(a2*a2))
- return (a1/n, a2/n)
-
-def inbetween((a1, a2), (b1, b2), (c1, c2)):
- """Return True if point b is in between points a and c."""
- x = (a1-_EPSILON<=b1<=c1+_EPSILON) or (a1+_EPSILON>=b1>=c1-_EPSILON)
- y = (a2-_EPSILON<=b2<=c2+_EPSILON) or (a2+_EPSILON>=b2>=c2-_EPSILON)
- return x == y == True
-
-def sectlines((a1, a2), (p1, p2), (b1, b2), (q1, q2)):
- '''Calculate the intersection point of two straight lines. Result in floats.'''
- if (a1, a2) == (p1, p2):
- return None
- r1 = a1-p1
- r2 = a2-p2
- r1, r2 = normalise(r1, r2)
- s1 = b1-q1
- s2 = b2-q2
- s1, s2 = normalise(s1, s2)
- f = float(s1*r2 - s2*r1)
- if f == 0:
- return None
- mu = (r1*(q2 - p2) + r2*(p1 - q1)) / f
- m1 = mu*s1 + q1
- m2 = mu*s2 + q2
- if (m1, m2) == (a1, a2):
- return None
- if inbetween((a1, a2), (m1, m2), (p1,p2)) and inbetween((b1, b2), (m1, m2), (q1,q2)):
- return m1, m2
- else:
- return None
-
-def _makeFlat(aGlyph, segmentLength = 10):
- """Helper function to flatten the glyph with a given approximate segment length."""
- return flattenGlyph(aGlyph, segmentLength)
-
-def intersect(aGlyph, startPt, endPt, segmentLength=10):
- """Find the intersections between a glyph and a straight line."""
- flat = _makeFlat(aGlyph)
- return _intersect(flat, startPt, endPt, segmentLength)
-
-def _intersect(flat, startPt, endPt, segmentLength=10):
- """Find the intersections between a flattened glyph and a straight line."""
- if len(flat.contours) == 0:
- return None
- if startPt == endPt:
- return None
- sect = None
- intersections = {}
- # new contains the flattened outline
- for c in flat:
- l =len(c.points)
- for i in range(l):
- cur = c.points[i]
- next = c.points[(i+1)%l]
- sect = sectlines((cur.x, cur.y), (next.x, next.y), startPt, endPt)
- if sect is None:
- continue
- intersections[sect] = 1
- return intersections.keys()
-
-def intersectGlyphs(glyphA, glyphB, segmentLength=10):
- """Approximate the intersection points between two glyphs by
- flattening both glyphs and checking each tiny segment for
- intersections. Slow, but perhaps more realistic then
- solving the equasions.
-
- Seems to work for basic curves and straights, but untested
- for edges cases, alsmost hits, near hits, double points, crap like that.
- """
- flatA = _makeFlat(glyphA)
- flatB = _makeFlat(glyphB)
- intersections = []
- for c in flatA:
- l =len(c.points)
- for i in range(l):
- cur = c.points[i]
- next = c.points[(i+1)%l]
- sect = _intersect(flatB, (cur.x, cur.y), (next.x, next.y))
- if sect is None:
- continue
- intersections = intersections + sect
- return intersections
-
-def makeTestGlyph():
- g = _RGlyph()
- pen = g.getPen()
- pen.moveTo((100, 100))
- pen.lineTo((800, 100))
- pen.curveTo((1000, 300), (1000, 600), (800, 800))
- pen.lineTo((100, 800))
- pen.lineTo((100, 100))
- pen.closePath()
- return g
-
-if __name__ == "__main__":
- g = makeTestGlyph()
- print intersect(g, (-10, 200), (650, 150))
- print intersect(g, (100, 100), (600, 600))
diff --git a/misc/pylib/robofab/pens/__init__.py b/misc/pylib/robofab/pens/__init__.py
deleted file mode 100755
index a5afc0dc3..000000000
--- a/misc/pylib/robofab/pens/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-"""
-
-Directory for all pen modules.
-If you make a pen, put it here so that we can keep track of it.
-
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/pens/adapterPens.py b/misc/pylib/robofab/pens/adapterPens.py
deleted file mode 100644
index 0cd9ae3f7..000000000
--- a/misc/pylib/robofab/pens/adapterPens.py
+++ /dev/null
@@ -1,293 +0,0 @@
-import math
-from fontTools.pens.basePen import AbstractPen
-from robofab.pens.pointPen import AbstractPointPen, BasePointToSegmentPen
-
-
-class FabToFontToolsPenAdapter:
-
- """Class that covers up the subtle differences between RoboFab
- Pens and FontTools Pens. 'Fab should eventually move to FontTools
- Pens, this class may help to make the transition smoother.
- """
-
- # XXX The change to FontTools pens has almost been completed. Any
- # usage of this class should be highly suspect.
-
- def __init__(self, fontToolsPen):
- self.fontToolsPen = fontToolsPen
-
- def moveTo(self, pt, **kargs):
- self.fontToolsPen.moveTo(pt)
-
- def lineTo(self, pt, **kargs):
- self.fontToolsPen.lineTo(pt)
-
- def curveTo(self, *pts, **kargs):
- self.fontToolsPen.curveTo(*pts)
-
- def qCurveTo(self, *pts, **kargs):
- self.fontToolsPen.qCurveTo(*pts)
-
- def closePath(self):
- self.fontToolsPen.closePath()
-
- def endPath(self):
- self.fontToolsPen.endPath()
-
- def addComponent(self, glyphName, offset=(0, 0), scale=(1, 1)):
- self.fontToolsPen.addComponent(glyphName,
- (scale[0], 0, 0, scale[1], offset[0], offset[1]))
-
- def setWidth(self, width):
- self.width = width
-
- def setNote(self, note):
- pass
-
- def addAnchor(self, name, pt):
- self.fontToolsPen.moveTo(pt)
- self.fontToolsPen.endPath()
-
- def doneDrawing(self):
- pass
-
-
-class PointToSegmentPen(BasePointToSegmentPen):
-
- """Adapter class that converts the PointPen protocol to the
- (Segment)Pen protocol.
- """
-
- def __init__(self, segmentPen, outputImpliedClosingLine=False):
- BasePointToSegmentPen.__init__(self)
- self.pen = segmentPen
- self.outputImpliedClosingLine = outputImpliedClosingLine
-
- def _flushContour(self, segments):
- assert len(segments) >= 1
- pen = self.pen
- if segments[0][0] == "move":
- # It's an open path.
- closed = False
- points = segments[0][1]
- assert len(points) == 1
- movePt, smooth, name, kwargs = points[0]
- del segments[0]
- else:
- # It's a closed path, do a moveTo to the last
- # point of the last segment.
- closed = True
- segmentType, points = segments[-1]
- movePt, smooth, name, kwargs = points[-1]
- if movePt is None:
- # quad special case: a contour with no on-curve points contains
- # one "qcurve" segment that ends with a point that's None. We
- # must not output a moveTo() in that case.
- pass
- else:
- pen.moveTo(movePt)
- outputImpliedClosingLine = self.outputImpliedClosingLine
- nSegments = len(segments)
- for i in range(nSegments):
- segmentType, points = segments[i]
- points = [pt for pt, smooth, name, kwargs in points]
- if segmentType == "line":
- assert len(points) == 1
- pt = points[0]
- if i + 1 != nSegments or outputImpliedClosingLine or not closed:
- pen.lineTo(pt)
- elif segmentType == "curve":
- pen.curveTo(*points)
- elif segmentType == "qcurve":
- pen.qCurveTo(*points)
- else:
- assert 0, "illegal segmentType: %s" % segmentType
- if closed:
- pen.closePath()
- else:
- pen.endPath()
-
- def addComponent(self, glyphName, transform):
- self.pen.addComponent(glyphName, transform)
-
-
-class SegmentToPointPen(AbstractPen):
-
- """Adapter class that converts the (Segment)Pen protocol to the
- PointPen protocol.
- """
-
- def __init__(self, pointPen, guessSmooth=True):
- if guessSmooth:
- self.pen = GuessSmoothPointPen(pointPen)
- else:
- self.pen = pointPen
- self.contour = None
-
- def _flushContour(self):
- pen = self.pen
- pen.beginPath()
- for pt, segmentType in self.contour:
- pen.addPoint(pt, segmentType=segmentType)
- pen.endPath()
-
- def moveTo(self, pt):
- self.contour = []
- self.contour.append((pt, "move"))
-
- def lineTo(self, pt):
- self.contour.append((pt, "line"))
-
- def curveTo(self, *pts):
- for pt in pts[:-1]:
- self.contour.append((pt, None))
- self.contour.append((pts[-1], "curve"))
-
- def qCurveTo(self, *pts):
- if pts[-1] is None:
- self.contour = []
- for pt in pts[:-1]:
- self.contour.append((pt, None))
- if pts[-1] is not None:
- self.contour.append((pts[-1], "qcurve"))
-
- def closePath(self):
- if len(self.contour) > 1 and self.contour[0][0] == self.contour[-1][0]:
- self.contour[0] = self.contour[-1]
- del self.contour[-1]
- else:
- # There's an implied line at the end, replace "move" with "line"
- # for the first point
- pt, tp = self.contour[0]
- if tp == "move":
- self.contour[0] = pt, "line"
- self._flushContour()
- self.contour = None
-
- def endPath(self):
- self._flushContour()
- self.contour = None
-
- def addComponent(self, glyphName, transform):
- assert self.contour is None
- self.pen.addComponent(glyphName, transform)
-
-
-class TransformPointPen(AbstractPointPen):
-
- """PointPen that transforms all coordinates, and passes them to another
- PointPen. It also transforms the transformation given to addComponent().
- """
-
- def __init__(self, outPen, transformation):
- if not hasattr(transformation, "transformPoint"):
- from fontTools.misc.transform import Transform
- transformation = Transform(*transformation)
- self._transformation = transformation
- self._transformPoint = transformation.transformPoint
- self._outPen = outPen
- self._stack = []
-
- def beginPath(self):
- self._outPen.beginPath()
-
- def endPath(self):
- self._outPen.endPath()
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- pt = self._transformPoint(pt)
- self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
-
- def addComponent(self, glyphName, transformation):
- transformation = self._transformation.transform(transformation)
- self._outPen.addComponent(glyphName, transformation)
-
-
-class GuessSmoothPointPen(AbstractPointPen):
-
- """Filtering PointPen that tries to determine whether an on-curve point
- should be "smooth", ie. that it's a "tangent" point or a "curve" point.
- """
-
- def __init__(self, outPen):
- self._outPen = outPen
- self._points = None
-
- def _flushContour(self):
- points = self._points
- nPoints = len(points)
- if not nPoints:
- return
- if points[0][1] == "move":
- # Open path.
- indices = range(1, nPoints - 1)
- elif nPoints > 1:
- # Closed path. To avoid having to mod the contour index, we
- # simply abuse Python's negative index feature, and start at -1
- indices = range(-1, nPoints - 1)
- else:
- # closed path containing 1 point (!), ignore.
- indices = []
- for i in indices:
- pt, segmentType, dummy, name, kwargs = points[i]
- if segmentType is None:
- continue
- prev = i - 1
- next = i + 1
- if points[prev][1] is not None and points[next][1] is not None:
- continue
- # At least one of our neighbors is an off-curve point
- pt = points[i][0]
- prevPt = points[prev][0]
- nextPt = points[next][0]
- if pt != prevPt and pt != nextPt:
- dx1, dy1 = pt[0] - prevPt[0], pt[1] - prevPt[1]
- dx2, dy2 = nextPt[0] - pt[0], nextPt[1] - pt[1]
- a1 = math.atan2(dx1, dy1)
- a2 = math.atan2(dx2, dy2)
- if abs(a1 - a2) < 0.05:
- points[i] = pt, segmentType, True, name, kwargs
-
- for pt, segmentType, smooth, name, kwargs in points:
- self._outPen.addPoint(pt, segmentType, smooth, name, **kwargs)
-
- def beginPath(self):
- assert self._points is None
- self._points = []
- self._outPen.beginPath()
-
- def endPath(self):
- self._flushContour()
- self._outPen.endPath()
- self._points = None
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self._points.append((pt, segmentType, False, name, kwargs))
-
- def addComponent(self, glyphName, transformation):
- assert self._points is None
- self._outPen.addComponent(glyphName, transformation)
-
-
-if __name__ == "__main__":
- from fontTools.pens.basePen import _TestPen as PSPen
- from robofab.pens.pointPen import PrintingPointPen
- segmentPen = PSPen(None)
-# pen = PointToSegmentPen(SegmentToPointPen(PointToSegmentPen(PSPen(None))))
- pen = PointToSegmentPen(SegmentToPointPen(PrintingPointPen()))
-# pen = PrintingPointPen()
- pen = PointToSegmentPen(PSPen(None), outputImpliedClosingLine=False)
-# pen.beginPath()
-# pen.addPoint((50, 50), name="an anchor")
-# pen.endPath()
- pen.beginPath()
- pen.addPoint((-100, 0), segmentType="line")
- pen.addPoint((0, 0), segmentType="line")
- pen.addPoint((0, 100), segmentType="line")
- pen.addPoint((30, 200))
- pen.addPoint((50, 100), name="superbezcontrolpoint!")
- pen.addPoint((70, 200))
- pen.addPoint((100, 100), segmentType="curve")
- pen.addPoint((100, 0), segmentType="line")
- pen.endPath()
-# pen.addComponent("a", (1, 0, 0, 1, 100, 200))
diff --git a/misc/pylib/robofab/pens/angledMarginPen.py b/misc/pylib/robofab/pens/angledMarginPen.py
deleted file mode 100644
index 49ff8eed3..000000000
--- a/misc/pylib/robofab/pens/angledMarginPen.py
+++ /dev/null
@@ -1,132 +0,0 @@
-from robofab.world import RFont
-from fontTools.pens.basePen import BasePen
-from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect
-from robofab.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
-from robofab.pens.filterPen import _estimateCubicCurveLength, _getCubicPoint
-import math
-
-
-
-__all__ = ["AngledMarginPen", "getAngledMargins",
- "setAngledLeftMargin", "setAngledRightMargin",
- "centerAngledMargins"]
-
-
-
-class AngledMarginPen(BasePen):
- """
- Angled Margin Pen
-
- Pen to calculate the margins as if the margin lines were slanted
- according to the font.info.italicAngle.
-
- Notes:
- - this pen works on the on-curve points, and approximates the distance to curves.
- - results will be float.
- - when used in FontLab, the resulting margins may be slightly
- different from the values originally set, due to rounding errors.
- - similar to what RoboFog used to do.
- - RoboFog had a special attribute for "italicoffset", horizontal
- shift of all glyphs. This is missing in Robofab.
- """
- def __init__(self, glyphSet, width, italicAngle):
- BasePen.__init__(self, glyphSet)
- self.width = width
- self._angle = math.radians(90+italicAngle)
- self.maxSteps = 100
- self.margin = None
- self._left = None
- self._right = None
- self._start = None
- self.currentPt = None
-
- def _getAngled(self, pt):
- r = (g.width + (pt[1] / math.tan(self._angle)))-pt[0]
- l = pt[0]-((pt[1] / math.tan(self._angle)))
- if self._right is None:
- self._right = r
- else:
- self._right = min(self._right, r)
- if self._left is None:
- self._left = l
- else:
- self._left = min(self._left, l)
- #print pt, l, r
- self.margin = self._left, self._right
-
- def _moveTo(self, pt):
- self._start = self.currentPt = pt
-
- def _addMoveTo(self):
- if self._start is None:
- return
- self._start = self.currentPt = None
-
- def _lineTo(self, pt):
- self._addMoveTo()
- self._getAngled(pt)
-
- def _curveToOne(self, pt1, pt2, pt3):
- step = 1.0/self.maxSteps
- factors = range(0, self.maxSteps+1)
- for i in factors:
- pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3)
- self._getAngled(pt)
- self.currentPt = pt3
-
- def _qCurveToOne(self, bcp, pt):
- self._addMoveTo()
- # add curve tracing magic here.
- self._getAngled(pt)
- self.currentPt = pt3
-
-def getAngledMargins(glyph, font):
- """Get the angled margins for this glyph."""
- pen = AngledMarginPen(font, glyph.width, font.info.italicAngle)
- glyph.draw(pen)
- return pen.margin
-
-def setAngledLeftMargin(glyph, font, value):
- """Set the left angled margin to value, adjusted for font.info.italicAngle."""
- pen = AngledMarginPen(font, glyph.width, font.info.italicAngle)
- g.draw(pen)
- isLeft, isRight = pen.margin
- glyph.leftMargin += value-isLeft
-
-def setAngledRightMargin(glyph, font, value):
- """Set the right angled margin to value, adjusted for font.info.italicAngle."""
- pen = AngledMarginPen(font, glyph.width, font.info.italicAngle)
- g.draw(pen)
- isLeft, isRight = pen.margin
- glyph.rightMargin += value-isRight
-
-def centerAngledMargins(glyph, font):
- """Center the glyph on angled margins."""
- pen = AngledMarginPen(font, glyph.width, font.info.italicAngle)
- g.draw(pen)
- isLeft, isRight = pen.margin
- setAngledLeftMargin(glyph, font, (isLeft+isRight)*.5)
- setAngledRightMargin(glyph, font, (isLeft+isRight)*.5)
-
-def guessItalicOffset(glyph, font):
- """Guess the italic offset based on the margins of a symetric glyph.
- For instance H or I.
- """
- l, r = getAngledMargins(glyph, font)
- return l - (l+r)*.5
-
-
-if __name__ == "__main__":
-
- # example for FontLab, with a glyph open.
- from robofab.world import CurrentFont, CurrentGlyph
- g = CurrentGlyph()
- f = CurrentFont()
-
- print "margins!", getAngledMargins(g, f)
- # set the angled margin to a value
- m = 50
- setAngledLeftMargin(g, f, m)
- setAngledRightMargin(g, f, m)
- g.update()
-
diff --git a/misc/pylib/robofab/pens/boundsPen.pyx b/misc/pylib/robofab/pens/boundsPen.pyx
deleted file mode 100644
index a1a72bd12..000000000
--- a/misc/pylib/robofab/pens/boundsPen.pyx
+++ /dev/null
@@ -1,95 +0,0 @@
-from fontTools.pens.basePen import BasePen
-from robofab.misc.arrayTools import updateBounds, pointInRect, unionRect
-from robofab.misc.bezierTools import calcCubicBounds, calcQuadraticBounds
-
-
-__all__ = ["BoundsPen", "ControlBoundsPen"]
-
-
-class ControlBoundsPen(BasePen):
-
- """Pen to calculate the "control bounds" of a shape. This is the
- bounding box of all control points __on closed paths__, so may be larger than the
- actual bounding box if there are curves that don't have points
- on their extremes.
-
- Single points, or anchors, are ignored.
-
- When the shape has been drawn, the bounds are available as the
- 'bounds' attribute of the pen object. It's a 4-tuple:
- (xMin, yMin, xMax, yMax)
-
- This replaces fontTools/pens/boundsPen (temporarily?)
- The fontTools bounds pen takes lose anchor points into account,
- this one doesn't.
- """
-
- def __init__(self, glyphSet):
- BasePen.__init__(self, glyphSet)
- self.bounds = None
- self._start = None
-
- def _moveTo(self, pt):
- self._start = pt
-
- def _addMoveTo(self):
- if self._start is None:
- return
- bounds = self.bounds
- if bounds:
- self.bounds = updateBounds(bounds, self._start)
- else:
- x, y = self._start
- self.bounds = (x, y, x, y)
- self._start = None
-
- def _lineTo(self, pt):
- self._addMoveTo()
- self.bounds = updateBounds(self.bounds, pt)
-
- def _curveToOne(self, bcp1, bcp2, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = updateBounds(bounds, bcp1)
- bounds = updateBounds(bounds, bcp2)
- bounds = updateBounds(bounds, pt)
- self.bounds = bounds
-
- def _qCurveToOne(self, bcp, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = updateBounds(bounds, bcp)
- bounds = updateBounds(bounds, pt)
- self.bounds = bounds
-
-
-class BoundsPen(ControlBoundsPen):
-
- """Pen to calculate the bounds of a shape. It calculates the
- correct bounds even when the shape contains curves that don't
- have points on their extremes. This is somewhat slower to compute
- than the "control bounds".
-
- When the shape has been drawn, the bounds are available as the
- 'bounds' attribute of the pen object. It's a 4-tuple:
- (xMin, yMin, xMax, yMax)
- """
-
- def _curveToOne(self, bcp1, bcp2, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = updateBounds(bounds, pt)
- if not pointInRect(bcp1, bounds) or not pointInRect(bcp2, bounds):
- bounds = unionRect(bounds, calcCubicBounds(
- self._getCurrentPoint(), bcp1, bcp2, pt))
- self.bounds = bounds
-
- def _qCurveToOne(self, bcp, pt):
- self._addMoveTo()
- bounds = self.bounds
- bounds = updateBounds(bounds, pt)
- if not pointInRect(bcp, bounds):
- bounds = unionRect(bounds, calcQuadraticBounds(
- self._getCurrentPoint(), bcp, pt))
- self.bounds = bounds
-
diff --git a/misc/pylib/robofab/pens/digestPen.py b/misc/pylib/robofab/pens/digestPen.py
deleted file mode 100755
index 930daf468..000000000
--- a/misc/pylib/robofab/pens/digestPen.py
+++ /dev/null
@@ -1,106 +0,0 @@
-"""A couple of point pens which return the glyph as a list of basic values."""
-
-
-from robofab.pens.pointPen import AbstractPointPen
-
-
-class DigestPointPen(AbstractPointPen):
-
- """Calculate a digest of all points
- AND coordinates
- AND components
- in a glyph.
- """
-
- def __init__(self, ignoreSmoothAndName=False):
- self._data = []
- self.ignoreSmoothAndName = ignoreSmoothAndName
-
- def beginPath(self):
- self._data.append('beginPath')
-
- def endPath(self):
- self._data.append('endPath')
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- if self.ignoreSmoothAndName:
- self._data.append((pt, segmentType))
- else:
- self._data.append((pt, segmentType, smooth, name))
-
- def addComponent(self, baseGlyphName, transformation):
- t = []
- for v in transformation:
- if int(v) == v:
- t.append(int(v))
- else:
- t.append(v)
- self._data.append((baseGlyphName, tuple(t)))
-
- def getDigest(self):
- return tuple(self._data)
-
- def getDigestPointsOnly(self, needSort=True):
- """ Return a tuple with all coordinates of all points,
- but without smooth info or drawing instructions.
- For instance if you want to compare 2 glyphs in shape,
- but not interpolatability.
- """
- points = []
- from types import TupleType
- for item in self._data:
- if type(item) == TupleType:
- points.append(item[0])
- if needSort:
- points.sort()
- return tuple(points)
-
-
-class DigestPointStructurePen(DigestPointPen):
-
- """Calculate a digest of the structure of the glyph
- NOT coordinates
- NOT values.
- """
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self._data.append(segmentType)
-
- def addComponent(self, baseGlyphName, transformation):
- self._data.append(baseGlyphName)
-
-if __name__ == "__main__":
- """
-
- beginPath
- ((112, 651), 'line', False, None)
- ((112, 55), 'line', False, None)
- ((218, 55), 'line', False, None)
- ((218, 651), 'line', False, None)
- endPath
-
- """
- # a test
-
- from robofab.objects.objectsRF import RGlyph
-
- g = RGlyph()
- p = g.getPen()
- p.moveTo((112, 651))
- p.lineTo((112, 55))
- p.lineTo((218, 55))
- p.lineTo((218, 651))
- p.closePath()
-
- print g, len(g)
-
- digestPen = DigestPointPen()
- g.drawPoints(digestPen)
-
- print
- print "getDigest", digestPen.getDigest()
-
- print
- print "getDigestPointsOnly", digestPen.getDigestPointsOnly()
-
- \ No newline at end of file
diff --git a/misc/pylib/robofab/pens/filterPen.py b/misc/pylib/robofab/pens/filterPen.py
deleted file mode 100755
index f7c84c082..000000000
--- a/misc/pylib/robofab/pens/filterPen.py
+++ /dev/null
@@ -1,407 +0,0 @@
-"""A couple of point pens to filter contours in various ways."""
-
-from fontTools.pens.basePen import AbstractPen, BasePen
-
-from robofab.pens.pointPen import AbstractPointPen
-from robofab.objects.objectsRF import RGlyph as _RGlyph
-from robofab.objects.objectsBase import _interpolatePt
-
-import math
-
-#
-# threshold filtering
-#
-
-def distance(pt1, pt2):
- return math.hypot(pt1[0]-pt2[0], pt1[1]-pt2[1])
-
-class ThresholdPointPen(AbstractPointPen):
-
- """
- Rewrite of the ThresholdPen as a PointPen
- so that we can preserve named points and other arguments.
- This pen will add components from the original glyph, but
- but it won't filter those components.
-
- "move", "line", "curve" or "qcurve"
-
- """
- def __init__(self, otherPointPen, threshold=10):
- self.threshold = threshold
- self._lastPt = None
- self._offCurveBuffer = []
- self.otherPointPen = otherPointPen
-
- def beginPath(self):
- """Start a new sub path."""
- self.otherPointPen.beginPath()
- self._lastPt = None
-
- def endPath(self):
- """End the current sub path."""
- self.otherPointPen.endPath()
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- """Add a point to the current sub path."""
- if segmentType in ['curve', 'qcurve']:
- # it's an offcurve, let's buffer them until we get another oncurve
- # and we know what to do with them
- self._offCurveBuffer.append((pt, segmentType, smooth, name, kwargs))
- return
-
- elif segmentType == "move":
- # start of an open contour
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- self._offCurveBuffer = []
-
- elif segmentType == "line":
- if self._lastPt is None:
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- elif distance(pt, self._lastPt) >= self.threshold:
- # we're oncurve and far enough from the last oncurve
- if self._offCurveBuffer:
- # empty any buffered offcurves
- for buf_pt, buf_segmentType, buf_smooth, buf_name, buf_kwargs in self._offCurveBuffer:
- self.otherPointPen.addPoint(buf_pt, buf_segmentType, buf_smooth, buf_name) # how to add kwargs?
- self._offCurveBuffer = []
- # finally add the oncurve.
- self.otherPointPen.addPoint(pt, segmentType, smooth, name) # how to add kwargs?
- self._lastPt = pt
- else:
- # we're too short, so we're not going to make it.
- # we need to clear out the offcurve buffer.
- self._offCurveBuffer = []
-
- def addComponent(self, baseGlyphName, transformation):
- """Add a sub glyph. Note: this way components are not filtered."""
- self.otherPointPen.addComponent(baseGlyphName, transformation)
-
-
-class ThresholdPen(AbstractPen):
-
- """Removes segments shorter in length than the threshold value."""
-
- def __init__(self, otherPen, threshold=10):
- self.threshold = threshold
- self._lastPt = None
- self.otherPen = otherPen
-
- def moveTo(self, pt):
- self._lastPt = pt
- self.otherPen.moveTo(pt)
-
- def lineTo(self, pt, smooth=False):
- if self.threshold <= distance(pt, self._lastPt):
- self.otherPen.lineTo(pt)
- self._lastPt = pt
-
- def curveTo(self, pt1, pt2, pt3):
- if self.threshold <= distance(pt3, self._lastPt):
- self.otherPen.curveTo(pt1, pt2, pt3)
- self._lastPt = pt3
-
- def qCurveTo(self, *points):
- if self.threshold <= distance(points[-1], self._lastPt):
- self.otherPen.qCurveTo(*points)
- self._lastPt = points[-1]
-
- def closePath(self):
- self.otherPen.closePath()
-
- def endPath(self):
- self.otherPen.endPath()
-
- def addComponent(self, glyphName, transformation):
- self.otherPen.addComponent(glyphName, transformation)
-
-
-def thresholdGlyph(aGlyph, threshold=10):
- """ Convenience function that handles the filtering. """
- from robofab.pens.adapterPens import PointToSegmentPen
- new = _RGlyph()
- filterpen = ThresholdPen(new.getPen(), threshold)
- wrappedPen = PointToSegmentPen(filterpen)
- aGlyph.drawPoints(wrappedPen)
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-def thresholdGlyphPointPen(aGlyph, threshold=10):
- """ Same a thresholdGlyph, but using the ThresholdPointPen, which should respect anchors."""
- from robofab.pens.adapterPens import PointToSegmentPen
- new = _RGlyph()
- wrappedPen = new.getPointPen()
- filterpen = ThresholdPointPen(wrappedPen, threshold)
- aGlyph.drawPoints(filterpen)
- aGlyph.clear()
- new.drawPoints(aGlyph.getPointPen())
- aGlyph.update()
- return aGlyph
-
-
-#
-# Curve flattening
-#
-
-def _estimateCubicCurveLength(pt0, pt1, pt2, pt3, precision=10):
- """Estimate the length of this curve by iterating
- through it and averaging the length of the flat bits.
- """
- points = []
- length = 0
- step = 1.0/precision
- factors = range(0, precision+1)
- for i in factors:
- points.append(_getCubicPoint(i*step, pt0, pt1, pt2, pt3))
- for i in range(len(points)-1):
- pta = points[i]
- ptb = points[i+1]
- length += distance(pta, ptb)
- return length
-
-def _mid((x0, y0), (x1, y1)):
- """(Point, Point) -> Point\nReturn the point that lies in between the two input points."""
- return 0.5 * (x0 + x1), 0.5 * (y0 + y1)
-
-def _getCubicPoint(t, pt0, pt1, pt2, pt3):
- if t == 0:
- return pt0
- if t == 1:
- return pt3
- if t == 0.5:
- a = _mid(pt0, pt1)
- b = _mid(pt1, pt2)
- c = _mid(pt2, pt3)
- d = _mid(a, b)
- e = _mid(b, c)
- return _mid(d, e)
- else:
- cx = (pt1[0] - pt0[0]) * 3
- cy = (pt1[1] - pt0[1]) * 3
- bx = (pt2[0] - pt1[0]) * 3 - cx
- by = (pt2[1] - pt1[1]) * 3 - cy
- ax = pt3[0] - pt0[0] - cx - bx
- ay = pt3[1] - pt0[1] - cy - by
- t3 = t ** 3
- t2 = t * t
- x = ax * t3 + bx * t2 + cx * t + pt0[0]
- y = ay * t3 + by * t2 + cy * t + pt0[1]
- return x, y
-
-
-class FlattenPen(BasePen):
-
- """Process the contours into a series of straight lines by flattening the curves.
- """
-
- def __init__(self, otherPen, approximateSegmentLength=5, segmentLines=False, filterDoubles=True):
- self.approximateSegmentLength = approximateSegmentLength
- BasePen.__init__(self, {})
- self.otherPen = otherPen
- self.currentPt = None
- self.firstPt = None
- self.segmentLines = segmentLines
- self.filterDoubles = filterDoubles
-
- def _moveTo(self, pt):
- self.otherPen.moveTo(pt)
- self.currentPt = pt
- self.firstPt = pt
-
- def _lineTo(self, pt):
- if self.filterDoubles:
- if pt == self.currentPt:
- return
- if not self.segmentLines:
- self.otherPen.lineTo(pt)
- self.currentPt = pt
- return
- d = distance(self.currentPt, pt)
- maxSteps = int(round(d / self.approximateSegmentLength))
- if maxSteps < 1:
- self.otherPen.lineTo(pt)
- self.currentPt = pt
- return
- step = 1.0/maxSteps
- factors = range(0, maxSteps+1)
- for i in factors[1:]:
- self.otherPen.lineTo(_interpolatePt(self.currentPt, pt, i*step))
- self.currentPt = pt
-
- def _curveToOne(self, pt1, pt2, pt3):
- est = _estimateCubicCurveLength(self.currentPt, pt1, pt2, pt3)/self.approximateSegmentLength
- maxSteps = int(round(est))
- falseCurve = (pt1==self.currentPt) and (pt2==pt3)
- if maxSteps < 1 or falseCurve:
- self.otherPen.lineTo(pt3)
- self.currentPt = pt3
- return
- step = 1.0/maxSteps
- factors = range(0, maxSteps+1)
- for i in factors[1:]:
- pt = _getCubicPoint(i*step, self.currentPt, pt1, pt2, pt3)
- self.otherPen.lineTo(pt)
- self.currentPt = pt3
-
- def _closePath(self):
- self.lineTo(self.firstPt)
- self.otherPen.closePath()
- self.currentPt = None
-
- def _endPath(self):
- self.otherPen.endPath()
- self.currentPt = None
-
- def addComponent(self, glyphName, transformation):
- self.otherPen.addComponent(glyphName, transformation)
-
-
-def flattenGlyph(aGlyph, threshold=10, segmentLines=True):
-
- """Replace curves with series of straight l ines."""
-
- from robofab.pens.adapterPens import PointToSegmentPen
- if len(aGlyph.contours) == 0:
- return
- new = _RGlyph()
- writerPen = new.getPen()
- filterpen = FlattenPen(writerPen, threshold, segmentLines)
- wrappedPen = PointToSegmentPen(filterpen)
- aGlyph.drawPoints(wrappedPen)
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-
-def spikeGlyph(aGlyph, segmentLength=20, spikeLength=40, patternFunc=None):
-
- """Add narly spikes or dents to the glyph.
- patternFunc is an optional function which recalculates the offset."""
-
- from math import atan2, sin, cos, pi
-
- new = _RGlyph()
- new.appendGlyph(aGlyph)
- new.width = aGlyph.width
-
- if len(new.contours) == 0:
- return
- flattenGlyph(new, segmentLength, segmentLines=True)
- for contour in new:
- l = len(contour.points)
- lastAngle = None
- for i in range(0, len(contour.points), 2):
- prev = contour.points[i-1]
- cur = contour.points[i]
- next = contour.points[(i+1)%l]
- angle = atan2(prev.x - next.x, prev.y - next.y)
- lastAngle = angle
- if patternFunc is not None:
- thisSpikeLength = patternFunc(spikeLength)
- else:
- thisSpikeLength = spikeLength
- cur.x -= sin(angle+.5*pi)*thisSpikeLength
- cur.y -= cos(angle+.5*pi)*thisSpikeLength
- new.update()
- aGlyph.clear()
- aGlyph.appendGlyph(new)
- aGlyph.update()
- return aGlyph
-
-
-def halftoneGlyph(aGlyph, invert=False):
-
- """Convert the glyph into some sort of halftoning pattern.
- Measure a bunch of inside/outside points to simulate grayscale levels.
- Slow.
- """
- print 'halftoneGlyph is running...'
- grid = {}
- drawing = {}
- dataDistance = 10
- scan = 2
- preload = 0
- cellDistance = dataDistance * 5
- overshoot = dataDistance * 2
- (xMin, yMin, xMax, yMax) = aGlyph.box
- for x in range(xMin-overshoot, xMax+overshoot, dataDistance):
- print 'scanning..', x
- for y in range(yMin-overshoot, yMax+overshoot, dataDistance):
- if aGlyph.pointInside((x, y)):
- grid[(x, y)] = True
- else:
- grid[(x, y)] = False
- #print 'gathering data', x, y, grid[(x, y)]
- print 'analyzing..'
- for x in range(xMin-overshoot, xMax+overshoot, cellDistance):
- for y in range(yMin-overshoot, yMax+overshoot, cellDistance):
- total = preload
- for scanx in range(-scan, scan):
- for scany in range(-scan, scan):
- if grid.get((x+scanx*dataDistance, y+scany*dataDistance)):
- total += 1
- if invert:
- drawing[(x, y)] = 2*scan**2 - float(total)
- else:
- drawing[(x, y)] = float(total)
- aGlyph.clear()
- print drawing
- for (x,y) in drawing.keys():
- size = drawing[(x,y)] / float(2*scan**2) * 5
- pen = aGlyph.getPen()
- pen.moveTo((x-size, y-size))
- pen.lineTo((x+size, y-size))
- pen.lineTo((x+size, y+size))
- pen.lineTo((x-size, y+size))
- pen.lineTo((x-size, y-size))
- pen.closePath()
- aGlyph.update()
-
-
-if __name__ == "__main__":
- from robofab.pens.pointPen import PrintingPointPen
- pp = PrintingPointPen()
- #pp.beginPath()
- #pp.addPoint((100, 100))
- #pp.endPath()
-
- tpp = ThresholdPointPen(pp, threshold=20)
- tpp.beginPath()
- #segmentType=None, smooth=False, name=None
- tpp.addPoint((100, 100), segmentType="line", smooth=True)
- # section that should be too small
- tpp.addPoint((100, 102), segmentType="line", smooth=True)
- tpp.addPoint((200, 200), segmentType="line", smooth=True)
- # curve section with final point that's far enough, but with offcurves that are under the threshold
- tpp.addPoint((200, 205), segmentType="curve", smooth=True)
- tpp.addPoint((300, 295), segmentType="curve", smooth=True)
- tpp.addPoint((300, 300), segmentType="line", smooth=True)
- # curve section with final point that is not far enough
- tpp.addPoint((550, 350), segmentType="curve", smooth=True)
- tpp.addPoint((360, 760), segmentType="curve", smooth=True)
- tpp.addPoint((310, 310), segmentType="line", smooth=True)
-
- tpp.addPoint((400, 400), segmentType="line", smooth=True)
- tpp.addPoint((100, 100), segmentType="line", smooth=True)
- tpp.endPath()
-
- # couple of single points with names
- tpp.beginPath()
- tpp.addPoint((500, 500), segmentType="move", smooth=True, name="named point")
- tpp.addPoint((600, 500), segmentType="move", smooth=True, name="named point")
- tpp.addPoint((601, 501), segmentType="move", smooth=True, name="named point")
- tpp.endPath()
-
- # open path
- tpp.beginPath()
- tpp.addPoint((500, 500), segmentType="move", smooth=True)
- tpp.addPoint((501, 500), segmentType="line", smooth=True)
- tpp.addPoint((101, 500), segmentType="line", smooth=True)
- tpp.addPoint((101, 100), segmentType="line", smooth=True)
- tpp.addPoint((498, 498), segmentType="line", smooth=True)
- tpp.endPath()
- \ No newline at end of file
diff --git a/misc/pylib/robofab/pens/flPen.py b/misc/pylib/robofab/pens/flPen.py
deleted file mode 100755
index d5a867cd9..000000000
--- a/misc/pylib/robofab/pens/flPen.py
+++ /dev/null
@@ -1,274 +0,0 @@
-"""Pens for creating glyphs in FontLab."""
-
-
-__all__ = ["FLPen", "FLPointPen", "drawFLGlyphOntoPointPen"]
-
-
-from FL import *
-
-try:
- from fl_cmd import *
-except ImportError:
- print "The fl_cmd module is not available here. flPen.py"
-
-from robofab.tools.toolsFL import NewGlyph
-from robofab.pens.pointPen import AbstractPointPen
-from robofab.pens.adapterPens import SegmentToPointPen
-
-
-def roundInt(x):
- return int(round(x))
-
-
-class FLPen(SegmentToPointPen):
-
- def __init__(self, glyph):
- SegmentToPointPen.__init__(self, FLPointPen(glyph))
-
-
-class FLPointPen(AbstractPointPen):
-
- def __init__(self, glyph):
- if hasattr(glyph, "isRobofab"):
- self.glyph = glyph.naked()
- else:
- self.glyph = glyph
- self.currentPath = None
-
- def beginPath(self):
- self.currentPath = []
-
- def endPath(self):
- # Love is... abstracting away FL's madness.
- path = self.currentPath
- self.currentPath = None
- glyph = self.glyph
- if len(path) == 1 and path[0][3] is not None:
- # Single point on the contour, it has a name. Make it an anchor.
- x, y = path[0][0]
- name = path[0][3]
- anchor = Anchor(name, roundInt(x), roundInt(y))
- glyph.anchors.append(anchor)
- return
- firstOnCurveIndex = None
- for i in range(len(path)):
- if path[i][1] is not None:
- firstOnCurveIndex = i
- break
- if firstOnCurveIndex is None:
- # TT special case: on-curve-less contour. FL doesn't support that,
- # so we insert an implied point at the end.
- x1, y1 = path[0][0]
- x2, y2 = path[-1][0]
- impliedPoint = 0.5 * (x1 + x2), 0.5 * (y1 + y2)
- path.append((impliedPoint, "qcurve", True, None))
- firstOnCurveIndex = 0
- path = path[firstOnCurveIndex + 1:] + path[:firstOnCurveIndex + 1]
- firstPoint, segmentType, smooth, name = path[-1]
- closed = True
- if segmentType == "move":
- path = path[:-1]
- closed = False
- # XXX The contour is not closed, but I can't figure out how to
- # create an open contour in FL. Creating one by hand shows type"0x8011"
- # for a move node in an open contour, but I'm not able to access
- # that flag.
- elif segmentType == "line":
- # The contour is closed and ends in a lineto, which is redundant
- # as it's implied by closepath.
- path = path[:-1]
- x, y = firstPoint
- node = Node(nMOVE, Point(roundInt(x), roundInt(y)))
- if smooth and closed:
- if segmentType == "line" or path[0][1] == "line":
- node.alignment = nFIXED
- else:
- node.alignment = nSMOOTH
- glyph.Insert(node, len(glyph))
- segment = []
- nPoints = len(path)
- for i in range(nPoints):
- pt, segmentType, smooth, name = path[i]
- segment.append(pt)
- if segmentType is None:
- continue
- if segmentType == "curve":
- if len(segment) < 2:
- segmentType = "line"
- elif len(segment) == 2:
- segmentType = "qcurve"
- if segmentType == "qcurve":
- for x, y in segment[:-1]:
- glyph.Insert(Node(nOFF, Point(roundInt(x), roundInt(y))), len(glyph))
- x, y = segment[-1]
- node = Node(nLINE, Point(roundInt(x), roundInt(y)))
- glyph.Insert(node, len(glyph))
- elif segmentType == "curve":
- if len(segment) == 3:
- cubicSegments = [segment]
- else:
- from fontTools.pens.basePen import decomposeSuperBezierSegment
- cubicSegments = decomposeSuperBezierSegment(segment)
- nSegments = len(cubicSegments)
- for i in range(nSegments):
- pt1, pt2, pt3 = cubicSegments[i]
- x, y = pt3
- node = Node(nCURVE, Point(roundInt(x), roundInt(y)))
- node.points[1].x, node.points[1].y = roundInt(pt1[0]), roundInt(pt1[1])
- node.points[2].x, node.points[2].y = roundInt(pt2[0]), roundInt(pt2[1])
- if i != nSegments - 1:
- node.alignment = nSMOOTH
- glyph.Insert(node, len(self.glyph))
- elif segmentType == "line":
- assert len(segment) == 1, segment
- x, y = segment[0]
- node = Node(nLINE, Point(roundInt(x), roundInt(y)))
- glyph.Insert(node, len(glyph))
- else:
- assert 0, "unsupported curve type (%s)" % segmentType
- if smooth:
- if i + 1 < nPoints or closed:
- # Can't use existing node, as you can't change node attributes
- # AFTER it's been appended to the glyph.
- node = glyph[-1]
- if segmentType == "line" or path[(i+1) % nPoints][1] == "line":
- # tangent
- node.alignment = nFIXED
- else:
- # curve
- node.alignment = nSMOOTH
- segment = []
- if closed:
- # we may have output a node too much
- node = glyph[-1]
- if node.type == nLINE and (node.x, node.y) == (roundInt(firstPoint[0]), roundInt(firstPoint[1])):
- glyph.DeleteNode(len(glyph) - 1)
-
- def addPoint(self, pt, segmentType=None, smooth=None, name=None, **kwargs):
- self.currentPath.append((pt, segmentType, smooth, name))
-
- def addComponent(self, baseName, transformation):
- assert self.currentPath is None
- # make base glyph if needed, Component() needs the index
- NewGlyph(self.glyph.parent, baseName, updateFont=False)
- baseIndex = self.glyph.parent.FindGlyph(baseName)
- if baseIndex == -1:
- raise KeyError, "couldn't find or make base glyph"
- xx, xy, yx, yy, dx, dy = transformation
- # XXX warn when xy or yx != 0
- new = Component(baseIndex, Point(dx, dy), Point(xx, yy))
- self.glyph.components.append(new)
-
-
-def drawFLGlyphOntoPointPen(flGlyph, pen):
- """Draw a FontLab glyph onto a PointPen."""
- for anchor in flGlyph.anchors:
- pen.beginPath()
- pen.addPoint((anchor.x, anchor.y), name=anchor.name)
- pen.endPath()
- for contour in _getContours(flGlyph):
- pen.beginPath()
- for pt, segmentType, smooth in contour:
- pen.addPoint(pt, segmentType=segmentType, smooth=smooth)
- pen.endPath()
- for baseGlyph, tranform in _getComponents(flGlyph):
- pen.addComponent(baseGlyph, tranform)
-
-
-
-class FLPointContourPen(FLPointPen):
- """Same as FLPointPen, except that it ignores components."""
- def addComponent(self, baseName, transformation):
- pass
-
-
-NODE_TYPES = {nMOVE: "move", nLINE: "line", nCURVE: "curve",
- nOFF: None}
-
-def _getContours(glyph):
- contours = []
- for i in range(len(glyph)):
- node = glyph[i]
- segmentType = NODE_TYPES[node.type]
- if segmentType == "move":
- contours.append([])
- for pt in node.points[1:]:
- contours[-1].append(((pt.x, pt.y), None, False))
- smooth = node.alignment != nSHARP
- contours[-1].append(((node.x, node.y), segmentType, smooth))
-
- for contour in contours:
- # filter out or change the move
- movePt, segmentType, smooth = contour[0]
- assert segmentType == "move"
- lastSegmentType = contour[-1][1]
- if movePt == contour[-1][0] and lastSegmentType == "curve":
- contour[0] = contour[-1]
- contour.pop()
- elif lastSegmentType is None:
- contour[0] = movePt, "qcurve", smooth
- else:
- assert lastSegmentType in ("line", "curve")
- contour[0] = movePt, "line", smooth
-
- # change "line" to "qcurve" if appropriate
- previousSegmentType = "ArbitraryValueOtherThanNone"
- for i in range(len(contour)):
- pt, segmentType, smooth = contour[i]
- if segmentType == "line" and previousSegmentType is None:
- contour[i] = pt, "qcurve", smooth
- previousSegmentType = segmentType
-
- return contours
-
-
-def _simplifyValues(*values):
- """Given a set of numbers, convert items to ints if they are
- integer float values, eg. 0.0, 1.0."""
- newValues = []
- for v in values:
- i = int(v)
- if v == i:
- v = i
- newValues.append(v)
- return newValues
-
-
-def _getComponents(glyph):
- components = []
- for comp in glyph.components:
- baseName = glyph.parent[comp.index].name
- dx, dy = comp.delta.x, comp.delta.y
- sx, sy = comp.scale.x, comp.scale.y
- dx, dy, sx, sy = _simplifyValues(dx, dy, sx, sy)
- components.append((baseName, (sx, 0, 0, sy, dx, dy)))
- return components
-
-
-def test():
- g = fl.glyph
- g.Clear()
-
- p = PLPen(g)
- p.moveTo((50, 50))
- p.lineTo((150,50))
- p.lineTo((170, 200), smooth=2)
- p.curveTo((173, 225), (150, 250), (120, 250), smooth=1)
- p.curveTo((85, 250), (50, 200), (50, 200))
- p.closePath()
-
- p.moveTo((300, 300))
- p.lineTo((400, 300))
- p.curveTo((450, 325), (450, 375), (400, 400))
- p.qCurveTo((400, 500), (350, 550), (300, 500), (300, 400))
- p.closePath()
- p.setWidth(600)
- p.setNote("Hello, this is a note")
- p.addAnchor("top", (250, 600))
-
- fl.UpdateGlyph(-1)
- fl.UpdateFont(-1)
-
-
-if __name__ == "__main__":
- test()
diff --git a/misc/pylib/robofab/pens/marginPen.py b/misc/pylib/robofab/pens/marginPen.py
deleted file mode 100644
index 03f13f917..000000000
--- a/misc/pylib/robofab/pens/marginPen.py
+++ /dev/null
@@ -1,155 +0,0 @@
-from fontTools.pens.basePen import AbstractPen, BasePen
-from robofab.misc.bezierTools import splitLine, splitCubic
-
-
-from sets import Set
-
-class MarginPen(BasePen):
-
- """
- Pen to calculate the margins at a given value.
- When isHorizontal is True, the margins at <value> are horizontal.
- When isHorizontal is False, the margins at <value> are vertical.
-
- When a glyphset or font is given, MarginPen will also calculate for glyphs with components.
-
- pen.getMargins() returns the minimum and maximum intersections of the glyph.
- pen.getContourMargins() returns the minimum and maximum intersections for each contour.
-
-
- Possible optimisation:
- Initialise the pen object with a list of points we want to measure,
- then draw the glyph once, but do the splitLine() math for all measure points.
-
- """
-
- def __init__(self, glyphSet, value, isHorizontal=True):
- BasePen.__init__(self, glyphSet)
- self.value = value
- self.hits = {}
- self.filterDoubles = True
- self.contourIndex = None
- self.startPt = None
- self.isHorizontal = isHorizontal
-
- def _moveTo(self, pt):
- self.currentPt = pt
- self.startPt = pt
- if self.contourIndex is None:
- self.contourIndex = 0
- else:
- self.contourIndex += 1
-
- def _lineTo(self, pt):
- if self.filterDoubles:
- if pt == self.currentPt:
- return
- hits = splitLine(self.currentPt, pt, self.value, self.isHorizontal)
- if len(hits)>1:
- # result will be 2 tuples of 2 coordinates
- # first two points: start to intersect
- # second two points: intersect to end
- # so, second point in first tuple is the intersect
- # then, the first coordinate of that point is the x.
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- if self.isHorizontal:
- self.hits[self.contourIndex].append(round(hits[0][-1][0], 4))
- else:
- self.hits[self.contourIndex].append(round(hits[0][-1][1], 4))
- if self.isHorizontal and pt[1] == self.value:
- # it could happen
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- self.hits[self.contourIndex].append(pt[0])
- elif (not self.isHorizontal) and (pt[0] == self.value):
- # it could happen
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- self.hits[self.contourIndex].append(pt[1])
- self.currentPt = pt
-
- def _curveToOne(self, pt1, pt2, pt3):
- hits = splitCubic(self.currentPt, pt1, pt2, pt3, self.value, self.isHorizontal)
- for i in range(len(hits)-1):
- # a number of intersections is possible. Just take the
- # last point of each segment.
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- if self.isHorizontal:
- self.hits[self.contourIndex].append(round(hits[i][-1][0], 4))
- else:
- self.hits[self.contourIndex].append(round(hits[i][-1][1], 4))
- if self.isHorizontal and pt3[1] == self.value:
- # it could happen
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- self.hits[self.contourIndex].append(pt3[0])
- if (not self.isHorizontal) and (pt3[0] == self.value):
- # it could happen
- if not self.contourIndex in self.hits:
- self.hits[self.contourIndex] = []
- self.hits[self.contourIndex].append(pt3[1])
- self.currentPt = pt3
-
- def _closePath(self):
- if self.currentPt != self.startPt:
- self._lineTo(self.startPt)
- self.currentPt = self.startPt = None
-
- def _endPath(self):
- self.currentPt = None
-
- def addComponent(self, baseGlyph, transformation):
- from fontTools.pens.transformPen import TransformPen
- if self.glyphSet is None:
- return
- if baseGlyph in self.glyphSet:
- glyph = self.glyphSet[baseGlyph]
- if glyph is None:
- return
- tPen = TransformPen(self, transformation)
- glyph.draw(tPen)
-
- def getMargins(self):
- """Get the horizontal margins for all contours combined, i.e. the whole glyph."""
- allHits = []
- for index, pts in self.hits.items():
- allHits.extend(pts)
- if allHits:
- return min(allHits), max(allHits)
- return None
-
- def getContourMargins(self):
- """Get the horizontal margins for each contour."""
- allHits = {}
- for index, pts in self.hits.items():
- unique = list(Set(pts))
- unique.sort()
- allHits[index] = unique
- return allHits
-
- def getAll(self):
- """Get all the slices."""
- allHits = []
- for index, pts in self.hits.items():
- allHits.extend(pts)
- unique = list(Set(allHits))
- unique = list(unique)
- unique.sort()
- return unique
-
-
-if __name__ == "__main__":
-
- from robofab.world import CurrentGlyph, CurrentFont
- f = CurrentFont()
- g = CurrentGlyph()
-
- pt = (74, 216)
-
- pen = MarginPen(f, pt[1], isHorizontal=False)
- g.draw(pen)
- print 'glyph Y margins', pen.getMargins()
- print pen.getContourMargins()
-
diff --git a/misc/pylib/robofab/pens/mathPens.py b/misc/pylib/robofab/pens/mathPens.py
deleted file mode 100755
index 1fe1026e1..000000000
--- a/misc/pylib/robofab/pens/mathPens.py
+++ /dev/null
@@ -1,185 +0,0 @@
-"""Some pens that are needed during glyph math"""
-
-
-from robofab.pens.pointPen import BasePointToSegmentPen, AbstractPointPen
-
-
-class GetMathDataPointPen(AbstractPointPen):
-
- """
- Point pen that converts all "line" segments into
- curve segments containing two off curve points.
- """
-
- def __init__(self):
- self.contours = []
- self.components = []
- self.anchors = []
- self._points = []
-
- def _flushContour(self):
- points = self._points
- if len(points) == 1:
- segmentType, pt, smooth, name = points[0]
- self.anchors.append((pt, name))
- else:
- self.contours.append([])
- prevOnCurve = None
- offCurves = []
- # deal with the first point
- segmentType, pt, smooth, name = points[0]
- # if it is an offcurve, add it to the offcurve list
- if segmentType is None:
- offCurves.append((segmentType, pt, smooth, name))
- # if it is a line, change the type to curve and add it to the contour
- # create offcurves corresponding with the last oncurve and
- # this point and add them to the points list
- elif segmentType == "line":
- prevOnCurve = pt
- self.contours[-1].append(("curve", pt, False, name))
- lastPoint = points[-1][1]
- points.append((None, lastPoint, False, None))
- points.append((None, pt, False, None))
- # a move, curve or qcurve. simple append the data.
- else:
- self.contours[-1].append((segmentType, pt, smooth, name))
- prevOnCurve = pt
- # now go through the rest of the points
- for segmentType, pt, smooth, name in points[1:]:
- # store the off curves
- if segmentType is None:
- offCurves.append((segmentType, pt, smooth, name))
- continue
- # make off curve corresponding the the previous
- # on curve an dthis point
- if segmentType == "line":
- segmentType = "curve"
- offCurves.append((None, prevOnCurve, False, None))
- offCurves.append((None, pt, False, None))
- # add the offcurves to the contour
- for offCurve in offCurves:
- self.contours[-1].append(offCurve)
- # add the oncurve to the contour
- self.contours[-1].append((segmentType, pt, smooth, name))
- # reset the stored data
- prevOnCurve = pt
- offCurves = []
- # catch offcurves that belong to the first
- if len(offCurves) != 0:
- self.contours[-1].extend(offCurves)
-
- def beginPath(self):
- self._points = []
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self._points.append((segmentType, pt, smooth, name))
-
- def endPath(self):
- self._flushContour()
-
- def addComponent(self, baseGlyphName, transformation):
- self.components.append((baseGlyphName, transformation))
-
- def getData(self):
- return {
- 'contours':list(self.contours),
- 'components':list(self.components),
- 'anchors':list(self.anchors)
- }
-
-
-class CurveSegmentFilterPointPen(AbstractPointPen):
-
- """
- Point pen that turns curve segments that contain
- unnecessary anchor points into line segments.
- """
- # XXX it would be great if this also checked to see if the
- # off curves are on the segment and therefre unneeded
-
- def __init__(self, anotherPointPen):
- self._pen = anotherPointPen
- self._points = []
-
- def _flushContour(self):
- points = self._points
- # an anchor
- if len(points) == 1:
- pt, segmentType, smooth, name = points[0]
- self._pen.addPoint(pt, segmentType, smooth, name)
- else:
- prevOnCurve = None
- offCurves = []
-
- pointsToDraw = []
-
- # deal with the first point
- pt, segmentType, smooth, name = points[0]
- # if it is an offcurve, add it to the offcurve list
- if segmentType is None:
- offCurves.append((pt, segmentType, smooth, name))
- else:
- # potential redundancy
- if segmentType == "curve":
- # gather preceding off curves
- testOffCurves = []
- lastPoint = None
- for i in xrange(len(points)):
- i = -i - 1
- testPoint = points[i]
- testSegmentType = testPoint[1]
- if testSegmentType is not None:
- lastPoint = testPoint[0]
- break
- testOffCurves.append(testPoint[0])
- # if two offcurves exist we can test for redundancy
- if len(testOffCurves) == 2:
- if testOffCurves[1] == lastPoint and testOffCurves[0] == pt:
- segmentType = "line"
- # remove the last two points
- points = points[:-2]
- # add the point to the contour
- pointsToDraw.append((pt, segmentType, smooth, name))
- prevOnCurve = pt
- for pt, segmentType, smooth, name in points[1:]:
- # store offcurves
- if segmentType is None:
- offCurves.append((pt, segmentType, smooth, name))
- continue
- # curves are a potential redundancy
- elif segmentType == "curve":
- if len(offCurves) == 2:
- # test for redundancy
- if offCurves[0][0] == prevOnCurve and offCurves[1][0] == pt:
- offCurves = []
- segmentType = "line"
- # add all offcurves
- for offCurve in offCurves:
- pointsToDraw.append(offCurve)
- # add the on curve
- pointsToDraw.append((pt, segmentType, smooth, name))
- # reset the stored data
- prevOnCurve = pt
- offCurves = []
- # catch any remaining offcurves
- if len(offCurves) != 0:
- for offCurve in offCurves:
- pointsToDraw.append(offCurve)
- # draw to the pen
- for pt, segmentType, smooth, name in pointsToDraw:
- self._pen.addPoint(pt, segmentType, smooth, name)
-
- def beginPath(self):
- self._points = []
- self._pen.beginPath()
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self._points.append((pt, segmentType, smooth, name))
-
- def endPath(self):
- self._flushContour()
- self._pen.endPath()
-
- def addComponent(self, baseGlyphName, transformation):
- self._pen.addComponent(baseGlyphName, transformation)
-
diff --git a/misc/pylib/robofab/pens/pointPen.py b/misc/pylib/robofab/pens/pointPen.py
deleted file mode 100644
index dc81df38e..000000000
--- a/misc/pylib/robofab/pens/pointPen.py
+++ /dev/null
@@ -1,173 +0,0 @@
-__all__ = ["AbstractPointPen", "BasePointToSegmentPen", "PrintingPointPen",
- "PrintingSegmentPen", "SegmentPrintingPointPen"]
-
-
-class AbstractPointPen:
-
- def beginPath(self):
- """Start a new sub path."""
- raise NotImplementedError
-
- def endPath(self):
- """End the current sub path."""
- raise NotImplementedError
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- """Add a point to the current sub path."""
- raise NotImplementedError
-
- def addComponent(self, baseGlyphName, transformation):
- """Add a sub glyph."""
- raise NotImplementedError
-
-
-class BasePointToSegmentPen(AbstractPointPen):
-
- """Base class for retrieving the outline in a segment-oriented
- way. The PointPen protocol is simple yet also a little tricky,
- so when you need an outline presented as segments but you have
- as points, do use this base implementation as it properly takes
- care of all the edge cases.
- """
-
- def __init__(self):
- self.currentPath = None
-
- def beginPath(self):
- assert self.currentPath is None
- self.currentPath = []
-
- def _flushContour(self, segments):
- """Override this method.
-
- It will be called for each non-empty sub path with a list
- of segments: the 'segments' argument.
-
- The segments list contains tuples of length 2:
- (segmentType, points)
-
- segmentType is one of "move", "line", "curve" or "qcurve".
- "move" may only occur as the first segment, and it signifies
- an OPEN path. A CLOSED path does NOT start with a "move", in
- fact it will not contain a "move" at ALL.
-
- The 'points' field in the 2-tuple is a list of point info
- tuples. The list has 1 or more items, a point tuple has
- four items:
- (point, smooth, name, kwargs)
- 'point' is an (x, y) coordinate pair.
-
- For a closed path, the initial moveTo point is defined as
- the last point of the last segment.
-
- The 'points' list of "move" and "line" segments always contains
- exactly one point tuple.
- """
- raise NotImplementedError
-
- def endPath(self):
- assert self.currentPath is not None
- points = self.currentPath
- self.currentPath = None
- if not points:
- return
- if len(points) == 1:
- # Not much more we can do than output a single move segment.
- pt, segmentType, smooth, name, kwargs = points[0]
- segments = [("move", [(pt, smooth, name, kwargs)])]
- self._flushContour(segments)
- return
- segments = []
- if points[0][1] == "move":
- # It's an open contour, insert a "move" segment for the first
- # point and remove that first point from the point list.
- pt, segmentType, smooth, name, kwargs = points[0]
- segments.append(("move", [(pt, smooth, name, kwargs)]))
- points.pop(0)
- else:
- # It's a closed contour. Locate the first on-curve point, and
- # rotate the point list so that it _ends_ with an on-curve
- # point.
- firstOnCurve = None
- for i in range(len(points)):
- segmentType = points[i][1]
- if segmentType is not None:
- firstOnCurve = i
- break
- if firstOnCurve is None:
- # Special case for quadratics: a contour with no on-curve
- # points. Add a "None" point. (See also the Pen protocol's
- # qCurveTo() method and fontTools.pens.basePen.py.)
- points.append((None, "qcurve", None, None, None))
- else:
- points = points[firstOnCurve+1:] + points[:firstOnCurve+1]
-
- currentSegment = []
- for pt, segmentType, smooth, name, kwargs in points:
- currentSegment.append((pt, smooth, name, kwargs))
- if segmentType is None:
- continue
- segments.append((segmentType, currentSegment))
- currentSegment = []
-
- self._flushContour(segments)
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self.currentPath.append((pt, segmentType, smooth, name, kwargs))
-
-
-class PrintingPointPen(AbstractPointPen):
- def __init__(self):
- self.havePath = False
- def beginPath(self):
- self.havePath = True
- print "pen.beginPath()"
- def endPath(self):
- self.havePath = False
- print "pen.endPath()"
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- assert self.havePath
- args = ["(%s, %s)" % (pt[0], pt[1])]
- if segmentType is not None:
- args.append("segmentType=%r" % segmentType)
- if smooth:
- args.append("smooth=True")
- if name is not None:
- args.append("name=%r" % name)
- if kwargs:
- args.append("**%s" % kwargs)
- print "pen.addPoint(%s)" % ", ".join(args)
- def addComponent(self, baseGlyphName, transformation):
- assert not self.havePath
- print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))
-
-
-from fontTools.pens.basePen import AbstractPen
-
-class PrintingSegmentPen(AbstractPen):
- def moveTo(self, pt):
- print "pen.moveTo(%s)" % (pt,)
- def lineTo(self, pt):
- print "pen.lineTo(%s)" % (pt,)
- def curveTo(self, *pts):
- print "pen.curveTo%s" % (pts,)
- def qCurveTo(self, *pts):
- print "pen.qCurveTo%s" % (pts,)
- def closePath(self):
- print "pen.closePath()"
- def endPath(self):
- print "pen.endPath()"
- def addComponent(self, baseGlyphName, transformation):
- print "pen.addComponent(%r, %s)" % (baseGlyphName, tuple(transformation))
-
-
-class SegmentPrintingPointPen(BasePointToSegmentPen):
- def _flushContour(self, segments):
- from pprint import pprint
- pprint(segments)
-
-
-if __name__ == "__main__":
- p = SegmentPrintingPointPen()
- from robofab.test.test_pens import TestShapes
- TestShapes.onCurveLessQuadShape(p)
diff --git a/misc/pylib/robofab/pens/quartzPen.py b/misc/pylib/robofab/pens/quartzPen.py
deleted file mode 100644
index dd1947ccf..000000000
--- a/misc/pylib/robofab/pens/quartzPen.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from fontTools.pens.basePen import BasePen
-
-class QuartzPen(BasePen):
-
- """Pen to draw onto a Quartz drawing context (Carbon.CG)."""
-
- def __init__(self, glyphSet, quartzContext):
- BasePen.__init__(self, glyphSet)
- self._context = quartzContext
-
- def _moveTo(self, (x, y)):
- self._context.CGContextMoveToPoint(x, y)
-
- def _lineTo(self, (x, y)):
- self._context.CGContextAddLineToPoint(x, y)
-
- def _curveToOne(self, (x1, y1), (x2, y2), (x3, y3)):
- self._context.CGContextAddCurveToPoint(x1, y1, x2, y2, x3, y3)
-
- def _closePath(self):
- self._context.closePath()
diff --git a/misc/pylib/robofab/pens/reverseContourPointPen.py b/misc/pylib/robofab/pens/reverseContourPointPen.py
deleted file mode 100755
index 8ce001b4d..000000000
--- a/misc/pylib/robofab/pens/reverseContourPointPen.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""PointPen for reversing the winding direction of contours."""
-
-
-__all__ = ["ReverseContourPointPen"]
-
-
-from robofab.pens.pointPen import AbstractPointPen
-
-
-class ReverseContourPointPen(AbstractPointPen):
-
- """This is a PointPen that passes outline data to another PointPen, but
- reversing the winding direction of all contours. Components are simply
- passed through unchanged.
-
- Closed contours are reversed in such a way that the first point remains
- the first point.
- """
-
- def __init__(self, outputPointPen):
- self.pen = outputPointPen
- self.currentContour = None # a place to store the points for the current sub path
-
- def _flushContour(self):
- pen = self.pen
- contour = self.currentContour
- if not contour:
- pen.beginPath()
- pen.endPath()
- return
-
- closed = contour[0][1] != "move"
- if not closed:
- lastSegmentType = "move"
- else:
- # Remove the first point and insert it at the end. When
- # the list of points gets reversed, this point will then
- # again be at the start. In other words, the following
- # will hold:
- # for N in range(len(originalContour)):
- # originalContour[N] == reversedContour[-N]
- contour.append(contour.pop(0))
- # Find the first on-curve point.
- firstOnCurve = None
- for i in range(len(contour)):
- if contour[i][1] is not None:
- firstOnCurve = i
- break
- if firstOnCurve is None:
- # There are no on-curve points, be basically have to
- # do nothing but contour.reverse().
- lastSegmentType = None
- else:
- lastSegmentType = contour[firstOnCurve][1]
-
- contour.reverse()
- if not closed:
- # Open paths must start with a move, so we simply dump
- # all off-curve points leading up to the first on-curve.
- while contour[0][1] is None:
- contour.pop(0)
- pen.beginPath()
- for pt, nextSegmentType, smooth, name in contour:
- if nextSegmentType is not None:
- segmentType = lastSegmentType
- lastSegmentType = nextSegmentType
- else:
- segmentType = None
- pen.addPoint(pt, segmentType=segmentType, smooth=smooth, name=name)
- pen.endPath()
-
- def beginPath(self):
- assert self.currentContour is None
- self.currentContour = []
- self.onCurve = []
-
- def endPath(self):
- assert self.currentContour is not None
- self._flushContour()
- self.currentContour = None
-
- def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
- self.currentContour.append((pt, segmentType, smooth, name))
-
- def addComponent(self, glyphName, transform):
- assert self.currentContour is None
- self.pen.addComponent(glyphName, transform)
-
-
-if __name__ == "__main__":
- from robofab.pens.pointPen import PrintingPointPen
- pP = PrintingPointPen()
- rP = ReverseContourPointPen(pP)
-
- rP.beginPath()
- rP.addPoint((339, -8), "curve")
- rP.addPoint((502, -8))
- rP.addPoint((635, 65))
- rP.addPoint((635, 305), "curve")
- rP.addPoint((635, 545))
- rP.addPoint((504, 623))
- rP.addPoint((340, 623), "curve")
- rP.addPoint((177, 623))
- rP.addPoint((43, 545))
- rP.addPoint((43, 305), "curve")
- rP.addPoint((43, 65))
- rP.addPoint((176, -8))
- rP.endPath()
-
- rP.beginPath()
- rP.addPoint((100, 100), "move", smooth=False, name='a')
- rP.addPoint((150, 150))
- rP.addPoint((200, 200))
- rP.addPoint((250, 250), "curve", smooth=True, name='b')
- rP.addPoint((300, 300), "line", smooth=False, name='c')
- rP.addPoint((350, 350))
- rP.addPoint((400, 400))
- rP.addPoint((450, 450))
- rP.addPoint((500, 500), "curve", smooth=True, name='d')
- rP.addPoint((550, 550))
- rP.addPoint((600, 600))
- rP.addPoint((650, 650))
- rP.addPoint((700, 700))
- rP.addPoint((750, 750), "qcurve", smooth=False, name='e')
- rP.endPath()
diff --git a/misc/pylib/robofab/pens/rfUFOPen.pyx b/misc/pylib/robofab/pens/rfUFOPen.pyx
deleted file mode 100755
index 265d7aea0..000000000
--- a/misc/pylib/robofab/pens/rfUFOPen.pyx
+++ /dev/null
@@ -1,103 +0,0 @@
-"""Pens for creating UFO glyphs."""
-
-from robofab.objects.objectsBase import MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE
-from robofab.objects.objectsRF import RContour, RSegment, RPoint
-from robofab.pens.pointPen import BasePointToSegmentPen
-from robofab.pens.adapterPens import SegmentToPointPen
-
-
-class RFUFOPen(SegmentToPointPen):
-
- def __init__(self, glyph):
- SegmentToPointPen.__init__(self, RFUFOPointPen(glyph))
-
-
-class RFUFOPointPen(BasePointToSegmentPen):
-
- """Point pen for building objectsRF glyphs"""
-
- def __init__(self, glyph):
- BasePointToSegmentPen.__init__(self)
- self.glyph = glyph
-
- def _flushContour(self, segments):
- #
- # adapted from robofab.pens.adapterPens.PointToSegmentPen
- #
- 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
- # we must have a contour
- contour = RContour()
- contour.setParent(self.glyph)
- if segments[0][0] == "move":
- # It's an open path.
- closed = False
- points = segments[0][1]
- assert len(points) == 1
- movePt, smooth, name, kwargs = points[0]
- del segments[0]
- else:
- # It's a closed path, do a moveTo to the last
- # point of the last segment. only if it isn't a qcurve
- closed = True
- segmentType, points = segments[-1]
- movePt, smooth, name, kwargs = points[-1]
- ## THIS IS STILL UNDECIDED!!!
- # since objectsRF currently follows the FL model of not
- # allowing open contours, remove the last segment
- # since it is being replaced by a move
- if segmentType == 'line':
- del segments[-1]
- # construct a move segment and apply it to the contour if we aren't dealing with a qcurve
- segment = RSegment()
- segment.setParent(contour)
- segment.smooth = smooth
- rPoint = RPoint(x=movePt[0], y=movePt[1], pointType=MOVE, name=name)
- rPoint.setParent(segment)
- segment.points = [rPoint]
- contour.segments.append(segment)
- # do the rest of the segments
- for segmentType, points in segments:
- points = [(pt, name) for pt, smooth, name, kwargs in points]
- if segmentType == "line":
- assert len(points) == 1
- sType = LINE
- elif segmentType == "curve":
- sType = CURVE
- elif segmentType == "qcurve":
- sType = QCURVE
- else:
- assert 0, "illegal segmentType: %s" % segmentType
- segment = RSegment()
- segment.setParent(contour)
- segment.smooth = smooth
- rPoints = []
- # handle the offCurves
- for point in points[:-1]:
- point, name = point
- rPoint = RPoint(x=point[0], y=point[1], pointType=OFFCURVE, name=name)
- rPoint.setParent(segment)
- rPoints.append(rPoint)
- # now the onCurve
- point, name = points[-1]
- rPoint = RPoint(x=point[0], y=point[1], pointType=sType, name=name)
- rPoint.setParent(segment)
- rPoints.append(rPoint)
- # apply them to the segment
- segment.points = rPoints
- contour.segments.append(segment)
- if contour.segments[-1].type == "curve":
- contour.segments[-1].points[-1].name = None
- self.glyph.contours.append(contour)
-
- def addComponent(self, glyphName, transform):
- xx, xy, yx, yy, dx, dy = transform
- self.glyph.appendComponent(baseGlyph=glyphName, offset=(dx, dy), scale=(xx, yy))
-
-
diff --git a/misc/pylib/robofab/plistFromTree.py b/misc/pylib/robofab/plistFromTree.py
deleted file mode 100755
index 5bf608467..000000000
--- a/misc/pylib/robofab/plistFromTree.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""Small helper module to parse Plist-formatted data from trees as created
-by xmlTreeBuilder.
-"""
-
-
-__all__ = "readPlistFromTree"
-
-
-from plistlib import PlistParser
-
-
-def readPlistFromTree(tree):
- """Given a (sub)tree created by xmlTreeBuilder, interpret it
- as Plist-formatted data, and return the root object.
- """
- parser = PlistTreeParser()
- return parser.parseTree(tree)
-
-
-class PlistTreeParser(PlistParser):
-
- def parseTree(self, tree):
- element, attributes, children = tree
- self.parseElement(element, attributes, children)
- return self.root
-
- def parseElement(self, element, attributes, children):
- self.handleBeginElement(element, attributes)
- for child in children:
- if isinstance(child, tuple):
- self.parseElement(child[0], child[1], child[2])
- else:
- if not isinstance(child, unicode):
- # ugh, xmlTreeBuilder returns utf-8 :-(
- child = unicode(child, "utf-8")
- self.handleData(child)
- self.handleEndElement(element)
-
-
-if __name__ == "__main__":
- from xmlTreeBuilder import buildTree
- tree = buildTree("xxx.plist", stripData=0)
- print readPlistFromTree(tree)
diff --git a/misc/pylib/robofab/plistlib.py b/misc/pylib/robofab/plistlib.py
deleted file mode 100755
index 282b7ea3e..000000000
--- a/misc/pylib/robofab/plistlib.py
+++ /dev/null
@@ -1,495 +0,0 @@
-"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
-
-The PropertList (.plist) file format is a simple XML pickle supporting
-basic object types, like dictionaries, lists, numbers and strings.
-Usually the top level object is a dictionary.
-
-To write out a plist file, use the writePlist(rootObject, pathOrFile)
-function. 'rootObject' is the top level object, 'pathOrFile' is a
-filename or a (writable) file object.
-
-To parse a plist from a file, use the readPlist(pathOrFile) function,
-with a file name or a (readable) file object as the only argument. It
-returns the top level object (again, usually a dictionary).
-
-To work with plist data in strings, you can use readPlistFromString()
-and writePlistToString().
-
-Values can be strings, integers, floats, booleans, tuples, lists,
-dictionaries, Data or datetime.datetime objects. String values (including
-dictionary keys) may be unicode strings -- they will be written out as
-UTF-8.
-
-The <data> plist type is supported through the Data class. This is a
-thin wrapper around a Python string.
-
-Generate Plist example:
-
- pl = dict(
- aString="Doodah",
- aList=["A", "B", 12, 32.1, [1, 2, 3]],
- aFloat = 0.1,
- anInt = 728,
- aDict=dict(
- anotherString="<hello & hi there!>",
- aUnicodeValue=u'M\xe4ssig, Ma\xdf',
- aTrueValue=True,
- aFalseValue=False,
- ),
- someData = Data("<binary gunk>"),
- someMoreData = Data("<lots of binary gunk>" * 10),
- aDate = datetime.fromtimestamp(time.mktime(time.gmtime())),
- )
- # unicode keys are possible, but a little awkward to use:
- pl[u'\xc5benraa'] = "That was a unicode key."
- writePlist(pl, fileName)
-
-Parse Plist example:
-
- pl = readPlist(pathOrFile)
- print pl["aKey"]
-"""
-
-
-__all__ = [
- "readPlist", "writePlist", "readPlistFromString", "writePlistToString",
- "readPlistFromResource", "writePlistToResource",
- "Plist", "Data", "Dict"
-]
-# Note: the Plist and Dict classes have been deprecated.
-
-import binascii
-from cStringIO import StringIO
-import re
-try:
- from datetime import datetime
-except ImportError:
- # We're running on Python < 2.3, we don't support dates here,
- # yet we provide a stub class so type dispatching works.
- class datetime(object):
- def __init__(self, *args, **kwargs):
- raise ValueError("datetime is not supported")
-
-
-def readPlist(pathOrFile):
- """Read a .plist file. 'pathOrFile' may either be a file name or a
- (readable) file object. Return the unpacked root object (which
- usually is a dictionary).
- """
- didOpen = 0
- if isinstance(pathOrFile, (str, unicode)):
- pathOrFile = open(pathOrFile)
- didOpen = 1
- p = PlistParser()
- rootObject = p.parse(pathOrFile)
- if didOpen:
- pathOrFile.close()
- return rootObject
-
-
-def writePlist(rootObject, pathOrFile):
- """Write 'rootObject' to a .plist file. 'pathOrFile' may either be a
- file name or a (writable) file object.
- """
- didOpen = 0
- if isinstance(pathOrFile, (str, unicode)):
- pathOrFile = open(pathOrFile, "w")
- didOpen = 1
- writer = PlistWriter(pathOrFile)
- writer.writeln("<plist version=\"1.0\">")
- writer.writeValue(rootObject)
- writer.writeln("</plist>")
- if didOpen:
- pathOrFile.close()
-
-
-def readPlistFromString(data):
- """Read a plist data from a string. Return the root object.
- """
- return readPlist(StringIO(data))
-
-
-def writePlistToString(rootObject):
- """Return 'rootObject' as a plist-formatted string.
- """
- f = StringIO()
- writePlist(rootObject, f)
- return f.getvalue()
-
-
-def readPlistFromResource(path, restype='plst', resid=0):
- """Read plst resource from the resource fork of path.
- """
- from Carbon.File import FSRef, FSGetResourceForkName
- from Carbon.Files import fsRdPerm
- from Carbon import Res
- fsRef = FSRef(path)
- resNum = Res.FSOpenResourceFile(fsRef, FSGetResourceForkName(), fsRdPerm)
- Res.UseResFile(resNum)
- plistData = Res.Get1Resource(restype, resid).data
- Res.CloseResFile(resNum)
- return readPlistFromString(plistData)
-
-
-def writePlistToResource(rootObject, path, restype='plst', resid=0):
- """Write 'rootObject' as a plst resource to the resource fork of path.
- """
- from Carbon.File import FSRef, FSGetResourceForkName
- from Carbon.Files import fsRdWrPerm
- from Carbon import Res
- plistData = writePlistToString(rootObject)
- fsRef = FSRef(path)
- resNum = Res.FSOpenResourceFile(fsRef, FSGetResourceForkName(), fsRdWrPerm)
- Res.UseResFile(resNum)
- try:
- Res.Get1Resource(restype, resid).RemoveResource()
- except Res.Error:
- pass
- res = Res.Resource(plistData)
- res.AddResource(restype, resid, '')
- res.WriteResource()
- Res.CloseResFile(resNum)
-
-
-class DumbXMLWriter:
-
- def __init__(self, file, indentLevel=0, indent="\t"):
- self.file = file
- self.stack = []
- self.indentLevel = indentLevel
- self.indent = indent
-
- def beginElement(self, element):
- self.stack.append(element)
- self.writeln("<%s>" % element)
- self.indentLevel += 1
-
- def endElement(self, element):
- assert self.indentLevel > 0
- assert self.stack.pop() == element
- self.indentLevel -= 1
- self.writeln("</%s>" % element)
-
- def simpleElement(self, element, value=None):
- if value is not None:
- value = _escapeAndEncode(value)
- self.writeln("<%s>%s</%s>" % (element, value, element))
- else:
- self.writeln("<%s/>" % element)
-
- def writeln(self, line):
- if line:
- self.file.write(self.indentLevel * self.indent + line + "\n")
- else:
- self.file.write("\n")
-
-
-# Contents should conform to a subset of ISO 8601
-# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with
-# a loss of precision)
-_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z")
-
-def _dateFromString(s):
- order = ('year', 'month', 'day', 'hour', 'minute', 'second')
- gd = _dateParser.match(s).groupdict()
- lst = []
- for key in order:
- val = gd[key]
- if val is None:
- break
- lst.append(int(val))
- return datetime(*lst)
-
-def _dateToString(d):
- return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
- d.year, d.month, d.day,
- d.hour, d.minute, d.second
- )
-
-
-# Regex to find any control chars, except for \t \n and \r
-_controlCharPat = re.compile(
- r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
- r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
-
-def _escapeAndEncode(text):
- m = _controlCharPat.search(text)
- if m is not None:
- raise ValueError("strings can't contains control characters; "
- "use plistlib.Data instead")
- text = text.replace("\r\n", "\n") # convert DOS line endings
- text = text.replace("\r", "\n") # convert Mac line endings
- text = text.replace("&", "&amp;") # escape '&'
- text = text.replace("<", "&lt;") # escape '<'
- text = text.replace(">", "&gt;") # escape '>'
- return text.encode("utf-8") # encode as UTF-8
-
-
-PLISTHEADER = """\
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-"""
-
-class PlistWriter(DumbXMLWriter):
-
- def __init__(self, file, indentLevel=0, indent="\t", writeHeader=1):
- if writeHeader:
- file.write(PLISTHEADER)
- DumbXMLWriter.__init__(self, file, indentLevel, indent)
-
- def writeValue(self, value):
- if isinstance(value, (str, unicode)):
- self.simpleElement("string", value)
- elif isinstance(value, bool):
- # must switch for bool before int, as bool is a
- # subclass of int...
- if value:
- self.simpleElement("true")
- else:
- self.simpleElement("false")
- elif isinstance(value, (int, long)):
- self.simpleElement("integer", "%d" % value)
- elif isinstance(value, float):
- self.simpleElement("real", repr(value))
- elif isinstance(value, dict):
- self.writeDict(value)
- elif isinstance(value, Data):
- self.writeData(value)
- elif isinstance(value, datetime):
- self.simpleElement("date", _dateToString(value))
- elif isinstance(value, (tuple, list)):
- self.writeArray(value)
- else:
- raise TypeError("unsuported type: %s" % type(value))
-
- def writeData(self, data):
- self.beginElement("data")
- self.indentLevel -= 1
- maxlinelength = 76 - len(self.indent.replace("\t", " " * 8) *
- self.indentLevel)
- for line in data.asBase64(maxlinelength).split("\n"):
- if line:
- self.writeln(line)
- self.indentLevel += 1
- self.endElement("data")
-
- def writeDict(self, d):
- self.beginElement("dict")
- items = d.items()
- items.sort()
- for key, value in items:
- if not isinstance(key, (str, unicode)):
- raise TypeError("keys must be strings")
- self.simpleElement("key", key)
- self.writeValue(value)
- self.endElement("dict")
-
- def writeArray(self, array):
- self.beginElement("array")
- for value in array:
- self.writeValue(value)
- self.endElement("array")
-
-
-class _InternalDict(dict):
-
- # This class is needed while Dict is scheduled for deprecation:
- # we only need to warn when a *user* instantiates Dict or when
- # the "attribute notation for dict keys" is used.
-
- def __getattr__(self, attr):
- try:
- value = self[attr]
- except KeyError:
- raise AttributeError, attr
- from warnings import warn
- warn("Attribute access from plist dicts is deprecated, use d[key] "
- "notation instead", PendingDeprecationWarning)
- return value
-
- def __setattr__(self, attr, value):
- from warnings import warn
- warn("Attribute access from plist dicts is deprecated, use d[key] "
- "notation instead", PendingDeprecationWarning)
- self[attr] = value
-
- def __delattr__(self, attr):
- try:
- del self[attr]
- except KeyError:
- raise AttributeError, attr
- from warnings import warn
- warn("Attribute access from plist dicts is deprecated, use d[key] "
- "notation instead", PendingDeprecationWarning)
-
-class Dict(_InternalDict):
-
- def __init__(self, **kwargs):
- from warnings import warn
- warn("The plistlib.Dict class is deprecated, use builtin dict instead",
- PendingDeprecationWarning)
- super(Dict, self).__init__(**kwargs)
-
-
-class Plist(_InternalDict):
-
- """This class has been deprecated. Use readPlist() and writePlist()
- functions instead, together with regular dict objects.
- """
-
- def __init__(self, **kwargs):
- from warnings import warn
- warn("The Plist class is deprecated, use the readPlist() and "
- "writePlist() functions instead", PendingDeprecationWarning)
- super(Plist, self).__init__(**kwargs)
-
- def fromFile(cls, pathOrFile):
- """Deprecated. Use the readPlist() function instead."""
- rootObject = readPlist(pathOrFile)
- plist = cls()
- plist.update(rootObject)
- return plist
- fromFile = classmethod(fromFile)
-
- def write(self, pathOrFile):
- """Deprecated. Use the writePlist() function instead."""
- writePlist(self, pathOrFile)
-
-
-def _encodeBase64(s, maxlinelength=76):
- # copied from base64.encodestring(), with added maxlinelength argument
- maxbinsize = (maxlinelength//4)*3
- pieces = []
- for i in range(0, len(s), maxbinsize):
- chunk = s[i : i + maxbinsize]
- pieces.append(binascii.b2a_base64(chunk))
- return "".join(pieces)
-
-class Data:
-
- """Wrapper for binary data."""
-
- def __init__(self, data):
- self.data = data
-
- def fromBase64(cls, data):
- # base64.decodestring just calls binascii.a2b_base64;
- # it seems overkill to use both base64 and binascii.
- return cls(binascii.a2b_base64(data))
- fromBase64 = classmethod(fromBase64)
-
- def asBase64(self, maxlinelength=76):
- return _encodeBase64(self.data, maxlinelength)
-
- def __cmp__(self, other):
- if isinstance(other, self.__class__):
- return cmp(self.data, other.data)
- elif isinstance(other, str):
- return cmp(self.data, other)
- else:
- return cmp(id(self), id(other))
-
- def __repr__(self):
- return "%s(%s)" % (self.__class__.__name__, repr(self.data))
-
-
-class PlistParser:
-
- def __init__(self):
- self.stack = []
- self.currentKey = None
- self.root = None
-
- def parse(self, fileobj):
- from xml.parsers.expat import ParserCreate
- parser = ParserCreate()
- parser.StartElementHandler = self.handleBeginElement
- parser.EndElementHandler = self.handleEndElement
- parser.CharacterDataHandler = self.handleData
- parser.ParseFile(fileobj)
- return self.root
-
- def handleBeginElement(self, element, attrs):
- self.data = []
- handler = getattr(self, "begin_" + element, None)
- if handler is not None:
- handler(attrs)
-
- def handleEndElement(self, element):
- handler = getattr(self, "end_" + element, None)
- if handler is not None:
- handler()
-
- def handleData(self, data):
- self.data.append(data)
-
- def addObject(self, value):
- if self.currentKey is not None:
- self.stack[-1][self.currentKey] = value
- self.currentKey = None
- elif not self.stack:
- # this is the root object
- self.root = value
- else:
- self.stack[-1].append(value)
-
- def getData(self):
- data = "".join(self.data)
- try:
- data = data.encode("ascii")
- except UnicodeError:
- pass
- self.data = []
- return data
-
- # element handlers
-
- def begin_dict(self, attrs):
- d = _InternalDict()
- self.addObject(d)
- self.stack.append(d)
- def end_dict(self):
- self.stack.pop()
-
- def end_key(self):
- self.currentKey = self.getData()
-
- def begin_array(self, attrs):
- a = []
- self.addObject(a)
- self.stack.append(a)
- def end_array(self):
- self.stack.pop()
-
- def end_true(self):
- self.addObject(True)
- def end_false(self):
- self.addObject(False)
- def end_integer(self):
- self.addObject(int(self.getData()))
- def end_real(self):
- self.addObject(float(self.getData()))
- def end_string(self):
- self.addObject(self.getData())
- def end_data(self):
- self.addObject(Data.fromBase64(self.getData()))
- def end_date(self):
- self.addObject(_dateFromString(self.getData()))
-
-
-# cruft to support booleans in Python <= 2.3
-import sys
-if sys.version_info[:2] < (2, 3):
- # Python 2.2 and earlier: no booleans
- # Python 2.2.x: booleans are ints
- class bool(int):
- """Imitation of the Python 2.3 bool object."""
- def __new__(cls, value):
- return int.__new__(cls, not not value)
- def __repr__(self):
- if self:
- return "True"
- else:
- return "False"
- True = bool(1)
- False = bool(0)
diff --git a/misc/pylib/robofab/setup.py b/misc/pylib/robofab/setup.py
deleted file mode 100755
index 813807a47..000000000
--- a/misc/pylib/robofab/setup.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from distutils.core import setup
-from distutils.extension import Extension
-from Cython.Distutils import build_ext
-
-ext_modules = [
- Extension("objects.objectsBase", ["objects/objectsBase.pyx"]),
- Extension("objects.objectsRF", ["objects/objectsRF.pyx"]),
- Extension("pens.rfUFOPen", ["pens/rfUFOPen.pyx"]),
- Extension("pens.boundsPen", ["pens/boundsPen.pyx"]),
- Extension("xmlTreeBuilder", ["xmlTreeBuilder.pyx"]),
- Extension("misc.arrayTools", ["misc/arrayTools.pyx"]),
- Extension("glifLib", ["glifLib.pyx"]),
-]
-
-setup(
- name = 'robofab',
- cmdclass = {'build_ext': build_ext},
- ext_modules = ext_modules
-)
diff --git a/misc/pylib/robofab/test/__init__.py b/misc/pylib/robofab/test/__init__.py
deleted file mode 100755
index f34415804..000000000
--- a/misc/pylib/robofab/test/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""Directory for unit tests.
-
-Modules here are typically named text_<something>.py, where <something> is
-usually a module name, for example "test_flPen.py", but it can also be the name
-of an area or concept to be tested, for example "test_drawing.py".
-
-Testmodules should use the unittest framework.
-"""
diff --git a/misc/pylib/robofab/test/runAll.py b/misc/pylib/robofab/test/runAll.py
deleted file mode 100644
index 28446a7b8..000000000
--- a/misc/pylib/robofab/test/runAll.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import glob
-import unittest
-
-import robofab.test
-
-if __name__ == "__main__":
- testDir = os.path.dirname(robofab.test.__file__)
- testFiles = glob.glob1(testDir, "test_*.py")
-
- loader = unittest.TestLoader()
- suites = []
- for fileName in testFiles:
- modName = "robofab.test." + fileName[:-3]
- print "importing", fileName
- try:
- mod = __import__(modName, {}, {}, ["*"])
- except ImportError:
- print "*** skipped", fileName
- continue
-
- suites.append(loader.loadTestsFromModule(mod))
-
- print "running tests..."
- testRunner = unittest.TextTestRunner(verbosity=0)
- testSuite = unittest.TestSuite(suites)
- testRunner.run(testSuite)
diff --git a/misc/pylib/robofab/test/testSupport.py b/misc/pylib/robofab/test/testSupport.py
deleted file mode 100755
index f7f96c9ff..000000000
--- a/misc/pylib/robofab/test/testSupport.py
+++ /dev/null
@@ -1,278 +0,0 @@
-"""Miscellaneous helpers for our test suite."""
-
-
-import sys
-import os
-import types
-import unittest
-
-
-def getDemoFontPath():
- """Return the path to Data/DemoFont.ufo/."""
- import robofab
- root = os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__)))
- return os.path.join(root, "Data", "DemoFont.ufo")
-
-
-def getDemoFontGlyphSetPath():
- """Return the path to Data/DemoFont.ufo/glyphs/."""
- return os.path.join(getDemoFontPath(), "glyphs")
-
-
-def _gatherTestCasesFromCallerByMagic():
- # UGLY magic: fetch TestClass subclasses from the globals of our
- # caller's caller.
- frame = sys._getframe(2)
- return _gatherTestCasesFromDict(frame.f_globals)
-
-
-def _gatherTestCasesFromDict(d):
- testCases = []
- for ob in d.values():
- if isinstance(ob, type) and issubclass(ob, unittest.TestCase):
- testCases.append(ob)
- return testCases
-
-
-def runTests(testCases=None, verbosity=1):
- """Run a series of tests."""
- if testCases is None:
- testCases = _gatherTestCasesFromCallerByMagic()
- loader = unittest.TestLoader()
- suites = []
- for testCase in testCases:
- suites.append(loader.loadTestsFromTestCase(testCase))
-
- testRunner = unittest.TextTestRunner(verbosity=verbosity)
- testSuite = unittest.TestSuite(suites)
- testRunner.run(testSuite)
-
-# font info values used by several tests
-
-fontInfoVersion1 = {
- "familyName" : "Some Font (Family Name)",
- "styleName" : "Regular (Style Name)",
- "fullName" : "Some Font-Regular (Postscript Full Name)",
- "fontName" : "SomeFont-Regular (Postscript Font Name)",
- "menuName" : "Some Font Regular (Style Map Family Name)",
- "fontStyle" : 64,
- "note" : "A note.",
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "notice" : "Some Font by Some Designer for Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "license" : "License info for Some Foundry.",
- "licenseURL" : "http://somefoundry.com/license",
- "createdBy" : "Some Foundry",
- "designer" : "Some Designer",
- "designerURL" : "http://somedesigner.com",
- "vendorURL" : "http://somefoundry.com",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "descender" : -250,
- "capHeight" : 750,
- "xHeight" : 500,
- "defaultWidth" : 400,
- "slantAngle" : -12.5,
- "italicAngle" : -12.5,
- "widthName" : "Medium (normal)",
- "weightName" : "Medium",
- "weightValue" : 500,
- "fondName" : "SomeFont Regular (FOND Name)",
- "otFamilyName" : "Some Font (Preferred Family Name)",
- "otStyleName" : "Regular (Preferred Subfamily Name)",
- "otMacName" : "Some Font Regular (Compatible Full Name)",
- "msCharSet" : 0,
- "fondID" : 15000,
- "uniqueID" : 4000000,
- "ttVendor" : "SOME",
- "ttUniqueID" : "OpenType name Table Unique ID",
- "ttVersion" : "OpenType name Table Version",
-}
-
-fontInfoVersion2 = {
- "familyName" : "Some Font (Family Name)",
- "styleName" : "Regular (Style Name)",
- "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
- "styleMapStyleName" : "regular",
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "unitsPerEm" : 1000,
- "descender" : -250,
- "xHeight" : 500,
- "capHeight" : 750,
- "ascender" : 750,
- "italicAngle" : -12.5,
- "note" : "A note.",
- "openTypeHeadCreated" : "2000/01/01 00:00:00",
- "openTypeHeadLowestRecPPEM" : 10,
- "openTypeHeadFlags" : [0, 1],
- "openTypeHheaAscender" : 750,
- "openTypeHheaDescender" : -250,
- "openTypeHheaLineGap" : 200,
- "openTypeHheaCaretSlopeRise" : 1,
- "openTypeHheaCaretSlopeRun" : 0,
- "openTypeHheaCaretOffset" : 0,
- "openTypeNameDesigner" : "Some Designer",
- "openTypeNameDesignerURL" : "http://somedesigner.com",
- "openTypeNameManufacturer" : "Some Foundry",
- "openTypeNameManufacturerURL" : "http://somefoundry.com",
- "openTypeNameLicense" : "License info for Some Foundry.",
- "openTypeNameLicenseURL" : "http://somefoundry.com/license",
- "openTypeNameVersion" : "OpenType name Table Version",
- "openTypeNameUniqueID" : "OpenType name Table Unique ID",
- "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
- "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
- "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameSampleText" : "Sample Text for Some Font.",
- "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)",
- "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)",
- "openTypeOS2WidthClass" : 5,
- "openTypeOS2WeightClass" : 500,
- "openTypeOS2Selection" : [3],
- "openTypeOS2VendorID" : "SOME",
- "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
- "openTypeOS2FamilyClass" : [1, 1],
- "openTypeOS2UnicodeRanges" : [0, 1],
- "openTypeOS2CodePageRanges" : [0, 1],
- "openTypeOS2TypoAscender" : 750,
- "openTypeOS2TypoDescender" : -250,
- "openTypeOS2TypoLineGap" : 200,
- "openTypeOS2WinAscent" : 750,
- "openTypeOS2WinDescent" : -250,
- "openTypeOS2Type" : [],
- "openTypeOS2SubscriptXSize" : 200,
- "openTypeOS2SubscriptYSize" : 400,
- "openTypeOS2SubscriptXOffset" : 0,
- "openTypeOS2SubscriptYOffset" : -100,
- "openTypeOS2SuperscriptXSize" : 200,
- "openTypeOS2SuperscriptYSize" : 400,
- "openTypeOS2SuperscriptXOffset" : 0,
- "openTypeOS2SuperscriptYOffset" : 200,
- "openTypeOS2StrikeoutSize" : 20,
- "openTypeOS2StrikeoutPosition" : 300,
- "openTypeVheaVertTypoAscender" : 750,
- "openTypeVheaVertTypoDescender" : -250,
- "openTypeVheaVertTypoLineGap" : 200,
- "openTypeVheaCaretSlopeRise" : 0,
- "openTypeVheaCaretSlopeRun" : 1,
- "openTypeVheaCaretOffset" : 0,
- "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
- "postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
- "postscriptSlantAngle" : -12.5,
- "postscriptUniqueID" : 4000000,
- "postscriptUnderlineThickness" : 20,
- "postscriptUnderlinePosition" : -200,
- "postscriptIsFixedPitch" : False,
- "postscriptBlueValues" : [500, 510],
- "postscriptOtherBlues" : [-250, -260],
- "postscriptFamilyBlues" : [500, 510],
- "postscriptFamilyOtherBlues" : [-250, -260],
- "postscriptStemSnapH" : [100, 120],
- "postscriptStemSnapV" : [80, 90],
- "postscriptBlueFuzz" : 1,
- "postscriptBlueShift" : 7,
- "postscriptBlueScale" : 0.039625,
- "postscriptForceBold" : True,
- "postscriptDefaultWidthX" : 400,
- "postscriptNominalWidthX" : 400,
- "postscriptWeightName" : "Medium",
- "postscriptDefaultCharacter" : ".notdef",
- "postscriptWindowsCharacterSet" : 1,
- "macintoshFONDFamilyID" : 15000,
- "macintoshFONDName" : "SomeFont Regular (FOND Name)",
-}
-
-expectedFontInfo1To2Conversion = {
- "familyName" : "Some Font (Family Name)",
- "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
- "styleMapStyleName" : "regular",
- "styleName" : "Regular (Style Name)",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "capHeight" : 750,
- "xHeight" : 500,
- "descender" : -250,
- "italicAngle" : -12.5,
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "note" : "A note.",
- "macintoshFONDFamilyID" : 15000,
- "macintoshFONDName" : "SomeFont Regular (FOND Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
- "openTypeNameDesigner" : "Some Designer",
- "openTypeNameDesignerURL" : "http://somedesigner.com",
- "openTypeNameLicense" : "License info for Some Foundry.",
- "openTypeNameLicenseURL" : "http://somefoundry.com/license",
- "openTypeNameManufacturer" : "Some Foundry",
- "openTypeNameManufacturerURL" : "http://somefoundry.com",
- "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
- "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameUniqueID" : "OpenType name Table Unique ID",
- "openTypeNameVersion" : "OpenType name Table Version",
- "openTypeOS2VendorID" : "SOME",
- "openTypeOS2WeightClass" : 500,
- "openTypeOS2WidthClass" : 5,
- "postscriptDefaultWidthX" : 400,
- "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
- "postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
- "postscriptSlantAngle" : -12.5,
- "postscriptUniqueID" : 4000000,
- "postscriptWeightName" : "Medium",
- "postscriptWindowsCharacterSet" : 1
-}
-
-expectedFontInfo2To1Conversion = {
- "familyName" : "Some Font (Family Name)",
- "menuName" : "Some Font Regular (Style Map Family Name)",
- "fontStyle" : 64,
- "styleName" : "Regular (Style Name)",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "capHeight" : 750,
- "xHeight" : 500,
- "descender" : -250,
- "italicAngle" : -12.5,
- "versionMajor" : 1,
- "versionMinor" : 0,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "note" : "A note.",
- "fondID" : 15000,
- "fondName" : "SomeFont Regular (FOND Name)",
- "fullName" : "Some Font Regular (Compatible Full Name)",
- "notice" : "Some Font by Some Designer for Some Foundry.",
- "designer" : "Some Designer",
- "designerURL" : "http://somedesigner.com",
- "license" : "License info for Some Foundry.",
- "licenseURL" : "http://somefoundry.com/license",
- "createdBy" : "Some Foundry",
- "vendorURL" : "http://somefoundry.com",
- "otFamilyName" : "Some Font (Preferred Family Name)",
- "otStyleName" : "Regular (Preferred Subfamily Name)",
- "otMacName" : "Some Font Regular (Compatible Full Name)",
- "ttUniqueID" : "OpenType name Table Unique ID",
- "ttVersion" : "OpenType name Table Version",
- "ttVendor" : "SOME",
- "weightValue" : 500,
- "widthName" : "Medium (normal)",
- "defaultWidth" : 400,
- "fontName" : "SomeFont-Regular (Postscript Font Name)",
- "fullName" : "Some Font-Regular (Postscript Full Name)",
- "slantAngle" : -12.5,
- "uniqueID" : 4000000,
- "weightName" : "Medium",
- "msCharSet" : 0,
- "year" : 2008
-}
diff --git a/misc/pylib/robofab/test/test_RInfoFL.py b/misc/pylib/robofab/test/test_RInfoFL.py
deleted file mode 100644
index bfbd13477..000000000
--- a/misc/pylib/robofab/test/test_RInfoFL.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import unittest
-from cStringIO import StringIO
-import sys
-from robofab import ufoLib
-from robofab.objects.objectsFL import NewFont
-from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2
-
-
-class RInfoRFTestCase(unittest.TestCase):
-
- def testRoundTripVersion2(self):
- font = NewFont()
- infoObject = font.info
- for attr, value in fontInfoVersion2.items():
- if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
- continue
- setattr(infoObject, attr, value)
- newValue = getattr(infoObject, attr)
- self.assertEqual((attr, newValue), (attr, value))
- font.close()
-
- def testVersion2UnsupportedSet(self):
- saveStderr = sys.stderr
- saveStdout = sys.stdout
- tempStderr = StringIO()
- sys.stderr = tempStderr
- sys.stdout = tempStderr
- font = NewFont()
- infoObject = font.info
- requiredWarnings = []
- try:
- for attr, value in fontInfoVersion2.items():
- if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None:
- continue
- setattr(infoObject, attr, value)
- s = "The attribute %s is not supported by FontLab." % attr
- requiredWarnings.append((attr, s))
- finally:
- sys.stderr = saveStderr
- sys.stdout = saveStdout
- tempStderr = tempStderr.getvalue()
- for attr, line in requiredWarnings:
- self.assertEquals((attr, line in tempStderr), (attr, True))
- font.close()
-
- def testVersion2UnsupportedGet(self):
- saveStderr = sys.stderr
- saveStdout = sys.stdout
- tempStderr = StringIO()
- sys.stderr = tempStderr
- sys.stdout = tempStderr
- font = NewFont()
- infoObject = font.info
- requiredWarnings = []
- try:
- for attr, value in fontInfoVersion2.items():
- if attr in infoObject._ufoToFLAttrMapping and infoObject._ufoToFLAttrMapping[attr]["nakedAttribute"] is not None:
- continue
- getattr(infoObject, attr, value)
- s = "The attribute %s is not supported by FontLab." % attr
- requiredWarnings.append((attr, s))
- finally:
- sys.stderr = saveStderr
- sys.stdout = saveStdout
- tempStderr = tempStderr.getvalue()
- for attr, line in requiredWarnings:
- self.assertEquals((attr, line in tempStderr), (attr, True))
- font.close()
-
- def testRoundTripVersion1(self):
- font = NewFont()
- infoObject = font.info
- for attr, value in fontInfoVersion1.items():
- if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
- setattr(infoObject, attr, value)
- for attr, expectedValue in fontInfoVersion1.items():
- if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
- value = getattr(infoObject, attr)
- self.assertEqual((attr, expectedValue), (attr, value))
- font.close()
-
- def testVersion1DeprecationRoundTrip(self):
- saveStderr = sys.stderr
- saveStdout = sys.stdout
- tempStderr = StringIO()
- sys.stderr = tempStderr
- sys.stdout = tempStderr
- font = NewFont()
- infoObject = font.info
- requiredWarnings = []
- try:
- for attr, value in fontInfoVersion1.items():
- if attr in ufoLib.deprecatedFontInfoAttributesVersion2:
- setattr(infoObject, attr, value)
- v = getattr(infoObject, attr)
- self.assertEquals((attr, value), (attr, v))
- s = "DeprecationWarning: The %s attribute has been deprecated." % attr
- requiredWarnings.append((attr, s))
- finally:
- sys.stderr = saveStderr
- sys.stdout = saveStdout
- tempStderr = tempStderr.getvalue()
- for attr, line in requiredWarnings:
- self.assertEquals((attr, line in tempStderr), (attr, True))
- font.close()
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
-
diff --git a/misc/pylib/robofab/test/test_RInfoRF.py b/misc/pylib/robofab/test/test_RInfoRF.py
deleted file mode 100644
index 3a8747033..000000000
--- a/misc/pylib/robofab/test/test_RInfoRF.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import unittest
-from cStringIO import StringIO
-import sys
-from robofab import ufoLib
-from robofab.objects.objectsRF import RInfo
-from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2
-
-
-class RInfoRFTestCase(unittest.TestCase):
-
- def testRoundTripVersion2(self):
- infoObject = RInfo()
- for attr, value in fontInfoVersion2.items():
- setattr(infoObject, attr, value)
- newValue = getattr(infoObject, attr)
- self.assertEqual((attr, newValue), (attr, value))
-
- def testRoundTripVersion1(self):
- infoObject = RInfo()
- for attr, value in fontInfoVersion1.items():
- if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
- setattr(infoObject, attr, value)
- for attr, expectedValue in fontInfoVersion1.items():
- if attr not in ufoLib.deprecatedFontInfoAttributesVersion2:
- value = getattr(infoObject, attr)
- self.assertEqual((attr, expectedValue), (attr, value))
-
- def testVersion1DeprecationRoundTrip(self):
- """
- unittest doesn't catch warnings in self.assertRaises,
- so some hackery is required to catch the warnings
- that are raised when setting deprecated attributes.
- """
- saveStderr = sys.stderr
- tempStderr = StringIO()
- sys.stderr = tempStderr
- infoObject = RInfo()
- requiredWarnings = []
- try:
- for attr, value in fontInfoVersion1.items():
- if attr in ufoLib.deprecatedFontInfoAttributesVersion2:
- setattr(infoObject, attr, value)
- v = getattr(infoObject, attr)
- self.assertEquals((attr, value), (attr, v))
- s = "DeprecationWarning: The %s attribute has been deprecated." % attr
- requiredWarnings.append((attr, s))
- finally:
- sys.stderr = saveStderr
- tempStderr = tempStderr.getvalue()
- for attr, line in requiredWarnings:
- self.assertEquals((attr, line in tempStderr), (attr, True))
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_dialogs.py b/misc/pylib/robofab/test/test_dialogs.py
deleted file mode 100644
index c296a6bd7..000000000
--- a/misc/pylib/robofab/test/test_dialogs.py
+++ /dev/null
@@ -1,218 +0,0 @@
-import robofab.interface.all.dialogs
-reload(robofab.interface.all.dialogs)
-from robofab.interface.all.dialogs import *
-
-import unittest
-
-
-__all__ = [
- "AskString", #x
- "AskYesNoCancel", #x
- "FindGlyph",
- "GetFile", #x
- "GetFolder", #x
- "GetFileOrFolder", #x
- "Message", #x
- "OneList",
- "PutFile", #x
- "SearchList",
- "SelectFont",
- "SelectGlyph",
- "TwoChecks",
- "TwoFields",
- "ProgressBar",
-]
-
-class DialogRunner(object):
- def __init__(self):
- prompt = "The prompt for %s."
- message = "The message for %s."
- title = "The title for %s."
- informativeText = "The informative text for %s."
- fileTypes = ['ufo']
- fileName = "The_filename.txt"
-
- self.fonts = fonts = [self.makeTestFont(n) for n in range(4)]
-
- t = "AskString"
- try:
- print "About to try", t
- print "\t>>>", AskString(
- message=prompt%t,
- value='',
- title=title%t
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "AskYesNoCancel"
- try:
- print "About to try", t
- print "\t>>>", AskYesNoCancel(
- message=prompt%t+" default set to 0",
- title=title%t,
- default=0,
- informativeText=informativeText%t
- )
- print "\t>>>", AskYesNoCancel(
- message=prompt%t+" default set to 1",
- title=title%t,
- default=1,
- informativeText=informativeText%t
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "GetFile"
- try:
- print "About to try", t
- print "\t>>>", GetFile(
- message=message%t+" Only fileTypes "+`fileTypes`,
- title=title%t,
- directory=None,
- fileName=fileName,
- allowsMultipleSelection=False,
- fileTypes=fileTypes
- )
- print "\t>>>", GetFile(
- message=message%t+" All filetypes, allow multiple selection.",
- title=title%t,
- directory=None,
- fileName=fileName,
- allowsMultipleSelection=True,
- fileTypes=None
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "GetFolder"
- try:
- print "About to try", t
- print "\t>>>", GetFolder(
- message=message%t,
- title=title%t,
- directory=None,
- allowsMultipleSelection=False
- )
- print "\t>>>", GetFolder(
- message=message%t + " Allow multiple selection.",
- title=title%t,
- directory=None,
- allowsMultipleSelection=True
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "GetFileOrFolder"
- try:
- print "About to try", t
- print "\t>>>", GetFileOrFolder(
- message=message%t+" Only fileTypes "+`fileTypes`,
- title=title%t,
- directory=None,
- fileName=fileName,
- allowsMultipleSelection=False,
- fileTypes=fileTypes
- )
- print "\t>>>", GetFileOrFolder(
- message=message%t + " Allow multiple selection.",
- title=title%t,
- directory=None,
- fileName=fileName,
- allowsMultipleSelection=True,
- fileTypes=None
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "Message"
- try:
- print "About to try", t
- print "\t>>>", Message(
- message=message%t,
- title=title%t,
- informativeText=informativeText%t
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- t = "PutFile"
- try:
- print "About to try", t
- print "\t>>>", PutFile(
- message=message%t,
- fileName=fileName,
- )
- except NotImplementedError:
- print t, "is not implemented."
-
- # t = "SelectFont"
- # try:
- #print "About to try", t
- # print "\t>>>", SelectFont(
- # message=message%t,
- # title=title%t,
- # allFonts=fonts,
- # )
- # except NotImplementedError:
- # print t, "is not implemented."
-
- # t = 'SelectGlyph'
- # try:
- #print "About to try", t
- # print "\t>>>", SelectGlyph(
- # font=fonts[0],
- # message=message%t,
- # title=title%t,
- # )
- # except NotImplementedError:
- # print t, "is not implemented."
-
- print 'No more tests.'
-
- def makeTestFont(self, number):
- from robofab.objects.objectsRF import RFont as _RFont
- f = _RFont()
- f.info.familyName = "TestFamily"
- f.info.styleName = "weight%d"%number
- f.info.postscriptFullName = "%s %s"%(f.info.familyName, f.info.styleName)
- # make some glyphs
- for name in ['A', 'B', 'C']:
- g = f.newGlyph(name)
- pen = g.getPen()
- pen.moveTo((0,0))
- pen.lineTo((500, 0))
- pen.lineTo((500, 800))
- pen.lineTo((0, 800))
- pen.closePath()
- return f
-
-
-class DialogTests(unittest.TestCase):
- def setUp(self):
- from robofab.interface.all.dialogs import test
- test()
-
- def tearDown(self):
- pass
-
- def testDialogs(self):
- import robofab.interface.all.dialogs
- dialogModuleName = robofab.interface.all.dialogs.platformApplicationModuleName
- application = robofab.interface.all.dialogs.application
-
- if application is None and dialogModuleName == "dialogs_mac_vanilla":
- # in vanilla, but not in a host application, run with executeVanillaTest
- print
- print "I'm running these tests with executeVanillaTest"
- from vanilla.test.testTools import executeVanillaTest
- executeVanillaTest(DialogRunner)
- else:
- print
- print "I'm running these tests natively in"
- DialogRunner()
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py b/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py
deleted file mode 100644
index 91c072d26..000000000
--- a/misc/pylib/robofab/test/test_fontLabUFOReadWrite.py
+++ /dev/null
@@ -1,565 +0,0 @@
-import os
-import shutil
-import unittest
-import tempfile
-from robofab.plistlib import readPlist
-import robofab
-from robofab.ufoLib import UFOReader, UFOWriter
-from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion
-from robofab.objects.objectsFL import NewFont, OpenFont
-
-vfbPath = os.path.dirname(robofab.__file__)
-vfbPath = os.path.dirname(vfbPath)
-vfbPath = os.path.dirname(vfbPath)
-vfbPath = os.path.join(vfbPath, "TestData", "TestFont1.vfb")
-
-ufoPath1 = os.path.dirname(robofab.__file__)
-ufoPath1 = os.path.dirname(ufoPath1)
-ufoPath1 = os.path.dirname(ufoPath1)
-ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo")
-ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo")
-
-
-expectedFormatVersion1Features = """@myClass = [A B];
-
-feature liga {
- sub A A by b;
-} liga;
-"""
-
-# robofab should remove these from the lib after a load.
-removeFromFormatVersion1Lib = [
- "org.robofab.opentype.classes",
- "org.robofab.opentype.features",
- "org.robofab.opentype.featureorder",
- "org.robofab.postScriptHintData"
-]
-
-
-class ReadUFOFormatVersion1TestCase(unittest.TestCase):
-
- def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
- self.font = NewFont()
- self.ufoPath = ufoPath1
- self.font.readUFO(ufoPath1, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
- self.font.update()
-
- def tearDownFont(self):
- self.font.close()
- self.font = None
-
- def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
- reader = UFOReader(self.ufoPath)
- results = {}
- if doInfo:
- infoMatches = True
- info = self.font.info
- for attr, expectedValue in expectedFontInfo1To2Conversion.items():
- writtenValue = getattr(info, attr)
- if expectedValue != writtenValue:
- infoMatches = False
- break
- results["info"]= infoMatches
- if doKerning:
- kerning = self.font.kerning.asDict()
- expectedKerning = reader.readKerning()
- results["kerning"] = expectedKerning == kerning
- if doGroups:
- groups = dict(self.font.groups)
- expectedGroups = reader.readGroups()
- results["groups"] = expectedGroups == groups
- if doFeatures:
- features = self.font.features.text
- expectedFeatures = expectedFormatVersion1Features
- # FontLab likes to add lines to the features, so skip blank lines.
- features = [line for line in features.splitlines() if line]
- expectedFeatures = [line for line in expectedFeatures.splitlines() if line]
- results["features"] = expectedFeatures == features
- if doLib:
- lib = dict(self.font.lib)
- expectedLib = reader.readLib()
- for key in removeFromFormatVersion1Lib:
- if key in expectedLib:
- del expectedLib[key]
- results["lib"] = expectedLib == lib
- return results
-
- def testFull(self):
- self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont(doInfo=True)
- otherResults = self.compareToUFO(doInfo=False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- info = self.font.info
- for attr, expectedValue in expectedFontInfo1To2Conversion.items():
- writtenValue = getattr(info, attr)
- self.assertEqual((attr, expectedValue), (attr, writtenValue))
- self.tearDownFont()
-
- def testFeatures(self):
- self.setUpFont(doFeatures=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testKerning(self):
- self.setUpFont(doKerning=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testGroups(self):
- self.setUpFont(doGroups=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testLib(self):
- self.setUpFont(doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-class ReadUFOFormatVersion2TestCase(unittest.TestCase):
-
- def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
- self.font = NewFont()
- self.ufoPath = ufoPath2
- self.font.readUFO(ufoPath2, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
- self.font.update()
-
- def tearDownFont(self):
- self.font.close()
- self.font = None
-
- def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
- reader = UFOReader(self.ufoPath)
- results = {}
- if doInfo:
- infoMatches = True
- info = self.font.info
- for attr, expectedValue in fontInfoVersion2.items():
- # cheat by skipping attrs that aren't supported
- if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
- continue
- writtenValue = getattr(info, attr)
- if expectedValue != writtenValue:
- infoMatches = False
- break
- results["info"]= infoMatches
- if doKerning:
- kerning = self.font.kerning.asDict()
- expectedKerning = reader.readKerning()
- results["kerning"] = expectedKerning == kerning
- if doGroups:
- groups = dict(self.font.groups)
- expectedGroups = reader.readGroups()
- results["groups"] = expectedGroups == groups
- if doFeatures:
- features = self.font.features.text
- expectedFeatures = reader.readFeatures()
- results["features"] = expectedFeatures == features
- if doLib:
- lib = dict(self.font.lib)
- expectedLib = reader.readLib()
- results["lib"] = expectedLib == lib
- return results
-
- def testFull(self):
- self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont(doInfo=True)
- otherResults = self.compareToUFO(doInfo=False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- info = self.font.info
- for attr, expectedValue in fontInfoVersion2.items():
- # cheat by skipping attrs that aren't supported
- if info._ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
- continue
- writtenValue = getattr(info, attr)
- self.assertEqual((attr, expectedValue), (attr, writtenValue))
- self.tearDownFont()
-
- def testFeatures(self):
- self.setUpFont(doFeatures=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testKerning(self):
- self.setUpFont(doKerning=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testGroups(self):
- self.setUpFont(doGroups=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testLib(self):
- self.setUpFont(doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-class WriteUFOFormatVersion1TestCase(unittest.TestCase):
-
- def setUpFont(self, doInfo=False, doKerning=False, doGroups=False):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- self.font = OpenFont(vfbPath)
- self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, formatVersion=1)
- self.font.close()
-
- def tearDownFont(self):
- shutil.rmtree(self.dstDir)
-
- def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
- readerExpected = UFOReader(ufoPath1)
- readerWritten = UFOReader(self.dstDir)
- results = {}
- if doInfo:
- matches = True
- expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- for attr, expectedValue in expected.items():
- if expectedValue != written[attr]:
- matches = False
- break
- results["info"] = matches
- if doKerning:
- matches = True
- expectedPath = os.path.join(ufoPath1, "kerning.plist")
- writtenPath = os.path.join(self.dstDir, "kerning.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["kerning"] = matches
- if doGroups:
- matches = True
- expectedPath = os.path.join(ufoPath1, "groups.plist")
- writtenPath = os.path.join(self.dstDir, "groups.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["groups"] = matches
- if doFeatures:
- matches = True
- featuresPath = os.path.join(self.dstDir, "features.fea")
- libPath = os.path.join(self.dstDir, "lib.plist")
- if os.path.exists(featuresPath):
- matches = False
- else:
- fontLib = readPlist(libPath)
- writtenText = [fontLib.get("org.robofab.opentype.classes", "")]
- features = fontLib.get("org.robofab.opentype.features", {})
- featureOrder= fontLib.get("org.robofab.opentype.featureorder", [])
- for featureName in featureOrder:
- writtenText.append(features.get(featureName, ""))
- writtenText = "\n".join(writtenText)
- # FontLab likes to add lines to the features, so skip blank lines.
- expectedText = [line for line in expectedFormatVersion1Features.splitlines() if line]
- writtenText = [line for line in writtenText.splitlines() if line]
- matches = "\n".join(expectedText) == "\n".join(writtenText)
- results["features"] = matches
- if doLib:
- matches = True
- expectedPath = os.path.join(ufoPath1, "lib.plist")
- writtenPath = os.path.join(self.dstDir, "lib.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- # the test file doesn't have the glyph order
- # so purge it from the written
- writtenLib = readPlist(writtenPath)
- del writtenLib["org.robofab.glyphOrder"]
- matches = readPlist(expectedPath) == writtenLib
- results["lib"] = matches
- return results
-
- def testFull(self):
- self.setUpFont(doInfo=True, doKerning=True, doGroups=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont(doInfo=True)
- otherResults = self.compareToUFO(doInfo=False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- for attr, expectedValue in expected.items():
- self.assertEqual((attr, expectedValue), (attr, written[attr]))
- self.tearDownFont()
-
- def testFeatures(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], True)
- self.tearDownFont()
-
- def testKerning(self):
- self.setUpFont(doKerning=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], False)
- self.tearDownFont()
-
- def testGroups(self):
- self.setUpFont(doGroups=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], True)
- self.tearDownFont()
-
- def testLib(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-
-class WriteUFOFormatVersion2TestCase(unittest.TestCase):
-
- def setUpFont(self, doInfo=False, doKerning=False, doGroups=False, doLib=False, doFeatures=False):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- self.font = OpenFont(vfbPath)
- self.font.writeUFO(self.dstDir, doInfo=doInfo, doKerning=doKerning, doGroups=doGroups, doLib=doLib, doFeatures=doFeatures)
- self.font.close()
-
- def tearDownFont(self):
- shutil.rmtree(self.dstDir)
-
- def compareToUFO(self, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True):
- readerExpected = UFOReader(ufoPath2)
- readerWritten = UFOReader(self.dstDir)
- results = {}
- if doInfo:
- matches = True
- expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- dummyFont = NewFont()
- _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping)
- dummyFont.close()
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- for attr, expectedValue in expected.items():
- # cheat by skipping attrs that aren't supported
- if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
- continue
- if expectedValue != written[attr]:
- matches = False
- break
- results["info"] = matches
- if doKerning:
- matches = True
- expectedPath = os.path.join(ufoPath2, "kerning.plist")
- writtenPath = os.path.join(self.dstDir, "kerning.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["kerning"] = matches
- if doGroups:
- matches = True
- expectedPath = os.path.join(ufoPath2, "groups.plist")
- writtenPath = os.path.join(self.dstDir, "groups.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["groups"] = matches
- if doFeatures:
- matches = True
- expectedPath = os.path.join(ufoPath2, "features.fea")
- writtenPath = os.path.join(self.dstDir, "features.fea")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- f = open(expectedPath, "r")
- expectedText = f.read()
- f.close()
- f = open(writtenPath, "r")
- writtenText = f.read()
- f.close()
- # FontLab likes to add lines to the features, so skip blank lines.
- expectedText = [line for line in expectedText.splitlines() if line]
- writtenText = [line for line in writtenText.splitlines() if line]
- matches = "\n".join(expectedText) == "\n".join(writtenText)
- results["features"] = matches
- if doLib:
- matches = True
- expectedPath = os.path.join(ufoPath2, "lib.plist")
- writtenPath = os.path.join(self.dstDir, "lib.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- # the test file doesn't have the glyph order
- # so purge it from the written
- writtenLib = readPlist(writtenPath)
- del writtenLib["org.robofab.glyphOrder"]
- matches = readPlist(expectedPath) == writtenLib
- results["lib"] = matches
- return results
-
- def testFull(self):
- self.setUpFont(doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont(doInfo=True)
- otherResults = self.compareToUFO(doInfo=False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- dummyFont = NewFont()
- _ufoToFLAttrMapping = dict(dummyFont.info._ufoToFLAttrMapping)
- dummyFont.close()
- for attr, expectedValue in expected.items():
- # cheat by skipping attrs that aren't supported
- if _ufoToFLAttrMapping[attr]["nakedAttribute"] is None:
- continue
- self.assertEqual((attr, expectedValue), (attr, written[attr]))
- self.tearDownFont()
-
- def testFeatures(self):
- self.setUpFont(doFeatures=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testKerning(self):
- self.setUpFont(doKerning=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testGroups(self):
- self.setUpFont(doGroups=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], False)
- self.tearDownFont()
-
- def testLib(self):
- self.setUpFont(doLib=True)
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], False)
- self.assertEqual(otherResults["kerning"], False)
- self.assertEqual(otherResults["groups"], False)
- self.assertEqual(otherResults["features"], False)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_glifLib.py b/misc/pylib/robofab/test/test_glifLib.py
deleted file mode 100644
index 13eca3a6a..000000000
--- a/misc/pylib/robofab/test/test_glifLib.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import os
-import tempfile
-import shutil
-import unittest
-
-from robofab.test.testSupport import getDemoFontGlyphSetPath
-from robofab.glifLib import GlyphSet, glyphNameToFileName, READ_MODE
-from robofab.tools.glyphNameSchemes import glyphNameToShortFileName
-
-
-GLYPHSETDIR = getDemoFontGlyphSetPath()
-
-
-class GlyphSetTests(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def testRoundTrip(self):
- srcDir = GLYPHSETDIR
- dstDir = self.dstDir
- src = GlyphSet(srcDir)
- dst = GlyphSet(dstDir)
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- # compare raw file data:
- for glyphName in src.keys():
- fileName = src.contents[glyphName]
- org = file(os.path.join(srcDir, fileName), READ_MODE).read()
- new = file(os.path.join(dstDir, fileName), READ_MODE).read()
- self.assertEqual(org, new, "%r .glif file differs after round tripping" % glyphName)
-
- def testRebuildContents(self):
- gset = GlyphSet(GLYPHSETDIR)
- contents = gset.contents
- gset.rebuildContents()
- self.assertEqual(contents, gset.contents)
-
- def testReverseContents(self):
- gset = GlyphSet(GLYPHSETDIR)
- d = {}
- for k, v in gset.getReverseContents().items():
- d[v] = k
- org = {}
- for k, v in gset.contents.items():
- org[k] = v.lower()
- self.assertEqual(d, org)
-
- def testReverseContents2(self):
- src = GlyphSet(GLYPHSETDIR)
- dst = GlyphSet(self.dstDir)
- dstMap = dst.getReverseContents()
- self.assertEqual(dstMap, {})
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- self.assertNotEqual(dstMap, {})
- srcMap = dict(src.getReverseContents()) # copy
- self.assertEqual(dstMap, srcMap)
- del srcMap["a.glif"]
- dst.deleteGlyph("a")
- self.assertEqual(dstMap, srcMap)
-
- def testCustomFileNamingScheme(self):
- def myGlyphNameToFileName(glyphName, glyphSet):
- return "prefix" + glyphNameToFileName(glyphName, glyphSet)
- src = GlyphSet(GLYPHSETDIR)
- dst = GlyphSet(self.dstDir, myGlyphNameToFileName)
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- d = {}
- for k, v in src.contents.items():
- print k, v
- d[k] = "prefix" + v
- self.assertEqual(d, dst.contents)
-
- def testGetUnicodes(self):
- src = GlyphSet(GLYPHSETDIR)
- unicodes = src.getUnicodes()
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- if not hasattr(g, "unicodes"):
- self.assertEqual(unicodes[glyphName], [])
- else:
- self.assertEqual(g.unicodes, unicodes[glyphName])
-
-
-class FileNameTests(unittest.TestCase):
-
- def testDefaultFileNameScheme(self):
- self.assertEqual(glyphNameToFileName("a", None), "a.glif")
- self.assertEqual(glyphNameToFileName("A", None), "A_.glif")
- self.assertEqual(glyphNameToFileName("Aring", None), "Aring_.glif")
- self.assertEqual(glyphNameToFileName("F_A_B", None), "F__A__B_.glif")
- self.assertEqual(glyphNameToFileName("A.alt", None), "A_.alt.glif")
- self.assertEqual(glyphNameToFileName("A.Alt", None), "A_.Alt_.glif")
- self.assertEqual(glyphNameToFileName(".notdef", None), "_notdef.glif")
- self.assertEqual(glyphNameToFileName("T_H", None), "T__H_.glif")
- self.assertEqual(glyphNameToFileName("T_h", None), "T__h.glif")
- self.assertEqual(glyphNameToFileName("t_h", None), "t_h.glif")
- self.assertEqual(glyphNameToFileName('F_F_I', None), "F__F__I_.glif")
- self.assertEqual(glyphNameToFileName('f_f_i', None), "f_f_i.glif")
-
-
- def testShortFileNameScheme(self):
- print "testShortFileNameScheme"
- self.assertEqual(glyphNameToShortFileName("a", None), "a.glif")
- self.assertEqual(glyphNameToShortFileName("A", None), "A_.glif")
- self.assertEqual(glyphNameToShortFileName("aE", None), "aE_.glif")
- self.assertEqual(glyphNameToShortFileName("AE", None), "A_E_.glif")
- self.assertEqual(glyphNameToShortFileName("a.alt", None), "a_alt.glif")
- self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif")
- self.assertEqual(glyphNameToShortFileName("a.alt#swash", None), "a_alt_swash.glif")
- self.assertEqual(glyphNameToShortFileName("A.alt", None), "A__alt.glif")
- self.assertEqual(glyphNameToShortFileName(".notdef", None), "_notdef.glif")
- self.assertEqual(glyphNameToShortFileName("f_f_i", None), "f_f_i.glif")
- self.assertEqual(glyphNameToShortFileName("F_F_I", None), "F__F__I_.glif")
- self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt1", None), "acircumflexdieresi0cfc8352.glif")
- self.assertEqual(glyphNameToShortFileName("acircumflexdieresis.swash.alt2", None), "acircumflexdieresi95f5d2e8.glif")
- self.assertEqual(glyphNameToShortFileName("Acircumflexdieresis.swash.alt1", None), "A_circumflexdieresed24fb56.glif")
- self.assertEqual(glyphNameToShortFileName("F#weight0.800_width0.425", None), "F__weight0_800_width0_425.glif")
- self.assertEqual(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None), "F__weight0_8324551c9a4143c.glif")
- self.assertEqual(len(glyphNameToShortFileName("F#weight0.83245511_width0.425693567", None)), 31)
-
- def testShortFileNameScheme_clashes(self):
- # test for the condition in code.robofab.com ticket #5
- name1 = glyphNameToShortFileName('Adieresis', None)
- name2 = glyphNameToShortFileName('a_dieresis', None)
- self.assertNotEqual(name1, name2)
- name1 = glyphNameToShortFileName('AE', None)
- name2 = glyphNameToShortFileName('aE', None)
- self.assertNotEqual(name1, name2)
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- import sys
- if len(sys.argv) > 1 and os.path.isdir(sys.argv[-1]):
- GLYPHSETDIR = sys.argv.pop()
- runTests()
diff --git a/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py b/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py
deleted file mode 100644
index 36dcc0582..000000000
--- a/misc/pylib/robofab/test/test_noneLabUFOReadWrite.py
+++ /dev/null
@@ -1,321 +0,0 @@
-import os
-import shutil
-import unittest
-import tempfile
-from robofab.plistlib import readPlist
-import robofab
-from robofab.test.testSupport import fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
-from robofab.objects.objectsRF import NewFont, OpenFont
-from robofab.ufoLib import UFOReader
-
-ufoPath1 = os.path.dirname(robofab.__file__)
-ufoPath1 = os.path.dirname(ufoPath1)
-ufoPath1 = os.path.dirname(ufoPath1)
-ufoPath1 = os.path.join(ufoPath1, "TestData", "TestFont1 (UFO1).ufo")
-ufoPath2 = ufoPath1.replace("TestFont1 (UFO1).ufo", "TestFont1 (UFO2).ufo")
-
-# robofab should remove these from the lib after a load.
-removeFromFormatVersion1Lib = [
- "org.robofab.opentype.classes",
- "org.robofab.opentype.features",
- "org.robofab.opentype.featureorder",
- "org.robofab.postScriptHintData"
-]
-
-
-class ReadUFOFormatVersion1TestCase(unittest.TestCase):
-
- def setUpFont(self):
- self.font = OpenFont(ufoPath1)
- self.font.update()
-
- def tearDownFont(self):
- self.font.close()
- self.font = None
-
- def compareToUFO(self, doInfo=True):
- reader = UFOReader(ufoPath1)
- results = {}
- # info
- infoMatches = True
- info = self.font.info
- for attr, expectedValue in expectedFontInfo1To2Conversion.items():
- writtenValue = getattr(info, attr)
- if expectedValue != writtenValue:
- infoMatches = False
- break
- results["info"]= infoMatches
- # kerning
- kerning = self.font.kerning.asDict()
- expectedKerning = reader.readKerning()
- results["kerning"] = expectedKerning == kerning
- # groups
- groups = dict(self.font.groups)
- expectedGroups = reader.readGroups()
- results["groups"] = expectedGroups == groups
- # features
- features = self.font.features.text
- f = open(os.path.join(ufoPath2, "features.fea"), "r")
- expectedFeatures = f.read()
- f.close()
- match = True
- features = [line for line in features.splitlines() if line]
- expectedFeatures = [line for line in expectedFeatures.splitlines() if line]
- if expectedFeatures != features or reader.readFeatures() != "":
- match = False
- results["features"] = match
- # lib
- lib = dict(self.font.lib)
- expectedLib = reader.readLib()
- for key in removeFromFormatVersion1Lib:
- if key in expectedLib:
- del expectedLib[key]
- results["lib"] = expectedLib == lib
- return results
-
- def testFull(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont()
- info = self.font.info
- for attr, expectedValue in expectedFontInfo1To2Conversion.items():
- writtenValue = getattr(info, attr)
- self.assertEqual((attr, expectedValue), (attr, writtenValue))
- self.tearDownFont()
-
-
-class ReadUFOFormatVersion2TestCase(unittest.TestCase):
-
- def setUpFont(self):
- self.font = OpenFont(ufoPath2)
- self.font.update()
-
- def tearDownFont(self):
- self.font.close()
- self.font = None
-
- def compareToUFO(self, doInfo=True):
- reader = UFOReader(ufoPath2)
- results = {}
- # info
- infoMatches = True
- info = self.font.info
- for attr, expectedValue in fontInfoVersion2.items():
- writtenValue = getattr(info, attr)
- if expectedValue != writtenValue:
- infoMatches = False
- break
- results["info"]= infoMatches
- # kerning
- kerning = self.font.kerning.asDict()
- expectedKerning = reader.readKerning()
- results["kerning"] = expectedKerning == kerning
- # groups
- groups = dict(self.font.groups)
- expectedGroups = reader.readGroups()
- results["groups"] = expectedGroups == groups
- # features
- features = self.font.features.text
- expectedFeatures = reader.readFeatures()
- results["features"] = expectedFeatures == features
- # lib
- lib = dict(self.font.lib)
- expectedLib = reader.readLib()
- results["lib"] = expectedLib == lib
- return results
-
- def testFull(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
- def testInfo(self):
- self.setUpFont()
- info = self.font.info
- for attr, expectedValue in fontInfoVersion2.items():
- writtenValue = getattr(info, attr)
- self.assertEqual((attr, expectedValue), (attr, writtenValue))
- self.tearDownFont()
-
-
-class WriteUFOFormatVersion1TestCase(unittest.TestCase):
-
- def setUpFont(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- self.font = OpenFont(ufoPath2)
- self.font.save(self.dstDir, formatVersion=1)
-
- def tearDownFont(self):
- shutil.rmtree(self.dstDir)
-
- def compareToUFO(self):
- readerExpected = UFOReader(ufoPath1)
- readerWritten = UFOReader(self.dstDir)
- results = {}
- # info
- matches = True
- expectedPath = os.path.join(ufoPath1, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- for attr, expectedValue in expected.items():
- if expectedValue != written.get(attr):
- matches = False
- break
- results["info"] = matches
- # kerning
- matches = True
- expectedPath = os.path.join(ufoPath1, "kerning.plist")
- writtenPath = os.path.join(self.dstDir, "kerning.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["kerning"] = matches
- # groups
- matches = True
- expectedPath = os.path.join(ufoPath1, "groups.plist")
- writtenPath = os.path.join(self.dstDir, "groups.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["groups"] = matches
- # features
- matches = True
- expectedPath = os.path.join(ufoPath1, "features.fea")
- writtenPath = os.path.join(self.dstDir, "features.fea")
- if os.path.exists(writtenPath):
- matches = False
- results["features"] = matches
- # lib
- matches = True
- expectedPath = os.path.join(ufoPath1, "lib.plist")
- writtenPath = os.path.join(self.dstDir, "lib.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- writtenLib = readPlist(writtenPath)
- matches = readPlist(expectedPath) == writtenLib
- results["lib"] = matches
- return results
-
- def testFull(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-class WriteUFOFormatVersion2TestCase(unittest.TestCase):
-
- def setUpFont(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- self.font = OpenFont(ufoPath2)
- self.font.save(self.dstDir)
-
- def tearDownFont(self):
- shutil.rmtree(self.dstDir)
-
- def compareToUFO(self):
- readerExpected = UFOReader(ufoPath2)
- readerWritten = UFOReader(self.dstDir)
- results = {}
- # info
- matches = True
- expectedPath = os.path.join(ufoPath2, "fontinfo.plist")
- writtenPath = os.path.join(self.dstDir, "fontinfo.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- expected = readPlist(expectedPath)
- written = readPlist(writtenPath)
- for attr, expectedValue in expected.items():
- if expectedValue != written[attr]:
- matches = False
- break
- results["info"] = matches
- # kerning
- matches = True
- expectedPath = os.path.join(ufoPath2, "kerning.plist")
- writtenPath = os.path.join(self.dstDir, "kerning.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["kerning"] = matches
- # groups
- matches = True
- expectedPath = os.path.join(ufoPath2, "groups.plist")
- writtenPath = os.path.join(self.dstDir, "groups.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- matches = readPlist(expectedPath) == readPlist(writtenPath)
- results["groups"] = matches
- # features
- matches = True
- expectedPath = os.path.join(ufoPath2, "features.fea")
- writtenPath = os.path.join(self.dstDir, "features.fea")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- f = open(expectedPath, "r")
- expectedText = f.read()
- f.close()
- f = open(writtenPath, "r")
- writtenText = f.read()
- f.close()
- # FontLab likes to add lines to the features, so skip blank lines.
- expectedText = [line for line in expectedText.splitlines() if line]
- writtenText = [line for line in writtenText.splitlines() if line]
- matches = "\n".join(expectedText) == "\n".join(writtenText)
- results["features"] = matches
- # lib
- matches = True
- expectedPath = os.path.join(ufoPath2, "lib.plist")
- writtenPath = os.path.join(self.dstDir, "lib.plist")
- if not os.path.exists(writtenPath):
- matches = False
- else:
- writtenLib = readPlist(writtenPath)
- matches = readPlist(expectedPath) == writtenLib
- results["lib"] = matches
- return results
-
- def testFull(self):
- self.setUpFont()
- otherResults = self.compareToUFO()
- self.assertEqual(otherResults["info"], True)
- self.assertEqual(otherResults["kerning"], True)
- self.assertEqual(otherResults["groups"], True)
- self.assertEqual(otherResults["features"], True)
- self.assertEqual(otherResults["lib"], True)
- self.tearDownFont()
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_objectsFL.py b/misc/pylib/robofab/test/test_objectsFL.py
deleted file mode 100755
index 948897097..000000000
--- a/misc/pylib/robofab/test/test_objectsFL.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""This test suite for various FontLab-specific tests."""
-
-
-import FL # needed to quickly raise ImportError if run outside of FL
-
-
-import os
-import tempfile
-import unittest
-
-from robofab.world import NewFont
-from robofab.test.testSupport import getDemoFontPath, getDemoFontGlyphSetPath
-from robofab.tools.glifImport import importAllGlifFiles
-from robofab.pens.digestPen import DigestPointPen
-from robofab.pens.adapterPens import SegmentToPointPen
-
-
-def getDigests(font):
- digests = {}
- for glyphName in font.keys():
- pen = DigestPointPen()
- font[glyphName].drawPoints(pen)
- digests[glyphName] = pen.getDigest()
- return digests
-
-
-class FLTestCase(unittest.TestCase):
-
- def testUFOVersusGlifImport(self):
- font = NewFont()
- font.readUFO(getDemoFontPath(), doProgress=False)
- d1 = getDigests(font)
- font.close(False)
- font = NewFont()
- importAllGlifFiles(font.naked(), getDemoFontGlyphSetPath(), doProgress=False)
- d2 = getDigests(font)
- self.assertEqual(d1, d2)
- font.close(False)
-
- def testTwoUntitledFonts(self):
- font1 = NewFont()
- font2 = NewFont()
- font1.unitsPerEm = 1024
- font2.unitsPerEm = 2048
- self.assertNotEqual(font1.unitsPerEm, font2.unitsPerEm)
- font1.update()
- font2.update()
- font1.close(False)
- font2.close(False)
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_objectsUFO.py b/misc/pylib/robofab/test/test_objectsUFO.py
deleted file mode 100755
index c974a121c..000000000
--- a/misc/pylib/robofab/test/test_objectsUFO.py
+++ /dev/null
@@ -1,203 +0,0 @@
-"""This test suite for ufo glyph methods"""
-
-
-import unittest
-import os
-import tempfile
-import shutil
-
-from robofab.objects.objectsRF import RFont
-from robofab.test.testSupport import getDemoFontPath
-from robofab.pens.digestPen import DigestPointPen
-from robofab.pens.adapterPens import SegmentToPointPen, FabToFontToolsPenAdapter
-
-
-class ContourMethodsTestCase(unittest.TestCase):
-
- def setUp(self):
- self.font = RFont(getDemoFontPath())
-
- def testReverseContour(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- for contour in glyph:
- contour.reverseContour()
- contour.reverseContour()
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after reversing twice" % glyph.name)
-
- def testStartSegment(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- for contour in glyph:
- contour.setStartSegment(2)
- contour.setStartSegment(-2)
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after seting start segment twice" % glyph.name)
-
- def testAppendSegment(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- for contour in glyph:
- contour.insertSegment(2, "curve", [(100, 100), (200, 200), (300, 300)])
- contour.removeSegment(2)
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after inserting and removing segment" % glyph.name)
-
-
-class GlyphsMethodsTestCase(ContourMethodsTestCase):
-
- def testCopyGlyph(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- copy = glyph.copy()
- pen = DigestPointPen()
- copy.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after copying" % glyph.name)
- self.assertEqual(glyph.lib, copy.lib, "%r's lib not the same after copying" % glyph.name)
- self.assertEqual(glyph.width, copy.width, "%r's width not the same after copying" % glyph.name)
- self.assertEqual(glyph.unicodes, copy.unicodes, "%r's unicodes not the same after copying" % glyph.name)
-
- def testMoveGlyph(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- glyph.move((100, 200))
- glyph.move((-100, -200))
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after moving twice" % glyph.name)
-
- def testScaleGlyph(self):
- for glyph in self.font:
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest1 = pen.getDigest()
- glyph.scale((2, 2))
- glyph.scale((.5, .5))
- pen = DigestPointPen()
- glyph.drawPoints(pen)
- digest2 = pen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same after scaling twice" % glyph.name)
-
- def testSegmentPenInterface(self):
- for glyph in self.font:
- digestPen = DigestPointPen(ignoreSmoothAndName=True)
- pen = SegmentToPointPen(digestPen)
- glyph.draw(pen)
- digest1 = digestPen.getDigest()
- digestPen = DigestPointPen(ignoreSmoothAndName=True)
- glyph.drawPoints(digestPen)
- digest2 = digestPen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name)
-
- def testFabPenCompatibility(self):
- for glyph in self.font:
- digestPen = DigestPointPen(ignoreSmoothAndName=True)
- pen = FabToFontToolsPenAdapter(SegmentToPointPen(digestPen))
- glyph.draw(pen)
- digest1 = digestPen.getDigest()
- digestPen = DigestPointPen(ignoreSmoothAndName=True)
- glyph.drawPoints(digestPen)
- digest2 = digestPen.getDigest()
- self.assertEqual(digest1, digest2, "%r not the same for gl.draw() and gl.drawPoints()" % glyph.name)
-
- def testComponentTransformations(self):
- from robofab.objects.objectsRF import RComponent
- name = "baseGlyphName"
- c = RComponent(name, transform=(1,0,0,1,0,0))
- # get values
- assert c.baseGlyph == "baseGlyphName"
- assert c.transformation == c.transformation
- assert c.scale == (1,1)
- assert c.offset == (0,0)
- # set values
- c.offset = (12,34)
- assert c.transformation == (1, 0, 0, 1, 12, 34)
- c.offset = (0,0)
- assert c.transformation == (1,0,0,1,0,0)
- c.scale = (12,34)
- assert c.transformation == (12, 0, 0, 34, 0, 0)
-
-
-class SaveTestCase(ContourMethodsTestCase):
-
- def testSaveAs(self):
- path = tempfile.mktemp(".ufo")
- try:
- keys1 = self.font.keys()
- self.font.save(path)
- keys2 = self.font.keys()
- keys1.sort()
- keys2.sort()
- self.assertEqual(keys1, keys2)
- self.assertEqual(self.font.path, path)
- font2 = RFont(path)
- keys3 = font2.keys()
- keys3.sort()
- self.assertEqual(keys1, keys3)
- finally:
- if os.path.exists(path):
- shutil.rmtree(path)
-
- def testSaveAs2(self):
- path = tempfile.mktemp(".ufo")
- # copy a glyph
- self.font["X"] = self.font["a"].copy()
-# self.assertEqual(self.font["X"].name, "X")
- # remove a glyph
- self.font.removeGlyph("a")
- keys1 = self.font.keys()
- try:
- self.font.save(path)
- self.assertEqual(self.font.path, path)
- keys2 = self.font.keys()
- keys1.sort()
- keys2.sort()
- self.assertEqual(keys1, keys2)
- font2 = RFont(path)
- keys3 = font2.keys()
- keys3.sort()
- self.assertEqual(keys1, keys3)
- finally:
- if os.path.exists(path):
- shutil.rmtree(path)
-
- def testCustomFileNameScheme(self):
- path = tempfile.mktemp(".ufo")
- libKey = "org.robofab.glyphNameToFileNameFuncName"
- self.font.lib[libKey] = "robofab.test.test_objectsUFO.testGlyphNameToFileName"
- try:
- self.font.save(path)
- self.assertEqual(os.path.exists(os.path.join(path,
- "glyphs", "test_a.glif")), True)
- finally:
- if os.path.exists(path):
- shutil.rmtree(path)
-
-
-def testGlyphNameToFileName(glyphName, glyphSet):
- from robofab.glifLib import glyphNameToFileName
- return "test_" + glyphNameToFileName(glyphName, glyphSet)
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_pens.py b/misc/pylib/robofab/test/test_pens.py
deleted file mode 100755
index a2a773cb2..000000000
--- a/misc/pylib/robofab/test/test_pens.py
+++ /dev/null
@@ -1,149 +0,0 @@
-"""This test suite test general Pen stuff, it should not contain
-FontLab-specific code.
-"""
-
-import unittest
-
-from robofab.pens.digestPen import DigestPointPen
-from robofab.pens.adapterPens import SegmentToPointPen, PointToSegmentPen
-from robofab.pens.adapterPens import GuessSmoothPointPen
-from robofab.pens.reverseContourPointPen import ReverseContourPointPen
-from robofab.test.testSupport import getDemoFontGlyphSetPath
-from robofab.glifLib import GlyphSet
-
-
-class TestShapes:
-
- # Collection of test shapes. It's probably better to add these as
- # glyphs to the demo font.
-
- def square(pen):
- # a simple square as a closed path (100, 100, 600, 600)
- pen.beginPath()
- pen.addPoint((100, 100), "line")
- pen.addPoint((100, 600), "line")
- pen.addPoint((600, 600), "line")
- pen.addPoint((600, 100), "line")
- pen.endPath()
- square = staticmethod(square)
-
- def onCurveLessQuadShape(pen):
- pen.beginPath()
- pen.addPoint((100, 100))
- pen.addPoint((100, 600))
- pen.addPoint((600, 600))
- pen.addPoint((600, 100))
- pen.endPath()
- onCurveLessQuadShape = staticmethod(onCurveLessQuadShape)
-
- def openPath(pen):
- # a simple square as a closed path (100, 100, 600, 600)
- pen.beginPath()
- pen.addPoint((100, 100), "move")
- pen.addPoint((100, 600), "line")
- pen.addPoint((600, 600), "line")
- pen.addPoint((600, 100), "line")
- pen.endPath()
- openPath = staticmethod(openPath)
-
- def circle(pen):
- pen.beginPath()
- pen.addPoint((0, 500), "curve")
- pen.addPoint((0, 800))
- pen.addPoint((200, 1000))
- pen.addPoint((500, 1000), "curve")
- pen.addPoint((800, 1000))
- pen.addPoint((1000, 800))
- pen.addPoint((1000, 500), "curve")
- pen.addPoint((1000, 200))
- pen.addPoint((800, 0))
- pen.addPoint((500, 0), "curve")
- pen.addPoint((200, 0))
- pen.addPoint((0, 200))
- pen.endPath()
- circle = staticmethod(circle)
-
-
-class RoundTripTestCase(unittest.TestCase):
-
- def _doTest(self, shapeFunc, shapeName):
- pen = DigestPointPen(ignoreSmoothAndName=True)
- shapeFunc(pen)
- digest1 = pen.getDigest()
-
- digestPen = DigestPointPen(ignoreSmoothAndName=True)
- pen = PointToSegmentPen(SegmentToPointPen(digestPen))
- shapeFunc(pen)
- digest2 = digestPen.getDigest()
- self.assertEqual(digest1, digest2, "%r failed round tripping" % shapeName)
-
- def testShapes(self):
- for name in dir(TestShapes):
- if name[0] != "_":
- self._doTest(getattr(TestShapes, name), name)
-
- def testShapesFromGlyphSet(self):
- glyphSet = GlyphSet(getDemoFontGlyphSetPath())
- for name in glyphSet.keys():
- self._doTest(glyphSet[name].drawPoints, name)
-
- def testGuessSmoothPen(self):
- glyphSet = GlyphSet(getDemoFontGlyphSetPath())
- for name in glyphSet.keys():
- digestPen = DigestPointPen()
- glyphSet[name].drawPoints(digestPen)
- digest1 = digestPen.getDigest()
- digestPen = DigestPointPen()
- pen = GuessSmoothPointPen(digestPen)
- glyphSet[name].drawPoints(pen)
- digest2 = digestPen.getDigest()
- self.assertEqual(digest1, digest2)
-
-
-class ReverseContourTestCase(unittest.TestCase):
-
- def testReverseContourClosedPath(self):
- digestPen = DigestPointPen()
- TestShapes.square(digestPen)
- d1 = digestPen.getDigest()
- digestPen = DigestPointPen()
- pen = ReverseContourPointPen(digestPen)
- pen.beginPath()
- pen.addPoint((100, 100), "line")
- pen.addPoint((600, 100), "line")
- pen.addPoint((600, 600), "line")
- pen.addPoint((100, 600), "line")
- pen.endPath()
- d2 = digestPen.getDigest()
- self.assertEqual(d1, d2)
-
- def testReverseContourOpenPath(self):
- digestPen = DigestPointPen()
- TestShapes.openPath(digestPen)
- d1 = digestPen.getDigest()
- digestPen = DigestPointPen()
- pen = ReverseContourPointPen(digestPen)
- pen.beginPath()
- pen.addPoint((600, 100), "move")
- pen.addPoint((600, 600), "line")
- pen.addPoint((100, 600), "line")
- pen.addPoint((100, 100), "line")
- pen.endPath()
- d2 = digestPen.getDigest()
- self.assertEqual(d1, d2)
-
- def testReversContourFromGlyphSet(self):
- glyphSet = GlyphSet(getDemoFontGlyphSetPath())
- digestPen = DigestPointPen()
- glyphSet["testglyph1"].drawPoints(digestPen)
- digest1 = digestPen.getDigest()
- digestPen = DigestPointPen()
- pen = ReverseContourPointPen(digestPen)
- glyphSet["testglyph1.reversed"].drawPoints(pen)
- digest2 = digestPen.getDigest()
- self.assertEqual(digest1, digest2)
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/test/test_psHints.py b/misc/pylib/robofab/test/test_psHints.py
deleted file mode 100644
index 991e9d9cc..000000000
--- a/misc/pylib/robofab/test/test_psHints.py
+++ /dev/null
@@ -1,110 +0,0 @@
-def test():
- """
- # some tests for the ps Hints operations
- >>> from robofab.world import RFont, RGlyph
- >>> g = RGlyph()
- >>> g.psHints.isEmpty()
- True
-
- >>> h = RGlyph()
- >>> i = g + h
- >>> i.psHints.isEmpty()
- True
-
- >>> i = g - h
- >>> i.psHints.isEmpty()
- True
-
- >>> i = g * 2
- >>> i.psHints.isEmpty()
- True
-
- >>> i = g / 2
- >>> i.psHints.isEmpty()
- True
-
- >>> g.psHints.vHints = [(100, 50), (200, 50)]
- >>> g.psHints.hHints = [(100, 50), (200, 5)]
-
- >>> not g.psHints.isEmpty()
- True
-
- >>> gc = g.copy()
- >>> gc.psHints.asDict() == g.psHints.asDict()
- True
-
- # multiplication
- >>> v = g.psHints * 2
- >>> v.asDict() == {'vHints': [[200, 100], [400, 100]], 'hHints': [[200, 100], [400, 10]]}
- True
-
- # division
- >>> v = g.psHints / 2
- >>> v.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
- True
-
- # multiplication with x, y, factor
- # vertically oriented values should respond different
- >>> v = g.psHints * (.5, 10)
- >>> v.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
- True
-
- # division with x, y, factor
- # vertically oriented values should respond different
- >>> v = g.psHints / (.5, 10)
- >>> v.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]}
- True
-
- # rounding to integer
- >>> v = g.psHints / 2
- >>> v.round()
- >>> v.asDict() == {'vHints': [(50, 25), (100, 25)], 'hHints': [(50, 25), (100, 3)]}
- True
-
- # "ps hint values calculating with a glyph"
- # ps hint values as part of glyphmath operations.
- # multiplication
- >>> h = g * 10
- >>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[1000, 500], [2000, 50]]}
- True
-
- # division
- >>> h = g / 2
- >>> h.psHints.asDict() == {'vHints': [[50.0, 25.0], [100.0, 25.0]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
- True
-
- # x, y factor multiplication
- >>> h = g * (.5, 10)
- >>> h.psHints.asDict() == {'vHints': [[1000, 500], [2000, 500]], 'hHints': [[50.0, 25.0], [100.0, 2.5]]}
- True
-
- # x, y factor division
- >>> h = g / (.5, 10)
- >>> h.psHints.asDict() == {'vHints': [[10.0, 5.0], [20.0, 5.0]], 'hHints': [[200.0, 100.0], [400.0, 10.0]]}
- True
-
- # "font ps hint values"
- >>> f = RFont()
- >>> f.psHints.isEmpty()
- True
-
- >>> f.psHints.blueScale = .5
- >>> f.psHints.blueShift = 1
- >>> f.psHints.blueFuzz = 1
- >>> f.psHints.forceBold = True
- >>> f.psHints.hStems = (100, 90)
- >>> f.psHints.vStems = (500, 10)
-
- >>> not f.psHints.isEmpty()
- True
-
- >>> f.insertGlyph(g, name="new")
- <RGlyph for None.new>
- >>> f["new"].psHints.asDict() == g.psHints.asDict()
- True
- """
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
-
diff --git a/misc/pylib/robofab/test/test_ufoLib.py b/misc/pylib/robofab/test/test_ufoLib.py
deleted file mode 100644
index e1da807bb..000000000
--- a/misc/pylib/robofab/test/test_ufoLib.py
+++ /dev/null
@@ -1,1659 +0,0 @@
-import os
-import shutil
-import unittest
-import tempfile
-from plistlib import writePlist, readPlist
-from robofab.ufoLib import UFOReader, UFOWriter, UFOLibError, \
- convertUFOFormatVersion1ToFormatVersion2, convertUFOFormatVersion2ToFormatVersion1
-from robofab.test.testSupport import fontInfoVersion1, fontInfoVersion2, expectedFontInfo1To2Conversion, expectedFontInfo2To1Conversion
-
-
-# the format version 1 lib.plist contains some data
-# that these tests shouldn't be concerned about.
-removeFromFormatVersion1Lib = [
- "org.robofab.opentype.classes",
- "org.robofab.opentype.features",
- "org.robofab.opentype.featureorder",
- "org.robofab.postScriptHintData"
-]
-
-
-class TestInfoObject(object): pass
-
-
-class ReadFontInfoVersion1TestCase(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- metaInfo = {
- "creator": "test",
- "formatVersion": 1
- }
- path = os.path.join(self.dstDir, "metainfo.plist")
- writePlist(metaInfo, path)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def _writeInfoToPlist(self, info):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- writePlist(info, path)
-
- def testRead(self):
- originalData = dict(fontInfoVersion1)
- self._writeInfoToPlist(originalData)
- infoObject = TestInfoObject()
- reader = UFOReader(self.dstDir)
- reader.readInfo(infoObject)
- for attr in dir(infoObject):
- if attr not in fontInfoVersion2:
- continue
- originalValue = fontInfoVersion2[attr]
- readValue = getattr(infoObject, attr)
- self.assertEqual(originalValue, readValue)
-
- def testFontStyleConversion(self):
- fontStyle1To2 = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
- }
- for old, new in fontStyle1To2.items():
- info = dict(fontInfoVersion1)
- info["fontStyle"] = old
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- infoObject = TestInfoObject()
- reader.readInfo(infoObject)
- self.assertEqual(new, infoObject.styleMapStyleName)
-
- def testWidthNameConversion(self):
- widthName1To2 = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9
- }
- for old, new in widthName1To2.items():
- info = dict(fontInfoVersion1)
- info["widthName"] = old
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- infoObject = TestInfoObject()
- reader.readInfo(infoObject)
- self.assertEqual(new, infoObject.openTypeOS2WidthClass)
-
-
-class ReadFontInfoVersion2TestCase(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- metaInfo = {
- "creator": "test",
- "formatVersion": 2
- }
- path = os.path.join(self.dstDir, "metainfo.plist")
- writePlist(metaInfo, path)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def _writeInfoToPlist(self, info):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- writePlist(info, path)
-
- def testRead(self):
- originalData = dict(fontInfoVersion2)
- self._writeInfoToPlist(originalData)
- infoObject = TestInfoObject()
- reader = UFOReader(self.dstDir)
- reader.readInfo(infoObject)
- readData = {}
- for attr in fontInfoVersion2.keys():
- readData[attr] = getattr(infoObject, attr)
- self.assertEqual(originalData, readData)
-
- def testGenericRead(self):
- # familyName
- info = dict(fontInfoVersion2)
- info["familyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleName
- info = dict(fontInfoVersion2)
- info["styleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapFamilyName
- info = dict(fontInfoVersion2)
- info["styleMapFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapStyleName
- ## not a string
- info = dict(fontInfoVersion2)
- info["styleMapStyleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["styleMapStyleName"] = "REGULAR"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMajor
- info = dict(fontInfoVersion2)
- info["versionMajor"] = "1"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMinor
- info = dict(fontInfoVersion2)
- info["versionMinor"] = "0"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # copyright
- info = dict(fontInfoVersion2)
- info["copyright"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # trademark
- info = dict(fontInfoVersion2)
- info["trademark"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # unitsPerEm
- info = dict(fontInfoVersion2)
- info["unitsPerEm"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # descender
- info = dict(fontInfoVersion2)
- info["descender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # xHeight
- info = dict(fontInfoVersion2)
- info["xHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # capHeight
- info = dict(fontInfoVersion2)
- info["capHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # ascender
- info = dict(fontInfoVersion2)
- info["ascender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # italicAngle
- info = dict(fontInfoVersion2)
- info["italicAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testHeadRead(self):
- # openTypeHeadCreated
- ## not a string
- info = dict(fontInfoVersion2)
- info["openTypeHeadCreated"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## invalid format
- info = dict(fontInfoVersion2)
- info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadLowestRecPPEM
- info = dict(fontInfoVersion2)
- info["openTypeHeadLowestRecPPEM"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadFlags
- info = dict(fontInfoVersion2)
- info["openTypeHeadFlags"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testHheaRead(self):
- # openTypeHheaAscender
- info = dict(fontInfoVersion2)
- info["openTypeHheaAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaDescender
- info = dict(fontInfoVersion2)
- info["openTypeHheaDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaLineGap
- info = dict(fontInfoVersion2)
- info["openTypeHheaLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRise
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRun
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretOffset
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testNameRead(self):
- # openTypeNameDesigner
- info = dict(fontInfoVersion2)
- info["openTypeNameDesigner"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDesignerURL
- info = dict(fontInfoVersion2)
- info["openTypeNameDesignerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturer
- info = dict(fontInfoVersion2)
- info["openTypeNameManufacturer"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturerURL
- info = dict(fontInfoVersion2)
- info["openTypeNameManufacturerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicense
- info = dict(fontInfoVersion2)
- info["openTypeNameLicense"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicenseURL
- info = dict(fontInfoVersion2)
- info["openTypeNameLicenseURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameVersion
- info = dict(fontInfoVersion2)
- info["openTypeNameVersion"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameUniqueID
- info = dict(fontInfoVersion2)
- info["openTypeNameUniqueID"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDescription
- info = dict(fontInfoVersion2)
- info["openTypeNameDescription"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredFamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNamePreferredFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredSubfamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNamePreferredSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameCompatibleFullName
- info = dict(fontInfoVersion2)
- info["openTypeNameCompatibleFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameSampleText
- info = dict(fontInfoVersion2)
- info["openTypeNameSampleText"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSFamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNameWWSFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSSubfamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNameWWSSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testOS2Read(self):
- # openTypeOS2WidthClass
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2WidthClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out or range
- info = dict(fontInfoVersion2)
- info["openTypeOS2WidthClass"] = 15
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WeightClass
- info = dict(fontInfoVersion2)
- ## not an int
- info["openTypeOS2WeightClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info["openTypeOS2WeightClass"] = -50
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Selection
- info = dict(fontInfoVersion2)
- info["openTypeOS2Selection"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2VendorID
- info = dict(fontInfoVersion2)
- info["openTypeOS2VendorID"] = 1234
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Panose
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2FamilyClass
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, str(1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, 1, 1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, 201]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2UnicodeRanges
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2UnicodeRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2UnicodeRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2CodePageRanges
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2CodePageRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2CodePageRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoAscender
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoDescender
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoLineGap
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinAscent
- info = dict(fontInfoVersion2)
- info["openTypeOS2WinAscent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinDescent
- info = dict(fontInfoVersion2)
- info["openTypeOS2WinDescent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Type
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2Type"] = ["1"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2Type"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2StrikeoutSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutPosition
- info = dict(fontInfoVersion2)
- info["openTypeOS2StrikeoutPosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testVheaRead(self):
- # openTypeVheaVertTypoAscender
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoDescender
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoLineGap
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRise
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRun
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretOffset
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testFONDRead(self):
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion2)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion2)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testPostscriptRead(self):
- # postscriptFontName
- info = dict(fontInfoVersion2)
- info["postscriptFontName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptFullName
- info = dict(fontInfoVersion2)
- info["postscriptFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptSlantAngle
- info = dict(fontInfoVersion2)
- info["postscriptSlantAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptUniqueID
- info = dict(fontInfoVersion2)
- info["postscriptUniqueID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlineThickness
- info = dict(fontInfoVersion2)
- info["postscriptUnderlineThickness"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlinePosition
- info = dict(fontInfoVersion2)
- info["postscriptUnderlinePosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptIsFixedPitch
- info = dict(fontInfoVersion2)
- info["postscriptIsFixedPitch"] = 2
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueValues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptOtherBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyOtherBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapH
- ## not list
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapH"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapH"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapV
- ## not list
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapV"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapV"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueFuzz
- info = dict(fontInfoVersion2)
- info["postscriptBlueFuzz"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueShift
- info = dict(fontInfoVersion2)
- info["postscriptBlueShift"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueScale
- info = dict(fontInfoVersion2)
- info["postscriptBlueScale"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptForceBold
- info = dict(fontInfoVersion2)
- info["postscriptForceBold"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultWidthX
- info = dict(fontInfoVersion2)
- info["postscriptDefaultWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptNominalWidthX
- info = dict(fontInfoVersion2)
- info["postscriptNominalWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWeightName
- info = dict(fontInfoVersion2)
- info["postscriptWeightName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultCharacter
- info = dict(fontInfoVersion2)
- info["postscriptDefaultCharacter"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWindowsCharacterSet
- info = dict(fontInfoVersion2)
- info["postscriptWindowsCharacterSet"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion2)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion2)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
-
-
-class WriteFontInfoVersion1TestCase(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def makeInfoObject(self):
- infoObject = TestInfoObject()
- for attr, value in fontInfoVersion2.items():
- setattr(infoObject, attr, value)
- return infoObject
-
- def readPlist(self):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- return readPlist(path)
-
- def testWrite(self):
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- for attr, originalValue in fontInfoVersion1.items():
- newValue = writtenData[attr]
- self.assertEqual(newValue, originalValue)
-
- def testFontStyleConversion(self):
- fontStyle1To2 = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
- }
- for old, new in fontStyle1To2.items():
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = new
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- self.assertEqual(writtenData["fontStyle"], old)
-
- def testWidthNameConversion(self):
- widthName1To2 = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9
- }
- for old, new in widthName1To2.items():
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = new
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- self.assertEqual(writtenData["widthName"], old)
-
-
-class WriteFontInfoVersion2TestCase(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def makeInfoObject(self):
- infoObject = TestInfoObject()
- for attr, value in fontInfoVersion2.items():
- setattr(infoObject, attr, value)
- return infoObject
-
- def readPlist(self):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- return readPlist(path)
-
- def testWrite(self):
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- for attr, originalValue in fontInfoVersion2.items():
- newValue = writtenData[attr]
- self.assertEqual(newValue, originalValue)
-
- def testGenericWrite(self):
- # familyName
- infoObject = self.makeInfoObject()
- infoObject.familyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleName
- infoObject = self.makeInfoObject()
- infoObject.styleName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleMapFamilyName
- infoObject = self.makeInfoObject()
- infoObject.styleMapFamilyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleMapStyleName
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = "REGULAR"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # versionMajor
- infoObject = self.makeInfoObject()
- infoObject.versionMajor = "1"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # versionMinor
- infoObject = self.makeInfoObject()
- infoObject.versionMinor = "0"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # copyright
- infoObject = self.makeInfoObject()
- infoObject.copyright = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # trademark
- infoObject = self.makeInfoObject()
- infoObject.trademark = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # unitsPerEm
- infoObject = self.makeInfoObject()
- infoObject.unitsPerEm = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # descender
- infoObject = self.makeInfoObject()
- infoObject.descender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # xHeight
- infoObject = self.makeInfoObject()
- infoObject.xHeight = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # capHeight
- infoObject = self.makeInfoObject()
- infoObject.capHeight = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # ascender
- infoObject = self.makeInfoObject()
- infoObject.ascender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # italicAngle
- infoObject = self.makeInfoObject()
- infoObject.italicAngle = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testHeadWrite(self):
- # openTypeHeadCreated
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## invalid format
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHeadLowestRecPPEM
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadLowestRecPPEM = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHeadFlags
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadFlags = [-1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testHheaWrite(self):
- # openTypeHheaAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaAscender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaDescender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaLineGap = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testNameWrite(self):
- # openTypeNameDesigner
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesigner = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameDesignerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesignerURL = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameManufacturer
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturer = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameManufacturerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturerURL = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameLicense
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicense = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameLicenseURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicenseURL = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameVersion
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameVersion = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameUniqueID
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameUniqueID = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameDescription
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDescription = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNamePreferredFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredFamilyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNamePreferredSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredSubfamilyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameCompatibleFullName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameCompatibleFullName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameSampleText
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameSampleText = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameWWSFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSFamilyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameWWSSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSSubfamilyName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testOS2Write(self):
- # openTypeOS2WidthClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out or range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = 15
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WeightClass
- infoObject = self.makeInfoObject()
- ## not an int
- infoObject.openTypeOS2WeightClass = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject.openTypeOS2WeightClass = -50
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Selection
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Selection = [-1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2VendorID
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2VendorID = 1234
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Panose
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2FamilyClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [0, str(1)]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 1, 1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 20]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2UnicodeRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = ["0"]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = [-1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2CodePageRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = ["0"]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = [-1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoAscender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoDescender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoLineGap = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WinAscent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinAscent = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WinDescent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinDescent = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Type
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = ["1"]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = [-1]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXSize = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYSize = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXSize = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYSize = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2StrikeoutSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutSize = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2StrikeoutPosition
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutPosition = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testVheaWrite(self):
- # openTypeVheaVertTypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoAscender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaVertTypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoDescender = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaVertTypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoLineGap = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testFONDWrite(self):
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
- def testPostscriptWrite(self):
- # postscriptFontName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFontName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFullName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFullName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptSlantAngle
- infoObject = self.makeInfoObject()
- infoObject.postscriptSlantAngle = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUniqueID
- infoObject = self.makeInfoObject()
- infoObject.postscriptUniqueID = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUnderlineThickness
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlineThickness = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUnderlinePosition
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlinePosition = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptIsFixedPitch
- infoObject = self.makeInfoObject()
- infoObject.postscriptIsFixedPitch = 2
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueValues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [500]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [500]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFamilyBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [500]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFamilyOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [500]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptStemSnapH
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptStemSnapV
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueFuzz
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueFuzz = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueShift
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueShift = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueScale
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueScale = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptForceBold
- infoObject = self.makeInfoObject()
- infoObject.postscriptForceBold = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptDefaultWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultWidthX = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptNominalWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptNominalWidthX = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptWeightName
- infoObject = self.makeInfoObject()
- infoObject.postscriptWeightName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptDefaultCharacter
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultCharacter = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptWindowsCharacterSet
- infoObject = self.makeInfoObject()
- infoObject.postscriptWindowsCharacterSet = -1
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
-
-
-
-
-class ConversionFunctionsTestCase(unittest.TestCase):
-
- def tearDown(self):
- path = self.getFontPath("TestFont1 (UFO1) converted.ufo")
- if os.path.exists(path):
- shutil.rmtree(path)
- path = self.getFontPath("TestFont1 (UFO2) converted.ufo")
- if os.path.exists(path):
- shutil.rmtree(path)
-
- def getFontPath(self, fileName):
- import robofab
- path = os.path.dirname(robofab.__file__)
- path = os.path.dirname(path)
- path = os.path.dirname(path)
- path = os.path.join(path, "TestData", fileName)
- return path
-
- def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures):
- # result
- metainfoPath1 = os.path.join(path1, "metainfo.plist")
- fontinfoPath1 = os.path.join(path1, "fontinfo.plist")
- kerningPath1 = os.path.join(path1, "kerning.plist")
- groupsPath1 = os.path.join(path1, "groups.plist")
- libPath1 = os.path.join(path1, "lib.plist")
- featuresPath1 = os.path.join(path1, "features.plist")
- glyphsPath1 = os.path.join(path1, "glyphs")
- glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist")
- glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif")
- glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif")
- # expected result
- metainfoPath2 = os.path.join(path2, "metainfo.plist")
- fontinfoPath2 = os.path.join(path2, "fontinfo.plist")
- kerningPath2 = os.path.join(path2, "kerning.plist")
- groupsPath2 = os.path.join(path2, "groups.plist")
- libPath2 = os.path.join(path2, "lib.plist")
- featuresPath2 = os.path.join(path2, "features.plist")
- glyphsPath2 = os.path.join(path2, "glyphs")
- glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist")
- glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif")
- glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif")
- # look for existence
- self.assertEqual(os.path.exists(metainfoPath1), True)
- self.assertEqual(os.path.exists(fontinfoPath1), True)
- self.assertEqual(os.path.exists(kerningPath1), True)
- self.assertEqual(os.path.exists(groupsPath1), True)
- self.assertEqual(os.path.exists(libPath1), True)
- self.assertEqual(os.path.exists(glyphsPath1), True)
- self.assertEqual(os.path.exists(glyphsPath1_contents), True)
- self.assertEqual(os.path.exists(glyphsPath1_A), True)
- self.assertEqual(os.path.exists(glyphsPath1_B), True)
- if testFeatures:
- self.assertEqual(os.path.exists(featuresPath1), True)
- # look for aggrement
- data1 = readPlist(metainfoPath1)
- data2 = readPlist(metainfoPath2)
- self.assertEqual(data1, data2)
- data1 = readPlist(fontinfoPath1)
- self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items()))
- data1 = readPlist(kerningPath1)
- data2 = readPlist(kerningPath2)
- self.assertEqual(data1, data2)
- data1 = readPlist(groupsPath1)
- data2 = readPlist(groupsPath2)
- self.assertEqual(data1, data2)
- data1 = readPlist(libPath1)
- data2 = readPlist(libPath2)
- if "UFO1" in libPath1:
- for key in removeFromFormatVersion1Lib:
- if key in data1:
- del data1[key]
- if "UFO1" in libPath2:
- for key in removeFromFormatVersion1Lib:
- if key in data2:
- del data2[key]
- self.assertEqual(data1, data2)
- data1 = readPlist(glyphsPath1_contents)
- data2 = readPlist(glyphsPath2_contents)
- self.assertEqual(data1, data2)
- data1 = readPlist(glyphsPath1_A)
- data2 = readPlist(glyphsPath2_A)
- self.assertEqual(data1, data2)
- data1 = readPlist(glyphsPath1_B)
- data2 = readPlist(glyphsPath2_B)
- self.assertEqual(data1, data2)
-
- def test1To2(self):
- path1 = self.getFontPath("TestFont1 (UFO1).ufo")
- path2 = self.getFontPath("TestFont1 (UFO1) converted.ufo")
- path3 = self.getFontPath("TestFont1 (UFO2).ufo")
- convertUFOFormatVersion1ToFormatVersion2(path1, path2)
- self.compareFileStructures(path2, path3, expectedFontInfo1To2Conversion, False)
-
- def test2To1(self):
- path1 = self.getFontPath("TestFont1 (UFO2).ufo")
- path2 = self.getFontPath("TestFont1 (UFO2) converted.ufo")
- path3 = self.getFontPath("TestFont1 (UFO1).ufo")
- convertUFOFormatVersion2ToFormatVersion1(path1, path2)
- self.compareFileStructures(path2, path3, expectedFontInfo2To1Conversion, False)
-
-
-if __name__ == "__main__":
- from robofab.test.testSupport import runTests
- runTests()
diff --git a/misc/pylib/robofab/tools/__init__.py b/misc/pylib/robofab/tools/__init__.py
deleted file mode 100755
index e97bd43c2..000000000
--- a/misc/pylib/robofab/tools/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
-
-Directory for all tool like code.
-Stuff that doesn't really belong to objects, pens, compilers etc.
-The code is split up into sections.
-
-
-"""
-
-
-
-
diff --git a/misc/pylib/robofab/tools/accentBuilder.py b/misc/pylib/robofab/tools/accentBuilder.py
deleted file mode 100755
index d122222fd..000000000
--- a/misc/pylib/robofab/tools/accentBuilder.py
+++ /dev/null
@@ -1,348 +0,0 @@
-"""A simple set of tools for building accented glyphs.
-# Hey look! A demonstration:
-from robofab.accentBuilder import AccentTools, buildRelatedAccentList
-font = CurrentFont
-# a list of accented glyphs that you want to build
-myList=['Aacute', 'aacute']
-# search for glyphs related to glyphs in myList and add them to myList
-myList=buildRelatedAccentList(font, myList)+myList
-# start the class
-at=AccentTools(font, myList)
-# clear away any anchors that exist (this is optional)
-at.clearAnchors()
-# add necessary anchors if you want to
-at.buildAnchors(ucXOffset=20, ucYOffset=40, lcXOffset=15, lcYOffset=30)
-# print a report of any errors that occured
-at.printAnchorErrors()
-# build the accented glyphs if you want to
-at.buildAccents()
-# print a report of any errors that occured
-at.printAccentErrors()
-"""
-#XXX! This is *very* experimental! I think it works, but you never know.
-
-from robofab.gString import lowercase_plain, accents, uppercase_plain, splitAccent, findAccentBase
-from robofab.tools.toolsAll import readGlyphConstructions
-import robofab
-from robofab.interface.all.dialogs import ProgressBar
-from robofab.world import RFWorld
-inFontLab = RFWorld().inFontLab
-
-anchorColor=125
-accentColor=75
-
-def stripSuffix(glyphName):
- """strip away unnecessary suffixes from a glyph name"""
- if glyphName.find('.') != -1:
- baseName = glyphName.split('.')[0]
- if glyphName.find('.sc') != -1:
- baseName = '.'.join([baseName, 'sc'])
- return baseName
- else:
- return glyphName
-
-def buildRelatedAccentList(font, list):
- """build a list of related glyphs suitable for use with AccentTools"""
- searchList = []
- baseGlyphs = {}
- foundList = []
- for glyphName in list:
- splitNames = splitAccent(glyphName)
- baseName = splitNames[0]
- accentNames = splitNames[1]
- if baseName not in searchList:
- searchList.append(baseName)
- if baseName not in baseGlyphs.keys():
- baseGlyphs[baseName] = [accentNames]
- else:
- baseGlyphs[baseName].append(accentNames)
- foundGlyphs = findRelatedGlyphs(font, searchList, doAccents=0)
- for baseGlyph in foundGlyphs.keys():
- for foundGlyph in foundGlyphs[baseGlyph]:
- for accentNames in baseGlyphs[baseGlyph]:
- foundList.append(makeAccentName(foundGlyph, accentNames))
- return foundList
-
-def findRelatedGlyphs(font, searchItem, doAccents=True):
- """Gather up a bunch of related glyph names. Send it either a
- single glyph name 'a', or a list of glyph names ['a', 'x'] and it
- returns a dict like: {'a': ['atilde', 'a.alt', 'a.swash']}. if doAccents
- is False it will skip accented glyph names.
- This is a relatively slow operation!"""
- relatedGlyphs = {}
- for name in font.keys():
- base = name.split('.')[0]
- if name not in relatedGlyphs.keys():
- relatedGlyphs[name] = []
- if base not in relatedGlyphs.keys():
- relatedGlyphs[base] = []
- if doAccents:
- accentBase = findAccentBase(name)
- if accentBase not in relatedGlyphs.keys():
- relatedGlyphs[accentBase] = []
- baseAccentBase = findAccentBase(base)
- if baseAccentBase not in relatedGlyphs.keys():
- relatedGlyphs[baseAccentBase] = []
- if base != name and name not in relatedGlyphs[base]:
- relatedGlyphs[base].append(name)
- if doAccents:
- if accentBase != name and name not in relatedGlyphs[accentBase]:
- relatedGlyphs[accentBase].append(name)
- if baseAccentBase != name and name not in relatedGlyphs[baseAccentBase]:
- relatedGlyphs[baseAccentBase].append(name)
- foundGlyphs = {}
- if isinstance(searchItem, str):
- searchList = [searchItem]
- else:
- searchList = searchItem
- for glyph in searchList:
- foundGlyphs[glyph] = relatedGlyphs[glyph]
- return foundGlyphs
-
-def makeAccentName(baseName, accentNames):
- """make an accented glyph name"""
- if isinstance(accentNames, str):
- accentNames = [accentNames]
- build = []
- if baseName.find('.') != -1:
- base = baseName.split('.')[0]
- suffix = baseName.split('.')[1]
- else:
- base = baseName
- suffix = ''
- build.append(base)
- for accent in accentNames:
- build.append(accent)
- buildJoin = ''.join(build)
- name = '.'.join([buildJoin, suffix])
- return name
-
-def nameBuster(glyphName, glyphConstruct):
- stripedSuffixName = stripSuffix(glyphName)
- suffix = None
- errors = []
- accentNames = []
- baseName = glyphName
- if glyphName.find('.') != -1:
- suffix = glyphName.split('.')[1]
- if glyphName.find('.sc') != -1:
- suffix = glyphName.split('.sc')[1]
- if stripedSuffixName not in glyphConstruct.keys():
- errors.append('%s: %s not in glyph construction database'%(glyphName, stripedSuffixName))
- else:
- if suffix is None:
- baseName = glyphConstruct[stripedSuffixName][0]
- else:
- if glyphName.find('.sc') != -1:
- baseName = ''.join([glyphConstruct[stripedSuffixName][0], suffix])
- else:
- baseName = '.'.join([glyphConstruct[stripedSuffixName][0], suffix])
- accentNames = glyphConstruct[stripedSuffixName][1:]
- return (baseName, stripedSuffixName, accentNames, errors)
-
-class AccentTools:
- def __init__(self, font, accentList):
- """several tools for working with anchors and building accents"""
- self.glyphConstructions = readGlyphConstructions()
- self.accentList = accentList
- self.anchorErrors = ['ANCHOR ERRORS:']
- self.accentErrors = ['ACCENT ERRORS:']
- self.font = font
-
- def clearAnchors(self, doProgress=True):
- """clear all anchors in the font"""
- tickCount = len(self.font)
- if doProgress:
- bar = ProgressBar("Cleaning all anchors...", tickCount)
- tick = 0
- for glyphName in self.accentList:
- if doProgress:
- bar.label(glyphName)
- baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
- existError = False
- if len(errors) > 0:
- existError = True
- if not existError:
- toClear = [baseName]
- for accent, position in accentNames:
- toClear.append(accent)
- for glyphName in toClear:
- try:
- self.font[glyphName].clearAnchors()
- except IndexError: pass
- if doProgress:
- bar.tick(tick)
- tick = tick+1
- if doProgress:
- bar.close()
-
- def buildAnchors(self, ucXOffset=0, ucYOffset=0, lcXOffset=0, lcYOffset=0, markGlyph=True, doProgress=True):
- """add the necessary anchors to the glyphs if they don't exist
- some flag definitions:
- uc/lc/X/YOffset=20 offset values for the anchors
- markGlyph=1 mark the glyph that is created
- doProgress=1 show a progress bar"""
- accentOffset = 10
- tickCount = len(self.accentList)
- if doProgress:
- bar = ProgressBar('Adding anchors...', tickCount)
- tick = 0
- for glyphName in self.accentList:
- if doProgress:
- bar.label(glyphName)
- previousPositions = {}
- baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
- existError = False
- if len(errors) > 0:
- existError = True
- for anchorError in errors:
- self.anchorErrors.append(anchorError)
- if not existError:
- existError = False
- try:
- self.font[baseName]
- except IndexError:
- self.anchorErrors.append(' '.join([glyphName, ':', baseName, 'does not exist.']))
- existError = True
- for accentName, accentPosition in accentNames:
- try:
- self.font[accentName]
- except IndexError:
- self.anchorErrors.append(' '.join([glyphName, ':', accentName, 'does not exist.']))
- existError = True
- if not existError:
- #glyph = self.font.newGlyph(glyphName, clear=True)
- for accentName, accentPosition in accentNames:
- if baseName.split('.')[0] in lowercase_plain:
- xOffset = lcXOffset-accentOffset
- yOffset = lcYOffset-accentOffset
- else:
- xOffset = ucXOffset-accentOffset
- yOffset = ucYOffset-accentOffset
- # should I add a cedilla and ogonek yoffset override here?
- if accentPosition not in previousPositions.keys():
- self._dropAnchor(self.font[baseName], accentPosition, xOffset, yOffset)
- if markGlyph:
- self.font[baseName].mark = anchorColor
- if inFontLab:
- self.font[baseName].update()
- else:
- self._dropAnchor(self.font[previousPositions[accentPosition]], accentPosition, xOffset, yOffset)
- self._dropAnchor(self.font[accentName], accentPosition, accentOffset, accentOffset, doAccentPosition=1)
- previousPositions[accentPosition] = accentName
- if markGlyph:
- self.font[accentName].mark = anchorColor
- if inFontLab:
- self.font[accentName].update()
- if inFontLab:
- self.font.update()
- if doProgress:
- bar.tick(tick)
- tick = tick+1
- if doProgress:
- bar.close()
-
- def printAnchorErrors(self):
- """print errors encounted during buildAnchors"""
- if len(self.anchorErrors) == 1:
- print 'No anchor errors encountered'
- else:
- for i in self.anchorErrors:
- print i
-
- def _dropAnchor(self, glyph, positionName, xOffset=0, yOffset=0, doAccentPosition=False):
- """anchor adding method. for internal use only."""
- existingAnchorNames = []
- for anchor in glyph.getAnchors():
- existingAnchorNames.append(anchor.name)
- if doAccentPosition:
- positionName = ''.join(['_', positionName])
- if positionName not in existingAnchorNames:
- glyphLeft, glyphBottom, glyphRight, glyphTop = glyph.box
- glyphXCenter = glyph.width/2
- if positionName == 'top':
- glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
- elif positionName == 'bottom':
- glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
- elif positionName == 'left':
- glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
- elif positionName == 'right':
- glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
- elif positionName == '_top':
- glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
- elif positionName == '_bottom':
- glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
- elif positionName == '_left':
- glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
- elif positionName == '_right':
- glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
- if inFontLab:
- glyph.update()
-
- def buildAccents(self, clear=True, adjustWidths=True, markGlyph=True, doProgress=True):
- """build accented glyphs. some flag definitions:
- clear=1 clear the glyphs if they already exist
- markGlyph=1 mark the glyph that is created
- doProgress=1 show a progress bar
- adjustWidths=1 will fix right and left margins when left or right accents are added"""
- tickCount = len(self.accentList)
- if doProgress:
- bar = ProgressBar('Building accented glyphs...', tickCount)
- tick = 0
- for glyphName in self.accentList:
- if doProgress:
- bar.label(glyphName)
- existError = False
- anchorError = False
-
- baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
- if len(errors) > 0:
- existError = True
- for accentError in errors:
- self.accentErrors.append(accentError)
-
- if not existError:
- baseAnchors = []
- try:
- self.font[baseName]
- except IndexError:
- self.accentErrors.append('%s: %s does not exist.'%(glyphName, baseName))
- existError = True
- else:
- for anchor in self.font[baseName].anchors:
- baseAnchors.append(anchor.name)
- for accentName, accentPosition in accentNames:
- accentAnchors = []
- try:
- self.font[accentName]
- except IndexError:
- self.accentErrors.append('%s: %s does not exist.'%(glyphName, accentName))
- existError = True
- else:
- for anchor in self.font[accentName].getAnchors():
- accentAnchors.append(anchor.name)
- if accentPosition not in baseAnchors:
- self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, accentPosition, baseName))
- anchorError = True
- if ''.join(['_', accentPosition]) not in accentAnchors:
- self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, ''.join(['_', accentPosition]), accentName))
- anchorError = True
- if not existError and not anchorError:
- destination = self.font.compileGlyph(glyphName, baseName, self.glyphConstructions[stripedSuffixName][1:], adjustWidths)
- if markGlyph:
- destination.mark = accentColor
- if doProgress:
- bar.tick(tick)
- tick = tick+1
- if doProgress:
- bar.close()
-
- def printAccentErrors(self):
- """print errors encounted during buildAccents"""
- if len(self.accentErrors) == 1:
- print 'No accent errors encountered'
- else:
- for i in self.accentErrors:
- print i
-
-
diff --git a/misc/pylib/robofab/tools/fontlabFeatureSplitter.py b/misc/pylib/robofab/tools/fontlabFeatureSplitter.py
deleted file mode 100644
index 3e0173dfc..000000000
--- a/misc/pylib/robofab/tools/fontlabFeatureSplitter.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import re
-
-featureRE = re.compile(
- "^" # start of line
- "\s*" #
- "feature" # feature
- "\s+" #
- "(\w{4})" # four alphanumeric characters
- "\s*" #
- "\{" # {
- , re.MULTILINE # run in multiline to preserve line seps
-)
-
-def splitFeaturesForFontLab(text):
- """
- >>> result = splitFeaturesForFontLab(testText)
- >>> result == expectedTestResult
- True
- """
- classes = ""
- features = []
- while text:
- m = featureRE.search(text)
- if m is None:
- classes = text
- text = ""
- else:
- start, end = m.span()
- # if start is not zero, this is the first match
- # and all previous lines are part of the "classes"
- if start > 0:
- assert not classes
- classes = text[:start]
- # extract the current feature
- featureName = m.group(1)
- featureText = text[start:end]
- text = text[end:]
- # grab all text before the next feature definition
- # and add it to the current definition
- if text:
- m = featureRE.search(text)
- if m is not None:
- start, end = m.span()
- featureText += text[:start]
- text = text[start:]
- else:
- featureText += text
- text = ""
- # store the feature
- features.append((featureName, featureText))
- return classes, features
-
-testText = """
-@class1 = [a b c d];
-
-feature liga {
- sub f i by fi;
-} liga;
-
-@class2 = [x y z];
-
-feature salt {
- sub a by a.alt;
-} salt; feature ss01 {sub x by x.alt} ss01;
-
-feature ss02 {sub y by y.alt} ss02;
-
-# feature calt {
-# sub a b' by b.alt;
-# } calt;
-"""
-
-expectedTestResult = (
- "\n@class1 = [a b c d];\n",
- [
- ("liga", "\nfeature liga {\n sub f i by fi;\n} liga;\n\n@class2 = [x y z];\n"),
- ("salt", "\nfeature salt {\n sub a by a.alt;\n} salt; feature ss01 {sub x by x.alt} ss01;\n"),
- ("ss02", "\nfeature ss02 {sub y by y.alt} ss02;\n\n# feature calt {\n# sub a b' by b.alt;\n# } calt;\n")
- ]
-)
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/misc/pylib/robofab/tools/glifExport.py b/misc/pylib/robofab/tools/glifExport.py
deleted file mode 100755
index bacb9190a..000000000
--- a/misc/pylib/robofab/tools/glifExport.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""Tool for exporting GLIFs from FontLab"""
-
-import FL
-import os
-from robofab.interface.all.dialogs import ProgressBar
-from robofab.glifLib import GlyphSet
-from robofab.tools.glifImport import GlyphPlaceholder
-from robofab.pens.flPen import drawFLGlyphOntoPointPen
-
-
-def exportGlyph(glyphName, flGlyph, glyphSet):
- """Export a FontLab glyph."""
-
- glyph = GlyphPlaceholder()
- glyph.width = flGlyph.width
- glyph.unicodes = flGlyph.unicodes
- if flGlyph.note:
- glyph.note = flGlyph.note
- customdata = flGlyph.customdata
- if customdata:
- from cStringIO import StringIO
- from robofab.plistlib import readPlist, Data
- f = StringIO(customdata)
- try:
- glyph.lib = readPlist(f)
- except: # XXX ugh, plistlib can raise lots of things
- # Anyway, customdata does not contain valid plist data,
- # but we don't need to toss it!
- glyph.lib = {"org.robofab.fontlab.customdata": Data(customdata)}
-
- def drawPoints(pen):
- # whoohoo, nested scopes are cool.
- drawFLGlyphOntoPointPen(flGlyph, pen)
-
- glyphSet.writeGlyph(glyphName, glyph, drawPoints)
-
-
-def exportGlyphs(font, glyphs=None, dest=None, doProgress=True, bar=None):
- """Export all glyphs in a FontLab font"""
- if dest is None:
- dir, base = os.path.split(font.file_name)
- base = base.split(".")[0] + ".glyphs"
- dest = os.path.join(dir, base)
-
- if not os.path.exists(dest):
- os.makedirs(dest)
-
- glyphSet = GlyphSet(dest)
-
- if glyphs is None:
- indices = range(len(font))
- else:
- indices = []
- for glyphName in glyphs:
- indices.append(font.FindGlyph(glyphName))
- barStart = 0
- closeBar = False
- if doProgress:
- if not bar:
- bar = ProgressBar("Exporting Glyphs", len(indices))
- closeBar = True
- else:
- barStart = bar.getCurrentTick()
- else:
- bar = None
- try:
- done = {}
- for i in range(len(indices)):
- #if not (i % 10) and not bar.tick(i + barStart):
- # raise KeyboardInterrupt
- index = indices[i]
- flGlyph = font[index]
- if flGlyph is None:
- continue
- glyphName = flGlyph.name
- if not glyphName:
- print "can't dump glyph #%s, it has no glyph name" % i
- else:
- if glyphName in done:
- n = 1
- while ("%s#%s" % (glyphName, n)) in done:
- n += 1
- glyphName = "%s#%s" % (glyphName, n)
- done[glyphName] = None
- exportGlyph(glyphName, flGlyph, glyphSet)
- if bar and not i % 10:
- bar.tick(barStart + i)
- # Write out contents.plist
- glyphSet.writeContents()
- except KeyboardInterrupt:
- if bar:
- bar.close()
- bar = None
- if bar and closeBar:
- bar.close()
diff --git a/misc/pylib/robofab/tools/glifImport.py b/misc/pylib/robofab/tools/glifImport.py
deleted file mode 100755
index 68d14460a..000000000
--- a/misc/pylib/robofab/tools/glifImport.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""Tools for importing GLIFs into FontLab"""
-
-import os
-from FL import fl
-from robofab.tools.toolsFL import NewGlyph, FontIndex
-from robofab.pens.flPen import FLPointPen
-from robofab.glifLib import GlyphSet
-from robofab.interface.all.dialogs import ProgressBar, GetFolder
-
-
-class GlyphPlaceholder:
- pass
-
-
-def importAllGlifFiles(font, dirName=None, doProgress=True, bar=None):
- """import all GLIFs into a FontLab font"""
- if dirName is None:
- if font.file_name:
- dir, base = os.path.split(font.file_name)
- base = base.split(".")[0] + ".glyphs"
- dirName = os.path.join(dir, base)
- else:
- dirName = GetFolder("Please select a folder with .glif files")
- glyphSet = GlyphSet(dirName)
- glyphNames = glyphSet.keys()
- glyphNames.sort()
- barStart = 0
- closeBar = False
- if doProgress:
- if not bar:
- bar = ProgressBar("Importing Glyphs", len(glyphNames))
- closeBar = True
- else:
- barStart = bar.getCurrentTick()
- else:
- bar = None
- try:
- for i in range(len(glyphNames)):
- #if not (i % 10) and not bar.tick(barStart + i):
- # raise KeyboardInterrupt
- glyphName = glyphNames[i]
- flGlyph = NewGlyph(font, glyphName, clear=True)
- pen = FLPointPen(flGlyph)
- glyph = GlyphPlaceholder()
- glyphSet.readGlyph(glyphName, glyph, pen)
- if hasattr(glyph, "width"):
- flGlyph.width = int(round(glyph.width))
- if hasattr(glyph, "unicodes"):
- flGlyph.unicodes = glyph.unicodes
- if hasattr(glyph, "note"):
- flGlyph.note = glyph.note # XXX must encode
- if hasattr(glyph, "lib"):
- from cStringIO import StringIO
- from robofab.plistlib import writePlist
- lib = glyph.lib
- if lib:
- if len(lib) == 1 and "org.robofab.fontlab.customdata" in lib:
- data = lib["org.robofab.fontlab.customdata"].data
- else:
- f = StringIO()
- writePlist(glyph.lib, f)
- data = f.getvalue()
- flGlyph.customdata = data
- # XXX the next bit is only correct when font is the current font :-(
- fl.UpdateGlyph(font.FindGlyph(glyphName))
- if bar and not i % 10:
- bar.tick(barStart + i)
- except KeyboardInterrupt:
- if bar:
- bar.close()
- bar = None
- fl.UpdateFont(FontIndex(font))
- if bar and closeBar:
- bar.close()
diff --git a/misc/pylib/robofab/tools/glyphConstruction.py b/misc/pylib/robofab/tools/glyphConstruction.py
deleted file mode 100644
index afe098423..000000000
--- a/misc/pylib/robofab/tools/glyphConstruction.py
+++ /dev/null
@@ -1,565 +0,0 @@
-
-_glyphConstruction = """\
-#
-# RoboFab Glyph Construction Database
-#
-# format:
-# Glyphname: BaseGlyph Accent.RelativePosition* Accent.RelativePosition*
-# *RelativePosition can be top, bottom, left, right
-#
-# NOTE: this is not a comprehensive, or even accurate, glyph list.
-# It was built by Python robots and, in many cases, by tired human hands.
-# Please report any omissions, errors or praise to the local RoboFab authorities.
-#
-##: Uppercase
-AEacute: AE acute.top
-AEmacron: AE macron.top
-Aacute: A acute.top
-Abreve: A breve.top
-Abreveacute: A breve.top acute.top
-Abrevedotaccent: A breve.top dotaccent.bottom
-Abrevegrave: A breve.top grave.top
-Abrevetilde: A breve.top tilde.top
-Acaron: A caron.top
-Acircumflex: A circumflex.top
-Acircumflexacute: A circumflex.top acute.top
-Acircumflexdotaccent: A circumflex.top dotaccent.bottom
-Acircumflexgrave: A circumflex.top grave.top
-Acircumflextilde: A circumflex.top tilde.top
-Adblgrave: A dblgrave.top
-Adieresis: A dieresis.top
-Adieresismacron: A dieresis.top macron.top
-Adotaccent: A dotaccent.top
-Adotaccentmacron: A dotaccent.top macron.top
-Agrave: A grave.top
-Amacron: A macron.top
-Aogonek: A ogonek.bottom
-Aring: A ring.top
-Aringacute: A ring.top acute.top
-Atilde: A tilde.top
-Bdotaccent: B dotaccent.top
-Cacute: C acute.top
-Ccaron: C caron.top
-Ccedilla: C cedilla.bottom
-Ccedillaacute: C cedilla.bottom acute.top
-Ccircumflex: C circumflex.top
-Cdotaccent: C dotaccent.top
-Dcaron: D caron.top
-Dcedilla: D cedilla.bottom
-Ddotaccent: D dotaccent.top
-Eacute: E acute.top
-Ebreve: E breve.top
-Ecaron: E caron.top
-Ecedilla: E cedilla.bottom
-Ecedillabreve: E cedilla.bottom breve.top
-Ecircumflex: E circumflex.top
-Ecircumflexacute: E circumflex.top acute.top
-Ecircumflexdotaccent: E circumflex.top dotaccent.bottom
-Ecircumflexgrave: E circumflex.top grave.top
-Ecircumflextilde: E circumflex.top tilde.top
-Edblgrave: E dblgrave.top
-Edieresis: E dieresis.top
-Edotaccent: E dotaccent.top
-Egrave: E grave.top
-Emacron: E macron.top
-Emacronacute: E macron.top acute.top
-Emacrongrave: E macron.top grave.top
-Eogonek: E ogonek.bottom
-Etilde: E tilde.top
-Fdotaccent: F dotaccent.top
-Gacute: G acute.top
-Gbreve: G breve.top
-Gcaron: G caron.top
-Gcedilla: G cedilla.bottom
-Gcircumflex: G circumflex.top
-Gcommaaccent: G commaaccent.bottom
-Gdotaccent: G dotaccent.top
-Gmacron: G macron.top
-Hcaron: H caron.top
-Hcedilla: H cedilla.top
-Hcircumflex: H circumflex.top
-Hdieresis: H dieresis.top
-Hdotaccent: H dotaccent.top
-Iacute: I acute.top
-Ibreve: I breve.top
-Icaron: I caron.top
-Icircumflex: I circumflex.top
-Idblgrave: I dblgrave.top
-Idieresis: I dieresis.top
-Idieresisacute: I dieresis.top acute.top
-Idotaccent: I dotaccent.top
-Igrave: I grave.top
-Imacron: I macron.top
-Iogonek: I ogonek.bottom
-Itilde: I tilde.top
-Jcircumflex: J circumflex.top
-Kacute: K acute.top
-Kcaron: K caron.top
-Kcedilla: K cedilla.bottom
-Kcommaaccent: K commaaccent.bottom
-Lacute: L acute.top
-Lcaron: L commaaccent.right
-Lcedilla: L cedilla.bottom
-Lcommaaccent: L commaaccent.bottom
-Ldot: L dot.right
-Ldotaccent: L dotaccent.bottom
-Ldotaccentmacron: L dotaccent.bottom macron.top
-Macute: M acute.top
-Mdotaccent: M dotaccent.top
-Nacute: N acute.top
-Ncaron: N caron.top
-Ncedilla: N cedilla.bottom
-Ncommaaccent: N commaaccent.bottom
-Ndotaccent: N dotaccent.top
-Ngrave: N grave.top
-Ntilde: N tilde.top
-Oacute: O acute.top
-Obreve: O breve.top
-Ocaron: O caron.top
-Ocircumflex: O circumflex.top
-Ocircumflexacute: O circumflex.top acute.top
-Ocircumflexdotaccent: O circumflex.top dotaccent.bottom
-Ocircumflexgrave: O circumflex.top grave.top
-Ocircumflextilde: O circumflex.top tilde.top
-Odblgrave: O dblgrave.top
-Odieresis: O dieresis.top
-Odieresismacron: O dieresis.top macron.top
-Ograve: O grave.top
-Ohungarumlaut: O hungarumlaut.top
-Omacron: O macron.top
-Omacronacute: O macron.top acute.top
-Omacrongrave: O macron.top grave.top
-Oogonek: O ogonek.bottom
-Oogonekmacron: O ogonek.bottom macron.top
-Oslashacute: Oslash acute.top
-Otilde: O tilde.top
-Otildeacute: O tilde.top acute.top
-Otildedieresis: O tilde.top dieresis.top
-Otildemacron: O tilde.top macron.top
-Pacute: P acute.top
-Pdotaccent: P dotaccent.top
-Racute: R acute.top
-Rcaron: R caron.top
-Rcedilla: R cedilla.bottom
-Rcommaaccent: R commaaccent.bottom
-Rdblgrave: R dblgrave.top
-Rdotaccent: R dotaccent.top
-Rdotaccentmacron: R dotaccent.top macron.top
-Sacute: S acute.top
-Sacutedotaccent: S acute.top dotaccent.top
-Scaron: S caron.top
-Scarondotaccent: S caron.top dotaccent.top
-Scedilla: S cedilla.bottom
-Scircumflex: S circumflex.top
-Scommaaccent: S commaaccent.bottom
-Sdotaccent: S dotaccent.top
-Tcaron: T caron.top
-Tcedilla: T cedilla.bottom
-Tcommaaccent: T commaaccent.bottom
-Tdotaccent: T dotaccent.top
-Uacute: U acute.top
-Ubreve: U breve.top
-Ucaron: U caron.top
-Ucircumflex: U circumflex.top
-Udblgrave: U dblgrave.top
-Udieresis: U dieresis.top
-Udieresisacute: U dieresis.top acute.top
-Udieresiscaron: U dieresis.top caron.top
-Udieresisgrave: U dieresis.top grave.top
-Udieresismacron: U dieresis.top macron.top
-Ugrave: U grave.top
-Uhungarumlaut: U hungarumlaut.top
-Umacron: U macron.top
-Umacrondieresis: U macron.top dieresis.top
-Uogonek: U ogonek.bottom
-Uring: U ring.top
-Utilde: U tilde.top
-Utildeacute: U tilde.top acute.top
-Vtilde: V tilde.top
-Wacute: W acute.top
-Wcircumflex: W circumflex.top
-Wdieresis: W dieresis.top
-Wdotaccent: W dotaccent.top
-Wgrave: W grave.top
-Xdieresis: X dieresis.top
-Xdotaccent: X dotaccent.top
-Yacute: Y acute.top
-Ycircumflex: Y circumflex.top
-Ydieresis: Y dieresis.top
-Ydotaccent: Y dotaccent.top
-Ygrave: Y grave.top
-Ytilde: Y tilde.top
-Zacute: Z acute.top
-Zcaron: Z caron.top
-Zcircumflex: Z circumflex.top
-Zdotaccent: Z dotaccent.top
-##: Lowercase
-aacute: a acute.top
-abreve: a breve.top
-abreveacute: a breve.top acute.top
-abrevedotaccent: a breve.top dotaccent.bottom
-abrevegrave: a breve.top grave.top
-abrevetilde: a breve.top tilde.top
-acaron: a caron.top
-acircumflex: a circumflex.top
-acircumflexacute: a circumflex.top acute.top
-acircumflexdotaccent: a circumflex.top dotaccent.bottom
-acircumflexgrave: a circumflex.top grave.top
-acircumflextilde: a circumflex.top tilde.top
-adblgrave: a dblgrave.top
-adieresis: a dieresis.top
-adieresismacron: a dieresis.top macron.top
-adotaccent: a dotaccent.top
-adotaccentmacron: a dotaccent.top macron.top
-aeacute: ae acute.top
-aemacron: ae macron.top
-agrave: a grave.top
-amacron: a macron.top
-aogonek: a ogonek.bottom
-aring: a ring.top
-aringacute: a ring.top acute.top
-atilde: a tilde.top
-bdotaccent: b dotaccent.top
-cacute: c acute.top
-ccaron: c caron.top
-ccedilla: c cedilla.bottom
-ccedillaacute: c cedilla.bottom acute.top
-ccircumflex: c circumflex.top
-cdotaccent: c dotaccent.top
-dcaron: d commaaccent.right
-dcedilla: d cedilla.bottom
-ddotaccent: d dotaccent.top
-dmacron: d macron.top
-eacute: e acute.top
-ebreve: e breve.top
-ecaron: e caron.top
-ecedilla: e cedilla.bottom
-ecedillabreve: e cedilla.bottom breve.top
-ecircumflex: e circumflex.top
-ecircumflexacute: e circumflex.top acute.top
-ecircumflexdotaccent: e circumflex.top dotaccent.bottom
-ecircumflexgrave: e circumflex.top grave.top
-ecircumflextilde: e circumflex.top tilde.top
-edblgrave: e dblgrave.top
-edieresis: e dieresis.top
-edotaccent: e dotaccent.top
-egrave: e grave.top
-emacron: e macron.top
-emacronacute: e macron.top acute.top
-emacrongrave: e macron.top grave.top
-eogonek: e ogonek.bottom
-etilde: e tilde.top
-fdotaccent: f dotaccent.top
-gacute: g acute.top
-gbreve: g breve.top
-gcaron: g caron.top
-gcedilla: g cedilla.top
-gcircumflex: g circumflex.top
-gcommaaccent: g commaaccent.top
-gdotaccent: g dotaccent.top
-gmacron: g macron.top
-hcaron: h caron.top
-hcedilla: h cedilla.bottom
-hcircumflex: h circumflex.top
-hdieresis: h dieresis.top
-hdotaccent: h dotaccent.top
-iacute: dotlessi acute.top
-ibreve: dotlessi breve.top
-icaron: dotlessi caron.top
-icircumflex: dotlessi circumflex.top
-idblgrave: dotlessi dblgrave.top
-idieresis: dotlessi dieresis.top
-idieresisacute: dotlessi dieresis.top acute.top
-igrave: dotlessi grave.top
-imacron: dotlessi macron.top
-iogonek: i ogonek.bottom
-itilde: dotlessi tilde.top
-jcaron: dotlessj caron.top
-jcircumflex: dotlessj circumflex.top
-jacute: dotlessj acute.top
-kacute: k acute.top
-kcaron: k caron.top
-kcedilla: k cedilla.bottom
-kcommaaccent: k commaaccent.bottom
-lacute: l acute.top
-lcaron: l commaaccent.right
-lcedilla: l cedilla.bottom
-lcommaaccent: l commaaccent.bottom
-ldot: l dot.right
-ldotaccent: l dotaccent.bottom
-ldotaccentmacron: l dotaccent.bottom macron.top
-macute: m acute.top
-mdotaccent: m dotaccent.top
-nacute: n acute.top
-ncaron: n caron.top
-ncedilla: n cedilla.bottom
-ncommaaccent: n commaaccent.bottom
-ndotaccent: n dotaccent.top
-ngrave: n grave.top
-ntilde: n tilde.top
-oacute: o acute.top
-obreve: o breve.top
-ocaron: o caron.top
-ocircumflex: o circumflex.top
-ocircumflexacute: o circumflex.top acute.top
-ocircumflexdotaccent: o circumflex.top dotaccent.bottom
-ocircumflexgrave: o circumflex.top grave.top
-ocircumflextilde: o circumflex.top tilde.top
-odblgrave: o dblgrave.top
-odieresis: o dieresis.top
-odieresismacron: o dieresis.top macron.top
-ograve: o grave.top
-ohungarumlaut: o hungarumlaut.top
-omacron: o macron.top
-omacronacute: o macron.top acute.top
-omacrongrave: o macron.top grave.top
-oogonek: o ogonek.bottom
-oogonekmacron: o ogonek.bottom macron.top
-oslashacute: oslash acute.top
-otilde: o tilde.top
-otildeacute: o tilde.top acute.top
-otildedieresis: o tilde.top dieresis.top
-otildemacron: o tilde.top macron.top
-pacute: p acute.top
-pdotaccent: p dotaccent.top
-racute: r acute.top
-rcaron: r caron.top
-rcedilla: r cedilla.bottom
-rcommaaccent: r commaaccent.bottom
-rdblgrave: r dblgrave.top
-rdotaccent: r dotaccent.top
-rdotaccentmacron: r dotaccent.top macron.top
-sacute: s acute.top
-sacutedotaccent: s acute.top dotaccent.top
-scaron: s caron.top
-scarondotaccent: s caron.top dotaccent.top
-scedilla: s cedilla.bottom
-scircumflex: s circumflex.top
-scommaaccent: s commaaccent.bottom
-sdotaccent: s dotaccent.top
-tcaron: t commaaccent.right
-tcedilla: t cedilla.bottom
-tcommaaccent: t commaaccent.bottom
-tdieresis: t dieresis.top
-tdotaccent: t dotaccent.top
-uacute: u acute.top
-ubreve: u breve.top
-ucaron: u caron.top
-ucircumflex: u circumflex.top
-udblgrave: u dblgrave.top
-udieresis: u dieresis.top
-udieresisacute: u dieresis.top acute.top
-udieresiscaron: u dieresis.top caron.top
-udieresisgrave: u dieresis.top grave.top
-udieresismacron: u dieresis.top macron.top
-ugrave: u grave.top
-uhungarumlaut: u hungarumlaut.top
-umacron: u macron.top
-umacrondieresis: u macron.top dieresis.top
-uogonek: u ogonek.bottom
-uring: u ring.top
-utilde: u tilde.top
-utildeacute: u tilde.top acute.top
-vtilde: v tilde.top
-wacute: w acute.top
-wcircumflex: w circumflex.top
-wdieresis: w dieresis.top
-wdotaccent: w dotaccent.top
-wgrave: w grave.top
-wring: w ring.top
-xdieresis: x dieresis.top
-xdotaccent: x dotaccent.top
-yacute: y acute.top
-ycircumflex: y circumflex.top
-ydieresis: y dieresis.top
-ydotaccent: y dotaccent.top
-ygrave: y grave.top
-yring: y ring.top
-ytilde: y tilde.top
-zacute: z acute.top
-zcaron: z caron.top
-zcircumflex: z circumflex.top
-zdotaccent: z dotaccent.top
-##: Small: Caps
-AEacute.sc: AE.sc acute.top
-AEmacron.sc: AE.sc macron.top
-Aacute.sc: A.sc acute.top
-Abreve.sc: A.sc breve.top
-Abreveacute.sc: A.sc breve.top acute.top
-Abrevedotaccent.sc: A.sc breve.top dotaccent.bottom
-Abrevegrave.sc: A.sc breve.top grave.top
-Abrevetilde.sc: A.sc breve.top tilde.top
-Acaron.sc: A.sc caron.top
-Acircumflex.sc: A.sc circumflex.top
-Acircumflexacute.sc: A.sc circumflex.top acute.top
-Acircumflexdotaccent.sc: A.sc circumflex.top dotaccent.bottom
-Acircumflexgrave.sc: A.sc circumflex.top grave.top
-Acircumflextilde.sc: A.sc circumflex.top tilde.top
-Adblgrave.sc: A.sc dblgrave.top
-Adieresis.sc: A.sc dieresis.top
-Adieresismacron.sc: A.sc dieresis.top macron.top
-Adotaccent.sc: A.sc dotaccent.top
-Adotaccentmacron.sc: A.sc dotaccent.top macron.top
-Agrave.sc: A.sc grave.top
-Amacron.sc: A.sc macron.top
-Aogonek.sc: A.sc ogonek.bottom
-Aring.sc: A.sc ring.top
-Aringacute.sc: A.sc ring.top acute.top
-Atilde.sc: A.sc tilde.top
-Bdotaccent.sc: B.sc dotaccent.top
-Cacute.sc: C.sc acute.top
-Ccaron.sc: C.sc caron.top
-Ccedilla.sc: C.sc cedilla.bottom
-Ccedillaacute.sc: C.sc cedilla.bottom acute.top
-Ccircumflex.sc: C.sc circumflex.top
-Cdotaccent.sc: C.sc dotaccent.top
-Dcaron.sc: D.sc caron.top
-Dcedilla.sc: D.sc cedilla.bottom
-Ddotaccent.sc: D.sc dotaccent.top
-Eacute.sc: E.sc acute.top
-Ebreve.sc: E.sc breve.top
-Ecaron.sc: E.sc caron.top
-Ecedilla.sc: E.sc cedilla.bottom
-Ecedillabreve.sc: E.sc cedilla.bottom breve.top
-Ecircumflex.sc: E.sc circumflex.top
-Ecircumflexacute.sc: E.sc circumflex.top acute.top
-Ecircumflexdotaccent.sc: E.sc circumflex.top dotaccent.bottom
-Ecircumflexgrave.sc: E.sc circumflex.top grave.top
-Ecircumflextilde.sc: E.sc circumflex.top tilde.top
-Edblgrave.sc: E.sc dblgrave.top
-Edieresis.sc: E.sc dieresis.top
-Edotaccent.sc: E.sc dotaccent.top
-Egrave.sc: E.sc grave.top
-Emacron.sc: E.sc macron.top
-Emacronacute.sc: E.sc macron.top acute.top
-Emacrongrave.sc: E.sc macron.top grave.top
-Eogonek.sc: E.sc ogonek.bottom
-Etilde.sc: E.sc tilde.top
-Fdotaccent.sc: F.sc dotaccent.top
-Gacute.sc: G.sc acute.top
-Gbreve.sc: G.sc breve.top
-Gcaron.sc: G.sc caron.top
-Gcedilla.sc: G.sc cedilla.bottom
-Gcircumflex.sc: G.sc circumflex.top
-Gcommaaccent.sc: G.sc commaaccent.bottom
-Gdotaccent.sc: G.sc dotaccent.top
-Gmacron.sc: G.sc macron.top
-Hcaron.sc: H.sc caron.top
-Hcedilla.sc: H.sc cedilla.top
-Hcircumflex.sc: H.sc circumflex.top
-Hdieresis.sc: H.sc dieresis.top
-Hdotaccent.sc: H.sc dotaccent.top
-Iacute.sc: I.sc acute.top
-Ibreve.sc: I.sc breve.top
-Icaron.sc: I.sc caron.top
-Icircumflex.sc: I.sc circumflex.top
-Idblgrave.sc: I.sc dblgrave.top
-Idieresis.sc: I.sc dieresis.top
-Idieresisacute.sc: I.sc dieresis.top acute.top
-Idotaccent.sc: I.sc dotaccent.top
-Igrave.sc: I.sc grave.top
-Imacron.sc: I.sc macron.top
-Iogonek.sc: I.sc ogonek.bottom
-Itilde.sc: I.sc tilde.top
-Jcircumflex.sc: J.sc circumflex.top
-Kacute.sc: K.sc acute.top
-Kcaron.sc: K.sc caron.top
-Kcedilla.sc: K.sc cedilla.bottom
-Kcommaaccent.sc: K.sc commaaccent.bottom
-Lacute.sc: L.sc acute.top
-Lcaron.sc: L.sc commaaccent.right
-Lcedilla.sc: L.sc cedilla.bottom
-Lcommaaccent.sc: L.sc commaaccent.bottom
-Ldot.sc: L.sc dot.right
-Ldotaccent.sc: L.sc dotaccent.bottom
-Ldotaccentmacron.sc: L.sc dotaccent.bottom macron.top
-Macute.sc: M.sc acute.top
-Mdotaccent.sc: M.sc dotaccent.top
-Nacute.sc: N.sc acute.top
-Ncaron.sc: N.sc caron.top
-Ncedilla.sc: N.sc cedilla.bottom
-Ncommaaccent.sc: N.sc commaaccent.bottom
-Ndotaccent.sc: N.sc dotaccent.top
-Ngrave.sc: N.sc grave.top
-Ntilde.sc: N.sc tilde.top
-Oacute.sc: O.sc acute.top
-Obreve.sc: O.sc breve.top
-Ocaron.sc: O.sc caron.top
-Ocircumflex.sc: O.sc circumflex.top
-Ocircumflexacute.sc: O.sc circumflex.top acute.top
-Ocircumflexdotaccent.sc: O.sc circumflex.top dotaccent.bottom
-Ocircumflexgrave.sc: O.sc circumflex.top grave.top
-Ocircumflextilde.sc: O.sc circumflex.top tilde.top
-Odblgrave.sc: O.sc dblgrave.top
-Odieresis.sc: O.sc dieresis.top
-Odieresismacron.sc: O.sc dieresis.top macron.top
-Ograve.sc: O.sc grave.top
-Ohungarumlaut.sc: O.sc hungarumlaut.top
-Omacron.sc: O.sc macron.top
-Omacronacute.sc: O.sc macron.top acute.top
-Omacrongrave.sc: O.sc macron.top grave.top
-Oogonek.sc: O.sc ogonek.bottom
-Oogonekmacron.sc: O.sc ogonek.bottom macron.top
-Oslashacute.sc: Oslash.sc acute.top
-Otilde.sc: O.sc tilde.top
-Otildeacute.sc: O.sc tilde.top acute.top
-Otildedieresis.sc: O.sc tilde.top dieresis.top
-Otildemacron.sc: O.sc tilde.top macron.top
-Pacute.sc: P.sc acute.top
-Pdotaccent.sc: P.sc dotaccent.top
-Racute.sc: R.sc acute.top
-Rcaron.sc: R.sc caron.top
-Rcedilla.sc: R.sc cedilla.bottom
-Rcommaaccent.sc: R.sc commaaccent.bottom
-Rdblgrave.sc: R.sc dblgrave.top
-Rdotaccent.sc: R.sc dotaccent.top
-Rdotaccentmacron.sc: R.sc dotaccent.top macron.top
-Sacute.sc: S.sc acute.top
-Sacutedotaccent.sc: S.sc acute.top dotaccent.top
-Scaron.sc: S.sc caron.top
-Scarondotaccent.sc: S.sc caron.top dotaccent.top
-Scedilla.sc: S.sc cedilla.bottom
-Scircumflex.sc: S.sc circumflex.top
-Scommaaccent.sc: S.sc commaaccent.bottom
-Sdotaccent.sc: S.sc dotaccent.top
-Tcaron.sc: T.sc caron.top
-Tcedilla.sc: T.sc cedilla.bottom
-Tcommaaccent.sc: T.sc commaaccent.bottom
-Tdotaccent.sc: T.sc dotaccent.top
-Uacute.sc: U.sc acute.top
-Ubreve.sc: U.sc breve.top
-Ucaron.sc: U.sc caron.top
-Ucircumflex.sc: U.sc circumflex.top
-Udblgrave.sc: U.sc dblgrave.top
-Udieresis.sc: U.sc dieresis.top
-Udieresisacute.sc: U.sc dieresis.top acute.top
-Udieresiscaron.sc: U.sc dieresis.top caron.top
-Udieresisgrave.sc: U.sc dieresis.top grave.top
-Udieresismacron.sc: U.sc dieresis.top macron.top
-Ugrave.sc: U.sc grave.top
-Uhungarumlaut.sc: U.sc hungarumlaut.top
-Umacron.sc: U.sc macron.top
-Umacrondieresis.sc: U.sc macron.top dieresis.top
-Uogonek.sc: U.sc ogonek.bottom
-Uring.sc: U.sc ring.top
-Utilde.sc: U.sc tilde.top
-Utildeacute.sc: U.sc tilde.top acute.top
-Vtilde.sc: V.sc tilde.top
-Wacute.sc: W.sc acute.top
-Wcircumflex.sc: W.sc circumflex.top
-Wdieresis.sc: W.sc dieresis.top
-Wdotaccent.sc: W.sc dotaccent.top
-Wgrave.sc: W.sc grave.top
-Xdieresis.sc: X.sc dieresis.top
-Xdotaccent.sc: X.sc dotaccent.top
-Yacute.sc: Y.sc acute.top
-Ycircumflex.sc: Y.sc circumflex.top
-Ydieresis.sc: Y.sc dieresis.top
-Ydotaccent.sc: Y.sc dotaccent.top
-Ygrave.sc: Y.sc grave.top
-Ytilde.sc: Y.sc tilde.top
-Zacute.sc: Z.sc acute.top
-Zcaron.sc: Z.sc caron.top
-Zcircumflex.sc: Z.sc circumflex.top
-Zdotaccent.sc: Z.sc dotaccent.top
-""" \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/glyphNameSchemes.py b/misc/pylib/robofab/tools/glyphNameSchemes.py
deleted file mode 100755
index cb6172b9a..000000000
--- a/misc/pylib/robofab/tools/glyphNameSchemes.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""A separate module for glyphname to filename functions.
-
-glyphNameToShortFileName() generates a non-clashing filename for systems with
-filename-length limitations.
-"""
-
-MAXLEN = 31
-
-def glyphNameToShortFileName(glyphName, glyphSet):
- """Alternative glyphname to filename function.
-
- Features a garuanteed maximum filename for really long glyphnames, and clash testing.
- - all non-ascii characters are converted to "_" (underscore), including "."
- - all glyphnames which are too long are truncated and a hash is added at the end
- - the hash is generated from the whole glyphname
- - finally, the candidate glyphname is checked against the contents.plist
- and a incrementing number is added at the end if there is a clash.
- """
- import binascii, struct, string
- ext = ".glif"
- ok = string.ascii_letters + string.digits + " _"
- h = binascii.hexlify(struct.pack(">l", binascii.crc32(glyphName)))
- n = ''
- for c in glyphName:
- if c in ok:
- if c != c.lower():
- n += c + "_"
- else:
- n += c
- else:
- n += "_"
- if len(n + ext) < MAXLEN:
- return n + ext
- count = 0
- candidate = n[:MAXLEN - len(h + ext)] + h + ext
- if glyphSet is not None:
- names = glyphSet.getReverseContents()
- while candidate.lower() in names:
- candidate = n[:MAXLEN - len(h + ext + str(count))] + h + str(count) + ext
- count += 1
- return candidate
diff --git a/misc/pylib/robofab/tools/objectDumper.py b/misc/pylib/robofab/tools/objectDumper.py
deleted file mode 100755
index 29110ca53..000000000
--- a/misc/pylib/robofab/tools/objectDumper.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""Simple and ugly way to print some attributes and properties of an object to stdout.
-FontLab doesn't have an object browser and sometimes we do need to look inside"""
-
-from pprint import pprint
-
-def classname(object, modname):
- """Get a class name and qualify it with a module name if necessary."""
- name = object.__name__
- if object.__module__ != modname:
- name = object.__module__ + '.' + name
- return name
-
-def _objectDumper(object, indent=0, private=False):
- """Collect a dict with the contents of the __dict__ as a quick means of peeking inside
- an instance. Some RoboFab locations do not support PyBrowser and still need debugging."""
- data = {}
- data['__class__'] = "%s at %d"%(classname(object.__class__, object.__module__), id(object))
- for k in object.__class__.__dict__.keys():
- if private and k[0] == "_":
- continue
- x = object.__class__.__dict__[k]
- if hasattr(x, "fget"): #other means of recognising a property?
- try:
- try:
- value = _objectDumper(x.fget(self), 1)
- except:
- value = x.fget(self)
- data[k] = "[property, %s] %s"%(type(x.fget(self)).__name__, value)
- except:
- data[k] = "[property] (Error getting property value)"
- for k in object.__dict__.keys():
- if private and k[0] == "_":
- continue
- try:
- data[k] = "[attribute, %s] %s"%(type(object.__dict__[k]).__name__, `object.__dict__[k]`)
- except:
- data[k] = "[attribute] (Error getting attribute value)"
- return data
-
-def flattenDict(dict, indent=0):
- t = []
- k = dict.keys()
- k.sort()
- print
- print '---RoboFab Object Dump---'
- for key in k:
- value = dict[key]
- t.append(indent*"\t"+"%s: %s"%(key, value))
- t.append('')
- return "\r".join(t)
-
-def dumpObject(object, private=False):
- print pprint(_objectDumper(object, private=private))
-
-
diff --git a/misc/pylib/robofab/tools/otFeatures.py b/misc/pylib/robofab/tools/otFeatures.py
deleted file mode 100755
index 8138a2fe7..000000000
--- a/misc/pylib/robofab/tools/otFeatures.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""Simple module to write features to font"""
-
-
-import string
-
-
-from types import StringType, ListType, TupleType
-
-from robofab.world import world
-if world.inFontLab:
- from FL import *
- from fl_cmd import *
-from robofab.tools.toolsFL import FontIndex
-
- #feat = []
- #feat.append('feature smcp {')
- #feat.append('\tlookup SMALLCAPS {')
- #feat.append('\t\tsub @LETTERS_LC by @LETTERS_LC;')
- #feat.append('\t} SMALLCAPS;')
- #feat.append('} smcp;')
-
-
-class FeatureWriter:
- """Make properly formatted feature code"""
- def __init__(self, type):
- self.type = type
- self.data = []
-
- def add(self, src, dst):
- """Add a substitution: change src to dst."""
- self.data.append((src, dst))
-
- def write(self, group=0):
- """Write the whole thing to string"""
- t = []
- if len(self.data) == 0:
- return None
- t.append('feature %s {' % self.type)
- for src, dst in self.data:
- if isinstance(src, (list, tuple)):
- if group:
- src = "[%s]" % string.join(src, ' ')
- else:
- src = string.join(src, ' ')
- if isinstance(dst, (list, tuple)):
- if group:
- dst = "[%s]" % string.join(dst, ' ')
- else:
- dst = string.join(dst, ' ')
- src = string.strip(src)
- dst = string.strip(dst)
- t.append("\tsub %s by %s;" % (src, dst))
- t.append('}%s;' % self.type)
- return string.join(t, '\n')
-
-
-class GlyphName:
- """Simple class that splits a glyphname in handy parts,
- access the parts as attributes of the name."""
- def __init__(self, name):
- self.suffix = []
- self.ligs = []
- self.name = self.base = name
- if '.' in name:
- self.bits = name.split('.')
- self.base = self.bits[0]
- self.suffix = self.bits[1:]
- if '_' in name:
- self.ligs = self.base.split('_')
-
-
-def GetAlternates(font, flavor="alt", match=0):
- """Sort the glyphs of this font by the parts of the name.
- flavor is the bit to look for, i.e. 'alt' in a.alt
- match = 1 if you want a exact match: alt1 != alt
- match = 0 if the flavor is a partial match: alt == alt1
- """
- names = {}
- for c in font.glyphs:
- name = GlyphName(c.name)
- if not names.has_key(name.base):
- names[name.base] = []
- if match:
- # only include if there is an exact match
- if flavor in name.suffix:
- names[name.base].append(c.name)
- else:
- # include if there is a partial match
- for a in name.suffix:
- if a.find(flavor) != -1:
- names[name.base].append(c.name)
- return names
-
-
-# XXX there should be a more generic glyph finder.
-
-def MakeCapsFeature(font):
- """Build a feature for smallcaps based on .sc glyphnames"""
- names = GetAlternates(font, 'sc', match=1)
- fw = FeatureWriter('smcp')
- k = names.keys()
- k.sort()
- for p in k:
- if names[p]:
- fw.add(p, names[p])
- feat = fw.write()
- if feat:
- font.features.append(Feature('smcp', feat))
- return feat
-
-
-def MakeAlternatesFeature(font):
- """Build a aalt feature based on glyphnames"""
- names = GetAlternates(font, 'alt', match=0)
- fw = FeatureWriter('aalt')
- k = names.keys()
- k.sort()
- for p in k:
- if names[p]:
- fw.add(p, names[p])
- feat = fw.write(group=1)
- if feat:
- font.features.append(Feature('aalt', feat))
- return feat
-
-
-def MakeSwashFeature(font):
- """Build a swash feature based on glyphnames"""
- names = GetAlternates(font, 'swash', match=0)
- fw = FeatureWriter('swsh')
- k=names.keys()
- k.sort()
- for p in k:
- if names[p]:
- l=names[p]
- l.sort()
- fw.add(p, l[0])
- feat=fw.write()
- if feat:
- font.features.append(Feature('swsh', feat))
- return feat
-
-
-def MakeLigaturesFeature(font):
- """Build a liga feature based on glyphnames"""
- from robofab.gString import ligatures
- ligCountDict = {}
- for glyph in font.glyphs:
- if glyph.name in ligatures:
- if len(glyph.name) not in ligCountDict.keys():
- ligCountDict[len(glyph.name)] = [glyph.name]
- else:
- ligCountDict[len(glyph.name)].append(glyph.name)
- elif glyph.name.find('_') != -1:
- usCounter=1
- for i in glyph.name:
- if i =='_':
- usCounter=usCounter+1
- if usCounter not in ligCountDict.keys():
- ligCountDict[usCounter] = [glyph.name]
- else:
- ligCountDict[usCounter].append(glyph.name)
- ligCount=ligCountDict.keys()
- ligCount.sort()
- foundLigs=[]
- for i in ligCount:
- l = ligCountDict[i]
- l.sort()
- foundLigs=foundLigs+l
- fw=FeatureWriter('liga')
- for i in foundLigs:
- if i.find('_') != -1:
- sub=i.split('_')
- else:
- sub=[]
- for c in i:
- sub.append(c)
- fw.add(sub, i)
- feat=fw.write()
- if feat:
- font.features.append(Feature('liga', feat))
- return feat
-
-
-if __name__ == "__main__":
- fw = FeatureWriter('liga')
- fw.add(['f', 'f', 'i'], ['f_f_i'])
- fw.add('f f ', 'f_f')
- fw.add(['f', 'i'], 'f_i')
- print fw.write()
diff --git a/misc/pylib/robofab/tools/proof.py b/misc/pylib/robofab/tools/proof.py
deleted file mode 100755
index bb3b9d100..000000000
--- a/misc/pylib/robofab/tools/proof.py
+++ /dev/null
@@ -1,119 +0,0 @@
-"""This is the place for stuff that makes proofs and test text settings etc"""
-
-import string
-
-
-
-
-idHeader = """<ASCII-MAC>
-<Version:2.000000><FeatureSet:InDesign-Roman><ColorTable:=<Black:COLOR:CMYK:Process:0.000000,0.000000,0.000000,1.000000>>"""
-
-idColor = """<cColor:COLOR\:%(model)s\:Process\:%(c)f\,%(m)f\,%(y)f\,%(k)f>"""
-
-idParaStyle = """<ParaStyle:><cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>"""
-idGlyphStyle = """<cTypeface:%(weight)s><cSize:%(size)f><cLeading:%(leading)f><cFont:%(family)s>"""
-
-seperator = ''
-
-autoLinespaceFactor = 1.2
-
-
-class IDTaggedText:
-
- """Export a text as a XML tagged text file for InDesign (2.0?).
- The tags can contain information about
- - family: font family i.e. "Times"
- - weight: font weight "Bold"
- - size: typesize in points
- - leading: leading in points
- - color: a CMYK color, as a 4 tuple of floats between 0 and 1
- - insert special glyphs based on glyphindex
- (which is why it only makes sense if you use this in FontLab,
- otherwise there is no other way to get the indices)
- """
-
- def __init__(self, family, weight, size=36, leading=None):
- self.family = family
- self.weight = weight
- self.size = size
- if not leading:
- self.leading = autoLinespaceFactor*size
- self.text = []
- self.data = []
- self.addHeader()
-
- def add(self, text):
- """Method to add text to the file."""
- t = self.charToGlyph(text)
- self.data.append(t)
-
- def charToGlyph(self, text):
- return text
-
- def addHeader(self):
- """Add the standard header."""
- # set colors too?
- self.data.append(idHeader)
-
- def replace(self, old, new):
- """Replace occurances of 'old' with 'new' in all content."""
- d = []
- for i in self.data:
- d.append(i.replace(old, new))
- self.data = d
-
- def save(self, path):
- """Save the tagged text here."""
- f = open(path, 'w')
- f.write(string.join(self.data, seperator))
- f.close()
-
- def addGlyph(self, index):
- """Add a special glyph, index is the glyphIndex in an OpenType font."""
- self.addStyle()
- self.data.append("<cSpecialGlyph:%d><0xFFFD>"%index)
-
- def addStyle(self, family=None, weight=None, size=None, leading=None, color=None):
- """Set the paragraph style for the following text."""
- if not family:
- family = self.family
- if not weight:
- weight = self.weight
- if not size:
- size = self.size
- if not leading:
- leading = autoLinespaceFactor*self.size
- self.data.append(idGlyphStyle%({'weight': weight, 'size': size, 'family': family, 'leading':leading}))
- if color:
- self.data.append(idColor%({'model': 'CMYK', 'c': color[0], 'm': color[1], 'y': color[2], 'k': color[3]}))
-
-
-
-if __name__ == "__main__":
- from random import randint
- id = IDTaggedText("Minion", "Regular", size=40, leading=50)
-
- id.addStyle(color=(0,0,0,1))
- id.add("Hello")
-
- id.addStyle(weight="Bold", color=(0,0.5,1,0))
- id.add(" Everybody")
- id.addStyle(weight="Regular", size=100, color=(0,1,1,0))
- id.addGlyph(102)
- id.addGlyph(202)
-
- from robofab.interface.all.dialogs import PutFile
- path = PutFile("Save the tagged file:", "TaggedText.txt")
- if path:
- id.save(path)
-
- # then: open a document in Adobe InDesign
- # select "Place" (cmd-D on Mac)
- # select the text file you just generated
- # place the text
-
-
-
-
-
- # \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/remote.py b/misc/pylib/robofab/tools/remote.py
deleted file mode 100755
index 288afcfe7..000000000
--- a/misc/pylib/robofab/tools/remote.py
+++ /dev/null
@@ -1,175 +0,0 @@
-"""Remote control for MacOS FontLab.
-initFontLabRemote() registers a callback for appleevents and
-runFontLabRemote() sends the code from a different application,
-such as a Mac Python IDE or Python interpreter.
-"""
-
-from robofab.world import world
-
-if world.inFontLab and world.mac is not None:
- from Carbon import AE as _AE
-
-else:
- import sys
- from aetools import TalkTo
-
- class FontLab(TalkTo):
- pass
-
-__all__ = ['initFontLabRemote', 'runFontLabRemote']
-
-def _executePython(theAppleEvent, theReply):
- import aetools
- import cStringIO
- import traceback
- import sys
- parms, attrs = aetools.unpackevent(theAppleEvent)
- source = parms.get("----")
- if source is None:
- return
- stdout = cStringIO.StringIO()
- #print "<executing remote command>"
- save = sys.stdout, sys.stderr
- sys.stdout = sys.stderr = stdout
- namespace = {}
- try:
- try:
- exec source in namespace
- except:
- traceback.print_exc()
- finally:
- sys.stdout, sys.stderr = save
- output = stdout.getvalue()
- aetools.packevent(theReply, {"----": output})
-
-_imported = False
-
-def initFontLabRemote():
- """Call this in FontLab at startup of the application to switch on the remote."""
- print "FontLabRemote is on."
- _AE.AEInstallEventHandler("Rfab", "exec", _executePython)
-
-if world.inFontLab and world.mac is not None:
- initFontLabRemote()
-
-def runFontLabRemote(code):
- """Call this in the MacOS Python IDE to make FontLab execute the code."""
- fl = FontLab("FLab", start=1)
- ae, parms, attrs = fl.send("Rfab", "exec", {"----": code})
- output = parms.get("----")
- return output
-
-
-
-# GlyphTransmit
-# Convert a glyph to a string using digestPen, transmit string, unpack string with pointpen.
-#
-
-
-def Glyph2String(glyph):
- from robofab.pens.digestPen import DigestPointPen
- import pickle
- p = DigestPointPen(glyph)
- glyph.drawPoints(p)
- info = {}
- info['name'] = glyph.name
- info['width'] = glyph.width
- info['points'] = p.getDigest()
- return str(pickle.dumps(info))
-
-def String2Glyph(gString, penClass, font):
- import pickle
- if gString is None:
- return None
- info = pickle.loads(gString)
- name = info['name']
- if not name in font.keys():
- glyph = font.newGlyph(name)
- else:
- glyph = font[name]
- pen = penClass(glyph)
- for p in info['points']:
- if p == "beginPath":
- pen.beginPath()
- elif p == "endPath":
- pen.endPath()
- else:
- pt, type = p
- pen.addPoint(pt, type)
- glyph.width = info['width']
- glyph.update()
- return glyph
-
-_makeFLGlyph = """
-from robofab.world import CurrentFont
-from robofab.tools.remote import receiveGlyph
-code = '''%s'''
-receiveGlyph(code, CurrentFont())
-"""
-
-def transmitGlyph(glyph):
- from robofab.world import world
- if world.inFontLab and world.mac is not None:
- # we're in fontlab, on a mac
- print Glyph2String(glyph)
- pass
- else:
- remoteProgram = _makeFLGlyph%Glyph2String(glyph)
- print "remoteProgram", remoteProgram
- return runFontLabRemote(remoteProgram)
-
-def receiveGlyph(glyphString, font=None):
- from robofab.world import world
- if world.inFontLab and world.mac is not None:
- # we're in fontlab, on a mac
- from robofab.pens.flPen import FLPointPen
- print String2Glyph(glyphString, FLPointPen, font)
- pass
- else:
- from robofab.pens.rfUFOPen import RFUFOPointPen
- print String2Glyph(glyphString, RFUFOPointPen, font)
-
-
-#
-# command to tell FontLab to open a UFO and save it as a vfb
-
-def os9PathConvert(path):
- """Attempt to convert a unix style path to a Mac OS9 style path.
- No support for relative paths!
- """
- if path.find("/Volumes") == 0:
- # it's on the volumes list, some sort of external volume
- path = path[len("/Volumes")+1:]
- elif path[0] == "/":
- # a dir on the root volume
- path = path[1:]
- new = path.replace("/", ":")
- return new
-
-
-_remoteUFOImportProgram = """
-from robofab.objects.objectsFL import NewFont
-import os.path
-destinationPathVFB = "%(destinationPathVFB)s"
-font = NewFont()
-font.readUFO("%(sourcePathUFO)s", doProgress=True)
-font.update()
-font.save(destinationPathVFB)
-print font, "done"
-font.close()
-"""
-
-def makeVFB(sourcePathUFO, destinationPathVFB=None):
- """FontLab convenience function to import a UFO and save it as a VFB"""
- import os
- fl = FontLab("FLab", start=1)
- if destinationPathVFB is None:
- destinationPathVFB = os.path.splitext(sourcePathUFO)[0]+".vfb"
- src9 = os9PathConvert(sourcePathUFO)
- dst9 = os9PathConvert(destinationPathVFB)
- code = _remoteUFOImportProgram%{'sourcePathUFO': src9, 'destinationPathVFB':dst9}
- ae, parms, attrs = fl.send("Rfab", "exec", {"----": code})
- output = parms.get("----")
- return output
-
- \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/rfPrefs.py b/misc/pylib/robofab/tools/rfPrefs.py
deleted file mode 100755
index 440984d0d..000000000
--- a/misc/pylib/robofab/tools/rfPrefs.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""A simple module for dealing with preferences that are used by scripts. Based almost entirely on MacPrefs.
-
-To save some preferences:
-myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist)
-myPrefs.myString = 'xyz'
-myPrefs.myInteger = 1234
-myPrefs.myList = ['a', 'b', 'c']
-myPrefs.myDict = {'a':1, 'b':2}
-myPrefs.save()
-
-To retrieve some preferences:
-myPrefs = RFPrefs(drive/directory/directory/myPrefs.plist)
-myString = myPrefs.myString
-myInteger = myPrefs.myInteger
-myList = myPrefs.myList
-myDict = myPrefs.myDict
-
-When using this module within FontLab, it is not necessary to
-provide the RFPrefs class with a path. If a path is not given,
-it will look for a file in FontLab/RoboFab Data/RFPrefs.plist.
-If that file does not exist, it will make it.
-"""
-
-from robofab import RoboFabError
-from robofab.plistlib import Plist
-from cStringIO import StringIO
-import os
-
-class _PrefObject:
-
- def __init__(self, dict=None):
- if not dict:
- self._prefs = {}
- else:
- self._prefs = dict
-
- def __len__(self):
- return len(self._prefs)
-
- def __delattr__(self, attr):
- if self._prefs.has_key(attr):
- del self._prefs[attr]
- else:
- raise AttributeError, 'delete non-existing instance attribute'
-
- def __getattr__(self, attr):
- if attr == '__members__':
- keys = self._prefs.keys()
- keys.sort()
- return keys
- try:
- return self._prefs[attr]
- except KeyError:
- raise AttributeError, attr
-
- def __setattr__(self, attr, value):
- if attr[0] != '_':
- self._prefs[attr] = value
- else:
- self.__dict__[attr] = value
-
- def asDict(self):
- return self._prefs
-
-class RFPrefs(_PrefObject):
-
- """The main preferences object to call"""
-
- def __init__(self, path=None):
- from robofab.world import world
- self.__path = path
- self._prefs = {}
- if world.inFontLab:
- #we don't have a path, but we know where we can put it
- if not path:
- from robofab.tools.toolsFL import makeDataFolder
- settingsPath = makeDataFolder()
- path = os.path.join(settingsPath, 'RFPrefs.plist')
- self.__path = path
- self._makePrefsFile()
- #we do have a path, make sure it exists and load it
- else:
- self._makePrefsFile()
- else:
- #no path, raise error
- if not path:
- raise RoboFabError, "no preferences path defined"
- #we do have a path, make sure it exists and load it
- else:
- self._makePrefsFile()
- self._prefs = Plist.fromFile(path)
-
- def _makePrefsFile(self):
- if not os.path.exists(self.__path):
- self.save()
-
- def __getattr__(self, attr):
- if attr[0] == '__members__':
- keys = self._prefs.keys()
- keys.sort()
- return keys
- try:
- return self._prefs[attr]
- except KeyError:
- raise AttributeError, attr
- #if attr[0] != '_':
- # self._prefs[attr] = _PrefObject()
- # return self._prefs[attr]
- #else:
- # raise AttributeError, attr
-
- def save(self):
- """save the plist file"""
- f = StringIO()
- pl = Plist()
- for i in self._prefs.keys():
- pl[i] = self._prefs[i]
- pl.write(f)
- data = f.getvalue()
- f = open(self.__path, 'wb')
- f.write(data)
- f.close()
diff --git a/misc/pylib/robofab/tools/toolsAll.py b/misc/pylib/robofab/tools/toolsAll.py
deleted file mode 100755
index d729fe770..000000000
--- a/misc/pylib/robofab/tools/toolsAll.py
+++ /dev/null
@@ -1,145 +0,0 @@
-"""A collection of non-environment specific tools"""
-
-
-import sys
-import os
-from robofab.objects.objectsRF import RInfo
-
-if sys.platform == "darwin" and sys.version_info[:3] == (2, 2, 0):
- # the Mac support of Jaguar's Python 2.2 is broken
- have_broken_macsupport = 1
-else:
- have_broken_macsupport = 0
-
-
-def readGlyphConstructions():
- """read GlyphConstruction and turn it into a dict"""
- from robofab.tools.glyphConstruction import _glyphConstruction
- data = _glyphConstruction.split("\n")
- glyphConstructions = {}
- for i in data:
- if len(i) == 0: continue
- if i[0] != '#':
- name = i.split(': ')[0]
- construction = i.split(': ')[1].split(' ')
- build = [construction[0]]
- for c in construction[1:]:
- accent = c.split('.')[0]
- position = c.split('.')[1]
- build.append((accent, position))
- glyphConstructions[name] = build
- return glyphConstructions
-
-#
-#
-# glyph.unicode: ttFont["cmap"].getcmap(3, 1)
-#
-#
-
-def guessFileType(fileName):
- if not os.path.exists(fileName):
- return None
- base, ext = os.path.splitext(fileName)
- ext = ext.lower()
- if not have_broken_macsupport:
- try:
- import MacOS
- except ImportError:
- pass
- else:
- cr, tp = MacOS.GetCreatorAndType(fileName)
- if tp in ("sfnt", "FFIL"):
- return "TTF"
- if tp == "LWFN":
- return "Type 1"
- if ext == ".dfont":
- return "TTF"
- if ext in (".otf", ".ttf"):
- return "TTF"
- if ext in (".pfb", ".pfa"):
- return "Type 1"
- return None
-
-def extractTTFFontInfo(font):
- # UFO.info attribute name / index.
- # name table entries index according to http://www.microsoft.com/typography/otspec/name.htm
- attrs = [
- ('copyright', 0),
- ('familyName', 1),
- ('styleMapStyleName', 2),
- ('postscriptFullName', 4),
- ('trademark', 7),
- ('openTypeNameDesigner', 9),
- ('openTypeNameLicenseURL', 14),
- ('openTypeNameDesignerURL', 12),
- ]
- info = RInfo()
- names = font['name']
- info.ascender = font['hhea'].ascent
- info.descender = font['hhea'].descent
- info.unitsPerEm = font['head'].unitsPerEm
- for name, index in attrs:
- entry = font["name"].getName(index, 3, 1, 0x409)
- if entry is not None:
- try:
- value = unicode(entry.string, "utf_16_be")
- if name == "styleMapStyleName":
- value = value.lower()
- setattr(info, name, value)
- except Exception, e:
- print "Error importing value %s: %s: %s"%(str(name), value, e.message)
- return info
-
-def extractT1FontInfo(font):
- info = RInfo()
- src = font.font['FontInfo']
- factor = font.font['FontMatrix'][0]
- assert factor > 0
- info.unitsPerEm = int(round(1/factor, 0))
- # assume something for ascender descender
- info.ascender = (info.unitsPerEm / 5) * 4
- info.descender = info.ascender - info.unitsPerEm
- info.versionMajor = font.font['FontInfo']['version']
- info.fullName = font.font['FontInfo']['FullName']
- info.familyName = font.font['FontInfo']['FullName'].split("-")[0]
- info.notice = unicode(font.font['FontInfo']['Notice'], "macroman")
- info.italicAngle = font.font['FontInfo']['ItalicAngle']
- info.uniqueID = font['UniqueID']
- return info
-
-def fontToUFO(src, dst, fileType=None):
- from robofab.ufoLib import UFOWriter
- from robofab.pens.adapterPens import SegmentToPointPen
- if fileType is None:
- fileType = guessFileType(src)
- if fileType is None:
- raise ValueError, "Can't determine input file type"
- ufoWriter = UFOWriter(dst)
- if fileType == "TTF":
- from fontTools.ttLib import TTFont
- font = TTFont(src, 0)
- elif fileType == "Type 1":
- from fontTools.t1Lib import T1Font
- font = T1Font(src)
- else:
- assert 0, "unknown file type: %r" % fileType
- inGlyphSet = font.getGlyphSet()
- outGlyphSet = ufoWriter.getGlyphSet()
- for glyphName in inGlyphSet.keys():
- print "-", glyphName
- glyph = inGlyphSet[glyphName]
- def drawPoints(pen):
- pen = SegmentToPointPen(pen)
- glyph.draw(pen)
- outGlyphSet.writeGlyph(glyphName, glyph, drawPoints)
- outGlyphSet.writeContents()
- if fileType == "TTF":
- info = extractTTFFontInfo(font)
- elif fileType == "Type 1":
- info = extractT1FontInfo(font)
- ufoWriter.writeInfo(info)
-
-if __name__ == "__main__":
- print readGlyphConstructions()
-
- \ No newline at end of file
diff --git a/misc/pylib/robofab/tools/toolsFL.py b/misc/pylib/robofab/tools/toolsFL.py
deleted file mode 100755
index c8aff19a1..000000000
--- a/misc/pylib/robofab/tools/toolsFL.py
+++ /dev/null
@@ -1,339 +0,0 @@
-"""
-T.O.O.L.S.: Things Other Objects Lack (Sometimes)
--assorted raw tools.
-
-This is an assorted colection of raw tools that do
-things inside of FontLab. Many of these functions
-form the bedrock of objectsFL. In short, use these
-tools only if you need the raw functions and they are
-not supported by the objects.
-
-Object model:
-Most of these tools were written before
-objectsFL. Some of these tools are used by
-objectsFL. That means that if you want to
-use functions from robofab.tools you can always
-feed them FontLab objects (like Font, Glyps,
-etc.). If the functions also accept Robjects from
-robofab.objects it is usually mentioned in the
-doc string.
-
-This is a simple way to convert a robofab Font
-object back to a FL Font object. Even if you don't
-know which particular faith an object belongs to
-you can use this:
-
-font = unwrapFont(font)
-"""
-
-
-from FL import *
-from warnings import warn
-
-try:
- from fl_cmd import *
-except ImportError:
- print "The fl_cmd module is not available here. toolsFL.py"
-
-import os
-
-from robofab import RoboFabError
-
-# local encoding
-if os.name == "mac":
- LOCAL_ENCODING = "macroman"
-else:
- LOCAL_ENCODING = "latin-1"
-
-
-#
-#
-#
-# stuff for fontlab app
-#
-#
-#
-
-def AppFolderRenamer():
- """This function will rename the folder that contains the
- FontLab application to a more specific name that includes
- the version of the application
- Warning: it messes with the paths of your app, if you have
- items that hardwired to this path you'd be in trouble.
- """
- if fl.count > 0:
- warn("Close all fonts before running AppFolderRenamer")
- return
- old = fl.path[:-1]
- root = os.path.dirname(old)
- new = "FontLab " + fl.version.replace('/', '_')
- path = os.path.join(root, new)
- if path != old:
- try:
- os.rename(old, path)
- except OSError:
- pass
- warn("Please quit and restart FontLab")
-
-#
-#
-#
-# stuff for fonts
-#
-#
-#
-
-def GetFont(full_name):
- """Return fontobjects which match full_name.
- Note: result is a list.
- Returns: a list of FL Font objects
- """
- found = []
- for f in AllFonts():
- if f.full_name == full_name:
- found.append(f)
- return found
-
-def AllFonts():
- """Collect a list of all open fonts.
- Returns: a list of FL Font objects.
- """
- fontcount = len(fl)
- af = []
- for i in range(fontcount):
- af.append(fl[i])
- return af
-
-def FontIndex(font):
- """return the index of a specified FL Font"""
- font = unwrapFont(font)
- a = AllFonts()
- p = []
- for f in a:
- p.append(f.file_name)
- if font.file_name in p:
- return p.index(font.file_name)
- else:
- return None
-
-def unwrapFont(font):
- """Unwrap the font if it happens to be a RoboFab Font"""
- if hasattr(font, 'isRobofab'):
- return font.naked()
- return font
-
-def MakeTempFont(font, dupemark=None, removeOverlap=True, decompose=True):
- """Save the current FL Font,
- - close the file,
- - duplicate the file in the finder (icon looks weird, but it works)
- - open the duplicate
- - decompose the glyphs
- - remove overlaps
- - return the fontobject
-
- font is either a FL Font or RF RFont object.
-
- Problems: doesn't check if the filename is getting too long.
- Note: it will overwrite older files with the same name.
- """
- import string
- f = unwrapFont(font)
- if not dupemark or dupemark == "":
- dupemark = "_tmp_"
- path = f.file_name
- a = f.file_name.split('.')
- a.insert(len(a)-1, dupemark)
- newpath = string.join(a, '.')
- f.Save(path)
- fl.Close(FontIndex(f))
- file = open(path, 'rb')
- data = file.read()
- file.close()
- file = open(newpath, 'wb')
- file.write(data)
- file.close()
- fl.Open(newpath, 1)
- nf = fl.font
- if nf is None:
- print 'uh oh, sup?'
- return None
- else:
- for g in nf.glyphs:
- if decompose:
- g.Decompose()
- if removeOverlap:
- g.RemoveOverlap()
- return nf
-
-def makePSFontName(name):
- """Create a postscript filename out of a regular postscript fontname,
- using the old fashioned macintosh 5:3:3 convention.
- """
- import string
- parts = []
- current = []
- final = []
- notAllowed = '-_+=,-'
- index = 0
- for c in name:
- if c in notAllowed:
- continue
- if c in string.uppercase or index == 0:
- c = string.upper(c)
- if current:
- parts.append("".join(current))
- current = [c]
- else:
- current.append(c)
- index = index + 1
- if current:
- parts.append("".join(current))
- final.append(parts[0][:5])
- for p in parts[1:]:
- final.append(p[:3])
- return "".join(final)
-
-#
-#
-#
-# stuff for glyphs
-#
-#
-#
-
-def NewGlyph(font, glyphName, clear=False, updateFont=True):
- """Make a new glyph if it doesn't already exist, return the glyph.
- font is either a FL Font or RF RFont object. If updateFont is True
- the (very slow) fl.UpdateFont function will be called.
- """
- font = unwrapFont(font)
- if isinstance(glyphName, unicode):
- glyphName = glyphName.encode(LOCAL_ENCODING)
- glyph = font[glyphName]
- if glyph is None:
- new = Glyph()
- new.name = glyphName
- font.glyphs.append(new)
- if updateFont:
- fl.UpdateFont(FontIndex(font))
- glyph = font[glyphName]
- elif clear:
- glyph.Clear()
- glyph.anchors.clean()
- glyph.components.clean()
- glyph.note = ""
- return glyph
-
-
-def AddToAlias(additions, sep='+'):
- """additions is a dict with glyphnames as keys
- and glyphConstruction as values. In order to make
- a bunch of additions in one go rather than open
- and close the file for each name. Add a glyph
- to the alias.dat file if it doesn't already exist.
- additions = {'Gcircumflex': ['G','circumflex'], }
- Returns a list of only the added glyphnames."""
- import string
- glyphs = {}
- data = []
- new = []
- path = os.path.join(fl.path, 'Mapping', 'alias.dat')
- if os.path.exists(path):
- file = open(path, 'r')
- data = file.read().split('\n')
- file.close()
- for i in data:
- if len(i) == 0: continue
- if i[0] != '%':
- glyphs[i.split(' ')[0]] = i.split(' ')[1]
- for glyphName, glyphConstruction in additions.items():
- if glyphName not in glyphs.keys():
- new.append(glyphName)
- glyphs[glyphName] = string.join(glyphConstruction, sep)
- newNames = ['%%FONTLAB ALIASES']
- l = glyphs.keys()
- l.sort()
- for i in l:
- newNames.append(string.join([i, glyphs[i]], ' '))
- file = open(path, 'w')
- file.write(string.join(newNames, '\n'))
- file.close()
- return new
-
-
-def GlyphIndexTable(font):
- """Make a glyph index table for font"""
- font = unwrapFont(font)
- idx = {}
- for i in range(len(font)):
- g = font.glyphs[i]
- idx[g.name] = i
- return idx
-
-def MakeReverseCompoMapping(font):
- """Return a dict that maps glyph names to lists containing tuples
- of the form:
- (clientGlyphName, componentIndex)
- """
- font = unwrapFont(font)
- reverseCompoMapping = {}
- for g in font.glyphs:
- for i, c in zip(range(len(g.components)), g.components):
- base = font[c.index].name
- if not base in reverseCompoMapping:
- reverseCompoMapping[base] = []
- reverseCompoMapping[base].append((g.name, i))
- return reverseCompoMapping
-
-
-#
-#
-#
-# stuff for text files
-#
-#
-#
-
-def textPrinter(text, name=None, path=None):
- """Write a string to a text file. If no name is given it becomes
- Untitled_hour_minute_second.txt . If no path is given it goes
- into the FontLab/RoboFab Data directory."""
- if not name:
- import time
- tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst = time.localtime()
- now = '_'.join((`tm_hour`, `tm_min`, `tm_sec`))
- name = 'Untitled_%s.txt'%now
- if not path:
- path = os.path.join(makeDataFolder(), name)
- f = open(path, 'wb')
- f.write(text)
- f.close()
-
-def makeDataFolder():
- """Make the RoboFab data folder"""
- folderPath = os.path.join(fl.path, "RoboFab Data")
- if not os.path.exists(folderPath):
- try:
- os.makedirs(folderPath)
- except:
- pass
- return folderPath
-
-
-def Log(text=None):
- """Make an entry in the default log file."""
- now = str(time.asctime(time.localtime(time.time())))
- if not text:
- text = "-"
- entry = "%s: %s\r"%(now, text)
- path = os.path.join(os.getcwd(), "Logs")
- new = 0
- if not os.path.exists(path):
- os.makedirs(path)
- new = 1
- log = os.path.join(path, "log.txt")
- f = open(log, 'a')
- if new:
- f.write("# log file for FL\r")
- f.write(entry)
- f.close()
diff --git a/misc/pylib/robofab/tools/toolsRF.py b/misc/pylib/robofab/tools/toolsRF.py
deleted file mode 100755
index 01624c5ad..000000000
--- a/misc/pylib/robofab/tools/toolsRF.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""
-
-Module for rf specific tool like code.
-
-"""
-
diff --git a/misc/pylib/robofab/ufoLib.py b/misc/pylib/robofab/ufoLib.py
deleted file mode 100755
index b329fd929..000000000
--- a/misc/pylib/robofab/ufoLib.py
+++ /dev/null
@@ -1,1084 +0,0 @@
-""""
-A library for importing .ufo files and their descendants.
-Refer to http://unifiedfontobject.com for the UFO specification.
-
-The UFOReader and UFOWriter classes support versions 1 and 2
-of the specification. Up and down conversion functions are also
-supplied in this library. These conversion functions are only
-necessary if conversion without loading the UFO data into
-a set of objects is desired. These functions are:
- convertUFOFormatVersion1ToFormatVersion2
- convertUFOFormatVersion2ToFormatVersion1
-
-Two sets that list the font info attribute names for the two
-fontinfo.plist formats are available for external use. These are:
- fontInfoAttributesVersion1
- fontInfoAttributesVersion2
-
-A set listing the fontinfo.plist attributes that were deprecated
-in version 2 is available for external use:
- deprecatedFontInfoAttributesVersion2
-
-A function, validateFontInfoVersion2ValueForAttribute, that does
-some basic validation on values for a fontinfo.plist value is
-available for external use.
-
-Two value conversion functions are availble for converting
-fontinfo.plist values between the possible format versions.
- convertFontInfoValueForAttributeFromVersion1ToVersion2
- convertFontInfoValueForAttributeFromVersion2ToVersion1
-"""
-
-
-import os
-import shutil
-from cStringIO import StringIO
-import calendar
-from robofab.plistlib import readPlist, writePlist
-from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE
-
-try:
- set
-except NameError:
- from sets import Set as set
-
-__all__ = [
- "makeUFOPath"
- "UFOLibError",
- "UFOReader",
- "UFOWriter",
- "convertUFOFormatVersion1ToFormatVersion2",
- "convertUFOFormatVersion2ToFormatVersion1",
- "fontInfoAttributesVersion1",
- "fontInfoAttributesVersion2",
- "deprecatedFontInfoAttributesVersion2",
- "validateFontInfoVersion2ValueForAttribute",
- "convertFontInfoValueForAttributeFromVersion1ToVersion2",
- "convertFontInfoValueForAttributeFromVersion2ToVersion1"
-]
-
-
-class UFOLibError(Exception): pass
-
-
-# ----------
-# File Names
-# ----------
-
-GLYPHS_DIRNAME = "glyphs"
-METAINFO_FILENAME = "metainfo.plist"
-FONTINFO_FILENAME = "fontinfo.plist"
-LIB_FILENAME = "lib.plist"
-GROUPS_FILENAME = "groups.plist"
-KERNING_FILENAME = "kerning.plist"
-FEATURES_FILENAME = "features.fea"
-
-supportedUFOFormatVersions = [1, 2]
-
-
-# ---------------------------
-# Format Conversion Functions
-# ---------------------------
-
-
-def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None):
- """
- Function for converting a version format 1 UFO
- to version format 2. inPath should be a path
- to a UFO. outPath is the path where the new UFO
- should be written. If outPath is not given, the
- inPath will be used and, therefore, the UFO will
- be converted in place. Otherwise, if outPath is
- specified, nothing must exist at that path.
- """
- if outPath is None:
- outPath = inPath
- if inPath != outPath and os.path.exists(outPath):
- raise UFOLibError("A file already exists at %s." % outPath)
- # use a reader for loading most of the data
- reader = UFOReader(inPath)
- if reader.formatVersion == 2:
- raise UFOLibError("The UFO at %s is already format version 2." % inPath)
- groups = reader.readGroups()
- kerning = reader.readKerning()
- libData = reader.readLib()
- # read the info data manually and convert
- infoPath = os.path.join(inPath, FONTINFO_FILENAME)
- if not os.path.exists(infoPath):
- infoData = {}
- else:
- infoData = readPlist(infoPath)
- infoData = _convertFontInfoDataVersion1ToVersion2(infoData)
- # if the paths are the same, only need to change the
- # fontinfo and meta info files.
- infoPath = os.path.join(outPath, FONTINFO_FILENAME)
- if inPath == outPath:
- metaInfoPath = os.path.join(inPath, METAINFO_FILENAME)
- metaInfo = dict(
- creator="org.robofab.ufoLib",
- formatVersion=2
- )
- writePlistAtomically(metaInfo, metaInfoPath)
- writePlistAtomically(infoData, infoPath)
- # otherwise write everything.
- else:
- writer = UFOWriter(outPath)
- writer.writeGroups(groups)
- writer.writeKerning(kerning)
- writer.writeLib(libData)
- # write the info manually
- writePlistAtomically(infoData, infoPath)
- # copy the glyph tree
- inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME)
- outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME)
- if os.path.exists(inGlyphs):
- shutil.copytree(inGlyphs, outGlyphs)
-
-def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None):
- """
- Function for converting a version format 2 UFO
- to version format 1. inPath should be a path
- to a UFO. outPath is the path where the new UFO
- should be written. If outPath is not given, the
- inPath will be used and, therefore, the UFO will
- be converted in place. Otherwise, if outPath is
- specified, nothing must exist at that path.
- """
- if outPath is None:
- outPath = inPath
- if inPath != outPath and os.path.exists(outPath):
- raise UFOLibError("A file already exists at %s." % outPath)
- # use a reader for loading most of the data
- reader = UFOReader(inPath)
- if reader.formatVersion == 1:
- raise UFOLibError("The UFO at %s is already format version 1." % inPath)
- groups = reader.readGroups()
- kerning = reader.readKerning()
- libData = reader.readLib()
- # read the info data manually and convert
- infoPath = os.path.join(inPath, FONTINFO_FILENAME)
- if not os.path.exists(infoPath):
- infoData = {}
- else:
- infoData = readPlist(infoPath)
- infoData = _convertFontInfoDataVersion2ToVersion1(infoData)
- # if the paths are the same, only need to change the
- # fontinfo, metainfo and feature files.
- infoPath = os.path.join(outPath, FONTINFO_FILENAME)
- if inPath == outPath:
- metaInfoPath = os.path.join(inPath, METAINFO_FILENAME)
- metaInfo = dict(
- creator="org.robofab.ufoLib",
- formatVersion=1
- )
- writePlistAtomically(metaInfo, metaInfoPath)
- writePlistAtomically(infoData, infoPath)
- featuresPath = os.path.join(inPath, FEATURES_FILENAME)
- if os.path.exists(featuresPath):
- os.remove(featuresPath)
- # otherwise write everything.
- else:
- writer = UFOWriter(outPath, formatVersion=1)
- writer.writeGroups(groups)
- writer.writeKerning(kerning)
- writer.writeLib(libData)
- # write the info manually
- writePlistAtomically(infoData, infoPath)
- # copy the glyph tree
- inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME)
- outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME)
- if os.path.exists(inGlyphs):
- shutil.copytree(inGlyphs, outGlyphs)
-
-
-# ----------
-# UFO Reader
-# ----------
-
-
-class UFOReader(object):
-
- """Read the various components of the .ufo."""
-
- def __init__(self, path):
- self._path = path
- self.readMetaInfo()
-
- def _get_formatVersion(self):
- return self._formatVersion
-
- formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.")
-
- def _checkForFile(self, path):
- if not os.path.exists(path):
- return False
- else:
- return True
-
- def readMetaInfo(self):
- """
- Read metainfo.plist. Only used for internal operations.
- """
- path = os.path.join(self._path, METAINFO_FILENAME)
- if not self._checkForFile(path):
- raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path)
- # should there be a blind try/except with a UFOLibError
- # raised in except here (and elsewhere)? It would be nice to
- # provide external callers with a single exception to catch.
- data = readPlist(path)
- formatVersion = data["formatVersion"]
- if formatVersion not in supportedUFOFormatVersions:
- raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path))
- self._formatVersion = formatVersion
-
- def readGroups(self):
- """
- Read groups.plist. Returns a dict.
- """
- path = os.path.join(self._path, GROUPS_FILENAME)
- if not self._checkForFile(path):
- return {}
- return readPlist(path)
-
- def readInfo(self, info):
- """
- Read fontinfo.plist. It requires an object that allows
- setting attributes with names that follow the fontinfo.plist
- version 2 specification. This will write the attributes
- defined in the file into the object.
- """
- # load the file and return if there is no file
- path = os.path.join(self._path, FONTINFO_FILENAME)
- if not self._checkForFile(path):
- return
- infoDict = readPlist(path)
- infoDataToSet = {}
- # version 1
- if self._formatVersion == 1:
- for attr in fontInfoAttributesVersion1:
- value = infoDict.get(attr)
- if value is not None:
- infoDataToSet[attr] = value
- infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet)
- # version 2
- elif self._formatVersion == 2:
- for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items():
- value = infoDict.get(attr)
- if value is None:
- continue
- infoDataToSet[attr] = value
- # unsupported version
- else:
- raise NotImplementedError
- # validate data
- infoDataToSet = _validateInfoVersion2Data(infoDataToSet)
- # populate the object
- for attr, value in infoDataToSet.items():
- try:
- setattr(info, attr, value)
- except AttributeError:
- raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr)
-
- def readKerning(self):
- """
- Read kerning.plist. Returns a dict.
- """
- path = os.path.join(self._path, KERNING_FILENAME)
- if not self._checkForFile(path):
- return {}
- kerningNested = readPlist(path)
- kerning = {}
- for left in kerningNested:
- for right in kerningNested[left]:
- value = kerningNested[left][right]
- kerning[left, right] = value
- return kerning
-
- def readLib(self):
- """
- Read lib.plist. Returns a dict.
- """
- path = os.path.join(self._path, LIB_FILENAME)
- if not self._checkForFile(path):
- return {}
- return readPlist(path)
-
- def readFeatures(self):
- """
- Read features.fea. Returns a string.
- """
- path = os.path.join(self._path, FEATURES_FILENAME)
- if not self._checkForFile(path):
- return ""
- f = open(path, READ_MODE)
- text = f.read()
- f.close()
- return text
-
- def getGlyphSet(self):
- """
- Return the GlyphSet associated with the
- glyphs directory in the .ufo.
- """
- glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME)
- return GlyphSet(glyphsPath)
-
- def getCharacterMapping(self):
- """
- Return a dictionary that maps unicode values (ints) to
- lists of glyph names.
- """
- glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME)
- glyphSet = GlyphSet(glyphsPath)
- allUnicodes = glyphSet.getUnicodes()
- cmap = {}
- for glyphName, unicodes in allUnicodes.iteritems():
- for code in unicodes:
- if code in cmap:
- cmap[code].append(glyphName)
- else:
- cmap[code] = [glyphName]
- return cmap
-
-
-# ----------
-# UFO Writer
-# ----------
-
-
-class UFOWriter(object):
-
- """Write the various components of the .ufo."""
-
- def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"):
- if formatVersion not in supportedUFOFormatVersions:
- raise UFOLibError("Unsupported UFO format (%d)." % formatVersion)
- self._path = path
- self._formatVersion = formatVersion
- self._fileCreator = fileCreator
- self._writeMetaInfo()
- # handle down conversion
- if formatVersion == 1:
- ## remove existing features.fea
- featuresPath = os.path.join(path, FEATURES_FILENAME)
- if os.path.exists(featuresPath):
- os.remove(featuresPath)
-
- def _get_formatVersion(self):
- return self._formatVersion
-
- formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.")
-
- def _get_fileCreator(self):
- return self._fileCreator
-
- fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.")
-
- def _makeDirectory(self, subDirectory=None):
- path = self._path
- if subDirectory:
- path = os.path.join(self._path, subDirectory)
- if not os.path.exists(path):
- os.makedirs(path)
- return path
-
- def _writeMetaInfo(self):
- self._makeDirectory()
- path = os.path.join(self._path, METAINFO_FILENAME)
- metaInfo = dict(
- creator=self._fileCreator,
- formatVersion=self._formatVersion
- )
- writePlistAtomically(metaInfo, path)
-
- def writeGroups(self, groups):
- """
- Write groups.plist. This method requires a
- dict of glyph groups as an argument.
- """
- self._makeDirectory()
- path = os.path.join(self._path, GROUPS_FILENAME)
- groupsNew = {}
- for key, value in groups.items():
- groupsNew[key] = list(value)
- if groupsNew:
- writePlistAtomically(groupsNew, path)
- elif os.path.exists(path):
- os.remove(path)
-
- def writeInfo(self, info):
- """
- Write info.plist. This method requires an object
- that supports getting attributes that follow the
- fontinfo.plist version 2 secification. Attributes
- will be taken from the given object and written
- into the file.
- """
- self._makeDirectory()
- path = os.path.join(self._path, FONTINFO_FILENAME)
- # gather version 2 data
- infoData = {}
- for attr in _fontInfoAttributesVersion2ValueData.keys():
- try:
- value = getattr(info, attr)
- except AttributeError:
- raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr)
- if value is None:
- continue
- infoData[attr] = value
- # validate data
- infoData = _validateInfoVersion2Data(infoData)
- # down convert data to version 1 if necessary
- if self._formatVersion == 1:
- infoData = _convertFontInfoDataVersion2ToVersion1(infoData)
- # write file
- writePlistAtomically(infoData, path)
-
- def writeKerning(self, kerning):
- """
- Write kerning.plist. This method requires a
- dict of kerning pairs as an argument.
- """
- self._makeDirectory()
- path = os.path.join(self._path, KERNING_FILENAME)
- kerningDict = {}
- for left, right in kerning.keys():
- value = kerning[left, right]
- if not left in kerningDict:
- kerningDict[left] = {}
- kerningDict[left][right] = value
- if kerningDict:
- writePlistAtomically(kerningDict, path)
- elif os.path.exists(path):
- os.remove(path)
-
- def writeLib(self, libDict):
- """
- Write lib.plist. This method requires a
- lib dict as an argument.
- """
- self._makeDirectory()
- path = os.path.join(self._path, LIB_FILENAME)
- if libDict:
- writePlistAtomically(libDict, path)
- elif os.path.exists(path):
- os.remove(path)
-
- def writeFeatures(self, features):
- """
- Write features.fea. This method requires a
- features string as an argument.
- """
- if self._formatVersion == 1:
- raise UFOLibError("features.fea is not allowed in UFO Format Version 1.")
- self._makeDirectory()
- path = os.path.join(self._path, FEATURES_FILENAME)
- writeFileAtomically(features, path)
-
- def makeGlyphPath(self):
- """
- Make the glyphs directory in the .ufo.
- Returns the path of the directory created.
- """
- glyphDir = self._makeDirectory(GLYPHS_DIRNAME)
- return glyphDir
-
- def getGlyphSet(self, glyphNameToFileNameFunc=None):
- """
- Return the GlyphSet associated with the
- glyphs directory in the .ufo.
- """
- return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc)
-
-# ----------------
-# Helper Functions
-# ----------------
-
-def makeUFOPath(path):
- """
- Return a .ufo pathname.
-
- >>> makeUFOPath("/directory/something.ext")
- '/directory/something.ufo'
- >>> makeUFOPath("/directory/something.another.thing.ext")
- '/directory/something.another.thing.ufo'
- """
- dir, name = os.path.split(path)
- name = ".".join([".".join(name.split(".")[:-1]), "ufo"])
- return os.path.join(dir, name)
-
-def writePlistAtomically(obj, path):
- """
- Write a plist for "obj" to "path". Do this sort of atomically,
- making it harder to cause corrupt files, for example when writePlist
- encounters an error halfway during write. This also checks to see
- if text matches the text that is already in the file at path.
- If so, the file is not rewritten so that the modification date
- is preserved.
- """
- f = StringIO()
- writePlist(obj, f)
- data = f.getvalue()
- writeFileAtomically(data, path)
-
-def writeFileAtomically(text, path):
- """Write text into a file at path. Do this sort of atomically
- making it harder to cause corrupt files. This also checks to see
- if text matches the text that is already in the file at path.
- If so, the file is not rewritten so that the modification date
- is preserved."""
- if os.path.exists(path):
- f = open(path, READ_MODE)
- oldText = f.read()
- f.close()
- if text == oldText:
- return
- # if the text is empty, remove the existing file
- if not text:
- os.remove(path)
- if text:
- f = open(path, WRITE_MODE)
- f.write(text)
- f.close()
-
-# ----------------------
-# fontinfo.plist Support
-# ----------------------
-
-# Version 1
-
-fontInfoAttributesVersion1 = set([
- "familyName",
- "styleName",
- "fullName",
- "fontName",
- "menuName",
- "fontStyle",
- "note",
- "versionMajor",
- "versionMinor",
- "year",
- "copyright",
- "notice",
- "trademark",
- "license",
- "licenseURL",
- "createdBy",
- "designer",
- "designerURL",
- "vendorURL",
- "unitsPerEm",
- "ascender",
- "descender",
- "capHeight",
- "xHeight",
- "defaultWidth",
- "slantAngle",
- "italicAngle",
- "widthName",
- "weightName",
- "weightValue",
- "fondName",
- "otFamilyName",
- "otStyleName",
- "otMacName",
- "msCharSet",
- "fondID",
- "uniqueID",
- "ttVendor",
- "ttUniqueID",
- "ttVersion",
-])
-
-# Version 2
-
-# Validators
-
-def validateFontInfoVersion2ValueForAttribute(attr, value):
- """
- This performs very basic validation of the value for attribute
- following the UFO fontinfo.plist specification. The results
- of this should not be interpretted as *correct* for the font
- that they are part of. This merely indicates that the value
- is of the proper type and, where the specification defines
- a set range of possible values for an attribute, that the
- value is in the accepted range.
- """
- dataValidationDict = _fontInfoAttributesVersion2ValueData[attr]
- valueType = dataValidationDict.get("type")
- validator = dataValidationDict.get("valueValidator")
- valueOptions = dataValidationDict.get("valueOptions")
- # have specific options for the validator
- if valueOptions is not None:
- isValidValue = validator(value, valueOptions)
- # no specific options
- else:
- if validator == _fontInfoTypeValidator:
- isValidValue = validator(value, valueType)
- else:
- isValidValue = validator(value)
- return isValidValue
-
-def _validateInfoVersion2Data(infoData):
- validInfoData = {}
- for attr, value in infoData.items():
- isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value)
- if not isValidValue:
- raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value)))
- else:
- validInfoData[attr] = value
- return infoData
-
-def _fontInfoTypeValidator(value, typ):
- return isinstance(value, typ)
-
-def _fontInfoVersion2IntListValidator(values, validValues):
- if not isinstance(values, (list, tuple)):
- return False
- valuesSet = set(values)
- validValuesSet = set(validValues)
- if len(valuesSet - validValuesSet) > 0:
- return False
- for value in values:
- if not isinstance(value, int):
- return False
- return True
-
-def _fontInfoVersion2StyleMapStyleNameValidator(value):
- options = ["regular", "italic", "bold", "bold italic"]
- return value in options
-
-def _fontInfoVersion2OpenTypeHeadCreatedValidator(value):
- # format: 0000/00/00 00:00:00
- if not isinstance(value, (str, unicode)):
- return False
- # basic formatting
- if not len(value) == 19:
- return False
- if value.count(" ") != 1:
- return False
- date, time = value.split(" ")
- if date.count("/") != 2:
- return False
- if time.count(":") != 2:
- return False
- # date
- year, month, day = date.split("/")
- if len(year) != 4:
- return False
- if len(month) != 2:
- return False
- if len(day) != 2:
- return False
- try:
- year = int(year)
- month = int(month)
- day = int(day)
- except ValueError:
- return False
- if month < 1 or month > 12:
- return False
- monthMaxDay = calendar.monthrange(year, month)
- if month > monthMaxDay:
- return False
- # time
- hour, minute, second = time.split(":")
- if len(hour) != 2:
- return False
- if len(minute) != 2:
- return False
- if len(second) != 2:
- return False
- try:
- hour = int(hour)
- minute = int(minute)
- second = int(second)
- except ValueError:
- return False
- if hour < 0 or hour > 23:
- return False
- if minute < 0 or minute > 59:
- return False
- if second < 0 or second > 59:
- return True
- # fallback
- return True
-
-def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value):
- if not isinstance(value, int):
- return False
- if value < 0:
- return False
- return True
-
-def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value):
- if not isinstance(value, int):
- return False
- if value < 1:
- return False
- if value > 9:
- return False
- return True
-
-def _fontInfoVersion2OpenTypeOS2PanoseValidator(values):
- if not isinstance(values, (list, tuple)):
- return False
- if len(values) != 10:
- return False
- for value in values:
- if not isinstance(value, int):
- return False
- # XXX further validation?
- return True
-
-def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values):
- if not isinstance(values, (list, tuple)):
- return False
- if len(values) != 2:
- return False
- for value in values:
- if not isinstance(value, int):
- return False
- classID, subclassID = values
- if classID < 0 or classID > 14:
- return False
- if subclassID < 0 or subclassID > 15:
- return False
- return True
-
-def _fontInfoVersion2PostscriptBluesValidator(values):
- if not isinstance(values, (list, tuple)):
- return False
- if len(values) > 14:
- return False
- if len(values) % 2:
- return False
- for value in values:
- if not isinstance(value, (int, float)):
- return False
- return True
-
-def _fontInfoVersion2PostscriptOtherBluesValidator(values):
- if not isinstance(values, (list, tuple)):
- return False
- if len(values) > 10:
- return False
- if len(values) % 2:
- return False
- for value in values:
- if not isinstance(value, (int, float)):
- return False
- return True
-
-def _fontInfoVersion2PostscriptStemsValidator(values):
- if not isinstance(values, (list, tuple)):
- return False
- if len(values) > 12:
- return False
- for value in values:
- if not isinstance(value, (int, float)):
- return False
- return True
-
-def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value):
- validValues = range(1, 21)
- if value not in validValues:
- return False
- return True
-
-# Attribute Definitions
-# This defines the attributes, types and, in some
-# cases the possible values, that can exist is
-# fontinfo.plist.
-
-_fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14)
-_fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4, 7, 8, 9]
-_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128)
-_fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64)
-_fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9]
-
-_fontInfoAttributesVersion2ValueData = {
- "familyName" : dict(type=(str, unicode)),
- "styleName" : dict(type=(str, unicode)),
- "styleMapFamilyName" : dict(type=(str, unicode)),
- "styleMapStyleName" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator),
- "versionMajor" : dict(type=int),
- "versionMinor" : dict(type=int),
- "year" : dict(type=int),
- "copyright" : dict(type=(str, unicode)),
- "trademark" : dict(type=(str, unicode)),
- "unitsPerEm" : dict(type=(int, float)),
- "descender" : dict(type=(int, float)),
- "xHeight" : dict(type=(int, float)),
- "capHeight" : dict(type=(int, float)),
- "ascender" : dict(type=(int, float)),
- "italicAngle" : dict(type=(float, int)),
- "note" : dict(type=(str, unicode)),
- "openTypeHeadCreated" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator),
- "openTypeHeadLowestRecPPEM" : dict(type=(int, float)),
- "openTypeHeadFlags" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions),
- "openTypeHheaAscender" : dict(type=(int, float)),
- "openTypeHheaDescender" : dict(type=(int, float)),
- "openTypeHheaLineGap" : dict(type=(int, float)),
- "openTypeHheaCaretSlopeRise" : dict(type=int),
- "openTypeHheaCaretSlopeRun" : dict(type=int),
- "openTypeHheaCaretOffset" : dict(type=(int, float)),
- "openTypeNameDesigner" : dict(type=(str, unicode)),
- "openTypeNameDesignerURL" : dict(type=(str, unicode)),
- "openTypeNameManufacturer" : dict(type=(str, unicode)),
- "openTypeNameManufacturerURL" : dict(type=(str, unicode)),
- "openTypeNameLicense" : dict(type=(str, unicode)),
- "openTypeNameLicenseURL" : dict(type=(str, unicode)),
- "openTypeNameVersion" : dict(type=(str, unicode)),
- "openTypeNameUniqueID" : dict(type=(str, unicode)),
- "openTypeNameDescription" : dict(type=(str, unicode)),
- "openTypeNamePreferredFamilyName" : dict(type=(str, unicode)),
- "openTypeNamePreferredSubfamilyName" : dict(type=(str, unicode)),
- "openTypeNameCompatibleFullName" : dict(type=(str, unicode)),
- "openTypeNameSampleText" : dict(type=(str, unicode)),
- "openTypeNameWWSFamilyName" : dict(type=(str, unicode)),
- "openTypeNameWWSSubfamilyName" : dict(type=(str, unicode)),
- "openTypeOS2WidthClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator),
- "openTypeOS2WeightClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator),
- "openTypeOS2Selection" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions),
- "openTypeOS2VendorID" : dict(type=(str, unicode)),
- "openTypeOS2Panose" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator),
- "openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator),
- "openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions),
- "openTypeOS2CodePageRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions),
- "openTypeOS2TypoAscender" : dict(type=(int, float)),
- "openTypeOS2TypoDescender" : dict(type=(int, float)),
- "openTypeOS2TypoLineGap" : dict(type=(int, float)),
- "openTypeOS2WinAscent" : dict(type=(int, float)),
- "openTypeOS2WinDescent" : dict(type=(int, float)),
- "openTypeOS2Type" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions),
- "openTypeOS2SubscriptXSize" : dict(type=(int, float)),
- "openTypeOS2SubscriptYSize" : dict(type=(int, float)),
- "openTypeOS2SubscriptXOffset" : dict(type=(int, float)),
- "openTypeOS2SubscriptYOffset" : dict(type=(int, float)),
- "openTypeOS2SuperscriptXSize" : dict(type=(int, float)),
- "openTypeOS2SuperscriptYSize" : dict(type=(int, float)),
- "openTypeOS2SuperscriptXOffset" : dict(type=(int, float)),
- "openTypeOS2SuperscriptYOffset" : dict(type=(int, float)),
- "openTypeOS2StrikeoutSize" : dict(type=(int, float)),
- "openTypeOS2StrikeoutPosition" : dict(type=(int, float)),
- "openTypeVheaVertTypoAscender" : dict(type=(int, float)),
- "openTypeVheaVertTypoDescender" : dict(type=(int, float)),
- "openTypeVheaVertTypoLineGap" : dict(type=(int, float)),
- "openTypeVheaCaretSlopeRise" : dict(type=int),
- "openTypeVheaCaretSlopeRun" : dict(type=int),
- "openTypeVheaCaretOffset" : dict(type=(int, float)),
- "postscriptFontName" : dict(type=(str, unicode)),
- "postscriptFullName" : dict(type=(str, unicode)),
- "postscriptSlantAngle" : dict(type=(float, int)),
- "postscriptUniqueID" : dict(type=int),
- "postscriptUnderlineThickness" : dict(type=(int, float)),
- "postscriptUnderlinePosition" : dict(type=(int, float)),
- "postscriptIsFixedPitch" : dict(type=bool),
- "postscriptBlueValues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator),
- "postscriptOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator),
- "postscriptFamilyBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator),
- "postscriptFamilyOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator),
- "postscriptStemSnapH" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator),
- "postscriptStemSnapV" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator),
- "postscriptBlueFuzz" : dict(type=(int, float)),
- "postscriptBlueShift" : dict(type=(int, float)),
- "postscriptBlueScale" : dict(type=(float, int)),
- "postscriptForceBold" : dict(type=bool),
- "postscriptDefaultWidthX" : dict(type=(int, float)),
- "postscriptNominalWidthX" : dict(type=(int, float)),
- "postscriptWeightName" : dict(type=(str, unicode)),
- "postscriptDefaultCharacter" : dict(type=(str, unicode)),
- "postscriptWindowsCharacterSet" : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator),
- "macintoshFONDFamilyID" : dict(type=int),
- "macintoshFONDName" : dict(type=(str, unicode)),
-}
-fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys())
-
-# insert the type validator for all attrs that
-# have no defined validator.
-for attr, dataDict in _fontInfoAttributesVersion2ValueData.items():
- if "valueValidator" not in dataDict:
- dataDict["valueValidator"] = _fontInfoTypeValidator
-
-# Version Conversion Support
-# These are used from converting from version 1
-# to version 2 or vice-versa.
-
-def _flipDict(d):
- flipped = {}
- for key, value in d.items():
- flipped[value] = key
- return flipped
-
-_fontInfoAttributesVersion1To2 = {
- "menuName" : "styleMapFamilyName",
- "designer" : "openTypeNameDesigner",
- "designerURL" : "openTypeNameDesignerURL",
- "createdBy" : "openTypeNameManufacturer",
- "vendorURL" : "openTypeNameManufacturerURL",
- "license" : "openTypeNameLicense",
- "licenseURL" : "openTypeNameLicenseURL",
- "ttVersion" : "openTypeNameVersion",
- "ttUniqueID" : "openTypeNameUniqueID",
- "notice" : "openTypeNameDescription",
- "otFamilyName" : "openTypeNamePreferredFamilyName",
- "otStyleName" : "openTypeNamePreferredSubfamilyName",
- "otMacName" : "openTypeNameCompatibleFullName",
- "weightName" : "postscriptWeightName",
- "weightValue" : "openTypeOS2WeightClass",
- "ttVendor" : "openTypeOS2VendorID",
- "uniqueID" : "postscriptUniqueID",
- "fontName" : "postscriptFontName",
- "fondID" : "macintoshFONDFamilyID",
- "fondName" : "macintoshFONDName",
- "defaultWidth" : "postscriptDefaultWidthX",
- "slantAngle" : "postscriptSlantAngle",
- "fullName" : "postscriptFullName",
- # require special value conversion
- "fontStyle" : "styleMapStyleName",
- "widthName" : "openTypeOS2WidthClass",
- "msCharSet" : "postscriptWindowsCharacterSet"
-}
-_fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2)
-deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys())
-
-_fontStyle1To2 = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
-}
-_fontStyle2To1 = _flipDict(_fontStyle1To2)
-# Some UFO 1 files have 0
-_fontStyle1To2[0] = "regular"
-
-_widthName1To2 = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9
-}
-_widthName2To1 = _flipDict(_widthName1To2)
-# FontLab's default width value is "Normal".
-# Many format version 1 UFOs will have this.
-_widthName1To2["Normal"] = 5
-# FontLab has an "All" width value. In UFO 1
-# move this up to "Normal".
-_widthName1To2["All"] = 5
-# "medium" appears in a lot of UFO 1 files.
-_widthName1To2["medium"] = 5
-# "Medium" appears in a lot of UFO 1 files.
-_widthName1To2["Medium"] = 5
-
-_msCharSet1To2 = {
- 0 : 1,
- 1 : 2,
- 2 : 3,
- 77 : 4,
- 128 : 5,
- 129 : 6,
- 130 : 7,
- 134 : 8,
- 136 : 9,
- 161 : 10,
- 162 : 11,
- 163 : 12,
- 177 : 13,
- 178 : 14,
- 186 : 15,
- 200 : 16,
- 204 : 17,
- 222 : 18,
- 238 : 19,
- 255 : 20
-}
-_msCharSet2To1 = _flipDict(_msCharSet1To2)
-
-def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value):
- """
- Convert value from version 1 to version 2 format.
- Returns the new attribute name and the converted value.
- If the value is None, None will be returned for the new value.
- """
- # convert floats to ints if possible
- if isinstance(value, float):
- if int(value) == value:
- value = int(value)
- if value is not None:
- if attr == "fontStyle":
- v = _fontStyle1To2.get(value)
- if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
- value = v
- elif attr == "widthName":
- v = _widthName1To2.get(value)
- if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
- value = v
- elif attr == "msCharSet":
- v = _msCharSet1To2.get(value)
- if v is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr))
- value = v
- attr = _fontInfoAttributesVersion1To2.get(attr, attr)
- return attr, value
-
-def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value):
- """
- Convert value from version 2 to version 1 format.
- Returns the new attribute name and the converted value.
- If the value is None, None will be returned for the new value.
- """
- if value is not None:
- if attr == "styleMapStyleName":
- value = _fontStyle2To1.get(value)
- elif attr == "openTypeOS2WidthClass":
- value = _widthName2To1.get(value)
- elif attr == "postscriptWindowsCharacterSet":
- value = _msCharSet2To1.get(value)
- attr = _fontInfoAttributesVersion2To1.get(attr, attr)
- return attr, value
-
-def _convertFontInfoDataVersion1ToVersion2(data):
- converted = {}
- for attr, value in data.items():
- # FontLab gives -1 for the weightValue
- # for fonts wil no defined value. Many
- # format version 1 UFOs will have this.
- if attr == "weightValue" and value == -1:
- continue
- newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value)
- # skip if the attribute is not part of version 2
- if newAttr not in fontInfoAttributesVersion2:
- continue
- # catch values that can't be converted
- if value is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr))
- # store
- converted[newAttr] = newValue
- return converted
-
-def _convertFontInfoDataVersion2ToVersion1(data):
- converted = {}
- for attr, value in data.items():
- newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value)
- # only take attributes that are registered for version 1
- if newAttr not in fontInfoAttributesVersion1:
- continue
- # catch values that can't be converted
- if value is None:
- raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr))
- # store
- converted[newAttr] = newValue
- return converted
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/misc/pylib/robofab/world.py b/misc/pylib/robofab/world.py
deleted file mode 100644
index 203753d6c..000000000
--- a/misc/pylib/robofab/world.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import os, sys
-from robofab import RoboFabError, version, numberVersion
-
-
-class RFWorld:
-
- """All parameters about platforms, versions and environments included in one object."""
-
- def __init__(self):
- self.mac = None
- self.pc = None
- self.platform = sys.platform
- self.applicationName = None # name of the application we're running in
- self.name = os.name
- self.version = version # the robofab version
- self.numberVersion = numberVersion
- self.run = True
-
- # get some platform information
- if self.name == 'mac' or self.name == 'posix':
- if self.platform == "darwin":
- self.mac = "X"
- else:
- self.mac = "pre-X"
- elif self.name == 'nt':
- # if you know more about PC & win stuff, add it here!
- self.pc = True
- else:
- raise RoboFabError, "We're running on an unknown platform."
-
- # collect versions
- self.pyVersion = sys.version[:3]
- self.inPython = False
- self.inFontLab = False
- self.flVersion = None
- self.inGlyphs = False
- self.glyphsVersion = None
- self.inRoboFont = False
- self.roboFontVersion = None
-
- # are we in Glyphs?
- try:
- import objectsGS
- from AppKit import NSBundle
- bundle = NSBundle.mainBundle()
- self.applicationName = bundle.infoDictionary()["CFBundleName"]
- self.inGlyphs = True
- self.glyphsVersion = bundle.infoDictionary()["CFBundleVersion"]
- except ImportError:
- # are we in RoboFont
- try:
- import mojo
- from AppKit import NSBundle
- bundle = NSBundle.mainBundle()
- self.applicationName = bundle.infoDictionary()["CFBundleName"]
- self.inRoboFont = True
- self.roboFontVersion = bundle.infoDictionary()["CFBundleVersion"]
- except ImportError:
- # are we in FontLab?
- try:
- from FL import fl
- self.applicationName = fl.filename
- self.inFontLab = True
- self.flVersion = fl.version
- except ImportError: pass
- # we are in NoneLab
- if not self.inFontLab:
- self.inPython = True
-
- # see if we have DialogKit
- self.supportsDialogKit = False
-
- def __repr__(self):
- s = [
- "Robofab is running on %s" % self.platform,
- "Python version: %s" % self.pyVersion,
- "Mac stuff: %s" % self.mac,
- "PC stuff: %s" % self.pc,
- "FontLab stuff: %s" % self.inFontLab,
- "FLversion: %s" % self.flVersion,
- "Glyphs stuff: %s" % self.inGlyphs,
- "Glyphs version: %s" % self.glyphsVersion,
- "RoboFont stuff: %s" %self.inRoboFont,
- "RoboFont version: %s" %self.roboFontVersion,
- ]
- return ", ".join(s)
-
-
-world = RFWorld()
-
-lineBreak = os.linesep
-
-if world.inFontLab:
- from robofab.interface.all.dialogs import SelectFont, SelectGlyph
- from robofab.objects.objectsFL import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts
- lineBreak = "\n"
-elif world.inRoboFont:
- from mojo.roboFont import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts
-elif world.inGlyphs:
- from objectsGS import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts
-elif world.inPython:
- from robofab.objects.objectsRF import CurrentFont, CurrentGlyph, RFont, RGlyph, OpenFont, NewFont, AllFonts
-
-
-
-if __name__ == "__main__":
- f = RFWorld()
- print f
diff --git a/misc/pylib/robofab/xmlTreeBuilder.pyx b/misc/pylib/robofab/xmlTreeBuilder.pyx
deleted file mode 100644
index be621e14f..000000000
--- a/misc/pylib/robofab/xmlTreeBuilder.pyx
+++ /dev/null
@@ -1,116 +0,0 @@
-import os
-try:
- from xml.parsers.expat import ParserCreate
-except ImportError:
- _haveExpat = 0
- from xml.parsers.xmlproc.xmlproc import XMLProcessor
-else:
- _haveExpat = 1
-
-
-class XMLParser:
-
- def __init__(self):
- self.root = []
- self.current = (self.root, None)
-
- def getRoot(self):
- assert len(self.root) == 1
- return self.root[0]
-
- def startElementHandler(self, name, attrs):
- children = []
- self.current = (children, name, attrs, self.current)
-
- def endElementHandler(self, name):
- children, name, attrs, previous = self.current
- previous[0].append((name, attrs, children))
- self.current = previous
-
- def characterDataHandler(self, data):
- nodes = self.current[0]
- if nodes and type(nodes[-1]) == type(data):
- nodes[-1] = nodes[-1] + data
- else:
- nodes.append(data)
-
- def _expatParseFile(self, pathOrFile):
- parser = ParserCreate()
- parser.returns_unicode = 0 # XXX, Don't remember why. It sucks, though.
- parser.StartElementHandler = self.startElementHandler
- parser.EndElementHandler = self.endElementHandler
- parser.CharacterDataHandler = self.characterDataHandler
- if isinstance(pathOrFile, (str, unicode)):
- f = open(pathOrFile)
- didOpen = 1
- else:
- didOpen = 0
- f = pathOrFile
- parser.ParseFile(f)
- if didOpen:
- f.close()
- return self.getRoot()
-
- def _xmlprocDataHandler(self, data, begin, end):
- self.characterDataHandler(data[begin:end])
-
- def _xmlprocParseFile(self, pathOrFile):
- proc = XMLProcessor()
- proc.app.handle_start_tag = self.startElementHandler
- proc.app.handle_end_tag = self.endElementHandler
- proc.app.handle_data = self._xmlprocDataHandler
- if isinstance(pathOrFile, (str, unicode)):
- f = open(pathOrFile)
- didOpen = 1
- else:
- didOpen = 0
- f = pathOrFile
- proc.parseStart()
- proc.read_from(f)
- proc.flush()
- proc.parseEnd()
- proc.deref()
- if didOpen:
- f.close()
- return self.getRoot()
-
- if _haveExpat:
- parseFile = _expatParseFile
- else:
- parseFile = _xmlprocParseFile
-
-
-def stripCharacterData(nodes, recursive=True):
- i = 0
- while 1:
- try:
- node = nodes[i]
- except IndexError:
- break
- if isinstance(node, tuple):
- if recursive:
- stripCharacterData(node[2])
- i = i + 1
- else:
- node = node.strip()
- if node:
- nodes[i] = node
- i = i + 1
- else:
- del nodes[i]
-
-
-def buildTree(pathOrFile, stripData=1):
- parser = XMLParser()
- tree = parser.parseFile(pathOrFile)
- if stripData:
- stripCharacterData(tree[2])
- return tree
-
-
-if __name__ == "__main__":
- from pprint import pprint
- import sys
- strip = bool(sys.argv[2:])
- tree = buildTree(sys.argv[1], strip)
- pprint(tree)
diff --git a/misc/cleanup_kerning.py b/misc/tools/cleanup_kerning.py
index e9dce5771..e9dce5771 100755
--- a/misc/cleanup_kerning.py
+++ b/misc/tools/cleanup_kerning.py
diff --git a/misc/download-count.py b/misc/tools/download-count.py
index c70532490..c70532490 100755
--- a/misc/download-count.py
+++ b/misc/tools/download-count.py
diff --git a/misc/enrich-glypnames.py b/misc/tools/enrich-glypnames.py
index b4c401217..b4c401217 100755
--- a/misc/enrich-glypnames.py
+++ b/misc/tools/enrich-glypnames.py
diff --git a/misc/fixup-diacritics.py b/misc/tools/fixup-diacritics.py
index 2453e7f3c..2453e7f3c 100755
--- a/misc/fixup-diacritics.py
+++ b/misc/tools/fixup-diacritics.py
diff --git a/misc/fixup-features.py b/misc/tools/fixup-features.py
index dd4c2658f..dd4c2658f 100755
--- a/misc/fixup-features.py
+++ b/misc/tools/fixup-features.py
diff --git a/misc/fixup-kerning.py b/misc/tools/fixup-kerning.py
index fc4ce8071..fc4ce8071 100755
--- a/misc/fixup-kerning.py
+++ b/misc/tools/fixup-kerning.py
diff --git a/misc/fontinfo.py b/misc/tools/fontinfo.py
index 0f406a14d..0f406a14d 100755
--- a/misc/fontinfo.py
+++ b/misc/tools/fontinfo.py
diff --git a/misc/gen-glyphinfo.py b/misc/tools/gen-glyphinfo.py
index 4fdf73fae..4fdf73fae 100755
--- a/misc/gen-glyphinfo.py
+++ b/misc/tools/gen-glyphinfo.py
diff --git a/misc/gen-glyphorder.py b/misc/tools/gen-glyphorder.py
index 3d44fdd16..3d44fdd16 100755
--- a/misc/gen-glyphorder.py
+++ b/misc/tools/gen-glyphorder.py
diff --git a/misc/gen-kern.py b/misc/tools/gen-kern.py
index e5a4c4875..e5a4c4875 100644
--- a/misc/gen-kern.py
+++ b/misc/tools/gen-kern.py
diff --git a/misc/gen-metrics-and-svgs.py b/misc/tools/gen-metrics-and-svgs.py
index ac100eb1c..ac100eb1c 100755
--- a/misc/gen-metrics-and-svgs.py
+++ b/misc/tools/gen-metrics-and-svgs.py
diff --git a/misc/gen-num-pairs.js b/misc/tools/gen-num-pairs.js
index 9dbb92090..9dbb92090 100644
--- a/misc/gen-num-pairs.js
+++ b/misc/tools/gen-num-pairs.js
diff --git a/misc/gen-tnum.py b/misc/tools/gen-tnum.py
index 015201bcc..015201bcc 100755
--- a/misc/gen-tnum.py
+++ b/misc/tools/gen-tnum.py
diff --git a/misc/glyf-props.py b/misc/tools/glyf-props.py
index 8783a422d..8783a422d 100755
--- a/misc/glyf-props.py
+++ b/misc/tools/glyf-props.py
diff --git a/misc/glyphcheck.py b/misc/tools/glyphcheck.py
index 755de686f..755de686f 100755
--- a/misc/glyphcheck.py
+++ b/misc/tools/glyphcheck.py
diff --git a/misc/kernsample.py b/misc/tools/kernsample.py
index 7e1fbe0f8..7e1fbe0f8 100755
--- a/misc/kernsample.py
+++ b/misc/tools/kernsample.py
diff --git a/misc/restore-diacritics-kerning.py b/misc/tools/restore-diacritics-kerning.py
index 6fd8c1601..6fd8c1601 100644
--- a/misc/restore-diacritics-kerning.py
+++ b/misc/tools/restore-diacritics-kerning.py
diff --git a/misc/rewrite-glyphorder.py b/misc/tools/rewrite-glyphorder.py
index 3da0c1699..3da0c1699 100755
--- a/misc/rewrite-glyphorder.py
+++ b/misc/tools/rewrite-glyphorder.py
diff --git a/misc/rmglyph.py b/misc/tools/rmglyph.py
index 7e6d7f408..7e6d7f408 100755
--- a/misc/rmglyph.py
+++ b/misc/tools/rmglyph.py
diff --git a/misc/show-changes.py b/misc/tools/show-changes.py
index 59f79b3e5..59f79b3e5 100755
--- a/misc/show-changes.py
+++ b/misc/tools/show-changes.py
diff --git a/misc/svgsync.py b/misc/tools/svgsync.py
index d8781272b..d8781272b 100755
--- a/misc/svgsync.py
+++ b/misc/tools/svgsync.py
diff --git a/misc/svgsync2.py b/misc/tools/svgsync2.py
index 992d6d314..992d6d314 100755
--- a/misc/svgsync2.py
+++ b/misc/tools/svgsync2.py
diff --git a/misc/ufo-color-glyphs.py b/misc/tools/ufo-color-glyphs.py
index 9ec050853..9ec050853 100755
--- a/misc/ufo-color-glyphs.py
+++ b/misc/tools/ufo-color-glyphs.py
diff --git a/misc/unicode_util.py b/misc/tools/unicode_util.py
index 18196e87e..18196e87e 100644
--- a/misc/unicode_util.py
+++ b/misc/tools/unicode_util.py
diff --git a/misc/versionize-css.py b/misc/tools/versionize-css.py
index 0b317462f..0b317462f 100755
--- a/misc/versionize-css.py
+++ b/misc/tools/versionize-css.py
diff --git a/misc/ufocompile b/misc/ufocompile
deleted file mode 100755
index 41cfeb043..000000000
--- a/misc/ufocompile
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/env python
-from __future__ import print_function
-import os
-import sys
-import argparse
-import logging
-import subprocess
-
-from shutil import copyfile
-from robofab.objects.objectsRF import RPoint
-from robofab.world import OpenFont
-from fontbuild.Build import FontProject
-from fontbuild.mix import Master
-from fontbuild.mix import Mix
-
-
-FAMILYNAME = "Inter UI"
-BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
-
-def extractSpecializedGlyphs(masterFont):
- glyphSpecializations = {}
- specializationSuffix = '.specz.'
-
- specializedGlyphNames = []
- font = masterFont.font
- for g in font:
- p = g.name.find(specializationSuffix)
- if p == -1:
- continue
- name = g.name[:p]
- category = g.name[p + len(specializationSuffix):]
- g2 = g.copy()
- g2.name = name
- if name in font:
- # copy unicodes
- masterGlyph = font[name]
- g2.unicodes = masterGlyph.unicodes
- if not category in glyphSpecializations:
- glyphSpecializations[category] = { name: g2 }
- else:
- glyphSpecializations[category][name] = g2
- specializedGlyphNames.append(g.name)
-
- ffont = masterFont.ffont
- for name in specializedGlyphNames:
- del ffont.glyphs[name]
- font.removeGlyph(name)
-
- return glyphSpecializations
-
-
-def readVersionControlTag(dir):
- try:
- return subprocess.check_output(
- ['git', '-C', dir, 'rev-parse', '--short', 'HEAD'],
- shell=False).strip()
- except:
- return ''
-
-
-def main():
- # silence warnings from fontTools.misc.fixedTools that is harmless and caused
- # by the ufo2ft module.
- logging.getLogger('fontTools.misc.fixedTools').setLevel(logging.ERROR)
-
- default_out_dir = os.path.join(BASEDIR,'build','tmp')
- srcDir = os.path.join(BASEDIR, 'src')
-
- argparser = argparse.ArgumentParser(
- description='Build TTF and OTF font files from UFO sources.')
-
- argparser.add_argument(
- 'styles', metavar='<style>', type=str, nargs='*',
- help='Build specific styles. Omit to build all.')
-
- argparser.add_argument(
- '--otf', dest='otf', action='store_const',
- const=True, default=False, help='Build OTF files')
-
- argparser.add_argument(
- '--no-ttf', dest='no_ttf', action='store_const',
- const=True, default=False, help='Do not build TTF files')
-
- argparser.add_argument(
- '--cufo', dest='cufo', action='store_const',
- const=True, default=False, help='Only compile composite UFO')
-
- argparser.add_argument(
- '--out', dest='out', metavar='<dir>', type=str, default=default_out_dir,
- help='Write output to <dir> instead of the default (%r)' % default_out_dir)
-
- args = argparser.parse_args()
- styles = [s.lower() for s in args.styles]
- handledStyles = []
- ALL = len(styles) == 0
-
- # version control tag, if any
- buildTag = readVersionControlTag(BASEDIR)
-
- # Since we reference a shared feature file, copy it to build dir so includes
- # works
- ufoTmpDir = os.path.join(args.out, 'InterUIUFO')
- try:
- os.makedirs(ufoTmpDir)
- except:
- pass
- copyfile(
- os.path.join(srcDir, 'features.fea'),
- os.path.join(ufoTmpDir, 'features.fea'))
-
- # Load masters
- print('loading master: Regular')
- rg = Master("%s/src/Inter-UI-Regular.ufo" % BASEDIR)
-
- bl = None
- if ALL \
- or 'black' in styles or 'blackitalic' in styles \
- or 'bold' in styles or 'bolditalic' in styles \
- or 'medium' in styles or 'mediumitalic' in styles:
- print('loading master: Black')
- bl = Master("%s/src/Inter-UI-Black.ufo" % BASEDIR)
-
-
- glyphSpecializations = extractSpecializedGlyphs(rg)
-
-
- class Mix2(Mix):
- def __init__(self, masters, v, glyphSpecializations=None):
- Mix.__init__(self, masters, v)
- self.glyphSpecializations = glyphSpecializations
-
- def mixGlyphs(self, gname):
- if self.glyphSpecializations is not None:
- specializedGlyph = self.glyphSpecializations.get(gname)
- if specializedGlyph is not None:
- print('mixglyph using specialized', gname)
- return specializedGlyph
- return Mix.mixGlyphs(self, gname)
-
- proj = FontProject(
- rg.font, BASEDIR, os.path.join(srcDir,'fontbuild.cfg'), buildTag=buildTag)
- proj.builddir = args.out
-
- if args.cufo:
- proj.justCompileUFO = True
-
- # panose for entire family
- panose = {
- 'bFamilyType': 2, # Latin Text
- 'bSerifStyle': 11, # Normal Sans
- 'bProportion': 2, # Old Style
- 'bContrast': 3, # Very Low (thickest vs thinnest stem of "O")
- 'bXHeight': 4, # Constant/Large
- # bWeight: see http://monotype.de/services/pan2#_Toc380547249
- }
-
- def mkpanose(weight):
- return dict(panose.items() + {
- 'bWeight': weight
- }.items())
-
-
- if args.otf:
- proj.buildOTF = True
-
- # name syntax: family/styleName/styleCode/subfamilyAbbrev
- #
- # styleCode should be one of:
- # Regular, Italic, Bold, Bold Italic
- #
- # scaleX controls scale on the x axis, used for italics. 1.0 means no scaling.
- # italicMeanYCenter controls how far on the x axis the glyph should slide
- # to compensate for the slant. -600 is sort of a magic number arrived to by
- # trial and error.
- #
-
- if ALL or 'regular' in styles:
- handledStyles.append('regular')
- proj.generateFont(rg.font, "%s/Regular/Regular/Rg" % FAMILYNAME,
- panose=mkpanose(5))
-
- if ALL or 'italic' in styles:
- handledStyles.append('italic')
- proj.generateFont(
- rg.font, "%s/Italic/Italic/Rg" % FAMILYNAME,
- italic=True, stemWidth=232, italicMeanYCenter=-600,
- panose=mkpanose(5))
-
-
- if ALL or 'medium' in styles:
- handledStyles.append('medium')
- proj.generateFont(
- Mix2([rg, bl], 0.32, glyphSpecializations.get('medium', {})),
- "%s/Medium/Regular/Me" % FAMILYNAME,
- panose=mkpanose(6))
-
- if ALL or 'mediumitalic' in styles:
- handledStyles.append('mediumitalic')
- proj.generateFont(
- Mix2([rg, bl], 0.32, glyphSpecializations.get('medium', {})),
- "%s/Medium Italic/Italic/Me" % FAMILYNAME,
- italic=True, stemWidth=300, italicMeanYCenter=-600,
- panose=mkpanose(6))
-
-
- if ALL or 'bold' in styles:
- handledStyles.append('bold')
- proj.generateFont(
- Mix2([rg, bl], 0.68, glyphSpecializations.get('bold', {})),
- "%s/Bold/Bold/Rg" % FAMILYNAME,
- panose=mkpanose(8))
-
- if ALL or 'bolditalic' in styles:
- handledStyles.append('bolditalic')
- proj.generateFont(
- Mix2([rg, bl], 0.68, glyphSpecializations.get('bold', {})),
- "%s/Bold Italic/Bold Italic/Rg" % FAMILYNAME,
- italic=True, stemWidth=350, italicMeanYCenter=-600,
- panose=mkpanose(8))
-
-
- if ALL or 'black' in styles:
- handledStyles.append('black')
- proj.generateFont(bl.font, "%s/Black/Regular/Bl" % FAMILYNAME,
- panose=mkpanose(9))
-
- if ALL or 'blackitalic' in styles:
- handledStyles.append('blackitalic')
- proj.generateFont(
- bl.font, "%s/Black Italic/Italic/Bl" % FAMILYNAME,
- italic=True, stemWidth=400, italicMeanYCenter=-600,
- panose=mkpanose(9))
-
- # generate TTFs
- if args.no_ttf == False and args.cufo == False:
- proj.generateTTFs()
-
- if not ALL:
- diff = set(styles).difference(set(handledStyles))
- if len(diff) != 0:
- print('Unknown styles %r' % diff, file=sys.stderr)
- sys.exit(1)
-
-main()
-
-# import hotshot, hotshot.stats, test.pystone
-# prof = hotshot.Profile("ufocompile.prof")
-# benchtime = prof.runcall(main)
-# prof.close()
-# stats = hotshot.stats.load("ufocompile.prof")
-# # stats.strip_dirs()
-# stats.sort_stats('time', 'calls')
-# stats.print_stats(40)
diff --git a/misc/version.py b/misc/version.py
deleted file mode 100755
index d6136ea9f..000000000
--- a/misc/version.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-# encoding: utf8
-from __future__ import print_function
-import os, sys
-from collections import OrderedDict
-from ConfigParser import RawConfigParser
-
-
-def main():
- srcDir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'src')
- config = RawConfigParser(dict_type=OrderedDict)
- config.read(os.path.join(srcDir, 'fontbuild.cfg'))
- sys.stdout.write(config.get('main', 'version'))
-
-if __name__ == '__main__':
- main()