|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtGui module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include <qglobal.h> |
|
43 |
|
44 #ifndef QT_NO_TEXTODFWRITER |
|
45 |
|
46 #include "qtextodfwriter_p.h" |
|
47 |
|
48 #include <QImageWriter> |
|
49 #include <QTextListFormat> |
|
50 #include <QTextList> |
|
51 #include <QBuffer> |
|
52 #include <QUrl> |
|
53 |
|
54 #include "qtextdocument_p.h" |
|
55 #include "qtexttable.h" |
|
56 #include "qtextcursor.h" |
|
57 #include "qtextimagehandler_p.h" |
|
58 #include "qzipwriter_p.h" |
|
59 |
|
60 #include <QDebug> |
|
61 |
|
62 QT_BEGIN_NAMESPACE |
|
63 |
|
64 /// Convert pixels to postscript point units |
|
65 static QString pixelToPoint(qreal pixels) |
|
66 { |
|
67 // we hardcode 96 DPI, we do the same in the ODF importer to have a perfect roundtrip. |
|
68 return QString::number(pixels * 72 / 96) + QString::fromLatin1("pt"); |
|
69 } |
|
70 |
|
71 // strategies |
|
72 class QOutputStrategy { |
|
73 public: |
|
74 QOutputStrategy() : contentStream(0), counter(1) { } |
|
75 virtual ~QOutputStrategy() {} |
|
76 virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) = 0; |
|
77 |
|
78 QString createUniqueImageName() |
|
79 { |
|
80 return QString::fromLatin1("Pictures/Picture%1").arg(counter++); |
|
81 } |
|
82 |
|
83 QIODevice *contentStream; |
|
84 int counter; |
|
85 }; |
|
86 |
|
87 class QXmlStreamStrategy : public QOutputStrategy { |
|
88 public: |
|
89 QXmlStreamStrategy(QIODevice *device) |
|
90 { |
|
91 contentStream = device; |
|
92 } |
|
93 |
|
94 virtual ~QXmlStreamStrategy() |
|
95 { |
|
96 if (contentStream) |
|
97 contentStream->close(); |
|
98 } |
|
99 virtual void addFile(const QString &, const QString &, const QByteArray &) |
|
100 { |
|
101 // we ignore this... |
|
102 } |
|
103 }; |
|
104 |
|
105 class QZipStreamStrategy : public QOutputStrategy { |
|
106 public: |
|
107 QZipStreamStrategy(QIODevice *device) |
|
108 : zip(device), |
|
109 manifestWriter(&manifest) |
|
110 { |
|
111 QByteArray mime("application/vnd.oasis.opendocument.text"); |
|
112 zip.setCompressionPolicy(QZipWriter::NeverCompress); |
|
113 zip.addFile(QString::fromLatin1("mimetype"), mime); // for mime-magick |
|
114 zip.setCompressionPolicy(QZipWriter::AutoCompress); |
|
115 contentStream = &content; |
|
116 content.open(QIODevice::WriteOnly); |
|
117 manifest.open(QIODevice::WriteOnly); |
|
118 |
|
119 manifestNS = QString::fromLatin1("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"); |
|
120 // prettyfy |
|
121 manifestWriter.setAutoFormatting(true); |
|
122 manifestWriter.setAutoFormattingIndent(1); |
|
123 |
|
124 manifestWriter.writeNamespace(manifestNS, QString::fromLatin1("manifest")); |
|
125 manifestWriter.writeStartDocument(); |
|
126 manifestWriter.writeStartElement(manifestNS, QString::fromLatin1("manifest")); |
|
127 addFile(QString::fromLatin1("/"), QString::fromLatin1("application/vnd.oasis.opendocument.text")); |
|
128 addFile(QString::fromLatin1("content.xml"), QString::fromLatin1("text/xml")); |
|
129 } |
|
130 |
|
131 ~QZipStreamStrategy() |
|
132 { |
|
133 manifestWriter.writeEndDocument(); |
|
134 manifest.close(); |
|
135 zip.addFile(QString::fromLatin1("META-INF/manifest.xml"), &manifest); |
|
136 content.close(); |
|
137 zip.addFile(QString::fromLatin1("content.xml"), &content); |
|
138 zip.close(); |
|
139 } |
|
140 |
|
141 virtual void addFile(const QString &fileName, const QString &mimeType, const QByteArray &bytes) |
|
142 { |
|
143 zip.addFile(fileName, bytes); |
|
144 addFile(fileName, mimeType); |
|
145 } |
|
146 |
|
147 private: |
|
148 void addFile(const QString &fileName, const QString &mimeType) |
|
149 { |
|
150 manifestWriter.writeEmptyElement(manifestNS, QString::fromLatin1("file-entry")); |
|
151 manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("media-type"), mimeType); |
|
152 manifestWriter.writeAttribute(manifestNS, QString::fromLatin1("full-path"), fileName); |
|
153 } |
|
154 |
|
155 QBuffer content; |
|
156 QBuffer manifest; |
|
157 QZipWriter zip; |
|
158 QXmlStreamWriter manifestWriter; |
|
159 QString manifestNS; |
|
160 }; |
|
161 |
|
162 static 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("1"); |
|
173 case QTextListFormat::ListLowerAlpha: |
|
174 return QString::fromLatin1("a"); |
|
175 case QTextListFormat::ListUpperAlpha: |
|
176 return QString::fromLatin1("A"); |
|
177 case QTextListFormat::ListLowerRoman: |
|
178 return QString::fromLatin1("i"); |
|
179 case QTextListFormat::ListUpperRoman: |
|
180 return QString::fromLatin1("I"); |
|
181 default: |
|
182 case QTextListFormat::ListStyleUndefined: |
|
183 return QString(); |
|
184 } |
|
185 } |
|
186 |
|
187 void QTextOdfWriter::writeFrame(QXmlStreamWriter &writer, const QTextFrame *frame) |
|
188 { |
|
189 Q_ASSERT(frame); |
|
190 const QTextTable *table = qobject_cast<const QTextTable*> (frame); |
|
191 |
|
192 if (table) { // Start a table. |
|
193 writer.writeStartElement(tableNS, QString::fromLatin1("table")); |
|
194 writer.writeEmptyElement(tableNS, QString::fromLatin1("table-column")); |
|
195 writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-repeated"), QString::number(table->columns())); |
|
196 } else if (frame->document() && frame->document()->rootFrame() != frame) { // start a section |
|
197 writer.writeStartElement(textNS, QString::fromLatin1("section")); |
|
198 } |
|
199 |
|
200 QTextFrame::iterator iterator = frame->begin(); |
|
201 QTextFrame *child = 0; |
|
202 |
|
203 int tableRow = -1; |
|
204 while (! iterator.atEnd()) { |
|
205 if (iterator.currentFrame() && child != iterator.currentFrame()) |
|
206 writeFrame(writer, iterator.currentFrame()); |
|
207 else { // no frame, its a block |
|
208 QTextBlock block = iterator.currentBlock(); |
|
209 if (table) { |
|
210 QTextTableCell cell = table->cellAt(block.position()); |
|
211 if (tableRow < cell.row()) { |
|
212 if (tableRow >= 0) |
|
213 writer.writeEndElement(); // close table row |
|
214 tableRow = cell.row(); |
|
215 writer.writeStartElement(tableNS, QString::fromLatin1("table-row")); |
|
216 } |
|
217 writer.writeStartElement(tableNS, QString::fromLatin1("table-cell")); |
|
218 if (cell.columnSpan() > 1) |
|
219 writer.writeAttribute(tableNS, QString::fromLatin1("number-columns-spanned"), QString::number(cell.columnSpan())); |
|
220 if (cell.rowSpan() > 1) |
|
221 writer.writeAttribute(tableNS, QString::fromLatin1("number-rows-spanned"), QString::number(cell.rowSpan())); |
|
222 if (cell.format().isTableCellFormat()) { |
|
223 writer.writeAttribute(tableNS, QString::fromLatin1("style-name"), QString::fromLatin1("T%1").arg(cell.tableCellFormatIndex())); |
|
224 } |
|
225 } |
|
226 writeBlock(writer, block); |
|
227 if (table) |
|
228 writer.writeEndElement(); // table-cell |
|
229 } |
|
230 child = iterator.currentFrame(); |
|
231 ++iterator; |
|
232 } |
|
233 if (tableRow >= 0) |
|
234 writer.writeEndElement(); // close table-row |
|
235 |
|
236 if (table || (frame->document() && frame->document()->rootFrame() != frame)) |
|
237 writer.writeEndElement(); // close table or section element |
|
238 } |
|
239 |
|
240 void QTextOdfWriter::writeBlock(QXmlStreamWriter &writer, const QTextBlock &block) |
|
241 { |
|
242 if (block.textList()) { // its a list-item |
|
243 const int listLevel = block.textList()->format().indent(); |
|
244 if (m_listStack.isEmpty() || m_listStack.top() != block.textList()) { |
|
245 // not the same list we were in. |
|
246 while (m_listStack.count() >= listLevel && !m_listStack.isEmpty() && m_listStack.top() != block.textList() ) { // we need to close tags |
|
247 m_listStack.pop(); |
|
248 writer.writeEndElement(); // list |
|
249 if (m_listStack.count()) |
|
250 writer.writeEndElement(); // list-item |
|
251 } |
|
252 while (m_listStack.count() < listLevel) { |
|
253 if (m_listStack.count()) |
|
254 writer.writeStartElement(textNS, QString::fromLatin1("list-item")); |
|
255 writer.writeStartElement(textNS, QString::fromLatin1("list")); |
|
256 if (m_listStack.count() == listLevel - 1) { |
|
257 m_listStack.push(block.textList()); |
|
258 writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("L%1") |
|
259 .arg(block.textList()->formatIndex())); |
|
260 } |
|
261 else { |
|
262 m_listStack.push(0); |
|
263 } |
|
264 } |
|
265 } |
|
266 writer.writeStartElement(textNS, QString::fromLatin1("list-item")); |
|
267 } |
|
268 else { |
|
269 while (! m_listStack.isEmpty()) { |
|
270 m_listStack.pop(); |
|
271 writer.writeEndElement(); // list |
|
272 if (m_listStack.count()) |
|
273 writer.writeEndElement(); // list-item |
|
274 } |
|
275 } |
|
276 |
|
277 if (block.length() == 1) { // only a linefeed |
|
278 writer.writeEmptyElement(textNS, QString::fromLatin1("p")); |
|
279 writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1") |
|
280 .arg(block.blockFormatIndex())); |
|
281 if (block.textList()) |
|
282 writer.writeEndElement(); // numbered-paragraph |
|
283 return; |
|
284 } |
|
285 writer.writeStartElement(textNS, QString::fromLatin1("p")); |
|
286 writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("p%1") |
|
287 .arg(block.blockFormatIndex())); |
|
288 for (QTextBlock::Iterator frag= block.begin(); !frag.atEnd(); frag++) { |
|
289 writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed in front of it. |
|
290 writer.writeStartElement(textNS, QString::fromLatin1("span")); |
|
291 |
|
292 QString fragmentText = frag.fragment().text(); |
|
293 if (fragmentText.length() == 1 && fragmentText[0] == 0xFFFC) { // its an inline character. |
|
294 writeInlineCharacter(writer, frag.fragment()); |
|
295 writer.writeEndElement(); // span |
|
296 continue; |
|
297 } |
|
298 |
|
299 writer.writeAttribute(textNS, QString::fromLatin1("style-name"), QString::fromLatin1("c%1") |
|
300 .arg(frag.fragment().charFormatIndex())); |
|
301 bool escapeNextSpace = true; |
|
302 int precedingSpaces = 0; |
|
303 int exportedIndex = 0; |
|
304 for (int i=0; i <= fragmentText.count(); ++i) { |
|
305 bool isSpace = false; |
|
306 QChar character = fragmentText[i]; |
|
307 isSpace = character.unicode() == ' '; |
|
308 |
|
309 // find more than one space. -> <text:s text:c="2" /> |
|
310 if (!isSpace && escapeNextSpace && precedingSpaces > 1) { |
|
311 const bool startParag = exportedIndex == 0 && i == precedingSpaces; |
|
312 if (!startParag) |
|
313 writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingSpaces + 1 - exportedIndex)); |
|
314 writer.writeEmptyElement(textNS, QString::fromLatin1("s")); |
|
315 const int count = precedingSpaces - (startParag?0:1); |
|
316 if (count > 1) |
|
317 writer.writeAttribute(textNS, QString::fromLatin1("c"), QString::number(count)); |
|
318 precedingSpaces = 0; |
|
319 exportedIndex = i; |
|
320 } |
|
321 |
|
322 if (i < fragmentText.count()) { |
|
323 if (character.unicode() == 0x2028) { // soft-return |
|
324 //if (exportedIndex < i) |
|
325 writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex)); |
|
326 writer.writeEmptyElement(textNS, QString::fromLatin1("line-break")); |
|
327 exportedIndex = i+1; |
|
328 continue; |
|
329 } else if (character.unicode() == '\t') { // Tab |
|
330 //if (exportedIndex < i) |
|
331 writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex)); |
|
332 writer.writeEmptyElement(textNS, QString::fromLatin1("tab")); |
|
333 exportedIndex = i+1; |
|
334 precedingSpaces = 0; |
|
335 } else if (isSpace) { |
|
336 ++precedingSpaces; |
|
337 escapeNextSpace = true; |
|
338 } else if (!isSpace) { |
|
339 precedingSpaces = 0; |
|
340 } |
|
341 } |
|
342 } |
|
343 |
|
344 writer.writeCharacters(fragmentText.mid(exportedIndex)); |
|
345 writer.writeEndElement(); // span |
|
346 } |
|
347 writer.writeCharacters(QString()); // Trick to make sure that the span gets no linefeed behind it. |
|
348 writer.writeEndElement(); // p |
|
349 if (block.textList()) |
|
350 writer.writeEndElement(); // list-item |
|
351 } |
|
352 |
|
353 void QTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer, const QTextFragment &fragment) const |
|
354 { |
|
355 writer.writeStartElement(drawNS, QString::fromLatin1("frame")); |
|
356 if (m_strategy == 0) { |
|
357 // don't do anything. |
|
358 } |
|
359 else if (fragment.charFormat().isImageFormat()) { |
|
360 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat(); |
|
361 writer.writeAttribute(drawNS, QString::fromLatin1("name"), imageFormat.name()); |
|
362 |
|
363 // vvv Copy pasted mostly from Qt ================= |
|
364 QImage image; |
|
365 QString name = imageFormat.name(); |
|
366 if (name.startsWith(QLatin1String(":/"))) // auto-detect resources |
|
367 name.prepend(QLatin1String("qrc")); |
|
368 QUrl url = QUrl::fromEncoded(name.toUtf8()); |
|
369 const QVariant data = m_document->resource(QTextDocument::ImageResource, url); |
|
370 if (data.type() == QVariant::Image) { |
|
371 image = qvariant_cast<QImage>(data); |
|
372 } else if (data.type() == QVariant::ByteArray) { |
|
373 image.loadFromData(data.toByteArray()); |
|
374 } |
|
375 |
|
376 if (image.isNull()) { |
|
377 QString context; |
|
378 if (QTextImageHandler::externalLoader) |
|
379 image = QTextImageHandler::externalLoader(name, context); |
|
380 |
|
381 if (image.isNull()) { // try direct loading |
|
382 name = imageFormat.name(); // remove qrc:/ prefix again |
|
383 image.load(name); |
|
384 } |
|
385 } |
|
386 |
|
387 // ^^^ Copy pasted mostly from Qt ================= |
|
388 if (! image.isNull()) { |
|
389 QBuffer imageBytes; |
|
390 QImageWriter imageWriter(&imageBytes, "png"); |
|
391 imageWriter.write(image); |
|
392 QString filename = m_strategy->createUniqueImageName(); |
|
393 m_strategy->addFile(filename, QString::fromLatin1("image/png"), imageBytes.data()); |
|
394 |
|
395 // get the width/height from the format. |
|
396 qreal width = (imageFormat.hasProperty(QTextFormat::ImageWidth)) ? imageFormat.width() : image.width(); |
|
397 writer.writeAttribute(svgNS, QString::fromLatin1("width"), pixelToPoint(width)); |
|
398 qreal height = (imageFormat.hasProperty(QTextFormat::ImageHeight)) ? imageFormat.height() : image.height(); |
|
399 writer.writeAttribute(svgNS, QString::fromLatin1("height"), pixelToPoint(height)); |
|
400 |
|
401 writer.writeStartElement(drawNS, QString::fromLatin1("image")); |
|
402 writer.writeAttribute(xlinkNS, QString::fromLatin1("href"), filename); |
|
403 writer.writeEndElement(); // image |
|
404 } |
|
405 } |
|
406 |
|
407 writer.writeEndElement(); // frame |
|
408 } |
|
409 |
|
410 void QTextOdfWriter::writeFormats(QXmlStreamWriter &writer, QSet<int> formats) const |
|
411 { |
|
412 writer.writeStartElement(officeNS, QString::fromLatin1("automatic-styles")); |
|
413 QVector<QTextFormat> allStyles = m_document->allFormats(); |
|
414 QSetIterator<int> formatId(formats); |
|
415 while(formatId.hasNext()) { |
|
416 int formatIndex = formatId.next(); |
|
417 QTextFormat textFormat = allStyles.at(formatIndex); |
|
418 switch (textFormat.type()) { |
|
419 case QTextFormat::CharFormat: |
|
420 if (textFormat.isTableCellFormat()) |
|
421 writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex); |
|
422 else |
|
423 writeCharacterFormat(writer, textFormat.toCharFormat(), formatIndex); |
|
424 break; |
|
425 case QTextFormat::BlockFormat: |
|
426 writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex); |
|
427 break; |
|
428 case QTextFormat::ListFormat: |
|
429 writeListFormat(writer, textFormat.toListFormat(), formatIndex); |
|
430 break; |
|
431 case QTextFormat::FrameFormat: |
|
432 writeFrameFormat(writer, textFormat.toFrameFormat(), formatIndex); |
|
433 break; |
|
434 case QTextFormat::TableFormat: |
|
435 ;break; |
|
436 } |
|
437 } |
|
438 |
|
439 writer.writeEndElement(); // automatic-styles |
|
440 } |
|
441 |
|
442 void QTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format, int formatIndex) const |
|
443 { |
|
444 writer.writeStartElement(styleNS, QString::fromLatin1("style")); |
|
445 writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("p%1").arg(formatIndex)); |
|
446 writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("paragraph")); |
|
447 writer.writeStartElement(styleNS, QString::fromLatin1("paragraph-properties")); |
|
448 |
|
449 if (format.hasProperty(QTextFormat::BlockAlignment)) { |
|
450 QString value; |
|
451 if (format.alignment() == Qt::AlignLeading) |
|
452 value = QString::fromLatin1("start"); |
|
453 else if (format.alignment() == Qt::AlignTrailing) |
|
454 value = QString::fromLatin1("end"); |
|
455 else if (format.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) |
|
456 value = QString::fromLatin1("left"); |
|
457 else if (format.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) |
|
458 value = QString::fromLatin1("right"); |
|
459 else if (format.alignment() == Qt::AlignHCenter) |
|
460 value = QString::fromLatin1("center"); |
|
461 else if (format.alignment() == Qt::AlignJustify) |
|
462 value = QString::fromLatin1("justify"); |
|
463 else |
|
464 qWarning() << "QTextOdfWriter: unsupported paragraph alignment; " << format.alignment(); |
|
465 if (! value.isNull()) |
|
466 writer.writeAttribute(foNS, QString::fromLatin1("text-align"), value); |
|
467 } |
|
468 |
|
469 if (format.hasProperty(QTextFormat::BlockTopMargin)) |
|
470 writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) ); |
|
471 if (format.hasProperty(QTextFormat::BlockBottomMargin)) |
|
472 writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) ); |
|
473 if (format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent)) |
|
474 writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), |
|
475 format.leftMargin() + format.indent()))); |
|
476 if (format.hasProperty(QTextFormat::BlockRightMargin)) |
|
477 writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) ); |
|
478 if (format.hasProperty(QTextFormat::TextIndent)) |
|
479 writer.writeAttribute(foNS, QString::fromLatin1("text-indent"), pixelToPoint(format.textIndent())); |
|
480 if (format.hasProperty(QTextFormat::PageBreakPolicy)) { |
|
481 if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysBefore) |
|
482 writer.writeAttribute(foNS, QString::fromLatin1("break-before"), QString::fromLatin1("page")); |
|
483 if (format.pageBreakPolicy() & QTextFormat::PageBreak_AlwaysAfter) |
|
484 writer.writeAttribute(foNS, QString::fromLatin1("break-after"), QString::fromLatin1("page")); |
|
485 } |
|
486 if (format.hasProperty(QTextFormat::BlockNonBreakableLines)) |
|
487 writer.writeAttribute(foNS, QString::fromLatin1("keep-together"), |
|
488 format.nonBreakableLines() ? QString::fromLatin1("true") : QString::fromLatin1("false")); |
|
489 if (format.hasProperty(QTextFormat::TabPositions)) { |
|
490 QList<QTextOption::Tab> tabs = format.tabPositions(); |
|
491 writer.writeStartElement(styleNS, QString::fromLatin1("style-tab-stops")); |
|
492 QList<QTextOption::Tab>::Iterator iterator = tabs.begin(); |
|
493 while(iterator != tabs.end()) { |
|
494 writer.writeEmptyElement(styleNS, QString::fromLatin1("style-tab-stop")); |
|
495 writer.writeAttribute(styleNS, QString::fromLatin1("position"), pixelToPoint(iterator->position) ); |
|
496 QString type; |
|
497 switch(iterator->type) { |
|
498 case QTextOption::DelimiterTab: type = QString::fromLatin1("char"); break; |
|
499 case QTextOption::LeftTab: type = QString::fromLatin1("left"); break; |
|
500 case QTextOption::RightTab: type = QString::fromLatin1("right"); break; |
|
501 case QTextOption::CenterTab: type = QString::fromLatin1("center"); break; |
|
502 } |
|
503 writer.writeAttribute(styleNS, QString::fromLatin1("type"), type); |
|
504 if (iterator->delimiter != 0) |
|
505 writer.writeAttribute(styleNS, QString::fromLatin1("char"), iterator->delimiter); |
|
506 ++iterator; |
|
507 } |
|
508 |
|
509 writer.writeEndElement(); // style-tab-stops |
|
510 } |
|
511 |
|
512 writer.writeEndElement(); // paragraph-properties |
|
513 writer.writeEndElement(); // style |
|
514 } |
|
515 |
|
516 void QTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format, int formatIndex) const |
|
517 { |
|
518 writer.writeStartElement(styleNS, QString::fromLatin1("style")); |
|
519 writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("c%1").arg(formatIndex)); |
|
520 writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("text")); |
|
521 writer.writeEmptyElement(styleNS, QString::fromLatin1("text-properties")); |
|
522 if (format.fontItalic()) |
|
523 writer.writeAttribute(foNS, QString::fromLatin1("font-style"), QString::fromLatin1("italic")); |
|
524 if (format.hasProperty(QTextFormat::FontWeight) && format.fontWeight() != QFont::Normal) { |
|
525 QString value; |
|
526 if (format.fontWeight() == QFont::Bold) |
|
527 value = QString::fromLatin1("bold"); |
|
528 else |
|
529 value = QString::number(format.fontWeight() * 10); |
|
530 writer.writeAttribute(foNS, QString::fromLatin1("font-weight"), value); |
|
531 } |
|
532 if (format.hasProperty(QTextFormat::FontFamily)) |
|
533 writer.writeAttribute(foNS, QString::fromLatin1("font-family"), format.fontFamily()); |
|
534 else |
|
535 writer.writeAttribute(foNS, QString::fromLatin1("font-family"), QString::fromLatin1("Sans")); // Qt default |
|
536 if (format.hasProperty(QTextFormat::FontPointSize)) |
|
537 writer.writeAttribute(foNS, QString::fromLatin1("font-size"), QString::fromLatin1("%1pt").arg(format.fontPointSize())); |
|
538 if (format.hasProperty(QTextFormat::FontCapitalization)) { |
|
539 switch(format.fontCapitalization()) { |
|
540 case QFont::MixedCase: |
|
541 writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("none")); break; |
|
542 case QFont::AllUppercase: |
|
543 writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("uppercase")); break; |
|
544 case QFont::AllLowercase: |
|
545 writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("lowercase")); break; |
|
546 case QFont::Capitalize: |
|
547 writer.writeAttribute(foNS, QString::fromLatin1("text-transform"), QString::fromLatin1("capitalize")); break; |
|
548 case QFont::SmallCaps: |
|
549 writer.writeAttribute(foNS, QString::fromLatin1("font-variant"), QString::fromLatin1("small-caps")); break; |
|
550 } |
|
551 } |
|
552 if (format.hasProperty(QTextFormat::FontLetterSpacing)) |
|
553 writer.writeAttribute(foNS, QString::fromLatin1("letter-spacing"), pixelToPoint(format.fontLetterSpacing())); |
|
554 if (format.hasProperty(QTextFormat::FontWordSpacing)) |
|
555 writer.writeAttribute(foNS, QString::fromLatin1("word-spacing"), pixelToPoint(format.fontWordSpacing())); |
|
556 if (format.hasProperty(QTextFormat::FontUnderline)) |
|
557 writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-type"), |
|
558 format.fontUnderline() ? QString::fromLatin1("single") : QString::fromLatin1("none")); |
|
559 if (format.hasProperty(QTextFormat::FontOverline)) { |
|
560 // bool fontOverline () const TODO |
|
561 } |
|
562 if (format.hasProperty(QTextFormat::FontStrikeOut)) |
|
563 writer.writeAttribute(styleNS,QString::fromLatin1( "text-line-through-type"), |
|
564 format.fontStrikeOut() ? QString::fromLatin1("single") : QString::fromLatin1("none")); |
|
565 if (format.hasProperty(QTextFormat::TextUnderlineColor)) |
|
566 writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-color"), format.underlineColor().name()); |
|
567 if (format.hasProperty(QTextFormat::FontFixedPitch)) { |
|
568 // bool fontFixedPitch () const TODO |
|
569 } |
|
570 if (format.hasProperty(QTextFormat::TextUnderlineStyle)) { |
|
571 QString value; |
|
572 switch (format.underlineStyle()) { |
|
573 case QTextCharFormat::NoUnderline: value = QString::fromLatin1("none"); break; |
|
574 case QTextCharFormat::SingleUnderline: value = QString::fromLatin1("solid"); break; |
|
575 case QTextCharFormat::DashUnderline: value = QString::fromLatin1("dash"); break; |
|
576 case QTextCharFormat::DotLine: value = QString::fromLatin1("dotted"); break; |
|
577 case QTextCharFormat::DashDotLine: value = QString::fromLatin1("dash-dot"); break; |
|
578 case QTextCharFormat::DashDotDotLine: value = QString::fromLatin1("dot-dot-dash"); break; |
|
579 case QTextCharFormat::WaveUnderline: value = QString::fromLatin1("wave"); break; |
|
580 case QTextCharFormat::SpellCheckUnderline: value = QString::fromLatin1("none"); break; |
|
581 } |
|
582 writer.writeAttribute(styleNS, QString::fromLatin1("text-underline-style"), value); |
|
583 } |
|
584 if (format.hasProperty(QTextFormat::TextVerticalAlignment)) { |
|
585 QString value; |
|
586 switch (format.verticalAlignment()) { |
|
587 case QTextCharFormat::AlignMiddle: |
|
588 case QTextCharFormat::AlignNormal: value = QString::fromLatin1("0%"); break; |
|
589 case QTextCharFormat::AlignSuperScript: value = QString::fromLatin1("super"); break; |
|
590 case QTextCharFormat::AlignSubScript: value = QString::fromLatin1("sub"); break; |
|
591 case QTextCharFormat::AlignTop: value = QString::fromLatin1("100%"); break; |
|
592 case QTextCharFormat::AlignBottom : value = QString::fromLatin1("-100%"); break; |
|
593 } |
|
594 writer.writeAttribute(styleNS, QString::fromLatin1("text-position"), value); |
|
595 } |
|
596 if (format.hasProperty(QTextFormat::TextOutline)) |
|
597 writer.writeAttribute(styleNS, QString::fromLatin1("text-outline"), QString::fromLatin1("true")); |
|
598 if (format.hasProperty(QTextFormat::TextToolTip)) { |
|
599 // QString toolTip () const TODO |
|
600 } |
|
601 if (format.hasProperty(QTextFormat::IsAnchor)) { |
|
602 // bool isAnchor () const TODO |
|
603 } |
|
604 if (format.hasProperty(QTextFormat::AnchorHref)) { |
|
605 // QString anchorHref () const TODO |
|
606 } |
|
607 if (format.hasProperty(QTextFormat::AnchorName)) { |
|
608 // QString anchorName () const TODO |
|
609 } |
|
610 if (format.hasProperty(QTextFormat::ForegroundBrush)) { |
|
611 QBrush brush = format.foreground(); |
|
612 // TODO |
|
613 writer.writeAttribute(foNS, QString::fromLatin1("color"), brush.color().name()); |
|
614 } |
|
615 |
|
616 writer.writeEndElement(); // style |
|
617 } |
|
618 |
|
619 void QTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format, int formatIndex) const |
|
620 { |
|
621 writer.writeStartElement(textNS, QString::fromLatin1("list-style")); |
|
622 writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("L%1").arg(formatIndex)); |
|
623 |
|
624 QTextListFormat::Style style = format.style(); |
|
625 if (style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha |
|
626 || style == QTextListFormat::ListUpperAlpha |
|
627 || style == QTextListFormat::ListLowerRoman |
|
628 || style == QTextListFormat::ListUpperRoman) { |
|
629 writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-number")); |
|
630 writer.writeAttribute(styleNS, QString::fromLatin1("num-format"), bulletChar(style)); |
|
631 writer.writeAttribute(styleNS, QString::fromLatin1("num-suffix"), QString::fromLatin1(".")); |
|
632 } else { |
|
633 writer.writeStartElement(textNS, QString::fromLatin1("list-level-style-bullet")); |
|
634 writer.writeAttribute(textNS, QString::fromLatin1("bullet-char"), bulletChar(style)); |
|
635 } |
|
636 |
|
637 writer.writeAttribute(textNS, QString::fromLatin1("level"), QString::number(format.indent())); |
|
638 writer.writeEmptyElement(styleNS, QString::fromLatin1("list-level-properties")); |
|
639 writer.writeAttribute(foNS, QString::fromLatin1("text-align"), QString::fromLatin1("start")); |
|
640 QString spacing = QString::fromLatin1("%1mm").arg(format.indent() * 8); |
|
641 writer.writeAttribute(textNS, QString::fromLatin1("space-before"), spacing); |
|
642 //writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing); |
|
643 |
|
644 writer.writeEndElement(); // list-level-style-* |
|
645 writer.writeEndElement(); // list-style |
|
646 } |
|
647 |
|
648 void QTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format, int formatIndex) const |
|
649 { |
|
650 writer.writeStartElement(styleNS, QString::fromLatin1("style")); |
|
651 writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("s%1").arg(formatIndex)); |
|
652 writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("section")); |
|
653 writer.writeEmptyElement(styleNS, QString::fromLatin1("section-properties")); |
|
654 if (format.hasProperty(QTextFormat::BlockTopMargin)) |
|
655 writer.writeAttribute(foNS, QString::fromLatin1("margin-top"), pixelToPoint(qMax(qreal(0.), format.topMargin())) ); |
|
656 if (format.hasProperty(QTextFormat::BlockBottomMargin)) |
|
657 writer.writeAttribute(foNS, QString::fromLatin1("margin-bottom"), pixelToPoint(qMax(qreal(0.), format.bottomMargin())) ); |
|
658 if (format.hasProperty(QTextFormat::BlockLeftMargin)) |
|
659 writer.writeAttribute(foNS, QString::fromLatin1("margin-left"), pixelToPoint(qMax(qreal(0.), format.leftMargin())) ); |
|
660 if (format.hasProperty(QTextFormat::BlockRightMargin)) |
|
661 writer.writeAttribute(foNS, QString::fromLatin1("margin-right"), pixelToPoint(qMax(qreal(0.), format.rightMargin())) ); |
|
662 |
|
663 writer.writeEndElement(); // style |
|
664 |
|
665 // TODO consider putting the following properties in a qt-namespace. |
|
666 // Position position () const |
|
667 // qreal border () const |
|
668 // QBrush borderBrush () const |
|
669 // BorderStyle borderStyle () const |
|
670 // qreal padding () const |
|
671 // QTextLength width () const |
|
672 // QTextLength height () const |
|
673 // PageBreakFlags pageBreakPolicy () const |
|
674 } |
|
675 |
|
676 void QTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format, int formatIndex) const |
|
677 { |
|
678 writer.writeStartElement(styleNS, QString::fromLatin1("style")); |
|
679 writer.writeAttribute(styleNS, QString::fromLatin1("name"), QString::fromLatin1("T%1").arg(formatIndex)); |
|
680 writer.writeAttribute(styleNS, QString::fromLatin1("family"), QString::fromLatin1("table")); |
|
681 writer.writeEmptyElement(styleNS, QString::fromLatin1("table-properties")); |
|
682 |
|
683 |
|
684 qreal padding = format.topPadding(); |
|
685 if (padding > 0 && padding == format.bottomPadding() |
|
686 && padding == format.leftPadding() && padding == format.rightPadding()) { |
|
687 writer.writeAttribute(foNS, QString::fromLatin1("padding"), pixelToPoint(padding)); |
|
688 } |
|
689 else { |
|
690 if (padding > 0) |
|
691 writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(padding)); |
|
692 if (format.bottomPadding() > 0) |
|
693 writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.bottomPadding())); |
|
694 if (format.leftPadding() > 0) |
|
695 writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.leftPadding())); |
|
696 if (format.rightPadding() > 0) |
|
697 writer.writeAttribute(foNS, QString::fromLatin1("padding-top"), pixelToPoint(format.rightPadding())); |
|
698 } |
|
699 |
|
700 if (format.hasProperty(QTextFormat::TextVerticalAlignment)) { |
|
701 QString pos; |
|
702 switch (format.verticalAlignment()) { |
|
703 case QTextCharFormat::AlignMiddle: |
|
704 pos = QString::fromLatin1("middle"); break; |
|
705 case QTextCharFormat::AlignTop: |
|
706 pos = QString::fromLatin1("top"); break; |
|
707 case QTextCharFormat::AlignBottom: |
|
708 pos = QString::fromLatin1("bottom"); break; |
|
709 default: |
|
710 pos = QString::fromLatin1("automatic"); break; |
|
711 } |
|
712 writer.writeAttribute(styleNS, QString::fromLatin1("vertical-align"), pos); |
|
713 } |
|
714 |
|
715 // TODO |
|
716 // ODF just search for style-table-cell-properties-attlist) |
|
717 // QTextFormat::BackgroundImageUrl |
|
718 // format.background |
|
719 // QTextFormat::FrameBorder |
|
720 |
|
721 writer.writeEndElement(); // style |
|
722 } |
|
723 |
|
724 /////////////////////// |
|
725 |
|
726 QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device) |
|
727 : officeNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:office:1.0")), |
|
728 textNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:text:1.0")), |
|
729 styleNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:style:1.0")), |
|
730 foNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")), |
|
731 tableNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:table:1.0")), |
|
732 drawNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")), |
|
733 xlinkNS (QLatin1String("http://www.w3.org/1999/xlink")), |
|
734 svgNS (QLatin1String("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0")), |
|
735 m_document(&document), |
|
736 m_device(device), |
|
737 m_strategy(0), |
|
738 m_codec(0), |
|
739 m_createArchive(true) |
|
740 { |
|
741 } |
|
742 |
|
743 bool QTextOdfWriter::writeAll() |
|
744 { |
|
745 if (m_createArchive) |
|
746 m_strategy = new QZipStreamStrategy(m_device); |
|
747 else |
|
748 m_strategy = new QXmlStreamStrategy(m_device); |
|
749 |
|
750 if (!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) { |
|
751 qWarning() << "QTextOdfWriter::writeAll: the device can not be opened for writing"; |
|
752 return false; |
|
753 } |
|
754 QXmlStreamWriter writer(m_strategy->contentStream); |
|
755 #ifndef QT_NO_TEXTCODEC |
|
756 if (m_codec) |
|
757 writer.setCodec(m_codec); |
|
758 #endif |
|
759 // prettyfy |
|
760 writer.setAutoFormatting(true); |
|
761 writer.setAutoFormattingIndent(2); |
|
762 |
|
763 writer.writeNamespace(officeNS, QString::fromLatin1("office")); |
|
764 writer.writeNamespace(textNS, QString::fromLatin1("text")); |
|
765 writer.writeNamespace(styleNS, QString::fromLatin1("style")); |
|
766 writer.writeNamespace(foNS, QString::fromLatin1("fo")); |
|
767 writer.writeNamespace(tableNS, QString::fromLatin1("table")); |
|
768 writer.writeNamespace(drawNS, QString::fromLatin1("draw")); |
|
769 writer.writeNamespace(xlinkNS, QString::fromLatin1("xlink")); |
|
770 writer.writeNamespace(svgNS, QString::fromLatin1("svg")); |
|
771 writer.writeStartDocument(); |
|
772 writer.writeStartElement(officeNS, QString::fromLatin1("document-content")); |
|
773 |
|
774 // add fragments. (for character formats) |
|
775 QTextDocumentPrivate::FragmentIterator fragIt = m_document->docHandle()->begin(); |
|
776 QSet<int> formats; |
|
777 while (fragIt != m_document->docHandle()->end()) { |
|
778 const QTextFragmentData * const frag = fragIt.value(); |
|
779 formats << frag->format; |
|
780 ++fragIt; |
|
781 } |
|
782 |
|
783 // add blocks (for blockFormats) |
|
784 QTextDocumentPrivate::BlockMap &blocks = m_document->docHandle()->blockMap(); |
|
785 QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin(); |
|
786 while (blockIt != blocks.end()) { |
|
787 const QTextBlockData * const block = blockIt.value(); |
|
788 formats << block->format; |
|
789 ++blockIt; |
|
790 } |
|
791 |
|
792 // add objects for lists, frames and tables |
|
793 QVector<QTextFormat> allFormats = m_document->allFormats(); |
|
794 QList<int> copy = formats.toList(); |
|
795 for (QList<int>::Iterator iter = copy.begin(); iter != copy.end(); ++iter) { |
|
796 QTextObject *object = m_document->objectForFormat(allFormats[*iter]); |
|
797 if (object) |
|
798 formats << object->formatIndex(); |
|
799 } |
|
800 |
|
801 writeFormats(writer, formats); |
|
802 |
|
803 writer.writeStartElement(officeNS, QString::fromLatin1("body")); |
|
804 writer.writeStartElement(officeNS, QString::fromLatin1("text")); |
|
805 QTextFrame *rootFrame = m_document->rootFrame(); |
|
806 writeFrame(writer, rootFrame); |
|
807 writer.writeEndElement(); // text |
|
808 writer.writeEndElement(); // body |
|
809 writer.writeEndElement(); // document-content |
|
810 writer.writeEndDocument(); |
|
811 delete m_strategy; |
|
812 m_strategy = 0; |
|
813 |
|
814 return true; |
|
815 } |
|
816 |
|
817 QT_END_NAMESPACE |
|
818 |
|
819 #endif // QT_NO_TEXTODFWRITER |