Skip to content

Commit c516dba

Browse files
committed
Updates
1 parent f82cd9a commit c516dba

5 files changed

Lines changed: 180 additions & 169 deletions

File tree

package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "strc",
3-
"version": "2.0.4",
3+
"version": "2.0.5",
44
"description": "JavaScript String Compressor - lossless string compression algorithm",
55
"main": "index.js",
66
"repository": {
@@ -18,14 +18,12 @@
1818
"homepage": "https://jssc.js.org/",
1919
"files": [
2020
"index.js",
21-
"src/index.d.ts",
21+
"src",
2222
"README.md",
2323
"LICENSE",
2424
"index.min.js",
2525
"test/index.js",
26-
"dist/jssc.js",
27-
"dist/jssc.mjs",
28-
"dist/jssc.cjs"
26+
"dist"
2927
],
3028
"dependencies": {
3129
"justc": "^0.1.0"

src/freqMap.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { prefix } from "./meta";
2+
3+
export const freqMap = {
4+
ESCAPE_BYTE: 0xFF,
5+
TOP_COUNT: 254,
6+
SPLITTER: " \u200B",
7+
8+
compress(text, splitter = this.SPLITTER) {
9+
const freq = {};
10+
for (let char of text) {
11+
freq[char] = (freq[char] || 0) + 1;
12+
}
13+
14+
const topChars = Object.entries(freq)
15+
.sort((a, b) => b[1] - a[1])
16+
.slice(0, this.TOP_COUNT)
17+
.map(entry => entry[0]);
18+
19+
const charToIndex = new Map(topChars.map((char, i) => [char, i]));
20+
21+
let header = String.fromCharCode(topChars.length) + topChars.join('');
22+
23+
let bytes = [];
24+
for (let char of text) {
25+
if (charToIndex.has(char)) {
26+
/* frequent */
27+
bytes.push(charToIndex.get(char));
28+
} else {
29+
/* rare */
30+
bytes.push(this.ESCAPE_BYTE);
31+
const code = char.charCodeAt(0);
32+
bytes.push((code >> 8) & 0xFF);
33+
bytes.push(code & 0xFF);
34+
}
35+
}
36+
37+
/* to UTF16 */
38+
let compressedBody = "";
39+
for (let i = 0; i < bytes.length; i += 2) {
40+
const b1 = bytes[i];
41+
const b2 = (i + 1 < bytes.length) ? bytes[i + 1] : 0x00;
42+
compressedBody += String.fromCharCode((b1 << 8) | b2);
43+
}
44+
45+
return header + splitter + compressedBody;
46+
},
47+
48+
decompress(compressedText, splitter = this.SPLITTER) {
49+
const parts = compressedText.split(splitter);
50+
51+
if (parts.length < 2) {
52+
throw new Error(prefix+'Invalid freqMap data: splitter not found');
53+
}
54+
55+
const headerPart = parts[0];
56+
const bodyPart = parts.slice(1).join(splitter);
57+
58+
const topCount = headerPart.charCodeAt(0);
59+
const topChars = headerPart.substring(1, topCount + 1);
60+
61+
let bytes = [];
62+
for (let i = 0; i < bodyPart.length; i++) {
63+
const code = bodyPart.charCodeAt(i);
64+
bytes.push((code >> 8) & 0xFF);
65+
bytes.push(code & 0xFF);
66+
}
67+
68+
let result = "";
69+
for (let i = 0; i < bytes.length; i++) {
70+
const b = bytes[i];
71+
if (b === this.ESCAPE_BYTE) {
72+
const charCode = (bytes[i + 1] << 8) | bytes[i + 2];
73+
result += String.fromCharCode(charCode);
74+
i += 2;
75+
} else if (b < topCount) {
76+
result += topChars[b];
77+
}
78+
}
79+
return result;
80+
},
81+
82+
/**
83+
* 0 = Fail
84+
* 1 = Success
85+
* 2 = Remove last character (Success)
86+
* @param {string} text
87+
* @param {string?} splitter
88+
* @returns {number|[number, number, string, string]}
89+
*/
90+
test(text, splitter = this.SPLITTER) {
91+
try {
92+
if (text.includes(splitter)) return 0;
93+
const packed = this.compress(text, splitter);
94+
const unpacked = this.decompress(packed, splitter);
95+
if (packed.length < text.length) {
96+
if (unpacked == text) return [1, packed.length, splitter, packed];
97+
else if (unpacked.slice(0,-1) == text) return [2, packed.length, splitter, packed];
98+
else return 0;
99+
}
100+
return 0;
101+
} catch (_) {
102+
return 0;
103+
}
104+
}
105+
};
106+
107+
export const freqMapSplitters = [
108+
" \u200B","\u0000",
109+
"\u001F", "\u0001",
110+
"\uFFFD", "\u2022",
111+
"|§|", "\uFEFF"
112+
];

src/index.js

Lines changed: 12 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,19 @@
11
import JUSTC from 'justc';
2-
3-
const name__ = 'JSSC';
4-
const prefix = name__+': ';
2+
import { name__, prefix } from './meta';
53
if ((String.fromCharCode(65536).charCodeAt(0) === 65536) || !(String.fromCharCode(256).charCodeAt(0) === 256)) {
64
throw new Error(prefix+'Supported UTF-16 only!')
75
}
86

9-
function stringCodes(str) {
10-
let output = [];
11-
let max = 0;
12-
let maxCharCode = 0;
13-
let min = Infinity;
14-
String(str).split('').forEach(char => {
15-
const code = char.charCodeAt();
16-
output.push(code);
17-
max = Math.max(max, code.toString().length);
18-
maxCharCode = Math.max(maxCharCode, code);
19-
min = Math.min(min, code.toString().length);
20-
});
21-
return {max, output, maxCharCode, min};
22-
}
23-
24-
function codesString(cds) {
25-
let output = '';
26-
cds.forEach(code => {
27-
output += String.fromCharCode(code);
28-
});
29-
return output
30-
}
31-
32-
function charCode(num) {
33-
return String.fromCharCode(num + 32);
34-
}
35-
function checkChar(cde) {
36-
return cde % 65535 === cde
37-
}
38-
39-
function stringChunks(str, num) {
40-
const output = [];
41-
for (let i = 0; i < str.length; i += num) {
42-
output.push(str.slice(i, i + num))
43-
}
44-
return output
45-
}
46-
function chunkArray(array, num) {
47-
const result = [];
48-
for (let i = 0; i < array.length; i += num) {
49-
result.push(array.slice(i, i + num));
50-
}
51-
return result;
52-
}
53-
54-
function decToBin(num, wnum) {
55-
return num.toString(2).padStart(wnum, '0');
56-
}
57-
function binToDec(str) {
58-
return parseInt(str, 2);
59-
}
7+
import {
8+
stringCodes,
9+
codesString,
10+
charCode,
11+
checkChar,
12+
stringChunks,
13+
chunkArray,
14+
decToBin,
15+
binToDec
16+
} from './utils';
6017

6118
function charsBase() {
6219
const charsBase = {};
@@ -596,116 +553,7 @@ function decompressSequences(str) {
596553
return result;
597554
}
598555

599-
const freqMap = {
600-
ESCAPE_BYTE: 0xFF,
601-
TOP_COUNT: 254,
602-
SPLITTER: " \u200B",
603-
604-
compress(text, splitter = this.SPLITTER) {
605-
const freq = {};
606-
for (let char of text) {
607-
freq[char] = (freq[char] || 0) + 1;
608-
}
609-
610-
const topChars = Object.entries(freq)
611-
.sort((a, b) => b[1] - a[1])
612-
.slice(0, this.TOP_COUNT)
613-
.map(entry => entry[0]);
614-
615-
const charToIndex = new Map(topChars.map((char, i) => [char, i]));
616-
617-
let header = String.fromCharCode(topChars.length) + topChars.join('');
618-
619-
let bytes = [];
620-
for (let char of text) {
621-
if (charToIndex.has(char)) {
622-
/* frequent */
623-
bytes.push(charToIndex.get(char));
624-
} else {
625-
/* rare */
626-
bytes.push(this.ESCAPE_BYTE);
627-
const code = char.charCodeAt(0);
628-
bytes.push((code >> 8) & 0xFF);
629-
bytes.push(code & 0xFF);
630-
}
631-
}
632-
633-
/* to UTF16 */
634-
let compressedBody = "";
635-
for (let i = 0; i < bytes.length; i += 2) {
636-
const b1 = bytes[i];
637-
const b2 = (i + 1 < bytes.length) ? bytes[i + 1] : 0x00;
638-
compressedBody += String.fromCharCode((b1 << 8) | b2);
639-
}
640-
641-
return header + splitter + compressedBody;
642-
},
643-
644-
decompress(compressedText, splitter = this.SPLITTER) {
645-
const parts = compressedText.split(splitter);
646-
647-
if (parts.length < 2) {
648-
throw new Error(prefix+'Invalid freqMap data: splitter not found');
649-
}
650-
651-
const headerPart = parts[0];
652-
const bodyPart = parts.slice(1).join(splitter);
653-
654-
const topCount = headerPart.charCodeAt(0);
655-
const topChars = headerPart.substring(1, topCount + 1);
656-
657-
let bytes = [];
658-
for (let i = 0; i < bodyPart.length; i++) {
659-
const code = bodyPart.charCodeAt(i);
660-
bytes.push((code >> 8) & 0xFF);
661-
bytes.push(code & 0xFF);
662-
}
663-
664-
let result = "";
665-
for (let i = 0; i < bytes.length; i++) {
666-
const b = bytes[i];
667-
if (b === this.ESCAPE_BYTE) {
668-
const charCode = (bytes[i + 1] << 8) | bytes[i + 2];
669-
result += String.fromCharCode(charCode);
670-
i += 2;
671-
} else if (b < topCount) {
672-
result += topChars[b];
673-
}
674-
}
675-
return result;
676-
},
677-
678-
/**
679-
* 0 = Fail
680-
* 1 = Success
681-
* 2 = Remove last character (Success)
682-
* @param {string} text
683-
* @param {string?} splitter
684-
* @returns {number|[number, number, string, string]}
685-
*/
686-
test(text, splitter = this.SPLITTER) {
687-
try {
688-
if (text.includes(splitter)) return 0;
689-
const packed = this.compress(text, splitter);
690-
const unpacked = this.decompress(packed, splitter);
691-
if (packed.length < text.length) {
692-
if (unpacked == text) return [1, packed.length, splitter, packed];
693-
else if (unpacked.slice(0,-1) == text) return [2, packed.length, splitter, packed];
694-
else return 0;
695-
}
696-
return 0;
697-
} catch (_) {
698-
return 0;
699-
}
700-
}
701-
};
702-
703-
const freqMapSplitters = [
704-
" \u200B","\u0000",
705-
"\u001F", "\u0001",
706-
"\uFFFD", "\u2022",
707-
"|§|", "\uFEFF"
708-
];
556+
import { freqMap, freqMapSplitters } from './freqMap';
709557

710558
function segments(str) {
711559
if (typeof str !== 'string' || str.length === 0) return [];

src/meta.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const name__ = 'JSSC';
2+
export const prefix = name__+': ';

src/utils.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export function stringCodes(str) {
2+
let output = [];
3+
let max = 0;
4+
let maxCharCode = 0;
5+
let min = Infinity;
6+
String(str).split('').forEach(char => {
7+
const code = char.charCodeAt();
8+
output.push(code);
9+
max = Math.max(max, code.toString().length);
10+
maxCharCode = Math.max(maxCharCode, code);
11+
min = Math.min(min, code.toString().length);
12+
});
13+
return {max, output, maxCharCode, min};
14+
}
15+
16+
export function codesString(cds) {
17+
let output = '';
18+
cds.forEach(code => {
19+
output += String.fromCharCode(code);
20+
});
21+
return output
22+
}
23+
24+
export function charCode(num) {
25+
return String.fromCharCode(num + 32);
26+
}
27+
export function checkChar(cde) {
28+
return cde % 65535 === cde
29+
}
30+
31+
export function stringChunks(str, num) {
32+
const output = [];
33+
for (let i = 0; i < str.length; i += num) {
34+
output.push(str.slice(i, i + num))
35+
}
36+
return output
37+
}
38+
export function chunkArray(array, num) {
39+
const result = [];
40+
for (let i = 0; i < array.length; i += num) {
41+
result.push(array.slice(i, i + num));
42+
}
43+
return result;
44+
}
45+
46+
export function decToBin(num, wnum) {
47+
return num.toString(2).padStart(wnum, '0');
48+
}
49+
export function binToDec(str) {
50+
return parseInt(str, 2);
51+
}

0 commit comments

Comments
 (0)