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 hbox.glue_ratio = GlueRatio {
272 num: excess,
273 den: total_glue.shrink,
274 };
275 }
276 Equal => {
277 // Do nothing: hbox defaults cover this case.
278 }
279 Greater => {
280 // TeX.2021.658
281 if total_glue.stretch != common::Scaled::ZERO {
282 hbox.glue_order = total_glue.stretch_order;
283 hbox.glue_ratio = GlueRatio {
284 num: excess,
285 den: total_glue.stretch,
286 };
287 }
288 if total_glue.stretch_order == GlueOrder::Normal {
289 // TODO(TeX.2021.660): report an underfull box
290 }
291 }
292 }
293 hbox
294 }
295}
296
297/// Ratio by which glue should shrink or stretch.
298///
299/// This is one of the few (only?) places in Knuth's TeX where a floating point
300/// number is used.
301/// In general TeX uses fixed point integers to ensure that the results are
302/// the same on every computer/CPU.
303/// But the exact semantics of the glue ratio don't affect the output, so
304/// using a float is deemed okay by Knuth.
305///
306/// However we opt to use a real ratio: i.e., a numerator and a denominator.
307///
308/// Described in TeX.2021.109.
309#[derive(Copy, Clone, Debug, Eq)]
310pub struct GlueRatio {
311 pub num: common::Scaled,
312 pub den: common::Scaled,
313}
314
315impl PartialEq for GlueRatio {
316 fn eq(&self, other: &Self) -> bool {
317 // We would prefer to use:
318 // (self.num.0 as i64) * (other.den.0 as i64) == (other.num.0 as i64) * (self.den.0 as i64)
319 // but the maping from ratios to floats and back to ratios is unfortunately
320 // lossy. A better approach might be to "canonicalize" glue ratios when we construct
321 // them. This would be equivalent to writing the string and parsing it back in.
322 let lhs = format!["{}", self];
323 let rhs = format!["{}", other];
324 lhs == rhs
325 }
326}
327
328impl Default for GlueRatio {
329 fn default() -> Self {
330 Self {
331 num: common::Scaled(0),
332 den: common::Scaled(1),
333 }
334 }
335}
336
337impl GlueRatio {
338 pub fn as_float(&self) -> f32 {
339 (self.num.0 as f32) / (self.den.0 as f32)
340 }
341
342 pub fn from_float_str(s: &str) -> Option<Self> {
343 let s = format!("{s}pt");
344 let num = common::Scaled::parse_from_string(&s).ok()?;
345 Some(Self {
346 num,
347 den: common::Scaled::ONE,
348 })
349 }
350}
351
352impl std::fmt::Display for GlueRatio {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 // TeX.2021.186
355 let g = self.as_float();
356 let g = g.abs();
357 let g = if g.abs() >= 20000.0 { 20000.0 } else { g };
358 let g = ((common::Scaled::ONE.0 as f32) * g).round() as i32;
359 write!(f, "{}", common::Scaled(g).display_no_units())
360 }
361}
362
363impl HBox {
364 /// Returns a hbox node corresponding to the TeX snippet `\hbox{}`.
365 ///
366 /// Described in TeX.2021.136.
367 pub fn new_null_box() -> Self {
368 Self {
369 height: Number::ZERO,
370 width: Number::ZERO,
371 depth: Number::ZERO,
372 shift_amount: Number::ZERO,
373 list: vec![],
374 glue_ratio: Default::default(),
375 glue_order: GlueOrder::Normal,
376 }
377 }
378}
379
380impl Default for HBox {
381 fn default() -> Self {
382 Self::new_null_box()
383 }
384}
385
386/// A box made from a vertical list.
387///
388/// This is the same as [HBox], except the list inside holds [Vertical] nodes
389/// instead of [Horizontal] nodes.
390///
391/// Described in TeX.2021.137.
392#[derive(Clone, Debug, Default, PartialEq)]
393pub struct VBox {
394 pub height: Number,
395 pub width: Number,
396 pub depth: Number,
397 pub shift_amount: Number,
398 pub list: Vec<Vertical>,
399 pub glue_ratio: GlueRatio,
400 pub glue_order: GlueOrder,
401}
402
403/// A rule stands for a solid black rectangle.
404///
405/// It has width, depth and height fields.
406/// However if any of these dimensions is -2^30, the actual value will be
407/// determined by running rule up to the boundary of the innermost, enclosing box.
408/// This is called a "running dimension".
409/// The width is never running in an hlist; the height and depth are never running
410/// in a vlist.
411///
412/// Described in TeX.2021.138.
413#[derive(Clone, Debug, PartialEq, Eq)]
414pub struct Rule {
415 pub height: Number,
416 pub width: Number,
417 pub depth: Number,
418}
419
420impl Rule {
421 pub const RUNNING: Number = Number(-2 << 30);
422
423 /// Creates a new rule.
424 ///
425 /// All of the dimensions are running.
426 ///
427 /// Described in TeX.2021.139.
428 pub fn new() -> Self {
429 Self {
430 height: Self::RUNNING,
431 width: Self::RUNNING,
432 depth: Self::RUNNING,
433 }
434 }
435}
436
437impl Default for Rule {
438 fn default() -> Self {
439 Self::new()
440 }
441}
442
443/// Vertical material to be inserted.
444///
445/// This node is related to the TeX primitive `\insert`.
446///
447/// Described in TeX.2021.140.
448#[derive(Clone, Debug, PartialEq)]
449pub struct Insertion {
450 pub box_number: u8,
451 /// Slightly misnamed: it actually holds the natural height plus depth
452 /// of the vertical list being inserted.
453 pub height: Number,
454 /// Used in case this insertion is split.
455 pub split_max_depth: Number,
456 pub split_top_skip: common::Glue,
457 /// Penalty to be used if this insertion floats to a subsequent
458 /// page after a split insertion of the same class.
459 pub float_penalty: u32,
460 pub vbox: Vec<Vertical>,
461}
462
463/// Contents of a user's `\mark` text.
464///
465/// TODO: At time of writing I don't know what to do with this node.
466/// In Knuth's TeX it references a token list, but I don't want Boxworks
467/// to depend on Texlang. So for the moment just leaving a dummy list.
468///
469/// Described in TeX.2021.141.
470#[derive(Clone, Debug, PartialEq, Eq)]
471pub struct Mark {
472 pub list: Vec<()>,
473}
474
475/// Specifies material that will be moved out into the surrounding vertical list.
476///
477/// E.g., used to implement the TeX primitive `\vadjust`.
478///
479/// Described in TeX.2021.142.
480#[derive(Clone, Debug, PartialEq)]
481pub struct Adjust {
482 pub list: Vec<Vertical>,
483}
484
485/// A ligature.
486///
487/// Described in TeX.2021.143.
488#[derive(Clone, Debug, PartialEq, Eq)]
489pub struct Ligature {
490 pub included_left_boundary: bool,
491 pub included_right_boundary: bool,
492 pub char: char,
493 pub font: u32,
494 /// The original characters that were replaced by the ligature.
495 /// This is used if the engine needs to break apart the ligature
496 /// in order to perform hyphenation.
497 ///
498 /// While most ligatures come from 2 characters (e.g. ff), TeX's
499 /// lig/kern programming language allows for a single ligature to come
500 /// from arbitrarily many characters.
501 pub original_chars: Rc<str>,
502}
503
504// Two constructors for ligature nodes are provided in TeX.2021.144
505// but they don't seem that useful so I'm omitting them.
506
507/// A discretionary break.
508///
509/// Described in TeX.2021.145.
510#[derive(Clone, Debug, PartialEq)]
511pub struct Discretionary {
512 /// Material to insert before this node, if the break occurs here.
513 pub pre_break: Vec<DiscretionaryElem>,
514 /// Material to insert after this node, if the break occurs here.
515 pub post_break: Vec<DiscretionaryElem>,
516 /// Number of subsequent nodes to skip if the break occurs here.
517 pub replace_count: u32,
518}
519
520impl Discretionary {
521 pub fn new() -> Self {
522 Self {
523 pre_break: vec![],
524 post_break: vec![],
525 replace_count: 0,
526 }
527 }
528}
529
530impl Default for Discretionary {
531 fn default() -> Self {
532 Self::new()
533 }
534}
535
536/// Element of a discretionary list.
537#[derive(Clone, Debug, PartialEq)]
538pub enum DiscretionaryElem {
539 Char(Char),
540 HBox(HBox),
541 VBox(VBox),
542 Rule(Rule),
543 Ligature(Ligature),
544 Kern(Kern),
545}
546
547impl From<DiscretionaryElem> for Horizontal {
548 fn from(value: DiscretionaryElem) -> Self {
549 use DiscretionaryElem as In;
550 use Horizontal as Out;
551 match value {
552 In::Char(char) => Out::Char(char),
553 In::HBox(hbox) => Out::HBox(hbox),
554 In::VBox(vbox) => Out::VBox(vbox),
555 In::Rule(rule) => Out::Rule(rule),
556 In::Ligature(ligature) => Out::Ligature(ligature),
557 In::Kern(kern) => Out::Kern(kern),
558 }
559 }
560}
561
562impl DiscretionaryElem {
563 pub fn width<F: super::FontRepo>(&self, font_width: &F) -> Number {
564 use DiscretionaryElem::*;
565 match self {
566 Char(char) => font_width
567 .width(char.char, char.font)
568 .unwrap_or(common::Scaled::ZERO),
569 HBox(hlist) => hlist.width,
570 VBox(vlist) => vlist.width,
571 Rule(rule) => rule.width,
572 Ligature(ligature) => font_width
573 .width(ligature.char, ligature.font)
574 .unwrap_or(common::Scaled::ZERO),
575 Kern(kern) => kern.width,
576 }
577 }
578}
579
580impl TryFrom<Horizontal> for DiscretionaryElem {
581 type Error = ();
582
583 fn try_from(value: Horizontal) -> Result<Self, Self::Error> {
584 use DiscretionaryElem as Out;
585 use Horizontal::*;
586 let out = match value {
587 Char(char) => Out::Char(char),
588 HBox(hlist) => Out::HBox(hlist),
589 VBox(vlist) => Out::VBox(vlist),
590 Rule(rule) => Out::Rule(rule),
591 Ligature(ligature) => Out::Ligature(ligature),
592 Kern(kern) => Out::Kern(kern),
593 _ => return Err(()),
594 };
595 Ok(out)
596 }
597}
598
599/// A whatsit node
600///
601/// This is used to facilitate extensions to TeX.
602/// It's unclear right now how what the API of it will be, though
603/// it can be figured out by reading the Chapter 53 Extensions of
604/// TeX.
605///
606/// Knuth uses this node type to implement both `\write` and `\special`
607/// so we'll eventually find out.
608///
609/// Described in TeX.2021.146.
610pub trait Whatsit: std::fmt::Debug {
611 // Invoked when this node is invoked when hyphenating.
612 //
613 // This is TeX.2021.1363 but given how we've architected the code, the logic in TeX.2021.1382
614 // (which changes the current language) should run here for \language whatsits.
615 fn hyphenation_hook(&self) {}
616}
617
618/// A marker placed before or after math mode.
619///
620/// Described in TeX.2021.147.
621///
622/// TODO: this also needs a width and so is wrong.
623#[derive(Clone, Copy, Debug, PartialEq, Eq)]
624pub enum Math {
625 Before,
626 After,
627}
628
629impl Horizontal {
630 /// Whether a glue node that comes after this node may be broken.
631 ///
632 /// For char nodes, this function is essentially undefined in Knuth's
633 /// TeX. More specifically, the value depends on the exact character code.
634 /// In TeX this function is never called for char nodes which is why this
635 /// is not a problem. Here, we return `true` for char nodes based on
636 /// my analysis of all places in Knuth's TeX where it is invoked:
637 ///
638 /// - TeX.2021.868: `precedes_break` is called on variable `cur_p` which
639 /// is a pointer to a horizontal list. Before this call, the calling code
640 /// first checks if the node is a character and if so follows the same
641 /// code path. Thus returning `true` here is the right thing to do.
642 ///
643 /// - TeX.2021.973: the function is called on a variable `prev_p` which
644 /// is a pointer to a vertical list and so the char case never arises.
645 ///
646 /// - TeX.2021.1000: same as the last case.
647 ///
648 /// This function is defined in TeX.2021.148.
649 pub fn precedes_break(&self) -> bool {
650 use Horizontal::*;
651 match self {
652 Char(_) | HBox(_) | VBox(_) | Rule(_) | Mark(_) | Insertion(_) | Adjust(_)
653 | Ligature(_) | Discretionary(_) | Whatsit(_) => true,
654 Kern(kern) => kern.kind != KernKind::Explicit,
655 Math(_) | Glue(_) | Penalty(_) => false,
656 }
657 }
658
659 /// Whether this node is discarded after a break.
660 ///
661 /// As with [Self::precedes_break], this function is essentially undefined
662 /// for char nodes in Knuth's TeX. However there is only one call site
663 /// (TeX.2021.879) and in that call site char nodes behave as if this
664 /// function returns true.
665 ///
666 /// This function is defined in TeX.2021.148.
667 pub fn non_discardable(&self) -> bool {
668 self.precedes_break()
669 }
670}
671
672impl Vertical {
673 /// Whether a glue node that comes after this node may be broken.
674 ///
675 /// This function is defined in TeX.2021.148.
676 pub fn precedes_break(&self) -> bool {
677 use Vertical::*;
678 matches!(
679 self,
680 HBox(_) | VBox(_) | Rule(_) | Mark(_) | Insertion(_) | Whatsit(_)
681 )
682 }
683}
684
685/// A piece of glue.
686///
687/// Described in TeX.2021.149.
688#[derive(Clone, Debug, PartialEq, Eq)]
689pub struct Glue {
690 pub value: common::Glue,
691 pub kind: GlueKind,
692}
693
694impl From<common::Glue> for Glue {
695 fn from(value: common::Glue) -> Self {
696 Self {
697 value,
698 kind: Default::default(),
699 }
700 }
701}
702
703/// The kind of a glue node.
704///
705/// Described in TeX.2021.149.
706#[derive(Clone, Debug, Default, PartialEq, Eq)]
707pub enum GlueKind {
708 #[default]
709 Normal,
710 ConditionalMath,
711 Math,
712 AlignedLeader,
713 CenteredLeader,
714 ExpandedLeader,
715}
716
717// TeX.2021.150 and TeX.2021.151 define the [font::Glue] type itself,
718// which is not in this crate.
719
720// Three constructors for glue nodes are provided in TeX.2021.152,
721// TeX.2021.153 and TeX.2021.154 but they don't seem that
722// useful so I'm omitting them.
723
724/// A kern.
725///
726/// Described in TeX.2021.155.
727#[derive(Clone, Debug, PartialEq, Eq)]
728pub struct Kern {
729 pub width: Number,
730 pub kind: KernKind,
731}
732
733/// The kind of a kern node.
734///
735/// Described in TeX.2021.155.
736#[derive(Clone, Copy, Debug, PartialEq, Eq)]
737pub enum KernKind {
738 /// Inserted from font information or math mode calculations.
739 Normal,
740 /// Inserted using e.g. TeX's `\kern` primitive.
741 Explicit,
742 /// Inserted from non-math accents.
743 Accent,
744 /// Inserted from e.g. `\mkern` specifications in math formulas.
745 Math,
746}
747
748// A constructor for kern nodes is provided in TeX.2021.156,
749// but it doesn't seem useful.
750
751/// A penalty.
752///
753/// Described in TeX.2021.157.
754#[derive(Clone, Debug, PartialEq, Eq)]
755pub struct Penalty(pub i32);
756
757impl Penalty {
758 /// Any penalty bigger than this is considered infinite and no
759 /// break will be allowed for such high values.
760 pub const INFINITE: Penalty = Penalty(10000);
761
762 /// Any penalty smaller than this will result in a forced break.
763 pub const EJECT: Penalty = Penalty(-10000);
764}
765
766// A constructor for penalty nodes is provided in TeX.2021.157,
767// but it doesn't seem useful.
768
769// TODO: Unset node(s) in TeX.2021.159
770
771pub fn short_display_hlist(w: &mut dyn std::fmt::Write, hlist: &[Horizontal]) -> std::fmt::Result {
772 // TeX.2021.174
773 let mut i = 0;
774 while let Some(elem) = hlist.get(i) {
775 use Horizontal::*;
776 match elem {
777 Char(char) => write!(w, "{}", char.char)?,
778 HBox(_) | VBox(_) | Whatsit(_) | Mark(_) | Adjust(_) => write!(w, "[]")?,
779 Rule(_) => write!(w, "|")?,
780 Ligature(ligature) => write!(w, "{}", ligature.original_chars)?,
781 Discretionary(discretionary) => {
782 short_display_dlist(w, &discretionary.pre_break)?;
783 short_display_dlist(w, &discretionary.post_break)?;
784 i += discretionary.replace_count as usize;
785 }
786 Math(_) => write!(w, "$")?,
787 Glue(glue) => {
788 if !glue.value.is_zero() {
789 write!(w, " ")?;
790 }
791 }
792 Kern(_) | Penalty(_) | Insertion(_) => {}
793 };
794 i += 1;
795 }
796 Ok(())
797}
798
799fn short_display_dlist(
800 w: &mut dyn std::fmt::Write,
801 dlist: &[DiscretionaryElem],
802) -> std::fmt::Result {
803 // TeX.2021.174
804 let mut i = 0;
805 while let Some(elem) = dlist.get(i) {
806 use DiscretionaryElem::*;
807 match elem {
808 Char(char) => write!(w, "{}", char.char)?,
809 HBox(_) | VBox(_) => write!(w, "[]")?,
810 Rule(_) => write!(w, "|")?,
811 Ligature(ligature) => write!(w, "{}", ligature.original_chars)?,
812 Kern(_) => {}
813 };
814 i += 1;
815 }
816 Ok(())
817}