svn path=/trunk/playground/graphics/okular/mobipocket/; revision=882081remotes/origin/mobipocket
commit
605ae18b99
15 changed files with 653 additions and 0 deletions
@ -0,0 +1,41 @@ |
||||
CMAKE_MINIMUM_REQUIRED( VERSION 2.6 ) |
||||
|
||||
|
||||
project(okular-mobi-generator) |
||||
|
||||
ENABLE_TESTING() |
||||
|
||||
find_package(KDE4 REQUIRED) |
||||
include_directories(${KDE4_INCLUDES} ${QT_INCLUDES} |
||||
${CMAKE_CURRENT_SOURCE_DIR} |
||||
) |
||||
|
||||
########### next target ############### |
||||
|
||||
find_package(SharedMimeInfo REQUIRED) |
||||
|
||||
|
||||
set(okularGenerator_mobi_PART_SRCS |
||||
converter.cpp |
||||
mobipocket.cpp |
||||
mobidocument.cpp |
||||
generator_mobi.cpp |
||||
) |
||||
|
||||
kde4_add_plugin(okularGenerator_mobi ${okularGenerator_mobi_PART_SRCS}) |
||||
|
||||
target_link_libraries(okularGenerator_mobi okularcore ${mobi_LIBRARIES} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY}) |
||||
|
||||
install(TARGETS okularGenerator_mobi DESTINATION ${PLUGIN_INSTALL_DIR}) |
||||
|
||||
|
||||
########### install files ############### |
||||
|
||||
install( FILES libokularGenerator_mobi.desktop okularMobi.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) |
||||
install( FILES okularApplication_mobi.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) |
||||
|
||||
install( |
||||
FILES x-mobipocket.xml |
||||
DESTINATION ${XDG_MIME_INSTALL_DIR} |
||||
) |
||||
update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) |
||||
@ -0,0 +1,2 @@ |
||||
#!/bin/sh |
||||
$XGETTEXT $(find . -name "*.cpp") -o $podir/okular_mobi.pot |
||||
@ -0,0 +1,7 @@ |
||||
- images are broken in some files |
||||
- better error handling |
||||
- tests for Mobipocket classes |
||||
- anchors (a filepos=) |
||||
- handle files compression with Huffman encoding |
||||
- decryption for DRMed files |
||||
|
||||
@ -0,0 +1,50 @@ |
||||
/***************************************************************************
|
||||
* 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 "converter.h" |
||||
|
||||
#include <QtGui/QAbstractTextDocumentLayout> |
||||
#include <QtGui/QTextDocument> |
||||
#include <QtGui/QTextFrame> |
||||
#include <QTextDocumentFragment> |
||||
#include <QtCore/QDebug> |
||||
#include <QtCore/QFile> |
||||
#include <qmobi.h> |
||||
|
||||
#include <klocale.h> |
||||
#include <okular/core/action.h> |
||||
|
||||
using namespace Mobi; |
||||
|
||||
Converter::Converter()
|
||||
{ |
||||
|
||||
} |
||||
|
||||
Converter::~Converter() |
||||
{
|
||||
} |
||||
|
||||
|
||||
QTextDocument* Converter::convert( const QString &fileName ) |
||||
{ |
||||
MobiDocument* newDocument=new MobiDocument(fileName); |
||||
if (!newDocument->isValid()) { |
||||
emit error(i18n("Error while opening the EPub document."), -1); |
||||
delete newDocument; |
||||
return NULL; |
||||
} |
||||
newDocument->setPageSize(QSizeF(600, 800)); |
||||
|
||||
QTextFrameFormat frameFormat; |
||||
frameFormat.setMargin( 20 ); |
||||
QTextFrame *rootFrame = newDocument->rootFrame(); |
||||
rootFrame->setFrameFormat( frameFormat );
|
||||
return newDocument; |
||||
} |
||||
@ -0,0 +1,30 @@ |
||||
/***************************************************************************
|
||||
* 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_CONVERTER_H |
||||
#define MOBI_CONVERTER_H |
||||
|
||||
#include <okular/core/textdocumentgenerator.h> |
||||
#include <okular/core/document.h> |
||||
|
||||
#include "mobidocument.h" |
||||
|
||||
class QTextCursor; |
||||
|
||||
namespace Mobi { |
||||
class Converter : public Okular::TextDocumentConverter |
||||
{ |
||||
public: |
||||
Converter(); |
||||
~Converter(); |
||||
|
||||
virtual QTextDocument *convert( const QString &fileName ); |
||||
}; |
||||
} |
||||
|
||||
#endif |
||||
@ -0,0 +1,37 @@ |
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Ely Levy <elylevy@cs.huji.ac.il> * |
||||
* * |
||||
* 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 "generator_mobi.h" |
||||
|
||||
#include "converter.h" |
||||
|
||||
#include <kaboutdata.h> |
||||
|
||||
static KAboutData createAboutData() |
||||
{ |
||||
KAboutData aboutData( |
||||
"okular_epub", |
||||
"okular_epub", |
||||
ki18n("Mobipocket Backend"), |
||||
"0.1", |
||||
ki18n("An mobipocket backend"), |
||||
KAboutData::License_GPL, |
||||
ki18n("© 2008 Jakub Stachowski") |
||||
); |
||||
aboutData.addAuthor(ki18n("Jakub Stachowski"), KLocalizedString(), |
||||
"qbast@go2.pl"); |
||||
|
||||
return aboutData; |
||||
} |
||||
|
||||
OKULAR_EXPORT_PLUGIN( MobiGenerator, createAboutData() ) |
||||
|
||||
MobiGenerator::MobiGenerator( QObject *parent, const QVariantList &args ) |
||||
: Okular::TextDocumentGenerator( new Mobi::Converter, parent, args ) |
||||
{ |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Ely Levy <elylevy@cs.huji.ac.il> * |
||||
* * |
||||
* 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 _OKULAR_GENERATOR_MOBI_H_ |
||||
#define _OKULAR_GENERATOR_MOBI_H_ |
||||
#include <okular/core/textdocumentgenerator.h> |
||||
|
||||
class MobiGenerator : public Okular::TextDocumentGenerator |
||||
{ |
||||
public: |
||||
MobiGenerator( QObject *parent, const QVariantList &args ); |
||||
~MobiGenerator() {} |
||||
}; |
||||
|
||||
#endif |
||||
@ -0,0 +1,10 @@ |
||||
[Desktop Entry] |
||||
Type=Service |
||||
Name=Mobipocket document |
||||
Comment=Mobipocket backend for Okular |
||||
X-KDE-ServiceTypes=okular/Generator |
||||
MimeType=application/x-mobipocket; |
||||
X-KDE-Library=okularGenerator_mobi |
||||
X-KDE-Priority=1 |
||||
X-KDE-okularAPIVersion=1 |
||||
X-KDE-okularHasInternalSettings=false |
||||
@ -0,0 +1,65 @@ |
||||
/***************************************************************************
|
||||
* 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 "mobidocument.h" |
||||
#include "qmobi.h" |
||||
#include <QFile> |
||||
#include <QRegExp> |
||||
#include <kdebug.h> |
||||
|
||||
using namespace Mobi; |
||||
|
||||
MobiDocument::MobiDocument(const QString &fileName) : QTextDocument()
|
||||
{ |
||||
file = new QFile(fileName); |
||||
file->open(QIODevice::ReadOnly); |
||||
doc = new Mobipocket::Document(file); |
||||
if (doc->isValid()) setHtml(fixMobiMarkup(doc->text())); |
||||
} |
||||
|
||||
bool MobiDocument::isValid() const |
||||
{ |
||||
return doc->isValid();
|
||||
} |
||||
|
||||
MobiDocument::~MobiDocument()
|
||||
{ |
||||
delete doc; |
||||
delete file; |
||||
} |
||||
|
||||
QVariant MobiDocument::loadResource(int type, const QUrl &name)
|
||||
{ |
||||
|
||||
kDebug() << "Requested resource: " << type << " URL " << name; |
||||
|
||||
if (type!=QTextDocument::ImageResource || name.scheme()!=QString("pdbrec")) return QVariant(); |
||||
bool ok; |
||||
quint16 recnum=name.path().mid(1).toUShort(&ok); |
||||
kDebug() << "Path" << name.path().mid(1) << " Img " << recnum << " all imgs " << doc->imageCount(); |
||||
if (!ok || recnum>=doc->imageCount()) return QVariant(); |
||||
|
||||
QVariant resource; |
||||
resource.setValue(doc->getImage(recnum)); |
||||
addResource(type, name, resource);
|
||||
|
||||
return resource; |
||||
} |
||||
|
||||
|
||||
QString MobiDocument::fixMobiMarkup(const QString& data)
|
||||
{ |
||||
QRegExp imgs("<[iI][mM][gG].*recindex=\"([0-9]*)\".*>"); |
||||
|
||||
imgs.setMinimal(true); |
||||
QString ret=data; |
||||
ret.replace(imgs,"<img src=\"pdbrec:/\\1\">"); |
||||
ret.replace("<mbp:pagebreak/>","<p style=\"page-break-after:always\"></p>"); |
||||
//FIXME: anchors
|
||||
return ret; |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
/***************************************************************************
|
||||
* 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_DOCUMENT_H |
||||
#define MOBI_DOCUMENT_H |
||||
|
||||
#include <QTextDocument> |
||||
#include <QUrl> |
||||
#include <QVariant> |
||||
|
||||
class QFile; |
||||
namespace Mobipocket { |
||||
class Document; |
||||
} |
||||
|
||||
namespace Mobi { |
||||
|
||||
class MobiDocument : public QTextDocument { |
||||
|
||||
public: |
||||
MobiDocument(const QString &fileName);
|
||||
bool isValid() const; |
||||
~MobiDocument();
|
||||
|
||||
protected: |
||||
virtual QVariant loadResource(int type, const QUrl &name); |
||||
|
||||
private: |
||||
QString fixMobiMarkup(const QString& data); |
||||
Mobipocket::Document *doc; |
||||
QFile* file; |
||||
}; |
||||
|
||||
} |
||||
#endif |
||||
@ -0,0 +1,230 @@ |
||||
/***************************************************************************
|
||||
* 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); |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
/***************************************************************************
|
||||
* 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 <QtCore/QString> |
||||
#include <QtCore/QByteArray> |
||||
#include <QtCore/QMap> |
||||
#include <QtGui/QImage> |
||||
|
||||
class QIODevice; |
||||
|
||||
namespace Mobipocket { |
||||
|
||||
struct PDBPrivate; |
||||
|
||||
class PDB { |
||||
public: |
||||
PDB(QIODevice* dev); |
||||
QString fileType() const; |
||||
int recordCount() const; |
||||
QByteArray getRecord(int i) const; |
||||
QString name() const; |
||||
bool isValid() const; |
||||
private: |
||||
PDBPrivate* const d; |
||||
}; |
||||
|
||||
class Decompressor { |
||||
public: |
||||
virtual QByteArray decompress(const QByteArray& data) = 0;
|
||||
virtual ~Decompressor() {} |
||||
}; |
||||
|
||||
struct DocumentPrivate; |
||||
class Document { |
||||
public: |
||||
Document(QIODevice* dev); |
||||
QMap<QString,QString> metadata() const; |
||||
QString text() const;
|
||||
int imageCount() const; |
||||
QImage getImage(int i) const; |
||||
bool isValid() const; |
||||
private: |
||||
DocumentPrivate* const d; |
||||
}; |
||||
} |
||||
@ -0,0 +1,8 @@ |
||||
[Desktop Entry] |
||||
Icon=okular |
||||
Name=Okular |
||||
Name[x-test]=xxOkularxx |
||||
X-KDE-ServiceTypes=KParts/ReadOnlyPart |
||||
X-KDE-Library=okularpart |
||||
Type=Service |
||||
MimeType=application/x-mobipocket; |
||||
@ -0,0 +1,8 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> |
||||
<mime-type type="application/x-mobipocket"> |
||||
<comment>Mobipocket document</comment> |
||||
<glob pattern="*.prc"/> |
||||
<glob pattern="*.mobi"/> |
||||
</mime-type> |
||||
</mime-info> |
||||
Loading…
Reference in new issue