<!-- vim: set nocp expandtab ft=markdown : -->
<!-- SPDX-License-Identifier: Multics or MIT-0 -->
<!-- Copyright (c) 2025 Jeffrey H. Johnson -->
<!-- scspell-id: 73135104-9b3c-11f0-b48f-80ee73e9b8e7 -->
# Multics Graphics Character Tables to TrueType conversion

* Impatient?
  * Check out the fonts as [PNG](#preview) or [PDF](https://gitlab.com/dps8m/font-gct-multics/-/raw/master/.preview/samples.pdf?inline=true) previews, or,
    * [**download the** ***TrueType fonts*** **here**](TrueType).

<table border="0"><tr><td align="center" valign="middle"><img style="display:block;" src=".images/MGS.png" width="60%" height="60%"/></td></tr></table>

## Overview

### Background

The [**Multics Graphics System**](https://multicians.org/features.html#tag110)
includes a set of twelve vector fonts, based on the
[Hershey fonts](https://en.wikipedia.org/wiki/Hershey_fonts) originally
developed in 1967 by Dr. Allen Vincent Hershey at the U.S. Naval Weapons
Laboratory, and were published and distributed in various forms by the U.S.
National Bureau of Standards (now NIST, the National Institute of Standards
and Technology), and NTIS, the National Technical Information Service,
through the mid-1980s.

This repository contains the original Multics GCT font data,
**TrueType conversions** of the Multics fonts, the software developed to
produce the conversions, and this *README* file intended to document
the process.

You can see the **Multics Graphics System** (and these fonts) in action in
this [**demonstration video**](https://www.youtube.com/watch?v=-Xyaz6Yk0wM),
or in print in this [brochure](https://multicians.org/multics-graphics.pdf).

> While the conversion techniques and software developed here might be
> applicable to the vanilla Hershey font set (or other vector fonts), these
> tools were developed *specifically* to convert Multics GCT (Graphics
> Character Table) files and *only* supports GCT input. Conversion of SVG,
> [JHF](https://paulbourke.net/dataformats/hershey/), or other vector font
> formats is out of scope for this project.

### Stage 1

The first step was to write a program to parse and render the GCT font files.
Thankfully, the GCT format is straightforward and the complete source code to
the Multics Graphics System is available to reference.

The Graphics Character Tables are standard text files that define each glyph
(character) using two commands: `vector` (for drawing "*strokes*" with a
virtual pen) and `shift` (which moves the pen without drawing), using a
relative coordinate system (and is actually a simplified subset of the file
format used by the Multics Graphics Editor).

* For example, this is the GCT data for the glyphs `L` and `l` from the
  *Block Roman* font:

  ```
  L:
            vector   0  -8
            vector   6   0
            shift    2   8
            end

  l:
            shift    3  -8
            vector   0   8
            shift    5   0
            end
  ```

> The complete font data was extracted from
> [`bound_graphic_fonts_.s.archive`](https://dps8m.gitlab.io/sb/MR12.8/library_dir_dir/system_library_unbundled/source/bound_graphic_fonts_.s.archive/)
> (from [Multics MR12.8](https://multics-wiki.swenson.org)).

#### Strokes

Rather than simply drawing lines, it was decided that each stroke should be
drawn in a way that emulates the style of lines produced by a turret pen
plotter (with somewhat wide pens), as this was the output system recommended
in the
[original publication](https://www.google.com/books/edition/Calligraphy_for_Computers/qFFCAAAAIAAJ?hl=en)
and other
[historical documentation](https://www.google.com/books/edition/A_Contribution_to_Computer_Typesetting_T/8DOGhKjPAyEC?hl=en).

Originally, the parser and rendering engine was written in clean C99, but this
was (sadly) soon converted to Python 3 to leverage FontForge for the actual
drawing of the strokes (since FontForge provides a Python 3 module).

This program is available here
(as [`convert_gct_stroke.py`](convert_gct_stroke.py)) and produces output for
all **1,130** GCT glyphs in the Fontforge SFD
([Spline Font Database](https://github.com/fontforge/fontforge/blob/master/fontforge/sfd.c))
format.

* The resulting SFD files can be loaded directly into FontForge for analysis:

<table border="0"><tr><td><img style="display:block;" src=".images/FF_Stroke.png" width="100%" height="100%"/></td></tr></table>

Note that some of the glyphs in the FontForge preview have filled interiors.
This is expected at this point, only affects the preview, and can be safely
ignored for now (since we will only use these files as a reference of correct
stroke rendering and not for directly creating the font outlines).

* You can see in the following images how the glyphs are made from a series
  of strokes:

<table border="0"><tr><td><img style="display:block;" src=".images/M0_Stroke.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".images/M1_Stroke.png" width="100%" height="100%"/></td></tr></table>

Notice there are no real curves at all.  Everything is a series of straight
lines and the rounded parts of the glyphs are created by simulating the
shape of the nib of the plotter's pen.

#### Conversion

Unfortunately, stroke-based vector fonts are fundamentally incompatible with
modern outline typography, like TrueType.  TrueType fonts are "outlined" and
then filled, where plotter/vector fonts are composed purely from the pen
strokes as rendered above.

What we need to do is draw these shapes again, but instead of pen strokes
representing paths, we need closed contour outlines so a TrueType font
rendering engine can fill them in.

This means we have to come up with a different way of drawing these glyphs,
so that we end up with fillable watertight outlines.  This is a non-trivial
task.  TrueType font rendering depends on "direction" and is very sensitive
to "nesting" and "winding", so if you draw the outlines in the "wrong"
direction you'll end up with the opposite parts of the glyph filled as what
you expected (or worse).

There simply isn't any exact conversion of centerline-stroked polylines to
closed contours (quadratic Béziers) that will *always* work.  Simply
converting a pen stroke (with a width from the vector centerlines) into
outlines seemed to be the most feasible approximation, but it still is not a
neutral transformation, as it requires careful handling of stroke overlaps
(where the centerlines cross) to ensure the final rendering is properly
filled.  There is also a fair bit of math involved (cubic to quadratic Bézier
conversion) which can introduce errors as approximation is necessary.

##### Trouble and a workaround

Lucky for us, FontForge comes to the rescue here, implementing the primitives
needed.

Unlucky for us, FontForge often crashes when faced with complex scenarios
(and when it crashes it takes down Python with it).  Different versions of
FontForge crash in a myriad of different ways.  The Fontforge people *do* fix
these problems, but many still remain.  At the time of writing, there were
[more than **1,000** open FontForge issues](https://github.com/fontforge/fontforge/issues)
with more than **100** open issues for bugs that result in crashes or hangs.

> If you are able, *please*
> *[help](http://designwithfontforge.com/en-US/When_Things_Go_Wrong_With_Fontforge_Itself.html)*
> FontForge by fixing bugs, or *[donate](https://fontforge.org/en-US/donate/)*
> to fund the project.  FontForge is the ***only*** professional and scriptable
> open/free/libre font authoring solution.

Working around these FontForge and Python issues required a lot extra effort,
and it was very difficult for me (someone who is ***not*** a Python person,
*and doesn't want to be*) to debug the problems I was encountering and find
usable solutions.

After many different approaches and failed attempts (**by far, fighting
Python and FontForge was the largest chunk of time spent on this project by**
***many orders of magnitude***), I settled on using the standard Python 3
[`multiprocessing`](https://docs.python.org/3/library/multiprocessing.html)
package to work around the problems.

We are **not** using this package to add any kind of parallelism or
multiprocessing support, but taking advantage of the fact we can use it to
spawn a fresh Python interpreter in a new process per glyph.  This way if
the FontForge module crashes (or just hangs forever), it doesn't take down the
entire Python process completely, and we can monitor the status of the child
interpreter for crashes or hangs, soldiering on when problems are encountered.

#### Outlines

The actual conversion of strokes to outlines is done with the
[`convert_gct_outline.py`](convert_gct_outline.py) script, which actually
implements two different outline conversion methods:

1. The first method I believe should *just work* (but in reality, it doesn't).
   For each glyph defined in the GCT input, we create a new FontForge glyph,
   and using the FontForge stroke tool, we draw the glyph by tracing the
   original stroked lines, applying the pen width to all paths simultaneously,
   finally performing a cleanup of any errors and overlaps of the entire glyph
   at once at the end of rendering.  This works for the vast majority of
   glyphs, producing incorrect output for a very small number of glyphs, but
   also hanging forever or outright crashing on others.

2. For any glyphs that failed to render using the standard method above, we
   use a fallback method which isn't as accurate and is much slower, involving
   processing each stroke independently of any other stroke, and then merging
   all the strokes together at the end, and then cleaning up the merged result.

The second method is about ten times slower than the first method.  It also
seems to have much worse output in many cases, while *still* crashing or
hanging on some inputs.  However, by a 'stroke' of luck (*da-dum-tssss*),
method #2 is able to successfully handle all the glyphs (and without crashing
or hanging or looking terrible) that method #1 cannot.

* The script [`run.sh`](run.sh), when run without arguments (or executed via
  `make stage1`) will automatically run **both** the stroke and outline
  conversions for all the GCTs automatically, creating a detailed log file for
  each font.  At the end of the process, the log files are analyzed and a
  summary is displayed:

  ```
  Converting GCTBlockRoman:         Stroke: Success        Outline: Success
  Converting GCTComplexItalic:      Stroke: Success        Outline: Success
  Converting GCTComplexRoman:       Stroke: Success        Outline: Success
  Converting GCTComplexScript:      Stroke: Success        Outline: Success
  Converting GCTDuplexRoman:        Stroke: Success        Outline: Success
  Converting GCTGothicEnglish:      Stroke: Success        Outline: Success
  Converting GCTGothicGerman:       Stroke: Success        Outline: Success
  Converting GCTGothicItalian:      Stroke: Success        Outline: Success
  Converting GCTSimplexRoman:       Stroke: Success        Outline: Success
  Converting GCTSimplexScript:      Stroke: Success        Outline: Success
  Converting GCTTriplexItalic:      Stroke: Success        Outline: Success
  Converting GCTTriplexRoman:       Stroke: Success        Outline: Success

  SUMMARY: 1130 total glyphs in, 1130 total glyphs out.
  ```
[]()

[]()
* The output from the above is archived in the
  [`intermediate/stage1`](intermediate/stage1) directory.

### Stage 2

The second step involves a (minimal) bit of manual work. We'll compare
the canonical (stroked) version of each glyph with our newly generated
outline conversion and correct any errors:

<table border="0"><tr><td><img style="display:block;" src=".images/A_Stroke.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".images/A_Outline.png" width="100%" height="100%"/></td></tr><tr><td><img style="display:block;" src=".images/W_Stroke.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".images/W_Outline.png" width="100%" height="100%"/></td></tr></table>

* **Conversion details per font:**
  * **Block Roman**: **100**% (93 of 93 glyphs) converted successfully.
  * **Complex Italic**: **100**% (94 of 94 glyphs) converted successfully.
  * **Complex Roman**: **100**% (94 of 94 glyphs) converted successfully.
  * **Complex Script**: **98**% (92 of 94 glyphs) converted successfully:
    manually fixed **`R`** and **`k`**.
  * **Duplex Roman**: **100**% (94 of 94 glyphs) converted successfully.
  * **Gothic English**: **97**% (91 of 94 glyphs) converted successfully:
    manually fixed **`G`**, **`R`** and **`s`**.
  * **Gothic German**: **94**% (91 of 97 glyphs) converted successfully:
    manually fixed **`C`**, **`E`**, **`K`**, **`R`**, **`W`**, and **`p`**.
  * **Gothic Italian**: **96**% (90 of 94 glyphs) converted successfully:
    manually fixed **`F`**, **`L`**, **`P`**, and **`Z`**.
  * **Simplex Roman**: **100**% (94 of 94 glyphs) converted successfully.
  * **Simplex Script**: **100**% (94 of 94 glyphs) converted successfully.
  * **Triplex Italic**: **100**% (94 of 94 glyphs) converted successfully.
  * **Triplex Roman**: **99**% (93 of 94 glyphs) converted successfully:
    manually fixed **`w`**.
[]()

[]()
* In total, **99**% (**1,114** of **1,130** glyphs) were automatically
  converted without issues, leaving **16** glyphs requiring manual attention.
[]()

[]()
* The output of the above is archived in the
  [`intermediate/stage2`](intermediate/stage2) directory.

### Stage 3

The third step consists of any final editing and tweaking of the fonts,
mostly metadata adjustments, before the final conversion to TrueType.

* I'll be working on these files here in the
  [`intermediate/stage3`](intermediate/stage2) directory.

While the other [`intermediate/`](intermediate) directories have files
that won't change going forward, the files in
[`intermediate/stage3`](intermediate/stage2) will continue to be updated
(under version control).

To actually build the TrueType fonts, the [`run.sh`](run.sh) script accepts
the '`ttf`' option to help automate the process.  You'll need to point the
`ERRNUM` environment variable to the path of the `errnum` utility.  For
example, to build TrueType fonts in the [`TrueType`](TrueType) directory,
from the top level of the repo:

```sh
$ make errnum
$ mkdir -p TrueType
$ cd TrueType
$ ln -s ../intermediate/stage3/*.sfd .
$ env ERRNUM=../errnum ../run.sh ttf  
Converting GCTBlockRoman:       TrueType: Success
Converting GCTComplexItalic:    TrueType: Success
Converting GCTComplexRoman:     TrueType: Success
Converting GCTComplexScript:    TrueType: Success
Converting GCTDuplexRoman:      TrueType: Success
Converting GCTGothicEnglish:    TrueType: Success
Converting GCTGothicGerman:     TrueType: Success
Converting GCTGothicItalian:    TrueType: Success
Converting GCTSimplexRoman:     TrueType: Success
Converting GCTSimplexScript:    TrueType: Success
Converting GCTTriplexItalic:    TrueType: Success
Converting GCTTriplexRoman:     TrueType: Success
$ rm -f ./*.sfd
```

The above command will create the TrueType format fonts and also write a log
file for each font.  Since these logs are uneventful (in the case of
successful conversions) I have not included them in the repository.

#### Finishing touches

* 2025-10-01: *Gothic English*: lowercase `z` needed a correction.
* 2025-10-02: *Block Roman*: scaled glyphs and adjusted metrics.

## Preview

<table border="0"><tr><td><img style="display:block;" src=".preview/GCTTriplexRoman.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTTriplexItalic.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTSimplexScript.png" width="100%" height="100%"/></td></tr><tr><td><img style="display:block;" src=".preview/GCTSimplexRoman.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTGothicItalian.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTGothicGerman.png" width="100%" height="100%"/></td></tr><tr><td><img style="display:block;" src=".preview/GCTGothicEnglish.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTDuplexRoman.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTComplexScript.png" width="100%" height="100%"/></td></tr><tr><td><img style="display:block;" src=".preview/GCTComplexRoman.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTComplexItalic.png" width="100%" height="100%"/></td><td><img style="display:block;" src=".preview/GCTBlockRoman.png" width="100%" height="100%"/></td></tr></table>

* A [**PDF sample**](https://gitlab.com/dps8m/font-gct-multics/-/raw/master/.preview/samples.pdf?inline=true) document is also available.

## Download

* [You can download the built **TrueType fonts** here.](TrueType)

## Build

* The build process has been tested on **AIX**, **Solaris**,
  **OpenBSD**, **FreeBSD**, and **Linux**.

### Requirements

* A [C99 compiler](https://gcc.gnu.org/), [Python 3](https://www.python.org/),
  and [FontForge](https://fontforge.org/) (with Python support).
* A POSIX **shell** environment with the `awk`, `date`, `grep`, `make`, `rm`,
  and `sed` tools.
* If your Python interpreter is not `python3`, minor modifications will be
  needed (*see* `grep python3 *`).

### Optional

* To run the `lint` target (`make lint`) used to check the code for
  correctness, some additional tools are **required**:
  * [Black](https://github.com/psf/black),
    [ShellCheck](https://www.shellcheck.net/),
    [Cppcheck](https://www.cppcheck.com/),
    [Clang Analyzer](https://clang-analyzer.llvm.org/),
    [`reuse`](https://github.com/fsfe/reuse-tool), and
    [`codespell`](https://github.com/codespell-project/codespell).
* [GNU roff](https://www.gnu.org/software/groff/) and
  [Oracle Lint 12.6](https://www.oracle.com/application-development/developerstudio/)
  are **optional** and will be used if available.

## Notes

### FontForge issues

You need a recent version **FontForge** to successfully build the outline SFDs
from the Multics GCTs.

* It is *recommend* to use FontForge **based on sources from 2025-01-01 or
  later**.
  * FontForge from **Fedora 42** and **43** is known to work.
  * FontForge from **Ubuntu 25.04** and **25.10** is known to work (and
    **24.04** *seems* to work).
[]()

[]()
* Older versions of FontForge are ***not*** sufficient and are **known
  to fail**.
  * FontForge from **Ubuntu 18.04**, **20.04**, and **22.04** is known to fail.
  * ~FontForge currently distributed by **Homebrew** is ***very*** old and is
    known to fail.~  *UPDATE: As of 2025-10-10, Homebrew has updated Fontforge
    to 2025-10-09.  I have not tested it yet but it should be good enough.*
[]()

[]()
* If you see warning messages such as the following, you have a **deficient**
  version of FontForge:
  \
  `*** Glyph processing errors for gct_gothic_german_: 5 glyphs failed (z, l_brace, vert_bar, r_brace, tilde)`
[]()

[]()
*  You can check your FontForge version from the command-line with `fontforge -v`.

### `errnum` utility

* [`errnum`](errnum.c) is a generally useful, portable C99 program for
  enhanced shell script exit status logging.
* Signal status decoding is supported for `ash`, `bash`, `dash`, `ksh88`,
  `ksh93`, `mksh`, `oksh`, `yash`, and `zsh`.
* Decoding of [`sysexits.h`](https://man.openbsd.org/sysexits.3) status codes
  is supported.
* It can also be used interactively to decode `errno` values.
[]()

[]()
* For fun, ***`errnum` was written without using any `if` statements*** (*but
  does contain* **50** *ternary operations*).
* *If you aren't ternary maxxing in 2025, you're NGMI!*

### Availability

* The canonical home of this project is [https://gitlab.com/dps8m/font-gct-multics](https://gitlab.com/dps8m/font-gct-multics).

## External links

The following links are of potential interest to readers, and not previously
referenced in this document:

* [dps8m/misc-scripts/**svg2gct.py**](https://gitlab.com/dps8m/misc-scripts/-/blob/master/svg2gct.py) -
  Python tool to convert SVG 1.1 fonts to Multics GCT (Graphics Character Table) files.

* [dps8m/misc-scripts/**gctinfo.py**](https://gitlab.com/dps8m/misc-scripts/-/blob/master/gctinfo.py) -
  Python (or standard C99) analyzer and linter for Multics GCT (Graphics Character Table) files.

* [**The Hershey Fonts** with Frank Grießhammer](https://coopertype.org/events/the_hershey_fonts) -
  A video of Frank Grießhammer's talk about the history of the Hershey fonts
  (also take a look at his
  [**History of the Hershey Fonts**](https://productiontype.com/article/history-of-the-hershey-fonts)
  web page).

* [**Exploring Dr. Hershey's Typography**](https://circuitousroot.com/oldstuff/hershey/index.html) -
  by [David M. MacMillan](https://www.circuitousroot.com/) and
  [Rollande Krandall](https://www.lemur.com/).

* [**An Algorithm for Automatically Fitting Digitized Curves**](https://lhf.impa.br/cursos/tmg/Schneider-1990.pdf) -
  by Philip J. Schneider, University of Geneva, Geneva, Switzerland.

* [dps8m/**font-courier-multics**](https://gitlab.com/dps8m/font-courier-multics/#courier-multics-fonts) -
  An MIT-licensed TrueType font derived from **Bitstream Courier 10 Pitch BT**,
  and used as the default font for
  [`prt2pdf`](https://gitlab.com/dps8m/dps8m/-/tree/master/src/prt2pdf)
  output.

* [dps8m/**font-rosy-multics**](https://gitlab.com/dps8m/font-rosy-multics/#rosy-multics-fonts) -
  An OFL-licensed TrueType font designed to simulate the Honeywell/Compuprint
  ROSY dot-matrix printer, also included in `prt2pdf` (enabled with the `-d`
  option).

* [dps8m/**font-bigletter-multics**](https://gitlab.com/dps8m/font-bigletter-multics/#bigletter-and-littleletter-multics-fonts) -
  A set of TrueType and FIGlet fonts derived from the Multics I/O daemon
  `bigletter_` procedure.
