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