Cython is used to compile some hot paths into native Python extensions. These hot paths were identified through running ufocompile with the hotshot profiler and then converting file by file to Cython, starting with the "hottest" paths and continuing until returns were deminishing. This means that only a few Python files were converted to Cython. Closes #23 Closes #20 (really this time)
1253 lines
39 KiB
Python
1253 lines
39 KiB
Python
|
|
|
|
__DEBUG__ = True
|
|
__version__ = "0.2"
|
|
|
|
"""
|
|
RoboFab API Objects for FontForge
|
|
http://fontforge.sourceforge.net
|
|
|
|
FontForge python docs:
|
|
http://fontforge.sourceforge.net/python.html
|
|
|
|
Note: This is dead. EvB: "objectsFF.py is very dead and should only serve as an example of "dead"
|
|
|
|
History
|
|
Version zero. May 2007. EvB
|
|
Experiment to see how far the API can be made to work.
|
|
|
|
0.1 extended testing and comparisons for attributes.
|
|
0.2 checked into svn. Still quite raw. Lots of print statements and tests at the end.
|
|
|
|
Notes
|
|
This code is best used with fontforge compiled as a python extension.
|
|
|
|
FontForge Python API:
|
|
__doc__
|
|
str(object) -> string
|
|
|
|
Return a nice string representation of the object.
|
|
If the argument is a string, the return value is the same object.
|
|
|
|
__file__
|
|
str(object) -> string
|
|
|
|
Return a nice string representation of the object.
|
|
If the argument is a string, the return value is the same object.
|
|
|
|
__name__
|
|
str(object) -> string
|
|
|
|
Return a nice string representation of the object.
|
|
If the argument is a string, the return value is the same object.
|
|
|
|
activeFont
|
|
If invoked from the UI, this returns the currently active font. When not in UI this returns None
|
|
|
|
activeFontInUI
|
|
If invoked from the UI, this returns the currently active font. When not in UI this returns None
|
|
|
|
activeGlyph
|
|
If invoked from the UI, this returns the currently active glyph (or None)
|
|
|
|
ask
|
|
Pops up a dialog asking the user a question and providing a set of buttons for the user to reply with
|
|
|
|
askChoices
|
|
Pops up a dialog asking the user a question and providing a scrolling list for the user to reply with
|
|
|
|
askString
|
|
Pops up a dialog asking the user a question and providing a textfield for the user to reply with
|
|
|
|
contour
|
|
fontforge Contour objects
|
|
|
|
contouriter
|
|
None
|
|
|
|
cvt
|
|
fontforge cvt objects
|
|
|
|
defaultOtherSubrs
|
|
Use FontForge's default "othersubrs" functions for Type1 fonts
|
|
|
|
font
|
|
FontForge Font object
|
|
|
|
fontiter
|
|
None
|
|
|
|
fonts
|
|
Returns a tuple of all loaded fonts
|
|
|
|
fontsInFile
|
|
Returns a tuple containing the names of any fonts in an external file
|
|
|
|
getPrefs
|
|
Get FontForge preference items
|
|
|
|
glyph
|
|
FontForge GlyphPen object
|
|
|
|
glyphPen
|
|
FontForge Glyph object
|
|
|
|
hasSpiro
|
|
Returns whether this fontforge has access to Raph Levien's spiro package
|
|
|
|
hasUserInterface
|
|
Returns whether this fontforge session has a user interface (True if it has opened windows) or is just running a script (False)
|
|
|
|
hooks
|
|
dict() -> new empty dictionary.
|
|
dict(mapping) -> new dictionary initialized from a mapping object's
|
|
(key, value) pairs.
|
|
dict(seq) -> new dictionary initialized as if via:
|
|
d = {}
|
|
for k, v in seq:
|
|
d[k] = v
|
|
dict(**kwargs) -> new dictionary initialized with the name=value pairs
|
|
in the keyword argument list. For example: dict(one=1, two=2)
|
|
|
|
layer
|
|
fontforge Layer objects
|
|
|
|
layeriter
|
|
None
|
|
|
|
loadEncodingFile
|
|
Load an encoding file into the list of encodings
|
|
|
|
loadNamelist
|
|
Load a namelist into the list of namelists
|
|
|
|
loadNamelistDir
|
|
Load a directory of namelist files into the list of namelists
|
|
|
|
loadPlugin
|
|
Load a FontForge plugin
|
|
|
|
loadPluginDir
|
|
Load a directory of FontForge plugin files
|
|
|
|
loadPrefs
|
|
Load FontForge preference items
|
|
|
|
logWarning
|
|
Adds a non-fatal message to the Warnings window
|
|
|
|
open
|
|
Opens a font and returns it
|
|
|
|
openFilename
|
|
Pops up a file picker dialog asking the user for a filename to open
|
|
|
|
parseTTInstrs
|
|
Takes a string and parses it into a tuple of truetype instruction bytes
|
|
|
|
point
|
|
fontforge Point objects
|
|
|
|
postError
|
|
Pops up an error dialog box with the given title and message
|
|
|
|
postNotice
|
|
Pops up an notice window with the given title and message
|
|
|
|
preloadCidmap
|
|
Load a cidmap file
|
|
|
|
printSetup
|
|
Prepare to print a font sample (select default printer or file, page size, etc.)
|
|
|
|
private
|
|
FontForge private dictionary
|
|
|
|
privateiter
|
|
None
|
|
|
|
readOtherSubrsFile
|
|
Read from a file, "othersubrs" functions for Type1 fonts
|
|
|
|
registerImportExport
|
|
Adds an import/export spline conversion module
|
|
|
|
registerMenuItem
|
|
Adds a menu item (which runs a python script) to the font or glyph (or both) windows -- in the Tools menu
|
|
|
|
saveFilename
|
|
Pops up a file picker dialog asking the user for a filename to use for saving
|
|
|
|
savePrefs
|
|
Save FontForge preference items
|
|
|
|
selection
|
|
fontforge selection objects
|
|
|
|
setPrefs
|
|
Set FontForge preference items
|
|
|
|
spiroCorner
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
spiroG2
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
spiroG4
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
spiroLeft
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
spiroOpen
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
spiroRight
|
|
int(x[, base]) -> integer
|
|
|
|
Convert a string or number to an integer, if possible. A floating point
|
|
argument will be truncated towards zero (this does not include a string
|
|
representation of a floating point number!) When converting a string, use
|
|
the optional base. It is an error to supply a base when converting a
|
|
non-string. If the argument is outside the integer range a long object
|
|
will be returned instead.
|
|
|
|
unParseTTInstrs
|
|
Takes a tuple of truetype instruction bytes and converts to a human readable string
|
|
|
|
unicodeFromName
|
|
Given a name, look it up in the namelists and find what unicode code point it maps to (returns -1 if not found)
|
|
|
|
version
|
|
Returns a string containing the current version of FontForge, as 20061116
|
|
|
|
|
|
|
|
|
|
Problems:
|
|
XXX: reading glif from UFO: is the contour order changed in some way?
|
|
|
|
|
|
ToDo:
|
|
- segments ?
|
|
|
|
|
|
"""
|
|
|
|
import os
|
|
from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\
|
|
BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\
|
|
roundPt, addPt, _box,\
|
|
MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\
|
|
relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut
|
|
|
|
from robofab.objects.objectsRF import RGlyph as _RGlyph
|
|
|
|
import fontforge
|
|
import psMat
|
|
|
|
|
|
# a list of attributes that are to be copied when copying a glyph.
|
|
# this is used by glyph.copy and font.insertGlyph
|
|
GLYPH_COPY_ATTRS = [
|
|
"name",
|
|
"width",
|
|
"unicodes",
|
|
"note",
|
|
"lib",
|
|
]
|
|
|
|
|
|
|
|
def CurrentFont():
|
|
if fontforge.hasUserInterface():
|
|
_font = fontforge.activeFontInUI()
|
|
return RFont(_font)
|
|
if __DEBUG__:
|
|
print "CurrentFont(): fontforge not running with user interface,"
|
|
return None
|
|
|
|
def OpenFont(fontPath):
|
|
obj = fontforge.open(fontPath)
|
|
if __DEBUG__:
|
|
print "OpenFont", fontPath
|
|
print "result:", obj
|
|
return RFont(obj)
|
|
|
|
def NewFont(fontPath=None):
|
|
_font = fontforge.font()
|
|
if __DEBUG__:
|
|
print "NewFont", fontPath
|
|
print "result:", _font
|
|
return RFont(_font)
|
|
|
|
|
|
|
|
|
|
class RFont(BaseFont):
|
|
def __init__(self, font=None):
|
|
if font is None:
|
|
# make a new font
|
|
pass
|
|
else:
|
|
self._object = font
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# access
|
|
|
|
def keys(self):
|
|
"""FF implements __iter__ for the font object - better?"""
|
|
return [n.glyphname for n in self._object.glyphs()]
|
|
|
|
def has_key(self, glyphName):
|
|
return glyphName in self
|
|
|
|
def _get_info(self):
|
|
return RInfo(self._object)
|
|
|
|
info = property(_get_info, doc="font info object")
|
|
|
|
def __iter__(self):
|
|
for glyphName in self.keys():
|
|
yield self.getGlyph(glyphName)
|
|
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# file
|
|
|
|
def _get_path(self):
|
|
return self._object.path
|
|
|
|
path = property(_get_path, doc="path of this file")
|
|
|
|
def __contains__(self, glyphName):
|
|
return glyphName in self.keys()
|
|
|
|
def save(self, path=None):
|
|
"""Save this font as sfd file.
|
|
XXX: how to set a sfd path if is none
|
|
"""
|
|
if path is not None:
|
|
# trying to save it somewhere else
|
|
_path = path
|
|
else:
|
|
_path = self.path
|
|
if os.path.splitext(_path)[-1] != ".sfd":
|
|
_path = os.path.splitext(_path)[0]+".sfd"
|
|
if __DEBUG__:
|
|
print "RFont.save() to", _path
|
|
self._object.save(_path)
|
|
|
|
def naked(self):
|
|
return self._object
|
|
|
|
def close(self):
|
|
if __DEBUG__:
|
|
print "RFont.close()"
|
|
self._object.close()
|
|
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# generate
|
|
|
|
def dummyGeneratePreHook(self, *args):
|
|
print "dummyGeneratePreHook", args
|
|
|
|
def dummyGeneratePostHook(self, *args):
|
|
print "dummyGeneratePostHook", args
|
|
|
|
def generate(self, outputType, path=None):
|
|
"""
|
|
generate the font. outputType is the type of font to ouput.
|
|
--Ouput Types:
|
|
'pctype1' : PC Type 1 font (binary/PFB)
|
|
'pcmm' : PC MultipleMaster font (PFB)
|
|
'pctype1ascii' : PC Type 1 font (ASCII/PFA)
|
|
'pcmmascii' : PC MultipleMaster font (ASCII/PFA)
|
|
'unixascii' : UNIX ASCII font (ASCII/PFA)
|
|
'mactype1' : Mac Type 1 font (generates suitcase and LWFN file)
|
|
'otfcff' : PS OpenType (CFF-based) font (OTF)
|
|
'otfttf' : PC TrueType/TT OpenType font (TTF)
|
|
'macttf' : Mac TrueType font (generates suitcase)
|
|
'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork)
|
|
(doc adapted from http://dev.fontlab.net/flpydoc/)
|
|
|
|
path can be a directory or a directory file name combo:
|
|
path="DirectoryA/DirectoryB"
|
|
path="DirectoryA/DirectoryB/MyFontName"
|
|
if no path is given, the file will be output in the same directory
|
|
as the vfb file. if no file name is given, the filename will be the
|
|
vfb file name with the appropriate suffix.
|
|
"""
|
|
|
|
extensions = {
|
|
'pctype1': 'pfm',
|
|
'otfcff': 'otf',
|
|
}
|
|
|
|
if __DEBUG__:
|
|
print "font.generate", outputType, path
|
|
|
|
# set pre and post hooks (necessary?)
|
|
temp = getattr(self._object, "temporary")
|
|
if temp is None:
|
|
self._object.temporary = {}
|
|
else:
|
|
if type(self._object.temporary)!=dict:
|
|
self._object.temporary = {}
|
|
self._object.temporary['generateFontPreHook'] = self.dummyGeneratePreHook
|
|
self._object.temporary['generateFontPostHook'] = self.dummyGeneratePostHook
|
|
|
|
# make a path for the destination
|
|
if path is None:
|
|
fileName = os.path.splitext(os.path.basename(self.path))[0]
|
|
dirName = os.path.dirname(self.path)
|
|
extension = extensions.get(outputType)
|
|
if extension is not None:
|
|
fileName = "%s.%s"%(fileName, extension)
|
|
else:
|
|
if __DEBUG__:
|
|
print "can't generate font in %s format"%outputType
|
|
return
|
|
path = os.path.join(dirName, fileName)
|
|
|
|
# prepare OTF fields
|
|
generateFlags = []
|
|
generateFlags.append('opentype')
|
|
# generate
|
|
self._object.generate(filename=path, flags=generateFlags)
|
|
if __DEBUG__:
|
|
print "font.generate():", path
|
|
return path
|
|
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# kerning stuff
|
|
|
|
def _get_kerning(self):
|
|
kerning = {}
|
|
f = self._object
|
|
for g in f.glyphs:
|
|
for p in g.kerning:
|
|
try:
|
|
key = (g.name, f[p.key].name)
|
|
kerning[key] = p.value
|
|
except AttributeError: pass #catch for TT exception
|
|
rk = RKerning(kerning)
|
|
rk.setParent(self)
|
|
return rk
|
|
|
|
kerning = property(_get_kerning, doc="a kerning object")
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# glyph stuff
|
|
|
|
def getGlyph(self, glyphName):
|
|
try:
|
|
ffGlyph = self._object[glyphName]
|
|
except TypeError:
|
|
print "font.getGlyph, can't find glyphName, returning new glyph"
|
|
return self.newGlyph(glyphName)
|
|
glyph = RGlyph(ffGlyph)
|
|
glyph.setParent(self)
|
|
return glyph
|
|
|
|
def newGlyph(self, glyphName, clear=True):
|
|
"""Make a new glyph
|
|
|
|
Notes: not sure how to make a new glyph without an encoded name.
|
|
createChar() seems to be intended for that, but when I pass it -1
|
|
for the unicode, it complains that it wants -1. Perhaps a bug?
|
|
"""
|
|
# is the glyph already there?
|
|
glyph = None
|
|
if glyphName in self:
|
|
if clear:
|
|
self._object[glyphName].clear()
|
|
return self[glyphName]
|
|
else:
|
|
# is the glyph in an encodable place:
|
|
slot = self._object.findEncodingSlot(glyphName)
|
|
if slot == -1:
|
|
# not encoded
|
|
print "font.newGlyph: unencoded slot", slot, glyphName
|
|
glyph = self._object.createChar(-1, glyphName)
|
|
else:
|
|
glyph = self._object.createMappedChar(glyphName)
|
|
glyph = RGlyph(self._object[glyphName])
|
|
glyph.setParent(self)
|
|
return glyph
|
|
|
|
def removeGlyph(self, glyphName):
|
|
self._object.removeGlyph(glyphName)
|
|
|
|
|
|
|
|
|
|
class RGlyph(BaseGlyph):
|
|
"""Fab wrapper for FF Glyph object"""
|
|
def __init__(self, ffGlyph=None):
|
|
if ffGlyph is None:
|
|
raise RoboFabError
|
|
self._object = ffGlyph
|
|
# XX anchors seem to be supported, but in a different way
|
|
# XX so, I will ignore them for now to get something working.
|
|
self.anchors = []
|
|
self.lib = {}
|
|
|
|
def naked(self):
|
|
return self._object
|
|
|
|
def setChanged(self):
|
|
self._object.changed()
|
|
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# attributes
|
|
|
|
def _get_name(self):
|
|
return self._object.glyphname
|
|
def _set_name(self, value):
|
|
self._object.glyphname = value
|
|
name = property(_get_name, _set_name, doc="name")
|
|
|
|
def _get_note(self):
|
|
return self._object.comment
|
|
def _set_note(self, note):
|
|
self._object.comment = note
|
|
note = property(_get_note, _set_note, doc="note")
|
|
|
|
def _get_width(self):
|
|
return self._object.width
|
|
def _set_width(self, width):
|
|
self._object.width = width
|
|
width = property(_get_width, _set_width, doc="width")
|
|
|
|
def _get_leftMargin(self):
|
|
return self._object.left_side_bearing
|
|
def _set_leftMargin(self, leftMargin):
|
|
self._object.left_side_bearing = leftMargin
|
|
leftMargin = property(_get_leftMargin, _set_leftMargin, doc="leftMargin")
|
|
|
|
def _get_rightMargin(self):
|
|
return self._object.right_side_bearing
|
|
def _set_rightMargin(self, rightMargin):
|
|
self._object.right_side_bearing = rightMargin
|
|
rightMargin = property(_get_rightMargin, _set_rightMargin, doc="rightMargin")
|
|
|
|
def _get_unicodes(self):
|
|
return [self._object.unicode]
|
|
def _set_unicodes(self, unicodes):
|
|
assert len(unicodes)==1
|
|
self._object.unicode = unicodes[0]
|
|
unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes")
|
|
|
|
def _get_unicode(self):
|
|
return self._object.unicode
|
|
def _set_unicode(self, unicode):
|
|
self._object.unicode = unicode
|
|
unicode = property(_get_unicode, _set_unicode, doc="unicode")
|
|
|
|
def _get_box(self):
|
|
bounds = self._object.boundingBox()
|
|
return bounds
|
|
box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)")
|
|
|
|
def _get_mark(self):
|
|
"""color of the glyph box in the font view. This accepts a 6 hex digit number.
|
|
|
|
XXX the FL implementation accepts a
|
|
"""
|
|
import colorsys
|
|
r = (self._object.color&0xff0000)>>16
|
|
g = (self._object.color&0xff00)>>8
|
|
g = (self._object.color&0xff)>>4
|
|
return colorsys.rgb_to_hsv( r, g, b)[0]
|
|
|
|
def _set_mark(self, markColor=-1):
|
|
import colorsys
|
|
self._object.color = colorSys.hsv_to_rgb(markColor, 1, 1)
|
|
|
|
mark = property(_get_mark, _set_mark, doc="the color of the glyph box in the font view")
|
|
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# pen, drawing
|
|
|
|
def getPen(self):
|
|
return self._object.glyphPen()
|
|
|
|
def __getPointPen(self):
|
|
"""Return a point pen.
|
|
|
|
Note: FontForge doesn't support segment pen, so return an adapter.
|
|
"""
|
|
from robofab.pens.adapterPens import PointToSegmentPen
|
|
segmentPen = self._object.glyphPen()
|
|
return PointToSegmentPen(segmentPen)
|
|
|
|
def getPointPen(self):
|
|
from robofab.pens.rfUFOPen import RFUFOPointPen
|
|
pen = RFUFOPointPen(self)
|
|
#print "getPointPen", pen, pen.__class__, dir(pen)
|
|
return pen
|
|
|
|
def draw(self, pen):
|
|
"""draw
|
|
|
|
"""
|
|
self._object.draw(pen)
|
|
pen = None
|
|
|
|
def drawPoints(self, pen):
|
|
"""drawPoints
|
|
|
|
Note: FontForge implements glyph.draw, but not glyph.drawPoints.
|
|
"""
|
|
from robofab.pens.adapterPens import PointToSegmentPen, SegmentToPointPen
|
|
adapter = SegmentToPointPen(pen)
|
|
self._object.draw(adapter)
|
|
pen = None
|
|
|
|
def appendGlyph(self, other):
|
|
pen = self.getPen()
|
|
other.draw(pen)
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# glyphmath
|
|
|
|
def round(self):
|
|
self._object.round()
|
|
|
|
def _getMathDestination(self):
|
|
from robofab.objects.objectsRF import RGlyph as _RGlyph
|
|
return _RGlyph()
|
|
|
|
def _mathCopy(self):
|
|
# copy self without contour, component and anchor data
|
|
glyph = self._getMathDestination()
|
|
glyph.name = self.name
|
|
glyph.unicodes = list(self.unicodes)
|
|
glyph.width = self.width
|
|
glyph.note = self.note
|
|
glyph.lib = dict(self.lib)
|
|
return glyph
|
|
|
|
def __mul__(self, factor):
|
|
if __DEBUG__:
|
|
print "glyphmath mul", factor
|
|
return self.copy() *factor
|
|
|
|
__rmul__ = __mul__
|
|
|
|
def __sub__(self, other):
|
|
if __DEBUG__:
|
|
print "glyphmath sub", other, other.__class__
|
|
return self.copy() - other.copy()
|
|
|
|
def __add__(self, other):
|
|
if __DEBUG__:
|
|
print "glyphmath add", other, other.__class__
|
|
return self.copy() + other.copy()
|
|
|
|
def getParent(self):
|
|
return self
|
|
|
|
def copy(self, aParent=None):
|
|
"""Make a copy of this glyph.
|
|
Note: the copy is not a duplicate fontlab glyph, but
|
|
a RF RGlyph with the same outlines. The new glyph is
|
|
not part of the fontlab font in any way. Use font.appendGlyph(glyph)
|
|
to get it in a FontLab glyph again."""
|
|
from robofab.objects.objectsRF import RGlyph as _RGlyph
|
|
newGlyph = _RGlyph()
|
|
newGlyph.appendGlyph(self)
|
|
for attr in GLYPH_COPY_ATTRS:
|
|
value = getattr(self, attr)
|
|
setattr(newGlyph, attr, value)
|
|
parent = self.getParent()
|
|
if aParent is not None:
|
|
newGlyph.setParent(aParent)
|
|
elif self.getParent() is not None:
|
|
newGlyph.setParent(self.getParent())
|
|
return newGlyph
|
|
|
|
def _get_contours(self):
|
|
# find the contour data and wrap it
|
|
|
|
"""get the contours in this glyph"""
|
|
contours = []
|
|
for n in range(len(self._object.foreground)):
|
|
item = self._object.foreground[n]
|
|
rc = RContour(item, n)
|
|
rc.setParent(self)
|
|
contours.append(rc)
|
|
#print contours
|
|
return contours
|
|
|
|
contours = property(_get_contours, doc="allow for iteration through glyph.contours")
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# transformations
|
|
|
|
def move(self, (x, y)):
|
|
matrix = psMat.translate((x,y))
|
|
self._object.transform(matrix)
|
|
|
|
def scale(self, (x, y), center=(0,0)):
|
|
matrix = psMat.scale(x,y)
|
|
self._object.transform(matrix)
|
|
|
|
def transform(self, matrix):
|
|
self._object.transform(matrix)
|
|
|
|
def rotate(self, angle, offset=None):
|
|
matrix = psMat.rotate(angle)
|
|
self._object.transform(matrix)
|
|
|
|
def skew(self, angle, offset=None):
|
|
matrix = psMat.skew(angle)
|
|
self._object.transform(matrix)
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# components stuff
|
|
|
|
def decompose(self):
|
|
self._object.unlinkRef()
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# unicode stuff
|
|
|
|
def autoUnicodes(self):
|
|
if __DEBUG__:
|
|
print "objectsFF.RGlyph.autoUnicodes() not implemented yet."
|
|
|
|
# -----------------------------------------------------------------
|
|
#
|
|
# contour stuff
|
|
|
|
def removeOverlap(self):
|
|
self._object.removeOverlap()
|
|
|
|
def correctDirection(self, trueType=False):
|
|
# no option for trueType, really.
|
|
self._object.correctDirection()
|
|
|
|
def clear(self):
|
|
self._object.clear()
|
|
|
|
def __getitem__(self, index):
|
|
return self.contours[index]
|
|
|
|
|
|
class RContour(BaseContour):
|
|
def __init__(self, contour, index=None):
|
|
self._object = contour
|
|
self.index = index
|
|
|
|
def _get_points(self):
|
|
pts = []
|
|
for pt in self._object:
|
|
wpt = RPoint(pt)
|
|
wpt.setParent(self)
|
|
pts.append(wpt)
|
|
return pts
|
|
|
|
points = property(_get_points, doc="get contour points")
|
|
|
|
def _get_box(self):
|
|
return self._object.boundingBox()
|
|
|
|
box = property(_get_box, doc="get contour bounding box")
|
|
|
|
def __len__(self):
|
|
return len(self._object)
|
|
|
|
def __getitem__(self, index):
|
|
return self.points[index]
|
|
|
|
|
|
|
|
class RPoint(BasePoint):
|
|
|
|
def __init__(self, pointObject):
|
|
self._object = pointObject
|
|
|
|
def _get_x(self):
|
|
return self._object.x
|
|
|
|
def _set_x(self, value):
|
|
self._object.x = value
|
|
|
|
x = property(_get_x, _set_x, doc="")
|
|
|
|
def _get_y(self):
|
|
return self._object.y
|
|
|
|
def _set_y(self, value):
|
|
self._object.y = value
|
|
|
|
y = property(_get_y, _set_y, doc="")
|
|
|
|
def _get_type(self):
|
|
if self._object.on_curve == 0:
|
|
return OFFCURVE
|
|
|
|
# XXX not always curve
|
|
return CURVE
|
|
|
|
def _set_type(self, value):
|
|
self._type = value
|
|
self._hasChanged()
|
|
|
|
type = property(_get_type, _set_type, doc="")
|
|
|
|
def __repr__(self):
|
|
font = "unnamed_font"
|
|
glyph = "unnamed_glyph"
|
|
contourIndex = "unknown_contour"
|
|
contourParent = self.getParent()
|
|
if contourParent is not None:
|
|
try:
|
|
contourIndex = `contourParent.index`
|
|
except AttributeError: pass
|
|
glyphParent = contourParent.getParent()
|
|
if glyphParent is not None:
|
|
try:
|
|
glyph = glyphParent.name
|
|
except AttributeError: pass
|
|
fontParent = glyphParent.getParent()
|
|
if fontParent is not None:
|
|
try:
|
|
font = fontParent.info.fullName
|
|
except AttributeError: pass
|
|
return "<RPoint for %s.%s[%s]>"%(font, glyph, contourIndex)
|
|
|
|
|
|
class RInfo(BaseInfo):
|
|
def __init__(self, font):
|
|
BaseInfo.__init__(self)
|
|
self._object = font
|
|
|
|
def _get_familyName(self):
|
|
return self._object.familyname
|
|
def _set_familyName(self, value):
|
|
self._object.familyname = value
|
|
familyName = property(_get_familyName, _set_familyName, doc="familyname")
|
|
|
|
def _get_fondName(self):
|
|
return self._object.fondname
|
|
def _set_fondName(self, value):
|
|
self._object.fondname = value
|
|
fondName = property(_get_fondName, _set_fondName, doc="fondname")
|
|
|
|
def _get_fontName(self):
|
|
return self._object.fontname
|
|
def _set_fontName(self, value):
|
|
self._object.fontname = value
|
|
fontName = property(_get_fontName, _set_fontName, doc="fontname")
|
|
|
|
# styleName doesn't have a specific field, FF has a whole sfnt dict.
|
|
# implement fullName because a repr depends on it
|
|
def _get_fullName(self):
|
|
return self._object.fullname
|
|
def _set_fullName(self, value):
|
|
self._object.fullname = value
|
|
fullName = property(_get_fullName, _set_fullName, doc="fullname")
|
|
|
|
def _get_unitsPerEm(self):
|
|
return self._object.em
|
|
def _set_unitsPerEm(self, value):
|
|
self._object.em = value
|
|
unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value")
|
|
|
|
def _get_ascender(self):
|
|
return self._object.ascent
|
|
def _set_ascender(self, value):
|
|
self._object.ascent = value
|
|
ascender = property(_get_ascender, _set_ascender, doc="ascender value")
|
|
|
|
def _get_descender(self):
|
|
return -self._object.descent
|
|
def _set_descender(self, value):
|
|
self._object.descent = -value
|
|
descender = property(_get_descender, _set_descender, doc="descender value")
|
|
|
|
def _get_copyright(self):
|
|
return self._object.copyright
|
|
def _set_copyright(self, value):
|
|
self._object.copyright = value
|
|
copyright = property(_get_copyright, _set_copyright, doc="copyright")
|
|
|
|
|
|
|
|
class RKerning(BaseKerning):
|
|
|
|
""" Object representing the kerning.
|
|
This is going to need some thinking about.
|
|
"""
|
|
|
|
|
|
__all__ = [ 'RFont', 'RGlyph', 'RContour', 'RPoint', 'RInfo',
|
|
'OpenFont', 'CurrentFont', 'NewFont', 'CurrentFont'
|
|
]
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import os
|
|
from robofab.objects.objectsRF import RFont as _RFont
|
|
from sets import Set
|
|
|
|
def dumpFontForgeAPI(testFontPath, printModule=False,
|
|
printFont=False, printGlyph=False,
|
|
printLayer=False, printContour=False, printPoint=False):
|
|
def printAPI(item, name):
|
|
print
|
|
print "-"*80
|
|
print "API of", item
|
|
names = dir(item)
|
|
names.sort()
|
|
print
|
|
|
|
if printAPI:
|
|
for n in names:
|
|
print
|
|
print "%s.%s"%(name, n)
|
|
try:
|
|
print getattr(item, n).__doc__
|
|
except:
|
|
print "# error showing", n
|
|
# module
|
|
if printModule:
|
|
print "module file:", fontforge.__file__
|
|
print "version:", fontforge.version()
|
|
print "module doc:", fontforge.__doc__
|
|
print "has User Interface:", fontforge.hasUserInterface()
|
|
print "has Spiro:", fontforge.hasSpiro()
|
|
printAPI(fontforge, "fontforge")
|
|
|
|
# font
|
|
fontObj = fontforge.open(testFontPath)
|
|
if printFont:
|
|
printAPI(fontObj, "font")
|
|
|
|
# glyph
|
|
glyphObj = fontObj["A"]
|
|
if printGlyph:
|
|
printAPI(glyphObj, "glyph")
|
|
|
|
# layer
|
|
layerObj = glyphObj.foreground
|
|
if printLayer:
|
|
printAPI(layerObj, "layer")
|
|
|
|
# contour
|
|
contourObj = layerObj[0]
|
|
if printContour:
|
|
printAPI(contourObj, "contour")
|
|
|
|
# point
|
|
if printPoint:
|
|
pointObj = contourObj[0]
|
|
printAPI(pointObj, "point")
|
|
|
|
|
|
# other objects
|
|
penObj = glyphObj.glyphPen()
|
|
printAPI(penObj, "glyphPen")
|
|
|
|
# use your own paths here.
|
|
demoRoot = "/Users/erik/Develop/Mess/FontForge/objectsFF_work/"
|
|
UFOPath = os.path.join(demoRoot, "Test.ufo")
|
|
SFDPath = os.path.join(demoRoot, "Test_realSFD2.sfd")
|
|
|
|
#dumpFontForgeAPI(UFOPath, printPoint=True)
|
|
|
|
# should return None
|
|
CurrentFont()
|
|
|
|
def compareAttr(obj1, obj2, attrName, isMethod=False):
|
|
if isMethod:
|
|
a = getattr(obj1, attrName)()
|
|
b = getattr(obj2, attrName)()
|
|
else:
|
|
a = getattr(obj1, attrName)
|
|
b = getattr(obj2, attrName)
|
|
if a == b and a is not None and b is not None:
|
|
print "\tattr %s ok"%attrName, a
|
|
return True
|
|
else:
|
|
print "\t?\t%s error:"%attrName, "%s:"%obj1.__class__, a, "%s:"%obj2.__class__, b
|
|
return False
|
|
|
|
f = OpenFont(UFOPath)
|
|
#f = OpenFont(SFDPath)
|
|
ref = _RFont(UFOPath)
|
|
|
|
if False:
|
|
print
|
|
print "test font attributes"
|
|
compareAttr(f, ref, "path")
|
|
|
|
a = Set(f.keys())
|
|
b = Set(ref.keys())
|
|
print "glyphs in ref, not in f", b.difference(a)
|
|
print "glyphs in f, not in ref", a.difference(b)
|
|
|
|
print "A" in f, "A" in ref
|
|
print f.has_key("A"), ref.has_key("A")
|
|
|
|
print
|
|
print "test font info attributes"
|
|
compareAttr(f.info, ref.info, "ascender")
|
|
compareAttr(f.info, ref.info, "descender")
|
|
compareAttr(f.info, ref.info, "unitsPerEm")
|
|
compareAttr(f.info, ref.info, "copyright")
|
|
compareAttr(f.info, ref.info, "fullName")
|
|
compareAttr(f.info, ref.info, "familyName")
|
|
compareAttr(f.info, ref.info, "fondName")
|
|
compareAttr(f.info, ref.info, "fontName")
|
|
|
|
# crash
|
|
f.save()
|
|
|
|
otfOutputPath = os.path.join(demoRoot, "test_ouput.otf")
|
|
ufoOutputPath = os.path.join(demoRoot, "test_ouput.ufo")
|
|
# generate without path, should end up in the source folder
|
|
|
|
f['A'].removeOverlap()
|
|
f.generate('otfcff') #, otfPath)
|
|
f.generate('pctype1') #, otfPath)
|
|
|
|
# generate with path. Type is taken from the extension.
|
|
f.generate('otfcff', otfOutputPath) #, otfPath)
|
|
f.generate(None, ufoOutputPath) #, otfPath)
|
|
|
|
featurePath = os.path.join(demoRoot, "testFeatureOutput.fea")
|
|
f.naked().generateFeatureFile(featurePath)
|
|
|
|
if False:
|
|
# new glyphs
|
|
# unencoded
|
|
print "new glyph: unencoded", f.newGlyph("test_unencoded_glyph")
|
|
# encoded
|
|
print "new glyph: encoded", f.newGlyph("Adieresis")
|
|
# existing
|
|
print "new glyph: existing", f.newGlyph("K")
|
|
|
|
print
|
|
print "test glyph attributes"
|
|
compareAttr(f['A'], ref['A'], "width")
|
|
compareAttr(f['A'], ref['A'], "unicode")
|
|
compareAttr(f['A'], ref['A'], "name")
|
|
compareAttr(f['A'], ref['A'], "box")
|
|
compareAttr(f['A'], ref['A'], "leftMargin")
|
|
compareAttr(f['A'], ref['A'], "rightMargin")
|
|
|
|
if False:
|
|
print
|
|
print "comparing glyph digests"
|
|
failed = []
|
|
for n in f.keys():
|
|
g1 = f[n]
|
|
#g1.round()
|
|
g2 = ref[n]
|
|
#g2.round()
|
|
d1 = g1._getDigest()
|
|
d2 = g2._getDigest()
|
|
if d1 != d2:
|
|
failed.append(n)
|
|
#print "f: ", d1
|
|
#print "ref: ", d2
|
|
print "digest failed for %s"%". ".join(failed)
|
|
|
|
g3 = f['A'] *.333
|
|
print g3
|
|
print g3._getDigest()
|
|
f.save()
|
|
|
|
if False:
|
|
print
|
|
print "test contour attributes"
|
|
compareAttr(f['A'].contours[0], ref['A'].contours[0], "index")
|
|
|
|
#for c in f['A'].contours:
|
|
# for p in c.points:
|
|
# print p, p.type
|
|
|
|
# test with a glyph with just 1 contour so we can be sure we're comparing the same thing
|
|
compareAttr(f['C'].contours[0], ref['C'].contours[0], "box")
|
|
compareAttr(f['C'].contours[0], ref['C'].contours[0], "__len__", isMethod=True)
|
|
|
|
ptf = f['C'].contours[0].points[0]
|
|
ptref = ref['C'].contours[0].points[0]
|
|
print "x, y", (ptf.x, ptf.y) == (ptref.x, ptref.y), (ptref.x, ptref.y)
|
|
print 'type', ptf.type, ptref.type
|
|
|
|
print "point inside", f['A'].pointInside((50,10)), ref['A'].pointInside((50,10))
|
|
|
|
|
|
print ref.kerning.keys()
|
|
|
|
class GlyphLookupWrapper(dict):
|
|
"""A wrapper for the lookups / subtable data in a FF glyph.
|
|
A lot of data is stored there, so it helps to have something to sort things out.
|
|
"""
|
|
def __init__(self, ffGlyph):
|
|
self._object = ffGlyph
|
|
self.refresh()
|
|
|
|
def __repr__(self):
|
|
return "<GlyphLookupWrapper for %s, %d keys>"%(self._object.glyphname, len(self))
|
|
|
|
def refresh(self):
|
|
"""Pick some of the values apart."""
|
|
lookups = self._object.getPosSub('*')
|
|
for t in lookups:
|
|
print 'lookup', t
|
|
lookupName = t[0]
|
|
lookupType = t[1]
|
|
if not lookupName in self:
|
|
self[lookupName] = []
|
|
self[lookupName].append(t[1:])
|
|
|
|
def getKerning(self):
|
|
"""Get a regular kerning dict for this glyph"""
|
|
d = {}
|
|
left = self._object.glyphname
|
|
for name in self.keys():
|
|
for item in self[name]:
|
|
print 'item', item
|
|
if item[0]!="Pair":
|
|
continue
|
|
#print 'next glyph:', item[1]
|
|
#print 'first glyph x Pos:', item[2]
|
|
#print 'first glyph y Pos:', item[3]
|
|
#print 'first glyph h Adv:', item[4]
|
|
#print 'first glyph v Adv:', item[5]
|
|
|
|
#print 'second glyph x Pos:', item[6]
|
|
#print 'second glyph y Pos:', item[7]
|
|
#print 'second glyph h Adv:', item[8]
|
|
#print 'second glyph v Adv:', item[9]
|
|
right = item[1]
|
|
d[(left, right)] = item[4]
|
|
return d
|
|
|
|
def setKerning(self, kernDict):
|
|
"""Set the values of a regular kerning dict to the lookups in a FF glyph."""
|
|
for left, right in kernDict.keys():
|
|
if left != self._object.glyphname:
|
|
# should we filter the dict before it gets here?
|
|
# easier just to filter it here.
|
|
continue
|
|
|
|
|
|
|
|
# lets try to find the kerning
|
|
A = f['A'].naked()
|
|
positionTypes = [ "Position", "Pair", "Substitution", "AltSubs", "MultSubs","Ligature"]
|
|
print A.getPosSub('*')
|
|
#for t in A.getPosSub('*'):
|
|
# print 'lookup subtable name:', t[0]
|
|
# print 'positioning type:', t[1]
|
|
# if t[1]in positionTypes:
|
|
# print 'next glyph:', t[2]
|
|
# print 'first glyph x Pos:', t[3]
|
|
# print 'first glyph y Pos:', t[4]
|
|
# print 'first glyph h Adv:', t[5]
|
|
# print 'first glyph v Adv:', t[6]
|
|
|
|
# print 'second glyph x Pos:', t[7]
|
|
# print 'second glyph y Pos:', t[8]
|
|
# print 'second glyph h Adv:', t[9]
|
|
# print 'second glyph v Adv:', t[10]
|
|
|
|
gw = GlyphLookupWrapper(A)
|
|
print gw
|
|
print gw.keys()
|
|
print gw.getKerning()
|
|
|
|
name = "'kern' Horizontal Kerning in Latin lookup 0 subtable"
|
|
item = (name, 'quoteright', 0, 0, -200, 0, 0, 0, 0, 0)
|
|
|
|
A.removePosSub(name)
|
|
apply(A.addPosSub, item)
|
|
|
|
|
|
print "after", A.getPosSub('*')
|
|
|
|
fn = f.naked()
|
|
|
|
name = "'kern' Horizontal Kerning in Latin lookup 0"
|
|
|
|
|
|
print "before removing stuff", fn.gpos_lookups
|
|
print "removing stuff", fn.removeLookup(name)
|
|
print "after removing stuff", fn.gpos_lookups
|
|
|
|
flags = ()
|
|
feature_script_lang = (("kern",(("latn",("dflt")),)),)
|
|
print fn.addLookup('kern', 'gpos_pair', flags, feature_script_lang)
|
|
print fn.addLookupSubtable('kern', 'myKerning')
|
|
|
|
|
|
print fn.gpos_lookups
|
|
A.addPosSub('myKerning', 'A', 0, 0, -400, 0, 0, 0, 0, 0)
|
|
A.addPosSub('myKerning', 'B', 0, 0, 200, 0, 0, 0, 0, 0)
|
|
A.addPosSub('myKerning', 'C', 0, 0, 10, 0, 0, 0, 0, 0)
|
|
A.addPosSub('myKerning', 'A', 0, 0, 77, 0, 0, 0, 0, 0)
|
|
|
|
|
|
gw = GlyphLookupWrapper(A)
|
|
print gw
|
|
print gw.keys()
|
|
print gw.getKerning()
|
|
|