Merge character paths and join them with random lines

Remove color option
Make stroke and fill styling optional
Change README and links to this repo, but refer to the original svg-captcha in README
Change example pics to our current captcha
This commit is contained in:
HF 2021-02-03 20:30:49 +01:00
parent a5257ace35
commit 7c6601d2d5
16 changed files with 400 additions and 221 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/node_modules
test.*
\.idea/

View File

@ -1,3 +1,14 @@
1.6.0 / 2021-02-03
==================
* Merge character paths together
* Join them with a randomly generated path
1.5.0 / 2020-03-23
==================
* Fixed [#45](https://github.com/produck/svg-captcha/issues/45)
1.4.0 / 2019-05-03
===================
@ -32,4 +43,4 @@
===================
* Bump `opentype.js@0.7.1`
* Fixed some code style inconsistence
* Fixed some code style inconsistence

View File

@ -1,17 +1,26 @@
![svg-captcha](media/header.png)
<div align="center">
# ppfun-captcha
[![Build Status](https://img.shields.io/travis/lemonce/svg-captcha/master.svg?style=flat-square)](https://travis-ci.org/lemonce/svg-captcha)
[![NPM Version](https://img.shields.io/npm/v/svg-captcha.svg?style=flat-square)](https://www.npmjs.com/package/svg-captcha)
[![NPM Downloads](https://img.shields.io/npm/dm/svg-captcha.svg?style=flat-square)](https://www.npmjs.com/package/svg-captcha)
</div>
> generate single-path svg captcha in node.js
> generate svg captcha in node.js
## sample image
## Translations
[中文](README_CN.md)
default captcha image:
![image](media/example.png)
with using fill instead of stroke:
![image2](media/example-2.png)
## Origin
Credits go to the original author [produck](https://github.com/produck).
This is a fork with added features and fixes for [svg-captcha](https://github.com/produck/svg-captcha) which didn't merge important [Bugfixes](https://github.com/produck/svg-captcha/pull/47) and is at the current published state [cracked](https://github.com/produck/svg-captcha/issues/45).
If you want the original svg-capcha, check out [svg-captcha-fixed](https://www.npmjs.com/package/svg-captcha-fixed).
## useful if you
@ -19,19 +28,23 @@
- have issue with install c++ addon
## install
```
npm install --save svg-captcha
npm install --save ppfun-captcha
```
## usage
```Javascript
var svgCaptcha = require('svg-captcha');
var svgCaptcha = require('ppfun-captcha');
var captcha = svgCaptcha.create();
console.log(captcha);
// {data: '<svg.../svg>', text: 'abcd'}
```
with express
```Javascript
var svgCaptcha = require('svg-captcha');
@ -51,8 +64,9 @@ If no option is passed, you will get a random string of four characters and corr
* `size`: 4 // size of random string
* `ignoreChars`: '0o1i' // filter out some characters like 0o1i
* `color`: true // characters will have distinct colors instead of grey, true if background option is set
* `background`: '#cc9966' // background color of the svg image
* `stroke`: 'black' // style/color of the svg path stroke
* `fill`: 'black' // style/color of the svg strokes fill
This function returns an object that has the following property:
* `data`: string // svg path data
@ -93,30 +107,24 @@ return a svg captcha based on text provided.
In pre 1.1.0 version you have to call these two functions,
now you can call create() to save some key strokes ;).
## sample image
default captcha image:
## FAQ
![image](media/example.png)
math expression image with color options:
![image2](media/example-2.png)
## why use svg?
It does not require any c++ addon.
The result image is smaller than jpeg image.
> This has to be a joke. /\<text.+\>;.+\<\/text\>/g.test...
### This has to be a joke. /\<text.+\>;.+\<\/text\>/g.test...
svg captcha uses opentype.js underneath, which means that there is no
'&lt;text&gt;1234&lt;/text&gt;'.
You get
'&lt;path fill="#444" d="M104.83 19.74L107.85 19.74L112 33.56L116.13 19.74L119.15 19.74L113.48 36.85...'
instead.
Even though you can write a program that convert svg to png, svg captcha has done its job
—— make captcha recognition harder
### Why no noise line option?
Noise lines can be easily filtered by path length and we decided to rather join characters with random paths, in order to to make the whole svg one single continuous path, which has the same purpose.
### Why no colors?
Colors don't add any security and can be easily changed to black & white anyway. The color of the stroke and fill can be manually choosen if it is neccessary for custom styles.
## License
[MIT](LICENSE.md)

16
index.d.ts vendored
View File

@ -23,12 +23,16 @@ declare class ConfigObject {
* random character preset
*/
charPreset?: string;
/**
* default: false
* if false, captcha will be black and white
* otherwise, it will be randomly colorized
*/
color?: boolean;
/**
* default: 'black'
* Stroke Style of svg path, can be color or none
*/
stroke?: string;
/**
* default: 'none'
* Fill Style of svg path, can be color or none
*/
fill?: string;
/**
* default: false
* if set to true, it will draw with light grey color

View File

@ -1,141 +1,6 @@
'use strict';
const assert = require('assert');
function rndPathCmd(cmd) {
const r = (Math.random() * 0.2) - 0.1;
switch (cmd.type) {
case 'M':
case 'L':
cmd.x += r;
cmd.y += r;
break;
case 'Q':
case 'C':
cmd.x += r;
cmd.y += r;
cmd.x1 += r;
cmd.y1 += r;
break;
default:
// Close path cmd
break;
}
return cmd;
}
//https://riptutorial.com/zh-CN/html5-canvas/example/19077/%E5%9C%A8%E4%BD%8D%E7%BD%AE%E6%8B%86%E5%88%86%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF
function splitQuadraticBezier(position, x1, y1, x2, y2, x3, y3) {
let v1, v2, v3, retPoints, i, c;
if (position <= 0 || position >= 1) {
throw RangeError("spliteCurveAt requires position > 0 && position < 1");
}
retPoints = []; // array of coordinates
i = 0;
v1 = {};
v2 = {};
v3 = {};
v1.x = x1;
v1.y = y1;
v2.x = x2;
v2.y = y2;
v3.x = x3;
v3.y = y3;
c = position;
retPoints[i++] = v1.x; // start point
retPoints[i++] = v1.y;
retPoints[i++] = (v1.x += (v2.x - v1.x) * c); // new control point for first curve
retPoints[i++] = (v1.y += (v2.y - v1.y) * c);
v2.x += (v3.x - v2.x) * c;
v2.y += (v3.y - v2.y) * c;
retPoints[i++] = v1.x + (v2.x - v1.x) * c; // new end and start of first and second curves
retPoints[i++] = v1.y + (v2.y - v1.y) * c;
retPoints[i++] = v2.x; // new control point for second curve
retPoints[i++] = v2.y;
retPoints[i++] = v3.x; // new endpoint of second curve
retPoints[i++] = v3.y;
return retPoints;
}
function randomRange(min, max) {
return Math.random() * (max - min) + min;
}
function randomizePathNodes(commands, opts) {
// 随机化路径节点
// 规则:
// 如果当前节点是 L(Line),下一个节点也是 Line那么随机插入一个中间点
// 如果当前节点是 Q且前节点为 L 或 M那么拆分这个曲线
const result = [];
for (let i = 0; i < commands.length - 1; i++) {
const command = commands[i];
if (command.type === "L") {
const next = commands[i + 1];
if (next.type === "L" && Math.random() > opts.truncateLineProbability) {
const r = randomRange(-0.1, 0.1);
result.push(command);
result.push({
type: "L",
x: (command.x + next.x) / 2 + r,
y: (command.y + next.y) / 2 + r,
});
} else {
result.push(command);
}
} else if (command.type === "Q" && i >= 1) {
const prev = commands[i - 1];
if ((prev.type === "L" || prev.type === "M") && Math.random() > opts.truncateCurveProbability) {
const p0_x = prev.x;
const p0_y = prev.y;
const r = randomRange(-0.1, 0.1);
const cp_x = command.x1 + r;
const cp_y = command.y1 + r;
const p1_x = command.x + r;
const p1_y = command.y + r;
const newCurve = splitQuadraticBezier(randomRange(opts.truncateCurvePositionMin, opts.truncateCurvePositionMax), p0_x, p0_y, cp_x, cp_y, p1_x, p1_y);
const q1 = {
type: "Q",
x1: newCurve[2],
y1: newCurve[3],
x: newCurve[4],
y: newCurve[5],
};
const l1 = {
type: "L",
x: newCurve[4],
y: newCurve[5],
};
const q2 = {
type: "Q",
x1: newCurve[6],
y1: newCurve[7],
x: newCurve[8],
y: newCurve[9],
};
const l2 = {
type: "L",
x: newCurve[8],
y: newCurve[9],
};
result.push(q1);
// 插入一个 L 是因为貌似原本的 Path 里不会存在连续的 QQ
result.push(l1);
result.push(q2);
result.push(l2);
}
} else {
result.push(command)
}
}
return result;
}
module.exports = function (text, opts) {
const ch = text[0];
assert(ch, 'expect a string');
@ -150,11 +15,6 @@ module.exports = function (text, opts) {
const height = (opts.ascender + opts.descender) * fontScale;
const top = opts.y + (height / 2);
const path = glyph.getPath(left, top, fontSize);
// Randomize path commands
path.commands.forEach(rndPathCmd);
path.commands = randomizePathNodes(path.commands, opts);
const pathData = path.toPathData();
return pathData;
return path;
};

View File

@ -1,15 +1,14 @@
'use strict';
const chToPath = require('./ch-to-path');
const randomizePath = require('./randomize-path');
const random = require('./random');
const optionMngr = require('./option-manager');
const opts = optionMngr.options;
const getText = function (text, width, height, options) {
const getTextPath = function (text, width, height, options) {
const len = text.length;
const spacing = (width - 2) / (len + 1);
const min = options.inverse ? 10 : 0;
const max = options.inverse ? 14 : 4;
let i = -1;
const out = [];
@ -17,33 +16,49 @@ const getText = function (text, width, height, options) {
const x = spacing * (i + 1);
const y = height / 2;
const charPath = chToPath(text[i], Object.assign({x, y}, options));
const color = options.color ?
random.color(options.background) : random.greyColor(min, max);
out.push(`<path fill="${color}" d="${charPath}"/>`);
out.push(charPath);
}
return out;
};
function mergePaths(paths) {
if (!paths.length) {
return [];
}
const out = paths[0];
for (let i = 1; i < paths.length; i += 1) {
out.commands = out.commands.concat(
paths[i].commands,
);
}
return out;
}
const createCaptcha = function (text, options) {
text = text || random.captchaText();
options = Object.assign({}, opts, options);
const width = options.width;
const height = options.height;
const bg = options.background;
if (bg) {
options.color = true;
}
const bgRect = bg ?
`<rect width="100%" height="100%" fill="${bg}"/>` : '';
const paths =
[].concat(getText(text, width, height, options))
.sort(() => Math.random() - 0.5)
.join('');
/* Create character paths and order them randomly */
let path =
[].concat(getTextPath(text, width, height, options))
.sort(() => Math.random() - 0.5);
/* Join paths together to one */
path = mergePaths(path);
/* Randomize nodes and randomly split them */
path = randomizePath(path, options);
/* Join characters with random lines */
path = randomizePath.removeGaps(path);
/* Create xml */
const start = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0,0,${width},${height}">`;
const xml = `${start}${bgRect}${paths}</svg>`;
path = `<path fill="${options.fill}" stroke="${options.stroke}" d="${path.toPathData()}"/>`;
const xml = `${start}${bgRect}${path}</svg>`;
return xml;
};
@ -66,6 +81,7 @@ const createMathExpr = function (options) {
module.exports = createCaptcha;
module.exports.randomText = random.captchaText;
module.exports.create = create;
module.exports.createMathExpr = createMathExpr;
module.exports.options = opts;
module.exports.loadFont = optionMngr.loadFont;

View File

@ -11,7 +11,8 @@ const descender = font.descender;
const options = {
width: 150,
height: 50,
color: false,
stroke: 'black',
fill: 'none',
background: '',
size: 4,
ignoreChars: '',
@ -20,7 +21,7 @@ const options = {
truncateLineProbability: 0.5,
truncateCurveProbability: 0.5,
truncateCurvePositionMin: 0.4,
truncateCurvePositionMax: 0.6,
truncateCurvePositionMax: 0.6
};
const loadFont = filepath => {

View File

@ -44,17 +44,17 @@ exports.captchaText = function (options) {
return out;
};
const mathExprPlus = function(leftNumber, rightNumber){
const mathExprPlus = function (leftNumber, rightNumber) {
const text = (leftNumber + rightNumber).toString();
const equation = leftNumber + '+' + rightNumber;
return {text, equation}
}
return {text, equation};
};
const mathExprMinus = function(leftNumber, rightNumber){
const mathExprMinus = function (leftNumber, rightNumber) {
const text = (leftNumber - rightNumber).toString();
const equation = leftNumber + '-' + rightNumber;
return {text, equation}
}
return {text, equation};
};
/**
* Creates a simple math expression using either the + or - operator
@ -69,11 +69,11 @@ exports.mathExpr = function (min, max, operator) {
operator = operator || '+';
const left = randomInt(min, max);
const right = randomInt(min, max);
switch(operator){
switch (operator) {
case '+':
return mathExprPlus(left, right)
return mathExprPlus(left, right);
case '-':
return mathExprMinus(left, right)
return mathExprMinus(left, right);
default:
return (randomInt(1, 2) % 2) ? mathExprPlus(left, right) : mathExprMinus(left, right);
}

259
lib/randomize-path.js Normal file
View File

@ -0,0 +1,259 @@
'use strict';
const assert = require('assert');
function rndPathCmd(cmd) {
const r = (Math.random() * 0.2) - 0.1;
switch (cmd.type) {
case 'M':
case 'L':
cmd.x += r;
cmd.y += r;
break;
case 'Q':
case 'C':
cmd.x += r;
cmd.y += r;
cmd.x1 += r;
cmd.y1 += r;
break;
default:
// Close path cmd
break;
}
return cmd;
}
// https://riptutorial.com/zh-CN/html5-canvas/example/19077/%E5%9C%A8%E4%BD%8D%E7%BD%AE%E6%8B%86%E5%88%86%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF
function splitQuadraticBezier(position, x1, y1, x2, y2, x3, y3) {
let v1, v2, v3, retPoints, i, c;
if (position <= 0 || position >= 1) {
throw new RangeError('spliteCurveAt requires position > 0 && position < 1');
}
retPoints = []; // Array of coordinates
i = 0;
v1 = {};
v2 = {};
v3 = {};
v1.x = x1;
v1.y = y1;
v2.x = x2;
v2.y = y2;
v3.x = x3;
v3.y = y3;
c = position;
retPoints[i++] = v1.x; // Start point
retPoints[i++] = v1.y;
retPoints[i++] = (v1.x += (v2.x - v1.x) * c); // New control point for first curve
retPoints[i++] = (v1.y += (v2.y - v1.y) * c);
v2.x += (v3.x - v2.x) * c;
v2.y += (v3.y - v2.y) * c;
retPoints[i++] = v1.x + (v2.x - v1.x) * c; // New end and start of first and second curves
retPoints[i++] = v1.y + (v2.y - v1.y) * c;
retPoints[i++] = v2.x; // New control point for second curve
retPoints[i++] = v2.y;
retPoints[i++] = v3.x; // New endpoint of second curve
retPoints[i++] = v3.y;
return retPoints;
}
function randomRange(min, max) {
return Math.random() * (max - min) + min;
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function randomizePathNodes(commands, opts) {
// 随机化路径节点
// 规则:
// 如果当前节点是 L(Line),下一个节点也是 Line那么随机插入一个中间点
// 如果当前节点是 Q且前节点为 L 或 M那么拆分这个曲线
const result = [];
for (let i = 0; i < commands.length - 1; i++) {
const command = commands[i];
if (command.type === 'L') {
const next = commands[i + 1];
if (next.type === 'L' && Math.random() > opts.truncateLineProbability) {
const r = randomRange(-0.1, 0.1);
result.push(command);
result.push({
type: 'L',
x: (command.x + next.x) / 2 + r,
y: (command.y + next.y) / 2 + r
});
} else {
result.push(command);
}
} else if (command.type === 'Q' && i >= 1) {
const prev = commands[i - 1];
if ((prev.type === 'L' || prev.type === 'M') && Math.random() > opts.truncateCurveProbability) {
const p0_x = prev.x;
const p0_y = prev.y;
const r = randomRange(-0.1, 0.1);
const cp_x = command.x1 + r;
const cp_y = command.y1 + r;
const p1_x = command.x + r;
const p1_y = command.y + r;
const newCurve = splitQuadraticBezier(randomRange(opts.truncateCurvePositionMin, opts.truncateCurvePositionMax), p0_x, p0_y, cp_x, cp_y, p1_x, p1_y);
const q1 = {
type: 'Q',
x1: newCurve[2],
y1: newCurve[3],
x: newCurve[4],
y: newCurve[5]
};
const l1 = {
type: 'L',
x: newCurve[4],
y: newCurve[5]
};
const q2 = {
type: 'Q',
x1: newCurve[6],
y1: newCurve[7],
x: newCurve[8],
y: newCurve[9]
};
const l2 = {
type: 'L',
x: newCurve[8],
y: newCurve[9]
};
result.push(q1);
// 插入一个 L 是因为貌似原本的 Path 里不会存在连续的 QQ
// result.push(l1);
result.push(q2);
// Result.push(l2);
}
} else {
result.push(command);
}
}
return result;
}
/*
* Connect two points with a random path
* @param avgDist average distance between nodes
* @param qDist ratio of curves to lines
* @return array of path commands
*/
function connectPoints(xa, ya, xb, yb, qDist, avgDist) {
const dist = distance(xa, ya, xb, yb);
const min = avgDist / 15;
const max = avgDist * 4;
let drawDist = randomRange(min, max);
let xp = xa;
let yp = ya;
const path = [];
while (drawDist < dist) {
const ratio = drawDist / dist;
const x = (xb - xa) * ratio + xa + Math.random() * 8 - 4;
const y = (yb - ya) * ratio + ya + Math.random() * 8 - 4;
const point = {};
if (Math.random() < qDist) {
const x1 = randomRange(xp, x) + Math.random() * 2 - 1;
const y1 = randomRange(yp, y) + Math.random() * 2 - 1;
point.type = 'Q';
point.x1 = x1;
point.y1 = y1;
} else {
point.type = 'L';
}
point.x = x;
point.y = y;
path.push(point);
xp = x;
yp = y;
drawDist += randomRange(min, max);
}
path.push({type: 'L', x: xb, y: yb});
return path;
}
/*
* Removes gaps in path (Z and following M command)
*/
function removeGaps(path) {
if (!path.commands.length) {
return path;
}
let commands = [path.commands[0]];
// Calculate metadata of path and filter zero-length paths
let points = 0;
let qCount = 0;
let length = 0;
let i = 1;
while (i < path.commands.length) {
const command = path.commands[i];
const type = command.type;
commands.push(command);
if (type === 'L' || type === 'Q') {
const prevCommand = path.commands[i - 1];
if (prevCommand.x) {
const dist = distance(
prevCommand.x, prevCommand.y, command.x, command.y,
);
if (!dist) {
commands.pop();
} else {
points += 1;
if (type === 'Q') {
qCount += 1;
}
length += dist;
}
}
}
i += 1;
}
const avgDist = length / points;
const qDist = qCount / points;
console.log(`qCount: ${qCount} / ${points} = ${qDist}`);
console.log(`avgDist: ${avgDist}`);
commands = [path.commands[0]];
path.commands.push(path.commands[0]);
i = 1;
while (i < path.commands.length) {
const command = path.commands[i];
if (command.type === 'Z') {
/*
* Is it save to assume that every Z command is always
* leaded and followed by a command with x,y?
* Might not be save outside of glyphs.
*/
const prevCommand = path.commands[i - 1];
const nextCommand = path.commands[i += 1];
console.log(`Starting point: ${prevCommand.x} ${prevCommand.y}`);
console.log(`End point: ${nextCommand.x} ${nextCommand.y}`);
const points = connectPoints(
prevCommand.x, prevCommand.y,
nextCommand.x, nextCommand.y,
qDist, avgDist,
);
console.log(points);
commands = commands.concat(points);
} else {
commands.push(command);
}
i += 1;
}
path.commands = commands;
return path;
}
module.exports = function (path, opts) {
// Randomize path commands
path.commands.forEach(rndPathCmd);
path.commands = randomizePathNodes(path.commands, opts);
return path;
};
module.exports.removeGaps = removeGaps;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

41
package-lock.json generated
View File

@ -2288,7 +2288,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -2309,12 +2310,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2329,17 +2332,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2456,7 +2462,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2468,6 +2475,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2482,6 +2490,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2489,12 +2498,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2513,6 +2524,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2593,7 +2605,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2605,6 +2618,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2690,7 +2704,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -2726,6 +2741,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2745,6 +2761,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -2788,12 +2805,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -1,7 +1,7 @@
{
"name": "svg-captcha",
"version": "1.4.0",
"description": "generate svg captcha in node.js or express.js",
"name": "ppfun-captcha",
"version": "1.6.0",
"description": "Generate single-path svg captcha in node.js or express.js",
"main": "index.js",
"scripts": {
"test": "jest",
@ -19,8 +19,8 @@
"captcha alternative"
],
"author": {
"name": "Weilin Shi",
"email": "934587911@qq.com"
"name": "ppfun",
"email": "pixelplanetdev@gmail.com"
},
"engines": {
"node": ">=4.x"

View File

@ -29,7 +29,7 @@ test('Generate math expression using default values 1, 9 and +', () => {
test('Generate math expression using non default numbers but default +, 1, 20', () => {
for (let i = 0; i < 10; i++) {
const expr = random.mathExpr(1,20);
const expr = random.mathExpr(1, 20);
expect(expr.text).toMatch(/^-?[0-9]\d*$/);
expect(expr.equation).toMatch(/^\d+[+]\d+$/);
}
@ -37,7 +37,7 @@ test('Generate math expression using non default numbers but default +, 1, 20',
test('Generate math expression using non default numbers with minus', () => {
for (let i = 0; i < 10; i++) {
const expr = random.mathExpr(1,20, '-');
const expr = random.mathExpr(1, 20, '-');
expect(expr.text).toMatch(/^-?[0-9]\d*$/);
expect(expr.equation).toMatch(/^\d+[-]\d+$/);
}
@ -45,7 +45,7 @@ test('Generate math expression using non default numbers with minus', () => {
test('Generate math expression using non default numbers with "+/-"', () => {
for (let i = 0; i < 10; i++) {
const expr = random.mathExpr(1,20, '-');
const expr = random.mathExpr(1, 20, '-');
expect(expr.text).toMatch(/^-?[0-9]\d*$/);
expect(expr.equation).toMatch(/^\d+[+/-]\d+$/);
}