sw/qa/core/text/data/placeholder.fodt |    9 ++++
 sw/qa/core/text/text.cxx              |   28 +++++++++++++
 sw/source/core/text/itrform2.cxx      |    2 
 sw/source/core/text/porfld.cxx        |   69 +++++++++++++++++++++++++++++++---
 sw/source/core/text/porfld.hxx        |   24 ++++++++++-
 sw/source/core/text/txtfld.cxx        |    3 -
 6 files changed, 125 insertions(+), 10 deletions(-)

New commits:
commit 696f664b3e901077d62d0dc6fd1878d7ea29821a
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Tue Oct 24 23:20:30 2023 +0300
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Fri Oct 27 11:32:14 2023 +0200

    Export text placeholder fields as PDF form fields
    
    Inspired by commit 82d90529dc2b3cb8359dec78852cbd910a66d275
    (sw content controls, rich text: add initial PDF export, 2022-09-12).
    
    Change-Id: I16cc45b6f2e070ab9dc83ba15e3c66ca0caa5e53
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158407
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158480
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>

diff --git a/sw/qa/core/text/data/placeholder.fodt 
b/sw/qa/core/text/data/placeholder.fodt
new file mode 100644
index 000000000000..01cb60437618
--- /dev/null
+++ b/sw/qa/core/text/data/placeholder.fodt
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:body>
+  <office:text>
+   <text:p><text:placeholder text:placeholder-type="text" 
text:description="reference text">&lt;placeholder 
text&gt;</text:placeholder></text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx
index 790f2f32415f..c68a0fa99cbe 100644
--- a/sw/qa/core/text/text.cxx
+++ b/sw/qa/core/text/text.cxx
@@ -1174,6 +1174,34 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, 
testRichContentControlPDF)
     CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
 }
 
+CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testPlaceholderFieldPDF)
+{
+    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
+    if (!pPDFium)
+        return;
+
+    // Given a file with a text-type placeholder field:
+    createSwDoc("placeholder.fodt");
+
+    // When exporting to PDF (default setting is "create a PDF form"):
+    save("writer_pdf_Export");
+
+    // Then make sure that a fillable form widget is emitted:
+    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+    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 placeholder field was just exported as normal text.
+    CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
+    std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = 
pPage->getAnnotation(0);
+    CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, 
pAnnotation->getSubType());
+
+    // Also verify that the widget description is correct:
+    CPPUNIT_ASSERT_EQUAL(OUString("reference text"),
+                         
pAnnotation->getFormFieldAlternateName(pPdfDocument.get()));
+}
+
 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testNumberPortionFormat)
 {
     // Given a document with a single paragraph, direct formatting asks 24pt 
font size for the
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index 51c2d7a9423f..8d52962bb7f7 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1182,7 +1182,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( 
SwTextFormatInfo &rInf ) const
                 }
             }
             assert(2 <= sal_Int32(nFieldLen));
-            pPor = new SwFieldPortion(aFieldName, nullptr, false, nFieldLen);
+            pPor = new SwFieldPortion(aFieldName, nullptr, nFieldLen);
         }
         else
         {
diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx
index 66f39644ffed..580b4a2635a7 100644
--- a/sw/source/core/text/porfld.cxx
+++ b/sw/source/core/text/porfld.cxx
@@ -22,10 +22,14 @@
 #include <com/sun/star/i18n/ScriptType.hpp>
 #include <com/sun/star/i18n/XBreakIterator.hpp>
 #include <utility>
+
+#include <comphelper/string.hxx>
 #include <vcl/graph.hxx>
 #include <editeng/brushitem.hxx>
 #include <vcl/metric.hxx>
 #include <vcl/outdev.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/pdfwriter.hxx>
 #include <viewopt.hxx>
 #include <SwPortionHandler.hxx>
 #include "porlay.hxx"
@@ -44,6 +48,7 @@
 #include <editeng/lrspitem.hxx>
 #include <unicode/ubidi.h>
 #include <bookmark.hxx>
+#include <docufld.hxx>
 
 using namespace ::com::sun::star;
 
@@ -59,7 +64,7 @@ SwFieldPortion *SwFieldPortion::Clone( const OUString 
&rExpand ) const
     }
     // #i107143#
     // pass placeholder property to created <SwFieldPortion> instance.
