diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 0f9b502a..67f2aca3 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -559,7 +559,7 @@
"Scatter chart",
"Series chart",
"Heatmap chart",
- "Extract Audio Metadata"
+ "Render PDF"
]
},
{
@@ -604,4 +604,4 @@
"Comment"
]
}
-]
+]
\ No newline at end of file
diff --git a/src/core/operations/RenderPDF.mjs b/src/core/operations/RenderPDF.mjs
new file mode 100644
index 00000000..c1b6cfb5
--- /dev/null
+++ b/src/core/operations/RenderPDF.mjs
@@ -0,0 +1,100 @@
+/**
+ * @author Shailendra [singhshailendra.in]
+ * @copyright Crown Copyright 2017
+ * @license Apache-2.0
+ */
+
+import { fromBase64, toBase64 } from "../lib/Base64.mjs";
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import Utils from "../Utils.mjs";
+
+/**
+ * Render PDF operation
+ */
+class RenderPDF extends Operation {
+
+ /**
+ * RenderPDF constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Render PDF";
+ this.module = "File";
+ this.description = "Displays the input as a PDF preview. Supports Raw and Base64 input formats.";
+ this.inputType = "string";
+ this.outputType = "byteArray";
+ this.presentType = "html";
+ this.args = [
+ {
+ "name": "Input format",
+ "type": "option",
+ "value": ["Base64", "Raw"],
+ }
+ ];
+ this.checks = [
+ {
+ pattern: "^%PDF-",
+ flags: "",
+ args: ["Raw"],
+ useful: true,
+ output: {
+ mime: "application/pdf"
+ }
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ run(input, args) {
+ const inputFormat = args[0];
+
+ if (!input.length) return [];
+
+ // Convert input to raw bytes
+ switch (inputFormat) {
+ case "Base64":
+ input = fromBase64(input, undefined, "byteArray");
+ break;
+ case "Raw":
+ default:
+ input = Utils.strToByteArray(input);
+ break;
+ }
+
+ // Check PDF signature
+ if (
+ input[0] !== 0x25 || // %
+ input[1] !== 0x50 || // P
+ input[2] !== 0x44 || // D
+ input[3] !== 0x46 // F
+ ) {
+ throw new OperationError("Input does not appear to be a PDF file.");
+ }
+
+ return input;
+ }
+
+ /**
+ * Displays the PDF using HTML for web apps.
+ *
+ * @param {byteArray} data
+ * @returns {html}
+ */
+ async present(data) {
+ if (!data.length) return "";
+
+ const base64 = toBase64(data);
+ const dataURI = "data:application/pdf;base64," + base64;
+
+ return ``;
+ }
+
+}
+
+export default RenderPDF;
diff --git a/tests/node/tests/nodeApi.mjs b/tests/node/tests/nodeApi.mjs
index 2510ef17..5f2476ee 100644
--- a/tests/node/tests/nodeApi.mjs
+++ b/tests/node/tests/nodeApi.mjs
@@ -136,7 +136,7 @@ TestRegister.addApiTests([
it("chef.help: returns multiple results", () => {
const result = chef.help("base 64");
- assert.strictEqual(result.length, 13);
+ assert.strictEqual(result.length, 14);
}),
it("chef.help: looks in description for matches too", () => {
diff --git a/tests/operations/tests/RenderPDF.mjs b/tests/operations/tests/RenderPDF.mjs
new file mode 100644
index 00000000..f359aa20
--- /dev/null
+++ b/tests/operations/tests/RenderPDF.mjs
@@ -0,0 +1,37 @@
+/**
+ * RenderPDF tests.
+ *
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+
+TestRegister.addTests([
+ {
+ name: "RenderPDF",
+ input: "Not a PDF",
+ expectedOutput: "Input does not appear to be a PDF file.",
+ recipeConfig: [
+ {
+ op: "Render PDF",
+ args: ["Raw"]
+ },
+ ],
+ },
+ {
+ name: "RenderPDF",
+ input: "",
+ expectedMatch: /^