From 5456af49ea21519135db98bc38976cdac6c63af6 Mon Sep 17 00:00:00 2001 From: ASHMIT <52139594+ashmitvoid@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:04:19 +0530 Subject: [PATCH] V1.1: enrich Properties with preview metadata --- qml/components/PropertiesSheet.qml | 219 +++++++++++++++++++++++++-- src/preview/FontProbe.cpp | 23 +-- src/preview/MediaPreviewWorkflow.cpp | 10 +- src/preview/MediaPreviewWorkflow.hpp | 5 + src/preview/TextPreviewLoader.cpp | 30 +++- src/preview/TextPreviewLoader.hpp | 10 ++ src/preview/preview_helper_main.cpp | 21 ++- tests/FontPreviewTest.cpp | 25 +++ tests/PreviewSchedulerTest.cpp | 32 ++++ 9 files changed, 336 insertions(+), 39 deletions(-) diff --git a/qml/components/PropertiesSheet.qml b/qml/components/PropertiesSheet.qml index d316a78..bc04d8f 100644 --- a/qml/components/PropertiesSheet.qml +++ b/qml/components/PropertiesSheet.qml @@ -33,11 +33,166 @@ Item { + desktop.folderSizeResult.folders + " folders · " + desktop.folderSizeResult.links + " links" } + readonly property string richPath: { + if (!root.visible || !root.details || root.details.path === undefined) + return "" + if (root.details.isDirectory === true || root.details.isSymlink === true) + return "" + return root.details.path || "" + } visible: false anchors.fill: parent z: 1000 + function addRow(rows, label, value) { + var text = value === undefined || value === null ? "" : String(value) + if (text !== "") + rows.push({ label: label, value: text }) + } + + function addSection(rows, label) { + rows.push({ section: true, label: label, value: "" }) + } + + function formatDuration(milliseconds) { + var total = Math.max(0, Math.round(Number(milliseconds) / 1000)) + if (!isFinite(total) || total <= 0) + return "" + var hours = Math.floor(total / 3600) + var minutes = Math.floor((total % 3600) / 60) + var seconds = total % 60 + function pad(value) { return value < 10 ? "0" + value : String(value) } + return hours > 0 + ? hours + ":" + pad(minutes) + ":" + pad(seconds) + : minutes + ":" + pad(seconds) + } + + function formatBitRate(value) { + var bits = Number(value) + if (!isFinite(bits) || bits <= 0) + return "" + if (bits >= 1000000) + return (bits / 1000000).toFixed(bits < 10000000 ? 1 : 0) + " Mbps" + return Math.round(bits / 1000) + " kbps" + } + + function imageCameraText() { + var parts = [] + if (imageMetadata.cameraMake !== "") + parts.push(imageMetadata.cameraMake) + if (imageMetadata.cameraModel !== "" + && imageMetadata.cameraModel !== imageMetadata.cameraMake) + parts.push(imageMetadata.cameraModel) + return parts.join(" · ") + } + + function propertyRows() { + var rows = [] + root.addSection(rows, "FILE") + root.addRow(rows, "TYPE", root.details.type || "") + root.addRow(rows, "MIME", root.details.mime || "") + root.addRow(rows, "LOCATION", root.details.parent || "") + root.addRow(rows, "PATH", root.details.path || "") + root.addRow(rows, "SIZE", root.displayedSize) + root.addRow(rows, "CONTENTS", root.folderContents) + root.addRow(rows, "MODIFIED", root.details.modified || "") + root.addRow(rows, "CREATED", root.details.created || "") + root.addRow(rows, "OWNER", root.details.owner || "") + root.addRow(rows, "GROUP", root.details.group || "") + root.addRow(rows, "PERMISSIONS", root.details.permissions || "") + root.addRow(rows, "LINK TARGET", root.details.symlinkTarget || "") + + if (imageMetadata.active) { + root.addSection(rows, "IMAGE") + if (imageMetadata.loading) { + root.addRow(rows, "STATUS", "Reading metadata…") + } else if (imageMetadata.supported) { + if (imageMetadata.pixelWidth > 0 && imageMetadata.pixelHeight > 0) + root.addRow(rows, "DIMENSIONS", imageMetadata.pixelWidth + " × " + imageMetadata.pixelHeight + " px") + root.addRow(rows, "FORMAT", imageMetadata.formatName.toUpperCase()) + if (imageMetadata.animated) + root.addRow(rows, "ANIMATION", imageMetadata.frameCount > 0 + ? imageMetadata.frameCount + " frames" : "Animated") + root.addRow(rows, "CAMERA", root.imageCameraText()) + root.addRow(rows, "LENS", imageMetadata.lensModel) + root.addRow(rows, "DATE TAKEN", imageMetadata.dateTaken) + root.addRow(rows, "EXPOSURE", imageMetadata.exposureTime) + root.addRow(rows, "APERTURE", imageMetadata.aperture) + root.addRow(rows, "ISO", imageMetadata.iso) + root.addRow(rows, "FOCAL LENGTH", imageMetadata.focalLength) + root.addRow(rows, "ORIENTATION", imageMetadata.orientation) + root.addRow(rows, "EXPOSURE BIAS", imageMetadata.exposureBias) + root.addRow(rows, "WHITE BALANCE", imageMetadata.whiteBalance) + root.addRow(rows, "COLOR SPACE", imageMetadata.colorSpace) + root.addRow(rows, "SOFTWARE", imageMetadata.software) + root.addRow(rows, "ARTIST", imageMetadata.artist) + root.addRow(rows, "COPYRIGHT", imageMetadata.copyright) + if (imageMetadata.metadataLimited) + root.addRow(rows, "METADATA", "Deep metadata skipped for large image") + } + } + + if (mediaMetadata.active) { + root.addSection(rows, "MEDIA") + if (mediaMetadata.loading) { + root.addRow(rows, "STATUS", "Reading metadata…") + } else if (mediaMetadata.supported) { + root.addRow(rows, "MEDIA KIND", mediaMetadata.mediaKind.toUpperCase()) + root.addRow(rows, "CONTAINER", mediaMetadata.formatName) + root.addRow(rows, "DURATION", root.formatDuration(mediaMetadata.durationMs)) + root.addRow(rows, "BIT RATE", root.formatBitRate(mediaMetadata.bitRate)) + root.addRow(rows, "TITLE", mediaMetadata.title) + root.addRow(rows, "ARTIST", mediaMetadata.artist) + root.addRow(rows, "ALBUM", mediaMetadata.album) + if (mediaMetadata.hasAudio) { + root.addRow(rows, "AUDIO CODEC", mediaMetadata.audioCodec) + root.addRow(rows, "SAMPLE RATE", mediaMetadata.sampleRate > 0 + ? mediaMetadata.sampleRate + " Hz" : "") + root.addRow(rows, "CHANNELS", mediaMetadata.channels > 0 + ? mediaMetadata.channels + (mediaMetadata.channelLayout !== "" + ? " · " + mediaMetadata.channelLayout : "") : mediaMetadata.channelLayout) + } + if (mediaMetadata.hasVideo) { + root.addRow(rows, "VIDEO CODEC", mediaMetadata.videoCodec) + if (mediaMetadata.videoWidth > 0 && mediaMetadata.videoHeight > 0) + root.addRow(rows, "VIDEO SIZE", mediaMetadata.videoWidth + " × " + mediaMetadata.videoHeight + " px") + root.addRow(rows, "FRAME RATE", mediaMetadata.frameRate > 0 + ? mediaMetadata.frameRate.toFixed(2).replace(/\.00$/, "") + " fps" : "") + } + } + } + + if (fontMetadata.active) { + root.addSection(rows, "FONT") + if (fontMetadata.loading) { + root.addRow(rows, "STATUS", "Reading metadata…") + } else if (fontMetadata.supported) { + root.addRow(rows, "FAMILY", fontMetadata.familyName) + root.addRow(rows, "STYLE NAME", fontMetadata.styleName) + root.addRow(rows, "STYLE", fontMetadata.styleLabel) + root.addRow(rows, "WEIGHT", fontMetadata.weight > 0 ? fontMetadata.weight : "") + root.addRow(rows, "UNITS / EM", fontMetadata.unitsPerEm > 0 + ? Math.round(fontMetadata.unitsPerEm) : "") + root.addRow(rows, "WRITING SYSTEMS", fontMetadata.writingSystems) + } + } + + if (pdfMetadata.active) { + root.addSection(rows, "PDF") + if (pdfMetadata.loading) { + root.addRow(rows, "STATUS", "Reading metadata…") + } else if (pdfMetadata.supported) { + root.addRow(rows, "PAGES", pdfMetadata.pageCount > 0 ? pdfMetadata.pageCount : "") + root.addRow(rows, "DOCUMENT TITLE", pdfMetadata.title) + root.addRow(rows, "AUTHOR", pdfMetadata.author) + root.addRow(rows, "SUBJECT", pdfMetadata.subject) + root.addRow(rows, "KEYWORDS", pdfMetadata.keywords) + } + } + return rows + } + function close() { desktop.cancelFolderSize() root.folderSizeError = "" @@ -56,6 +211,33 @@ Item { desktop.cancelFolderSize() } + ImagePreviewLoader { + id: imageMetadata + active: root.richPath !== "" && imageMetadata.isCandidate(root.richPath) + path: active ? root.richPath : "" + } + + MediaPreviewLoader { + id: mediaMetadata + metadataOnly: true + active: root.richPath !== "" && mediaMetadata.isCandidate(root.richPath) + path: active ? root.richPath : "" + } + + FontPreviewLoader { + id: fontMetadata + metadataOnly: true + active: root.richPath !== "" && fontMetadata.isCandidate(root.richPath) + path: active ? root.richPath : "" + } + + PdfPreviewLoader { + id: pdfMetadata + metadataOnly: true + active: root.richPath !== "" && pdfMetadata.isCandidate(root.richPath) + path: active ? root.richPath : "" + } + Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.48) @@ -120,34 +302,37 @@ Item { height: parent.height - 154 * root.uiScale clip: true boundsBehavior: Flickable.StopAtBounds - model: [ - { label: "TYPE", value: root.details.type || "" }, - { label: "MIME", value: root.details.mime || "" }, - { label: "LOCATION", value: root.details.parent || "" }, - { label: "PATH", value: root.details.path || "" }, - { label: "SIZE", value: root.displayedSize }, - { label: "CONTENTS", value: root.folderContents }, - { label: "MODIFIED", value: root.details.modified || "" }, - { label: "CREATED", value: root.details.created || "" }, - { label: "OWNER", value: root.details.owner || "" }, - { label: "GROUP", value: root.details.group || "" }, - { label: "PERMISSIONS", value: root.details.permissions || "" }, - { label: "LINK TARGET", value: root.details.symlinkTarget || "" } - ] + model: root.propertyRows() delegate: Item { id: propertyRow required property var modelData + readonly property bool section: modelData.section === true width: ListView.view.width - height: modelData.value !== "" ? 52 * root.uiScale : 0 - visible: modelData.value !== "" + height: section ? 31 * root.uiScale + : (modelData.value !== "" ? 52 * root.uiScale : 0) + visible: section || modelData.value !== "" + + Text { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: propertyRow.section ? 9 * root.uiScale : 7 * root.uiScale + visible: propertyRow.section + text: "// " + propertyRow.modelData.label + color: Ryoku.inkFaint + font.family: Ryoku.monoFont + font.pixelSize: 8 * root.uiScale + font.letterSpacing: 1.1 + } Text { anchors.left: parent.left anchors.top: parent.top anchors.topMargin: 7 * root.uiScale width: 110 * root.uiScale + visible: !propertyRow.section text: propertyRow.modelData.label color: Ryoku.inkMuted font.family: Ryoku.monoFont @@ -161,6 +346,7 @@ Item { anchors.right: parent.right anchors.top: parent.top anchors.topMargin: 7 * root.uiScale + visible: !propertyRow.section text: propertyRow.modelData.value elide: Text.ElideMiddle color: Ryoku.ink @@ -173,6 +359,7 @@ Item { anchors.right: parent.right anchors.bottom: parent.bottom height: 1 + visible: !propertyRow.section color: Ryoku.lineSoft } } diff --git a/src/preview/FontProbe.cpp b/src/preview/FontProbe.cpp index 5e75e24..676e339 100644 --- a/src/preview/FontProbe.cpp +++ b/src/preview/FontProbe.cpp @@ -246,6 +246,19 @@ QJsonObject probe( return {}; } + QJsonObject payload; + payload.insert(QStringLiteral("family"), rawFont.familyName()); + payload.insert(QStringLiteral("styleName"), rawFont.styleName()); + payload.insert(QStringLiteral("style"), styleLabel(rawFont.style())); + payload.insert(QStringLiteral("weight"), rawFont.weight()); + payload.insert(QStringLiteral("unitsPerEm"), rawFont.unitsPerEm()); + payload.insert(QStringLiteral("writingSystems"), detectedWritingSystems(rawFont)); + payload.insert(QStringLiteral("fileSize"), QString::number(fileSize)); + + const bool renderSample = request.value(QStringLiteral("renderSample")).toBool(true); + if (!renderSample) + return payload; + QString firstLine = filterSupported(rawFont, QStringLiteral("Aa Bb Cc 0123456789")); QString secondLine = filterSupported(rawFont, QStringLiteral("The quick brown fox")); if (visibleCharacterCount(firstLine) < 4) @@ -357,21 +370,11 @@ QJsonObject probe( if (png.isEmpty()) return {}; - const QJsonArray writingSystems = detectedWritingSystems(rawFont); - - QJsonObject payload; - payload.insert(QStringLiteral("family"), rawFont.familyName()); - payload.insert(QStringLiteral("styleName"), rawFont.styleName()); - payload.insert(QStringLiteral("style"), styleLabel(rawFont.style())); - payload.insert(QStringLiteral("weight"), rawFont.weight()); - payload.insert(QStringLiteral("unitsPerEm"), rawFont.unitsPerEm()); - payload.insert(QStringLiteral("writingSystems"), writingSystems); payload.insert(QStringLiteral("sampleText"), sampleLines.join(QChar::LineFeed)); payload.insert(QStringLiteral("sampleFormat"), QStringLiteral("png")); payload.insert(QStringLiteral("sampleBase64"), QString::fromLatin1(png.toBase64())); payload.insert(QStringLiteral("sampleWidth"), image.width()); payload.insert(QStringLiteral("sampleHeight"), image.height()); - payload.insert(QStringLiteral("fileSize"), QString::number(fileSize)); return payload; } diff --git a/src/preview/MediaPreviewWorkflow.cpp b/src/preview/MediaPreviewWorkflow.cpp index 432ca8e..0f6f8e7 100644 --- a/src/preview/MediaPreviewWorkflow.cpp +++ b/src/preview/MediaPreviewWorkflow.cpp @@ -77,6 +77,14 @@ void MediaPreviewLoader::setActive(bool active) { scheduleLoad(); } +void MediaPreviewLoader::setMetadataOnly(bool metadataOnly) { + if (m_metadataOnly == metadataOnly) + return; + m_metadataOnly = metadataOnly; + emit metadataOnlyChanged(); + scheduleLoad(); +} + bool MediaPreviewLoader::isCandidate(const QString& path) const { return isMediaCandidatePath(path); } @@ -145,7 +153,7 @@ void MediaPreviewLoader::startLoad() { QJsonObject request; request.insert(QStringLiteral("op"), QStringLiteral("media-probe")); request.insert(QStringLiteral("path"), loadPath); - request.insert(QStringLiteral("poster"), true); + request.insert(QStringLiteral("poster"), !m_metadataOnly); request.insert(QStringLiteral("maxWidth"), 1000); request.insert(QStringLiteral("maxHeight"), 700); diff --git a/src/preview/MediaPreviewWorkflow.hpp b/src/preview/MediaPreviewWorkflow.hpp index 148eaa8..abbca4c 100644 --- a/src/preview/MediaPreviewWorkflow.hpp +++ b/src/preview/MediaPreviewWorkflow.hpp @@ -17,6 +17,7 @@ class MediaPreviewLoader : public QObject { Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool metadataOnly READ metadataOnly WRITE setMetadataOnly NOTIFY metadataOnlyChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(bool supported READ supported NOTIFY resultChanged) Q_PROPERTY(bool hasAudio READ hasAudio NOTIFY resultChanged) @@ -47,6 +48,8 @@ class MediaPreviewLoader : public QObject { void setPath(const QString& path); bool active() const { return m_active; } void setActive(bool active); + bool metadataOnly() const { return m_metadataOnly; } + void setMetadataOnly(bool metadataOnly); bool loading() const { return m_loading; } bool supported() const { return m_supported; } @@ -75,6 +78,7 @@ class MediaPreviewLoader : public QObject { signals: void pathChanged(); void activeChanged(); + void metadataOnlyChanged(); void loadingChanged(); void resultChanged(); @@ -86,6 +90,7 @@ class MediaPreviewLoader : public QObject { QString m_path; bool m_active = false; + bool m_metadataOnly = false; bool m_loading = false; bool m_supported = false; bool m_hasAudio = false; diff --git a/src/preview/TextPreviewLoader.cpp b/src/preview/TextPreviewLoader.cpp index 181a117..79e429c 100644 --- a/src/preview/TextPreviewLoader.cpp +++ b/src/preview/TextPreviewLoader.cpp @@ -323,6 +323,14 @@ void PdfPreviewLoader::setActive(bool active) { scheduleLoad(); } +void PdfPreviewLoader::setMetadataOnly(bool metadataOnly) { + if (m_metadataOnly == metadataOnly) + return; + m_metadataOnly = metadataOnly; + emit metadataOnlyChanged(); + scheduleLoad(); +} + void PdfPreviewLoader::setPage(int page) { int bounded = std::max(0, page); if (m_pageCount > 0) @@ -393,6 +401,7 @@ void PdfPreviewLoader::startLoad() { request.insert(QStringLiteral("op"), QStringLiteral("pdf-page")); request.insert(QStringLiteral("path"), loadPath); request.insert(QStringLiteral("page"), requestedPage); + request.insert(QStringLiteral("renderPage"), !m_metadataOnly); request.insert(QStringLiteral("maxWidth"), 1200); request.insert(QStringLiteral("maxHeight"), 1600); @@ -420,17 +429,21 @@ void PdfPreviewLoader::startLoad() { const QJsonObject payload = result.payload; const QString base64 = payload.value(QStringLiteral("imageBase64")).toString(); const int pageCount = payload.value(QStringLiteral("pageCount")).toInt(0); - if (base64.isEmpty() || pageCount <= 0) { + if (pageCount <= 0 || (!m_metadataOnly && base64.isEmpty())) { m_supported = false; m_imageSource.clear(); - m_error = QStringLiteral("PDF preview returned no image"); + m_error = m_metadataOnly + ? QStringLiteral("PDF metadata unavailable") + : QStringLiteral("PDF preview returned no image"); emit resultChanged(); return; } m_supported = true; m_pageCount = pageCount; - m_imageSource = QStringLiteral("data:image/png;base64,") + base64; + m_imageSource = base64.isEmpty() + ? QString{} + : QStringLiteral("data:image/png;base64,") + base64; m_title = payload.value(QStringLiteral("title")).toString(); m_author = payload.value(QStringLiteral("author")).toString(); m_subject = payload.value(QStringLiteral("subject")).toString(); @@ -475,6 +488,14 @@ void FontPreviewLoader::setActive(bool active) { scheduleLoad(); } +void FontPreviewLoader::setMetadataOnly(bool metadataOnly) { + if (m_metadataOnly == metadataOnly) + return; + m_metadataOnly = metadataOnly; + emit metadataOnlyChanged(); + scheduleLoad(); +} + bool FontPreviewLoader::isCandidate(const QString& path) const { return isFontCandidatePath(path); } @@ -530,6 +551,7 @@ void FontPreviewLoader::startLoad() { QJsonObject request; request.insert(QStringLiteral("op"), QStringLiteral("font-preview")); request.insert(QStringLiteral("path"), loadPath); + request.insert(QStringLiteral("renderSample"), !m_metadataOnly); request.insert(QStringLiteral("maxWidth"), 1000); request.insert(QStringLiteral("maxHeight"), 520); request.insert(QStringLiteral("pixelSize"), 64); @@ -574,7 +596,7 @@ void FontPreviewLoader::startLoad() { m_sampleSource = sampleBase64.isEmpty() ? QString{} : QStringLiteral("data:image/png;base64,") + sampleBase64; - m_supported = !m_sampleSource.isEmpty(); + m_supported = m_metadataOnly || !m_sampleSource.isEmpty(); m_error = m_supported ? QString{} : QStringLiteral("Font preview returned no sample image"); diff --git a/src/preview/TextPreviewLoader.hpp b/src/preview/TextPreviewLoader.hpp index 00e83c6..2a8484b 100644 --- a/src/preview/TextPreviewLoader.hpp +++ b/src/preview/TextPreviewLoader.hpp @@ -125,6 +125,7 @@ class PdfPreviewLoader : public QObject { Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool metadataOnly READ metadataOnly WRITE setMetadataOnly NOTIFY metadataOnlyChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(bool supported READ supported NOTIFY resultChanged) Q_PROPERTY(int page READ page WRITE setPage NOTIFY pageChanged) @@ -145,6 +146,8 @@ class PdfPreviewLoader : public QObject { bool active() const { return m_active; } void setActive(bool active); + bool metadataOnly() const { return m_metadataOnly; } + void setMetadataOnly(bool metadataOnly); bool loading() const { return m_loading; } bool supported() const { return m_supported; } @@ -163,6 +166,7 @@ class PdfPreviewLoader : public QObject { signals: void pathChanged(); void activeChanged(); + void metadataOnlyChanged(); void loadingChanged(); void pageChanged(); void resultChanged(); @@ -175,6 +179,7 @@ class PdfPreviewLoader : public QObject { QString m_path; bool m_active = false; + bool m_metadataOnly = false; bool m_loading = false; bool m_supported = false; int m_page = 0; @@ -194,6 +199,7 @@ class FontPreviewLoader : public QObject { Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(bool metadataOnly READ metadataOnly WRITE setMetadataOnly NOTIFY metadataOnlyChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(bool supported READ supported NOTIFY resultChanged) Q_PROPERTY(QString familyName READ familyName NOTIFY resultChanged) @@ -214,6 +220,8 @@ class FontPreviewLoader : public QObject { void setPath(const QString& path); bool active() const { return m_active; } void setActive(bool active); + bool metadataOnly() const { return m_metadataOnly; } + void setMetadataOnly(bool metadataOnly); bool loading() const { return m_loading; } bool supported() const { return m_supported; } QString familyName() const { return m_familyName; } @@ -231,6 +239,7 @@ class FontPreviewLoader : public QObject { signals: void pathChanged(); void activeChanged(); + void metadataOnlyChanged(); void loadingChanged(); void resultChanged(); @@ -242,6 +251,7 @@ class FontPreviewLoader : public QObject { QString m_path; bool m_active = false; + bool m_metadataOnly = false; bool m_loading = false; bool m_supported = false; quint64 m_generation = 0; diff --git a/src/preview/preview_helper_main.cpp b/src/preview/preview_helper_main.cpp index f43e3cf..5859e46 100644 --- a/src/preview/preview_helper_main.cpp +++ b/src/preview/preview_helper_main.cpp @@ -179,6 +179,19 @@ QJsonObject pdfPagePayload(const QString& path, const QJsonObject& request, QStr return {}; } + QJsonObject payload; + payload.insert(QStringLiteral("page"), pageIndex); + payload.insert(QStringLiteral("pageCount"), pageCount); + payload.insert(QStringLiteral("title"), document->title()); + payload.insert(QStringLiteral("author"), document->author()); + payload.insert(QStringLiteral("subject"), document->subject()); + payload.insert(QStringLiteral("keywords"), document->keywords()); + payload.insert(QStringLiteral("fileSize"), QString::number(opened.size)); + + const bool renderPage = request.value(QStringLiteral("renderPage")).toBool(true); + if (!renderPage) + return payload; + const int maxWidth = std::clamp( request.value(QStringLiteral("maxWidth")).toInt(PreviewProtocol::kDefaultPdfRenderWidth), 128, @@ -236,18 +249,10 @@ QJsonObject pdfPagePayload(const QString& path, const QJsonObject& request, QStr if (png.isEmpty()) return {}; - QJsonObject payload; - payload.insert(QStringLiteral("page"), pageIndex); - payload.insert(QStringLiteral("pageCount"), pageCount); payload.insert(QStringLiteral("pixelWidth"), image.width()); payload.insert(QStringLiteral("pixelHeight"), image.height()); payload.insert(QStringLiteral("imageFormat"), QStringLiteral("png")); payload.insert(QStringLiteral("imageBase64"), QString::fromLatin1(png.toBase64())); - payload.insert(QStringLiteral("title"), document->title()); - payload.insert(QStringLiteral("author"), document->author()); - payload.insert(QStringLiteral("subject"), document->subject()); - payload.insert(QStringLiteral("keywords"), document->keywords()); - payload.insert(QStringLiteral("fileSize"), QString::number(opened.size)); return payload; } #endif diff --git a/tests/FontPreviewTest.cpp b/tests/FontPreviewTest.cpp index f1ca6a2..6365141 100644 --- a/tests/FontPreviewTest.cpp +++ b/tests/FontPreviewTest.cpp @@ -108,6 +108,31 @@ private slots: QVERIFY(png.startsWith("\x89PNG\r\n\x1a\n")); } + void metadataOnlySkipsSampleRendering() { + QVERIFY2(QFile::exists(testFontPath()), qPrintable(testFontPath())); + + PreviewScheduler scheduler(helperPath()); + QObject owner; + const PreviewResult result = execute( + scheduler, + owner, + QJsonObject { + {QStringLiteral("op"), QStringLiteral("font-preview")}, + {QStringLiteral("path"), testFontPath()}, + {QStringLiteral("renderSample"), false}, + }); + + QVERIFY2(result.ok, qPrintable(result.error)); + QVERIFY(result.payload.value(QStringLiteral("family")).toString().contains( + QStringLiteral("DejaVu Sans"), Qt::CaseInsensitive)); + QVERIFY(result.payload.value(QStringLiteral("unitsPerEm")).toDouble() > 0.0); + QVERIFY(!result.payload.value(QStringLiteral("writingSystems")).toArray().isEmpty()); + QVERIFY(!result.payload.contains(QStringLiteral("sampleFormat"))); + QVERIFY(!result.payload.contains(QStringLiteral("sampleBase64"))); + QVERIFY(!result.payload.contains(QStringLiteral("sampleWidth"))); + QVERIFY(!result.payload.contains(QStringLiteral("sampleHeight"))); + } + void rejectsFontSymlinkWithoutFollowing() { QVERIFY2(QFile::exists(testFontPath()), qPrintable(testFontPath())); QTemporaryDir directory; diff --git a/tests/PreviewSchedulerTest.cpp b/tests/PreviewSchedulerTest.cpp index 5a3fa91..0b0e385 100644 --- a/tests/PreviewSchedulerTest.cpp +++ b/tests/PreviewSchedulerTest.cpp @@ -234,6 +234,38 @@ private slots: QVERIFY(png.startsWith("\x89PNG\r\n\x1a\n")); } + void readsPdfMetadataWithoutRenderingPage() { + QTemporaryDir directory; + QVERIFY(directory.isValid()); + const QString path = directory.filePath(QStringLiteral("metadata.pdf")); + QFile file(path); + QVERIFY(file.open(QIODevice::WriteOnly)); + const QByteArray bytes = onePagePdf(); + QCOMPARE(file.write(bytes), bytes.size()); + file.close(); + + PreviewScheduler scheduler(helperPath()); + QObject owner; + const PreviewResult result = execute( + scheduler, + owner, + QJsonObject { + {QStringLiteral("op"), QStringLiteral("pdf-page")}, + {QStringLiteral("path"), path}, + {QStringLiteral("page"), 0}, + {QStringLiteral("renderPage"), false}, + }); + + QVERIFY2(result.ok, qPrintable(result.error)); + QCOMPARE(result.payload.value(QStringLiteral("page")).toInt(), 0); + QCOMPARE(result.payload.value(QStringLiteral("pageCount")).toInt(), 1); + QVERIFY(!result.payload.value(QStringLiteral("fileSize")).toString().isEmpty()); + QVERIFY(!result.payload.contains(QStringLiteral("pixelWidth"))); + QVERIFY(!result.payload.contains(QStringLiteral("pixelHeight"))); + QVERIFY(!result.payload.contains(QStringLiteral("imageFormat"))); + QVERIFY(!result.payload.contains(QStringLiteral("imageBase64"))); + } + void pdfRenderRejectsSymlinkInput() { QTemporaryDir directory; QVERIFY(directory.isValid());