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