Added image rendering and notifications

This commit is contained in:
Kilian Schuettler 2025-02-10 12:13:27 +01:00
parent 98d34ad5c3
commit d15aa2a4ed
2 changed files with 215 additions and 0 deletions

200
src-tauri/src/retrieval.rs Normal file
View File

@ -0,0 +1,200 @@
use std::{
collections::VecDeque,
ffi::{OsStr, OsString},
io::Cursor,
sync::Arc,
};
use image::{DynamicImage, GrayImage, RgbImage};
use pdf::{
enc::StreamFilter,
file::FileOptions,
object::{ColorSpace, ImageXObject, ObjectWrite, PlainRef, Resolve, Stream},
primitive::{PdfStream, Primitive},
};
use crate::{CosFile, PathTrace, Step};
#[macro_export]
macro_rules! t {
($result:expr) => {{
match $result {
Ok(f) => f,
Err(e) => return Err(e.to_string()),
}
}};
}
pub fn get_prim_by_steps_with_file(
mut steps: VecDeque<Step>,
file: &CosFile,
) -> Result<(Step, Primitive, Vec<PathTrace>), String> {
if steps.len() == 0 {
return Err(String::from(format!("{:?} is not a valid path!", steps)));
}
let step = steps.pop_front().unwrap();
let (mut parent, trace) = resolve_parent(step.clone(), file)?;
let mut last_jump = trace.last_jump.clone();
let mut trace = vec![trace];
let mut current_prim = &parent;
while !steps.is_empty() {
let step = steps.pop_front().unwrap();
current_prim = resolve_step(&current_prim, &step)?;
if let Primitive::Reference(xref) = current_prim {
last_jump = xref.id.to_string();
parent = resolve_p_ref(xref.clone(), file)?;
current_prim = &parent;
}
trace.push(PathTrace::new(step.get_key(), last_jump.clone()));
}
Ok((step, current_prim.clone(), trace))
}
pub fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
let parent = match step {
Step::Page(page_num) => return retrieve_page(page_num, file),
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
Step::Trailer => retrieve_trailer(file),
_ => return Err(String::from(format!("{:?} is not a valid path!", step))),
};
Ok((parent, PathTrace::new(step.get_key(), step.get_key())))
}
pub fn resolve_step<'a>(current_prim: &'a Primitive, step: &Step) -> Result<&'a Primitive, String> {
Ok(match step {
Step::Number(index) => match current_prim {
Primitive::Array(prim_array) => {
let i = index.clone() as usize;
if prim_array.len() <= i {
return Err(String::from(format!(
"{} index out of bounds!",
step.get_key()
)));
}
&prim_array[i]
}
p => {
return Err(String::from(format!(
"{} is not indexed with numbers!",
p.get_debug_name()
)))
}
},
Step::String(key) => match current_prim {
Primitive::Dictionary(dict) => match dict.get(key) {
Some(prim) => prim,
None => {
return Err(String::from(format!(
"Key {} does not exist in Dictionary!",
key
)))
}
},
Primitive::Stream(stream) => match stream.info.get(key) {
Some(prim) => prim,
None => {
return Err(String::from(format!(
"Key {} does not exist in Info Dictionary!",
key
)))
}
},
p => {
return Err(String::from(format!(
"{} has no String paths!",
p.get_debug_name()
)))
}
},
_ => return Err(format!("Invalid Step: {}", step.get_key())),
})
}
pub fn retrieve_trailer(file: &CosFile) -> Primitive {
let mut updater = FileOptions::uncached().storage();
file.trailer.to_primitive(&mut updater).unwrap()
}
pub fn retrieve_page(page_num: u32, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
if page_num <= 0 {
return Err("Page 0 does not exist, use 1-based index!".to_string());
}
let page_rc = t!(file.get_page(page_num - 1));
let p_ref = page_rc.get_ref().get_inner();
Ok((
resolve_p_ref(p_ref, file)?,
PathTrace::new(format!("Page{}", page_num), p_ref.id.to_string()),
))
}
pub fn resolve_xref(id: u64, file: &CosFile) -> Result<Primitive, String> {
let plain_ref = PlainRef { id, gen: 0 };
resolve_p_ref(plain_ref, file)
}
pub fn resolve_p_ref(plain_ref: PlainRef, file: &CosFile) -> Result<Primitive, String> {
file.resolver()
.resolve(plain_ref)
.map_err(|e| e.to_string())
}
pub fn get_prim_by_path_with_file(
path: &str,
file: &CosFile,
) -> Result<(Step, Primitive, Vec<PathTrace>), String> {
get_prim_by_steps_with_file(Step::parse(path), file)
}
pub fn get_image_by_path(path: &str, file: &CosFile) -> Result<DynamicImage, String> {
let stream = get_pdf_stream_by_path_with_file(path, file)?;
let resolver = file.resolver();
let img = t!(ImageXObject::from_stream(stream, &resolver));
let data = t!(img.image_data(&resolver));
let color_space = img.color_space.as_ref();
match color_space {
Some(ColorSpace::DeviceRGB) | Some(ColorSpace::CalRGB(_)) => {
RgbImage::from_raw(img.width, img.height, data.to_vec()).map(DynamicImage::ImageRgb8)
}
Some(ColorSpace::DeviceGray) | Some(ColorSpace::CalGray(_)) => {
GrayImage::from_raw(img.width, img.height, data.to_vec()).map(DynamicImage::ImageLuma8)
}
_ => return parse_image_lossy(data),
}
.ok_or_else(|| "Failed to decode image".to_string())
}
fn parse_image_lossy(data: Arc<[u8]>) -> Result<DynamicImage, String> {
let img = image::ImageReader::new(Cursor::new(data));
match img.decode() {
Ok(img) => Ok(img),
Err(e) => Err(format!("Failed to decode image without color space: {}", e)),
}
}
pub fn get_pdf_stream_by_path_with_file(path: &str, file: &CosFile) -> Result<PdfStream, String> {
let mut steps = Step::parse(path);
if steps
.pop_back()
.filter(|last| *last == Step::Data)
.is_none()
{
return Err(format!("Path {} does not end with Data", path));
}
let (_, prim, _) = get_prim_by_steps_with_file(steps, file)?;
let Primitive::Stream(stream) = prim else {
return Err(format!("Path {} does not point to a stream", path));
};
Ok(stream)
}
pub fn get_stream_data_by_path_with_file(path: &str, file: &CosFile) -> Result<Vec<u8>, String> {
let stream = get_pdf_stream_by_path_with_file(path, file)?;
let data =
t!(t!(Stream::<Primitive>::from_stream(stream, &file.resolver())).data(&file.resolver()));
Ok(data.to_vec())
}

View File

@ -0,0 +1,15 @@
import type { Trace } from "./Primitive.svelte";
export class TreeViewModel {
constructor(
public depth: number,
public key: string,
public ptype: string,
public sub_type: string,
public value: string,
public container: boolean,
public expanded: boolean,
public path: Trace[],
public active: boolean,
) { }
}