Skip to main content

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