forked from twitter/twemoji
-
Notifications
You must be signed in to change notification settings - Fork 0
/
twemoji-generator.js
executable file
·883 lines (804 loc) · 31 KB
/
twemoji-generator.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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
#!/usr/bin/env node
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
*/
// dependencies
var fs = require('fs');
var http = require('http');
// Twitter assets by property name
var assets = {
'16x16': [],
'36x36': [],
'72x72': []
};
// white spaces we don't want to catch via the RegExp
// there is no asset equivalent for these
var ignoreMissing = ['2002', '2003', '2005'];
// basic utility to organize async code
// see: http://webreflection.blogspot.co.uk/2012/03/tweet-sized-queue-system.html
// or: http://webreflection.blogspot.co.uk/2012/06/working-with-queues.html
function Queue(args, f) {
setTimeout(args.next = function next() {
return (f = args.shift()) ? !!f(args) || !0 : !1;
}, 0);
return args;
}
// main task
Queue([
// will populate assets arrays
function grabAllAssets(q) {
console.log('analyzing all assets ... ');
// per each path/folder
Object.keys(assets).forEach(function (path, i, paths) {
// grab all files in that folder
fs.readdir('./' + path, function (err, files) {
// and add them to the assets path
assets[path].push.apply(
assets[path],
files.map(upperCaseWithoutExtension)
);
// once all assets arrays have been populated
if (paths.reduce(completed, true)) {
console.log('[INFO] assets contains ' + assets[path].length + ' emoji.');
q.next();
}
});
});
// drop extension + uppercase
function upperCaseWithoutExtension(file) {
return file.slice(0, file.lastIndexOf('.')).toUpperCase();
}
// returns true if all assets have been populated
function completed(p, c) {
return p && assets[c].length;
}
},
// will fetch and store all emoji from unicode.org
function fetchEmojiSources(q) {
console.log('fetching EmojiSources.txt ... ');
// grab all emoji and test them against them
http.get("http://www.unicode.org/Public/UNIDATA/EmojiSources.txt", function (res) {
var chunks = [];
// if all good ...
if (res.statusCode === 200) {
// grab all data
res.on('data', chunks.push.bind(chunks));
// once done ...
res.on('end', function () {
console.log('analizing EmojiSources VS our assets ... ');
// store all missing assets in one object
var missing = {};
// will be used to store an array with all missing
var missingGrouped = {};
// will be needed later on
// parse it, clean it, and store it once
q.emojiSource = chunks
.join('')
.split(/\r\n|\r|\n/)
// filter once
.filter(function (line) {
return this.test(line);
}, /^[0-9A-F]/)
// take only emoji info
.map(function (codePoint) {
return codePoint
.slice(0, codePoint.indexOf(';'))
.toUpperCase()
// drop spaces
.replace(/\s+/g, '-')
// drop 0 padded prefixes
.replace(/^0+/g, '');
});
console.log('[INFO] parsed ' + q.emojiSource.length + ' standard emoji.');
// find out which one is missing from our assets
q.emojiSource.forEach(
function (emoji) {
// do not loop for emoji we know we should ignore
if (ignoreMissing.indexOf(emoji) < 0) {
// verify all others per each folder
this.forEach(function (path) {
if (assets[path].indexOf(emoji) < 0) {
(missing[path] || (missing[path] = [])).push(emoji);
missingGrouped[emoji] = true;
}
});
}
},
// and per each folder
Object.keys(assets)
);
// if some missing emoji has been found
if (Object.keys(missing).length) {
// warn and show which one is missing
console.warn('[WARNING] missing assets for:');
console.log(missing);
}
// create the array of all emoji we should ignore
q.ignore = ignoreMissing.concat(Object.keys(missingGrouped));
q.next();
});
} else {
console.error('[ERROR] unable to fetch emoji at unicode.org');
process.exit(1);
}
});
},
// grab the list of emoji that behave differently when
// variants such \uFE0E and \uFE0F are in place
function grabStandardVariants(q) {
console.log('fetching emoji-variation-sequences.txt ... ');
http.get(
"http://unicode.org/Public/emoji/latest/emoji-variation-sequences.txt",
function(res) {
var chunks = [];
if (res.statusCode == 200) {
res.on('data', chunks.push.bind(chunks));
res.on('end', function () {
// cleaning up parsing sensitive emoji
q.variantsSensitive = chunks
.join('') // all content
.split(/\r\n|\r|\n/) // split in lines
.filter(function (line) { // containing FE0E; info
return this.test(line); // avoiding duplicated with FE0F
}, / FE0E; text style/)
.map(function (line) { // cleaned up to grab
return line.replace(this, '$1') // only first unicode
.toUpperCase(); // normalized as uppercase
}, /^([0-9A-F]{4,}) FE0E;.+$/) // sensitive char
;
console.log('[INFO] parsed ' + q.variantsSensitive.length + ' variant sensitive emoji.');
q.next();
});
} else {
console.error('[ERROR] unable to fetch standard variants at unicode.org');
process.exit(1);
}
}
);
},
// add our own assets that are not part of the Unicode standard
function addMissingEmojiAndSort(q) {
q.nonStandard = [];
Object.keys(assets).forEach(function (path, i) {
assets[path].forEach(function (emoji) {
if (
q.emojiSource.indexOf(emoji) < 0 &&
q.variantsSensitive.indexOf(emoji) < 0 &&
q.nonStandard.indexOf(emoji) < 0
) {
q.nonStandard.push(emoji);
}
});
});
if (q.nonStandard.length) {
console.warn('[WARNING] assets contain ' + q.nonStandard.length + ' non standard emoji:');
// console.log(q.nonStandard.join(', '));
}
// order by sequence of chars length
q.emojiSource = q.emojiSource.concat(q.nonStandard).sort(sort);
// actually this is not needed
// q.variantsSensitive.sort(sort);
q.next();
function sort(a, b) {
var diff = b.length - a.length;
if (diff) return diff;
return parseInt(b.split('-')[0], 10) -
parseInt(a.split('-')[0], 10)
}
},
// with all info, generate a RegExp that will catch
// only standard emoji that are present in our assets
function generateRegExp(q) {
console.log('generating a RegExp for available assets');
var sensitive = [];
var regular = [];
q.emojiSource.forEach(function (codePoint) {
var u;
if (q.ignore.indexOf(codePoint) < 0) {
u = codePoint.split('-').map(toJSON).join('');
if (q.variantsSensitive.indexOf(codePoint) < 0) {
regular.push(u);
} else {
sensitive.push(u);
}
}
});
// create a RegExp with properly ordered matches
q.re = '((?:' +
regular.join('|') + ')|(?:(?:' +
sensitive.join('|') +
')([\\uFE0E\\uFE0F]?)))';
q.next();
// basic utilities to convert codepoints to JSON strings
function toJSON(point) {
return UTF162JSON(fromCodePoint(point));
}
function fromCodePoint(codepoint) {
var code = typeof codepoint === 'string' ?
parseInt(codepoint, 16) : codepoint;
if (code < 0x10000) {
return String.fromCharCode(code);
}
code -= 0x10000;
return String.fromCharCode(
0xD800 + (code >> 10),
0xDC00 + (code & 0x3FF)
);
}
function UTF162JSON(text) {
for (var i = 0, r = []; i < text.length; i++) {
r.push('\\u' + ('000' + text.charCodeAt(i).toString(16)).slice(-4));
}
return r.join('');
}
},
function generateFile(q) {
console.log('generating ./twemoji.js');
createTwemoji(q.re);
require('child_process').spawn('node', ['twemoji-dist.js']);
}
]);
function createTwemoji(re) {
fs.writeFileSync(
'twemoji.js',
'/*jslint indent: 2, browser: true, bitwise: true, plusplus: true */\n' +
'var twemoji = (' +
function (
/*! Copyright Twitter Inc. and other contributors. Licensed under MIT *//*
https://github.com/twitter/twemoji/blob/gh-pages/LICENSE
*/
// WARNING: this file is generated automatically via
// `node twemoji-generator.js`
// please update its `createTwemoji` function
// at the bottom of the same file instead.
) {
'use strict';
/*jshint maxparams:4 */
var
// the exported module object
twemoji = {
/////////////////////////
// properties //
/////////////////////////
// default assets url, by default will be Twitter Inc. CDN
base: (location.protocol === 'https:' ? 'https:' : 'http:') +
'//twemoji.maxcdn.com/',
// default assets file extensions, by default '.png'
ext: '.png',
// default assets/folder size, by default "36x36"
// available via Twitter CDN: 16, 36, 72
size: '36x36',
// default class name, by default 'emoji'
className: 'emoji',
// basic utilities / helpers to convert code points
// to JavaScript surrogates and vice versa
convert: {
/**
* Given an HEX codepoint, returns UTF16 surrogate pairs.
*
* @param string generic codepoint, i.e. '1F4A9'
* @return string codepoint transformed into utf16 surrogates pair,
* i.e. \uD83D\uDCA9
*
* @example
* twemoji.convert.fromCodePoint('1f1e8');
* // "\ud83c\udde8"
*
* '1f1e8-1f1f3'.split('-').map(twemoji.convert.fromCodePoint).join('')
* // "\ud83c\udde8\ud83c\uddf3"
*/
fromCodePoint: fromCodePoint,
/**
* Given UTF16 surrogate pairs, returns the equivalent HEX codepoint.
*
* @param string generic utf16 surrogates pair, i.e. \uD83D\uDCA9
* @param string optional separator for double code points, default='-'
* @return string utf16 transformed into codepoint, i.e. '1F4A9'
*
* @example
* twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3');
* // "1f1e8-1f1f3"
*
* twemoji.convert.toCodePoint('\ud83c\udde8\ud83c\uddf3', '~');
* // "1f1e8~1f1f3"
*/
toCodePoint: toCodePoint
},
/////////////////////////
// methods //
/////////////////////////
/**
* User first: used to remove missing images
* preserving the original text intent when
* a fallback for network problems is desired.
* Automatically added to Image nodes via DOM
* It could be recycled for string operations via:
* $('img.emoji').on('error', twemoji.onerror)
*/
onerror: function onerror() {
if (this.parentNode) {
this.parentNode.replaceChild(createText(this.alt), this);
}
},
/**
* Main method/logic to generate either <img> tags or HTMLImage nodes.
* "emojify" a generic text or DOM Element.
*
* @overloads
*
* String replacement for `innerHTML` or server side operations
* twemoji.parse(string);
* twemoji.parse(string, Function);
* twemoji.parse(string, Object);
*
* HTMLElement tree parsing for safer operations over existing DOM
* twemoji.parse(HTMLElement);
* twemoji.parse(HTMLElement, Function);
* twemoji.parse(HTMLElement, Object);
*
* @param string|HTMLElement the source to parse and enrich with emoji.
*
* string replace emoji matches with <img> tags.
* Mainly used to inject emoji via `innerHTML`
* It does **not** parse the string or validate it,
* it simply replaces found emoji with a tag.
* NOTE: be sure this won't affect security.
*
* HTMLElement walk through the DOM tree and find emoji
* that are inside **text node only** (nodeType === 3)
* Mainly used to put emoji in already generated DOM
* without compromising surrounding nodes and
* **avoiding** the usage of `innerHTML`.
* NOTE: Using DOM elements instead of strings should
* improve security without compromising too much
* performance compared with a less safe `innerHTML`.
*
* @param Function|Object [optional]
* either the callback that will be invoked or an object
* with all properties to use per each found emoji.
*
* Function if specified, this will be invoked per each emoji
* that has been found through the RegExp except
* those follwed by the invariant \uFE0E ("as text").
* Once invoked, parameters will be:
*
* codePoint:string the lower case HEX code point
* i.e. "1f4a9"
*
* options:Object all info for this parsing operation
*
* variant:char the optional \uFE0F ("as image")
* variant, in case this info
* is anyhow meaningful.
* By default this is ignored.
*
* If such callback will return a falsy value instead
* of a valid `src` to use for the image, nothing will
* actually change for that specific emoji.
*
*
* Object if specified, an object containing the following properties
*
* callback Function the callback to invoke per each found emoji.
* base string the base url, by default twemoji.base
* ext string the image extension, by default twemoji.ext
* size string the assets size, by default twemoji.size
*
* @example
*
* twemoji.parse("I \u2764\uFE0F emoji!");
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
*
*
* twemoji.parse("I \u2764\uFE0F emoji!", function(icon, options, variant) {
* return '/assets/' + icon + '.gif';
* });
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
*
*
* twemoji.parse("I \u2764\uFE0F emoji!", {
* size: 72,
* callback: function(icon, options, variant) {
* return '/assets/' + options.size + '/' + icon + options.ext;
* }
* });
* // I <img class="emoji" draggable="false" alt="❤️" src="/assets/72x72/2764.png"> emoji!
*
*/
parse: parse,
/**
* Given a string, invokes the callback argument
* per each emoji found in such string.
* This is the most raw version used by
* the .parse(string) method itself.
*
* @param string generic string to parse
* @param Function a generic callback that will be
* invoked to replace the content.
* This calback wil receive standard
* String.prototype.replace(str, callback)
* arguments such:
* callback(
* match, // the emoji match
* icon, // the emoji text (same as text)
* variant // either '\uFE0E' or '\uFE0F', if present
* );
*
* and others commonly received via replace.
*
* NOTE: When the variant \uFE0E is found, remember this is an explicit intent
* from the user: the emoji should **not** be replaced with an image.
* In \uFE0F case one, it's the opposite, it should be graphic.
* This utility convetion is that only \uFE0E are not translated into images.
*/
replace: replace,
/**
* Simplify string tests against emoji.
*
* @param string some text that might contain emoji
* @return boolean true if any emoji was found, false otherwise.
*
* @example
*
* if (twemoji.test(someContent)) {
* console.log("emoji All The Things!");
* }
*/
test: test
},
// used to escape HTML special chars in attributes
escaper = {
'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'
},
// RegExp based on emoji's official Unicode standards
// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
re = /twemoji/,
// used to find HTML special chars in attributes
rescaper = /[&<>'"]/g,
// nodes with type 1 which should **not** be parsed (including lower case svg)
shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA|[a-z]/,
// just a private shortcut
fromCharCode = String.fromCharCode;
return twemoji;
/////////////////////////
// private functions //
// declaration //
/////////////////////////
/**
* Shortcut to create text nodes
* @param string text used to create DOM text node
* @return Node a DOM node with that text
*/
function createText(text) {
return document.createTextNode(text);
}
/**
* Utility function to escape html attribute text
* @param string text use in HTML attribute
* @return string text encoded to use in HTML attribute
*/
function escapeHTML(s) {
return s.replace(rescaper, replacer);
}
/**
* Default callback used to generate emoji src
* based on Twitter CDN
* @param string the emoji codepoint string
* @param string the default size to use, i.e. "36x36"
* @param string optional "\uFE0F" variant char, ignored by default
* @return string the image source to use
*/
function defaultImageSrcGenerator(icon, options) {
return ''.concat(options.base, options.size, '/', icon, options.ext);
}
/**
* Given a generic DOM nodeType 1, walk through all children
* and store every nodeType 3 (#text) found in the tree.
* @param Element a DOM Element with probably some text in it
* @param Array the list of previously discovered text nodes
* @return Array same list with new discovered nodes, if any
*/
function grabAllTextNodes(node, allText) {
var
childNodes = node.childNodes,
length = childNodes.length,
subnode,
nodeType;
while (length--) {
subnode = childNodes[length];
nodeType = subnode.nodeType;
// parse emoji only in text nodes
if (nodeType === 3) {
// collect them to process emoji later
allText.push(subnode);
}
// ignore all nodes that are not type 1 or that
// should not be parsed as script, style, and others
else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
grabAllTextNodes(subnode, allText);
}
}
return allText;
}
/**
* Used to both remove the possible variant
* and to convert utf16 into code points
* @param string the emoji surrogate pair
* @param string the optional variant char, if any
* @return string the code point
*/
function grabTheRightIcon(icon, variant) {
// if variant is present as \uFE0F
return toCodePoint(
variant === '\uFE0F' ?
// the icon should not contain it
icon.slice(0, -1) :
// fix non standard OSX behavior
(icon.length === 3 && icon.charAt(1) === '\uFE0F' ?
icon.charAt(0) + icon.charAt(2) : icon)
);
}
/**
* DOM version of the same logic / parser:
* emojify all found sub-text nodes placing images node instead.
* @param Element generic DOM node with some text in some child node
* @param Object options containing info about how to parse
*
* .callback Function the callback to invoke per each found emoji.
* .base string the base url, by default twemoji.base
* .ext string the image extension, by default twemoji.ext
* .size string the assets size, by default twemoji.size
*
* @return Element same generic node with emoji in place, if any.
*/
function parseNode(node, options) {
var
allText = grabAllTextNodes(node, []),
length = allText.length,
attrib,
attrname,
modified,
fragment,
subnode,
text,
match,
i,
index,
img,
alt,
icon,
variant,
src;
while (length--) {
modified = false;
fragment = document.createDocumentFragment();
subnode = allText[length];
text = subnode.nodeValue;
i = 0;
while ((match = re.exec(text))) {
index = match.index;
if (index !== i) {
fragment.appendChild(
createText(text.slice(i, index))
);
}
alt = match[0];
icon = match[1];
variant = match[2];
i = index + alt.length;
if (variant !== '\uFE0E') {
src = options.callback(
grabTheRightIcon(icon, variant),
options,
variant
);
if (src) {
img = new Image();
img.onerror = options.onerror;
img.setAttribute('draggable', 'false');
attrib = options.attributes(icon, variant);
for (attrname in attrib) {
if (
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
!img.hasAttribute(attrname)
) {
img.setAttribute(attrname, attrib[attrname]);
}
}
img.className = options.className;
img.alt = alt;
img.src = src;
modified = true;
fragment.appendChild(img);
}
}
if (!img) fragment.appendChild(createText(alt));
img = null;
}
// is there actually anything to replace in here ?
if (modified) {
// any text left to be added ?
if (i < text.length) {
fragment.appendChild(
createText(text.slice(i))
);
}
// replace the text node only, leave intact
// anything else surrounding such text
subnode.parentNode.replaceChild(fragment, subnode);
}
}
return node;
}
/**
* String/HTML version of the same logic / parser:
* emojify a generic text placing images tags instead of surrogates pair.
* @param string generic string with possibly some emoji in it
* @param Object options containing info about how to parse
*
* .callback Function the callback to invoke per each found emoji.
* .base string the base url, by default twemoji.base
* .ext string the image extension, by default twemoji.ext
* .size string the assets size, by default twemoji.size
*
* @return the string with <img tags> replacing all found and parsed emoji
*/
function parseString(str, options) {
return replace(str, function (match, icon, variant) {
var
ret = match,
attrib,
attrname,
src;
// verify the variant is not the FE0E one
// this variant means "emoji as text" and should not
// require any action/replacement
// http://unicode.org/Public/UNIDATA/StandardizedVariants.html
if (variant !== '\uFE0E') {
src = options.callback(
grabTheRightIcon(icon, variant),
options,
variant
);
if (src) {
// recycle the match string replacing the emoji
// with its image counter part
ret = '<img '.concat(
'class="', options.className, '" ',
'draggable="false" ',
// needs to preserve user original intent
// when variants should be copied and pasted too
'alt="',
match,
'"',
' src="',
src,
'"'
);
attrib = options.attributes(icon, variant);
for (attrname in attrib) {
if (
attrib.hasOwnProperty(attrname) &&
// don't allow any handlers to be set + don't allow overrides
attrname.indexOf('on') !== 0 &&
ret.indexOf(' ' + attrname + '=') === -1
) {
ret = ret.concat(' ', attrname, '="', escapeHTML(attrib[attrname]), '"');
}
}
ret = ret.concat('>');
}
}
return ret;
});
}
/**
* Function used to actually replace HTML special chars
* @param string HTML special char
* @return string encoded HTML special char
*/
function replacer(m) {
return escaper[m];
}
/**
* Default options.attribute callback
* @return null
*/
function returnNull() {
return null;
}
/**
* Given a generic value, creates its squared counterpart if it's a number.
* As example, number 36 will return '36x36'.
* @param any a generic value.
* @return any a string representing asset size, i.e. "36x36"
* only in case the value was a number.
* Returns initial value otherwise.
*/
function toSizeSquaredAsset(value) {
return typeof value === 'number' ?
value + 'x' + value :
value;
}
/////////////////////////
// exported functions //
// declaration //
/////////////////////////
function fromCodePoint(codepoint) {
var code = typeof codepoint === 'string' ?
parseInt(codepoint, 16) : codepoint;
if (code < 0x10000) {
return fromCharCode(code);
}
code -= 0x10000;
return fromCharCode(
0xD800 + (code >> 10),
0xDC00 + (code & 0x3FF)
);
}
function parse(what, how) {
if (!how || typeof how === 'function') {
how = {callback: how};
}
// if first argument is string, inject html <img> tags
// otherwise use the DOM tree and parse text nodes only
return (typeof what === 'string' ? parseString : parseNode)(what, {
callback: how.callback || defaultImageSrcGenerator,
attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
base: typeof how.base === 'string' ? how.base : twemoji.base,
ext: how.ext || twemoji.ext,
size: how.folder || toSizeSquaredAsset(how.size || twemoji.size),
className: how.className || twemoji.className,
onerror: how.onerror || twemoji.onerror
});
}
function replace(text, callback) {
return String(text).replace(re, callback);
}
function test(text) {
// IE6 needs a reset before too
re.lastIndex = 0;
var result = re.test(text);
re.lastIndex = 0;
return result;
}
function toCodePoint(unicodeSurrogates, sep) {
var
r = [],
c = 0,
p = 0,
i = 0;
while (i < unicodeSurrogates.length) {
c = unicodeSurrogates.charCodeAt(i++);
if (p) {
r.push((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).toString(16));
p = 0;
} else if (0xD800 <= c && c <= 0xDBFF) {
p = c;
} else {
r.push(c.toString(16));
}
}
return r.join(sep || '-');
}
}.toString()
// drop current indentation
.replace(/^ /gm, '')
// add the RegExp in the right place
.replace('re = /twemoji/', 're = /' + re + '/g')
.replace(/(\\u00[2-3][0-9])(\\u20e3)/g, '$1\\ufe0f?$2')
// add the full license
.replace('/*! (C) Twitter Inc. */',
'/*! (C) Twitter Inc. *//*\n' +
fs.readFileSync('LICENSE').toString().replace(
/^./gm, ' '
) +
'\n */'
) + '());');
}