retex-engine 0.9.0

The TeX typesetting engine for retex
Documentation
//! Language data: `\patterns`, `\hyphenation`, `\hjcode` and the
//! per-language hyphen characters (LuaTeX manual §5.4–§5.8), stored per
//! `\language` with `retex-hyph`.

use crate::Engine;
use crate::primitives_gen::assign_int as ip;
use retex_hyph::Language;

/// `hyph_data` modifiers.
#[allow(missing_docs)]
pub mod codes {
    pub const HYPHENATION: u32 = 0;
    pub const PATTERNS: u32 = 1;
    pub const PRE_HYPHEN_CHAR: u32 = 2;
    pub const POST_HYPHEN_CHAR: u32 = 3;
    pub const PRE_EX_HYPHEN_CHAR: u32 = 4;
    pub const POST_EX_HYPHEN_CHAR: u32 = 5;
    pub const HYPHENATION_MIN: u32 = 6;
    pub const HJCODE: u32 = 7;
}

/// The largest language number.
pub const MAX_LANGUAGE: i32 = 16383;

impl Engine {
    /// The current language number (`\language`, clamped as LuaTeX does).
    pub fn cur_lang(&self) -> u32 {
        let l = self.eqtb.int(ip::LANGUAGE);
        if (0..=MAX_LANGUAGE).contains(&l) {
            l as u32
        } else {
            0
        }
    }

    /// Language `n`, created on first use.
    pub fn language_mut(&mut self, n: u32) -> &mut Language {
        let i = n as usize;
        if self.languages.len() <= i {
            self.languages.resize_with(i + 1, || None);
        }
        self.languages[i].get_or_insert_with(Language::new)
    }

    /// Language `n`, if it exists.
    pub fn language(&self, n: u32) -> Option<&Language> {
        self.languages.get(n as usize).and_then(Option::as_ref)
    }

    /// Initializes a language's hj codes from the current `\lccode`s, as
    /// `\savinghyphcodes > 0` requests when patterns are loaded.
    fn save_hyph_codes(&mut self, n: u32) {
        if self.eqtb.int(ip::SAVINGHYPHCODES) <= 0 {
            return;
        }
        if self.language(n).is_some_and(Language::has_hj_codes) {
            return;
        }
        let codes: Vec<(u32, u32)> = self
            .eqtb
            .lc
            .explicit()
            .filter(|(_, v, _)| *v > 0)
            .map(|(c, v, _)| (c, v as u32))
            .collect();
        self.language_mut(n).init_hj_codes(codes);
    }

    /// The text of a braced argument as a string (for `\patterns` and
    /// `\hyphenation`).
    fn scan_hyph_text(&mut self) -> String {
        let toks = self.scan_toks_expanded();
        let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
        String::from_utf8_lossy(&s).into_owned()
    }

    /// Executes a `hyph_data` command.
    pub fn hyph_data(&mut self, _global: bool) {
        let lang = self.cur_lang();
        match self.cur_chr {
            codes::PATTERNS => {
                let text = self.scan_hyph_text();
                self.save_hyph_codes(lang);
                self.language_mut(lang).add_patterns(&text);
            }
            codes::HYPHENATION => {
                let text = self.scan_hyph_text();
                let r = if self.language(lang).is_some_and(Language::has_hj_codes) {
                    self.language_mut(lang).add_exceptions(&text)
                } else {
                    let lc = self.eqtb.lc.clone();
                    self.language_mut(lang)
                        .add_exceptions_with(&text, &|c| lc.get(c).max(0) as u32)
                };
                if r.is_err() {
                    self.print_err("exception syntax error");
                    self.help(&[]);
                    self.error();
                }
            }
            codes::HJCODE => {
                self.scan_char_num();
                let c = self.cur_val as u32;
                self.scan_optional_equals();
                self.scan_int();
                let v = self.cur_val.max(0) as u32;
                self.language_mut(lang).set_hj_code(c, v);
            }
            code => {
                self.scan_optional_equals();
                self.scan_int();
                let v = self.cur_val;
                let s = &mut self.language_mut(lang).settings;
                match code {
                    codes::PRE_HYPHEN_CHAR => s.pre_hyphen_char = v,
                    codes::POST_HYPHEN_CHAR => s.post_hyphen_char = v,
                    codes::PRE_EX_HYPHEN_CHAR => s.pre_ex_hyphen_char = v,
                    codes::POST_EX_HYPHEN_CHAR => s.post_ex_hyphen_char = v,
                    _ => s.hyphenation_min = v,
                }
            }
        }
    }
}