oxipdf-ir 0.1.0

Intermediate representation types for the oxipdf PDF engine
Documentation
//! Typography style properties.

use crate::color::Color;
use crate::units::Pt;

/// Typography-related style properties.
///
/// The consumer must resolve font inheritance before constructing the IR.
/// `font_families` must list at least one family; the font provider resolves
/// the first available match in order.
#[derive(Debug, Clone, PartialEq)]
pub struct TypographyStyle {
    /// Ordered list of font family names. The font provider tries each in
    /// order until one resolves. Must not be empty.
    pub font_families: Vec<String>,
    /// Font size in points.
    pub font_size: Pt,
    /// Font weight (100–900). 400 = normal, 700 = bold.
    pub font_weight: u16,
    /// Font style.
    pub font_style: FontStyle,
    /// Line height.
    pub line_height: LineHeight,
    /// Additional spacing between characters.
    pub letter_spacing: Pt,
    /// Additional spacing between words.
    pub word_spacing: Pt,
    /// Text alignment within the line box.
    pub text_align: TextAlign,
    /// Text decoration.
    pub text_decoration: TextDecoration,
    /// First-line indent.
    pub text_indent: Pt,
    /// Whitespace handling.
    pub white_space: WhiteSpace,
    /// Text overflow behavior when content exceeds the container width.
    pub text_overflow: TextOverflow,
    /// Vertical alignment within an inline formatting context.
    pub vertical_align: VerticalAlign,
    /// Case transformation applied before shaping.
    pub text_transform: TextTransform,
    /// Shadow effects on text.
    pub text_shadow: Vec<TextShadow>,
    /// Word breaking behavior for CJK and long words.
    pub word_break: WordBreak,
    /// Whether long words that overflow can be broken mid-word.
    pub overflow_wrap: OverflowWrap,
    /// Width of tab characters in spaces (for pre-formatted text).
    pub tab_size: u32,
    /// Text color.
    pub color: Color,
    /// Text writing direction.
    pub direction: TextDirection,
    /// OpenType font feature settings.
    pub font_features: OpenTypeFeatures,
    /// Microtypography settings for print-quality text.
    pub microtypography: Microtypography,
}

/// Microtypography configuration for print-quality text.
///
/// These settings make subtle adjustments to character positioning and
/// spacing that are invisible individually but dramatically improve
/// overall text quality, especially in justified paragraphs.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Microtypography {
    /// Hanging punctuation: punctuation at line edges protrudes slightly
    /// into margins for optical alignment. The value is the fraction of
    /// the character width that protrudes (0.0 = disabled, typical: 0.5–1.0).
    pub hanging_punctuation: f64,
    /// Optical margin alignment: hyphens at line breaks protrude into the
    /// right margin by this fraction of the hyphen width (0.0 = disabled,
    /// typical: 0.5–1.0). Makes the right margin appear more even.
    pub optical_margin: f64,
    /// Paragraph-level tracking adjustment: maximum letter-spacing delta
    /// (in points) that the engine may apply per character to improve
    /// justification quality. 0.0 = disabled. Typical: 0.2–0.5pt.
    /// Positive values allow expansion; the engine may also compress
    /// by up to half this value.
    pub max_tracking_adjust: f64,
}

/// OpenType font feature configuration.
///
/// Controls ligatures, small caps, old-style figures, kerning, and
/// arbitrary OpenType feature tags. These map to `rustybuzz::Feature`
/// entries passed to the shaping engine.
#[derive(Debug, Clone, PartialEq, Default)]
pub struct OpenTypeFeatures {
    /// Standard ligature control. `None` = engine default (usually on).
    /// `Some(false)` disables ligatures. `Some(true)` explicitly enables.
    pub ligatures: Option<bool>,
    /// Small caps via OpenType `smcp` feature.
    pub small_caps: bool,
    /// Old-style (lowercase) figures via OpenType `onum` feature.
    pub old_style_nums: bool,
    /// Kerning control. `None` = engine default (auto).
    /// `Some(false)` disables kerning. `Some(true)` explicitly enables.
    pub kerning: Option<bool>,
    /// Arbitrary OpenType feature tags with their values.
    /// Each entry is a 4-byte tag string (e.g., "swsh") and a value
    /// (1 = enable, 0 = disable, >1 = select alternate).
    pub custom: Vec<(String, u32)>,
}

