[refactor] use opentype.js directly with a 3d font

[add] header
This commit is contained in:
Weilin 2016-07-01 13:59:22 +08:00
parent 84d9bfd891
commit be05347d8f
11 changed files with 161 additions and 101 deletions

View File

@ -1,7 +1,9 @@
[![Build Status](https://travis-ci.org/steambap/svg-captcha.svg?branch=master)](https://travis-ci.org/steambap/svg-captcha)
![svg-captcha](media/header.png)
# svg captcha
[![Build Status](https://travis-ci.org/lemonce/svg-captcha.svg?branch=master)](https://travis-ci.org/steambap/svg-captcha)
generate svg captcha in node.js
## useful if you
@ -10,7 +12,7 @@ generate svg captcha in node.js
- have issue with install c++ addon
## usage
```
```js
var svgCaptcha = require('svg-captcha');
// generate random text of length 4
var text = svgCaptcha.randomText();
@ -18,7 +20,7 @@ var text = svgCaptcha.randomText();
var captcha = svgCaptcha(text);
```
with express
```
```js
var svgCaptcha = require('svg-captcha');
app.get('/captcha', function (req, res) {
@ -37,7 +39,18 @@ app.get('/captcha', function (req, res) {
## why use svg?
It does not require any c++ addon.
It uses opentype.js underneath and the result image is smaller than jpeg image.
The result image is smaller than jpeg image.
> 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
## Translations
[中文](README_CN.md)

View File

@ -1,7 +1,9 @@
[![Build Status](https://travis-ci.org/steambap/svg-captcha.svg?branch=master)](https://travis-ci.org/steambap/svg-captcha)
![svg-captcha](media/header.png)
# svg验证码
[![Build Status](https://travis-ci.org/lemonce/svg-captcha.svg?branch=master)](https://travis-ci.org/steambap/svg-captcha)
在node.js中生成svg格式的验证码
## 如果你遇到这些问题
@ -10,7 +12,7 @@
- 无法安装 c++ 模块
## 使用方法
```
```js
var svgCaptcha = require('svg-captcha');
// generate random text of length 4
var text = svgCaptcha.randomText();
@ -18,7 +20,7 @@ var text = svgCaptcha.randomText();
var captcha = svgCaptcha(text);
```
在 express中使用
```
```js
var svgCaptcha = require('svg-captcha');
app.get('/captcha', function (req, res) {
@ -37,7 +39,7 @@ app.get('/captcha', function (req, res) {
## 为什么使用 svg 格式?
不需要引用 c++ 模块。
使用 opentype.js了而且svg图片比jpeg格式图片要小
svg图片比jpeg格式图片要小
## Translations
[中文](README_CN.md)

BIN
fonts/Comismsh.ttf Normal file

Binary file not shown.

View File

@ -1,88 +1 @@
'use strict';
const textToSVG = require('text-to-svg').loadSync();
const random = require('./random');
const generateBackground = function (width, height) {
const seed = random.int(0, 10);
return `<filter id="n" x="0" y="0">
<feTurbulence baseFrequency=".7,.07" seed="${seed}"/>
<feColorMatrix type="luminanceToAlpha"/>
</filter>
<rect width="${width}" height="${height}" filter="url(#n)" opacity="0.2"/>`;
};
const getLineNoise = function (lv, width, height) {
const noiseString = [];
var i = -1;
while (++i < lv) {
var start = random.int(5, 25) + ' ' +
random.int(10, height - 10);
var end = random.int(width - 25, width - 5) + ' ' +
random.int(10, height - 10);
var mid1 = random.int((width / 2) - 25, (width / 2) + 25) + ' ' +
random.int(10, height - 10);
var mid2 = random.int((width / 2) - 25, (width / 2) + 25) + ' ' +
random.int(10, height - 10);
var color = random.greyColor();
noiseString.push(`<path d="M${start} C${mid1},${mid2},${end}"
stroke="${color}" fill="transparent"/>`);
}
return noiseString.join('');
};
const getSVGOptions = function (x, width, height) {
return {
x: x, y: height / 2, fontSize: Math.floor(height * 0.72),
anchor: 'center middle',
attributes: {fill: 'red', stroke: 'black'}
};
};
const getText = function (text, width, height) {
const len = text.length;
const spacing = (width - 2) / (len + 1);
var i = -1;
var out = [];
while (++i < len) {
var charPath = textToSVG.getD(text[i],
getSVGOptions((i + 1) * spacing, width, height));
// randomly scale it to 95% - 105%, skew
var randomMatrix = random.matrix();
var color = random.greyColor(0, 4);
out.push(`<path fill="${color}" d="${charPath}"
transform="matrix(${randomMatrix})"/>`);
}
return out.join('');
};
const createCaptcha = function (options) {
if (typeof options === 'string') {
options = {text: options};
}
options = options || {};
const width = options.width || 150;
const height = options.height || 50;
const noiseLv = options.noise || 3;
const text = options.text || random.captchaText();
const lineNoise = getLineNoise(noiseLv, width, height);
const bg = generateBackground(width, height);
const textPath = getText(text, width, height);
const xml = `<svg xmlns="http://www.w3.org/2000/svg"
width="${width}" height="${height}">
${textPath}
${lineNoise}
${bg}
</svg>`;
return xml.replace(/[\t]/g, '').replace(/\n(\W)/g, '$1');
};
module.exports = createCaptcha;
module.exports.randomText = random.captchaText;
module.exports = require('./lib');

87
lib/index.js Normal file
View File

@ -0,0 +1,87 @@
'use strict';
const textToPath = require('./text-to-path');
const random = require('./random');
const generateBackground = function (width, height) {
const seed = random.int(0, 10);
return `<filter id="n" x="0" y="0">
<feTurbulence baseFrequency=".7,.07" seed="${seed}"/>
<feColorMatrix type="luminanceToAlpha"/>
</filter>
<rect width="${width}" height="${height}" filter="url(#n)" opacity="0.2"/>`;
};
const getLineNoise = function (lv, width, height) {
const noiseString = [];
var i = -1;
while (++i < lv) {
var start = random.int(5, 25) + ' ' +
random.int(10, height - 10);
var end = random.int(width - 25, width - 5) + ' ' +
random.int(10, height - 10);
var mid1 = random.int((width / 2) - 25, (width / 2) + 25) + ' ' +
random.int(10, height - 10);
var mid2 = random.int((width / 2) - 25, (width / 2) + 25) + ' ' +
random.int(10, height - 10);
var color = random.greyColor();
noiseString.push(`<path d="M${start} C${mid1},${mid2},${end}"
stroke="${color}" fill="transparent"/>`);
}
return noiseString.join('');
};
const getSVGOptions = function (x, height) {
return {
x: x, y: height / 2, fontSize: Math.floor(height * 0.98)
};
};
const getText = function (text, width, height, options) {
const len = text.length;
const spacing = (width - 2) / (len + 1);
var i = -1;
var out = [];
while (++i < len) {
var charPath = textToPath(text[i],
getSVGOptions((i + 1) * spacing, height));
// randomly scale it to 95% - 105%, skew
var transform = options.transform ?
`transform="matrix(${random.matrix()})"` : '';
var color = random.greyColor(0, 4);
out.push(`<path fill="${color}" d="${charPath}"
${transform}/>`);
}
return out.join('');
};
const createCaptcha = function (options) {
if (typeof options === 'string') {
options = {text: options};
}
options = options || {};
const width = options.width || 150;
const height = options.height || 50;
const noiseLv = options.noise || 1;
const text = options.text || random.captchaText();
const lineNoise = getLineNoise(noiseLv, width, height);
const bg = options.bg ? generateBackground(width, height) : '';
const textPath = getText(text, width, height, options);
const xml = `<svg xmlns="http://www.w3.org/2000/svg"
width="${width}" height="${height}">
${textPath}
${lineNoise}
${bg}
</svg>`;
return xml.replace(/[\t]/g, '').replace(/\n(\W)/g, '$1');
};
module.exports = createCaptcha;
module.exports.randomText = random.captchaText;

45
lib/text-to-path.js Normal file
View File

@ -0,0 +1,45 @@
const path = require('path');
const opentype = require('opentype.js');
const fontPath = path.join(__dirname, '../fonts/Comismsh.ttf');
const font = opentype.loadSync(fontPath);
var getWidth = function getWidth(text, fontScale) {
var width = 0;
var glyph;
const glyphs = font.stringToGlyphs(text);
for (var i = 0; i < glyphs.length; i++) {
glyph = glyphs[i];
if (glyph.advanceWidth) {
width += glyph.advanceWidth * fontScale;
}
if (i < glyphs.length - 1) {
width += font.getKerningValue(glyph, glyphs[i + 1]) * fontScale;
}
}
return width;
};
var getHeight = function getHeight(fontScale) {
return (font.ascender + font.descender) * fontScale;
};
module.exports = function getPath(text, options) {
options = options === undefined ? {} : options;
const fontSize = options.fontSize || 72;
const fontScale = 1 / font.unitsPerEm * fontSize;
const width = getWidth(text, fontScale);
const left = (options.x || 0) - (width / 2);
const height = getHeight(fontScale);
const baseline = (options.y || 0) + (height / 2);
const path = font.getPath(text, left, baseline, fontSize, {kerning: true});
return path.toPathData();
};

BIN
media/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
media/header.psd Normal file

Binary file not shown.

View File

@ -1,11 +1,11 @@
{
"name": "svg-captcha",
"version": "0.9.5",
"version": "1.0.0",
"description": "generate svg captcha in node.js or express.js",
"main": "index.js",
"scripts": {
"test": "xo && mocha",
"lint": "xo",
"test": "mocha",
"lint": "xo",
"test:visual": "node test-visual.js"
},
"repository": {
@ -29,7 +29,7 @@
},
"homepage": "https://github.com/steambap/svg-captcha#readme",
"dependencies": {
"text-to-svg": "^3.0.1"
"opentype.js": "^0.6.4"
},
"devDependencies": {
"mocha": "^2.5.3",

View File

@ -17,7 +17,7 @@ describe('svg captcha', function () {
});
});
const random = require('../random');
const random = require('../lib/random');
describe('random function', function () {
it('should generate random integer', function () {