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