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