From c3456adc2bb8746bdb079e524beaa97beb92ae0f Mon Sep 17 00:00:00 2001
From: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Date: Tue, 2 Dec 2025 13:56:28 +0000
Subject: [PATCH 1/3] Print with embed (#5109)
---
frontend/package-lock.json | 75 +++++++++----------
frontend/package.json | 1 +
.../public/locales/en-GB/translation.toml | 1 +
.../core/components/viewer/LocalEmbedPDF.tsx | 6 ++
.../core/components/viewer/PrintAPIBridge.tsx | 25 +++++++
.../viewer/useViewerRightRailButtons.tsx | 14 +++-
frontend/src/core/contexts/ViewerContext.tsx | 4 +
.../src/core/contexts/viewer/viewerActions.ts | 13 ++++
.../src/core/contexts/viewer/viewerBridges.ts | 7 ++
9 files changed, 104 insertions(+), 42 deletions(-)
create mode 100644 frontend/src/core/components/viewer/PrintAPIBridge.tsx
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index bbbe0dc9ee..2dfd2c9afb 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -20,6 +20,7 @@
"@embedpdf/plugin-interaction-manager": "^1.4.1",
"@embedpdf/plugin-loader": "^1.4.1",
"@embedpdf/plugin-pan": "^1.4.1",
+ "@embedpdf/plugin-print": "^1.4.1",
"@embedpdf/plugin-render": "^1.4.1",
"@embedpdf/plugin-rotate": "^1.4.1",
"@embedpdf/plugin-scroll": "^1.4.1",
@@ -455,7 +456,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -499,7 +499,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -580,7 +579,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz",
"integrity": "sha512-TGpxn2CvAKRnOJWJ3bsK+dKBiCp75ehxftRUmv7wAmPomhnG5XrDfoWJungvO+zbbqAwso6PocdeXINVt3hlAw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/engines": "1.4.1",
"@embedpdf/models": "1.4.1"
@@ -680,7 +678,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.4.1.tgz",
"integrity": "sha512-5WLDiNMH6tACkLGGv/lJtNsDeozOhSbrh0mjD1btHun8u7Yscu/Vf8tdJRUOsd+nULivo2nQ2NFNKu0OTbVo8w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -697,7 +694,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.4.1.tgz",
"integrity": "sha512-Ng02S9SFIAi9JZS5rI+NXSnZZ1Yk9YYRw4MlN2pig49qOyivZdz0oScZaYxQPewo8ccJkLeghjdeWswOBW/6cA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -715,7 +711,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.4.1.tgz",
"integrity": "sha512-m3ZOk8JygsLxoa4cZ+0BVB5pfRWuBCg2/gPqjhoFZNKTqAFw4J6HGUrhYKg94GRYe+w1cTJl/NbTBYuU5DOrsA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -747,12 +742,28 @@
"vue": ">=3.2.0"
}
},
+ "node_modules/@embedpdf/plugin-print": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.4.1.tgz",
+ "integrity": "sha512-YEjU6rQVW8wb125JXl1wma95+JISwADZpfqZZOtvPBRABp6ce4byblDTNjWVmTYWSgKUZrlXCL3Ff3Ig+bjbjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@embedpdf/models": "1.4.1"
+ },
+ "peerDependencies": {
+ "@embedpdf/core": "1.4.1",
+ "preact": "^10.26.4",
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0",
+ "svelte": ">=5 <6",
+ "vue": ">=3.2.0"
+ }
+ },
"node_modules/@embedpdf/plugin-render": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.4.1.tgz",
"integrity": "sha512-gKCdNKw6WBHBEpTc2DLBWIWOxzsNnaNbpfeY6C4f2Bum0EO+XW3Hl2oIx1uaRHjIhhnXso1J3QweqelsPwDGwg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -787,7 +798,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.4.1.tgz",
"integrity": "sha512-Y9O+matB4j4fLim5s/jn7qIi+lMC9vmDJRpJhiWe8bvD9oYLP2xfD/DdhFgAjRKcNhPoxC+j8q8QN5BMeGAv2Q==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -824,7 +834,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.4.1.tgz",
"integrity": "sha512-lo5Ytk1PH0PrRKv6zKVupm4t02VGsqIrnSIeP6NO8Ujx0wfqEhj//sqIuO/EwfFVJD8lcQIP9UUo9y8baCrEog==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -900,7 +909,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.4.1.tgz",
"integrity": "sha512-+TgFHKPCLTBiDYe2DdsmTS37hwQgcZ3dYIc7bE0l5cp+GVwouu1h0MTmjL+90loizeWwCiu10E/zXR6hz+CUaQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@embedpdf/models": "1.4.1"
},
@@ -1056,7 +1064,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1100,7 +1107,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -2131,7 +2137,6 @@
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz",
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@floating-ui/react": "^0.27.16",
"clsx": "^2.1.1",
@@ -2182,7 +2187,6 @@
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz",
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==",
"license": "MIT",
- "peer": true,
"peerDependencies": {
"react": "^18.x || ^19.x"
}
@@ -2250,7 +2254,6 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.5",
@@ -3183,7 +3186,6 @@
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12.16"
}
@@ -3302,6 +3304,7 @@
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"acorn": "^8.9.0"
}
@@ -4078,7 +4081,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -4407,7 +4409,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4418,7 +4419,6 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -4488,7 +4488,6 @@
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3",
@@ -5202,6 +5201,7 @@
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/shared": "3.5.24"
}
@@ -5211,6 +5211,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.24"
@@ -5221,6 +5222,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.24",
@@ -5233,6 +5235,7 @@
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24"
@@ -5259,7 +5262,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5667,6 +5669,7 @@
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -5943,7 +5946,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@@ -6991,8 +6993,7 @@
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz",
"integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==",
"dev": true,
- "license": "BSD-3-Clause",
- "peer": true
+ "license": "BSD-3-Clause"
},
"node_modules/dezalgo": {
"version": "1.0.4",
@@ -7387,7 +7388,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7558,7 +7558,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -7725,7 +7724,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/espree": {
"version": "10.4.0",
@@ -7790,6 +7790,7 @@
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
@@ -8880,7 +8881,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -9357,6 +9357,7 @@
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "^1.0.6"
}
@@ -9677,7 +9678,6 @@
"integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.19",
"@asamuzakjp/dom-selector": "^6.7.3",
@@ -10264,7 +10264,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -11410,7 +11411,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -11690,7 +11690,6 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
"license": "MIT",
- "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -12073,7 +12072,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12083,7 +12081,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -13595,6 +13592,7 @@
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -13803,7 +13801,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -14105,7 +14102,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14187,7 +14183,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"napi-postinstall": "^0.3.0"
},
@@ -14392,7 +14387,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -14544,7 +14538,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -14558,7 +14551,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -15170,7 +15162,8 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/zod": {
"version": "3.25.76",
diff --git a/frontend/package.json b/frontend/package.json
index b75c5109ac..0656fcc790 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,6 +16,7 @@
"@embedpdf/plugin-interaction-manager": "^1.4.1",
"@embedpdf/plugin-loader": "^1.4.1",
"@embedpdf/plugin-pan": "^1.4.1",
+ "@embedpdf/plugin-print": "^1.4.1",
"@embedpdf/plugin-render": "^1.4.1",
"@embedpdf/plugin-rotate": "^1.4.1",
"@embedpdf/plugin-scroll": "^1.4.1",
diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml
index 487c8d4cf6..bb0b7a814e 100644
--- a/frontend/public/locales/en-GB/translation.toml
+++ b/frontend/public/locales/en-GB/translation.toml
@@ -3899,6 +3899,7 @@ toggleSidebar = "Toggle Sidebar"
exportSelected = "Export Selected Pages"
toggleAnnotations = "Toggle Annotations Visibility"
annotationMode = "Toggle Annotation Mode"
+print = "Print PDF"
draw = "Draw"
save = "Save"
saveChanges = "Save Changes"
diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
index adef8c4fbf..b953b2205c 100644
--- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
+++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
@@ -20,6 +20,7 @@ import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark';
+import { PrintPluginPackage } from '@embedpdf/plugin-print/react';
// Import annotation plugins
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
@@ -41,6 +42,7 @@ import { HistoryAPIBridge } from '@app/components/viewer/HistoryAPIBridge';
import type { SignatureAPI, HistoryAPI } from '@app/components/viewer/viewerTypes';
import { ExportAPIBridge } from '@app/components/viewer/ExportAPIBridge';
import { BookmarkAPIBridge } from '@app/components/viewer/BookmarkAPIBridge';
+import { PrintAPIBridge } from '@app/components/viewer/PrintAPIBridge';
import { isPdfFile } from '@app/utils/fileUtils';
import { useTranslation } from 'react-i18next';
import { LinkLayer } from '@app/components/viewer/LinkLayer';
@@ -156,6 +158,9 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
createPluginRegistration(ExportPluginPackage, {
defaultFileName: 'document.pdf',
}),
+
+ // Register print plugin for printing PDFs
+ createPluginRegistration(PrintPluginPackage),
];
}, [pdfUrl]);
@@ -301,6 +306,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
{enableAnnotations && }
+
{
+ if (print) {
+ // Register this bridge with ViewerContext
+ registerBridge('print', {
+ state: {},
+ api: {
+ print: () => print.print(),
+ }
+ });
+ }
+ }, [print, registerBridge]);
+
+ return null;
+}
diff --git a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
index c02de92782..995d7d095d 100644
--- a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
+++ b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
@@ -24,6 +24,7 @@ export function useViewerRightRailButtons() {
const rotateRightLabel = t('rightRail.rotateRight', 'Rotate Right');
const sidebarLabel = t('rightRail.toggleSidebar', 'Toggle Sidebar');
const bookmarkLabel = t('rightRail.toggleBookmarks', 'Toggle Bookmarks');
+ const printLabel = t('rightRail.print', 'Print PDF');
const viewerButtons = useMemo(() => {
return [
@@ -127,6 +128,17 @@ export function useViewerRightRailButtons() {
viewer.toggleBookmarkSidebar();
}
},
+ {
+ id: 'viewer-print',
+ icon: ,
+ tooltip: printLabel,
+ ariaLabel: printLabel,
+ section: 'top' as const,
+ order: 56,
+ onClick: () => {
+ viewer.printActions.print();
+ }
+ },
{
id: 'viewer-annotation-controls',
section: 'top' as const,
@@ -136,7 +148,7 @@ export function useViewerRightRailButtons() {
)
}
];
- }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, tooltipPosition]);
+ }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, printLabel, tooltipPosition]);
useRightRailButtons(viewerButtons);
}
diff --git a/frontend/src/core/contexts/ViewerContext.tsx b/frontend/src/core/contexts/ViewerContext.tsx
index fc1eaf3464..937c6067ab 100644
--- a/frontend/src/core/contexts/ViewerContext.tsx
+++ b/frontend/src/core/contexts/ViewerContext.tsx
@@ -18,6 +18,7 @@ import {
SearchActions,
ExportActions,
BookmarkActions,
+ PrintActions,
} from '@app/contexts/viewer/viewerActions';
import {
BridgeRef,
@@ -125,6 +126,7 @@ interface ViewerContextType {
searchActions: SearchActions;
exportActions: ExportActions;
bookmarkActions: BookmarkActions;
+ printActions: PrintActions;
// Bridge registration - internal use by bridges
registerBridge: (
@@ -277,6 +279,7 @@ export const ViewerProvider: React.FC = ({ children }) => {
searchActions,
exportActions,
bookmarkActions,
+ printActions,
} = createViewerActions({
registry: bridgeRefs,
getScrollState,
@@ -333,6 +336,7 @@ export const ViewerProvider: React.FC = ({ children }) => {
searchActions,
exportActions,
bookmarkActions,
+ printActions,
// Bridge registration
registerBridge,
diff --git a/frontend/src/core/contexts/viewer/viewerActions.ts b/frontend/src/core/contexts/viewer/viewerActions.ts
index 05a38440a5..d32c5077c5 100644
--- a/frontend/src/core/contexts/viewer/viewerActions.ts
+++ b/frontend/src/core/contexts/viewer/viewerActions.ts
@@ -65,6 +65,10 @@ export interface BookmarkActions {
setLocalBookmarks: (bookmarks: PdfBookmarkObject[] | null, error?: string | null) => void;
}
+export interface PrintActions {
+ print: () => void;
+}
+
export interface ViewerActionsBundle {
scrollActions: ScrollActions;
zoomActions: ZoomActions;
@@ -75,6 +79,7 @@ export interface ViewerActionsBundle {
searchActions: SearchActions;
exportActions: ExportActions;
bookmarkActions: BookmarkActions;
+ printActions: PrintActions;
}
interface ViewerActionDependencies {
@@ -332,5 +337,13 @@ export function createViewerActions({
api?.setLocalBookmarks?.(bookmarks ?? null, error);
},
},
+ printActions: {
+ print: () => {
+ const api = registry.current.print?.api;
+ if (api?.print) {
+ api.print();
+ }
+ },
+ },
};
}
diff --git a/frontend/src/core/contexts/viewer/viewerBridges.ts b/frontend/src/core/contexts/viewer/viewerBridges.ts
index e7f1ebf3c0..66eb5ada08 100644
--- a/frontend/src/core/contexts/viewer/viewerBridges.ts
+++ b/frontend/src/core/contexts/viewer/viewerBridges.ts
@@ -49,6 +49,10 @@ export interface SearchAPIWrapper {
goToResult: (index: number) => void;
}
+export interface PrintAPIWrapper {
+ print: () => void;
+}
+
export interface ThumbnailAPIWrapper {
renderThumb: (pageIndex: number, scale: number) => {
toPromise: () => Promise;
@@ -132,6 +136,7 @@ export interface BridgeStateMap {
thumbnail: unknown;
export: ExportState;
bookmark: BookmarkState;
+ print: unknown;
}
export interface BridgeApiMap {
@@ -145,6 +150,7 @@ export interface BridgeApiMap {
thumbnail: ThumbnailAPIWrapper;
export: ExportAPIWrapper;
bookmark: BookmarkAPIWrapper;
+ print: PrintAPIWrapper;
}
export type BridgeKey = keyof BridgeStateMap;
@@ -164,6 +170,7 @@ export const createBridgeRegistry = (): ViewerBridgeRegistry => ({
thumbnail: null,
export: null,
bookmark: null,
+ print: null,
});
export function registerBridge(
From c2a63cf4257c11cd869a48f225eed37be8853397 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Tue, 2 Dec 2025 17:15:29 +0000
Subject: [PATCH 2/3] java frontend (#5097)
# Description of Changes
---
## Checklist
### General
- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### Translations (if applicable)
- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
---------
Co-authored-by: Reece
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
---
.dockerignore | 1 +
.../workflows/PR-Demo-Comment-with-react.yml | 2 +-
.github/workflows/build.yml | 4 +-
.github/workflows/push-docker-v2.yml | 19 +--
.github/workflows/push-docker.yml | 6 +-
.github/workflows/testdriver.yml | 2 +-
.../software/common/util/RequestUriUtils.java | 1 +
.../web/ReactRoutingController.java | 55 ++++++-
.../configuration/SecurityConfiguration.java | 6 +-
docker/embedded/Dockerfile | 138 +++++++++++++++++
docker/embedded/Dockerfile.fat | 142 ++++++++++++++++++
docker/embedded/Dockerfile.ultra-lite | 104 +++++++++++++
frontend/index.html | 8 +-
frontend/src/core/constants/app.ts | 23 ++-
.../contexts/TourOrchestrationContext.tsx | 2 +-
.../configSections/AdminGeneralSection.tsx | 4 +-
16 files changed, 483 insertions(+), 34 deletions(-)
create mode 100644 docker/embedded/Dockerfile
create mode 100644 docker/embedded/Dockerfile.fat
create mode 100644 docker/embedded/Dockerfile.ultra-lite
diff --git a/.dockerignore b/.dockerignore
index 54b49fd807..50f7376686 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,6 +5,7 @@ frontend/dist
frontend/build
frontend/.vite
frontend/.tauri
+frontend/src-tauri/target
# Gradle build artifacts
.gradle
diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml
index 82f1e0140e..bfde13275f 100644
--- a/.github/workflows/PR-Demo-Comment-with-react.yml
+++ b/.github/workflows/PR-Demo-Comment-with-react.yml
@@ -180,7 +180,7 @@ jobs:
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
- file: ./Dockerfile
+ file: ./docker/embedded/Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: VERSION_TAG=alpha
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 03f684c4f0..9a049bb90c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -262,7 +262,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- docker-rev: ["Dockerfile", "Dockerfile.ultra-lite", "Dockerfile.fat"]
+ docker-rev: ["docker/embedded/Dockerfile", "docker/embedded/Dockerfile.ultra-lite", "docker/embedded/Dockerfile.fat"]
steps:
- name: Harden Runner
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
@@ -301,7 +301,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./docker/backend/${{ matrix.docker-rev }}
+ file: ./${{ matrix.docker-rev }}
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.github/workflows/push-docker-v2.yml b/.github/workflows/push-docker-v2.yml
index 23d89ef880..5f2b70f50d 100644
--- a/.github/workflows/push-docker-v2.yml
+++ b/.github/workflows/push-docker-v2.yml
@@ -5,6 +5,7 @@ on:
push:
branches:
- V2-master
+ - alljavadocker
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
@@ -93,10 +94,10 @@ jobs:
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}
type=raw,value=latest
- - name: Generate tags for latest (V2-demo branch - test)
+ - name: Generate tags for latest (alljavadocker branch - test)
id: meta-test
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
- if: github.ref == 'refs/heads/V2-demo'
+ if: github.ref == 'refs/heads/alljavadocker'
with:
images: |
ghcr.io/stirling-tools/stirling-pdf-test
@@ -110,7 +111,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./docker/Dockerfile.unified
+ file: ./docker/embedded/Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -149,10 +150,10 @@ jobs:
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat
type=raw,value=latest-fat
- - name: Generate tags for latest-fat (V2-demo branch - test)
+ - name: Generate tags for latest-fat (alljavadocker branch - test)
id: meta-fat-test
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
- if: github.ref == 'refs/heads/V2-demo'
+ if: github.ref == 'refs/heads/alljavadocker'
with:
images: |
ghcr.io/stirling-tools/stirling-pdf-test
@@ -166,7 +167,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./docker/Dockerfile.unified
+ file: ./docker/embedded/Dockerfile.fat
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -203,10 +204,10 @@ jobs:
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite
type=raw,value=latest-ultra-lite
- - name: Generate tags for ultra-lite (V2-demo branch - test)
+ - name: Generate tags for ultra-lite (alljavadocker branch - test)
id: meta-lite-test
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
- if: github.ref == 'refs/heads/V2-demo'
+ if: github.ref == 'refs/heads/alljavadocker'
with:
images: |
ghcr.io/stirling-tools/stirling-pdf-test
@@ -220,7 +221,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./docker/Dockerfile.unified-lite
+ file: ./docker/embedded/Dockerfile.ultra-lite
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml
index 48e3514333..3f2a0c8d07 100644
--- a/.github/workflows/push-docker.yml
+++ b/.github/workflows/push-docker.yml
@@ -107,7 +107,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./Dockerfile
+ file: ./docker/embedded/Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -152,7 +152,7 @@ jobs:
if: github.ref != 'refs/heads/main'
with:
context: .
- file: ./Dockerfile.ultra-lite
+ file: ./docker/embedded/Dockerfile.ultra-lite
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -183,7 +183,7 @@ jobs:
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
- file: ./Dockerfile.fat
+ file: ./docker/embedded/Dockerfile.fat
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml
index 2bd47bee34..828c84d621 100644
--- a/.github/workflows/testdriver.yml
+++ b/.github/workflows/testdriver.yml
@@ -66,7 +66,7 @@ jobs:
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
- file: ./Dockerfile
+ file: ./docker/embedded/Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
diff --git a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java
index 2dd3d420a5..a0adec7de2 100644
--- a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java
+++ b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java
@@ -39,6 +39,7 @@ public class RequestUriUtils {
// Specific static files bundled with the frontend
if (normalizedUri.equals("/robots.txt")
|| normalizedUri.equals("/favicon.ico")
+ || normalizedUri.equals("/manifest.json")
|| normalizedUri.equals("/site.webmanifest")
|| normalizedUri.equals("/manifest-classic.json")
|| normalizedUri.equals("/index.html")) {
diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java
index daa6233ce8..db0d95a36c 100644
--- a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java
+++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java
@@ -1,20 +1,61 @@
package stirling.software.SPDF.controller.web;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
+import jakarta.servlet.http.HttpServletRequest;
+
@Controller
public class ReactRoutingController {
- @GetMapping(
- "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*$}")
- public String forwardRootPaths() {
- return "forward:/index.html";
+ @Value("${server.servlet.context-path:/}")
+ private String contextPath;
+
+ @GetMapping(value = {"/", "/index.html"}, produces = MediaType.TEXT_HTML_VALUE)
+ public ResponseEntity serveIndexHtml(HttpServletRequest request)
+ throws IOException {
+ ClassPathResource resource = new ClassPathResource("static/index.html");
+
+ try (InputStream inputStream = resource.getInputStream()) {
+ String html = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+
+ // Replace %BASE_URL% with the actual context path for base href
+ String baseUrl = contextPath.endsWith("/") ? contextPath : contextPath + "/";
+ html = html.replace("%BASE_URL%", baseUrl);
+ // Also rewrite any existing tag (Vite may have baked one in)
+ html =
+ html.replaceFirst(
+ "",
+ "");
+
+ // Inject context path as a global variable for API calls
+ String contextPathScript =
+ "";
+ html = html.replace("", contextPathScript + "");
+
+ return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(html);
+ }
}
@GetMapping(
- "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
- public String forwardNestedPaths() {
- return "forward:/index.html";
+ "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}")
+ public ResponseEntity forwardRootPaths(HttpServletRequest request)
+ throws IOException {
+ return serveIndexHtml(request);
+ }
+
+ @GetMapping(
+ "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
+ public ResponseEntity forwardNestedPaths(HttpServletRequest request)
+ throws IOException {
+ return serveIndexHtml(request);
}
}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java
index e683fa8dc5..a3a5eee4f0 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java
@@ -324,10 +324,14 @@ public class SecurityConfiguration {
.authenticated());
// Handle User/Password Logins
if (securityProperties.isUserPass()) {
+ // v2: Authentication is handled via API (/api/v1/auth/login), not form login
+ // We configure form login to handle Spring Security redirects,
+ // but use /perform_login as the processing URL so /login remains a React route
http.formLogin(
formLogin ->
formLogin
- .loginPage("/login")
+ .loginPage("/login") // Redirect here when unauthenticated
+ .loginProcessingUrl("/perform_login") // Process form posts here (not /login)
.successHandler(
new CustomAuthenticationSuccessHandler(
loginAttemptService,
diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile
new file mode 100644
index 0000000000..a38aee9b4a
--- /dev/null
+++ b/docker/embedded/Dockerfile
@@ -0,0 +1,138 @@
+# Stirling-PDF Dockerfile - Full version with embedded frontend
+# Single JAR contains both frontend and backend
+
+# Stage 1: Build application with embedded frontend
+FROM gradle:8.14-jdk21 AS build
+
+# Install Node.js and npm for frontend build
+RUN apt-get update && apt-get install -y \
+ curl \
+ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
+ && apt-get install -y nodejs \
+ && npm --version \
+ && node --version \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy gradle files for dependency resolution
+COPY build.gradle .
+COPY settings.gradle .
+COPY gradlew .
+COPY gradle gradle/
+COPY app/core/build.gradle core/.
+COPY app/common/build.gradle common/.
+COPY app/proprietary/build.gradle proprietary/.
+RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
+
+# Set working directory
+WORKDIR /app
+
+# Copy entire project
+COPY . .
+
+# Build JAR with embedded frontend (includes security features controlled at runtime)
+RUN DISABLE_ADDITIONAL_FEATURES=false \
+ STIRLING_PDF_DESKTOP_UI=false \
+ ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
+
+# Stage 2: Runtime image
+FROM alpine:3.22.1
+
+ARG VERSION_TAG
+
+# Labels
+LABEL org.opencontainers.image.title="Stirling-PDF"
+LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Full version"
+LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
+LABEL org.opencontainers.image.licenses="MIT"
+LABEL org.opencontainers.image.vendor="Stirling-Tools"
+LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
+LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
+LABEL maintainer="Stirling-Tools"
+LABEL org.opencontainers.image.authors="Stirling-Tools"
+LABEL org.opencontainers.image.version="${VERSION_TAG}"
+LABEL org.opencontainers.image.keywords="PDF, manipulation, API, Spring Boot, React"
+
+# Copy scripts and fonts
+COPY scripts /scripts
+COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
+
+# Copy built JAR from build stage
+COPY --from=build /app/app/core/build/libs/*.jar /app.jar
+COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar
+
+# Environment Variables
+ENV VERSION_TAG=$VERSION_TAG \
+ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
+ JAVA_CUSTOM_OPTS="" \
+ HOME=/home/stirlingpdfuser \
+ PUID=1000 \
+ PGID=1000 \
+ UMASK=022 \
+ PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
+ UNO_PATH=/usr/lib/libreoffice/program \
+ URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
+ PATH=$PATH:/opt/venv/bin \
+ STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
+ TMPDIR=/tmp/stirling-pdf \
+ TEMP=/tmp/stirling-pdf \
+ TMP=/tmp/stirling-pdf
+
+# Install all dependencies
+RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
+ echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
+ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
+ apk upgrade --no-cache -a && \
+ apk add --no-cache \
+ ca-certificates \
+ tzdata \
+ tini \
+ bash \
+ curl \
+ shadow \
+ su-exec \
+ openssl \
+ openssl-dev \
+ openjdk21-jre \
+ # Doc conversion
+ gcompat \
+ libc6-compat \
+ libreoffice \
+ ghostscript \
+ fontforge \
+ # pdftohtml
+ poppler-utils \
+ # OCR MY PDF
+ unpaper \
+ tesseract-ocr-data-eng \
+ tesseract-ocr-data-chi_sim \
+ tesseract-ocr-data-deu \
+ tesseract-ocr-data-fra \
+ tesseract-ocr-data-por \
+ ocrmypdf \
+ # CV
+ py3-opencv \
+ python3 \
+ py3-pip \
+ py3-pillow@testing \
+ py3-pdf2image@testing && \
+ python3 -m venv /opt/venv && \
+ /opt/venv/bin/pip install --upgrade pip setuptools && \
+ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
+ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
+ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
+ ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
+ mv /usr/share/tessdata /usr/share/tessdata-original && \
+ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
+ fc-cache -f -v && \
+ chmod +x /scripts/* && \
+ # User permissions
+ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
+ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /pipeline /usr/share/fonts/opentype/noto /configs /customFiles /tmp/stirling-pdf && \
+ chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar
+
+EXPOSE 8080/tcp
+
+# Set user and run command
+ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
+CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat
new file mode 100644
index 0000000000..67e648aee0
--- /dev/null
+++ b/docker/embedded/Dockerfile.fat
@@ -0,0 +1,142 @@
+# Stirling-PDF Dockerfile - Fat version with embedded frontend
+# Single JAR contains both frontend and backend with extra fonts for air-gapped environments
+
+# Stage 1: Build application with embedded frontend
+FROM gradle:8.14-jdk21 AS build
+
+# Install Node.js and npm for frontend build
+RUN apt-get update && apt-get install -y \
+ curl \
+ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
+ && apt-get install -y nodejs \
+ && npm --version \
+ && node --version \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy gradle files for dependency resolution
+COPY build.gradle .
+COPY settings.gradle .
+COPY gradlew .
+COPY gradle gradle/
+COPY app/core/build.gradle core/.
+COPY app/common/build.gradle common/.
+COPY app/proprietary/build.gradle proprietary/.
+RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
+
+# Set working directory
+WORKDIR /app
+
+# Copy entire project
+COPY . .
+
+# Build JAR with embedded frontend (includes security features controlled at runtime)
+RUN DISABLE_ADDITIONAL_FEATURES=false \
+ STIRLING_PDF_DESKTOP_UI=false \
+ ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
+
+# Stage 2: Runtime image
+FROM alpine:3.22.1
+
+ARG VERSION_TAG
+
+# Labels
+LABEL org.opencontainers.image.title="Stirling-PDF Fat"
+LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Fat version with extra fonts for air-gapped environments"
+LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
+LABEL org.opencontainers.image.licenses="MIT"
+LABEL org.opencontainers.image.vendor="Stirling-Tools"
+LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
+LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
+LABEL maintainer="Stirling-Tools"
+LABEL org.opencontainers.image.authors="Stirling-Tools"
+LABEL org.opencontainers.image.version="${VERSION_TAG}"
+LABEL org.opencontainers.image.keywords="PDF, manipulation, fat, air-gapped, API, Spring Boot, React"
+
+# Copy scripts and fonts
+COPY scripts /scripts
+COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
+
+# Copy built JAR from build stage
+COPY --from=build /app/app/core/build/libs/*.jar /app.jar
+COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar
+
+# Environment Variables
+ENV VERSION_TAG=$VERSION_TAG \
+ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
+ JAVA_CUSTOM_OPTS="" \
+ HOME=/home/stirlingpdfuser \
+ PUID=1000 \
+ PGID=1000 \
+ UMASK=022 \
+ FAT_DOCKER=true \
+ INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
+ PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
+ UNO_PATH=/usr/lib/libreoffice/program \
+ URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
+ PATH=$PATH:/opt/venv/bin \
+ STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
+ TMPDIR=/tmp/stirling-pdf \
+ TEMP=/tmp/stirling-pdf \
+ TMP=/tmp/stirling-pdf
+
+# Install all dependencies plus extra fonts for air-gapped environments
+RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
+ echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
+ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
+ apk upgrade --no-cache -a && \
+ apk add --no-cache \
+ ca-certificates \
+ tzdata \
+ tini \
+ bash \
+ curl \
+ shadow \
+ su-exec \
+ openssl \
+ openssl-dev \
+ openjdk21-jre \
+ # Doc conversion
+ gcompat \
+ libc6-compat \
+ libreoffice \
+ ghostscript \
+ fontforge \
+ # pdftohtml
+ poppler-utils \
+ # OCR MY PDF
+ unpaper \
+ tesseract-ocr-data-eng \
+ tesseract-ocr-data-chi_sim \
+ tesseract-ocr-data-deu \
+ tesseract-ocr-data-fra \
+ tesseract-ocr-data-por \
+ ocrmypdf \
+ # Extra fonts for fat version
+ font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \
+ # CV
+ py3-opencv \
+ python3 \
+ py3-pip \
+ py3-pillow@testing \
+ py3-pdf2image@testing && \
+ python3 -m venv /opt/venv && \
+ /opt/venv/bin/pip install --upgrade pip setuptools && \
+ /opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
+ ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
+ ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
+ ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
+ mv /usr/share/tessdata /usr/share/tessdata-original && \
+ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
+ fc-cache -f -v && \
+ chmod +x /scripts/* && \
+ # User permissions
+ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
+ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /pipeline /usr/share/fonts/opentype/noto /configs /customFiles /tmp/stirling-pdf && \
+ chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar
+
+EXPOSE 8080/tcp
+
+# Set user and run command
+ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
+CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
diff --git a/docker/embedded/Dockerfile.ultra-lite b/docker/embedded/Dockerfile.ultra-lite
new file mode 100644
index 0000000000..a39a522ebc
--- /dev/null
+++ b/docker/embedded/Dockerfile.ultra-lite
@@ -0,0 +1,104 @@
+# Stirling-PDF Dockerfile - Ultra-lite version with embedded frontend
+# Single JAR contains both frontend and backend with minimal dependencies
+
+# Stage 1: Build application with embedded frontend
+FROM gradle:8.14-jdk21 AS build
+
+# Install Node.js and npm for frontend build
+RUN apt-get update && apt-get install -y \
+ curl \
+ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
+ && apt-get install -y nodejs \
+ && npm --version \
+ && node --version \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy gradle files for dependency resolution
+COPY build.gradle .
+COPY settings.gradle .
+COPY gradlew .
+COPY gradle gradle/
+COPY app/core/build.gradle core/.
+COPY app/common/build.gradle common/.
+COPY app/proprietary/build.gradle proprietary/.
+RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
+
+# Set working directory
+WORKDIR /app
+
+# Copy entire project
+COPY . .
+
+# Build ultra-lite JAR with embedded frontend (minimal features)
+RUN DISABLE_ADDITIONAL_FEATURES=true \
+ STIRLING_PDF_DESKTOP_UI=false \
+ ./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
+
+# Stage 2: Runtime image
+FROM alpine:3.22.1
+
+ARG VERSION_TAG
+
+# Labels
+LABEL org.opencontainers.image.title="Stirling-PDF Ultra-Lite"
+LABEL org.opencontainers.image.description="Stirling-PDF with embedded frontend - Ultra-lite version with minimal dependencies"
+LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
+LABEL org.opencontainers.image.licenses="MIT"
+LABEL org.opencontainers.image.vendor="Stirling-Tools"
+LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
+LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
+LABEL maintainer="Stirling-Tools"
+LABEL org.opencontainers.image.authors="Stirling-Tools"
+LABEL org.opencontainers.image.version="${VERSION_TAG}"
+LABEL org.opencontainers.image.keywords="PDF, manipulation, ultra-lite, API, Spring Boot, React"
+
+# Copy scripts
+COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
+COPY scripts/installFonts.sh /scripts/installFonts.sh
+
+# Copy built JAR from build stage
+COPY --from=build /app/app/core/build/libs/*.jar /app.jar
+COPY --from=build /app/build/libs/restart-helper.jar /restart-helper.jar
+
+# Environment Variables
+ENV VERSION_TAG=$VERSION_TAG \
+ JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
+ JAVA_CUSTOM_OPTS="" \
+ HOME=/home/stirlingpdfuser \
+ PUID=1000 \
+ PGID=1000 \
+ UMASK=022 \
+ STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
+ TMPDIR=/tmp/stirling-pdf \
+ TEMP=/tmp/stirling-pdf \
+ TMP=/tmp/stirling-pdf \
+ ENDPOINTS_GROUPS_TO_REMOVE=CLI
+
+# Install minimal dependencies
+RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
+ echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
+ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
+ apk upgrade --no-cache -a && \
+ apk add --no-cache \
+ ca-certificates \
+ tzdata \
+ tini \
+ bash \
+ curl \
+ shadow \
+ su-exec \
+ openjdk21-jre && \
+ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
+ mkdir -p /usr/share/fonts/opentype/noto && \
+ chmod +x /scripts/*.sh && \
+ # User permissions
+ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
+ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline /tmp/stirling-pdf && \
+ chown stirlingpdfuser:stirlingpdfgroup /app.jar /restart-helper.jar
+
+EXPOSE 8080/tcp
+
+# Set user and run command
+ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
+CMD ["java", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=/tmp/stirling-pdf", "-jar", "/app.jar"]
diff --git a/frontend/index.html b/frontend/index.html
index 5dbaa18938..5042c6a41d 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -3,21 +3,21 @@
-
+
-
-
+
+
Stirling PDF
-
+