bin/find-can-be-private-symbols.functions.results | 2 configure.ac | 2 officecfg/registry/schema/org/openoffice/Office/Writer.xcs | 6 sw/inc/doc.hxx | 3 sw/inc/editsh.hxx | 2 sw/qa/core/text/data/placeholder.fodt | 9 sw/qa/core/text/text.cxx | 28 + sw/source/core/doc/doc.cxx | 128 ++++--- sw/source/core/text/itrform2.cxx | 2 sw/source/core/text/porfld.cxx | 69 +++ sw/source/core/text/porfld.hxx | 25 + sw/source/core/text/txtfld.cxx | 226 +++---------- 12 files changed, 271 insertions(+), 231 deletions(-)
New commits: commit 428184ff5a5600877df8d7f80eaad4180f5882a0 Author: Andras Timar <[email protected]> AuthorDate: Mon Nov 6 08:25:39 2023 +0100 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:25:39 2023 +0100 bump product version to 7.5.8.3 Change-Id: I9cfffa9be677600ce95cc06d890067c4aa7c9296 diff --git a/configure.ac b/configure.ac index 1ca9eec124ab..2a09d30b9dd1 100644 --- a/configure.ac +++ b/configure.ac @@ -9,7 +9,7 @@ dnl in order to create a configure script. # several non-alphanumeric characters, those are split off and used only for the # ABOUTBOXPRODUCTVERSIONSUFFIX in openoffice.lst. Why that is necessary, no idea. -AC_INIT([LibreOffice],[7.5.8.2],[],[],[http://documentfoundation.org/]) +AC_INIT([LibreOffice],[7.5.8.3],[],[],[http://documentfoundation.org/]) dnl libnumbertext needs autoconf 2.68, but that can pick up autoconf268 just fine if it is installed dnl whereas aclocal (as run by autogen.sh) insists on using autoconf and fails hard commit 5344feee54709efbef80f1c62b6cd9578773abf3 Author: Mike Kaganski <[email protected]> AuthorDate: Tue Oct 24 23:20:30 2023 +0300 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:24:48 2023 +0100 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 <[email protected]> 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"><placeholder text></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 0ddc9a0885d8..4ac2431e9599 100644 --- a/sw/qa/core/text/text.cxx +++ b/sw/qa/core/text/text.cxx @@ -899,6 +899,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 dbf5765ef1b6..71026928c5b9 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1163,7 +1163,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 ad1abc4c7ca9..ba6b3f3071e6 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() ) @@ -440,7 +444,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 ); @@ -1382,4 +1386,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->BeginStructureElement(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 8c2daa19ecfd..6dd272d131ff 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; commit 2a8123d7fda41772f628af51652da3d53c5e76be Author: Mike Kaganski <[email protected]> AuthorDate: Wed Oct 25 12:34:51 2023 +0200 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:24:36 2023 +0100 Simplify SwTextFormatter::NewFieldPortion Change-Id: I7e5cbce45bba4550f572606f44300b914ff5a41c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158433 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx index a07da12c6bf5..8c2daa19ecfd 100644 --- a/sw/source/core/text/txtfld.cxx +++ b/sw/source/core/text/txtfld.cxx @@ -69,17 +69,24 @@ static bool lcl_IsInBody( SwFrame const *pFrame ) return pFrame->IsInDocBody(); } +static OUString ExpandField(const SwField& rField, const SwTextFormatter& rFormatter, + const SwTextFormatInfo& rInf) +{ + if (rInf.GetOpt().IsFieldName()) + return rField.GetFieldName(); + + const SwViewShell* pSh = rInf.GetVsh(); + const SwDoc* pDoc(pSh ? pSh->GetDoc() : nullptr); + const bool bInClipboard(!pDoc || pDoc->IsClipBoard()); + return rField.ExpandField(bInClipboard, rFormatter.GetTextFrame()->getRootFrame()); +} + SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, const SwTextAttr *pHint ) const { - SwExpandPortion *pRet = nullptr; - SwFrame *pFrame = m_pFrame; SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField()); const bool bName = rInf.GetOpt().IsFieldName(); - SwCharFormat* pChFormat = nullptr; - bool bNewFlyPor = false; - // set language const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf ); if (pField->GetLanguage() != GetFnt()->GetLanguage()) @@ -91,191 +98,100 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, } SwViewShell *pSh = rInf.GetVsh(); - SwDoc *const pDoc( pSh ? pSh->GetDoc() : nullptr ); - bool const bInClipboard( pDoc == nullptr || pDoc->IsClipBoard() ); - bool bPlaceHolder = false; - switch( pField->GetTyp()->Which() ) + switch (pField->GetTyp()->Which()) { case SwFieldIds::Script: case SwFieldIds::Postit: - pRet = new SwPostItsPortion( SwFieldIds::Script == pField->GetTyp()->Which() ); - break; - + return new SwPostItsPortion(SwFieldIds::Script == pField->GetTyp()->Which()); case SwFieldIds::CombinedChars: - { - if( bName ) - pRet = new SwFieldPortion( pField->GetFieldName() ); - else - pRet = new SwCombinedPortion( pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - } + if (!bName) + return new SwCombinedPortion(ExpandField(*pField, *this, rInf)); break; - case SwFieldIds::HiddenText: - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwHiddenPortion(aStr); - } - break; - + return new SwHiddenPortion(ExpandField(*pField, *this, rInf)); case SwFieldIds::Chapter: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - static_cast<SwChapterField*>(pField)->ChangeExpansion(*pFrame, - &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode()); - } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); + static_cast<SwChapterField*>(pField)->ChangeExpansion( + *m_pFrame, &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode()); } break; - case SwFieldIds::DocStat: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - static_cast<SwDocStatField*>(pField)->ChangeExpansion( pFrame ); - } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); + static_cast<SwDocStatField*>(pField)->ChangeExpansion(m_pFrame); } break; - case SwFieldIds::PageNumber: - { - if( !bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields()) { - SwPageNumberFieldType *pPageNr = static_cast<SwPageNumberFieldType *>(pField->GetTyp()); + auto pPageNr = static_cast<SwPageNumberFieldType*>(pField->GetTyp()); const SwRootFrame* pTmpRootFrame = pSh->GetLayout(); const bool bVirt = pTmpRootFrame->IsVirtPageNum(); - sal_uInt16 nVirtNum = pFrame->GetVirtPageNum(); + sal_uInt16 nVirtNum = m_pFrame->GetVirtPageNum(); sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum(); SvxNumType nNumFormat = SvxNumType(-1); - if(SVX_NUM_PAGEDESC == pField->GetFormat()) - nNumFormat = pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType(); - static_cast<SwPageNumberField*>(pField) - ->ChangeExpansion(nVirtNum, nNumPages); - pPageNr->ChangeExpansion(pDoc, - bVirt, nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr); - } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); + if (SVX_NUM_PAGEDESC == pField->GetFormat()) + nNumFormat + = m_pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType(); + static_cast<SwPageNumberField*>(pField)->ChangeExpansion(nVirtNum, nNumPages); + pPageNr->ChangeExpansion(pSh->GetDoc(), bVirt, + nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr); } break; - } case SwFieldIds::GetExp: - { - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - SwGetExpField* pExpField = static_cast<SwGetExpField*>(pField); - if( !::lcl_IsInBody( pFrame ) ) + auto pExpField = static_cast<SwGetExpField*>(pField); + if (!::lcl_IsInBody(m_pFrame)) { - pExpField->ChgBodyTextFlag( false ); - pExpField->ChangeExpansion(*pFrame, - *static_txtattr_cast<SwTextField const*>(pHint)); + pExpField->ChgBodyTextFlag(false); + pExpField->ChangeExpansion(*m_pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); } - else if( !pExpField->IsInBodyText() ) + else if (!pExpField->IsInBodyText()) { // Was something else previously, thus: expand first, then convert it! - pExpField->ChangeExpansion(*pFrame, - *static_txtattr_cast<SwTextField const*>(pHint)); - pExpField->ChgBodyTextFlag( true ); + pExpField->ChangeExpansion(*m_pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); + pExpField->ChgBodyTextFlag(true); } } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); - } break; - } case SwFieldIds::Database: - { - if( !bName ) - { - SwDBField* pDBField = static_cast<SwDBField*>(pField); - pDBField->ChgBodyTextFlag( ::lcl_IsInBody( pFrame ) ); - } + if (!bName) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); + static_cast<SwDBField*>(pField)->ChgBodyTextFlag(::lcl_IsInBody(m_pFrame)); } break; - } case SwFieldIds::RefPageGet: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) - { - static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(*pFrame, - static_txtattr_cast<SwTextField const*>(pHint)); - } + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); + static_cast<SwRefPageGetField*>(pField)->ChangeExpansion( + *m_pFrame, static_txtattr_cast<SwTextField const*>(pHint)); } break; - case SwFieldIds::JumpEdit: - if( !bName ) - pChFormat = static_cast<SwJumpEditField*>(pField)->GetCharFormat(); - bNewFlyPor = true; - bPlaceHolder = true; - break; - case SwFieldIds::GetRef: - { - OUString const str( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(str); - } - break; - case SwFieldIds::DateTime: + { + std::unique_ptr<SwFont> pFont; + if (!bName) { - OUString const str( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(str); + pFont = std::make_unique<SwFont>(*m_pFont); + pFont->SetDiffFnt( + &static_cast<SwJumpEditField*>(pField)->GetCharFormat()->GetAttrSet(), + &m_pFrame->GetDoc().getIDocumentSettingAccess()); } + return new SwFieldPortion(ExpandField(*pField, *this, rInf), std::move(pFont), true); + } + case SwFieldIds::GetRef: break; default: - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); - } - } - - if( bNewFlyPor ) - { - std::unique_ptr<SwFont> pTmpFnt; - if( !bName ) - { - pTmpFnt.reset(new SwFont( *m_pFont )); - pTmpFnt->SetDiffFnt(&pChFormat->GetAttrSet(), &m_pFrame->GetDoc().getIDocumentSettingAccess()); - } - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr, std::move(pTmpFnt), bPlaceHolder); + break; } - - return pRet; + return new SwFieldPortion(ExpandField(*pField, *this, rInf)); } static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix) commit fefceca4054958bec7eb3a0ed52c91ce7739193c Author: Mike Kaganski <[email protected]> AuthorDate: Wed Oct 25 11:51:13 2023 +0200 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:24:26 2023 +0100 Drop unused SwFieldPortion::m_nAttrFieldType Change-Id: Id5858018607c6b0cab0d55a5d247fb39db0978bb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158431 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx index a13781baadf4..ad1abc4c7ca9 100644 --- a/sw/source/core/text/porfld.cxx +++ b/sw/source/core/text/porfld.cxx @@ -81,7 +81,6 @@ SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFont, , m_bAnimated( false), m_bNoPaint( false) , m_bReplace( false), m_bPlaceHolder( bPlaceHold ) , m_bNoLength( false ) - , m_nAttrFieldType(0) { SetWhichPor( PortionType::Field ); } @@ -103,7 +102,6 @@ SwFieldPortion::SwFieldPortion( const SwFieldPortion& rField ) , m_bReplace( rField.m_bReplace ) , m_bPlaceHolder( rField.m_bPlaceHolder ) , m_bNoLength( rField.m_bNoLength ) - , m_nAttrFieldType( rField.m_nAttrFieldType) { if ( rField.HasFont() ) m_pFont.reset( new SwFont( *rField.GetFont() ) ); diff --git a/sw/source/core/text/porfld.hxx b/sw/source/core/text/porfld.hxx index 39a08a5db665..e1c18fc9bd18 100644 --- a/sw/source/core/text/porfld.hxx +++ b/sw/source/core/text/porfld.hxx @@ -63,7 +63,6 @@ public: SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, bool bPlaceHolder = false, TextFrameIndex nLen = TextFrameIndex(1)); virtual ~SwFieldPortion() override; - sal_uInt16 m_nAttrFieldType; void TakeNextOffset( const SwFieldPortion* pField ); void CheckScript( const SwTextSizeInfo &rInf ); bool HasFont() const { return nullptr != m_pFont; } diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx index e2a483be80b0..a07da12c6bf5 100644 --- a/sw/source/core/text/txtfld.cxx +++ b/sw/source/core/text/txtfld.cxx @@ -79,7 +79,6 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, SwCharFormat* pChFormat = nullptr; bool bNewFlyPor = false; - sal_uInt16 subType = 0; // set language const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf ); @@ -146,7 +145,6 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); pRet = new SwFieldPortion( aStr ); } - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGECOUNTFLD; break; case SwFieldIds::PageNumber: @@ -174,7 +172,6 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); pRet = new SwFieldPortion( aStr ); } - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGENUMBERFLD; break; } case SwFieldIds::GetExp: @@ -240,30 +237,20 @@ SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, bPlaceHolder = true; break; case SwFieldIds::GetRef: - subType = static_cast<SwGetRefField*>(pField)->GetSubType(); { OUString const str( bName ? pField->GetFieldName() : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); pRet = new SwFieldPortion(str); } - if( subType == REF_BOOKMARK ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_BOOKMARKFLD; - else if( subType == REF_SETREFATTR ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_SETREFATTRFLD; break; case SwFieldIds::DateTime: - subType = static_cast<SwDateTimeField*>(pField)->GetSubType(); { OUString const str( bName ? pField->GetFieldName() : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); pRet = new SwFieldPortion(str); } - if( subType & DATEFLD ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_DATEFLD; - else if( subType & TIMEFLD ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_TIMEFLD; break; default: { commit b18025b0358f7a376d8e82e6481f7c7d62061a60 Author: Mike Kaganski <[email protected]> AuthorDate: Wed Oct 25 11:47:56 2023 +0200 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:24:11 2023 +0100 Simplify a bit Change-Id: I6b98dd9b383b82c5bcba9235d81b0a9a63a918b2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158430 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx index 5a4e7229334b..e2a483be80b0 100644 --- a/sw/source/core/text/txtfld.cxx +++ b/sw/source/core/text/txtfld.cxx @@ -63,14 +63,10 @@ static bool lcl_IsInBody( SwFrame const *pFrame ) { if ( pFrame->IsInDocBody() ) return true; - else - { - const SwFrame *pTmp = pFrame; - const SwFlyFrame *pFly; - while ( nullptr != (pFly = pTmp->FindFlyFrame()) ) - pTmp = pFly->GetAnchorFrame(); - return pTmp->IsInDocBody(); - } + + while (const SwFlyFrame* pFly = pFrame->FindFlyFrame()) + pFrame = pFly->GetAnchorFrame(); + return pFrame->IsInDocBody(); } SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, commit 2055e2558cb2637a4f966f4904c6683631d842b5 Author: Mike Kaganski <[email protected]> AuthorDate: Sat Oct 21 17:42:56 2023 +0300 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:23:58 2023 +0100 Related: tdf#89178 Add an option to avoid converting some fields into text ... during mail merge. In some modes (generating individual documents; creating PDF) the mail merge process converts all fields into text. But sometimes it is undesirable for fields not involved into mail merge itself: * It is inconsistent with how MS Word behaves; * The generated editable documents could benefit from having other fields kept as fields; * Some fields, when exported to PDF, produce different results: e.g., placeholder fields are output as empty spaces, not as placeholder text. An expert boolean configuration option is added: Office/Writer/FormLetter/ConvertToTextOnlyMMFields; it is false by default, in which case, the behavior is unchanged. When true, all fields in the mail merge document, except for database fields and hidden text fields, are not converted to text during mail merge process. Change-Id: Ibdb505ed3f2762db063bb0a91b674d27ecbc2e7f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158306 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs index c4dec43ad67a..c7eae2a459c4 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs @@ -5984,6 +5984,12 @@ </prop> </group> </group> + <prop oor:name="ConvertToTextOnlyMMFields" oor:type="xs:boolean" oor:nillable="false"> + <info> + <desc>When true, only fields that can be used in mail merge will be converted to text; all other fields will be kept as fields in the Mail Merge output</desc> + </info> + <value>false</value> + </prop> </group> <group oor:name="Misc"> <info> diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx index 8906dc7b90ad..8bd92d6801c1 100644 --- a/sw/inc/doc.hxx +++ b/sw/inc/doc.hxx @@ -1437,6 +1437,7 @@ public: // restore the invisible content if it's available on the undo stack bool RestoreInvisibleContent(); + // Replace fields by text - mailmerge support SAL_DLLPRIVATE bool ConvertFieldsToText(SwRootFrame const& rLayout); // Create sub-documents according to given collection. diff --git a/sw/source/core/doc/doc.cxx b/sw/source/core/doc/doc.cxx index 0c00640cc10c..5ce9fafecd79 100644 --- a/sw/source/core/doc/doc.cxx +++ b/sw/source/core/doc/doc.cxx @@ -53,6 +53,8 @@ #include <editeng/pbinitem.hxx> #include <unotools/localedatawrapper.hxx> +#include <officecfg/Office/Writer.hxx> + #include <swatrset.hxx> #include <swmodule.hxx> #include <fmtrfmrk.hxx> @@ -1577,12 +1579,32 @@ bool SwDoc::RestoreInvisibleContent() return false; } +static bool IsMailMergeField(SwFieldIds fieldId) +{ + switch (fieldId) + { + case SwFieldIds::Database: // Mail merge fields + case SwFieldIds::DatabaseName: // Database name + case SwFieldIds::HiddenText: // Hidden text may use database fields in condition + case SwFieldIds::HiddenPara: // Hidden paragraph may use database fields in condition + case SwFieldIds::DbNextSet: // Moving to next mail merge record + case SwFieldIds::DbNumSet: // Moving to a specific mail merge record + case SwFieldIds::DbSetNumber: // Number of current mail merge record + return true; + default: + return false; + } +} + bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) { bool bRet = false; getIDocumentFieldsAccess().LockExpFields(); GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_REPLACE, nullptr ); + const bool bOnlyConvertDBFields + = officecfg::Office::Writer::FormLetter::ConvertToTextOnlyMMFields::get(); + const SwFieldTypes* pMyFieldTypes = getIDocumentFieldsAccess().GetFieldTypes(); const SwFieldTypes::size_type nCount = pMyFieldTypes->size(); //go backward, field types are removed @@ -1593,6 +1615,9 @@ bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) if ( SwFieldIds::Postit == pCurType->Which() ) continue; + if (bOnlyConvertDBFields && !IsMailMergeField(pCurType->Which())) + continue; + std::vector<SwFormatField*> vFieldFormats; pCurType->GatherFields(vFieldFormats, false); for(const auto& rpFieldFormat : vFieldFormats) @@ -1603,67 +1628,66 @@ bool SwDoc::ConvertFieldsToText(SwRootFrame const& rLayout) bool bSkip = !pTextField || !pTextField->GetpTextNode()->GetNodes().IsDocNodes(); + if (bSkip) + continue; - if (!bSkip) + bool bInHeaderFooter = IsInHeaderFooter(*pTextField->GetpTextNode()); + const SwFormatField& rFormatField = pTextField->GetFormatField(); + const SwField* pField = rFormatField.GetField(); + + //#i55595# some fields have to be excluded in headers/footers + SwFieldIds nWhich = pField->GetTyp()->Which(); + if(!bInHeaderFooter || + (nWhich != SwFieldIds::PageNumber && + nWhich != SwFieldIds::Chapter && + nWhich != SwFieldIds::GetExp&& + nWhich != SwFieldIds::SetExp&& + nWhich != SwFieldIds::Input&& + nWhich != SwFieldIds::RefPageGet&& + nWhich != SwFieldIds::RefPageSet)) { - bool bInHeaderFooter = IsInHeaderFooter(*pTextField->GetpTextNode()); - const SwFormatField& rFormatField = pTextField->GetFormatField(); - const SwField* pField = rFormatField.GetField(); - - //#i55595# some fields have to be excluded in headers/footers - SwFieldIds nWhich = pField->GetTyp()->Which(); - if(!bInHeaderFooter || - (nWhich != SwFieldIds::PageNumber && - nWhich != SwFieldIds::Chapter && - nWhich != SwFieldIds::GetExp&& - nWhich != SwFieldIds::SetExp&& - nWhich != SwFieldIds::Input&& - nWhich != SwFieldIds::RefPageGet&& - nWhich != SwFieldIds::RefPageSet)) - { - OUString sText = pField->ExpandField(true, &rLayout); + OUString sText = pField->ExpandField(true, &rLayout); - // database fields should not convert their command into text - if( SwFieldIds::Database == pCurType->Which() && !static_cast<const SwDBField*>(pField)->IsInitialized()) - sText.clear(); + // database fields should not convert their command into text + if( SwFieldIds::Database == pCurType->Which() && !static_cast<const SwDBField*>(pField)->IsInitialized()) + sText.clear(); - SwPaM aInsertPam(*pTextField->GetpTextNode(), pTextField->GetStart()); - aInsertPam.SetMark(); + SwPaM aInsertPam(*pTextField->GetpTextNode(), pTextField->GetStart()); + aInsertPam.SetMark(); - // go to the end of the field - const SwTextField *pFieldAtEnd = sw::DocumentFieldsManager::GetTextFieldAtPos(*aInsertPam.End()); - if (pFieldAtEnd && pFieldAtEnd->Which() == RES_TXTATR_INPUTFIELD) - { - SwPosition &rEndPos = *aInsertPam.GetPoint(); - rEndPos.SetContent( SwCursorShell::EndOfInputFieldAtPos( *aInsertPam.End() ) ); - } - else - { - aInsertPam.Move(); - } - - // first insert the text after field to keep the field's attributes, - // then delete the field - if (!sText.isEmpty()) - { - // to keep the position after insert - SwPaM aDelPam( *aInsertPam.GetMark(), *aInsertPam.GetPoint() ); - aDelPam.Move( fnMoveBackward ); - aInsertPam.DeleteMark(); + // go to the end of the field + const SwTextField *pFieldAtEnd = sw::DocumentFieldsManager::GetTextFieldAtPos(*aInsertPam.End()); + if (pFieldAtEnd && pFieldAtEnd->Which() == RES_TXTATR_INPUTFIELD) + { + SwPosition &rEndPos = *aInsertPam.GetPoint(); + rEndPos.SetContent( SwCursorShell::EndOfInputFieldAtPos( *aInsertPam.End() ) ); + } + else + { + aInsertPam.Move(); + } - getIDocumentContentOperations().InsertString( aInsertPam, sText ); + // first insert the text after field to keep the field's attributes, + // then delete the field + if (!sText.isEmpty()) + { + // to keep the position after insert + SwPaM aDelPam( *aInsertPam.GetMark(), *aInsertPam.GetPoint() ); + aDelPam.Move( fnMoveBackward ); + aInsertPam.DeleteMark(); - aDelPam.Move(); - // finally remove the field - getIDocumentContentOperations().DeleteAndJoin( aDelPam ); - } - else - { - getIDocumentContentOperations().DeleteAndJoin( aInsertPam ); - } + getIDocumentContentOperations().InsertString( aInsertPam, sText ); - bRet = true; + aDelPam.Move(); + // finally remove the field + getIDocumentContentOperations().DeleteAndJoin( aDelPam ); } + else + { + getIDocumentContentOperations().DeleteAndJoin( aInsertPam ); + } + + bRet = true; } } } commit 9324d766f18a45ca4079c827015ff4f57b7f5ced Author: Mike Kaganski <[email protected]> AuthorDate: Sat Oct 21 14:28:27 2023 +0300 Commit: Andras Timar <[email protected]> CommitDate: Mon Nov 6 08:23:39 2023 +0100 Make ConvertFieldsToText private Change-Id: Ie3d22b63260d2b2b05a46c5b2a6ca915cf9d4bcd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158304 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/bin/find-can-be-private-symbols.functions.results b/bin/find-can-be-private-symbols.functions.results index d147531663df..f4f89fd05433 100644 --- a/bin/find-can-be-private-symbols.functions.results +++ b/bin/find-can-be-private-symbols.functions.results @@ -11940,7 +11940,6 @@ SwDoc::CloneSdrObj(SdrObject const&, bool, bool) SwDoc::CompareDoc(SwDoc const&) SwDoc::ContainsHiddenChars() const SwDoc::ContainsPageDesc(SwPageDesc const*, unsigned long*) const -SwDoc::ConvertFieldsToText(SwRootFrame const&) SwDoc::CopyCharFormat(SwCharFormat const&) SwDoc::CopyFormat(SwFormat const&, SwFormatsBase const&, SwFormat* (SwDoc::*)(rtl::OUString const&, SwFormat*, bool, bool), SwFormat const&) SwDoc::CopyFormatArr(SwFormatsBase const&, SwFormatsBase const&, SwFormat* (SwDoc::*)(rtl::OUString const&, SwFormat*, bool, bool), SwFormat&) @@ -12355,7 +12354,6 @@ SwEditShell::ClearAutomaticContour() SwEditShell::CollectAdvancedClassification() SwEditShell::CollectParagraphClassification() SwEditShell::CompareDoc(SwDoc const&) -SwEditShell::ConvertFieldsToText() SwEditShell::Copy(SwEditShell&) SwEditShell::CopySelToDoc(SwDoc&) SwEditShell::CreateExtTextInput(o3tl::strong_int<unsigned short, LanguageTypeTag>) diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx index e62bc4ba1f37..8906dc7b90ad 100644 --- a/sw/inc/doc.hxx +++ b/sw/inc/doc.hxx @@ -1437,7 +1437,7 @@ public: // restore the invisible content if it's available on the undo stack bool RestoreInvisibleContent(); - bool ConvertFieldsToText(SwRootFrame const& rLayout); + SAL_DLLPRIVATE bool ConvertFieldsToText(SwRootFrame const& rLayout); // Create sub-documents according to given collection. // If no collection is given, use chapter styles for 1st level. diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index 663a0ffc35f7..217e0203c59f 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -185,7 +185,7 @@ public: void CountWords( SwDocStat& rStat ) const; /// Replace fields by text - mailmerge support. - bool ConvertFieldsToText(); + SAL_DLLPRIVATE bool ConvertFieldsToText(); /// Set all numbering start points to a fixed value - mailmerge support. void SetNumberingRestart();
