]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
When adding an existing kdenlive title clip to the project , detach embedded images:
[kdenlive] / src / titledocument.cpp
1 /***************************************************************************
2                           titledocument.h  -  description
3                              -------------------
4     begin                : Feb 28 2008
5     copyright            : (C) 2008 by Marco Gittler
6     email                : g.marco@freenet.de
7  ***************************************************************************/
8
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17
18 #include "titledocument.h"
19
20 #include <KDebug>
21 #include <KTemporaryFile>
22 #include <kio/netaccess.h>
23 #include <KApplication>
24 #include <KStandardDirs>
25 #include <KLocale>
26 #include <KMessageBox>
27
28 #include <QGraphicsScene>
29 #include <QDomElement>
30 #include <QGraphicsItem>
31 #include <QGraphicsRectItem>
32 #include <QGraphicsTextItem>
33 #include <QGraphicsSvgItem>
34 #include <QCryptographicHash>
35 #include <QSvgRenderer>
36 #include <QFontInfo>
37 #include <QFile>
38 #include <QTextCursor>
39
40 #if QT_VERSION >= 0x040600
41 #include <QGraphicsEffect>
42 #include <QGraphicsBlurEffect>
43 #include <QGraphicsDropShadowEffect>
44 #endif
45
46 QByteArray fileToByteArray(const QString& filename)
47 {
48     QByteArray ret;
49     QFile file(filename);
50     if (file.open(QIODevice::ReadOnly)) {
51         while (!file.atEnd()) {
52             ret.append(file.readLine());
53         }
54     }
55     return ret;
56 }
57
58 TitleDocument::TitleDocument()
59 {
60     m_scene = NULL;
61 }
62
63 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
64 {
65     m_scene = _scene;
66     m_width = width;
67     m_height = height;
68 }
69
70 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
71 {
72     if (embed) {
73         if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
74             content.setAttribute("base64", item->data(Qt::UserRole + 1).toString());
75         } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
76             content.setAttribute("base64", fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
77         }
78         content.removeAttribute("url");
79     } else {
80         // save for project files to disk
81         QString base64 = item->data(Qt::UserRole + 1).toString();
82         if (!base64.isEmpty()) {
83             QString titlePath;
84             if (!m_projectPath.isEmpty()) {
85                 titlePath = m_projectPath;
86             } else {
87                 titlePath = "/tmp/titles";
88             }
89             QString filename = extractBase64Image(titlePath, base64);
90             if (!filename.isEmpty()) {
91                 content.setAttribute("url", filename);
92                 content.removeAttribute("base64");
93             }
94
95         } else {
96             return 1;
97         }
98     }
99     return 0;
100 }
101
102
103 //static
104 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
105 {
106     QString filename = titlePath + QString(QCryptographicHash::hash(data.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
107     KStandardDirs::makeDir(titlePath);
108     QFile f(filename);
109     if (f.open(QIODevice::WriteOnly)) {
110         f.write(QByteArray::fromBase64(data.toAscii())) ;
111         f.close();
112         return filename;
113     }
114     return QString();
115 }
116
117 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
118 {
119     QDomDocument doc;
120
121     QDomElement main = doc.createElement("kdenlivetitle");
122     main.setAttribute("width", m_width);
123     main.setAttribute("height", m_height);
124     doc.appendChild(main);
125
126     foreach(QGraphicsItem * item, m_scene->items()) {
127         QDomElement e = doc.createElement("item");
128         QDomElement content = doc.createElement("content");
129         QFont font;
130         QGraphicsTextItem *t;
131
132         switch (item->type()) {
133         case 7:
134             e.setAttribute("type", "QGraphicsPixmapItem");
135             content.setAttribute("url", item->data(Qt::UserRole).toString());
136             base64ToUrl(item, content, embed);
137             break;
138         case 13:
139             e.setAttribute("type", "QGraphicsSvgItem");
140             content.setAttribute("url", item->data(Qt::UserRole).toString());
141             base64ToUrl(item, content, embed);
142             break;
143         case 3:
144             e.setAttribute("type", "QGraphicsRectItem");
145             content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
146             content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
147             content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
148             content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
149             break;
150         case 8:
151             e.setAttribute("type", "QGraphicsTextItem");
152             t = static_cast<QGraphicsTextItem *>(item);
153             // Don't save empty text nodes
154             if (t->toPlainText().simplified().isEmpty()) continue;
155             //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
156             content.appendChild(doc.createTextNode(t->toPlainText()));
157             font = t->font();
158             content.setAttribute("font", font.family());
159             content.setAttribute("font-weight", font.weight());
160             content.setAttribute("font-pixel-size", font.pixelSize());
161             content.setAttribute("font-italic", font.italic());
162             content.setAttribute("font-underline", font.underline());
163             {
164                 QTextCursor cursor(t->document());
165                 cursor.select(QTextCursor::Document);
166                 QColor fontcolor = cursor.charFormat().foreground().color();
167                 content.setAttribute("font-color", colorToString(fontcolor));
168                 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
169                 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
170             }
171             if (!t->data(100).isNull()) {
172                 QStringList effectParams = t->data(100).toStringList();
173                 QString effectName = effectParams.takeFirst();
174                 content.setAttribute(effectName, effectParams.join(";"));
175             }
176
177             // Only save when necessary.
178             if (t->data(OriginXLeft).toInt() == AxisInverted) {
179                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
180             }
181             if (t->data(OriginYTop).toInt() == AxisInverted) {
182                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
183             }
184             if (t->textWidth() != -1) {
185                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
186             }
187             break;
188         default:
189             continue;
190         }
191
192         // position
193         QDomElement pos = doc.createElement("position");
194         pos.setAttribute("x", item->pos().x());
195         pos.setAttribute("y", item->pos().y());
196         QTransform transform = item->transform();
197         QDomElement tr = doc.createElement("transform");
198         if (!item->data(ZOOMFACTOR).isNull()) {
199             tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
200         }
201         if (!item->data(ROTATEFACTOR).isNull()) {
202             QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
203             tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
204         }
205         tr.appendChild(doc.createTextNode(
206                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
207                                transform.m11()).arg(transform.m12()).arg(transform.m13()).arg(transform.m21()).arg(transform.m22()).arg(transform.m23()).arg(transform.m31()).arg(transform.m32()).arg(transform.m33())
208                        )
209                       );
210         e.setAttribute("z-index", item->zValue());
211         pos.appendChild(tr);
212
213 #if QT_VERSION >= 0x040600
214         // effects
215         QGraphicsEffect *eff = item->graphicsEffect();
216         if (eff) {
217             QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
218             QDomElement effect = doc.createElement("effect");
219             if (blur) {
220                 effect.setAttribute("type", "blur");
221                 effect.setAttribute("blurradius", blur->blurRadius());
222             } else {
223                 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
224                 if (shadow) {
225                     effect.setAttribute("type", "shadow");
226                     effect.setAttribute("blurradius", shadow->blurRadius());
227                     effect.setAttribute("xoffset", shadow->xOffset());
228                     effect.setAttribute("yoffset", shadow->yOffset());
229                 }
230             }
231             e.appendChild(effect);
232         }
233 #endif
234
235         e.appendChild(pos);
236         e.appendChild(content);
237         if (item->zValue() > -1000) main.appendChild(e);
238     }
239     if (startv && endv) {
240         QDomElement endp = doc.createElement("endviewport");
241         QDomElement startp = doc.createElement("startviewport");
242         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
243         endp.setAttribute("rect", rectFToString(r));
244         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
245         startp.setAttribute("rect", rectFToString(r2));
246
247         main.appendChild(startp);
248         main.appendChild(endp);
249     }
250     QDomElement backgr = doc.createElement("background");
251     QColor color = getBackgroundColor();
252     backgr.setAttribute("color", colorToString(color));
253     main.appendChild(backgr);
254
255     return doc;
256 }
257
258 /** \brief Get the background color (incl. alpha) from the document, if possibly
259   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
260 QColor TitleDocument::getBackgroundColor()
261 {
262     QColor color(0, 0, 0, 0);
263     if (m_scene) {
264         QList<QGraphicsItem *> items = m_scene->items();
265         for (int i = 0; i < items.size(); i++) {
266             if (items.at(i)->zValue() == -1100) {
267                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
268                 return color;
269             }
270         }
271     }
272     return color;
273 }
274
275
276 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
277 {
278     if (!m_scene)
279         return false;
280
281     QDomDocument doc = xml(startv, endv, embed);
282     doc.documentElement().setAttribute("out", out);
283     KTemporaryFile tmpfile;
284     if (!tmpfile.open()) {
285         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
286         return false;
287     }
288     QFile xmlf(tmpfile.fileName());
289     xmlf.open(QIODevice::WriteOnly);
290     xmlf.write(doc.toString().toUtf8());
291     if (xmlf.error() != QFile::NoError) {
292         xmlf.close();
293         return false;
294     }
295     xmlf.close();
296     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
297 }
298
299 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
300 {
301     m_projectPath = projectpath;
302     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
303     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
304     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
305         int doc_width = doc.documentElement().attribute("width").toInt();
306         int doc_height = doc.documentElement().attribute("height").toInt();
307         if (doc_width != m_width || doc_height != m_height) {
308             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
309             //TODO: convert using QTransform
310             m_width = doc_width;
311             m_height = doc_height;
312         }
313     } else {
314         // Document has no size info, it is likely an old version title, so ignore viewport data
315         QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
316         if (!viewportlist.isEmpty()) {
317             doc.documentElement().removeChild(viewportlist.at(0));
318         }
319         viewportlist = doc.documentElement().elementsByTagName("endviewport");
320         if (!viewportlist.isEmpty()) {
321             doc.documentElement().removeChild(viewportlist.at(0));
322         }
323     }
324     //TODO: get default title duration instead of hardcoded one
325     if (doc.documentElement().hasAttribute("out"))
326         *out = doc.documentElement().attribute("out").toInt();
327     else
328         *out = 125;
329
330     int maxZValue = 0;
331     if (titles.size()) {
332
333         QDomNodeList items = titles.item(0).childNodes();
334         for (int i = 0; i < items.count(); i++) {
335             QGraphicsItem *gitem = NULL;
336             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
337             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
338             if (zValue > -1000) {
339                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
340                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
341                     QFont font(txtProperties.namedItem("font").nodeValue());
342
343                     QDomNode node = txtProperties.namedItem("font-bold");
344                     if (!node.isNull()) {
345                         // Old: Bold/Not bold.
346                         font.setBold(node.nodeValue().toInt());
347                     } else {
348                         // New: Font weight (QFont::)
349                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
350                     }
351                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
352                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
353                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
354                     // Older Kdenlive version did not store pixel size but point size
355                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
356                         KMessageBox::information(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. They will be converted to pixel size, making them portable, but you could have to adjust their size."), i18n("Text Clips Updated"));
357                         QFont f2;
358                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
359                         font.setPixelSize(QFontInfo(f2).pixelSize());
360                     } else
361                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
362                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
363                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
364                     QTextCursor cursor(txt->document());
365                     cursor.select(QTextCursor::Document);
366                     QTextCharFormat format;
367                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
368                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
369                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
370                         format.setTextOutline(
371                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
372                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
373                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
374                         );
375
376                     }
377                     format.setForeground(QBrush(col));
378                     cursor.mergeCharFormat(format);
379                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
380                     if (txtProperties.namedItem("alignment").isNull() == false) {
381                         txt->setTextWidth(txt->boundingRect().width());
382                         QTextCursor cur = txt->textCursor();
383                         QTextBlockFormat format = cur.blockFormat();
384                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
385                         cur.select(QTextCursor::Document);
386                         cur.setBlockFormat(format);
387                         txt->setTextCursor(cur);
388                         cur.clearSelection();
389                         txt->setTextCursor(cur);
390                     }
391
392                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
393                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
394                     }
395                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
396                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
397                     }
398
399                     // Effects
400                     if (!txtProperties.namedItem("typewriter").isNull()) {
401                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
402                         txt->setData(100, effData);
403                     }
404
405                     gitem = txt;
406                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
407                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
408                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
409                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
410                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
411                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
412                     gitem = rec;
413                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
414                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
415                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
416                     QPixmap pix;
417                     if (base64.isEmpty()) {
418                         pix.load(url);
419                     } else {
420                         pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
421                     }
422                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
423                     rec->setData(Qt::UserRole, url);
424                     if (!base64.isEmpty()) {
425                         rec->setData(Qt::UserRole + 1, base64);
426                     }
427                     gitem = rec;
428                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
429                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
430                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
431                     QGraphicsSvgItem *rec = NULL;
432                     if (base64.isEmpty()) {
433                         rec = new QGraphicsSvgItem(url);
434                     } else {
435                         rec = new QGraphicsSvgItem();
436                         QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
437                         rec->setSharedRenderer(renderer);
438                         //QString elem=rec->elementId();
439                         //QRectF bounds = renderer->boundsOnElement(elem);
440                     }
441                     if (rec) {
442                         m_scene->addItem(rec);
443                         rec->setData(Qt::UserRole, url);
444                         if (!base64.isEmpty()) {
445                             rec->setData(Qt::UserRole + 1, base64);
446                         }
447                         gitem = rec;
448                     }
449                 }
450             }
451             //pos and transform
452             if (gitem) {
453                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
454                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
455                 gitem->setPos(p);
456                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
457                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
458                 QString rotate = trans.attribute("rotation");
459                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
460                 QString zoom = trans.attribute("zoom");
461                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
462                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
463                 if (zValue > maxZValue) maxZValue = zValue;
464                 gitem->setZValue(zValue);
465                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
466
467 #if QT_VERSION >= 0x040600
468                 // effects
469                 QDomNode eff = items.item(i).namedItem("effect");
470                 if (!eff.isNull()) {
471                     QDomElement e = eff.toElement();
472                     if (e.attribute("type") == "blur") {
473                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
474                         blur->setBlurRadius(e.attribute("blurradius").toInt());
475                         gitem->setGraphicsEffect(blur);
476                     } else if (e.attribute("type") == "shadow") {
477                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
478                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
479                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
480                         gitem->setGraphicsEffect(shadow);
481                     }
482                 }
483 #endif
484             }
485
486             if (items.item(i).nodeName() == "background") {
487                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
488                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
489                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
490                 QList<QGraphicsItem *> items = m_scene->items();
491                 for (int i = 0; i < items.size(); i++) {
492                     if (items.at(i)->zValue() == -1100) {
493                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
494                         break;
495                     }
496                 }
497             } else if (items.item(i).nodeName() == "startviewport" && startv) {
498                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
499                 QRectF r = stringToRect(rect);
500                 startv->setRect(0, 0, r.width(), r.height());
501                 startv->setPos(r.topLeft());
502             } else if (items.item(i).nodeName() == "endviewport" && endv) {
503                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
504                 QRectF r = stringToRect(rect);
505                 endv->setRect(0, 0, r.width(), r.height());
506                 endv->setPos(r.topLeft());
507             }
508         }
509     }
510     return maxZValue;
511 }
512
513 QString TitleDocument::colorToString(const QColor& c)
514 {
515     QString ret = "%1,%2,%3,%4";
516     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
517     return ret;
518 }
519
520 QString TitleDocument::rectFToString(const QRectF& c)
521 {
522     QString ret = "%1,%2,%3,%4";
523     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
524     return ret;
525 }
526
527 QRectF TitleDocument::stringToRect(const QString & s)
528 {
529
530     QStringList l = s.split(',');
531     if (l.size() < 4)
532         return QRectF();
533     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
534 }
535
536 QColor TitleDocument::stringToColor(const QString & s)
537 {
538     QStringList l = s.split(',');
539     if (l.size() < 4)
540         return QColor();
541     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
542 }
543
544 QTransform TitleDocument::stringToTransform(const QString& s)
545 {
546     QStringList l = s.split(',');
547     if (l.size() < 9)
548         return QTransform();
549     return QTransform(
550                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
551                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
552                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
553            );
554 }
555
556 QList<QVariant> TitleDocument::stringToList(const QString & s)
557 {
558     QStringList l = s.split(',');
559     if (l.size() < 3)
560         return QList<QVariant>();
561     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
562 }
563
564 int TitleDocument::frameWidth() const
565 {
566     return m_width;
567 }
568
569 int TitleDocument::frameHeight() const
570 {
571     return m_height;
572 }
573
574