1// Uses Node, AMD or browser globals to create a module. 2// 3// https://github.com/umdjs/umd/blob/master/returnExports.js 4(function (root, factory) { 5 if (typeof define === 'function' && define.amd) { 6 // AMD. Register as an anonymous module. 7 define(['levenshtein'], factory); 8 } else if (typeof exports === 'object') { 9 // Node. Does not work with strict CommonJS, but 10 // only CommonJS-like enviroments that support module.exports, 11 // like Node. 12 module.exports = factory(require('levenshtein')); 13 } else { 14 // Browser globals (root is window) 15 root.textMix = factory(root.Levenshtein); 16 } 17}(this, function (Levenshtein) { 18 'use strict'; 19 20 var utils = { 21 // from jQuery, MIT license 22 isNumeric: function( obj ) { 23 return !isNaN( parseFloat(obj) ) && isFinite( obj ); 24 } 25 }; 26 27 var debug = function() { 28 if (false) 29 console.log.apply(console, arguments); 30 }; 31 32 33 var NOOP = 'noop', 34 SUB = 'sub', 35 INSERT = 'ins', 36 DELETION = 'del'; 37 38 // cache creation of Levenshtein matrix 39 var matrixMemory = {}, 40 cachedLevenshtein = function (text1, text2) { 41 var mmKey = text1 + ' ' + text2; 42 if (!matrixMemory[mmKey]) { 43 matrixMemory[mmKey] = (new Levenshtein(text1, text2)); 44 } 45 return matrixMemory[mmKey]; 46 }; 47 48 // An iteration step through a Levenshtein matrix (reverse backwards) 49 var next = function (matrix, startX, startY) { 50 // http://stackoverflow.com/questions/5849139/levenshtein-distance-inferring-the-edit-operations-from-the-matrix 51 // assert startY > matrix.length 52 // assert startX > matrix[0].length 53 var val = matrix[startY][startX], 54 nextX = startX, nextY = startY, operation, 55 diag = Infinity, 56 up = Infinity, 57 left = Infinity; 58 if (startY > 0) { 59 up = matrix[startY - 1][startX]; 60 } 61 if (startX > 0) { 62 left = matrix[startY][startX - 1]; 63 } 64 if (startX > 0 && startY > 0) { 65 diag = matrix[startY - 1][startX - 1]; 66 } 67 var min = Math.min(up, left, diag); 68 // console.log('val:', val, 'up:', up, 'diag:', diag, 'left:', left, 'min:', min); 69 if (diag === 0 || diag <= min) { 70 nextX = startX - 1; 71 nextY = startY - 1; 72 if (diag < val) { 73 operation = SUB; 74 } else if (diag === val) { 75 // NOOP sometimes should be SUB 76 operation = NOOP; 77 } 78 } else if (left === 0 || left <= min) { 79 operation = INSERT; 80 nextX = startX - 1; 81 } else { 82 operation = DELETION; 83 nextY = startY -1; 84 } 85 return [min, operation, nextX, nextY]; 86 }; 87 88 // Traverse a path through the Levenshtein matrix 89 function traverse (text1, text2, iterations) { 90 var lev = cachedLevenshtein(text1, text2); 91 if (lev.distance === 0) { 92 // text1 == text2 93 return text1; 94 } 95 var matrix = lev.getMatrix(), 96 startY = matrix.length - 1, 97 startX = matrix[0].length - 1, 98 out, 99 ret = text2.split(''); 100 101 debug('traverse', text1, text2, iterations); 102 debug(lev.inspect()); 103 while (iterations-- && (startX || startY)) { 104 out = next(matrix, startX, startY); 105 debug('.next', 'iterations:', iterations, 'startX:', startX, 'startY:', startY); 106 debug('..out:', out); 107 startX = out[2]; 108 startY = out[3]; 109 switch (out[1]) { 110 case NOOP: 111 ++iterations; 112 // NOOP sometimes should be SUB 113 /* falls through */ 114 case SUB: 115 ret[startY] = text1[startX]; 116 break; 117 case INSERT: 118 ret.splice(startY, 0, text1[startX]); 119 break; 120 case DELETION: 121 ret.splice(startX, 1); 122 break; 123 } 124 debug('..ret:', ret.join('')); 125 } 126 return ret.join(''); 127 } 128 129 // helper for `stringMix` 130 // @returns {char} The character between text1 and text2 at index idx 131 var _pick = function(text1, text2, idx, amount) { 132 // assert idx < Math.max(text1.length, text2.length) 133 var n_max = Math.max(text1.length, text2.length); 134 if (idx >= text1.length) { 135 return text2[idx]; 136 } else if (idx >= text2.length) { 137 return text1[idx]; 138 } 139 // left to right: 140 return (idx < amount * n_max) ? text2[idx]: text1[idx]; 141 // random method: 142 // return (Math.random() < amount) ? text2[idx]: text1[idx]; 143 }; 144 145 function reverse (s) { 146 return s.split('').reverse().join(''); 147 } 148 149 // Simple tween between two strings 150 // @param {String} text1 - The starting text 151 // @param {String} text2 - The ending text 152 // @param {Number} amount - The amount to tween, between 0.0 and 1.0 for LTR 153 // and 0.0 to -1.0 for RTL 154 function stringMix (text1, text2, amount) { 155 var newLength = text1.length + Math.floor((text2.length - text1.length) * Math.abs(amount)), 156 out = '', i; 157 if (amount < 0) { 158 var rt1 = reverse(text1); 159 var rt2 = reverse(text2); 160 for (i = 0; i < newLength; i++) { 161 out += _pick(rt1, rt2, i, -amount); 162 } 163 return reverse(out); 164 } else { 165 for (i = 0; i < newLength; i++) { 166 out += _pick(text1, text2, i, amount); 167 } 168 return out; 169 } 170 } 171 172 // Tween between two numbers 173 function numberMix (num1, num2, amount) { 174 // FIXME sig digs 175 return Math.round(num1 + (num2 - num1) * amount); 176 } 177 178 // Splits a sentence into words 179 // Demo: http://youtu.be/M7FIvfx5J10 180 var get_words = function(text) { 181 return text.split(' '); 182 // return [text]; 183 }; 184 185 // Tween text between two values 186 // @param {String} text1 - The starting text 187 // @param {String} text2 - The ending text 188 // @param {Number} amount - The amount to tween, between 0.0 and 1.0 189 // @param {Object} [options] - Any additional options 190 // @param {bool} [options.rtl] - Text is right-to-left 191 var textMix = function(text1, text2, amount, options) { 192 var words1 = get_words(text1), 193 words2 = get_words(text2), 194 n_max = Math.max(words1.length, words2.length), 195 out = [], 196 w1, w2; 197 for (var i = 0; i < n_max; i++) { 198 w1 = words1[i]; 199 w2 = words2[i]; 200 if (utils.isNumeric(w1) && utils.isNumeric(w2)) { 201 out.push(numberMix(parseFloat(w1), parseFloat(w2), amount)); 202 } else if (!w1 || !w1.length || !w2 || !w2.length) { 203 out.push(stringMix(w1 || '', w2 || '', options && options.rtl ? -amount : amount)); 204 } else { 205 var d = (cachedLevenshtein(w1, w2)).distance; 206 if (options && options.rtl) { 207 out.push(traverse(w2, w1, Math.round(amount * d))); 208 } else { 209 out.push(traverse(w1, w2, d - Math.round(amount * d))); 210 } 211 } 212 } 213 return out.join(' '); 214 }; 215 216 // Variation of `textMix` to use with `mix`. 217 var _textMix = function(tweenFuncs, amount) { 218 var out = [], tweenFunc, _amount; 219 for (var i = 0; i < tweenFuncs.length; i++) { 220 tweenFunc = tweenFuncs[i]; 221 if (tweenFunc.distance) { 222 // need to convert amount to an iteration count 223 _amount = Math.round(amount * tweenFunc.distance); 224 if (!tweenFunc.rtl) { 225 _amount = tweenFunc.distance - _amount; 226 } 227 } else { 228 _amount = amount; 229 } 230 out.push(tweenFunc.func(tweenFunc.a, tweenFunc.b, _amount)); 231 } 232 return out.join(' '); 233 }; 234 235 // A version of textMix optimized for use with the same words 236 // @param {String} text1 - The starting text 237 // @param {String} text2 - The ending text 238 // @param {Object} [options] - Any additional options 239 // @param {bool} [options.rtl] - Text is right-to-left 240 // @returns {Function} 241 function mix(text1, text2, options) { 242 var words1 = get_words(text1), 243 words2 = get_words(text2); 244 var n_max = Math.max(words1.length, words2.length); 245 var tweenFuncs = []; 246 var w1, w2; 247 for (var i = 0; i < n_max; i++) { 248 w1 = words1[i]; 249 w2 = words2[i]; 250 if (utils.isNumeric(w1) && utils.isNumeric(w2)) { 251 tweenFuncs.push({func: numberMix, a: parseFloat(w1), b: parseFloat(w2)}); 252 } else if (!w1 || !w1.length || !w2 || !w2.length) {
253 tweenFuncs.push({func: stringMix, a: w1 || '', b: w2 || ''}); 254 } else { 255 var d = (cachedLevenshtein(w1, w2)).distance; 256 if (options && options.rtl) { 257 tweenFuncs.push({func: traverse, a: w2, b: w1, distance: d, rtl: true}); 258 } else { 259 tweenFuncs.push({func: traverse, a: w1, b: w2, distance: d}); 260 } 261 } 262 } 263 return function (amount) { 264 return _textMix(tweenFuncs, amount); 265 }; 266 } 267 268 269 return { 270 traverse: traverse, 271 stringMix: stringMix, 272 numberMix: numberMix, 273 textMix: textMix, 274 mix: mix 275 }; 276}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.