Skip to content

Instantly share code, notes, and snippets.

@darrenwiens
Last active November 19, 2024 14:09
Show Gist options
  • Save darrenwiens/ad6d07fc267fc3c9fb647b87d55af28c to your computer and use it in GitHub Desktop.
Save darrenwiens/ad6d07fc267fc3c9fb647b87d55af28c to your computer and use it in GitHub Desktop.
Supplements a font with ligatures of country and US state shapes. E.g. type "stateCA" -> California shape, "countryCA" -> Canada shape.
import fontforge
import json
from shapely.geometry import shape, MultiPolygon
from shapely.affinity import translate
font = fontforge.open("Arial.ttf") # font to edit
font.fullname = "Geo Ligatures"
font.familyname = "Geo Ligatures"
font.fontname = "GEO_LIGATURES"
font.version = "1.0"
font.addLookup('ligatures','gsub_ligature', (),[['rlig',[['latn',['dflt']]]]])
font.addLookupSubtable('ligatures', 'ligatureshi')
glyph_w = 1000
glyph_h = 800
def scaled_xy(x, y, minx, miny, maxx, maxy):
scaled_x = (x - minx)/(maxx - minx)
scaled_y = (y - miny)/(maxy - miny)
return (scaled_x * glyph_w, scaled_y * glyph_h)
def create_ligature(geo_file, code_col, ligature_prefix):
with open(geo_file, "r") as f:
features = json.load(f)["features"]
for i, feature in enumerate(features):
first_char = feature["properties"][code_col][0]
second_char = feature["properties"][code_col][1]
ligature_name = f"{ligature_prefix}_{first_char}_{second_char}"
ligature_glyph = font.createChar(-1, ligature_name)
ligature_tuple = tuple(list(ligature_prefix) + [first_char, second_char])
ligature_glyph.addPosSub('ligatureshi', ligature_tuple)
pen = ligature_glyph.glyphPen()
mpoly = shape(feature["geometry"])
if mpoly.geom_type == "Polygon":
mpoly = MultiPolygon([mpoly])
new_geoms = []
for part in mpoly.geoms: # handle features that straddle antimeridian
if part.centroid.x < 0:
new_geoms.append(translate(part, xoff=360.0, yoff=0.0, zoff=0.0))
else:
new_geoms.append(part)
mpoly = MultiPolygon(new_geoms)
bnds = mpoly.bounds
for j, part in enumerate(mpoly.geoms):
coords = scaled_xy(*part.exterior.coords[0], *bnds)
pen.moveTo(coords)
for coords in part.exterior.coords[1:]:
pen.lineTo(scaled_xy(*coords, *bnds))
pen.closePath()
pen = None
ligature_glyph.width = 1000
ligature_glyph.autoInstr()
create_ligature("states.geojson", "postal", "state")
create_ligature("countries.geojson", "ISO_A2", "country")
font.generate("./geo-ligatures.ttf") # output font
font.save()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment