daegun

Rust methods

122 methods, extracted from the source at build time so this list cannot drift from it.

Bidi and segmentation

Unicode text algorithms that do not need a font: bidirectional ordering, script runs, and where a string may be split. All of these are free functions at the crate root: daegun::resolve_bidi and so on.

daegun::grapheme_boundaries
pub fn grapheme_boundaries(text: &str) -> alloc::vec::Vec<usize>

Every position where a user-perceived character begins, as byte offsets.

What a person calls "a character" is a grapheme cluster, and it is often several code points: a base letter plus combining accents, a regional indicator pair, an emoji with a skin tone modifier, a Hangul syllable assembled from jamo. This returns the boundaries between them, which is what cursor movement, selection and deletion should step by. Never step by char – that splits emoji and strips accents off letters.

use daegun::grapheme_boundaries;

let b = grapheme_boundaries("é👨‍👩‍👧‍👦!");
// 3 graphemes, though the string holds many more code points
daegun::line_break_opportunities
pub fn line_break_opportunities(text: &str) -> alloc::vec::Vec<LineBreak>

Every position where a line may be broken, each marked as allowed or mandatory.

The Unicode line breaking algorithm. at is the byte offset and mandatory marks the breaks you must take – after a newline or a paragraph separator – as opposed to the ones you may take if the line is full. It knows that a break is allowed after a hyphen but not before a closing bracket, and that CJK breaks almost anywhere. Font::layout uses this internally; call it directly only when doing your own line breaking.

use daegun::line_break_opportunities;

for b in line_break_opportunities("a long-ish line\nand another") {
    if b.mandatory { /* must break here */ }
}
daegun::line_visual_runs
pub fn line_visual_runs(
    para: &BidiParagraph,
    text: &str,
    start: usize,
    end: usize,
) -> alloc::vec::Vec<VisualRun>

Splits one line of a resolved paragraph into runs of a single direction, in the order they should be drawn.

Bidi resolution happens per paragraph, but drawing happens per line, and a line taken out of a paragraph has to be reordered on its own. Give it the paragraph, the text, and the character range of the line; it returns the runs in visual order with the level of each.

daegun::resolve_bidi
pub fn resolve_bidi(text: &str, base: Option<bool>) -> BidiParagraph

Runs the Unicode bidirectional algorithm over a string, returning each character's embedding level and the visual order.

Mixed Arabic and Latin text is stored in reading order but drawn in a different one, and working out that order is a real algorithm, not a reversal. This applies it: levels gives each character its embedding level – even is left to right, odd is right to left – and visual_order gives the character indices in the order they should be drawn. Pass None for base to detect the paragraph direction from the first strong character, Some(true) to force right to left. Font::shape_bidi does this and the shaping together, and is usually what you want.

use daegun::resolve_bidi;

let para = resolve_bidi("hello שלום", None);
println!("base level {}", para.base_level);   // 0, so left to right overall
daegun::script_runs
pub fn script_runs(text: &str) -> alloc::vec::Vec<ScriptRun>

Splits a string into runs of a single script, in order.

Shaping works on one script at a time, so text mixing Latin and Greek and Han has to be split first. Common characters – spaces, digits, punctuation – are folded into the surrounding run rather than starting a new one, which is what keeps a full stop after Greek from becoming a run of its own.

use daegun::script_runs;

for r in script_runs("English ελληνικά 日本語") {
    // one run per script, in order
}
daegun::word_boundaries
pub fn word_boundaries(text: &str) -> alloc::vec::Vec<usize>

Every position where a word begins or ends, as byte offsets.

Unicode word segmentation, which is what double-click selection and word-wise cursor movement should use. It handles the cases splitting on spaces does not: contractions stay whole, numbers with separators stay whole, and scripts written without spaces are handled by their own rules.

Color

Fonts that carry their own color: COLR layer graphs, CPAL palettes, and embedded bitmap strikes. A color glyph renders to a scene of RGBA pixels rather than to single-channel coverage.

Font::colr_layers
pub fn colr_layers(&self, gid: u16) -> Option<Vec<ColrLayer>>

The COLR v0 layers of a glyph – a flat list of glyph ids with colors – or None if the glyph has none.

COLR v0 is the simple form: a glyph is a stack of ordinary glyphs, each filled with one color from the palette. Each layer is a tuple – (gid, r, g, b, a, is_foreground) – so destructure it rather than reaching for field names. Draw them in the order returned, back to front. When the last element is true the layer takes your text color rather than one from the palette, and the four channels are then meaningless. Colors come from palette 0; use colr_layers_for_palette to choose another.

// a layer is a tuple: (gid, r, g, b, a, is_foreground)
if let Some(layers) = font.colr_layers(gid) {
    for &(lgid, r, g, b, a, is_foreground) in &layers {   // back to front
        let color = if is_foreground { text_color } else { [r, g, b, a] };
        draw_glyph_with(lgid, color);
    }
}
Font::colr_layers_for_palette
pub fn colr_layers_for_palette(&self, gid: u16, palette_index: u16) -> Option<Vec<ColrLayer>>

The COLR v0 layers of a glyph with colors taken from a specific palette.

The same as colr_layers but reading palette palette_index rather than 0. Use it to honour a font's dark-mode palette, which palette_info will identify.

Font::colr_v1_paint
pub fn colr_v1_paint(&self, gid: u16, axes: &[(&str, f64)], palette_index: u16) -> Option<Paint>

The COLR v1 paint graph for a glyph, unrendered, or None if the glyph has none.

COLR v1 describes a glyph as a tree of paint operations – solid fills, linear and radial gradients, transforms, clips and blends. This hands you that tree instead of rendering it, for when you want to feed it to your own compositor. Use render_colr_glyph if you just want pixels.

Font::glyph_bitmap
pub fn glyph_bitmap(&self, gid: u16, target_ppem: u16) -> Option<GlyphBitmap>

An embedded bitmap for a glyph at or near a target pixel size, or None if the font has no strike for it.

Some color fonts – Apple's emoji among them – ship photographic bitmaps rather than outlines, in sbix or CBDT tables. target_ppem is the size you want; the font picks the nearest strike it actually has, which may be larger or smaller, so scale the result to the size you asked for. The data may be PNG rather than raw pixels, since that is how these tables store it.

Font::palette_count
pub fn palette_count(&self) -> u16

How many CPAL palettes the font carries, or 0 if it carries none.

Most color fonts ship exactly one. A font that offers alternatives – a light and a dark set, say – reports more, and any index below this count is valid.

Font::palette_info
pub fn palette_info(&self) -> Vec<PaletteInfo>

What each palette is for: its index, whether it is safe on a light or a dark background, and its name id.

Fonts may label their palettes with usability flags, and this surfaces them – light_safe and dark_safe – so you can pick the palette matching your background rather than always taking 0. name_id points into the name table when the designer named the palette, and is None when they did not. Empty for a font with no CPAL table.

// pick a palette meant for a dark background, falling back to the default
let dark = font.palette_info().iter()
    .find(|p| p.dark_safe)
    .map_or(0, |p| p.index);

let scene = font.render_colr_glyph(gid, 48.0, &[], dark);
Font::render_colr_glyph
pub fn render_colr_glyph(
    &self,
    gid: u16,
    px: f32,
    axes: &[(&str, f64)],
    palette_index: u16,
) -> Option<crate::daerizer::RenderedScene>

Renders a color glyph to finished RGBA pixels, or None if the glyph is not a color glyph.

The call to use for emoji and other color fonts. Where rasterize_glyph gives single-channel coverage you tint yourself, this returns a RenderedScene whose rgba field is straight – not premultiplied – RGBA8, width by height, ready to blit. It walks the COLR v1 paint graph, so gradients, transforms and compositing all resolve here. palette_index selects a CPAL palette, 0 being the default; fonts that ship a dark-mode palette put it at 1.

let scene = font.render_colr_glyph(gid, 48.0, &[], 0).expect("is a color glyph");
// scene.rgba is straight alpha, 4 bytes per pixel
upload_texture(scene.width, scene.height, &scene.rgba);

Drawing

A small front end that picks the CPU or the GPU for you, per glyph, and tells you which it chose.

DrawnGlyph::bitmap
pub fn bitmap(&self) -> Option<&RasterizedGlyph>

The rasterized bitmap inside a DrawnGlyph, or None when the glyph did not go down a CPU path.

Answers for the Cpu and Reference variants and gives None for everything else, so it is the short way to get pixels when you do not care which of the two produced them.

