Fontsloth is an elisp OTF/TTF font loader and renderer.
It is inspired by fontdue. While fontdue is “the fastest font renderer in the world, written in pure Rust”, fontsloth is “the slowest font renderer in the world, written in pure Elisp”.
Despite the name, being slow is neither a goal nor a requirement.
It is currently pre version 1.0 and the API is not yet stable.
Mostly it is modeled after the aforementioned fontdue and the outliner in Rust ttf-parser. The only novel bit is using Emacs bindat.el to unpack OTF/TTF.
Current semantic version: 0.17.0
Note: at present this requires Emacs 28
To make it work on Emacs 27.1 would involved backporting the latest bindat.el which itself involves backporting the latest subr-x.el. If you would like this to happen, please let me know.
- Synopsis of work in progress
- Project organization
- Usage caveats
- Installation
- Usage
- Attribution
- Performance
- Future plans
- Fonts tested so far (working for simple and composite glyphs)
- Load simple and composite glyphs and character mappings in TTF and OTF fonts
These must contain loca/glyf or CFF1 tables for glyphs and cmap format4, format0, format6, and/or format12 for mappings.
- Outline and rasterize glyphs that use quadratic or cubic bezier curves
- Layouts for lines of horizontal text
- kerning (as of 0.15.1)
- OTF font outlining and rasterization (as of 0.16.0) It can now outline OpenType fonts with CFF version 1 tables.
- Web Open Font Format (WOFF) (as of 0.17.0) Note: since WOFF is a meta format, the underlying format must also be supported.
- OTF CFF version 2
- Font variation indices
- Vertical metrics tables (and vertical text layout)
- All possible cmap tables for char-code to glyph-id mappings
- All of GPOS and GSUB for every script/lang
- Font collections
- Allow custom per font family sets of additional char-code -> glyph mappings that derive new glyph outlines not in a font from those already present. E.g. subtract, add, or modify contours to produce glyphs that are variations of one or more existing glyphs. This could be fun for icon fonts or possibly to fill in missing standard unicode chars for fonts with similar enough existing glyphs.
Fontsloth consists of four primary components:
- OTF/TTF parser
Entry point: file:fontsloth-otf.el
This uses elisp#Byte Packing (i.e. bindat.el) to unpack TTF and OTF fonts.
Packing is not currently supported but planned for after version 1.0
- Glyph outliner
Entry points:
- file:fontsloth-otf.el: generics
- fontsloth-otf-glyf.el: base implementation for TTF outlines
- fontsloth-otf-cff.el: base implementation for OTF outlines
- file:fontsloth-geometry.el: dispatch type and method implementations
This is modeled after Rust ttf-parser and should be extensible for multiple backends. The current backend is modeled after fontdue.
- Text layout
Entry point: file:fontsloth-layout.el
This is modeled after fontdue’s layout which is in an immature state (as is this).
The gist of it is that given text styles, fonts, and a coordinate system, it produces a series of indexable glyph positions to aid in compositing glyphs for display.
- Raster
Entry point: file:fontsloth-raster.el
This is currently just a port of fontdue’s raster. It is planned to support multiple raster implementations.
The fontdue author includes the following:
Notice to anyone that wants to repurpose the raster for your library: Please don’t reuse this raster. Fontdue’s raster is very unsafe, with nuanced invariants that need to be accounted for. Fontdue sanitizes the input that the raster will consume to ensure it is safe. Please be aware of this.
This is part of the reason why it is planned to support multiple raster implementations.
So far, I have tested the elisp implementation to faithfully reproduce fontdue’s raster on a byte by byte level for multiple fonts and pixel sizes.
Currently fontsloth uses pcache.el to provide a persistent cache for loaded
fonts, which is the default for fontsloth-load-font
.
Invalidation at present must be handled manually. Expect cache load times in the seconds if you load more than 10 or so fonts at a time.
;; it will take longer, but won’t end up in cache
;; this is useful if you just want to try it out and see if it works
(fontsloth-load-font my/font :cache 'bypass)
;; this reloads the font and then stores the result in cache
(fontsloth-load-font my/font :cache 'reload)
;; this removes a single font entry from cache
(pcache-invalidate fontsloth-cache my/font)
(pcache-clear fontsloth-cache)
Install from MELPA using the builtin package manager assuming MELPA is in your
package-archives
list.
Alternatively install using quelpa (see below) or straight.el.
Quelpa allows an installation directly from this repo that is then managed the usual way via package.el. Quelpa can be installed from MELPA or bootstrapped directly from source if desired.
;;; after installing quelpa
;; note this uses a MELPA recipe, so the usual MELPA options also apply
(quelpa '(fontsloth :fetcher github :repo "jollm/fontsloth"))
First install quelpa-use-package (either with quelpa or from MELPA).
;; if quelpa use-package is installed, this should install fontsloth
(use-package fontsloth
:quelpa ((fontsloth :fetcher github :repo "jollm/fontsloth")))
;; if you want to auto-check for upgrades
(use-package fontsloth
:quelpa ((fontsloth :fetcher github :repo "jollm/fontsloth") :upgrade t))
After installation:
M-x: quelpa-upgrade
See Usage caveats for how to load fonts without caching them in pcache.
(require 'fontsloth)
;; Rasterize the fontawesome wifi icon and put it in a preview buffer
;; Saving the buffer should turn on image-mode and display it
(defvar my/current-font
(fontsloth-load-font "/usr/share/fonts/TTF/fontawesome.ttf"))
(pcase-let* ((font my/current-font)
(glyph-id (fontsloth-font-glyph-id font ?))
(px 32.0)
((cl-struct fontsloth-metrics+pixmap metrics pixmap)
(benchmark-progn (fontsloth-font-rasterize font glyph-id px)))
(pgm (fontsloth-raster-npbm pixmap
(fontsloth-metrics-width metrics)
(fontsloth-metrics-height metrics)
'pgm))
(buffer (get-buffer-create "fontsloth-raster-preview")))
(with-current-buffer buffer
(set-buffer-multibyte nil)
(insert pgm)))
;; note that fontsloth-raster-npbm is unnecessary if you just want a pixmap
(require 'fontsloth-layout)
;; this will return a sequence of glyph position structs
(let ((font (fontsloth-load-font "/usr/share/fonts/TTF/AppleGaramond.ttf"))
(x-start 0)
(layout (fontsloth-layout-create)))
(fontsloth-layout-reset layout (fontsloth-layout-settings-create
:x x-start))
(fontsloth-layout-append layout `(,font) (fontsloth-layout-text-style-create
:text "Hello world!"
:px 35.0 :font-index 0))
(fontsloth-layout-finalize layout))
Fontsloth at this stage wouldn’t at all be possible without fontdue and ttf-parser. In addition I began learning about TTF from TTF file parsing.
Update 11/18/21: Some initial profiling indicates unsurprisingly that the slowest aspects of this are loading and particularly outlining all glyphs and that time spent in garbage collection is ~50%. It may be possible to mitigate this by adding an option for lazy outlining. As for loading, I plan to try out having bindat work with streams.
How slow is it really? The short answer is I don’t know yet as benchmarking is still a TODO.
Anecdotally, on Thinkpad t440 with Emacs 28 native:
- Glyph rasters for pixel sizes around 30.0 take on the order of a few milliseconds
- To load a font and outline all of its glyphs at present takes longer (e.g. ~320 milliseconds on the same machine for AppleGaramond TTF), hence the font cache
- Layout for short text strings takes sub 1 millisecond with the same setup
I would like for this to be robust enough to handle everything that Emacs currently delegates to FreeType/Harfbuzz/Cairo, not necessarily for actual inclusion in Emacs proper, but as a good acid test if it could accomplish that without any loss of functionality.
Assuming feature parity with FreeType, I would like to port this to Guile 3 so that it could be an option for handling fonts in Guile Emacs. The Guile version would be a rewrite with requirements for being idiomatic and thread-safe.
In order of most to least tested:
- free version of FontAwesome 5
- IBMPlex series, the TTF and OTF versions
- Bookerly TTF
- all-the-icons TTF
- AppleGaramond TTF
- Roboto series, the TTF versions
- DejaVu series, the TTF versions
- Charter OTF (for some reason line height metrics are off for this specific font)
- SF-Pro (apple’s flagship afaict) OTF (this is an enormous font that takes about 20 seconds to load the first time but it does work)
- Hermit OTF
- Fantasque series OTF
- Cascadia series OTF
This documentation is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Copyright (C) 2021 Jo Gay <[email protected]>