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 let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
246 *index = n;
247 }
248 self.tail_append(p);
249 }
250
251 pub fn is_image(&self, p: NodeRef) -> bool {
253 let n = self.nodes.get(p);
254 n.subtype == IMAGE_RULE && matches!(n.data, crate::nodes::NodeData::Rule { .. })
255 }
256
257 pub fn image(&self, n: u32) -> Option<&ImageResource> {
259 let i = self
261 .images
262 .binary_search_by_key(&(n as i32), |r| r.obj)
263 .ok()?;
264 self.images.get(i)
265 }
266
267 pub fn mark_image_written(&mut self, n: u32) -> bool {
269 let Ok(i) = self.images.binary_search_by_key(&(n as i32), |r| r.obj) else {
270 return false;
271 };
272 !std::mem::replace(&mut self.images[i].written, true)
273 }
274
275 pub fn print_image_name(&mut self, n: u32) {
277 let Some(path) = self.image(n).map(|i| i.path.to_string_lossy().into_owned()) else {
278 return;
279 };
280 let mut text = b"<".to_vec();
282 text.extend_from_slice(path.as_bytes());
283 text.push(b'>');
284 self.printer.print_lua_bytes(&text);
285 }
286
287 pub fn image_name(&self, n: u32) -> u32 {
289 self.images
290 .binary_search_by_key(&(n as i32), |r| r.obj)
291 .map_or(n, |i| i as u32 + 1)
292 }
293
294 pub fn box_resource(&self, n: u32) -> Option<(&BoxResource, u32)> {
297 let i = self.box_resource_slot(n)?;
298 Some((&self.box_resources[i], i as u32 + 1))
299 }
300
301 pub fn box_resource_slot(&self, n: u32) -> Option<usize> {
304 self.box_resources
305 .binary_search_by_key(&(n as i32), |r| r.obj)
306 .ok()
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn completing_sizes() {
316 let nat = (26312704, 19734528); let d = |w, h, dp| Dims {
318 width: w,
319 height: h,
320 depth: dp,
321 };
322 assert_eq!(
324 complete(d(Some(3729359), None, None), nat),
325 (3729359, 2797019, 0)
326 );
327 assert_eq!(
329 complete(d(None, Some(327680), Some(65536)), nat),
330 (524288, 327680, 65536)
331 );
332 }
333}
334
335pub const BOX_RESOURCE_RULE: u16 = 1;
337
338#[derive(Clone, Debug)]
341pub struct BoxResource {
342 pub list: NodeRef,
344 pub size: (i32, i32, i32),
346 pub attr: Vec<u8>,
348 pub resources: Vec<u8>,
350 pub written: bool,
352 pub obj: i32,
354}
355
356impl Engine {
357 pub fn save_box_resource(&mut self, immediate: bool) {
360 let mut attr = Vec::new();
361 let mut resources = Vec::new();
362 loop {
363 if self.scan_keyword("attr") {
364 let toks = self.scan_toks_expanded();
365 attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
366 } else if self.scan_keyword("resources") {
367 let toks = self.scan_toks_expanded();
368 resources =
369 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
370 } else if self.scan_keyword("type") {
371 self.scan_int();
372 } else if self.scan_keyword("margin") {
373 self.scan_normal_dimen();
374 } else {
375 break;
376 }
377 }
378 self.scan_register_num();
379 let n = self.cur_val as usize;
380 let list = self.eqtb.boxes[n].val;
381 if list.is_null() {
382 self.print_err("Invalid \\box");
383 self.help(&["\\saveboxresource needs a box register that is not void."]);
384 self.error();
385 return;
386 }
387 self.eqtb.boxes[n].val = NodeRef::NULL;
388 let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Xform);
389 let size = self
390 .nodes
391 .box_data(list)
392 .map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
393 self.box_resources.push(BoxResource {
394 list,
395 size,
396 attr,
397 resources,
398 written: false,
399 obj,
400 });
401 self.last_box_index = obj;
402 if immediate && let Some(be) = self.backend.take() {
403 let be = self.write_box_resource(be, obj as u32);
404 self.pdf.stream_written = true;
405 self.backend = Some(be);
406 }
407 }
408
409 pub fn use_box_resource(&mut self) {
411 let mut dims = Dims::default();
412 while self.scan_image_dims(&mut dims) {}
413 self.scan_int();
414 let n = self.cur_val;
415 let Some(res) = u32::try_from(n)
416 .ok()
417 .and_then(|n| self.box_resource_slot(n))
418 .map(|i| &self.box_resources[i])
419 else {
420 self.print_err("Invalid box resource index ");
421 self.printer.print_int(i64::from(n));
422 self.help(&["\\useboxresource needs a number from \\lastsavedboxresourceindex."]);
423 self.error();
424 return;
425 };
426 let (w0, h0, d0) = res.size;
427 let (w, h, d) = match (dims.width, dims.height, dims.depth) {
429 (None, None, None) => (w0, h0, d0),
430 (Some(w), Some(h), Some(d)) => (w, h, d),
431 (Some(w), h, d) => (
432 w,
433 h.unwrap_or_else(|| scale(h0, w, w0)),
434 d.unwrap_or_else(|| scale(d0, w, w0)),
435 ),
436 (None, Some(h), d) => (scale(w0, h, h0), h, d.unwrap_or_else(|| scale(d0, h, h0))),
437 (None, None, Some(d)) => (scale(w0, d, d0), scale(h0, d, d0), d),
438 };
439 let p = self.nodes.new_rule(w, h, d);
440 self.nodes.get_mut(p).subtype = BOX_RESOURCE_RULE;
441 if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
442 *index = n;
443 }
444 self.tail_append(p);
445 }
446}