Skip to main content

retex_engine/
hmode.rs

1//! Characters and spaces in horizontal mode (*TeX: The Program*
2//! §1034–§1044, LuaTeX style).
3//!
4//! In LuaTeX a character simply becomes a glyph node: ligatures and kerns
5//! are applied later (see [`crate::nodeproc`]). The space factor is
6//! maintained as in TeX82. Interword glue made from the font or from
7//! `\spaceskip` has subtype `spaceskip`; glue from `\xspaceskip` has
8//! subtype `xspaceskip`.
9
10use crate::Engine;
11use crate::cmd::Cmd;
12use crate::glue::Glue;
13use crate::nodes::glue_sub;
14use crate::primitives_gen::{assign_glue as gp, assign_int as ip};
15
16/// Whether a glue specification is zero (TeX's `zero_glue` test).
17#[inline]
18fn is_zero(g: &Glue) -> bool {
19    g.width == 0 && g.stretch == 0 && g.shrink == 0
20}
21
22impl Engine {
23    /// Characters in horizontal mode: appends glyph nodes for the current
24    /// character and the characters that follow; on return the next
25    /// non-character token is current (TeX's main loop, `goto reswitch`).
26    pub fn main_loop_char(&mut self) {
27        let tracing = self.eqtb.int(ip::TRACINGCOMMANDS) > 0;
28        loop {
29            self.append_char(self.cur_chr);
30            if tracing {
31                // LuaTeX reports every character when tracing commands.
32                self.get_x_token();
33                return;
34            }
35            self.get_next();
36            if matches!(self.cur_cmd, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) {
37                continue;
38            }
39            self.x_token();
40            match self.cur_cmd {
41                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => continue,
42                Cmd::CharNum => {
43                    self.scan_char_num();
44                    self.cur_chr = self.cur_val as u32;
45                    continue;
46                }
47                _ => return,
48            }
49        }
50    }
51
52    /// Appends one character in the current font and adjusts the space
53    /// factor (`adjust_space_factor`).
54    #[inline]
55    fn append_char(&mut self, c: u32) {
56        let s = self.eqtb.sf.get(c) as i32;
57        let cl = self.cur_list_mut();
58        if s == 1000 {
59            cl.space_factor = 1000;
60        } else if s < 1000 {
61            if s > 0 {
62                cl.space_factor = s;
63            }
64        } else if cl.space_factor < 1000 {
65            cl.space_factor = 1000;
66        } else {
67            cl.space_factor = s;
68        }
69        let f = self.eqtb.cur_font.val;
70        let g = self.new_char_node(f, c);
71        self.tail_append(g);
72    }
73
74    /// The interword glue of font `f` (`font_glue`).
75    fn font_glue(&self, f: u32) -> Glue {
76        let p = &self.font(f).params;
77        Glue {
78            width: p.get(2).copied().unwrap_or(0),
79            stretch: p.get(3).copied().unwrap_or(0),
80            shrink: p.get(4).copied().unwrap_or(0),
81            ..Glue::ZERO
82        }
83    }
84
85    /// A space in horizontal mode.
86    pub fn app_space_main(&mut self) {
87        if self.cur_list().space_factor == 1000 {
88            self.append_normal_space();
89        } else {
90            self.app_space();
91        }
92    }
93
94    /// `\ ` (explicit space).
95    pub fn app_explicit_space(&mut self) {
96        self.append_normal_space();
97    }
98
99    /// Appends a normal interword space (§1041).
100    pub(crate) fn append_normal_space(&mut self) {
101        let ss = self.eqtb.glue(gp::SPACESKIP);
102        let g = if is_zero(&ss) {
103            self.font_glue(self.eqtb.cur_font.val)
104        } else {
105            ss
106        };
107        let p = self.new_glue_node(g, glue_sub::SPACE_SKIP);
108        self.tail_append(p);
109    }
110
111    /// Appends a space when the space factor is not 1000 (`app_space`).
112    fn app_space(&mut self) {
113        let sf = self.cur_list().space_factor;
114        let xs = self.eqtb.glue(gp::XSPACESKIP);
115        let p = if sf >= 2000 && !is_zero(&xs) {
116            self.new_glue_node(xs, glue_sub::XSPACE_SKIP)
117        } else {
118            let ss = self.eqtb.glue(gp::SPACESKIP);
119            let f = self.eqtb.cur_font.val;
120            let mut g = if is_zero(&ss) { self.font_glue(f) } else { ss };
121            if sf >= 2000 {
122                g.width += self.font(f).params.get(7).copied().unwrap_or(0);
123            }
124            g.stretch = retex_arith::xn_over_d(g.stretch, sf, 1000)
125                .map(|r| r.quotient)
126                .unwrap_or(g.stretch);
127            g.shrink = retex_arith::xn_over_d(g.shrink, 1000, sf)
128                .map(|r| r.quotient)
129                .unwrap_or(g.shrink);
130            self.new_glue_node(g, glue_sub::SPACE_SKIP)
131        };
132        self.tail_append(p);
133    }
134}