feature/IO-3052-Skia-Canvas-Handler: Initial commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,43 +1,28 @@
|
||||
const { createCanvas } = require("canvas");
|
||||
const { Canvas, FontLibrary } = require("skia-canvas");
|
||||
const { performance } = require("perf_hooks");
|
||||
const Chart = require("chart.js/auto");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const { backgroundColors, borderColors } = require("./canvas-colors");
|
||||
const { isObject, defaultsDeep, isNumber } = require("lodash");
|
||||
|
||||
exports.canvastest = function (req, res) {
|
||||
//console.log("Incoming test request.", req);
|
||||
res.status(200).send("OK");
|
||||
};
|
||||
try {
|
||||
FontLibrary.use("Montserrat", [
|
||||
"/usr/share/fonts/Montserrat-Regular.ttf",
|
||||
"/usr/share/fonts/Montserrat-Bold.ttf",
|
||||
"/usr/share/fonts/Montserrat-Italic.ttf"
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error loading fonts Skia Canvas Fonts, please be sure to install Montserrat font package",
|
||||
error.message
|
||||
);
|
||||
}
|
||||
|
||||
exports.canvas = function (req, res) {
|
||||
const { w, h, values, keys, override } = req.body;
|
||||
//console.log("Incoming Canvas Request:", w, h, values, keys, override);
|
||||
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
||||
// Gate required values
|
||||
if (!values || !keys) {
|
||||
res.status(400).send("Missing required data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Override must be an object if it exists
|
||||
if (override && !isObject(override)) {
|
||||
res.status(400).send("Override must be an object");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the default Width and Height
|
||||
let [width, height] = [500, 275];
|
||||
|
||||
// Allow for custom width and height
|
||||
if (isNumber(w)) {
|
||||
width = w;
|
||||
}
|
||||
if (isNumber(h)) {
|
||||
height = h;
|
||||
}
|
||||
|
||||
const configuration = {
|
||||
// Utility to create a chart configuration
|
||||
const getChartConfiguration = (keys, values, override) => {
|
||||
const defaultConfiguration = {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: keys,
|
||||
@@ -53,6 +38,7 @@ exports.canvas = function (req, res) {
|
||||
options: {
|
||||
devicePixelRatio: 4,
|
||||
responsive: false,
|
||||
animation: false,
|
||||
maintainAspectRatio: true,
|
||||
circumference: 180,
|
||||
rotation: -90,
|
||||
@@ -73,21 +59,106 @@ exports.canvas = function (req, res) {
|
||||
}
|
||||
};
|
||||
|
||||
// If we have a valid override object, merge it with the default configuration object.
|
||||
// This allows for you to override the default configuration with a custom one.
|
||||
const defaults = () => {
|
||||
if (!override || !isObject(override)) {
|
||||
return configuration;
|
||||
}
|
||||
return defaultsDeep(override, configuration);
|
||||
};
|
||||
return defaultsDeep(override || {}, defaultConfiguration);
|
||||
};
|
||||
|
||||
// Utility to validate input
|
||||
const validateCanvasInput = ({ values, keys, override }, res) => {
|
||||
if (!Array.isArray(values) || !Array.isArray(keys)) {
|
||||
res.status(400).send("Invalid input: 'values' and 'keys' must be arrays.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.some((value) => typeof value !== "number")) {
|
||||
res.status(400).send("Invalid input: 'values' must be an array of numbers.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keys.some((key) => typeof key !== "string")) {
|
||||
res.status(400).send("Invalid input: 'keys' must be an array of strings.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (override && !isObject(override)) {
|
||||
res.status(400).send("Override must be an object");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.canvastest = function (req, res) {
|
||||
//console.log("Incoming test request.", req);
|
||||
res.status(200).send("OK");
|
||||
};
|
||||
|
||||
exports.canvas = function (req, res) {
|
||||
const startTime = performance.now();
|
||||
|
||||
const { w, h, values, keys, override } = req.body;
|
||||
//console.log("Incoming Canvas Request:", w, h, values, keys, override);
|
||||
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
||||
|
||||
if (!validateCanvasInput(req.body, res)) return;
|
||||
|
||||
// Set the default Width and Height
|
||||
let [width, height] = [500, 275];
|
||||
|
||||
// Allow for custom width and height
|
||||
if (isNumber(w)) {
|
||||
width = w;
|
||||
}
|
||||
if (isNumber(h)) {
|
||||
height = h;
|
||||
}
|
||||
|
||||
const configuration = getChartConfiguration(keys, values, override);
|
||||
|
||||
res.status(200).send(
|
||||
(() => {
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
new Chart(ctx, defaults());
|
||||
new Chart(ctx, configuration);
|
||||
return canvas.toDataURL();
|
||||
})()
|
||||
);
|
||||
console.log("Canvas generation time:", performance.now() - startTime, "ms");
|
||||
};
|
||||
|
||||
exports.canvasSkia = async function (req, res) {
|
||||
const startTime = performance.now();
|
||||
const { w, h, values, keys, override } = req.body;
|
||||
|
||||
// Log incoming request for debugging
|
||||
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
||||
|
||||
if (!validateCanvasInput(req.body, res)) return;
|
||||
|
||||
// Default width and height
|
||||
const width = typeof w === "number" && w > 0 ? w : 500;
|
||||
const height = typeof h === "number" && h > 0 ? h : 275;
|
||||
|
||||
const configuration = getChartConfiguration(keys, values, override);
|
||||
|
||||
try {
|
||||
// Create a canvas and get the 2D rendering context
|
||||
const canvas = new Canvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// Render the chart
|
||||
new Chart(ctx, configuration);
|
||||
|
||||
// Convert the canvas to a Base64-encoded image
|
||||
const chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
||||
const dataURL = `data:image/png;base64,${chartImage}`;
|
||||
|
||||
// Send the Base64-encoded image as the response
|
||||
res.status(200).send(dataURL);
|
||||
console.log("Canvas generation time:", performance.now() - startTime, "ms");
|
||||
} catch (error) {
|
||||
// Log and handle rendering errors
|
||||
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
||||
console.error("Error generating chart:", error.message);
|
||||
res.status(500).send("Failed to generate canvas.");
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user