// SPDX-FileCopyrightText: 2022-2023 Open Mobile Platform LLC // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include #include "pagessizesloader.h" #include "pagepreloader.h" #include "basepage.h" #include "basebookmark.h" #include "pdfdocumentitem.h" PdfDocumentItem::PdfDocumentItem(QObject *parent) : BaseDocument(parent) { qRegisterMetaType>(); m_status = DocumentStatus::Null; } PdfDocumentItem::~PdfDocumentItem() { if (m_preloaderAllPage) m_preloaderAllPage->cancel(); } QString PdfDocumentItem::path() const { return m_pdfiumDocument ? m_pdfiumDocument->path() : QStringLiteral(""); } QSizeF PdfDocumentItem::pageSize(int pageNumber) const { if (m_status != DocumentStatus::Ready) return { }; return m_pageSizes.value(pageNumber); } int PdfDocumentItem::count() const { return m_pdfiumDocument ? m_pdfiumDocument->pageCount() : -1; } void PdfDocumentItem::loadAllPages() { if (m_preloaderAllPage) return; QVector notLoadedPage; for (int i = 0; i < count(); i++) { if (!m_loadedPages.contains(i) && !m_pagesInProcess.contains(i)) notLoadedPage.append(i); } if (notLoadedPage.empty()) { return; } QVector> rangesToLoad; int startPage = notLoadedPage[0]; int endPage = startPage; for (int i = 1; i < notLoadedPage.size(); ++i) { if (notLoadedPage[i] == endPage + 1) { endPage = notLoadedPage[i]; } else { rangesToLoad.push_back(QPair(startPage, endPage)); startPage = endPage = notLoadedPage[i]; } } rangesToLoad.push_back(QPair(startPage, endPage)); for (const auto &range : rangesToLoad) { m_preloaderAllPage = new PagePreloader(m_pdfiumDocument, range.first, range.second - range.first); connect(m_preloaderAllPage, &PagePreloader::done, this, [&] (int loadedPageIndex, PageLoadStatus loadStatus) { if (loadStatus == PageLoadStatus::Success) { if (!m_loadedPages.contains(loadedPageIndex)) m_loadedPages.insert(loadedPageIndex, QSharedPointer( new PdfPageItem(m_pdfiumDocument->page(loadedPageIndex)))); } emit pageLoaded(loadedPageIndex, loadStatus); }); QThreadPool::globalInstance()->start(m_preloaderAllPage, -1); for (int i = range.first; i < range.second; i++) m_pagesInProcess.insert(i); } } QSharedPointer PdfDocumentItem::loadPage(int pageIndex) { if (pageIndex < 0 || pageIndex >= count()) return { }; if (m_loadedPages.contains(pageIndex)) return m_loadedPages.value(pageIndex); if (m_pagesInProcess.contains(pageIndex)) return { }; auto pageLoader = new PagePreloader(m_pdfiumDocument, pageIndex); connect(pageLoader, &PagePreloader::done, this, [&] (int loadedPageIndex, PageLoadStatus loadStatus) { if (loadStatus == PageLoadStatus::Success) m_loadedPages.insert(loadedPageIndex, QSharedPointer( new PdfPageItem(m_pdfiumDocument->page(loadedPageIndex)))); emit pageLoaded(loadedPageIndex, loadStatus); }); QThreadPool::globalInstance()->start(pageLoader); m_pagesInProcess.insert(pageIndex); return { }; } void PdfDocumentItem::startLoadBookmarks() const { auto *bookmarksWatcher = new QFutureWatcher>(); connect(bookmarksWatcher, &QFutureWatcher>::finished, this, [bookmarksWatcher, ctx = const_cast(this)]() { if (bookmarksWatcher == nullptr) return; if (bookmarksWatcher->isFinished() && !bookmarksWatcher->isCanceled()) { auto bookmarks = bookmarksWatcher->result(); if (bookmarks.isEmpty()) { bookmarksWatcher->deleteLater(); return; } qDeleteAll(ctx->m_baseBookmarks); ctx->m_baseBookmarks.clear(); for (const auto &bookmark : bookmarks) { ctx->m_baseBookmarks.push_back(new BaseBookmark{ bookmark.title, bookmark.page, bookmark.level, bookmark.locationInPage }); } emit ctx->bookmarksLoaded(); } bookmarksWatcher->deleteLater(); }); bookmarksWatcher->setFuture(m_pdfiumDocument->bookmarks()); } QVector PdfDocumentItem::bookmarks() const { return m_baseBookmarks; } int PdfDocumentItem::fileVersion() const { return (m_pdfiumDocument.isNull() ? -1 : m_pdfiumDocument->fileVersion()); } bool PdfDocumentItem::saveDocumentAs(const QString &path) const { if (!m_pdfiumDocument) return false; if (!QFile::exists(path)) return m_pdfiumDocument->saveDocumentAs(path); static QString firstSuffix = QStringLiteral(".amber_bak1"); auto backupPathFirst = path + firstSuffix; auto saveResult = m_pdfiumDocument->saveDocumentAs(backupPathFirst); if (!saveResult) return false; static QString seconsSuffix = QStringLiteral(".amber_bak2"); auto backupPathSecond = path + seconsSuffix; saveResult = QFile::rename(path, backupPathSecond); if (!saveResult) return false; saveResult = QFile::rename(backupPathFirst, path); if (!saveResult) return false; QFile::remove(backupPathFirst); QFile::remove(backupPathSecond); return true; } void PdfDocumentItem::setPath(const QString &path) { if (m_pdfiumDocument && (m_pdfiumDocument->path() == path)) return; if (m_status != DocumentStatus::Loading) { m_status = DocumentStatus::Loading; emit statusChanged(m_status); } m_pdfiumDocument.reset(new PdfDocument()); m_pageSizes.clear(); m_loadedPages.clear(); m_pagesInProcess.clear(); m_baseBookmarks.clear(); if (m_pdfiumDocument == nullptr) { m_status = DocumentStatus::Error; emit statusChanged(m_status); return; } connect(m_pdfiumDocument.data(), &PdfDocument::statusChanged, this, [&](PdfDocument::DocumentStatus status) { if (status == PdfDocument::Success || status == PdfDocument::Loading) { m_pdfiumDocument->pageCount(); return; } m_status = DocumentStatus::Error; emit statusChanged(m_status); }); connect(m_pdfiumDocument.data(), &PdfDocument::pageCountChanged, this, [&](int pagesCount) { if (m_pdfiumDocument->status() != PdfDocument::Success) return; if (pagesCount < 0) { m_status = DocumentStatus::Error; emit statusChanged(m_status); return; } auto pageLoader = new PagesSizesLoader(m_pdfiumDocument); connect(pageLoader, &PagesSizesLoader::done, this, [this](QHash sizes) { m_pageSizes.swap(sizes); m_status = DocumentStatus::Ready; emit statusChanged(m_status); }); QThreadPool::globalInstance()->start(pageLoader); }); m_pdfiumDocument->loadDocument(path); emit pathChanged(path); } void PdfDocumentItem::onPagePreloaderDone(int loadedPageIndex, PageLoadStatus loadStatus) { if (m_pagesInProcess.isEmpty()) { // the document was probably changed recently // drop loading the pages of the previous document emit pageLoaded(loadedPageIndex, PageLoadStatus::Fail); return; } if (loadStatus == PageLoadStatus::Success) { auto pdfPageFuture = m_pdfiumDocument->page(loadedPageIndex); if (!pdfPageFuture.resultCount()) return; auto pdfPageItem = new PdfPageItem(pdfPageFuture); auto basePage = QSharedPointer(pdfPageItem); m_loadedPages.insert(loadedPageIndex, basePage); m_pagesInProcess.remove(loadedPageIndex); } emit pageLoaded(loadedPageIndex, loadStatus); }