Get started with patching up the Qt GUI docs
[profile/ivi/qtbase.git] / src / gui / text / qtextodfwriter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
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(name);
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 (image.isNull()) { // try direct loading
380                 name = imageFormat.name(); // remove qrc:/ prefix again
381                 image.load(name);
382             }
383         }
384
385         // ^^^ Copy pasted mostly from Qt =================
386         if (! image.isNull()) {
387             QBuffer imageBytes;
388             QImageWriter imageWriter(&imageBytes, "png");
389             imageWriter.write(image);
390             QString filename = m_strategy->createUniqueImageName();
391             m_strategy->addFile(filename, QString::fromLatin1("image/png"), imageBytes.data());
392
393             // get the width/height from the format.
394             qreal width = (imageFormat.hasProperty(QTextFormat::ImageWidth)) ? imageFormat.width() : image.width();
395             writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width));
396             qreal height = (imageFormat.hasProperty(QTextFormat::ImageHeight)) ? imageFormat.height() : image.height();
397             writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height));
398
399             writer.writeStartElement(drawNS, QString::fromLatin1("image"));
400             writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename);
401             writer.writeEndElement(); // image
402         }
403     }
404
405     writer.writeEndElement(); // frame
406 }
407
408 void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, QSet<int> formats) const
409 {
410     writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles"));
411     QVector<QTextFormat> allStyles = m_document->allFormats();
412     QSetIterator<int> formatId(formats);
413     while(formatId.hasNext()) {
414         int formatIndex = formatId.next();
415         QTextFormat textFormat = allStyles.at(formatIndex);
416         switch (textFormat.type()) {
417         case QTextFormat::CharFormat:
418             if (textFormat.isTableCellFormat())
419                 writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex);
420             else
421                 writeCharacterFormat(writer, textFormat.toCharFormat(), formatIndex);
422             break;
423         case QTextFormat::BlockFormat:
424             writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex);
425             break;
426         case QTextFormat::ListFormat:
427             writeListFormat(writer, textFormat.toListFormat(), formatIndex);
428             break;
429         case QTextFormat::FrameFormat:
430             writeFrameFormat(writer, textFormat.toFrameFormat(), formatIndex);
431             break;
432         case QTextFormat::TableFormat:
433             ;break;
434         }
435     }
436
437     writer.writeEndElement(); // automatic-styles
438 }
439
440 void QTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const
441 {
442     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
443     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("p%1").arg(formatIndex));
444     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("paragraph"));
445     writer.writeStartElement(styleNS, QString::fromLatin1("paragraph-properties"));
446
447     if (format.hasProperty(QTextFormat::BlockAlignment)) {
448         const Qt::Alignment alignment = format.alignment() & Qt::AlignHorizontal_Mask;
449         QString value;
450         if (alignment == Qt::AlignLeading)
451             value = QString::fromLatin1("start");
452         else if (alignment == Qt::AlignTrailing)
453             value = QString::fromLatin1("end");
454         else if (alignment == (Qt::AlignLeft | Qt::AlignAbsolute))
455             value = QString::fromLatin1("left");
456         else if (alignment == (Qt::AlignRight | Qt::AlignAbsolute))
457             value = QString::fromLatin1("right");
458         else if (alignment == Qt::AlignHCenter)
459             value = QString::fromLatin1("center");
460         else if (alignment == Qt::AlignJustify)
461             value = QString::fromLatin1("justify");
462         else
463             qWarning() << "QTextOdfWriter: unsupported paragraph alignment; " << format.alignment();
464         if (! value.isNull())
465             writer.writeAttribute(foNS, QString::fromLatin1("text-align"), value);
466     }
467
468     if (format.hasProperty(QTextFormat::BlockTopMargin))
469         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
470     if (format.hasProperty(QTextFormat::BlockBottomMargin))
471         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
472     if (format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent))
473         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.),
474             format.leftMargin() + format.indent())));
475     if (format.hasProperty(QTextFormat::BlockRightMargin))
476         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
477     if (format.hasProperty(QTextFormat::TextIndent))
478         writer.writeAttribute(foNS, QString::fromLatin1("text-indent"), pixelToPoint(format.textIndent()));
479     if (format.hasProperty(QTextFormat::PageBreakPolicy)) {
480         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
481             writer.writeAttribute(foNS, QString::fromLatin1("break-before"), QString::fromLatin1("page"));
482         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
483             writer.writeAttribute(foNS, QString::fromLatin1("break-after"), QString::fromLatin1("page"));
484     }
485     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
486         QBrush brush = format.background();
487         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
488     }
489     if (format.hasProperty(QTextFormat::BlockNonBreakableLines))
490         writer.writeAttribute(foNS, QString::fromLatin1("keep-together"),
491                 format.nonBreakableLines() ? QString::fromLatin1("true") : QString::fromLatin1("false"));
492     if (format.hasProperty(QTextFormat::TabPositions)) {
493         QList<QTextOption::Tab> tabs = format.tabPositions();
494         writer.writeStartElement(styleNS, QString::fromLatin1("tab-stops"));
495         QList<QTextOption::Tab>::Iterator iterator = tabs.begin();
496         while(iterator != tabs.end()) {
497             writer.writeEmptyElement(styleNS, QString::fromLatin1("tab-stop"));
498             writer.writeAttribute(styleNS, QString::fromLatin1("position"), pixelToPoint(iterator->position) );
499             QString type;
500             switch(iterator->type) {
501             case QTextOption::DelimiterTab: type = QString::fromLatin1("char"); break;
502             case QTextOption::LeftTab: type = QString::fromLatin1("left"); break;
503             case QTextOption::RightTab: type = QString::fromLatin1("right"); break;
504             case QTextOption::CenterTab: type = QString::fromLatin1("center"); break;
505             }
506             writer.writeAttribute(styleNS, QString::fromLatin1("type"), type);
507             if (iterator->delimiter != 0)
508                 writer.writeAttribute(styleNS, QString::fromLatin1("char"), iterator->delimiter);
509             ++iterator;
510         }
511
512         writer.writeEndElement(); // tab-stops
513     }
514
515     writer.writeEndElement(); // paragraph-properties
516     writer.writeEndElement(); // style
517 }
518
519 void QTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const
520 {
521     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
522     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("c%1").arg(formatIndex));
523     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("text"));
524     writer.writeEmptyElement(styleNS, QString::fromLatin1("text-properties"));
525     if (format.fontItalic())
526         writer.writeAttribute(foNS, QString::fromLatin1("font-style"), QString::fromLatin1("italic"));
527     if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != QFont::Normal) {
528         QString value;
529         if (format.fontWeight() == QFont::Bold)
530             value = QString::fromLatin1("bold");
531         else
532             value = QString::number(format.fontWeight() * 10);
533         writer.writeAttribute(foNS, QString::fromLatin1("font-weight"), value);
534     }
535     if (format.hasProperty(QTextFormat::FontFamily))
536         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), format.fontFamily());
537     else
538         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), QString::fromLatin1("Sans")); // Qt default
539     if (format.hasProperty(QTextFormat::FontPointSize))
540         writer.writeAttribute(foNS, QString::fromLatin1("font-size"), QString::fromLatin1("%1pt").arg(format.fontPointSize()));
541     if (format.hasProperty(QTextFormat::FontCapitalization)) {
542         switch(format.fontCapitalization()) {
543         case QFont::MixedCase:
544             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("none")); break;
545         case QFont::AllUppercase:
546             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("uppercase")); break;
547         case QFont::AllLowercase:
548             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("lowercase")); break;
549         case QFont::Capitalize:
550             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("capitalize")); break;
551         case QFont::SmallCaps:
552             writer.writeAttribute(foNS, QString::fromLatin1("font-variant"), QString::fromLatin1("small-caps")); break;
553         }
554     }
555     if (format.hasProperty(QTextFormat::FontLetterSpacing))
556         writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontLetterSpacing()));
557     if (format.hasProperty(QTextFormat::FontWordSpacing) && format.fontWordSpacing() != 0)
558             writer.writeAttribute(foNS, QString::fromLatin1("word-spacing"), pixelToPoint(format.fontWordSpacing()));
559     if (format.hasProperty(QTextFormat::FontUnderline))
560         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-type"),
561                 format.fontUnderline() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
562     if (format.hasProperty(QTextFormat::FontOverline)) {
563         //   bool   fontOverline () const  TODO
564     }
565     if (format.hasProperty(QTextFormat::FontStrikeOut))
566         writer.writeAttribute(styleNS,QString::fromLatin1( "text-line-through-type"),
567                 format.fontStrikeOut() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
568     if (format.hasProperty(QTextFormat::TextUnderlineColor))
569         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-color"), format.underlineColor().name());
570     if (format.hasProperty(QTextFormat::FontFixedPitch)) {
571         //   bool   fontFixedPitch () const  TODO
572     }
573     if (format.hasProperty(QTextFormat::TextUnderlineStyle)) {
574         QString value;
575         switch (format.underlineStyle()) {
576         case QTextCharFormat::NoUnderline: value = QString::fromLatin1("none"); break;
577         case QTextCharFormat::SingleUnderline: value = QString::fromLatin1("solid"); break;
578         case QTextCharFormat::DashUnderline: value = QString::fromLatin1("dash"); break;
579         case QTextCharFormat::DotLine: value = QString::fromLatin1("dotted"); break;
580         case QTextCharFormat::DashDotLine: value = QString::fromLatin1("dash-dot"); break;
581         case QTextCharFormat::DashDotDotLine: value = QString::fromLatin1("dot-dot-dash"); break;
582         case QTextCharFormat::WaveUnderline: value = QString::fromLatin1("wave"); break;
583         case QTextCharFormat::SpellCheckUnderline: value = QString::fromLatin1("none"); break;
584         }
585         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-style"), value);
586     }
587     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
588         QString value;
589         switch (format.verticalAlignment()) {
590         case QTextCharFormat::AlignMiddle:
591         case QTextCharFormat::AlignNormal: value = QString::fromLatin1("0%"); break;
592         case QTextCharFormat::AlignSuperScript: value = QString::fromLatin1("super"); break;
593         case QTextCharFormat::AlignSubScript: value = QString::fromLatin1("sub"); break;
594         case QTextCharFormat::AlignTop: value = QString::fromLatin1("100%"); break;
595         case QTextCharFormat::AlignBottom : value = QString::fromLatin1("-100%"); break;
596         case QTextCharFormat::AlignBaseline: break;
597         }
598         writer.writeAttribute(styleNS, QString::fromLatin1("text-position"), value);
599     }
600     if (format.hasProperty(QTextFormat::TextOutline))
601         writer.writeAttribute(styleNS, QString::fromLatin1("text-outline"), QString::fromLatin1("true"));
602     if (format.hasProperty(QTextFormat::TextToolTip)) {
603         //   QString   toolTip () const  TODO
604     }
605     if (format.hasProperty(QTextFormat::IsAnchor)) {
606         //   bool   isAnchor () const  TODO
607     }
608     if (format.hasProperty(QTextFormat::AnchorHref)) {
609         //   QString   anchorHref () const  TODO
610     }
611     if (format.hasProperty(QTextFormat::AnchorName)) {
612         //   QString   anchorName () const  TODO
613     }
614     if (format.hasProperty(QTextFormat::ForegroundBrush)) {
615         QBrush brush = format.foreground();
616         writer.writeAttribute(foNS, QString::fromLatin1("color"), brush.color().name());
617     }
618     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
619         QBrush brush = format.background();
620         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
621     }
622
623     writer.writeEndElement(); // style
624 }
625
626 void QTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const
627 {
628     writer.writeStartElement(textNS, QString::fromLatin1("list-style"));
629     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("L%1").arg(formatIndex));
630
631     QTextListFormat::Style style = format.style();
632     if (style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
633             || style == QTextListFormat::ListUpperAlpha
634             || style == QTextListFormat::ListLowerRoman
635             || style == QTextListFormat::ListUpperRoman) {
636         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-number"));
637         writer.writeAttribute(styleNS, QString::fromLatin1("num-format"), bulletChar(style));
638
639         if (format.hasProperty(QTextFormat::ListNumberSuffix))
640             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), format.numberSuffix());
641         else
642             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), QString::fromLatin1("."));
643
644         if (format.hasProperty(QTextFormat::ListNumberPrefix))
645             writer.writeAttribute(styleNS, QString::fromLatin1("num-prefix"), format.numberPrefix());
646
647     } else {
648         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-bullet"));
649         writer.writeAttribute(textNS, QString::fromLatin1("bullet-char"), bulletChar(style));
650     }
651
652     writer.writeAttribute(textNS, QString::fromLatin1("level"), QString::number(format.indent()));
653     writer.writeEmptyElement(styleNS, QString::fromLatin1("list-level-properties"));
654     writer.writeAttribute(foNS, QString::fromLatin1("text-align"), QString::fromLatin1("start"));
655     QString spacing = QString::fromLatin1("%1mm").arg(format.indent() * 8);
656     writer.writeAttribute(textNS, QString::fromLatin1("space-before"), spacing);
657     //writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing);
658
659     writer.writeEndElement(); // list-level-style-*
660     writer.writeEndElement(); // list-style
661 }
662
663 void QTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const
664 {
665     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
666     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("s%1").arg(formatIndex));
667     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("section"));
668     writer.writeEmptyElement(styleNS, QString::fromLatin1("section-properties"));
669     if (format.hasProperty(QTextFormat::BlockTopMargin))
670         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
671     if (format.hasProperty(QTextFormat::BlockBottomMargin))
672         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
673     if (format.hasProperty(QTextFormat::BlockLeftMargin))
674         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), format.leftMargin())) );
675     if (format.hasProperty(QTextFormat::BlockRightMargin))
676         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
677
678     writer.writeEndElement(); // style
679
680 // TODO consider putting the following properties in a qt-namespace.
681 // Position   position () const 
682 // qreal   border () const 
683 // QBrush   borderBrush () const 
684 // BorderStyle   borderStyle () const 
685 // qreal   padding () const 
686 // QTextLength   width () const 
687 // QTextLength   height () const 
688 // PageBreakFlags   pageBreakPolicy () const
689 }
690
691 void QTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format, int formatIndex) const
692 {
693     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
694     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("T%1").arg(formatIndex));
695     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table"));
696     writer.writeEmptyElement(styleNS, QString::fromLatin1("table-properties"));
697
698
699     qreal padding = format.topPadding();
700     if (padding > 0 && padding == format.bottomPadding()
701         && padding == format.leftPadding() && padding == format.rightPadding()) {
702         writer.writeAttribute(foNS, QString::fromLatin1("padding"), pixelToPoint(padding));
703     }
704     else {
705         if (padding > 0)
706             writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(padding));
707         if (format.bottomPadding() > 0)
708             writer.writeAttribute(foNS, QString::fromLatin1("padding-bottom"), pixelToPoint(format.bottomPadding()));
709         if (format.leftPadding() > 0)
710             writer.writeAttribute(foNS, QString::fromLatin1("padding-left"), pixelToPoint(format.leftPadding()));
711         if (format.rightPadding() > 0)
712             writer.writeAttribute(foNS, QString::fromLatin1("padding-right"), pixelToPoint(format.rightPadding()));
713     }
714
715     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
716         QString pos;
717         switch (format.verticalAlignment()) {
718         case QTextCharFormat::AlignMiddle:
719             pos = QString::fromLatin1("middle"); break;
720         case QTextCharFormat::AlignTop:
721             pos = QString::fromLatin1("top"); break;
722         case QTextCharFormat::AlignBottom:
723             pos = QString::fromLatin1("bottom"); break;
724         default:
725             pos = QString::fromLatin1("automatic"); break;
726         }
727         writer.writeAttribute(styleNS, QString::fromLatin1("vertical-align"), pos);
728     }
729
730     // TODO
731     // ODF just search for style-table-cell-properties-attlist)
732     // QTextFormat::BackgroundImageUrl
733     // format.background
734     // QTextFormat::FrameBorder
735
736     writer.writeEndElement(); // style
737 }
738
739 ///////////////////////
740
741 QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device)
742     : officeNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:office:1.0")),
743     textNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:text:1.0")),
744     styleNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:style:1.0")),
745     foNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")),
746     tableNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:table:1.0")),
747     drawNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")),
748     xlinkNS (QLatin1String("http://www.w3.org/1999/xlink")),
749     svgNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0")),
750     m_document(&document),
751     m_device(device),
752     m_strategy(0),
753     m_codec(0),
754     m_createArchive(true)
755 {
756 }
757
758 bool QTextOdfWriter::writeAll()
759 {
760     if (m_createArchive)
761         m_strategy = new QZipStreamStrategy(m_device);
762     else
763         m_strategy = new QXmlStreamStrategy(m_device);
764
765     if (!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) {
766         qWarning() << "QTextOdfWriter::writeAll: the device can not be opened for writing";
767         return false;
768     }
769     QXmlStreamWriter writer(m_strategy->contentStream);
770 #ifndef QT_NO_TEXTCODEC
771     if (m_codec)
772         writer.setCodec(m_codec);
773 #endif
774     // prettyfy
775     writer.setAutoFormatting(true);
776     writer.setAutoFormattingIndent(2);
777
778     writer.writeNamespace(officeNS, QString::fromLatin1("office"));
779     writer.writeNamespace(textNS, QString::fromLatin1("text"));
780     writer.writeNamespace(styleNS, QString::fromLatin1("style"));
781     writer.writeNamespace(foNS, QString::fromLatin1("fo"));
782     writer.writeNamespace(tableNS, QString::fromLatin1("table"));
783     writer.writeNamespace(drawNS, QString::fromLatin1("draw"));
784     writer.writeNamespace(xlinkNS, QString::fromLatin1("xlink"));
785     writer.writeNamespace(svgNS, QString::fromLatin1("svg"));
786     writer.writeStartDocument();
787     writer.writeStartElement(officeNS, QString::fromLatin1("document-content"));
788     writer.writeAttribute(officeNS, QString::fromLatin1("version"), QString::fromLatin1("1.2"));
789
790     // add fragments. (for character formats)
791     QTextDocumentPrivate::FragmentIterator fragIt = m_document->docHandle()->begin();
792     QSet<int> formats;
793     while (fragIt != m_document->docHandle()->end()) {
794         const QTextFragmentData * const frag = fragIt.value();
795         formats << frag->format;
796         ++fragIt;
797     }
798
799     // add blocks (for blockFormats)
800     QTextDocumentPrivate::BlockMap &blocks = m_document->docHandle()->blockMap();
801     QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin();
802     while (blockIt != blocks.end()) {
803         const QTextBlockData * const block = blockIt.value();
804         formats << block->format;
805         ++blockIt;
806     }
807
808     // add objects for lists, frames and tables
809     QVector<QTextFormat> allFormats = m_document->allFormats();
810     QList<int> copy = formats.toList();
811     for (QList<int>::Iterator iter = copy.begin(); iter != copy.end(); ++iter) {
812         QTextObject *object = m_document->objectForFormat(allFormats[*iter]);
813         if (object)
814             formats << object->formatIndex();
815     }
816
817     writeFormats(writer, formats);
818
819     writer.writeStartElement(officeNS, QString::fromLatin1("body"));
820     writer.writeStartElement(officeNS, QString::fromLatin1("text"));
821     QTextFrame *rootFrame = m_document->rootFrame();
822     writeFrame(writer, rootFrame);
823     writer.writeEndElement(); // text
824     writer.writeEndElement(); // body
825     writer.writeEndElement(); // document-content
826     writer.writeEndDocument();
827     delete m_strategy;
828     m_strategy = 0;
829
830     return true;
831 }
832
833 QT_END_NAMESPACE
834
835 #endif // QT_NO_TEXTODFWRITER