|
|
|
@ -4,6 +4,8 @@ import "react-pdf/dist/esm/Page/AnnotationLayer.css";
|
|
|
|
import "react-pdf/dist/esm/Page/TextLayer.css";
|
|
|
|
import "react-pdf/dist/esm/Page/TextLayer.css";
|
|
|
|
import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft";
|
|
|
|
import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft";
|
|
|
|
import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
|
|
|
|
import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
|
|
|
|
|
|
|
|
import ZoomInIcon from "@mui/icons-material/ZoomIn";
|
|
|
|
|
|
|
|
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
|
|
|
|
import { Box, IconButton } from "@mui/material";
|
|
|
|
import { Box, IconButton } from "@mui/material";
|
|
|
|
import type {
|
|
|
|
import type {
|
|
|
|
CustomTextRenderer,
|
|
|
|
CustomTextRenderer,
|
|
|
|
@ -30,7 +32,7 @@ export default function PDFViewer({
|
|
|
|
}: PDFViewerProps) {
|
|
|
|
}: PDFViewerProps) {
|
|
|
|
const [numPages, setNumPages] = useState<number | null>(null);
|
|
|
|
const [numPages, setNumPages] = useState<number | null>(null);
|
|
|
|
const [pageNumber, setPageNumber] = useState(currentPage || 1);
|
|
|
|
const [pageNumber, setPageNumber] = useState(currentPage || 1);
|
|
|
|
const [containerWidth, setContainerWidth] = useState<number | null>(null);
|
|
|
|
const [baseWidth, setBaseWidth] = useState<number | null>(null);
|
|
|
|
const [pdfKey, setPdfKey] = useState(Date.now());
|
|
|
|
const [pdfKey, setPdfKey] = useState(Date.now());
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const [posHighlight, setPosHighlight] = useState<string[]>([]);
|
|
|
|
const [posHighlight, setPosHighlight] = useState<string[]>([]);
|
|
|
|
@ -38,6 +40,7 @@ export default function PDFViewer({
|
|
|
|
const [textContent, setTextContent] = useState<
|
|
|
|
const [textContent, setTextContent] = useState<
|
|
|
|
{ posKey: string; text: string; i: number }[]
|
|
|
|
{ posKey: string; text: string; i: number }[]
|
|
|
|
>([]);
|
|
|
|
>([]);
|
|
|
|
|
|
|
|
const [zoomLevel, setZoomLevel] = useState(1.0);
|
|
|
|
|
|
|
|
|
|
|
|
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
|
|
|
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
|
|
|
setNumPages(numPages);
|
|
|
|
setNumPages(numPages);
|
|
|
|
@ -46,7 +49,9 @@ export default function PDFViewer({
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
const updateWidth = () => {
|
|
|
|
const updateWidth = () => {
|
|
|
|
if (containerRef.current) {
|
|
|
|
if (containerRef.current) {
|
|
|
|
setContainerWidth(containerRef.current.offsetWidth);
|
|
|
|
if (!baseWidth) {
|
|
|
|
|
|
|
|
setBaseWidth(containerRef.current.offsetWidth);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@ -112,8 +117,9 @@ export default function PDFViewer({
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (positions.length === 0) {
|
|
|
|
if (positions.length === 0) {
|
|
|
|
let cumulativeText = '';
|
|
|
|
let cumulativeText = "";
|
|
|
|
const textBoundaries: { start: number; end: number; index: number }[] = [];
|
|
|
|
const textBoundaries: { start: number; end: number; index: number }[] =
|
|
|
|
|
|
|
|
[];
|
|
|
|
|
|
|
|
|
|
|
|
textContent.forEach((item, index) => {
|
|
|
|
textContent.forEach((item, index) => {
|
|
|
|
const start = cumulativeText.length;
|
|
|
|
const start = cumulativeText.length;
|
|
|
|
@ -128,7 +134,7 @@ export default function PDFViewer({
|
|
|
|
while (searchIndex !== -1) {
|
|
|
|
while (searchIndex !== -1) {
|
|
|
|
const endIndex = searchIndex + normalizedSearch.length;
|
|
|
|
const endIndex = searchIndex + normalizedSearch.length;
|
|
|
|
|
|
|
|
|
|
|
|
textBoundaries.forEach(boundary => {
|
|
|
|
textBoundaries.forEach((boundary) => {
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
(boundary.start <= searchIndex && searchIndex < boundary.end) || // Search starts in this item
|
|
|
|
(boundary.start <= searchIndex && searchIndex < boundary.end) || // Search starts in this item
|
|
|
|
(boundary.start < endIndex && endIndex <= boundary.end) || // Search ends in this item
|
|
|
|
(boundary.start < endIndex && endIndex <= boundary.end) || // Search ends in this item
|
|
|
|
@ -139,16 +145,19 @@ export default function PDFViewer({
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
searchIndex = lowerCumulative.indexOf(normalizedSearch, searchIndex + 1);
|
|
|
|
searchIndex = lowerCumulative.indexOf(
|
|
|
|
|
|
|
|
normalizedSearch,
|
|
|
|
|
|
|
|
searchIndex + 1,
|
|
|
|
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return positions.sort((a, b) => a - b);
|
|
|
|
return positions.sort((a, b) => a - b);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
highlight
|
|
|
|
highlight
|
|
|
|
.filter(h => h.page === pageNumber)
|
|
|
|
.filter((h) => h.page === pageNumber)
|
|
|
|
.forEach(highlightItem => {
|
|
|
|
.forEach((highlightItem) => {
|
|
|
|
const positions = findTextPositions(highlightItem.text);
|
|
|
|
const positions = findTextPositions(highlightItem.text);
|
|
|
|
positions.forEach(pos => {
|
|
|
|
positions.forEach((pos) => {
|
|
|
|
if (pos >= 0 && pos < textContent.length) {
|
|
|
|
if (pos >= 0 && pos < textContent.length) {
|
|
|
|
tmpPos.push(textContent[pos].posKey);
|
|
|
|
tmpPos.push(textContent[pos].posKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -158,7 +167,7 @@ export default function PDFViewer({
|
|
|
|
if (focusHighlight?.page === pageNumber && focusHighlight.text) {
|
|
|
|
if (focusHighlight?.page === pageNumber && focusHighlight.text) {
|
|
|
|
const positions = findTextPositions(focusHighlight.text);
|
|
|
|
const positions = findTextPositions(focusHighlight.text);
|
|
|
|
|
|
|
|
|
|
|
|
positions.forEach(pos => {
|
|
|
|
positions.forEach((pos) => {
|
|
|
|
if (pos >= 0 && pos < textContent.length) {
|
|
|
|
if (pos >= 0 && pos < textContent.length) {
|
|
|
|
tmpPosHighlight.push(textContent[pos].posKey);
|
|
|
|
tmpPosHighlight.push(textContent[pos].posKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -186,16 +195,30 @@ export default function PDFViewer({
|
|
|
|
justifyContent="center"
|
|
|
|
justifyContent="center"
|
|
|
|
alignItems="center"
|
|
|
|
alignItems="center"
|
|
|
|
width="100%"
|
|
|
|
width="100%"
|
|
|
|
height="auto"
|
|
|
|
maxWidth="850px"
|
|
|
|
|
|
|
|
margin="0 auto"
|
|
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
|
|
backgroundColor: "#f5f5f5",
|
|
|
|
|
|
|
|
borderRadius: 2,
|
|
|
|
|
|
|
|
boxShadow: 2,
|
|
|
|
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Box
|
|
|
|
<Box
|
|
|
|
ref={containerRef}
|
|
|
|
ref={containerRef}
|
|
|
|
|
|
|
|
width="100%"
|
|
|
|
|
|
|
|
// width="700px"
|
|
|
|
|
|
|
|
height="500px"
|
|
|
|
sx={{
|
|
|
|
sx={{
|
|
|
|
width: "100%",
|
|
|
|
backgroundColor: "transparent",
|
|
|
|
height: "auto",
|
|
|
|
border: "none",
|
|
|
|
|
|
|
|
borderRadius: 0,
|
|
|
|
|
|
|
|
boxShadow: "none",
|
|
|
|
|
|
|
|
overflow: "auto",
|
|
|
|
display: "flex",
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "center",
|
|
|
|
justifyContent: "center",
|
|
|
|
alignItems: "center",
|
|
|
|
alignItems: "center",
|
|
|
|
|
|
|
|
marginTop: 2,
|
|
|
|
|
|
|
|
marginBottom: 2,
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Document
|
|
|
|
<Document
|
|
|
|
@ -207,10 +230,10 @@ export default function PDFViewer({
|
|
|
|
}
|
|
|
|
}
|
|
|
|
onSourceError={(error) => console.error("Ungültige PDF:", error)}
|
|
|
|
onSourceError={(error) => console.error("Ungültige PDF:", error)}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
{containerWidth && (
|
|
|
|
{baseWidth && (
|
|
|
|
<Page
|
|
|
|
<Page
|
|
|
|
pageNumber={pageNumber}
|
|
|
|
pageNumber={pageNumber}
|
|
|
|
width={containerWidth * 0.98}
|
|
|
|
width={baseWidth * 0.98 * zoomLevel}
|
|
|
|
customTextRenderer={textRenderer}
|
|
|
|
customTextRenderer={textRenderer}
|
|
|
|
onGetTextSuccess={onGetTextSuccess}
|
|
|
|
onGetTextSuccess={onGetTextSuccess}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
@ -225,6 +248,13 @@ export default function PDFViewer({
|
|
|
|
gap={1}
|
|
|
|
gap={1}
|
|
|
|
p={1}
|
|
|
|
p={1}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
|
|
|
|
<IconButton
|
|
|
|
|
|
|
|
disabled={zoomLevel <= 0.3}
|
|
|
|
|
|
|
|
onClick={() => setZoomLevel((z) => Math.max(0.3, z - 0.1))}
|
|
|
|
|
|
|
|
title="Verkleinern"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<ZoomOutIcon fontSize="large" />
|
|
|
|
|
|
|
|
</IconButton>
|
|
|
|
<IconButton
|
|
|
|
<IconButton
|
|
|
|
disabled={pageNumber <= 1}
|
|
|
|
disabled={pageNumber <= 1}
|
|
|
|
onClick={() => handlePageChange(pageNumber - 1)}
|
|
|
|
onClick={() => handlePageChange(pageNumber - 1)}
|
|
|
|
@ -240,6 +270,12 @@ export default function PDFViewer({
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<ArrowCircleRightIcon fontSize="large" />
|
|
|
|
<ArrowCircleRightIcon fontSize="large" />
|
|
|
|
</IconButton>
|
|
|
|
</IconButton>
|
|
|
|
|
|
|
|
<IconButton
|
|
|
|
|
|
|
|
onClick={() => setZoomLevel((z) => Math.min(3, z + 0.1))}
|
|
|
|
|
|
|
|
title="Vergrößern"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<ZoomInIcon fontSize="large" />
|
|
|
|
|
|
|
|
</IconButton>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|