Skip to content

Instantly share code, notes, and snippets.

@mekkablue
Created July 27, 2019 15:51
Show Gist options
  • Save mekkablue/d743096938f9619a043cb6f00339f977 to your computer and use it in GitHub Desktop.
Save mekkablue/d743096938f9619a043cb6f00339f977 to your computer and use it in GitHub Desktop.
Wackelpudding
#MenuTitle: Wackelpudding
# -*- coding: utf-8 -*-
__doc__="""
Create pseudorandom rotation feature for selected glyphs.
"""
maxangle = 20.0 # Maximum angle by which a glyph may rotate
minangle = 4.0
alphabets = 5 # Instances of rotated glyphs
linelength = 80 # length of the line the feature should be working on
featurename = "calt"
import math, random
random.seed()
Font = Glyphs.font
FontMaster = Font.selectedFontMaster
selectedGlyphs = [ x.parent for x in Font.selectedLayers ]
def rotationTransform( angle=180.0, xOrigin=0.0, yOrigin=0.0 ):
"""Returns a TransformStruct for rotating."""
RotationTransform = NSAffineTransform.transform()
RotationTransform.translateXBy_yBy_( xOrigin, yOrigin )
RotationTransform.rotateByDegrees_( angle )
RotationTransform.translateXBy_yBy_( -xOrigin, -yOrigin )
return RotationTransform
def clearLayer( thisLayer ):
thisLayer.parent.beginUndo()
for i in range( len( thisLayer.paths ))[::-1]:
del thisLayer.paths[i]
for i in range( len( thisLayer.components ))[::-1]:
del thisLayer.components[i]
thisLayer.parent.endUndo()
def glyphcopy( sourceGlyph, targetGlyphName ):
targetGlyph = sourceGlyph.copy()
targetGlyph.name = targetGlyphName
Font.glyphs.append( targetGlyph )
# targetGlyph = Font.glyphs[ targetGlyphName ]
# Place Components:
MasterIDs = [m.id for m in Font.masters]
for thisMaster in MasterIDs:
sourceLayer = sourceGlyph.layers[ thisMaster ]
targetLayer = targetGlyph.layers[ thisMaster ]
clearLayer( targetLayer )
targetLayer.width = sourceLayer.width
myComponent = GSComponent( sourceGlyph.name )
myComponent.componentName = sourceGlyph.name
targetLayer.components.append( myComponent )
return targetGlyph
def ssXXsuffix( i ):
"""Turns an integer into an ssXX ending between .ss01 and .ss20, e.g. 5 -> '.ss05'."""
i = i%21 # max 20
if not i: # if 0
i = 1
return ".calt.ss%.2d" % ( i )
def make_ssXX( thisGlyph, number ):
myListOfGlyphs = []
for x in range(number):
newName = thisGlyph.name + ssXXsuffix( x + 1 )
print "glyphcopy( thisGlyph, newName ): ", thisGlyph, newName
copiedGlyph = glyphcopy( thisGlyph, newName )
myListOfGlyphs.append( copiedGlyph )
return myListOfGlyphs
def wiggle( thisGlyph, maxangle, minangle ):
rotateBy = random.random() * (maxangle-minangle) + minangle
positiveOrNegative = -1 + 2 * round(random.random()) # should be either -1 or 1
rotateBy *= positiveOrNegative
for thisLayer in thisGlyph.layers:
# Disable aligment for components, so we can transform freely:
for thisComponent in thisLayer.components:
thisComponent.setDisableAlignment_( True )
# Transform the whole layer:
xOrigin = thisLayer.width * 0.5
yOrigin = thisLayer.bounds.size.height * 0.5
thisLayer.transform_checkForSelection_doComponents_(
rotationTransform( angle=rotateBy, xOrigin=xOrigin, yOrigin=yOrigin ), # rotate around origin
False, # checkForSelection
True # doComponents
)
def create_otclass( classname = "@default",
classglyphs = [ x.parent.name for x in Font.selectedLayers ],
targetfont = Font ):
# strip '@' from beginning:
classname = classname.lstrip("@")
classcode = " ".join( classglyphs )
otClass = targetfont.classes[classname]
if otClass:
otClass.code = classcode
return "Updated existing OT class '%s'." % classname
else:
newClass = GSClass( classname, classcode )
targetfont.classes.append( newClass )
return "Created new OT class: '%s'" % classname
def updateCode( oldcode, beginsig, endsig, newcode ):
"""Replaces text in oldcode with newcode, but only between beginsig and endsig."""
begin_offset = oldcode.find( beginsig )
end_offset = oldcode.find( endsig ) + len( endsig )
newcode = oldcode[:begin_offset] + beginsig + newcode + "\n" + endsig + oldcode[end_offset:]
return newcode
def otFeatureCode( featurename = "calt",
featurecode = "# empty feature code",
targetfont = Font,
codesig = "DEFAULT-CODE-SIGNATURE" ):
"""
Creates or updates an OpenType feature in the font.
Returns a status message in form of a string.
"""
beginSig = "# BEGIN " + codesig + "\n"
endSig = "# END " + codesig + "\n"
if featurename in [ f.name for f in targetfont.features ]:
# feature already exists:
targetfeature = targetfont.features[ featurename ]
if beginSig in targetfeature.code:
# replace old code with new code:
targetfeature.code = updateCode( targetfeature.code, beginSig, endSig, featurecode )
else:
# append new code:
targetfeature.code += "\n" + beginSig + featurecode + "\n" + endSig
return "Updated existing OT feature '%s'." % featurename
else:
# create feature with new code:
newFeature = GSFeature( featurename, beginSig + featurecode + "\n" + endSig )
targetfont.features.append( newFeature )
return "Created new OT feature '%s'" % featurename
def pseudoRandomize( featurename="calt", defaultClassName="@default", pseudoClassName="@calt", alphabets=5, linelength=70 ):
featuretext = ""
listOfClasses = (range(alphabets)*((linelength//alphabets)+2))
for i in range( (alphabets * ( linelength//alphabets ) + 1), 0, -1 ):
newline = "sub @default' " + "@default "*i + "by @calt" + str( listOfClasses[i] ) + ";\n"
featuretext = featuretext + newline
return otFeatureCode( featurename=featurename, featurecode=featuretext, codesig="WACKELPUDDING")
# Make ssXX copies of selected glyphs and rotate them randomly:
classlist = []
for thisGlyph in selectedGlyphs:
if thisGlyph.export == True:
glyphName = thisGlyph.name
print "Processing %s" % ( glyphName )
classlist.append( glyphName )
glyphList = make_ssXX( thisGlyph, alphabets )
for thisVeryGlyph in glyphList:
print "Wiggling", thisVeryGlyph.name
wiggle( thisVeryGlyph, maxangle, minangle )
# Create OT classes:
print create_otclass( classname="default", classglyphs=classlist )
for x in range( alphabets ):
print create_otclass( classname="@calt%i" % x, classglyphs=[s + ssXXsuffix( x+1 ) for s in classlist] )
# Create OT feature:
print pseudoRandomize( alphabets=alphabets, linelength=linelength, featurename=featurename )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment