1use std::{fmt::Write as FmtWrite, fs, io, path::PathBuf};
11use tectonic_bridge_core::SecuritySettings;
12use tectonic_bundles::{detect_bundle, Bundle};
13use tectonic_docmodel::{
14 document::{BuildTargetType, Document, InputFile},
15 workspace::{Workspace, WorkspaceCreator},
16};
17use tectonic_geturl::{DefaultBackend, GetUrlBackend};
18
19use crate::{
20 config, ctry,
21 driver::{OutputFormat, PassSetting, ProcessingSessionBuilder},
22 errors::{ErrorKind, Result},
23 status::StatusBackend,
24 test_util, tt_note,
25 unstable_opts::UnstableOptions,
26};
27
28#[derive(Clone, Debug, Default)]
30pub struct DocumentSetupOptions {
31 only_cached: bool,
34
35 security: SecuritySettings,
37
38 deterministic_mode: bool,
40}
41
42impl DocumentSetupOptions {
43 pub fn new_with_security(security: SecuritySettings) -> Self {
46 DocumentSetupOptions {
47 only_cached: false,
48 deterministic_mode: false,
49 security,
50 }
51 }
52
53 pub fn only_cached(&mut self, s: bool) -> &mut Self {
59 self.only_cached = s;
60 self
61 }
62
63 pub fn deterministic_mode(&mut self, s: bool) -> &mut Self {
65 self.deterministic_mode = s;
66 self
67 }
68}
69
70pub trait DocumentExt {
72 fn bundle(&self, setup_options: &DocumentSetupOptions) -> Result<Box<dyn Bundle>>;
77
78 fn setup_session(
83 &self,
84 output_profile: &str,
85 setup_options: &DocumentSetupOptions,
86 status: &mut dyn StatusBackend,
87 ) -> Result<ProcessingSessionBuilder>;
88}
89
90impl DocumentExt for Document {
91 fn bundle(&self, setup_options: &DocumentSetupOptions) -> Result<Box<dyn Bundle>> {
92 if config::is_config_test_mode_activated() {
94 let bundle = test_util::TestBundle::default();
95 return Ok(Box::new(bundle));
96 }
97
98 let d = detect_bundle(self.bundle_loc.clone(), setup_options.only_cached, None)?;
99
100 match d {
101 Some(b) => Ok(b),
102 None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Could not get bundle").into()),
103 }
104 }
105
106 fn setup_session(
107 &self,
108 output_profile: &str,
109 setup_options: &DocumentSetupOptions,
110 status: &mut dyn StatusBackend,
111 ) -> Result<ProcessingSessionBuilder> {
112 let profile = self.outputs.get(output_profile).ok_or_else(|| {
113 ErrorKind::Msg(format!(
114 "unrecognized output profile name \"{output_profile}\""
115 ))
116 })?;
117
118 let output_format = match profile.target_type {
119 BuildTargetType::Html => OutputFormat::Html,
120 BuildTargetType::Pdf => OutputFormat::Pdf,
121 };
122
123 let mut input_buffer = String::new();
124
125 for input in &profile.inputs {
126 match input {
127 InputFile::Inline(s) => {
128 writeln!(input_buffer, "{s}")?;
129 }
130 InputFile::File(f) => {
131 writeln!(input_buffer, "\\input{{{f}}}")?;
132 }
133 };
134 }
135
136 let mut sess_builder =
137 ProcessingSessionBuilder::new_with_security(setup_options.security.clone());
138
139 let extra_paths: Vec<PathBuf> = self
141 .extra_paths
142 .iter()
143 .map(|x| self.src_dir().join(x))
144 .collect();
145
146 sess_builder
147 .output_format(output_format)
148 .format_name(&profile.tex_format)
149 .build_date_from_env(setup_options.deterministic_mode)
150 .unstables(UnstableOptions {
151 deterministic_mode: setup_options.deterministic_mode,
152 extra_search_paths: extra_paths,
153 ..Default::default()
154 })
155 .pass(PassSetting::Default)
156 .primary_input_buffer(input_buffer.as_bytes())
157 .tex_input_name(output_profile)
158 .synctex(profile.synctex);
159
160 if profile.shell_escape {
161 if let Some(cwd) = &profile.shell_escape_cwd {
163 sess_builder.shell_escape_with_work_dir(cwd);
164 } else {
165 sess_builder.shell_escape_with_temp_dir();
166 }
167 }
168
169 if setup_options.only_cached {
170 tt_note!(status, "using only cached resource files");
171 }
172 sess_builder.bundle(self.bundle(setup_options)?);
173
174 let mut tex_dir = self.src_dir().to_owned();
175 tex_dir.push("src");
176 sess_builder.filesystem_root(&tex_dir);
177
178 let mut output_dir = self.build_dir().to_owned();
179 output_dir.push(output_profile);
180 ctry!(
181 fs::create_dir_all(&output_dir);
182 "couldn\'t create output directory `{}`", output_dir.display()
183 );
184 sess_builder.output_dir(&output_dir);
185
186 Ok(sess_builder)
187 }
188}
189
190pub trait WorkspaceCreatorExt {
192 fn create_defaulted(
198 self,
199 config: &config::PersistentConfig,
200 bundle: Option<String>,
201 ) -> Result<Workspace>;
202}
203
204impl WorkspaceCreatorExt for WorkspaceCreator {
205 fn create_defaulted(
206 self,
207 config: &config::PersistentConfig,
208 bundle: Option<String>,
209 ) -> Result<Workspace> {
210 let bundle_loc = if config::is_test_bundle_wanted(bundle.clone()) {
211 "test-bundle://".to_owned()
212 } else {
213 let loc = bundle.unwrap_or(config.default_bundle_loc().to_owned());
214 let mut gub = DefaultBackend::default();
215 gub.resolve_url(&loc)?
216 };
217
218 Ok(self.create(bundle_loc, Vec::new())?)
219 }
220}