diff --git a/OpenBoard.pro b/OpenBoard.pro index be78285b..7674b5de 100644 --- a/OpenBoard.pro +++ b/OpenBoard.pro @@ -9,9 +9,9 @@ CONFIG += debug_and_release \ VERSION_MAJ = 1 VERSION_MIN = 6 -VERSION_PATCH = 0 -VERSION_TYPE = r # a = alpha, b = beta, rc = release candidate, r = release, other => error -VERSION_BUILD = 0 +VERSION_PATCH = 1 +VERSION_TYPE = a # a = alpha, b = beta, rc = release candidate, r = release, other => error +VERSION_BUILD = 1116 VERSION = "$${VERSION_MAJ}.$${VERSION_MIN}.$${VERSION_PATCH}-$${VERSION_TYPE}.$${VERSION_BUILD}" diff --git a/release_scripts/linux/package.sh b/release_scripts/linux/package.sh index f1f139e7..f3b35afe 100755 --- a/release_scripts/linux/package.sh +++ b/release_scripts/linux/package.sh @@ -358,7 +358,9 @@ for ((i=0;i<${#tab[@]};i++)); do if [[ "${tab[$i]}" == *"libavcodec"* ]]; then depName="${tab[$i]::-2}" versionNumber="${tab[$i]: -2}" - echo -n "${depName}${versionNumber} (>= ${depdVer}) | ${depName}-extra${versionNumber} (>= ${depdVer})" >> "$CONTROL_FILE" + depdVer_part1=`echo ${depdVer} | awk -F'.' '{print $1}'` + depdVer_part2=`echo ${depdVer} | awk -F'.' '{print $2}'` + echo -n "${depName}${versionNumber} (>= ${depdVer_part1}.${depdVer_part2}) | ${depName}-extra${versionNumber} (>= ${depdVer_part1}.${depdVer_part2})" >> "$CONTROL_FILE" else echo -n "${tab[$i]} (>= ${depdVer})" >> "$CONTROL_FILE" fi diff --git a/release_scripts/osx/release.macx.sh b/release_scripts/osx/release.macx.sh index 1e602c7f..02b152f5 100755 --- a/release_scripts/osx/release.macx.sh +++ b/release_scripts/osx/release.macx.sh @@ -19,7 +19,7 @@ PROJECT_ROOT="$SCRIPT_PATH/../.." APPLICATION_NAME="OpenBoard" -BASE_QT_DIR=~/Qt/5.13.1/clang_64 +BASE_QT_DIR=~/Qt/5.14.2/clang_64 # Executables QMAKE=$BASE_QT_DIR/bin/qmake MACDEPLOYQT=$BASE_QT_DIR/bin/macdeployqt @@ -203,25 +203,25 @@ cd - # make sure libs installed via homebrew 2.0 refer to in-app libs notify "relinking libs ..." # libavformat -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libavcodec.58.dylib @executable_path/../Frameworks/libavcodec.58.dylib -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib -install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib +install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavcodec.58.dylib @executable_path/../Frameworks/libavcodec.58.dylib +install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib +install_name_tool "$APP/Contents/Frameworks/libavformat.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib # libavcodec -install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib -install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib +install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libswresample.3.dylib @executable_path/../Frameworks/libswresample.3.dylib +install_name_tool "$APP/Contents/Frameworks/libavcodec.58.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib #libswresample -install_name_tool "$APP/Contents/Frameworks/libswresample.3.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib +install_name_tool "$APP/Contents/Frameworks/libswresample.3.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib #libswscale -install_name_tool "$APP/Contents/Frameworks/libswscale.5.dylib" -change /usr/local/Cellar/ffmpeg/4.2.1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib +install_name_tool "$APP/Contents/Frameworks/libswscale.5.dylib" -change /usr/local/Cellar/ffmpeg/4.3.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib # libhogweed -install_name_tool "$APP/Contents/Frameworks/libhogweed.4.dylib" -change /usr/local/Cellar/nettle/3.4.1/lib/libnettle.6.dylib @executable_path/../Frameworks/libnettle.6.dylib +install_name_tool "$APP/Contents/Frameworks/libhogweed.6.dylib" -change /usr/local/Cellar/nettle/3.6/lib/libnettle.8.dylib @executable_path/../Frameworks/libnettle.8.dylib # libssl -install_name_tool "$APP/Contents/Frameworks/libssl.1.1.dylib" -change /usr/local/Cellar/openssl@1.1/1.1.1d/lib/libcrypto.1.1.dylib @executable_path/../Frameworks/libcrypto.1.1.dylib +install_name_tool "$APP/Contents/Frameworks/libssl.1.1.dylib" -change /usr/local/Cellar/openssl@1.1/1.1.1h/lib/libcrypto.1.1.dylib @executable_path/../Frameworks/libcrypto.1.1.dylib # libvorbis install_name_tool "$APP/Contents/Frameworks/libvorbisenc.2.dylib" -change /usr/local/Cellar/libvorbis/1.3.6/lib/libvorbis.0.dylib @executable_path/../Frameworks/libvorbis.0.dylib diff --git a/release_scripts/windows/OpenBoard.iss b/release_scripts/windows/OpenBoard.iss index 7a1a36e1..a64559c0 100644 --- a/release_scripts/windows/OpenBoard.iss +++ b/release_scripts/windows/OpenBoard.iss @@ -56,7 +56,8 @@ Type: files ; Name: "{app}\*.dll" #define QtDir GetEnv('QT_DIR') [Files] -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\microsoft\vcredist_x86.exe"; DestDir:"{tmp}" +Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\microsoft\vcredist_2013.x64.exe"; DestDir:"{tmp}" +Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\microsoft\vcredist_2015_2019.x64.exe"; DestDir:"{tmp}" Source: "{#ProjectRoot}\build\win32\release\product\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ;OpenSSL @@ -87,6 +88,12 @@ Source: "{#QtLibs}\libGLESv2.dll"; DestDir: "{app}" Source: "{#QtLibs}\Qt5Quick.dll"; DestDir: "{app}" Source: "{#QtLibs}\Qt5Positioning.dll"; DestDir: "{app}" Source: "{#QtLibs}\Qt5Sensors.dll"; DestDir: "{app}" +Source: "{#QtLibs}\icuuc65.dll"; DestDir: "{app}" +Source: "{#QtLibs}\icuin65.dll"; DestDir: "{app}" +Source: "{#QtLibs}\icudt65.dll"; DestDir: "{app}" +Source: "{#QtLibs}\libxslt.dll"; DestDir: "{app}" +Source: "{#QtLibs}\libxml2.dll"; DestDir: "{app}" +Source: "{#QtLibs}\Qt5QmlModels.dll"; DestDir: "{app}" Source: "{#QtLibs}\Qt5WebChannel.dll"; DestDir: "{app}" Source: "{#QtLibs}\libEGL.dll"; DestDir: "{app}" ;Source: "/etc/freezedWidgetWrapper.html"; DestDir: "{app}" @@ -94,12 +101,6 @@ Source: "{#QtLibs}\libEGL.dll"; DestDir: "{app}" Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\zlib\1.2.11\bin\zlib.dll"; DestDir:"{app}"; Flags: ignoreversion -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\qtwebkit\bin\icudt64.dll"; DestDir: "{app}" -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\qtwebkit\bin\icuin64.dll"; DestDir: "{app}" -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\qtwebkit\bin\icuuc64.dll"; DestDir: "{app}" -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\qtwebkit\bin\libxml2.dll"; DestDir: "{app}" -Source: "{#ProjectRoot}\..\OpenBoard-ThirdParty\qtwebkit\bin\libxslt.dll"; DestDir: "{app}" - ; NOTE: Don't use "Flags: ignoreversion" on any shared system files ;Qt windows plugins @@ -166,7 +167,8 @@ Root: HKLM64; Subkey: "SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Low Righ Root: HKLM64; Subkey: "SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Low Rights\DragDrop\{{E63D17F8-D9DA-479D-B9B5-0D101A03703B}"; ValueType: string; ValueName: "AppPath"; ValueData: "{app}"; Flags: uninsdeletevalue; Check: isProcessorX64 [Run] -Filename: "{tmp}\vcredist_x86.exe";WorkingDir:"{tmp}"; Parameters: "/q /norestart"; StatusMsg: Installing CRT... +Filename: "{tmp}\vcredist_2013.x64.exe";WorkingDir:"{tmp}"; Parameters: "/norestart"; StatusMsg: Installing CRT 2013... +Filename: "{tmp}\vcredist_2015_2019.x64.exe";WorkingDir:"{tmp}"; Parameters: "/norestart"; StatusMsg: Installing CRT 2015-2019 ... Filename: "{app}\OpenBoard.exe"; Description: "{cm:LaunchProgram,OpenBoard}"; Flags: nowait postinstall skipifsilent [UninstallDelete] diff --git a/release_scripts/windows/release.win7.vc9.bat b/release_scripts/windows/release.win7.vc9.bat index 47f344d7..3682b9ff 100644 --- a/release_scripts/windows/release.win7.vc9.bat +++ b/release_scripts/windows/release.win7.vc9.bat @@ -19,7 +19,7 @@ set SCRIPT_PATH=%~dp0 set PROJECT_ROOT=%SCRIPT_PATH%\..\.. set APPLICATION_NAME=OpenBoard -set QT_DIR=C:\Qt\5.13.2\msvc2017_64 +set QT_DIR=C:\Qt\5.14.2\msvc2017_64 set QT_BIN=%QT_DIR%\bin set PROGRAMS_FILE_PATH=C:\Program Files (x86) diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index 06c81ef6..b0665b82 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -123,6 +123,7 @@ RefreshRateInFramePerSecond=2 Margin=20 PageFormat=A4 Resolution=300 +ZoomBehavior=4 [Podcast] AudioRecordingDevice=Default diff --git a/src/adaptors/UBExportPDF.cpp b/src/adaptors/UBExportPDF.cpp index 30834b9a..177f716d 100644 --- a/src/adaptors/UBExportPDF.cpp +++ b/src/adaptors/UBExportPDF.cpp @@ -113,7 +113,7 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin QSize pageSize = scene->sceneSize(); // set high res rendering - scene->setRenderingQuality(UBItem::RenderingQualityHigh); + scene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed); scene->setRenderingContext(UBGraphicsScene::NonScreen); // Setting output page size @@ -132,7 +132,7 @@ bool UBExportPDF::persistsDocument(UBDocumentProxy* pDocumentProxy, const QStrin // Restore screen rendering quality scene->setRenderingContext(UBGraphicsScene::Screen); - scene->setRenderingQuality(UBItem::RenderingQualityNormal); + scene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed); // Restore background state scene->setBackground(isDark, pageBackground); diff --git a/src/adaptors/UBImportDocumentSetAdaptor.cpp b/src/adaptors/UBImportDocumentSetAdaptor.cpp index cdc9250b..8d74bf93 100644 --- a/src/adaptors/UBImportDocumentSetAdaptor.cpp +++ b/src/adaptors/UBImportDocumentSetAdaptor.cpp @@ -94,11 +94,17 @@ QFileInfoList UBImportDocumentSetAdaptor::importData(const QString &zipFile, con foreach(QFileInfo readDir, tDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden , QDir::Name)) { QString newFileName = readDir.fileName(); - if (QFileInfo(destination + "/" + readDir.fileName()).exists()) { - newFileName = QFileInfo(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath(tmpDir)).fileName(); + if (QFileInfo(destination + "/" + newFileName).exists()) + { + //if the generateUniqueDocumentPath is called twice in the same millisecond, the destination files are overwritten + do + { + newFileName = QFileInfo(UBPersistenceManager::persistenceManager()->generateUniqueDocumentPath(tmpDir)).fileName(); + } while (QFileInfo(destination + "/" + newFileName).exists()); } + QString newFilePath = destination + "/" + newFileName; - if (UBFileSystemUtils::copy(readDir.absoluteFilePath(), newFilePath)) { + if (UBFileSystemUtils::copy(readDir.absoluteFilePath(), newFilePath, true)) { result.append(newFilePath); } } diff --git a/src/adaptors/UBThumbnailAdaptor.cpp b/src/adaptors/UBThumbnailAdaptor.cpp index e73973d8..b73f9e57 100644 --- a/src/adaptors/UBThumbnailAdaptor.cpp +++ b/src/adaptors/UBThumbnailAdaptor.cpp @@ -153,12 +153,12 @@ void UBThumbnailAdaptor::persistScene(UBDocumentProxy* proxy, UBGraphicsScene* p } pScene->setRenderingContext(UBGraphicsScene::NonScreen); - pScene->setRenderingQuality(UBItem::RenderingQualityHigh); + pScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed); pScene->render(&painter, imageRect, sceneRect, Qt::KeepAspectRatio); pScene->setRenderingContext(UBGraphicsScene::Screen); - pScene->setRenderingQuality(UBItem::RenderingQualityNormal); + pScene->setRenderingQuality(UBItem::RenderingQualityNormal, UBItem::CacheAllowed); thumb.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(fileName, "JPG"); } diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index 9d108bce..a301017d 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -779,6 +779,7 @@ void UBBoardController::deleteScene(int nIndex) scIndexes << nIndex; deletePages(scIndexes); selectedDocument()->setMetaData(UBSettings::documentUpdatedAt, UBStringUtils::toUtcIsoDateTime(QDateTime::currentDateTime())); + UBMetadataDcSubsetAdaptor::persist(selectedDocument()); if (nIndex >= pageCount()) nIndex = pageCount()-1; @@ -2178,13 +2179,13 @@ void UBBoardController::grabScene(const QRectF& pSceneRect) painter.setRenderHint(QPainter::Antialiasing); mActiveScene->setRenderingContext(UBGraphicsScene::NonScreen); - mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh); + mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheNotAllowed); mActiveScene->render(&painter, targetRect, pSceneRect); mActiveScene->setRenderingContext(UBGraphicsScene::Screen); // mActiveScene->setRenderingQuality(UBItem::RenderingQualityNormal); - mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh); + mActiveScene->setRenderingQuality(UBItem::RenderingQualityHigh, UBItem::CacheAllowed); mPaletteManager->addItem(QPixmap::fromImage(image)); diff --git a/src/core/UBPersistenceManager.cpp b/src/core/UBPersistenceManager.cpp index 753c8a2d..0c78f096 100644 --- a/src/core/UBPersistenceManager.cpp +++ b/src/core/UBPersistenceManager.cpp @@ -867,12 +867,12 @@ void UBPersistenceManager::insertDocumentSceneAt(UBDocumentProxy* proxy, UBGraph mSceneCache.insert(proxy, index, scene); + proxy->incPageCount(); + if (persist) { persistDocumentScene(proxy, scene, index); } - proxy->incPageCount(); - emit documentSceneCreated(proxy, index); } diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index b83d5f88..78be3c15 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -408,7 +408,7 @@ void UBSettings::init() pdfMargin = new UBSetting(this, "PDF", "Margin", "20"); pdfPageFormat = new UBSetting(this, "PDF", "PageFormat", "A4"); pdfResolution = new UBSetting(this, "PDF", "Resolution", "300"); - pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "1"); + pdfZoomBehavior = new UBSetting(this, "PDF", "ZoomBehavior", "4"); podcastFramesPerSecond = new UBSetting(this, "Podcast", "FramesPerSecond", 10); podcastVideoSize = new UBSetting(this, "Podcast", "VideoSize", "Medium"); diff --git a/src/document/UBDocumentController.cpp b/src/document/UBDocumentController.cpp index 03d676fb..bfb75bff 100644 --- a/src/document/UBDocumentController.cpp +++ b/src/document/UBDocumentController.cpp @@ -2786,6 +2786,8 @@ void UBDocumentController::importFile() if (fileInfo.suffix().toLower() == "ubx") { UBPersistenceManager::persistenceManager()->createDocumentProxiesStructure(docManager->importUbx(filePath, UBSettings::userDocumentDirectory()), true); + emit documentThumbnailsUpdated(this); // some documents might have been overwritten while not having the same page count + } else { UBSettings::settings()->lastImportFilePath->set(QVariant(fileInfo.absolutePath())); diff --git a/src/domain/UBGraphicsPDFItem.cpp b/src/domain/UBGraphicsPDFItem.cpp index b9fa19c0..e7161094 100644 --- a/src/domain/UBGraphicsPDFItem.cpp +++ b/src/domain/UBGraphicsPDFItem.cpp @@ -58,6 +58,16 @@ QVariant UBGraphicsPDFItem::itemChange(GraphicsItemChange change, const QVariant return GraphicsPDFItem::itemChange(change, newValue); } +void UBGraphicsPDFItem::updateChild() +{ + CacheMode prevCacheMode = cacheMode(); + + GraphicsPDFItem::update(); + + // Workaround: Necessary, otherwise only the control scene is updated, the display scene refresh is ignored for an unknown reason. + setCacheMode(prevCacheMode); +} + void UBGraphicsPDFItem::setUuid(const QUuid &pUuid) { UBItem::setUuid(pUuid); @@ -142,6 +152,11 @@ void UBGraphicsPDFItem::setRenderingQuality(RenderingQuality pRenderingQuality) } } +void UBGraphicsPDFItem::setCacheBehavior(UBItem::CacheBehavior cacheBehavior) +{ + UBItem::setCacheBehavior(cacheBehavior); + GraphicsPDFItem::setCacheAllowed(cacheBehavior == UBItem::CacheAllowed); +} UBGraphicsScene* UBGraphicsPDFItem::scene() { @@ -153,7 +168,7 @@ UBGraphicsPixmapItem* UBGraphicsPDFItem::toPixmapItem() const { QPixmap pixmap(mRenderer->pageSizeF(mPageNumber).toSize()); QPainter painter(&pixmap); - mRenderer->render(&painter, mPageNumber); + mRenderer->render(&painter, mPageNumber, false /* Cache allowed */); UBGraphicsPixmapItem *pixmapItem = new UBGraphicsPixmapItem(); pixmapItem->setPixmap(pixmap); diff --git a/src/domain/UBGraphicsPDFItem.h b/src/domain/UBGraphicsPDFItem.h index 3107cb2d..3d234c94 100644 --- a/src/domain/UBGraphicsPDFItem.h +++ b/src/domain/UBGraphicsPDFItem.h @@ -59,13 +59,14 @@ class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphic virtual void setRenderingQuality(RenderingQuality pRenderingQuality); + virtual void setCacheBehavior(CacheBehavior cacheBehavior); + virtual UBGraphicsScene* scene(); virtual UBGraphicsPixmapItem* toPixmapItem() const; virtual void clearSource(){;} virtual void setUuid(const QUuid &pUuid); - protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent *event); @@ -74,7 +75,9 @@ class UBGraphicsPDFItem: public GraphicsPDFItem, public UBItem, public UBGraphic virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); - + virtual void updateChild(); + private slots: + void OnRequireUpdate(); }; #endif /* UBGRAPHICSPDFITEM_H_ */ diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 27b0bf9f..a2a654a5 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -2395,7 +2395,7 @@ void UBGraphicsScene::addMask(const QPointF ¢er) curtain->setSelected(true); } -void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQuality) +void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior) { QListIterator itItems(mFastAccessItems); @@ -2408,6 +2408,7 @@ void UBGraphicsScene::setRenderingQuality(UBItem::RenderingQuality pRenderingQua if (ubItem) { ubItem->setRenderingQuality(pRenderingQuality); + ubItem->setCacheBehavior(cacheBehavior); } } } diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index 3c6d6751..4040af7e 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -295,7 +295,7 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem mViewState = pViewState; } - virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality); + virtual void setRenderingQuality(UBItem::RenderingQuality pRenderingQuality, UBItem::CacheBehavior cacheBehavior); QList relativeDependencies() const; diff --git a/src/domain/UBItem.h b/src/domain/UBItem.h index 063724f7..43629949 100644 --- a/src/domain/UBItem.h +++ b/src/domain/UBItem.h @@ -51,6 +51,13 @@ class UBItem RenderingQualityNormal = 0, RenderingQualityHigh }; + enum CacheBehavior + { + CacheNotAllowed, + CacheAllowed, + nbrCacheBehavior + }; + virtual QUuid uuid() const { return mUuid; @@ -71,6 +78,11 @@ class UBItem mRenderingQuality = pRenderingQuality; } + virtual void setCacheBehavior(CacheBehavior cacheBehavior) + { + mCacheBehavior = cacheBehavior; + } + virtual UBItem* deepCopy() const = 0; virtual void copyItemParameters(UBItem *copy) const = 0; @@ -98,6 +110,7 @@ class UBItem QUrl mSourceUrl; + CacheBehavior mCacheBehavior; }; class UBGraphicsItem diff --git a/src/frameworks/UBFileSystemUtils.cpp b/src/frameworks/UBFileSystemUtils.cpp index 59c72aad..6c5ae6d6 100644 --- a/src/frameworks/UBFileSystemUtils.cpp +++ b/src/frameworks/UBFileSystemUtils.cpp @@ -121,7 +121,7 @@ bool UBFileSystemUtils::copyFile(const QString &source, const QString &destinati bool UBFileSystemUtils::copy(const QString &source, const QString &destination, bool overwrite) { if (QFileInfo(source).isDir()) { - return copyDir(source, destination); + return copyDir(source, destination, overwrite); } else { return copyFile(source, destination, overwrite); } @@ -280,7 +280,7 @@ bool UBFileSystemUtils::deleteDir(const QString& pDirPath) } -bool UBFileSystemUtils::copyDir(const QString& pSourceDirPath, const QString& pTargetDirPath) +bool UBFileSystemUtils::copyDir(const QString& pSourceDirPath, const QString& pTargetDirPath, bool overwite) { if (pSourceDirPath == "" || pSourceDirPath == "." || pSourceDirPath == "..") return false; @@ -304,8 +304,7 @@ bool UBFileSystemUtils::copyDir(const QString& pSourceDirPath, const QString& pT } else { - QFile f(pSourceDirPath + "/" + dirContent.fileName()); - successSoFar = f.copy(pTargetDirPath + "/" + dirContent.fileName()); + successSoFar = copyFile(pSourceDirPath + "/" + dirContent.fileName(), pTargetDirPath + "/" + dirContent.fileName(), overwite); } } else diff --git a/src/frameworks/UBFileSystemUtils.h b/src/frameworks/UBFileSystemUtils.h index da74b069..a145177d 100644 --- a/src/frameworks/UBFileSystemUtils.h +++ b/src/frameworks/UBFileSystemUtils.h @@ -62,7 +62,7 @@ class UBFileSystemUtils : public QObject static bool deleteDir(const QString& pDirPath); - static bool copyDir(const QString& pSourceDirPath, const QString& pTargetDirPath); + static bool copyDir(const QString& pSourceDirPath, const QString& pTargetDirPath, bool overwrite = false); static bool moveDir(const QString& pSourceDirPath, const QString& pTargetDirPath); diff --git a/src/gui/UBDocumentNavigator.cpp b/src/gui/UBDocumentNavigator.cpp index b54d82c8..980e8276 100644 --- a/src/gui/UBDocumentNavigator.cpp +++ b/src/gui/UBDocumentNavigator.cpp @@ -358,7 +358,6 @@ void UBDocumentNavigator::mouseReleaseEvent(QMouseEvent *event) void UBDocumentNavigator::keyPressEvent(QKeyEvent *event) { - return; UBBoardController* controller = UBApplication::boardController; // send to the scene anyway QApplication::sendEvent (scene (), event); diff --git a/src/pdf/GraphicsPDFItem.cpp b/src/pdf/GraphicsPDFItem.cpp index 090d1142..110ad015 100644 --- a/src/pdf/GraphicsPDFItem.cpp +++ b/src/pdf/GraphicsPDFItem.cpp @@ -39,13 +39,16 @@ GraphicsPDFItem::GraphicsPDFItem(PDFRenderer *renderer, int pageNumber, QGraphic : QObject(0), QGraphicsItem(parentItem) , mRenderer(renderer) , mPageNumber(pageNumber) + , mIsCacheAllowed(true) { setCacheMode(QGraphicsItem::DeviceCoordinateCache); mRenderer->attach(); + connect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate())); } GraphicsPDFItem::~GraphicsPDFItem() { + disconnect(mRenderer, SIGNAL(signalUpdateParent()), this, SLOT(OnRequireUpdate())); mRenderer->detach(); } @@ -70,8 +73,15 @@ void GraphicsPDFItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o } if (option) - mRenderer->render(painter, mPageNumber, option->exposedRect); - else + { + mRenderer->render(painter, mPageNumber, mIsCacheAllowed, option->exposedRect); + } else qWarning("GraphicsPDFItem::paint: option is null, ignoring painting"); } + +void GraphicsPDFItem::OnRequireUpdate() +{ + updateChild(); +} + diff --git a/src/pdf/GraphicsPDFItem.h b/src/pdf/GraphicsPDFItem.h index a8cb91f2..5ec60808 100644 --- a/src/pdf/GraphicsPDFItem.h +++ b/src/pdf/GraphicsPDFItem.h @@ -51,10 +51,15 @@ class GraphicsPDFItem : public QObject, public QGraphicsItem int pageNumber() const { return mPageNumber; } QUuid fileUuid() const { return mRenderer->fileUuid(); } QByteArray fileData() const { return mRenderer->fileData(); } - + void setCacheAllowed(bool const value) { mIsCacheAllowed = value; } + virtual void updateChild() = 0; protected: PDFRenderer *mRenderer; int mPageNumber; + bool mIsCacheAllowed; + + private slots: + void OnRequireUpdate(); }; #endif // GRAPHICSPDFITEM_H diff --git a/src/pdf/PDFRenderer.h b/src/pdf/PDFRenderer.h index 994a7b09..8dd3f298 100644 --- a/src/pdf/PDFRenderer.h +++ b/src/pdf/PDFRenderer.h @@ -66,8 +66,7 @@ class PDFRenderer : public QObject void setDPI(int desiredDPI) { this->dpiForRendering = desiredDPI; } - public slots: - virtual void render(QPainter *p, int pageNumber, const QRectF &bounds = QRectF()) = 0; + virtual void render(QPainter *p, int pageNumber, bool const cacheAllowed, const QRectF &bounds = QRectF()) = 0; private: QAtomicInt mRefCount; diff --git a/src/pdf/UBWebPluginPDFWidget.cpp b/src/pdf/UBWebPluginPDFWidget.cpp index b7d71150..98c10655 100644 --- a/src/pdf/UBWebPluginPDFWidget.cpp +++ b/src/pdf/UBWebPluginPDFWidget.cpp @@ -152,7 +152,9 @@ void UBWebPluginPDFWidget::paintEvent(QPaintEvent *event) painter.translate((geometry().width() - (pageSize.width() * mScale)) / 2, 0); painter.scale(mScale, mScale); - mRenderer->render(&painter, mPageNumber, event->rect()); + // Note: If you ever want to use the cache, you need to make sure the object 'update' is called + // when the processing is completed. See 'signalUpdateParent'. + mRenderer->render(&painter, mPageNumber, false /* Cache allowed */, event->rect()); painter.setPen(QPen(Qt::gray, 1)); painter.drawRect(0, 0, pageSize.width(), pageSize.height()); } diff --git a/src/pdf/XPDFRenderer.cpp b/src/pdf/XPDFRenderer.cpp index 92a6e1e6..301406c5 100644 --- a/src/pdf/XPDFRenderer.cpp +++ b/src/pdf/XPDFRenderer.cpp @@ -43,26 +43,41 @@ QAtomicInt XPDFRenderer::sInstancesCount = 0; namespace constants{ - const double mode1_zoomFactor = 3.0; - const double mode2_zoomFactorStage1 = 2.5; - const double mode2_zoomFactorStage2 = 5.0; - const double mode2_zoomFactorStage3 = 10.0; + SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white } -XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) : - mpSplashBitmapHistorical(nullptr), mSplashHistorical(nullptr), mDocument(nullptr) +XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) + : m_pdfZoomMode(UBSettings::settings()->pdfZoomBehavior->get().toUInt()) + , mpSplashBitmapHistorical(nullptr) + , mSplashHistorical(nullptr) + , mDocument(nullptr) { - switch (UBSettings::settings()->pdfZoomBehavior->get().toUInt()) { + switch (m_pdfZoomMode) { case 0: // Render each time (historical initial implementation). break; case 1: // Render a single image, degradated quality when zoomed big. default: - m_pdfZoomCache.push_back(constants::mode1_zoomFactor); + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode1_zoomFactor); break; - case 2: // Render three images, optimal quality all the time. - m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage1); - m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage2); - m_pdfZoomCache.push_back(constants::mode2_zoomFactorStage3); + case 2: // Render three images, use downsampling, optimal quality all the time, slower. + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage1); + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage2); + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode2_zoomFactorStage3); + break; + case 3: // Do not downsample, minimal loss, faster. Not necessarily the expected result, + // because a 'zoom factor 1' here does not correspond to a user choice 'zoom factor 1'. + // The zoom requested is dependent on many factors, including the input pdf, the output screen resolution + // and the zoom user choice. Thus, the 'mode3_zoomFactorStage1' might be fine on one screen, but + // fuzzy on another one. + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode3_zoomFactorStage1); + m_pdfZoomCache.push_back(XPDFRendererZoomFactor::mode3_zoomFactorStage2); + break; + case 4: // Multithreaded, several steps, downsampled. + for (int i = 0; i < XPDFRendererZoomFactor::mode4_zoomFactorIterations; i++ ) + { + double const zoomValue = XPDFRendererZoomFactor::mode4_zoomFactorStart+XPDFRendererZoomFactor::mode4_zoomFactorStepSquare*static_cast(i*i); + m_pdfZoomCache.push_back(zoomValue); + } break; } @@ -84,18 +99,19 @@ XPDFRenderer::XPDFRenderer(const QString &filename, bool importingFile) : mDocument = new PDFDoc(new GooString(filename.toLocal8Bit()), 0, 0, 0); // the filename GString is deleted on PDFDoc desctruction #endif sInstancesCount.ref(); + connect(&m_cacheThread, SIGNAL(finished()), this, SLOT(OnThreadFinished())); } XPDFRenderer::~XPDFRenderer() { + disconnect(&m_cacheThread, SIGNAL(finished()), this, SLOT(OnThreadFinished())); + m_cacheThread.cancelPending(); + m_cacheThread.wait(); // Would crash if data deleted during processing. + for(int i = 0; i < m_pdfZoomCache.size(); i++) { PdfZoomCacheData &cacheData = m_pdfZoomCache[i]; - if(cacheData.splash != nullptr){ - cacheData.cachedImage = QImage(); // The 'cachedImage' uses a buffer from 'splash'. - delete cacheData.splash; - cacheData.splash = nullptr; - } + cacheData.cleanup(); } if(mSplashHistorical) @@ -209,10 +225,10 @@ QImage* XPDFRenderer::createPDFImageHistorical(int pageNumber, qreal xscale, qre { if (isValid()) { - SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white if(mSplashHistorical) delete mSplashHistorical; - mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); + + mSplashHistorical = new SplashOutputDev(splashModeRGB8, 1, false, constants::paperColor); #ifdef USE_XPDF mSplashHistorical->startDoc(mDocument->getXRef()); #else @@ -246,41 +262,120 @@ QImage* XPDFRenderer::createPDFImageHistorical(int pageNumber, qreal xscale, qre return new QImage(mpSplashBitmapHistorical->getDataPtr(), mpSplashBitmapHistorical->getWidth(), mpSplashBitmapHistorical->getHeight(), mpSplashBitmapHistorical->getWidth() * 3, QImage::Format_RGB888); } -void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds) +void XPDFRenderer::OnThreadFinished() +{ + emit signalUpdateParent(); + if (m_cacheThread.isJobPending()) + m_cacheThread.start(); +} + +void XPDFRenderer::render(QPainter *p, int pageNumber, bool const cacheAllowed, const QRectF &bounds) { + //qDebug() << "render enter"; Q_UNUSED(bounds); if (isValid()) { - if (m_pdfZoomCache.size() > 0) + if (m_pdfZoomCache.size() > 0 && cacheAllowed) { qreal xscale = p->worldTransform().m11(); qreal yscale = p->worldTransform().m22(); Q_ASSERT(qFuzzyCompare(xscale, yscale)); // Zoom equal in all axes expected. Q_ASSERT(xscale > 0.0); // Potential Div0 later if this assert fail. + qreal zoomRequested = xscale; int zoomIndex = 0; - bool foundIndex = false; - for (; zoomIndex < m_pdfZoomCache.size() && !foundIndex;) + if (m_pdfZoomMode == 3) { - if (xscale <= m_pdfZoomCache[zoomIndex].ratio) { - foundIndex = true; - } else { - zoomIndex++; + // Choose a zoom which is inferior or equivalent than the user choice (= minor loss, downscaling). + bool foundIndex = false; + for (zoomIndex = m_pdfZoomCache.size()-1; zoomIndex >= 0 && !foundIndex;) + { + if (zoomRequested >= m_pdfZoomCache[zoomIndex].ratio) { + foundIndex = true; + } else { + zoomIndex--; + } } + + if (!foundIndex) // Use the smallest one. + zoomIndex = 0; + + if (zoomIndex == 0 && m_pdfZoomCache[zoomIndex].ratio != zoomRequested) + { + m_pdfZoomCache[zoomIndex].cleanup(); + m_pdfZoomCache[zoomIndex] = PdfZoomCacheData(zoomRequested); + } + } else { + // Choose a zoom which is superior or equivalent than the user choice (= no loss, upscaling). + bool foundIndex = false; + for (; zoomIndex < m_pdfZoomCache.size() && !foundIndex;) + { + if (zoomRequested <= (m_pdfZoomCache[zoomIndex].ratio+0.1)) { + foundIndex = true; + } else { + zoomIndex++; + } + } + + if (!foundIndex) // Use the previous one. + zoomIndex--; } - if (!foundIndex) // Use the previous one. - zoomIndex--; + QImage pdfImage = createPDFImageCached(pageNumber, m_pdfZoomCache[zoomIndex]); + qreal ratioExpected = m_pdfZoomCache[zoomIndex].ratio; + qreal ratioObtained = ratioExpected; + int const initialZoomIndex = zoomIndex; + if (pdfImage == QImage() && m_pdfZoomCache[zoomIndex].hasToBeProcessed) + { + // Try to temporarily fallback on a valid image, for a fuzzy or downsampled preview. + // The actual result will be updated after the processing. + bool isCurrent = true; + while (zoomIndex < m_pdfZoomCache.size()-1 && (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || (m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber && !isCurrent))) + { + zoomIndex = zoomIndex+1; + isCurrent = false; + } + while (zoomIndex > 0 && (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber)) + zoomIndex = zoomIndex-1; + ratioObtained = m_pdfZoomCache[zoomIndex].ratio; + } + + if (m_pdfZoomCache[zoomIndex].cachedImage == QImage() || m_pdfZoomCache[zoomIndex].cachedPageNumber != pageNumber) + { + // No alternate image found. Build an alternate image in order to display some progress. + // Also make sure we fallback to the initial ratio request. + zoomIndex = initialZoomIndex; + qreal ratioDiff = m_pdfZoomCache[zoomIndex].ratio; + pdfImage = QImage(bounds.width()*ratioDiff, bounds.height()*ratioDiff, QImage::Format_RGB888); + pdfImage.fill("white"); + + QPainter painter(&pdfImage); + QString const text = tr("Processing..."); + QFont font = painter.font(); + if (font.pixelSize() != -1) + font.setPixelSize(ratioDiff*font.pixelSize()); + else + font.setPointSizeF(ratioDiff*font.pointSizeF()); + painter.setFont(font); + QFontMetrics textMetric(font, &pdfImage); + QSize textSize = textMetric.size(0, text); + painter.drawText((bounds.width()*ratioDiff-textSize.width())/2, (bounds.height()*ratioDiff-textSize.height())/2, text); + } else { + pdfImage = m_pdfZoomCache[zoomIndex].cachedImage; + } - QImage const &pdfImage = createPDFImageCached(pageNumber, m_pdfZoomCache[zoomIndex]); QTransform savedTransform = p->worldTransform(); double const ratioDifferenceBetweenWorldAndImage = 1.0/m_pdfZoomCache[zoomIndex].ratio; - // The 'pdfImage' is rendered with a quality equal or superior. We adjust the 'transform' to zoom it - // out the required ratio. + // The 'pdfImage' is maybe rendered with a different quality than requested. We adjust the 'transform' to zoom it + // in or out of the required ratio. QTransform newTransform = savedTransform.scale(ratioDifferenceBetweenWorldAndImage, ratioDifferenceBetweenWorldAndImage); p->setWorldTransform(newTransform); + /* qDebug() << "drawImage size=" << p->viewport() << "bounds" << bounds << + "pdfImage" << pdfImage.size() << "savedTransform" << savedTransform.m11() << + "ratioDiff" << ratioDifferenceBetweenWorldAndImage << "zoomRequested" << zoomRequested << + "zoomIndex" << zoomIndex; */ p->drawImage(QPointF( mSliceX, mSliceY), pdfImage); p->setWorldTransform(savedTransform); @@ -291,46 +386,89 @@ void XPDFRenderer::render(QPainter *p, int pageNumber, const QRectF &bounds) QImage *pdfImage = createPDFImageHistorical(pageNumber, xscale, yscale, bounds); QTransform savedTransform = p->worldTransform(); p->resetTransform(); + //qDebug() << "drawImage size=" << p->viewport() << "bounds" << bounds << "pdfImage" << pdfImage->size() << "savedTransform" << savedTransform.m11(); p->drawImage(QPointF(savedTransform.dx() + mSliceX, savedTransform.dy() + mSliceY), *pdfImage); p->setWorldTransform(savedTransform); delete pdfImage; } } + //qDebug() << "render leave"; } QImage& XPDFRenderer::createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData) { if (isValid()) { - SplashColor paperColor = {0xFF, 0xFF, 0xFF}; // white - if (cacheData.requireUpdateImage(pageNumber)) - { - cacheData.prepareNewSplash(pageNumber, paperColor); - -#ifdef USE_XPDF - cacheData.splash->startDoc(mDocument->getXRef()); -#else - cacheData.splash->startDoc(mDocument); -#endif - int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation) - bool useMediaBox = false; - bool crop = true; - bool printing = false; + if (cacheData.requireUpdateImage(pageNumber) && !cacheData.hasToBeProcessed) + { mSliceX = 0.; mSliceY = 0.; - mDocument->displayPage(cacheData.splash, pageNumber, this->dpiForRendering * cacheData.ratio, this->dpiForRendering * cacheData.ratio, - rotation, useMediaBox, crop, printing); - cacheData.splashBitmap = cacheData.splash->getBitmap(); + CacheThread::JobData jobData; + jobData.cacheData = &cacheData; + jobData.document = mDocument; + jobData.dpiForRendering = this->dpiForRendering; + jobData.pageNumber = pageNumber; + jobData.cacheData->hasToBeProcessed = true; + // Make sure we reset that image, because the data uses 'splash' buffer, which will be deallocated and + // reallocated when the job is started. + jobData.cacheData->cachedImage = QImage(); + m_cacheThread.pushJob(jobData); + + if (m_pdfZoomMode == 4) + { + // Start the job multithreaded. The item will be refreshed when the signal 'finished' is emitted. + m_cacheThread.start(); + } else { + // Perform the job now. Note this will lock the GUI until the job is done. + m_cacheThread.run(); + } } - - // Note this uses the 'cacheData.splash->getBitmap()->getDataPtr()' as data buffer. - cacheData.cachedImage = QImage(cacheData.splashBitmap->getDataPtr(), cacheData.splashBitmap->getWidth(), cacheData.splashBitmap->getHeight(), - cacheData.splashBitmap->getWidth() * 3 /* bytesPerLine, 24 bits for RGB888, = 3 bytes */, - QImage::Format_RGB888); } else { cacheData.cachedImage = QImage(); } return cacheData.cachedImage; } + +void XPDFRenderer::CacheThread::run() +{ + m_jobMutex.lock(); + + CacheThread::JobData jobData = m_nextJob.first(); + m_nextJob.pop_front(); + /* qDebug() << "XPDFRenderer::CacheThread starting page" << jobData.pageNumber + << "ratio" << jobData.cacheData->ratio; */ + + jobData.cacheData->prepareNewSplash(jobData.pageNumber, constants::paperColor); + +#ifdef USE_XPDF + jobData.cacheData->splash->startDoc(jobData.document->getXRef()); +#else + jobData.cacheData->splash->startDoc(jobData.document); +#endif + + m_jobMutex.unlock(); + + int rotation = 0; // in degrees (get it from the worldTransform if we want to support rotation) + bool useMediaBox = false; + bool crop = true; + bool printing = false; + + jobData.document->displayPage(jobData.cacheData->splash, jobData.pageNumber, jobData.dpiForRendering * jobData.cacheData->ratio, + jobData.dpiForRendering * jobData.cacheData->ratio, + rotation, useMediaBox, crop, printing); + + m_jobMutex.lock(); + jobData.cacheData->splashBitmap = jobData.cacheData->splash->getBitmap(); + // Note this uses the 'cacheData.splash->getBitmap()->getDataPtr()' as data buffer. + jobData.cacheData->cachedImage = QImage(jobData.cacheData->splashBitmap->getDataPtr(), jobData.cacheData->splashBitmap->getWidth(), jobData.cacheData->splashBitmap->getHeight(), + jobData.cacheData->splashBitmap->getWidth() * 3 /* bytesPerLine, 24 bits for RGB888, = 3 bytes */, + QImage::Format_RGB888); + + /* qDebug() << "XPDFRenderer::CacheThread completed page" << jobData.pageNumber + << "ratio" << jobData.cacheData->ratio << "final size is" << jobData.cacheData->cachedImage.size(); */ + + jobData.cacheData->hasToBeProcessed = false; + m_jobMutex.unlock(); +} diff --git a/src/pdf/XPDFRenderer.h b/src/pdf/XPDFRenderer.h index 73f64a91..43dd389d 100644 --- a/src/pdf/XPDFRenderer.h +++ b/src/pdf/XPDFRenderer.h @@ -30,6 +30,8 @@ #ifndef XPDFRENDERER_H #define XPDFRENDERER_H #include +#include +#include #include "PDFRenderer.h" #include @@ -51,6 +53,20 @@ class PDFDoc; + +namespace XPDFRendererZoomFactor +{ + const double mode1_zoomFactor = 3.0; + const double mode2_zoomFactorStage1 = 2.5; + const double mode2_zoomFactorStage2 = 5.0; + const double mode2_zoomFactorStage3 = 10.0; + const double mode3_zoomFactorStage1 = 1.0; + const double mode3_zoomFactorStage2 = 3.0; + const double mode4_zoomFactorStart = .25; + const double mode4_zoomFactorStepSquare = .25; + const double mode4_zoomFactorIterations = 7; +} + class XPDFRenderer : public PDFRenderer { Q_OBJECT @@ -59,31 +75,31 @@ class XPDFRenderer : public PDFRenderer XPDFRenderer(const QString &filename, bool importingFile = false); virtual ~XPDFRenderer(); - bool isValid() const; - - virtual int pageCount() const; - - virtual QSizeF pageSizeF(int pageNumber) const; + virtual bool isValid() const override; + virtual int pageCount() const override; + virtual QSizeF pageSizeF(int pageNumber) const override; + virtual int pageRotation(int pageNumber) const override; + virtual QString title() const override; + virtual void render(QPainter *p, int pageNumber, const bool cacheAllowed, const QRectF &bounds = QRectF()) override; - virtual int pageRotation(int pageNumber) const; - - virtual QString title() const; - - public slots: - void render(QPainter *p, int pageNumber, const QRectF &bounds = QRectF()); + signals: + void signalUpdateParent(); private: void init(); struct PdfZoomCacheData { - PdfZoomCacheData() : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(1.0) {}; - PdfZoomCacheData(double const a_ratio) : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(a_ratio) {}; + PdfZoomCacheData() : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(1.0), hasToBeProcessed(false) {}; + PdfZoomCacheData(double const a_ratio) : splashBitmap(nullptr), cachedPageNumber(-1), splash(nullptr), ratio(a_ratio), hasToBeProcessed(false) {}; ~PdfZoomCacheData() {}; SplashBitmap* splashBitmap; + //! Note: The 'cachedImage' uses a buffer from 'splash'. Make sure it is invalidated BEFORE 'splash' deallocation. QImage cachedImage; int cachedPageNumber; SplashOutputDev* splash; - double const ratio; + double ratio; + bool hasToBeProcessed; + QList updateListAfterProcessing; bool requireUpdateImage(int const pageNumber) const { return (pageNumber != cachedPageNumber) || (splash == nullptr); @@ -99,15 +115,55 @@ class XPDFRenderer : public PDFRenderer splash = new SplashOutputDev(splashModeRGB8, 1, false, paperColor); cachedPageNumber = pageNumber; } + + void cleanup() + { + if(splash != nullptr){ + cachedImage = QImage(); + delete splash; + splash = nullptr; + } + } }; + //! Spawned when a pdf processing is required, when no matching image is found in cache. + class CacheThread : public QThread + { + public: + struct JobData { + PdfZoomCacheData* cacheData; + PDFDoc *document; + int pageNumber; + double dpiForRendering; + }; + + CacheThread() {} + ~CacheThread() {} + void pushJob(JobData &jobData) { + QMutexLocker lock(&m_jobMutex); + m_nextJob.push_back(jobData); + } + + virtual void run() override; + bool isJobPending() { QMutexLocker lock(&m_jobMutex); return m_nextJob.size() > 0; } + void cancelPending() { QMutexLocker lock(&m_jobMutex); m_nextJob.clear(); } + private: + QList m_nextJob; + QMutex m_jobMutex; + }; + + CacheThread m_cacheThread; + QImage &createPDFImageCached(int pageNumber, PdfZoomCacheData &cacheData); QImage* createPDFImageHistorical(int pageNumber, qreal xscale, qreal yscale, const QRectF &bounds); - // Used when 'ZoomBehavior == 1 or 2'. + // Used when 'ZoomBehavior == 1, 2, 3 or 4'. // =1 has only x3 zoom in cache (= loss if user zoom > 3.0). // =2, has 2.5, 5 and 10 (= no loss, but a bit slower). + // =3, has 1.0, 2.5, 5 and 10, but downsampled instead of upsampled (= minor quality loss, a bit faster). + // =4, multithreaded, multiple level of zoom. QVector m_pdfZoomCache; + int const m_pdfZoomMode; // Used when 'ZoomBehavior == 0' (no cache). SplashBitmap* mpSplashBitmapHistorical; @@ -118,6 +174,9 @@ class XPDFRenderer : public PDFRenderer static QAtomicInt sInstancesCount; qreal mSliceX; qreal mSliceY; + +private slots: + void OnThreadFinished(); }; #endif // XPDFRENDERER_H