Skip to content

Commit

Permalink
[refactor] use opentype.js directly with a 3d font
Browse files Browse the repository at this point in the history
[add] header
  • Loading branch information
steambap committed Jul 1, 2016
1 parent 84d9bfd commit be05347
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 101 deletions.
21 changes: 17 additions & 4 deletions README.md
@@ -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
Expand All @@ -10,15 +12,15 @@ 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();
// generate svg image
var captcha = svgCaptcha(text);
```
with express
```
```js
var svgCaptcha = require('svg-captcha');

app.get('/captcha', function (req, res) {
Expand All @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions README_CN.md
@@ -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格式的验证码

## 如果你遇到这些问题
Expand All @@ -10,15 +12,15 @@
- 无法安装 c++ 模块

## 使用方法
```
```js
var svgCaptcha = require('svg-captcha');
// generate random text of length 4
var text = svgCaptcha.randomText();
// generate svg image
var captcha = svgCaptcha(text);
```
在 express中使用
```
```js
var svgCaptcha = require('svg-captcha');

app.get('/captcha', function (req, res) {
Expand All @@ -37,7 +39,7 @@ app.get('/captcha', function (req, res) {
## 为什么使用 svg 格式?

不需要引用 c++ 模块。
使用 opentype.js了,而且svg图片比jpeg格式图片要小
svg图片比jpeg格式图片要小。

## Translations
[中文](README_CN.md)
Expand Down
Binary file added fonts/Comismsh.ttf
Binary file not shown.
89 changes: 1 addition & 88 deletions index.js
@@ -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 changes: 87 additions & 0 deletions lib/index.js
@@ -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;
File renamed without changes.
45 changes: 45 additions & 0 deletions lib/text-to-path.js
@@ -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();
};
Binary file added media/header.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/header.psd
Binary file not shown.
8 changes: 4 additions & 4 deletions package.json
@@ -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": {
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion test/test.js
Expand Up @@ -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 () {
Expand Down

0 comments on commit be05347

Please sign in to comment.