summaryrefslogtreecommitdiffstats
path: root/src/gui/text/qtextodfwriter.cpp
blob: e980e1bb4eefd96c4eb8e21ccdde1d157c7d4154 (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
// Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include <qglobal.h>#ifndef QT_NO_TEXTODFWRITER#include"qtextodfwriter_p.h"#include <QImageReader>#include <QImageWriter>#include <QTextListFormat>#include <QTextList>#include <QBuffer>#include <QUrl>#include"qtextdocument_p.h"#include"qtexttable.h"#include"qtextcursor.h"#include"qtextimagehandler_p.h"#include <QDebug>#include <QtCore/private/qzipwriter_p.h> QT_BEGIN_NAMESPACE using namespaceQt::StringLiterals;/// Convert pixels to postscript point unitsstatic QString pixelToPoint(qreal pixels){// we hardcode 96 DPI, we do the same in the ODF importer to have a perfect roundtrip.returnQString::number(pixels *72/96) +"pt"_L1;}// strategiesclass QOutputStrategy {public:QOutputStrategy() :contentStream(nullptr),counter(1) { }virtual~QOutputStrategy() {}virtualvoidaddFile(const QString &fileName,const QString &mimeType,const QByteArray &bytes) =0; QString createUniqueImageName(){returnQString::fromLatin1("Pictures/Picture%1").arg(counter++);} QIODevice *contentStream;int counter;};class QXmlStreamStrategy :public QOutputStrategy {public:QXmlStreamStrategy(QIODevice *device){ contentStream = device;}~QXmlStreamStrategy(){if(contentStream) contentStream->close();}virtualvoidaddFile(const QString &,const QString &,const QByteArray &) override {// we ignore this...}};class QZipStreamStrategy :public QOutputStrategy {public:QZipStreamStrategy(QIODevice *device):zip(device),manifestWriter(&manifest){ QByteArray mime("application/vnd.oasis.opendocument.text"); zip.setCompressionPolicy(QZipWriter::NeverCompress); zip.addFile(QString::fromLatin1("mimetype"), mime);// for mime-magick zip.setCompressionPolicy(QZipWriter::AutoCompress); contentStream = &content; content.open(QIODevice::WriteOnly); manifest.open(QIODevice::WriteOnly); manifestNS =QString::fromLatin1("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");// prettyfy manifestWriter.setAutoFormatting(true); manifestWriter.setAutoFormattingIndent(1); manifestWriter.writeNamespace(manifestNS,QString::fromLatin1("manifest")); manifestWriter.writeStartDocument(); manifestWriter.writeStartElement(manifestNS,QString::fromLatin1("manifest")); manifestWriter.writeAttribute(manifestNS,QString::fromLatin1("version"),QString::fromLatin1("1.2"));addFile(QString::fromLatin1("/"),QString::fromLatin1("application/vnd.oasis.opendocument.text"));addFile(QString::fromLatin1("content.xml"),QString::fromLatin1("text/xml"));}~QZipStreamStrategy(){ manifestWriter.writeEndDocument(); manifest.close(); zip.addFile(QString::fromLatin1("META-INF/manifest.xml"), &manifest); content.close(); zip.addFile(QString::fromLatin1("content.xml"), &content); zip.close();}virtualvoidaddFile(const QString &fileName,const QString &mimeType,const QByteArray &bytes) override { zip.addFile(fileName, bytes);addFile(fileName, mimeType);}private:voidaddFile(const QString &fileName,const QString &mimeType){ manifestWriter.writeEmptyElement(manifestNS,QString::fromLatin1("file-entry")); manifestWriter.writeAttribute(manifestNS,QString::fromLatin1("media-type"), mimeType); manifestWriter.writeAttribute(manifestNS,QString::fromLatin1("full-path"), fileName);} QBuffer content; QBuffer manifest; QZipWriter zip; QXmlStreamWriter manifestWriter; QString manifestNS;};static QStringView bullet_char(QTextListFormat::Style style){static_assert(int(QTextListFormat::ListDisc) == -1);static_assert(int(QTextListFormat::ListUpperRoman) == -8);static const char16_t chars[] = { u'\x25cf',// bullet character u'\x25cb',// white circle u'\x25a1',// white square u'1', u'a', u'A', u'i', u'I',};constauto map = [](QTextListFormat::Style s) {return-int(s) -1; };static_assert(uint(map(QTextListFormat::ListUpperRoman)) ==std::size(chars) -1);constauto idx =map(style);if(idx <0)returnnullptr;elsereturn{chars + idx,1};}static QString bulletChar(QTextListFormat::Style style){returnbullet_char(style).toString();}static QString borderStyleName(QTextFrameFormat::BorderStyle style){switch(style) {caseQTextFrameFormat::BorderStyle_None:returnQString::fromLatin1("none");caseQTextFrameFormat::BorderStyle_Dotted:returnQString::fromLatin1("dotted");caseQTextFrameFormat::BorderStyle_Dashed:returnQString::fromLatin1("dashed");caseQTextFrameFormat::BorderStyle_Solid:returnQString::fromLatin1("solid");caseQTextFrameFormat::BorderStyle_Double:returnQString::fromLatin1("double");caseQTextFrameFormat::BorderStyle_DotDash:returnQString::fromLatin1("dashed");caseQTextFrameFormat::BorderStyle_DotDotDash:returnQString::fromLatin1("dotted");caseQTextFrameFormat::BorderStyle_Groove:returnQString::fromLatin1("groove");caseQTextFrameFormat::BorderStyle_Ridge:returnQString::fromLatin1("ridge");caseQTextFrameFormat::BorderStyle_Inset:returnQString::fromLatin1("inset");caseQTextFrameFormat::BorderStyle_Outset:returnQString::fromLatin1("outset");}returnQString::fromLatin1("");}voidQTextOdfWriter::writeFrame(QXmlStreamWriter &writer,const QTextFrame *frame){Q_ASSERT(frame);const QTextTable *table = qobject_cast<const QTextTable*> (frame);if(table) {// Start a table. writer.writeStartElement(tableNS,QString::fromLatin1("table")); writer.writeAttribute(tableNS,QString::fromLatin1("style-name"),QString::fromLatin1("Table%1").arg(table->formatIndex()));// check if column widths are set, if so add TableNS line above for all columns and link to styleif(m_tableFormatsWithColWidthConstraints.contains(table->formatIndex())) {for(int colit =0; colit < table->columns(); ++colit) { writer.writeStartElement(tableNS,QString::fromLatin1("table-column")); writer.writeAttribute(tableNS,QString::fromLatin1("style-name"),QString::fromLatin1("Table%1.%2").arg(table->formatIndex()).arg(colit)); writer.writeEndElement();}}else{ writer.writeEmptyElement(tableNS,QString::fromLatin1("table-column")); writer.writeAttribute(tableNS,QString::fromLatin1("number-columns-repeated"),QString::number(table->columns()));}}else if(frame->document() && frame->document()->rootFrame() != frame) {// start a section writer.writeStartElement(textNS,QString::fromLatin1("section"));}QTextFrame::iterator iterator = frame->begin(); QTextFrame *child =nullptr;int tableRow = -1;while(! iterator.atEnd()) {if(iterator.currentFrame() && child != iterator.currentFrame())writeFrame(writer, iterator.currentFrame());else{// no frame, its a block QTextBlock block = iterator.currentBlock();if(table) { QTextTableCell cell = table->cellAt(block.position());if(tableRow < cell.row()) {if(tableRow >=0) writer.writeEndElement();// close table row tableRow = cell.row(); writer.writeStartElement(tableNS,QString::fromLatin1("table-row"));} writer.writeStartElement(tableNS,QString::fromLatin1("table-cell"));if(cell.columnSpan() >1) writer.writeAttribute(tableNS,QString::fromLatin1("number-columns-spanned"),QString::number(cell.columnSpan()));if(cell.rowSpan() >1) writer.writeAttribute(tableNS,QString::fromLatin1("number-rows-spanned"),QString::number(cell.rowSpan()));if(cell.format().isTableCellFormat()) {if(m_cellFormatsInTablesWithBorders.contains(cell.tableCellFormatIndex()) ) {// writing table:style-name tag in <table:table-cell> element writer.writeAttribute(tableNS,QString::fromLatin1("style-name"),QString::fromLatin1("TB%1.%2").arg(table->formatIndex()).arg(cell.tableCellFormatIndex()));}else{ writer.writeAttribute(tableNS,QString::fromLatin1("style-name"),QString::fromLatin1("T%1").arg(cell.tableCellFormatIndex()));}}}writeBlock(writer, block);if(table) writer.writeEndElement();// table-cell} child = iterator.currentFrame();++iterator;}if(tableRow >=0) writer.writeEndElement();// close table-rowif(table || (frame->document() && frame->document()->rootFrame() != frame)) writer.writeEndElement();// close table or section element}voidQTextOdfWriter::writeBlock(QXmlStreamWriter &writer,const QTextBlock &block){if(block.textList()) {// its a list-itemconst int listLevel = block.textList()->format().indent();if(m_listStack.isEmpty() || m_listStack.top() != block.textList()) {// not the same list we were in.while(m_listStack.size() >= listLevel && !m_listStack.isEmpty() && m_listStack.top() != block.textList() ) {// we need to close tags m_listStack.pop(); writer.writeEndElement();// listif(m_listStack.size()) writer.writeEndElement();// list-item}while(m_listStack.size() < listLevel) {if(m_listStack.size()) writer.writeStartElement(textNS,QString::fromLatin1("list-item")); writer.writeStartElement(textNS,QString::fromLatin1("list"));if(m_listStack.size() == listLevel -1) { m_listStack.push(block.textList()); writer.writeAttribute(textNS,QString::fromLatin1("style-name"),QString::fromLatin1("L%1").arg(block.textList()->formatIndex()));}else{ m_listStack.push(nullptr);}}} writer.writeStartElement(textNS,QString::fromLatin1("list-item"));}else{while(! m_listStack.isEmpty()) { m_listStack.pop(); writer.writeEndElement();// listif(m_listStack.size()) writer.writeEndElement();// list-item}}if(block.length() ==1) {// only a linefeed writer.writeEmptyElement(textNS,QString::fromLatin1("p")); writer.writeAttribute(textNS,QString::fromLatin1("style-name"),QString::fromLatin1("p%1").arg(block.blockFormatIndex()));if(block.textList()) writer.writeEndElement();// numbered-paragraphreturn;} writer.writeStartElement(textNS,QString::fromLatin1("p")); writer.writeAttribute(textNS,QString::fromLatin1("style-name"),QString::fromLatin1("p%1").arg(block.blockFormatIndex()));for(QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {bool isHyperlink = frag.fragment().charFormat().hasProperty(QTextFormat::AnchorHref);if(isHyperlink) { QString value = frag.fragment().charFormat().property(QTextFormat::AnchorHref).toString(); writer.writeStartElement(textNS,QString::fromLatin1("a")); writer.writeAttribute(xlinkNS,QString::fromLatin1("href"), value);} writer.writeCharacters(QString());// Trick to make sure that the span gets no linefeed in front of it. writer.writeStartElement(textNS,QString::fromLatin1("span")); QString fragmentText = frag.fragment().text();if(fragmentText.size() ==1&& fragmentText[0] == u'\xFFFC') {// its an inline character.writeInlineCharacter(writer, frag.fragment()); writer.writeEndElement();// spancontinue;} writer.writeAttribute(textNS,QString::fromLatin1("style-name"),QString::fromLatin1("c%1").arg(frag.fragment().charFormatIndex()));bool escapeNextSpace =true;int precedingSpaces =0;int exportedIndex =0;for(int i=0; i <= fragmentText.size(); ++i) { QChar character = (i == fragmentText.size() ?QChar() : fragmentText.at(i));bool isSpace = character.unicode() ==' ';// find more than one space. -> <text:s text:c="2" />if(!isSpace && escapeNextSpace && precedingSpaces >1) {const bool startParag = exportedIndex ==0&& i == precedingSpaces;if(!startParag) writer.writeCharacters(fragmentText.mid(exportedIndex, i - precedingSpaces +1- exportedIndex)); writer.writeEmptyElement(textNS,QString::fromLatin1("s"));const int count = precedingSpaces - (startParag?0:1);if(count >1) writer.writeAttribute(textNS,QString::fromLatin1("c"),QString::number(count)); precedingSpaces =0; exportedIndex = i;}if(i < fragmentText.size()) {if(character.unicode() ==0x2028) {// soft-return//if (exportedIndex < i) writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex));// adding tab before line-break, so last line in justified paragraph// will not stretch to the end writer.writeEmptyElement(textNS,QString::fromLatin1("tab")); writer.writeEmptyElement(textNS,QString::fromLatin1("line-break")); exportedIndex = i+1;continue;}else if(character.unicode() =='\t') {// Tab//if (exportedIndex < i) writer.writeCharacters(fragmentText.mid(exportedIndex, i - exportedIndex)); writer.writeEmptyElement(textNS,QString::fromLatin1("tab")); exportedIndex = i+1; precedingSpaces =0;}else if(isSpace) {++precedingSpaces; escapeNextSpace =true;}else if(!isSpace) { precedingSpaces =0;}}} writer.writeCharacters(fragmentText.mid(exportedIndex)); writer.writeEndElement();// span writer.writeCharacters(QString());// Trick to make sure that the span gets no linefeed behind it.if(isHyperlink) writer.writeEndElement();// a} writer.writeCharacters(QString());// Trick to make sure that the span gets no linefeed behind it. writer.writeEndElement();// pif(block.textList()) writer.writeEndElement();// list-item}static boolprobeImageData(QIODevice *device, QImage *image, QString *mimeType, qreal *width, qreal *height){ QImageReader reader(device);const QByteArray format = reader.format().toLower();if(format =="png") {*mimeType =QStringLiteral("image/png");}else if(format =="jpg") {*mimeType =QStringLiteral("image/jpg");}else if(format =="svg") {*mimeType =QStringLiteral("image/svg+xml");}else{*image = reader.read();return false;}const QSize size = reader.size();*width = size.width();*height = size.height();return true;}voidQTextOdfWriter::writeInlineCharacter(QXmlStreamWriter &writer,const QTextFragment &fragment)const{ writer.writeStartElement(drawNS,QString::fromLatin1("frame"));if(m_strategy ==nullptr) {// don't do anything.}else if(fragment.charFormat().isImageFormat()) { QTextImageFormat imageFormat = fragment.charFormat().toImageFormat(); writer.writeAttribute(drawNS,QString::fromLatin1("name"), imageFormat.name()); QByteArray data; QString mimeType; qreal width =0; qreal height =0; QImage image; QString name = imageFormat.name();if(name.startsWith(":/"_L1))// auto-detect resources name.prepend("qrc"_L1); QUrl url =QUrl(name);const QVariant variant = m_document->resource(QTextDocument::ImageResource, url);if(variant.userType() ==QMetaType::QPixmap || variant.userType() ==QMetaType::QImage) { image = qvariant_cast<QImage>(variant);}else if(variant.userType() ==QMetaType::QByteArray) { data = variant.toByteArray(); QBuffer buffer(&data); buffer.open(QIODevice::ReadOnly);probeImageData(&buffer, &image, &mimeType, &width, &height);}else{// try direct loading QFile file(imageFormat.name());if(file.open(QIODevice::ReadOnly) && !probeImageData(&file, &image, &mimeType, &width, &height)) { file.seek(0); data = file.readAll();}}if(! image.isNull()) { QBuffer imageBytes;int imgQuality = imageFormat.quality();if(imgQuality >=100|| imgQuality <=0|| image.hasAlphaChannel()) { QImageWriter imageWriter(&imageBytes,"png"); imageWriter.write(image); data = imageBytes.data(); mimeType =QStringLiteral("image/png");}else{// Write images without alpha channel as jpg with quality set by QTextImageFormat QImageWriter imageWriter(&imageBytes,"jpg"); imageWriter.setQuality(imgQuality); imageWriter.write(image); data = imageBytes.data(); mimeType =QStringLiteral("image/jpg");} width = image.width(); height = image.height();}if(!data.isEmpty()) {if(imageFormat.hasProperty(QTextFormat::ImageWidth)) { width = imageFormat.width();}if(imageFormat.hasProperty(QTextFormat::ImageHeight)) { height = imageFormat.height();} QString filename = m_strategy->createUniqueImageName(); m_strategy->addFile(filename, mimeType, data); writer.writeAttribute(svgNS,QString::fromLatin1("width"),pixelToPoint(width)); writer.writeAttribute(svgNS,QString::fromLatin1("height"),pixelToPoint(height)); writer.writeAttribute(textNS,QStringLiteral("anchor-type"),QStringLiteral("as-char")); writer.writeStartElement(drawNS,QString::fromLatin1("image")); writer.writeAttribute(xlinkNS,QString::fromLatin1("href"), filename); writer.writeEndElement();// image}} writer.writeEndElement();// frame}voidQTextOdfWriter::writeFormats(QXmlStreamWriter &writer,const QSet<int> &formats)const{ writer.writeStartElement(officeNS,QString::fromLatin1("automatic-styles")); QList<QTextFormat> allStyles = m_document->allFormats();for(int formatIndex : formats) { QTextFormat textFormat = allStyles.at(formatIndex);switch(textFormat.type()) {caseQTextFormat::CharFormat:if(textFormat.isTableCellFormat())writeTableCellFormat(writer, textFormat.toTableCellFormat(), formatIndex, allStyles);elsewriteCharacterFormat(writer, textFormat.toCharFormat(), formatIndex);break;caseQTextFormat::BlockFormat:writeBlockFormat(writer, textFormat.toBlockFormat(), formatIndex);break;caseQTextFormat::ListFormat:writeListFormat(writer, textFormat.toListFormat(), formatIndex);break;caseQTextFormat::FrameFormat:if(textFormat.isTableFormat())writeTableFormat(writer, textFormat.toTableFormat(), formatIndex);elsewriteFrameFormat(writer, textFormat.toFrameFormat(), formatIndex);break;}} writer.writeEndElement();// automatic-styles}voidQTextOdfWriter::writeBlockFormat(QXmlStreamWriter &writer, QTextBlockFormat format,int formatIndex)const{ writer.writeStartElement(styleNS,QString::fromLatin1("style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("p%1").arg(formatIndex)); writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("paragraph")); writer.writeStartElement(styleNS,QString::fromLatin1("paragraph-properties"));if(format.hasProperty(QTextBlockFormat::LineHeightType)) {const int blockLineHeightType = format.lineHeightType();const qreal blockLineHeight = format.lineHeight(); QString type, value;switch(blockLineHeightType) {caseQTextBlockFormat::SingleHeight: type =QString::fromLatin1("line-height"); value =QString::fromLatin1("100%");break;caseQTextBlockFormat::ProportionalHeight: type =QString::fromLatin1("line-height"); value =QString::number(blockLineHeight) +QString::fromLatin1("%");break;caseQTextBlockFormat::FixedHeight: type =QString::fromLatin1("line-height"); value =pixelToPoint(qMax(qreal(0.), blockLineHeight));break;caseQTextBlockFormat::MinimumHeight: type =QString::fromLatin1("line-height-at-least"); value =pixelToPoint(qMax(qreal(0.), blockLineHeight));break;caseQTextBlockFormat::LineDistanceHeight: type =QString::fromLatin1("line-spacing"); value =pixelToPoint(qMax(qreal(0.), blockLineHeight));}if(!type.isNull()) writer.writeAttribute(styleNS, type, value);}if(format.hasProperty(QTextFormat::BlockAlignment)) {constQt::Alignment alignment = format.alignment() &Qt::AlignHorizontal_Mask; QString value;if(alignment ==Qt::AlignLeading) value =QString::fromLatin1("start");else if(alignment ==Qt::AlignTrailing) value =QString::fromLatin1("end");else if(alignment == (Qt::AlignLeft |Qt::AlignAbsolute)) value =QString::fromLatin1("left");else if(alignment == (Qt::AlignRight |Qt::AlignAbsolute)) value =QString::fromLatin1("right");else if(alignment ==Qt::AlignHCenter) value =QString::fromLatin1("center");else if(alignment ==Qt::AlignJustify) value =QString::fromLatin1("justify");elseqWarning() <<"QTextOdfWriter: unsupported paragraph alignment; "<< format.alignment();if(! value.isNull()) writer.writeAttribute(foNS,QString::fromLatin1("text-align"), value);}if(format.hasProperty(QTextFormat::BlockTopMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-top"),pixelToPoint(qMax(qreal(0.), format.topMargin())) );if(format.hasProperty(QTextFormat::BlockBottomMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-bottom"),pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );if(format.hasProperty(QTextFormat::BlockLeftMargin) || format.hasProperty(QTextFormat::BlockIndent)) writer.writeAttribute(foNS,QString::fromLatin1("margin-left"),pixelToPoint(qMax(qreal(0.), format.leftMargin() + format.indent())));if(format.hasProperty(QTextFormat::BlockRightMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-right"),pixelToPoint(qMax(qreal(0.), format.rightMargin())) );if(format.hasProperty(QTextFormat::TextIndent)) writer.writeAttribute(foNS,QString::fromLatin1("text-indent"),pixelToPoint(format.textIndent()));if(format.hasProperty(QTextFormat::PageBreakPolicy)) {if(format.pageBreakPolicy() &QTextFormat::PageBreak_AlwaysBefore) writer.writeAttribute(foNS,QString::fromLatin1("break-before"),QString::fromLatin1("page"));if(format.pageBreakPolicy() &QTextFormat::PageBreak_AlwaysAfter) writer.writeAttribute(foNS,QString::fromLatin1("break-after"),QString::fromLatin1("page"));}if(format.hasProperty(QTextFormat::BackgroundBrush)) { QBrush brush = format.background(); writer.writeAttribute(foNS,QString::fromLatin1("background-color"), brush.color().name());}if(format.hasProperty(QTextFormat::BlockNonBreakableLines)) writer.writeAttribute(foNS,QString::fromLatin1("keep-together"), format.nonBreakableLines() ?QString::fromLatin1("true") :QString::fromLatin1("false"));if(format.hasProperty(QTextFormat::TabPositions)) { QList<QTextOption::Tab> tabs = format.tabPositions(); writer.writeStartElement(styleNS,QString::fromLatin1("tab-stops")); QList<QTextOption::Tab>::Iterator iterator = tabs.begin();while(iterator != tabs.end()) { writer.writeEmptyElement(styleNS,QString::fromLatin1("tab-stop")); writer.writeAttribute(styleNS,QString::fromLatin1("position"),pixelToPoint(iterator->position) ); QString type;switch(iterator->type) {caseQTextOption::DelimiterTab: type =QString::fromLatin1("char");break;caseQTextOption::LeftTab: type =QString::fromLatin1("left");break;caseQTextOption::RightTab: type =QString::fromLatin1("right");break;caseQTextOption::CenterTab: type =QString::fromLatin1("center");break;} writer.writeAttribute(styleNS,QString::fromLatin1("type"), type);if(!iterator->delimiter.isNull()) writer.writeAttribute(styleNS,QString::fromLatin1("char"), iterator->delimiter);++iterator;} writer.writeEndElement();// tab-stops} writer.writeEndElement();// paragraph-properties writer.writeEndElement();// style}voidQTextOdfWriter::writeCharacterFormat(QXmlStreamWriter &writer, QTextCharFormat format,int formatIndex)const{ writer.writeStartElement(styleNS,QString::fromLatin1("style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("c%1").arg(formatIndex)); writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("text")); writer.writeEmptyElement(styleNS,QString::fromLatin1("text-properties"));const QFont defaultFont = m_document->defaultFont();const uint defaultFontResolveMask = defaultFont.resolveMask();if(format.hasProperty(QTextFormat::FontItalic)|| (defaultFontResolveMask &QFont::StyleResolved)) {const bool italic = format.hasProperty(QTextFormat::FontItalic) ? format.fontItalic() : defaultFont.italic();if(italic) writer.writeAttribute(foNS,QString::fromLatin1("font-style"),QString::fromLatin1("italic"));}if(format.hasProperty(QTextFormat::FontWeight)|| (defaultFontResolveMask &QFont::WeightResolved)) {int weight = format.hasProperty(QTextFormat::FontWeight)? format.fontWeight(): defaultFont.weight();if(weight !=QFont::Normal) { QString value;if(weight ==QFont::Bold) value =QString::fromLatin1("bold");else value =QString::number(weight); writer.writeAttribute(foNS,QString::fromLatin1("font-weight"), value);}}if(format.hasProperty(QTextFormat::OldFontFamily)|| format.hasProperty(QTextFormat::FontFamilies)|| (defaultFontResolveMask &QFont::FamiliesResolved)) {const QString fontFamily = (format.hasProperty(QTextFormat::OldFontFamily)|| format.hasProperty(QTextFormat::FontFamilies))? format.fontFamilies().toStringList().value(0,QString()): defaultFont.family(); writer.writeAttribute(foNS,QString::fromLatin1("font-family"), fontFamily);}else{ writer.writeAttribute(foNS,QString::fromLatin1("font-family"),QString::fromLatin1("Sans"));// Qt default}if(format.hasProperty(QTextFormat::FontPointSize)|| (defaultFontResolveMask &QFont::SizeResolved)) {const qreal pointSize = format.hasProperty(QTextFormat::FontPointSize)? format.fontPointSize(): defaultFont.pointSizeF(); writer.writeAttribute(foNS,QString::fromLatin1("font-size"),QString::fromLatin1("%1pt").arg(pointSize));}if(format.hasProperty(QTextFormat::FontCapitalization)|| (defaultFontResolveMask &QFont::CapitalizationResolved)) {QFont::Capitalization capitalization = format.hasProperty(QTextFormat::FontCapitalization)? format.fontCapitalization(): defaultFont.capitalization();switch(capitalization) {caseQFont::MixedCase: writer.writeAttribute(foNS,QString::fromLatin1("text-transform"),QString::fromLatin1("none"));break;caseQFont::AllUppercase: writer.writeAttribute(foNS,QString::fromLatin1("text-transform"),QString::fromLatin1("uppercase"));break;caseQFont::AllLowercase: writer.writeAttribute(foNS,QString::fromLatin1("text-transform"),QString::fromLatin1("lowercase"));break;caseQFont::Capitalize: writer.writeAttribute(foNS,QString::fromLatin1("text-transform"),QString::fromLatin1("capitalize"));break;caseQFont::SmallCaps: writer.writeAttribute(foNS,QString::fromLatin1("font-variant"),QString::fromLatin1("small-caps"));break;}}if(format.hasProperty(QTextFormat::FontLetterSpacing) ||(defaultFontResolveMask &QFont::LetterSpacingResolved)) {const qreal letterSpacing = format.hasProperty(QTextFormat::FontLetterSpacing)? format.fontLetterSpacing(): defaultFont.letterSpacing(); writer.writeAttribute(foNS,QString::fromLatin1("letter-spacing"),pixelToPoint(letterSpacing));}if(format.hasProperty(QTextFormat::FontWordSpacing)|| (defaultFontResolveMask &QFont::WordSpacingResolved)) {const qreal wordSpacing = format.hasProperty(QTextFormat::FontWordSpacing)? format.fontWordSpacing(): defaultFont.wordSpacing();if(wordSpacing !=0) writer.writeAttribute(foNS,QString::fromLatin1("word-spacing"),pixelToPoint(wordSpacing));}if(format.hasProperty(QTextFormat::FontUnderline)|| ((defaultFontResolveMask &QFont::UnderlineResolved)&& !format.hasProperty(QTextFormat::TextUnderlineStyle))) {const bool underline = format.hasProperty(QTextFormat::FontUnderline)? format.fontUnderline(): defaultFont.underline(); writer.writeAttribute(styleNS,QString::fromLatin1("text-underline-type"), underline ?QString::fromLatin1("single") :QString::fromLatin1("none"));}if(format.hasProperty(QTextFormat::FontOverline)) {// bool fontOverline () const TODO}if(format.hasProperty(QTextFormat::FontStrikeOut)|| (defaultFontResolveMask &QFont::StrikeOutResolved)) {const bool strikeOut = format.hasProperty(QTextFormat::FontStrikeOut)? format.fontStrikeOut(): defaultFont.strikeOut(); writer.writeAttribute(styleNS,QString::fromLatin1("text-line-through-type"), strikeOut ?QString::fromLatin1("single") :QString::fromLatin1("none"));}if(format.hasProperty(QTextFormat::TextUnderlineColor)) writer.writeAttribute(styleNS,QString::fromLatin1("text-underline-color"), format.underlineColor().name());if(format.hasProperty(QTextFormat::FontFixedPitch)) {// bool fontFixedPitch () const TODO}if(format.hasProperty(QTextFormat::TextUnderlineStyle)) { QString value;switch(format.underlineStyle()) {caseQTextCharFormat::NoUnderline: value =QString::fromLatin1("none");break;caseQTextCharFormat::SingleUnderline: value =QString::fromLatin1("solid");break;caseQTextCharFormat::DashUnderline: value =QString::fromLatin1("dash");break;caseQTextCharFormat::DotLine: value =QString::fromLatin1("dotted");break;caseQTextCharFormat::DashDotLine: value =QString::fromLatin1("dash-dot");break;caseQTextCharFormat::DashDotDotLine: value =QString::fromLatin1("dot-dot-dash");break;caseQTextCharFormat::WaveUnderline: value =QString::fromLatin1("wave");break;caseQTextCharFormat::SpellCheckUnderline: value =QString::fromLatin1("none");break;} writer.writeAttribute(styleNS,QString::fromLatin1("text-underline-style"), value);}if(format.hasProperty(QTextFormat::TextVerticalAlignment)) { QString value;switch(format.verticalAlignment()) {caseQTextCharFormat::AlignMiddle:caseQTextCharFormat::AlignNormal: value =QString::fromLatin1("0%");break;caseQTextCharFormat::AlignSuperScript: value =QString::fromLatin1("super");break;caseQTextCharFormat::AlignSubScript: value =QString::fromLatin1("sub");break;caseQTextCharFormat::AlignTop: value =QString::fromLatin1("100%");break;caseQTextCharFormat::AlignBottom : value =QString::fromLatin1("-100%");break;caseQTextCharFormat::AlignBaseline:break;} writer.writeAttribute(styleNS,QString::fromLatin1("text-position"), value);}if(format.hasProperty(QTextFormat::TextOutline)) writer.writeAttribute(styleNS,QString::fromLatin1("text-outline"),QString::fromLatin1("true"));if(format.hasProperty(QTextFormat::TextToolTip)) {// QString toolTip () const TODO}if(format.hasProperty(QTextFormat::IsAnchor)) {// bool isAnchor () const TODO}if(format.hasProperty(QTextFormat::AnchorHref)) {// QString anchorHref () const TODO}if(format.hasProperty(QTextFormat::AnchorName)) {// QString anchorName () const TODO}if(format.hasProperty(QTextFormat::ForegroundBrush)) { QBrush brush = format.foreground(); writer.writeAttribute(foNS,QString::fromLatin1("color"), brush.color().name());}if(format.hasProperty(QTextFormat::BackgroundBrush)) { QBrush brush = format.background(); writer.writeAttribute(foNS,QString::fromLatin1("background-color"), brush.color().name());} writer.writeEndElement();// style}voidQTextOdfWriter::writeListFormat(QXmlStreamWriter &writer, QTextListFormat format,int formatIndex)const{ writer.writeStartElement(textNS,QString::fromLatin1("list-style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("L%1").arg(formatIndex));QTextListFormat::Style style = format.style();if(style ==QTextListFormat::ListDecimal || style ==QTextListFormat::ListLowerAlpha || style ==QTextListFormat::ListUpperAlpha || style ==QTextListFormat::ListLowerRoman || style ==QTextListFormat::ListUpperRoman) { writer.writeStartElement(textNS,QString::fromLatin1("list-level-style-number")); writer.writeAttribute(styleNS,QString::fromLatin1("num-format"),bulletChar(style));if(format.hasProperty(QTextFormat::ListNumberSuffix)) writer.writeAttribute(styleNS,QString::fromLatin1("num-suffix"), format.numberSuffix());else writer.writeAttribute(styleNS,QString::fromLatin1("num-suffix"),QString::fromLatin1("."));if(format.hasProperty(QTextFormat::ListNumberPrefix)) writer.writeAttribute(styleNS,QString::fromLatin1("num-prefix"), format.numberPrefix());}else{ writer.writeStartElement(textNS,QString::fromLatin1("list-level-style-bullet")); writer.writeAttribute(textNS,QString::fromLatin1("bullet-char"),bulletChar(style));} writer.writeAttribute(textNS,QString::fromLatin1("level"),QString::number(format.indent())); writer.writeEmptyElement(styleNS,QString::fromLatin1("list-level-properties")); writer.writeAttribute(foNS,QString::fromLatin1("text-align"),QString::fromLatin1("start")); QString spacing =QString::fromLatin1("%1mm").arg(format.indent() *8); writer.writeAttribute(textNS,QString::fromLatin1("space-before"), spacing);//writer.writeAttribute(textNS, QString::fromLatin1("min-label-width"), spacing); writer.writeEndElement();// list-level-style-* writer.writeEndElement();// list-style}voidQTextOdfWriter::writeFrameFormat(QXmlStreamWriter &writer, QTextFrameFormat format,int formatIndex)const{ writer.writeStartElement(styleNS,QString::fromLatin1("style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("s%1").arg(formatIndex)); writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("section")); writer.writeEmptyElement(styleNS,QString::fromLatin1("section-properties"));if(format.hasProperty(QTextFormat::FrameTopMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-top"),pixelToPoint(qMax(qreal(0.), format.topMargin())) );if(format.hasProperty(QTextFormat::FrameBottomMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-bottom"),pixelToPoint(qMax(qreal(0.), format.bottomMargin())) );if(format.hasProperty(QTextFormat::FrameLeftMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-left"),pixelToPoint(qMax(qreal(0.), format.leftMargin())) );if(format.hasProperty(QTextFormat::FrameRightMargin)) writer.writeAttribute(foNS,QString::fromLatin1("margin-right"),pixelToPoint(qMax(qreal(0.), format.rightMargin())) ); writer.writeEndElement();// style// TODO consider putting the following properties in a qt-namespace.// Position position () const// qreal border () const// QBrush borderBrush () const// BorderStyle borderStyle () const// qreal padding () const// QTextLength width () const// QTextLength height () const// PageBreakFlags pageBreakPolicy () const}voidQTextOdfWriter::writeTableFormat(QXmlStreamWriter &writer, QTextTableFormat format,int formatIndex)const{// start writing table style element writer.writeStartElement(styleNS,QString::fromLatin1("style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("Table%1").arg(formatIndex)); writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("table")); writer.writeEmptyElement(styleNS,QString::fromLatin1("table-properties"));if(m_tableFormatsWithBorders.contains(formatIndex)) {// write border format collapsing to table style writer.writeAttribute(tableNS,QString::fromLatin1("border-model"),QString::fromLatin1("collapsing"));}const char* align =nullptr;switch(format.alignment()) {caseQt::AlignLeft: align ="left";break;caseQt::AlignRight: align ="right";break;caseQt::AlignHCenter: align ="center";break;caseQt::AlignJustify: align ="margins";break;}if(align) writer.writeAttribute(tableNS,QString::fromLatin1("align"),QString::fromLatin1(align));if(format.width().rawValue()) { writer.writeAttribute(styleNS,QString::fromLatin1("width"),QString::number(format.width().rawValue()) +"pt"_L1);} writer.writeEndElement();// start writing table-column style elementif(format.columnWidthConstraints().size()) {// write table-column-properties for columns with constraints m_tableFormatsWithColWidthConstraints.insert(formatIndex);// needed for linking of columns to stylesfor(int colit =0; colit < format.columnWidthConstraints().size(); ++colit) { writer.writeStartElement(styleNS,QString::fromLatin1("style")); writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("Table%1.%2").arg(formatIndex).arg(colit)); writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("table-column")); writer.writeEmptyElement(styleNS,QString::fromLatin1("table-column-properties")); QString columnWidth;if(format.columnWidthConstraints().at(colit).type() ==QTextLength::PercentageLength) { columnWidth =QString::number(format.columnWidthConstraints().at(colit).rawValue())+"%"_L1;}else if(format.columnWidthConstraints().at(colit).type() ==QTextLength::FixedLength) { columnWidth =QString::number(format.columnWidthConstraints().at(colit).rawValue())+"pt"_L1;}else{//!! HARD-CODING variableWidth Constraints to 100% / nr constraints columnWidth =QString::number(100/ format.columnWidthConstraints().size())+"%"_L1;} writer.writeAttribute(styleNS,QString::fromLatin1("column-width"), columnWidth); writer.writeEndElement();}}}voidQTextOdfWriter::writeTableCellFormat(QXmlStreamWriter &writer, QTextTableCellFormat format,int formatIndex, QList<QTextFormat> &styles)const{// check for all table cells here if they are in a table with borderif(m_cellFormatsInTablesWithBorders.contains(formatIndex)) {const QList<int> tableIdVector = m_cellFormatsInTablesWithBorders.value(formatIndex);for(constauto&tableId : tableIdVector) {constauto&tmpStyle = styles.at(tableId);if(tmpStyle.isTableFormat()) { QTextTableFormat tableFormatTmp = tmpStyle.toTableFormat();tableCellStyleElement(writer, formatIndex, format,true, tableId, tableFormatTmp);}else{qDebug("QTextOdfWriter::writeTableCellFormat: ERROR writing table border format");}}}tableCellStyleElement(writer, formatIndex, format,false);}voidQTextOdfWriter::tableCellStyleElement(QXmlStreamWriter &writer,const int&formatIndex,const QTextTableCellFormat &format,bool hasBorder,int tableId,const QTextTableFormat tableFormatTmp)const{ writer.writeStartElement(styleNS,QString::fromLatin1("style"));if(hasBorder) { writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("TB%1.%2").arg(tableId).arg(formatIndex));}else{ writer.writeAttribute(styleNS,QString::fromLatin1("name"),QString::fromLatin1("T%1").arg(formatIndex));} writer.writeAttribute(styleNS,QString::fromLatin1("family"),QString::fromLatin1("table-cell")); writer.writeEmptyElement(styleNS,QString::fromLatin1("table-cell-properties"));if(hasBorder) { writer.writeAttribute(foNS,QString::fromLatin1("border"),pixelToPoint(tableFormatTmp.border()) +" "_L1 +borderStyleName(tableFormatTmp.borderStyle()) +" "_L1 + tableFormatTmp.borderBrush().color().name(QColor::HexRgb));} qreal topPadding = format.topPadding(); qreal padding = topPadding + tableFormatTmp.cellPadding();if(padding >0&& topPadding == format.bottomPadding()&& topPadding == format.leftPadding() && topPadding == format.rightPadding()) { writer.writeAttribute(foNS,QString::fromLatin1("padding"),pixelToPoint(padding));}else{if(padding >0) writer.writeAttribute(foNS,QString::fromLatin1("padding-top"),pixelToPoint(padding)); padding = format.bottomPadding() + tableFormatTmp.cellPadding();if(padding >0) writer.writeAttribute(foNS,QString::fromLatin1("padding-bottom"),pixelToPoint(padding)); padding = format.leftPadding() + tableFormatTmp.cellPadding();if(padding >0) writer.writeAttribute(foNS,QString::fromLatin1("padding-left"),pixelToPoint(padding)); padding = format.rightPadding() + tableFormatTmp.cellPadding();if(padding >0) writer.writeAttribute(foNS,QString::fromLatin1("padding-right"),pixelToPoint(padding));}if(format.hasProperty(QTextFormat::TextVerticalAlignment)) { QString pos;switch(format.verticalAlignment()) {// TODO - review: doesn't handle all casescaseQTextCharFormat::AlignMiddle: pos =QString::fromLatin1("middle");break;caseQTextCharFormat::AlignTop: pos =QString::fromLatin1("top");break;caseQTextCharFormat::AlignBottom: pos =QString::fromLatin1("bottom");break;default: pos =QString::fromLatin1("automatic");break;} writer.writeAttribute(styleNS,QString::fromLatin1("vertical-align"), pos);}// TODO// ODF just search for style-table-cell-properties-attlist)// QTextFormat::BackgroundImageUrl// format.background writer.writeEndElement();// style}///////////////////////QTextOdfWriter::QTextOdfWriter(const QTextDocument &document, QIODevice *device):officeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0"_L1),textNS("urn:oasis:names:tc:opendocument:xmlns:text:1.0"_L1),styleNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0"_L1),foNS("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"_L1),tableNS("urn:oasis:names:tc:opendocument:xmlns:table:1.0"_L1),drawNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"_L1),xlinkNS("http://www.w3.org/1999/xlink"_L1),svgNS("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"_L1),m_document(&document),m_device(device),m_strategy(nullptr),m_createArchive(true){}boolQTextOdfWriter::writeAll(){if(m_createArchive) m_strategy =newQZipStreamStrategy(m_device);else m_strategy =newQXmlStreamStrategy(m_device);if(!m_device->isWritable() && ! m_device->open(QIODevice::WriteOnly)) {qWarning("QTextOdfWriter::writeAll: the device cannot be opened for writing");return false;} QXmlStreamWriter writer(m_strategy->contentStream);// prettyfy writer.setAutoFormatting(true); writer.setAutoFormattingIndent(2); writer.writeNamespace(officeNS,QString::fromLatin1("office")); writer.writeNamespace(textNS,QString::fromLatin1("text")); writer.writeNamespace(styleNS,QString::fromLatin1("style")); writer.writeNamespace(foNS,QString::fromLatin1("fo")); writer.writeNamespace(tableNS,QString::fromLatin1("table")); writer.writeNamespace(drawNS,QString::fromLatin1("draw")); writer.writeNamespace(xlinkNS,QString::fromLatin1("xlink")); writer.writeNamespace(svgNS,QString::fromLatin1("svg")); writer.writeStartDocument(); writer.writeStartElement(officeNS,QString::fromLatin1("document-content")); writer.writeAttribute(officeNS,QString::fromLatin1("version"),QString::fromLatin1("1.2"));// add fragments. (for character formats)QTextDocumentPrivate::FragmentIterator fragIt =QTextDocumentPrivate::get(m_document)->begin(); QSet<int> formats;while(fragIt !=QTextDocumentPrivate::get(m_document)->end()) {const QTextFragmentData *const frag = fragIt.value(); formats << frag->format;++fragIt;}// add blocks (for blockFormats)QTextDocumentPrivate::BlockMap &blocks =const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(m_document))->blockMap();QTextDocumentPrivate::BlockMap::Iterator blockIt = blocks.begin();while(blockIt != blocks.end()) {const QTextBlockData *const block = blockIt.value(); formats << block->format;++blockIt;}// add objects for lists, frames and tablesconst QList<QTextFormat> allFormats = m_document->allFormats();const QList<int> copy = formats.values();for(auto index : copy) { QTextObject *object = m_document->objectForFormat(allFormats[index]);if(object) { formats << object->formatIndex();if(auto*tableobject = qobject_cast<QTextTable *>(object)) {if(tableobject->format().borderStyle()) {int tableID = tableobject->formatIndex(); m_tableFormatsWithBorders.insert(tableID);// loop through all rows and cols of table and store cell IDs,// create Hash with cell ID as Key and table IDs as Vectorfor(int rowindex =0; rowindex < tableobject->rows(); ++rowindex) {for(int colindex =0; colindex < tableobject->columns(); ++colindex) {const int cellFormatID = tableobject->cellAt(rowindex, colindex).tableCellFormatIndex(); QList<int> tableIdsTmp;if(m_cellFormatsInTablesWithBorders.contains(cellFormatID)) tableIdsTmp = m_cellFormatsInTablesWithBorders.value(cellFormatID);if(!tableIdsTmp.contains(tableID)) tableIdsTmp.append(tableID); m_cellFormatsInTablesWithBorders.insert(cellFormatID, tableIdsTmp);}}}}}}writeFormats(writer, formats); writer.writeStartElement(officeNS,QString::fromLatin1("body")); writer.writeStartElement(officeNS,QString::fromLatin1("text")); QTextFrame *rootFrame = m_document->rootFrame();writeFrame(writer, rootFrame); writer.writeEndElement();// text writer.writeEndElement();// body writer.writeEndElement();// document-content writer.writeEndDocument();delete m_strategy; m_strategy =nullptr;return true;} QT_END_NAMESPACE #endif// QT_NO_TEXTODFWRITER
close