Added monaco, some formatting
@ -14,15 +14,18 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@geoffcox/svelte-splitter": "^1.0.1",
|
||||
"@monaco-editor/loader": "^1.4.0",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"flowbite-svelte": "^0.47.4",
|
||||
"flowbite-svelte-icons": "^2.0.2",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"paths": "^0.1.1",
|
||||
"svelte-split-pane": "^0.1.2",
|
||||
"svelte-splitpanes": "^8.0.9"
|
||||
"svelte-splitpanes": "^8.0.9",
|
||||
"vite-plugin-monaco-editor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
@ -38,6 +41,6 @@
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.3"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ops::{Deref, Range, RangeFrom};
|
||||
/// Lexing an input file, in the sense of breaking it up into substrings based on delimiters and
|
||||
/// whitespace.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::ops::{Range, Deref, RangeFrom};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::primitive::Name;
|
||||
|
||||
mod str;
|
||||
pub use self::str::{StringLexer, HexStringLexer};
|
||||
|
||||
pub use self::str::{HexStringLexer, StringLexer};
|
||||
|
||||
/// `Lexer` has functionality to jump around and traverse the PDF lexemes of a string in any direction.
|
||||
#[derive(Copy, Clone)]
|
||||
@ -24,18 +22,18 @@ pub struct Lexer<'a> {
|
||||
// find the position where condition(data[pos-1]) == false and condition(data[pos]) == true
|
||||
#[inline]
|
||||
fn boundary_rev(data: &[u8], pos: usize, condition: impl Fn(u8) -> bool) -> usize {
|
||||
match data[.. pos].iter().rposition(|&b| !condition(b)) {
|
||||
match data[..pos].iter().rposition(|&b| !condition(b)) {
|
||||
Some(start) => start + 1,
|
||||
None => 0
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// find the position where condition(data[pos-1]) == true and condition(data[pos]) == false
|
||||
#[inline]
|
||||
fn boundary(data: &[u8], pos: usize, condition: impl Fn(u8) -> bool) -> usize {
|
||||
match data[pos ..].iter().position(|&b| !condition(b)) {
|
||||
match data[pos..].iter().position(|&b| !condition(b)) {
|
||||
Some(start) => pos + start,
|
||||
None => data.len()
|
||||
None => data.len(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,14 +50,14 @@ impl<'a> Lexer<'a> {
|
||||
Lexer {
|
||||
pos: 0,
|
||||
buf,
|
||||
file_offset: 0
|
||||
file_offset: 0,
|
||||
}
|
||||
}
|
||||
pub fn with_offset(buf: &'a [u8], file_offset: usize) -> Lexer<'a> {
|
||||
Lexer {
|
||||
pos: 0,
|
||||
buf,
|
||||
file_offset
|
||||
file_offset,
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,10 +72,10 @@ impl<'a> Lexer<'a> {
|
||||
/// consume the whitespace sequence following the stream start
|
||||
pub fn next_stream(&mut self) -> Result<()> {
|
||||
let pos = self.skip_whitespace(self.pos)?;
|
||||
if !self.buf[pos ..].starts_with(b"stream") {
|
||||
if !self.buf[pos..].starts_with(b"stream") {
|
||||
// bail!("next token isn't 'stream'");
|
||||
}
|
||||
|
||||
|
||||
let &b0 = self.buf.get(pos + 6).ok_or(PdfError::EOF)?;
|
||||
if b0 == b'\n' {
|
||||
self.pos = pos + 7;
|
||||
@ -96,13 +94,13 @@ impl<'a> Lexer<'a> {
|
||||
/// Gives previous lexeme. Lexer moves to the first byte of this lexeme. (needs to be tested)
|
||||
pub fn back(&mut self) -> Result<Substr<'a>> {
|
||||
//println!("back: {:?}", String::from_utf8_lossy(&self.buf[self.pos.saturating_sub(20) .. self.pos]));
|
||||
|
||||
|
||||
// first reverse until we find non-whitespace
|
||||
let end_pos = boundary_rev(self.buf, self.pos, is_whitespace);
|
||||
let start_pos = boundary_rev(self.buf, end_pos, not(is_whitespace));
|
||||
self.pos = start_pos;
|
||||
|
||||
Ok(self.new_substr(start_pos .. end_pos))
|
||||
|
||||
Ok(self.new_substr(start_pos..end_pos))
|
||||
}
|
||||
|
||||
/// Look at the next lexeme. Will return empty substr if the next character is EOF.
|
||||
@ -112,7 +110,6 @@ impl<'a> Lexer<'a> {
|
||||
Err(PdfError::EOF) => Ok(self.new_substr(self.pos..self.pos)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Returns `Ok` if the next lexeme matches `expected` - else `Err`.
|
||||
@ -124,7 +121,7 @@ impl<'a> Lexer<'a> {
|
||||
Err(PdfError::UnexpectedLexeme {
|
||||
pos: self.pos,
|
||||
lexeme: word.to_string(),
|
||||
expected
|
||||
expected,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -153,13 +150,13 @@ impl<'a> Lexer<'a> {
|
||||
while self.buf.get(pos) == Some(&b'%') {
|
||||
pos += 1;
|
||||
if let Some(off) = self.buf[pos..].iter().position(|&b| b == b'\n') {
|
||||
pos += off+1;
|
||||
pos += off + 1;
|
||||
}
|
||||
|
||||
|
||||
// Move away from eventual whitespace
|
||||
pos = self.skip_whitespace(pos)?;
|
||||
}
|
||||
|
||||
|
||||
let start_pos = pos;
|
||||
|
||||
// If first character is delimiter, this lexeme only contains that character.
|
||||
@ -177,7 +174,7 @@ impl<'a> Lexer<'a> {
|
||||
return Ok((self.new_substr(start_pos..pos), pos));
|
||||
}
|
||||
|
||||
if let Some(slice) = self.buf.get(pos..=pos+1) {
|
||||
if let Some(slice) = self.buf.get(pos..=pos + 1) {
|
||||
if slice == b"<<" || slice == b">>" {
|
||||
pos = self.advance_pos(pos)?;
|
||||
}
|
||||
@ -213,7 +210,9 @@ impl<'a> Lexer<'a> {
|
||||
|
||||
#[inline]
|
||||
pub fn next_as<T>(&mut self) -> Result<T>
|
||||
where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.next().and_then(|word| word.to::<T>())
|
||||
}
|
||||
@ -265,16 +264,14 @@ impl<'a> Lexer<'a> {
|
||||
|
||||
/// Moves pos to start of next line. Returns the skipped-over substring.
|
||||
#[allow(dead_code)]
|
||||
pub fn seek_newline(&mut self) -> Substr{
|
||||
pub fn seek_newline(&mut self) -> Substr {
|
||||
let start = self.pos;
|
||||
while self.buf[self.pos] != b'\n'
|
||||
&& self.incr_pos() { }
|
||||
while self.buf[self.pos] != b'\n' && self.incr_pos() {}
|
||||
self.incr_pos();
|
||||
|
||||
self.new_substr(start..self.pos)
|
||||
}
|
||||
|
||||
|
||||
// TODO: seek_substr and seek_substr_back should use next() or back()?
|
||||
/// Moves pos to after the found `substr`. Returns Substr with traversed text if `substr` is found.
|
||||
#[allow(dead_code)]
|
||||
@ -285,7 +282,7 @@ impl<'a> Lexer<'a> {
|
||||
let mut matched = 0;
|
||||
loop {
|
||||
if self.pos >= self.buf.len() {
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
if self.buf[self.pos] == substr[matched] {
|
||||
matched += 1;
|
||||
@ -306,12 +303,17 @@ impl<'a> Lexer<'a> {
|
||||
/// Substr if found.
|
||||
pub fn seek_substr_back(&mut self, substr: &[u8]) -> Result<Substr<'a>> {
|
||||
let end = self.pos;
|
||||
match self.buf[.. end].windows(substr.len()).rposition(|w| w == substr) {
|
||||
match self.buf[..end]
|
||||
.windows(substr.len())
|
||||
.rposition(|w| w == substr)
|
||||
{
|
||||
Some(start) => {
|
||||
self.pos = start + substr.len();
|
||||
Ok(self.new_substr(self.pos .. end))
|
||||
Ok(self.new_substr(self.pos..end))
|
||||
}
|
||||
None => Err(PdfError::NotFound {word: String::from_utf8_lossy(substr).into() })
|
||||
None => Err(PdfError::NotFound {
|
||||
word: String::from_utf8_lossy(substr).into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +340,9 @@ impl<'a> Lexer<'a> {
|
||||
|
||||
/// for debugging
|
||||
pub fn ctx(&self) -> Cow<str> {
|
||||
String::from_utf8_lossy(&self.buf[self.pos.saturating_sub(40)..self.buf.len().min(self.pos+40)])
|
||||
String::from_utf8_lossy(
|
||||
&self.buf[self.pos.saturating_sub(40)..self.buf.len().min(self.pos + 40)],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -352,18 +356,21 @@ impl<'a> Lexer<'a> {
|
||||
}
|
||||
#[inline]
|
||||
fn is_whitespace(&self, pos: usize) -> bool {
|
||||
self.buf.get(pos).map(|&b| is_whitespace(b)).unwrap_or(false)
|
||||
self.buf
|
||||
.get(pos)
|
||||
.map(|&b| is_whitespace(b))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_delimiter(&self, pos: usize) -> bool {
|
||||
self.buf.get(pos).map(|b| b"()<>[]{}/%".contains(b)).unwrap_or(false)
|
||||
self.buf
|
||||
.get(pos)
|
||||
.map(|b| b"()<>[]{}/%".contains(b))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// A slice from some original string - a lexeme.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Substr<'a> {
|
||||
@ -372,7 +379,10 @@ pub struct Substr<'a> {
|
||||
}
|
||||
impl<'a> Substr<'a> {
|
||||
pub fn new<T: AsRef<[u8]> + ?Sized>(data: &'a T, file_offset: usize) -> Self {
|
||||
Substr { slice: data.as_ref(), file_offset }
|
||||
Substr {
|
||||
slice: data.as_ref(),
|
||||
file_offset,
|
||||
}
|
||||
}
|
||||
// to: &S -> U. Possibly expensive conversion.
|
||||
// as: &S -> &U. Cheap borrow conversion
|
||||
@ -389,9 +399,13 @@ impl<'a> Substr<'a> {
|
||||
self.slice.to_vec()
|
||||
}
|
||||
pub fn to<T>(&self) -> Result<T>
|
||||
where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
std::str::from_utf8(self.slice)?.parse::<T>().map_err(|e| PdfError::Parse { source: e.into() })
|
||||
std::str::from_utf8(self.slice)?
|
||||
.parse::<T>()
|
||||
.map_err(|e| PdfError::Parse { source: e.into() })
|
||||
}
|
||||
pub fn is_integer(&self) -> bool {
|
||||
if self.slice.len() == 0 {
|
||||
@ -424,7 +438,7 @@ impl<'a> Substr<'a> {
|
||||
if !is_int(&slice[..i]) {
|
||||
return None;
|
||||
}
|
||||
slice = &slice[i+1..];
|
||||
slice = &slice[i + 1..];
|
||||
}
|
||||
if let Some(len) = slice.iter().position(|&b| !b.is_ascii_digit()) {
|
||||
if len == 0 {
|
||||
@ -433,7 +447,7 @@ impl<'a> Substr<'a> {
|
||||
let end = self.slice.len() - slice.len() + len;
|
||||
Some(Substr {
|
||||
file_offset: self.file_offset,
|
||||
slice: &self.slice[..end]
|
||||
slice: &self.slice[..end],
|
||||
})
|
||||
} else {
|
||||
Some(*self)
|
||||
@ -459,7 +473,7 @@ impl<'a> Substr<'a> {
|
||||
}
|
||||
|
||||
pub fn file_range(&self) -> Range<usize> {
|
||||
self.file_offset .. self.file_offset + self.slice.len()
|
||||
self.file_offset..self.file_offset + self.slice.len()
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,6 +505,7 @@ mod tests {
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::u32::MAX;
|
||||
|
||||
#[test]
|
||||
fn test_boundary_rev() {
|
||||
@ -522,14 +537,19 @@ mod tests {
|
||||
fn test_lexed() {
|
||||
let file_data = fs::read("/home/kschuettler/Dokumente/TestFiles/18 - EVIDIS - Corrosao Irritacao ocular aguda.pdf").expect("File not found!");
|
||||
println!("{}", file_data.len());
|
||||
let mut lexer = Lexer::new(&*file_data);
|
||||
let mut lexer = Lexer::new(&file_data[0..]);
|
||||
let file = File::create("/tmp/pdf.txt").unwrap();
|
||||
|
||||
let mut writer = BufWriter::new(file);
|
||||
let mut depth = false;
|
||||
let mut stream = false;
|
||||
let mut dict = 0;
|
||||
let mut dict = 0u32;
|
||||
let lex_count = MAX;
|
||||
let mut lex_count_left = lex_count;
|
||||
while let Ok(s) = lexer.next() {
|
||||
if lex_count_left == 0 {
|
||||
break;
|
||||
}
|
||||
if stream && s.to_string().as_str() == "endstream" {
|
||||
stream = false;
|
||||
writer
|
||||
@ -539,6 +559,7 @@ mod tests {
|
||||
} else if stream {
|
||||
continue;
|
||||
}
|
||||
lex_count_left -= 1;
|
||||
|
||||
match s.to_string().as_str() {
|
||||
"obj" => depth = true,
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
@ -2767,7 +2767,9 @@ dependencies = [
|
||||
name = "pdf-forge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"pdf",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
||||
@ -26,4 +26,6 @@ pdf = { path = "../src-pdfrs/pdf", features = ["cache"] }
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
uuid = { version = "1.12.0", features = ["v4"] }
|
||||
regex = "1.10.3"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
|
||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 974 B |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@ -1,15 +1,18 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
extern crate pdf;
|
||||
|
||||
use crate::pdf::object::Resolve;
|
||||
|
||||
use pdf::content::Op;
|
||||
use lazy_static::lazy_static;
|
||||
use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache};
|
||||
use pdf::object::{InfoDict, Object, ObjectWrite, PlainRef};
|
||||
use pdf::object::{Object, ObjectWrite, PlainRef, Stream};
|
||||
use pdf::primitive::Primitive;
|
||||
use pdf::xref::XRef;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt::format;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
@ -48,7 +51,6 @@ pub struct PdfFile {
|
||||
pub pages: Vec<PageModel>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct PrimitiveModel {
|
||||
pub key: String,
|
||||
@ -56,25 +58,25 @@ pub struct PrimitiveModel {
|
||||
pub sub_type: String,
|
||||
pub value: String,
|
||||
pub children: Vec<PrimitiveModel>,
|
||||
pub detail_path: Vec<DetailPathStep>,
|
||||
pub trace: Vec<PathTrace>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct DetailPathStep {
|
||||
pub struct PathTrace {
|
||||
pub key: String,
|
||||
pub last_jump: String,
|
||||
}
|
||||
impl DetailPathStep {
|
||||
fn new(key: String, last_jump: String) -> DetailPathStep {
|
||||
DetailPathStep { key, last_jump }
|
||||
impl PathTrace {
|
||||
fn new(key: String, last_jump: String) -> PathTrace {
|
||||
PathTrace { key, last_jump }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct PageModel {
|
||||
key: String,
|
||||
id: u64,
|
||||
obj_num: u64,
|
||||
page_num: u64,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct TreeViewNode {
|
||||
@ -83,14 +85,14 @@ pub struct TreeViewNode {
|
||||
}
|
||||
|
||||
impl TreeViewNode {
|
||||
fn step(&self) -> Step {
|
||||
fn step(&self) -> Result<Step, String> {
|
||||
Step::parse_step(&self.key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ContentsModel {
|
||||
parts: Vec<Vec<String>>
|
||||
parts: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -141,22 +143,30 @@ fn upload(path: &str, session: State<Mutex<Session>>) -> Result<String, String>
|
||||
}
|
||||
|
||||
fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
|
||||
|
||||
fn parse_title_from_path(path: &str) -> Option<String> {
|
||||
Path::new(path).file_name()
|
||||
Path::new(path)
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str().map(|s| s.to_string()))
|
||||
}
|
||||
|
||||
let file_name = if let Some(ref info) = file.trailer.info_dict {
|
||||
info.title.as_ref().map(|p| p.to_string_lossy())
|
||||
.unwrap_or( parse_title_from_path(path)
|
||||
.unwrap_or_else(|| "Not found".to_string()))
|
||||
info.title
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.unwrap_or(parse_title_from_path(path).unwrap_or_else(|| "Not found".to_string()))
|
||||
} else {
|
||||
"Not found".to_string()
|
||||
};
|
||||
|
||||
|
||||
let pages = file.pages().enumerate().map(|(i, page_ref)| PageModel { key: format!("Page {}", i + 1), id: page_ref.unwrap().get_ref().get_inner().id }).collect();
|
||||
let pages = file
|
||||
.pages()
|
||||
.enumerate()
|
||||
.map(|(i, page_ref)| PageModel {
|
||||
key: format!("Page {}", i + 1),
|
||||
obj_num: page_ref.unwrap().get_ref().get_inner().id,
|
||||
page_num: (i + 1) as u64,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let pdf_file = PdfFile {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
@ -170,13 +180,17 @@ fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_contents(id: &str, path: &str, session: State<Mutex<Session>>) -> Result<ContentsModel, String> {
|
||||
fn get_contents(
|
||||
id: &str,
|
||||
path: &str,
|
||||
session: State<Mutex<Session>>,
|
||||
) -> Result<ContentsModel, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(path, &session_guard)?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
|
||||
let (_, page_prim, _) = get_prim_by_path_with_file(id, &file.cos_file)?;
|
||||
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));
|
||||
@ -187,12 +201,40 @@ fn get_contents(id: &str, path: &str, session: State<Mutex<Session>>) -> Result<
|
||||
let ops = t!(pdf::content::parse_ops(&data, &resolver));
|
||||
let part = t!(pdf::content::display_ops(&ops));
|
||||
parts.push(part);
|
||||
};
|
||||
return Ok(ContentsModel {parts});
|
||||
}
|
||||
return Ok(ContentsModel { parts });
|
||||
}
|
||||
Err(String::from("Error occurred"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_stream_data(id: &str, path: &str, session: State<Mutex<Session>>) -> Result<String, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
get_stream_data_by_path_with_file(path, &file.cos_file)
|
||||
}
|
||||
|
||||
fn get_stream_data_by_path_with_file(path: &str, file: &CosFile) -> Result<String, 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));
|
||||
};
|
||||
let resolver = file.resolver();
|
||||
let data = t!(t!(Stream::<Primitive>::from_stream(stream, &resolver)).data(&resolver));
|
||||
Ok(String::from_utf8_lossy(&data).into_owned())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_prim_by_path(
|
||||
id: &str,
|
||||
@ -206,44 +248,60 @@ fn get_prim_by_path(
|
||||
|
||||
get_prim_model_by_path_with_file(path, &file.cos_file)
|
||||
}
|
||||
|
||||
fn get_prim_model_by_path_with_file(path: &str, file: &CosFile) -> Result<PrimitiveModel, String> {
|
||||
let (key, prim, detail_path) = get_prim_by_path_with_file(path, file)?;
|
||||
let (step, prim, detail_path) = get_prim_by_path_with_file(path, file)?;
|
||||
|
||||
Ok(PrimitiveModel::from_primitive_with_children(
|
||||
key,
|
||||
step.get_key(),
|
||||
&prim,
|
||||
detail_path
|
||||
detail_path,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_prim_by_path_with_file(path: &str, file: &CosFile) -> Result<(String, Primitive, Vec<DetailPathStep>), String> {
|
||||
let mut steps = Step::parse(path);
|
||||
if steps.len() == 0 {
|
||||
return Err(String::from(format!("{} is not a valid path!", path)));
|
||||
}
|
||||
let mut step = steps.pop_front().unwrap();
|
||||
let mut parent = match step {
|
||||
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
|
||||
Step::Trailer => retrieve_trailer(file),
|
||||
_ => return Err(String::from(format!("{} is not a valid path!", path))),
|
||||
};
|
||||
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)
|
||||
}
|
||||
|
||||
let mut detail_path = vec![DetailPathStep::new(step.get_key(), step.get_key())];
|
||||
let mut last_jump = step.get_key();
|
||||
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() {
|
||||
step = steps.pop_front().unwrap();
|
||||
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_xref(xref.id, file)?;
|
||||
parent = resolve_pref(xref.clone(), file)?;
|
||||
current_prim = &parent;
|
||||
}
|
||||
detail_path.push(DetailPathStep::new(step.get_key(), last_jump.clone()));
|
||||
trace.push(PathTrace::new(step.get_key(), last_jump.clone()));
|
||||
}
|
||||
Ok((step.get_key(), current_prim.clone(), detail_path))
|
||||
Ok((step, current_prim.clone(), trace))
|
||||
}
|
||||
|
||||
fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
|
||||
let parent = match step {
|
||||
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
|
||||
Step::Trailer => retrieve_trailer(file),
|
||||
Step::Page(page_num) => retrieve_page(page_num, file)?,
|
||||
_ => return Err(String::from(format!("{:?} is not a valid path!", step))),
|
||||
};
|
||||
Ok((parent, PathTrace::new(step.get_key(), step.get_key())))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -264,15 +322,13 @@ fn get_prim_tree_by_path_with_file(
|
||||
node: TreeViewNode,
|
||||
file: &CosFile,
|
||||
) -> Result<PrimitiveModel, String> {
|
||||
let step = node.step();
|
||||
let parent = match step {
|
||||
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
|
||||
Step::Trailer => retrieve_trailer(file),
|
||||
_ => return Err(String::from(format!("{:?} is not a valid path!", node))),
|
||||
};
|
||||
let path = vec![DetailPathStep::new(step.get_key(), step.get_key())];
|
||||
let step = node.step()?;
|
||||
let (mut parent, trace) = resolve_parent(step.clone(), file)?;
|
||||
|
||||
let mut parent_model = PrimitiveModel::from_primitive_with_children(step.get_key(), &parent, path);
|
||||
let path = vec![trace];
|
||||
|
||||
let mut parent_model =
|
||||
PrimitiveModel::from_primitive_with_children(step.get_key(), &parent, path);
|
||||
for child in node.children.iter() {
|
||||
expand(child, &mut parent_model, &parent, file)?;
|
||||
}
|
||||
@ -286,17 +342,20 @@ fn expand(
|
||||
parent: &Primitive,
|
||||
file: &CosFile,
|
||||
) -> Result<(), String> {
|
||||
let step = node.step();
|
||||
let step = node.step()?;
|
||||
let prim = resolve_step(parent, &step)?;
|
||||
if let Primitive::Reference(x_ref) = prim {
|
||||
let jump = resolve_xref(x_ref.id, file)?;
|
||||
// parent_model.ptype = format!("{}-Reference", jump.get_debug_name());
|
||||
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
||||
to_expand.add_children(&jump, append_path_with_jump(step.get_key(), x_ref.id.to_string(), &to_expand.detail_path));
|
||||
to_expand.add_children(
|
||||
&jump,
|
||||
append_path_with_jump(step.get_key(), x_ref.id.to_string(), &to_expand.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, append_path(step.get_key(), &to_expand.detail_path));
|
||||
to_expand.add_children(prim, append_path(step.get_key(), &to_expand.trace));
|
||||
expand_children(node, file, prim, &mut to_expand)?;
|
||||
}
|
||||
Ok(())
|
||||
@ -371,53 +430,67 @@ fn retrieve_trailer(file: &CosFile) -> Primitive {
|
||||
file.trailer.to_primitive(&mut updater).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
fn retrieve_page(page_num: u32, file: &CosFile) -> Result<Primitive, String> {
|
||||
let page_rc = t!(file.get_page(page_num - 1));
|
||||
resolve_pref(page_rc.get_ref().get_inner(), file)
|
||||
}
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Step {
|
||||
String(String),
|
||||
Page(u32),
|
||||
Number(u64),
|
||||
Trailer,
|
||||
Data,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
fn parse_step(path: &str) -> Step {
|
||||
match &path.parse::<u64>().ok() {
|
||||
fn parse_step(path: &str) -> Result<Step, String> {
|
||||
lazy_static! {
|
||||
static ref PAGE_RE: Regex = Regex::new(r"^Page(\d+)$").unwrap();
|
||||
}
|
||||
if path.len() == 0 {
|
||||
return Err(String::from("Path is empty"));
|
||||
}
|
||||
|
||||
Ok(match &path.parse::<u64>().ok() {
|
||||
Some(i) => Step::Number(*i),
|
||||
None => match &path[..] {
|
||||
"Data" => Step::Data,
|
||||
"Trailer" => Step::Trailer,
|
||||
"/" => Step::Trailer,
|
||||
_ => Step::String(path.to_string().clone()),
|
||||
_ => {
|
||||
if let Some(caps) = PAGE_RE.captures(path) {
|
||||
Step::Page(
|
||||
caps[1]
|
||||
.parse::<u32>()
|
||||
.map_err(|_| format!("Invalid page number in {}", path))?,
|
||||
)
|
||||
} else {
|
||||
Step::String(path.to_string())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse(path: &str) -> VecDeque<Step> {
|
||||
let mut steps = VecDeque::new();
|
||||
|
||||
if path.starts_with("/") {
|
||||
steps.push_back(Step::Trailer);
|
||||
}
|
||||
let split_path = path.split("/").collect::<VecDeque<&str>>();
|
||||
for path_component in split_path {
|
||||
if path_component.len() == 0 {
|
||||
continue;
|
||||
}
|
||||
let step = match &path_component.parse::<u64>().ok() {
|
||||
Some(i) => Step::Number(*i),
|
||||
None => match path_component {
|
||||
"Data" => Step::Data,
|
||||
_ => Step::String(path_component.to_string().clone()),
|
||||
},
|
||||
};
|
||||
steps.push_back(step);
|
||||
}
|
||||
steps
|
||||
split_path
|
||||
.iter()
|
||||
.filter_map(|s| Step::parse_step(s).ok())
|
||||
.collect::<VecDeque<Step>>()
|
||||
}
|
||||
|
||||
fn get_key(&self) -> String {
|
||||
match self {
|
||||
Step::String(s) => s.clone(),
|
||||
Step::Number(i) => i.to_string(),
|
||||
Step::Trailer => "/".to_string(),
|
||||
Step::Trailer => "Trailer".to_string(),
|
||||
Step::Page(n) => format!("Page{}", n),
|
||||
Step::Data => "Data".into(),
|
||||
}
|
||||
}
|
||||
@ -425,6 +498,10 @@ impl Step {
|
||||
|
||||
fn resolve_xref(id: u64, file: &CosFile) -> Result<Primitive, String> {
|
||||
let plain_ref = PlainRef { id, gen: 0 };
|
||||
resolve_pref(plain_ref, file)
|
||||
}
|
||||
|
||||
fn resolve_pref(plain_ref: PlainRef, file: &CosFile) -> Result<Primitive, String> {
|
||||
file.resolver()
|
||||
.resolve(plain_ref)
|
||||
.map_err(|e| e.to_string())
|
||||
@ -440,31 +517,30 @@ fn get_file_from_state<'a>(
|
||||
.ok_or_else(|| format!("File with id {} does not exist!", id))
|
||||
}
|
||||
|
||||
fn append_path_with_jump(key: String, last_jump: String, path: &Vec<DetailPathStep>) -> Vec<DetailPathStep> {
|
||||
fn append_path_with_jump(key: String, last_jump: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(DetailPathStep::new(key, last_jump));
|
||||
new_path.push(PathTrace::new(key, last_jump));
|
||||
new_path
|
||||
}
|
||||
|
||||
fn append_path(key: String, path: &Vec<DetailPathStep>) -> Vec<DetailPathStep> {
|
||||
fn append_path(key: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
|
||||
let mut new_path = path.clone();
|
||||
let last_jump = new_path.last().unwrap().last_jump.clone();
|
||||
new_path.push(DetailPathStep::new(key, last_jump));
|
||||
new_path.push(PathTrace::new(key, last_jump));
|
||||
new_path
|
||||
}
|
||||
|
||||
|
||||
impl PrimitiveModel {
|
||||
fn from_primitive(key: String, primitive: &Primitive, path: Vec<DetailPathStep>) -> PrimitiveModel {
|
||||
fn from_primitive(key: String, primitive: &Primitive, path: Vec<PathTrace>) -> PrimitiveModel {
|
||||
let value: String = match primitive {
|
||||
Primitive::Null => "Null".to_string(),
|
||||
Primitive::Integer(i) => i.to_string(),
|
||||
Primitive::Number(f) => f.to_string(),
|
||||
Primitive::Boolean(b) => b.to_string(),
|
||||
Primitive::String(s) => s.to_string().unwrap_or(String::new()),
|
||||
Primitive::String(s) => s.to_string_lossy(),
|
||||
Primitive::Stream(_) => "-".to_string(),
|
||||
Primitive::Dictionary(_) => "-".to_string(),
|
||||
Primitive::Array(arr) =>PrimitiveModel::format_arr_content(arr),
|
||||
Primitive::Array(arr) => PrimitiveModel::format_arr_content(arr),
|
||||
Primitive::Reference(pref) => {
|
||||
format!("Obj Number: {} Gen Number: {}", pref.id, pref.gen)
|
||||
}
|
||||
@ -476,10 +552,10 @@ impl PrimitiveModel {
|
||||
.get("Type")
|
||||
.and_then(|value| match value {
|
||||
Primitive::Name(name) => Some(name.clone().as_str().to_string()),
|
||||
_ => None
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(String::from("-")),
|
||||
_ => String::from("-")
|
||||
_ => String::from("-"),
|
||||
};
|
||||
PrimitiveModel {
|
||||
key: key,
|
||||
@ -487,7 +563,7 @@ impl PrimitiveModel {
|
||||
sub_type: sub_type,
|
||||
value: value,
|
||||
children: Vec::new(),
|
||||
detail_path: path,
|
||||
trace: path,
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,16 +595,24 @@ impl PrimitiveModel {
|
||||
result
|
||||
}
|
||||
|
||||
fn from_primitive_with_children(key: String, primitive: &Primitive, path: Vec<DetailPathStep>) -> PrimitiveModel {
|
||||
fn from_primitive_with_children(
|
||||
key: String,
|
||||
primitive: &Primitive,
|
||||
path: Vec<PathTrace>,
|
||||
) -> PrimitiveModel {
|
||||
let mut model = PrimitiveModel::from_primitive(key, primitive, path.clone());
|
||||
model.add_children(primitive, path);
|
||||
model
|
||||
}
|
||||
|
||||
fn add_children(&mut self, primitive: &Primitive, path: Vec<DetailPathStep>) {
|
||||
fn add_children(&mut self, primitive: &Primitive, path: Vec<PathTrace>) {
|
||||
match primitive {
|
||||
Primitive::Dictionary(dict) => dict.iter().for_each(|(name, value)| {
|
||||
self.add_child(name.clone().as_str().to_string(), value, append_path(name.clone().as_str().to_string(), &path));
|
||||
self.add_child(
|
||||
name.clone().as_str().to_string(),
|
||||
value,
|
||||
append_path(name.clone().as_str().to_string(), &path),
|
||||
);
|
||||
}),
|
||||
Primitive::Array(arr) => arr.iter().enumerate().for_each(|(i, obj)| {
|
||||
self.add_child(i.to_string(), obj, append_path(i.to_string(), &path));
|
||||
@ -540,17 +624,26 @@ impl PrimitiveModel {
|
||||
sub_type: "-".to_string(),
|
||||
value: "".to_string(),
|
||||
children: vec![],
|
||||
detail_path: append_path("Data".to_string(), &path),
|
||||
trace: append_path("Data".to_string(), &path),
|
||||
});
|
||||
stream.info.iter().for_each(|(name, value)| {
|
||||
self.add_child(name.clone().as_str().to_string(), value, append_path(name.clone().as_str().to_string(), &path));
|
||||
self.add_child(
|
||||
name.clone().as_str().to_string(),
|
||||
value,
|
||||
append_path(name.clone().as_str().to_string(), &path),
|
||||
);
|
||||
})
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
fn add_child(&mut self, key: String, child: &Primitive, path: Vec<DetailPathStep>) -> &PrimitiveModel {
|
||||
fn add_child(
|
||||
&mut self,
|
||||
key: String,
|
||||
child: &Primitive,
|
||||
path: Vec<PathTrace>,
|
||||
) -> &PrimitiveModel {
|
||||
let child_model = Self::from_primitive(key, child, path);
|
||||
self.children.push(child_model);
|
||||
&self.children[self.children.len() - 1]
|
||||
@ -561,7 +654,7 @@ impl PrimitiveModel {
|
||||
}
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_xref_table(id: &str, session: State<Mutex<Session>>) -> Result<XRefTableModel, String> {
|
||||
fn get_xref_table(id: &str, session: State<Mutex<Session>>) -> Result<XRefTableModel, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
@ -676,9 +769,9 @@ pub fn run() {
|
||||
get_prim_by_path,
|
||||
get_prim_tree_by_path,
|
||||
get_xref_table,
|
||||
get_contents
|
||||
get_contents,
|
||||
get_stream_data
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@ extern crate pdf;
|
||||
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_xref_table_model_with_file, to_pdf_file, DetailPathStep, PrimitiveModel, TreeViewNode
|
||||
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, TreeViewNode,
|
||||
};
|
||||
|
||||
use pdf::content::{display_ops, serialize_ops, Op};
|
||||
@ -101,7 +103,7 @@ mod tests {
|
||||
|
||||
fn print_node(node: PrimitiveModel, depth: usize) {
|
||||
let spaces = " ".repeat(depth);
|
||||
println!("{:?}", node.detail_path);
|
||||
println!("{:?}", node.trace);
|
||||
println!("{}{} | {} | {}", spaces, node.key, node.ptype, node.value);
|
||||
for child in node.children {
|
||||
print_node(child, depth + 1);
|
||||
@ -113,14 +115,17 @@ mod tests {
|
||||
FileOptions::cached().open(FILE_PATH).unwrap(),
|
||||
"Loading file"
|
||||
);
|
||||
let mut file2 = timed!(
|
||||
FileOptions::uncached().storage(),
|
||||
"Loading storage"
|
||||
let mut file2 = timed!(FileOptions::uncached().storage(), "Loading storage");
|
||||
|
||||
let trail = timed!(
|
||||
file.trailer.to_primitive(&mut file2).unwrap(),
|
||||
"writing trailer"
|
||||
);
|
||||
let trail_model = PrimitiveModel::from_primitive_with_children(
|
||||
"Trailer".to_string(),
|
||||
&trail,
|
||||
vec![PathTrace::new("/".to_string(), "/".to_string())],
|
||||
);
|
||||
|
||||
|
||||
let trail = timed!(file.trailer.to_primitive(&mut file2).unwrap(), "writing trailer");
|
||||
let trail_model = PrimitiveModel::from_primitive_with_children("Trailer".to_string(), &trail, vec![DetailPathStep::new("/".to_string(), "/".to_string())]);
|
||||
print_node(trail_model, 5);
|
||||
println!("{:?}", file.trailer.info_dict);
|
||||
}
|
||||
@ -139,7 +144,6 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn test_read_contents() {
|
||||
|
||||
let file = timed!(
|
||||
FileOptions::cached().open(FILE_PATH).unwrap(),
|
||||
"Loading file"
|
||||
@ -148,7 +152,10 @@ mod tests {
|
||||
let (_, page2_prim, _) = get_prim_by_path_with_file("1", &file).unwrap();
|
||||
let resolver = file.resolver();
|
||||
let page2 = Page::from_primitive(page2_prim, &resolver).unwrap();
|
||||
let mut ops: Vec<Op> = timed!(page2.contents.unwrap().operations(&resolver).unwrap(), "parse ops");
|
||||
let mut ops: Vec<Op> = timed!(
|
||||
page2.contents.unwrap().operations(&resolver).unwrap(),
|
||||
"parse ops"
|
||||
);
|
||||
let serialized = timed!(serialize_ops(&mut ops).unwrap(), "serializing");
|
||||
let display = timed!(display_ops(&mut ops).unwrap(), "displaying");
|
||||
println!("Serialized -----------------------------------------------------------------");
|
||||
@ -157,6 +164,22 @@ mod tests {
|
||||
for (line, s) in display.iter().enumerate() {
|
||||
println!("{}: {}", line, s);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_read_stream() {
|
||||
let file = timed!(
|
||||
FileOptions::cached().open(FILE_PATH).unwrap(),
|
||||
"Loading file"
|
||||
);
|
||||
let prim = timed!(
|
||||
get_prim_model_by_path_with_file("1/Contents/1", &file).unwrap(),
|
||||
"get prim"
|
||||
);
|
||||
print_node(prim, 3);
|
||||
let content1 = timed!(
|
||||
get_stream_data_by_path_with_file("1/Contents/1/Data", &file).unwrap(),
|
||||
"get content 1"
|
||||
);
|
||||
println!("{}", content1);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/app.css
@ -19,14 +19,14 @@ body {
|
||||
color: var(--font-color);
|
||||
border-color: var(--secondary-color)
|
||||
}
|
||||
::before, ::after {
|
||||
|
||||
::before,
|
||||
::after {
|
||||
border-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.full-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import WelcomeScreen from "./WelcomeScreen.svelte";
|
||||
import {Pane, Splitpanes} from 'svelte-splitpanes';
|
||||
import {open} from "@tauri-apps/plugin-dialog";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import { Pane, Splitpanes } from "svelte-splitpanes";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type PdfFile from "../models/PdfFile";
|
||||
import ToolbarLeft from "./ToolbarLeft.svelte";
|
||||
import FileView from "./FileView.svelte";
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
const footerHeight: number = 30;
|
||||
const titleBarHeight: number = 30;
|
||||
const tabBarHeight: number = 30;
|
||||
let files: PdfFile[] = $state([]);
|
||||
let innerHeight: number = $state(1060);
|
||||
let errorMessage: string = $state("");
|
||||
@ -21,24 +22,28 @@
|
||||
let xrefTableShowing: boolean = $state(false);
|
||||
let treeShowing: boolean = $state(true);
|
||||
let pagesShowing: boolean = $state(false);
|
||||
let fileViewHeight: number = $derived(Math.max(innerHeight - footerHeight - titleBarHeight, 0));
|
||||
let fileViewHeight: number = $derived(
|
||||
Math.max(innerHeight - footerHeight - titleBarHeight, 0),
|
||||
);
|
||||
|
||||
let fStates: Map<string, FileViewState> = new Map<string, FileViewState>();
|
||||
let fState: FileViewState | undefined = $state();
|
||||
let selected_file = $derived(fState ? fState.file : undefined);
|
||||
initialLoadAllFiles()
|
||||
initialLoadAllFiles();
|
||||
|
||||
function initialLoadAllFiles() {
|
||||
invoke<PdfFile[]>("get_all_files").then(result_list => {
|
||||
files = result_list;
|
||||
createFileStates(files);
|
||||
if (files.length > 0) {
|
||||
selectFile(files[files.length - 1])
|
||||
}
|
||||
}).catch(error => {
|
||||
errorMessage = error
|
||||
console.error("File retrieval failed: " + error)
|
||||
});
|
||||
invoke<PdfFile[]>("get_all_files")
|
||||
.then((result_list) => {
|
||||
files = result_list;
|
||||
createFileStates(files);
|
||||
if (files.length > 0) {
|
||||
selectFile(files[files.length - 1]);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("File retrieval failed: " + error);
|
||||
});
|
||||
}
|
||||
|
||||
function createFileStates(files: PdfFile[]) {
|
||||
@ -46,7 +51,7 @@
|
||||
if (fStates.has(file.id)) {
|
||||
continue;
|
||||
}
|
||||
let fState = new FileViewState(file)
|
||||
let fState = new FileViewState(file);
|
||||
fStates.set(file.id, fState);
|
||||
}
|
||||
}
|
||||
@ -55,28 +60,32 @@
|
||||
let file_path = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [{name: 'pdf', extensions: ['pdf']}]
|
||||
})
|
||||
filters: [{ name: "pdf", extensions: ["pdf"] }],
|
||||
});
|
||||
if (file_path === null || Array.isArray(file_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
invoke<String>("upload", {path: file_path}).then(result => {
|
||||
invoke<PdfFile[]>("get_all_files").then(result_list => {
|
||||
files = result_list;
|
||||
createFileStates(files);
|
||||
let file = files.find(file => file.id === result);
|
||||
if (file) {
|
||||
selectFile(file);
|
||||
}
|
||||
}).catch(error => {
|
||||
errorMessage = error
|
||||
console.error("Fetch all files failed with: " + error)
|
||||
invoke<String>("upload", { path: file_path })
|
||||
.then((result) => {
|
||||
invoke<PdfFile[]>("get_all_files")
|
||||
.then((result_list) => {
|
||||
files = result_list;
|
||||
createFileStates(files);
|
||||
let file = files.find((file) => file.id === result);
|
||||
if (file) {
|
||||
selectFile(file);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("Fetch all files failed with: " + error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("File upload failed with: " + error);
|
||||
});
|
||||
}).catch(error => {
|
||||
errorMessage = error
|
||||
console.error("File upload failed with: " + error)
|
||||
});
|
||||
}
|
||||
|
||||
function selectFile(file: PdfFile) {
|
||||
@ -84,29 +93,44 @@
|
||||
}
|
||||
|
||||
function closeFile(file: PdfFile) {
|
||||
invoke("close_file", {id: file.id}).then(_ => {
|
||||
files = files.filter(f => f.id != file.id)
|
||||
if (file === selected_file) {
|
||||
fState = undefined;
|
||||
}
|
||||
}).catch(err => console.error(err));
|
||||
invoke("close_file", { id: file.id })
|
||||
.then((_) => {
|
||||
files = files.filter((f) => f.id != file.id);
|
||||
if (file === selected_file) {
|
||||
fState = undefined;
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
}
|
||||
|
||||
</script>
|
||||
<svelte:window bind:innerHeight/>
|
||||
<main style="height: {innerHeight}px">
|
||||
|
||||
<svelte:window bind:innerHeight />
|
||||
<main style="height: {innerHeight}px; overflow: hidden;">
|
||||
<div style="height: {titleBarHeight}px">
|
||||
<TitleBar></TitleBar>
|
||||
</div>
|
||||
<div style="height: {fileViewHeight}px">
|
||||
<Splitpanes theme="forge-movable" dblClickSplitter={false}>
|
||||
<Pane size={2.5} minSize={1.5} maxSize={4}>
|
||||
<ToolbarLeft bind:tree={treeShowing} bind:pages={pagesShowing}></ToolbarLeft>
|
||||
<ToolbarLeft bind:tree={treeShowing} bind:pages={pagesShowing}
|
||||
></ToolbarLeft>
|
||||
</Pane>
|
||||
<Pane>
|
||||
<TabBar {files} {selected_file} closeTab={closeFile} openTab={upload} selectTab={selectFile}></TabBar>
|
||||
{#if (fState)}
|
||||
<FileView {treeShowing} {xrefTableShowing} {pagesShowing} {fState} height={fileViewHeight}></FileView>
|
||||
<TabBar
|
||||
{files}
|
||||
{selected_file}
|
||||
closeTab={closeFile}
|
||||
openTab={upload}
|
||||
selectTab={selectFile}
|
||||
></TabBar>
|
||||
{#if fState}
|
||||
<FileView
|
||||
{treeShowing}
|
||||
{xrefTableShowing}
|
||||
{pagesShowing}
|
||||
{fState}
|
||||
height={fileViewHeight}
|
||||
></FileView>
|
||||
{:else}
|
||||
<WelcomeScreen {upload}></WelcomeScreen>
|
||||
{/if}
|
||||
@ -116,10 +140,9 @@
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
<Footer {fState} {footerHeight} ></Footer>
|
||||
<Footer {fState} {footerHeight}></Footer>
|
||||
</main>
|
||||
|
||||
|
||||
<style global>
|
||||
/* Resiable layout */
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__pane) {
|
||||
@ -133,7 +156,8 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__splitter:before), :global(.splitpanes.forge-movable) :global(.splitpanes__splitter:after) {
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__splitter:before),
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__splitter:after) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -143,61 +167,103 @@
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__splitter:hover:before), :global(.splitpanes.forge-movable) :global(.splitpanes__splitter:hover:after) {
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:hover:before),
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:hover:after) {
|
||||
background-color: var(--boundary-color);
|
||||
}
|
||||
|
||||
:global(.splitpanes.forge-movable) :global(.splitpanes__splitter:first-child) {
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:first-child) {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes) :global(.splitpanes) :global(.splitpanes__splitter) {
|
||||
:global(.forge-movable.splitpanes)
|
||||
:global(.splitpanes)
|
||||
:global(.splitpanes__splitter) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical) > :global(.splitpanes__splitter),
|
||||
:global(.forge-movable) :global(.splitpanes--vertical) > :global(.splitpanes__splitter) {
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter) {
|
||||
width: 2px;
|
||||
border-left: 1px solid var(--boundary-color);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical) > :global(.splitpanes__splitter:before), :global(.forge-movable.splitpanes--vertical) > :global(.splitpanes__splitter:after), :global(.forge-movable) :global(.splitpanes--vertical) > :global(.splitpanes__splitter:before), :global(.forge-movable) :global(.splitpanes--vertical) > :global(.splitpanes__splitter:after) {
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical) > :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable) :global(.splitpanes--vertical) > :global(.splitpanes__splitter:before) {
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical) > :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable) :global(.splitpanes--vertical) > :global(.splitpanes__splitter:after) {
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal) > :global(.splitpanes__splitter),
|
||||
:global(.forge-movable) :global(.splitpanes--horizontal) > :global(.splitpanes__splitter) {
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter) {
|
||||
height: 2px;
|
||||
border-top: 1px solid var(--boundary-color);
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal) > :global(.splitpanes__splitter:before), :global(.forge-movable.splitpanes--horizontal) > :global(.splitpanes__splitter:after), :global(.forge-movable) :global(.splitpanes--horizontal) > :global(.splitpanes__splitter:before), :global(.forge-movable) :global(.splitpanes--horizontal) > :global(.splitpanes__splitter:after) {
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
transform: translateX(-50%);
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal) > :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable) :global(.splitpanes--horizontal) > :global(.splitpanes__splitter:before) {
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal) > :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable) :global(.splitpanes--horizontal) > :global(.splitpanes__splitter:after) {
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
@ -211,7 +277,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
:global(div.splitpanes--horizontal.splitpanes--dragging) {
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
@ -1,36 +1,68 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type ContentModel from "../models/ContentModel.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import * as monaco from "monaco-editor";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
|
||||
let {id, path, h}: {id: string, path: string, h: number} = $props();
|
||||
let { fState, height }: { fState: FileViewState; height: number } =
|
||||
$props();
|
||||
let h = $derived(height - 34);
|
||||
let path = $derived(fState.prim?.getLastJump().toString());
|
||||
let id = $derived(fState.file.id);
|
||||
let contents: ContentModel | undefined = $state(undefined);
|
||||
$effect( () => {
|
||||
loadContents(path, id)
|
||||
})
|
||||
let editorContainer: HTMLElement;
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
|
||||
function loadContents(id: string, path: string) {
|
||||
invoke<ContentModel>("get_contents", {id: id, path: path})
|
||||
.then(result => contents = result)
|
||||
.catch(err => console.error(err));
|
||||
onMount(() => {
|
||||
editor = monaco.editor.create(editorContainer, {
|
||||
value: "",
|
||||
language: "plaintext",
|
||||
theme: "vs-dark",
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: 14,
|
||||
automaticLayout: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
editor.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
loadContents(path, id);
|
||||
});
|
||||
|
||||
$inspect(contents);
|
||||
function loadContents(path: string | undefined, id: string) {
|
||||
console.log("Loading contents for", path, id);
|
||||
if (!path || !id) return;
|
||||
|
||||
invoke<ContentModel>("get_contents", { id, path })
|
||||
.then((result) => {
|
||||
contents = result;
|
||||
if (contents && editor) {
|
||||
const text = contents.parts
|
||||
.map((part) => part.join("\n"))
|
||||
.join(
|
||||
"\n\n%-------------------% EOF %-------------------%\n\n",
|
||||
);
|
||||
editor.setValue(text);
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
monaco.editor.setModelLanguage(
|
||||
model,
|
||||
"pdf-content-stream",
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
}
|
||||
</script>
|
||||
{#if contents}
|
||||
<div class="overflow-auto">
|
||||
<div class="whitespace-nowrap" style="height: {h}px">
|
||||
{#each contents.parts as part }
|
||||
<div class="part">
|
||||
{#each part as line}
|
||||
<div class="line">{line}</div>
|
||||
{/each}
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{"Loading id: " + id + " Path: " + path}
|
||||
{/if}
|
||||
|
||||
<div bind:this={editorContainer} style="height: {h}px; width: 100%;"></div>
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@ -1,23 +1,34 @@
|
||||
<script lang="ts">
|
||||
import {Pane, Splitpanes} from "svelte-splitpanes";
|
||||
import { Pane, Splitpanes } from "svelte-splitpanes";
|
||||
import XRefTable from "./XRefTable.svelte";
|
||||
import PrimitiveView from "./PrimitiveView.svelte";
|
||||
import TreeView from "./TreeView.svelte";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import {onMount} from 'svelte';
|
||||
import { onMount } from "svelte";
|
||||
import PageList from "./PageList.svelte";
|
||||
import ContentsView from "./ContentsView.svelte";
|
||||
|
||||
let {treeShowing, xrefTableShowing, pagesShowing, fState, height}: {
|
||||
treeShowing: boolean,
|
||||
xrefTableShowing: boolean,
|
||||
pagesShowing: boolean,
|
||||
fState: FileViewState,
|
||||
height: number
|
||||
} = $props()
|
||||
let {
|
||||
treeShowing,
|
||||
xrefTableShowing,
|
||||
pagesShowing,
|
||||
fState,
|
||||
height,
|
||||
}: {
|
||||
treeShowing: boolean;
|
||||
xrefTableShowing: boolean;
|
||||
pagesShowing: boolean;
|
||||
fState: FileViewState;
|
||||
height: number;
|
||||
} = $props();
|
||||
|
||||
let width: number = $state(0);
|
||||
let xRefTableWidth = $derived(xrefTableShowing ? 281 : 0);
|
||||
let splitPanesWidth: number = $derived(width - xRefTableWidth);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
// Check for "Alt + Left Arrow"
|
||||
if (event.altKey && event.key === 'ArrowLeft') {
|
||||
if (event.altKey && event.key === "ArrowLeft") {
|
||||
fState.popPath();
|
||||
}
|
||||
}
|
||||
@ -30,39 +41,99 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
window.addEventListener('mousedown', handleMouseButton);
|
||||
window.addEventListener("keydown", handleKeydown);
|
||||
window.addEventListener("mousedown", handleMouseButton);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeydown);
|
||||
window.removeEventListener('mousedown', handleMouseButton);
|
||||
window.removeEventListener("keydown", handleKeydown);
|
||||
window.removeEventListener("mousedown", handleMouseButton);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<Splitpanes theme="forge-movable">
|
||||
<Pane size={treeShowing || pagesShowing ? 15 : 0} minSize={treeShowing || pagesShowing ? 1 : 0} maxSize={treeShowing || pagesShowing ? 100 : 0}>
|
||||
<Splitpanes theme="forge-movable" horizontal>
|
||||
<Pane size={treeShowing ? pagesShowing ? 50 : 100 : 0} minSize={treeShowing ? 2 : 0} maxSize={treeShowing ? 100 : 0}>
|
||||
<TreeView {fState}></TreeView>
|
||||
<div bind:clientWidth={width} class="file-view-container">
|
||||
<div style="width: {splitPanesWidth}px">
|
||||
<Splitpanes theme="forge-movable" pushOtherPanes={false}>
|
||||
<Pane
|
||||
size={treeShowing || pagesShowing ? 15 : 0}
|
||||
minSize={treeShowing || pagesShowing ? 1 : 0}
|
||||
maxSize={treeShowing || pagesShowing ? 100 : 0}
|
||||
>
|
||||
<Splitpanes
|
||||
theme="forge-movable"
|
||||
horizontal
|
||||
pushOtherPanes={false}
|
||||
style="height: {height}px"
|
||||
>
|
||||
<Pane
|
||||
size={treeShowing ? (pagesShowing ? 50 : 100) : 0}
|
||||
minSize={treeShowing ? 2 : 0}
|
||||
maxSize={treeShowing ? 100 : 0}
|
||||
>
|
||||
<TreeView {fState}></TreeView>
|
||||
</Pane>
|
||||
<Pane
|
||||
size={pagesShowing ? (treeShowing ? 50 : 100) : 0}
|
||||
minSize={pagesShowing ? 2 : 0}
|
||||
maxSize={pagesShowing ? 100 : 0}
|
||||
>
|
||||
<PageList {fState}></PageList>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
<Pane size={pagesShowing ? treeShowing ? 50 : 100 : 0} minSize={pagesShowing ? 2 : 0} maxSize={pagesShowing ? 100 : 0}>
|
||||
<PageList {fState}></PageList>
|
||||
|
||||
<Pane minSize={1}>
|
||||
<div>
|
||||
<PrimitiveView {fState} {height}></PrimitiveView>
|
||||
</div>
|
||||
</Pane>
|
||||
{#if fState.prim?.isPage()}
|
||||
<Pane minSize={1}>
|
||||
<div class="overflow-hidden">
|
||||
<ContentsView {fState} {height}></ContentsView>
|
||||
</div>
|
||||
</Pane>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
|
||||
|
||||
<Pane minSize={1}>
|
||||
<div>
|
||||
<PrimitiveView {fState} {height}></PrimitiveView>
|
||||
</div>
|
||||
{#if xrefTableShowing}
|
||||
<div class="xref-modal" class:visible={xrefTableShowing}>
|
||||
<XRefTable {fState}></XRefTable>
|
||||
</div>
|
||||
</Pane>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Pane size={xrefTableShowing ? 16 : 0} minSize={xrefTableShowing ? 1 : 0} maxSize={xrefTableShowing ? 100 : 0}>
|
||||
<XRefTable {fState}></XRefTable>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
<style lang="postcss">
|
||||
.file-view-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
.xref-modal-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.xref-modal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 281px;
|
||||
background: var(--background-color);
|
||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.xref-modal.visible {
|
||||
transform: translateX(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import type { DetailPath } from "../models/Primitive.svelte";
|
||||
import type { Trace } from "../models/Primitive.svelte";
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import { CaretRightOutline } from "flowbite-svelte-icons";
|
||||
|
||||
class Path {
|
||||
public value: string;
|
||||
public jump?: string;
|
||||
public index: number;
|
||||
|
||||
constructor(value: string, jump?: string) {
|
||||
constructor(value: string, index: number, jump?: string) {
|
||||
this.value = value;
|
||||
this.jump = jump;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,51 +21,64 @@
|
||||
footerHeight,
|
||||
}: { fState: FileViewState | undefined; footerHeight: number } = $props();
|
||||
let elements: Path[] | undefined = $derived(
|
||||
fState && fState.prim ? toElements(fState.prim.detail_path) : undefined,
|
||||
fState && fState.prim ? toElements(fState.prim.trace) : undefined,
|
||||
);
|
||||
|
||||
$inspect(elements);
|
||||
$inspect(fState?.prim?.detail_path);
|
||||
function toElements(path: DetailPath[]): Path[] {
|
||||
$inspect(fState?.path);
|
||||
$inspect(fState?.prim?.trace);
|
||||
function toElements(path: Trace[]): Path[] {
|
||||
if (path.length == 0) {
|
||||
return [{ value: "Trailer" } as Path];
|
||||
return [new Path("Trailer", 0)];
|
||||
}
|
||||
|
||||
let display: Path[] = [];
|
||||
if (path[0].key === "/") {
|
||||
display.push(new Path("Trailer"));
|
||||
} else {
|
||||
display.push(new Path(path[0].key, path[0].last_jump));
|
||||
}
|
||||
|
||||
let lastJump = path[0].last_jump;
|
||||
|
||||
for (const pathElement of path.slice(1, path.length)) {
|
||||
let lastJump: string | undefined;
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const pathElement = path[i];
|
||||
if (pathElement.last_jump !== lastJump) {
|
||||
lastJump = pathElement.last_jump;
|
||||
display.push(new Path(pathElement.key, lastJump));
|
||||
display.push(new Path(pathElement.key, i, lastJump));
|
||||
} else {
|
||||
display.push(new Path(pathElement.key, undefined));
|
||||
display.push(new Path(pathElement.key, i, undefined));
|
||||
}
|
||||
}
|
||||
return display;
|
||||
}
|
||||
|
||||
function selectPath(path: Path) {
|
||||
if (fState) {
|
||||
let newPath = fState.copyPath().slice(0, path.index + 1);
|
||||
fState.selectPath(newPath);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="bg-forge-prim border border-forge-bound"
|
||||
style="height: {footerHeight}px"
|
||||
>
|
||||
<div class="m-1 flex flex-row">
|
||||
<div class="flex flex-row items-center" style="height: {footerHeight}px">
|
||||
{#if elements}
|
||||
{#each elements as path}
|
||||
<button class="m-1 flex flex-row mt-auto mb-auto">
|
||||
{#if path.jump}
|
||||
<div class="m-auto" style="height: {footerHeight}px">
|
||||
<PrimitiveIcon ptype={"Reference"}></PrimitiveIcon>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="text-xs ml-1 c">{path.value}</p>
|
||||
<button
|
||||
class="flex flex-row items-center ml-2"
|
||||
onclick={() => selectPath(path)}
|
||||
>
|
||||
{#if path.jump}
|
||||
<div
|
||||
class="flex items-center mr-1 ml-1"
|
||||
style="height: {footerHeight}px"
|
||||
>
|
||||
<PrimitiveIcon ptype={"Reference"}></PrimitiveIcon>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="flex items-center"
|
||||
style="height: {footerHeight}px"
|
||||
>
|
||||
<CaretRightOutline class="text-forge-sec" />
|
||||
</div>
|
||||
{/if}
|
||||
<p class="text-xs ml-1">{path.value}</p>
|
||||
</button>
|
||||
{/each}
|
||||
{/if}
|
||||
@ -70,5 +86,4 @@
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
</style>
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import type PageModel from "../models/PageModel";
|
||||
|
||||
let {fState}: { fState: FileViewState } = $props();
|
||||
let { fState }: { fState: FileViewState } = $props();
|
||||
let h: number = $state(100);
|
||||
let selected: PageModel | undefined = $state(undefined);
|
||||
|
||||
function handlePageSelect(page: PageModel) {
|
||||
selected = page
|
||||
fState.selectPath([page.id]);
|
||||
selected = page;
|
||||
fState.selectPath(["Page" + page.page_num]);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -18,40 +18,47 @@
|
||||
<div class="w-[251px]">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="page-cell t-header border-forge-prim ">Page</td>
|
||||
<td class="ref-cell t-header border-forge-sec ">Ref</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="page-cell t-header border-forge-prim"
|
||||
>Page</td
|
||||
>
|
||||
<td class="ref-cell t-header border-forge-sec">Ref</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="overflow-y-auto" style="height: {h-55}px">
|
||||
<div class="overflow-y-auto" style="height: {h - 55}px">
|
||||
<table>
|
||||
<tbody>
|
||||
{#each fState.file.pages as page}
|
||||
<tr class:selected={page === selected} class="hover:bg-forge-sec" ondblclick={() => handlePageSelect(page)}>
|
||||
<td class="page-cell t-data">
|
||||
<div class="key-field">
|
||||
<PrimitiveIcon ptype={"Reference"}/>
|
||||
<p class="text-left">
|
||||
{page.key}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="ref-cell t-data">{page.id}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{#each fState.file.pages as page}
|
||||
<tr
|
||||
class:selected={page === selected}
|
||||
class="hover:bg-forge-sec"
|
||||
ondblclick={() => handlePageSelect(page)}
|
||||
>
|
||||
<td class="page-cell t-data">
|
||||
<div class="key-field">
|
||||
<PrimitiveIcon ptype={"Reference"} />
|
||||
<p class="text-left">
|
||||
{page.key}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="ref-cell t-data">{page.id}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.key-field {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 3px
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
@ -78,5 +85,4 @@
|
||||
@apply min-w-[150px] w-[150px] min-h-[20px] h-[20px] p-1 m-0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
let selected: Primitive | undefined = $state(undefined);
|
||||
let prim = $derived(fState.prim);
|
||||
let showContents = $state(false);
|
||||
|
||||
function handlePrimSelect(prim: Primitive) {
|
||||
if (prim.isContainer()) {
|
||||
if (!prim) {
|
||||
@ -30,8 +31,10 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="page-cell t-header border-forge-prim">Key</td>
|
||||
<td class="ref-cell t-header border-forge-prim">Type</td>
|
||||
<td class="page-cell t-header border-forge-prim">Key</td
|
||||
>
|
||||
<td class="ref-cell t-header border-forge-prim">Type</td
|
||||
>
|
||||
<td class="cell t-header border-forge-sec">Value</td>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -60,21 +63,6 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="flex flex-row">
|
||||
Dict Type: {prim.sub_type}
|
||||
</p>
|
||||
{#if prim.isPage()}
|
||||
<button onclick={() => (showContents = true)}
|
||||
>View contents</button
|
||||
>
|
||||
{#if showContents}
|
||||
<ContentsView
|
||||
id={fState.file.id}
|
||||
path={prim.getLastJump().toString()}
|
||||
h={500}
|
||||
></ContentsView>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
<script lang="ts">
|
||||
import type Primitive from "../models/Primitive.svelte";
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import {CaretRightOutline, CaretDownOutline} from "flowbite-svelte-icons";
|
||||
import { CaretRightOutline, CaretDownOutline } from "flowbite-svelte-icons";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import {arraysAreEqual} from "../utils.js";
|
||||
|
||||
let {prim, path, fState}: { prim: Primitive | undefined, path: string[], fState: FileViewState } = $props();
|
||||
let active = $derived(arraysAreEqual(path, fState.path))
|
||||
import { arraysAreEqual } from "../utils.js";
|
||||
|
||||
let {
|
||||
prim,
|
||||
path,
|
||||
fState,
|
||||
}: { prim: Primitive | undefined; path: string[]; fState: FileViewState } =
|
||||
$props();
|
||||
let active = $derived(arraysAreEqual(path, fState.path));
|
||||
|
||||
function copyPathAndAppend(key: string): string[] {
|
||||
const _path = copyPath();
|
||||
@ -34,31 +38,45 @@
|
||||
fState.selectPath(_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{#if prim}
|
||||
<ul class="active">
|
||||
{#each prim.children as child}
|
||||
<li>
|
||||
<div class="item ">
|
||||
<div class="item">
|
||||
{#if child.children.length > 0}
|
||||
<button onclick={() => fState.collapseTree(copyPathAndAppend(child.key))}><span class="caret"><CaretDownOutline/></span></button>
|
||||
<button
|
||||
onclick={() =>
|
||||
fState.collapseTree(
|
||||
copyPathAndAppend(child.key),
|
||||
)}
|
||||
><span class="caret"><CaretDownOutline /></span
|
||||
></button
|
||||
>
|
||||
{:else if child.isContainer()}
|
||||
<button onclick={() => fState.expandTree(copyPathAndAppend(child.key))}><span class="caret"><CaretRightOutline/></span></button>
|
||||
<button
|
||||
onclick={() =>
|
||||
fState.expandTree(copyPathAndAppend(child.key))}
|
||||
><span class="caret"><CaretRightOutline /></span
|
||||
></button
|
||||
>
|
||||
{:else}
|
||||
<span class="no-caret"></span>
|
||||
{/if}
|
||||
<button class="select-button" ondblclick={() => selectItem(child)}>
|
||||
<button
|
||||
class="select-button"
|
||||
ondblclick={() => selectItem(child)}
|
||||
>
|
||||
<div class="item">
|
||||
<PrimitiveIcon ptype={child.ptype}/>
|
||||
<PrimitiveIcon ptype={child.ptype} />
|
||||
<div class="row">
|
||||
<p>
|
||||
{child.key}
|
||||
</p>
|
||||
{#if child.subType}
|
||||
<p class="ml-1 text-forge-sec small">
|
||||
{child.subType}
|
||||
{#if child.sub_type}
|
||||
<p class="ml-1 text-forge-sec small">
|
||||
{child.sub_type}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@ -66,14 +84,17 @@
|
||||
</button>
|
||||
</div>
|
||||
{#if child.children.length > 0}
|
||||
<svelte:self prim={child} path={copyPathAndAppend(child.key)} {fState}></svelte:self>
|
||||
<svelte:self
|
||||
prim={child}
|
||||
path={copyPathAndAppend(child.key)}
|
||||
{fState}
|
||||
></svelte:self>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
.select-button {
|
||||
@apply hover:bg-forge-sec pr-3;
|
||||
@ -103,7 +124,8 @@
|
||||
}
|
||||
|
||||
/* Remove default bullets */
|
||||
ul, #myUL {
|
||||
ul,
|
||||
#myUL {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
@ -111,7 +133,6 @@
|
||||
@apply pl-5;
|
||||
}
|
||||
|
||||
|
||||
.no-caret {
|
||||
@apply pl-5;
|
||||
user-select: none;
|
||||
@ -122,7 +143,6 @@
|
||||
@apply text-forge-sec;
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
@ -135,4 +155,4 @@
|
||||
.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,39 +1,40 @@
|
||||
<script lang="ts">
|
||||
|
||||
import TreeNode from "./TreeNode.svelte";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import type Primitive from "../models/Primitive.svelte";
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
|
||||
let {fState}: { fState: FileViewState } = $props()
|
||||
let { fState }: { fState: FileViewState } = $props();
|
||||
let prim: Primitive | undefined = $derived(fState.treeView);
|
||||
let h = $state(100);
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:clientHeight={h} class="full-container">
|
||||
<div class="overflow-auto" style="height: {h}px">
|
||||
<ul id="myUL">
|
||||
{#if prim}
|
||||
<li>
|
||||
<div class="item">
|
||||
<PrimitiveIcon ptype={"Dictionary"}/>
|
||||
<PrimitiveIcon ptype={"Dictionary"} />
|
||||
{"Trailer "}
|
||||
</div>
|
||||
<TreeNode {prim} path={["/"]} {fState}></TreeNode>
|
||||
<TreeNode {prim} path={prim.getTrace()} {fState}></TreeNode>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
getTrace
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.item {
|
||||
@apply text-sm rounded-sm;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
}
|
||||
ul, #myUL {
|
||||
ul,
|
||||
#myUL {
|
||||
list-style-type: none;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,38 +1,45 @@
|
||||
<script lang="ts">
|
||||
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import type XRefEntry from "../models/XRefEntry";
|
||||
|
||||
const cellH = 25;
|
||||
const headerOffset = 80;
|
||||
let {fState}: { fState: FileViewState } = $props();
|
||||
|
||||
let { fState }: { fState: FileViewState } = $props();
|
||||
|
||||
let viewHeight: number = $state(100);
|
||||
let fillerHeight: number = $state(0);
|
||||
|
||||
let firstEntry = $state(0);
|
||||
let lastEntry = $state(100);
|
||||
let entriesToDisplay: XRefEntry[] = $derived(fState.xref_entries.slice(firstEntry, lastEntry));
|
||||
let entriesToDisplay: XRefEntry[] = $derived(
|
||||
fState.xref_entries.slice(firstEntry, lastEntry),
|
||||
);
|
||||
|
||||
let bodyViewHeight: number = $derived(Math.max(viewHeight - headerOffset, 0));
|
||||
let selectedPath: number | string | undefined = $derived(fState.getLastJump());
|
||||
let totalBodyHeight: number = $derived(Math.max(0, (fState.file.xref_entries * cellH) - headerOffset));
|
||||
let bodyViewHeight: number = $derived(
|
||||
Math.max(viewHeight - headerOffset, 0),
|
||||
);
|
||||
let selectedPath: number | string | undefined = $derived(
|
||||
fState.getLastJump(),
|
||||
);
|
||||
let totalBodyHeight: number = $derived(
|
||||
Math.max(0, fState.file.xref_entries * cellH - headerOffset),
|
||||
);
|
||||
let scrollContainer: HTMLElement;
|
||||
|
||||
$effect(() => {
|
||||
if (typeof selectedPath === "number") {
|
||||
smoothScrollTo(scrollContainer, selectedPath)
|
||||
}
|
||||
if (typeof selectedPath === "number") {
|
||||
smoothScrollTo(scrollContainer, selectedPath);
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
function smoothScrollTo(scrollContainer: HTMLElement, target: number) {
|
||||
|
||||
const targetY = Math.max(target, 0) * cellH;
|
||||
const startY = scrollContainer.scrollTop;
|
||||
|
||||
if (targetY - startY > 0 && targetY - startY < Math.max(viewHeight - headerOffset, 0)) {
|
||||
if (
|
||||
targetY - startY > 0 &&
|
||||
targetY - startY < Math.max(viewHeight - headerOffset, 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -65,41 +72,60 @@
|
||||
|
||||
fillerHeight = firstEntry * cellH;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:clientHeight={viewHeight} class="full-container">
|
||||
<div class="overflow-x-auto">
|
||||
<div class="w-[281px]">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="cell t-header border-forge-prim">Obj Nr</td>
|
||||
<td class="cell t-header border-forge-prim">Gen Nr</td>
|
||||
<td class="cell t-header border-forge-prim">Type</td>
|
||||
<td class="cell t-header border-forge-sec">Offset</td>
|
||||
</tr>
|
||||
<tr class={selectedPath === "/" ? "bg-forge-acc" : "hover:bg-forge-sec"} ondblclick={() => fState.selectXref(undefined)}>
|
||||
<td class="cell t-data">Trailer</td>
|
||||
<td class="cell t-data">65535</td>
|
||||
<td class="cell t-data">Dictionary</td>
|
||||
<td class="cell t-data">End of file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="cell t-header border-forge-prim">Obj Nr</td>
|
||||
<td class="cell t-header border-forge-prim">Gen Nr</td>
|
||||
<td class="cell t-header border-forge-prim">Type</td>
|
||||
<td class="cell t-header border-forge-sec">Offset</td>
|
||||
</tr>
|
||||
<tr
|
||||
class={selectedPath === "/"
|
||||
? "bg-forge-acc"
|
||||
: "hover:bg-forge-sec"}
|
||||
ondblclick={() => fState.selectXref(undefined)}
|
||||
>
|
||||
<td class="cell t-data">Trailer</td>
|
||||
<td class="cell t-data">65535</td>
|
||||
<td class="cell t-data">Dictionary</td>
|
||||
<td class="cell t-data">End of file</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="scrollContainer" style="height: {bodyViewHeight}px" onscroll={handleScroll} bind:this={scrollContainer}>
|
||||
<div class="container" style="height: {totalBodyHeight - headerOffset}px">
|
||||
<div
|
||||
class="scrollContainer"
|
||||
style="height: {bodyViewHeight}px"
|
||||
onscroll={handleScroll}
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<div
|
||||
class="container"
|
||||
style="height: {totalBodyHeight - headerOffset}px"
|
||||
>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="filler" style="height: {fillerHeight}px"></tr>
|
||||
{#each entriesToDisplay as entry}
|
||||
<tr class={selectedPath === entry.obj_num ? "bg-forge-acc" : "hover:bg-forge-sec"} ondblclick={() => fState.selectXref(entry)}>
|
||||
<td class="cell t-data">{entry.obj_num}</td>
|
||||
<td class="cell t-data">{entry.gen_num}</td>
|
||||
<td class="cell t-data">{entry.obj_type}</td>
|
||||
<td class="cell t-data">{entry.offset}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
<tr class="filler" style="height: {fillerHeight}px"
|
||||
></tr>
|
||||
{#each entriesToDisplay as entry}
|
||||
<tr
|
||||
class={selectedPath === entry.obj_num
|
||||
? "bg-forge-acc"
|
||||
: "hover:bg-forge-sec"}
|
||||
ondblclick={() => fState.selectXref(entry)}
|
||||
>
|
||||
<td class="cell t-data">{entry.obj_num}</td>
|
||||
<td class="cell t-data">{entry.gen_num}</td>
|
||||
<td class="cell t-data">{entry.obj_type}</td
|
||||
>
|
||||
<td class="cell t-data">{entry.offset}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -130,5 +156,15 @@
|
||||
.scrollContainer {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
.xref-modal {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 281px;
|
||||
background: var(--background-color);
|
||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import type PdfFile from "./PdfFile";
|
||||
import type XRefEntry from "./XRefEntry";
|
||||
import Primitive from "./Primitive.svelte";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import TreeViewNode from "./TreeViewNode.svelte";
|
||||
import type XRefTable from "./XRefTable";
|
||||
|
||||
export default class FileViewState {
|
||||
|
||||
public path: string[] = $state(["/"]);
|
||||
public treeRoot: TreeViewNode = $state(new TreeViewNode("/", [new TreeViewNode("Root", [])]));
|
||||
public path: string[] = $state(["Trailer"]);
|
||||
public treeRoot: TreeViewNode = $state(new TreeViewNode("Trailer", [new TreeViewNode("Root", [])]));
|
||||
public file: PdfFile;
|
||||
public prim: Primitive | undefined = $state();
|
||||
public treeView: Primitive | undefined = $state();
|
||||
@ -27,8 +27,12 @@ export default class FileViewState {
|
||||
return this.prim?.getLastJump()
|
||||
}
|
||||
|
||||
getFirstJump(): string | number | undefined {
|
||||
return this.prim?.getFirstJump()
|
||||
}
|
||||
|
||||
public loadXrefEntries() {
|
||||
invoke<XRefTable>("get_xref_table", {id: this.file.id})
|
||||
invoke<XRefTable>("get_xref_table", { id: this.file.id })
|
||||
.then(result => {
|
||||
this.xref_entries = result.entries;
|
||||
})
|
||||
@ -36,7 +40,7 @@ export default class FileViewState {
|
||||
}
|
||||
|
||||
public selectPath(newPath: string[]) {
|
||||
invoke<Primitive>("get_prim_by_path", {id: this.file.id, path: this.mergePaths(newPath)})
|
||||
invoke<Primitive>("get_prim_by_path", { id: this.file.id, path: this.mergePaths(newPath) })
|
||||
.then(result => {
|
||||
this.prim = new Primitive(result)
|
||||
this.path = newPath
|
||||
@ -46,11 +50,11 @@ export default class FileViewState {
|
||||
|
||||
|
||||
public loadTreeView() {
|
||||
invoke<Primitive>("get_prim_tree_by_path", {id: this.file.id, path: this.treeRoot})
|
||||
invoke<Primitive>("get_prim_tree_by_path", { id: this.file.id, path: this.treeRoot })
|
||||
.then(result => {
|
||||
this.treeView = new Primitive(result);
|
||||
}
|
||||
).catch(err => console.error(err))
|
||||
).catch(err => console.error(err))
|
||||
}
|
||||
|
||||
public getTreeRoot() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
|
||||
export default interface PageModel {
|
||||
key: string,
|
||||
id: number
|
||||
}
|
||||
obj_num: number,
|
||||
page_num: number,
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ export default class Primitive {
|
||||
public sub_type: string;
|
||||
public value: string;
|
||||
public children: Primitive[];
|
||||
public detail_path: DetailPath[] = $state([]);
|
||||
public trace: Trace[] = $state([]);
|
||||
|
||||
constructor(
|
||||
p: Primitive
|
||||
@ -17,9 +17,9 @@ export default class Primitive {
|
||||
for (let child of p.children) {
|
||||
this.children.push(new Primitive(child));
|
||||
}
|
||||
this.detail_path = [];
|
||||
for (let path of p.detail_path) {
|
||||
this.detail_path.push(path);
|
||||
this.trace = [];
|
||||
for (let path of p.trace) {
|
||||
this.trace.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,18 +27,29 @@ export default class Primitive {
|
||||
return this.ptype === "Dictionary" || this.ptype === "Array" || this.ptype === "Reference" || this.ptype === "Stream";
|
||||
}
|
||||
|
||||
public getTrace(): string[] {
|
||||
return this.trace.map(path => path.key);
|
||||
}
|
||||
|
||||
public getLastJump(): string | number {
|
||||
let path = this.detail_path[this.detail_path.length - 1].last_jump;
|
||||
if (path === "/") {return path};
|
||||
let path = this.trace[this.trace.length - 1].last_jump;
|
||||
if (path === "/") { return path };
|
||||
return +path;
|
||||
}
|
||||
|
||||
public isPage(): boolean {
|
||||
return this.sub_type === "Page";
|
||||
return this.trace[0].last_jump.startsWith("Page");
|
||||
}
|
||||
|
||||
public getFirstJump(): string | number | undefined {
|
||||
let path = this.trace[0].last_jump;
|
||||
if (path === "Trailer") { return path };
|
||||
if (path.startsWith("Page")) { return path };
|
||||
return +path;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DetailPath {
|
||||
readonly key: string ;
|
||||
readonly last_jump: string ;
|
||||
export interface Trace {
|
||||
readonly key: string;
|
||||
readonly last_jump: string;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
||||
import adapter from "@sveltejs/adapter-static";
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
import {sveltePreprocess} from "svelte-preprocess";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
@ -11,6 +11,9 @@ const config = {
|
||||
adapter: adapter(),
|
||||
},
|
||||
preprocess: vitePreprocess(),
|
||||
// compilerOptions: {
|
||||
// runes: true
|
||||
// }
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@ -16,4 +16,4 @@
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,15 @@
|
||||
import {defineConfig} from "vite";
|
||||
import {sveltekit} from "@sveltejs/kit/vite";
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
// import monacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||
// @ts-expect-error process is a nodejs global
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [sveltekit()],
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
// monacoEditorPlugin({})
|
||||
],
|
||||
css: {
|
||||
postcss: './postcss.config.cjs', // Path to PostCSS config
|
||||
},
|
||||