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

source code of qtbase/src/gui/text/qtextodfwriter.cpp