9 Development
dxtr edited this page 2026-10-18 16:23:24 +02:00
This file contains ambiguous Unicode characters

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-sdf.asd                    systems cl-sdf and cl-sdf/test
stiftfile                     test dependency (parachute), pinned
src/
  package.lisp                the package and its exports
  conditions.lisp             domain types, conditions, contour validation
  geometry.lisp               bounds, flattening
  grid.lisp                   uniform grid over boxes
  edge-table.lisp             packed lines and the per-sample distance kernels
  distance.lisp               SHAPE, point queries, encoding
  boundary.lisp               resolving the filled boundary
  coloring.lisp               COLORED-EDGE, COLOR-EDGES
  error-correction.lisp       port of msdfgen's MSDF error correction
  field.lisp                  FIELD and its accessors
  generate.lisp               GENERATE-SDF, GENERATE-MSDF, GENERATE-MTSDF
tests/
  package.lisp                package cl-sdf/test, suite ALL-TESTS
  sdf-test.lisp               the public API, function by function
  algorithm-test.lisp         analytic oracles, fill rules, coloring, invariance
  field-test.lisp             whole fields against msdfgen, reconstruction
  index-test.lisp             grid-indexed queries against exhaustive ones
  api-test.lisp               error reports, accessors, metadata, declared types
  reference/
    msdfgen-samples.lisp      msdfgen's output, as Lisp data
    generate-msdfgen.cpp      the program that produced it
    README.org                provenance and how to regenerate

How It Works describes what each source file does.

Running the tests

Dependencies are pinned in the stiftfile with stift, and the tests use parachute.

stift test

The suite runs in a few seconds and passes 22690 assertions. asdf:test-system signals an error if any fails, so the command exits unsuccessfully.

To run one test from a REPL:

(asdf:load-system :cl-sdf/test)
(parachute:test 'cl-sdf/test::overlap-fields :report 'parachute:summary)

What the tests check

Matching msdfgen is one check among several. The suite also has its own oracles, so it would catch a bug that msdfgen shared:

  • Analytic distances. Lines, boxes and grids of boxes, whose distances are known in closed form.
  • Curves. Distances and fill compared with dense, independent sampling of the Bernstein polynomials, not with cl-sdf's flattening.
  • Fill rules and overlaps. Holes, separate components, overlapping and coincident contours, shared edges removed, both rules.
  • Coloring. Corner invariants: every sharp corner changes color, palettes, one- and two-corner curves, recoloring after shared edges disappear, the angle threshold's meaning across its whole range, and color-edges' output giving exactly the automatic field when passed in place of the contours.
  • Tie rules. Which line wins a channel when several are equally near, pinned so that the order of the scan cannot change a field.
  • Reconstruction. Fields are interpolated and their median taken as a shader does, and the drawn edge is compared with an oversampled true distance, at sharp corners in particular.
  • Invariance. Reversal, translation, scaling down to 0.0001 and up to 100, a translation by a million, and rotation, all giving the same samples.
  • Indexing. Every nearest-segment and inside query through the grid compared bit for bit with an exhaustive search, on shapes chosen to stress it, such as a needle whose tip turns almost all the way back.
  • Flattening. The default tolerance is relative to texels: a circle in units a thousand times smaller or larger gives the same field, in texels, within 0.03 texel of the true distance.
  • Errors. Every error is an sdf-error or type-error with the message and readers documented in Errors. Coordinates and lengths at the edge of the ±1e36 range work, and beyond it are reported rather than overflowing; numbers in messages read back exactly, whatever their size.
  • Declarations. Every argument's declared type refuses what it must: a constant number or keyword outside it when the call is compiled, and anything outside it when the call runs, with SBCL's type-error.
  • Printing. Fields and shapes print briefly, without their samples or geometry.
  • Degenerate input. Empty outlines, zero-area shapes, point segments.

The msdfgen fixtures

tests/reference/msdfgen-samples.lisp holds output of msdfgen at revision 1c106ed8117893bf943e577f62eb0665fb271e46, made by generate-msdfgen.cpp. It does not use cl-sdf at all.

Variable What
*msdfgen-reference* 256 points of raw RGBA distances from msdfgen's distance finder, for a square, a triangle, an acute triangle and a concave L with fixed edge colors. Compared within 2 × 10⁻⁶.
*msdfgen-field-reference* Whole 16 by 16 raw MTSDF fields without error correction, for a quadratic teardrop and lens, a line-and-cubic D, and a cubic circle with a triangular hole, with msdfgen's own contours and coloring. Compared within the flattening error.
*msdfgen-corrected-reference* Whole MSDFs with msdfgen's default error correction, decoded to raw distances, for four stars, two twisted. Compared within 10⁻⁴ texel.

msdfgen's signs are negated to match cl-sdf's positive inside.

Running the tests needs neither C++ nor msdfgen. To regenerate the fixtures, after adding cases to generate-msdfgen.cpp or moving to another msdfgen revision, check out the revision as /tmp/msdfgen-reference, and from cl-sdf's directory:

cmake -S /tmp/msdfgen-reference -B /tmp/msdfgen-reference-build \
  -DMSDFGEN_CORE_ONLY=ON -DMSDFGEN_BUILD_STANDALONE=OFF \
  -DMSDFGEN_USE_VCPKG=OFF -DMSDFGEN_USE_SKIA=OFF
cmake --build /tmp/msdfgen-reference-build -j4
c++ -std=c++11 -DMSDFGEN_PUBLIC= -DMSDFGEN_USE_CPP11 \
  -I/tmp/msdfgen-reference tests/reference/generate-msdfgen.cpp \
  /tmp/msdfgen-reference-build/libmsdfgen-core.a -o /tmp/generate-msdfgen
/tmp/generate-msdfgen > tests/reference/msdfgen-samples.lisp

The fixtures do not depend on cl-sdf, so regenerating them cannot make a failing test pass. The expected differences from msdfgen are why some comparisons allow for flattening error.

CI

.forgejo/workflows/ci.yml runs on every push to main and every pull request, in a debian:forky container, with fakehacker/actions: checkout, then stift-ci, which installs SBCL 2.6.9 and stift, and runs stift test.

Conventions

cl-sdf follows the conventions of the Pyssel workspace:

  • Every exported function has a declaimed ftype with each argument's full domain type, which SBCL checks on entry. What a type cannot say, such as contours, lists of any length, is checked by contour validation, whose reports say what to change.
  • Every function, macro, type and constant has a docstring, internal ones included.
  • The package has no global nickname; programs and the tests give it a local one.
  • Every struct slot has a :type. Fields, shapes and colored edges are read-only structures; their docstrings document their slots and their thread rules.
  • Every error the library signals is an sdf-error, and says what to change.
  • Domain types are deftypes: coordinate, segment, rectangle, fill-rule, y-axis, edge-color.
  • No dependencies outside the test system.
  • A change without tests is not finished; a failing test is fixed in the code, never weakened.