unhwp 0.2.4

A high-performance library for extracting HWP/HWPX documents into structured Markdown
Documentation
//! Style definitions for text and paragraphs.

use serde::Serialize;
use std::collections::HashMap;

/// Text formatting style.
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct TextStyle {
    /// Bold text
    pub bold: bool,
    /// Italic text
    pub italic: bool,
    /// Underline
    pub underline: bool,
    /// Strikethrough
    pub strikethrough: bool,
    /// Superscript
    pub superscript: bool,
    /// Subscript
    pub subscript: bool,
    /// Font name
    pub font_name: Option<String>,
    /// Font size in points
    pub font_size: Option<f32>,
    /// Text color (RGB hex)
    pub color: Option<String>,
    /// Background/highlight color (RGB hex)
    pub background_color: Option<String>,
}

impl TextStyle {
    /// Creates a new empty text style.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a bold style.
    pub fn bold() -> Self {
        Self {
            bold: true,
            ..Default::default()
        }
    }

    /// Creates an italic style.
    pub fn italic() -> Self {
        Self {
            italic: true,
            ..Default::default()
        }
    }

    /// Returns true if this style has any formatting.
    pub fn has_formatting(&self) -> bool {
        self.bold
            || self.italic
            || self.underline
            || self.strikethrough
            || self.superscript
            || self.subscript
    }
}

/// Paragraph-level style.
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct ParagraphStyle {
    /// Heading level (0 = normal paragraph, 1-6 = heading levels)
    pub heading_level: u8,
    /// Text alignment
    pub alignment: Alignment,
    /// List style
    pub list_style: Option<ListStyle>,
    /// Indentation level (for nested lists)
    pub indent_level: u8,
    /// Line spacing multiplier (1.0 = single, 1.5, 2.0, etc.)
    pub line_spacing: Option<f32>,
    /// Space before paragraph in points
    pub space_before: Option<f32>,
    /// Space after paragraph in points
    pub space_after: Option<f32>,
}

impl ParagraphStyle {
    /// Creates a new default paragraph style.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a heading style with the specified level.
    pub fn heading(level: u8) -> Self {
        Self {
            heading_level: level.min(6),
            ..Default::default()
        }
    }

    /// Returns true if this is a heading.
    pub fn is_heading(&self) -> bool {
        self.heading_level > 0
    }

    /// Returns true if this is a list item.
    pub fn is_list_item(&self) -> bool {
        self.list_style.is_some()
    }
}

/// Text alignment options.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
pub enum Alignment {
    #[default]
    Left,
    Center,
    Right,
    Justify,
}

/// List style types.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum ListStyle {
    /// Ordered list (1, 2, 3, ...)
    Ordered,
    /// Unordered list with bullet
    Unordered,
    /// Custom bullet character
    CustomBullet(char),
}

/// Named style definition that references CharShape and ParaShape by index.
#[derive(Debug, Clone, Default, Serialize)]
pub struct NamedStyle {
    /// Style name
    pub name: String,
    /// Reference to ParaShape by ID
    pub para_shape_id: u32,
    /// Reference to CharShape by ID
    pub char_shape_id: u32,
}

/// Style registry for resolving style references.
#[derive(Debug, Clone, Default, Serialize)]
pub struct StyleRegistry {
    /// Character (text) styles by ID
    pub char_styles: HashMap<u32, TextStyle>,
    /// Paragraph styles by ID
    pub para_styles: HashMap<u32, ParagraphStyle>,
    /// Named styles by name (for lookup)
    pub named_styles: HashMap<String, u32>,
    /// Named style definitions by ID
    pub named_style_defs: HashMap<u32, NamedStyle>,
    /// BinData index to filename mapping (for HWP5 images)
    /// Key: 0-based index in DocInfo BinData records
    /// Value: filename like "BIN0001.bmp"
    #[serde(skip)]
    pub bindata_mapping: HashMap<u32, String>,
}

impl StyleRegistry {
    /// Creates a new empty style registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers a character style.
    pub fn register_char_style(&mut self, id: u32, style: TextStyle) {
        self.char_styles.insert(id, style);
    }

    /// Registers a paragraph style.
    pub fn register_para_style(&mut self, id: u32, style: ParagraphStyle) {
        self.para_styles.insert(id, style);
    }

    /// Gets a character style by ID.
    pub fn get_char_style(&self, id: u32) -> Option<&TextStyle> {
        self.char_styles.get(&id)
    }

    /// Gets a paragraph style by ID.
    pub fn get_para_style(&self, id: u32) -> Option<&ParagraphStyle> {
        self.para_styles.get(&id)
    }

    /// Registers a BinData index to filename mapping.
    pub fn register_bindata(&mut self, index: u32, filename: String) {
        self.bindata_mapping.insert(index, filename);
    }

    /// Gets the filename for a BinData index.
    pub fn get_bindata_filename(&self, index: u32) -> Option<&String> {
        self.bindata_mapping.get(&index)
    }

    /// Registers a named style that references CharShape and ParaShape.
    pub fn register_named_style(
        &mut self,
        id: u32,
        name: String,
        para_shape_id: u32,
        char_shape_id: u32,
    ) {
        // Register by name for lookup
        if !name.is_empty() {
            self.named_styles.insert(name.clone(), id);
        }

        // Store the full definition
        self.named_style_defs.insert(
            id,
            NamedStyle {
                name,
                para_shape_id,
                char_shape_id,
            },
        );
    }

    /// Gets a named style by name.
    pub fn get_named_style_by_name(&self, name: &str) -> Option<&NamedStyle> {
        self.named_styles
            .get(name)
            .and_then(|id| self.named_style_defs.get(id))
    }

    /// Gets a named style by ID.
    pub fn get_named_style(&self, id: u32) -> Option<&NamedStyle> {
        self.named_style_defs.get(&id)
    }
}