Skip to content

Instantly share code, notes, and snippets.

@arrowtype
Last active June 30, 2023 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arrowtype/47937ba868b0b2a49e80684319e56037 to your computer and use it in GitHub Desktop.
Save arrowtype/47937ba868b0b2a49e80684319e56037 to your computer and use it in GitHub Desktop.
A script to help sync spacing & shaping for support sources within a large designspace.
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="5.0">
<axes elidedfallbackname="Regular">
<axis tag="opsz" name="Optical Size" minimum="8" maximum="72" default="8">
<labelname xml:lang="en">Optical Size</labelname>
<map input="72" output="72"/>
<map input="20" output="40"/>
<map input="8" output="8" name="Text" elidable="true"/>
</axis>
<axis tag="wght" name="Weight" minimum="1" maximum="1000" default="700">
<labelname xml:lang="en">Weight</labelname>
<labels>
<label uservalue="25" name="Hairline" />
<label uservalue="50" name="ExtraThin" />
<label uservalue="100" name="Thin" />
<label uservalue="200" name="ExtraLight" />
<label uservalue="300" name="Light" />
<label uservalue="400" name="Regular" linkeduservalue="700" elidable="true"/>
<label uservalue="500" name="Medium" />
<label uservalue="600" name="SemiBold" />
<label uservalue="700" name="Bold" linkeduservalue="400"/>
<label uservalue="800" name="ExtraBold" />
<label uservalue="900" name="Black" />
<label uservalue="1000" name="Ultra" />
</labels>
<map input="1000" output="1000"/>
<map input="900" output="850"/>
<map input="800" output="775"/>
<map input="700" output="700"/>
<map input="600" output="600"/>
<map input="500" output="500"/>
<map input="400" output="400"/>
<map input="300" output="300"/>
<map input="200" output="200"/>
<map input="100" output="100"/>
<map input="50" output="50"/>
<map input="1" output="1"/>
</axis>
<axis name="Italic" tag="ital" default="0" values="0 1">
<labels>
<label uservalue="0" name="Roman" elidable="true" />
<label uservalue="1" name="Italic" />
</labels>
</axis>
</axes>
<variable-fonts>
<variable-font name="AT-NameSansVariable">
<axis-subsets>
<axis-subset name="Optical Size"/>
<axis-subset name="Weight"/>
<axis-subset name="Italic" uservalue="0"/>
</axis-subsets>
</variable-font>
<variable-font name="AT-NameSansVariable-Italic">
<axis-subsets>
<axis-subset name="Optical Size"/>
<axis-subset name="Weight"/>
<axis-subset name="Italic" uservalue="1"/>
</axis-subsets>
</variable-font>
</variable-fonts>
<rules>
<rule name="Italic a">
<conditionset>
<condition name="Italic" minimum="1" maximum="1"/>
</conditionset>
<sub name="a" with="a.italic"/>
<sub name="agrave" with="agrave.italic"/>
<sub name="aacute" with="aacute.italic"/>
<sub name="acircumflex" with="acircumflex.italic"/>
<sub name="atilde" with="atilde.italic"/>
<sub name="adieresis" with="adieresis.italic"/>
<sub name="aring" with="aring.italic"/>
<sub name="amacron" with="amacron.italic"/>
<sub name="abreve" with="abreve.italic"/>
<sub name="aogonek" with="aogonek.italic"/>
<sub name="acaron" with="acaron.italic"/>
<sub name="adotbelow" with="adotbelow.italic"/>
<sub name="ahook" with="ahook.italic"/>
<sub name="acircumflexacute" with="acircumflexacute.italic"/>
<sub name="acircumflexgrave" with="acircumflexgrave.italic"/>
<sub name="acircumflexhook" with="acircumflexhook.italic"/>
<sub name="acircumflextilde" with="acircumflextilde.italic"/>
<sub name="acircumflexdot" with="acircumflexdot.italic"/>
<sub name="abreveacute" with="abreveacute.italic"/>
<sub name="abrevegrave" with="abrevegrave.italic"/>
<sub name="abrevehook" with="abrevehook.italic"/>
<sub name="abrevetilde" with="abrevetilde.italic"/>
<sub name="abrevedot" with="abrevedot.italic"/>
</rule>
<rule name="Default R in Display">
<conditionset>
<condition name="Weight" minimum="1" maximum="1000"/>
<condition name="Optical Size" minimum="55.999990" maximum="72"/>
</conditionset>
<sub name="R" with="R.default"/>
<sub name="Rcedilla" with="Rcedilla.default"/>
<sub name="Racute" with="Racute.default"/>
<sub name="Rcaron" with="Rcaron.default"/>
</rule>
<rule name="Open Rounds">
<conditionset>
<condition name="Weight" minimum="728" maximum="1000"/>
<condition name="Optical Size" minimum="8" maximum="72"/>
</conditionset>
<sub name="Q" with="Q.open"/>
<sub name="Oslash" with="Oslash.open"/>
<sub name="Oslashacute" with="Oslashacute.open"/>
<sub name="Q.RECT" with="Q.RECT_open"/>
<sub name="Oslash.RECT" with="Oslash.RECT_open"/>
<sub name="Oslashacute.RECT" with="Oslashacute.RECT_open"/>
<sub name="oslash" with="oslash.open"/>
<sub name="oslashacute" with="oslashacute.open"/>
</rule>
<rule name="Open Dollar">
<conditionset>
<condition name="Weight" minimum="550" maximum="1000"/>
<condition name="Optical Size" minimum="8" maximum="72"/>
</conditionset>
<sub name="dollar" with="dollar.open"/>
<sub name="cent" with="cent.open"/>
</rule>
<rule name="Ultra Alts">
<conditionset>
<condition name="Weight" minimum="928" maximum="1000"/>
<condition name="Optical Size" minimum="8" maximum="72"/>
</conditionset>
<sub name="l" with="l.text"/>
<sub name="lslash" with="lslash.text"/>
<sub name="lacute" with="lacute.text"/>
<sub name="lcedilla" with="lcedilla.text"/>
<sub name="lcedilla" with="lcedilla.text"/>
<sub name="ldot" with="ldot.text"/>
<sub name="l_periodcentered_l.loclCAT" with="l_periodcentered_l.text.loclCAT"/>
</rule>
<rule name="Ultra T">
<conditionset>
<condition name="Weight" minimum="781" maximum="1000"/>
<condition name="Optical Size" minimum="55.999990" maximum="72"/>
</conditionset>
<sub name="T" with="T.ultra"/>
<sub name="Tcaron" with="Tcaron.ultra"/>
</rule>
<rule name="Hairline Alts">
<conditionset>
<condition name="Weight" minimum="1" maximum="350"/>
<condition name="Optical Size" minimum="8" maximum="72"/>
</conditionset>
<sub name="paragraph" with="paragraph.light"/>
<sub name="won" with="won.light"/>
</rule>
<rule name="Text Alts">
<conditionset>
<condition name="Weight" minimum="1" maximum="1000"/>
<condition name="Optical Size" minimum="8" maximum="39.999990"/>
</conditionset>
<sub name="g" with="g.default"/>
<sub name="gbreve" with="gbreve.default"/>
<sub name="gcedilla" with="gcedilla.default"/>
<sub name="l" with="l.default"/>
<sub name="lcaron" with="lcaron.default"/>
<sub name="lslash" with="lslash.default"/>
<sub name="lacute" with="lacute.default"/>
<sub name="lcedilla" with="lcedilla.default"/>
<sub name="ldot" with="ldot.default"/>
<sub name="l_periodcentered_l.loclCAT" with="l_periodcentered_l.default.loclCAT"/>
<sub name="I" with="I.default"/>
<sub name="Igrave" with="Igrave.default"/>
<sub name="Iacute" with="Iacute.default"/>
<sub name="Icircumflex" with="Icircumflex.default"/>
<sub name="Idieresis" with="Idieresis.default"/>
<sub name="Itilde" with="Itilde.default"/>
<sub name="Imacron" with="Imacron.default"/>
<sub name="Ibreve" with="Ibreve.default"/>
<sub name="Idotaccent" with="Idotaccent.default"/>
<sub name="Ihook" with="Ihook.default"/>
<sub name="Idotbelow" with="Idotbelow.default"/>
<sub name="Iogonek" with="Iogonek.default"/>
</rule>
<rule name="90deg Ones">
<conditionset>
<condition name="Weight" minimum="350" maximum="1000"/>
<condition name="Optical Size" minimum="8" maximum="72"/>
</conditionset>
<sub name="one" with="one.90deg"/>
<sub name="one.tnum" with="one.90deg_tnum"/>
<sub name="one.onum" with="one.90deg_onum"/>
</rule>
<rule name="Bold y">
<conditionset>
<condition name="Weight" minimum="550" maximum="1000"/>
<condition name="Optical Size" minimum="62" maximum="72"/>
</conditionset>
<sub name="y" with="y.bold"/>
<sub name="yacute" with="yacute.bold"/>
<sub name="ydieresis" with="ydieresis.bold"/>
<sub name="ygrave" with="ygrave.bold"/>
<sub name="ydotbelow" with="ydotbelow.bold"/>
<sub name="yhookabove" with="yhookabove.bold"/>
<sub name="ytilde" with="ytilde.bold"/>
</rule>
</rules>
<sources>
<source filename="Name_Sans-Display_Hairline.ufo" familyname="AT Name Sans" stylename="Display Hairline">
<location>
<dimension name="Weight" xvalue="1"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Display_100.ufo" familyname="AT Name Sans" stylename="Display Thin">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Display_200.ufo" familyname="AT Name Sans" stylename="Display ExtraLight">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Display_500.ufo" familyname="AT Name Sans" stylename="Display Medium">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Name_Sans-Display_Bold.ufo" familyname="AT Name Sans" stylename="Display Bold">
<lib copy="1"/>
<groups copy="1"/>
<features copy="1"/>
<info copy="1"/>
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Display_800.ufo" familyname="AT Name Sans" stylename="Display ExtraBold">
<location>
<dimension name="Weight" xvalue="800"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Name_Sans-Display_Ultra.ufo" familyname="AT Name Sans" stylename="Display Ultra">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Name_Sans-Text_Hairline.ufo" familyname="AT Name Sans" stylename="Text Hairline">
<location>
<dimension name="Weight" xvalue="1"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Text_100.ufo" familyname="AT Name Sans" stylename="Text Thin">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Text_200.ufo" familyname="AT Name Sans" stylename="Text ExtraLight">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="sparse--Name_Sans-Text_500.ufo" familyname="AT Name Sans" stylename="Text Medium">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Name_Sans-Text_Bold.ufo" familyname="AT Name Sans" stylename="Text Bold">
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<source filename="Name_Sans-Text_Ultra.ufo" familyname="AT Name Sans" stylename="Text Ultra">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
</source>
<!-- ITALICS -->
<source filename="italic/Name_Sans-Display_Hairline_Italic.ufo" familyname="AT Name Sans" stylename="Display Hairline Italic">
<location>
<dimension name="Weight" xvalue="1"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Display_100_Italic.ufo" familyname="AT Name Sans" stylename="Display Thin Italic">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Display_200_Italic.ufo" familyname="AT Name Sans" stylename="Display ExtraLight Italic">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Display_500_Italic.ufo" familyname="AT Name Sans" stylename="Display Medium Italic">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/Name_Sans-Display_Bold_Italic.ufo" familyname="AT Name Sans" stylename="Display Bold Italic">
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Display_800_Italic.ufo" familyname="AT Name Sans" stylename="Display ExtraBold Italic">
<location>
<dimension name="Weight" xvalue="800"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/Name_Sans-Display_Ultra_Italic.ufo" familyname="AT Name Sans" stylename="Display Ultra Italic">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="72"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/Name_Sans-Text_Hairline_Italic.ufo" familyname="AT Name Sans" stylename="Text Hairline Italic">
<location>
<dimension name="Weight" xvalue="1"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Text_100_Italic.ufo" familyname="AT Name Sans" stylename="Text Thin Italic">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Text_200_Italic.ufo" familyname="AT Name Sans" stylename="Text ExtraLight Italic">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/sparse--Name_Sans-Text_500_Italic.ufo" familyname="AT Name Sans" stylename="Text Medium Italic">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/Name_Sans-Text_Bold_Italic.ufo" familyname="AT Name Sans" stylename="Text Bold Italic">
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
<source filename="italic/Name_Sans-Text_Ultra_Italic.ufo" familyname="AT Name Sans" stylename="Text Ultra Italic">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</source>
</sources>
<instances>
<instance familyname="AT Name Sans Variable" stylename="Hairline" filename="instances/AT-NameSansVariable-Hairline.ufo" postscriptfontname="ATNameSansVariable-Hairline" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Hairline">
<location>
<dimension name="Weight" xvalue="25"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraThin" filename="instances/AT-NameSansVariable-ExtraThin.ufo" postscriptfontname="ATNameSansVariable-ExtraThin" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable ExtraThin">
<location>
<dimension name="Weight" xvalue="50"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Thin" filename="instances/AT-NameSansVariable-Thin.ufo" postscriptfontname="ATNameSansVariable-Thin" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Thin">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraLight" filename="instances/AT-NameSansVariable-ExtraLight.ufo" postscriptfontname="ATNameSansVariable-ExtraLight" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable ExtraLight">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Light" filename="instances/AT-NameSansVariable-Light.ufo" postscriptfontname="ATNameSansVariable-Light" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Light">
<location>
<dimension name="Weight" xvalue="300"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Regular" filename="instances/AT-NameSansVariable-Regular.ufo" postscriptfontname="ATNameSansVariable-Regular" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable">
<location>
<dimension name="Weight" xvalue="400"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Medium" filename="instances/AT-NameSansVariable-Medium.ufo" postscriptfontname="ATNameSansVariable-Medium" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Medium">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="SemiBold" filename="instances/AT-NameSansVariable-SemiBold.ufo" postscriptfontname="ATNameSansVariable-SemiBold" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable SemiBold">
<location>
<dimension name="Weight" xvalue="600"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Bold" filename="instances/AT-NameSansVariable-Bold.ufo" postscriptfontname="ATNameSansVariable-Bold" stylemapstylename="bold" stylemapfamilyname="AT Name Sans Variable">
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraBold" filename="instances/AT-NameSansVariable-ExtraBold.ufo" postscriptfontname="ATNameSansVariable-ExtraBold" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable ExtraBold">
<location>
<dimension name="Weight" xvalue="775"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Black" filename="instances/AT-NameSansVariable-Black.ufo" postscriptfontname="ATNameSansVariable-Black" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Black">
<location>
<dimension name="Weight" xvalue="850"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Ultra" filename="instances/AT-NameSansVariable-Ultra.ufo" postscriptfontname="ATNameSansVariable-Ultra" stylemapstylename="regular" stylemapfamilyname="AT Name Sans Variable Ultra">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="0"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Hairline Italic" filename="instances/AT-NameSansVariable-HairlineItalic.ufo" postscriptfontname="ATNameSansVariable-HairlineItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Hairline">
<location>
<dimension name="Weight" xvalue="25"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraThin Italic" filename="instances/AT-NameSansVariable-ExtraThinItalic.ufo" postscriptfontname="ATNameSansVariable-ExtraThinItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable ExtraThin">
<location>
<dimension name="Weight" xvalue="50"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Thin Italic" filename="instances/AT-NameSansVariable-ThinItalic.ufo" postscriptfontname="ATNameSansVariable-ThinItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Thin">
<location>
<dimension name="Weight" xvalue="100"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraLight Italic" filename="instances/AT-NameSansVariable-ExtraLightItalic.ufo" postscriptfontname="ATNameSansVariable-ExtraLightItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable ExtraLight">
<location>
<dimension name="Weight" xvalue="200"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Light Italic" filename="instances/AT-NameSansVariable-LightItalic.ufo" postscriptfontname="ATNameSansVariable-LightItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Light">
<location>
<dimension name="Weight" xvalue="300"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Italic" filename="instances/AT-NameSansVariable-RegularItalic.ufo" postscriptfontname="ATNameSansVariable-RegularItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable">
<location>
<dimension name="Weight" xvalue="400"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Medium Italic" filename="instances/AT-NameSansVariable-MediumItalic.ufo" postscriptfontname="ATNameSansVariable-MediumItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Medium">
<location>
<dimension name="Weight" xvalue="500"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="SemiBold Italic" filename="instances/AT-NameSansVariable-SemiBoldItalic.ufo" postscriptfontname="ATNameSansVariable-SemiBoldItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable SemiBold">
<location>
<dimension name="Weight" xvalue="600"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Bold Italic" filename="instances/AT-NameSansVariable-BoldItalic.ufo" postscriptfontname="ATNameSansVariable-BoldItalic" stylemapstylename="bold italic" stylemapfamilyname="AT Name Sans Variable">
<location>
<dimension name="Weight" xvalue="700"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="ExtraBold Italic" filename="instances/AT-NameSansVariable-ExtraBoldItalic.ufo" postscriptfontname="ATNameSansVariable-ExtraBoldItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable ExtraBold">
<location>
<dimension name="Weight" xvalue="775"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Black Italic" filename="instances/AT-NameSansVariable-BlackItalic.ufo" postscriptfontname="ATNameSansVariable-BlackItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Black">
<location>
<dimension name="Weight" xvalue="850"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
<instance familyname="AT Name Sans Variable" stylename="Ultra Italic" filename="instances/AT-NameSansVariable-UltraItalic.ufo" postscriptfontname="ATNameSansVariable-UltraItalic" stylemapstylename="italic" stylemapfamilyname="AT Name Sans Variable Ultra">
<location>
<dimension name="Weight" xvalue="1000"/>
<dimension name="Optical Size" xvalue="8"/>
<dimension name="Italic" xvalue="1"/>
</location>
<kerning/>
<info/>
</instance>
</instances>
</designspace>
"""
A script to make it slightly less painful to work with support sources.
Checks the spacing of glyphs in support sources, by re-interpolating
those glyphs from main sources, and checking the support glyphs against that.
Also places interpolated versions in the background, for visual reference.
Fixes small spacing discrepancies, but leaves bigger ones, as they might be intentional.
Can be run as a standalone script, or via RoboFont if you have the latest FontTools installed in RoboFont:
https://robofont.com/documentation/how-tos/overriding-embedded-libraries/#overriding-embedded-libraries
Assumptions / prequesites:
- You have a designspace that includes "sparse" / "support" sources
- These support sources may have to be separate UFOs. This hasn’t been tested with support layers.
- These support sources include the substring "sparse" or "support" in their filenames
- Full sources *don’t* include the substring "sparse" or "support" in their filenames
- You want to clear glyph color marks in support sources, and use red marks to
indicate glyphs to check (this can be configured below)
USAGE IN THE TERMINAL:
1. Install dependencies. FontTools, FontParts, ufoProcessor, ufonormalizer
2. If you have glyphs you don’t want to respace in support sources, update them in glyphsToNotRespace, below
3. Run this script in a terminal:
python3 <this_script_path>.py <your_designspace_path>.designspace
---
MIT LICENSE:
Copyright 2023 ArrowType / Stephen Nixon
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from fontTools.designspaceLib import (DesignSpaceDocument,
InstanceDescriptor)
from fontParts.fontshell import RFont as Font
from ufoProcessor.ufoOperator import UFOOperator
from ufonormalizer import normalizeUFO
import subprocess
import sys
import os
# -------------------------------------------------------------------
# CONFIGURATION
# if you have glyphs you don’t want to automatically respace (because left/right margins are intended to change), list them here
glyphsToNotRespace = "dotbelowcmb quoteleft quoteright quotedblleft quotedblright quotesinglbase quotedblbase period comma colon semicolon exclam exclamdown question questiondown dotcomb ldotcomb comma.brut semicolon.brut exclam.brut exclamdown.brut colon.tnum semicolon.tnum exclamdown.case_brut exclamdown.case questiondown.case".split(" ")
# a threshold of units above which to not auto-fix spacing differences
ignoreSpacingDiffsAbove = 5
# set to True if you want to unmark all glyphs in support sources, then add a certain color to glyphs to check
markGlyphsToFix = True
markColor = (1.0, 0.65, 0.65, 1) # (1.0, 0.65, 0.65, 1) is a nice pinkish red
# CONFIGURATION
# -------------------------------------------------------------------
# basics to run the script in RoboFont or in the terminal
# if we can import mojo.UI, we are running the script in RoboFont
try:
from mojo.UI import OutputWindow
runInRobofont = True
# if we can’t import mojo.UI, we are running the script via the terminal
except ImportError:
runInRobofont = False
# try to get the designspace path passed in
try:
mainDSpath = sys.argv[1]
# if a user doesn’t pass a designspace path, tell them to try again
except IndexError:
print("You must give a designspace path as an argument, like:")
print()
print(" python3 <this_script_path>.py <your_designspace_path>.designspace")
print()
exit()
# open & clear output window if running in robofont
if runInRobofont:
from vanilla.dialogs import getFile
from mojo.UI import OutputWindow
OutputWindow().show()
OutputWindow().clear()
mainDSpath = getFile("Select Designspace with sparse/support sources", allowsMultipleSelection=False, fileTypes=["designspace"])[0]
# set up a place to store information to print out later
report = {}
# open main DS & fonts with ufoProcessor.ufoOperator
mainDS = DesignSpaceDocument.fromfile(mainDSpath)
openedMainDS = UFOOperator()
openedMainDS.read(mainDSpath)
def createGeneratorDS(mainDS):
"""
mainDS should be a DesignSpaceDocument which includes "support sources"
This function creates a second, more simplistic DesignSpaceDocument, excluding supports.
This allows the script to generate glyphs as they would be, without support sources,
and in turn, that allows you to update your support sources to more smoothly integrate
into the overall designspace, only affecting what you want to affect.
"""
# creates new generator DS with FontTools
generatorDS = DesignSpaceDocument()
try:
# copies basic things
generatorDS.axes = mainDS.axes
generatorDS.axisMappings = mainDS.axisMappings
generatorDS.variableFonts = mainDS.variableFonts
generatorDS.instances = mainDS.instances
except AttributeError as e:
print(e)
print()
print("You must first install FontTools version 4.40.0 or higher in the RoboFont app Resources:")
print()
print("https://robofont.com/documentation/how-tos/overriding-embedded-libraries/#overriding-embedded-libraries")
exit()
for source in mainDS.sources:
# copies sources that aren’t titled as sparse or support sources
if "sparse" not in source.filename and "support" not in source.filename:
generatorDS.sources.append(source)
# also makes instances for support locations, which is important later
if "sparse" not in source.filename or "support" not in source.filename:
supportInstance = InstanceDescriptor()
supportInstance.location = source.location
generatorDS.addInstance(supportInstance)
# also adds a default instance, as required by mutator math
defaultInstance = InstanceDescriptor()
defaultInstance.location = dict(weight=0)
# saves new DS as a file, so we can open it with ufoProcessor
generatorDSpath = mainDS.path.replace(".designspace","-generator.designspace")
generatorDS.write(generatorDSpath)
return generatorDS, generatorDSpath
generatorDS, generatorDSpath = createGeneratorDS(mainDS)
# open generator DS & fonts with ufoProcessor
openedGeneratorDS = UFOOperator()
openedGeneratorDS.read(generatorDSpath)
openedGeneratorDS.loadFonts()
def fixAnchors(glyph1, glyph2):
"""
Clears anchors in the support glyph, and copies over new ones with rounded positioning
"""
# clear any existing anchors
glyph1.clearAnchors()
# # attempt to position anchors better
# glyph2.moveBy((italicOffset, 0))
# copy anchors (may need a loop instead...?)
if len(glyph2.anchors) > 0:
for a in glyph2.anchors:
glyph1.appendAnchor(a.name, (a.x, a.y))
# apply italic offset from interpolated font
# for anchor in glyph1.anchors:
# anchor.x += -1 * glyph2.font.info.italicOffset
def copyG2toG1bg(glyph1, glyph2):
"""
Copies the outline of glyph2 to the background layer of glyph1. Also places guidelines in the glyph1 foreground to show margins of glyph2.
Args:
- glyph1: a manually edited glyph in the "support" source of a buildable designspace
- glyph2: a newly-generated glyph, interpolated from the main/non-support sources of that designspace
"""
supportSourceFont = glyph1.font
italicOffset = supportSourceFont.lib["com.typemytype.robofont.italicSlantOffset"]
# let’s also add a single guide for the interpolated width (TODO: check if italic angle is working)
glyph1.clearGuidelines()
glyph1.appendGuideline((glyph2.width + italicOffset, 0), 90 + supportSourceFont.info.italicAngle)
# also just add a guideline at the left edge, to make the point more obvious
glyph1.appendGuideline((0 + italicOffset, 0), 90 + supportSourceFont.info.italicAngle)
# get background layer name
supportBg = supportSourceFont.layers[1]
supportFontBgLayerName = supportBg.name
# check if g1 has background layer (really, if layer has g1); if not, add it
if glyph1.name not in supportBg:
supportBg.newGlyph(glyph1.name)
# get glyph1 bg, then clear it
glyph1Bg = supportSourceFont[glyph1.name].getLayer(supportFontBgLayerName)
glyph1Bg.clear()
# # move glyph2 so we can position it better in background
# glyph2.moveBy((italicOffset, 0))
# get the point pen of the layer glyph
penToDrawWith = glyph1Bg.getPointPen()
# draw the points of the imported glyph into the layered glyph
glyph2.drawPoints(penToDrawWith)
# match new background layer width with expected layer width
glyph1Bg.width = glyph2.width
def fixSpacing(glyph1, glyph2):
"""
Fixes/updates spacing for glyphs, so long as they aren't "dot glyphs,"
which I want to keep the interpolated width, but get a little wider to stay squarish at thinish weights.
"""
if glyph1.isEmpty() and (glyph1.width - glyph2.width) > ignoreSpacingDiffsAbove:
glyph1.width = round(glyph2.width)
else:
# only fix/update the spacing if the support glyph is not listed in glyphsToNotRespace, and width difference is more than 2 units (i.e. not a rounding issue)
if glyph1.name not in glyphsToNotRespace and (glyph1.width - glyph2.width) > ignoreSpacingDiffsAbove:
glyph1.leftMargin = glyph2.leftMargin
glyph1.width = glyph2.width
def checkForSpacingDiffs(glyph1, glyph2):
"""
Checks for spacing discrepancies in glyph1 vs glyph2.
Args:
- glyph1: a manually edited glyph in the "support" source of a buildable designspace
- glyph2: a newly-generated glyph, interpolated from the main/non-support sources of that designspace
"""
# get basic dimensions of g1
supportGlyphDimensions = (round(glyph1.width), round(glyph1.leftMargin), round(glyph1.rightMargin))
# get basic dimensions of g2
generatedGlyphDimensions = (round(glyph2.width), round(glyph2.leftMargin), round(glyph2.rightMargin))
spacing = (supportGlyphDimensions, generatedGlyphDimensions)
# save differences to logger
if supportGlyphDimensions != generatedGlyphDimensions:
return spacing
def fuzzyReport(glyph1, spacingDiff, marginOfError=2):
"""
Check spacing differences. If they exceed margin of error, add them to the report for fixing.
Spacing diffs arg is a tuple of (glyph1dimensions, glyph2dimensions) if they don't
have exactly matched (width, leftMargin, rightMargin):
> (
> (589, 164, 189), # from support source
> (502, 159, 189) # from generated interpolation
> ),
"""
reportSection = ""
# if these equations are true, these variables become True. If false, they become False
widthDiff = abs(spacingDiff[1][0] - spacingDiff[0][0]) >= marginOfError
leftMarginDiff = abs(spacingDiff[1][1] - spacingDiff[0][1]) >= marginOfError
rightMarginDiff = abs(spacingDiff[1][2] - spacingDiff[0][2]) >= marginOfError
# if spacing differences are found (above the marginOfError), add it to our report
if widthDiff or leftMarginDiff or rightMarginDiff:
reportSection += "\n" + "- [ ] " + glyph1.name + "\n"
# defined in top configuration
if markGlyphsToFix:
glyph1.markColor = markColor
if widthDiff:
reportSection += " - width difference is " + str(abs(spacingDiff[0][0] - spacingDiff[1][0])) + "\n"
if leftMarginDiff:
reportSection += " - leftMargin difference is " + str(abs(spacingDiff[0][1] - spacingDiff[1][1])) + "\n"
if rightMarginDiff:
reportSection += " - rightMargin difference is " + str(abs(spacingDiff[0][2] - spacingDiff[1][2])) + "\n"
report[glyph1.font.path] += reportSection
# a list of locations to check
supportLocations = {}
# first, we make a dict of sparse sources and their locations
for dsSource in openedMainDS.sources:
if "sparse" in dsSource.filename or "support" in dsSource.filename:
supportLocations[dsSource] = dsSource.location
# then we loop through the instances of the "generator" designspace
for dsInstance in generatorDS.instances:
# and check if it matches a support source
for supportSource, location in supportLocations.items():
if dsInstance.location == location:
report[supportSource.path] = ""
# # open support source as RFont ... should we use RF’s OpenFont() to access angledLeftMargin, etc?
supportSourceFont = Font(supportSource.path, showInterface=False)
# feed the instance object into makeInstance from ufoProcessor, to generate that font
# but wrap this in the FontParts OpenFont() to allow manipulation of the glyph (i.e. rounding coordinates)
# generatedInstance = OpenFont(openedGeneratorDS.makeInstance(dsInstance), showInterface=False)
generatedInstance = Font(openedGeneratorDS.makeInstance(dsInstance), showInterface=False)
## only uncomment this if you want to check that the script is really generating UFOs at support locations
# generatedInstance.save(supportSource.path.replace(".ufo","-generated.ufo"))
# go through each glyph in manually edited support source
for g1 in supportSourceFont:
# get corresponding glyph in generated font at same location
g2 = generatedInstance[g1.name]
# round the glyph geometry
g2.round()
# copy new glyphs to background of main support glyphs, for reference
copyG2toG1bg(g1, g2)
# fix some things automatically if possible
fixSpacing(g1, g2)
# clear existing anchors from support, then add new ones
fixAnchors(g1, g2)
spacingDiffs = checkForSpacingDiffs(g1, g2)
# if spacingDiffs not "None"
if spacingDiffs:
if markGlyphsToFix:
# set mark to None, to overwrite it with fuzzy report
g1.markColor = None
# make report if things are more than marginOfError off
fuzzyReport(g1, spacingDiffs, marginOfError=ignoreSpacingDiffsAbove)
supportSourceFont.save()
normalizeUFO(supportSourceFont.path, writeModTimes=False)
print(".", end=" ")
continue
finalReport = ""
# print the spacing report in a readable way
for fontpath, reportSection in report.items():
finalReport += "\n"
finalReport += "<details><summary><b>"
finalReport += f"{os.path.split(fontpath)[1]}"
finalReport += "</b> (Click to expand)</summary>"
finalReport += "\n"
finalReport += reportSection
finalReport += "\n"
finalReport += "</details>"
finalReport += "\n"
print(finalReport)
# remove temporary generator DS
os.remove(generatorDSpath)
subprocess.run("pbcopy", text=True, input=finalReport)
print("GitHub-ready markdown report copied to clipboard!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment