Generate Styled QR Code
Generate a styled QR code with custom colors, shapes, gradients, and optional logo
Source Code
import fs from "fs";
import path from "path";
import QRCodeStyling from "qr-code-styling-node";
import { JSDOM } from "jsdom";
const [text, outputDir, optionsJson = "{}"] = process.argv.slice(2);
// Validation
if (!text) {
console.error("Error: text is required");
process.exit(1);
}
if (!outputDir) {
console.error("Error: outputDir is required");
process.exit(1);
}
// Parse options
let options;
try {
options = JSON.parse(optionsJson);
} catch (e) {
console.error("Error: options must be valid JSON");
process.exit(1);
}
// Defaults
const config = {
size: options.size || 300,
format: options.format || "png", // png, svg, jpeg
dotStyle: options.dotStyle || "square", // square, dots, rounded, extra-rounded, classy, classy-rounded
dotColor: options.dotColor || "#000000",
cornerSquareStyle: options.cornerStyle || "square", // square, dot, extra-rounded
cornerSquareColor: options.cornerColor || options.dotColor || "#000000",
cornerDotStyle: options.cornerDotStyle || "square", // square, dot
cornerDotColor: options.cornerDotColor || options.cornerColor || options.dotColor || "#000000",
backgroundColor: options.backgroundColor || "#FFFFFF",
gradient: options.gradient || null, // { type: 'linear'|'radial', colors: ['#000', '#333'], rotation: 0 }
logoPath: options.logoPath || null,
logoSize: options.logoSize || 0.3, // Proportion of QR size (0.1 to 0.4)
margin: options.margin ?? 10,
errorCorrectionLevel: options.errorCorrectionLevel || "M", // L, M, Q, H
};
// Build gradient config if specified
function buildGradient(gradientConfig, fallbackColor) {
if (!gradientConfig) return fallbackColor;
return {
type: gradientConfig.type || "linear", // linear or radial
rotation: gradientConfig.rotation || 0,
colorStops: gradientConfig.colors
? gradientConfig.colors.map((color, i, arr) => ({
offset: i / (arr.length - 1),
color: color,
}))
: [
{ offset: 0, color: fallbackColor },
{ offset: 1, color: fallbackColor },
],
};
}
async function main() {
console.log(`Generating styled QR code for: "${text.slice(0, 50)}${text.length > 50 ? "..." : ""}"`);
console.log(`Size: ${config.size}px, Format: ${config.format}`);
console.log(`Dot style: ${config.dotStyle}, Color: ${config.dotColor}`);
if (config.gradient) {
console.log(`Gradient: ${config.gradient.type} with ${config.gradient.colors?.length || 2} colors`);
}
if (config.logoPath) {
console.log(`Logo: ${config.logoPath} (${Math.round(config.logoSize * 100)}% size)`);
}
// Build QR code styling options
const qrOptions = {
width: config.size,
height: config.size,
data: text,
margin: config.margin,
qrOptions: {
errorCorrectionLevel: config.errorCorrectionLevel,
},
dotsOptions: {
type: config.dotStyle,
color: config.dotColor,
...(config.gradient && { gradient: buildGradient(config.gradient, config.dotColor) }),
},
cornersSquareOptions: {
type: config.cornerSquareStyle,
color: config.cornerSquareColor,
},
cornersDotOptions: {
type: config.cornerDotStyle,
color: config.cornerDotColor,
},
backgroundOptions: {
color: config.backgroundColor,
},
};
// Add logo if specified
if (config.logoPath) {
// Check if logo exists
if (!fs.existsSync(config.logoPath)) {
console.error(`Error: Logo file not found: ${config.logoPath}`);
process.exit(1);
}
qrOptions.image = config.logoPath;
qrOptions.imageOptions = {
crossOrigin: "anonymous",
margin: 5,
imageSize: config.logoSize,
hideBackgroundDots: true,
};
// Bump error correction for logo QR codes
qrOptions.qrOptions.errorCorrectionLevel = config.errorCorrectionLevel === "L" ? "M" : config.errorCorrectionLevel;
}
// Generate filename from text content
const slug = text
.toLowerCase()
.replace(/^https?:\/\//, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 40);
const filename = `qr-${slug || "code"}.${config.format}`;
// Ensure output directory exists
fs.mkdirSync(outputDir, { recursive: true });
const outputPath = path.join(outputDir, filename);
// Create QR code
const qrCode = new QRCodeStyling({
jsdom: JSDOM,
nodeCanvas: await import("canvas"),
...qrOptions,
});
// Generate and save
const buffer = await qrCode.getRawData(config.format);
fs.writeFileSync(outputPath, buffer);
console.log(`✓ Saved: ${outputPath}`);
console.log(
JSON.stringify({
success: true,
path: outputPath,
text: text,
filename: filename,
format: config.format,
size: config.size,
style: {
dotStyle: config.dotStyle,
dotColor: config.dotColor,
cornerStyle: config.cornerSquareStyle,
backgroundColor: config.backgroundColor,
hasGradient: !!config.gradient,
hasLogo: !!config.logoPath,
},
})
);
}
main().catch((err) => {
console.error("Failed:", err.message);
process.exit(1);
});