retex-engine 1.0.0

The TeX typesetting engine for retex
Documentation
//! Characters and spaces in horizontal mode (*TeX: The Program*
//! §1034–§1044, LuaTeX style).
//!
//! In LuaTeX a character simply becomes a glyph node: ligatures and kerns
//! are applied later (see [`crate::nodeproc`]). The space factor is
//! maintained as in TeX82. Interword glue made from the font or from
//! `\spaceskip` has subtype `spaceskip`; glue from `\xspaceskip` has
//! subtype `xspaceskip`.

use crate::Engine;
use crate::cmd::Cmd;
use crate::glue::Glue;
use crate::nodes::glue_sub;
use crate::primitives_gen::{assign_glue as gp, assign_int as ip};

/// Whether a glue specification is zero (TeX's `zero_glue` test).
#[inline]
fn is_zero(g: &Glue) -> bool {
    g.width == 0 && g.stretch == 0 && g.shrink == 0
}

impl Engine {
    /// Characters in horizontal mode: appends glyph nodes for the current
    /// character and the characters that follow; on return the next
    /// non-character token is current (TeX's main loop, `goto reswitch`).
    pub fn main_loop_char(&mut self) {
        let tracing = self.eqtb.int(ip::TRACINGCOMMANDS) > 0;
        loop {
            self.append_char(self.cur_chr);
            if tracing {
                // LuaTeX reports every character when tracing commands.
                self.get_x_token();
                return;
            }
            self.get_next();
            if matches!(self.cur_cmd, Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven) {
                continue;
            }
            self.x_token();
            match self.cur_cmd {
                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => continue,
                Cmd::CharNum => {
                    self.scan_char_num();
                    self.cur_chr = self.cur_val as u32;
                    continue;
                }
                _ => return,
            }
        }
    }

    /// Appends one character in the current font and adjusts the space
    /// factor (`adjust_space_factor`).
    #[inline]
    fn append_char(&mut self, c: u32) {
        let s = self.eqtb.sf.get(c) as i32;
        let cl = self.cur_list_mut();
        if s == 1000 {
            cl.space_factor = 1000;
        } else if s < 1000 {
            if s > 0 {
                cl.space_factor = s;
            }
        } else if cl.space_factor < 1000 {
            cl.space_factor = 1000;
        } else {
            cl.space_factor = s;
        }
        let f = self.eqtb.cur_font.val;
        let g = self.new_char_node(f, c);
        self.tail_append(g);
    }

    /// The interword glue of font `f` (`font_glue`).
    fn font_glue(&self, f: u32) -> Glue {
        let p = &self.font(f).params;
        Glue {
            width: p.get(2).copied().unwrap_or(0),
            stretch: p.get(3).copied().unwrap_or(0),
            shrink: p.get(4).copied().unwrap_or(0),
            ..Glue::ZERO
        }
    }

    /// A space in horizontal mode.
    pub fn app_space_main(&mut self) {
        if self.cur_list().space_factor == 1000 {
            self.append_normal_space();
        } else {
            self.app_space();
        }
    }

    /// `\ ` (explicit space).
    pub fn app_explicit_space(&mut self) {
        self.append_normal_space();
    }

    /// Appends a normal interword space (§1041).
    pub(crate) fn append_normal_space(&mut self) {
        let ss = self.eqtb.glue(gp::SPACESKIP);
        let g = if is_zero(&ss) {
            self.font_glue(self.eqtb.cur_font.val)
        } else {
            ss
        };
        let p = self.new_glue_node(g, glue_sub::SPACE_SKIP);
        self.tail_append(p);
    }

    /// Appends a space when the space factor is not 1000 (`app_space`).
    fn app_space(&mut self) {
        let sf = self.cur_list().space_factor;
        let xs = self.eqtb.glue(gp::XSPACESKIP);
        let p = if sf >= 2000 && !is_zero(&xs) {
            self.new_glue_node(xs, glue_sub::XSPACE_SKIP)
        } else {
            let ss = self.eqtb.glue(gp::SPACESKIP);
            let f = self.eqtb.cur_font.val;
            let mut g = if is_zero(&ss) { self.font_glue(f) } else { ss };
            if sf >= 2000 {
                g.width += self.font(f).params.get(7).copied().unwrap_or(0);
            }
            g.stretch = retex_arith::xn_over_d(g.stretch, sf, 1000)
                .map(|r| r.quotient)
                .unwrap_or(g.stretch);
            g.shrink = retex_arith::xn_over_d(g.shrink, 1000, sf)
                .map(|r| r.quotient)
                .unwrap_or(g.shrink);
            self.new_glue_node(g, glue_sub::SPACE_SKIP)
        };
        self.tail_append(p);
    }
}