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