2008-01-07 17:40:34 +00:00
""" UFO implementation for the objects as used by FontLab 4.5 and higher """
from FL import *
2009-03-22 11:09:56 +00:00
from robofab . tools . toolsFL import GlyphIndexTable , NewGlyph
2008-01-07 17:40:34 +00:00
from robofab . objects . objectsBase import BaseFont , BaseGlyph , BaseContour , BaseSegment , \
2009-02-28 15:47:24 +00:00
BasePoint , BaseBPoint , BaseAnchor , BaseGuide , BaseComponent , BaseKerning , BaseInfo , BaseFeatures , BaseGroups , BaseLib , \
2008-01-07 17:40:34 +00:00
roundPt , addPt , _box , \
MOVE , LINE , CORNER , CURVE , QCURVE , OFFCURVE , \
2008-02-21 17:37:39 +00:00
relativeBCPIn , relativeBCPOut , absoluteBCPIn , absoluteBCPOut , \
2008-02-25 11:35:12 +00:00
BasePostScriptFontHintValues , postScriptHintDataLibKey , BasePostScriptGlyphHintValues
2008-01-07 17:40:34 +00:00
from fontTools . misc import arrayTools
2009-04-14 13:41:04 +00:00
from robofab . pens . flPen import FLPointPen , FLPointContourPen
2008-01-07 17:40:34 +00:00
from robofab import RoboFabError
import os
from robofab . plistlib import Data , Dict , readPlist , writePlist
from StringIO import StringIO
2009-02-28 15:47:24 +00:00
from robofab import ufoLib
from warnings import warn
import datetime
from robofab . tools . fontlabFeatureSplitter import splitFeaturesForFontLab
try :
set
except NameError :
from sets import Set as set
2008-01-07 17:40:34 +00:00
# local encoding
2008-02-02 12:40:17 +00:00
if os . name in [ " mac " , " posix " ] :
2008-01-07 17:40:34 +00:00
LOCAL_ENCODING = " macroman "
else :
LOCAL_ENCODING = " latin-1 "
2009-02-28 15:47:24 +00:00
_IN_UFO_EXPORT = False
2008-01-07 17:40:34 +00:00
# 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 " ,
]
# Generate Types
PC_TYPE1 = ' pctype1 '
PC_MM = ' pcmm '
PC_TYPE1_ASCII = ' pctype1ascii '
PC_MM_ASCII = ' pcmmascii '
UNIX_ASCII = ' unixascii '
MAC_TYPE1 = ' mactype1 '
OTF_CFF = ' otfcff '
OTF_TT = ' otfttf '
MAC_TT = ' macttf '
MAC_TT_DFONT = ' macttdfont '
# doc for these functions taken from: http://dev.fontlab.net/flpydoc/
# internal name (FontLab name, extension)
_flGenerateTypes = { PC_TYPE1 : ( ftTYPE1 , ' pfb ' ) , # PC Type 1 font (binary/PFB)
PC_MM : ( ftTYPE1_MM , ' mm ' ) , # PC MultipleMaster font (PFB)
PC_TYPE1_ASCII : ( ftTYPE1ASCII , ' pfa ' ) , # PC Type 1 font (ASCII/PFA)
PC_MM_ASCII : ( ftTYPE1ASCII_MM , ' mm ' ) , # PC MultipleMaster font (ASCII/PFA)
UNIX_ASCII : ( ftTYPE1ASCII , ' pfa ' ) , # UNIX ASCII font (ASCII/PFA)
OTF_TT : ( ftTRUETYPE , ' ttf ' ) , # PC TrueType/TT OpenType font (TTF)
OTF_CFF : ( ftOPENTYPE , ' otf ' ) , # PS OpenType (CFF-based) font (OTF)
MAC_TYPE1 : ( ftMACTYPE1 , ' suit ' ) , # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM)
MAC_TT : ( ftMACTRUETYPE , ' ttf ' ) , # Mac TrueType font (generates suitcase)
MAC_TT_DFONT : ( ftMACTRUETYPE_DFONT , ' dfont ' ) , # Mac TrueType font (generates suitcase with resources in data fork)
}
## FL Hint stuff
# this should not be referenced outside of this module
# since we may be changing the way this works in the future.
2008-02-21 17:37:39 +00:00
"""
2008-02-25 11:35:12 +00:00
FontLab implementation of psHints objects
2008-02-21 17:37:39 +00:00
Most of the FL methods relating to ps hints return a list of 16 items .
These values are for the 16 corners of a 4 axis multiple master .
The odd thing is that even single masters get these 16 values .
RoboFab doesn ' t access the MM masters, so by default, the psHints
object only works with the first element . If you want to access the other
values in the list , give a value between 0 and 15 for impliedMasterIndex
when creating the object .
From the FontLab docs :
http : / / dev . fontlab . net / flpydoc /
blue_fuzz
blue_scale
blue_shift
blue_values_num ( integer ) - number of defined blue values
blue_values [ integer [ integer ] ] - two - dimentional array of BlueValues
master index is top - level index
other_blues_num ( integer ) - number of defined OtherBlues values
other_blues [ integer [ integer ] ] - two - dimentional array of OtherBlues
master index is top - level index
family_blues_num ( integer ) - number of FamilyBlues records
family_blues [ integer [ integer ] ] - two - dimentional array of FamilyBlues
master index is top - level index
family_other_blues_num ( integer ) - number of FamilyOtherBlues records
family_other_blues [ integer [ integer ] ] - two - dimentional array of FamilyOtherBlues
master index is top - level index
force_bold [ integer ] - list of Force Bold values , one for
each master
stem_snap_h_num ( integer )
stem_snap_h
stem_snap_v_num ( integer )
stem_snap_v
"""
class PostScriptFontHintValues ( BasePostScriptFontHintValues ) :
""" Wrapper for font-level PostScript hinting information for FontLab.
Blues values , stem values .
"""
def __init__ ( self , font = None , impliedMasterIndex = 0 ) :
self . _object = font . naked ( )
self . _masterIndex = impliedMasterIndex
2008-02-23 19:40:07 +00:00
def copy ( self ) :
from robofab . objects . objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues
return _PostScriptFontHintValues ( data = self . asDict ( ) )
2008-02-21 17:37:39 +00:00
2008-02-25 11:35:12 +00:00
class PostScriptGlyphHintValues ( BasePostScriptGlyphHintValues ) :
""" Wrapper for glyph-level PostScript hinting information for FontLab.
2008-07-14 20:14:50 +00:00
vStems , hStems .
2008-02-25 11:35:12 +00:00
"""
def __init__ ( self , glyph = None ) :
self . _object = glyph . naked ( )
def copy ( self ) :
from robofab . objects . objectsRF import PostScriptGlyphHintValues as _PostScriptGlyphHintValues
return _PostScriptGlyphHintValues ( data = self . asDict ( ) )
def _hintObjectsToList ( self , item ) :
data = [ ]
done = [ ]
for hint in item :
p = ( hint . position , hint . width )
if p in done :
continue
data . append ( p )
done . append ( p )
data . sort ( )
return data
def _listToHintObjects ( self , item ) :
hints = [ ]
done = [ ]
for pos , width in item :
if ( pos , width ) in done :
# we don't want to set duplicates
continue
hints . append ( Hint ( pos , width ) )
done . append ( ( pos , width ) )
return hints
def _getVHints ( self ) :
return self . _hintObjectsToList ( self . _object . vhints )
def _setVHints ( self , values ) :
# 1 = horizontal hints and links,
# 2 = vertical hints and links
# 3 = all hints and links
self . _object . RemoveHints ( 2 )
2008-02-25 12:51:13 +00:00
if values is None :
# just clearing it then
return
2008-02-25 11:35:12 +00:00
values . sort ( )
for hint in self . _listToHintObjects ( values ) :
self . _object . vhints . append ( hint )
def _getHHints ( self ) :
return self . _hintObjectsToList ( self . _object . hhints )
def _setHHints ( self , values ) :
# 1 = horizontal hints and links,
# 2 = vertical hints and links
# 3 = all hints and links
self . _object . RemoveHints ( 1 )
2008-02-25 12:51:13 +00:00
if values is None :
# just clearing it then
return
2008-02-25 11:35:12 +00:00
values . sort ( )
for hint in self . _listToHintObjects ( values ) :
self . _object . hhints . append ( hint )
vHints = property ( _getVHints , _setVHints , doc = " postscript hints: vertical hint zones " )
hHints = property ( _getHHints , _setHHints , doc = " postscript hints: horizontal hint zones " )
2008-01-07 17:40:34 +00:00
def _glyphHintsToDict ( glyph ) :
data = { }
##
## horizontal and vertical hints
##
# glyph.hhints and glyph.vhints returns a list of Hint objects.
# Hint objects have position and width attributes.
2008-02-25 11:35:12 +00:00
data [ ' hHints ' ] = [ ]
2008-01-07 17:40:34 +00:00
for index in xrange ( len ( glyph . hhints ) ) :
hint = glyph . hhints [ index ]
2008-02-25 11:35:12 +00:00
data [ ' hHints ' ] . append ( ( hint . position , hint . width ) )
if not data [ ' hHints ' ] :
del data [ ' hHints ' ]
data [ ' vHints ' ] = [ ]
2008-01-07 17:40:34 +00:00
for index in xrange ( len ( glyph . vhints ) ) :
hint = glyph . vhints [ index ]
2008-02-25 11:35:12 +00:00
data [ ' vHints ' ] . append ( ( hint . position , hint . width ) )
if not data [ ' vHints ' ] :
del data [ ' vHints ' ]
2008-01-07 17:40:34 +00:00
##
## horizontal and vertical links
##
# glyph.hlinks and glyph.vlinks returns a list of Link objects.
# Link objects have node1 and node2 attributes.
2008-02-25 11:35:12 +00:00
data [ ' hLinks ' ] = [ ]
2008-01-07 17:40:34 +00:00
for index in xrange ( len ( glyph . hlinks ) ) :
link = glyph . hlinks [ index ]
d = { ' node1 ' : link . node1 ,
' node2 ' : link . node2 ,
}
2008-02-25 11:35:12 +00:00
data [ ' hLinks ' ] . append ( d )
if not data [ ' hLinks ' ] :
del data [ ' hLinks ' ]
data [ ' vLinks ' ] = [ ]
2008-01-07 17:40:34 +00:00
for index in xrange ( len ( glyph . vlinks ) ) :
link = glyph . vlinks [ index ]
d = { ' node1 ' : link . node1 ,
' node2 ' : link . node2 ,
}
2008-02-25 11:35:12 +00:00
data [ ' vLinks ' ] . append ( d )
if not data [ ' vLinks ' ] :
del data [ ' vLinks ' ]
2008-01-07 17:40:34 +00:00
##
## replacement table
##
# glyph.replace_table returns a list of Replace objects.
# Replace objects have type and index attributes.
2008-02-25 11:35:12 +00:00
data [ ' replaceTable ' ] = [ ]
2008-01-07 17:40:34 +00:00
for index in xrange ( len ( glyph . replace_table ) ) :
replace = glyph . replace_table [ index ]
d = { ' type ' : replace . type ,
' index ' : replace . index ,
}
2008-02-25 11:35:12 +00:00
data [ ' replaceTable ' ] . append ( d )
if not data [ ' replaceTable ' ] :
del data [ ' replaceTable ' ]
2008-01-07 17:40:34 +00:00
# XXX
# need to support glyph.instructions and glyph.hdmx?
# they are not documented very well.
return data
def _dictHintsToGlyph ( glyph , aDict ) :
# clear existing hints first
# RemoveHints requires an "integer mode" argument
# but it is not documented. from some simple experiments
# i deduced that
# 1 = horizontal hints and links,
# 2 = vertical hints and links
# 3 = all hints and links
glyph . RemoveHints ( 3 )
##
## horizontal and vertical hints
##
2008-02-25 11:35:12 +00:00
if aDict . has_key ( ' hHints ' ) :
for d in aDict [ ' hHints ' ] :
2008-02-25 12:51:13 +00:00
glyph . hhints . append ( Hint ( d [ 0 ] , d [ 1 ] ) )
2008-02-25 11:35:12 +00:00
if aDict . has_key ( ' vHints ' ) :
for d in aDict [ ' vHints ' ] :
2008-02-25 12:51:13 +00:00
glyph . vhints . append ( Hint ( d [ 0 ] , d [ 1 ] ) )
2008-01-07 17:40:34 +00:00
##
## horizontal and vertical links
##
2008-02-25 11:35:12 +00:00
if aDict . has_key ( ' hLinks ' ) :
for d in aDict [ ' hLinks ' ] :
2008-01-07 17:40:34 +00:00
glyph . hlinks . append ( Link ( d [ ' node1 ' ] , d [ ' node2 ' ] ) )
2008-02-25 11:35:12 +00:00
if aDict . has_key ( ' vLinks ' ) :
for d in aDict [ ' vLinks ' ] :
2008-01-07 17:40:34 +00:00
glyph . vlinks . append ( Link ( d [ ' node1 ' ] , d [ ' node2 ' ] ) )
##
## replacement table
##
2008-02-25 11:35:12 +00:00
if aDict . has_key ( ' replaceTable ' ) :
for d in aDict [ ' replaceTable ' ] :
2008-01-07 17:40:34 +00:00
glyph . replace_table . append ( Replace ( d [ ' type ' ] , d [ ' index ' ] ) )
# FL Node Types
flMOVE = 17
flLINE = 1
flCURVE = 35
flOFFCURVE = 65
flSHARP = 0
# I have no idea what the difference between
# "smooth" and "fixed" is, but booth values
# are returned by FL
flSMOOTH = 4096
flFIXED = 12288
_flToRFSegmentDict = { flMOVE : MOVE ,
flLINE : LINE ,
flCURVE : CURVE ,
flOFFCURVE : OFFCURVE
}
_rfToFLSegmentDict = { }
for k , v in _flToRFSegmentDict . items ( ) :
_rfToFLSegmentDict [ v ] = k
def _flToRFSegmentType ( segmentType ) :
return _flToRFSegmentDict [ segmentType ]
def _rfToFLSegmentType ( segmentType ) :
return _rfToFLSegmentDict [ segmentType ]
def _scalePointFromCenter ( ( pointX , pointY ) , ( scaleX , scaleY ) , ( centerX , centerY ) ) :
ogCenter = ( centerX , centerY )
scaledCenter = ( centerX * scaleX , centerY * scaleY )
shiftVal = ( scaledCenter [ 0 ] - ogCenter [ 0 ] , scaledCenter [ 1 ] - ogCenter [ 1 ] )
scaledPointX = ( pointX * scaleX ) - shiftVal [ 0 ]
scaledPointY = ( pointY * scaleY ) - shiftVal [ 1 ]
return ( scaledPointX , scaledPointY )
# Nostalgia code:
def CurrentFont ( ) :
""" Return a RoboFab font object for the currently selected font. """
f = fl . font
if f is not None :
return RFont ( fl . font )
return None
def CurrentGlyph ( ) :
""" Return a RoboFab glyph object for the currently selected glyph. """
currentPath = fl . font . file_name
if fl . glyph is None :
return None
glyphName = fl . glyph . name
currentFont = None
# is this font already loaded as an RFont?
for font in AllFonts ( ) :
# ugh this won't work because AllFonts sees non RFonts as well....
if font . path == currentPath :
currentFont = font
break
xx = currentFont [ glyphName ]
2008-01-07 21:05:52 +00:00
#print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent()
2008-01-07 17:40:34 +00:00
return xx
def OpenFont ( path = None , note = None ) :
""" Open a font from a path. """
if path == None :
from robofab . interface . all . dialogs import GetFile
path = GetFile ( note )
if path :
if path [ - 4 : ] . lower ( ) in [ ' .vfb ' , ' .VFB ' , ' .bak ' , ' .BAK ' ] :
f = Font ( path )
fl . Add ( f )
return RFont ( f )
return None
def NewFont ( familyName = None , styleName = None ) :
""" Make a new font """
from FL import fl , Font
f = Font ( )
fl . Add ( f )
rf = RFont ( f )
2009-02-28 15:47:24 +00:00
if familyName is not None :
rf . info . familyName = familyName
if styleName is not None :
rf . info . styleName = styleName
2008-01-07 17:40:34 +00:00
return rf
2008-01-07 21:05:52 +00:00
def AllFonts ( ) :
""" Return a list of all open fonts. """
fontCount = len ( fl )
all = [ ]
for index in xrange ( fontCount ) :
naked = fl [ index ]
all . append ( RFont ( naked ) )
return all
2009-03-22 11:09:56 +00:00
from robofab . world import CurrentGlyph
def getGlyphFromMask ( g ) :
""" Get a Fab glyph object for the data in the mask layer. """
from robofab . objects . objectsFL import RGlyph as FL_RGlyph
from robofab . objects . objectsRF import RGlyph as RF_RGlyph
n = g . naked ( )
mask = n . mask
fg = FL_RGlyph ( mask )
rf = RF_RGlyph ( )
pen = rf . getPointPen ( )
fg . drawPoints ( pen )
rf . width = g . width # can we get to the mask glyph width without flipping the UI?
return rf
def setMaskToGlyph ( maskGlyph , targetGlyph , clear = True ) :
""" Set the maskGlyph as a mask layer in targetGlyph.
maskGlyph is a FontLab or RoboFab RGlyph , orphaned or not .
targetGlyph is a FontLab RGLyph .
clear is a bool . False : keep the existing mask data , True : clear the existing mask data .
"""
from robofab . objects . objectsFL import RGlyph as FL_RGlyph
from FL import Glyph as FL_NakedGlyph
flGlyph = FL_NakedGlyph ( ) # new, orphaned FL glyph
wrapped = FL_RGlyph ( flGlyph ) # rf wrapper for FL glyph
if not clear :
2009-04-14 13:41:04 +00:00
# copy the existing mask data first
existingMask = getGlyphFromMask ( targetGlyph )
2009-06-26 13:24:44 +00:00
if existingMask is not None :
pen = FLPointContourPen ( existingMask )
existingMask . drawPoints ( pen )
2009-04-14 13:41:04 +00:00
pen = FLPointContourPen ( wrapped )
2009-03-22 11:09:56 +00:00
maskGlyph . drawPoints ( pen ) # draw the data
targetGlyph . naked ( ) . mask = wrapped . naked ( )
targetGlyph . update ( )
2008-01-07 17:40:34 +00:00
# the lib getter and setter are shared by RFont and RGlyph
def _get_lib ( self ) :
data = self . _object . customdata
if data :
f = StringIO ( data )
try :
pList = readPlist ( f )
except : # XXX ugh, plistlib can raise lots of things
# Anyway, customdata does not contain valid plist data,
# but we don't need to toss it!
pList = { " org.robofab.fontlab.customdata " : Data ( data ) }
else :
pList = { }
# pass it along to the lib object
l = RLib ( pList )
l . setParent ( self )
return l
def _set_lib ( self , aDict ) :
l = RLib ( { } )
l . setParent ( self )
l . update ( aDict )
def _normalizeLineEndings ( s ) :
return s . replace ( " \r \n " , " \n " ) . replace ( " \r " , " \n " )
class RFont ( BaseFont ) :
""" RoboFab UFO wrapper for FL Font object """
_title = " FLFont "
def __init__ ( self , font = None ) :
BaseFont . __init__ ( self )
if font is None :
from FL import fl , Font
# rather than raise an error we could just start a new font.
font = Font ( )
fl . Add ( font )
#raise RoboFabError, "RFont: there's nothing to wrap!?"
self . _object = font
self . _lib = { }
2008-02-25 12:51:13 +00:00
self . _supportHints = True
2009-02-28 15:47:24 +00:00
self . psHints = PostScriptFontHintValues ( self )
self . psHints . setParent ( self )
2008-01-07 17:40:34 +00:00
def keys ( self ) :
keys = { }
for glyph in self . _object . glyphs :
glyphName = glyph . name
if glyphName in keys :
n = 1
while ( " %s # %s " % ( glyphName , n ) ) in keys :
n + = 1
newGlyphName = " %s # %s " % ( glyphName , n )
print " RoboFab encountered a duplicate glyph name, renaming %r to %r " % ( glyphName , newGlyphName )
glyphName = newGlyphName
glyph . name = glyphName
keys [ glyphName ] = None
return keys . keys ( )
def has_key ( self , glyphName ) :
glyph = self . _object [ glyphName ]
if glyph is None :
return False
else :
return True
__contains__ = has_key
def __setitem__ ( self , glyphName , glyph ) :
self . _object [ glyphName ] = glyph . naked ( )
def __cmp__ ( self , other ) :
if not hasattr ( other , ' _object ' ) :
return - 1
return self . _compare ( other )
# if self._object.file_name == other._object.file_name:
# # so, names match.
# # this will falsely identify two distinct "Untitled"
# # let's check some more
# return 0
# else:
# return -1
2008-02-21 17:37:39 +00:00
2009-02-28 15:47:24 +00:00
# def _get_psHints(self):
# h = PostScriptFontHintValues(self)
# h.setParent(self)
# return h
#
# psHints = property(_get_psHints, doc="font level postscript hint data")
2008-02-21 17:37:39 +00:00
2008-01-07 17:40:34 +00:00
def _get_info ( self ) :
return RInfo ( self . _object )
info = property ( _get_info , doc = " font info object " )
2009-02-28 15:47:24 +00:00
def _get_features ( self ) :
return RFeatures ( self . _object )
features = property ( _get_features , doc = " features object " )
2008-01-07 17:40:34 +00:00
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 " )
def _set_groups ( self , aDict ) :
g = RGroups ( { } )
g . setParent ( self )
g . update ( aDict )
def _get_groups ( self ) :
groups = { }
for i in self . _object . classes :
# test to make sure that the class is properly formatted
if i . find ( ' : ' ) == - 1 :
continue
key = i . split ( ' : ' ) [ 0 ]
value = i . split ( ' : ' ) [ 1 ] . lstrip ( ) . split ( ' ' )
groups [ key ] = value
rg = RGroups ( groups )
rg . setParent ( self )
return rg
groups = property ( _get_groups , _set_groups , doc = " a group object " )
lib = property ( _get_lib , _set_lib , doc = " font lib object " )
#
# attributes
#
def _get_fontIndex ( self ) :
2008-01-07 21:05:52 +00:00
# find the index of the font
# by comparing the file_name
# to all open fonts. if the
# font has no file_name, meaning
# it is a new, unsaved font,
# return the index of the first
# font with no file_name.
selfFileName = self . _object . file_name
fontCount = len ( fl )
for index in xrange ( fontCount ) :
other = fl [ index ]
if other . file_name == selfFileName :
return index
2008-01-07 17:40:34 +00:00
fontIndex = property ( _get_fontIndex , doc = " the fontindex for this font " )
def _get_path ( self ) :
return self . _object . file_name
path = property ( _get_path , doc = " path to the font " )
def _get_fileName ( self ) :
if self . path is None :
return None
return os . path . split ( self . path )
fileName = property ( _get_fileName , doc = " the font ' s file name " )
def _get_selection ( self ) :
# return a list of glyph names for glyphs selected in the font window
l = [ ]
for i in range ( len ( self . _object . glyphs ) ) :
if fl . Selected ( i ) == 1 :
l . append ( self . _object [ i ] . name )
return l
def _set_selection ( self , list ) :
fl . Unselect ( )
for i in list :
fl . Select ( i )
selection = property ( _get_selection , _set_selection , doc = " the glyph selection in the font window " )
def _makeGlyphlist ( self ) :
# To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters.
gl = [ ]
for c in self :
gl . append ( c )
return gl
def _get_glyphs ( self ) :
return self . _makeGlyphlist ( )
glyphs = property ( _get_glyphs , doc = " A list of all glyphs in the font, to allow iterations through Font.glyphs " )
def update ( self ) :
""" Don ' t forget to update the font when you are done. """
2008-01-07 21:05:52 +00:00
fl . UpdateFont ( self . fontIndex )
2008-01-07 17:40:34 +00:00
def save ( self , path = None ) :
""" Save the font, path is required. """
if not path :
if not self . _object . file_name :
raise RoboFabError , " No destination path specified. "
else :
path = self . _object . file_name
2008-01-07 21:05:52 +00:00
fl . Save ( self . fontIndex , path )
2008-01-07 17:40:34 +00:00
def close ( self , save = False ) :
""" Close the font, saving is optional. """
if save :
self . save ( )
else :
self . _object . modified = 0
2008-01-07 21:05:52 +00:00
fl . Close ( self . fontIndex )
2008-01-07 17:40:34 +00:00
def getGlyph ( self , glyphName ) :
# XXX may need to become private
flGlyph = self . _object [ glyphName ]
if flGlyph is not None :
glyph = RGlyph ( flGlyph )
glyph . setParent ( self )
return glyph
return self . newGlyph ( glyphName )
2008-01-07 21:05:52 +00:00
def newGlyph ( self , glyphName , clear = True ) :
2011-02-13 23:13:54 +00:00
""" Make a new glyph. """
# the old implementation always updated the font.
# that proved to be very slow. so, the updating is
# now left up to the caller where it can be more
# efficiently managed.
g = NewGlyph ( self . _object , glyphName , clear , updateFont = False )
2008-01-07 17:40:34 +00:00
return RGlyph ( g )
2009-09-03 04:09:29 +00:00
def insertGlyph ( self , glyph , name = None ) :
2008-01-07 17:40:34 +00:00
""" Returns a new glyph that has been inserted into the font.
2009-09-03 04:09:29 +00:00
name = another glyphname if you want to insert as with that . """
2008-01-07 17:40:34 +00:00
from robofab . objects . objectsRF import RFont as _RFont
from robofab . objects . objectsRF import RGlyph as _RGlyph
oldGlyph = glyph
2009-09-03 04:09:29 +00:00
if name is None :
2008-01-07 17:40:34 +00:00
name = oldGlyph . name
# clear the destination glyph if it exists.
if self . has_key ( name ) :
self [ name ] . clear ( )
# get the parent for the glyph
otherFont = oldGlyph . getParent ( )
# in some cases we will use the native
# FL method for appending a glyph.
useNative = True
testingNative = True
while testingNative :
# but, maybe it is an orphan glyph.
# in that case we should not use the native method.
if otherFont is None :
useNative = False
testingNative = False
# or maybe the glyph is coming from a NoneLab font
if otherFont is not None :
if isinstance ( otherFont , _RFont ) :
useNative = False
testingNative = False
# but, it could be a copied FL glyph
# which is a NoneLab glyph that
# has a FontLab font as the parent
elif isinstance ( otherFont , RFont ) :
useNative = False
testingNative = False
# or, maybe the glyph is being replaced, in which
# case the native method should not be used
# since FL will destroy any references to the glyph
if self . has_key ( name ) :
useNative = False
testingNative = False
# if the glyph contains components the native
# method should not be used since FL does
# not reference glyphs in components by
# name, but by index (!!!).
if len ( oldGlyph . components ) != 0 :
useNative = False
testingNative = False
testingNative = False
# finally, insert the glyph.
if useNative :
font = self . naked ( )
otherFont = oldGlyph . getParent ( ) . naked ( )
self . naked ( ) . glyphs . append ( otherFont [ name ] )
newGlyph = self . getGlyph ( name )
else :
newGlyph = self . newGlyph ( name )
newGlyph . appendGlyph ( oldGlyph )
for attr in GLYPH_COPY_ATTRS :
if attr == " name " :
value = name
else :
value = getattr ( oldGlyph , attr )
setattr ( newGlyph , attr , value )
if self . _supportHints :
# now we need to transfer the hints from
# the old glyph to the new glyph. we'll do this
# via the dict to hint functions.
hintDict = { }
# if the glyph is a NoneLab glyph, then we need
# to extract the ps hints from the lib
if isinstance ( oldGlyph , _RGlyph ) :
2008-02-21 17:37:39 +00:00
hintDict = oldGlyph . lib . get ( postScriptHintDataLibKey , { } )
2008-01-07 17:40:34 +00:00
# otherwise we need to extract the hint dict from the glyph
else :
hintDict = _glyphHintsToDict ( oldGlyph . naked ( ) )
# now apply the hint data
if hintDict :
_dictHintsToGlyph ( newGlyph . naked ( ) , hintDict )
# delete any remaining hint data from the glyph lib
2008-02-21 17:37:39 +00:00
if newGlyph . lib . has_key ( postScriptHintDataLibKey ) :
del newGlyph . lib [ postScriptHintDataLibKey ]
2008-01-07 17:40:34 +00:00
return newGlyph
def removeGlyph ( self , glyphName ) :
""" remove a glyph from the font """
index = self . _object . FindGlyph ( glyphName )
if index != - 1 :
del self . _object . glyphs [ index ]
#
# opentype
#
def getOTClasses ( self ) :
""" Return all OpenType classes as a dict. Relies on properly formatted classes. """
classes = { }
c = self . _object . ot_classes
if c is None :
return classes
c = c . replace ( ' \r ' , ' ' ) . replace ( ' \n ' , ' ' ) . split ( ' ; ' )
for i in c :
if i . find ( ' = ' ) != - 1 :
value = [ ]
i = i . replace ( ' = ' , ' = ' )
name = i . split ( ' = ' ) [ 0 ]
v = i . split ( ' = ' ) [ 1 ] . replace ( ' [ ' , ' ' ) . replace ( ' ] ' , ' ' ) . split ( ' ' )
#catch double spaces?
for j in v :
if len ( j ) > 0 :
value . append ( j )
classes [ name ] = value
return classes
def setOTClasses ( self , dict ) :
""" Set all OpenType classes. """
l = [ ]
for i in dict . keys ( ) :
l . append ( ' ' . join ( [ i , ' = [ ' , ' ' . join ( dict [ i ] ) , ' ]; ' ] ) )
self . _object . ot_classes = ' \n ' . join ( l )
def getOTClass ( self , name ) :
""" Get a specific OpenType class. """
classes = self . getOTClasses ( )
return classes [ name ]
def setOTClass ( self , name , list ) :
""" Set a specific OpenType class. """
classes = self . getOTClasses ( )
classes [ name ] = list
self . setOTClasses ( classes )
def getOTFeatures ( self ) :
""" Return all OpenType features as a dict keyed by name.
The value is a string of the text of the feature . """
features = { }
for i in self . _object . features :
v = [ ]
for j in i . value . replace ( ' \r ' , ' \n ' ) . split ( ' \n ' ) :
if j . find ( i . tag ) == - 1 :
v . append ( j )
features [ i . tag ] = ' \n ' . join ( v )
return features
def setOTFeatures ( self , dict ) :
""" Set all OpenType features in the font. """
features = { }
for i in dict . keys ( ) :
f = [ ]
f . append ( ' feature %s { ' % i )
f . append ( dict [ i ] )
f . append ( ' } %s ; ' % i )
features [ i ] = ' \n ' . join ( f )
self . _object . features . clean ( )
for i in features . keys ( ) :
self . _object . features . append ( Feature ( i , features [ i ] ) )
def getOTFeature ( self , name ) :
""" return a specific OpenType feature. """
features = self . getOTFeatures ( )
return features [ name ]
def setOTFeature ( self , name , text ) :
""" Set a specific OpenType feature. """
features = self . getOTFeatures ( )
features [ name ] = text
self . setOTFeatures ( features )
#
# guides
#
def getVGuides ( self ) :
""" Return a list of wrapped vertical guides in this RFont """
vguides = [ ]
for i in range ( len ( self . _object . vguides ) ) :
g = RGuide ( self . _object . vguides [ i ] , i )
g . setParent ( self )
vguides . append ( g )
return vguides
def getHGuides ( self ) :
""" Return a list of wrapped horizontal guides in this RFont """
hguides = [ ]
for i in range ( len ( self . _object . hguides ) ) :
g = RGuide ( self . _object . hguides [ i ] , i )
g . setParent ( self )
hguides . append ( g )
return hguides
def appendHGuide ( self , position , angle = 0 ) :
""" Append a horizontal guide """
position = int ( round ( position ) )
angle = int ( round ( angle ) )
g = Guide ( position , angle )
self . _object . hguides . append ( g )
def appendVGuide ( self , position , angle = 0 ) :
""" Append a horizontal guide """
position = int ( round ( position ) )
angle = int ( round ( angle ) )
g = Guide ( position , angle )
self . _object . vguides . append ( g )
def removeHGuide ( self , guide ) :
""" Remove a horizontal guide. """
pos = ( guide . position , guide . angle )
for g in self . getHGuides ( ) :
if ( g . position , g . angle ) == pos :
del self . _object . hguides [ g . index ]
break
def removeVGuide ( self , guide ) :
""" Remove a vertical guide. """
pos = ( guide . position , guide . angle )
for g in self . getVGuides ( ) :
if ( g . position , g . angle ) == pos :
del self . _object . vguides [ g . index ]
break
def clearHGuides ( self ) :
""" Clear all horizontal guides. """
self . _object . hguides . clean ( )
def clearVGuides ( self ) :
""" Clear all vertical guides. """
self . _object . vguides . clean ( )
#
# generators
#
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 .
"""
outputType = outputType . lower ( )
if not _flGenerateTypes . has_key ( outputType ) :
raise RoboFabError , " %s output type is not supported " % outputType
flOutputType , suffix = _flGenerateTypes [ outputType ]
if path is None :
filePath , fileName = os . path . split ( self . path )
fileName = fileName . replace ( ' .vfb ' , ' ' )
else :
if os . path . isdir ( path ) :
filePath = path
fileName = os . path . split ( self . path ) [ 1 ] . replace ( ' .vfb ' , ' ' )
else :
filePath , fileName = os . path . split ( path )
if ' . ' in fileName :
2008-01-07 21:05:52 +00:00
raise RoboFabError , " filename cannot contain periods. " , fileName
2008-01-07 17:40:34 +00:00
fileName = ' . ' . join ( [ fileName , suffix ] )
finalPath = os . path . join ( filePath , fileName )
2009-06-25 08:50:25 +00:00
if isinstance ( finalPath , unicode ) :
finalPath = finalPath . encode ( " utf-8 " )
2008-01-07 21:05:52 +00:00
# generate is (oddly) an application level method
# rather than a font level method. because of this,
# the font must be the current font. so, make it so.
fl . ifont = self . fontIndex
2008-01-07 17:40:34 +00:00
fl . GenerateFont ( flOutputType , finalPath )
2009-02-28 15:47:24 +00:00
def writeUFO ( self , path = None , doProgress = False , glyphNameToFileNameFunc = None ,
doHints = False , doInfo = True , doKerning = True , doGroups = True , doLib = True , doFeatures = True , glyphs = None , formatVersion = 2 ) :
from robofab . interface . all . dialogs import ProgressBar , Message
# special glyph name to file name conversion
2008-01-07 17:40:34 +00:00
if glyphNameToFileNameFunc is None :
glyphNameToFileNameFunc = self . getGlyphNameToFileNameFunc ( )
if glyphNameToFileNameFunc is None :
from robofab . tools . glyphNameSchemes import glyphNameToShortFileName
glyphNameToFileNameFunc = glyphNameToShortFileName
2009-02-28 15:47:24 +00:00
# get a valid path
2008-01-07 17:40:34 +00:00
if not path :
if self . path is None :
Message ( " Please save this font first before exporting to UFO... " )
return
else :
2009-02-28 15:47:24 +00:00
path = ufoLib . makeUFOPath ( self . path )
# get the glyphs to export
if glyphs is None :
glyphs = self . keys ( )
# if the file exists, check the format version.
# if the format version being written is different
# from the format version of the existing UFO
# and only some files are set to be written
# raise an error.
if os . path . exists ( path ) :
if os . path . exists ( os . path . join ( path , " metainfo.plist " ) ) :
reader = ufoLib . UFOReader ( path )
existingFormatVersion = reader . formatVersion
if formatVersion != existingFormatVersion :
if False in [ doInfo , doKerning , doGroups , doLib , doFeatures , set ( glyphs ) == set ( self . keys ( ) ) ] :
Message ( " When overwriting an existing UFO with a different format version all files must be written. " )
return
# the lib must be written if format version is 1
if not doLib and formatVersion == 1 :
Message ( " The lib must be written when exporting format version 1. " )
return
# set up the progress bar
nonGlyphCount = [ doInfo , doKerning , doGroups , doLib , doFeatures ] . count ( True )
2008-01-07 17:40:34 +00:00
bar = None
if doProgress :
2009-02-28 15:47:24 +00:00
bar = ProgressBar ( " Exporting UFO " , nonGlyphCount + len ( glyphs ) )
# try writing
2008-01-07 17:40:34 +00:00
try :
2009-02-28 15:47:24 +00:00
writer = ufoLib . UFOWriter ( path , formatVersion = formatVersion )
## We make a shallow copy if lib, since we add some stuff for export
## that doesn't need to be retained in memory.
fontLib = dict ( self . lib )
# write the font info
if doInfo :
global _IN_UFO_EXPORT
_IN_UFO_EXPORT = True
writer . writeInfo ( self . info )
_IN_UFO_EXPORT = False
if bar :
bar . tick ( )
# write the kerning
if doKerning :
writer . writeKerning ( self . kerning . asDict ( ) )
if bar :
bar . tick ( )
# write the groups
if doGroups :
writer . writeGroups ( self . groups )
if bar :
bar . tick ( )
# write the features
if doFeatures :
if formatVersion == 2 :
writer . writeFeatures ( self . features . text )
else :
self . _writeOpenTypeFeaturesToLib ( fontLib )
if bar :
bar . tick ( )
# write the lib
if doLib :
## Always export the postscript font hint values to the lib in format version 1
if formatVersion == 1 :
d = self . psHints . asDict ( )
fontLib [ postScriptHintDataLibKey ] = d
## Export the glyph order to the lib
glyphOrder = [ nakedGlyph . name for nakedGlyph in self . naked ( ) . glyphs ]
fontLib [ " org.robofab.glyphOrder " ] = glyphOrder
## export the features
if doFeatures and formatVersion == 1 :
self . _writeOpenTypeFeaturesToLib ( fontLib )
if bar :
bar . tick ( )
writer . writeLib ( fontLib )
if bar :
bar . tick ( )
# write the glyphs
if glyphs :
glyphSet = writer . getGlyphSet ( glyphNameToFileNameFunc )
count = nonGlyphCount
for nakedGlyph in self . naked ( ) . glyphs :
if nakedGlyph . name not in glyphs :
continue
glyph = RGlyph ( nakedGlyph )
if doHints :
hintStuff = _glyphHintsToDict ( glyph . naked ( ) )
if hintStuff :
glyph . lib [ postScriptHintDataLibKey ] = hintStuff
glyphSet . writeGlyph ( glyph . name , glyph , glyph . drawPoints )
# remove the hint dict from the lib
if doHints and glyph . lib . has_key ( postScriptHintDataLibKey ) :
del glyph . lib [ postScriptHintDataLibKey ]
if bar and not count % 10 :
bar . tick ( count )
count = count + 1
glyphSet . writeContents ( )
# only blindly stop if the user says to
except KeyboardInterrupt :
2008-01-07 17:40:34 +00:00
if bar :
2009-02-28 15:47:24 +00:00
bar . close ( )
bar = None
# kill the bar
if bar :
bar . close ( )
def _writeOpenTypeFeaturesToLib ( self , fontLib ) :
# this should only be used for UFO format version 1
flFont = self . naked ( )
2009-03-10 09:18:03 +00:00
cls = flFont . ot_classes
if cls is not None :
fontLib [ " org.robofab.opentype.classes " ] = _normalizeLineEndings ( cls ) . rstrip ( ) + " \n "
2009-03-24 14:40:03 +00:00
if flFont . features :
2009-02-28 15:47:24 +00:00
features = { }
order = [ ]
for feature in flFont . features :
order . append ( feature . tag )
features [ feature . tag ] = _normalizeLineEndings ( feature . value ) . rstrip ( ) + " \n "
fontLib [ " org.robofab.opentype.features " ] = features
fontLib [ " org.robofab.opentype.featureorder " ] = order
def readUFO ( self , path , doProgress = False ,
doHints = False , doInfo = True , doKerning = True , doGroups = True , doLib = True , doFeatures = True , glyphs = None ) :
""" read a .ufo into the font """
from robofab . pens . flPen import FLPointPen
from robofab . interface . all . dialogs import ProgressBar
# start up the reader
reader = ufoLib . UFOReader ( path )
glyphSet = reader . getGlyphSet ( )
# get a list of glyphs that should be imported
if glyphs is None :
glyphs = glyphSet . keys ( )
# set up the progress bar
nonGlyphCount = [ doInfo , doKerning , doGroups , doLib , doFeatures ] . count ( True )
bar = None
if doProgress :
bar = ProgressBar ( " Importing UFO " , nonGlyphCount + len ( glyphs ) )
# start reading
try :
fontLib = reader . readLib ( )
# info
if doInfo :
reader . readInfo ( self . info )
if bar :
bar . tick ( )
# glyphs
count = 1
glyphOrder = self . _getGlyphOrderFromLib ( fontLib , glyphSet )
for glyphName in glyphOrder :
if glyphName not in glyphs :
continue
glyph = self . newGlyph ( glyphName , clear = True )
pen = FLPointPen ( glyph . naked ( ) )
glyphSet . readGlyph ( glyphName = glyphName , glyphObject = glyph , pointPen = pen )
2008-01-07 17:40:34 +00:00
if doHints :
2009-02-28 15:47:24 +00:00
hintData = glyph . lib . get ( postScriptHintDataLibKey )
if hintData :
_dictHintsToGlyph ( glyph . naked ( ) , hintData )
# now that the hints have been extracted from the glyph
# there is no reason to keep the location in the lib.
if glyph . lib . has_key ( postScriptHintDataLibKey ) :
del glyph . lib [ postScriptHintDataLibKey ]
2008-01-07 17:40:34 +00:00
if bar and not count % 10 :
bar . tick ( count )
count = count + 1
2009-02-28 15:47:24 +00:00
# features
if doFeatures :
if reader . formatVersion == 1 :
self . _readOpenTypeFeaturesFromLib ( fontLib )
else :
featureText = reader . readFeatures ( )
self . features . text = featureText
if bar :
bar . tick ( )
else :
# remove features stored in the lib
self . _readOpenTypeFeaturesFromLib ( fontLib , setFeatures = False )
# kerning
if doKerning :
self . kerning . clear ( )
self . kerning . update ( reader . readKerning ( ) )
if bar :
bar . tick ( )
# groups
if doGroups :
self . groups . clear ( )
self . groups . update ( reader . readGroups ( ) )
if bar :
bar . tick ( )
# hints in format version 1
if doHints and reader . formatVersion == 1 :
self . psHints . _loadFromLib ( fontLib )
else :
# remove hint data stored in the lib
if fontLib . has_key ( postScriptHintDataLibKey ) :
del fontLib [ postScriptHintDataLibKey ]
# lib
if doLib :
self . lib . clear ( )
self . lib . update ( fontLib )
if bar :
bar . tick ( )
2011-02-13 23:13:54 +00:00
# update the font
self . update ( )
2009-02-28 15:47:24 +00:00
# only blindly stop if the user says to
2008-01-07 17:40:34 +00:00
except KeyboardInterrupt :
2009-02-28 15:47:24 +00:00
bar . close ( )
2008-01-07 17:40:34 +00:00
bar = None
2009-02-28 15:47:24 +00:00
# kill the bar
2008-01-07 17:40:34 +00:00
if bar :
bar . close ( )
2009-02-28 15:47:24 +00:00
2008-01-07 17:40:34 +00:00
def _getGlyphOrderFromLib ( self , fontLib , glyphSet ) :
glyphOrder = fontLib . get ( " org.robofab.glyphOrder " )
if glyphOrder is not None :
2008-02-21 17:37:39 +00:00
# no need to keep track if the glyph order in lib once the font is loaded.
2008-01-07 17:40:34 +00:00
del fontLib [ " org.robofab.glyphOrder " ]
glyphNames = [ ]
done = { }
for glyphName in glyphOrder :
if glyphName in glyphSet :
glyphNames . append ( glyphName )
done [ glyphName ] = 1
allGlyphNames = glyphSet . keys ( )
allGlyphNames . sort ( )
for glyphName in allGlyphNames :
if glyphName not in done :
glyphNames . append ( glyphName )
else :
glyphNames = glyphSet . keys ( )
glyphNames . sort ( )
return glyphNames
2009-02-28 15:47:24 +00:00
def _readOpenTypeFeaturesFromLib ( self , fontLib , setFeatures = True ) :
# setFeatures may be False. in this case, this method
# should only clear the data from the lib.
2008-01-07 17:40:34 +00:00
classes = fontLib . get ( " org.robofab.opentype.classes " )
if classes is not None :
del fontLib [ " org.robofab.opentype.classes " ]
2009-02-28 15:47:24 +00:00
if setFeatures :
self . naked ( ) . ot_classes = classes
2008-01-07 17:40:34 +00:00
features = fontLib . get ( " org.robofab.opentype.features " )
if features is not None :
2008-01-07 21:05:52 +00:00
order = fontLib . get ( " org.robofab.opentype.featureorder " )
if order is None :
# for UFOs saved without the feature order, do the same as before.
order = features . keys ( )
order . sort ( )
else :
del fontLib [ " org.robofab.opentype.featureorder " ]
2008-01-07 17:40:34 +00:00
del fontLib [ " org.robofab.opentype.features " ]
2008-01-07 21:05:52 +00:00
#features = features.items()
orderedFeatures = [ ]
for tag in order :
2008-02-21 07:46:17 +00:00
oneFeature = features . get ( tag )
if oneFeature is not None :
orderedFeatures . append ( ( tag , oneFeature ) )
2009-02-28 15:47:24 +00:00
if setFeatures :
self . naked ( ) . features . clean ( )
for tag , src in orderedFeatures :
self . naked ( ) . features . append ( Feature ( tag , src ) )
2008-01-07 17:40:34 +00:00
class RGlyph ( BaseGlyph ) :
""" RoboFab wrapper for FL Glyph object """
_title = " FLGlyph "
def __init__ ( self , flGlyph ) :
#BaseGlyph.__init__(self)
if flGlyph is None :
raise RoboFabError , " RGlyph: there ' s nothing to wrap!? "
self . _object = flGlyph
self . _lib = { }
self . _contours = None
def __getitem__ ( self , index ) :
return self . contours [ index ]
def __delitem__ ( self , index ) :
self . _object . DeleteContour ( index )
self . _invalidateContours ( )
def __len__ ( self ) :
return len ( self . contours )
lib = property ( _get_lib , _set_lib , doc = " glyph lib object " )
def _invalidateContours ( self ) :
self . _contours = None
def _buildContours ( self ) :
self . _contours = [ ]
for contourIndex in range ( self . _object . GetContoursNumber ( ) ) :
c = RContour ( contourIndex )
c . setParent ( self )
c . _buildSegments ( )
self . _contours . append ( c )
#
# attribute handlers
#
def _get_index ( self ) :
return self . _object . parent . FindGlyph ( self . name )
index = property ( _get_index , doc = " return the index of the glyph in the font " )
def _get_name ( self ) :
return self . _object . name
def _set_name ( self , value ) :
self . _object . name = value
name = property ( _get_name , _set_name , doc = " name " )
def _get_psName ( self ) :
return self . _object . name
def _set_psName ( self , value ) :
self . _object . name = value
psName = property ( _get_psName , _set_psName , doc = " name " )
def _get_baseName ( self ) :
return self . _object . name . split ( ' . ' ) [ 0 ]
baseName = property ( _get_baseName , doc = " " )
def _get_unicode ( self ) :
return self . _object . unicode
def _set_unicode ( self , value ) :
self . _object . unicode = value
unicode = property ( _get_unicode , _set_unicode , doc = " unicode " )
def _get_unicodes ( self ) :
return self . _object . unicodes
def _set_unicodes ( self , value ) :
self . _object . unicodes = value
unicodes = property ( _get_unicodes , _set_unicodes , doc = " unicodes " )
def _get_width ( self ) :
return self . _object . width
def _set_width ( self , value ) :
value = int ( round ( value ) )
self . _object . width = value
width = property ( _get_width , _set_width , doc = " the width " )
def _get_box ( self ) :
2008-01-07 21:05:52 +00:00
if not len ( self . contours ) and not len ( self . components ) :
return ( 0 , 0 , 0 , 0 )
r = self . _object . GetBoundingRect ( )
2008-01-07 17:40:34 +00:00
return ( int ( round ( r . ll . x ) ) , int ( round ( r . ll . y ) ) , int ( round ( r . ur . x ) ) , int ( round ( r . ur . y ) ) )
box = property ( _get_box , doc = " box of glyph as a tuple (xMin, yMin, xMax, yMax) " )
def _get_selected ( self ) :
if fl . Selected ( self . _object . parent . FindGlyph ( self . _object . name ) ) :
return 1
else :
return 0
def _set_selected ( self , value ) :
fl . Select ( self . _object . name , value )
selected = property ( _get_selected , _set_selected , doc = " Select or deselect the glyph in the font window " )
def _get_mark ( self ) :
return self . _object . mark
def _set_mark ( self , value ) :
self . _object . mark = value
mark = property ( _get_mark , _set_mark , doc = " mark " )
def _get_note ( self ) :
s = self . _object . note
if s is None :
return s
return unicode ( s , LOCAL_ENCODING )
def _set_note ( self , value ) :
if value is None :
value = ' '
if type ( value ) == type ( u " " ) :
value = value . encode ( LOCAL_ENCODING )
self . _object . note = value
note = property ( _get_note , _set_note , doc = " note " )
2008-02-25 11:35:12 +00:00
def _get_psHints ( self ) :
# get an object representing the postscript zone information
return PostScriptGlyphHintValues ( self )
2008-02-21 17:37:39 +00:00
2008-02-25 11:35:12 +00:00
psHints = property ( _get_psHints , doc = " postscript hint data " )
2008-02-21 17:37:39 +00:00
2008-01-07 17:40:34 +00:00
#
# necessary evil
#
def update ( self ) :
""" Don ' t forget to update the glyph when you are done. """
fl . UpdateGlyph ( self . _object . parent . FindGlyph ( self . _object . name ) )
#
# methods to make RGlyph compatible with FL.Glyph
# ##are these still needed?
#
def GetBoundingRect ( self , masterIndex ) :
""" FL compatibility """
return self . _object . GetBoundingRect ( masterIndex )
def GetMetrics ( self , masterIndex ) :
""" FL compatibility """
return self . _object . GetMetrics ( masterIndex )
def SetMetrics ( self , value , masterIndex ) :
""" FL compatibility """
return self . _object . SetMetrics ( value , masterIndex )
#
# object builders
#
def _get_anchors ( self ) :
return self . getAnchors ( )
anchors = property ( _get_anchors , doc = " allow for iteration through glyph.anchors " )
def _get_components ( self ) :
return self . getComponents ( )
components = property ( _get_components , doc = " allow for iteration through glyph.components " )
def _get_contours ( self ) :
if self . _contours is None :
self . _buildContours ( )
return self . _contours
contours = property ( _get_contours , doc = " allow for iteration through glyph.contours " )
def getAnchors ( self ) :
""" Return a list of wrapped anchors in this RGlyph. """
anchors = [ ]
for i in range ( len ( self . _object . anchors ) ) :
a = RAnchor ( self . _object . anchors [ i ] , i )
a . setParent ( self )
anchors . append ( a )
return anchors
def getComponents ( self ) :
""" Return a list of wrapped components in this RGlyph. """
components = [ ]
for i in range ( len ( self . _object . components ) ) :
c = RComponent ( self . _object . components [ i ] , i )
c . setParent ( self )
components . append ( c )
return components
def getVGuides ( self ) :
""" Return a list of wrapped vertical guides in this RGlyph """
vguides = [ ]
for i in range ( len ( self . _object . vguides ) ) :
g = RGuide ( self . _object . vguides [ i ] , i )
g . setParent ( self )
vguides . append ( g )
return vguides
def getHGuides ( self ) :
""" Return a list of wrapped horizontal guides in this RGlyph """
hguides = [ ]
for i in range ( len ( self . _object . hguides ) ) :
g = RGuide ( self . _object . hguides [ i ] , i )
g . setParent ( self )
hguides . append ( g )
return hguides
#
# tools
#
def getPointPen ( self ) :
self . _invalidateContours ( )
# Now just don't muck with glyph.contours before you're done drawing...
return FLPointPen ( self )
def appendComponent ( self , baseGlyph , offset = ( 0 , 0 ) , scale = ( 1 , 1 ) ) :
""" Append a component to the glyph. x and y are optional offset values """
offset = roundPt ( ( offset [ 0 ] , offset [ 1 ] ) )
p = FLPointPen ( self . naked ( ) )
xx , yy = scale
dx , dy = offset
p . addComponent ( baseGlyph , ( xx , 0 , 0 , yy , dx , dy ) )
def appendAnchor ( self , name , position ) :
""" Append an anchor to the glyph """
value = roundPt ( ( position [ 0 ] , position [ 1 ] ) )
anchor = Anchor ( name , value [ 0 ] , value [ 1 ] )
self . _object . anchors . append ( anchor )
def appendHGuide ( self , position , angle = 0 ) :
""" Append a horizontal guide """
position = int ( round ( position ) )
g = Guide ( position , angle )
self . _object . hguides . append ( g )
def appendVGuide ( self , position , angle = 0 ) :
""" Append a horizontal guide """
position = int ( round ( position ) )
g = Guide ( position , angle )
self . _object . vguides . append ( g )
def clearComponents ( self ) :
""" Clear all components. """
self . _object . components . clean ( )
def clearAnchors ( self ) :
""" Clear all anchors. """
self . _object . anchors . clean ( )
def clearHGuides ( self ) :
""" Clear all horizontal guides. """
self . _object . hguides . clean ( )
def clearVGuides ( self ) :
""" Clear all vertical guides. """
self . _object . vguides . clean ( )
def removeComponent ( self , component ) :
""" Remove a specific component from the glyph. This only works
if the glyph does not have duplicate components in the same location . """
pos = ( component . baseGlyph , component . offset , component . scale )
a = self . getComponents ( )
found = [ ]
for i in a :
if ( i . baseGlyph , i . offset , i . scale ) == pos :
found . append ( i )
if len ( found ) > 1 :
raise RoboFabError , ' Found more than one possible component to remove '
elif len ( found ) == 1 :
del self . _object . components [ found [ 0 ] . index ]
else :
raise RoboFabError , ' Component does not exist '
def removeContour ( self , index ) :
""" remove a specific contour from the glyph """
self . _object . DeleteContour ( index )
self . _invalidateContours ( )
def removeAnchor ( self , anchor ) :
""" Remove a specific anchor from the glyph. This only works
if the glyph does not have anchors with duplicate names
in exactly the same location with the same mark . """
pos = ( anchor . name , anchor . position , anchor . mark )
a = self . getAnchors ( )
found = [ ]
for i in a :
if ( i . name , i . position , i . mark ) == pos :
found . append ( i )
if len ( found ) > 1 :
raise RoboFabError , ' Found more than one possible anchor to remove '
elif len ( found ) == 1 :
del self . _object . anchors [ found [ 0 ] . index ]
else :
raise RoboFabError , ' Anchor does not exist '
def removeHGuide ( self , guide ) :
""" Remove a horizontal guide. """
pos = ( guide . position , guide . angle )
for g in self . getHGuides ( ) :
if ( g . position , g . angle ) == pos :
del self . _object . hguides [ g . index ]
break
def removeVGuide ( self , guide ) :
""" Remove a vertical guide. """
pos = ( guide . position , guide . angle )
for g in self . getVGuides ( ) :
if ( g . position , g . angle ) == pos :
del self . _object . vguides [ g . index ]
break
def center ( self , padding = None ) :
""" Equalise sidebearings, set to padding if wanted. """
left = self . leftMargin
right = self . rightMargin
if padding :
e_left = e_right = padding
else :
e_left = ( left + right ) / 2
e_right = ( left + right ) - e_left
self . leftMargin = e_left
self . rightMargin = e_right
def removeOverlap ( self ) :
""" Remove overlap """
self . _object . RemoveOverlap ( )
self . _invalidateContours ( )
def decompose ( self ) :
""" Decompose all components """
self . _object . Decompose ( )
self . _invalidateContours ( )
##broken!
#def removeHints(self):
# """Remove the hints."""
# self._object.RemoveHints()
def autoHint ( self ) :
""" Automatically generate type 1 hints. """
self . _object . Autohint ( )
def move ( self , ( x , y ) , contours = True , components = True , anchors = True ) :
""" Move a glyph ' s items that are flagged as True """
x , y = roundPt ( ( x , y ) )
self . _object . Shift ( Point ( x , y ) )
for c in self . getComponents ( ) :
c . move ( ( x , y ) )
for a in self . getAnchors ( ) :
a . move ( ( x , y ) )
def clear ( self , contours = True , components = True , anchors = True , guides = True , hints = True ) :
""" Clear all items marked as true from the glyph """
if contours :
self . _object . Clear ( )
self . _invalidateContours ( )
if components :
self . _object . components . clean ( )
if anchors :
self . _object . anchors . clean ( )
if guides :
self . _object . hguides . clean ( )
self . _object . vguides . clean ( )
if hints :
# RemoveHints requires an "integer mode" argument
# but it is not documented. from some simple experiments
# i deduced that
# 1 = horizontal hints and links,
# 2 = vertical hints and links
# 3 = all hints and links
self . _object . RemoveHints ( 3 )
#
# special treatment for GlyphMath support in FontLab
#
def _getMathDestination ( self ) :
from robofab . objects . objectsRF import RGlyph as _RGlyph
return _RGlyph ( )
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 )
# hints
doHints = False
parent = self . getParent ( )
if parent is not None and parent . _supportHints :
hintStuff = _glyphHintsToDict ( self . naked ( ) )
if hintStuff :
2008-02-21 17:37:39 +00:00
newGlyph . lib [ postScriptHintDataLibKey ] = hintStuff
2008-01-07 17:40:34 +00:00
if aParent is not None :
newGlyph . setParent ( aParent )
elif self . getParent ( ) is not None :
newGlyph . setParent ( self . getParent ( ) )
return newGlyph
def __mul__ ( self , factor ) :
return self . copy ( ) * factor
__rmul__ = __mul__
def __sub__ ( self , other ) :
return self . copy ( ) - other . copy ( )
def __add__ ( self , other ) :
return self . copy ( ) + other . copy ( )
class RContour ( BaseContour ) :
""" RoboFab wrapper for non FL contour object """
_title = " FLContour "
def __init__ ( self , index ) :
self . _index = index
self . _parentGlyph = None
self . segments = [ ]
def __len__ ( self ) :
return len ( self . points )
def _buildSegments ( self ) :
#######################
# Notes about FL node contour structure
#######################
# for TT curves, FL lists them as seperate nodes:
# [move, off, off, off, line, off, off]
# and, this list is sequential. after the last on curve,
# it is possible (and likely) that there will be more offCurves
# in our segment object, these should be associated with the
# first segment in the contour.
#
# for PS curves, it is a very different scenerio.
# curve nodes contain points:
# [on, off, off]
# and the list is not in sequential order. the first point in
# the list is the on curve and the subsequent points are the off
# curve points leading up to that on curve.
#
# it is very important to remember these structures when trying
# to understand the code below
self . segments = [ ]
offList = [ ]
nodes = self . _nakedParent . nodes
for index in range ( self . _nodeLength ) :
x = index + self . _startNodeIndex
node = nodes [ x ]
# we do have a loose off curve. deal with it.
if node . type == flOFFCURVE :
offList . append ( x )
# we are not dealing with a loose off curve
else :
s = RSegment ( x )
s . setParent ( self )
# but do we have a collection of loose off curves above?
# if so, apply them to the segment, and clear the list
if len ( offList ) != 0 :
s . _looseOffCurve = offList
offList = [ ]
self . segments . append ( s )
# do we have some off curves now that the contour is complete?
if len ( offList ) != 0 :
# ugh. apply them to the first segment
self . segments [ 0 ] . _looseOffCurve = offList
def setParent ( self , parentGlyph ) :
self . _parentGlyph = parentGlyph
def getParent ( self ) :
return self . _parentGlyph
def _get__nakedParent ( self ) :
return self . _parentGlyph . naked ( )
_nakedParent = property ( _get__nakedParent , doc = " " )
def _get__startNodeIndex ( self ) :
return self . _nakedParent . GetContourBegin ( self . _index )
_startNodeIndex = property ( _get__startNodeIndex , doc = " " )
def _get__nodeLength ( self ) :
return self . _nakedParent . GetContourLength ( self . _index )
_nodeLength = property ( _get__nodeLength , doc = " " )
def _get__lastNodeIndex ( self ) :
return self . _startNodeIndex + self . _nodeLength - 1
_lastNodeIndex = property ( _get__lastNodeIndex , doc = " " )
def _previousNodeIndex ( self , index ) :
return ( index - 1 ) % self . _nodeLength
def _nextNodeIndex ( self , index ) :
return ( index + 1 ) % self . _nodeLength
def _getNode ( self , index ) :
return self . _nodes [ index ]
def _get__nodes ( self ) :
nodes = [ ]
for node in self . _nakedParent . nodes [ self . _startNodeIndex : self . _startNodeIndex + self . _nodeLength - 1 ] :
nodes . append ( node )
return nodes
_nodes = property ( _get__nodes , doc = " " )
def _get_points ( self ) :
points = [ ]
for segment in self . segments :
for point in segment . points :
points . append ( point )
return points
points = property ( _get_points , doc = " " )
def _get_bPoints ( self ) :
bPoints = [ ]
for segment in self . segments :
bp = RBPoint ( segment . index )
bp . setParent ( self )
bPoints . append ( bp )
return bPoints
bPoints = property ( _get_bPoints , doc = " " )
def _get_index ( self ) :
return self . _index
def _set_index ( self , index ) :
if index != self . _index :
self . _nakedParent . ReorderContour ( self . _index , index )
# reorder and set the _index of the existing RContour objects
# this will be a better solution than reconstructing all the objects
# segment objects will still, sadly, have to be reconstructed
contourList = self . getParent ( ) . contours
contourList . insert ( index , contourList . pop ( self . _index ) )
for i in range ( len ( contourList ) ) :
contourList [ i ] . _index = i
contourList [ i ] . _buildSegments ( )
index = property ( _get_index , _set_index , doc = " the index of the contour " )
def _get_selected ( self ) :
selected = 0
nodes = self . _nodes
for node in nodes :
if node . selected == 1 :
selected = 1
break
return selected
def _set_selected ( self , value ) :
if value == 1 :
self . _nakedParent . SelectContour ( self . _index )
else :
for node in self . _nodes :
node . selected = value
selected = property ( _get_selected , _set_selected , doc = " selection of the contour: 1-selected or 0-unselected " )
def appendSegment ( self , segmentType , points , smooth = False ) :
segment = self . insertSegment ( index = self . _nodeLength , segmentType = segmentType , points = points , smooth = smooth )
return segment
def insertSegment ( self , index , segmentType , points , smooth = False ) :
""" insert a seggment into the contour """
# do a qcurve insertion
if segmentType == QCURVE :
count = 0
for point in points [ : - 1 ] :
newNode = Node ( flOFFCURVE , Point ( point [ 0 ] , point [ 1 ] ) )
self . _nakedParent . Insert ( newNode , self . _startNodeIndex + index + count )
count = count + 1
newNode = Node ( flLINE , Point ( points [ - 1 ] [ 0 ] , points [ - 1 ] [ 1 ] ) )
self . _nakedParent . Insert ( newNode , self . _startNodeIndex + index + len ( points ) - 1 )
# do a regular insertion
else :
onX , onY = points [ - 1 ]
newNode = Node ( _rfToFLSegmentType ( segmentType ) , Point ( onX , onY ) )
# fix the off curves in case the user is inserting a curve
# but is not specifying off curve points
if segmentType == CURVE and len ( points ) == 1 :
pSeg = self . _prevSegment ( index )
pOn = pSeg . onCurve
newNode . points [ 1 ] . Assign ( Point ( pOn . x , pOn . y ) )
newNode . points [ 2 ] . Assign ( Point ( onX , onY ) )
for pointIndex in range ( len ( points [ : - 1 ] ) ) :
x , y = points [ pointIndex ]
newNode . points [ 1 + pointIndex ] . Assign ( Point ( x , y ) )
if smooth :
node . alignment = flSMOOTH
self . _nakedParent . Insert ( newNode , self . _startNodeIndex + index )
self . _buildSegments ( )
return self . segments [ index ]
def removeSegment ( self , index ) :
""" remove a segment from the contour """
segment = self . segments [ index ]
# we have a qcurve. umph.
if segment . type == QCURVE :
indexList = [ segment . _nodeIndex ] + segment . _looseOffCurve
indexList . sort ( )
indexList . reverse ( )
parent = self . _nakedParent
for nodeIndex in indexList :
parent . DeleteNode ( nodeIndex )
# we have a more sane structure to follow
else :
# store some info for later
next = self . _nextSegment ( index )
nextOffA = None
nextOffB = None
nextType = next . type
if nextType != LINE and nextType != MOVE :
pA = next . offCurve [ 0 ]
nextOffA = ( pA . x , pA . y )
pB = next . offCurve [ - 1 ]
nextOffB = ( pB . x , pB . y )
nodeIndex = segment . _nodeIndex
self . _nakedParent . DeleteNode ( nodeIndex )
self . _buildSegments ( )
# now we must override FL guessing about offCurves
next = self . _nextSegment ( index - 1 )
nextType = next . type
if nextType != LINE and nextType != MOVE :
pA = next . offCurve [ 0 ]
pB = next . offCurve [ - 1 ]
pA . x , pA . y = nextOffA
pB . x , pB . y = nextOffB
def reverseContour ( self ) :
""" reverse contour direction """
self . _nakedParent . ReverseContour ( self . _index )
self . _buildSegments ( )
def setStartSegment ( self , segmentIndex ) :
""" set the first node on the contour """
self . _nakedParent . SetStartNode ( self . _startNodeIndex + segmentIndex )
self . getParent ( ) . _invalidateContours ( )
self . getParent ( ) . _buildContours ( )
def copy ( self , aParent = None ) :
""" Copy this object -- result is an ObjectsRF flavored object.
There is no way to make this work using FontLab objects .
Copy is mainly used for glyphmath .
"""
raise RoboFabError , " copy() for objectsFL.RContour is not implemented. "
class RSegment ( BaseSegment ) :
_title = " FLSegment "
def __init__ ( self , flNodeIndex ) :
BaseSegment . __init__ ( self )
self . _nodeIndex = flNodeIndex
self . _looseOffCurve = [ ] #a list of indexes to loose off curve nodes
def _get__node ( self ) :
glyph = self . getParent ( ) . _nakedParent
return glyph . nodes [ self . _nodeIndex ]
_node = property ( _get__node , doc = " " )
def _get_qOffCurve ( self ) :
nodes = self . getParent ( ) . _nakedParent . nodes
off = [ ]
for x in self . _looseOffCurve :
off . append ( nodes [ x ] )
return off
_qOffCurve = property ( _get_qOffCurve , doc = " free floating off curve nodes in the segment " )
def _get_index ( self ) :
contour = self . getParent ( )
return self . _nodeIndex - contour . _startNodeIndex
index = property ( _get_index , doc = " " )
def _isQCurve ( self ) :
# loose off curves only appear in q curves
if len ( self . _looseOffCurve ) != 0 :
return True
return False
def _get_type ( self ) :
if self . _isQCurve ( ) :
return QCURVE
return _flToRFSegmentType ( self . _node . type )
def _set_type ( self , segmentType ) :
if self . _isQCurve ( ) :
raise RoboFabError , ' qcurve point types cannot be changed '
oldNode = self . _node
oldType = oldNode . type
oldPointType = _flToRFSegmentType ( oldType )
if oldPointType == MOVE :
raise RoboFabError , ' %s point types cannot be changed ' % oldPointType
if segmentType == MOVE or segmentType == OFFCURVE :
raise RoboFabError , ' %s point types cannot be assigned ' % oldPointType
if oldPointType == segmentType :
return
oldNode . type = _rfToFLSegmentType ( segmentType )
type = property ( _get_type , _set_type , doc = " " )
def _get_smooth ( self ) :
alignment = self . _node . alignment
if alignment == flSMOOTH or alignment == flFIXED :
return True
return False
def _set_smooth ( self , value ) :
if value :
self . _node . alignment = flSMOOTH
else :
self . _node . alignment = flSHARP
smooth = property ( _get_smooth , _set_smooth , doc = " " )
def _get_points ( self ) :
points = [ ]
node = self . _node
# gather the off curves
#
# are we dealing with a qCurve? ugh.
# gather the loose off curves
if self . _isQCurve ( ) :
off = self . _qOffCurve
x = 0
for n in off :
p = RPoint ( 0 )
p . setParent ( self )
p . _qOffIndex = x
points . append ( p )
x = x + 1
# otherwise get the points associated with the node
else :
index = 1
for point in node . points [ 1 : ] :
p = RPoint ( index )
p . setParent ( self )
points . append ( p )
index = index + 1
# the last point should always be the on curve
p = RPoint ( 0 )
p . setParent ( self )
points . append ( p )
return points
points = property ( _get_points , doc = " " )
def _get_selected ( self ) :
return self . _node . selected
def _set_selected ( self , value ) :
self . _node . selected = value
selected = property ( _get_selected , _set_selected , doc = " " )
def move ( self , ( x , y ) ) :
x , y = roundPt ( ( x , y ) )
self . _node . Shift ( Point ( x , y ) )
if self . _isQCurve ( ) :
qOff = self . _qOffCurve
for node in qOff :
node . Shift ( Point ( x , y ) )
def copy ( self , aParent = None ) :
""" Copy this object -- result is an ObjectsRF flavored object.
There is no way to make this work using FontLab objects .
Copy is mainly used for glyphmath .
"""
raise RoboFabError , " copy() for objectsFL.RSegment is not implemented. "
class RPoint ( BasePoint ) :
_title = " FLPoint "
def __init__ ( self , pointIndex ) :
#BasePoint.__init__(self)
self . _pointIndex = pointIndex
self . _qOffIndex = None
def _get__parentGlyph ( self ) :
return self . _parentContour . getParent ( )
_parentGlyph = property ( _get__parentGlyph , doc = " " )
def _get__parentContour ( self ) :
return self . _parentSegment . getParent ( )
_parentContour = property ( _get__parentContour , doc = " " )
def _get__parentSegment ( self ) :
return self . getParent ( )
_parentSegment = property ( _get__parentSegment , doc = " " )
def _get__node ( self ) :
if self . _qOffIndex is not None :
return self . getParent ( ) . _qOffCurve [ self . _qOffIndex ]
return self . getParent ( ) . _node
_node = property ( _get__node , doc = " " )
def _get__point ( self ) :
return self . _node . points [ self . _pointIndex ]
_point = property ( _get__point , doc = " " )
def _get_x ( self ) :
return self . _point . x
def _set_x ( self , value ) :
value = int ( round ( value ) )
self . _point . x = value
x = property ( _get_x , _set_x , doc = " " )
def _get_y ( self ) :
return self . _point . y
def _set_y ( self , value ) :
value = int ( round ( value ) )
self . _point . y = value
y = property ( _get_y , _set_y , doc = " " )
def _get_type ( self ) :
if self . _pointIndex == 0 :
# FL store quad contour data as a list of off curves and lines
# (see note in RContour._buildSegments). So, we need to do
# a bit of trickery to return a decent point type.
# if the straight FL node type is off curve, it is a loose
# quad off curve. return that.
tp = _flToRFSegmentType ( self . _node . type )
if tp == OFFCURVE :
return OFFCURVE
# otherwise we are dealing with an on curve. in this case,
# we attempt to get the parent segment type and return it.
segment = self . getParent ( )
if segment is not None :
return segment . type
# we must not have a segment, fall back to straight conversion
return tp
return OFFCURVE
type = property ( _get_type , doc = " " )
def _set_selected ( self , value ) :
if self . _pointIndex == 0 :
self . _node . selected = value
def _get_selected ( self ) :
if self . _pointIndex == 0 :
return self . _node . selected
return False
selected = property ( _get_selected , _set_selected , doc = " " )
def move ( self , ( x , y ) ) :
x , y = roundPt ( ( x , y ) )
self . _point . Shift ( Point ( x , y ) )
def scale ( self , ( x , y ) , center = ( 0 , 0 ) ) :
centerX , centerY = roundPt ( center )
point = self . _point
point . x , point . y = _scalePointFromCenter ( ( point . x , point . y ) , ( x , y ) , ( centerX , centerY ) )
def copy ( self , aParent = None ) :
""" Copy this object -- result is an ObjectsRF flavored object.
There is no way to make this work using FontLab objects .
Copy is mainly used for glyphmath .
"""
raise RoboFabError , " copy() for objectsFL.RPoint is not implemented. "
class RBPoint ( BaseBPoint ) :
_title = " FLBPoint "
def __init__ ( self , segmentIndex ) :
#BaseBPoint.__init__(self)
self . _segmentIndex = segmentIndex
def _get__parentSegment ( self ) :
return self . getParent ( ) . segments [ self . _segmentIndex ]
_parentSegment = property ( _get__parentSegment , doc = " " )
def _get_index ( self ) :
return self . _segmentIndex
index = property ( _get_index , doc = " " )
def _get_selected ( self ) :
return self . _parentSegment . selected
def _set_selected ( self , value ) :
self . _parentSegment . selected = value
selected = property ( _get_selected , _set_selected , doc = " " )
def copy ( self , aParent = None ) :
""" Copy this object -- result is an ObjectsRF flavored object.
There is no way to make this work using FontLab objects .
Copy is mainly used for glyphmath .
"""
raise RoboFabError , " copy() for objectsFL.RBPoint is not implemented. "
class RComponent ( BaseComponent ) :
""" RoboFab wrapper for FL Component object """
_title = " FLComponent "
def __init__ ( self , flComponent , index ) :
BaseComponent . __init__ ( self )
self . _object = flComponent
self . _index = index
def _get_index ( self ) :
return self . _index
index = property ( _get_index , doc = " index of component " )
def _get_baseGlyph ( self ) :
return self . _object . parent . parent [ self . _object . index ] . name
baseGlyph = property ( _get_baseGlyph , doc = " " )
def _get_offset ( self ) :
return ( int ( self . _object . delta . x ) , int ( self . _object . delta . y ) )
def _set_offset ( self , value ) :
value = roundPt ( ( value [ 0 ] , value [ 1 ] ) )
self . _object . delta = Point ( value [ 0 ] , value [ 1 ] )
offset = property ( _get_offset , _set_offset , doc = " the offset of the component " )
def _get_scale ( self ) :
return ( self . _object . scale . x , self . _object . scale . y )
def _set_scale ( self , ( x , y ) ) :
self . _object . scale = Point ( x , y )
scale = property ( _get_scale , _set_scale , doc = " the scale of the component " )
def move ( self , ( x , y ) ) :
""" Move the component """
x , y = roundPt ( ( x , y ) )
self . _object . delta = Point ( self . _object . delta . x + x , self . _object . delta . y + y )
def decompose ( self ) :
""" Decompose the component """
self . _object . Paste ( )
def copy ( self , aParent = None ) :
""" Copy this object -- result is an ObjectsRF flavored object.
There is no way to make this work using FontLab objects .
Copy is mainly used for glyphmath .
"""
raise RoboFabError , " copy() for objectsFL.RComponent is not implemented. "
class RAnchor ( BaseAnchor ) :
""" RoboFab wrapper for FL Anchor object """
_title = " FLAnchor "
def __init__ ( self , flAnchor , index ) :
BaseAnchor . __init__ ( self )
self . _object = flAnchor
self . _index = index
def _get_y ( self ) :
return self . _object . y
def _set_y ( self , value ) :
self . _object . y = int ( round ( value ) )
y = property ( _get_y , _set_y , doc = " y " )
def _get_x ( self ) :
return self . _object . x
def _set_x ( self , value ) :
self . _object . x = int ( round ( value ) )
x = property ( _get_x , _set_x , doc = " x " )
def _get_name ( self ) :
return self . _object . name
def _set_name ( self , value ) :
self . _object . name = value
name = property ( _get_name , _set_name , doc = " name " )
def _get_mark ( self ) :
return self . _object . mark
def _set_mark ( self , value ) :
self . _object . mark = value
mark = property ( _get_mark , _set_mark , doc = " mark " )
def _get_index ( self ) :
return self . _index
index = property ( _get_index , doc = " index of the anchor " )
def _get_position ( self ) :
return ( self . _object . x , self . _object . y )
def _set_position ( self , value ) :
value = roundPt ( ( value [ 0 ] , value [ 1 ] ) )
self . _object . x = value [ 0 ]
self . _object . y = value [ 1 ]
position = property ( _get_position , _set_position , doc = " position of the anchor " )
class RGuide ( BaseGuide ) :
""" RoboFab wrapper for FL Guide object """
_title = " FLGuide "
def __init__ ( self , flGuide , index ) :
BaseGuide . __init__ ( self )
self . _object = flGuide
self . _index = index
def __repr__ ( self ) :
# this is a doozy!
parent = " unknown_parent "
parentObject = self . getParent ( )
if parentObject is not None :
# do we have a font?
try :
2009-02-28 15:47:24 +00:00
parent = parentObject . info . postscriptFullName
2008-01-07 17:40:34 +00:00
except AttributeError :
# or do we have a glyph?
try :
parent = parentObject . name
# we must be an orphan
except AttributeError : pass
return " <Robofab guide wrapper for %s > " % parent
def _get_position ( self ) :
return self . _object . position
def _set_position ( self , value ) :
self . _object . position = value
position = property ( _get_position , _set_position , doc = " position " )
def _get_angle ( self ) :
return self . _object . angle
def _set_angle ( self , value ) :
self . _object . angle = value
angle = property ( _get_angle , _set_angle , doc = " angle " )
def _get_index ( self ) :
return self . _index
index = property ( _get_index , doc = " index of the guide " )
class RGroups ( BaseGroups ) :
""" RoboFab wrapper for FL group data """
_title = " FLGroups "
def __init__ ( self , aDict ) :
self . update ( aDict )
def __setitem__ ( self , key , value ) :
# override baseclass so that data is stored in FL classes
if not isinstance ( key , str ) :
raise RoboFabError , ' key must be a string '
if not isinstance ( value , list ) :
raise RoboFabError , ' group must be a list '
super ( RGroups , self ) . __setitem__ ( key , value )
self . _setFLGroups ( )
def __delitem__ ( self , key ) :
# override baseclass so that data is stored in FL classes
super ( RGroups , self ) . __delitem__ ( key , value )
self . _setFLGroups ( )
def _setFLGroups ( self ) :
# set the group data into the font.
if self . getParent ( ) is not None :
groups = [ ]
for i in self . keys ( ) :
value = ' ' . join ( self [ i ] )
groups . append ( ' : ' . join ( [ i , value ] ) )
groups . sort ( )
self . getParent ( ) . naked ( ) . classes = groups
def update ( self , aDict ) :
# override baseclass so that data is stored in FL classes
super ( RGroups , self ) . update ( aDict )
self . _setFLGroups ( )
def clear ( self ) :
# override baseclass so that data is stored in FL classes
super ( RGroups , self ) . clear ( )
self . _setFLGroups ( )
def pop ( self , key ) :
# override baseclass so that data is stored in FL classes
i = super ( RGroups , self ) . pop ( key )
self . _setFLGroups ( )
return i
def popitem ( self ) :
# override baseclass so that data is stored in FL classes
i = super ( RGroups , self ) . popitem ( )
self . _setFLGroups ( )
return i
def setdefault ( self , key , value = None ) :
# override baseclass so that data is stored in FL classes
i = super ( RGroups , self ) . setdefault ( key , value )
self . _setFLGroups ( )
return i
class RKerning ( BaseKerning ) :
""" RoboFab wrapper for FL Kerning data """
_title = " FLKerning "
def __setitem__ ( self , pair , value ) :
if not isinstance ( pair , tuple ) :
raise RoboFabError , ' kerning pair must be a tuple: (left, right) '
else :
if len ( pair ) != 2 :
raise RoboFabError , ' kerning pair must be a tuple: (left, right) '
else :
if value == 0 :
if self . _kerning . get ( pair ) is not None :
#see note about setting kerning values to 0 below
self . _setFLKerning ( pair , 0 )
del self . _kerning [ pair ]
else :
#self._kerning[pair] = value
self . _setFLKerning ( pair , value )
def _setFLKerning ( self , pair , value ) :
# write a pair back into the font
#
# this is fairly speedy, but setting a pair to 0 is roughly
# 2-3 times slower than setting a real value. this is because
# of all the hoops that must be jumped through to keep FL
# from storing kerning pairs with a value of 0.
parentFont = self . getParent ( ) . naked ( )
left = parentFont [ pair [ 0 ] ]
right = parentFont . FindGlyph ( pair [ 1 ] )
# the left glyph doesn not exist
if left is None :
return
# the right glyph doesn not exist
if right == - 1 :
return
self . _kerning [ pair ] = value
leftName = pair [ 0 ]
value = int ( round ( value ) )
# pairs set to 0 need to be handled carefully. FL will allow
# for pairs to have a value of 0 (!?), so we must catch them
# when they pop up and make sure that the pair is actually
# removed from the font.
if value == 0 :
foundPair = False
# if the value is 0, we don't need to construct a pair
# we just need to make sure that the pair is not in the list
pairs = [ ]
# so, go through all the pairs and add them to a new list
for flPair in left . kerning :
# we have found the pair. flag it.
if flPair . key == right :
foundPair = True
# not the pair. add it to the list.
else :
pairs . append ( ( flPair . key , flPair . value ) )
# if we found it, write it back to the glyph.
if foundPair :
left . kerning = [ ]
for p in pairs :
new = KerningPair ( p [ 0 ] , p [ 1 ] )
left . kerning . append ( new )
else :
# non-zero pairs are a bit easier to handle
# we just need to look to see if the pair exists
# if so, change the value and stop the loop.
# if not, add a new pair to the glyph
self . _kerning [ pair ] = value
foundPair = False
for flPair in left . kerning :
if flPair . key == right :
flPair . value = value
foundPair = True
break
if not foundPair :
p = KerningPair ( right , value )
left . kerning . append ( p )
def update ( self , kerningDict ) :
""" replace kerning data with the data in the given kerningDict """
# override base class here for speed
parentFont = self . getParent ( ) . naked ( )
# add existing data to the new kerning dict is not being replaced
for pair in self . keys ( ) :
if not kerningDict . has_key ( pair ) :
kerningDict [ pair ] = self . _kerning [ pair ]
# now clear the existing kerning to make sure that
# all the kerning in residing in the glyphs is gone
self . clear ( )
self . _kerning = kerningDict
kDict = { }
# nest the pairs into a dict keyed by the left glyph
# {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...}
for left , right in kerningDict . keys ( ) :
value = kerningDict [ left , right ]
if not left in kDict :
kDict [ left ] = { }
kDict [ left ] [ right ] = value
for left in kDict . keys ( ) :
leftGlyph = parentFont [ left ]
if leftGlyph is not None :
for right in kDict [ left ] . keys ( ) :
value = kDict [ left ] [ right ]
if value != 0 :
rightIndex = parentFont . FindGlyph ( right )
if rightIndex != - 1 :
p = KerningPair ( rightIndex , value )
leftGlyph . kerning . append ( p )
def clear ( self ) :
""" clear all kerning """
# override base class here for speed
self . _kerning = { }
for glyph in self . getParent ( ) . naked ( ) . glyphs :
glyph . kerning = [ ]
def __add__ ( self , other ) :
""" Math operations on FL Kerning objects return RF Kerning objects
as they need to be orphaned objects and FL can ' t deal with that. " " "
from sets import Set
from robofab . objects . objectsRF import RKerning as _RKerning
new = _RKerning ( )
k = Set ( self . keys ( ) ) | Set ( other . keys ( ) )
for key in k :
new [ key ] = self . get ( key , 0 ) + other . get ( key , 0 )
return new
def __sub__ ( self , other ) :
""" Math operations on FL Kerning objects return RF Kerning objects
as they need to be orphaned objects and FL can ' t deal with that. " " "
from sets import Set
from robofab . objects . objectsRF import RKerning as _RKerning
new = _RKerning ( )
k = Set ( self . keys ( ) ) | Set ( other . keys ( ) )
for key in k :
new [ key ] = self . get ( key , 0 ) - other . get ( key , 0 )
return new
def __mul__ ( self , factor ) :
""" Math operations on FL Kerning objects return RF Kerning objects
as they need to be orphaned objects and FL can ' t deal with that. " " "
from robofab . objects . objectsRF import RKerning as _RKerning
new = _RKerning ( )
for name , value in self . items ( ) :
new [ name ] = value * factor
return new
__rmul__ = __mul__
def __div__ ( self , factor ) :
""" Math operations on FL Kerning objects return RF Kerning objects
as they need to be orphaned objects and FL can ' t deal with that. " " "
if factor == 0 :
raise ZeroDivisionError
return self . __mul__ ( 1.0 / factor )
class RLib ( BaseLib ) :
""" RoboFab wrapper for FL lib """
# XXX: As of FL 4.6 the customdata field in glyph objects is busted.
# storing anything there causes the glyph to become uneditable.
# however, the customdata field in font objects is stable.
def __init__ ( self , aDict ) :
self . update ( aDict )
def __setitem__ ( self , key , value ) :
# override baseclass so that data is stored in customdata field
super ( RLib , self ) . __setitem__ ( key , value )
self . _stashLib ( )
def __delitem__ ( self , key ) :
# override baseclass so that data is stored in customdata field
super ( RLib , self ) . __delitem__ ( key )
self . _stashLib ( )
def _stashLib ( self ) :
# write the plist into the customdata field of the FL object
if self . getParent ( ) is None :
return
if not self :
data = None
elif len ( self ) == 1 and " org.robofab.fontlab.customdata " in self :
data = self [ " org.robofab.fontlab.customdata " ] . data
else :
f = StringIO ( )
writePlist ( self , f )
data = f . getvalue ( )
f . close ( )
parent = self . getParent ( )
parent . naked ( ) . customdata = data
def update ( self , aDict ) :
# override baseclass so that data is stored in customdata field
super ( RLib , self ) . update ( aDict )
self . _stashLib ( )
def clear ( self ) :
# override baseclass so that data is stored in customdata field
super ( RLib , self ) . clear ( )
self . _stashLib ( )
def pop ( self , key ) :
# override baseclass so that data is stored in customdata field
i = super ( RLib , self ) . pop ( key )
self . _stashLib ( )
return i
def popitem ( self ) :
# override baseclass so that data is stored in customdata field
i = super ( RLib , self ) . popitem ( )
self . _stashLib ( )
return i
def setdefault ( self , key , value = None ) :
# override baseclass so that data is stored in customdata field
i = super ( RLib , self ) . setdefault ( key , value )
self . _stashLib ( )
return i
2009-02-28 15:47:24 +00:00
def _infoMapDict ( * * kwargs ) :
default = dict (
nakedAttribute = None ,
type = None ,
requiresSetNum = False ,
masterSpecific = False ,
libLocation = None ,
specialGetSet = False
)
default . update ( kwargs )
return default
def _flipDict ( d ) :
f = { }
for k , v in d . items ( ) :
f [ v ] = k
return f
_styleMapStyleName_fromFL = {
64 : " regular " ,
1 : " italic " ,
32 : " bold " ,
33 : " bold italic "
}
_styleMapStyleName_toFL = _flipDict ( _styleMapStyleName_fromFL )
_postscriptWindowsCharacterSet_fromFL = {
0 : 1 ,
1 : 2 ,
2 : 3 ,
77 : 4 ,
128 : 5 ,
129 : 6 ,
130 : 7 ,
134 : 8 ,
136 : 9 ,
161 : 10 ,
162 : 11 ,
163 : 12 ,
177 : 13 ,
178 : 14 ,
186 : 15 ,
200 : 16 ,
204 : 17 ,
222 : 18 ,
238 : 19 ,
255 : 20 ,
}
_postscriptWindowsCharacterSet_toFL = _flipDict ( _postscriptWindowsCharacterSet_fromFL )
_openTypeOS2Type_toFL = {
1 : 0x0002 ,
2 : 0x0004 ,
3 : 0x0008 ,
8 : 0x0100 ,
9 : 0x0200 ,
}
_openTypeOS2Type_fromFL = _flipDict ( _openTypeOS2Type_toFL )
_openTypeOS2WidthClass_fromFL = {
" Ultra-condensed " : 1 ,
" Extra-condensed " : 2 ,
" Condensed " : 3 ,
" Semi-condensed " : 4 ,
" Medium (normal) " : 5 ,
" Semi-expanded " : 6 ,
" Expanded " : 7 ,
" Extra-expanded " : 8 ,
" Ultra-expanded " : 9 ,
}
_openTypeOS2WidthClass_toFL = _flipDict ( _openTypeOS2WidthClass_fromFL )
_postscriptHintAttributes = set ( (
" postscriptBlueValues " ,
" postscriptOtherBlues " ,
" postscriptFamilyBlues " ,
" postscriptFamilyOtherBlues " ,
" postscriptStemSnapH " ,
" postscriptStemSnapV " ,
) )
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
class RInfo ( BaseInfo ) :
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
""" RoboFab wrapper for FL Font Info """
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
_title = " FLInfo "
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
_ufoToFLAttrMapping = {
" familyName " : _infoMapDict ( valueType = str , nakedAttribute = " family_name " ) ,
" styleName " : _infoMapDict ( valueType = str , nakedAttribute = " style_name " ) ,
" styleMapFamilyName " : _infoMapDict ( valueType = str , nakedAttribute = " menu_name " ) ,
" styleMapStyleName " : _infoMapDict ( valueType = str , nakedAttribute = " font_style " , specialGetSet = True ) ,
" versionMajor " : _infoMapDict ( valueType = int , nakedAttribute = " version_major " ) ,
" versionMinor " : _infoMapDict ( valueType = int , nakedAttribute = " version_minor " ) ,
" year " : _infoMapDict ( valueType = int , nakedAttribute = " year " ) ,
" copyright " : _infoMapDict ( valueType = str , nakedAttribute = " copyright " ) ,
" trademark " : _infoMapDict ( valueType = str , nakedAttribute = " trademark " ) ,
" unitsPerEm " : _infoMapDict ( valueType = int , nakedAttribute = " upm " ) ,
" descender " : _infoMapDict ( valueType = int , nakedAttribute = " descender " , masterSpecific = True ) ,
" xHeight " : _infoMapDict ( valueType = int , nakedAttribute = " x_height " , masterSpecific = True ) ,
" capHeight " : _infoMapDict ( valueType = int , nakedAttribute = " cap_height " , masterSpecific = True ) ,
" ascender " : _infoMapDict ( valueType = int , nakedAttribute = " ascender " , masterSpecific = True ) ,
" italicAngle " : _infoMapDict ( valueType = float , nakedAttribute = " italic_angle " ) ,
" note " : _infoMapDict ( valueType = str , nakedAttribute = " note " ) ,
" openTypeHeadCreated " : _infoMapDict ( valueType = str , nakedAttribute = None , specialGetSet = True ) , # i can't figure out the ttinfo.head_creation values
" openTypeHeadLowestRecPPEM " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.head_lowest_rec_ppem " ) ,
" openTypeHeadFlags " : _infoMapDict ( valueType = " intList " , nakedAttribute = None ) , # There is an attribute (ttinfo.head_flags), but no user interface.
" openTypeHheaAscender " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.hhea_ascender " ) ,
" openTypeHheaDescender " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.hhea_descender " ) ,
" openTypeHheaLineGap " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.hhea_line_gap " ) ,
" openTypeHheaCaretSlopeRise " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeHheaCaretSlopeRun " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeHheaCaretOffset " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeNameDesigner " : _infoMapDict ( valueType = str , nakedAttribute = " designer " ) ,
" openTypeNameDesignerURL " : _infoMapDict ( valueType = str , nakedAttribute = " designer_url " ) ,
" openTypeNameManufacturer " : _infoMapDict ( valueType = str , nakedAttribute = " source " ) ,
" openTypeNameManufacturerURL " : _infoMapDict ( valueType = str , nakedAttribute = " vendor_url " ) ,
" openTypeNameLicense " : _infoMapDict ( valueType = str , nakedAttribute = " license " ) ,
" openTypeNameLicenseURL " : _infoMapDict ( valueType = str , nakedAttribute = " license_url " ) ,
" openTypeNameVersion " : _infoMapDict ( valueType = str , nakedAttribute = " tt_version " ) ,
" openTypeNameUniqueID " : _infoMapDict ( valueType = str , nakedAttribute = " tt_u_id " ) ,
" openTypeNameDescription " : _infoMapDict ( valueType = str , nakedAttribute = " notice " ) ,
" openTypeNamePreferredFamilyName " : _infoMapDict ( valueType = str , nakedAttribute = " pref_family_name " ) ,
" openTypeNamePreferredSubfamilyName " : _infoMapDict ( valueType = str , nakedAttribute = " pref_style_name " ) ,
" openTypeNameCompatibleFullName " : _infoMapDict ( valueType = str , nakedAttribute = " mac_compatible " ) ,
" openTypeNameSampleText " : _infoMapDict ( valueType = str , nakedAttribute = None ) ,
" openTypeNameWWSFamilyName " : _infoMapDict ( valueType = str , nakedAttribute = None ) ,
" openTypeNameWWSSubfamilyName " : _infoMapDict ( valueType = str , nakedAttribute = None ) ,
" openTypeOS2WidthClass " : _infoMapDict ( valueType = int , nakedAttribute = " width " ) ,
" openTypeOS2WeightClass " : _infoMapDict ( valueType = int , nakedAttribute = " weight_code " , specialGetSet = True ) ,
" openTypeOS2Selection " : _infoMapDict ( valueType = " intList " , nakedAttribute = None ) , # ttinfo.os2_fs_selection only returns 0
" openTypeOS2VendorID " : _infoMapDict ( valueType = str , nakedAttribute = " vendor " ) ,
" openTypeOS2Panose " : _infoMapDict ( valueType = " intList " , nakedAttribute = " panose " , specialGetSet = True ) ,
" openTypeOS2FamilyClass " : _infoMapDict ( valueType = " intList " , nakedAttribute = " ttinfo.os2_s_family_class " , specialGetSet = True ) ,
" openTypeOS2UnicodeRanges " : _infoMapDict ( valueType = " intList " , nakedAttribute = " unicoderanges " ) ,
" openTypeOS2CodePageRanges " : _infoMapDict ( valueType = " intList " , nakedAttribute = " codepages " ) ,
" openTypeOS2TypoAscender " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_s_typo_ascender " ) ,
" openTypeOS2TypoDescender " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_s_typo_descender " ) ,
" openTypeOS2TypoLineGap " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_s_typo_line_gap " ) ,
" openTypeOS2WinAscent " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_us_win_ascent " ) ,
" openTypeOS2WinDescent " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_us_win_descent " , specialGetSet = True ) ,
" openTypeOS2Type " : _infoMapDict ( valueType = " intList " , nakedAttribute = " ttinfo.os2_fs_type " , specialGetSet = True ) ,
" openTypeOS2SubscriptXSize " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_subscript_x_size " ) ,
" openTypeOS2SubscriptYSize " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_subscript_y_size " ) ,
" openTypeOS2SubscriptXOffset " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_subscript_x_offset " ) ,
" openTypeOS2SubscriptYOffset " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_subscript_y_offset " ) ,
" openTypeOS2SuperscriptXSize " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_superscript_x_size " ) ,
" openTypeOS2SuperscriptYSize " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_superscript_y_size " ) ,
" openTypeOS2SuperscriptXOffset " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_superscript_x_offset " ) ,
" openTypeOS2SuperscriptYOffset " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_superscript_y_offset " ) ,
" openTypeOS2StrikeoutSize " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_strikeout_size " ) ,
" openTypeOS2StrikeoutPosition " : _infoMapDict ( valueType = int , nakedAttribute = " ttinfo.os2_y_strikeout_position " ) ,
" openTypeVheaVertTypoAscender " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeVheaVertTypoDescender " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeVheaVertTypoLineGap " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeVheaCaretSlopeRise " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeVheaCaretSlopeRun " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" openTypeVheaCaretOffset " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" postscriptFontName " : _infoMapDict ( valueType = str , nakedAttribute = " font_name " ) ,
" postscriptFullName " : _infoMapDict ( valueType = str , nakedAttribute = " full_name " ) ,
" postscriptSlantAngle " : _infoMapDict ( valueType = float , nakedAttribute = " slant_angle " ) ,
" postscriptUniqueID " : _infoMapDict ( valueType = int , nakedAttribute = " unique_id " ) ,
" postscriptUnderlineThickness " : _infoMapDict ( valueType = int , nakedAttribute = " underline_thickness " ) ,
" postscriptUnderlinePosition " : _infoMapDict ( valueType = int , nakedAttribute = " underline_position " ) ,
2010-05-05 17:14:56 +00:00
" postscriptIsFixedPitch " : _infoMapDict ( valueType = " boolint " , nakedAttribute = " is_fixed_pitch " ) ,
2009-02-28 15:47:24 +00:00
" postscriptBlueValues " : _infoMapDict ( valueType = " intList " , nakedAttribute = " blue_values " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptOtherBlues " : _infoMapDict ( valueType = " intList " , nakedAttribute = " other_blues " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptFamilyBlues " : _infoMapDict ( valueType = " intList " , nakedAttribute = " family_blues " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptFamilyOtherBlues " : _infoMapDict ( valueType = " intList " , nakedAttribute = " family_other_blues " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptStemSnapH " : _infoMapDict ( valueType = " intList " , nakedAttribute = " stem_snap_h " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptStemSnapV " : _infoMapDict ( valueType = " intList " , nakedAttribute = " stem_snap_v " , masterSpecific = True , requiresSetNum = True ) ,
" postscriptBlueFuzz " : _infoMapDict ( valueType = int , nakedAttribute = " blue_fuzz " , masterSpecific = True ) ,
" postscriptBlueShift " : _infoMapDict ( valueType = int , nakedAttribute = " blue_shift " , masterSpecific = True ) ,
" postscriptBlueScale " : _infoMapDict ( valueType = float , nakedAttribute = " blue_scale " , masterSpecific = True ) ,
2010-05-05 17:14:56 +00:00
" postscriptForceBold " : _infoMapDict ( valueType = " boolint " , nakedAttribute = " force_bold " , masterSpecific = True ) ,
2009-02-28 15:47:24 +00:00
" postscriptDefaultWidthX " : _infoMapDict ( valueType = int , nakedAttribute = " default_width " , masterSpecific = True ) ,
" postscriptNominalWidthX " : _infoMapDict ( valueType = int , nakedAttribute = None ) ,
" postscriptWeightName " : _infoMapDict ( valueType = str , nakedAttribute = " weight " ) ,
" postscriptDefaultCharacter " : _infoMapDict ( valueType = str , nakedAttribute = " default_character " ) ,
" postscriptWindowsCharacterSet " : _infoMapDict ( valueType = int , nakedAttribute = " ms_charset " , specialGetSet = True ) ,
" macintoshFONDFamilyID " : _infoMapDict ( valueType = int , nakedAttribute = " fond_id " ) ,
" macintoshFONDName " : _infoMapDict ( valueType = str , nakedAttribute = " apple_name " ) ,
}
_environmentOverrides = [ " width " , " openTypeOS2WidthClass " ] # ugh.
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def __init__ ( self , font ) :
super ( RInfo , self ) . __init__ ( )
self . _object = font
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _environmentSetAttr ( self , attr , value ) :
# special fontlab workarounds
if attr == " width " :
warn ( " The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute. " , DeprecationWarning )
attr = " openTypeOS2WidthClass "
if attr == " openTypeOS2WidthClass " :
if isinstance ( value , basestring ) and value not in _openTypeOS2WidthClass_toFL :
print " The openTypeOS2WidthClass value \" %s \" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now. " % value
self . _object . width = value
else :
self . _object . width = _openTypeOS2WidthClass_toFL [ value ]
return
# get the attribute data
data = self . _ufoToFLAttrMapping [ attr ]
flAttr = data [ " nakedAttribute " ]
valueType = data [ " valueType " ]
masterSpecific = data [ " masterSpecific " ]
requiresSetNum = data [ " requiresSetNum " ]
specialGetSet = data [ " specialGetSet " ]
# warn about setting attributes not supported by FL
if flAttr is None :
print " The attribute %s is not supported by FontLab. This data will not be set. " % attr
return
# make sure that the value is the proper type for FL
if valueType == " intList " :
value = [ int ( i ) for i in value ]
2010-05-05 17:14:56 +00:00
elif valueType == " boolint " :
value = int ( bool ( value ) )
2009-02-28 15:47:24 +00:00
elif valueType == str :
if value is None :
value = " "
2008-01-07 17:40:34 +00:00
value = value . encode ( LOCAL_ENCODING )
2009-02-28 15:47:24 +00:00
elif valueType == int and not isinstance ( value , int ) :
value = int ( round ( value ) )
elif not isinstance ( value , valueType ) :
value = valueType ( value )
# handle postscript hint bug in FL
if attr in _postscriptHintAttributes :
value = self . _handlePSHintBug ( attr , value )
# handle special cases
if specialGetSet :
attr = " _set_ %s " % attr
method = getattr ( self , attr )
return method ( value )
# set the value
obj = self . _object
if len ( flAttr . split ( " . " ) ) > 1 :
flAttrList = flAttr . split ( " . " )
for i in flAttrList [ : - 1 ] :
obj = getattr ( obj , i )
flAttr = flAttrList [ - 1 ]
## set the foo_num attribute if necessary
if requiresSetNum :
numAttr = flAttr + " _num "
setattr ( obj , numAttr , len ( value ) )
## set master 0 if the data is master specific
if masterSpecific :
subObj = getattr ( obj , flAttr )
if valueType == " intList " :
for index , v in enumerate ( value ) :
subObj [ 0 ] [ index ] = v
else :
subObj [ 0 ] = value
## otherwise use a regular set
else :
setattr ( obj , flAttr , value )
def _environmentGetAttr ( self , attr ) :
# special fontlab workarounds
if attr == " width " :
warn ( " The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute. " , DeprecationWarning )
attr = " openTypeOS2WidthClass "
if attr == " openTypeOS2WidthClass " :
value = self . _object . width
if value not in _openTypeOS2WidthClass_fromFL :
print " The existing openTypeOS2WidthClass value \" %s \" cannot be found in the OpenType OS/2 usWidthClass specification. " % value
return
else :
return _openTypeOS2WidthClass_fromFL [ value ]
# get the attribute data
data = self . _ufoToFLAttrMapping [ attr ]
flAttr = data [ " nakedAttribute " ]
valueType = data [ " valueType " ]
masterSpecific = data [ " masterSpecific " ]
specialGetSet = data [ " specialGetSet " ]
# warn about setting attributes not supported by FL
if flAttr is None :
if not _IN_UFO_EXPORT :
print " The attribute %s is not supported by FontLab. " % attr
return
# handle special cases
if specialGetSet :
attr = " _get_ %s " % attr
method = getattr ( self , attr )
return method ( )
# get the value
if len ( flAttr . split ( " . " ) ) > 1 :
flAttrList = flAttr . split ( " . " )
obj = self . _object
for i in flAttrList :
obj = getattr ( obj , i )
value = obj
else :
value = getattr ( self . _object , flAttr )
# grab the first master value if necessary
if masterSpecific :
value = value [ 0 ]
# convert if necessary
if valueType == " intList " :
value = [ int ( i ) for i in value ]
2010-05-05 17:14:56 +00:00
elif valueType == " boolint " :
value = bool ( value )
2009-02-28 15:47:24 +00:00
elif valueType == str :
if value is None :
pass
else :
value = unicode ( value , LOCAL_ENCODING )
elif not isinstance ( value , valueType ) :
value = valueType ( value )
return value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# ------------------------------
# individual attribute overrides
# ------------------------------
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# styleMapStyleName
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_styleMapStyleName ( self ) :
return _styleMapStyleName_fromFL [ self . _object . font_style ]
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_styleMapStyleName ( self , value ) :
value = _styleMapStyleName_toFL [ value ]
self . _object . font_style = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# # openTypeHeadCreated
#
# # fontlab epoch: 1969-12-31 19:00:00
#
# def _get_openTypeHeadCreated(self):
# value = self._object.ttinfo.head_creation
# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0)
# delta = datetime.timedelta(seconds=value[0])
# t = epoch - delta
# string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2))
# return string
#
# def _set_openTypeHeadCreated(self, value):
# date, time = value.split(" ")
# year, month, day = [int(i) for i in date.split("-")]
# hour, minute, second = [int(i) for i in time.split(":")]
# value = datetime.datetime(year, month, day, hour, minute, second)
# epoch = datetime.datetime(1969, 12, 31, 19, 0, 0)
# delta = epoch - value
# seconds = delta.seconds
# self._object.ttinfo.head_creation[0] = seconds
# openTypeOS2WeightClass
def _get_openTypeOS2WeightClass ( self ) :
value = self . _object . weight_code
if value == - 1 :
value = None
return value
def _set_openTypeOS2WeightClass ( self , value ) :
self . _object . weight_code = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# openTypeOS2WinDescent
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_openTypeOS2WinDescent ( self ) :
2011-05-17 15:53:58 +00:00
return self . _object . ttinfo . os2_us_win_descent
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_openTypeOS2WinDescent ( self , value ) :
2011-05-17 15:53:58 +00:00
if value < 0 :
raise ValueError ( " FontLab can only handle positive values for openTypeOS2WinDescent. " )
self . _object . ttinfo . os2_us_win_descent = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# openTypeOS2Type
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_openTypeOS2Type ( self ) :
value = self . _object . ttinfo . os2_fs_type
intList = [ ]
for bit , bitNumber in _openTypeOS2Type_fromFL . items ( ) :
if value & bit :
intList . append ( bitNumber )
return intList
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_openTypeOS2Type ( self , values ) :
value = 0
for bitNumber in values :
bit = _openTypeOS2Type_toFL [ bitNumber ]
value = value | bit
self . _object . ttinfo . os2_fs_type = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# openTypeOS2Panose
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_openTypeOS2Panose ( self ) :
return [ i for i in self . _object . panose ]
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_openTypeOS2Panose ( self , values ) :
for index , value in enumerate ( values ) :
self . _object . panose [ index ] = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# openTypeOS2FamilyClass
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_openTypeOS2FamilyClass ( self ) :
value = self . _object . ttinfo . os2_s_family_class
for classID in range ( 15 ) :
classValue = classID * 256
if classValue > value :
classID - = 1
classValue = classID * 256
break
subclassID = value - classValue
return [ classID , subclassID ]
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_openTypeOS2FamilyClass ( self , values ) :
classID , subclassID = values
classID = classID * 256
value = classID + subclassID
self . _object . ttinfo . os2_s_family_class = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# postscriptWindowsCharacterSet
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_postscriptWindowsCharacterSet ( self ) :
value = self . _object . ms_charset
value = _postscriptWindowsCharacterSet_fromFL [ value ]
return value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _set_postscriptWindowsCharacterSet ( self , value ) :
value = _postscriptWindowsCharacterSet_toFL [ value ]
self . _object . ms_charset = value
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
# -----------------
# FL bug workaround
# -----------------
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _handlePSHintBug ( self , attribute , values ) :
""" Function to handle problems with FontLab not allowing the max number of
alignment zones to be set to the max number .
Input : the name of the zones and the values to be set
Output : a warning when there are too many values to be set
and the max values which FontLab will allow .
"""
originalValues = values
truncatedLength = None
if attribute in ( " postscriptStemSnapH " , " postscriptStemSnapV " ) :
if len ( values ) > 10 :
values = values [ : 10 ]
truncatedLength = 10
elif attribute in ( " postscriptBlueValues " , " postscriptFamilyBlues " ) :
if len ( values ) > 12 :
values = values [ : 12 ]
truncatedLength = 12
elif attribute in ( " postscriptOtherBlues " , " postscriptFamilyOtherBlues " ) :
if len ( values ) > 8 :
values = values [ : 8 ]
truncatedLength = 8
if truncatedLength is not None :
print " * * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s . " % ( truncatedLength , attribute , str ( originalValues [ truncatedLength : ] ) )
return values
class RFeatures ( BaseFeatures ) :
_title = " FLFeatures "
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def __init__ ( self , font ) :
super ( RFeatures , self ) . __init__ ( )
self . _object = font
2008-01-07 17:40:34 +00:00
2009-02-28 15:47:24 +00:00
def _get_text ( self ) :
naked = self . _object
features = [ ]
if naked . ot_classes :
features . append ( _normalizeLineEndings ( naked . ot_classes ) )
for feature in naked . features :
features . append ( _normalizeLineEndings ( feature . value ) )
return " " . join ( features )
def _set_text ( self , value ) :
classes , features = splitFeaturesForFontLab ( value )
naked = self . _object
naked . ot_classes = classes
naked . features . clean ( )
for featureName , featureText in features :
f = Feature ( featureName , featureText )
naked . features . append ( f )
text = property ( _get_text , _set_text , doc = " raw feature text. " )
2008-01-07 17:40:34 +00:00