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 let info = found
153 .as_ref()
154 .map(|p| retex_image::probe_file(p, page, page_box));
155 if let Some(p) = &found
156 && !self.files.watched.contains(p)
157 {
158 self.files.watched.push(p.clone());
159 }
160 let (path, info) = match (found, info) {
161 (Some(p), Some(Ok(i))) => (p, i),
162 (Some(p), Some(Err(e))) if e.to_string().contains("does not exist") => {
163 if let Ok(i) = retex_image::probe_file(&p, 1, page_box) {
167 self.image_version_check(&name, &i);
168 }
169 let category = format!("file {name}) (pdf inclusion");
170 let msg = format!("required page '{page}' does not exist");
171 if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
172 self.pdf_fatal_in(&category, &msg);
173 }
174 self.luatex_fatal(&category, &msg);
175 }
176 (_, Some(Err(e))) => {
177 self.image_error(&name, &e.to_string());
178 return;
179 }
180 _ => {
181 self.image_error(&name, "file not found");
182 return;
183 }
184 };
185 if !self.image_version_check(&name, &info) {
186 return;
187 }
188 for (cat, msg) in &info.warnings {
189 self.luatex_warning(&format!("file {name}) ({cat}"), msg);
190 }
191
192 let natural = retex_image::natural_size(&info);
193 let size = complete(dims, natural);
194 let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Ximage);
195 self.last_image_pages = info.pages as i32;
196 self.images.push(ImageResource {
197 path,
198 name,
199 info,
200 page,
201 page_box,
202 attr,
203 size,
204 natural,
205 obj,
206 written: false,
207 });
208 self.last_image_index = obj;
209 if immediate && let Some(mut be) = self.backend.take() {
210 be.write_image(self, obj as u32);
211 self.backend = Some(be);
212 self.mark_image_written(obj as u32);
213 self.print_image_name(obj as u32);
214 self.pdf.stream_written = true;
215 }
216 }
217
218 fn image_version_check(&mut self, name: &str, info: &retex_image::Info) -> bool {
222 if let Some(found) = info.pdf_version {
223 let int = |i: u32| self.eqtb.int(i).max(0) as u32;
224 let allowed = (
225 int(crate::backend::MAJOR_VERSION),
226 int(crate::backend::MINOR_VERSION),
227 );
228 if let Some(msg) = retex_image::pdf::version_message(found, allowed) {
229 if self.eqtb.int(145) > 0 {
230 self.image_error(name, &msg);
231 return false;
232 }
233 self.luatex_warning(&format!("file {name}) (pdf inclusion"), &msg);
234 }
235 }
236 true
237 }
238
239 fn image_error(&mut self, name: &str, why: &str) {
240 self.print_err("Image file `");
241 self.printer.print_str(name);
242 self.printer.print_str("': ");
243 self.printer.print_str(why);
244 self.help(&["The image could not be read, so nothing was saved."]);
245 self.error();
246 }
247
248 pub fn use_image_resource(&mut self) {
250 let mut dims = Dims::default();
251 while self.scan_image_dims(&mut dims) {}
252 self.scan_int();
253 let n = self.cur_val;
254 let Some(img) = self.image(n as u32) else {
255 self.print_err("Invalid image resource index ");
256 self.printer.print_int(i64::from(n));
257 self.help(&["\\useimageresource needs a number from \\lastsavedimageresourceindex."]);
258 self.error();
259 return;
260 };
261 let (w, h, d) = if dims.width.is_none() && dims.height.is_none() && dims.depth.is_none() {
262 img.size
263 } else {
264 complete(dims, img.natural)
265 };
266 let p = self.nodes.new_rule(w, h, d);
267 self.nodes.get_mut(p).subtype = IMAGE_RULE;
268 if self.eqtb.attrs_in_use {
269 let a = self.cur_attr();
270 self.nodes.set_attr(p, a);
271 }
272 if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
273 *index = n;
274 }
275 self.tail_append(p);
276 }
277
278 pub fn is_image(&self, p: NodeRef) -> bool {
280 let n = self.nodes.get(p);
281 n.subtype == IMAGE_RULE && matches!(n.data, crate::nodes::NodeData::Rule { .. })
282 }
283
284 pub fn image(&self, n: u32) -> Option<&ImageResource> {
286 let i = self
288 .images
289 .binary_search_by_key(&(n as i32), |r| r.obj)
290 .ok()?;
291 self.images.get(i)
292 }
293
294 pub fn mark_image_written(&mut self, n: u32) -> bool {
296 let Ok(i) = self.images.binary_search_by_key(&(n as i32), |r| r.obj) else {
297 return false;
298 };
299 !std::mem::replace(&mut self.images[i].written, true)
300 }
301
302 pub fn print_image_name(&mut self, n: u32) {
304 let Some(path) = self.image(n).map(|i| i.path.to_string_lossy().into_owned()) else {
305 return;
306 };
307 let mut text = b"<".to_vec();
309 text.extend_from_slice(path.as_bytes());
310 text.push(b'>');
311 self.printer.print_lua_bytes(&text);
312 }
313
314 pub fn image_name(&self, n: u32) -> u32 {
316 self.images
317 .binary_search_by_key(&(n as i32), |r| r.obj)
318 .map_or(n, |i| i as u32 + 1)
319 }
320
321 pub fn box_resource(&self, n: u32) -> Option<(&BoxResource, u32)> {
324 let i = self.box_resource_slot(n)?;
325 Some((&self.box_resources[i], i as u32 + 1))
326 }
327
328 pub fn box_resource_slot(&self, n: u32) -> Option<usize> {
331 self.box_resources
332 .binary_search_by_key(&(n as i32), |r| r.obj)
333 .ok()
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn completing_sizes() {
343 let nat = (26312704, 19734528); let d = |w, h, dp| Dims {
345 width: w,
346 height: h,
347 depth: dp,
348 };
349 assert_eq!(
351 complete(d(Some(3729359), None, None), nat),
352 (3729359, 2797019, 0)
353 );
354 assert_eq!(
356 complete(d(None, Some(327680), Some(65536)), nat),
357 (524288, 327680, 65536)
358 );
359 }
360}
361
362pub const BOX_RESOURCE_RULE: u16 = 1;
364
365#[derive(Clone, Debug)]
368pub struct BoxResource {
369 pub list: NodeRef,
371 pub size: (i32, i32, i32),
373 pub attr: Vec<u8>,
375 pub resources: Vec<u8>,
377 pub written: bool,
379 pub obj: i32,
381}
382
383impl Engine {
384 pub fn save_box_resource(&mut self, immediate: bool) {
387 let mut attr = Vec::new();
388 let mut resources = Vec::new();
389 loop {
390 if self.scan_keyword("attr") {
391 let toks = self.scan_toks_expanded();
392 attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
393 } else if self.scan_keyword("resources") {
394 let toks = self.scan_toks_expanded();
395 resources =
396 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
397 } else if self.scan_keyword("type") {
398 self.scan_int();
399 } else if self.scan_keyword("margin") {
400 self.scan_normal_dimen();
401 } else {
402 break;
403 }
404 }
405 self.scan_register_num();
406 let n = self.cur_val as usize;
407 let list = self.eqtb.boxes[n].val;
408 if list.is_null() {
409 self.print_err("Invalid \\box");
410 self.help(&["\\saveboxresource needs a box register that is not void."]);
411 self.error();
412 return;
413 }
414 self.eqtb.boxes[n].val = NodeRef::NULL;
415 let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Xform);
416 let size = self
417 .nodes
418 .box_data(list)
419 .map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
420 self.box_resources.push(BoxResource {
421 list,
422 size,
423 attr,
424 resources,
425 written: false,
426 obj,
427 });
428 self.last_box_index = obj;
429 if immediate && let Some(be) = self.backend.take() {
430 let be = self.write_box_resource(be, obj as u32);
431 self.pdf.stream_written = true;
432 self.backend = Some(be);
433 }
434 }
435
436 pub fn use_box_resource(&mut self) {
438 let mut dims = Dims::default();
439 while self.scan_image_dims(&mut dims) {}
440 self.scan_int();
441 let n = self.cur_val;
442 let Some(res) = u32::try_from(n)
443 .ok()
444 .and_then(|n| self.box_resource_slot(n))
445 .map(|i| &self.box_resources[i])
446 else {
447 self.print_err("Invalid box resource index ");
448 self.printer.print_int(i64::from(n));
449 self.help(&["\\useboxresource needs a number from \\lastsavedboxresourceindex."]);
450 self.error();
451 return;
452 };
453 let (w0, h0, d0) = res.size;
454 let (w, h, d) = match (dims.width, dims.height, dims.depth) {
456 (None, None, None) => (w0, h0, d0),
457 (Some(w), Some(h), Some(d)) => (w, h, d),
458 (Some(w), h, d) => (
459 w,
460 h.unwrap_or_else(|| scale(h0, w, w0)),
461 d.unwrap_or_else(|| scale(d0, w, w0)),
462 ),
463 (None, Some(h), d) => (scale(w0, h, h0), h, d.unwrap_or_else(|| scale(d0, h, h0))),
464 (None, None, Some(d)) => (scale(w0, d, d0), scale(h0, d, d0), d),
465 };
466 let p = self.nodes.new_rule(w, h, d);
467 self.nodes.get_mut(p).subtype = BOX_RESOURCE_RULE;
468 if self.eqtb.attrs_in_use {
469 let a = self.cur_attr();
470 self.nodes.set_attr(p, a);
471 }
472 if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
473 *index = n;
474 }
475 self.tail_append(p);
476 }
477}