About »

Concepts

about Lettersoup

get it

read on it

Overview

Traditional font file formats - PS Type 1, TrueType, OpenType - have been historically constrained to simple point-by-point representations of glyphs. While this approach apparently makes things simpler, it restricts one's creativity to their own skills in point manipulation. These formats, and the software packages that operate on them, offer no straightforward way to apply changes such as "make the serifs wider by 2 points" or "make the shapes a little rounder" without going through a cumbersome process of editing every single glyph. Modern software packages, such as FontLab Studio, offer a few shortcuts for this purpose, but they're still inevitably bound to the point-by-point design paradigm. Furthermore, type design software and file formats have historically been closed, proprietary and thus solely oriented towards a technically-savvy group of specialists.

Lettersoup is an attempt to propose a different way to design and manipulate typefaces, away from the conventions of traditional type design software. Some principles would constitute the foundation of such a system:

  • Glyphs have to be more than simple groups of points or pixels; they should be defined in human terms - a letter is built out of specific components (stems, bars, serifs, etc), and the software should reflect this, as well as the file format
  • It's far more efficient to generate a whole group of typefaces out of a set of common elements (again, stems, bars et al) than to trace drawings and manually double-check each glyph's dimensions
  • Instead of presenting the user with the full shebang of glyphs, an ideal interface would provide them with several tweakable parameters which would determine the shape and features of the final glyph set
  • The general program which creates the font (we'll refer to it as the "font master") should be both accessible via plaintext formats, as well as a clean and direct GUI
  • The data formats ought to be open and, thus, hackable

The Lettersoup model inevitably has more demands than regular font file formats, since it has to account for the dynamic rules to generate the final lettershapes. The file format thus has to go beyond a simple description of vector data in order to support variable parameters.

Font master structure

A font master is no more than a drawing machine that takes user input and returns a font according to the specified values. The creation process consists of

  • getting variables (input values) from the user
  • generating the font parts (stems, bars, diagonals, etc.)
  • creating SVG files from each glyph from the generated parts according to a blueprint that specifies what goes where
  • taking the vector data and create a final TrueType font file

The Lettersoup font master file structure places each different step of the generation process into its own file:

  • parts.py
  • blueprint.yaml
  • widths.yaml
  • variables.yaml

Variables

Lettersoup's model is based on having a fixed set of variables which are then fed to the font master script, which in turn builds the final glyph set according to the values specified. The reference for these is Donald Knuth's METAFONT; while the sample Lettersoup fontmaster only uses a few of them, it is perfectly feasible to build a fontmaster that takes advantage of having more tweakable parameters. The variables supported by the sample font-master are

  • em_width - glyph width
  • cap_height - glyph height (uppercase)
  • stem - vertical stroke thickness
  • bar - horizontal stroke thickness
  • jut - serif width
  • slab - serif height

These variables can be fed to Lettersoup via a variables.yaml file or directly through a GUI like the lettersoup-demo.py script. The way variables define the final lettershapes is defined in the parts file, the structure of which is explained below.

Blueprint

The blueprint file specifies how Lettersoup should draw each glyph by indicating which parts go into it, as well as how they are drawn. For illustration purposes, we'll draw on the letter "H" from the sample fontmaster.

    h: 
        - part_stem:
            pos: left
            serifs: NW,NE,SW,SE
        - part_stem:
            pos: right
            serifs: NW,NE,SW,SE
        - part_bar:
            start: left
            end: right

The blueprint.yaml file, as its extension implies, is written in YAML, a markup language which blends CSS-style syntax with Python indentation rules, resulting in highly readable data structures. Let's now dissect the composition of this letter:

  • it is made from 3 parts: two stems and one bar.
  • both stems include serifs in all four corners, specified here using cardinal points (NW = Northwest, SW = Southwest, NE = Northeast, SE = Southeast). Bars also support serifs, but in this case they are not appropriate or necessary.
  • the 'pos' attribute specifies the position of each stem: one goes to the left, and the other is placed on the right side.
  • the bar's attributes indicate that it should start on the left side and end on the right side of the glyph. Stems also accept 'start' and 'end' attributes, but by leaving them out we indicate we want stems that go from the top until the bottom of the glyph.

Next we'll look over how each of these parts is defined, as well as the attributes it accepts and how it interacts with the user variables.

Parts

The parts file - parts.py - is a Shoebot script which defines how the common letter components - stems, bars, corners, diagonals, serifs - should be drawn. This file is written in the Shoebot/Nodebox domain-specific language, which is essentially Python code with a few vector drawing functions thrown in. Each part is defined in how it articulates with the variables specified above (em_width, cap_height, etc.). As an example, here's the code for generating the stem of the sample fontmaster in Lettersoup. It starts with a regular Python function declaration specifying the part name and the creation parameters it takes:

    def part_stem(serifs='NW,NE,SW,SE', pos=LEFT, start=None, end=None):

The name of this part is part_stem - every part name has a "part-" prefix in order to avoid name collisions. The parameters it accepts is "serifs", "pos", "start" and "end". These are specified in the blueprint file to determine the final shape and position of each part. The function docstring specifies the necessary syntax:

        '''Vertical stem, used for example in I or H.

        pos: can be LEFT, RIGHT or CENTER.
        serifs: a comma-separated string, can include NW, NE, SW and/or SE.

        Without further parameters, the stem will go from the top of the glyph
        to the baseline. In other cases, such as the side of the O, you can
        specify the start and end parameters:

        start: can be 
          - TOPCORNER (starts right below the top corner height)
          - BARCORNER (starts below the bar corner height, e.g. the
                       straight part of the B lower belly)
          - or MIDDLE (starts at the middle of the glyph, depending on bar height)
          If this is not specified, the stem starts at the top of the bounding box.

        end: can be BARCORNER or BOTTOMCORNER. If this is not specified, the stem 
        ends at the bottom of the bounding box.
        '''

Following that we have the actual code. The final shape is defined using Shoebot commands. In the sample font's case, the body of the stem is a rectangle, so we have to determine its coordinates according to the input parameters.

        # the stem body is a rectangle, so we start by defining the necessary
        # coordinates to draw it (starting point x and y, width and height)

        # set horizontal starting point according to position
        if pos == LEFT:
            x = jut
        elif pos == RIGHT:
            x = _glyph_width - jut - stem
        elif pos == CENTER:
            x = _glyph_width / 2 - stem/2

        # set vertical starting point according to position
        if start == TOPCORNER:
            y = bar + gap
        elif start == BARCORNER:
            y = cap_height/2 + gap + bar_height
        elif start == MIDDLE:
            y = cap_height/2 + bar_height
        else:
            y = 0

        # the width equals the stem variable
        w = stem

        if end == MIDDLE:
            h = cap_height / 2 + bar/2 + bar_height
        elif end == BOTTOMCORNER:
            h = cap_height - bar - gap - y
        elif end == BARCORNER:
            h = cap_height/2 - gap - y + bar_height
        else:
            h = cap_height - y

        # finally, draw the stem body
        rect(x,y,w,h)

Afterwards, we take care of the serifs which, conveniently, are rectangles as well.

        # if serifs were specified, draw them
        if serifs:
            # make a list out of the input string
            # which can be something like 'NW,NE'
            serifs = serifs.split(',')
            # NW serif
            if NW in serifs:
                x = stem_x - stem/2 - jut
                y = 0
                w = stem/2 + jut
                h = slab
                rect(x, y, w, h)

            # NE serif
            if NE in serifs:
                x = stem_x
                y = 0
                w = stem/2 + jut
                h = slab
                rect(x, y, w, h)

            # SW serif
            if SW in serifs:
                x = stem_x - stem/2 - jut
                y = cap_height - slab
                w = stem/2 + jut
                h = slab
                rect(x, y, w, h)

            # SE serif
            if SE in serifs:
                x = stem_x
                y = cap_height - slab
                w = stem/2 + jut
                h = slab
                rect(x, y, w, h)

(NOTE: There are some helper variables, e.g. 'gap' and 'stem_x' which have to be replaced for clarity)

Widths

Finally, we have the widths.yaml file. Its purpose is to simply store the width values for each glyph. It is also written in YAML, and looks like this:

    J: .55
    K: .85
    L: .65
    M: 1.
    N: .7
    O: .65

The values are specified in relation to the em-width (note that the 'M' has a width of one).

Generating a font

TODO: All the examples I used for my MA were quick hacks; the most necessary task now is to write a simple general-purpose script to generate font faces (all parts are working, we just need a simple interface).

Page last modified on August 29, 2008, at 11:47 AM

Edit - History - Print - Recent Changes (All) - Search