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