├── .gitignore ├── README.md ├── Resources ├── mathShape.js ├── snap.svg.js ├── styles.css └── template.html ├── ResponsiveLettering.glyphsPlugin └── Contents │ ├── Info.plist │ ├── MacOS │ ├── plugin │ └── python │ ├── PkgInfo │ └── Resources │ ├── __boot__.py │ ├── __error__.sh │ ├── mathShape │ ├── __init__.py │ ├── cmd_exportCurrentFont.py │ ├── cmd_prepareNewShape.py │ ├── cmd_scalePastedSVGs.py │ ├── cmd_validateMathShape.py │ ├── exportTools.py │ ├── makePage.py │ └── mathImageSVGPathPen.py │ ├── plugin.py │ ├── resources │ ├── mathShape.js │ ├── placeholder_ms │ │ ├── files.json │ │ ├── narrow-bold.svg │ │ ├── narrow-thin.svg │ │ ├── wide-bold.svg │ │ └── wide-thin.svg │ ├── styles.css │ └── template.html │ └── site.py ├── ResponsiveLettering.roboFontExt ├── Icon ├── info.plist ├── lib │ └── mathShape │ │ ├── __init__.py │ │ ├── cmd_exportCurrentFont.py │ │ ├── cmd_exportVariableFont.py │ │ ├── cmd_prepareNewShape.py │ │ ├── cmd_scalePastedSVGs.py │ │ ├── cmd_validateMathShape.py │ │ ├── exportTools.py │ │ ├── makePage.py │ │ └── mathImageSVGPathPen.py └── resources │ ├── mathShape.js │ ├── placeholder_ms │ ├── files.json │ ├── narrow-bold.svg │ ├── narrow-thin.svg │ ├── wide-bold.svg │ └── wide-thin.svg │ ├── styles.css │ └── template.html ├── RoboFontMathShapeExporter_screen.gif ├── buildExtension.py ├── lib └── mathShape │ ├── __init__.py │ ├── cmd_exportCurrentFont.py │ ├── cmd_exportVariableFont.py │ ├── cmd_prepareNewShape.py │ ├── cmd_scalePastedSVGs.py │ ├── cmd_validateMathShape.py │ ├── exportTools.py │ ├── makePage.py │ └── mathImageSVGPathPen.py ├── peace.png ├── responsiveLettering_screen.jpg └── www ├── example ├── 00_Hnib_original_scan.jpg ├── 01_initial_digitisation_in_robofont_Hnib.ufo │ ├── fontinfo.plist │ ├── glyphs │ │ ├── H_.glif │ │ ├── I_.glif │ │ └── contents.plist │ ├── lib.plist │ └── metainfo.plist ├── 02_template_with_glyphs.ufo │ ├── fontinfo.plist │ ├── glyphs │ │ ├── contents.plist │ │ ├── narrow-bold.glif │ │ ├── narrow-thin.glif │ │ ├── wide-bold.glif │ │ └── wide-thin.glif │ ├── lib.plist │ └── metainfo.plist ├── 02_template_with_glyphs_preview.html ├── 03_more_variations.ufo │ ├── fontinfo.plist │ ├── glyphs │ │ ├── contents.plist │ │ ├── narrow-bold.glif │ │ ├── narrow-thin.glif │ │ ├── wide-bold.glif │ │ └── wide-thin.glif │ ├── lib.plist │ └── metainfo.plist ├── 03_more_variations_preview.html ├── 04_new_version_in_responsive_lettering_template.ufo │ ├── fontinfo.plist │ ├── glyphs │ │ ├── contents.plist │ │ ├── narrow-bold.glif │ │ ├── narrow-thin.glif │ │ ├── wide-bold.glif │ │ └── wide-thin.glif │ ├── lib.plist │ └── metainfo.plist ├── contourordernarrow.jpg ├── contourorderwide.jpg ├── fontwindow.jpg ├── newtemplate.jpg ├── paste.jpg ├── readme.html ├── readme.md ├── readme.pdf ├── responsivedialog.jpg └── spacecenter.jpg ├── example_ms ├── files.json ├── narrow-bold.svg ├── narrow-thin.svg ├── wide-bold.svg └── wide-thin.svg ├── img ├── basicUFO.jpg ├── example.jpg ├── excellence.jpg ├── mathShape.jpg └── peace.jpg ├── index.html ├── introduction.html ├── mathShape.js ├── snap.svg.js ├── styles.css └── template_ms ├── files.json ├── narrow-bold.svg ├── narrow-thin.svg ├── wide-bold.svg └── wide-thin.svg /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # responsiveLettering 2 | 3 | ![Responsive Lettering](responsiveLettering_screen.jpg) 4 | 5 | Responsive lettering: scalable, interpolating vector shapes that can make themselves fit in a range of rectangles. 6 | 7 | * More examples here. 8 | * An introduction of the parts is here. 9 | 10 | The python code consists of a couple of scripts to generate the SVG needed for responsive lettering. These are also included in the RoboFontExtension. All scripts are for RoboFont. 11 | 12 | The www/ folder contains a working example. As it is loading .js and .json files it might be necessary to serve the files from a real server. When everything works it should look something like this. 13 | 14 | The www code depends on 15 | 16 | * jQuery, but probably not a very specific version 17 | * snap.js, a very handy library for manipulating SVG data. 18 | 19 | All the vector data comes from json, so in theory it might be possible to rewrite all this without snap.js. 20 | 21 | ## Robofont extension 22 | 23 | ![RoboFont Extension](RoboFontMathShapeExporter_screen.gif) 24 | 25 | The RoboFont extension is a useful tool for previewing and exporting vector work in a UFO to mathshape data. It can also be installed director from RoboFontMechanic. 26 | 27 | ## Export to Designspace 28 | 29 | ![Variable font generated from a responsive lettering project exported to designspace](peace.png) 30 | Version 1.7 introduces a simple export to [designspace](https://github.com/LettError/designSpaceDocument) with separate master UFOs, which is useful if you want to generate a variable font from the data using [Batch](https://github.com/typemytype/batchRoboFontExtension). The exporter makes some assumptions about the glyph names in the source file. These are valid glyphnames (depending on the model): 31 | 32 | * narrow-thin 33 | * wide-thin 34 | * medium-thin 35 | * narrow-bold 36 | * wide-bold 37 | 38 | The script checks the `font.lib entry` at `com.letterror.mathshape.designspace` for information about the designspace topology. If you worked from a template generated with this extension then the settings are probably right. If there is no entry in the lib the script assumes a twobytwo designspace. 39 | 40 | * `twobytwo`: two axes, four masters 41 | * `twobyone`: one axis, two masters 42 | * `threebyone`: one axis, three masters 43 | 44 | ## Credits 45 | 46 | Ideas and code very much in debt to Jeremie Hornus, Nina Stössinger, Andrew Johnson, Onur Yazıcıgil, and Nick Sherman. 47 | 48 | ## License 49 | 50 | The Responsive Lettering package is published under the [BSD-3 license](http://opensource.org/licenses/BSD-3-Clause). 51 | -------------------------------------------------------------------------------- /Resources/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Verdana"; 3 | font-size: 10pt; 4 | color: orange; 5 | margin: 0; 6 | padding: 0; 7 | background-color: black; 8 | } 9 | h1{ 10 | font-family: "LTRCondensedNo3-Grade2"; 11 | font-size: 40pt; 12 | margin-bottom: 10pt; 13 | margin-top: 2pt; 14 | font-weight: normal; 15 | } 16 | h2{ 17 | font-family: "LTRCondensedNo1-Grade2"; 18 | font-size: 30pt; 19 | margin-bottom: 5pt; 20 | font-weight: normal; 21 | } 22 | .value{ 23 | color:white; 24 | } 25 | 26 | #svgcontainer{ 27 | padding: 0; 28 | margin:0; 29 | height: 100vh; 30 | background-color: none; 31 | margin: auto; 32 | } 33 | 34 | 35 | /*show or hide the reporter*/ 36 | .hideReporter{ 37 | display: hidden; 38 | } 39 | #heyheyhey{ 40 | display: block; 41 | position: fixed; 42 | top: 0; 43 | width: 50vw; 44 | z-index: 1000; 45 | margin-left: 50%; 46 | font-size: 1em; 47 | color: white; 48 | background-color: rgba(100, 100, 100, 0.8); 49 | } 50 | .control{ 51 | position: fixed; 52 | top: 0; 53 | left: 0; 54 | color: white; 55 | padding: 10px; 56 | background-color:black; 57 | } 58 | #outline ul{ 59 | display: table; 60 | } 61 | .cmd { 62 | display: table-row; 63 | } 64 | p.comment{ 65 | color: white; 66 | z-index: 1000; 67 | } -------------------------------------------------------------------------------- /Resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LettError MathShape Preview 5 | 6 | 7 | 8 | 10 | 12 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 32 | 33 |
34 | 35 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | Responsive Lettering 9 | CFBundleExecutable 10 | plugin 11 | CFBundleIdentifier 12 | com.Letterror.ResponsiveLettering 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Responsive Lettering 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | CFBundleShortVersionString 24 | 1.0 25 | LSHasLocalizedDisplayName 26 | 27 | NSAppleScriptEnabled 28 | 29 | NSHumanReadableCopyright 30 | Copyright, LettError, 2016 31 | NSMainNibFile 32 | MainMenu 33 | NSPrincipalClass 34 | ResponsiveLettering 35 | PyMainFileNames 36 | 37 | __boot__ 38 | 39 | PyOptions 40 | 41 | alias 42 | 43 | argv_emulation 44 | 45 | no_chdir 46 | 47 | optimize 48 | 0 49 | prefer_ppc 50 | 51 | site_packages 52 | 53 | use_pythonpath 54 | 55 | 56 | PyResourcePackages 57 | 58 | lib/python2.6 59 | lib/python2.6/lib-dynload 60 | lib/python2.6/site-packages.zip 61 | lib/python26.zip 62 | 63 | PyRuntimeLocations 64 | 65 | @executable_path/../Frameworks/Python.framework/Versions/2.6/Python 66 | /System/Library/Frameworks/Python.framework/Versions/2.6/Python 67 | 68 | PythonInfoDict 69 | 70 | PythonExecutable 71 | /usr/bin/python2.6 72 | PythonLongVersion 73 | 2.6.7 (r267:88850, Oct 11 2012, 20:15:00) 74 | [GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] 75 | PythonShortVersion 76 | 2.6 77 | py2app 78 | 79 | template 80 | bundle 81 | version 82 | 0.6.3 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/MacOS/plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/ResponsiveLettering.glyphsPlugin/Contents/MacOS/plugin -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/MacOS/python: -------------------------------------------------------------------------------- 1 | /System/Library/Frameworks/Python.framework/Versions/2.6/bin/python -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | BNDL???? -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/__boot__.py: -------------------------------------------------------------------------------- 1 | # def _site_packages(): 2 | # import site, sys, os 3 | # paths = [] 4 | # prefixes = [sys.prefix] 5 | # if sys.exec_prefix != sys.prefix: 6 | # prefixes.append(sys.exec_prefix) 7 | # for prefix in prefixes: 8 | # if prefix == sys.prefix: 9 | # paths.append(os.path.join("/Library/Python", sys.version[:3], "site-packages")) 10 | # paths.append(os.path.join(sys.prefix, "Extras", "lib", "python")) 11 | # else: 12 | # paths.append(os.path.join(prefix, 'lib', 'python' + sys.version[:3], 'site-packages')) 13 | # if os.path.join('.framework', '') in os.path.join(sys.prefix, ''): 14 | # home = os.environ.get('HOME') 15 | # if home: 16 | # paths.append(os.path.join(home, 'Library', 'Python', sys.version[:3], 'site-packages')) 17 | # 18 | # # Workaround for a misfeature in setuptools: easy_install.pth places 19 | # # site-packages way too early on sys.path and that breaks py2app bundles. 20 | # # NOTE: this hacks into an undocumented feature of setuptools and 21 | # # might stop to work without warning. 22 | # sys.__egginsert = len(sys.path) 23 | # 24 | # for path in paths: 25 | # site.addsitedir(path) 26 | # 27 | # _site_packages() 28 | # 29 | # def _path_inject(): 30 | # import sys 31 | # sys.path[:0] = sys.path[0] 32 | # 33 | # _path_inject() 34 | 35 | def _run(*scripts): 36 | global __file__ 37 | import os, sys #, site 38 | sys.frozen = 'macosx_plugin' 39 | base = os.environ['RESOURCEPATH'] 40 | # site.addsitedir(base) 41 | # site.addsitedir(os.path.join(base, 'Python', 'site-packages')) 42 | for script in scripts: 43 | path = os.path.join(base, script) 44 | __file__ = path 45 | execfile(path, globals(), globals()) 46 | 47 | _run('plugin.py') 48 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/__error__.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This is the default bundletemplate error script 4 | # Note that this DOES NOT present a GUI dialog, because 5 | # it has no output on stdout, and has a return value of 0. 6 | # 7 | if ( test -n "$2" ) ; then 8 | echo "[$1] Unexpected Exception:" 1>&2 9 | echo "$2: $3" 1>&2 10 | else 11 | echo "[$1] Could not find a suitable Python runtime" 1>&2 12 | fi 13 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/__init__.py: -------------------------------------------------------------------------------- 1 | # package for exporting mathshapes from robofont. -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/cmd_prepareNewShape.py: -------------------------------------------------------------------------------- 1 | import vanilla 2 | from mojo.canvas import Canvas 3 | from AppKit import NSNumberFormatter 4 | from defconAppKit.windows.baseWindow import BaseWindowController 5 | from robofab.world import NewFont 6 | """ 7 | 8 | Make a new UFO and give it the appropriate glyphs and layers. 9 | 10 | """ 11 | import os, time 12 | 13 | def prepareMathShapeUFO(narrow=500, wide=2500, upm=1000, familyName="MathShape", styleName="New"): 14 | f = NewFont(familyName=familyName, styleName=styleName) 15 | f.info.note = "This is a template font for a MathShape. The font names and glyph widths can all tbe changed." 16 | f.info.unitsPerEm = upm 17 | f.info.ascender = .75*upm 18 | f.info.descender = -.25*upm 19 | glyphs = [ 20 | ('narrow-thin', narrow), 21 | ('wide-thin', wide), 22 | ('narrow-bold',narrow), 23 | ('wide-bold', wide), 24 | ] 25 | names = [a for a, b in glyphs] 26 | f.lib['public.glyphOrder'] = names 27 | # draw bounds layer 28 | asc = f.info.ascender 29 | dsc = f.info.descender 30 | for name, width in glyphs: 31 | f.newGlyph(name) 32 | g = f[name] 33 | g.width = width 34 | boundsGlyph = g.getLayer('bounds', clear=True) 35 | pen = boundsGlyph.getPen() 36 | pen.moveTo((0,dsc)) 37 | pen.lineTo((g.width,dsc)) 38 | pen.lineTo((g.width,asc)) 39 | pen.lineTo((0,asc)) 40 | pen.closePath() 41 | g.update() 42 | # draw some sort of intro / test shape? 43 | thin = 5 44 | thick = 100 45 | for g in f: 46 | w = g.width 47 | if g.name.find("thin")!=-1: 48 | thin = 5 49 | else: 50 | thin = 100 51 | pen = g.getPen() 52 | pen.moveTo((0,dsc)) 53 | pen.lineTo((thin, dsc)) 54 | pen.lineTo((w, asc-thin)) 55 | pen.lineTo((w, asc)) 56 | pen.lineTo((w-thin,asc)) 57 | pen.lineTo((0,dsc+thin)) 58 | pen.closePath() 59 | pen.moveTo((0,asc)) 60 | pen.lineTo((0,asc-thin)) 61 | pen.lineTo((w-thin,dsc)) 62 | pen.lineTo((w,dsc)) 63 | pen.lineTo((w,dsc+thin)) 64 | pen.lineTo((thin,asc)) 65 | pen.closePath() 66 | return f # handle the saving in the UI 67 | 68 | class NewMathShapePicker(BaseWindowController): 69 | windowWidth = 250 70 | windowHeight = 200 71 | def __init__(self): 72 | self.fontObject = None 73 | self.size = 50 74 | proposedStyleName = time.strftime("%Y%m%d", time.localtime()) 75 | 76 | upmFormatter = NSNumberFormatter.alloc().init() 77 | upmFormatter.setPositiveFormat_("#") 78 | upmFormatter.setAllowsFloats_(False) 79 | upmFormatter.setMinimum_(500) 80 | upmFormatter.setMaximum_(10000) 81 | 82 | narrowFormatter = NSNumberFormatter.alloc().init() 83 | narrowFormatter.setPositiveFormat_("#") 84 | narrowFormatter.setAllowsFloats_(False) 85 | narrowFormatter.setMinimum_(10) 86 | narrowFormatter.setMaximum_(10000) 87 | 88 | wideFormatter = NSNumberFormatter.alloc().init() 89 | wideFormatter.setPositiveFormat_("#") 90 | wideFormatter.setAllowsFloats_(False) 91 | wideFormatter.setMinimum_(10) 92 | wideFormatter.setMaximum_(10000) 93 | 94 | self.w = vanilla.Window((self.windowWidth, self.windowHeight), "New MathShape UFO", textured=False) 95 | self.w.cancel = vanilla.Button((10, -30, 100, 20), "Cancel", callback=self.cancelCallback) 96 | self.w.ok = vanilla.Button((-110, -30, 101, 20), "Make", callback=self.makeCallback) 97 | self.w.setDefaultButton(self.w.ok) 98 | valueColumn = 140 99 | cpOffset = 4 100 | self.w.narrowValue = vanilla.EditText((valueColumn, 10, -10, 20), 500, formatter=narrowFormatter, sizeStyle="small") 101 | self.w.narrowValueCp = vanilla.TextBox((10, 10+cpOffset, 100, 20), "Narrowest width", sizeStyle="small") 102 | self.w.wideValue = vanilla.EditText((valueColumn, 40, -10, 20), 2500, formatter=wideFormatter, sizeStyle="small") 103 | self.w.wideValueCp = vanilla.TextBox((10, 40+cpOffset, 100, 20), "Widest width", sizeStyle="small") 104 | self.w.upmValue = vanilla.EditText((valueColumn, 70, -10, 20), 1000, formatter=upmFormatter, sizeStyle="small") 105 | self.w.upmValueCp = vanilla.TextBox((10, 70+cpOffset, 100, 20), "Units per Em", sizeStyle="small") 106 | 107 | self.w.familyNameString = vanilla.EditText((valueColumn, 100, -10, 20), "MathShape", sizeStyle="small") 108 | self.w.familyNameStringCp = vanilla.TextBox((10, 100+cpOffset, 100, 20), "Familyname", sizeStyle="small") 109 | self.w.styleNameString = vanilla.EditText((valueColumn, 130, -10, 20), proposedStyleName, sizeStyle="small") 110 | self.w.styleNameStringCp = vanilla.TextBox((10, 130+cpOffset, 100, 20), "Stylename", sizeStyle="small") 111 | 112 | self.setUpBaseWindowBehavior() 113 | self.w.open() 114 | 115 | def makeCallback(self, sender): 116 | upm = int(self.w.upmValue.get()) 117 | narrowWidth = int(self.w.narrowValue.get()) 118 | wideWidth = int(self.w.wideValue.get()) 119 | fName = self.w.familyNameString.get() 120 | sName = self.w.styleNameString.get() 121 | self.fontObject = prepareMathShapeUFO(narrow=narrowWidth, 122 | wide=wideWidth, 123 | upm=upm, 124 | familyName=fName, 125 | styleName=sName) 126 | preferredName = "%s-%s.ufo"%(self.fontObject.info.familyName, self.fontObject.info.styleName) 127 | self.showPutFile(["ufo"], fileName=preferredName, callback=self._saveFile) 128 | 129 | def _saveFile(self, path): 130 | if self.fontObject is not None: 131 | for g in self.fontObject: 132 | g.update() 133 | self.fontObject.save(path) 134 | self.w.close() 135 | 136 | def cancelCallback(self, sender): 137 | print 'cancelling' 138 | self.w.close() 139 | 140 | 141 | nmsp = NewMathShapePicker() 142 | 143 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/cmd_scalePastedSVGs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | paste a bunch of SVGs into a UFO and then scale and position the outlines. 5 | 6 | """ 7 | f = CurrentFont() 8 | 9 | 10 | def scaleSVGToBounds(g): 11 | wantHeight = 750 12 | box = g.box 13 | if box is None: 14 | return 15 | xMin, yMin, xMax, yMax = g.box 16 | ratio = (xMax-xMin)/(yMax-yMin) 17 | g.move((-xMin, -yMin)) 18 | s = wantHeight/(yMax-yMin) 19 | g.scale((s,s)) 20 | g.round() 21 | g.rightMargin = 0 22 | 23 | for g in f: 24 | scaleSVGToBounds(g) -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/cmd_validateMathShape.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def validate(f): 4 | names = ['narrow-bold', 'wide-bold', 'narrow-thin', 'wide-thin'] 5 | 6 | for n in names: 7 | g = f[n] 8 | print 9 | print g.name, len(g.contours) 10 | for c in g.contours: 11 | print g.name, len(c) 12 | 13 | 14 | if __name__ == "__main__": 15 | f = CurrentFont() 16 | validate(f) -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/exportTools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from fontTools.pens.basePen import BasePen 5 | from robofab.pens.boundsPen import BoundsPen 6 | from fontTools.pens.transformPen import TransformPen 7 | from fontTools.misc.transform import Transform 8 | 9 | import json 10 | from mathImageSVGPathPen import MathImageSVGPathPen 11 | 12 | def makeMaster(filePath, svg): 13 | docType = """""" 14 | f = open(filePath, 'w') 15 | # print filePath 16 | f.write(docType+svg) 17 | f.close() 18 | 19 | def makeSVGShape(glyph, name=None, width=None, opacity=None): 20 | attrs = { 21 | 'id': 'mathShape', 22 | 'title': "None", 23 | 'xmlns': "http://www.w3.org/2000/svg", 24 | 'xmlns:xlink' : "http://www.w3.org/1999/xlink", 25 | 'xml:space':'preserve', 26 | 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", 27 | } 28 | # try to get the bounds from the bounds layer. 29 | # if that does not work, get it from the glyph itself. 30 | bounds = None 31 | try: 32 | boundsGlyph = glyph.getLayer('bounds') 33 | if boundsGlyph is not None: 34 | bounds = boundsGlyph.box 35 | # print 'using bounds from bounds layer' 36 | except: 37 | pass 38 | # print 'using bounds from glyph' 39 | if bounds is None: 40 | boundsPen = BoundsPen({}) 41 | glyph.draw(boundsPen) 42 | bounds = boundsPen.bounds 43 | 44 | xOffset = 0 45 | yOffset = 0 46 | attrs['id']= name; 47 | if width is None: 48 | attrs['width'] = "100%" 49 | else: 50 | attrs['width'] = width 51 | if name is not None: 52 | attrs['name'] = name 53 | else: 54 | attrs['name'] = glyph.name 55 | if opacity is not None: 56 | attrs['fill-opacity'] = "%3.3f"%opacity 57 | 58 | 59 | t = Transform() 60 | # print bounds, -(bounds[3]-bounds[1]) 61 | t = t.scale(1,-1) 62 | t = t.translate(0, -bounds[3]) 63 | vb = (0, 0, glyph.width, bounds[3]-bounds[1]) 64 | attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f"%(vb[0],vb[1],vb[2],vb[3]) 65 | attrs['enable-background'] = attrs['viewBox'] 66 | sPen = MathImageSVGPathPen({}, optimise=False, lineAsCurve=True) 67 | tPen = TransformPen(sPen, t) 68 | glyph.draw(tPen) 69 | path = ""%(sPen.getCommands()) 70 | tag = "%s"%(" ".join(["%s=\"%s\""%(k,v) for k, v in attrs.items()]), path) 71 | return vb, tag 72 | 73 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/makePage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | 6 | import codecs 7 | 8 | """ 9 | 10 | Because we need a way to preview a mathshape in a single page without having to load a json file locally. 11 | """ 12 | 13 | 14 | class PageMaker(object): 15 | # XXXX does not need to be a class. 16 | 17 | htmlPath = "template.html" 18 | cssPath = "styles.css" 19 | mathShapePath = "mathShape.js" 20 | outputPath = "test.html" 21 | 22 | def hexColor(self, color): 23 | return u"#%02x%02x%02x"%(int(round(color[0]*0xff)), int(round(color[1]*0xff)), int(round(color[2]*0xff))) 24 | 25 | def rgbaColor(self, color): 26 | return u"rgba(%d, %d, %d, %2.2f)"%(int(round(color[0]*255)), int(round(color[1]*255)), int(round(color[2]*255)), color[3]) 27 | 28 | def __init__(self, resourcesRoot, shapePath, outputPath, shapeColor=None, bgColor=None, saveFile=True): 29 | self.shapePath = shapePath 30 | self.root = resourcesRoot 31 | # acquire the code 32 | f = codecs.open(os.path.join(self.root, self.htmlPath), 'r', 'utf-8') 33 | self.html = f.read() 34 | f.close() 35 | f = codecs.open(os.path.join(self.root, self.cssPath), 'r', 'utf-8') 36 | css = f.read() 37 | f.close() 38 | f = codecs.open(os.path.join(self.root, self.mathShapePath), 'r', 'utf-8') 39 | js = f.read() 40 | f.close() 41 | # mathshape data 42 | jsonPath = os.path.join(shapePath, 'files.json'); 43 | # print "jsonPath", jsonPath 44 | f = codecs.open(jsonPath, 'r', 'utf-8') 45 | data = f.read() 46 | f.close() 47 | jsonData = json.loads(data) 48 | svgLoaderContainer = [] 49 | for name in jsonData['files']: 50 | svgPath = os.path.join(shapePath, os.path.basename(name)) 51 | # print svgPath, os.path.exists(svgPath) 52 | f = codecs.open(svgPath, 'r', 'utf-8') 53 | svgData = f.read() 54 | svgLoaderContainer.append(svgData) 55 | 56 | # paste 57 | self.html = self.html.replace("//mathshape", js) 58 | self.html = self.html.replace("/*styles*/", css) 59 | self.html = self.html.replace("//__jsondata", data) 60 | self.html = self.html.replace("", "\n".join(svgLoaderContainer)) 61 | 62 | self.html = self.html.replace(u'/*shapeFillColor*/', u"\""+self.rgbaColor(shapeColor)+u"\"") 63 | self.html = self.html.replace(u'/*bgColor*/', self.rgbaColor(bgColor)) 64 | 65 | 66 | 67 | # release 68 | # print self.html] 69 | if saveFile: 70 | f = codecs.open(os.path.join(self.root, outputPath), 'w', 'utf-8') 71 | f.write(self.html) 72 | f.close() 73 | 74 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/mathShape/mathImageSVGPathPen.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from fontTools.pens.basePen import BasePen 4 | from ufo2svg.tools import pointToString, valueToString 5 | 6 | 7 | """ 8 | Almost indentical to the pen in ufo2svg, except 9 | that this one does not optimise horizontal or 10 | vertical line segments. All lines are written with 11 | x and y components. Otherwise differences in shape 12 | between the masters could cause incompatibilities. 13 | """ 14 | 15 | class MathImageSVGPathPen(BasePen): 16 | 17 | def __init__(self, glyphSet, optimise=False, lineAsCurve=False): 18 | BasePen.__init__(self, glyphSet) 19 | self._commands = [] 20 | self._lastCommand = None 21 | self._lastX = None 22 | self._lastY = None 23 | self.optimise = optimise 24 | self.lineAsCurve = lineAsCurve 25 | 26 | def _handleAnchor(self): 27 | """ 28 | >>> pen = MathImageSVGPathPen(None) 29 | >>> pen.moveTo((0, 0)) 30 | >>> pen.moveTo((10, 10)) 31 | >>> pen._commands 32 | ['M10 10'] 33 | """ 34 | if self._lastCommand == "M": 35 | self._commands.pop(-1) 36 | 37 | def _moveTo(self, pt): 38 | """ 39 | >>> pen = MathImageSVGPathPen(None) 40 | >>> pen.moveTo((0, 0)) 41 | >>> pen._commands 42 | ['M0 0'] 43 | 44 | >>> pen = MathImageSVGPathPen(None) 45 | >>> pen.moveTo((10, 0)) 46 | >>> pen._commands 47 | ['M10 0'] 48 | 49 | >>> pen = MathImageSVGPathPen(None) 50 | >>> pen.moveTo((0, 10)) 51 | >>> pen._commands 52 | ['M0 10'] 53 | """ 54 | self._handleAnchor() 55 | t = "M%s" % (pointToString(pt)) 56 | self._commands.append(t) 57 | self._lastCommand = "M" 58 | self._lastX, self._lastY = pt 59 | 60 | def _lineTo(self, pt): 61 | """ 62 | # duplicate point 63 | >>> pen = MathImageSVGPathPen(None, optimise=True) 64 | >>> pen.moveTo((10, 10)) 65 | >>> pen.lineTo((10, 10)) 66 | >>> pen._commands 67 | ['M10 10'] 68 | 69 | >>> pen = MathImageSVGPathPen(None, optimise=False) 70 | >>> pen.moveTo((10, 10)) 71 | >>> pen.lineTo((10, 10)) 72 | >>> pen._commands 73 | ['M10 10', 'L10 10'] 74 | 75 | # vertical line 76 | >>> pen = MathImageSVGPathPen(None, optimise=True) 77 | >>> pen.moveTo((10, 10)) 78 | >>> pen.lineTo((10, 0)) 79 | >>> pen._commands 80 | ['M10 10', 'V0'] 81 | 82 | >>> pen = MathImageSVGPathPen(None, optimise=False) 83 | >>> pen.moveTo((10, 10)) 84 | >>> pen.lineTo((10, 0)) 85 | >>> pen._commands 86 | ['M10 10', 'L10 0'] 87 | 88 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 89 | >>> pen.moveTo((10, 10)) 90 | >>> pen.lineTo((10, 0)) 91 | >>> pen._commands 92 | ['M10 10', 'C10 10 10 0 10 0'] 93 | 94 | # horizontal line 95 | >>> pen = MathImageSVGPathPen(None, optimise=True) 96 | >>> pen.moveTo((10, 10)) 97 | >>> pen.lineTo((0, 10)) 98 | >>> pen._commands 99 | ['M10 10', 'H0'] 100 | 101 | >>> pen = MathImageSVGPathPen(None, optimise=False) 102 | >>> pen.moveTo((10, 10)) 103 | >>> pen.lineTo((0, 10)) 104 | >>> pen._commands 105 | ['M10 10', 'L0 10'] 106 | 107 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 108 | >>> pen.moveTo((10, 10)) 109 | >>> pen.lineTo((0, 10)) 110 | >>> pen._commands 111 | ['M10 10', 'C10 10 0 10 0 10'] 112 | 113 | # basic 114 | >>> pen = MathImageSVGPathPen(None, optimise=True) 115 | >>> pen.lineTo((70, 80)) 116 | >>> pen._commands 117 | ['L70 80'] 118 | 119 | >>> pen = MathImageSVGPathPen(None, optimise=False) 120 | >>> pen.lineTo((70, 80)) 121 | >>> pen._commands 122 | ['L70 80'] 123 | 124 | # basic following a moveto 125 | >>> pen = MathImageSVGPathPen(None, optimise=True) 126 | >>> pen.moveTo((0, 0)) 127 | >>> pen.lineTo((10, 10)) 128 | >>> pen._commands 129 | ['M0 0', ' 10 10'] 130 | 131 | >>> pen = MathImageSVGPathPen(None, optimise=False) 132 | >>> pen.moveTo((0, 0)) 133 | >>> pen.lineTo((10, 10)) 134 | >>> pen._commands 135 | ['M0 0', 'L10 10'] 136 | 137 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 138 | >>> pen.moveTo((0, 0)) 139 | >>> pen.lineTo((10, 10)) 140 | >>> pen._commands 141 | ['M0 0', 'C0 0 10 10 10 10'] 142 | 143 | """ 144 | x, y = pt 145 | if self.lineAsCurve: 146 | # draw straight lines as curves with on-point controls 147 | self._curveToOne((self._lastX, self._lastY), (x,y), (x,y)) 148 | return 149 | if not self.optimise: 150 | cmd = "L" 151 | pts = pointToString(pt) 152 | else: 153 | # duplicate point 154 | if x == self._lastX and y == self._lastY: 155 | return 156 | # vertical line 157 | elif x == self._lastX: 158 | cmd = "V" 159 | pts = valueToString(y) 160 | # horizontal line 161 | elif y == self._lastY: 162 | cmd = "H" 163 | pts = valueToString(x) 164 | # previous was a moveto 165 | elif self._lastCommand == "M": 166 | cmd = None 167 | pts = " " + pointToString(pt) 168 | # basic 169 | else: 170 | cmd = "L" 171 | pts = pointToString(pt) 172 | # write the string 173 | t = "" 174 | if cmd: 175 | t += cmd 176 | self._lastCommand = cmd 177 | t += pts 178 | self._commands.append(t) 179 | # store for future reference 180 | self._lastX, self._lastY = pt 181 | 182 | def _curveToOne(self, pt1, pt2, pt3): 183 | """ 184 | >>> pen = MathImageSVGPathPen(None) 185 | >>> pen.curveTo((10, 20), (30, 40), (50, 60)) 186 | >>> pen._commands 187 | ['C10 20 30 40 50 60'] 188 | """ 189 | t = "C" 190 | t += pointToString(pt1) + " " 191 | t += pointToString(pt2) + " " 192 | t += pointToString(pt3) 193 | self._commands.append(t) 194 | self._lastCommand = "C" 195 | self._lastX, self._lastY = pt3 196 | 197 | def _qCurveToOne(self, pt1, pt2): 198 | """ 199 | >>> pen = MathImageSVGPathPen(None) 200 | >>> pen.qCurveTo((10, 20), (30, 40)) 201 | >>> pen._commands 202 | ['Q10 20 30 40'] 203 | """ 204 | assert pt2 is not None 205 | t = "Q" 206 | t += pointToString(pt1) + " " 207 | t += pointToString(pt2) 208 | self._commands.append(t) 209 | self._lastCommand = "Q" 210 | self._lastX, self._lastY = pt2 211 | 212 | def _closePath(self): 213 | """ 214 | >>> pen = MathImageSVGPathPen(None) 215 | >>> pen.closePath() 216 | >>> pen._commands 217 | ['Z'] 218 | """ 219 | self._commands.append("Z") 220 | self._lastCommand = "Z" 221 | self._lastX = self._lastY = None 222 | 223 | def _endPath(self): 224 | """ 225 | >>> pen = MathImageSVGPathPen(None) 226 | >>> pen.endPath() 227 | >>> pen._commands 228 | ['Z'] 229 | """ 230 | self._closePath() 231 | self._lastCommand = None 232 | self._lastX = self._lastY = None 233 | 234 | def getCommands(self): 235 | return "".join(self._commands) 236 | 237 | 238 | if __name__ == "__main__": 239 | import doctest 240 | doctest.testmod() -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/plugin.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ########################################################################################################### 4 | # 5 | # 6 | # General Plugin 7 | # 8 | # Read the docs: 9 | # https://github.com/schriftgestalt/GlyphsSDK/tree/master/Python%20Templates/General%20Plugin 10 | # 11 | # 12 | ########################################################################################################### 13 | 14 | 15 | from GlyphsApp.plugins import * 16 | 17 | from mathShape.cmd_exportCurrentFont import ExportUI 18 | 19 | class ResponsiveLettering(GeneralPlugin): 20 | def settings(self): 21 | self.name = "Responsive Lettering" 22 | 23 | def start(self): 24 | print 'GeneralPlugin loaded' 25 | mainMenu = NSApplication.sharedApplication().mainMenu() 26 | s = objc.selector(self.exportUI, signature='v@:') 27 | newMenuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(self.name, s, "") 28 | newMenuItem.setTarget_(self) 29 | mainMenu.itemWithTag_(5).submenu().addItem_(newMenuItem) 30 | 31 | def exportUI(self): 32 | print "ExportUI()" 33 | ExportUI() 34 | 35 | def __file__(self): 36 | """Please leave this method unchanged""" 37 | return __file__ -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/placeholder_ms/files.json: -------------------------------------------------------------------------------- 1 | {"files": ["placeholder_ms/narrow-thin.svg", "placeholder_ms/wide-thin.svg", "placeholder_ms/narrow-bold.svg", "placeholder_ms/wide-bold.svg"], "extrapolatemin": 0, "designspace": "twobytwo", "sizebounds": [[500, 1000], [2500, 1000]], "extrapolatemax": 1.25} -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/placeholder_ms/narrow-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/placeholder_ms/narrow-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/placeholder_ms/wide-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/placeholder_ms/wide-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/styles.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:0; 3 | padding:0; 4 | } 5 | .column p{ 6 | margin-top: 2em; 7 | font-family: "Georgia"; 8 | font-size: 10pt; 9 | line-height: 16pt; 10 | color: white; 11 | } 12 | div.svgloader{ 13 | display: none; 14 | } 15 | @media screen { 16 | /*two boxes next to each other*/ 17 | #svgcontainer{ 18 | margin-top:0vh; 19 | margin-left:0vw; 20 | height: 100vh; 21 | width: 100vw; 22 | } 23 | .column{ 24 | position: absolute; 25 | margin-top:65vh; 26 | margin-left:50vw; 27 | height: 45vh; 28 | width: 20%; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LettError MathShape Preview 5 | 6 | 7 | 8 | 10 | 12 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 32 | 33 |
34 | 35 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ResponsiveLettering.glyphsPlugin/Contents/Resources/site.py: -------------------------------------------------------------------------------- 1 | """ 2 | Append module search paths for third-party packages to sys.path. 3 | 4 | This is stripped down and customized for use in py2app applications 5 | """ 6 | 7 | import sys 8 | # os is actually in the zip, so we need to do this here. 9 | # we can't call it python24.zip because zlib is not a built-in module (!) 10 | _libdir = '/lib/python' + sys.version[:3] 11 | _parent = '/'.join(__file__.split('/')[:-1]) 12 | if not _parent.endswith(_libdir): 13 | _parent += _libdir 14 | sys.path.append(_parent + '/site-packages.zip') 15 | 16 | # Stuffit decompresses recursively by default, that can mess up py2app bundles, 17 | # add the uncompressed site-packages to the path to compensate for that. 18 | sys.path.append(_parent + '/site-packages') 19 | 20 | import os 21 | try: 22 | basestring 23 | except NameError: 24 | basestring = str 25 | 26 | def makepath(*paths): 27 | dir = os.path.abspath(os.path.join(*paths)) 28 | return dir, os.path.normcase(dir) 29 | 30 | for m in sys.modules.values(): 31 | f = getattr(m, '__file__', None) 32 | if isinstance(f, basestring) and os.path.exists(f): 33 | m.__file__ = os.path.abspath(m.__file__) 34 | del m 35 | 36 | # This ensures that the initial path provided by the interpreter contains 37 | # only absolute pathnames, even if we're running from the build directory. 38 | L = [] 39 | _dirs_in_sys_path = {} 40 | dir = dircase = None # sys.path may be empty at this point 41 | for dir in sys.path: 42 | # Filter out duplicate paths (on case-insensitive file systems also 43 | # if they only differ in case); turn relative paths into absolute 44 | # paths. 45 | dir, dircase = makepath(dir) 46 | if not dircase in _dirs_in_sys_path: 47 | L.append(dir) 48 | _dirs_in_sys_path[dircase] = 1 49 | sys.path[:] = L 50 | del dir, dircase, L 51 | _dirs_in_sys_path = None 52 | 53 | def _init_pathinfo(): 54 | global _dirs_in_sys_path 55 | _dirs_in_sys_path = d = {} 56 | for dir in sys.path: 57 | if dir and not os.path.isdir(dir): 58 | continue 59 | dir, dircase = makepath(dir) 60 | d[dircase] = 1 61 | 62 | def addsitedir(sitedir): 63 | global _dirs_in_sys_path 64 | if _dirs_in_sys_path is None: 65 | _init_pathinfo() 66 | reset = 1 67 | else: 68 | reset = 0 69 | sitedir, sitedircase = makepath(sitedir) 70 | if not sitedircase in _dirs_in_sys_path: 71 | sys.path.append(sitedir) # Add path component 72 | try: 73 | names = os.listdir(sitedir) 74 | except os.error: 75 | return 76 | names.sort() 77 | for name in names: 78 | if name[-4:] == os.extsep + "pth": 79 | addpackage(sitedir, name) 80 | if reset: 81 | _dirs_in_sys_path = None 82 | 83 | def addpackage(sitedir, name): 84 | global _dirs_in_sys_path 85 | if _dirs_in_sys_path is None: 86 | _init_pathinfo() 87 | reset = 1 88 | else: 89 | reset = 0 90 | fullname = os.path.join(sitedir, name) 91 | try: 92 | f = open(fullname) 93 | except IOError: 94 | return 95 | while 1: 96 | dir = f.readline() 97 | if not dir: 98 | break 99 | if dir[0] == '#': 100 | continue 101 | if dir.startswith("import"): 102 | exec(dir) 103 | continue 104 | if dir[-1] == '\n': 105 | dir = dir[:-1] 106 | dir, dircase = makepath(sitedir, dir) 107 | if not dircase in _dirs_in_sys_path and os.path.exists(dir): 108 | sys.path.append(dir) 109 | _dirs_in_sys_path[dircase] = 1 110 | if reset: 111 | _dirs_in_sys_path = None 112 | 113 | 114 | #sys.setdefaultencoding('utf-8') 115 | 116 | # 117 | # Run custom site specific code, if available. 118 | # 119 | try: 120 | import sitecustomize 121 | except ImportError: 122 | pass 123 | 124 | # 125 | # Remove sys.setdefaultencoding() so that users cannot change the 126 | # encoding after initialization. The test for presence is needed when 127 | # this module is run as a script, because this code is executed twice. 128 | # 129 | if hasattr(sys, "setdefaultencoding"): 130 | del sys.setdefaultencoding 131 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/Icon : -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/ResponsiveLettering.roboFontExt/Icon -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | addToMenu 6 | 7 | 8 | path 9 | mathShape/cmd_exportCurrentFont.py 10 | preferredName 11 | Preview and Export 12 | shortKey 13 | 14 | 15 | 16 | path 17 | mathShape/cmd_prepareNewShape.py 18 | preferredName 19 | New Template 20 | shortKey 21 | 22 | 23 | 24 | path 25 | mathShape/cmd_exportVariableFont.py 26 | preferredName 27 | Export to Designspace 28 | shortKey 29 | 30 | 31 | 32 | developer 33 | LettError 34 | developerURL 35 | http://letterror.com 36 | html 37 | 38 | launchAtStartUp 39 | 0 40 | mainScript 41 | 42 | name 43 | Responsive Lettering 44 | timeStamp 45 | 1578561930.56 46 | version 47 | 2.0 48 | com.robofontmechanic.Mechanic 49 | 50 | repository 51 | LettError/responsiveLettering 52 | summary 53 | Tools for previewing, editing, generating responsive lettering with svg/html/css/javascript. 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/__init__.py: -------------------------------------------------------------------------------- 1 | # package for exporting mathshapes from robofont. -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/cmd_exportVariableFont.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ufoProcessor import DesignSpaceDocument, AxisDescriptor, SourceDescriptor, InstanceDescriptor 3 | 4 | class VariableFontExporter(object): 5 | designSpaceModelLibKey = "com.letterror.mathshape.designspace" 6 | shapeColorLibKey = "com.letterror.mathshape.preview.shapecolor" 7 | backgroundColorLibKey = "com.letterror.mathshape.preview.bgcolor" 8 | preferredFilenameLibKey = "com.letterror.mathshape.filename" 9 | animatingModels = ["twobytwo"] 10 | 11 | def __init__(self, font): 12 | self.font = font 13 | self.designSpaceModel = self.font.lib.get(self.designSpaceModelLibKey, "twobytwo") 14 | if self.designSpaceModel == "twobytwo": 15 | self.masterNames = ['narrow-thin', 'wide-thin', 'narrow-bold', 'wide-bold'] 16 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin', 'narrow-bold':'NarrowBold', 'wide-bold':'WideBold'} 17 | self.axes = ['width', 'weight'] 18 | self.locations = {'narrow-thin': dict(weight=0, width=0), 'wide-thin': dict(width=1000, weight=0), 'narrow-bold': dict(width=0, weight=1000), 'wide-bold': dict(width=1000, weight=1000)} 19 | elif self.designSpaceModel == "twobyone": 20 | self.masterNames = ['narrow-thin', 'wide-thin'] 21 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin'} 22 | self.axes = ['width'] 23 | self.locations = {'narrow-thin': dict(weight=0), 'wide-thin': dict(weight=1000)} 24 | elif self.designSpaceModel == "threebyone": 25 | self.masterNames = ['narrow-thin', 'wide-thin', 'medium-thin'] 26 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin', 'medium-thin':'MediumThin'} 27 | self.axes = ['width'] 28 | self.locations = {'narrow-thin': dict(weight=0), 'medium-thin': dict(weight=500), 'wide-thin': dict(weight=1000)} 29 | 30 | projectRoot = os.path.join(os.path.dirname(self.font.path), "%s_variableFont"%self.font.info.styleName) 31 | docPath = os.path.join(projectRoot, "%s_variableFont.designspace"%self.font.info.styleName) 32 | if not os.path.exists(projectRoot): 33 | os.makedirs(projectRoot) 34 | 35 | widthMinimum = self.font['narrow-thin'].width 36 | widthMaximum = self.font['wide-thin'].width 37 | for k, v in self.locations.items(): 38 | new = {} 39 | for axisName, axisValue in v.items(): 40 | if axisName == "width": 41 | if axisValue == 1000: 42 | new[axisName] = widthMaximum 43 | elif axisValue == 0: 44 | new[axisName] = widthMinimum 45 | else: 46 | new[axisName] = axisValue 47 | self.locations[k] = new 48 | 49 | doc = DesignSpaceDocument() 50 | for name in self.axes: 51 | if name == "width": 52 | a = AxisDescriptor() 53 | a.minimum = widthMinimum 54 | a.maximum = widthMaximum 55 | a.default = widthMinimum 56 | a.name = "width" 57 | a.tag = "wdth" 58 | #a.labelNames['en'] = "Width" 59 | doc.addAxis(a) 60 | elif name == "weight": 61 | a = AxisDescriptor() 62 | a.minimum = 0 63 | a.maximum = 1000 64 | a.default = 0 65 | a.name = "weight" 66 | a.tag = "wght" 67 | #a.labelNames['en'] = "Weight" 68 | doc.addAxis(a) 69 | 70 | masterCount = 0 71 | for name in self.masterNames: 72 | if name in self.font: 73 | masterCount += 1 74 | if masterCount != len(self.masterNames): 75 | print("missing glyphs, can't generate") 76 | return 77 | closers = [] 78 | for name in self.masterNames: 79 | print("processing", name, self.locations[name]) 80 | m = RFont() 81 | m.info.unitsPerEm = self.font.info.unitsPerEm 82 | m.info.ascender = self.font.info.ascender 83 | m.info.descender = self.font.info.descender 84 | if self.font.info.familyName in ["MathShape", "Responsive"]: 85 | m.info.familyName = self.font.info.styleName 86 | else: 87 | m.info.familyName = "ResponsiveLettering" 88 | m.info.styleName = self.fontNames[name] 89 | m.info.copyright = "Generated from Responsive Lettering project %s"%(os.path.basename(self.font.path)) 90 | m.info.openTypeNameSampleText = "A" 91 | fontPath = os.path.join(projectRoot, "master_%s.ufo"%(m.info.styleName)) 92 | instancePath = os.path.join(projectRoot, "instance_%s%s.ufo"%(m.info.familyName, m.info.styleName)) 93 | m.save(fontPath) 94 | 95 | m.newGlyph("A") 96 | g = m['A'] 97 | g.unicode = 0x0041 98 | pen = g.getPointPen() 99 | self.font[name].drawPoints(pen) 100 | #g.appendGlyph(self.font[name]) 101 | g.width = self.font[name].width 102 | g.clearImage() 103 | g.update() 104 | m.save() 105 | 106 | m.newGlyph("space") 107 | g = m['space'] 108 | g.unicode = 0x0020 109 | g.clearImage() 110 | g.width = self.font[name].width 111 | #g.note = "test" 112 | #g.appendGlyph(self.font[name]) 113 | m.newGlyph(".notdef") 114 | g = m['.notdef'] 115 | g.clearImage() 116 | g.width = self.font[name].width 117 | #g.note = "test" 118 | g.update() 119 | m.save() 120 | 121 | s = SourceDescriptor() 122 | s.path = fontPath 123 | s.location = self.locations[name] 124 | if name == 'narrow-thin': 125 | s.copyLib = True 126 | s.copyInfo = True 127 | 128 | doc.addSource(s) 129 | # add some instances 130 | i = InstanceDescriptor() 131 | i.path = instancePath 132 | i.location = self.locations[name] 133 | i.copyLib = True 134 | i.copyInfo = True 135 | i.copyFeatures = True 136 | i.familyName = "ResponsiveLetteringVariable" 137 | i.styleName = m.info.styleName 138 | #i.kerning = True 139 | doc.addInstance(i) 140 | closers.append(m) 141 | doc.write(docPath) 142 | for m in closers: 143 | m.save() 144 | m.close() 145 | 146 | f = CurrentFont() 147 | d = VariableFontExporter(f) 148 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/cmd_prepareNewShape.py: -------------------------------------------------------------------------------- 1 | import mojo 2 | import time 3 | import vanilla 4 | from AppKit import NSNumberFormatter 5 | from defconAppKit.windows.baseWindow import BaseWindowController 6 | from mojo.UI import CurrentFontWindow 7 | 8 | # the location of smart sets has moved in RF3: 9 | rf_version = mojo.roboFont.version 10 | if int(rf_version[0]) >= 3: 11 | from mojo.smartSet import SmartSet 12 | else: 13 | from mojo.UI import SmartSet 14 | 15 | """ 16 | 17 | Make a new UFO and give it the appropriate glyphs and layers. 18 | 19 | """ 20 | 21 | designSpaceModelLibKey = "com.letterror.mathshape.designspace" 22 | 23 | def prepareMathShapeUFO(narrow=500, wide=2500, upm=1000, familyName="MathShape", styleName="New", model="twobytwo"): 24 | f = NewFont(familyName=familyName, styleName=styleName) 25 | f.info.note = "This is a template font for a Responsive Lettering project, using a %s designspace. The font names and glyph widths can all tbe changed."%model 26 | f.info.unitsPerEm = upm 27 | f.info.ascender = .75*upm 28 | f.info.descender = -.25*upm 29 | f.lib[designSpaceModelLibKey] = model 30 | if model == "twobytwo": 31 | glyphs = [ 32 | ('narrow-thin', narrow), 33 | ('wide-thin', wide), 34 | ('narrow-bold',narrow), 35 | ('wide-bold', wide), 36 | ] 37 | elif model == "twobyone": 38 | glyphs = [ 39 | ('narrow-thin', narrow), 40 | ('wide-thin', wide), 41 | ] 42 | names = [a for a, b in glyphs] 43 | f.lib['public.glyphOrder'] = names 44 | # draw bounds layer 45 | asc = f.info.ascender 46 | dsc = f.info.descender 47 | for name, width in glyphs: 48 | f.newGlyph(name) 49 | g = f[name] 50 | g.width = width 51 | boundsGlyph = g.getLayer('bounds') 52 | boundsGlyph.clear() 53 | pen = boundsGlyph.getPen() 54 | pen.moveTo((0,dsc)) 55 | pen.lineTo((g.width,dsc)) 56 | pen.lineTo((g.width,asc)) 57 | pen.lineTo((0,asc)) 58 | pen.closePath() 59 | g.update() 60 | # draw some sort of intro / test shape? 61 | thin = 5 62 | thick = 100 63 | for g in f: 64 | w = g.width 65 | if g.name.find("thin")!=-1: 66 | thin = 5 67 | else: 68 | thin = 100 69 | pen = g.getPen() 70 | pen.moveTo((0,dsc)) 71 | pen.lineTo((thin, dsc)) 72 | pen.lineTo((w, asc-thin)) 73 | pen.lineTo((w, asc)) 74 | pen.lineTo((w-thin,asc)) 75 | pen.lineTo((0,dsc+thin)) 76 | pen.closePath() 77 | pen.moveTo((0,asc)) 78 | pen.lineTo((0,asc-thin)) 79 | pen.lineTo((w-thin,dsc)) 80 | pen.lineTo((w,dsc)) 81 | pen.lineTo((w,dsc+thin)) 82 | pen.lineTo((thin,asc)) 83 | pen.closePath() 84 | return f # handle the saving in the UI 85 | 86 | class NewMathShapePicker(BaseWindowController): 87 | windowWidth = 250 88 | windowHeight = 230 89 | def __init__(self): 90 | self.fontObject = None 91 | self.size = 50 92 | proposedStyleName = time.strftime("%Y%m%d", time.localtime()) 93 | 94 | upmFormatter = NSNumberFormatter.alloc().init() 95 | upmFormatter.setPositiveFormat_("#") 96 | upmFormatter.setAllowsFloats_(False) 97 | upmFormatter.setMinimum_(500) 98 | upmFormatter.setMaximum_(10000) 99 | 100 | narrowFormatter = NSNumberFormatter.alloc().init() 101 | narrowFormatter.setPositiveFormat_("#") 102 | narrowFormatter.setAllowsFloats_(False) 103 | narrowFormatter.setMinimum_(10) 104 | narrowFormatter.setMaximum_(10000) 105 | 106 | wideFormatter = NSNumberFormatter.alloc().init() 107 | wideFormatter.setPositiveFormat_("#") 108 | wideFormatter.setAllowsFloats_(False) 109 | wideFormatter.setMinimum_(10) 110 | wideFormatter.setMaximum_(10000) 111 | 112 | self.designSpaceOptions = [("Responsive + animation (4 masters)", "twobytwo"), ("Only responsive (2 masters)", "twobyone")] 113 | 114 | self.w = vanilla.Window((self.windowWidth, self.windowHeight), "New MathShape UFO", textured=False) 115 | self.w.cancel = vanilla.Button((10, -30, 100, 20), "Cancel", callback=self.cancelCallback) 116 | self.w.ok = vanilla.Button((-110, -30, 101, 20), "Make", callback=self.makeCallback) 117 | self.w.setDefaultButton(self.w.ok) 118 | valueColumn = 140 119 | cpOffset = 4 120 | self.w.narrowValue = vanilla.EditText((valueColumn, 10, -10, 20), 500, formatter=narrowFormatter, sizeStyle="small") 121 | self.w.narrowValueCp = vanilla.TextBox((10, 10+cpOffset, 100, 20), "Narrowest width", sizeStyle="small") 122 | self.w.wideValue = vanilla.EditText((valueColumn, 40, -10, 20), 2500, formatter=wideFormatter, sizeStyle="small") 123 | self.w.wideValueCp = vanilla.TextBox((10, 40+cpOffset, 100, 20), "Widest width", sizeStyle="small") 124 | self.w.upmValue = vanilla.EditText((valueColumn, 70, -10, 20), 1000, formatter=upmFormatter, sizeStyle="small") 125 | self.w.upmValueCp = vanilla.TextBox((10, 70+cpOffset, 100, 20), "Units per Em", sizeStyle="small") 126 | 127 | self.w.familyNameString = vanilla.EditText((valueColumn, 100, -10, 20), "Responsive", sizeStyle="small") 128 | self.w.familyNameStringCp = vanilla.TextBox((10, 100+cpOffset, 100, 20), "Familyname", sizeStyle="small") 129 | self.w.styleNameString = vanilla.EditText((valueColumn, 130, -10, 20), proposedStyleName, sizeStyle="small") 130 | self.w.styleNameStringCp = vanilla.TextBox((10, 130+cpOffset, 100, 20), "Stylename", sizeStyle="small") 131 | self.w.modelPopup = vanilla.PopUpButton((10, 160, -10, 20), [a for a,b in self.designSpaceOptions], sizeStyle="small") 132 | self.setUpBaseWindowBehavior() 133 | self.w.open() 134 | 135 | def makeCallback(self, sender): 136 | upm = int(self.w.upmValue.get()) 137 | narrowWidth = int(self.w.narrowValue.get()) 138 | wideWidth = int(self.w.wideValue.get()) 139 | fName = self.w.familyNameString.get() 140 | sName = self.w.styleNameString.get() 141 | designSpaceModel = self.designSpaceOptions[self.w.modelPopup.get()][1] 142 | self.fontObject = prepareMathShapeUFO(narrow=narrowWidth, 143 | wide=wideWidth, 144 | upm=upm, 145 | familyName=fName, 146 | styleName=sName, 147 | model=designSpaceModel) 148 | self.displayGlyphSet(self.fontObject.keys()) 149 | preferredName = "%s-%s.ufo"%(self.fontObject.info.familyName, self.fontObject.info.styleName) 150 | self.showPutFile(["ufo"], fileName=preferredName, callback=self._saveFile) 151 | 152 | def displayGlyphSet(self, glyphNames): 153 | # find the new glyphs in the font window. 154 | search = SmartSet() 155 | search.glyphNames = glyphNames 156 | window = CurrentFontWindow() 157 | window.getGlyphCollection().setQuery(search.getQueryObject()) 158 | 159 | def _saveFile(self, path): 160 | if self.fontObject is not None: 161 | for g in self.fontObject: 162 | g.update() 163 | self.fontObject.save(path) 164 | self.w.close() 165 | 166 | def cancelCallback(self, sender): 167 | print('cancelling') 168 | self.w.close() 169 | 170 | 171 | nmsp = NewMathShapePicker() 172 | 173 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/cmd_scalePastedSVGs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | paste a bunch of SVGs into a UFO and then scale and position the outlines. 5 | 6 | """ 7 | f = CurrentFont() 8 | 9 | 10 | def scaleSVGToBounds(g): 11 | wantHeight = 750 12 | box = g.box 13 | if box is None: 14 | return 15 | xMin, yMin, xMax, yMax = g.box 16 | ratio = (xMax-xMin)/(yMax-yMin) 17 | g.move((-xMin, -yMin)) 18 | s = wantHeight/(yMax-yMin) 19 | g.scale((s,s)) 20 | g.round() 21 | g.rightMargin = 0 22 | 23 | for g in f: 24 | scaleSVGToBounds(g) -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/cmd_validateMathShape.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def validate(f): 4 | names = ['narrow-bold', 'wide-bold', 'narrow-thin', 'wide-thin'] 5 | if f is None: 6 | return 7 | for n in names: 8 | g = f[n] 9 | print(g.name, len(g.contours)) 10 | for c in g.contours: 11 | print(g.name, len(c)) 12 | 13 | 14 | if __name__ == "__main__": 15 | f = CurrentFont() 16 | validate(f) -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/exportTools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from fontTools.pens.basePen import BasePen 5 | from fontTools.pens.boundsPen import BoundsPen 6 | from fontTools.pens.transformPen import TransformPen 7 | from fontTools.misc.transform import Transform 8 | 9 | import json 10 | from mathImageSVGPathPen import MathImageSVGPathPen 11 | 12 | def makeMaster(filePath, svg): 13 | docType = """""" 14 | f = open(filePath, 'w') 15 | f.write(docType+svg) 16 | f.close() 17 | 18 | def makeSVGShape(glyph, name=None, width=None, opacity=None): 19 | attrs = { 20 | 'id': 'mathShape', 21 | 'title': "None", 22 | 'xmlns': "http://www.w3.org/2000/svg", 23 | 'xmlns:xlink' : "http://www.w3.org/1999/xlink", 24 | 'xml:space':'preserve', 25 | 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", 26 | } 27 | # try to get the bounds from the bounds layer. 28 | # if that does not work, get it from the glyph itself. 29 | bounds = None 30 | try: 31 | boundsGlyph = glyph.getLayer('bounds') 32 | if boundsGlyph is not None: 33 | bounds = boundsGlyph.box 34 | except: 35 | pass 36 | if bounds is None: 37 | boundsPen = BoundsPen({}) 38 | glyph.draw(boundsPen) 39 | bounds = boundsPen.bounds 40 | 41 | xOffset = 0 42 | yOffset = 0 43 | attrs['id']= name; 44 | if width is None: 45 | attrs['width'] = "100%" 46 | else: 47 | attrs['width'] = width 48 | if name is not None: 49 | attrs['name'] = name 50 | else: 51 | attrs['name'] = glyph.name 52 | if opacity is not None: 53 | attrs['fill-opacity'] = "%3.3f"%opacity 54 | 55 | t = Transform() 56 | t = t.scale(1,-1) 57 | t = t.translate(0, -bounds[3]) 58 | vb = (0, 0, glyph.width, bounds[3]-bounds[1]) 59 | attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f"%(vb[0],vb[1],vb[2],vb[3]) 60 | attrs['enable-background'] = attrs['viewBox'] 61 | sPen = MathImageSVGPathPen({}, optimise=False, lineAsCurve=True) 62 | tPen = TransformPen(sPen, t) 63 | glyph.draw(tPen) 64 | path = ""%(sPen.getCommands()) 65 | tag = "%s"%(" ".join(["%s=\"%s\""%(k,v) for k, v in attrs.items()]), path) 66 | return vb, tag 67 | 68 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/makePage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | 6 | import codecs 7 | 8 | """ 9 | 10 | Because we need a way to preview a mathshape in a single page without having to load a json file locally. 11 | """ 12 | 13 | 14 | class PageMaker(object): 15 | # XXXX does not need to be a class. 16 | 17 | htmlPath = "template.html" 18 | cssPath = "styles.css" 19 | mathShapePath = "mathShape.js" 20 | outputPath = "test.html" 21 | 22 | animateBreatheCode = """// make the images breathe. 23 | // entirely optional but definitely entertaining 24 | setInterval(function () { 25 | breathShape += 0.04; 26 | myMathShape.breathe(0.5*Math.sin(breathShape*Math.PI)+0.5) 27 | }, 50); 28 | """ 29 | 30 | def hexColor(self, color): 31 | return u"#%02x%02x%02x"%(int(round(color[0]*0xff)), int(round(color[1]*0xff)), int(round(color[2]*0xff))) 32 | 33 | def rgbaColor(self, color): 34 | return u"rgba(%d, %d, %d, %2.2f)"%(int(round(color[0]*255)), int(round(color[1]*255)), int(round(color[2]*255)), color[3]) 35 | 36 | def __init__(self, resourcesRoot, shapePath, outputPath, shapeColor=None, bgColor=None, saveFile=True, animate=True): 37 | self.shapePath = shapePath 38 | self.root = resourcesRoot 39 | # acquire the code 40 | f = codecs.open(os.path.join(self.root, self.htmlPath), 'r', 'utf-8') 41 | self.html = f.read() 42 | f.close() 43 | f = codecs.open(os.path.join(self.root, self.cssPath), 'r', 'utf-8') 44 | css = f.read() 45 | f.close() 46 | f = codecs.open(os.path.join(self.root, self.mathShapePath), 'r', 'utf-8') 47 | js = f.read() 48 | f.close() 49 | # mathshape data 50 | jsonPath = os.path.join(shapePath, 'files.json'); 51 | # print "jsonPath", jsonPath 52 | f = codecs.open(jsonPath, 'r', 'utf-8') 53 | data = f.read() 54 | f.close() 55 | jsonData = json.loads(data) 56 | svgLoaderContainer = [] 57 | for name in jsonData['files']: 58 | svgPath = os.path.join(shapePath, os.path.basename(name)) 59 | # print svgPath, os.path.exists(svgPath) 60 | f = codecs.open(svgPath, 'r', 'utf-8') 61 | svgData = f.read() 62 | svgLoaderContainer.append(svgData) 63 | f.close() 64 | 65 | # paste 66 | self.html = self.html.replace("//mathshape", js) 67 | self.html = self.html.replace("/*styles*/", css) 68 | self.html = self.html.replace("//__jsondata", data) 69 | self.html = self.html.replace("", "\n".join(svgLoaderContainer)) 70 | 71 | self.html = self.html.replace(u'/*shapeFillColor*/', u"\""+self.rgbaColor(shapeColor)+u"\"") 72 | self.html = self.html.replace(u'/*bgColor*/', self.rgbaColor(bgColor)) 73 | if animate: 74 | # single dimension designspaces do not animate. So there is no need to include the code. 75 | self.html = self.html.replace(u'// animateBreatheCode', self.animateBreatheCode) 76 | 77 | 78 | 79 | 80 | # release 81 | # print self.html] 82 | if saveFile: 83 | f = codecs.open(os.path.join(self.root, outputPath), 'w', 'utf-8') 84 | f.write(self.html) 85 | f.close() 86 | 87 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/lib/mathShape/mathImageSVGPathPen.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from fontTools.pens.basePen import BasePen 4 | from ufo2svg.tools import pointToString, valueToString 5 | 6 | """ 7 | Almost indentical to the pen in ufo2svg, except 8 | that this one does not optimise horizontal or 9 | vertical line segments. All lines are written with 10 | x and y components. Otherwise differences in shape 11 | between the masters could cause incompatibilities. 12 | """ 13 | 14 | class MathImageSVGPathPen(BasePen): 15 | 16 | def __init__(self, glyphSet, optimise=False, lineAsCurve=False): 17 | BasePen.__init__(self, glyphSet) 18 | self._commands = [] 19 | self._lastCommand = None 20 | self._lastX = None 21 | self._lastY = None 22 | self.optimise = optimise 23 | self.lineAsCurve = lineAsCurve 24 | 25 | def _handleAnchor(self): 26 | """ 27 | >>> pen = MathImageSVGPathPen(None) 28 | >>> pen.moveTo((0, 0)) 29 | >>> pen.moveTo((10, 10)) 30 | >>> pen._commands 31 | ['M10 10'] 32 | """ 33 | if self._lastCommand == "M": 34 | self._commands.pop(-1) 35 | 36 | def _moveTo(self, pt): 37 | """ 38 | >>> pen = MathImageSVGPathPen(None) 39 | >>> pen.moveTo((0, 0)) 40 | >>> pen._commands 41 | ['M0 0'] 42 | 43 | >>> pen = MathImageSVGPathPen(None) 44 | >>> pen.moveTo((10, 0)) 45 | >>> pen._commands 46 | ['M10 0'] 47 | 48 | >>> pen = MathImageSVGPathPen(None) 49 | >>> pen.moveTo((0, 10)) 50 | >>> pen._commands 51 | ['M0 10'] 52 | """ 53 | self._handleAnchor() 54 | t = "M%s" % (pointToString(pt)) 55 | self._commands.append(t) 56 | self._lastCommand = "M" 57 | self._lastX, self._lastY = pt 58 | 59 | def _lineTo(self, pt): 60 | """ 61 | # duplicate point 62 | >>> pen = MathImageSVGPathPen(None, optimise=True) 63 | >>> pen.moveTo((10, 10)) 64 | >>> pen.lineTo((10, 10)) 65 | >>> pen._commands 66 | ['M10 10'] 67 | 68 | >>> pen = MathImageSVGPathPen(None, optimise=False) 69 | >>> pen.moveTo((10, 10)) 70 | >>> pen.lineTo((10, 10)) 71 | >>> pen._commands 72 | ['M10 10', 'L10 10'] 73 | 74 | # vertical line 75 | >>> pen = MathImageSVGPathPen(None, optimise=True) 76 | >>> pen.moveTo((10, 10)) 77 | >>> pen.lineTo((10, 0)) 78 | >>> pen._commands 79 | ['M10 10', 'V0'] 80 | 81 | >>> pen = MathImageSVGPathPen(None, optimise=False) 82 | >>> pen.moveTo((10, 10)) 83 | >>> pen.lineTo((10, 0)) 84 | >>> pen._commands 85 | ['M10 10', 'L10 0'] 86 | 87 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 88 | >>> pen.moveTo((10, 10)) 89 | >>> pen.lineTo((10, 0)) 90 | >>> pen._commands 91 | ['M10 10', 'C10 10 10 0 10 0'] 92 | 93 | # horizontal line 94 | >>> pen = MathImageSVGPathPen(None, optimise=True) 95 | >>> pen.moveTo((10, 10)) 96 | >>> pen.lineTo((0, 10)) 97 | >>> pen._commands 98 | ['M10 10', 'H0'] 99 | 100 | >>> pen = MathImageSVGPathPen(None, optimise=False) 101 | >>> pen.moveTo((10, 10)) 102 | >>> pen.lineTo((0, 10)) 103 | >>> pen._commands 104 | ['M10 10', 'L0 10'] 105 | 106 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 107 | >>> pen.moveTo((10, 10)) 108 | >>> pen.lineTo((0, 10)) 109 | >>> pen._commands 110 | ['M10 10', 'C10 10 0 10 0 10'] 111 | 112 | # basic 113 | >>> pen = MathImageSVGPathPen(None, optimise=True) 114 | >>> pen.lineTo((70, 80)) 115 | >>> pen._commands 116 | ['L70 80'] 117 | 118 | >>> pen = MathImageSVGPathPen(None, optimise=False) 119 | >>> pen.lineTo((70, 80)) 120 | >>> pen._commands 121 | ['L70 80'] 122 | 123 | # basic following a moveto 124 | >>> pen = MathImageSVGPathPen(None, optimise=True) 125 | >>> pen.moveTo((0, 0)) 126 | >>> pen.lineTo((10, 10)) 127 | >>> pen._commands 128 | ['M0 0', ' 10 10'] 129 | 130 | >>> pen = MathImageSVGPathPen(None, optimise=False) 131 | >>> pen.moveTo((0, 0)) 132 | >>> pen.lineTo((10, 10)) 133 | >>> pen._commands 134 | ['M0 0', 'L10 10'] 135 | 136 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 137 | >>> pen.moveTo((0, 0)) 138 | >>> pen.lineTo((10, 10)) 139 | >>> pen._commands 140 | ['M0 0', 'C0 0 10 10 10 10'] 141 | 142 | """ 143 | x, y = pt 144 | if self.lineAsCurve: 145 | # draw straight lines as curves with on-point controls 146 | self._curveToOne((self._lastX, self._lastY), (x,y), (x,y)) 147 | return 148 | if not self.optimise: 149 | cmd = "L" 150 | pts = pointToString(pt) 151 | else: 152 | # duplicate point 153 | if x == self._lastX and y == self._lastY: 154 | return 155 | # vertical line 156 | elif x == self._lastX: 157 | cmd = "V" 158 | pts = valueToString(y) 159 | # horizontal line 160 | elif y == self._lastY: 161 | cmd = "H" 162 | pts = valueToString(x) 163 | # previous was a moveto 164 | elif self._lastCommand == "M": 165 | cmd = None 166 | pts = " " + pointToString(pt) 167 | # basic 168 | else: 169 | cmd = "L" 170 | pts = pointToString(pt) 171 | # write the string 172 | t = "" 173 | if cmd: 174 | t += cmd 175 | self._lastCommand = cmd 176 | t += pts 177 | self._commands.append(t) 178 | # store for future reference 179 | self._lastX, self._lastY = pt 180 | 181 | def _curveToOne(self, pt1, pt2, pt3): 182 | """ 183 | >>> pen = MathImageSVGPathPen(None) 184 | >>> pen.curveTo((10, 20), (30, 40), (50, 60)) 185 | >>> pen._commands 186 | ['C10 20 30 40 50 60'] 187 | """ 188 | t = "C" 189 | t += pointToString(pt1) + " " 190 | t += pointToString(pt2) + " " 191 | t += pointToString(pt3) 192 | self._commands.append(t) 193 | self._lastCommand = "C" 194 | self._lastX, self._lastY = pt3 195 | 196 | def _qCurveToOne(self, pt1, pt2): 197 | """ 198 | >>> pen = MathImageSVGPathPen(None) 199 | >>> pen.qCurveTo((10, 20), (30, 40)) 200 | >>> pen._commands 201 | ['Q10 20 30 40'] 202 | """ 203 | assert pt2 is not None 204 | t = "Q" 205 | t += pointToString(pt1) + " " 206 | t += pointToString(pt2) 207 | self._commands.append(t) 208 | self._lastCommand = "Q" 209 | self._lastX, self._lastY = pt2 210 | 211 | def _closePath(self): 212 | """ 213 | >>> pen = MathImageSVGPathPen(None) 214 | >>> pen.closePath() 215 | >>> pen._commands 216 | ['Z'] 217 | """ 218 | self._commands.append("Z") 219 | self._lastCommand = "Z" 220 | self._lastX = self._lastY = None 221 | 222 | def _endPath(self): 223 | """ 224 | >>> pen = MathImageSVGPathPen(None) 225 | >>> pen.endPath() 226 | >>> pen._commands 227 | ['Z'] 228 | """ 229 | self._closePath() 230 | self._lastCommand = None 231 | self._lastX = self._lastY = None 232 | 233 | def getCommands(self): 234 | return "".join(self._commands) 235 | 236 | 237 | if __name__ == "__main__": 238 | import doctest 239 | doctest.testmod() 240 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/placeholder_ms/files.json: -------------------------------------------------------------------------------- 1 | {"files": ["placeholder_ms/narrow-thin.svg", "placeholder_ms/wide-thin.svg", "placeholder_ms/narrow-bold.svg", "placeholder_ms/wide-bold.svg"], "extrapolatemin": 0, "designspace": "twobytwo", "sizebounds": [[500, 1000], [2500, 1000]], "extrapolatemax": 1.25} -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/placeholder_ms/narrow-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/placeholder_ms/narrow-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/placeholder_ms/wide-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/placeholder_ms/wide-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/styles.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:0; 3 | padding:0; 4 | } 5 | .column p{ 6 | margin-top: 2em; 7 | font-family: "Georgia"; 8 | font-size: 10pt; 9 | line-height: 16pt; 10 | color: white; 11 | } 12 | div.svgloader{ 13 | display: none; 14 | } 15 | @media screen { 16 | /*two boxes next to each other*/ 17 | #svgcontainer{ 18 | margin-top:0vh; 19 | margin-left:0vw; 20 | height: 100vh; 21 | width: 100vw; 22 | } 23 | .column{ 24 | position: absolute; 25 | margin-top:65vh; 26 | margin-left:50vw; 27 | height: 45vh; 28 | width: 20%; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ResponsiveLettering.roboFontExt/resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LettError MathShape Preview 5 | 6 | 7 | 8 | 10 | 12 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 32 | 33 |
34 | 35 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /RoboFontMathShapeExporter_screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/RoboFontMathShapeExporter_screen.gif -------------------------------------------------------------------------------- /buildExtension.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | 4 | This script intends to copy a couple of things to the roboFontExt 5 | so that all the python code can live in an accessible place. 6 | 7 | """ 8 | 9 | import os, shutil 10 | 11 | root = os.getcwd() 12 | print(root) 13 | extensionPath = os.path.join(root, u"ResponsiveLettering.roboFontExt") 14 | srcLibPath = os.path.join(root, u'lib', u'mathShape') 15 | dstLibPath = os.path.join(root, u"ResponsiveLettering.roboFontExt", u'lib', u'mathShape') 16 | 17 | srcMathShapePath = os.path.join(root, u'www', u'mathShape.js') 18 | dstMathShapePath = os.path.join(extensionPath, u'resources') 19 | 20 | print("extensionPath", extensionPath) 21 | print("srcLibPath", srcLibPath) 22 | print('dstLibPath', dstLibPath) 23 | 24 | # print os.listdir(srcLibPath) 25 | # print os.listdir(dstLibPath) 26 | # print os.listdir(extensionPath) 27 | 28 | # copy mathShape.js from www to extension 29 | print(os.path.exists(srcMathShapePath), srcMathShapePath) 30 | print(os.path.exists(dstMathShapePath), dstMathShapePath) 31 | print(os.listdir(dstMathShapePath)) 32 | print(shutil.copy(srcMathShapePath, dstMathShapePath)) 33 | 34 | # copy the lib to the extension 35 | if os.path.exists(dstLibPath): 36 | shutil.rmtree(dstLibPath) 37 | shutil.copytree(srcLibPath, dstLibPath, ) 38 | 39 | -------------------------------------------------------------------------------- /lib/mathShape/__init__.py: -------------------------------------------------------------------------------- 1 | # package for exporting mathshapes from robofont. -------------------------------------------------------------------------------- /lib/mathShape/cmd_exportVariableFont.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ufoProcessor import DesignSpaceDocument, AxisDescriptor, SourceDescriptor, InstanceDescriptor 3 | 4 | class VariableFontExporter(object): 5 | designSpaceModelLibKey = "com.letterror.mathshape.designspace" 6 | shapeColorLibKey = "com.letterror.mathshape.preview.shapecolor" 7 | backgroundColorLibKey = "com.letterror.mathshape.preview.bgcolor" 8 | preferredFilenameLibKey = "com.letterror.mathshape.filename" 9 | animatingModels = ["twobytwo"] 10 | 11 | def __init__(self, font): 12 | self.font = font 13 | self.designSpaceModel = self.font.lib.get(self.designSpaceModelLibKey, "twobytwo") 14 | if self.designSpaceModel == "twobytwo": 15 | self.masterNames = ['narrow-thin', 'wide-thin', 'narrow-bold', 'wide-bold'] 16 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin', 'narrow-bold':'NarrowBold', 'wide-bold':'WideBold'} 17 | self.axes = ['width', 'weight'] 18 | self.locations = {'narrow-thin': dict(weight=0, width=0), 'wide-thin': dict(width=1000, weight=0), 'narrow-bold': dict(width=0, weight=1000), 'wide-bold': dict(width=1000, weight=1000)} 19 | elif self.designSpaceModel == "twobyone": 20 | self.masterNames = ['narrow-thin', 'wide-thin'] 21 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin'} 22 | self.axes = ['width'] 23 | self.locations = {'narrow-thin': dict(weight=0), 'wide-thin': dict(weight=1000)} 24 | elif self.designSpaceModel == "threebyone": 25 | self.masterNames = ['narrow-thin', 'wide-thin', 'medium-thin'] 26 | self.fontNames = {'narrow-thin':'NarrowThin', 'wide-thin':'WideThin', 'medium-thin':'MediumThin'} 27 | self.axes = ['width'] 28 | self.locations = {'narrow-thin': dict(weight=0), 'medium-thin': dict(weight=500), 'wide-thin': dict(weight=1000)} 29 | 30 | projectRoot = os.path.join(os.path.dirname(self.font.path), "%s_variableFont"%self.font.info.styleName) 31 | docPath = os.path.join(projectRoot, "%s_variableFont.designspace"%self.font.info.styleName) 32 | if not os.path.exists(projectRoot): 33 | os.makedirs(projectRoot) 34 | 35 | widthMinimum = self.font['narrow-thin'].width 36 | widthMaximum = self.font['wide-thin'].width 37 | for k, v in self.locations.items(): 38 | new = {} 39 | for axisName, axisValue in v.items(): 40 | if axisName == "width": 41 | if axisValue == 1000: 42 | new[axisName] = widthMaximum 43 | elif axisValue == 0: 44 | new[axisName] = widthMinimum 45 | else: 46 | new[axisName] = axisValue 47 | self.locations[k] = new 48 | 49 | doc = DesignSpaceDocument() 50 | for name in self.axes: 51 | if name == "width": 52 | a = AxisDescriptor() 53 | a.minimum = widthMinimum 54 | a.maximum = widthMaximum 55 | a.default = widthMinimum 56 | a.name = "width" 57 | a.tag = "wdth" 58 | #a.labelNames['en'] = "Width" 59 | doc.addAxis(a) 60 | elif name == "weight": 61 | a = AxisDescriptor() 62 | a.minimum = 0 63 | a.maximum = 1000 64 | a.default = 0 65 | a.name = "weight" 66 | a.tag = "wght" 67 | #a.labelNames['en'] = "Weight" 68 | doc.addAxis(a) 69 | 70 | masterCount = 0 71 | for name in self.masterNames: 72 | if name in self.font: 73 | masterCount += 1 74 | if masterCount != len(self.masterNames): 75 | print("missing glyphs, can't generate") 76 | return 77 | closers = [] 78 | for name in self.masterNames: 79 | print("processing", name, self.locations[name]) 80 | m = RFont() 81 | m.info.unitsPerEm = self.font.info.unitsPerEm 82 | m.info.ascender = self.font.info.ascender 83 | m.info.descender = self.font.info.descender 84 | if self.font.info.familyName in ["MathShape", "Responsive"]: 85 | m.info.familyName = self.font.info.styleName 86 | else: 87 | m.info.familyName = "ResponsiveLettering" 88 | m.info.styleName = self.fontNames[name] 89 | m.info.copyright = "Generated from Responsive Lettering project %s"%(os.path.basename(self.font.path)) 90 | m.info.openTypeNameSampleText = "A" 91 | fontPath = os.path.join(projectRoot, "master_%s.ufo"%(m.info.styleName)) 92 | instancePath = os.path.join(projectRoot, "instance_%s%s.ufo"%(m.info.familyName, m.info.styleName)) 93 | m.save(fontPath) 94 | 95 | m.newGlyph("A") 96 | g = m['A'] 97 | g.unicode = 0x0041 98 | pen = g.getPointPen() 99 | self.font[name].drawPoints(pen) 100 | #g.appendGlyph(self.font[name]) 101 | g.width = self.font[name].width 102 | g.clearImage() 103 | g.update() 104 | m.save() 105 | 106 | m.newGlyph("space") 107 | g = m['space'] 108 | g.unicode = 0x0020 109 | g.clearImage() 110 | g.width = self.font[name].width 111 | #g.note = "test" 112 | #g.appendGlyph(self.font[name]) 113 | m.newGlyph(".notdef") 114 | g = m['.notdef'] 115 | g.clearImage() 116 | g.width = self.font[name].width 117 | #g.note = "test" 118 | g.update() 119 | m.save() 120 | 121 | s = SourceDescriptor() 122 | s.path = fontPath 123 | s.location = self.locations[name] 124 | if name == 'narrow-thin': 125 | s.copyLib = True 126 | s.copyInfo = True 127 | 128 | doc.addSource(s) 129 | # add some instances 130 | i = InstanceDescriptor() 131 | i.path = instancePath 132 | i.location = self.locations[name] 133 | i.copyLib = True 134 | i.copyInfo = True 135 | i.copyFeatures = True 136 | i.familyName = "ResponsiveLetteringVariable" 137 | i.styleName = m.info.styleName 138 | #i.kerning = True 139 | doc.addInstance(i) 140 | closers.append(m) 141 | doc.write(docPath) 142 | for m in closers: 143 | m.save() 144 | m.close() 145 | 146 | f = CurrentFont() 147 | d = VariableFontExporter(f) 148 | -------------------------------------------------------------------------------- /lib/mathShape/cmd_prepareNewShape.py: -------------------------------------------------------------------------------- 1 | import mojo 2 | import time 3 | import vanilla 4 | from AppKit import NSNumberFormatter 5 | from defconAppKit.windows.baseWindow import BaseWindowController 6 | from mojo.UI import CurrentFontWindow 7 | 8 | # the location of smart sets has moved in RF3: 9 | rf_version = mojo.roboFont.version 10 | if int(rf_version[0]) >= 3: 11 | from mojo.smartSet import SmartSet 12 | else: 13 | from mojo.UI import SmartSet 14 | 15 | """ 16 | 17 | Make a new UFO and give it the appropriate glyphs and layers. 18 | 19 | """ 20 | 21 | designSpaceModelLibKey = "com.letterror.mathshape.designspace" 22 | 23 | def prepareMathShapeUFO(narrow=500, wide=2500, upm=1000, familyName="MathShape", styleName="New", model="twobytwo"): 24 | f = NewFont(familyName=familyName, styleName=styleName) 25 | f.info.note = "This is a template font for a Responsive Lettering project, using a %s designspace. The font names and glyph widths can all tbe changed."%model 26 | f.info.unitsPerEm = upm 27 | f.info.ascender = .75*upm 28 | f.info.descender = -.25*upm 29 | f.lib[designSpaceModelLibKey] = model 30 | if model == "twobytwo": 31 | glyphs = [ 32 | ('narrow-thin', narrow), 33 | ('wide-thin', wide), 34 | ('narrow-bold',narrow), 35 | ('wide-bold', wide), 36 | ] 37 | elif model == "twobyone": 38 | glyphs = [ 39 | ('narrow-thin', narrow), 40 | ('wide-thin', wide), 41 | ] 42 | names = [a for a, b in glyphs] 43 | f.lib['public.glyphOrder'] = names 44 | # draw bounds layer 45 | asc = f.info.ascender 46 | dsc = f.info.descender 47 | for name, width in glyphs: 48 | f.newGlyph(name) 49 | g = f[name] 50 | g.width = width 51 | boundsGlyph = g.getLayer('bounds') 52 | boundsGlyph.clear() 53 | pen = boundsGlyph.getPen() 54 | pen.moveTo((0,dsc)) 55 | pen.lineTo((g.width,dsc)) 56 | pen.lineTo((g.width,asc)) 57 | pen.lineTo((0,asc)) 58 | pen.closePath() 59 | g.update() 60 | # draw some sort of intro / test shape? 61 | thin = 5 62 | thick = 100 63 | for g in f: 64 | w = g.width 65 | if g.name.find("thin")!=-1: 66 | thin = 5 67 | else: 68 | thin = 100 69 | pen = g.getPen() 70 | pen.moveTo((0,dsc)) 71 | pen.lineTo((thin, dsc)) 72 | pen.lineTo((w, asc-thin)) 73 | pen.lineTo((w, asc)) 74 | pen.lineTo((w-thin,asc)) 75 | pen.lineTo((0,dsc+thin)) 76 | pen.closePath() 77 | pen.moveTo((0,asc)) 78 | pen.lineTo((0,asc-thin)) 79 | pen.lineTo((w-thin,dsc)) 80 | pen.lineTo((w,dsc)) 81 | pen.lineTo((w,dsc+thin)) 82 | pen.lineTo((thin,asc)) 83 | pen.closePath() 84 | return f # handle the saving in the UI 85 | 86 | class NewMathShapePicker(BaseWindowController): 87 | windowWidth = 250 88 | windowHeight = 230 89 | def __init__(self): 90 | self.fontObject = None 91 | self.size = 50 92 | proposedStyleName = time.strftime("%Y%m%d", time.localtime()) 93 | 94 | upmFormatter = NSNumberFormatter.alloc().init() 95 | upmFormatter.setPositiveFormat_("#") 96 | upmFormatter.setAllowsFloats_(False) 97 | upmFormatter.setMinimum_(500) 98 | upmFormatter.setMaximum_(10000) 99 | 100 | narrowFormatter = NSNumberFormatter.alloc().init() 101 | narrowFormatter.setPositiveFormat_("#") 102 | narrowFormatter.setAllowsFloats_(False) 103 | narrowFormatter.setMinimum_(10) 104 | narrowFormatter.setMaximum_(10000) 105 | 106 | wideFormatter = NSNumberFormatter.alloc().init() 107 | wideFormatter.setPositiveFormat_("#") 108 | wideFormatter.setAllowsFloats_(False) 109 | wideFormatter.setMinimum_(10) 110 | wideFormatter.setMaximum_(10000) 111 | 112 | self.designSpaceOptions = [("Responsive + animation (4 masters)", "twobytwo"), ("Only responsive (2 masters)", "twobyone")] 113 | 114 | self.w = vanilla.Window((self.windowWidth, self.windowHeight), "New MathShape UFO", textured=False) 115 | self.w.cancel = vanilla.Button((10, -30, 100, 20), "Cancel", callback=self.cancelCallback) 116 | self.w.ok = vanilla.Button((-110, -30, 101, 20), "Make", callback=self.makeCallback) 117 | self.w.setDefaultButton(self.w.ok) 118 | valueColumn = 140 119 | cpOffset = 4 120 | self.w.narrowValue = vanilla.EditText((valueColumn, 10, -10, 20), 500, formatter=narrowFormatter, sizeStyle="small") 121 | self.w.narrowValueCp = vanilla.TextBox((10, 10+cpOffset, 100, 20), "Narrowest width", sizeStyle="small") 122 | self.w.wideValue = vanilla.EditText((valueColumn, 40, -10, 20), 2500, formatter=wideFormatter, sizeStyle="small") 123 | self.w.wideValueCp = vanilla.TextBox((10, 40+cpOffset, 100, 20), "Widest width", sizeStyle="small") 124 | self.w.upmValue = vanilla.EditText((valueColumn, 70, -10, 20), 1000, formatter=upmFormatter, sizeStyle="small") 125 | self.w.upmValueCp = vanilla.TextBox((10, 70+cpOffset, 100, 20), "Units per Em", sizeStyle="small") 126 | 127 | self.w.familyNameString = vanilla.EditText((valueColumn, 100, -10, 20), "Responsive", sizeStyle="small") 128 | self.w.familyNameStringCp = vanilla.TextBox((10, 100+cpOffset, 100, 20), "Familyname", sizeStyle="small") 129 | self.w.styleNameString = vanilla.EditText((valueColumn, 130, -10, 20), proposedStyleName, sizeStyle="small") 130 | self.w.styleNameStringCp = vanilla.TextBox((10, 130+cpOffset, 100, 20), "Stylename", sizeStyle="small") 131 | self.w.modelPopup = vanilla.PopUpButton((10, 160, -10, 20), [a for a,b in self.designSpaceOptions], sizeStyle="small") 132 | self.setUpBaseWindowBehavior() 133 | self.w.open() 134 | 135 | def makeCallback(self, sender): 136 | upm = int(self.w.upmValue.get()) 137 | narrowWidth = int(self.w.narrowValue.get()) 138 | wideWidth = int(self.w.wideValue.get()) 139 | fName = self.w.familyNameString.get() 140 | sName = self.w.styleNameString.get() 141 | designSpaceModel = self.designSpaceOptions[self.w.modelPopup.get()][1] 142 | self.fontObject = prepareMathShapeUFO(narrow=narrowWidth, 143 | wide=wideWidth, 144 | upm=upm, 145 | familyName=fName, 146 | styleName=sName, 147 | model=designSpaceModel) 148 | self.displayGlyphSet(self.fontObject.keys()) 149 | preferredName = "%s-%s.ufo"%(self.fontObject.info.familyName, self.fontObject.info.styleName) 150 | self.showPutFile(["ufo"], fileName=preferredName, callback=self._saveFile) 151 | 152 | def displayGlyphSet(self, glyphNames): 153 | # find the new glyphs in the font window. 154 | search = SmartSet() 155 | search.glyphNames = glyphNames 156 | window = CurrentFontWindow() 157 | window.getGlyphCollection().setQuery(search.getQueryObject()) 158 | 159 | def _saveFile(self, path): 160 | if self.fontObject is not None: 161 | for g in self.fontObject: 162 | g.update() 163 | self.fontObject.save(path) 164 | self.w.close() 165 | 166 | def cancelCallback(self, sender): 167 | print('cancelling') 168 | self.w.close() 169 | 170 | 171 | nmsp = NewMathShapePicker() 172 | 173 | -------------------------------------------------------------------------------- /lib/mathShape/cmd_scalePastedSVGs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | paste a bunch of SVGs into a UFO and then scale and position the outlines. 5 | 6 | """ 7 | f = CurrentFont() 8 | 9 | 10 | def scaleSVGToBounds(g): 11 | wantHeight = 750 12 | box = g.box 13 | if box is None: 14 | return 15 | xMin, yMin, xMax, yMax = g.box 16 | ratio = (xMax-xMin)/(yMax-yMin) 17 | g.move((-xMin, -yMin)) 18 | s = wantHeight/(yMax-yMin) 19 | g.scale((s,s)) 20 | g.round() 21 | g.rightMargin = 0 22 | 23 | for g in f: 24 | scaleSVGToBounds(g) -------------------------------------------------------------------------------- /lib/mathShape/cmd_validateMathShape.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def validate(f): 4 | names = ['narrow-bold', 'wide-bold', 'narrow-thin', 'wide-thin'] 5 | if f is None: 6 | return 7 | for n in names: 8 | g = f[n] 9 | print(g.name, len(g.contours)) 10 | for c in g.contours: 11 | print(g.name, len(c)) 12 | 13 | 14 | if __name__ == "__main__": 15 | f = CurrentFont() 16 | validate(f) -------------------------------------------------------------------------------- /lib/mathShape/exportTools.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from fontTools.pens.basePen import BasePen 5 | from fontTools.pens.boundsPen import BoundsPen 6 | from fontTools.pens.transformPen import TransformPen 7 | from fontTools.misc.transform import Transform 8 | 9 | import json 10 | from mathImageSVGPathPen import MathImageSVGPathPen 11 | 12 | def makeMaster(filePath, svg): 13 | docType = """""" 14 | f = open(filePath, 'w') 15 | f.write(docType+svg) 16 | f.close() 17 | 18 | def makeSVGShape(glyph, name=None, width=None, opacity=None): 19 | attrs = { 20 | 'id': 'mathShape', 21 | 'title': "None", 22 | 'xmlns': "http://www.w3.org/2000/svg", 23 | 'xmlns:xlink' : "http://www.w3.org/1999/xlink", 24 | 'xml:space':'preserve', 25 | 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", 26 | } 27 | # try to get the bounds from the bounds layer. 28 | # if that does not work, get it from the glyph itself. 29 | bounds = None 30 | try: 31 | boundsGlyph = glyph.getLayer('bounds') 32 | if boundsGlyph is not None: 33 | bounds = boundsGlyph.box 34 | except: 35 | pass 36 | if bounds is None: 37 | boundsPen = BoundsPen({}) 38 | glyph.draw(boundsPen) 39 | bounds = boundsPen.bounds 40 | 41 | xOffset = 0 42 | yOffset = 0 43 | attrs['id']= name; 44 | if width is None: 45 | attrs['width'] = "100%" 46 | else: 47 | attrs['width'] = width 48 | if name is not None: 49 | attrs['name'] = name 50 | else: 51 | attrs['name'] = glyph.name 52 | if opacity is not None: 53 | attrs['fill-opacity'] = "%3.3f"%opacity 54 | 55 | t = Transform() 56 | t = t.scale(1,-1) 57 | t = t.translate(0, -bounds[3]) 58 | vb = (0, 0, glyph.width, bounds[3]-bounds[1]) 59 | attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f"%(vb[0],vb[1],vb[2],vb[3]) 60 | attrs['enable-background'] = attrs['viewBox'] 61 | sPen = MathImageSVGPathPen({}, optimise=False, lineAsCurve=True) 62 | tPen = TransformPen(sPen, t) 63 | glyph.draw(tPen) 64 | path = ""%(sPen.getCommands()) 65 | tag = "%s"%(" ".join(["%s=\"%s\""%(k,v) for k, v in attrs.items()]), path) 66 | return vb, tag 67 | 68 | -------------------------------------------------------------------------------- /lib/mathShape/makePage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import json 5 | 6 | import codecs 7 | 8 | """ 9 | 10 | Because we need a way to preview a mathshape in a single page without having to load a json file locally. 11 | """ 12 | 13 | 14 | class PageMaker(object): 15 | # XXXX does not need to be a class. 16 | 17 | htmlPath = "template.html" 18 | cssPath = "styles.css" 19 | mathShapePath = "mathShape.js" 20 | outputPath = "test.html" 21 | 22 | animateBreatheCode = """// make the images breathe. 23 | // entirely optional but definitely entertaining 24 | setInterval(function () { 25 | breathShape += 0.04; 26 | myMathShape.breathe(0.5*Math.sin(breathShape*Math.PI)+0.5) 27 | }, 50); 28 | """ 29 | 30 | def hexColor(self, color): 31 | return u"#%02x%02x%02x"%(int(round(color[0]*0xff)), int(round(color[1]*0xff)), int(round(color[2]*0xff))) 32 | 33 | def rgbaColor(self, color): 34 | return u"rgba(%d, %d, %d, %2.2f)"%(int(round(color[0]*255)), int(round(color[1]*255)), int(round(color[2]*255)), color[3]) 35 | 36 | def __init__(self, resourcesRoot, shapePath, outputPath, shapeColor=None, bgColor=None, saveFile=True, animate=True): 37 | self.shapePath = shapePath 38 | self.root = resourcesRoot 39 | # acquire the code 40 | f = codecs.open(os.path.join(self.root, self.htmlPath), 'r', 'utf-8') 41 | self.html = f.read() 42 | f.close() 43 | f = codecs.open(os.path.join(self.root, self.cssPath), 'r', 'utf-8') 44 | css = f.read() 45 | f.close() 46 | f = codecs.open(os.path.join(self.root, self.mathShapePath), 'r', 'utf-8') 47 | js = f.read() 48 | f.close() 49 | # mathshape data 50 | jsonPath = os.path.join(shapePath, 'files.json'); 51 | # print "jsonPath", jsonPath 52 | f = codecs.open(jsonPath, 'r', 'utf-8') 53 | data = f.read() 54 | f.close() 55 | jsonData = json.loads(data) 56 | svgLoaderContainer = [] 57 | for name in jsonData['files']: 58 | svgPath = os.path.join(shapePath, os.path.basename(name)) 59 | # print svgPath, os.path.exists(svgPath) 60 | f = codecs.open(svgPath, 'r', 'utf-8') 61 | svgData = f.read() 62 | svgLoaderContainer.append(svgData) 63 | f.close() 64 | 65 | # paste 66 | self.html = self.html.replace("//mathshape", js) 67 | self.html = self.html.replace("/*styles*/", css) 68 | self.html = self.html.replace("//__jsondata", data) 69 | self.html = self.html.replace("", "\n".join(svgLoaderContainer)) 70 | 71 | self.html = self.html.replace(u'/*shapeFillColor*/', u"\""+self.rgbaColor(shapeColor)+u"\"") 72 | self.html = self.html.replace(u'/*bgColor*/', self.rgbaColor(bgColor)) 73 | if animate: 74 | # single dimension designspaces do not animate. So there is no need to include the code. 75 | self.html = self.html.replace(u'// animateBreatheCode', self.animateBreatheCode) 76 | 77 | 78 | 79 | 80 | # release 81 | # print self.html] 82 | if saveFile: 83 | f = codecs.open(os.path.join(self.root, outputPath), 'w', 'utf-8') 84 | f.write(self.html) 85 | f.close() 86 | 87 | -------------------------------------------------------------------------------- /lib/mathShape/mathImageSVGPathPen.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from fontTools.pens.basePen import BasePen 4 | from ufo2svg.tools import pointToString, valueToString 5 | 6 | """ 7 | Almost indentical to the pen in ufo2svg, except 8 | that this one does not optimise horizontal or 9 | vertical line segments. All lines are written with 10 | x and y components. Otherwise differences in shape 11 | between the masters could cause incompatibilities. 12 | """ 13 | 14 | class MathImageSVGPathPen(BasePen): 15 | 16 | def __init__(self, glyphSet, optimise=False, lineAsCurve=False): 17 | BasePen.__init__(self, glyphSet) 18 | self._commands = [] 19 | self._lastCommand = None 20 | self._lastX = None 21 | self._lastY = None 22 | self.optimise = optimise 23 | self.lineAsCurve = lineAsCurve 24 | 25 | def _handleAnchor(self): 26 | """ 27 | >>> pen = MathImageSVGPathPen(None) 28 | >>> pen.moveTo((0, 0)) 29 | >>> pen.moveTo((10, 10)) 30 | >>> pen._commands 31 | ['M10 10'] 32 | """ 33 | if self._lastCommand == "M": 34 | self._commands.pop(-1) 35 | 36 | def _moveTo(self, pt): 37 | """ 38 | >>> pen = MathImageSVGPathPen(None) 39 | >>> pen.moveTo((0, 0)) 40 | >>> pen._commands 41 | ['M0 0'] 42 | 43 | >>> pen = MathImageSVGPathPen(None) 44 | >>> pen.moveTo((10, 0)) 45 | >>> pen._commands 46 | ['M10 0'] 47 | 48 | >>> pen = MathImageSVGPathPen(None) 49 | >>> pen.moveTo((0, 10)) 50 | >>> pen._commands 51 | ['M0 10'] 52 | """ 53 | self._handleAnchor() 54 | t = "M%s" % (pointToString(pt)) 55 | self._commands.append(t) 56 | self._lastCommand = "M" 57 | self._lastX, self._lastY = pt 58 | 59 | def _lineTo(self, pt): 60 | """ 61 | # duplicate point 62 | >>> pen = MathImageSVGPathPen(None, optimise=True) 63 | >>> pen.moveTo((10, 10)) 64 | >>> pen.lineTo((10, 10)) 65 | >>> pen._commands 66 | ['M10 10'] 67 | 68 | >>> pen = MathImageSVGPathPen(None, optimise=False) 69 | >>> pen.moveTo((10, 10)) 70 | >>> pen.lineTo((10, 10)) 71 | >>> pen._commands 72 | ['M10 10', 'L10 10'] 73 | 74 | # vertical line 75 | >>> pen = MathImageSVGPathPen(None, optimise=True) 76 | >>> pen.moveTo((10, 10)) 77 | >>> pen.lineTo((10, 0)) 78 | >>> pen._commands 79 | ['M10 10', 'V0'] 80 | 81 | >>> pen = MathImageSVGPathPen(None, optimise=False) 82 | >>> pen.moveTo((10, 10)) 83 | >>> pen.lineTo((10, 0)) 84 | >>> pen._commands 85 | ['M10 10', 'L10 0'] 86 | 87 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 88 | >>> pen.moveTo((10, 10)) 89 | >>> pen.lineTo((10, 0)) 90 | >>> pen._commands 91 | ['M10 10', 'C10 10 10 0 10 0'] 92 | 93 | # horizontal line 94 | >>> pen = MathImageSVGPathPen(None, optimise=True) 95 | >>> pen.moveTo((10, 10)) 96 | >>> pen.lineTo((0, 10)) 97 | >>> pen._commands 98 | ['M10 10', 'H0'] 99 | 100 | >>> pen = MathImageSVGPathPen(None, optimise=False) 101 | >>> pen.moveTo((10, 10)) 102 | >>> pen.lineTo((0, 10)) 103 | >>> pen._commands 104 | ['M10 10', 'L0 10'] 105 | 106 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 107 | >>> pen.moveTo((10, 10)) 108 | >>> pen.lineTo((0, 10)) 109 | >>> pen._commands 110 | ['M10 10', 'C10 10 0 10 0 10'] 111 | 112 | # basic 113 | >>> pen = MathImageSVGPathPen(None, optimise=True) 114 | >>> pen.lineTo((70, 80)) 115 | >>> pen._commands 116 | ['L70 80'] 117 | 118 | >>> pen = MathImageSVGPathPen(None, optimise=False) 119 | >>> pen.lineTo((70, 80)) 120 | >>> pen._commands 121 | ['L70 80'] 122 | 123 | # basic following a moveto 124 | >>> pen = MathImageSVGPathPen(None, optimise=True) 125 | >>> pen.moveTo((0, 0)) 126 | >>> pen.lineTo((10, 10)) 127 | >>> pen._commands 128 | ['M0 0', ' 10 10'] 129 | 130 | >>> pen = MathImageSVGPathPen(None, optimise=False) 131 | >>> pen.moveTo((0, 0)) 132 | >>> pen.lineTo((10, 10)) 133 | >>> pen._commands 134 | ['M0 0', 'L10 10'] 135 | 136 | >>> pen = MathImageSVGPathPen(None, lineAsCurve=True) 137 | >>> pen.moveTo((0, 0)) 138 | >>> pen.lineTo((10, 10)) 139 | >>> pen._commands 140 | ['M0 0', 'C0 0 10 10 10 10'] 141 | 142 | """ 143 | x, y = pt 144 | if self.lineAsCurve: 145 | # draw straight lines as curves with on-point controls 146 | self._curveToOne((self._lastX, self._lastY), (x,y), (x,y)) 147 | return 148 | if not self.optimise: 149 | cmd = "L" 150 | pts = pointToString(pt) 151 | else: 152 | # duplicate point 153 | if x == self._lastX and y == self._lastY: 154 | return 155 | # vertical line 156 | elif x == self._lastX: 157 | cmd = "V" 158 | pts = valueToString(y) 159 | # horizontal line 160 | elif y == self._lastY: 161 | cmd = "H" 162 | pts = valueToString(x) 163 | # previous was a moveto 164 | elif self._lastCommand == "M": 165 | cmd = None 166 | pts = " " + pointToString(pt) 167 | # basic 168 | else: 169 | cmd = "L" 170 | pts = pointToString(pt) 171 | # write the string 172 | t = "" 173 | if cmd: 174 | t += cmd 175 | self._lastCommand = cmd 176 | t += pts 177 | self._commands.append(t) 178 | # store for future reference 179 | self._lastX, self._lastY = pt 180 | 181 | def _curveToOne(self, pt1, pt2, pt3): 182 | """ 183 | >>> pen = MathImageSVGPathPen(None) 184 | >>> pen.curveTo((10, 20), (30, 40), (50, 60)) 185 | >>> pen._commands 186 | ['C10 20 30 40 50 60'] 187 | """ 188 | t = "C" 189 | t += pointToString(pt1) + " " 190 | t += pointToString(pt2) + " " 191 | t += pointToString(pt3) 192 | self._commands.append(t) 193 | self._lastCommand = "C" 194 | self._lastX, self._lastY = pt3 195 | 196 | def _qCurveToOne(self, pt1, pt2): 197 | """ 198 | >>> pen = MathImageSVGPathPen(None) 199 | >>> pen.qCurveTo((10, 20), (30, 40)) 200 | >>> pen._commands 201 | ['Q10 20 30 40'] 202 | """ 203 | assert pt2 is not None 204 | t = "Q" 205 | t += pointToString(pt1) + " " 206 | t += pointToString(pt2) 207 | self._commands.append(t) 208 | self._lastCommand = "Q" 209 | self._lastX, self._lastY = pt2 210 | 211 | def _closePath(self): 212 | """ 213 | >>> pen = MathImageSVGPathPen(None) 214 | >>> pen.closePath() 215 | >>> pen._commands 216 | ['Z'] 217 | """ 218 | self._commands.append("Z") 219 | self._lastCommand = "Z" 220 | self._lastX = self._lastY = None 221 | 222 | def _endPath(self): 223 | """ 224 | >>> pen = MathImageSVGPathPen(None) 225 | >>> pen.endPath() 226 | >>> pen._commands 227 | ['Z'] 228 | """ 229 | self._closePath() 230 | self._lastCommand = None 231 | self._lastX = self._lastY = None 232 | 233 | def getCommands(self): 234 | return "".join(self._commands) 235 | 236 | 237 | if __name__ == "__main__": 238 | import doctest 239 | doctest.testmod() 240 | -------------------------------------------------------------------------------- /peace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/peace.png -------------------------------------------------------------------------------- /responsiveLettering_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/responsiveLettering_screen.jpg -------------------------------------------------------------------------------- /www/example/00_Hnib_original_scan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/00_Hnib_original_scan.jpg -------------------------------------------------------------------------------- /www/example/01_initial_digitisation_in_robofont_Hnib.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 750 7 | capHeight 8 | 750 9 | descender 10 | -250 11 | postscriptBlueValues 12 | 13 | 14 | postscriptFamilyBlues 15 | 16 | 17 | postscriptFamilyOtherBlues 18 | 19 | 20 | postscriptOtherBlues 21 | 22 | 23 | postscriptStemSnapH 24 | 25 | 26 | postscriptStemSnapV 27 | 28 | 29 | unitsPerEm 30 | 1000 31 | xHeight 32 | 500 33 | 34 | 35 | -------------------------------------------------------------------------------- /www/example/01_initial_digitisation_in_robofont_Hnib.ufo/glyphs/H_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /www/example/01_initial_digitisation_in_robofont_Hnib.ufo/glyphs/I_.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /www/example/01_initial_digitisation_in_robofont_Hnib.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | H 6 | H_.glif 7 | I 8 | I_.glif 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/example/01_initial_digitisation_in_robofont_Hnib.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 2 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 750.0 7 | capHeight 8 | 750 9 | descender 10 | -250.0 11 | familyName 12 | Hnib 13 | note 14 | This is a template font for a MathShape. The font names and glyph widths can all tbe changed. 15 | postscriptBlueValues 16 | 17 | 18 | postscriptFamilyBlues 19 | 20 | 21 | postscriptFamilyOtherBlues 22 | 23 | 24 | postscriptOtherBlues 25 | 26 | 27 | postscriptStemSnapH 28 | 29 | 30 | postscriptStemSnapV 31 | 32 | 33 | styleName 34 | Sketch 35 | unitsPerEm 36 | 1000 37 | xHeight 38 | 500 39 | 40 | 41 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | narrow-bold 6 | narrow-bold.glif 7 | narrow-thin 8 | narrow-thin.glif 9 | wide-bold 10 | wide-bold.glif 11 | wide-thin 12 | wide-thin.glif 13 | 14 | 15 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/glyphs/narrow-bold.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -250.0 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -250.0 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750.0 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750.0 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-bold 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/glyphs/narrow-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -250.0 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -250.0 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750.0 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750.0 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-thin 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/glyphs/wide-bold.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -250.0 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 2269 124 | y 125 | -250.0 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 2269 134 | y 135 | 750.0 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750.0 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | wide-bold 155 | unicodes 156 | 157 | 158 | width 159 | 2269 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/glyphs/wide-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -250.0 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 2269 124 | y 125 | -250.0 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 2269 134 | y 135 | 750.0 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750.0 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | wide-thin 155 | unicodes 156 | 157 | 158 | width 159 | 2269 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/lib.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.typemytype.robofont.background.layerStrokeColor 6 | 7 | 0.0 8 | 0.8 9 | 0.2 10 | 0.7 11 | 12 | com.typemytype.robofont.bounds.layerStrokeColor 13 | 14 | 1.0 15 | 0.75 16 | 0.0 17 | 0.7 18 | 19 | com.typemytype.robofont.layerOrder 20 | 21 | background 22 | bounds 23 | 24 | com.typemytype.robofont.segmentType 25 | curve 26 | com.typemytype.robofont.sort 27 | 28 | 29 | ascending 30 | 31 | narrow-thin 32 | wide-thin 33 | narrow-bold 34 | wide-bold 35 | 36 | type 37 | glyphList 38 | 39 | 40 | public.glyphOrder 41 | 42 | narrow-thin 43 | wide-thin 44 | narrow-bold 45 | wide-bold 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /www/example/02_template_with_glyphs.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 2 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 750.0 7 | capHeight 8 | 750 9 | descender 10 | -250.0 11 | familyName 12 | ISIA_Hnib 13 | note 14 | This is a template font for a MathShape. The font names and glyph widths can all tbe changed. 15 | postscriptBlueValues 16 | 17 | 18 | postscriptFamilyBlues 19 | 20 | 21 | postscriptFamilyOtherBlues 22 | 23 | 24 | postscriptOtherBlues 25 | 26 | 27 | postscriptStemSnapH 28 | 29 | 30 | postscriptStemSnapV 31 | 32 | 33 | styleName 34 | 20160503 35 | unitsPerEm 36 | 1000 37 | xHeight 38 | 500 39 | 40 | 41 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | narrow-bold 6 | narrow-bold.glif 7 | narrow-thin 8 | narrow-thin.glif 9 | wide-bold 10 | wide-bold.glif 11 | wide-thin 12 | wide-thin.glif 13 | 14 | 15 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/glyphs/narrow-bold.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750.0 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750.0 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-bold 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/glyphs/narrow-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-thin 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/glyphs/wide-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 2270 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 2270 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | wide-thin 155 | unicodes 156 | 157 | 158 | width 159 | 2270 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/lib.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.letterror.mathshape.preview.bgcolor 6 | 7 | 1.0 8 | 0.5555695262832946 9 | 0.06304671192689248 10 | 1.0 11 | 12 | com.letterror.mathshape.preview.shapecolor 13 | 14 | 1.0 15 | 0.0 16 | 0.0 17 | 1.0 18 | 19 | com.typemytype.robofont.background.layerStrokeColor 20 | 21 | 0.0 22 | 0.8 23 | 0.2 24 | 0.7 25 | 26 | com.typemytype.robofont.bounds.layerStrokeColor 27 | 28 | 1.0 29 | 0.75 30 | 0.0 31 | 0.7 32 | 33 | com.typemytype.robofont.foreground.layerStrokeColor 34 | 35 | 0.5 36 | 0.0 37 | 0.5 38 | 0.7 39 | 40 | com.typemytype.robofont.layerOrder 41 | 42 | background 43 | bounds 44 | 45 | com.typemytype.robofont.segmentType 46 | curve 47 | com.typemytype.robofont.sort 48 | 49 | 50 | ascending 51 | 52 | narrow-thin 53 | wide-thin 54 | narrow-bold 55 | wide-bold 56 | 57 | type 58 | glyphList 59 | 60 | 61 | public.glyphOrder 62 | 63 | narrow-thin 64 | wide-thin 65 | narrow-bold 66 | wide-bold 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /www/example/03_more_variations.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 2 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/fontinfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ascender 6 | 750.0 7 | capHeight 8 | 750 9 | descender 10 | -250.0 11 | familyName 12 | ISIA_Hnib 13 | note 14 | This is a template font for a MathShape. The font names and glyph widths can all tbe changed. 15 | postscriptBlueValues 16 | 17 | 18 | postscriptFamilyBlues 19 | 20 | 21 | postscriptFamilyOtherBlues 22 | 23 | 24 | postscriptOtherBlues 25 | 26 | 27 | postscriptStemSnapH 28 | 29 | 30 | postscriptStemSnapV 31 | 32 | 33 | styleName 34 | 20160503 35 | unitsPerEm 36 | 1000 37 | xHeight 38 | 500 39 | 40 | 41 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/glyphs/contents.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | narrow-bold 6 | narrow-bold.glif 7 | narrow-thin 8 | narrow-thin.glif 9 | wide-bold 10 | wide-bold.glif 11 | wide-thin 12 | wide-thin.glif 13 | 14 | 15 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/glyphs/narrow-bold.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-bold 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/glyphs/narrow-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 1170 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 1170 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | narrow-thin 155 | unicodes 156 | 157 | 158 | width 159 | 1170 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/glyphs/wide-bold.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 2270 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 2270 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | wide-bold 155 | unicodes 156 | 157 | 158 | width 159 | 2270 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/glyphs/wide-thin.glif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.typemytype.robofont.layerData 93 | 94 | bounds 95 | 96 | anchors 97 | 98 | 99 | components 100 | 101 | 102 | contours 103 | 104 | 105 | points 106 | 107 | 108 | segmentType 109 | line 110 | smooth 111 | 112 | x 113 | 0 114 | y 115 | -50 116 | 117 | 118 | segmentType 119 | line 120 | smooth 121 | 122 | x 123 | 2270 124 | y 125 | -50 126 | 127 | 128 | segmentType 129 | line 130 | smooth 131 | 132 | x 133 | 2270 134 | y 135 | 750 136 | 137 | 138 | segmentType 139 | line 140 | smooth 141 | 142 | x 143 | 0 144 | y 145 | 750 146 | 147 | 148 | 149 | 150 | lib 151 | 152 | 153 | name 154 | wide-thin 155 | unicodes 156 | 157 | 158 | width 159 | 2270 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/lib.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.letterror.mathshape.preview.bgcolor 6 | 7 | 1.0 8 | 0.5555695262832946 9 | 0.06304671192689248 10 | 1.0 11 | 12 | com.letterror.mathshape.preview.shapecolor 13 | 14 | 1.0 15 | 0.0 16 | 0.0 17 | 1.0 18 | 19 | com.typemytype.robofont.background.layerStrokeColor 20 | 21 | 0.0 22 | 0.8 23 | 0.2 24 | 0.7 25 | 26 | com.typemytype.robofont.bounds.layerStrokeColor 27 | 28 | 1.0 29 | 0.75 30 | 0.0 31 | 0.7 32 | 33 | com.typemytype.robofont.foreground.layerStrokeColor 34 | 35 | 0.5 36 | 0.0 37 | 0.5 38 | 0.7 39 | 40 | com.typemytype.robofont.layerOrder 41 | 42 | background 43 | bounds 44 | 45 | com.typemytype.robofont.segmentType 46 | curve 47 | com.typemytype.robofont.sort 48 | 49 | 50 | ascending 51 | 52 | narrow-thin 53 | wide-thin 54 | narrow-bold 55 | wide-bold 56 | 57 | type 58 | glyphList 59 | 60 | 61 | public.glyphOrder 62 | 63 | narrow-thin 64 | wide-thin 65 | narrow-bold 66 | wide-bold 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /www/example/04_new_version_in_responsive_lettering_template.ufo/metainfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | creator 6 | org.robofab.ufoLib 7 | formatVersion 8 | 2 9 | 10 | 11 | -------------------------------------------------------------------------------- /www/example/contourordernarrow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/contourordernarrow.jpg -------------------------------------------------------------------------------- /www/example/contourorderwide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/contourorderwide.jpg -------------------------------------------------------------------------------- /www/example/fontwindow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/fontwindow.jpg -------------------------------------------------------------------------------- /www/example/newtemplate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/newtemplate.jpg -------------------------------------------------------------------------------- /www/example/paste.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/paste.jpg -------------------------------------------------------------------------------- /www/example/readme.md: -------------------------------------------------------------------------------- 1 | # Hnib Example Project 2 | 3 | This document describes the necessary steps for a responsive lettering thing in a small example project. There are many ways of doing this and the following steps are only an indication of how to work. 4 | 5 | These tools currently only work for RoboFont. 6 | 7 | [RoboFont](http://doc.robofont.com) 8 | 9 | [Responsive Lettering project on GitHub.](https://github.com/LettError/responsiveLettering) 10 | 11 | ## Steps in this document: 12 | 13 | * sketch 14 | * first digitisation 15 | * a new template 16 | * more animations 17 | 18 | ## Sketch 19 | The sketch is a simple drawing, it keeps the baseline and the x-height more or less the same. Important: draw width variations, so one is narrow, the other is wide. The drawing can be scanned or photographed. 20 | ![Hnib sketch](00_Hnib_original_scan.jpg) 21 | 22 | ## Digitisation 23 | 24 | * In RoboFont the scan is placed in a new font, in a new glyph. At this stage it does not matter which glyph. 25 | * Draw the outlines for the narrow version. 26 | * For the wide version copy the narrow outlines and then **move** the points to make the wide. By moving you make sure the new version will be compatible and the interpolation will work. 27 | 28 | ### Checking contour order 29 | 30 | Hopefully the order of the points and contours is still the same. Select **Contour Indexes** from the view options in the bottom right of the glyph window. 31 | 32 | ![Contour order narrow](contourordernarrow.jpg) 33 | 34 | But check if the wide version has the right order. You can change the order by cutting and pasting contours. 35 | 36 | ![Contour order wide](contourorderwide.jpg) 37 | 38 | ## A New Template 39 | 40 | * With the narrow and the wide versions done, have a look at how wide both images are in the Space Center. 41 | 42 | ![RoboFont Space Center with the digitised letters](spacecenter.jpg) 43 | 44 | In this example the narrow drawing is **1170** units and the wide image is **2269** units. We can enter these values in the Responsive Lettering > "New Template" dialog. 45 | 46 | ![The Repsonsive Lettering "New Template" dialog](newtemplate.jpg) 47 | 48 | When you click **Make** RoboFont will make a new UFO with the right glyphs. It will also ask for a place to save the UFO. Saving is good. 49 | 50 | ![RoboFont Font Window with the 4 template shapes](fontwindow.jpg) 51 | 52 | The new UFO has 4 glyphs with names that indicate what they're for: 53 | 54 | * Narrow Bold 55 | * Narrow Thin 56 | * Wide Bold 57 | * Wide Thin 58 | 59 | The yellow boxes are in the **bounds** layer and they are used for calculating the right size. The narrow glyphs need to have the same box, and the wide glyphs need to have the same box. 60 | 61 | ### Moving the glyphs to the template 62 | Copy and paste the glyphs to the right glyphs in the template. We only have a narrow and a wide glyph, so duplicate the glyphs for now. 63 | 64 | ![](paste.jpg) 65 | 66 | ### Preview and export 67 | The **Preview and Export** dialog gives you a moving preview of the letters. You can change the background and foreground colors. 68 | 69 | ![](responsivedialog.jpg) 70 | 71 | The table shows the width and height of the glyphs and if each glyph has the right rectangle in the bounds layer. The table also shows the number of contours and the total number of points. 72 | 73 | ## Troubleshooting 74 | 75 | ### Nothing shows up in the Preview? 76 | 77 | * Do all the glyphs have the right names from the template? 78 | * Do you have all 4 glyphs? 79 | * Are the contours in the right order? 80 | * Do all contours have the same number of points? 81 | 82 | **Fix the contour order.** 83 | 84 | ### Some contours fly all over the place? 85 | 86 | * This can happen if you have at least 2 contours with the same number of points. They interpolate, but with the wrong shape. 87 | 88 | **Check the contour indexes.** 89 | 90 | ### I made a new template and saved and then when I opened it up again all glyphs were gone. 91 | 92 | **Download the 1.3 version of the extension.** 93 | -------------------------------------------------------------------------------- /www/example/readme.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/readme.pdf -------------------------------------------------------------------------------- /www/example/responsivedialog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/responsivedialog.jpg -------------------------------------------------------------------------------- /www/example/spacecenter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/example/spacecenter.jpg -------------------------------------------------------------------------------- /www/example_ms/files.json: -------------------------------------------------------------------------------- 1 | {"files": ["example_ms/narrow-thin.svg", "example_ms/wide-thin.svg", "example_ms/narrow-bold.svg", "example_ms/wide-bold.svg"], "extrapolatemin": 0, "sizebounds": [[247, 750], [830, 750]], "extrapolatemax": 1.25} -------------------------------------------------------------------------------- /www/example_ms/narrow-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/example_ms/narrow-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/example_ms/wide-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/example_ms/wide-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/img/basicUFO.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/img/basicUFO.jpg -------------------------------------------------------------------------------- /www/img/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/img/example.jpg -------------------------------------------------------------------------------- /www/img/excellence.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/img/excellence.jpg -------------------------------------------------------------------------------- /www/img/mathShape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/img/mathShape.jpg -------------------------------------------------------------------------------- /www/img/peace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LettError/responsiveLettering/73a68eae72a86a6831e795157e7ef28e50212f62/www/img/peace.jpg -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LettError: Responsive Lettering 5 | 6 | 7 | 8 | 9 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 82 | 83 |
84 | 85 | 86 | 87 |
88 |

89 | Responsive lettering: scalable, interpolating vector shapes that can make themselves fit in a range of rectangles. More examples here. 90 | 91 | Ideas and code very much in debt to Jeremie Hornus, Nina Stössinger, Andrew Johnson, Onur Yazıcıgil, and Nick Sherman. Find all the code on Github.. Introduction here 92 | 93 |
94 |
95 | Depends on Snap.svg and jQuery. November 2015. 96 |

97 |
98 | 99 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /www/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Verdana"; 3 | font-size: 10pt; 4 | color: orange; 5 | margin: 0; 6 | padding: 0; 7 | background-color: black; 8 | } 9 | h1{ 10 | font-family: "LTRCondensedNo3-Grade2"; 11 | font-size: 40pt; 12 | margin-bottom: 10pt; 13 | margin-top: 2pt; 14 | font-weight: normal; 15 | } 16 | h2{ 17 | font-family: "LTRCondensedNo1-Grade2"; 18 | font-size: 30pt; 19 | margin-bottom: 5pt; 20 | font-weight: normal; 21 | } 22 | .value{ 23 | color:white; 24 | } 25 | 26 | #svgcontainer{ 27 | padding: 0; 28 | margin:0; 29 | height: 100vh; 30 | background-color: none; 31 | margin: auto; 32 | } 33 | 34 | 35 | /*show or hide the reporter*/ 36 | .hideReporter{ 37 | display: hidden; 38 | } 39 | #heyheyhey{ 40 | display: block; 41 | position: fixed; 42 | top: 0; 43 | width: 50vw; 44 | z-index: 1000; 45 | margin-left: 50%; 46 | font-size: 1em; 47 | color: white; 48 | background-color: rgba(100, 100, 100, 0.8); 49 | } 50 | .control{ 51 | position: fixed; 52 | top: 0; 53 | left: 0; 54 | color: white; 55 | padding: 10px; 56 | background-color:black; 57 | } 58 | #outline ul{ 59 | display: table; 60 | } 61 | .cmd { 62 | display: table-row; 63 | } 64 | p.comment{ 65 | color: white; 66 | z-index: 1000; 67 | } -------------------------------------------------------------------------------- /www/template_ms/files.json: -------------------------------------------------------------------------------- 1 | {"files": ["template_ms/narrow-thin.svg", "template_ms/wide-thin.svg", "template_ms/narrow-bold.svg", "template_ms/wide-bold.svg"], "extrapolatemin": 0, "designspace": "twobytwo", "sizebounds": [[500, 1000], [2500, 1000]], "extrapolatemax": 1.25} -------------------------------------------------------------------------------- /www/template_ms/narrow-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/template_ms/narrow-thin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/template_ms/wide-bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/template_ms/wide-thin.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------