Files
bodyshop/server/render/canvas-handler.js
2024-12-05 11:26:23 -08:00

165 lines
4.7 KiB
JavaScript

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");
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
);
}
// Utility to create a chart configuration
const getChartConfiguration = (keys, values, override) => {
const defaultConfiguration = {
type: "doughnut",
data: {
labels: keys,
datasets: [
{
data: values,
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 1
}
]
},
options: {
devicePixelRatio: 4,
responsive: false,
animation: false,
maintainAspectRatio: true,
circumference: 180,
rotation: -90,
plugins: {
legend: {
labels: {
boxWidth: 20,
font: {
family: "'Montserrat'",
size: 10,
style: "normal",
weight: "normal"
}
},
position: "left"
}
}
}
};
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, 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.");
}
};