1use crate::machine::Executor;
2use crate::machine::Machine;
3use crate::machine::Position;
4use crate::machine::PreambleData;
5use crate::machine::SpecialHandler;
6use crate::tfm::FontDataHelper;
7use crate::utils::tex_color_to_hex;
8use dvi::FontDef;
9use std::char;
10use std::collections::HashMap;
11
12#[derive(Debug)]
13pub struct HTMLMachine {
14 content: String,
15 color: String,
16 color_stack: Vec<String>,
17 points_per_dvi_unit: Option<f64>,
18 svg_depth: u8,
19 paperwidth: Option<f64>,
20 paperheight: Option<f64>,
21
22 position: Position,
23 position_stack: Vec<Position>,
24 font: Option<FontDef>,
25 fonts: HashMap<u32, FontDef>,
26
27 nb_pages: u16,
28
29 svg_buffer: String,
30}
31
32impl HTMLMachine {
33 pub fn new() -> HTMLMachine {
34 HTMLMachine {
35 content: "".to_string(),
36 color: "black".to_string(),
37 color_stack: Vec::new(),
38 points_per_dvi_unit: None,
39 svg_depth: 0,
40 paperwidth: None,
41 paperheight: None,
42 position: Position::empty(),
43 position_stack: Vec::new(),
44 font: None,
45 fonts: HashMap::new(),
46 nb_pages: 0,
47 svg_buffer: "".to_string(),
48 }
49 }
50}
51
52impl Machine for HTMLMachine {
53 fn get_content(&self) -> String {
54 let width_text = if let Some(w) = self.paperwidth {
55 format!("width:{}pt;", w)
56 } else {
57 "".to_string()
58 };
59 let height_text = if let Some(h) = self.paperheight {
60 format!("height:{}pt;", h * (self.nb_pages as f64))
61 } else {
62 "".to_string()
63 };
64 format!(
65 r#"<div style="{}{}">{}</div>"#,
66 width_text, height_text, self.content
67 )
68 }
69 fn set_nb_pages(&mut self, nb_pages: u16) {
70 self.nb_pages = nb_pages;
71 }
72 fn get_position(&mut self) -> &mut Position {
73 &mut self.position
74 }
75 fn put_text(&mut self, buffer: Vec<u32>, font_helper: &FontDataHelper) -> f64 {
76 let mut text_width = 0;
77 let mut text_height = 0;
78 let mut text_depth = 0;
79
80 let mut html_text = "".to_string();
81
82 let font = self.font.as_ref().unwrap(); let font_name = std::str::from_utf8(&font.filename).unwrap();
86 let font_data = font_helper.get(font_name.to_string()).unwrap_or_else(|| {
87 eprintln!("Using fallback cmb10 for {}", font_name);
88 font_helper.get("cmb10".to_string()).unwrap()
89 }); for &c in buffer.iter() {
92 let mut metrics_option = font_data.characters.get(&c);
93 if metrics_option.is_none() {
94 eprintln!("Could not find font metric for {}", c);
96 metrics_option = font_data.characters.get(&126);
97 }
98 if let Some(metrics) = metrics_option {
99 text_width += metrics.width;
100 text_height = std::cmp::max(text_height, metrics.height);
101 text_depth = std::cmp::max(text_depth, metrics.depth);
102
103 if c <= 9 {
105 html_text.push_str(&format!("&#{};", 161 + c));
106 } else if c >= 10 && c <= 19 {
107 html_text.push_str(&format!("&#{};", 173 + c - 10));
108 } else if c == 20 {
109 html_text.push_str("∙"); } else if c >= 21 && c <= 32 {
111 html_text.push_str(&format!("&#{};", 184 + c - 21));
112 } else if c == 127 {
113 html_text.push_str("Ä");
114 } else {
115 html_text.push_str(&char::from_u32(c).unwrap().to_string());
116 }
118 }
119 }
120
121 let dvi_units_per_font_unit =
123 (font_data.design_size as f64) / 1_048_576.0 * 65536.0 / 1_048_576.0;
124 let points_per_dvi_unit = self.points_per_dvi_unit.unwrap(); let _top = ((self.position.v() as f64) - (text_height as f64) * dvi_units_per_font_unit)
128 * points_per_dvi_unit;
129 let left = (self.position.h() as f64) * points_per_dvi_unit;
130
131 let _width = (text_width as f64) * points_per_dvi_unit * dvi_units_per_font_unit;
132 let height = (text_height as f64) * points_per_dvi_unit * dvi_units_per_font_unit;
133 let _depth = (text_depth as f64) * points_per_dvi_unit * dvi_units_per_font_unit;
134 let top = (self.position.v() as f64) * points_per_dvi_unit;
135
136 let fontsize = ((font_data.design_size as f64) / 1_048_576.0) * (font.scale_factor as f64)
137 / (font.design_size as f64);
138
139 if self.svg_depth == 0 {
140 self.content.push_str(&format!(r#"
141<span style="line-height: 0; color: {}; font-family: {}; font-size: {}pt; position: absolute; top: {}pt; left: {}pt; overflow: visible;">
142<span style="margin-top: -{}pt; line-height: 0pt; height: {}pt; display: inline-block; vertical-align: baseline; ">
143{}
144</span>
145<span style="display: inline-block; vertical-align: {}pt; height: 0pt; line-height: 0;">
146</span>
147</span>
148"#, self.color, font_name, fontsize, top-height, left, fontsize, fontsize, html_text, height));
149 } else {
150 let bottom = (self.position.v() as f64) * points_per_dvi_unit;
151 self.content.push_str(&format!(
153 r#"
154<text alignment-baseline="baseline" y="{}" x="{}" style="font-family: {};" font-size="{}">
155{}
156</text>"#,
157 bottom, left, font_name, fontsize, html_text
158 ));
159 }
160
161 (text_width as f64) * dvi_units_per_font_unit * (font.scale_factor as f64)
162 / (font.design_size as f64)
163 }
164
165 fn put_rule(&mut self, ai: i32, bi: i32) {
166 let points_per_dvi_unit = self.points_per_dvi_unit.unwrap(); let a = (ai as f64) * points_per_dvi_unit;
169 let b = (bi as f64) * points_per_dvi_unit;
170 let left = self.position.h() * points_per_dvi_unit;
171 let bottom = self.position.v() * points_per_dvi_unit;
172 let top = bottom - a;
173
174 self.content.push_str(&format!(r#"
175<span style="background: {}; position: absolute; top: {}pt; left: {}pt; width:{}pt; height: {}pt;"></span>
176"#, self.color, top, left, b, a));
177 }
178 fn begin_page(&mut self, _arr: [i32; 10], _p: i32) {
179 self.position_stack.clear();
180 }
182 fn end_page(&mut self) {
183 }
185 fn push_position(&mut self) {
186 self.position_stack.push(self.position.clone());
187 }
188 fn pop_position(&mut self) {
189 self.position = self.position_stack.pop().unwrap(); }
191 fn set_font(&mut self, index: u32) {
192 self.font = self.fonts.get(&index).cloned();
193 }
194 fn add_font(&mut self, font: FontDef) {
195 self.fonts.insert(font.number, font);
196 }
197 fn set_preamble_data(&mut self, data: PreambleData) {
198 let magnification = data.magnification as f64;
199 let numerator = data.numerator as f64;
200 let denominator = data.denominator as f64;
201 let dvi_unit = (magnification * numerator) / (1000.0 * denominator);
202
203 let resolution = 300.0; let _tfm_conv = (25_400_000.0 / numerator) * (denominator / 473_628_672.0) / 16.0;
205 let _conv = (numerator / 254_000.0) * (resolution / denominator) * (magnification / 1000.0);
206
207 self.points_per_dvi_unit = Some(dvi_unit * 72.27 / 100_000.0 / 2.54);
208 }
209 fn handle_special(&mut self, special_handlers: &[SpecialHandler], command: &str) {
210 for special in special_handlers.iter() {
211 if special(self, command) {
212 break;
213 }
214 }
215 }
216}
217
218impl Executor for HTMLMachine {}
219
220impl HTMLMachine {
222 fn special_color(&mut self, command: &str) -> bool {
223 if command.starts_with("color pop") {
224 self.color = self.color_stack.pop().unwrap(); return true;
226 } else if command.starts_with("color push ") {
227 let color = tex_color_to_hex(command.split_at("color push ".len()).1);
228 self.color_stack.push(color.to_string());
229 self.color = color;
230 return true;
231 }
232 false
233 }
234
235 fn special_papersize(&mut self, command: &str) -> bool {
236 let pattern = "papersize=";
237 if command.starts_with(pattern) {
238 let sizes = command
239 .split_at(pattern.len())
240 .1
241 .split(',')
242 .collect::<Vec<_>>();
243 let width = Some(
247 sizes[0]
248 .split_at(sizes[0].len() - 2)
249 .0
250 .parse::<f64>()
251 .unwrap(),
252 ); let height = Some(
254 sizes[1]
255 .split_at(sizes[1].len() - 2)
256 .0
257 .parse::<f64>()
258 .unwrap(),
259 ); self.paperwidth = width;
261 self.paperheight = height;
262 }
263 false
264 }
265
266 fn append_svg(&mut self, s: &str) {
267 self.svg_buffer.push_str(s);
268 } fn put_svg(&mut self) {
271 let points_per_dvi_unit = self.points_per_dvi_unit.unwrap();
272 let left = self.position.h() * points_per_dvi_unit;
273 let top = self.position.v() * points_per_dvi_unit;
274
275 self.svg_depth += self.svg_buffer.matches("<svg>").count() as u8;
276 self.svg_depth -= self.svg_buffer.matches("</svg>").count() as u8;
277
278 let mut result_svg = self.svg_buffer.replacen("<svg>", r#"<svg width="10pt" height="10pt" viewBox="-5 -5 10 10" style="overflow: visible; position: absolute;">"#, 1);
279 result_svg = result_svg.replace(r#"{?x}"#, &format!("{}", left));
280 result_svg = result_svg.replace(r#"{?y}"#, &format!("{}", top));
281 result_svg = result_svg.replace(r#"{?nl}"#, "\n");
282
283 self.content.push_str(&result_svg);
284 self.svg_buffer = "".to_string();
285 }
286
287 fn special_svg(&mut self, command: &str) -> bool {
288 let pattern = "dvisvgm:raw ";
289 if command.starts_with(pattern) {
290 let svg = command.split_at(pattern.len()).1;
291 self.append_svg(svg);
292 return true;
293 } else if !self.svg_buffer.is_empty() {
294 self.put_svg();
295 }
296 false
297 }
298}
299
300pub fn special_html_color(m: &mut HTMLMachine, command: &str) -> bool {
301 m.special_color(command)
302}
303
304pub fn special_html_papersize(m: &mut HTMLMachine, command: &str) -> bool {
305 m.special_papersize(command)
306}
307
308pub fn special_html_svg(m: &mut HTMLMachine, command: &str) -> bool {
309 m.special_svg(command)
310}