2026-01-09 12:48:09 -05:00
|
|
|
/**
|
2026-01-17 15:24:43 -06:00
|
|
|
* @file JSC.cpp
|
2026-01-09 12:48:09 -05:00
|
|
|
* @brief Implementation of JSC class for compression and decompression
|
2026-01-18 12:53:55 -06:00
|
|
|
*
|
2018-10-04 13:52:52 -04:00
|
|
|
* This file is part of JS8Call.
|
2018-09-30 17:17:47 -04:00
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*
|
|
|
|
|
* (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
|
2026-01-18 12:53:55 -06:00
|
|
|
*/
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
#include "JSC.h"
|
|
|
|
|
#include "JS8_Main/Varicode.h"
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2018-10-15 03:03:26 -04:00
|
|
|
#include <QCache>
|
2026-01-17 15:24:43 -06:00
|
|
|
#include <QDebug>
|
2018-10-15 03:03:26 -04:00
|
|
|
|
2026-01-18 12:53:55 -06:00
|
|
|
#include <cmath>
|
|
|
|
|
|
2018-10-15 03:03:26 -04:00
|
|
|
QMap<QString, quint32> LOOKUP_CACHE;
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Generates a codeword for the given index and parameters.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param index
|
|
|
|
|
* @param separate
|
|
|
|
|
* @param bytesize
|
|
|
|
|
* @param s
|
|
|
|
|
* @param c
|
|
|
|
|
* @return Codeword
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
Codeword JSC::codeword(quint32 index, bool separate, quint32 bytesize,
|
|
|
|
|
quint32 s, quint32 c) {
|
2018-09-30 17:17:47 -04:00
|
|
|
QList<Codeword> out;
|
|
|
|
|
|
2018-10-01 09:57:37 -04:00
|
|
|
quint32 v = ((index % s) << 1) + (quint32)separate;
|
2018-09-30 17:17:47 -04:00
|
|
|
out.prepend(Varicode::intToBits(v, bytesize + 1));
|
|
|
|
|
|
|
|
|
|
quint32 x = index / s;
|
2026-01-17 15:24:43 -06:00
|
|
|
while (x > 0) {
|
2018-09-30 17:17:47 -04:00
|
|
|
x -= 1;
|
|
|
|
|
out.prepend(Varicode::intToBits((x % c) + s, bytesize));
|
|
|
|
|
x /= c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Codeword word;
|
2026-01-17 15:24:43 -06:00
|
|
|
foreach (auto w, out) {
|
2018-09-30 17:17:47 -04:00
|
|
|
word.append(w);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return word;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Compresses the given text into a list of codeword pairs.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param text
|
|
|
|
|
* @return QList<CodewordPair>
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
QList<CodewordPair> JSC::compress(QString text) {
|
2018-09-30 17:17:47 -04:00
|
|
|
QList<CodewordPair> out;
|
|
|
|
|
|
|
|
|
|
const quint32 b = 4;
|
|
|
|
|
const quint32 s = 7;
|
|
|
|
|
const quint32 c = pow(2, 4) - s;
|
|
|
|
|
|
2018-10-08 15:36:09 -04:00
|
|
|
QString space(" ");
|
|
|
|
|
|
2024-08-31 09:40:28 -07:00
|
|
|
QStringList words = text.split(" ", Qt::KeepEmptyParts);
|
2019-10-03 20:26:55 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
for (int i = 0, len = words.length(); i < len; i++) {
|
2019-10-03 20:26:55 -04:00
|
|
|
QString w = words[i];
|
2018-10-06 01:43:47 -04:00
|
|
|
|
2019-10-03 20:26:55 -04:00
|
|
|
bool isLastWord = (i == len - 1);
|
|
|
|
|
bool ok = false;
|
2018-10-08 15:36:09 -04:00
|
|
|
bool isSpaceCharacter = false;
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
// if this is an empty part, it should be a space, unless its the last
|
|
|
|
|
// word.
|
|
|
|
|
if (w.isEmpty() && !isLastWord) {
|
2018-10-08 15:36:09 -04:00
|
|
|
w = space;
|
|
|
|
|
isSpaceCharacter = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
while (!w.isEmpty()) {
|
2018-10-06 01:43:47 -04:00
|
|
|
// this does both prefix and full match lookup
|
|
|
|
|
auto index = lookup(w, &ok);
|
2026-01-17 15:24:43 -06:00
|
|
|
if (!ok) {
|
2018-10-06 01:43:47 -04:00
|
|
|
break;
|
2018-09-30 17:17:47 -04:00
|
|
|
}
|
2018-10-06 01:43:47 -04:00
|
|
|
|
|
|
|
|
auto t = JSC::map[index];
|
2019-10-03 20:26:55 -04:00
|
|
|
|
2018-10-09 14:12:00 -04:00
|
|
|
w = QString(w).mid(t.size);
|
2018-10-06 01:43:47 -04:00
|
|
|
|
|
|
|
|
bool isLast = w.isEmpty();
|
2019-10-03 20:26:55 -04:00
|
|
|
bool shouldAppendSpace = isLast && !isSpaceCharacter && !isLastWord;
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
out.append(
|
|
|
|
|
{codeword(index, shouldAppendSpace, b, s, c),
|
|
|
|
|
(quint32)t.size + (shouldAppendSpace
|
|
|
|
|
? 1
|
|
|
|
|
: 0) /* for the space that follows */});
|
2018-09-30 17:17:47 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Decompresses the given bit vector into a string.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param bitvec
|
|
|
|
|
* @return QString
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
QString JSC::decompress(Codeword const &bitvec) {
|
2018-09-30 17:17:47 -04:00
|
|
|
const quint32 b = 4;
|
|
|
|
|
const quint32 s = 7;
|
|
|
|
|
const quint32 c = pow(2, b) - s;
|
|
|
|
|
|
|
|
|
|
QStringList out;
|
|
|
|
|
|
|
|
|
|
quint32 base[8];
|
|
|
|
|
base[0] = 0;
|
|
|
|
|
base[1] = s;
|
2026-01-17 15:24:43 -06:00
|
|
|
base[2] = base[1] + s * c;
|
|
|
|
|
base[3] = base[2] + s * c * c;
|
|
|
|
|
base[4] = base[3] + s * c * c * c;
|
|
|
|
|
base[5] = base[4] + s * c * c * c * c;
|
|
|
|
|
base[6] = base[5] + s * c * c * c * c * c;
|
|
|
|
|
base[7] = base[6] + s * c * c * c * c * c * c;
|
2018-09-30 17:17:47 -04:00
|
|
|
|
|
|
|
|
QList<quint64> bytes;
|
2019-11-25 20:13:00 -05:00
|
|
|
QList<quint32> separators;
|
2018-11-03 01:14:31 -04:00
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
int count = bitvec.count();
|
2026-01-17 15:24:43 -06:00
|
|
|
while (i < count) {
|
2018-11-03 01:14:31 -04:00
|
|
|
auto b = bitvec.mid(i, 4);
|
2026-01-17 15:24:43 -06:00
|
|
|
if (b.length() != 4) {
|
2018-11-03 01:14:31 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
quint64 byte = Varicode::bitsToInt(b);
|
2018-09-30 17:17:47 -04:00
|
|
|
bytes.append(byte);
|
2018-11-03 01:14:31 -04:00
|
|
|
i += 4;
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
if (byte < s) {
|
|
|
|
|
if (count - i > 0 && bitvec.at(i)) {
|
|
|
|
|
separators.append(bytes.length() - 1);
|
2018-09-30 17:17:47 -04:00
|
|
|
}
|
2018-11-03 01:14:31 -04:00
|
|
|
i += 1;
|
2018-09-30 17:17:47 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-25 20:13:00 -05:00
|
|
|
quint32 start = 0;
|
2026-01-17 15:24:43 -06:00
|
|
|
while (start < (quint32)bytes.length()) {
|
2019-11-25 20:13:00 -05:00
|
|
|
quint32 k = 0;
|
|
|
|
|
quint32 j = 0;
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
while (start + k < (quint32)bytes.length() && bytes[start + k] >= s) {
|
|
|
|
|
j = j * c + (bytes[start + k] - s);
|
2018-09-30 17:17:47 -04:00
|
|
|
k++;
|
|
|
|
|
}
|
2026-01-17 15:24:43 -06:00
|
|
|
if (j >= JSC::size) {
|
2019-11-25 20:13:00 -05:00
|
|
|
break;
|
|
|
|
|
}
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
if (start + k >= (quint32)bytes.length()) {
|
2018-11-03 22:14:42 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2026-01-17 15:24:43 -06:00
|
|
|
j = j * s + bytes[start + k] + base[k];
|
2018-09-30 17:17:47 -04:00
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
if (j >= JSC::size) {
|
2018-11-03 22:14:42 -04:00
|
|
|
break;
|
|
|
|
|
}
|
2019-10-03 20:26:55 -04:00
|
|
|
|
2019-11-22 01:34:57 -05:00
|
|
|
// map is in latin1 format, not utf-8
|
|
|
|
|
auto word = QLatin1String(JSC::map[j].str);
|
2018-10-09 14:12:00 -04:00
|
|
|
|
|
|
|
|
out.append(word);
|
2026-01-17 15:24:43 -06:00
|
|
|
if (!separators.isEmpty() && separators.first() == start + k) {
|
2018-09-30 17:17:47 -04:00
|
|
|
out.append(" ");
|
|
|
|
|
separators.removeFirst();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start = start + (k + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out.join("");
|
|
|
|
|
}
|
2018-10-01 09:57:37 -04:00
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Checks if the given word exists in the compression map.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param w
|
|
|
|
|
* @param pIndex
|
|
|
|
|
* @return true
|
|
|
|
|
* @return false
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
bool JSC::exists(QString w, quint32 *pIndex) {
|
2018-12-30 20:18:35 -05:00
|
|
|
bool found = false;
|
|
|
|
|
quint32 index = lookup(w, &found);
|
2026-01-17 15:24:43 -06:00
|
|
|
if (pIndex)
|
|
|
|
|
*pIndex = index;
|
2018-12-30 20:18:35 -05:00
|
|
|
return found && JSC::map[index].size == w.length();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Looks up the index of the given word in the compression map.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param w
|
|
|
|
|
* @param ok
|
|
|
|
|
* @return quint32
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
quint32 JSC::lookup(QString w, bool *ok) {
|
|
|
|
|
if (LOOKUP_CACHE.contains(w)) {
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = true;
|
2018-10-15 03:03:26 -04:00
|
|
|
return LOOKUP_CACHE[w];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
|
quint32 result = lookup(w.toLatin1().data(), &found);
|
2026-01-17 15:24:43 -06:00
|
|
|
if (found) {
|
2018-10-15 03:03:26 -04:00
|
|
|
LOOKUP_CACHE[w] = result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
if (ok)
|
|
|
|
|
*ok = found;
|
2018-10-15 03:03:26 -04:00
|
|
|
return result;
|
2018-10-01 09:57:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 12:48:09 -05:00
|
|
|
/**
|
|
|
|
|
* @brief Looks up the index of the given C-style string in the compression map.
|
2026-01-17 15:24:43 -06:00
|
|
|
*
|
|
|
|
|
* @param b
|
|
|
|
|
* @param ok
|
|
|
|
|
* @return quint32
|
2026-01-09 12:48:09 -05:00
|
|
|
*/
|
2026-01-17 15:24:43 -06:00
|
|
|
quint32 JSC::lookup(char const *b, bool *ok) {
|
2018-10-06 01:43:47 -04:00
|
|
|
quint32 index = 0;
|
|
|
|
|
quint32 count = 0;
|
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
|
|
// first find prefix match to jump into the list faster
|
2026-01-17 15:24:43 -06:00
|
|
|
for (quint32 i = 0; i < JSC::prefixSize; i++) {
|
2018-10-06 01:43:47 -04:00
|
|
|
// skip obvious non-prefixes...
|
2026-01-17 15:24:43 -06:00
|
|
|
if (b[0] != JSC::prefix[i].str[0]) {
|
2018-10-06 01:43:47 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ok, we found one... let's end early for single char strings.
|
2026-01-17 15:24:43 -06:00
|
|
|
if (JSC::prefix[i].size == 1) {
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = true;
|
2018-10-06 01:43:47 -04:00
|
|
|
return JSC::list[JSC::prefix[i].index].index;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
// otherwise, keep track of the first index in the list and the number
|
|
|
|
|
// of elements
|
2018-10-06 01:43:47 -04:00
|
|
|
index = JSC::prefix[i].index;
|
|
|
|
|
count = JSC::prefix[i].size;
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no prefix found... no lookup
|
2026-01-17 15:24:43 -06:00
|
|
|
if (!found) {
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = false;
|
2018-10-06 01:43:47 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
// now that we have the first index in the list, let's just iterate through
|
|
|
|
|
// the list, comparing words along the way
|
|
|
|
|
for (quint32 i = index; i < index + count; i++) {
|
2018-10-06 01:43:47 -04:00
|
|
|
quint32 len = JSC::list[i].size;
|
2026-01-17 15:24:43 -06:00
|
|
|
if (strncmp(b, JSC::list[i].str, len) == 0) {
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = true;
|
2018-10-06 01:43:47 -04:00
|
|
|
return JSC::list[i].index;
|
2018-10-01 09:57:37 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 15:24:43 -06:00
|
|
|
if (ok)
|
|
|
|
|
*ok = false;
|
2018-10-01 09:57:37 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|