This is a suite of scripts used to convert JSON-based descriptions of glyphs for the StepTech typeface into SVG files and then into a FontForge project.
Currently, the steps involved are these:
- The information in the master JSON glyphs file,
elements-all.json
, is split up into individual JSON glyph files, one per glyph.- Glyphs whose names start with
_
are treated as fragments; they are the same as other glyphs except that they have no corresponding codepoint and are not rendered directly into SVG. Rather, they are included using a compose operation as described below.
- Glyphs whose names start with
- Each JSON glyph description consists of:
- A list of zero or more directed line segments (
lines
key):- Each segment can have a
guide_color
property. This does not impact the final product but can be helpful when editing the description. - Each segment can have a
draw
property which is set totrue
(default) orfalse
. If set tofalse
, the segment is never actually drawn, but the bounding box for the glyph is calculated as if it were.- This is used for space-only glyphs (as done with space (U+0020)) or to artificially extend the width of a narrow glyph (as done with
1
to make its width match that of the other digits).
- This is used for space-only glyphs (as done with space (U+0020)) or to artificially extend the width of a narrow glyph (as done with
- Each segment can have a
spread
property which is set totrue
(default) orfalse
. If set tofalse
, the bounding box for the glyph is calculated as if the segment were omitted.- This is used to omit diacritics from the width calculation (as done with
ï
since the dieresis is wider than the letter itself).
- This is used to omit diacritics from the width calculation (as done with
- Each segment has two endpoints,
from
andto
.- Each endpoint has an
x
and ay
, which are defined as a number of pixels right and down, respectively, from the base point. - Each endpoint has a
cap
which describes the end cap for that endpoint.none
indicates no cap; the stroke ends at the endpoint, cut perpendicular to the segment.shear
indicates no cap; the stroke is cut diagonally to that the stroke edge counterclockwise from the endpoint isS
farther out than the endpoint, while the stroke edge clockwise from the endpoint isS
behind the endpoint, whereS
is the value of a separateshear
property multiplied by the stroke width.- If a stroke is viewed as vertical, and it is observed that
+y
is the downward direction, theshear
value is the slope of the cut. - If the
shear
value is 0, theshear
cap is equivalent to thenone
cap.
- If a stroke is viewed as vertical, and it is observed that
s
indicates a square cap; the stroke extends past the endpoint by half the stroke width before being cut perpendicular to the segment.- The effect is the same as extending this end of the segment by half the stroke width, then specifying a
cap
ofnone
.
- The effect is the same as extending this end of the segment by half the stroke width, then specifying a
c
indicates a circular arc with a diameter the same as the stroke width.sc
indicates a cap that is likes
its counterclockwise half and likec
on its clockwise half.cs
indicates a cap that is likec
its counterclockwise half and likes
on its clockwise half.in
(default forfrom
endpoint) andout
(default forto
endpoint) indicate placeholder caps (the tail and head ends of an arrow) to help determine the orientation of segments not yet manually edited.
- An endpoint may have a
dotdir
value ofx
,y
, or a number of degrees that will be used to determine the orientation of a segment having zero length.- If the segment length is nonzero, the orientation is determined by the relative positions of the endpoints and
dotdir
is ignored.
- If the segment length is nonzero, the orientation is determined by the relative positions of the endpoints and
- Each endpoint has an
- Each segment can have a
- A list of zero or more composition operations (
compose
key):- Each compose item specifies a
glyph
, which is the name of another glyph defined in the master.- Theoretically, the
glyph
object can be inlined here as well (this is how dereferencing is implemented), but I haven't tested doing this directly in the master.
- Theoretically, the
- Each compose item may specify a
name
by which it can be referenced later. - Each compose item may specify
op
, a list of zero or more "compose operations" such as translation and scaling.- The possible operations are not documented here. The curious may look at
elements-all.json
for examples of usage and theop_*
methods inComposeRunner.pm
for implementation.
- The possible operations are not documented here. The curious may look at
- Each compose item specifies a
- A map (JSON object) of zero or more
anchors
which are simply points that can be referenced by name by compose operations.- A glyph automatically defines
top
,topright
,right
,bottomright
,bottom
,bottomleft
,left
,topleft
, andcenter
as the corresponding points of the bounding box, but these can be overridden by defining them explicitly. (This should be done with care.) - Diacritic glyphs usually define an anchor that specifies its placement on a letter by using a name of
d-
followed by the desired anchor name. For example, a dieresis specifiesd-top
at a certain distance below its bottom-center so that when itsd-top
is aligned with the letter'stop
the result looks correct. - If the compose item for the letter has the name
base
, this diacritic can use thealigndia
compose op as a shorthand for a longer translation op. See glyphsdieresis
/idieresis
andcedilla
/ccedilla
inelements-all.json
for examples.
- A glyph automatically defines
- A list of zero or more directed line segments (
- Each JSON glyph file is converted to a simple "loose shapes" SVG file using
element-to-loose-svg.pl
(which utilizesComposeRunner.pm
for compose op calculations).- The result of this step is used to determine what modifications should be made to the master JSON.
- This is where
guide_color
appears. - The "segments" from the description are rendered here as paths, not strokes, to accommodate the various end caps more easily.
- This script hardcodes some parameters of the font.
- The page height is 1000 pixels.
- The horizontal baseline 800 pixels from the top (200 pixels from the bottom).
- The vertical baseline is 0.
- The stroke width is 100 pixels.
- The generated loose shapes SVG contains metadata values produced in the drawing process, including the advance width for the glyph and the full list of drawn segments after compose ops are processed.
- This data is included as JSON stuffed into a funny XML comment such that it can be extracted with a perl one-liner. I've done it this way so that both metadata and SVG data can appear in the same output stream. (This makes it easier to work with from make, at least intuitively.) If the SVG weren't just an intermediate file, The Right Way to include the metadata would likely be to encode the information as part of the XML proper. I saw no benefit in doing that.
- Each loose shapes SVG has its contents unioned together via
inkscape
into an SVG containing a single "unioned shape" path.- This step takes the bulk of the processing time.
inkscape
is started and exited for each file processed. (If you know of a way this can be batched more efficiently, I'm listening.)
- This step takes the bulk of the processing time.
- Each unioned shapes SVG has its contents sanitized via
scour
into a "tight shape" SVG suitable for import into FontForge.- Without the
scour
step, FontForge sees a triple outline on the shapes instead of the desired single outline.
- Without the
- The tight shapes (with information from the JSON metadata embedded in the corresponding loose shape SVGs) are dumped into a FontForge project file (
build/project.sfd
).
- Python 2.7 (we should port this bit to Python 3)
- FontForge Python bindings (Ubuntu package
python-fontforge
)
- FontForge Python bindings (Ubuntu package
- Relatively recent Perl
- Perl JSON module (Ubuntu package
libjson-perl
) - Perl XML::LibXML module (Ubuntu package
libxml-libxml-perl
) - Perl Image::SVG::Path module (install using CPAN)
- Perl JSON module (Ubuntu package
- Inkscape (Ubuntu package
inkscape
) - scour (Ubuntu package
python-scour
)
The loose
target converts each JSON description into a "loose" SVG with one path per stroke. This target only requires the Perl JSON module.
The unioned
target unions all of the paths in each loose SVG into a single path, resulting in a "unioned" SVG. This target requires Inkscape to union the paths.
The post-unioned
target performs some arbitrary post-processing on the unioned SVG output of Inkscape. This includes filtering out extremely short segments accidentally created while unioning.
The tight
target sanitizes the still Inkscape-like post-unioned SVGs into a form readily consumed by FontForge. This target requires scour to remove parts of the Inkscape result too complex for FontForge to read correctly.
The ext-meta-all
target extracts JSON metadata produced by the loose SVG generation, adds other related data, and produces a table of glyph metadata used later by the fontforge-project
target. This target only requires Perl.
The fontforge-project
target uses the tight SVG outlines and additional metadata (from ext-meta-all
) to produce a new FontForge project file (build/project.sfd
). This target requires Python bindings for FontForge.
Currently, attempting to generate an OpenType font from the FontForge project results in Missing Points at Extrema
errors for many glyphs. It is possible to ignore these warnings and generate the font anyway, and in all my testing so far these issues have not caused any practical problems.
The glyphs for this font were given their initial rough outlines in gEDA PCB. While that program is for circuit board design and not for graphics, per se, it has over Inkscape the ability to preserve extremely precise position information without complicated transformations, and its file format is simple enough for extraction using Perl.
That's where pcb-to-elements.pl
comes in. This script pulls information out of a PCB file, with the following expectations:
- The PCB format hasn't changed substantially from the version the script was designed around.
- Each element is a glyph.
- Each element's name is the glyph's name. (See Adobe Glyph List for New Fonts (AGLFN) for appropriate values.)
- Each element consists only of
Pad
lines (others will be skipped). - Each element originates at the baselines.
- All pad endpoint coordinates are in
mil
or in the base unit (0.01 mil).- Figures in
mm
will be misread.
- Figures in
- 1 mil means 1 pixel.
Additionally, the trace width should be 100 mil (i.e. whatever element-to-loose-svg.pl
expects, in mil instead of pixels).
PCB can't by itself encode the none
, shear
, sc
, and cs
endcaps I needed, so that information is added manually to the master JSON. Currently, the square
flag on a pad will not be encoded as having a square cap, though perhaps it should.
My original PCB file is not included here because elements-all.json
contains all of the same information plus the end cap data, dotdirs, and a few other bits; it's effectively the source, and the PCB file is not.
The StepTech font description consists of the following file in this distribution:
elements-all.json
The StepTech font description is provided under the terms of the GNU General Public License (see notice below).
These terms do not include a font exception. This means, for example, that documents embedding all or part of a font based on this description or artwork featuring such a font may qualify as a derivative work (and therefore may be subject to the same terms).
This may change in the future as I familiarize myself with the limitations of other free software font licenses.
I reserve the right to make this software available to other parties under alternate terms without offering the same to the public at large.
StepTech font description
Copyright © 2014-2020 Peter S. May
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
The StepTech font generator consists of the following files in this distribution:
ComposeRunner.pm
element-to-loose-svg.pl
generate-font-project.py
json-files-to-array.sh
Makefile
pcb-to-elements-data.pl
reformat-master.pl
rules.mk
split-char-data.pl
unioned-to-post-unioned-svg.pl
The StepTech font generator is provided under the terms of the OSI-approved MIT License (see terms below).
Copyright © 2014-2020 Peter S. May
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.