fonts

Experimental package to manipulate open source font data.

html5 canvas
pdf
svg
open source
fonts
moon add gmlewis/fonts@0.19.13
Download zip
Author
Version
0.19.13
License
Apache-2.0
Last updated
8 days ago
Downloads
10K

Dependencies

README

#gmlewis/moonbit-fonts

check

This is an experimental package to manipulate open source fonts with MoonBit.

All fonts are open source and their licenses can all be found in their corresponding repos which are organized by the first letter of the name of the font:

mbt-fonts-* packages contain hardcoded font data using Map[String, @fonts.Glyph].

To add a font dependency, use the MoonBit package name (which follows the gmlewis/fonts-<letter>/<fontname> pattern). For example, to add the baloo font, run:
moon add gmlewis/fonts-b/baloo

The corresponding GitHub repositories are:

  • font: https://github.com/gmlewis/mbt-fonts-a/tree/master/aaarghnormal
  • license: https://github.com/gmlewis/go-fonts-a/tree/master/fonts/aaarghnormal
  • ...
  • font: https://github.com/gmlewis/mbt-fonts-b/tree/master/baloo
  • license: https://github.com/gmlewis/go-fonts-b/tree/master/fonts/baloo
  • ...
  • font: https://github.com/gmlewis/mbt-fonts-z/tree/master/znikomitno24
  • license: https://github.com/gmlewis/go-fonts-z/tree/master/fonts/znikomitno24

#Dynamic Font Loading

For projects that need to load fonts on-demand by name (e.g., from a configuration file) without compiling every font into the binary, you can use the Font::load_font API.

This requires the all-fonts/ directory to be available on your filesystem. This directory contains compressed JSON files for each font (e.g., baloo.json.gz).

#Usage

// Load a font by name
let font = @fonts.Font::load_font("baloo")

#Search Logic

load_font searches for the font in the following order:
  1. Relative to the current working directory: all-fonts/{font_name}.json.gz.
  2. Relative to the MOONBIT_FONTS_DIR environment variable: ${MOONBIT_FONTS_DIR}/all-fonts/{font_name}.json.gz.

#Setting up MOONBIT_FONTS_DIR

If you have a central location for your font data, set the environment variable:

export MOONBIT_FONTS_DIR=/path/to/your/gmlewis/fonts/repo

The directory should contain an all-fonts/ subdirectory with the .json.gz files.

#Quick Start

See the examples/quick-start directory for a valid example of how to use this package.

#Functional Per-Grapheme Text Styling

The @draw.text_with API allows custom styling per grapheme without relying on internal Path / CompoundPath structure.

#Callback Model

  • @draw.text_with_style_fn((ctx) => action) creates a callback.
  • ctx is GlyphStyleContext with:
    • grapheme_index
    • grapheme
    • line_index
    • grapheme_index_in_line
  • Return a GlyphStyleAction via helpers:
    • @draw.glyph_style_use_default()
    • @draw.glyph_style_override(stroke=?, fill=?)
    • @draw.glyph_style_skip()

#Example (marker-based styling)

This example skips *, colors the next visible glyph red, and keeps all others black:

let y_label = @draw.text_with(
font,
text,
align=Right,
style_fn=@draw.text_with_style_fn(fn(ctx) {
if ctx.grapheme == "*" {
@draw.glyph_style_skip()
} else if ctx.grapheme_index == 1 {
@draw.glyph_style_override(fill=red_fill)
} else {
@draw.glyph_style_override(fill=black_fill)
}
}),
)
.transform(scale=vec2(0.2, 0.2), position=y_tick_pos + vec2(-0.02, 0.05))

graph.push(y_label)

#Tuple Compatibility API

If you prefer the original tuple callback style, use @draw.text_with_tuple with @draw.text_with_tuple_style_fn((index, grapheme) -> (stroke?, fill?)).

#Examples

#checkerboard

checkerboard is a simple example to create a draw.Graphic using the @draw API.

#canvas-checkerboard

canvas-checkerboard renders the checkerboard Graphic to an HTML5 canvas. Type ./run-canvas-checkerboard.sh in a terminal then open your browser to http://localhost:8080/examples/canvas-checkerboard to view it.

#svg-checkerboard

svg-checkerboard "renders" the checkerboard Graphic to an SVG file. Type moon run examples/svg-checkerboard > examples/svg-checkerboard/checkerboard.svg in a terminal then open this file in your browser to view it. For example, google-chrome examples/svg-checkerboard/checkerboard.svg.

