-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathserver.js
366 lines (308 loc) · 12.2 KB
/
server.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// server.js
// where your node app starts
// init project
const express = require('express');
const app = express();
const port = 3000;
const bodyParser = require("body-parser");
const svgPath = require('svg-path'); //https://github.com/PPvG/svg-path
const TextToSVG = require('text-to-svg');
var textToSVG;
const svgpath = require('svgpath');
const ClipperLib = require('clipper-lib');
const pathProperties = require('svg-path-properties');
var point = require('point-at-length');
// file storage
var tmp = require('tmp');
var fs = require('fs');
const fileUpload = require('express-fileupload');
var counterStrings = "A,B,D,O,P,Q,R,a,b,d,e,g,o,p,q,0,4,6,8,9,&,@,%";
var counters = counterStrings.split(",");
var paddingFactor = 1.1; // how much extra to size the resulting SVG box
// font information
const attributes = {stroke: 'black', fill: 'transparent'};
var defaultOptions = {x:0, y: 1, fontSize: 100, anchor: 'top baseline', attributes: attributes};
var scale = 100;
var textWidths;; // keep track of the width of each character
var fullHeight; // height of resulting SVG
var defaultFontPath = "fonts/Roboto-Regular.ttf"
app.use(express.static('public'));
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
app.use(fileUpload());
app.get('/', function(request, response) {
setupSVG();
response.sendFile(__dirname + '/views/index.html');
});
/////////
// SAVE FONT - POST
// Saves uploaded font to temporary storage
////////
app.post('/saveFont', function(req, res){
console.log('saveFont');
if(!req.files) console.log('no files were uploaded!');
let fontFile = req.files.file;
// console.log(fontFile);
var fontName = fontFile.name;
var fontType = fontName.substring(fontName.indexOf('.'));
// move the font file to temporary storage
tmp.file({postfix: fontType, keep: false, dir: "tmp"}, function _tempFileCreated(err, path, fd, cleanupCallback) {
if (err) throw err;
// console.log('File: ', path);
// console.log('Filedescriptor: ', fd);
// console.log(`FileName: ${fontName}`);
fs.writeFile(path, fontFile.data, function(err){
console.log('wrote to file!');
// setup font with new font file
setupSVG(path);
cleanupCallback();
// return font name
console.log(`returning font name ${fontName}`);
res.send(fontName);
});
});
});
/////////
// LOAD FONT - POST
// Loads selected font from pre-defined set
////////
app.post('/loadFont', function(req, res){
console.log('loadFont post request received with input: ' + JSON.stringify(req.body));
var fontPath = "fonts/" + req.body.font + "-Regular.ttf";
setupSVG(fontPath); // update to load custom font
});
//////////
// CREATE STENCIL - POST
// Take text input from form and create Stencil SVG
//////////
app.post('/createStencil', function(req, res){
console.log('//////// post request received with input: ' + req.body.text);
var input = req.body.text;
textWidths = [];
var inputChars = input.split('');
console.log(`inputChars: ${inputChars}`);
var origPathArr = []; // storing individual paths for each character, before removing counters
var newPathArr = []; // storing individual paths for each character, after removing counters
fullHeight = (textToSVG.getMetrics(inputChars[0], defaultOptions).height-textToSVG.getMetrics(inputChars[0], defaultOptions).descender)*paddingFactor; // for now, restrict output SVG to single line
// construct each individual character from the input
for(var i=0; i<inputChars.length; i++){
// create SVG from character (no stencil applied yet)
var newOptions = constructOptions(i);
var svgPath = textToSVG.getPath(inputChars[i], newOptions);
// console.log(`svgPath for ${inputCharts[i]`: ${svgPath}`);
var charWidth = textToSVG.getMetrics(inputChars[i], defaultOptions).width;
var charHeight = textToSVG.getMetrics(inputChars[i], defaultOptions).height;
if(charHeight > fullHeight) fullHeight = chartHeight;
// console.log('charWidth: ' + charWidth);
textWidths.push(charWidth);
origPathArr.push(svgPath);
// apply stencil if necessary (if character has a counter)
if(counters.includes(inputChars[i])){
var newSVGpath = removeCounters(svgPath, inputChars[i]); // remove counters if necessary
// console.log(`newSVGpath for ${inputChars[i]}: ${newSVGpath}`);
}else{
var newSVGpath = svgPath;
}
// console.log(`newSVGpath ${newSVGpath}`);
newPathArr.push(newSVGpath);
// console.log('');
}
// compile all paths into a single svg
var origSVG = compileSVGfromPaths(origPathArr);
// console.log(`origSVG: ${origSVG}`);
var newSVG = compileSVGfromPaths(newPathArr);
// console.log(`newSVG: ${newSVG}`);
// return cleaned svg
var respBody = {origSVG, newSVG};
res.send(respBody);
});
// constructOptions(index)
// create options for generating svg with text-to-svg that moves x position for each character
function constructOptions(index){
// console.log(`constructOptions with index ${index}`);
var options = {y: 0, fontSize: 100, anchor: 'top baseline', attributes: attributes};
if(index == 0){
var x = 0;
}else{
var x = getNewX(); // finds the new X position for the latest character
}
options["x"] = x;
// console.log('newX: ' + options["x"]);
// console.log(`textWidths ${textWidths}`);
return options;
}
// listen for requests :)
var listener = app.listen(process.env.PORT || port, function () {
// console.log('Your app is listening on port ' + listener.address().port);
});
var exports = module.exports = {};
function setupSVG(fontPath){
console.log(`setupSVG with path ${fontPath}`);
if(fontPath){
console.log('loading selected font');
textToSVG = TextToSVG.loadSync(fontPath);
}else{
console.log('loading default font');
textToSVG = TextToSVG.loadSync(defaultFontPath);
}
// console.log('textToSVG ready!');
}
function getNewX(){
const reducer = (accumulator, currentValue) => accumulator + currentValue;
return textWidths.reduce(reducer);
}
// getSVGinfo
// get SVG width, height, and path data from text
function getSVGinfo(input){
var info = {};
var width = textToSVG.getMetrics(input, defaultOptions).width;
var height = textToSVG.getMetrics(input, defaultOptions).height-textToSVG.getMetrics(input, defaultOptions).descender;
var pathD = textToSVG.getD(input, defaultOptions);
info["width"] = width;
info["height"] = height;
info["pathD"] = pathD;
return info;
}
// removeCounters(svgPath, char)
// takes an SVG Path and character and returns an SVG Path that has been stenciled
function removeCounters(svgPath, char) {
// console.log("removeCounters");
var maskDim = 5; // how wide the mask rectangle should be
var svgInfo = getSVGinfo(char);
// console.log(`svgWidth: ${svgWidth} svgHeight: ${svgHeight}`);
// console.log(`//////////// on char ${char}`);
// console.log(`svgInfo.pathD for ${char}: ${svgInfo.pathD}`);
var subjPaths = createPath(svgInfo.pathD);
// console.log(`polygonPaths for ${char}: ${JSON.stringify(subjPaths)}`);
var clipXstart = (svgInfo.width-maskDim)/2;
var clipXend = (svgInfo.width+maskDim)/2;
var clipPaths = new ClipperLib.Paths();
var clipPath = new ClipperLib.Path();
clipPath.push(
new ClipperLib.IntPoint(clipXstart,0),
new ClipperLib.IntPoint(clipXend,0),
new ClipperLib.IntPoint(clipXend, svgInfo.height),
new ClipperLib.IntPoint(clipXstart, svgInfo.height)
);
clipPaths.push(clipPath);
// setup stuff
ClipperLib.JS.ScaleUpPaths(subjPaths, scale);
ClipperLib.JS.ScaleUpPaths(clipPaths, scale);
var cpr = new ClipperLib.Clipper();
cpr.AddPaths(subjPaths, ClipperLib.PolyType.ptSubject, true);
cpr.AddPaths(clipPaths, ClipperLib.PolyType.ptClip, true);
var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
var clip_fillType = ClipperLib.PolyFillType.pftNonZero;
var clipType = ClipperLib.ClipType.ctDifference;
// perform boolean
var solution_paths = new ClipperLib.Paths();
cpr.Execute(clipType, solution_paths, subject_fillType, clip_fillType);
// console.log('solutionsPath: ' + JSON.stringify(solution_paths));
var newSVGPathD = paths2string(solution_paths, scale);
// console.log('newSVGPathD ' + newSVGPathD);
var transformed = svgpath(newSVGPathD).translate(getNewX()-textWidths[textWidths.length-1], 0);
// console.log('transformed ' + transformed);
var newSVGPath = '<path stroke="black" fill="none" stroke-width="1" d="' + transformed + '"/>';
// console.log(`newSVGPath for ${char}: ${newSVGPath}`);
// newSVGPath = svgpath(newSVGPath).translate(getNewX, 0);
return newSVGPath;
}
// createPathFromSolution(solution_paths)
// creates SVG path for clipper.js solution path
function createPathFromSolution(solution_paths){
return '<path stroke="black" fill="none" stroke-width="1" d="' + paths2string(solution_paths, scale) + '"/>';
}
// compileSVGfromPaths(pathsArr)
// create an svg from a path of arrays
function compileSVGfromPaths(pathsArr){
const reducer = (accumulator, currentValue) => accumulator + currentValue;
var fullWidth = textWidths.reduce(reducer)*paddingFactor; // add a little bit extra
var newSVG = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:transparent" width="${fullWidth}" height="${fullHeight}">`;
for(var i=0; i< pathsArr.length; i++){
newSVG += pathsArr[i];
}
return newSVG;
}
// path2strings
// takes paths from clipper.js and converts them to svg paths
function paths2string (paths, scale) {
var svgpath = "", i, j;
if (!scale) scale = 1;
for(i = 0; i < paths.length; i++) {
for(j = 0; j < paths[i].length; j++){
if (!j) svgpath += "M";
else svgpath += "L";
svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
}
svgpath += "Z";
}
// if (svgpath=="") svgpath = "M0,0";
return svgpath;
}
// createPath
// create polygon path from an SVG path to use with clipper.js
function createPath(svgPathD){
var paths = new ClipperLib.Paths();
// split svgPathD into arrays based on closed paths
var indexes = getAllIndexes(svgPathD, "M");
// console.log(`indexes: ${JSON.stringify(indexes)}`);
var newSVGpathsD = [];
for(i=0; i<indexes.length; i++){
var subPath = "";
if(i == 0){
// console.log('first index');
subPath = svgPathD.substring(i, indexes[i+1]);
}else if(i == indexes.length-1){
// last path
// console.log('last index');
subPath = svgPathD.substring(indexes[i]);
}else{
// console.log(`substring ${indexes[i]} to ${indexes[i+1]}`);
subPath = svgPathD.substring(indexes[i], indexes[i+1]);
}
// sometimes run into issue with the path not ending with Z - a quick fix here
if(subPath.slice(-1) != "Z"){
subPath = subPath.replaceAt(subPath.length-1, "Z");
}
// console.log(`${i} subPath with length ${subPath.length} : ${subPath}`);
// console.log(`----------------------`);
newSVGpathsD.push(subPath);
}
// console.log(`newSVGpathsD: ${JSON.stringify(newSVGpathsD)}`);
for(x=0; x<newSVGpathsD.length; x++){
// console.log(`path creation loop ${x}`);
var path = new ClipperLib.Path();
// var properties = pathProperties.svgPathProperties(newSVGpathsD[x]);
// var len = Math.round(properties.getTotalLength());
var pts = point(newSVGpathsD[x]);
var len = Math.round(pts.length());
// console.log(`path length ${len}`);
for(var i=0; i<len; i++){
var p = pts.at(i);
// var p = properties.getPointAtLength(i);
// console.log(`${i} p ${JSON.stringify(p)}`);
// path.push(new ClipperLib.IntPoint(p.x, p.y));
path.push(new ClipperLib.IntPoint(p[0], p[1]));
}
// console.log(`path: ${JSON.stringify(path)}`);
// console.log('');
// add this array to paths
paths.push(path);
}
// console.log(`paths: ${JSON.stringify(paths)}`);
return paths;
}
// getAllIndexes
// find indexes of all occurences of val within a string
function getAllIndexes(arr, val) {
var indexes = [], i;
for(i = 0; i < arr.length; i++)
if (arr[i] === val)
indexes.push(i);
return indexes;
}
String.prototype.replaceAt=function(index, replacement) {
return this.substr(0, index) + replacement+ this.substr(index + replacement.length);
}