UNPKG

20 kBJavaScriptView Raw
1/* eslint-disable newline-per-chained-call */
2'use strict';
3
4const assert = require('assert');
5const fs = require('fs');
6const path = require('path');
7const ucs2 = require('punycode').ucs2;
8
9const SVGIcons2SVGFontStream = require('../src/index.js');
10const StringDecoder = require('string_decoder').StringDecoder;
11const SVGIconsDirStream = require('../src/iconsdir');
12const streamtest = require('streamtest');
13
14const neatequal = require('neatequal');
15const codepoint = require('./expected/test-codepoint.json');
16
17// Helpers
18function generateFontToFile(options, done, fileSuffix, startUnicode, files) {
19 // eslint-disable-line
20 const dest = path.join(
21 __dirname,
22 'results',
23 `${options.fontName + (fileSuffix || '')}.svg`
24 );
25
26 options.log = () => {};
27 options.round = options.round || 1e3;
28 const svgFontStream = new SVGIcons2SVGFontStream(options);
29
30 svgFontStream.pipe(fs.createWriteStream(dest)).on('finish', () => {
31 assert.equal(
32 fs.readFileSync(dest, { encoding: 'utf8' }),
33 fs.readFileSync(
34 path.join(
35 __dirname,
36 'expected',
37 `${options.fontName + (fileSuffix || '')}.svg`
38 ),
39 { encoding: 'utf8' }
40 )
41 );
42 done();
43 });
44
45 new SVGIconsDirStream(
46 files || path.join(__dirname, 'fixtures', options.fontName),
47 {
48 startUnicode: startUnicode || 0xe001,
49 }
50 ).pipe(svgFontStream);
51}
52
53function generateFontToMemory(options, done, files, startUnicode) {
54 let content = '';
55 const decoder = new StringDecoder('utf8');
56
57 options.log = () => {};
58 options.round = options.round || 1e3;
59
60 options.callback = (glyphs) => {
61 const fontName = options.fontName;
62
63 neatequal(glyphs, codepoint[fontName]);
64 };
65
66 const svgFontStream = new SVGIcons2SVGFontStream(options);
67
68 svgFontStream.on('data', (chunk) => {
69 content += decoder.write(chunk);
70 });
71
72 svgFontStream.on('finish', () => {
73 assert.equal(
74 content,
75 fs.readFileSync(
76 path.join(__dirname, 'expected', `${options.fontName}.svg`),
77 { encoding: 'utf8' }
78 )
79 );
80 done();
81 });
82
83 new SVGIconsDirStream(
84 files || path.join(__dirname, 'fixtures', options.fontName),
85 {
86 startUnicode: startUnicode || 0xe001,
87 }
88 ).pipe(svgFontStream);
89}
90
91// Tests
92describe('Generating fonts to files', () => {
93 it('should work for simple SVG', (done) => {
94 generateFontToFile(
95 {
96 fontName: 'originalicons',
97 },
98 done
99 );
100 });
101
102 it('should work for simple fixedWidth and normalize option', (done) => {
103 generateFontToFile(
104 {
105 fontName: 'originalicons',
106 fixedWidth: true,
107 normalize: true,
108 },
109 done,
110 'n'
111 );
112 });
113
114 it('should work for simple SVG', (done) => {
115 generateFontToFile(
116 {
117 fontName: 'cleanicons',
118 },
119 done
120 );
121 });
122
123 it('should work for simple SVG and custom ascent', (done) => {
124 generateFontToFile(
125 {
126 fontName: 'cleanicons',
127 ascent: 100,
128 },
129 done,
130 '-ascent'
131 );
132 });
133
134 it('should work for simple SVG and custom properties', (done) => {
135 generateFontToFile(
136 {
137 fontName: 'cleanicons',
138 fontStyle: 'italic',
139 fontWeight: 'bold',
140 },
141 done,
142 '-stw'
143 );
144 });
145
146 it('should work for codepoint mapped SVG icons', (done) => {
147 generateFontToFile(
148 {
149 fontName: 'prefixedicons',
150 callback: () => {},
151 },
152 done
153 );
154 });
155
156 it('should work with multipath SVG icons', (done) => {
157 generateFontToFile(
158 {
159 fontName: 'multipathicons',
160 },
161 done
162 );
163 });
164
165 it('should work with simple shapes SVG icons', (done) => {
166 generateFontToFile(
167 {
168 fontName: 'shapeicons',
169 },
170 done
171 );
172 });
173
174 it('should work with variable height icons', (done) => {
175 generateFontToFile(
176 {
177 fontName: 'variableheighticons',
178 },
179 done
180 );
181 });
182
183 it('should work with variable height icons and the normalize option', (done) => {
184 generateFontToFile(
185 {
186 fontName: 'variableheighticons',
187 normalize: true,
188 },
189 done,
190 'n'
191 );
192 });
193
194 it('should work with variable width icons', (done) => {
195 generateFontToFile(
196 {
197 fontName: 'variablewidthicons',
198 },
199 done
200 );
201 });
202
203 it('should work with centered variable width icons and the fixed width option', (done) => {
204 generateFontToFile(
205 {
206 fontName: 'variablewidthicons',
207 fixedWidth: true,
208 centerHorizontally: true,
209 },
210 done,
211 'n'
212 );
213 });
214
215 it('should work with a font id', (done) => {
216 generateFontToFile(
217 {
218 fontName: 'variablewidthicons',
219 fixedWidth: true,
220 centerHorizontally: true,
221 fontId: 'plop',
222 },
223 done,
224 'id'
225 );
226 });
227
228 it('should work with scaled icons', (done) => {
229 generateFontToFile(
230 {
231 fontName: 'scaledicons',
232 fixedWidth: true,
233 centerHorizontally: true,
234 fontId: 'plop',
235 },
236 done
237 );
238 });
239
240 it('should not display hidden paths', (done) => {
241 generateFontToFile(
242 {
243 fontName: 'hiddenpathesicons',
244 },
245 done
246 );
247 });
248
249 it('should work with real world icons', (done) => {
250 generateFontToFile(
251 {
252 fontName: 'realicons',
253 },
254 done
255 );
256 });
257
258 it('should work with rendering test SVG icons', (done) => {
259 generateFontToFile(
260 {
261 fontName: 'rendricons',
262 },
263 done
264 );
265 });
266
267 it('should work with a single SVG icon', (done) => {
268 generateFontToFile(
269 {
270 fontName: 'singleicon',
271 },
272 done
273 );
274 });
275
276 it('should work with transformed SVG icons', (done) => {
277 generateFontToFile(
278 {
279 fontName: 'transformedicons',
280 },
281 done
282 );
283 });
284
285 it('should work when horizontally centering SVG icons', (done) => {
286 generateFontToFile(
287 {
288 fontName: 'tocentericons',
289 centerHorizontally: true,
290 },
291 done
292 );
293 });
294
295 it('should work when vertically centering SVG icons', (done) => {
296 generateFontToFile(
297 {
298 fontName: 'toverticalcentericons',
299 centerVertically: true,
300 },
301 done
302 );
303 });
304
305 it('should work with a icons with path with fill none', (done) => {
306 generateFontToFile(
307 {
308 fontName: 'pathfillnone',
309 },
310 done
311 );
312 });
313
314 it('should work with shapes with rounded corners', (done) => {
315 generateFontToFile(
316 {
317 fontName: 'roundedcorners',
318 },
319 done
320 );
321 });
322
323 it('should work with a lot of icons', (done) => {
324 generateFontToFile(
325 {
326 fontName: 'lotoficons',
327 },
328 done,
329 '',
330 0,
331 [
332 'tests/fixtures/cleanicons/account.svg',
333 'tests/fixtures/cleanicons/arrow-down.svg',
334 'tests/fixtures/cleanicons/arrow-left.svg',
335 'tests/fixtures/cleanicons/arrow-right.svg',
336 'tests/fixtures/cleanicons/arrow-up.svg',
337 'tests/fixtures/cleanicons/basket.svg',
338 'tests/fixtures/cleanicons/close.svg',
339 'tests/fixtures/cleanicons/minus.svg',
340 'tests/fixtures/cleanicons/plus.svg',
341 'tests/fixtures/cleanicons/search.svg',
342 'tests/fixtures/hiddenpathesicons/sound--off.svg',
343 'tests/fixtures/hiddenpathesicons/sound--on.svg',
344 'tests/fixtures/multipathicons/kikoolol.svg',
345 'tests/fixtures/originalicons/mute.svg',
346 'tests/fixtures/originalicons/sound.svg',
347 'tests/fixtures/originalicons/speaker.svg',
348 'tests/fixtures/realicons/diegoliv.svg',
349 'tests/fixtures/realicons/hannesjohansson.svg',
350 'tests/fixtures/realicons/roelvanhitum.svg',
351 'tests/fixtures/realicons/safety-icon.svg',
352 'tests/fixtures/realicons/sb-icon.svg',
353 'tests/fixtures/realicons/settings-icon.svg',
354 'tests/fixtures/realicons/track-icon.svg',
355 'tests/fixtures/realicons/web-icon.svg',
356 'tests/fixtures/roundedcorners/roundedrect.svg',
357 'tests/fixtures/shapeicons/circle.svg',
358 'tests/fixtures/shapeicons/ellipse.svg',
359 'tests/fixtures/shapeicons/lines.svg',
360 'tests/fixtures/shapeicons/polygon.svg',
361 'tests/fixtures/shapeicons/polyline.svg',
362 'tests/fixtures/shapeicons/rect.svg',
363 'tests/fixtures/tocentericons/bottomleft.svg',
364 'tests/fixtures/tocentericons/center.svg',
365 'tests/fixtures/tocentericons/topright.svg',
366 ]
367 );
368 });
369
370 it('should work with rotated rectangle icon', (done) => {
371 generateFontToFile(
372 {
373 fontName: 'rotatedrectangle',
374 },
375 done
376 );
377 });
378
379 /**
380 * Issue #6
381 * icon by @paesku
382 * https://github.com/nfroidure/svgicons2svgfont/issues/6#issuecomment-125545925
383 */
384 it('should work with complicated nested transforms', (done) => {
385 generateFontToFile(
386 {
387 fontName: 'paesku',
388 round: 1e3,
389 },
390 done
391 );
392 });
393
394 /**
395 * Issue #76
396 * https://github.com/nfroidure/svgicons2svgfont/issues/76#issue-259831969
397 */
398 it('should work with transform=translate(x) without y', (done) => {
399 generateFontToFile(
400 {
401 fontName: 'translatex',
402 round: 1e3,
403 },
404 done
405 );
406 });
407
408 it('should work when only rx is present', (done) => {
409 generateFontToFile(
410 {
411 fontName: 'onlywithrx',
412 },
413 done
414 );
415 });
416
417 it('should work when only ry is present', (done) => {
418 generateFontToFile(
419 {
420 fontName: 'onlywithry',
421 },
422 done
423 );
424 });
425});
426
427describe('Generating fonts to memory', () => {
428 it('should work for simple SVG', (done) => {
429 generateFontToMemory(
430 {
431 fontName: 'originalicons',
432 },
433 done
434 );
435 });
436
437 it('should work for simple SVG', (done) => {
438 generateFontToMemory(
439 {
440 fontName: 'cleanicons',
441 },
442 done
443 );
444 });
445
446 it('should work for codepoint mapped SVG icons', (done) => {
447 generateFontToMemory(
448 {
449 fontName: 'prefixedicons',
450 },
451 done
452 );
453 });
454
455 it('should work with multipath SVG icons', (done) => {
456 generateFontToMemory(
457 {
458 fontName: 'multipathicons',
459 },
460 done
461 );
462 });
463
464 it('should work with simple shapes SVG icons', (done) => {
465 generateFontToMemory(
466 {
467 fontName: 'shapeicons',
468 },
469 done
470 );
471 });
472});
473
474describe('Using options', () => {
475 it('should work with fixedWidth option set to true', (done) => {
476 generateFontToFile(
477 {
478 fontName: 'originalicons',
479 fixedWidth: true,
480 },
481 done,
482 '2'
483 );
484 });
485
486 it('should work with custom fontHeight option', (done) => {
487 generateFontToFile(
488 {
489 fontName: 'originalicons',
490 fontHeight: 800,
491 },
492 done,
493 '3'
494 );
495 });
496
497 it('should work with custom descent option', (done) => {
498 generateFontToFile(
499 {
500 fontName: 'originalicons',
501 descent: 200,
502 },
503 done,
504 '4'
505 );
506 });
507
508 it('should work with fixedWidth set to true and with custom fontHeight option', (done) => {
509 generateFontToFile(
510 {
511 fontName: 'originalicons',
512 fontHeight: 800,
513 fixedWidth: true,
514 },
515 done,
516 '5'
517 );
518 });
519
520 it(
521 'should work with fixedWidth and centerHorizontally set to true and with' +
522 ' custom fontHeight option',
523 (done) => {
524 generateFontToFile(
525 {
526 fontName: 'originalicons',
527 fontHeight: 800,
528 fixedWidth: true,
529 centerHorizontally: true,
530 round: 1e5,
531 },
532 done,
533 '6'
534 );
535 }
536 );
537
538 it(
539 'should work with fixedWidth, normalize and centerHorizontally set to' +
540 ' true and with custom fontHeight option',
541 (done) => {
542 generateFontToFile(
543 {
544 fontName: 'originalicons',
545 fontHeight: 800,
546 normalize: true,
547 fixedWidth: true,
548 centerHorizontally: true,
549 round: 1e5,
550 },
551 done,
552 '7'
553 );
554 }
555 );
556
557 it(
558 'should work with fixedWidth, normalize and centerHorizontally set to' +
559 ' true and with a large custom fontHeight option',
560 (done) => {
561 generateFontToFile(
562 {
563 fontName: 'originalicons',
564 fontHeight: 5000,
565 normalize: true,
566 fixedWidth: true,
567 centerHorizontally: true,
568 round: 1e5,
569 },
570 done,
571 '8'
572 );
573 }
574 );
575
576 it('should work with nested icons', (done) => {
577 generateFontToFile(
578 {
579 fontName: 'nestedicons',
580 },
581 done,
582 '',
583 0xea01
584 );
585 });
586});
587
588describe('Passing code points', () => {
589 it('should work with multiple unicode values for a single icon', (done) => {
590 const svgIconStream = fs.createReadStream(
591 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
592 );
593
594 const svgFontStream = new SVGIcons2SVGFontStream({ round: 1e3 });
595 let content = '';
596 const decoder = new StringDecoder('utf8');
597
598 svgIconStream.metadata = {
599 name: 'account',
600 unicode: ['\uE001', '\uE002'],
601 };
602
603 svgFontStream.on('data', (chunk) => {
604 content += decoder.write(chunk);
605 });
606
607 svgFontStream.on('finish', () => {
608 assert.equal(
609 content,
610 fs.readFileSync(
611 path.join(__dirname, 'expected', 'cleanicons-multi.svg'),
612 { encoding: 'utf8' }
613 )
614 );
615 done();
616 });
617 svgFontStream.write(svgIconStream);
618 svgFontStream.end();
619 });
620
621 it('should work with ligatures', (done) => {
622 const svgIconStream = fs.createReadStream(
623 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
624 );
625 const svgFontStream = new SVGIcons2SVGFontStream({ round: 1e3 });
626 let content = '';
627 const decoder = new StringDecoder('utf8');
628
629 svgIconStream.metadata = {
630 name: 'account',
631 unicode: ['\uE001\uE002'],
632 };
633
634 svgFontStream.on('data', (chunk) => {
635 content += decoder.write(chunk);
636 });
637
638 svgFontStream.on('finish', () => {
639 assert.equal(
640 content,
641 fs.readFileSync(
642 path.join(__dirname, 'expected', 'cleanicons-lig.svg'),
643 { encoding: 'utf8' }
644 )
645 );
646 done();
647 });
648 svgFontStream.write(svgIconStream);
649 svgFontStream.end();
650 });
651
652 it('should work with high code points', (done) => {
653 const svgIconStream = fs.createReadStream(
654 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
655 );
656 const svgFontStream = new SVGIcons2SVGFontStream({ round: 1e3 });
657 let content = '';
658 const decoder = new StringDecoder('utf8');
659
660 svgIconStream.metadata = {
661 name: 'account',
662 unicode: [ucs2.encode([0x1f63a])],
663 };
664
665 svgFontStream.on('data', (chunk) => {
666 content += decoder.write(chunk);
667 });
668
669 svgFontStream.on('finish', () => {
670 assert.equal(
671 content,
672 fs.readFileSync(
673 path.join(__dirname, 'expected', 'cleanicons-high.svg'),
674 { encoding: 'utf8' }
675 )
676 );
677 done();
678 });
679 svgFontStream.write(svgIconStream);
680 svgFontStream.end();
681 });
682});
683
684describe('Providing bad glyphs', () => {
685 it('should fail when not providing glyph name', (done) => {
686 const svgIconStream = fs.createReadStream(
687 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
688 );
689
690 svgIconStream.metadata = {
691 unicode: '\uE001',
692 };
693 new SVGIcons2SVGFontStream({ round: 1e3 })
694 .on('error', (err) => {
695 assert.equal(err instanceof Error, true);
696 assert.equal(
697 err.message,
698 'Please provide a name for the glyph at index 0'
699 );
700 done();
701 })
702 .write(svgIconStream);
703 });
704
705 it('should fail when not providing codepoints', (done) => {
706 const svgIconStream = fs.createReadStream(
707 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
708 );
709
710 svgIconStream.metadata = {
711 name: 'test',
712 };
713 new SVGIcons2SVGFontStream({ round: 1e3 })
714 .on('error', (err) => {
715 assert.equal(err instanceof Error, true);
716 assert.equal(
717 err.message,
718 'Please provide a codepoint for the glyph "test"'
719 );
720 done();
721 })
722 .write(svgIconStream);
723 });
724
725 it('should fail when providing unicode value with duplicates', (done) => {
726 const svgIconStream = fs.createReadStream(
727 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
728 );
729
730 svgIconStream.metadata = {
731 name: 'test',
732 unicode: ['\uE002', '\uE002'],
733 };
734 new SVGIcons2SVGFontStream({ round: 1e3 })
735 .on('error', (err) => {
736 assert.equal(err instanceof Error, true);
737 assert.equal(
738 err.message,
739 'Given codepoints for the glyph "test" contain duplicates.'
740 );
741 done();
742 })
743 .write(svgIconStream);
744 });
745
746 it('should fail when providing the same codepoint twice', (done) => {
747 const svgIconStream = fs.createReadStream(
748 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
749 );
750 const svgIconStream2 = fs.createReadStream(
751 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
752 );
753 const svgFontStream = new SVGIcons2SVGFontStream({ round: 1e3 });
754
755 svgIconStream.metadata = {
756 name: 'test',
757 unicode: '\uE002',
758 };
759 svgIconStream2.metadata = {
760 name: 'test2',
761 unicode: '\uE002',
762 };
763 svgFontStream.on('error', (err) => {
764 assert.equal(err instanceof Error, true);
765 assert.equal(
766 err.message,
767 'The glyph "test2" codepoint seems to be used already elsewhere.'
768 );
769 done();
770 });
771 svgFontStream.write(svgIconStream);
772 svgFontStream.write(svgIconStream2);
773 });
774
775 it('should fail when providing the same name twice', (done) => {
776 const svgIconStream = fs.createReadStream(
777 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
778 );
779 const svgIconStream2 = fs.createReadStream(
780 path.join(__dirname, 'fixtures', 'cleanicons', 'account.svg')
781 );
782 const svgFontStream = new SVGIcons2SVGFontStream({ round: 1e3 });
783
784 svgIconStream.metadata = {
785 name: 'test',
786 unicode: '\uE001',
787 };
788 svgIconStream2.metadata = {
789 name: 'test',
790 unicode: '\uE002',
791 };
792 svgFontStream.on('error', (err) => {
793 assert.equal(err instanceof Error, true);
794 assert.equal(err.message, 'The glyph name "test" must be unique.');
795 done();
796 });
797 svgFontStream.write(svgIconStream);
798 svgFontStream.write(svgIconStream2);
799 });
800
801 it('should fail when providing bad pathdata', (done) => {
802 const svgIconStream = fs.createReadStream(
803 path.join(__dirname, 'fixtures', 'badicons', 'pathdata.svg')
804 );
805
806 svgIconStream.metadata = {
807 name: 'test',
808 unicode: ['\uE002'],
809 };
810 new SVGIcons2SVGFontStream({ round: 1e3 })
811 .on('error', (err) => {
812 assert.equal(err instanceof Error, true);
813 assert.equal(
814 err.message,
815 'Got an error parsing the glyph "test":' +
816 ' Expected a flag, got "20" at index "23".'
817 );
818 done();
819 })
820 .on('end', () => {
821 done();
822 })
823 .write(svgIconStream);
824 });
825
826 it('should fail when providing bad XML', (done) => {
827 const svgIconStream = streamtest.v2.fromChunks(['bad', 'xml']);
828
829 svgIconStream.metadata = {
830 name: 'test',
831 unicode: ['\uE002'],
832 };
833
834 let firstError = true;
835
836 new SVGIcons2SVGFontStream({ round: 1e3 })
837 .on('error', (err) => {
838 assert.equal(err instanceof Error, true);
839
840 if (firstError) {
841 firstError = false;
842 assert.equal(
843 err.message,
844 'Non-whitespace before first tag.\nLine: 0\nColumn: 1\nChar: b'
845 );
846
847 done();
848 }
849 })
850 .write(svgIconStream);
851 });
852});