Added image rendering and notifications
This commit is contained in:
parent
98d34ad5c3
commit
d15aa2a4ed
200
src-tauri/src/retrieval.rs
Normal file
200
src-tauri/src/retrieval.rs
Normal 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(¤t_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())
|
||||
}
|
||||
15
src/models/TreeViewModel.ts
Normal file
15
src/models/TreeViewModel.ts
Normal 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,
|
||||
) { }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user