ai-nevermore 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/bin/nevermore +76 -6
- package/demo.mjs +29 -0
- package/package.json +15 -5
- package/src/image.mjs +186 -0
- package/src/index.mjs +16 -83
- package/src/render-modes.mjs +120 -0
- package/test.jpg +0 -0
- package/textures/A.jpg +0 -0
- package/textures/B.jpg +0 -0
- package/textures/C.jpg +0 -0
- package/textures/D.jpg +0 -0
- package/textures/E.jpg +0 -0
- package/textures/F.jpg +0 -0
- package/textures/G.jpg +0 -0
- package/textures/H.jpg +0 -0
- package/textures/I.jpg +0 -0
- package/textures/J.jpg +0 -0
- package/textures/K.jpg +0 -0
- package/textures/L.jpg +0 -0
- package/textures/M.jpg +0 -0
- package/textures/N.jpg +0 -0
- package/textures/O.jpg +0 -0
- package/textures/P.jpg +0 -0
- package/textures/Q.jpg +0 -0
- package/textures/R.jpg +0 -0
- package/textures/S.jpg +0 -0
- package/textures/T.jpg +0 -0
- package/textures/U.jpg +0 -0
- package/textures/V.jpg +0 -0
- package/textures/W.jpg +0 -0
- package/textures/X.jpg +0 -0
- package/textures/Y.jpg +0 -0
- package/textures/Z.jpg +0 -0
package/README.md
CHANGED
|
@@ -29,21 +29,32 @@ Install with `npm install -g ai-nevermore`
|
|
|
29
29
|
nevermore [command]
|
|
30
30
|
|
|
31
31
|
Commands:
|
|
32
|
-
nevermore pseudotext [input-file]
|
|
32
|
+
nevermore pseudotext [input-file] transform text to poison
|
|
33
|
+
nevermore pseudoimage [input-file] transform XOR image encoding
|
|
33
34
|
|
|
34
35
|
Options:
|
|
35
|
-
--version
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
36
|
+
--version Show version number [boolean]
|
|
37
|
+
-K, --key key to use for decoding the image [string]
|
|
38
|
+
-U, --unified-output File to generate html + css into [string]
|
|
39
|
+
-C, --css-output File to generate css into [string]
|
|
40
|
+
-I, --image-output File to output image to [string]
|
|
41
|
+
-H, --html-output File to generate html into [string]
|
|
42
|
+
-r, --raw-output Do not wrap the ouput [boolean]
|
|
43
|
+
-E, --encode inline encoding [boolean]
|
|
44
|
+
-D, --decode inline decoding [boolean]
|
|
45
|
+
-m, --render-mode output mode
|
|
46
|
+
[string] [choices: "fixed-mode", "inline-mode"] [default: "inline-mode"]
|
|
47
|
+
-s, --size The size of the font in pixels (required for
|
|
48
|
+
fixed-width) [number] [default: 12]
|
|
49
|
+
-d, --custom-dictionary A json map of replacement words [string]
|
|
50
|
+
-f, --font The font in question (required for fixed-width; only
|
|
51
|
+
webfonts are supported)
|
|
42
52
|
[string] [choices: "Andale Mono", "Arial", "Avenir", "Avenir Next", "Comic
|
|
43
53
|
Sans MS", "Courier New", "Georgia", "Helvetica", "Impact", "Inter", "Times New
|
|
44
54
|
Roman", "Trebuchet MS", "Verdana", "Webdings", "Open Sans", "Tahoma"]
|
|
45
55
|
[default: "Arial"]
|
|
46
|
-
--help
|
|
56
|
+
--help Show help [boolean]
|
|
57
|
+
|
|
47
58
|
```
|
|
48
59
|
|
|
49
60
|
Roadmap
|
|
@@ -52,7 +63,10 @@ Roadmap
|
|
|
52
63
|
- [x] raw output mode
|
|
53
64
|
- [x] stdin, stdout support
|
|
54
65
|
- [x] custom dictionary
|
|
66
|
+
- [x] image encoding
|
|
67
|
+
- [ ] web component decoder
|
|
55
68
|
- [ ] self randomizing dictionary
|
|
69
|
+
- [ ] add a replacement mode (opposed to a tokenizer based solution)
|
|
56
70
|
|
|
57
71
|
Development
|
|
58
72
|
-----------
|
package/bin/nevermore
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
4
|
import { computeIndexKeys, generateHTMLAndCSS } from '../src/index.mjs';
|
|
5
|
+
import { NevermoreImage } from '../src/image.mjs';
|
|
6
|
+
import { Canvas } from '@environment-safe/canvas';
|
|
5
7
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
6
8
|
import { join } from 'node:path';
|
|
7
9
|
const { cwd } = process;
|
|
10
|
+
const __dirname = import.meta.dirname;
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
const formatHTML = (html, css, isRaw=false)=>{
|
|
11
14
|
if(isRaw) return `${css?`<style>${css}</style>`:''}${html}`;
|
|
12
|
-
return `<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >${css?`<style>${css}</style
|
|
15
|
+
return `<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >${css.indexOf('\n')===-1?`<link rel="stylesheet" href="${css}">`:`<style>${css}</style>`}</head><body>${html}</body></html>`;
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
yargs(hideBin(process.argv))
|
|
16
|
-
.command('pseudotext [input-file]', '
|
|
19
|
+
.command('pseudotext [input-file]', 'transform text to poison', (yargs) => {
|
|
17
20
|
return yargs
|
|
18
21
|
.positional('input-file', {
|
|
19
22
|
describe: 'file input'
|
|
@@ -45,7 +48,13 @@ yargs(hideBin(process.argv))
|
|
|
45
48
|
idx = result.toString();
|
|
46
49
|
}
|
|
47
50
|
//const { index, root } = await computeIndexKeys(input);
|
|
48
|
-
const { html, css } = await generateHTMLAndCSS(
|
|
51
|
+
const { html, css } = await generateHTMLAndCSS(
|
|
52
|
+
root, idx, {
|
|
53
|
+
mode: argv['render-mode'],
|
|
54
|
+
font: argv['font'],
|
|
55
|
+
size: argv['size']
|
|
56
|
+
}
|
|
57
|
+
);
|
|
49
58
|
if(argv['unified-output']){
|
|
50
59
|
const output = formatHTML(html, css, argv['raw-output']);
|
|
51
60
|
await writeFile(argv['unified-output'], output);
|
|
@@ -53,7 +62,11 @@ yargs(hideBin(process.argv))
|
|
|
53
62
|
if(argv['css-output'] && argv['html-output']){
|
|
54
63
|
const cssLocation = join(cwd(), argv['css-output']);
|
|
55
64
|
const htmlLocation = join(cwd(), argv['html-output']);
|
|
56
|
-
const output = formatHTML(
|
|
65
|
+
const output = formatHTML(
|
|
66
|
+
html,
|
|
67
|
+
`./${argv['css-output']}`,
|
|
68
|
+
argv['raw-output']
|
|
69
|
+
);
|
|
57
70
|
await writeFile(htmlLocation, output);
|
|
58
71
|
await writeFile(cssLocation, css);
|
|
59
72
|
|
|
@@ -62,6 +75,43 @@ yargs(hideBin(process.argv))
|
|
|
62
75
|
process.stdout.write(output);
|
|
63
76
|
}
|
|
64
77
|
}
|
|
78
|
+
}).command('pseudoimage [input-file]', 'transform XOR image encoding', (yargs) => {
|
|
79
|
+
return yargs
|
|
80
|
+
.positional('input-file', {
|
|
81
|
+
describe: 'file input'
|
|
82
|
+
})
|
|
83
|
+
}, async (argv) => {
|
|
84
|
+
let image = null;
|
|
85
|
+
if(!argv['input-file']){
|
|
86
|
+
//TODO: pipe support
|
|
87
|
+
throw new Error('pipe not yet supported');
|
|
88
|
+
}else{
|
|
89
|
+
const options = {
|
|
90
|
+
url: argv['input-file'],
|
|
91
|
+
maskDir: join(__dirname, '..', 'textures')
|
|
92
|
+
};
|
|
93
|
+
if(argv['key']){
|
|
94
|
+
options.key = argv['key']
|
|
95
|
+
}
|
|
96
|
+
image = new NevermoreImage(options);
|
|
97
|
+
await image.ready;
|
|
98
|
+
}
|
|
99
|
+
if(image && argv['image-output']){
|
|
100
|
+
let canvas = null;
|
|
101
|
+
if(argv['encode']){
|
|
102
|
+
canvas = await image.encode();
|
|
103
|
+
}
|
|
104
|
+
if(argv['decode']){
|
|
105
|
+
canvas = await image.decode();
|
|
106
|
+
}
|
|
107
|
+
Canvas.save(argv['image-output'], canvas);
|
|
108
|
+
}else{
|
|
109
|
+
throw new Error('piped output not yet supported');
|
|
110
|
+
}
|
|
111
|
+
}).option('key', {
|
|
112
|
+
alias: 'K',
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'key to use for decoding the image'
|
|
65
115
|
}).option('unified-output', {
|
|
66
116
|
alias: 'U',
|
|
67
117
|
type: 'string',
|
|
@@ -70,6 +120,10 @@ yargs(hideBin(process.argv))
|
|
|
70
120
|
alias: 'C',
|
|
71
121
|
type: 'string',
|
|
72
122
|
description: 'File to generate css into'
|
|
123
|
+
}).option('image-output', {
|
|
124
|
+
alias: 'I',
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'File to output image to'
|
|
73
127
|
}).option('html-output', {
|
|
74
128
|
alias: 'H',
|
|
75
129
|
type: 'string',
|
|
@@ -78,11 +132,27 @@ yargs(hideBin(process.argv))
|
|
|
78
132
|
alias: 'r',
|
|
79
133
|
type: 'boolean',
|
|
80
134
|
description: 'Do not wrap the ouput'
|
|
135
|
+
}).option('encode', {
|
|
136
|
+
alias: 'E',
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
description: 'inline encoding'
|
|
139
|
+
}).option('decode', {
|
|
140
|
+
alias: 'D',
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
description: 'inline decoding'
|
|
143
|
+
}).option('render-mode', {
|
|
144
|
+
alias: 'm',
|
|
145
|
+
type: 'string',
|
|
146
|
+
description: 'output mode',
|
|
147
|
+
default: 'inline-mode',
|
|
148
|
+
choices: [
|
|
149
|
+
'fixed-mode', 'inline-mode'
|
|
150
|
+
],
|
|
81
151
|
}).option('size', {
|
|
82
152
|
alias: 's',
|
|
83
153
|
type: 'number',
|
|
84
154
|
default: 12,
|
|
85
|
-
description: 'The size of the font in pixels'
|
|
155
|
+
description: 'The size of the font in pixels (required for fixed-width)'
|
|
86
156
|
}).option('custom-dictionary', {
|
|
87
157
|
alias: 'd',
|
|
88
158
|
type: 'string',
|
|
@@ -97,5 +167,5 @@ yargs(hideBin(process.argv))
|
|
|
97
167
|
'Open Sans', 'Tahoma'
|
|
98
168
|
],
|
|
99
169
|
default: 'Arial',
|
|
100
|
-
description: 'The font in question (only webfonts are supported)'
|
|
170
|
+
description: 'The font in question (required for fixed-width; only webfonts are supported)'
|
|
101
171
|
}).help().parse();
|
package/demo.mjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { channelXOR, NevermoreImage } from './src/image.mjs';
|
|
2
|
+
import { Canvas, Image } from '@environment-safe/canvas';
|
|
3
|
+
|
|
4
|
+
(async ()=>{
|
|
5
|
+
const image = new NevermoreImage({
|
|
6
|
+
url:'./test.jpg',
|
|
7
|
+
maskDir: './textures'
|
|
8
|
+
});
|
|
9
|
+
await image.ready;
|
|
10
|
+
const canvas = image.encode();
|
|
11
|
+
await Canvas.save('./encoded.jpg', canvas);
|
|
12
|
+
const decoded = image.decode();
|
|
13
|
+
await Canvas.save('./decoded.jpg', decoded);
|
|
14
|
+
/*
|
|
15
|
+
const canvas = await Canvas.load('./test.jpg');
|
|
16
|
+
const texture = await Canvas.load('./textures/A.jpg');
|
|
17
|
+
await Canvas.save('./t.jpg', canvas);
|
|
18
|
+
await Canvas.save('./x.jpg', texture);
|
|
19
|
+
const pixels = channelXOR(canvas, texture, 'red', 1);
|
|
20
|
+
const context = canvas.getContext('2d');
|
|
21
|
+
console.log(pixels, 0, 0, canvas.width, canvas.height)
|
|
22
|
+
context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
|
|
23
|
+
await Canvas.save('./out.jpg', canvas);
|
|
24
|
+
const resetPixels = channelXOR(canvas, texture, 'red', -1);
|
|
25
|
+
context.putImageData(resetPixels, 0, 0, 0, 0, canvas.width, canvas.height);
|
|
26
|
+
await Canvas.save('./reset.jpg', canvas);
|
|
27
|
+
//*/
|
|
28
|
+
|
|
29
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-nevermore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"adversarial",
|
|
6
6
|
"content",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
".": {
|
|
19
19
|
"import": "./src/index.mjs"
|
|
20
20
|
},
|
|
21
|
+
"./image": {
|
|
22
|
+
"import": "./src/image.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./src/image.mjs": {
|
|
25
|
+
"import": "./src/image.mjs"
|
|
26
|
+
},
|
|
21
27
|
"./package.json": {
|
|
22
28
|
"import": "./package.json"
|
|
23
29
|
},
|
|
@@ -25,16 +31,20 @@
|
|
|
25
31
|
"import": "./src/index.mjs"
|
|
26
32
|
}
|
|
27
33
|
},
|
|
28
|
-
"bin":{
|
|
34
|
+
"bin": {
|
|
29
35
|
"nevermore": "./bin/nevermore"
|
|
30
36
|
},
|
|
31
|
-
"scripts":{
|
|
37
|
+
"scripts": {
|
|
32
38
|
"single-file-demo": "./bin/nevermore pseudotext -U output.html nevermore.txt",
|
|
33
39
|
"pipe-demo": "cat nevermore.txt | ./bin/nevermore pseudotext",
|
|
34
|
-
"demo": "./bin/nevermore pseudotext -H out.html -C out.css nevermore.txt"
|
|
40
|
+
"demo": "./bin/nevermore pseudotext -H out.html -C out.css nevermore.txt",
|
|
41
|
+
"image-encode": "./bin/nevermore pseudoimage ./test.jpg --image-output out.jpg --encode",
|
|
42
|
+
"image-decode": "./bin/nevermore pseudoimage ./out.jpg --image-output decoded.jpg --decode",
|
|
43
|
+
"help": "./bin/nevermore --help"
|
|
35
44
|
},
|
|
36
45
|
"dependencies": {
|
|
37
|
-
"@environment-safe/
|
|
46
|
+
"@environment-safe/canvas": "^4.2.3",
|
|
47
|
+
"@environment-safe/file": "^0.4.3",
|
|
38
48
|
"object-hash": "^3.0.0",
|
|
39
49
|
"parse-english": "^7.0.0",
|
|
40
50
|
"self-dict": "^1.0.0",
|
package/src/image.mjs
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { Canvas, Image } from '@environment-safe/canvas';
|
|
2
|
+
import { File, Path } from '@environment-safe/file';
|
|
3
|
+
|
|
4
|
+
export const channelXOR = (imageCanvas, textureCanvas, chn='', direction= 1)=>{
|
|
5
|
+
const imageContext = imageCanvas.getContext('2d', { willReadFrequently: true });
|
|
6
|
+
const textureContext = textureCanvas.getContext('2d', { willReadFrequently: true });
|
|
7
|
+
const texturePixels = textureContext.getImageData(0,0, textureCanvas.width, textureCanvas.height);
|
|
8
|
+
const newPixels = imageContext.getImageData(0,0, imageCanvas.width, imageCanvas.height);
|
|
9
|
+
const sx = imageCanvas.width; //getx
|
|
10
|
+
const sy = imageCanvas.height; //gety
|
|
11
|
+
const tx = textureCanvas.width; //getx
|
|
12
|
+
const ty = textureCanvas.height; //gety
|
|
13
|
+
let x = null;
|
|
14
|
+
let y = null;
|
|
15
|
+
let pxl = null;
|
|
16
|
+
let tex_r, tex_g, tex_b, mask_offset, pixel_offset, value = null;
|
|
17
|
+
let channel = '';
|
|
18
|
+
switch(chn.toLowerCase()){
|
|
19
|
+
case 'red':
|
|
20
|
+
case 'r':
|
|
21
|
+
channel = 'r';
|
|
22
|
+
break;
|
|
23
|
+
case 'green':
|
|
24
|
+
case 'g':
|
|
25
|
+
channel = 'g';
|
|
26
|
+
break;
|
|
27
|
+
case 'blue':
|
|
28
|
+
case 'b':
|
|
29
|
+
channel = 'b';
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
//kernel_size = filter.length; //coming soon
|
|
33
|
+
const intervalWidth = 256;
|
|
34
|
+
for(y = 0; y < sy; y++){
|
|
35
|
+
for(x = 0; x < sx; x++){
|
|
36
|
+
mask_offset = (((y%ty)*((sx%tx)*4)) + ((x%tx)*4));
|
|
37
|
+
pixel_offset = ((y*(sx*4)) + (x*4));
|
|
38
|
+
tex_r = texturePixels.data[mask_offset ];
|
|
39
|
+
tex_g = texturePixels.data[mask_offset + 1];
|
|
40
|
+
tex_b = texturePixels.data[mask_offset + 2];
|
|
41
|
+
if(!(tex_r === tex_g && tex_g === tex_b)){
|
|
42
|
+
value = Math.floor((tex_r + tex_g + tex_b) / 3);
|
|
43
|
+
}else{
|
|
44
|
+
value = tex_r;
|
|
45
|
+
}
|
|
46
|
+
if(channel === 'r'){
|
|
47
|
+
newPixels.data[pixel_offset ] = (
|
|
48
|
+
newPixels.data[pixel_offset ] + direction*value + intervalWidth
|
|
49
|
+
)%intervalWidth;
|
|
50
|
+
}
|
|
51
|
+
if(channel === 'g'){
|
|
52
|
+
newPixels.data[pixel_offset+1] = (
|
|
53
|
+
newPixels.data[pixel_offset+1] + direction*value + intervalWidth
|
|
54
|
+
)%intervalWidth;
|
|
55
|
+
}
|
|
56
|
+
if(channel === 'b'){
|
|
57
|
+
newPixels.data[pixel_offset + 2] = (
|
|
58
|
+
newPixels.data[pixel_offset+2] + direction*value + intervalWidth
|
|
59
|
+
)%intervalWidth;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return newPixels;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const makeKey = (len, alpha)=>{
|
|
67
|
+
const alphabet = alpha || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
|
68
|
+
let key = '';
|
|
69
|
+
for(let lcv=0; lcv < len; lcv++){
|
|
70
|
+
key += alphabet[Math.floor(Math.random()*alphabet.length)];
|
|
71
|
+
}
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class NevermoreImage{
|
|
76
|
+
constructor(options={}){
|
|
77
|
+
if(!(
|
|
78
|
+
options.masks || options.maskDir
|
|
79
|
+
)) throw new Error('a set of masks are required');
|
|
80
|
+
this.ready = new Promise(async (resolve, reject)=>{
|
|
81
|
+
// load all masks
|
|
82
|
+
// TODO: load from cache
|
|
83
|
+
let masks = options.masks;
|
|
84
|
+
const textureWork = [];
|
|
85
|
+
const textureDictionary = {};
|
|
86
|
+
if(options.maskDir){
|
|
87
|
+
const types = ['jpg', 'jpeg', 'gif', 'png'];
|
|
88
|
+
const files = (await File.list(options.maskDir)).filter((name)=>{
|
|
89
|
+
const parts = name.split('.');
|
|
90
|
+
const ext = parts.pop().toLowerCase();
|
|
91
|
+
return types.indexOf(ext) !== -1;
|
|
92
|
+
// TODO: support mime through magic numbers
|
|
93
|
+
});
|
|
94
|
+
const fileLoads = [];
|
|
95
|
+
for(let lcv=0; lcv<files.length; lcv++){
|
|
96
|
+
const pth = new Path(files[lcv]);
|
|
97
|
+
if(!pth.parsed) throw new Error('path not parsed');
|
|
98
|
+
const parsed = pth.parsed.posix || pth.parsed.win32;
|
|
99
|
+
const canvasLoad = Canvas.load(
|
|
100
|
+
Path.join(options.maskDir, files[lcv])
|
|
101
|
+
);
|
|
102
|
+
textureWork.push(new Promise(async (resolve)=>{
|
|
103
|
+
resolve({
|
|
104
|
+
name: parsed.name,
|
|
105
|
+
canvas: await canvasLoad
|
|
106
|
+
});
|
|
107
|
+
}))
|
|
108
|
+
fileLoads.push(Canvas.load(Path.join(options.maskDir, files[lcv])));
|
|
109
|
+
}
|
|
110
|
+
masks = await Promise.all(fileLoads);
|
|
111
|
+
const texturesLoaded = await Promise.all(textureWork);
|
|
112
|
+
for(let lcv=0; lcv<texturesLoaded.length; lcv++){
|
|
113
|
+
textureDictionary[
|
|
114
|
+
texturesLoaded[lcv].name
|
|
115
|
+
] = texturesLoaded[lcv].canvas;
|
|
116
|
+
};
|
|
117
|
+
this.textureDictionary = textureDictionary;
|
|
118
|
+
}
|
|
119
|
+
this.masks = masks;
|
|
120
|
+
this.canvas = options.image;
|
|
121
|
+
// masks loaded, now load the base image
|
|
122
|
+
if(options.url){
|
|
123
|
+
this.canvas = await Canvas.load(options.url);
|
|
124
|
+
}
|
|
125
|
+
if(!this.canvas){
|
|
126
|
+
return reject(new Error(
|
|
127
|
+
'no base image provided to Image File'
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
//make an id
|
|
131
|
+
this.key = options.key || `${makeKey(5)}-${makeKey(5)}-${makeKey(5)}`;
|
|
132
|
+
console.log('KEY:', this.key);
|
|
133
|
+
//we're ready to do work
|
|
134
|
+
resolve();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
transform(options={}){
|
|
139
|
+
const { channelKeys, direction, key } = options;
|
|
140
|
+
let pixels = null;
|
|
141
|
+
let channelKey = null;
|
|
142
|
+
const canvas = this.canvas;
|
|
143
|
+
for(let lcv=0; lcv < channelKeys.r.length; lcv++){
|
|
144
|
+
const texture = this.textureDictionary[channelKeys.r[lcv]];
|
|
145
|
+
const pixels = channelXOR(canvas, texture, 'red', direction);
|
|
146
|
+
const context = canvas.getContext('2d');
|
|
147
|
+
context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
|
|
148
|
+
}
|
|
149
|
+
for(let lcv=0; lcv < channelKeys.r.length; lcv++){
|
|
150
|
+
const texture = this.textureDictionary[channelKeys.r[lcv]];
|
|
151
|
+
const pixels = channelXOR(canvas, texture, 'green', direction);
|
|
152
|
+
const context = canvas.getContext('2d');
|
|
153
|
+
context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
|
|
154
|
+
}
|
|
155
|
+
for(let lcv=0; lcv < channelKeys.r.length; lcv++){
|
|
156
|
+
const texture = this.textureDictionary[channelKeys.r[lcv]];
|
|
157
|
+
const pixels = channelXOR(canvas, texture, 'blue', direction);
|
|
158
|
+
const context = canvas.getContext('2d');
|
|
159
|
+
context.putImageData(pixels, 0, 0, 0, 0, canvas.width, canvas.height);
|
|
160
|
+
}
|
|
161
|
+
// should this mutate? current answer: yes
|
|
162
|
+
return canvas;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
encode(options={}){
|
|
166
|
+
const key = options.key || this.key;
|
|
167
|
+
const parts = key.split('-')
|
|
168
|
+
const channelKeys = {
|
|
169
|
+
r: parts[0].split(''),
|
|
170
|
+
g: parts[1].split(''),
|
|
171
|
+
b: parts[2].split('')
|
|
172
|
+
}
|
|
173
|
+
return this.transform({ channelKeys, key, direction: 1 });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
decode(options={}){
|
|
177
|
+
const key = options.key || this.key;
|
|
178
|
+
const parts = key.split('-');
|
|
179
|
+
const channelKeys = {
|
|
180
|
+
r: parts[0].split('').reverse(),
|
|
181
|
+
g: parts[1].split('').reverse(),
|
|
182
|
+
b: parts[2].split('').reverse()
|
|
183
|
+
}
|
|
184
|
+
return this.transform({ channelKeys, key, direction: -1 });
|
|
185
|
+
}
|
|
186
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ParseEnglish } from 'parse-english'
|
|
2
2
|
import { inspect } from 'unist-util-inspect'
|
|
3
3
|
import { define } from 'self-dict';
|
|
4
|
+
import pixelWidth from 'string-pixel-width';
|
|
4
5
|
import { thesaurus } from './thesaurus.mjs';
|
|
5
6
|
import { hash } from './hash.mjs';
|
|
6
|
-
import
|
|
7
|
-
|
|
7
|
+
import { modes } from './render-modes.mjs';
|
|
8
8
|
|
|
9
9
|
let wordlist = null;
|
|
10
10
|
|
|
@@ -24,8 +24,6 @@ const randomWord = async ()=>{
|
|
|
24
24
|
while((!word) || word.split(' ').length > 1){
|
|
25
25
|
word = wordlist[Math.floor(wordlist.length * Math.random())];
|
|
26
26
|
}
|
|
27
|
-
//console.log(word);
|
|
28
|
-
//process.exit();
|
|
29
27
|
return word;
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -40,97 +38,30 @@ const traverse = async (node, handler)=>{
|
|
|
40
38
|
}
|
|
41
39
|
};
|
|
42
40
|
|
|
43
|
-
const render = async (node, index, fnt, sz)=>{
|
|
41
|
+
const render = async (node, index, modeName, fnt, sz)=>{
|
|
42
|
+
const mode = modes[modeName];
|
|
44
43
|
let html = '';
|
|
45
44
|
let css = '';
|
|
46
45
|
let className = null;
|
|
47
46
|
let width = null;
|
|
48
47
|
const font = fnt || 'Arial';
|
|
49
48
|
const size = sz || 12;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
display:inline-block;
|
|
56
|
-
vertical-align: baseline;
|
|
57
|
-
}\n`;
|
|
58
|
-
break;
|
|
59
|
-
case 'ParagraphNode':
|
|
60
|
-
html += '<p>';
|
|
61
|
-
break;
|
|
62
|
-
case 'SentenceNode': break;
|
|
63
|
-
case 'WordNode': break;
|
|
64
|
-
case 'TextNode':
|
|
65
|
-
if(node.replacement){
|
|
66
|
-
width = pixelWidth(node.word, {
|
|
67
|
-
size: size,
|
|
68
|
-
font
|
|
69
|
-
});
|
|
70
|
-
className = 'R'+hash(node.word);
|
|
71
|
-
css += `.${className}{
|
|
72
|
-
visibility: hidden;
|
|
73
|
-
position: relative;
|
|
74
|
-
overflow:none;
|
|
75
|
-
vertical-align: text-bottom;
|
|
76
|
-
display:inline-block;
|
|
77
|
-
font-size: ${size}px;
|
|
78
|
-
height: ${size}px;
|
|
79
|
-
width: ${width}px;
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
css += `.${className}::after{
|
|
83
|
-
visibility: visible;
|
|
84
|
-
position: absolute;
|
|
85
|
-
vertical-align: text-bottom;
|
|
86
|
-
font-family: ${font};
|
|
87
|
-
font-size: ${size}px;
|
|
88
|
-
display:inline-block;
|
|
89
|
-
margin-top: -${Math.round(size/4)}px;
|
|
90
|
-
top: 0;
|
|
91
|
-
left: 0;
|
|
92
|
-
width: ${width}px;
|
|
93
|
-
height: ${size}px;
|
|
94
|
-
content : '${node.value}'
|
|
95
|
-
}
|
|
96
|
-
`;
|
|
97
|
-
html += `<span class="guarded ${
|
|
98
|
-
className
|
|
99
|
-
} ">${node.replacement}</span>`;
|
|
100
|
-
}else{
|
|
101
|
-
//console.log(node); process.exit();
|
|
102
|
-
html += `<span>${node.value}</span>`;
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case 'WhiteSpaceNode':
|
|
106
|
-
break;
|
|
107
|
-
case 'PunctuationNode':
|
|
108
|
-
break;
|
|
109
|
-
default: {console.log(node); process.exit()}
|
|
49
|
+
if(mode[node.type] && mode[node.type].pre){
|
|
50
|
+
html += mode[node.type].pre({font, size, node});
|
|
51
|
+
}
|
|
52
|
+
if(mode[node.type] && mode[node.type].style){
|
|
53
|
+
css += mode[node.type].style({font, size, node});
|
|
110
54
|
}
|
|
111
55
|
if(node.children){
|
|
112
56
|
let vals = null;
|
|
113
57
|
for(let lcv=0; lcv< node.children.length; lcv++){
|
|
114
|
-
vals = await render(node.children[lcv]);
|
|
58
|
+
vals = await render(node.children[lcv], index, modeName, fnt, sz);
|
|
115
59
|
html += vals.html;
|
|
116
60
|
css += vals.css;
|
|
117
61
|
}
|
|
118
62
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
case 'ParagraphNode':
|
|
122
|
-
html += '</p>';
|
|
123
|
-
break;
|
|
124
|
-
case 'SentenceNode':
|
|
125
|
-
break;
|
|
126
|
-
case 'WordNode': break;
|
|
127
|
-
case 'TextNode': break;
|
|
128
|
-
case 'WhiteSpaceNode':
|
|
129
|
-
html += `${node.value}`;
|
|
130
|
-
break;
|
|
131
|
-
case 'PunctuationNode':
|
|
132
|
-
html += `<span>${node.value}</span>`;
|
|
133
|
-
break;
|
|
63
|
+
if(mode[node.type] && mode[node.type].post){
|
|
64
|
+
html += mode[node.type].post({font, size, node});
|
|
134
65
|
}
|
|
135
66
|
return {html, css};
|
|
136
67
|
};
|
|
@@ -166,8 +97,10 @@ export const computeIndexKeys = async (textBody)=>{
|
|
|
166
97
|
return { index, root: node };
|
|
167
98
|
};
|
|
168
99
|
|
|
169
|
-
export const generateHTMLAndCSS = async (node, index,
|
|
100
|
+
export const generateHTMLAndCSS = async (node, index, options)=>{
|
|
170
101
|
await checkThesaurusInit();
|
|
171
|
-
const { html, css } = await render(
|
|
102
|
+
const { html, css } = await render(
|
|
103
|
+
node, index, options.mode, options.font, options.size
|
|
104
|
+
);
|
|
172
105
|
return { html, css };
|
|
173
106
|
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pixelWidth from 'string-pixel-width';
|
|
2
|
+
import { thesaurus } from './thesaurus.mjs';
|
|
3
|
+
import { hash } from './hash.mjs';
|
|
4
|
+
|
|
5
|
+
export const fixedMode = {
|
|
6
|
+
RootNode : {
|
|
7
|
+
style : ({font, size, node})=>{
|
|
8
|
+
return `span{
|
|
9
|
+
font-family: ${font};
|
|
10
|
+
font-size: ${size}px;
|
|
11
|
+
display:inline-block;
|
|
12
|
+
vertical-align: baseline;
|
|
13
|
+
}\n`;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
ParagraphNode : {
|
|
17
|
+
pre : ({font, size, node})=>{
|
|
18
|
+
return `<p>`;
|
|
19
|
+
},
|
|
20
|
+
post : ({font, size, node})=>{
|
|
21
|
+
return `</p>`;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
TextNode: {
|
|
25
|
+
style : ({font, size, node})=>{
|
|
26
|
+
if(node.replacement){
|
|
27
|
+
const width = pixelWidth(node.word, {
|
|
28
|
+
size: size,
|
|
29
|
+
font
|
|
30
|
+
});
|
|
31
|
+
const className = 'R'+hash(node.word);
|
|
32
|
+
return `.${className}{
|
|
33
|
+
visibility: hidden;
|
|
34
|
+
position: relative;
|
|
35
|
+
overflow:none;
|
|
36
|
+
vertical-align: text-bottom;
|
|
37
|
+
display:inline-block;
|
|
38
|
+
font-size: ${size}px;
|
|
39
|
+
height: ${size}px;
|
|
40
|
+
width: ${width}px;
|
|
41
|
+
}
|
|
42
|
+
.${className}::after{
|
|
43
|
+
content : '${node.value}'
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
}else{
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
pre : ({font, size, node})=>{
|
|
51
|
+
if(node.replacement){
|
|
52
|
+
const className = 'R'+hash(node.word);
|
|
53
|
+
return `<span class="guarded ${
|
|
54
|
+
className
|
|
55
|
+
} ">${node.replacement}</span>`;
|
|
56
|
+
}else{
|
|
57
|
+
return `<span>${node.value}</span>`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const inlineMode = {
|
|
64
|
+
RootNode : {
|
|
65
|
+
style : ({font, size, node})=>{
|
|
66
|
+
return `.guarded{
|
|
67
|
+
visibility: hidden;
|
|
68
|
+
font-size: 0;
|
|
69
|
+
}
|
|
70
|
+
.guarded::after{
|
|
71
|
+
visibility: visible;
|
|
72
|
+
display: inline-block;
|
|
73
|
+
font-size: 1.0rem;
|
|
74
|
+
}
|
|
75
|
+
\n`;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
ParagraphNode : {
|
|
79
|
+
pre : ({font, size, node})=>{
|
|
80
|
+
return `<p>`;
|
|
81
|
+
},
|
|
82
|
+
post : ({font, size, node})=>{
|
|
83
|
+
return `</p>`;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
WhiteSpaceNode :{
|
|
87
|
+
post : ({font, size, node})=>{
|
|
88
|
+
return `${node.value}`;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
TextNode: {
|
|
92
|
+
style : ({font, size, node})=>{
|
|
93
|
+
if(node.replacement){
|
|
94
|
+
const width = pixelWidth(node.word, {
|
|
95
|
+
size: size,
|
|
96
|
+
font
|
|
97
|
+
});
|
|
98
|
+
const className = 'R'+hash(node.word);
|
|
99
|
+
return `.${className}::after{ content : '${node.value}'; }`;
|
|
100
|
+
}else{
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
pre : ({font, size, node})=>{
|
|
105
|
+
if(node.replacement){
|
|
106
|
+
const className = 'R'+hash(node.word);
|
|
107
|
+
return `<span class="guarded ${
|
|
108
|
+
className
|
|
109
|
+
} ">${node.replacement}</span>`;
|
|
110
|
+
}else{
|
|
111
|
+
return `<span>${node.value}</span>`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const modes = {
|
|
118
|
+
'fixed-mode' : fixedMode,
|
|
119
|
+
'inline-mode' : inlineMode
|
|
120
|
+
}
|
package/test.jpg
ADDED
|
Binary file
|
package/textures/A.jpg
ADDED
|
Binary file
|
package/textures/B.jpg
ADDED
|
Binary file
|
package/textures/C.jpg
ADDED
|
Binary file
|
package/textures/D.jpg
ADDED
|
Binary file
|
package/textures/E.jpg
ADDED
|
Binary file
|
package/textures/F.jpg
ADDED
|
Binary file
|
package/textures/G.jpg
ADDED
|
Binary file
|
package/textures/H.jpg
ADDED
|
Binary file
|
package/textures/I.jpg
ADDED
|
Binary file
|
package/textures/J.jpg
ADDED
|
Binary file
|
package/textures/K.jpg
ADDED
|
Binary file
|
package/textures/L.jpg
ADDED
|
Binary file
|
package/textures/M.jpg
ADDED
|
Binary file
|
package/textures/N.jpg
ADDED
|
Binary file
|
package/textures/O.jpg
ADDED
|
Binary file
|
package/textures/P.jpg
ADDED
|
Binary file
|
package/textures/Q.jpg
ADDED
|
Binary file
|
package/textures/R.jpg
ADDED
|
Binary file
|
package/textures/S.jpg
ADDED
|
Binary file
|
package/textures/T.jpg
ADDED
|
Binary file
|
package/textures/U.jpg
ADDED
|
Binary file
|
package/textures/V.jpg
ADDED
|
Binary file
|
package/textures/W.jpg
ADDED
|
Binary file
|
package/textures/X.jpg
ADDED
|
Binary file
|
package/textures/Y.jpg
ADDED
|
Binary file
|
package/textures/Z.jpg
ADDED
|
Binary file
|