diff --git a/core/textdocumentgenerator.cpp b/core/textdocumentgenerator.cpp index b8b1e0f36..09c419a75 100644 --- a/core/textdocumentgenerator.cpp +++ b/core/textdocumentgenerator.cpp @@ -1,567 +1,587 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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 "textdocumentgenerator.h" #include "textdocumentgenerator_p.h" #include #include #include #include #include #include #include #include #include #include #include "action.h" #include "annotations.h" +#include "document_p.h" #include "page.h" #include "textpage.h" #include "document.h" #include using namespace Okular; /** * Generic Converter Implementation */ TextDocumentConverter::TextDocumentConverter() : QObject( nullptr ), d_ptr( new TextDocumentConverterPrivate ) { } TextDocumentConverter::~TextDocumentConverter() { delete d_ptr; } QTextDocument *TextDocumentConverter::convert( const QString & ) { return nullptr; } Document::OpenResult TextDocumentConverter::convertWithPassword( const QString &fileName, const QString & ) { QTextDocument *doc = convert( fileName ); setDocument( doc ); return doc != nullptr ? Document::OpenSuccess : Document::OpenError; } QTextDocument *TextDocumentConverter::document() { return d_ptr->mDocument; } void TextDocumentConverter::setDocument( QTextDocument *document ) { d_ptr->mDocument = document; } DocumentViewport TextDocumentConverter::calculateViewport( QTextDocument *document, const QTextBlock &block ) { return TextDocumentUtils::calculateViewport( document, block ); } TextDocumentGenerator* TextDocumentConverter::generator() const { return d_ptr->mParent ? d_ptr->mParent->q_func() : nullptr; } /** * Generic Generator Implementation */ Okular::TextPage* TextDocumentGeneratorPrivate::createTextPage( int pageNumber ) const { #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( const TextDocumentGenerator ); #endif Okular::TextPage *textPage = new Okular::TextPage; int start, end; #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif TextDocumentUtils::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 ) { QRectF rect; TextDocumentUtils::calculateBoundingRect( mDocument, i, i + 1, rect, pageNumber ); if ( pageNumber == -1 ) text = QStringLiteral("\n"); textPage->append( text, new Okular::NormalizedRect( rect.left(), rect.top(), rect.right(), rect.bottom() ) ); } } } #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif return textPage; } void TextDocumentGeneratorPrivate::addAction( Action *action, int cursorBegin, int cursorEnd ) { if ( !action ) return; LinkPosition position; position.link = action; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mLinkPositions.append( position ); } void TextDocumentGeneratorPrivate::addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ) { if ( !annotation ) return; annotation->setFlags( annotation->flags() | Okular::Annotation::External ); AnnotationPosition position; position.annotation = annotation; position.startPosition = cursorBegin; position.endPosition = cursorEnd; mAnnotationPositions.append( position ); } void TextDocumentGeneratorPrivate::addTitle( int level, const QString &title, const QTextBlock &block ) { TitlePosition position; position.level = level; position.title = title; position.block = block; mTitlePositions.append( position ); } void TextDocumentGeneratorPrivate::addMetaData( const QString &key, const QString &value, const QString &title ) { mDocumentInfo.set( key, value, title ); } void TextDocumentGeneratorPrivate::addMetaData( DocumentInfo::Key key, const QString &value ) { mDocumentInfo.set( key, value ); } QList TextDocumentGeneratorPrivate::generateLinkInfos() const { QList result; for ( int i = 0; i < mLinkPositions.count(); ++i ) { const LinkPosition &linkPosition = mLinkPositions[ i ]; const QVector rects = TextDocumentUtils::calculateBoundingRects( mDocument, linkPosition.startPosition, linkPosition.endPosition ); for ( int i = 0; i < rects.count(); ++i) { const QRectF &rect = rects[ i ]; LinkInfo info; info.link = linkPosition.link; info.ownsLink = i == 0; info.page = std::floor( rect.y() ); info.boundingRect = QRectF( rect.x(), rect.y() - info.page, rect.width(), rect.height() ); result.append( info ); } } return result; } QList TextDocumentGeneratorPrivate::generateAnnotationInfos() const { QList result; for ( int i = 0; i < mAnnotationPositions.count(); ++i ) { const AnnotationPosition &annotationPosition = mAnnotationPositions[ i ]; AnnotationInfo info; info.annotation = annotationPosition.annotation; TextDocumentUtils::calculateBoundingRect( mDocument, annotationPosition.startPosition, annotationPosition.endPosition, info.boundingRect, info.page ); if ( info.page >= 0 ) result.append( info ); } return result; } void TextDocumentGeneratorPrivate::generateTitleInfos() { QStack< QPair > parentNodeStack; QDomNode parentNode = mDocumentSynopsis; parentNodeStack.push( qMakePair( 0, parentNode ) ); for ( int i = 0; i < mTitlePositions.count(); ++i ) { const TitlePosition &position = mTitlePositions[ i ]; Okular::DocumentViewport viewport = TextDocumentUtils::calculateViewport( mDocument, position.block ); QDomElement item = mDocumentSynopsis.createElement( position.title ); item.setAttribute( QStringLiteral("Viewport"), viewport.toString() ); int headingLevel = position.level; // we need a parent, which has to be at a higher heading level than this heading level // so we just work through the stack while ( ! parentNodeStack.isEmpty() ) { int parentLevel = parentNodeStack.top().first; if ( parentLevel < headingLevel ) { // this is OK as a parent parentNode = parentNodeStack.top().second; break; } else { // we'll need to be further into the stack parentNodeStack.pop(); } } parentNode.appendChild( item ); parentNodeStack.push( qMakePair( headingLevel, QDomNode(item) ) ); } } void TextDocumentGeneratorPrivate::initializeGenerator() { Q_Q( TextDocumentGenerator ); mConverter->d_ptr->mParent = q->d_func(); if ( mGeneralSettings ) { mFont = mGeneralSettings->font(); } q->setFeature( Generator::TextExtraction ); q->setFeature( Generator::PrintNative ); q->setFeature( Generator::PrintToFile ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING if ( QFontDatabase::supportsThreadedFontRendering() ) q->setFeature( Generator::Threaded ); #endif QObject::connect( mConverter, SIGNAL(addAction(Action*,int,int)), q, SLOT(addAction(Action*,int,int)) ); QObject::connect( mConverter, SIGNAL(addAnnotation(Annotation*,int,int)), q, SLOT(addAnnotation(Annotation*,int,int)) ); QObject::connect( mConverter, SIGNAL(addTitle(int,QString,QTextBlock)), q, SLOT(addTitle(int,QString,QTextBlock)) ); QObject::connect( mConverter, SIGNAL(addMetaData(QString,QString,QString)), q, SLOT(addMetaData(QString,QString,QString)) ); QObject::connect( mConverter, SIGNAL(addMetaData(DocumentInfo::Key,QString)), q, SLOT(addMetaData(DocumentInfo::Key,QString)) ); QObject::connect( mConverter, &TextDocumentConverter::error, q, &Generator::error ); QObject::connect( mConverter, &TextDocumentConverter::warning, q, &Generator::warning ); QObject::connect( mConverter, &TextDocumentConverter::notice, q, &Generator::notice ); } TextDocumentGenerator::TextDocumentGenerator(TextDocumentConverter *converter, const QString& configName , QObject *parent, const QVariantList &args) : Okular::Generator( *new TextDocumentGeneratorPrivate( converter ), parent, args ) { Q_D( TextDocumentGenerator ); d->mGeneralSettings = new TextDocumentSettings( configName, this ); d->initializeGenerator(); } TextDocumentGenerator::~TextDocumentGenerator() { } Document::OpenResult TextDocumentGenerator::loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) { Q_D( TextDocumentGenerator ); const Document::OpenResult openResult = d->mConverter->convertWithPassword( fileName, password ); if ( openResult != Document::OpenSuccess ) { d->mDocument = nullptr; // loading failed, cleanup all the stuff eventually gathered from the converter d->mTitlePositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::LinkPosition &linkPos, d->mLinkPositions ) { delete linkPos.link; } d->mLinkPositions.clear(); Q_FOREACH ( const TextDocumentGeneratorPrivate::AnnotationPosition &annPos, d->mAnnotationPositions ) { delete annPos.annotation; } d->mAnnotationPositions.clear(); return openResult; } d->mDocument = d->mConverter->document(); d->generateTitleInfos(); const QList linkInfos = d->generateLinkInfos(); const QList annotationInfos = d->generateAnnotationInfos(); pagesVector.resize( d->mDocument->pageCount() ); const QSize size = d->mDocument->pageSize().toSize(); QVector< QLinkedList > objects( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::LinkInfo &info : linkInfos ) { // in case that the converter report bogus link info data, do not assert here if ( info.page >= objects.count() ) continue; const QRectF rect = info.boundingRect; if ( info.ownsLink ) { objects[ info.page ].append( new Okular::ObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link ) ); } else { objects[ info.page ].append( new Okular::NonOwningObjectRect( rect.left(), rect.top(), rect.right(), rect.bottom(), false, Okular::ObjectRect::Action, info.link ) ); } } QVector< QLinkedList > annots( d->mDocument->pageCount() ); for ( const TextDocumentGeneratorPrivate::AnnotationInfo &info : annotationInfos ) { annots[ info.page ].append( info.annotation ); } for ( int i = 0; i < d->mDocument->pageCount(); ++i ) { Okular::Page * page = new Okular::Page( i, size.width(), size.height(), Okular::Rotation0 ); pagesVector[ i ] = page; if ( !objects.at( i ).isEmpty() ) { page->setObjectRects( objects.at( i ) ); } QLinkedList::ConstIterator annIt = annots.at( i ).begin(), annEnd = annots.at( i ).end(); for ( ; annIt != annEnd; ++annIt ) { page->addAnnotation( *annIt ); } } return openResult; } bool TextDocumentGenerator::doCloseDocument() { Q_D( TextDocumentGenerator ); delete d->mDocument; d->mDocument = nullptr; d->mTitlePositions.clear(); d->mLinkPositions.clear(); d->mAnnotationPositions.clear(); // do not use clear() for the following two, otherwise they change type d->mDocumentInfo = Okular::DocumentInfo(); d->mDocumentSynopsis = Okular::DocumentSynopsis(); return true; } bool TextDocumentGenerator::canGeneratePixmap() const { return Generator::canGeneratePixmap(); } void TextDocumentGenerator::generatePixmap( Okular::PixmapRequest * request ) { Generator::generatePixmap( request ); } QImage TextDocumentGeneratorPrivate::image( PixmapRequest * request ) { if ( !mDocument ) return QImage(); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING Q_Q( TextDocumentGenerator ); #endif QImage image( request->width(), request->height(), QImage::Format_ARGB32 ); image.fill( Qt::white ); QPainter p; p.begin( &image ); qreal width = request->width(); qreal height = request->height(); const QSize size = mDocument->pageSize().toSize(); 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 ) ); p.setClipRect( rect ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->lock(); #endif QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor( QPalette::Text, Qt::black ); // FIXME Fix Qt, this doesn't work, we have horrible hacks // in the generators that return html, remove that code // if Qt ever gets fixed // context.palette.setColor( QPalette::Link, Qt::blue ); context.clip = rect; mDocument->setDefaultFont( mFont ); mDocument->documentLayout()->draw( &p, context ); #ifdef OKULAR_TEXTDOCUMENT_THREADED_RENDERING q->userMutex()->unlock(); #endif p.end(); return image; } Okular::TextPage* TextDocumentGenerator::textPage( Okular::TextRequest * request ) { Q_D( TextDocumentGenerator ); return d->createTextPage( request->page()->number() ); } bool TextDocumentGenerator::print( QPrinter& printer ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; d->mDocument->print( &printer ); return true; } Okular::DocumentInfo TextDocumentGenerator::generateDocumentInfo( const QSet & /*keys*/ ) const { Q_D( const TextDocumentGenerator ); return d->mDocumentInfo; } const Okular::DocumentSynopsis* TextDocumentGenerator::generateDocumentSynopsis() { Q_D( TextDocumentGenerator ); if ( !d->mDocumentSynopsis.hasChildNodes() ) return nullptr; else return &d->mDocumentSynopsis; } QVariant TextDocumentGeneratorPrivate::metaData( const QString &key, const QVariant &option ) const { Q_UNUSED( option ) if ( key == QLatin1String("DocumentTitle") ) { return mDocumentInfo.get( DocumentInfo::Title ); } return QVariant(); } Okular::ExportFormat::List TextDocumentGenerator::exportFormats( ) const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PDF ) ); if ( QTextDocumentWriter::supportedDocumentFormats().contains( "ODF" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::OpenDocumentText ) ); } if ( QTextDocumentWriter::supportedDocumentFormats().contains( "HTML" ) ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::HTML ) ); } } return formats; } bool TextDocumentGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { Q_D( TextDocumentGenerator ); if ( !d->mDocument ) return false; 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 ); d->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 << d->mDocument->toPlainText(); return true; } else if ( format.mimeType().name() == QLatin1String( "application/vnd.oasis.opendocument.text" ) ) { QTextDocumentWriter odfWriter( fileName, "odf" ); return odfWriter.write( d->mDocument ); } else if ( format.mimeType().name() == QLatin1String( "text/html" ) ) { QTextDocumentWriter odfWriter( fileName, "html" ); return odfWriter.write( d->mDocument ); } return false; } bool TextDocumentGenerator::reparseConfig() { Q_D( TextDocumentGenerator ); const QFont newFont = d->mGeneralSettings->font(); if ( newFont != d->mFont ) { d->mFont = newFont; return true; } return false; } void TextDocumentGenerator::addPages( KConfigDialog* /*dlg*/ ) { qCWarning(OkularCoreDebug) << "You forgot to reimplement addPages in your TextDocumentGenerator"; return; } TextDocumentSettings* TextDocumentGenerator::generalSettings() { Q_D( TextDocumentGenerator ); return d->mGeneralSettings; } +TextDocumentConverter* TextDocumentGenerator::converter() +{ + Q_D( TextDocumentGenerator ); + + return d->mConverter; +} + +void TextDocumentGenerator::setTextDocument( QTextDocument *textDocument ) +{ + Q_D( TextDocumentGenerator ); + + d->mDocument = textDocument; + + Q_FOREACH (Page *p, d->m_document->m_pagesVector ) + { + p->setTextPage( nullptr ); + } +} + #include "moc_textdocumentgenerator.cpp" diff --git a/core/textdocumentgenerator.h b/core/textdocumentgenerator.h index 30ddfd974..355a0cb53 100644 --- a/core/textdocumentgenerator.h +++ b/core/textdocumentgenerator.h @@ -1,229 +1,235 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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_TEXTDOCUMENTGENERATOR_H_ #define _OKULAR_TEXTDOCUMENTGENERATOR_H_ #include "okularcore_export.h" #include "document.h" #include "generator.h" #include "textdocumentsettings.h" #include "../interfaces/configinterface.h" class QTextBlock; class QTextDocument; namespace Okular { class TextDocumentConverterPrivate; class TextDocumentGenerator; class TextDocumentGeneratorPrivate; class OKULARCORE_EXPORT TextDocumentConverter : public QObject { Q_OBJECT friend class TextDocumentGenerator; friend class TextDocumentGeneratorPrivate; public: /** * Creates a new generic converter. */ TextDocumentConverter(); /** * Destroys the generic converter. */ ~TextDocumentConverter(); /** * Returns the generated QTextDocument object. The caller takes ownership of the QTextDocument * * @note there is no need to implement this one if you implement convertWithPassword */ virtual QTextDocument *convert( const QString &fileName ); /** * Returns the generated QTextDocument object. */ virtual Document::OpenResult convertWithPassword( const QString &fileName, const QString &password ); /** * Returns the generated QTextDocument object. Will be null if convert didn't succeed */ QTextDocument *document(); Q_SIGNALS: /** * Adds a new link object which is located between cursorBegin and * cursorEnd to the generator. */ void addAction( Action *link, int cursorBegin, int cursorEnd ); /** * Adds a new annotation object which is located between cursorBegin and * cursorEnd to the generator. */ void addAnnotation( Annotation *annotation, int cursorBegin, int cursorEnd ); /** * Adds a new title at the given level which is located as position to the generator. */ void addTitle( int level, const QString &title, const QTextBlock &position ); /** * Adds a set of meta data to the generator. */ void addMetaData( const QString &key, const QString &value, const QString &title ); /** * Adds a set of meta data to the generator. * * @since 0.7 (KDE 4.1) */ void addMetaData( DocumentInfo::Key key, const QString &value ); /** * This signal should be emitted whenever an error occurred in the converter. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void error( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be warned. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void warning( const QString &message, int duration ); /** * This signal should be emitted whenever the user should be noticed. * * @param message The message which should be shown to the user. * @param duration The time that the message should be shown to the user. */ void notice( const QString &message, int duration ); protected: /** * Sets the converted QTextDocument object. */ void setDocument( QTextDocument *document ); /** * This method can be used to calculate the viewport for a given text block. * * @note This method should be called at the end of the conversion, because it * triggers QTextDocument to do the layout calculation. */ DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ); /** * Returns the generator that owns this converter. * * @note May be null if the converter was not created for a generator. * * @since 0.7 (KDE 4.1) */ TextDocumentGenerator* generator() const; private: TextDocumentConverterPrivate *d_ptr; Q_DECLARE_PRIVATE( TextDocumentConverter ) Q_DISABLE_COPY( TextDocumentConverter ) }; /** * @brief QTextDocument-based Generator * * This generator provides a document in the form of a QTextDocument object, * parsed using a specialized TextDocumentConverter. */ class OKULARCORE_EXPORT TextDocumentGenerator : public Generator, public Okular::ConfigInterface { /// @cond PRIVATE friend class TextDocumentConverter; /// @endcond Q_OBJECT Q_INTERFACES( Okular::ConfigInterface ) public: /** * Creates a new generator that uses the specified @p converter. * * @param converter The text document converter. * @param configName - see Okular::TextDocumentSettings * @param parent The parent object. * @param args The arguments. * * @note the generator will take ownership of the converter, so you * don't have to delete it yourself * @since 0.17 (KDE 4.11) */ TextDocumentGenerator(TextDocumentConverter *converter, const QString& configName, QObject *parent, const QVariantList &args); virtual ~TextDocumentGenerator(); // [INHERITED] load a document and fill up the pagesVector Document::OpenResult loadDocumentWithPassword( const QString & fileName, QVector & pagesVector, const QString &password ) override; // [INHERITED] perform actions on document / pages bool canGeneratePixmap() const override; void generatePixmap( Okular::PixmapRequest * request ) override; // [INHERITED] print document using already configured QPrinter bool print( QPrinter& printer ) override; // [INHERITED] text exporting Okular::ExportFormat::List exportFormats() const override; bool exportTo( const QString &fileName, const Okular::ExportFormat &format ) override; // [INHERITED] config interface /// By default checks if the default font has changed or not bool reparseConfig() override; /// Does nothing by default. You need to reimplement it in your generator void addPages( KConfigDialog* dlg ) override; /** * Config skeleton for TextDocumentSettingsWidget * * You must use new construtor to initialize TextDocumentSettings, * that contain @p configName . * * @since 0.17 (KDE 4.11) */ TextDocumentSettings* generalSettings(); Okular::DocumentInfo generateDocumentInfo( const QSet &keys ) const override; const Okular::DocumentSynopsis* generateDocumentSynopsis() override; protected: bool doCloseDocument() override; Okular::TextPage* textPage( Okular::TextRequest *request ) override; + /* @since 1.8 */ + TextDocumentConverter* converter(); + + /* @since 1.8 */ + void setTextDocument( QTextDocument *textDocument ); + private: Q_DECLARE_PRIVATE( TextDocumentGenerator ) Q_DISABLE_COPY( TextDocumentGenerator ) Q_PRIVATE_SLOT( d_func(), void addAction( Action*, int, int ) ) Q_PRIVATE_SLOT( d_func(), void addAnnotation( Annotation*, int, int ) ) Q_PRIVATE_SLOT( d_func(), void addTitle( int, const QString&, const QTextBlock& ) ) Q_PRIVATE_SLOT( d_func(), void addMetaData( const QString&, const QString&, const QString& ) ) Q_PRIVATE_SLOT( d_func(), void addMetaData( DocumentInfo::Key, const QString& ) ) }; } #endif diff --git a/generators/markdown/converter.cpp b/generators/markdown/converter.cpp index ec7d1ef14..5c2da2fbc 100644 --- a/generators/markdown/converter.cpp +++ b/generators/markdown/converter.cpp @@ -1,168 +1,191 @@ /*************************************************************************** * Copyright (C) 2017 by Julian Wolff * * * * 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 "generator_md.h" + #include #include #include #include #include #include #include #include "debug_md.h" extern "C" { #include } // older versions of discount might not have these flags. // defining them to 0 allows us to convert without them #ifndef MKD_FENCEDCODE #define MKD_FENCEDCODE 0 #endif #ifndef MKD_GITHUBTAGS #define MKD_GITHUBTAGS 0 #endif #ifndef MKD_AUTOLINK #define MKD_AUTOLINK 0 #endif - + using namespace Markdown; Converter::Converter() + : m_markdownFile( nullptr ) { } Converter::~Converter() { + if ( m_markdownFile ) + { + fclose( m_markdownFile ); + } } QTextDocument* Converter::convert( const QString &fileName ) { - FILE *markdownFile = fopen( fileName.toLocal8Bit(), "rb" ); - if ( !markdownFile ) { + m_markdownFile = fopen( fileName.toLocal8Bit(), "rb" ); + if ( !m_markdownFile ) { emit error( i18n( "Failed to open the document" ), -1 ); return nullptr; } - const QDir dir = QDir( fileName.left( fileName.lastIndexOf( '/' ) ) ); - - MMIOT *markdownHandle = mkd_in( markdownFile, 0 ); + m_fileDir = QDir( fileName.left( fileName.lastIndexOf( '/' ) ) ); + + QTextDocument *doc = convertOpenFile(); + extractLinks( doc->rootFrame() ); + return doc; +} + +void Converter::convertAgain() +{ + setDocument( convertOpenFile() ); +} + +QTextDocument *Converter::convertOpenFile() +{ + rewind( m_markdownFile ); + + MMIOT *markdownHandle = mkd_in( m_markdownFile, 0 ); - if ( !mkd_compile( markdownHandle, MKD_FENCEDCODE | MKD_GITHUBTAGS | MKD_AUTOLINK ) ) { + int flags = MKD_FENCEDCODE | MKD_GITHUBTAGS | MKD_AUTOLINK; + if (!MarkdownGenerator::isFancyPantsEnabled()) + flags |= MKD_NOPANTS; + if ( !mkd_compile( markdownHandle, flags ) ) { emit error( i18n( "Failed to compile the Markdown document." ), -1 ); return 0; } char *htmlDocument; const int size = mkd_document( markdownHandle, &htmlDocument ); - + const QString html = QString::fromUtf8( htmlDocument, size ); QTextDocument *textDocument = new QTextDocument; textDocument->setPageSize( QSizeF( 980, 1307 ) ); textDocument->setHtml( html ); textDocument->setDefaultFont( generator()->generalSettings()->font() ); mkd_cleanup( markdownHandle ); QTextFrameFormat frameFormat; frameFormat.setMargin( 45 ); QTextFrame *rootFrame = textDocument->rootFrame(); rootFrame->setFrameFormat( frameFormat ); - convertLinks( rootFrame ); - convertImages( rootFrame, dir, textDocument ); + convertImages( rootFrame, m_fileDir, textDocument ); return textDocument; } -void Converter::convertLinks(QTextFrame * parent) +void Converter::extractLinks(QTextFrame * parent) { for ( QTextFrame::iterator it = parent->begin(); !it.atEnd(); ++it ) { QTextFrame *textFrame = it.currentFrame(); const QTextBlock textBlock = it.currentBlock(); if ( textFrame ) { - convertLinks(textFrame); + extractLinks(textFrame); } else if ( textBlock.isValid() ) { - convertLinks(textBlock); + extractLinks(textBlock); } } } -void Converter::convertLinks(const QTextBlock & parent) +void Converter::extractLinks(const QTextBlock & parent) { for ( QTextBlock::iterator it = parent.begin(); !it.atEnd(); ++it ) { const QTextFragment textFragment = it.fragment(); if ( textFragment.isValid() ) { const QTextCharFormat textCharFormat = textFragment.charFormat(); if ( textCharFormat.isAnchor() ) { Okular::BrowseAction *action = new Okular::BrowseAction( QUrl( textCharFormat.anchorHref() ) ); emit addAction( action, textFragment.position(), textFragment.position()+textFragment.length() ); } } } } void Converter::convertImages(QTextFrame * parent, const QDir &dir, QTextDocument *textDocument) { for ( QTextFrame::iterator it = parent->begin(); !it.atEnd(); ++it ) { QTextFrame *textFrame = it.currentFrame(); const QTextBlock textBlock = it.currentBlock(); if ( textFrame ) { convertImages(textFrame, dir, textDocument); } else if ( textBlock.isValid() ) { convertImages(textBlock, dir, textDocument); } } } void Converter::convertImages(const QTextBlock & parent, const QDir &dir, QTextDocument *textDocument) { for ( QTextBlock::iterator it = parent.begin(); !it.atEnd(); ++it ) { const QTextFragment textFragment = it.fragment(); if ( textFragment.isValid() ) { const QTextCharFormat textCharFormat = textFragment.charFormat(); if( textCharFormat.isImageFormat() ) { //TODO: Show images from http URIs QTextImageFormat format; format.setName( QDir::cleanPath( dir.absoluteFilePath( textCharFormat.toImageFormat().name() ) ) ); const QImage img = QImage( format.name() ); if ( img.width() > 890 ) { format.setWidth( 890 ); format.setHeight( img.height() * 890. / img.width() ); } else { format.setWidth( img.width() ); format.setHeight( img.height() ); } QTextCursor cursor( textDocument ); cursor.setPosition( textFragment.position(), QTextCursor::MoveAnchor ); cursor.setPosition( textFragment.position() + textFragment.length(), QTextCursor::KeepAnchor ); cursor.removeSelectedText(); cursor.insertImage( format ); } } } } diff --git a/generators/markdown/converter.h b/generators/markdown/converter.h index 860f1cbd7..46a24c670 100644 --- a/generators/markdown/converter.h +++ b/generators/markdown/converter.h @@ -1,40 +1,48 @@ /*************************************************************************** * Copyright (C) 2017 by Julian Wolff * * * * 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 MARKDOWN_CONVERTER_H #define MARKDOWN_CONVERTER_H #include -class QDir; +#include + class QTextBlock; class QTextFrame; namespace Markdown { class Converter : public Okular::TextDocumentConverter { Q_OBJECT public: Converter(); ~Converter(); QTextDocument *convert( const QString &fileName ) override; + void convertAgain(); + + QTextDocument *convertOpenFile(); + private: - void convertLinks(QTextFrame *parent); - void convertLinks(const QTextBlock& parent); + void extractLinks(QTextFrame *parent); + void extractLinks(const QTextBlock& parent); void convertImages(QTextFrame *parent, const QDir &dir, QTextDocument *textDocument); void convertImages(const QTextBlock& parent, const QDir &dir, QTextDocument *textDocument); + + FILE *m_markdownFile; + QDir m_fileDir; }; } #endif diff --git a/generators/markdown/generator_md.cpp b/generators/markdown/generator_md.cpp index ff7f1da34..32664b7b3 100644 --- a/generators/markdown/generator_md.cpp +++ b/generators/markdown/generator_md.cpp @@ -1,37 +1,69 @@ /*************************************************************************** * Copyright (C) 2017 by Julian Wolff * * * * 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_md.h" #include "converter.h" #include "debug_md.h" +#include "mdsettings.h" #include #include #include +#include + OKULAR_EXPORT_PLUGIN(MarkdownGenerator, "libokularGenerator_md.json") +bool MarkdownGenerator::s_isFancyPantsEnabled = true; +bool MarkdownGenerator::s_wasFancyPantsEnabled = true; + MarkdownGenerator::MarkdownGenerator( QObject *parent, const QVariantList &args ) : Okular::TextDocumentGenerator( new Markdown::Converter, QStringLiteral("okular_markdown_generator_settings"), parent, args ) { + Okular::TextDocumentSettings *mdSettings = generalSettings(); + + mdSettings->addItemBool( "SmartyPants", s_isFancyPantsEnabled, true ); + mdSettings->load(); + s_wasFancyPantsEnabled = s_isFancyPantsEnabled; +} + +bool MarkdownGenerator::reparseConfig() +{ + const bool textDocumentGeneratorChangedConfig = Okular::TextDocumentGenerator::reparseConfig(); + + if (s_wasFancyPantsEnabled != s_isFancyPantsEnabled) { + s_wasFancyPantsEnabled = s_isFancyPantsEnabled; + + Markdown::Converter *c = static_cast( converter() ); + c->convertAgain(); + setTextDocument( c->document() ); + + return true; + } + + return textDocumentGeneratorChangedConfig; } void MarkdownGenerator::addPages( KConfigDialog* dlg ) { Okular::TextDocumentSettingsWidget *widget = new Okular::TextDocumentSettingsWidget(); + QCheckBox *enableSmartyPants = new QCheckBox( dlg ); + enableSmartyPants->setObjectName( QString::fromUtf8( "kcfg_SmartyPants" ) ); + widget->addRow( i18n("Enable SmartyPants formatting"), enableSmartyPants ); + dlg->addPage( widget, generalSettings(), i18n("Markdown"), QStringLiteral("text-markdown"), i18n("Markdown Backend Configuration") ); } Q_LOGGING_CATEGORY(OkularMdDebug, "org.kde.okular.generators.md", QtWarningMsg) #include "generator_md.moc" diff --git a/generators/markdown/generator_md.h b/generators/markdown/generator_md.h index 3ee0b3fc3..de562909e 100644 --- a/generators/markdown/generator_md.h +++ b/generators/markdown/generator_md.h @@ -1,27 +1,34 @@ /*************************************************************************** * Copyright (C) 2017 by Julian Wolff * * * * 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_MD_H_ #define _OKULAR_GENERATOR_MD_H_ #include class MarkdownGenerator : public Okular::TextDocumentGenerator { Q_OBJECT Q_INTERFACES( Okular::Generator ) public: MarkdownGenerator( QObject *parent, const QVariantList &args ); // [INHERITED] reparse configuration + bool reparseConfig() override; void addPages( KConfigDialog* dlg ) override; + + static bool isFancyPantsEnabled() { return s_isFancyPantsEnabled; } + + private: + static bool s_isFancyPantsEnabled; + static bool s_wasFancyPantsEnabled; }; #endif