|
1 | 1 | // Copyright 2018-2021 the Tectonic Project |
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | | -//! Convert Tectonic’s SPX format to HTML |
5 | | -//! |
6 | | -//! Yay, an engine actually written in pure Rust! |
7 | | -
|
8 | | -use std::io::Write; |
9 | | -use tectonic_bridge_core::DriverHooks; |
10 | | -use tectonic_xdv::{FileType, XdvEvents, XdvParser}; |
11 | | - |
12 | | -use crate::errors::{Error, Result}; |
13 | | -use crate::io::{OpenResult, OutputHandle}; |
14 | | -use crate::status::StatusBackend; |
15 | | -use crate::{errmsg, tt_warning}; |
16 | | - |
17 | | -#[derive(Default)] |
18 | | -pub struct Spx2HtmlEngine {} |
19 | | - |
20 | | -impl Spx2HtmlEngine { |
21 | | - pub fn new() -> Spx2HtmlEngine { |
22 | | - Default::default() |
23 | | - } |
24 | | - |
25 | | - pub fn process( |
26 | | - &mut self, |
27 | | - hooks: &mut dyn DriverHooks, |
28 | | - status: &mut dyn StatusBackend, |
29 | | - spx: &str, |
30 | | - ) -> Result<()> { |
31 | | - let mut input = hooks.io().input_open_name(spx, status).must_exist()?; |
32 | | - |
33 | | - // FIXME? The engine should probably be responsible for choosing this. |
34 | | - let outname = { |
35 | | - let mut s = spx.to_owned(); |
36 | | - |
37 | | - if spx.ends_with(".spx") { |
38 | | - let l = s.len(); |
39 | | - s.truncate(l - 4); |
40 | | - } |
41 | | - |
42 | | - s.push_str(".html"); |
43 | | - s |
44 | | - }; |
45 | | - |
46 | | - { |
47 | | - let state = State::new(outname, hooks, status); |
48 | | - let (state, _n_bytes) = XdvParser::process(&mut input, state)?; |
49 | | - state.finished(); |
50 | | - } |
51 | | - |
52 | | - let (name, digest_opt) = input.into_name_digest(); |
53 | | - hooks.event_input_closed(name, digest_opt, status); |
54 | | - Ok(()) |
55 | | - } |
56 | | -} |
57 | | - |
58 | | -struct State<'a> { |
59 | | - outname: String, |
60 | | - hooks: &'a mut dyn DriverHooks, |
61 | | - status: &'a mut dyn StatusBackend, |
62 | | - cur_output: Option<OutputHandle>, |
63 | | - warned_lost_chars: bool, |
64 | | - buf: Vec<u8>, |
65 | | -} |
66 | | - |
67 | | -impl<'a> State<'a> { |
68 | | - pub fn new( |
69 | | - outname: String, |
70 | | - hooks: &'a mut dyn DriverHooks, |
71 | | - status: &'a mut dyn StatusBackend, |
72 | | - ) -> Self { |
73 | | - Self { |
74 | | - outname, |
75 | | - hooks, |
76 | | - status, |
77 | | - cur_output: None, |
78 | | - warned_lost_chars: false, |
79 | | - buf: Vec::new(), |
80 | | - } |
81 | | - } |
82 | | -} |
83 | | - |
84 | | -#[inline(always)] |
85 | | -fn as_printable_ascii(c: i32) -> Option<u8> { |
86 | | - if c > 0x20 && c < 0x7F { |
87 | | - Some(c as u8) |
88 | | - } else { |
89 | | - None |
90 | | - } |
91 | | -} |
92 | | - |
93 | | -impl<'a> State<'a> { |
94 | | - pub fn finished(self) { |
95 | | - if let Some(oh) = self.cur_output { |
96 | | - let (name, digest) = oh.into_name_digest(); |
97 | | - self.hooks.event_output_closed(name, digest, self.status); |
98 | | - } |
99 | | - } |
100 | | -} |
101 | | - |
102 | | -impl<'a> XdvEvents for State<'a> { |
103 | | - type Error = Error; |
104 | | - |
105 | | - fn handle_header(&mut self, filetype: FileType, _comment: &[u8]) -> Result<()> { |
106 | | - if filetype != FileType::Spx { |
107 | | - return Err(errmsg!("file should be SPX format but got {}", filetype)); |
108 | | - } |
109 | | - |
110 | | - self.cur_output = Some(match self.hooks.io().output_open_name(&self.outname) { |
111 | | - OpenResult::Ok(h) => h, |
112 | | - OpenResult::NotAvailable => { |
113 | | - return Err(errmsg!("no way to write output file \"{}\"", self.outname)); |
114 | | - } |
115 | | - OpenResult::Err(e) => { |
116 | | - return Err(e.into()); |
117 | | - } |
118 | | - }); |
119 | | - |
120 | | - Ok(()) |
121 | | - } |
122 | | - |
123 | | - fn handle_char_run(&mut self, chars: &[i32]) -> Result<()> { |
124 | | - let dest = match self.cur_output { |
125 | | - Some(ref mut h) => h, |
126 | | - None => { |
127 | | - if !self.warned_lost_chars { |
128 | | - tt_warning!( |
129 | | - self.status, |
130 | | - "losing characters in SPX file: no current output" |
131 | | - ); |
132 | | - self.warned_lost_chars = true; |
133 | | - } |
134 | | - |
135 | | - return Ok(()); |
136 | | - } |
137 | | - }; |
138 | | - |
139 | | - self.buf.clear(); |
140 | | - |
141 | | - for c in chars.iter() { |
142 | | - if let Some(b) = as_printable_ascii(*c) { |
143 | | - self.buf.push(b); |
144 | | - } |
145 | | - } |
146 | | - |
147 | | - if !self.buf.is_empty() { |
148 | | - self.buf.push(0x0a); // newline |
149 | | - dest.write_all(&self.buf)?; |
150 | | - } |
151 | | - |
152 | | - Ok(()) |
153 | | - } |
154 | | -} |
| 4 | +pub use tectonic_engine_spx2html::Spx2HtmlEngine; |
0 commit comments