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