Export QTextImageHandler and add accessor for image
[profile/ivi/qtbase.git] / src / gui / text / qtextodfwriter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtGui module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <qglobal.h>
43
44 #ifndef QT_NO_TEXTODFWRITER
45
46 #include "qtextodfwriter_p.h"
47
48 #include <QImageWriter>
49 #include <QTextListFormat>
50 #include <QTextList>
51 #include <QBuffer>
52 #include <QUrl>
53
54 #include "qtextdocument_p.h"
55 #include "qtexttable.h"
56 #include "qtextcursor.h"
57 #include "qtextimagehandler_p.h"
58 #include "qzipwriter_p.h"
59
60 #include <QDebug>
61
62 QT_BEGIN_NAMESPACE
63
64 /// Convert pixels to postscript point units
65 static QString pixelToPoint(qreal pixels)
66 {
67     // we hardcode 96 DPI, we do the same in the ODF importer to have a perfect roundtrip.
68     return QString::number(pixels * 72 / 96) + QString::fromLatin1("pt");
69 }
70
71 // strategies
72 class QOutputStrategy {
73 public:
74     QOutputStrategy() : contentStream(0), counter(1) { }
75     virtual ~QOutputStrategy() {}
76     virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) = 0;
77
78     QString createUniqueImageName()
79     {
80         return QString::fromLatin1("Pictures/Picture%1").arg(counter++);
81     }
82
83     QIODevice *contentStream;
84     int counter;
85 };
86
87 class QXmlStreamStrategy : public QOutputStrategy {
88 public:
89     QXmlStreamStrategy(QIODevice *device)
90     {
91         contentStream = device;
92     }
93
94     virtual ~QXmlStreamStrategy()
95     {
96         if (contentStream)
97             contentStream->close();
98     }
99     virtual void addFile(const QString &, const QString &, const QByteArray &)
100     {
101         // we ignore this...
102     }
103 };
104
105 class QZipStreamStrategy : public QOutputStrategy {
106 public:
107     QZipStreamStrategy(QIODevice *device)
108         : zip(device),
109         manifestWriter(&manifest)
110     {
111         QByteArray mime("application/vnd.oasis.opendocument.text");
112         zip.setCompressionPolicy(QZipWriter::NeverCompress);
113         zip.addFile(QString::fromLatin1("mimetype"), mime); // for mime-magick
114         zip.setCompressionPolicy(QZipWriter::AutoCompress);
115         contentStream = &content;
116         content.open(QIODevice::WriteOnly);
117         manifest.open(QIODevice::WriteOnly);
118
119         manifestNS = QString::fromLatin1("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
120         // prettyfy
121         manifestWriter.setAutoFormatting(true);
122         manifestWriter.setAutoFormattingIndent(1);
123
124         manifestWriter.writeNamespace(manifestNS, QString::fromLatin1("manifest"));
125         manifestWriter.writeStartDocument();
126         manifestWriter.writeStartElement(manifestNS, QString::fromLatin1("manifest"));
127         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("version"), QString::fromLatin1("1.2"));
128         addFile(QString::fromLatin1("/"), QString::fromLatin1("application/vnd.oasis.opendocument.text"));
129         addFile(QString::fromLatin1("content.xml"), QString::fromLatin1("text/xml"));
130     }
131
132     ~QZipStreamStrategy()
133     {
134         manifestWriter.writeEndDocument();
135         manifest.close();
136         zip.addFile(QString::fromLatin1("META-INF/manifest.xml"), &manifest);
137         content.close();
138         zip.addFile(QString::fromLatin1("content.xml"), &content);
139         zip.close();
140     }
141
142     virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes)
143     {
144         zip.addFile(fileName, bytes);
145         addFile(fileName, mimeType);
146     }
147
148 private:
149     void addFile(const QString &fileName, const QString &mimeType)
150     {
151         manifestWriter.writeEmptyElement(manifestNS, QString::fromLatin1("file-entry"));
152         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("media-type"), mimeType);
153         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("full-path"), fileName);
154     }
155
156     QBuffer content;
157     QBuffer manifest;
158     QZipWriter zip;
159     QXmlStreamWriter manifestWriter;
160     QString manifestNS;
161 };
162
163 static QString bulletChar(QTextListFormat::Style style)
164 {
165     switch(style) {
166     case QTextListFormat::ListDisc:
167         return QChar(0x25cf); // bullet character
168     case QTextListFormat::ListCircle:
169         return QChar(0x25cb); // white circle
170     case QTextListFormat::ListSquare:
171         return QChar(0x25a1); // white square
172     case QTextListFormat::ListDecimal:
173         return QString::fromLatin1("1");
174     case QTextListFormat::ListLowerAlpha:
175         return QString::fromLatin1("a");
176     case QTextListFormat::ListUpperAlpha:
177         return QString::fromLatin1("A");
178     case QTextListFormat::ListLowerRoman:
179         return QString::fromLatin1("i");
180     case QTextListFormat::ListUpperRoman:
181         return QString::fromLatin1("I");
182     default:
183     case QTextListFormat::ListStyleUndefined:
184         return QString();
185     }
186 }
187
188 void QTextOdfWriter::writeFrame(QXmlStreamWriter &writer, const QTextFrame *frame)
189 {
190     Q_ASSERT(frame);
191     const QTextTable *table = qobject_cast<const QTextTable*> (frame);
192
193     if (table) { // Start a table.
194         writer.writeStartElement(tableNS, QString::fromLatin1("table"));
195         writer.writeEmptyElement(tableNS, QString::fromLatin1("table-column"));
196         writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-repeated"), QString::number(table->columns()));
197     } else if (frame->document() && frame->document()->rootFrame() != frame) { // start a section
198         writer.writeStartElement(textNS, QString::fromLatin1("section"));
199     }
200
201     QTextFrame::iterator iterator = frame->begin();
202     QTextFrame *child = 0;
203
204     int tableRow = -1;
205     while (! iterator.atEnd()) {
206         if (iterator.currentFrame() && child != iterator.currentFrame())
207             writeFrame(writer, iterator.currentFrame());
208         else { // no frame, its a block
209             QTextBlock block = iterator.currentBlock();
210             if (table) {
211                 QTextTableCell cell = table->cellAt(block.position());
212                 if (tableRow < cell.row()) {
213                     if (tableRow >= 0)
214                         writer.writeEndElement(); // close table row
215                     tableRow = cell.row();
216                     writer.writeStartElement(tableNS, QString::fromLatin1("table-row"));
217                 }
218                 writer.writeStartElement(tableNS, QString::fromLatin1("table-cell"));
219                 if (cell.columnSpan() > 1)
220                     writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-spanned"), QString::number(cell.columnSpan()));
221                 if (cell.rowSpan() > 1)
222                     writer.writeAttribute(tableNS, QString::fromLatin1("number-rows-spanned"), QString::number(cell.rowSpan()));
223                 if (cell.format().isTableCellFormat()) {
224                     writer.writeAttribute(tableNS, QString::fromLatin1("style-name"), QString::fromLatin1("T%1").arg(cell.tableCellFormatIndex()));
225                 }
226             }
227             writeBlock(writer, block);
228             if (table)
229                 writer.writeEndElement(); // table-cell
230         }
231         child = iterator.currentFrame();
232         ++iterator;
233     }
234     if (tableRow >= 0)
235         writer.writeEndElement(); // close table-row
236
237     if (table || (frame->document() && frame->document()->rootFrame() != frame))
238         writer.writeEndElement();  // close table or section element
239 }
240
241 void QTextOdfWriter::writeBlock(QXmlStreamWriter &writer, const QTextBlock &block)
242 {
243     if (block.textList()) { // its a list-item
244         const int listLevel = block.textList()->format().indent();
245         if (m_listStack.isEmpty() || m_listStack.top() != block.textList()) {
246             // not the same list we were in.
247             while (m_listStack.count() >= listLevel && !m_listStack.isEmpty() && m_listStack.top() != block.textList() ) { // we need to close tags
248                 m_listStack.pop();
249                 writer.writeEndElement(); // list
250                 if (m_listStack.count())
251                     writer.writeEndElement(); // list-item
252             }
253             while (m_listStack.count() < listLevel) {
254                 if (m_listStack.count())
255                     writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
256                 writer.writeStartElement(textNS, QString::fromLatin1("list"));
257                 if (m_listStack.count() == listLevel - 1) {
258                     m_listStack.push(block.textList());
259                     writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("L%1")
260                             .arg(block.textList()->formatIndex()));
261                 }
262                 else {
263                     m_listStack.push(0);
264                 }
265             }
266         }
267         writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
268     }
269     else {
270         while (! m_listStack.isEmpty()) {
271             m_listStack.pop();
272             writer.writeEndElement(); // list
273             if (m_listStack.count())
274                 writer.writeEndElement(); // list-item
275         }
276     }
277
278     if (block.length() == 1) { // only a linefeed
279         writer.writeEmptyElement(textNS, QString::fromLatin1("p"));
280         writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
281             .arg(block.blockFormatIndex()));
282         if (block.textList())
283             writer.writeEndElement(); // numbered-paragraph
284         return;
285     }
286     writer.writeStartElement(textNS, QString::fromLatin1("p"));
287     writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
288         .arg(block.blockFormatIndex()));
289     for (QTextBlock::Iterator frag= block.begin(); !frag.atEnd(); frag++) {
290         writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed in front of it.
291         writer.writeStartElement(textNS, QString::fromLatin1("span"));
292
293         QString fragmentText = frag.fragment().text();
294         if (fragmentText.length() == 1 && fragmentText[0] == 0xFFFC) { // its an inline character.
295             writeInlineCharacter(writer, frag.fragment());
296             writer.writeEndElement(); // span
297             continue;
298         }
299
300         writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("c%1")
301             .arg(frag.fragment().charFormatIndex()));
302         bool escapeNextSpace = true;
303         int precedingSpaces = 0;
304         int exportedIndex = 0;
305         for (int i=0; i <= fragmentText.count(); ++i) {
306             bool isSpace = false;
307                 QChar character = fragmentText[i];
308                 isSpace = character.unicode() == ' ';
309
310             // find more than one space. -> <text:s text:c="2" />
311             if (!isSpace && escapeNextSpace && precedingSpaces > 1) {
312                 const bool startParag = exportedIndex == 0 && i == precedingSpaces;
313                 if (!startParag)
314                     writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingSpaces + 1 - exportedIndex));
315                 writer.writeEmptyElement(textNS, QString::fromLatin1("s"));
316                 const int count = precedingSpaces - (startParag?0:1);
317                 if (count > 1)
318                     writer.writeAttribute(textNS, QString::fromLatin1("c"), QString::number(count));
319                 precedingSpaces = 0;
320                 exportedIndex = i;
321             }
322
323             if (i < fragmentText.count()) {
324                 if (character.unicode() == 0x2028) { // soft-return
325                     //if (exportedIndex < i)
326                         writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex));
327                     writer.writeEmptyElement(textNS, QString::fromLatin1("line-break"));
328                     exportedIndex = i+1;
329                     continue;
330                 } else if (character.unicode() == '\t') { // Tab
331                     //if (exportedIndex < i)
332                         writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex));
333                     writer.writeEmptyElement(textNS, QString::fromLatin1("tab"));
334                     exportedIndex = i+1;
335                     precedingSpaces = 0;
336                 } else if (isSpace) {
337                     ++precedingSpaces;
338                     escapeNextSpace = true;
339                 } else if (!isSpace) {
340                     precedingSpaces = 0;
341                 }
342             }
343         }
344
345         writer.writeCharacters(fragmentText.mid(exportedIndex));
346         writer.writeEndElement(); // span
347     }
348     writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed behind it.
349     writer.writeEndElement(); // p
350     if (block.textList())
351         writer.writeEndElement(); // list-item
352 }
353
354 void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const
355 {
356     writer.writeStartElement(drawNS, QString::fromLatin1("frame"));
357     if (m_strategy == 0) {
358         // don't do anything.
359     }
360     else if (fragment.charFormat().isImageFormat()) {
361         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
362         writer.writeAttribute(drawNS, QString::fromLatin1("name"), imageFormat.name());
363
364         // vvv  Copy pasted mostly from Qt =================
365         QImage image;
366         QString name = imageFormat.name();
367         if (name.startsWith(QLatin1String(":/"))) // auto-detect resources
368             name.prepend(QLatin1String("qrc"));
369         QUrl url = QUrl::fromEncoded(name.toUtf8());
370         const QVariant data = m_document->resource(QTextDocument::ImageResource, url);
371         if (data.type() == QVariant::Image) {
372             image = qvariant_cast<QImage>(data);
373         } else if (data.type() == QVariant::ByteArray) {
374             image.loadFromData(data.toByteArray());
375         }
376
377         if (image.isNull()) {
378             QString context;
379             if (QTextImageHandler::externalLoader)
380                 image = QTextImageHandler::externalLoader(name, context);
381
382             if (image.isNull()) { // try direct loading
383                 name = imageFormat.name(); // remove qrc:/ prefix again
384                 image.load(name);
385             }
386         }
387
388         // ^^^ Copy pasted mostly from Qt =================
389         if (! image.isNull()) {
390             QBuffer imageBytes;
391             QImageWriter imageWriter(&imageBytes, "png");
392             imageWriter.write(image);
393             QString filename = m_strategy->createUniqueImageName();
394             m_strategy->addFile(filename, QString::fromLatin1("image/png"), imageBytes.data());
395
396             // get the width/height from the format.
397             qreal width = (imageFormat.hasProperty(QTextFormat::ImageWidth)) ? imageFormat.width() : image.width();
398             writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width));
399             qreal height = (imageFormat.hasProperty(QTextFormat::ImageHeight)) ? imageFormat.height() : image.height();
400             writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height));
401
402             writer.writeStartElement(drawNS, QString::fromLatin1("image"));
403             writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename);
404             writer.writeEndElement(); // image
405         }
406     }
407
408     writer.writeEndElement(); // frame
409 }
410
411 void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, QSet<int> formats) const
412 {
413     writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles"));
414     QVector<QTextFormat> allStyles = m_document->allFormats();
415     QSetIterator<int> formatId(formats);
416     while(formatId.hasNext()) {
417         int formatIndex = formatId.next();
418         QTextFormat textFormat = allStyles.at(formatIndex);
419         switch (textFormat.type()) {
420         case QTextFormat::CharFormat:
421             if (textFormat.isTableCellFormat())
422                 writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex);
423             else
424                 writeCharacterFormat(writer, textFormat.toCharFormat(), formatIndex);
425             break;
426         case QTextFormat::BlockFormat:
427             writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex);
428             break;
429         case QTextFormat::ListFormat:
430             writeListFormat(writer, textFormat.toListFormat(), formatIndex);
431             break;
432         case QTextFormat::FrameFormat:
433             writeFrameFormat(writer, textFormat.toFrameFormat(), formatIndex);
434             break;
435         case QTextFormat::TableFormat:
436             ;break;
437         }
438     }
439
440     writer.writeEndElement(); // automatic-styles
441 }
442
443 void QTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const
444 {
445     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
446     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("p%1").arg(formatIndex));
447     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("paragraph"));
448     writer.writeStartElement(styleNS, QString::fromLatin1("paragraph-properties"));
449
450     if (format.hasProperty(QTextFormat::BlockAlignment)) {
451         const Qt::Alignment alignment = format.alignment() & Qt::AlignHorizontal_Mask;
452         QString value;
453         if (alignment == Qt::AlignLeading)
454             value = QString::fromLatin1("start");
455         else if (alignment == Qt::AlignTrailing)
456             value = QString::fromLatin1("end");
457         else if (alignment == (Qt::AlignLeft | Qt::AlignAbsolute))
458             value = QString::fromLatin1("left");
459         else if (alignment == (Qt::AlignRight | Qt::AlignAbsolute))
460             value = QString::fromLatin1("right");
461         else if (alignment == Qt::AlignHCenter)
462             value = QString::fromLatin1("center");
463         else if (alignment == Qt::AlignJustify)
464             value = QString::fromLatin1("justify");
465         else
466             qWarning() << "QTextOdfWriter: unsupported paragraph alignment; " << format.alignment();
467         if (! value.isNull())
468             writer.writeAttribute(foNS, QString::fromLatin1("text-align"), value);
469     }
470
471     if (format.hasProperty(QTextFormat::BlockTopMargin))
472         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
473     if (format.hasProperty(QTextFormat::BlockBottomMargin))
474         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
475     if (format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent))
476         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.),
477             format.leftMargin() + format.indent())));
478     if (format.hasProperty(QTextFormat::BlockRightMargin))
479         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
480     if (format.hasProperty(QTextFormat::TextIndent))
481         writer.writeAttribute(foNS, QString::fromLatin1("text-indent"), pixelToPoint(format.textIndent()));
482     if (format.hasProperty(QTextFormat::PageBreakPolicy)) {
483         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
484             writer.writeAttribute(foNS, QString::fromLatin1("break-before"), QString::fromLatin1("page"));
485         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
486             writer.writeAttribute(foNS, QString::fromLatin1("break-after"), QString::fromLatin1("page"));
487     }
488     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
489         QBrush brush = format.background();
490         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
491     }
492     if (format.hasProperty(QTextFormat::BlockNonBreakableLines))
493         writer.writeAttribute(foNS, QString::fromLatin1("keep-together"),
494                 format.nonBreakableLines() ? QString::fromLatin1("true") : QString::fromLatin1("false"));
495     if (format.hasProperty(QTextFormat::TabPositions)) {
496         QList<QTextOption::Tab> tabs = format.tabPositions();
497         writer.writeStartElement(styleNS, QString::fromLatin1("tab-stops"));
498         QList<QTextOption::Tab>::Iterator iterator = tabs.begin();
499         while(iterator != tabs.end()) {
500             writer.writeEmptyElement(styleNS, QString::fromLatin1("tab-stop"));
501             writer.writeAttribute(styleNS, QString::fromLatin1("position"), pixelToPoint(iterator->position) );
502             QString type;
503             switch(iterator->type) {
504             case QTextOption::DelimiterTab: type = QString::fromLatin1("char"); break;
505             case QTextOption::LeftTab: type = QString::fromLatin1("left"); break;
506             case QTextOption::RightTab: type = QString::fromLatin1("right"); break;
507             case QTextOption::CenterTab: type = QString::fromLatin1("center"); break;
508             }
509             writer.writeAttribute(styleNS, QString::fromLatin1("type"), type);
510             if (iterator->delimiter != 0)
511                 writer.writeAttribute(styleNS, QString::fromLatin1("char"), iterator->delimiter);
512             ++iterator;
513         }
514
515         writer.writeEndElement(); // tab-stops
516     }
517
518     writer.writeEndElement(); // paragraph-properties
519     writer.writeEndElement(); // style
520 }
521
522 void QTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const
523 {
524     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
525     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("c%1").arg(formatIndex));
526     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("text"));
527     writer.writeEmptyElement(styleNS, QString::fromLatin1("text-properties"));
528     if (format.fontItalic())
529         writer.writeAttribute(foNS, QString::fromLatin1("font-style"), QString::fromLatin1("italic"));
530     if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != QFont::Normal) {
531         QString value;
532         if (format.fontWeight() == QFont::Bold)
533             value = QString::fromLatin1("bold");
534         else
535             value = QString::number(format.fontWeight() * 10);
536         writer.writeAttribute(foNS, QString::fromLatin1("font-weight"), value);
537     }
538     if (format.hasProperty(QTextFormat::FontFamily))
539         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), format.fontFamily());
540     else
541         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), QString::fromLatin1("Sans")); // Qt default
542     if (format.hasProperty(QTextFormat::FontPointSize))
543         writer.writeAttribute(foNS, QString::fromLatin1("font-size"), QString::fromLatin1("%1pt").arg(format.fontPointSize()));
544     if (format.hasProperty(QTextFormat::FontCapitalization)) {
545         switch(format.fontCapitalization()) {
546         case QFont::MixedCase:
547             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("none")); break;
548         case QFont::AllUppercase:
549             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("uppercase")); break;
550         case QFont::AllLowercase:
551             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("lowercase")); break;
552         case QFont::Capitalize:
553             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("capitalize")); break;
554         case QFont::SmallCaps:
555             writer.writeAttribute(foNS, QString::fromLatin1("font-variant"), QString::fromLatin1("small-caps")); break;
556         }
557     }
558     if (format.hasProperty(QTextFormat::FontLetterSpacing))
559         writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontLetterSpacing()));
560     if (format.hasProperty(QTextFormat::FontWordSpacing) && format.fontWordSpacing() != 0)
561             writer.writeAttribute(foNS, QString::fromLatin1("word-spacing"), pixelToPoint(format.fontWordSpacing()));
562     if (format.hasProperty(QTextFormat::FontUnderline))
563         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-type"),
564                 format.fontUnderline() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
565     if (format.hasProperty(QTextFormat::FontOverline)) {
566         //   bool   fontOverline () const  TODO
567     }
568     if (format.hasProperty(QTextFormat::FontStrikeOut))
569         writer.writeAttribute(styleNS,QString::fromLatin1( "text-line-through-type"),
570                 format.fontStrikeOut() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
571     if (format.hasProperty(QTextFormat::TextUnderlineColor))
572         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-color"), format.underlineColor().name());
573     if (format.hasProperty(QTextFormat::FontFixedPitch)) {
574         //   bool   fontFixedPitch () const  TODO
575     }
576     if (format.hasProperty(QTextFormat::TextUnderlineStyle)) {
577         QString value;
578         switch (format.underlineStyle()) {
579         case QTextCharFormat::NoUnderline: value = QString::fromLatin1("none"); break;
580         case QTextCharFormat::SingleUnderline: value = QString::fromLatin1("solid"); break;
581         case QTextCharFormat::DashUnderline: value = QString::fromLatin1("dash"); break;
582         case QTextCharFormat::DotLine: value = QString::fromLatin1("dotted"); break;
583         case QTextCharFormat::DashDotLine: value = QString::fromLatin1("dash-dot"); break;
584         case QTextCharFormat::DashDotDotLine: value = QString::fromLatin1("dot-dot-dash"); break;
585         case QTextCharFormat::WaveUnderline: value = QString::fromLatin1("wave"); break;
586         case QTextCharFormat::SpellCheckUnderline: value = QString::fromLatin1("none"); break;
587         }
588         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-style"), value);
589     }
590     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
591         QString value;
592         switch (format.verticalAlignment()) {
593         case QTextCharFormat::AlignMiddle:
594         case QTextCharFormat::AlignNormal: value = QString::fromLatin1("0%"); break;
595         case QTextCharFormat::AlignSuperScript: value = QString::fromLatin1("super"); break;
596         case QTextCharFormat::AlignSubScript: value = QString::fromLatin1("sub"); break;
597         case QTextCharFormat::AlignTop: value = QString::fromLatin1("100%"); break;
598         case QTextCharFormat::AlignBottom : value = QString::fromLatin1("-100%"); break;
599         }
600         writer.writeAttribute(styleNS, QString::fromLatin1("text-position"), value);
601     }
602     if (format.hasProperty(QTextFormat::TextOutline))
603         writer.writeAttribute(styleNS, QString::fromLatin1("text-outline"), QString::fromLatin1("true"));
604     if (format.hasProperty(QTextFormat::TextToolTip)) {
605         //   QString   toolTip () const  TODO
606     }
607     if (format.hasProperty(QTextFormat::IsAnchor)) {
608         //   bool   isAnchor () const  TODO
609     }
610     if (format.hasProperty(QTextFormat::AnchorHref)) {
611         //   QString   anchorHref () const  TODO
612     }
613     if (format.hasProperty(QTextFormat::AnchorName)) {
614         //   QString   anchorName () const  TODO
615     }
616     if (format.hasProperty(QTextFormat::ForegroundBrush)) {
617         QBrush brush = format.foreground();
618         writer.writeAttribute(foNS, QString::fromLatin1("color"), brush.color().name());
619     }
620     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
621         QBrush brush = format.background();
622         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
623     }
624
625     writer.writeEndElement(); // style
626 }
627
628 void QTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const
629 {
630     writer.writeStartElement(textNS, QString::fromLatin1("list-style"));
631     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("L%1").arg(formatIndex));
632
633     QTextListFormat::Style style = format.style();
634     if (style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
635             || style == QTextListFormat::ListUpperAlpha
636             || style == QTextListFormat::ListLowerRoman
637             || style == QTextListFormat::ListUpperRoman) {
638         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-number"));
639         writer.writeAttribute(styleNS, QString::fromLatin1("num-format"), bulletChar(style));
640
641         if (format.hasProperty(QTextFormat::ListNumberSuffix))
642             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), format.numberSuffix());
643         else
644             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), QString::fromLatin1("."));
645
646         if (format.hasProperty(QTextFormat::ListNumberPrefix))
647             writer.writeAttribute(styleNS, QString::fromLatin1("num-prefix"), format.numberPrefix());
648
649     } else {
650         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-bullet"));
651         writer.writeAttribute(textNS, QString::fromLatin1("bullet-char"), bulletChar(style));
652     }
653
654     writer.writeAttribute(textNS, QString::fromLatin1("level"), QString::number(format.indent()));
655     writer.writeEmptyElement(styleNS, QString::fromLatin1("list-level-properties"));
656     writer.writeAttribute(foNS, QString::fromLatin1("text-align"), QString::fromLatin1("start"));
657     QString spacing = QString::fromLatin1("%1mm").arg(format.indent() * 8);
658     writer.writeAttribute(textNS, QString::fromLatin1("space-before"), spacing);
659     //writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing);
660
661     writer.writeEndElement(); // list-level-style-*
662     writer.writeEndElement(); // list-style
663 }
664
665 void QTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const
666 {
667     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
668     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("s%1").arg(formatIndex));
669     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("section"));
670     writer.writeEmptyElement(styleNS, QString::fromLatin1("section-properties"));
671     if (format.hasProperty(QTextFormat::BlockTopMargin))
672         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
673     if (format.hasProperty(QTextFormat::BlockBottomMargin))
674         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
675     if (format.hasProperty(QTextFormat::BlockLeftMargin))
676         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), format.leftMargin())) );
677     if (format.hasProperty(QTextFormat::BlockRightMargin))
678         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
679
680     writer.writeEndElement(); // style
681
682 // TODO consider putting the following properties in a qt-namespace.
683 // Position   position () const 
684 // qreal   border () const 
685 // QBrush   borderBrush () const 
686 // BorderStyle   borderStyle () const 
687 // qreal   padding () const 
688 // QTextLength   width () const 
689 // QTextLength   height () const 
690 // PageBreakFlags   pageBreakPolicy () const
691 }
692
693 void QTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format, int formatIndex) const
694 {
695     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
696     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("T%1").arg(formatIndex));
697     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table"));
698     writer.writeEmptyElement(styleNS, QString::fromLatin1("table-properties"));
699
700
701     qreal padding = format.topPadding();
702     if (padding > 0 && padding == format.bottomPadding()
703         && padding == format.leftPadding() && padding == format.rightPadding()) {
704         writer.writeAttribute(foNS, QString::fromLatin1("padding"), pixelToPoint(padding));
705     }
706     else {
707         if (padding > 0)
708             writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(padding));
709         if (format.bottomPadding() > 0)
710             writer.writeAttribute(foNS, QString::fromLatin1("padding-bottom"), pixelToPoint(format.bottomPadding()));
711         if (format.leftPadding() > 0)
712             writer.writeAttribute(foNS, QString::fromLatin1("padding-left"), pixelToPoint(format.leftPadding()));
713         if (format.rightPadding() > 0)
714             writer.writeAttribute(foNS, QString::fromLatin1("padding-right"), pixelToPoint(format.rightPadding()));
715     }
716
717     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
718         QString pos;
719         switch (format.verticalAlignment()) {
720         case QTextCharFormat::AlignMiddle:
721             pos = QString::fromLatin1("middle"); break;
722         case QTextCharFormat::AlignTop:
723             pos = QString::fromLatin1("top"); break;
724         case QTextCharFormat::AlignBottom:
725             pos = QString::fromLatin1("bottom"); break;
726         default:
727             pos = QString::fromLatin1("automatic"); break;
728         }
729         writer.writeAttribute(styleNS, QString::fromLatin1("vertical-align"), pos);
730     }
731
732     // TODO
733     // ODF just search for style-table-cell-properties-attlist)
734     // QTextFormat::BackgroundImageUrl
735     // format.background
736     // QTextFormat::FrameBorder
737
738     writer.writeEndElement(); // style
739 }
740
741 ///////////////////////
742
743 QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device)
744     : officeNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:office:1.0")),
745     textNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:text:1.0")),
746     styleNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:style:1.0")),
747     foNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")),
748     tableNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:table:1.0")),
749     drawNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")),
750     xlinkNS (QLatin1String("http://www.w3.org/1999/xlink")),
751     svgNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0")),
752     m_document(&document),
753     m_device(device),
754     m_strategy(0),
755     m_codec(0),
756     m_createArchive(true)
757 {
758 }
759
760 bool QTextOdfWriter::writeAll()
761 {
762     if (m_createArchive)
763         m_strategy = new QZipStreamStrategy(m_device);
764     else
765         m_strategy = new QXmlStreamStrategy(m_device);
766
767     if (!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) {
768         qWarning() << "QTextOdfWriter::writeAll: the device can not be opened for writing";
769         return false;
770     }
771     QXmlStreamWriter writer(m_strategy->contentStream);
772 #ifndef QT_NO_TEXTCODEC
773     if (m_codec)
774         writer.setCodec(m_codec);
775 #endif
776     // prettyfy
777     writer.setAutoFormatting(true);
778     writer.setAutoFormattingIndent(2);
779
780     writer.writeNamespace(officeNS, QString::fromLatin1("office"));
781     writer.writeNamespace(textNS, QString::fromLatin1("text"));
782     writer.writeNamespace(styleNS, QString::fromLatin1("style"));
783     writer.writeNamespace(foNS, QString::fromLatin1("fo"));
784     writer.writeNamespace(tableNS, QString::fromLatin1("table"));
785     writer.writeNamespace(drawNS, QString::fromLatin1("draw"));
786     writer.writeNamespace(xlinkNS, QString::fromLatin1("xlink"));
787     writer.writeNamespace(svgNS, QString::fromLatin1("svg"));
788     writer.writeStartDocument();
789     writer.writeStartElement(officeNS, QString::fromLatin1("document-content"));
790     writer.writeAttribute(officeNS, QString::fromLatin1("version"), QString::fromLatin1("1.2"));
791
792     // add fragments. (for character formats)
793     QTextDocumentPrivate::FragmentIterator fragIt = m_document->docHandle()->begin();
794     QSet<int> formats;
795     while (fragIt != m_document->docHandle()->end()) {
796         const QTextFragmentData * const frag = fragIt.value();
797         formats << frag->format;
798         ++fragIt;
799     }
800
801     // add blocks (for blockFormats)
802     QTextDocumentPrivate::BlockMap &blocks = m_document->docHandle()->blockMap();
803     QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin();
804     while (blockIt != blocks.end()) {
805         const QTextBlockData * const block = blockIt.value();
806         formats << block->format;
807         ++blockIt;
808     }
809
810     // add objects for lists, frames and tables
811     QVector<QTextFormat> allFormats = m_document->allFormats();
812     QList<int> copy = formats.toList();
813     for (QList<int>::Iterator iter = copy.begin(); iter != copy.end(); ++iter) {
814         QTextObject *object = m_document->objectForFormat(allFormats[*iter]);
815         if (object)
816             formats << object->formatIndex();
817     }
818
819     writeFormats(writer, formats);
820
821     writer.writeStartElement(officeNS, QString::fromLatin1("body"));
822     writer.writeStartElement(officeNS, QString::fromLatin1("text"));
823     QTextFrame *rootFrame = m_document->rootFrame();
824     writeFrame(writer, rootFrame);
825     writer.writeEndElement(); // text
826     writer.writeEndElement(); // body
827     writer.writeEndElement(); // document-content
828     writer.writeEndDocument();
829     delete m_strategy;
830     m_strategy = 0;
831
832     return true;
833 }
834
835 QT_END_NAMESPACE
836
837 #endif // QT_NO_TEXTODFWRITER