retex-engine 1.0.0-rc.2

The TeX typesetting engine for retex
Documentation
//! Displaying noads (*TeX: The Program* §690–§698) in LuaTeX's format.
//!
//! LuaTeX keeps TeX82's layout: a noad prints its type (`\mathord`,
//! `\mathop\limits`, …) followed by its fields, each on a line prefixed by
//! the recursion history extended with `.` (nucleus), `^` (superscript),
//! `_` (subscript), `\` (numerator), `/` (denominator or radical degree).
//! Observed differences from TeX82:
//!
//! * styles include the cramped ones (`\crampedtextstyle`);
//! * radicals print their primitive (`\radical`, `\Uroot`,
//!   `\Uoverdelimiter`, …), math accents print `\Umathaccent` followed by
//!   ` bottom`, ` both`, ` fixed ` as applicable;
//! * `\middle` noads print `\middle`.
//!
//! LuaTeX does not initialize some fields of delimiter nodes, so it
//! sometimes prints garbage such as `height=0.00143` or `[ exact ]` before a
//! delimiter code; retex prints the clean form.

use crate::Engine;
use crate::nodes::{
    DEFAULT_CODE, NodeData, NodeRef, accent_sub, fence_sub, id, noad_sub, radical_sub,
};

/// The names of the eight math styles.
pub const STYLE_NAMES: [&str; 8] = [
    "displaystyle",
    "crampeddisplaystyle",
    "textstyle",
    "crampedtextstyle",
    "scriptstyle",
    "crampedscriptstyle",
    "scriptscriptstyle",
    "crampedscriptscriptstyle",
];

impl Engine {
    /// Prints a style (`print_style`).
    pub fn print_style(&mut self, s: u32) {
        match STYLE_NAMES.get(s as usize) {
            Some(n) => self.print_esc_str(n),
            None => self.printer.print_str("Unknown style!"),
        }
    }

    /// Prints family and character of a `math_char` kernel
    /// (`print_fam_and_char`).
    pub fn print_fam_and_char(&mut self, p: NodeRef) {
        let (fam, chr) = match self.nodes.data(p) {
            NodeData::MathChar { fam, chr } => (*fam, *chr),
            _ => (0, 0),
        };
        self.print_esc_str("fam");
        self.printer.print_int(i64::from(fam));
        self.printer.print_char(' ' as u32);
        self.printer.print_char(chr);
    }

    /// Prints a delimiter as a hexadecimal value (`print_delimiter`).
    pub fn print_delimiter(&mut self, p: NodeRef) {
        let (sf, sc, lf, lc) = self.delim_fields(p);
        let mut a = i64::from(sf) * 256 + i64::from(sc);
        a = a * 0x1000 + i64::from(lf) * 256 + i64::from(lc);
        if a < 0 {
            self.printer.print_int(a);
        } else {
            self.printer.print_hex(a);
        }
    }

    /// The fields (small family, small character, large family, large
    /// character) of a delimiter node; zeros for null.
    pub fn delim_fields(&self, p: NodeRef) -> (u32, u32, u32, u32) {
        if p.is_null() {
            return (0, 0, 0, 0);
        }
        match self.nodes.data(p) {
            NodeData::Delim {
                small_fam,
                small_char,
                large_fam,
                large_char,
            } => (*small_fam, *small_char, *large_fam, *large_char),
            _ => (0, 0, 0, 0),
        }
    }

    /// Displays a noad field (`print_subsidiary_data`).
    fn print_subsidiary_data(&mut self, p: NodeRef, c: u8) {
        if self.ts.show_prefix.len() as i64 >= i64::from(self.depth_threshold) {
            if !p.is_null() {
                self.printer.print_str(" []");
            }
            return;
        }
        if p.is_null() {
            return;
        }
        self.ts.show_prefix.push(c);
        match self.nodes.data(p) {
            NodeData::MathChar { .. } => {
                self.printer.print_ln();
                self.print_current_prefix();
                self.print_fam_and_char(p);
            }
            NodeData::SubList { head } => {
                let head = *head;
                if self.nodes.id(p) == id::SUB_MLIST && head.is_null() {
                    self.printer.print_ln();
                    self.print_current_prefix();
                    self.printer.print_str("{}");
                } else {
                    self.show_node_list(head);
                }
            }
            _ => {}
        }
        self.ts.show_prefix.pop();
    }

    fn print_current_prefix(&mut self) {
        let s = std::mem::take(&mut self.ts.show_prefix);
        for &b in &s {
            self.printer.print_char(u32::from(b));
        }
        self.ts.show_prefix = s;
    }

    fn show_list_with(&mut self, p: NodeRef, c: u8) {
        self.ts.show_prefix.push(c);
        self.show_node_list(p);
        self.ts.show_prefix.pop();
    }

