Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 124feacd authored by Jakub Stachowski's avatar Jakub Stachowski

Split into directories

Convert <mbp:pagebreak/>


svn path=/trunk/playground/graphics/okular/mobipocket/okular/; revision=917031
parent f03beb39
CMAKE_MINIMUM_REQUIRED( VERSION 2.6 )
project(okular-mobi-generator)
ENABLE_TESTING()
find_package(KDE4 REQUIRED)
find_package(Strigi REQUIRED)
include_directories(${KDE4_INCLUDES} ${QT_INCLUDES}
${CMAKE_CURRENT_SOURCE_DIR}
${STRIGI_INCLUDE_DIR}
)
########### next target ###############
find_package(SharedMimeInfo REQUIRED)
set(okularGenerator_mobi_PART_SRCS
converter.cpp
mobipocket.cpp
decompressor.cpp
mobidocument.cpp
generator_mobi.cpp
)
set(mobithumbnail_SRCS
mobipocket.cpp
decompressor.cpp
mobithumbnail.cpp
)
set(mobianalyzer_SRCS
mobipocket.cpp
decompressor.cpp
mobiendanalyzer.cpp
)
kde4_add_plugin(okularGenerator_mobi ${okularGenerator_mobi_PART_SRCS})
kde4_add_plugin(mobithumbnail ${mobithumbnail_SRCS})
kde4_add_library(mobi MODULE ${mobianalyzer_SRCS})
# FIXME: remove QtGui dependency on analyzer
target_link_libraries(mobi ${STRIGI_STREAMS_LIBRARY} ${STRIGI_STREAMANALYZER_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
target_link_libraries(okularGenerator_mobi okularcore ${mobi_LIBRARIES} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY})
target_link_libraries(mobithumbnail ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ${QT_QTGUI_LIBRARY} )
install(TARGETS mobithumbnail okularGenerator_mobi DESTINATION ${PLUGIN_INSTALL_DIR})
# install the module in the right directory so it is picked up
set_target_properties(mobi PROPERTIES PREFIX ${STRIGI_LINE_ANALYZER_PREFIX})
########### install files ###############
install( FILES mobithumbnail.desktop libokularGenerator_mobi.desktop okularMobi.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
install( FILES okularApplication_mobi.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} )
install(TARGETS mobi LIBRARY DESTINATION ${LIB_INSTALL_DIR}/strigi)
install(
FILES x-mobipocket-ebook.xml
DESTINATION ${XDG_MIME_INSTALL_DIR}
)
update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR})
#!/bin/sh
$XGETTEXT $(find . -name "*.cpp") -o $podir/okular_mobi.pot
- better error handling
- tests for Mobipocket classes
- decryption for DRMed files (may be impossible and/or illegal)
......@@ -7,7 +7,7 @@
* (at your option) any later version. *
***************************************************************************/
#include "converter.h"
#include "okular/converter.h"
#include <QtGui/QAbstractTextDocumentLayout>
#include <QtGui/QTextDocument>
......@@ -16,7 +16,7 @@
#include <QtGui/QTextDocumentFragment>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <mobipocket.h>
#include <lib/mobipocket.h>
#include <klocale.h>
#include <okular/core/action.h>
......
......@@ -12,8 +12,8 @@
#include <okular/core/textdocumentgenerator.h>
#include <okular/core/document.h>
#include "mobidocument.h"
#include "mobipocket.h"
#include "okular/mobidocument.h"
#include "lib/mobipocket.h"
namespace Mobi {
......
/***************************************************************************
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> *
* *
* RLE decompressor based on FBReader *
* Copyright (C) 2004-2008 Geometer Plus <contact@geometerplus.com> *
* *
* Huffdic decompressor based on Python code by Igor Skochinsky *
* *
* 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 "decompressor.h"
#include "mobipocket.h"
#include <QtCore/QList>
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:
NOOPDecompressor(const PDB& p) : Decompressor(p) {}
QByteArray decompress(const QByteArray& data) { return data; }
};
class RLEDecompressor : public Decompressor
{
public:
RLEDecompressor(const PDB& p) : Decompressor(p) {}
QByteArray decompress(const QByteArray& data);
};
class BitReader
{
public:
BitReader(const QByteArray& d) : pos(0), data(d)
{
data.append("\000\000\000\000"); //krazy:exclude=strings
len=data.size()*8;
}
quint32 read() {
quint32 g=0;
quint64 r=0;
while (g<32) {
r=(r << 8) | (quint8)data[(pos+g)>>3];
g=g+8 - ((pos+g) & 7);
}
return (r >> (g-32));
}
bool eat(int n) {
pos+=n;
return pos <= len;
}
int left() {
return len - pos;
}
private:
int pos;
int len;
QByteArray data;
};
class HuffdicDecompressor : public Decompressor
{
public:
HuffdicDecompressor(const PDB& p);
QByteArray decompress(const QByteArray& data);
private:
void unpack(BitReader reader, int depth = 0);
QList<QByteArray> dicts;
quint32 entry_bits;
quint32 dict1[256];
quint32 dict2[64];
QByteArray buf;
};
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;
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;
}
quint32 readBELong(const QByteArray& data, int offset)
{
quint32 ret=0;
for (int i=0;i<4;i++) { ret<<=8; ret+=(unsigned char)data[offset+i]; }
return ret;
}
HuffdicDecompressor::HuffdicDecompressor(const PDB& p) : Decompressor(p)
{
QByteArray header=p.getRecord(0);
quint32 huff_ofs=readBELong(header,0x70);
quint32 huff_num=readBELong(header,0x74);
quint32 off1,off2;
QByteArray huff1=p.getRecord(huff_ofs);
if (huff1.isNull()) goto fail;
for (unsigned int i=1;i<huff_num;i++) {
QByteArray h=p.getRecord(huff_ofs+i);
if (h.isNull()) goto fail;
dicts.append(h);
}
off1=readBELong(huff1,16);
off2=readBELong(huff1,20);
if (!huff1.startsWith("HUFF")) goto fail; //krazy:exclude=strings
if (!dicts[0].startsWith("CDIC")) goto fail; //krazy:exclude=strings
entry_bits=readBELong(dicts[0],12);
memcpy(dict1,huff1.data()+off1, 256*4);
memcpy(dict2,huff1.data()+off2, 64*4);
return;
fail:
valid=false;
}
QByteArray HuffdicDecompressor::decompress(const QByteArray& data)
{
buf.clear();
unpack(BitReader(data));
return buf;
}
void HuffdicDecompressor::unpack(BitReader reader,int depth)
{
if (depth>32) goto fail;
while (reader.left()) {
quint32 dw=reader.read();
quint32 v=dict1[dw>>24];
quint8 codelen = v & 0x1F;
if (!codelen) goto fail;
quint32 code = dw >> (32 - codelen);
quint32 r=(v >> 8);
if (!( v & 0x80)) {
while (code < dict2[(codelen-1)*2]) {
codelen++;
code = dw >> (32 - codelen);
}
r = dict2[(codelen-1)*2+1];
}
r-=code;
if (!codelen) goto fail;
if (!reader.eat(codelen)) return;
quint32 dict_no = r >> entry_bits;
quint32 off1 = 16 + (r - (dict_no << entry_bits))*2;
QByteArray dict=dicts[dict_no];
quint32 off2 = 16 + (unsigned char)dict[off1]*256 + (unsigned char)dict[off1+1];
quint32 blen = (unsigned char)dict[off2]*256 + (unsigned char)dict[off2+1];
QByteArray slice=dict.mid(off2+2,(blen & 0x7fff));
if (blen & 0x8000) buf+=slice;
else unpack(BitReader(slice),depth+1);
}
return;
fail:
valid=false;
}
Decompressor* Decompressor::create(quint8 type, const PDB& pdb)
{
switch (type) {
case 1 : return new NOOPDecompressor(pdb);
case 2 : return new RLEDecompressor(pdb);
case 'H' : return new HuffdicDecompressor(pdb);
default : return 0;
}
}
}
/***************************************************************************
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> *
* *
* 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. *
***************************************************************************/
#ifndef MOBI_DECOMPRESSOR_H
#define MOBI_DECOMPRESSOR_H
#include <QtCore/QByteArray>
namespace Mobipocket {
class PDB;
class Decompressor {
public:
Decompressor(const PDB& p) : pdb(p), valid(true) {}
virtual QByteArray decompress(const QByteArray& data) = 0;
virtual ~Decompressor() {}
bool isValid() const { return valid; }
static Decompressor* create(quint8 type, const PDB& pdb);
protected:
const PDB& pdb;
bool valid;
};
quint32 readBELong(const QByteArray& data, int offset);
}
#endif
......@@ -6,9 +6,9 @@
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "mobidocument.h"
#include "mobipocket.h"
#include "qfilestream.h"
#include "okular/mobidocument.h"
#include "lib/mobipocket.h"
#include "lib/qfilestream.h"
#include <QtCore/QFile>
#include <QtCore/QRegExp>
#include <kdebug.h>
......@@ -54,12 +54,7 @@ int outsideTag(const QString& data, int pos)
QString MobiDocument::fixMobiMarkup(const QString& data)
{
static QRegExp imgs("<img.*recindex=\"([\\d]*)\".*>", Qt::CaseInsensitive);
imgs.setMinimal(true);
QString ret=data;
ret.replace(imgs,"<img src=\"pdbrec:/\\1\">");
//ret.replace("<mbp:pagebreak/>","<p style=\"page-break-after:always\"></p>");
QMap<int,QString> anchorPositions;
static QRegExp anchors("<a(?: href=\"[^\"]*\"){0,1}[\\s]+filepos=['\"]{0,1}([\\d]+)[\"']{0,1}", Qt::CaseInsensitive);
int pos=0;
......@@ -71,19 +66,27 @@ QString MobiDocument::fixMobiMarkup(const QString& data)
pos+=anchors.matchedLength();
}
// put anchors in all link destinations
// put HTML anchors in all link destinations
int offset=0;
QMapIterator<int,QString> it(anchorPositions);
while (it.hasNext()) {
it.next();
// link pointing outside the document
// link pointing outside the document, ignore
if ( (it.key()+offset) >= ret.size()) continue;
int fixedpos=outsideTag(ret, it.key()+offset);
ret.insert(fixedpos,QString("<a name=\"")+it.value()+QString("\"/>"));
offset+=12+it.value().size();
ret.insert(fixedpos,QString("<a name=\"")+it.value()+QString("\">&nbsp;</a>"));
// inserting anchor shifts all offsets after the anchor
offset+=21+it.value().size();
}
// replace links referencing filepos with normal internal links
ret.replace(anchors,"<a href=\"#\\1\"");
// Mobipocket uses strange variang of IMG tags: <img recindex="3232"> where recindex is number of
// record containing image
static QRegExp imgs("<img.*recindex=\"([\\d]*)\".*>", Qt::CaseInsensitive);
imgs.setMinimal(true);
ret.replace(imgs,"<img src=\"pdbrec:/\\1\">");
ret.replace("<mbp:pagebreak/>","<p style=\"page-break-after:always\"></p>");
return ret;
}
/***************************************************************************
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> *
* *
* 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 "mobiendanalyzer.h"
#define STRIGI_IMPORT_API
#include <strigi/strigiconfig.h>
#include <strigi/analysisresult.h>
#include <strigi/fieldtypes.h>
#include <strigi/streamendanalyzer.h>
#include <strigi/analyzerplugin.h>
#include <list>
#include "mobipocket.h"
using namespace Strigi;
class StrigiStream : public Mobipocket::Stream
{
public:
StrigiStream(InputStream* str) : d(str) {}
int read(char* buf, int len) {
const char* b2;
int l=d->read(b2, len,len);
memcpy(buf,b2,len);
return l;
}
bool seek(int pos) { d->reset(pos); return (pos==d->position()); }
private:
InputStream *d;
};
void
MobiEndAnalyzerFactory::registerFields(FieldRegister& reg) {
subjectField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#subject");
titleField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#title");
authorField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#author");
descriptionField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#description");
copyrightField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#copyright");
encryptedField = reg.registerField(
"http://freedesktop.org/standards/xesam/1.0/core#isContentEncrypted");
// typeField = reg.typeField;
//FIXME: check other fields - for example encryption
/* add the fields to the internal list of fields */
addField(subjectField);
addField(titleField);
addField(authorField);
addField(descriptionField);
addField(copyrightField);
addField(encryptedField);
// addField(typeField);
}
MobiEndAnalyzer::MobiEndAnalyzer(const MobiEndAnalyzerFactory* f) :factory(f) {
}
bool
MobiEndAnalyzer::checkHeader(const char* header, int32_t headersize) const { //krazy:exclude=typedefs
static const char magic1[] = "TEXtREAd";
static const char magic2[] = "BOOKMOBI";
return headersize >= 67 && (!memcmp(header+60, magic1, 8) || !memcmp(header+60, magic2, 8));
}
signed char
MobiEndAnalyzer::analyze(AnalysisResult& as, InputStream* in) {
StrigiStream str(in);
Mobipocket::Document doc(&str);
if (!doc.isValid()) return -1;
// as.addValue(factory->typeField, "http://freedesktop.org/standards/xesam/1.0/core#Document");
as.addValue(factory->encryptedField, doc.hasDRM());
QMapIterator<Mobipocket::Document::MetaKey,QString> it(doc.metadata());
while (it.hasNext()) {
it.next();
switch (it.key()) {
case Mobipocket::Document::Title: as.addValue(factory->titleField, it.value().toUtf8().data() ); break;
case Mobipocket::Document::Author: as.addValue(factory->authorField, it.value().toUtf8().data() ); break;
case Mobipocket::Document::Description: as.addValue(factory->descriptionField, it.value().toUtf8().data() ); break;
case Mobipocket::Document::Subject: as.addValue(factory->subjectField, it.value().toUtf8().data() ); break;
case Mobipocket::Document::Copyright: as.addValue(factory->copyrightField, it.value().toUtf8().data() ); break;
}
}
if (!doc.hasDRM()) {
QByteArray text=doc.text(20480).toUtf8();
as.addText(text.data(), text.size());
}
return 0;
}
class Factory : public AnalyzerFactoryFactory
{
public:
std::list<StreamEndAnalyzerFactory*>
streamEndAnalyzerFactories() const {
std::list<StreamEndAnalyzerFactory*> af;
af.push_back(new MobiEndAnalyzerFactory());
return af;
}
};
STRIGI_ANALYZER_FACTORY(Factory)
/***************************************************************************
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> *
* *
* 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. *
***************************************************************************/
#ifndef MOBIENDANALYZER
#define MOBIENDANALYZER
#include <strigi/streamendanalyzer.h>
#include <strigi/streambase.h>
class MobiEndAnalyzerFactory;
class MobiEndAnalyzer : public Strigi::StreamEndAnalyzer {
private:
const MobiEndAnalyzerFactory* factory;
bool checkHeader(const char* header, int32_t headersize) const; //krazy:exclude=typedefs
signed char analyze(Strigi::AnalysisResult& idx, Strigi::InputStream* in);
const char* name() const { return "MobiEndAnalyzer"; }
public:
MobiEndAnalyzer(const MobiEndAnalyzerFactory* f);
};
class MobiEndAnalyzerFactory : public Strigi::StreamEndAnalyzerFactory {
friend class MobiEndAnalyzer;
private:
const Strigi::RegisteredField* titleField;
const Strigi::RegisteredField* authorField;
const Strigi::RegisteredField* copyrightField;
const Strigi::RegisteredField* subjectField;
const Strigi::RegisteredField* descriptionField;
const Strigi::RegisteredField* encryptedField;
// const Strigi::RegisteredField* typeField;
const char* name() const {
return "MobiEndAnalyzer";
}
Strigi::StreamEndAnalyzer* newInstance() const {
return new MobiEndAnalyzer(this);
}
void registerFields(Strigi::FieldRegister&);
};
#endif
/***************************************************************************
* Copyright (C) 2008 by Jakub Stachowski <qbast@go2.pl> *
* *
* 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 "decompressor.h"
#include <QtCore/QIODevice>
#include <QtCore/QtEndian>
#include <QtCore/QBuffer>
#include <QtGui/QImageReader>
#include <kdebug.h>
namespace Mobipocket {
QByteArray Stream::read(int len)
{
QByteArray ret;
ret.resize(len);
len=read(ret.data(),len);
if (len<0) len=0;
ret.resize(len);
return ret;
}
QByteArray Stream::readAll()
{
QByteArray ret, bit;
while (!(bit=read(4096)).isEmpty()) ret+=bit;
return ret;
}
struct PDBPrivate {
QList<quint32> recordOffsets;
Stream* device;
QString fileType;
quint16 nrecords;
bool valid;
void init();
};
void PDBPrivate::init()
{
valid=true;
quint16 word;
quint32 dword;
if (!device->seek(0x3c)) goto fail;
fileType=QString::fromLatin1(device->read(8));
if (!device->seek(0x4c)) goto fail;
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);
}
return;
fail:
valid=false;
}
PDB::PDB(Stream* 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];