def test_rulesDocument(tmpdir): # tests of rules in a document, roundtripping. tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "testRules.designspace") testDocPath2 = os.path.join(tmpdir, "testRules_roundtrip.designspace") doc = DesignSpaceDocument() doc.rulesProcessingLast = True a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 a1.name = "axisName_a" a1.tag = "TAGA" b1 = AxisDescriptor() b1.minimum = 2000 b1.maximum = 3000 b1.default = 2000 b1.name = "axisName_b" b1.tag = "TAGB" doc.addAxis(a1) doc.addAxis(b1) r1 = RuleDescriptor() r1.name = "named.rule.1" r1.conditionSets.append([ dict(name='axisName_a', minimum=0, maximum=1000), dict(name='axisName_b', minimum=0, maximum=3000) ]) r1.subs.append(("a", "a.alt")) # rule with minium and maximum doc.addRule(r1) assert len(doc.rules) == 1 assert len(doc.rules[0].conditionSets) == 1 assert len(doc.rules[0].conditionSets[0]) == 2 assert _axesAsDict(doc.axes) == { 'axisName_a': { 'map': [], 'name': 'axisName_a', 'default': 0, 'minimum': 0, 'maximum': 1000, 'tag': 'TAGA' }, 'axisName_b': { 'map': [], 'name': 'axisName_b', 'default': 2000, 'minimum': 2000, 'maximum': 3000, 'tag': 'TAGB' } } assert doc.rules[0].conditionSets == [[{ 'minimum': 0, 'maximum': 1000, 'name': 'axisName_a' }, { 'minimum': 0, 'maximum': 3000, 'name': 'axisName_b' }]] assert doc.rules[0].subs == [('a', 'a.alt')] doc.normalize() assert doc.rules[0].name == 'named.rule.1' assert doc.rules[0].conditionSets == [[{ 'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_a' }, { 'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_b' }]] # still one conditionset assert len(doc.rules[0].conditionSets) == 1 doc.write(testDocPath) # add a stray conditionset _addUnwrappedCondition(testDocPath) doc2 = DesignSpaceDocument() doc2.read(testDocPath) assert doc2.rulesProcessingLast assert len(doc2.axes) == 2 assert len(doc2.rules) == 1 assert len(doc2.rules[0].conditionSets) == 2 doc2.write(testDocPath2) # verify these results # make sure the stray condition is now neatly wrapped in a conditionset. doc3 = DesignSpaceDocument() doc3.read(testDocPath2) assert len(doc3.rules) == 1 assert len(doc3.rules[0].conditionSets) == 2
def _extractSubSpace( doc: DesignSpaceDocument, userRegion: Region, *, keepVFs: bool, makeNames: bool, expandLocations: bool, makeInstanceFilename: MakeInstanceFilenameCallable, ) -> DesignSpaceDocument: subDoc = DesignSpaceDocument() # Don't include STAT info # FIXME: (Jany) let's think about it. Not include = OK because the point of # the splitting is to build VFs and we'll use the STAT data of the full # document to generate the STAT of the VFs, so "no need" to have STAT data # in sub-docs. Counterpoint: what if someone wants to split this DS for # other purposes? Maybe for that it would be useful to also subset the STAT # data? # subDoc.elidedFallbackName = doc.elidedFallbackName def maybeExpandDesignLocation(object): if expandLocations: return object.getFullDesignLocation(doc) else: return object.designLocation for axis in doc.axes: range = userRegion[axis.name] if isinstance(range, Range) and hasattr(axis, "minimum"): # Mypy doesn't support narrowing union types via hasattr() # TODO(Python 3.10): use TypeGuard # https://mypy.readthedocs.io/en/stable/type_narrowing.html axis = cast(AxisDescriptor, axis) subDoc.addAxis( AxisDescriptor( # Same info tag=axis.tag, name=axis.name, labelNames=axis.labelNames, hidden=axis.hidden, # Subset range minimum=max(range.minimum, axis.minimum), default=range.default or axis.default, maximum=min(range.maximum, axis.maximum), map=[ (user, design) for user, design in axis.map if range.minimum <= user <= range.maximum ], # Don't include STAT info axisOrdering=None, axisLabels=None, ) ) # Don't include STAT info # subDoc.locationLabels = doc.locationLabels # Rules: subset them based on conditions designRegion = userRegionToDesignRegion(doc, userRegion) subDoc.rules = _subsetRulesBasedOnConditions(doc.rules, designRegion) subDoc.rulesProcessingLast = doc.rulesProcessingLast # Sources: keep only the ones that fall within the kept axis ranges for source in doc.sources: if not locationInRegion(doc.map_backward(source.designLocation), userRegion): continue subDoc.addSource( SourceDescriptor( filename=source.filename, path=source.path, font=source.font, name=source.name, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(source) ), layerName=source.layerName, familyName=source.familyName, styleName=source.styleName, muteKerning=source.muteKerning, muteInfo=source.muteInfo, mutedGlyphNames=source.mutedGlyphNames, ) ) # Copy family name translations from the old default source to the new default vfDefault = subDoc.findDefault() oldDefault = doc.findDefault() if vfDefault is not None and oldDefault is not None: vfDefault.localisedFamilyName = oldDefault.localisedFamilyName # Variable fonts: keep only the ones that fall within the kept axis ranges if keepVFs: # Note: call getVariableFont() to make the implicit VFs explicit for vf in doc.getVariableFonts(): vfUserRegion = getVFUserRegion(doc, vf) if regionInRegion(vfUserRegion, userRegion): subDoc.addVariableFont( VariableFontDescriptor( name=vf.name, filename=vf.filename, axisSubsets=[ axisSubset for axisSubset in vf.axisSubsets if isinstance(userRegion[axisSubset.name], Range) ], lib=vf.lib, ) ) # Instances: same as Sources + compute missing names for instance in doc.instances: if not locationInRegion(instance.getFullUserLocation(doc), userRegion): continue if makeNames: statNames = getStatNames(doc, instance.getFullUserLocation(doc)) familyName = instance.familyName or statNames.familyNames.get("en") styleName = instance.styleName or statNames.styleNames.get("en") subDoc.addInstance( InstanceDescriptor( filename=instance.filename or makeInstanceFilename(doc, instance, statNames), path=instance.path, font=instance.font, name=instance.name or f"{familyName} {styleName}", userLocation={} if expandLocations else instance.userLocation, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(instance) ), familyName=familyName, styleName=styleName, postScriptFontName=instance.postScriptFontName or statNames.postScriptFontName, styleMapFamilyName=instance.styleMapFamilyName or statNames.styleMapFamilyNames.get("en"), styleMapStyleName=instance.styleMapStyleName or statNames.styleMapStyleName, localisedFamilyName=instance.localisedFamilyName or statNames.familyNames, localisedStyleName=instance.localisedStyleName or statNames.styleNames, localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName or statNames.styleMapFamilyNames, localisedStyleMapStyleName=instance.localisedStyleMapStyleName or {}, lib=instance.lib, ) ) else: subDoc.addInstance( InstanceDescriptor( filename=instance.filename, path=instance.path, font=instance.font, name=instance.name, userLocation={} if expandLocations else instance.userLocation, designLocation=_filterLocation( userRegion, maybeExpandDesignLocation(instance) ), familyName=instance.familyName, styleName=instance.styleName, postScriptFontName=instance.postScriptFontName, styleMapFamilyName=instance.styleMapFamilyName, styleMapStyleName=instance.styleMapStyleName, localisedFamilyName=instance.localisedFamilyName, localisedStyleName=instance.localisedStyleName, localisedStyleMapFamilyName=instance.localisedStyleMapFamilyName, localisedStyleMapStyleName=instance.localisedStyleMapStyleName, lib=instance.lib, ) ) subDoc.lib = doc.lib return subDoc
def test_fill_document(tmpdir): tmpdir = str(tmpdir) testDocPath = os.path.join(tmpdir, "test.designspace") masterPath1 = os.path.join(tmpdir, "masters", "masterTest1.ufo") masterPath2 = os.path.join(tmpdir, "masters", "masterTest2.ufo") instancePath1 = os.path.join(tmpdir, "instances", "instanceTest1.ufo") instancePath2 = os.path.join(tmpdir, "instances", "instanceTest2.ufo") doc = DesignSpaceDocument() doc.rulesProcessingLast = True # write some axes a1 = AxisDescriptor() a1.minimum = 0 a1.maximum = 1000 a1.default = 0 a1.name = "weight" a1.tag = "wght" # note: just to test the element language, not an actual label name recommendations. 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 = 15 a2.name = "width" a2.tag = "wdth" a2.map = [(0.0, 10.0), (15.0, 20.0), (401.0, 66.0), (1000.0, 990.0)] a2.hidden = True a2.labelNames[u'fr'] = u"Chasse" doc.addAxis(a2) # add master 1 s1 = SourceDescriptor() s1.filename = os.path.relpath(masterPath1, os.path.dirname(testDocPath)) assert s1.font is None 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.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath)) 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 master 3 from a different layer s3 = SourceDescriptor() s3.filename = os.path.relpath(masterPath2, os.path.dirname(testDocPath)) s3.name = "master.ufo2" s3.copyLib = False s3.copyInfo = False s3.copyFeatures = False s3.muteKerning = False s3.layerName = "supports" s3.location = dict(weight=1000) s3.familyName = "MasterFamilyName" s3.styleName = "Supports" doc.addSource(s3) # add instance 1 i1 = InstanceDescriptor() i1.filename = os.path.relpath(instancePath1, os.path.dirname(testDocPath)) 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, unicodes=[0x123, 0x124, 0x125]) i1.glyphs['arrow'] = glyphData i1.lib['com.coolDesignspaceApp.binaryData'] = plistlib.Data( b'<binary gunk>') i1.lib['com.coolDesignspaceApp.specimenText'] = "Hamburgerwhatever" doc.addInstance(i1) # add instance 2 i2 = InstanceDescriptor() i2.filename = os.path.relpath(instancePath2, os.path.dirname(testDocPath)) i2.familyName = "InstanceFamilyName" i2.styleName = "InstanceStyleName" i2.name = "instance.ufo2" # anisotropic location 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", unicodes=[101, 201, 301]) 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) doc.filename = "suggestedFileName.designspace" doc.lib['com.coolDesignspaceApp.previewSize'] = 30 # write some rules r1 = RuleDescriptor() r1.name = "named.rule.1" r1.conditionSets.append([ dict(name='axisName_a', minimum=0, maximum=1), dict(name='axisName_b', minimum=2, maximum=3) ]) r1.subs.append(("a", "a.alt")) doc.addRule(r1) # write the document doc.write(testDocPath) assert os.path.exists(testDocPath) assert_equals_test_file(testDocPath, 'data/test.designspace') # import it again new = DesignSpaceDocument() new.read(testDocPath) assert new.default.location == {'width': 20.0, 'weight': 0.0} assert new.filename == 'test.designspace' assert new.lib == doc.lib assert new.instances[0].lib == doc.instances[0].lib # test roundtrip for the axis attributes and data axes = {} for axis in doc.axes: if axis.tag not in axes: axes[axis.tag] = [] axes[axis.tag].append(axis.serialize()) for axis in new.axes: if axis.tag[0] == "_": continue if axis.tag not in axes: axes[axis.tag] = [] axes[axis.tag].append(axis.serialize()) for v in axes.values(): a, b = v assert a == b
} # Running script if len(sys.argv) != 2: print("Must specify a name:") print("python processDesignSpace.py PiazzollaItalic") exit() file = sys.argv[1] folder = "temp/building/%s/" % (file) path = "temp/building/%s/%s.designspace" % (file, file) minPath = "temp/building/%s/%s-OpszMin.designspace" % (file, file) doc = DesignSpaceDocument() doc.read(path) doc.rulesProcessingLast = True mainMasters = set([m.filename for m in doc.sources]) if len(mainMasters) != 2: raise RuntimeError("File %s doesn't have 2 masters" % file) print() print("Mapping weight axis") for axis in doc.axes: if axis.tag == 'wght': axis.map = [(100, 30), (200, 46), (300, 61), (400, 75), (500, 93), (600, 114), (700, 139), (800, 171), (900, 208)] axis.default = 100 axis.minimum = 100 axis.maximum = 900