Algorithmically Generating Aesthetically Pleasing Glyphs

Let's make some glyph fonts for my conlang.

Hey there! This is the first issue of my newsletter. I’d appreciate it if you could share it with people who you think would find this sort of content interesting.

I’m a worldbuilder and conlanger in my spare time, which means I spend entirely too much time constructing imaginary languages with painstaking etymological evolution. In the process, I forget that not everything has to have a logical progression— for example, the word “whiskey” came from the Old Irish word “uisce beatha”, meaning “water of life”, because some Irish monks believed it to be a medicinal cure-all. Regardless, I decided to attempt to create my own rune-like font for my conlang, Qurwenyan.

Qurwenyan 101

Qurwenyan is the language officially spoken by the Imperial court and most of the population of the Qurwenyan Empire (see below). The Qurwenyan ruling class is descended from a tribe of nomadic warrior merchants who used their battle-tested skills (passed on by each generation) to acquire a variety of rare hides, tusks, and other animal/monster resources. This resulted in the Qurwenyan language being optimized over the centuries for ease of pronunciation in battle.

World Map of Zindabuul. The Qurwenyan Empire is on the left.

As a consequence of their history, Qurwenyan follows a syllabic structure similar to Japanese, where each syllable consists of a consonant followed by a vowel. The following syllabary outlines the Qurwenyan syllabic alphabet.

a

e (“ay”)

i (“e”)

o

u (“ooh”)

y (“aye“)

b

ba

be

bi

bo

bu

by

f

fa

fe

fi

fo

fu

fy

j

ja

je

ji

jo

ju

jy

k

ka

ke

ki

ko

ku

ky

l

la

le

li

lo

lu

ly

m

ma

me

mi

mo

mu

my

n

na

ne

ni

no

nu

ny

p

pa

pe

pi

po

pu

py

r

ra

re

ri

ro

ru

ry

s

sa

se

si

so

su

sy

t

ta

te

ti

to

tu

ty

v

va

ve

vi

vo

vu

vy

Some notable differences between Qurwenyan and English:

  • by nature of having as few conflicting sounds as possible, there are no equivalent sounds for the English alphabets “d”, “g”, “q”, “w”, “x”, and “z” as the written language was created long after the spoken language was in use,

  • Qurwenyan uses six vowels, the “y” vowel is typically used in “warning words”, a distinctive language feature which evolved from the language’s origins as a battle language,

  • while the syllabary above is useful for reference, only the distinct syllables are part of the Qurwenyan alphabet— consonants and vowels are not separated by part.

Phrases Commonly Heard In Qurweny

na-topeki pyma

  • “na-” is a female honorific,

  • “topeki” translates to “traveler”,

  • and “pyma” is a semiformal greeting which generally indicates openness to a conversation— a little like “how are you”.

no-ri tanekomaki ryfula kajiso

  • “no-” is a male honorific

  • “ri” is a personal pronoun, typically used with either the “na-” or “no-” prefix

  • “tanekomaki” refers to a type of desert pangolin-like sacred animal who is thought by the Qurwenyans to bless military exploits

  • “ryfula” is a farewell greeting

  • “kajiso” roughly translates to “to bid”

The phrase translates to “I bid the tanekomaki farewell” (a Qurwenyan custom before heading to war).

Setting Up Glyph Generation

To generate glyphs for my conlang’s font, we’re going to be using a couple of tools.

While Python and FontForge (a font-generation software) are fairly self-explanatory, PolyGlot might be new to most of you. It’s an open-source conlanging software which has a ton of useful features— a lexicon, custom logographs and fonts, grammar rules, phonology and text databases, and even a quiz generator so you can learn your own language faster!

Fortunately for those of you new to the weird world of conlangs, I’ll only be using two features— the phonology database, and the lexicon.

Before all that, though, I had to create a quick python script to generate some glyphs for the Qurwenyan alphabet. Let me take you through how it works.

import matplotlib.pyplot as plt
import numpy as np
import random
from itertools import combinations

n = 26
vowels = ['a', 'e', 'i', 'o', 'u', 'y']
radical = 'a'

