File indexing completed on 2024-04-14 04:47:22

0001 /*
0002     SPDX-FileCopyrightText: 2008 Marco Gittler <g.marco@freenet.de>
0003     SPDX-FileCopyrightText: Rafał Lalik
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 /*
0009  *                                                                         *
0010  *   Modifications by Rafał Lalik to implement Patterns mechanism          *
0011  *                                                                         *
0012  ***************************************************************************/
0013 
0014 #include "titledocument.h"
0015 #include "gradientwidget.h"
0016 
0017 #include "graphicsscenerectmove.h"
0018 #include "kdenlivesettings.h"
0019 #include "utils/timecode.h"
0020 #include "xml/xml.hpp"
0021 
0022 #include <KIO/FileCopyJob>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 
0026 #include "kdenlive_debug.h"
0027 #include <QApplication>
0028 #include <QCryptographicHash>
0029 #include <QDir>
0030 #include <QDomElement>
0031 #include <QFile>
0032 #include <QFontInfo>
0033 #include <QGraphicsItem>
0034 #include <QGraphicsRectItem>
0035 #include <QGraphicsScene>
0036 #include <QGraphicsSvgItem>
0037 #include <QGraphicsTextItem>
0038 #include <QSaveFile>
0039 #include <QSvgRenderer>
0040 #include <QTextCursor>
0041 #include <locale>
0042 #ifdef Q_OS_MAC
0043 #include <xlocale.h>
0044 #endif
0045 
0046 #include <QGraphicsBlurEffect>
0047 #include <QGraphicsDropShadowEffect>
0048 #include <QGraphicsEffect>
0049 #include <QPainter>
0050 
0051 QByteArray fileToByteArray(const QString &filename)
0052 {
0053     QByteArray ret;
0054     QFile file(filename);
0055     if (file.open(QIODevice::ReadOnly)) {
0056         while (!file.atEnd()) {
0057             ret.append(file.readLine());
0058         }
0059     }
0060     return ret;
0061 }
0062 
0063 TitleDocument::TitleDocument()
0064 {
0065     m_scene = nullptr;
0066     m_width = 0;
0067     m_height = 0;
0068     m_missingElements = 0;
0069 }
0070 
0071 void TitleDocument::setScene(QGraphicsScene *_scene, int width, int height)
0072 {
0073     m_scene = _scene;
0074     m_width = width;
0075     m_height = height;
0076 }
0077 
0078 int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed, const QString &projectPath)
0079 {
0080     if (embed) {
0081         if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
0082             content.setAttribute(QStringLiteral("base64"), item->data(Qt::UserRole + 1).toString());
0083         } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
0084             content.setAttribute(QStringLiteral("base64"), fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
0085         }
0086         content.removeAttribute(QStringLiteral("url"));
0087     } else {
0088         // save for project files to disk
0089         QString base64 = item->data(Qt::UserRole + 1).toString();
0090         if (!base64.isEmpty()) {
0091             QString titlePath;
0092             if (!projectPath.isEmpty()) {
0093                 titlePath = projectPath;
0094             } else {
0095                 titlePath = QStringLiteral("/tmp/titles/");
0096             }
0097             QString filename = extractBase64Image(titlePath, base64);
0098             if (!filename.isEmpty()) {
0099                 content.setAttribute(QStringLiteral("url"), filename);
0100                 content.removeAttribute(QStringLiteral("base64"));
0101             }
0102 
0103         } else {
0104             return 1;
0105         }
0106     }
0107     return 0;
0108 }
0109 
0110 // static
0111 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
0112 {
0113     QDir dir(titlePath);
0114     dir.mkpath(titlePath);
0115     const QString filename = dir.absoluteFilePath(QString(QCryptographicHash::hash(data.toLatin1(), QCryptographicHash::Md5).toHex().append(".titlepart")));
0116     QFile f(filename);
0117     if (f.open(QIODevice::WriteOnly)) {
0118         f.write(QByteArray::fromBase64(data.toLatin1()));
0119         f.close();
0120         return filename;
0121     }
0122     return QString();
0123 }
0124 
0125 QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed)
0126 {
0127     return xml(m_scene->items(), m_width, m_height, startv, endv, embed, m_projectPath);
0128 }
0129 
0130 QDomDocument TitleDocument::xml(const QList<QGraphicsItem *> &items, int width, int height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed,
0131                                 const QString &projectPath)
0132 {
0133     QDomDocument doc;
0134 
0135     QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle"));
0136     main.setAttribute(QStringLiteral("width"), width);
0137     main.setAttribute(QStringLiteral("height"), height);
0138 
0139     // Save locale. Since 20.08, we always use the C locale for serialising.
0140     main.setAttribute(QStringLiteral("LC_NUMERIC"), "C");
0141     doc.appendChild(main);
0142     QTextCursor cur;
0143     QTextBlockFormat format;
0144 
0145     for (QGraphicsItem *item : items) {
0146         if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) {
0147             continue;
0148         }
0149         QDomElement e = doc.createElement(QStringLiteral("item"));
0150         QDomElement content = doc.createElement(QStringLiteral("content"));
0151         QFont font;
0152         QString gradient;
0153         MyTextItem *t;
0154         double xPosition = item->pos().x();
0155 
0156         switch (item->type()) {
0157         case 7:
0158             e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem"));
0159             content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString());
0160             base64ToUrl(item, content, embed, projectPath);
0161             break;
0162         case 13:
0163             e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem"));
0164             content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString());
0165             base64ToUrl(item, content, embed, projectPath);
0166             break;
0167         case 3:
0168             e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem"));
0169             content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast<QGraphicsRectItem *>(item)->rect().normalized()));
0170             content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast<QGraphicsRectItem *>(item)->pen().color()));
0171             if (static_cast<QGraphicsRectItem *>(item)->pen() == Qt::NoPen) {
0172                 content.setAttribute(QStringLiteral("penwidth"), 0);
0173             } else {
0174                 content.setAttribute(QStringLiteral("penwidth"), static_cast<QGraphicsRectItem *>(item)->pen().width());
0175             }
0176             content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast<QGraphicsRectItem *>(item)->brush().color()));
0177             gradient = item->data(TitleDocument::Gradient).toString();
0178             if (!gradient.isEmpty()) {
0179                 content.setAttribute(QStringLiteral("gradient"), gradient);
0180             }
0181             break;
0182         case 4:
0183             e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsEllipseItem"));
0184             content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast<QGraphicsEllipseItem *>(item)->rect().normalized()));
0185             content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast<QGraphicsEllipseItem *>(item)->pen().color()));
0186             if (static_cast<QGraphicsEllipseItem *>(item)->pen() == Qt::NoPen) {
0187                 content.setAttribute(QStringLiteral("penwidth"), 0);
0188             } else {
0189                 content.setAttribute(QStringLiteral("penwidth"), static_cast<QGraphicsEllipseItem *>(item)->pen().width());
0190             }
0191             content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast<QGraphicsEllipseItem *>(item)->brush().color()));
0192             gradient = item->data(TitleDocument::Gradient).toString();
0193             if (!gradient.isEmpty()) {
0194                 content.setAttribute(QStringLiteral("gradient"), gradient);
0195             }
0196             break;
0197         case 8:
0198             e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem"));
0199             t = static_cast<MyTextItem *>(item);
0200             // Don't save empty text nodes
0201             if (t->toPlainText().simplified().isEmpty()) {
0202                 continue;
0203             }
0204             // content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
0205             content.appendChild(doc.createTextNode(t->toPlainText()));
0206             font = t->font();
0207             content.setAttribute(QStringLiteral("font"), font.family());
0208             content.setAttribute(QStringLiteral("font-weight"), font.weight());
0209             content.setAttribute(QStringLiteral("font-pixel-size"), font.pixelSize());
0210             content.setAttribute(QStringLiteral("font-italic"), static_cast<int>(font.italic()));
0211             content.setAttribute(QStringLiteral("font-underline"), static_cast<int>(font.underline()));
0212             content.setAttribute(QStringLiteral("letter-spacing"), QString::number(font.letterSpacing()));
0213             gradient = item->data(TitleDocument::Gradient).toString();
0214             if (!gradient.isEmpty()) {
0215                 content.setAttribute(QStringLiteral("gradient"), gradient);
0216             }
0217             cur = QTextCursor(t->document());
0218             cur.select(QTextCursor::Document);
0219             format = cur.blockFormat();
0220             if (t->toPlainText() == QLatin1String("%s")) {
0221                 // template text box, adjust size for later replacement text
0222                 if (t->alignment() == Qt::AlignHCenter) {
0223                     // grow dimensions on both sides
0224                     double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2;
0225                     double offset = qMin(xcenter, width - xcenter);
0226                     xPosition = xcenter - offset;
0227                     content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset));
0228                 } else if (t->alignment() == Qt::AlignRight) {
0229                     // grow to the left
0230                     double offset = item->pos().x() + (t->baseBoundingRect().width());
0231                     xPosition = 0;
0232                     content.setAttribute(QStringLiteral("box-width"), QString::number(offset));
0233                 } else {
0234                     // left align, grow on right side
0235                     double offset = width - item->pos().x();
0236                     content.setAttribute(QStringLiteral("box-width"), QString::number(offset));
0237                 }
0238             } else {
0239                 content.setAttribute(QStringLiteral("box-width"), QString::number(t->baseBoundingRect().width()));
0240             }
0241             content.setAttribute(QStringLiteral("box-height"), QString::number(t->baseBoundingRect().height()));
0242             if (!t->data(TitleDocument::LineSpacing).isNull()) {
0243                 content.setAttribute(QStringLiteral("line-spacing"), QString::number(t->data(TitleDocument::LineSpacing).toInt()));
0244             }
0245             {
0246                 QTextCursor cursor(t->document());
0247                 cursor.select(QTextCursor::Document);
0248                 QColor fontcolor = cursor.charFormat().foreground().color();
0249                 content.setAttribute(QStringLiteral("font-color"), colorToString(fontcolor));
0250                 if (!t->data(TitleDocument::OutlineWidth).isNull()) {
0251                     content.setAttribute(QStringLiteral("font-outline"), QString::number(t->data(TitleDocument::OutlineWidth).toDouble()));
0252                 }
0253                 if (!t->data(TitleDocument::OutlineColor).isNull()) {
0254                     QVariant variant = t->data(TitleDocument::OutlineColor);
0255                     QColor outlineColor = variant.value<QColor>();
0256                     content.setAttribute(QStringLiteral("font-outline-color"), colorToString(outlineColor));
0257                 }
0258             }
0259             if (!t->data(100).isNull()) {
0260                 QStringList effectParams = t->data(100).toStringList();
0261                 QString effectName = effectParams.takeFirst();
0262                 content.setAttribute(QStringLiteral("textwidth"), QString::number(t->sceneBoundingRect().width()));
0263                 content.setAttribute(effectName, effectParams.join(QLatin1Char(';')));
0264             }
0265 
0266             // Only save when necessary.
0267             if (t->data(OriginXLeft).toInt() == AxisInverted) {
0268                 content.setAttribute(QStringLiteral("kdenlive-axis-x-inverted"), t->data(OriginXLeft).toInt());
0269             }
0270             if (t->data(OriginYTop).toInt() == AxisInverted) {
0271                 content.setAttribute(QStringLiteral("kdenlive-axis-y-inverted"), t->data(OriginYTop).toInt());
0272             }
0273             if (t->textWidth() > 0) {
0274                 content.setAttribute(QStringLiteral("alignment"), int(t->alignment()));
0275             }
0276 
0277             content.setAttribute(QStringLiteral("shadow"), t->shadowInfo().join(QLatin1Char(';')));
0278             content.setAttribute(QStringLiteral("typewriter"), t->twInfo().join(QLatin1Char(';')));
0279             break;
0280         default:
0281             continue;
0282         }
0283 
0284         // position
0285         QDomElement pos = doc.createElement(QStringLiteral("position"));
0286         pos.setAttribute(QStringLiteral("x"), QString::number(xPosition));
0287         pos.setAttribute(QStringLiteral("y"), QString::number(item->pos().y()));
0288         QTransform transform = item->transform();
0289         QDomElement tr = doc.createElement(QStringLiteral("transform"));
0290         if (!item->data(TitleDocument::ZoomFactor).isNull()) {
0291             tr.setAttribute(QStringLiteral("zoom"), QString::number(item->data(TitleDocument::ZoomFactor).toInt()));
0292         }
0293         if (!item->data(TitleDocument::RotateFactor).isNull()) {
0294             QList<QVariant> rotlist = item->data(TitleDocument::RotateFactor).toList();
0295             tr.setAttribute(QStringLiteral("rotation"),
0296                             QStringLiteral("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
0297         }
0298         tr.appendChild(doc.createTextNode(QStringLiteral("%1,%2,%3,%4,%5,%6,%7,%8,%9")
0299                                               .arg(transform.m11())
0300                                               .arg(transform.m12())
0301                                               .arg(transform.m13())
0302                                               .arg(transform.m21())
0303                                               .arg(transform.m22())
0304                                               .arg(transform.m23())
0305                                               .arg(transform.m31())
0306                                               .arg(transform.m32())
0307                                               .arg(transform.m33())));
0308         e.setAttribute(QStringLiteral("z-index"), item->zValue());
0309         pos.appendChild(tr);
0310         e.appendChild(pos);
0311         e.appendChild(content);
0312         if (item->zValue() > -1000) {
0313             main.appendChild(e);
0314         }
0315     }
0316     if ((startv != nullptr) && (endv != nullptr)) {
0317         QDomElement endport = doc.createElement(QStringLiteral("endviewport"));
0318         QDomElement startport = doc.createElement(QStringLiteral("startviewport"));
0319         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
0320         endport.setAttribute(QStringLiteral("rect"), rectFToString(r));
0321         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
0322         startport.setAttribute(QStringLiteral("rect"), rectFToString(r2));
0323 
0324         main.appendChild(startport);
0325         main.appendChild(endport);
0326     }
0327     QDomElement backgr = doc.createElement(QStringLiteral("background"));
0328     QColor color = getBackgroundColor(items);
0329     backgr.setAttribute(QStringLiteral("color"), colorToString(color));
0330     main.appendChild(backgr);
0331 
0332     return doc;
0333 }
0334 
0335 /** \brief Get the background color (incl. alpha) from the document, if possibly
0336  * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
0337 QColor TitleDocument::getBackgroundColor() const
0338 {
0339     QColor color(0, 0, 0, 0);
0340     if (m_scene) {
0341         return getBackgroundColor(m_scene->items());
0342     }
0343     return color;
0344 }
0345 
0346 /** \brief Get the background color (incl. alpha) from list of items, if possibly
0347  * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
0348 QColor TitleDocument::getBackgroundColor(const QList<QGraphicsItem *> &items)
0349 {
0350     QColor color(0, 0, 0, 0);
0351     for (auto item : qAsConst(items)) {
0352         if (int(item->zValue()) == -1100) {
0353             color = static_cast<QGraphicsRectItem *>(item)->brush().color();
0354             return color;
0355         }
0356     }
0357     return color;
0358 }
0359 
0360 bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed)
0361 {
0362     if (!m_scene) {
0363         return false;
0364     }
0365 
0366     QDomDocument doc = xml(startv, endv, embed);
0367     doc.documentElement().setAttribute(QStringLiteral("duration"), duration);
0368     // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12
0369     doc.documentElement().setAttribute(QStringLiteral("out"), duration);
0370     return Xml::docContentToFile(doc, url.toLocalFile());
0371 }
0372 
0373 int TitleDocument::loadFromXml(const QDomDocument &doc, GraphicsSceneRectMove *scene, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration,
0374                                const QString &projectpath)
0375 {
0376     m_projectPath = projectpath;
0377 
0378     QList<QGraphicsItem *> items;
0379 
0380     int width, height;
0381     int res = loadFromXml(doc, items, width, height, scene, startv, endv, duration, m_missingElements);
0382 
0383     if (m_width != width || m_height != height) {
0384         KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
0385         // TODO: convert using QTransform
0386         m_width = width;
0387         m_height = height;
0388     }
0389 
0390     for (auto i : qAsConst(items)) {
0391         m_scene->addItem(i);
0392     }
0393     return res;
0394 }
0395 
0396 int TitleDocument::loadFromXml(const QDomDocument &doc, QList<QGraphicsItem *> &gitems, int &width, int &height, GraphicsSceneRectMove *scene,
0397                                QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, int &missingElements)
0398 {
0399     for (auto *i : gitems) {
0400         delete i;
0401     }
0402     gitems.clear();
0403 
0404     missingElements = 0;
0405     QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle"));
0406     // TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
0407     if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) {
0408         width = doc.documentElement().attribute(QStringLiteral("width")).toInt();
0409         height = doc.documentElement().attribute(QStringLiteral("height")).toInt();
0410     } else {
0411         // Document has no size info, it is likely an old version title, so ignore viewport data
0412         QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport"));
0413         if (!viewportlist.isEmpty()) {
0414             doc.documentElement().removeChild(viewportlist.at(0));
0415         }
0416         viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("endviewport"));
0417         if (!viewportlist.isEmpty()) {
0418             doc.documentElement().removeChild(viewportlist.at(0));
0419         }
0420     }
0421     if (doc.documentElement().hasAttribute(QStringLiteral("duration"))) {
0422         *duration = doc.documentElement().attribute(QStringLiteral("duration")).toInt();
0423     } else if (doc.documentElement().hasAttribute(QStringLiteral("out"))) {
0424         *duration = doc.documentElement().attribute(QStringLiteral("out")).toInt();
0425     } else {
0426         *duration = Timecode().getFrameCount(KdenliveSettings::title_duration());
0427     }
0428 
0429     int maxZValue = 0;
0430     if (!titles.isEmpty()) {
0431         QDomNodeList items = titles.item(0).childNodes();
0432         for (int i = 0; i < items.count(); ++i) {
0433             QGraphicsItem *gitem = nullptr;
0434             QDomNode itemNode = items.item(i);
0435             // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("type").nodeValue();
0436             int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt();
0437             double xPosition = itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("x")).nodeValue().toDouble();
0438             if (zValue > -1000) {
0439                 if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) {
0440                     QDomNamedNodeMap txtProperties = itemNode.namedItem(QStringLiteral("content")).attributes();
0441                     QFont font(txtProperties.namedItem(QStringLiteral("font")).nodeValue());
0442 
0443                     QDomNode node = txtProperties.namedItem(QStringLiteral("font-bold"));
0444                     if (!node.isNull()) {
0445                         // Old: Bold/Not bold.
0446                         font.setBold(node.nodeValue().toInt() != 0);
0447                     } else {
0448                         // New: Font weight (QFont::)
0449                         font.setWeight(QFont::Weight(txtProperties.namedItem(QStringLiteral("font-weight")).nodeValue().toInt()));
0450                     }
0451                     // font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
0452                     font.setItalic(txtProperties.namedItem(QStringLiteral("font-italic")).nodeValue().toInt() != 0);
0453                     font.setUnderline(txtProperties.namedItem(QStringLiteral("font-underline")).nodeValue().toInt() != 0);
0454                     // Older Kdenlive version did not store pixel size but point size
0455                     if (txtProperties.namedItem(QStringLiteral("font-pixel-size")).isNull()) {
0456                         KMessageBox::information(QApplication::activeWindow(),
0457                                                  i18n("Some of your text clips were saved with size in points, which means "
0458                                                       "different sizes on different displays. They will be converted to pixel "
0459                                                       "size, making them portable, but you could have to adjust their size."),
0460                                                  i18n("Text Clips Updated"));
0461                         QFont f2;
0462                         f2.setPointSize(txtProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt());
0463                         font.setPixelSize(QFontInfo(f2).pixelSize());
0464                     } else {
0465                         font.setPixelSize(txtProperties.namedItem(QStringLiteral("font-pixel-size")).nodeValue().toInt());
0466                     }
0467                     font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt());
0468                     QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue()));
0469                     MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), nullptr);
0470                     gitems.append(txt);
0471                     txt->setFont(font);
0472                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
0473                     QTextCursor cursor(txt->document());
0474                     cursor.select(QTextCursor::Document);
0475                     QTextCharFormat cformat = cursor.charFormat();
0476                     if (txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble() > 0.0) {
0477                         txt->setData(TitleDocument::OutlineWidth, txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble());
0478                         txt->setData(TitleDocument::OutlineColor, stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue()));
0479                         cformat.setTextOutline(QPen(QColor(stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())),
0480                                                     txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap,
0481                                                     Qt::RoundJoin));
0482                     }
0483                     if (!txtProperties.namedItem(QStringLiteral("line-spacing")).isNull()) {
0484                         int lineSpacing = txtProperties.namedItem(QStringLiteral("line-spacing")).nodeValue().toInt();
0485                         QTextBlockFormat format = cursor.blockFormat();
0486                         format.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight);
0487                         cursor.setBlockFormat(format);
0488                         txt->setData(TitleDocument::LineSpacing, lineSpacing);
0489                     }
0490                     txt->setTextColor(col);
0491                     cformat.setForeground(QBrush(col));
0492                     cursor.setCharFormat(cformat);
0493                     if (!txtProperties.namedItem(QStringLiteral("gradient")).isNull()) {
0494                         // Gradient color
0495                         QString data = txtProperties.namedItem(QStringLiteral("gradient")).nodeValue();
0496                         txt->setData(TitleDocument::Gradient, data);
0497                         QLinearGradient gr = GradientWidget::gradientFromString(data, int(txt->boundingRect().width()), int(txt->boundingRect().height()));
0498                         cformat.setForeground(QBrush(gr));
0499                         cursor.setCharFormat(cformat);
0500                     }
0501 
0502                     if (!txtProperties.namedItem(QStringLiteral("alignment")).isNull()) {
0503                         txt->setAlignment(Qt::Alignment(txtProperties.namedItem(QStringLiteral("alignment")).nodeValue().toInt()));
0504                     }
0505 
0506                     if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).isNull()) {
0507                         txt->setData(OriginXLeft, txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).nodeValue().toInt());
0508                     }
0509                     if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).isNull()) {
0510                         txt->setData(OriginYTop, txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).nodeValue().toInt());
0511                     }
0512 
0513                     if (!txtProperties.namedItem(QStringLiteral("shadow")).isNull()) {
0514                         QString info = txtProperties.namedItem(QStringLiteral("shadow")).nodeValue();
0515                         txt->loadShadow(info.split(QLatin1Char(';')));
0516                     }
0517 
0518                     // Effects
0519                     if (!txtProperties.namedItem(QStringLiteral("typewriter")).isNull()) {
0520                         QString info = txtProperties.namedItem(QStringLiteral("typewriter")).nodeValue();
0521                         txt->loadTW(info.split(QLatin1Char(';')));
0522                     }
0523                     if (txt->toPlainText() == QLatin1String("%s")) {
0524                         // template text box, adjust size for later replacement text
0525                         if (txt->alignment() == Qt::AlignHCenter) {
0526                             // grow dimensions on both sides
0527                             double boxWidth = txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble();
0528                             double xcenter = (boxWidth - xPosition) / 2.0;
0529                             xPosition = xcenter - txt->boundingRect().width() / 2;
0530                         } else if (txt->alignment() == Qt::AlignRight) {
0531                             // grow to the left
0532                             xPosition = xPosition + txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble() - txt->boundingRect().width();
0533                         } else {
0534                             // left align, grow on right side, nothing to do
0535                         }
0536                     }
0537 
0538                     gitem = txt;
0539                 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsRectItem")) {
0540                     QDomNamedNodeMap rectProperties = itemNode.namedItem(QStringLiteral("content")).attributes();
0541                     QString rect = rectProperties.namedItem(QStringLiteral("rect")).nodeValue();
0542                     QString br_str = rectProperties.namedItem(QStringLiteral("brushcolor")).nodeValue();
0543                     QString pen_str = rectProperties.namedItem(QStringLiteral("pencolor")).nodeValue();
0544                     double penwidth = rectProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble();
0545                     auto *rec = new MyRectItem();
0546                     rec->setRect(stringToRect(rect));
0547                     if (penwidth > 0) {
0548                         rec->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
0549                     } else {
0550                         rec->setPen(Qt::NoPen);
0551                     }
0552                     if (!rectProperties.namedItem(QStringLiteral("gradient")).isNull()) {
0553                         // Gradient color
0554                         QString data = rectProperties.namedItem(QStringLiteral("gradient")).nodeValue();
0555                         rec->setData(TitleDocument::Gradient, data);
0556                         QLinearGradient gr = GradientWidget::gradientFromString(data, int(rec->rect().width()), int(rec->rect().height()));
0557                         rec->setBrush(QBrush(gr));
0558                     } else {
0559                         rec->setBrush(QBrush(stringToColor(br_str)));
0560                     }
0561                     gitems.append(rec);
0562 
0563                     gitem = rec;
0564                 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsEllipseItem")) {
0565                     QDomNamedNodeMap ellipseProperties = itemNode.namedItem(QStringLiteral("content")).attributes();
0566                     QString rect = ellipseProperties.namedItem(QStringLiteral("rect")).nodeValue();
0567                     QString br_str = ellipseProperties.namedItem(QStringLiteral("brushcolor")).nodeValue();
0568                     QString pen_str = ellipseProperties.namedItem(QStringLiteral("pencolor")).nodeValue();
0569                     double penwidth = ellipseProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble();
0570                     auto *ellipse = new MyEllipseItem();
0571                     ellipse->setRect(stringToRect(rect));
0572                     if (penwidth > 0) {
0573                         ellipse->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
0574                     } else {
0575                         ellipse->setPen(Qt::NoPen);
0576                     }
0577                     if (!ellipseProperties.namedItem(QStringLiteral("gradient")).isNull()) {
0578                         // Gradient color
0579                         QString data = ellipseProperties.namedItem(QStringLiteral("gradient")).nodeValue();
0580                         ellipse->setData(TitleDocument::Gradient, data);
0581                         QLinearGradient gr = GradientWidget::gradientFromString(data, int(ellipse->rect().width()), int(ellipse->rect().height()));
0582                         ellipse->setBrush(QBrush(gr));
0583                     } else {
0584                         ellipse->setBrush(QBrush(stringToColor(br_str)));
0585                     }
0586                     gitems.append(ellipse);
0587 
0588                     gitem = ellipse;
0589                 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) {
0590                     QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue();
0591                     QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue();
0592                     QPixmap pix;
0593                     bool missing = false;
0594                     if (base64.isEmpty()) {
0595                         pix.load(url);
0596                         if (pix.isNull()) {
0597                             pix = createInvalidPixmap(url, height);
0598                             missingElements++;
0599                             missing = true;
0600                         }
0601                     } else {
0602                         pix.loadFromData(QByteArray::fromBase64(base64.toLatin1()));
0603                     }
0604                     auto *rec = new MyPixmapItem(pix);
0605                     if (missing) {
0606                         rec->setData(Qt::UserRole + 2, 1);
0607                     }
0608                     gitems.append(rec);
0609                     rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
0610                     rec->setData(Qt::UserRole, url);
0611                     if (!base64.isEmpty()) {
0612                         rec->setData(Qt::UserRole + 1, base64);
0613                     }
0614                     gitem = rec;
0615                 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsSvgItem")) {
0616                     QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue();
0617                     QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue();
0618                     QGraphicsSvgItem *rec = nullptr;
0619                     if (base64.isEmpty()) {
0620                         if (QFile::exists(url)) {
0621                             rec = new MySvgItem(url);
0622                         }
0623                     } else {
0624                         rec = new MySvgItem();
0625                         QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec);
0626                         rec->setSharedRenderer(renderer);
0627                         // QString elem=rec->elementId();
0628                         // QRectF bounds = renderer->boundsOnElement(elem);
0629                     }
0630                     if (rec) {
0631                         gitems.append(rec);
0632                         rec->setData(Qt::UserRole, url);
0633                         if (!base64.isEmpty()) {
0634                             rec->setData(Qt::UserRole + 1, base64);
0635                         }
0636                         gitem = rec;
0637                     } else {
0638                         QPixmap pix = createInvalidPixmap(url, height);
0639                         missingElements++;
0640                         auto *rec2 = new MyPixmapItem(pix);
0641                         rec2->setData(Qt::UserRole + 2, 1);
0642                         gitems.append(rec2);
0643                         rec2->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
0644                         rec2->setData(Qt::UserRole, url);
0645                         gitem = rec2;
0646                     }
0647                 }
0648             }
0649             // pos and transform
0650             if (gitem) {
0651                 QPointF p(xPosition, itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("y")).nodeValue().toDouble());
0652                 gitem->setPos(p);
0653                 QDomElement trans = itemNode.namedItem(QStringLiteral("position")).firstChild().toElement();
0654                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
0655                 QString rotate = trans.attribute(QStringLiteral("rotation"));
0656                 if (!rotate.isEmpty()) {
0657                     gitem->setData(TitleDocument::RotateFactor, stringToList(rotate));
0658                 }
0659                 QString zoom = trans.attribute(QStringLiteral("zoom"));
0660                 if (!zoom.isEmpty()) {
0661                     gitem->setData(TitleDocument::ZoomFactor, zoom.toInt());
0662                 }
0663                 if (zValue >= maxZValue) {
0664                     maxZValue = zValue + 1;
0665                 }
0666                 gitem->setZValue(zValue);
0667                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
0668 
0669                 // effects
0670                 QDomNode eff = itemNode.namedItem(QStringLiteral("effect"));
0671                 if (!eff.isNull()) {
0672                     QDomElement e = eff.toElement();
0673                     if (e.attribute(QStringLiteral("type")) == QLatin1String("blur")) {
0674                         auto *blur = new QGraphicsBlurEffect();
0675                         blur->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt());
0676                         gitem->setGraphicsEffect(blur);
0677                     } else if (e.attribute(QStringLiteral("type")) == QLatin1String("shadow")) {
0678                         auto *shadow = new QGraphicsDropShadowEffect();
0679                         shadow->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt());
0680                         shadow->setOffset(e.attribute(QStringLiteral("xoffset")).toInt(), e.attribute(QStringLiteral("yoffset")).toInt());
0681                         gitem->setGraphicsEffect(shadow);
0682                     }
0683                 }
0684             }
0685 
0686             if (itemNode.nodeName() == QLatin1String("background")) {
0687                 // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("color").nodeValue();
0688                 QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue()));
0689                 // color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt());
0690                 if (scene) {
0691                     QList<QGraphicsItem *> sceneItems = scene->items();
0692                     for (auto sceneItem : qAsConst(sceneItems)) {
0693                         if (int(sceneItem->zValue()) == -1100) {
0694                             static_cast<QGraphicsRectItem *>(sceneItem)->setBrush(QBrush(color));
0695                             break;
0696                         }
0697                     }
0698                     scene->setBackgroundBrush(QBrush(color));
0699                 }
0700             } else if (itemNode.nodeName() == QLatin1String("startviewport") && (startv != nullptr)) {
0701                 QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue();
0702                 QRectF r = stringToRect(rect);
0703                 startv->setRect(0, 0, r.width(), r.height());
0704                 startv->setPos(r.topLeft());
0705             } else if (itemNode.nodeName() == QLatin1String("endviewport") && (endv != nullptr)) {
0706                 QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue();
0707                 QRectF r = stringToRect(rect);
0708                 endv->setRect(0, 0, r.width(), r.height());
0709                 endv->setPos(r.topLeft());
0710             }
0711         }
0712     }
0713     return maxZValue;
0714 }
0715 
0716 int TitleDocument::invalidCount() const
0717 {
0718     return m_missingElements;
0719 }
0720 
0721 QPixmap TitleDocument::createInvalidPixmap(const QString &url, int height)
0722 {
0723     int missingHeight = height / 10;
0724     QPixmap pix(missingHeight, missingHeight);
0725     QIcon icon = QIcon::fromTheme(QStringLiteral("messagebox_warning"));
0726     pix.fill(QColor(255, 0, 0, 50));
0727     QPainter ptr(&pix);
0728     icon.paint(&ptr, 0, 0, missingHeight / 2, missingHeight / 2);
0729     QPen pen(Qt::red);
0730     pen.setWidth(3);
0731     ptr.setPen(pen);
0732     ptr.drawText(QRectF(2, 2, missingHeight - 4, missingHeight - 4), Qt::AlignHCenter | Qt::AlignBottom, QFileInfo(url).fileName());
0733     ptr.drawRect(2, 1, missingHeight - 4, missingHeight - 4);
0734     ptr.end();
0735     return pix;
0736 }
0737 
0738 QString TitleDocument::colorToString(const QColor &c)
0739 {
0740     QString ret = QStringLiteral("%1,%2,%3,%4");
0741     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
0742     return ret;
0743 }
0744 
0745 QString TitleDocument::rectFToString(const QRectF &c)
0746 {
0747     QString ret = QStringLiteral("%1,%2,%3,%4");
0748     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
0749     return ret;
0750 }
0751 
0752 QRectF TitleDocument::stringToRect(const QString &s)
0753 {
0754 
0755     QStringList l = s.split(QLatin1Char(','));
0756     if (l.size() < 4) {
0757         return {};
0758     }
0759     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
0760 }
0761 
0762 QColor TitleDocument::stringToColor(const QString &s)
0763 {
0764     QStringList l = s.split(QLatin1Char(','));
0765     if (l.size() < 4) {
0766         return QColor();
0767     }
0768     return {l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt()};
0769 }
0770 
0771 QTransform TitleDocument::stringToTransform(const QString &s)
0772 {
0773     QStringList l = s.split(QLatin1Char(','));
0774     if (l.size() < 9) {
0775         return QTransform();
0776     }
0777     return {l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(),
0778             l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()};
0779 }
0780 
0781 QList<QVariant> TitleDocument::stringToList(const QString &s)
0782 {
0783     QStringList l = s.split(QLatin1Char(','));
0784     if (l.size() < 3) {
0785         return QList<QVariant>();
0786     }
0787     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
0788 }
0789 
0790 int TitleDocument::frameWidth() const
0791 {
0792     return m_width;
0793 }
0794 
0795 int TitleDocument::frameHeight() const
0796 {
0797     return m_height;
0798 }