-    SwFieldPortion* pClone = new SwFieldPortion( rExpand, std::move(pNewFnt), 
m_bPlaceHolder );
+    SwFieldPortion* pClone = new SwFieldPortion(rExpand, std::move(pNewFnt));
     pClone->SetNextOffset( m_nNextOffset );
     pClone->m_bNoLength = m_bNoLength;
     return pClone;
@@ -73,13 +78,13 @@ void SwFieldPortion::TakeNextOffset( const SwFieldPortion* 
pField )
     m_bFollow = true;
 }
 
-SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> 
pFont, bool bPlaceHold, TextFrameIndex const nFieldLen)
+SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> 
pFont, TextFrameIndex const nFieldLen)
     : m_aExpand(std::move(aExpand)), m_pFont(std::move(pFont)), 
m_nNextOffset(0)
     , m_nNextScriptChg(COMPLETE_STRING), m_nFieldLen(nFieldLen), 
m_nViewWidth(0)
     , m_bFollow( false ), m_bLeft( false), m_bHide( false)
     , m_bCenter (false), m_bHasFollow( false )
     , m_bAnimated( false), m_bNoPaint( false)
-    , m_bReplace( false), m_bPlaceHolder( bPlaceHold )
+    , m_bReplace(false)
     , m_bNoLength( false )
 {
     SetWhichPor( PortionType::Field );
@@ -100,7 +105,6 @@ SwFieldPortion::SwFieldPortion( const SwFieldPortion& 
rField )
     , m_bAnimated ( rField.m_bAnimated )
     , m_bNoPaint( rField.m_bNoPaint)
     , m_bReplace( rField.m_bReplace )
-    , m_bPlaceHolder( rField.m_bPlaceHolder )
     , m_bNoLength( rField.m_bNoLength )
 {
     if ( rField.HasFont() )
@@ -458,7 +462,7 @@ void SwFieldPortion::Paint( const SwTextPaintInfo &rInf ) 
const
     SwFontSave aSave( rInf, m_pFont.get() );
 
 //    OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: 
rest-portion pollution?");
-    if( Width() && ( !m_bPlaceHolder || 
rInf.GetOpt().IsShowPlaceHolderFields() ) && !m_bContentControl )
+    if (Width() && !m_bContentControl)
     {
         // A very liberal use of the background
         rInf.DrawViewOpt( *this, PortionType::Field );
@@ -536,7 +540,7 @@ SwNumberPortion::SwNumberPortion( const OUString &rExpand,
                                   const bool bCntr,
                                   const sal_uInt16 nMinDst,
                                   const bool 
bLabelAlignmentPosAndSpaceModeActive )
-    : SwFieldPortion(rExpand, std::move(pFont), false, TextFrameIndex(0))
+    : SwFieldPortion(rExpand, std::move(pFont), TextFrameIndex(0))
     , m_nFixWidth(0)
     , m_nMinDist(nMinDst)
     , 
mbLabelAlignmentPosAndSpaceModeActive(bLabelAlignmentPosAndSpaceModeActive)
@@ -1392,4 +1396,57 @@ void SwFieldFormDatePortion::Paint( const 
SwTextPaintInfo &rInf ) const
     }
 }
 
+SwFieldPortion* SwJumpFieldPortion::Clone(const OUString& rExpand) const
+{
+    auto pRet = new SwJumpFieldPortion(*this);
+    pRet->m_aExpand = rExpand;
+    return pRet;
+}
+
+bool SwJumpFieldPortion::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_nFormat != SwJumpEditFormat::JE_FMT_TEXT)
+        return false;
+
+    vcl::PDFWriter::EditWidget aDescriptor;
+
+    aDescriptor.Border = true;
+    aDescriptor.BorderColor = COL_BLACK;
+
+    SwRect aLocation;
+    rInf.CalcRect(*this, &aLocation);
+    aDescriptor.Location = aLocation.SVRect();
+
+    // Map the text of the field to the descriptor's text.
+    static sal_Unicode constexpr aForbidden[] = { CH_TXTATR_BREAKWORD, 0 };
+    aDescriptor.Text = comphelper::string::removeAny(GetExp(), aForbidden);
+
+    // Description for accessibility purposes.
+    if (!m_sHelp.isEmpty())
+        aDescriptor.Description = m_sHelp;
+
+    pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form);
+    pPDFExtOutDevData->CreateControl(aDescriptor);
+    pPDFExtOutDevData->EndStructureElement();
+
+    return true;
+}
+
+void SwJumpFieldPortion::Paint(const SwTextPaintInfo& rInf) const
+{
+    if (Width() && DescribePDFControl(rInf))
+        return;
+
+    if (rInf.GetOpt().IsShowPlaceHolderFields())
+        SwFieldPortion::Paint(rInf);
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porfld.hxx b/sw/source/core/text/porfld.hxx
index e1c18fc9bd18..b92372942425 100644
--- a/sw/source/core/text/porfld.hxx
+++ b/sw/source/core/text/porfld.hxx
@@ -50,7 +50,6 @@ protected:
     bool m_bAnimated : 1;         // Used by SwGrfNumPortion
     bool m_bNoPaint : 1;          // Used by SwGrfNumPortion
     bool m_bReplace : 1;          // Used by SwGrfNumPortion
-    const bool m_bPlaceHolder : 1;
     bool m_bNoLength : 1;       // HACK for meta suffix (no CH_TXTATR)
     bool m_bContentControl = false;
 
@@ -60,7 +59,7 @@ protected:
 
 public:
     SwFieldPortion( const SwFieldPortion& rField );
-    SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, 
bool bPlaceHolder = false, TextFrameIndex nLen = TextFrameIndex(1));
+    SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, 
TextFrameIndex nLen = TextFrameIndex(1));
     virtual ~SwFieldPortion() override;
 
     void TakeNextOffset( const SwFieldPortion* pField );
