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