kas-text 0.6.0

Text layout and font management
Documentation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! KAS Rich-Text library — text-display environment

use crate::fonts::{fonts, FontId, InvalidFontId};
use crate::Vec2;

/// Environment in which text is prepared for display
///
/// An `Environment` can be default-constructed (without line-wrapping).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Environment {
    /// Text direction
    ///
    /// By default, text direction (LTR or RTL) is automatically detected with
    /// full bi-directional text support (Unicode Technical Report #9).
    pub direction: Direction,
    /// Line wrapping
    ///
    /// By default, this is true and long text lines are wrapped based on the
    /// width bounds. If set to false, lines are not wrapped at the width
    /// boundary, but explicit line-breaks such as `\n` still result in new
    /// lines.
    pub wrap: bool,
    /// Alignment (`horiz`, `vert`)
    ///
    /// By default, horizontal alignment is left or right depending on the
    /// text direction (see [`Self::direction`]), and vertical alignment is
    /// to the top.
    pub align: (Align, Align),
    /// Default font
    ///
    /// This font is used unless a formatting token (see [`crate::format`])
    /// is used.
    pub font_id: FontId,
    /// Font size: pixels per Em
    ///
    /// This is a scaling factor used to convert font sizes, with units
    /// `pixels/Em`. Equivalently, this is the line-height in pixels.
    /// See [`crate::fonts`] documentation.
    ///
    /// To calculate this from text size in Points, use `dpem = dpp * pt_size`
    /// where the dots-per-point is usually `dpp = scale_factor * 96.0 / 72.0`
    /// on PC platforms, or `dpp = 1` on MacOS (or 2 for retina displays).
    pub dpem: f32,
    /// The available (horizontal and vertical) space
    ///
    /// This defaults to infinity (implying no bounds). To enable line-wrapping
    /// set at least a horizontal bound. The vertical bound is required for
    /// alignment (when aligning to the center or bottom).
    /// Glyphs outside of these bounds may not be drawn.
    pub bounds: Vec2,
}

impl Default for Environment {
    fn default() -> Self {
        Environment {
            direction: Direction::default(),
            wrap: true,
            font_id: Default::default(),
            dpem: 11.0 * 96.0 / 72.0,
            bounds: Vec2::INFINITY,
            align: Default::default(),
        }
    }
}

impl Environment {
    /// Set font size
    ///
    /// This is an alternative to setting [`Self::dpem`] directly. It is assumed
    /// that 72 Points = 1 Inch and the base screen resolution is 96 DPI.
    /// (Note: MacOS uses a different definition where 1 Point = 1 Pixel.)
    pub fn set_font_size(&mut self, pt_size: f32, scale_factor: f32) {
        self.dpem = pt_size * scale_factor * (96.0 / 72.0);
    }

    /// Returns the height of horizontal text
    ///
    /// This should be similar to the value of [`Self::dpem`], but depends on
    /// the font.
    ///
    /// To use "the standard font", use `font_id = Default::default()`.
    pub fn line_height(&self, font_id: FontId) -> Result<f32, InvalidFontId> {
        fonts()
            .get_first_face(font_id)
            .map(|face| face.height(self.dpem))
    }
}

impl Environment {
    /// Construct, with explicit font size (pixels per Em)
    pub fn new(dpem: f32) -> Self {
        Environment {
            dpem,
            ..Default::default()
        }
    }
}

/// Alignment of contents
///
/// Note that alignment information is often passed as a `(horiz, vert)` pair.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Align {
    /// Default alignment
    ///
    /// This is context dependent. For example, for Left-To-Right text it means
    /// `TL`; for things which want to stretch it may mean `Stretch`.
    Default,
    /// Align to top or left
    TL,
    /// Align to center
    Center,
    /// Align to bottom or right
    BR,
    /// Stretch to fill space
    ///
    /// For text, this is known as "justified alignment".
    Stretch,
}

impl Default for Align {
    fn default() -> Self {
        Align::Default
    }
}

/// Directionality of text
///
/// This can be used to force the text direction.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
    /// Auto-detect with bi-directional support (default)
    Bidi,
    /// Auto-detect with bi-directional support, defaulting to right-to-left
    BidiRtl,
    /// Auto-detect, single line direction only
    Single,
    /// Force left-to-right text direction
    Ltr,
    /// Force right-to-left text direction
    Rtl,
}

impl Default for Direction {
    fn default() -> Self {
        Direction::Bidi
    }
}

#[test]
fn size() {
    assert_eq!(std::mem::size_of::<Environment>(), 20);
}