#Scripts

A few utility scripts are available in the scripts/ directory to help with common tasks.

#render-to-svg.py

Quickly render text to an SVG file using any available font. This script handles creating a temporary MoonBit project, importing the necessary font packages, and running the code to generate the SVG. It supports multi-line text and custom alignment.

Examples:

# Render "Hello World" using the default font (aaarghnormal) ./scripts/render-to-svg.py "Hello World" -o hello.svg # Render multi-line text with horizontal centering ./scripts/render-to-svg.py "Line 1\nLine 2 Centered" -a center -o centered.svg # Render text using a specific font (e.g. baloo) ./scripts/render-to-svg.py "Custom Font" -f baloo -o custom.svg # List all available font families ./scripts/render-to-svg.py --list-fonts # Use Markdown-style markers for bold/italic if the font family supports them ./scripts/render-to-svg.py "**Bold Text** and *Italic Text*" -f aileron -o styled.svg

#render-to-json.py

Similar to render-to-svg.py, but outputs a serialized JSON representation of the draw.Graphic object instead of an SVG. This is useful for passing graphic data to other tools like ray tracers. It defaults to y-up coordinates.

Examples:

# Render text to JSON (defaults to y-up) ./scripts/render-to-json.py "Data Graphic" -f abeezee -o graphic.json # Render multi-line text with right-alignment and y-down coordinates ./scripts/render-to-json.py "Line 1\nLine 2" -a right --y-down -o down.json

#sample-all-fonts.py

Generates one or more SVG files showing a sample of text rendered in every available font. This is useful for visual font selection.

Examples:

# Generate sample SVGs for all fonts (in batches of 221) ./scripts/sample-all-fonts.py -o all-fonts.svg "The quick brown fox" # Use a specific label font ./scripts/sample-all-fonts.py -o samples.svg --label-font aileron_bold

#Status

The code has been updated to support compiler:

$ moon version --all moon 0.1.20260803 (c19f78e 2026-08-03) ~/.moon/bin/moon moonc v0.10.6+80dc50f24 (2026-08-04) ~/.moon/bin/moonc moonrun 0.1.20260803 (c19f78e 2026-08-03) ~/.moon/bin/moonrun


Enjoy!


#License

Please note that all fonts have their own licenses which are included in their respective original directories (see above).

Copyright 2019-2024 Glenn M. Lewis. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


#
FontError

pub(all) suberror FontError {
FontError(String)
} derive(Eq)

FontError represents an error that occurred during font processing.
impl Show for FontError

#
AbsoluteCmd

pub(all) enum AbsoluteCmd {
M
L
C
Q
Z
} derive(Eq,
Debug
)

AbsoluteCmd represents a supported absolute SVG command.
impl Show for AbsoluteCmd

#
Cmd

Cmd represents an SVG command along with its parameters.
impl Show for Cmd

#
Font

pub(all) struct Font {
id : String
horiz_adv_x : Double
units_per_em : Double
ascent : Double
descent : Double
glyphs : Map[String, Glyph]
} derive(Eq, ToJson,
Debug
,
FromJson
)

Font represents an entire font.
impl Show for Font

#
Font::gen_path

fn Font::gen_path(self : Font, text : String, alignment? :
Alignment
, y_up? : Bool) -> Glyph raise FontError

gen_path "renders" the provided text string into an SVG path using the provided font information. It performs the necessary path translations in order to combine the individual glyphs into a "super glyph" that can be rendered as a whole.

Note that gen_path operates on the string as a left-justified whole and aligns the entire path relative to alignment accordingly.

If you want multiple lines centered horizontally, use gen_paths instead.

Note that the SVG standard states that positive-Y is "down" with origin coordinates in the upper-left, however, all SVG fonts appear to have their origin in the lower left with positive-Y moving "up". This package attempts to convert the SVG paths such that the font is equally readable in both the y_up=false SVG canvas environment or in a 3D y_up=true rendering environment.

#
Font::gen_paths

fn Font::gen_paths(self : Font, lines : Array[String], alignment? :
Alignment
, y_up? : Bool) -> Array[Glyph] raise FontError

gen_paths handles multiple strings independently so that they can be aligned and distributed more flexibly.

#
GerberLP

