From 6720231f097e2a0a1a175c6285d5ab1097f4e2b2 Mon Sep 17 00:00:00 2001 From: ZhangTingan Date: Tue, 11 Nov 2025 14:03:09 +0800 Subject: [PATCH] feat: Improve XPS print quality by using printer DPI-aware rendering Log: as title task:https://pms.uniontech.com/task-view-383459.html --- reader/document/XpsDocumentAdapter.cpp | 1 + reader/uiframe/DocSheet.cpp | 210 +++++++++++++++++++++++-- reader/uiframe/DocSheet.h | 8 + reader/uiframe/SheetRenderer.cpp | 1 + 4 files changed, 208 insertions(+), 12 deletions(-) diff --git a/reader/document/XpsDocumentAdapter.cpp b/reader/document/XpsDocumentAdapter.cpp index 0dae9c56..9dc838e8 100644 --- a/reader/document/XpsDocumentAdapter.cpp +++ b/reader/document/XpsDocumentAdapter.cpp @@ -537,6 +537,7 @@ QImage XpsDocumentAdapter::renderPage(int pageIndex, int width, int height, cons cairo_set_source_rgb(context.get(), 1.0, 1.0, 1.0); cairo_paint(context.get()); + // Scaling via cairo_matrix keeps high-DPI print requests accurate, no device-scale override needed here. cairo_matrix_t matrix; cairo_set_antialias(context.get(), CAIRO_ANTIALIAS_BEST); diff --git a/reader/uiframe/DocSheet.cpp b/reader/uiframe/DocSheet.cpp index 56f20780..fce66f1e 100644 --- a/reader/uiframe/DocSheet.cpp +++ b/reader/uiframe/DocSheet.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include @@ -46,6 +48,12 @@ extern "C" { } DWIDGET_USE_NAMESPACE +namespace { +constexpr double kXpsLogicalDpi = 96.0; +constexpr int kFallbackPrintDpi = 300; +constexpr int kMaxPrintPixelsPerSide = 10000; +} + QReadWriteLock DocSheet::g_lock; QStringList DocSheet::g_uuidList; QList DocSheet::g_sheetList; @@ -508,6 +516,56 @@ QImage DocSheet::getImage(int index, int width, int height, const QRect &slice) return m_renderer->getImage(index, width, height, slice);; } +QSize DocSheet::calculatePrintTargetSize(int pageIndex, const QPrinter &printer, const QRectF &pageRect) const +{ + if (m_fileType != Dr::XPS) { + return QSize(); + } + + const QSizeF logicalSize = m_renderer ? m_renderer->getPageSize(pageIndex) : QSizeF(); + if (logicalSize.isEmpty() || logicalSize.width() <= 0.0 || logicalSize.height() <= 0.0) { + qCWarning(appLog) << "Unable to determine logical page size for high DPI print. index:" << pageIndex; + return QSize(); + } + + int printerDpi = printer.resolution(); + if (printerDpi <= 0) { + qCWarning(appLog) << "Printer resolution is invalid, fallback to default DPI:" << kFallbackPrintDpi; + printerDpi = kFallbackPrintDpi; + } + + double widthPixels = logicalSize.width() / kXpsLogicalDpi * static_cast(printerDpi); + double heightPixels = logicalSize.height() / kXpsLogicalDpi * static_cast(printerDpi); + + if (widthPixels <= 0.0 || heightPixels <= 0.0) { + qCWarning(appLog) << "Calculated print size is invalid:" << widthPixels << heightPixels; + return QSize(); + } + + if (pageRect.width() > 0.0 && pageRect.height() > 0.0) { + const double scale = qMin(pageRect.width() / widthPixels, pageRect.height() / heightPixels); + if (scale < 1.0) { + widthPixels *= scale; + heightPixels *= scale; + } + } + + const double maxDimension = qMax(widthPixels, heightPixels); + if (maxDimension > kMaxPrintPixelsPerSide) { + const double clampScale = static_cast(kMaxPrintPixelsPerSide) / maxDimension; + widthPixels *= clampScale; + heightPixels *= clampScale; + qCWarning(appLog) << "Requested print resolution too large, clamping to" << kMaxPrintPixelsPerSide << "pixels. index:" << pageIndex; + } + + const int roundedWidth = qMax(1, qRound(widthPixels)); + const int roundedHeight = qMax(1, qRound(heightPixels)); + + qCDebug(appLog) << "Calculated print target size for page" << pageIndex << ":" << roundedWidth << "x" << roundedHeight << "@" << printerDpi << "DPI"; + + return QSize(roundedWidth, roundedHeight); +} + bool DocSheet::fileChanged() { qCDebug(appLog) << "fileChanged"; @@ -870,22 +928,72 @@ void DocSheet::onPrintRequested(DPrinter *printer, const QVector &pageRange painter.setRenderHints(QPainter::Antialiasing | QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + const bool isXpsDocument = (m_fileType == Dr::XPS); + auto targetRectForSize = [&pageRect](const QSize &sourceSize) -> QRect { + if (!sourceSize.isValid() || sourceSize.isEmpty()) { + return QRect(); + } + + qreal targetWidth = pageRect.width(); + qreal targetHeight = targetWidth * static_cast(sourceSize.height()) / static_cast(sourceSize.width()); + + if (targetHeight > pageRect.height()) { + targetHeight = pageRect.height(); + targetWidth = targetHeight * static_cast(sourceSize.width()) / static_cast(sourceSize.height()); + } + + const int left = qRound((pageRect.width() - targetWidth) / 2.0); + const int top = qRound((pageRect.height() - targetHeight) / 2.0); + return QRect(left, + top, + qMax(1, qRound(targetWidth)), + qMax(1, qRound(targetHeight))); + }; + for (int i = 0; i < pageRange.count(); ++i) { if (pageRange[i] > pageCount() || pageRange[i] > m_browser->pages().count()) continue; - const QRectF boundingrect = m_browser->pages().at(pageRange[i] - 1)->boundingRect(); //文档页缩放后的原区域不受旋转影响 - qreal printWidth = pageRect.width(); //适合打印的图片宽度 - qreal printHeight = printWidth * boundingrect.height() / boundingrect.width(); //适合打印的图片高度 - if (printHeight > pageRect.height()) { - printHeight = pageRect.height(); - printWidth = printHeight * boundingrect.width() / boundingrect.height(); + QImage image; + QRect targetRect; + + if (isXpsDocument) { + const int zeroBasedIndex = pageRange[i] - 1; + const QSize requestedSize = calculatePrintTargetSize(zeroBasedIndex, *printer, pageRect); + if (!requestedSize.isValid()) { + qCWarning(appLog) << "Falling back to view-based print size for XPS page" << zeroBasedIndex; + } + + if (requestedSize.isValid()) { + image = loading.getImageForPrint(this, zeroBasedIndex, requestedSize); + targetRect = targetRectForSize(image.size()); + } + } + + if (image.isNull()) { + const QRectF boundingrect = m_browser->pages().at(pageRange[i] - 1)->boundingRect(); + qreal printWidth = pageRect.width(); + qreal printHeight = printWidth * boundingrect.height() / boundingrect.width(); + if (printHeight > pageRect.height()) { + printHeight = pageRect.height(); + printWidth = printHeight * boundingrect.width() / boundingrect.height(); + } + image = loading.getImage(this, pageRange[i] - 1, static_cast(printWidth), static_cast(printHeight)); + targetRect = QRect((static_cast(pageRect.width()) - image.width()) / 2, + (static_cast(pageRect.height()) - image.height()) / 2, + image.width(), image.height()); + } + + if (image.isNull()) { + qCWarning(appLog) << "Failed to render image for printing page" << pageRange[i]; + continue; + } + + if (!targetRect.isValid()) { + targetRect = targetRectForSize(image.size()); } - QImage image = loading.getImage(this, pageRange[i] - 1, static_cast(printWidth), static_cast(printHeight)); - painter.drawImage(QRect((static_cast(pageRect.width()) - image.width()) / 2, - (static_cast(pageRect.height()) - image.height()) / 2, - image.width(), image.height()), image); + painter.drawImage(targetRect, image, image.rect()); if (i != pageRange.count() - 1) printer->newPage(); @@ -946,13 +1054,62 @@ void DocSheet::onPrintRequested(DPrinter *printer) LoadingWidget loading(qApp->activeWindow()); loading.show(); + const bool isXpsDocument = (m_fileType == Dr::XPS); + auto targetRectForSize = [&pageRect](const QSize &sourceSize) -> QRect { + if (!sourceSize.isValid() || sourceSize.isEmpty()) { + return QRect(); + } + + qreal targetWidth = pageRect.width(); + qreal targetHeight = targetWidth * static_cast(sourceSize.height()) / static_cast(sourceSize.width()); + + if (targetHeight > pageRect.height()) { + targetHeight = pageRect.height(); + targetWidth = targetHeight * static_cast(sourceSize.width()) / static_cast(sourceSize.height()); + } + + const int left = qRound((pageRect.width() - targetWidth) / 2.0); + const int top = qRound((pageRect.height() - targetHeight) / 2.0); + return QRect(left, + top, + qMax(1, qRound(targetWidth)), + qMax(1, qRound(targetHeight))); + }; + for (int index = fromIndex; index <= toIndex; index++) { if (index >= pagesCount) break; - QImage imageX3 = loading.getImage(this, index, int(pageRect.width() * 3), int(pageRect.height() * 3)); + QImage image; + QRect targetRect; + + if (isXpsDocument) { + const QSize requestedSize = calculatePrintTargetSize(index, *printer, pageRect); + if (!requestedSize.isValid()) { + qCWarning(appLog) << "Falling back to view-based print size for XPS page" << index; + } else { + image = loading.getImageForPrint(this, index, requestedSize); + targetRect = targetRectForSize(image.size()); + } + } + + if (image.isNull()) { + const int fallbackWidth = int(pageRect.width() * 3); + const int fallbackHeight = int(pageRect.height() * 3); + image = loading.getImage(this, index, fallbackWidth, fallbackHeight); + targetRect = pageRect.toRect(); + } + + if (image.isNull()) { + qCWarning(appLog) << "Failed to render image for printing page" << index; + continue; + } + + if (!targetRect.isValid()) { + targetRect = targetRectForSize(image.size()); + } - painter.drawImage(pageRect, imageX3, QRect(0, 0, imageX3.width(), imageX3.height())); + painter.drawImage(targetRect, image, image.rect()); if (index != toIndex) printer->newPage(); } @@ -1593,6 +1750,35 @@ QImage DocSheet::LoadingWidget::getImage(DocSheet *doc, int index, int width, in return image; } +QImage DocSheet::LoadingWidget::getImageForPrint(DocSheet *doc, int index, const QSize &targetSize) +{ + qCDebug(appLog) << "getImageForPrint" << targetSize; + if (!doc) { + qCWarning(appLog) << "DocSheet pointer is null when requesting print image"; + return QImage(); + } + if (!targetSize.isValid() || targetSize.width() <= 0 || targetSize.height() <= 0) { + qCWarning(appLog) << "Invalid target size for print image" << targetSize; + return QImage(); + } + + QImage image; + QEventLoop loop; + const QSize requestSize = targetSize; + QThread *thread = QThread::create([ =, &image]() { + image = doc->getImage(index, requestSize.width(), requestSize.height()); + }); + QObject::connect(thread, &QThread::finished, &loop, &QEventLoop::quit); + QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); + + thread->start(); + + loop.exec(QEventLoop::ExcludeSocketNotifiers); + + qCDebug(appLog) << "getImageForPrint end"; + return image; +} + void DocSheet::LoadingWidget::paintEvent(QPaintEvent *) { // qCDebug(appLog) << "paintEvent"; diff --git a/reader/uiframe/DocSheet.h b/reader/uiframe/DocSheet.h index af3c17a3..def4cb3f 100644 --- a/reader/uiframe/DocSheet.h +++ b/reader/uiframe/DocSheet.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include class SheetSidebar; class SlideWidget; @@ -810,6 +812,7 @@ private slots: void onExtractPassword(const QString &password); private: + QSize calculatePrintTargetSize(int pageIndex, const QPrinter &printer, const QRectF &pageRect) const; /** * @brief setAlive * 设置当前sheet是否存活 @@ -873,6 +876,11 @@ private slots: */ QImage getImage(DocSheet *doc, int index, int width, int height); + /** + * @brief getImageForPrint 打印专用图像获取接口 + */ + QImage getImageForPrint(DocSheet *doc, int index, const QSize &targetSize); + protected: void paintEvent(QPaintEvent */*event*/) override; diff --git a/reader/uiframe/SheetRenderer.cpp b/reader/uiframe/SheetRenderer.cpp index 2561b5cd..01b5579c 100644 --- a/reader/uiframe/SheetRenderer.cpp +++ b/reader/uiframe/SheetRenderer.cpp @@ -80,6 +80,7 @@ QImage SheetRenderer::getImage(int index, int width, int height, const QRect &sl return QImage(); } + // width/height may come from high-DPI print requests: keep pipeline ready for large images. QImage image = m_pages.value(index)->render(width, height, slice); qCDebug(appLog) << "SheetRenderer::getImage end"; return image;