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-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-errorortype-errorwith 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
ftypewith 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.