oxitext-sdf 0.1.2

Signed-distance-field glyph atlas generation for OxiText
Documentation

oxitext-sdf — Signed-distance-field glyph atlas generation for OxiText

Crates.io License

oxitext-sdf turns glyph coverage bitmaps and outlines into GPU-ready signed-distance-field (SDF) texture atlases. It powers resolution-independent text rendering: a single small atlas can be sampled in a fragment shader to draw crisp glyphs at any magnification, with cheap effects such as outlines, glows, and soft shadows.

The crate is 100% Pure Rust — no C/C++ dependencies. It implements the Felzenszwalb–Huttenlocher Euclidean distance transform (EDT) for single-channel SDFs, a Chlumsky-style edge-coloring pipeline for multi-channel SDFs (MSDF/MTSDF) generated directly from outlines, an analytic per-pixel SDF generator, a pseudo-distance (PSDF) variant, three bin-packing algorithms (shelf, MaxRects, skyline) with single- and multi-page atlases, a self-describing binary atlas format, and a backend-agnostic GPU descriptor that uploads to wgpu/Vulkan/Metal/OpenGL without depending on any of them. Outline parsing uses ttf-parser; packing parallelizes with rayon.

Installation

[dependencies]
oxitext-sdf = "0.1.0"

Feature flags

# SIMD-accelerated EDT inner loop (pulls in the `wide` crate)
oxitext-sdf = { version = "0.1.0", features = ["simd"] }
Feature Default Effect
sdf no Marker feature for the public SDF API. All symbols are public by default; enabling it makes intent explicit.
simd no Enables wide-based SIMD lanes in the distance-transform hot loop. No API change.

Quick Start

Single-channel SDF from a coverage bitmap, packed into an atlas:

use oxitext_sdf::{compute_sdf, SdfAtlas, SdfTile};

// A solid 32×32 square (inside everywhere).
let coverage = vec![255u8; 32 * 32];
let sdf = compute_sdf(&coverage, 32, 32, 8.0, 0)?;
assert_eq!(sdf.len(), 32 * 32);

// Pack a single tile into an atlas.
let tile = SdfTile {
    glyph_id: 0,
    width: 32,
    height: 32,
    data: sdf,
    bearing_x: 0,
    bearing_y: 0,
    advance_x: 32.0,
};
let atlas = SdfAtlas::pack(&[tile]);
assert!(atlas.uv_map.contains_key(&0));
# Ok::<(), oxitext_sdf::SdfError>(())

Multi-channel SDF straight from a font outline, then GPU upload:

use oxitext_sdf::{glyph_to_msdf_tile, MsdfAtlas, GpuAtlasFormat};

let font = std::fs::read("font.ttf")?;
// 64×64 MSDF tile at 48px, spread 4, padding 2.
let tile = glyph_to_msdf_tile(&font, /* glyph_id */ 36, 48.0, 64, 64, 4.0, 2)?
    .expect("glyph has an outline");

let atlas = MsdfAtlas::pack(&[tile], 512);
let desc = atlas.to_gpu_descriptor();
assert_eq!(desc.format, GpuAtlasFormat::Rgb8Unorm);
// Upload desc.data (RGB) + desc.uv_map to your GPU texture.
# Ok::<(), Box<dyn std::error::Error>>(())

API Overview

Single-channel SDF (coverage-based)

Item Kind Description
compute_sdf(coverage, width, height, spread, padding) fn Felzenszwalb–Huttenlocher EDT over a u8 coverage bitmap → Vec<u8> SDF (<128 outside, ≈128 on the outline, >128 inside).
edt_2d(grid, width, height) fn Raw 2D Euclidean distance transform over an f32 seed grid (squared distances).
glyph_to_sdf_tile(coverage, src_width, src_height, tile_size) fn Compute an SDF at source resolution then bilinearly resample to tile_size × tile_size. Default spread 8.0.
bitmap_to_sdf_tile(bitmap, glyph_id, bearing_x, bearing_y, advance_x, spread) fn Convert an oxitext-core coverage Bitmap into an SdfTile (None for empty bitmaps).

Outline-based SDF generators

Item Kind Description
glyph_to_sdf_tile_analytic(face_data, glyph_id, px_size, tile_size, spread) fn Analytic per-pixel true SDF rendered directly from the outline → Option<SdfTile>.
glyph_to_psdf_tile(face_data, glyph_id, px_size, tile_size, spread) fn Pseudo-distance-field tile (perpendicular distance) → Option<PsdfTile>.
extract_glyph_shape(face_data, glyph_id) fn Parse a glyph outline into a GlyphShape (Option).
color_edges(&mut shape) fn Assign Chlumsky edge colors to a GlyphShape in place (call before compute_msdf).

Multi-channel SDF (MSDF / MTSDF)

