commit 605ae18b99fe55b19b854a4d54bc4fb9c7827147 Author: Jakub Stachowski Date: Sun Nov 9 18:13:04 2008 +0000 Initial import of mobipocket generator svn path=/trunk/playground/graphics/okular/mobipocket/; revision=882081 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..878961d02 --- /dev/null +++ b/CMakeLists.txt @@ -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}) diff --git a/Messages.sh b/Messages.sh new file mode 100755 index 000000000..48b9406a4 --- /dev/null +++ b/Messages.sh @@ -0,0 +1,2 @@ +#!/bin/sh +$XGETTEXT $(find . -name "*.cpp") -o $podir/okular_mobi.pot diff --git a/TODO b/TODO new file mode 100644 index 000000000..2945cb4b9 --- /dev/null +++ b/TODO @@ -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 + diff --git a/converter.cpp b/converter.cpp new file mode 100644 index 000000000..b019aafef --- /dev/null +++ b/converter.cpp @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +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; +} diff --git a/converter.h b/converter.h new file mode 100644 index 000000000..cc60e8d0c --- /dev/null +++ b/converter.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * 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 +#include + +#include "mobidocument.h" + +class QTextCursor; + +namespace Mobi { + class Converter : public Okular::TextDocumentConverter + { + public: + Converter(); + ~Converter(); + + virtual QTextDocument *convert( const QString &fileName ); + }; +} + +#endif diff --git a/generator_mobi.cpp b/generator_mobi.cpp new file mode 100644 index 000000000..950dea77e --- /dev/null +++ b/generator_mobi.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + * Copyright (C) 2008 by Ely Levy * + * * + * 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 + +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 ) +{ +} diff --git a/generator_mobi.h b/generator_mobi.h new file mode 100644 index 000000000..94b3cf3bd --- /dev/null +++ b/generator_mobi.h @@ -0,0 +1,20 @@ +/*************************************************************************** + * Copyright (C) 2008 by Ely Levy * + * * + * 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 + +class MobiGenerator : public Okular::TextDocumentGenerator +{ + public: + MobiGenerator( QObject *parent, const QVariantList &args ); + ~MobiGenerator() {} +}; + +#endif diff --git a/libokularGenerator_mobi.desktop b/libokularGenerator_mobi.desktop new file mode 100644 index 000000000..c9d97a6a5 --- /dev/null +++ b/libokularGenerator_mobi.desktop @@ -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 diff --git a/mobidocument.cpp b/mobidocument.cpp new file mode 100644 index 000000000..9b5c6b5bb --- /dev/null +++ b/mobidocument.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * 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 +#include +#include + +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,""); + ret.replace("","

"); + //FIXME: anchors + return ret; +} diff --git a/mobidocument.h b/mobidocument.h new file mode 100644 index 000000000..a6442ff5c --- /dev/null +++ b/mobidocument.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * 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 +#include +#include + +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 diff --git a/mobipocket.cpp b/mobipocket.cpp new file mode 100644 index 000000000..b8b737aaf --- /dev/null +++ b/mobipocket.cpp @@ -0,0 +1,230 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * RLE decompressor based on FBReader * + * Copyright (C) 2004-2008 Geometer Plus * + * * + * 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 +#include +#include +#include +#include + +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 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 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;iread((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;intextrecords;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); +} +} diff --git a/mobipocket.h b/mobipocket.h new file mode 100644 index 000000000..94f77a66b --- /dev/null +++ b/mobipocket.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2008 by Jakub Stachowski * + * * + * 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 +#include +#include +#include + +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 metadata() const; + QString text() const; + int imageCount() const; + QImage getImage(int i) const; + bool isValid() const; +private: + DocumentPrivate* const d; +}; +} diff --git a/okularApplication_mobi.desktop b/okularApplication_mobi.desktop new file mode 100644 index 000000000..c9840e73d --- /dev/null +++ b/okularApplication_mobi.desktop @@ -0,0 +1,54 @@ +[Desktop Entry] +MimeType=application/x-mobipocket; +Terminal=false +Name=Okular +Name[x-test]=xxOkularxx +GenericName=Document Viewer +GenericName[bg]=Визуализатор +GenericName[ca]=Visualitzador de documents +GenericName[cs]=Prohlížeč dokumentů +GenericName[da]=Dokumentfremviser +GenericName[de]=Dokumentenbetrachter +GenericName[el]=Προβολέας εγγράφων +GenericName[eo]=Dokumenta rigardilo +GenericName[es]=Visor de documentos +GenericName[et]=Dokumendinäitaja +GenericName[fa]=مشاهده‌گر سند +GenericName[fi]=Asiakirjan näyttöohjelma +GenericName[fr]=Visionneuse de document +GenericName[ga]=Amharcán Cáipéisí +GenericName[gl]=Visor de documentos +GenericName[hi]=दस्तावेज़ प्रदर्शक +GenericName[hu]=Dokumentumnézegető +GenericName[is]=Skjalaskoðari +GenericName[it]=Visore di documenti +GenericName[ja]=文書ビューア +GenericName[kk]=Құжатты қарау +GenericName[km]=កម្មវិធី​មើល​ឯកសារ +GenericName[ko]=문서 뷰어 +GenericName[lv]=Dokumentu skatītājs +GenericName[nb]=Dokumentviser +GenericName[nds]=Dokmentkieker +GenericName[ne]=कागजात दर्शक +GenericName[nl]=Documentenviewer +GenericName[nn]=Dokumentvisar +GenericName[oc]=Visualizaire de documents +GenericName[pa]=ਡੌਕੂਮੈਂਟ ਦਰਸ਼ਕ +GenericName[pl]=Przeglądarka dokumentów +GenericName[pt]=Visualizador de Documentos +GenericName[pt_BR]=Visualizador de documentos +GenericName[ru]=Просмотр документов +GenericName[sl]=Pregledovalnik dokumentov +GenericName[sv]=Dokumentvisare +GenericName[th]=เครื่องมือแสดงเอกสาร +GenericName[tr]=Belge Gösterici +GenericName[uk]=Переглядач документів +GenericName[x-test]=xxDocument Viewerxx +GenericName[zh_CN]=文档查看器 +GenericName[zh_TW]=文件檢視器 +Exec=okular %U %i -caption "%c" +Icon=okular +Type=Application +InitialPreference=1 +Categories=Qt;KDE;Graphics;Viewer; +NoDisplay=true diff --git a/okularMobi.desktop b/okularMobi.desktop new file mode 100644 index 000000000..3d772eff6 --- /dev/null +++ b/okularMobi.desktop @@ -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; diff --git a/x-mobipocket.xml b/x-mobipocket.xml new file mode 100644 index 000000000..aa7316687 --- /dev/null +++ b/x-mobipocket.xml @@ -0,0 +1,8 @@ + + + + Mobipocket document + + + +