tectonic_engine_xetex/lib.rs
1// Copyright 2021-2022 the Tectonic Project
2// Licensed under the MIT License.
3
4//! The [XeTeX] program as a reusable crate.
5//!
6//! [XeTeX]: http://www.xetex.org/
7//!
8//! This crate provides the core TeX engine implementation used by [Tectonic].
9//! However, in order to obtain the full Tectonic user experience, it must be
10//! combined with a variety of other utilities: the `xdvipdfmx` engine, code to
11//! fetch support files, and so on. Rather than using this crate directly you
12//! should probably use the main [`tectonic`] crate, which combines all of these
13//! pieces into a (semi) coherent whole.
14//!
15//! [Tectonic]: https://tectonic-typesetting.github.io/
16//! [`tectonic`]: https://docs.rs/tectonic/
17
18// TODO: the internal interface we're using here is pretty janky. The bibtex
19// engine has a nicer approach that we should probably start using.
20
21use std::{ffi::CString, time::SystemTime};
22use tectonic_bridge_core::{CoreBridgeLauncher, EngineAbortedError};
23use tectonic_errors::prelude::*;
24
25/// A serial number describing the detailed binary layout of the TeX "format
26/// files" used by this crate. This number will occasionally increment,
27/// indicating that the format file structure has changed. There is no provision
28/// for partial forwards or backwards compatibility: if the number changes, you
29/// need to regenerate your format files. If you’re generating format files, you
30/// should munge this serial number in the filename, or something along those
31/// lines, to make sure that when the engine is updated you don’t attempt to
32/// reuse old files.
33//
34// DEVELOPER NOTE: if you change this, rerun cbindgen! This value is exported
35// into the C/C++ code as a #define.
36pub const FORMAT_SERIAL: u32 = 33;
37
38/// A possible outcome from a (Xe)TeX engine invocation.
39///
40/// The classic TeX implementation provides a fourth outcome: "fatal error". In
41/// Tectonic, this outcome is represented as an `Err` result rather than a
42/// [`TexOutcome`].
43///
44/// The `Errors` possibility will only occur if the `halt_on_error` engine
45/// option is false: if it’s true, errors get upgraded to fatals.
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub enum TexOutcome {
48 /// Nothing bad happened.
49 Spotless,
50
51 /// Warnings were issued by the TeX engine. Note that, due to the ways that
52 /// people are used to using TeX, warnings are *extremely* common in the
53 /// wild. It’s rare to find a real document that *doesn’t* compile with
54 /// warnings.
55 Warnings,
56
57 /// Errors were issued by the TeX engine. Note that, in TeX terminology,
58 /// errors are not necessarily *fatal* errors: the engine will try extremely
59 /// hard to proceed when it encounters them. It is not uncommon to find TeX
60 /// documents in the wild that produce errors.
61 Errors,
62}
63
64/// A struct for invoking the (Xe)TeX engine.
65///
66/// This struct has a fairly straightforward "builder" interface: you create it,
67/// apply any settings that you wish, and eventually run the
68/// [`process()`](Self::process) method.
69///
70/// Due to constraints of the gnarly C/C++ code underlying the engine
71/// implementation, only one engine may run at once in one process. The engine
72/// execution framework uses a global mutex to ensure that this is the case.
73/// This restriction applies not only to the [`TexEngine`] type but to *all*
74/// Tectonic engines. I.e., you can't run this engine and the BibTeX engine at
75/// the same time.
76#[derive(Debug)]
77pub struct TexEngine {
78 // One day, the engine will hold its own state. For the time being,
79 // though, it's just a proxy for the global constants in the C code.
80 halt_on_error: bool,
81 initex_mode: bool,
82 synctex_enabled: bool,
83 semantic_pagination_enabled: bool,
84 shell_escape_enabled: bool,
85 build_date: SystemTime,
86}
87
88impl Default for TexEngine {
89 fn default() -> Self {
90 TexEngine {
91 halt_on_error: true,
92 initex_mode: false,
93 synctex_enabled: false,
94 semantic_pagination_enabled: false,
95 shell_escape_enabled: false,
96 build_date: SystemTime::UNIX_EPOCH,
97 }
98 }
99}
100
101impl TexEngine {
102 /// Configure whether the engine will halt on errors.
103 ///
104 /// The default setting is true. If false, the engine will plunge on ahead
105 /// in the face of all but the most catastrophic problems. It’s really quite
106 /// impressive!
107 pub fn halt_on_error_mode(&mut self, halt_on_error: bool) -> &mut Self {
108 self.halt_on_error = halt_on_error;
109 self
110 }
111
112 /// Configure the engine to run in "initex" mode, in which it generates a
113 /// "format" file that serializes the engine state rather than a PDF
114 /// document. The default is false.
115 pub fn initex_mode(&mut self, initex: bool) -> &mut Self {
116 self.initex_mode = initex;
117 self
118 }
119
120 /// Configure the engine to produce SyncTeX data.
121 ///
122 /// The default is false.
123 pub fn synctex(&mut self, synctex_enabled: bool) -> &mut Self {
124 self.synctex_enabled = synctex_enabled;
125 self
126 }
127
128 /// Configure the engine to use "semantic pagination".
129 ///
130 /// **Important:** this mode is essentially unimplemented.
131 ///
132 /// The goal of this mode is to set up the engine to create HTML-friendly
133 /// output by altering how paragraphs and pages are constructed. When this
134 /// mode is activated, the engine output type changes from XDV to SPX
135 /// (although the two formats are quite similar).
136 ///
137 /// The default is false.
138 pub fn semantic_pagination(&mut self, enabled: bool) -> &mut Self {
139 self.semantic_pagination_enabled = enabled;
140 self
141 }
142
143 /// Configure whether the "shell escape" TeX feature is enabled.
144 ///
145 /// The default is false.
146 pub fn shell_escape(&mut self, shell_escape_enabled: bool) -> &mut Self {
147 self.shell_escape_enabled = shell_escape_enabled;
148 self
149 }
150
151 /// Sets the date and time used by the TeX engine. This affects things like
152 /// LaTeX's \today command.
153 ///
154 /// The default vaue is the Unix epoch, so you should almost always override
155 /// this setting. If you are aiming to achieve reproducible builds, you will
156 /// need a way to fix this parameter from one engine invocation to the next.
157 pub fn build_date(&mut self, date: SystemTime) -> &mut Self {
158 self.build_date = date;
159 self
160 }
161
162 /// Process a document using the current engine configuration.
163 ///
164 /// The *launcher* parameter gives overarching environmental context in
165 /// which the engine will be run.
166 ///
167 /// The *format_file_name* is the name for the TeX "format file" giving
168 /// preloaded engine state. It must be findable in the I/O stack, using the
169 /// special hooks that are provided for handing format files, which allow
170 /// updates to the file format to be handed (see [`FORMAT_SERIAL`]). If in
171 /// "initex" mode, this parameter will be ignored.
172 ///
173 /// The *input_file_name* is used to name the "primary input file". The I/O
174 /// system has special hooks for opening this primary input, so be aware
175 /// that this filename is *not* opened using the usual mechanisms. This
176 /// setting affects some of the names used by the engine internally,
177 /// including the name it uses to create its main output files. The
178 /// traditional default value is `"texput"`.
179 pub fn process(
180 &mut self,
181 launcher: &mut CoreBridgeLauncher,
182 format_file_name: &str,
183 input_file_name: &str,
184 ) -> Result<TexOutcome> {
185 let cformat = CString::new(format_file_name)?;
186 let cinput = CString::new(input_file_name)?;
187
188 launcher.with_global_lock(|state| {
189 // Note that we have to do all of this setup while holding the
190 // lock, because we're modifying static state variables.
191
192 // SAFETY: All methods are called with valid C-strings and while the global lock is held.
193 let r = unsafe {
194 use c_api::*;
195 tt_xetex_set_int_variable(
196 c"shell_escape_enabled".as_ptr(),
197 self.shell_escape_enabled.into(),
198 );
199 tt_xetex_set_int_variable(c"halt_on_error_p".as_ptr(), self.halt_on_error.into());
200 tt_xetex_set_int_variable(c"in_initex_mode".as_ptr(), self.initex_mode.into());
201 tt_xetex_set_int_variable(c"synctex_enabled".as_ptr(), self.synctex_enabled.into());
202 tt_xetex_set_int_variable(
203 c"semantic_pagination_enabled".as_ptr(),
204 self.semantic_pagination_enabled.into(),
205 );
206
207 tt_engine_xetex_main(
208 state,
209 cformat.as_ptr(),
210 cinput.as_ptr(),
211 self.build_date
212 .duration_since(SystemTime::UNIX_EPOCH)
213 .expect("invalid build date")
214 .as_secs(),
215 )
216 };
217
218 match r {
219 0 => Ok(TexOutcome::Spotless),
220 1 => Ok(TexOutcome::Warnings),
221 2 => Ok(TexOutcome::Errors),
222 3 => Err(EngineAbortedError::new_abort_indicator().into()),
223 x => Err(anyhow!("internal error: unexpected 'history' value {}", x)),
224 }
225 })
226 }
227}
228
229#[doc(hidden)]
230pub mod c_api {
231 // If you change the interfaces here, rerun cbindgen as described in the README!
232
233 use tectonic_bridge_core::CoreBridgeState;
234
235 #[allow(improper_ctypes)] // for CoreBridgeState
236 extern "C" {
237 pub fn tt_xetex_set_int_variable(
238 var_name: *const libc::c_char,
239 value: libc::c_int,
240 ) -> libc::c_int;
241
242 pub fn tt_engine_xetex_main(
243 api: &mut CoreBridgeState,
244 dump_name: *const libc::c_char,
245 input_file_name: *const libc::c_char,
246 build_date: u64,
247 ) -> libc::c_int;
248 }
249}
250
251/// Import things from our bridge crates to ensure that we actually link with
252/// them.
253mod linkage {
254 #[allow(unused_imports)]
255 use tectonic_pdf_io as clipyrenamehack1;
256
257 #[allow(unused_imports)]
258 use tectonic_xetex_layout as clipyrenamehack2;
259
260 #[allow(unused_imports)]
261 use tectonic_bridge_icu as clipyrenamehack3;
262}
263
264/// Does our resulting executable link correctly?
265#[test]
266fn linkage() {}