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