ab-glyph

repository·main·Indexed 19 days ago

https://github.com/alexheretic/ab-glyph

A collection of Rust crates for high-performance font manipulation and vector rasterization. It includes ab_glyph for loading, scaling, and positioning OpenType font glyphs, and ab_glyph_rasterizer for coverage rasterization of lines, quadratic beziers, and cubic beziers. The library supports no_std environments via the libm feature and provides tools for text layout, including kerning and word wrapping.

Tokens
10.1K
Snippets
43
Records
54
Agent score
65%

What's inside ab-glyph

  1. Overview of ab-glyph crates

    main

    The ab-glyph repository consists of two primary crates designed for font handling and vector graphics rasterization:

    1. ab_glyph: A fast API for loading, scaling, positioning, and rasterizing OpenType font glyphs.
    2. ab_glyph_rasterizer: A zero-dependency coverage rasterizer specifically for lines, quadratic beziers, and cubic beziers.

    All crates are maintained using the latest stable Rust compiler.

  2. Quickstart: Load and rasterize a glyph with ab_glyph

    main

    To use ab_glyph, you can load a font from a byte slice using FontRef::try_from_slice. You can then retrieve a specific glyph by its character, apply scaling and positioning using .with_scale_and_position(), and finally rasterize the outline using .outline_glyph(). The draw method on the resulting outline provides a closure to handle pixel coverage at specific coordinates.

    Note: This example requires the point function from ab_glyph to define positions.

    use ab_glyph::{FontRef, Font, Glyph, point};
    
    let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
    
    // Get a glyph for 'q' with a scale & position.
    let q_glyph: Glyph = font.glyph_id('q').with_scale_and_position(24.0, point(100.0, 0.0));
    
    // Draw it.
    if let Some(q) = font.outline_glyph(q_glyph) {
        q.draw(|x, y, c| { /* draw pixel `(x, y)` with coverage: `c` */ });
    }
  3. Configure ab_glyph for no_std environments

    main

    ab_glyph supports no_std environments. To use it in such environments, you must disable default features and enable the libm feature. This setup requires the alloc crate to be available.

    ab_glyph = { default-features = false, features = ["libm"] }
  4. Configure ab_glyph_rasterizer for no_std environments

    main

    ab_glyph_rasterizer supports no_std environments. To use it in such environments, you must disable default features and enable the libm feature. This requires the alloc crate to be available.

    ab_glyph_rasterizer = { default-features = false, features = ["libm"] }
  5. Rasterize lines and Bezier curves with ab_glyph_rasterizer

    main

    ab_glyph_rasterizer provides coverage rasterization for lines, quadratic beziers, and cubic beziers. It is primarily used for drawing glyphs from .otf fonts.

    To use it, initialize a Rasterizer with the target width and height, then use the drawing methods to define the shape. Finally, iterate over the resulting pixel alphas using for_each_pixel to process the output (e.g., saving to a buffer or rendering to a screen).

    let mut rasterizer = ab_glyph_rasterizer::Rasterizer::new(106, 183);
    
    // draw shapes using lines and cubic beziers
    // Note: point() is assumed to be a helper for creating coordinates
    rasterizer.draw_cubic(point(103.0, 163.5), point(86.25, 169.25), point(77.0, 165.0), point(82.25, 151.5));
    rasterizer.draw_line(point(102.0, 122.0), point(100.25, 111.25));
    // ... more drawing commands ...
    
    // iterate over the resultant pixel alphas, e.g. save pixel to a buffer
    rasterizer.for_each_pixel(|index, alpha| {
        // index is the pixel index, alpha is the coverage value
        // ...
    });
  6. The `Font` trait

    main

    The Font trait defines the core functionality required to access font data and glyph metrics. It provides access to unscaled metrics (in 'font units') and methods to convert these into scaled pixel values.

    Key Concepts

    • Units: Unscaled accessors return values in "font units", an arbitrary unit defined by the font. Use units_per_em() to determine the scale.
    • Scaling: ab_glyph uses a non-standard scale called PxScale, which represents the pixel height of the text.
    • Glyph Layout: The trait provides metrics necessary for layout, including ascent, descent, line gap, horizontal/vertical advances, and side bearings.

    To work with a font at a specific pixel size, use as_scaled(scale) or into_scaled(scale) to obtain a PxScaleFont wrapper.

    # use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
    # fn main() -> Result<(), ab_glyph::InvalidFont> {
    let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
    
    // Access unscaled metrics
    assert_eq!(font.descent_unscaled(), -201.0);
    
    // Access scaled metrics via PxScaleFont
    assert_eq!(font.as_scaled(24.0).descent(), -4.02);
    assert_eq!(font.as_scaled(50.0).descent(), -8.375);
    # Ok(())
  7. How FontRef and FontVec implement the Font trait

    main

    Both FontRef and FontVec implement the Font trait, providing a unified interface for accessing font metrics, glyphs, and outlines regardless of whether the data is borrowed or owned.

    Commonly used methods available on both include:

    • glyph_id(c: char) -> GlyphId: Returns the ID for a character.
    • outline(id: GlyphId) -> Option<Outline>: Returns the vector outline of a glyph.
    • units_per_em() -> Option<f32>: Returns the font's units per EM.
    • ascent_unscaled(), descent_unscaled(), line_gap_unscaled(): Returns vertical metrics.
    • h_advance_unscaled(id: GlyphId) -> f32: Returns horizontal advance.
    • glyph_raster_image2(id: GlyphId, size: u16) -> Option<v2::GlyphImage<'_>>: Returns a rasterized image of a glyph.
  8. Associate a scale with a font using ScaleFont

    main

    The ScaleFont trait allows you to work with a Font that has an associated PxScale. This is useful because it automatically converts unscaled font metrics (like advances, ascent, and descent) into pixel-scaled values.

    Commonly, you can use font.as_scaled(scale) to create a scaled font view. This provides access to:

    • height(): The pixel height (equal to scale.y).
    • h_advance(id): Pixel-scaled horizontal advance.
    • v_advance(id): Pixel-scaled vertical advance.
    • ascent() / descent(): Pixel-scaled ascent and descent.
    • scaled_glyph(c): A Glyph with the font's scale applied at position (0.0, 0.0).
    • kern(first, second): Pixel-scaled kerning for a pair of glyphs.

    Note: glyph_bounds and outline_glyph do not use the ScaleFont's internal scale, as the Glyph object itself carries its own scale.

    use ab_glyph::{FontRef, PxScale, ScaleFont};
    
    // Assuming font is a FontRef
    let font = FontRef::try_from_slice(include_bytes!("font.otf")).unwrap();
    
    // Associate the font with a scale of 45px
    let scaled_font = font.as_scaled(PxScale::from(45.0));
    
    assert_eq!(scaled_font.height(), 45.0);
    assert_eq!(scaled_font.h_advance(scaled_font.glyph_id('b')), 21.225);
    
    // Replace associated scale with another
    let scaled_font = scaled_font.with_scale(180.0);
    assert_eq!(scaled_font.height(), 180.0);
  9. Use FontArc for type-erased font management

    main

    FontArc is a wrapper around any type implementing the Font trait, stored within an Arc. It provides type erasure and cheap cloning, making it ideal for managing multiple font types (like FontVec or FontRef) in a single collection or passing them around easily. Because it uses an Arc, cloning a FontArc is a cheap operation that does not duplicate the underlying font data.

    use ab_glyph::{Font, FontArc};
    
    // FontArc can wrap a concrete Font implementation
    let font = FontArc::new(some_font_instance);
  10. Layout a paragraph of text using `layout_paragraph`

    main

    The layout_paragraph function provides a simple implementation for laying out glyphs into a target buffer. It handles basic text features including:

    • Vertical Positioning: Starts text at (position.x, position.y + font.ascent()).
    • Line Breaks: Resets the caret to a new line when encountering \n control characters.
    • Kerning: Applies font kerning between consecutive glyphs.
    • Word Wrapping: Performs basic wrapping by moving to a new line if a non-whitespace character exceeds the max_width.

    Parameters

    • font: A type implementing ScaleFont<F>.
    • position: The starting Point for the paragraph.
    • max_width: The maximum width allowed before wrapping occurs.
    • text: The string slice to be laid out.
    • target: A mutable reference to a Vec<Glyph> where the resulting glyphs and their calculated positions will be stored.
    use ab_glyph::{point, Font, Glyph, Point, ScaleFont};
    
    // Example usage signature
    pub fn layout_paragraph<F, SF>(
        font: SF,
        position: Point,
        max_width: f32,
        text: &str,
        target: &mut Vec<Glyph>,
    ) where
        F: Font,
        SF: ScaleFont<F,
    {
        // ... implementation
    }
  11. Use the Rasterizer to draw outlines and iterate over pixels

    main

    The Rasterizer is used to perform coverage rasterization for lines, quadratic beziers, and cubic beziers. This is particularly useful for drawing .otf font glyphs.

    To use it:

    1. Initialize a Rasterizer with a specific width and height.
    2. Use draw_line, draw_quad, or draw_cubic to draw outlines onto the rasterizer.
    3. Use for_each_pixel to iterate over the resulting pixel alpha values (e.g., to save them to a buffer).

    Note: You must activate either the std or libm feature in your configuration.

    use ab_glyph_rasterizer::Rasterizer;
    use ab_glyph_rasterizer::point;
    
    let (width, height) = (100, 100);
    let mut rasterizer = Rasterizer::new(width, height);
    
    // Define points using the point() helper
    let [l0, l1, q0, q1, q2, c0, c1, c2, c3] = [point(0.0, 0.0); 9];
    
    // Draw outlines
    rasterizer.draw_line(l0, l1);
    rasterizer.draw_quad(q0, q1, q2);
    rasterizer.draw_cubic(c0, c1, c2, c3);
    
    // Iterate over the resultant pixel alphas
    rasterizer.for_each_pixel(|index, alpha| {
        // index is the pixel index, alpha is the coverage value
        // e.g., save pixel to a buffer
    });
  12. Basic usage of ab_glyph for font rasterization

    main

    To use ab_glyph, you can load a font from a byte slice using FontRef::try_from_slice, retrieve a specific glyph by its character, apply scaling and positioning, and then draw its outline. The draw method on an outlined glyph provides a closure that receives the pixel coordinates (x, y) and the coverage value c for that pixel.

    Note: The example uses include_bytes! which is common for embedding fonts in Rust binaries.

    use ab_glyph::{point, Font, FontRef, Glyph};
    
    fn main() -> Result<(), ab_glyph::InvalidFont> {
        // Load font from bytes
        let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
    
        // Get a glyph for 'q' with a scale & position.
        let q_glyph: Glyph = font
            .glyph_id('q')
            .with_scale_and_position(24.0, point(100.0, 0.0));
    
        // Draw it.
        if let Some(q) = font.outline_glyph(q_glyph) {
            q.draw(|x, y, c| { /* draw pixel `(x, y)` with coverage: `c` */ });
        }
        Ok(()) 
    }