chore(display): add ai generated pdf.js integration (#58)

Co-authored-by: E44 <129310925+programmer-44@users.noreply.github.com>
Reviewed-on: https://codeberg.org/PLG-Development/PLG-MuDiCS/pulls/58
This commit is contained in:
E44
2026-06-14 22:10:04 +02:00
parent 2dc46c186e
commit 21cb07a465
8 changed files with 285 additions and 1 deletions
+1
View File
@@ -1,2 +1,3 @@
.aider*
.env
/display/internal/pdfjs/node_modules
+7
View File
@@ -0,0 +1,7 @@
package pdfjs
import "embed"
//go:embed node_modules/pdfjs-dist/build/pdf.min.mjs
//go:embed node_modules/pdfjs-dist/build/pdf.worker.min.mjs
var Files embed.FS
+10
View File
@@ -0,0 +1,10 @@
{
"nodeModulesDir": "manual",
"nodeModulesLinker": "hoisted",
"imports": {
"pdfjs-dist": "npm:pdfjs-dist@6.0.227"
},
"tasks": {
"install": "deno install --frozen"
}
}
+90
View File
@@ -0,0 +1,90 @@
{
"version": "5",
"specifiers": {
"npm:pdfjs-dist@6.0.227": "6.0.227"
},
"npm": {
"@napi-rs/canvas-android-arm64@1.0.0": {
"integrity": "sha512-3hNKJObUK7JsCF9aJlVCs1J0/KE/gGfZNeK8MO1ge6bB3aicr5walGme9t9No1f/oyk9GgvdAT/rjSdsx3gbIw==",
"os": ["android"],
"cpu": ["arm64"]
},
"@napi-rs/canvas-darwin-arm64@1.0.0": {
"integrity": "sha512-ZIja19/BiGz2puhki+WUYSRriwFeFJ8Mi9eK3hZdSS85w4Y60cuEAJVhMCfKwswQkKkUtrnzdKMBuO7TupvexA==",
"os": ["darwin"],
"cpu": ["arm64"]
},
"@napi-rs/canvas-darwin-x64@1.0.0": {
"integrity": "sha512-hImggWc82jqZVpEsFR9S7PE9OQYjq/H/D7vwCGB6X1jRH+UVBP1+1niJTPBOat1B154T6GKK7/kcFtoWgjgFzQ==",
"os": ["darwin"],
"cpu": ["x64"]
},
"@napi-rs/canvas-linux-arm-gnueabihf@1.0.0": {
"integrity": "sha512-hlJRy6d+kWLKVOG/+1rEvNQVURZ0DxxRPJsLmEWwhwiXZUJc0BF5o9esALHSEP4CoJK4wChRtj3hnyBgVx2oWA==",
"os": ["linux"],
"cpu": ["arm"]
},
"@napi-rs/canvas-linux-arm64-gnu@1.0.0": {
"integrity": "sha512-5Hru4T3RXkosRQafcjelv7AUzw9mXqmGYsxnzeDDOWveFCJyEPMSJltvGCM+jfH98seOCbfwm9KyFg6Jm5FhAA==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@napi-rs/canvas-linux-arm64-musl@1.0.0": {
"integrity": "sha512-LTUl9jS8WsLSUGaxQZKQkxfluOJRpgvBuxxdM4pYcjib+di8AU4OzQc6+L6SzGMLcKc9H0RAjojRatBhTMqYdg==",
"os": ["linux"],
"cpu": ["arm64"]
},
"@napi-rs/canvas-linux-riscv64-gnu@1.0.0": {
"integrity": "sha512-Iz931SAZf+WVDzpjk52Q3ffW3zw0YflFwEZMgs036Wfu1kX/LrwT9wGjsuSqyduqefUkl91/vTdAjn8hQu5ezA==",
"os": ["linux"],
"cpu": ["riscv64"]
},
"@napi-rs/canvas-linux-x64-gnu@1.0.0": {
"integrity": "sha512-pFEQ5eFK4JusgN1K6KkO9DKP/Hi1WMJOkF8Ch03/khTc4bFbCKkCCsJG4YcOMOW9bI4XbT2/eMAWxhO0xaWgPA==",
"os": ["linux"],
"cpu": ["x64"]
},
"@napi-rs/canvas-linux-x64-musl@1.0.0": {
"integrity": "sha512-jnvr8NrLHiZ3NCiOKWqDbkI4Ah+QDrqtZ+sddPZBltEb1mQ2coSvCSJYfict+oAwcm0c970oTmVySpjKP/lnaA==",
"os": ["linux"],
"cpu": ["x64"]
},
"@napi-rs/canvas-win32-arm64-msvc@1.0.0": {
"integrity": "sha512-y2j9/Gfd5joqiqxdP/L1smqjQ+uAx3C4N0EC7bDHrnZEEH8ToM/OC5p3uHvtj4Lq591aHj+ArL01UDLNwT5HgQ==",
"os": ["win32"],
"cpu": ["arm64"]
},
"@napi-rs/canvas-win32-x64-msvc@1.0.0": {
"integrity": "sha512-qwdhh9N6Gge/hC4pL9S1tQp0iKwhSl/dYjg7+RGp9k26iRGRi5MqqUyKGOXIWli0zOcuy5Y2wIH/jk2ry6i/jA==",
"os": ["win32"],
"cpu": ["x64"]
},
"@napi-rs/canvas@1.0.0": {
"integrity": "sha512-Jqxcy1XOIqj+lH9sl1GT+il6GR3uQv13vI2mrwubP3uT8Olak2ClDrK2RnxlQKjwv8BRr4b3ug0YR7c6hBX8wg==",
"optionalDependencies": [
"@napi-rs/canvas-android-arm64",
"@napi-rs/canvas-darwin-arm64",
"@napi-rs/canvas-darwin-x64",
"@napi-rs/canvas-linux-arm-gnueabihf",
"@napi-rs/canvas-linux-arm64-gnu",
"@napi-rs/canvas-linux-arm64-musl",
"@napi-rs/canvas-linux-riscv64-gnu",
"@napi-rs/canvas-linux-x64-gnu",
"@napi-rs/canvas-linux-x64-musl",
"@napi-rs/canvas-win32-arm64-msvc",
"@napi-rs/canvas-win32-x64-msvc"
]
},
"pdfjs-dist@6.0.227": {
"integrity": "sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==",
"optionalDependencies": [
"@napi-rs/canvas"
]
}
},
"workspace": {
"dependencies": [
"npm:pdfjs-dist@6.0.227"
]
}
}
+1
View File
@@ -1,5 +1,6 @@
gen:
go tool templ generate
cd internal/pdfjs && deno install --frozen
dev: gen
go run *.go
+162
View File
@@ -1,5 +1,7 @@
package pkg
import "strings"
templ basicTemplate() {
<!DOCTYPE html>
<html lang="en">
@@ -115,6 +117,166 @@ templ deviceInfoTemplate(ip string, mac string, showQR bool) {
}
}
templ pdfTemplate(path string, serverOrigin string) {
@basicTemplate() {
<base href={ serverOrigin + "/" }/>
<div
id="pdf"
data-src={ "api/file/" + strings.Split(path, "/.local/share/plg-mudics/display")[1] }
data-lib="static/pdfjs/pdf.min.mjs"
data-worker="static/pdfjs/pdf.worker.min.mjs"
></div>
<style>
html,body,#pdf {
margin:0;
width:100%;
height:100%;
overflow:hidden;
background:#000;
}
#pdf {
display:grid;
place-items:center;
}
#pdf canvas {
grid-area:1/1;
display:block;
}
</style>
<script type="module">
const host=document.querySelector("#pdf");
const resolve=x=>new URL(x,document.baseURI).href;
const P=await import(resolve(host.dataset.lib));
P.GlobalWorkerOptions.workerSrc=resolve(host.dataset.worker);
const pdf=await P.getDocument({
url:resolve(host.dataset.src),
useWasm:false
}).promise;
const cache=new Map();
let currentPage=1;
let wantedPage=1;
let requestNumber=0;
function renderPage(number) {
if(cache.has(number)) return cache.get(number);
const promise=(async()=>{
const page=await pdf.getPage(number);
const original=page.getViewport({scale:1});
const scale=Math.min(
innerWidth/original.width,
innerHeight/original.height
);
// Use 1 on old hardware. Raise to 1.5 for sharper output.
const pixelRatio=1;
const viewport=page.getViewport({
scale:scale*pixelRatio
});
const canvas=document.createElement("canvas");
canvas.width=Math.ceil(viewport.width);
canvas.height=Math.ceil(viewport.height);
canvas.style.width=`${viewport.width/pixelRatio}px`;
canvas.style.height=`${viewport.height/pixelRatio}px`;
const task=page.render({
canvasContext:canvas.getContext("2d"),
viewport
});
await task.promise;
return canvas;
})().catch(error=>{
cache.delete(number);
throw error;
});
cache.set(number,promise);
return promise;
}
async function show(number) {
wantedPage=Math.max(1,Math.min(pdf.numPages,number));
const request=++requestNumber;
const canvas=await renderPage(wantedPage);
// Ignore an obsolete result after multiple fast key presses.
if(request!==requestNumber) return;
// The new canvas is already completely rendered.
host.replaceChildren(canvas);
currentPage=wantedPage;
pruneCache();
preloadNeighbours();
}
function preloadNeighbours() {
const page=currentPage;
requestIdleCallback(async()=>{
if(page+1<=pdf.numPages) {
await renderPage(page+1);
}
if(page-1>=1) {
await renderPage(page-1);
}
},{timeout:250});
}
function pruneCache() {
const keep=new Set([
currentPage-1,
currentPage,
currentPage+1
]);
for(const [number,promise] of cache) {
if(keep.has(number)) continue;
cache.delete(number);
promise.then(canvas=>{
if(!canvas.isConnected) {
canvas.width=0;
canvas.height=0;
}
}).catch(()=>{});
}
}
addEventListener("keydown",event=>{
if(event.key==="ArrowRight") {
event.preventDefault();
show(wantedPage+1);
} else if(event.key==="ArrowLeft") {
event.preventDefault();
show(wantedPage-1);
}
});
addEventListener("resize",()=>{
cache.clear();
show(currentPage);
});
window.pdfViewer={
page:()=>currentPage,
count:()=>pdf.numPages,
show
};
await show(1);
</script>
}
}
templ startScreenTemplate(splashScreenHtml string, ip string, mac string, qrPath string) {
@basicTemplate() {
<div style="width: 100vw; height: 100vh; display: flex; flex-direction: row; justify-content: space-between;">
+6 -1
View File
@@ -40,7 +40,12 @@ func OpenFile(path string) error {
_ = imageTemplate(path).Render(context.Background(), &templateBuffer)
err = browser.Browser.OpenHTML(templateBuffer.String())
case "application/pdf":
err = browser.Browser.OpenPDF(path)
var templateBuffer bytes.Buffer
err = pdfTemplate(path, "http://127.0.0.1:1323").Render(context.Background(), &templateBuffer)
if err == nil {
err = browser.Browser.OpenHTML(templateBuffer.String())
}
case "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.oasis.opendocument.presentation":
err = fileHandler.openFileWithApp(path)
default:
+8
View File
@@ -16,12 +16,20 @@ import (
"github.com/labstack/echo/v4/middleware"
"plg-mudics/display/browser"
"plg-mudics/display/internal/pdfjs"
"plg-mudics/display/pkg"
)
func StartWebServer(port string) {
e := echo.New()
pdfJSGroup := e.Group("/static/pdfjs")
pdfJSGroup.Use(middleware.CORS())
pdfJSGroup.StaticFS(
"/",
echo.MustSubFS(pdfjs.Files, "node_modules/pdfjs-dist/build"),
)
apiGroup := e.Group("/api")
apiGroup.Use(middleware.CORS())
apiGroup.GET("/ping", pingRoute)