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_boundariespub 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 pointsdaegun::line_break_opportunitiespub 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.
atis the byte offset andmandatorymarks 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::layoutuses 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_runspub 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_bidipub fn resolve_bidi(text: &str, base: Option<bool>) -> BidiParagraphRuns 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:
levelsgives each character its embedding level – even is left to right, odd is right to left – andvisual_ordergives the character indices in the order they should be drawn. PassNoneforbaseto detect the paragraph direction from the first strong character,Some(true)to force right to left.Font::shape_bididoes 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 overalldaegun::script_runspub 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_boundariespub 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_layerspub 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
Noneif 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; usecolr_layers_for_paletteto 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_palettepub 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_layersbut reading palettepalette_indexrather than 0. Use it to honour a font's dark-mode palette, whichpalette_infowill identify.Font::colr_v1_paintpub 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
Noneif 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_glyphif you just want pixels.Font::glyph_bitmappub 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
Noneif the font has no strike for it.Some color fonts – Apple's emoji among them – ship photographic bitmaps rather than outlines, in
sbixorCBDTtables.target_ppemis 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_countpub fn palette_count(&self) -> u16How 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_infopub 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_safeanddark_safe– so you can pick the palette matching your background rather than always taking 0.name_idpoints into thenametable when the designer named the palette, and isNonewhen 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_glyphpub 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
Noneif the glyph is not a color glyph.The call to use for emoji and other color fonts. Where
rasterize_glyphgives single-channel coverage you tint yourself, this returns aRenderedScenewhosergbafield is straight – not premultiplied – RGBA8,widthbyheight, ready to blit. It walks the COLR v1 paint graph, so gradients, transforms and compositing all resolve here.palette_indexselects 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::bitmappub fn bitmap(&self) -> Option<&RasterizedGlyph>The rasterized bitmap inside a
DrawnGlyph, orNonewhen the glyph did not go down a CPU path.Answers for the
CpuandReferencevariants and givesNonefor everything else, so it is the short way to get pixels when you do not care which of the two produced them.DrawTarget::cpu_onlypub fn cpu_only(batch: &'a mut GpuBatch) -> DrawTarget<'a>Creates a
DrawTargetthat 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_glyphpub fn draw_glyph( &self, target: &mut DrawTarget<'_>, gid: u16, px: f32, axes: &[(&str, f64)], opts: &RasterOptions, palette: Option<u16>, ) -> DrawnGlyphDraws one glyph through whichever path the policy selects, returning a
DrawnGlyphthat says what happened.The single entry point that hides the CPU/GPU decision. The result tells you which way it went:
CpuandReferencecarry a finished bitmap,Gpucarries a slot to instance,GpuColorcarries one slot per colored layer,Scenecarries finished RGBA pixels for a color glyph,Nothingmeans the glyph had no ink,BatchFullmeans the batch cannot take more, andRefusedexplains why the request could not be served.is_okcollapses that to a yes or no, andbitmapgets 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_okpub fn is_ok(&self) -> boolTrue unless the glyph was refused or the batch was full.
A quick check that the request was served. Note that
Nothingcounts as ok – a glyph with no ink, such as a space, was handled correctly and simply produced nothing to draw.DrawTarget::newpub fn new(batch: &'a mut GpuBatch, device: &'a DeviceProfile) -> DrawTarget<'a>Creates a
DrawTargetthat may use the GPU, given a batch to fill and a description of the device.The
DeviceProfiletells 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. Usecpu_onlyinstead when there is no device.use daegun::{DrawTarget, GpuBatch}; let mut batch = GpuBatch::new(); let mut target = DrawTarget::new(&mut batch, &device);DrawTarget::with_policypub 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.
Policycarries 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_cachepub 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_bytespub fn from_bytes(bytes: &[u8]) -> Result<Font, FontError>Opens a font from a byte slice and returns a
Fontyou can shape and rasterize with.The slice is parsed and the tables it needs are copied out, so the
Fontdoes 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 asErr(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 itFont::from_ttcpub fn from_ttc(bytes: &[u8], index: usize) -> Result<Font, FontError>Opens one font out of a TrueType Collection by its index.
A
.ttcor.otcpacks several faces into one file so they can share tables. This picks the face atindex, counting from zero, and gives you a normalFont. An index past the end returnsErr(FontError). Askttc_font_countfirst 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_vecpub fn from_vec(bytes: alloc::vec::Vec<u8>) -> Result<Font, FontError>Opens a font from a
Vec<u8>it takes ownership of, avoiding the copyfrom_bytesmakes.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_statspub 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_boldpub fn is_bold(&self) -> boolTrue when the font declares itself bold, from bit 5 of the OS/2
fsSelectionfield.A declaration rather than a weight. On a variable font this reflects the default instance, so a face you have moved along
wghtstill answers about where it started – readaxesfor the live value instead.Os2Info::is_italicpub fn is_italic(&self) -> boolTrue when the font declares itself italic, from bit 0 of the OS/2
fsSelectionfield.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_anglewhen you need to be sure. Note this is a method onOs2Info, whichFont::os2_inforeturns and which isNonefor 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_obliquepub fn is_oblique(&self) -> boolTrue when the font declares itself oblique, from bit 9 of the OS/2
fsSelectionfield.Oblique means slanted upright forms; italic means redrawn letterforms. Fonts distinguish them and so does this bit, though many faces set only
is_italicregardless of which they are.Os2Info::is_regularpub fn is_regular(&self) -> boolTrue 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_bytespub 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_countpub fn ttc_font_count(bytes: &[u8]) -> usizeReports 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
.ttfand 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_metricspub fn uses_typo_metrics(&self) -> boolTrue 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
sTypovalues are the intended ones for line spacing; when it is clear, theusWinvalues are what most software will have used.line_metricsalready 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_emboldenpub fn with_embolden(mut self, units: f32) -> RasterOptionsThickens 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
wghtaxis when the font has one, since a designer compensated for optical effects that this cannot. Units are the font’s own, so scale byupmif 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_gammapub fn with_gamma(mut self, gamma: f32) -> RasterOptionsSets 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_hintingpub fn with_hinting(mut self, hinting: HintMode) -> RasterOptionsChooses the hinting mode, returning the options for chaining.
Hinting nudges outlines onto the pixel grid so stems stay crisp at small sizes.
HintMode::Noneskips it, which is right for large text and for GPU rendering.HintMode::Fontruns the instructions the font ships – TrueType bytecode or CFF hints.HintMode::Autoruns 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_layoutpub fn with_layout(mut self, layout: SubpixelLayout) -> RasterOptionsSets 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_obliquepub fn with_oblique(mut self, tangent: f32) -> RasterOptionsShears 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_strokepub fn with_stroke(mut self, stroke: StrokeStyle) -> RasterOptionsStrokes the outline instead of filling it, returning the options for chaining.
The
StrokeStylecarries 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, usewith_embolden.use daegun::{RasterOptions, StrokeStyle}; let opts = RasterOptions::default().with_stroke(StrokeStyle::default());RasterOptions::with_transformpub fn with_transform(mut self, transform: [f32; 6]) -> RasterOptionsSets an affine transform applied to the outline before rasterizing, returning the options for chaining.
The array is
[a, b, c, d, e, f], mappingx′ = a·x + c·y + eandy′ = 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_glyphpub 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_indexpicks 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_glyphpub fn gpu_glyph(&self, batch: &mut GpuBatch, gid: u16, axes: &[(&str, f64)]) -> Result<GlyphSlot, GpuGlyphError>Adds a glyph's curves to a
GpuBatchand 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_widthspub 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
shapereturns may be fractional, since positioning can adjust them. For measuring text, use the advances fromshape: 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_positionspub 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.
Noneif 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 glyphFont::codepointspub fn codepoints(&self) -> Vec<u32>Every code point the font supports, without the glyph ids.
The same data as
coveragewhen you only care which characters are available.println!("{} characters supported", font.codepoints().len());Font::coveragepub fn coverage(&self) -> Vec<(u32, u16)>Every code point the font supports, paired with the glyph it maps to.
The whole
cmapflattened 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_originpub fn default_vertical_origin(&self) -> i32The 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_boundspub 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, orNonefor 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 returnsNonebecause 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_classpub fn glyph_class(&self, gid: u16) -> Option<GlyphClass>What kind of glyph this is – base, ligature, mark or component – or
Noneif the font does not classify it.From the
GDEFtable. 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.Nonemeans the font has noGDEFtable 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_idpub fn glyph_id(&self, codepoint: u32) -> Option<u16>The glyph id for one Unicode code point, or
Noneif the font has no glyph for it.Takes a
u32code point rather than achar, sou32::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_idspub fn glyph_ids(&self, text: &str) -> Vec<Option<u16>>Maps each character of a string to its glyph id, with
Nonewhere the font has no glyph.A straight per-character
cmaplookup, one entry perchar. 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 –shapeis 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_namepub fn glyph_name(&self, gid: u16) -> Option<String>The PostScript name of one glyph, such as
"A"or"uni4E00", orNoneif the font stores no names.From the
posttable for TrueType or the charset for CFF. Many fonts ship version 3post, which stores no names at all, soNoneis 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_namespub fn glyph_names(&self) -> Vec<Option<String>>The PostScript name of every glyph, indexed by glyph id, with
Nonewhere a name is missing.One pass over the whole font instead of
num_glyphsseparate lookups. The vector is alwaysnum_glyphslong, so it can be indexed by glyph id directly.let names = font.glyph_names(); println!("{:?}", names[gid as usize]);Font::has_glyphpub fn has_glyph(&self, codepoint: u32) -> boolTrue when the font can render the given code point.
The same lookup as
glyph_idwithout building the id. Handy for choosing a fallback font before committing to one.let fonts = [&inter, ¬o_jp]; let pick = fonts.iter().find(|f| f.has_glyph(u32::from('字')));Font::ligature_caretspub 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 noGDEFtable. For cursor positions across a whole string,caret_positionsis the call you want.let carets = font.ligature_carets(fi_gid, &[]); // e.g. [512.0]Font::mark_attachment_classpub fn mark_attachment_class(&self, gid: u16) -> u16The 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_idpub fn variation_glyph_id(&self, base: u32, selector: u32) -> Option<u16>Resolves a base character plus a variation selector to a glyph, or
Noneif 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
cmapsubtable that maps those pairs.shapealready 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_advancepub fn vertical_advance(&self, gid: u16, axes: &[(&str, f64)]) -> u32The 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
VORGandcmapwill 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_originpub 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
Noneif 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
VORGtable; TrueType fonts derive them. ReturnsNonerather than a plausible wrong number for an id past the end of the font.
Layout
The direction a laid-out line runs.
WritingMode::is_verticalpub fn is_vertical(self) -> boolTrue when this writing mode runs top to bottom.
WritingMode::Horizontalis false;VerticalRlandVerticalLrare 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_forcedpub fn is_forced(&self) -> boolTrue 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::startpub fn start() -> BreakpointThe breakpoint at the beginning of a paragraph, which is where a break search starts.
A
Breakpointmarks 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 throughFont::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::ascenderpub fn ascender(&self) -> i32The 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::axespub 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:
wghtfor weight,wdthfor width,opszfor optical size,slntfor slant,italfor 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::bboxpub 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 ifheadcould 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_heightpub fn cap_height(&self) -> i32The 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 withglyph_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::descenderpub fn descender(&self) -> i32The 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_namepub fn family_name(&self) -> Option<String>The font family name, or
Noneif the font carries no usable name table.Read from the
nametable, 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. Usename_stringif you want a specific name id such as the full name or the PostScript name.println!("{:?}", font.family_name()); // Some("Inter")Font::flagspub fn flags(&self) -> u32PDF font descriptor flags for this font, ready to write into a
/Flagsentry.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::instancepub 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_variablepub fn is_variable(&self) -> boolTrue when the font has variation axes, meaning it carries an
fvartable.A variable font holds a continuous design space rather than one fixed design. When this is true, every call that takes
axescan 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_anglepub fn italic_angle(&self) -> f64The italic angle in degrees from the
posttable, 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
posttable. UnlikeOs2Info::is_italicthis 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_metricspub fn line_metrics(&self, vertical: bool) -> LineMetricsAscent, 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_METRICSflag, so you do not have to. Passtruefor vertical text and it readsvheainstead, 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 isascent - 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_stringpub fn name_string(&self, name_id: u16) -> Option<String>One string from the
nametable by its id, orNoneif the font does not carry it.Cheaper than
nameswhen 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_instancespub 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, apostscript_name– both optional – andcoords, 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::namespub fn names(&self) -> alloc::collections::BTreeMap<u16, String>Every string in the
nametable, 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_axespub 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 –
wght100 to 900, say – onto -1, 0, 1 through itsavartable 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_glyphspub fn num_glyphs(&self) -> u16How many glyphs the font contains, so valid glyph ids run from 0 to this minus one.
Glyph 0 is
.notdefby convention, the box or blank drawn for a character the font cannot render. Returns 0 for a font whosemaxptable could not be read.for gid in 1..font.num_glyphs() { // ... }Font::os2_infopub fn os2_info(&self) -> Option<Os2Info>The OS/2 table as a struct, or
Noneif the font has no OS/2 table.Carries the version, the family class, the raw
fsSelectionbits 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 onFont. CFF fonts converted from older formats sometimes lack the table entirely, hence theOption.if let Some(os2) = font.os2_info() { println!("bold: {}, italic: {}", os2.is_bold(), os2.is_italic()); }Font::stylepub fn style(&self) -> &'static strEither
"italic"or"normal", decided from the OS/2 andheaditalic bits.A deliberately narrow answer covering the case CSS cares about. It reports italic when either the OS/2
fsSelectionitalic bit or theheadmacStyleitalic bit is set, so a font that sets only one of the two is still caught. For weight, obliqueness or anything finer, reados2_info.Font::trackingpub fn tracking(&self, ptem: f64, horizontal: bool) -> f64The tracking adjustment the font asks for at a given point size, on the 1000-unit em.
Read from Apple's
traktable, which lets a designer specify letter spacing that changes with size – tighter for display, looser for captions.ptemis the point size andhorizontalpicks the horizontal or vertical track. Returns 0.0 for the great majority of fonts, which carry notraktable.let extra = font.tracking(11.0, true); // per glyph, on the 1000-unit emFont::typographic_metricspub fn typographic_metrics(&self, axes: &[(&str, f64)]) -> Option<TypographicMetrics>The font's typographic ascender, descender and line gap at a given axis position, or
Nonewithout an OS/2 table.These are the
sTypovalues specifically, unresolved – it does not consult theUSE_TYPO_METRICSflag or fall back to the Windows metrics. Because it takes axes, it reflects anyMVARvariation of those metrics, whichline_metricsat the default position would not show. Useline_metricsunless you specifically need the typographic set.let at_bold = font.typographic_metrics(&[("wght", 700.0)]);Font::upmpub fn upm(&self) -> u16Units 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
OutlinePenthroughoutline_glyph, and the paths inside aGpuBatch. Converting those to pixels isvalue * px / upm. Everything else – advances, vertical metrics, andglyph_bounds– is already normalized to 1000, so it usesvalue * px / 1000.0instead. 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_hintspub 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
Nonefor 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
Nonefor TrueType outlines, which hint through instructions instead.Font::clear_prewarmpub 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_cacheclears the other cache – the rasterized bitmaps – and the two are independent.Font::hinted_glyphpub 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::Fontruns the font's own instructions,HintMode::Autoruns daegun's autohinter, andHintMode::Nonereturns 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_glyphpub 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 bypx / font.upm(). ReturnsNoneif 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 emFont::outline_glyph_instancedpub 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::prewarmpub fn prewarm(&self, gids: impl IntoIterator<Item = u16>, axes: &[(&str, f64)]) -> usizeLoads 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_glyphpub 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
Noneif 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.widthandmetrics.heightgive the bitmap's size, andmetrics.xminandmetrics.ymingive its position relative to the pen, in pixels, with y up. Rows run top to bottom. A space returnsNonebecause 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_withpub fn rasterize_glyph_with( &self, gid: u16, px: f32, axes: &[(&str, f64)], opts: &RasterOptions, ) -> Option<RasterizedGlyph>The same as
rasterize_glyph, with aRasterOptionscontrolling hinting, gamma, transform, stroke and weight.Everything
rasterize_glyphdoes plus the switches.RasterOptionsis a builder, so you name only what you change. Subpixel layouts make the bitmap three bytes per pixel instead of one, so readmetrics.widthrather 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_tablepub fn has_table(&self, tag: &str) -> boolTrue when the font contains a table with the given tag.
Cheaper than
tablewhen 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_tablespub 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
Noneif the font is not variable.Where
Font::instancebuilds a whole font file, this hands you the tables individually, borrowed where a table is unchanged and owned where instancing rewrote it – hence theCow. 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::tablepub fn table(&self, tag: &str) -> Option<&[u8]>One OpenType table's raw bytes by its four-character tag, or
Noneif 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_tagspub 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_extenderspub 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 callingjustify.Font::justifypub 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.
JustifyOptionsnames the script, the target width and the tolerance, and has noDefault– 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 bypx / 1000.0on 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::layoutpub 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.
LayoutOptionscarries 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 aspx_value / (px / 1000.0)and comes back the same way.BreakStrategy::Greedytakes the last break that fits on each line;Optimalsearches 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_widthpub fn measure_width(&self, text: &str, axes: &[(&str, f64)], font_size: f64) -> f64The 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 16ptFont::shapepub 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
GSUBandGPOS(or Apple'smorxandkerx), and gives back aShapedRunwhoseglyphs,advancesandoffsetsare index-aligned. Advances are on the 1000-unit em, so a pen position in pixels isadvance * px / 1000.0. Passtrueforverticalto set the text top to bottom. ReturnsNoneonly when the text cannot be shaped at all, which for a valid font means an empty string – a character the font lacks yields.notdefrather than a failure. The number of glyphs is not the number of characters: ligatures reduce it, decomposition and reordering can raise it, andclustersmaps 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_bidipub 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
BidiRunper 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. PassNoneforbaseto detect the paragraph direction from the first strong character, orSome(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_withpub 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 fullShapeOptionsapplied to every run.Use it when mixed-direction text also needs specific features, a language tag, or the surrounding context that
beforeandaftersupply.Font::shape_justifiedpub 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
JSTFtable.modsselects which of those the font should apply andshrinkchooses tightening instead of stretching.justifyis the higher-level call that takes a target width; this one takes the modifications directly.Font::shape_with_featurespub 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:ligastandard ligatures,dligdiscretionary ligatures,kernkerning,smcpsmall caps,onumold-style figures,tnumtabular figures,ss01and up for stylistic sets. Passingscriptoverrides 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_languagepub 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_optionspub fn shape_with_options( &self, text: &str, axes: &[(&str, f64)], vertical: bool, opts: &ShapeOptions, ) -> Option<ShapedRun>Shapes a string with the full
ShapeOptionsstruct, 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.
beforeandaftermatter 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_infopub fn base_info(&self, script_tag: &str, vertical: bool) -> Option<BaseScriptInfo>Baseline positions for a script, or
Noneif 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
BASEtable says.verticalasks about vertical text instead.let latin = font.base_info("latn", false);Font::base_is_glyph_freepub fn base_is_glyph_free(&self) -> boolTrue when the font's
BASEtable describes baselines without referring to specific glyphs.A
BASEtable 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_tagspub 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_featuresand expect to have an effect. PassingNonefor 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_closurepub 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
GSUBto 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_textdoes this for you.Font::justification_glyphspub fn justification_glyphs(&self, script_tag: &str) -> Option<Vec<u16>>The glyphs a script nominates for justification, or
Noneif the font does not say.From the
JSTFtable. 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_prioritiespub 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
Noneif 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.
justifyuses it for you.Font::language_tagspub 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_languagetakes BCP 47 and maps it for you.Font::math_constantspub fn math_constants(&self) -> Option<MathConstants>The
MATHtable's layout constants, orNoneif 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_variantspub 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.
verticalpicks whether you are growing it vertically or horizontally.Font::math_is_extended_shapepub fn math_is_extended_shape(&self, gid: u16) -> boolTrue 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_correctionpub fn math_italics_correction(&self, gid: u16) -> Option<f64>The italic correction for a glyph, or
Noneif 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_kernpub fn math_kern(&self, gid: u16, corner: MathKernCorner, height: f64) -> f64The 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.
cornerpicks which of the four corners you are asking about, andheightis 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_overlappub fn math_min_connector_overlap(&self) -> Option<f64>How much adjacent pieces of an assembled glyph must overlap, or
Noneif 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_attachmentpub fn math_top_accent_attachment(&self, gid: u16) -> f64Where 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_tagspub 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 hasGSUBandGPOSrules for, which is not the same as what it has glyphs for – checkhas_glyphfor coverage.println!("{:?}", font.script_tags()); // ["DFLT", "arab", "latn"]Font::stat_infopub fn stat_info(&self) -> Option<StatInfo>The
STATtable, 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.
Nonefor a font without the table.Font::subsetpub 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_closurefirst if the set came from a plaincmaplookup. Prefersubset_textwhen you have the text.let gids = font.glyph_closure(&base_gids, &[])?; // pull in what substitution needs let subset = font.subset(&gids, &[])?;Font::subset_textpub 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-facewith no further work.result.ttfis the font;result.gid_mapmaps 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());