Skip to main content

retex_engine/
dvi.rs

1//! The DVI writer (*TeX: The Program* §583–§602, §617–§642), as LuaTeX
2//! uses it when `\outputmode=0`.
3//!
4//! A DVI file is a preamble, one `bop`…`eop` sequence per page and a
5//! postamble that repeats the font definitions. The commands follow
6//! TeX82, with these differences observed in LuaTeX's output:
7//!
8//! * movements are emitted lazily, just before the next character, rule
9//!   or special, as `right`/`down` commands with the smallest length that
10//!   TeX82's `movement` would pick; the `w`, `x`, `y`, `z` registers are
11//!   never used (TeX82's §607–§615 optimization is absent);
12//! * rules are always `set_rule`, also in vertical lists, so the reader's
13//!   `h` moves by the rule width;
14//! * a box nested inside the page is bracketed by `push`/`pop`; a `push`
15//!   immediately followed by its `pop` is removed (`dvi_pop`, §601);
16//! * characters and fonts use the shortest command for their number
17//!   (`set1`…`set4`, `fnt1`…`fnt4`, `fnt_def1`…`fnt_def4`);
18//! * the preamble comment is `" LuaTeX output YYYY.MM.DD:HHMM"`, from
19//!   `\year`, `\month`, `\day` and `\time` at the first page.
20//!
21//! The walker in [`crate::shipout`] computes positions with TeX's rounding
22//! rules and calls this writer with absolute DVI coordinates (`h` to the
23//! right and `v` downwards from the DVI origin, `\hoffset` and `\voffset`
24//! included).
25
26use crate::Engine;
27use crate::primitives_gen::assign_int as ip;
28
29/// DVI opcodes (§586).
30mod op {
31    pub const SET1: u8 = 128;
32    pub const SET_RULE: u8 = 132;
33    pub const BOP: u8 = 139;
34    pub const EOP: u8 = 140;
35    pub const PUSH: u8 = 141;
36    pub const POP: u8 = 142;
37    pub const RIGHT1: u8 = 143;
38    pub const DOWN1: u8 = 157;
39    pub const FNT_NUM_0: u8 = 171;
40    pub const FNT1: u8 = 235;
41    pub const XXX1: u8 = 239;
42    pub const XXX4: u8 = 242;
43    pub const FNT_DEF1: u8 = 243;
44    pub const PRE: u8 = 247;
45    pub const POST: u8 = 248;
46    pub const POST_POST: u8 = 249;
47}
48
49/// The DVI format identification byte (§587).
50const ID_BYTE: u8 = 2;
51/// The unit numerator and denominator: sp in units of 10^{-7} m (§587).
52const NUM: i32 = 25_400_000;
53const DEN: i32 = 473_628_672;
54
55/// The state of the DVI file being written.
56#[derive(Debug, Default)]
57pub struct DviWriter {
58    buf: Vec<u8>,
59    /// The reader's position (`dvi_h`, `dvi_v`).
60    h: i32,
61    v: i32,
62    /// The font currently selected (`dvi_f`), if any.
63    font: Option<u32>,
64    /// Fonts already defined in the file (`font_used`).
65    used: Vec<bool>,
66    /// Byte offset of the last `bop`, -1 before the first page.
67    last_bop: i32,
68    /// Pages written.
69    pages: u32,
70    /// Largest page height plus depth plus `\voffset` (`max_v`).
71    max_v: i32,
72    /// Largest page width plus `\hoffset` (`max_h`).
73    max_h: i32,
74    /// Deepest box nesting (`max_push`).
75    max_push: i32,
76    /// For each open `push`: the file length after it and the position
77    /// it saved.
78    stack: Vec<(usize, i32, i32)>,
79}
80
81impl DviWriter {
82    /// The number of pages written so far.
83    pub fn pages(&self) -> u32 {
84        self.pages
85    }
86
87    fn out(&mut self, b: u8) {
88        self.buf.push(b);
89    }
90
91    fn four(&mut self, x: i32) {
92        self.buf.extend_from_slice(&x.to_be_bytes());
93    }
94
95    /// Writes `x` in `n` bytes (big-endian, two's complement).
96    fn bytes(&mut self, x: u32, n: usize) {
97        self.buf.extend_from_slice(&x.to_be_bytes()[4 - n..]);
98    }
99
100    /// Writes command `base + n - 1` with an `n`-byte unsigned argument,
101    /// `n` as small as possible.
102    fn unsigned_cmd(&mut self, base: u8, x: u32) {
103        let n = match x {
104            0..=0xff => 1,
105            0x100..=0xffff => 2,
106            0x1_0000..=0xff_ffff => 3,
107            _ => 4,
108        };
109        self.out(base + n as u8 - 1);
110        self.bytes(x, n);
111    }
112
113    /// A `right` or `down` command (`movement`, §607 without the
114    /// `w`/`x`/`y`/`z` optimization; the length rule is TeX's).
115    fn movement(&mut self, w: i32, base: u8) {
116        let a = w.unsigned_abs();
117        let n = if a >= 0o40000000 {
118            4
119        } else if a >= 0o100000 {
120            3
121        } else if a >= 0o200 {
122            2
123        } else {
124            1
125        };
126        self.out(base + n as u8 - 1);
127        self.bytes(w as u32, n);
128    }
129
130    /// Moves the reader to (`h`, `v`) (`synch_h`, `synch_v`).
131    fn synch(&mut self, h: i32, v: i32) {
132        if h != self.h {
133            self.movement(h.wrapping_sub(self.h), op::RIGHT1);
134            self.h = h;
135        }
136        if v != self.v {
137            self.movement(v.wrapping_sub(self.v), op::DOWN1);
138            self.v = v;
139        }
140    }
141
142    /// Starts a page (§640): writes the preamble before the first page,
143    /// then `bop` with `\count0`–`\count9`. `width`, `height` and `depth`
144    /// are the box dimensions, `h_off` and `v_off` the page offsets.
145    pub fn begin_page(&mut self, engine: &mut Engine, dims: (i32, i32, i32), offsets: (i32, i32)) {
146        let (width, height, depth) = dims;
147        let (h_off, v_off) = offsets;
148        let t = height.wrapping_add(depth).wrapping_add(v_off);
149        if t > self.max_v {
150            self.max_v = t;
151        }
152        let t = width.wrapping_add(h_off);
153        if t > self.max_h {
154            self.max_h = t;
155        }
156        if self.pages == 0 {
157            engine.prepare_mag();
158            self.write_preamble(engine);
159        }
160        let page_loc = self.buf.len() as i32;
161        self.out(op::BOP);
162        for k in 0..10 {
163            self.four(engine.eqtb.count[k].val);
164        }
165        self.four(self.last_bop);
166        self.last_bop = page_loc;
167        self.h = 0;
168        self.v = 0;
169        self.font = None;
170        self.stack.clear();
171    }
172
173    /// The preamble (§617).
174    fn write_preamble(&mut self, engine: &Engine) {
175        self.last_bop = -1;
176        self.out(op::PRE);
177        self.out(ID_BYTE);
178        self.four(NUM);
179        self.four(DEN);
180        self.four(engine.eqtb.int(ip::MAG));
181        let int = |i: u32| engine.eqtb.int(i);
182        let two = |n: i32| format!("{:02}", n.unsigned_abs() % 100);
183        let time = int(ip::TIME);
184        let comment = engine.output_comment.clone().unwrap_or_else(|| {
185            format!(
186                " LuaTeX output {}.{}.{}:{}{}",
187                int(ip::YEAR),
188                two(int(ip::MONTH)),
189                two(int(ip::DAY)),
190                two(time / 60),
191                two(time % 60)
192            )
193        });
194        let c = &comment.as_bytes()[..comment.len().min(255)];
195        self.out(c.len() as u8);
196        self.buf.extend_from_slice(c);
197    }
198
199    /// Finishes a page.
200    pub fn end_page(&mut self) {
201        self.out(op::EOP);
202        self.pages += 1;
203    }
204
205    /// Opens a nested box (`push`); `depth` is its nesting level
206    /// (`cur_s`, 1 for a box inside the page box).
207    pub fn push(&mut self, depth: i32) {
208        if depth > self.max_push {
209            self.max_push = depth;
210        }
211        self.out(op::PUSH);
212        self.stack.push((self.buf.len(), self.h, self.v));
213    }
214
215    /// Records the nesting level of the page box itself (0).
216    pub fn note_depth(&mut self, depth: i32) {
217        if depth > self.max_push {
218            self.max_push = depth;
219        }
220    }
221
222    /// Closes a nested box (`dvi_pop`, §601).
223    pub fn pop(&mut self) {
224        let Some((loc, h, v)) = self.stack.pop() else {
225            return;
226        };
227        if loc == self.buf.len() {
228            self.buf.pop();
229        } else {
230            self.out(op::POP);
231        }
232        self.h = h;
233        self.v = v;
234    }
235
236    /// Typesets character `chr` of font `f` at (`h`, `v`) (§620); the
237    /// reader then advances by `width`.
238    pub fn glyph(&mut self, engine: &Engine, f: u32, chr: u32, h: i32, v: i32, width: i32) {
239        if f == 0 {
240            return;
241        }
242        self.synch(h, v);
243        if self.font != Some(f) {
244            let k = f - 1;
245            let idx = f as usize;
246            if self.used.len() <= idx {
247                self.used.resize(idx + 1, false);
248            }
249            if !self.used[idx] {
250                self.font_def(engine, f);
251                self.used[idx] = true;
252            }
253            if k < 64 {
254                self.out(op::FNT_NUM_0 + k as u8);
255            } else {
256                self.unsigned_cmd(op::FNT1, k);
257            }
258            self.font = Some(f);
259        }
260        if chr < 128 {
261            self.out(chr as u8);
262        } else {
263            self.unsigned_cmd(op::SET1, chr);
264        }
265        self.h = h.wrapping_add(width);
266    }
267
268    /// A rule of the given width and height with its lower left corner at
269    /// (`h`, `v`); the reader then advances by `width`.
270    pub fn rule(&mut self, h: i32, v: i32, width: i32, height: i32) {
271        self.synch(h, v);
272        self.out(op::SET_RULE);
273        self.four(height);
274        self.four(width);
275        self.h = h.wrapping_add(width);
276    }
277
278    /// A `\special` at (`h`, `v`) (§1368).
279    pub fn special(&mut self, h: i32, v: i32, data: &[u8]) {
280        self.synch(h, v);
281        if data.len() < 256 {
282            self.out(op::XXX1);
283            self.out(data.len() as u8);
284        } else {
285            self.out(op::XXX4);
286            self.four(data.len() as i32);
287        }
288        self.buf.extend_from_slice(data);
289    }
290
291    /// A font definition (`dvi_font_def`, §602): checksum, size, design
292    /// size and the file name without its directory.
293    fn font_def(&mut self, engine: &Engine, f: u32) {
294        let fi = engine.font(f);
295        let checksum = fi.checksum;
296        let name = fi.name.rsplit(|&b| b == b'/').next().unwrap_or(&fi.name);
297        let name = &name[..name.len().min(255)];
298        let (size, dsize) = (fi.size, fi.design_size);
299        self.unsigned_cmd(op::FNT_DEF1, f - 1);
300        self.four(checksum as i32);
301        self.four(size);
302        self.four(dsize);
303        self.out(0);
304        self.out(name.len() as u8);
305        self.buf.extend_from_slice(name);
306    }
307
308    /// Writes the postamble (§642) and returns the finished file.
309    pub fn finish(&mut self, engine: &mut Engine) -> Vec<u8> {
310        self.out(op::POST);
311        self.four(self.last_bop);
312        self.last_bop = self.buf.len() as i32 - 5;
313        self.four(NUM);
314        self.four(DEN);
315        engine.prepare_mag();
316        self.four(engine.eqtb.int(ip::MAG));
317        self.four(self.max_v);
318        self.four(self.max_h);
319        self.bytes(self.max_push.clamp(0, 0xffff) as u32, 2);
320        self.bytes(self.pages & 0xffff, 2);
321        for f in (1..self.used.len()).rev() {
322            if self.used[f] {
323                self.font_def(engine, f as u32);
324            }
325        }
326        self.out(op::POST_POST);
327        self.four(self.last_bop);
328        self.out(ID_BYTE);
329        let k = 4 + (4 - self.buf.len() % 4) % 4;
330        for _ in 0..k {
331            self.out(223);
332        }
333        std::mem::take(&mut self.buf)
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn movement_lengths_follow_tex() {
343        let mut d = DviWriter::default();
344        for w in [127, -127, -128, 32767, -32768, 8388607, -8388608] {
345            d.movement(w, op::RIGHT1);
346        }
347        assert_eq!(
348            d.buf,
349            [
350                143, 127, 143, 129, 144, 255, 128, 144, 127, 255, 145, 255, 128, 0, 145, 127, 255,
351                255, 146, 255, 128, 0, 0
352            ]
353        );
354    }
355
356    #[test]
357    fn push_pop_without_output_cancels() {
358        let mut d = DviWriter::default();
359        d.push(1);
360        d.push(2);
361        d.pop();
362        d.pop();
363        assert!(d.buf.is_empty());
364        assert_eq!(d.max_push, 2);
365        d.push(1);
366        d.rule(5, 0, 1, 1);
367        d.pop();
368        assert_eq!(d.buf.first(), Some(&op::PUSH));
369        assert_eq!(d.buf.last(), Some(&op::POP));
370        assert_eq!((d.h, d.v), (0, 0));
371    }
372}