impl Default for TypographyStyle {
    fn default() -> Self {
        Self {
            font_families: Vec::new(),
            font_size: Pt::new(12.0),
            font_weight: 400,
            font_style: FontStyle::Normal,
            line_height: LineHeight::Normal,
            letter_spacing: Pt::ZERO,
            word_spacing: Pt::ZERO,
            text_align: TextAlign::Start,
            text_decoration: TextDecoration::None,
            text_indent: Pt::ZERO,
            white_space: WhiteSpace::Normal,
            text_overflow: TextOverflow::Clip,
            vertical_align: VerticalAlign::Baseline,
            text_transform: TextTransform::None,
            text_shadow: Vec::new(),
            word_break: WordBreak::Normal,
            overflow_wrap: OverflowWrap::Normal,
            tab_size: 8,
            color: Color::BLACK,
            direction: TextDirection::Ltr,
            font_features: OpenTypeFeatures::default(),
            microtypography: Microtypography::default(),
        }
    }
}

/// Font style (posture).
///
/// `#[repr(u8)]` ensures stable discriminants for use as cache keys.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum FontStyle {
    #[default]
    Normal = 0,
    Italic = 1,
    Oblique = 2,
}

/// Line height specification.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum LineHeight {
    /// Use the font's default line spacing (typically ~1.2× font size).
    #[default]
    Normal,
    /// Absolute line height in points.
    Length(Pt),
    /// Multiplier of the font size (e.g., 1.5 means 1.5× font-size).
    Number(f64),
}

/// Text alignment.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextAlign {
    /// Align to the start edge (left for LTR, right for RTL).
    #[default]
    Start,
    /// Align to the end edge.
    End,
    /// Align left regardless of direction.
    Left,
    /// Align right regardless of direction.
    Right,
    /// Center alignment.
    Center,
    /// Justify: stretch word/letter spacing to fill the line. Last line
    /// reverts to start alignment.
    Justify,
}

/// Text decoration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextDecoration {
    #[default]
    None,
    Underline,
    Overline,
    LineThrough,
}

/// Whitespace handling mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum WhiteSpace {
    /// Collapse whitespace, allow wrapping.
    #[default]
    Normal,
    /// Preserve whitespace and newlines, no wrapping.
    Pre,
    /// Collapse whitespace, prevent wrapping.
    NoWrap,
    /// Preserve whitespace, allow wrapping.
    PreWrap,
    /// Collapse whitespace, preserve newlines, allow wrapping.
    PreLine,
}

/// Text overflow behavior when content exceeds the container.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextOverflow {
    /// Clip overflowing text at the container boundary (no visual indicator).
    #[default]
    Clip,
    /// Truncate text and append an ellipsis ("...") to indicate overflow.
    Ellipsis,
}

/// Vertical alignment within an inline formatting context.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum VerticalAlign {
    /// Align to the parent's baseline (default).
    #[default]
    Baseline,
    /// Align top of element with top of line.
    Top,
    /// Align center of element with center of line.
    Middle,
    /// Align bottom of element with bottom of line.
    Bottom,
    /// Shift baseline down (subscript).
    Sub,
    /// Shift baseline up (superscript).
    Super,
}

/// Case transformation applied to text before shaping.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextTransform {
    /// No transformation.
    #[default]
    None,
    /// Convert all characters to uppercase.
    Uppercase,
    /// Convert all characters to lowercase.
    Lowercase,
    /// Capitalize the first letter of each word.
    Capitalize,
}

impl TextTransform {
    /// Apply the transformation to a text string.
    #[must_use]
    pub fn apply(&self, text: &str) -> String {
        match self {
            Self::None => text.to_string(),
            Self::Uppercase => text.to_uppercase(),
            Self::Lowercase => text.to_lowercase(),
            Self::Capitalize => capitalize_words(text),
        }
    }
}

/// Capitalize the first letter of each word.
fn capitalize_words(text: &str) -> String {
    let mut result = String::with_capacity(text.len());
    let mut prev_is_space = true;
    for ch in text.chars() {
        if prev_is_space && ch.is_alphabetic() {
            for c in ch.to_uppercase() {
                result.push(c);
            }
        } else {
            result.push(ch);
        }
        prev_is_space = ch.is_whitespace();
    }
    result
}

/// Shadow effect on text.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TextShadow {
    /// Horizontal offset.
    pub offset_x: Pt,
    /// Vertical offset.
    pub offset_y: Pt,
    /// Blur radius (approximated).
    pub blur_radius: Pt,
    /// Shadow color.
    pub color: Color,
}

/// Word breaking behavior.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum WordBreak {
    /// Break at normal word boundaries (spaces, hyphens).
    #[default]
    Normal,
    /// Allow breaks within any word (CJK-style).
    BreakAll,
    /// Don't allow word breaks for CJK text (keep words together).
    KeepAll,
}

/// Whether long unbreakable words can be broken to prevent overflow.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum OverflowWrap {
    /// Only break at allowed break points.
    #[default]
    Normal,
    /// Break long words that would overflow.
    BreakWord,
    /// Same as BreakWord (alias).
    Anywhere,
}

/// Text writing direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextDirection {
    /// Left-to-right.
    #[default]
    Ltr,
    /// Right-to-left.
    Rtl,
}