Skip to main content

retex_engine/
hyph.rs

1//! Language data: `\patterns`, `\hyphenation`, `\hjcode` and the
2//! per-language hyphen characters (LuaTeX manual §5.4–§5.8), stored per
3//! `\language` with `retex-hyph`.
4
5use crate::Engine;
6use crate::primitives_gen::assign_int as ip;
7use retex_hyph::Language;
8
9/// `hyph_data` modifiers.
10#[allow(missing_docs)]
11pub mod codes {
12    pub const HYPHENATION: u32 = 0;
13    pub const PATTERNS: u32 = 1;
14    pub const PRE_HYPHEN_CHAR: u32 = 2;
15    pub const POST_HYPHEN_CHAR: u32 = 3;
16    pub const PRE_EX_HYPHEN_CHAR: u32 = 4;
17    pub const POST_EX_HYPHEN_CHAR: u32 = 5;
18    pub const HYPHENATION_MIN: u32 = 6;
19    pub const HJCODE: u32 = 7;
20}
21
22/// The largest language number.
23pub const MAX_LANGUAGE: i32 = 16383;
24
25impl Engine {
26    /// The current language number (`\language`, clamped as LuaTeX does).
27    pub fn cur_lang(&self) -> u32 {
28        let l = self.eqtb.int(ip::LANGUAGE);
29        if (0..=MAX_LANGUAGE).contains(&l) {
30            l as u32
31        } else {
32            0
33        }
34    }
35
36    /// Language `n`, created on first use.
37    pub fn language_mut(&mut self, n: u32) -> &mut Language {
38        let i = n as usize;
39        if self.languages.len() <= i {
40            self.languages.resize_with(i + 1, || None);
41        }
42        self.languages[i].get_or_insert_with(Language::new)
43    }
44
45    /// Language `n`, if it exists.
46    pub fn language(&self, n: u32) -> Option<&Language> {
47        self.languages.get(n as usize).and_then(Option::as_ref)
48    }
49
50    /// Initializes a language's hj codes from the current `\lccode`s, as
51    /// `\savinghyphcodes > 0` requests when patterns are loaded.
52    fn save_hyph_codes(&mut self, n: u32) {
53        if self.eqtb.int(ip::SAVINGHYPHCODES) <= 0 {
54            return;
55        }
56        if self.language(n).is_some_and(Language::has_hj_codes) {
57            return;
58        }
59        let codes: Vec<(u32, u32)> = self
60            .eqtb
61            .lc
62            .explicit()
63            .filter(|(_, v, _)| *v > 0)
64            .map(|(c, v, _)| (c, v as u32))
65            .collect();
66        self.language_mut(n).init_hj_codes(codes);
67    }
68
69    /// The text of a braced argument as a string (for `\patterns` and
70    /// `\hyphenation`).
71    fn scan_hyph_text(&mut self) -> String {
72        let toks = self.scan_toks_expanded();
73        let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
74        String::from_utf8_lossy(&s).into_owned()
75    }
76
77    /// Executes a `hyph_data` command.
78    pub fn hyph_data(&mut self, _global: bool) {
79        let lang = self.cur_lang();
80        match self.cur_chr {
81            codes::PATTERNS => {
82                let text = self.scan_hyph_text();
83                self.save_hyph_codes(lang);
84                self.language_mut(lang).add_patterns(&text);
85            }
86            codes::HYPHENATION => {
87                let text = self.scan_hyph_text();
88                let r = if self.language(lang).is_some_and(Language::has_hj_codes) {
89                    self.language_mut(lang).add_exceptions(&text)
90                } else {
91                    let lc = self.eqtb.lc.clone();
92                    self.language_mut(lang)
93                        .add_exceptions_with(&text, &|c| lc.get(c).max(0) as u32)
94                };
95                if r.is_err() {
96                    self.print_err("exception syntax error");
97                    self.help(&[]);
98                    self.error();
99                }
100            }
101            codes::HJCODE => {
102                self.scan_char_num();
103                let c = self.cur_val as u32;
104                self.scan_optional_equals();
105                self.scan_int();
106                let v = self.cur_val.max(0) as u32;
107                self.language_mut(lang).set_hj_code(c, v);
108            }
109            code => {
110                self.scan_optional_equals();
111                self.scan_int();
112                let v = self.cur_val;
113                let s = &mut self.language_mut(lang).settings;
114                match code {
115                    codes::PRE_HYPHEN_CHAR => s.pre_hyphen_char = v,
116                    codes::POST_HYPHEN_CHAR => s.post_hyphen_char = v,
117                    codes::PRE_EX_HYPHEN_CHAR => s.pre_ex_hyphen_char = v,
118                    codes::POST_EX_HYPHEN_CHAR => s.post_ex_hyphen_char = v,
119                    _ => s.hyphenation_min = v,
120                }
121            }
122        }
123    }
124}