const { Canvas, FontLibrary } = require("skia-canvas"); const Chart = require("chart.js/auto"); const { backgroundColors, borderColors } = require("./canvas-colors"); const { defaultsDeep, isNumber } = require("lodash"); const CANVAS_QUEUE_LIMIT = 100; let isProcessing = false; const requestQueue = []; 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); }; const processCanvasRequest = async (req, res) => { const { logger } = req; const { w, h, values, keys, override } = req.body; logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override }); // Default width and height const width = isNumber(w) && w > 0 ? w : 500; const height = isNumber(h) && h > 0 ? h : 275; const configuration = getChartConfiguration(keys, values, override); let canvas = null; let ctx = null; let chart = null; let chartImage = null; try { // Create the canvas canvas = new Canvas(width, height); ctx = canvas.getContext("2d"); // Render the chart chart = new Chart(ctx, configuration); // Generate and send the image chartImage = (await canvas.toBuffer("image/png")).toString("base64"); res.status(200).send(`data:image/png;base64,${chartImage}`); } catch (error) { // Log the error and send the response logger.log("canvas-error", "error", "jsr", null, { error: error.message }); res.status(500).send("Failed to generate canvas."); } finally { // Cleanup resources if (chart) { chart.destroy(); } ctx = null; canvas = null; chartImage = null; } }; const enqueueRequest = (req, res) => { if (requestQueue.length >= CANVAS_QUEUE_LIMIT) { res.status(503).send("Server is busy. Please try again later."); return false; } requestQueue.push({ req, res }); req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length }); return true; }; const processNextInQueue = async () => { while (requestQueue.length > 0) { const { req, res } = requestQueue.shift(); try { await processCanvasRequest(req, res); } catch (err) { console.error("canvas-queue-error", "error", "jsr", null, { error: err.message }); } } isProcessing = false; }; exports.canvastest = function (req, res) { res.status(200).send("OK"); }; exports.canvas = async (req, res) => { if (isProcessing || !enqueueRequest(req, res)) return; isProcessing = true; processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message })); };