captcha-canvas
Version:
A captcha generator by using skia-canvas module.
529 lines (528 loc) • 17.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CaptchaGenerator = void 0;
const skia_canvas_1 = require("skia-canvas");
const _1 = require(".");
const constants_1 = require("./constants");
const util_1 = require("./util");
/**
* Advanced CAPTCHA generator class with fluent API for creating highly customizable CAPTCHAs.
*
* This class provides a builder pattern interface for generating secure CAPTCHA images
* with extensive customization options including text styling, background images,
* trace lines, and decoy characters for enhanced security.
*
* @example Basic usage
* ```typescript
* const captcha = new CaptchaGenerator()
* .setDimension(300, 100)
* .setCaptcha({ text: 'HELLO', size: 50 });
*
* const buffer = await captcha.generate();
* console.log('Generated text:', captcha.text);
* ```
*
* @example Advanced customization
* ```typescript
* const captcha = new CaptchaGenerator({ width: 400, height: 150 })
* .setCaptcha({
* text: 'SECURE',
* size: 60,
* colors: ['#ff6b6b', '#4ecdc4', '#45b7d1'],
* rotate: 15,
* skew: true
* })
* .setTrace({ color: '#95a5a6', size: 3, opacity: 0.7 })
* .setDecoy({ total: 50, opacity: 0.4 })
* .setBackground('./background.jpg');
*
* const buffer = await captcha.generate();
* ```
*
* @since 2.0.0
*/
class CaptchaGenerator {
/**
* Creates a new CaptchaGenerator instance with specified dimensions.
*
* Initializes the generator with default settings and random text generation.
* All configuration methods can be chained after construction for fluent API usage.
*
* @param options - Configuration options for the CAPTCHA generator
* @param options.height - Height of the CAPTCHA image in pixels (default: 100)
* @param options.width - Width of the CAPTCHA image in pixels (default: 300)
*
* @example Basic initialization
* ```typescript
* const captcha = new CaptchaGenerator();
* // Creates 300x100 CAPTCHA with default settings
* ```
*
* @example Custom dimensions
* ```typescript
* const captcha = new CaptchaGenerator({ height: 200, width: 600 });
* // Creates 600x200 CAPTCHA
* ```
*
* @since 2.0.0
*/
constructor(options = { height: 100, width: 300 }) {
this.height = options.height;
this.width = options.width;
this.captcha = constants_1.defaultCaptchaOption;
this.trace = constants_1.defaultTraceOptions;
this.decoy = constants_1.defaultDecoyOptions;
if (!Array.isArray(this.captcha)) {
this.captcha.text = (0, util_1.randomText)(this.captcha.characters || 6);
}
}
/**
* Gets the current CAPTCHA text that will be displayed in the image.
*
* This property returns the text that users need to enter to solve the CAPTCHA.
* For array-based captchas with multiple text segments, it returns the concatenated result.
*
* @returns The complete CAPTCHA text string
*
* @example
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'HELLO' });
*
* console.log(captcha.text); // Output: "HELLO"
* ```
*
* @example Array-based captcha
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha([
* { text: 'HE', color: '#ff0000' },
* { text: 'LLO', color: '#00ff00' }
* ]);
*
* console.log(captcha.text); // Output: "HELLO"
* ```
*
* @since 2.0.3
*/
get text() {
if (Array.isArray(this.captcha)) {
return this.captcha.map((c) => c.text || "").join("");
}
return this.captcha.text;
}
/**
* Sets the dimensions of the CAPTCHA image.
*
* This method allows you to customize the width and height of the generated CAPTCHA image.
* Larger dimensions provide more space for text and security features but result in larger file sizes.
*
* @param height - Height of the CAPTCHA image in pixels
* @param width - Width of the CAPTCHA image in pixels
* @returns The CaptchaGenerator instance for method chaining
*
* @example Standard web form size
* ```typescript
* const captcha = new CaptchaGenerator()
* .setDimension(150, 400);
* ```
*
* @example Large size for better readability
* ```typescript
* const captcha = new CaptchaGenerator()
* .setDimension(200, 600)
* .setCaptcha({ size: 80 }); // Larger text for larger canvas
* ```
*
* @example Mobile-friendly dimensions
* ```typescript
* const captcha = new CaptchaGenerator()
* .setDimension(100, 250);
* ```
*
* @since 2.0.0
*/
setDimension(height, width) {
this.height = height;
this.width = width;
return this;
}
/**
* Sets a background image for the CAPTCHA to increase visual complexity and security.
*
* Background images make it significantly harder for OCR systems to automatically
* solve the CAPTCHA while maintaining human readability. The image will be scaled
* to fit the CAPTCHA dimensions.
*
* @param image - Image source as file path, URL, or Buffer
* @returns The CaptchaGenerator instance for method chaining
*
* @example Using a local file path
* ```typescript
* const captcha = new CaptchaGenerator()
* .setBackground('./assets/noise-pattern.jpg')
* .setCaptcha({ opacity: 0.9 }); // Make text more visible over background
* ```
*
* @example Using a URL
* ```typescript
* const captcha = new CaptchaGenerator()
* .setBackground('https://example.com/background.png');
* ```
*
* @example Using a Buffer
* ```typescript
* import fs from 'fs';
*
* const imageBuffer = fs.readFileSync('./background.jpg');
* const captcha = new CaptchaGenerator()
* .setBackground(imageBuffer);
* ```
*
* @since 2.0.0
*/
setBackground(image) {
this.background = image;
return this;
}
/**
* Configures the CAPTCHA text appearance and content.
*
* This method allows extensive customization of the CAPTCHA text including font,
* size, colors, rotation, skewing, and the actual text content. Supports both
* single configuration objects and arrays for multi-styled text segments.
*
* @param option - Single captcha configuration or array of configurations for multi-styled text
* @returns The CaptchaGenerator instance for method chaining
*
* @example Basic text customization
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({
* text: 'SECURE',
* font: 'Arial',
* size: 60,
* color: '#2c3e50'
* });
* ```
*
* @example Advanced styling with rotation and skewing
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({
* text: 'VERIFY',
* size: 50,
* rotate: 20, // Random rotation up to ±20 degrees
* skew: true, // Apply random skewing
* opacity: 0.8 // Semi-transparent text
* });
* ```
*
* @example Multi-color text using colors array
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({
* text: 'RAINBOW',
* size: 45,
* colors: ['#e74c3c', '#f39c12', '#f1c40f', '#27ae60', '#3498db', '#9b59b6']
* });
* ```
*
* @example Multi-styled text segments
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha([
* { text: 'SEC', size: 50, color: '#e74c3c', font: 'Arial' },
* { text: 'URE', size: 45, color: '#27ae60', font: 'Times' }
* ]);
* ```
*
* @example Random text generation
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({
* characters: 8, // Generate 8 random characters
* size: 40,
* colors: ['#34495e', '#e67e22']
* });
* ```
*
* @since 2.0.0
*/
setCaptcha(option) {
if (Array.isArray(option)) {
this.captcha = option.map((o) => ({ ...constants_1.defaultCaptchaOption, ...o }));
for (const o of this.captcha) {
if (!o.text)
throw new Error("Each captcha option in array must have a text property.");
o.characters = o.text.length;
}
}
else {
this.captcha = {
...(Array.isArray(this.captcha) ? constants_1.defaultCaptchaOption : this.captcha),
...option,
};
if (!Array.isArray(this.captcha)) {
if (option.text)
this.captcha.characters = option.text.length;
else if (this.captcha.characters)
this.captcha.text = (0, util_1.randomText)(this.captcha.characters);
}
}
return this;
}
/**
* Configures trace lines that connect CAPTCHA characters for enhanced security.
*
* Trace lines are drawn connecting the CAPTCHA characters, making it significantly
* harder for automated systems to segment and recognize individual characters while
* maintaining human readability.
*
* @param option - Trace line appearance configuration
* @returns The CaptchaGenerator instance for method chaining
*
* @example Basic trace line
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'HELLO' })
* .setTrace({ color: '#95a5a6', size: 3 });
* ```
*
* @example Subtle trace for better readability
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'VERIFY' })
* .setTrace({
* color: '#bdc3c7',
* size: 2,
* opacity: 0.6 // Semi-transparent trace
* });
* ```
*
* @example Bold security trace
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'SECURE' })
* .setTrace({
* color: '#e74c3c',
* size: 5,
* opacity: 0.8
* });
* ```
*
* @example Disable trace lines
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'CLEAN' })
* .setTrace({ opacity: 0 }); // Completely transparent = disabled
* ```
*
* @since 2.0.0
*/
setTrace(option) {
this.trace = { ...this.trace, ...option };
return this;
}
/**
* Configures decoy characters that add visual noise to prevent automated solving.
*
* Decoy characters are randomly placed fake characters that confuse OCR systems
* while being distinguishable from the actual CAPTCHA text by humans through
* differences in opacity, size, or color.
*
* @param option - Decoy characters configuration
* @returns The CaptchaGenerator instance for method chaining
*
* @example Basic decoy setup
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'HELLO' })
* .setDecoy({
* total: 30,
* opacity: 0.3,
* color: '#95a5a6'
* });
* ```
*
* @example Subtle decoys for clean look
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'VERIFY' })
* .setDecoy({
* total: 15,
* size: 12,
* opacity: 0.2,
* font: 'Arial'
* });
* ```
*
* @example Heavy security with many decoys
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'SECURE' })
* .setDecoy({
* total: 50,
* size: 18,
* opacity: 0.4,
* color: '#7f8c8d'
* });
* ```
*
* @example Disable decoy characters
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'SIMPLE' })
* .setDecoy({ opacity: 0 }); // No decoy characters
* ```
*
* @since 2.0.0
*/
setDecoy(option) {
this.decoy = { ...this.decoy, ...option };
return this;
}
/**
* Generates the final CAPTCHA image as a PNG buffer asynchronously.
*
* This method renders all configured elements (background, decoys, text, traces)
* into a final PNG image buffer that can be saved to file or sent to clients.
* The generation process is asynchronous to handle background image loading.
*
* @returns Promise that resolves to a PNG image buffer
*
* @example Basic generation and file saving
* ```typescript
* import fs from 'fs';
*
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'HELLO' });
*
* const buffer = await captcha.generate();
* fs.writeFileSync('captcha.png', buffer);
* console.log('CAPTCHA text:', captcha.text);
* ```
*
* @example Web server response
* ```typescript
* import express from 'express';
*
* app.get('/captcha', async (req, res) => {
* const captcha = new CaptchaGenerator()
* .setDimension(300, 100)
* .setCaptcha({ characters: 6 });
*
* const buffer = await captcha.generate();
*
* // Store captcha.text in session for verification
* req.session.captcha = captcha.text;
*
* res.type('png').send(buffer);
* });
* ```
*
* @example Complete customization
* ```typescript
* const captcha = new CaptchaGenerator({ width: 400, height: 150 })
* .setBackground('./noise-bg.jpg')
* .setCaptcha({
* text: 'SECURE',
* size: 60,
* colors: ['#e74c3c', '#3498db'],
* rotate: 15
* })
* .setTrace({ color: '#95a5a6', size: 3 })
* .setDecoy({ total: 40, opacity: 0.3 });
*
* const buffer = await captcha.generate();
* ```
*
* @throws {Error} When background image fails to load or other rendering errors occur
*
* @since 2.0.0
*/
async generate() {
const captchaCanvas = new _1.Captcha(this.width, this.height);
if (this.background)
captchaCanvas.drawImage(await (0, skia_canvas_1.loadImage)(this.background));
if (this.decoy.opacity)
captchaCanvas.addDecoy(this.decoy);
if (this.captcha)
captchaCanvas.drawCaptcha(this.captcha);
if (this.trace.opacity)
captchaCanvas.drawTrace(this.trace);
return captchaCanvas.png;
}
/**
* Generates the CAPTCHA image synchronously without async/await.
*
* This method provides synchronous CAPTCHA generation for use cases where
* async operations are not suitable. Note that background images set via
* `setBackground()` are ignored - use the `background` parameter instead.
*
* @param option - Additional options for synchronous generation
* @param option.background - Pre-loaded background image (use `resolveImage()` to load)
* @returns PNG image buffer
*
* @example Basic synchronous generation
* ```typescript
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'SYNC' });
*
* const buffer = captcha.generateSync();
* fs.writeFileSync('captcha.png', buffer);
* ```
*
* @example With pre-loaded background image
* ```typescript
* import { CaptchaGenerator, resolveImage } from 'captcha-canvas';
*
* // Load background image first
* const backgroundImage = await resolveImage('./background.jpg');
*
* const captcha = new CaptchaGenerator()
* .setCaptcha({ text: 'HELLO' });
*
* // Generate synchronously with background
* const buffer = captcha.generateSync({ background: backgroundImage });
* fs.writeFileSync('captcha.png', buffer);
* ```
*
* @example Synchronous generation in non-async context
* ```typescript
* function createCaptchaSync() {
* const captcha = new CaptchaGenerator()
* .setDimension(250, 80)
* .setCaptcha({ characters: 5, size: 35 })
* .setTrace({ size: 2, opacity: 0.7 });
*
* return {
* buffer: captcha.generateSync(),
* text: captcha.text
* };
* }
* ```
*
*
* **Note:** Background images set via `setBackground()` are not used in sync mode.
* Use the `background` parameter with pre-loaded images instead.
*
* @since 2.2.0
*/
generateSync(option = {}) {
const captchaCanvas = new _1.Captcha(this.width, this.height, Array.isArray(this.captcha)
? this.captcha.reduce((acc, val) => acc + (val.characters || 0), 0)
: this.captcha.characters);
captchaCanvas.async = false;
if (option.background)
captchaCanvas.drawImage(option.background);
if (this.decoy.opacity)
captchaCanvas.addDecoy(this.decoy);
if (this.captcha)
captchaCanvas.drawCaptcha(this.captcha);
if (this.trace.opacity)
captchaCanvas.drawTrace(this.trace);
return captchaCanvas.png;
}
}
exports.CaptchaGenerator = CaptchaGenerator;