latexml_post/
math_images.rs1use libxml::tree::Node;
7
8use crate::{
9 document::PostDocument,
10 math_processor::{MathConversion, MathProcessor},
11 processor::{ProcessResult, Processor},
12};
13
14const MIME_TYPES: &[(&str, &str)] = &[
15 ("gif", "image/gif"),
16 ("jpeg", "image/jpeg"),
17 ("png", "image/png"),
18 ("svg", "image/svg+xml"),
19];
20
21pub struct MathImages {
25 name: String,
26 is_secondary: bool,
27 resource_directory: String,
28 resource_prefix: String,
29 image_type: String,
30}
31
32impl MathImages {
33 pub fn new(image_type: &str) -> Self {
34 MathImages {
35 name: "MathImages".to_string(),
36 is_secondary: false,
37 resource_directory: "mi".to_string(),
38 resource_prefix: "mi".to_string(),
39 image_type: image_type.to_string(),
40 }
41 }
42
43 fn extract_tex(&self, node: &Node) -> Option<String> {
47 let mode = node
48 .get_attribute("mode")
49 .map(|m| m.to_uppercase())
50 .unwrap_or_else(|| "INLINE".to_string());
51 let mut tex = node.get_attribute("tex")?;
52 let display = if tex.trim_start().starts_with("\\displaystyle") {
53 tex = tex
54 .trim_start()
55 .strip_prefix("\\displaystyle")?
56 .trim_start()
57 .to_string();
58 "DISPLAY"
59 } else {
60 &mode
61 };
62 if tex.trim().is_empty() {
63 return None;
64 }
65 Some(format!("\\begin{} {}\\end{}", display, tex, display))
66 }
67}
68
69impl Processor for MathImages {
70 fn get_name(&self) -> &str { &self.name }
71
72 fn to_process(&self, doc: &PostDocument) -> Vec<Node> { doc.findnodes("//ltx:Math") }
73
74 fn resource_directory(&self) -> Option<&str> { Some(&self.resource_directory) }
75
76 fn resource_prefix(&self) -> Option<&str> { Some(&self.resource_prefix) }
77
78 fn process(&mut self, doc: PostDocument, _nodes: Vec<Node>) -> ProcessResult { Ok(vec![doc]) }
79}
80
81impl MathProcessor for MathImages {
82 fn convert_node(&self, doc: &PostDocument, xmath: &Node) -> Option<MathConversion> {
83 let math = xmath.get_parent()?;
84 let tex = self.extract_tex(&math)?;
85
86 let key = format!("MathImages:{}:{}", self.image_type, tex);
87 if let Some(cached) = doc.cache_lookup(&key) {
88 let parts: Vec<&str> = cached.split(';').collect();
90 if parts.len() == 4 {
91 let mimetype = MIME_TYPES
92 .iter()
93 .find(|(ext, _)| *ext == self.image_type)
94 .map(|(_, mime)| mime.to_string());
95 return Some(MathConversion {
96 processor_name: self.name.clone(),
97 mimetype,
98 xml: None,
99 string: None,
100 src: Some(parts[0].to_string()),
101 width: Some(parts[1].to_string()),
102 height: Some(parts[2].to_string()),
103 depth: Some(parts[3].to_string()),
104 });
105 }
106 }
107
108 Warn!(
109 "missing_file",
110 "math_images",
111 "MathImages: no cached image for '{}'",
112 key
113 );
114 Some(MathConversion {
115 processor_name: self.name.clone(),
116 mimetype: None,
117 xml: None,
118 string: None,
119 src: None,
120 width: None,
121 height: None,
122 depth: None,
123 })
124 }
125
126 fn raw_id_suffix(&self) -> &str { ".mi" }
127
128 fn is_secondary(&self) -> bool { self.is_secondary }
129
130 fn preprocess(&self, _doc: &PostDocument, nodes: &[Node]) {
131 Info!(
132 "math_images",
133 "generate",
134 "MathImages: would generate {} images",
135 nodes.len()
136 );
137 }
138}