pub(all) enum GerberLP {
Dark
Clear
} derive(Eq,
Debug
)

GerberLP represents whether a subpath is Dark or Clear.
impl Show for GerberLP

#
Glyph

pub(all) struct Glyph {
char : String
horiz_adv_x : Double
gerber_lp : String
d : String
xmin : Double
ymin : Double
xmax : Double
ymax : Double
} derive(Eq, ToJson,
Debug
,
FromJson
)

Glyph represents a single glyph within each Font and also represents multiple glyphs combined together (for example after using Font.gen_path and switching back and forth between Glyphs which are optimized for storage, and Paths which are optimized for processing).
impl Show for Glyph

#
ParamPair

pub(all) struct ParamPair {
x : Double
y : Double
} derive(Eq,
Debug
)

ParamPair represents an X,Y absolute coordinate pair of parameters.
impl Show for ParamPair

#
ParamPair::to_string

fn ParamPair::to_string(self : ParamPair) -> String

to_string returns a string representation of a ParamPair.

#
Params

Params represents the parameters to an SVG command.
impl Show for Params

#
Params::length

fn Params::length(self : Params) -> Int

#
PathCmd

pub(all) struct PathCmd {
cmd : AbsoluteCmd
gerber_lp : GerberLP
params : Array[ParamPair]
} derive(Eq,
Debug
)

PathCmd represents an individual absolute SVG command.
impl Show for PathCmd

#
PathCmd::bbox

bbox returns the minimum bounding box of a PathCmd.

#
PathCmd::clone

fn PathCmd::clone(self : PathCmd) -> PathCmd

clone makes a deep copy of a PathCmd.

#
PathCmd::from_svg_cmd

fn PathCmd::from_svg_cmd(svg_cmd : Cmd, gerber_lp : GerberLP) -> PathCmd raise FontError

from_svg_cmd creates a PathCmd from a raw SVG command and a GerberLP.

#
PathCmd::to_svg_cmd

fn PathCmd::to_svg_cmd(self : PathCmd) -> (Cmd, String)

to_svg_cmd converts a PathCmd back to a raw SVG command and its GerberLP code.

#
PathCmdFn

pub(all) struct PathCmdFn((Int, PathCmd) -> PathCmd)

PathCmdFn represents a function that processes or transforms individual commands. The first argument is the index within the command.

#
SVGPath

pub(all) struct SVGPath {
char : String
cmds : Array[PathCmd]
xmin : Double
ymin : Double
xmax : Double
ymax : Double
} derive(Eq)

An SVGPath is identical to a Glyph but has been optimized internally for further manipulation, whereas a Glyph is optimized for compact storage of font data within all the font packages. An SVGPath can be converted to a Glyph and vice versa.
impl Show for SVGPath

#
SVGPath::clone

fn SVGPath::clone(self : SVGPath) -> SVGPath

clone makes a deep copy of an SVGPath.

#
SVGPath::from_glyph

fn SVGPath::from_glyph(g : Glyph, path_cmd_fn? : PathCmdFn) -> SVGPath raise FontError

from_glyph returns an SVGPath from a Glyph, optionally processing every Cmd with a processing function. Note that apart from path_cmd_fn, from_glyph makes no attempt to process the individual glyphs and simply transforms the representation.

#
SVGPath::to_glyph

fn SVGPath::to_glyph(self : SVGPath, path_cmd_fn? : PathCmdFn) -> Glyph

to_glyph returns a "super" Glyph from an SVGPath, optionally processing every PathCmd with a processing function. Note that apart from path_cmd_fn, to_glyph makes no attempt to process the individual glyphs and simply transforms the representation.

#
glyph_bbox

fn glyph_bbox(d : String) ->
BoundingBox
raise FontError

glyph_bbox returns the conservative bounding box of a Glyph path. It only supports the absolute SVG commands: M, L, C, Q, Z.

Note that this function does NOT fully analyze the Cubic or Quadratic Bézier curves to determine their exact bounding boxes, but instead takes a naive and conservative approach by encapsulating the bounds of all control points in addition to curve anchor points which may result in returning a much larger bounding box than is actually needed to fully contain the glyph.

#
translate_path

fn translate_path(d : String, x : Double, y : Double, invert_y? : Bool) -> String raise FontError

translate_path translates (moves) an SVG path by the provided offsets. If invert_y is true, all y values are scaled by -1.