include/vcl/pdfwriter.hxx | 6 - sw/inc/formatcontentcontrol.hxx | 2 sw/qa/core/text/text.cxx | 33 +++++++++ sw/source/core/text/itrform2.cxx | 89 +++++++++++++++++++++++++- sw/source/core/txtnode/attrcontentcontrol.cxx | 30 ++++++++ 5 files changed, 155 insertions(+), 5 deletions(-)
New commits: commit b6496ce5718e90110a6a73d49e66a3969f8b6474 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Sep 12 09:30:38 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Sep 13 10:26:26 2022 +0200 sw content controls, rich text: add initial PDF export Similar to how such form widgets are emitted for form shapes in drawinglayer::processor2d::VclMetafileProcessor2D::processControlPrimitive2D(). This is just initial support for rich and plain text, the other types from SwContentControlType are not yet handled. (cherry picked from commit 82d90529dc2b3cb8359dec78852cbd910a66d275) Conflicts: sw/qa/core/text/text.cxx Change-Id: I765fec134f1959fe03f01ecaa128e830d86ab610 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139798 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index 822b6870c89c..be15738fbd5d 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -215,7 +215,7 @@ public: Error_Signature_Failed, }; - struct UNLESS_MERGELIBS(VCL_DLLPUBLIC) AnyWidget + struct VCL_DLLPUBLIC AnyWidget { WidgetType Type; // primitive RTTI public: @@ -286,7 +286,7 @@ public: ,TabOrder( rSource.TabOrder ) { } - AnyWidget& operator=( const AnyWidget& ); // never implemented + AnyWidget& operator=( const AnyWidget& ) = delete; // never implemented }; struct PushButtonWidget final : public AnyWidget @@ -373,7 +373,7 @@ public: // in the group }; - struct EditWidget final : public AnyWidget + struct VCL_DLLPUBLIC EditWidget final : public AnyWidget { bool MultiLine; // whether multiple lines are allowed bool Password; // visible echo off diff --git a/sw/inc/formatcontentcontrol.hxx b/sw/inc/formatcontentcontrol.hxx index 6c1dffd240ae..d0c56b6ae326 100644 --- a/sw/inc/formatcontentcontrol.hxx +++ b/sw/inc/formatcontentcontrol.hxx @@ -319,6 +319,8 @@ public: void SetReadWrite(bool bReadWrite) { m_bReadWrite = bReadWrite; } bool GetReadWrite() const { return m_bReadWrite; } + + SwContentControlType GetType() const; }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx index a571ced2f289..ce1dab48f0cb 100644 --- a/sw/qa/core/text/text.cxx +++ b/sw/qa/core/text/text.cxx @@ -33,6 +33,7 @@ #include <fmtcntnt.hxx> #include <fmtfsize.hxx> #include <IDocumentRedlineAccess.hxx> +#include <formatcontentcontrol.hxx> constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/core/text/data/"; @@ -534,6 +535,38 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testRedlineDelete) pDoc->getIDocumentRedlineAccess().GetRedlineTable().size()); } +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testContentControlPDF) +{ + // Given a file with a content control: + SwDoc* pDoc = createSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::RICH_TEXT); + + // When exporting to PDF: + uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); + xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); + + // Then make sure that a fillable form widget is emitted: + SvFileStream aFile(maTempFile.GetURL(), StreamMode::READ); + SvMemoryStream aMemory; + aMemory.WriteStream(aFile); + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + if (!pPDFium) + { + return; + } + std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument + = pPDFium->openDocument(aMemory.GetData(), aMemory.GetSize(), OString()); + std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // i.e. the content control was just exported as normal text. + CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 50fdf3f6acf5..1828967f8481 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -55,8 +55,10 @@ #include <IMark.hxx> #include <IDocumentMarkAccess.hxx> #include <comphelper/processfactory.hxx> +#include <vcl/pdfextoutdevdata.hxx> #include <docsh.hxx> #include <unocrsrhelper.hxx> +#include <textcontentcontrol.hxx> #include <com/sun/star/rdf/Statement.hpp> #include <com/sun/star/rdf/URI.hpp> #include <com/sun/star/rdf/URIs.hpp> @@ -866,9 +868,13 @@ public: /// A content control portion is a text portion that is inside RES_TXTATR_CONTENTCONTROL. class SwContentControlPortion : public SwTextPortion { + SwTextContentControl* m_pTextContentControl; public: - SwContentControlPortion() { SetWhichPor(PortionType::ContentControl); } + SwContentControlPortion(SwTextContentControl* pTextContentControl); virtual void Paint(const SwTextPaintInfo& rInf) const override; + + /// Emits a PDF form widget for this portion on success, does nothing on failure. + bool DescribePDFControl(const SwTextPaintInfo& rInf) const; }; } @@ -886,11 +892,78 @@ void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const } } +SwContentControlPortion::SwContentControlPortion(SwTextContentControl* pTextContentControl) + : m_pTextContentControl(pTextContentControl) +{ + SetWhichPor(PortionType::ContentControl); +} + +bool SwContentControlPortion::DescribePDFControl(const SwTextPaintInfo& rInf) const +{ + auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(rInf.GetOut()->GetExtOutDevData()); + if (!pPDFExtOutDevData) + { + return false; + } + + if (!pPDFExtOutDevData->GetIsExportFormFields()) + { + return false; + } + + if (!m_pTextContentControl) + { + return false; + } + + const SwFormatContentControl& rFormatContentControl = m_pTextContentControl->GetContentControl(); + const std::shared_ptr<SwContentControl>& pContentControl = rFormatContentControl.GetContentControl(); + if (!pContentControl) + { + return false; + } + + std::unique_ptr<vcl::PDFWriter::AnyWidget> pDescriptor; + switch (pContentControl->GetType()) + { + case SwContentControlType::RICH_TEXT: + case SwContentControlType::PLAIN_TEXT: + pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>(); + break; + default: + break; + } + + if (!pDescriptor) + { + return false; + } + + pDescriptor->Border = true; + pDescriptor->BorderColor = COL_BLACK; + + SwRect aLocation; + rInf.CalcRect(*this, &aLocation); + pDescriptor->Location = aLocation.SVRect(); + + pPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + pPDFExtOutDevData->CreateControl(*pDescriptor); + pPDFExtOutDevData->EndStructureElement(); + + return true; +} + void SwContentControlPortion::Paint(const SwTextPaintInfo& rInf) const { if (Width()) { rInf.DrawViewOpt(*this, PortionType::ContentControl); + + if (DescribePDFControl(rInf)) + { + return; + } + SwTextPortion::Paint(rInf); } } @@ -1013,7 +1086,19 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const } else if (GetFnt()->IsContentControl()) { - pPor = new SwContentControlPortion; + SwTextFrame const*const pFrame(rInf.GetTextFrame()); + SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx())); + SwTextNode* pTextNode = aPosition.nNode.GetNode().GetTextNode(); + SwTextContentControl* pTextContentControl = nullptr; + if (pTextNode) + { + sal_Int32 nIndex = aPosition.nContent.GetIndex(); + if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, SwTextNode::PARENT)) + { + pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + } + } + pPor = new SwContentControlPortion(pTextContentControl); } else { diff --git a/sw/source/core/txtnode/attrcontentcontrol.cxx b/sw/source/core/txtnode/attrcontentcontrol.cxx index 68be93b2464f..39b048fdf59e 100644 --- a/sw/source/core/txtnode/attrcontentcontrol.cxx +++ b/sw/source/core/txtnode/attrcontentcontrol.cxx @@ -328,6 +328,36 @@ bool SwContentControl::ShouldOpenPopup(const vcl::KeyCode& rKeyCode) return false; } +SwContentControlType SwContentControl::GetType() const +{ + if (m_bCheckbox) + { + return SwContentControlType::CHECKBOX; + } + + if (!m_aListItems.empty()) + { + return SwContentControlType::DROP_DOWN_LIST; + } + + if (m_bPicture) + { + return SwContentControlType::PICTURE; + } + + if (m_bDate) + { + return SwContentControlType::DATE; + } + + if (m_bPlainText) + { + return SwContentControlType::PLAIN_TEXT; + } + + return SwContentControlType::RICH_TEXT; +} + void SwContentControl::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwContentControl"));