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(null); const stage = useRef(null); const pageLayer = useRef(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, }; };