diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk> | 2023-01-23 12:33:39 +0900 |
---|---|---|
committer | Tomaž Vajngerl <quikee@gmail.com> | 2023-01-24 10:50:49 +0000 |
commit | e052f6e1d49a5289411b31561d6e310bf414d896 (patch) | |
tree | 8aa7d8683589077346abe32b4a6c8e6d0d777ae8 | |
parent | 217ef2ed9b8a757b7b02feac799621d20d0f312e (diff) |
tdf#66580 write ODF document as an attachment in hybrid mode
This changes the hybrid mode so that the ODF document is written
as an PDF compatible file attachment into the PDF document. It also
keeps writing the /AdditionalStreams element into the trailer to
keep backwards compatibility for now.
Change-Id: Ica31159cfbd591c6741e3da62c42d1fefd085696
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146053
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
-rw-r--r-- | filter/source/pdf/pdfexport.cxx | 32 | ||||
-rw-r--r-- | include/vcl/pdfwriter.hxx | 18 | ||||
-rw-r--r-- | vcl/inc/pdf/pdfwriter_impl.hxx | 33 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter.cxx | 4 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 266 |
5 files changed, 194 insertions, 159 deletions
diff --git a/filter/source/pdf/pdfexport.cxx b/filter/source/pdf/pdfexport.cxx index 338463f184f8..7e710dabd928 100644 --- a/filter/source/pdf/pdfexport.cxx +++ b/filter/source/pdf/pdfexport.cxx @@ -27,6 +27,7 @@ #include <vcl/canvastools.hxx> #include <vcl/mapmod.hxx> #include <vcl/gdimtf.hxx> +#include <rtl/ustring.hxx> #include <comphelper/propertyvalue.hxx> #include <comphelper/sequence.hxx> #include <comphelper/string.hxx> @@ -312,19 +313,16 @@ void PDFExportStreamDoc::write( const Reference< XOutputStream >& xStream ) if( !xStore.is() ) return; - Sequence< beans::PropertyValue > aArgs( 2 + (m_aPreparedPassword.hasElements() ? 1 : 0) ); - aArgs.getArray()[0].Name = "FilterName"; - aArgs.getArray()[1].Name = "OutputStream"; - aArgs.getArray()[1].Value <<= xStream; - if( m_aPreparedPassword.hasElements() ) - { - aArgs.getArray()[2].Name = "EncryptionData"; - aArgs.getArray()[2].Value <<= m_aPreparedPassword; - } + std::vector<beans::PropertyValue> aArgs { + comphelper::makePropertyValue("FilterName", OUString()), + comphelper::makePropertyValue("OutputStream", xStream), + }; + if (m_aPreparedPassword.hasElements()) + aArgs.push_back(comphelper::makePropertyValue("EncryptionData", m_aPreparedPassword)); try { - xStore->storeToURL( "private:stream", aArgs ); + xStore->storeToURL("private:stream", comphelper::containerToSequence(aArgs)); } catch( const IOException& ) { @@ -927,9 +925,17 @@ bool PDFExport::Export( const OUString& rFile, const Sequence< PropertyValue >& // export stream // get mimetype OUString aSrcMimetype = getMimetypeForDocument( mxContext, mxSrcDoc ); - aPDFWriter.AddStream( aSrcMimetype, - new PDFExportStreamDoc( mxSrcDoc, aPreparedPermissionPassword ) - ); + OUString aExt; + if (aSrcMimetype == "application/vnd.oasis.opendocument.text") + aExt = ".odt"; + else if (aSrcMimetype == "application/vnd.oasis.opendocument.presentation") + aExt = ".odp"; + else if (aSrcMimetype == "application/vnd.oasis.opendocument.spreadsheet") + aExt = ".ods"; + else if (aSrcMimetype == "application/vnd.oasis.opendocument.graphics") + aExt = ".odg"; + std::unique_ptr<vcl::PDFOutputStream> pStream(new PDFExportStreamDoc(mxSrcDoc, aPreparedPermissionPassword)); + aPDFWriter.AddAttachedFile("Original" + aExt, aSrcMimetype, std::move(pStream)); } if ( pOut ) diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index f98f2c231261..80b71237138e 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -1193,24 +1193,24 @@ The following structure describes the permissions used in PDF security */ sal_Int32 CreateControl( const AnyWidget& rControlType ); - /** Inserts an additional stream to the PDF file + /** Attaches an additional file to the PDF file - This function adds an arbitrary stream to the produced PDF file. May be called - any time before Emit(). The stream will be written during - Emit by calling the PDFOutputStream Object's write - method. After the call the PDFOutputStream will be deleted. + This function adds an arbitrary stream that represents an attached file + in the PDF file. - All additional streams and their mimetypes will be entered into an array - in the trailer dictionary. + This also adds an additional stream array entry (with the mimetype) in + the trailer dictionary for backwards compatibility. + + @param rFileName + the filename of the additional file as presented in the stream @param rMimeType the mimetype of the stream @param pStream the interface to the additional stream - */ - void AddStream( const OUString& rMimeType, PDFOutputStream* pStream ); + void AddAttachedFile(OUString const& rFileName, const OUString& rMimeType, std::unique_ptr<PDFOutputStream> pStream); /// Write rString as a PDF hex string into rBuffer. static void AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer); diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index ea7c3d3462ab..5f6abd7bca82 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -440,8 +440,10 @@ struct PDFEmbeddedFile { /// ID of the file. sal_Int32 m_nObject; + OUString m_aSubType; /// Contents of the file. BinaryDataContainer m_aDataContainer; + std::unique_ptr<PDFOutputStream> m_pStream; PDFEmbeddedFile() : m_nObject(0) @@ -673,7 +675,15 @@ struct GraphicsState enum class Mode { DEFAULT, NOWRITE }; -} +struct PDFDocumentAttachedFile +{ + OUString maFilename; + OUString maMimeType; + sal_Int32 mnEmbeddedFileObjectId; + sal_Int32 mnObjectId; +}; + +} // end pdf namespace class PDFWriterImpl final : public VirtualDevice, public PDFObjectContainer { @@ -728,6 +738,9 @@ private: std::vector<PDFScreen> m_aScreens; /// Contains embedded files. std::vector<PDFEmbeddedFile> m_aEmbeddedFiles; + + std::vector<PDFDocumentAttachedFile> m_aDocumentAttachedFiles; + /* makes correctly encoded for export to PDF URLS */ css::uno::Reference< css::util::XURLTransformer > m_xTrans; @@ -810,11 +823,20 @@ private: std::unique_ptr<ZCodec> m_pCodec; std::unique_ptr<SvMemoryStream> m_pMemStream; - std::vector< PDFAddStream > m_aAdditionalStreams; std::set< PDFWriter::ErrorCode > m_aErrors; ::comphelper::Hash m_DocDigest; + sal_uInt64 getCurrentFilePosition() + { + sal_uInt64 nPosition{}; + if (osl::File::E_None != m_aFile.getPos(nPosition)) + { + m_aFile.close(); + m_bOpen = false; + } + return nPosition; + } /* variables for PDF security i12626 @@ -1317,8 +1339,11 @@ public: // controls sal_Int32 createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr = -1 ); - // additional streams - void addStream( const OUString& rMimeType, PDFOutputStream* pStream ); + // attached file + void addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, std::unique_ptr<PDFOutputStream> rStream); + + sal_Int32 addEmbeddedFile(BinaryDataContainer const & rDataContainer); + sal_Int32 addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType); // helper: eventually begin marked content sequence and // emit a comment in debug case diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx index 294d071db154..7d53f45c4699 100644 --- a/vcl/source/gdi/pdfwriter.cxx +++ b/vcl/source/gdi/pdfwriter.cxx @@ -443,9 +443,9 @@ PDFOutputStream::~PDFOutputStream() { } -void PDFWriter::AddStream( const OUString& rMimeType, PDFOutputStream* pStream ) +void PDFWriter::AddAttachedFile(OUString const& rFileName, OUString const& rMimeType, std::unique_ptr<PDFOutputStream> pStream) { - xImplementation->addStream( rMimeType, pStream ); + xImplementation->addDocumentAttachedFile(rFileName, rMimeType, std::move(pStream)); } std::set< PDFWriter::ErrorCode > const & PDFWriter::GetErrors() const diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index e0f868d5d926..2a87f8cb4e23 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -3423,6 +3423,35 @@ bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) return true; } +void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, std::unique_ptr<PDFOutputStream> rStream) +{ + sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType); + auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back(); + rAttachedFile.maFilename = rFileName; + rAttachedFile.maMimeType = rMimeType; + rAttachedFile.mnEmbeddedFileObjectId = nObjectID; + rAttachedFile.mnObjectId = createObject(); +} + +sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType) +{ + sal_Int32 aObjectID = createObject(); + auto& rEmbedded = m_aEmbeddedFiles.emplace_back(); + rEmbedded.m_nObject = aObjectID; + rEmbedded.m_aSubType = rMimeType; + rEmbedded.m_pStream = std::move(rStream); + return aObjectID; +} + +sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer) +{ + sal_Int32 aObjectID = createObject(); + m_aEmbeddedFiles.emplace_back(); + m_aEmbeddedFiles.back().m_nObject = aObjectID; + m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer; + return aObjectID; +} + bool PDFWriterImpl::emitScreenAnnotations() { int nAnnots = m_aScreens.size(); @@ -4871,26 +4900,81 @@ bool PDFWriterImpl::emitAnnotations() return true; } +class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream > +{ + VclPtr<PDFWriterImpl> m_pWriter; + bool m_bWrite; + public: + explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} + + virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override + { + if( m_bWrite && aData.hasElements() ) + { + sal_Int32 nBytes = aData.getLength(); + m_pWriter->writeBuffer( aData.getConstArray(), nBytes ); + } + } + virtual void SAL_CALL flush() override {} + virtual void SAL_CALL closeOutput() override + { + m_bWrite = false; + } +}; + bool PDFWriterImpl::emitEmbeddedFiles() { - for (const auto& rEmbeddedFile : m_aEmbeddedFiles) + for (auto& rEmbeddedFile : m_aEmbeddedFiles) { if (!updateObject(rEmbeddedFile.m_nObject)) continue; + sal_Int32 nSizeObject = createObject(); + OStringBuffer aLine; aLine.append(rEmbeddedFile.m_nObject); aLine.append(" 0 obj\n"); - aLine.append("<< /Type /EmbeddedFile /Length "); - aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_aDataContainer.getSize())); - aLine.append(" >>\nstream\n"); + aLine.append("<< /Type /EmbeddedFile"); + if (!rEmbeddedFile.m_aSubType.isEmpty()) + { + aLine.append("/Subtype /"); + appendName(rEmbeddedFile.m_aSubType, aLine); + } + aLine.append(" /Length "); + appendObjectReference(nSizeObject, aLine); + aLine.append(">>\nstream\n"); + checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject); CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); + disableStreamEncryption(); aLine.setLength(0); - CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize())); - + sal_Int64 nSize{}; + if (!rEmbeddedFile.m_aDataContainer.isEmpty()) + { + nSize = rEmbeddedFile.m_aDataContainer.getSize(); + CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize())); + } + else if (rEmbeddedFile.m_pStream) + { + sal_uInt64 nBegin = getCurrentFilePosition(); + css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this)); + rEmbeddedFile.m_pStream->write(xStream); + rEmbeddedFile.m_pStream.reset(); + xStream.clear(); + nSize = sal_Int64(getCurrentFilePosition() - nBegin); + } aLine.append("\nendstream\nendobj\n\n"); CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength())); + aLine.setLength(0); + + if (!updateObject(nSizeObject)) + return false; + aLine.append(nSizeObject); + aLine.append(" 0 obj\n"); + aLine.append(nSize); + aLine.append("\nendobj\n\n"); + if (!writeBuffer(aLine.getStr(), aLine.getLength())) + return false; } return true; } @@ -5001,6 +5085,25 @@ bool PDFWriterImpl::emitCatalog() CHECK_RETURN( emitAnnotations() ); CHECK_RETURN( emitEmbeddedFiles() ); + // emit attached files + for (auto & rAttachedFile : m_aDocumentAttachedFiles) + { + if (!updateObject(rAttachedFile.mnObjectId)) + return false; + aLine.setLength( 0 ); + + appendObjectID(rAttachedFile.mnObjectId, aLine); + aLine.append("<</Type/Filespec /F"); + aLine.append('<'); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); + aLine.append('>'); + aLine.append(" /EF <</F "); + appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); + aLine.append(">>"); + aLine.append(">>\nendobj\n\n"); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + // emit Catalog m_nCatalogObject = createObject(); if( ! updateObject( m_nCatalogObject ) ) @@ -5020,6 +5123,22 @@ bool PDFWriterImpl::emitCatalog() aLine.append( " 0 R\n" ); } + if (!m_aDocumentAttachedFiles.empty()) + { + aLine.append("/Names "); + aLine.append("<</EmbeddedFiles <</Names ["); + for (auto & rAttachedFile : m_aDocumentAttachedFiles) + { + aLine.append('<'); + PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine); + aLine.append('>'); + aLine.append(' '); + appendObjectReference(rAttachedFile.mnObjectId, aLine); + } + aLine.append("]>>>>"); + aLine.append("\n" ); + } + if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) switch( m_aContext.PageLayout ) { @@ -5797,19 +5916,21 @@ bool PDFWriterImpl::emitTrailer() aLine.append( aDocChecksum ); aLine.append( "\n" ); } - if( !m_aAdditionalStreams.empty() ) + + if (!m_aDocumentAttachedFiles.empty()) { aLine.append( "/AdditionalStreams [" ); - for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams) + for (auto const& rAttachedFile : m_aDocumentAttachedFiles) { aLine.append( "/" ); - appendName( rAdditionalStream.m_aMimeType, aLine ); - aLine.append( " " ); - aLine.append( rAdditionalStream.m_nStreamObject ); - aLine.append( " 0 R\n" ); + appendName(rAttachedFile.maMimeType, aLine); + aLine.append(" "); + appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); + aLine.append("\n"); } aLine.append( "]\n" ); } + aLine.append( ">>\n" "startxref\n" ); aLine.append( static_cast<sal_Int64>(nXRefOffset) ); @@ -5927,104 +6048,6 @@ void PDFWriterImpl::sortWidgets() // FIXME: implement tab order in structure tree for PDF 1.5 } -class PDFStreamIf : - public cppu::WeakImplHelper< css::io::XOutputStream > -{ - VclPtr<PDFWriterImpl> m_pWriter; - bool m_bWrite; - public: - explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} - - virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override; - virtual void SAL_CALL flush() override; - virtual void SAL_CALL closeOutput() override; -}; - -void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) -{ - if( m_bWrite && aData.hasElements() ) - { - sal_Int32 nBytes = aData.getLength(); - m_pWriter->writeBuffer( aData.getConstArray(), nBytes ); - } -} - -void SAL_CALL PDFStreamIf::flush() -{ -} - -void SAL_CALL PDFStreamIf::closeOutput() -{ - m_bWrite = false; -} - -bool PDFWriterImpl::emitAdditionalStreams() -{ - unsigned int nStreams = m_aAdditionalStreams.size(); - for( unsigned int i = 0; i < nStreams; i++ ) - { - PDFAddStream& rStream = m_aAdditionalStreams[i]; - rStream.m_nStreamObject = createObject(); - sal_Int32 nSizeObject = createObject(); - - if( ! updateObject( rStream.m_nStreamObject ) ) - return false; - - OStringBuffer aLine; - aLine.append( rStream.m_nStreamObject ); - aLine.append( " 0 obj\n<</Length " ); - aLine.append( nSizeObject ); - aLine.append( " 0 R" ); - if( rStream.m_bCompress ) - aLine.append( "/Filter/FlateDecode" ); - aLine.append( ">>\nstream\n" ); - if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) - return false; - sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0; - if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) ) - { - m_aFile.close(); - m_bOpen = false; - } - if( rStream.m_bCompress ) - beginCompression(); - - checkAndEnableStreamEncryption( rStream.m_nStreamObject ); - css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) ); - assert(rStream.m_pStream); - if (!rStream.m_pStream) - return false; - rStream.m_pStream->write( xStream ); - xStream.clear(); - delete rStream.m_pStream; - rStream.m_pStream = nullptr; - disableStreamEncryption(); - - if( rStream.m_bCompress ) - endCompression(); - - if (osl::File::E_None != m_aFile.getPos(nEndStreamPos)) - { - m_aFile.close(); - m_bOpen = false; - return false; - } - if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) - return false ; - // emit stream length object - if( ! updateObject( nSizeObject ) ) - return false; - aLine.setLength( 0 ); - aLine.append( nSizeObject ); - aLine.append( " 0 obj\n" ); - aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) ); - aLine.append( "\nendobj\n\n" ); - if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) - return false; - } - return true; -} - bool PDFWriterImpl::emit() { endPage(); @@ -6043,9 +6066,6 @@ bool PDFWriterImpl::emit() } #endif - // emit additional streams - CHECK_RETURN( emitAdditionalStreams() ); - // emit catalog CHECK_RETURN( emitCatalog() ); @@ -9461,10 +9481,8 @@ void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObject if (m_aContext.UseReferenceXObject) { // Store the original PDF data as an embedded file. - m_aEmbeddedFiles.emplace_back(); - m_aEmbeddedFiles.back().m_nObject = createObject(); - m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer; - rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject; + auto nObjectID = addEmbeddedFile(rDataContainer); + rEmit.m_nEmbeddedObject = nObjectID; } else { @@ -11493,20 +11511,6 @@ sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sa return nNewWidget; } -void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream ) -{ - if( pStream ) - { - m_aAdditionalStreams.emplace_back( ); - PDFAddStream& rStream = m_aAdditionalStreams.back(); - rStream.m_aMimeType = !rMimeType.isEmpty() - ? rMimeType - : OUString( "application/octet-stream" ); - rStream.m_pStream = pStream; - rStream.m_bCompress = false; - } -} - void PDFWriterImpl::MARK( const char* pString ) { beginStructureElementMCSeq(); |