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