Skip to main content

rustex_lib/shipout/
html.rs

1#![allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
2
3use crate::RUSTEX_CSS_URL;
4use crate::engine::extension::CSS;
5use crate::engine::{Font, Types};
6use crate::shipout::state::{
7    Alignment, CharOrStr, Common, FontData, ShipoutNodeH, ShipoutNodeHRow, ShipoutNodeM,
8    ShipoutNodeSVG, ShipoutNodeTable, ShipoutNodeV, SourceRef,
9};
10use crate::utils::{Flex, Margin, VecMap, VecSet};
11use std::borrow::Cow;
12use std::fmt::Write;
13use std::fmt::{Display, Formatter};
14use std::path::Path;
15use tex_engine::engine::fontsystem::Font as FontT;
16use tex_engine::pdflatex::nodes::{NumOrName, PDFColor, PDFImage};
17use tex_engine::tex::nodes::boxes::{HBoxInfo, ToOrSpread, VBoxInfo};
18use tex_engine::tex::nodes::math::MathClass;
19use tex_engine::tex::numerics::{Dim32, TeXDimen};
20use tex_engine::utils::HMap;
21use tex_glyphs::fontstyles::FontModifier;
22
23#[derive(Default)]
24pub enum ImageOptions {
25    #[default]
26    AsIs,
27    ModifyURL(Box<dyn Fn(&Path) -> String>),
28    Embed,
29}
30
31pub struct CompilationDisplay<'a, 'b> {
32    pub(crate) width: i32,
33    pub(crate) indent: u8,
34    pub(crate) color: PDFColor,
35    pub(crate) font: Font,
36    pub(crate) in_link: bool,
37    pub(crate) font_data: &'a HMap<Box<str>, FontData>,
38    pub(crate) attrs: VecMap<Cow<'static, str>, Cow<'static, str>>,
39    pub(crate) styles: VecMap<Cow<'static, str>, Cow<'static, str>>,
40    pub(crate) sourcerefs: bool,
41    pub(crate) image: &'a ImageOptions,
42    pub(crate) f: &'a mut Formatter<'b>,
43    pub(crate) font_info: bool,
44}
45
46macro_rules! node {
47    ($self:ident !$($tk:tt)* ) => {{
48        $self.do_indent()?;$self.indent +=1;
49        node!(@START $self;$($tk)* IND );
50    }};
51    ($self:ident $($tk:tt)* ) => {
52        node!(@START $self;$($tk)*)
53    };
54    (@START $self:ident;<<$tag:expr; $($tk:tt)*) => {
55        write!($self.f,"<{}",$tag)?;
56        node!(@ATTRS $self;$tag; $($tk)*);
57    };
58    (@START $self:ident;<$tag:ident $($tk:tt)*) => {{
59        write!($self.f,"<{}",stringify!($tag))?;
60        node!(@ATTRS $self;stringify!($tag); $($tk)*);
61    }};
62    (@ATTRS $self:ident;$tag:expr; class=$cls:literal?($b:expr) $($tk:tt)*) => {
63        if $b {write!($self.f," class=\"{}\"",$cls)?;}
64        node!(@ATTRS $self;$tag; $($tk)*);
65    };
66    (@ATTRS $self:ident;$tag:expr; class=$cls:literal $($tk:tt)*) => {
67        write!($self.f," class=\"{}\"",$cls)?;
68        node!(@ATTRS $self;$tag; $($tk)*);
69    };
70    (@ATTRS $self:ident;$tag:expr; class=$cls:expr;? $($tk:tt)*) => {
71        if (!$cls.is_empty()) {write!($self.f," class=\"{}\"",$cls)?;}
72        node!(@ATTRS $self;$tag; $($tk)*);
73    };
74    (@ATTRS $self:ident;$tag:expr; class=$cls:expr; $($tk:tt)*) => {
75        write!($self.f," class=\"{}\"",$cls)?;
76        node!(@ATTRS $self;$tag; $($tk)*);
77    };
78    (@ATTRS $self:ident;$tag:expr; ref=$r:ident $($tk:tt)*) => {
79        if $self.sourcerefs { write!($self.f," data-rustex-sourceref=\"{}\"",$r)? }
80        node!(@ATTRS $self;$tag; $($tk)*);
81    };
82    (@ATTRS $self:ident;$tag:expr; $a:literal=$v:expr; $($tk:tt)*) => {
83        write!($self.f," {}=\"{}\"",$a,$v)?;
84        node!(@ATTRS $self;$tag; $($tk)*);
85    };
86    (@ATTRS $self:ident;$tag:expr; ?($v:expr) $($tk:tt)*) => {
87        if let Some((k,v)) = $v {write!($self.f," {}=\"{}\"",k,v)?;}
88        node!(@ATTRS $self;$tag; $($tk)*);
89    };
90    (@ATTRS $self:ident;$tag:expr; $($tk:tt)*) => {
91        for (k,v) in std::mem::take(&mut $self.attrs) {
92            write!($self.f," {}=\"{}\"",k,v)?
93        }
94        node!(@STYLES? $self;$tag; $($tk)*);
95    };
96    (@STYLES? $self:ident;$tag:expr; style:$style:block $($tk:tt)*) => {
97        let mut in_style = false;
98        #[allow(unused_mut)]
99        let mut needs_span:Option<i32> = None;
100        macro_rules! style {
101            (!$s:ident; ($a:expr)=$v:expr) => {{
102                if !in_style {
103                    write!($s.f," style=\"")?;
104                    in_style = true;
105                }
106                write!($s.f,"{}:{};",$a,$v)?;
107            }};
108            ($a:literal=$v:expr) => {{
109                if !in_style {
110                    write!($self.f," style=\"")?;
111                    in_style = true;
112                }
113                write!($self.f,"{}:{};",$a,$v)?;
114            }};
115        }
116        #[allow(unused_macros)]
117        macro_rules! width {
118            ($v:expr) => {
119                if $v == 0 {style!("width"="0");} else if $v != $self.width {
120                    needs_span = Some($self.width);
121                    let pctg = $v as f32 / ($self.width as f32);
122                    $self.width = $v;
123                    style!("--rustex-scale-width"=format_args!("{:.2}",pctg));
124                }
125            }
126        }
127        $style
128        if in_style {
129            if let Some(w) = needs_span {
130                node!(@STYLES! $self;$tag; {$($tk)*} WIDTH=w;);
131            } else {
132                node!(@STYLES! $self;$tag; {$($tk)*});
133            }
134        }
135        else { node!(@STYLES? $self;$tag; $($tk)*); }
136    };
137    (@STYLES? $self:ident;$tag:expr; style:$a:literal=$v:expr; $($tk:tt)*) => {
138        write!($self.f," style=\"{}:{};",$a,$v)?;
139        node!(@STYLES! $self;$tag; {$($tk)*});
140    };
141    (@STYLES? $self:ident;$tag:expr; $($tk:tt)*) => {
142        if !$self.styles.is_empty() {
143            write!($self.f," style=\"")?;
144            $self.do_styles()?;
145            $self.f.write_char('"')?;
146        }
147        node!(@BODY $self;$tag; $($tk)*);
148    };
149    (@STYLES! $self:ident;$tag:expr; {style:$a:literal=$v:expr; $($tk:tt)*} $(WIDTH=$w:expr;)?) => {
150        write!($self.f,"{}:{};",$a,$v)?;
151        node!(@STYLES! $self;$tag; {$($tk)*} $(WIDTH=$w;)?);
152    };
153    (@STYLES! $self:ident;$tag:expr; {$($tk:tt)*} $(WIDTH=$w:expr;)?) => {
154        $self.do_styles()?;
155        $self.f.write_char('"')?;
156        node!(@BODY $self;$tag; $($tk)* $(WIDTH=$w;)?);
157    };
158    (@BODY $self:ident;$tag:expr; />>) => {
159        $self.f.write_str("/>")?
160    };
161    (@BODY $self:ident;$tag:expr; /> $(WIDTH=$w:expr;)?) => {
162        write!($self.f,"></{}>",$tag)?;
163        $( $self.width = $w; )?
164    };
165    (@BODY $self:ident;$tag:expr; /> IND $(WIDTH=$w:expr;)?) => {
166        write!($self.f,"></{}>",$tag)?;
167        $self.indent -= 1;
168        $( $self.width = $w; )?
169    };
170    (@BODY $self:ident;$tag:expr; $b:block/>) => {
171        $self.f.write_char('>')?;
172        $b
173        write!($self.f,"</{}>",$tag)?;
174    };
175    (@BODY $self:ident;$tag:expr; $b:block/> IND) => {
176        $self.f.write_char('>')?;
177        $b
178        $self.indent -= 1;
179        $self.do_indent()?;
180        write!($self.f,"</{}>",$tag)?;
181    };
182    (@BODY $self:ident;$tag:expr; $b:block/> WIDTH=$w:expr;) => {
183        $self.f.write_str("><div class=\"rustex-setwidth\">")?;
184        $b
185        $self.width = $w;
186        write!($self.f,"</div></{}>",$tag)?;
187    };
188    (@BODY $self:ident;$tag:expr; $b:block/> IND WIDTH=$w:expr;) => {
189        $self.f.write_str("><div class=\"rustex-setwidth\">")?;
190        $b
191        $self.indent -= 1;
192        $self.do_indent()?;
193        $self.width = $w;
194        write!($self.f,"</div></{}>",$tag)?;
195    };
196}
197
198impl CompilationDisplay<'_, '_> {
199    pub fn display(
200        &mut self,
201        metas: &[VecMap<String, String>],
202        top: &VecMap<String, String>,
203        css: &[CSS],
204        page_width: i32,
205        out: &[ShipoutNodeV],
206    ) -> std::fmt::Result {
207        self.f.write_str("<!DOCTYPE html>\n<html lang=\"en\"")?;
208        for (k, v) in top.iter() {
209            write!(self.f, " {k}=\"{v}\"")?;
210        }
211        self.f.write_str(">\n<head>\t<meta charset=\"UTF-8\">\n")?;
212        for m in metas {
213            write!(self.f, "\t<meta")?;
214            for (k, v) in m.iter() {
215                write!(self.f, " {k}=\"{v}\"")?;
216            }
217            self.f.write_str(">\n")?;
218        }
219        writeln!(
220            self.f,
221            "\t<link rel=\"stylesheet\" type=\"text/css\" href=\"{RUSTEX_CSS_URL}\">",
222        )?;
223        for c in css {
224            match c {
225                CSS::File(s) => writeln!(
226                    self.f,
227                    "\t<link rel=\"stylesheet\" type=\"text/css\" href=\"{s}\">"
228                )?,
229                CSS::Literal(s) => writeln!(self.f, "\t<style>\n{s}</style>")?,
230            }
231        }
232        let mut fonts = VecSet::default();
233        for (name, d) in self.font_data {
234            //.filter_map(|d| d.1.web.as_ref().map(|s| s.as_ref().ok()).flatten()) {
235            match &d.web {
236                Some((l, _)) => fonts.insert(l),
237                None => writeln!(self.f, "\t<!-- Missing web font for {name} -->")?,
238            }
239        }
240        for font in fonts {
241            //.filter_map(|d| d.1.web.as_ref().map(|s| s.as_ref().ok()).flatten()) {
242            writeln!(self.f, "\t<link rel=\"stylesheet\" href=\"{font}\">")?;
243        }
244        self.f.write_str("</head>")?;
245        write!(
246            self.f,
247            "<body class=\"rustex-body\" style=\"--rustex-text-width:{};--rustex-page-width:{};",
248            Self::dim_to_num(self.width),
249            Self::dim_to_num(page_width)
250        )?;
251        if let Some(font) = self.font_data.get(self.font.filename()) {
252            if let Some((_, css)) = font.web.as_ref() {
253                write!(self.f, "font-family:{css};")?;
254            }
255            write!(
256                self.f,
257                "font-size:{};",
258                Self::dim_to_string(self.font.get_at().0)
259            )?;
260        }
261        self.f.write_str("\">")?;
262        for c in out {
263            self.do_v(c, true)?;
264        }
265        self.f.write_str("\n</body></html>")
266    }
267
268    #[inline]
269    fn dim_to_px(d: i32) -> f32 {
270        d as f32 / 65536.0 * 1.5
271    }
272    #[inline]
273    fn dim_to_num(d: i32) -> impl std::fmt::Display {
274        struct F(i32);
275        impl std::fmt::Display for F {
276            #[inline]
277            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
278                let v = (CompilationDisplay::dim_to_px(self.0) * 100_000.0).round() / 100_000.0;
279                v.fmt(f)
280            }
281        }
282        F(d)
283    }
284    #[inline]
285    fn dim_to_int(d: i32) -> impl std::fmt::Display {
286        struct F(i32);
287        impl std::fmt::Display for F {
288            #[inline]
289            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
290                let v = ((self.0 as f32) / 65536.0 * 1.5).round() as i32;
291                v.fmt(f)
292            }
293        }
294        F(d)
295    }
296    #[inline]
297    fn dim_to_string(d: i32) -> impl std::fmt::Display {
298        struct F(i32);
299        impl std::fmt::Display for F {
300            #[inline]
301            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
302                write!(f, "{}px", CompilationDisplay::dim_to_num(self.0))
303            }
304        }
305        F(d)
306    }
307    #[inline]
308    fn mu_to_string(d: i32) -> impl std::fmt::Display {
309        struct F(i32);
310        impl std::fmt::Display for F {
311            #[inline]
312            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
313                let v = ((self.0 as f32) / 18.0 / 65536.0 * 100_000.0).round() / 100_000.0;
314                write!(f, "{v}em")
315            }
316        }
317        F(d)
318    }
319    #[inline]
320    fn do_indent(&mut self) -> std::fmt::Result {
321        self.f.write_char('\n')?;
322        for _ in 0..=self.indent {
323            self.f.write_str("\t")?;
324        }
325        Ok(())
326    }
327    fn do_styles(&mut self) -> std::fmt::Result {
328        for (k, v) in std::mem::take(&mut self.styles) {
329            write!(self.f, "{k}:{v};")?;
330        }
331        Ok(())
332    }
333
334    fn font_attrs(
335        &mut self,
336        old: &Font,
337        mut style: impl FnMut(&mut Self, &'static str, Cow<'static, str>) -> std::fmt::Result,
338    ) -> std::fmt::Result {
339        let oldd = self.font_data.get(old.filename()).unwrap();
340        let newd = self.font_data.get(self.font.filename()).unwrap();
341        let oldcss = oldd.web.as_ref().map(|(_, c)| c.as_str());
342        let newcss = newd.web.as_ref().map(|(_, c)| c.as_str());
343        if oldcss != newcss {
344            if let Some(c) = newcss {
345                style(self, "font-family", c.to_string().into())?;
346            }
347        }
348        let size = ((self.font.get_at().0 as f32 / (old.get_at().0 as f32)) * 100.0).round();
349        if size != 100.0 {
350            style(self, "font-size", format!("{}%", size).into())?;
351        }
352        let old = oldd.modifiers.unwrap_or_default();
353        let new = newd.modifiers.unwrap_or_default();
354        if new.has(FontModifier::Capitals) && !old.has(FontModifier::Capitals) {
355            style(self, "font-variant", "small-caps".into())?;
356        } else if old.has(FontModifier::Capitals) && !new.has(FontModifier::Capitals) {
357            style(self, "font-variant", "normal".into())?;
358        }
359        if new.has(FontModifier::Bold) && !old.has(FontModifier::Bold) {
360            style(self, "font-weight", "bold".into())?;
361        } else if old.has(FontModifier::Bold) && !new.has(FontModifier::Bold) {
362            style(self, "font-weight", "normal".into())?;
363        }
364        if new.has(FontModifier::Italic) && !old.has(FontModifier::Italic) {
365            style(self, "font-style", "italic".into())?;
366        } else if new.has(FontModifier::Oblique) && !old.has(FontModifier::Oblique) {
367            style(self, "font-style", "oblique".into())?;
368        } else if (old.has(FontModifier::Italic) || old.has(FontModifier::Oblique))
369            && !(new.has(FontModifier::Italic) || new.has(FontModifier::Oblique))
370        {
371            style(self, "font-style", "normal".into())?;
372        }
373        Ok(())
374    }
375
376    fn do_color<N>(
377        &mut self,
378        node: &'static str,
379        color: &PDFColor,
380        children: &Vec<N>,
381        mut f: impl FnMut(&mut Self, &N) -> std::fmt::Result,
382    ) -> std::fmt::Result {
383        if *color == self.color {
384            for c in children {
385                f(self, c)?
386            }
387            Ok(())
388        } else {
389            let old = self.color;
390            self.color = *color;
391            if children.len() == 1 {
392                self.styles
393                    .insert("color".into(), format!("{}", color).into());
394                for c in children {
395                    f(self, c)?
396                }
397            } else {
398                node!(self <<node; class="rustex-contents"?(node!="mrow" && node !="g") style:"color"=color; {
399                    for c in children { f(self,c)? }
400                }/>);
401            }
402            self.color = old;
403            Ok(())
404        }
405    }
406
407    fn do_font<N>(
408        &mut self,
409        node: &'static str,
410        font: &Font,
411        children: &Vec<N>,
412        mut f: impl FnMut(&mut Self, &N) -> std::fmt::Result,
413    ) -> std::fmt::Result {
414        if *font == self.font {
415            for c in children {
416                f(self, c)?
417            }
418            Ok(())
419        } else {
420            let old = std::mem::replace(&mut self.font, font.clone());
421            if children.len() == 1 {
422                self.font_attrs(&old, |s, a, b| {
423                    s.styles.insert(a.into(), b);
424                    Ok(())
425                })?;
426                for c in children {
427                    f(self, c)?
428                }
429            } else {
430                node!(self <<node; class="rustex-contents"?(node!="mrow" && node !="g") style:{
431                    self.font_attrs(&old,|s,k,v| Ok(style!(!s; (k)=v)))?;
432                } {
433                    for c in children { f(self,c)? }
434                }/>);
435            }
436            self.font = old;
437            Ok(())
438        }
439    }
440    fn do_annotations<N>(
441        &mut self,
442        node: &str,
443        attrs: &VecMap<Cow<'static, str>, Cow<'static, str>>,
444        styles: &VecMap<Cow<'static, str>, Cow<'static, str>>,
445        classes: &[Cow<'static, str>],
446        children: &Vec<N>,
447        mut f: impl FnMut(&mut Self, &N) -> std::fmt::Result,
448    ) -> std::fmt::Result {
449        let class_str = || {
450            if classes.is_empty() {
451                if node == "mrow" || node == "g" {
452                    Cow::Borrowed("")
453                } else {
454                    Cow::Borrowed("rustex-contents")
455                }
456            } else {
457                Cow::Owned(classes.join(" "))
458            }
459        };
460        if !attrs.iter().any(|(k, _)| self.attrs.get(k).is_some()) {
461            for (k, v) in attrs.iter() {
462                self.attrs.insert(k.clone(), v.clone());
463            }
464            for (k, v) in styles.iter() {
465                self.styles.insert(k.clone(), v.clone());
466            }
467            node!(self <<node; class=class_str();? {
468                for c in children { f(self,c)? }
469            }/>);
470        } else {
471            node!(self <<node; class=class_str();? {
472                for (k,v) in attrs.iter() {
473                    self.attrs.insert(k.clone(),v.clone());
474                }
475                for (k,v) in styles.iter() {
476                    self.styles.insert(k.clone(),v.clone());
477                }
478                node!(self <<node; class=class_str();? {
479                    for c in children { f(self,c)? }
480                }/>);
481            }/>);
482        }
483        Ok(())
484    }
485
486    fn do_v(&mut self, c: &ShipoutNodeV, top: bool) -> std::fmt::Result {
487        match c {
488            ShipoutNodeV::Common(Common::WithColor {
489                color, children, ..
490            }) => self.do_color("div", color, children, |s, n| s.do_v(n, top)),
491            ShipoutNodeV::Common(Common::WithFont { font, children, .. }) => {
492                self.do_font("div", font, children, |s, n| s.do_v(n, top))
493            }
494            ShipoutNodeV::Common(Common::WithAnnotation {
495                attrs,
496                styles,
497                classes,
498                children,
499                tag,
500                ..
501            }) => self.do_annotations(
502                tag.as_ref().map_or("div", |s| s.as_str()),
503                attrs,
504                styles,
505                &classes.inner,
506                children,
507                |s, n| s.do_v(n, top),
508            ),
509            ShipoutNodeV::Common(Common::Literal(s)) => self.f.write_str(s),
510            ShipoutNodeV::Common(Common::WithLink { children, .. }) if self.in_link => {
511                for c in children {
512                    self.do_v(c, top)?
513                }
514                Ok(())
515            }
516            ShipoutNodeV::Common(Common::WithLink { href, children, .. }) => {
517                node!(self <a class="rustex-link" "href"=href;{
518                    self.in_link = true;
519                    for c in children { self.do_v(c,top)? }
520                    self.in_link = false;
521                }/>);
522                Ok(())
523            }
524            ShipoutNodeV::Common(Common::WithMatrix {
525                scale,
526                rotate,
527                skewx,
528                skewy,
529                children,
530                ..
531            }) => {
532                node!(self <div class="rustex-pdfmatrix" style:"transform"=format_args!("matrix({scale},{rotate},{skewx},{skewy},0,0)"); {
533                for c in children { self.do_v(c,top)? }
534            }/>);
535                Ok(())
536            }
537            ShipoutNodeV::Common(Common::PDFDest(n)) => {
538                match n {
539                    NumOrName::Name(s) => node!(self !<a "id"=s;/>),
540                    NumOrName::Num(n) => {
541                        node!(self !<a "id"=format_args!("NUM_{}",n);/>)
542                    }
543                }
544                Ok(())
545            }
546            ShipoutNodeV::KernSkip(m) => {
547                node!(self !<div class="rustex-vskip" style:{
548                if m.base.is_positive() {
549                    style!("min-height"=Self::dim_to_string(m.base))
550                } else {
551                    style!("margin-bottom"=Self::dim_to_string(m.base))
552                }
553                match m.stretch {
554                    Flex::Fil(_) | Flex::Fill(_) | Flex::Filll(_) =>
555                        style!("margin-top"="auto"),
556                    _ => ()
557                }
558            }/>);
559                match m.stretch {
560                    Flex::Fill(_) => {
561                        node!(self !<div class="rustex-vskip" style:"margin-top"="auto";/>);
562                    }
563                    Flex::Filll(_) => {
564                        node!(self !<div class="rustex-vskip" style:"margin-top"="auto";/>);
565                        node!(self !<div class="rustex-vskip" style:"margin-top"="auto";/>);
566                    }
567                    _ => (),
568                }
569                Ok(())
570            }
571            ShipoutNodeV::Common(Common::VBox {
572                sref,
573                info: info @ VBoxInfo::VBox { .. },
574                children,
575                ..
576            }) => {
577                self.do_indent()?;
578                self.do_vbox(sref, info, children, false, false, top)
579            }
580            ShipoutNodeV::Common(Common::VBox {
581                sref,
582                info: info @ VBoxInfo::VTop { .. },
583                children,
584                ..
585            }) => self.do_vtop(sref, info, children, false, false, top),
586            ShipoutNodeV::Common(Common::HBox {
587                sref,
588                info: info @ HBoxInfo::HBox { .. },
589                children,
590                ..
591            }) => {
592                self.do_indent()?;
593                self.do_hbox(sref, info, false, children)
594            }
595            ShipoutNodeV::HRule {
596                width,
597                height,
598                depth,
599            } => {
600                let ht = height.map(|h| h.0).unwrap_or(26214) + depth.map(|d| d.0).unwrap_or(0);
601                if ht <= 0 {
602                    return Ok(());
603                }
604                let bottom = match depth {
605                    None => None,
606                    Some(d) if d.0 == 0 => None,
607                    Some(d) => Some(-d.0),
608                };
609                let color = self.color;
610
611                node!(self !<div class="rustex-hrule" style:{
612                style!("height"=Self::dim_to_string(ht));
613                match width {
614                    None => style!("min-width"="100%"),
615                    Some(w) => style!("--rustex-scale-width"=(w.0 as f32) / (self.width as f32))
616                }
617            }{node!(self !<div style:{
618                style!("background"=color);
619                style!("height"=Self::dim_to_string(ht));
620                if let Some(b) = bottom {
621                    style!("margin-bottom"=Self::dim_to_string(b));
622                }
623            }/>)}/>);
624                Ok(())
625            }
626            ShipoutNodeV::Paragraph {
627                children,
628                alignment,
629                parskip: _,
630                line_skip: _,
631                left_skip,
632                right_skip,
633                width,
634                sref,
635                ..
636            } => {
637                self.do_indent()?;
638                self.indent += 1;
639                let cls = match width {
640                    i if *i != 0 && *i != self.width => "rustex-paragraph rustex-scalewidth",
641                    0 => todo!(),
642                    _ => "rustex-paragraph rustex-withwidth",
643                };
644                node!(self <div class=cls;ref=sref style:{
645                    if !left_skip.is_zero() {
646                        style!("margin-left"=Self::dim_to_string(left_skip.base))
647                    }
648                    if !right_skip.is_zero() {
649                        style!("margin-right"=Self::dim_to_string(right_skip.base))
650                    }
651                    match alignment {
652                        Alignment::L => style!("text-align"="left"),
653                        Alignment::C => style!("text-align"="center"),
654                        Alignment::R => style!("text-align"="right"),
655                        _ => ()
656                    }
657                    width!(*width);
658                }{for c in children {
659                    self.do_h(c,true,false)?
660                }}/>);
661                self.indent -= 1;
662                Ok(())
663            }
664            ShipoutNodeV::HAlign {
665                children,
666                num_cols,
667                line_skip,
668                ..
669            } => {
670                //let lineht = line_skip.factor(&self.font);
671                node!(self !<table class="rustex-halign"
672                    style:"--rustex-align-num"=num_cols;
673                    //style:"line-height"=lineht;
674                    {
675                    node!(self <tbody {
676                        for c in children {
677                            self.do_row(c)?
678                        }
679                    }/>);
680                    }
681                />);
682                Ok(())
683            }
684            _ => todo!("{c:?}"),
685        }
686    }
687
688    fn do_h(&mut self, c: &ShipoutNodeH, in_para: bool, escape: bool) -> std::fmt::Result {
689        match c {
690            ShipoutNodeH::Common(Common::WithColor {
691                color, children, ..
692            }) => self.do_color("div", color, children, |s, n| s.do_h(n, in_para, escape)),
693            ShipoutNodeH::Common(Common::WithFont { font, children, .. }) => {
694                self.do_font("div", font, children, |s, n| s.do_h(n, in_para, escape))
695            }
696            ShipoutNodeH::Common(Common::WithAnnotation {
697                attrs,
698                styles,
699                classes,
700                children,
701                tag,
702                ..
703            }) => self.do_annotations(
704                tag.as_ref().map_or("div", |s| s.as_str()),
705                attrs,
706                styles,
707                &classes.inner,
708                children,
709                |s, n| s.do_h(n, in_para, escape),
710            ),
711            ShipoutNodeH::Common(Common::Literal(s)) => self.f.write_str(s),
712            ShipoutNodeH::Common(Common::WithLink { children, .. }) if self.in_link => {
713                for c in children {
714                    self.do_h(c, in_para, escape)?
715                }
716                Ok(())
717            }
718            ShipoutNodeH::Common(Common::WithLink { href, children, .. }) => {
719                node!(self <a class="rustex-link" "href"=href;{
720                    self.in_link = true;
721                    for c in children { self.do_h(c,in_para,escape)? }
722                    self.in_link = false;
723                }/>);
724                Ok(())
725            }
726            ShipoutNodeH::Common(Common::WithMatrix {
727                scale,
728                rotate,
729                skewx,
730                skewy,
731                children,
732                ..
733            }) => {
734                node!(self <div class="rustex-pdfmatrix" style:"transform"=format_args!("matrix({scale},{rotate},{skewx},{skewy},0,0)"); {
735                for c in children { self.do_h(c,in_para,escape)? }
736            }/>);
737                Ok(())
738            }
739            ShipoutNodeH::Common(Common::PDFDest(n)) => {
740                match n {
741                    NumOrName::Name(s) => node!(self <a "id"=s;/>),
742                    NumOrName::Num(n) => {
743                        node!(self <a "id"=format_args!("NUM_{}",n);/>)
744                    }
745                }
746                Ok(())
747            }
748            ShipoutNodeH::KernSkip(m) => {
749                node!(self <div class="rustex-hskip" style:{
750                style!("margin-left"=Self::dim_to_string(m.base));
751                match m.stretch {
752                    Flex::Fil(_) | Flex::Fill(_) | Flex::Filll(_) =>
753                        style!("margin-right"="auto"),
754                    _ => ()
755                }
756            }/>);
757                match m.stretch {
758                    Flex::Fill(_) => {
759                        node!(self <div class="rustex-hskip" style:"margin-right"="auto";/>);
760                    }
761                    Flex::Filll(_) => {
762                        node!(self <div class="rustex-hskip" style:"margin-right"="auto";/>);
763                        node!(self <div class="rustex-hskip" style:"margin-right"="auto";/>);
764                    }
765                    _ => (),
766                }
767                Ok(())
768            }
769            ShipoutNodeH::Common(Common::SVG {
770                sref,
771                minx,
772                maxx,
773                miny,
774                maxy,
775                children,
776                ..
777            }) => self.do_svg(sref, *minx, *maxx, *miny, *maxy, children),
778            ShipoutNodeH::Common(Common::HBox {
779                sref,
780                info: info @ HBoxInfo::HBox { .. },
781                children,
782                ..
783            }) => self.do_hbox(sref, info, true, children),
784            ShipoutNodeH::Common(Common::VBox {
785                sref,
786                info: info @ VBoxInfo::VBox { .. },
787                children,
788                ..
789            }) => self.do_vbox(sref, info, children, true, true, false),
790            ShipoutNodeH::Common(Common::VBox {
791                sref,
792                info: info @ VBoxInfo::VTop { .. },
793                children,
794                ..
795            }) => self.do_vtop(sref, info, children, true, true, false),
796            ShipoutNodeH::VRule {
797                width,
798                height,
799                depth,
800            } => {
801                let wd = match width {
802                    Some(w) if w.0 <= 0 => return Ok(()),
803                    Some(w) => w.0,
804                    None => 26214,
805                };
806                let color = self.color;
807                if height.is_none() && depth.is_none() {
808                    node!(self <div class="rustex-vrule" style:{
809                    style!("--rustex-this-width"=Self::dim_to_string(wd));
810                    style!("background"=color);
811                    if escape {
812                        style!("align-self"="stretch");
813                    } else {
814                        let font_size = self.font.get_at().0;
815                        style!("min-height"=Self::dim_to_string(font_size));
816                    }
817                }/>);
818                } else {
819                    let ht = height.map(|h| h.0).unwrap_or(0) + depth.map(|d| d.0).unwrap_or(0);
820                    let dp = if depth.is_none() && !escape {
821                        None
822                    } else {
823                        Some(depth.map(|d| d.0).unwrap_or(0))
824                    };
825                    node!(self <div class="rustex-vrule-container"
826                    style:"height"=Self::dim_to_string(ht);
827                    style:"--rustex-this-width"=Self::dim_to_string(wd);
828                    {node!(self <div style:{
829                        style!("background"=color);
830                        match dp {
831                            Some(dp) => style!("margin-bottom"=Self::dim_to_string(-dp)),
832                            None => {
833                                style!("height"=format_args!("calc(0.5ex + {})",Self::dim_to_string(ht)));
834                                style!("margin-bottom"="-0.5ex");
835                            }
836                        }
837                    }/>)}
838                />);
839                }
840                Ok(())
841            }
842            ShipoutNodeH::Char(c) => {
843                if self.attrs.is_empty() && self.styles.is_empty() {
844                    Display::fmt(&Escaped(c), self.f)?
845                } else {
846                    node!(self <div class="rustex-contents" {Display::fmt(&Escaped(c), self.f)?}/>)
847                }
848                Ok(())
849            }
850            ShipoutNodeH::Space if !escape => {
851                if self.attrs.is_empty() && self.styles.is_empty() {
852                    self.f.write_str(" ")?
853                } else {
854                    node!(self <div class="rustex-contents" {self.f.write_char(' ')?}/>)
855                }
856                Ok(())
857            }
858            ShipoutNodeH::Space => {
859                node!(self <div class="rustex-space-in-hbox" />);
860                Ok(())
861            }
862            ShipoutNodeH::Math {
863                display,
864                sref,
865                children,
866                ..
867            } => self.math_list(display, sref, children),
868
869            ShipoutNodeH::Img(img) => match (&self.image, &img.img) {
870                (ImageOptions::AsIs, PDFImage::PDF(imgfile)) => {
871                    let width = img.width().0;
872                    let height = img.height().0;
873                    let path = format!("{}-rustex.png", img.filepath.display());
874
875                    node!(self <img "src"=path;
876                        "width"=Self::dim_to_int(width);
877                        "height"=Self::dim_to_int(height);
878                    />>);
879                    if !std::path::Path::new(&path).exists() {
880                        let _ = imgfile.save_with_format(path, image::ImageFormat::Png);
881                    }
882                    Ok(())
883                }
884                (ImageOptions::AsIs, _) => {
885                    let width = img.width().0;
886                    let height = img.height().0;
887                    node!(self <img "src"=img.filepath.display();
888                        "width"=Self::dim_to_int(width);
889                        "height"=Self::dim_to_int(height);
890                    />>);
891                    Ok(())
892                }
893                (ImageOptions::ModifyURL(f), _) => {
894                    let width = img.width().0;
895                    let height = img.height().0;
896                    node!(self <img "src"=f(&img.filepath);
897                        "width"=Self::dim_to_int(width);
898                        "height"=Self::dim_to_int(height);
899                    />>);
900                    Ok(())
901                }
902                _ => todo!(),
903            },
904            ShipoutNodeH::Indent(i) => {
905                if *i != 0 {
906                    node!(self <div class="rustex-parindent" style:"margin-left"=Self::dim_to_string(*i);/>);
907                }
908                Ok(())
909            }
910            ShipoutNodeH::LineBreak => self.f.write_str("<br/>"),
911            ShipoutNodeH::MissingGlyph {
912                char, font_name, ..
913            } => {
914                node!(self <span class="rustex-missing-glyph" "title"=format_args!("Missing Glyph {char} in {font_name}");/>);
915                Ok(())
916            }
917            _ => todo!("{c:?}"),
918        }
919    }
920
921    const fn cls(cls: MathClass) -> &'static str {
922        match cls {
923            MathClass::Ord => "rustex-math-ord",
924            MathClass::Op => "rustex-math-op",
925            MathClass::Bin => "rustex-math-bin",
926            MathClass::Rel => "rustex-math-rel",
927            MathClass::Open => "rustex-math-open",
928            MathClass::Close => "rustex-math-close",
929            MathClass::Punct => "rustex-math-punct",
930        }
931    }
932
933    fn math_list(
934        &mut self,
935        display: &Option<(Margin, Margin)>,
936        sref: &SourceRef,
937        children: &[ShipoutNodeM],
938    ) -> std::fmt::Result {
939        if children.len() == 1 {
940            if let Some((sref, width, children, ..)) = children.iter().find_map(|e| {
941                if let ShipoutNodeM::VCenter {
942                    sref,
943                    width,
944                    children,
945                    uses_font,
946                    uses_color,
947                } = e
948                {
949                    Some((sref, width, children, uses_font, uses_color))
950                } else {
951                    None
952                }
953            }) {
954                let oldwd = self.width;
955                node!(self <div style:{
956                    if display.is_some() {
957                        style!("display"="flex");
958                    } else {
959                        style!("display"="inline-flex");
960                    }
961                match *width {
962                    0 => style!("width"="0"),
963                    x if x > 0 => {
964                        self.width = *width;
965                        let wd = Self::dim_to_string(*width);
966                        style!("width"=wd);
967                        if display.is_some() {
968                            style!("margin-left"="auto");
969                            style!("margin-right"="auto");
970                        }
971                        style!("--rustex-curr-width"=wd);
972                        style!("--rustex-this-width"=wd);
973                    }
974                    _ => ()
975                }
976            } {
977                node!(self <div class="rustex-vcenter" ref=sref {
978                    node!(self <div {
979                        for c in children {
980                            self.do_v(c,false)?;
981                        }
982                    } />)
983                }/>);
984            }/>);
985                self.width = oldwd;
986                return Ok(());
987            }
988        }
989        let inner = move |s: &mut Self| {
990            //s.f.write_str("​");
991            node!(s <math class="rustex-math" ref=sref {
992                if children.iter().map(|c| c.num_nodes(s)).sum::<usize>() == 1 {
993                    for c in children {
994                        s.do_indent()?;s.do_math(c,None/*,false */)?
995                    }
996                } else {
997                    node!(s !<mrow {
998                        for c in children {
999                            s.do_indent()?;s.do_math(c,None/*,false */)?
1000                        }
1001                    }/>);
1002                }
1003            }/>);
1004            Ok(())
1005        };
1006
1007        match display {
1008            Some((above, below)) => {
1009                node!(self <div class="rustex-display"
1010                style:"margin-top"=Self::dim_to_string(above.base);
1011                style:"margin-bottom"=Self::dim_to_string(below.base);{
1012                inner(self)?
1013            }/>);
1014                Ok(())
1015            }
1016            None => inner(self),
1017        }
1018    }
1019
1020    fn do_math(
1021        &mut self,
1022        c: &ShipoutNodeM,
1023        cls: Option<MathClass>, /*,cramped:bool */
1024    ) -> std::fmt::Result {
1025        match c {
1026            ShipoutNodeM::Common(Common::Literal(s)) => self.f.write_str(s),
1027            ShipoutNodeM::Common(Common::WithLink { href, children, .. }) => {
1028                node!(self !<mrow "href"=href; {
1029                for c in children { self.do_math(c,cls/*,cramped*/)? }
1030            }/>);
1031                Ok(())
1032            }
1033            ShipoutNodeM::Common(Common::WithColor {
1034                color, children, ..
1035            }) => self.do_color("mrow", color, children, |s, n| {
1036                s.do_math(n, cls /*,cramped*/)
1037            }),
1038            ShipoutNodeM::Common(Common::WithAnnotation {
1039                attrs,
1040                styles,
1041                classes,
1042                children,
1043                tag,
1044                ..
1045            }) => {
1046                self.do_annotations(
1047                    tag.as_ref().map_or("mrow", |s| s.as_str()),
1048                    attrs,
1049                    styles,
1050                    &classes.inner,
1051                    children,
1052                    |s, n| s.do_math(n, cls /*,cramped*/),
1053                )
1054            }
1055            ShipoutNodeM::Common(Common::WithFont { font, children, .. }) => {
1056                // TODO check if this is right - maybe move it down to the next non-math-node...?
1057                self.do_font("mrow", font, children, |s, n| {
1058                    s.do_math(n, cls /*,cramped*/)
1059                })
1060            }
1061            ShipoutNodeM::Common(Common::PDFDest(n)) => {
1062                match n {
1063                    NumOrName::Name(s) => node!(self !<mspace "id"=s;/>),
1064                    NumOrName::Num(n) => {
1065                        node!(self !<mspace "id"=format_args!("NUM_{}",n);/>);
1066                    }
1067                }
1068                Ok(())
1069            }
1070            /*ShipoutNodeM::MSkip{base,mu} if !*mu => {
1071                self.do_indent()?;
1072                node!(self !<mspace class="rustex-mkern" "width"=Self::dim_to_string(*base);/>);
1073                Ok(())
1074            }*/
1075            ShipoutNodeM::MSkip { base, .. } => {
1076                self.do_indent()?;
1077                if *base > 0 {
1078                    node!(self !<mspace class="rustex-mkern" "width"=Self::mu_to_string(*base);/>);
1079                } else {
1080                    let s = Self::mu_to_string(*base);
1081                    node!(self !<mspace class="rustex-mkern" "width"="0"; style:"margin-right"=s;/>);
1082                }
1083                Ok(())
1084            }
1085            ShipoutNodeM::WithClass {
1086                class, children, ..
1087            } => {
1088                if children.iter().map(|c| c.num_nodes(self)).sum::<usize>() == 1 {
1089                    for c in children {
1090                        self.do_math(c, Some(*class) /*,cramped*/)?;
1091                    }
1092                } else if children
1093                    .iter()
1094                    .all(|c| matches!(c, ShipoutNodeM::Glyph { .. }))
1095                {
1096                    let mut display = true;
1097                    let mut cramped = true;
1098                    let mut string = String::new();
1099                    let mut glyphs = children.iter().map(|c| {
1100                        let ShipoutNodeM::Glyph {
1101                            char,
1102                            cramped: c,
1103                            display: d,
1104                            ..
1105                        } = c
1106                        else {
1107                            unreachable!()
1108                        };
1109                        display = display && *d;
1110                        cramped = cramped && *c;
1111                        char
1112                    });
1113                    thread_local! {
1114                        static MAPSTO : std::cell::LazyCell<tex_glyphs::Glyph> = std::cell::LazyCell::new(|| tex_glyphs::Glyph::get("mapsto"));
1115                        static ARROWS: std::cell::LazyCell<[tex_glyphs::Glyph;3]> = std::cell::LazyCell::new(|| [
1116                            tex_glyphs::Glyph::get("a161"),
1117                            tex_glyphs::Glyph::get("arrowright"),
1118                            tex_glyphs::Glyph::get("shortrightarrow"),
1119                        ]);
1120                    }
1121                    // TODO optimize
1122                    while let Some(glyph) = glyphs.next() {
1123                        if MAPSTO.with(|v| glyph.glyph == **v) {
1124                            if let Some(next) = glyphs.next() {
1125                                if ARROWS.with(|a| a.contains(&next.glyph)) {
1126                                    write!(&mut string, "{glyph}")?;
1127                                } else {
1128                                    write!(&mut string, "{glyph}{next}")?;
1129                                }
1130                            } else {
1131                                write!(&mut string, "{glyph}")?;
1132                            }
1133                        } else if let Some(comb) = glyph.as_combinator() {
1134                            if let Some(next) = glyphs.next() {
1135                                let mut s = comb.apply_glyph(&next.glyph);
1136                                if let Some(m) = next.modifiers {
1137                                    use tex_glyphs::fontstyles::FontModifiable;
1138                                    s = s.apply(m).to_string();
1139                                }
1140                                string.push_str(&s);
1141                            } else {
1142                                write!(&mut string, "{glyph}")?;
1143                            }
1144                        } else {
1145                            write!(&mut string, "{glyph}")?;
1146                        }
1147                    }
1148                    let mi = matches!(class, MathClass::Ord);
1149                    let stretchy = display && matches!(class, MathClass::Op);
1150
1151                    if mi {
1152                        node!(self <mi class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&string.into()), self.f)?}/>);
1153                    } else if cramped {
1154                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&string.into()), self.f)?}/>);
1155                    } else if stretchy {
1156                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(MathClass::Op); "stretchy"="true"; {Display::fmt(&Escaped(&string.into()), self.f)?}/>);
1157                    } else {
1158                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(*class); "stretchy"="false"; {Display::fmt(&Escaped(&string.into()), self.f)?}/>);
1159                    }
1160                } else {
1161                    let cls = Self::cls(*class);
1162                    node!(self !<mrow class=cls; {
1163                        for c in children {
1164                            self.do_math(c, Some(*class)/*,cramped*/)?;
1165                        }
1166                    }/>);
1167                }
1168                Ok(())
1169            }
1170            ShipoutNodeM::Common(Common::HBox {
1171                sref,
1172                info: info @ HBoxInfo::HBox { .. },
1173                children,
1174                ..
1175            }) => {
1176                /* let oldwd = self.width;
1177                let wd = info
1178                    .assigned_width()
1179                    .unwrap_or_else(|| info.computed_width().unwrap_or_default())
1180                    .0; */
1181                node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1182               /* match wd {
1183                    0 => style!("width"="0"),
1184                    x if x > 0 => {
1185                        self.width = wd;
1186                        let wd = Self::dim_to_string(wd);
1187                        style!("min-width"=wd);
1188                        style!("--rustex-curr-width"=wd);
1189                    }
1190                    _ => ()
1191                } */
1192            } {
1193                self.do_hbox(sref,info,true,children)?;
1194            }/>);
1195                //self.width = oldwd;
1196                Ok(())
1197            }
1198            ShipoutNodeM::Common(Common::VBox {
1199                sref,
1200                info: info @ VBoxInfo::VTop { .. },
1201                children,
1202                ..
1203            }) => {
1204                /*let oldwd = self.width;
1205                let wd = info
1206                    .assigned_width()
1207                    .unwrap_or_else(|| info.computed_width().unwrap_or_default())
1208                    .0;*/
1209                node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1210                /*match wd {
1211                    0 => style!("width"="0"),
1212                    x if x > 0 => {
1213                        self.width = wd;
1214                        let wd = Self::dim_to_string(wd);
1215                        style!("min-width"=wd);
1216                        style!("--rustex-curr-width"=wd);
1217                    }
1218                    _ => ()
1219                }*/
1220            } {
1221                node!(self <div class="rustex-box-hh" {
1222                    self.do_vtop(sref,info,children,true,false,false)?;
1223                }/>);
1224            }/>);
1225                //self.width = oldwd;
1226                Ok(())
1227            }
1228            ShipoutNodeM::Common(Common::VBox {
1229                sref,
1230                info,
1231                children,
1232                ..
1233            }) => {
1234                /*let oldwd = self.width;
1235                let wd = info
1236                    .assigned_width()
1237                    .unwrap_or_else(|| info.computed_width().unwrap_or_default())
1238                    .0;*/
1239                node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1240                /*match wd {
1241                    0 => style!("width"="0"),
1242                    x if x > 0 => {
1243                        self.width = wd;
1244                        let wd = Self::dim_to_string(wd);
1245                        style!("min-width"=wd);
1246                        style!("--rustex-curr-width"=wd);
1247                    }
1248                    _ => ()
1249                }*/
1250            } {
1251                node!(self <div class="rustex-box-hh" {
1252                    self.do_vbox(sref,info,children,true,false,false)?;
1253                }/>);
1254            }/>);
1255                //self.width = oldwd;
1256                Ok(())
1257            }
1258            ShipoutNodeM::VCenter {
1259                sref,
1260                width,
1261                children,
1262                ..
1263            } => {
1264                let oldwd = self.width;
1265                node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1266                match *width {
1267                    0 => style!("width"="0"),
1268                    x if x > 0 => {
1269                        self.width = *width;
1270                        let wd = Self::dim_to_string(*width);
1271                        style!("width"=wd);
1272                        style!("--rustex-curr-width"=wd);
1273                        style!("--rustex-this-width"=wd);
1274                    }
1275                    _ => ()
1276                }
1277            } {
1278                node!(self <div class="rustex-vcenter" {
1279                    node!(self <div {
1280                        for c in children {
1281                            self.do_v(c,false)?;
1282                        }
1283                    } />);
1284                }/>);
1285            }/>);
1286                self.width = oldwd;
1287                Ok(())
1288            }
1289            ShipoutNodeM::VRule {
1290                width,
1291                height,
1292                depth,
1293            } => {
1294                let wd = match width {
1295                    Some(w) if w.0 <= 0 => return Ok(()),
1296                    Some(w) => w.0,
1297                    None => 26214,
1298                };
1299                let color = self.color;
1300                self.do_indent()?;
1301                node!(self <mspace "background"=color;
1302                "width"=Self::dim_to_string(wd);
1303                "height"=Self::dim_to_string(height.unwrap_or_default().0);
1304                "depth"=Self::dim_to_string(depth.unwrap_or_default().0);
1305            />);
1306                Ok(())
1307            }
1308            ShipoutNodeM::Glyph {
1309                char,
1310                cramped,
1311                idx,
1312                font,
1313                display,
1314            } if self.font_info => {
1315                match cls {
1316                    Some(MathClass::Ord) | None => {
1317                        node!(self <mi "data-rustex-font"=font.filename();"data-rustex-glyph"=idx.to_string(); class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1318                    }
1319                    _ if *cramped => {
1320                        node!(self <mo "lspace"="0"; "rspace"="0"; "data-rustex-font"=font.filename();"data-rustex-glyph"=idx.to_string(); class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1321                    }
1322                    Some(MathClass::Op) if *display => {
1323                        node!(self <mo "lspace"="0"; "rspace"="0"; "data-rustex-font"=font.filename();"data-rustex-glyph"=idx.to_string(); class=Self::cls(MathClass::Op); "stretchy"="true"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1324                    }
1325                    Some(MathClass::Op) => {
1326                        node!(self <mo "lspace"="0"; "rspace"="0"; "data-rustex-font"=font.filename();"data-rustex-glyph"=idx.to_string(); class=Self::cls(MathClass::Op); "stretchy"="false"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1327                    }
1328                    Some(o) => {
1329                        node!(self <mo "lspace"="0"; "rspace"="0"; "data-rustex-font"=font.filename();"data-rustex-glyph"=idx.to_string(); class=Self::cls(o); "stretchy"="false"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1330                    }
1331                };
1332                Ok(())
1333            }
1334            ShipoutNodeM::Glyph {
1335                char,
1336                cramped,
1337                display,
1338                ..
1339            } => {
1340                match cls {
1341                    Some(MathClass::Ord) | None => {
1342                        node!(self <mi class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1343                    }
1344                    _ if *cramped => {
1345                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(MathClass::Ord); {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1346                    }
1347                    Some(MathClass::Op) if *display => {
1348                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(MathClass::Op); "stretchy"="true"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1349                    }
1350                    Some(MathClass::Op) => {
1351                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(MathClass::Op); "stretchy"="false"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1352                    }
1353                    Some(o) => {
1354                        node!(self <mo "lspace"="0"; "rspace"="0"; class=Self::cls(o); "stretchy"="false"; {Display::fmt(&Escaped(&char.into()), self.f)?}/>);
1355                    }
1356                };
1357                Ok(())
1358            }
1359            ShipoutNodeM::Space => {
1360                node!(self !<mspace class="rustex-mkern" "width"="0.25em";/>);
1361                Ok(())
1362            }
1363            ShipoutNodeM::Sup { base, sup, limits } => {
1364                node!(self !<<if *limits {"mover"} else {"msup"};
1365            ?(if *limits {Some(("displaystyle","true"))} else {None})
1366            {
1367                self.do_math(base,None/*,cramped*/)?;
1368                let at = self.font.get_at();
1369                self.font.set_at(at.scale_float(0.7));
1370                if sup.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1371                    for c in sup {
1372                        self.do_math(c,None/*,cramped*/)?;
1373                    }
1374                } else {
1375                    node!(self !<mrow {
1376                        for c in sup {
1377                            self.do_math(c,None/*,cramped*/)?;
1378                        }
1379                    }/>);
1380                }
1381                self.font.set_at(at);
1382            } />);
1383                Ok(())
1384            }
1385            ShipoutNodeM::Sub { base, sub, limits } => {
1386                node!(self !<<if *limits {"munder"} else {"msub"};
1387            ?(if *limits {Some(("displaystyle","true"))} else {None})
1388            {
1389                self.do_math(base,None/*,cramped*/)?;
1390                let at = self.font.get_at();
1391                self.font.set_at(at.scale_float(0.7));
1392                if sub.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1393                    for c in sub {
1394                        self.do_math(c,None/*,cramped*/)?;
1395                    }
1396                } else {
1397                    node!(self !<mrow {
1398                        for c in sub {
1399                            self.do_math(c,None/*,cramped*/)?;
1400                        }
1401                    }/>);
1402                }
1403                self.font.set_at(at);
1404            } />);
1405                Ok(())
1406            }
1407            ShipoutNodeM::LeftRight {
1408                sref,
1409                left,
1410                right,
1411                children,
1412                ..
1413            } => {
1414                node!(self !<mrow ref=sref {
1415                    if let Some(Ok(c)) = left {
1416                        node!(self <mo "lspace"="0"; "rspace"="0";  class="rustex-math-open" "stretchy"="true"; {Display::fmt(&Escaped(&c.into()),self.f)?} />);
1417                    }
1418                    for c in children {
1419                        self.do_math(c,None/*,cramped*/)?;
1420                    }
1421                    if let Some(Ok(c)) = right {
1422                        node!(self <mo "lspace"="0"; "rspace"="0";  class="rustex-math-close" "stretchy"="true"; {Display::fmt(&Escaped(&c.into()),self.f)?} />);
1423                    }
1424                }/>);
1425                Ok(())
1426            }
1427            ShipoutNodeM::Middle(r) => {
1428                match r {
1429                    Ok(c) => {
1430                        node!(self <mo "lspace"="0"; "rspace"="0";  "stretchy"="true"; {Display::fmt(&Escaped(&c.into()),self.f)?}/>);
1431                    }
1432                    Err((_, char, font_name)) => {
1433                        node!(self <mtext class="rustex-missing" "title"=format_args!("Missing Glyph {char} in {font_name}");/>);
1434                    }
1435                }
1436                Ok(())
1437            }
1438            ShipoutNodeM::SubSup {
1439                base,
1440                sub,
1441                sup,
1442                limits,
1443            } => {
1444                node!(self !<<if *limits {"munderover"} else {"msubsup"};
1445                ?(if *limits {Some(("displaystyle","true"))} else {None})
1446            {
1447                self.do_math(base,None/*,cramped*/)?;
1448                let at = self.font.get_at();
1449                self.font.set_at(at.scale_float(0.7));
1450                if sub.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1451                    for c in sub {
1452                        self.do_math(c,None/*,cramped*/)?;
1453                    }
1454                } else {
1455                    node!(self !<mrow {
1456                        for c in sub {
1457                            self.do_math(c,None/*,cramped*/)?;
1458                        }
1459                    }/>);
1460                }
1461                if sup.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1462                    for c in sup {
1463                        self.do_math(c,None/*,cramped*/)?;
1464                    }
1465                } else {
1466                    node!(self !<mrow {
1467                        for c in sup {
1468                            self.do_math(c,None/*,cramped*/)?;
1469                        }
1470                    }/>);
1471                }
1472                self.font.set_at(at);
1473            } />);
1474                Ok(())
1475            }
1476            ShipoutNodeM::Phantom {
1477                width,
1478                height,
1479                depth,
1480            } => {
1481                self.do_indent()?;
1482                node!(self !<mspace
1483                "width"=Self::dim_to_string(*width);
1484                "height"=Self::dim_to_string(*height);
1485                "depth"=Self::dim_to_string(*depth);
1486            />);
1487                Ok(())
1488            }
1489            ShipoutNodeM::Over {
1490                sref,
1491                sep,
1492                left,
1493                right,
1494                top,
1495                bottom,
1496                ..
1497            } => {
1498                let inner = move |s: &mut Self| {
1499                    node!(s !<mfrac ref=sref ?(sep.map(|i| ("linethickness",Self::dim_to_string(i)))) {
1500                        if top.iter().map(|n| n.num_nodes(s)).sum::<usize>() == 1 {
1501                            for c in top {
1502                                s.do_math(c,None/*,cramped*/)?;
1503                            }
1504                        } else {
1505                            node!(s !<mrow {
1506                                for c in top {
1507                                    s.do_math(c,None/*,cramped*/)?;
1508                                }
1509                            }/>);
1510                        }
1511                        if bottom.iter().map(|n| n.num_nodes(s)).sum::<usize>() == 1 {
1512                            for c in bottom {
1513                                s.do_math(c,None/*,cramped*/)?;
1514                            }
1515                        } else {
1516                            node!(s !<mrow {
1517                                for c in bottom {
1518                                    s.do_math(c,None/*,cramped*/)?;
1519                                }
1520                            }/>);
1521                        }
1522                    }/>);
1523                    Ok::<_, std::fmt::Error>(())
1524                };
1525                match (left, right) {
1526                    (None, None) => inner(self)?,
1527                    _ => node!(self !<mrow {
1528                        if let Some(Ok(c)) = left {
1529                            node!(self !<mo "lspace"="0"; "rspace"="0"; class="rustex-math-open" "stretchy"="true"; {Display::fmt(&Escaped(&c.into()),self.f)?} />);
1530                        }
1531                        inner(self)?;
1532                        if let Some(Ok(c)) = right {
1533                            node!(self !<mo "lspace"="0"; "rspace"="0"; class="rustex-math-close" "stretchy"="true"; {Display::fmt(&Escaped(&c.into()),self.f)?} />);
1534                        }
1535                    }/>),
1536                }
1537                Ok(())
1538            }
1539            ShipoutNodeM::Underline { children, .. } => {
1540                if children.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1541                    self.styles
1542                        .insert("text-decoration".into(), "underline".into());
1543                    for c in children {
1544                        self.do_math(c, cls /*,cramped*/)?;
1545                    }
1546                } else {
1547                    node!(self !<mrow style:"text-decoration"="underline"; {
1548                        for c in children {
1549                            self.do_math(c,cls/*,cramped*/)?;
1550                        }
1551                    }/>);
1552                }
1553                Ok(())
1554            }
1555            ShipoutNodeM::Overline { children, .. } => {
1556                if children.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1557                    self.styles
1558                        .insert("text-decoration".into(), "overline".into());
1559                    for c in children {
1560                        self.do_math(c, cls /*,cramped*/)?;
1561                    }
1562                } else {
1563                    node!(self !<mrow style:"text-decoration"="overline"; {
1564                        for c in children {
1565                            self.do_math(c,cls/*,cramped*/)?;
1566                        }
1567                    }/>);
1568                }
1569                Ok(())
1570            }
1571            ShipoutNodeM::Accent {
1572                accent, children, ..
1573            } => {
1574                node!(self !<mover {
1575                    if children.iter().map(|n| n.num_nodes(self)).sum::<usize>() == 1 {
1576                        for c in children {
1577                            self.do_math(c,None/*,cramped*/)?;
1578                        }
1579                    } else {
1580                        node!(self !<mrow {
1581                            for c in children {
1582                                self.do_math(c,None/*,cramped*/)?;
1583                            }
1584                        }/>);
1585                    }
1586                    match accent {
1587                        Ok(c) => node!(self !<mo "lspace"="0"; "rspace"="0"; "stretchy"="false"; {Display::fmt(&Escaped(&c.into()),self.f)?}/>),
1588                        Err((_,c,fnt)) =>
1589                            node!(self !<mtext class="rustex-missing" "title"=format_args!("Missing Glyph {c} in {fnt}");/>)
1590                    }
1591                }/>);
1592                Ok(())
1593            }
1594            ShipoutNodeM::MissingGlyph {
1595                char, font_name, ..
1596            } => {
1597                node!(self !<mtext class="rustex-missing" "title"=format_args!("Missing Glyph {char} in {font_name}");/>);
1598                Ok(())
1599            }
1600            ShipoutNodeM::Radical { children, .. } => {
1601                node!(self <msqrt {
1602                    for c in children {
1603                        self.do_math(c,None/*,cramped*/)?;
1604                    }
1605                }/>);
1606                Ok(())
1607            }
1608            ShipoutNodeM::Img(img) => Ok(node!(self <mtext {match (&self.image, &img.img) {
1609                (ImageOptions::AsIs, PDFImage::PDF(imgfile)) => {
1610                    let width = img.width().0;
1611                    let height = img.height().0;
1612                    let path = format!("{}-rustex.png", img.filepath.display());
1613
1614                    node!(self <img "src"=path;
1615                        "width"=Self::dim_to_int(width);
1616                        "height"=Self::dim_to_int(height);
1617                    />>);
1618                    if !std::path::Path::new(&path).exists() {
1619                        let _ = imgfile.save_with_format(path, image::ImageFormat::Png);
1620                    }
1621                }
1622                (ImageOptions::AsIs, _) => {
1623                    let width = img.width().0;
1624                    let height = img.height().0;
1625                    node!(self <img "src"=img.filepath.display();
1626                    "width"=Self::dim_to_int(width);
1627                    "height"=Self::dim_to_int(height);
1628                />>);
1629                }
1630                (ImageOptions::ModifyURL(f), _) => {
1631                    let width = img.width().0;
1632                    let height = img.height().0;
1633                    node!(self <img "src"=f(&img.filepath);
1634                    "width"=Self::dim_to_int(width);
1635                    "height"=Self::dim_to_int(height);
1636                />>);
1637                }
1638                _ => todo!(),
1639            }}/>)),
1640            _ => todo!("{c:?}"),
1641        }
1642    }
1643
1644    fn do_row(&mut self, c: &ShipoutNodeTable) -> std::fmt::Result {
1645        match c {
1646            ShipoutNodeTable::Common(Common::Literal(s)) => self.f.write_str(s),
1647            ShipoutNodeTable::Common(Common::WithAnnotation {
1648                children,
1649                attrs,
1650                styles,
1651                ..
1652            }) => {
1653                for c in children {
1654                    for (k, v) in attrs.iter() {
1655                        self.attrs.insert(k.clone(), v.clone())
1656                    }
1657                    for (k, v) in styles.iter() {
1658                        self.styles.insert(k.clone(), v.clone())
1659                    }
1660                    self.do_row(c)?;
1661                }
1662                Ok(())
1663            }
1664            ShipoutNodeTable::Common(Common::WithFont {
1665                children, /*font,*/
1666                ..
1667            }) => {
1668                for c in children {
1669                    // TODO insert font
1670                    self.do_row(c)?;
1671                }
1672                Ok(())
1673            }
1674            ShipoutNodeTable::Row { children, .. } => {
1675                node!(self !<tr {
1676                for c in children {
1677                    self.do_cell(c)?;
1678                }
1679            }/>);
1680                Ok(())
1681            }
1682            ShipoutNodeTable::NoAlign { children, .. } => {
1683                node!(self !<tr {node!(self <td class="rustex-noalign" {
1684                for c in children {
1685                    self.do_v(c,false)?;
1686                }
1687            }/>);}/>);
1688                Ok(())
1689            }
1690            _ => todo!("{c:?}"),
1691        }
1692    }
1693
1694    fn do_cell(&mut self, c: &ShipoutNodeHRow) -> std::fmt::Result {
1695        match c {
1696            ShipoutNodeHRow::Common(Common::Literal(s)) => self.f.write_str(s),
1697            ShipoutNodeHRow::Common(Common::WithAnnotation {
1698                children,
1699                attrs,
1700                styles,
1701                ..
1702            }) => {
1703                for c in children {
1704                    for (k, v) in attrs.iter() {
1705                        self.attrs.insert(k.clone(), v.clone())
1706                    }
1707                    for (k, v) in styles.iter() {
1708                        self.styles.insert(k.clone(), v.clone())
1709                    }
1710                    self.do_cell(c)?;
1711                }
1712                Ok(())
1713            }
1714            ShipoutNodeHRow::Cell {
1715                children,
1716                spans,
1717                height,
1718                ..
1719            } => {
1720                node!(self !<td class="rustex-halign-cell" style:{
1721                if *spans > 1 {
1722                    style!("grid-column"=format_args!("span {}",spans))
1723                }
1724            }{
1725                if height.0 < self.font.get_ht(0).0 {
1726                    node!(self <span { self.f.write_char(' ')?;} />);
1727                }
1728                for c in children {
1729                    self.do_h(c,false,true)?;
1730                }
1731            }/>);
1732                Ok(())
1733            }
1734            _ => todo!("{c:?}"),
1735        }
1736    }
1737
1738    fn do_svg(
1739        &mut self,
1740        sref: &SourceRef,
1741        minx: i32,
1742        maxx: i32,
1743        miny: i32,
1744        maxy: i32,
1745        children: &Vec<ShipoutNodeSVG>,
1746    ) -> std::fmt::Result {
1747        node!(self <div class="rustex-svg" {node!(self <svg ref=sref
1748            "width"=Self::dim_to_string(maxx - minx);
1749            "height"=Self::dim_to_string(maxy - miny);
1750            "viewBox"=format_args!("{} {} {} {}",
1751                Self::dim_to_num(minx),
1752                Self::dim_to_num(miny),
1753                Self::dim_to_num(maxx - minx),
1754                Self::dim_to_num(maxy - miny)
1755            );{node!(self !<g "transform"=format_args!("translate(0,{})",Self::dim_to_num(maxy + miny)); {
1756                for c in children {
1757                    self.do_svg_node(c)?
1758                }
1759            }/>);}
1760        />)}/>);
1761        Ok(())
1762    }
1763
1764    fn do_svg_node(&mut self, c: &ShipoutNodeSVG) -> std::fmt::Result {
1765        match c {
1766            ShipoutNodeSVG::Common(Common::Literal(s)) => self.f.write_str(s),
1767            ShipoutNodeSVG::SVGNode {
1768                tag,
1769                attrs,
1770                children,
1771                ..
1772            } => {
1773                for (k, v) in attrs.iter() {
1774                    self.attrs.insert((*k).into(), v.clone().into())
1775                }
1776                node!(self !<<tag; {for c in children {
1777                self.do_svg_node(c)?
1778            }} />);
1779                Ok(())
1780            }
1781            ShipoutNodeSVG::Common(Common::WithFont { font, children, .. }) => {
1782                // that should be okay...
1783                self.do_font("g", font, children, |s, n| s.do_svg_node(n))
1784            }
1785            ShipoutNodeSVG::Common(Common::WithColor {
1786                color, children, ..
1787            }) => self.do_color("g", color, children, |s, n| s.do_svg_node(n)),
1788            ShipoutNodeSVG::Common(Common::WithAnnotation {
1789                attrs,
1790                styles,
1791                classes,
1792                children,
1793                ..
1794            }) => self.do_annotations("g", attrs, styles, &classes.inner, children, |s, n| {
1795                s.do_svg_node(n)
1796            }),
1797            ShipoutNodeSVG::Common(Common::HBox {
1798                sref,
1799                info: info @ HBoxInfo::HBox { .. },
1800                children,
1801                ..
1802            }) => {
1803                let wd = info.computed_width().map(|d| d.0).unwrap_or_default();
1804                let ht = info.computed_height().map(|d| d.0).unwrap_or_default()
1805                    + info.assigned_depth().map(|d| d.0).unwrap_or_default();
1806                self.do_indent()?;
1807                node!(self !<foreignObject class="rustex-foreign"
1808                style:"width"=Self::dim_to_string(wd);
1809                style:"height"=Self::dim_to_string(ht);
1810                style:"translate"=format_args!("0 {}",Self::dim_to_string(-ht));
1811                {node!(self <div
1812                    {self.do_hbox(sref,info,true,children)?;}
1813                />)}
1814            />);
1815                Ok(())
1816            }
1817            _ => todo!("{c:?}"),
1818        }
1819    }
1820
1821    fn moveraise(
1822        &mut self,
1823        left: Option<Dim32>,
1824        raised: Option<Dim32>,
1825        then: impl FnOnce(&mut Self) -> std::fmt::Result,
1826    ) -> std::fmt::Result {
1827        match (raised, left) {
1828            (Some(r), None) => {
1829                node!(self <div class="rustex-raise" style:"--rustex-raise"=Self::dim_to_string(r.0);{
1830                    then(self)?
1831                }/>);
1832            }
1833            (None, Some(ml)) => {
1834                node!(self <div class="rustex-moveleft" style:"--rustex-moveleft"=Self::dim_to_string(ml.0);{
1835                    then(self)?
1836                }/>);
1837            }
1838            _ => then(self)?, // both are impossible anyway
1839        }
1840        Ok(())
1841    }
1842
1843    fn do_hbox(
1844        &mut self,
1845        sref: &SourceRef,
1846        info: &HBoxInfo<Types>,
1847        inh: bool,
1848        children: &Vec<ShipoutNodeH>,
1849    ) -> std::fmt::Result {
1850        let ass_width = info.assigned_width().map(|d| d.0);
1851        let orig_width = info.computed_width().map(|d| d.0);
1852        let ass_height = info.assigned_height().map(|d| d.0);
1853        let orig_height = info.computed_height().map(|d| d.0);
1854        let ass_dp = info.assigned_depth().map(|d| d.0);
1855        let orig_dp = info.computed_depth().map(|d| d.0);
1856        let to = match info.to_or_scaled() {
1857            Some(ToOrSpread::To(to)) => Some(to.0),
1858            Some(ToOrSpread::Spread(to)) => Some(to.0 + orig_width.unwrap_or_default()),
1859            _ => None,
1860        };
1861        let cls = match (ass_width, inh) {
1862            (Some(w), true) if w > 0 => "rustex-box-hh rustex-scalewidth",
1863            (_, true) => "rustex-box-hh",
1864            (Some(w), _) if w > 0 => "rustex-box-vh rustex-scalewidth",
1865            (_, _) => "rustex-box-vh",
1866        };
1867        self.moveraise(info.moved_left(), info.raised(), move |slf| {
1868            let inner = |slf:&mut Self| {
1869                node!(slf <div class=cls; ref=sref
1870                    //?(ass_width.map(|d| ("data-rustex-assigned-width",Self::dim_to_string(d))))
1871                    //?(orig_width.map(|d| ("data-rustex-original-width",Self::dim_to_string(d))))
1872                    //?(ass_height.map(|d| ("data-rustex-assigned-height",Self::dim_to_string(d))))
1873                    //?(orig_height.map(|d| ("data-rustex-original-height",Self::dim_to_string(d))))
1874                    //?(ass_dp.map(|d| ("data-rustex-assigned-depth",Self::dim_to_string(d))))
1875                    //?(orig_dp.map(|d| ("data-rustex-original-depth",Self::dim_to_string(d))))
1876                    //?(to.map(|d| ("data-rustex-to",Self::dim_to_string(d))))
1877                    style:{
1878                        if let Some(wd) = ass_width {
1879                            if wd >= 0 {
1880                                width!(wd);
1881                            } else {
1882                                style!("max-width"=0);
1883                                style!("margin-right"=Self::dim_to_string(wd));
1884                            }
1885                        }
1886                        if ass_width.unwrap_or_default() < 0 || orig_width.unwrap_or_default() <= 0 {
1887                            if let Some(ShipoutNodeH::KernSkip(Margin {base,..})) = children.last() && *base != 0 {
1888                                style!("margin-left"=Self::dim_to_string(-*base));
1889                                style!("margin-right"=Self::dim_to_string(*base));
1890                            }
1891                        }
1892                    }
1893                    {
1894                        match to {
1895                            Some(t) if t > 0 => {
1896                                let cls = if inh {"rustex-box-hh rustex-scalewidth"} else {"rustex-box-vh rustex-scalewidth"};
1897                                node!(slf <div class=cls; style:{
1898                                    style!("justify-content"="space-between");
1899                                    width!(t);
1900                                } {
1901                                    for c in children {
1902                                        slf.do_h(c,false,true)?;
1903                                    }
1904                                }/>)
1905                            }
1906                            Some(t) if t <= 0 => {
1907                                let cls = if inh {"rustex-box-hh"} else {"rustex-box-vh"};
1908                                node!(slf <div class=cls; style:{
1909                                    style!("width"="0");
1910                                } {
1911                                    for c in children {
1912                                        slf.do_h(c,false,true)?;
1913                                    }
1914                                }/>);
1915                            }
1916                            _ => for c in children {
1917                                slf.do_h(c,false,true)?;
1918                            }
1919                        }
1920                    }
1921                />);
1922                Ok::<_,std::fmt::Error>(())
1923            };
1924            if let Some(ht) = ass_height {
1925                let cls = if inh {"rustex-box-hhc"} else {"rustex-box-vhc"};
1926                node!(slf <div class=cls; style:{
1927                    if ht >= 0 {
1928                        style!("height"=Self::dim_to_string(ht));
1929                        if let Some(dp) = ass_dp {
1930                            style!("margin-bottom"=Self::dim_to_string(dp));
1931                        }
1932                    } else {
1933                        style!("max-height"="0");
1934                        let dp = if let Some(dp) = ass_dp {
1935                            ht + dp
1936                        } else {
1937                            ht
1938                        };
1939                        style!("margin-bottom"=Self::dim_to_string(dp));
1940                    }
1941                } {inner(slf)?;} />);
1942            } else if let Some(dp) = ass_dp {
1943                let cls = if inh {"rustex-box-hhc"} else {"rustex-box-vhc"};
1944                node!(slf <div class=cls; style:{
1945                    style!("margin-bottom"=Self::dim_to_string(dp));
1946                }  {inner(slf)?;} />);
1947            } else {
1948                inner(slf)?;
1949            }
1950            if let Some(t) = to && t < 0 {
1951                node!(slf <div class="rustex-hskip" style:{
1952                    style!("margin-left"=Self::dim_to_string(t));
1953                }/>);
1954            }
1955            Ok(())
1956        })
1957    }
1958
1959    fn do_vbox(
1960        &mut self,
1961        sref: &SourceRef,
1962        info: &VBoxInfo<Types>,
1963        children: &Vec<ShipoutNodeV>,
1964        inh: bool,
1965        in_para: bool,
1966        top: bool,
1967    ) -> std::fmt::Result {
1968        let cls = if inh {
1969            "rustex-box-hv"
1970        } else {
1971            "rustex-box-vv"
1972        };
1973        let ass_width = info.assigned_width().map(|d| d.0);
1974        let orig_width = info.computed_width().map(|d| d.0);
1975        let ass_height = info.assigned_height().map(|d| d.0);
1976        let orig_height = info.computed_height().map(|d| d.0);
1977        let ass_dp = info.assigned_depth().map(|d| d.0);
1978        let orig_dp = info.computed_depth().map(|d| d.0);
1979        let to = match info.to_or_scaled() {
1980            Some(ToOrSpread::To(to)) => Some(to.0),
1981            Some(ToOrSpread::Spread(to)) => Some(to.0 + orig_height.unwrap_or_default()),
1982            _ => None,
1983        };
1984        self.moveraise(info.moved_left(), info.raised(), move |slf| {
1985            let inner = move |slf: &mut Self| {
1986                node!(slf <div class=cls; ref=sref
1987                    //?(ass_width.map(|d| ("data-rustex-assigned-width",Self::dim_to_string(d))))
1988                    //?(orig_width.map(|d| ("data-rustex-original-width",Self::dim_to_string(d))))
1989                    //?(ass_height.map(|d| ("data-rustex-assigned-height",Self::dim_to_string(d))))
1990                    //?(orig_height.map(|d| ("data-rustex-original-height",Self::dim_to_string(d))))
1991                    //?(ass_dp.map(|d| ("data-rustex-assigned-depth",Self::dim_to_string(d))))
1992                    //?(orig_dp.map(|d| ("data-rustex-original-depth",Self::dim_to_string(d))))
1993                    //?(to.map(|d| ("data-rustex-to",Self::dim_to_string(d))))
1994                    style:{
1995                        if ass_height.is_some() {
1996                            style!("height"="0");
1997                        }
1998                        if let Some(w) = ass_width {
1999                            style!("width"=Self::dim_to_string(w));
2000                        }
2001                    }
2002                    {
2003                        if let Some(t) = to {
2004                            node!(slf <div class="rustex-vbox-to" style:{
2005                                style!("height"=Self::dim_to_string(t));
2006                            } {
2007                                for c in children {
2008                                    slf.do_v(c,true)?;
2009                                }
2010                            }/>)
2011                        } else {
2012                            for c in children {
2013                                slf.do_v(c,true)?;
2014                            }
2015                        }
2016                        if to.is_some() {
2017                            node!(slf <div class="rustex-box-after-v"/>);
2018                        }
2019                    }
2020                />);
2021                Ok(())
2022            };
2023            let inner = move |slf: &mut Self| {
2024                if ass_height.is_some() || ass_dp.is_some() {
2025                    node!(slf <div class=cls; style:{
2026                        style!("width"="fit-content;");
2027                        if let Some(ht) = ass_height {
2028                            style!("height"=Self::dim_to_string(ht));
2029                        }
2030                        if let Some(dp) = ass_dp {
2031                            style!("margin-bottom"=Self::dim_to_string(dp));
2032                        }
2033                    } {
2034                        inner(slf)?;
2035                        if ass_height.is_some() {
2036                            node!(slf <div class="rustex-box-after-v"/>);
2037                        }
2038                    }/>);
2039                    Ok(())
2040                } else {
2041                    inner(slf)
2042                }
2043            };
2044            if in_para {
2045                node!(slf <div class="rustex-box-hh" {inner(slf)?;} />);
2046                Ok(())
2047            } else {
2048                inner(slf)
2049            }
2050        })
2051    }
2052
2053    fn do_vtop(
2054        &mut self,
2055        sref: &SourceRef,
2056        info: &VBoxInfo<Types>,
2057        children: &Vec<ShipoutNodeV>,
2058        inh: bool,
2059        in_para: bool,
2060        top: bool,
2061    ) -> std::fmt::Result {
2062        let cls = if inh {
2063            "rustex-box-ht"
2064        } else {
2065            "rustex-box-vt"
2066        };
2067        let ass_width = info.assigned_width().map(|d| d.0);
2068        let orig_width = info.computed_width().map(|d| d.0);
2069        let ass_height = info.assigned_height().map(|d| d.0);
2070        let orig_height = info.computed_height().map(|d| d.0);
2071        let ass_dp = info.assigned_depth().map(|d| d.0);
2072        let orig_dp = info.computed_depth().map(|d| d.0);
2073        let to = match info.to_or_scaled() {
2074            Some(ToOrSpread::To(to)) => Some(to.0),
2075            Some(ToOrSpread::Spread(to)) => Some(to.0 + orig_height.unwrap_or_default()),
2076            _ => None,
2077        };
2078
2079        self.moveraise(info.moved_left(), info.raised(), move |slf| {
2080            let inner = move |slf: &mut Self| {
2081                node!(slf <div class=cls; ref=sref
2082                    //?(ass_width.map(|d| ("data-rustex-assigned-width",Self::dim_to_string(d))))
2083                    //?(orig_width.map(|d| ("data-rustex-original-width",Self::dim_to_string(d))))
2084                    //?(ass_height.map(|d| ("data-rustex-assigned-height",Self::dim_to_string(d))))
2085                    //?(orig_height.map(|d| ("data-rustex-original-height",Self::dim_to_string(d))))
2086                    //?(ass_dp.map(|d| ("data-rustex-assigned-depth",Self::dim_to_string(d))))
2087                    //?(orig_dp.map(|d| ("data-rustex-original-depth",Self::dim_to_string(d))))
2088                    //?(to.map(|d| ("data-rustex-to",Self::dim_to_string(d))))
2089                    style:{
2090                        if let Some(dp) = orig_dp {
2091                            style!("top"=Self::dim_to_string(dp));
2092                            style!("margin-bottom"=Self::dim_to_string(dp));
2093                            style!("margin-top"=Self::dim_to_string(-dp));
2094                        }
2095                        if ass_height.is_some() {
2096                            style!("height"="0");
2097                        }
2098                        if let Some(w) = ass_width {
2099                            style!("width"=Self::dim_to_string(w));
2100                        }
2101                    }
2102                    {
2103                        if let Some(t) = to {
2104                            node!(slf <div class="rustex-vbox-to" style:{
2105                                style!("height"=Self::dim_to_string(t));
2106                            } {
2107                                for c in children {
2108                                    slf.do_v(c,true)?;
2109                                }
2110                            }/>)
2111                        } else {
2112                            for c in children {
2113                                slf.do_v(c,true)?;
2114                            }
2115                        }
2116                        if to.is_some() {
2117                            node!(slf <div class="rustex-box-after-v"/>);
2118                        }
2119                    }
2120                />);
2121                Ok(())
2122            };
2123            let inner = move |slf: &mut Self| {
2124                if ass_height.is_some() || ass_dp.is_some() {
2125                    node!(slf <div class=cls; style:{
2126                        style!("width"="fit-content;");
2127                        if let Some(ht) = ass_height {
2128                            style!("height"=Self::dim_to_string(ht));
2129                        } else if let Some(ht) = orig_height {
2130                            style!("height"=Self::dim_to_string(ht));
2131                        }
2132                        if let Some(dp) = ass_dp {
2133                            style!("margin-bottom"=Self::dim_to_string(dp));
2134                        } else if let Some(dp) = orig_dp {
2135                            style!("margin-bottom"=Self::dim_to_string(dp));
2136                        }
2137                    } {
2138                        inner(slf)?;
2139                        if ass_height.is_some() {
2140                            node!(slf <div class="rustex-box-after-v"/>);
2141                        }
2142                    }/>);
2143                    Ok(())
2144                } else {
2145                    inner(slf)
2146                }
2147            };
2148            if in_para {
2149                node!(slf <div class="rustex-box-hh" {inner(slf)?;} />);
2150                Ok(())
2151            } else {
2152                inner(slf)
2153            }
2154        })
2155    }
2156}
2157
2158impl ShipoutNodeM {
2159    /*fn render(
2160        &self,
2161        renderer: &mut CompilationDisplay,
2162        cls: Option<MathClass>,
2163    ) -> std::fmt::Result {
2164        todo!()
2165    }*/
2166    fn num_nodes(&self, renderer: &CompilationDisplay) -> usize {
2167        match self {
2168            Self::Common(Common::Literal(_)) => 10, // doesn't matter, but we assume > 1,
2169            Self::Common(
2170                Common::WithLink { .. }
2171                | Common::PDFDest(_)
2172                | Common::HBox { .. }
2173                | Common::VBox { .. }
2174                | Common::WithAnnotation { .. },
2175            )
2176            | Self::MSkip { .. }
2177            | Self::WithClass { .. }
2178            | Self::VCenter { .. }
2179            | Self::VRule { .. }
2180            | Self::Glyph { .. }
2181            | Self::Space
2182            | Self::Sup { .. }
2183            | Self::Sub { .. }
2184            | Self::LeftRight { .. }
2185            | Self::Middle(_)
2186            | Self::SubSup { .. }
2187            | Self::Phantom { .. }
2188            | Self::Over { .. }
2189            | Self::Underline { .. }
2190            | Self::Overline { .. }
2191            | Self::Accent { .. }
2192            | Self::MissingGlyph { .. }
2193            | Self::Radical { .. }
2194            | Self::Img(_) => 1,
2195            Self::Common(Common::WithColor {
2196                color, children, ..
2197            }) => {
2198                if *color == renderer.color {
2199                    children.iter().map(|c| c.num_nodes(renderer)).sum()
2200                } else {
2201                    1
2202                }
2203            }
2204            Self::Common(Common::WithFont { font, children, .. }) => {
2205                if *font == renderer.font {
2206                    children.iter().map(|c| c.num_nodes(renderer)).sum()
2207                } else {
2208                    1
2209                }
2210            }
2211            _ => 10,
2212        }
2213    }
2214}
2215
2216struct Escaped<'a>(&'a CharOrStr);
2217impl Display for Escaped<'_> {
2218    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2219        use CharOrStr as C;
2220        const TRIGGER: [char; 3] = ['<', '>', '&'];
2221        match self.0 {
2222            C::Char('<') => f.write_str("&lt;"),
2223            C::Char('>') => f.write_str("&gt;"),
2224            C::Char('&') => f.write_str("&amp;"),
2225            //'"' => self.f.write_str("&quot;"),
2226            //'\'' => self.f.write_str("&apos;"),
2227            C::Char(c) => f.write_char(*c),
2228            C::Str(s) if s.contains(TRIGGER) => f.write_str(
2229                &s.replace('&', "&amp;")
2230                    .replace('<', "&lt;")
2231                    .replace('>', "&gt;"),
2232            ),
2233            C::Str(s) => f.write_str(s),
2234        }
2235    }
2236}