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            // What a macro expands to here is executed by main control
40            // (`goto reswitch`), as after the big switch.
41            self.exec_context = true;
42            self.x_token();
43            self.exec_context = false;
44            match self.cur_cmd {
45                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => continue,
46                Cmd::CharNum => {
47                    self.scan_char_num();
48                    self.cur_chr = self.cur_val as u32;
49                    continue;
50                }
51                _ => return,
52            }
53        }
54    }
55
56    /// Appends one character in the current font and adjusts the space
57    /// factor (`adjust_space_factor`).
58    #[inline]
59    fn append_char(&mut self, c: u32) {
60        let s = self.eqtb.sf.get(c) as i32;
61        let cl = self.cur_list_mut();
62        if s == 1000 {
63            cl.space_factor = 1000;
64        } else if s < 1000 {
65            if s > 0 {
66                cl.space_factor = s;
67            }
68        } else if cl.space_factor < 1000 {
69            cl.space_factor = 1000;
70        } else {
71            cl.space_factor = s;
72        }
73        let f = self.eqtb.cur_font.val;
74        let g = self.new_char_node(f, c);
75        self.tail_append(g);
76    }
77
78    /// The interword glue of font `f` (`font_glue`).
79    fn font_glue(&self, f: u32) -> Glue {
80        let p = &self.font(f).params;
81        Glue {
82            width: p.get(2).copied().unwrap_or(0),
83            stretch: p.get(3).copied().unwrap_or(0),
84            shrink: p.get(4).copied().unwrap_or(0),
85            ..Glue::ZERO
86        }
87    }
88
89    /// A space in horizontal mode.
90    pub fn app_space_main(&mut self) {
91        if self.cur_list().space_factor == 1000 {
92            self.append_normal_space();
93        } else {
94            self.app_space();
95        }
96    }
97
98    /// `\ ` (explicit space).
99    pub fn app_explicit_space(&mut self) {
100        self.append_normal_space();
101    }
102
103    /// Appends a normal interword space (§1041).
104    pub(crate) fn append_normal_space(&mut self) {
105        let ss = self.eqtb.glue(gp::SPACESKIP);
106        let g = if is_zero(&ss) {
107            self.font_glue(self.eqtb.cur_font.val)
108        } else {
109            ss
110        };
111        let p = self.new_glue_node(g, glue_sub::SPACE_SKIP);
112        self.tail_append(p);
113    }
114
115    /// Appends a space when the space factor is not 1000 (`app_space`).
116    fn app_space(&mut self) {
117        let sf = self.cur_list().space_factor;
118        let xs = self.eqtb.glue(gp::XSPACESKIP);
119        let p = if sf >= 2000 && !is_zero(&xs) {
120            self.new_glue_node(xs, glue_sub::XSPACE_SKIP)
121        } else {
122            let ss = self.eqtb.glue(gp::SPACESKIP);
123            let f = self.eqtb.cur_font.val;
124            let mut g = if is_zero(&ss) { self.font_glue(f) } else { ss };
125            if sf >= 2000 {
126                g.width += self.font(f).params.get(7).copied().unwrap_or(0);
127            }
128            g.stretch = retex_arith::xn_over_d(g.stretch, sf, 1000)
129                .map(|r| r.quotient)
130                .unwrap_or(g.stretch);
131            g.shrink = retex_arith::xn_over_d(g.shrink, 1000, sf)
132                .map(|r| r.quotient)
133                .unwrap_or(g.shrink);
134            self.new_glue_node(g, glue_sub::SPACE_SKIP)
135        };
136        self.tail_append(p);
137    }
138}