tectonic_engine_xdvipdfmx/
lib.rs

1// Copyright 2021 the Tectonic Project
2// Licensed under the MIT License.
3
4#![deny(missing_docs)]
5
6//! The `xdvipdfmx` program from [XeTeX] as a reusable crate.
7//!
8//! [XeTeX]: http://xetex.sourceforge.net/
9//!
10//! The `xdvipdfmx` progam converts XeTeX "XDV" intermediate files into PDF
11//! output files.
12//!
13//! This crate provides the `xdvipdfmx` implementation used by [Tectonic].
14//! However, in order to obtain the full Tectonic user experience, it must be
15//! combined with a variety of other utilities: the main XeTeX engine, code to
16//! fetch support files, and so on. Rather than using this crate directly you
17//! should probably use the main [`tectonic`] crate, which combines all of these
18//! pieces into a (semi) coherent whole.
19//!
20//! [Tectonic]: https://tectonic-typesetting.github.io/
21//! [`tectonic`]: https://docs.rs/tectonic/
22
23use std::{ffi::CString, time::SystemTime};
24use tectonic_bridge_core::{CoreBridgeLauncher, EngineAbortedError};
25use tectonic_errors::prelude::*;
26
27/// A struct for invoking the `xdvipdfmx` engine.
28///
29/// This struct has a fairly straightforward “builder” interface: you create it,
30/// apply any settings that you wish, and eventually run the
31/// [`process()`](Self::process) method.
32///
33/// Due to constraints of the gnarly C/C++ code underlying the engine
34/// implementation, only one engine may run at once in one process. The engine
35/// execution framework uses a global mutex to ensure that this is the case.
36/// This restriction applies not only to the [`XdvipdfmxEngine`] type but to
37/// *all* Tectonic engines. I.e., you can't run this engine and the XeTeX engine
38/// at the same time.
39pub struct XdvipdfmxEngine {
40    paper_spec: String,
41    enable_compression: bool,
42    deterministic_tags: bool,
43    build_date: SystemTime,
44}
45
46impl Default for XdvipdfmxEngine {
47    fn default() -> Self {
48        XdvipdfmxEngine {
49            paper_spec: "letter".to_owned(),
50            enable_compression: true,
51            deterministic_tags: false,
52            build_date: SystemTime::UNIX_EPOCH,
53        }
54    }
55}
56
57impl XdvipdfmxEngine {
58    /// Set whether compression will be enabled in the output PDF.
59    ///
60    /// The default value is true. You might want to set this to false to
61    /// improve the reproducibility of your generated PDFs, since different
62    /// environments may create different compressed outputs even if their
63    /// inputs and algorithms are the same. If this is your interest,
64    /// see also [`enable_deterministic_tags`](Self::enable_deterministic_tags).
65    pub fn enable_compression(&mut self, enable_compression: bool) -> &mut Self {
66        self.enable_compression = enable_compression;
67        self
68    }
69
70    /// Set whether font tags will be generated deterministically.
71    ///
72    /// The default is false: the engine includes some random characters when
73    /// creating font tags. Changing this to true helps create byte-for-byte
74    /// reproducible PDF outputs.
75    pub fn enable_deterministic_tags(&mut self, deterministic_tags: bool) -> &mut Self {
76        self.deterministic_tags = deterministic_tags;
77        self
78    }
79
80    /// Sets the build date embedded in the output artifacts
81    ///
82    /// The default value is the Unix epoch, which is almost certainly not what
83    /// you want. This value is used as a source of entropy and is written to
84    /// the output PDF.
85    pub fn build_date(&mut self, date: SystemTime) -> &mut Self {
86        self.build_date = date;
87        self
88    }
89
90    /// Set the initial paper size specification to be used.
91    ///
92    /// The default is `"letter"`, regardless of current locale.
93    pub fn paper_spec(&mut self, paper_spec: String) -> &mut Self {
94        self.paper_spec = paper_spec;
95        self
96    }
97
98    /// Run xdvipdfmx.
99    ///
100    /// The *launcher* parameter gives overarching environmental context in
101    /// which the engine will be run.
102    ///
103    /// The *dvi* parameter gives the name of the DVI file, created by the TeX
104    /// engine, that will be processed. In Tectonic this is actually an XDV
105    /// file, containing extended features needed for XeTeX Unicode processing.
106    ///
107    /// The *pdf* parameter gives the name of the output PDF file to create.
108    pub fn process(
109        &mut self,
110        launcher: &mut CoreBridgeLauncher,
111        dvi: &str,
112        pdf: &str,
113    ) -> Result<()> {
114        let paperspec_str = atry!(
115            CString::new(self.paper_spec.as_str());
116            ["paper_spec may not contain internal NULs"]
117        );
118
119        let config = c_api::XdvipdfmxConfig {
120            paperspec: paperspec_str.as_c_str().as_ptr(),
121            enable_compression: u8::from(self.enable_compression),
122            deterministic_tags: u8::from(self.deterministic_tags),
123            build_date: self
124                .build_date
125                .duration_since(SystemTime::UNIX_EPOCH)
126                .expect("invalid build date")
127                .as_secs(),
128        };
129
130        let cdvi = CString::new(dvi)?;
131        let cpdf = CString::new(pdf)?;
132
133        launcher.with_global_lock(|state| {
134            let r = unsafe {
135                c_api::tt_engine_xdvipdfmx_main(state, &config, cdvi.as_ptr(), cpdf.as_ptr())
136            };
137
138            // At the moment, the only possible return codes are 0 and 99 (= abort).
139            if r == 99 {
140                Err(EngineAbortedError::new_abort_indicator().into())
141            } else {
142                Ok(())
143            }
144        })
145    }
146}
147
148#[doc(hidden)]
149pub mod c_api {
150    // If you change the interfaces here, rerun cbindgen as described in the README!
151
152    use tectonic_bridge_core::CoreBridgeState;
153
154    #[derive(Debug)]
155    #[repr(C)]
156    pub struct XdvipdfmxConfig {
157        pub paperspec: *const libc::c_char,
158        pub enable_compression: libc::c_uchar,
159        pub deterministic_tags: libc::c_uchar,
160        pub build_date: u64,
161    }
162
163    #[allow(improper_ctypes)] // for CoreBridgeState
164    extern "C" {
165        pub fn tt_engine_xdvipdfmx_main(
166            api: &mut CoreBridgeState,
167            cfg: &XdvipdfmxConfig,
168            dviname: *const libc::c_char,
169            pdfname: *const libc::c_char,
170        ) -> libc::c_int;
171    }
172}
173
174/// Import things from our bridge crates to ensure that we actually link with
175/// them.
176mod linkage {
177    #[allow(unused_imports)]
178    use tectonic_pdf_io as clipyrenamehack;
179}
180
181/// Does our resulting executable link correctly?
182#[test]
183fn linkage() {}