File indexing completed on 2024-05-19 03:56:44

0001 /*
0002     SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 
0008 #include "kritaextractor.h"
0009 #include "kfilemetadata_debug.h"
0010 
0011 #include <KZip>
0012 #include <QXmlStreamReader>
0013 
0014 using namespace KFileMetaData;
0015 
0016 KritaExtractor::KritaExtractor(QObject* parent)
0017     : ExtractorPlugin(parent)
0018 {
0019 }
0020 
0021 QStringList KritaExtractor::mimetypes() const
0022 {
0023     return {QStringLiteral("application/x-krita")};
0024 }
0025 
0026 void KritaExtractor::extract(ExtractionResult* result)
0027 {
0028     // Krita files are secretly zip files
0029     KZip zip(result->inputUrl());
0030     if (!zip.open(QIODevice::ReadOnly)) {
0031         return;
0032     }
0033 
0034     result->addType(Type::Image);
0035 
0036     if (!result->inputFlags().testFlag(ExtractionResult::ExtractMetaData)) {
0037         return;
0038     }
0039 
0040     // Read main image information, e.g width and height
0041     {
0042         const KArchiveFile *entry = zip.directory()->file(QLatin1String("maindoc.xml"));
0043         if (!entry) {
0044             return;
0045         }
0046 
0047         std::unique_ptr<QIODevice> fileDevice{entry->createDevice()};
0048 
0049         // There is only one element of the maindoc we care about: the IMAGE element.
0050         // The document width and height are stored as attributes.
0051         QXmlStreamReader xml{fileDevice.get()};
0052         while (xml.readNextStartElement()) {
0053             if (xml.name() == QLatin1String("IMAGE")) {
0054                 bool ok = false;
0055                 const int width = xml.attributes().value(QLatin1String("width")).toInt(&ok);
0056                 if (ok && width != 0) {
0057                     result->add(Property::Width, width);
0058                 }
0059 
0060                 const int height = xml.attributes().value(QLatin1String("height")).toInt(&ok);
0061                 if (ok && height != 0) {
0062                     result->add(Property::Height, height);
0063                 }
0064 
0065                 break;
0066             }
0067         }
0068     }
0069 
0070     // Read extra metadata entered by the author, e.g. title and description
0071     {
0072         const KArchiveFile *entry = zip.directory()->file(QLatin1String("documentinfo.xml"));
0073         if (!entry) {
0074             return;
0075         }
0076 
0077         std::unique_ptr<QIODevice> fileDevice{entry->createDevice()};
0078 
0079         // The documentinfo xml schema is very simple, every field in the GUI is exposed
0080         // as an element of the same name. The one exception is "description" which seems
0081         // to be stored under <abstract>.
0082         static const QHash<QStringView, Property::Property> propertyMapping {{
0083             {QStringLiteral("title"), Property::Title},
0084             {QStringLiteral("license"), Property::License},
0085             {QStringLiteral("keyword"), Property::Keywords},
0086             {QStringLiteral("abstract"), Property::Description},
0087         }};
0088 
0089         QXmlStreamReader xml{fileDevice.get()};
0090         if (!xml.readNextStartElement()  || xml.name() != QStringLiteral("document-info")) {
0091             return;
0092         }
0093 
0094         while (xml.readNextStartElement()) {
0095             const QStringView elementName = xml.name();
0096 
0097             if (elementName == QStringLiteral("about")) {
0098                 while (xml.readNextStartElement()) {
0099                     const QStringView childElementName = xml.name();
0100 
0101                     const Property::Property property = propertyMapping.value(childElementName);
0102                     if (property != Property::Empty) {
0103                         const QString value = xml.readElementText();
0104                         result->add(property, value);
0105                     } else if (childElementName == QStringLiteral("creation-date")) {
0106                         const QString value = xml.readElementText();
0107 
0108                             const QDateTime creationDate = QDateTime::fromString(value, Qt::ISODate);
0109                             if (creationDate.isValid()) {
0110                                 result->add(Property::CreationDate, creationDate);
0111                             }
0112                         } else {
0113                             xml.skipCurrentElement();
0114                         }
0115                     }
0116                 } else if (elementName == QStringLiteral("author")) {
0117                     while (xml.readNextStartElement()) {
0118                         const QStringView childElementName = xml.name();
0119 
0120                     if (childElementName == QStringLiteral("full-name")) {
0121                         const QString value = xml.readElementText();
0122                         if (!value.isEmpty()) {
0123                             result->add(Property::Author, value);
0124                         }
0125                     } else {
0126                         xml.skipCurrentElement();
0127                     }
0128                 }
0129             } else {
0130                 xml.skipCurrentElement();
0131             }
0132         }
0133     }
0134 }
0135 
0136 #include "moc_kritaextractor.cpp"