klyff 0.1.3

Text rendering library for games with MSDF support
Documentation
use crate::Color;

/// Styling of texts.
///
/// Note that styles that are not enabled in [`crate::Features`] are ignored.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct TextStyle {
    /// The text body's color.
    pub color: GlyphColoring,
    /// The outer stroke.
    pub stroke_out: Outline,
    /// The inner stroke.
    pub stroke_in: Outline,
    /// The outer glow. Rendered outside the outer stroke.
    pub glow_out: Glow,
    /// The outer glow. Rendered inside the inner stroke.
    pub glow_in: Glow,
    /// The text's shadow. Does not consider additional thickness from outer stroke or outer glow.
    pub shadow: Shadow,
}

impl TextStyle {
    /// Scale effect widths to fit within the MSDF atlas's representable range.
    ///
    /// The MSDF atlas can only represent distances in the range `-0.5em` to `0.5em`. If the total
    /// outward expansion (outer stroke + outer glow) or inward expansion (inner stroke + inner glow)
    /// exceeds this range for the given `em_size`, effect widths are scaled down proportionally.
    ///
    /// # Arguments
    /// * `em_size` - The em size of the glyph in screen pixels.
    ///
    /// # Returns
    /// A new `TextStyle` with widths scaled to fit within the representable range.
    pub fn clamped_to_msdf_range(self, em_size: f32) -> Self {
        let max_expansion = 0.5 * em_size;

        let total_out = self.stroke_out.width + self.glow_out.width;
        let total_in = self.stroke_in.width + self.glow_in.width;

        let scale_out = if total_out > max_expansion && total_out > 0.0 {
            max_expansion / total_out
        } else {
            1.0
        };

        let scale_in = if total_in > max_expansion && total_in > 0.0 {
            max_expansion / total_in
        } else {
            1.0
        };

        Self {
            color: self.color,
            stroke_out: Outline {
                width: self.stroke_out.width * scale_out,
                ..self.stroke_out
            },
            stroke_in: Outline {
                width: self.stroke_in.width * scale_in,
                ..self.stroke_in
            },
            glow_out: Glow {
                width: self.glow_out.width * scale_out,
                ..self.glow_out
            },
            glow_in: Glow {
                width: self.glow_in.width * scale_in,
                ..self.glow_in
            },
            shadow: Shadow {
                additional_width: self.shadow.additional_width * scale_out,
                ..self.shadow
            },
        }
    }
}

/// How a glyph is colored.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GlyphColoring {
    /// A single solid color.
    Solid(Color),
    /// A linear gradient between two colors.
    Gradient {
        /// The color at gradient position 0.
        primary: Color,
        /// The color at gradient position 1.
        secondary: Color,
        /// The gradient direction vector (normalized).
        direction: glam::Vec2,
    },
}
impl Default for GlyphColoring {
    fn default() -> Self {
        GlyphColoring::Solid(Color::WHITE)
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Outline {
    /// The outline's color.
    pub color: GlyphColoring,
    /// The width of the outline, in pixels.
    pub width: f32,
    /// Roundness of the shape when extruded.
    ///
    /// Implemented as blend factor between pseudo-sdf and true sdf. At 0, the pseudo-sdf is used
    /// which preserves corner, at 1 the true sdf is used which is has rounder color. Only
    /// points outside of the glyph body is relevant, since inside the glyph body, the true sdf
    /// pseudo-sdf is identical.
    pub roundness: f32,
}
impl Default for Outline {
    fn default() -> Self {
        Self {
            color: GlyphColoring::Solid(Color::TRANSPARENT),
            width: 0.0,
            roundness: 0.0,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Glow {
    /// The glow's color.
    pub color: GlyphColoring,
    /// The width of the glow, in pixels.
    pub width: f32,
    /// The spread of the glow, from 0.0 to 1.0.
    ///
    /// This divides the glow into two, at the width*spread point, the inner region is drawn with
    /// full alpha, and the outer region is drawn with alpha fading to 0.
    pub spread: f32,
    /// Roundness of the shape when extruded.
    ///
    /// Implemented as blend factor between pseudo-sdf and true sdf. At 0, the pseudo-sdf is used
    /// which preserves corner, at 1 the true sdf is used which is has rounder color. Only
    /// points outside of the glyph body is relevant, since inside the glyph body, the true sdf
    /// pseudo-sdf is identical.
    pub roundness: f32,
}
impl Default for Glow {
    fn default() -> Self {
        Self {
            color: GlyphColoring::Solid(Color::TRANSPARENT),
            width: 0.0,
            spread: 0.0,
            roundness: 0.0,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Shadow {
    /// The shadow's color.
    pub color: GlyphColoring,
    /// The direction of the shadow.
    pub direction: glam::Vec2,
    /// Expand the shadow by additional amount in pixels, compared to the main glyph body.
    pub additional_width: f32,
    /// The spread of the shadow, from 0.0 to 1.0, in the expanded region.
    ///
    /// This divides the glow into two, at the width*spread point, the inner region is drawn with
    /// full alpha, and the outer region is drawn with alpha fading to 0.
    pub spread: f32,
    /// Roundness of the shape when extruded.
    ///
    /// Implemented as blend factor between pseudo-sdf and true sdf. At 0, the pseudo-sdf is used
    /// which preserves corner, at 1 the true sdf is used which is has rounder color. Only
    /// points outside of the glyph body is relevant, since inside the glyph body, the true sdf
    /// pseudo-sdf is identical.
    pub roundness: f32,
}
impl Default for Shadow {
    fn default() -> Self {
        Self {
            color: GlyphColoring::Solid(Color::TRANSPARENT),
            direction: glam::Vec2::ZERO,
            additional_width: 0.0,
            spread: 0.0,
            roundness: 0.0,
        }
    }
}