@nu-art/ts-pdf-frontend 0.400.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ import type { CSSProperties } from 'react';
2
+ import { Module } from '@nu-art/ts-common';
3
+ import { PDFDocumentProxy } from 'pdfjs-dist';
4
+ export interface PDFEngine {
5
+ loadFromBuffer(buffer: ArrayBuffer): Promise<PDFDocumentProxy>;
6
+ }
7
+ export declare class ModuleFE_PDF_Class extends Module {
8
+ private readonly engine;
9
+ constructor(engine?: PDFEngine);
10
+ protected init(): void;
11
+ appendCanvas(parent: HTMLElement, style?: CSSProperties): CanvasRenderingContext2D;
12
+ appendTextLayer(parent: HTMLElement, style?: CSSProperties): HTMLDivElement;
13
+ /** Render a 1-based page index. Returns the viewport used. */
14
+ renderPage(canvasContext: CanvasRenderingContext2D, pdfFile: PDFDocumentProxy, pageNumber: number, textLayerDiv?: HTMLDivElement, scale?: number): Promise<import("pdfjs-dist/types/src/display/display_utils.js").PageViewport>;
15
+ loadFromFile(file: File): Promise<PDFDocumentProxy>;
16
+ loadFromUrl(url: string, useFetch?: boolean): Promise<PDFDocumentProxy>;
17
+ loadFromBuffer(buffer: ArrayBuffer): Promise<import("pdfjs-dist/types/src/display/api.js").PDFDocumentProxy>;
18
+ private _fetchAsArrayBuffer;
19
+ private _xhrArrayBuffer;
20
+ }
21
+ export declare const ModuleFE_PDF: ModuleFE_PDF_Class;
@@ -0,0 +1,124 @@
1
+ /*
2
+ * ModuleFE_PDF (ESM-compatible) – PDF.js v4
3
+ * - Directly uses official PDF.js v4 types (no redundant aliases)
4
+ * - Worker configured once at init
5
+ * - Optional TextLayer overlay using v4 `TextLayer` helper
6
+ */
7
+ import { _keys, Module } from '@nu-art/ts-common';
8
+ // ---- PDF.js v4 imports ----
9
+ // NOTE: The worker import with `?url` works in Vite/Webpack/Rspack. Adjust if your bundler differs.
10
+ import * as pdfjsLib from 'pdfjs-dist';
11
+ const workerUrl = new URL('pdfjs-dist/build/pdf.worker.mjs', import.meta.url).toString();
12
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
13
+ class PDFJSEngine {
14
+ _workerSet = false;
15
+ constructor() {
16
+ this.ensureWorker();
17
+ }
18
+ ensureWorker() {
19
+ if (this._workerSet)
20
+ return;
21
+ pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;
22
+ this._workerSet = true;
23
+ }
24
+ async loadFromBuffer(buffer) {
25
+ this.ensureWorker();
26
+ const task = pdfjsLib.getDocument({ data: buffer });
27
+ return (await task.promise);
28
+ }
29
+ }
30
+ // ---- Module implementation ----
31
+ export class ModuleFE_PDF_Class extends Module {
32
+ engine;
33
+ constructor(engine = new PDFJSEngine()) {
34
+ super();
35
+ this.engine = engine;
36
+ }
37
+ init() {
38
+ }
39
+ appendCanvas(parent, style) {
40
+ const canvas = document.createElement('canvas');
41
+ const _style = { height: '100%', width: '100%', ...style };
42
+ // @ts-ignore
43
+ _keys(_style).forEach(key => (canvas.style[key] = _style[key]));
44
+ parent.appendChild(canvas);
45
+ const ctx = canvas.getContext('2d');
46
+ if (!ctx)
47
+ throw new Error('Failed to get 2D context for PDF canvas');
48
+ return ctx;
49
+ }
50
+ appendTextLayer(parent, style) {
51
+ const textLayer = document.createElement('div');
52
+ const _style = { height: '100%', width: '100%', position: 'absolute', inset: 0, ...style };
53
+ // @ts-ignore
54
+ _keys(_style).forEach(key => (textLayer.style[key] = _style[key]));
55
+ textLayer.classList.add('pdf-text-layer');
56
+ parent.appendChild(textLayer);
57
+ return textLayer;
58
+ }
59
+ /** Render a 1-based page index. Returns the viewport used. */
60
+ async renderPage(canvasContext, pdfFile, pageNumber, textLayerDiv, scale = 1) {
61
+ if (pageNumber < 1 || pageNumber > pdfFile.numPages)
62
+ throw new Error(`Page ${pageNumber} out of bounds (1..${pdfFile.numPages})`);
63
+ const page = await pdfFile.getPage(pageNumber);
64
+ if (!page) {
65
+ this.logError('PDF page is undefined');
66
+ return { width: 0, height: 0 };
67
+ }
68
+ const viewport = page.getViewport({ scale });
69
+ canvasContext.canvas.width = viewport.width;
70
+ canvasContext.canvas.height = viewport.height;
71
+ const renderTask = page.render({
72
+ canvasContext,
73
+ canvas: canvasContext.canvas, // <-- required in v4 types
74
+ viewport
75
+ });
76
+ await renderTask.promise;
77
+ if (textLayerDiv) {
78
+ const textContentStream = await page.streamTextContent();
79
+ const layer = new pdfjsLib.TextLayer({
80
+ textContentSource: textContentStream,
81
+ container: textLayerDiv,
82
+ viewport,
83
+ });
84
+ await Promise.resolve(layer.render());
85
+ }
86
+ return viewport;
87
+ }
88
+ async loadFromFile(file) {
89
+ this.logDebug(`loading pdf from file: ${file.name}`);
90
+ const buffer = await file.arrayBuffer();
91
+ return this.engine.loadFromBuffer(buffer);
92
+ }
93
+ async loadFromUrl(url, useFetch = true) {
94
+ this.logDebug(`loading pdf from url: ${url}`);
95
+ const buffer = useFetch ? await this._fetchAsArrayBuffer(url) : await this._xhrArrayBuffer(url);
96
+ return this.engine.loadFromBuffer(buffer);
97
+ }
98
+ async loadFromBuffer(buffer) {
99
+ this.logDebug('loading pdf from ArrayBuffer');
100
+ return this.engine.loadFromBuffer(buffer);
101
+ }
102
+ async _fetchAsArrayBuffer(url) {
103
+ const res = await fetch(url);
104
+ if (!res.ok)
105
+ throw new Error(`Failed to fetch PDF: ${res.status} ${res.statusText}`);
106
+ return await res.arrayBuffer();
107
+ }
108
+ async _xhrArrayBuffer(url) {
109
+ return await new Promise((resolve, reject) => {
110
+ const request = new XMLHttpRequest();
111
+ request.open('GET', url);
112
+ request.responseType = 'arraybuffer';
113
+ request.onerror = () => reject(new Error('XHR network error while fetching PDF'));
114
+ request.onload = () => {
115
+ if (request.status >= 200 && request.status < 300)
116
+ resolve(request.response);
117
+ else
118
+ reject(new Error(`XHR failed: ${request.status}`));
119
+ };
120
+ request.send();
121
+ });
122
+ }
123
+ }
124
+ export const ModuleFE_PDF = new ModuleFE_PDF_Class();
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@nu-art/ts-pdf-frontend",
3
+ "version": "0.400.7",
4
+ "description": "ts-pdf - Express & Typescript based backend framework Frontend",
5
+ "keywords": [
6
+ "TacB0sS",
7
+ "infra",
8
+ "nu-art",
9
+ "thunderstorm",
10
+ "typescript",
11
+ "ts-pdf"
12
+ ],
13
+ "homepage": "https://github.com/nu-art-js/thunderstorm",
14
+ "bugs": {
15
+ "url": "https://github.com/nu-art-js/thunderstorm/issues"
16
+ },
17
+ "publishConfig": {
18
+ "directory": "dist",
19
+ "linkDirectory": true
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+ssh://git@github.com:nu-art-js/thunderstorm.git"
24
+ },
25
+ "license": "Apache-2.0",
26
+ "author": "TacB0sS",
27
+ "files": [
28
+ "**/*"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc"
32
+ },
33
+ "dependencies": {
34
+ "@nu-art/ts-common": "0.400.7",
35
+ "@nu-art/thunderstorm-frontend": "0.400.7",
36
+ "@nu-art/thunderstorm-shared": "0.400.7",
37
+ "react": "^18.0.0",
38
+ "pdf.js": "^0.1.0",
39
+ "pdfjs-dist": "5.4.149"
40
+ },
41
+ "devDependencies": {
42
+ "@types/pdfjs-dist": "2.10.378",
43
+ "@types/react": "^18.0.0",
44
+ "@types/chai": "^4.3.4",
45
+ "@types/mocha": "^10.0.1"
46
+ },
47
+ "unitConfig": {
48
+ "type": "typescript-lib"
49
+ },
50
+ "type": "module",
51
+ "exports": {
52
+ ".": {
53
+ "types": "./index.d.ts",
54
+ "import": "./index.js"
55
+ },
56
+ "./*": {
57
+ "types": "./*.d.ts",
58
+ "import": "./*.js"
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+ import './PDF_Renderer.scss';
3
+ import { ComponentAsync } from '@nu-art/thunderstorm-frontend/index';
4
+ import { PDFDocumentProxy } from 'pdfjs-dist';
5
+ type State = {
6
+ index: number;
7
+ pdfFile?: PDFDocumentProxy;
8
+ width: number;
9
+ height: number;
10
+ };
11
+ type Props = {
12
+ pageIndex?: number;
13
+ src: string;
14
+ };
15
+ export declare class PDF_Renderer extends ComponentAsync<Props, State> {
16
+ private ctx;
17
+ private textLayer;
18
+ constructor(props: Props);
19
+ protected deriveStateFromProps(nextProps: Props): Promise<{
20
+ index: number;
21
+ isLoading: boolean;
22
+ width: number;
23
+ height: number;
24
+ }>;
25
+ private loadPdf;
26
+ componentDidMount(): Promise<void>;
27
+ componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any): Promise<void>;
28
+ render(): import("react/jsx-runtime").JSX.Element;
29
+ renderState(): React.ReactNode;
30
+ private renderPage;
31
+ }
32
+ export {};
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import './PDF_Renderer.scss';
3
+ import { ModuleFE_PDF } from '../modules/ModuleFE_PDF.js';
4
+ import { ComponentAsync, TS_Loader } from '@nu-art/thunderstorm-frontend/index';
5
+ import { _logger_logException } from '@nu-art/ts-common';
6
+ export class PDF_Renderer extends ComponentAsync {
7
+ ctx;
8
+ textLayer;
9
+ constructor(props) {
10
+ super(props);
11
+ }
12
+ async deriveStateFromProps(nextProps) {
13
+ const state = {
14
+ index: nextProps.pageIndex || 1,
15
+ isLoading: false,
16
+ width: 500,
17
+ height: 500
18
+ };
19
+ return state;
20
+ }
21
+ async loadPdf(pdfSrc) {
22
+ this.setState({ isLoading: true });
23
+ try {
24
+ const pdfFile = await ModuleFE_PDF.loadFromUrl(pdfSrc);
25
+ this.setState({ pdfFile });
26
+ await this.renderPage(this.state.index);
27
+ this.setState({ isLoading: false });
28
+ }
29
+ catch (err) {
30
+ this.setState({ error: err.message, isLoading: false });
31
+ }
32
+ }
33
+ async componentDidMount() {
34
+ return this.loadPdf(this.props.src);
35
+ }
36
+ async componentDidUpdate(prevProps, prevState, snapshot) {
37
+ if (this.props.src !== prevProps.src) {
38
+ return this.loadPdf(this.props.src);
39
+ }
40
+ }
41
+ render() {
42
+ return _jsxs(_Fragment, { children: [_jsx("div", { className: 'pdf-renderer', ref: instance => {
43
+ if (!instance)
44
+ return;
45
+ if (!this.ctx)
46
+ this.ctx = ModuleFE_PDF.appendCanvas(instance, { position: 'absolute' });
47
+ }, children: this.renderState() }), _jsx("div", { ref: instance => {
48
+ if (!instance)
49
+ return;
50
+ if (!this.textLayer)
51
+ this.textLayer = instance;
52
+ } })] });
53
+ // new Array(this.state.pdfFile?._pdfInfo.numPages || 0)
54
+ // .fill(0)
55
+ // .map((val, index) => <div key={index} className="clickable" style={{margin: "5px", padding: "3px"}}
56
+ // onClick={async () => {
57
+ // await this.renderPage(index + 1);
58
+ // }}>{index + 1}</div>);
59
+ }
60
+ renderState() {
61
+ if (this.state.isLoading)
62
+ return _jsx(TS_Loader, {});
63
+ if (this.state.error)
64
+ return _jsx("div", { children: _logger_logException(this.state.error) });
65
+ }
66
+ async renderPage(index) {
67
+ const pdfFile = this.state.pdfFile;
68
+ if (!pdfFile) {
69
+ this.logError('cannot render pdfFile - undefined');
70
+ return;
71
+ }
72
+ this.logInfo(`isLoading page ${index}`);
73
+ const viewport = await ModuleFE_PDF.renderPage(this.ctx, pdfFile, index, this.textLayer);
74
+ this.logInfo(`loaded page ${index}`);
75
+ this.setState({ index, width: viewport.width, height: viewport.height });
76
+ }
77
+ }
@@ -0,0 +1,5 @@
1
+ .pdf-renderer {
2
+ position: relative;
3
+ width: 500px;
4
+ height: 500px;
5
+ }