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