/*************************************************************************** * Copyright (C) 2005 by Piotr SzymaƄski * * Copyright (C) 2008 by Albert Astals Cid * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include OKULAR_EXPORT_PLUGIN(CHMGenerator, "libokularGenerator_chmlib.json") static QString absolutePath( const QString &baseUrl, const QString &path ) { QString absPath; if ( path.startsWith(QLatin1Char( '/' )) ) { // already absolute absPath = path; } else { QUrl url = QUrl::fromLocalFile( baseUrl ).adjusted(QUrl::RemoveFilename); url.setPath( url.path() + 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_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 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(QStringLiteral("ViewportName"), url); } item.setAttribute(QStringLiteral("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()); m_rectsGenerated.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, &KParts::ReadOnlyPart::canceled, this, &CHMGenerator::slotCompleted ); return true; } bool CHMGenerator::doCloseDocument() { // delete the document information of the old document delete m_file; m_file=0; m_textpageAddedList.clear(); m_rectsGenerated.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) { QString pAddress= QString("ms-its:" + m_fileName + "::" + url); m_chmUrl = url; m_syncGen->setZoomFactor(zoom); m_syncGen->openUrl(QUrl(pAddress)); m_syncGen->view()->layout(); QEventLoop loop; connect( m_syncGen, SIGNAL(completed()), &loop, SLOT(quit()) ); connect( m_syncGen, &KParts::ReadOnlyPart::canceled, &loop, &QEventLoop::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->observer(), new QPixmap( QPixmap::fromImage( image ) ) ); signalPixmapRequestDone( req ); } Okular::DocumentInfo CHMGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; if ( keys.contains( Okular::DocumentInfo::MimeType ) ) docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/x-chm") ); if ( keys.contains( Okular::DocumentInfo::Title ) ) docInfo.set( Okular::DocumentInfo::Title, m_file->title() ); return 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(requestWidth)/static_cast(request->page()->width()) , static_cast(requestHeight)/static_cast(request->page()->height()) ) ) * 100; QString 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 ( QUrl(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;iappend(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(); const bool genObjectRects = !m_rectsGenerated.at( m_request->page()->number() ); const 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;ipage()->setObjectRects( objRects ); m_rectsGenerated[ m_request->page()->number() ] = true; } 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 == QLatin1String("NamedViewport") && !option.toString().isEmpty() ) { const int pos = option.toString().indexOf('#'); QString tmpUrl = pos == -1 ? option.toString() : option.toString().left(pos); Okular::DocumentViewport viewport; QMap::const_iterator it = m_urlPage.find(tmpUrl); if (it != m_urlPage.end()) { viewport.pageNumber = it.value(); return viewport.toString(); } } else if ( key == QLatin1String("DocumentTitle") ) { return m_file->title(); } return QVariant(); } /* kate: replace-tabs on; tab-width 4; */ #include "generator_chm.moc"