You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
15 KiB
467 lines
15 KiB
/*************************************************************************** |
|
* Copyright (C) 2005 by Piotr Szymański <niedakh@gmail.com> * |
|
* Copyright (C) 2008 by Albert Astals Cid <aacid@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 "generator_chm.h" |
|
|
|
#include <QtCore/QEventLoop> |
|
#include <QtCore/QMutex> |
|
#include <QtGui/QPainter> |
|
#include <QtXml/QDomElement> |
|
|
|
#include <kaboutdata.h> |
|
#include <khtml_part.h> |
|
#include <khtmlview.h> |
|
#include <klocale.h> |
|
#include <kurl.h> |
|
#include <dom/html_misc.h> |
|
#include <dom/dom_node.h> |
|
#include <dom/dom_html.h> |
|
|
|
#include <core/action.h> |
|
#include <core/observer.h> //for PAGEVIEW_ID |
|
#include <core/page.h> |
|
#include <core/textpage.h> |
|
#include <core/utils.h> |
|
|
|
static KAboutData createAboutData() |
|
{ |
|
KAboutData aboutData( |
|
"okular_chm", |
|
"okular_chm", |
|
ki18n( "CHM Backend" ), |
|
"0.1.4", |
|
ki18n( "A Microsoft Windows help file renderer" ), |
|
KAboutData::License_GPL, |
|
ki18n( "© 2005-2007 Piotr Szymański\n© 2008 Albert Astals Cid" ) |
|
); |
|
aboutData.addAuthor( ki18n( "Piotr Szymański" ), KLocalizedString(), "niedakh@gmail.com" ); |
|
aboutData.addAuthor( ki18n( "Albert Astals Cid" ), KLocalizedString(), "aacid@kde.org" ); |
|
return aboutData; |
|
} |
|
|
|
OKULAR_EXPORT_PLUGIN( CHMGenerator, createAboutData() ) |
|
|
|
static QString absolutePath( const QString &baseUrl, const QString &path ) |
|
{ |
|
QString absPath; |
|
if ( path.at( 0 ) == QLatin1Char( '/' ) ) |
|
{ |
|
// already absolute |
|
absPath = path; |
|
} |
|
else |
|
{ |
|
KUrl url = KUrl::fromPath( baseUrl ); |
|
url.setFileName( path ); |
|
absPath = url.toLocalFile(); |
|
} |
|
return absPath; |
|
} |
|
|
|
CHMGenerator::CHMGenerator( QObject *parent, const QVariantList &args ) |
|
: Okular::Generator( parent, args ) |
|
{ |
|
setFeature( TextExtraction ); |
|
|
|
m_syncGen=0; |
|
m_file=0; |
|
m_docInfo=0; |
|
m_pixmapRequestZoom=1; |
|
m_request = 0; |
|
} |
|
|
|
CHMGenerator::~CHMGenerator() |
|
{ |
|
delete m_syncGen; |
|
} |
|
|
|
bool CHMGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector ) |
|
{ |
|
m_file = new LCHMFile(); |
|
if (!m_file->loadFile(fileName)) |
|
{ |
|
delete m_file; |
|
m_file = 0; |
|
return false; |
|
} |
|
m_fileName=fileName; |
|
QVector< LCHMParsedEntry > topics; |
|
m_file->parseTableOfContents(&topics); |
|
|
|
// fill m_docSyn |
|
QMap<int, QDomElement> lastIndentElement; |
|
foreach(const LCHMParsedEntry &e, topics) |
|
{ |
|
QDomElement item = m_docSyn.createElement(e.name); |
|
if (!e.urls.isEmpty()) |
|
{ |
|
QString url = e.urls.first(); |
|
if (url.contains(QChar::fromLatin1('%'))) |
|
url = QString::fromUtf8(QByteArray::fromPercentEncoding(url.toUtf8())); |
|
item.setAttribute("ViewportName", url); |
|
} |
|
item.setAttribute("Icon", e.imageid); |
|
if (e.indent == 0) m_docSyn.appendChild(item); |
|
else lastIndentElement[e.indent - 1].appendChild(item); |
|
lastIndentElement[e.indent] = item; |
|
} |
|
|
|
// fill m_urlPage and m_pageUrl |
|
int pageNum = 0; |
|
QStringList pageList; |
|
m_file->enumerateFiles(&pageList); |
|
const QString home = m_file->homeUrl(); |
|
if (home != QLatin1String("/")) |
|
pageList.prepend(home); |
|
foreach (const QString &url, pageList) |
|
{ |
|
const QString urlLower = url.toLower(); |
|
if (!urlLower.endsWith(QLatin1String(".html")) && !urlLower.endsWith(QLatin1String(".htm"))) |
|
continue; |
|
|
|
int pos = url.indexOf ('#'); |
|
QString tmpUrl = pos == -1 ? url : url.left(pos); |
|
|
|
// url already there, abort insertion |
|
if (m_urlPage.contains(tmpUrl)) continue; |
|
|
|
// insert the url into the maps, but insert always the variant without the #ref part |
|
m_urlPage.insert(tmpUrl, pageNum); |
|
m_pageUrl.append(tmpUrl); |
|
pageNum++; |
|
} |
|
|
|
pagesVector.resize(m_pageUrl.count()); |
|
m_textpageAddedList.fill(false, pagesVector.count()); |
|
|
|
if (!m_syncGen) |
|
{ |
|
m_syncGen = new KHTMLPart(); |
|
} |
|
disconnect( m_syncGen, 0, this, 0 ); |
|
|
|
for (int i = 0; i < m_pageUrl.count(); ++i) |
|
{ |
|
preparePageForSyncOperation(100, m_pageUrl.at(i)); |
|
pagesVector[ i ] = new Okular::Page (i, m_syncGen->view()->contentsWidth(), |
|
m_syncGen->view()->contentsHeight(), Okular::Rotation0 ); |
|
} |
|
|
|
connect( m_syncGen, SIGNAL(completed()), this, SLOT(slotCompleted()) ); |
|
connect( m_syncGen, SIGNAL(canceled(QString)), this, SLOT(slotCompleted()) ); |
|
|
|
return true; |
|
} |
|
|
|
bool CHMGenerator::doCloseDocument() |
|
{ |
|
// delete the document information of the old document |
|
delete m_docInfo; |
|
m_docInfo=0; |
|
delete m_file; |
|
m_file=0; |
|
m_textpageAddedList.clear(); |
|
m_urlPage.clear(); |
|
m_pageUrl.clear(); |
|
m_docSyn.clear(); |
|
if (m_syncGen) |
|
{ |
|
m_syncGen->closeUrl(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CHMGenerator::preparePageForSyncOperation( int zoom , const QString & url) |
|
{ |
|
KUrl pAddress= QString("ms-its:" + m_fileName + "::" + url); |
|
m_chmUrl = url; |
|
m_syncGen->setZoomFactor(zoom); |
|
m_syncGen->openUrl(pAddress); |
|
m_syncGen->view()->layout(); |
|
|
|
QEventLoop loop; |
|
connect( m_syncGen, SIGNAL(completed()), &loop, SLOT(quit()) ); |
|
connect( m_syncGen, SIGNAL(canceled(QString)), &loop, SLOT(quit()) ); |
|
// discard any user input, otherwise it breaks the "synchronicity" of this |
|
// function |
|
loop.exec( QEventLoop::ExcludeUserInputEvents ); |
|
} |
|
|
|
void CHMGenerator::slotCompleted() |
|
{ |
|
if ( !m_request ) |
|
return; |
|
|
|
QImage image( m_request->width(), m_request->height(), QImage::Format_ARGB32 ); |
|
image.fill( qRgb( 255, 255, 255 ) ); |
|
|
|
QPainter p( &image ); |
|
QRect r( 0, 0, m_request->width(), m_request->height() ); |
|
|
|
bool moreToPaint; |
|
m_syncGen->paint( &p, r, 0, &moreToPaint ); |
|
|
|
p.end(); |
|
|
|
if ( m_pixmapRequestZoom > 1 ) |
|
m_pixmapRequestZoom = 1; |
|
|
|
if ( !m_textpageAddedList.at( m_request->pageNumber() ) ) { |
|
additionalRequestData(); |
|
m_textpageAddedList[ m_request->pageNumber() ] = true; |
|
} |
|
|
|
m_syncGen->closeUrl(); |
|
m_chmUrl = QString(); |
|
|
|
userMutex()->unlock(); |
|
|
|
Okular::PixmapRequest *req = m_request; |
|
m_request = 0; |
|
|
|
if ( !req->page()->isBoundingBoxKnown() ) |
|
updatePageBoundingBox( req->page()->number(), Okular::Utils::imageBoundingBox( &image ) ); |
|
req->page()->setPixmap( req->id(), new QPixmap( QPixmap::fromImage( image ) ) ); |
|
signalPixmapRequestDone( req ); |
|
} |
|
|
|
const Okular::DocumentInfo * CHMGenerator::generateDocumentInfo() |
|
{ |
|
if (!m_docInfo) |
|
{ |
|
m_docInfo=new Okular::DocumentInfo(); |
|
|
|
m_docInfo->set( Okular::DocumentInfo::MimeType, "application/x-chm" ); |
|
m_docInfo->set( Okular::DocumentInfo::Title, m_file->title() ); |
|
} |
|
return m_docInfo; |
|
} |
|
|
|
const Okular::DocumentSynopsis * CHMGenerator::generateDocumentSynopsis() |
|
{ |
|
return &m_docSyn; |
|
} |
|
|
|
bool CHMGenerator::canGeneratePixmap () const |
|
{ |
|
bool isLocked = true; |
|
if ( userMutex()->tryLock() ) { |
|
userMutex()->unlock(); |
|
isLocked = false; |
|
} |
|
|
|
return !isLocked; |
|
} |
|
|
|
void CHMGenerator::generatePixmap( Okular::PixmapRequest * request ) |
|
{ |
|
int requestWidth = request->width(); |
|
int requestHeight = request->height(); |
|
if (requestWidth<300) |
|
{ |
|
m_pixmapRequestZoom=900/requestWidth; |
|
requestWidth*=m_pixmapRequestZoom; |
|
requestHeight*=m_pixmapRequestZoom; |
|
} |
|
|
|
userMutex()->lock(); |
|
QString url= m_pageUrl[request->pageNumber()]; |
|
int zoom = qRound( qMax( static_cast<double>(requestWidth)/static_cast<double>(request->page()->width()) |
|
, static_cast<double>(requestHeight)/static_cast<double>(request->page()->height()) |
|
) ) * 100; |
|
|
|
KUrl pAddress= QString("ms-its:" + m_fileName + "::" + url); |
|
m_chmUrl = url; |
|
m_syncGen->setZoomFactor(zoom); |
|
m_syncGen->view()->resize(requestWidth,requestHeight); |
|
m_request=request; |
|
// will emit openURL without problems |
|
m_syncGen->openUrl ( pAddress ); |
|
} |
|
|
|
|
|
void CHMGenerator::recursiveExploreNodes(DOM::Node node,Okular::TextPage *tp) |
|
{ |
|
if (node.nodeType() == DOM::Node::TEXT_NODE && !node.getRect().isNull()) |
|
{ |
|
QString nodeText=node.nodeValue().string(); |
|
QRect r=node.getRect(); |
|
int vWidth=m_syncGen->view()->width(); |
|
int vHeight=m_syncGen->view()->height(); |
|
Okular::NormalizedRect *nodeNormRect; |
|
#define NOEXP |
|
#ifndef NOEXP |
|
int x,y,height; |
|
int x_next,y_next,height_next; |
|
int nodeTextLength = nodeText.length(); |
|
if (nodeTextLength==1) |
|
{ |
|
nodeNormRect=new Okular::NormalizedRect (r,vWidth,vHeight); |
|
tp->append(nodeText,nodeNormRect,nodeNormRect->bottom,0,(nodeText=="\n")); |
|
} |
|
else |
|
{ |
|
for (int i=0;i<nodeTextLength;i++) |
|
{ |
|
node.getCursor(i,x,y,height); |
|
if (i==0) |
|
// i is 0, use left rect boundary |
|
{ |
|
// if (nodeType[i+1] |
|
node.getCursor(i+1,x_next,y_next,height_next); |
|
nodeNormRect=new Okular::NormalizedRect (QRect(x,y,x_next-x-1,height),vWidth,vHeight); |
|
} |
|
else if ( i <nodeTextLength -1 ) |
|
// i is between zero and the last element |
|
{ |
|
node.getCursor(i+1,x_next,y_next,height_next); |
|
nodeNormRect=new Okular::NormalizedRect (QRect(x,y,x_next-x-1,height),vWidth,vHeight); |
|
} |
|
else |
|
// the last element use right rect boundary |
|
{ |
|
node.getCursor(i-1,x_next,y_next,height_next); |
|
} |
|
} |
|
#else |
|
nodeNormRect=new Okular::NormalizedRect (r,vWidth,vHeight); |
|
tp->append(nodeText,nodeNormRect/*,0*/); |
|
#endif |
|
} |
|
DOM::Node child = node.firstChild(); |
|
while ( !child.isNull() ) |
|
{ |
|
recursiveExploreNodes(child,tp); |
|
child = child.nextSibling(); |
|
} |
|
} |
|
|
|
void CHMGenerator::additionalRequestData() |
|
{ |
|
Okular::Page * page=m_request->page(); |
|
bool genObjectRects = m_request->id() & (PAGEVIEW_ID | PRESENTATION_ID); |
|
bool genTextPage = !m_request->page()->hasTextPage() && genObjectRects; |
|
|
|
if (genObjectRects || genTextPage ) |
|
{ |
|
DOM::HTMLDocument domDoc=m_syncGen->htmlDocument(); |
|
// only generate object info when generating a full page not a thumbnail |
|
if ( genObjectRects ) |
|
{ |
|
QLinkedList< Okular::ObjectRect * > objRects; |
|
int xScale=m_syncGen->view()->width(); |
|
int yScale=m_syncGen->view()->height(); |
|
// getting links |
|
DOM::HTMLCollection coll=domDoc.links(); |
|
DOM::Node n; |
|
QRect r; |
|
if (! coll.isNull() ) |
|
{ |
|
int size=coll.length(); |
|
for(int i=0;i<size;i++) |
|
{ |
|
n=coll.item(i); |
|
if ( !n.isNull() ) |
|
{ |
|
QString url = n.attributes().getNamedItem("href").nodeValue().string(); |
|
r=n.getRect(); |
|
// there is no way for us to support javascript properly |
|
if (url.startsWith("JavaScript:"), Qt::CaseInsensitive) |
|
continue; |
|
else if (url.contains (":")) |
|
{ |
|
objRects.push_back( |
|
new Okular::ObjectRect ( Okular::NormalizedRect(r,xScale,yScale), |
|
false, |
|
Okular::ObjectRect::Action, |
|
new Okular::BrowseAction ( url ))); |
|
} |
|
else |
|
{ |
|
Okular::DocumentViewport viewport( metaData( "NamedViewport", absolutePath( m_chmUrl, url ) ).toString() ); |
|
objRects.push_back( |
|
new Okular::ObjectRect ( Okular::NormalizedRect(r,xScale,yScale), |
|
false, |
|
Okular::ObjectRect::Action, |
|
new Okular::GotoAction ( QString::null, viewport))); //krazy:exclude=nullstrassign for old broken gcc |
|
} |
|
} |
|
} |
|
} |
|
|
|
// getting images |
|
coll=domDoc.images(); |
|
if (! coll.isNull() ) |
|
{ |
|
int size=coll.length(); |
|
for(int i=0;i<size;i++) |
|
{ |
|
n=coll.item(i); |
|
if ( !n.isNull() ) |
|
{ |
|
objRects.push_back( |
|
new Okular::ObjectRect ( Okular::NormalizedRect(n.getRect(),xScale,yScale), |
|
false, |
|
Okular::ObjectRect::Image, |
|
0)); |
|
} |
|
} |
|
} |
|
m_request->page()->setObjectRects( objRects ); |
|
} |
|
|
|
if ( genTextPage ) |
|
{ |
|
Okular::TextPage *tp=new Okular::TextPage(); |
|
recursiveExploreNodes(domDoc,tp); |
|
page->setTextPage (tp); |
|
} |
|
} |
|
} |
|
|
|
Okular::TextPage* CHMGenerator::textPage( Okular::Page * page ) |
|
{ |
|
userMutex()->lock(); |
|
const int zoom = 100; |
|
m_syncGen->view()->resize(page->width(), page->height()); |
|
|
|
preparePageForSyncOperation(zoom, m_pageUrl[page->number()]); |
|
Okular::TextPage *tp=new Okular::TextPage(); |
|
recursiveExploreNodes( m_syncGen->htmlDocument(), tp); |
|
userMutex()->unlock(); |
|
return tp; |
|
} |
|
|
|
QVariant CHMGenerator::metaData( const QString &key, const QVariant &option ) const |
|
{ |
|
if ( key == "NamedViewport" && !option.toString().isEmpty() ) |
|
{ |
|
const int pos = option.toString().indexOf('#'); |
|
QString tmpUrl = pos == -1 ? option.toString() : option.toString().left(pos); |
|
Okular::DocumentViewport viewport; |
|
QMap<QString,int>::const_iterator it = m_urlPage.find(tmpUrl); |
|
if (it != m_urlPage.end()) |
|
{ |
|
viewport.pageNumber = it.value(); |
|
return viewport.toString(); |
|
} |
|
|
|
} |
|
else if ( key == "DocumentTitle" ) |
|
{ |
|
return m_file->title(); |
|
} |
|
return QVariant(); |
|
} |
|
|
|
/* kate: replace-tabs on; tab-width 4; */ |
|
|
|
#include "generator_chm.moc"
|
|
|