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."); } };