From 4e9e01bf94266db0e4a63005249dbc9edd384cfd Mon Sep 17 00:00:00 2001 From: Kilian Schuettler Date: Tue, 18 Feb 2025 01:37:16 +0100 Subject: [PATCH] updates working --- src-pdfrs/pdf/src/content.rs | 8 +- src-pdfrs/pdf/src/file.rs | 12 ++- src-pdfrs/pdf/src/xref.rs | 12 +++ src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/lib.rs | 156 +++++++++++++++++++++------- src-tauri/src/retrieval.rs | 25 +++-- src-tauri/src/tests.rs | 27 ++++- src/components/App.svelte | 1 - src/components/ChangesModal.svelte | 49 +++++++++ src/components/ContentsView.svelte | 59 ----------- src/components/FileView.svelte | 18 +++- src/components/PageView.svelte | 4 +- src/components/TitleBar.svelte | 111 +++++++++++++++++--- src/components/ToolbarRight.svelte | 28 ++++- src/components/TreeView.svelte | 50 +++------ src/components/TreeViewEntry.svelte | 87 ++++++++-------- src/components/XRefTable.svelte | 2 +- src/models/ContentModel.svelte.ts | 10 +- src/models/FileViewState.svelte.ts | 83 +++++++++++---- src/models/PageViewState.svelte.ts | 31 ++++-- src/models/PathHistory.svelte.ts | 54 ++++++++++ src/models/TreeViewState.svelte.ts | 8 +- 23 files changed, 589 insertions(+), 248 deletions(-) create mode 100644 src/components/ChangesModal.svelte delete mode 100644 src/components/ContentsView.svelte create mode 100644 src/models/PathHistory.svelte.ts diff --git a/src-pdfrs/pdf/src/content.rs b/src-pdfrs/pdf/src/content.rs index e831e53..25ace28 100644 --- a/src-pdfrs/pdf/src/content.rs +++ b/src-pdfrs/pdf/src/content.rs @@ -1269,7 +1269,13 @@ impl ObjectWrite for Content { let obj = self.parts[0].to_primitive(update)?; update.create(obj)?.to_primitive(update) } else { - self.parts.to_primitive(update) + let mut arr = vec![]; + for part in &self.parts { + let pdf_stream = part.to_primitive(update)?; + let rc_ref = update.create(pdf_stream)?; + arr.push(rc_ref.to_primitive(update)?); + } + Ok(Primitive::Array(arr)) } } } diff --git a/src-pdfrs/pdf/src/file.rs b/src-pdfrs/pdf/src/file.rs index a5bb1dc..50b3a0a 100644 --- a/src-pdfrs/pdf/src/file.rs +++ b/src-pdfrs/pdf/src/file.rs @@ -377,11 +377,12 @@ where let r = match self.refs.get(old.id)? { XRef::Free { .. } => panic!(), XRef::Raw { gen_nr, .. } => PlainRef { id: old.id, gen: gen_nr }, - XRef::Stream { .. } => return self.create(obj), + XRef::Stream { .. } => PlainRef { id: old.id, gen: 0 }, XRef::Promised => PlainRef { id: old.id, gen: 0 }, XRef::Invalid => panic!() }; let primitive = obj.to_primitive(self)?; + match self.changes.entry(old.id) { Entry::Vacant(e) => { e.insert((primitive, r.gen)); @@ -499,6 +500,7 @@ where fn fulfill(&mut self, promise: PromisedRef, obj: T) -> Result> { self.storage.fulfill(promise, obj) } + } impl File, OC, SC, L> @@ -660,6 +662,14 @@ where pub fn log(&self) -> &L { &self.storage.log } + + pub fn changes(&self) -> &HashMap { + &self.storage.changes + } + pub fn clear_changes(&mut self) { + self.storage.changes.clear(); + self.storage.refs.remove_promised(); + } } #[derive(Object, ObjectWrite, DataSize)] diff --git a/src-pdfrs/pdf/src/xref.rs b/src-pdfrs/pdf/src/xref.rs index c40cd6b..b898275 100644 --- a/src-pdfrs/pdf/src/xref.rs +++ b/src-pdfrs/pdf/src/xref.rs @@ -93,6 +93,18 @@ impl XRefTable { pub fn num_entries(&self) -> usize { self.entries.len() } + + pub fn remove_promised(&mut self) { + let mut new_entries = vec![]; + for entry in self.entries.iter() { + if let XRef::Promised = entry { + continue; + } + new_entries.push(entry.clone()); + } + self.entries = new_entries; + } + pub fn max_field_widths(&self) -> (u64, u64) { let mut max_a = 0; let mut max_b = 0; diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2d2f658..531085a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3689,6 +3689,7 @@ dependencies = [ "image 0.25.5", "indexmap 1.9.3", "lazy_static", + "log", "pathfinder_geometry", "pathfinder_rasterize", "pdf", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0f27e34..86dbe40 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -35,3 +35,4 @@ pathfinder_rasterize = { git = "https://github.com/s3bk/pathfinder_rasterizer" } pathfinder_geometry = { git = "https://github.com/servo/pathfinder" } indexmap = "1.9.3" tauri-plugin-websocket = "2" +log = "0.4.25" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 364c59b..ec4e98c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -11,18 +11,20 @@ use crate::render::Renderer; use image::DynamicImage; use lazy_static::lazy_static; use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache}; -use pdf::object::{Object, PlainRef, Stream, Updater}; +use pdf::object::{Object, ObjectWrite, Page, PageRc, PlainRef, Stream, Updater}; use pdf::primitive::{Dictionary, Primitive}; use pdf::xref::XRef; use regex::Regex; use retrieval::{get_prim_by_path_with_file, get_stream_data_by_path_with_file}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; +use std::ops::Deref; use std::path::Path; use std::sync::{Arc, RwLock}; use tauri::ipc::{InvokeResponseBody, Response}; use tauri::{Manager, State}; use uuid::Uuid; +use pdf::backend::Backend; type CosFile = File, ObjectCache, StreamCache, NoLog>; #[derive(Serialize, Debug, Clone)] @@ -74,11 +76,27 @@ pub struct PrimitiveTreeView { #[derive(Serialize, Debug, Clone)] pub struct PathTrace { pub key: String, + #[serde(skip_serializing)] + pub last_ref: Option, pub last_jump: String, } impl PathTrace { - fn new(key: String, last_jump: String) -> PathTrace { - PathTrace { key, last_jump } + fn new(key: String, last_ref: Option) -> PathTrace { + PathTrace { + key, + last_jump: last_ref + .map(|r| r.id.to_string()) + .unwrap_or("Trailer".to_string()), + last_ref: last_ref, + } + } + + fn trailer() -> PathTrace { + PathTrace { + key: "Trailer".to_string(), + last_ref: None, + last_jump: "Trailer".to_string(), + } } } @@ -115,6 +133,22 @@ fn get_all_files(session: State) -> Vec { .collect() } +#[tauri::command] +fn get_changes(id: &str, session: State) -> Result, String> { + let lock = session.get_file(id)?; + let file = lock.read().unwrap(); + Ok(file.cos_file.changes().iter() + .map(|(obj, (prim, gen))| PrimitiveModel::from_primitive_until_refs(prim, PathTrace::new(obj.to_string(), Some(PlainRef{id: *obj, gen: *gen})))) + .collect()) +} + +#[tauri::command] +fn clear_changes(id: &str, session: State) -> Result<(), String> { + let lock = session.get_file(id)?; + let mut file = lock.write().unwrap(); + file.cos_file.clear_changes(); + Ok(()) +} #[tauri::command] fn get_all_file_ids(session: State) -> Vec { session.get_all_file_ids() @@ -181,21 +215,16 @@ fn to_pdf_file(path: &str, file: &CosFile) -> Result { Ok(pdf_file) } -#[tauri::command] -fn get_contents(id: &str, path: &str, session: State) -> Result { - let lock = session.get_file(id)?; - let file = lock.read().unwrap(); - - let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?; - let resolver = file.cos_file.resolver(); - - let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver)); - if let Some(contents) = page.contents { +fn read_contents(page: &Page, resolver: &impl Resolve) -> Result { + if let Some(ref contents) = page.contents { let mut parts = vec![]; - for part in contents.parts { - let data = &t!(part.data(&resolver)); - let ops = t!(pdf::content::parse_ops(&data, &resolver)); - let part = t!(pdf::content::display_ops(&ops)); + for part in &contents.parts { + let data = &t!(part.data(resolver)); + let string = String::from_utf8_lossy(data); + let part = string + .split("\n") + .map(|s| s.to_string()) + .collect::>(); parts.push(part); } return Ok(ContentsModel { parts }); @@ -204,10 +233,53 @@ fn get_contents(id: &str, path: &str, session: State) -> Result) -> Result { + let lock = session.get_file(id)?; + let file = lock.read().unwrap(); + + let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?; + let resolver = file.cos_file.resolver(); + + let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver)); + read_contents(&page, &resolver) +} + +#[tauri::command] +async fn update_contents( id: &str, path: &str, - session: State, + session: State<'_, Session>, + contents: ContentsModel, +) -> Result<(), String> { + let lock = session.get_file(id)?; + let mut file = lock.write().unwrap(); + update_contents_with_file(path, contents, &mut file.cos_file) +} + +fn update_contents_with_file(path: &str, contents: ContentsModel, file: &mut CosFile) -> Result<(), String> { + let mut parts = vec![]; + for part in contents.parts { + let stream_ref = Stream::new((), part.join("\n").as_bytes()); + parts.push(stream_ref); + } + let new_contents = pdf::content::Content{parts}; + + if let Some(Step::Page(num)) = Step::parse(path).pop_front() { + let page_rc: PageRc = t!(file.get_page(num - 1)); + let page_ref = page_rc.get_ref().get_inner().clone(); + let mut page = page_rc.deref().clone(); + page.contents = Some(new_contents); + let _ = t!(file.update(page_ref, page)); + return Ok(()); + } + Err(String::from("Path does not lead to a page!")) +} + +#[tauri::command] +async fn get_stream_data_as_string( + id: &str, + path: &str, + session: State<'_, Session>, ) -> Result { let lock = session.get_file(id)?; let file = lock.read().unwrap(); @@ -226,8 +298,7 @@ async fn update_stream_data_as_string( let mut file = lock.write().unwrap(); let (_, _, trace) = get_prim_by_path_with_file(path, &file.cos_file)?; let i = trace.len() - 1; - let id = t!(trace[i].last_jump.parse::()); - let plain_ref = PlainRef { id, gen: 0 }; + let plain_ref = trace[i].last_ref.expect("No trace to update"); let stream = Stream::new((), new_data.as_bytes()); let _ = t!(file.cos_file.update(plain_ref, stream)); Ok(()) @@ -398,18 +469,19 @@ fn expand( return Ok(()); } let step = node.step()?; - let prim = retrieval::resolve_step(parent, &step)?; - if let Primitive::Reference(x_ref) = prim { - let jump = retrieval::resolve_xref(x_ref.id, file)?; - let mut jump_trace = parent_model.trace.clone(); - jump_trace.push(PathTrace::new(step.get_key(), x_ref.id.to_string())); - let mut to_expand = parent_model.get_child(step.get_key()).unwrap(); - to_expand.add_children(&jump, &jump_trace); - expand_children(node, file, &jump, &mut to_expand)?; - } else { - let mut to_expand = parent_model.get_child(step.get_key()).unwrap(); - to_expand.add_children(prim, &to_expand.trace.clone()); - expand_children(node, file, prim, &mut to_expand)?; + if let Ok(prim) = retrieval::resolve_step(parent, &step) { + if let Primitive::Reference(x_ref) = prim { + let jump = retrieval::resolve_xref(x_ref.id, file)?; + let mut jump_trace = parent_model.trace.clone(); + jump_trace.push(PathTrace::new(step.get_key(), Some(x_ref.clone()))); + let mut to_expand = parent_model.get_child(step.get_key()).unwrap(); + to_expand.add_children(&jump, &jump_trace); + expand_children(node, file, &jump, &mut to_expand)?; + } else { + let mut to_expand = parent_model.get_child(step.get_key()).unwrap(); + to_expand.add_children(prim, &to_expand.trace.clone()); + expand_children(node, file, prim, &mut to_expand)?; + } } Ok(()) } @@ -491,7 +563,7 @@ impl Step { fn append_path(key: String, path: &Vec) -> Vec { let mut new_path = path.clone(); - let last_jump = new_path.last().unwrap().last_jump.clone(); + let last_jump = new_path.last().unwrap().last_ref.clone(); new_path.push(PathTrace::new(key, last_jump)); new_path } @@ -611,6 +683,17 @@ impl PrimitiveModel { model } + fn from_primitive_until_refs(primitive: &Primitive, trace: PathTrace) -> PrimitiveModel { + let mut parent_model = PrimitiveModel::from_primitive( + trace.key.clone(), + primitive, + vec![trace.clone()], + ); + let mut model = parent_model; + model.add_children(primitive, &vec!(trace)); + model + } + fn add_children(&mut self, primitive: &Primitive, path: &Vec) { self.expanded = true; match primitive { @@ -837,10 +920,13 @@ pub fn run() { get_prim_tree_by_path, get_xref_table, get_contents, + update_contents, get_stream_data_as_string, + update_stream_data_as_string, get_stream_data_as_image, get_page_by_num, - update_stream_data_as_string + get_changes, + clear_changes ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/retrieval.rs b/src-tauri/src/retrieval.rs index d5fb24d..530036c 100644 --- a/src-tauri/src/retrieval.rs +++ b/src-tauri/src/retrieval.rs @@ -34,7 +34,7 @@ pub fn get_prim_by_steps_with_file( 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 last_jump: Option = trace.last_ref.clone(); let mut trace = vec![trace]; let mut current_prim = &parent; @@ -43,7 +43,7 @@ pub fn get_prim_by_steps_with_file( current_prim = resolve_step(¤t_prim, &step)?; if let Primitive::Reference(xref) = current_prim { - last_jump = xref.id.to_string(); + last_jump = Some(xref.clone()); parent = resolve_p_ref(xref.clone(), file)?; current_prim = &parent; } @@ -53,13 +53,18 @@ pub fn get_prim_by_steps_with_file( } 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()))) + match step { + Step::Page(page_num) => retrieve_page(page_num, file), + Step::Number(obj_num) => { + let parent = resolve_xref(obj_num, file)?; + Ok((parent, PathTrace::new(step.get_key(), Some(PlainRef {id: obj_num, gen: 0})))) + }, + Step::Trailer => { + let parent = retrieve_trailer(file); + Ok((parent, PathTrace::trailer())) + }, + _ => Err(String::from(format!("{:?} is not a valid path!", step))), + } } pub fn resolve_step<'a>(current_prim: &'a Primitive, step: &Step) -> Result<&'a Primitive, String> { @@ -125,7 +130,7 @@ pub fn retrieve_page(page_num: u32, file: &CosFile) -> Result<(Primitive, PathTr 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()), + PathTrace::new(format!("Page{}", page_num), Some(p_ref)), )) } diff --git a/src-tauri/src/tests.rs b/src-tauri/src/tests.rs index f07739e..948eb14 100644 --- a/src-tauri/src/tests.rs +++ b/src-tauri/src/tests.rs @@ -7,7 +7,8 @@ mod tests { use crate::{ get_prim_by_path_with_file, get_prim_model_by_path_with_file, get_prim_tree_by_path_with_file, get_stream_data_by_path_with_file, - get_xref_table_model_with_file, to_pdf_file, PathTrace, PrimitiveModel, TreeViewRequest, + get_xref_table_model_with_file, to_pdf_file, update_contents, update_contents_with_file, + ContentsModel, PathTrace, PrimitiveModel, TreeViewRequest, }; use image::codecs::jpeg::JpegEncoder; @@ -155,7 +156,7 @@ mod tests { ); let trail_model = PrimitiveModel::from_primitive_with_children( &trail, - vec![PathTrace::new("Trailer".to_string(), "Trailer".to_string())], + vec![PathTrace::new("Trailer".to_string(), None)], ); print_node(trail_model, 5); println!("{:?}", file.trailer.info_dict); @@ -264,7 +265,10 @@ mod tests { let mut rendered_pages: Vec = vec![]; timed!( for i in 0..1 { - let img = timed!(renderer.render(i), format!("render page {}", i)); + let img = timed!( + renderer.render(&file.get_page(i).unwrap()), + format!("render page {}", i) + ); rendered_pages.push(img.unwrap()) }, "rendering some pages" @@ -276,4 +280,21 @@ mod tests { "saving images" ); } + #[test] + fn test_update_contents() { + let mut file = timed!( + FileOptions::cached().open(PDF_SPEC_PATH).unwrap(), + "Loading file" + ); + let string = timed!(update_contents_with_file( + "Page1", + ContentsModel { + parts: vec![vec!["q".to_string(), "Q".to_string()]], + }, + &mut file, + ) + .unwrap(), "Updating contents"); + + println!("{}", string); + } } diff --git a/src/components/App.svelte b/src/components/App.svelte index c185d5e..df48bb1 100644 --- a/src/components/App.svelte +++ b/src/components/App.svelte @@ -97,7 +97,6 @@ closeTab={closeFile} openTab={upload} selectTab={(state) => (fState = state)} - reload={() => fState?.reload()} >
diff --git a/src/components/ChangesModal.svelte b/src/components/ChangesModal.svelte new file mode 100644 index 0000000..7715015 --- /dev/null +++ b/src/components/ChangesModal.svelte @@ -0,0 +1,49 @@ + + +
+
+ Changes: +
+ +
+
+ {#each changes as change} +
+

{change.key + ":"}

+   +

{change.ptype}

 |  +

{change.sub_type}

 |  +

{change.value}

+
+
+ {/each} +
+ + + diff --git a/src/components/ContentsView.svelte b/src/components/ContentsView.svelte deleted file mode 100644 index 7d8fdfd..0000000 --- a/src/components/ContentsView.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/components/FileView.svelte b/src/components/FileView.svelte index df07cd9..cb628e0 100644 --- a/src/components/FileView.svelte +++ b/src/components/FileView.svelte @@ -8,6 +8,7 @@ import type {PathSelectedEvent} from "../events/PathSelectedEvent"; import {onMount} from "svelte"; import NotificationModal from "./NotificationModal.svelte"; + import ChangesModal from "./ChangesModal.svelte"; let { fState, @@ -18,20 +19,26 @@ } = $props(); let width: number = $state(0); - let modalWidth = $derived(fState.xRefShowing || fState.notificationsShowing ? 281 : 0); + let modalWidth = $derived(fState.xRefShowing || fState.notificationsShowing || fState.changesShowing ? 281 : 0); let splitPanesWidth: number = $derived(width - modalWidth); function handleKeydown(event: KeyboardEvent) { // Check for "Alt + Left Arrow" if (event.altKey && event.key === "ArrowLeft") { - fState.popPath(); + fState.back(); + } + if (event.altKey && event.key === "ArrowRight") { + fState.forward(); } } function handleMouseButton(event: MouseEvent) { // Check for mouse back button (button 4) if (event.button === 3) { - fState.popPath(); + fState.back(); + } + if (event.button === 4) { + fState.forward(); } } @@ -86,6 +93,11 @@
{/if} + {#if fState.changesShowing} +
+ +
+ {/if} diff --git a/src/components/TreeViewEntry.svelte b/src/components/TreeViewEntry.svelte index ab4a244..45ee26e 100644 --- a/src/components/TreeViewEntry.svelte +++ b/src/components/TreeViewEntry.svelte @@ -15,50 +15,50 @@ return key; } - {#if entry.depth == 0} -
- {/if} - + +