Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A RoboFont script to generate a faux-italic font
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.
"""
[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