|
import solara, ipyreact |
|
from pathlib import Path |
|
import base64 |
|
import requests |
|
|
|
def pdf_url_to_base64(url): |
|
response = requests.get(url) |
|
if response.status_code == 200: |
|
pdf_bytes = response.content |
|
base64_encoded = base64.b64encode(pdf_bytes).decode("utf-8") |
|
return base64_encoded |
|
else: |
|
print("Failed to fetch PDF from URL:", url) |
|
return None |
|
|
|
|
|
ipyreact.define_module("ts-pdf", "./tp.mjs") |
|
ipyreact.define_import_map({ |
|
"pdfjs-dist": "https://esm.sh/pdfjs-dist@2.16.105/build/pdf.worker.js", |
|
}) |
|
|
|
@solara.component |
|
def PDFViewer(type,pdf): |
|
if type=="bytes": |
|
pdf_bytes=pdf |
|
else: |
|
pdf_bytes=pdf_url_to_base64(pdf) |
|
|
|
app = ipyreact.ValueWidget(_esm= |
|
""" |
|
import React, { useEffect } from 'react'; |
|
import { AnnotEventDetail, CustomStampEventDetail, TsPdfViewer, TsPdfViewerOptions } from 'ts-pdf'; |
|
|
|
const PdfViewerComponent = () => { |
|
useEffect(() => { |
|
const mobileVhHack = () => { |
|
const vh = window.innerHeight * 0.01; |
|
document.documentElement.style.setProperty('--vh', `${vh}px`); |
|
}; |
|
|
|
mobileVhHack(); |
|
window.addEventListener("resize", mobileVhHack); |
|
|
|
return () => { |
|
window.removeEventListener("resize", mobileVhHack); |
|
}; |
|
}, []); |
|
|
|
var arrayBuffer = base64ToArrayBuffer('"""+pdf_bytes+"""'); |
|
|
|
function base64ToArrayBuffer(base64) { |
|
var binaryString = window.atob(base64); |
|
var binaryLen = binaryString.length; |
|
var bytes = new Uint8Array(binaryLen); |
|
for (var i = 0; i < binaryLen; i++) { |
|
var ascii = binaryString.charCodeAt(i); |
|
bytes[i] = ascii; |
|
} |
|
return bytes; |
|
} |
|
var blob = new Blob([arrayBuffer], {type: "application/pdf"}); |
|
var link = window.URL.createObjectURL(blob); |
|
|
|
useEffect(() => { |
|
const run = async () => { |
|
const options: TsPdfViewerOptions = { |
|
containerSelector: "#pdf-main-container", |
|
workerSource: "https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/build/pdf.worker.js", |
|
userName: "corran", |
|
fileButtons: ["close", "save"], |
|
comparableFileButtons: ["open", "close"], |
|
annotChangeCallback: (detail: AnnotEventDetail) => { |
|
if (detail.type === "focus" || detail.type === "select" || detail.type === "render") { |
|
return; |
|
} |
|
console.log(detail); |
|
}, |
|
customStampChangeCallback: (detail: CustomStampEventDetail) => { |
|
// console.log(JSON.stringify(detail.stamp)); |
|
}, |
|
}; |
|
const viewer = new TsPdfViewer(options); |
|
// viewer.importAnnotationsFromJson(dtos); |
|
// }, 5000); |
|
// for debug |
|
window["pdfViewer"] = viewer; |
|
await viewer.openPdfAsync(link); |
|
}; |
|
|
|
run(); |
|
|
|
// Clean up function |
|
return () => { |
|
// Perform any cleanup of the effect here |
|
}; |
|
}, []); |
|
|
|
return ( |
|
<div style={{ |
|
position: "relative", |
|
minWidth: "320px", |
|
width: "100vw", |
|
height: "calc(var(--vh, 1vh) * 100)", |
|
margin: "0", |
|
backgroundColor: "var(--tspdf-color-bg)", |
|
transition: "height .25s ease" |
|
}}> |
|
<style> |
|
{` |
|
.abs-stretch { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
} |
|
`} |
|
</style> |
|
<div className="test-container abs-stretch"> |
|
<div id="pdf-main-container" className="abs-stretch"> |
|
{/* Your PDF content will be rendered here */} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
export default PdfViewerComponent; |
|
""") |
|
solara.display(app) |
|
|
|
|
|
@solara.component |
|
def Reader(): |
|
solara.Style(""" |
|
#app > div > div:nth-child(2) > |
|
div:nth-child(2){ |
|
display: none; |
|
} |
|
""") |
|
router = solara.use_router() |
|
path = router.path |
|
if "bytes=" in path: |
|
data=path.split("bytes=")[-1] |
|
PDFViewer("bytes",data) |
|
elif "url=" in path: |
|
data=path.split("url=")[-1] |
|
PDFViewer("url",data) |
|
else: |
|
solara.Markdown("Invalid PDF") |
|
solara.Markdown(path) |
|
|
|
routes = [ |
|
solara.Route( |
|
"reader", |
|
component=Reader, |
|
), |
|
] |
|
Reader() |
|
|