PageSourceSearch

https://crccheck.github.io/text-mix.js/src/text-mix.js

js crccheck.github.io collected 2026-10-03 08:50:14 UTC 8,665 bytes, 276 lines download raw bytes

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.