Files
documenso/packages/lib/client-only/hooks/use-page-renderer.ts
T
2026-05-08 16:04:22 +10:00

86 lines
2.0 KiB
TypeScript

import Konva from 'konva';
import { useEffect, useMemo, useRef } from 'react';
import type { PageRenderData } from '../providers/envelope-render-provider';
type RenderFunction = (props: { stage: Konva.Stage; pageLayer: Konva.Layer }) => void;
export const usePageRenderer = (renderFunction: RenderFunction, pageData: PageRenderData) => {
const { pageWidth, pageHeight, scale, imageLoadingState, pageNumber } = pageData;
const konvaContainer = useRef<HTMLDivElement>(null);
const stage = useRef<Konva.Stage | null>(null);
const pageLayer = useRef<Konva.Layer | null>(null);
/**
* The raw viewport with no scaling. Basically the actual PDF size.
*/
const unscaledViewport = useMemo(
() => ({
scale: 1,
width: pageWidth,
height: pageHeight,
}),
[pageWidth, pageHeight],
);
/**
* The viewport scaled according to page width.
*/
const scaledViewport = useMemo(
() => ({
scale,
width: pageWidth * scale,
height: pageHeight * scale,
}),
[pageWidth, pageHeight, scale],
);
useEffect(() => {
const { current: container } = konvaContainer;
if (!container || imageLoadingState !== 'loaded') {
return;
}
stage.current = new Konva.Stage({
container,
id: `page-${pageNumber}`,
width: scaledViewport.width,
height: scaledViewport.height,
scale: {
x: scale,
y: scale,
},
});
// Create the main layer for interactive elements.
pageLayer.current = new Konva.Layer();
stage.current.add(pageLayer.current);
renderFunction({
stage: stage.current,
pageLayer: pageLayer.current,
});
void document.fonts.ready.then(() => {
pageLayer.current?.batchDraw();
});
return () => {
stage.current?.destroy();
stage.current = null;
};
}, [imageLoadingState, scaledViewport]);
return {
konvaContainer,
stage,
pageLayer,
unscaledViewport,
scaledViewport,
};
};