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 | |