You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
6.0 KiB
230 lines
6.0 KiB
/*************************************************************************** |
|
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> * |
|
* * |
|
* RLE decompressor based on FBReader * |
|
* Copyright (C) 2004-2008 Geometer Plus <contact@geometerplus.com> * |
|
* * |
|
* 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 2 of the License, or * |
|
* (at your option) any later version. * |
|
***************************************************************************/ |
|
|
|
#include <mobipocket.h> |
|
#include <QtCore/QIODevice> |
|
#include <QtCore/QtEndian> |
|
#include <QtCore/QBuffer> |
|
#include <kdebug.h> |
|
|
|
static unsigned char TOKEN_CODE[256] = { |
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
|
}; |
|
|
|
namespace Mobipocket { |
|
|
|
class NOOPDecompressor : public Decompressor |
|
{ |
|
public: |
|
QByteArray decompress(const QByteArray& data) { return data; } |
|
}; |
|
|
|
|
|
class RLEDecompressor : public Decompressor |
|
{ |
|
public: |
|
QByteArray decompress(const QByteArray& data); |
|
}; |
|
|
|
QByteArray RLEDecompressor::decompress(const QByteArray& data) |
|
{ |
|
QByteArray ret; |
|
ret.reserve(8192); |
|
|
|
unsigned char token; |
|
unsigned short copyLength, N, shift; |
|
unsigned short shifted; |
|
int i=0; |
|
int maxIndex=data.size()-1; |
|
|
|
while (i<data.size()) { |
|
token = data.at(i++); |
|
switch (TOKEN_CODE[token]) { |
|
case 0: |
|
ret.append(token); |
|
break; |
|
case 1: |
|
if ((i + token > maxIndex) ) { |
|
goto endOfLoop; |
|
} |
|
ret.append(data.mid(i,token)); |
|
i+=token; |
|
break; |
|
case 2: |
|
ret.append(' '); |
|
ret.append(token ^ 0x80); |
|
break; |
|
case 3: |
|
if (i + 1 > maxIndex) { |
|
goto endOfLoop; |
|
} |
|
// N = (token << 8) + data.at(i++); |
|
N = token; |
|
N<<=8; |
|
N+=(unsigned char)data.at(i++); |
|
copyLength = (N & 7) + 3; |
|
shift = (N & 0x3fff) / 8; |
|
shifted = ret.size()-shift; |
|
if (shifted>(ret.size()-1)) goto endOfLoop; |
|
for (int i=0;i<copyLength;i++) ret.append(ret.at(shifted+i)); |
|
break; |
|
} |
|
} |
|
endOfLoop: |
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
///////////////////////////////////////////// |
|
|
|
|
|
struct PDBPrivate { |
|
QList<quint32> recordOffsets; |
|
QIODevice* device; |
|
QString fileType; |
|
QString name; |
|
quint16 nrecords; |
|
bool valid; |
|
|
|
void init(); |
|
}; |
|
|
|
void PDBPrivate::init() |
|
{ |
|
valid=true; |
|
quint16 word; |
|
quint32 dword; |
|
device->seek(0); |
|
name=QString::fromLatin1(device->read(32)); |
|
device->seek(0x3c); |
|
fileType=QString::fromLatin1(device->read(8)); |
|
|
|
device->seek(0x4c); |
|
device->read((char*)&word,2); |
|
nrecords=qFromBigEndian(word); |
|
|
|
for (int i=0;i<nrecords;i++) { |
|
device->read((char*)&dword,4); |
|
recordOffsets.append(qFromBigEndian(dword)); |
|
device->read((char*)&dword,4); |
|
} |
|
} |
|
|
|
PDB::PDB(QIODevice* dev) : d(new PDBPrivate) |
|
{ |
|
d->device=dev; |
|
d->init(); |
|
} |
|
|
|
QByteArray PDB::getRecord(int i) const |
|
{ |
|
if (i>=d->nrecords) return QByteArray(); |
|
quint32 offset=d->recordOffsets[i]; |
|
bool last=(i==(d->nrecords-1)); |
|
quint32 size=0; |
|
if (last) size=d->device->size()-offset; |
|
else size=d->recordOffsets[i+1]-offset; |
|
d->device->seek(offset); |
|
return d->device->read(size); |
|
} |
|
|
|
QString PDB::name() const |
|
{ |
|
return d->name; |
|
} |
|
|
|
bool PDB::isValid() const |
|
{ |
|
return d->valid; |
|
} |
|
|
|
int PDB::recordCount() const |
|
{ |
|
return d->nrecords; |
|
} |
|
|
|
//////////////////////////////////////////// |
|
struct DocumentPrivate |
|
{ |
|
DocumentPrivate(QIODevice* d) : pdb(d), valid(true) {} |
|
PDB pdb; |
|
Decompressor* dec; |
|
quint16 ntextrecords; |
|
bool valid; |
|
|
|
void init() { |
|
valid=pdb.isValid(); |
|
if (!valid) return; |
|
QByteArray mhead=pdb.getRecord(0); |
|
if (mhead[0]!=(char)0) {} |
|
|
|
switch (mhead[1]) { |
|
case 1 : dec = new NOOPDecompressor(); break; |
|
case 2 : dec = new RLEDecompressor(); break; |
|
default : dec=0; {} |
|
} |
|
ntextrecords=(unsigned char)mhead[8]; |
|
ntextrecords<<=8; |
|
ntextrecords+=(unsigned char)mhead[9]; |
|
} |
|
}; |
|
|
|
Document::Document(QIODevice* dev) : d(new DocumentPrivate(dev)) |
|
{ |
|
d->init(); |
|
} |
|
|
|
QString Document::text() const |
|
{ |
|
QByteArray whole; |
|
for (int i=1;i<d->ntextrecords;i++) |
|
whole+=d->dec->decompress(d->pdb.getRecord(i)); |
|
return QString::fromUtf8(whole); |
|
} |
|
|
|
int Document::imageCount() const |
|
{ |
|
//FIXME: don't count FLIS and FCIS records |
|
return d->pdb.recordCount()-d->ntextrecords; |
|
} |
|
|
|
bool Document::isValid() const |
|
{ |
|
return d->pdb.isValid(); |
|
} |
|
|
|
QImage Document::getImage(int i) const |
|
{ |
|
QByteArray rec=d->pdb.getRecord(d->ntextrecords+i); |
|
kDebug() << "Want IMG " << i; |
|
kDebug() << rec[0] << rec[1] << rec[2] << rec[3]; |
|
//FIXME: check if i is in range |
|
return QImage::fromData(rec); |
|
} |
|
}
|
|
|