-
Notifications
You must be signed in to change notification settings - Fork 16
/
index.js
137 lines (122 loc) · 4.31 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"use strict";
const Promise = require("bluebird");
const request = require("request");
const Canvas = require("canvas");
const fs = Promise.promisifyAll(require("fs"));
function downloadPhoto (uri) {
return new Promise((resolve, reject) => {
let data;
const stream = request(uri);
stream.on("data", (chunk) => data = data ? Buffer.concat([data, chunk]) : chunk);
stream.on("error", reject);
stream.on("end", () => resolve(data));
});
}
function getPhoto (src) {
if (src instanceof Buffer) {
return src;
} else if (typeof src === "string") {
if (/^http/.test(src) || /^ftp/.test(src)) {
return downloadPhoto(src)
.catch(() => {throw new Error(`Could not download url source: ${src}`);});
} else {
return fs.readFileAsync(src)
.catch(() => {throw new Error(`Could not load file source: ${src}`);});
}
} else if (src instanceof Canvas) {
return src.toBuffer();
} else {
throw new Error(`Unsupported source type: ${src}`);
}
}
function wrapText(context, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
let initialY = y;
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
context.fillText(line, x, y);
return y - initialY + lineHeight; // height used
}
const PARAMS = [
{field: "sources", required: true},
{field: "width", required: true},
{field: "height", required: true},
{field: "imageWidth", required: true},
{field: "imageHeight", required: true},
{field: "spacing", default: 0},
{field: "backgroundColor", default: "#eeeeee"},
{field: "lines", default: []},
{field: "textStyle", default: {}},
];
module.exports = function (options) {
if (Array.isArray(options)) {
options = {sources: options};
}
PARAMS.forEach((param) => {
if (options[param.field]) {
return;
} else if (param.default != null) {
options[param.field] = param.default;
} else if (param.required) {
throw new Error(`Missing required option: ${param.field}`);
}
});
const headerHeight = (options.header || {}).height || 0;
const canvasWidth = options.width * options.imageWidth + (options.width - 1) * (options.spacing);
const canvasHeight = headerHeight + options.height * options.imageHeight + (options.height - 1) * (options.spacing) + (options.textStyle.height || 200);
const canvas = new Canvas(canvasWidth, canvasHeight);
const ctx = canvas.getContext("2d");
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
const sources = options.sources;
let maxImages = options.width * options.height;
if ((options.header || {}).image) {
maxImages += 1;
sources.unshift(options.header.image);
}
return Promise
.map(sources, getPhoto)
.each((photoBuffer, i) => {
if (i >= maxImages) return;
const img = new Canvas.Image();
img.src = photoBuffer;
if ((options.header || {}).image) { // only for header
if (!i) { // first time
ctx.drawImage(img, 0, 0, canvasWidth, options.header.height);
return;
}
i -= 1;
}
const x = (i % options.width) * (options.imageWidth + options.spacing);
const y = Math.floor(i / options.width) * (options.imageHeight + options.spacing);
ctx.drawImage(img, x, y + headerHeight, options.imageWidth, options.imageHeight);
})
.then(() => {
if (options.text) {
ctx.font = (options.textStyle.fontSize || "20") + "px " + (options.textStyle.font || "Helvetica");
wrapText(ctx, options.text, 10, canvasHeight - (options.textStyle.height || 200) + 50, canvasWidth - 10, (options.textStyle.fontSize || 20) * 1.2);
}
else {
let curHeight = 150;
options.lines.map((line) => {
ctx.font = line.font || "20px Helvetica";
ctx.fillStyle = line.color || "#333333";
const heightUsed = wrapText(ctx, line.text, 10, canvasHeight - curHeight, canvasWidth - 10, (parseInt(line.font) || 20) * 1.2);
curHeight -= heightUsed;
});
}
})
.return(canvas);
};