# Define the radicals as line segments
radicals = {
    'a': [(1, 0.5), (1.5, 1)],
    'e': [(1, 1), (1.5, 0.5)],
    'i': [(1, 1), (1.5, 0.5), (2, 1)],
    'o': [(1, 0.5), (1.5, 1), (2, 0.5)],
    'u': [(1.5, 1.5), (2, 1), (1.5, 0.5)],
    'y': [(2, 1.5), (1.5, 1), (2, 0.5)]
}

def check_intersection(p1, p2, p3, p4):
    # Helper function to check if two line segments (p1p2 and p3p4) intersect
    def ccw(A, B, C):
        return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0])

    return ccw(p1, p3, p4) != ccw(p2, p3, p4) and ccw(p1, p2, p3) != ccw(p1, p2, p4)

def glyph_generator(grid_size=5, num_points=5):
    while True:
        points = set()
        while len(points) < num_points:
            points.add((random.randint(1, grid_size - 2), random.randint(1, grid_size - 2)))
        points = list(points)
        random.shuffle(points)

        segments = [(points[i], points[i + 1]) for i in range(len(points) - 1)]
        intersection_count = sum(check_intersection(s1[0], s1[1], s2[0], s2[1]) for s1, s2 in combinations(segments, 2))

        if intersection_count <= 1:
            x_values, y_values = zip(*points)
            yield x_values, y_values

def save_glyphs(num_glyphs, grid_size=5, num_points=5):
    generator = glyph_generator(grid_size, num_points)
    chosen_radical = radical
    
    for i in range(num_glyphs):
        x_values, y_values = next(generator)
        
        fig, ax = plt.subplots()
        ax.set_xlim(0.5, grid_size - 1.5)
        ax.set_ylim(0.5, grid_size - 1.5)
        ax.set_xticks(np.arange(0, grid_size, 1))
        ax.set_yticks(np.arange(0, grid_size, 1))
        ax.grid(True)
        
        ax.plot(x_values, y_values, marker='o', linewidth=10)
        
        # Add the chosen radical at the top of the grid
        radical_points = radicals[chosen_radical]
        radical_x, radical_y = zip(*radical_points)
        ax.plot(radical_x, radical_y, marker='o', color='red', linewidth=10)
        
        plt.gca().invert_yaxis()
        plt.axis('off')
        plt.savefig(f'glyph_{i+1}.png')
        plt.close()

# Save n glyphs to the current folder
save_glyphs(n)

The above script:

  1. Defines a helper function (`check_intersection`) to check if two line segments intersect. This is useful to make sure our glyphs are not just a scribble of lines.

  2. Uses randomized point selection on the graph to create the turning points of the glyph, after which line segments are drawn between them.

  3. Adds pre-defined radicals (each one represents a vowel) to each generated glyph.

  4. Saves each glyph to a picture in the current directory.

Choosing Glyphs For Each Alphabet

After a few runs of the glyph generator, I was able to generate satisfactory glyphs for all 72(!) alphabets. One important thing to note is that each vowel is represented as a radical on its base consonant. However, by virtue of clarity of pronunciation, consonants have never been used independently or in series. After generating all the glyphs, I sorted them by consonant in readiness for conversion into a usable font:

My folder structure might be massive, but it turned out to be pretty useful when adding the images on FontForge.

Next up, I used Calligraphr.com to import each of the created images and convert them into a downloadable and usable .otf font file. Here’s the result:

Not bad, not bad at all! Now let me try typing out the phrases I mentioned earlier using my newly created font.

na-topeki pyma

how are you doing, traveler?

It works!

After getting to this point, I decided to write an AutoHotKey (scripting language for Windows) script to replace sequences of normally-typed syllables with the corresponding symbol for my font, since each symbol in the font is mapped to really weird keys (I could not be bothered to change it, since the AHK script fixes it anyways). Here’s a short gif of it working:

initially, I type it without the AHK script running, and then I start it, and show the difference

This was a really fun project, and I certainly learnt a lot about fonts along the way. I’ll probably keep improving the font with things like kerning and bold/italic versions, but this is it for now. I hope you liked reading this! If you’re interested in downloading and installing the fonts and all the source code, I’ve uploaded everything to this GitHub repo.

I haven’t decided what to write the next issue of Tetraslam’s Technofuture Transmissions on yet, but I’m thinking of doing a rundown of my favorite papers from the Association for Computational Heresy (yes, that’s a real thing)! Now that would be an interesting post.

Check out my socials/info: