Add generator plugin for the FictionBook format (http://en.wikipedia.org/wiki/FictionBook),

you can find documents e.g. under http://www.fictionbook.ru

svn path=/trunk/playground/graphics/okular/; revision=620876
This commit is contained in:
Tobias Koenig 2007-01-07 16:42:24 +00:00
parent 2538e1fce0
commit 53c97a481b
10 changed files with 1428 additions and 0 deletions

View file

@ -28,3 +28,4 @@ add_subdirectory(xps)
add_subdirectory(ooo)
add_subdirectory(fictionbook)

View file

@ -0,0 +1,26 @@
include_directories(
${CMAKE_BINARY_DIR}/okular
${CMAKE_SOURCE_DIR}/okular
)
########### next target ###############
set(okularGenerator_fb_PART_SRCS
converter.cpp
document.cpp
generator_fb.cpp
)
kde4_automoc(${okularGenerator_fb_PART_SRCS})
kde4_add_plugin(okularGenerator_fb WITH_PREFIX ${okularGenerator_fb_PART_SRCS})
target_link_libraries(okularGenerator_fb ${POPPLER_LIBRARY} okularcore ${KDE4_KDEPRINT_LIBS} m )
install(TARGETS okularGenerator_fb DESTINATION ${PLUGIN_INSTALL_DIR})
########### install files ###############
install( FILES libokularGenerator_fb.desktop okularFb.desktop DESTINATION ${SERVICES_INSTALL_DIR} )

View file

@ -0,0 +1,826 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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/QStack>
#include <QtCore/QUrl>
#include <QtGui/QAbstractTextDocumentLayout>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocument>
#include <QtGui/QTextFrame>
#include <QtXml/QDomElement>
#include <QtXml/QDomText>
#include <kglobal.h>
#include <klocale.h>
#include <okular/core/document.h>
#include "converter.h"
#include "document.h"
using namespace FictionBook;
class Converter::TitleInfo
{
public:
QStringList mGenres;
QString mAuthor;
QString mTitle;
QStringList mKeywords;
QDate mDate;
QDomElement mCoverPage;
QString mLanguage;
};
class Converter::DocumentInfo
{
public:
QString mAuthor;
QString mProducer;
QDate mDate;
QString mId;
QString mVersion;
};
Converter::Converter( const Document *document )
: mDocument( document ), mTextDocument( 0 ), mCursor( 0 ),
mTitleInfo( 0 ), mDocumentInfo( 0 )
{
}
Converter::~Converter()
{
delete mTitleInfo;
delete mDocumentInfo;
}
bool Converter::convert()
{
delete mTextDocument;
delete mCursor;
mTextDocument = new QTextDocument;
mCursor = new QTextCursor( mTextDocument );
mSectionCounter = 0;
mLinkInfosGenerated = false;
const QDomDocument document = mDocument->content();
/**
* Set the correct page size
*/
mTextDocument->setPageSize( QSizeF( 600, 800 ) );
QTextFrameFormat frameFormat;
frameFormat.setMargin( 20 );
QTextFrame *rootFrame = mTextDocument->rootFrame();
rootFrame->setFrameFormat( frameFormat );
/**
* Parse the content of the document
*/
const QDomElement documentElement = document.documentElement();
if ( documentElement.tagName() != QLatin1String( "FictionBook" ) ) {
qDebug( "Not a valid FictionBook!" );
return false;
}
/**
* First we read all images, so we can calculate the size later.
*/
QDomElement element = documentElement.firstChildElement();
while ( !element.isNull() ) {
if ( element.tagName() == QLatin1String( "binary" ) ) {
if ( !convertBinary( element ) )
return false;
}
element = element.nextSiblingElement();
}
/**
* Read the rest...
*/
element = documentElement.firstChildElement();
while ( !element.isNull() ) {
if ( element.tagName() == QLatin1String( "description" ) ) {
if ( !convertDescription( element ) )
return false;
} else if ( element.tagName() == QLatin1String( "body" ) ) {
if ( !mTitleInfo->mCoverPage.isNull() ) {
convertCover( mTitleInfo->mCoverPage );
mCursor->insertBlock();
}
QTextFrame *topFrame = mCursor->currentFrame();
QTextFrameFormat frameFormat;
frameFormat.setBorder( 2 );
frameFormat.setPadding( 8 );
frameFormat.setBackground( Qt::lightGray );
if ( !mTitleInfo->mTitle.isEmpty() ) {
mCursor->insertFrame( frameFormat );
QTextCharFormat charFormat;
charFormat.setFontPointSize( 22 );
charFormat.setFontWeight( QFont::Bold );
mCursor->insertText( mTitleInfo->mTitle, charFormat );
mCursor->setPosition( topFrame->lastPosition() );
}
if ( !mTitleInfo->mAuthor.isEmpty() ) {
frameFormat.setBorder( 1 );
mCursor->insertFrame( frameFormat );
QTextCharFormat charFormat;
charFormat.setFontPointSize( 10 );
mCursor->insertText( mTitleInfo->mAuthor, charFormat );
mCursor->setPosition( topFrame->lastPosition() );
mCursor->insertBlock();
}
mCursor->insertBlock();
if ( !convertBody( element ) )
return false;
}
element = element.nextSiblingElement();
}
return true;
}
bool Converter::convertBody( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "section" ) ) {
mCursor->insertBlock();
if ( !convertSection( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "image" ) ) {
if ( !convertImage( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "title" ) ) {
if ( !convertTitle( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
if ( !convertEpigraph( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertDescription( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "title-info" ) ) {
if ( !convertTitleInfo( child ) )
return false;
} if ( child.tagName() == QLatin1String( "document-info" ) ) {
if ( !convertDocumentInfo( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertTitleInfo( const QDomElement &element )
{
delete mTitleInfo;
mTitleInfo = new TitleInfo;
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "genre" ) ) {
QString genre;
if ( !convertTextNode( child, genre ) )
return false;
if ( !genre.isEmpty() )
mTitleInfo->mGenres.append( genre );
} else if ( child.tagName() == QLatin1String( "author" ) ) {
QString firstName, middleName, lastName, dummy;
if ( !convertAuthor( child, firstName, middleName, lastName, dummy, dummy ) )
return false;
mTitleInfo->mAuthor = QString( "%1 %2 %3" ).arg( firstName, middleName, lastName );
} else if ( child.tagName() == QLatin1String( "book-title" ) ) {
if ( !convertTextNode( child, mTitleInfo->mTitle ) )
return false;
} else if ( child.tagName() == QLatin1String( "keywords" ) ) {
QString keywords;
if ( !convertTextNode( child, keywords ) )
return false;
mTitleInfo->mKeywords = keywords.split( ' ', QString::SkipEmptyParts );
} else if ( child.tagName() == QLatin1String( "date" ) ) {
if ( !convertDate( child, mTitleInfo->mDate ) )
return false;
} else if ( child.tagName() == QLatin1String( "coverpage" ) ) {
mTitleInfo->mCoverPage = child;
} else if ( child.tagName() == QLatin1String( "lang" ) ) {
if ( !convertTextNode( child, mTitleInfo->mLanguage ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertDocumentInfo( const QDomElement &element )
{
delete mDocumentInfo;
mDocumentInfo = new DocumentInfo;
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "author" ) ) {
QString firstName, middleName, lastName, email, nickname;
if ( !convertAuthor( child, firstName, middleName, lastName, email, nickname ) )
return false;
mDocumentInfo->mAuthor = QString( "%1 %2 %3 <%4> (%5)" )
.arg( firstName ).arg( middleName ).arg( lastName )
.arg( email ).arg( nickname );
} else if ( child.tagName() == QLatin1String( "program-used" ) ) {
if ( !convertTextNode( child, mDocumentInfo->mProducer ) )
return false;
} else if ( child.tagName() == QLatin1String( "date" ) ) {
if ( !convertDate( child, mDocumentInfo->mDate ) )
return false;
} else if ( child.tagName() == QLatin1String( "id" ) ) {
if ( !convertTextNode( child, mDocumentInfo->mId ) )
return false;
} else if ( child.tagName() == QLatin1String( "version" ) ) {
if ( !convertTextNode( child, mDocumentInfo->mVersion ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertAuthor( const QDomElement &element, QString &firstName, QString &middleName, QString &lastName,
QString &email, QString &nickname )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "first-name" ) ) {
if ( !convertTextNode( child, firstName ) )
return false;
} else if ( child.tagName() == QLatin1String( "middle-name" ) ) {
if ( !convertTextNode( child, middleName ) )
return false;
} else if ( child.tagName() == QLatin1String( "last-name" ) ) {
if ( !convertTextNode( child, lastName ) )
return false;
} else if ( child.tagName() == QLatin1String( "email" ) ) {
if ( !convertTextNode( child, email ) )
return false;
} else if ( child.tagName() == QLatin1String( "nickname" ) ) {
if ( !convertTextNode( child, nickname ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertTextNode( const QDomElement &element, QString &data )
{
QDomNode child = element.firstChild();
while ( !child.isNull() ) {
QDomText text = child.toText();
if ( !text.isNull() )
data = text.data();
child = child.nextSibling();
}
return true;
}
bool Converter::convertDate( const QDomElement &element, QDate &date )
{
if ( element.hasAttribute( "value" ) )
date = QDate::fromString( element.attribute( "value" ), Qt::ISODate );
return true;
}
bool Converter::convertSection( const QDomElement &element )
{
mSectionCounter++;
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "title" ) ) {
if ( !convertTitle( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
if ( !convertEpigraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "image" ) ) {
if ( !convertImage( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "section" ) ) {
if ( !convertSection( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "p" ) ) {
QTextBlockFormat format;
format.setTextIndent( 10 );
mCursor->insertBlock( format );
if ( !convertParagraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "poem" ) ) {
if ( !convertPoem( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "subtitle" ) ) {
if ( !convertSubTitle( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "cite" ) ) {
if ( !convertCite( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
if ( !convertEmptyLine( child ) )
return false;
}
child = child.nextSiblingElement();
}
mSectionCounter--;
return true;
}
bool Converter::convertTitle( const QDomElement &element )
{
QTextFrame *topFrame = mCursor->currentFrame();
QTextFrameFormat frameFormat;
frameFormat.setBorder( 1 );
frameFormat.setPadding( 8 );
frameFormat.setBackground( Qt::lightGray );
mCursor->insertFrame( frameFormat );
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "p" ) ) {
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat titleFormat( origFormat );
titleFormat.setFontPointSize( 22 - ( mSectionCounter*2 ) );
titleFormat.setFontWeight( QFont::Bold );
mCursor->setCharFormat( titleFormat );
if ( !convertParagraph( child ) )
return false;
mCursor->setCharFormat( origFormat );
HeaderInfo headerInfo;
headerInfo.block = mCursor->block();
headerInfo.text = child.text();
headerInfo.level = mSectionCounter;
mHeaderInfos.append( headerInfo );
} else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
if ( !convertEmptyLine( child ) )
return false;
}
child = child.nextSiblingElement();
}
mCursor->setPosition( topFrame->lastPosition() );
return true;
}
bool Converter::convertParagraph( const QDomElement &element )
{
QDomNode child = element.firstChild();
while ( !child.isNull() ) {
if ( child.isElement() ) {
const QDomElement childElement = child.toElement();
if ( childElement.tagName() == QLatin1String( "emphasis" ) ) {
if ( !convertEmphasis( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "strong" ) ) {
if ( !convertStrong( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "style" ) ) {
if ( !convertStyle( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "a" ) ) {
if ( !convertLink( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "image" ) ) {
if ( !convertImage( childElement ) )
return false;
}
} else if ( child.isText() ) {
const QDomText childText = child.toText();
mCursor->insertText( childText.data() );
}
child = child.nextSibling();
}
return true;
}
bool Converter::convertEmphasis( const QDomElement &element )
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat italicFormat( origFormat );
italicFormat.setFontItalic( true );
mCursor->setCharFormat( italicFormat );
if ( !convertParagraph( element ) )
return false;
mCursor->setCharFormat( origFormat );
return true;
}
bool Converter::convertStrong( const QDomElement &element )
{
QTextCharFormat origFormat = mCursor->charFormat();
QTextCharFormat boldFormat( origFormat );
boldFormat.setFontWeight( QFont::Bold );
mCursor->setCharFormat( boldFormat );
if ( !convertParagraph( element ) )
return false;
mCursor->setCharFormat( origFormat );
return true;
}
bool Converter::convertStyle( const QDomElement &element )
{
if ( !convertParagraph( element ) )
return false;
return true;
}
bool Converter::convertBinary( const QDomElement &element )
{
const QString id = element.attribute( "id" );
const QDomText textNode = element.firstChild().toText();
QByteArray data = textNode.data().toLatin1();
data = QByteArray::fromBase64( data );
mTextDocument->addResource( QTextDocument::ImageResource, QUrl( id ), QImage::fromData( data ) );
return true;
}
bool Converter::convertCover( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "image" ) ) {
if ( !convertImage( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertImage( const QDomElement &element )
{
QString href = element.attributeNS( "http://www.w3.org/1999/xlink", "href" );
if ( href.startsWith( '#' ) )
href = href.mid( 1 );
const QImage img = qVariantValue<QImage>( mTextDocument->resource( QTextDocument::ImageResource, QUrl( href ) ) );
QTextImageFormat format;
format.setName( href );
if ( img.width() > 560 )
format.setWidth( 560 );
format.setHeight( img.height() );
mCursor->insertImage( format );
return true;
}
bool Converter::convertEpigraph( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "p" ) ) {
QTextBlockFormat format;
format.setTextIndent( 10 );
mCursor->insertBlock( format );
if ( !convertParagraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "poem" ) ) {
if ( !convertPoem( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "cite" ) ) {
if ( !convertCite( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
if ( !convertEmptyLine( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertPoem( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "title" ) ) {
if ( !convertTitle( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
if ( !convertEpigraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
if ( !convertEmptyLine( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertSubTitle( const QDomElement &element )
{
return true;
}
bool Converter::convertCite( const QDomElement &element )
{
QDomElement child = element.firstChildElement();
while ( !child.isNull() ) {
if ( child.tagName() == QLatin1String( "p" ) ) {
QTextBlockFormat format;
format.setTextIndent( 10 );
mCursor->insertBlock( format );
if ( !convertParagraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "poem" ) ) {
if ( !convertParagraph( child ) )
return false;
} else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
if ( !convertEmptyLine( child ) )
return false;
}
child = child.nextSiblingElement();
}
return true;
}
bool Converter::convertEmptyLine( const QDomElement &element )
{
mCursor->insertText( "\n\n" );
return true;
}
bool Converter::convertLink( const QDomElement &element )
{
QString href = element.attributeNS( "http://www.w3.org/1999/xlink", "href" );
QString type = element.attributeNS( "http://www.w3.org/1999/xlink", "type" );
if ( type == "note" )
mCursor->insertText( "[" );
LinkPosition pos;
pos.startPosition = mCursor->position();
QTextCharFormat origFormat( mCursor->charFormat() );
QTextCharFormat format( mCursor->charFormat() );
format.setForeground( Qt::blue );
format.setFontUnderline( true );
mCursor->setCharFormat( format );
QDomNode child = element.firstChild();
while ( !child.isNull() ) {
if ( child.isElement() ) {
const QDomElement childElement = child.toElement();
if ( childElement.tagName() == QLatin1String( "emphasis" ) ) {
if ( !convertEmphasis( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "strong" ) ) {
if ( !convertStrong( childElement ) )
return false;
} else if ( childElement.tagName() == QLatin1String( "style" ) ) {
if ( !convertStyle( childElement ) )
return false;
}
} else if ( child.isText() ) {
const QDomText text = child.toText();
if ( !text.isNull() ) {
mCursor->insertText( text.data() );
}
}
child = child.nextSibling();
}
mCursor->setCharFormat( origFormat );
pos.endPosition = mCursor->position();
pos.url = href;
if ( type == "note" )
mCursor->insertText( "]" );
mLinkPositions.append( pos );
return true;
}
QTextDocument *Converter::textDocument() const
{
return mTextDocument;
}
Okular::DocumentInfo Converter::documentInfo() const
{
Okular::DocumentInfo info;
if ( mTitleInfo ) {
if ( !mTitleInfo->mTitle.isEmpty() )
info.set( "title", mTitleInfo->mTitle, i18n( "Title" ) );
if ( !mTitleInfo->mAuthor.isEmpty() )
info.set( "author", mTitleInfo->mAuthor, i18n( "Author" ) );
}
if ( mDocumentInfo ) {
if ( !mDocumentInfo->mProducer.isEmpty() )
info.set( "producer", mDocumentInfo->mProducer, i18n( "Producer" ) );
if ( !mDocumentInfo->mProducer.isEmpty() )
info.set( "creator", mDocumentInfo->mAuthor, i18n( "Creator" ) );
if ( mDocumentInfo->mDate.isValid() )
info.set( "creationDate",
KGlobal::locale()->formatDate( mDocumentInfo->mDate, true ),
i18n( "Created" ) );
}
return info;
}
Okular::DocumentSynopsis Converter::tableOfContents() const
{
const QSizeF pageSize = mTextDocument->pageSize();
QStack<QDomNode> parentNodeStack;
Okular::DocumentSynopsis tableOfContents;
QDomNode parentNode = tableOfContents;
int level = 1000;
for ( int i = 0; i < mHeaderInfos.count(); ++i )
level = qMin( level, mHeaderInfos[ i ].level );
for ( int i = 0; i < mHeaderInfos.count(); ++i ) {
const HeaderInfo headerInfo = mHeaderInfos[ i ];
const QRectF rect = mTextDocument->documentLayout()->blockBoundingRect( headerInfo.block );
int page = qRound( rect.y() ) / qRound( pageSize.height() );
int offset = qRound( rect.y() ) % qRound( pageSize.height() );
Okular::DocumentViewport viewport( page );
viewport.rePos.normalizedX = (double)rect.x() / (double)pageSize.width();
viewport.rePos.normalizedY = (double)offset / (double)pageSize.height();
viewport.rePos.enabled = true;
viewport.rePos.pos = Okular::DocumentViewport::Center;
QDomElement item = tableOfContents.createElement( headerInfo.text );
item.setAttribute( "Viewport", viewport.toString() );
int newLevel = headerInfo.level;
if ( newLevel == level ) {
parentNode.appendChild( item );
} else if ( newLevel > level ) {
parentNodeStack.push( parentNode );
parentNode = parentNode.lastChildElement();
parentNode.appendChild( item );
level = newLevel;
} else {
for ( int i = level; i > newLevel; i-- ) {
level--;
parentNode = parentNodeStack.pop();
}
parentNode.appendChild( item );
}
}
return tableOfContents;
}
void Converter::calculateBoundingRect( int startPosition, int endPosition, QRectF &rect, int &page )
{
const QSizeF pageSize = mTextDocument->pageSize();
const QTextBlock startBlock = mTextDocument->findBlock( startPosition );
const QRectF startBoundingRect = mTextDocument->documentLayout()->blockBoundingRect( startBlock );
const QTextBlock endBlock = mTextDocument->findBlock( endPosition );
const QRectF endBoundingRect = mTextDocument->documentLayout()->blockBoundingRect( endBlock );
QTextLayout *startLayout = startBlock.layout();
QTextLayout *endLayout = endBlock.layout();
int startPos = startPosition - startBlock.position();
int endPos = endPosition - endBlock.position();
const QTextLine startLine = startLayout->lineForTextPosition( startPos );
const QTextLine endLine = endLayout->lineForTextPosition( endPos );
double x = startBoundingRect.x() + startLine.cursorToX( startPos );
double y = startBoundingRect.y() + startLine.y();
double r = endBoundingRect.x() + endLine.cursorToX( endPos );
double b = endBoundingRect.y() + endLine.y() + endLine.height();
int offset = qRound( y ) % qRound( pageSize.height() );
page = qRound( y ) / qRound( pageSize.height() );
rect = QRectF( x / pageSize.width(), offset / pageSize.height(),
(r - x) / pageSize.width(), (b - y) / pageSize.height() );
}
Converter::LinkInfo::List Converter::links()
{
if ( !mLinkInfosGenerated )
createLinkInfos();
return mLinkInfos;
}
void Converter::createLinkInfos()
{
for ( int i = 0; i < mLinkPositions.count(); ++i ) {
const LinkPosition linkPosition = mLinkPositions[ i ];
LinkInfo info;
info.url = linkPosition.url;
calculateBoundingRect( linkPosition.startPosition, linkPosition.endPosition,
info.boundingRect, info.page );
mLinkInfos.append( info );
}
}

View file

@ -0,0 +1,118 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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 FICTIONBOOK_CONVERTER_H
#define FICTIONBOOK_CONVERTER_H
#include <QtCore/QDateTime>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCharFormat>
#include <QtXml/QDomDocument>
#include <okular/core/document.h>
class QDomElement;
class QDomText;
namespace FictionBook {
class Document;
class Converter
{
public:
Converter( const Document *document );
~Converter();
bool convert();
QTextDocument *textDocument() const;
Okular::DocumentInfo documentInfo() const;
Okular::DocumentSynopsis tableOfContents() const;
class LinkInfo
{
public:
typedef QList<LinkInfo> List;
int page;
QRectF boundingRect;
QString url;
};
LinkInfo::List links();
private:
bool convertBody( const QDomElement &element );
bool convertDescription( const QDomElement &element );
bool convertSection( const QDomElement &element );
bool convertTitle( const QDomElement &element );
bool convertParagraph( const QDomElement &element );
bool convertBinary( const QDomElement &element );
bool convertCover( const QDomElement &element );
bool convertImage( const QDomElement &element );
bool convertEpigraph( const QDomElement &element );
bool convertPoem( const QDomElement &element );
bool convertSubTitle( const QDomElement &element );
bool convertCite( const QDomElement &element );
bool convertEmptyLine( const QDomElement &element );
bool convertLink( const QDomElement &element );
bool convertEmphasis( const QDomElement &element );
bool convertStrong( const QDomElement &element );
bool convertStyle( const QDomElement &element );
bool convertTitleInfo( const QDomElement &element );
bool convertDocumentInfo( const QDomElement &element );
bool convertAuthor( const QDomElement &element,
QString &firstName, QString &middleName, QString &lastName,
QString &email, QString &nickname );
bool convertDate( const QDomElement &element, QDate &date );
bool convertTextNode( const QDomElement &element, QString &data );
void calculateBoundingRect( int, int, QRectF&, int& );
void createLinkInfos();
const Document *mDocument;
QTextDocument *mTextDocument;
QTextCursor *mCursor;
class TitleInfo;
TitleInfo *mTitleInfo;
class DocumentInfo;
DocumentInfo *mDocumentInfo;
struct HeaderInfo
{
QTextBlock block;
QString text;
int level;
};
QList<HeaderInfo> mHeaderInfos;
struct LinkPosition
{
int startPosition;
int endPosition;
QString url;
};
QList<LinkPosition> mLinkPositions;
LinkInfo::List mLinkInfos;
bool mLinkInfosGenerated;
int mSectionCounter;
};
}
#endif

View file

@ -0,0 +1,73 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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/QFile>
#include <kzip.h>
#include "document.h"
using namespace FictionBook;
Document::Document( const QString &fileName )
: mFileName( fileName )
{
}
bool Document::open()
{
QIODevice *device;
QFile file( mFileName );
KZip zip( mFileName );
if ( mFileName.endsWith( ".fb" ) || mFileName.endsWith( ".fb2" ) ) {
if ( !file.open( QIODevice::ReadOnly ) )
return false;
device = &file;
} else {
if ( !zip.open( QIODevice::ReadOnly ) )
return false;
const KArchiveDirectory *directory = zip.directory();
if ( !directory )
return false;
const QStringList entries = directory->entries();
QString documentFile;
for ( int i = 0; i < entries.count(); ++i ) {
if ( entries[ i ].endsWith( ".fb2" ) ) {
documentFile = entries[ i ];
break;
}
}
if ( documentFile.isEmpty() )
return false;
const KArchiveFile *entry = static_cast<const KArchiveFile*>( directory->entry( documentFile ) );
device = entry->device();
}
QString errorMsg;
int errorRow, errorColumn;
if ( !mDocument.setContent( device, true, &errorMsg, &errorRow, &errorColumn ) ) {
qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorRow, errorColumn );
return false;
}
return true;
}
QDomDocument Document::content() const
{
return mDocument;
}

View file

@ -0,0 +1,36 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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 FICTIONBOOK_DOCUMENT_H
#define FICTIONBOOK_DOCUMENT_H
#include <QtCore/QByteArray>
#include <QtCore/QMap>
#include <QtCore/QString>
#include <QtXml/QDomDocument>
namespace FictionBook {
class Document
{
public:
Document( const QString &fileName );
bool open();
QDomDocument content() const;
private:
QString mFileName;
QDomDocument mDocument;
};
}
#endif

View file

@ -0,0 +1,269 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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/QFile>
#include <QtCore/QTextStream>
#include <QtGui/QAbstractTextDocumentLayout>
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
#include <QtGui/QTextDocument>
#include <klocale.h>
#include <kprinter.h>
#include <okular/core/link.h>
#include <okular/core/page.h>
#include <okular/core/textpage.h>
#include "converter.h"
#include "document.h"
#include "generator_fb.h"
OKULAR_EXPORT_PLUGIN(FictionBookGenerator)
static void calculateBoundingRect( QTextDocument *document, int startPosition, int endPosition, QRectF &rect, int &page )
{
const QSizeF pageSize = document->pageSize();
const QTextBlock startBlock = document->findBlock( startPosition );
const QRectF startBoundingRect = document->documentLayout()->blockBoundingRect( startBlock );
const QTextBlock endBlock = document->findBlock( endPosition );
const QRectF endBoundingRect = document->documentLayout()->blockBoundingRect( endBlock );
QTextLayout *startLayout = startBlock.layout();
QTextLayout *endLayout = endBlock.layout();
int startPos = startPosition - startBlock.position();
int endPos = endPosition - endBlock.position();
const QTextLine startLine = startLayout->lineForTextPosition( startPos );
const QTextLine endLine = endLayout->lineForTextPosition( endPos );
double x = startBoundingRect.x() + startLine.cursorToX( startPos );
double y = startBoundingRect.y() + startLine.y();
double r = endBoundingRect.x() + endLine.cursorToX( endPos );
double b = endBoundingRect.y() + endLine.y() + endLine.height();
int offset = qRound( y ) % qRound( pageSize.height() );
page = qRound( y ) / qRound( pageSize.height() );
rect = QRectF( x / pageSize.width(), offset / pageSize.height(),
(r - x) / pageSize.width(), (b - y) / pageSize.height() );
}
static void calculatePositions( QTextDocument *document, int page, int &start, int &end )
{
const QAbstractTextDocumentLayout *layout = document->documentLayout();
const QSizeF pageSize = document->pageSize();
double margin = document->rootFrame()->frameFormat().margin();
/**
* Take the upper left and lower left corner including the margin
*/
start = layout->hitTest( QPointF( margin, (page * pageSize.height()) + margin ), Qt::FuzzyHit );
end = layout->hitTest( QPointF( margin, ((page + 1) * pageSize.height()) - margin ), Qt::FuzzyHit );
}
FictionBookGenerator::FictionBookGenerator()
: Okular::Generator(), mDocument( 0 )
{
}
FictionBookGenerator::~FictionBookGenerator()
{
delete mDocument;
mDocument = 0;
}
bool FictionBookGenerator::loadDocument( const QString & fileName, QVector<Okular::Page*> & pagesVector )
{
FictionBook::Document document( fileName );
if ( !document.open() )
return false;
FictionBook::Converter converter( &document );
if ( !converter.convert() )
return false;
mDocument = converter.textDocument();
mDocumentInfo = converter.documentInfo();
mDocumentSynopsis = converter.tableOfContents();
mLinks = converter.links();
pagesVector.resize( mDocument->pageCount() );
const QSize size = mDocument->pageSize().toSize();
for ( int i = 0; i < mDocument->pageCount(); ++i ) {
Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 );
pagesVector[ i ] = page;
}
return true;
}
bool FictionBookGenerator::closeDocument()
{
delete mDocument;
mDocument = 0;
return true;
}
bool FictionBookGenerator::canGeneratePixmap( bool ) const
{
return true;
}
void FictionBookGenerator::generatePixmap( Okular::PixmapRequest * request )
{
const QSize size = mDocument->pageSize().toSize();
QPixmap *pixmap = new QPixmap( request->width(), request->height() );
pixmap->fill( Qt::white );
QPainter p;
p.begin( pixmap );
qreal width = request->width();
qreal height = request->height();
p.scale( width / (qreal)size.width(), height / (qreal)size.height() );
QRect rect;
rect = QRect( 0, request->pageNumber() * size.height(), size.width(), size.height() );
p.translate( QPoint( 0, request->pageNumber() * size.height() * -1 ) );
mDocument->drawContents( &p, rect );
p.end();
request->page()->setPixmap( request->id(), pixmap );
/**
* Add link information
*/
QLinkedList<Okular::ObjectRect*> objects;
for ( int i = 0; i < mLinks.count(); ++i ) {
if ( mLinks[ i ].page == request->pageNumber() ) {
const QRectF rect = mLinks[ i ].boundingRect;
objects.append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false,
Okular::ObjectRect::Link, new Okular::LinkBrowse( mLinks[ i ].url ) ) );
}
}
request->page()->setObjectRects( objects );
signalRequestDone( request );
}
bool FictionBookGenerator::canGenerateTextPage() const
{
qDebug( "tokoe: can generate text page called" );
return true;
}
void FictionBookGenerator::generateSyncTextPage( Okular::Page * page )
{
qDebug( "tokoe: generate sync text page called" );
page->setTextPage( createTextPage( page->number() ) );
}
bool FictionBookGenerator::print( KPrinter& printer )
{
QPainter p( &printer );
const QSize size = mDocument->pageSize().toSize();
for ( int i = 0; i < mDocument->pageCount(); ++i ) {
if ( i != 0 )
printer.newPage();
QRect rect( 0, i * size.height(), size.width(), size.height() );
p.translate( QPoint( 0, i * size.height() * -1 ) );
mDocument->drawContents( &p, rect );
}
return true;
}
const Okular::DocumentInfo* FictionBookGenerator::generateDocumentInfo()
{
return &mDocumentInfo;
}
const Okular::DocumentSynopsis* FictionBookGenerator::generateDocumentSynopsis()
{
if ( !mDocumentSynopsis.hasChildNodes() )
return 0;
else
return &mDocumentSynopsis;
}
Okular::ExportFormat::List FictionBookGenerator::exportFormats( ) const
{
static Okular::ExportFormat::List formats;
if ( formats.isEmpty() ) {
formats.append( Okular::ExportFormat( i18n( "PDF" ), KMimeType::mimeType( "application/pdf" ) ) );
formats.append( Okular::ExportFormat( i18n( "Plain Text" ), KMimeType::mimeType( "text/plain" ) ) );
}
return formats;
}
bool FictionBookGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format )
{
if ( format.mimeType()->name() == QLatin1String( "application/pdf" ) ) {
QFile file( fileName );
if ( !file.open( QIODevice::WriteOnly ) )
return false;
QPrinter printer( QPrinter::HighResolution );
printer.setOutputFormat( QPrinter::PdfFormat );
printer.setOutputFileName( fileName );
mDocument->print( &printer );
return true;
} else if ( format.mimeType()->name() == QLatin1String( "text/plain" ) ) {
QFile file( fileName );
if ( !file.open( QIODevice::WriteOnly ) )
return false;
QTextStream out( &file );
out << mDocument->toPlainText();
return true;
}
return false;
}
Okular::TextPage* FictionBookGenerator::createTextPage( int pageNumber ) const
{
Okular::TextPage *textPage = new Okular::TextPage;
int start, end;
calculatePositions( mDocument, pageNumber, start, end );
QTextCursor cursor( mDocument );
for ( int i = start; i < end - 1; ++i ) {
cursor.setPosition( i );
cursor.setPosition( i + 1, QTextCursor::KeepAnchor );
QString text = cursor.selectedText();
if ( text.length() == 1 && text[ 0 ].isPrint() ) {
QRectF rect;
calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber );
textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) );
}
}
return textPage;
}
#include "generator_fb.moc"

View file

@ -0,0 +1,60 @@
/***************************************************************************
* Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org> *
* *
* 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_FB_H_
#define _OKULAR_GENERATOR_FB_H_
#include <okular/core/document.h>
#include <okular/core/generator.h>
#include "converter.h"
class QTextDocument;
class FictionBookGenerator : public Okular::Generator
{
Q_OBJECT
public:
FictionBookGenerator();
virtual ~FictionBookGenerator();
// [INHERITED] load a document and fill up the pagesVector
bool loadDocument( const QString & fileName, QVector<Okular::Page*> & pagesVector );
bool closeDocument();
// [INHERITED] perform actions on document / pages
bool canGeneratePixmap( bool async ) const;
void generatePixmap( Okular::PixmapRequest * request );
bool canGenerateTextPage() const;
void generateSyncTextPage( Okular::Page * page );
bool supportsSearching() const { return true; };
// [INHERITED] print document using already configured kprinter
bool print( KPrinter& printer );
// [INHERITED] text exporting
Okular::ExportFormat::List exportFormats() const;
bool exportTo( const QString &fileName, const Okular::ExportFormat &format );
const Okular::DocumentInfo* generateDocumentInfo();
const Okular::DocumentSynopsis* generateDocumentSynopsis();
private:
Okular::TextPage* createTextPage( int ) const;
QTextDocument *mDocument;
Okular::DocumentInfo mDocumentInfo;
Okular::DocumentSynopsis mDocumentSynopsis;
FictionBook::Converter::LinkInfo::List mLinks;
};
#endif

View file

@ -0,0 +1,11 @@
[Desktop Entry]
Encoding=UTF-8
Type=Service
Name=FictionBook document
Comment=FictionBook backend for okular
ServiceTypes=okular/Generator
MimeType=application/x-fb
X-KDE-Library=libokularGenerator_fb
X-KDE-Priority=1
X-KDE-okularAPIVersion=1
X-KDE-okularHasInternalSettings=false

View file

@ -0,0 +1,8 @@
[Desktop Entry]
Encoding=UTF-8
Icon=okular
Name=okular
ServiceTypes=KParts/ReadOnlyPart
X-KDE-Library=libokularpart
Type=Service
MimeType=application/x-fb