diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -137,6 +137,20 @@ LINK_LIBRARIES Qt5::Test KF5::FileMetaData ) +# +# Postscript DSC +# +set(postscriptdscextractor_SRCS + postscriptdscextractortest.cpp + ../src/extractors/postscriptdscextractor.cpp + ../src/kfilemetadata_debug.cpp +) + +ecm_add_test(${postscriptdscextractor_SRCS} + TEST_NAME "postscriptdscextractortest" + LINK_LIBRARIES Qt5::Test KF5::FileMetaData +) + ################ # Writer tests # ################ diff --git a/autotests/postscriptdscextractortest.h b/autotests/postscriptdscextractortest.h new file mode 100644 --- /dev/null +++ b/autotests/postscriptdscextractortest.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 Stefan Brüns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef POSTSCRIPTDSCEXTRACTORTEST_H +#define POSTSCRIPTDSCEXTRACTORTEST_H + +#include + +class PostscriptDscExtractorTest : public QObject +{ + Q_OBJECT +private: + QString testFilePath(const QString& fileName) const; + +private Q_SLOTS: + void testPS(); + void testEPS(); +}; + +#endif // POSTSCRIPTDSCEXTRACTORTEST_H diff --git a/autotests/postscriptdscextractortest.cpp b/autotests/postscriptdscextractortest.cpp new file mode 100644 --- /dev/null +++ b/autotests/postscriptdscextractortest.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 Stefan Brüns + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "postscriptdscextractortest.h" +#include "simpleextractionresult.h" +#include "indexerextractortestsconfig.h" +#include "extractors/postscriptdscextractor.h" + +#include +#include +#include + +using namespace KFileMetaData; + +QString PostscriptDscExtractorTest::testFilePath(const QString& fileName) const +{ + return QLatin1String(INDEXER_TESTS_SAMPLE_FILES_PATH) + QLatin1Char('/') + fileName; +} + +void PostscriptDscExtractorTest::testPS() +{ + DscExtractor plugin{this}; + + SimpleExtractionResult result(testFilePath("test.ps"), "application/postscript"); + plugin.extract(&result); + + QCOMPARE(result.types().size(), 1); + QCOMPARE(result.types().constFirst(), Type::Document); + + QCOMPARE(result.properties().value(Property::Title).toString(), QStringLiteral("The Big Brown Bear")); + QCOMPARE(result.properties().value(Property::PageCount).toInt(), 2); + + QDateTime dt(QDate(2018, 10, 28), QTime(21, 13, 39)); + dt.setOffsetFromUtc(+3600); + QCOMPARE(result.properties().value(Property::CreationDate), QVariant(dt)); + + QCOMPARE(result.properties().size(), 3); +} + +void PostscriptDscExtractorTest::testEPS() +{ + DscExtractor plugin{this}; + + SimpleExtractionResult result(testFilePath("test.eps"), "image/x-eps"); + plugin.extract(&result); + + QCOMPARE(result.types().size(), 1); + QCOMPARE(result.types().constFirst(), Type::Image); + + QCOMPARE(result.properties().value(Property::Title).toString(), QStringLiteral("The Big Brown Bear")); + QCOMPARE(result.properties().value(Property::PageCount).toInt(), 1); + + QDateTime dt(QDate(2018, 10, 28), QTime(21, 13, 39)); + dt.setOffsetFromUtc(-5400); + QCOMPARE(result.properties().value(Property::CreationDate), QVariant(dt)); + QCOMPARE(result.properties().size(), 3); +} + +QTEST_GUILESS_MAIN(PostscriptDscExtractorTest) diff --git a/autotests/samplefiles/test.eps b/autotests/samplefiles/test.eps new file mode 100644 --- /dev/null +++ b/autotests/samplefiles/test.eps @@ -0,0 +1,21 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%BoundingBox: 0 0 100 140 +%%HiResBoundingBox: 0 0 100.00 140.00 +%%Creator: Vim +%%LanguageLevel: 2 +%%CreationDate: D:20181028211339-01'30' +%%Pages: 1 +%%Title: (The Big Brown Bear) +%%EndComments +%%BeginProlog +%%EndProlog +%%Page: 1 1 +newpath +10 10 moveto +90 70 lineto +10 130 lineto +2 setlinewidth +stroke + +%%Trailer +%%EOF diff --git a/autotests/samplefiles/test.ps b/autotests/samplefiles/test.ps new file mode 100644 --- /dev/null +++ b/autotests/samplefiles/test.ps @@ -0,0 +1,19 @@ +%!PS-Adobe-3.0 +%%BoundingBox: 0 0 612 792 +%%HiResBoundingBox: 0 0 612.00 792.00 +%%Creator: Vim +%%LanguageLevel: 2 +%%CreationDate: D:20181028211339+01'00' +%%Pages: 2 +%%Title: The Big Brown Bear +%%EndComments +%%BeginProlog +%%EndProlog +%%Page: 1 2 +%%PageBoundingBox: 0 0 612 792 +showpage +%%Page: 1 2 +%%PageBoundingBox: 0 0 612 792 +showpage +%%Trailer +%%EOF diff --git a/src/extractors/CMakeLists.txt b/src/extractors/CMakeLists.txt --- a/src/extractors/CMakeLists.txt +++ b/src/extractors/CMakeLists.txt @@ -105,6 +105,24 @@ TARGETS kfilemetadata_poextractor DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata) +# +# Postscript DSC +# +add_library(kfilemetadata_postscriptdscextractor MODULE + postscriptdscextractor.cpp + ../kfilemetadata_debug.cpp +) +target_link_libraries( kfilemetadata_postscriptdscextractor + KF5::FileMetaData + Qt5::Core +) + +set_target_properties(kfilemetadata_postscriptdscextractor PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kfilemetadata") +install( + TARGETS kfilemetadata_postscriptdscextractor + DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kfilemetadata +) + # # ODF # diff --git a/src/extractors/postscriptdscextractor.h b/src/extractors/postscriptdscextractor.h new file mode 100644 --- /dev/null +++ b/src/extractors/postscriptdscextractor.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2018 Stefan Brüns + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#ifndef DSC_EXTRACTOR_H +#define DSC_EXTRACTOR_H + +#include "extractorplugin.h" + +namespace KFileMetaData +{ + +class DscExtractor : public ExtractorPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.kde.kf5.kfilemetadata.ExtractorPlugin") + Q_INTERFACES(KFileMetaData::ExtractorPlugin) + +public: + explicit DscExtractor(QObject* parent = nullptr); + + QStringList mimetypes() const override; + void extract(ExtractionResult* result) override; + +private: +}; + +} + +#endif // DSC_EXTRACTOR_H diff --git a/src/extractors/postscriptdscextractor.cpp b/src/extractors/postscriptdscextractor.cpp new file mode 100644 --- /dev/null +++ b/src/extractors/postscriptdscextractor.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2018 Stefan Brüns + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "postscriptdscextractor.h" +#include "kfilemetadata_debug.h" + +#include +#include + +namespace KFileMetaData +{ + +DscExtractor::DscExtractor(QObject* parent) + : ExtractorPlugin(parent) +{ + +} + +QStringList DscExtractor::mimetypes() const +{ + QStringList list; + list << QStringLiteral("application/postscript") + << QStringLiteral("image/x-eps"); + + return list; +} + +void DscExtractor::extract(ExtractionResult* result) +{ + auto flags = result->inputFlags(); + if (!(flags & ExtractionResult::ExtractMetaData)) { + return; + } + + QFile file(result->inputUrl()); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(KFILEMETADATA_LOG) << "Document is not a valid file"; + return; + } + + // A little bit heuristic - assume EPS files are images, PS complete documents + if (result->inputMimetype() == QLatin1String("application/postscript")) { + result->addType(Type::Document); + } else { + result->addType(Type::Image); + } + + // Try to find some DSC (PostScript Language Document Structuring Conventions) conforming data + QTextStream stream(&file); + QString line; + + while (stream.readLineInto(&line)) { + if (!line.startsWith(QLatin1String("%%"))) { + continue; + } + + if (line.startsWith(QLatin1String("%%Pages:"))) { + bool ok = false; + int pages = line.midRef(8).toInt(&ok, 10); + if (ok) { + result->add(Property::PageCount, pages); + } + + } else if (line.startsWith(QLatin1String("%%Title:"))) { + QStringRef title = line.midRef(8); + title = title.trimmed(); + if (title.startsWith(QLatin1Char('(')) && title.endsWith(QLatin1Char(')'))) { + title = title.mid(1, title.size() - 2); + } + if (!title.isEmpty()) { + result->add(Property::Title, title.toString()); + } + + } else if (line.startsWith(QLatin1String("%%CreationDate:"))) { + // "Neither the date nor time need be in any standard format." + QStringRef date = line.midRef(15); + date = date.trimmed(); + if (date.startsWith(QLatin1Char('(')) && date.endsWith(QLatin1Char(')'))) { + date = date.mid(1, date.size() - 2); + date = date.trimmed(); + } + if (date.startsWith(QLatin1String("D:")) && date.size() >= 23) { + // Standard PDF date format, ASN.1 like - (D:YYYYMMDDHHmmSSOHH'mm') + auto dt = QDateTime::fromString(date.mid(2, 14).toString(), QLatin1String("yyyyMMddhhmmss")); + auto offset = QTime::fromString(date.mid(17, 5).toString(), QLatin1String("hh'\\''mm")); + if (date.mid(16,1) == QLatin1String("+")) { + dt.setOffsetFromUtc(QTime(0, 0).secsTo(offset)); + } else { + dt.setOffsetFromUtc(-1 * QTime(0, 0).secsTo(offset)); + } + result->add(Property::CreationDate, dt); + } else { + auto dt = QDateTime::fromString(date.toString()); + if (dt.isValid()) { + result->add(Property::CreationDate, dt); + } + } + + } else if (line.startsWith(QLatin1String("%%EndComments"))) { + break; + } + } +} + +} // namespace KFileMetaData