src/gui/text/qtextodfwriter.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     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