@@ -262,4 +261,25 @@ private:
     bool m_bStart;
 };
 
+class SwJumpFieldPortion final : public SwFieldPortion
+{
+public:
+    explicit SwJumpFieldPortion(OUString aExpand, OUString aHelp, 
std::unique_ptr<SwFont> pFont,
+                                sal_uInt32 nFormat)
+        : SwFieldPortion(std::move(aExpand), std::move(pFont))
+        , m_nFormat(nFormat)
+        , m_sHelp(std::move(aHelp))
+    {
+    }
+    virtual SwFieldPortion* Clone(const OUString& rExpand) const override;
+
+    virtual void Paint(const SwTextPaintInfo& rInf) const override;
+
+private:
+    sal_uInt32 m_nFormat; // SwJumpEditFormat from SwField::GetFormat()
+    OUString m_sHelp;
+
+    bool DescribePDFControl(const SwTextPaintInfo& rInf) const;
+};
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx
index 7ca9b9e34fd6..7de5583cd1a9 100644
--- a/sw/source/core/text/txtfld.cxx
+++ b/sw/source/core/text/txtfld.cxx
@@ -184,7 +184,8 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( 
SwTextFormatInfo &rInf,
                     
&static_cast<SwJumpEditField*>(pField)->GetCharFormat()->GetAttrSet(),
                     &m_pFrame->GetDoc().getIDocumentSettingAccess());
             }
-            return new SwFieldPortion(ExpandField(*pField, *this, rInf), 
std::move(pFont), true);
+            return new SwJumpFieldPortion(ExpandField(*pField, *this, rInf), 
pField->GetPar2(),
+                                          std::move(pFont), 
pField->GetFormat());
         }
         case SwFieldIds::GetRef:
             break;

Reply via email to