UNPKG

@onesy/lz77

Version:
242 lines (241 loc) 12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const is_1 = __importDefault(require("@onesy/utils/is")); const isEnvironment_1 = __importDefault(require("@onesy/utils/isEnvironment")); const to_1 = __importDefault(require("@onesy/utils/to")); const castParam_1 = __importDefault(require("@onesy/utils/castParam")); const OnesyDate_1 = __importDefault(require("@onesy/date/OnesyDate")); const duration_1 = __importDefault(require("@onesy/date/duration")); class OnesyLZ77Response { constructor(value = '', original_byte_size, value_byte_size, compression_ratio, compression_percentage, positive, performance_milliseconds, performance) { this.value = value; this.original_byte_size = original_byte_size; this.value_byte_size = value_byte_size; this.compression_ratio = compression_ratio; this.compression_percentage = compression_percentage; this.positive = positive; this.performance_milliseconds = performance_milliseconds; this.performance = performance; } } class OnesyLZ77 { constructor(value) { this.value = value; this.response = new OnesyLZ77Response(); if (this.value !== undefined) this.init(); } static get OnesyLZ77Response() { return OnesyLZ77Response; } static decode(value) { return new OnesyLZ77().decode(value); } static prefix(value) { if (!(0, is_1.default)('string', value)) return '`'; const special = ['`', `'`, `"`, '(', ')', '|', ',', '+', '*', '-', '.', '^', ';', ':', '=', '?', '[', ']', '_']; let index = 0; let amount = 1; let prefix = ''; while (true) { prefix = special[index].repeat(amount); const regexp = new RegExp(`\\${prefix}[^\\${prefix}]{1,4},[^\\${prefix}]{1,4}\\${prefix}`); const match = value.match(regexp); if (!match) break; // Increment if (index === special.length - 1) { index = 0; amount++; } else index++; } return prefix; } get encoded() { return this.response; } init() { if (['uint8array', 'buffer', 'string'].some((item) => (0, is_1.default)(item, this.value))) { this.getVariant(); if ((0, is_1.default)('string', this.value)) { this.valueString = this.value; this.valueEncoded = new TextEncoder().encode(this.value); } else this.valueEncoded = this.value; if ((0, is_1.default)('buffer', this.value)) this.valueString = this.value.toString('utf-8'); if ((0, is_1.default)('uint8array', this.value)) this.valueString = new TextDecoder().decode(this.value); // Encode this.encode(); } } encode() { const startTime = OnesyDate_1.default.milliseconds; const prefix = OnesyLZ77.prefix(this.valueString); const windowLength = 32e4; const searchIndexes = { start: -1, end: 0 }; let search = this.valueEncoded.subarray(searchIndexes.start, searchIndexes.end); const lookaheadIndexes = { start: 0, end: this.valueEncoded.length > windowLength ? windowLength : this.valueEncoded.length }; let lookahead = this.valueEncoded.subarray(lookaheadIndexes.start, lookaheadIndexes.end); let output = []; const getItem = () => { const allFirstMatches = []; for (let i = searchIndexes.end - 1; i > -1; i--) { if (this.valueEncoded[i] === lookahead[0]) allFirstMatches.push(i); } if (!allFirstMatches.length) return [0, 0, lookaheadIndexes.start, lookaheadIndexes.start]; let matchLongest = []; allFirstMatches.sort(); for (let i = 0; i < allFirstMatches.length; i++) { const start = allFirstMatches[i]; let offset = 2; let equal = this.equal(this.valueEncoded.subarray(start, start + offset), this.valueEncoded.subarray(lookaheadIndexes.start, lookaheadIndexes.start + offset)); while (equal) { offset += 1; equal = this.equal(this.valueEncoded.subarray(start, start + offset), this.valueEncoded.subarray(lookaheadIndexes.start, lookaheadIndexes.start + offset)); } if (!matchLongest.length || (offset >= matchLongest[1])) matchLongest = [start, offset]; } return [lookaheadIndexes.start - matchLongest[0], matchLongest[1] - 1, lookaheadIndexes.start, lookaheadIndexes.start + matchLongest[1] - 1]; }; while (lookahead.length) { const [start, offset, lookaheadStartIndex, lookaheadEndIndex] = getItem(); const diff = Math.abs(start - offset); let value = ''; const valueToAppend = this.valueEncoded[lookaheadEndIndex]; const actualValue = this.valueEncoded.subarray(lookaheadStartIndex - start, lookaheadStartIndex - start + offset); if (!(start === 0 && offset === 0)) value = `${prefix}${start === offset ? this.valueEncode(start) : `${this.valueEncode(start)},${this.valueEncode(offset)}`}${prefix}`; output.push(...((0, to_1.default)(actualValue.reduce((result, item) => result += String.fromCharCode(item), ''), 'byte-size') <= (0, to_1.default)(value, 'byte-size') ? actualValue : [value]), valueToAppend); const move = lookaheadEndIndex - lookaheadStartIndex + diff; searchIndexes.start = search.length + lookahead.length >= windowLength ? searchIndexes.start + move : 0; searchIndexes.end = lookaheadEndIndex + 1; lookaheadIndexes.start = searchIndexes.end; lookaheadIndexes.end += move; search = this.valueEncoded.subarray(searchIndexes.start, searchIndexes.end); lookahead = this.valueEncoded.subarray(lookaheadIndexes.start, lookaheadIndexes.end); } output = output.map((item) => (0, is_1.default)('number', item) ? String.fromCharCode(item) : item).join(''); output = ` ${output}`; const variantCodeValue = this.variantToValue(); if (variantCodeValue > 0 || prefix !== '`') { if (prefix !== '`') output = `${prefix}${output}`; if (variantCodeValue > 0) output = `${variantCodeValue}${output}`; } const response = new OnesyLZ77Response(output); response.performance_milliseconds = OnesyDate_1.default.milliseconds - startTime; response.performance = (0, duration_1.default)(response.performance_milliseconds) || '0 milliseconds'; response.original_byte_size = (0, to_1.default)(this.valueEncoded, 'byte-size'); response.value_byte_size = (0, to_1.default)(response.value, 'byte-size'); response.compression_ratio = Number((((response.value_byte_size + response.original_byte_size) / response.value_byte_size) - 1).toFixed(2)); response.compression_percentage = response.original_byte_size === 0 ? response.value_byte_size === 0 ? 0 : response.value_byte_size * -100 : Number(((((response.original_byte_size - response.value_byte_size) / response.original_byte_size)) * 100).toFixed(2)); response.positive = response.compression_ratio > 1; this.response = response; return response; } decode(value_) { const response = new OnesyLZ77Response(); const startTime = OnesyDate_1.default.milliseconds; if ((0, is_1.default)('string', value_)) { let meta = ''; let value = value_; const indexSeparator = value_.indexOf(' '); meta = value_.substring(0, indexSeparator); value = value_.substring(indexSeparator + 1); let variant = '0'; let prefix = '`'; if (['1', '2'].indexOf(meta[0]) > -1) { variant = meta[0]; meta = meta.substring(1); } if (meta.length) prefix = meta; const regexp = new RegExp(`\\${prefix}[^\\${prefix}]{1,4}(,[^\\${prefix}]{1,4})?\\${prefix}`, 'g'); const abbrs = value.match(regexp) || []; for (const abbr of abbrs) { let valueAbbr = abbr; if (!valueAbbr.includes(',')) valueAbbr = `${prefix}${valueAbbr.replace(new RegExp(`${prefix}`, 'g'), '')},${valueAbbr.replace(new RegExp(`${prefix}`, 'g'), '')}${prefix}`; let [position, offset] = valueAbbr.slice(1, -1).split(','); [position, offset] = [position, offset].map(item => this.valueDecode(item)); const index = value.indexOf(abbr); const addStartIndex = index - position; const addEndIndex = addStartIndex + offset; const array = []; if (offset > position) { let left = offset; while (left > 0) { const add = value.substring(addStartIndex, left < position ? addStartIndex + left : addStartIndex + position); array.push(add); left -= position; } } else { const add = value.substring(addStartIndex, addEndIndex); array.push(add); } const itemValue = array.join(''); value = value.replace(abbr, itemValue); } value = this.valueToVariant(value, variant); // Convert value back from UTF-16 -> Utf8Array -> Text decoded // ie. back to Japanese characters if ((0, is_1.default)('string', value)) { const array = []; value.split('').forEach(item => array.push(item.charCodeAt(0))); value = new TextDecoder().decode(new Uint8Array(array)); } response.value = value; response.performance_milliseconds = OnesyDate_1.default.milliseconds - startTime; response.performance = (0, duration_1.default)(response.performance_milliseconds) || '0 milliseconds'; response.original_byte_size = (0, to_1.default)(value, 'byte-size'); response.value_byte_size = (0, to_1.default)(value_, 'byte-size'); } return response; } valueToVariant(value, variant_) { const variant = (0, castParam_1.default)(variant_); if ((0, isEnvironment_1.default)('nodejs') && variant === 1) return Buffer.from(value); if (((0, isEnvironment_1.default)('browser') && variant === 1) || variant === 2) return new TextEncoder().encode(value); return value; } variantToValue() { let value = 0; if ((0, isEnvironment_1.default)('nodejs') && this.variant === Buffer) value = 1; else if (this.variant === Uint8Array) value = 2; return value; } valueEncode(value) { return Number(value).toString(36); } valueDecode(value) { return parseInt(value, 36); } getVariant() { if ((0, isEnvironment_1.default)('nodejs') && Buffer.isBuffer(this.value)) this.variant = Buffer; else if ((0, is_1.default)('uint8array', this.value)) this.variant = Uint8Array; else this.variant = String; } equal(value, value1) { return value.join('') === value1.join(''); } } exports.default = OnesyLZ77;