boxworks/ds.rs
1//! Core data structures for the typesetting engine.
2//!
3//! This module contains the fundamental data structures for the Boxworks typesetting engine.
4//! As in TeX, the Boxworks is based around various lists (horizontal, vertical, etc.)
5//! that contains elements (which themselves may be nested lists).
6//! The Rust representations of these lists and their elements are defined here.
7//!
8//! This module implements the entirety of TeX.2021 part 10, "data structures
9//! for boxes and their friends".
10
11use common::GlueOrder;
12use common::Scaled as Number;
13use std::rc::Rc;
14
15/// Element of a horizontal list.
16#[derive(Debug, Clone)]
17pub enum Horizontal {
18 Char(Char),
19 HBox(HBox),
20 VBox(VBox),
21 Rule(Rule),
22 Mark(Mark),
23 Insertion(Insertion),
24 Adjust(Adjust),
25 Ligature(Ligature),
26 Discretionary(Discretionary),
27 Whatsit(Rc<dyn Whatsit>),
28 Math(Math),
29 Glue(Glue),
30 Kern(Kern),
31 Penalty(Penalty),
32}
33
34macro_rules! horizontal_impl {
35 ( $( $variant: ident , )+ ) => {
36 impl PartialEq for Horizontal {
37 fn eq(&self, other: &Self) -> bool {
38 match (self, other) {
39 $(
40 (Self::$variant(l), Self::$variant(r)) => l == r,
41 )+
42 _ => false,
43 }
44 }
45 }
46 $(
47 impl From<$variant> for Horizontal {
48 fn from(value: $variant) -> Self {
49 Horizontal::$variant(value)
50 }
51 }
52 )+
53 };
54}
55
56horizontal_impl!(
57 Char,
58 HBox,
59 VBox,
60 Rule,
61 Mark,
62 Insertion,
63 Adjust,
64 Ligature,
65 Discretionary,
66 Math,
67 Glue,
68 Kern,
69 Penalty,
70);
71
72/// Element of a vertical list.
73#[derive(Clone, Debug)]
74pub enum Vertical {
75 HBox(HBox),
76 VBox(VBox),
77 Rule(Rule),
78 Mark(Mark),
79 Insertion(Insertion),
80 Whatsit(Rc<dyn Whatsit>),
81 Math(Math),
82 Glue(Glue),
83 Kern(Kern),
84 Penalty(Penalty),
85}
86
87macro_rules! vertical_impl {
88 ( $( $variant: ident , )+ ) => {
89 impl PartialEq for Vertical {
90 fn eq(&self, other: &Self) -> bool {
91 match (self, other) {
92 $(
93 (Self::$variant(l), Self::$variant(r)) => l == r,
94 )+
95 _ => false,
96 }
97 }
98 }
99 $(
100 impl From<$variant> for Vertical {
101 fn from(value: $variant) -> Self {
102 Vertical::$variant(value)
103 }
104 }
105 )+
106 };
107}
108
109vertical_impl!(HBox, VBox, Rule, Mark, Insertion, Math, Glue, Kern, Penalty,);
110
111/// A character in a specific font.
112///
113/// This node can only appear in horizontal mode.
114///
115/// Described in TeX.2021.134.
116#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct Char {
118 pub char: char,
119 pub font: u32,
120}
121
122/// A box made from a horizontal list.
123///
124/// Described in TeX.2021.135.
125#[derive(Clone, Debug, PartialEq)]
126pub struct HBox {
127 pub height: Number,
128 pub width: Number,
129 pub depth: Number,
130 /// How much this box should be lowered (if it appears in a horizontal list),
131 /// or how much it should be moved to the right (if it appears in a vertical
132 /// list).
133 pub shift_amount: Number,
134 pub list: Vec<Horizontal>,
135 pub glue_ratio: GlueRatio,
136 pub glue_order: GlueOrder,
137}
138
139/// Pack width specifies how width is handled when packing
140pub enum PackWidth {
141 /// Make the box exactly this width, generally by stretching or shrinking
142 /// glue within the box.
143 Exact(common::Scaled),
144
145 /// Make the box its natural width, plus the additional width specified here.
146 Additional(common::Scaled),
147}
148
149impl HBox {
150 /// Create a horizontal from a vertical list.
151 ///
152 pub fn pack<F: super::FontRepo>(
153 font_repo: &F,
154 list: Vec<Horizontal>,
155 pack_width: PackWidth,
156 ) -> HBox {
157 // This function corresponds to hpack in TeX.2021.649.
158 let mut hbox = HBox {
159 list,
160 ..Default::default()
161 };
162 let mut total_glue = common::Glue::default();
163 let mut natural_width = common::Scaled::ZERO;
164 for elem in &hbox.list {
165 // TeX.2021.658
166 use Horizontal as H;
167 let [w, h, d] = match elem {
168 H::Ligature(Ligature { char, font, .. }) | H::Char(Char { char, font }) => {
169 // TeX.2021.654
170 let Some([w, h, d]) = font_repo.width_height_depth(*char, *font) else {
171 continue;
172 };
173 [w, h, d]
174 }
175 // The next 3 cases are TeX.2021.653.
176 H::HBox(HBox {
177 height,
178 width,
179 depth,
180 shift_amount,
181 ..
182 })
183 | H::VBox(VBox {
184 height,
185 width,
186 depth,
187 shift_amount,
188 ..
189 }) => [*height - *shift_amount, *width, *depth + *shift_amount],
190 H::Rule(Rule {
191 height,
192 width,
193 depth,
194 }) => [*height, *width, *depth],
195 // The next 3 cases are TeX.2021.655
196 H::Mark(_) | H::Insertion(_) | H::Adjust(_) => {
197 todo!("support more nodes here")
198 }
199 H::Discretionary(_discretionary) => {
200 // Do nothing. Discretionaries are only relevant if they are break points.
201 continue;
202 }
203 H::Whatsit(_whatsit) => {
204 // Do nothing for the moment. But maybe support a callback here.
205 // TeX.2021.1360.
206 continue;
207 }
208 H::Math(_math) => {
209 todo!("support math nodes here")
210 }
211 H::Glue(glue) => {
212 // TeX.2021.656
213 use std::cmp::Ordering::*;
214 match total_glue.shrink_order.cmp(&glue.value.shrink_order) {
215 Less => {
216 total_glue.shrink = glue.value.shrink;
217 total_glue.shrink_order = glue.value.shrink_order;
218 }
219 Equal => {
220 total_glue.shrink += glue.value.shrink;
221 }
222 Greater => {
223 // Do nothing.
224 // This glue has smaller order than some other glue in the box, so will
225 // not be used for shrinking.
226 }
227 }
228 match total_glue.stretch_order.cmp(&glue.value.stretch_order) {
229 Less => {
230 total_glue.stretch = glue.value.stretch;
231 total_glue.stretch_order = glue.value.stretch_order;
232 }
233 Equal => {
234 total_glue.stretch += glue.value.stretch;
235 }
236 Greater => {
237 // Do nothing.
238 // This glue has smaller order than some other glue in the box, so will
239 // not be used for stretching.
240 }
241 }
242 // TODO: implement leader support.
243 [glue.value.width, common::Scaled::ZERO, common::Scaled::ZERO]
244 }
245 H::Kern(kern) => [kern.width, common::Scaled::ZERO, common::Scaled::ZERO],
246 H::Penalty(_) => {
247 // Do nothing.
248 continue;
249 }
250 };
251 natural_width += w;
252 if h > hbox.height {
253 hbox.height = h;
254 }
255 if d > hbox.depth {
256 hbox.depth = d;
257 }
258 }
259
260 // TeX.2021.657
261 hbox.width = match pack_width {
262 PackWidth::Exact(exact) => exact,
263 PackWidth::Additional(additional) => natural_width + additional,
264 };
265 let excess = hbox.width - natural_width;
266 use std::cmp::Ordering::*;
267 match excess.cmp(&common::Scaled::ZERO) {
268 Less => {
269 // TeX.2021.664
270 hbox.glue_order = total_glue.shrink_order;
271 if total_glue.shrink_order == GlueOrder::Normal && total_glue.shrink < -excess {
272 // The box is overfull: the glue shrinks by exactly its
273 // shrinkability (TeX sets the glue ratio to unity) and
274 // the content overflows the box.
275 // TODO(TeX.2021.666): report the overfull box and append
276 // the \overfullrule rule.
277 if total_glue.shrink == common::Scaled::ZERO {
278 // It doesn't look like this case exists in Knuth, but it does, subtly.
279 // The key thing is that in the `[total_shrink]==0` branch, Knuth sets the
280 // glue_sign to be normal (i.e., not shrinking or stretching, so zero). This
281 // means that the assignment of 1 to the glue ratio in overfull branch does nothing
282 // because the glue_sign being zero means the ratio is always considered zero.
283 // This was discovered while debugging a failing unit test in the line
284 // breaker.
285 hbox.glue_ratio = GlueRatio {
286 num: common::Scaled::ZERO,
287 den: common::Scaled::ONE,
288 };
289 } else {
290 hbox.glue_ratio = GlueRatio {
291 num: common::Scaled::ONE,
292 den: common::Scaled::ONE,
293 };
294 }
295 } else if total_glue.shrink != common::Scaled::ZERO {
296 hbox.glue_ratio = GlueRatio {
297 num: excess,
298 den: total_glue.shrink,
299 };
300 } else {
301 hbox.glue_ratio = GlueRatio {
302 num: common::Scaled::ZERO,
303 den: common::Scaled::ONE,
304 }
305 }
306 }
307 Equal => {
308 // Do nothing: hbox defaults cover this case.
309 }
310 Greater => {
311 // TeX.2021.658
312 if total_glue.stretch != common::Scaled::ZERO {
313 hbox.glue_order = total_glue.stretch_order;
314 hbox.glue_ratio = GlueRatio {
315 num: excess,
316 den: total_glue.stretch,
317 };
318 }
319 if total_glue.stretch_order == GlueOrder::Normal {
320 // TODO(TeX.2021.660): report an underfull box
321 }
322 }
323 }
324 hbox
325 }
326}
327
328/// Ratio by which glue should shrink or stretch.
329///
330/// This is one of the few (only?) places in Knuth's TeX where a floating point
331/// number is used.
332/// In general TeX uses fixed point integers to ensure that the results are
333/// the same on every computer/CPU.
334/// But the exact semantics of the glue ratio don't affect the output, so
335/// using a float is deemed okay by Knuth.
336///
337/// However we opt to use a real ratio: i.e., a numerator and a denominator.
338///
339/// Described in TeX.2021.109.
340#[derive(Copy, Clone, Debug, Eq)]
341pub struct GlueRatio {
342 pub num: common::Scaled,
343 pub den: common::Scaled,
344}
345
346impl PartialEq for GlueRatio {
347 fn eq(&self, other: &Self) -> bool {
348 // We would prefer to use:
349 // (self.num.0 as i64) * (other.den.0 as i64) == (other.num.0 as i64) * (self.den.0 as i64)
350 // but the maping from ratios to floats and back to ratios is unfortunately
351 // lossy. A better approach might be to "canonicalize" glue ratios when we construct
352 // them. This would be equivalent to writing the string and parsing it back in.
353 let lhs = format!["{}", self];
354 let rhs = format!["{}", other];
355 lhs == rhs
356 }
357}
358
359impl Default for GlueRatio {
360 fn default() -> Self {
361 Self {
362 num: common::Scaled(0),
363 den: common::Scaled(1),
364 }
365 }
366}
367
368impl GlueRatio {
369 pub fn as_float(&self) -> f32 {
370 (self.num.0 as f32) / (self.den.0 as f32)
371 }
372
373 pub fn from_float_str(s: &str) -> Option<Self> {
374 let s = format!("{s}pt");
375 let num = common::Scaled::parse_from_string(&s).ok()?;
376 Some(Self {
377 num,
378 den: common::Scaled::ONE,
379 })
380 }
381}
382
383impl std::fmt::Display for GlueRatio {
384 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385 // TeX.2021.186
386 let g = self.as_float();
387 let g = g.abs();
388 let g = if g.abs() >= 20000.0 { 20000.0 } else { g };
389 let g = ((common::Scaled::ONE.0 as f32) * g).round() as i32;
390 write!(f, "{}", common::Scaled(g).display_no_units())
391 }
392}
393
394impl HBox {
395 /// Returns a hbox node corresponding to the TeX snippet `\hbox{}`.
396 ///
397 /// Described in TeX.2021.136.
398 pub fn new_null_box() -> Self {
399 Self {
400 height: Number::ZERO,
401 width: Number::ZERO,
402 depth: Number::ZERO,
403 shift_amount: Number::ZERO,
404 list: vec![],
405 glue_ratio: Default::default(),
406 glue_order: GlueOrder::Normal,
407 }
408 }
409}
410
411impl Default for HBox {
412 fn default() -> Self {
413 Self::new_null_box()
414 }
415}
416
417/// A box made from a vertical list.
418///
419/// This is the same as [HBox], except the list inside holds [Vertical] nodes
420/// instead of [Horizontal] nodes.
421///
422/// Described in TeX.2021.137.
423#[derive(Clone, Debug, Default, PartialEq)]
424pub struct VBox {
425 pub height: Number,
426 pub width: Number,
427 pub depth: Number,
428 pub shift_amount: Number,
429 pub list: Vec<Vertical>,
430 pub glue_ratio: GlueRatio,
431 pub glue_order: GlueOrder,
432}
433
434impl std::fmt::Display for VBox {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 use crate::lang::convert::ToBoxLang;
437 write!(f, "{}", self.to_box_lang())
438 }
439}
440
441/// A rule stands for a solid black rectangle.
442///
443/// It has width, depth and height fields.
444/// However if any of these dimensions is -2^30, the actual value will be
445/// determined by running rule up to the boundary of the innermost, enclosing box.
446/// This is called a "running dimension".
447/// The width is never running in an hlist; the height and depth are never running
448/// in a vlist.
449///
450/// Described in TeX.2021.138.
451#[derive(Clone, Debug, PartialEq, Eq)]
452pub struct Rule {
453 pub height: Number,
454 pub width: Number,
455 pub depth: Number,
456}
457
458impl Rule {
459 pub const RUNNING: Number = Number(-2 << 30);
460
461 /// Creates a new rule.
462 ///
463 /// All of the dimensions are running.
464 ///
465 /// Described in TeX.2021.139.
466 pub fn new() -> Self {
467 Self {
468 height: Self::RUNNING,
469 width: Self::RUNNING,
470 depth: Self::RUNNING,
471 }
472 }
473}
474
475impl Default for Rule {
476 fn default() -> Self {
477 Self::new()
478 }
479}
480
481/// Vertical material to be inserted.
482///
483/// This node is related to the TeX primitive `\insert`.
484///
485/// Described in TeX.2021.140.
486#[derive(Clone, Debug, PartialEq)]
487pub struct Insertion {
488 pub box_number: u8,
489 /// Slightly misnamed: it actually holds the natural height plus depth
490 /// of the vertical list being inserted.
491 pub height: Number,
492 /// Used in case this insertion is split.
493 pub split_max_depth: Number,
494 pub split_top_skip: common::Glue,
495 /// Penalty to be used if this insertion floats to a subsequent
496 /// page after a split insertion of the same class.
497 pub float_penalty: u32,
498 pub vbox: Vec<Vertical>,
499}
500
501/// Contents of a user's `\mark` text.
502///
503/// TODO: At time of writing I don't know what to do with this node.
504/// In Knuth's TeX it references a token list, but I don't want Boxworks
505/// to depend on Texlang. So for the moment just leaving a dummy list.
506///
507/// Described in TeX.2021.141.
508#[derive(Clone, Debug, PartialEq, Eq)]
509pub struct Mark {
510 pub list: Vec<()>,
511}
512
513/// Specifies material that will be moved out into the surrounding vertical list.
514///
515/// E.g., used to implement the TeX primitive `\vadjust`.
516///
517/// Described in TeX.2021.142.
518#[derive(Clone, Debug, PartialEq)]
519pub struct Adjust {
520 pub list: Vec<Vertical>,
521}
522
523/// A ligature.
524///
525/// Described in TeX.2021.143.
526#[derive(Clone, Debug, PartialEq, Eq)]
527pub struct Ligature {
528 pub included_left_boundary: bool,
529 pub included_right_boundary: bool,
530 pub char: char,
531 pub font: u32,
532 /// The original characters that were replaced by the ligature.
533 /// This is used if the engine needs to break apart the ligature
534 /// in order to perform hyphenation.
535 ///
536 /// While most ligatures come from 2 characters (e.g. ff), TeX's
537 /// lig/kern programming language allows for a single ligature to come
538 /// from arbitrarily many characters.
539 pub original_chars: Rc<str>,
540}
541
542// Two constructors for ligature nodes are provided in TeX.2021.144
543// but they don't seem that useful so I'm omitting them.
544
545/// A discretionary break.
546///
547/// Described in TeX.2021.145.
548#[derive(Clone, Debug, PartialEq)]
549pub struct Discretionary {
550 /// Material to insert before this node, if the break occurs here.
551 pub pre_break: Vec<DiscretionaryElem>,
552 /// Material to insert after this node, if the break occurs here.
553 pub post_break: Vec<DiscretionaryElem>,
554 /// Number of subsequent nodes to skip if the break occurs here.
555 pub replace_count: u32,
556}
557
558impl Discretionary {
559 pub fn new() -> Self {
560 Self {
561 pre_break: vec![],
562 post_break: vec![],
563 replace_count: 0,
564 }
565 }
566}
567
568impl Default for Discretionary {
569 fn default() -> Self {
570 Self::new()
571 }
572}
573
574/// Element of a discretionary list.
575#[derive(Clone, Debug, PartialEq)]
576pub enum DiscretionaryElem {
577 Char(Char),
578 HBox(HBox),
579 VBox(VBox),
580 Rule(Rule),
581 Ligature(Ligature),
582 Kern(Kern),
583}
584
585impl From<Char> for DiscretionaryElem {
586 fn from(value: Char) -> Self {
587 DiscretionaryElem::Char(value)
588 }
589}
590
591impl From<Kern> for DiscretionaryElem {
592 fn from(value: Kern) -> Self {
593 DiscretionaryElem::Kern(value)
594 }
595}
596
597impl From<Ligature> for DiscretionaryElem {
598 fn from(value: Ligature) -> Self {
599 DiscretionaryElem::Ligature(value)
600 }
601}
602
603impl From<DiscretionaryElem> for Horizontal {
604 fn from(value: DiscretionaryElem) -> Self {
605 use DiscretionaryElem as In;
606 use Horizontal as Out;
607 match value {
608 In::Char(char) => Out::Char(char),
609 In::HBox(hbox) => Out::HBox(hbox),
610 In::VBox(vbox) => Out::VBox(vbox),
611 In::Rule(rule) => Out::Rule(rule),
612 In::Ligature(ligature) => Out::Ligature(ligature),
613 In::Kern(kern) => Out::Kern(kern),
614 }
615 }
616}
617
618impl DiscretionaryElem {
619 pub fn width<F: super::FontRepo>(&self, font_width: &F) -> Number {
620 use DiscretionaryElem::*;
621 match self {
622 Char(char) => font_width
623 .width(char.char, char.font)
624 .unwrap_or(common::Scaled::ZERO),
625 HBox(hlist) => hlist.width,
626 VBox(vlist) => vlist.width,
627 Rule(rule) => rule.width,
628 Ligature(ligature) => font_width
629 .width(ligature.char, ligature.font)
630 .unwrap_or(common::Scaled::ZERO),
631 Kern(kern) => kern.width,
632 }
633 }
634}
635
636impl TryFrom<Horizontal> for DiscretionaryElem {
637 type Error = ();
638
639 fn try_from(value: Horizontal) -> Result<Self, Self::Error> {
640 use DiscretionaryElem as Out;
641 use Horizontal::*;
642 let out = match value {
643 Char(char) => Out::Char(char),
644 HBox(hlist) => Out::HBox(hlist),
645 VBox(vlist) => Out::VBox(vlist),
646 Rule(rule) => Out::Rule(rule),
647 Ligature(ligature) => Out::Ligature(ligature),
648 Kern(kern) => Out::Kern(kern),
649 _ => return Err(()),
650 };
651 Ok(out)
652 }
653}
654
655/// A whatsit node
656///
657/// This is used to facilitate extensions to TeX.
658/// It's unclear right now how what the API of it will be, though
659/// it can be figured out by reading the Chapter 53 Extensions of
660/// TeX.
661///
662/// Knuth uses this node type to implement both `\write` and `\special`
663/// so we'll eventually find out.
664///
665/// Described in TeX.2021.146.
666pub trait Whatsit: std::fmt::Debug {
667 // Invoked when this node is invoked when hyphenating.
668 //
669 // This is TeX.2021.1363 but given how we've architected the code, the logic in TeX.2021.1382
670 // (which changes the current language) should run here for \language whatsits.
671 fn hyphenation_hook(&self) {}
672}
673
674/// A marker placed before or after math mode.
675///
676/// Described in TeX.2021.147.
677///
678/// TODO: this also needs a width and so is wrong.
679#[derive(Clone, Copy, Debug, PartialEq, Eq)]
680pub enum Math {
681 Before,
682 After,
683}
684
685impl Horizontal {
686 /// Whether a glue node that comes after this node may be broken.
687 ///
688 /// For char nodes, this function is essentially undefined in Knuth's
689 /// TeX. More specifically, the value depends on the exact character code.
690 /// In TeX this function is never called for char nodes which is why this
691 /// is not a problem. Here, we return `true` for char nodes based on
692 /// my analysis of all places in Knuth's TeX where it is invoked:
693 ///
694 /// - TeX.2021.868: `precedes_break` is called on variable `cur_p` which
695 /// is a pointer to a horizontal list. Before this call, the calling code
696 /// first checks if the node is a character and if so follows the same
697 /// code path. Thus returning `true` here is the right thing to do.
698 ///
699 /// - TeX.2021.973: the function is called on a variable `prev_p` which
700 /// is a pointer to a vertical list and so the char case never arises.
701 ///
702 /// - TeX.2021.1000: same as the last case.
703 ///
704 /// This function is defined in TeX.2021.148.
705 pub fn precedes_break(&self) -> bool {
706 use Horizontal::*;
707 match self {
708 Char(_) | HBox(_) | VBox(_) | Rule(_) | Mark(_) | Insertion(_) | Adjust(_)
709 | Ligature(_) | Discretionary(_) | Whatsit(_) => true,
710 Kern(kern) => kern.kind != KernKind::Explicit,
711 Math(_) | Glue(_) | Penalty(_) => false,
712 }
713 }
714
715 /// Whether this node is discarded after a break.
716 ///
717 /// As with [Self::precedes_break], this function is essentially undefined
718 /// for char nodes in Knuth's TeX. However there is only one call site
719 /// (TeX.2021.879) and in that call site char nodes behave as if this
720 /// function returns true.
721 ///
722 /// This function is defined in TeX.2021.148.
723 pub fn non_discardable(&self) -> bool {
724 self.precedes_break()
725 }
726}
727
728impl Vertical {
729 /// Whether a glue node that comes after this node may be broken.
730 ///
731 /// This function is defined in TeX.2021.148.
732 pub fn precedes_break(&self) -> bool {
733 use Vertical::*;
734 matches!(
735 self,
736 HBox(_) | VBox(_) | Rule(_) | Mark(_) | Insertion(_) | Whatsit(_)
737 )
738 }
739}
740
741/// A piece of glue.
742///
743/// Described in TeX.2021.149.
744#[derive(Clone, Debug, PartialEq, Eq)]
745pub struct Glue {
746 pub value: common::Glue,
747 pub kind: GlueKind,
748}
749
750impl From<common::Glue> for Glue {
751 fn from(value: common::Glue) -> Self {
752 Self {
753 value,
754 kind: Default::default(),
755 }
756 }
757}
758
759/// The kind of a glue node.
760///
761/// Described in TeX.2021.149.
762#[derive(Clone, Debug, Default, PartialEq, Eq)]
763pub enum GlueKind {
764 #[default]
765 Normal,
766 ConditionalMath,
767 Math,
768 AlignedLeader,
769 CenteredLeader,
770 ExpandedLeader,
771}
772
773// TeX.2021.150 and TeX.2021.151 define the [font::Glue] type itself,
774// which is not in this crate.
775
776// Three constructors for glue nodes are provided in TeX.2021.152,
777// TeX.2021.153 and TeX.2021.154 but they don't seem that
778// useful so I'm omitting them.
779
780/// A kern.
781///
782/// Described in TeX.2021.155.
783#[derive(Clone, Debug, PartialEq, Eq)]
784pub struct Kern {
785 pub width: Number,
786 pub kind: KernKind,
787}
788
789/// The kind of a kern node.
790///
791/// Described in TeX.2021.155.
792#[derive(Clone, Copy, Debug, PartialEq, Eq)]
793pub enum KernKind {
794 /// Inserted from font information or math mode calculations.
795 Normal,
796 /// Inserted using e.g. TeX's `\kern` primitive.
797 Explicit,
798 /// Inserted from non-math accents.
799 Accent,
800 /// Inserted from e.g. `\mkern` specifications in math formulas.
801 Math,
802}
803
804// A constructor for kern nodes is provided in TeX.2021.156,
805// but it doesn't seem useful.
806
807/// A penalty.
808///
809/// Described in TeX.2021.157.
810#[derive(Clone, Debug, PartialEq, Eq)]
811pub struct Penalty(pub i32);
812
813impl Penalty {
814 /// Any penalty bigger than this is considered infinite and no
815 /// break will be allowed for such high values.
816 pub const INFINITE: Penalty = Penalty(10000);
817
818 /// Any penalty smaller than this will result in a forced break.
819 pub const EJECT: Penalty = Penalty(-10000);
820}
821
822// A constructor for penalty nodes is provided in TeX.2021.157,
823// but it doesn't seem useful.
824
825// TODO: Unset node(s) in TeX.2021.159
826
827pub fn short_display_hlist(w: &mut dyn std::fmt::Write, hlist: &[Horizontal]) -> std::fmt::Result {
828 // TeX.2021.174
829 let mut i = 0;
830 while let Some(elem) = hlist.get(i) {
831 use Horizontal::*;
832 match elem {
833 Char(char) => write!(w, "{}", char.char)?,
834 HBox(_) | VBox(_) | Whatsit(_) | Mark(_) | Adjust(_) => write!(w, "[]")?,
835 Rule(_) => write!(w, "|")?,
836 Ligature(ligature) => write!(w, "{}", ligature.original_chars)?,
837 Discretionary(discretionary) => {
838 short_display_dlist(w, &discretionary.pre_break)?;
839 short_display_dlist(w, &discretionary.post_break)?;
840 i += discretionary.replace_count as usize;
841 }
842 Math(_) => write!(w, "$")?,
843 Glue(glue) => {
844 if !glue.value.is_zero() {
845 write!(w, " ")?;
846 }
847 }
848 Kern(_) | Penalty(_) | Insertion(_) => {}
849 };
850 i += 1;
851 }
852 Ok(())
853}
854
855fn short_display_dlist(
856 w: &mut dyn std::fmt::Write,
857 dlist: &[DiscretionaryElem],
858) -> std::fmt::Result {
859 // TeX.2021.174
860 let mut i = 0;
861 while let Some(elem) = dlist.get(i) {
862 use DiscretionaryElem::*;
863 match elem {
864 Char(char) => write!(w, "{}", char.char)?,
865 HBox(_) | VBox(_) => write!(w, "[]")?,
866 Rule(_) => write!(w, "|")?,
867 Ligature(ligature) => write!(w, "{}", ligature.original_chars)?,
868 Kern(_) => {}
869 };
870 i += 1;
871 }
872 Ok(())
873}