securityos/components/apps/PDF/usePDF.ts

160 lines
4.5 KiB
TypeScript

import type { MetadataInfo } from "components/apps/PDF/types";
import useTitle from "components/system/Window/useTitle";
import { useFileSystem } from "contexts/fileSystem";
import { useProcesses } from "contexts/process";
import { basename } from "path";
import type * as PdfjsLib from "pdfjs-dist";
import type { PDFDocumentProxy } from "pdfjs-dist/types/src/display/api";
import { useCallback, useEffect, useState } from "react";
import {
BASE_2D_CONTEXT_OPTIONS,
DEFAULT_SCROLLBAR_WIDTH,
} from "utils/constants";
import { loadFiles } from "utils/functions";
export const scales = [
0.25, 0.33, 0.5, 0.67, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4,
5,
];
const getInitialScale = (windowWidth = 0, canvasWidth = 0): number => {
const adjustedWindowWidth = windowWidth - DEFAULT_SCROLLBAR_WIDTH;
if (adjustedWindowWidth >= canvasWidth) return 1;
const minScale = adjustedWindowWidth / canvasWidth;
const minScaleIndex = scales.findIndex((scale) => scale >= minScale);
return minScaleIndex > 0 ? scales[minScaleIndex - 1] : 1;
};
const usePDF = (
id: string,
url: string,
containerRef: React.MutableRefObject<HTMLDivElement | null>,
setLoading: React.Dispatch<React.SetStateAction<boolean>>
): void => {
const { readFile } = useFileSystem();
const { argument, processes: { [id]: process } = {} } = useProcesses();
const { libs = [], scale } = process || {};
const [pages, setPages] = useState<HTMLCanvasElement[]>([]);
const renderPage = useCallback(
async (
pageNumber: number,
doc: PDFDocumentProxy
): Promise<HTMLCanvasElement> => {
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext(
"2d",
BASE_2D_CONTEXT_OPTIONS
) as CanvasRenderingContext2D;
const page = await doc.getPage(pageNumber);
let viewport: PdfjsLib.PageViewport;
if (scale) {
viewport = page.getViewport({ scale });
} else {
const pageWidth = page.getViewport().viewBox[2];
const initialScale = getInitialScale(
containerRef.current?.clientWidth,
pageWidth
);
const { info } = await doc.getMetadata();
argument(id, "scale", initialScale);
if ((info as MetadataInfo)?.Title) {
argument(id, "subTitle", (info as MetadataInfo).Title);
}
viewport = page.getViewport({ scale: initialScale });
}
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({ canvasContext, viewport }).promise;
return canvas;
},
[argument, containerRef, id, scale]
);
const { prependFileToTitle } = useTitle(id);
const renderPages = useCallback(async (): Promise<void> => {
if (window.pdfjsLib && url && containerRef.current) {
setLoading(true);
const doc = await window.pdfjsLib.getDocument(await readFile(url))
.promise;
argument(id, "count", doc.numPages);
setPages(
await Promise.all(
Array.from({ length: doc.numPages }).map((_, i) =>
renderPage(i + 1, doc)
)
)
);
prependFileToTitle(basename(url));
}
setLoading(false);
}, [
argument,
containerRef,
id,
prependFileToTitle,
readFile,
renderPage,
setLoading,
url,
]);
useEffect(() => {
loadFiles(libs).then(() => {
if (window.pdfjsLib) {
window.pdfjsLib.GlobalWorkerOptions.workerSrc =
"/Program Files/PDF.js/pdf.worker.js";
renderPages();
}
});
}, [libs, renderPages]);
useEffect(() => {
if (pages.length > 0) {
const ol = containerRef.current?.querySelector(
"#pages"
) as HTMLOListElement;
if (ol) {
[...ol.children].forEach((li) => li.remove());
pages.forEach((page, pageNumber) => {
const li = document.createElement("li");
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
argument(id, "page", pageNumber + 1);
}
});
},
{
root: containerRef.current,
threshold: 0.4,
}
);
li.append(page);
ol.append(li);
observer.observe(li);
});
}
}
}, [argument, containerRef, id, pages]);
};
export default usePDF;