rendering with pdf.js working

This commit is contained in:
Kilian Schuettler 2025-02-19 10:06:59 +01:00
parent 34d2c05f21
commit 0e1e7c359b
10 changed files with 136 additions and 70 deletions

View File

@ -7,8 +7,15 @@
import ContentModel from "../models/ContentModel.svelte";
let {fState, height}: {fState: FileViewState, height: number} = $props();
let state: PageViewState | undefined = $derived(fState.pageViewState);
let state: PageViewState = new PageViewState(fState, render);
let contents: ContentModel | undefined = $derived(state?.contents)
let display: HTMLCanvasElement;
fState.pageViewState = state;
function render() {
console.log("render", display)
state.loadImage(display);
}
</script>
{#if state}
@ -19,7 +26,7 @@
</div>
</Pane>
<Pane minSize={1}>
<RenderedPageView img={state.img_data} {height}></RenderedPageView>
<RenderedPageView bind:img={display} {height}></RenderedPageView>
</Pane>
</Splitpanes>
{:else }

View File

@ -1,20 +1,21 @@
<script lang="ts">
import ZoomableContainer from "./ZoomableContainer.svelte";
import { RefreshOutline } from "flowbite-svelte-icons";
import {RawImageData} from "../models/RawImageData";
let { img, height }: { img: RawImageData | undefined; height: number } =
let { img = $bindable(), height }: { img: HTMLCanvasElement; height: number } =
$props();
</script>
<div class="page-container" style:height={height + "px"}>
{#if !img}
<div class="loading-container">
<RefreshOutline class="animate-spin" size="xl" />
</div>
{:else}
<ZoomableContainer {img} {height} />
{/if}
<!--{#if !img}-->
<!-- <div class="loading-container">-->
<!-- <RefreshOutline class="animate-spin" size="xl" />-->
<!-- </div>-->
<!--{:else}-->
<canvas bind:this={img} class="bg-forge-dark">
</canvas>
<!-- <ZoomableContainer imgUrl={img} {height} />-->
<!-- {/if}-->
</div>
<style lang="postcss">

View File

@ -6,7 +6,7 @@
minZoom?: number;
maxZoom?: number;
zoomStep?: number;
img: RawImageData;
imgUrl: string;
height?: string | number;
};
@ -14,7 +14,7 @@
minZoom = 0.5,
maxZoom = 10,
zoomStep = 0.1,
img,
imgUrl,
height,
}: ZoomableProps = $props();
@ -134,7 +134,7 @@
>
<img
alt="rendered-page"
src={img.toObjectUrl()}
src={imgUrl}
style:max-height={height + "px"}
/>
</div>

View File

@ -1,14 +1,40 @@
import {fromBase64Util} from "pdfjs-dist/types/src/shared/util";
import {getDocument} from "pdfjs-dist";
import {getDocument, GlobalWorkerOptions, type PDFDocumentProxy} from "pdfjs-dist";
import type {RenderParameters} from "pdfjs-dist/types/src/display/api";
export default class Document {
doc: Promise<PDFDocumentProxy>;
constructor(bytesB64: string) {
let bytes = fromBase64Util(bytesB64)
let loadingTask = getDocument({ data: bytes });
let doc = await loadingTask.promise
let dict: Dict = new Dict()
GlobalWorkerOptions.workerSrc = new URL('/pdf.worker.min.mjs', import.meta.url).toString();
let loadingTask = getDocument({ data: atob(bytesB64) });
this.doc = loadingTask.promise
}
public async render_page(num: number, canvas: HTMLCanvasElement) {
console.log("render");
let doc = await this.doc;
let page = await doc.getPage(num);
const outputScale = window.devicePixelRatio || 1;
const scale = 1.5;
let viewport = page.getViewport({ scale: scale, });
canvas.height = viewport.height;
canvas.width = viewport.width;
canvas.style.width = Math.floor(canvas.width) + "px";
canvas.style.height = Math.floor(canvas.height) + "px";
const context = canvas.getContext("2d");
if (!context) return "";
const transform = [1, 0, 0, 1, 0, 0];
// viewport = page.getViewport({ scale: 0.7, offsetX: canvas.width / 4, offsetY: canvas.height / 4});
const renderContext: RenderParameters = {
canvasContext: context,
transform: transform,
viewport: viewport,
};
let renderTask = page.render(renderContext);
context.fillStyle = '#ffffff';
context.fillRect(viewport.offsetX, viewport.offsetY, viewport.width, viewport.height);
await renderTask.promise
}
}

View File

@ -33,7 +33,6 @@ export default class FileViewState {
public xref_entries: XRefEntry[] = $state([]);
public treeState: TreeViewState | undefined = $state();
pageViewStates: Map<number, PageViewState> = new Map();
public pageViewState: PageViewState | undefined = $state();
public notifications: ForgeNotification[] = $state([]);
@ -84,11 +83,6 @@ export default class FileViewState {
this.loadXrefEntries();
this.treeState?.reload();
await this.selectPath(this.path);
this.pageViewStates.clear();
if (this.pageViewState) {
this.pageViewState.reload()
this.pageViewStates.set(this.pageViewState.page_num, this.pageViewState);
}
}
public logError(message: string) {
@ -256,21 +250,15 @@ export default class FileViewState {
}
private async reloadPageState(page_num: number) {
this.pageViewState = new PageViewState(page_num, this);
this.pageViewStates.set(page_num, this.pageViewState);
public getCurrentPageNumber() : number | undefined {
if (!(this.container_prim && this.container_prim.isPage())) return undefined;
return this.container_prim.getPageNumber();
}
private async loadPageState() {
if (!(this.container_prim && this.container_prim.isPage())) return;
let page_num = this.container_prim.getPageNumber();
if (this.pageViewState && this.pageViewState.page_num === page_num) return;
this.pageViewState = this.pageViewStates.get(page_num);
if (!this.pageViewState) {
await this.reloadPageState(page_num);
let page_num = this.getCurrentPageNumber();
if (page_num) {
await this.pageViewState?.load(page_num);
}
}
}

View File

@ -1,64 +1,67 @@
import ContentModel from "./ContentModel.svelte";
import {invoke} from "@tauri-apps/api/core";
import {RawImageData} from "./RawImageData";
import type FileViewState from "./FileViewState.svelte";
import {Mutex} from "async-mutex";
export class PageViewState {
file_id: string;
page_num: number;
fState: FileViewState;
img_data: RawImageData | undefined = $state();
display_lock = new Mutex();
contents: ContentModel | undefined = $state(undefined);
constructor(page_num: number, fState: FileViewState) {
render: () => void;
constructor(fState: FileViewState, render: () => void) {
this.file_id = fState.file.id;
this.page_num = page_num;
this.fState = fState;
this.load();
this.render = render;
const num = fState.getCurrentPageNumber();
if (num) {
this.load(num);
}
}
private logError(err: string) {
this.fState.logError(err);
}
public async reload() {
await this.load();
}
public async load() {
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + this.page_num})
public async load(page_num: number) {
console.log("load", page_num)
this.render();
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + page_num})
.then((result) => {
this.contents = undefined;
this.contents = new ContentModel(result.parts);
this.loadImage();
})
.catch(this.logError);
}
public async loadImage() {
this.img_data?.dispose();
this.img_data = undefined;
let result = await invoke<ArrayBuffer>("get_page_by_num", {
id: this.file_id,
num: this.page_num,
})
.catch(this.logError);
if (result) {
this.img_data = new RawImageData(result);
}
public async loadImage(display: HTMLCanvasElement) {
console.log("load", display)
this.display_lock.acquire().then(release => {
try {
let page_num = this.fState.getCurrentPageNumber();
if (!page_num) return;
this.fState.renderer.render_page(page_num, display)
} finally {
release()
}
}
)
this.display_lock.release();
}
public handleSave(newData: string) {
let page_num = this.fState.getCurrentPageNumber();
if (!page_num) return;
let contents = ContentModel.fromDisplay(newData);
console.log(contents);
invoke<ContentModel>("update_contents", {id: this.file_id, path: "Page" + this.page_num, contents: contents})
invoke<ContentModel>("update_contents", {id: this.file_id, path: "Page" + page_num, contents: contents})
.then((result) => {
this.contents = undefined;
this.fState.reload();
})
.catch(this.logError);
}
}

View File

@ -54,7 +54,7 @@ export default class TreeViewState {
result.push(entry);
}
totalIndex += 1;
if (totalIndex >= end) {
if (totalIndex >= end + 5) {
return [result, stickies];
}
}

21
static/pdf.min.mjs Normal file

File diff suppressed because one or more lines are too long

21
static/pdf.worker.min.mjs Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,6 @@ 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: [