UNSOLVED Existing script to scale UPM, plus dimensions, glyphs, components, kerning, etc?
-
I'd like to scale the UPM of a UFO from 1000 to 2000 (or to any arbitrary number).
Of course, I can't just scale the
unitsPerEm
and call it good, because I also need to scale vertical metrics, glyphs, components, kerning, etc.I imagine this is a script that has been written before, so it seems worth checking before I put an hour into it and maybe miss something. Jens Kutilek has one on GitHub, but it's written for RF1.8 and relies on robofab.
Have I missed something in the docs, or something available online? Thanks for any pointers!
-
Thank you all for sharing this!
Would it make sense to also include Italic Slant Offset to the attributes at the bottom of the script? I remember this caused issues for me once after scaling a font.
-
here is a full simplified example – will be added to the docs soon.
'''Scale a font and what's inside it.''' font = CurrentFont() newUpm = 100 oldUpm = font.info.unitsPerEm factor = newUpm / oldUpm for layer in font.layers: for glyph in layer: for contour in glyph: contour.scaleBy(factor) for anchor in glyph.anchors: anchor.scaleBy(factor) for guideline in glyph.guidelines: guideline.scaleBy(factor) glyph.width *= factor font.kerning.scaleBy(factor) for guideline in font.guidelines: guideline.scaleBy(factor) for attr in ['unitsPerEm', 'descender', 'xHeight', 'capHeight', 'ascender']: oldValue = getattr(font.info, attr) newValue = oldValue * factor setattr(font.info, attr, newValue) font.changed()
@ArrowType thanks for the request!
-
and may just loop over all layers instead of ignoring the default layer:
for layer in font.layers: for glyph in layer: scaleGlyph(g, factor)
in RF3 each layer glyph has his own width and does not inherit it from the default layer (like in RF1)
-
why
flipLayers
?
-
Here's my updated script:
# Change upm # Jens Kutilek 2013-01-02 # # updated for RoboFont 3 by Stephen Nixon / @arrowtype 2019-10-22 from mojo.roboFont import version from mojo.UI import AskString def scaleGlyph(glyph, factor): for contour in glyph: contour.transformBy(factor, 0,0,factor,0,0) for anchor in glyph.anchors: anchor.transformBy(factor, 0,0,factor,0,0) for guideline in glyph.guidelines: guideline.transformBy(factor, 0,0,factor,0,0) # and dont transform components def changeUPM(font, factor, roundCoordinates=True): # glyphs for g in font: scaleGlyph(g, factor) # layers mainLayer = "foreground" for layerName in font.layerOrder: if layerName != mainLayer: for g in font: g.flipLayers(mainLayer, layerName) scaleGlyph(g, factor, scaleWidth=False) g.flipLayers(layerName, mainLayer) # kerning if font.kerning: font.kerning.scale(factor) # vertical dimensions font.info.descender = int(round(font.info.descender * factor)) font.info.xHeight = int(round(font.info.xHeight * factor)) font.info.capHeight = int(round(font.info.capHeight * factor)) font.info.ascender = int(round(font.info.ascender * factor)) # finally set new UPM font.info.unitsPerEm = newUpm font.update() if __name__ == "__main__": from mojo.UI import AskString print("Change Units Per Em") if CurrentFont() is not None: oldUpm = CurrentFont().info.unitsPerEm newUpm = CurrentFont().info.unitsPerEm try: newUpm = int(AskString("New units per em size?", oldUpm)) except: pass if newUpm == oldUpm: print(" Not changing upm size.") else: factor = float(newUpm) / oldUpm print(" Scaling all font measurements by", factor) changeUPM(CurrentFont(), factor) else: print(" Open a font first to change upm, please.") print(" Done.")
-
Awesome, thanks Frederik! Ha, that makes a lot more sense. :)
-
the script does way to much... removing components and placing them back? flipLayers around?
for contour in glyph: contour.transformBy(matrix) for anchor in glyph.anchors: anchor.transformBy(matrix) for guideline in glyph.guidelines: guideline.transformBy(matrix) # and dont transform components
-
Well, I've hastily updated Jens's script for now. 😄
I assume someone has a better version (please post if you do!), but here's a start for the next person that searches on the forum.
# Change upm # Jens Kutilek 2013-01-02 # hastily updated for RoboFont 3 by Stephen Nixon / @arrowtype from mojo.roboFont import version from mojo.UI import AskString def scalePoints(glyph, factor): if version == "1.4": # stupid workaround for bug in RoboFont 1.4 for contour in glyph: for point in contour.points: point.x *= factor point.y *= factor glyph.width *= factor else: glyph *= factor def scaleGlyph(glyph, factor, scaleWidth=True, roundCoordinates=True): if not(scaleWidth): oldWidth = glyph.width if len(glyph.components) == 0: glyph.transformBy((factor,0,0,factor,0,0), origin=(0,0)) glyph.width *= factor if roundCoordinates: glyph.round() else: # save components # this may be a tad too convoluted ... components = [] for i in range(len(glyph.components)): components.append(glyph.components[i]) for c in components: glyph.removeComponent(c) glyph.transformBy((factor,0,0,factor,0,0), origin=(0,0)) glyph.width *= factor if roundCoordinates: glyph.round() # restore components for i in range(len(components)): newOffset = (int(round(components[i].offset[0] * factor)), int(round(components[i].offset[1] * factor))) glyph.appendComponent(components[i].baseGlyph, newOffset, components[i].scale) if not(scaleWidth): # restore width glyph.width = oldWidth def changeUPM(font, factor, roundCoordinates=True): # Glyphs for g in font: scaleGlyph(g, factor) for guide in g.guides: guide.x *= factor guide.y *= factor # Glyph layers mainLayer = "foreground" for layerName in font.layerOrder: if layerName != mainLayer: for g in font: g.flipLayers(mainLayer, layerName) scaleGlyph(g, factor, scaleWidth=False) g.flipLayers(layerName, mainLayer) # Kerning if font.kerning: font.kerning.scale(factor) if roundCoordinates: if not version in ["1.4", "1.5", "1.5.1"]: font.kerning.round(1) else: print("WARNING: kerning values cannot be rounded to integer in this RoboFont version") # TODO: Change positioning feature code? # Vertical dimensions font.info.descender = int(round(font.info.descender * factor)) font.info.xHeight = int(round(font.info.xHeight * factor)) font.info.capHeight = int(round(font.info.capHeight * factor)) font.info.ascender = int(round(font.info.ascender * factor)) # Finally set new UPM font.info.unitsPerEm = newUpm font.update() if __name__ == "__main__": from mojo.UI import AskString print("Change Units Per Em") if CurrentFont() is not None: oldUpm = CurrentFont().info.unitsPerEm newUpm = CurrentFont().info.unitsPerEm try: newUpm = int(AskString("New units per em size?", oldUpm)) except: pass if newUpm == oldUpm: print(" Not changing upm size.") else: factor = float(newUpm) / oldUpm print(" Scaling all font measurements by", factor) changeUPM(CurrentFont(), factor) else: print(" Open a font first to change upm, please.") print(" Done.")