1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <qglobal.h>
41 
42 #ifndef QT_NO_TEXTODFWRITER
43 
44 #include "qtextodfwriter_p.h"
45 
46 #include <QImageReader>
47 #include <QImageWriter>
48 #include <QTextListFormat>
49 #include <QTextList>
50 #include <QBuffer>
51 #include <QUrl>
52 
53 #include "qtextdocument_p.h"
54 #include "qtexttable.h"
55 #include "qtextcursor.h"
56 #include "qtextimagehandler_p.h"
57 #include "qzipwriter_p.h"
58 
59 #include <QDebug>
60 
61 QT_BEGIN_NAMESPACE
62 
63 /// Convert pixels to postscript point units
pixelToPoint(qreal pixels)64 static QString pixelToPoint(qreal pixels)
65 {
66     // we hardcode 96 DPI, we do the same in the ODF importer to have a perfect roundtrip.
67     return QString::number(pixels * 72 / 96) + QLatin1String("pt");
68 }
69 
70 // strategies
71 class QOutputStrategy {
72 public:
QOutputStrategy()73     QOutputStrategy() : contentStream(nullptr), counter(1) { }
~QOutputStrategy()74     virtual ~QOutputStrategy() {}
75     virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) = 0;
76 
createUniqueImageName()77     QString createUniqueImageName()
78     {
79         return QString::fromLatin1("Pictures/Picture%1").arg(counter++);
80     }
81 
82     QIODevice *contentStream;
83     int counter;
84 };
85 
86 class QXmlStreamStrategy : public QOutputStrategy {
87 public:
QXmlStreamStrategy(QIODevice * device)88     QXmlStreamStrategy(QIODevice *device)
89     {
90         contentStream = device;
91     }
92 
~QXmlStreamStrategy()93     ~QXmlStreamStrategy()
94     {
95         if (contentStream)
96             contentStream->close();
97     }
addFile(const QString &,const QString &,const QByteArray &)98     virtual void addFile(const QString &, const QString &, const QByteArray &) override
99     {
100         // we ignore this...
101     }
102 };
103 
104 class QZipStreamStrategy : public QOutputStrategy {
105 public:
QZipStreamStrategy(QIODevice * device)106     QZipStreamStrategy(QIODevice *device)
107         : zip(device),
108         manifestWriter(&manifest)
109     {
110         QByteArray mime("application/vnd.oasis.opendocument.text");
111         zip.setCompressionPolicy(QZipWriter::NeverCompress);
112         zip.addFile(QString::fromLatin1("mimetype"), mime); // for mime-magick
113         zip.setCompressionPolicy(QZipWriter::AutoCompress);
114         contentStream = &content;
115         content.open(QIODevice::WriteOnly);
116         manifest.open(QIODevice::WriteOnly);
117 
118         manifestNS = QString::fromLatin1("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
119         // prettyfy
120         manifestWriter.setAutoFormatting(true);
121         manifestWriter.setAutoFormattingIndent(1);
122 
123         manifestWriter.writeNamespace(manifestNS, QString::fromLatin1("manifest"));
124         manifestWriter.writeStartDocument();
125         manifestWriter.writeStartElement(manifestNS, QString::fromLatin1("manifest"));
126         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("version"), QString::fromLatin1("1.2"));
127         addFile(QString::fromLatin1("/"), QString::fromLatin1("application/vnd.oasis.opendocument.text"));
128         addFile(QString::fromLatin1("content.xml"), QString::fromLatin1("text/xml"));
129     }
130 
~QZipStreamStrategy()131     ~QZipStreamStrategy()
132     {
133         manifestWriter.writeEndDocument();
134         manifest.close();
135         zip.addFile(QString::fromLatin1("META-INF/manifest.xml"), &manifest);
136         content.close();
137         zip.addFile(QString::fromLatin1("content.xml"), &content);
138         zip.close();
139     }
140 
addFile(const QString & fileName,const QString & mimeType,const QByteArray & bytes)141     virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) override
142     {
143         zip.addFile(fileName, bytes);
144         addFile(fileName, mimeType);
145     }
146 
147 private:
addFile(const QString & fileName,const QString & mimeType)148     void addFile(const QString &fileName, const QString &mimeType)
149     {
150         manifestWriter.writeEmptyElement(manifestNS, QString::fromLatin1("file-entry"));
151         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("media-type"), mimeType);
152         manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("full-path"), fileName);
153     }
154 
155     QBuffer content;
156     QBuffer manifest;
157     QZipWriter zip;
158     QXmlStreamWriter manifestWriter;
159     QString manifestNS;
160 };
161 
bulletChar(QTextListFormat::Style style)162 static QString bulletChar(QTextListFormat::Style style)
163 {
164     switch(style) {
165     case QTextListFormat::ListDisc:
166         return QChar(0x25cf); // bullet character
167     case QTextListFormat::ListCircle:
168         return QChar(0x25cb); // white circle
169     case QTextListFormat::ListSquare:
170         return QChar(0x25a1); // white square
171     case QTextListFormat::ListDecimal:
172         return QString::fromLatin1("1");
173     case QTextListFormat::ListLowerAlpha:
174         return QString::fromLatin1("a");
175     case QTextListFormat::ListUpperAlpha:
176         return QString::fromLatin1("A");
177     case QTextListFormat::ListLowerRoman:
178         return QString::fromLatin1("i");
179     case QTextListFormat::ListUpperRoman:
180         return QString::fromLatin1("I");
181     default:
182     case QTextListFormat::ListStyleUndefined:
183         return QString();
184     }
185 }
186 
borderStyleName(QTextFrameFormat::BorderStyle style)187 static QString borderStyleName(QTextFrameFormat::BorderStyle style)
188 {
189     switch (style) {
190     case QTextFrameFormat::BorderStyle_None:
191         return QString::fromLatin1("none");
192     case QTextFrameFormat::BorderStyle_Dotted:
193         return QString::fromLatin1("dotted");
194     case QTextFrameFormat::BorderStyle_Dashed:
195         return QString::fromLatin1("dashed");
196     case QTextFrameFormat::BorderStyle_Solid:
197         return QString::fromLatin1("solid");
198     case QTextFrameFormat::BorderStyle_Double:
199         return QString::fromLatin1("double");
200     case QTextFrameFormat::BorderStyle_DotDash:
201         return QString::fromLatin1("dashed");
202     case QTextFrameFormat::BorderStyle_DotDotDash:
203         return QString::fromLatin1("dotted");
204     case QTextFrameFormat::BorderStyle_Groove:
205         return QString::fromLatin1("groove");
206     case QTextFrameFormat::BorderStyle_Ridge:
207         return QString::fromLatin1("ridge");
208     case QTextFrameFormat::BorderStyle_Inset:
209         return QString::fromLatin1("inset");
210     case QTextFrameFormat::BorderStyle_Outset:
211         return QString::fromLatin1("outset");
212     }
213     return QString::fromLatin1("");
214 }
215 
writeFrame(QXmlStreamWriter & writer,const QTextFrame * frame)216 void QTextOdfWriter::writeFrame(QXmlStreamWriter &writer, const QTextFrame *frame)
217 {
218     Q_ASSERT(frame);
219     const QTextTable *table = qobject_cast<const QTextTable*> (frame);
220 
221     if (table) { // Start a table.
222         writer.writeStartElement(tableNS, QString::fromLatin1("table"));
223         writer.writeAttribute(tableNS, QString::fromLatin1("style-name"),
224                               QString::fromLatin1("Table%1").arg(table->formatIndex()));
225         // check if column widths are set, if so add TableNS line above for all columns and link to style
226         if (m_tableFormatsWithColWidthConstraints.contains(table->formatIndex())) {
227             for (int colit = 0; colit < table->columns(); ++colit) {
228                 writer.writeStartElement(tableNS, QString::fromLatin1("table-column"));
229                 writer.writeAttribute(tableNS, QString::fromLatin1("style-name"),
230                                       QString::fromLatin1("Table%1.%2").arg(table->formatIndex()).arg(colit));
231                 writer.writeEndElement();
232             }
233         } else {
234             writer.writeEmptyElement(tableNS, QString::fromLatin1("table-column"));
235             writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-repeated"),
236                                   QString::number(table->columns()));
237         }
238     } else if (frame->document() && frame->document()->rootFrame() != frame) { // start a section
239         writer.writeStartElement(textNS, QString::fromLatin1("section"));
240     }
241 
242     QTextFrame::iterator iterator = frame->begin();
243     QTextFrame *child = nullptr;
244 
245     int tableRow = -1;
246     while (! iterator.atEnd()) {
247         if (iterator.currentFrame() && child != iterator.currentFrame())
248             writeFrame(writer, iterator.currentFrame());
249         else { // no frame, its a block
250             QTextBlock block = iterator.currentBlock();
251             if (table) {
252                 QTextTableCell cell = table->cellAt(block.position());
253                 if (tableRow < cell.row()) {
254                     if (tableRow >= 0)
255                         writer.writeEndElement(); // close table row
256                     tableRow = cell.row();
257                     writer.writeStartElement(tableNS, QString::fromLatin1("table-row"));
258                 }
259                 writer.writeStartElement(tableNS, QString::fromLatin1("table-cell"));
260                 if (cell.columnSpan() > 1)
261                     writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-spanned"), QString::number(cell.columnSpan()));
262                 if (cell.rowSpan() > 1)
263                     writer.writeAttribute(tableNS, QString::fromLatin1("number-rows-spanned"), QString::number(cell.rowSpan()));
264                 if (cell.format().isTableCellFormat()) {
265                     if (m_cellFormatsInTablesWithBorders.contains(cell.tableCellFormatIndex()) ) {
266                         // writing table:style-name tag in <table:table-cell> element
267                         writer.writeAttribute(tableNS, QString::fromLatin1("style-name"),
268                                               QString::fromLatin1("TB%1.%2").arg(table->formatIndex())
269                                               .arg(cell.tableCellFormatIndex()));
270                     } else {
271                         writer.writeAttribute(tableNS, QString::fromLatin1("style-name"),
272                                               QString::fromLatin1("T%1").arg(cell.tableCellFormatIndex()));
273                     }
274                 }
275             }
276             writeBlock(writer, block);
277             if (table)
278                 writer.writeEndElement(); // table-cell
279         }
280         child = iterator.currentFrame();
281         ++iterator;
282     }
283     if (tableRow >= 0)
284         writer.writeEndElement(); // close table-row
285 
286     if (table || (frame->document() && frame->document()->rootFrame() != frame))
287         writer.writeEndElement();  // close table or section element
288 }
289 
writeBlock(QXmlStreamWriter & writer,const QTextBlock & block)290 void QTextOdfWriter::writeBlock(QXmlStreamWriter &writer, const QTextBlock &block)
291 {
292     if (block.textList()) { // its a list-item
293         const int listLevel = block.textList()->format().indent();
294         if (m_listStack.isEmpty() || m_listStack.top() != block.textList()) {
295             // not the same list we were in.
296             while (m_listStack.count() >= listLevel && !m_listStack.isEmpty() && m_listStack.top() != block.textList() ) { // we need to close tags
297                 m_listStack.pop();
298                 writer.writeEndElement(); // list
299                 if (m_listStack.count())
300                     writer.writeEndElement(); // list-item
301             }
302             while (m_listStack.count() < listLevel) {
303                 if (m_listStack.count())
304                     writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
305                 writer.writeStartElement(textNS, QString::fromLatin1("list"));
306                 if (m_listStack.count() == listLevel - 1) {
307                     m_listStack.push(block.textList());
308                     writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("L%1")
309                             .arg(block.textList()->formatIndex()));
310                 }
311                 else {
312                     m_listStack.push(0);
313                 }
314             }
315         }
316         writer.writeStartElement(textNS, QString::fromLatin1("list-item"));
317     }
318     else {
319         while (! m_listStack.isEmpty()) {
320             m_listStack.pop();
321             writer.writeEndElement(); // list
322             if (m_listStack.count())
323                 writer.writeEndElement(); // list-item
324         }
325     }
326 
327     if (block.length() == 1) { // only a linefeed
328         writer.writeEmptyElement(textNS, QString::fromLatin1("p"));
329         writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
330             .arg(block.blockFormatIndex()));
331         if (block.textList())
332             writer.writeEndElement(); // numbered-paragraph
333         return;
334     }
335     writer.writeStartElement(textNS, QString::fromLatin1("p"));
336     writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1")
337         .arg(block.blockFormatIndex()));
338     for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
339         bool isHyperlink = frag.fragment().charFormat().hasProperty(QTextFormat::AnchorHref);
340         if (isHyperlink) {
341             QString value = frag.fragment().charFormat().property(QTextFormat::AnchorHref).toString();
342             writer.writeStartElement(textNS, QString::fromLatin1("a"));
343             writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), value);
344         }
345         writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed in front of it.
346         writer.writeStartElement(textNS, QString::fromLatin1("span"));
347 
348         QString fragmentText = frag.fragment().text();
349         if (fragmentText.length() == 1 && fragmentText[0] == QChar(0xFFFC)) { // its an inline character.
350             writeInlineCharacter(writer, frag.fragment());
351             writer.writeEndElement(); // span
352             continue;
353         }
354 
355         writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("c%1")
356             .arg(frag.fragment().charFormatIndex()));
357         bool escapeNextSpace = true;
358         int precedingSpaces = 0;
359         int exportedIndex = 0;
360         for (int i=0; i <= fragmentText.count(); ++i) {
361             QChar character = (i == fragmentText.count() ? QChar() : fragmentText.at(i));
362             bool isSpace = character.unicode() == ' ';
363 
364             // find more than one space. -> <text:s text:c="2" />
365             if (!isSpace && escapeNextSpace && precedingSpaces > 1) {
366                 const bool startParag = exportedIndex == 0 && i == precedingSpaces;
367                 if (!startParag)
368                     writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingSpaces + 1 - exportedIndex));
369                 writer.writeEmptyElement(textNS, QString::fromLatin1("s"));
370                 const int count = precedingSpaces - (startParag?0:1);
371                 if (count > 1)
372                     writer.writeAttribute(textNS, QString::fromLatin1("c"), QString::number(count));
373                 precedingSpaces = 0;
374                 exportedIndex = i;
375             }
376 
377             if (i < fragmentText.count()) {
378                 if (character.unicode() == 0x2028) { // soft-return
379                     //if (exportedIndex < i)
380                     writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex));
381                     // adding tab before line-break, so last line in justified paragraph
382                     // will not stretch to the end
383                     writer.writeEmptyElement(textNS, QString::fromLatin1("tab"));
384                     writer.writeEmptyElement(textNS, QString::fromLatin1("line-break"));
385                     exportedIndex = i+1;
386                     continue;
387                 } else if (character.unicode() == '\t') { // Tab
388                     //if (exportedIndex < i)
389                         writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex));
390                     writer.writeEmptyElement(textNS, QString::fromLatin1("tab"));
391                     exportedIndex = i+1;
392                     precedingSpaces = 0;
393                 } else if (isSpace) {
394                     ++precedingSpaces;
395                     escapeNextSpace = true;
396                 } else if (!isSpace) {
397                     precedingSpaces = 0;
398                 }
399             }
400         }
401 
402         writer.writeCharacters(fragmentText.mid(exportedIndex));
403         writer.writeEndElement(); // span
404         writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed behind it.
405         if (isHyperlink)
406             writer.writeEndElement(); // a
407     }
408     writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed behind it.
409     writer.writeEndElement(); // p
410     if (block.textList())
411         writer.writeEndElement(); // list-item
412 }
413 
probeImageData(QIODevice * device,QImage * image,QString * mimeType,qreal * width,qreal * height)414 static bool probeImageData(QIODevice *device, QImage *image, QString *mimeType, qreal *width, qreal *height)
415 {
416     QImageReader reader(device);
417     const QByteArray format = reader.format().toLower();
418     if (format == "png") {
419         *mimeType = QStringLiteral("image/png");
420     } else if (format == "jpg") {
421         *mimeType = QStringLiteral("image/jpg");
422     } else if (format == "svg") {
423         *mimeType = QStringLiteral("image/svg+xml");
424     } else {
425         *image = reader.read();
426         return false;
427     }
428 
429     const QSize size = reader.size();
430 
431     *width = size.width();
432     *height = size.height();
433 
434     return true;
435 }
436 
writeInlineCharacter(QXmlStreamWriter & writer,const QTextFragment & fragment) const437 void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const
438 {
439     writer.writeStartElement(drawNS, QString::fromLatin1("frame"));
440     if (m_strategy == nullptr) {
441         // don't do anything.
442     }
443     else if (fragment.charFormat().isImageFormat()) {
444         QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
445         writer.writeAttribute(drawNS, QString::fromLatin1("name"), imageFormat.name());
446 
447         QByteArray data;
448         QString mimeType;
449         qreal width = 0;
450         qreal height = 0;
451 
452         QImage image;
453         QString name = imageFormat.name();
454         if (name.startsWith(QLatin1String(":/"))) // auto-detect resources
455             name.prepend(QLatin1String("qrc"));
456         QUrl url = QUrl(name);
457         const QVariant variant = m_document->resource(QTextDocument::ImageResource, url);
458         if (variant.userType() == QMetaType::QPixmap || variant.userType() == QMetaType::QImage) {
459             image = qvariant_cast<QImage>(variant);
460         } else if (variant.userType() == QMetaType::QByteArray) {
461             data = variant.toByteArray();
462 
463             QBuffer buffer(&data);
464             buffer.open(QIODevice::ReadOnly);
465             probeImageData(&buffer, &image, &mimeType, &width, &height);
466         } else {
467             // try direct loading
468             QFile file(imageFormat.name());
469             if (file.open(QIODevice::ReadOnly) && !probeImageData(&file, &image, &mimeType, &width, &height)) {
470                 file.seek(0);
471                 data = file.readAll();
472             }
473         }
474 
475         if (! image.isNull()) {
476             QBuffer imageBytes;
477 
478             int imgQuality = imageFormat.quality();
479             if (imgQuality >= 100 || imgQuality <= 0 || image.hasAlphaChannel()) {
480                 QImageWriter imageWriter(&imageBytes, "png");
481                 imageWriter.write(image);
482 
483                 data = imageBytes.data();
484                 mimeType = QStringLiteral("image/png");
485             } else {
486                 // Write images without alpha channel as jpg with quality set by QTextImageFormat
487                 QImageWriter imageWriter(&imageBytes, "jpg");
488                 imageWriter.setQuality(imgQuality);
489                 imageWriter.write(image);
490 
491                 data = imageBytes.data();
492                 mimeType = QStringLiteral("image/jpg");
493             }
494 
495             width = image.width();
496             height = image.height();
497         }
498 
499         if (!data.isEmpty()) {
500             if (imageFormat.hasProperty(QTextFormat::ImageWidth)) {
501                 width = imageFormat.width();
502             }
503             if (imageFormat.hasProperty(QTextFormat::ImageHeight)) {
504                 height = imageFormat.height();
505             }
506 
507             QString filename = m_strategy->createUniqueImageName();
508 
509             m_strategy->addFile(filename, mimeType, data);
510 
511             writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width));
512             writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height));
513             writer.writeAttribute(textNS, QStringLiteral("anchor-type"), QStringLiteral("as-char"));
514             writer.writeStartElement(drawNS, QString::fromLatin1("image"));
515             writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename);
516             writer.writeEndElement(); // image
517         }
518     }
519     writer.writeEndElement(); // frame
520 }
521 
writeFormats(QXmlStreamWriter & writer,const QSet<int> & formats) const522 void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, const QSet<int> &formats) const
523 {
524     writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles"));
525     QVector<QTextFormat> allStyles = m_document->allFormats();
526     for (int formatIndex : formats) {
527         QTextFormat textFormat = allStyles.at(formatIndex);
528         switch (textFormat.type()) {
529         case QTextFormat::CharFormat:
530             if (textFormat.isTableCellFormat())
531                 writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex, allStyles);
532             else
533                 writeCharacterFormat(writer, textFormat.toCharFormat(), formatIndex);
534             break;
535         case QTextFormat::BlockFormat:
536             writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex);
537             break;
538         case QTextFormat::ListFormat:
539             writeListFormat(writer, textFormat.toListFormat(), formatIndex);
540             break;
541         case QTextFormat::FrameFormat:
542             if (textFormat.isTableFormat())
543                 writeTableFormat(writer, textFormat.toTableFormat(), formatIndex);
544             else
545                 writeFrameFormat(writer, textFormat.toFrameFormat(), formatIndex);
546             break;
547 #if QT_DEPRECATED_SINCE(5, 3)
548         case QTextFormat::TableFormat:
549             // this case never happens, because TableFormat is a FrameFormat
550             Q_UNREACHABLE();
551             break;
552 #endif
553         }
554     }
555 
556     writer.writeEndElement(); // automatic-styles
557 }
558 
writeBlockFormat(QXmlStreamWriter & writer,QTextBlockFormat format,int formatIndex) const559 void QTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const
560 {
561     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
562     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("p%1").arg(formatIndex));
563     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("paragraph"));
564     writer.writeStartElement(styleNS, QString::fromLatin1("paragraph-properties"));
565 
566     if (format.hasProperty(QTextBlockFormat::LineHeightType)) {
567         const int blockLineHeightType = format.lineHeightType();
568         const qreal blockLineHeight = format.lineHeight();
569         QString type, value;
570         switch (blockLineHeightType) {
571         case QTextBlockFormat::SingleHeight:
572             type = QString::fromLatin1("line-height");
573             value = QString::fromLatin1("100%");
574             break;
575         case QTextBlockFormat::ProportionalHeight:
576             type = QString::fromLatin1("line-height");
577             value = QString::number(blockLineHeight) + QString::fromLatin1("%");
578             break;
579         case QTextBlockFormat::FixedHeight:
580             type = QString::fromLatin1("line-height");
581             value = pixelToPoint(qMax(qreal(0.), blockLineHeight));
582             break;
583         case QTextBlockFormat::MinimumHeight:
584             type = QString::fromLatin1("line-height-at-least");
585             value = pixelToPoint(qMax(qreal(0.), blockLineHeight));
586             break;
587         case QTextBlockFormat::LineDistanceHeight:
588             type = QString::fromLatin1("line-spacing");
589             value = pixelToPoint(qMax(qreal(0.), blockLineHeight));
590         }
591 
592         if (!type.isNull())
593             writer.writeAttribute(styleNS, type, value);
594     }
595 
596     if (format.hasProperty(QTextFormat::BlockAlignment)) {
597         const Qt::Alignment alignment = format.alignment() & Qt::AlignHorizontal_Mask;
598         QString value;
599         if (alignment == Qt::AlignLeading)
600             value = QString::fromLatin1("start");
601         else if (alignment == Qt::AlignTrailing)
602             value = QString::fromLatin1("end");
603         else if (alignment == (Qt::AlignLeft | Qt::AlignAbsolute))
604             value = QString::fromLatin1("left");
605         else if (alignment == (Qt::AlignRight | Qt::AlignAbsolute))
606             value = QString::fromLatin1("right");
607         else if (alignment == Qt::AlignHCenter)
608             value = QString::fromLatin1("center");
609         else if (alignment == Qt::AlignJustify)
610             value = QString::fromLatin1("justify");
611         else
612             qWarning() << "QTextOdfWriter: unsupported paragraph alignment; " << format.alignment();
613         if (! value.isNull())
614             writer.writeAttribute(foNS, QString::fromLatin1("text-align"), value);
615     }
616 
617     if (format.hasProperty(QTextFormat::BlockTopMargin))
618         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
619     if (format.hasProperty(QTextFormat::BlockBottomMargin))
620         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
621     if (format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent))
622         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.),
623             format.leftMargin() + format.indent())));
624     if (format.hasProperty(QTextFormat::BlockRightMargin))
625         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
626     if (format.hasProperty(QTextFormat::TextIndent))
627         writer.writeAttribute(foNS, QString::fromLatin1("text-indent"), pixelToPoint(format.textIndent()));
628     if (format.hasProperty(QTextFormat::PageBreakPolicy)) {
629         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore)
630             writer.writeAttribute(foNS, QString::fromLatin1("break-before"), QString::fromLatin1("page"));
631         if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter)
632             writer.writeAttribute(foNS, QString::fromLatin1("break-after"), QString::fromLatin1("page"));
633     }
634     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
635         QBrush brush = format.background();
636         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
637     }
638     if (format.hasProperty(QTextFormat::BlockNonBreakableLines))
639         writer.writeAttribute(foNS, QString::fromLatin1("keep-together"),
640                 format.nonBreakableLines() ? QString::fromLatin1("true") : QString::fromLatin1("false"));
641     if (format.hasProperty(QTextFormat::TabPositions)) {
642         QList<QTextOption::Tab> tabs = format.tabPositions();
643         writer.writeStartElement(styleNS, QString::fromLatin1("tab-stops"));
644         QList<QTextOption::Tab>::Iterator iterator = tabs.begin();
645         while(iterator != tabs.end()) {
646             writer.writeEmptyElement(styleNS, QString::fromLatin1("tab-stop"));
647             writer.writeAttribute(styleNS, QString::fromLatin1("position"), pixelToPoint(iterator->position) );
648             QString type;
649             switch(iterator->type) {
650             case QTextOption::DelimiterTab: type = QString::fromLatin1("char"); break;
651             case QTextOption::LeftTab: type = QString::fromLatin1("left"); break;
652             case QTextOption::RightTab: type = QString::fromLatin1("right"); break;
653             case QTextOption::CenterTab: type = QString::fromLatin1("center"); break;
654             }
655             writer.writeAttribute(styleNS, QString::fromLatin1("type"), type);
656             if (!iterator->delimiter.isNull())
657                 writer.writeAttribute(styleNS, QString::fromLatin1("char"), iterator->delimiter);
658             ++iterator;
659         }
660 
661         writer.writeEndElement(); // tab-stops
662     }
663 
664     writer.writeEndElement(); // paragraph-properties
665     writer.writeEndElement(); // style
666 }
667 
writeCharacterFormat(QXmlStreamWriter & writer,QTextCharFormat format,int formatIndex) const668 void QTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const
669 {
670     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
671     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("c%1").arg(formatIndex));
672     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("text"));
673     writer.writeEmptyElement(styleNS, QString::fromLatin1("text-properties"));
674     if (format.fontItalic())
675         writer.writeAttribute(foNS, QString::fromLatin1("font-style"), QString::fromLatin1("italic"));
676     if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != QFont::Normal) {
677         QString value;
678         if (format.fontWeight() == QFont::Bold)
679             value = QString::fromLatin1("bold");
680         else
681             value = QString::number(format.fontWeight() * 10);
682         writer.writeAttribute(foNS, QString::fromLatin1("font-weight"), value);
683     }
684     if (format.hasProperty(QTextFormat::FontFamily))
685         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), format.fontFamily());
686     else
687         writer.writeAttribute(foNS, QString::fromLatin1("font-family"), QString::fromLatin1("Sans")); // Qt default
688     if (format.hasProperty(QTextFormat::FontPointSize))
689         writer.writeAttribute(foNS, QString::fromLatin1("font-size"), QString::fromLatin1("%1pt").arg(format.fontPointSize()));
690     if (format.hasProperty(QTextFormat::FontCapitalization)) {
691         switch(format.fontCapitalization()) {
692         case QFont::MixedCase:
693             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("none")); break;
694         case QFont::AllUppercase:
695             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("uppercase")); break;
696         case QFont::AllLowercase:
697             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("lowercase")); break;
698         case QFont::Capitalize:
699             writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("capitalize")); break;
700         case QFont::SmallCaps:
701             writer.writeAttribute(foNS, QString::fromLatin1("font-variant"), QString::fromLatin1("small-caps")); break;
702         }
703     }
704     if (format.hasProperty(QTextFormat::FontLetterSpacing))
705         writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontLetterSpacing()));
706     if (format.hasProperty(QTextFormat::FontWordSpacing) && format.fontWordSpacing() != 0)
707             writer.writeAttribute(foNS, QString::fromLatin1("word-spacing"), pixelToPoint(format.fontWordSpacing()));
708     if (format.hasProperty(QTextFormat::FontUnderline))
709         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-type"),
710                 format.fontUnderline() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
711     if (format.hasProperty(QTextFormat::FontOverline)) {
712         //   bool   fontOverline () const  TODO
713     }
714     if (format.hasProperty(QTextFormat::FontStrikeOut))
715         writer.writeAttribute(styleNS,QString::fromLatin1( "text-line-through-type"),
716                 format.fontStrikeOut() ? QString::fromLatin1("single") : QString::fromLatin1("none"));
717     if (format.hasProperty(QTextFormat::TextUnderlineColor))
718         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-color"), format.underlineColor().name());
719     if (format.hasProperty(QTextFormat::FontFixedPitch)) {
720         //   bool   fontFixedPitch () const  TODO
721     }
722     if (format.hasProperty(QTextFormat::TextUnderlineStyle)) {
723         QString value;
724         switch (format.underlineStyle()) {
725         case QTextCharFormat::NoUnderline: value = QString::fromLatin1("none"); break;
726         case QTextCharFormat::SingleUnderline: value = QString::fromLatin1("solid"); break;
727         case QTextCharFormat::DashUnderline: value = QString::fromLatin1("dash"); break;
728         case QTextCharFormat::DotLine: value = QString::fromLatin1("dotted"); break;
729         case QTextCharFormat::DashDotLine: value = QString::fromLatin1("dash-dot"); break;
730         case QTextCharFormat::DashDotDotLine: value = QString::fromLatin1("dot-dot-dash"); break;
731         case QTextCharFormat::WaveUnderline: value = QString::fromLatin1("wave"); break;
732         case QTextCharFormat::SpellCheckUnderline: value = QString::fromLatin1("none"); break;
733         }
734         writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-style"), value);
735     }
736     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
737         QString value;
738         switch (format.verticalAlignment()) {
739         case QTextCharFormat::AlignMiddle:
740         case QTextCharFormat::AlignNormal: value = QString::fromLatin1("0%"); break;
741         case QTextCharFormat::AlignSuperScript: value = QString::fromLatin1("super"); break;
742         case QTextCharFormat::AlignSubScript: value = QString::fromLatin1("sub"); break;
743         case QTextCharFormat::AlignTop: value = QString::fromLatin1("100%"); break;
744         case QTextCharFormat::AlignBottom : value = QString::fromLatin1("-100%"); break;
745         case QTextCharFormat::AlignBaseline: break;
746         }
747         writer.writeAttribute(styleNS, QString::fromLatin1("text-position"), value);
748     }
749     if (format.hasProperty(QTextFormat::TextOutline))
750         writer.writeAttribute(styleNS, QString::fromLatin1("text-outline"), QString::fromLatin1("true"));
751     if (format.hasProperty(QTextFormat::TextToolTip)) {
752         //   QString   toolTip () const  TODO
753     }
754     if (format.hasProperty(QTextFormat::IsAnchor)) {
755         //   bool   isAnchor () const  TODO
756     }
757     if (format.hasProperty(QTextFormat::AnchorHref)) {
758         //   QString   anchorHref () const  TODO
759     }
760     if (format.hasProperty(QTextFormat::AnchorName)) {
761         //   QString   anchorName () const  TODO
762     }
763     if (format.hasProperty(QTextFormat::ForegroundBrush)) {
764         QBrush brush = format.foreground();
765         writer.writeAttribute(foNS, QString::fromLatin1("color"), brush.color().name());
766     }
767     if (format.hasProperty(QTextFormat::BackgroundBrush)) {
768         QBrush brush = format.background();
769         writer.writeAttribute(foNS, QString::fromLatin1("background-color"), brush.color().name());
770     }
771 
772     writer.writeEndElement(); // style
773 }
774 
writeListFormat(QXmlStreamWriter & writer,QTextListFormat format,int formatIndex) const775 void QTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const
776 {
777     writer.writeStartElement(textNS, QString::fromLatin1("list-style"));
778     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("L%1").arg(formatIndex));
779 
780     QTextListFormat::Style style = format.style();
781     if (style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
782             || style == QTextListFormat::ListUpperAlpha
783             || style == QTextListFormat::ListLowerRoman
784             || style == QTextListFormat::ListUpperRoman) {
785         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-number"));
786         writer.writeAttribute(styleNS, QString::fromLatin1("num-format"), bulletChar(style));
787 
788         if (format.hasProperty(QTextFormat::ListNumberSuffix))
789             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), format.numberSuffix());
790         else
791             writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), QString::fromLatin1("."));
792 
793         if (format.hasProperty(QTextFormat::ListNumberPrefix))
794             writer.writeAttribute(styleNS, QString::fromLatin1("num-prefix"), format.numberPrefix());
795 
796     } else {
797         writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-bullet"));
798         writer.writeAttribute(textNS, QString::fromLatin1("bullet-char"), bulletChar(style));
799     }
800 
801     writer.writeAttribute(textNS, QString::fromLatin1("level"), QString::number(format.indent()));
802     writer.writeEmptyElement(styleNS, QString::fromLatin1("list-level-properties"));
803     writer.writeAttribute(foNS, QString::fromLatin1("text-align"), QString::fromLatin1("start"));
804     QString spacing = QString::fromLatin1("%1mm").arg(format.indent() * 8);
805     writer.writeAttribute(textNS, QString::fromLatin1("space-before"), spacing);
806     //writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing);
807 
808     writer.writeEndElement(); // list-level-style-*
809     writer.writeEndElement(); // list-style
810 }
811 
writeFrameFormat(QXmlStreamWriter & writer,QTextFrameFormat format,int formatIndex) const812 void QTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const
813 {
814     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
815     writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("s%1").arg(formatIndex));
816     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("section"));
817     writer.writeEmptyElement(styleNS, QString::fromLatin1("section-properties"));
818     if (format.hasProperty(QTextFormat::FrameTopMargin))
819         writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) );
820     if (format.hasProperty(QTextFormat::FrameBottomMargin))
821         writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );
822     if (format.hasProperty(QTextFormat::FrameLeftMargin))
823         writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), format.leftMargin())) );
824     if (format.hasProperty(QTextFormat::FrameRightMargin))
825         writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) );
826 
827     writer.writeEndElement(); // style
828 
829 // TODO consider putting the following properties in a qt-namespace.
830 // Position   position () const
831 // qreal   border () const
832 // QBrush   borderBrush () const
833 // BorderStyle   borderStyle () const
834 // qreal   padding () const
835 // QTextLength   width () const
836 // QTextLength   height () const
837 // PageBreakFlags   pageBreakPolicy () const
838 }
839 
writeTableFormat(QXmlStreamWriter & writer,QTextTableFormat format,int formatIndex) const840 void QTextOdfWriter::writeTableFormat(QXmlStreamWriter &writer, QTextTableFormat format, int formatIndex) const
841 {
842     // start writing table style element
843     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
844     writer.writeAttribute(styleNS, QString::fromLatin1("name"),
845                           QString::fromLatin1("Table%1").arg(formatIndex));
846     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table"));
847     writer.writeEmptyElement(styleNS, QString::fromLatin1("table-properties"));
848 
849     if (m_tableFormatsWithBorders.contains(formatIndex)) {
850         // write border format collapsing to table style
851         writer.writeAttribute(tableNS, QString::fromLatin1("border-model"),
852                               QString::fromLatin1("collapsing"));
853     }
854     const char* align = nullptr;
855     switch (format.alignment()) {
856     case Qt::AlignLeft:
857         align = "left";
858         break;
859     case Qt::AlignRight:
860         align = "right";
861         break;
862     case Qt::AlignHCenter:
863         align = "center";
864         break;
865     case Qt::AlignJustify:
866         align = "margins";
867         break;
868     }
869     if (align)
870        writer.writeAttribute(tableNS, QString::fromLatin1("align"), QString::fromLatin1(align));
871     if (format.width().rawValue()) {
872         writer.writeAttribute(styleNS, QString::fromLatin1("width"),
873                               QString::number(format.width().rawValue()) + QLatin1String("pt"));
874     }
875     writer.writeEndElement();
876     // start writing table-column style element
877     if (format.columnWidthConstraints().size()) {
878         // write table-column-properties for columns with constraints
879         m_tableFormatsWithColWidthConstraints.insert(formatIndex); // needed for linking of columns to styles
880         for (int colit = 0; colit < format.columnWidthConstraints().size(); ++colit) {
881             writer.writeStartElement(styleNS, QString::fromLatin1("style"));
882             writer.writeAttribute(styleNS, QString::fromLatin1("name"),
883                                   QString::fromLatin1("Table%1.%2").arg(formatIndex).arg(colit));
884             writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table-column"));
885             writer.writeEmptyElement(styleNS, QString::fromLatin1("table-column-properties"));
886             QString columnWidth;
887             if (format.columnWidthConstraints().at(colit).type() == QTextLength::PercentageLength) {
888                 columnWidth = QString::number(format.columnWidthConstraints().at(colit).rawValue())
889                         + QLatin1String("%");
890             } else if (format.columnWidthConstraints().at(colit).type() == QTextLength::FixedLength) {
891                 columnWidth = QString::number(format.columnWidthConstraints().at(colit).rawValue())
892                         + QLatin1String("pt");
893             } else {
894                 //!! HARD-CODING variableWidth Constraints to 100% / nr constraints
895                 columnWidth = QString::number(100 / format.columnWidthConstraints().size())
896                                       + QLatin1String("%");
897             }
898             writer.writeAttribute(styleNS, QString::fromLatin1("column-width"), columnWidth);
899             writer.writeEndElement();
900         }
901     }
902 }
903 
writeTableCellFormat(QXmlStreamWriter & writer,QTextTableCellFormat format,int formatIndex,QVector<QTextFormat> & styles) const904 void QTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format,
905                                           int formatIndex, QVector<QTextFormat> &styles) const
906 {
907     // check for all table cells here if they are in a table with border
908     if (m_cellFormatsInTablesWithBorders.contains(formatIndex)) {
909         const QVector<int> tableIdVector = m_cellFormatsInTablesWithBorders.value(formatIndex);
910         for (const auto &tableId : tableIdVector) {
911             const auto &tmpStyle = styles.at(tableId);
912             if (tmpStyle.isTableFormat()) {
913                 QTextTableFormat tableFormatTmp = tmpStyle.toTableFormat();
914                 tableCellStyleElement(writer, formatIndex, format, true, tableId, tableFormatTmp);
915             } else {
916                 qDebug("QTextOdfWriter::writeTableCellFormat: ERROR writing table border format");
917             }
918         }
919     }
920     tableCellStyleElement(writer, formatIndex, format, false);
921 }
922 
tableCellStyleElement(QXmlStreamWriter & writer,const int & formatIndex,const QTextTableCellFormat & format,bool hasBorder,int tableId,const QTextTableFormat tableFormatTmp) const923 void QTextOdfWriter::tableCellStyleElement(QXmlStreamWriter &writer, const int &formatIndex,
924                                            const QTextTableCellFormat &format,
925                                            bool hasBorder, int tableId,
926                                            const QTextTableFormat tableFormatTmp) const {
927     writer.writeStartElement(styleNS, QString::fromLatin1("style"));
928     if (hasBorder) {
929         writer.writeAttribute(styleNS, QString::fromLatin1("name"),
930                               QString::fromLatin1("TB%1.%2").arg(tableId).arg(formatIndex));
931     } else {
932         writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("T%1").arg(formatIndex));
933     }
934     writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table-cell"));
935     writer.writeEmptyElement(styleNS, QString::fromLatin1("table-cell-properties"));
936     if (hasBorder) {
937         writer.writeAttribute(foNS, QString::fromLatin1("border"),
938                               pixelToPoint(tableFormatTmp.border()) + QLatin1String(" ")
939                               + borderStyleName(tableFormatTmp.borderStyle()) + QLatin1String(" ")
940                               + tableFormatTmp.borderBrush().color().name(QColor::HexRgb));
941     }
942     qreal topPadding = format.topPadding();
943     qreal padding = topPadding + tableFormatTmp.cellPadding();
944     if (padding > 0 && topPadding == format.bottomPadding()
945         && topPadding == format.leftPadding() && topPadding == format.rightPadding()) {
946         writer.writeAttribute(foNS, QString::fromLatin1("padding"), pixelToPoint(padding));
947     }
948     else {
949         if (padding > 0)
950             writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(padding));
951         padding = format.bottomPadding() + tableFormatTmp.cellPadding();
952         if (padding > 0)
953             writer.writeAttribute(foNS, QString::fromLatin1("padding-bottom"),
954                                   pixelToPoint(padding));
955         padding = format.leftPadding() + tableFormatTmp.cellPadding();
956         if (padding > 0)
957             writer.writeAttribute(foNS, QString::fromLatin1("padding-left"),
958                                   pixelToPoint(padding));
959         padding = format.rightPadding() + tableFormatTmp.cellPadding();
960         if (padding > 0)
961             writer.writeAttribute(foNS, QString::fromLatin1("padding-right"),
962                                   pixelToPoint(padding));
963     }
964 
965     if (format.hasProperty(QTextFormat::TextVerticalAlignment)) {
966         QString pos;
967         switch (format.verticalAlignment()) {  // TODO - review: doesn't handle all cases
968         case QTextCharFormat::AlignMiddle:
969             pos = QString::fromLatin1("middle"); break;
970         case QTextCharFormat::AlignTop:
971             pos = QString::fromLatin1("top"); break;
972         case QTextCharFormat::AlignBottom:
973             pos = QString::fromLatin1("bottom"); break;
974         default:
975             pos = QString::fromLatin1("automatic"); break;
976         }
977         writer.writeAttribute(styleNS, QString::fromLatin1("vertical-align"), pos);
978     }
979 
980     // TODO
981     // ODF just search for style-table-cell-properties-attlist)
982     // QTextFormat::BackgroundImageUrl
983     // format.background
984     writer.writeEndElement(); // style
985 }
986 
987 ///////////////////////
988 
QTextOdfWriter(const QTextDocument & document,QIODevice * device)989 QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device)
990     : officeNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:office:1.0")),
991     textNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:text:1.0")),
992     styleNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:style:1.0")),
993     foNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")),
994     tableNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:table:1.0")),
995     drawNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")),
996     xlinkNS (QLatin1String("http://www.w3.org/1999/xlink")),
997     svgNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0")),
998     m_document(&document),
999     m_device(device),
1000     m_strategy(nullptr),
1001     m_codec(nullptr),
1002     m_createArchive(true)
1003 {
1004 }
1005 
writeAll()1006 bool QTextOdfWriter::writeAll()
1007 {
1008     if (m_createArchive)
1009         m_strategy = new QZipStreamStrategy(m_device);
1010     else
1011         m_strategy = new QXmlStreamStrategy(m_device);
1012 
1013     if (!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) {
1014         qWarning("QTextOdfWriter::writeAll: the device cannot be opened for writing");
1015         return false;
1016     }
1017     QXmlStreamWriter writer(m_strategy->contentStream);
1018 #if QT_CONFIG(textcodec)
1019     if (m_codec)
1020         writer.setCodec(m_codec);
1021 #endif
1022     // prettyfy
1023     writer.setAutoFormatting(true);
1024     writer.setAutoFormattingIndent(2);
1025 
1026     writer.writeNamespace(officeNS, QString::fromLatin1("office"));
1027     writer.writeNamespace(textNS, QString::fromLatin1("text"));
1028     writer.writeNamespace(styleNS, QString::fromLatin1("style"));
1029     writer.writeNamespace(foNS, QString::fromLatin1("fo"));
1030     writer.writeNamespace(tableNS, QString::fromLatin1("table"));
1031     writer.writeNamespace(drawNS, QString::fromLatin1("draw"));
1032     writer.writeNamespace(xlinkNS, QString::fromLatin1("xlink"));
1033     writer.writeNamespace(svgNS, QString::fromLatin1("svg"));
1034     writer.writeStartDocument();
1035     writer.writeStartElement(officeNS, QString::fromLatin1("document-content"));
1036     writer.writeAttribute(officeNS, QString::fromLatin1("version"), QString::fromLatin1("1.2"));
1037 
1038     // add fragments. (for character formats)
1039     QTextDocumentPrivate::FragmentIterator fragIt = m_document->docHandle()->begin();
1040     QSet<int> formats;
1041     while (fragIt != m_document->docHandle()->end()) {
1042         const QTextFragmentData * const frag = fragIt.value();
1043         formats << frag->format;
1044         ++fragIt;
1045     }
1046 
1047     // add blocks (for blockFormats)
1048     QTextDocumentPrivate::BlockMap &blocks = m_document->docHandle()->blockMap();
1049     QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin();
1050     while (blockIt != blocks.end()) {
1051         const QTextBlockData * const block = blockIt.value();
1052         formats << block->format;
1053         ++blockIt;
1054     }
1055 
1056     // add objects for lists, frames and tables
1057     const QVector<QTextFormat> allFormats = m_document->allFormats();
1058     const QList<int> copy = formats.values();
1059     for (auto index : copy) {
1060         QTextObject *object = m_document->objectForFormat(allFormats[index]);
1061         if (object) {
1062             formats << object->formatIndex();
1063             if (auto *tableobject = qobject_cast<QTextTable *>(object)) {
1064                 if (tableobject->format().borderStyle()) {
1065                     int tableID = tableobject->formatIndex();
1066                     m_tableFormatsWithBorders.insert(tableID);
1067                     // loop through all rows and cols of table and store cell IDs,
1068                     // create Hash with cell ID as Key and table IDs as Vector
1069                     for (int rowindex = 0; rowindex < tableobject->rows(); ++rowindex) {
1070                         for (int colindex = 0; colindex < tableobject->columns(); ++colindex) {
1071                             const int cellFormatID = tableobject->cellAt(rowindex, colindex).tableCellFormatIndex();
1072                             QVector<int> tableIdsTmp;
1073                             if (m_cellFormatsInTablesWithBorders.contains(cellFormatID))
1074                                 tableIdsTmp = m_cellFormatsInTablesWithBorders.value(cellFormatID);
1075                             if (!tableIdsTmp.contains(tableID))
1076                                 tableIdsTmp.append(tableID);
1077                             m_cellFormatsInTablesWithBorders.insert(cellFormatID, tableIdsTmp);
1078                         }
1079                     }
1080                 }
1081             }
1082         }
1083     }
1084 
1085     writeFormats(writer, formats);
1086 
1087     writer.writeStartElement(officeNS, QString::fromLatin1("body"));
1088     writer.writeStartElement(officeNS, QString::fromLatin1("text"));
1089     QTextFrame *rootFrame = m_document->rootFrame();
1090     writeFrame(writer, rootFrame);
1091     writer.writeEndElement(); // text
1092     writer.writeEndElement(); // body
1093     writer.writeEndElement(); // document-content
1094     writer.writeEndDocument();
1095     delete m_strategy;
1096     m_strategy = nullptr;
1097 
1098     return true;
1099 }
1100 
1101 QT_END_NAMESPACE
1102 
1103 #endif // QT_NO_TEXTODFWRITER
1104