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