use std::path::PathBuf;
use retex_image::{Info, PageBox};
use crate::Engine;
use crate::nodes::NodeRef;
pub const IMAGE_RULE: u16 = 2;
#[derive(Clone, Debug)]
pub struct ImageResource {
pub path: PathBuf,
pub name: String,
pub info: Info,
pub page: u32,
pub page_box: PageBox,
pub attr: Vec<u8>,
pub size: (i32, i32, i32),
pub natural: (i32, i32),
pub obj: i32,
pub written: bool,
}
#[derive(Clone, Copy, Debug, Default)]
struct Dims {
width: Option<i32>,
height: Option<i32>,
depth: Option<i32>,
}
fn scale(a: i32, b: i32, c: i32) -> i32 {
if c == 0 {
return 0;
}
let (a, b, c) = (i64::from(a), i64::from(b), i64::from(c));
((a * b + c / 2) / c) as i32
}
fn complete(d: Dims, (w0, h0): (i32, i32)) -> (i32, i32, i32) {
let depth = d.depth.unwrap_or(0);
match (d.width, d.height, d.depth) {
(None, None, None) => (w0, h0, 0),
(Some(w), Some(h), _) => (w, h, depth),
(Some(w), None, _) => (w, scale(w, h0, w0) - depth, depth),
(None, Some(h), _) => (scale(h + depth, w0, h0), h, depth),
(None, None, Some(dp)) => (w0, h0 - dp, dp),
}
}
impl Engine {
fn scan_image_dims(&mut self, d: &mut Dims) -> bool {
if self.scan_keyword("width") {
self.scan_normal_dimen();
d.width = Some(self.cur_val);
} else if self.scan_keyword("height") {
self.scan_normal_dimen();
d.height = Some(self.cur_val);
} else if self.scan_keyword("depth") {
self.scan_normal_dimen();
d.depth = Some(self.cur_val);
} else {
return false;
}
true
}
pub fn save_image_resource(&mut self, immediate: bool) {
let mut dims = Dims::default();
let mut attr = Vec::new();
let mut page = 1u32;
let mut page_box = PageBox::Crop;
loop {
if self.scan_image_dims(&mut dims) {
continue;
}
if self.scan_keyword("attr") {
let toks = self.scan_toks_expanded();
attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
} else if self.scan_keyword("named") {
let _ = self.scan_toks_expanded();
} else if self.scan_keyword("page") {
self.scan_int();
page = self.cur_val.max(1) as u32;
} else if self.scan_keyword("colorspace") {
self.scan_int();
} else if self.scan_keyword("userpassword") || self.scan_keyword("ownerpassword") {
let _ = self.scan_toks_expanded();
} else if self.scan_keyword("mediabox") {
page_box = PageBox::Media;
} else if self.scan_keyword("cropbox") {
page_box = PageBox::Crop;
} else if self.scan_keyword("bleedbox") {
page_box = PageBox::Bleed;
} else if self.scan_keyword("trimbox") {
page_box = PageBox::Trim;
} else if self.scan_keyword("artbox") {
page_box = PageBox::Art;
} else {
break;
}
}
let toks = self.scan_toks_expanded();
let name_bytes =
self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
let name = String::from_utf8_lossy(&name_bytes).into_owned();
let found = self.find_input_file(&name, retex_kpath::FileFormat::Tex);
let info = found
.as_ref()
.map(|p| retex_image::probe_file(p, page, page_box));
if let Some(p) = &found
&& !self.files.watched.contains(p)
{
self.files.watched.push(p.clone());
}
let (path, info) = match (found, info) {
(Some(p), Some(Ok(i))) => (p, i),
(Some(p), Some(Err(e))) if e.to_string().contains("does not exist") => {
if let Ok(i) = retex_image::probe_file(&p, 1, page_box) {
self.image_version_check(&name, &i);
}
let category = format!("file {name}) (pdf inclusion");
let msg = format!("required page '{page}' does not exist");
if self.eqtb.int(crate::primitives_gen::assign_int::OUTPUTMODE) > 0 {
self.pdf_fatal_in(&category, &msg);
}
self.luatex_fatal(&category, &msg);
}
(_, Some(Err(e))) => {
self.image_error(&name, &e.to_string());
return;
}
_ => {
self.image_error(&name, "file not found");
return;
}
};
if !self.image_version_check(&name, &info) {
return;
}
for (cat, msg) in &info.warnings {
self.luatex_warning(&format!("file {name}) ({cat}"), msg);
}
let natural = retex_image::natural_size(&info);
let size = complete(dims, natural);
let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Ximage);
self.last_image_pages = info.pages as i32;
self.images.push(ImageResource {
path,
name,
info,
page,
page_box,
attr,
size,
natural,
obj,
written: false,
});
self.last_image_index = obj;
if immediate && let Some(mut be) = self.backend.take() {
be.write_image(self, obj as u32);
self.backend = Some(be);
self.mark_image_written(obj as u32);
self.print_image_name(obj as u32);
self.pdf.stream_written = true;
}
}
fn image_version_check(&mut self, name: &str, info: &retex_image::Info) -> bool {
if let Some(found) = info.pdf_version {
let int = |i: u32| self.eqtb.int(i).max(0) as u32;
let allowed = (
int(crate::backend::MAJOR_VERSION),
int(crate::backend::MINOR_VERSION),
);
if let Some(msg) = retex_image::pdf::version_message(found, allowed) {
if self.eqtb.int(145) > 0 {
self.image_error(name, &msg);
return false;
}
self.luatex_warning(&format!("file {name}) (pdf inclusion"), &msg);
}
}
true
}
fn image_error(&mut self, name: &str, why: &str) {
self.print_err("Image file `");
self.printer.print_str(name);
self.printer.print_str("': ");
self.printer.print_str(why);
self.help(&["The image could not be read, so nothing was saved."]);
self.error();
}
pub fn use_image_resource(&mut self) {
let mut dims = Dims::default();
while self.scan_image_dims(&mut dims) {}
self.scan_int();
let n = self.cur_val;
let Some(img) = self.image(n as u32) else {
self.print_err("Invalid image resource index ");
self.printer.print_int(i64::from(n));
self.help(&["\\useimageresource needs a number from \\lastsavedimageresourceindex."]);
self.error();
return;
};
let (w, h, d) = if dims.width.is_none() && dims.height.is_none() && dims.depth.is_none() {
img.size
} else {
complete(dims, img.natural)
};
let p = self.nodes.new_rule(w, h, d);
self.nodes.get_mut(p).subtype = IMAGE_RULE;
if self.eqtb.attrs_in_use {
let a = self.cur_attr();
self.nodes.set_attr(p, a);
}
if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
*index = n;
}
self.tail_append(p);
}
pub fn is_image(&self, p: NodeRef) -> bool {
let n = self.nodes.get(p);
n.subtype == IMAGE_RULE && matches!(n.data, crate::nodes::NodeData::Rule { .. })
}
pub fn image(&self, n: u32) -> Option<&ImageResource> {
let i = self
.images
.binary_search_by_key(&(n as i32), |r| r.obj)
.ok()?;
self.images.get(i)
}
pub fn mark_image_written(&mut self, n: u32) -> bool {
let Ok(i) = self.images.binary_search_by_key(&(n as i32), |r| r.obj) else {
return false;
};
!std::mem::replace(&mut self.images[i].written, true)
}
pub fn print_image_name(&mut self, n: u32) {
let Some(path) = self.image(n).map(|i| i.path.to_string_lossy().into_owned()) else {
return;
};
let mut text = b"<".to_vec();
text.extend_from_slice(path.as_bytes());
text.push(b'>');
self.printer.print_lua_bytes(&text);
}
pub fn image_name(&self, n: u32) -> u32 {
self.images
.binary_search_by_key(&(n as i32), |r| r.obj)
.map_or(n, |i| i as u32 + 1)
}
pub fn box_resource(&self, n: u32) -> Option<(&BoxResource, u32)> {
let i = self.box_resource_slot(n)?;
Some((&self.box_resources[i], i as u32 + 1))
}
pub fn box_resource_slot(&self, n: u32) -> Option<usize> {
self.box_resources
.binary_search_by_key(&(n as i32), |r| r.obj)
.ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn completing_sizes() {
let nat = (26312704, 19734528); let d = |w, h, dp| Dims {
width: w,
height: h,
depth: dp,
};
assert_eq!(
complete(d(Some(3729359), None, None), nat),
(3729359, 2797019, 0)
);
assert_eq!(
complete(d(None, Some(327680), Some(65536)), nat),
(524288, 327680, 65536)
);
}
}
pub const BOX_RESOURCE_RULE: u16 = 1;
#[derive(Clone, Debug)]
pub struct BoxResource {
pub list: NodeRef,
pub size: (i32, i32, i32),
pub attr: Vec<u8>,
pub resources: Vec<u8>,
pub written: bool,
pub obj: i32,
}
impl Engine {
pub fn save_box_resource(&mut self, immediate: bool) {
let mut attr = Vec::new();
let mut resources = Vec::new();
loop {
if self.scan_keyword("attr") {
let toks = self.scan_toks_expanded();
attr = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
} else if self.scan_keyword("resources") {
let toks = self.scan_toks_expanded();
resources =
self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
} else if self.scan_keyword("type") {
self.scan_int();
} else if self.scan_keyword("margin") {
self.scan_normal_dimen();
} else {
break;
}
}
self.scan_register_num();
let n = self.cur_val as usize;
let list = self.eqtb.boxes[n].val;
if list.is_null() {
self.print_err("Invalid \\box");
self.help(&["\\saveboxresource needs a box register that is not void."]);
self.error();
return;
}
self.eqtb.boxes[n].val = NodeRef::NULL;
let obj = self.pdf_alloc_obj(crate::pdfext::ObjKind::Xform);
let size = self
.nodes
.box_data(list)
.map_or((0, 0, 0), |b| (b.width, b.height, b.depth));
self.box_resources.push(BoxResource {
list,
size,
attr,
resources,
written: false,
obj,
});
self.last_box_index = obj;
if immediate && let Some(be) = self.backend.take() {
let be = self.write_box_resource(be, obj as u32);
self.pdf.stream_written = true;
self.backend = Some(be);
}
}
pub fn use_box_resource(&mut self) {
let mut dims = Dims::default();
while self.scan_image_dims(&mut dims) {}
self.scan_int();
let n = self.cur_val;
let Some(res) = u32::try_from(n)
.ok()
.and_then(|n| self.box_resource_slot(n))
.map(|i| &self.box_resources[i])
else {
self.print_err("Invalid box resource index ");
self.printer.print_int(i64::from(n));
self.help(&["\\useboxresource needs a number from \\lastsavedboxresourceindex."]);
self.error();
return;
};
let (w0, h0, d0) = res.size;
let (w, h, d) = match (dims.width, dims.height, dims.depth) {
(None, None, None) => (w0, h0, d0),
(Some(w), Some(h), Some(d)) => (w, h, d),
(Some(w), h, d) => (
w,
h.unwrap_or_else(|| scale(h0, w, w0)),
d.unwrap_or_else(|| scale(d0, w, w0)),
),
(None, Some(h), d) => (scale(w0, h, h0), h, d.unwrap_or_else(|| scale(d0, h, h0))),
(None, None, Some(d)) => (scale(w0, d, d0), scale(h0, d, d0), d),
};
let p = self.nodes.new_rule(w, h, d);
self.nodes.get_mut(p).subtype = BOX_RESOURCE_RULE;
if self.eqtb.attrs_in_use {
let a = self.cur_attr();
self.nodes.set_attr(p, a);
}
if let crate::nodes::NodeData::Rule { index, .. } = &mut self.nodes.get_mut(p).data {
*index = n;
}
self.tail_append(p);
}
}