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() {}