dvi2html/
htmlmachine.rs

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(); //TODO
83
84        // TODO: better error handling
85        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        }); // Fallback if font not found
90
91        for &c in buffer.iter() {
92            let mut metrics_option = font_data.characters.get(&c);
93            if metrics_option.is_none() {
94                //TODO: Handle this better. Error only happens for c === 127
95                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                // This is ridiculous.
104                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("&#8729;"); // O RLLY?!
110                } 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("&#196;");
114                } else {
115                    html_text.push_str(&char::from_u32(c).unwrap().to_string());
116                    //TODO?
117                }
118            }
119        }
120
121        // tfm is based on 1/2^16 pt units, rather than dviunit which is 10^−7 meters
122        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(); //TODO
125
126        // TODO: remove unused
127        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            // No 'pt' on fontsize since those units are potentially scaled
152            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(); //TODO
167
168        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        //self.position = Position::empty(); //TODO: Optional
181    }
182    fn end_page(&mut self) {
183        //TODO check if position stack is empty
184    }
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(); //TODO?
190    }
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; // ppi
204        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
220//Specials -> maybe PopColor etc to Machine trait
221impl 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(); //TODO
225            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            //TODO: error if sizes is not of len 2
244            //Error if first or second element doesn't end with 'pt'
245
246            let width = Some(
247                sizes[0]
248                    .split_at(sizes[0].len() - 2)
249                    .0
250                    .parse::<f64>()
251                    .unwrap(),
252            ); //TODO
253            let height = Some(
254                sizes[1]
255                    .split_at(sizes[1].len() - 2)
256                    .0
257                    .parse::<f64>()
258                    .unwrap(),
259            ); //TODO
260            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    } //TODO: go to all specials for every special handler
269
270    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}