2016-11-15 13:27:39 +01:00
# -*- coding: utf-8 -*-
2016-11-28 17:12:46 +01:00
from __future__ import print_function , division , absolute_import
2016-12-02 12:22:07 +01:00
import logging
2016-11-15 13:27:39 +01:00
import os
import xml . etree . ElementTree as ET
2016-12-02 12:22:07 +01:00
from mutatorMath . objects . location import biasFromLocations , Location
2016-11-15 13:27:39 +01:00
"""
designSpaceDocument
- read and write designspace files
- axes must be defined .
- warpmap is stored in its axis element
"""
2016-11-26 14:45:56 +01:00
__all__ = [ ' DesignSpaceDocumentError ' , ' BaseDocReader ' , ' DesignSpaceDocument ' , ' SourceDescriptor ' , ' InstanceDescriptor ' , ' AxisDescriptor ' , ' BaseDocReader ' , ' BaseDocWriter ' ]
2016-11-15 20:15:04 +00:00
2016-11-28 17:12:46 +01:00
2016-11-15 13:27:39 +01:00
class DesignSpaceDocumentError ( Exception ) :
def __init__ ( self , msg , obj = None ) :
self . msg = msg
self . obj = obj
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def __str__ ( self ) :
return repr ( self . msg ) + repr ( self . obj )
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def _indent ( elem , whitespace = " " , level = 0 ) :
# taken from http://effbot.org/zone/element-lib.htm#prettyprint
i = " \n " + level * whitespace
if len ( elem ) :
if not elem . text or not elem . text . strip ( ) :
elem . text = i + whitespace
if not elem . tail or not elem . tail . strip ( ) :
elem . tail = i
for elem in elem :
_indent ( elem , whitespace , level + 1 )
if not elem . tail or not elem . tail . strip ( ) :
elem . tail = i
else :
if level and ( not elem . tail or not elem . tail . strip ( ) ) :
elem . tail = i
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
class SimpleDescriptor ( object ) :
""" Containers for a bunch of attributes """
def compare ( self , other ) :
# test if this object contains the same data as the other
for attr in self . _attrs :
try :
2016-11-15 20:15:04 +00:00
assert ( getattr ( self , attr ) == getattr ( other , attr ) )
2016-11-15 13:27:39 +01:00
except AssertionError :
2016-11-15 20:16:48 +00:00
print ( " failed attribute " , attr , getattr ( self , attr ) , " != " , getattr ( other , attr ) )
2016-11-15 13:27:39 +01:00
class SourceDescriptor ( SimpleDescriptor ) :
""" Simple container for data related to the source """
2016-11-15 20:15:04 +00:00
flavor = " source "
_attrs = [ ' path ' , ' name ' ,
' location ' , ' copyLib ' ,
' copyGroups ' , ' copyFeatures ' ,
' muteKerning ' , ' muteInfo ' ,
' mutedGlyphNames ' ,
' familyName ' , ' styleName ' ]
2016-11-15 13:27:39 +01:00
def __init__ ( self ) :
self . path = None
self . name = None
self . location = None
self . copyLib = False
self . copyInfo = False
self . copyGroups = False
self . copyFeatures = False
self . muteKerning = False
self . muteInfo = False
self . mutedGlyphNames = [ ]
self . familyName = None
self . styleName = None
class InstanceDescriptor ( SimpleDescriptor ) :
""" Simple container for data related to the instance """
2016-11-15 20:15:04 +00:00
flavor = " instance "
_attrs = [ ' path ' , ' name ' ,
' location ' , ' familyName ' ,
' styleName ' , ' postScriptFontName ' ,
' styleMapFamilyName ' ,
' styleMapStyleName ' ,
' kerning ' , ' info ' ]
2016-11-15 13:27:39 +01:00
def __init__ ( self ) :
self . path = None
self . name = None
self . location = None
self . familyName = None
self . styleName = None
self . postScriptFontName = None
self . styleMapFamilyName = None
self . styleMapStyleName = None
self . glyphs = { }
self . kerning = True
self . info = True
2016-12-02 12:22:07 +01:00
def tagForAxisName ( name ) :
# try to find or make a tag name for this axis name
names = {
' weight ' : ( ' wght ' , dict ( en = ' Weight ' ) ) ,
' width ' : ( ' wdth ' , dict ( en = ' Width ' ) ) ,
' optical ' : ( ' opsz ' , dict ( en = ' Optical Size ' ) ) ,
' slant ' : ( ' slnt ' , dict ( en = ' Slant ' ) ) ,
' italic ' : ( ' ital ' , dict ( en = ' Italic ' ) ) ,
}
if name . lower ( ) in names :
return names [ name . lower ( ) ]
if len ( name ) < 4 :
tag = name + " * " * ( 4 - len ( name ) )
else :
tag = name [ : 4 ]
return tag , dict ( en = name )
2016-11-15 13:27:39 +01:00
class AxisDescriptor ( SimpleDescriptor ) :
""" Simple container for the axis data """
2016-11-15 20:15:04 +00:00
flavor = " axis "
2016-11-15 13:27:39 +01:00
_attrs = [ ' tag ' , ' name ' , ' maximum ' , ' minimum ' , ' default ' , ' map ' ]
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def __init__ ( self ) :
2016-11-15 20:15:04 +00:00
self . tag = None # opentype tag for this axis
self . name = None # name of the axis used in locations
self . labelNames = { } # names for UI purposes, if this is not a standard axis,
2016-11-15 13:27:39 +01:00
self . minimum = None
self . maximum = None
self . default = None
self . map = [ ]
2016-11-20 10:05:55 +01:00
def serialize ( self ) :
2016-11-20 10:14:25 +01:00
# output to a dict, used in testing
2016-11-20 10:05:55 +01:00
d = dict ( tag = self . tag ,
name = self . name ,
labelNames = self . labelNames ,
maximum = self . maximum ,
minimum = self . minimum ,
default = self . default ,
map = self . map ,
)
return d
2016-11-15 13:27:39 +01:00
class BaseDocWriter ( object ) :
_whiteSpace = " "
axisDescriptorClass = AxisDescriptor
sourceDescriptorClass = SourceDescriptor
instanceDescriptorClass = InstanceDescriptor
2016-11-15 20:15:04 +00:00
2016-12-02 12:22:07 +01:00
@classmethod
def getAxisDecriptor ( cls ) :
return cls . axisDescriptorClass ( )
2016-11-15 13:27:39 +01:00
def __init__ ( self , documentPath , documentObject ) :
self . path = documentPath
self . documentObject = documentObject
self . toolVersion = 3
self . root = ET . Element ( " designspace " )
2016-11-15 20:15:04 +00:00
self . root . attrib [ ' format ' ] = " %d " % self . toolVersion
2016-11-15 13:27:39 +01:00
self . root . append ( ET . Element ( " axes " ) )
self . root . append ( ET . Element ( " sources " ) )
self . root . append ( ET . Element ( " instances " ) )
self . axes = [ ]
def newDefaultLocation ( self ) :
loc = { }
for axisDescriptor in self . axes :
loc [ axisDescriptor . name ] = axisDescriptor . default
return loc
def write ( self , pretty = True ) :
for axisObject in self . documentObject . axes :
self . _addAxis ( axisObject )
for sourceObject in self . documentObject . sources :
self . _addSource ( sourceObject )
for instanceObject in self . documentObject . instances :
self . _addInstance ( instanceObject )
if pretty :
_indent ( self . root , whitespace = self . _whiteSpace )
tree = ET . ElementTree ( self . root )
2016-11-15 20:17:57 +00:00
tree . write ( self . path , encoding = " utf-8 " , method = ' xml ' , xml_declaration = True )
2016-11-15 13:27:39 +01:00
def _makeLocationElement ( self , locationObject , name = None ) :
""" Convert Location dict to a locationElement. """
locElement = ET . Element ( " location " )
if name is not None :
2016-11-15 20:15:04 +00:00
locElement . attrib [ ' name ' ] = name
2016-11-15 13:27:39 +01:00
defaultLoc = self . newDefaultLocation ( )
validatedLocation = { }
for axisName , axisValue in defaultLoc . items ( ) :
# update the location dict with missing default axis values
2016-11-20 15:48:22 +01:00
validatedLocation [ axisName ] = locationObject . get ( axisName , axisValue )
2016-11-15 13:27:39 +01:00
for dimensionName , dimensionValue in validatedLocation . items ( ) :
2016-11-15 20:15:04 +00:00
dimElement = ET . Element ( ' dimension ' )
dimElement . attrib [ ' name ' ] = dimensionName
if type ( dimensionValue ) == tuple :
dimElement . attrib [ ' xvalue ' ] = self . intOrFloat ( dimensionValue [ 0 ] )
dimElement . attrib [ ' yvalue ' ] = self . intOrFloat ( dimensionValue [ 1 ] )
else :
dimElement . attrib [ ' xvalue ' ] = self . intOrFloat ( dimensionValue )
locElement . append ( dimElement )
2016-11-15 13:27:39 +01:00
return locElement , validatedLocation
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def intOrFloat ( self , num ) :
if int ( num ) == num :
2016-11-15 20:15:04 +00:00
return " %d " % num
return " %f " % num
2016-11-15 13:27:39 +01:00
def _addAxis ( self , axisObject ) :
self . axes . append ( axisObject )
axisElement = ET . Element ( ' axis ' )
axisElement . attrib [ ' tag ' ] = axisObject . tag
axisElement . attrib [ ' name ' ] = axisObject . name
axisElement . attrib [ ' minimum ' ] = str ( axisObject . minimum )
axisElement . attrib [ ' maximum ' ] = str ( axisObject . maximum )
axisElement . attrib [ ' default ' ] = str ( axisObject . default )
for languageCode , labelName in axisObject . labelNames . items ( ) :
2016-11-20 10:05:55 +01:00
languageElement = ET . Element ( ' labelname ' )
2016-11-15 13:27:39 +01:00
languageElement . attrib [ u ' xml:lang ' ] = languageCode
languageElement . text = labelName
axisElement . append ( languageElement )
if axisObject . map :
for inputValue , outputValue in axisObject . map :
mapElement = ET . Element ( ' map ' )
mapElement . attrib [ ' input ' ] = str ( inputValue )
mapElement . attrib [ ' output ' ] = str ( outputValue )
axisElement . append ( mapElement )
self . root . findall ( ' .axes ' ) [ 0 ] . append ( axisElement )
def _addInstance ( self , instanceObject ) :
instanceElement = ET . Element ( ' instance ' )
if instanceObject . name is not None :
instanceElement . attrib [ ' name ' ] = instanceObject . name
if instanceObject . familyName is not None :
instanceElement . attrib [ ' familyname ' ] = instanceObject . familyName
if instanceObject . styleName is not None :
instanceElement . attrib [ ' stylename ' ] = instanceObject . styleName
if instanceObject . location is not None :
locationElement , instanceObject . location = self . _makeLocationElement ( instanceObject . location )
instanceElement . append ( locationElement )
if instanceObject . path is not None :
pathRelativeToDocument = os . path . relpath ( instanceObject . path , os . path . dirname ( self . path ) )
instanceElement . attrib [ ' filename ' ] = pathRelativeToDocument
if instanceObject . postScriptFontName is not None :
instanceElement . attrib [ ' postscriptfontname ' ] = instanceObject . postScriptFontName
if instanceObject . styleMapFamilyName is not None :
instanceElement . attrib [ ' stylemapfamilyname ' ] = instanceObject . styleMapFamilyName
if instanceObject . styleMapStyleName is not None :
instanceElement . attrib [ ' stylemapstylename ' ] = instanceObject . styleMapStyleName
if instanceObject . glyphs :
if instanceElement . findall ( ' .glyphs ' ) == [ ] :
glyphsElement = ET . Element ( ' glyphs ' )
instanceElement . append ( glyphsElement )
glyphsElement = instanceElement . findall ( ' .glyphs ' ) [ 0 ]
for glyphName , data in instanceObject . glyphs . items ( ) :
glyphElement = self . _writeGlyphElement ( instanceElement , instanceObject , glyphName , data )
glyphsElement . append ( glyphElement )
if instanceObject . kerning :
kerningElement = ET . Element ( ' kerning ' )
instanceElement . append ( kerningElement )
if instanceObject . info :
infoElement = ET . Element ( ' info ' )
instanceElement . append ( infoElement )
self . root . findall ( ' .instances ' ) [ 0 ] . append ( instanceElement )
def _addSource ( self , sourceObject ) :
sourceElement = ET . Element ( " source " )
pathRelativeToDocument = os . path . relpath ( sourceObject . path , os . path . dirname ( self . path ) )
sourceElement . attrib [ ' filename ' ] = pathRelativeToDocument
if sourceObject . name is not None :
sourceElement . attrib [ ' name ' ] = sourceObject . name
if sourceObject . familyName is not None :
sourceElement . attrib [ ' familyname ' ] = sourceObject . familyName
if sourceObject . styleName is not None :
sourceElement . attrib [ ' stylename ' ] = sourceObject . styleName
if sourceObject . copyLib :
libElement = ET . Element ( ' lib ' )
libElement . attrib [ ' copy ' ] = " 1 "
sourceElement . append ( libElement )
if sourceObject . copyGroups :
groupsElement = ET . Element ( ' groups ' )
groupsElement . attrib [ ' copy ' ] = " 1 "
sourceElement . append ( groupsElement )
if sourceObject . copyFeatures :
featuresElement = ET . Element ( ' features ' )
featuresElement . attrib [ ' copy ' ] = " 1 "
sourceElement . append ( featuresElement )
if sourceObject . copyInfo or sourceObject . muteInfo :
infoElement = ET . Element ( ' info ' )
if sourceObject . copyInfo :
infoElement . attrib [ ' copy ' ] = " 1 "
if sourceObject . muteInfo :
infoElement . attrib [ ' mute ' ] = " 1 "
sourceElement . append ( infoElement )
if sourceObject . muteKerning :
kerningElement = ET . Element ( " kerning " )
kerningElement . attrib [ " mute " ] = ' 1 '
sourceElement . append ( kerningElement )
if sourceObject . mutedGlyphNames :
for name in sourceObject . mutedGlyphNames :
glyphElement = ET . Element ( " glyph " )
glyphElement . attrib [ " name " ] = name
glyphElement . attrib [ " mute " ] = ' 1 '
sourceElement . append ( glyphElement )
locationElement , sourceObject . location = self . _makeLocationElement ( sourceObject . location )
sourceElement . append ( locationElement )
self . root . findall ( ' .sources ' ) [ 0 ] . append ( sourceElement )
def _writeGlyphElement ( self , instanceElement , instanceObject , glyphName , data ) :
glyphElement = ET . Element ( ' glyph ' )
if data . get ( ' mute ' ) :
glyphElement . attrib [ ' mute ' ] = " 1 "
if data . get ( ' unicodeValue ' ) is not None :
glyphElement . attrib [ ' unicode ' ] = hex ( data . get ( ' unicodeValue ' ) )
if data . get ( ' instanceLocation ' ) is not None :
locationElement , data [ ' instanceLocation ' ] = self . _makeLocationElement ( data . get ( ' instanceLocation ' ) )
glyphElement . append ( locationElement )
if glyphName is not None :
glyphElement . attrib [ ' name ' ] = glyphName
if data . get ( ' note ' ) is not None :
noteElement = ET . Element ( ' note ' )
noteElement . text = data . get ( ' note ' )
glyphElement . append ( noteElement )
if data . get ( ' masters ' ) is not None :
mastersElement = ET . Element ( " masters " )
for m in data . get ( ' masters ' ) :
masterElement = ET . Element ( " master " )
if m . get ( ' glyphName ' ) is not None :
masterElement . attrib [ ' glyphname ' ] = m . get ( ' glyphName ' )
if m . get ( ' font ' ) is not None :
masterElement . attrib [ ' source ' ] = m . get ( ' font ' )
if m . get ( ' location ' ) is not None :
locationElement , m [ ' location ' ] = self . _makeLocationElement ( m . get ( ' location ' ) )
masterElement . append ( locationElement )
mastersElement . append ( masterElement )
glyphElement . append ( mastersElement )
return glyphElement
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
class BaseDocReader ( object ) :
axisDescriptorClass = AxisDescriptor
sourceDescriptorClass = SourceDescriptor
instanceDescriptorClass = InstanceDescriptor
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def __init__ ( self , documentPath , documentObject ) :
self . path = documentPath
self . documentObject = documentObject
self . documentObject . formatVersion = 0
tree = ET . parse ( self . path )
self . root = tree . getroot ( )
self . documentObject . formatVersion = int ( self . root . attrib . get ( " format " , 0 ) )
self . axes = [ ]
self . sources = [ ]
self . instances = [ ]
self . axisDefaults = { }
2016-12-02 16:53:39 +01:00
self . _strictAxisNames = True
2016-11-15 13:27:39 +01:00
def read ( self ) :
self . readAxes ( )
self . readSources ( )
self . readInstances ( )
def getSourcePaths ( self , makeGlyphs = True , makeKerning = True , makeInfo = True ) :
paths = [ ]
for name in self . documentObject . sources . keys ( ) :
paths . append ( self . documentObject . sources [ name ] [ 0 ] . path )
return paths
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def newDefaultLocation ( self ) :
loc = { }
for axisDescriptor in self . axes :
loc [ axisDescriptor . name ] = axisDescriptor . default
return loc
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
def readAxes ( self ) :
2016-11-15 20:15:04 +00:00
# read the axes elements, including the warp map.
2016-11-15 13:27:39 +01:00
axes = [ ]
for axisElement in self . root . findall ( " .axes/axis " ) :
axisObject = self . axisDescriptorClass ( )
axisObject . name = axisElement . attrib . get ( " name " )
axisObject . minimum = float ( axisElement . attrib . get ( " minimum " ) )
axisObject . maximum = float ( axisElement . attrib . get ( " maximum " ) )
# we need to check if there is an attribute named "initial"
if axisElement . attrib . get ( " default " ) is None :
if axisElement . attrib . get ( " initial " ) is not None :
axisObject . default = float ( axisElement . attrib . get ( " initial " ) )
else :
axisObject . default = axisObject . minimum
else :
axisObject . default = float ( axisElement . attrib . get ( " default " ) )
axisObject . tag = axisElement . attrib . get ( " tag " )
for mapElement in axisElement . findall ( ' map ' ) :
a = float ( mapElement . attrib [ ' input ' ] )
b = float ( mapElement . attrib [ ' output ' ] )
axisObject . map . append ( ( a , b ) )
2016-11-20 10:05:55 +01:00
for labelNameElement in axisElement . findall ( ' labelname ' ) :
# Note: elementtree reads the xml:lang attribute name as
# '{http://www.w3.org/XML/1998/namespace}lang'
for key , lang in labelNameElement . items ( ) :
labelName = labelNameElement . text
axisObject . labelNames [ lang ] = labelName
2016-11-15 13:27:39 +01:00
self . documentObject . axes . append ( axisObject )
self . axisDefaults [ axisObject . name ] = axisObject . default
2016-12-02 16:53:39 +01:00
if not axes :
self . _strictAxisNames = False
2016-11-15 13:27:39 +01:00
def readSources ( self ) :
for sourceElement in self . root . findall ( " .sources/source " ) :
filename = sourceElement . attrib . get ( ' filename ' )
sourcePath = os . path . abspath ( os . path . join ( os . path . dirname ( self . path ) , filename ) )
sourceName = sourceElement . attrib . get ( ' name ' )
sourceObject = self . sourceDescriptorClass ( )
sourceObject . path = sourcePath
sourceObject . name = sourceName
familyName = sourceElement . attrib . get ( " familyname " )
if familyName is not None :
sourceObject . familyName = familyName
styleName = sourceElement . attrib . get ( " stylename " )
if styleName is not None :
sourceObject . styleName = styleName
sourceObject . location = self . locationFromElement ( sourceElement )
for libElement in sourceElement . findall ( ' .lib ' ) :
if libElement . attrib . get ( ' copy ' ) == ' 1 ' :
sourceObject . copyLib = True
for groupsElement in sourceElement . findall ( ' .groups ' ) :
if groupsElement . attrib . get ( ' copy ' ) == ' 1 ' :
sourceObject . copyGroups = True
for infoElement in sourceElement . findall ( " .info " ) :
if infoElement . attrib . get ( ' copy ' ) == ' 1 ' :
sourceObject . copyInfo = True
if infoElement . attrib . get ( ' mute ' ) == ' 1 ' :
sourceObject . muteInfo = True
for featuresElement in sourceElement . findall ( " .features " ) :
if featuresElement . attrib . get ( ' copy ' ) == ' 1 ' :
sourceObject . copyFeatures = True
for glyphElement in sourceElement . findall ( " .glyph " ) :
glyphName = glyphElement . attrib . get ( ' name ' )
if glyphName is None :
continue
if glyphElement . attrib . get ( ' mute ' ) == ' 1 ' :
sourceObject . mutedGlyphNames . append ( glyphName )
for kerningElement in sourceElement . findall ( " .kerning " ) :
if kerningElement . attrib . get ( ' mute ' ) == ' 1 ' :
sourceObject . muteKerning = True
self . documentObject . sources . append ( sourceObject )
def locationFromElement ( self , element ) :
elementLocation = None
for locationElement in element . findall ( ' .location ' ) :
elementLocation = self . readLocationElement ( locationElement )
break
return elementLocation
def readLocationElement ( self , locationElement ) :
""" Format 0 location reader """
loc = { }
for dimensionElement in locationElement . findall ( " .dimension " ) :
dimName = dimensionElement . attrib . get ( " name " )
2016-12-02 16:53:39 +01:00
if self . _strictAxisNames and dimName not in self . axisDefaults :
# In case the document contains axis definitions,
# then we should only read the axes we know about.
# However, if the document does not contain axes,
# then we need to create them after reading.
2016-11-15 13:27:39 +01:00
continue
xValue = yValue = None
try :
xValue = dimensionElement . attrib . get ( ' xvalue ' )
xValue = float ( xValue )
except ValueError :
self . logger . info ( " KeyError in readLocation xValue %3.3f " , xValue )
try :
yValue = dimensionElement . attrib . get ( ' yvalue ' )
if yValue is not None :
yValue = float ( yValue )
except ValueError :
pass
if yValue is not None :
loc [ dimName ] = ( xValue , yValue )
else :
loc [ dimName ] = xValue
return loc
def readInstances ( self , makeGlyphs = True , makeKerning = True , makeInfo = True ) :
instanceElements = self . root . findall ( ' .instances/instance ' )
for instanceElement in self . root . findall ( ' .instances/instance ' ) :
self . _readSingleInstanceElement ( instanceElement , makeGlyphs = makeGlyphs , makeKerning = makeKerning , makeInfo = makeInfo )
def _readSingleInstanceElement ( self , instanceElement , makeGlyphs = True , makeKerning = True , makeInfo = True ) :
filename = instanceElement . attrib . get ( ' filename ' )
if filename is not None :
instancePath = os . path . join ( os . path . dirname ( self . documentObject . path ) , filename )
filenameTokenForResults = os . path . basename ( filename )
else :
instancePath = None
instanceObject = self . instanceDescriptorClass ( )
instanceObject . path = instancePath
name = instanceElement . attrib . get ( " name " )
if name is not None :
instanceObject . name = name
familyname = instanceElement . attrib . get ( ' familyname ' )
if familyname is not None :
instanceObject . familyName = familyname
stylename = instanceElement . attrib . get ( ' stylename ' )
if stylename is not None :
instanceObject . styleName = stylename
postScriptFontName = instanceElement . attrib . get ( ' postscriptfontname ' )
if postScriptFontName is not None :
instanceObject . postScriptFontName = postScriptFontName
styleMapFamilyName = instanceElement . attrib . get ( ' stylemapfamilyname ' )
if styleMapFamilyName is not None :
instanceObject . styleMapFamilyName = styleMapFamilyName
styleMapStyleName = instanceElement . attrib . get ( ' stylemapstylename ' )
if styleMapStyleName is not None :
instanceObject . styleMapStyleName = styleMapStyleName
instanceLocation = self . locationFromElement ( instanceElement )
if instanceLocation is not None :
instanceObject . location = instanceLocation
for glyphElement in instanceElement . findall ( ' .glyphs/glyph ' ) :
self . readGlyphElement ( glyphElement , instanceObject )
for infoElement in instanceElement . findall ( " info " ) :
self . readInfoElement ( infoElement , instanceObject )
self . documentObject . instances . append ( instanceObject )
def readInfoElement ( self , infoElement , instanceObject ) :
""" Read the info element.
: :
< info / >
2016-11-15 20:15:04 +00:00
2016-11-15 13:27:39 +01:00
Let ' s drop support for a different location for the info. Never needed it.
"""
infoLocation = self . locationFromElement ( infoElement )
instanceObject . info = True
def readKerningElement ( self , kerningElement , instanceObject ) :
""" Read the kerning element.
: :
Make kerning at the location and with the masters specified at the instance level .
< kerning / >
"""
kerningLocation = self . locationFromElement ( kerningElement )
instanceObject . addKerning ( kerningLocation )
def readGlyphElement ( self , glyphElement , instanceObject ) :
"""
Read the glyph element .
: :
< glyph name = " b " unicode = " 0x62 " / >
< glyph name = " b " / >
< glyph name = " b " >
< master location = " location-token-bbb " source = " master-token-aaa2 " / >
< master glyphname = " b.alt1 " location = " location-token-ccc " source = " master-token-aaa3 " / >
< note >
This is an instance from an anisotropic interpolation .
< / note >
< / glyph >
"""
glyphData = { }
glyphName = glyphElement . attrib . get ( ' name ' )
if glyphName is None :
raise DesignSpaceDocumentError ( " Glyph object without name attribute. " )
mute = glyphElement . attrib . get ( " mute " )
if mute == " 1 " :
glyphData [ ' mute ' ] = True
unicodeValue = glyphElement . attrib . get ( ' unicode ' )
if unicodeValue is not None :
unicodeValue = int ( unicodeValue , 16 )
glyphData [ ' unicodeValue ' ] = unicodeValue
note = None
for noteElement in glyphElement . findall ( ' .note ' ) :
glyphData [ ' note ' ] = noteElement . text
break
instanceLocation = self . locationFromElement ( glyphElement )
if instanceLocation is not None :
glyphData [ ' instanceLocation ' ] = instanceLocation
glyphSources = None
for masterElement in glyphElement . findall ( ' .masters/master ' ) :
fontSourceName = masterElement . attrib . get ( ' source ' )
sourceLocation = self . locationFromElement ( masterElement )
masterGlyphName = masterElement . attrib . get ( ' glyphname ' )
if masterGlyphName is None :
# if we don't read a glyphname, use the one we have
masterGlyphName = glyphName
2016-11-15 20:15:04 +00:00
d = dict ( font = fontSourceName ,
location = sourceLocation ,
glyphName = masterGlyphName )
2016-11-15 13:27:39 +01:00
if glyphSources is None :
glyphSources = [ ]
glyphSources . append ( d )
if glyphSources is not None :
glyphData [ ' masters ' ] = glyphSources
instanceObject . glyphs [ glyphName ] = glyphData
class DesignSpaceDocument ( object ) :
""" Read, write data from the designspace file """
2016-11-22 22:47:34 +01:00
def __init__ ( self , readerClass = None , writerClass = None , fontClass = None ) :
2016-12-02 12:22:07 +01:00
self . logger = logging . getLogger ( " DesignSpaceDocumentLog " )
2016-11-15 13:27:39 +01:00
self . path = None
self . formatVersion = None
self . sources = [ ]
self . instances = [ ]
self . axes = [ ]
2016-12-02 12:22:07 +01:00
self . default = None # name of the default master
self . defaultLoc = None
2016-11-15 13:27:39 +01:00
#
if readerClass is not None :
self . readerClass = readerClass
else :
self . readerClass = BaseDocReader
if writerClass is not None :
self . writerClass = writerClass
else :
self . writerClass = BaseDocWriter
2016-11-22 22:47:34 +01:00
if fontClass is not None :
self . fontClass = fontClass
else :
from defcon . objects . font import Font
self . fontClass = Font
2016-11-15 13:27:39 +01:00
def read ( self , path ) :
self . path = path
reader = self . readerClass ( path , self )
reader . read ( )
def write ( self , path ) :
writer = self . writerClass ( path , self )
writer . write ( )
def addSource ( self , sourceDescriptor ) :
self . sources . append ( sourceDescriptor )
def addInstance ( self , instanceDescriptor ) :
self . instances . append ( instanceDescriptor )
def addAxis ( self , axisDescriptor ) :
self . axes . append ( axisDescriptor )
def newDefaultLocation ( self ) :
loc = { }
for axisDescriptor in self . axes :
loc [ axisDescriptor . name ] = axisDescriptor . default
return loc
2016-11-22 22:47:34 +01:00
def getFonts ( self ) :
# convenience method that delivers the masters and their locations
# so someone can build a thing for a thing.
fonts = [ ]
for sourceDescriptor in self . sources :
if sourceDescriptor . path is not None :
if os . path . exists ( sourceDescriptor . path ) :
f = self . fontClass ( sourceDescriptor . path )
fonts . append ( ( f , sourceDescriptor . location ) )
return fonts
2016-12-02 12:22:07 +01:00
def newAxisDescriptor ( self ) :
# Ask the writer class to make us a new axisDescriptor
return self . writerClass . getAxisDecriptor ( )
2016-11-26 14:45:56 +01:00
def getAxisOrder ( self ) :
names = [ ]
for axisDescriptor in self . axes :
names . append ( axisDescriptor . name )
return names
2016-12-02 12:22:07 +01:00
def check ( self ) :
"""
After reading we need to make sure we have a valid designspace .
This means making repairs if things are missing
- check if we have axes and deduce them from the masters if they ' re missing
- that can include axes referenced in masters , instances , glyphs .
- if no default is assigned , use mutatormath to find out .
- record the default in the designspace
- report all the changes in a log
- save a " repaired " version of the doc
"""
self . checkAxes ( )
self . checkDefault ( )
def checkDefault ( self ) :
""" Check the sources for a copyInfo flag. """
flaggedDefaultCandidate = None
for sourceDescriptor in self . sources :
names = set ( )
if sourceDescriptor . copyInfo :
# we choose you!
flaggedDefaultCandidate = sourceDescriptor
masterLocations = [ src . location for src in self . sources ]
mutatorBias = biasFromLocations ( masterLocations , preferOrigin = False )
c = [ src for src in self . sources if src . location == mutatorBias ]
if c :
mutatorDefaultCandidate = c [ 0 ]
else :
mutatorDefaultCandidate = None
# what are we going to do?
if flaggedDefaultCandidate is not None :
if mutatorDefaultCandidate is not None :
if mutatorDefaultCandidate . name != flaggedDefaultCandidate . name :
# warn if we have a conflict
self . logger . info ( " Note: conflicting default masters: \n \t Using %s as default \n \t Mutator found %s " % ( flaggedDefaultCandidate . name , mutatorDefaultCandidate . name ) )
self . default = flaggedDefaultCandidate
self . defaultLoc = self . default . location
else :
# we have no flagged default candidate
# let's use the one from mutator
if flaggedDefaultCandidate is None and mutatorDefaultCandidate is not None :
# we didn't have a flag, use the one selected by mutator
self . default = mutatorDefaultCandidate
self . defaultLoc = self . default . location
2016-12-02 16:53:39 +01:00
self . default . copyInfo = True
2016-12-02 12:22:07 +01:00
def checkAxes ( self ) :
"""
If we don ' t have axes in the document, make some, report
Should we include the instance locations when determining the axis extrema ?
"""
axisValues = { }
# find all the axes
locations = [ ]
for sourceDescriptor in self . sources :
locations . append ( sourceDescriptor . location )
for instanceDescriptor in self . instances :
locations . append ( instanceDescriptor . location )
for name , glyphData in instanceDescriptor . glyphs . items ( ) :
loc = glyphData . get ( " instanceLocation " )
if loc is not None :
locations . append ( loc )
for m in glyphData . get ( ' masters ' , [ ] ) :
locations . append ( m [ ' location ' ] )
for loc in locations :
for name , value in loc . items ( ) :
if not name in axisValues :
axisValues [ name ] = [ ]
if type ( value ) == tuple :
for v in value :
axisValues [ name ] . append ( v )
else :
axisValues [ name ] . append ( value )
have = self . getAxisOrder ( )
for name , values in axisValues . items ( ) :
if name not in have :
# we need to make this axis
a = self . newAxisDescriptor ( )
a . name = name
a . minimum = min ( values )
a . maximum = max ( values )
a . default = a . minimum
a . tag , a . labelNames = tagForAxisName ( a . name )
self . addAxis ( a )
2016-12-02 16:53:39 +01:00
self . logger . info ( " CheckAxes: added a missing axis %s , %3.3f %3.3f " , a . name , a . minimum , a . maximum )
#print("CheckAxes: added a missing axis %s, %3.3f %3.3f"%(a.name, a.minimum, a.maximum))
2016-12-02 12:22:07 +01:00
2016-11-28 17:12:46 +01:00
def normalizeLocation ( self , location ) :
# scale this location based on the axes
# accept only values for the axes that we have definitions for
# only normalise if we're valid?
# normalise anisotropic cooordinates to isotropic.
# copied from fontTools.varlib.models.normalizeLocation
new = { }
for axis in self . axes :
if not axis . name in location :
# skipping this dimension it seems
continue
v = location . get ( axis . name , axis . default )
if type ( v ) == tuple :
v = v [ 0 ]
if v == axis . default :
v = 0.0
elif v < axis . default :
if axis . default == axis . minimum :
v = 0.0
else :
v = ( max ( v , axis . minimum ) - axis . default ) / ( axis . default - axis . minimum )
else :
if axis . default == axis . maximum :
v = 0.0
else :
v = ( min ( v , axis . maximum ) - axis . default ) / ( axis . maximum - axis . default )
new [ axis . name ] = v
return new
def normalize ( self ) :
# scale all the locations of all masters and instances to the -1 - 0 - 1 value.
2016-11-28 22:29:14 +01:00
# we need the axis data to do the scaling, so we do those last.
# masters
2016-11-28 17:12:46 +01:00
for item in self . sources :
item . location = self . normalizeLocation ( item . location )
2016-11-28 22:29:14 +01:00
# instances
2016-11-28 17:12:46 +01:00
for item in self . instances :
2016-11-28 22:29:14 +01:00
# glyph masters for this instance
for name , glyphData in item . glyphs . items ( ) :
glyphData [ ' instanceLocation ' ] = self . normalizeLocation ( glyphData [ ' instanceLocation ' ] )
for glyphMaster in glyphData [ ' masters ' ] :
glyphMaster [ ' location ' ] = self . normalizeLocation ( glyphMaster [ ' location ' ] )
2016-11-28 17:12:46 +01:00
item . location = self . normalizeLocation ( item . location )
2016-11-28 22:29:14 +01:00
# now the axes
2016-11-28 17:12:46 +01:00
for axis in self . axes :
2016-11-28 17:18:36 +01:00
# scale the map first
newMap = [ ]
for inputValue , outputValue in axis . map :
newOutputValue = self . normalizeLocation ( { axis . name : outputValue } ) . get ( axis . name )
newMap . append ( ( inputValue , newOutputValue ) )
if newMap :
axis . map = newMap
2016-11-28 22:29:14 +01:00
# finally the axis values
minimum = self . normalizeLocation ( { axis . name : axis . minimum } ) . get ( axis . name )
maximum = self . normalizeLocation ( { axis . name : axis . maximum } ) . get ( axis . name )
default = self . normalizeLocation ( { axis . name : axis . default } ) . get ( axis . name )
2016-12-02 12:22:07 +01:00
# and set them in the axis.minimum
2016-11-28 17:12:46 +01:00
axis . minimum = minimum
axis . maximum = maximum
axis . default = default
2016-11-15 13:27:39 +01:00
2016-11-28 17:18:36 +01:00
2016-11-15 13:27:39 +01:00
if __name__ == " __main__ " :
2016-12-02 16:53:39 +01:00
def __removeAxesFromDesignSpace ( path ) :
# only for testing, so we can make an invalid designspace file
# without making the designSpaceDocument also support it.
f = open ( path , ' r ' )
d = f . read ( )
f . close ( )
start = d . find ( " <axes> " )
end = d . find ( " </axes> " ) + len ( " </axes> " )
n = d [ 0 : start ] + d [ end : ]
f = open ( path , ' w ' )
f . write ( n )
f . close ( )
2016-12-02 12:22:07 +01:00
# print(tagForAxisName('weight'))
# print(tagForAxisName('width'))
# print(tagForAxisName('Optical'))
# print(tagForAxisName('Poids'))
# print(tagForAxisName('wt'))
2016-11-15 13:27:39 +01:00
def test ( ) :
2016-11-15 20:17:57 +00:00
"""
2016-11-15 13:27:39 +01:00
>> > import os
>> > testDocPath = os . path . join ( os . getcwd ( ) , " test.designspace " )
>> > masterPath1 = os . path . join ( os . getcwd ( ) , " masters " , " masterTest1.ufo " )
>> > masterPath2 = os . path . join ( os . getcwd ( ) , " masters " , " masterTest2.ufo " )
>> > instancePath1 = os . path . join ( os . getcwd ( ) , " instances " , " instanceTest1.ufo " )
>> > instancePath2 = os . path . join ( os . getcwd ( ) , " instances " , " instanceTest2.ufo " )
>> > doc = DesignSpaceDocument ( )
>> > # add master 1
>> > s1 = SourceDescriptor ( )
>> > s1 . path = masterPath1
>> > s1 . name = " master.ufo1 "
>> > s1 . copyLib = True
>> > s1 . copyInfo = True
>> > s1 . copyFeatures = True
>> > s1 . location = dict ( weight = 0 )
>> > s1 . familyName = " MasterFamilyName "
>> > s1 . styleName = " MasterStyleNameOne "
>> > s1 . mutedGlyphNames . append ( " A " )
>> > s1 . mutedGlyphNames . append ( " Z " )
>> > doc . addSource ( s1 )
>> > # add master 2
>> > s2 = SourceDescriptor ( )
>> > s2 . path = masterPath2
>> > s2 . name = " master.ufo2 "
>> > s2 . copyLib = False
>> > s2 . copyInfo = False
>> > s2 . copyFeatures = False
>> > s2 . muteKerning = True
>> > s2 . location = dict ( weight = 1000 )
>> > s2 . familyName = " MasterFamilyName "
>> > s2 . styleName = " MasterStyleNameTwo "
>> > doc . addSource ( s2 )
>> > # add instance 1
>> > i1 = InstanceDescriptor ( )
>> > i1 . path = instancePath1
>> > i1 . familyName = " InstanceFamilyName "
>> > i1 . styleName = " InstanceStyleName "
>> > i1 . name = " instance.ufo1 "
>> > i1 . location = dict ( weight = 500 , spooky = 666 ) # this adds a dimension that is not defined.
>> > i1 . postScriptFontName = " InstancePostscriptName "
>> > i1 . styleMapFamilyName = " InstanceStyleMapFamilyName "
>> > i1 . styleMapStyleName = " InstanceStyleMapStyleName "
>> > glyphData = dict ( name = " arrow " , mute = True , unicode = " 0x123 " )
>> > i1 . glyphs [ ' arrow ' ] = glyphData
>> > doc . addInstance ( i1 )
>> > # add instance 2
>> > i2 = InstanceDescriptor ( )
>> > i2 . path = instancePath2
>> > i2 . familyName = " InstanceFamilyName "
>> > i2 . styleName = " InstanceStyleName "
>> > i2 . name = " instance.ufo2 "
2016-11-15 20:15:04 +00:00
>> > # anisotropic location
2016-11-15 13:27:39 +01:00
>> > i2 . location = dict ( weight = 500 , width = ( 400 , 300 ) )
>> > i2 . postScriptFontName = " InstancePostscriptName "
>> > i2 . styleMapFamilyName = " InstanceStyleMapFamilyName "
>> > i2 . styleMapStyleName = " InstanceStyleMapStyleName "
>> > glyphMasters = [ dict ( font = " master.ufo1 " , glyphName = " BB " , location = dict ( width = 20 , weight = 20 ) ) , dict ( font = " master.ufo2 " , glyphName = " CC " , location = dict ( width = 900 , weight = 900 ) ) ]
>> > glyphData = dict ( name = " arrow " , unicodeValue = 1234 )
>> > glyphData [ ' masters ' ] = glyphMasters
>> > glyphData [ ' note ' ] = " A note about this glyph "
>> > glyphData [ ' instanceLocation ' ] = dict ( width = 100 , weight = 120 )
>> > i2 . glyphs [ ' arrow ' ] = glyphData
>> > i2 . glyphs [ ' arrow2 ' ] = dict ( mute = False )
>> > doc . addInstance ( i2 )
2016-12-02 12:22:07 +01:00
>> > # now we have sounrces and instances, but no axes yet.
>> > doc . check ( )
>> > doc . getAxisOrder ( )
[ ' spooky ' , ' weight ' , ' width ' ]
>> > doc . axes = [ ] # clear the axes
2016-11-15 13:27:39 +01:00
>> > # write some axes
>> > a1 = AxisDescriptor ( )
>> > a1 . minimum = 0
>> > a1 . maximum = 1000
>> > a1 . default = 0
>> > a1 . name = " weight "
>> > a1 . tag = " wght "
2016-11-20 10:05:55 +01:00
>> > # note: just to test the element language, not an actual label name recommendations.
2016-11-15 13:27:39 +01:00
>> > a1 . labelNames [ u ' fa-IR ' ] = u " قطر "
>> > a1 . labelNames [ u ' en ' ] = u " Wéíght "
>> > doc . addAxis ( a1 )
>> > a2 = AxisDescriptor ( )
>> > a2 . minimum = 0
>> > a2 . maximum = 1000
>> > a2 . default = 0
>> > a2 . name = " width "
>> > a2 . tag = " wdth "
>> > a2 . map = [ ( 0.0 , 10.0 ) , ( 401.0 , 66.0 ) , ( 1000.0 , 990.0 ) ]
2016-11-20 10:05:55 +01:00
>> > a2 . labelNames [ u ' fr ' ] = u " Poids "
2016-11-15 13:27:39 +01:00
>> > doc . addAxis ( a2 )
>> > # add an axis that is not part of any location to see if that works
>> > a3 = AxisDescriptor ( )
>> > a3 . minimum = 333
>> > a3 . maximum = 666
>> > a3 . default = 444
>> > a3 . name = " spooky "
>> > a3 . tag = " spok "
>> > a3 . map = [ ( 0.0 , 10.0 ) , ( 401.0 , 66.0 ) , ( 1000.0 , 990.0 ) ]
>> > #doc.addAxis(a3) # uncomment this line to test the effects of default axes values
>> > # write the document
>> > doc . write ( testDocPath )
2016-11-22 22:47:34 +01:00
>> > assert os . path . exists ( testDocPath )
2016-11-15 13:27:39 +01:00
>> > # import it again
>> > new = DesignSpaceDocument ( )
>> > new . read ( testDocPath )
>> > for a , b in zip ( doc . instances , new . instances ) :
. . . a . compare ( b )
>> > for a , b in zip ( doc . sources , new . sources ) :
. . . a . compare ( b )
>> > for a , b in zip ( doc . axes , new . axes ) :
. . . a . compare ( b )
>> > [ n . mutedGlyphNames for n in new . sources ]
[ [ ' A ' , ' Z ' ] , [ ] ]
2016-11-22 22:47:34 +01:00
>> > doc . getFonts ( )
[ ]
2016-11-20 10:05:55 +01:00
>> > # test roundtrip for the axis attributes and data
>> > axes = { }
>> > for axis in doc . axes :
. . . if not axis . tag in axes :
. . . axes [ axis . tag ] = [ ]
. . . axes [ axis . tag ] . append ( axis . serialize ( ) )
>> > for axis in new . axes :
. . . if not axis . tag in axes :
. . . axes [ axis . tag ] = [ ]
. . . axes [ axis . tag ] . append ( axis . serialize ( ) )
>> > for v in axes . values ( ) :
. . . a , b = v
2016-11-28 17:12:46 +01:00
. . . assert a == b
"""
def testNormalise ( ) :
"""
>> > doc = DesignSpaceDocument ( )
>> > # write some axes
>> > a1 = AxisDescriptor ( )
>> > a1 . minimum = - 1000
>> > a1 . maximum = 1000
>> > a1 . default = 0
>> > a1 . name = " aaa "
>> > doc . addAxis ( a1 )
>> > doc . normalizeLocation ( dict ( aaa = 0 ) )
{ ' aaa ' : 0.0 }
>> > doc . normalizeLocation ( dict ( aaa = 1000 ) )
{ ' aaa ' : 1.0 }
>> > # clipping beyond max values:
>> > doc . normalizeLocation ( dict ( aaa = 1001 ) )
{ ' aaa ' : 1.0 }
>> > doc . normalizeLocation ( dict ( aaa = 500 ) )
{ ' aaa ' : 0.5 }
>> > doc . normalizeLocation ( dict ( aaa = - 1000 ) )
{ ' aaa ' : - 1.0 }
>> > doc . normalizeLocation ( dict ( aaa = - 1001 ) )
{ ' aaa ' : - 1.0 }
>> > # anisotropic coordinates normalise to isotropic
>> > doc . normalizeLocation ( dict ( aaa = ( 1000 , - 1000 ) ) )
{ ' aaa ' : 1.0 }
>> > doc . normalize ( )
>> > r = [ ]
>> > for axis in doc . axes :
. . . r . append ( ( axis . name , axis . minimum , axis . default , axis . maximum ) )
>> > r . sort ( )
>> > r
[ ( ' aaa ' , - 1.0 , 0.0 , 1.0 ) ]
>> > doc = DesignSpaceDocument ( )
>> > # write some axes
>> > a2 = AxisDescriptor ( )
>> > a2 . minimum = 100
>> > a2 . maximum = 1000
>> > a2 . default = 100
>> > a2 . name = " bbb "
>> > doc . addAxis ( a2 )
>> > doc . normalizeLocation ( dict ( bbb = 0 ) )
{ ' bbb ' : 0.0 }
>> > doc . normalizeLocation ( dict ( bbb = 1000 ) )
{ ' bbb ' : 1.0 }
>> > # clipping beyond max values:
>> > doc . normalizeLocation ( dict ( bbb = 1001 ) )
{ ' bbb ' : 1.0 }
>> > doc . normalizeLocation ( dict ( bbb = 500 ) )
{ ' bbb ' : 0.4444444444444444 }
>> > doc . normalizeLocation ( dict ( bbb = - 1000 ) )
{ ' bbb ' : 0.0 }
>> > doc . normalizeLocation ( dict ( bbb = - 1001 ) )
{ ' bbb ' : 0.0 }
>> > # anisotropic coordinates normalise to isotropic
>> > doc . normalizeLocation ( dict ( bbb = ( 1000 , - 1000 ) ) )
{ ' bbb ' : 1.0 }
>> > doc . normalizeLocation ( dict ( bbb = 1001 ) )
{ ' bbb ' : 1.0 }
>> > doc . normalize ( )
>> > r = [ ]
>> > for axis in doc . axes :
. . . r . append ( ( axis . name , axis . minimum , axis . default , axis . maximum ) )
>> > r . sort ( )
>> > r
[ ( ' bbb ' , 0.0 , 0.0 , 1.0 ) ]
>> > doc = DesignSpaceDocument ( )
>> > # write some axes
>> > a3 = AxisDescriptor ( )
>> > a3 . minimum = - 1000
>> > a3 . maximum = 0
>> > a3 . default = 0
>> > a3 . name = " ccc "
>> > doc . addAxis ( a3 )
>> > doc . normalizeLocation ( dict ( ccc = 0 ) )
{ ' ccc ' : 0.0 }
>> > doc . normalizeLocation ( dict ( ccc = 1 ) )
{ ' ccc ' : 0.0 }
>> > doc . normalizeLocation ( dict ( ccc = - 1000 ) )
{ ' ccc ' : - 1.0 }
>> > doc . normalizeLocation ( dict ( ccc = - 1001 ) )
{ ' ccc ' : - 1.0 }
>> > doc . normalize ( )
>> > r = [ ]
>> > for axis in doc . axes :
. . . r . append ( ( axis . name , axis . minimum , axis . default , axis . maximum ) )
>> > r . sort ( )
>> > r
[ ( ' ccc ' , - 1.0 , 0.0 , 0.0 ) ]
2016-11-28 17:18:36 +01:00
>> > doc = DesignSpaceDocument ( )
>> > # write some axes
>> > a4 = AxisDescriptor ( )
>> > a4 . minimum = 0
>> > a4 . maximum = 1000
>> > a4 . default = 0
>> > a4 . name = " ddd "
>> > a4 . map = [ ( 0 , 100 ) , ( 300 , 500 ) , ( 600 , 500 ) , ( 1000 , 900 ) ]
>> > doc . addAxis ( a4 )
>> > doc . normalize ( )
>> > r = [ ]
>> > for axis in doc . axes :
. . . r . append ( ( axis . name , axis . map ) )
>> > r . sort ( )
>> > r
[ ( ' ddd ' , [ ( 0 , 0.1 ) , ( 300 , 0.5 ) , ( 600 , 0.5 ) , ( 1000 , 0.9 ) ] ) ]
2016-11-15 13:27:39 +01:00
"""
2016-11-15 20:15:04 +00:00
2016-12-02 12:22:07 +01:00
def testCheck ( ) :
"""
>> > # check if the checks are checking
>> > testDocPath = os . path . join ( os . getcwd ( ) , " testCheck.designspace " )
>> > masterPath1 = os . path . join ( os . getcwd ( ) , " masters " , " masterTest1.ufo " )
>> > masterPath2 = os . path . join ( os . getcwd ( ) , " masters " , " masterTest2.ufo " )
>> > instancePath1 = os . path . join ( os . getcwd ( ) , " instances " , " instanceTest1.ufo " )
>> > instancePath2 = os . path . join ( os . getcwd ( ) , " instances " , " instanceTest2.ufo " )
>> > # no default selected
>> > doc = DesignSpaceDocument ( )
>> > # add master 1
>> > s1 = SourceDescriptor ( )
>> > s1 . path = masterPath1
>> > s1 . name = " master.ufo1 "
>> > s1 . location = dict ( snap = 0 , pop = 10 )
>> > s1 . familyName = " MasterFamilyName "
>> > s1 . styleName = " MasterStyleNameOne "
>> > doc . addSource ( s1 )
>> > # add master 2
>> > s2 = SourceDescriptor ( )
>> > s2 . path = masterPath2
>> > s2 . name = " master.ufo2 "
>> > s2 . location = dict ( snap = 1000 , pop = 20 )
>> > s2 . familyName = " MasterFamilyName "
>> > s2 . styleName = " MasterStyleNameTwo "
>> > doc . addSource ( s2 )
>> > doc . checkAxes ( )
>> > doc . getAxisOrder ( )
[ ' snap ' , ' pop ' ]
>> > assert doc . default == None
>> > doc . checkDefault ( )
>> > doc . default . name
' master.ufo1 '
>> > # default selected
>> > doc = DesignSpaceDocument ( )
>> > # add master 1
>> > s1 = SourceDescriptor ( )
>> > s1 . path = masterPath1
>> > s1 . name = " master.ufo1 "
>> > s1 . location = dict ( snap = 0 , pop = 10 )
>> > s1 . familyName = " MasterFamilyName "
>> > s1 . styleName = " MasterStyleNameOne "
>> > doc . addSource ( s1 )
>> > # add master 2
>> > s2 = SourceDescriptor ( )
>> > s2 . path = masterPath2
>> > s2 . name = " master.ufo2 "
>> > s2 . copyInfo = True
>> > s2 . location = dict ( snap = 1000 , pop = 20 )
>> > s2 . familyName = " MasterFamilyName "
>> > s2 . styleName = " MasterStyleNameTwo "
>> > doc . addSource ( s2 )
>> > doc . checkAxes ( )
>> > doc . getAxisOrder ( )
[ ' snap ' , ' pop ' ]
>> > assert doc . default == None
>> > doc . checkDefault ( )
>> > doc . default . name
' master.ufo2 '
2016-12-02 16:53:39 +01:00
>> > # generate a doc without axes, save and read again
>> > doc = DesignSpaceDocument ( )
>> > # add master 1
>> > s1 = SourceDescriptor ( )
>> > s1 . path = masterPath1
>> > s1 . name = " master.ufo1 "
>> > s1 . location = dict ( snap = 0 , pop = 10 )
>> > s1 . familyName = " MasterFamilyName "
>> > s1 . styleName = " MasterStyleNameOne "
>> > doc . addSource ( s1 )
>> > # add master 2
>> > s2 = SourceDescriptor ( )
>> > s2 . path = masterPath2
>> > s2 . name = " master.ufo2 "
>> > s2 . location = dict ( snap = 1000 , pop = 20 )
>> > s2 . familyName = " MasterFamilyName "
>> > s2 . styleName = " MasterStyleNameTwo "
>> > doc . addSource ( s2 )
>> > doc . checkAxes ( )
>> > doc . write ( testDocPath )
>> > __removeAxesFromDesignSpace ( testDocPath )
2016-12-02 12:22:07 +01:00
2016-12-02 16:53:39 +01:00
>> > new = DesignSpaceDocument ( )
>> > new . read ( testDocPath )
>> > new . axes
[ ]
>> > new . checkAxes ( )
>> > len ( new . axes )
2
>> > new . write ( testDocPath )
2016-12-02 12:22:07 +01:00
2016-12-02 16:53:39 +01:00
"""
2016-12-02 12:22:07 +01:00
2016-12-02 16:53:39 +01:00
p = " testCheck.designspace "
__removeAxesFromDesignSpace ( p )
2016-11-15 13:27:39 +01:00
def _test ( ) :
import doctest
doctest . testmod ( )
_test ( )