    /// Displays a math node or noad; returns false if `p` is not one.
    pub fn display_math_node(&mut self, p: NodeRef) -> bool {
        let nid = self.nodes.id(p);
        let sub = self.nodes.subtype(p);
        match self.nodes.data(p).clone() {
            NodeData::Style { style } => self.print_style(u32::from(style)),
            NodeData::Choice(c) => {
                self.print_esc_str("mathchoice");
                for (l, ch) in c.iter().zip([b'D', b'T', b'S', b's']) {
                    self.show_list_with(*l, ch);
                }
            }
            NodeData::Noad(n) => {
                match nid {
                    id::RADICAL => {
                        let name = match sub {
                            radical_sub::UROOT => "Uroot",
                            radical_sub::UUNDERDELIMITER => "Uunderdelimiter",
                            radical_sub::UOVERDELIMITER => "Uoverdelimiter",
                            radical_sub::UDELIMITERUNDER => "Udelimiterunder",
                            radical_sub::UDELIMITEROVER => "Udelimiterover",
                            radical_sub::UHEXTENSIBLE => "Uhextensible",
                            _ => "radical",
                        };
                        self.print_esc_str(name);
                        self.print_delimiter(n.left);
                        if n.width != 0 {
                            self.printer.print_str("width=");
                            self.printer.print_scaled(n.width);
                            self.printer.print_char(' ' as u32);
                        }
                        if !n.degree.is_null() {
                            self.print_subsidiary_data(n.degree, b'/');
                        }
                    }
                    id::ACCENT => {
                        self.print_esc_str("Umathaccent");
                        let (top, bot) = (!n.accent.is_null(), !n.bot_accent.is_null());
                        if top && bot {
                            self.printer.print_str(" both");
                        } else if bot {
                            self.printer.print_str(" bottom");
                        }
                        if n.options & crate::nodes::noad_opt::OVERLAY != 0 {
                            self.printer.print_str(" overlay");
                        }
                        if n.fraction != 0 {
                            self.printer.print_str(" fraction=");
                            self.printer.print_int(i64::from(n.fraction));
                            self.printer.print_char(' ' as u32);
                        }
                        if top {
                            if sub == accent_sub::FIXED_TOP || sub == accent_sub::FIXED_BOTH {
                                self.printer.print_str(" fixed ");
                            }
                            self.print_fam_and_char(n.accent);
                        }
                        if bot {
                            if sub == accent_sub::FIXED_BOTTOM || sub == accent_sub::FIXED_BOTH {
                                self.printer.print_str(" fixed ");
                            }
                            self.print_fam_and_char(n.bot_accent);
                        }
                    }
                    _ => {
                        let name = match sub {
                            noad_sub::ORD => "mathord",
                            noad_sub::OP_DISPLAY_LIMITS
                            | noad_sub::OP_LIMITS
                            | noad_sub::OP_NO_LIMITS => "mathop",
                            noad_sub::BIN => "mathbin",
                            noad_sub::REL => "mathrel",
                            noad_sub::OPEN => "mathopen",
                            noad_sub::CLOSE => "mathclose",
                            noad_sub::PUNCT => "mathpunct",
                            noad_sub::INNER => "mathinner",
                            noad_sub::UNDER => "underline",
                            noad_sub::OVER => "overline",
                            _ => "vcenter",
                        };
                        self.print_esc_str(name);
                        if sub == noad_sub::OP_LIMITS {
                            self.print_esc_str("limits");
                        } else if sub == noad_sub::OP_NO_LIMITS {
                            self.print_esc_str("nolimits");
                        }
                    }
                }
                self.print_subsidiary_data(n.nucleus, b'.');
                self.print_subsidiary_data(n.sup, b'^');
                self.print_subsidiary_data(n.sub, b'_');
            }
            NodeData::Fence(f) => {
                self.print_esc_str(match sub {
                    fence_sub::LEFT => "left",
                    fence_sub::MIDDLE | fence_sub::NO => "middle",
                    _ => "right",
                });
                self.print_delimiter(f.delim);
            }
            NodeData::Fraction(f) => {
                self.print_esc_str("fraction, thickness ");
                if f.thickness == DEFAULT_CODE {
                    self.printer.print_str("= default");
                } else {
                    self.printer.print_scaled(f.thickness);
                }
                if self.delim_fields(f.left) != (0, 0, 0, 0) {
                    self.printer.print_str(", left-delimiter ");
                    self.print_delimiter(f.left);
                }
                if self.delim_fields(f.right) != (0, 0, 0, 0) {
                    self.printer.print_str(", right-delimiter ");
                    self.print_delimiter(f.right);
                }
                self.print_subsidiary_data(f.num, b'\\');
                self.print_subsidiary_data(f.denom, b'/');
            }
            _ => return false,
        }
        true
    }
}