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