1use std::path::PathBuf;
28
29use retex_image::{Info, PageBox};
30
31use crate::Engine;
32use crate::nodes::NodeRef;
33
34pub const IMAGE_RULE: u16 = 2;
36
37#[derive(Clone, Debug)]
39pub struct ImageResource {
40 pub path: PathBuf,
42 pub name: String,
44 pub info: Info,
46 pub page: u32,
48 pub page_box: PageBox,
50 pub attr: Vec<u8>,
52 pub size: (i32, i32, i32),
54 pub natural: (i32, i32),
56 pub obj: i32,
58 pub written: bool,
60}
61
62#[derive(Clone, Copy, Debug, Default)]
64struct Dims {
65 width: Option<i32>,
66 height: Option<i32>,
67 depth: Option<i32>,
68}
69
70fn scale(a: i32, b: i32, c: i32) -> i32 {
72 if c == 0 {
73 return 0;
74 }
75 let (a, b, c) = (i64::from(a), i64::from(b), i64::from(c));
76 ((a * b + c / 2) / c) as i32
77}
78
79fn complete(d: Dims, (w0, h0): (i32, i32)) -> (i32, i32, i32) {
81 let depth = d.depth.unwrap_or(0);
82 match (d.width, d.height, d.depth) {
83 (None, None, None) => (w0, h0, 0),
84 (Some(w), Some(h), _) => (w, h, depth),
85 (Some(w), None, _) => (w, scale(w, h0, w0) - depth, depth),
86 (None, Some(h), _) => (scale(h + depth, w0, h0), h, depth),
87 (None, None, Some(dp)) => (w0, h0 - dp, dp),
88 }
89}
90
91impl Engine {
92 fn scan_image_dims(&mut self, d: &mut Dims) -> bool {
94 if self.scan_keyword("width") {
95 self.scan_normal_dimen();
96 d.width = Some(self.cur_val);
97 } else if self.scan_keyword("height") {
98 self.scan_normal_dimen();
99 d.height = Some(self.cur_val);
100 } else if self.scan_keyword("depth") {
101 self.scan_normal_dimen();
102 d.depth = Some(self.cur_val);
103 } else {
104 return false;
105 }
106 true
107 }
108
109 pub fn save_image_resource(&mut self, immediate: bool) {
111 let mut dims = Dims::default();
112 let mut attr = Vec::new();
113 let mut page = 1u32;
114 let mut page_box = PageBox::Crop;
115 loop {
116 if self.scan_image_dims(&mut dims) {
117 continue;
118 }
119 if self.scan_keyword("attr") {
120 let toks = self.scan_toks_expanded();
121 attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
122 } else if self.scan_keyword("named") {
123 let _ = self.scan_toks_expanded();
126 } else if self.scan_keyword("page") {
127 self.scan_int();
128 page = self.cur_val.max(1) as u32;
129 } else if self.scan_keyword("colorspace") {
130 self.scan_int();
131 } else if self.scan_keyword("userpassword") || self.scan_keyword("ownerpassword") {
132 let _ = self.scan_toks_expanded();
133 } else if self.scan_keyword("mediabox") {
134 page_box = PageBox::Media;
135 } else if self.scan_keyword("cropbox") {
136 page_box = PageBox::Crop;
137 } else if self.scan_keyword("bleedbox") {
138 page_box = PageBox::Bleed;
139 } else if self.scan_keyword("trimbox") {
140 page_box = PageBox::Trim;
141 } else if self.scan_keyword("artbox") {
142 page_box = PageBox::Art;
143 } else {
144 break;
145 }
146 }
147 let toks = self.scan_toks_expanded();
148 let name_bytes =
149 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
150 let name = String::from_utf8_lossy(&name_bytes).into_owned();
151 let found = self.find_input_file(&name, retex_kpath::FileFormat::Tex);
152 if found.is_some() {
153 self.set_svg_font_finder();
154 }
155 let info = found
156 .as_ref()
157 .map(|p| retex_image::probe_file(p, page, page_box));
158 if let Some(p) = &found
159 && !self.files.watched.contains(p)
160 {
161 self.files.watched.push(p.clone());
162 }
163 let (path, info) = match (found, info) {
164 (Some(p), Some(Ok(i))) => (p, i),
165 (Some(p), Some(Err(e))) if e.to_string().contains("does not exist") => {
166 if let Ok(i) = retex_image::probe_file(&p, 1, page_box) {
170 self.image_version_check(&name, &i);
171 }
172 let category = format!("file {name}) (pdf inclusion");
173 let msg = format!("required page '{page}' does not exist");
174 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
175 self.pdf_fatal_in(&category, &msg);
176 }
177 self.luatex_fatal(&category, &msg);
178 }
179 (_, Some(Err(e))) => {
180 self.image_error(&name, &e.to_string());
181 return;
182 }
183 _ => {
184 self.image_error(&name, "file not found");
185 return;
186 }
187 };
188 if !self.image_version_check(&name, &info) {
189 return;
190 }
191 for (cat, msg) in &info.warnings {
192 self.luatex_warning(&format!("file {name}) ({cat}"), msg);
193 }
194
195 let natural = retex_image::natural_size(&info);
196 let size = complete(dims, natural);
197 let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Ximage);
198 self.last_image_pages = info.pages as i32;
199 self.images.push(ImageResource {
200 path,
201 name,
202 info,
203 page,
204 page_box,
205 attr,
206 size,
207 natural,
208 obj,
209 written: false,
210 });
211 self.last_image_index = obj;
212 if immediate && let Some(mut be) = self.backend.take() {
213 be.write_image(self, obj as u32);
214 self.backend = Some(be);
215 self.mark_image_written(obj as u32);
216 self.print_image_name(obj as u32);
217 self.pdf.stream_written = true;
218 }
219 }
220
221 fn image_version_check(&mut self, name: &str, info: &retex_image::Info) -> bool {
225 if let Some(found) = info.pdf_version {
226 let int = |i: u32| self.eqtb.int(i).max(0) as u32;
227 let allowed = (
228 int(crate::backend::MAJOR_VERSION),
229 int(crate::backend::MINOR_VERSION),
230 );
231 if let Some(msg) = retex_image::pdf::version_message(found, allowed) {
232 if self.eqtb.int(145) > 0 {
233 self.image_error(name, &msg);
234 return false;
235 }
236 self.luatex_warning(&format!("file {name}) (pdf inclusion"), &msg);
237 }
238 }
239 true
240 }
241
242 fn image_error(&mut self, name: &str, why: &str) {
243 self.print_err("Image file `");
244 self.printer.print_str(name);
245 self.printer.print_str("': ");
246 self.printer.print_str(why);
247 self.help(&["The image could not be read, so nothing was saved."]);
248 self.error();
249 }
250
251 pub fn use_image_resource(&mut self) {
253 let mut dims = Dims::default();
254 while self.scan_image_dims(&mut dims) {}
255 self.scan_int();
256 let n = self.cur_val;
257 let Some(img) = self.image(n as u32) else {
258 self.print_err("Invalid image resource index ");
259 self.printer.print_int(i64::from(n));
260 self.help(&["\\useimageresource needs a number from \\lastsavedimageresourceindex."]);
261 self.error();
262 return;
263 };
264 let (w, h, d) = if dims.width.is_none() && dims.height.is_none() && dims.depth.is_none() {
265 img.size
266 } else {
267 complete(dims, img.natural)
268 };
269 let p = self.nodes.new_rule(w, h, d);
270 self.nodes.get_mut(p).subtype = IMAGE_RULE;
271 if self.eqtb.attrs_in_use {
272 let a = self.cur_attr();
273 self.nodes.set_attr(p, a);
274 }
275 if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
276 *index = n;
277 }
278 self.tail_append(p);
279 }
280
281 pub fn is_image(&self, p: NodeRef) -> bool {
283 let n = self.nodes.get(p);
284 n.subtype == IMAGE_RULE && matches!(n.data, crate::nodes::NodeData::Rule { .. })
285 }
286
287 pub fn image(&self, n: u32) -> Option<&ImageResource> {
289 let i = self
291 .images
292 .binary_search_by_key(&(n as i32), |r| r.obj)
293 .ok()?;
294 self.images.get(i)
295 }
296
297 pub fn mark_image_written(&mut self, n: u32) -> bool {
299 let Ok(i) = self.images.binary_search_by_key(&(n as i32), |r| r.obj) else {
300 return false;
301 };
302 !std::mem::replace(&mut self.images[i].written, true)
303 }
304
305 pub fn print_image_name(&mut self, n: u32) {
307 let Some(path) = self.image(n).map(|i| i.path.to_string_lossy().into_owned()) else {
308 return;
309 };
310 let mut text = b"<".to_vec();
312 text.extend_from_slice(path.as_bytes());
313 text.push(b'>');
314 self.printer.print_lua_bytes(&text);
315 }
316
317 pub fn image_name(&self, n: u32) -> u32 {
319 self.images
320 .binary_search_by_key(&(n as i32), |r| r.obj)
321 .map_or(n, |i| i as u32 + 1)
322 }
323
324 pub fn box_resource(&self, n: u32) -> Option<(&BoxResource, u32)> {
327 let i = self.box_resource_slot(n)?;
328 Some((&self.box_resources[i], i as u32 + 1))
329 }
330
331 pub fn box_resource_slot(&self, n: u32) -> Option<usize> {
334 self.box_resources
335 .binary_search_by_key(&(n as i32), |r| r.obj)
336 .ok()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn completing_sizes() {
346 let nat = (26312704, 19734528); let d = |w, h, dp| Dims {
348 width: w,
349 height: h,
350 depth: dp,
351 };
352 assert_eq!(
354 complete(d(Some(3729359), None, None), nat),
355 (3729359, 2797019, 0)
356 );
357 assert_eq!(
359 complete(d(None, Some(327680), Some(65536)), nat),
360 (524288, 327680, 65536)
361 );
362 }
363}
364
365pub const BOX_RESOURCE_RULE: u16 = 1;
367
368#[derive(Clone, Debug)]
371pub struct BoxResource {
372 pub list: NodeRef,
374 pub size: (i32, i32, i32),
376 pub attr: Vec<u8>,
378 pub resources: Vec<u8>,
380 pub written: bool,
382 pub obj: i32,
384}
385
386impl Engine {
387 pub fn save_box_resource(&mut self, immediate: bool) {
390 let mut attr = Vec::new();
391 let mut resources = Vec::new();
392 loop {
393 if self.scan_keyword("attr") {
394 let toks = self.scan_toks_expanded();
395 attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
396 } else if self.scan_keyword("resources") {
397 let toks = self.scan_toks_expanded();
398 resources =
399 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
400 } else if self.scan_keyword("type") {
401 self.scan_int();
402 } else if self.scan_keyword("margin") {
403 self.scan_normal_dimen();
404 } else {
405 break;
406 }
407 }
408 self.scan_register_num();
409 let n = self.cur_val as usize;
410 let list = self.eqtb.boxes[n].val;
411 if list.is_null() {
412 self.print_err("Invalid \\box");
413 self.help(&["\\saveboxresource needs a box register that is not void."]);
414 self.error();
415 return;
416 }
417 self.eqtb.boxes[n].val = NodeRef::NULL;
418 let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Xform);
419 let size = self
420 .nodes
421 .box_data(list)
422 .map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
423 self.box_resources.push(BoxResource {
424 list,
425 size,
426 attr,
427 resources,
428 written: false,
429 obj,
430 });
431 self.last_box_index = obj;
432 if immediate && let Some(be) = self.backend.take() {
433 let be = self.write_box_resource(be, obj as u32);
434 self.pdf.stream_written = true;
435 self.backend = Some(be);
436 }
437 }
438
439 pub fn use_box_resource(&mut self) {
441 let mut dims = Dims::default();
442 while self.scan_image_dims(&mut dims) {}
443 self.scan_int();
444 let n = self.cur_val;
445 let Some(res) = u32::try_from(n)
446 .ok()
447 .and_then(|n| self.box_resource_slot(n))
448 .map(|i| &self.box_resources[i])
449 else {
450 self.print_err("Invalid box resource index ");
451 self.printer.print_int(i64::from(n));
452 self.help(&["\\useboxresource needs a number from \\lastsavedboxresourceindex."]);
453 self.error();
454 return;
455 };
456 let (w0, h0, d0) = res.size;
457 let (w, h, d) = match (dims.width, dims.height, dims.depth) {
459 (None, None, None) => (w0, h0, d0),
460 (Some(w), Some(h), Some(d)) => (w, h, d),
461 (Some(w), h, d) => (
462 w,
463 h.unwrap_or_else(|| scale(h0, w, w0)),
464 d.unwrap_or_else(|| scale(d0, w, w0)),
465 ),
466 (None, Some(h), d) => (scale(w0, h, h0), h, d.unwrap_or_else(|| scale(d0, h, h0))),
467 (None, None, Some(d)) => (scale(w0, d, d0), scale(h0, d, d0), d),
468 };
469 let p = self.nodes.new_rule(w, h, d);
470 self.nodes.get_mut(p).subtype = BOX_RESOURCE_RULE;
471 if self.eqtb.attrs_in_use {
472 let a = self.cur_attr();
473 self.nodes.set_attr(p, a);
474 }
475 if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
476 *index = n;
477 }
478 self.tail_append(p);
479 }
480}
481
482const PACKAGE_ADDITIONS: &[(&str, &[u8], &[u8])] = &[
494 (
495 "luatex.def",
496 b"\\def\\Gin@extensions",
497 b"\\@namedef{Gin@rule@.svg}#1{{pdf}{.svg}{#1}}%
498\\@namedef{Gin@rule@.SVG}#1{{pdf}{.SVG}{#1}}%
499\\edef\\Gin@extensions{\\Gin@extensions,.svg,.SVG}%
500",
501 ),
502 (
503 "svg.sty",
504 b"\\newcommand*\\includesvg",
505 b"\\renewcommand*\\includesvg[2][]{%
506 \\begingroup
507 \\svg@deprecated@param
508 \\svg@local@param@set{#1}%
509 \\@svg@ink@latexfalse
510 \\svg@get@path{#2}{}%
511 \\if@svg@file@found
512 \\svg@@input{\\svg@file@base.\\svg@file@ext}%
513 \\else
514 \\svg@file@missing[\\svg@file@ext]{\\svg@file@base}{}%
515 \\fi
516 \\endgroup
517}%
518",
519 ),
520];
521
522impl Engine {
523 pub(crate) fn extend_package(&self, path: &std::path::Path, data: Vec<u8>) -> Vec<u8> {
526 if self.diagnostics.is_none() {
527 return data;
528 }
529 let Some(&(_, _, addition)) = PACKAGE_ADDITIONS.iter().find(|(name, mark, _)| {
530 path.file_name() == Some(std::ffi::OsStr::new(name))
531 && data.windows(mark.len()).any(|w| w == *mark)
532 }) else {
533 return data;
534 };
535 let Some(at) = data
536 .windows(b"\\endinput".len())
537 .rposition(|w| w == b"\\endinput")
538 .filter(|&i| i == 0 || data[i - 1] == b'\n')
539 else {
540 return data;
541 };
542 [&data[..at], addition, &data[at..]].concat()
543 }
544}
545
546impl Engine {
547 fn set_svg_font_finder(&mut self) {
551 if self.svg_fonts_set {
552 return;
553 }
554 self.svg_fonts_set = true;
555 let Some(k) = &self.kpse else {
556 return;
557 };
558 let mut k = k.borrow_mut();
559 let dirs = retex_font::loader::name_dirs(&mut k);
560 let cache = k.cache_dir().map(std::path::Path::to_path_buf);
561 retex_image::svg::set_font_finder(std::sync::Arc::new(move |family| {
562 retex_font::names::NameDatabase::shared(dirs.clone(), cache.as_deref())
563 .family_files(family)
564 .into_iter()
565 .map(std::path::Path::to_path_buf)
566 .collect()
567 }));
568 }
569}