DrawTarget::cpu_only
pub fn cpu_only(batch: &'a mut GpuBatch) -> DrawTarget<'a>

Creates a DrawTarget that always rasterizes on the CPU.

Takes no device, so every glyph comes back as DrawnGlyph::Cpu. This is the right target for server-side rendering, for tests, and for anywhere you want output that does not depend on which GPU is present.

let mut batch = GpuBatch::new();
let mut target = DrawTarget::cpu_only(&mut batch);
Font::draw_glyph
pub fn draw_glyph(
    &self,
    target: &mut DrawTarget<'_>,
    gid: u16,
    px: f32,
    axes: &[(&str, f64)],
    opts: &RasterOptions,
    palette: Option<u16>,
) -> DrawnGlyph

Draws one glyph through whichever path the policy selects, returning a DrawnGlyph that says what happened.

The single entry point that hides the CPU/GPU decision. The result tells you which way it went: Cpu and Reference carry a finished bitmap, Gpu carries a slot to instance, GpuColor carries one slot per colored layer, Scene carries finished RGBA pixels for a color glyph, Nothing means the glyph had no ink, BatchFull means the batch cannot take more, and Refused explains why the request could not be served. is_ok collapses that to a yes or no, and bitmap gets you the pixels when there are any.

use daegun::DrawnGlyph;

match font.draw_glyph(&mut target, gid, 32.0, &[], &opts, None) {
    DrawnGlyph::Cpu(g) | DrawnGlyph::Reference(g) => upload_mask(&g.bitmap),
    DrawnGlyph::Gpu(slot) => instances.push(slot.instance(pos, scale, [em_px, em_px], tint)),
    DrawnGlyph::Nothing => {}
    other => eprintln!("not drawn: {other:?}"),
}
DrawnGlyph::is_ok
pub fn is_ok(&self) -> bool

True unless the glyph was refused or the batch was full.

A quick check that the request was served. Note that Nothing counts as ok – a glyph with no ink, such as a space, was handled correctly and simply produced nothing to draw.

DrawTarget::new
pub fn new(batch: &'a mut GpuBatch, device: &'a DeviceProfile) -> DrawTarget<'a>

Creates a DrawTarget that may use the GPU, given a batch to fill and a description of the device.

The DeviceProfile tells the router what the GPU can do, so it can decide per glyph whether the GPU is the right choice. The batch collects the geometry and instances the GPU will need. Use cpu_only instead when there is no device.

use daegun::{DrawTarget, GpuBatch};

let mut batch = GpuBatch::new();
let mut target = DrawTarget::new(&mut batch, &device);
DrawTarget::with_policy
pub fn with_policy(mut self, policy: Policy) -> DrawTarget<'a>

Sets the routing policy – how the target decides between CPU and GPU – returning the target for chaining.

Policy carries the preference and the limits: whether to favor the GPU or the CPU, and the sizes past which a glyph should go one way or the other. Very large glyphs are cheaper on the GPU; very small ones are better hinted on the CPU. The default is a reasonable middle.

use daegun::{Policy, Prefer};

let mut target = DrawTarget::new(&mut batch, &device)
    .with_policy(Policy { prefer: Prefer::Gpu, ..Policy::default() });

Font and metrics

Opening a font, and the switches that change how a glyph is drawn. Font is the handle every other call hangs off. It is cheap to clone, safe to share between threads, and it caches what it parses, so opening once and keeping it is always the right shape.

Font::clear_glyph_cache
pub fn clear_glyph_cache(&self)

Drops every cached glyph, keeping the size budget you set.

Useful when you have finished with one size or one variable-axis position and want the memory back without rebuilding the cache from scratch.

font.clear_glyph_cache();
assert_eq!(font.glyph_cache_stats().0, 0);
Font::from_bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Font, FontError>

Opens a font from a byte slice and returns a Font you can shape and rasterize with.

The slice is parsed and the tables it needs are copied out, so the Font does not borrow from your buffer and you are free to drop it. TrueType, OpenType/CFF, CFF2 and variable fonts all open through this one call – there is no separate constructor per format. A file that is not a font, or one damaged badly enough that its table directory cannot be trusted, comes back as Err(FontError) rather than panicking. That matters: a font file is data from strangers, and every parser behind this call forbids unsafe code outright.

use daegun::Font;

let bytes = std::fs::read("Inter.ttf")?;
let font = Font::from_bytes(&bytes)?;

println!("{} glyphs, {} units per em", font.num_glyphs(), font.upm());
// the slice can go out of scope here; the font does not borrow it
Font::from_ttc
pub fn from_ttc(bytes: &[u8], index: usize) -> Result<Font, FontError>

Opens one font out of a TrueType Collection by its index.

A .ttc or .otc packs several faces into one file so they can share tables. This picks the face at index, counting from zero, and gives you a normal Font. An index past the end returns Err(FontError). Ask ttc_font_count first if you do not already know how many the file holds.

use daegun::Font;

let bytes = std::fs::read("Helvetica.ttc")?;
for i in 0..Font::ttc_font_count(&bytes) {
    let face = Font::from_ttc(&bytes, i)?;
    println!("{}: {:?}", i, face.family_name());
}
Font::from_vec
pub fn from_vec(bytes: alloc::vec::Vec<u8>) -> Result<Font, FontError>

Opens a font from a Vec<u8> it takes ownership of, avoiding the copy from_bytes makes.

Identical in result to from_bytes, but it consumes the buffer instead of copying out of it. Use it when you have just read the file and have no other use for the bytes – it is the cheaper of the two, and for a large CJK face the difference is megabytes.

use daegun::Font;

// std::fs::read already gives you an owned Vec, so hand it straight over
let font = Font::from_vec(std::fs::read("SourceHanSans.otf")?)?;
Font::glyph_cache_stats
pub fn glyph_cache_stats(&self) -> (usize, usize)

Returns (entries, bytes) – how many glyphs are cached and how much memory they hold.

A snapshot, not a live view. The second value counts the pixel data actually held, so it is the number to compare against the budget you gave set_glyph_cache_bytes.

let (entries, bytes) = font.glyph_cache_stats();
println!("{entries} glyphs cached, {:.1} KB", bytes as f64 / 1024.0);
Os2Info::is_bold
pub fn is_bold(&self) -> bool

True when the font declares itself bold, from bit 5 of the OS/2 fsSelection field.

A declaration rather than a weight. On a variable font this reflects the default instance, so a face you have moved along wght still answers about where it started – read axes for the live value instead.

Os2Info::is_italic
pub fn is_italic(&self) -> bool

True when the font declares itself italic, from bit 0 of the OS/2 fsSelection field.

This is the font stating its own intent, not a measurement. A face can be visually slanted and leave the bit clear, so pair it with italic_angle when you need to be sure. Note this is a method on Os2Info, which Font::os2_info returns and which is None for a font with no OS/2 table.

if let Some(os2) = font.os2_info() {
    if os2.is_italic() { println!("italic by declaration"); }
}
// or measure it instead
if font.italic_angle() != 0.0 { println!("slanted by {} degrees", font.italic_angle()); }
Os2Info::is_oblique
pub fn is_oblique(&self) -> bool

True when the font declares itself oblique, from bit 9 of the OS/2 fsSelection field.

Oblique means slanted upright forms; italic means redrawn letterforms. Fonts distinguish them and so does this bit, though many faces set only is_italic regardless of which they are.

Os2Info::is_regular
pub fn is_regular(&self) -> bool

True when the font declares itself the regular member of its family, from bit 6 of fsSelection.

Well-behaved families set this on exactly one face. It is the cleanest way to pick a default from a directory of files without parsing style names.

Font::set_glyph_cache_bytes
pub fn set_glyph_cache_bytes(&self, bytes: usize)

Sets how many bytes of rasterized glyphs the font keeps cached, and clears what is cached now.

The font memoizes rasterized glyphs so drawing the same text twice does not rasterize it twice. This call replaces the cache with a fresh one of the given budget, so everything already cached is discarded – it is a resize and a clear in one. Set it once after opening rather than on a hot path. A budget of 0 effectively disables caching.

let font = Font::from_bytes(&bytes)?;
font.set_glyph_cache_bytes(8 * 1024 * 1024);   // 8 MB, and drops anything cached

// takes &self, so it works through a shared reference
let shared = std::sync::Arc::new(font);
shared.set_glyph_cache_bytes(2 * 1024 * 1024);
Font::ttc_font_count
pub fn ttc_font_count(bytes: &[u8]) -> usize

Reports how many faces a TrueType Collection holds, without opening any of them.

Returns 0 for a file that is not a collection, which is the honest answer for a plain .ttf and lets you branch on it without an error path.

let n = daegun::Font::ttc_font_count(&bytes);
if n == 0 {
    let font = daegun::Font::from_bytes(&bytes)?;   // a single face
} else {
    let font = daegun::Font::from_ttc(&bytes, 0)?;  // the first of n
}
Os2Info::uses_typo_metrics
pub fn uses_typo_metrics(&self) -> bool

True when the font asks you to use its typographic metrics rather than its Windows metrics, from bit 7 of fsSelection.

A font carries two sets of vertical metrics that frequently disagree. When this bit is set the designer is telling you the sTypo values are the intended ones for line spacing; when it is clear, the usWin values are what most software will have used. line_metrics already applies this rule for you – read this only if you are choosing between them yourself.

let m = font.line_metrics(false);   // already resolves which set to trust
// the raw pair, if you want to decide yourself
if let Some(os2) = font.os2_info() {
    let prefer_typo = os2.uses_typo_metrics();
    println!("typo: {:?}, win: {:?}", os2.typo_metrics, os2.win_metrics);
}
RasterOptions::with_embolden
pub fn with_embolden(mut self, units: f32) -> RasterOptions

Thickens the outline by the given amount in font units, returning the options for chaining.

A synthetic bold: the contours are pushed outward, so the shape stays filled and simply gains weight. Always prefer a real bold face or a wght axis when the font has one, since a designer compensated for optical effects that this cannot. Units are the font’s own, so scale by upm if you are thinking in ems.

// about 2% of the em, a restrained synthetic bold
let units = font.upm() as f32 * 0.02;
let opts = RasterOptions::default().with_embolden(units);
RasterOptions::with_gamma
pub fn with_gamma(mut self, gamma: f32) -> RasterOptions

Sets the gamma applied to coverage before it is written out, returning the options for chaining.

Coverage is linear, but blending it against a background in a non-linear color space makes light text on dark look thinner than dark text on light. A gamma of about 1.8 is the usual correction for light-on-dark. Leaving it unset applies none.

let opts = RasterOptions::default().with_gamma(1.8);
RasterOptions::with_hinting
pub fn with_hinting(mut self, hinting: HintMode) -> RasterOptions

Chooses the hinting mode, returning the options for chaining.

Hinting nudges outlines onto the pixel grid so stems stay crisp at small sizes. HintMode::None skips it, which is right for large text and for GPU rendering. HintMode::Font runs the instructions the font ships – TrueType bytecode or CFF hints. HintMode::Auto runs daegun’s own autohinter, which is the better answer for a face with weak or missing hints.

use daegun::{HintMode, RasterOptions};

let opts = RasterOptions::default().with_hinting(HintMode::Auto);
let glyph = font.rasterize_glyph_with(gid, 12.0, &[], &opts).expect("has ink");
RasterOptions::with_layout
pub fn with_layout(mut self, layout: SubpixelLayout) -> RasterOptions

Sets the subpixel stripe order, returning the options for chaining.

An LCD panel puts its red, green and blue subpixels in a fixed order, and rasterizing against that order triples the horizontal resolution you have to work with. SubpixelLayout::grayscale() is the default and gives one coverage value per pixel; the RGB and BGR layouts give three. Choose by what the display actually is – the wrong order produces visible color fringing.

use daegun::{RasterOptions, StripeOrder, SubpixelLayout};

let opts = RasterOptions::default()
    .with_layout(SubpixelLayout::horizontal(StripeOrder::Rgb));
let glyph = font.rasterize_glyph_with(gid, 16.0, &[], &opts).expect("has ink");
RasterOptions::with_oblique
pub fn with_oblique(mut self, tangent: f32) -> RasterOptions

Shears the outline by the given tangent, returning the options for chaining.

A synthetic italic. The value is the tangent of the slant angle, so 0.21 is roughly 12 degrees, the conventional amount. As with with_embolden, a real italic face is always better – this slants the upright letterforms rather than substituting the drawn-for-italic ones.

let opts = RasterOptions::default().with_oblique(12f32.to_radians().tan());
RasterOptions::with_stroke
pub fn with_stroke(mut self, stroke: StrokeStyle) -> RasterOptions

Strokes the outline instead of filling it, returning the options for chaining.

The StrokeStyle carries the width in font units along with the join and cap treatment. This gives you an outlined letterform – the counters stay open – rather than a bolder solid one. For that, use with_embolden.

use daegun::{RasterOptions, StrokeStyle};

let opts = RasterOptions::default().with_stroke(StrokeStyle::default());
RasterOptions::with_transform
pub fn with_transform(mut self, transform: [f32; 6]) -> RasterOptions

Sets an affine transform applied to the outline before rasterizing, returning the options for chaining.

The array is [a, b, c, d, e, f], mapping x′ = a·x + c·y + e and y′ = b·x + d·y + f, the same order Core Graphics and PostScript use. It is applied in font units before scaling to pixels, so the result is hinted and antialiased properly rather than being a scaled bitmap. Use it for rotation, mirroring, or a shear you want the rasterizer to know about.

// rotate 15 degrees about the origin
let (s, c) = (15f32.to_radians().sin(), 15f32.to_radians().cos());
let opts = RasterOptions::default().with_transform([c, s, -s, c, 0.0, 0.0]);

GPU

The GPU path hands you data rather than drawing. Owning a device needs unsafe and the engine does not, so daegun produces the buffers and the instances and you submit them.

Font::gpu_color_glyph
pub fn gpu_color_glyph(
    &self,
    batch: &mut crate::daerizer::daegpu::GpuBatch,
    gid: u16,
    axes: &[(&str, f64)],
    palette_index: u16,
) -> Result<alloc::vec::Vec<ColorSlot>, GpuGlyphError>

The color equivalent of gpu_glyph: one slot per flat-colored layer of a COLR glyph, each with its own tint.

A color glyph is a stack of shapes with their own colors, so it needs one slot per layer rather than one for the glyph. Draw them in the order returned – they paint back to front and the shader does no depth testing. palette_index picks which CPAL palette supplies the colors, 0 being the default.

let slots = font.gpu_color_glyph(&mut batch, gid, &[], 0)?;
for s in &slots {                    // back to front; order matters
    instances.push(s.slot.instance(pos, scale, [em_px, em_px], s.tint));
}
Font::gpu_glyph
pub fn gpu_glyph(&self, batch: &mut GpuBatch, gid: u16, axes: &[(&str, f64)])
    -> Result<GlyphSlot, GpuGlyphError>

Adds a glyph's curves to a GpuBatch and returns the slot that identifies it, so the GPU can draw it at any size.

The GPU path stores a glyph once, in em space, and draws it at whatever size you like – the shader evaluates coverage from the curves directly, so there is no atlas and no resolution to pick. The batch accumulates curves, bands and hulls, which you upload as buffers; the slot then makes instances through GlyphSlot::instance. Because a slot is keyed only by glyph and axis position, one slot serves every size on screen. Err(GpuGlyphError) if the glyph cannot be prepared.

use daegun::GpuBatch;

let mut batch = GpuBatch::new();
let slot = font.gpu_glyph(&mut batch, gid, &[])?;

// upload once
upload(batch.curves(), batch.bands(), batch.band_curves(), batch.hulls());

// one instance per placement:
//   offset    [f32; 2]  device pixels, y up from the BOTTOM of the target
//   scale     f32
//   em_pixels [f32; 2]  the em box in pixels, x and y
//   tint      [f32; 4]  straight-alpha RGBA, 0..1
let inst = slot.instance([x, y], scale, [em_px, em_px], [1.0, 1.0, 1.0, 1.0]);

Glyphs

Going from characters to glyph ids, and asking about one glyph in particular. A glyph id is an index into this font and means nothing in another one.

Font::advance_widths
pub fn advance_widths(&self, gids: &[u16], axes: &[(&str, f64)]) -> Vec<f64>

The advance width of each given glyph, on the 1000-unit em.

Batched because the underlying values are cached per axis position, so asking for many at once is much cheaper than one call each. The values are whole units – the advance table stores integers – whereas the advances shape returns may be fractional, since positioning can adjust them. For measuring text, use the advances from shape: these are the raw per-glyph widths with no kerning or positioning applied.

let px = 16.0;
let run = font.shape("Wave", &[], false).expect("shapes");
let width_px: f64 = run.advances.iter().sum::<f64>() * px / 1000.0;   // correct

// raw, unpositioned widths
let raw = font.advance_widths(&run.glyphs, &[]);
Font::caret_positions
pub fn caret_positions(&self, text: &str, axes: &[(&str, f64)], vertical: bool) -> Option<Vec<f64>>

Every position a text cursor may occupy in a string, one per character boundary, on the 1000-unit em.

Shapes the text and then works out where each character boundary landed, which is harder than it sounds: ligatures collapse several characters into one glyph, marks take no width of their own, and right-to-left runs advance leftward. This handles all three, so the result is directly usable for hit testing and for drawing a caret. Returns one more value than there are characters – the position after the last one. None if the text cannot be shaped.

let px = 16.0;
let carets = font.caret_positions("office", &[], false).expect("shapes");
let x_px: Vec<f64> = carets.iter().map(|c| c * px / 1000.0).collect();
// x_px[2] is between the "f" and the "f", even though they shaped to one ligature glyph
Font::codepoints
pub fn codepoints(&self) -> Vec<u32>

Every code point the font supports, without the glyph ids.

The same data as coverage when you only care which characters are available.

println!("{} characters supported", font.codepoints().len());
Font::coverage
pub fn coverage(&self) -> Vec<(u32, u16)>

Every code point the font supports, paired with the glyph it maps to.

The whole cmap flattened into (code point, glyph id) pairs, filtered to glyphs that actually exist in this font. Capped at 200,000 entries, which is above any real font – the largest CJK faces sit near 70,000 – so the cap protects against a damaged table rather than truncating a genuine one. Build a set from it once if you are testing many characters.

let supported: std::collections::HashSet<u32> =
    font.coverage().into_iter().map(|(cp, _)| cp).collect();
Font::default_vertical_origin
pub fn default_vertical_origin(&self) -> i32

The vertical origin the font uses for glyphs that do not specify their own, on the 1000-unit em.

Returns 0 for a TrueType outline font, which derives origins per glyph rather than defaulting. For CFF fonts it reads the default from VORG.

Font::glyph_bounds
pub fn glyph_bounds(&self, gid: u16, axes: &[(&str, f64)]) -> Option<(f64, f64, f64, f64)>

The tight bounding box of one glyph as (xmin, ymin, xmax, ymax) on the 1000-unit em, or None for a glyph with no outline.

Measured from the real outline at the axis position you pass, not read from a table, so it is accurate for variable fonts at any position. It is on the 1000-unit em like the other metrics, so pixels are value * px / 1000.0. A space returns None because it has no ink – that is not an error, just an empty glyph.

let gid = font.glyph_id(u32::from('H')).unwrap();
let (x0, y0, x1, y1) = font.glyph_bounds(gid, &[]).expect("H has ink");
let cap_height_px = (y1 - y0) * 16.0 / 1000.0;
Font::glyph_class
pub fn glyph_class(&self, gid: u16) -> Option<GlyphClass>

What kind of glyph this is – base, ligature, mark or component – or None if the font does not classify it.

From the GDEF table. Shaping needs this to know that a mark should attach rather than advance, and it is the reliable way to tell a combining glyph from a spacing one. None means the font has no GDEF table or leaves this glyph unclassified, which is common and not an error.

use daegun::GlyphClass;

if font.glyph_class(gid) == Some(GlyphClass::Mark) {
    // takes no advance of its own
}
Font::glyph_id
pub fn glyph_id(&self, codepoint: u32) -> Option<u16>

The glyph id for one Unicode code point, or None if the font has no glyph for it.

Takes a u32 code point rather than a char, so u32::from(c) for a character literal. A glyph id is an index into this font only and means something different in another font.

let gid = font.glyph_id(u32::from('A')).expect("font has A");
let bitmap = font.rasterize_glyph(gid, 32.0, &[]);
Font::glyph_ids
pub fn glyph_ids(&self, text: &str) -> Vec<Option<u16>>

Maps each character of a string to its glyph id, with None where the font has no glyph.

A straight per-character cmap lookup, one entry per char. This is not shaping: it applies no ligatures, no joining, no reordering and no marks, so for anything beyond simple Latin the result is not what should be drawn. Use it to test coverage, not to render – shape is the call that produces drawable glyphs.

// coverage check
let missing = font.glyph_ids("café").iter().filter(|g| g.is_none()).count();

// to actually draw the text, shape it instead
let run = font.shape("café", &[], false).expect("shapes");
Font::glyph_name
pub fn glyph_name(&self, gid: u16) -> Option<String>

The PostScript name of one glyph, such as "A" or "uni4E00", or None if the font stores no names.

From the post table for TrueType or the charset for CFF. Many fonts ship version 3 post, which stores no names at all, so None is common and expected. Names are useful for debugging and for PDF, never for identifying a glyph programmatically – use the glyph id.

println!("{:?}", font.glyph_name(gid));   // Some("aacute")
Font::glyph_names
pub fn glyph_names(&self) -> Vec<Option<String>>

The PostScript name of every glyph, indexed by glyph id, with None where a name is missing.

One pass over the whole font instead of num_glyphs separate lookups. The vector is always num_glyphs long, so it can be indexed by glyph id directly.

let names = font.glyph_names();
println!("{:?}", names[gid as usize]);
Font::has_glyph
pub fn has_glyph(&self, codepoint: u32) -> bool

True when the font can render the given code point.

The same lookup as glyph_id without building the id. Handy for choosing a fallback font before committing to one.

let fonts = [&inter, &noto_jp];
let pick = fonts.iter().find(|f| f.has_glyph(u32::from('字')));
Font::ligature_carets
pub fn ligature_carets(&self, gid: u16, axes: &[(&str, f64)]) -> Vec<f64>

Where a text cursor may sit inside a ligature, on the 1000-unit em.

When "fi" becomes one glyph, a cursor still has to be placeable between the f and the i. The font records those positions in GDEF, and this returns them, offset from the ligature's own origin and in the order they appear. Empty for an ordinary glyph, and empty for a font with no GDEF table. For cursor positions across a whole string, caret_positions is the call you want.

let carets = font.ligature_carets(fi_gid, &[]);   // e.g. [512.0]
Font::mark_attachment_class
pub fn mark_attachment_class(&self, gid: u16) -> u16

The mark attachment class of a glyph, or 0 when it has none.

Fonts group marks into classes – above-base, below-base and so on – so a positioning rule can apply to a whole group. This returns the raw class number from GDEF, which is meaningful only against the rules in that same font.

Font::variation_glyph_id
pub fn variation_glyph_id(&self, base: u32, selector: u32) -> Option<u16>

Resolves a base character plus a variation selector to a glyph, or None if the pair is not mapped.

Unicode variation sequences let one character request a specific form: U+FE0E asks for the text presentation of an emoji and U+FE0F for the emoji presentation, and the Ideographic Variation Database uses U+E0100 and beyond to pick between CJK forms. This reads the format 14 cmap subtable that maps those pairs. shape already applies them, so reach for this only when handling selectors yourself.

// the text-style form of ☂ (U+2602 U+FE0E)
let gid = font.variation_glyph_id(0x2602, 0xFE0E);
Font::vertical_advance
pub fn vertical_advance(&self, gid: u16, axes: &[(&str, f64)]) -> u32

The vertical advance of one glyph on the 1000-unit em, for text set top to bottom.

How far the pen moves down after drawing this glyph. Returns 0 for a glyph id past the end of the font – a deliberate refusal, since VORG and cmap will both answer confidently for ids that do not exist. A font with no vertical metrics falls back to the em size.

let down = font.vertical_advance(gid, &[]) * 16 / 1000;
Font::vertical_origin
pub fn vertical_origin(&self, gid: u16, axes: &[(&str, f64)]) -> Option<i32>

The y origin used when this glyph is set vertically, on the 1000-unit em, or None if the glyph id is out of range.

In vertical text a glyph hangs from an origin above the baseline rather than sitting on it. CFF fonts carry these in a VORG table; TrueType fonts derive them. Returns None rather than a plausible wrong number for an id past the end of the font.

Layout

The direction a laid-out line runs.

WritingMode::is_vertical
pub fn is_vertical(self) -> bool

True when this writing mode runs top to bottom.

WritingMode::Horizontal is false; VerticalRl and VerticalLr are both true. The two vertical modes differ in which way successive lines advance – right to left is traditional for Japanese and Chinese, left to right is used for some Mongolian settings – but both run their glyphs downward, which is what this asks about.

Line breaking

Where a paragraph may break, and how a chosen break behaves.

Breakpoint::is_forced
pub fn is_forced(&self) -> bool

True when this breakpoint is a break that must be taken rather than one that may be.

Forced breaks come from newlines and paragraph separators. An optimal-fit search has to honour them exactly, while ordinary opportunities are weighed against each other.

Breakpoint::start
pub fn start() -> Breakpoint

The breakpoint at the beginning of a paragraph, which is where a break search starts.

A Breakpoint marks one candidate break in an optimal-fit search. This constructs the starting one – position zero, not forced. You need it only when running the break search yourself rather than through Font::layout.

Metrics

What the font says about itself: its names, its axes, its vertical metrics. Every measurement here comes back on a 1000-unit em, whatever the font's own units are, so a value in pixels is value * px / 1000.0. Outlines are the exception and stay in font units – upm reports those.

Font::ascender
pub fn ascender(&self) -> i32

The ascender on the 1000-unit em: how far the font rises above the baseline.

Resolved the way a text engine should resolve it, respecting the font's own preference between its typographic and Windows metrics. For laying out lines use line_metrics, which gives ascent, descent and line gap together and already agrees with this value.

let px = 16.0;
let ascent_px = font.ascender() as f64 * px / 1000.0;
Font::axes
pub fn axes(&self) -> Vec<FvarAxis>

The variation axes, each with its tag, its minimum, default and maximum, and its name.

Tags are the usual four-character OpenType ones: wght for weight, wdth for width, opsz for optical size, slnt for slant, ital for italic, plus any custom axis the designer invented. The range given here is in user coordinates, which is what every daegun call that takes axes expects. Empty for a static font.

for a in font.axes() {
    println!("{} [{}..{}] default {}", a.tag, a.min, a.max, a.default);
}
// then use a tag from that list
let bold = font.shape("Wave", &[("wght", 700.0)], false);
Font::bbox
pub fn bbox(&self) -> Vec<i32>

The font's overall bounding box as [xmin, ymin, xmax, ymax] on the 1000-unit em.

The box that contains every glyph in the font, read from head. Useful for sizing a buffer that must hold any glyph, though it is usually far larger than any single glyph needs. Returns four zeros if head could not be read.

let bbox = font.bbox();                  // [xmin, ymin, xmax, ymax]
let (xmin, ymin) = (bbox[0], bbox[1]);
let (xmax, ymax) = (bbox[2], bbox[3]);
Font::cap_height
pub fn cap_height(&self) -> i32

The height of a flat capital letter on the 1000-unit em.

Taken from OS/2 sCapHeight. When the font does not carry that value this returns the ascender instead, which is larger than a real cap height, so treat it as a best effort rather than a measurement. If you need certainty, measure a capital directly with glyph_bounds, which is on the same 1000-unit em and so compares directly.

// measure a real capital instead; also on the 1000-unit em
let h = font.glyph_id(u32::from('H'))
    .and_then(|g| font.glyph_bounds(g, &[]))
    .map(|(_, ymin, _, ymax)| ymax - ymin);
Font::descender
pub fn descender(&self) -> i32

The descender on the 1000-unit em, as a negative number, measuring how far the font drops below the baseline.

Negative because it is a coordinate below the baseline, not a distance. Line height is therefore ascender - descender + line_gap, not a sum of all three.

let px = 16.0;
let m = font.line_metrics(false);
let line_height = (m.ascent - m.descent + m.line_gap) * px / 1000.0;
Font::family_name
pub fn family_name(&self) -> Option<String>

The font family name, or None if the font carries no usable name table.

Read from the name table, preferring the English entry. This is the family, not the full name, so a bold italic face still answers with the family it belongs to. Use name_string if you want a specific name id such as the full name or the PostScript name.

println!("{:?}", font.family_name());   // Some("Inter")
Font::flags
pub fn flags(&self) -> u32

PDF font descriptor flags for this font, ready to write into a /Flags entry.

A bit field in the shape PDF defines: bit 0 fixed pitch, bit 1 serif, bit 3 script, bit 5 nonsymbolic, bit 6 italic. Bit 5 is always set, since daegun treats text fonts as nonsymbolic. Serif and script are inferred from the OS/2 family class, and italic from the italic angle. It exists so a PDF writer does not have to work any of this out.

let flags = font.flags();
let is_monospaced = flags & 1 != 0;
Font::instance
pub fn instance(&self, axes: &[(&str, f64)]) -> Vec<u8>

Produces a complete static font file for one position in the variation space.

Returns the bytes of a real font, with the variations applied and baked in, which you can write to disk or hand to any other consumer. This is how you turn one variable file into the fixed instance a system that does not understand variations can use. The result is cached, so asking twice for the same position is cheap.

let semibold = font.instance(&[("wght", 600.0)]);
std::fs::write("Inter-SemiBold.ttf", &semibold)?;
Font::is_variable
pub fn is_variable(&self) -> bool

True when the font has variation axes, meaning it carries an fvar table.

A variable font holds a continuous design space rather than one fixed design. When this is true, every call that takes axes can move the font within that space; when it is false, passing axes is harmless and simply has no effect.

if font.is_variable() {
    for axis in font.axes() {
        println!("{}: {} to {}", axis.tag, axis.min, axis.max);
    }
}
Font::italic_angle
pub fn italic_angle(&self) -> f64

The italic angle in degrees from the post table, counter-clockwise from vertical.

Negative for the usual forward slant, so an italic typically reports something near -12. Zero means upright, and is also what you get from a font with no post table. Unlike Os2Info::is_italic this is a measurement rather than a declaration, which makes it the better test when a font sets its bits carelessly.

if font.italic_angle() != 0.0 {
    println!("slanted {} degrees", -font.italic_angle());
}
Font::line_metrics
pub fn line_metrics(&self, vertical: bool) -> LineMetrics

Ascent, descent and line gap together, on the 1000-unit em, for horizontal or vertical text.

This is the call to use when setting lines. It resolves the disagreement between the font's typographic and Windows metrics according to the font's own USE_TYPO_METRICS flag, so you do not have to. Pass true for vertical text and it reads vhea instead, falling back to half the em above and below the baseline for a font with no vertical metrics at all. Descent is negative, so line height is ascent - descent + line_gap.

let px = 16.0;
let m = font.line_metrics(false);
let line_height_px = (m.ascent - m.descent + m.line_gap) * px / 1000.0;
Font::name_string
pub fn name_string(&self, name_id: u16) -> Option<String>

One string from the name table by its id, or None if the font does not carry it.

Cheaper than names when you want a single entry. Id 6 is the PostScript name, which is the one to use when a PDF or a system API asks a font to identify itself.

let postscript = font.name_string(6);   // Some("Inter-Regular")
let license = font.name_string(13);
Font::named_instances
pub fn named_instances(&self) -> Vec<NamedInstance>

The named positions the designer defined in the variation space, such as "Bold" or "Condensed Light".

Each instance carries a name, a postscript_name – both optional – and coords, one (tag, value) pair per axis. These are the positions the designer considered worth naming, and they are what a font menu should offer – the axes themselves are continuous, but users pick from this list. Empty for a static font.

for inst in font.named_instances() {
    // name and postscript_name are Option<String>; coords is Vec<(String, f64)>
    println!("{:?} at {:?}", inst.name, inst.coords);
}
Font::names
pub fn names(&self) -> alloc::collections::BTreeMap<u16, String>

Every string in the name table, keyed by its name id.

The whole table at once, for when you want to inspect rather than look one up. Familiar ids: 1 family, 2 subfamily, 4 full name, 6 PostScript name, 8 manufacturer, 13 license, 14 license URL. Ordered by id, since it is a BTreeMap.

for (id, s) in font.names() {
    println!("{id}: {s}");
}
Font::normalized_axes
pub fn normalized_axes(&self, axes: &[(&str, f64)]) -> Vec<f64>

Converts user-space axis values into the normalized -1 to 1 coordinates the font's variation tables use.

Every variable font maps its user-facing ranges – wght 100 to 900, say – onto -1, 0, 1 through its avar table if it has one. You almost never need this, because every daegun call that takes axes does the conversion itself. It is here for inspecting a font's variation behavior, or for driving the raw table calls directly.

let loc = font.normalized_axes(&[("wght", 700.0)]);
// e.g. [0.52] rather than [700.0]
Font::num_glyphs
pub fn num_glyphs(&self) -> u16

How many glyphs the font contains, so valid glyph ids run from 0 to this minus one.

Glyph 0 is .notdef by convention, the box or blank drawn for a character the font cannot render. Returns 0 for a font whose maxp table could not be read.

for gid in 1..font.num_glyphs() {
    // ...
}
Font::os2_info
pub fn os2_info(&self) -> Option<Os2Info>

The OS/2 table as a struct, or None if the font has no OS/2 table.

Carries the version, the family class, the raw fsSelection bits and both sets of vertical metrics. The style predicates – is_italic, is_bold, is_regular, is_oblique, uses_typo_metrics – are methods on the struct this returns rather than on Font. CFF fonts converted from older formats sometimes lack the table entirely, hence the Option.

if let Some(os2) = font.os2_info() {
    println!("bold: {}, italic: {}", os2.is_bold(), os2.is_italic());
}
Font::style
pub fn style(&self) -> &'static str

Either "italic" or "normal", decided from the OS/2 and head italic bits.

A deliberately narrow answer covering the case CSS cares about. It reports italic when either the OS/2 fsSelection italic bit or the head macStyle italic bit is set, so a font that sets only one of the two is still caught. For weight, obliqueness or anything finer, read os2_info.

Font::tracking
pub fn tracking(&self, ptem: f64, horizontal: bool) -> f64

The tracking adjustment the font asks for at a given point size, on the 1000-unit em.

Read from Apple's trak table, which lets a designer specify letter spacing that changes with size – tighter for display, looser for captions. ptem is the point size and horizontal picks the horizontal or vertical track. Returns 0.0 for the great majority of fonts, which carry no trak table.

let extra = font.tracking(11.0, true);   // per glyph, on the 1000-unit em
Font::typographic_metrics
pub fn typographic_metrics(&self, axes: &[(&str, f64)]) -> Option<TypographicMetrics>

The font's typographic ascender, descender and line gap at a given axis position, or None without an OS/2 table.

These are the sTypo values specifically, unresolved – it does not consult the USE_TYPO_METRICS flag or fall back to the Windows metrics. Because it takes axes, it reflects any MVAR variation of those metrics, which line_metrics at the default position would not show. Use line_metrics unless you specifically need the typographic set.

let at_bold = font.typographic_metrics(&[("wght", 700.0)]);
Font::upm
pub fn upm(&self) -> u16

Units per em: the size of the design grid the font's outlines are drawn on.

Typically 1000 for CFF fonts and 1024 or 2048 for TrueType. This is the one number that does not follow the 1000-unit rule. Outlines are the only thing daegun hands back in these units: the points that reach an OutlinePen through outline_glyph, and the paths inside a GpuBatch. Converting those to pixels is value * px / upm. Everything else – advances, vertical metrics, and glyph_bounds – is already normalized to 1000, so it uses value * px / 1000.0 instead. Mixing the two up is the most common mistake against this API, and it is why both conventions are stated everywhere they apply.

let px = 32.0;
let outline_scale = px / font.upm() as f64;   // outlines are in font units
let advance_scale = px / 1000.0;              // advances are on the 1000-unit em

Rasterizing

Turning a glyph into pixels, or into an outline you walk yourself. The rasterizer returns coverage: one byte per pixel saying how much of it the glyph covers, which you then tint.

Font::cff_hints
pub fn cff_hints(&self, gid: u16) -> Option<crate::daecore::daetype::outline::CffHints>

The hint data a CFF glyph carries – its stem positions and any hint replacement – or None for a non-CFF font.

CFF fonts declare their stems declaratively rather than running bytecode. This exposes those declarations for inspection or for a rasterizer of your own. Returns None for TrueType outlines, which hint through instructions instead.

Font::clear_prewarm
pub fn clear_prewarm(&self)

Drops every cached outline, leaving the rasterized glyph cache alone.

The counterpart to prewarm. Use it when you have moved to a different axis position and the outlines you cached are no longer the ones you need. clear_glyph_cache clears the other cache – the rasterized bitmaps – and the two are independent.

Font::hinted_glyph
pub fn hinted_glyph(
    &self,
    gid: u16,
    px: f32,
    axes: &[(&str, f64)],
    mode: HintMode,
) -> Option<crate::daecore::daetype::hinting::HintedOutline>

Returns a glyph's outline after hinting has moved it onto the pixel grid, without rasterizing it.

Hinting is normally invisible – it happens inside rasterize_glyph_with. This exposes the result so you can inspect it or rasterize it yourself. HintMode::Font runs the font's own instructions, HintMode::Auto runs daegun's autohinter, and HintMode::None returns the outline unchanged. The result is in pixel space at the size you asked for, not in font units, because grid fitting is only meaningful at a size.

Font::outline_glyph
pub fn outline_glyph(&self, gid: u16, pen: &mut dyn crate::daecore::daetype::outline::OutlinePen) -> Option<()>

Walks a glyph's outline into any OutlinePen, in font units, at the default axis position.

Rather than returning a path, it calls your pen: move_to, line_to, quad_to, curve_to, close. That keeps daegun out of the business of owning a path type you then have to convert. Points are in font units, not on the 1000-unit em, so scale by px / font.upm(). Returns None if the glyph does not exist; a glyph with no ink simply produces no calls.

use daegun::{OutlinePen, Path};

// Path implements OutlinePen, so it works as a collector
let mut path = Path::default();
font.outline_glyph(gid, &mut path).expect("glyph exists");

let scale = 32.0 / font.upm() as f64;   // font units, not the 1000-unit em
Font::outline_glyph_instanced
pub fn outline_glyph_instanced(&self, gid: u16, axes: &[(&str, f64)], pen: &mut dyn crate::daecore::daetype::outline::OutlinePen) -> Option<()>

The same as outline_glyph, at a position in the variation space.

The one to use with a variable font, since it applies the axis values before handing points to your pen. Points are in font units here too.

let mut path = Path::default();
font.outline_glyph_instanced(gid, &[("wght", 700.0)], &mut path);
Font::prewarm
pub fn prewarm(&self, gids: impl IntoIterator<Item = u16>, axes: &[(&str, f64)]) -> usize

Loads and caches the outlines for a set of glyphs ahead of time, returning how many were newly added.

Parsing and instancing an outline is the expensive part of drawing a glyph for the first time. Doing that work up front – during a load screen, or on a background thread – keeps it off the frame that needs the glyph. It caches outlines, not rasterized bitmaps, so it helps at every size rather than one. Glyphs already cached are skipped, hence the count.

// warm the Latin range before the first frame
let ascii: Vec<u16> = (0x20..0x7Fu32)
    .filter_map(|c| font.glyph_id(c))
    .collect();
let added = font.prewarm(ascii, &[]);
Font::rasterize_glyph
pub fn rasterize_glyph(&self, gid: u16, px: f32, axes: &[(&str, f64)]) -> Option<RasterizedGlyph>

Rasterizes one glyph at a pixel size and returns its coverage bitmap, or None if the glyph has no ink.

The bitmap is one byte per pixel, 0 to 255, saying how much of that pixel the glyph covers – it is a mask, not a color. You tint it: pixel = text_color, alpha = coverage. metrics.width and metrics.height give the bitmap's size, and metrics.xmin and metrics.ymin give its position relative to the pen, in pixels, with y up. Rows run top to bottom. A space returns None because there is nothing to draw, which is not an error. Results are cached, so drawing the same glyph at the same size twice costs once.

let g = font.rasterize_glyph(gid, 32.0, &[]).expect("has ink");

for y in 0..g.metrics.height {
    for x in 0..g.metrics.width {
        let coverage = g.bitmap[y * g.metrics.width + x];
        // blend text_color over the background with alpha = coverage
    }
}
// where it goes, relative to the pen
let left = g.metrics.xmin;
let top = -(g.metrics.ymin + g.metrics.height as i32);
Font::rasterize_glyph_with
pub fn rasterize_glyph_with(
    &self,
    gid: u16,
    px: f32,
    axes: &[(&str, f64)],
    opts: &RasterOptions,
) -> Option<RasterizedGlyph>

The same as rasterize_glyph, with a RasterOptions controlling hinting, gamma, transform, stroke and weight.

Everything rasterize_glyph does plus the switches. RasterOptions is a builder, so you name only what you change. Subpixel layouts make the bitmap three bytes per pixel instead of one, so read metrics.width rather than assuming.

use daegun::{HintMode, RasterOptions};

let opts = RasterOptions::default()
    .with_hinting(HintMode::Auto)
    .with_gamma(1.8);
let g = font.rasterize_glyph_with(gid, 13.0, &[], &opts).expect("has ink");

Raw tables

The font's tables as bytes, for when you need something daegun does not model.

Font::has_table
pub fn has_table(&self, tag: &str) -> bool

True when the font contains a table with the given tag.

Cheaper than table when you only want to know whether something is there – testing for "glyf" against "CFF " to tell TrueType outlines from CFF ones, for instance.

let is_truetype = font.has_table("glyf");
let is_variable = font.has_table("fvar");
Font::instance_tables
pub fn instance_tables(
    &self,
    axes: &[(&str, f64)],
) -> Option<alloc::collections::BTreeMap<String, alloc::borrow::Cow<'_, [u8]>>>

Every table with variations applied at a given axis position, or None if the font is not variable.

Where Font::instance builds a whole font file, this hands you the tables individually, borrowed where a table is unchanged and owned where instancing rewrote it – hence the Cow. It is the building block for writing your own font container, and it avoids the cost of assembling a file you are only going to take apart.

Font::table
pub fn table(&self, tag: &str) -> Option<&[u8]>

One OpenType table's raw bytes by its four-character tag, or None if the font has no such table.

An escape hatch for anything daegun does not model. The slice borrows from the font and stays valid as long as it does. Tags are the usual ones: "glyf", "CFF " (note the trailing space), "GSUB", "name". Bytes are big-endian, as OpenType stores them.

if let Some(head) = font.table("head") {
    let upm = u16::from_be_bytes([head[18], head[19]]);
}
Font::table_tags
pub fn table_tags(&self) -> Vec<&str>

The tags of every table the font contains.

What is actually present, which is the quickest way to see what kind of font you have.

println!("{:?}", font.table_tags());
// ["CFF ", "GSUB", "OS/2", "cmap", "head", ...]

Shaping and text

Turning a string into positioned glyphs. Shaping applies the font's own rules – ligatures, kerning, joining, reordering – so the result is what the typeface was designed to produce, not a character-by-character transcription.

Font::justification_extenders
pub fn justification_extenders(&self, script_tag: &str) -> Vec<u16>

The glyphs a script uses to stretch a line, such as the Arabic kashida.

Given a script tag such as "arab", returns the glyph ids the font nominates for elongation. Empty for scripts that do not justify this way, and for fonts that do not say. You need this only when implementing justification yourself rather than calling justify.

Font::justify
pub fn justify(
    &self,
    text: &str,
    axes: &[(&str, f64)],
    vertical: bool,
    opts: &JustifyOptions,
) -> Option<Justified>

Shapes a string and fits it to a target width using the font's own justification rules.

The call to use for justified text. JustifyOptions names the script, the target width and the tolerance, and has no Default – set all four fields. The result reports what was achieved along with the shaped run. It prefers the font's designed justification – kashida elongation for Arabic, alternate forms where the font provides them – and falls back to spacing adjustments. Widths are on the 1000-unit em like every other measurement, so divide a pixel target by px / 1000.0 on the way in.

use daegun::JustifyOptions;

let px = 16.0;
let scale = px / 1000.0;

// every field is required: there is no Default for this one
let opts = JustifyOptions {
    script_tag: "latn",
    lang_sys_tag: None,
    target_width: 320.0 / scale,   // a 320px measure, on the 1000-unit em
    tolerance: 0.05,
};
let fitted = font.justify(line, &[], false, &opts).expect("justifies");
println!("reached {} of {}", fitted.width, opts.target_width);
Font::layout
pub fn layout(&self, text: &str, axes: &[(&str, f64)], opts: &LayoutOptions) -> Option<TextLayout>

Shapes, breaks and positions a whole paragraph into lines in one call.

The complete paragraph path: it shapes the text, finds the break opportunities, chooses where to break, and positions each line according to the alignment you ask for. LayoutOptions carries the measure, the line height, the break strategy and the alignment. Every size in and out is on the 1000-unit em, so a pixel measure goes in as px_value / (px / 1000.0) and comes back the same way. BreakStrategy::Greedy takes the last break that fits on each line; Optimal searches the whole paragraph for the set of breaks with the least total raggedness, which is slower and looks considerably better.

use daegun::{Align, BreakStrategy, LayoutOptions};

let px = 16.0;
let scale = px / 1000.0;
let layout = font.layout(text, &[], &LayoutOptions {
    max_inline_size: 320.0 / scale,
    line_height: Some(24.0 / scale),
    strategy: BreakStrategy::Optimal,
    align: Align::Start,
    ..LayoutOptions::default()
}).expect("lays out");

for line in &layout.lines {
    for run in &line.runs {
        let (ox, oy) = run.offset;          // a tuple, both on the 1000-unit em
        let (x, y) = (ox * scale, oy * scale);
        // run.run.glyphs and run.run.advances, drawn from (x, y)
    }
}
Font::measure_width
pub fn measure_width(&self, text: &str, axes: &[(&str, f64)], font_size: f64) -> f64

The width of a string in points at a given font size, without producing any glyphs.

Note the units: unlike almost everything else here, this takes a point size and answers in the same units, because it exists for measuring rather than drawing. It still shapes the text, so the answer accounts for ligatures and kerning. If you are about to draw the text anyway, shape it once and sum the advances instead of calling this as well.

let w = font.measure_width("Hello", &[], 16.0);   // in points at 16pt
Font::shape
pub fn shape(&self, text: &str, axes: &[(&str, f64)], vertical: bool) -> Option<ShapedRun>

Shapes a string into positioned glyphs, applying the font's own ligature, kerning and reordering rules.

The call most work starts from. It picks the script, runs the right shaper for it, applies GSUB and GPOS (or Apple's morx and kerx), and gives back a ShapedRun whose glyphs, advances and offsets are index-aligned. Advances are on the 1000-unit em, so a pen position in pixels is advance * px / 1000.0. Pass true for vertical to set the text top to bottom. Returns None only when the text cannot be shaped at all, which for a valid font means an empty string – a character the font lacks yields .notdef rather than a failure. The number of glyphs is not the number of characters: ligatures reduce it, decomposition and reordering can raise it, and clusters maps each glyph back to the byte it came from.

let px = 32.0;
let run = font.shape("Waffle", &[], false).expect("shapes");

let mut x = 0.0;
for (i, &gid) in run.glyphs.iter().enumerate() {
    let (dx, dy) = run.offsets[i];
    let pen_x = (x + dx) * px / 1000.0;
    let pen_y = dy * px / 1000.0;
    // draw gid at (pen_x, pen_y)
    x += run.advances[i];
}
println!("{} chars became {} glyphs via the {} shaper",
         "Waffle".chars().count(), run.glyphs.len(), run.shaper);
Font::shape_bidi
pub fn shape_bidi(&self, text: &str, axes: &[(&str, f64)], base: Option<bool>) -> Option<Vec<BidiRun>>

Resolves mixed-direction text and shapes each run, returning them in visual order.

Text that mixes Arabic or Hebrew with Latin has to be split into runs of a single direction before any of it can be shaped, and those runs then have to be reordered for display. This does both, applying the Unicode bidirectional algorithm and returning a BidiRun per piece carrying the shaped glyphs, the embedding level, and the character indices it covers. Even levels run left to right, odd levels right to left. Pass None for base to detect the paragraph direction from the first strong character, or Some(true) to force right-to-left.

let runs = font.shape_bidi("hello שלום world", &[], None).expect("shapes");

let mut x = 0.0;
for r in &runs {
    let rtl = r.level % 2 == 1;
    // runs already come back in the order they should be drawn
    x += r.run.advances.iter().sum::<f64>();
}
Font::shape_bidi_with
pub fn shape_bidi_with(
    &self,
    text: &str,
    axes: &[(&str, f64)],
    base: Option<bool>,
    opts: &ShapeOptions,
) -> Option<Vec<BidiRun>>

The same as shape_bidi, with the full ShapeOptions applied to every run.

Use it when mixed-direction text also needs specific features, a language tag, or the surrounding context that before and after supply.

Font::shape_justified
pub fn shape_justified(&self, text: &str, axes: &[(&str, f64)], vertical: bool, mods: &JstfModLists, shrink: bool) -> Option<ShapedRun>

Shapes a string and applies the font's justification rules to stretch or shrink it.

Rather than adding space between words, this uses what the font itself offers for justification – elongating Arabic kashida, substituting wider or narrower forms – as described by its JSTF table. mods selects which of those the font should apply and shrink chooses tightening instead of stretching. justify is the higher-level call that takes a target width; this one takes the modifications directly.

Font::shape_with_features
pub fn shape_with_features(&self, text: &str, axes: &[(&str, f64)], vertical: bool, script: Option<&str>, features: &[(&str, u32)]) -> Option<ShapedRun>

Shapes a string with specific OpenType features switched on or off, and optionally a forced script.

Features are (tag, value) pairs, where 0 disables and any non-zero value enables – for a feature that selects among alternates, the value picks which one. Common tags: liga standard ligatures, dlig discretionary ligatures, kern kerning, smcp small caps, onum old-style figures, tnum tabular figures, ss01 and up for stylistic sets. Passing script overrides detection, which you want only when you know better than the text does.

// tabular figures for a column of numbers, ligatures off
let run = font.shape_with_features(
    "1,204.50", &[], false, None,
    &[("tnum", 1), ("liga", 0)],
).expect("shapes");
Font::shape_with_language
pub fn shape_with_language(&self, text: &str, axes: &[(&str, f64)], vertical: bool, language: &str) -> Option<ShapedRun>

Shapes a string with an explicit language tag, so language-specific forms are applied.

Some fonts draw differently depending on language even within one script. Serbian Cyrillic wants different italic forms from Russian, Polish wants a taller kreska on its accents, and Turkish needs the dotless i handled correctly. The tag is a BCP 47 code such as "sr" or "tr". Without it the font's default rules apply, which is right for most text.

let sr = font.shape_with_language("бити", &[], false, "sr");
Font::shape_with_options
pub fn shape_with_options(
    &self,
    text: &str,
    axes: &[(&str, f64)],
    vertical: bool,
    opts: &ShapeOptions,
) -> Option<ShapedRun>

Shapes a string with the full ShapeOptions struct, for everything the simpler calls do not expose.

The complete surface: cluster level, features, script and language, the text before and after this run so joining behaves across a boundary, point size for optical sizing, how to treat default-ignorable characters, whether to suppress the dotted circle drawn for an isolated mark, and which glyph to use for invisible characters. before and after matter when you shape a paragraph in pieces – an Arabic letter joins differently depending on what sits beside it, and without them a split run draws the wrong forms at the seam.

use daegun::ShapeOptions;

// shaping the middle of a line, so joining is correct at both ends
let opts = ShapeOptions {
    before: "الس",
    after: "لام",
    features: &[("liga", 1)],
    ..ShapeOptions::default()
};
let run = font.shape_with_options("ـ", &[], false, &opts);

Tables and subsetting

Cutting a font down to the glyphs a page actually uses, and reading the OpenType tables that describe math, baselines and language support.

Font::base_info
pub fn base_info(&self, script_tag: &str, vertical: bool) -> Option<BaseScriptInfo>

Baseline positions for a script, or None if the font does not describe that script.

Scripts sit on different baselines: Latin on the alphabetic baseline, Devanagari hanging from a line above, CJK centered on an ideographic baseline. Mixing them on one line requires knowing where each wants to sit, and the BASE table says. vertical asks about vertical text instead.

let latin = font.base_info("latn", false);
Font::base_is_glyph_free
pub fn base_is_glyph_free(&self) -> bool

True when the font's BASE table describes baselines without referring to specific glyphs.

A BASE table may define its baselines by pointing at glyphs, which makes it unusable after subsetting removes those glyphs. This tells you whether that dependency exists.

Font::feature_tags
pub fn feature_tags(&self, script: Option<&str>, language: Option<&str>) -> Vec<String>

The OpenType features available, optionally narrowed to one script and language.

What you can pass to shape_with_features and expect to have an effect. Passing None for both gives every feature in the font; naming a script and language gives the ones that apply there.

let available = font.feature_tags(Some("latn"), None);
if available.iter().any(|f| f == "smcp") {
    let run = font.shape_with_features("Title", &[], false, None, &[("smcp", 1)]);
}
Font::glyph_closure
pub fn glyph_closure(&self, gids: &[u16], axes: &[(&str, f64)]) -> Result<Vec<u16>, FontError>

Expands a set of glyphs to include every glyph the font's substitution rules could turn them into.

Starting from the ids you give, it follows GSUB to find everything reachable – ligatures those glyphs could form, alternates a feature could select, marks that could be substituted in. Subsetting to the closure rather than the bare set is what keeps a subset font from breaking when a feature fires. subset_text does this for you.

Font::justification_glyphs
pub fn justification_glyphs(&self, script_tag: &str) -> Option<Vec<u16>>

The glyphs a script nominates for justification, or None if the font does not say.

From the JSTF table. These are the glyphs that may be inserted or elongated to stretch a line – the Arabic kashida chiefly. Needed only when implementing justification yourself.

Font::justification_priorities
pub fn justification_priorities(&self, script_tag: &str, lang_sys_tag: Option<&str>) -> Option<Vec<JstfModLists>>

The ordered list of adjustments a font suggests for justifying a script, or None if it suggests none.

Fonts describe justification as a sequence of steps to try in order: elongate here first, then substitute wider forms, then adjust spacing. This returns that sequence so it can be applied in the intended order. justify uses it for you.

Font::language_tags
pub fn language_tags(&self, script: &str) -> Vec<String>

The languages a given script has specific rules for.

Within one script a font may treat some languages specially – Serbian within Cyrillic, Turkish within Latin. Tags are OpenType's own, such as "SRB " and "TRK ", which are not BCP 47 codes. shape_with_language takes BCP 47 and maps it for you.

Font::math_constants
pub fn math_constants(&self) -> Option<MathConstants>

The MATH table's layout constants, or None if the font is not a math font.

Math typesetting needs dozens of measurements the font supplies: how far to raise a superscript, how thick a fraction bar is, how much gap to leave above a radical. This returns them together. Only fonts built for mathematics – STIX, Latin Modern Math, Cambria Math – carry the table.

Font::math_glyph_variants
pub fn math_glyph_variants(&self, gid: u16, vertical: bool) -> Option<MathGlyphConstruction>

The larger or assembled forms of a glyph, for symbols that grow to fit their contents.

A parenthesis around a tall fraction has to grow. Fonts provide this in two ways: a series of progressively larger glyphs, and an assembly recipe that stacks pieces – a top, a repeating middle, a bottom. This returns both. vertical picks whether you are growing it vertically or horizontally.

Font::math_is_extended_shape
pub fn math_is_extended_shape(&self, gid: u16) -> bool

True when a glyph is tall enough that math layout should treat it as an extended shape.

Extended shapes – big integrals, tall brackets – take different superscript and subscript positioning from ordinary letters. The font flags them and this reports the flag.

Font::math_italics_correction
pub fn math_italics_correction(&self, gid: u16) -> Option<f64>

The italic correction for a glyph, or None if the font gives none.

A slanted glyph leans past its advance, so anything set immediately after it – a superscript especially – needs nudging right by this amount. Ignoring it is why naive math rendering collides subscripts with the italic letters before them. On the 1000-unit em.

Font::math_kern
pub fn math_kern(&self, gid: u16, corner: MathKernCorner, height: f64) -> f64

The math kerning at one corner of a glyph, at a given height above the baseline.

Math kerning varies with height, which ordinary kerning does not: how close a superscript can sit to a V depends on how high up it is. corner picks which of the four corners you are asking about, and height is where on the glyph. Both height and result are on the 1000-unit em.

use daegun::MathKernCorner;

let k = font.math_kern(gid, MathKernCorner::TopRight, 400.0);
Font::math_min_connector_overlap
pub fn math_min_connector_overlap(&self) -> Option<f64>

How much adjacent pieces of an assembled glyph must overlap, or None if the font does not say.

When stacking the pieces of a tall bracket, butting them exactly end to end leaves visible seams. The font states a minimum overlap and this returns it, on the 1000-unit em.

Font::math_top_accent_attachment
pub fn math_top_accent_attachment(&self, gid: u16) -> f64

Where an accent should be centered over a glyph, horizontally, on the 1000-unit em.

Centring an accent on the glyph's bounding box looks wrong over a slanted or asymmetric letter. The font names the correct point instead, and this returns it.

Font::script_tags
pub fn script_tags(&self) -> Vec<String>

Every script the font has layout rules for, as four-character OpenType tags.

Tags such as "latn", "arab", "deva", "hani". This is what the font has GSUB and GPOS rules for, which is not the same as what it has glyphs for – check has_glyph for coverage.

println!("{:?}", font.script_tags());   // ["DFLT", "arab", "latn"]
Font::stat_info
pub fn stat_info(&self) -> Option<StatInfo>

The STAT table, which describes how this font relates to the rest of its family.

STAT names the axes of the family and the value each face sits at, so a system can tell that three separate files are Light, Regular and Bold of one family and build a menu accordingly. None for a font without the table.

Font::subset
pub fn subset(&self, gids: &[u16], axes: &[(&str, f64)]) -> Result<SubsetResult, FontError>

Cuts the font down to a specific set of glyph ids, returning a complete font file.

Use this when you already know the glyphs – because you shaped the text yourself, or you are keeping a fixed set. It takes ids rather than text, so it cannot know about substitutions you have not already accounted for; run glyph_closure first if the set came from a plain cmap lookup. Prefer subset_text when you have the text.

let gids = font.glyph_closure(&base_gids, &[])?;   // pull in what substitution needs
let subset = font.subset(&gids, &[])?;
Font::subset_text
pub fn subset_text(&self, text: &str, axes: &[(&str, f64)]) -> Result<SubsetResult, FontError>

Cuts the font down to just what is needed to render the given text, returning a complete font file.

The call to reach for. It shapes the text first, then keeps the glyphs shaping actually produced – so ligatures, joined Arabic forms and reordered Indic clusters all survive, which a naive character-to-glyph subset would break. The result carries a real cmap, so it drops straight into @font-face with no further work. result.ttf is the font; result.gid_map maps old glyph ids to new ones if you need to follow them. The savings are large: a page of Japanese wanting 37 characters ships about 12 KB instead of 8 MB.

let subset = font.subset_text("Type is the voice of the page.", &[])?;
std::fs::write("subset.woff-source.ttf", &subset.ttf)?;
println!("{} bytes down to {}", bytes.len(), subset.ttf.len());

Type to search.