diff --git a/src/vlivepreviewhelper.cpp b/src/vlivepreviewhelper.cpp index 499f860b..91f6ffd1 100644 --- a/src/vlivepreviewhelper.cpp +++ b/src/vlivepreviewhelper.cpp @@ -1,6 +1,5 @@ #include "vlivepreviewhelper.h" -#include #include #include "veditor.h" @@ -30,8 +29,6 @@ extern VMainWindow *g_mainWin; #define INDEX_MASK 0x00ffffffUL -#define SCALE_FACTOR_THRESHOLD 1.1 - CodeBlockPreviewInfo::CodeBlockPreviewInfo() { } @@ -41,33 +38,12 @@ CodeBlockPreviewInfo::CodeBlockPreviewInfo(const VCodeBlock &p_cb) { } -void CodeBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc, - const VCodeBlock &p_cb) -{ - m_codeBlock.updateNonContent(p_cb); - if (m_inplacePreview.isNull()) { - return; - } - - QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock); - if (block.isValid()) { - m_inplacePreview->m_startPos = block.position(); - m_inplacePreview->m_endPos = block.position() + block.length(); - m_inplacePreview->m_blockPos = block.position(); - m_inplacePreview->m_blockNumber = m_codeBlock.m_endBlock; - // Padding is not changed since content is not changed. - } else { - m_inplacePreview.clear(); - } -} - void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc, - qreal p_scaleFactor) + const QPixmap &p_image) { QTextBlock block = p_doc->findBlockByNumber(m_codeBlock.m_endBlock); if (block.isValid()) { - Qt::TransformationMode tMode = Qt::SmoothTransformation; VImageToPreview *preview = new VImageToPreview(); preview->m_startPos = block.position(); @@ -79,29 +55,7 @@ void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, preview->m_name = QString::number(getImageIndex()); preview->m_isBlock = true; - if (hasImageData()) { - if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) { - preview->m_image.loadFromData(m_imgData.toUtf8(), - m_imgFormat.toLocal8Bit().data()); - } else { - QPixmap tmpImg; - tmpImg.loadFromData(m_imgData.toUtf8(), - m_imgFormat.toLocal8Bit().data()); - preview->m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, tMode); - } - } else if (hasImageDataBa()) { - if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) { - preview->m_image.loadFromData(m_imgDataBa, - m_imgFormat.toLocal8Bit().data()); - } else { - QPixmap tmpImg; - tmpImg.loadFromData(m_imgDataBa, - m_imgFormat.toLocal8Bit().data()); - preview->m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, tMode); - } - } else { - preview->m_image = QPixmap(); - } + preview->m_image = p_image; m_inplacePreview.reset(preview); } else { @@ -110,6 +64,9 @@ void CodeBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, } +#define CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF 10 +#define CODE_BLOCK_IMAGE_CACHE_TIME_DIFF 5 + VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor, VDocument *p_document, QObject *p_parent) @@ -144,13 +101,26 @@ VLivePreviewHelper::VLivePreviewHelper(VEditor *p_editor, this, &VLivePreviewHelper::mathjaxPreviewResultReady); } -bool VLivePreviewHelper::isPreviewLang(const QString &p_lang) const +void VLivePreviewHelper::checkLang(const QString &p_lang, + bool &p_livePreview, + bool &p_inplacePreview) const { - return (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart")) - || (m_mermaidEnabled && p_lang == "mermaid") - || (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml") - || (m_graphvizEnabled && p_lang == "dot") - || (m_mathjaxEnabled && p_lang == "mathjax"); + if (m_flowchartEnabled && (p_lang == "flow" || p_lang == "flowchart")) { + p_livePreview = p_inplacePreview = true; + } else if (m_plantUMLMode != PlantUMLMode::DisablePlantUML && p_lang == "puml") { + p_livePreview = true; + p_inplacePreview = m_plantUMLMode == PlantUMLMode::LocalPlantUML; + } else if (m_graphvizEnabled && p_lang == "dot") { + p_livePreview = p_inplacePreview = true; + } else if (m_mermaidEnabled && p_lang == "mermaid") { + p_livePreview = true; + p_inplacePreview = false; + } else if (m_mathjaxEnabled && p_lang == "mathjax") { + p_livePreview = false; + p_inplacePreview = true; + } else { + p_livePreview = p_inplacePreview = false; + } } void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlocks) @@ -164,34 +134,42 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock int lastIndex = m_cbIndex; m_cbIndex = -1; int cursorBlock = m_editor->textCursorW().block().blockNumber(); - int idx = 0; bool needUpdate = m_livePreviewEnabled; bool manualInplacePreview = m_inplacePreviewEnabled; - for (auto const & vcb : p_codeBlocks) { - if (!isPreviewLang(vcb.m_lang)) { + m_codeBlocks.clear(); + + for (int i = 0; i < p_codeBlocks.size(); ++i) { + const VCodeBlock &vcb = p_codeBlocks[i]; + bool livePreview = false, inplacePreview = false; + checkLang(vcb.m_lang, livePreview, inplacePreview); + if (!livePreview && !inplacePreview) { continue; } + const QString &text = vcb.m_text; bool cached = false; - if (idx < m_codeBlocks.size()) { - CodeBlockPreviewInfo &cb = m_codeBlocks[idx]; - if (cb.codeBlock().equalContent(vcb)) { - cb.updateNonContent(m_doc, vcb); - cached = true; - } else { - cb.setCodeBlock(vcb); - } - } else { - m_codeBlocks.append(CodeBlockPreviewInfo(vcb)); + + m_codeBlocks.append(CodeBlockPreviewInfo(vcb)); + int idx = m_codeBlocks.size() - 1; + + auto it = m_cache.find(text); + if (it != m_cache.end()) { + QSharedPointer &entry = it.value(); + entry->m_ts = m_timeStamp; + cached = true; + m_codeBlocks[idx].setImageData(entry->m_imgFormat, entry->m_imgData); + m_codeBlocks[idx].updateInplacePreview(m_editor, m_doc, entry->m_image); } if (m_inplacePreviewEnabled + && inplacePreview && (!cached || !m_codeBlocks[idx].inplacePreviewReady())) { - processForInplacePreview(idx); manualInplacePreview = false; + processForInplacePreview(idx); } if (m_livePreviewEnabled + && livePreview && vcb.m_startBlock <= cursorBlock && vcb.m_endBlock >= cursorBlock) { if (lastIndex == idx && cached) { @@ -200,14 +178,6 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock m_cbIndex = idx; } - - ++idx; - } - - if (idx == m_codeBlocks.size()) { - manualInplacePreview = false; - } else { - m_codeBlocks.resize(idx); } if (manualInplacePreview) { @@ -217,6 +187,8 @@ void VLivePreviewHelper::updateCodeBlocks(const QVector &p_codeBlock if (needUpdate) { updateLivePreview(); } + + clearObsoleteCache(); } void VLivePreviewHelper::handleCursorPositionChanged() @@ -319,6 +291,7 @@ void VLivePreviewHelper::setLivePreviewEnabled(bool p_enabled) if (!m_inplacePreviewEnabled) { m_codeBlocks.clear(); + m_cache.clear(); updateInplacePreview(); } } @@ -333,6 +306,7 @@ void VLivePreviewHelper::setInplacePreviewEnabled(bool p_enabled) m_inplacePreviewEnabled = p_enabled; if (!m_inplacePreviewEnabled && !m_livePreviewEnabled) { m_codeBlocks.clear(); + m_cache.clear(); } updateInplacePreview(); @@ -371,7 +345,17 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id, } CodeBlockPreviewInfo &cb = m_codeBlocks[idx]; + const QString &text = cb.codeBlock().m_text; + + QSharedPointer entry(new CodeBlockImageCacheEntry(p_timeStamp, + p_format, + p_result, + getScaleFactor(cb))); + m_cache.insert(text, entry); + cb.setImageData(p_format, p_result); + cb.updateInplacePreview(m_editor, m_doc, entry->m_image); + if (livePreview) { if (idx != m_cbIndex) { return; @@ -380,8 +364,6 @@ void VLivePreviewHelper::localAsyncResultReady(int p_id, m_document->setPreviewContent(lang, p_result); } else { // Inplace preview. - cb.updateInplacePreview(m_editor, m_doc, getScaleFactor(cb)); - updateInplacePreview(); } } @@ -390,7 +372,7 @@ void VLivePreviewHelper::processForInplacePreview(int p_idx) { CodeBlockPreviewInfo &cb = m_codeBlocks[p_idx]; const VCodeBlock &vcb = cb.codeBlock(); - Q_ASSERT(!(cb.hasImageData() || cb.hasImageDataBa())); + Q_ASSERT(!cb.hasImageData()); if (vcb.m_lang == "dot") { if (!m_graphvizHelper) { m_graphvizHelper = new VGraphvizHelper(this); @@ -474,7 +456,31 @@ void VLivePreviewHelper::mathjaxPreviewResultReady(int p_identitifer, } CodeBlockPreviewInfo &cb = m_codeBlocks[p_id]; - cb.setImageDataBa(p_format, p_data); - cb.updateInplacePreview(m_editor, m_doc, getScaleFactor(cb)); + const QString &text = cb.codeBlock().m_text; + + QSharedPointer entry(new CodeBlockImageCacheEntry(p_timeStamp, + p_format, + p_data, + getScaleFactor(cb))); + m_cache.insert(text, entry); + + cb.updateInplacePreview(m_editor, m_doc, entry->m_image); + updateInplacePreview(); } + +void VLivePreviewHelper::clearObsoleteCache() +{ + if (m_cache.size() - m_codeBlocks.size() <= CODE_BLOCK_IMAGE_CACHE_SIZE_DIFF) { + return; + } + + for (auto it = m_cache.begin(); it != m_cache.end();) { + if (m_timeStamp - it.value()->m_ts > CODE_BLOCK_IMAGE_CACHE_TIME_DIFF) { + it.value().clear(); + it = m_cache.erase(it); + } else { + ++it; + } + } +} diff --git a/src/vlivepreviewhelper.h b/src/vlivepreviewhelper.h index 0c3fcfc8..5fd95db7 100644 --- a/src/vlivepreviewhelper.h +++ b/src/vlivepreviewhelper.h @@ -21,19 +21,9 @@ public: explicit CodeBlockPreviewInfo(const VCodeBlock &p_cb); - void clearImageData() - { - m_imgData.clear(); - m_imgDataBa.clear(); - m_inplacePreview.clear(); - } - - void updateNonContent(const QTextDocument *p_doc, - const VCodeBlock &p_cb); - void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc, - qreal p_scaleFactor); + const QPixmap &p_image); VCodeBlock &codeBlock() { @@ -45,13 +35,6 @@ public: return m_codeBlock; } - void setCodeBlock(const VCodeBlock &p_cb) - { - m_codeBlock = p_cb; - - clearImageData(); - } - bool inplacePreviewReady() const { return !m_inplacePreview.isNull(); @@ -67,16 +50,6 @@ public: return m_imgData; } - bool hasImageDataBa() const - { - return !m_imgDataBa.isEmpty(); - } - - const QByteArray &imageDataBa() const - { - return m_imgDataBa; - } - const QString &imageFormat() const { return m_imgFormat; @@ -84,22 +57,10 @@ public: void setImageData(const QString &p_format, const QString &p_data) { - m_imgDataBa.clear(); - m_inplacePreview.clear(); - m_imgFormat = p_format; m_imgData = p_data; } - void setImageDataBa(const QString &p_format, const QByteArray &p_data) - { - m_imgData.clear(); - m_inplacePreview.clear(); - - m_imgFormat = p_format; - m_imgDataBa = p_data; - } - const QSharedPointer inplacePreview() const { return m_inplacePreview; @@ -116,8 +77,6 @@ private: QString m_imgData; - QByteArray m_imgDataBa; - QString m_imgFormat; QSharedPointer m_inplacePreview; @@ -161,7 +120,79 @@ private slots: const QByteArray &p_data); private: - bool isPreviewLang(const QString &p_lang) const; + struct CodeBlockImageCacheEntry + { + #define SCALE_FACTOR_THRESHOLD 1.1 + + CodeBlockImageCacheEntry() + : m_ts(0) + { + } + + CodeBlockImageCacheEntry(TimeStamp p_ts, + const QString &p_format, + const QByteArray &p_data, + qreal p_scaleFactor) + : m_ts(p_ts) + { + if (!p_data.isEmpty()) { + if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) { + m_image.loadFromData(p_data, + p_format.toLocal8Bit().data()); + } else { + QPixmap tmpImg; + tmpImg.loadFromData(p_data, + p_format.toLocal8Bit().data()); + m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, + Qt::SmoothTransformation); + } + } + } + + CodeBlockImageCacheEntry(TimeStamp p_ts, + const QString &p_format, + const QString &p_data, + qreal p_scaleFactor) + : m_ts(p_ts), + m_imgData(p_data), + m_imgFormat(p_format) + { + if (!p_data.isEmpty()) { + if (p_scaleFactor < SCALE_FACTOR_THRESHOLD) { + m_image.loadFromData(p_data.toUtf8(), + p_format.toLocal8Bit().data()); + } else { + QPixmap tmpImg; + tmpImg.loadFromData(p_data.toUtf8(), + p_format.toLocal8Bit().data()); + m_image = tmpImg.scaledToWidth(tmpImg.width() * p_scaleFactor, + Qt::SmoothTransformation); + } + } + } + + bool hasImageData() const + { + return !m_imgData.isEmpty(); + } + + bool hasImage() const + { + return !m_image.isNull(); + } + + TimeStamp m_ts; + + // For live preview. + QString m_imgData; + QString m_imgFormat; + + // For in-place preview. + QPixmap m_image; + }; + + + void checkLang(const QString &p_lang, bool &p_livePreview, bool &p_inplacePreview) const; // Get image data for this code block for inplace preview. void processForInplacePreview(int p_idx); @@ -171,6 +202,8 @@ private: qreal getScaleFactor(const CodeBlockPreviewInfo &p_cb); + void clearObsoleteCache(); + // Sorted by m_startBlock in ascending order. QVector m_codeBlocks; @@ -206,6 +239,9 @@ private: TimeStamp m_timeStamp; const qreal m_scaleFactor; + + // Indexed by content. + QHash> m_cache; }; inline bool VLivePreviewHelper::isPreviewEnabled() const diff --git a/src/vmathjaxinplacepreviewhelper.cpp b/src/vmathjaxinplacepreviewhelper.cpp index 1626773a..998abb4b 100644 --- a/src/vmathjaxinplacepreviewhelper.cpp +++ b/src/vmathjaxinplacepreviewhelper.cpp @@ -19,32 +19,9 @@ MathjaxBlockPreviewInfo::MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb) { } -void MathjaxBlockPreviewInfo::updateNonContent(const QTextDocument *p_doc, - const VEditor *p_editor, - const VMathjaxBlock &p_mb) -{ - m_mathjaxBlock.updateNonContent(p_mb); - if (m_inplacePreview.isNull()) { - return; - } - - QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber); - if (block.isValid()) { - m_inplacePreview->m_startPos = block.position() + m_mathjaxBlock.m_index; - m_inplacePreview->m_endPos = m_inplacePreview->m_startPos + m_mathjaxBlock.m_length; - m_inplacePreview->m_blockPos = block.position(); - m_inplacePreview->m_blockNumber = m_mathjaxBlock.m_blockNumber; - // Padding may changed. - m_inplacePreview->m_padding = VPreviewManager::calculateBlockMargin(block, - p_editor->tabStopWidthW()); - m_inplacePreview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock; - } else { - m_inplacePreview.clear(); - } -} - void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, - const QTextDocument *p_doc) + const QTextDocument *p_doc, + const QPixmap &p_image) { QTextBlock block = p_doc->findBlockByNumber(m_mathjaxBlock.m_blockNumber); if (block.isValid()) { @@ -59,12 +36,7 @@ void MathjaxBlockPreviewInfo::updateInplacePreview(const VEditor *p_editor, preview->m_name = QString::number(getImageIndex()); preview->m_isBlock = m_mathjaxBlock.m_previewedAsBlock; - if (hasImageDataBa()) { - preview->m_image.loadFromData(m_imgDataBa, - m_imgFormat.toLocal8Bit().data()); - } else { - preview->m_image = QPixmap(); - } + preview->m_image = p_image; m_inplacePreview.reset(preview); } else { @@ -133,13 +105,12 @@ void VMathJaxInplacePreviewHelper::updateMathjaxBlocks(const QVector &entry = it.value(); entry->m_ts = m_timeStamp; cached = true; - m_mathjaxBlocks[i].setImageDataBa(entry->m_imgFormat, entry->m_imgDataBa); - m_mathjaxBlocks[i].updateInplacePreview(m_editor, m_doc); + m_mathjaxBlocks.last().updateInplacePreview(m_editor, m_doc, entry->m_image); } - if (!cached || !m_mathjaxBlocks[i].inplacePreviewReady()) { + if (!cached || !m_mathjaxBlocks.last().inplacePreviewReady()) { manualUpdate = false; - processForInplacePreview(i); + processForInplacePreview(m_mathjaxBlocks.size() - 1); } } @@ -222,14 +193,12 @@ void VMathJaxInplacePreviewHelper::mathjaxPreviewResultReady(int p_identitifer, } MathjaxBlockPreviewInfo &mb = m_mathjaxBlocks[p_id]; - mb.setImageDataBa(p_format, p_data); - mb.updateInplacePreview(m_editor, m_doc); - // Update the cache. QSharedPointer entry(new MathjaxImageCacheEntry(p_timeStamp, p_data, p_format)); m_cache.insert(mb.mathjaxBlock().m_text, entry); + mb.updateInplacePreview(m_editor, m_doc, entry->m_image); updateInplacePreview(); } diff --git a/src/vmathjaxinplacepreviewhelper.h b/src/vmathjaxinplacepreviewhelper.h index 75f49175..53b08cbf 100644 --- a/src/vmathjaxinplacepreviewhelper.h +++ b/src/vmathjaxinplacepreviewhelper.h @@ -19,17 +19,9 @@ public: explicit MathjaxBlockPreviewInfo(const VMathjaxBlock &p_mb); - void clearImageData() - { - m_imgDataBa.clear(); - m_inplacePreview.clear(); - } - - void updateNonContent(const QTextDocument *p_doc, - const VEditor *p_editor, - const VMathjaxBlock &p_mb); - - void updateInplacePreview(const VEditor *p_editor, const QTextDocument *p_doc); + void updateInplacePreview(const VEditor *p_editor, + const QTextDocument *p_doc, + const QPixmap &p_image); VMathjaxBlock &mathjaxBlock() { @@ -41,29 +33,11 @@ public: return m_mathjaxBlock; } - void setMathjaxBlock(const VMathjaxBlock &p_mb) - { - m_mathjaxBlock = p_mb; - clearImageData(); - } - bool inplacePreviewReady() const { return !m_inplacePreview.isNull(); } - void setImageDataBa(const QString &p_format, const QByteArray &p_data) - { - m_imgFormat = p_format; - m_imgDataBa = p_data; - m_inplacePreview.clear(); - } - - bool hasImageDataBa() const - { - return !m_imgDataBa.isEmpty(); - } - const QSharedPointer inplacePreview() const { return m_inplacePreview; @@ -78,10 +52,6 @@ private: VMathjaxBlock m_mathjaxBlock; - QByteArray m_imgDataBa; - - QString m_imgFormat; - QSharedPointer m_inplacePreview; }; @@ -124,15 +94,15 @@ private: MathjaxImageCacheEntry(TimeStamp p_ts, const QByteArray &p_dataBa, const QString &p_format) - : m_ts(p_ts), - m_imgDataBa(p_dataBa), - m_imgFormat(p_format) + : m_ts(p_ts) { + if (!p_dataBa.isEmpty()) { + m_image.loadFromData(p_dataBa, p_format.toLocal8Bit().data()); + } } TimeStamp m_ts; - QByteArray m_imgDataBa; - QString m_imgFormat; + QPixmap m_image; };