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 && 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 node!(self !<table class="rustex-halign"
680 style:"--rustex-align-num"=num_cols;
681 {
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 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)?
1003 }
1004 } else {
1005 node!(s !<mrow {
1006 for c in children {
1007 s.do_indent()?;s.do_math(c,None)?
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>, ) -> 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)? }
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 )
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 ),
1061 )
1062 }
1063 ShipoutNodeM::Common(Common::WithFont { font, children, .. }) => {
1064 self.do_font("mrow", font, children, |s, n| {
1066 s.do_math(n, cls )
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, .. } => {
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) )?;
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 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))?;
1177 }
1178 }/>);
1179 }
1180 Ok(())
1181 }
1182 ShipoutNodeM::Common(Common::HBox {
1183 sref,
1184 info: info @ HBoxInfo::HBox { .. },
1185 children,
1186 ..
1187 }) => {
1188 node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1194 } {
1205 self.do_hbox(sref,info,true,children)?;
1206 }/>);
1207 Ok(())
1209 }
1210 ShipoutNodeM::Common(Common::VBox {
1211 sref,
1212 info: info @ VBoxInfo::VTop { .. },
1213 children,
1214 ..
1215 }) => {
1216 node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1222 } {
1233 node!(self <div class="rustex-box-hh" {
1234 self.do_vtop(sref,info,children,true,false,false)?;
1235 }/>);
1236 }/>);
1237 Ok(())
1239 }
1240 ShipoutNodeM::Common(Common::VBox {
1241 sref,
1242 info,
1243 children,
1244 ..
1245 }) => {
1246 node!(self !<mtext class="rustex-math-escape" ref=sref style:{
1252 } {
1263 node!(self <div class="rustex-box-hh" {
1264 self.do_vbox(sref,info,children,true,false,false)?;
1265 }/>);
1266 }/>);
1267 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)?;
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)?;
1385 }
1386 } else {
1387 node!(self !<mrow {
1388 for c in sup {
1389 self.do_math(c,None)?;
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)?;
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)?;
1407 }
1408 } else {
1409 node!(self !<mrow {
1410 for c in sub {
1411 self.do_math(c,None)?;
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)?;
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)?;
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)?;
1465 }
1466 } else {
1467 node!(self !<mrow {
1468 for c in sub {
1469 self.do_math(c,None)?;
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)?;
1476 }
1477 } else {
1478 node!(self !<mrow {
1479 for c in sup {
1480 self.do_math(c,None)?;
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)?;
1515 }
1516 } else {
1517 node!(s !<mrow {
1518 for c in top {
1519 s.do_math(c,None)?;
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)?;
1526 }
1527 } else {
1528 node!(s !<mrow {
1529 for c in bottom {
1530 s.do_math(c,None)?;
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 )?;
1557 }
1558 } else {
1559 node!(self !<mrow style:"text-decoration"="underline"; {
1560 for c in children {
1561 self.do_math(c,cls)?;
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 )?;
1573 }
1574 } else {
1575 node!(self !<mrow style:"text-decoration"="overline"; {
1576 for c in children {
1577 self.do_math(c,cls)?;
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)?;
1590 }
1591 } else {
1592 node!(self !<mrow {
1593 for c in children {
1594 self.do_math(c,None)?;
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)?;
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, ..
1679 }) => {
1680 for c in children {
1681 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 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)?, }
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 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 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 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 num_nodes(&self, renderer: &CompilationDisplay) -> usize {
2179 match self {
2180 Self::Common(Common::Literal(_)) => 10, 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("<"),
2235 C::Char('>') => f.write_str(">"),
2236 C::Char('&') => f.write_str("&"),
2237 C::Char(c) => f.write_char(*c),
2240 C::Str(s) if s.contains(TRIGGER) => f.write_str(
2241 &s.replace('&', "&")
2242 .replace('<', "<")
2243 .replace('>', ">"),
2244 ),
2245 C::Str(s) => f.write_str(s),
2246 }
2247 }
2248}