Table of Contents
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Development
Layout
cl-ft2.asd systems cl-ft2 and cl-ft2/test
CLAUDE.md notes and code standards for working on cl-ft2
native/
build-windows.sh builds windows-x64/freetype.dll from FreeType's source
windows-x64/
freetype.dll the FreeType that cl-ft2 loads on Windows
FTL.TXT FREETYPE-LICENSE.TXT
src/
package.lisp both package definitions and export lists
conditions.lisp FREETYPE-ERROR, the error table and explanations
ffi-library.lisp loading libfreetype, or the bundled DLL on Windows
ffi-types.lisp scalar types, enums, bitfields
ffi-structs.lisp record layouts, written by hand
ffi-functions.lisp defcfun entry points
units.lisp 26.6 / 16.16 conversion, keyword types, load flags
library.lisp LIBRARY
face.lisp FACE, face properties, the face-dependent protocol
size.lisp sizes, strikes, size metrics, transforms
charmap.lisp charmaps, char-index, iteration, glyph names
glyph.lisp loading, metrics, advances, kerning, RENDER-GLYPH
bitmap.lisp BITMAP, GLYPH-BITMAP, CHAR-BITMAP, GLYPH-INDEX-BITMAP
outline.lisp GLYPH-OUTLINE, GLYPH-CONTROL-BOX
glyph-object.lisp GLYPH objects
stroker.lisp STROKER, STROKE-GLYPH
outline-object.lisp OUTLINEs of your own, STROKE-OUTLINE, RENDER-OUTLINE
variation.lisp variable fonts, and FreeType's coordinate quirks
tests/
package.lisp test package, test font lookup
library-test.lisp face-test.lisp glyph-test.lisp
glyph-object-test.lisp outline-object-test.lisp variation-test.lisp
fonts/
cl-ft2-test-variable.ttf tiny variable font with exact geometry
make-test-variable-font.py script that builds it (fontTools)
Running the tests
Dependencies are pinned in the stiftfile with stift, and the tests use parachute.
stift test
;; Summary:
Passed: 551
Failed: 0
Skipped: 0
asdf:test-system signals an error if any test fails, so the command exits unsuccessfully.
The tests need:
libfreetype(Debian/Ubuntu:libfreetype6). On Windows, the bundled DLL is used.- DejaVu Sans (Debian/Ubuntu:
fonts-dejavu-core), looked up at/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf. To use a copy elsewhere, setCL_FT2_TEST_FONTto its path; on Windows, where there is no such default, that is how the tests find it. Some assertions check DejaVu Sans's exact metrics, so another font will fail them.
The test variable font
tests/fonts/cl-ft2-test-variable.ttf is committed to the repository. It is deliberately tiny and exactly specified, so that the variation tests can assert precise values:
- axes
wght100–400–900 andwdth75–100–125 - named instances Thin (100, 100), Regular (400, 100) and Black (900, 100)
- one real glyph,
I(U+0049): a rectangle whose geometry is linear in the normalized axis valuesAxis (normalized) Effect wght−1 … 0 … +1right edge x 200 … 300 … 500, advance 300 … 400 … 600 wdth0 … +1top edge y 700 … 800
To change it, edit tests/fonts/make-test-variable-font.py and rerun it. It needs fontTools (pip install fonttools):
python3 tests/fonts/make-test-variable-font.py
The Windows DLL
native/windows-x64/freetype.dll is FreeType 2.14.3, cross-compiled with MinGW-w64 by native/build-windows.sh from FreeType's release tarball, whose SHA-256 the script pins. The build is reproducible: paths and timestamps are kept out of the binary, so the same source and toolchain give the same bytes.
# Debian: apt install mingw-w64 cmake curl xz-utils
native/build-windows.sh # rebuild native/windows-x64/freetype.dll
native/build-windows.sh --check # fail unless the committed DLL is what the source builds to
To move to a new FreeType release, change FREETYPE_VERSION and FREETYPE_SHA256 in the script, run it, and commit the DLL and the licence files it copies. A MinGW-w64 update can change the bytes too; then the CI check fails, and the fix is the same: rebuild and commit.
CI
.forgejo/workflows/ci.yml runs on every push to main and on pull requests. Both jobs run in a debian:forky container:
- test uses the shared actions
fakehacker/actions/checkout@v1.0.0andfakehacker/actions/stift-ci@v1, the latter's major tag, which follows its compatible releases, withsbcl-version: "2.6.9",lisps: sbclandpackages: libfreetype6 fonts-dejavu-core; it runsstift teston SBCL. No C toolchain or FreeType headers are installed, because none are needed. - windows-dll installs MinGW-w64 and CMake and runs
native/build-windows.sh --check, so the committed DLL always matches its source.
Conventions
- Two layers.
cl-ft2/ffiis a pure transcription: no error checks and no Lisp conveniences. All policy lives incl-ft2. - Lisp values at the boundary. Strings, keywords, integers and exact rationals go out. Bitmaps are copied into Lisp arrays. No foreign types leak out of
cl-ft2. - One flat condition. Every failure is a
freetype-error, with context in its slots. Messages are written in terms of the cl-ft2 call, with a hint on what to do. Bad keywords and types aretype-errors raised before any foreign call. - Explicit lifetimes with idempotent
destroy-…functions, named as in cl-harfbuzz. A library destroys its dependents. Destroyed objects are detected and reported, never dereferenced. - Settable state is a
setfplace, with noset-functions:face-design-coordinates,face-normalized-coordinates,face-axis-coordinate,face-named-instanceandface-charmap. So areface-size,face-strike,face-transform-matrixandface-transform-delta, which the face remembers because FreeType cannot report them back exactly. Two-dimensional places such asface-sizeread as two values and are set from a number or an(x y)list. glyph-…takes a glyph source (a face's slot or aglyph) and is generic;glyph-index-…takes a face and a glyph index.- Docstrings: struct and class fields are documented in the struct or class docstring, not per accessor. Every other function, macro, type and package has one, internal ones included.
- Types: every exported function declaims its
ftypedirectly above itsdefun, every struct and class slot has a type, and the keywords FreeType's enumerations take are types made from the raw layer's enumerations. Tests that pass a deliberately wrong argument pass it throughopaque, since SBCL refuses a constant of the wrong type at compile time.api-audit.lispat the workspace's root lists missing docstrings and ftypes. - Names: internal names are plain, with no
%prefix; a raw reader beside a checked public one says what it returns, asface-handle-or-nilbesideface-handle. - Changes to cl-ft2 that cl-harfbuzz depends on: once they are pushed, move cl-harfbuzz's pin with
stift work syncat the workspace's root, orstift update https://fakehacker.cc/dxtr/cl-ft2.gitin the cl-harfbuzz checkout.