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 = format!(
185            " LuaTeX output {}.{}.{}:{}{}",
186            int(ip::YEAR),
187            two(int(ip::MONTH)),
188            two(int(ip::DAY)),
189            two(time / 60),
190            two(time % 60)
191        );
192        let c = &comment.as_bytes()[..comment.len().min(255)];
193        self.out(c.len() as u8);
194        self.buf.extend_from_slice(c);
195    }
196
197    /// Finishes a page.
198    pub fn end_page(&mut self) {
199        self.out(op::EOP);
200        self.pages += 1;
201    }
202
203    /// Opens a nested box (`push`); `depth` is its nesting level
204    /// (`cur_s`, 1 for a box inside the page box).
205    pub fn push(&mut self, depth: i32) {
206        if depth > self.max_push {
207            self.max_push = depth;
208        }
209        self.out(op::PUSH);
210        self.stack.push((self.buf.len(), self.h, self.v));
211    }
212
213    /// Records the nesting level of the page box itself (0).
214    pub fn note_depth(&mut self, depth: i32) {
215        if depth > self.max_push {
216            self.max_push = depth;
217        }
218    }
219
220    /// Closes a nested box (`dvi_pop`, §601).
221    pub fn pop(&mut self) {
222        let Some((loc, h, v)) = self.stack.pop() else {
223            return;
224        };
225        if loc == self.buf.len() {
226            self.buf.pop();
227        } else {
228            self.out(op::POP);
229        }
230        self.h = h;
231        self.v = v;
232    }
233
234    /// Typesets character `chr` of font `f` at (`h`, `v`) (§620); the
235    /// reader then advances by `width`.
236    pub fn glyph(&mut self, engine: &Engine, f: u32, chr: u32, h: i32, v: i32, width: i32) {
237        if f == 0 {
238            return;
239        }
240        self.synch(h, v);
241        if self.font != Some(f) {
242            let k = f - 1;
243            let idx = f as usize;
244            if self.used.len() <= idx {
245                self.used.resize(idx + 1, false);
246            }
247            if !self.used[idx] {
248                self.font_def(engine, f);
249                self.used[idx] = true;
250            }
251            if k < 64 {
252                self.out(op::FNT_NUM_0 + k as u8);
253            } else {
254                self.unsigned_cmd(op::FNT1, k);
255            }
256            self.font = Some(f);
257        }
258        if chr < 128 {
259            self.out(chr as u8);
260        } else {
261            self.unsigned_cmd(op::SET1, chr);
262        }
263        self.h = h.wrapping_add(width);
264    }
265
266    /// A rule of the given width and height with its lower left corner at
267    /// (`h`, `v`); the reader then advances by `width`.
268    pub fn rule(&mut self, h: i32, v: i32, width: i32, height: i32) {
269        self.synch(h, v);
270        self.out(op::SET_RULE);
271        self.four(height);
272        self.four(width);
273        self.h = h.wrapping_add(width);
274    }
275
276    /// A `\special` at (`h`, `v`) (§1368).
277    pub fn special(&mut self, h: i32, v: i32, data: &[u8]) {
278        self.synch(h, v);
279        if data.len() < 256 {
280            self.out(op::XXX1);
281            self.out(data.len() as u8);
282        } else {
283            self.out(op::XXX4);
284            self.four(data.len() as i32);
285        }
286        self.buf.extend_from_slice(data);
287    }
288
289    /// A font definition (`dvi_font_def`, §602): checksum, size, design
290    /// size and the file name without its directory.
291    fn font_def(&mut self, engine: &Engine, f: u32) {
292        let fi = engine.font(f);
293        let checksum = fi.checksum;
294        let name = fi.name.rsplit(|&b| b == b'/').next().unwrap_or(&fi.name);
295        let name = &name[..name.len().min(255)];
296        let (size, dsize) = (fi.size, fi.design_size);
297        self.unsigned_cmd(op::FNT_DEF1, f - 1);
298        self.four(checksum as i32);
299        self.four(size);
300        self.four(dsize);
301        self.out(0);
302        self.out(name.len() as u8);
303        self.buf.extend_from_slice(name);
304    }
305
306    /// Writes the postamble (§642) and returns the finished file.
307    pub fn finish(&mut self, engine: &mut Engine) -> Vec<u8> {
308        self.out(op::POST);
309        self.four(self.last_bop);
310        self.last_bop = self.buf.len() as i32 - 5;
311        self.four(NUM);
312        self.four(DEN);
313        engine.prepare_mag();
314        self.four(engine.eqtb.int(ip::MAG));
315        self.four(self.max_v);
316        self.four(self.max_h);
317        self.bytes(self.max_push.clamp(0, 0xffff) as u32, 2);
318        self.bytes(self.pages & 0xffff, 2);
319        for f in (1..self.used.len()).rev() {
320            if self.used[f] {
321                self.font_def(engine, f as u32);
322            }
323        }
324        self.out(op::POST_POST);
325        self.four(self.last_bop);
326        self.out(ID_BYTE);
327        let k = 4 + (4 - self.buf.len() % 4) % 4;
328        for _ in 0..k {
329            self.out(223);
330        }
331        std::mem::take(&mut self.buf)
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn movement_lengths_follow_tex() {
341        let mut d = DviWriter::default();
342        for w in [127, -127, -128, 32767, -32768, 8388607, -8388608] {
343            d.movement(w, op::RIGHT1);
344        }
345        assert_eq!(
346            d.buf,
347            [
348                143, 127, 143, 129, 144, 255, 128, 144, 127, 255, 145, 255, 128, 0, 145, 127, 255,
349                255, 146, 255, 128, 0, 0
350            ]
351        );
352    }
353
354    #[test]
355    fn push_pop_without_output_cancels() {
356        let mut d = DviWriter::default();
357        d.push(1);
358        d.push(2);
359        d.pop();
360        d.pop();
361        assert!(d.buf.is_empty());
362        assert_eq!(d.max_push, 2);
363        d.push(1);
364        d.rule(5, 0, 1, 1);
365        d.pop();
366        assert_eq!(d.buf.first(), Some(&op::PUSH));
367        assert_eq!(d.buf.last(), Some(&op::POP));
368        assert_eq!((d.h, d.v), (0, 0));
369    }
370}