From 24c26133b4dda63a9940d8aa396a3f481e7c9bbf Mon Sep 17 00:00:00 2001 From: Debao Zhang Date: Wed, 15 Jan 2014 15:57:51 +0800 Subject: [PATCH] Code refactoring: Improve picture support Don't lost picture information when edit existing .xlsx files --- examples/xlsx/image/main.cpp | 10 +- src/xlsx/qtxlsx.pri | 8 +- src/xlsx/xlsxdocument.cpp | 60 +++-- src/xlsx/xlsxdrawing.cpp | 176 +++---------- src/xlsx/xlsxdrawing_p.h | 43 +--- src/xlsx/xlsxdrawinganchor.cpp | 457 +++++++++++++++++++++++++++++++++ src/xlsx/xlsxdrawinganchor_p.h | 148 +++++++++++ src/xlsx/xlsxmediafile.cpp | 99 +++++++ src/xlsx/xlsxmediafile_p.h | 68 +++++ src/xlsx/xlsxworkbook.cpp | 68 ++--- src/xlsx/xlsxworkbook.h | 7 +- src/xlsx/xlsxworkbook_p.h | 3 +- src/xlsx/xlsxworksheet.cpp | 188 ++++---------- src/xlsx/xlsxworksheet.h | 10 +- src/xlsx/xlsxworksheet_p.h | 66 +---- 15 files changed, 964 insertions(+), 447 deletions(-) create mode 100644 src/xlsx/xlsxdrawinganchor.cpp create mode 100644 src/xlsx/xlsxdrawinganchor_p.h create mode 100644 src/xlsx/xlsxmediafile.cpp create mode 100644 src/xlsx/xlsxmediafile_p.h diff --git a/examples/xlsx/image/main.cpp b/examples/xlsx/image/main.cpp index bfef3e1..7dcf9c4 100644 --- a/examples/xlsx/image/main.cpp +++ b/examples/xlsx/image/main.cpp @@ -6,12 +6,14 @@ int main(int argc, char** argv) QGuiApplication(argc, argv); QXlsx::Document xlsx; - - QImage image(400, 300, QImage::Format_RGB32); + QImage image(40, 30, QImage::Format_RGB32); image.fill(Qt::green); - xlsx.insertImage(5, 5, image); + for (int i=0; i<10; ++i) + xlsx.insertImage(10*i, 5, image); + xlsx.saveAs("Book1.xlsx"); - xlsx.save(); + QXlsx::Document xlsx2("Book1.xlsx"); + xlsx2.saveAs("Book2.xlsx"); return 0; } diff --git a/src/xlsx/qtxlsx.pri b/src/xlsx/qtxlsx.pri index 1058864..b2d68d9 100755 --- a/src/xlsx/qtxlsx.pri +++ b/src/xlsx/qtxlsx.pri @@ -34,7 +34,9 @@ HEADERS += $$PWD/xlsxdocpropscore_p.h \ $$PWD/xlsxconditionalformatting.h \ $$PWD/xlsxconditionalformatting_p.h \ $$PWD/xlsxcolor_p.h \ - $$PWD/xlsxnumformatparser_p.h + $$PWD/xlsxnumformatparser_p.h \ + $$PWD/xlsxdrawinganchor_p.h \ + $$PWD/xlsxmediafile_p.h SOURCES += $$PWD/xlsxdocpropscore.cpp \ $$PWD/xlsxdocpropsapp.cpp \ @@ -57,4 +59,6 @@ SOURCES += $$PWD/xlsxdocpropscore.cpp \ $$PWD/xlsxrichstring.cpp \ $$PWD/xlsxconditionalformatting.cpp \ $$PWD/xlsxcolor.cpp \ - $$PWD/xlsxnumformatparser.cpp + $$PWD/xlsxnumformatparser.cpp \ + $$PWD/xlsxdrawinganchor.cpp \ + $$PWD/xlsxmediafile.cpp diff --git a/src/xlsx/xlsxdocument.cpp b/src/xlsx/xlsxdocument.cpp index 2e9c531..69ab79e 100644 --- a/src/xlsx/xlsxdocument.cpp +++ b/src/xlsx/xlsxdocument.cpp @@ -37,12 +37,14 @@ #include "xlsxutility_p.h" #include "xlsxworkbook_p.h" #include "xlsxdrawing_p.h" +#include "xlsxmediafile_p.h" #include "xlsxzipreader_p.h" #include "xlsxzipwriter_p.h" #include #include #include +#include QT_BEGIN_NAMESPACE_XLSX @@ -189,6 +191,27 @@ bool DocumentPrivate::loadPackage(QIODevice *device) if (zipReader.filePaths().contains(rel_path)) sheet->relationships().loadFromXmlData(zipReader.fileData(rel_path)); sheet->loadFromXmlData(zipReader.fileData(worksheet_path)); + + //load drawing if exists + if (!sheet->drawingPath().isEmpty()) { + QString drawingPath = QDir::cleanPath(splitPath(worksheet_path)[0] + QLatin1String("/") + sheet->drawingPath()); + Drawing *drawing = new Drawing(workbook.data()); + drawing->pathInPackage = drawingPath; + QString drawing_rel_path = getRelFilePath(drawingPath); + if (zipReader.filePaths().contains(drawing_rel_path)) + drawing->relationships.loadFromXmlData(zipReader.fileData(drawing_rel_path)); + drawing->loadFromXmlData(zipReader.fileData(drawingPath)); + sheet->setDrawing(drawing); + } + } + + //load media files + QList > mediaFileToLoad = workbook->mediaFiles(); + for (int i=0; i mf = mediaFileToLoad[i]; + const QString path = mf->fileName(); + const QString suffix = path.mid(path.lastIndexOf(QLatin1Char('.'))+1); + mf->set(zipReader.fileData(path), suffix); } return true; @@ -206,9 +229,6 @@ bool DocumentPrivate::savePackage(QIODevice *device) const DocPropsApp docPropsApp; DocPropsCore docPropsCore; - //: Todo - workbook->prepareDrawings(); - // save worksheet xml files for (int i=0; iworksheetCount(); ++i) { Worksheet *sheet = workbook->worksheet(i); @@ -232,19 +252,8 @@ bool DocumentPrivate::savePackage(QIODevice *device) const Drawing *drawing = workbook->drawings()[i]; zipWriter.addFile(QStringLiteral("xl/drawings/drawing%1.xml").arg(i+1), drawing->saveToXmlData()); - } - - for (int i=0; iworksheetCount(); ++i) { - Worksheet *sheet = workbook->worksheet(i); - if (sheet->drawingLinks().size() == 0) - continue; - Relationships rels; - - typedef QPair PairType; - foreach (PairType pair, sheet->drawingLinks()) - rels.addDocumentRelationship(pair.first, pair.second); - - zipWriter.addFile(QStringLiteral("xl/drawings/_rels/drawing%1.xml.rels").arg(i+1), rels.saveToXmlData()); + if (!drawing->relationships.isEmpty()) + zipWriter.addFile(QStringLiteral("xl/drawings/_rels/drawing%1.xml.rels").arg(i+1), drawing->relationships.saveToXmlData()); } // save docProps app/core xml file @@ -274,17 +283,12 @@ bool DocumentPrivate::savePackage(QIODevice *device) const zipWriter.addFile(QStringLiteral("xl/theme/theme1.xml"), workbook->theme()->saveToXmlData()); // save image files - if (!workbook->images().isEmpty()) - contentTypes.addDefault(QStringLiteral("png"), QStringLiteral("image/png")); - - for (int i=0; iimages().size(); ++i) { - QImage image = workbook->images()[i]; + for (int i=0; imediaFiles().size(); ++i) { + QSharedPointer mf = workbook->mediaFiles()[i]; + if (!mf->mimeType().isEmpty()) + contentTypes.addDefault(mf->suffix(), mf->mimeType()); - QByteArray data; - QBuffer buffer(&data); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "png"); - zipWriter.addFile(QStringLiteral("xl/media/image%1.png").arg(i+1), data); + zipWriter.addFile(QStringLiteral("xl/media/image%1.%2").arg(i+1).arg(mf->suffix()), mf->contents()); } // save root .rels xml file @@ -388,9 +392,9 @@ QVariant Document::read(int row, int col) const * \brief Insert an \a image to current active worksheet to the position \a row, \a column with the given * \a xOffset, \a yOffset, \a xScale and \a yScale. */ -int Document::insertImage(int row, int column, const QImage &image, double xOffset, double yOffset, double xScale, double yScale) +int Document::insertImage(int row, int column, const QImage &image, double /*xOffset*/, double /*yOffset*/, double /*xScale*/, double /*yScale*/) { - return currentWorksheet()->insertImage(row, column, image, QPointF(xOffset, yOffset), xScale, yScale); + return currentWorksheet()->insertImage(row, column, image); } /*! diff --git a/src/xlsx/xlsxdrawing.cpp b/src/xlsx/xlsxdrawing.cpp index 76c7787..2ff2633 100644 --- a/src/xlsx/xlsxdrawing.cpp +++ b/src/xlsx/xlsxdrawing.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "xlsxdrawing_p.h" +#include "xlsxdrawinganchor_p.h" #include #include @@ -31,10 +32,15 @@ namespace QXlsx { -Drawing::Drawing() +Drawing::Drawing(Workbook *workbook) + :workbook(workbook) { - embedded = false; - orientation = 0; + +} + +Drawing::~Drawing() +{ + qDeleteAll(anchors); } QByteArray Drawing::saveToXmlData() const @@ -48,6 +54,8 @@ QByteArray Drawing::saveToXmlData() const void Drawing::saveToXmlFile(QIODevice *device) const { + relationships.clear(); + QXmlStreamWriter writer(device); writer.writeStartDocument(QStringLiteral("1.0"), true); @@ -55,156 +63,42 @@ void Drawing::saveToXmlFile(QIODevice *device) const writer.writeAttribute(QStringLiteral("xmlns:xdr"), QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing")); writer.writeAttribute(QStringLiteral("xmlns:a"), QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/main")); - if (embedded) { - int index = 1; - foreach (XlsxDrawingDimensionData *dimension, dimensionList) { - writeTwoCellAnchor(writer, index, dimension); - index++; - } - } else { - //write the xdr:absoluteAnchor element - writeAbsoluteAnchor(writer, 1); - } + foreach (DrawingAnchor *anchor, anchors) + anchor->saveToXml(writer); writer.writeEndElement();//xdr:wsDr writer.writeEndDocument(); } -void Drawing::writeTwoCellAnchor(QXmlStreamWriter &writer, int index, XlsxDrawingDimensionData *data) const -{ - writer.writeStartElement(QStringLiteral("xdr:twoCellAnchor")); - if (data->drawing_type == 2) - writer.writeAttribute(QStringLiteral("editAs"), QStringLiteral("oneCell")); -// if (shape) -// writer.writeAttribute(QStringLiteral("editAs"), ); - - writer.writeStartElement(QStringLiteral("xdr:from")); - writer.writeTextElement(QStringLiteral("xdr:col"), QString::number(data->col_from)); - writer.writeTextElement(QStringLiteral("xdr:colOff"), QString::number((int)data->col_from_offset)); - writer.writeTextElement(QStringLiteral("xdr:row"), QString::number(data->row_from)); - writer.writeTextElement(QStringLiteral("xdr:rowOff"), QString::number((int)data->row_from_offset)); - writer.writeEndElement(); //xdr:from - - writer.writeStartElement(QStringLiteral("xdr:to")); - writer.writeTextElement(QStringLiteral("xdr:col"), QString::number(data->col_to)); - writer.writeTextElement(QStringLiteral("xdr:colOff"), QString::number((int)data->col_to_offset)); - writer.writeTextElement(QStringLiteral("xdr:row"), QString::number(data->row_to)); - writer.writeTextElement(QStringLiteral("xdr:rowOff"), QString::number((int)data->row_to_offset)); - writer.writeEndElement(); //xdr:to - - if (data->drawing_type == 1) { - //Graphics frame, xdr:graphicFrame - writeGraphicFrame(writer, index, data->description); - } else if (data->drawing_type == 2) { - //Image, xdr:pic - writePicture(writer, index, data->col_absolute, data->row_absolute, data->width, data->height, data->description); - } else { - //Shape, xdr:sp - } - - writer.writeEmptyElement(QStringLiteral("xdr:clientData")); - writer.writeEndElement(); //xdr:twoCellAnchor -} - -void Drawing::writeAbsoluteAnchor(QXmlStreamWriter &writer, int index) const +bool Drawing::loadFromXmlFile(QIODevice *device) { - writer.writeStartElement(QStringLiteral("xdr:absoluteAnchor")); - if (orientation == 0) { - writePos(writer, 0, 0); - writeExt(writer, 9308969, 6078325); - } else { - writePos(writer, 0, -47625); - writeExt(writer, 6162675, 6124575); + QXmlStreamReader reader(device); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("absoluteAnchor")) { + DrawingAbsoluteAnchor * anchor = new DrawingAbsoluteAnchor(this); + anchor->loadFromXml(reader); + } else if (reader.name() == QLatin1String("oneCellAnchor")) { + DrawingOneCellAnchor * anchor = new DrawingOneCellAnchor(this); + anchor->loadFromXml(reader); + } else if (reader.name() == QLatin1String("twoCellAnchor")) { + DrawingTwoCellAnchor * anchor = new DrawingTwoCellAnchor(this); + anchor->loadFromXml(reader); + } + } } - writeGraphicFrame(writer, index); - writer.writeEmptyElement(QStringLiteral("xdr:clientData")); - - writer.writeEndElement(); //xdr:absoluteAnchor -} - -void Drawing::writePos(QXmlStreamWriter &writer, int x, int y) const -{ - writer.writeEmptyElement(QStringLiteral("xdr:pos")); - writer.writeAttribute(QStringLiteral("x"), QString::number(x)); - writer.writeAttribute(QStringLiteral("y"), QString::number(y)); -} - -void Drawing::writeExt(QXmlStreamWriter &writer, int cx, int cy) const -{ - writer.writeStartElement(QStringLiteral("xdr:ext")); - writer.writeAttribute(QStringLiteral("cx"), QString::number(cx)); - writer.writeAttribute(QStringLiteral("cy"), QString::number(cy)); + return true; } -void Drawing::writeGraphicFrame(QXmlStreamWriter &writer, int index, const QString &name) const +bool Drawing::loadFromXmlData(const QByteArray &data) { - writer.writeStartElement(QStringLiteral("xdr:graphicFrame")); - writer.writeAttribute(QStringLiteral("macro"), QString()); - - writer.writeStartElement(QStringLiteral("xdr:nvGraphicFramePr")); - writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); - writer.writeAttribute(QStringLiteral("id"), QString::number(index+1)); - writer.writeAttribute(QStringLiteral("name"), name.isEmpty() ? QStringLiteral("Chart%1").arg(index): name); - if (embedded) { - writer.writeEmptyElement(QStringLiteral("xdr:cNvGraphicFramePr")); - } else { - writer.writeStartElement(QStringLiteral("xdr:cNvGraphicFramePr")); - writer.writeEmptyElement(QStringLiteral("a:graphicFrameLocks")); - writer.writeAttribute(QStringLiteral("noGrp"), QStringLiteral("1")); - writer.writeEndElement(); //xdr:cNvGraphicFramePr - } + QBuffer buffer; + buffer.setData(data); + buffer.open(QIODevice::ReadOnly); - writer.writeEndElement();//xdr:nvGraphicFramePr - writer.writeEndElement(); //xdr:graphicFrame -} - -void Drawing::writePicture(QXmlStreamWriter &writer, int index, double col_abs, double row_abs, int width, int height, const QString &description) const -{ - writer.writeStartElement(QStringLiteral("xdr:pic")); - - writer.writeStartElement(QStringLiteral("xdr:nvPicPr")); - writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); - writer.writeAttribute(QStringLiteral("id"), QString::number(index+1)); - writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Picture%1").arg(index)); - if (!description.isEmpty()) - writer.writeAttribute(QStringLiteral("descr"), description); - - writer.writeStartElement(QStringLiteral("xdr:cNvPicPr")); - writer.writeEmptyElement(QStringLiteral("a:picLocks")); - writer.writeAttribute(QStringLiteral("noChangeAspect"), QStringLiteral("1")); - writer.writeEndElement(); //xdr:cNvPicPr - - writer.writeEndElement(); //xdr:nvPicPr - - writer.writeStartElement(QStringLiteral("xdr:blipFill")); - writer.writeEmptyElement(QStringLiteral("a:blip")); - writer.writeAttribute(QStringLiteral("xmlns:r"), QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); - writer.writeAttribute(QStringLiteral("r:embed"), QStringLiteral("rId%1").arg(index)); - writer.writeStartElement(QStringLiteral("a:stretch")); - writer.writeEmptyElement(QStringLiteral("a:fillRect")); - writer.writeEndElement(); //a:stretch - writer.writeEndElement();//xdr:blipFill - - writer.writeStartElement(QStringLiteral("xdr:spPr")); - - writer.writeStartElement(QStringLiteral("a:xfrm")); - writer.writeEmptyElement(QStringLiteral("a:off")); - writer.writeAttribute(QStringLiteral("x"), QString::number((int)col_abs)); - writer.writeAttribute(QStringLiteral("y"), QString::number((int)row_abs)); - writer.writeEmptyElement(QStringLiteral("a:ext")); - writer.writeAttribute(QStringLiteral("cx"), QString::number(width)); - writer.writeAttribute(QStringLiteral("cy"), QString::number(height)); - writer.writeEndElement(); //a:xfrm - - writer.writeStartElement(QStringLiteral("a:prstGeom")); - writer.writeAttribute(QStringLiteral("prst"), QStringLiteral("rect")); - writer.writeEmptyElement(QStringLiteral("a:avLst")); - writer.writeEndElement(); //a:prstGeom - - writer.writeEndElement(); //xdr:spPr - - writer.writeEndElement(); //xdr:pic + return loadFromXmlFile(&buffer); } } // namespace QXlsx diff --git a/src/xlsx/xlsxdrawing_p.h b/src/xlsx/xlsxdrawing_p.h index a8da8a6..dba29a5 100644 --- a/src/xlsx/xlsxdrawing_p.h +++ b/src/xlsx/xlsxdrawing_p.h @@ -37,51 +37,36 @@ // We mean it. // +#include "xlsxrelationships_p.h" + #include #include +#include class QIODevice; class QXmlStreamWriter; namespace QXlsx { -struct XlsxDrawingDimensionData -{ - int drawing_type; - int col_from; - int row_from; - double col_from_offset; - double row_from_offset; - int col_to; - int row_to; - double col_to_offset; - double row_to_offset; - int col_absolute; - int row_absolute; - int width; - int height; - QString description; - int shape; -}; +class DrawingAnchor; +class Workbook; +class MediaFile; class Drawing { public: - Drawing(); + Drawing(Workbook *workbook); + ~Drawing(); void saveToXmlFile(QIODevice *device) const; QByteArray saveToXmlData() const; + bool loadFromXmlFile(QIODevice *device); + bool loadFromXmlData(const QByteArray &data); - bool embedded; - int orientation; - QList dimensionList; + Workbook *workbook; + QList anchors; + mutable Relationships relationships; -private: - void writeTwoCellAnchor(QXmlStreamWriter &writer, int index, XlsxDrawingDimensionData *data) const; - void writeAbsoluteAnchor(QXmlStreamWriter &writer, int index) const; - void writePos(QXmlStreamWriter &writer, int x, int y) const; - void writeExt(QXmlStreamWriter &writer, int cx, int cy) const; - void writeGraphicFrame(QXmlStreamWriter &writer, int index, const QString &name=QString()) const; - void writePicture(QXmlStreamWriter &writer, int index, double col_abs, double row_abs, int width, int height, const QString &description) const; + QString pathInPackage; }; } // namespace QXlsx diff --git a/src/xlsx/xlsxdrawinganchor.cpp b/src/xlsx/xlsxdrawinganchor.cpp new file mode 100644 index 0000000..87e532f --- /dev/null +++ b/src/xlsx/xlsxdrawinganchor.cpp @@ -0,0 +1,457 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#include "xlsxdrawinganchor_p.h" +#include "xlsxdrawing_p.h" +#include "xlsxmediafile_p.h" +#include "xlsxworkbook.h" +#include "xlsxutility_p.h" + +#include +#include +#include +#include + +namespace QXlsx { + +/* + The vertices that define the position of a graphical object + within the worksheet in pixels. + + +------------+------------+ + | A | B | + +-----+------------+------------+ + | |(x1,y1) | | + | 1 |(A1)._______|______ | + | | | | | + | | | | | + +-----+----| OBJECT |-----+ + | | | | | + | 2 | |______________. | + | | | (B2)| + | | | (x2,y2)| + +---- +------------+------------+ + + Example of an object that covers some of the area from cell A1 to B2. + + Based on the width and height of the object we need to calculate 8 vars: + + col_start, row_start, col_end, row_end, x1, y1, x2, y2. + + We also calculate the absolute x and y position of the top left vertex of + the object. This is required for images. + + The width and height of the cells that the object occupies can be + variable and have to be taken into account. +*/ + +//anchor + +DrawingAnchor::DrawingAnchor(Drawing *drawing, ObjectType objectType) + :m_drawing(drawing), m_objectType(objectType) +{ + m_drawing->anchors.append(this); + m_id = m_drawing->anchors.size();//must be unique in one drawing{x}.xml file. +} + +DrawingAnchor::~DrawingAnchor() +{ + +} + +void DrawingAnchor::setObjectPicture(const QImage &img) +{ + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + img.save(&buffer, "PNG"); + + m_pictureFile = QSharedPointer(new MediaFile(ba, QStringLiteral("png"), QStringLiteral("image/png"))); + m_drawing->workbook->addMediaFile(m_pictureFile); +} + +QPoint DrawingAnchor::loadXmlPos(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("pos")); + + QPoint pos; + QXmlStreamAttributes attrs = reader.attributes(); + pos.setX(attrs.value(QLatin1String("x")).toString().toInt()); + pos.setY(attrs.value(QLatin1String("y")).toString().toInt()); + return pos; +} + +QSize DrawingAnchor::loadXmlExt(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("ext")); + + QSize size; + QXmlStreamAttributes attrs = reader.attributes(); + size.setWidth(attrs.value(QLatin1String("cx")).toString().toInt()); + size.setHeight(attrs.value(QLatin1String("cy")).toString().toInt()); + return size; +} + +XlsxMarker DrawingAnchor::loadXmlMarker(QXmlStreamReader &reader, const QString &node) +{ + Q_ASSERT(reader.name() == node); + + int col, colOffset, row, rowOffset; + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("col")) { + col = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("colOff")) { + colOffset = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("row")) { + row = reader.readElementText().toInt(); + } else if (reader.name() == QLatin1String("rowOff")) { + rowOffset = reader.readElementText().toInt(); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == node) { + break; + } + } + + return XlsxMarker(row, col, rowOffset, colOffset); +} + +void DrawingAnchor::loadXmlObject(QXmlStreamReader &reader) +{ + if (reader.name() == QLatin1String("sp")) { + //Shape + m_objectType = Shape; + loadXmlObjectShape(reader); + } else if (reader.name() == QLatin1String("grpSp")) { + //Group Shape + m_objectType = GroupShape; + loadXmlObjectGroupShape(reader); + } else if (reader.name() == QLatin1String("graphicFrame")) { + //Graphic Frame + m_objectType = GraphicFrame; + loadXmlObjectGraphicFrame(reader); + } else if (reader.name() == QLatin1String("cxnSp")) { + //Connection Shape + m_objectType = ConnectionShape; + loadXmlObjectConnectionShape(reader); + } else if (reader.name() == QLatin1String("pic")) { + //Picture + m_objectType = Picture; + loadXmlObjectPicture(reader); + } +} + +void DrawingAnchor::loadXmlObjectConnectionShape(QXmlStreamReader &reader) +{ + Q_UNUSED(reader) +} + +void DrawingAnchor::loadXmlObjectGraphicFrame(QXmlStreamReader &reader) +{ + Q_UNUSED(reader) +} + +void DrawingAnchor::loadXmlObjectGroupShape(QXmlStreamReader &reader) +{ + Q_UNUSED(reader) +} + +void DrawingAnchor::loadXmlObjectPicture(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("pic")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("blip")) { + QString rId = reader.attributes().value(QLatin1String("r:embed")).toString(); + QString name = m_drawing->relationships.getRelationshipById(rId).target; + QString path = QDir::cleanPath(splitPath(m_drawing->pathInPackage)[0] + QLatin1String("/") + name); + + bool exist = false; + QList > mfs = m_drawing->workbook->mediaFiles(); + for (int i=0; ifileName() == path) { + //already exist + exist = true; + m_pictureFile = mfs[i]; + } + } + if (!exist) { + m_pictureFile = QSharedPointer (new MediaFile(path)); + m_drawing->workbook->addMediaFile(m_pictureFile, true); + } + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == QLatin1String("pic")) { + break; + } + } + + return; +} + +void DrawingAnchor::loadXmlObjectShape(QXmlStreamReader &reader) +{ + Q_UNUSED(reader) +} + +void DrawingAnchor::saveXmlPos(QXmlStreamWriter &writer, const QPoint &pos) const +{ + writer.writeEmptyElement(QStringLiteral("xdr:pos")); + writer.writeAttribute(QStringLiteral("x"), QString::number(pos.x())); + writer.writeAttribute(QStringLiteral("y"), QString::number(pos.y())); +} + +void DrawingAnchor::saveXmlExt(QXmlStreamWriter &writer, const QSize &ext) const +{ + writer.writeStartElement(QStringLiteral("xdr:ext")); + writer.writeAttribute(QStringLiteral("cx"), QString::number(ext.width())); + writer.writeAttribute(QStringLiteral("cy"), QString::number(ext.height())); + writer.writeEndElement(); //xdr:ext +} + +void DrawingAnchor::saveXmlMarker(QXmlStreamWriter &writer, const XlsxMarker &marker, const QString &node) const +{ + writer.writeStartElement(node); //xdr:from or xdr:to + writer.writeTextElement(QStringLiteral("xdr:col"), QString::number(marker.col())); + writer.writeTextElement(QStringLiteral("xdr:colOff"), QString::number(marker.colOff())); + writer.writeTextElement(QStringLiteral("xdr:row"), QString::number(marker.row())); + writer.writeTextElement(QStringLiteral("xdr:rowOff"), QString::number(marker.rowOff())); + writer.writeEndElement(); +} + +void DrawingAnchor::saveXmlObject(QXmlStreamWriter &writer) const +{ + if (m_objectType == Picture) + saveXmlObjectPicture(writer); + else if (m_objectType == ConnectionShape) + saveXmlObjectConnectionShape(writer); + else if (m_objectType == GraphicFrame) + saveXmlObjectGraphicFrame(writer); + else if (m_objectType == GroupShape) + saveXmlObjectGroupShape(writer); + else if (m_objectType == Shape) + saveXmlObjectShape(writer); +} + +void DrawingAnchor::saveXmlObjectConnectionShape(QXmlStreamWriter &writer) const +{ + Q_UNUSED(writer) +} + +void DrawingAnchor::saveXmlObjectGraphicFrame(QXmlStreamWriter &writer) const +{ + Q_UNUSED(writer) +} + +void DrawingAnchor::saveXmlObjectGroupShape(QXmlStreamWriter &writer) const +{ + Q_UNUSED(writer) +} + +void DrawingAnchor::saveXmlObjectPicture(QXmlStreamWriter &writer) const +{ + Q_ASSERT(m_objectType == Picture && !m_pictureFile.isNull()); + + writer.writeStartElement(QStringLiteral("xdr:pic")); + + writer.writeStartElement(QStringLiteral("xdr:nvPicPr")); + writer.writeEmptyElement(QStringLiteral("xdr:cNvPr")); + writer.writeAttribute(QStringLiteral("id"), QString::number(m_id)); + writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Picture %1").arg(m_id)); + + writer.writeStartElement(QStringLiteral("xdr:cNvPicPr")); + writer.writeEmptyElement(QStringLiteral("a:picLocks")); + writer.writeAttribute(QStringLiteral("noChangeAspect"), QStringLiteral("1")); + writer.writeEndElement(); //xdr:cNvPicPr + + writer.writeEndElement(); //xdr:nvPicPr + + m_drawing->relationships.addDocumentRelationship(QStringLiteral("/image"), QStringLiteral("../media/image%1.%2") + .arg(m_pictureFile->index()+1) + .arg(m_pictureFile->suffix())); + + writer.writeStartElement(QStringLiteral("xdr:blipFill")); + writer.writeEmptyElement(QStringLiteral("a:blip")); + writer.writeAttribute(QStringLiteral("xmlns:r"), QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships")); + writer.writeAttribute(QStringLiteral("r:embed"), QStringLiteral("rId%1").arg(m_drawing->relationships.count())); + writer.writeStartElement(QStringLiteral("a:stretch")); + writer.writeEmptyElement(QStringLiteral("a:fillRect")); + writer.writeEndElement(); //a:stretch + writer.writeEndElement();//xdr:blipFill + + writer.writeStartElement(QStringLiteral("xdr:spPr")); + + writer.writeStartElement(QStringLiteral("a:prstGeom")); + writer.writeAttribute(QStringLiteral("prst"), QStringLiteral("rect")); + writer.writeEmptyElement(QStringLiteral("a:avLst")); + writer.writeEndElement(); //a:prstGeom + + writer.writeEndElement(); //xdr:spPr + + writer.writeEndElement(); //xdr:pic +} + +void DrawingAnchor::saveXmlObjectShape(QXmlStreamWriter &writer) const +{ + Q_UNUSED(writer) +} + +//absolute anchor + +DrawingAbsoluteAnchor::DrawingAbsoluteAnchor(Drawing *drawing, ObjectType objectType) + :DrawingAnchor(drawing, objectType) +{ + +} + +bool DrawingAbsoluteAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("absoluteAnchor")); + + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("pos")) { + pos = loadXmlPos(reader); + } else if (reader.name() == QLatin1String("ext")) { + ext = loadXmlExt(reader); + } else { + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == QLatin1String("absoluteAnchor")) { + break; + } + } + return true; +} + +void DrawingAbsoluteAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:absoluteAnchor")); + saveXmlPos(writer, pos); + saveXmlExt(writer, ext); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); //xdr:absoluteAnchor +} + +//one cell anchor + +DrawingOneCellAnchor::DrawingOneCellAnchor(Drawing *drawing, ObjectType objectType) + :DrawingAnchor(drawing, objectType) +{ + +} + +bool DrawingOneCellAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("oneCellAnchor")); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("from")) { + from = loadXmlMarker(reader, QLatin1String("from")); + } else if (reader.name() == QLatin1String("ext")) { + ext = loadXmlExt(reader); + } else { + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == QLatin1String("oneCellAnchor")) { + break; + } + } + return true; +} + +void DrawingOneCellAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:oneCellAnchor")); + + saveXmlMarker(writer, from, QStringLiteral("xdr:from")); + saveXmlExt(writer, ext); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); //xdr:oneCellAnchor +} + +/* + Two cell anchor + + This class specifies a two cell anchor placeholder for a group + , a shape, or a drawing element. It moves with + cells and its extents are in EMU units. +*/ +DrawingTwoCellAnchor::DrawingTwoCellAnchor(Drawing *drawing, ObjectType objectType) + :DrawingAnchor(drawing, objectType) +{ + +} + +bool DrawingTwoCellAnchor::loadFromXml(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.name() == QLatin1String("twoCellAnchor")); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.tokenType() == QXmlStreamReader::StartElement) { + if (reader.name() == QLatin1String("from")) { + from = loadXmlMarker(reader, QLatin1String("from")); + } else if (reader.name() == QLatin1String("to")) { + to = loadXmlMarker(reader, QLatin1String("to")); + } else { + loadXmlObject(reader); + } + } else if (reader.tokenType() == QXmlStreamReader::EndElement + && reader.name() == QLatin1String("twoCellAnchor")) { + break; + } + } + return true; +} + +void DrawingTwoCellAnchor::saveToXml(QXmlStreamWriter &writer) const +{ + writer.writeStartElement(QStringLiteral("xdr:twoCellAnchor")); + writer.writeAttribute(QStringLiteral("editAs"), QStringLiteral("oneCell")); + + saveXmlMarker(writer, from, QStringLiteral("xdr:from")); + saveXmlMarker(writer, to, QStringLiteral("xdr:to")); + + saveXmlObject(writer); + + writer.writeEmptyElement(QStringLiteral("xdr:clientData")); + writer.writeEndElement(); //xdr:twoCellAnchor +} + +} // namespace QXlsx diff --git a/src/xlsx/xlsxdrawinganchor_p.h b/src/xlsx/xlsxdrawinganchor_p.h new file mode 100644 index 0000000..be83688 --- /dev/null +++ b/src/xlsx/xlsxdrawinganchor_p.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#ifndef QXLSX_XLSXDRAWINGANCHOR_P_H +#define QXLSX_XLSXDRAWINGANCHOR_P_H + +#include "xlsxglobal.h" + +#include +#include +#include +#include + +class QXmlStreamReader; +class QXmlStreamWriter; + +namespace QXlsx { + +class Drawing; +class MediaFile; + +//Helper class +struct XlsxMarker +{ + XlsxMarker(){} + XlsxMarker(int row, int column, int rowOffset, int colOffset) + :cell(QPoint(row, column)), offset(rowOffset, colOffset) + { + + } + + int row() const {return cell.x();} + int col() const {return cell.y();} + int rowOff() const {return offset.width();} + int colOff() const {return offset.height();} + + QPoint cell; + QSize offset; +}; + +class DrawingAnchor +{ +public: + enum ObjectType { + GraphicFrame, + Shape, + GroupShape, + ConnectionShape, + Picture, + Unknown + }; + + DrawingAnchor(Drawing *drawing, ObjectType objectType); + virtual ~DrawingAnchor(); + void setObjectPicture(const QImage &img); + + virtual bool loadFromXml(QXmlStreamReader &reader) = 0; + virtual void saveToXml(QXmlStreamWriter &writer) const = 0; + +protected: + QPoint loadXmlPos(QXmlStreamReader &reader); + QSize loadXmlExt(QXmlStreamReader &reader); + XlsxMarker loadXmlMarker(QXmlStreamReader &reader, const QString &node); + void loadXmlObject(QXmlStreamReader &reader); + void loadXmlObjectShape(QXmlStreamReader &reader); + void loadXmlObjectGroupShape(QXmlStreamReader &reader); + void loadXmlObjectGraphicFrame(QXmlStreamReader &reader); + void loadXmlObjectConnectionShape(QXmlStreamReader &reader); + void loadXmlObjectPicture(QXmlStreamReader &reader); + + void saveXmlPos(QXmlStreamWriter &writer, const QPoint &pos) const; + void saveXmlExt(QXmlStreamWriter &writer, const QSize &ext) const; + void saveXmlMarker(QXmlStreamWriter &writer, const XlsxMarker &marker, const QString &node) const; + void saveXmlObject(QXmlStreamWriter &writer) const; + void saveXmlObjectShape(QXmlStreamWriter &writer) const; + void saveXmlObjectGroupShape(QXmlStreamWriter &writer) const; + void saveXmlObjectGraphicFrame(QXmlStreamWriter &writer) const; + void saveXmlObjectConnectionShape(QXmlStreamWriter &writer) const; + void saveXmlObjectPicture(QXmlStreamWriter &writer) const; + + Drawing *m_drawing; + ObjectType m_objectType; + QSharedPointer m_pictureFile; + + int m_id; +}; + +class DrawingAbsoluteAnchor : public DrawingAnchor +{ +public: + DrawingAbsoluteAnchor(Drawing *drawing, ObjectType objectType=Unknown); + + QPoint pos; + QSize ext; + + bool loadFromXml(QXmlStreamReader &reader); + void saveToXml(QXmlStreamWriter &writer) const; +}; + +class DrawingOneCellAnchor : public DrawingAnchor +{ +public: + DrawingOneCellAnchor(Drawing *drawing, ObjectType objectType=Unknown); + + XlsxMarker from; + QSize ext; + + bool loadFromXml(QXmlStreamReader &reader); + void saveToXml(QXmlStreamWriter &writer) const; +}; + +class DrawingTwoCellAnchor : public DrawingAnchor +{ +public: + DrawingTwoCellAnchor(Drawing *drawing, ObjectType objectType=Unknown); + + XlsxMarker from; + XlsxMarker to; + + bool loadFromXml(QXmlStreamReader &reader); + void saveToXml(QXmlStreamWriter &writer) const; +}; + +} // namespace QXlsx + +#endif // QXLSX_XLSXDRAWINGANCHOR_P_H diff --git a/src/xlsx/xlsxmediafile.cpp b/src/xlsx/xlsxmediafile.cpp new file mode 100644 index 0000000..39fbc03 --- /dev/null +++ b/src/xlsx/xlsxmediafile.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#include "xlsxmediafile_p.h" +#include + +namespace QXlsx { + +MediaFile::MediaFile(const QByteArray &bytes, const QString &suffix, const QString &mimeType) + : m_contents(bytes), m_suffix(suffix), m_mimeType(mimeType) + , m_index(0), m_indexValid(false) +{ + m_hashKey = QCryptographicHash::hash(m_contents, QCryptographicHash::Md5); +} + +MediaFile::MediaFile(const QString &fileName) + :m_fileName(fileName), m_index(0), m_indexValid(false) +{ + +} + +void MediaFile::set(const QByteArray &bytes, const QString &suffix, const QString &mimeType) +{ + m_contents = bytes; + m_suffix = suffix; + m_mimeType = mimeType; + m_hashKey = QCryptographicHash::hash(m_contents, QCryptographicHash::Md5); + m_indexValid = false; +} + +void MediaFile::setFileName(const QString &name) +{ + m_fileName = name; +} + +QString MediaFile::fileName() const +{ + return m_fileName; +} + +QString MediaFile::suffix() const +{ + return m_suffix; +} + +QString MediaFile::mimeType() const +{ + return m_mimeType; +} + +QByteArray MediaFile::contents() const +{ + return m_contents; +} + +int MediaFile::index() const +{ + return m_index; +} + +bool MediaFile::isIndexValid() const +{ + return m_indexValid; +} + +void MediaFile::setIndex(int idx) +{ + m_index = idx; + m_indexValid = true; +} + +QByteArray MediaFile::hashKey() const +{ + return m_hashKey; +} + +} // namespace QXlsx diff --git a/src/xlsx/xlsxmediafile_p.h b/src/xlsx/xlsxmediafile_p.h new file mode 100644 index 0000000..6978fc3 --- /dev/null +++ b/src/xlsx/xlsxmediafile_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** Copyright (c) 2013-2014 Debao Zhang +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ + +#ifndef QXLSX_XLSXMEDIAFILE_H +#define QXLSX_XLSXMEDIAFILE_H + +#include "xlsxglobal.h" + +#include +#include + +namespace QXlsx { + +class MediaFile +{ +public: + MediaFile(const QString &fileName); + MediaFile(const QByteArray &bytes, const QString &suffix, const QString &mimeType=QString()); + + void set(const QByteArray &bytes, const QString &suffix, const QString &mimeType=QString()); + QString suffix() const; + QString mimeType() const; + QByteArray contents() const; + + bool isIndexValid() const; + int index() const; + void setIndex(int idx); + QByteArray hashKey() const; + + void setFileName(const QString &name); + QString fileName() const; + +private: + QString m_fileName; //... + QByteArray m_contents; + QString m_suffix; + QString m_mimeType; + + bool m_indexValid; + int m_index; + QByteArray m_hashKey; +}; + +} // namespace QXlsx + +#endif // QXLSX_XLSXMEDIAFILE_H diff --git a/src/xlsx/xlsxworkbook.cpp b/src/xlsx/xlsxworkbook.cpp index 216cf46..144744e 100755 --- a/src/xlsx/xlsxworkbook.cpp +++ b/src/xlsx/xlsxworkbook.cpp @@ -30,6 +30,7 @@ #include "xlsxformat.h" #include "xlsxworksheet_p.h" #include "xlsxformat_p.h" +#include "xlsxmediafile_p.h" #include #include @@ -370,41 +371,22 @@ Theme *Workbook::theme() return d->theme.data(); } -QList Workbook::images() -{ - Q_D(Workbook); - return d->images; -} - +/*! + * \internal + * + * Unlike media files, drawing file is a property of the sheet. + */ QList Workbook::drawings() { Q_D(Workbook); - return d->drawings; -} - -void Workbook::prepareDrawings() -{ - Q_D(Workbook); - int image_ref_id = 0; - d->images.clear(); - d->drawings.clear(); - + QList ds; for (int i=0; iworksheets.size(); ++i) { QSharedPointer sheet = d->worksheets[i]; - if (sheet->images().isEmpty()) //No drawing (such as Image, ...) - continue; - - sheet->clearExtraDrawingInfo(); - - //At present, only picture type supported - for (int idx = 0; idx < sheet->images().size(); ++idx) { - image_ref_id += 1; - sheet->prepareImage(idx, image_ref_id); - d->images.append(sheet->images()[idx]->image); - } - - d->drawings.append(sheet->drawing()); + if (sheet->drawing()) + ds.append(sheet->drawing()); } + + return ds; } void Workbook::saveToXmlFile(QIODevice *device) const @@ -585,4 +567,32 @@ Relationships &Workbook::relationships() return d->relationships; } +/*! + * \internal + */ +QList > Workbook::mediaFiles() const +{ + Q_D(const Workbook); + + return d->mediaFiles; +} + +/*! + * \internal + */ +void Workbook::addMediaFile(QSharedPointer media, bool force) +{ + Q_D(Workbook); + if (!force) { + for (int i=0; imediaFiles.size(); ++i) { + if (d->mediaFiles[i]->hashKey() == media->hashKey()) { + media->setIndex(i); + return; + } + } + } + media->setIndex(d->mediaFiles.size()); + d->mediaFiles.append(media); +} + QT_END_NAMESPACE_XLSX diff --git a/src/xlsx/xlsxworkbook.h b/src/xlsx/xlsxworkbook.h index a23a6b9..9b24ab1 100755 --- a/src/xlsx/xlsxworkbook.h +++ b/src/xlsx/xlsxworkbook.h @@ -42,6 +42,7 @@ class Document; class Theme; class Relationships; class DocumentPrivate; +class MediaFile; class WorkbookPrivate; class Q_XLSX_EXPORT Workbook @@ -75,6 +76,10 @@ public: QString defaultDateFormat() const; void setDefaultDateFormat(const QString &format); + //internal used member + void addMediaFile(QSharedPointer media, bool force=false); + QList > mediaFiles() const; + private: friend class Worksheet; friend class WorksheetPrivate; @@ -94,9 +99,9 @@ private: Theme *theme(); QList images(); QList drawings(); - void prepareDrawings(); QStringList worksheetNames() const; Worksheet *addWorksheet(const QString &name, int sheetId); + WorkbookPrivate * const d_ptr; }; diff --git a/src/xlsx/xlsxworkbook_p.h b/src/xlsx/xlsxworkbook_p.h index e69e3b9..682a7ad 100644 --- a/src/xlsx/xlsxworkbook_p.h +++ b/src/xlsx/xlsxworkbook_p.h @@ -87,8 +87,7 @@ public: QStringList worksheetNames; QSharedPointer styles; QSharedPointer theme; - QList images; - QList drawings; + QList > mediaFiles; QList definedNamesList; QList sheetItemInfoList;//Data from xml file diff --git a/src/xlsx/xlsxworksheet.cpp b/src/xlsx/xlsxworksheet.cpp index 583d819..f4a693d 100755 --- a/src/xlsx/xlsxworksheet.cpp +++ b/src/xlsx/xlsxworksheet.cpp @@ -36,6 +36,7 @@ #include "xlsxcell_p.h" #include "xlsxcellrange.h" #include "xlsxconditionalformatting_p.h" +#include "xlsxdrawinganchor_p.h" #include #include @@ -455,12 +456,6 @@ void Worksheet::setWhiteSpaceVisible(bool visible) d->showWhiteSpace = visible; } -QList > Worksheet::drawingLinks() const -{ - Q_D(const Worksheet); - return d->drawingLinks; -} - /*! * Write \a value to cell (\a row, \a column) with the \a format. * Both \a row and \a column are all 1-indexed value. @@ -1024,12 +1019,33 @@ bool Worksheet::addConditionalFormatting(const ConditionalFormatting &cf) return true; } -int Worksheet::insertImage(int row, int column, const QImage &image, const QPointF &offset, double xScale, double yScale) +bool Worksheet::insertImage(int row, int column, const QImage &image) { Q_D(Worksheet); - d->imageList.append(new XlsxImageData(row, column, image, offset, xScale, yScale)); - return 0; + if (image.isNull()) + return false; + + if (!d->drawing) + d->drawing = new Drawing(d->workbook); + + DrawingOneCellAnchor *anchor = new DrawingOneCellAnchor(d->drawing, DrawingAnchor::Picture); + + /* + The size are expressed as English Metric Units (EMUs). There are + 12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per + pixel + */ + anchor->from = XlsxMarker(row, column, 0, 0); + anchor->ext = QSize(image.width() * 9525, image.height() * 9525); + + anchor->setObjectPicture(image); + return true; +} + +int Worksheet::insertImage(int row, int column, const QImage &image, const QPointF & /*offset*/, double /*xScale*/, double /*yScale*/) +{ + return insertImage(row, column, image); } /*! @@ -1678,61 +1694,38 @@ CellRange Worksheet::dimension() const return d->dimension; } +/*! + * \internal + */ Drawing *Worksheet::drawing() const { Q_D(const Worksheet); return d->drawing; } -QList Worksheet::images() const +/*! + * \internal + * + * When loading the .xlsx package, the drawing{x}.xml file path + * is extracted when we parse the sheet{x}.xml file. + */ +QString Worksheet::drawingPath() const { Q_D(const Worksheet); - return d->imageList; + return d->drawingPath_in_zip; } -void Worksheet::clearExtraDrawingInfo() -{ - Q_D(Worksheet); - if (d->drawing) { - delete d->drawing; - d->drawing = 0; - d->drawingLinks.clear(); - } -} - -void Worksheet::prepareImage(int index, int image_id) +/*! + * \internal + * + * Note, the object will be managed by this sheet. + */ +void Worksheet::setDrawing(Drawing *draw) { Q_D(Worksheet); - if (!d->drawing) { - d->drawing = new Drawing; - d->drawing->embedded = true; - } - - XlsxImageData *imageData = d->imageList[index]; - - XlsxDrawingDimensionData *data = new XlsxDrawingDimensionData; - data->drawing_type = 2; + Q_ASSERT(!d->drawing); - double width = imageData->image.width() * imageData->xScale; - double height = imageData->image.height() * imageData->yScale; - - XlsxObjectPositionData posData = d->pixelsToEMUs(d->objectPixelsPosition(imageData->col, imageData->row, imageData->offset.x(), imageData->offset.y(), width, height)); - data->col_from = posData.col_start; - data->col_from_offset = posData.x1; - data->row_from = posData.row_start; - data->row_from_offset = posData.y1; - data->col_to = posData.col_end; - data->col_to_offset = posData.x2; - data->row_to = posData.row_end; - data->row_to_offset = posData.y2; - data->width = posData.width; - data->height = posData.height; - data->col_absolute = posData.x_abs; - data->row_absolute = posData.y_abs; - - d->drawing->dimensionList.append(data); - - d->drawingLinks.append(QPair(QStringLiteral("/image"), QStringLiteral("../media/image%1.png").arg(image_id))); + d->drawing = draw; } /* @@ -1774,96 +1767,6 @@ int WorksheetPrivate::colPixelsSize(int col) const return pixels; } -/* - col_start Col containing upper left corner of object. - x1 Distance to left side of object. - row_start Row containing top left corner of object. - y1 Distance to top of object. - col_end Col containing lower right corner of object. - x2 Distance to right side of object. - row_end Row containing bottom right corner of object. - y2 Distance to bottom of object. - width Width of object frame. - height Height of object frame. - x_abs Absolute distance to left side of object. - y_abs Absolute distance to top side of object. -*/ -XlsxObjectPositionData WorksheetPrivate::objectPixelsPosition(int col_start, int row_start, double x1, double y1, double width, double height) const -{ - double x_abs = 0; - double y_abs = 0; - for (int col_id = 1; col_id < col_start; ++col_id) - x_abs += colPixelsSize(col_id); - x_abs += x1; - for (int row_id = 1; row_id < row_start; ++row_id) - y_abs += rowPixelsSize(row_id); - y_abs += y1; - - // Adjust start column for offsets that are greater than the col width. - while (x1 > colPixelsSize(col_start)) { - x1 -= colPixelsSize(col_start); - col_start += 1; - } - while (y1 > rowPixelsSize(row_start)) { - y1 -= rowPixelsSize(row_start); - row_start += 1; - } - - int col_end = col_start; - int row_end = row_start; - double x2 = width + x1; - double y2 = height + y1; - - while (x2 > colPixelsSize(col_end)) { - x2 -= colPixelsSize(col_end); - col_end += 1; - } - - while (y2 > rowPixelsSize(row_end)) { - y2 -= rowPixelsSize(row_end); - row_end += 1; - } - - XlsxObjectPositionData data; - data.col_start = col_start; - data.x1 = x1; - data.row_start = row_start; - data.y1 = y1; - data.col_end = col_end; - data.x2 = x2; - data.row_end = row_end; - data.y2 = y2; - data.x_abs = x_abs; - data.y_abs = y_abs; - data.width = width; - data.height = height; - - return data; -} - -/* - Calculate the vertices that define the position of a graphical - object within the worksheet in EMUs. - - The vertices are expressed as English Metric Units (EMUs). There are - 12,700 EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per - pixel -*/ -XlsxObjectPositionData WorksheetPrivate::pixelsToEMUs(const XlsxObjectPositionData &data) const -{ - XlsxObjectPositionData result = data; - result.x1 = static_cast(data.x1 * 9525 + 0.5); - result.y1 = static_cast(data.y1 * 9525 + 0.5); - result.x2 = static_cast(data.x2 * 9525 + 0.5); - result.y2 = static_cast(data.y2 * 9525 + 0.5); - result.x_abs = static_cast(data.x_abs * 9525 + 0.5); - result.y_abs = static_cast(data.y_abs * 9525 + 0.5); - result.width = static_cast(data.width * 9525 + 0.5); - result.height = static_cast(data.height * 9525 + 0.5); - - return result; -} - QByteArray Worksheet::saveToXmlData() const { QByteArray data; @@ -2220,6 +2123,9 @@ bool Worksheet::loadFromXmlFile(QIODevice *device) d->conditionalFormattingList.append(cf); } else if (reader.name() == QLatin1String("hyperlinks")) { d->loadXmlHyperlinks(reader); + } else if (reader.name() == QLatin1String("drawing")) { + QString rId = reader.attributes().value(QStringLiteral("r:id")).toString(); + d->drawingPath_in_zip = d->relationships.getRelationshipById(rId).target; } } } diff --git a/src/xlsx/xlsxworksheet.h b/src/xlsx/xlsxworksheet.h index e0b35fe..301d111 100755 --- a/src/xlsx/xlsxworksheet.h +++ b/src/xlsx/xlsxworksheet.h @@ -90,7 +90,8 @@ public: Cell *cellAt(const QString &row_column) const; Cell *cellAt(int row, int column) const; - int insertImage(int row, int column, const QImage &image, const QPointF &offset=QPointF(), double xScale=1, double yScale=1); + bool insertImage(int row, int column, const QImage &image); + Q_DECL_DEPRECATED int insertImage(int row, int column, const QImage &image, const QPointF &offset, double xScale=1, double yScale=1); int mergeCells(const QString &range, const Format &format=Format()); int mergeCells(const CellRange &range, const Format &format=Format()); @@ -149,11 +150,10 @@ private: bool isHidden() const; void setHidden(bool hidden); int sheetId() const; - QList > drawingLinks() const; + Drawing *drawing() const; - QList images() const; - void prepareImage(int index, int image_id); - void clearExtraDrawingInfo(); + void setDrawing(Drawing *d); + QString drawingPath() const; WorksheetPrivate * const d_ptr; }; diff --git a/src/xlsx/xlsxworksheet_p.h b/src/xlsx/xlsxworksheet_p.h index 15346a2..41cfd56 100644 --- a/src/xlsx/xlsxworksheet_p.h +++ b/src/xlsx/xlsxworksheet_p.h @@ -79,67 +79,6 @@ struct XlsxHyperlinkData QString tooltip; }; -struct XlsxImageData -{ - XlsxImageData(int row, int col, const QImage &image, const QPointF &offset, double xScale, double yScale) : - row(row), col(col), image(image), offset(offset), xScale(xScale), yScale(yScale) - { - } - - int row; - int col; - QImage image; - QPointF offset; - double xScale; - double yScale; -}; - -/* - The vertices that define the position of a graphical object - within the worksheet in pixels. - - +------------+------------+ - | A | B | - +-----+------------+------------+ - | |(x1,y1) | | - | 1 |(A1)._______|______ | - | | | | | - | | | | | - +-----+----| OBJECT |-----+ - | | | | | - | 2 | |______________. | - | | | (B2)| - | | | (x2,y2)| - +---- +------------+------------+ - - Example of an object that covers some of the area from cell A1 to B2. - - Based on the width and height of the object we need to calculate 8 vars: - - col_start, row_start, col_end, row_end, x1, y1, x2, y2. - - We also calculate the absolute x and y position of the top left vertex of - the object. This is required for images. - - The width and height of the cells that the object occupies can be - variable and have to be taken into account. -*/ -struct XlsxObjectPositionData -{ - int col_start; - double x1; - int row_start; - double y1; - int col_end; - double x2; - int row_end; - double y2; - double width; - double height; - double x_abs; - double y_abs; -}; - struct XlsxRowInfo { XlsxRowInfo(double height=0, const Format &format=Format(), bool hidden=false) : @@ -193,8 +132,6 @@ public: void saveXmlDataValidations(QXmlStreamWriter &writer) const; int rowPixelsSize(int row) const; int colPixelsSize(int col) const; - XlsxObjectPositionData objectPixelsPosition(int col_start, int row_start, double x1, double y1, double width, double height) const; - XlsxObjectPositionData pixelsToEMUs(const XlsxObjectPositionData &data) const; QSharedPointer loadXmlNumericCellData(QXmlStreamReader &reader); void loadXmlSheetData(QXmlStreamReader &reader); @@ -210,15 +147,14 @@ public: Workbook *workbook; mutable Relationships relationships; Drawing *drawing; + QString drawingPath_in_zip; QMap > > cellTable; QMap > comments; QMap > > urlTable; QList merges; - QList imageList; QMap > rowsInfo; QMap > colsInfo; QMap > colsInfoHelper; - QList > drawingLinks; QList dataValidationsList; QList conditionalFormattingList;