1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
//! Typesetting: shared state, hooks and small helpers.
//!
//! The typesetting routines are spread over several modules:
//!
//! * [`crate::build`] — list building in main control (§1055–§1135);
//! * [`crate::hmode`] — characters, spaces and discretionaries;
//! * [`crate::pack`] — `hpack` and `vpack` (§644–§679);
//! * [`crate::nodeproc`] — LuaTeX's hyphenation, ligaturing and kerning
//! passes;
//! * [`crate::linebreak`] — breaking paragraphs into lines (§813–§890);
//! * [`crate::page`] — `\vsplit`, the page builder, the output routine and
//! `\shipout` (§967–§1028);
//! * [`crate::align`] — alignments (§768–§812);
//! * [`crate::showbox`] — displaying boxes.
use std::collections::HashMap;
use crate::Engine;
use crate::glue::Glue;
use crate::nodes::{AttrList, NodeRef, Nodes};
use crate::scan::{SET_AUX_PREV_DEPTH, ValLevel};
use crate::token::TokenList;
/// The "running" dimension of rules (`null_flag`).
pub const NULL_FLAG: i32 = -0o10000000000;
/// The penalty that forces a break (`eject_penalty`).
pub const EJECT_PENALTY: i32 = -10000;
/// The infinite penalty (`inf_penalty`).
pub const INF_PENALTY: i32 = 10000;
/// Infinite badness (`inf_bad`).
pub const INF_BAD: i32 = 10000;
/// Awful badness (`awful_bad`).
pub const AWFUL_BAD: i32 = 0o7777777777;
/// Deplorable badness (`deplorable`).
pub const DEPLORABLE: i32 = 100000;
/// Mark texts: `\topmark` … `\splitbotmark` for each marks class.
#[derive(Debug, Default, Clone)]
pub struct Marks {
/// For each class: top, first, bot, split first, split bot.
pub classes: HashMap<u32, [Option<TokenList>; 5]>,
}
impl Marks {
/// The mark text of `class` and kind `code` (0–4).
pub fn get(&self, class: u32, code: u32) -> Option<TokenList> {
self.classes
.get(&class)
.and_then(|m| m.get(code as usize).cloned().flatten())
}
/// Sets the mark text of `class` and kind `code`.
pub fn set(&mut self, class: u32, code: usize, t: Option<TokenList>) {
let e = self.classes.entry(class).or_default();
e[code] = t;
}
}
/// Mark kinds as indices into [`Marks`] entries.
pub mod mark_code {
/// `\topmark`.
pub const TOP: usize = 0;
/// `\firstmark`.
pub const FIRST: usize = 1;
/// `\botmark`.
pub const BOT: usize = 2;
/// `\splitfirstmark`.
pub const SPLIT_FIRST: usize = 3;
/// `\splitbotmark`.
pub const SPLIT_BOT: usize = 4;
}
/// What the current page contains (`page_contents`).
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PageContents {
/// Nothing yet.
#[default]
Empty,
/// Only insertions.
InsertsOnly,
/// A box or rule has been seen.
BoxThere,
}
/// An insertion class record on the current page (TeX's page_ins nodes,
/// §981).
#[derive(Debug, Clone, Copy)]
pub struct PageIns {
/// The insertion class.
pub n: u32,
/// Whether the class has been split (`split_up`), as opposed to
/// `inserting`.
pub split_up: bool,
/// Natural height plus depth accumulated so far.
pub height: i32,
/// For split classes: where the split occurs (`broken_ptr`).
pub broken_ptr: NodeRef,
/// The insertion node that is split (`broken_ins`).
pub broken_ins: NodeRef,
/// The most recent insertion of this class (`last_ins_ptr`).
pub last_ins_ptr: NodeRef,
/// The last insertion of this class on the best page
/// (`best_ins_ptr`).
pub best_ins_ptr: NodeRef,
}
/// The state of the page builder (§980–§982).
#[derive(Debug, Default, Clone)]
pub struct PageState {
/// Head of the contribution list.
pub contrib_head: NodeRef,
/// Head of the current page.
pub page_head: NodeRef,
/// Tail of the current page.
pub page_tail: NodeRef,
/// `page_so_far`: goal, total, stretch (normal, fil, fill, filll),
/// shrink, depth. The `fi` stretch is in [`PageState::fi_stretch`].
pub so_far: [i32; 8],
/// Page stretch of order `fi` (LuaTeX).
pub fi_stretch: i32,
/// `page_max_depth`.
pub max_depth: i32,
/// What the page contains.
pub contents: PageContents,
/// The best break found so far (`best_page_break`).
pub best_break: NodeRef,
/// Its cost (`least_page_cost`).
pub least_cost: i32,
/// The page goal at the best break (`best_size`).
pub best_size: i32,
/// `so_far` at the best break (for [`crate::pagenote`]).
pub best_so_far: [i32; 8],
/// Whether the page had infinite stretch at the best break.
pub best_infinite: bool,
/// What the last page break left out (default output mode).
pub last_break: Option<crate::pagenote::PageBreakInfo>,
/// Insertion classes on the page, in increasing class order.
pub ins: Vec<PageIns>,
/// `\lastskip` etc. of the page (`last_glue`); `None` if the last item
/// was not glue.
pub last_glue: Option<Glue>,
/// `last_penalty`.
pub last_penalty: i32,
/// `last_kern`.
pub last_kern: i32,
/// `\lastnodetype` of the page.
pub last_node_type: i32,
}
impl PageState {
/// Allocates the list heads.
pub fn init(&mut self, nodes: &mut Nodes) {
self.contrib_head = nodes.new_temp();
self.page_head = nodes.new_temp();
self.page_tail = self.page_head;
self.last_node_type = -1;
}
/// The page stretch of glue order `o` (LuaTeX numbering).
pub fn stretch(&self, o: u8) -> i32 {
match o {
0 => self.so_far[2],
1 => self.fi_stretch,
2 => self.so_far[3],
3 => self.so_far[4],
_ => self.so_far[5],
}
}
/// Mutable access to the page stretch of order `o`.
pub fn stretch_mut(&mut self, o: u8) -> &mut i32 {
match o {
0 => &mut self.so_far[2],
1 => &mut self.fi_stretch,
2 => &mut self.so_far[3],
3 => &mut self.so_far[4],
_ => &mut self.so_far[5],
}
}
}
/// Mutable state of the typesetting routines.
#[derive(Default)]
pub struct TsState {
/// The nesting string of `show_box` (`cur_length` of TeX's string
/// pool).
pub show_prefix: Vec<u8>,
/// The font assumed at the start of `short_display` (−1 for none).
pub font_in_short_display: i64,
/// Whether the output routine is active.
pub output_active: bool,
/// Head of the adjustment list (`adjust_head`), allocated on first use.
pub adjust_head: NodeRef,
/// Head of the pre-adjustment list (LuaTeX's `\vadjust pre`).
pub pre_adjust_head: NodeRef,
/// Tail of the adjustment list collected by `hpack`, if active.
pub adjust_tail: Option<NodeRef>,
/// Tail of the pre-adjustment list collected by `hpack`, if active.
pub pre_adjust_tail: Option<NodeRef>,
/// Line where the current paragraph or alignment started
/// (`pack_begin_line`), negative for alignments.
pub pack_begin_line: i32,
/// Items removed by the page builder and `\vsplit` (`\pagediscards`,
/// `\splitdiscards`).
pub disc_ptr: [NodeRef; 4],
/// Cached current attribute list: (generation, list).
pub attr_cache: Option<(u64, Option<AttrList>)>,
/// The attributes new nodes get instead of the current ones, while
/// `mlist_to_hlist` builds nodes for a noad (LuaTeX gives them the
/// attributes of the noad or kernel they come from).
pub attr_override: Option<Option<AttrList>>,
/// The attributes of the boxes being built (indexed from the save
/// stack).
pub box_attrs: Vec<Option<AttrList>>,
/// Alignment state.
pub align: crate::align::AlignState,
/// Stretch and shrink totals of the most recent `hpack`/`vpack`.
pub last_totals: ([i32; 5], [i32; 5]),
/// The last line box made by `line_break` (`just_box`).
pub just_box: NodeRef,
/// The state of `mlist_to_hlist`.
pub math: crate::mlist::MathState,
}
impl std::fmt::Debug for TsState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TsState")
.field("output_active", &self.output_active)
.finish_non_exhaustive()
}
}
impl Engine {
/// Runs the node-list filter callback `cb` (LuaTeX's
/// `pre_linebreak_filter`, `hpack_filter`, …) on the list starting at
/// `head`; returns the new first node. A callback returning `false`
/// discards the list.
pub fn filter_list(&mut self, cb: &str, head: NodeRef, group: &str) -> NodeRef {
// Observed: LuaTeX does not call the list callbacks for an empty
// list (`\hbox{}`, `\vbox{}`), so they never get nil.
if head.is_null() {
return head;
}
match self.run_list_filter(cb, head, group) {
crate::ListFilter::Keep(h) => {
if !h.is_null() {
self.nodes.set_prev(h, NodeRef::NULL);
}
h
}
crate::ListFilter::Flush => {
self.nodes.flush_list(head);
NodeRef::NULL
}
}
}
/// Whether the current page and contributions are empty.
pub fn page_is_empty(&self) -> bool {
self.page.page_head == self.page.page_tail
&& self.nodes.next(self.page.contrib_head).is_null()
}
/// Reports a command that is not implemented yet.
pub fn not_implemented(&mut self, what: &str) {
self.print_err("retex: not implemented yet: ");
self.printer.print_str(what);
self.help(&["This command is not supported by retex yet."]);
self.error();
}
/// The current attribute list (`current_attribute_list`).
pub fn cur_attr(&mut self) -> Option<AttrList> {
let generation = self.eqtb.attr_generation;
if let Some((g, a)) = &self.ts.attr_cache
&& *g == generation
{
return a.clone();
}
let mut v: Vec<(u32, i32)> = Vec::new();
for (i, s) in self.eqtb.attr.iter().enumerate() {
if s.val != crate::eqtb::UNUSED_ATTRIBUTE {
v.push((i as u32, s.val));
}
}
let a: Option<AttrList> = if v.is_empty() {
None
} else {
Some(std::rc::Rc::from(v))
};
self.ts.attr_cache = Some((generation, a.clone()));
a
}
/// Allocates a node carrying the current attributes.
pub fn new_node(&mut self, id: u8, subtype: u16, data: crate::nodes::NodeData) -> NodeRef {
let p = self.nodes.alloc(id, subtype, data);
self.give_new_attr(p);
p
}
/// Gives new node `p` the current attributes, or those of the noad
/// `mlist_to_hlist` is building nodes for.
#[inline]
pub fn give_new_attr(&mut self, p: NodeRef) {
if let Some(a) = &self.ts.attr_override {
let a = a.clone();
self.nodes.set_attr(p, a);
} else if self.eqtb.attrs_in_use {
let a = self.cur_attr();
self.nodes.set_attr(p, a);
}
}
/// `\spacefactor`, `\prevdepth` (`alter_aux`).
pub fn alter_aux(&mut self) {
let c = self.cur_chr;
if (c == SET_AUX_PREV_DEPTH)
!= (self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical)
{
self.report_illegal_case();
return;
}
self.scan_optional_equals();
if c == SET_AUX_PREV_DEPTH {
self.scan_normal_dimen();
self.cur_list_mut().prev_depth = self.cur_val;
} else {
self.scan_int();
if self.cur_val <= 0 || self.cur_val > 32767 {
self.print_err("Bad space factor");
self.help(&["I allow only values in the range 1..32767 here."]);
self.int_error(self.cur_val);
} else {
self.cur_list_mut().space_factor = self.cur_val;
}
}
}
/// `\prevgraf` (`alter_prev_graf`).
pub fn alter_prev_graf(&mut self) {
self.scan_optional_equals();
self.scan_int();
let v = self.cur_val;
if v < 0 {
self.print_err("Bad ");
self.print_esc_str("prevgraf");
self.help(&["I allow only nonnegative values here."]);
self.int_error(v);
} else {
// TeX stores \prevgraf in the innermost non-math level.
let mut p = self.nest.len() - 1;
while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
p -= 1;
}
self.nest[p].prev_graf = v;
}
}
/// `\pagegoal` etc. (`alter_page_so_far`).
pub fn alter_page_so_far(&mut self) {
let c = self.cur_chr as usize;
self.scan_optional_equals();
self.scan_normal_dimen();
if c < self.page.so_far.len() {
self.page.so_far[c] = self.cur_val;
}
}
/// `\deadcycles`, `\insertpenalties` assignments (`alter_integer`) are
/// in the prefixed-command code; `\wd`, `\ht`, `\dp` assignments
/// (`alter_box_dimen`).
pub fn alter_box_dimen(&mut self) {
let c = self.cur_chr;
self.scan_register_num();
let b = self.eqtb.boxes[self.cur_val as usize].val;
self.scan_optional_equals();
self.scan_normal_dimen();
let v = self.cur_val;
if let Some(bd) = self.nodes.box_data_mut(b) {
match c {
2 => bd.width = v,
3 => bd.depth = v,
_ => bd.height = v,
}
}
}
/// `\spacefactor`/`\prevdepth` values for `\the`.
pub fn fetch_aux(&mut self, m: u32) -> Option<(i32, ValLevel)> {
let vertical = self.mode.abs_kind() == crate::maincontrol::ModeKind::Vertical;
if (m == SET_AUX_PREV_DEPTH) != vertical {
self.print_err("Improper ");
self.print_cmd_chr(crate::cmd::Cmd::SetAux, m);
self.help(&[
"You can refer to \\spacefactor only in horizontal mode;",
"you can refer to \\prevdepth only in vertical mode; and",
"neither of these is meaningful inside \\write. So",
"I'm forgetting what you said and using zero instead.",
]);
self.error();
return Some((
0,
if m == SET_AUX_PREV_DEPTH {
ValLevel::Dimen
} else {
ValLevel::Int
},
));
}
let cl = self.cur_list();
if m == SET_AUX_PREV_DEPTH {
Some((cl.prev_depth, ValLevel::Dimen))
} else {
Some((cl.space_factor, ValLevel::Int))
}
}
/// `\prevgraf` value.
pub fn fetch_prev_graf(&mut self) -> i32 {
if self.mode == crate::maincontrol::Mode::None {
return 0;
}
let mut p = self.nest.len() - 1;
while self.nest[p].mode.abs_kind() == crate::maincontrol::ModeKind::Math && p > 0 {
p -= 1;
}
self.nest[p].prev_graf
}
/// `\pagegoal` etc. values.
pub fn page_so_far_value(&self, m: u32) -> i32 {
// tex.web §421: on an empty page, outside the output routine,
// \pagegoal is \maxdimen and the other values are zero.
if self.page.contents == PageContents::Empty && !self.ts.output_active {
return if m == 0 { retex_arith::MAX_DIMEN } else { 0 };
}
self.page.so_far.get(m as usize).copied().unwrap_or(0)
}
/// `\lastpenalty`, `\lastkern`, `\lastskip`, `\lastnodetype` (§424).
pub fn fetch_last_node_item(&mut self, m: u32) -> (i32, crate::glue::Glue, ValLevel) {
use crate::nodes::NodeData;
use crate::scan::last_item as li;
let level = match m {
li::LAST_KERN => ValLevel::Dimen,
li::LAST_SKIP => ValLevel::Glue,
_ => ValLevel::Int,
};
let tail = self.cur_list().tail;
let head = self.cur_list().head;
if m == li::LAST_NODE_TYPE {
if tail == head {
if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
return (self.page.last_node_type, Glue::ZERO, level);
}
return (-1, Glue::ZERO, level);
}
let v = etex_node_type(self.nodes.id(tail), self.nodes.subtype(tail));
return (v, Glue::ZERO, level);
}
if tail != head {
match &self.nodes.get(tail).data {
NodeData::Penalty { penalty } if m == li::LAST_PENALTY => {
return (*penalty, Glue::ZERO, level);
}
NodeData::Kern { kern, .. } if m == li::LAST_KERN => {
return (*kern, Glue::ZERO, level);
}
NodeData::Glue { spec, .. } if m == li::LAST_SKIP => {
let l = if self.nodes.get(tail).subtype == crate::nodes::glue_sub::MU_GLUE {
ValLevel::Mu
} else {
level
};
return (spec.width, *spec, l);
}
_ => {}
}
} else if self.mode == crate::maincontrol::Mode::Vertical && self.nest.len() == 1 {
match m {
li::LAST_PENALTY => return (self.page.last_penalty, Glue::ZERO, level),
li::LAST_KERN => return (self.page.last_kern, Glue::ZERO, level),
li::LAST_SKIP => {
if let Some(g) = self.page.last_glue {
return (g.width, g, level);
}
}
_ => {}
}
}
(0, Glue::ZERO, level)
}
/// Whether box `p` is an hlist.
pub fn box_is_hlist(&self, p: NodeRef) -> bool {
self.nodes.id(p) == crate::nodes::id::HLIST
}
/// Current math style (`\mathstyle`), or −1 outside math.
pub fn cur_math_style(&self) -> i32 {
if self.mode.abs_kind() == crate::maincontrol::ModeKind::Math {
self.cur_list().math_style
} else {
-1
}
}
/// `\insertht`: the height of insertion class `n` on the current page.
pub fn insert_height(&self, n: u32) -> i32 {
self.page
.ins
.iter()
.find(|r| r.n == n)
.map_or(0, |r| r.height)
}
}
/// e-TeX's `\lastnodetype` value for a node of LuaTeX type `t`: −1 none,
/// 0 glyph, 1 hlist, 2 vlist, 3 rule, 4 ins, 5 mark, 6 adjust,
/// 7 ligature, 8 disc, 9 whatsit, 10 math, 11 glue, 12 kern,
/// 13 penalty, 14 unset, 15 math mode nodes.
pub fn etex_node_type(t: u8, subtype: u16) -> i32 {
use crate::nodes::id;
match t {
id::GLYPH => {
if subtype & crate::nodes::glyph_sub::LIGATURE != 0 {
7
} else {
0
}
}
id::HLIST => 1,
id::VLIST => 2,
id::RULE => 3,
id::INS => 4,
id::MARK => 5,
id::ADJUST => 6,
id::DISC => 8,
// Observed: a local_par node (at the start of a paragraph, after
// a display, or for a change of \localinterlinepenalty) counts as
// none.
id::LOCAL_PAR => -1,
id::WHATSIT | id::DIR | id::BOUNDARY => 9,
id::MATH => 10,
id::GLUE => 11,
id::KERN | id::MARGIN_KERN => 12,
id::PENALTY => 13,
id::UNSET => 14,
_ => 15,
}
}