Skip to content

Instantly share code, notes, and snippets.

@madig
Last active July 19, 2019 16:58
Show Gist options
  • Save madig/6ded5e68a06f6f0301de6b704eaba5cb to your computer and use it in GitHub Desktop.
Save madig/6ded5e68a06f6f0301de6b704eaba5cb to your computer and use it in GitHub Desktop.
designspace deskew
#%%
import collections
import itertools
import fontTools.designspaceLib as designspaceLib
import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial
def designspace_vertices(sources):
source_locations = [
tuple(x for x in source.location.values()) for source in sources
]
n_axes = len(source_locations[0])
vertices = sorted(scipy.spatial.ConvexHull(source_locations).vertices)
expected_vertices = 2 ** n_axes
if len(vertices) != expected_vertices:
raise ValueError(
f"Designspace has {n_axes} axes. Expected {expected_vertices} vertices, "
f"got {vertices}, cannot rectangularize."
)
return [source_locations[i] for i in vertices]
def project_coordinates(coords, projection_matrix):
coords_np = np.array([x for x in coords] + [1])
coords_projected_homogenous_full = projection_matrix.dot(coords_np)
coords_projected_homogenous = coords_projected_homogenous_full[:-1]
coords_projected = (
coords_projected_homogenous / coords_projected_homogenous_full[-1]
)
return coords_projected.round()
#%%
d = designspaceLib.DesignSpaceDocument.fromfile("Encode-Sans-skewed.designspace")
#%%
plt.xlabel(d.axes[0].name)
plt.ylabel(d.axes[1].name)
for source in d.sources:
plt.plot(*source.location.values(), "sy")
for instance in d.instances:
plt.plot(*instance.location.values(), ".k")
plt.show()
#%%
# https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript/339033
vertices_orig = sorted(designspace_vertices(d.sources))
axis_bounds = [
(axis.map_forward(axis.minimum), axis.map_forward(axis.maximum)) for axis in d.axes
]
vertices_new = sorted(itertools.product(*axis_bounds))
coord_orig = [(*t, 1) for t in vertices_orig]
coord_orig_a = coord_orig[:-1]
coord_orig_b = coord_orig[-1]
coord_orig_a_m = np.array(coord_orig_a).transpose()
coord_orig_b_m = np.array(coord_orig_b).transpose()
A = coord_orig_a_m * np.linalg.solve(coord_orig_a_m, coord_orig_b_m)
coord_new = [(*t, 1) for t in vertices_new]
coord_new_a = coord_new[:-1]
coord_new_b = coord_new[-1]
coord_new_a_m = np.array(coord_new_a).transpose()
coord_new_b_m = np.array(coord_new_b).transpose()
B = coord_new_a_m * np.linalg.solve(coord_new_a_m, coord_new_b_m)
H = B.dot(np.linalg.inv(A))
#%%
plt.xlabel(d.axes[0].name)
plt.ylabel(d.axes[1].name)
for source in d.sources:
coords = project_coordinates(source.location.values(), H)
plt.plot(*coords, "sy")
for instance in d.instances:
coords = project_coordinates(instance.location.values(), H)
plt.plot(*coords, ".k")
plt.show()
<?xml version='1.0' encoding='UTF-8'?>
<designspace format="4.0">
<axes>
<axis tag="wght" name="Weight" minimum="100" maximum="900" default="100">
<map input="100" output="34"/>
<map input="200" output="45"/>
<map input="300" output="62"/>
<map input="400" output="84"/>
<map input="500" output="111"/>
<map input="600" output="141"/>
<map input="700" output="173"/>
<map input="800" output="204"/>
<map input="900" output="232"/>
</axis>
<axis tag="wdth" name="Width" minimum="75" maximum="125" default="75">
<map input="75" output="0"/>
<map input="87.5" output="250"/>
<map input="100" output="500"/>
<map input="112.5" output="750"/>
<map input="125" output="1000"/>
</axis>
</axes>
<sources>
<source filename="EncodeSans-ThinCondensed.ufo">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="0"/>
</location>
</source>
<source filename="EncodeSans-CondensedBold.ufo">
<location>
<dimension name="Weight" xvalue="193"/>
<dimension name="Width" xvalue="0"/>
</location>
</source>
<source filename="EncodeSans-ThinExpanded.ufo">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="1000"/>
</location>
</source>
<source filename="EncodeSans-BoldExpanded.ufo">
<location>
<dimension name="Weight" xvalue="232"/>
<dimension name="Width" xvalue="1000"/>
</location>
</source>
</sources>
<instances>
<instance stylename="Thin">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="ExtraLight">
<location>
<dimension name="Weight" xvalue="44"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Light">
<location>
<dimension name="Weight" xvalue="58"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Regular">
<location>
<dimension name="Weight" xvalue="76"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Medium">
<location>
<dimension name="Weight" xvalue="97"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="SemiBold">
<location>
<dimension name="Weight" xvalue="121"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Bold">
<location>
<dimension name="Weight" xvalue="146"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="ExtraBold">
<location>
<dimension name="Weight" xvalue="171"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Black">
<location>
<dimension name="Weight" xvalue="193"/>
<dimension name="Width" xvalue="0"/>
</location>
</instance>
<instance stylename="Thin">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="ExtraLight">
<location>
<dimension name="Weight" xvalue="44"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Light">
<location>
<dimension name="Weight" xvalue="59"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Regular">
<location>
<dimension name="Weight" xvalue="78"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Medium">
<location>
<dimension name="Weight" xvalue="101"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="SemiBold">
<location>
<dimension name="Weight" xvalue="126"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Bold">
<location>
<dimension name="Weight" xvalue="153"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="ExtraBold">
<location>
<dimension name="Weight" xvalue="180"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Black">
<location>
<dimension name="Weight" xvalue="203"/>
<dimension name="Width" xvalue="250"/>
</location>
</instance>
<instance stylename="Thin">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="ExtraLight">
<location>
<dimension name="Weight" xvalue="44"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Light">
<location>
<dimension name="Weight" xvalue="60"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Regular">
<location>
<dimension name="Weight" xvalue="80"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Medium">
<location>
<dimension name="Weight" xvalue="104"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="SemiBold">
<location>
<dimension name="Weight" xvalue="131"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Bold">
<location>
<dimension name="Weight" xvalue="160"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="ExtraBold">
<location>
<dimension name="Weight" xvalue="188"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Black">
<location>
<dimension name="Weight" xvalue="213"/>
<dimension name="Width" xvalue="500"/>
</location>
</instance>
<instance stylename="Thin">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="ExtraLight">
<location>
<dimension name="Weight" xvalue="45"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Light">
<location>
<dimension name="Weight" xvalue="61"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Regular">
<location>
<dimension name="Weight" xvalue="82"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Medium">
<location>
<dimension name="Weight" xvalue="108"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="SemiBold">
<location>
<dimension name="Weight" xvalue="136"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Bold">
<location>
<dimension name="Weight" xvalue="167"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="ExtraBold">
<location>
<dimension name="Weight" xvalue="196"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Black">
<location>
<dimension name="Weight" xvalue="222"/>
<dimension name="Width" xvalue="750"/>
</location>
</instance>
<instance stylename="Thin">
<location>
<dimension name="Weight" xvalue="34"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="ExtraLight">
<location>
<dimension name="Weight" xvalue="45"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="Light">
<location>
<dimension name="Weight" xvalue="62"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="Regular">
<location>
<dimension name="Weight" xvalue="84"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="Medium">
<location>
<dimension name="Weight" xvalue="111"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="SemiBold">
<location>
<dimension name="Weight" xvalue="141"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="Bold">
<location>
<dimension name="Weight" xvalue="173"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="ExtraBold">
<location>
<dimension name="Weight" xvalue="204"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
<instance stylename="Black">
<location>
<dimension name="Weight" xvalue="232"/>
<dimension name="Width" xvalue="1000"/>
</location>
</instance>
</instances>
</designspace>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment