Created
September 21, 2020 14:52
-
-
Save arrowtype/a3c4445cd6b9a6174f885eccff38e1c9 to your computer and use it in GitHub Desktop.
A RoboFont script to generate a faux-italic font
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Copyright 2020 Arrow Type / Stephen Nixon | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
[WORK IN PROGRESS – works pretty well, but could use a few more improvements.] | |
A script to take the sources of Name Sans and output slanted versions of these, | |
for the purposes of A) prototyping & B) jumpstarting the italic drawings. | |
Copies selected UFOs to an italic path, then: | |
1. Slants glyphs at a selected angle | |
2. Slant round glyphs by less – maybe 7 degrees? # TODO: make this work better | |
3. Adds extreme points only if you want (or any points) | |
Based largely on the Slanter extension: | |
https://github.com/roboDocs/slanterRoboFontExtension/blob/57d7ac8ad9c91b0dce480cccb5a5dc2d4d4c51c4/Slanter.roboFontExt/lib/slanter.py | |
""" | |
from vanilla.dialogs import * | |
from math import radians | |
from fontTools.misc.transform import Transform | |
from mojo.roboFont import CurrentGlyph, CurrentFont, RGlyph, RPoint | |
import os | |
import shutil | |
# ------------------------------ | |
# set preferences below | |
openNewFonts = True | |
saveNewFonts = True | |
addExtremesToNewFonts = False | |
outputFolder = "faux-italics" | |
setSkew = 10.24 | |
setRotation = 0 | |
rounds = "O o e c".split() | |
roundSkew = 10.24 # TODO: set this to 7? | |
# TODO: if you skew rounds differently, you must translate them on the x axis to match the movement of everything else | |
dialogMessage = "Select UFOs to copy into faux-italics" | |
# ------------------------------ | |
# Function mostly copied from the Slanter extension | |
def getGlyph(glyph, skew, rotation, addComponents=False, skipComponents=False, addExtremes=False): | |
skew = radians(skew) | |
rotation = radians(-rotation) | |
dest = glyph.copy() | |
if not addComponents: | |
for component in dest.components: | |
pointPen = DecomposePointPen(glyph.layer, dest.getPointPen(), component.transformation) | |
component.drawPoints(pointPen) | |
dest.removeComponent(component) | |
for contour in list(dest): | |
if contour.open: | |
dest.removeContour(contour) | |
if skew == 0 and rotation == 0: | |
return dest | |
for contour in dest: | |
for bPoint in contour.bPoints: | |
bcpIn = bPoint.bcpIn | |
bcpOut = bPoint.bcpOut | |
if bcpIn == (0, 0): | |
continue | |
if bcpOut == (0, 0): | |
continue | |
if bcpIn[0] == bcpOut[0] and bcpIn[1] != bcpOut[1]: | |
bPoint.anchorLabels = ["extremePoint"] | |
if rotation and bcpIn[0] != bcpOut[0] and bcpIn[1] == bcpOut[1]: | |
bPoint.anchorLabels = ["extremePoint"] | |
cx, cy = 0, 0 | |
box = glyph.bounds | |
if box: | |
cx = box[0] + (box[2] - box[0]) * .5 | |
cy = box[1] + (box[3] - box[1]) * .5 | |
t = Transform() | |
t = t.skew(skew) | |
t = t.translate(cx, cy).rotate(rotation).translate(-cx, -cy) | |
if not skipComponents: | |
dest.transformBy(tuple(t)) | |
else: | |
for contour in dest.contours: | |
contour.transformBy(tuple(t)) | |
# this seems to work !!! | |
for component in dest.components: | |
# get component center | |
_box = glyph.layer[component.baseGlyph].bounds | |
if not _box: | |
continue | |
_cx = _box[0] + (_box[2] - _box[0]) * .5 | |
_cy = _box[1] + (_box[3] - _box[1]) * .5 | |
# calculate origin in relation to base glyph | |
dx = cx - _cx | |
dy = cy - _cy | |
# create transformation matrix | |
tt = Transform() | |
tt = tt.skew(skew) | |
tt = tt.translate(dx, dy).rotate(rotation).translate(-dx, -dy) | |
# apply transformation matrix to component offset | |
P = RPoint() | |
P.position = component.offset | |
P.transformBy(tuple(tt)) | |
# set component offset position | |
component.offset = P.position | |
# check if "add extremes" is set to True | |
if addExtremes: | |
dest.extremePoints(round=0) | |
for contour in dest: | |
for point in contour.points: | |
if "extremePoint" in point.labels: | |
point.selected = True | |
point.smooth = True | |
else: | |
point.selected = False | |
dest.removeSelection() | |
dest.round() | |
return dest | |
# Function adapted from the Slanter extension | |
def generateFont(fontToCopy): | |
outFont = RFont(showInterface=False) | |
outFont.info.update(fontToCopy.info.asDict()) | |
outFont.features.text = fontToCopy.features.text | |
for glyph in fontToCopy: | |
outFont.newGlyph(glyph.name) | |
outGlyph = outFont[glyph.name] | |
outGlyph.width = glyph.width | |
outGlyph.unicodes = glyph.unicodes | |
if glyph.name not in rounds: | |
resultGlyph = getGlyph(glyph, setSkew, setRotation, addComponents=True, skipComponents=True, addExtremes=addExtremesToNewFonts) | |
else: | |
resultGlyph = getGlyph(glyph, roundSkew, setRotation, addComponents=True, skipComponents=True, addExtremes=addExtremesToNewFonts) | |
outGlyph.appendGlyph(resultGlyph) | |
# copy glyph order | |
outFont.templateGlyphOrder = fontToCopy.templateGlyphOrder | |
# copy groups & kerning | |
outFont.groups.update(fontToCopy.groups.asDict()) | |
outFont.kerning.update(fontToCopy.kerning.asDict()) | |
# quick/lazy update to relative feature link | |
if "sparse" not in fontToCopy.path: | |
outFont.features.text = "include(../../features/features.fea);" | |
outFont.info.styleName = outFont.info.styleName + " Italic" | |
return outFont | |
# Get input font paths | |
inputFonts = getFile(dialogMessage, allowsMultipleSelection=True, fileTypes=["ufo"]) | |
# Go through input paths & use to generate slanted fonts | |
for fontPath in inputFonts: | |
font = OpenFont(fontPath, showInterface=False) | |
print("\n\n\n\n--------------------------------") | |
print(font.info.styleName, "\n\n") | |
# set up paths, clear existing UFOs | |
fontDir, fontFile = os.path.split(fontPath) | |
italicDir = fontDir + "/" + outputFolder | |
if not os.path.exists(italicDir): | |
os.makedirs(italicDir) | |
slantedFontPath = italicDir + "/" + f"{fontFile.replace('.ufo','_Italic.ufo')}" | |
# delete faux-italic UFO if it already exists, to avoid filesystem clashes | |
if os.path.exists(slantedFontPath): | |
shutil.rmtree(slantedFontPath) | |
# Generate faux italic font | |
slantedFont = generateFont(font) | |
if saveNewFonts: | |
slantedFont.save(slantedFontPath) | |
if openNewFonts: | |
slantedFont.openInterface() | |
else: | |
slantedFont.close() | |
font.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment