I want to replace a string in my ODT file with an image, but this image is from binary data, not a file. I don't want to write to temp file and load it, I want to purely do so in memory.
I have this code which implements a ByteArrayInputStream (Blob is essentially a wrapper around std::vector<unsigned char>
).
#include <cstring>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/io/XSeekable.hpp>
#include <cppuhelper/implbase2.hxx>
using namespace com::sun::star;
class Document_Blob_Input_Stream : public cppu::WeakImplHelper2<io::XInputStream,
io::XSeekable>
{
public:
explicit Document_Blob_Input_Stream(const Blob& data)
: data(data), currentPosition(0), closed(false)
{
}
virtual ~Document_Blob_Input_Stream() override
{
}
virtual sal_Int32 SAL_CALL readBytes(uno::Sequence<sal_Int8>& outputData, sal_Int32 bytesToRead) override
{
if (closed || bytesToRead <= 0 || currentPosition >= static_cast<sal_Int32>(data.size())) {
outputData.realloc(0);
return 0;
}
sal_Int32 avail = available();
sal_Int32 bytes = std::min(avail, bytesToRead);
outputData.realloc(bytes);
std::memcpy(outputData.getArray(), data.data() + currentPosition, static_cast<size_t>(bytes));
currentPosition += bytes;
return bytes;
}
virtual sal_Int32 SAL_CALL readSomeBytes(uno::Sequence<sal_Int8>& outputData, sal_Int32 maxBytesToRead) override
{
return readBytes(outputData, maxBytesToRead);
}
virtual void SAL_CALL skipBytes(sal_Int32 bytesToSkip) override
{
if (!closed && bytesToSkip > 0) {
sal_Int32 avail = static_cast<sal_Int32>(data.size()) - currentPosition;
sal_Int32 toSkip = std::min(avail, bytesToSkip);
currentPosition += toSkip;
}
}
virtual sal_Int32 SAL_CALL available() override
{
if (closed) {
return 0;
}
sal_Int32 avail = static_cast<sal_Int32>(data.size()) - currentPosition;
return avail;
}
virtual void SAL_CALL closeInput() override
{
closed = true;
}
virtual sal_Int64 SAL_CALL getPosition() override
{
return currentPosition;
}
virtual sal_Int64 SAL_CALL getLength() override
{
return data.size();
}
virtual void SAL_CALL seek(sal_Int64 newPosition) override
{
if (newPosition < 0)
newPosition = 0;
else if (newPosition > static_cast<sal_Int64>(data.size()))
newPosition = data.size();
currentPosition = static_cast<sal_Int32>(newPosition);
}
private:
const Blob& data;
sal_Int32 currentPosition;
bool closed;
};
I use this code to load ODT files from memory and write them to an output stream, and it works completely fine. Can even do output stream, back to input stream, etc. So I feel quite confident that this code is correct.
As for replacing text with an image, here is the initialization
context = cppu::bootstrap();
serviceManager = uno::Reference<lang::XMultiServiceFactory>(context->getServiceManager(), uno::UNO_QUERY);
uno::Reference<text::XTextDocument> textDocument(templateOdt, uno::UNO_QUERY);
uno::Reference<lang::XMultiServiceFactory> documentServiceManager(textDocument, uno::UNO_QUERY);
//Cursor - this traverses till it finds text to replace
uno::Reference<text::XTextCursor> cursor = text->createTextCursor();
//Found text..
//Load image from file into blob
std::string filename = "/tmp/image.png";
std::ifstream file(filename, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char*>(buffer.data()), size))
throw std::runtime_error("Error reading file: " + filename);
Blob* blobData = new Blob((void*)buffer.data(), buffer.size());
//Note here, writing to file, this works completely fine, can open the image1.png
WriteToFile(*blobData, "/tmp/image1.png");
uno::Reference<io::XInputStream> xBlobStream(new Document_Blob_Input_Stream(*blobData));
beans::PropertyValue aProps[2];
aProps[0].Name = rtl::OUString("InputStream");
aProps[0].Value <<= xBlobStream;
aProps[1].Name = rtl::OUString("MimeType");
aProps[1].Value <<= rtl::OUString("image/png");
uno::Sequence<beans::PropertyValue> propsSeq(aProps, 2);
uno::Reference<graphic::XGraphicProvider> xGraphicProvider(
serviceManager->createInstance(rtl::OUString("com.sun.star.graphic.GraphicProvider")),
uno::UNO_QUERY
);
uno::Reference<graphic::XGraphic> xGraphic = xGraphicProvider->queryGraphic(propsSeq);
uno::Reference<beans::XPropertySet> xGraphicProps(xGraphic, uno::UNO_QUERY);
uno::Reference<text::XTextContent> xTextGraphic(
documentServiceManager->createInstance(rtl::OUString("com.sun.star.text.TextGraphicObject")),
uno::UNO_QUERY
);
uno::Reference<beans::XPropertySet> xProps(xTextGraphic, uno::UNO_QUERY);
xProps->setPropertyValue(rtl::OUString("Graphic"), uno::makeAny(xGraphic));
xProps->setPropertyValue(rtl::OUString("Width"), uno::makeAny(sal_Int32(4500)));
xProps->setPropertyValue(rtl::OUString("Height"), uno::makeAny(sal_Int32(4000)));
//Note that all of the uno::Reference values up until this point return fine for .is(), so they are not null or no reference. I have just omitted.
uno::Any graphicAny = xProps->getPropertyValue(rtl::OUString("Graphic"));
if (graphicAny.hasValue()) {
std::cout << "Graphic property set successfully." << std::endl;
}
else {
std::cout << "Failed to set Graphic property." << std::endl;
}
uno::Reference<com::sun::star::text::XTextRange> xTextRange(cursor, uno::UNO_QUERY);
text->insertTextContent(xTextRange, xTextGraphic, false);
This code runs as if the graphic set "successfully", but it just seems to draw a border equal to the size I specified, at the location of the text, but it does not actually draw the image.
If I instead use a temp file (just use loadFromURL method over Graphic) the code works fine and the image draws.
How can I get this to work with an in-memory binary image.
c++17, libreoffice7.0 (7.0.4.2)