rendering with pdf.js working
This commit is contained in:
parent
34d2c05f21
commit
0e1e7c359b
@ -7,8 +7,15 @@
|
|||||||
import ContentModel from "../models/ContentModel.svelte";
|
import ContentModel from "../models/ContentModel.svelte";
|
||||||
|
|
||||||
let {fState, height}: {fState: FileViewState, height: number} = $props();
|
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 contents: ContentModel | undefined = $derived(state?.contents)
|
||||||
|
let display: HTMLCanvasElement;
|
||||||
|
fState.pageViewState = state;
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
console.log("render", display)
|
||||||
|
state.loadImage(display);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{#if state}
|
{#if state}
|
||||||
@ -19,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane minSize={1}>
|
<Pane minSize={1}>
|
||||||
<RenderedPageView img={state.img_data} {height}></RenderedPageView>
|
<RenderedPageView bind:img={display} {height}></RenderedPageView>
|
||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
{:else }
|
{:else }
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ZoomableContainer from "./ZoomableContainer.svelte";
|
|
||||||
import { RefreshOutline } from "flowbite-svelte-icons";
|
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();
|
$props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="page-container" style:height={height + "px"}>
|
<div class="page-container" style:height={height + "px"}>
|
||||||
{#if !img}
|
<!--{#if !img}-->
|
||||||
<div class="loading-container">
|
<!-- <div class="loading-container">-->
|
||||||
<RefreshOutline class="animate-spin" size="xl" />
|
<!-- <RefreshOutline class="animate-spin" size="xl" />-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
{:else}
|
<!--{:else}-->
|
||||||
<ZoomableContainer {img} {height} />
|
<canvas bind:this={img} class="bg-forge-dark">
|
||||||
{/if}
|
|
||||||
|
</canvas>
|
||||||
|
<!-- <ZoomableContainer imgUrl={img} {height} />-->
|
||||||
|
<!-- {/if}-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
minZoom?: number;
|
minZoom?: number;
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
zoomStep?: number;
|
zoomStep?: number;
|
||||||
img: RawImageData;
|
imgUrl: string;
|
||||||
height?: string | number;
|
height?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,7 +14,7 @@
|
|||||||
minZoom = 0.5,
|
minZoom = 0.5,
|
||||||
maxZoom = 10,
|
maxZoom = 10,
|
||||||
zoomStep = 0.1,
|
zoomStep = 0.1,
|
||||||
img,
|
imgUrl,
|
||||||
height,
|
height,
|
||||||
}: ZoomableProps = $props();
|
}: ZoomableProps = $props();
|
||||||
|
|
||||||
@ -134,7 +134,7 @@
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="rendered-page"
|
alt="rendered-page"
|
||||||
src={img.toObjectUrl()}
|
src={imgUrl}
|
||||||
style:max-height={height + "px"}
|
style:max-height={height + "px"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +1,40 @@
|
|||||||
import {fromBase64Util} from "pdfjs-dist/types/src/shared/util";
|
import {getDocument, GlobalWorkerOptions, type PDFDocumentProxy} from "pdfjs-dist";
|
||||||
import {getDocument} from "pdfjs-dist";
|
import type {RenderParameters} from "pdfjs-dist/types/src/display/api";
|
||||||
|
|
||||||
export default class Document {
|
export default class Document {
|
||||||
|
|
||||||
|
doc: Promise<PDFDocumentProxy>;
|
||||||
constructor(bytesB64: string) {
|
constructor(bytesB64: string) {
|
||||||
let bytes = fromBase64Util(bytesB64)
|
GlobalWorkerOptions.workerSrc = new URL('/pdf.worker.min.mjs', import.meta.url).toString();
|
||||||
let loadingTask = getDocument({ data: bytes });
|
let loadingTask = getDocument({ data: atob(bytesB64) });
|
||||||
let doc = await loadingTask.promise
|
this.doc = loadingTask.promise
|
||||||
let dict: Dict = new Dict()
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -33,7 +33,6 @@ export default class FileViewState {
|
|||||||
public xref_entries: XRefEntry[] = $state([]);
|
public xref_entries: XRefEntry[] = $state([]);
|
||||||
|
|
||||||
public treeState: TreeViewState | undefined = $state();
|
public treeState: TreeViewState | undefined = $state();
|
||||||
pageViewStates: Map<number, PageViewState> = new Map();
|
|
||||||
public pageViewState: PageViewState | undefined = $state();
|
public pageViewState: PageViewState | undefined = $state();
|
||||||
|
|
||||||
public notifications: ForgeNotification[] = $state([]);
|
public notifications: ForgeNotification[] = $state([]);
|
||||||
@ -84,11 +83,6 @@ export default class FileViewState {
|
|||||||
this.loadXrefEntries();
|
this.loadXrefEntries();
|
||||||
this.treeState?.reload();
|
this.treeState?.reload();
|
||||||
await this.selectPath(this.path);
|
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) {
|
public logError(message: string) {
|
||||||
@ -256,21 +250,15 @@ export default class FileViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async reloadPageState(page_num: number) {
|
public getCurrentPageNumber() : number | undefined {
|
||||||
|
if (!(this.container_prim && this.container_prim.isPage())) return undefined;
|
||||||
this.pageViewState = new PageViewState(page_num, this);
|
return this.container_prim.getPageNumber();
|
||||||
this.pageViewStates.set(page_num, this.pageViewState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPageState() {
|
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;
|
let page_num = this.getCurrentPageNumber();
|
||||||
this.pageViewState = this.pageViewStates.get(page_num);
|
if (page_num) {
|
||||||
|
await this.pageViewState?.load(page_num);
|
||||||
if (!this.pageViewState) {
|
|
||||||
await this.reloadPageState(page_num);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,64 +1,67 @@
|
|||||||
import ContentModel from "./ContentModel.svelte";
|
import ContentModel from "./ContentModel.svelte";
|
||||||
import {invoke} from "@tauri-apps/api/core";
|
import {invoke} from "@tauri-apps/api/core";
|
||||||
import {RawImageData} from "./RawImageData";
|
|
||||||
import type FileViewState from "./FileViewState.svelte";
|
import type FileViewState from "./FileViewState.svelte";
|
||||||
|
import {Mutex} from "async-mutex";
|
||||||
|
|
||||||
export class PageViewState {
|
export class PageViewState {
|
||||||
file_id: string;
|
file_id: string;
|
||||||
page_num: number;
|
|
||||||
fState: FileViewState;
|
fState: FileViewState;
|
||||||
|
|
||||||
img_data: RawImageData | undefined = $state();
|
|
||||||
|
display_lock = new Mutex();
|
||||||
contents: ContentModel | undefined = $state(undefined);
|
contents: ContentModel | undefined = $state(undefined);
|
||||||
|
render: () => void;
|
||||||
constructor(page_num: number, fState: FileViewState) {
|
constructor(fState: FileViewState, render: () => void) {
|
||||||
this.file_id = fState.file.id;
|
this.file_id = fState.file.id;
|
||||||
this.page_num = page_num;
|
|
||||||
this.fState = fState;
|
this.fState = fState;
|
||||||
|
this.render = render;
|
||||||
this.load();
|
const num = fState.getCurrentPageNumber();
|
||||||
|
if (num) {
|
||||||
|
this.load(num);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private logError(err: string) {
|
private logError(err: string) {
|
||||||
this.fState.logError(err);
|
this.fState.logError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async reload() {
|
|
||||||
await this.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async load() {
|
public async load(page_num: number) {
|
||||||
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + this.page_num})
|
console.log("load", page_num)
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + page_num})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.contents = undefined;
|
|
||||||
this.contents = new ContentModel(result.parts);
|
this.contents = new ContentModel(result.parts);
|
||||||
this.loadImage();
|
|
||||||
})
|
})
|
||||||
.catch(this.logError);
|
.catch(this.logError);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadImage() {
|
public async loadImage(display: HTMLCanvasElement) {
|
||||||
this.img_data?.dispose();
|
console.log("load", display)
|
||||||
this.img_data = undefined;
|
this.display_lock.acquire().then(release => {
|
||||||
let result = await invoke<ArrayBuffer>("get_page_by_num", {
|
try {
|
||||||
id: this.file_id,
|
let page_num = this.fState.getCurrentPageNumber();
|
||||||
num: this.page_num,
|
if (!page_num) return;
|
||||||
})
|
this.fState.renderer.render_page(page_num, display)
|
||||||
.catch(this.logError);
|
} finally {
|
||||||
if (result) {
|
release()
|
||||||
this.img_data = new RawImageData(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
this.display_lock.release();
|
||||||
|
}
|
||||||
|
|
||||||
public handleSave(newData: string) {
|
public handleSave(newData: string) {
|
||||||
|
let page_num = this.fState.getCurrentPageNumber();
|
||||||
|
if (!page_num) return;
|
||||||
let contents = ContentModel.fromDisplay(newData);
|
let contents = ContentModel.fromDisplay(newData);
|
||||||
console.log(contents);
|
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) => {
|
.then((result) => {
|
||||||
this.contents = undefined;
|
this.contents = undefined;
|
||||||
this.fState.reload();
|
this.fState.reload();
|
||||||
})
|
})
|
||||||
.catch(this.logError);
|
.catch(this.logError);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ export default class TreeViewState {
|
|||||||
result.push(entry);
|
result.push(entry);
|
||||||
}
|
}
|
||||||
totalIndex += 1;
|
totalIndex += 1;
|
||||||
if (totalIndex >= end) {
|
if (totalIndex >= end + 5) {
|
||||||
return [result, stickies];
|
return [result, stickies];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
static/pdf.min.mjs
Normal file
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
21
static/pdf.worker.min.mjs
Normal file
File diff suppressed because one or more lines are too long
@ -3,7 +3,6 @@ import { sveltekit } from "@sveltejs/kit/vite";
|
|||||||
// import monacoEditorPlugin from 'vite-plugin-monaco-editor';
|
// import monacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||||
// @ts-expect-error process is a nodejs global
|
// @ts-expect-error process is a nodejs global
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(async () => ({
|
export default defineConfig(async () => ({
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user