Item Kind Description
compute_msdf(shape, width, height, spread, scale, offset_x, offset_y) fn 3-channel RGB MSDF from a colored GlyphShapeVec<u8> (w·h·3).
compute_mtsdf(shape, width, height, spread, scale, offset_x, offset_y) fn 4-channel RGBA MTSDF: RGB from MSDF, alpha from a true SDF → Vec<u8> (w·h·4).
glyph_to_msdf_tile(face_data, glyph_id, px_size, tile_width, tile_height, spread, padding) fn End-to-end MSDF tile from a font → Option<MsdfTile>.
glyph_to_mtsdf_tile(face_data, glyph_id, px_size, tile_width, tile_height, spread, padding) fn End-to-end MTSDF tile from a font → Option<MtsdfTile>.
EdgeColor struct Bit-flag edge color (RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA, WHITE) with has_red/has_green/has_blue.
GlyphShape struct Decomposed outline (contours, units_per_em) used by all outline generators.
MsdfTile struct glyph_id, width, height, data (RGB), bearing_x, bearing_y, advance_x.
MtsdfTile struct As MsdfTile but data is RGBA (w·h·4).

Tiles and atlases

Item Kind Description
SdfTile struct A single single-channel tile: glyph_id, width, height, data, bearing_x/y, advance_x.
SdfTile::from_coverage(glyph_id, coverage, width, height, spread, bearing_x, bearing_y, advance_x) fn Build a tile from an f32 coverage bitmap (values in [0, 1]).
SdfAtlas struct Single-channel atlas: width, height, texture (u8), uv_map: HashMap<u16, UvRect>.
SdfAtlas::new(width, height) / with_capacity(...) fn Construct a blank / capacity-hinted atlas.
SdfAtlas::pack(tiles) fn Power-of-two shelf-pack a tile set.
SdfAtlas::pack_with_options(tiles, options) fn Pack with AtlasOptions; returns (SdfAtlas, AtlasStats).
SdfAtlas::pack_growing(tiles, initial_size, max_size) fn Pack with dynamic atlas growth; returns (SdfAtlas, AtlasStats).
SdfAtlas::add_tile(&mut self, tile) / remove_tile(glyph_id) fn Incrementally insert (Option<UvRect>) or remove (bool) a tile.
SdfAtlas::to_bytes() / from_bytes(data) / from_static(data) fn Serialize to / load from the SDFA binary atlas format.
SdfAtlas::export_png(path) fn Write the atlas texture as a greyscale PNG.
MsdfAtlas struct Multi-channel atlas; MsdfAtlas::pack(tiles, atlas_size), export_png(path).
MultiPageAtlas struct Multi-page packer for tile sets that exceed one texture; pack(tiles, page_size, padding), lookup(glyph_id) -> Option<(page, &UvRect)>.
pack_growing(tiles, initial_size, max_size, padding) fn Free-function growing packer.
UvRect struct u_min, v_min, u_max, v_max (all in [0, 1]).
AtlasOptions struct atlas_size, padding, max_size, algorithm.
AtlasStats struct tiles_packed, tiles_dropped, utilization, wasted_pixels.
PackingAlgorithm enum Shelf (default), MaxRects, Skyline.

GPU descriptors

Item Kind Description
SdfAtlas::to_gpu_descriptor() / MsdfAtlas::to_gpu_descriptor() fn Produce a backend-agnostic GpuAtlasDescriptor.
GpuAtlasDescriptor struct width, height, format, data, uv_map: HashMap<u16, NormalizedUvRect>, glyph_metrics.
GpuAtlasFormat enum R8Unorm (single-channel) or Rgb8Unorm (MSDF).
NormalizedUvRect struct UV rectangle in normalized [0, 1] coordinates.
AtlasGlyphMetrics struct bearing_x, bearing_y, advance_x, width_px, height_px.

Build-time helpers

Item Kind Description
generate_atlas_binary(font_data, glyph_ids, px_size, tile_size, spread, atlas_size, output_path) fn Render a glyph set to a .bin atlas on disk (errors if tiles overflow).
generate_ascii_atlas(font_data, px_size, output_path) fn Convenience: pre-bake the printable-ASCII range (64×64 tiles, spread 4, 512×512 atlas).

Error type

Variant Description
SdfError::InvalidInput(String) Coverage slice length does not match width × height.
SdfError::ZeroSize Width or height was zero.
SdfError::InvalidFont Font bytes could not be parsed by ttf-parser.
SdfError::InvalidData(String) Binary atlas data is malformed (bad magic, version mismatch, truncated).
SdfError::Io(String) I/O error (e.g. during PNG/atlas export).

Cross-references

License

Apache-2.0 — COOLJAPAN OU (Team Kitasan)