tectonic/lib.rs
1// src/lib.rs -- main module file for the Tectonic library.
2// Copyright 2016-2022 the Tectonic Project
3// Licensed under the MIT License.
4
5#![recursion_limit = "1024"] // "error_chain can recurse deeply"
6
7//! Tectonic is a complete
8//! [TeX](https://www.tug.org/)/[LaTeX](https://www.latex-project.org/) engine
9//! converted into a standalone library. It is derived from the
10//! [XeTeX](http://xetex.sourceforge.net/) variant of TeX and uses the support
11//! files packages by the [TeX Live](https://www.tug.org/texlive/) project.
12//! Tectonic would not be possible without the hard work that has gone into
13//! these projects.
14//!
15//! Because Tectonic is based on the XeTeX engine, it can take advantage of the
16//! features of modern fonts (TrueType, OpenType, etc.), outputs directly to the
17//! PDF file format, and supports Unicode inputs. Most importantly, the TeX
18//! experience delivered by Tectonic is completely embeddable: if you link with
19//! this crate you can fully process TeX documents, from source to PDF, without
20//! relying on any externally installed software, configuration, or resource
21//! files. This is possible because Tectonic bundles the traditional TeX tools
22//! and routes their I/O through a pluggable backend system.
23//!
24//! This crate delivers command-line frontend, `tectonic`, that has a modernized
25//! user experience that hides TeX’s copious output (by default) and never asks
26//! for user input. Virtually all of the functionality of the frontend is
27//! accessible programmatically through the [`driver`] module of this crate.
28//!
29//! This crate joins a set of sub-crates that combine to provide the Tectonic
30//! user experience. Those crates generally have APIs that are more carefully
31//! structured and better documented than this crate, which grew somewhat
32//! organically. The foundational crates are:
33//!
34//! - [`tectonic_errors`](https://docs.rs/tectonic_errors) for core error
35//! handing types.
36//! - [`tectonic_status_base`](https://docs.rs/tectonic_status_base) for a basic
37//! user-facing status-reporting framework.
38//! - [`tectonic_io_base`](https://docs.rs/tectonic_io_base) for the I/O
39//! abstraction framework.
40//! - [`tectonic_bridge_core`](https://docs.rs/tectonic_bridge_core) for a
41//! framework to launch unsafe (C/C++) "engines" through [FFI].
42//!
43//! [FFI]: https://doc.rust-lang.org/std/ffi/index.html
44//!
45//! Building on these and other support crates of less general interest are the
46//! following major pieces of Tectonic’s functionality:
47//!
48//! - [`tectonic_bundles`](https://docs.rs/tectonic_bundles) for the"bundles" of
49//! TeX support files underlying Tectonic processing.
50//! - [`tectonic_docmodel`](https://docs.rs/tectonic_docmodel) for the Tectonic
51//! "document model" expressed in `Tectonic.toml` files.
52//! - [`tectonic_engine_xetex`](https://docs.rs/tectonic_engine_xetex) for the
53//! XeTeX engine.
54//! - [`tectonic_engine_xdvipdfmx`](https://docs.rs/tectonic_engine_xdvipdfmx)
55//! for the `xdvipdfmx` engine.
56//! - [`tectonic_engine_bibtex`](https://docs.rs/tectonic_engine_bibtex) for the
57//! BibTeX engine.
58//!
59//! The main module of this crate provides an all-in-wonder function for
60//! compiling LaTeX code to a PDF:
61//!
62//! ```
63//! use tectonic;
64//!
65//! let latex = r#"
66//! \documentclass{article}
67//! \begin{document}
68//! Hello, world!
69//! \end{document}
70//! "#;
71//!
72//! # tectonic::test_util::activate_test_mode_augmented(env!("CARGO_MANIFEST_DIR"));
73//! let pdf_data: Vec<u8> = tectonic::latex_to_pdf(latex).expect("processing failed");
74//! println!("Output PDF size is {} bytes", pdf_data.len());
75//! ```
76//!
77//! The [`driver`] module provides a high-level interface for driving the
78//! engines in more realistic circumstances.
79
80pub mod config;
81pub mod digest;
82#[cfg(feature = "serialization")]
83pub mod docmodel;
84pub mod driver;
85pub mod engines;
86pub mod errors;
87pub mod io;
88pub mod status;
89pub mod unstable_opts;
90
91// Note: this module is intentionally *not* gated by #[cfg(test)] -- see its
92// docstring for details.
93#[doc(hidden)]
94pub mod test_util;
95
96pub use crate::engines::bibtex::BibtexEngine;
97pub use crate::engines::spx2html::Spx2HtmlEngine;
98pub use crate::engines::tex::{TexEngine, TexOutcome};
99pub use crate::engines::xdvipdfmx::XdvipdfmxEngine;
100pub use crate::errors::{Error, ErrorKind, Result};
101
102// Convenienece re-exports for migration into our multi-crate setup
103pub use tectonic_engine_xetex::FORMAT_SERIAL;
104pub use tectonic_status_base::{tt_error, tt_note, tt_warning};
105
106/// Compile LaTeX text to a PDF.
107///
108/// This function is an all-in-one interface to the main Tectonic workflow. Given
109/// a string representing a LaTeX input file, it will compile it to a PDF and return
110/// a byte vector corresponding to the resulting file:
111///
112/// ```
113/// let latex = r#"
114/// \documentclass{article}
115/// \begin{document}
116/// Hello, world!
117/// \end{document}
118/// "#;
119///
120/// # tectonic::test_util::activate_test_mode_augmented(env!("CARGO_MANIFEST_DIR"));
121/// let pdf_data: Vec<u8> = tectonic::latex_to_pdf(latex).expect("processing failed");
122/// println!("Output PDF size is {} bytes", pdf_data.len());
123/// ```
124///
125/// The compilation uses the default bundle, the location of which is embedded
126/// in the crate or potentially specified in the user’s configuration file.
127/// The current working directory will be searched for any `\\input` files.
128/// Messages aimed at the user are suppressed, but (in the default
129/// configuration) network I/O may occur to pull down needed resource files.
130/// No outputs are written to disk; all supporting files besides the PDF
131/// document are discarded. The XeTeX engine is run multiple times if needed
132/// to get the output file to converge.
133///
134/// For more sophisticated uses, use the [`driver`] module, which provides a
135/// high-level interface for driving the typesetting engines with much more
136/// control over their behavior.
137///
138/// Note that the current engine implementations use lots of global state, so
139/// they are not thread-safe. This crate uses a global mutex to serialize
140/// invocations of the engines. This means that if you call this function from
141/// multiple threads simultaneously, the bulk of the work will be done in
142/// serial. The aim is to lift this limitation one day, but it will require
143/// extensive work on the underlying C/C++ code.
144pub fn latex_to_pdf<T: AsRef<str>>(latex: T) -> Result<Vec<u8>> {
145 let mut status = status::NoopStatusBackend::default();
146
147 let auto_create_config_file = false;
148 let config = ctry!(config::PersistentConfig::open(auto_create_config_file);
149 "failed to open the default configuration file");
150
151 let only_cached = false;
152 let bundle = ctry!(config.default_bundle(only_cached);
153 "failed to load the default resource bundle");
154
155 let format_cache_path = ctry!(config.format_cache_path();
156 "failed to set up the format cache");
157
158 let mut files = {
159 // Looking forward to non-lexical lifetimes!
160 let mut sb = driver::ProcessingSessionBuilder::default();
161 sb.bundle(bundle)
162 .primary_input_buffer(latex.as_ref().as_bytes())
163 .tex_input_name("texput.tex")
164 .format_name("latex")
165 .format_cache_path(format_cache_path)
166 .keep_logs(false)
167 .keep_intermediates(false)
168 .print_stdout(false)
169 .output_format(driver::OutputFormat::Pdf)
170 .do_not_write_output_files();
171
172 let mut sess =
173 ctry!(sb.create(&mut status); "failed to initialize the LaTeX processing session");
174 ctry!(sess.run(&mut status); "the LaTeX engine failed");
175 sess.into_file_data()
176 };
177
178 match files.remove("texput.pdf") {
179 Some(file) => Ok(file.data),
180 None => Err(errmsg!(
181 "LaTeX didn't report failure, but no PDF was created (??)"
182 )),
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 #[allow(unused_imports)]
189 use super::*;
190
191 #[test]
192 #[allow(unused_must_use)]
193 #[cfg(target_os = "linux")]
194 fn no_segfault_after_failed_compilation() {
195 /*
196 This is mostly relevant when using tectonic as a library.
197 After a compilation error xetex assumes the process will exit so
198 it doesn't fully cleanup its auxiliary structures. User some
199 conditions (like using fontconfig), compiling afterwards results in
200 a segmentation fault.
201 This test has no assertions because the simple fact that it didn't
202 crash the test runner means it succeeded.
203 */
204 for _ in 0..2 {
205 latex_to_pdf(
206 r"\documentclass{article}
207\usepackage{fontspec}
208\setmainfont{Ubuntu Mono}
209\begin{document}
210\invalidcommand{}
211\end{document}",
212 );
213 }
214 }
215}