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 |
Definitions
- pixelToPoint
- QOutputStrategy
- QOutputStrategy
- ~QOutputStrategy
- createUniqueImageName
- QXmlStreamStrategy
- QXmlStreamStrategy
- ~QXmlStreamStrategy
- addFile
- QZipStreamStrategy
- QZipStreamStrategy
- ~QZipStreamStrategy
- addFile
- addFile
- bulletChar
- borderStyleName
- writeFrame
- writeBlock
- probeImageData
- writeInlineCharacter
- writeFormats
- writeBlockFormat
- writeCharacterFormat
- writeListFormat
- writeFrameFormat
- writeTableFormat
- writeTableCellFormat
- tableCellStyleElement
- QTextOdfWriter
Learn to use CMake with our Intro Training
Find out more