summaryrefslogtreecommitdiffstats
path: root/sw/source/core/text
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/text')
-rw-r--r--sw/source/core/text/EnhancedPDFExportHelper.cxx3061
-rw-r--r--sw/source/core/text/SwGrammarMarkUp.cxx145
-rw-r--r--sw/source/core/text/atrhndl.hxx117
-rw-r--r--sw/source/core/text/atrstck.cxx851
-rw-r--r--sw/source/core/text/frmcrsr.cxx1691
-rw-r--r--sw/source/core/text/frmform.cxx2299
-rw-r--r--sw/source/core/text/frminf.cxx291
-rw-r--r--sw/source/core/text/frmpaint.cxx810
-rw-r--r--sw/source/core/text/guess.cxx761
-rw-r--r--sw/source/core/text/guess.hxx65
-rw-r--r--sw/source/core/text/inftxt.cxx2150
-rw-r--r--sw/source/core/text/inftxt.hxx811
-rw-r--r--sw/source/core/text/itradj.cxx852
-rw-r--r--sw/source/core/text/itratr.cxx1632
-rw-r--r--sw/source/core/text/itratr.hxx112
-rw-r--r--sw/source/core/text/itrcrsr.cxx2023
-rw-r--r--sw/source/core/text/itrform2.cxx3321
-rw-r--r--sw/source/core/text/itrform2.hxx245
-rw-r--r--sw/source/core/text/itrpaint.cxx807
-rw-r--r--sw/source/core/text/itrpaint.hxx71
-rw-r--r--sw/source/core/text/itrtxt.cxx465
-rw-r--r--sw/source/core/text/itrtxt.hxx341
-rw-r--r--sw/source/core/text/noteurl.cxx25
-rw-r--r--sw/source/core/text/pordrop.hxx107
-rw-r--r--sw/source/core/text/porexp.cxx317
-rw-r--r--sw/source/core/text/porexp.hxx78
-rw-r--r--sw/source/core/text/porfld.cxx1462
-rw-r--r--sw/source/core/text/porfld.hxx285
-rw-r--r--sw/source/core/text/porfly.cxx423
-rw-r--r--sw/source/core/text/porfly.hxx95
-rw-r--r--sw/source/core/text/porftn.hxx104
-rw-r--r--sw/source/core/text/porglue.cxx285
-rw-r--r--sw/source/core/text/porglue.hxx93
-rw-r--r--sw/source/core/text/porhyph.hxx88
-rw-r--r--sw/source/core/text/porlay.cxx2996
-rw-r--r--sw/source/core/text/porlay.hxx352
-rw-r--r--sw/source/core/text/porlin.cxx358
-rw-r--r--sw/source/core/text/porlin.hxx220
-rw-r--r--sw/source/core/text/pormulti.cxx2635
-rw-r--r--sw/source/core/text/pormulti.hxx256
-rw-r--r--sw/source/core/text/porref.cxx75
-rw-r--r--sw/source/core/text/porref.hxx45
-rw-r--r--sw/source/core/text/porrst.cxx949
-rw-r--r--sw/source/core/text/porrst.hxx222
-rw-r--r--sw/source/core/text/portab.hxx111
-rw-r--r--sw/source/core/text/portox.cxx77
-rw-r--r--sw/source/core/text/portox.hxx46
-rw-r--r--sw/source/core/text/portxt.cxx925
-rw-r--r--sw/source/core/text/portxt.hxx104
-rw-r--r--sw/source/core/text/possiz.hxx72
-rw-r--r--sw/source/core/text/redlnitr.cxx1201
-rw-r--r--sw/source/core/text/redlnitr.hxx135
-rw-r--r--sw/source/core/text/txtcache.cxx193
-rw-r--r--sw/source/core/text/txtcache.hxx65
-rw-r--r--sw/source/core/text/txtdrop.cxx1093
-rw-r--r--sw/source/core/text/txtfld.cxx715
-rw-r--r--sw/source/core/text/txtfly.cxx1493
-rw-r--r--sw/source/core/text/txtfrm.cxx4219
-rw-r--r--sw/source/core/text/txtftn.cxx1590
-rw-r--r--sw/source/core/text/txthyph.cxx582
-rw-r--r--sw/source/core/text/txtinit.cxx60
-rw-r--r--sw/source/core/text/txtpaint.cxx113
-rw-r--r--sw/source/core/text/txtpaint.hxx126
-rw-r--r--sw/source/core/text/txttab.cxx663
-rw-r--r--sw/source/core/text/widorp.cxx673
-rw-r--r--sw/source/core/text/widorp.hxx88
-rw-r--r--sw/source/core/text/wrong.cxx938
-rw-r--r--sw/source/core/text/xmldump.cxx203
68 files changed, 49876 insertions, 0 deletions
diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx
new file mode 100644
index 0000000000..499dcc2417
--- /dev/null
+++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx
@@ -0,0 +1,3061 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <EnhancedPDFExportHelper.hxx>
+
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <hintids.hxx>
+
+#include <sot/exchange.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <tools/multisel.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/langitem.hxx>
+#include <tools/urlobj.hxx>
+#include <svl/languageoptions.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <swatrset.hxx>
+#include <frmatr.hxx>
+#include <paratr.hxx>
+#include <ndtxt.hxx>
+#include <ndole.hxx>
+#include <section.hxx>
+#include <tox.hxx>
+#include <fmtfld.hxx>
+#include <txtinet.hxx>
+#include <fmtinfmt.hxx>
+#include <fchrfmt.hxx>
+#include <charfmt.hxx>
+#include <fmtanchr.hxx>
+#include <fmturl.hxx>
+#include <editsh.hxx>
+#include <viscrs.hxx>
+#include <txtfld.hxx>
+#include <reffld.hxx>
+#include <doc.hxx>
+#include <IDocumentOutlineNodes.hxx>
+#include <mdiexp.hxx>
+#include <docufld.hxx>
+#include <ftnidx.hxx>
+#include <txtftn.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <txtfrm.hxx>
+#include <tabfrm.hxx>
+#include <rowfrm.hxx>
+#include <cellfrm.hxx>
+#include <sectfrm.hxx>
+#include <ftnfrm.hxx>
+#include <flyfrm.hxx>
+#include <notxtfrm.hxx>
+#include "porfld.hxx"
+#include "pormulti.hxx"
+#include <SwStyleNameMapper.hxx>
+#include "itrpaint.hxx"
+#include <i18nlangtag/languagetag.hxx>
+#include <IMark.hxx>
+#include <printdata.hxx>
+#include <vprint.hxx>
+#include <SwNodeNum.hxx>
+#include <calbck.hxx>
+#include <frmtool.hxx>
+#include <strings.hrc>
+#include <frameformats.hxx>
+#include <tblafmt.hxx>
+#include <authfld.hxx>
+#include <dcontact.hxx>
+
+#include <tools/globname.hxx>
+#include <svx/svdobj.hxx>
+
+#include <stack>
+#include <map>
+#include <set>
+#include <optional>
+
+using namespace ::com::sun::star;
+
+#if OSL_DEBUG_LEVEL > 1
+
+static std::vector< sal_uInt16 > aStructStack;
+
+void lcl_DBGCheckStack()
+{
+ /* NonStructElement = 0 Document = 1 Part = 2
+ * Article = 3 Section = 4 Division = 5
+ * BlockQuote = 6 Caption = 7 TOC = 8
+ * TOCI = 9 Index = 10 Paragraph = 11
+ * Heading = 12 H1-6 = 13 - 18 List = 19
+ * ListItem = 20 LILabel = 21 LIBody = 22
+ * Table = 23 TableRow = 24 TableHeader = 25
+ * TableData = 26 Span = 27 Quote = 28
+ * Note = 29 Reference = 30 BibEntry = 31
+ * Code = 32 Link = 33 Figure = 34
+ * Formula = 35 Form = 36 Continued frame = 99
+ */
+
+ sal_uInt16 nElement;
+ for ( const auto& rItem : aStructStack )
+ {
+ nElement = rItem;
+ }
+ (void)nElement;
+};
+
+#endif
+
+typedef std::set< tools::Long, lt_TableColumn > TableColumnsMapEntry;
+typedef std::pair< SwRect, sal_Int32 > IdMapEntry;
+typedef std::vector< IdMapEntry > LinkIdMap;
+typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap;
+typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListIdMap;
+typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListBodyIdMap;
+typedef std::set<const void*> FrameTagSet;
+
+struct SwEnhancedPDFState
+{
+ TableColumnsMap m_TableColumnsMap;
+ LinkIdMap m_LinkIdMap;
+ NumListIdMap m_NumListIdMap;
+ NumListBodyIdMap m_NumListBodyIdMap;
+ FrameTagSet m_FrameTagSet;
+
+ LanguageType m_eLanguageDefault;
+
+ struct Span
+ {
+ FontLineStyle eUnderline;
+ FontLineStyle eOverline;
+ FontStrikeout eStrikeout;
+ FontEmphasisMark eFontEmphasis;
+ short nEscapement;
+ SwFontScript nScript;
+ LanguageType nLang;
+ OUString StyleName;
+ };
+
+ ::std::optional<Span> m_oCurrentSpan;
+ ::std::optional<SwTextAttr const*> m_oCurrentLink;
+
+ SwEnhancedPDFState(LanguageType const eLanguageDefault)
+ : m_eLanguageDefault(eLanguageDefault)
+ {
+ }
+};
+
+namespace
+{
+// ODF Style Names:
+const char aTableHeadingName[] = "Table Heading";
+const char aQuotations[] = "Quotations";
+const char aCaption[] = "Caption";
+const char aHeading[] = "Heading";
+const char aQuotation[] = "Quotation";
+const char aSourceText[] = "Source Text";
+
+// PDF Tag Names:
+constexpr OUStringLiteral aDocumentString = u"Document";
+constexpr OUString aDivString = u"Div"_ustr;
+constexpr OUStringLiteral aSectString = u"Sect";
+constexpr OUStringLiteral aHString = u"H";
+constexpr OUStringLiteral aH1String = u"H1";
+constexpr OUStringLiteral aH2String = u"H2";
+constexpr OUStringLiteral aH3String = u"H3";
+constexpr OUStringLiteral aH4String = u"H4";
+constexpr OUStringLiteral aH5String = u"H5";
+constexpr OUStringLiteral aH6String = u"H6";
+constexpr OUStringLiteral aH7String = u"H7";
+constexpr OUStringLiteral aH8String = u"H8";
+constexpr OUStringLiteral aH9String = u"H9";
+constexpr OUStringLiteral aH10String = u"H10";
+constexpr OUStringLiteral aListString = u"L";
+constexpr OUStringLiteral aListItemString = u"LI";
+constexpr OUStringLiteral aListLabelString = u"Lbl";
+constexpr OUString aListBodyString = u"LBody"_ustr;
+constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote";
+constexpr OUString aCaptionString = u"Caption"_ustr;
+constexpr OUStringLiteral aIndexString = u"Index";
+constexpr OUStringLiteral aTOCString = u"TOC";
+constexpr OUStringLiteral aTOCIString = u"TOCI";
+constexpr OUStringLiteral aTableString = u"Table";
+constexpr OUStringLiteral aTRString = u"TR";
+constexpr OUStringLiteral aTDString = u"TD";
+constexpr OUStringLiteral aTHString = u"TH";
+constexpr OUStringLiteral aBibEntryString = u"BibEntry";
+constexpr OUStringLiteral aQuoteString = u"Quote";
+constexpr OUString aSpanString = u"Span"_ustr;
+constexpr OUStringLiteral aCodeString = u"Code";
+constexpr OUStringLiteral aFigureString = u"Figure";
+constexpr OUStringLiteral aFormulaString = u"Formula";
+constexpr OUString aLinkString = u"Link"_ustr;
+constexpr OUStringLiteral aNoteString = u"Note";
+
+// returns true if first paragraph in cell frame has 'table heading' style
+bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame )
+{
+ bool bRet = false;
+
+ const SwContentFrame *pCnt = rCellFrame.ContainsContent();
+ if ( pCnt && pCnt->IsTextFrame() )
+ {
+ SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps();
+ const SwFormat* pTextFormat = pTextNode->GetFormatColl();
+
+ OUString sStyleName;
+ SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
+ bRet = sStyleName == aTableHeadingName;
+ }
+
+ // tdf#153935 wild guessing for 1st row based on table autoformat
+ if (!bRet && !rCellFrame.GetUpper()->GetPrev())
+ {
+ SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable());
+ assert(pTable);
+ OUString const& rStyleName(pTable->GetTableStyleName());
+ if (!rStyleName.isEmpty())
+ {
+ if (SwTableAutoFormat const*const pTableAF =
+ pTable->GetFrameFormat()->GetDoc()->GetTableStyles().FindAutoFormat(rStyleName))
+ {
+ bRet |= pTableAF->HasHeaderRow();
+ }
+ }
+ }
+
+ return bRet;
+}
+
+// List all frames for which the NonStructElement tag is set:
+bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
+{
+ bool bRet = false;
+
+ if ( nullptr != rFrame.FindFooterOrHeader() &&
+ !rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() )
+ {
+ bRet = true;
+ }
+ else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() )
+ {
+ const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
+ if ( rFrame.GetUpper() != pTabFrame &&
+ pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) )
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+// Generate key from frame for reopening tags:
+void const* lcl_GetKeyFromFrame( const SwFrame& rFrame )
+{
+ void const* pKey = nullptr;
+
+ if ( rFrame.IsPageFrame() )
+ pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()));
+ else if ( rFrame.IsTextFrame() )
+ pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst());
+ else if ( rFrame.IsSctFrame() )
+ pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection());
+ else if ( rFrame.IsTabFrame() )
+ pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable());
+ else if ( rFrame.IsRowFrame() )
+ pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine());
+ else if ( rFrame.IsCellFrame() )
+ {
+ const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
+ const SwTable* pTable = pTabFrame->GetTable();
+ pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable));
+ }
+ else if (rFrame.IsFootnoteFrame())
+ {
+ pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr());
+ }
+
+ return pKey;
+}
+
+bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode)
+{
+ bool bRet = false;
+ SwNodeIndex aIdx( rNode );
+ const SwDoc& rDoc = rNode.GetDoc();
+ const SwNodes& rNodes = rDoc.GetNodes();
+ const SwNode* pNode = &rNode;
+ const SwNumRule* pNumRule = rNode.GetNumRule();
+
+ while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) )
+ {
+ sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame());
+
+ if (aIdx.GetNode().IsTextNode())
+ {
+ const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode(
+ *rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode());
+ const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule();
+
+ // We find the previous text node. Now check, if the previous text node
+ // has the same numrule like rNode:
+ if ( (pPrevNumRule == pNumRule) &&
+ (!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
+ bRet = true;
+
+ break;
+ }
+
+ pNode = &aIdx.GetNode();
+ }
+ return bRet;
+}
+
+bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
+{
+ // 1. Check if the whole paragraph is hidden
+ // 2. Move to the field
+ // 3. Check for hidden text attribute
+ if(rNd.IsHidden())
+ return false;
+ if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
+ {
+ rShell.SwCursorShell::ClearMark();
+ return false;
+ }
+ return true;
+};
+
+// tdf#157816: try to check if the rectangle contains actual text
+::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell)
+{
+ ::std::vector<SwRect> ret;
+ SwRects rects;
+ rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys);
+ for (SwRect const& rRect : rects)
+ {
+ Point center(rRect.Center());
+ SwSpecialPos special;
+ SwCursorMoveState cms(CursorMoveState::NONE);
+ cms.m_pSpecialPos = &special;
+ cms.m_bFieldInfo = true;
+ SwPosition pos(rShell.GetDoc()->GetNodes());
+ auto const [pStart, pEnd] = rShell.GetCursor_()->StartEnd();
+ if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms)
+ && *pStart <= pos && pos <= *pEnd)
+ {
+ SwRect charRect;
+ if (rShell.GetCurrFrame(false)->GetCharRect(charRect, pos, &cms, false)
+ && rRect.Overlaps(charRect))
+ {
+ ret.push_back(rRect);
+ }
+ }
+ // reset stupid static var that may have gotten set now
+ SwTextCursor::SetRightMargin(false); // WTF is this crap
+ }
+ return ret;
+}
+
+} // end namespace
+
+SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo,
+ const Frame_Info* pFrameInfo,
+ const Por_Info* pPorInfo,
+ OutputDevice const & rOut )
+ : m_nEndStructureElement( 0 ),
+ m_nRestoreCurrentTag( -1 ),
+ mpNumInfo( pNumInfo ),
+ mpFrameInfo( pFrameInfo ),
+ mpPorInfo( pPorInfo )
+{
+ mpPDFExtOutDevData =
+ dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
+
+ if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
+ lcl_DBGCheckStack();
+#endif
+ if ( mpNumInfo )
+ BeginNumberedListStructureElements();
+ else if ( mpFrameInfo )
+ BeginBlockStructureElements();
+ else if ( mpPorInfo )
+ BeginInlineStructureElements();
+ else
+ BeginTag( vcl::PDFWriter::NonStructElement, OUString() );
+
+#if OSL_DEBUG_LEVEL > 1
+ nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
+ lcl_DBGCheckStack();
+ (void)nCurrentStruct;
+#endif
+}
+
+SwTaggedPDFHelper::~SwTaggedPDFHelper()
+{
+ if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
+ return;
+
+#if OSL_DEBUG_LEVEL > 1
+ sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
+ lcl_DBGCheckStack();
+#endif
+ EndStructureElements();
+
+#if OSL_DEBUG_LEVEL > 1
+ nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
+ lcl_DBGCheckStack();
+ (void)nCurrentStruct;
+#endif
+}
+
+void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj)
+{
+ SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame());
+ return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr;
+}
+
+bool SwTaggedPDFHelper::CheckReopenTag()
+{
+ bool bRet = false;
+ void const* pReopenKey(nullptr);
+ bool bContinue = false; // in some cases we just have to reopen a tag without early returning
+
+ if ( mpFrameInfo )
+ {
+ const SwFrame& rFrame = mpFrameInfo->mrFrame;
+ const SwFrame* pKeyFrame = nullptr;
+
+ // Reopen an existing structure element if
+ // - rFrame is not the first page frame (reopen Document tag)
+ // - rFrame is a follow frame (reopen Master tag)
+ // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag)
+ // - rFrame is a fly frame anchored at page (reopen Document tag)
+ // - rFrame is a follow flow row (reopen TableRow tag)
+ // - rFrame is a cell frame in a follow flow row (reopen TableData tag)
+ if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
+ ( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
+ (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
+ ( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
+ ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
+ {
+ pKeyFrame = &rFrame;
+ }
+ else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
+ {
+ const SwFormatAnchor& rAnchor =
+ static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor();
+ if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
+ (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
+ (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
+ {
+ pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
+ bContinue = true;
+ }
+ }
+
+ if ( pKeyFrame )
+ {
+ void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
+ FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
+ if (rFrameTagSet.find(pKey) != rFrameTagSet.end()
+ || rFrame.IsFlyFrame()) // for hell layer flys
+ {
+ pReopenKey = pKey;
+ }
+ }
+ }
+
+ if (pReopenKey)
+ {
+ // note: it would be possible to get rid of the SetCurrentStructureElement()
+ // - which is quite ugly - for most cases by recreating the parents until the
+ // current ancestor, but there are special cases cell frame rowspan > 1 follow
+ // and footnote frame follow where the parent of the follow is different from
+ // the parent of the first one, and so in PDFExtOutDevData the wrong parent
+ // would be restored and used for next elements.
+ m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
+ sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
+ mpPDFExtOutDevData->SetCurrentStructureElement(id);
+
+ bRet = true;
+ }
+
+ return bRet && !bContinue;
+}
+
+void SwTaggedPDFHelper::CheckRestoreTag() const
+{
+ if ( m_nRestoreCurrentTag != -1 )
+ {
+ mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag );
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.pop_back();
+#endif
+ }
+}
+
+void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey)
+{
+ sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey);
+ mpPDFExtOutDevData->BeginStructureElement(id);
+ ++m_nEndStructureElement;
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.push_back( 99 );
+#endif
+}
+
+sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey,
+ vcl::PDFWriter::StructElement const eType, const OUString& rString)
+{
+ // write new tag
+ const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey);
+ mpPDFExtOutDevData->InitStructureElement(nId, eType, rString);
+ mpPDFExtOutDevData->BeginStructureElement(nId);
+ ++m_nEndStructureElement;
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) );
+#endif
+
+ return nId;
+}
+
+void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString )
+{
+ void const* pKey(nullptr);
+
+ if (mpFrameInfo)
+ {
+ const SwFrame& rFrame = mpFrameInfo->mrFrame;
+
+ if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
+ ( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
+ rFrame.IsSctFrame() || // all of them, so that opening parent sections works
+ ( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
+ (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
+ ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
+ ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) )
+ {
+ pKey = lcl_GetKeyFromFrame(rFrame);
+
+ if (pKey)
+ {
+ FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
+ assert(rFrameTagSet.find(pKey) == rFrameTagSet.end());
+ rFrameTagSet.emplace(pKey);
+ }
+ }
+ }
+
+ sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
+
+ // Store the id of the current structure element if
+ // - it is a list structure element
+ // - it is a list body element with children
+ // - rFrame is the first page frame
+ // - rFrame is a master frame
+ // - rFrame has objects anchored to it
+ // - rFrame is a row frame or cell frame in a split table row
+
+ if ( mpNumInfo )
+ {
+ const SwTextFrame& rTextFrame = mpNumInfo->mrFrame;
+ SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps();
+ const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
+
+ if ( vcl::PDFWriter::List == eType )
+ {
+ NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
+ rNumListIdMap[ pNodeNum ] = nId;
+ }
+ else if ( vcl::PDFWriter::LIBody == eType )
+ {
+ NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
+ rNumListBodyIdMap[ pNodeNum ] = nId;
+ }
+ }
+
+ SetAttributes( eType );
+}
+
+void SwTaggedPDFHelper::EndTag()
+{
+ mpPDFExtOutDevData->EndStructureElement();
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.pop_back();
+#endif
+}
+
+namespace {
+
+ // link the link annotation to the link structured element
+ void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
+ {
+ const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
+ const Point aCenter = rRect.Center();
+ auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
+ [&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
+ if (aIter != rLinkIdMap.end())
+ {
+ sal_Int32 nLinkId = (*aIter).second;
+ rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, nLinkId);
+ }
+ }
+}
+
+// Sets the attributes according to the structure type.
+void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType )
+{
+ sal_Int32 nVal;
+
+ /*
+ * ATTRIBUTES FOR BLSE
+ */
+ if ( mpFrameInfo )
+ {
+ vcl::PDFWriter::StructAttributeValue eVal;
+ const SwFrame* pFrame = &mpFrameInfo->mrFrame;
+ SwRectFnSet aRectFnSet(pFrame);
+
+ bool bPlacement = false;
+ bool bWritingMode = false;
+ bool bSpaceBefore = false;
+ bool bSpaceAfter = false;
+ bool bStartIndent = false;
+ bool bEndIndent = false;
+ bool bTextIndent = false;
+ bool bTextAlign = false;
+ bool bWidth = false;
+ bool bHeight = false;
+ bool bBox = false;
+ bool bRowSpan = false;
+ bool bAltText = false;
+
+ // Check which attributes to set:
+
+ switch ( eType )
+ {
+ case vcl::PDFWriter::Document :
+ bWritingMode = true;
+ break;
+
+ case vcl::PDFWriter::Note:
+ bPlacement = true;
+ break;
+
+ case vcl::PDFWriter::Table :
+ bPlacement =
+ bWritingMode =
+ bSpaceBefore =
+ bSpaceAfter =
+ bStartIndent =
+ bEndIndent =
+ bWidth =
+ bHeight =
+ bBox = true;
+ break;
+
+ case vcl::PDFWriter::TableRow :
+ bPlacement =
+ bWritingMode = true;
+ break;
+
+ case vcl::PDFWriter::TableHeader :
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column);
+ [[fallthrough]];
+ case vcl::PDFWriter::TableData :
+ bPlacement =
+ bWritingMode =
+ bWidth =
+ bHeight =
+ bRowSpan = true;
+ break;
+
+ case vcl::PDFWriter::Caption:
+ if (pFrame->IsSctFrame())
+ {
+ break;
+ }
+ [[fallthrough]];
+ case vcl::PDFWriter::H1 :
+ case vcl::PDFWriter::H2 :
+ case vcl::PDFWriter::H3 :
+ case vcl::PDFWriter::H4 :
+ case vcl::PDFWriter::H5 :
+ case vcl::PDFWriter::H6 :
+ case vcl::PDFWriter::Paragraph :
+ case vcl::PDFWriter::Heading :
+ case vcl::PDFWriter::BlockQuote :
+
+ bPlacement =
+ bWritingMode =
+ bSpaceBefore =
+ bSpaceAfter =
+ bStartIndent =
+ bEndIndent =
+ bTextIndent =
+ bTextAlign = true;
+ break;
+
+ case vcl::PDFWriter::Formula :
+ case vcl::PDFWriter::Figure :
+ bAltText =
+ bPlacement =
+ bWidth =
+ bHeight =
+ bBox = true;
+ break;
+
+ case vcl::PDFWriter::Division:
+ if (pFrame->IsFlyFrame()) // this can be something else too
+ {
+ bAltText = true;
+ bBox = true;
+ }
+ break;
+
+ case vcl::PDFWriter::NonStructElement:
+ if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
+ {
+ // ISO 14289-1:2014, Clause: 7.8
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination);
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
+ pFrame->IsHeaderFrame()
+ ? vcl::PDFWriter::Header
+ : vcl::PDFWriter::Footer);
+ }
+ break;
+
+ default :
+ break;
+ }
+
+ // Set the attributes:
+
+ if ( bPlacement )
+ {
+ eVal = vcl::PDFWriter::TableHeader == eType ||
+ vcl::PDFWriter::TableData == eType ?
+ vcl::PDFWriter::Inline :
+ vcl::PDFWriter::Block;
+
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal );
+ }
+
+ if ( bWritingMode )
+ {
+ eVal = pFrame->IsVertical() ?
+ vcl::PDFWriter::TbRl :
+ pFrame->IsRightToLeft() ?
+ vcl::PDFWriter::RlTb :
+ vcl::PDFWriter::LrTb;
+
+ if ( vcl::PDFWriter::LrTb != eVal )
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal );
+ }
+
+ if ( bSpaceBefore )
+ {
+ nVal = aRectFnSet.GetTopMargin(*pFrame);
+ if ( 0 != nVal )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal );
+ }
+
+ if ( bSpaceAfter )
+ {
+ nVal = aRectFnSet.GetBottomMargin(*pFrame);
+ if ( 0 != nVal )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal );
+ }
+
+ if ( bStartIndent )
+ {
+ nVal = aRectFnSet.GetLeftMargin(*pFrame);
+ if ( 0 != nVal )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal );
+ }
+
+ if ( bEndIndent )
+ {
+ nVal = aRectFnSet.GetRightMargin(*pFrame);
+ if ( 0 != nVal )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal );
+ }
+
+ if ( bTextIndent )
+ {
+ OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
+ const SvxFirstLineIndentItem& rFirstLine(
+ static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
+ nVal = rFirstLine.GetTextFirstLineOffset();
+ if ( 0 != nVal )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal );
+ }
+
+ if ( bTextAlign )
+ {
+ OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
+ const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet();
+ const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
+ if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust ||
+ ( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) ||
+ (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) )
+ {
+ eVal = SvxAdjust::Block == nAdjust ?
+ vcl::PDFWriter::Justify :
+ SvxAdjust::Center == nAdjust ?
+ vcl::PDFWriter::Center :
+ vcl::PDFWriter::End;
+
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal );
+ }
+ }
+
+ // ISO 14289-1:2014, Clause: 7.3
+ // ISO 14289-1:2014, Clause: 7.7
+ // For images (but not embedded objects), an ObjectInfoPrimitive2D is
+ // created, but it's not evaluated by VclMetafileProcessor2D any more;
+ // that would require producing StructureTagPrimitive2D here but that
+ // looks impossible so instead duplicate the code that sets the Alt
+ // text here again.
+ if (bAltText)
+ {
+ SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
+ OUString const sep(
+ (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
+ ? OUString() : OUString(" - "));
+ OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
+ if (!altText.isEmpty())
+ {
+ mpPDFExtOutDevData->SetAlternateText(altText);
+ }
+ }
+
+ if ( bWidth )
+ {
+ nVal = aRectFnSet.GetWidth(pFrame->getFrameArea());
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal );
+ }
+
+ if ( bHeight )
+ {
+ nVal = aRectFnSet.GetHeight(pFrame->getFrameArea());
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal );
+ }
+
+ if ( bBox )
+ {
+ // BBox only for non-split tables:
+ if ( vcl::PDFWriter::Table != eType ||
+ ( pFrame->IsTabFrame() &&
+ !static_cast<const SwTabFrame*>(pFrame)->IsFollow() &&
+ !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) )
+ {
+ mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect());
+ }
+ }
+
+ if ( bRowSpan )
+ {
+ if ( pFrame->IsCellFrame() )
+ {
+ const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame);
+ nVal = pThisCell->GetTabBox()->getRowSpan();
+ if ( nVal > 1 )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal );
+
+ // calculate colspan:
+ const SwTabFrame* pTabFrame = pThisCell->FindTabFrame();
+ const SwTable* pTable = pTabFrame->GetTable();
+
+ SwRectFnSet fnRectX(pTabFrame);
+
+ const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]);
+
+ const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea());
+ const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea());
+ const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft );
+ const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight );
+
+ OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" );
+ if ( aLeftIter != rCols.end() && aRightIter != rCols.end() )
+ {
+ nVal = std::distance( aLeftIter, aRightIter );
+ if ( nVal > 1 )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal );
+ }
+ }
+ }
+
+ if (mpFrameInfo->m_isLink)
+ {
+ SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea());
+ LinkLinkLink(*mpPDFExtOutDevData, aRect);
+ }
+ }
+
+ /*
+ * ATTRIBUTES FOR ILSE
+ */
+ else if ( mpPorInfo )
+ {
+ const SwLinePortion* pPor = &mpPorInfo->mrPor;
+ const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
+
+ bool bActualText = false;
+ bool bBaselineShift = false;
+ bool bTextDecorationType = false;
+ bool bLinkAttribute = false;
+ bool bLanguage = false;
+
+ // Check which attributes to set:
+
+ switch ( eType )
+ {
+ case vcl::PDFWriter::Span :
+ case vcl::PDFWriter::Quote :
+ case vcl::PDFWriter::Code :
+ if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() ||
+ PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() )
+ bActualText = true;
+ else
+ {
+ bBaselineShift =
+ bTextDecorationType =
+ bLanguage = true;
+ }
+ break;
+
+ case vcl::PDFWriter::Link :
+ bTextDecorationType =
+ bBaselineShift =
+ bLinkAttribute =
+ bLanguage = true;
+ break;
+
+ case vcl::PDFWriter::BibEntry :
+ bTextDecorationType =
+ bBaselineShift =
+ bLinkAttribute =
+ bLanguage = true;
+ break;
+
+ case vcl::PDFWriter::RT:
+ {
+ SwRubyPortion const*const pRuby(static_cast<SwRubyPortion const*>(pPor));
+ vcl::PDFWriter::StructAttributeValue nAlign = {};
+ switch (pRuby->GetAdjustment())
+ {
+ case text::RubyAdjust_LEFT:
+ nAlign = vcl::PDFWriter::RStart;
+ break;
+ case text::RubyAdjust_CENTER:
+ nAlign = vcl::PDFWriter::RCenter;
+ break;
+ case text::RubyAdjust_RIGHT:
+ nAlign = vcl::PDFWriter::REnd;
+ break;
+ case text::RubyAdjust_BLOCK:
+ nAlign = vcl::PDFWriter::RJustify;
+ break;
+ case text::RubyAdjust_INDENT_BLOCK:
+ nAlign = vcl::PDFWriter::RDistribute;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ ::std::optional<vcl::PDFWriter::StructAttributeValue> oPos;
+ switch (pRuby->GetRubyPosition())
+ {
+ case RubyPosition::ABOVE:
+ oPos = vcl::PDFWriter::RBefore;
+ break;
+ case RubyPosition::BELOW:
+ oPos = vcl::PDFWriter::RAfter;
+ break;
+ case RubyPosition::RIGHT:
+ break; // no such thing???
+ }
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyAlign, nAlign);
+ if (oPos)
+ {
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyPosition, *oPos);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if ( bActualText )
+ {
+ OUString aActualText;
+ if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen)
+ aActualText = OUString(u'\x00ad'); // soft hyphen
+ else
+ aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen()));
+ mpPDFExtOutDevData->SetActualText( aActualText );
+ }
+
+ if ( bBaselineShift )
+ {
+ // TODO: Calculate correct values!
+ nVal = rInf.GetFont()->GetEscapement();
+ if ( nVal > 0 ) nVal = 33;
+ else if ( nVal < 0 ) nVal = -33;
+
+ if ( 0 != nVal )
+ {
+ nVal = nVal * pPor->Height() / 100;
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal );
+ }
+ }
+
+ if ( bTextDecorationType )
+ {
+ if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() )
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline );
+ if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() )
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
+ if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() )
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough );
+ if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() )
+ mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
+ }
+
+ if ( bLanguage )
+ {
+
+ const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
+ const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
+
+ if ( nDefaultLang != nCurrentLanguage )
+ mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) );
+ }
+
+ if ( bLinkAttribute )
+ {
+ SwRect aPorRect;
+ rInf.CalcRect( *pPor, &aPorRect );
+ LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
+ }
+ }
+ else if (mpNumInfo && eType == vcl::PDFWriter::List)
+ {
+ SwTextFrame const& rFrame(mpNumInfo->mrFrame);
+ SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps());
+ SwNumRule const*const pNumRule = rNode.GetNumRule();
+ assert(pNumRule); // was required for List
+
+ auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) {
+ switch (rFormat.GetNumberingType())
+ {
+ case css::style::NumberingType::CHARS_UPPER_LETTER:
+ return vcl::PDFWriter::UpperAlpha;
+ case css::style::NumberingType::CHARS_LOWER_LETTER:
+ return vcl::PDFWriter::LowerAlpha;
+ case css::style::NumberingType::ROMAN_UPPER:
+ return vcl::PDFWriter::UpperRoman;
+ case css::style::NumberingType::ROMAN_LOWER:
+ return vcl::PDFWriter::LowerRoman;
+ case css::style::NumberingType::ARABIC:
+ return vcl::PDFWriter::Decimal;
+ case css::style::NumberingType::CHAR_SPECIAL:
+ switch (rFormat.GetBulletChar())
+ {
+ case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437':
+ return vcl::PDFWriter::Disc;
+ case u'\u2218': case u'\u25CB': case u'\u25E6':
+ return vcl::PDFWriter::Circle;
+ case u'\u25A0': case u'\u25AA': case u'\uE00A':
+ return vcl::PDFWriter::Square;
+ default:
+ return vcl::PDFWriter::NONE;
+ }
+ default: // the other 50 types
+ return vcl::PDFWriter::NONE;
+ }
+ };
+
+ // Note: for every level, BeginNumberedListStructureElements() produces
+ // a separate List element, so even though in PDF this is limited to
+ // the whole List we can just export the current level here.
+ vcl::PDFWriter::StructAttributeValue const value(
+ ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
+ // ISO 14289-1:2014, Clause: 7.6
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
+ }
+}
+
+void SwTaggedPDFHelper::BeginNumberedListStructureElements()
+{
+ OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" );
+ if ( !mpNumInfo )
+ return;
+
+ const SwFrame& rFrame = mpNumInfo->mrFrame;
+ assert(rFrame.IsTextFrame());
+ const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame);
+
+ // Lowers of NonStructureElements should not be considered:
+ if (lcl_IsInNonStructEnv(rTextFrame))
+ return;
+
+ // do it for the first one in the follow chain that has content
+ for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
+ {
+ SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede));
+ if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
+ {
+ return;
+ }
+ }
+
+ const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps();
+ const SwNumRule* pNumRule = pTextNd->GetNumRule();
+ const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
+
+ const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule;
+
+ // Check, if we have to reopen a list or a list body:
+ // First condition:
+ // Paragraph is numbered/bulleted
+ if ( !bNumbered )
+ return;
+
+ const SwNumberTreeNode* pParent = pNodeNum->GetParent();
+ const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd);
+
+ // Second condition: current numbering is not 'interrupted'
+ if ( bSameNumbering )
+ {
+ sal_Int32 nReopenTag = -1;
+
+ // Two cases:
+ // 1. We have to reopen an existing list body tag:
+ // - If the current node is either the first child of its parent
+ // and its level > 1 or
+ // - Numbering should restart at the current node and its level > 1
+ // - The current item has no label
+ const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() );
+ const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart();
+ if ( bNewSubListStart || bNoLabel )
+ {
+ // Fine, we try to reopen the appropriate list body
+ NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
+
+ if ( bNewSubListStart )
+ {
+ // The list body tag associated with the parent has to be reopened
+ // to start a new list inside the list body
+ NumListBodyIdMap::const_iterator aIter;
+
+ do
+ aIter = rNumListBodyIdMap.find( pParent );
+ while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );
+
+ if ( aIter != rNumListBodyIdMap.end() )
+ nReopenTag = (*aIter).second;
+ }
+ else // if(bNoLabel)
+ {
+ // The list body tag of a 'counted' predecessor has to be reopened
+ const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
+ while ( pPrevious )
+ {
+ if ( pPrevious->IsCounted())
+ {
+ // get id of list body tag
+ const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious );
+ if ( aIter != rNumListBodyIdMap.end() )
+ {
+ nReopenTag = (*aIter).second;
+ break;
+ }
+ }
+ pPrevious = pPrevious->GetPred(true);
+ }
+ }
+ }
+ // 2. We have to reopen an existing list tag:
+ else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
+ {
+ // any other than the first node in a list level has to reopen the current
+ // list. The current list is associated in a map with the first child of the list:
+ NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
+
+ // Search backwards and check if any of the previous nodes has a list associated with it:
+ const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
+ while ( pPrevious )
+ {
+ // get id of list tag
+ const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious );
+ if ( aIter != rNumListIdMap.end() )
+ {
+ nReopenTag = (*aIter).second;
+ break;
+ }
+
+ pPrevious = pPrevious->GetPred(true);
+ }
+ }
+
+ if ( -1 != nReopenTag )
+ {
+ m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
+ mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag );
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.push_back( 99 );
+#endif
+ }
+ }
+ else
+ {
+ // clear list maps in case a list has been interrupted
+ NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
+ rNumListIdMap.clear();
+ NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
+ rNumListBodyIdMap.clear();
+ }
+
+ // New tags:
+ const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering);
+ const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:
+
+ if ( bNewListTag )
+ BeginTag( vcl::PDFWriter::List, aListString );
+
+ if ( bNewItemTag )
+ {
+ BeginTag( vcl::PDFWriter::ListItem, aListItemString );
+ assert(rTextFrame.GetPara());
+ // check whether to open LBody now or delay until after Lbl
+ if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
+ {
+ BeginTag(vcl::PDFWriter::LIBody, aListBodyString);
+ }
+ }
+}
+
+void SwTaggedPDFHelper::BeginBlockStructureElements()
+{
+ const SwFrame* pFrame = &mpFrameInfo->mrFrame;
+
+ // Lowers of NonStructureElements should not be considered:
+
+ if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame())
+ return;
+
+ // Check if we have to reopen an existing structure element.
+ // This has to be done e.g., if pFrame is a follow frame.
+ if ( CheckReopenTag() )
+ return;
+
+ sal_uInt16 nPDFType = USHRT_MAX;
+ OUString aPDFType;
+
+ switch ( pFrame->GetType() )
+ {
+ /*
+ * GROUPING ELEMENTS
+ */
+
+ case SwFrameType::Page :
+
+ // Document: Document
+
+ nPDFType = vcl::PDFWriter::Document;
+ aPDFType = aDocumentString;
+ break;
+
+ case SwFrameType::Header :
+ case SwFrameType::Footer :
+
+ // Header, Footer: NonStructElement
+
+ nPDFType = vcl::PDFWriter::NonStructElement;
+ break;
+
+ case SwFrameType::FtnCont :
+
+ // Footnote container: Division
+
+ nPDFType = vcl::PDFWriter::Division;
+ aPDFType = aDivString;
+ break;
+
+ case SwFrameType::Ftn :
+
+ // Footnote frame: Note
+
+ // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless
+ // we treat it like a grouping element!
+ nPDFType = vcl::PDFWriter::Note;
+ aPDFType = aNoteString;
+ break;
+
+ case SwFrameType::Section :
+
+ // Section: TOX, Index, or Sect
+
+ {
+ const SwSection* pSection =
+ static_cast<const SwSectionFrame*>(pFrame)->GetSection();
+
+ // open all parent sections, so that the SEs of sections
+ // are nested in the same way as their SwSectionNodes
+ std::vector<SwSection const*> parents;
+ for (SwSection const* pParent = pSection->GetParent();
+ pParent != nullptr; pParent = pParent->GetParent())
+ {
+ parents.push_back(pParent);
+ }
+ for (auto it = parents.rbegin(); it != parents.rend(); ++it)
+ {
+ // key is the SwSection - see lcl_GetKeyFromFrame()
+ OpenTagImpl(*it);
+ }
+
+ FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
+ if (rFrameTagSet.find(pSection) != rFrameTagSet.end())
+ {
+ // special case: section may have *multiple* master frames,
+ // when it is interrupted by nested section - reopen!
+ OpenTagImpl(pSection);
+ break;
+ }
+ else if (SectionType::ToxHeader == pSection->GetType())
+ {
+ nPDFType = vcl::PDFWriter::Caption;
+ aPDFType = aCaptionString;
+ }
+ else if (SectionType::ToxContent == pSection->GetType())
+ {
+ const SwTOXBase* pTOXBase = pSection->GetTOXBase();
+ if ( pTOXBase )
+ {
+ if ( TOX_INDEX == pTOXBase->GetType() )
+ {
+ nPDFType = vcl::PDFWriter::Index;
+ aPDFType = aIndexString;
+ }
+ else
+ {
+ nPDFType = vcl::PDFWriter::TOC;
+ aPDFType = aTOCString;
+ }
+ }
+ }
+ else if ( SectionType::Content == pSection->GetType() )
+ {
+ nPDFType = vcl::PDFWriter::Section;
+ aPDFType = aSectString;
+ }
+ }
+ break;
+
+ /*
+ * BLOCK-LEVEL STRUCTURE ELEMENTS
+ */
+
+ case SwFrameType::Txt :
+ {
+ SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(pFrame));
+ const SwTextNode *const pTextNd(rTextFrame.GetTextNodeForParaProps());
+
+ // lazy open LBody after Lbl
+ if (!pTextNd->IsOutline()
+ && rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
+ {
+ sal_Int32 const nId = BeginTagImpl(nullptr, vcl::PDFWriter::LIBody, aListBodyString);
+ SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame()));
+ NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
+ rNumListBodyIdMap[ pNodeNum ] = nId;
+ }
+
+ const SwFormat* pTextFormat = pTextNd->GetFormatColl();
+ const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;
+
+ OUString sStyleName;
+ OUString sParentStyleName;
+
+ if ( pTextFormat)
+ SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
+ if ( pParentTextFormat)
+ SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl );
+
+ // This is the default. If the paragraph could not be mapped to
+ // any of the standard pdf tags, we write a user defined tag
+ // <stylename> with role = P
+ nPDFType = vcl::PDFWriter::Paragraph;
+ aPDFType = sStyleName;
+
+ // Quotations: BlockQuote
+
+ if (sStyleName == aQuotations)
+ {
+ nPDFType = vcl::PDFWriter::BlockQuote;
+ aPDFType = aBlockQuoteString;
+ }
+
+ // Caption: Caption
+
+ else if (sStyleName == aCaption)
+ {
+ nPDFType = vcl::PDFWriter::Caption;
+ aPDFType = aCaptionString;
+ }
+
+ // Caption: Caption
+
+ else if (sParentStyleName == aCaption)
+ {
+ nPDFType = vcl::PDFWriter::Caption;
+ aPDFType = sStyleName + aCaptionString;
+ }
+
+ // Heading: H
+
+ else if (sStyleName == aHeading)
+ {
+ nPDFType = vcl::PDFWriter::Heading;
+ aPDFType = aHString;
+ }
+
+ // Heading: H1 - H6
+
+ if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1;
+ nRealLevel >= 0
+ && !pTextNd->IsInRedlines()
+ && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd))
+ {
+ switch(nRealLevel)
+ {
+ case 0 :
+ aPDFType = aH1String;
+ break;
+ case 1 :
+ aPDFType = aH2String;
+ break;
+ case 2 :
+ aPDFType = aH3String;
+ break;
+ case 3 :
+ aPDFType = aH4String;
+ break;
+ case 4 :
+ aPDFType = aH5String;
+ break;
+ case 5:
+ aPDFType = aH6String;
+ break;
+ case 6:
+ aPDFType = aH7String;
+ break;
+ case 7:
+ aPDFType = aH8String;
+ break;
+ case 8:
+ aPDFType = aH9String;
+ break;
+ case 9:
+ aPDFType = aH10String;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ // PDF/UA allows unlimited headings, but PDF only up to H6
+ // ... and apparently the extra H7.. must be declared in
+ // RoleMap, or veraPDF complains.
+ nRealLevel = std::min(nRealLevel, 5);
+ nPDFType = o3tl::narrowing<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel);
+ }
+
+ // Section: TOCI
+
+ else if ( pFrame->IsInSct() )
+ {
+ const SwSectionFrame* pSctFrame = pFrame->FindSctFrame();
+ const SwSection* pSection = pSctFrame->GetSection();
+
+ if ( SectionType::ToxContent == pSection->GetType() )
+ {
+ const SwTOXBase* pTOXBase = pSection->GetTOXBase();
+ if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() )
+ {
+ // Special case: Open additional TOCI tag:
+ BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString);
+ }
+ }
+ }
+ }
+ break;
+
+ case SwFrameType::Tab :
+
+ // TabFrame: Table
+
+ nPDFType = vcl::PDFWriter::Table;
+ aPDFType = aTableString;
+
+ {
+ // set up table column data:
+ const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame);
+ const SwTable* pTable = pTabFrame->GetTable();
+
+ TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap);
+ const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable );
+
+ if ( aIter == rTableColumnsMap.end() )
+ {
+ SwRectFnSet aRectFnSet(pTabFrame);
+ TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ];
+
+ const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame;
+
+ while ( pMasterFrame )
+ {
+ const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower());
+
+ while ( pRowFrame )
+ {
+ const SwFrame* pCellFrame = pRowFrame->GetLower();
+
+ const tools::Long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea());
+ rCols.insert( nLeft );
+
+ while ( pCellFrame )
+ {
+ const tools::Long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea());
+ rCols.insert( nRight );
+ pCellFrame = pCellFrame->GetNext();
+ }
+ pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext());
+ }
+ pMasterFrame = pMasterFrame->GetFollow();
+ }
+ }
+ }
+
+ break;
+
+ /*
+ * TABLE ELEMENTS
+ */
+
+ case SwFrameType::Row :
+
+ // RowFrame: TR
+
+ if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() )
+ {
+ nPDFType = vcl::PDFWriter::TableRow;
+ aPDFType = aTRString;
+ }
+ else
+ {
+ nPDFType = vcl::PDFWriter::NonStructElement;
+ }
+ break;
+
+ case SwFrameType::Cell :
+
+ // CellFrame: TH, TD
+
+ {
+ const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame();
+ if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) )
+ {
+ nPDFType = vcl::PDFWriter::TableHeader;
+ aPDFType = aTHString;
+ }
+ else
+ {
+ nPDFType = vcl::PDFWriter::TableData;
+ aPDFType = aTDString;
+ }
+ }
+ break;
+
+ /*
+ * ILLUSTRATION
+ */
+
+ case SwFrameType::Fly :
+
+ // FlyFrame: Figure, Formula, Control
+ // fly in content or fly at page
+ if (mpFrameInfo->m_isLink)
+ { // tdf#154939 additional inner link element for flys
+ nPDFType = vcl::PDFWriter::Link;
+ aPDFType = aLinkString;
+ }
+ else
+ {
+ const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame);
+ if (pFly->GetAnchorFrame()->FindFooterOrHeader() != nullptr
+ || pFly->GetFrameFormat().GetAttrSet().Get(RES_DECORATIVE).GetValue())
+ {
+ nPDFType = vcl::PDFWriter::NonStructElement;
+ }
+ else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame())
+ {
+ bool bFormula = false;
+
+ const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower());
+ SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode());
+ if ( pOLENd )
+ {
+ SwOLEObj& aOLEObj = pOLENd->GetOLEObj();
+ uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef();
+ if ( aRef.is() )
+ {
+ bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) );
+ }
+ }
+ if ( bFormula )
+ {
+ nPDFType = vcl::PDFWriter::Formula;
+ aPDFType = aFormulaString;
+ }
+ else
+ {
+ nPDFType = vcl::PDFWriter::Figure;
+ aPDFType = aFigureString;
+ }
+ }
+ else
+ {
+ nPDFType = vcl::PDFWriter::Division;
+ aPDFType = aDivString;
+ }
+ }
+ break;
+
+ default: break;
+ }
+
+ if ( USHRT_MAX != nPDFType )
+ {
+ BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
+ }
+}
+
+void SwTaggedPDFHelper::EndStructureElements()
+{
+ if (mpFrameInfo != nullptr)
+ {
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
+ { // close span at end of paragraph
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
+ ++m_nEndStructureElement;
+ }
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ { // close link at end of paragraph
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
+ ++m_nEndStructureElement;
+ }
+ }
+
+ while ( m_nEndStructureElement > 0 )
+ {
+ EndTag();
+ --m_nEndStructureElement;
+ }
+
+ CheckRestoreTag();
+}
+
+void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut)
+{
+ vcl::PDFExtOutDevData *const pPDFExtOutDevData(
+ dynamic_cast<vcl::PDFExtOutDevData *>(rOut.GetExtOutDevData()));
+ if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ {
+ pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
+ pPDFExtOutDevData->EndStructureElement();
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.pop_back();
+#endif
+ }
+}
+
+void SwTaggedPDFHelper::EndCurrentAll()
+{
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
+ {
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
+ }
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ {
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
+ }
+}
+
+void SwTaggedPDFHelper::EndCurrentSpan()
+{
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
+ EndTag(); // close span
+}
+
+void SwTaggedPDFHelper::CreateCurrentSpan(
+ SwTextPaintInfo const& rInf, OUString const& rStyleName)
+{
+ assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(
+ SwEnhancedPDFState::Span{
+ rInf.GetFont()->GetUnderline(),
+ rInf.GetFont()->GetOverline(),
+ rInf.GetFont()->GetStrikeout(),
+ rInf.GetFont()->GetEmphasisMark(),
+ rInf.GetFont()->GetEscapement(),
+ rInf.GetFont()->GetActual(),
+ rInf.GetFont()->GetLanguage(),
+ rStyleName});
+ // leave it open to let next portion decide to merge or close
+ --m_nEndStructureElement;
+}
+
+bool SwTaggedPDFHelper::CheckContinueSpan(
+ SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
+ SwTextAttr const*const pInetFormatAttr)
+{
+ // for now, don't create span inside of link - this should be very rare
+ // situation and it looks complicated to implement.
+ assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
+ || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ {
+ if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ {
+ return true;
+ }
+ else
+ {
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
+ EndTag();
+ return false;
+ }
+ }
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
+ {
+ EndCurrentSpan();
+ return false;
+ }
+
+ if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
+ return false;
+
+ SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
+
+ bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline()
+ && rCurrent.eOverline == rInf.GetFont()->GetOverline()
+ && rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout()
+ && rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark()
+ && rCurrent.nEscapement == rInf.GetFont()->GetEscapement()
+ && rCurrent.nScript == rInf.GetFont()->GetActual()
+ && rCurrent.nLang == rInf.GetFont()->GetLanguage()
+ && rCurrent.StyleName == rStyleName);
+ if (!ret)
+ {
+ EndCurrentSpan();
+ }
+ return ret;
+}
+
+void SwTaggedPDFHelper::BeginInlineStructureElements()
+{
+ const SwLinePortion* pPor = &mpPorInfo->mrPor;
+ const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
+ const SwTextFrame* pFrame = rInf.GetTextFrame();
+
+ // Lowers of NonStructureElements should not be considered:
+
+ if ( lcl_IsInNonStructEnv( *pFrame ) )
+ return;
+
+ std::pair<SwTextNode const*, sal_Int32> const pos(
+ pFrame->MapViewToModel(rInf.GetIdx()));
+ SwTextAttr const*const pInetFormatAttr =
+ pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT);
+
+ OUString sStyleName;
+ if (!pInetFormatAttr)
+ {
+ std::vector<SwTextAttr *> const charAttrs(
+ pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT));
+ // TODO: handle more than 1 char style?
+ const SwCharFormat* pCharFormat = (charAttrs.size())
+ ? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr;
+ if (pCharFormat)
+ SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
+ }
+
+ // note: ILSE may be nested, so only end the span if needed to start new one
+ bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName, pInetFormatAttr));
+
+ sal_uInt16 nPDFType = USHRT_MAX;
+ OUString aPDFType;
+
+ switch ( pPor->GetWhichPor() )
+ {
+ case PortionType::Hyphen :
+ case PortionType::SoftHyphen :
+ // Check for alternative spelling:
+ case PortionType::HyphenStr :
+ case PortionType::SoftHyphenStr :
+ nPDFType = vcl::PDFWriter::Span;
+ aPDFType = aSpanString;
+ break;
+
+ case PortionType::Fly:
+ // if a link is split by a fly overlap, then there will be multiple
+ // annotations for the link, and hence there must be multiple SEs,
+ // so every annotation has its own SE.
+ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
+ {
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
+ EndTag();
+ }
+ break;
+
+ case PortionType::Lay :
+ case PortionType::Text :
+ case PortionType::Para :
+ {
+ // Check for Link:
+ if( pInetFormatAttr )
+ {
+ if (!isContinueSpan)
+ {
+ nPDFType = vcl::PDFWriter::Link;
+ aPDFType = aLinkString;
+ assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
+ mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr);
+ // leave it open to let next portion decide to merge or close
+ --m_nEndStructureElement;
+ }
+ }
+ // Check for Quote/Code character style:
+ else if (sStyleName == aQuotation)
+ {
+ if (!isContinueSpan)
+ {
+ nPDFType = vcl::PDFWriter::Quote;
+ aPDFType = aQuoteString;
+ CreateCurrentSpan(rInf, sStyleName);
+ }
+ }
+ else if (sStyleName == aSourceText)
+ {
+ if (!isContinueSpan)
+ {
+ nPDFType = vcl::PDFWriter::Code;
+ aPDFType = aCodeString;
+ CreateCurrentSpan(rInf, sStyleName);
+ }
+ }
+ else if (!isContinueSpan)
+ {
+ const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
+ const SwFontScript nFont = rInf.GetFont()->GetActual();
+ const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
+
+ if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ||
+ LINESTYLE_NONE != rInf.GetFont()->GetOverline() ||
+ STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ||
+ FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ||
+ 0 != rInf.GetFont()->GetEscapement() ||
+ SwFontScript::Latin != nFont ||
+ nCurrentLanguage != nDefaultLang ||
+ !sStyleName.isEmpty())
+ {
+ nPDFType = vcl::PDFWriter::Span;
+ if (!sStyleName.isEmpty())
+ aPDFType = sStyleName;
+ else
+ aPDFType = aSpanString;
+ CreateCurrentSpan(rInf, sStyleName);
+ }
+ }
+ }
+ break;
+
+ case PortionType::Footnote :
+ nPDFType = vcl::PDFWriter::Link;
+ aPDFType = aLinkString;
+ break;
+
+ case PortionType::Field :
+ {
+ // check field type:
+ TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow()
+ ? rInf.GetIdx() - TextFrameIndex(1)
+ : rInf.GetIdx();
+ const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx );
+ if ( pHint && RES_TXTATR_FIELD == pHint->Which() )
+ {
+ const SwField* pField = pHint->GetFormatField().GetField();
+ if ( SwFieldIds::GetRef == pField->Which() )
+ {
+ nPDFType = vcl::PDFWriter::Link;
+ aPDFType = aLinkString;
+ }
+ else if ( SwFieldIds::TableOfAuthorities == pField->Which() )
+ {
+ nPDFType = vcl::PDFWriter::BibEntry;
+ aPDFType = aBibEntryString;
+ }
+ }
+ }
+ break;
+
+ case PortionType::Multi:
+ {
+ SwMultiPortion const*const pMulti(static_cast<SwMultiPortion const*>(pPor));
+ if (pMulti->IsRuby())
+ {
+ EndCurrentAll();
+ switch (mpPorInfo->m_Mode)
+ {
+ case 0:
+ nPDFType = vcl::PDFWriter::Ruby;
+ aPDFType = "Ruby";
+ break;
+ case 1:
+ nPDFType = vcl::PDFWriter::RT;
+ aPDFType = "RT";
+ break;
+ case 2:
+ nPDFType = vcl::PDFWriter::RB;
+ aPDFType = "RB";
+ break;
+ }
+ }
+ else if (pMulti->IsDouble())
+ {
+ EndCurrentAll();
+ switch (mpPorInfo->m_Mode)
+ {
+ case 0:
+ nPDFType = vcl::PDFWriter::Warichu;
+ aPDFType = "Warichu";
+ break;
+ case 1:
+ nPDFType = vcl::PDFWriter::WP;
+ aPDFType = "WP";
+ break;
+ case 2:
+ nPDFType = vcl::PDFWriter::WT;
+ aPDFType = "WT";
+ break;
+ }
+ }
+ }
+ break;
+
+
+ // for FootnoteNum, is called twice: outer generates Lbl, inner Link
+ case PortionType::FootnoteNum:
+ assert(!isContinueSpan); // is at start
+ if (mpPorInfo->m_Mode == 0)
+ { // tdf#152218 link both directions
+ nPDFType = vcl::PDFWriter::Link;
+ aPDFType = aLinkString;
+ break;
+ }
+ [[fallthrough]];
+ case PortionType::Number:
+ case PortionType::Bullet:
+ case PortionType::GrfNum:
+ assert(!isContinueSpan); // is at start
+ if (mpPorInfo->m_Mode == 1)
+ { // only works for multiple lines via wrapper from PaintSwFrame
+ nPDFType = vcl::PDFWriter::LILabel;
+ aPDFType = aListLabelString;
+ }
+ break;
+
+ case PortionType::Tab :
+ case PortionType::TabRight :
+ case PortionType::TabCenter :
+ case PortionType::TabDecimal :
+ nPDFType = vcl::PDFWriter::NonStructElement;
+ break;
+ default: break;
+ }
+
+ if ( USHRT_MAX != nPDFType )
+ {
+ BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
+ }
+}
+
+bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut )
+{
+ vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
+ return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF();
+}
+
+SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh,
+ OutputDevice& rOut,
+ const OUString& rPageRange,
+ bool bSkipEmptyPages,
+ bool bEditEngineOnly,
+ const SwPrintData& rPrintData )
+ : mrSh( rSh ),
+ mrOut( rOut ),
+ mbSkipEmptyPages( bSkipEmptyPages ),
+ mbEditEngineOnly( bEditEngineOnly ),
+ mrPrintData( rPrintData )
+{
+ if ( !rPageRange.isEmpty() )
+ mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) );
+
+ if ( mbSkipEmptyPages )
+ {
+ maPageNumberMap.resize( mrSh.GetPageCount() );
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+ sal_Int32 nPageNumber = 0;
+ for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i )
+ {
+ if ( pCurrPage->IsEmptyPage() )
+ maPageNumberMap[i] = -1;
+ else
+ maPageNumberMap[i] = nPageNumber++;
+
+ pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() );
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ aStructStack.clear();
+#endif
+
+ const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
+ TypedWhichId<SvxLanguageItem> nLangRes = RES_CHRATR_LANGUAGE;
+
+ if ( i18n::ScriptType::ASIAN == nScript )
+ nLangRes = RES_CHRATR_CJK_LANGUAGE;
+ else if ( i18n::ScriptType::COMPLEX == nScript )
+ nLangRes = RES_CHRATR_CTL_LANGUAGE;
+
+ const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes );
+ auto const eLanguageDefault = rLangItem.GetLanguage();
+
+ EnhancedPDFExport(eLanguageDefault);
+}
+
+SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper()
+{
+}
+
+tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage,
+ const tools::Rectangle& rRectangle) const
+{
+ if (!::sw::IsShrinkPageForPostIts(mrSh, mrPrintData)) // tdf#148729
+ {
+ return rRectangle;
+ }
+ //the page has been scaled by 75% and vertically centered, so adjust these
+ //rectangles equivalently
+ tools::Rectangle aRect(rRectangle);
+ Size aRectSize(aRect.GetSize());
+ double fScale = 0.75;
+ aRectSize.setWidth( aRectSize.Width() * fScale );
+ aRectSize.setHeight( aRectSize.Height() * fScale );
+ tools::Long nOrigHeight = pCurrPage->getFrameArea().Height();
+ tools::Long nNewHeight = nOrigHeight*fScale;
+ tools::Long nShiftY = (nOrigHeight-nNewHeight)/2;
+ aRect.SetLeft( aRect.Left() * fScale );
+ aRect.SetTop( aRect.Top() * fScale );
+ aRect.Move(0, nShiftY);
+ aRect.SetSize(aRectSize);
+ return aRect;
+}
+
+void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault)
+{
+ vcl::PDFExtOutDevData* pPDFExtOutDevData =
+ dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() );
+
+ if ( !pPDFExtOutDevData )
+ return;
+
+ // set the document locale
+
+ lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() );
+ pPDFExtOutDevData->SetDocumentLocale( aDocLocale );
+
+ // Prepare the output device:
+
+ mrOut.Push( vcl::PushFlags::MAPMODE );
+ MapMode aMapMode( mrOut.GetMapMode() );
+ aMapMode.SetMapUnit( MapUnit::MapTwip );
+ mrOut.SetMapMode( aMapMode );
+
+ // Create new cursor and lock the view:
+
+ SwDoc* pDoc = mrSh.GetDoc();
+ mrSh.SwCursorShell::Push();
+ mrSh.SwCursorShell::ClearMark();
+ const bool bOldLockView = mrSh.IsViewLocked();
+ mrSh.LockView( true );
+
+ if ( !mbEditEngineOnly )
+ {
+ assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
+ pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));
+
+ // POSTITS
+
+ if ( pPDFExtOutDevData->GetIsExportNotes() )
+ {
+ std::vector<SwFormatField*> vpFields;
+ mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields);
+ for(auto pFormatField : vpFields)
+ {
+ const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
+ OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing");
+ if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
+ continue;
+ // Link Rectangle
+ const SwRect& rNoteRect = mrSh.GetCharRect();
+ const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
+
+ // Link PageNums
+ std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect);
+ for (sal_Int32 aNotePageNum : aNotePageNums)
+ {
+
+ // Use the NumberFormatter to get the date string:
+ const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
+ SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter();
+ const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate());
+ const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
+ OUString sDate;
+ const Color* pColor;
+ pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);
+
+ vcl::PDFNote aNote;
+ // The title should consist of the author and the date:
+ aNote.Title = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : "");
+ // Guess what the contents contains...
+ aNote.Contents = pField->GetText();
+
+ // Link Export
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
+ pPDFExtOutDevData->CreateNote(aRect, aNote, aNotePageNum);
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+ }
+
+ // HYPERLINKS
+
+ SwGetINetAttrs aArr;
+ mrSh.GetINetAttrs( aArr );
+ for( auto &rAttr : aArr )
+ {
+ SwGetINetAttr* p = &rAttr;
+ OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" );
+
+ const SwTextNode* pTNd = p->rINetAttr.GetpTextNode();
+ OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
+
+ // 1. Check if the whole paragraph is hidden
+ // 2. Move to the hyperlink
+ // 3. Check for hidden text attribute
+ if ( !pTNd->IsHidden() &&
+ mrSh.GotoINetAttr( p->rINetAttr ) &&
+ !mrSh.IsInHiddenRange(/*bSelect=*/false) )
+ {
+ // Select the hyperlink:
+ mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
+ if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) )
+ {
+ // First, we create the destination, because there may be more
+ // than one link to this destination:
+ OUString aURL( INetURLObject::decode(
+ p->rINetAttr.GetINetFormat().GetValue(),
+ INetURLObject::DecodeMechanism::Unambiguous ) );
+
+ // We have to distinguish between internal and real URLs
+ const bool bInternal = '#' == aURL[0];
+
+ // GetCursor_() is a SwShellCursor, which is derived from
+ // SwSelPaintRects, therefore the rectangles of the current
+ // selection can be easily obtained:
+ // Note: We make a copy of the rectangles, because they may
+ // be deleted again in JumpToSwMark.
+ SwRects const aTmp(GetCursorRectsContainingText(mrSh));
+ OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
+ OUString const altText(mrSh.GetSelText());
+
+ const SwPageFrame* pSelectionPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Create the destination for internal links:
+ sal_Int32 nDestId = -1;
+ if ( bInternal )
+ {
+ aURL = aURL.copy( 1 );
+ mrSh.SwCursorShell::ClearMark();
+ if (! JumpToSwMark( &mrSh, aURL ))
+ {
+ continue; // target deleted
+ }
+
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ // Destination Export
+ if ( -1 != nDestPageNum )
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+ }
+ }
+
+ if ( !bInternal || -1 != nDestId )
+ {
+ // #i44368# Links in Header/Footer
+ const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
+
+ // Create links for all selected rectangles:
+ const size_t nNumOfRects = aTmp.size();
+ for ( size_t i = 0; i < nNumOfRects; ++i )
+ {
+ // Link Rectangle
+ const SwRect& rLinkRect( aTmp[ i ] );
+
+ // Link PageNums
+ std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
+
+ for (sal_Int32 aLinkPageNum : aLinkPageNums)
+ {
+ // Link Export
+ tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect()));
+ const sal_Int32 nLinkId =
+ pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum);
+
+ // Store link info for tagged pdf output:
+ const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+
+ // Connect Link and Destination:
+ if ( bInternal )
+ pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
+ else
+ pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
+
+ // #i44368# Links in Header/Footer
+ if ( bHeaderFooter )
+ MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText);
+ }
+ }
+ }
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+
+ // HYPERLINKS (Graphics, Frames, OLEs )
+
+ for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats())
+ {
+ const SwFormatURL* pItem;
+ if ( RES_DRAWFRMFMT != pFrameFormat->Which() &&
+ GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) &&
+ (pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) )
+ {
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ OUString aURL( pItem->GetURL() );
+ if (aURL.isEmpty())
+ continue;
+ const bool bInternal = '#' == aURL[0];
+
+ // Create the destination for internal links:
+ sal_Int32 nDestId = -1;
+ if ( bInternal )
+ {
+ aURL = aURL.copy( 1 );
+ mrSh.SwCursorShell::ClearMark();
+ if (! JumpToSwMark( &mrSh, aURL ))
+ {
+ continue; // target deleted
+ }
+
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ // Destination Export
+ if ( -1 != nDestPageNum )
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+ }
+ }
+
+ if ( !bInternal || -1 != nDestId )
+ {
+ Point aNullPt;
+ const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt );
+ OUString const formatName(pFrameFormat->GetName());
+ // Link PageNums
+ std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect );
+
+ // Link Export
+ for (sal_Int32 aLinkPageNum : aLinkPageNums)
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()));
+ const sal_Int32 nLinkId =
+ pPDFExtOutDevData->CreateLink(aRect, formatName, aLinkPageNum);
+
+ // Store link info for tagged pdf output:
+ const IdMapEntry aLinkEntry(aLinkRect, nLinkId);
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+
+ // Connect Link and Destination:
+ if ( bInternal )
+ pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
+ else
+ pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
+
+ // #i44368# Links in Header/Footer
+ const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor();
+ if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId())
+ {
+ const SwNode* pAnchorNode = rAnch.GetAnchorNode();
+ if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) )
+ {
+ const SwTextNode* pTNd = pAnchorNode->GetTextNode();
+ if ( pTNd )
+ MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName);
+ }
+ }
+ }
+ }
+ }
+ else if (pFrameFormat->Which() == RES_DRAWFRMFMT)
+ {
+ // Turn media shapes into Screen annotations.
+ if (SdrObject* pObject = pFrameFormat->FindRealSdrObject())
+ {
+ SwRect aSnapRect(pObject->GetSnapRect());
+ std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect);
+ if (aScreenPageNums.empty())
+ continue;
+
+ uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY);
+ if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape")
+ {
+ uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY);
+ OUString title;
+ xShapePropSet->getPropertyValue("Title") >>= title;
+ OUString description;
+ xShapePropSet->getPropertyValue("Description") >>= description;
+ OUString const altText(title.isEmpty()
+ ? description
+ : description.isEmpty()
+ ? title
+ : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));
+
+ OUString aMediaURL;
+ xShapePropSet->getPropertyValue("MediaURL") >>= aMediaURL;
+ if (!aMediaURL.isEmpty())
+ {
+ OUString const mimeType(xShapePropSet->getPropertyValue("MediaMimeType").get<OUString>());
+ const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center());
+ tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect()));
+ for (sal_Int32 nScreenPageNum : aScreenPageNums)
+ {
+ sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject);
+ if (aMediaURL.startsWith("vnd.sun.star.Package:"))
+ {
+ // Embedded media.
+ OUString aTempFileURL;
+ xShapePropSet->getPropertyValue("PrivateTempFileURL") >>= aTempFileURL;
+ pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL);
+ }
+ else
+ // Linked media.
+ pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL);
+ }
+ }
+ }
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+
+ // REFERENCES
+
+ std::vector<SwFormatField*> vpFields;
+ mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields);
+ for(auto pFormatField : vpFields )
+ {
+ if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() )
+ {
+ const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
+ OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
+ if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
+ continue;
+ // Select the field:
+ mrSh.SwCursorShell::SetMark();
+ mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
+
+ // Link Rectangles
+ SwRects const aTmp(GetCursorRectsContainingText(mrSh));
+ OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
+
+ mrSh.SwCursorShell::ClearMark();
+
+ // Destination Rectangle
+ const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField());
+ const OUString& rRefName = pField->GetSetRefName();
+ mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() );
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ if ( -1 != nDestPageNum )
+ {
+ // Destination Export
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+
+ // #i44368# Links in Header/Footer
+ const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
+ OUString const content(pField->ExpandField(true, mrSh.GetLayout()));
+
+ // Create links for all selected rectangles:
+ const size_t nNumOfRects = aTmp.size();
+ for ( size_t i = 0; i < nNumOfRects; ++i )
+ {
+ // Link rectangle
+ const SwRect& rLinkRect( aTmp[ i ] );
+
+ // Link PageNums
+ std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
+
+ for (sal_Int32 aLinkPageNum : aLinkPageNums)
+ {
+ // Link Export
+ aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect());
+ const sal_Int32 nLinkId =
+ pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum);
+
+ // Store link info for tagged pdf output:
+ const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+
+ // Connect Link and Destination:
+ pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
+
+ // #i44368# Links in Header/Footer
+ if ( bHeaderFooter )
+ {
+ MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true, content);
+ }
+ }
+ }
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+
+ ExportAuthorityEntryLinks();
+
+ // FOOTNOTES
+
+ const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size();
+ for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx )
+ {
+ // Set cursor to text node that contains the footnote:
+ const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ];
+ SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
+
+ mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart());
+
+ // 1. Check if the whole paragraph is hidden
+ // 2. Check for hidden text attribute
+ if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false)
+ || (mrSh.GetLayout()->IsHideRedlines()
+ && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote)))
+ {
+ continue;
+ }
+
+ SwCursorSaveState aSaveState( *mrSh.GetCursor_() );
+
+ // Select the footnote:
+ mrSh.SwCursorShell::SetMark();
+ mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
+
+ // Link Rectangle
+ SwRects aTmp;
+ aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
+ OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
+
+ mrSh.GetCursor_()->RestoreSavePos();
+ mrSh.SwCursorShell::ClearMark();
+
+ if (aTmp.empty())
+ continue;
+
+ const SwRect aLinkRect( aTmp[ 0 ] );
+
+ // Goto footnote text:
+ if ( mrSh.GotoFootnoteText() )
+ {
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+ if ( -1 != nDestPageNum )
+ {
+ const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+ // Destination PageNum
+ tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
+ // Back link rectangle calculation
+ const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
+ SwRect fnSymbolRect;
+ if (fnBodyPage->IsVertical()){
+ tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top();
+ tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop;
+ fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight);
+ } else {
+ if (fnBodyPage->IsRightToLeft()){
+ tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin();
+ tools::Long symbolWidth = fnSymbolRight - rDestRect.Right();
+ fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
+ } else {
+ tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left();
+ tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft;
+ fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
+ }
+ }
+ tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect());
+
+ OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true));
+ OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false));
+
+ // Export back link
+ const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrSymbol, nDestPageNum);
+ // Destination Export
+ const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+ mrSh.GotoFootnoteAnchor();
+ // Link PageNums
+ sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect );
+ pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+ // Link Export
+ aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect());
+ const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrRef, aLinkPageNum);
+ // Back link destination Export
+ const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum);
+ // Store link info for tagged pdf output:
+ const IdMapEntry aLinkEntry( aLinkRect, nLinkId );
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+
+ // Store backlink info for tagged pdf output:
+ const IdMapEntry aBackLinkEntry( aFootnoteSymbolRect, nBackLinkId );
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aBackLinkEntry);
+ // Connect Links and Destinations:
+ pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
+ pPDFExtOutDevData->SetLinkDest( nBackLinkId, nBackDestId );
+ }
+ }
+ }
+
+ // OUTLINE
+
+ if( pPDFExtOutDevData->GetIsExportBookmarks() )
+ {
+ typedef std::pair< sal_Int8, sal_Int32 > StackEntry;
+ std::stack< StackEntry > aOutlineStack;
+ aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value
+
+ const SwOutlineNodes::size_type nOutlineCount =
+ mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount();
+ for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
+ {
+ // Check if outline is hidden
+ const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode();
+ OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
+
+ if ( pTNd->IsHidden() ||
+ !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) ||
+ // #i40292# Skip empty outlines:
+ pTNd->GetText().isEmpty())
+ continue;
+
+ // Get parent id from stack:
+ const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i ));
+ sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
+ while ( nLevelOnTopOfStack >= nLevel &&
+ nLevelOnTopOfStack != -1 )
+ {
+ aOutlineStack.pop();
+ nLevelOnTopOfStack = aOutlineStack.top().first;
+ }
+ const sal_Int32 nParent = aOutlineStack.top().second;
+
+ // Destination rectangle
+ mrSh.GotoOutline(i);
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ if ( -1 != nDestPageNum )
+ {
+ // Destination Export
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ const sal_Int32 nDestId =
+ pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+
+ // Outline entry text
+ const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
+ i, mrSh.GetLayout(), true, false, false );
+
+ // Create a new outline item:
+ const sal_Int32 nOutlineId =
+ pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId );
+
+ // Push current level and nOutlineId on stack:
+ aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
+ }
+ }
+ }
+
+ if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
+ {
+ // #i56629# the iteration to convert the OOo bookmark (#bookmark)
+ // into PDF named destination, see section 8.2.1 in PDF 1.4 spec
+ // We need:
+ // 1. a name for the destination, formed from the standard OOo bookmark name
+ // 2. the destination, obtained from where the bookmark destination lies
+ IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess();
+ for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin();
+ ppMark != pMarkAccess->getBookmarksEnd();
+ ++ppMark)
+ {
+ //get the name
+ const ::sw::mark::IMark* pBkmk = *ppMark;
+ mrSh.SwCursorShell::ClearMark();
+ const OUString& sBkName = pBkmk->GetName();
+
+ //jump to it
+ if (! JumpToSwMark( &mrSh, sBkName ))
+ {
+ continue;
+ }
+
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ // Destination Export
+ if ( -1 != nDestPageNum )
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum);
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ //<--- i56629
+ }
+ }
+ else
+ {
+
+ // LINKS FROM EDITENGINE
+
+ std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
+ for ( const auto& rBookmark : rBookmarks )
+ {
+ OUString aBookmarkName( rBookmark.aBookmark );
+ const bool bInternal = '#' == aBookmarkName[0];
+ if ( bInternal )
+ {
+ aBookmarkName = aBookmarkName.copy( 1 );
+ JumpToSwMark( &mrSh, aBookmarkName );
+
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ if ( -1 != nDestPageNum )
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ if ( rBookmark.nLinkId != -1 )
+ {
+ // Destination Export
+ const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+
+ // Connect Link and Destination:
+ pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId );
+ }
+ else
+ {
+ pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum);
+ }
+ }
+ }
+ else
+ pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName );
+ }
+ rBookmarks.clear();
+ assert(pPDFExtOutDevData->GetSwPDFState());
+ delete pPDFExtOutDevData->GetSwPDFState();
+ pPDFExtOutDevData->SetSwPDFState(nullptr);
+ }
+
+ // Restore view, cursor, and outdev:
+ mrSh.LockView( bOldLockView );
+ mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent);
+ mrOut.Pop();
+}
+
+void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
+{
+ auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
+ if (!pPDFExtOutDevData)
+ {
+ return;
+ }
+
+ // Create PDF destinations for bibliography table entries
+ std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations;
+ // string is the row node text, sal_Int32 is number of the destination
+ // Note: This way of iterating doesn't seem to take into account TOXes
+ // that are in a frame, probably in some other cases too
+ {
+ mrSh.GotoPage(1);
+ while (mrSh.GotoNextTOXBase())
+ {
+ const SwTOXBase* pIteratedTOX = nullptr;
+ while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
+ && pIteratedTOX->GetType() == TOX_AUTHORITIES)
+ {
+ if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
+ rCurrentNode.GetNodeType() == SwNodeType::Text)
+ {
+ if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
+ == SectionType::ToxContent) // this checks it's not a heading
+ {
+ // Destination Rectangle
+ const SwRect& rDestRect = mrSh.GetCharRect();
+
+ const SwPageFrame* pCurrPage =
+ static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
+
+ // Destination PageNum
+ const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
+
+ // Destination Export
+ if ( -1 != nDestPageNum )
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
+ const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
+ const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
+ vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
+ }
+ }
+ }
+ if (!mrSh.MovePara(GoNextPara, fnParaStart))
+ { // Cursor is stuck in the TOX due to document ending immediately afterwards
+ break;
+ }
+ }
+ }
+ }
+
+ // Generate links to matching entries in the bibliography tables
+ std::vector<SwFormatField*> aFields;
+ SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString());
+ if (!pType)
+ {
+ return;
+ }
+
+ pType->GatherFields(aFields);
+ const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
+ for (const auto pFormatField : aFields)
+ {
+ if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc())
+ {
+ continue;
+ }
+
+ const auto& rAuthorityField
+ = *static_cast<const SwAuthorityField*>(pFormatField->GetField());
+
+ if (auto targetType = rAuthorityField.GetTargetType();
+ targetType == SwAuthorityField::TargetType::UseDisplayURL
+ || targetType == SwAuthorityField::TargetType::UseTargetURL)
+ {
+ // Since the target type specifies to use an URL, link to it
+ const OUString& rURL = rAuthorityField.GetAbsoluteURL();
+ if (rURL.getLength() == 0)
+ {
+ continue;
+ }
+
+ const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
+ if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
+ {
+ continue;
+ }
+
+ OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
+
+ // Select the field.
+ mrSh.SwCursorShell::SetMark();
+ mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
+
+ // Create the links.
+ SwRects const rects(GetCursorRectsContainingText(mrSh));
+ for (const auto& rLinkRect : rects)
+ {
+ for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
+ sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
+ IdMapEntry aLinkEntry(rLinkRect, nLinkId);
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+ pPDFExtOutDevData->SetLinkURL(nLinkId, rURL);
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+ else if (targetType == SwAuthorityField::TargetType::BibliographyTableRow)
+ {
+ // As the target type specifies, try linking to a bibliography table row
+ sal_Int32 nDestId = -1;
+
+ std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings;
+ for (const auto& rDestinationTuple : vDestinations)
+ {
+ if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple))
+ == vFormattedFieldStrings.end())
+ vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple),
+ rAuthorityField.GetAuthority(mrSh.GetLayout(),
+ &std::get<0>(rDestinationTuple)->GetTOXForm()));
+
+ if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple))
+ {
+ nDestId = std::get<2>(rDestinationTuple);
+ break;
+ }
+ }
+
+ if (nDestId == -1)
+ continue;
+
+ const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
+ if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
+ {
+ continue;
+ }
+
+ OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
+
+ // Select the field.
+ mrSh.SwCursorShell::SetMark();
+ mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
+
+ // Create the links.
+ SwRects const rects(GetCursorRectsContainingText(mrSh));
+ for (const auto& rLinkRect : rects)
+ {
+ for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
+ {
+ tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
+ sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
+ IdMapEntry aLinkEntry(rLinkRect, nLinkId);
+ pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
+ pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId);
+ }
+ }
+ mrSh.SwCursorShell::ClearMark();
+ }
+ }
+}
+
+// Returns the page number in the output pdf on which the given rect is located.
+// If this page is duplicated, method will return first occurrence of it.
+sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const
+{
+ std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect );
+ if ( !aPageNums.empty() )
+ return aPageNums[0];
+ return -1;
+}
+
+// Returns a vector of the page numbers in the output pdf on which the given
+// rect is located. There can be many such pages since StringRangeEnumerator
+// allows duplication of its entries.
+std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums(
+ const SwRect& rRect ) const
+{
+ std::vector< sal_Int32 > aPageNums;
+
+ // Document page number.
+ sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect );
+ if ( nPageNumOfRect < 0 )
+ return aPageNums;
+
+ // What will be the page numbers of page nPageNumOfRect in the output pdf?
+ if ( mpRangeEnum )
+ {
+ if ( mbSkipEmptyPages )
+ // Map the page number to the range without empty pages.
+ nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ];
+
+ if ( mpRangeEnum->hasValue( nPageNumOfRect ) )
+ {
+ sal_Int32 nOutputPageNum = 0;
+ StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin();
+ StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end();
+ for ( ; aIter != aEnd; ++aIter )
+ {
+ if ( *aIter == nPageNumOfRect )
+ aPageNums.push_back( nOutputPageNum );
+ ++nOutputPageNum;
+ }
+ }
+ }
+ else
+ {
+ if ( mbSkipEmptyPages )
+ {
+ sal_Int32 nOutputPageNum = 0;
+ for ( size_t i = 0; i < maPageNumberMap.size(); ++i )
+ {
+ if ( maPageNumberMap[i] >= 0 ) // is not empty?
+ {
+ if ( i == static_cast<size_t>( nPageNumOfRect ) )
+ {
+ aPageNums.push_back( nOutputPageNum );
+ break;
+ }
+ ++nOutputPageNum;
+ }
+ }
+ }
+ else
+ aPageNums.push_back( nPageNumOfRect );
+ }
+
+ return aPageNums;
+}
+
+void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData,
+ const SwTextNode& rTNd,
+ const SwRect& rLinkRect,
+ sal_Int32 nDestId,
+ const OUString& rURL,
+ bool bInternal,
+ OUString const& rContent) const
+{
+ // We assume, that the primary link has just been exported. Therefore
+ // the offset of the link rectangle calculates as follows:
+ const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin();
+
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
+ for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
+ {
+ // Add offset to current page:
+ const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame();
+ SwRect aHFLinkRect( rLinkRect );
+ aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset;
+
+ // #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong
+ // fool it by comparing the position only (the width and height are the
+ // same anyway)
+ if ( aHFLinkRect.Pos() != rLinkRect.Pos() )
+ {
+ // Link PageNums
+ std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect );
+
+ for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums)
+ {
+ // Link Export
+ tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect()));
+ const sal_Int32 nHFLinkId =
+ rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum);
+
+ // Connect Link and Destination:
+ if ( bInternal )
+ rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId );
+ else
+ rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL );
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/SwGrammarMarkUp.cxx b/sw/source/core/text/SwGrammarMarkUp.cxx
new file mode 100644
index 0000000000..53fd4b13c0
--- /dev/null
+++ b/sw/source/core/text/SwGrammarMarkUp.cxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <SwGrammarMarkUp.hxx>
+
+SwGrammarMarkUp::~SwGrammarMarkUp()
+{
+}
+
+SwWrongList* SwGrammarMarkUp::Clone()
+{
+ SwWrongList* pClone = new SwGrammarMarkUp();
+ pClone->CopyFrom( *this );
+ return pClone;
+}
+
+void SwGrammarMarkUp::CopyFrom( const SwWrongList& rCopy )
+{
+ maSentence = static_cast<const SwGrammarMarkUp&>(rCopy).maSentence;
+ SwWrongList::CopyFrom( rCopy );
+}
+
+void SwGrammarMarkUp::MoveGrammar( sal_Int32 nPos, sal_Int32 nDiff )
+{
+ Move( nPos, nDiff );
+ if( maSentence.empty() )
+ return;
+ auto pIter = std::find_if(maSentence.begin(), maSentence.end(),
+ [nPos](const sal_Int32& rPos) { return rPos >= nPos; });
+ const sal_Int32 nEnd = nDiff < 0 ? nPos-nDiff : nPos;
+ while( pIter != maSentence.end() )
+ {
+ if( *pIter >= nEnd )
+ *pIter += nDiff;
+ else
+ *pIter = nPos;
+ ++pIter;
+ }
+}
+
+std::unique_ptr<SwGrammarMarkUp> SwGrammarMarkUp::SplitGrammarList( sal_Int32 nSplitPos )
+{
+ std::unique_ptr<SwGrammarMarkUp> pNew( static_cast<SwGrammarMarkUp*>(SplitList( nSplitPos ).release()) );
+ if( maSentence.empty() )
+ return pNew;
+ auto pIter = std::find_if(maSentence.begin(), maSentence.end(),
+ [nSplitPos](const sal_Int32& rPos) { return rPos >= nSplitPos; });
+ if( pIter != maSentence.begin() )
+ {
+ if( !pNew ) {
+ pNew.reset(new SwGrammarMarkUp());
+ pNew->SetInvalid( 0, COMPLETE_STRING );
+ }
+ pNew->maSentence.insert( pNew->maSentence.begin(), maSentence.begin(), pIter );
+ maSentence.erase( maSentence.begin(), pIter );
+ }
+ return pNew;
+}
+
+void SwGrammarMarkUp::JoinGrammarList( SwGrammarMarkUp* pNext, sal_Int32 nInsertPos )
+{
+ JoinList( pNext, nInsertPos );
+ if (pNext)
+ {
+ if( pNext->maSentence.empty() )
+ return;
+ for( auto& rPos : pNext->maSentence )
+ {
+ rPos += nInsertPos;
+ }
+ maSentence.insert( maSentence.end(), pNext->maSentence.begin(), pNext->maSentence.end() );
+ }
+}
+
+void SwGrammarMarkUp::ClearGrammarList( sal_Int32 nSentenceEnd )
+{
+ if( COMPLETE_STRING == nSentenceEnd ) {
+ ClearList();
+ maSentence.clear();
+ Validate();
+ } else if( GetBeginInv() <= nSentenceEnd ) {
+ std::vector< sal_Int32 >::iterator pIter = maSentence.begin();
+ sal_Int32 nStart = 0;
+ while( pIter != maSentence.end() && *pIter < GetBeginInv() )
+ {
+ nStart = *pIter;
+ ++pIter;
+ }
+ auto pLast = std::find_if(pIter, maSentence.end(),
+ [nSentenceEnd](const sal_Int32& rPos) { return rPos > nSentenceEnd; });
+ maSentence.erase( pIter, pLast );
+ RemoveEntry( nStart, nSentenceEnd );
+ SetInvalid( nSentenceEnd + 1, COMPLETE_STRING );
+ }
+}
+
+void SwGrammarMarkUp::setSentence( sal_Int32 nStart )
+{
+ auto pIter = std::find_if(maSentence.begin(), maSentence.end(),
+ [nStart](const sal_Int32& rPos) { return rPos >= nStart; });
+ if( pIter == maSentence.end() || *pIter > nStart )
+ maSentence.insert( pIter, nStart );
+}
+
+sal_Int32 SwGrammarMarkUp::getSentenceStart( sal_Int32 nPos )
+{
+ if( maSentence.empty() )
+ return 0;
+ auto pIter = std::find_if(maSentence.begin(), maSentence.end(),
+ [nPos](const sal_Int32& rPos) { return rPos >= nPos; });
+ if( pIter != maSentence.begin() )
+ --pIter;
+ if( pIter != maSentence.end() && *pIter < nPos )
+ return *pIter;
+ return 0;
+}
+
+sal_Int32 SwGrammarMarkUp::getSentenceEnd( sal_Int32 nPos )
+{
+ if( maSentence.empty() )
+ return COMPLETE_STRING;
+ auto pIter = std::find_if(maSentence.begin(), maSentence.end(),
+ [nPos](const sal_Int32& rPos) { return rPos > nPos; });
+ if( pIter != maSentence.end() )
+ return *pIter;
+ return COMPLETE_STRING;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/atrhndl.hxx b/sw/source/core/text/atrhndl.hxx
new file mode 100644
index 0000000000..851615325a
--- /dev/null
+++ b/sw/source/core/text/atrhndl.hxx
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+#define NUM_ATTRIBUTE_STACKS 45
+
+#include <vector>
+#include <swfntcch.hxx>
+
+class SwTextAttr;
+class SwAttrSet;
+class IDocumentSettingAccess;
+class SwViewShell;
+class SfxPoolItem;
+extern const sal_uInt8 StackPos[];
+
+/**
+ * Used by Attribute Iterators to organize attributes on stacks to
+ * find the valid attribute in each category
+ */
+class SwAttrHandler
+{
+private:
+ std::vector<const SwTextAttr*> m_aAttrStack[NUM_ATTRIBUTE_STACKS]; // stack collection
+ const SfxPoolItem* m_pDefaultArray[ NUM_DEFAULT_VALUES ];
+ const IDocumentSettingAccess* m_pIDocumentSettingAccess;
+ const SwViewShell* m_pShell;
+
+ // This is the base font for the paragraph. It is stored in order to have
+ // a template, if we have to restart the attribute evaluation
+ std::optional<SwFont> m_oFnt;
+
+ bool m_bVertLayout;
+ bool m_bVertLayoutLRBT;
+
+ const SwTextAttr* GetTop(sal_uInt16 nStack);
+ void RemoveFromStack(sal_uInt16 nWhich, const SwTextAttr& rAttr);
+
+ // change font according to pool item
+ void FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush );
+
+ // push attribute to specified stack, returns true, if attribute has
+ // been pushed on top of stack (important for stacks containing different
+ // attributes with different priority and redlining)
+ bool Push( const SwTextAttr& rAttr, const SfxPoolItem& rItem );
+
+ // apply top attribute on stack to font
+ void ActivateTop( SwFont& rFnt, sal_uInt16 nStackPos );
+
+public:
+ // Ctor
+ SwAttrHandler();
+ ~SwAttrHandler();
+
+ // set default attributes to values in rAttrSet or from cache
+ void Init( const SwAttrSet& rAttrSet,
+ const IDocumentSettingAccess& rIDocumentSettingAccess );
+ void Init( const SfxPoolItem** pPoolItem, const SwAttrSet* pAttrSet,
+ const IDocumentSettingAccess& rIDocumentSettingAccess,
+ const SwViewShell* pShell, SwFont& rFnt,
+ bool bVertLayout, bool bVertLayoutLRBT );
+
+ bool IsVertLayout() const { return m_bVertLayout; }
+
+ // remove everything from internal stacks, keep default data
+ void Reset( );
+
+ // insert specified attribute and change font
+ void PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt );
+
+ // remove specified attribute and reset font
+ void PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt );
+ void Pop( const SwTextAttr& rAttr );
+
+ // apply script dependent attributes
+ // void ChangeScript( SwFont& rFnt, const sal_uInt8 nScr );
+
+ // do not call these if you only used the small init function
+ inline void ResetFont( SwFont& rFnt ) const;
+ inline const SwFont* GetFont() const;
+
+ void GetDefaultAscentAndHeight(SwViewShell const * pShell,
+ OutputDevice const & rOut,
+ sal_uInt16& nAscent,
+ sal_uInt16& nHeight) const;
+};
+
+inline void SwAttrHandler::ResetFont( SwFont& rFnt ) const
+{
+ OSL_ENSURE(m_oFnt, "ResetFont without a font");
+ if (m_oFnt)
+ rFnt = *m_oFnt;
+};
+
+inline const SwFont* SwAttrHandler::GetFont() const
+{
+ return m_oFnt ? &*m_oFnt : nullptr;
+};
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/atrstck.cxx b/sw/source/core/text/atrstck.cxx
new file mode 100644
index 0000000000..048878292f
--- /dev/null
+++ b/sw/source/core/text/atrstck.cxx
@@ -0,0 +1,851 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "atrhndl.hxx"
+#include <svl/itemiter.hxx>
+#include <vcl/outdev.hxx>
+#include <editeng/cmapitem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/autokernitem.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/twolinesitem.hxx>
+#include <editeng/charhiddenitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <viewopt.hxx>
+#include <charfmt.hxx>
+#include <fchrfmt.hxx>
+#include <fmtautofmt.hxx>
+#include <editeng/brushitem.hxx>
+#include <fmtinfmt.hxx>
+#include <txtinet.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <viewsh.hxx>
+
+/**
+ * Attribute to Stack Mapping
+ *
+ * Attributes applied to a text are pushed on different stacks. For each
+ * stack, the top most attribute on the stack is valid. Because some
+ * kinds of attributes have to be pushed to the same stacks we map their
+ * ids to stack ids
+ * Attention: The first NUM_DEFAULT_VALUES ( defined in swfntcch.hxx )
+ * are stored in the defaultitem-cache, if you add one, you have to increase
+ * NUM_DEFAULT_VALUES.
+ * Also adjust NUM_ATTRIBUTE_STACKS in atrhndl.hxx.
+ */
+const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN + 1 ] =
+{
+ 0, // // 0
+ 1, // RES_CHRATR_CASEMAP = RES_CHRATR_BEGIN // 1
+ 0, // RES_CHRATR_CHARSETCOLOR, // 2
+ 2, // RES_CHRATR_COLOR, // 3
+ 3, // RES_CHRATR_CONTOUR, // 4
+ 4, // RES_CHRATR_CROSSEDOUT, // 5
+ 5, // RES_CHRATR_ESCAPEMENT, // 6
+ 6, // RES_CHRATR_FONT, // 7
+ 7, // RES_CHRATR_FONTSIZE, // 8
+ 8, // RES_CHRATR_KERNING, // 9
+ 9, // RES_CHRATR_LANGUAGE, // 10
+ 10, // RES_CHRATR_POSTURE, // 11
+ 0, // RES_CHRATR_UNUSED1, // 12
+ 11, // RES_CHRATR_SHADOWED, // 13
+ 12, // RES_CHRATR_UNDERLINE, // 14
+ 13, // RES_CHRATR_WEIGHT, // 15
+ 14, // RES_CHRATR_WORDLINEMODE, // 16
+ 15, // RES_CHRATR_AUTOKERN, // 17
+ 16, // RES_CHRATR_BLINK, // 18
+ 17, // RES_CHRATR_NOHYPHEN, // 19
+ 0, // RES_CHRATR_UNUSED2, // 20
+ 18, // RES_CHRATR_BACKGROUND, // 21
+ 19, // RES_CHRATR_CJK_FONT, // 22
+ 20, // RES_CHRATR_CJK_FONTSIZE, // 23
+ 21, // RES_CHRATR_CJK_LANGUAGE, // 24
+ 22, // RES_CHRATR_CJK_POSTURE, // 25
+ 23, // RES_CHRATR_CJK_WEIGHT, // 26
+ 24, // RES_CHRATR_CTL_FONT, // 27
+ 25, // RES_CHRATR_CTL_FONTSIZE, // 28
+ 26, // RES_CHRATR_CTL_LANGUAGE, // 29
+ 27, // RES_CHRATR_CTL_POSTURE, // 30
+ 28, // RES_CHRATR_CTL_WEIGHT, // 31
+ 29, // RES_CHRATR_ROTATE, // 32
+ 30, // RES_CHRATR_EMPHASIS_MARK, // 33
+ 31, // RES_CHRATR_TWO_LINES, // 34
+ 32, // RES_CHRATR_SCALEW, // 35
+ 33, // RES_CHRATR_RELIEF, // 36
+ 34, // RES_CHRATR_HIDDEN, // 37
+ 35, // RES_CHRATR_OVERLINE, // 38
+ 0, // RES_CHRATR_RSID, // 39
+ 36, // RES_CHRATR_BOX, // 40
+ 37, // RES_CHRATR_SHADOW, // 41
+ 38, // RES_CHRATR_HIGHLIGHT, // 42
+ 0, // RES_CHRATR_GRABBAG, // 43
+ 0, // RES_CHRATR_BIDIRTL, // 44
+ 0, // RES_CHRATR_IDCTHINT, // 45
+ 39, // RES_TXTATR_REFMARK, // 46
+ 40, // RES_TXTATR_TOXMARK, // 47
+ 41, // RES_TXTATR_META, // 48
+ 41, // RES_TXTATR_METAFIELD, // 49
+ 0, // RES_TXTATR_AUTOFMT, // 50
+ 0, // RES_TXTATR_INETFMT // 51
+ 0, // RES_TXTATR_CHARFMT, // 52
+ 42, // RES_TXTATR_CJK_RUBY, // 53
+ 0, // RES_TXTATR_UNKNOWN_CONTAINER, // 54
+ 43, // RES_TXTATR_INPUTFIELD // 55
+ 44, // RES_TXTATR_CONTENTCONTROL // 56
+};
+
+namespace CharFormat
+{
+
+/// Returns the item set associated with a character/inet/auto style
+const SfxItemSet* GetItemSet( const SfxPoolItem& rAttr )
+{
+ const SfxItemSet* pSet = nullptr;
+
+ if ( RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ pSet = rAttr.StaticWhichCast(RES_TXTATR_AUTOFMT).GetStyleHandle().get();
+ }
+ else
+ {
+ // Get the attributes from the template
+ const SwCharFormat* pFormat = RES_TXTATR_INETFMT == rAttr.Which() ?
+ rAttr.StaticWhichCast(RES_TXTATR_INETFMT).GetTextINetFormat()->GetCharFormat() :
+ static_cast<const SwFormatCharFormat&>(rAttr).GetCharFormat();
+ if( pFormat )
+ {
+ pSet = &pFormat->GetAttrSet();
+ }
+ }
+
+ return pSet;
+}
+
+/// Extracts pool item of type nWhich from rAttr
+const SfxPoolItem* GetItem( const SwTextAttr& rAttr, sal_uInt16 nWhich )
+{
+ if ( RES_TXTATR_INETFMT == rAttr.Which() ||
+ RES_TXTATR_CHARFMT == rAttr.Which() ||
+ RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
+ if ( !pSet ) return nullptr;
+
+ bool bInParent = RES_TXTATR_AUTOFMT != rAttr.Which();
+ const SfxPoolItem* pItem;
+ bool bRet = SfxItemState::SET == pSet->GetItemState( nWhich, bInParent, &pItem );
+
+ return bRet ? pItem : nullptr;
+ }
+
+ return ( nWhich == rAttr.Which() ) ? &rAttr.GetAttr() : nullptr;
+}
+
+/// Checks if item is included in character/inet/auto style
+bool IsItemIncluded( const sal_uInt16 nWhich, const SwTextAttr *pAttr )
+{
+ bool bRet = false;
+
+ const SfxItemSet* pItemSet = CharFormat::GetItemSet( pAttr->GetAttr() );
+ if ( pItemSet )
+ bRet = SfxItemState::SET == pItemSet->GetItemState( nWhich );
+
+ return bRet;
+}
+}
+
+/**
+ * The color of hyperlinks is taken from the associated character attribute,
+ * depending on its 'visited' state. There are actually two cases, which
+ * should override the colors from the character attribute:
+ * 1. We never take the 'visited' color during printing/pdf export/preview
+ * 2. The user has chosen to override these colors in the view options
+ */
+static bool lcl_ChgHyperLinkColor( const SwTextAttr& rAttr,
+ const SfxPoolItem& rItem,
+ const SwViewShell* pShell,
+ Color* pColor )
+{
+ if ( !pShell ||
+ RES_TXTATR_INETFMT != rAttr.Which() ||
+ RES_CHRATR_COLOR != rItem.Which() )
+ return false;
+
+ // #i15455#
+ // 1. case:
+ // We do not want to show visited links:
+ // (printing, pdf export, page preview)
+
+ SwTextINetFormat & rINetAttr(const_cast<SwTextINetFormat&>(
+ static_txtattr_cast<SwTextINetFormat const&>(rAttr)));
+ if ( pShell->GetOut()->GetOutDevType() == OUTDEV_PRINTER ||
+ pShell->GetViewOptions()->IsPDFExport() ||
+ pShell->GetViewOptions()->IsPagePreview() )
+ {
+ if (rINetAttr.IsVisited())
+ {
+ if ( pColor )
+ {
+ // take color from character format 'unvisited link'
+ rINetAttr.SetVisited(false);
+ const SwCharFormat* pTmpFormat = rINetAttr.GetCharFormat();
+ if (const SvxColorItem* pItem = pTmpFormat->GetItemIfSet(RES_CHRATR_COLOR))
+ *pColor = pItem->GetValue();
+ rINetAttr.SetVisited(true);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ // 2. case:
+ // We do not want to apply the color set in the hyperlink
+ // attribute, instead we take the colors from the view options:
+
+ if ( pShell->GetWin() &&
+ (
+ (rINetAttr.IsVisited() && pShell->GetViewOptions()->IsVisitedLinks()) ||
+ (!rINetAttr.IsVisited() && pShell->GetViewOptions()->IsLinks())
+ )
+ )
+ {
+ if ( pColor )
+ {
+ if (rINetAttr.IsVisited())
+ {
+ // take color from view option 'visited link color'
+ *pColor = pShell->GetViewOptions()->GetVisitedLinksColor();
+ }
+ else
+ {
+ // take color from view option 'unvisited link color'
+ *pColor = pShell->GetViewOptions()->GetLinksColor();
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+SwAttrHandler::SwAttrHandler()
+ : m_pIDocumentSettingAccess(nullptr)
+ , m_pShell(nullptr)
+ , m_bVertLayout(false)
+ , m_bVertLayoutLRBT(false)
+{
+ memset( m_pDefaultArray, 0, NUM_DEFAULT_VALUES * sizeof(SfxPoolItem*) );
+}
+
+SwAttrHandler::~SwAttrHandler()
+{
+}
+
+void SwAttrHandler::Init( const SwAttrSet& rAttrSet,
+ const IDocumentSettingAccess& rIDocumentSettingAcces )
+{
+ m_pIDocumentSettingAccess = &rIDocumentSettingAcces;
+ m_pShell = nullptr;
+
+ for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++ )
+ m_pDefaultArray[ StackPos[ i ] ] = &rAttrSet.Get( i );
+}
+
+void SwAttrHandler::Init( const SfxPoolItem** pPoolItem, const SwAttrSet* pAS,
+ const IDocumentSettingAccess& rIDocumentSettingAcces,
+ const SwViewShell* pSh,
+ SwFont& rFnt, bool bVL, bool bVertLayoutLRBT )
+{
+ // initialize default array
+ memcpy( m_pDefaultArray, pPoolItem,
+ NUM_DEFAULT_VALUES * sizeof(SfxPoolItem*) );
+
+ m_pIDocumentSettingAccess = &rIDocumentSettingAcces;
+ m_pShell = pSh;
+
+ // do we have to apply additional paragraph attributes?
+ m_bVertLayout = bVL;
+ m_bVertLayoutLRBT = bVertLayoutLRBT;
+
+ if ( pAS && pAS->Count() )
+ {
+ SfxItemIter aIter( *pAS );
+ sal_uInt16 nWhich;
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ do
+ {
+ nWhich = pItem->Which();
+ if (isCHRATR(nWhich))
+ {
+ m_pDefaultArray[ StackPos[ nWhich ] ] = pItem;
+ FontChg( *pItem, rFnt, true );
+ }
+
+ pItem = aIter.NextItem();
+ } while (pItem);
+ }
+
+ // It is possible, that Init is called more than once, e.g., in a
+ // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide)
+ // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt
+ // is an alias of m_pFnt so it must not be deleted!
+ if (m_oFnt)
+ *m_oFnt = rFnt;
+ else
+ m_oFnt.emplace(rFnt);
+}
+
+void SwAttrHandler::Reset( )
+{
+ for (auto& i : m_aAttrStack)
+ i.clear();
+}
+
+void SwAttrHandler::PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt )
+{
+ // these special attributes in fact represent a collection of attributes
+ // they have to be pushed to each stack they belong to
+ if ( RES_TXTATR_INETFMT == rAttr.Which() ||
+ RES_TXTATR_CHARFMT == rAttr.Which() ||
+ RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
+ if ( !pSet ) return;
+
+ for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++)
+ {
+ const SfxPoolItem* pItem;
+ bool bRet = SfxItemState::SET == pSet->GetItemState( i, rAttr.Which() != RES_TXTATR_AUTOFMT, &pItem );
+
+ if ( bRet )
+ {
+ // we push rAttr onto the appropriate stack
+ if ( Push( rAttr, *pItem ) )
+ {
+ // we let pItem change rFnt
+ Color aColor;
+ if (lcl_ChgHyperLinkColor(rAttr, *pItem, m_pShell, &aColor))
+ {
+ SvxColorItem aItemNext( aColor, RES_CHRATR_COLOR );
+ FontChg( aItemNext, rFnt, true );
+ }
+ else
+ FontChg( *pItem, rFnt, true );
+ }
+ }
+ }
+ }
+ // this is the usual case, we have a basic attribute, push it onto the
+ // stack and change the font
+ else
+ {
+ if ( Push( rAttr, rAttr.GetAttr() ) )
+ // we let pItem change rFnt
+ FontChg( rAttr.GetAttr(), rFnt, true );
+ }
+}
+
+const SwTextAttr* SwAttrHandler::GetTop(sal_uInt16 nStack)
+{
+ return m_aAttrStack[nStack].empty() ? nullptr : m_aAttrStack[nStack].back();
+}
+
+bool SwAttrHandler::Push( const SwTextAttr& rAttr, const SfxPoolItem& rItem )
+{
+ OSL_ENSURE( rItem.Which() < RES_TXTATR_WITHEND_END,
+ "I do not want this attribute, nWhich >= RES_TXTATR_WITHEND_END" );
+
+ // robust
+ if ( RES_TXTATR_WITHEND_END <= rItem.Which() )
+ return false;
+
+ const sal_uInt16 nStack = StackPos[ rItem.Which() ];
+
+ // attributes originating from redlining have highest priority
+ // second priority are hyperlink attributes, which have a color replacement
+ const SwTextAttr* pTopAttr = GetTop(nStack);
+ if ( !pTopAttr
+ || rAttr.IsPriorityAttr()
+ || ( !pTopAttr->IsPriorityAttr()
+ && !lcl_ChgHyperLinkColor(*pTopAttr, rItem, m_pShell, nullptr)))
+ {
+ m_aAttrStack[nStack].push_back(&rAttr);
+ return true;
+ }
+
+ const auto it = m_aAttrStack[nStack].end() - 1;
+ m_aAttrStack[nStack].insert(it, &rAttr);
+ return false;
+}
+
+void SwAttrHandler::RemoveFromStack(sal_uInt16 nWhich, const SwTextAttr& rAttr)
+{
+ auto& rStack = m_aAttrStack[StackPos[nWhich]];
+ const auto it = std::find(rStack.begin(), rStack.end(), &rAttr);
+ if (it != rStack.end())
+ rStack.erase(it);
+}
+
+void SwAttrHandler::PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt )
+{
+ if ( RES_TXTATR_WITHEND_END <= rAttr.Which() )
+ return; // robust
+
+ // these special attributes in fact represent a collection of attributes
+ // they have to be removed from each stack they belong to
+ if ( RES_TXTATR_INETFMT == rAttr.Which() ||
+ RES_TXTATR_CHARFMT == rAttr.Which() ||
+ RES_TXTATR_AUTOFMT == rAttr.Which() )
+ {
+ const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() );
+ if ( !pSet ) return;
+
+ for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++)
+ {
+ const SfxPoolItem* pItem;
+ bool bRet = SfxItemState::SET == pSet->GetItemState( i, RES_TXTATR_AUTOFMT != rAttr.Which(), &pItem );
+ if ( bRet )
+ {
+ // we remove rAttr from the appropriate stack
+ RemoveFromStack(i, rAttr);
+ // reset font according to attribute on top of stack
+ // or default value
+ ActivateTop( rFnt, i );
+ }
+ }
+ }
+ // this is the usual case, we have a basic attribute, remove it from the
+ // stack and reset the font
+ else
+ {
+ RemoveFromStack(rAttr.Which(), rAttr);
+ // reset font according to attribute on top of stack
+ // or default value
+ ActivateTop( rFnt, rAttr.Which() );
+ }
+}
+
+/// Only used during redlining
+void SwAttrHandler::Pop( const SwTextAttr& rAttr )
+{
+ OSL_ENSURE( rAttr.Which() < RES_TXTATR_WITHEND_END,
+ "I do not have this attribute, nWhich >= RES_TXTATR_WITHEND_END" );
+
+ if ( rAttr.Which() < RES_TXTATR_WITHEND_END )
+ {
+ RemoveFromStack(rAttr.Which(), rAttr);
+ }
+}
+
+void SwAttrHandler::ActivateTop( SwFont& rFnt, const sal_uInt16 nAttr )
+{
+ OSL_ENSURE( nAttr < RES_TXTATR_WITHEND_END,
+ "I cannot activate this attribute, nWhich >= RES_TXTATR_WITHEND_END" );
+
+ const sal_uInt16 nStackPos = StackPos[ nAttr ];
+ const SwTextAttr* pTopAt = GetTop(nStackPos);
+ if ( pTopAt )
+ {
+ const SfxPoolItem* pItemNext(nullptr);
+
+ // check if top attribute is collection of attributes
+ if ( RES_TXTATR_INETFMT == pTopAt->Which() ||
+ RES_TXTATR_CHARFMT == pTopAt->Which() ||
+ RES_TXTATR_AUTOFMT == pTopAt->Which() )
+ {
+ const SfxItemSet* pSet = CharFormat::GetItemSet( pTopAt->GetAttr() );
+ if (pSet)
+ pSet->GetItemState( nAttr, RES_TXTATR_AUTOFMT != pTopAt->Which(), &pItemNext );
+ }
+
+ if (pItemNext)
+ {
+ Color aColor;
+ if (lcl_ChgHyperLinkColor(*pTopAt, *pItemNext, m_pShell, &aColor))
+ {
+ SvxColorItem aItemNext( aColor, RES_CHRATR_COLOR );
+ FontChg( aItemNext, rFnt, false );
+ }
+ else
+ FontChg( *pItemNext, rFnt, false );
+ }
+ else
+ FontChg( pTopAt->GetAttr(), rFnt, false );
+ }
+
+ // default value has to be set, we only have default values for char attribs
+ else if ( nStackPos < NUM_DEFAULT_VALUES )
+ FontChg( *m_pDefaultArray[ nStackPos ], rFnt, false );
+ else if ( RES_TXTATR_REFMARK == nAttr )
+ rFnt.GetRef()--;
+ else if ( RES_TXTATR_TOXMARK == nAttr )
+ rFnt.GetTox()--;
+ else if ( (RES_TXTATR_META == nAttr) || (RES_TXTATR_METAFIELD == nAttr) )
+ {
+ rFnt.GetMeta()--;
+ }
+ else if (nAttr == RES_TXTATR_CONTENTCONTROL)
+ {
+ rFnt.GetContentControl()--;
+ }
+ else if ( RES_TXTATR_CJK_RUBY == nAttr )
+ {
+ // ruby stack has no more attributes
+ // check, if a rotation attribute has to be applied
+ const sal_uInt16 nTwoLineStack = StackPos[ RES_CHRATR_TWO_LINES ];
+ bool bTwoLineAct = false;
+ const SwTextAttr* pTwoLineAttr = GetTop(nTwoLineStack);
+
+ if ( pTwoLineAttr )
+ {
+ const auto& rTwoLineItem = *CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES );
+ bTwoLineAct = rTwoLineItem.GetValue();
+ }
+ else
+ bTwoLineAct = m_pDefaultArray[ nTwoLineStack ]->StaticWhichCast(RES_CHRATR_TWO_LINES).GetValue();
+
+ if ( bTwoLineAct )
+ return;
+
+ // eventually, a rotate attribute has to be activated
+ const sal_uInt16 nRotateStack = StackPos[ RES_CHRATR_ROTATE ];
+ const SwTextAttr* pRotateAttr = GetTop(nRotateStack);
+
+ if ( pRotateAttr )
+ {
+ const auto& rRotateItem = *CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE );
+ rFnt.SetVertical( rRotateItem.GetValue(), m_bVertLayout );
+ }
+ else
+ rFnt.SetVertical( m_pDefaultArray[ nRotateStack ]->StaticWhichCast(RES_CHRATR_ROTATE).GetValue(), m_bVertLayout );
+ }
+ else if ( RES_TXTATR_INPUTFIELD == nAttr )
+ rFnt.GetInputField()--;
+}
+
+/**
+ * When popping an attribute from the stack, the top more remaining
+ * attribute in the stack becomes valid. The following function change
+ * a font depending on the stack id.
+ */
+void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush )
+{
+ switch ( rItem.Which() )
+ {
+ case RES_CHRATR_CASEMAP :
+ rFnt.SetCaseMap( rItem.StaticWhichCast(RES_CHRATR_CASEMAP).GetCaseMap() );
+ break;
+ case RES_CHRATR_COLOR :
+ rFnt.SetColor( rItem.StaticWhichCast(RES_CHRATR_COLOR).GetValue() );
+ break;
+ case RES_CHRATR_CONTOUR :
+ rFnt.SetOutline( rItem.StaticWhichCast(RES_CHRATR_CONTOUR).GetValue() );
+ break;
+ case RES_CHRATR_CROSSEDOUT :
+ rFnt.SetStrikeout( rItem.StaticWhichCast(RES_CHRATR_CROSSEDOUT).GetStrikeout() );
+ break;
+ case RES_CHRATR_ESCAPEMENT :
+ rFnt.SetEscapement( rItem.StaticWhichCast(RES_CHRATR_ESCAPEMENT).GetEsc() );
+ rFnt.SetProportion( rItem.StaticWhichCast(RES_CHRATR_ESCAPEMENT).GetProportionalHeight() );
+ break;
+ case RES_CHRATR_FONT :
+ {
+ auto& rFontItem = rItem.StaticWhichCast(RES_CHRATR_FONT);
+ rFnt.SetName( rFontItem.GetFamilyName(), SwFontScript::Latin );
+ rFnt.SetStyleName( rFontItem.GetStyleName(), SwFontScript::Latin );
+ rFnt.SetFamily( rFontItem.GetFamily(), SwFontScript::Latin );
+ rFnt.SetPitch( rFontItem.GetPitch(), SwFontScript::Latin );
+ rFnt.SetCharSet( rFontItem.GetCharSet(), SwFontScript::Latin );
+ break;
+ }
+ case RES_CHRATR_FONTSIZE :
+ rFnt.SetSize(Size(0, rItem.StaticWhichCast(RES_CHRATR_FONTSIZE).GetHeight() ), SwFontScript::Latin );
+ break;
+ case RES_CHRATR_KERNING :
+ rFnt.SetFixKerning( rItem.StaticWhichCast(RES_CHRATR_KERNING).GetValue() );
+ break;
+ case RES_CHRATR_LANGUAGE :
+ rFnt.SetLanguage( rItem.StaticWhichCast(RES_CHRATR_LANGUAGE).GetLanguage(), SwFontScript::Latin );
+ break;
+ case RES_CHRATR_POSTURE :
+ rFnt.SetItalic( rItem.StaticWhichCast(RES_CHRATR_POSTURE).GetPosture(), SwFontScript::Latin );
+ break;
+ case RES_CHRATR_SHADOWED :
+ rFnt.SetShadow( rItem.StaticWhichCast(RES_CHRATR_SHADOWED).GetValue() );
+ break;
+ case RES_CHRATR_UNDERLINE :
+ {
+ const sal_uInt16 nStackPos = StackPos[ RES_CHRATR_HIDDEN ];
+ const SwTextAttr* pTopAt = GetTop(nStackPos);
+
+ const SfxPoolItem* pTmpItem = pTopAt ?
+ CharFormat::GetItem( *pTopAt, RES_CHRATR_HIDDEN ) :
+ m_pDefaultArray[ nStackPos ];
+
+ if ((m_pShell && !m_pShell->GetWin()) ||
+ (pTmpItem && !pTmpItem->StaticWhichCast(RES_CHRATR_HIDDEN).GetValue()) )
+ {
+ rFnt.SetUnderline( rItem.StaticWhichCast(RES_CHRATR_UNDERLINE).GetLineStyle() );
+ rFnt.SetUnderColor( rItem.StaticWhichCast(RES_CHRATR_UNDERLINE).GetColor() );
+ }
+ break;
+ }
+ case RES_CHRATR_BOX:
+ {
+ const SvxBoxItem& aBoxItem = rItem.StaticWhichCast(RES_CHRATR_BOX);
+ rFnt.SetTopBorder( aBoxItem.GetTop() );
+ rFnt.SetBottomBorder( aBoxItem.GetBottom() );
+ rFnt.SetRightBorder( aBoxItem.GetRight() );
+ rFnt.SetLeftBorder( aBoxItem.GetLeft() );
+ rFnt.SetTopBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::TOP) );
+ rFnt.SetBottomBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::BOTTOM) );
+ rFnt.SetRightBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::RIGHT) );
+ rFnt.SetLeftBorderDist( aBoxItem.GetDistance(SvxBoxItemLine::LEFT) );
+ break;
+ }
+ case RES_CHRATR_SHADOW:
+ {
+ const SvxShadowItem& aShadowItem = rItem.StaticWhichCast(RES_CHRATR_SHADOW);
+ rFnt.SetShadowColor( aShadowItem.GetColor() );
+ rFnt.SetShadowWidth( aShadowItem.GetWidth() );
+ rFnt.SetShadowLocation( aShadowItem.GetLocation() );
+ break;
+ }
+ case RES_CHRATR_OVERLINE :
+ rFnt.SetOverline( rItem.StaticWhichCast(RES_CHRATR_OVERLINE).GetLineStyle() );
+ rFnt.SetOverColor( rItem.StaticWhichCast(RES_CHRATR_OVERLINE).GetColor() );
+ break;
+ case RES_CHRATR_WEIGHT :
+ rFnt.SetWeight( rItem.StaticWhichCast(RES_CHRATR_WEIGHT).GetWeight(), SwFontScript::Latin );
+ break;
+ case RES_CHRATR_WORDLINEMODE :
+ rFnt.SetWordLineMode( rItem.StaticWhichCast(RES_CHRATR_WORDLINEMODE).GetValue() );
+ break;
+ case RES_CHRATR_AUTOKERN :
+ if( rItem.StaticWhichCast(RES_CHRATR_AUTOKERN).GetValue() )
+ {
+ rFnt.SetAutoKern( (!m_pIDocumentSettingAccess ||
+ !m_pIDocumentSettingAccess->get(DocumentSettingId::KERN_ASIAN_PUNCTUATION)) ?
+ FontKerning::FontSpecific :
+ FontKerning::Asian );
+ }
+ else
+ rFnt.SetAutoKern( FontKerning::NONE );
+ break;
+ case RES_CHRATR_BACKGROUND :
+ rFnt.SetBackColor(rItem.StaticWhichCast(RES_CHRATR_BACKGROUND).GetColor());
+ break;
+ case RES_CHRATR_HIGHLIGHT :
+ rFnt.SetHighlightColor( rItem.StaticWhichCast(RES_CHRATR_HIGHLIGHT).GetColor() );
+ break;
+ case RES_CHRATR_CJK_FONT :
+ {
+ auto& rFontItem = rItem.StaticWhichCast(RES_CHRATR_CJK_FONT);
+ rFnt.SetName( rFontItem.GetFamilyName(), SwFontScript::CJK );
+ rFnt.SetStyleName( rFontItem.GetStyleName(), SwFontScript::CJK );
+ rFnt.SetFamily( rFontItem.GetFamily(), SwFontScript::CJK );
+ rFnt.SetPitch( rFontItem.GetPitch(), SwFontScript::CJK );
+ rFnt.SetCharSet( rFontItem.GetCharSet(), SwFontScript::CJK );
+ break;
+ }
+ case RES_CHRATR_CJK_FONTSIZE :
+ rFnt.SetSize(Size( 0, rItem.StaticWhichCast(RES_CHRATR_CJK_FONTSIZE).GetHeight()), SwFontScript::CJK);
+ break;
+ case RES_CHRATR_CJK_LANGUAGE :
+ rFnt.SetLanguage( rItem.StaticWhichCast(RES_CHRATR_CJK_LANGUAGE).GetLanguage(), SwFontScript::CJK );
+ break;
+ case RES_CHRATR_CJK_POSTURE :
+ rFnt.SetItalic( rItem.StaticWhichCast(RES_CHRATR_CJK_POSTURE).GetPosture(), SwFontScript::CJK );
+ break;
+ case RES_CHRATR_CJK_WEIGHT :
+ rFnt.SetWeight( rItem.StaticWhichCast(RES_CHRATR_CJK_WEIGHT).GetWeight(), SwFontScript::CJK );
+ break;
+ case RES_CHRATR_CTL_FONT :
+ {
+ auto& rFontItem = rItem.StaticWhichCast(RES_CHRATR_CTL_FONT);
+ rFnt.SetName( rFontItem.GetFamilyName(), SwFontScript::CTL );
+ rFnt.SetStyleName( rFontItem.GetStyleName(), SwFontScript::CTL );
+ rFnt.SetFamily( rFontItem.GetFamily(), SwFontScript::CTL );
+ rFnt.SetPitch( rFontItem.GetPitch(), SwFontScript::CTL );
+ rFnt.SetCharSet( rFontItem.GetCharSet(), SwFontScript::CTL );
+ break;
+ }
+ case RES_CHRATR_CTL_FONTSIZE :
+ rFnt.SetSize(Size(0, rItem.StaticWhichCast(RES_CHRATR_CTL_FONTSIZE).GetHeight() ), SwFontScript::CTL);
+ break;
+ case RES_CHRATR_CTL_LANGUAGE :
+ rFnt.SetLanguage( rItem.StaticWhichCast(RES_CHRATR_CTL_LANGUAGE).GetLanguage(), SwFontScript::CTL );
+ break;
+ case RES_CHRATR_CTL_POSTURE :
+ rFnt.SetItalic( rItem.StaticWhichCast(RES_CHRATR_CTL_POSTURE).GetPosture(), SwFontScript::CTL );
+ break;
+ case RES_CHRATR_CTL_WEIGHT :
+ rFnt.SetWeight( rItem.StaticWhichCast(RES_CHRATR_CTL_WEIGHT).GetWeight(), SwFontScript::CTL );
+ break;
+ case RES_CHRATR_EMPHASIS_MARK :
+ rFnt.SetEmphasisMark( rItem.StaticWhichCast(RES_CHRATR_EMPHASIS_MARK).GetEmphasisMark() );
+ break;
+ case RES_CHRATR_SCALEW :
+ rFnt.SetPropWidth( rItem.StaticWhichCast(RES_CHRATR_SCALEW).GetValue() );
+ break;
+ case RES_CHRATR_RELIEF :
+ rFnt.SetRelief( rItem.StaticWhichCast(RES_CHRATR_RELIEF).GetValue() );
+ break;
+ case RES_CHRATR_HIDDEN :
+ if (m_pShell && m_pShell->GetWin())
+ {
+ if ( rItem.StaticWhichCast(RES_CHRATR_HIDDEN).GetValue() )
+ rFnt.SetUnderline( LINESTYLE_DOTTED );
+ else
+ ActivateTop( rFnt, RES_CHRATR_UNDERLINE );
+ }
+ break;
+ case RES_CHRATR_ROTATE :
+ {
+ // rotate attribute is applied, when:
+ // 1. ruby stack is empty and
+ // 2. top of two line stack ( or default attribute )is an
+ // deactivated two line attribute
+ const bool bRuby =
+ 0 != m_aAttrStack[ StackPos[ RES_TXTATR_CJK_RUBY ] ].size();
+
+ if ( bRuby )
+ break;
+
+ const sal_uInt16 nTwoLineStack = StackPos[ RES_CHRATR_TWO_LINES ];
+ bool bTwoLineAct = false;
+ const SwTextAttr* pTwoLineAttr = GetTop(nTwoLineStack);
+
+ if ( pTwoLineAttr )
+ {
+ const auto& rTwoLineItem = *CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES );
+ bTwoLineAct = rTwoLineItem.GetValue();
+ }
+ else
+ bTwoLineAct = m_pDefaultArray[ nTwoLineStack ]->StaticWhichCast(RES_CHRATR_TWO_LINES).GetValue();
+
+ if ( !bTwoLineAct )
+ rFnt.SetVertical( rItem.StaticWhichCast(RES_CHRATR_ROTATE).GetValue(), m_bVertLayout, m_bVertLayoutLRBT );
+
+ break;
+ }
+ case RES_CHRATR_TWO_LINES :
+ {
+ bool bRuby = 0 !=
+ m_aAttrStack[ StackPos[ RES_TXTATR_CJK_RUBY ] ].size();
+
+ // two line is activated, if
+ // 1. no ruby attribute is set and
+ // 2. attribute is active
+ if ( !bRuby && rItem.StaticWhichCast(RES_CHRATR_TWO_LINES).GetValue() )
+ {
+ rFnt.SetVertical( 0_deg10, m_bVertLayout );
+ break;
+ }
+
+ // a deactivating two line attribute is on top of stack,
+ // check if rotate attribute has to be enabled
+ if ( bRuby )
+ break;
+
+ const sal_uInt16 nRotateStack = StackPos[ RES_CHRATR_ROTATE ];
+ const SwTextAttr* pRotateAttr = GetTop(nRotateStack);
+
+ if ( pRotateAttr )
+ {
+ const auto& rRotateItem = *CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE );
+ rFnt.SetVertical( rRotateItem.GetValue(), m_bVertLayout );
+ }
+ else
+ rFnt.SetVertical(m_pDefaultArray[ nRotateStack ]->StaticWhichCast(RES_CHRATR_ROTATE).GetValue(), m_bVertLayout);
+ break;
+ }
+ case RES_TXTATR_CJK_RUBY :
+ rFnt.SetVertical( 0_deg10, m_bVertLayout );
+ break;
+ case RES_TXTATR_REFMARK :
+ if ( bPush )
+ rFnt.GetRef()++;
+ else
+ rFnt.GetRef()--;
+ break;
+ case RES_TXTATR_TOXMARK :
+ if ( bPush )
+ rFnt.GetTox()++;
+ else
+ rFnt.GetTox()--;
+ break;
+ case RES_TXTATR_META:
+ case RES_TXTATR_METAFIELD:
+ if ( bPush )
+ rFnt.GetMeta()++;
+ else
+ rFnt.GetMeta()--;
+ break;
+ case RES_TXTATR_CONTENTCONTROL:
+ if (bPush)
+ {
+ rFnt.GetContentControl()++;
+ }
+ else
+ {
+ rFnt.GetContentControl()--;
+ }
+ break;
+ case RES_TXTATR_INPUTFIELD :
+ if ( bPush )
+ rFnt.GetInputField()++;
+ else
+ rFnt.GetInputField()--;
+ break;
+ }
+}
+
+/// Takes the default font and calculated the ascent and height
+void SwAttrHandler::GetDefaultAscentAndHeight( SwViewShell const * pShell, OutputDevice const & rOut,
+ sal_uInt16& nAscent, sal_uInt16& nHeight ) const
+{
+ OSL_ENSURE(m_oFnt, "No font available for GetDefaultAscentAndHeight");
+
+ if (m_oFnt)
+ {
+ SwFont aFont( *m_oFnt );
+ nHeight = aFont.GetHeight( pShell, rOut );
+ nAscent = aFont.GetAscent( pShell, rOut );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/frmcrsr.cxx b/sw/source/core/text/frmcrsr.cxx
new file mode 100644
index 0000000000..d7f7a2cab9
--- /dev/null
+++ b/sw/source/core/text/frmcrsr.cxx
@@ -0,0 +1,1691 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ndtxt.hxx>
+#include <pam.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <viewopt.hxx>
+#include <paratr.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <colfrm.hxx>
+#include <swtypes.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/lspcitem.hxx>
+#include "pormulti.hxx"
+#include <doc.hxx>
+#include <IDocumentDeviceAccess.hxx>
+#include <sortedobjs.hxx>
+
+#include <unicode/ubidi.h>
+
+#include <txtfrm.hxx>
+#include "inftxt.hxx"
+#include "itrtxt.hxx"
+#include <crstate.hxx>
+#include <viewsh.hxx>
+#include <swfntcch.hxx>
+#include <flyfrm.hxx>
+
+#define MIN_OFFSET_STEP 10
+
+using namespace ::com::sun::star;
+
+/*
+ * - SurvivalKit: For how long do we get past the last char of the line.
+ * - RightMargin abstains from adjusting position with -1
+ * - GetCharRect returns a GetEndCharRect for CursorMoveState::RightMargin
+ * - GetEndCharRect sets bRightMargin to true
+ * - SwTextCursor::bRightMargin is set to false by CharCursorToLine
+ */
+
+namespace
+{
+
+SwTextFrame *GetAdjFrameAtPos( SwTextFrame *pFrame, const SwPosition &rPos,
+ const bool bRightMargin, const bool bNoScroll = true )
+{
+ // RightMargin in the last master line
+ TextFrameIndex const nOffset = pFrame->MapModelToViewPos(rPos);
+ SwTextFrame *pFrameAtPos = pFrame;
+ if( !bNoScroll || pFrame->GetFollow() )
+ {
+ pFrameAtPos = pFrame->GetFrameAtPos( rPos );
+ if (nOffset < pFrameAtPos->GetOffset() &&
+ !pFrameAtPos->IsFollow() )
+ {
+ assert(pFrameAtPos->MapModelToViewPos(rPos) == nOffset);
+ TextFrameIndex nNew(nOffset);
+ if (nNew < TextFrameIndex(MIN_OFFSET_STEP))
+ nNew = TextFrameIndex(0);
+ else
+ nNew -= TextFrameIndex(MIN_OFFSET_STEP);
+ sw_ChangeOffset( pFrameAtPos, nNew );
+ }
+ }
+ while( pFrame != pFrameAtPos )
+ {
+ pFrame = pFrameAtPos;
+ pFrame->GetFormatted();
+ pFrameAtPos = pFrame->GetFrameAtPos( rPos );
+ }
+
+ if( nOffset && bRightMargin )
+ {
+ while (pFrameAtPos &&
+ pFrameAtPos->MapViewToModelPos(pFrameAtPos->GetOffset()) == rPos &&
+ pFrameAtPos->IsFollow() )
+ {
+ pFrameAtPos->GetFormatted();
+ pFrameAtPos = pFrameAtPos->FindMaster();
+ }
+ OSL_ENSURE( pFrameAtPos, "+GetCharRect: no frame with my rightmargin" );
+ }
+ return pFrameAtPos ? pFrameAtPos : pFrame;
+}
+
+}
+
+bool sw_ChangeOffset(SwTextFrame* pFrame, TextFrameIndex nNew)
+{
+ // Do not scroll in areas and outside of flies
+ OSL_ENSURE( !pFrame->IsFollow(), "Illegal Scrolling by Follow!" );
+ if( pFrame->GetOffset() != nNew && !pFrame->IsInSct() )
+ {
+ SwFlyFrame *pFly = pFrame->FindFlyFrame();
+ // Attention: if e.g. in a column frame the size is still invalid
+ // we must not scroll around just like that
+ if ( ( pFly && pFly->isFrameAreaDefinitionValid() &&
+ !pFly->GetNextLink() && !pFly->GetPrevLink() ) ||
+ ( !pFly && pFrame->IsInTab() ) )
+ {
+ SwViewShell* pVsh = pFrame->getRootFrame()->GetCurrShell();
+ if( pVsh )
+ {
+ if( pVsh->GetRingContainer().size() > 1 ||
+ ( pFrame->GetDrawObjs() && pFrame->GetDrawObjs()->size() ) )
+ {
+ if( !pFrame->GetOffset() )
+ return false;
+ nNew = TextFrameIndex(0);
+ }
+ pFrame->SetOffset( nNew );
+ pFrame->SetPara( nullptr );
+ pFrame->GetFormatted();
+ if( pFrame->getFrameArea().HasArea() )
+ pFrame->getRootFrame()->GetCurrShell()->InvalidateWindows( pFrame->getFrameArea() );
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+SwTextFrame& SwTextFrame::GetFrameAtOfst(TextFrameIndex const nWhere)
+{
+ SwTextFrame* pRet = this;
+ while( pRet->HasFollow() && nWhere >= pRet->GetFollow()->GetOffset() )
+ pRet = pRet->GetFollow();
+ return *pRet;
+}
+
+SwTextFrame *SwTextFrame::GetFrameAtPos( const SwPosition &rPos )
+{
+ TextFrameIndex const nPos(MapModelToViewPos(rPos));
+ SwTextFrame *pFoll = this;
+ while( pFoll->GetFollow() )
+ {
+ if (nPos > pFoll->GetFollow()->GetOffset())
+ pFoll = pFoll->GetFollow();
+ else
+ {
+ if (nPos == pFoll->GetFollow()->GetOffset()
+ && !SwTextCursor::IsRightMargin() )
+ pFoll = pFoll->GetFollow();
+ else
+ break;
+ }
+ }
+ return pFoll;
+}
+
+/*
+ * GetCharRect() returns the char's char line described by aPos.
+ * GetModelPositionForViewPoint() does the reverse: It goes from a document coordinate to
+ * a Pam.
+ * Both are virtual in the frame base class and thus are redefined here.
+ */
+
+bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos,
+ SwCursorMoveState *pCMS, bool bAllowFarAway ) const
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::GetCharRect with swapped frame" );
+
+ if (IsLocked())
+ return false;
+
+ // Find the right frame first. We need to keep in mind that:
+ // - the cached information could be invalid (GetPara() == 0)
+ // - we could have a Follow
+ // - the Follow chain grows dynamically; the one we end up in
+ // needs to be formatted
+
+ // Optimisation: reading ahead saves us a GetAdjFrameAtPos
+ const bool bRightMargin = pCMS && ( CursorMoveState::RightMargin == pCMS->m_eState );
+ const bool bNoScroll = pCMS && pCMS->m_bNoScroll;
+ SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), rPos, bRightMargin,
+ bNoScroll );
+ pFrame->GetFormatted();
+
+ const SwFrame* pTmpFrame = pFrame->GetUpper();
+ if (pTmpFrame->getFrameArea().Top() == FAR_AWAY && !bAllowFarAway)
+ return false;
+
+ SwRectFnSet aRectFnSet(pFrame);
+ const SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame);
+ const SwTwips nFrameMaxY = aRectFnSet.GetPrtBottom(*pFrame);
+
+ // nMaxY is an absolute value
+ SwTwips nMaxY = aRectFnSet.IsVert() ?
+ ( aRectFnSet.IsVertL2R() ? std::min( nFrameMaxY, nUpperMaxY ) : std::max( nFrameMaxY, nUpperMaxY ) ) :
+ std::min( nFrameMaxY, nUpperMaxY );
+
+ bool bRet = false;
+
+ if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) )
+ {
+ Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos();
+ SwTextNode const*const pTextNd(GetTextNodeForParaProps());
+ short nFirstOffset;
+ pTextNd->GetFirstLineOfsWithNum( nFirstOffset );
+
+ Point aPnt2;
+ if ( aRectFnSet.IsVert() )
+ {
+ if( nFirstOffset > 0 )
+ aPnt1.AdjustY(nFirstOffset );
+ if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() )
+ aPnt1.setX( nMaxY );
+ aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() );
+ aPnt2.setY( aPnt1.Y() );
+ if( aPnt2.X() < nMaxY )
+ aPnt2.setX( nMaxY );
+ }
+ else
+ {
+ if( nFirstOffset > 0 )
+ aPnt1.AdjustX(nFirstOffset );
+
+ if( aPnt1.Y() > nMaxY )
+ aPnt1.setY( nMaxY );
+ aPnt2.setX( aPnt1.X() );
+ aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() );
+ if( aPnt2.Y() > nMaxY )
+ aPnt2.setY( nMaxY );
+ }
+
+ rOrig = SwRect( aPnt1, aPnt2 );
+
+ if ( pCMS )
+ {
+ pCMS->m_aRealHeight.setX( 0 );
+ pCMS->m_aRealHeight.setY( aRectFnSet.IsVert() ? -rOrig.Width() : rOrig.Height() );
+ }
+
+ if ( pFrame->IsRightToLeft() )
+ pFrame->SwitchLTRtoRTL( rOrig );
+
+ bRet = true;
+ }
+ else
+ {
+ if( !pFrame->HasPara() )
+ return false;
+
+ SwFrameSwapper aSwapper( pFrame, true );
+ if ( aRectFnSet.IsVert() )
+ nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY );
+
+ bool bGoOn = true;
+ TextFrameIndex const nOffset = MapModelToViewPos(rPos);
+ assert(nOffset != TextFrameIndex(COMPLETE_STRING)); // not going to end well
+ TextFrameIndex nNextOfst;
+
+ do
+ {
+ {
+ SwTextSizeInfo aInf( pFrame );
+ SwTextCursor aLine( pFrame, &aInf );
+ nNextOfst = aLine.GetEnd();
+ // See comment in AdjustFrame
+ // Include the line's last char?
+ if (bRightMargin)
+ aLine.GetEndCharRect( &rOrig, nOffset, pCMS, nMaxY );
+ else
+ aLine.GetCharRect( &rOrig, nOffset, pCMS, nMaxY );
+ bRet = true;
+ }
+
+ if ( pFrame->IsRightToLeft() )
+ pFrame->SwitchLTRtoRTL( rOrig );
+
+ if ( aRectFnSet.IsVert() )
+ pFrame->SwitchHorizontalToVertical( rOrig );
+
+ if( pFrame->IsUndersized() && pCMS && !pFrame->GetNext() &&
+ aRectFnSet.GetBottom(rOrig) == nUpperMaxY &&
+ pFrame->GetOffset() < nOffset &&
+ !pFrame->IsFollow() && !bNoScroll &&
+ TextFrameIndex(pFrame->GetText().getLength()) != nNextOfst)
+ {
+ bGoOn = sw_ChangeOffset( pFrame, nNextOfst );
+ }
+ else
+ bGoOn = false;
+ } while ( bGoOn );
+
+ if ( pCMS )
+ {
+ if ( pFrame->IsRightToLeft() )
+ {
+ if( pCMS->m_b2Lines && pCMS->m_p2Lines)
+ {
+ pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aLine );
+ pFrame->SwitchLTRtoRTL( pCMS->m_p2Lines->aPortion );
+ }
+ }
+
+ if ( aRectFnSet.IsVert() )
+ {
+ if ( pCMS->m_bRealHeight )
+ {
+ pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() );
+ if ( pCMS->m_aRealHeight.Y() < 0 )
+ {
+ // writing direction is from top to bottom
+ pCMS->m_aRealHeight.setX( rOrig.Width() -
+ pCMS->m_aRealHeight.X() +
+ pCMS->m_aRealHeight.Y() );
+ }
+ }
+ if( pCMS->m_b2Lines && pCMS->m_p2Lines)
+ {
+ pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aLine );
+ pFrame->SwitchHorizontalToVertical( pCMS->m_p2Lines->aPortion );
+ }
+ }
+
+ }
+ }
+ if( bRet )
+ {
+ SwPageFrame *pPage = pFrame->FindPageFrame();
+ OSL_ENSURE( pPage, "Text escaped from page?" );
+ const SwTwips nOrigTop = aRectFnSet.GetTop(rOrig);
+ const SwTwips nPageTop = aRectFnSet.GetTop(pPage->getFrameArea());
+ const SwTwips nPageBott = aRectFnSet.GetBottom(pPage->getFrameArea());
+
+ // We have the following situation: if the frame is in an invalid
+ // sectionframe, it's possible that the frame is outside the page.
+ // If we restrict the cursor position to the page area, we enforce
+ // the formatting of the page, of the section frame and the frame itself.
+ if( aRectFnSet.YDiff( nPageTop, nOrigTop ) > 0 )
+ aRectFnSet.SetTop( rOrig, nPageTop );
+
+ if ( aRectFnSet.YDiff( nOrigTop, nPageBott ) > 0 )
+ aRectFnSet.SetTop( rOrig, nPageBott );
+ }
+
+ return bRet;
+}
+
+/*
+ * GetAutoPos() looks up the char's char line which is described by rPos
+ * and is used by the auto-positioned frame.
+ */
+
+bool SwTextFrame::GetAutoPos( SwRect& rOrig, const SwPosition &rPos ) const
+{
+ if( IsHiddenNow() )
+ return false;
+
+ TextFrameIndex const nOffset = MapModelToViewPos(rPos);
+ SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset ));
+
+ pFrame->GetFormatted();
+ const SwFrame* pTmpFrame = pFrame->GetUpper();
+
+ SwRectFnSet aRectFnSet(pTmpFrame);
+ SwTwips nUpperMaxY = aRectFnSet.GetPrtBottom(*pTmpFrame);
+
+ // nMaxY is in absolute value
+ SwTwips nMaxY;
+ if ( aRectFnSet.IsVert() )
+ {
+ if ( aRectFnSet.IsVertL2R() )
+ nMaxY = std::min( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY );
+ else
+ nMaxY = std::max( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY );
+ }
+ else
+ nMaxY = std::min( SwTwips(aRectFnSet.GetPrtBottom(*pFrame)), nUpperMaxY );
+ if ( pFrame->IsEmpty() || ! aRectFnSet.GetHeight(pFrame->getFramePrintArea()) )
+ {
+ Point aPnt1 = pFrame->getFrameArea().Pos() + pFrame->getFramePrintArea().Pos();
+ Point aPnt2;
+ if ( aRectFnSet.IsVert() )
+ {
+ if ( aPnt1.X() < nMaxY && !aRectFnSet.IsVertL2R() )
+ aPnt1.setX( nMaxY );
+
+ aPnt2.setX( aPnt1.X() + pFrame->getFramePrintArea().Width() );
+ aPnt2.setY( aPnt1.Y() );
+ if( aPnt2.X() < nMaxY )
+ aPnt2.setX( nMaxY );
+ }
+ else
+ {
+ if( aPnt1.Y() > nMaxY )
+ aPnt1.setY( nMaxY );
+ aPnt2.setX( aPnt1.X() );
+ aPnt2.setY( aPnt1.Y() + pFrame->getFramePrintArea().Height() );
+ if( aPnt2.Y() > nMaxY )
+ aPnt2.setY( nMaxY );
+ }
+ rOrig = SwRect( aPnt1, aPnt2 );
+ return true;
+ }
+ else
+ {
+ if( !pFrame->HasPara() )
+ return false;
+
+ SwFrameSwapper aSwapper( pFrame, true );
+ if ( aRectFnSet.IsVert() )
+ nMaxY = pFrame->SwitchVerticalToHorizontal( nMaxY );
+
+ SwTextSizeInfo aInf( pFrame );
+ SwTextCursor aLine( pFrame, &aInf );
+ SwCursorMoveState aTmpState( CursorMoveState::SetOnlyText );
+ aTmpState.m_bRealHeight = true;
+ aLine.GetCharRect( &rOrig, nOffset, &aTmpState, nMaxY );
+ if( aTmpState.m_aRealHeight.X() >= 0 )
+ {
+ rOrig.Pos().AdjustY(aTmpState.m_aRealHeight.X() );
+ rOrig.Height( aTmpState.m_aRealHeight.Y() );
+ }
+
+ if ( pFrame->IsRightToLeft() )
+ pFrame->SwitchLTRtoRTL( rOrig );
+
+ if ( aRectFnSet.IsVert() )
+ pFrame->SwitchHorizontalToVertical( rOrig );
+
+ return true;
+ }
+}
+
+/** determine top of line for given position in the text frame
+
+ - Top of first paragraph line is the top of the printing area of the text frame
+ - If a proportional line spacing is applied use top of anchor character as
+ top of the line.
+*/
+bool SwTextFrame::GetTopOfLine( SwTwips& _onTopOfLine,
+ const SwPosition& _rPos ) const
+{
+ bool bRet = true;
+
+ // get position offset
+ TextFrameIndex const nOffset = MapModelToViewPos(_rPos);
+
+ if (TextFrameIndex(GetText().getLength()) < nOffset)
+ {
+ bRet = false;
+ }
+ else
+ {
+ SwRectFnSet aRectFnSet(this);
+ if ( IsEmpty() || !aRectFnSet.GetHeight(getFramePrintArea()) )
+ {
+ // consider upper space amount considered
+ // for previous frame and the page grid.
+ _onTopOfLine = aRectFnSet.GetPrtTop(*this);
+ }
+ else
+ {
+ // determine formatted text frame that contains the requested position
+ SwTextFrame* pFrame = &(const_cast<SwTextFrame*>(this)->GetFrameAtOfst( nOffset ));
+ pFrame->GetFormatted();
+ aRectFnSet.Refresh(pFrame);
+ // If proportional line spacing is applied
+ // to the text frame, the top of the anchor character is also the
+ // top of the line.
+ // Otherwise the line layout determines the top of the line
+ const SvxLineSpacingItem& rSpace = GetAttrSet()->GetLineSpacing();
+ if ( rSpace.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
+ {
+ SwRect aCharRect;
+ if ( GetAutoPos( aCharRect, _rPos ) )
+ {
+ _onTopOfLine = aRectFnSet.GetTop(aCharRect);
+ }
+ else
+ {
+ bRet = false;
+ }
+ }
+ else
+ {
+ // assure that text frame is in a horizontal layout
+ SwFrameSwapper aSwapper( pFrame, true );
+ // determine text line that contains the requested position
+ SwTextSizeInfo aInf( pFrame );
+ SwTextCursor aLine( pFrame, &aInf );
+ aLine.CharCursorToLine( nOffset );
+ // determine top of line
+ _onTopOfLine = aLine.Y();
+ if ( aRectFnSet.IsVert() )
+ {
+ _onTopOfLine = pFrame->SwitchHorizontalToVertical( _onTopOfLine );
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+// Minimum distance of non-empty lines is a little less than 2 cm
+#define FILL_MIN_DIST 1100
+
+struct SwFillData
+{
+ SwRect aFrame;
+ const SwCursorMoveState *pCMS;
+ SwPosition* pPos;
+ const Point& rPoint;
+ SwTwips nLineWidth;
+ bool bFirstLine : 1;
+ bool bInner : 1;
+ bool bColumn : 1;
+ bool bEmpty : 1;
+ SwFillData( const SwCursorMoveState *pC, SwPosition* pP, const SwRect& rR,
+ const Point& rPt ) : aFrame( rR ), pCMS( pC ), pPos( pP ), rPoint( rPt ),
+ nLineWidth( 0 ), bFirstLine( true ), bInner( false ), bColumn( false ),
+ bEmpty( true ){}
+ SwFillMode Mode() const { return pCMS->m_pFill->eMode; }
+ tools::Long X() const { return rPoint.X(); }
+ tools::Long Y() const { return rPoint.Y(); }
+ tools::Long Left() const { return aFrame.Left(); }
+ tools::Long Right() const { return aFrame.Right(); }
+ tools::Long Bottom() const { return aFrame.Bottom(); }
+ SwFillCursorPos &Fill() const { return *pCMS->m_pFill; }
+ void SetTab( sal_uInt16 nNew ) { pCMS->m_pFill->nTabCnt = nNew; }
+ void SetSpace( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceCnt = nNew; }
+ void SetSpaceOnly( sal_uInt16 nNew ) { pCMS->m_pFill->nSpaceOnlyCnt = nNew; }
+ void SetOrient( const sal_Int16 eNew ){ pCMS->m_pFill->eOrient = eNew; }
+};
+
+bool SwTextFrame::GetModelPositionForViewPoint_(SwPosition* pPos, const Point& rPoint,
+ const bool bChgFrame, SwCursorMoveState* pCMS ) const
+{
+ // GetModelPositionForViewPoint_ is called by GetModelPositionForViewPoint and GetKeyCursorOfst.
+ // Never just a return false.
+
+ if( IsLocked() || IsHiddenNow() )
+ return false;
+
+ const_cast<SwTextFrame*>(this)->GetFormatted();
+
+ Point aOldPoint( rPoint );
+
+ if ( IsVertical() )
+ {
+ SwitchVerticalToHorizontal( const_cast<Point&>(rPoint) );
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+ }
+
+ if ( IsRightToLeft() )
+ SwitchRTLtoLTR( const_cast<Point&>(rPoint) );
+
+ std::unique_ptr<SwFillData> pFillData;
+ if ( pCMS && pCMS->m_pFill )
+ pFillData.reset(new SwFillData( pCMS, pPos, getFrameArea(), rPoint ));
+
+ if ( IsEmpty() )
+ {
+ *pPos = MapViewToModelPos(TextFrameIndex(0));
+ if( pCMS && pCMS->m_bFieldInfo )
+ {
+ SwTwips nDiff = rPoint.X() - getFrameArea().Left() - getFramePrintArea().Left();
+ if( nDiff > 50 || nDiff < 0 )
+ pCMS->m_bPosCorr = true;
+ }
+ }
+ else
+ {
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf );
+
+ // See comment in AdjustFrame()
+ SwTwips nMaxY = getFrameArea().Top() + getFramePrintArea().Top() + getFramePrintArea().Height();
+ aLine.TwipsToLine( rPoint.Y() );
+ while( aLine.Y() + aLine.GetLineHeight() > nMaxY )
+ {
+ if( !aLine.Prev() )
+ break;
+ }
+
+ if( aLine.GetDropLines() >= aLine.GetLineNr() && 1 != aLine.GetLineNr()
+ && rPoint.X() < aLine.FirstLeft() + aLine.GetDropLeft() )
+ while( aLine.GetLineNr() > 1 )
+ aLine.Prev();
+
+ TextFrameIndex nOffset = aLine.GetModelPositionForViewPoint(pPos, rPoint, bChgFrame, pCMS);
+
+ if( pCMS && pCMS->m_eState == CursorMoveState::NONE && aLine.GetEnd() == nOffset )
+ pCMS->m_eState = CursorMoveState::RightMargin;
+
+ // pPos is a pure IN parameter and must not be evaluated.
+ // pIter->GetModelPositionForViewPoint returns from a nesting with COMPLETE_STRING.
+ // If SwTextIter::GetModelPositionForViewPoint calls GetModelPositionForViewPoint further by itself
+ // nNode changes the position.
+ // In such cases, pPos must not be calculated.
+ if (TextFrameIndex(COMPLETE_STRING) != nOffset)
+ {
+ *pPos = MapViewToModelPos(nOffset);
+ if( pFillData )
+ {
+ if (TextFrameIndex(GetText().getLength()) > nOffset ||
+ rPoint.Y() < getFrameArea().Top() )
+ pFillData->bInner = true;
+ pFillData->bFirstLine = aLine.GetLineNr() < 2;
+ if (GetText().getLength())
+ {
+ pFillData->bEmpty = false;
+ pFillData->nLineWidth = aLine.GetCurr()->Width();
+ }
+ }
+ }
+ }
+ bool bChgFillData = false;
+ if( pFillData && FindPageFrame()->getFrameArea().Contains( aOldPoint ) )
+ {
+ FillCursorPos( *pFillData );
+ bChgFillData = true;
+ }
+
+ if ( IsVertical() )
+ {
+ if ( bChgFillData )
+ SwitchHorizontalToVertical( pFillData->Fill().aCursor.Pos() );
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+ }
+
+ if ( IsRightToLeft() && bChgFillData )
+ {
+ SwitchLTRtoRTL( pFillData->Fill().aCursor.Pos() );
+ const sal_Int16 eOrient = pFillData->pCMS->m_pFill->eOrient;
+
+ if ( text::HoriOrientation::LEFT == eOrient )
+ pFillData->SetOrient( text::HoriOrientation::RIGHT );
+ else if ( text::HoriOrientation::RIGHT == eOrient )
+ pFillData->SetOrient( text::HoriOrientation::LEFT );
+ }
+
+ const_cast<Point&>(rPoint) = aOldPoint;
+
+ return true;
+}
+
+bool SwTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& rPoint,
+ SwCursorMoveState* pCMS, bool ) const
+{
+ const bool bChgFrame = !(pCMS && CursorMoveState::UpDown == pCMS->m_eState);
+ return GetModelPositionForViewPoint_( pPos, rPoint, bChgFrame, pCMS );
+}
+
+/*
+ * Layout-oriented cursor movement to the line start.
+ */
+
+bool SwTextFrame::LeftMargin(SwPaM *pPam) const
+{
+ assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep()));
+
+ SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(),
+ SwTextCursor::IsRightMargin() );
+ pFrame->GetFormatted();
+ TextFrameIndex nIndx;
+ if ( pFrame->IsEmpty() )
+ nIndx = TextFrameIndex(0);
+ else
+ {
+ SwTextSizeInfo aInf( pFrame );
+ SwTextCursor aLine( pFrame, &aInf );
+
+ aLine.CharCursorToLine(pFrame->MapModelToViewPos(*pPam->GetPoint()));
+ nIndx = aLine.GetStart();
+ if( pFrame->GetOffset() && !pFrame->IsFollow() && !aLine.GetPrev() )
+ {
+ sw_ChangeOffset(pFrame, TextFrameIndex(0));
+ nIndx = TextFrameIndex(0);
+ }
+ }
+ *pPam->GetPoint() = pFrame->MapViewToModelPos(nIndx);
+ SwTextCursor::SetRightMargin( false );
+ return true;
+}
+
+/*
+ * To the line end: That's the position before the last char of the line.
+ * Exception: In the last line, it should be able to place the cursor after
+ * the last char in order to append text.
+ */
+
+bool SwTextFrame::RightMargin(SwPaM *pPam, bool bAPI) const
+{
+ assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep()));
+
+ SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(),
+ SwTextCursor::IsRightMargin() );
+ pFrame->GetFormatted();
+ TextFrameIndex nRightMargin(0);
+ if (!IsEmpty())
+ {
+ SwTextSizeInfo aInf( pFrame );
+ SwTextCursor aLine( pFrame, &aInf );
+
+ aLine.CharCursorToLine(MapModelToViewPos(*pPam->GetPoint()));
+ nRightMargin = aLine.GetStart() + aLine.GetCurr()->GetLen();
+
+ // We skip hard line breaks
+ if( aLine.GetCurr()->GetLen() &&
+ CH_BREAK == aInf.GetText()[sal_Int32(nRightMargin) - 1])
+ --nRightMargin;
+ else if( !bAPI && (aLine.GetNext() || pFrame->GetFollow()) )
+ {
+ while( nRightMargin > aLine.GetStart() &&
+ ' ' == aInf.GetText()[sal_Int32(nRightMargin) - 1])
+ --nRightMargin;
+ }
+ }
+ *pPam->GetPoint() = pFrame->MapViewToModelPos(nRightMargin);
+ SwTextCursor::SetRightMargin( !bAPI );
+ return true;
+}
+
+// The following two methods try to put the Cursor into the next/successive
+// line. If we do not have a preceding/successive line we forward the call
+// to the base class.
+// The Cursor's horizontal justification is done afterwards by the CursorShell.
+
+namespace {
+
+class SwSetToRightMargin
+{
+ bool m_bRight;
+
+public:
+ SwSetToRightMargin()
+ : m_bRight(false)
+ {
+ }
+ ~SwSetToRightMargin() { SwTextCursor::SetRightMargin(m_bRight); }
+ void SetRight(const bool bNew) { m_bRight = bNew; }
+};
+
+}
+
+bool SwTextFrame::UnitUp_( SwPaM *pPam, const SwTwips nOffset,
+ bool bSetInReadOnly ) const
+{
+ // Set the RightMargin if needed
+ SwSetToRightMargin aSet;
+
+ if( IsInTab() &&
+ pPam->GetPointNode().StartOfSectionNode() !=
+ pPam->GetMarkNode().StartOfSectionNode() )
+ {
+ // If the PaM is located within different boxes, we have a table selection,
+ // which is handled by the base class.
+ return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly );
+ }
+
+ const_cast<SwTextFrame*>(this)->GetFormatted();
+ const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint());
+ SwRect aCharBox;
+
+ if( !IsEmpty() && !IsHiddenNow() )
+ {
+ TextFrameIndex nFormat(COMPLETE_STRING);
+ do
+ {
+ if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow())
+ sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat );
+
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf );
+
+ // Optimize away flys with no flow and IsDummy()
+ if( nPos )
+ aLine.CharCursorToLine( nPos );
+ else
+ aLine.Top();
+
+ const SwLineLayout *pPrevLine = aLine.GetPrevLine();
+ const TextFrameIndex nStart = aLine.GetStart();
+ aLine.GetCharRect( &aCharBox, nPos );
+
+ bool bSecondOfDouble = ( aInf.IsMulti() && ! aInf.IsFirstMulti() );
+ bool bPrevLine = ( pPrevLine && pPrevLine != aLine.GetCurr() );
+
+ if( !pPrevLine && !bSecondOfDouble && GetOffset() && !IsFollow() )
+ {
+ nFormat = GetOffset();
+ TextFrameIndex nDiff = aLine.GetLength();
+ if( !nDiff )
+ nDiff = TextFrameIndex(MIN_OFFSET_STEP);
+ if( nFormat > nDiff )
+ nFormat = nFormat - nDiff;
+ else
+ nFormat = TextFrameIndex(0);
+ continue;
+ }
+
+ // We select the target line for the cursor, in case we are in a
+ // double line portion, prev line = curr line
+ if( bPrevLine && !bSecondOfDouble )
+ {
+ aLine.PrevLine();
+ while ( aLine.GetStart() == nStart &&
+ nullptr != ( pPrevLine = aLine.GetPrevLine() ) &&
+ pPrevLine != aLine.GetCurr() )
+ aLine.PrevLine();
+ }
+
+ if ( bPrevLine || bSecondOfDouble )
+ {
+ aCharBox.Width( aCharBox.SSize().Width() / 2 );
+ aCharBox.Pos().setX( aCharBox.Pos().X() - 150 );
+
+ // See comment in SwTextFrame::GetModelPositionForViewPoint()
+#if OSL_DEBUG_LEVEL > 0
+ const SwNodeOffset nOldNode = pPam->GetPoint()->GetNodeIndex();
+#endif
+ // The node should not be changed
+ TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint(pPam->GetPoint(),
+ aCharBox.Pos(), false );
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( nOldNode == pPam->GetPoint()->GetNodeIndex(),
+ "SwTextFrame::UnitUp: illegal node change" );
+#endif
+
+ // We make sure that we move up.
+ if( nTmpOfst >= nStart && nStart && !bSecondOfDouble )
+ {
+ nTmpOfst = nStart;
+ aSet.SetRight( true );
+ }
+ *pPam->GetPoint() = MapViewToModelPos(nTmpOfst);
+ return true;
+ }
+
+ if ( IsFollow() )
+ {
+ aLine.GetCharRect( &aCharBox, nPos );
+ aCharBox.Width( aCharBox.SSize().Width() / 2 );
+ }
+ break;
+ } while ( true );
+ }
+ /* If 'this' is a follow and a prev failed, we need to go to the
+ * last line of the master, which is us.
+ * Or: If we are a follow with follow, we need to get the master.
+ */
+ if ( IsFollow() )
+ {
+ const SwTextFrame *pTmpPrev = FindMaster();
+ TextFrameIndex nOffs = GetOffset();
+ if( pTmpPrev )
+ {
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ const bool bProtectedAllowed = pSh && pSh->GetViewOptions()->IsCursorInProtectedArea();
+ const SwTextFrame *pPrevPrev = pTmpPrev;
+ // We skip protected frames and frames without content here
+ while( pPrevPrev && ( pPrevPrev->GetOffset() == nOffs ||
+ ( !bProtectedAllowed && pPrevPrev->IsProtected() ) ) )
+ {
+ pTmpPrev = pPrevPrev;
+ nOffs = pTmpPrev->GetOffset();
+ if ( pPrevPrev->IsFollow() )
+ pPrevPrev = pTmpPrev->FindMaster();
+ else
+ pPrevPrev = nullptr;
+ }
+ if ( !pPrevPrev )
+ return pTmpPrev->SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly );
+ aCharBox.Pos().setY( pPrevPrev->getFrameArea().Bottom() - 1 );
+ return pPrevPrev->GetKeyCursorOfst( pPam->GetPoint(), aCharBox.Pos() );
+ }
+ }
+ return SwContentFrame::UnitUp( pPam, nOffset, bSetInReadOnly );
+}
+
+// Used for Bidi. nPos is the logical position in the string, bLeft indicates
+// if left arrow or right arrow was pressed. The return values are:
+// nPos: the new visual position
+// bLeft: whether the break iterator has to add or subtract from the
+// current position
+static void lcl_VisualMoveRecursion(const SwLineLayout& rCurrLine, TextFrameIndex nIdx,
+ TextFrameIndex & nPos, bool& bRight,
+ sal_uInt8& nCursorLevel, sal_uInt8 nDefaultDir )
+{
+ const SwLinePortion* pPor = rCurrLine.GetFirstPortion();
+ const SwLinePortion* pLast = nullptr;
+
+ // What's the current portion?
+ while ( pPor && nIdx + pPor->GetLen() <= nPos )
+ {
+ nIdx = nIdx + pPor->GetLen();
+ pLast = pPor;
+ pPor = pPor->GetNextPortion();
+ }
+
+ if ( bRight )
+ {
+ bool bRecurse = pPor && pPor->IsMultiPortion() &&
+ static_cast<const SwMultiPortion*>(pPor)->IsBidi();
+
+ // 1. special case: at beginning of bidi portion
+ if ( bRecurse && nIdx == nPos )
+ {
+ nPos = nPos + pPor->GetLen();
+
+ // leave bidi portion
+ if ( nCursorLevel != nDefaultDir )
+ {
+ bRecurse = false;
+ }
+ else
+ // special case:
+ // buffer: abcXYZ123 in LTR paragraph
+ // view: abc123ZYX
+ // cursor is between c and X in the buffer and cursor level = 0
+ nCursorLevel++;
+ }
+
+ // 2. special case: at beginning of portion after bidi portion
+ else if ( pLast && pLast->IsMultiPortion() &&
+ static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos )
+ {
+ // enter bidi portion
+ if ( nCursorLevel != nDefaultDir )
+ {
+ bRecurse = true;
+ nIdx = nIdx - pLast->GetLen();
+ pPor = pLast;
+ }
+ }
+
+ // Recursion
+ if ( bRecurse )
+ {
+ const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot();
+ TextFrameIndex nTmpPos = nPos - nIdx;
+ bool bTmpForward = ! bRight;
+ sal_uInt8 nTmpCursorLevel = nCursorLevel;
+ lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward,
+ nTmpCursorLevel, nDefaultDir + 1 );
+
+ nPos = nTmpPos + nIdx;
+ bRight = bTmpForward;
+ nCursorLevel = nTmpCursorLevel;
+ }
+
+ // go forward
+ else
+ {
+ bRight = true;
+ nCursorLevel = nDefaultDir;
+ }
+
+ }
+ else
+ {
+ bool bRecurse = pPor && pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsBidi();
+
+ // 1. special case: at beginning of bidi portion
+ if ( bRecurse && nIdx == nPos )
+ {
+ // leave bidi portion
+ if ( nCursorLevel == nDefaultDir )
+ {
+ bRecurse = false;
+ }
+ }
+
+ // 2. special case: at beginning of portion after bidi portion
+ else if ( pLast && pLast->IsMultiPortion() &&
+ static_cast<const SwMultiPortion*>(pLast)->IsBidi() && nIdx == nPos )
+ {
+ nPos = nPos - pLast->GetLen();
+
+ // enter bidi portion
+ if ( nCursorLevel % 2 == nDefaultDir % 2 )
+ {
+ bRecurse = true;
+ nIdx = nIdx - pLast->GetLen();
+ pPor = pLast;
+
+ // special case:
+ // buffer: abcXYZ123 in LTR paragraph
+ // view: abc123ZYX
+ // cursor is behind 3 in the buffer and cursor level = 2
+ if ( nDefaultDir + 2 == nCursorLevel )
+ nPos = nPos + pLast->GetLen();
+ }
+ }
+
+ // go forward
+ if ( bRecurse )
+ {
+ const SwLineLayout& rLine = static_cast<const SwMultiPortion*>(pPor)->GetRoot();
+ TextFrameIndex nTmpPos = nPos - nIdx;
+ bool bTmpForward = ! bRight;
+ sal_uInt8 nTmpCursorLevel = nCursorLevel;
+ lcl_VisualMoveRecursion(rLine, TextFrameIndex(0), nTmpPos, bTmpForward,
+ nTmpCursorLevel, nDefaultDir + 1 );
+
+ // special case:
+ // buffer: abcXYZ123 in LTR paragraph
+ // view: abc123ZYX
+ // cursor is between Z and 1 in the buffer and cursor level = 2
+ if ( nTmpPos == pPor->GetLen() && nTmpCursorLevel == nDefaultDir + 1 )
+ {
+ nTmpPos = nTmpPos - pPor->GetLen();
+ nTmpCursorLevel = nDefaultDir;
+ bTmpForward = ! bTmpForward;
+ }
+
+ nPos = nTmpPos + nIdx;
+ bRight = bTmpForward;
+ nCursorLevel = nTmpCursorLevel;
+ }
+
+ // go backward
+ else
+ {
+ bRight = false;
+ nCursorLevel = nDefaultDir;
+ }
+ }
+}
+
+void SwTextFrame::PrepareVisualMove(TextFrameIndex & nPos, sal_uInt8& nCursorLevel,
+ bool& bForward, bool bInsertCursor )
+{
+ if( IsEmpty() || IsHiddenNow() )
+ return;
+
+ GetFormatted();
+
+ SwTextSizeInfo aInf(this);
+ SwTextCursor aLine(this, &aInf);
+
+ if( nPos )
+ aLine.CharCursorToLine( nPos );
+ else
+ aLine.Top();
+
+ const SwLineLayout* pLine = aLine.GetCurr();
+ const TextFrameIndex nStt = aLine.GetStart();
+ const TextFrameIndex nLen = pLine->GetLen();
+
+ // We have to distinguish between an insert and overwrite cursor:
+ // The insert cursor position depends on the cursor level:
+ // buffer: abcXYZdef in LTR paragraph
+ // display: abcZYXdef
+ // If cursor is between c and X in the buffer and cursor level is 0,
+ // the cursor blinks between c and Z and -> sets the cursor between Z and Y.
+ // If the cursor level is 1, the cursor blinks between X and d and
+ // -> sets the cursor between d and e.
+ // The overwrite cursor simply travels to the next visual character.
+ if ( bInsertCursor )
+ {
+ lcl_VisualMoveRecursion( *pLine, nStt, nPos, bForward,
+ nCursorLevel, IsRightToLeft() ? 1 : 0 );
+ return;
+ }
+
+ const sal_uInt8 nDefaultDir = static_cast<sal_uInt8>(IsRightToLeft() ? UBIDI_RTL : UBIDI_LTR);
+ const bool bVisualRight = ( nDefaultDir == UBIDI_LTR && bForward ) ||
+ ( nDefaultDir == UBIDI_RTL && ! bForward );
+
+ // Bidi functions from icu 2.0
+
+ const sal_Unicode* pLineString = GetText().getStr();
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( sal_Int32(nLen), 0, &nError );
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(pLineString),
+ sal_Int32(nLen), nDefaultDir, nullptr, &nError );
+
+ TextFrameIndex nTmpPos(0);
+ bool bOutOfBounds = false;
+
+ if ( nPos < nStt + nLen )
+ {
+ nTmpPos = TextFrameIndex(ubidi_getVisualIndex( pBidi, sal_Int32(nPos), &nError ));
+
+ // visual indices are always LTR aligned
+ if ( bVisualRight )
+ {
+ if (nTmpPos + TextFrameIndex(1) < nStt + nLen)
+ ++nTmpPos;
+ else
+ {
+ nPos = nDefaultDir == UBIDI_RTL ? TextFrameIndex(0) : nStt + nLen;
+ bOutOfBounds = true;
+ }
+ }
+ else
+ {
+ if ( nTmpPos )
+ --nTmpPos;
+ else
+ {
+ nPos = nDefaultDir == UBIDI_RTL ? nStt + nLen : TextFrameIndex(0);
+ bOutOfBounds = true;
+ }
+ }
+ }
+ else
+ {
+ nTmpPos = nDefaultDir == UBIDI_LTR ? nPos - TextFrameIndex(1) : TextFrameIndex(0);
+ }
+
+ if ( ! bOutOfBounds )
+ {
+ nPos = TextFrameIndex(ubidi_getLogicalIndex( pBidi, sal_Int32(nTmpPos), &nError ));
+
+ if ( bForward )
+ {
+ if ( nPos )
+ --nPos;
+ else
+ {
+ ++nPos;
+ bForward = ! bForward;
+ }
+ }
+ else
+ ++nPos;
+ }
+
+ ubidi_close( pBidi );
+}
+
+bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset,
+ bool bSetInReadOnly ) const
+{
+
+ if ( IsInTab() &&
+ pPam->GetPointNode().StartOfSectionNode() !=
+ pPam->GetMarkNode().StartOfSectionNode() )
+ {
+ // If the PaM is located within different boxes, we have a table selection,
+ // which is handled by the base class.
+ return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly );
+ }
+ const_cast<SwTextFrame*>(this)->GetFormatted();
+ const TextFrameIndex nPos = MapModelToViewPos(*pPam->GetPoint());
+ SwRect aCharBox;
+ const SwContentFrame *pTmpFollow = nullptr;
+
+ if ( IsVertical() )
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+
+ if ( !IsEmpty() && !IsHiddenNow() )
+ {
+ TextFrameIndex nFormat(COMPLETE_STRING);
+ do
+ {
+ if (nFormat != TextFrameIndex(COMPLETE_STRING) && !IsFollow() &&
+ !sw_ChangeOffset( const_cast<SwTextFrame*>(this), nFormat ) )
+ break;
+
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(this), &aInf );
+ nFormat = aLine.GetEnd();
+
+ aLine.CharCursorToLine( nPos );
+
+ const SwLineLayout* pNextLine = aLine.GetNextLine();
+ const TextFrameIndex nStart = aLine.GetStart();
+ aLine.GetCharRect( &aCharBox, nPos );
+
+ bool bFirstOfDouble = ( aInf.IsMulti() && aInf.IsFirstMulti() );
+
+ if( pNextLine || bFirstOfDouble )
+ {
+ aCharBox.Width( aCharBox.SSize().Width() / 2 );
+#if OSL_DEBUG_LEVEL > 0
+ // See comment in SwTextFrame::GetModelPositionForViewPoint()
+ const SwNodeOffset nOldNode = pPam->GetPoint()->GetNodeIndex();
+#endif
+ if ( pNextLine && ! bFirstOfDouble )
+ aLine.NextLine();
+
+ TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint( pPam->GetPoint(),
+ aCharBox.Pos(), false );
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( nOldNode == pPam->GetPoint()->GetNodeIndex(),
+ "SwTextFrame::UnitDown: illegal node change" );
+#endif
+
+ // We make sure that we move down.
+ if( nTmpOfst <= nStart && ! bFirstOfDouble )
+ nTmpOfst = nStart + TextFrameIndex(1);
+ *pPam->GetPoint() = MapViewToModelPos(nTmpOfst);
+
+ if ( IsVertical() )
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+
+ return true;
+ }
+ pTmpFollow = GetFollow();
+ if( nullptr != pTmpFollow )
+ { // Skip protected follows
+ const SwContentFrame* pTmp = pTmpFollow;
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if( !pSh || !pSh->GetViewOptions()->IsCursorInProtectedArea() )
+ {
+ while( pTmpFollow && pTmpFollow->IsProtected() )
+ {
+ pTmp = pTmpFollow;
+ pTmpFollow = pTmpFollow->GetFollow();
+ }
+ }
+ if( !pTmpFollow ) // Only protected ones left
+ {
+ if ( IsVertical() )
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+ return pTmp->SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly );
+ }
+
+ aLine.GetCharRect( &aCharBox, nPos );
+ aCharBox.Width( aCharBox.SSize().Width() / 2 );
+ }
+ else if( !IsFollow() )
+ {
+ TextFrameIndex nTmpLen(aInf.GetText().getLength());
+ if( aLine.GetEnd() < nTmpLen )
+ {
+ if( nFormat <= GetOffset() )
+ {
+ nFormat = std::min(GetOffset() + TextFrameIndex(MIN_OFFSET_STEP),
+ nTmpLen );
+ if( nFormat <= GetOffset() )
+ break;
+ }
+ continue;
+ }
+ }
+ break;
+ } while( true );
+ }
+ else
+ pTmpFollow = GetFollow();
+
+ if ( IsVertical() )
+ const_cast<SwTextFrame*>(this)->SwapWidthAndHeight();
+
+ // We take a shortcut for follows
+ if( pTmpFollow )
+ {
+ aCharBox.Pos().setY( pTmpFollow->getFrameArea().Top() + 1 );
+ return static_cast<const SwTextFrame*>(pTmpFollow)->GetKeyCursorOfst( pPam->GetPoint(),
+ aCharBox.Pos() );
+ }
+ return SwContentFrame::UnitDown( pPam, nOffset, bSetInReadOnly );
+}
+
+bool SwTextFrame::UnitUp(SwPaM *pPam, const SwTwips nOffset,
+ bool bSetInReadOnly ) const
+{
+ /* We call ContentNode::GertFrame() in CursorSh::Up().
+ * This _always returns the master.
+ * In order to not mess with cursor travelling, we correct here
+ * in SwTextFrame.
+ * We calculate UnitUp for pFrame. pFrame is either a master (= this) or a
+ * follow (!= this).
+ */
+ const SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *(pPam->GetPoint()),
+ SwTextCursor::IsRightMargin() );
+ const bool bRet = pFrame->UnitUp_( pPam, nOffset, bSetInReadOnly );
+
+ // No SwTextCursor::SetRightMargin( false );
+ // Instead we have a SwSetToRightMargin in UnitUp_
+ return bRet;
+}
+
+bool SwTextFrame::UnitDown(SwPaM *pPam, const SwTwips nOffset,
+ bool bSetInReadOnly ) const
+{
+ const SwTextFrame *pFrame = GetAdjFrameAtPos(const_cast<SwTextFrame*>(this), *(pPam->GetPoint()),
+ SwTextCursor::IsRightMargin() );
+ const bool bRet = pFrame->UnitDown_( pPam, nOffset, bSetInReadOnly );
+ SwTextCursor::SetRightMargin( false );
+ return bRet;
+}
+
+void SwTextFrame::FillCursorPos( SwFillData& rFill ) const
+{
+ if( !rFill.bColumn && GetUpper()->IsColBodyFrame() ) // ColumnFrames now with BodyFrame
+ {
+ const SwColumnFrame* pTmp =
+ static_cast<const SwColumnFrame*>(GetUpper()->GetUpper()->GetUpper()->Lower()); // The 1st column
+ // The first SwFrame in BodyFrame of the first column
+ const SwFrame* pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower();
+ sal_uInt16 nNextCol = 0;
+ // In which column do we end up in?
+ while( rFill.X() > pTmp->getFrameArea().Right() && pTmp->GetNext() )
+ {
+ pTmp = static_cast<const SwColumnFrame*>(pTmp->GetNext());
+ if( static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower() ) // ColumnFrames now with BodyFrame
+ {
+ pFrame = static_cast<const SwLayoutFrame*>(pTmp->Lower())->Lower();
+ nNextCol = 0;
+ }
+ else
+ ++nNextCol; // Empty columns require column breaks
+ }
+ if( pTmp != GetUpper()->GetUpper() ) // Did we end up in another column?
+ {
+ if( !pFrame )
+ return;
+ if( nNextCol )
+ {
+ while( pFrame->GetNext() )
+ pFrame = pFrame->GetNext();
+ }
+ else
+ {
+ while( pFrame->GetNext() && pFrame->getFrameArea().Bottom() < rFill.Y() )
+ pFrame = pFrame->GetNext();
+ }
+ // No filling, if the last frame in the targeted column does
+ // not contain a paragraph, but e.g. a table
+ if( pFrame->IsTextFrame() )
+ {
+ rFill.Fill().nColumnCnt = nNextCol;
+ rFill.bColumn = true;
+ if( rFill.pPos )
+ {
+ SwTextFrame const*const pTextFrame(static_cast<const SwTextFrame*>(pFrame));
+ *rFill.pPos = pTextFrame->MapViewToModelPos(
+ TextFrameIndex(pTextFrame->GetText().getLength()));
+ }
+ if( nNextCol )
+ {
+ rFill.aFrame = pTmp->getFramePrintArea();
+ rFill.aFrame += pTmp->getFrameArea().Pos();
+ }
+ else
+ rFill.aFrame = pFrame->getFrameArea();
+ static_cast<const SwTextFrame*>(pFrame)->FillCursorPos( rFill );
+ }
+ return;
+ }
+ }
+ std::unique_ptr<SwFont> pFnt;
+ SwTextFormatColl* pColl = GetTextNodeForParaProps()->GetTextColl();
+ SwTwips nFirst = GetTextNodeForParaProps()->GetSwAttrSet().GetULSpace().GetLower();
+ SwTwips nDiff = rFill.Y() - getFrameArea().Bottom();
+ if( nDiff < nFirst )
+ nDiff = -1;
+ else
+ pColl = &pColl->GetNextTextFormatColl();
+ SwAttrSet aSet(const_cast<SwDoc&>(GetDoc()).GetAttrPool(), aTextFormatCollSetRange );
+ const SwAttrSet* pSet = &pColl->GetAttrSet();
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if (GetTextNodeForParaProps()->HasSwAttrSet())
+ {
+ // sw_redlinehide: pSet is mostly used for para props, but there are
+ // accesses to char props via pFnt - why does it use only the node's
+ // props for this, and not hints?
+ aSet.Put( *GetTextNodeForParaProps()->GetpSwAttrSet() );
+ aSet.SetParent( pSet );
+ pSet = &aSet;
+ pFnt.reset(new SwFont( pSet, &GetDoc().getIDocumentSettingAccess() ));
+ }
+ else
+ {
+ SwFontAccess aFontAccess( pColl, pSh );
+ pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() ));
+ pFnt->CheckFontCacheId( pSh, pFnt->GetActual() );
+ }
+ OutputDevice* pOut = pSh->GetOut();
+ if( !pSh->GetViewOptions()->getBrowseMode() || pSh->GetViewOptions()->IsPrtFormat() )
+ pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true );
+
+ pFnt->SetFntChg( true );
+ pFnt->ChgPhysFnt( pSh, *pOut );
+
+ SwTwips nLineHeight = pFnt->GetHeight( pSh, *pOut );
+
+ bool bFill = false;
+ if( nLineHeight )
+ {
+ bFill = true;
+ const SvxULSpaceItem &rUL = pSet->GetULSpace();
+ SwTwips nDist = std::max( rUL.GetLower(), rUL.GetUpper() );
+ if( rFill.Fill().nColumnCnt )
+ {
+ rFill.aFrame.Height( nLineHeight );
+ nDiff = rFill.Y() - rFill.Bottom();
+ nFirst = 0;
+ }
+ else if( nDist < nFirst )
+ nFirst = nFirst - nDist;
+ else
+ nFirst = 0;
+ nDist = std::max( nDist, SwTwips(GetLineSpace()) );
+ nDist += nLineHeight;
+ nDiff -= nFirst;
+
+ if( nDiff > 0 )
+ {
+ nDiff /= nDist;
+ rFill.Fill().nParaCnt = o3tl::narrowing<sal_uInt16>(nDiff + 1);
+ rFill.nLineWidth = 0;
+ rFill.bInner = false;
+ rFill.bEmpty = true;
+ rFill.SetOrient( text::HoriOrientation::LEFT );
+ }
+ else
+ nDiff = -1;
+ if( rFill.bInner )
+ bFill = false;
+ else
+ {
+ const SvxTabStopItem &rRuler = pSet->GetTabStops();
+ const SvxFirstLineIndentItem& rFirstLine(pSet->GetFirstLineIndent());
+ const SvxTextLeftMarginItem& rTextLeftMargin(pSet->GetTextLeftMargin());
+ const SvxRightMarginItem& rRightMargin(pSet->GetRightMargin());
+
+ SwRect &rRect = rFill.Fill().aCursor;
+ rRect.Top( rFill.Bottom() + (nDiff+1) * nDist - nLineHeight );
+ if( nFirst && nDiff > -1 )
+ rRect.Top( rRect.Top() + nFirst );
+ rRect.Height( nLineHeight );
+ SwTwips nLeft = rFill.Left() + rTextLeftMargin.GetLeft(rFirstLine) +
+ GetTextNodeForParaProps()->GetLeftMarginWithNum();
+ SwTwips nRight = rFill.Right() - rRightMargin.GetRight();
+ SwTwips nCenter = ( nLeft + nRight ) / 2;
+ rRect.Left( nLeft );
+ if( SwFillMode::Margin == rFill.Mode() )
+ {
+ if( rFill.bEmpty )
+ {
+ rFill.SetOrient( text::HoriOrientation::LEFT );
+ if( rFill.X() < nCenter )
+ {
+ if( rFill.X() > ( nLeft + 2 * nCenter ) / 3 )
+ {
+ rFill.SetOrient( text::HoriOrientation::CENTER );
+ rRect.Left( nCenter );
+ }
+ }
+ else if( rFill.X() > ( nRight + 2 * nCenter ) / 3 )
+ {
+ rFill.SetOrient( text::HoriOrientation::RIGHT );
+ rRect.Left( nRight );
+ }
+ else
+ {
+ rFill.SetOrient( text::HoriOrientation::CENTER );
+ rRect.Left( nCenter );
+ }
+ }
+ else
+ bFill = false;
+ }
+ else
+ {
+ SwTwips nSpace = 0;
+ if( SwFillMode::Tab != rFill.Mode() )
+ {
+ SwDrawTextInfo aDrawInf( pSh, *pOut, " ", 0, 2 );
+ nSpace = pFnt->GetTextSize_( aDrawInf ).Width()/2;
+ }
+ if( rFill.X() >= nRight )
+ {
+ if( SwFillMode::Indent != rFill.Mode() && ( rFill.bEmpty ||
+ rFill.X() > rFill.nLineWidth + FILL_MIN_DIST ) )
+ {
+ rFill.SetOrient( text::HoriOrientation::RIGHT );
+ rRect.Left( nRight );
+ }
+ else
+ bFill = false;
+ }
+ else if( SwFillMode::Indent == rFill.Mode() )
+ {
+ SwTwips nIndent = rFill.X();
+ if( !rFill.bEmpty || nIndent > nRight )
+ bFill = false;
+ else
+ {
+ nIndent -= rFill.Left();
+ if( nIndent >= 0 && nSpace )
+ {
+ nIndent /= nSpace;
+ nIndent *= nSpace;
+ rFill.SetTab( sal_uInt16( nIndent ) );
+ rRect.Left( nIndent + rFill.Left() );
+ }
+ else
+ bFill = false;
+ }
+ }
+ else if( rFill.X() > nLeft )
+ {
+ SwTwips nTextLeft = rFill.Left() + rTextLeftMargin.GetTextLeft() +
+ GetTextNodeForParaProps()->GetLeftMarginWithNum(true);
+ rFill.nLineWidth += rFill.bFirstLine ? nLeft : nTextLeft;
+ SwTwips nLeftTab;
+ SwTwips nRightTab = nLeft;
+ sal_uInt16 nSpaceCnt = 0;
+ sal_uInt16 nSpaceOnlyCnt = 0;
+ sal_uInt16 nTabCnt = 0;
+ sal_uInt16 nIdx = 0;
+ do
+ {
+ nLeftTab = nRightTab;
+ if( nIdx < rRuler.Count() )
+ {
+ const SvxTabStop &rTabStop = rRuler.operator[](nIdx);
+ nRightTab = nTextLeft + rTabStop.GetTabPos();
+ if( nLeftTab < nTextLeft && nRightTab > nTextLeft )
+ nRightTab = nTextLeft;
+ else
+ ++nIdx;
+ if( nRightTab > rFill.nLineWidth )
+ ++nTabCnt;
+ }
+ else
+ {
+ const SvxTabStopItem& rTab =
+ pSet->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP );
+ const SwTwips nDefTabDist = rTab[0].GetTabPos();
+ nRightTab = nLeftTab - nTextLeft;
+ nRightTab /= nDefTabDist;
+ nRightTab = nRightTab * nDefTabDist + nTextLeft;
+ while ( nRightTab <= nLeftTab )
+ nRightTab += nDefTabDist;
+ if( nRightTab > rFill.nLineWidth )
+ ++nTabCnt;
+ while ( nRightTab < rFill.X() )
+ {
+ nRightTab += nDefTabDist;
+ if( nRightTab > rFill.nLineWidth )
+ ++nTabCnt;
+ }
+ if( nLeftTab < nRightTab - nDefTabDist )
+ nLeftTab = nRightTab - nDefTabDist;
+ }
+ if( nRightTab > nRight )
+ nRightTab = nRight;
+ }
+ while( rFill.X() > nRightTab );
+ --nTabCnt;
+ if( SwFillMode::TabSpace == rFill.Mode() )
+ {
+ if( nSpace > 0 )
+ {
+ if( !nTabCnt )
+ nLeftTab = rFill.nLineWidth;
+ while( nLeftTab < rFill.X() )
+ {
+ nLeftTab += nSpace;
+ ++nSpaceCnt;
+ }
+ if( nSpaceCnt )
+ {
+ nLeftTab -= nSpace;
+ --nSpaceCnt;
+ }
+ if( rFill.X() - nLeftTab > nRightTab - rFill.X() )
+ {
+ nSpaceCnt = 0;
+ ++nTabCnt;
+ rRect.Left( nRightTab );
+ }
+ else
+ {
+ if( rFill.X() - nLeftTab > nSpace/2 )
+ {
+ ++nSpaceCnt;
+ rRect.Left( nLeftTab + nSpace );
+ }
+ else
+ rRect.Left( nLeftTab );
+ }
+ }
+ else if( rFill.X() - nLeftTab < nRightTab - rFill.X() )
+ rRect.Left( nLeftTab );
+ else
+ {
+ if( nRightTab >= nRight )
+ {
+ rFill.SetOrient( text::HoriOrientation::RIGHT );
+ rRect.Left( nRight );
+ nTabCnt = 0;
+ nSpaceCnt = 0;
+ }
+ else
+ {
+ rRect.Left( nRightTab );
+ ++nTabCnt;
+ }
+ }
+ }
+ else if( SwFillMode::Space == rFill.Mode() )
+ {
+ SwTwips nLeftSpace = nLeft;
+ while( nLeftSpace < rFill.X() )
+ {
+ nLeftSpace += nSpace;
+ ++nSpaceOnlyCnt;
+ }
+ rRect.Left( nLeftSpace );
+ }
+ else
+ {
+ if( rFill.X() - nLeftTab < nRightTab - rFill.X() )
+ rRect.Left( nLeftTab );
+ else
+ {
+ if( nRightTab >= nRight )
+ {
+ rFill.SetOrient( text::HoriOrientation::RIGHT );
+ rRect.Left( nRight );
+ nTabCnt = 0;
+ nSpaceCnt = 0;
+ }
+ else
+ {
+ rRect.Left( nRightTab );
+ ++nTabCnt;
+ }
+ }
+ }
+ rFill.SetTab( nTabCnt );
+ rFill.SetSpace( nSpaceCnt );
+ rFill.SetSpaceOnly( nSpaceOnlyCnt );
+ if( bFill )
+ {
+ if( std::abs( rFill.X() - nCenter ) <=
+ std::abs( rFill.X() - rRect.Left() ) )
+ {
+ rFill.SetOrient( text::HoriOrientation::CENTER );
+ rFill.SetTab( 0 );
+ rFill.SetSpace( 0 );
+ rFill.SetSpaceOnly( 0 );
+ rRect.Left( nCenter );
+ }
+ if( !rFill.bEmpty )
+ rFill.nLineWidth += FILL_MIN_DIST;
+ if( rRect.Left() < rFill.nLineWidth )
+ bFill = false;
+ }
+ }
+ }
+ // Do we extend over the page's/column's/etc. lower edge?
+ const SwFrame* pUp = GetUpper();
+ if( pUp->IsInSct() )
+ {
+ if( pUp->IsSctFrame() )
+ pUp = pUp->GetUpper();
+ else if( pUp->IsColBodyFrame() &&
+ pUp->GetUpper()->GetUpper()->IsSctFrame() )
+ pUp = pUp->GetUpper()->GetUpper()->GetUpper();
+ }
+ SwRectFnSet aRectFnSet(this);
+ SwTwips nLimit = aRectFnSet.GetPrtBottom(*pUp);
+ SwTwips nRectBottom = rRect.Bottom();
+ if ( aRectFnSet.IsVert() )
+ nRectBottom = SwitchHorizontalToVertical( nRectBottom );
+
+ if( aRectFnSet.YDiff( nLimit, nRectBottom ) < 0 )
+ bFill = false;
+ else
+ rRect.Width( 1 );
+ }
+ }
+ const_cast<SwCursorMoveState*>(rFill.pCMS)->m_bFillRet = bFill;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx
new file mode 100644
index 0000000000..76a1df5621
--- /dev/null
+++ b/sw/source/core/text/frmform.cxx
@@ -0,0 +1,2299 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_wasm_strip.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <IDocumentRedlineAccess.hxx>
+#include <anchoredobject.hxx>
+#include <bodyfrm.hxx>
+#include <hintids.hxx>
+#include <editeng/keepitem.hxx>
+#include <editeng/hyphenzoneitem.hxx>
+#include <pagefrm.hxx>
+#include <ndtxt.hxx>
+#include <ftnfrm.hxx>
+#include <txtftn.hxx>
+#include <fmtftn.hxx>
+#include <paratr.hxx>
+#include <viewopt.hxx>
+#include <viewsh.hxx>
+#include <frmatr.hxx>
+#include <pam.hxx>
+#include <fmtanchr.hxx>
+#include "itrform2.hxx"
+#include "widorp.hxx"
+#include "txtcache.hxx"
+#include <sectfrm.hxx>
+#include <rootfrm.hxx>
+#include <frmfmt.hxx>
+#include <sortedobjs.hxx>
+#include <editeng/tstpitem.hxx>
+#include <redline.hxx>
+#include <comphelper/lok.hxx>
+#include <flyfrms.hxx>
+#include <frmtool.hxx>
+#include <layouter.hxx>
+
+// Tolerance in formatting and text output
+#define SLOPPY_TWIPS 5
+
+namespace {
+
+class FormatLevel
+{
+ static sal_uInt16 s_nLevel;
+public:
+ FormatLevel() { ++s_nLevel; }
+ ~FormatLevel() { --s_nLevel; }
+ static sal_uInt16 GetLevel() { return s_nLevel; }
+ static bool LastLevel() { return 10 < s_nLevel; }
+};
+
+}
+
+sal_uInt16 FormatLevel::s_nLevel = 0;
+
+void ValidateText( SwFrame *pFrame ) // Friend of frame
+{
+ if ( ( ! pFrame->IsVertical() &&
+ pFrame->getFrameArea().Width() == pFrame->GetUpper()->getFramePrintArea().Width() ) ||
+ ( pFrame->IsVertical() &&
+ pFrame->getFrameArea().Height() == pFrame->GetUpper()->getFramePrintArea().Height() ) )
+ {
+ pFrame->setFrameAreaSizeValid(true);
+ }
+}
+
+void SwTextFrame::ValidateFrame()
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ // Validate surroundings to avoid oscillation
+ SwSwapIfSwapped swap( this );
+
+ if ( !IsInFly() && !IsInTab() )
+ { // Only validate 'this' when inside a fly, the rest should actually only be
+ // needed for footnotes, which do not exist in flys.
+ SwSectionFrame* pSct = FindSctFrame();
+ if( pSct )
+ {
+ if( !pSct->IsColLocked() )
+ pSct->ColLock();
+ else
+ pSct = nullptr;
+ }
+
+ SwFrame *pUp = GetUpper();
+ pUp->Calc(pRenderContext);
+ if( pSct )
+ pSct->ColUnlock();
+ }
+ ValidateText( this );
+
+ // We at least have to save the MustFit flag!
+ assert(HasPara() && "ResetPreps(), missing ParaPortion, SwCache bug?");
+ SwParaPortion *pPara = GetPara();
+ const bool bMustFit = pPara->IsPrepMustFit();
+ ResetPreps();
+ pPara->SetPrepMustFit( bMustFit );
+}
+
+// After a RemoveFootnote the BodyFrame and all Frames contained within it, need to be
+// recalculated, so that the DeadLine is right.
+// First we search outwards, on the way back we calculate everything.
+static void ValidateBodyFrame_( SwFrame *pFrame )
+{
+ vcl::RenderContext* pRenderContext = pFrame ? pFrame->getRootFrame()->GetCurrShell()->GetOut() : nullptr;
+ if( !pFrame || pFrame->IsCellFrame() )
+ return;
+
+ if( !pFrame->IsBodyFrame() && pFrame->GetUpper() )
+ ValidateBodyFrame_( pFrame->GetUpper() );
+ if( !pFrame->IsSctFrame() )
+ pFrame->Calc(pRenderContext);
+ else
+ {
+ const bool bOld = static_cast<SwSectionFrame*>(pFrame)->IsContentLocked();
+ static_cast<SwSectionFrame*>(pFrame)->SetContentLock( true );
+ pFrame->Calc(pRenderContext);
+ if( !bOld )
+ static_cast<SwSectionFrame*>(pFrame)->SetContentLock( false );
+ }
+}
+
+void SwTextFrame::ValidateBodyFrame()
+{
+ SwSwapIfSwapped swap( this );
+
+ // See comment in ValidateFrame()
+ if ( !IsInFly() && !IsInTab() &&
+ !( IsInSct() && FindSctFrame()->Lower()->IsColumnFrame() ) )
+ ValidateBodyFrame_( GetUpper() );
+}
+
+bool SwTextFrame::GetDropRect_( SwRect &rRect ) const
+{
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ OSL_ENSURE( HasPara(), "SwTextFrame::GetDropRect_: try again next year." );
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(this) );
+ SwTextMargin aLine( const_cast<SwTextFrame*>(this), &aInf );
+ if( aLine.GetDropLines() )
+ {
+ rRect.Top( aLine.Y() );
+ rRect.Left( aLine.GetLineStart() );
+ rRect.Height( aLine.GetDropHeight() );
+ rRect.Width( aLine.GetDropLeft() );
+
+ if ( IsRightToLeft() )
+ SwitchLTRtoRTL( rRect );
+
+ if ( IsVertical() )
+ SwitchHorizontalToVertical( rRect );
+ return true;
+ }
+
+ return false;
+}
+
+bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst)
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ SwSwapIfSwapped swap( this );
+
+ OSL_ENSURE( HasFollow(), "CalcFollow: missing Follow." );
+
+ SwTextFrame* pMyFollow = GetFollow();
+
+ SwParaPortion *pPara = GetPara();
+ const bool bFollowField = pPara && pPara->IsFollowField();
+
+ if( !pMyFollow->GetOffset() || pMyFollow->GetOffset() != nTextOfst ||
+ bFollowField || pMyFollow->IsFieldFollow() ||
+ ( pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Width() ) ||
+ ( ! pMyFollow->IsVertical() && !pMyFollow->getFramePrintArea().Height() ) )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ const SwFrame *pOldUp = GetUpper();
+#endif
+
+ SwRectFnSet aRectFnSet(this);
+ SwTwips nOldBottom = aRectFnSet.GetBottom(GetUpper()->getFrameArea());
+ SwTwips nMyPos = aRectFnSet.GetTop(getFrameArea());
+
+ const SwPageFrame *pPage = nullptr;
+ bool bOldInvaContent = true;
+ if ( !IsInFly() && GetNext() )
+ {
+ pPage = FindPageFrame();
+ // Minimize (reset if possible) invalidations: see below
+ bOldInvaContent = pPage->IsInvalidContent();
+ }
+
+ pMyFollow->SetOffset_( nTextOfst );
+ pMyFollow->SetFieldFollow( bFollowField );
+ if( HasFootnote() || pMyFollow->HasFootnote() )
+ {
+ ValidateFrame();
+ ValidateBodyFrame();
+ if( pPara )
+ {
+ pPara->GetReformat() = SwCharRange();
+ pPara->SetDelta(0);
+ }
+ }
+
+ // The footnote area must not get larger
+ SwSaveFootnoteHeight aSave( FindFootnoteBossFrame( true ), LONG_MAX );
+
+ pMyFollow->CalcFootnoteFlag();
+ if ( !pMyFollow->GetNext() && !pMyFollow->HasFootnote() )
+ nOldBottom = aRectFnSet.IsVert() ? 0 : LONG_MAX;
+
+ // tdf#122892 check flag:
+ // 1. WidowsAndOrphans::FindWidows() determines follow is a widow
+ // 2. SwTextFrame::PrepWidows() calls SetPrepWidows() on master;
+ // if it can spare lines, master truncates one line
+ // 3. SwTextFrame::CalcPreps() on master (below);
+ // unless IsPrepMustFit(), if master hasn't shrunk via 2., it will SetWidow()
+ // 4. loop must exit then, because the follow didn't grow so nothing will ever change
+ while (!IsWidow())
+ {
+ if( !FormatLevel::LastLevel() )
+ {
+ // If the follow is contained within a column section or column
+ // frame, we need to calculate that first. This is because the
+ // FormatWidthCols() does not work if it is called from MakeAll
+ // of the _locked_ follow.
+ SwSectionFrame* pSct = pMyFollow->FindSctFrame();
+ if( pSct && !pSct->IsAnLower( this ) )
+ {
+ if( pSct->GetFollow() )
+ pSct->SimpleFormat();
+ else if( ( pSct->IsVertical() && !pSct->getFrameArea().Width() ) ||
+ ( ! pSct->IsVertical() && !pSct->getFrameArea().Height() ) )
+ break;
+ }
+ // i#11760 - Intrinsic format of follow is controlled.
+ if ( FollowFormatAllowed() )
+ {
+ // i#11760 - No nested format of follows, if
+ // text frame is contained in a column frame.
+ // Thus, forbid intrinsic format of follow.
+ {
+ bool bIsFollowInColumn = false;
+ SwFrame* pFollowUpper = pMyFollow->GetUpper();
+ while ( pFollowUpper )
+ {
+ if ( pFollowUpper->IsColumnFrame() )
+ {
+ bIsFollowInColumn = true;
+ break;
+ }
+ if ( pFollowUpper->IsPageFrame() ||
+ pFollowUpper->IsFlyFrame() )
+ {
+ break;
+ }
+ pFollowUpper = pFollowUpper->GetUpper();
+ }
+ if ( bIsFollowInColumn )
+ {
+ pMyFollow->ForbidFollowFormat();
+ }
+ }
+
+ pMyFollow->Calc(pRenderContext);
+ // The Follow can tell from its getFrameArea().Height() that something went wrong
+ OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: cheesy follow" );
+ if( pMyFollow->GetPrev() )
+ {
+ pMyFollow->Prepare();
+ pMyFollow->Calc(pRenderContext);
+ OSL_ENSURE( !pMyFollow->GetPrev(), "SwTextFrame::CalcFollow: very cheesy follow" );
+ }
+
+ // i#11760 - Reset control flag for follow format.
+ pMyFollow->AllowFollowFormat();
+ }
+
+ // Make sure that the Follow gets painted
+ pMyFollow->SetCompletePaint();
+ }
+
+ pPara = GetPara();
+ // As long as the Follow requests lines due to Orphans, it is
+ // passed these and is formatted again if possible
+ if( pPara && pPara->IsPrepWidows() )
+ CalcPreps();
+ else
+ break;
+ }
+
+ if( HasFootnote() || pMyFollow->HasFootnote() )
+ {
+ ValidateBodyFrame();
+ ValidateFrame();
+ if( pPara )
+ {
+ pPara->GetReformat() = SwCharRange();
+ pPara->SetDelta(0);
+ }
+ }
+
+ if ( pPage && !bOldInvaContent )
+ pPage->ValidateContent();
+
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( pOldUp == GetUpper(), "SwTextFrame::CalcFollow: heavy follow" );
+#endif
+
+ const tools::Long nRemaining =
+ - aRectFnSet.BottomDist( GetUpper()->getFrameArea(), nOldBottom );
+ if ( nRemaining > 0 &&
+ nRemaining != ( aRectFnSet.IsVert() ?
+ nMyPos - getFrameArea().Right() :
+ getFrameArea().Top() - nMyPos ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SwTextFrame::MakePos()
+{
+ Point aOldPos = getFrameArea().Pos();
+ SwFrame::MakePos();
+
+ // Recalc split flys if our position changed.
+ if (aOldPos != getFrameArea().Pos())
+ {
+ // Find the master frame.
+ const SwTextFrame* pMaster = this;
+ while (pMaster->IsFollow())
+ {
+ pMaster = pMaster->FindMaster();
+ }
+ // Find which flys are effectively anchored to this frame.
+ for (const auto& pFly : pMaster->GetSplitFlyDrawObjs())
+ {
+ SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
+ if (pFlyAnchor != this)
+ {
+ continue;
+ }
+ // Possibly this fly was positioned relative to us, invalidate its position now that our
+ // position is changed.
+ SwPageFrame* pPageFrame = pFly->FindPageFrame();
+ bool bFlyNeedsPositioning = false;
+ bool bFlyPageMismatch = false;
+ if (pPageFrame)
+ {
+ // Was the position just adjusted to be inside the page frame?
+ bFlyNeedsPositioning = pFly->getFrameArea().Pos() == pPageFrame->getFrameArea().Pos();
+ // Is the fly on a page different than the anchor frame?
+ bFlyPageMismatch = pPageFrame != FindPageFrame();
+ }
+ if (bFlyNeedsPositioning || bFlyPageMismatch)
+ {
+ // Not really positioned, unlock the position once to allow a recalc.
+ pFly->UnlockPosition();
+ }
+ pFly->InvalidatePos();
+ }
+ }
+
+ // Inform LOK clients about change in position of redlines (if any)
+ if(!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ SwTextNode const* pTextNode = GetTextNodeFirst();
+ const SwRedlineTable& rTable = pTextNode->getIDocumentRedlineAccess().GetRedlineTable();
+ for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos)
+ {
+ SwRangeRedline* pRedln = rTable[nRedlnPos];
+ if (pTextNode->GetIndex() == pRedln->GetPoint()->GetNode().GetIndex())
+ {
+ pRedln->MaybeNotifyRedlinePositionModification(getFrameArea().Top());
+ if (GetMergedPara()
+ && pRedln->GetType() == RedlineType::Delete
+ && pRedln->GetPoint()->GetNode() != pRedln->GetMark()->GetNode())
+ {
+ pTextNode = pRedln->End()->GetNode().GetTextNode();
+ }
+ }
+ }
+}
+
+void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit )
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ if( IsUndersized() )
+ {
+ if( GetOffset() && !IsFollow() ) // A scrolled paragraph (undersized)
+ return;
+ SetUndersized( nChgHght == 0 || bHasToFit );
+ }
+
+ // AdjustFrame is called with a swapped frame during
+ // formatting but the frame is not swapped during FormatEmpty
+ SwSwapIfSwapped swap( this );
+ SwRectFnSet aRectFnSet(this);
+
+ // The Frame's size variable is incremented by Grow or decremented by Shrink.
+ // If the size cannot change, nothing should happen!
+ if( nChgHght >= 0)
+ {
+ SwTwips nChgHeight = nChgHght;
+ if( nChgHght && !bHasToFit )
+ {
+ if( IsInFootnote() && !IsInSct() )
+ {
+ SwTwips nReal = Grow( nChgHght, true );
+ if( nReal < nChgHght )
+ {
+ SwTwips nBot = aRectFnSet.YInc( aRectFnSet.GetBottom(getFrameArea()),
+ nChgHght - nReal );
+ SwFrame* pCont = FindFootnoteFrame()->GetUpper();
+
+ if( aRectFnSet.BottomDist( pCont->getFrameArea(), nBot ) > 0 )
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aRectFnSet.AddBottom( aFrm, nChgHght );
+
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+
+ if( aRectFnSet.IsVert() )
+ {
+ aPrt.AddWidth(nChgHght );
+ }
+ else
+ {
+ aPrt.AddHeight(nChgHght );
+ }
+
+ return;
+ }
+ }
+ }
+
+ Grow( nChgHght );
+
+ if ( IsInFly() )
+ {
+ // If one of the Upper is a Fly, it's very likely that this fly changes its
+ // position by the Grow. Therefore, my position has to be corrected also or
+ // the check further down is not meaningful.
+ // The predecessors need to be calculated, so that the position can be
+ // calculated correctly.
+ if ( GetPrev() )
+ {
+ SwFrame *pPre = GetUpper()->Lower();
+ do
+ { pPre->Calc(pRenderContext);
+ pPre = pPre->GetNext();
+ } while ( pPre && pPre != this );
+ }
+ const Point aOldPos( getFrameArea().Pos() );
+ MakePos();
+ if ( aOldPos != getFrameArea().Pos() )
+ {
+ InvalidateObjs(false);
+ }
+ }
+ nChgHeight = 0;
+ }
+ // A Grow() is always accepted by the Layout, even if the
+ // FixSize of the surrounding layout frame should not allow it.
+ // We text for this case and correct the values.
+ // The Frame must NOT be shrunk further than its size permits
+ // even in the case of an emergency.
+ SwTwips nRstHeight;
+ if ( IsVertical() )
+ {
+ OSL_ENSURE( ! IsSwapped(),"Swapped frame while calculating nRstHeight" );
+
+ if ( IsVertLR() )
+ nRstHeight = GetUpper()->getFrameArea().Left()
+ + GetUpper()->getFramePrintArea().Left()
+ + GetUpper()->getFramePrintArea().Width()
+ - getFrameArea().Left();
+ else
+ nRstHeight = getFrameArea().Left() + getFrameArea().Width() -
+ ( GetUpper()->getFrameArea().Left() + GetUpper()->getFramePrintArea().Left() );
+ }
+ else
+ nRstHeight = GetUpper()->getFrameArea().Top()
+ + GetUpper()->getFramePrintArea().Top()
+ + GetUpper()->getFramePrintArea().Height()
+ - getFrameArea().Top();
+
+ // We can get a bit of space in table cells, because there could be some
+ // left through a vertical alignment to the top.
+ // Assure that first lower in upper is the current one or is valid.
+ if ( IsInTab() &&
+ ( GetUpper()->Lower() == this ||
+ GetUpper()->Lower()->isFrameAreaDefinitionValid() ) )
+ {
+ tools::Long nAdd = aRectFnSet.YDiff( aRectFnSet.GetTop(GetUpper()->Lower()->getFrameArea()),
+ aRectFnSet.GetPrtTop(*GetUpper()) );
+ OSL_ENSURE( nAdd >= 0, "Ey" );
+ nRstHeight += nAdd;
+ }
+
+ // nRstHeight < 0 means that the TextFrame is located completely outside of its Upper.
+ // This can happen, if it's located within a FlyAtContentFrame, which changed sides by a
+ // Grow(). In such a case, it's wrong to execute the following Grow().
+ // In the case of a bug, we end up with an infinite loop.
+ SwTwips nFrameHeight = aRectFnSet.GetHeight(getFrameArea());
+ SwTwips nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea());
+
+ if( nRstHeight < nFrameHeight )
+ {
+ // It can be that I have the right size, but the Upper is too small and can get me some room
+ if( ( nRstHeight >= 0 || ( IsInFootnote() && IsInSct() ) ) && !bHasToFit )
+ nRstHeight += GetUpper()->Grow( nFrameHeight - nRstHeight );
+ // In column sections we do not want to get too big or else more areas are created by
+ // GetNextSctLeaf. Instead, we shrink and remember bUndersized, so that FormatWidthCols
+ // can calculate the right column size.
+ if ( nRstHeight < nFrameHeight )
+ {
+ if( bHasToFit || !IsMoveable() ||
+ ( IsInSct() && !FindSctFrame()->MoveAllowed(this) ) )
+ {
+ SetUndersized( true );
+ Shrink( std::min( ( nFrameHeight - nRstHeight), nPrtHeight ) );
+ }
+ else
+ SetUndersized( false );
+ }
+ }
+ else if( nChgHeight )
+ {
+ if( nRstHeight - nFrameHeight < nChgHeight )
+ nChgHeight = nRstHeight - nFrameHeight;
+ if( nChgHeight )
+ Grow( nChgHeight );
+ }
+ }
+ else
+ Shrink( -nChgHght );
+}
+
+css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips CurrentPos )
+{
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
+ SwTextFormatter aLine( this, &aInf );
+ SwTextCursor TextCursor( this, &aInf );
+ const Point aCharPos( TextCursor.GetTopLeft() );
+
+ SwTwips nRight = aLine.Right();
+ CurrentPos -= aCharPos.X();
+
+ // get current tab stop information stored in the Frame
+ const SvxTabStop *pTS = aLine.GetLineInfo().GetTabStop( CurrentPos, nRight );
+
+ if( !pTS )
+ {
+ return {};
+ }
+
+ // copy tab stop information into a Sequence, which only contains one element.
+ css::style::TabStop ts;
+ ts.Position = pTS->GetTabPos();
+ ts.DecimalChar = pTS->GetDecimal();
+ ts.FillChar = pTS->GetFill();
+ switch( pTS->GetAdjustment() )
+ {
+ case SvxTabAdjust::Left : ts.Alignment = css::style::TabAlign_LEFT; break;
+ case SvxTabAdjust::Center : ts.Alignment = css::style::TabAlign_CENTER; break;
+ case SvxTabAdjust::Right : ts.Alignment = css::style::TabAlign_RIGHT; break;
+ case SvxTabAdjust::Decimal: ts.Alignment = css::style::TabAlign_DECIMAL; break;
+ case SvxTabAdjust::Default: ts.Alignment = css::style::TabAlign_DEFAULT; break;
+ default: break; // prevent warning
+ }
+
+ return { ts };
+}
+
+// AdjustFollow expects the following situation:
+// The SwTextIter points to the lower end of the Master, the Offset is set in the Follow.
+// nOffset holds the Offset in the text string, from which the Master closes
+// and the Follow starts.
+// If it's 0, the FollowFrame is deleted.
+void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine,
+ const TextFrameIndex nOffset, const TextFrameIndex nEnd,
+ const sal_uInt8 nMode )
+{
+ SwFrameSwapper aSwapper( this, false );
+
+ // We got the rest of the text mass: Delete all Follows
+ // DummyPortions() are a special case.
+ // Special cases are controlled by parameter <nMode>.
+ bool bDontJoin = nMode & 1;
+ if( HasFollow() && !bDontJoin && nOffset == nEnd )
+ {
+ while( GetFollow() )
+ {
+ if( GetFollow()->IsLocked() )
+ {
+ // this can happen when follow calls pMaster->GetFormatted()
+ SAL_INFO("sw.core", "+SwTextFrame::JoinFrame: Follow is locked." );
+ return;
+ }
+ if (GetFollow()->IsDeleteForbidden())
+ return;
+
+ if (HasNonLastSplitFlyDrawObj())
+ {
+ // If a fly frame is anchored to us that has a follow, then don't join the anchor.
+ // First those fly frames have to be joined.
+ return;
+ }
+
+ JoinFrame();
+ }
+
+ return;
+ }
+
+ // Dancing on the volcano: We'll just format the last line quickly
+ // for the QuoVadis stuff.
+ // The Offset can move of course:
+ const TextFrameIndex nNewOfst = (IsInFootnote() && (!GetIndNext() || HasFollow()))
+ ? rLine.FormatQuoVadis(nOffset) : nOffset;
+
+ bool bHasNonLastSplitFlyDrawObj = false;
+ if (GetFollow() && GetOffset() == GetFollow()->GetOffset())
+ {
+ bHasNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj();
+ }
+
+ if( !bDontJoin )
+ {
+ // We steal text mass from our Follows
+ // It can happen that we have to join some of them
+ while( GetFollow() && GetFollow()->GetFollow() &&
+ nNewOfst >= GetFollow()->GetFollow()->GetOffset() )
+ {
+ if (bHasNonLastSplitFlyDrawObj)
+ {
+ // A non-last split fly is anchored to us, don't move content from the last frame to
+ // this one and don't join.
+ return;
+ }
+
+ JoinFrame();
+ }
+ }
+
+ if (IsEmptyMasterWithSplitFly())
+ {
+ // A split fly is anchored to us, don't move content from the follow frame to this one.
+ return;
+ }
+
+ // The Offset moved
+ if( GetFollow() )
+ {
+ if (!bDontJoin && bHasNonLastSplitFlyDrawObj)
+ {
+ // A non-last split fly is anchored to us, our follow is the last one in the text frame
+ // chain. No move of text from that follow to this text frame.
+ return;
+ }
+
+ if ( nMode )
+ GetFollow()->ManipOfst(TextFrameIndex(0));
+
+ if ( CalcFollow( nNewOfst ) ) // CalcFollow only at the end, we do a SetOffset there
+ rLine.SetOnceMore( true );
+ }
+}
+
+SwContentFrame *SwTextFrame::JoinFrame()
+{
+ OSL_ENSURE( GetFollow(), "+SwTextFrame::JoinFrame: no follow" );
+ SwTextFrame *pFoll = GetFollow();
+
+ SwTextFrame *pNxt = pFoll->GetFollow();
+
+ // All footnotes of the to-be-destroyed Follow are relocated to us
+ TextFrameIndex nStart = pFoll->GetOffset();
+ if ( pFoll->HasFootnote() )
+ {
+ SwFootnoteBossFrame *pFootnoteBoss = nullptr;
+ SwFootnoteBossFrame *pEndBoss = nullptr;
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*pFoll);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if (RES_TXTATR_FTN == pHt->Which()
+ && nStart <= pFoll->MapModelToView(pNode, pHt->GetStart()))
+ {
+ if (pHt->GetFootnote().IsEndNote())
+ {
+ if (!pEndBoss)
+ pEndBoss = pFoll->FindFootnoteBossFrame();
+ SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this );
+ }
+ else
+ {
+ if (!pFootnoteBoss)
+ pFootnoteBoss = pFoll->FindFootnoteBossFrame( true );
+ SwFootnoteBossFrame::ChangeFootnoteRef( pFoll, static_cast<const SwTextFootnote*>(pHt), this );
+ }
+ SetFootnote( true );
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ else if ( pFoll->isFramePrintAreaValid() ||
+ pFoll->isFrameAreaSizeValid() )
+ {
+ pFoll->CalcFootnoteFlag();
+ OSL_ENSURE( !pFoll->HasFootnote(), "Missing FootnoteFlag." );
+ }
+#endif
+
+ pFoll->MoveFlyInCnt( this, nStart, TextFrameIndex(COMPLETE_STRING) );
+ pFoll->SetFootnote( false );
+ // i#27138
+ // Notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation.
+ // Relation CONTENT_FLOWS_FROM for current next paragraph will change
+ // and relation CONTENT_FLOWS_TO for current previous paragraph, which
+ // is <this>, will change.
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ {
+ SwViewShell* pViewShell( pFoll->getRootFrame()->GetCurrShell() );
+ if ( pViewShell && pViewShell->GetLayout() &&
+ pViewShell->GetLayout()->IsAnyShellAccessible() )
+ {
+ auto pNext = pFoll->FindNextCnt( true );
+ pViewShell->InvalidateAccessibleParaFlowRelation(
+ pNext ? pNext->DynCastTextFrame() : nullptr,
+ this );
+ }
+ }
+#endif
+
+ pFoll->Cut();
+ SetFollow(pNxt);
+ SwFrame::DestroyFrame(pFoll);
+ return pNxt;
+}
+
+void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos)
+{
+ SwSwapIfSwapped swap( this );
+
+ // The Paste sends a Modify() to me
+ // I lock myself, so that my data does not disappear
+ TextFrameLockGuard aLock( this );
+ SwTextFrame *const pNew = static_cast<SwTextFrame *>(GetTextNodeFirst()->MakeFrame(this));
+
+ pNew->SetFollow( GetFollow() );
+ SetFollow( pNew );
+
+ pNew->Paste( GetUpper(), GetNext() );
+ // i#27138
+ // notify accessibility paragraphs objects about changed CONTENT_FLOWS_FROM/_TO relation.
+ // Relation CONTENT_FLOWS_FROM for current next paragraph will change
+ // and relation CONTENT_FLOWS_TO for current previous paragraph, which
+ // is <this>, will change.
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ {
+ SwViewShell* pViewShell( pNew->getRootFrame()->GetCurrShell() );
+ if ( pViewShell && pViewShell->GetLayout() &&
+ pViewShell->GetLayout()->IsAnyShellAccessible() )
+ {
+ auto pNext = pNew->FindNextCnt( true );
+ pViewShell->InvalidateAccessibleParaFlowRelation(
+ pNext ? pNext->DynCastTextFrame() : nullptr,
+ this );
+ }
+ }
+#endif
+
+ // If footnotes end up in pNew bz our actions, we need
+ // to re-register them
+ if ( HasFootnote() )
+ {
+ SwFootnoteBossFrame *pFootnoteBoss = nullptr;
+ SwFootnoteBossFrame *pEndBoss = nullptr;
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*this);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if (RES_TXTATR_FTN == pHt->Which()
+ && nTextPos <= MapModelToView(pNode, pHt->GetStart()))
+ {
+ if (pHt->GetFootnote().IsEndNote())
+ {
+ if (!pEndBoss)
+ pEndBoss = FindFootnoteBossFrame();
+ SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew );
+ }
+ else
+ {
+ if (!pFootnoteBoss)
+ pFootnoteBoss = FindFootnoteBossFrame( true );
+ SwFootnoteBossFrame::ChangeFootnoteRef( this, static_cast<const SwTextFootnote*>(pHt), pNew );
+ }
+ pNew->SetFootnote( true );
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ else
+ {
+ CalcFootnoteFlag( nTextPos - TextFrameIndex(1) );
+ OSL_ENSURE( !HasFootnote(), "Missing FootnoteFlag." );
+ }
+#endif
+
+ MoveFlyInCnt( pNew, nTextPos, TextFrameIndex(COMPLETE_STRING) );
+
+ // No SetOffset or CalcFollow, because an AdjustFollow follows immediately anyways
+
+ pNew->ManipOfst( nTextPos );
+}
+
+void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst)
+{
+ // We do not need to invalidate our Follow.
+ // We are a Follow, get formatted right away and call
+ // SetOffset() from there
+ mnOffset = nNewOfst;
+ SwParaPortion *pPara = GetPara();
+ if( pPara )
+ {
+ SwCharRange &rReformat = pPara->GetReformat();
+ rReformat.Start() = TextFrameIndex(0);
+ rReformat.Len() = TextFrameIndex(GetText().getLength());
+ pPara->SetDelta(sal_Int32(rReformat.Len()));
+ }
+ InvalidateSize();
+}
+
+bool SwTextFrame::CalcPreps()
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::CalcPreps with swapped frame" );
+ SwRectFnSet aRectFnSet(this);
+
+ SwParaPortion *pPara = GetPara();
+ if ( !pPara )
+ return false;
+ const bool bPrep = pPara->IsPrep();
+ const bool bPrepWidows = pPara->IsPrepWidows();
+ const bool bPrepAdjust = pPara->IsPrepAdjust();
+ const bool bPrepMustFit = pPara->IsPrepMustFit();
+ ResetPreps();
+
+ bool bRet = false;
+ if( bPrep && !pPara->GetReformat().Len() )
+ {
+ // PrepareHint::Widows means that the orphans rule got activated in the Follow.
+ // In unfortunate cases we could also have a PrepAdjust!
+ if( bPrepWidows )
+ {
+ if( !GetFollow() )
+ {
+ OSL_ENSURE( GetFollow(), "+SwTextFrame::CalcPreps: no credits" );
+ return false;
+ }
+
+ // We need to prepare for two cases:
+ // We were able to hand over a few lines to the Follow
+ // -> we need to shrink
+ // or we need to go on the next page
+ // -> we let our Frame become too big
+
+ SwTwips nChgHeight = GetParHeight();
+ if( nChgHeight >= aRectFnSet.GetHeight(getFramePrintArea()) )
+ {
+ if( bPrepMustFit )
+ {
+ GetFollow()->SetJustWidow( true );
+ GetFollow()->Prepare();
+ }
+ else if ( aRectFnSet.IsVert() )
+ {
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( aFrm.Width() + aFrm.Left() );
+ aFrm.Left( 0 );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( aPrt.Width() + getFrameArea().Left() );
+ }
+
+ SetWidow( true );
+ }
+ else
+ {
+ // nTmp should be very large, but not so large as to cause overflow later (e.g.,
+ // GetFrameOfModify in sw/source/core/layout/frmtool.cxx calculates nCurrentDist
+ // from, among others, the square of aDiff.getY(), which can be close to nTmp);
+ // the previously used value TWIPS_MAX/2 (i.e., (LONG_MAX - 1)/2) depended on
+ // the range of 'long', while the value (SAL_MAX_INT32 - 1)/2 (which matches the
+ // old value on platforms where 'long' is 'sal_Int32') is empirically shown to
+ // be large enough in practice even on platforms where 'long' is 'sal_Int64':
+ SwTwips const nTmp = sw::WIDOW_MAGIC - (getFrameArea().Top()+10000);
+ SwTwips nDiff = nTmp - getFrameArea().Height();
+
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Height( nTmp );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Height( aPrt.Height() + nDiff );
+ }
+
+ SetWidow( true );
+ }
+ }
+ else
+ {
+ OSL_ENSURE( nChgHeight < aRectFnSet.GetHeight(getFramePrintArea()),
+ "+SwTextFrame::CalcPrep: want to shrink" );
+
+ nChgHeight = aRectFnSet.GetHeight(getFramePrintArea()) - nChgHeight;
+
+ GetFollow()->SetJustWidow( true );
+ GetFollow()->Prepare();
+ Shrink( nChgHeight );
+ SwRect &rRepaint = pPara->GetRepaint();
+
+ if ( aRectFnSet.IsVert() )
+ {
+ SwRect aRepaint( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
+ SwitchVerticalToHorizontal( aRepaint );
+ rRepaint.Chg( aRepaint.Pos(), aRepaint.SSize() );
+ }
+ else
+ rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
+
+ if( 0 >= rRepaint.Width() )
+ rRepaint.Width(1);
+ }
+ bRet = true;
+ }
+ else if ( bPrepAdjust )
+ {
+ if ( HasFootnote() )
+ {
+ if( !CalcPrepFootnoteAdjust() )
+ {
+ if( bPrepMustFit )
+ {
+ SwTextLineAccess aAccess( this );
+ aAccess.GetPara()->SetPrepMustFit(true);
+ }
+ return false;
+ }
+ }
+
+ {
+ SwSwapIfNotSwapped swap( this );
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
+ SwTextFormatter aLine( this, &aInf );
+
+ WidowsAndOrphans aFrameBreak( this );
+ // Whatever the attributes say: we split the paragraph in
+ // MustFit case if necessary
+ if( bPrepMustFit )
+ {
+ aFrameBreak.SetKeep( false );
+ aFrameBreak.ClrOrphLines();
+ }
+ // Before calling FormatAdjust, we need to make sure
+ // that the lines protruding at the bottom get indeed
+ // truncated
+ bool bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine );
+ bRet = true;
+ while( !bBreak && aLine.Next() )
+ {
+ bBreak = aFrameBreak.IsBreakNowWidAndOrp( aLine );
+ }
+ if( bBreak )
+ {
+ // We run into troubles: when TruncLines is called, the
+ // conditions in IsInside change immediately such that
+ // IsBreakNow can return different results.
+ // For this reason, we tell rFrameBreak that the
+ // end is reached at the location of rLine.
+ // Let's see if it works ...
+ aLine.TruncLines();
+ aFrameBreak.SetRstHeight( aLine );
+ FormatAdjust( aLine, aFrameBreak, TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() );
+ }
+ else
+ {
+ if( !GetFollow() )
+ {
+ FormatAdjust( aLine, aFrameBreak,
+ TextFrameIndex(aInf.GetText().getLength()), aInf.IsStop() );
+ }
+ else if ( !aFrameBreak.IsKeepAlways() )
+ {
+ // We delete a line before the Master, because the Follow
+ // could hand over a line
+ const SwCharRange aFollowRg(GetFollow()->GetOffset(), TextFrameIndex(1));
+ pPara->GetReformat() += aFollowRg;
+ // We should continue!
+ bRet = false;
+ }
+ }
+ }
+
+ // A final check, if FormatAdjust() didn't help we need to
+ // truncate
+ if( bPrepMustFit )
+ {
+ const SwTwips nMust = aRectFnSet.GetPrtBottom(*GetUpper());
+ const SwTwips nIs = aRectFnSet.GetBottom(getFrameArea());
+
+ if( aRectFnSet.IsVert() && nIs < nMust )
+ {
+ Shrink( nMust - nIs );
+
+ if( getFramePrintArea().Width() < 0 )
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( 0 );
+ }
+
+ SetUndersized( true );
+ }
+ else if ( ! aRectFnSet.IsVert() && nIs > nMust )
+ {
+ Shrink( nIs - nMust );
+
+ if( getFramePrintArea().Height() < 0 )
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Height( 0 );
+ }
+
+ SetUndersized( true );
+ }
+ }
+ }
+ }
+ pPara->SetPrepMustFit( bPrepMustFit );
+ return bRet;
+}
+
+// Move the as-character objects - footnotes must be moved by RemoveFootnote!
+void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew )
+{
+ if( pFrame->GetOffset() < nNew )
+ pFrame->MoveFlyInCnt( this, TextFrameIndex(0), nNew );
+ else if( pFrame->GetOffset() > nNew )
+ MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) );
+}
+
+void SwTextFrame::FormatAdjust( SwTextFormatter &rLine,
+ WidowsAndOrphans &rFrameBreak,
+ TextFrameIndex const nStrLen,
+ const bool bDummy )
+{
+ SwSwapIfNotSwapped swap( this );
+
+ SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
+
+ TextFrameIndex nEnd = rLine.GetStart();
+
+ const bool bHasToFit = pPara->IsPrepMustFit();
+
+ // The StopFlag is set by footnotes which want to go onto the next page
+ // Call base class method <SwTextFrameBreak::IsBreakNow(..)>
+ // instead of method <WidowsAndOrphans::IsBreakNow(..)> to get a break,
+ // even if due to widow rule no enough lines exists.
+ sal_uInt8 nNew = ( !GetFollow() &&
+ nEnd < nStrLen &&
+ ( rLine.IsStop() ||
+ ( bHasToFit
+ ? ( rLine.GetLineNr() > 1 &&
+ !rFrameBreak.IsInside( rLine ) )
+ : rFrameBreak.IsBreakNow( rLine ) ) ) )
+ ? 1 : 0;
+
+ SwTextFormatInfo& rInf = rLine.GetInfo();
+ if (nNew == 0 && !nStrLen && !rInf.GetTextFly().IsOn() && IsEmptyWithSplitFly())
+ {
+ // Empty paragraph, so IsBreakNow() is not called, but we should split the fly portion and
+ // the paragraph marker.
+ nNew = 1;
+ }
+
+ // i#84870
+ // no split of text frame, which only contains an as-character anchored object
+ bool bOnlyContainsAsCharAnchoredObj =
+ !IsFollow() && nStrLen == TextFrameIndex(1) &&
+ GetDrawObjs() && GetDrawObjs()->size() == 1 &&
+ (*GetDrawObjs())[0]->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR;
+
+ // Still try split text frame if we have columns.
+ if (FindColFrame())
+ bOnlyContainsAsCharAnchoredObj = false;
+
+ if ( nNew && bOnlyContainsAsCharAnchoredObj )
+ {
+ nNew = 0;
+ }
+
+ if ( nNew )
+ {
+ SplitFrame( nEnd );
+ }
+
+ const SwFrame *pBodyFrame = FindBodyFrame();
+
+ const tools::Long nBodyHeight = pBodyFrame ? ( IsVertical() ?
+ pBodyFrame->getFrameArea().Width() :
+ pBodyFrame->getFrameArea().Height() ) : 0;
+
+ // If the current values have been calculated, show that they
+ // are valid now
+ pPara->GetReformat() = SwCharRange();
+ bool bDelta = pPara->GetDelta() != 0;
+ pPara->SetDelta(0);
+
+ if( rLine.IsStop() )
+ {
+ rLine.TruncLines( true );
+ nNew = 1;
+ }
+
+ // FindBreak truncates the last line
+ if( !rFrameBreak.FindBreak( this, rLine, bHasToFit ) )
+ {
+ // If we're done formatting, we set nEnd to the end.
+ // AdjustFollow might execute JoinFrame() because of this.
+ // Else, nEnd is the end of the last line in the Master.
+ TextFrameIndex nOld = nEnd;
+ // Make sure content from the last floating table anchor is not shifted to previous anchors.
+ if (!HasNonLastSplitFlyDrawObj())
+ {
+ nEnd = rLine.GetEnd();
+ }
+ if( GetFollow() )
+ {
+ if( nNew && nOld < nEnd )
+ RemoveFootnote( nOld, nEnd - nOld );
+ ChangeOffset( GetFollow(), nEnd );
+ if( !bDelta )
+ GetFollow()->ManipOfst( nEnd );
+ }
+ }
+ else
+ { // If we pass over lines, we must not call Join in Follows, instead we even
+ // need to create a Follow.
+ // We also need to do this if the whole mass of text remains in the Master,
+ // because a hard line break could necessitate another line (without text mass)!
+ TextFrameIndex const nOld(nEnd);
+ nEnd = rLine.GetEnd();
+ if( GetFollow() )
+ {
+ // Another case for not joining the follow:
+ // Text frame has no content, but a numbering. Then, do *not* join.
+ // Example of this case: When an empty, but numbered paragraph
+ // at the end of page is completely displaced by a fly frame.
+ // Thus, the text frame introduced a follow by a
+ // <SwTextFrame::SplitFrame(..)> - see below. The follow then shows
+ // the numbering and must stay.
+ if ( GetFollow()->GetOffset() != nEnd ||
+ GetFollow()->IsFieldFollow() ||
+ (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule()))
+ {
+ nNew |= 3;
+ }
+ else if (FindTabFrame() && nEnd > TextFrameIndex(0) &&
+ rLine.GetInfo().GetChar(nEnd - TextFrameIndex(1)) == CH_BREAK)
+ {
+ // We are in a table, the paragraph has a follow and the text
+ // ends with a hard line break. Don't join the follow just
+ // because the follow would have no content, we may still need it
+ // for the paragraph mark.
+ nNew |= 1;
+ }
+ // move footnotes if the follow is kept - if RemoveFootnote() is
+ // called in next format iteration, it will be with the *new*
+ // offset so no effect!
+ if (nNew && nOld < nEnd)
+ {
+ RemoveFootnote(nOld, nEnd - nOld);
+ }
+ ChangeOffset( GetFollow(), nEnd );
+
+ SwFlyAtContentFrame* pNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj();
+ if (pNonLastSplitFlyDrawObj && !pNonLastSplitFlyDrawObj->IsWrapOnAllPages())
+ {
+ // Make sure content from the last floating table anchor is not shifted to previous
+ // anchors, unless we're in the special "wrap on all pages" mode.
+ nEnd = TextFrameIndex(0);
+ }
+
+ GetFollow()->ManipOfst( nEnd );
+ }
+ else
+ {
+ const SwTextNode* pTextNode = GetTextNodeForParaProps();
+ bool bHasVisibleNumRule = nStrLen == TextFrameIndex(0) && pTextNode->GetNumRule();
+
+ if (!pTextNode->HasVisibleNumberingOrBullet())
+ {
+ bHasVisibleNumRule = false;
+ }
+
+ // Only split frame, if the frame contains
+ // content or contains no content, but has a numbering.
+ // i#84870 - No split, if text frame only contains one
+ // as-character anchored object.
+ if ( !bOnlyContainsAsCharAnchoredObj &&
+ (nStrLen > TextFrameIndex(0) ||
+ bHasVisibleNumRule )
+ )
+ {
+ SplitFrame( nEnd );
+ nNew |= 3;
+ }
+ }
+ // If the remaining height changed e.g by RemoveFootnote() we need to
+ // fill up in order to avoid oscillation.
+ if( bDummy && pBodyFrame &&
+ nBodyHeight < ( IsVertical() ?
+ pBodyFrame->getFrameArea().Width() :
+ pBodyFrame->getFrameArea().Height() ) )
+ rLine.MakeDummyLine();
+ }
+
+ // In AdjustFrame() we set ourselves via Grow/Shrink
+ // In AdjustFollow() we set our FollowFrame
+
+ const SwTwips nDocPrtTop = getFrameArea().Top() + getFramePrintArea().Top();
+ const SwTwips nOldHeight = getFramePrintArea().SSize().Height();
+ SwTwips nChg = rLine.CalcBottomLine() - nDocPrtTop - nOldHeight;
+
+ //#i84870# - no shrink of text frame, if it only contains one as-character anchored object.
+ if ( nChg < 0 && !bDelta && bOnlyContainsAsCharAnchoredObj )
+ {
+ nChg = 0;
+ }
+
+ // Vertical Formatting:
+ // The (rotated) repaint rectangle's x coordinate refers to the frame.
+ // If the frame grows (or shirks) the repaint rectangle cannot simply
+ // be rotated back after formatting, because we use the upper left point
+ // of the frame for rotation. This point changes when growing/shrinking.
+
+ if ( IsVertical() && !IsVertLR() && nChg )
+ {
+ SwRect &rRepaint = pPara->GetRepaint();
+ rRepaint.Left( rRepaint.Left() - nChg );
+ rRepaint.Width( rRepaint.Width() - nChg );
+ }
+
+ AdjustFrame( nChg, bHasToFit );
+
+ if( HasFollow() || IsInFootnote() )
+ AdjustFollow_( rLine, nEnd, nStrLen, nNew );
+
+ pPara->SetPrepMustFit( false );
+}
+
+// bPrev is set whether Reformat.Start() was called because of Prev().
+// Else, we don't know whether we can limit the repaint or not.
+bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev )
+{
+ OSL_ENSURE( ! IsVertical() || IsSwapped(),
+ "SwTextFrame::FormatLine( rLine, bPrev) with unswapped frame" );
+ SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
+ const SwLineLayout *pOldCur = rLine.GetCurr();
+ const TextFrameIndex nOldLen = pOldCur->GetLen();
+ const SwTwips nOldAscent = pOldCur->GetAscent();
+ const SwTwips nOldHeight = pOldCur->Height();
+ const SwTwips nOldWidth = pOldCur->Width() + pOldCur->GetHangingMargin();
+ const bool bOldHyph = pOldCur->IsEndHyph();
+ SwTwips nOldTop = 0;
+ SwTwips nOldBottom = 0;
+ if( rLine.GetCurr()->IsClipping() )
+ rLine.CalcUnclipped( nOldTop, nOldBottom );
+
+ TextFrameIndex const nNewStart = rLine.FormatLine( rLine.GetStart() );
+
+ OSL_ENSURE( getFrameArea().Pos().Y() + getFramePrintArea().Pos().Y() == rLine.GetFirstPos(),
+ "SwTextFrame::FormatLine: frame leaves orbit." );
+ OSL_ENSURE( rLine.GetCurr()->Height(),
+ "SwTextFrame::FormatLine: line height is zero" );
+
+ // The current line break object
+ const SwLineLayout *pNew = rLine.GetCurr();
+
+ bool bUnChg = nOldLen == pNew->GetLen() &&
+ bOldHyph == pNew->IsEndHyph();
+ if ( bUnChg && !bPrev )
+ {
+ const tools::Long nWidthDiff = nOldWidth > pNew->Width()
+ ? nOldWidth - pNew->Width()
+ : pNew->Width() - nOldWidth;
+
+ // we only declare a line as unchanged, if its main values have not
+ // changed and it is not the last line (!paragraph end symbol!)
+ bUnChg = nOldHeight == pNew->Height() &&
+ nOldAscent == pNew->GetAscent() &&
+ nWidthDiff <= SLOPPY_TWIPS &&
+ pOldCur->GetNext();
+ }
+
+ // Calculate rRepaint
+ const SwTwips nBottom = rLine.Y() + rLine.GetLineHeight();
+ SwRepaint &rRepaint = pPara->GetRepaint();
+ if( bUnChg && rRepaint.Top() == rLine.Y()
+ && (bPrev || nNewStart <= pPara->GetReformat().Start())
+ && (nNewStart < TextFrameIndex(GetText().getLength())))
+ {
+ rRepaint.Top( nBottom );
+ rRepaint.Height( 0 );
+ }
+ else
+ {
+ if( nOldTop )
+ {
+ if( nOldTop < rRepaint.Top() )
+ rRepaint.Top( nOldTop );
+ if( !rLine.IsUnclipped() || nOldBottom > rRepaint.Bottom() )
+ {
+ rRepaint.Bottom( nOldBottom - 1 );
+ rLine.SetUnclipped( true );
+ }
+ }
+ if( rLine.GetCurr()->IsClipping() && rLine.IsFlyInCntBase() )
+ {
+ SwTwips nTmpTop, nTmpBottom;
+ rLine.CalcUnclipped( nTmpTop, nTmpBottom );
+ if( nTmpTop < rRepaint.Top() )
+ rRepaint.Top( nTmpTop );
+ if( !rLine.IsUnclipped() || nTmpBottom > rRepaint.Bottom() )
+ {
+ rRepaint.Bottom( nTmpBottom - 1 );
+ rLine.SetUnclipped( true );
+ }
+ }
+ else
+ {
+ if( !rLine.IsUnclipped() || nBottom > rRepaint.Bottom() )
+ {
+ rRepaint.Bottom( nBottom - 1 );
+ rLine.SetUnclipped( false );
+ }
+ }
+ SwTwips nRght = std::max( nOldWidth, pNew->Width() +
+ pNew->GetHangingMargin() );
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ const SwViewOption *pOpt = pSh ? pSh->GetViewOptions() : nullptr;
+ if( pOpt && (pOpt->IsParagraph() || pOpt->IsLineBreak()) )
+ nRght += ( std::max( nOldAscent, pNew->GetAscent() ) );
+ else
+ nRght += ( std::max( nOldAscent, pNew->GetAscent() ) / 4);
+ nRght += rLine.GetLeftMargin();
+ if( rRepaint.GetOffset() || rRepaint.GetRightOfst() < nRght )
+ rRepaint.SetRightOfst( nRght );
+
+ // Finally we enlarge the repaint rectangle if we found an underscore
+ // within our line. 40 Twips should be enough
+ const bool bHasUnderscore =
+ ( rLine.GetInfo().GetUnderScorePos() < nNewStart );
+ if ( bHasUnderscore || rLine.GetCurr()->HasUnderscore() )
+ rRepaint.Bottom( rRepaint.Bottom() + 40 );
+
+ const_cast<SwLineLayout*>(rLine.GetCurr())->SetUnderscore( bHasUnderscore );
+ }
+
+ // Calculating the good ol' nDelta
+ const sal_Int32 nDiff = sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen);
+ pPara->SetDelta(pPara->GetDelta() - nDiff);
+
+ // Stop!
+ if( rLine.IsStop() )
+ return false;
+
+ // Absolutely another line
+ if( rLine.IsNewLine() )
+ return true;
+
+ // Until the String's end?
+ if (nNewStart >= TextFrameIndex(GetText().getLength()))
+ return false;
+
+ if( rLine.GetInfo().IsShift() )
+ return true;
+
+ // Reached the Reformat's end?
+ const TextFrameIndex nEnd = pPara->GetReformat().Start() +
+ pPara->GetReformat().Len();
+
+ if( nNewStart <= nEnd )
+ return true;
+
+ return 0 != pPara->GetDelta();
+}
+
+void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf,
+ const bool bAdjust )
+{
+ OSL_ENSURE( ! IsVertical() || IsSwapped(),"SwTextFrame::Format_ with unswapped frame" );
+
+ SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
+ rLine.SetUnclipped( false );
+
+ const OUString & rString = GetText();
+ const TextFrameIndex nStrLen(rString.getLength());
+
+ SwCharRange &rReformat = pPara->GetReformat();
+ SwRepaint &rRepaint = pPara->GetRepaint();
+ std::unique_ptr<SwRepaint> pFreeze;
+
+ // Due to performance reasons we set rReformat to COMPLETE_STRING in Init()
+ // In this case we adjust rReformat
+ if( rReformat.Len() > nStrLen )
+ rReformat.Len() = nStrLen;
+
+ if( rReformat.Start() + rReformat.Len() > nStrLen )
+ rReformat.Len() = nStrLen - rReformat.Start();
+
+ SwTwips nOldBottom;
+ if( GetOffset() && !IsFollow() )
+ {
+ rLine.Bottom();
+ nOldBottom = rLine.Y();
+ rLine.Top();
+ }
+ else
+ nOldBottom = 0;
+ rLine.CharToLine( rReformat.Start() );
+
+ // When inserting or removing a Space, words can be moved out of the edited
+ // line and into the preceding line, hence the preceding line must be
+ // formatted as well.
+ // Optimization: If rReformat starts after the first word of the line,
+ // this line cannot possibly influence the previous one.
+ // ...Turns out that unfortunately it can: Text size changes + FlyFrames;
+ // the feedback can affect multiple lines (Frames!)!
+
+ // i#46560
+ // FME: Yes, consider this case: "(word )" has to go to the next line
+ // because ")" is a forbidden character at the beginning of a line although
+ // "(word" would still fit on the previous line. Adding text right in front
+ // of ")" would not trigger a reformatting of the previous line. Adding 1
+ // to the result of FindBrk() does not solve the problem in all cases,
+ // nevertheless it should be sufficient.
+ bool bPrev = rLine.GetPrev() &&
+ (FindBrk(rString, rLine.GetStart(), rReformat.Start() + TextFrameIndex(1))
+ // i#46560
+ + TextFrameIndex(1)
+ >= rReformat.Start() ||
+ rLine.GetCurr()->IsRest() );
+ if( bPrev )
+ {
+ while( rLine.Prev() )
+ if( rLine.GetCurr()->GetLen() && !rLine.GetCurr()->IsRest() )
+ {
+ if( !rLine.GetStart() )
+ rLine.Top(); // So that NumDone doesn't get confused
+ break;
+ }
+ TextFrameIndex nNew = rLine.GetStart() + rLine.GetLength();
+ if( nNew )
+ {
+ --nNew;
+ if (CH_BREAK == rString[sal_Int32(nNew)])
+ {
+ ++nNew;
+ rLine.Next();
+ bPrev = false;
+ }
+ }
+ rReformat.Len() += rReformat.Start() - nNew;
+ rReformat.Start() = nNew;
+ }
+
+ rRepaint.SetOffset( 0 );
+ rRepaint.SetRightOfst( 0 );
+ rRepaint.Chg( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
+ if( pPara->IsMargin() )
+ rRepaint.Width( rRepaint.Width() + pPara->GetHangingMargin() );
+ rRepaint.Top( rLine.Y() );
+ if( 0 >= rRepaint.Width() )
+ rRepaint.Width(1);
+ WidowsAndOrphans aFrameBreak( this, rInf.IsTest() ? 1 : 0 );
+
+ // rLine is now set to the first line which needs formatting.
+ // The bFirst flag makes sure that Next() is not called.
+ // The whole thing looks weird, but we need to make sure that
+ // rLine stops at the last non-fitting line when calling IsBreakNow.
+ bool bFirst = true;
+ bool bFormat = true;
+
+ // The CharToLine() can also get us into the danger zone.
+ // In that case we need to walk back until rLine is set
+ // to the non-fitting line. Or else the mass of text is lost,
+ // because the Ofst was set wrongly in the Follow.
+
+ bool bBreak = ( !pPara->IsPrepMustFit() || rLine.GetLineNr() > 1 )
+ && aFrameBreak.IsBreakNowWidAndOrp( rLine );
+ if( bBreak )
+ {
+ bool bPrevDone = nullptr != rLine.Prev();
+ while( bPrevDone && aFrameBreak.IsBreakNowWidAndOrp(rLine) )
+ bPrevDone = nullptr != rLine.Prev();
+ if( bPrevDone )
+ {
+ aFrameBreak.SetKeep( false );
+ rLine.Next();
+ }
+ rLine.TruncLines();
+
+ // Play it safe
+ aFrameBreak.IsBreakNowWidAndOrp(rLine);
+ }
+
+ /* Meaning if the following flags are set:
+
+ Watch(End/Mid)Hyph: we need to format if we have a break at
+ the line end/Fly, as long as MaxHyph is reached
+
+ Jump(End/Mid)Flag: the next line which has no break (line end/Fly),
+ needs to be formatted, because we could wrap now. This might have been
+ forbidden earlier by MaxHyph
+
+ Watch(End/Mid)Hyph: if the last formatted line got a cutoff point, but
+ didn't have one before
+
+ Jump(End/Mid)Hyph: if a cutoff point disappears
+ */
+ bool bJumpEndHyph = false;
+ bool bWatchEndHyph = false;
+ bool bJumpMidHyph = false;
+ bool bWatchMidHyph = false;
+
+ const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet();
+ rInf.MaxHyph() = rAttrSet.GetHyphenZone().GetMaxHyphens();
+ bool bMaxHyph = 0 != rInf.MaxHyph();
+ if ( bMaxHyph )
+ rLine.InitCntHyph();
+
+ if( IsFollow() && IsFieldFollow() && rLine.GetStart() == GetOffset() )
+ {
+ SwTextFrame *pMaster = FindMaster();
+ OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" );
+ const SwLineLayout* pLine=nullptr;
+ if (pMaster)
+ {
+ if (!pMaster->HasPara())
+ { // master could be locked because it's being formatted upstack
+ SAL_WARN("sw", "SwTextFrame::Format_: master not formatted!");
+ }
+ else
+ {
+ SwTextSizeInfo aInf( pMaster );
+ SwTextIter aMasterLine( pMaster, &aInf );
+ aMasterLine.Bottom();
+ pLine = aMasterLine.GetCurr();
+ assert(aMasterLine.GetEnd() == GetOffset());
+ }
+ }
+ SwLinePortion* pRest = pLine ?
+ rLine.MakeRestPortion(pLine, GetOffset()) : nullptr;
+ if( pRest )
+ rInf.SetRest( pRest );
+ else
+ SetFieldFollow( false );
+ }
+
+ /* Ad cancel criterion:
+ * In order to recognize, whether a line does not fit onto the page
+ * anymore, we need to format it. This overflow is removed again in
+ * e.g. AdjustFollow.
+ * Another complication: if we are the Master, we need to traverse
+ * the lines, because it could happen that one line can overflow
+ * from the Follow to the Master.
+ */
+ do
+ {
+ if( bFirst )
+ bFirst = false;
+ else
+ {
+ if ( bMaxHyph )
+ {
+ if ( rLine.GetCurr()->IsEndHyph() )
+ rLine.CntEndHyph()++;
+ else
+ rLine.CntEndHyph() = 0;
+ if ( rLine.GetCurr()->IsMidHyph() )
+ rLine.CntMidHyph()++;
+ else
+ rLine.CntMidHyph() = 0;
+ }
+ if( !rLine.Next() )
+ {
+ if( !bFormat )
+ {
+ SwLinePortion* pRest =
+ rLine.MakeRestPortion( rLine.GetCurr(), rLine.GetEnd() );
+ if( pRest )
+ rInf.SetRest( pRest );
+ }
+ rLine.Insert( new SwLineLayout() );
+ rLine.Next();
+ bFormat = true;
+ }
+ }
+ if ( !bFormat && bMaxHyph &&
+ (bWatchEndHyph || bJumpEndHyph || bWatchMidHyph || bJumpMidHyph) )
+ {
+ if ( rLine.GetCurr()->IsEndHyph() )
+ {
+ if ( bWatchEndHyph )
+ bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() );
+ }
+ else
+ {
+ bFormat = bJumpEndHyph;
+ bWatchEndHyph = false;
+ bJumpEndHyph = false;
+ }
+ if ( rLine.GetCurr()->IsMidHyph() )
+ {
+ if ( bWatchMidHyph && !bFormat )
+ bFormat = ( rLine.CntEndHyph() == rInf.MaxHyph() );
+ }
+ else
+ {
+ bFormat |= bJumpMidHyph;
+ bWatchMidHyph = false;
+ bJumpMidHyph = false;
+ }
+ }
+ if( bFormat )
+ {
+ const bool bOldEndHyph = rLine.GetCurr()->IsEndHyph();
+ const bool bOldMidHyph = rLine.GetCurr()->IsMidHyph();
+ bFormat = FormatLine( rLine, bPrev );
+ // There can only be one bPrev ... (???)
+ bPrev = false;
+ if ( bMaxHyph )
+ {
+ if ( rLine.GetCurr()->IsEndHyph() != bOldEndHyph )
+ {
+ bWatchEndHyph = !bOldEndHyph;
+ bJumpEndHyph = bOldEndHyph;
+ }
+ if ( rLine.GetCurr()->IsMidHyph() != bOldMidHyph )
+ {
+ bWatchMidHyph = !bOldMidHyph;
+ bJumpMidHyph = bOldMidHyph;
+ }
+ }
+ }
+
+ if( !rInf.IsNewLine() )
+ {
+ if( !bFormat )
+ bFormat = nullptr != rInf.GetRest();
+ if( rInf.IsStop() || rInf.GetIdx() >= nStrLen )
+ break;
+ if( !bFormat && ( !bMaxHyph || ( !bWatchEndHyph &&
+ !bJumpEndHyph && !bWatchMidHyph && !bJumpMidHyph ) ) )
+ {
+ if( GetFollow() )
+ {
+ while( rLine.Next() )
+ ; //Nothing
+ pFreeze.reset(new SwRepaint( rRepaint )); // to minimize painting
+ }
+ else
+ break;
+ }
+ }
+ bBreak = aFrameBreak.IsBreakNowWidAndOrp(rLine);
+ }while( !bBreak );
+
+ if( pFreeze )
+ {
+ rRepaint = *pFreeze;
+ pFreeze.reset();
+ }
+
+ if( !rLine.IsStop() )
+ {
+ // If we're finished formatting the text and we still
+ // have other line objects left, these are superfluous
+ // now because the text has gotten shorter.
+ bool bTruncLines = false;
+ if( rLine.GetStart() + rLine.GetLength() >= nStrLen &&
+ rLine.GetCurr()->GetNext() )
+ {
+ bTruncLines = true;
+ }
+ else if (GetMergedPara() && rLine.GetCurr()->GetNext())
+ {
+ // We can also have superfluous lines with redlining in case the current line is shorter
+ // than the text length, but the total length of lines is still more than expected.
+ // Truncate in this case as well.
+ TextFrameIndex nLen(0);
+ for (const SwLineLayout* pLine = pPara; pLine; pLine = pLine->GetNext())
+ {
+ nLen += pLine->GetLen();
+ }
+ bTruncLines = nLen > nStrLen;
+ }
+
+ if (bTruncLines)
+ {
+ rLine.TruncLines();
+ rLine.SetTruncLines( true );
+ }
+ }
+
+ if( rInf.IsTest() )
+ return;
+
+ // FormatAdjust does not pay off at OnceMore
+ if( bAdjust || !rLine.GetDropFormat() || !rLine.CalcOnceMore() )
+ {
+ FormatAdjust( rLine, aFrameBreak, nStrLen, rInf.IsStop() );
+ }
+ if( rRepaint.HasArea() )
+ SetRepaint();
+ rLine.SetTruncLines( false );
+ if( nOldBottom ) // We check whether paragraphs that need scrolling can
+ // be shrunk, so that they don't need scrolling anymore
+ {
+ rLine.Bottom();
+ SwTwips nNewBottom = rLine.Y();
+ if( nNewBottom < nOldBottom )
+ SetOffset_(TextFrameIndex(0));
+ }
+}
+
+void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf )
+{
+ OSL_ENSURE( ! IsVertical() || IsSwapped(),
+ "A frame is not swapped in SwTextFrame::FormatOnceMore" );
+
+ SwParaPortion *pPara = rLine.GetInfo().GetParaPortion();
+ if( !pPara )
+ return;
+
+ // If necessary the pPara
+ sal_uInt16 nOld = static_cast<const SwTextMargin&>(rLine).GetDropHeight();
+ bool bShrink = false;
+ bool bGrow = false;
+ bool bGoOn = rLine.IsOnceMore();
+ sal_uInt8 nGo = 0;
+ while( bGoOn )
+ {
+ ++nGo;
+ rInf.Init();
+ rLine.Top();
+ if( !rLine.GetDropFormat() )
+ rLine.SetOnceMore( false );
+ SwCharRange aRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength()));
+ pPara->GetReformat() = aRange;
+ Format_( rLine, rInf );
+
+ bGoOn = rLine.IsOnceMore();
+ if( bGoOn )
+ {
+ const sal_uInt16 nNew = static_cast<const SwTextMargin&>(rLine).GetDropHeight();
+ if( nOld == nNew )
+ bGoOn = false;
+ else
+ {
+ if( nOld > nNew )
+ bShrink = true;
+ else
+ bGrow = true;
+
+ if( bShrink == bGrow || 5 < nGo )
+ bGoOn = false;
+
+ nOld = nNew;
+ }
+
+ // If something went wrong, we need to reformat again
+ if( !bGoOn )
+ {
+ rInf.CtorInitTextFormatInfo( getRootFrame()->GetCurrShell()->GetOut(), this );
+ rLine.CtorInitTextFormatter( this, &rInf );
+ rLine.SetDropLines( 1 );
+ rLine.CalcDropHeight( 1 );
+ SwCharRange aTmpRange(TextFrameIndex(0), TextFrameIndex(rInf.GetText().getLength()));
+ pPara->GetReformat() = aTmpRange;
+ Format_( rLine, rInf, true );
+ // We paint everything ...
+ SetCompletePaint();
+ }
+ }
+ }
+}
+
+void SwTextFrame::FormatImpl(vcl::RenderContext* pRenderContext, SwParaPortion *pPara,
+ std::vector<SwAnchoredObject *> & rIntersectingObjs)
+{
+ const bool bIsEmpty = GetText().isEmpty();
+
+ if ( bIsEmpty )
+ {
+ // Empty lines do not get tortured for very long:
+ // pPara is cleared, which is the same as:
+ // *pPara = SwParaPortion;
+ const bool bMustFit = pPara->IsPrepMustFit();
+ pPara->Truncate();
+ pPara->FormatReset();
+
+ // delete pSpaceAdd and pKanaComp
+ pPara->FinishSpaceAdd();
+ pPara->FinishKanaComp();
+ pPara->ResetFlags();
+ pPara->SetPrepMustFit( bMustFit );
+ }
+
+ OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Format_" );
+
+ if ( IsVertical() )
+ SwapWidthAndHeight();
+
+ SwTextFormatInfo aInf( pRenderContext, this );
+ SwTextFormatter aLine( this, &aInf );
+
+ HideAndShowObjects();
+
+ Format_( aLine, aInf );
+
+ if( aLine.IsOnceMore() )
+ FormatOnceMore( aLine, aInf );
+
+ if (aInf.GetTextFly().IsOn())
+ {
+ SwRect const aRect(aInf.GetTextFly().GetFrameArea());
+ for (SwAnchoredObject *const pObj : aInf.GetTextFly().GetAnchoredObjList())
+ {
+ if (!aInf.GetTextFly().AnchoredObjToRect(pObj, aRect).IsEmpty())
+ {
+ rIntersectingObjs.push_back(pObj);
+ }
+ }
+ }
+
+ if ( IsVertical() )
+ SwapWidthAndHeight();
+
+ OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Format_" );
+
+ if( 1 >= aLine.GetDropLines() )
+ return;
+
+ if( SvxAdjust::Left != aLine.GetAdjust() &&
+ SvxAdjust::Block != aLine.GetAdjust() )
+ {
+ aLine.CalcDropAdjust();
+ aLine.SetPaintDrop( true );
+ }
+
+ if( aLine.IsPaintDrop() )
+ {
+ aLine.CalcDropRepaint();
+ aLine.SetPaintDrop( false );
+ }
+}
+
+// We calculate the text frame's size and send a notification.
+// Shrink() or Grow() to adjust the frame's size to the changed required space.
+void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttrs * )
+{
+ SwRectFnSet aRectFnSet(this);
+
+ CalcAdditionalFirstLineOffset();
+
+ // The range autopilot or the BASIC interface pass us TextFrames with
+ // a width <= 0 from time to time
+ if( aRectFnSet.GetWidth(getFramePrintArea()) <= 0 )
+ {
+ // If MustFit is set, we shrink to the Upper's bottom edge if needed.
+ SwTextLineAccess aAccess( this );
+
+ if( aAccess.GetPara()->IsPrepMustFit() )
+ {
+ const SwTwips nLimit = aRectFnSet.GetPrtBottom(*GetUpper());
+ const SwTwips nDiff = - aRectFnSet.BottomDist( getFrameArea(), nLimit );
+ if( nDiff > 0 )
+ Shrink( nDiff );
+ }
+
+ tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea());
+ const tools::Long nTop = aRectFnSet.GetTopMargin(*this);
+
+ if( nTop > nFrameHeight )
+ {
+ aRectFnSet.SetYMargins( *this, nFrameHeight, 0 );
+ }
+ else if( aRectFnSet.GetHeight(getFramePrintArea()) < 0 )
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aRectFnSet.SetHeight( aPrt, 0 );
+ }
+
+ return;
+ }
+
+ TextFrameIndex nStrLen(GetText().getLength());
+
+ if (HasNonLastSplitFlyDrawObj())
+ {
+ // Non-last part of split fly anchor: consider this empty.
+ nStrLen = TextFrameIndex(0);
+ }
+
+ if ( nStrLen || !FormatEmpty() )
+ {
+
+ SetEmpty( false );
+ // In order to not get confused by nested Formats
+ FormatLevel aLevel;
+ if( 12 == FormatLevel::GetLevel() )
+ return;
+
+ // We could be possibly not allowed to alter the format information
+ if( IsLocked() )
+ return;
+
+ // Attention: Format() could be triggered by GetFormatted()
+ if( IsHiddenNow() )
+ {
+ tools::Long nPrtHeight = aRectFnSet.GetHeight(getFramePrintArea());
+ if( nPrtHeight )
+ {
+ HideHidden();
+ Shrink( nPrtHeight );
+ }
+ else
+ {
+ // Assure that objects anchored
+ // at paragraph resp. at/as character inside paragraph
+ // are hidden.
+ HideAndShowObjects();
+ }
+ ChgThisLines();
+ return;
+ }
+
+ // We do not want to be interrupted during formatting
+ TextFrameLockGuard aLock(this);
+
+ // this is to ensure that the similar code in SwTextFrame::Format_
+ // finds the master formatted in case it's needed
+ if (IsFollow() && IsFieldFollow())
+ {
+ SwTextFrame *pMaster = FindMaster();
+ assert(pMaster);
+ if (!pMaster->HasPara())
+ {
+ pMaster->GetFormatted();
+ }
+ if (!pMaster->HasPara())
+ { // master could be locked because it's being formatted upstack
+ SAL_WARN("sw", "SwTextFrame::Format: failed to format master!");
+ }
+ else
+ {
+ SwTextSizeInfo aInf( pMaster );
+ SwTextIter aMasterLine( pMaster, &aInf );
+ aMasterLine.Bottom();
+ SetOffset(aMasterLine.GetEnd());
+ }
+ }
+
+ SwTextLineAccess aAccess( this );
+ const bool bNew = !aAccess.IsAvailable();
+ const bool bSetOffset =
+ (GetOffset() && GetOffset() > TextFrameIndex(GetText().getLength()));
+
+ if( CalcPreps() )
+ ; // nothing
+ // We return if already formatted, but if the TextFrame was just created
+ // and does not have any format information
+ else if( !bNew && !aAccess.GetPara()->GetReformat().Len() )
+ {
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ {
+ aAccess.GetPara()->SetPrepAdjust();
+ aAccess.GetPara()->SetPrep();
+ CalcPreps();
+ }
+ SetWidow( false );
+ }
+ else if( bSetOffset && IsFollow() )
+ {
+ SwTextFrame *pMaster = FindMaster();
+ OSL_ENSURE( pMaster, "SwTextFrame::Format: homeless follow" );
+ if( pMaster )
+ pMaster->Prepare( PrepareHint::FollowFollows );
+ SwTwips nMaxY = aRectFnSet.GetPrtBottom(*GetUpper());
+
+ if( aRectFnSet.OverStep( getFrameArea(), nMaxY ) )
+ {
+ aRectFnSet.SetLimit( *this, nMaxY );
+ }
+ else if( aRectFnSet.BottomDist( getFrameArea(), nMaxY ) < 0 )
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aRectFnSet.AddBottom( aFrm, -aRectFnSet.GetHeight(aFrm) );
+ }
+ }
+ else
+ {
+ // bSetOffset here means that we have the "red arrow situation"
+ if ( bSetOffset )
+ SetOffset_(TextFrameIndex(0));
+
+ const bool bOrphan = IsWidow();
+ const SwFootnoteBossFrame* pFootnoteBoss = HasFootnote() ? FindFootnoteBossFrame() : nullptr;
+ SwTwips nFootnoteHeight = 0;
+ if( pFootnoteBoss )
+ {
+ const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont();
+ nFootnoteHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0;
+ }
+ do
+ {
+ ::std::vector<SwAnchoredObject *> intersectingObjs;
+ ::std::vector<SwFrame const*> nexts;
+ for (SwFrame const* pNext = GetNext(); pNext; pNext = pNext->GetNext())
+ {
+ nexts.push_back(pNext);
+ }
+ FormatImpl(pRenderContext, aAccess.GetPara(), intersectingObjs);
+ if( pFootnoteBoss && nFootnoteHeight )
+ {
+ const SwFootnoteContFrame* pCont = pFootnoteBoss->FindFootnoteCont();
+ SwTwips nNewHeight = pCont ? aRectFnSet.GetHeight(pCont->getFrameArea()) : 0;
+ // If we lost some footnotes, we may have more space
+ // for our main text, so we have to format again ...
+ if( nNewHeight < nFootnoteHeight )
+ {
+ nFootnoteHeight = nNewHeight;
+ continue;
+ }
+ }
+ if (!intersectingObjs.empty())
+ {
+ // assumption is that FormatImpl() only moves frames
+ // in the next-chain to next page
+ SwPageFrame *const pPage(FindPageFrame());
+ SwTextFrame * pLastMovedAnchor(nullptr);
+ auto lastIter(nexts.end());
+ for (SwAnchoredObject *const pObj : intersectingObjs)
+ {
+ SwFrame *const pAnchor(pObj->AnchorFrame());
+ SwPageFrame *const pAnchorPage(pAnchor->FindPageFrame());
+ if (pAnchorPage != pPage)
+ {
+ auto const iter(::std::find(nexts.begin(), nexts.end(), pAnchor));
+ if (iter != nexts.end())
+ {
+ assert(pAnchor->IsTextFrame());
+ // (can't check SwOszControl::IsInProgress()?)
+ // called in loop in FormatAnchorFrameAndItsPrevs()
+ if (static_cast<SwTextFrame const*>(pAnchor)->IsJoinLocked()
+ // called in loop in SwFrame::PrepareMake()
+ || pAnchor->IsDeleteForbidden())
+ {
+ // when called via FormatAnchorFrameAndItsPrevs():
+ // don't do anything, caller will handle it
+ pLastMovedAnchor = nullptr;
+ break;
+ }
+ assert(pPage->GetPhyPageNum() < pAnchorPage->GetPhyPageNum()); // how could it move backward?
+
+ if (!pLastMovedAnchor || iter < lastIter)
+ {
+ pLastMovedAnchor = static_cast<SwTextFrame *>(pAnchor);
+ lastIter = iter;
+ }
+ }
+ }
+ }
+ SwPageFrame const*const pPrevPage(static_cast<SwPageFrame const*>(pPage->GetPrev()));
+ if (pLastMovedAnchor)
+ {
+ for (SwAnchoredObject *const pObj : intersectingObjs)
+ {
+ if (pObj->AnchorFrame() == pLastMovedAnchor)
+ {
+ SwPageFrame *const pAnchorPage(pLastMovedAnchor->FindPageFrame());
+ SAL_INFO("sw.layout", "SwTextFrame::Format: move anchored " << pObj << " from " << pPage->GetPhyPageNum() << " to " << pAnchorPage->GetPhyPageNum());
+ pObj->RegisterAtPage(*pAnchorPage);
+ // tdf#143239 if the position remains valid, it may not be
+ // positioned again so would remain on the wrong page!
+ pObj->InvalidateObjPos();
+ ::Notify_Background(pObj->GetDrawObj(), pPage,
+ pObj->GetObjRect(), PrepareHint::FlyFrameLeave, false);
+ pObj->SetForceNotifyNewBackground(true);
+ }
+ }
+ if (GetFollow() // this frame was split
+ && (!pPrevPage // prev page is still valid
+ || (!pPrevPage->IsInvalid()
+ && (!pPrevPage->GetSortedObjs() || !pPrevPage->IsInvalidFly()))))
+ { // this seems a bit risky...
+ SwLayouter::InsertMovedFwdFrame(GetTextNodeFirst()->GetDoc(),
+ *pLastMovedAnchor, FindPageFrame()->GetPhyPageNum() + 1);
+ }
+ continue; // try again without the fly
+ }
+ }
+ break;
+ } while ( pFootnoteBoss );
+ if( bOrphan )
+ {
+ ValidateFrame();
+ SetWidow( false );
+ }
+ }
+ if( IsEmptyMaster() )
+ {
+ SwFrame* pPre = GetPrev();
+ if( pPre &&
+ // i#10826 It's the first, it cannot keep!
+ pPre->GetIndPrev() &&
+ pPre->GetAttrSet()->GetKeep().GetValue() )
+ {
+ pPre->InvalidatePos();
+ }
+
+ if (IsEmptyMasterWithSplitFly())
+ {
+ // A fly is anchored to us, reduce size, so we definitely still fit the current
+ // page.
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aRectFnSet.SetHeight(aFrm, 0);
+
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aRectFnSet.SetTop(aPrt, 0);
+ aRectFnSet.SetHeight(aPrt, 0);
+ }
+ }
+ }
+
+ ChgThisLines();
+
+ // the PrepMustFit should not survive a Format operation
+ SwParaPortion *pPara = GetPara();
+ if ( pPara )
+ pPara->SetPrepMustFit( false );
+
+ CalcBaseOfstForFly();
+ CalcHeightOfLastLine(); // i#11860 - Adjust spacing implementation for
+ // object positioning - Compatibility to MS Word
+ // tdf#117982 -- Fix cell spacing hides content
+ // Check if the cell's content has greater size than the row height
+ if (IsInTab() && GetUpper() && ((GetUpper()->getFramePrintArea().Height() < getFramePrintArea().Height())
+ || (getFramePrintArea().Height() <= 0)))
+ {
+ SAL_INFO("sw.core", "Warn: Cell content has greater size than cell height!");
+ //get font size...
+ SwTwips aTmpHeight = getFrameArea().Height();
+ //...and push it into the text frame
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ //if only bottom margin what we have:
+ if (GetTopMargin() == 0)
+ //set the frame to its original location
+ aPrt.SetTopAndHeight(0, aTmpHeight);
+ }
+}
+
+// bForceQuickFormat is set if GetFormatted() has been called during the
+// painting process. Actually I cannot imagine a situation which requires
+// a full formatting of the paragraph during painting, on the other hand
+// a full formatting can cause the invalidation of other layout frames,
+// e.g., if there are footnotes in this paragraph, and invalid layout
+// frames will not calculated during the painting. So I actually want to
+// avoid a formatting during painting, but since I'm a coward, I'll only
+// force the quick formatting in the situation of issue i29062.
+bool SwTextFrame::FormatQuick( bool bForceQuickFormat )
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::FormatQuick with swapped frame" );
+
+ if( IsEmpty() && FormatEmpty() )
+ return true;
+
+ // We're very picky:
+ if( HasPara() || IsWidow() || IsLocked()
+ || !isFrameAreaSizeValid() ||
+ ( ( IsVertical() ? getFramePrintArea().Width() : getFramePrintArea().Height() ) && IsHiddenNow() ) )
+ return false;
+
+ SwTextLineAccess aAccess( this );
+ SwParaPortion *pPara = aAccess.GetPara();
+ if( !pPara )
+ return false;
+
+ SwFrameSwapper aSwapper( this, true );
+
+ TextFrameLockGuard aLock(this);
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true );
+ if( 0 != aInf.MaxHyph() ) // Respect MaxHyphen!
+ return false;
+
+ SwTextFormatter aLine( this, &aInf );
+
+ // DropCaps are too complicated ...
+ if( aLine.GetDropFormat() )
+ return false;
+
+ TextFrameIndex nStart = GetOffset();
+ const TextFrameIndex nEnd = GetFollow()
+ ? GetFollow()->GetOffset()
+ : TextFrameIndex(aInf.GetText().getLength());
+
+ int nLoopProtection = 0;
+ do
+ {
+ TextFrameIndex nNewStart = aLine.FormatLine(nStart);
+ if (nNewStart == nStart)
+ ++nLoopProtection;
+ else
+ nLoopProtection = 0;
+ nStart = nNewStart;
+ const bool bWillEndlessInsert = nLoopProtection > 250;
+ SAL_WARN_IF(bWillEndlessInsert, "sw", "loop detection triggered");
+ if ((!bWillEndlessInsert) // Check for special case: line is invisible,
+ // like in too thin table cell: tdf#66141
+ && (aInf.IsNewLine() || (!aInf.IsStop() && nStart < nEnd)))
+ aLine.Insert( new SwLineLayout() );
+ } while( aLine.Next() );
+
+ // Last exit: the heights need to match
+ Point aTopLeft( getFrameArea().Pos() );
+ aTopLeft += getFramePrintArea().Pos();
+ const SwTwips nNewHeight = aLine.Y() + aLine.GetLineHeight();
+ const SwTwips nOldHeight = aTopLeft.Y() + getFramePrintArea().Height();
+
+ if( !bForceQuickFormat && nNewHeight != nOldHeight && !IsUndersized() )
+ {
+ // Attention: This situation can occur due to FormatLevel==12. Don't panic!
+ TextFrameIndex const nStrt = GetOffset();
+ InvalidateRange_( SwCharRange( nStrt, nEnd - nStrt) );
+ return false;
+ }
+
+ if (m_pFollow && nStart != static_cast<SwTextFrame*>(m_pFollow)->GetOffset())
+ return false; // can be caused by e.g. Orphans
+
+ // We made it!
+
+ // Set repaint
+ pPara->GetRepaint().Pos( aTopLeft );
+ pPara->GetRepaint().SSize( getFramePrintArea().SSize() );
+
+ // Delete reformat
+ pPara->GetReformat() = SwCharRange();
+ pPara->SetDelta(0);
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/frminf.cxx b/sw/source/core/text/frminf.cxx
new file mode 100644
index 0000000000..a123691db7
--- /dev/null
+++ b/sw/source/core/text/frminf.cxx
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <frminf.hxx>
+#include "itrtxt.hxx"
+
+TextFrameIndex SwTextMargin::GetTextStart() const
+{
+ const OUString &rText = GetInfo().GetText();
+ const TextFrameIndex nEnd = m_nStart + m_pCurr->GetLen();
+
+ for (TextFrameIndex i = m_nStart; i < nEnd; ++i)
+ {
+ const sal_Unicode aChar = rText[sal_Int32(i)];
+ if( CH_TAB != aChar && ' ' != aChar )
+ return i;
+ }
+ return nEnd;
+}
+
+TextFrameIndex SwTextMargin::GetTextEnd() const
+{
+ const OUString &rText = GetInfo().GetText();
+ const TextFrameIndex nEnd = m_nStart + m_pCurr->GetLen();
+ for (TextFrameIndex i = nEnd - TextFrameIndex(1); i >= m_nStart; --i)
+ {
+ const sal_Unicode aChar = rText[sal_Int32(i)];
+ if( CH_TAB != aChar && CH_BREAK != aChar && ' ' != aChar )
+ return i + TextFrameIndex(1);
+ }
+ return m_nStart;
+}
+
+// Does the paragraph fit into one line?
+bool SwTextFrameInfo::IsOneLine() const
+{
+ const SwLineLayout *pLay = m_pFrame->GetPara();
+ if( !pLay )
+ return false;
+
+ // For follows false of course
+ if( m_pFrame->GetFollow() )
+ return false;
+
+ pLay = pLay->GetNext();
+ while( pLay )
+ {
+ if( pLay->GetLen() )
+ return false;
+ pLay = pLay->GetNext();
+ }
+ return true;
+}
+
+// Is the line filled for X percent?
+bool SwTextFrameInfo::IsFilled( const sal_uInt8 nPercent ) const
+{
+ const SwLineLayout *pLay = m_pFrame->GetPara();
+ if( !pLay )
+ return false;
+
+ tools::Long nWidth = m_pFrame->getFramePrintArea().Width();
+ nWidth *= nPercent;
+ nWidth /= 100;
+ return nWidth <= pLay->Width();
+}
+
+// Where does the text start (without whitespace)? (document global)
+SwTwips SwTextFrameInfo::GetLineStart( const SwTextCursor &rLine )
+{
+ const TextFrameIndex nTextStart = rLine.GetTextStart();
+ if( rLine.GetStart() == nTextStart )
+ return rLine.GetLineStart();
+
+ SwRect aRect;
+ const_cast<SwTextCursor&>(rLine).GetCharRect( &aRect, nTextStart );
+ return aRect.Left();
+}
+
+// Where does the text start (without whitespace)? (relative in the Frame)
+SwTwips SwTextFrameInfo::GetLineStart() const
+{
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+ return GetLineStart( aLine ) - m_pFrame->getFrameArea().Left() - m_pFrame->getFramePrintArea().Left();
+}
+
+// Calculates the character's position and returns the middle position
+SwTwips SwTextFrameInfo::GetCharPos(TextFrameIndex const nChar, bool bCenter) const
+{
+ SwRectFnSet aRectFnSet(m_pFrame);
+ SwFrameSwapper aSwapper( m_pFrame, true );
+
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+
+ SwTwips nStt, nNext;
+ SwRect aRect;
+ aLine.GetCharRect( &aRect, nChar );
+ if ( aRectFnSet.IsVert() )
+ m_pFrame->SwitchHorizontalToVertical( aRect );
+
+ nStt = aRectFnSet.GetLeft(aRect);
+
+ if( !bCenter )
+ return nStt - aRectFnSet.GetLeft(m_pFrame->getFrameArea());
+
+ aLine.GetCharRect( &aRect, nChar + TextFrameIndex(1) );
+ if ( aRectFnSet.IsVert() )
+ m_pFrame->SwitchHorizontalToVertical( aRect );
+
+ nNext = aRectFnSet.GetLeft(aRect);
+
+ return (( nNext + nStt ) / 2 ) - aRectFnSet.GetLeft(m_pFrame->getFrameArea());
+}
+
+static void
+AddRange(std::vector<std::pair<TextFrameIndex, TextFrameIndex>> & rRanges,
+ TextFrameIndex const nPos, TextFrameIndex const nLen)
+{
+ assert(rRanges.empty() || rRanges.back().second <= nPos);
+ if( nLen )
+ {
+ if (!rRanges.empty() && nPos == rRanges.back().second)
+ {
+ rRanges.back().second += nLen;
+ }
+ else
+ {
+ rRanges.emplace_back(nPos, nPos + nLen);
+ }
+ }
+}
+
+// Accumulates the whitespace at line start and end in the vector
+void SwTextFrameInfo::GetSpaces(
+ std::vector<std::pair<TextFrameIndex, TextFrameIndex>> & rRanges,
+ bool const bWithLineBreak) const
+{
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextMargin aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+ bool bFirstLine = true;
+ do {
+
+ if( aLine.GetCurr()->GetLen() )
+ {
+ TextFrameIndex nPos = aLine.GetTextStart();
+ // Do NOT include the blanks/tabs from the first line
+ // in the selection
+ if( !bFirstLine && nPos > aLine.GetStart() )
+ AddRange( rRanges, aLine.GetStart(), nPos - aLine.GetStart() );
+
+ // Do NOT include the blanks/tabs from the last line
+ // in the selection
+ if( aLine.GetNext() )
+ {
+ nPos = aLine.GetTextEnd();
+
+ if( nPos < aLine.GetEnd() )
+ {
+ TextFrameIndex const nOff( !bWithLineBreak && CH_BREAK ==
+ aLine.GetInfo().GetChar(aLine.GetEnd() - TextFrameIndex(1))
+ ? 1 : 0 );
+ AddRange( rRanges, nPos, aLine.GetEnd() - nPos - nOff );
+ }
+ }
+ }
+ bFirstLine = false;
+ }
+ while( aLine.Next() );
+}
+
+// Is there a bullet/symbol etc. at the text position?
+// Fonts: CharSet, SYMBOL and DONTKNOW
+bool SwTextFrameInfo::IsBullet(TextFrameIndex const nTextStart) const
+{
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextMargin aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+ aInf.SetIdx( nTextStart );
+ return aLine.IsSymbol( nTextStart );
+}
+
+// Get first line indent
+// The precondition for a positive or negative first line indent:
+// All lines (except for the first one) have the same left margin.
+// We do not want to be so picky and work with a tolerance of TOLERANCE twips.
+SwTwips SwTextFrameInfo::GetFirstIndent() const
+{
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+ const SwTwips nFirst = GetLineStart( aLine );
+ const SwTwips TOLERANCE = 20;
+
+ if( !aLine.Next() )
+ return 0;
+
+ SwTwips nLeft = GetLineStart( aLine );
+ while( aLine.Next() )
+ {
+ if( aLine.GetCurr()->GetLen() )
+ {
+ const SwTwips nCurrLeft = GetLineStart( aLine );
+ if( nLeft + TOLERANCE < nCurrLeft ||
+ nLeft - TOLERANCE > nCurrLeft )
+ return 0;
+ }
+ }
+
+ // At first we only return +1, -1 and 0
+ if( nLeft == nFirst )
+ return 0;
+
+ if( nLeft > nFirst )
+ return -1;
+
+ return 1;
+}
+
+sal_Int32 SwTextFrameInfo::GetBigIndent(TextFrameIndex& rFndPos,
+ const SwTextFrame *pNextFrame ) const
+{
+ SwTextSizeInfo aInf( const_cast<SwTextFrame*>(m_pFrame) );
+ SwTextCursor aLine( const_cast<SwTextFrame*>(m_pFrame), &aInf );
+ SwTwips nNextIndent = 0;
+
+ if( pNextFrame )
+ {
+ // I'm a single line
+ SwTextSizeInfo aNxtInf( const_cast<SwTextFrame*>(pNextFrame) );
+ SwTextCursor aNxtLine( const_cast<SwTextFrame*>(pNextFrame), &aNxtInf );
+ nNextIndent = GetLineStart( aNxtLine );
+ }
+ else
+ {
+ // I'm multi-line
+ if( aLine.Next() )
+ {
+ nNextIndent = GetLineStart( aLine );
+ aLine.Prev();
+ }
+ }
+
+ if( nNextIndent <= GetLineStart( aLine ) )
+ return 0;
+
+ const Point aPoint( nNextIndent, aLine.Y() );
+ rFndPos = aLine.GetModelPositionForViewPoint( nullptr, aPoint, false );
+ if (TextFrameIndex(1) >= rFndPos)
+ return 0;
+
+ // Is on front of a non-space
+ const OUString& rText = aInf.GetText();
+ sal_Unicode aChar = rText[sal_Int32(rFndPos)];
+ if( CH_TAB == aChar || CH_BREAK == aChar || ' ' == aChar ||
+ (( CH_TXTATR_BREAKWORD == aChar || CH_TXTATR_INWORD == aChar ) &&
+ aInf.HasHint( rFndPos ) ) )
+ return 0;
+
+ // and after a space
+ aChar = rText[sal_Int32(rFndPos) - 1];
+ if( CH_TAB != aChar && CH_BREAK != aChar &&
+ ( ( CH_TXTATR_BREAKWORD != aChar && CH_TXTATR_INWORD != aChar ) ||
+ !aInf.HasHint(rFndPos - TextFrameIndex(1))) &&
+ // More than two Blanks!
+ (' ' != aChar || ' ' != rText[sal_Int32(rFndPos) - 2]))
+ return 0;
+
+ SwRect aRect;
+ aLine.GetCharRect( &aRect, rFndPos );
+ return static_cast<sal_Int32>(aRect.Left() - m_pFrame->getFrameArea().Left() - m_pFrame->getFramePrintArea().Left());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx
new file mode 100644
index 0000000000..57458a0218
--- /dev/null
+++ b/sw/source/core/text/frmpaint.cxx
@@ -0,0 +1,810 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <com/sun/star/text/HoriOrientation.hpp>
+#include <editeng/pgrditem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <tgrditem.hxx>
+#include <paratr.hxx>
+
+#include <fmtline.hxx>
+#include <lineinfo.hxx>
+#include <charfmt.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <viewsh.hxx>
+#include <viewopt.hxx>
+#include <frmatr.hxx>
+#include <txtfrm.hxx>
+#include "itrpaint.hxx"
+#include "txtpaint.hxx"
+#include "txtcache.hxx"
+#include <flyfrm.hxx>
+#include "redlnitr.hxx"
+#include <redline.hxx>
+#include <swmodule.hxx>
+#include <tabfrm.hxx>
+#include <numrule.hxx>
+#include <wrong.hxx>
+
+#include <EnhancedPDFExportHelper.hxx>
+
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+
+#define REDLINE_DISTANCE 567/4
+#define REDLINE_MINDIST 567/10
+
+using namespace ::com::sun::star;
+
+static bool bInitFont = true;
+
+namespace {
+
+class SwExtraPainter
+{
+ SwSaveClip m_aClip;
+ SwRect m_aRect;
+ const SwTextFrame* m_pTextFrame;
+ SwViewShell *m_pSh;
+ std::unique_ptr<SwFont> m_pFnt;
+ const SwLineNumberInfo &m_rLineInf;
+ SwTwips m_nX;
+ SwTwips m_nRedX;
+ sal_Int32 m_nLineNr;
+ sal_uInt16 m_nDivider;
+ bool m_bGoLeft;
+ bool IsClipChg() const { return m_aClip.IsChg(); }
+
+ SwExtraPainter(const SwExtraPainter&) = delete;
+ SwExtraPainter& operator=(const SwExtraPainter&) = delete;
+
+public:
+ SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh,
+ const SwLineNumberInfo &rLnInf, const SwRect &rRct,
+ sal_Int16 eHor, bool bLnNm );
+ SwFont* GetFont() const { return m_pFnt.get(); }
+ void IncLineNr() { ++m_nLineNr; }
+ bool HasNumber() const {
+ assert( m_rLineInf.GetCountBy() != 0 );
+ if( m_rLineInf.GetCountBy() == 0 )
+ return false;
+ return !( m_nLineNr % static_cast<sal_Int32>(m_rLineInf.GetCountBy()) );
+ }
+ bool HasDivider() const {
+ assert( m_rLineInf.GetDividerCountBy() != 0 );
+ if( !m_nDivider || m_rLineInf.GetDividerCountBy() == 0 )
+ return false;
+ return !(m_nLineNr % m_rLineInf.GetDividerCountBy());
+ }
+
+ void PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, bool bRed, const OUString* pRedlineText = nullptr );
+ void PaintRedline( SwTwips nY, tools::Long nMax );
+};
+
+}
+
+SwExtraPainter::SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh,
+ const SwLineNumberInfo &rLnInf, const SwRect &rRct,
+ sal_Int16 eHor, bool bLineNum )
+ : m_aClip( pVwSh->GetWin() || pFrame->IsUndersized() ? pVwSh->GetOut() : nullptr )
+ , m_aRect( rRct )
+ , m_pTextFrame( pFrame )
+ , m_pSh( pVwSh )
+ , m_rLineInf( rLnInf )
+ , m_nX(0)
+ , m_nRedX(0)
+ , m_nLineNr( 1 )
+ , m_nDivider(0)
+ , m_bGoLeft(false)
+{
+ if( pFrame->IsUndersized() )
+ {
+ SwTwips nBottom = pFrame->getFrameArea().Bottom();
+ if( m_aRect.Bottom() > nBottom )
+ m_aRect.Bottom( nBottom );
+ }
+ std::optional<bool> oIsRightPage;
+ {
+ /* Initializes the Members necessary for line numbering:
+
+ nDivider, how often do we want a substring; 0 == never
+ nX, line number's x position
+ pFnt, line number's font
+ nLineNr, the first line number
+ bLineNum is set back to false if the numbering is completely
+ outside of the paint rect
+ */
+ m_nDivider = !m_rLineInf.GetDivider().isEmpty() ? m_rLineInf.GetDividerCountBy() : 0;
+ m_nX = pFrame->getFrameArea().Left();
+ SwCharFormat* pFormat = m_rLineInf.GetCharFormat( const_cast<IDocumentStylePoolAccess&>(pFrame->GetDoc().getIDocumentStylePoolAccess()) );
+ OSL_ENSURE( pFormat, "PaintExtraData without CharFormat" );
+ m_pFnt.reset( new SwFont(&pFormat->GetAttrSet(), &pFrame->GetDoc().getIDocumentSettingAccess()) );
+ m_pFnt->Invalidate();
+ m_pFnt->ChgPhysFnt( m_pSh, *m_pSh->GetOut() );
+ m_pFnt->SetVertical( 0_deg10, pFrame->IsVertical() );
+ }
+
+ if( bLineNum )
+ {
+ m_nLineNr += pFrame->GetAllLines() - pFrame->GetThisLines();
+ LineNumberPosition ePos = m_rLineInf.GetPos();
+ if( ePos != LINENUMBER_POS_LEFT && ePos != LINENUMBER_POS_RIGHT )
+ {
+ if( pFrame->FindPageFrame()->OnRightPage() )
+ {
+ oIsRightPage = true;
+ ePos = ePos == LINENUMBER_POS_INSIDE ?
+ LINENUMBER_POS_LEFT : LINENUMBER_POS_RIGHT;
+ }
+ else
+ {
+ oIsRightPage = false;
+ ePos = ePos == LINENUMBER_POS_OUTSIDE ?
+ LINENUMBER_POS_LEFT : LINENUMBER_POS_RIGHT;
+ }
+ }
+ if( LINENUMBER_POS_LEFT == ePos )
+ {
+ m_bGoLeft = true;
+ m_nX -= m_rLineInf.GetPosFromLeft();
+ }
+ else
+ {
+ m_bGoLeft = false;
+ m_nX += pFrame->getFrameArea().Width() + m_rLineInf.GetPosFromLeft();
+ }
+ }
+ if( eHor == text::HoriOrientation::NONE )
+ return;
+
+ if( text::HoriOrientation::INSIDE == eHor || text::HoriOrientation::OUTSIDE == eHor )
+ {
+ if (!oIsRightPage.has_value())
+ oIsRightPage = pFrame->FindPageFrame()->OnRightPage();
+ if (*oIsRightPage)
+ eHor = eHor == text::HoriOrientation::INSIDE ? text::HoriOrientation::LEFT : text::HoriOrientation::RIGHT;
+ else
+ eHor = eHor == text::HoriOrientation::OUTSIDE ? text::HoriOrientation::LEFT : text::HoriOrientation::RIGHT;
+ }
+ const SwFrame* pTmpFrame = pFrame->FindTabFrame();
+ if( !pTmpFrame )
+ pTmpFrame = pFrame;
+ m_nRedX = text::HoriOrientation::LEFT == eHor ? pTmpFrame->getFrameArea().Left() - REDLINE_DISTANCE :
+ pTmpFrame->getFrameArea().Right() + REDLINE_DISTANCE;
+}
+
+void SwExtraPainter::PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, bool bRed, const OUString* pRedlineText )
+{
+ const OUString aTmp( pRedlineText
+ // Tracked change is stronger than the line number
+ ? *pRedlineText
+ : ( HasNumber()
+ // Line number is stronger than the divider
+ ? m_rLineInf.GetNumType().GetNumStr( m_nLineNr )
+ : m_rLineInf.GetDivider() ) );
+
+ // Get script type of line numbering:
+ m_pFnt->SetActual( SwScriptInfo::WhichFont(0, aTmp) );
+
+ if ( pRedlineText )
+ {
+ m_pFnt->SetColor(NON_PRINTING_CHARACTER_COLOR);
+ // don't strike out text in Insertions In Margin mode
+ if ( !m_pSh->GetViewOptions()->IsShowChangesInMargin2() )
+ m_pFnt->SetStrikeout( STRIKEOUT_SINGLE );
+ m_pFnt->SetSize( Size( 0, 200), m_pFnt->GetActual() );
+ }
+
+ SwDrawTextInfo aDrawInf( m_pSh, *m_pSh->GetOut(), aTmp, 0, aTmp.getLength() );
+ aDrawInf.SetSpace( 0 );
+ aDrawInf.SetWrong( nullptr );
+ aDrawInf.SetGrammarCheck( nullptr );
+ aDrawInf.SetSmartTags( nullptr );
+ aDrawInf.SetFrame( m_pTextFrame );
+ aDrawInf.SetFont( m_pFnt.get() );
+ aDrawInf.SetSnapToGrid( false );
+ aDrawInf.SetIgnoreFrameRTL( true );
+
+ bool bTooBig = m_pFnt->GetSize( m_pFnt->GetActual() ).Height() > nMax &&
+ m_pFnt->GetHeight( m_pSh, *m_pSh->GetOut() ) > nMax;
+ SwFont* pTmpFnt;
+ if( bTooBig )
+ {
+ pTmpFnt = new SwFont( *GetFont() );
+ if( nMax >= 20 )
+ {
+ nMax *= 17;
+ nMax /= 20;
+ }
+ pTmpFnt->SetSize( Size( 0, nMax ), pTmpFnt->GetActual() );
+ }
+ else
+ pTmpFnt = GetFont();
+ Point aTmpPos( m_nX, nY );
+ aTmpPos.AdjustY(nAsc );
+ if ( pRedlineText )
+ {
+ Size aSize = pTmpFnt->GetTextSize_( aDrawInf );
+ aTmpPos.AdjustX( -(aSize.Width()) - 200 );
+ }
+ bool bPaint = true;
+ if( !IsClipChg() )
+ {
+ Size aSize = pTmpFnt->GetTextSize_( aDrawInf );
+ if( m_bGoLeft )
+ aTmpPos.AdjustX( -(aSize.Width()) );
+ // calculate rectangle containing the line number
+ SwRect aRct( Point( aTmpPos.X(),
+ aTmpPos.Y() - pTmpFnt->GetAscent( m_pSh, *m_pSh->GetOut() )
+ ), aSize );
+ if( !m_aRect.Contains( aRct ) )
+ {
+ if( aRct.Intersection( m_aRect ).IsEmpty() )
+ bPaint = false;
+ else
+ m_aClip.ChgClip( m_aRect, m_pTextFrame );
+ }
+ }
+ else if( m_bGoLeft )
+ aTmpPos.AdjustX( -(pTmpFnt->GetTextSize_( aDrawInf ).Width()) );
+ aDrawInf.SetPos( aTmpPos );
+ if( bPaint )
+ pTmpFnt->DrawText_( aDrawInf );
+
+ if( bTooBig )
+ delete pTmpFnt;
+ if( bRed )
+ {
+ tools::Long nDiff = m_bGoLeft ? m_nRedX - m_nX : m_nX - m_nRedX;
+ if( nDiff > REDLINE_MINDIST )
+ PaintRedline( nY, nMax );
+ }
+}
+
+void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax )
+{
+ Point aStart( m_nRedX, nY );
+ Point aEnd( m_nRedX, nY + nMax );
+
+ if( !IsClipChg() )
+ {
+ SwRect aRct( aStart, aEnd );
+ if( !m_aRect.Contains( aRct ) )
+ {
+ if( aRct.Intersection( m_aRect ).IsEmpty() )
+ return;
+ m_aClip.ChgClip( m_aRect, m_pTextFrame );
+ }
+ }
+ const Color aOldCol( m_pSh->GetOut()->GetLineColor() );
+ m_pSh->GetOut()->SetLineColor( SW_MOD()->GetRedlineMarkColor() );
+
+ if ( m_pTextFrame->IsVertical() )
+ {
+ m_pTextFrame->SwitchHorizontalToVertical( aStart );
+ m_pTextFrame->SwitchHorizontalToVertical( aEnd );
+ }
+
+ m_pSh->GetOut()->DrawLine( aStart, aEnd );
+ m_pSh->GetOut()->SetLineColor( aOldCol );
+}
+
+void SwTextFrame::PaintExtraData( const SwRect &rRect ) const
+{
+ if( getFrameArea().Top() > rRect.Bottom() || getFrameArea().Bottom() < rRect.Top() )
+ return;
+
+ PaintOutlineContentVisibilityButton();
+
+ SwDoc const& rDoc(GetDoc());
+ const IDocumentRedlineAccess& rIDRA = rDoc.getIDocumentRedlineAccess();
+ const SwLineNumberInfo &rLineInf = rDoc.GetLineNumberInfo();
+ const SwFormatLineNumber &rLineNum = GetAttrSet()->GetLineNumber();
+ bool bLineNum = !IsInTab() && rLineInf.IsPaintLineNumbers() &&
+ ( !IsInFly() || rLineInf.IsCountInFlys() ) && rLineNum.IsCount();
+ sal_Int16 eHor = static_cast<sal_Int16>(SW_MOD()->GetRedlineMarkPos());
+ if (eHor != text::HoriOrientation::NONE
+ && (!IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
+ || getRootFrame()->IsHideRedlines()))
+ {
+ eHor = text::HoriOrientation::NONE;
+ }
+ bool bRedLine = eHor != text::HoriOrientation::NONE;
+ if ( !bLineNum && !bRedLine )
+ return;
+
+ if( IsLocked() || IsHiddenNow() || !getFramePrintArea().Height() )
+ return;
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+ SwRect rOldRect( rRect );
+
+ if ( IsVertical() )
+ SwitchVerticalToHorizontal( const_cast<SwRect&>(rRect) );
+
+ SwLayoutModeModifier aLayoutModeModifier( *pSh->GetOut() );
+ aLayoutModeModifier.Modify( false );
+
+ // #i16816# tagged pdf support
+ SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pSh->GetOut() );
+
+ SwExtraPainter aExtra( this, pSh, rLineInf, rRect, eHor, bLineNum );
+
+ if( HasPara() )
+ {
+ TextFrameLockGuard aLock(const_cast<SwTextFrame*>(this));
+
+ SwTextLineAccess aAccess( this );
+ aAccess.GetPara();
+
+ SwTextPaintInfo aInf( const_cast<SwTextFrame*>(this), rRect );
+
+ aLayoutModeModifier.Modify( false );
+
+ SwTextPainter aLine( const_cast<SwTextFrame*>(this), &aInf );
+ bool bNoDummy = !aLine.GetNext(); // Only one empty line!
+
+ while( aLine.Y() + aLine.GetLineHeight() <= rRect.Top() )
+ {
+ if( !aLine.GetCurr()->IsDummy() &&
+ ( rLineInf.IsCountBlankLines() ||
+ aLine.GetCurr()->HasContent() ) )
+ aExtra.IncLineNr();
+ if( !aLine.Next() )
+ {
+ const_cast<SwRect&>(rRect) = rOldRect;
+ return;
+ }
+ }
+
+ tools::Long nBottom = rRect.Bottom();
+
+ bool bNoPrtLine = 0 == GetMinPrtLine();
+ if( !bNoPrtLine )
+ {
+ while ( aLine.Y() < GetMinPrtLine() )
+ {
+ if( ( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() )
+ && !aLine.GetCurr()->IsDummy() )
+ aExtra.IncLineNr();
+ if( !aLine.Next() )
+ break;
+ }
+ bNoPrtLine = aLine.Y() >= GetMinPrtLine();
+ }
+ const bool bIsShowChangesInMargin = pSh->GetViewOptions()->IsShowChangesInMargin();
+ if( bNoPrtLine )
+ {
+ do
+ {
+ if( bNoDummy || !aLine.GetCurr()->IsDummy() )
+ {
+ bool bRed = bRedLine && aLine.GetCurr()->HasRedline();
+ if( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() )
+ {
+ bool bRedInMargin = bIsShowChangesInMargin && bRed;
+ bool bNum = bLineNum && ( aExtra.HasNumber() || aExtra.HasDivider() );
+ if( bRedInMargin || bNum )
+ {
+ SwTwips nTmpHeight, nTmpAscent;
+ aLine.CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+ if ( bRedInMargin )
+ {
+ const OUString* pRedlineText = aLine.GetCurr()->GetRedlineText();
+ if( !pRedlineText->isEmpty() )
+ {
+ aExtra.PaintExtra( aLine.Y(), nTmpAscent,
+ nTmpHeight, bRed, pRedlineText );
+ bRed = false;
+ bNum = false;
+ }
+ }
+ if ( bNum )
+ {
+ aExtra.PaintExtra( aLine.Y(), nTmpAscent, nTmpHeight, bRed );
+ bRed = false;
+ }
+ }
+ aExtra.IncLineNr();
+ }
+ if( bRed )
+ aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight() );
+ }
+ } while( aLine.Next() && aLine.Y() <= nBottom );
+ }
+ }
+ else
+ {
+ if (SwRedlineTable::npos == rIDRA.GetRedlinePos(*GetTextNodeFirst(), RedlineType::Any))
+ {
+ bRedLine = false;
+ }
+
+ if( bLineNum && rLineInf.IsCountBlankLines() &&
+ ( aExtra.HasNumber() || aExtra.HasDivider() ) )
+ {
+ aExtra.PaintExtra( getFrameArea().Top()+getFramePrintArea().Top(), aExtra.GetFont()
+ ->GetAscent( pSh, *pSh->GetOut() ), getFramePrintArea().Height(), bRedLine );
+ }
+ else if( bRedLine )
+ aExtra.PaintRedline( getFrameArea().Top()+getFramePrintArea().Top(), getFramePrintArea().Height() );
+ }
+
+ const_cast<SwRect&>(rRect) = rOldRect;
+
+}
+
+SwRect SwTextFrame::GetPaintSwRect()
+{
+ // finger layout
+ OSL_ENSURE( isFrameAreaPositionValid(), "+SwTextFrame::GetPaintSwRect: no Calc()" );
+
+ SwRect aRet( getFramePrintArea() );
+ if ( IsEmpty() || !HasPara() )
+ aRet += getFrameArea().Pos();
+ else
+ {
+ // We return the right paint rect. Use the calculated PaintOfst as the
+ // left margin
+ SwRepaint& rRepaint = GetPara()->GetRepaint();
+ tools::Long l;
+
+ if ( IsVertLR() && !IsVertLRBT()) // mba: the following line was added, but we don't need it for the existing directions; kept for IsVertLR(), but should be checked
+ rRepaint.Chg( GetUpper()->getFrameArea().Pos() + GetUpper()->getFramePrintArea().Pos(), GetUpper()->getFramePrintArea().SSize() );
+
+ if( rRepaint.GetOffset() )
+ rRepaint.Left( rRepaint.GetOffset() );
+
+ l = rRepaint.GetRightOfst();
+ if( l && l > rRepaint.Right() )
+ rRepaint.Right( l );
+ rRepaint.SetOffset( 0 );
+ aRet = rRepaint;
+
+ // In case our left edge is the same as the body frame's left edge,
+ // then extend the rectangle to include the page margin as well,
+ // otherwise some font will be clipped.
+ SwLayoutFrame* pBodyFrame = GetUpper();
+ if (pBodyFrame->IsBodyFrame() && aRet.Left() == (pBodyFrame->getFrameArea().Left() + pBodyFrame->getFramePrintArea().Left()))
+ if (SwLayoutFrame* pPageFrame = pBodyFrame->GetUpper())
+ aRet.Left(pPageFrame->getFrameArea().Left());
+
+ if ( IsRightToLeft() )
+ SwitchLTRtoRTL( aRet );
+
+ if ( IsVertical() )
+ SwitchHorizontalToVertical( aRet );
+ }
+ ResetRepaint();
+
+ return aRet;
+}
+
+bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const
+{
+ PaintParagraphStylesHighlighting();
+
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if( pSh && ( pSh->GetViewOptions()->IsParagraph() || bInitFont ) )
+ {
+ bInitFont = false;
+ SwTextFly aTextFly( this );
+ aTextFly.SetTopRule();
+ SwRect aRect;
+ if( bCheck && aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) )
+ return false;
+ else if( pSh->GetWin() )
+ {
+ std::unique_ptr<SwFont> pFnt;
+ RedlineType eRedline = RedlineType::None;
+ const SwTextNode& rTextNode = *GetTextNodeForParaProps();
+ if ( rTextNode.HasSwAttrSet() )
+ {
+ const SwAttrSet *pAttrSet = &( rTextNode.GetSwAttrSet() );
+ pFnt.reset(new SwFont( pAttrSet, rTextNode.getIDocumentSettingAccess() ));
+ }
+ else
+ {
+ SwFontAccess aFontAccess( &rTextNode.GetAnyFormatColl(), pSh );
+ pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() ));
+ }
+
+ const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess();
+ if (IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
+ && !getRootFrame()->IsHideRedlines())
+ {
+ const SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any );
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ SwAttrHandler aAttrHandler;
+ aAttrHandler.Init( rTextNode.GetSwAttrSet(),
+ *rTextNode.getIDocumentSettingAccess() );
+ SwRedlineItr aRedln(rTextNode, *pFnt, aAttrHandler, nRedlPos, SwRedlineItr::Mode::Show);
+ const SwRangeRedline* pRedline = rIDRA.GetRedlineTable()[nRedlPos];
+ // show redlining only on the inserted/deleted empty paragraph, but not on the next one
+ if ( rTextNode.GetIndex() != pRedline->End()->GetNodeIndex() )
+ eRedline = pRedline->GetType();
+ // except if the next empty paragraph starts a new redline (e.g. deletion after insertion)
+ else if ( nRedlPos + 1 < rIDRA.GetRedlineTable().size() )
+ {
+ const SwRangeRedline* pNextRedline = rIDRA.GetRedlineTable()[nRedlPos + 1];
+ if ( rTextNode.GetIndex() == pNextRedline->Start()->GetNodeIndex() )
+ eRedline = pNextRedline->GetType();
+ }
+ }
+ }
+
+ if( pSh->GetViewOptions()->IsParagraph() && getFramePrintArea().Height() )
+ {
+ if( RTL_TEXTENCODING_SYMBOL == pFnt->GetCharSet( SwFontScript::Latin ) &&
+ pFnt->GetName( SwFontScript::Latin ) != numfunc::GetDefBulletFontname() )
+ {
+ pFnt->SetFamily( FAMILY_DONTKNOW, SwFontScript::Latin );
+ pFnt->SetName( numfunc::GetDefBulletFontname(), SwFontScript::Latin );
+ pFnt->SetStyleName(OUString(), SwFontScript::Latin);
+ pFnt->SetCharSet( RTL_TEXTENCODING_SYMBOL, SwFontScript::Latin );
+ }
+ pFnt->SetVertical( 0_deg10, IsVertical() );
+ SwFrameSwapper aSwapper( this, true );
+ SwLayoutModeModifier aLayoutModeModifier( *pSh->GetOut() );
+ aLayoutModeModifier.Modify( IsRightToLeft() );
+
+ pFnt->Invalidate();
+ pFnt->ChgPhysFnt( pSh, *pSh->GetOut() );
+ Point aPos = getFrameArea().Pos() + getFramePrintArea().Pos();
+
+ const SvxFirstLineIndentItem& rFirstLine(
+ GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
+
+ if (0 < rFirstLine.GetTextFirstLineOffset())
+ {
+ aPos.AdjustX(rFirstLine.GetTextFirstLineOffset());
+ }
+
+ std::unique_ptr<SwSaveClip, o3tl::default_delete<SwSaveClip>> xClip;
+ if( IsUndersized() )
+ {
+ xClip.reset(new SwSaveClip( pSh->GetOut() ));
+ xClip->ChgClip( rRect );
+ }
+
+ aPos.AdjustY(pFnt->GetAscent( pSh, *pSh->GetOut() ) );
+
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue() &&
+ IsInDocBody() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if ( pGrid )
+ {
+ // center character in grid line
+ aPos.AdjustY(( pGrid->GetBaseHeight() -
+ pFnt->GetHeight( pSh, *pSh->GetOut() ) ) / 2 );
+
+ if ( ! pGrid->GetRubyTextBelow() )
+ aPos.AdjustY(pGrid->GetRubyHeight() );
+ }
+ }
+
+ // Don't show the paragraph mark for collapsed paragraphs, when they are hidden
+ // No paragraph marker in the non-last part of a split fly anchor, either.
+ if ( EmptyHeight( ) > 1 && !HasNonLastSplitFlyDrawObj() )
+ {
+ SwDrawTextInfo aDrawInf( pSh, *pSh->GetOut(), CH_PAR, 0, 1 );
+ aDrawInf.SetPos( aPos );
+ aDrawInf.SetSpace( 0 );
+ aDrawInf.SetKanaComp( 0 );
+ aDrawInf.SetWrong( nullptr );
+ aDrawInf.SetGrammarCheck( nullptr );
+ aDrawInf.SetSmartTags( nullptr );
+ aDrawInf.SetFrame( this );
+ aDrawInf.SetFont( pFnt.get() );
+ aDrawInf.SetSnapToGrid( false );
+
+ // show redline color and settings drawing a background pilcrow,
+ // but keep also other formattings (with neutral pilcrow color)
+ if ( eRedline != RedlineType::None )
+ {
+ pFnt->DrawText_( aDrawInf );
+ if ( eRedline == RedlineType::Delete )
+ pFnt->SetStrikeout( STRIKEOUT_NONE );
+ else
+ pFnt->SetUnderline( LINESTYLE_NONE );
+ }
+
+ pFnt->SetColor(NON_PRINTING_CHARACTER_COLOR);
+ pFnt->DrawText_( aDrawInf );
+ }
+ }
+ return true;
+ }
+ }
+ else
+ return true;
+ return false;
+}
+
+void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const
+{
+ ResetRepaint();
+
+ // #i16816# tagged pdf support
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+
+ if( IsEmpty() && PaintEmpty( rRect, true ) )
+ return;
+
+ if( IsLocked() || IsHiddenNow() || ! getFramePrintArea().HasArea() )
+ return;
+
+ // It can happen that the IdleCollector withdrew my cached information
+ if( !HasPara() )
+ {
+ OSL_ENSURE( isFrameAreaPositionValid(), "+SwTextFrame::PaintSwFrame: no Calc()" );
+
+ // #i29062# pass info that we are currently
+ // painting.
+ const_cast<SwTextFrame*>(this)->GetFormatted( true );
+ if( IsEmpty() )
+ {
+ PaintEmpty( rRect, false );
+ return;
+ }
+ if( !HasPara() )
+ {
+ OSL_ENSURE( false, "+SwTextFrame::PaintSwFrame: missing format information" );
+ return;
+ }
+ }
+
+ // tdf140219-2.odt text frame with only fly portions and a follow is not
+ // actually a paragraph - delay creating all structured elements to follow.
+ bool const isPDFTaggingEnabled(!HasFollow() || GetPara()->HasContentPortions());
+ ::std::optional<SwTaggedPDFHelper> oTaggedPDFHelperNumbering;
+ if (isPDFTaggingEnabled)
+ {
+ Num_Info aNumInfo(*this);
+ oTaggedPDFHelperNumbering.emplace(&aNumInfo, nullptr, nullptr, rRenderContext);
+ }
+
+ // Lbl unfortunately must be able to contain multiple numbering portions
+ // that may be on multiple lines of text (but apparently always in the
+ // master frame), so it gets complicated.
+ ::std::optional<SwTaggedPDFHelper> oTaggedLabel;
+ // Paragraph tag - if there is a list label, opening should be delayed.
+ ::std::optional<SwTaggedPDFHelper> oTaggedParagraph;
+
+ if (isPDFTaggingEnabled
+ && (GetTextNodeForParaProps()->IsOutline()
+ || !GetPara()->HasNumberingPortion(SwParaPortion::FootnoteToo)))
+ { // no Lbl needed => open paragraph tag now
+ Frame_Info aFrameInfo(*this, false);
+ oTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, rRenderContext);
+ }
+
+ // We don't want to be interrupted while painting.
+ // Do that after thr Format()!
+ TextFrameLockGuard aLock(const_cast<SwTextFrame*>(this));
+
+ // We only paint the part of the TextFrame which changed, is within the
+ // range and was requested to paint.
+ // One could think that the area rRect _needs_ to be painted, although
+ // rRepaint is set. Indeed, we cannot avoid this problem from a formal
+ // perspective. Luckily we can assume rRepaint to be empty when we need
+ // paint the while Frame.
+ SwTextLineAccess aAccess( this );
+ SwParaPortion *pPara = aAccess.GetPara();
+
+ SwRepaint &rRepaint = pPara->GetRepaint();
+
+ // Switch off recycling when in the FlyContentFrame.
+ // A DrawRect is called for repainting the line anyways.
+ if( rRepaint.GetOffset() )
+ {
+ const SwFlyFrame *pFly = FindFlyFrame();
+ if( pFly && pFly->IsFlyInContentFrame() )
+ rRepaint.SetOffset( 0 );
+ }
+
+ // Ge the String for painting. The length is of special interest.
+
+ // Rectangle
+ OSL_ENSURE( ! IsSwapped(), "A frame is swapped before Paint" );
+ SwRect aOldRect( rRect );
+
+ {
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ if ( IsVertical() )
+ SwitchVerticalToHorizontal( const_cast<SwRect&>(rRect) );
+
+ if ( IsRightToLeft() )
+ SwitchRTLtoLTR( const_cast<SwRect&>(rRect) );
+
+ SwTextPaintInfo aInf( const_cast<SwTextFrame*>(this), rRect );
+ sw::WrongListIterator iterWrong(*this, &SwTextNode::GetWrong);
+ sw::WrongListIterator iterGrammar(*this, &SwTextNode::GetGrammarCheck);
+ sw::WrongListIterator iterSmartTags(*this, &SwTextNode::GetSmartTags);
+ if (iterWrong.LooksUseful())
+ {
+ aInf.SetWrongList( &iterWrong );
+ }
+ if (iterGrammar.LooksUseful())
+ {
+ aInf.SetGrammarCheckList( &iterGrammar );
+ }
+ if (iterSmartTags.LooksUseful())
+ {
+ aInf.SetSmartTags( &iterSmartTags );
+ }
+ aInf.GetTextFly().SetTopRule();
+
+ SwTextPainter aLine( const_cast<SwTextFrame*>(this), &aInf );
+ // Optimization: if no free flying Frame overlaps into our line, the
+ // SwTextFly just switches off
+ aInf.GetTextFly().Relax();
+
+ OutputDevice* pOut = aInf.GetOut();
+ const bool bOnWin = pSh->GetWin() != nullptr;
+
+ SwSaveClip aClip( bOnWin || IsUndersized() ? pOut : nullptr );
+
+ // Output loop: For each Line ... (which is still visible) ...
+ // adapt rRect (Top + 1, Bottom - 1)
+ // Because the Iterator attaches the Lines without a gap to each other
+ aLine.TwipsToLine( rRect.Top() + 1 );
+ tools::Long nBottom = rRect.Bottom();
+
+ bool bNoPrtLine = 0 == GetMinPrtLine();
+ if( !bNoPrtLine )
+ {
+ while ( aLine.Y() < GetMinPrtLine() && aLine.Next() )
+ ;
+ bNoPrtLine = aLine.Y() >= GetMinPrtLine();
+ }
+ if( bNoPrtLine )
+ {
+ do
+ {
+ aLine.DrawTextLine(rRect, aClip, IsUndersized(), oTaggedLabel, oTaggedParagraph, isPDFTaggingEnabled);
+
+ } while( aLine.Next() && aLine.Y() <= nBottom );
+ }
+
+ // Once is enough:
+ if( aLine.IsPaintDrop() )
+ aLine.PaintDropPortion();
+
+ if( rRepaint.HasArea() )
+ rRepaint.Clear();
+ }
+
+ PaintParagraphStylesHighlighting();
+
+ const_cast<SwRect&>(rRect) = aOldRect;
+
+ OSL_ENSURE( ! IsSwapped(), "A frame is swapped after Paint" );
+
+ assert(!oTaggedLabel); // must have been closed if opened
+ assert(!isPDFTaggingEnabled || oTaggedParagraph || rRect.GetIntersection(getFrameArea()) != getFrameArea()); // must have been created during complete paint (PDF export is always complete paint)
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
new file mode 100644
index 0000000000..3346fe345a
--- /dev/null
+++ b/sw/source/core/text/guess.cxx
@@ -0,0 +1,761 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <editeng/unolingu.hxx>
+#include <breakit.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include "guess.hxx"
+#include "inftxt.hxx"
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include <com/sun/star/i18n/BreakType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <unotools/charclass.hxx>
+#include <svl/urihelper.hxx>
+#include "porfld.hxx"
+#include <paratr.hxx>
+#include <doc.hxx>
+#include <unotools/linguprops.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::linguistic2;
+
+namespace{
+
+bool IsBlank(sal_Unicode ch) { return ch == CH_BLANK || ch == CH_FULL_BLANK || ch == CH_NB_SPACE || ch == CH_SIX_PER_EM; }
+
+// Used when spaces should not be counted in layout
+// Returns adjusted cut position
+TextFrameIndex AdjustCutPos(TextFrameIndex cutPos, TextFrameIndex& rBreakPos,
+ const SwTextFormatInfo& rInf)
+{
+ assert(cutPos >= rInf.GetIdx());
+ TextFrameIndex x = rBreakPos = cutPos;
+
+ // we step back until a non blank character has been found
+ // or there is only one more character left
+ while (x && x > rInf.GetIdx() + TextFrameIndex(1) && IsBlank(rInf.GetChar(--x)))
+ --rBreakPos;
+
+ while (IsBlank(rInf.GetChar(cutPos)))
+ ++cutPos;
+
+ return cutPos;
+}
+
+bool hasBlanksInLine(const SwTextFormatInfo& rInf, TextFrameIndex end)
+{
+ for (auto x = rInf.GetLineStart(); x < end; ++x)
+ if (IsBlank(rInf.GetChar(x)))
+ return true;
+ return false;
+}
+
+// Called for the last text run in a line; if it is block-adjusted, or center / right-adjusted
+// with Word compatibility option set, and it has trailing spaces, then the function sets the
+// values, and returns 'false' value that SwTextGuess::Guess should return, to create a
+// trailing SwHolePortion.
+bool maybeAdjustPositionsForBlockAdjust(TextFrameIndex& rCutPos, TextFrameIndex& rBreakPos,
+ TextFrameIndex& rBreakStart, sal_uInt16& rBreakWidth,
+ sal_uInt16& rExtraBlankWidth, sal_uInt16& rMaxSizeDiff,
+ const SwTextFormatInfo& rInf, const SwScriptInfo& rSI,
+ sal_uInt16 maxComp)
+{
+ const auto& adjObj = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
+ const SvxAdjust& adjust = adjObj.GetAdjust();
+ if (adjust == SvxAdjust::Block)
+ {
+ if (rInf.DontBlockJustify())
+ return true; // See tdf#106234
+ }
+ else
+ {
+ // tdf#104668 space chars at the end should be cut if the compatibility option is enabled
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
+ return true;
+ // for LTR mode only
+ if (rInf.GetTextFrame()->IsRightToLeft())
+ return true;
+ }
+ if (auto ch = rInf.GetChar(rCutPos); !ch) // end of paragraph - last line
+ {
+ if (adjust == SvxAdjust::Block)
+ {
+ // Check adjustment for last line
+ switch (adjObj.GetLastBlock())
+ {
+ default:
+ return true;
+ case SvxAdjust::Center: // tdf#104668
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
+ return true;
+ break;
+ case SvxAdjust::Block:
+ break; // OK - last line uses block-adjustment
+ }
+ }
+ }
+ else if (ch != CH_BREAK && !IsBlank(ch))
+ return true;
+
+ // tdf#57187: block-adjusted line shorter than full width, terminated by manual
+ // line break, must not use trailing spaces for adjustment
+ TextFrameIndex breakPos;
+ TextFrameIndex newCutPos = AdjustCutPos(rCutPos, breakPos, rInf);
+
+ if (auto ch = rInf.GetChar(newCutPos); ch && ch != CH_BREAK)
+ return true; // next is neither line break nor paragraph end
+ if (breakPos == newCutPos)
+ return true; // no trailing whitespace
+ if (adjust == SvxAdjust::Block && adjObj.GetOneWord() != SvxAdjust::Block
+ && !hasBlanksInLine(rInf, breakPos))
+ return true; // line can't block-adjust
+
+ // Some trailing spaces actually found, and in case of block adjustment, the text portion
+ // itself has spaces to be able to block-adjust, or single word is allowed to adjust
+ rBreakStart = rCutPos = newCutPos;
+ rBreakPos = breakPos;
+
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), breakPos - rInf.GetIdx(), maxComp, rBreakWidth,
+ rMaxSizeDiff, rInf.GetCachedVclData().get());
+ rInf.GetTextSize(&rSI, breakPos, rBreakStart - breakPos, maxComp, rExtraBlankWidth,
+ rMaxSizeDiff, rInf.GetCachedVclData().get());
+
+ return false; // require SwHolePortion creation
+}
+
+}
+
+// provides information for line break calculation
+// returns true if no line break has to be performed
+// otherwise possible break or hyphenation position is determined
+bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
+ const sal_uInt16 nPorHeight, sal_Int32 nSpacesInLine )
+{
+ m_nCutPos = rInf.GetIdx();
+
+ // Empty strings are always 0
+ if( !rInf.GetLen() || rInf.GetText().isEmpty() )
+ return false;
+
+ OSL_ENSURE( rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength()),
+ "+SwTextGuess::Guess: invalid SwTextFormatInfo" );
+
+ OSL_ENSURE( nPorHeight, "+SwTextGuess::Guess: no height" );
+
+ sal_uInt16 nMaxSizeDiff;
+
+ const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
+
+ sal_uInt16 nMaxComp = ( SwFontScript::CJK == rInf.GetFont()->GetActual() ) &&
+ rSI.CountCompChg() &&
+ ! rInf.IsMulti() &&
+ ! rPor.InFieldGrp() &&
+ ! rPor.IsDropPortion() ?
+ 10000 :
+ 0 ;
+
+ SwTwips nLineWidth = rInf.GetLineWidth();
+ TextFrameIndex nMaxLen = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx();
+
+ const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust();
+
+ // allow up to 20% shrinking of the spaces
+ if ( nSpacesInLine )
+ {
+ static constexpr OUStringLiteral STR_BLANK = u" ";
+ sal_Int16 nSpaceWidth = rInf.GetTextSize(STR_BLANK).Width();
+ nLineWidth += nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth);
+ }
+
+ if ( rInf.GetLen() < nMaxLen )
+ nMaxLen = rInf.GetLen();
+
+ if( !nMaxLen )
+ return false;
+
+ sal_uInt16 nItalic = 0;
+ if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
+ {
+ bool bAddItalic = true;
+
+ // do not add extra italic value if we have an active character grid
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid(
+ GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
+ bAddItalic = !pGrid || GRID_LINES_CHARS != pGrid->GetGridType();
+ }
+
+ // do not add extra italic value for an isolated blank:
+ if (TextFrameIndex(1) == rInf.GetLen() &&
+ CH_BLANK == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ bAddItalic = false;
+ }
+
+ if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::TAB_OVER_MARGIN))
+ {
+ // Content is allowed over the margin: in this case over-margin content caused by italic
+ // formatting is OK.
+ bAddItalic = false;
+ }
+
+ nItalic = bAddItalic ? nPorHeight / 12 : 0;
+
+ nLineWidth -= nItalic;
+
+ // #i46524# LineBreak bug with italics
+ if ( nLineWidth < 0 ) nLineWidth = 0;
+ }
+
+ const sal_Int32 nLeftRightBorderSpace =
+ (!rPor.GetJoinBorderWithNext() ? rInf.GetFont()->GetRightBorderSpace() : 0) +
+ (!rPor.GetJoinBorderWithPrev() ? rInf.GetFont()->GetLeftBorderSpace() : 0);
+
+ nLineWidth -= nLeftRightBorderSpace;
+
+ const bool bUnbreakableNumberings = rInf.GetTextFrame()->GetDoc()
+ .getIDocumentSettingAccess().get(DocumentSettingId::UNBREAKABLE_NUMBERINGS);
+
+ // first check if everything fits to line
+ if ( ( nLineWidth * 2 > SwTwips(sal_Int32(nMaxLen)) * nPorHeight ) ||
+ ( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
+ {
+ // call GetTextSize with maximum compression (for kanas)
+ rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen,
+ nMaxComp, m_nBreakWidth, nMaxSizeDiff );
+
+ if ( ( m_nBreakWidth <= nLineWidth ) || ( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
+ {
+ // portion fits to line
+ m_nCutPos = rInf.GetIdx() + nMaxLen;
+ bool bRet = rPor.InFieldGrp()
+ || maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
+ m_nBreakWidth, m_nExtraBlankWidth,
+ nMaxSizeDiff, rInf, rSI, nMaxComp);
+ if( nItalic &&
+ (m_nCutPos >= TextFrameIndex(rInf.GetText().getLength()) ||
+ // #i48035# Needed for CalcFitToContent
+ // if first line ends with a manual line break
+ rInf.GetText()[sal_Int32(m_nCutPos)] == CH_BREAK))
+ m_nBreakWidth += nItalic;
+
+ // save maximum width for later use
+ if ( nMaxSizeDiff )
+ rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff );
+
+ m_nBreakWidth += nLeftRightBorderSpace;
+
+ return bRet;
+ }
+ }
+
+ bool bHyph = rInf.IsHyphenate() && !rInf.IsHyphForbud();
+ TextFrameIndex nHyphPos(0);
+
+ // nCutPos is the first character not fitting to the current line
+ // nHyphPos is the first character not fitting to the current line,
+ // considering an additional "-" for hyphenation
+ if( bHyph )
+ {
+ // nHyphZone is the first character not fitting in the hyphenation zone,
+ // or 0, if the whole line in the hyphenation zone,
+ // or -1, if no hyphenation zone defined (i.e. it is 0)
+ sal_Int32 nHyphZone = -1;
+ const css::beans::PropertyValues & rHyphValues = rInf.GetHyphValues();
+ assert( rHyphValues.getLength() > 5 && rHyphValues[5].Name == UPN_HYPH_ZONE );
+ // hyphenation zone (distance from the line end in twips)
+ sal_uInt16 nTextHyphenZone;
+ if ( rHyphValues[5].Value >>= nTextHyphenZone )
+ nHyphZone = nTextHyphenZone >= nLineWidth
+ ? 0
+ : sal_Int32(rInf.GetTextBreak( nLineWidth - nTextHyphenZone,
+ nMaxLen, nMaxComp, rInf.GetCachedVclData().get() ));
+
+ m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, nHyphPos, rInf.GetCachedVclData().get() );
+
+ // don't try to hyphenate in the hyphenation zone
+ if ( nHyphZone != -1 && TextFrameIndex(COMPLETE_STRING) != m_nCutPos )
+ {
+ sal_Int32 nZonePos = sal_Int32(m_nCutPos);
+ // disable hyphenation, if there is a space within the hyphenation zone
+ // Note: for better interoperability, not fitting space character at
+ // rInf.GetIdx()[nHyphZone] always disables the hyphenation, don't need to calculate
+ // with its fitting part. Moreover, do not check double or more spaces there, they
+ // are accepted outside of the hyphenation zone, too.
+ for (; sal_Int32(rInf.GetIdx()) <= nZonePos && nHyphZone <= nZonePos; --nZonePos )
+ {
+ sal_Unicode cChar = rInf.GetText()[nZonePos];
+ if ( cChar == CH_BLANK || cChar == CH_FULL_BLANK || cChar == CH_SIX_PER_EM )
+ {
+ bHyph = false;
+ }
+ }
+ }
+
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::HYPHENATE_URLS))
+ {
+ // check URL from preceding space - similar to what AutoFormat does
+ const CharClass& rCC = GetAppCharClass();
+ sal_Int32 begin(m_nCutPos == TextFrameIndex(COMPLETE_STRING) ? rInf.GetText().getLength() : sal_Int32(m_nCutPos));
+ sal_Int32 end(begin);
+ for (; 0 < begin; --begin)
+ {
+ sal_Unicode cChar = rInf.GetText()[begin - 1];
+ if (cChar == CH_BLANK || cChar == CH_FULL_BLANK || cChar == CH_SIX_PER_EM)
+ {
+ break;
+ }
+ }
+ for (; end < rInf.GetText().getLength(); ++end)
+ {
+ sal_Unicode cChar = rInf.GetText()[end];
+ if (cChar == CH_BLANK || cChar == CH_FULL_BLANK || cChar == CH_SIX_PER_EM)
+ {
+ break;
+ }
+ }
+ if (!URIHelper::FindFirstURLInText(rInf.GetText(), begin, end, rCC).isEmpty())
+ {
+ bHyph = false;
+ }
+ }
+
+ // search start of the last word, if needed
+ if ( bHyph )
+ {
+ // nLastWord is the space character before the last word
+ sal_Int32 nLastWord = rInf.GetText().getLength() - 1;
+ bool bHyphenationNoLastWord = false;
+ assert( rHyphValues.getLength() > 3 && rHyphValues[3].Name == UPN_HYPH_NO_LAST_WORD );
+ if ( rHyphValues[3].Value >>= bHyphenationNoLastWord )
+ {
+ // skip spaces after the last word
+ bool bCutBlank = false;
+ for (; sal_Int32(rInf.GetIdx()) <= nLastWord; --nLastWord )
+ {
+ sal_Unicode cChar = rInf.GetText()[nLastWord];
+ if ( cChar != CH_BLANK && cChar != CH_FULL_BLANK && cChar != CH_SIX_PER_EM )
+ bCutBlank = true;
+ else if ( bCutBlank )
+ break;
+ }
+ }
+
+ // don't hyphenate the last word of the paragraph
+ if ( bHyphenationNoLastWord && sal_Int32(m_nCutPos) > nLastWord &&
+ TextFrameIndex(COMPLETE_STRING) != m_nCutPos &&
+ // if the last word is multiple line long, e.g. an URL,
+ // apply this only if the space before the word is there
+ // in the actual line, i.e. start the long word in a new
+ // line, but still allows to break its last parts
+ sal_Int32(rInf.GetIdx()) < nLastWord )
+ {
+ m_nCutPos = TextFrameIndex(nLastWord);
+ }
+ }
+
+ if ( !nHyphPos && rInf.GetIdx() )
+ nHyphPos = rInf.GetIdx() - TextFrameIndex(1);
+ }
+ else
+ {
+ m_nCutPos = rInf.GetTextBreak( nLineWidth, nMaxLen, nMaxComp, rInf.GetCachedVclData().get() );
+
+#if OSL_DEBUG_LEVEL > 1
+ if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos )
+ {
+ sal_uInt16 nMinSize;
+ rInf.GetTextSize( &rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(),
+ nMaxComp, nMinSize, nMaxSizeDiff );
+ OSL_ENSURE( nMinSize <= nLineWidth, "What a Guess!!!" );
+ }
+#endif
+ }
+
+ if( m_nCutPos > rInf.GetIdx() + nMaxLen )
+ {
+ // second check if everything fits to line
+ m_nCutPos = m_nBreakPos = rInf.GetIdx() + nMaxLen - TextFrameIndex(1);
+ rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, nMaxComp,
+ m_nBreakWidth, nMaxSizeDiff );
+
+ // The following comparison should always give true, otherwise
+ // there likely has been a pixel rounding error in GetTextBreak
+ if ( m_nBreakWidth <= nLineWidth )
+ {
+ bool bRet = rPor.InFieldGrp()
+ || maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
+ m_nBreakWidth, m_nExtraBlankWidth,
+ nMaxSizeDiff, rInf, rSI, nMaxComp);
+
+ if (nItalic && (m_nBreakPos + TextFrameIndex(1)) >= TextFrameIndex(rInf.GetText().getLength()))
+ m_nBreakWidth += nItalic;
+
+ // save maximum width for later use
+ if ( nMaxSizeDiff )
+ rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff );
+
+ m_nBreakWidth += nLeftRightBorderSpace;
+
+ return bRet;
+ }
+ }
+
+ // we have to trigger an underflow for a footnote portion
+ // which does not fit to the current line
+ if ( rPor.IsFootnotePortion() )
+ {
+ m_nBreakPos = rInf.GetIdx();
+ m_nCutPos = TextFrameIndex(-1);
+ return false;
+ }
+
+ TextFrameIndex nPorLen(0);
+ // do not call the break iterator nCutPos is a blank
+ sal_Unicode cCutChar = rInf.GetChar(m_nCutPos);
+ if (IsBlank(cCutChar))
+ {
+ m_nCutPos = m_nBreakStart = AdjustCutPos(m_nCutPos, m_nBreakPos, rInf);
+ nPorLen = m_nBreakPos - rInf.GetIdx();
+ }
+ else
+ {
+ // New: We should have a look into the last portion, if it was a
+ // field portion. For this, we expand the text of the field portion
+ // into our string. If the line break position is inside of before
+ // the field portion, we trigger an underflow.
+
+ TextFrameIndex nOldIdx = rInf.GetIdx();
+ sal_Unicode cFieldChr = 0;
+
+#if OSL_DEBUG_LEVEL > 0
+ OUString aDebugString;
+#endif
+
+ // be careful: a field portion can be both: 0x01 (common field)
+ // or 0x02 (the follow of a footnode)
+ if ( rInf.GetLast() && rInf.GetLast()->InFieldGrp() &&
+ ! rInf.GetLast()->IsFootnotePortion() &&
+ rInf.GetIdx() > rInf.GetLineStart() &&
+ CH_TXTATR_BREAKWORD ==
+ (cFieldChr = rInf.GetText()[sal_Int32(rInf.GetIdx()) - 1]))
+ {
+ SwFieldPortion* pField = static_cast<SwFieldPortion*>(rInf.GetLast());
+ OUString aText;
+ pField->GetExpText( rInf, aText );
+
+ if ( !aText.isEmpty() )
+ {
+ m_nFieldDiff = TextFrameIndex(aText.getLength() - 1);
+ m_nCutPos = m_nCutPos + m_nFieldDiff;
+ nHyphPos = nHyphPos + m_nFieldDiff;
+
+#if OSL_DEBUG_LEVEL > 0
+ aDebugString = rInf.GetText();
+#endif
+
+ // this is pretty nutso... reverted at the end...
+ OUString& rOldText = const_cast<OUString&> (rInf.GetText());
+ rOldText = rOldText.replaceAt(sal_Int32(rInf.GetIdx()) - 1, 1, aText);
+ rInf.SetIdx( rInf.GetIdx() + m_nFieldDiff );
+ }
+ else
+ cFieldChr = 0;
+ }
+
+ LineBreakHyphenationOptions aHyphOpt;
+ Reference< XHyphenator > xHyph;
+ if( bHyph )
+ {
+ xHyph = ::GetHyphenator();
+ aHyphOpt = LineBreakHyphenationOptions( xHyph,
+ rInf.GetHyphValues(), sal_Int32(nHyphPos));
+ }
+
+ // Get Language for break iterator.
+ // We have to switch the current language if we have a script
+ // change at nCutPos. Otherwise LATIN punctuation would never
+ // be allowed to be hanging punctuation.
+ // NEVER call GetLang if the string has been modified!!!
+ LanguageType aLang = rInf.GetFont()->GetLanguage();
+
+ // If we are inside a field portion, we use a temporary string which
+ // differs from the string at the textnode. Therefore we are not allowed
+ // to call the GetLang function.
+ if ( m_nCutPos && ! rPor.InFieldGrp() )
+ {
+ const CharClass& rCC = GetAppCharClass();
+
+ // step back until a non-punctuation character is reached
+ TextFrameIndex nLangIndex = m_nCutPos;
+
+ // If a field has been expanded right in front of us we do not
+ // step further than the beginning of the expanded field
+ // (which is the position of the field placeholder in our
+ // original string).
+ const TextFrameIndex nDoNotStepOver = CH_TXTATR_BREAKWORD == cFieldChr
+ ? rInf.GetIdx() - m_nFieldDiff - TextFrameIndex(1)
+ : TextFrameIndex(0);
+
+ if ( nLangIndex > nDoNotStepOver &&
+ TextFrameIndex(rInf.GetText().getLength()) == nLangIndex)
+ --nLangIndex;
+
+ while ( nLangIndex > nDoNotStepOver &&
+ !rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nLangIndex)))
+ --nLangIndex;
+
+ // last "real" character is not inside our current portion
+ // we have to check the script type of the last "real" character
+ if ( nLangIndex < rInf.GetIdx() )
+ {
+ sal_uInt16 nScript = g_pBreakIt->GetRealScriptOfText( rInf.GetText(),
+ sal_Int32(nLangIndex));
+ OSL_ENSURE( nScript, "Script is not between 1 and 4" );
+
+ // compare current script with script from last "real" character
+ if ( SwFontScript(nScript - 1) != rInf.GetFont()->GetActual() )
+ {
+ aLang = rInf.GetTextFrame()->GetLangOfChar(
+ CH_TXTATR_BREAKWORD == cFieldChr
+ ? nDoNotStepOver
+ : nLangIndex,
+ nScript, true);
+ }
+ }
+ }
+
+ const ForbiddenCharacters aForbidden(
+ *rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().getForbiddenCharacters(aLang, true));
+
+ const bool bAllowHanging = rInf.IsHanging() && ! rInf.IsMulti() &&
+ ! rInf.GetTextFrame()->IsInTab() &&
+ ! rPor.InFieldGrp();
+
+ LineBreakUserOptions aUserOpt(
+ aForbidden.beginLine, aForbidden.endLine,
+ rInf.HasForbiddenChars(), bAllowHanging, false );
+
+ // !!! We must have a local copy of the locale, because inside
+ // getLineBreak the LinguEventListener can trigger a new formatting,
+ // which can corrupt the locale pointer inside pBreakIt.
+ const lang::Locale aLocale = g_pBreakIt->GetLocale( aLang );
+
+ // determines first possible line break from nCutPos to
+ // start index of current line
+ LineBreakResults aResult = g_pBreakIt->GetBreakIter()->getLineBreak(
+ rInf.GetText(), sal_Int32(m_nCutPos), aLocale,
+ sal_Int32(rInf.GetLineStart()), aHyphOpt, aUserOpt );
+
+ m_nBreakPos = TextFrameIndex(aResult.breakIndex);
+
+ // if we are formatting multi portions we want to allow line breaks
+ // at the border between single line and multi line portion
+ // we have to be careful with footnote portions, they always come in
+ // with an index 0
+ if ( m_nBreakPos < rInf.GetLineStart() && rInf.IsFirstMulti() &&
+ ! rInf.IsFootnoteInside() )
+ m_nBreakPos = rInf.GetLineStart();
+
+ m_nBreakStart = m_nBreakPos;
+
+ bHyph = BreakType::HYPHENATION == aResult.breakType;
+
+ if (bHyph && m_nBreakPos != TextFrameIndex(COMPLETE_STRING))
+ {
+ // found hyphenation position within line
+ // nBreakPos is set to the hyphenation position
+ m_xHyphWord = aResult.rHyphenatedWord;
+ m_nBreakPos += TextFrameIndex(m_xHyphWord->getHyphenationPos() + 1);
+
+ // if not in interactive mode, we have to break behind a soft hyphen
+ if ( ! rInf.IsInterHyph() && rInf.GetIdx() )
+ {
+ sal_Int32 const nSoftHyphPos =
+ m_xHyphWord->getWord().indexOf( CHAR_SOFTHYPHEN );
+
+ if ( nSoftHyphPos >= 0 &&
+ m_nBreakStart + TextFrameIndex(nSoftHyphPos) <= m_nBreakPos &&
+ m_nBreakPos > rInf.GetLineStart() )
+ m_nBreakPos = rInf.GetIdx() - TextFrameIndex(1);
+ }
+
+ if( m_nBreakPos >= rInf.GetIdx() )
+ {
+ nPorLen = m_nBreakPos - rInf.GetIdx();
+ if ('-' == rInf.GetText()[ sal_Int32(m_nBreakPos) - 1 ])
+ m_xHyphWord = nullptr;
+ }
+ }
+ else if ( !bHyph && m_nBreakPos >= rInf.GetLineStart() )
+ {
+ OSL_ENSURE(sal_Int32(m_nBreakPos) != COMPLETE_STRING, "we should have found a break pos");
+
+ // found break position within line
+ m_xHyphWord = nullptr;
+
+ // check, if break position is soft hyphen and an underflow
+ // has to be triggered
+ if( m_nBreakPos > rInf.GetLineStart() && rInf.GetIdx() &&
+ CHAR_SOFTHYPHEN == rInf.GetText()[ sal_Int32(m_nBreakPos) - 1 ])
+ {
+ m_nBreakPos = rInf.GetIdx() - TextFrameIndex(1);
+ }
+
+ if( rAdjust != SvxAdjust::Left )
+ {
+ // Delete any blanks at the end of a line, but be careful:
+ // If a field has been expanded, we do not want to delete any
+ // blanks inside the field portion. This would cause an unwanted
+ // underflow
+ TextFrameIndex nX = m_nBreakPos;
+ while( nX > rInf.GetLineStart() &&
+ ( CH_TXTATR_BREAKWORD != cFieldChr || nX > rInf.GetIdx() ) &&
+ ( CH_BLANK == rInf.GetChar( --nX ) ||
+ CH_SIX_PER_EM == rInf.GetChar( nX ) ||
+ CH_FULL_BLANK == rInf.GetChar( nX ) ) )
+ m_nBreakPos = nX;
+ }
+ if( m_nBreakPos > rInf.GetIdx() )
+ nPorLen = m_nBreakPos - rInf.GetIdx();
+ }
+ else
+ {
+ // no line break found, setting nBreakPos to COMPLETE_STRING
+ // causes a break cut
+ m_nBreakPos = TextFrameIndex(COMPLETE_STRING);
+ OSL_ENSURE( m_nCutPos >= rInf.GetIdx(), "Deep cut" );
+ nPorLen = m_nCutPos - rInf.GetIdx();
+ }
+
+ if (m_nBreakPos > m_nCutPos && m_nBreakPos != TextFrameIndex(COMPLETE_STRING))
+ {
+ const TextFrameIndex nHangingLen = m_nBreakPos - m_nCutPos;
+ SwPosSize aTmpSize = rInf.GetTextSize( &rSI, m_nCutPos, nHangingLen );
+ aTmpSize.Width(aTmpSize.Width() + nLeftRightBorderSpace);
+ OSL_ENSURE( !m_pHanging, "A hanging portion is hanging around" );
+ m_pHanging.reset( new SwHangingPortion( std::move(aTmpSize) ) );
+ m_pHanging->SetLen( nHangingLen );
+ nPorLen = m_nCutPos - rInf.GetIdx();
+ }
+
+ // If we expanded a field, we must repair the original string.
+ // In case we do not trigger an underflow, we correct the nBreakPos
+ // value, but we cannot correct the nBreakStart value:
+ // If we have found a hyphenation position, nBreakStart can lie before
+ // the field.
+ if ( CH_TXTATR_BREAKWORD == cFieldChr )
+ {
+ if ( m_nBreakPos < rInf.GetIdx() )
+ m_nBreakPos = nOldIdx - TextFrameIndex(1);
+ else if (TextFrameIndex(COMPLETE_STRING) != m_nBreakPos)
+ {
+ OSL_ENSURE( m_nBreakPos >= m_nFieldDiff, "I've got field trouble!" );
+ m_nBreakPos = m_nBreakPos - m_nFieldDiff;
+ }
+
+ OSL_ENSURE( m_nCutPos >= rInf.GetIdx() && m_nCutPos >= m_nFieldDiff,
+ "I've got field trouble, part2!" );
+ m_nCutPos = m_nCutPos - m_nFieldDiff;
+
+ OUString& rOldText = const_cast<OUString&> (rInf.GetText());
+ OUString aReplacement( cFieldChr );
+ rOldText = rOldText.replaceAt(sal_Int32(nOldIdx) - 1, sal_Int32(m_nFieldDiff) + 1, aReplacement);
+ rInf.SetIdx( nOldIdx );
+
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( aDebugString == rInf.GetText(),
+ "Somebody, somebody, somebody put something in my string" );
+#endif
+ }
+ }
+
+ if( nPorLen )
+ {
+ rInf.GetTextSize( &rSI, rInf.GetIdx(), nPorLen,
+ nMaxComp, m_nBreakWidth, nMaxSizeDiff,
+ rInf.GetCachedVclData().get() );
+
+ // save maximum width for later use
+ if ( nMaxSizeDiff )
+ rInf.SetMaxWidthDiff( &rPor, nMaxSizeDiff );
+
+ m_nBreakWidth += nItalic + nLeftRightBorderSpace;
+ }
+ else
+ m_nBreakWidth = 0;
+
+ if (m_nBreakStart > rInf.GetIdx() + nPorLen + m_nFieldDiff)
+ {
+ rInf.GetTextSize(&rSI, rInf.GetIdx() + nPorLen,
+ m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff, nMaxComp,
+ m_nExtraBlankWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
+ }
+
+ if( m_pHanging )
+ {
+ m_nBreakPos = m_nCutPos;
+ // Keep following SwBreakPortion in the same line.
+ if ( CH_BREAK == rInf.GetChar( m_nBreakPos + m_pHanging->GetLen() ) )
+ return true;
+ }
+
+ return false;
+}
+
+// returns true if word at position nPos has a different spelling
+// if hyphenated at this position (old german spelling)
+bool SwTextGuess::AlternativeSpelling( const SwTextFormatInfo &rInf,
+ const TextFrameIndex nPos)
+{
+ // get word boundaries
+ Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ rInf.GetText(), sal_Int32(nPos),
+ g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ),
+ WordType::DICTIONARY_WORD, true );
+ m_nBreakStart = TextFrameIndex(aBound.startPos);
+ sal_Int32 nWordLen = aBound.endPos - sal_Int32(m_nBreakStart);
+
+ // if everything else fails, we want to cut at nPos
+ m_nCutPos = nPos;
+
+ OUString const aText( rInf.GetText().copy(sal_Int32(m_nBreakStart), nWordLen) );
+
+ // check, if word has alternative spelling
+ Reference< XHyphenator > xHyph( ::GetHyphenator() );
+ OSL_ENSURE( xHyph.is(), "Hyphenator is missing");
+ //! subtract 1 since the UNO-interface is 0 based
+ m_xHyphWord = xHyph->queryAlternativeSpelling( aText,
+ g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ),
+ sal::static_int_cast<sal_Int16>(sal_Int32(nPos - m_nBreakStart)),
+ rInf.GetHyphValues() );
+ return m_xHyphWord.is() && m_xHyphWord->isAlternativeSpelling();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/guess.hxx b/sw/source/core/text/guess.hxx
new file mode 100644
index 0000000000..5a7a9ac1cf
--- /dev/null
+++ b/sw/source/core/text/guess.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+#include <memory>
+
+#include "porrst.hxx"
+
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star::linguistic2 { class XHyphenatedWord; }
+
+class SwTextFormatInfo;
+
+class SwTextGuess
+{
+ css::uno::Reference< css::linguistic2::XHyphenatedWord > m_xHyphWord;
+ std::unique_ptr<SwHangingPortion> m_pHanging; // for hanging punctuation
+ TextFrameIndex m_nCutPos; // this character doesn't fit
+ TextFrameIndex m_nBreakStart; // start index of word containing line break
+ TextFrameIndex m_nBreakPos; // start index of break position
+ TextFrameIndex m_nFieldDiff; // absolute positions can be wrong if we
+ // a field in the text has been expanded
+ sal_uInt16 m_nBreakWidth; // width of the broken portion
+ sal_uInt16 m_nExtraBlankWidth; // width of spaces after the break
+public:
+ SwTextGuess(): m_nCutPos(0), m_nBreakStart(0),
+ m_nBreakPos(0), m_nFieldDiff(0), m_nBreakWidth(0), m_nExtraBlankWidth(0)
+ { }
+
+ // true, if current portion still fits to current line
+ bool Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
+ const sal_uInt16 nHeight, sal_Int32 nSpacesInLine = 0 );
+ bool AlternativeSpelling( const SwTextFormatInfo &rInf, const TextFrameIndex nPos );
+
+ SwHangingPortion* GetHangingPortion() const { return m_pHanging.get(); }
+ SwHangingPortion* ReleaseHangingPortion() { return m_pHanging.release(); }
+ sal_uInt16 BreakWidth() const { return m_nBreakWidth; }
+ sal_uInt16 ExtraBlankWidth() const { return m_nExtraBlankWidth; }
+ TextFrameIndex CutPos() const { return m_nCutPos; }
+ TextFrameIndex BreakStart() const { return m_nBreakStart; }
+ TextFrameIndex BreakPos() const {return m_nBreakPos; }
+ TextFrameIndex FieldDiff() const {return m_nFieldDiff; }
+ const css::uno::Reference< css::linguistic2::XHyphenatedWord >& HyphWord() const
+ { return m_xHyphWord; }
+};
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx
new file mode 100644
index 0000000000..3d9121ef1e
--- /dev/null
+++ b/sw/source/core/text/inftxt.cxx
@@ -0,0 +1,2150 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+
+#include <unotools/linguprops.hxx>
+#include <unotools/lingucfg.hxx>
+#include <hintids.hxx>
+#include <svl/ctloptions.hxx>
+#include <sfx2/infobar.hxx>
+#include <sfx2/printer.hxx>
+#include <sal/log.hxx>
+#include <editeng/hyphenzoneitem.hxx>
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/splwrap.hxx>
+#include <editeng/pgrditem.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/shaditem.hxx>
+
+#include <SwSmartTagMgr.hxx>
+#include <breakit.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <swmodule.hxx>
+#include <vcl/svapp.hxx>
+#include <viewsh.hxx>
+#include <viewopt.hxx>
+#include <frmtool.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentDeviceAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <paratr.hxx>
+#include <rootfrm.hxx>
+#include "inftxt.hxx"
+#include <noteurl.hxx>
+#include "porftn.hxx"
+#include "porrst.hxx"
+#include "itratr.hxx"
+#include "portab.hxx"
+#include <wrong.hxx>
+#include <doc.hxx>
+#include <pam.hxx>
+#include <numrule.hxx>
+#include <EnhancedPDFExportHelper.hxx>
+#include <docsh.hxx>
+#include <strings.hrc>
+#include <o3tl/deleter.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gradient.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <formatlinebreak.hxx>
+
+#include <view.hxx>
+#include <wrtsh.hxx>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <unotextrange.hxx>
+#include <SwStyleNameMapper.hxx>
+#include <unoprnms.hxx>
+#include <editeng/unoprnms.hxx>
+#include <unomap.hxx>
+#include <com/sun/star/awt/FontSlant.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::linguistic2;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+
+#define CHAR_UNDERSCORE u'_'
+#define CHAR_LEFT_ARROW u'\x25C0'
+#define CHAR_RIGHT_ARROW u'\x25B6'
+#define CHAR_TAB u'\x2192'
+#define CHAR_TAB_RTL u'\x2190'
+#define CHAR_LINEBREAK u'\x21B5'
+#define CHAR_LINEBREAK_RTL u'\x21B3'
+
+#define DRAW_SPECIAL_OPTIONS_CENTER 1
+#define DRAW_SPECIAL_OPTIONS_ROTATE 2
+
+SwLineInfo::SwLineInfo()
+ : m_pSpace( nullptr ),
+ m_nVertAlign( SvxParaVertAlignItem::Align::Automatic ),
+ m_nDefTabStop( 0 ),
+ m_bListTabStopIncluded( false ),
+ m_nListTabStopPosition( 0 )
+{
+}
+
+SwLineInfo::~SwLineInfo()
+{
+}
+
+void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet,
+ const SwTextNode& rTextNode )
+{
+ m_oRuler.emplace( rAttrSet.GetTabStops() );
+ if ( rTextNode.GetListTabStopPosition( m_nListTabStopPosition ) )
+ {
+ m_bListTabStopIncluded = true;
+
+ // insert the list tab stop into SvxTabItem instance <pRuler>
+ const SvxTabStop aListTabStop( m_nListTabStopPosition,
+ SvxTabAdjust::Left );
+ m_oRuler->Insert( aListTabStop );
+
+ // remove default tab stops, which are before the inserted list tab stop
+ for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ )
+ {
+ if ( (*m_oRuler)[i].GetTabPos() < m_nListTabStopPosition &&
+ (*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default )
+ {
+ m_oRuler->Remove(i);
+ continue;
+ }
+ }
+ }
+
+ if ( !rTextNode.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) )
+ {
+ // remove default tab stop at position 0
+ for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ )
+ {
+ if ( (*m_oRuler)[i].GetTabPos() == 0 &&
+ (*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default )
+ {
+ m_oRuler->Remove(i);
+ break;
+ }
+ }
+ }
+
+ m_pSpace = &rAttrSet.GetLineSpacing();
+ m_nVertAlign = rAttrSet.GetParaVertAlign().GetValue();
+ m_nDefTabStop = USHRT_MAX;
+}
+
+void SwTextInfo::CtorInitTextInfo( SwTextFrame *pFrame )
+{
+ m_pPara = pFrame->GetPara();
+ m_nTextStart = pFrame->GetOffset();
+ if (!m_pPara)
+ {
+ SAL_WARN("sw.core", "+SwTextInfo::CTOR: missing paragraph information");
+ pFrame->Format(pFrame->getRootFrame()->GetCurrShell()->GetOut());
+ m_pPara = pFrame->GetPara();
+ }
+}
+
+SwTextInfo::SwTextInfo( const SwTextInfo &rInf )
+ : m_pPara( const_cast<SwTextInfo&>(rInf).GetParaPortion() )
+ , m_nTextStart( rInf.GetTextStart() )
+{ }
+
+#if OSL_DEBUG_LEVEL > 0
+
+static void ChkOutDev( const SwTextSizeInfo &rInf )
+{
+ if ( !rInf.GetVsh() )
+ return;
+
+ const OutputDevice* pOut = rInf.GetOut();
+ const OutputDevice* pRef = rInf.GetRefDev();
+ OSL_ENSURE( pOut && pRef, "ChkOutDev: invalid output devices" );
+}
+#endif
+
+static TextFrameIndex GetMinLen( const SwTextSizeInfo &rInf )
+{
+ const TextFrameIndex nTextLen(rInf.GetText().getLength());
+ if (rInf.GetLen() == TextFrameIndex(COMPLETE_STRING))
+ return nTextLen;
+ const TextFrameIndex nInfLen = rInf.GetIdx() + rInf.GetLen();
+ return std::min(nTextLen, nInfLen);
+}
+
+SwTextSizeInfo::SwTextSizeInfo()
+: m_pKanaComp(nullptr)
+, m_pVsh(nullptr)
+, m_pOut(nullptr)
+, m_pRef(nullptr)
+, m_pFnt(nullptr)
+, m_pUnderFnt(nullptr)
+, m_pFrame(nullptr)
+, m_pOpt(nullptr)
+, m_pText(nullptr)
+, m_nIdx(0)
+, m_nLen(0)
+, m_nMeasureLen(COMPLETE_STRING)
+, m_nKanaIdx(0)
+, m_bOnWin (false)
+, m_bNotEOL (false)
+, m_bURLNotify(false)
+, m_bStopUnderflow(false)
+, m_bFootnoteInside(false)
+, m_bOtherThanFootnoteInside(false)
+, m_bMulti(false)
+, m_bFirstMulti(false)
+, m_bRuby(false)
+, m_bHanging(false)
+, m_bScriptSpace(false)
+, m_bForbiddenChars(false)
+, m_bSnapToGrid(false)
+, m_nDirection(0)
+{}
+
+SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew )
+ : SwTextInfo( rNew ),
+ m_pKanaComp(rNew.GetpKanaComp()),
+ m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()),
+ m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()),
+ m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()),
+ m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()),
+ m_pUnderFnt(rNew.GetUnderFnt()),
+ m_pFrame(rNew.m_pFrame),
+ m_pOpt(&rNew.GetOpt()),
+ m_pText(&rNew.GetText()),
+ m_nIdx(rNew.GetIdx()),
+ m_nLen(rNew.GetLen()),
+ m_nMeasureLen(rNew.GetMeasureLen()),
+ m_nKanaIdx( rNew.GetKanaIdx() ),
+ m_bOnWin( rNew.OnWin() ),
+ m_bNotEOL( rNew.NotEOL() ),
+ m_bURLNotify( rNew.URLNotify() ),
+ m_bStopUnderflow( rNew.StopUnderflow() ),
+ m_bFootnoteInside( rNew.IsFootnoteInside() ),
+ m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ),
+ m_bMulti( rNew.IsMulti() ),
+ m_bFirstMulti( rNew.IsFirstMulti() ),
+ m_bRuby( rNew.IsRuby() ),
+ m_bHanging( rNew.IsHanging() ),
+ m_bScriptSpace( rNew.HasScriptSpace() ),
+ m_bForbiddenChars( rNew.HasForbiddenChars() ),
+ m_bSnapToGrid( rNew.SnapToGrid() ),
+ m_nDirection( rNew.GetDirection() )
+{
+#if OSL_DEBUG_LEVEL > 0
+ ChkOutDev( *this );
+#endif
+}
+
+void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame,
+ TextFrameIndex const nNewIdx)
+{
+ m_pKanaComp = nullptr;
+ m_nKanaIdx = 0;
+ m_pFrame = pFrame;
+ CtorInitTextInfo( m_pFrame );
+ SwDoc const& rDoc(m_pFrame->GetDoc());
+ m_pVsh = m_pFrame->getRootFrame()->GetCurrShell();
+
+ // Get the output and reference device
+ if ( m_pVsh )
+ {
+ m_pOut = pRenderContext;
+ m_pRef = &m_pVsh->GetRefDev();
+ m_bOnWin = m_pVsh->GetWin() || OUTDEV_WINDOW == m_pOut->GetOutDevType() || m_pVsh->isOutputToWindow();
+ }
+ else
+ {
+ // Access via StarONE. We do not need a Shell or an active one.
+ if (rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE))
+ {
+ // We can only pick the AppWin here? (there's nothing better to pick?)
+ m_pOut = Application::GetDefaultDevice();
+ }
+ else
+ m_pOut = rDoc.getIDocumentDeviceAccess().getPrinter(false);
+
+ m_pRef = m_pOut;
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ ChkOutDev( *this );
+#endif
+
+ // Set default layout mode ( LTR or RTL ).
+ if ( m_pFrame->IsRightToLeft() )
+ {
+ m_pOut->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
+ m_pRef->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
+ m_nDirection = DIR_RIGHT2LEFT;
+ }
+ else
+ {
+ m_pOut->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+ m_pRef->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+ m_nDirection = DIR_LEFT2RIGHT;
+ }
+
+ // The Options
+
+ m_pOpt = m_pVsh ?
+ m_pVsh->GetViewOptions() :
+ SW_MOD()->GetViewOption(rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)); // Options from Module, due to StarONE
+
+ // bURLNotify is set if MakeGraphic prepares it
+ // TODO: Unwind
+ m_bURLNotify = pNoteURL && !m_bOnWin;
+
+ SetSnapToGrid( m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue() &&
+ m_pFrame->IsInDocBody() );
+
+ m_pFnt = nullptr;
+ m_pUnderFnt = nullptr;
+ m_pText = &m_pFrame->GetText();
+
+ m_nIdx = nNewIdx;
+ m_nLen = m_nMeasureLen = TextFrameIndex(COMPLETE_STRING);
+ m_bNotEOL = false;
+ m_bStopUnderflow = m_bFootnoteInside = m_bOtherThanFootnoteInside = false;
+ m_bMulti = m_bFirstMulti = m_bRuby = m_bHanging = m_bScriptSpace =
+ m_bForbiddenChars = false;
+
+ SetLen( GetMinLen( *this ) );
+}
+
+SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pText,
+ TextFrameIndex const nIndex)
+ : SwTextInfo( rNew ),
+ m_pKanaComp(rNew.GetpKanaComp()),
+ m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()),
+ m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()),
+ m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()),
+ m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()),
+ m_pUnderFnt(rNew.GetUnderFnt()),
+ m_pFrame( rNew.m_pFrame ),
+ m_pOpt(&rNew.GetOpt()),
+ m_pText(pText),
+ m_nIdx(nIndex),
+ m_nLen(COMPLETE_STRING),
+ m_nMeasureLen(COMPLETE_STRING),
+ m_nKanaIdx( rNew.GetKanaIdx() ),
+ m_bOnWin( rNew.OnWin() ),
+ m_bNotEOL( rNew.NotEOL() ),
+ m_bURLNotify( rNew.URLNotify() ),
+ m_bStopUnderflow( rNew.StopUnderflow() ),
+ m_bFootnoteInside( rNew.IsFootnoteInside() ),
+ m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ),
+ m_bMulti( rNew.IsMulti() ),
+ m_bFirstMulti( rNew.IsFirstMulti() ),
+ m_bRuby( rNew.IsRuby() ),
+ m_bHanging( rNew.IsHanging() ),
+ m_bScriptSpace( rNew.HasScriptSpace() ),
+ m_bForbiddenChars( rNew.HasForbiddenChars() ),
+ m_bSnapToGrid( rNew.SnapToGrid() ),
+ m_nDirection( rNew.GetDirection() )
+{
+#if OSL_DEBUG_LEVEL > 0
+ ChkOutDev( *this );
+#endif
+ SetLen( GetMinLen( *this ) );
+}
+
+SwTextSizeInfo::SwTextSizeInfo(SwTextFrame *const pTextFrame,
+ TextFrameIndex const nIndex)
+ : m_bOnWin(false)
+{
+ CtorInitTextSizeInfo( pTextFrame->getRootFrame()->GetCurrShell()->GetOut(), pTextFrame, nIndex );
+}
+
+void SwTextSizeInfo::SelectFont()
+{
+ // The path needs to go via ChgPhysFnt or the FontMetricCache gets confused.
+ // In this case pLastMet has it's old value.
+ // Wrong: GetOut()->SetFont( GetFont()->GetFnt() );
+ GetFont()->Invalidate();
+ GetFont()->ChgPhysFnt( m_pVsh, *GetOut() );
+}
+
+void SwTextSizeInfo::NoteAnimation() const
+{
+ if( OnWin() )
+ SwRootFrame::FlushVout();
+
+ OSL_ENSURE( m_pOut == m_pVsh->GetOut(),
+ "SwTextSizeInfo::NoteAnimation() changed m_pOut" );
+}
+
+SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev,
+ const SwScriptInfo* pSI,
+ const OUString& rText,
+ const TextFrameIndex nIndex,
+ const TextFrameIndex nLength) const
+{
+ SwDrawTextInfo aDrawInf( m_pVsh, *pOutDev, pSI, rText, nIndex, nLength );
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetFont( m_pFnt );
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ aDrawInf.SetKanaComp( 0 );
+ return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
+}
+
+SwPosSize SwTextSizeInfo::GetTextSize() const
+{
+ const SwScriptInfo& rSI =
+ const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
+
+ // in some cases, compression is not allowed or suppressed for
+ // performance reasons
+ sal_uInt16 nComp =( SwFontScript::CJK == GetFont()->GetActual() &&
+ rSI.CountCompChg() &&
+ ! IsMulti() ) ?
+ GetKanaComp() :
+ 0 ;
+
+ SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen );
+ aDrawInf.SetMeasureLen( m_nMeasureLen );
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetFont( m_pFnt );
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ aDrawInf.SetKanaComp( nComp );
+ return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
+}
+
+void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex nIndex,
+ const TextFrameIndex nLength, const sal_uInt16 nComp,
+ sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff,
+ vcl::text::TextLayoutCache const*const pCache) const
+{
+ SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength,
+ 0, false, pCache);
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetFont( m_pFnt );
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ aDrawInf.SetKanaComp( nComp );
+ SwPosSize aSize( m_pFnt->GetTextSize_( aDrawInf ) );
+ nMaxSizeDiff = o3tl::narrowing<sal_uInt16>(aDrawInf.GetKanaDiff());
+ nMinSize = aSize.Width();
+}
+
+TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
+ const TextFrameIndex nMaxLen,
+ const sal_uInt16 nComp,
+ vcl::text::TextLayoutCache const*const pCache) const
+{
+ const SwScriptInfo& rScriptInfo =
+ const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
+
+ OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" );
+ SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo,
+ *m_pText, GetIdx(), nMaxLen, 0, false, pCache );
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetFont( m_pFnt );
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ aDrawInf.SetKanaComp( nComp );
+ aDrawInf.SetHyphPos( nullptr );
+
+ return m_pFnt->GetTextBreak( aDrawInf, nLineWidth );
+}
+
+TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
+ const TextFrameIndex nMaxLen,
+ const sal_uInt16 nComp,
+ TextFrameIndex& rExtraCharPos,
+ vcl::text::TextLayoutCache const*const pCache) const
+{
+ const SwScriptInfo& rScriptInfo =
+ const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
+
+ OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" );
+ SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rScriptInfo,
+ *m_pText, GetIdx(), nMaxLen, 0, false, pCache );
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetFont( m_pFnt );
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ aDrawInf.SetKanaComp( nComp );
+ aDrawInf.SetHyphPos( &rExtraCharPos );
+
+ return m_pFnt->GetTextBreak( aDrawInf, nLineWidth );
+}
+
+bool SwTextSizeInfo::HasHint(TextFrameIndex const nPos) const
+{
+ std::pair<SwTextNode const*, sal_Int32> const pos(m_pFrame->MapViewToModel(nPos));
+ return pos.first->GetTextAttrForCharAt(pos.second);
+}
+
+void SwTextPaintInfo::CtorInitTextPaintInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const SwRect &rPaint )
+{
+ CtorInitTextSizeInfo( pRenderContext, pFrame, TextFrameIndex(0) );
+ m_aTextFly.CtorInitTextFly( pFrame );
+ m_aPaintRect = rPaint;
+ m_nSpaceIdx = 0;
+ m_pSpaceAdd = nullptr;
+ m_pWrongList = nullptr;
+ m_pGrammarCheckList = nullptr;
+ m_pSmartTags = nullptr;
+ m_pBrushItem = nullptr;
+}
+
+SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* pText )
+ : SwTextSizeInfo( rInf, pText )
+ , m_pWrongList( rInf.GetpWrongList() )
+ , m_pGrammarCheckList( rInf.GetGrammarCheckList() )
+ , m_pSmartTags( rInf.GetSmartTags() )
+ , m_pSpaceAdd( rInf.GetpSpaceAdd() ),
+ m_pBrushItem( rInf.GetBrushItem() ),
+ m_aTextFly( rInf.GetTextFly() ),
+ m_aPos( rInf.GetPos() ),
+ m_aPaintRect( rInf.GetPaintRect() ),
+ m_nSpaceIdx( rInf.GetSpaceIdx() )
+{ }
+
+SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf )
+ : SwTextSizeInfo( rInf )
+ , m_pWrongList( rInf.GetpWrongList() )
+ , m_pGrammarCheckList( rInf.GetGrammarCheckList() )
+ , m_pSmartTags( rInf.GetSmartTags() )
+ , m_pSpaceAdd( rInf.GetpSpaceAdd() ),
+ m_pBrushItem( rInf.GetBrushItem() ),
+ m_aTextFly( rInf.GetTextFly() ),
+ m_aPos( rInf.GetPos() ),
+ m_aPaintRect( rInf.GetPaintRect() ),
+ m_nSpaceIdx( rInf.GetSpaceIdx() )
+{ }
+
+SwTextPaintInfo::SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint )
+{
+ CtorInitTextPaintInfo( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, rPaint );
+}
+
+namespace
+{
+/**
+ * Context class that captures the draw operations on rDrawInf's output device for transparency
+ * purposes.
+ */
+class SwTransparentTextGuard
+{
+ ScopedVclPtrInstance<VirtualDevice> m_aContentVDev;
+ GDIMetaFile m_aContentMetafile;
+ MapMode m_aNewMapMode;
+ SwRect m_aPorRect;
+ SwTextPaintInfo& m_rPaintInf;
+ SwDrawTextInfo& m_rDrawInf;
+
+public:
+ SwTransparentTextGuard(const SwLinePortion& rPor, SwTextPaintInfo& rPaintInf,
+ SwDrawTextInfo& rDrawInf);
+ ~SwTransparentTextGuard();
+};
+
+SwTransparentTextGuard::SwTransparentTextGuard(const SwLinePortion& rPor,
+ SwTextPaintInfo& rPaintInf, SwDrawTextInfo& rDrawInf)
+ : m_aNewMapMode(rPaintInf.GetOut()->GetMapMode())
+ , m_rPaintInf(rPaintInf)
+ , m_rDrawInf(rDrawInf)
+{
+ rPaintInf.CalcRect(rPor, &m_aPorRect);
+ rDrawInf.SetOut(*m_aContentVDev);
+ m_aContentVDev->SetMapMode(rPaintInf.GetOut()->GetMapMode());
+ m_aContentMetafile.Record(m_aContentVDev.get());
+ m_aContentVDev->SetLineColor(rPaintInf.GetOut()->GetLineColor());
+ m_aContentVDev->SetFillColor(rPaintInf.GetOut()->GetFillColor());
+ m_aContentVDev->SetFont(rPaintInf.GetOut()->GetFont());
+ m_aContentVDev->SetDrawMode(rPaintInf.GetOut()->GetDrawMode());
+ m_aContentVDev->SetSettings(rPaintInf.GetOut()->GetSettings());
+ m_aContentVDev->SetRefPoint(rPaintInf.GetOut()->GetRefPoint());
+}
+
+SwTransparentTextGuard::~SwTransparentTextGuard()
+{
+ m_aContentMetafile.Stop();
+ m_aContentMetafile.WindStart();
+ m_aNewMapMode.SetOrigin(m_aPorRect.TopLeft());
+ m_aContentMetafile.SetPrefMapMode(m_aNewMapMode);
+ m_aContentMetafile.SetPrefSize(m_aPorRect.SSize());
+ m_rDrawInf.SetOut(*m_rPaintInf.GetOut());
+ Gradient aVCLGradient;
+ sal_uInt8 nTransPercentVcl = 255 - m_rPaintInf.GetFont()->GetColor().GetAlpha();
+ const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl);
+ aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR);
+ aVCLGradient.SetStartColor(aTransColor);
+ aVCLGradient.SetEndColor(aTransColor);
+ aVCLGradient.SetAngle(0_deg10);
+ aVCLGradient.SetBorder(0);
+ aVCLGradient.SetOfsX(0);
+ aVCLGradient.SetOfsY(0);
+ aVCLGradient.SetStartIntensity(100);
+ aVCLGradient.SetEndIntensity(100);
+ aVCLGradient.SetSteps(2);
+ m_rPaintInf.GetOut()->DrawTransparent(m_aContentMetafile, m_aPorRect.TopLeft(),
+ m_aPorRect.SSize(), aVCLGradient);
+}
+}
+
+void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPor,
+ TextFrameIndex const nStart, TextFrameIndex const nLength,
+ const bool bKern, const bool bWrong,
+ const bool bSmartTag,
+ const bool bGrammarCheck )
+{
+ if( !nLength )
+ return;
+
+ // The SwScriptInfo is useless if we are inside a field portion
+ SwScriptInfo* pSI = nullptr;
+ if ( ! rPor.InFieldGrp() )
+ pSI = &GetParaPortion()->GetScriptInfo();
+
+ // in some cases, kana compression is not allowed or suppressed for
+ // performance reasons
+ sal_uInt16 nComp = 0;
+ if ( ! IsMulti() )
+ nComp = GetKanaComp();
+
+ bool bCfgIsAutoGrammar = false;
+ SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bCfgIsAutoGrammar;
+ const bool bBullet = OnWin() && GetOpt().IsBlank() && IsNoSymbol();
+ const bool bTmpWrong = bWrong && OnWin() && GetOpt().IsOnlineSpell();
+ const bool bTmpGrammarCheck = bGrammarCheck && OnWin() && bCfgIsAutoGrammar && GetOpt().IsOnlineSpell();
+ const bool bTmpSmart = bSmartTag && OnWin() && !GetOpt().IsPagePreview() && SwSmartTagMgr::Get().IsSmartTagsEnabled();
+
+ OSL_ENSURE( GetParaPortion(), "No paragraph!");
+ SwDrawTextInfo aDrawInf( m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart, nLength,
+ rPor.Width(), bBullet );
+
+ aDrawInf.SetUnderFnt( m_pUnderFnt );
+
+ const tools::Long nSpaceAdd = ( rPor.IsBlankPortion() || rPor.IsDropPortion() ||
+ rPor.InNumberGrp() ) ? 0 : GetSpaceAdd(/*bShrink=*/true);
+ if ( nSpaceAdd )
+ {
+ TextFrameIndex nCharCnt(0);
+ // #i41860# Thai justified alignment needs some
+ // additional information:
+ aDrawInf.SetNumberOfBlanks( rPor.InTextGrp() ?
+ static_cast<const SwTextPortion&>(rPor).GetSpaceCnt( *this, nCharCnt ) :
+ TextFrameIndex(0) );
+ }
+
+ aDrawInf.SetSpace( nSpaceAdd );
+ aDrawInf.SetKanaComp( nComp );
+
+ // the font is used to identify the current script via nActual
+ aDrawInf.SetFont( m_pFnt );
+ // the frame is used to identify the orientation
+ aDrawInf.SetFrame( GetTextFrame() );
+ // we have to know if the paragraph should snap to grid
+ aDrawInf.SetSnapToGrid( SnapToGrid() );
+ // for underlining we must know when not to add extra space behind
+ // a character in justified mode
+ aDrawInf.SetSpaceStop( ! rPor.GetNextPortion() ||
+ rPor.GetNextPortion()->InFixMargGrp() ||
+ rPor.GetNextPortion()->IsHolePortion() );
+
+ // Draw text next to the left border
+ Point aFontPos(m_aPos);
+ if( m_pFnt->GetLeftBorder() && rPor.InTextGrp() && !static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev() )
+ {
+ const sal_uInt16 nLeftBorderSpace = m_pFnt->GetLeftBorderSpace();
+ if ( GetTextFrame()->IsRightToLeft() )
+ {
+ aFontPos.AdjustX( -nLeftBorderSpace );
+ }
+ else
+ {
+ switch( m_pFnt->GetOrientation(GetTextFrame()->IsVertical()).get() )
+ {
+ case 0 :
+ aFontPos.AdjustX(nLeftBorderSpace );
+ break;
+ case 900 :
+ aFontPos.AdjustY( -nLeftBorderSpace );
+ break;
+ case 1800 :
+ aFontPos.AdjustX( -nLeftBorderSpace );
+ break;
+ case 2700 :
+ aFontPos.AdjustY(nLeftBorderSpace );
+ break;
+ }
+ }
+ if( aFontPos.X() < 0 )
+ aFontPos.setX( 0 );
+ if( aFontPos.Y() < 0 )
+ aFontPos.setY( 0 );
+ }
+
+ // Handle semi-transparent text if necessary.
+ std::unique_ptr<SwTransparentTextGuard, o3tl::default_delete<SwTransparentTextGuard>> pTransparentText;
+ if (m_pFnt->GetColor() != COL_AUTO && m_pFnt->GetColor().IsTransparent())
+ {
+ // if drawing to a backend that supports transparency for text color, then we don't need to use this
+ if (!m_bOnWin || !m_pOut->SupportsOperation(OutDevSupportType::TransparentText) || m_pOut->GetConnectMetaFile())
+ pTransparentText.reset(new SwTransparentTextGuard(rPor, *this, aDrawInf));
+ }
+
+ if( GetTextFly().IsOn() )
+ {
+ // aPos needs to be the TopLeft, because we cannot calculate the
+ // ClipRects otherwise
+ const Point aPoint( aFontPos.X(), aFontPos.Y() - rPor.GetAscent() );
+ const Size aSize( rPor.Width(), rPor.Height() );
+ aDrawInf.SetPos( aPoint );
+ aDrawInf.SetSize( aSize );
+ aDrawInf.SetAscent( rPor.GetAscent() );
+ aDrawInf.SetKern( bKern ? rPor.Width() : 0 );
+ aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr );
+ aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr );
+ aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr );
+ GetTextFly().DrawTextOpaque( aDrawInf );
+ }
+ else
+ {
+ aDrawInf.SetPos( aFontPos );
+ if( bKern )
+ m_pFnt->DrawStretchText_( aDrawInf );
+ else
+ {
+ aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr );
+ aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr );
+ aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr );
+ m_pFnt->DrawText_( aDrawInf );
+ }
+ }
+}
+
+void SwTextPaintInfo::CalcRect( const SwLinePortion& rPor,
+ SwRect* pRect, SwRect* pIntersect,
+ const bool bInsideBox ) const
+{
+ Size aSize( rPor.Width(), rPor.Height() );
+ if( rPor.IsHangingPortion() )
+ aSize.setWidth( static_cast<const SwHangingPortion&>(rPor).GetInnerWidth() );
+ if( rPor.InSpaceGrp() && GetSpaceAdd() )
+ {
+ SwTwips nAdd = rPor.CalcSpacing( GetSpaceAdd(), *this );
+ if( rPor.InFieldGrp() && GetSpaceAdd() < 0 && nAdd )
+ nAdd += GetSpaceAdd() / SPACING_PRECISION_FACTOR;
+ aSize.AdjustWidth(nAdd );
+ }
+
+ Point aPoint;
+
+ if( IsRotated() )
+ {
+ tools::Long nTmp = aSize.Width();
+ aSize.setWidth( aSize.Height() );
+ aSize.setHeight( nTmp );
+ if ( 1 == GetDirection() )
+ {
+ aPoint.setX( X() - rPor.GetAscent() );
+ aPoint.setY( Y() - aSize.Height() );
+ }
+ else
+ {
+ aPoint.setX( X() - rPor.Height() + rPor.GetAscent() );
+ aPoint.setY( Y() );
+ }
+ }
+ else
+ {
+ aPoint.setX( X() );
+ if (GetTextFrame()->IsVertLR() && !GetTextFrame()->IsVertLRBT())
+ aPoint.setY( Y() - rPor.Height() + rPor.GetAscent() );
+ else
+ aPoint.setY( Y() - rPor.GetAscent() );
+ }
+
+ // Adjust x coordinate if we are inside a bidi portion
+ const bool bFrameDir = GetTextFrame()->IsRightToLeft();
+ const bool bCounterDir = ( !bFrameDir && DIR_RIGHT2LEFT == GetDirection() ) ||
+ ( bFrameDir && DIR_LEFT2RIGHT == GetDirection() );
+
+ if ( bCounterDir )
+ aPoint.AdjustX( -(aSize.Width()) );
+
+ SwRect aRect( aPoint, aSize );
+
+ if ( GetTextFrame()->IsRightToLeft() )
+ GetTextFrame()->SwitchLTRtoRTL( aRect );
+
+ if ( GetTextFrame()->IsVertical() )
+ GetTextFrame()->SwitchHorizontalToVertical( aRect );
+
+ if( bInsideBox && rPor.InTextGrp() )
+ {
+ const bool bJoinWithPrev =
+ static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev();
+ const bool bJoinWithNext =
+ static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithNext();
+ const bool bIsVert = GetTextFrame()->IsVertical();
+ const bool bIsVertLRBT = GetTextFrame()->IsVertLRBT();
+ aRect.AddTop( GetFont()->CalcShadowSpace(SvxShadowItemSide::TOP, bIsVert, bIsVertLRBT,
+ bJoinWithPrev, bJoinWithNext));
+ aRect.AddBottom( - GetFont()->CalcShadowSpace(SvxShadowItemSide::BOTTOM, bIsVert, bIsVertLRBT,
+ bJoinWithPrev, bJoinWithNext));
+ aRect.AddLeft( GetFont()->CalcShadowSpace(SvxShadowItemSide::LEFT, bIsVert, bIsVertLRBT,
+ bJoinWithPrev, bJoinWithNext));
+ aRect.AddRight( - GetFont()->CalcShadowSpace(SvxShadowItemSide::RIGHT, bIsVert, bIsVertLRBT,
+ bJoinWithPrev, bJoinWithNext));
+ }
+
+ if ( pRect )
+ *pRect = aRect;
+
+ if( aRect.HasArea() && pIntersect )
+ {
+ ::SwAlignRect( aRect, GetVsh(), GetOut() );
+
+ if ( GetOut()->IsClipRegion() )
+ {
+ SwRect aClip( GetOut()->GetClipRegion().GetBoundRect() );
+ aRect.Intersection( aClip );
+ }
+
+ *pIntersect = aRect;
+ }
+}
+
+/**
+ * Draws a special portion
+ * E.g.: line break portion, tab portion
+ *
+ * @param rPor The portion
+ * @param rRect The rectangle surrounding the character
+ * @param rCol Specify a color for the character
+ * @param bCenter Draw the character centered, otherwise left aligned
+ * @param bRotate Rotate the character if character rotation is set
+ */
+static void lcl_DrawSpecial( const SwTextPaintInfo& rTextPaintInfo, const SwLinePortion& rPor,
+ SwRect& rRect, const Color& rCol, sal_Unicode cChar,
+ sal_uInt8 nOptions )
+{
+ bool bCenter = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_CENTER );
+ bool bRotate = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_ROTATE );
+
+ // rRect is given in absolute coordinates
+ if ( rTextPaintInfo.GetTextFrame()->IsRightToLeft() )
+ rTextPaintInfo.GetTextFrame()->SwitchRTLtoLTR( rRect );
+ if ( rTextPaintInfo.GetTextFrame()->IsVertical() )
+ rTextPaintInfo.GetTextFrame()->SwitchVerticalToHorizontal( rRect );
+
+ const SwFont* pOldFnt = rTextPaintInfo.GetFont();
+
+ // Font is generated only once:
+ static SwFont s_aFnt = [&]()
+ {
+ SwFont tmp( *pOldFnt );
+ tmp.SetFamily( FAMILY_DONTKNOW, tmp.GetActual() );
+ tmp.SetName( numfunc::GetDefBulletFontname(), tmp.GetActual() );
+ tmp.SetStyleName(OUString(), tmp.GetActual());
+ tmp.SetCharSet( RTL_TEXTENCODING_SYMBOL, tmp.GetActual() );
+ return tmp;
+ }();
+
+ // Some of the current values are set at the font:
+ if ( ! bRotate )
+ s_aFnt.SetVertical( 0_deg10, rTextPaintInfo.GetTextFrame()->IsVertical() );
+ else
+ s_aFnt.SetVertical( pOldFnt->GetOrientation() );
+
+ s_aFnt.SetColor(rCol);
+
+ Size aFontSize( 0, SPECIAL_FONT_HEIGHT );
+ s_aFnt.SetSize( aFontSize, s_aFnt.GetActual() );
+
+ SwTextPaintInfo& rNonConstTextPaintInfo = const_cast<SwTextPaintInfo&>(rTextPaintInfo);
+
+ rNonConstTextPaintInfo.SetFont( &s_aFnt );
+
+ // The maximum width depends on the current orientation
+ const Degree10 nDir = s_aFnt.GetOrientation( rTextPaintInfo.GetTextFrame()->IsVertical() );
+ SwTwips nMaxWidth;
+ if (nDir == 900_deg10 || nDir == 2700_deg10)
+ nMaxWidth = rRect.Height();
+ else
+ {
+ assert(nDir == 0_deg10); //Unknown direction set at font
+ nMaxWidth = rRect.Width();
+ }
+
+ // check if char fits into rectangle
+ const OUString aTmp( cChar );
+ aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize();
+ while ( aFontSize.Width() > nMaxWidth )
+ {
+ SwTwips nFactor = ( 100 * aFontSize.Width() ) / nMaxWidth;
+ const SwTwips nOldWidth = aFontSize.Width();
+
+ // new height for font
+ const SwFontScript nAct = s_aFnt.GetActual();
+ aFontSize.setHeight( ( 100 * s_aFnt.GetSize( nAct ).Height() ) / nFactor );
+ aFontSize.setWidth( ( 100 * s_aFnt.GetSize( nAct).Width() ) / nFactor );
+
+ if ( !aFontSize.Width() && !aFontSize.Height() )
+ break;
+
+ s_aFnt.SetSize( aFontSize, nAct );
+
+ aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize();
+
+ if ( aFontSize.Width() >= nOldWidth )
+ break;
+ }
+
+ const Point aOldPos( rTextPaintInfo.GetPos() );
+
+ // adjust values so that tab is vertically and horizontally centered
+ SwTwips nX = rRect.Left();
+ SwTwips nY = rRect.Top();
+ switch ( nDir.get() )
+ {
+ case 0 :
+ if ( bCenter )
+ nX += ( rRect.Width() - aFontSize.Width() ) / 2;
+ nY += ( rRect.Height() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent();
+ break;
+ case 900 :
+ if ( bCenter )
+ nX += ( rRect.Width() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent();
+ nY += ( rRect.Height() + aFontSize.Width() ) / 2;
+ break;
+ case 2700 :
+ if ( bCenter )
+ nX += ( rRect.Width() + aFontSize.Height() ) / 2 - rTextPaintInfo.GetAscent();
+ nY += ( rRect.Height() - aFontSize.Width() ) / 2;
+ break;
+ }
+
+ Point aTmpPos( nX, nY );
+ rNonConstTextPaintInfo.SetPos( aTmpPos );
+ sal_uInt16 nOldWidth = rPor.Width();
+ const_cast<SwLinePortion&>(rPor).Width( o3tl::narrowing<sal_uInt16>(aFontSize.Width()) );
+ rTextPaintInfo.DrawText( aTmp, rPor );
+ const_cast<SwLinePortion&>(rPor).Width( nOldWidth );
+ rNonConstTextPaintInfo.SetFont( const_cast<SwFont*>(pOldFnt) );
+ rNonConstTextPaintInfo.SetPos( aOldPos );
+}
+
+void SwTextPaintInfo::DrawRect( const SwRect &rRect, bool bRetouche ) const
+{
+ if ( OnWin() || !bRetouche )
+ {
+ if( m_aTextFly.IsOn() )
+ const_cast<SwTextPaintInfo*>(this)->GetTextFly().
+ DrawFlyRect( m_pOut, rRect );
+ else
+ m_pOut->DrawRect( rRect.SVRect() );
+ }
+}
+
+void SwTextPaintInfo::DrawTab( const SwLinePortion &rPor ) const
+{
+ if( !OnWin() )
+ return;
+
+ SwRect aRect;
+ CalcRect( rPor, &aRect );
+
+ if ( ! aRect.HasArea() )
+ return;
+
+ const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ? CHAR_TAB_RTL : CHAR_TAB;
+ const sal_uInt8 nOptions = DRAW_SPECIAL_OPTIONS_CENTER | DRAW_SPECIAL_OPTIONS_ROTATE;
+
+ lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions );
+}
+
+void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const
+{
+ if( !OnWin() )
+ return;
+
+ SwLineBreakClear eClear = SwLineBreakClear::NONE;
+ if (rPor.IsBreakPortion())
+ {
+ const auto& rBreakPortion = static_cast<const SwBreakPortion&>(rPor);
+ eClear = rBreakPortion.GetClear();
+ }
+
+ sal_uInt16 nOldWidth = rPor.Width();
+ const_cast<SwLinePortion&>(rPor).Width( LINE_BREAK_WIDTH );
+
+ SwRect aRect;
+ CalcRect( rPor, &aRect );
+
+ if( aRect.HasArea() )
+ {
+ const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ?
+ CHAR_LINEBREAK_RTL : CHAR_LINEBREAK;
+ const sal_uInt8 nOptions = 0;
+
+ SwRect aTextRect(aRect);
+ if (eClear == SwLineBreakClear::LEFT || eClear == SwLineBreakClear::ALL)
+ aTextRect.AddLeft(30);
+ if (eClear == SwLineBreakClear::RIGHT || eClear == SwLineBreakClear::ALL)
+ aTextRect.AddRight(-30);
+ lcl_DrawSpecial( *this, rPor, aTextRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions );
+
+ if (eClear != SwLineBreakClear::NONE)
+ {
+ // Paint indicator if this clear is left/right/all.
+ m_pOut->Push(vcl::PushFlags::LINECOLOR);
+ m_pOut->SetLineColor(NON_PRINTING_CHARACTER_COLOR);
+ if (eClear != SwLineBreakClear::RIGHT)
+ m_pOut->DrawLine(aRect.BottomLeft(), aRect.TopLeft());
+ if (eClear != SwLineBreakClear::LEFT)
+ m_pOut->DrawLine(aRect.BottomRight(), aRect.TopRight());
+ m_pOut->Pop();
+ }
+ }
+
+ const_cast<SwLinePortion&>(rPor).Width( nOldWidth );
+}
+
+void SwTextPaintInfo::DrawRedArrow( const SwLinePortion &rPor ) const
+{
+ Size aSize( SPECIAL_FONT_HEIGHT, SPECIAL_FONT_HEIGHT );
+ SwRect aRect( static_cast<const SwArrowPortion&>(rPor).GetPos(), aSize );
+ sal_Unicode cChar;
+ if( static_cast<const SwArrowPortion&>(rPor).IsLeft() )
+ {
+ aRect.Pos().AdjustY(20 - GetAscent() );
+ aRect.Pos().AdjustX(20 );
+ if( aSize.Height() > rPor.Height() )
+ aRect.Height( rPor.Height() );
+ cChar = CHAR_LEFT_ARROW;
+ }
+ else
+ {
+ if( aSize.Height() > rPor.Height() )
+ aRect.Height( rPor.Height() );
+ aRect.Pos().AdjustY( -(aRect.Height() + 20) );
+ aRect.Pos().AdjustX( -(aRect.Width() + 20) );
+ cChar = CHAR_RIGHT_ARROW;
+ }
+
+ if ( GetTextFrame()->IsVertical() )
+ GetTextFrame()->SwitchHorizontalToVertical( aRect );
+
+ if( aRect.HasArea() )
+ {
+ const sal_uInt8 nOptions = 0;
+ lcl_DrawSpecial( *this, rPor, aRect, COL_LIGHTRED, cChar, nOptions );
+ }
+}
+
+void SwTextPaintInfo::DrawPostIts( bool bScript ) const
+{
+ if( !OnWin() || !m_pOpt->IsPostIts() )
+ return;
+
+ Size aSize;
+ Point aTmp;
+
+ const sal_uInt16 nPostItsWidth = SwViewOption::GetPostItsWidth( GetOut() );
+ const sal_uInt16 nFontHeight = m_pFnt->GetHeight( m_pVsh, *GetOut() );
+ const sal_uInt16 nFontAscent = m_pFnt->GetAscent( m_pVsh, *GetOut() );
+
+ switch ( m_pFnt->GetOrientation( GetTextFrame()->IsVertical() ).get() )
+ {
+ case 0 :
+ aSize.setWidth( nPostItsWidth );
+ aSize.setHeight( nFontHeight );
+ aTmp.setX( m_aPos.X() );
+ aTmp.setY( m_aPos.Y() - nFontAscent );
+ break;
+ case 900 :
+ aSize.setHeight( nPostItsWidth );
+ aSize.setWidth( nFontHeight );
+ aTmp.setX( m_aPos.X() - nFontAscent );
+ aTmp.setY( m_aPos.Y() );
+ break;
+ case 2700 :
+ aSize.setHeight( nPostItsWidth );
+ aSize.setWidth( nFontHeight );
+ aTmp.setX( m_aPos.X() - nFontHeight +
+ nFontAscent );
+ aTmp.setY( m_aPos.Y() );
+ break;
+ }
+
+ SwRect aTmpRect( aTmp, aSize );
+
+ if ( GetTextFrame()->IsRightToLeft() )
+ GetTextFrame()->SwitchLTRtoRTL( aTmpRect );
+
+ if ( GetTextFrame()->IsVertical() )
+ GetTextFrame()->SwitchHorizontalToVertical( aTmpRect );
+
+ GetOpt().PaintPostIts( const_cast<OutputDevice*>(GetOut()), aTmpRect, bScript );
+
+}
+
+void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const
+{
+ SwRect aIntersect;
+ CalcRect( rPor, &aIntersect );
+ if ( !aIntersect.HasArea() )
+ return;
+
+ if (OnWin() && GetOpt().IsFieldShadings() &&
+ !GetOpt().IsPagePreview())
+ {
+ OutputDevice* pOut = const_cast<OutputDevice*>(GetOut());
+ pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ if( m_pFnt->GetHighlightColor() != COL_TRANSPARENT )
+ pOut->SetFillColor(m_pFnt->GetHighlightColor());
+ else
+ pOut->SetFillColor(GetOpt().GetFieldShadingsColor());
+ pOut->SetLineColor();
+ pOut->DrawRect( aIntersect.SVRect() );
+ pOut->Pop();
+ }
+ const int delta = 25;
+ tools::Rectangle r(aIntersect.Left()+delta, aIntersect.Top()+delta, aIntersect.Right()-delta, aIntersect.Bottom()-delta);
+ m_pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ m_pOut->SetLineColor( Color(0, 0, 0));
+ m_pOut->SetFillColor();
+ m_pOut->DrawRect( r );
+ if (bChecked)
+ {
+ m_pOut->DrawLine(r.TopLeft(), r.BottomRight());
+ m_pOut->DrawLine(r.TopRight(), r.BottomLeft());
+ }
+ m_pOut->Pop();
+}
+
+void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor, const Color *pColor ) const
+{
+ OSL_ENSURE( OnWin(), "SwTextPaintInfo::DrawBackground: printer pollution ?" );
+
+ SwRect aIntersect;
+ CalcRect( rPor, nullptr, &aIntersect, true );
+
+ if ( !aIntersect.HasArea() )
+ return;
+
+ OutputDevice* pOut = const_cast<OutputDevice*>(GetOut());
+ pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+
+ if ( pColor )
+ pOut->SetFillColor( *pColor );
+ else
+ pOut->SetFillColor( GetOpt().GetFieldShadingsColor() );
+
+ pOut->SetLineColor();
+
+ DrawRect( aIntersect, true );
+ pOut->Pop();
+}
+
+void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const
+{
+ {
+ SwRect aIntersect;
+ CalcRect( rPor, &aIntersect, nullptr, true );
+ if(aIntersect.HasArea())
+ {
+ SwPosition const aPosition(m_pFrame->MapViewToModelPos(GetIdx()));
+ const ::sw::mark::IMark* pFieldmark =
+ m_pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
+ bool bIsStartMark = (TextFrameIndex(1) == GetLen()
+ && CH_TXT_ATR_FIELDSTART == GetText()[sal_Int32(GetIdx())]);
+ if(pFieldmark) {
+ SAL_INFO("sw.core", "Found Fieldmark " << pFieldmark->ToString());
+ }
+ if(bIsStartMark)
+ SAL_INFO("sw.core", "Found StartMark");
+ if (OnWin() && (pFieldmark!=nullptr || bIsStartMark) &&
+ GetOpt().IsFieldShadings() &&
+ !GetOpt().IsPagePreview())
+ {
+ OutputDevice* pOutDev = const_cast<OutputDevice*>(GetOut());
+ pOutDev->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ pOutDev->SetFillColor( GetOpt().GetFieldShadingsColor() );
+ pOutDev->SetLineColor( );
+ pOutDev->DrawRect( aIntersect.SVRect() );
+ pOutDev->Pop();
+ }
+ }
+ }
+
+ SwRect aIntersect;
+ CalcRect( rPor, nullptr, &aIntersect, true );
+
+ if ( !aIntersect.HasArea() )
+ return;
+
+ OutputDevice* pTmpOut = const_cast<OutputDevice*>(GetOut());
+
+ // #i16816# tagged pdf support
+ SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pTmpOut );
+
+ Color aFillColor;
+
+ if( m_pFnt->GetHighlightColor() != COL_TRANSPARENT )
+ {
+ aFillColor = m_pFnt->GetHighlightColor();
+ }
+ else
+ {
+ if( !m_pFnt->GetBackColor() )
+ return;
+ aFillColor = *m_pFnt->GetBackColor();
+ }
+
+ pTmpOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+
+ pTmpOut->SetFillColor(aFillColor);
+ pTmpOut->SetLineColor();
+
+ DrawRect( aIntersect, false );
+
+ pTmpOut->Pop();
+}
+
+void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const
+{
+ SwRect aDrawArea;
+ CalcRect( rPor, &aDrawArea );
+ if ( aDrawArea.HasArea() )
+ {
+ PaintCharacterBorder(*m_pFnt, aDrawArea, GetTextFrame()->IsVertical(),
+ GetTextFrame()->IsVertLRBT(), rPor.GetJoinBorderWithPrev(),
+ rPor.GetJoinBorderWithNext());
+ }
+}
+
+namespace {
+
+bool HasValidPropertyValue(const uno::Any& rAny)
+{
+ if (bool bValue; rAny >>= bValue)
+ {
+ return true;
+ }
+ else if (OUString aValue; (rAny >>= aValue) && !(aValue.isEmpty()))
+ {
+ return true;
+ }
+ else if (awt::FontSlant eValue; rAny >>= eValue)
+ {
+ return true;
+ }
+ else if (tools::Long nValueLong; rAny >>= nValueLong)
+ {
+ return true;
+ }
+ else if (double fValue; rAny >>= fValue)
+ {
+ return true;
+ }
+ else if (short nValueShort; rAny >>= nValueShort)
+ {
+ return true;
+ }
+ else
+ return false;
+}
+}
+
+void SwTextPaintInfo::DrawCSDFHighlighting(const SwLinePortion &rPor) const
+{
+ // Don't use GetActiveView() as it does not work as expected when there are multiple open
+ // documents.
+ SwView* pView = SwTextFrame::GetView();
+ if (!pView)
+ return;
+
+ StylesHighlighterColorMap& rCharStylesColorMap = pView->GetStylesHighlighterCharColorMap();
+
+ if (rCharStylesColorMap.empty() && !pView->IsHighlightCharDF())
+ return;
+
+ SwRect aRect;
+ CalcRect(rPor, &aRect, nullptr, true);
+ if(!aRect.HasArea())
+ return;
+
+ SwTextFrame* pFrame = const_cast<SwTextFrame*>(GetTextFrame());
+ if (!pFrame)
+ return;
+
+ SwPosition aPosition(pFrame->MapViewToModelPos(GetIdx()));
+ SwPosition aMarkPosition(pFrame->MapViewToModelPos(GetIdx() + GetLen()));
+
+ rtl::Reference<SwXTextRange> xRange(
+ SwXTextRange::CreateXTextRange(pFrame->GetDoc(), aPosition, &aMarkPosition));
+
+ OUString sCurrentCharStyle;
+ xRange->getPropertyValue("CharStyleName") >>= sCurrentCharStyle;
+
+ std::optional<OUString> sCSNumberOrDF; // CS number or "df" or not used
+ std::optional<Color> aFillColor;
+
+ // check for CS formatting, if not CS formatted check for direct character formatting
+ if (!sCurrentCharStyle.isEmpty())
+ {
+ if (!rCharStylesColorMap.empty())
+ {
+ OUString sCharStyleDisplayName;
+ sCharStyleDisplayName = SwStyleNameMapper::GetUIName(sCurrentCharStyle,
+ SwGetPoolIdFromName::ChrFmt);
+ if (!sCharStyleDisplayName.isEmpty()
+ && rCharStylesColorMap.find(sCharStyleDisplayName)
+ != rCharStylesColorMap.end())
+ {
+ aFillColor = rCharStylesColorMap[sCharStyleDisplayName].first;
+ sCSNumberOrDF = OUString::number(rCharStylesColorMap[sCharStyleDisplayName].second);
+ }
+ }
+ }
+ // not character style formatted
+ else if (pView->IsHighlightCharDF())
+ {
+ const std::vector<OUString> aHiddenProperties{ UNO_NAME_RSID,
+ UNO_NAME_PARA_IS_NUMBERING_RESTART,
+ UNO_NAME_PARA_STYLE_NAME,
+ UNO_NAME_PARA_CONDITIONAL_STYLE_NAME,
+ UNO_NAME_PAGE_STYLE_NAME,
+ UNO_NAME_NUMBERING_START_VALUE,
+ UNO_NAME_NUMBERING_IS_NUMBER,
+ UNO_NAME_PARA_CONTINUEING_PREVIOUS_SUB_TREE,
+ UNO_NAME_CHAR_STYLE_NAME,
+ UNO_NAME_NUMBERING_LEVEL,
+ UNO_NAME_SORTED_TEXT_ID,
+ UNO_NAME_PARRSID,
+ UNO_NAME_CHAR_COLOR_THEME,
+ UNO_NAME_CHAR_COLOR_TINT_OR_SHADE };
+
+ SfxItemPropertySet const& rPropSet(
+ *aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE));
+ SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap());
+
+
+ const uno::Sequence<beans::Property> aProperties
+ = xRange->getPropertySetInfo()->getProperties();
+
+ for (const beans::Property& rProperty : aProperties)
+ {
+ const OUString& rPropName = rProperty.Name;
+
+ if (!rMap.hasPropertyByName(rPropName))
+ continue;
+
+ if (std::find(aHiddenProperties.begin(), aHiddenProperties.end(), rPropName)
+ != aHiddenProperties.end())
+ continue;
+
+ if (xRange->getPropertyState(rPropName) == beans::PropertyState_DIRECT_VALUE)
+ {
+ const uno::Any aAny = xRange->getPropertyValue(rPropName);
+ if (HasValidPropertyValue(aAny))
+ {
+ sCSNumberOrDF = SwResId(STR_CHARACTER_DIRECT_FORMATTING_TAG);
+ aFillColor = COL_LIGHTGRAY;
+ break;
+ }
+ }
+ }
+ }
+ if (sCSNumberOrDF)
+ {
+ OutputDevice* pTmpOut = const_cast<OutputDevice*>(GetOut());
+ pTmpOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR
+ | vcl::PushFlags::TEXTLAYOUTMODE | vcl::PushFlags::FONT);
+
+ // draw a filled rectangle at the formatted CS or DF text
+ pTmpOut->SetFillColor(aFillColor.value());
+ pTmpOut->SetLineColor(aFillColor.value());
+ tools::Rectangle aSVRect(aRect.SVRect());
+ pTmpOut->DrawRect(aSVRect);
+
+ // calculate size and position for the CS number or "df" text and rectangle
+ tools::Long nWidth = pTmpOut->GetTextWidth(sCSNumberOrDF.value());
+ tools::Long nHeight = pTmpOut->GetTextHeight();
+ aSVRect.SetSize(Size(nWidth, nHeight));
+ aSVRect.Move(-(nWidth / 1.5), -(nHeight / 1.5));
+
+ vcl::Font aFont(pTmpOut->GetFont());
+ aFont.SetOrientation(Degree10(0));
+ pTmpOut->SetFont(aFont);
+
+ pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
+ //pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+
+ pTmpOut->SetTextFillColor(aFillColor.value());
+ pTmpOut->DrawText(aSVRect, sCSNumberOrDF.value(), DrawTextFlags::NONE);
+
+ pTmpOut->Pop();
+ }
+}
+
+void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor,
+ PortionType nWhich, const Color *pColor ) const
+{
+ if( !OnWin() || IsMulti() )
+ return;
+
+ bool bDraw = false;
+ switch( nWhich )
+ {
+ case PortionType::Footnote:
+ case PortionType::QuoVadis:
+ case PortionType::Number:
+ case PortionType::Field:
+ case PortionType::Hidden:
+ case PortionType::Tox:
+ case PortionType::Ref:
+ case PortionType::Meta:
+ case PortionType::ContentControl:
+ case PortionType::ControlChar:
+ if ( !GetOpt().IsPagePreview()
+ && !GetOpt().IsReadonly()
+ && GetOpt().IsFieldShadings()
+ && ( PortionType::Number != nWhich
+ || m_pFrame->GetTextNodeForParaProps()->HasMarkedLabel())) // #i27615#
+ {
+ bDraw = PortionType::Footnote != nWhich || m_pFrame->IsFootnoteAllowed();
+ }
+ bDraw &= GetOpt().IsHardBlank();
+ break;
+ case PortionType::Bookmark:
+ // no shading
+ break;
+ case PortionType::InputField:
+ // input field shading also in read-only mode
+ if ( !GetOpt().IsPagePreview()
+ && GetOpt().IsFieldShadings() )
+ {
+ bDraw = true;
+ }
+ break;
+ case PortionType::Tab:
+ if ( GetOpt().IsTab() ) bDraw = true;
+ break;
+ case PortionType::SoftHyphen:
+ if ( GetOpt().IsSoftHyph() )bDraw = true;
+ break;
+ case PortionType::Blank:
+ if ( GetOpt().IsHardBlank())bDraw = true;
+ break;
+ default:
+ {
+ OSL_ENSURE( false, "SwTextPaintInfo::DrawViewOpt: don't know how to draw this" );
+ break;
+ }
+ }
+ if ( bDraw )
+ DrawBackground( rPor, pColor );
+}
+
+static void lcl_InitHyphValues( PropertyValues &rVals,
+ sal_Int16 nMinLeading, sal_Int16 nMinTrailing,
+ bool bNoCapsHyphenation, bool bNoLastWordHyphenation,
+ sal_Int16 nMinWordLength, sal_Int16 nTextHyphZone )
+{
+ sal_Int32 nLen = rVals.getLength();
+
+ if (0 == nLen) // yet to be initialized?
+ {
+ rVals.realloc( 6 );
+ PropertyValue *pVal = rVals.getArray();
+
+ pVal[0].Name = UPN_HYPH_MIN_LEADING;
+ pVal[0].Handle = UPH_HYPH_MIN_LEADING;
+ pVal[0].Value <<= nMinLeading;
+
+ pVal[1].Name = UPN_HYPH_MIN_TRAILING;
+ pVal[1].Handle = UPH_HYPH_MIN_TRAILING;
+ pVal[1].Value <<= nMinTrailing;
+
+ pVal[2].Name = UPN_HYPH_NO_CAPS;
+ pVal[2].Handle = UPH_HYPH_NO_CAPS;
+ pVal[2].Value <<= bNoCapsHyphenation;
+
+ pVal[3].Name = UPN_HYPH_NO_LAST_WORD;
+ pVal[3].Handle = UPH_HYPH_NO_LAST_WORD;
+ pVal[3].Value <<= bNoLastWordHyphenation;
+
+ pVal[4].Name = UPN_HYPH_MIN_WORD_LENGTH;
+ pVal[4].Handle = UPH_HYPH_MIN_WORD_LENGTH;
+ pVal[4].Value <<= nMinWordLength;
+
+ pVal[5].Name = UPN_HYPH_ZONE;
+ pVal[5].Handle = UPH_HYPH_ZONE;
+ pVal[5].Value <<= nTextHyphZone;
+ }
+ else if (6 == nLen) // already initialized once?
+ {
+ PropertyValue *pVal = rVals.getArray();
+ pVal[0].Value <<= nMinLeading;
+ pVal[1].Value <<= nMinTrailing;
+ pVal[2].Value <<= bNoCapsHyphenation;
+ pVal[3].Value <<= bNoLastWordHyphenation;
+ pVal[4].Value <<= nMinWordLength;
+ pVal[5].Value <<= nTextHyphZone;
+ }
+ else {
+ OSL_FAIL( "unexpected size of sequence" );
+ }
+}
+
+const PropertyValues & SwTextFormatInfo::GetHyphValues() const
+{
+ OSL_ENSURE( 6 == m_aHyphVals.getLength(),
+ "hyphenation values not yet initialized" );
+ return m_aHyphVals;
+}
+
+bool SwTextFormatInfo::InitHyph( const bool bAutoHyphen )
+{
+ const SwAttrSet& rAttrSet = GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet();
+ SetHanging( rAttrSet.GetHangingPunctuation().GetValue() );
+ SetScriptSpace( rAttrSet.GetScriptSpace().GetValue() );
+ SetForbiddenChars( rAttrSet.GetForbiddenRule().GetValue() );
+ const SvxHyphenZoneItem &rAttr = rAttrSet.GetHyphenZone();
+ MaxHyph() = rAttr.GetMaxHyphens();
+ const bool bAuto = bAutoHyphen || rAttr.IsHyphen();
+ if( bAuto || m_bInterHyph )
+ {
+ const sal_Int16 nMinimalLeading = std::max(rAttr.GetMinLead(), sal_uInt8(2));
+ const sal_Int16 nMinimalTrailing = rAttr.GetMinTrail();
+ const sal_Int16 nMinimalWordLength = rAttr.GetMinWordLength();
+ const bool bNoCapsHyphenation = rAttr.IsNoCapsHyphenation();
+ const bool bNoLastWordHyphenation = rAttr.IsNoLastWordHyphenation();
+ const sal_Int16 nTextHyphZone = rAttr.GetTextHyphenZone();
+ lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing,
+ bNoCapsHyphenation, bNoLastWordHyphenation,
+ nMinimalWordLength, nTextHyphZone );
+ }
+ return bAuto;
+}
+
+void SwTextFormatInfo::CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwTextFrame *pNewFrame, const bool bNewInterHyph,
+ const bool bNewQuick, const bool bTst )
+{
+ CtorInitTextPaintInfo( pRenderContext, pNewFrame, SwRect() );
+
+ m_bQuick = bNewQuick;
+ m_bInterHyph = bNewInterHyph;
+
+ //! needs to be done in this order
+ m_bAutoHyph = InitHyph();
+
+ m_bIgnoreFly = false;
+ m_bFakeLineStart = false;
+ m_bShift = false;
+ m_bDropInit = false;
+ m_bTestFormat = bTst;
+ m_nLeft = 0;
+ m_nRight = 0;
+ m_nFirst = 0;
+ m_nRealWidth = 0;
+ m_nForcedLeftMargin = 0;
+ m_pRest = nullptr;
+ m_nLineHeight = 0;
+ m_nLineNetHeight = 0;
+ SetLineStart(TextFrameIndex(0));
+
+ SvtCTLOptions::TextNumerals const nTextNumerals(
+ SvtCTLOptions::GetCTLTextNumerals());
+ // cannot cache for NUMERALS_CONTEXT because we need to know the string
+ // for the whole paragraph now
+ if (nTextNumerals != SvtCTLOptions::NUMERALS_CONTEXT)
+ {
+ // set digit mode to what will be used later to get same results
+ SwDigitModeModifier const m(*m_pRef, LANGUAGE_NONE /*dummy*/);
+ assert(m_pRef->GetDigitLanguage() != LANGUAGE_NONE);
+ SetCachedVclData(OutputDevice::CreateTextLayoutCache(*m_pText));
+ }
+
+ Init();
+}
+
+/**
+ * If the Hyphenator returns ERROR or the language is set to NOLANGUAGE
+ * we do not hyphenate.
+ * Else, we always hyphenate if we do interactive hyphenation.
+ * If we do not do interactive hyphenation, we only hyphenate if ParaFormat is
+ * set to automatic hyphenation.
+ */
+bool SwTextFormatInfo::IsHyphenate() const
+{
+ if( !m_bInterHyph && !m_bAutoHyph )
+ return false;
+
+ LanguageType eTmp = GetFont()->GetLanguage();
+ // TODO: check for more ideographic langs w/o hyphenation as a concept
+ if ( LANGUAGE_DONTKNOW == eTmp || LANGUAGE_NONE == eTmp
+ || !MsLangId::usesHyphenation(eTmp) )
+ return false;
+
+ uno::Reference< XHyphenator > xHyph = ::GetHyphenator();
+ if (!xHyph.is())
+ return false;
+
+ if (m_bInterHyph)
+ SvxSpellWrapper::CheckHyphLang( xHyph, eTmp );
+
+ if (!xHyph->hasLocale(g_pBreakIt->GetLocale(eTmp)))
+ {
+ SfxObjectShell* pShell = m_pFrame->GetDoc().GetDocShell();
+ if (pShell)
+ {
+ pShell->AppendInfoBarWhenReady(
+ "hyphenationmissing", SwResId(STR_HYPH_MISSING),
+ SwResId(STR_HYPH_MISSING_DETAIL)
+ .replaceFirst("%1", LanguageTag::convertToBcp47( g_pBreakIt->GetLocale(eTmp))),
+ InfobarType::WARNING);
+ }
+ }
+
+ return xHyph->hasLocale( g_pBreakIt->GetLocale(eTmp) );
+}
+
+const SwFormatDrop *SwTextFormatInfo::GetDropFormat() const
+{
+ const SwFormatDrop *pDrop = &GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetDrop();
+ if( 1 >= pDrop->GetLines() ||
+ ( !pDrop->GetChars() && !pDrop->GetWholeWord() ) )
+ pDrop = nullptr;
+ return pDrop;
+}
+
+void SwTextFormatInfo::Init()
+{
+ // Not initialized: pRest, nLeft, nRight, nFirst, nRealWidth
+ X(0);
+ m_bArrowDone = m_bFull = m_bFootnoteDone = m_bErgoDone = m_bNumDone = m_bNoEndHyph =
+ m_bNoMidHyph = m_bStop = m_bNewLine = m_bUnderflow = m_bTabOverflow = false;
+
+ // generally we do not allow number portions in follows, except...
+ if ( GetTextFrame()->IsFollow() )
+ {
+ const SwTextFrame* pMaster = GetTextFrame()->FindMaster();
+ OSL_ENSURE(pMaster, "pTextFrame without Master");
+ const SwLinePortion* pTmpPara = pMaster ? pMaster->GetPara() : nullptr;
+
+ // there is a master for this follow and the master does not have
+ // any contents (especially it does not have a number portion)
+ m_bNumDone = ! pTmpPara ||
+ ! static_cast<const SwParaPortion*>(pTmpPara)->GetFirstPortion()->IsFlyPortion();
+ }
+
+ m_pRoot = nullptr;
+ m_pLast = nullptr;
+ m_pFly = nullptr;
+ m_pLastTab = nullptr;
+ m_pUnderflow = nullptr;
+ m_cTabDecimal = 0;
+ m_nWidth = m_nRealWidth;
+ m_nForcedLeftMargin = 0;
+ m_nSoftHyphPos = TextFrameIndex(0);
+ m_nUnderScorePos = TextFrameIndex(COMPLETE_STRING);
+ m_nLastBookmarkPos = TextFrameIndex(-1);
+ m_cHookChar = 0;
+ SetIdx(TextFrameIndex(0));
+ SetLen(TextFrameIndex(GetText().getLength()));
+ SetPaintOfst(0);
+}
+
+SwTextFormatInfo::SwTextFormatInfo(OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyphL,
+ const bool bQuickL, const bool bTst)
+{
+ CtorInitTextFormatInfo(pRenderContext, pFrame, bInterHyphL, bQuickL, bTst);
+}
+
+/**
+ * There are a few differences between a copy constructor
+ * and the following constructor for multi-line formatting.
+ * The root is the first line inside the multi-portion,
+ * the line start is the actual position in the text,
+ * the line width is the rest width from the surrounding line
+ * and the bMulti and bFirstMulti-flag has to be set correctly.
+ */
+SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf,
+ SwLineLayout& rLay, SwTwips nActWidth ) :
+ SwTextPaintInfo( rInf ),
+ m_pRoot(&rLay),
+ m_pLast(&rLay),
+ m_pFly(nullptr),
+ m_pUnderflow(nullptr),
+ m_pRest(nullptr),
+ m_pLastTab(nullptr),
+ m_nSoftHyphPos(TextFrameIndex(0)),
+ m_nLineStart(rInf.GetIdx()),
+ m_nUnderScorePos(TextFrameIndex(COMPLETE_STRING)),
+ m_nLeft(rInf.m_nLeft),
+ m_nRight(rInf.m_nRight),
+ m_nFirst(rInf.m_nLeft),
+ m_nRealWidth(sal_uInt16(nActWidth)),
+ m_nWidth(m_nRealWidth),
+ m_nLineHeight(0),
+ m_nLineNetHeight(0),
+ m_nForcedLeftMargin(0),
+ m_bFull(false),
+ m_bFootnoteDone(true),
+ m_bErgoDone(true),
+ m_bNumDone(true),
+ m_bArrowDone(true),
+ m_bStop(false),
+ m_bNewLine(true),
+ m_bShift(false),
+ m_bUnderflow(false),
+ m_bInterHyph(false),
+ m_bAutoHyph(false),
+ m_bDropInit(false),
+ m_bQuick(rInf.m_bQuick),
+ m_bNoEndHyph(false),
+ m_bNoMidHyph(false),
+ m_bIgnoreFly(false),
+ m_bFakeLineStart(false),
+ m_bTabOverflow( false ),
+ m_bTestFormat(rInf.m_bTestFormat),
+ m_cTabDecimal(0),
+ m_cHookChar(0),
+ m_nMaxHyph(0)
+{
+ SetMulti( true );
+ SetFirstMulti( rInf.IsFirstMulti() );
+}
+
+void SwTextFormatInfo::UpdateTabSeen(PortionType type)
+{
+ switch (type)
+ {
+ case PortionType::TabLeft:
+ m_eLastTabsSeen = TabSeen::Left;
+ break;
+ case PortionType::TabRight:
+ m_eLastTabsSeen = TabSeen::Right;
+ break;
+ case PortionType::TabCenter:
+ m_eLastTabsSeen = TabSeen::Center;
+ break;
+ case PortionType::TabDecimal:
+ m_eLastTabsSeen = TabSeen::Decimal;
+ break;
+ case PortionType::Break:
+ m_eLastTabsSeen = TabSeen::None;
+ break;
+ default:
+ break;
+ }
+}
+
+void SwTextFormatInfo::SetLast(SwLinePortion* pNewLast)
+{
+ m_pLast = pNewLast;
+ assert(pNewLast); // We never pass nullptr here. If we start, then a check is needed below.
+ UpdateTabSeen(pNewLast->GetWhichPor());
+}
+
+bool SwTextFormatInfo::CheckFootnotePortion_( SwLineLayout const * pCurr )
+{
+ const sal_uInt16 nHeight = pCurr->GetRealHeight();
+ for( SwLinePortion *pPor = pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion() )
+ {
+ if( pPor->IsFootnotePortion() && nHeight > static_cast<SwFootnotePortion*>(pPor)->Orig() )
+ {
+ SetLineHeight( nHeight );
+ SetLineNetHeight( pCurr->Height() );
+ return true;
+ }
+ }
+ return false;
+}
+
+TextFrameIndex SwTextFormatInfo::ScanPortionEnd(TextFrameIndex const nStart,
+ TextFrameIndex const nEnd)
+{
+ m_cHookChar = 0;
+ TextFrameIndex i = nStart;
+
+ // Used for decimal tab handling:
+ const sal_Unicode cTabDec = GetLastTab() ? GetTabDecimal() : 0;
+ const sal_Unicode cThousandSep = ',' == cTabDec ? '.' : ',';
+
+ // #i45951# German (Switzerland) uses ' as thousand separator
+ const sal_Unicode cThousandSep2 = ',' == cTabDec ? '.' : '\'';
+
+ bool bNumFound = false;
+ const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
+
+ for( ; i < nEnd; ++i )
+ {
+ const sal_Unicode cPos = GetChar( i );
+ switch( cPos )
+ {
+ case CH_TXTATR_BREAKWORD:
+ case CH_TXTATR_INWORD:
+ if( !HasHint( i ))
+ break;
+ [[fallthrough]];
+
+ case CHAR_SOFTHYPHEN:
+ case CHAR_HARDHYPHEN:
+ case CHAR_HARDBLANK:
+ case CH_TAB:
+ case CH_BREAK:
+ case CHAR_ZWSP :
+ case CHAR_WJ :
+ m_cHookChar = cPos;
+ return i;
+
+ case CHAR_UNDERSCORE:
+ if (TextFrameIndex(COMPLETE_STRING) == m_nUnderScorePos)
+ m_nUnderScorePos = i;
+ break;
+
+ default:
+ if ( cTabDec )
+ {
+ if( cTabDec == cPos )
+ {
+ OSL_ENSURE( cPos, "Unexpected end of string" );
+ if( cPos ) // robust
+ {
+ m_cHookChar = cPos;
+ return i;
+ }
+ }
+
+ // Compatibility: First non-digit character behind a
+ // a digit character becomes the hook character
+ if ( bTabCompat )
+ {
+ if ( ( 0x2F < cPos && cPos < 0x3A ) ||
+ ( bNumFound && ( cPos == cThousandSep || cPos == cThousandSep2 ) ) )
+ {
+ bNumFound = true;
+ }
+ else
+ {
+ if ( bNumFound )
+ {
+ m_cHookChar = cPos;
+ SetTabDecimal( cPos );
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Check if character *behind* the portion has
+ // to become the hook:
+ if (i == nEnd && i < TextFrameIndex(GetText().getLength()) && bNumFound)
+ {
+ const sal_Unicode cPos = GetChar( i );
+ if ( cPos != cTabDec && cPos != cThousandSep && cPos !=cThousandSep2 && ( 0x2F >= cPos || cPos >= 0x3A ) )
+ {
+ m_cHookChar = GetChar( i );
+ SetTabDecimal( m_cHookChar );
+ }
+ }
+
+ return i;
+}
+
+bool SwTextFormatInfo::LastKernPortion()
+{
+ if( GetLast() )
+ {
+ if( GetLast()->IsKernPortion() )
+ return true;
+ if( GetLast()->Width() || ( GetLast()->GetLen() &&
+ !GetLast()->IsHolePortion() ) )
+ return false;
+ }
+ SwLinePortion* pPor = GetRoot();
+ SwLinePortion *pKern = nullptr;
+ while( pPor )
+ {
+ if( pPor->IsKernPortion() )
+ pKern = pPor;
+ else if( pPor->Width() || ( pPor->GetLen() && !pPor->IsHolePortion() ) )
+ pKern = nullptr;
+ pPor = pPor->GetNextPortion();
+ }
+ if( pKern )
+ {
+ SetLast( pKern );
+ return true;
+ }
+ return false;
+}
+
+SwTwips SwTextFormatInfo::GetLineWidth()
+{
+ SwTwips nLineWidth = Width() - X();
+
+ const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::TAB_OVER_MARGIN);
+ const bool bTabOverSpacing = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::TAB_OVER_SPACING);
+ if (!bTabOverMargin && !bTabOverSpacing)
+ return nLineWidth;
+
+ SwTabPortion* pLastTab = GetLastTab();
+ if (!pLastTab)
+ return nLineWidth;
+
+ // Consider tab portions over the printing bounds of the text frame.
+ if (pLastTab->GetTabPos() <= Width())
+ return nLineWidth;
+
+ // Calculate the width that starts at the left (or in case of first line:
+ // first) margin, but ends after the right paragraph margin:
+ //
+ // +--------------------+
+ // |LL| |RR|
+ // +--------------------+
+ // ^ m_nLeftMargin (absolute)
+ // ^ nLeftMarginWidth (relative to m_nLeftMargin), X() is relative to this
+ // ^ right margin
+ // ^ paragraph right
+ // <--------------------> is GetTextFrame()->getFrameArea().Width()
+ // <--------------> is Width()
+ // <-----------------> is what we need to be able to compare to X() (nTextFrameWidth)
+ SwTwips nLeftMarginWidth = m_nLeftMargin - GetTextFrame()->getFrameArea().Left();
+ SwTwips nTextFrameWidth = GetTextFrame()->getFrameArea().Width() - nLeftMarginWidth;
+
+ // If there is one such tab portion, then text is allowed to use the full
+ // text frame area to the right (RR above, but not LL).
+ nLineWidth = nTextFrameWidth - X();
+
+ if (!bTabOverMargin) // thus bTabOverSpacing only
+ {
+ // right, center, decimal can back-fill all the available space - same as TabOverMargin
+ if (pLastTab->GetWhichPor() == PortionType::TabLeft)
+ nLineWidth = nTextFrameWidth - pLastTab->GetTabPos();
+ }
+ return nLineWidth;
+}
+
+SwTextSlot::SwTextSlot(
+ const SwTextSizeInfo *pNew,
+ const SwLinePortion *pPor,
+ bool bTextLen,
+ bool bExgLists,
+ OUString const & rCh )
+ : pOldText(nullptr)
+ , m_pOldSmartTagList(nullptr)
+ , m_pOldGrammarCheckList(nullptr)
+ , nIdx(0)
+ , nLen(0)
+ , nMeasureLen(0)
+ , pInf(nullptr)
+{
+ if( rCh.isEmpty() )
+ {
+ bOn = pPor->GetExpText( *pNew, aText );
+ }
+ else
+ {
+ aText = rCh;
+ bOn = true;
+ }
+
+ // The text is replaced ...
+ if( !bOn )
+ return;
+
+ pInf = const_cast<SwTextSizeInfo*>(pNew);
+ nIdx = pInf->GetIdx();
+ nLen = pInf->GetLen();
+ nMeasureLen = pInf->GetMeasureLen();
+ pOldText = &(pInf->GetText());
+ m_pOldCachedVclData = pInf->GetCachedVclData();
+ pInf->SetText( aText );
+ pInf->SetIdx(TextFrameIndex(0));
+ pInf->SetLen(bTextLen ? TextFrameIndex(pInf->GetText().getLength()) : pPor->GetLen());
+ if (nMeasureLen != TextFrameIndex(COMPLETE_STRING))
+ pInf->SetMeasureLen(TextFrameIndex(COMPLETE_STRING));
+
+ pInf->SetCachedVclData(nullptr);
+
+ // ST2
+ if ( !bExgLists )
+ return;
+
+ m_pOldSmartTagList = static_cast<SwTextPaintInfo*>(pInf)->GetSmartTags();
+ if (m_pOldSmartTagList)
+ {
+ std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx));
+ SwWrongList const*const pSmartTags(pos.first->GetSmartTags());
+ if (pSmartTags)
+ {
+ const sal_uInt16 nPos = pSmartTags->GetWrongPos(pos.second);
+ const sal_Int32 nListPos = pSmartTags->Pos(nPos);
+ if (nListPos == pos.second && pSmartTags->SubList(nPos) != nullptr)
+ {
+ m_pTempIter.reset(new sw::WrongListIterator(*pSmartTags->SubList(nPos)));
+ static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get());
+ }
+ else if (!m_pTempList && nPos < pSmartTags->Count()
+ && nListPos < pos.second && !aText.isEmpty())
+ {
+ m_pTempList.reset(new SwWrongList( WRONGLIST_SMARTTAG ));
+ m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 );
+ m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList));
+ static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get());
+ }
+ else
+ static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr);
+ }
+ else
+ static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr);
+ }
+ m_pOldGrammarCheckList = static_cast<SwTextPaintInfo*>(pInf)->GetGrammarCheckList();
+ if (!m_pOldGrammarCheckList)
+ return;
+
+ std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx));
+ SwWrongList const*const pGrammar(pos.first->GetGrammarCheck());
+ if (pGrammar)
+ {
+ const sal_uInt16 nPos = pGrammar->GetWrongPos(pos.second);
+ const sal_Int32 nListPos = pGrammar->Pos(nPos);
+ if (nListPos == pos.second && pGrammar->SubList(nPos) != nullptr)
+ {
+ m_pTempIter.reset(new sw::WrongListIterator(*pGrammar->SubList(nPos)));
+ static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get());
+ }
+ else if (!m_pTempList && nPos < pGrammar->Count()
+ && nListPos < pos.second && !aText.isEmpty())
+ {
+ m_pTempList.reset(new SwWrongList( WRONGLIST_GRAMMAR ));
+ m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 );
+ m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList));
+ static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get());
+ }
+ else
+ static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr);
+ }
+ else
+ static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr);
+}
+
+SwTextSlot::~SwTextSlot()
+{
+ if( !bOn )
+ return;
+
+ pInf->SetCachedVclData(m_pOldCachedVclData);
+ pInf->SetText( *pOldText );
+ pInf->SetIdx( nIdx );
+ pInf->SetLen( nLen );
+ pInf->SetMeasureLen( nMeasureLen );
+
+ // ST2
+ // Restore old smart tag list
+ if (m_pOldSmartTagList)
+ static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pOldSmartTagList);
+ if (m_pOldGrammarCheckList)
+ static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pOldGrammarCheckList);
+}
+
+SwFontSave::SwFontSave(const SwTextSizeInfo &rInf, SwFont *pNew,
+ SwAttrIter* pItr)
+ : pInf(nullptr)
+ , pFnt(pNew ? const_cast<SwTextSizeInfo&>(rInf).GetFont() : nullptr)
+ , pIter(nullptr)
+{
+ if( !pFnt )
+ return;
+
+ pInf = &const_cast<SwTextSizeInfo&>(rInf);
+ // In these cases we temporarily switch to the new font:
+ // 1. the fonts have a different magic number
+ // 2. they have different script types
+ // 3. their background colors differ (this is not covered by 1.)
+ if( pFnt->DifferentFontCacheId( pNew, pFnt->GetActual() ) ||
+ pNew->GetActual() != pFnt->GetActual() ||
+ ( ! pNew->GetBackColor() && pFnt->GetBackColor() ) ||
+ ( pNew->GetBackColor() && ! pFnt->GetBackColor() ) ||
+ ( pNew->GetBackColor() && pFnt->GetBackColor() &&
+ ( *pNew->GetBackColor() != *pFnt->GetBackColor() ) ) )
+ {
+ pNew->SetTransparent( true );
+ pNew->SetAlign( ALIGN_BASELINE );
+ pInf->SetFont( pNew );
+ }
+ else
+ pFnt = nullptr;
+ pNew->Invalidate();
+ pNew->ChgPhysFnt( pInf->GetVsh(), *pInf->GetOut() );
+ if( pItr && pItr->GetFnt() == pFnt )
+ {
+ pIter = pItr;
+ pIter->SetFnt( pNew );
+ }
+}
+
+SwFontSave::~SwFontSave()
+{
+ if( pFnt )
+ {
+ // Reset SwFont
+ pFnt->Invalidate();
+ pInf->SetFont( pFnt );
+ if( pIter )
+ {
+ pIter->SetFnt( pFnt );
+ pIter->m_nPosition = COMPLETE_STRING;
+ }
+ }
+}
+
+bool SwTextFormatInfo::ChgHyph( const bool bNew )
+{
+ const bool bOld = m_bAutoHyph;
+ if( m_bAutoHyph != bNew )
+ {
+ m_bAutoHyph = bNew;
+ InitHyph( bNew );
+ // Set language in the Hyphenator
+ if( m_pFnt )
+ m_pFnt->ChgPhysFnt( m_pVsh, *m_pOut );
+ }
+ return bOld;
+}
+
+
+bool SwTextFormatInfo::CheckCurrentPosBookmark()
+{
+ if (m_nLastBookmarkPos != GetIdx())
+ {
+ m_nLastBookmarkPos = GetIdx();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx
new file mode 100644
index 0000000000..2ddaee7b2e
--- /dev/null
+++ b/sw/source/core/text/inftxt.hxx
@@ -0,0 +1,811 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <com/sun/star/beans/PropertyValues.hpp>
+
+#include <map>
+
+#include <swtypes.hxx>
+#include <swrect.hxx>
+#include <txtfly.hxx>
+#include <swfont.hxx>
+#include "porlay.hxx"
+#include <txtfrm.hxx>
+#include <ndtxt.hxx>
+#include <editeng/paravertalignitem.hxx>
+
+namespace com::sun::star::linguistic2 { class XHyphenatedWord; }
+
+class SvxBrushItem;
+class SvxLineSpacingItem;
+class SvxTabStop;
+class SvxTabStopItem;
+class SwFlyPortion;
+class SwFormatDrop;
+class SwLinePortion;
+class SwTabPortion;
+class SwViewOption;
+class SwViewShell;
+class SwAttrIter;
+struct SwMultiCreator;
+class SwMultiPortion;
+namespace sw { class WrongListIterator; }
+
+#define ARROW_WIDTH 200
+#define DIR_LEFT2RIGHT 0
+#define DIR_BOTTOM2TOP 1
+#define DIR_RIGHT2LEFT 2
+#define DIR_TOP2BOTTOM 3
+
+// Respects the attribute LineSpace when calculating the Height/Ascent
+class SwLineInfo
+{
+ friend class SwTextIter;
+
+ std::optional<SvxTabStopItem> m_oRuler;
+ const SvxLineSpacingItem *m_pSpace;
+ SvxParaVertAlignItem::Align m_nVertAlign;
+ sal_uInt16 m_nDefTabStop;
+ bool m_bListTabStopIncluded;
+ tools::Long m_nListTabStopPosition;
+
+ void CtorInitLineInfo( const SwAttrSet& rAttrSet,
+ const SwTextNode& rTextNode );
+
+ SwLineInfo();
+ ~SwLineInfo();
+public:
+ // #i24363# tab stops relative to indent - returns the tab stop following nSearchPos or NULL
+ const SvxTabStop* GetTabStop(const SwTwips nSearchPos, SwTwips& nRight) const;
+ const SvxLineSpacingItem *GetLineSpacing() const { return m_pSpace; }
+ sal_uInt16 GetDefTabStop() const { return m_nDefTabStop; }
+ void SetDefTabStop( sal_uInt16 nNew ) const
+ { const_cast<SwLineInfo*>(this)->m_nDefTabStop = nNew; }
+
+ // vertical alignment
+ SvxParaVertAlignItem::Align GetVertAlign() const { return m_nVertAlign; }
+ bool HasSpecialAlign( bool bVert ) const
+ { return bVert ?
+ ( SvxParaVertAlignItem::Align::Baseline != m_nVertAlign ) :
+ ( SvxParaVertAlignItem::Align::Baseline != m_nVertAlign &&
+ SvxParaVertAlignItem::Align::Automatic != m_nVertAlign ); }
+
+ sal_uInt16 NumberOfTabStops() const;
+
+ bool IsListTabStopIncluded() const
+ {
+ return m_bListTabStopIncluded;
+ }
+ tools::Long GetListTabStopPosition() const
+ {
+ return m_nListTabStopPosition;
+ }
+};
+
+class SwTextInfo
+{
+ // Implementation in txthyph.cxx
+ friend void SetParaPortion( SwTextInfo *pInf, SwParaPortion *pRoot );
+ SwParaPortion *m_pPara;
+ TextFrameIndex m_nTextStart; // TextOfst for Follows
+
+protected:
+ SwTextInfo()
+ : m_pPara(nullptr)
+ , m_nTextStart(0)
+ {}
+
+public:
+ void CtorInitTextInfo( SwTextFrame *pFrame );
+ SwTextInfo( const SwTextInfo &rInf );
+ explicit SwTextInfo( SwTextFrame *pFrame ) { CtorInitTextInfo( pFrame ); }
+ SwParaPortion *GetParaPortion() { return m_pPara; }
+ const SwParaPortion *GetParaPortion() const { return m_pPara; }
+ TextFrameIndex GetTextStart() const { return m_nTextStart; }
+};
+
+class SwTextSizeInfo : public SwTextInfo
+{
+private:
+ typedef std::map< SwLinePortion const *, sal_uInt16 > SwTextPortionMap;
+
+protected:
+ // during formatting, a small database is built, mapping portion pointers
+ // to their maximum size (used for kana compression)
+ SwTextPortionMap m_aMaxWidth;
+ // for each line, an array of compression values is calculated
+ // this array is passed over to the info structure
+ std::deque<sal_uInt16>* m_pKanaComp;
+
+ SwViewShell *m_pVsh;
+
+ // m_pOut is the output device, m_pRef is the device used for formatting
+ VclPtr<OutputDevice> m_pOut;
+ VclPtr<OutputDevice> m_pRef;
+
+ // performance hack - this is only used by SwTextFormatInfo but
+ // because it's not even possible to dynamic_cast these things
+ // currently it has to be stored here
+ std::shared_ptr<const vcl::text::TextLayoutCache> m_pCachedVclData;
+
+ SwFont *m_pFnt;
+ SwUnderlineFont *m_pUnderFnt; // Font for underlining
+ SwTextFrame *m_pFrame;
+ const SwViewOption *m_pOpt;
+ const OUString *m_pText;
+ TextFrameIndex m_nIdx;
+ TextFrameIndex m_nLen;
+ TextFrameIndex m_nMeasureLen;
+ sal_uInt16 m_nKanaIdx;
+ bool m_bOnWin : 1;
+ bool m_bNotEOL : 1;
+ bool m_bURLNotify : 1;
+ bool m_bStopUnderflow : 1; // Underflow was stopped e.g. by a FlyPortion
+ bool m_bFootnoteInside : 1; // the current line contains a footnote
+ bool m_bOtherThanFootnoteInside : 1; // the current line contains another portion than a footnote portion.
+ // needed for checking keep together of footnote portion with previous portion
+ bool m_bMulti : 1; // inside a multiportion
+ bool m_bFirstMulti : 1; // this flag is used for two purposes:
+ // - the multiportion is the first lineportion
+ // - indicates, if we are currently in second
+ // line of multi portion
+ bool m_bRuby : 1; // during the formatting of a phonetic line
+ bool m_bHanging : 1; // formatting of hanging punctuation allowed
+ bool m_bScriptSpace : 1; // space between different scripts (Asian/Latin)
+ bool m_bForbiddenChars : 1; // Forbidden start/endline characters
+ bool m_bSnapToGrid : 1; // paragraph snaps to grid
+ sal_uInt8 m_nDirection : 2; // writing direction: 0/90/180/270 degree
+
+protected:
+ void CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame,
+ TextFrameIndex nIdx);
+ SwTextSizeInfo();
+public:
+ SwTextSizeInfo( const SwTextSizeInfo &rInf );
+ SwTextSizeInfo( const SwTextSizeInfo &rInf, const OUString* pText,
+ TextFrameIndex nIdx = TextFrameIndex(0) );
+ SwTextSizeInfo(SwTextFrame *pTextFrame, TextFrameIndex nIndex = TextFrameIndex(0));
+
+ // GetMultiAttr returns the text attribute of the multiportion,
+ // if rPos is inside any multi-line part.
+ // rPos will set to the end of the multi-line part.
+ std::optional<SwMultiCreator> GetMultiCreator(TextFrameIndex &rPos, SwMultiPortion const* pM) const;
+
+ bool OnWin() const { return m_bOnWin; }
+ void SetOnWin( const bool bNew ) { m_bOnWin = bNew; }
+ bool NotEOL() const { return m_bNotEOL; }
+ void SetNotEOL( const bool bNew ) { m_bNotEOL = bNew; }
+ bool URLNotify() const { return m_bURLNotify; }
+ bool StopUnderflow() const { return m_bStopUnderflow; }
+ void SetStopUnderflow( const bool bNew ) { m_bStopUnderflow = bNew; }
+ bool IsFootnoteInside() const { return m_bFootnoteInside; }
+ void SetFootnoteInside( const bool bNew ) { m_bFootnoteInside = bNew; }
+ bool IsOtherThanFootnoteInside() const { return m_bOtherThanFootnoteInside; }
+ void SetOtherThanFootnoteInside( const bool bNew ) { m_bOtherThanFootnoteInside = bNew; }
+ bool IsMulti() const { return m_bMulti; }
+ void SetMulti( const bool bNew ) { m_bMulti = bNew; }
+ bool IsFirstMulti() const { return m_bFirstMulti; }
+ void SetFirstMulti( const bool bNew ) { m_bFirstMulti = bNew; }
+ bool IsRuby() const { return m_bRuby; }
+ void SetRuby( const bool bNew ) { m_bRuby = bNew; }
+ bool IsHanging() const { return m_bHanging; }
+ void SetHanging( const bool bNew ) { m_bHanging = bNew; }
+ bool HasScriptSpace() const { return m_bScriptSpace; }
+ void SetScriptSpace( const bool bNew ) { m_bScriptSpace = bNew; }
+ bool HasForbiddenChars() const { return m_bForbiddenChars; }
+ void SetForbiddenChars( const bool bN ) { m_bForbiddenChars = bN; }
+ bool SnapToGrid() const { return m_bSnapToGrid; }
+ void SetSnapToGrid( const bool bN ) { m_bSnapToGrid = bN; }
+ sal_uInt8 GetDirection() const { return m_nDirection; }
+ void SetDirection( const sal_uInt8 nNew ) { m_nDirection = nNew; }
+ bool IsRotated() const { return ( 1 & m_nDirection ); }
+
+ SwViewShell *GetVsh() { return m_pVsh; }
+ const SwViewShell *GetVsh() const { return m_pVsh; }
+
+ vcl::RenderContext *GetOut() { return m_pOut; }
+ const vcl::RenderContext *GetOut() const { return m_pOut; }
+ void SetOut( OutputDevice* pNewOut ) { m_pOut = pNewOut; }
+
+ vcl::RenderContext *GetRefDev() { return m_pRef; }
+ const vcl::RenderContext *GetRefDev() const { return m_pRef; }
+
+ SwFont *GetFont() { return m_pFnt; }
+ const SwFont *GetFont() const { return m_pFnt; }
+ void SetFont( SwFont *pNew ) { m_pFnt = pNew; }
+ void SelectFont();
+ void SetUnderFnt( SwUnderlineFont* pNew ) { m_pUnderFnt = pNew; }
+ SwUnderlineFont* GetUnderFnt() const { return m_pUnderFnt; }
+
+ const SwViewOption &GetOpt() const { return *m_pOpt; }
+ const OUString &GetText() const { return *m_pText; }
+ sal_Unicode GetChar(TextFrameIndex const nPos) const {
+ if (m_pText && nPos < TextFrameIndex(m_pText->getLength())) return (*m_pText)[sal_Int32(nPos)];
+ return 0;
+ }
+
+ sal_uInt16 GetTextHeight() const;
+
+ SwPosSize GetTextSize( OutputDevice* pOut, const SwScriptInfo* pSI,
+ const OUString& rText, TextFrameIndex nIdx,
+ TextFrameIndex nLen ) const;
+ SwPosSize GetTextSize() const;
+ void GetTextSize( const SwScriptInfo* pSI, TextFrameIndex nIdx,
+ TextFrameIndex nLen, const sal_uInt16 nComp,
+ sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff,
+ vcl::text::TextLayoutCache const* = nullptr) const;
+ inline SwPosSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx,
+ TextFrameIndex nLen) const;
+ inline SwPosSize GetTextSize( const OUString &rText ) const;
+
+ TextFrameIndex GetTextBreak( const tools::Long nLineWidth,
+ const TextFrameIndex nMaxLen,
+ const sal_uInt16 nComp,
+ vcl::text::TextLayoutCache const*) const;
+ TextFrameIndex GetTextBreak( const tools::Long nLineWidth,
+ const TextFrameIndex nMaxLen,
+ const sal_uInt16 nComp,
+ TextFrameIndex& rExtraCharPos,
+ vcl::text::TextLayoutCache const*) const;
+
+ sal_uInt16 GetAscent() const;
+ sal_uInt16 GetHangingBaseline() const;
+
+ TextFrameIndex GetIdx() const { return m_nIdx; }
+ void SetIdx(const TextFrameIndex nNew) { m_nIdx = nNew; }
+ TextFrameIndex GetLen() const { return m_nLen; }
+ void SetLen(const TextFrameIndex nNew) { m_nLen = nNew; }
+ TextFrameIndex GetMeasureLen() const { return m_nMeasureLen; }
+ void SetMeasureLen(const TextFrameIndex nNew) { m_nMeasureLen = nNew; }
+ void SetText( const OUString &rNew ){ m_pText = &rNew; }
+
+ // No Bullets for the symbol font!
+ bool IsNoSymbol() const
+ { return RTL_TEXTENCODING_SYMBOL != m_pFnt->GetCharSet( m_pFnt->GetActual() ); }
+
+ void NoteAnimation() const;
+
+ // Home is where Your heart is...
+ SwTextFrame *GetTextFrame() { return m_pFrame; }
+ const SwTextFrame *GetTextFrame() const { return m_pFrame; }
+
+ bool HasHint(TextFrameIndex nPos) const;
+
+ // If Kana Compression is enabled, a minimum and maximum portion width
+ // is calculated. We format lines with minimal size and share remaining
+ // space among compressed kanas.
+ // During formatting, the maximum values of compressible portions are
+ // stored in m_aMaxWidth and discarded after a line has been formatted.
+ void SetMaxWidthDiff( const SwLinePortion *nKey, sal_uInt16 nVal )
+ {
+ m_aMaxWidth.insert( std::make_pair( nKey, nVal ) );
+ };
+ sal_uInt16 GetMaxWidthDiff( const SwLinePortion *nKey )
+ {
+ SwTextPortionMap::iterator it = m_aMaxWidth.find( nKey );
+
+ if( it != m_aMaxWidth.end() )
+ return it->second;
+ else
+ return 0;
+ };
+ void ResetMaxWidthDiff()
+ {
+ m_aMaxWidth.clear();
+ };
+ bool CompressLine()
+ {
+ return !m_aMaxWidth.empty();
+ };
+
+ // Feature: Kana Compression
+
+ sal_uInt16 GetKanaIdx() const { return m_nKanaIdx; }
+ void ResetKanaIdx(){ m_nKanaIdx = 0; }
+ void SetKanaIdx( sal_uInt16 nNew ) { m_nKanaIdx = nNew; }
+ void IncKanaIdx() { ++m_nKanaIdx; }
+ void SetKanaComp( std::deque<sal_uInt16> *pNew ){ m_pKanaComp = pNew; }
+ std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp; }
+ sal_uInt16 GetKanaComp() const
+ { return ( m_pKanaComp && m_nKanaIdx < m_pKanaComp->size() )
+ ? (*m_pKanaComp)[m_nKanaIdx] : 0; }
+
+ const std::shared_ptr<const vcl::text::TextLayoutCache>& GetCachedVclData() const
+ {
+ return m_pCachedVclData;
+ }
+ void SetCachedVclData(std::shared_ptr<const vcl::text::TextLayoutCache> const& pCachedVclData)
+ {
+ m_pCachedVclData = pCachedVclData;
+ }
+};
+
+class SwTextPaintInfo : public SwTextSizeInfo
+{
+ sw::WrongListIterator *m_pWrongList;
+ sw::WrongListIterator *m_pGrammarCheckList;
+ sw::WrongListIterator *m_pSmartTags;
+ std::vector<tools::Long>* m_pSpaceAdd;
+ const SvxBrushItem *m_pBrushItem; // For the background
+ SwTextFly m_aTextFly; // Calculate the FlyFrame
+ Point m_aPos; // Paint position
+ SwRect m_aPaintRect; // Original paint rect (from Layout paint)
+
+ sal_uInt16 m_nSpaceIdx;
+ void DrawText_(const OUString &rText, const SwLinePortion &rPor,
+ const TextFrameIndex nIdx, const TextFrameIndex nLen,
+ const bool bKern, const bool bWrong = false,
+ const bool bSmartTag = false,
+ const bool bGrammarCheck = false );
+
+ SwTextPaintInfo &operator=(const SwTextPaintInfo&) = delete;
+
+protected:
+ SwTextPaintInfo()
+ : m_pWrongList(nullptr)
+ , m_pGrammarCheckList(nullptr)
+ , m_pSmartTags(nullptr)
+ , m_pSpaceAdd(nullptr)
+ , m_pBrushItem(nullptr)
+ , m_nSpaceIdx(0)
+ {}
+
+public:
+ SwTextPaintInfo( const SwTextPaintInfo &rInf );
+ SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* pText );
+
+ void CtorInitTextPaintInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const SwRect &rPaint );
+
+ const SvxBrushItem *GetBrushItem() const { return m_pBrushItem; }
+
+ SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint );
+
+ SwTwips X() const { return m_aPos.X(); }
+ void X( const tools::Long nNew ) { m_aPos.setX(nNew); }
+ SwTwips Y() const { return m_aPos.Y(); }
+ void Y( const SwTwips nNew ) { m_aPos.setY(nNew); }
+
+ SwTextFly& GetTextFly() { return m_aTextFly; }
+ const SwTextFly& GetTextFly() const { return m_aTextFly; }
+ inline void DrawText( const OUString &rText, const SwLinePortion &rPor,
+ TextFrameIndex nIdx = TextFrameIndex(0),
+ TextFrameIndex nLen = TextFrameIndex(COMPLETE_STRING),
+ const bool bKern = false) const;
+ inline void DrawText( const SwLinePortion &rPor, TextFrameIndex nLen,
+ const bool bKern = false ) const;
+ inline void DrawMarkedText( const SwLinePortion &rPor, TextFrameIndex nLen,
+ const bool bWrong,
+ const bool bSmartTags,
+ const bool bGrammarCheck ) const;
+
+ void DrawRect( const SwRect &rRect, bool bRetouche ) const;
+
+ void DrawTab( const SwLinePortion &rPor ) const;
+ void DrawLineBreak( const SwLinePortion &rPor ) const;
+ void DrawRedArrow( const SwLinePortion &rPor ) const;
+ void DrawPostIts( bool bScript ) const;
+ void DrawBackground( const SwLinePortion &rPor, const Color *pColor=nullptr ) const;
+ void DrawViewOpt( const SwLinePortion &rPor, PortionType nWhich, const Color *pColor=nullptr ) const;
+ void DrawBackBrush( const SwLinePortion &rPor ) const;
+
+ /**
+ * Draw character border around a line portion.
+ *
+ * @param[in] rPor line portion around which border have to be drawn.
+ **/
+ void DrawBorder( const SwLinePortion &rPor ) const;
+
+ void DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const;
+
+ void DrawCSDFHighlighting(const SwLinePortion &rPor) const;
+
+ /**
+ * Calculate the rectangular area where the portion takes place.
+ * @param[in] rPor portion for which the method specify the painting area
+ * @param[out] pRect whole area of the portion
+ * @param[out] pIntersect part of the portion area clipped by OutputDevice's clip region
+ * @param[in] bInsideBox area of portion's content, padding and border, but shadow
+ * is excluded (e.g. for background)
+ **/
+ void CalcRect( const SwLinePortion& rPor, SwRect* pRect,
+ SwRect* pIntersect = nullptr, const bool bInsideBox = false ) const;
+
+ inline SwTwips GetPaintOfst() const;
+ inline void SetPaintOfst( const SwTwips nNew );
+ const Point &GetPos() const { return m_aPos; }
+ void SetPos( const Point &rNew ) { m_aPos = rNew; }
+
+ const SwRect &GetPaintRect() const { return m_aPaintRect; }
+
+ // STUFF FOR JUSTIFIED ALIGNMENT
+
+ sal_uInt16 GetSpaceIdx() const { return m_nSpaceIdx; }
+ void ResetSpaceIdx(){m_nSpaceIdx = 0; }
+ void SetSpaceIdx( sal_uInt16 nNew ) { m_nSpaceIdx = nNew; }
+ void IncSpaceIdx() { ++m_nSpaceIdx; }
+ void RemoveFirstSpaceAdd() { m_pSpaceAdd->erase( m_pSpaceAdd->begin() ); }
+ tools::Long GetSpaceAdd( bool bShrink = false ) const
+ { return ( m_pSpaceAdd && m_nSpaceIdx < m_pSpaceAdd->size() &&
+ // get shrink data only if asked explicitly, otherwise zero it
+ ( bShrink || (*m_pSpaceAdd)[m_nSpaceIdx] < LONG_MAX/2 ) )
+ ? (*m_pSpaceAdd)[m_nSpaceIdx] : 0; }
+
+ void SetpSpaceAdd( std::vector<tools::Long>* pNew ){ m_pSpaceAdd = pNew; }
+ std::vector<tools::Long>* GetpSpaceAdd() const { return m_pSpaceAdd; }
+
+ void SetWrongList(sw::WrongListIterator *const pNew) { m_pWrongList = pNew; }
+ sw::WrongListIterator* GetpWrongList() const { return m_pWrongList; }
+
+ void SetGrammarCheckList(sw::WrongListIterator *const pNew) { m_pGrammarCheckList = pNew; }
+ sw::WrongListIterator* GetGrammarCheckList() const { return m_pGrammarCheckList; }
+
+ void SetSmartTags(sw::WrongListIterator *const pNew) { m_pSmartTags = pNew; }
+ sw::WrongListIterator* GetSmartTags() const { return m_pSmartTags; }
+};
+
+class SwTextFormatInfo : public SwTextPaintInfo
+{
+ // temporary arguments for hyphenation
+ css::beans::PropertyValues m_aHyphVals;
+
+ SwLineLayout *m_pRoot; // The Root of the current line (pCurr)
+ SwLinePortion *m_pLast; // The last Portion
+ SwFlyPortion *m_pFly; // The following FlyPortion
+ SwLinePortion *m_pUnderflow; // Underflow: Last Portion
+ SwLinePortion *m_pRest; // The Rest is the start of the next Line
+
+ SwTabPortion *m_pLastTab; // The _last_ TabPortion
+
+ TextFrameIndex m_nSoftHyphPos; ///< SoftHyphPos for Hyphenation
+ TextFrameIndex m_nLineStart; ///< Current line start in rText
+ TextFrameIndex m_nUnderScorePos; ///< enlarge repaint if underscore has been found
+ TextFrameIndex m_nLastBookmarkPos; ///< need to check for bookmarks at every portion
+ // #i34348# Changed type from sal_uInt16 to SwTwips
+ SwTwips m_nLeft; // Left margin
+ SwTwips m_nRight; // Right margin
+ SwTwips m_nFirst; // EZE
+ /// First or left margin, depending on context.
+ SwTwips m_nLeftMargin = 0;
+ sal_uInt16 m_nRealWidth; // "real" line width
+ sal_uInt16 m_nWidth; // "virtual" line width
+ sal_uInt16 m_nLineHeight; // Final height after CalcLine
+ sal_uInt16 m_nLineNetHeight; // line height without spacing
+ sal_uInt16 m_nForcedLeftMargin; // Shift of left margin due to frame
+
+ bool m_bFull : 1; // Line is full
+ bool m_bFootnoteDone : 1; // Footnote already formatted
+ bool m_bErgoDone : 1; // ErgoDone already formatted
+ bool m_bNumDone : 1; // bNumDone already formatted
+ bool m_bArrowDone : 1; // Arrow to the left for scrolling paragraphs
+ bool m_bStop : 1; // Cancel immediately, discarding the line
+ bool m_bNewLine : 1; // Format another line
+ bool m_bShift : 1; // Position change: Repaint until further notice
+ bool m_bUnderflow : 1; // Context: Underflow() ?
+ bool m_bInterHyph : 1; // Interactive hyphenation?
+ bool m_bAutoHyph : 1; // Automatic hyphenation?
+ bool m_bDropInit : 1; // Set DropWidth
+ bool m_bQuick : 1; // FormatQuick()
+ bool m_bNoEndHyph : 1; // Switch off hyphenation at the line end (due to MaxHyphens)
+ bool m_bNoMidHyph : 1; // Switch off hyphenation before flys (due to MaxHyphens)
+ bool m_bIgnoreFly : 1; // FitToContent ignores flys
+ bool m_bFakeLineStart : 1; // String has been replaced by field portion
+ // info structure only pretends that we are at
+ // the beginning of a line
+ bool m_bTabOverflow : 1; // Tabs are expanding after the end margin
+ bool m_bTestFormat : 1; // Test formatting from WouldFit, no notification etc.
+
+ sal_Unicode m_cTabDecimal; // the current decimal delimiter
+ sal_Unicode m_cHookChar; // For tabs in fields etc.
+ sal_uInt8 m_nMaxHyph; // Max. line count of followup hyphenations
+
+ // Used to stop justification after center/right/decimal tab stops - see tdf#tdf#106234
+ enum class TabSeen
+ {
+ None,
+ Left,
+ Center,
+ Right,
+ Decimal,
+ } m_eLastTabsSeen = TabSeen::None;
+
+ // Hyphenating ...
+ bool InitHyph( const bool bAuto = false );
+ bool CheckFootnotePortion_( SwLineLayout const * pCurr );
+
+public:
+ void CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyph = false,
+ const bool bQuick = false, const bool bTst = false );
+ SwTextFormatInfo(OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyphL = false,
+ const bool bQuickL = false, const bool bTst = false);
+
+ // For the formatting inside a double line in a line (multi-line portion)
+ // we need a modified text-format-info:
+ SwTextFormatInfo( const SwTextFormatInfo& rInf, SwLineLayout& rLay,
+ SwTwips nActWidth );
+
+ sal_uInt16 Width() const { return m_nWidth; }
+ void Width( const sal_uInt16 nNew ) { m_nWidth = nNew; }
+ void Init();
+
+ /**
+ * Returns the distance between the current horizontal position and the end
+ * of the line.
+ */
+ SwTwips GetLineWidth();
+
+ // Returns the first changed position of the paragraph
+ inline TextFrameIndex GetReformatStart() const;
+
+ // Margins
+ SwTwips Left() const { return m_nLeft; }
+ void Left( const SwTwips nNew ) { m_nLeft = nNew; }
+ SwTwips Right() const { return m_nRight; }
+ void Right( const SwTwips nNew ) { m_nRight = nNew; }
+ SwTwips First() const { return m_nFirst; }
+ void First( const SwTwips nNew ) { m_nFirst = nNew; }
+ void LeftMargin( const SwTwips nNew) { m_nLeftMargin = nNew; }
+ sal_uInt16 RealWidth() const { return m_nRealWidth; }
+ void RealWidth( const sal_uInt16 nNew ) { m_nRealWidth = nNew; }
+ sal_uInt16 ForcedLeftMargin() const { return m_nForcedLeftMargin; }
+ void ForcedLeftMargin( const sal_uInt16 nN ) { m_nForcedLeftMargin = nN; }
+
+ sal_uInt8 &MaxHyph() { return m_nMaxHyph; }
+ const sal_uInt8 &MaxHyph() const { return m_nMaxHyph; }
+
+ SwLineLayout *GetRoot() { return m_pRoot; }
+ const SwLineLayout *GetRoot() const { return m_pRoot; }
+
+ void SetRoot( SwLineLayout *pNew ) { m_pRoot = pNew; }
+ SwLinePortion *GetLast() { return m_pLast; }
+ void SetLast(SwLinePortion* pNewLast);
+ bool IsFull() const { return m_bFull; }
+ void SetFull( const bool bNew ) { m_bFull = bNew; }
+ bool IsHyphForbud() const
+ { return m_pFly ? m_bNoMidHyph : m_bNoEndHyph; }
+ void ChkNoHyph( const sal_uInt8 bEnd, const sal_uInt8 bMid )
+ { m_bNoEndHyph = (m_nMaxHyph && bEnd >= m_nMaxHyph);
+ m_bNoMidHyph = (m_nMaxHyph && bMid >= m_nMaxHyph); }
+ bool IsIgnoreFly() const { return m_bIgnoreFly; }
+ void SetIgnoreFly( const bool bNew ) { m_bIgnoreFly = bNew; }
+ bool IsFakeLineStart() const { return m_bFakeLineStart; }
+ void SetFakeLineStart( const bool bNew ) { m_bFakeLineStart = bNew; }
+ bool IsStop() const { return m_bStop; }
+ void SetStop( const bool bNew ) { m_bStop = bNew; }
+ SwLinePortion *GetRest() { return m_pRest; }
+ void SetRest( SwLinePortion *pNewRest ) { m_pRest = pNewRest; }
+ bool IsNewLine() const { return m_bNewLine; }
+ void SetNewLine( const bool bNew ) { m_bNewLine = bNew; }
+ bool IsShift() const { return m_bShift; }
+ void SetShift( const bool bNew ) { m_bShift = bNew; }
+ bool IsInterHyph() const { return m_bInterHyph; }
+ bool IsUnderflow() const { return m_bUnderflow; }
+ void ClrUnderflow() { m_bUnderflow = false; }
+ bool IsDropInit() const { return m_bDropInit; }
+ void SetDropInit( const bool bNew ) { m_bDropInit = bNew; }
+ bool IsQuick() const { return m_bQuick; }
+ bool IsTest() const { return m_bTestFormat; }
+ // see tdf#106234
+ void UpdateTabSeen(PortionType);
+ bool DontBlockJustify() const
+ {
+ return m_eLastTabsSeen == TabSeen::Center || m_eLastTabsSeen == TabSeen::Right
+ || m_eLastTabsSeen == TabSeen::Decimal;
+ }
+
+ TextFrameIndex GetLineStart() const { return m_nLineStart; }
+ void SetLineStart(TextFrameIndex const nNew) { m_nLineStart = nNew; }
+
+ // these are used during fly calculation
+ sal_uInt16 GetLineHeight() const { return m_nLineHeight; }
+ void SetLineHeight( const sal_uInt16 nNew ) { m_nLineHeight = nNew; }
+ sal_uInt16 GetLineNetHeight() const { return m_nLineNetHeight; }
+ void SetLineNetHeight( const sal_uInt16 nNew ) { m_nLineNetHeight = nNew; }
+
+ const SwLinePortion *GetUnderflow() const { return m_pUnderflow; }
+ SwLinePortion *GetUnderflow() { return m_pUnderflow; }
+ void SetUnderflow( SwLinePortion *pNew )
+ { m_pUnderflow = pNew; m_bUnderflow = true; }
+ TextFrameIndex GetSoftHyphPos() const { return m_nSoftHyphPos; }
+ void SetSoftHyphPos(TextFrameIndex const nNew) { m_nSoftHyphPos = nNew; }
+
+ inline void SetParaFootnote();
+
+ // FlyFrames
+ SwFlyPortion *GetFly() { return m_pFly; }
+ void SetFly( SwFlyPortion *pNew ) { m_pFly = pNew; }
+
+ inline const SwAttrSet& GetCharAttr() const;
+
+ // Tabs
+ SwTabPortion *GetLastTab() { return m_pLastTab; }
+ void SetLastTab( SwTabPortion *pNew ) { m_pLastTab = pNew; }
+ sal_Unicode GetTabDecimal() const { return m_cTabDecimal; }
+ void SetTabDecimal( const sal_Unicode cNew ) { m_cTabDecimal = cNew;}
+
+ void ClearHookChar() { m_cHookChar = 0; }
+ void SetHookChar( const sal_Unicode cNew ) { m_cHookChar = cNew; }
+ sal_Unicode GetHookChar() const { return m_cHookChar; }
+
+ // Done-Flags
+ bool IsFootnoteDone() const { return m_bFootnoteDone; }
+ void SetFootnoteDone( const bool bNew ) { m_bFootnoteDone = bNew; }
+ bool IsErgoDone() const { return m_bErgoDone; }
+ void SetErgoDone( const bool bNew ) { m_bErgoDone = bNew; }
+ bool IsNumDone() const { return m_bNumDone; }
+ void SetNumDone( const bool bNew ) { m_bNumDone = bNew; }
+ bool IsArrowDone() const { return m_bArrowDone; }
+ void SetArrowDone( const bool bNew ) { m_bArrowDone = bNew; }
+
+ bool CheckCurrentPosBookmark();
+
+ // For SwTextPortion::Hyphenate
+ bool ChgHyph( const bool bNew );
+
+ // Should the hyphenate helper be discarded?
+ bool IsHyphenate() const;
+ TextFrameIndex GetUnderScorePos() const { return m_nUnderScorePos; }
+ void SetUnderScorePos(TextFrameIndex const nNew) { m_nUnderScorePos = nNew; }
+
+ // Calls HyphenateWord() of Hyphenator
+ css::uno::Reference< css::linguistic2::XHyphenatedWord >
+ HyphWord( const OUString &rText, const sal_Int32 nMinTrail );
+ const css::beans::PropertyValues & GetHyphValues() const;
+
+ bool CheckFootnotePortion( SwLineLayout const * pCurr )
+ { return IsFootnoteInside() && CheckFootnotePortion_( pCurr ); }
+
+ // Dropcaps called by SwTextFormatter::CTOR
+ const SwFormatDrop *GetDropFormat() const;
+
+ // Sets the last SwKernPortion as pLast, if it is followed by empty portions
+ bool LastKernPortion();
+
+ // Looks for tabs, TabDec, TXTATR and BRK from nIdx until nEnd.
+ // Return: Position; sets cHookChar if necessary
+ TextFrameIndex ScanPortionEnd(TextFrameIndex nStart, TextFrameIndex nEnd);
+
+ void SetTabOverflow( bool bOverflow ) { m_bTabOverflow = bOverflow; }
+ bool IsTabOverflow() const { return m_bTabOverflow; }
+
+};
+
+/**
+ * For the text replacement and restoration of SwTextSizeInfo.
+ * The way this is done is a bit of a hack: Although rInf is const we change it
+ * anyway.
+ * Because rInf is restored again in the DTOR, we can do this.
+ * You could call it a "logical const", if you wish.
+ */
+class SwTextSlot final
+{
+ OUString aText;
+ std::shared_ptr<const vcl::text::TextLayoutCache> m_pOldCachedVclData;
+ const OUString *pOldText;
+ sw::WrongListIterator * m_pOldSmartTagList;
+ sw::WrongListIterator * m_pOldGrammarCheckList;
+ std::unique_ptr<SwWrongList> m_pTempList;
+ std::unique_ptr<sw::WrongListIterator> m_pTempIter;
+ TextFrameIndex nIdx;
+ TextFrameIndex nLen;
+ TextFrameIndex nMeasureLen;
+ bool bOn;
+ SwTextSizeInfo *pInf;
+
+public:
+ // The replacement string originates either from the portion via GetExpText()
+ // or from the rCh, if it is not empty.
+ SwTextSlot( const SwTextSizeInfo *pNew, const SwLinePortion *pPor, bool bTextLen,
+ bool bExgLists, OUString const & rCh = OUString() );
+ ~SwTextSlot();
+};
+
+class SwFontSave
+{
+ SwTextSizeInfo *pInf;
+ SwFont *pFnt;
+ SwAttrIter *pIter;
+public:
+ SwFontSave( const SwTextSizeInfo &rInf, SwFont *pFnt,
+ SwAttrIter* pItr = nullptr );
+ ~SwFontSave();
+};
+
+inline sal_uInt16 SwTextSizeInfo::GetAscent() const
+{
+ assert(GetOut());
+ return const_cast<SwFont*>(GetFont())->GetAscent( m_pVsh, *GetOut() );
+}
+
+inline sal_uInt16 SwTextSizeInfo::GetTextHeight() const
+{
+ assert(GetOut());
+ return const_cast<SwFont*>(GetFont())->GetHeight( m_pVsh, *GetOut() );
+}
+
+inline sal_uInt16 SwTextSizeInfo::GetHangingBaseline() const
+{
+ assert(GetOut());
+ return const_cast<SwFont*>(GetFont())->GetHangingBaseline( m_pVsh, *GetOut() );
+}
+
+inline SwPosSize SwTextSizeInfo::GetTextSize( const OUString &rText ) const
+{
+ return GetTextSize(m_pOut, nullptr, rText, TextFrameIndex(0), TextFrameIndex(rText.getLength()));
+}
+
+inline SwPosSize SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI,
+ TextFrameIndex const nNewIdx,
+ TextFrameIndex const nNewLen) const
+{
+ return GetTextSize( m_pOut, pSI, *m_pText, nNewIdx, nNewLen );
+}
+
+inline SwTwips SwTextPaintInfo::GetPaintOfst() const
+{
+ return GetParaPortion()->GetRepaint().GetOffset();
+}
+
+inline void SwTextPaintInfo::SetPaintOfst( const SwTwips nNew )
+{
+ GetParaPortion()->GetRepaint().SetOffset( nNew );
+}
+
+inline void SwTextPaintInfo::DrawText( const OUString &rText,
+ const SwLinePortion &rPor,
+ const TextFrameIndex nStart, const TextFrameIndex nLength,
+ const bool bKern ) const
+{
+ const_cast<SwTextPaintInfo*>(this)->DrawText_( rText, rPor, nStart, nLength, bKern );
+}
+
+inline void SwTextPaintInfo::DrawText( const SwLinePortion &rPor,
+ const TextFrameIndex nLength, const bool bKern ) const
+{
+ const_cast<SwTextPaintInfo*>(this)->DrawText_( *m_pText, rPor, m_nIdx, nLength, bKern );
+}
+
+inline void SwTextPaintInfo::DrawMarkedText( const SwLinePortion &rPor,
+ const TextFrameIndex nLength,
+ const bool bWrong,
+ const bool bSmartTags,
+ const bool bGrammarCheck ) const
+{
+ const_cast<SwTextPaintInfo*>(this)->DrawText_( *m_pText, rPor, m_nIdx, nLength, false/*bKern*/, bWrong, bSmartTags, bGrammarCheck );
+}
+
+inline TextFrameIndex SwTextFormatInfo::GetReformatStart() const
+{
+ return GetParaPortion()->GetReformat().Start();
+}
+
+inline const SwAttrSet& SwTextFormatInfo::GetCharAttr() const
+{
+ // sw_redlinehide: this is used for numbering/footnote number portions, so:
+ return GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet();
+}
+
+inline void SwTextFormatInfo::SetParaFootnote()
+{
+ GetTextFrame()->SetFootnote( true );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
new file mode 100644
index 0000000000..4dcaf03df1
--- /dev/null
+++ b/sw/source/core/text/itradj.cxx
@@ -0,0 +1,852 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+
+#include <IDocumentSettingAccess.hxx>
+#include <doc.hxx>
+
+#include "itrtxt.hxx"
+#include "porglue.hxx"
+#include "porlay.hxx"
+#include "porfly.hxx"
+#include "pormulti.hxx"
+#include "portab.hxx"
+#include <memory>
+
+#define MIN_TAB_WIDTH 60
+
+using namespace ::com::sun::star;
+
+void SwTextAdjuster::FormatBlock( )
+{
+ // Block format does not apply to the last line.
+ // And for tabs it doesn't exist out of tradition
+ // If we have Flys we continue.
+
+ const SwLinePortion *pFly = nullptr;
+
+ bool bSkip = !IsLastBlock() &&
+ m_nStart + m_pCurr->GetLen() >= TextFrameIndex(GetInfo().GetText().getLength());
+
+ // Multi-line fields are tricky, because we need to check whether there are
+ // any other text portions in the paragraph.
+ if( bSkip )
+ {
+ const SwLineLayout *pLay = m_pCurr->GetNext();
+ while( pLay && !pLay->GetLen() )
+ {
+ const SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ while( pPor && bSkip )
+ {
+ if( pPor->InTextGrp() )
+ bSkip = false;
+ pPor = pPor->GetNextPortion();
+ }
+ pLay = bSkip ? pLay->GetNext() : nullptr;
+ }
+ }
+
+ if( bSkip )
+ {
+ if( !GetInfo().GetParaPortion()->HasFly() )
+ {
+ if( IsLastCenter() )
+ CalcFlyAdjust( m_pCurr );
+ m_pCurr->FinishSpaceAdd();
+ return;
+ }
+ else
+ {
+ const SwLinePortion *pTmpFly = nullptr;
+
+ // End at the last Fly
+ const SwLinePortion *pPos = m_pCurr->GetFirstPortion();
+ while( pPos )
+ {
+ // Look for the last Fly which has text coming after it:
+ if( pPos->IsFlyPortion() )
+ pTmpFly = pPos; // Found a Fly
+ else if ( pTmpFly && pPos->InTextGrp() )
+ {
+ pFly = pTmpFly; // A Fly with follow-up text!
+ pTmpFly = nullptr;
+ }
+ pPos = pPos->GetNextPortion();
+ }
+ // End if we didn't find one
+ if( !pFly )
+ {
+ if( IsLastCenter() )
+ CalcFlyAdjust( m_pCurr );
+ m_pCurr->FinishSpaceAdd();
+ return;
+ }
+ }
+ }
+
+ const TextFrameIndex nOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx( m_nStart );
+ CalcNewBlock( m_pCurr, pFly );
+ GetInfo().SetIdx( nOldIdx );
+ GetInfo().GetParaPortion()->GetRepaint().SetOffset(0);
+}
+
+static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr,
+ sal_Int32& rKashidas, TextFrameIndex& nGluePortion)
+{
+ // i60594 validate Kashida justification
+ TextFrameIndex nIdx = rItr.GetStart();
+ TextFrameIndex nEnd = rItr.GetEnd();
+
+ // Note on calling KashidaJustify():
+ // Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
+ // total number of kashida positions, or the number of kashida positions after some positions
+ // have been dropped.
+ // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before.
+ rKashidas = rSI.KashidaJustify(nullptr, nullptr, rItr.GetStart(), rItr.GetLength());
+
+ if (rKashidas <= 0) // nothing to do
+ return true;
+
+ // kashida positions found in SwScriptInfo are not necessarily valid in every font
+ // if two characters are replaced by a ligature glyph, there will be no place for a kashida
+ std::vector<TextFrameIndex> aKashidaPos;
+ rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aKashidaPos);
+ assert(aKashidaPos.size() >= o3tl::make_unsigned(rKashidas));
+ std::vector<TextFrameIndex> aKashidaPosDropped(aKashidaPos.size());
+ sal_Int32 nKashidaIdx = 0;
+ while ( rKashidas && nIdx < nEnd )
+ {
+ rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() );
+ TextFrameIndex nNext = rItr.GetNextAttr();
+
+ // is there also a script change before?
+ // if there is, nNext should point to the script change
+ TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
+ if( nNextScript < nNext )
+ nNext = nNextScript;
+
+ if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
+ nNext = nEnd;
+ sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
+ if (nKashidasInAttr > 0)
+ {
+ // Kashida glyph looks suspicious, skip Kashida justification
+ if ( rInf.GetOut()->GetMinKashida() <= 0 )
+ {
+ return false;
+ }
+
+ sal_Int32 nKashidasDropped = 0;
+ if ( !SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) )
+ {
+ nKashidasDropped = nKashidasInAttr;
+ rKashidas -= nKashidasDropped;
+ }
+ else
+ {
+ vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode();
+ rInf.GetOut()->SetLayoutMode ( nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
+ nKashidasDropped = rInf.GetOut()->ValidateKashidas(
+ rInf.GetText(), sal_Int32(nIdx), sal_Int32(nNext - nIdx),
+ nKashidasInAttr,
+ reinterpret_cast<sal_Int32*>(aKashidaPos.data() + nKashidaIdx),
+ reinterpret_cast<sal_Int32*>(aKashidaPosDropped.data()));
+ rInf.GetOut()->SetLayoutMode ( nOldLayout );
+ if ( nKashidasDropped )
+ {
+ rSI.MarkKashidasInvalid(nKashidasDropped, aKashidaPosDropped.data());
+ rKashidas -= nKashidasDropped;
+ nGluePortion -= TextFrameIndex(nKashidasDropped);
+ }
+ }
+ nKashidaIdx += nKashidasInAttr;
+ }
+ nIdx = nNext;
+ }
+
+ // return false if all kashidas have been eliminated
+ return (rKashidas > 0);
+}
+
+static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr, sal_Int32& rKashidas,
+ TextFrameIndex& nGluePortion, const tools::Long nGluePortionWidth, tools::Long& nSpaceAdd )
+{
+ // check kashida width
+ // if width is smaller than minimal kashida width allowed by fonts in the current line
+ // drop one kashida after the other until kashida width is OK
+ while (rKashidas)
+ {
+ bool bAddSpaceChanged = false;
+ TextFrameIndex nIdx = rItr.GetStart();
+ TextFrameIndex nEnd = rItr.GetEnd();
+ while ( nIdx < nEnd )
+ {
+ rItr.SeekAndChgAttrIter( nIdx, rInf.GetOut() );
+ TextFrameIndex nNext = rItr.GetNextAttr();
+
+ // is there also a script change before?
+ // if there is, nNext should point to the script change
+ TextFrameIndex const nNextScript = rSI.NextScriptChg( nIdx );
+ if( nNextScript < nNext )
+ nNext = nNextScript;
+
+ if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
+ nNext = nEnd;
+ sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
+
+ tools::Long nFontMinKashida = rInf.GetOut()->GetMinKashida();
+ if ( nFontMinKashida && nKashidasInAttr > 0 && SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) )
+ {
+ sal_Int32 nKashidasDropped = 0;
+ while ( rKashidas && nGluePortion && nKashidasInAttr > 0 &&
+ nSpaceAdd / SPACING_PRECISION_FACTOR < nFontMinKashida )
+ {
+ --nGluePortion;
+ --rKashidas;
+ --nKashidasInAttr;
+ ++nKashidasDropped;
+ if( !rKashidas || !nGluePortion ) // nothing left, return false to
+ return false; // do regular blank justification
+
+ nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
+ bAddSpaceChanged = true;
+ }
+ if( nKashidasDropped )
+ rSI.MarkKashidasInvalid( nKashidasDropped, nIdx, nNext - nIdx );
+ }
+ if ( bAddSpaceChanged )
+ break; // start all over again
+ nIdx = nNext;
+ }
+ if ( !bAddSpaceChanged )
+ break; // everything was OK
+ }
+ return true;
+}
+
+// CalcNewBlock() must only be called _after_ CalcLine()!
+// We always span between two RandPortions or FixPortions (Tabs and Flys).
+// We count the Glues and call ExpandBlock.
+void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
+ const SwLinePortion *pStopAt, SwTwips nReal, bool bSkipKashida )
+{
+ OSL_ENSURE( GetInfo().IsMulti() || SvxAdjust::Block == GetAdjust(),
+ "CalcNewBlock: Why?" );
+ OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
+
+ pCurrent->InitSpaceAdd();
+ TextFrameIndex nGluePortion(0);
+ TextFrameIndex nCharCnt(0);
+ sal_uInt16 nSpaceIdx = 0;
+
+ // i60591: hennerdrews
+ SwScriptInfo& rSI = GetInfo().GetParaPortion()->GetScriptInfo();
+ SwTextSizeInfo aInf ( GetTextFrame() );
+ SwTextIter aItr ( GetTextFrame(), &aInf );
+
+ if ( rSI.CountKashida() )
+ {
+ while (aItr.GetCurr() != pCurrent && aItr.GetNext())
+ aItr.Next();
+
+ if( bSkipKashida )
+ {
+ rSI.SetNoKashidaLine ( aItr.GetStart(), aItr.GetLength());
+ }
+ else
+ {
+ rSI.ClearKashidaInvalid ( aItr.GetStart(), aItr.GetLength() );
+ rSI.ClearNoKashidaLine( aItr.GetStart(), aItr.GetLength() );
+ }
+ }
+
+ // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
+ if (!bSkipKashida)
+ CalcRightMargin( pCurrent, nReal );
+
+ // #i49277#
+ const bool bDoNotJustifyLinesWithManualBreak =
+ GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::DO_NOT_JUSTIFY_LINES_WITH_MANUAL_BREAK);
+ bool bDoNotJustifyTab = false;
+
+ SwLinePortion *pPos = pCurrent->GetNextPortion();
+ // calculate real text width for shrinking
+ tools::Long nBreakWidth = 0;
+
+ while( pPos )
+ {
+ if ( ( bDoNotJustifyLinesWithManualBreak || bDoNotJustifyTab ) &&
+ pPos->IsBreakPortion() && !IsLastBlock() )
+ {
+ pCurrent->FinishSpaceAdd();
+ break;
+ }
+
+ switch ( pPos->GetWhichPor() )
+ {
+ case PortionType::TabCenter :
+ case PortionType::TabRight :
+ case PortionType::TabDecimal :
+ bDoNotJustifyTab = true;
+ break;
+ case PortionType::TabLeft :
+ case PortionType::Break:
+ bDoNotJustifyTab = false;
+ break;
+ default: break;
+ }
+
+ if ( pPos->InTextGrp() )
+ nGluePortion = nGluePortion + static_cast<SwTextPortion*>(pPos)->GetSpaceCnt( GetInfo(), nCharCnt );
+ else if( pPos->IsMultiPortion() )
+ {
+ SwMultiPortion* pMulti = static_cast<SwMultiPortion*>(pPos);
+ // a multiportion with a tabulator inside breaks the text adjustment
+ // a ruby portion will not be stretched by text adjustment
+ // a double line portion takes additional space for each blank
+ // in the wider line
+ if( pMulti->HasTabulator() )
+ {
+ if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
+ pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
+
+ nSpaceIdx++;
+ nGluePortion = TextFrameIndex(0);
+ nCharCnt = TextFrameIndex(0);
+ }
+ else if( pMulti->IsDouble() )
+ nGluePortion = nGluePortion + static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
+ else if ( pMulti->IsBidi() )
+ nGluePortion = nGluePortion + static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt( GetInfo() ); // i60594
+ }
+
+ if( pPos->InGlueGrp() )
+ {
+ if( pPos->InFixMargGrp() )
+ {
+ if ( nSpaceIdx == pCurrent->GetLLSpaceAddCount() )
+ pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
+
+ const tools::Long nGluePortionWidth = static_cast<SwGluePortion*>(pPos)->GetPrtGlue() *
+ SPACING_PRECISION_FACTOR;
+
+ sal_Int32 nKashidas = 0;
+ if( nGluePortion && rSI.CountKashida() && !bSkipKashida )
+ {
+ // kashida positions found in SwScriptInfo are not necessarily valid in every font
+ // if two characters are replaced by a ligature glyph, there will be no place for a kashida
+ if ( !lcl_CheckKashidaPositions ( rSI, aInf, aItr, nKashidas, nGluePortion ))
+ {
+ // all kashida positions are invalid
+ // do regular blank justification
+ pCurrent->FinishSpaceAdd();
+ GetInfo().SetIdx( m_nStart );
+ CalcNewBlock( pCurrent, pStopAt, nReal, true );
+ return;
+ }
+ }
+
+ if( nGluePortion )
+ {
+ tools::Long nSpaceAdd = nGluePortionWidth / sal_Int32(nGluePortion);
+ // shrink, if portions exceed the line width
+ tools::Long nSpaceSub = ( nBreakWidth > pCurrent->Width() )
+ ? (nBreakWidth - pCurrent->Width()) * SPACING_PRECISION_FACTOR /
+ sal_Int32(nGluePortion) + LONG_MAX/2
+ : 0;
+
+ // i60594
+ if( rSI.CountKashida() && !bSkipKashida )
+ {
+ if( !lcl_CheckKashidaWidth( rSI, aInf, aItr, nKashidas, nGluePortion, nGluePortionWidth, nSpaceAdd ))
+ {
+ // no kashidas left
+ // do regular blank justification
+ pCurrent->FinishSpaceAdd();
+ GetInfo().SetIdx( m_nStart );
+ CalcNewBlock( pCurrent, pStopAt, nReal, true );
+ return;
+ }
+ }
+
+ pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : nSpaceAdd, nSpaceIdx );
+ pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
+ }
+ else if (IsOneBlock() && nCharCnt > TextFrameIndex(1))
+ {
+ const tools::Long nSpaceAdd = - nGluePortionWidth / (sal_Int32(nCharCnt) - 1);
+ pCurrent->SetLLSpaceAdd( nSpaceAdd, nSpaceIdx );
+ pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() );
+ }
+
+ nSpaceIdx++;
+ nGluePortion = TextFrameIndex(0);
+ nCharCnt = TextFrameIndex(0);
+ }
+ else
+ ++nGluePortion;
+ }
+ else
+ {
+ nBreakWidth += pPos->Width();
+ }
+ GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() );
+ if ( pPos == pStopAt )
+ {
+ pCurrent->SetLLSpaceAdd( 0, nSpaceIdx );
+ break;
+ }
+ pPos = pPos->GetNextPortion();
+ }
+}
+
+SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent )
+{
+ OSL_ENSURE( pCurrent->Height(), "SwTextAdjuster::CalcBlockAdjust: missing CalcLine()" );
+ OSL_ENSURE( !pCurrent->GetpKanaComp(), "pKanaComp already exists!!" );
+
+ pCurrent->SetKanaComp( std::make_unique<std::deque<sal_uInt16>>() );
+
+ const sal_uInt16 nNull = 0;
+ size_t nKanaIdx = 0;
+ tools::Long nKanaDiffSum = 0;
+ SwTwips nRepaintOfst = 0;
+ SwTwips nX = 0;
+ bool bNoCompression = false;
+
+ // Do not forget: CalcRightMargin() sets pCurrent->Width() to the line width!
+ CalcRightMargin( pCurrent );
+
+ SwLinePortion* pPos = pCurrent->GetNextPortion();
+
+ while( pPos )
+ {
+ if ( pPos->InTextGrp() )
+ {
+ // get maximum portion width from info structure, calculated
+ // during text formatting
+ sal_uInt16 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos );
+
+ // check, if information is stored under other key
+ if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() )
+ nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent );
+
+ // calculate difference between portion width and max. width
+ nKanaDiffSum += nMaxWidthDiff;
+
+ // we store the beginning of the first compressible portion
+ // for repaint
+ if ( nMaxWidthDiff && !nRepaintOfst )
+ nRepaintOfst = nX + GetLeftMargin();
+ }
+ else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
+ {
+ if ( nKanaIdx == pCurrent->GetKanaComp().size() )
+ pCurrent->GetKanaComp().push_back( nNull );
+
+ tools::Long nRest;
+
+ if ( pPos->InTabGrp() )
+ {
+ nRest = ! bNoCompression &&
+ ( pPos->Width() > MIN_TAB_WIDTH ) ?
+ pPos->Width() - MIN_TAB_WIDTH :
+ 0;
+
+ // for simplifying the handling of left, right ... tabs,
+ // we do expand portions, which are lying behind
+ // those special tabs
+ bNoCompression = !pPos->IsTabLeftPortion();
+ }
+ else
+ {
+ nRest = ! bNoCompression ?
+ static_cast<SwGluePortion*>(pPos)->GetPrtGlue() :
+ 0;
+
+ bNoCompression = false;
+ }
+
+ if( nKanaDiffSum )
+ {
+ sal_uLong nCompress = ( 10000 * nRest ) / nKanaDiffSum;
+
+ if ( nCompress >= 10000 )
+ // kanas can be expanded to 100%, and there is still
+ // some space remaining
+ nCompress = 0;
+
+ else
+ nCompress = 10000 - nCompress;
+
+ ( pCurrent->GetKanaComp() )[ nKanaIdx ] = o3tl::narrowing<sal_uInt16>(nCompress);
+ nKanaDiffSum = 0;
+ }
+
+ nKanaIdx++;
+ }
+
+ nX += pPos->Width();
+ pPos = pPos->GetNextPortion();
+ }
+
+ // set portion width
+ nKanaIdx = 0;
+ sal_uInt16 nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
+ pPos = pCurrent->GetNextPortion();
+ tools::Long nDecompress = 0;
+
+ while( pPos )
+ {
+ if ( pPos->InTextGrp() )
+ {
+ const SwTwips nMinWidth = pPos->Width();
+
+ // get maximum portion width from info structure, calculated
+ // during text formatting
+ SwTwips nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos );
+
+ // check, if information is stored under other key
+ if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() )
+ nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pCurrent );
+ pPos->Width( nMinWidth +
+ ( ( 10000 - nCompress ) * nMaxWidthDiff ) / 10000 );
+ nDecompress += pPos->Width() - nMinWidth;
+ }
+ else if( pPos->InGlueGrp() && pPos->InFixMargGrp() )
+ {
+ pPos->Width(pPos->Width() - nDecompress);
+
+ if ( pPos->InTabGrp() )
+ // set fix width to width
+ static_cast<SwTabPortion*>(pPos)->SetFixWidth( pPos->Width() );
+
+ if ( ++nKanaIdx < pCurrent->GetKanaComp().size() )
+ nCompress = ( pCurrent->GetKanaComp() )[ nKanaIdx ];
+
+ nDecompress = 0;
+ }
+ pPos = pPos->GetNextPortion();
+ }
+
+ return nRepaintOfst;
+}
+
+SwMarginPortion *SwTextAdjuster::CalcRightMargin( SwLineLayout *pCurrent,
+ SwTwips nReal )
+{
+ tools::Long nRealWidth;
+ const sal_uInt16 nRealHeight = GetLineHeight();
+ const sal_uInt16 nLineHeight = pCurrent->Height();
+
+ sal_uInt16 nPrtWidth = pCurrent->PrtWidth();
+ SwLinePortion *pLast = pCurrent->FindLastPortion();
+
+ if( GetInfo().IsMulti() )
+ nRealWidth = nReal;
+ else
+ {
+ nRealWidth = GetLineWidth();
+ // For each FlyFrame extending into the right margin, we create a FlyPortion.
+ const tools::Long nLeftMar = GetLeftMargin();
+ SwRect aCurrRect( nLeftMar + nPrtWidth, Y() + nRealHeight - nLineHeight,
+ nRealWidth - nPrtWidth, nLineHeight );
+
+ SwFlyPortion *pFly = CalcFlyPortion( nRealWidth, aCurrRect );
+ while( pFly && tools::Long( nPrtWidth )< nRealWidth )
+ {
+ pLast->Append( pFly );
+ pLast = pFly;
+ if( pFly->GetFix() > nPrtWidth )
+ pFly->Width( ( pFly->GetFix() - nPrtWidth) + pFly->Width() + 1);
+ nPrtWidth += pFly->Width() + 1;
+ aCurrRect.Left( nLeftMar + nPrtWidth );
+ pFly = CalcFlyPortion( nRealWidth, aCurrRect );
+ }
+ delete pFly;
+ }
+
+ SwMarginPortion *pRight = new SwMarginPortion;
+ pLast->Append( pRight );
+
+ if( tools::Long( nPrtWidth )< nRealWidth )
+ pRight->PrtWidth( sal_uInt16( nRealWidth - nPrtWidth ) );
+
+ // pCurrent->Width() is set to the real size, because we attach the
+ // MarginPortions.
+ // This trick gives miraculous results:
+ // If pCurrent->Width() == nRealWidth, then the adjustment gets overruled
+ // implicitly. GetLeftMarginAdjust() and IsJustified() think they have a
+ // line filled with chars.
+
+ pCurrent->PrtWidth( sal_uInt16( nRealWidth ) );
+ return pRight;
+}
+
+void SwTextAdjuster::CalcFlyAdjust( SwLineLayout *pCurrent )
+{
+ // 1) We insert a left margin:
+ SwMarginPortion *pLeft = pCurrent->CalcLeftMargin();
+ SwGluePortion *pGlue = pLeft; // the last GluePortion
+
+ // 2) We attach a right margin:
+ // CalcRightMargin also calculates a possible overlap with FlyFrames.
+ CalcRightMargin( pCurrent );
+
+ SwLinePortion *pPos = pLeft->GetNextPortion();
+ TextFrameIndex nLen(0);
+
+ // If we only have one line, the text portion is consecutive and we center, then ...
+ bool bComplete = TextFrameIndex(0) == m_nStart;
+ const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
+ bool bMultiTab = false;
+
+ while( pPos )
+ {
+ if ( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasTabulator() )
+ bMultiTab = true;
+ else if( pPos->InFixMargGrp() &&
+ ( bTabCompat ? ! pPos->InTabGrp() : ! bMultiTab ) )
+ {
+ // in tab compat mode we do not want to change tab portions
+ // in non tab compat mode we do not want to change margins if we
+ // found a multi portion with tabs
+ if( SvxAdjust::Right == GetAdjust() )
+ static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue );
+ else
+ {
+ // We set the first text portion to right-aligned and the last one
+ // to left-aligned.
+ // The first text portion gets the whole Glue, but only if we have
+ // more than one line.
+ if (bComplete && TextFrameIndex(GetInfo().GetText().getLength()) == nLen)
+ static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
+ else
+ {
+ if ( ! bTabCompat )
+ {
+ if( pLeft == pGlue )
+ {
+ // If we only have a left and right margin, the
+ // margins share the Glue.
+ if( nLen + pPos->GetLen() >= pCurrent->GetLen() )
+ static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
+ else
+ static_cast<SwGluePortion*>(pPos)->MoveAllGlue( pGlue );
+ }
+ else
+ {
+ // The last text portion retains its Glue.
+ if( !pPos->IsMarginPortion() )
+ static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
+ }
+ }
+ else
+ static_cast<SwGluePortion*>(pPos)->MoveHalfGlue( pGlue );
+ }
+ }
+
+ pGlue = static_cast<SwGluePortion*>(pPos);
+ bComplete = false;
+ }
+ nLen = nLen + pPos->GetLen();
+ pPos = pPos->GetNextPortion();
+ }
+
+ if( ! bTabCompat && ! bMultiTab && SvxAdjust::Right == GetAdjust() )
+ // portions are moved to the right if possible
+ pLeft->AdjustRight( pCurrent );
+}
+
+void SwTextAdjuster::CalcAdjLine( SwLineLayout *pCurrent )
+{
+ OSL_ENSURE( pCurrent->IsFormatAdj(), "CalcAdjLine: Why?" );
+
+ pCurrent->SetFormatAdj(false);
+
+ SwParaPortion* pPara = GetInfo().GetParaPortion();
+
+ switch( GetAdjust() )
+ {
+ case SvxAdjust::Right:
+ case SvxAdjust::Center:
+ {
+ CalcFlyAdjust( pCurrent );
+ pPara->GetRepaint().SetOffset( 0 );
+ break;
+ }
+ case SvxAdjust::Block:
+ {
+ FormatBlock();
+ break;
+ }
+ default : return;
+ }
+}
+
+// This is a quite complicated calculation: nCurrWidth is the width _before_
+// adding the word, that still fits onto the line! For this reason the FlyPortion's
+// width is still correct if we get a deadlock-situation of:
+// bFirstWord && !WORDFITS
+SwFlyPortion *SwTextAdjuster::CalcFlyPortion( const tools::Long nRealWidth,
+ const SwRect &rCurrRect )
+{
+ SwTextFly aTextFly( GetTextFrame() );
+
+ const sal_uInt16 nCurrWidth = m_pCurr->PrtWidth();
+ SwFlyPortion *pFlyPortion = nullptr;
+
+ SwRect aLineVert( rCurrRect );
+ if ( GetTextFrame()->IsRightToLeft() )
+ GetTextFrame()->SwitchLTRtoRTL( aLineVert );
+ if ( GetTextFrame()->IsVertical() )
+ GetTextFrame()->SwitchHorizontalToVertical( aLineVert );
+
+ // aFlyRect is document-global!
+ SwRect aFlyRect( aTextFly.GetFrame( aLineVert ) );
+
+ if ( GetTextFrame()->IsRightToLeft() )
+ GetTextFrame()->SwitchRTLtoLTR( aFlyRect );
+ if ( GetTextFrame()->IsVertical() )
+ GetTextFrame()->SwitchVerticalToHorizontal( aFlyRect );
+
+ // If a Frame overlapps we open a Portion
+ if( aFlyRect.HasArea() )
+ {
+ // aLocal is frame-local
+ SwRect aLocal( aFlyRect );
+ aLocal.Pos( aLocal.Left() - GetLeftMargin(), aLocal.Top() );
+ if( nCurrWidth > aLocal.Left() )
+ aLocal.Left( nCurrWidth );
+
+ // If the rect is wider than the line, we adjust it to the right size
+ const tools::Long nLocalWidth = aLocal.Left() + aLocal.Width();
+ if( nRealWidth < nLocalWidth )
+ aLocal.Width( nRealWidth - aLocal.Left() );
+ GetInfo().GetParaPortion()->SetFly();
+ pFlyPortion = new SwFlyPortion( aLocal );
+ pFlyPortion->Height( sal_uInt16( rCurrRect.Height() ) );
+ // The Width could be smaller than the FixWidth, thus:
+ pFlyPortion->AdjFixWidth();
+ }
+ return pFlyPortion;
+}
+
+// CalcDropAdjust is called at the end by Format() if needed
+void SwTextAdjuster::CalcDropAdjust()
+{
+ OSL_ENSURE( 1<GetDropLines() && SvxAdjust::Left!=GetAdjust() && SvxAdjust::Block!=GetAdjust(),
+ "CalcDropAdjust: No reason for DropAdjustment." );
+
+ const sal_Int32 nLineNumber = GetLineNr();
+
+ // 1) Skip dummies
+ Top();
+
+ if( !m_pCurr->IsDummy() || NextLine() )
+ {
+ // Adjust first
+ GetAdjusted();
+
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+
+ // 2) Make sure we include the ropPortion
+ // 3) pLeft is the GluePor preceding the DropPor
+ if( pPor->InGlueGrp() && pPor->GetNextPortion()
+ && pPor->GetNextPortion()->IsDropPortion() )
+ {
+ const SwLinePortion *pDropPor = pPor->GetNextPortion();
+ SwGluePortion *pLeft = static_cast<SwGluePortion*>( pPor );
+
+ // 4) pRight: Find the GluePor coming after the DropPor
+ pPor = pPor->GetNextPortion();
+ while( pPor && !pPor->InFixMargGrp() )
+ pPor = pPor->GetNextPortion();
+
+ SwGluePortion *pRight = ( pPor && pPor->InGlueGrp() ) ?
+ static_cast<SwGluePortion*>(pPor) : nullptr;
+ if( pRight && pRight != pLeft )
+ {
+ // 5) Calculate nMinLeft. Who is the most to left?
+ const auto nDropLineStart =
+ GetLineStart() + pLeft->Width() + pDropPor->Width();
+ auto nMinLeft = nDropLineStart;
+ for( sal_Int32 i = 1; i < GetDropLines(); ++i )
+ {
+ if( NextLine() )
+ {
+ // Adjust first
+ GetAdjusted();
+
+ pPor = m_pCurr->GetFirstPortion();
+ const SwMarginPortion *pMar = pPor->IsMarginPortion() ?
+ static_cast<SwMarginPortion*>(pPor) : nullptr;
+ if( !pMar )
+ nMinLeft = 0;
+ else
+ {
+ const auto nLineStart =
+ GetLineStart() + pMar->Width();
+ if( nMinLeft > nLineStart )
+ nMinLeft = nLineStart;
+ }
+ }
+ }
+
+ // 6) Distribute the Glue anew between pLeft and pRight
+ if( nMinLeft < nDropLineStart )
+ {
+ // The Glue is always passed from pLeft to pRight, so that
+ // the text moves to the left.
+ const auto nGlue = nDropLineStart - nMinLeft;
+ if( !nMinLeft )
+ pLeft->MoveAllGlue( pRight );
+ else
+ pLeft->MoveGlue( pRight, nGlue );
+ }
+ }
+ }
+ }
+
+ if( nLineNumber != GetLineNr() )
+ {
+ Top();
+ while( nLineNumber != GetLineNr() && Next() )
+ ;
+ }
+}
+
+void SwTextAdjuster::CalcDropRepaint()
+{
+ Top();
+ SwRepaint &rRepaint = GetInfo().GetParaPortion()->GetRepaint();
+ if( rRepaint.Top() > Y() )
+ rRepaint.Top( Y() );
+ for( sal_Int32 i = 1; i < GetDropLines(); ++i )
+ NextLine();
+ const SwTwips nBottom = Y() + GetLineHeight() - 1;
+ if( rRepaint.Bottom() < nBottom )
+ rRepaint.Bottom( nBottom );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
new file mode 100644
index 0000000000..24203ecb53
--- /dev/null
+++ b/sw/source/core/text/itratr.cxx
@@ -0,0 +1,1632 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <algorithm>
+
+#include <hintids.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <svl/itemiter.hxx>
+#include <svx/svdobj.hxx>
+#include <vcl/svapp.hxx>
+#include <fmtanchr.hxx>
+#include <fmtfsize.hxx>
+#include <fmtornt.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtcntnt.hxx>
+#include <fmtftn.hxx>
+#include <frmatr.hxx>
+#include <frmfmt.hxx>
+#include <fmtfld.hxx>
+#include <doc.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <txatbase.hxx>
+#include <viewsh.hxx>
+#include <rootfrm.hxx>
+#include <docary.hxx>
+#include <ndtxt.hxx>
+#include <fldbas.hxx>
+#include <pam.hxx>
+#include "itratr.hxx"
+#include <htmltbl.hxx>
+#include <swtable.hxx>
+#include "redlnitr.hxx"
+#include <redline.hxx>
+#include <fmtsrnd.hxx>
+#include "itrtxt.hxx"
+#include <breakit.hxx>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <editeng/lrspitem.hxx>
+#include <calbck.hxx>
+#include <frameformats.hxx>
+#include <sortedobjs.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrm.hxx>
+#include <flyfrms.hxx>
+
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star;
+
+static sal_Int32 GetNextAttrImpl(SwTextNode const* pTextNode,
+ size_t nStartIndex, size_t nEndIndex, sal_Int32 nPosition);
+
+SwAttrIter::SwAttrIter(SwTextNode const * pTextNode)
+ : m_pViewShell(nullptr)
+ , m_pFont(nullptr)
+ , m_pScriptInfo(nullptr)
+ , m_pLastOut(nullptr)
+ , m_nChgCnt(0)
+ , m_nStartIndex(0)
+ , m_nEndIndex(0)
+ , m_nPosition(0)
+ , m_nPropFont(0)
+ , m_pTextNode(pTextNode)
+ , m_pMergedPara(nullptr)
+{
+ m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
+}
+
+SwAttrIter::SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame)
+ : m_pViewShell(nullptr)
+ , m_pFont(nullptr)
+ , m_pScriptInfo(nullptr)
+ , m_pLastOut(nullptr)
+ , m_nChgCnt(0)
+ , m_nPropFont(0)
+ , m_pTextNode(&rTextNode)
+ , m_pMergedPara(nullptr)
+{
+ CtorInitAttrIter(rTextNode, rScrInf, pFrame);
+}
+
+void SwAttrIter::Chg( SwTextAttr const *pHt )
+{
+ assert(pHt && m_pFont && "No attribute of font available for change");
+ if( m_pRedline && m_pRedline->IsOn() )
+ m_pRedline->ChangeTextAttr( m_pFont, *pHt, true );
+ else
+ m_aAttrHandler.PushAndChg( *pHt, *m_pFont );
+ m_nChgCnt++;
+}
+
+void SwAttrIter::Rst( SwTextAttr const *pHt )
+{
+ assert(pHt && m_pFont && "No attribute of font available for reset");
+ // get top from stack after removing pHt
+ if( m_pRedline && m_pRedline->IsOn() )
+ m_pRedline->ChangeTextAttr( m_pFont, *pHt, false );
+ else
+ m_aAttrHandler.PopAndChg( *pHt, *m_pFont );
+ m_nChgCnt--;
+}
+
+SwAttrIter::~SwAttrIter()
+{
+ m_pRedline.reset();
+ delete m_pFont;
+}
+
+bool SwAttrIter::MaybeHasHints() const
+{
+ return nullptr != m_pTextNode->GetpSwpHints() || nullptr != m_pMergedPara;
+}
+
+/**
+ * Returns the attribute for a position
+ *
+ * Only if the attribute is exactly at the position @param nPos and
+ * does not have an EndIndex
+ *
+ * We need this function for attributes which should alter formatting without
+ * changing the content of the string.
+ * Such "degenerated" attributes are e.g.: fields which retain expanded text and
+ * line-bound Frames.
+ * In order to avoid ambiguities between different such attributes, we insert a
+ * special character at the start of the string, when creating such an attribute.
+ * The Formatter later on encounters such a special character and retrieves the
+ * degenerate attribute via GetAttr().
+ */
+SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const
+{
+ std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
+ ? sw::MapViewToModel(*m_pMergedPara, nPosition)
+ : std::make_pair(m_pTextNode, sal_Int32(nPosition)));
+ return pos.first->GetTextAttrForCharAt(pos.second);
+}
+
+bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut)
+{
+ std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
+ ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
+ : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
+ bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition
+ ? m_pFont->IsFntChg()
+ : Seek( nNewPos );
+ if ( m_pLastOut.get() != pOut )
+ {
+ m_pLastOut = pOut;
+ m_pFont->SetFntChg( true );
+ bChg = true;
+ }
+ if( bChg )
+ {
+ // if the change counter is zero, we know the cache id of the wanted font
+ if ( !m_nChgCnt && !m_nPropFont )
+ m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
+ m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
+ m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
+ }
+
+ return bChg;
+}
+
+bool SwAttrIter::IsSymbol(TextFrameIndex const nNewPos)
+{
+ Seek( nNewPos );
+ if ( !m_nChgCnt && !m_nPropFont )
+ m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
+ m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
+ return m_pFont->IsSymbol( m_pViewShell );
+}
+
+bool SwTextFrame::IsSymbolAt(TextFrameIndex const nPos) const
+{
+ SwTextInfo info(const_cast<SwTextFrame*>(this));
+ SwTextIter iter(const_cast<SwTextFrame*>(this), &info);
+ return iter.IsSymbol(nPos);
+}
+
+bool SwAttrIter::SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont )
+{
+ SwTextNode const*const pFirstTextNode(m_pMergedPara ? m_pMergedPara->pFirstNode : m_pTextNode);
+ if ( m_pRedline && m_pRedline->ExtOn() )
+ m_pRedline->LeaveExtend(*m_pFont, pFirstTextNode->GetIndex(), 0);
+
+ if (m_pTextNode != pFirstTextNode)
+ {
+ assert(m_pMergedPara);
+ m_pTextNode = m_pMergedPara->pFirstNode;
+ InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
+ m_pMergedPara->mergedText, nullptr, nullptr);
+ }
+
+ // reset font to its original state
+ m_aAttrHandler.Reset();
+ m_aAttrHandler.ResetFont( *m_pFont );
+
+ m_nStartIndex = 0;
+ m_nEndIndex = 0;
+ m_nPosition = 0;
+ m_nChgCnt = 0;
+ if( m_nPropFont )
+ m_pFont->SetProportion( m_nPropFont );
+ if( m_pRedline )
+ {
+ m_pRedline->Clear( m_pFont );
+ if( !bParaFont )
+ m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, pFirstTextNode->GetIndex(), 0, COMPLETE_STRING);
+ else
+ m_pRedline->Reset();
+ }
+
+ SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
+ if (pHints && !bParaFont)
+ {
+ SwTextAttr *pTextAttr;
+ // While we've not reached the end of the StartArray && the TextAttribute starts at position 0...
+ while ((m_nStartIndex < pHints->Count()) &&
+ !((pTextAttr = pHints->Get(m_nStartIndex))->GetStart()))
+ {
+ // open the TextAttributes
+ Chg( pTextAttr );
+ m_nStartIndex++;
+ }
+ }
+
+ bool bChg = m_pFont->IsFntChg();
+ if ( m_pLastOut.get() != pOut )
+ {
+ m_pLastOut = pOut;
+ m_pFont->SetFntChg( true );
+ bChg = true;
+ }
+ if( bChg )
+ {
+ // if the application counter is zero, we know the cache id of the wanted font
+ if ( !m_nChgCnt && !m_nPropFont )
+ m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
+ m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
+ m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
+ }
+ return bChg;
+}
+
+// AMA: New AttrIter Nov 94
+void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos)
+{
+ SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
+ SwTextAttr *pTextAttr;
+ const auto nHintsCount = pHints->Count();
+
+ if ( m_nStartIndex ) // If attributes have been opened at all ...
+ {
+ // Close attributes that are currently open, but stop at nNewPos+1
+
+ // As long as we've not yet reached the end of EndArray and the
+ // TextAttribute ends before or at the new position ...
+ while ((m_nEndIndex < nHintsCount) &&
+ ((pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos))
+ {
+ // Close the TextAttributes, whose StartPos were before or at
+ // the old nPos and are currently open
+ if (pTextAttr->GetStart() <= nOldPos) Rst( pTextAttr );
+ m_nEndIndex++;
+ }
+ }
+ else // skip the not opened ends
+ {
+ while ((m_nEndIndex < nHintsCount) &&
+ (pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos))
+ {
+ m_nEndIndex++;
+ }
+ }
+
+ // As long as we've not yet reached the end of EndArray and the
+ // TextAttribute ends before or at the new position...
+ while ((m_nStartIndex < nHintsCount) &&
+ ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos))
+ {
+
+ // open the TextAttributes, whose ends lie behind the new position
+ if ( pTextAttr->GetAnyEnd() > nNewPos ) Chg( pTextAttr );
+ m_nStartIndex++;
+ }
+
+}
+
+bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
+{
+ // note: nNewPos isn't necessarily an index returned from GetNextAttr
+ std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara
+ ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
+ : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
+
+ if ( m_pRedline && m_pRedline->ExtOn() )
+ m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second);
+ if (m_pTextNode->GetIndex() < newPos.first->GetIndex())
+ {
+ // Skipping to a different node - first seek until the end of this node
+ // to get rid of all hint items
+ if (m_pTextNode->GetpSwpHints())
+ {
+ sal_Int32 nPos(m_nPosition);
+ do
+ {
+ sal_Int32 const nOldPos(nPos);
+ nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
+ if (nPos <= m_pTextNode->Len())
+ {
+ SeekFwd(nOldPos, nPos);
+ }
+ else
+ {
+ SeekFwd(nOldPos, m_pTextNode->Len());
+ }
+ }
+ while (nPos < m_pTextNode->Len());
+ }
+ // Unapply current para items:
+ // the SwAttrHandler doesn't appear to be capable of *unapplying*
+ // items at all; it can only apply a previously effective item.
+ // So do this by recreating the font from scratch.
+ // Apply new para items:
+ assert(m_pMergedPara);
+ InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *newPos.first,
+ m_pMergedPara->mergedText, nullptr, nullptr);
+ // reset to next
+ m_pTextNode = newPos.first;
+ m_nStartIndex = 0;
+ m_nEndIndex = 0;
+ m_nPosition = 0;
+ assert(m_pRedline);
+ }
+
+ // sw_redlinehide: Seek(0) must move before the first character, which
+ // has a special case where the first node starts with delete redline.
+ if ((!nNewPos && !m_pMergedPara)
+ || newPos.first != m_pTextNode
+ || newPos.second < m_nPosition)
+ {
+ if (m_pMergedPara)
+ {
+ if (m_pTextNode != newPos.first)
+ {
+ m_pTextNode = newPos.first;
+ // sw_redlinehide: hope it's okay to use the current text node
+ // here; the AttrHandler shouldn't care about non-char items
+ InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
+ m_pMergedPara->mergedText, nullptr, nullptr);
+ }
+ }
+ if (m_pMergedPara || m_pTextNode->GetpSwpHints())
+ {
+ if( m_pRedline )
+ m_pRedline->Clear( nullptr );
+
+ // reset font to its original state
+ m_aAttrHandler.Reset();
+ m_aAttrHandler.ResetFont( *m_pFont );
+
+ if( m_nPropFont )
+ m_pFont->SetProportion( m_nPropFont );
+ m_nStartIndex = 0;
+ m_nEndIndex = 0;
+ m_nPosition = 0;
+ m_nChgCnt = 0;
+
+ // Attention!
+ // resetting the font here makes it necessary to apply any
+ // changes for extended input directly to the font
+ if ( m_pRedline && m_pRedline->ExtOn() )
+ {
+ m_pRedline->UpdateExtFont( *m_pFont );
+ ++m_nChgCnt;
+ }
+ }
+ }
+
+ if (m_pTextNode->GetpSwpHints())
+ {
+ if (m_pMergedPara)
+ {
+ // iterate hint by hint: SeekFwd does not mix ends and starts,
+ // it always applies all the starts last, so it must be called once
+ // per position where hints start/end!
+ sal_Int32 nPos(m_nPosition);
+ do
+ {
+ sal_Int32 const nOldPos(nPos);
+ nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
+ if (nPos <= newPos.second)
+ {
+ SeekFwd(nOldPos, nPos);
+ }
+ else
+ {
+ SeekFwd(nOldPos, newPos.second);
+ }
+ }
+ while (nPos < newPos.second);
+ }
+ else
+ {
+ SeekFwd(m_nPosition, newPos.second);
+ }
+ }
+
+ m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) );
+
+ if( m_pRedline )
+ m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, m_pTextNode->GetIndex(), newPos.second, m_nPosition);
+ m_nPosition = newPos.second;
+
+ if( m_nPropFont )
+ m_pFont->SetProportion( m_nPropFont );
+
+ return m_pFont->IsFntChg();
+}
+
+static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems)
+{
+ SfxItemIter iter(rItems);
+ for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem())
+ {
+ auto const nWhich(pItem->Which());
+ if (isCHRATR(nWhich) && RES_CHRATR_RSID != nWhich)
+ {
+ pAttrs[nWhich - RES_CHRATR_BEGIN] = pItem;
+ }
+ else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
+ {
+ pAttrs[RES_CHRATR_END - RES_CHRATR_BEGIN] = pItem;
+ }
+ }
+}
+
+// if return false: portion ends at start of redline, indexes unchanged
+// if return true: portion end not known (past end of redline), indexes point to first hint past end of redline
+static bool CanSkipOverRedline(
+ SwTextNode const& rStartNode, sal_Int32 const nStartRedline,
+ SwRangeRedline const& rRedline,
+ size_t & rStartIndex, size_t & rEndIndex,
+ bool const isTheAnswerYes)
+{
+ size_t nStartIndex(rStartIndex);
+ size_t nEndIndex(rEndIndex);
+ SwPosition const*const pRLEnd(rRedline.End());
+ if (!pRLEnd->GetNode().IsTextNode() // if fully deleted...
+ || pRLEnd->GetContentIndex() == pRLEnd->GetNode().GetTextNode()->Len())
+ {
+ // shortcut: nothing follows redline
+ // current state is end state
+ return false;
+ }
+ std::vector<SwTextAttr*> activeCharFmts;
+ // can't compare the SwFont that's stored somewhere, it doesn't have compare
+ // operator, so try to recreate the situation with some temp arrays here
+ SfxPoolItem const* activeCharAttrsStart[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
+ if (rStartNode != pRLEnd->GetNode())
+ { // nodes' attributes are only needed if there are different nodes
+ InsertCharAttrs(activeCharAttrsStart, rStartNode.GetSwAttrSet());
+ }
+ if (SwpHints const*const pStartHints = rStartNode.GetpSwpHints())
+ {
+ // check hint ends of hints that start before and end within
+ sal_Int32 const nRedlineEnd(rStartNode == pRLEnd->GetNode()
+ ? pRLEnd->GetContentIndex()
+ : rStartNode.Len());
+ for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex)
+ {
+ SwTextAttr *const pAttr(pStartHints->GetSortedByEnd(nEndIndex));
+ if (!pAttr->End())
+ {
+ continue;
+ }
+ if (nRedlineEnd < *pAttr->End())
+ {
+ break;
+ }
+ if (nStartRedline <= pAttr->GetStart())
+ {
+ continue;
+ }
+ if (pAttr->IsFormatIgnoreEnd())
+ {
+ continue;
+ }
+ switch (pAttr->Which())
+ {
+ // if any of these ends inside RL then we need a new portion
+ case RES_TXTATR_REFMARK:
+ case RES_TXTATR_TOXMARK:
+ case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
+ case RES_TXTATR_METAFIELD:
+ case RES_TXTATR_INETFMT:
+ case RES_TXTATR_CJK_RUBY:
+ case RES_TXTATR_INPUTFIELD:
+ case RES_TXTATR_CONTENTCONTROL:
+ {
+ if (!isTheAnswerYes) return false; // always break
+ }
+ break;
+ // these are guaranteed not to overlap
+ // and come in order of application
+ case RES_TXTATR_AUTOFMT:
+ case RES_TXTATR_CHARFMT:
+ {
+ if (pAttr->Which() == RES_TXTATR_CHARFMT)
+ {
+ activeCharFmts.push_back(pAttr);
+ }
+ // pure formatting hints may end inside the redline &
+ // start again inside the redline, which must not cause
+ // a new text portion if they have the same items - so
+ // store the effective items & compare all at the end
+ SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
+ ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
+ : *pAttr->GetAutoFormat().GetStyleHandle());
+ InsertCharAttrs(activeCharAttrsStart, rSet);
+ }
+ break;
+ // SwTextNode::SetAttr puts it into AUTOFMT which is quite
+ // sensible so it doesn't actually exist as a hint
+ case RES_TXTATR_UNKNOWN_CONTAINER:
+ default: assert(false);
+ }
+ }
+ assert(nEndIndex == pStartHints->Count() ||
+ pRLEnd->GetContentIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd());
+ }
+
+ if (rStartNode != pRLEnd->GetNode())
+ {
+ nStartIndex = 0;
+ nEndIndex = 0;
+ }
+
+ // treat para properties as text properties
+ // ... with the FormatToTextAttr we get autofmts that correspond to the *effective* attr set difference
+ // effective attr set: para props + charfmts + autofmt *in that order*
+ // ... and the charfmt must be *nominally* the same
+
+ SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
+ if (rStartNode != pRLEnd->GetNode())
+ { // nodes' attributes are only needed if there are different nodes
+ InsertCharAttrs(activeCharAttrsEnd,
+ pRLEnd->GetNode().GetTextNode()->GetSwAttrSet());
+ }
+
+ if (SwpHints *const pEndHints = pRLEnd->GetNode().GetTextNode()->GetpSwpHints())
+ {
+ // check hint starts of hints that start within and end after
+#ifndef NDEBUG
+ sal_Int32 const nRedlineStart(rStartNode == pRLEnd->GetNode()
+ ? nStartRedline
+ : 0);
+#endif
+ for ( ; nStartIndex < pEndHints->Count(); ++nStartIndex)
+ {
+ SwTextAttr *const pAttr(pEndHints->Get(nStartIndex));
+ // compare with < here, not <=, to get the effective formatting
+ // of the 1st char after the redline; should not cause problems
+ // with consecutive delete redlines because those are handed by
+ // GetNextRedln() and here we have the last end pos.
+ if (pRLEnd->GetContentIndex() < pAttr->GetStart())
+ {
+ break;
+ }
+ if (!pAttr->End())
+ continue;
+ if (pAttr->IsFormatIgnoreStart())
+ {
+ continue;
+ }
+ assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise?
+ if (*pAttr->End() <= pRLEnd->GetContentIndex())
+ {
+ continue;
+ }
+ switch (pAttr->Which())
+ {
+ case RES_TXTATR_REFMARK:
+ case RES_TXTATR_TOXMARK:
+ case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
+ case RES_TXTATR_METAFIELD:
+ case RES_TXTATR_INETFMT:
+ case RES_TXTATR_CJK_RUBY:
+ case RES_TXTATR_INPUTFIELD:
+ case RES_TXTATR_CONTENTCONTROL:
+ {
+ if (!isTheAnswerYes) return false;
+ }
+ break;
+ case RES_TXTATR_AUTOFMT:
+ case RES_TXTATR_CHARFMT:
+ {
+ // char formats must be *nominally* the same
+ if (pAttr->Which() == RES_TXTATR_CHARFMT)
+ {
+ auto iter = std::find_if(activeCharFmts.begin(), activeCharFmts.end(),
+ [&pAttr](const SwTextAttr* pCharFmt) { return *pCharFmt == *pAttr; });
+ if (iter != activeCharFmts.end())
+ activeCharFmts.erase(iter);
+ else if (!isTheAnswerYes)
+ return false;
+ }
+ SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
+ ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
+ : *pAttr->GetAutoFormat().GetStyleHandle());
+ InsertCharAttrs(activeCharAttrsEnd, rSet);
+
+ }
+ break;
+ // SwTextNode::SetAttr puts it into AUTOFMT which is quite
+ // sensible so it doesn't actually exist as a hint
+ case RES_TXTATR_UNKNOWN_CONTAINER:
+ default: assert(false);
+ }
+ }
+ if (rStartNode != pRLEnd->GetNode())
+ {
+ // need to iterate the nEndIndex forward too so the loop in the
+ // caller can look for the right ends in the next iteration
+ for (nEndIndex = 0; nEndIndex < pEndHints->Count(); ++nEndIndex)
+ {
+ SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex));
+ if (!pAttr->End())
+ continue;
+ if (pRLEnd->GetContentIndex() < *pAttr->End())
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ // if we didn't find a matching start for any end, then it really ends inside
+ if (!activeCharFmts.empty())
+ {
+ if (!isTheAnswerYes) return false;
+ }
+ for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i)
+ {
+ // all of these should be shareable (but we have no SfxItemPool to check it here)
+ // assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->Shareable(*activeCharAttrsStart[i]));
+ if (!SfxPoolItem::areSame(activeCharAttrsStart[i], activeCharAttrsEnd[i]))
+ {
+ if (!isTheAnswerYes) return false;
+ }
+ }
+ rStartIndex = nStartIndex;
+ rEndIndex = nEndIndex;
+ return true;
+}
+
+static sal_Int32 GetNextAttrImpl(SwTextNode const*const pTextNode,
+ size_t const nStartIndex, size_t const nEndIndex,
+ sal_Int32 const nPosition)
+{
+ // note: this used to be COMPLETE_STRING, but was set to Len() + 1 below,
+ // which is rather silly, so set it to Len() instead
+ sal_Int32 nNext = pTextNode->Len();
+ if (SwpHints const*const pHints = pTextNode->GetpSwpHints())
+ {
+ // are there attribute starts left?
+ for (size_t i = nStartIndex; i < pHints->Count(); ++i)
+ {
+ SwTextAttr *const pAttr(pHints->Get(i));
+ if (!pAttr->IsFormatIgnoreStart())
+ {
+ nNext = pAttr->GetStart();
+ break;
+ }
+ }
+ // are there attribute ends left?
+ for (size_t i = nEndIndex; i < pHints->Count(); ++i)
+ {
+ SwTextAttr *const pAttr(pHints->GetSortedByEnd(i));
+ if (!pAttr->IsFormatIgnoreEnd())
+ {
+ sal_Int32 const nNextEnd = pAttr->GetAnyEnd();
+ nNext = std::min(nNext, nNextEnd); // pick nearest one
+ break;
+ }
+ }
+ }
+ // TODO: maybe use hints like FieldHints for this instead of looking at the text...
+ const sal_Int32 l = std::min(nNext, pTextNode->Len());
+ sal_Int32 p = nPosition;
+ const sal_Unicode* pStr = pTextNode->GetText().getStr();
+ while (p < l)
+ {
+ sal_Unicode aChar = pStr[p];
+ switch (aChar)
+ {
+ case CH_TXT_ATR_FORMELEMENT:
+ case CH_TXT_ATR_FIELDSTART:
+ case CH_TXT_ATR_FIELDSEP:
+ case CH_TXT_ATR_FIELDEND:
+ goto break_; // sigh...
+ default:
+ ++p;
+ }
+ }
+break_:
+ assert(p <= nNext);
+ if (p < l)
+ {
+ // found a CH_TXT_ATR_FIELD*: if it's same as current position,
+ // skip behind it so that both before- and after-positions are returned
+ nNext = (nPosition < p) ? p : p + 1;
+ }
+ return nNext;
+}
+
+TextFrameIndex SwAttrIter::GetNextAttr() const
+{
+ size_t nStartIndex(m_nStartIndex);
+ size_t nEndIndex(m_nEndIndex);
+ size_t nPosition(m_nPosition);
+ SwTextNode const* pTextNode(m_pTextNode);
+ SwRedlineTable::size_type nActRedline(m_pRedline ? m_pRedline->GetAct() : SwRedlineTable::npos);
+
+ while (true)
+ {
+ sal_Int32 nNext = GetNextAttrImpl(pTextNode, nStartIndex, nEndIndex, nPosition);
+ if( m_pRedline )
+ {
+ std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> const redline(
+ m_pRedline->GetNextRedln(nNext, pTextNode, nActRedline));
+ if (redline.second.first)
+ {
+ assert(m_pMergedPara);
+ assert(redline.second.first->End()->GetNodeIndex() <= m_pMergedPara->pLastNode->GetIndex()
+ || !redline.second.first->End()->GetNode().IsTextNode());
+ if (CanSkipOverRedline(*pTextNode, redline.first, *redline.second.first,
+ nStartIndex, nEndIndex, m_nPosition == redline.first))
+ { // if current position is start of the redline, must skip!
+ nActRedline += redline.second.second;
+ if (&redline.second.first->End()->GetNode() != pTextNode)
+ {
+ pTextNode = redline.second.first->End()->GetNode().GetTextNode();
+ nPosition = redline.second.first->End()->GetContentIndex();
+ }
+ else
+ {
+ nPosition = redline.second.first->End()->GetContentIndex();
+ }
+ }
+ else
+ {
+ return sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first);
+ }
+ }
+ else
+ {
+ return m_pMergedPara
+ ? sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first)
+ : TextFrameIndex(redline.first);
+ }
+ }
+ else
+ {
+ return TextFrameIndex(nNext);
+ }
+ }
+}
+
+namespace {
+
+class SwMinMaxArgs
+{
+public:
+ VclPtr<OutputDevice> m_pOut;
+ SwViewShell const* m_pSh;
+ sal_uLong& m_rMin;
+ sal_uLong& m_rAbsMin;
+ tools::Long m_nRowWidth;
+ tools::Long m_nWordWidth;
+ tools::Long m_nWordAdd;
+ sal_Int32 m_nNoLineBreak;
+ SwMinMaxArgs(OutputDevice* pOutI, SwViewShell const* pShI, sal_uLong& rMinI, sal_uLong& rAbsI)
+ : m_pOut(pOutI)
+ , m_pSh(pShI)
+ , m_rMin(rMinI)
+ , m_rAbsMin(rAbsI)
+ , m_nRowWidth(0)
+ , m_nWordWidth(0)
+ , m_nWordAdd(0)
+ , m_nNoLineBreak(COMPLETE_STRING)
+ { }
+ void Minimum( tools::Long nNew ) const {
+ if (static_cast<tools::Long>(m_rMin) < nNew)
+ m_rMin = nNew;
+ }
+ void NewWord() { m_nWordAdd = m_nWordWidth = 0; }
+};
+
+}
+
+static bool lcl_MinMaxString( SwMinMaxArgs& rArg, SwFont* pFnt, const OUString &rText,
+ sal_Int32 nIdx, sal_Int32 nEnd )
+{
+ bool bRet = false;
+ while( nIdx < nEnd )
+ {
+ sal_Int32 nStop = nIdx;
+ LanguageType eLang = pFnt->GetLanguage();
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ bool bClear = CH_BLANK == rText[ nStop ];
+ Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( rText, nIdx,
+ g_pBreakIt->GetLocale( eLang ),
+ WordType::DICTIONARY_WORD, true ) );
+ nStop = aBndry.endPos;
+ if (nIdx <= aBndry.startPos && nIdx && nIdx - 1 != rArg.m_nNoLineBreak)
+ rArg.NewWord();
+ if( nStop == nIdx )
+ ++nStop;
+ if( nStop > nEnd )
+ nStop = nEnd;
+
+ SwDrawTextInfo aDrawInf(rArg.m_pSh, *rArg.m_pOut, rText, nIdx, nStop - nIdx);
+ tools::Long nCurrentWidth = pFnt->GetTextSize_( aDrawInf ).Width();
+ rArg.m_nRowWidth += nCurrentWidth;
+ if( bClear )
+ rArg.NewWord();
+ else
+ {
+ rArg.m_nWordWidth += nCurrentWidth;
+ if (static_cast<tools::Long>(rArg.m_rAbsMin) < rArg.m_nWordWidth)
+ rArg.m_rAbsMin = rArg.m_nWordWidth;
+ rArg.Minimum(rArg.m_nWordWidth + rArg.m_nWordAdd);
+ bRet = true;
+ }
+ nIdx = nStop;
+ }
+ return bRet;
+}
+
+bool SwTextNode::IsSymbolAt(const sal_Int32 nBegin) const
+{
+ SwScriptInfo aScriptInfo;
+ SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
+ aIter.Seek( TextFrameIndex(nBegin) );
+ return aIter.GetFnt()->IsSymbol( getIDocumentLayoutAccess().GetCurrentViewShell() );
+}
+
+namespace {
+
+class SwMinMaxNodeArgs
+{
+public:
+ sal_uLong m_nMaxWidth; // sum of all frame widths
+ tools::Long m_nMinWidth; // biggest frame
+ tools::Long m_nLeftRest; // space not already covered by frames in the left margin
+ tools::Long m_nRightRest; // space not already covered by frames in the right margin
+ tools::Long m_nLeftDiff; // Min/Max-difference of the frame in the left margin
+ tools::Long m_nRightDiff; // Min/Max-difference of the frame in the right margin
+ SwNodeOffset m_nIndex; // index of the node
+ void Minimum( tools::Long nNew ) {
+ if (nNew > m_nMinWidth)
+ m_nMinWidth = nNew;
+ }
+};
+
+}
+
+static void lcl_MinMaxNode(SwFrameFormat* pNd, SwMinMaxNodeArgs& rIn)
+{
+ const SwFormatAnchor& rFormatA = pNd->GetAnchor();
+
+ if ((RndStdIds::FLY_AT_PARA != rFormatA.GetAnchorId()) &&
+ (RndStdIds::FLY_AT_CHAR != rFormatA.GetAnchorId()))
+ {
+ return;
+ }
+
+ const SwNode *pAnchorNode = rFormatA.GetAnchorNode();
+ OSL_ENSURE(pAnchorNode, "Unexpected NULL arguments");
+ if (!pAnchorNode || rIn.m_nIndex != pAnchorNode->GetIndex())
+ return;
+
+ tools::Long nMin, nMax;
+ SwHTMLTableLayout *pLayout = nullptr;
+ const bool bIsDrawFrameFormat = pNd->Which()==RES_DRAWFRMFMT;
+ if( !bIsDrawFrameFormat )
+ {
+ // Does the frame contain a table at the start or the end?
+ const SwNodes& rNodes = pNd->GetDoc()->GetNodes();
+ const SwFormatContent& rFlyContent = pNd->GetContent();
+ SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex();
+ SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode();
+ if( !pTableNd )
+ {
+ SwNode *pNd2 = rNodes[nStt];
+ pNd2 = rNodes[pNd2->EndOfSectionIndex()-1];
+ if( pNd2->IsEndNode() )
+ pTableNd = pNd2->StartOfSectionNode()->GetTableNode();
+ }
+
+ if( pTableNd )
+ pLayout = pTableNd->GetTable().GetHTMLTableLayout();
+ }
+
+ const SwFormatHoriOrient& rOrient = pNd->GetHoriOrient();
+ sal_Int16 eHoriOri = rOrient.GetHoriOrient();
+
+ tools::Long nDiff;
+ if( pLayout )
+ {
+ nMin = pLayout->GetMin();
+ nMax = pLayout->GetMax();
+ nDiff = nMax - nMin;
+ }
+ else
+ {
+ if( bIsDrawFrameFormat )
+ {
+ const SdrObject* pSObj = pNd->FindSdrObject();
+ if( pSObj )
+ nMin = pSObj->GetCurrentBoundRect().GetWidth();
+ else
+ nMin = 0;
+
+ }
+ else
+ {
+ const SwFormatFrameSize &rSz = pNd->GetFrameSize();
+ nMin = rSz.GetWidth();
+ }
+ nMax = nMin;
+ nDiff = 0;
+ }
+
+ const SvxLRSpaceItem &rLR = pNd->GetLRSpace();
+ nMin += rLR.GetLeft();
+ nMin += rLR.GetRight();
+ nMax += rLR.GetLeft();
+ nMax += rLR.GetRight();
+
+ if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() )
+ {
+ rIn.Minimum( nMin );
+ return;
+ }
+
+ // Frames, which are left- or right-aligned are only party considered
+ // when calculating the maximum, since the border is already being considered.
+ // Only if the frame extends into the text body, this part is being added
+ switch( eHoriOri )
+ {
+ case text::HoriOrientation::RIGHT:
+ {
+ if( nDiff )
+ {
+ rIn.m_nRightRest -= rIn.m_nRightDiff;
+ rIn.m_nRightDiff = nDiff;
+ }
+ if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() )
+ {
+ if (rIn.m_nRightRest > 0)
+ rIn.m_nRightRest = 0;
+ }
+ rIn.m_nRightRest -= nMin;
+ break;
+ }
+ case text::HoriOrientation::LEFT:
+ {
+ if( nDiff )
+ {
+ rIn.m_nLeftRest -= rIn.m_nLeftDiff;
+ rIn.m_nLeftDiff = nDiff;
+ }
+ if (text::RelOrientation::FRAME != rOrient.GetRelationOrient() && rIn.m_nLeftRest < 0)
+ rIn.m_nLeftRest = 0;
+ rIn.m_nLeftRest -= nMin;
+ break;
+ }
+ default:
+ {
+ rIn.m_nMaxWidth += nMax;
+ rIn.Minimum(nMin);
+ }
+ }
+}
+
+#define FLYINCNT_MIN_WIDTH 284
+
+/**
+ * Changing this method very likely requires changing of GetScalingOfSelectedText
+ * This one is called exclusively from import filters, so there is no layout.
+ */
+void SwTextNode::GetMinMaxSize( SwNodeOffset nIndex, sal_uLong& rMin, sal_uLong &rMax,
+ sal_uLong& rAbsMin ) const
+{
+ SwViewShell const * pSh = GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell();
+ OutputDevice* pOut = nullptr;
+ if( pSh )
+ pOut = pSh->GetWin()->GetOutDev();
+ if( !pOut )
+ pOut = Application::GetDefaultDevice();
+
+ MapMode aOldMap( pOut->GetMapMode() );
+ pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
+
+ rMin = 0;
+ rMax = 0;
+ rAbsMin = 0;
+
+ SvxTextLeftMarginItem const& rTextLeftMargin(GetSwAttrSet().GetTextLeftMargin());
+ SvxRightMarginItem const& rRightMargin(GetSwAttrSet().GetRightMargin());
+ tools::Long nLROffset = rTextLeftMargin.GetTextLeft() + GetLeftMarginWithNum( true );
+ short nFLOffs;
+ // For enumerations a negative first line indentation is probably filled already
+ if( !GetFirstLineOfsWithNum( nFLOffs ) || nFLOffs > nLROffset )
+ nLROffset = nFLOffs;
+
+ SwMinMaxNodeArgs aNodeArgs;
+ aNodeArgs.m_nMinWidth = 0;
+ aNodeArgs.m_nMaxWidth = 0;
+ aNodeArgs.m_nLeftRest = nLROffset;
+ aNodeArgs.m_nRightRest = rRightMargin.GetRight();
+ aNodeArgs.m_nLeftDiff = 0;
+ aNodeArgs.m_nRightDiff = 0;
+ if( nIndex )
+ {
+ sw::SpzFrameFormats* pSpzs = const_cast<sw::SpzFrameFormats*>(GetDoc().GetSpzFrameFormats());
+ if(pSpzs)
+ {
+ aNodeArgs.m_nIndex = nIndex;
+ for(auto pFormat: *pSpzs)
+ lcl_MinMaxNode(pFormat, aNodeArgs);
+ }
+ }
+ if (aNodeArgs.m_nLeftRest < 0)
+ aNodeArgs.Minimum(nLROffset - aNodeArgs.m_nLeftRest);
+ aNodeArgs.m_nLeftRest -= aNodeArgs.m_nLeftDiff;
+ if (aNodeArgs.m_nLeftRest < 0)
+ aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nLeftRest;
+
+ if (aNodeArgs.m_nRightRest < 0)
+ aNodeArgs.Minimum(rRightMargin.GetRight() - aNodeArgs.m_nRightRest);
+ aNodeArgs.m_nRightRest -= aNodeArgs.m_nRightDiff;
+ if (aNodeArgs.m_nRightRest < 0)
+ aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nRightRest;
+
+ SwScriptInfo aScriptInfo;
+ SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
+ TextFrameIndex nIdx(0);
+ aIter.SeekAndChgAttrIter( nIdx, pOut );
+ TextFrameIndex nLen(m_Text.getLength());
+ tools::Long nCurrentWidth = 0;
+ tools::Long nAdd = 0;
+ SwMinMaxArgs aArg( pOut, pSh, rMin, rAbsMin );
+ while( nIdx < nLen )
+ {
+ TextFrameIndex nNextChg = aIter.GetNextAttr();
+ TextFrameIndex nStop = aScriptInfo.NextScriptChg( nIdx );
+ if( nNextChg > nStop )
+ nNextChg = nStop;
+ SwTextAttr *pHint = nullptr;
+ sal_Unicode cChar = CH_BLANK;
+ nStop = nIdx;
+ while( nStop < nLen && nStop < nNextChg &&
+ CH_TAB != (cChar = m_Text[sal_Int32(nStop)]) &&
+ CH_BREAK != cChar && CHAR_HARDBLANK != cChar &&
+ CHAR_HARDHYPHEN != cChar && CHAR_SOFTHYPHEN != cChar &&
+ CH_TXT_ATR_INPUTFIELDSTART != cChar &&
+ CH_TXT_ATR_INPUTFIELDEND != cChar &&
+ CH_TXT_ATR_FORMELEMENT != cChar &&
+ CH_TXT_ATR_FIELDSTART != cChar &&
+ CH_TXT_ATR_FIELDSEP != cChar &&
+ CH_TXT_ATR_FIELDEND != cChar &&
+ !pHint )
+ {
+ // this looks like some defensive programming to handle dummy char
+ // with missing hint? but it's rather silly because it may pass the
+ // dummy char to lcl_MinMaxString in that case...
+ if( ( CH_TXTATR_BREAKWORD != cChar && CH_TXTATR_INWORD != cChar )
+ || ( nullptr == ( pHint = aIter.GetAttr( nStop ) ) ) )
+ ++nStop;
+ }
+ if (lcl_MinMaxString(aArg, aIter.GetFnt(), m_Text, sal_Int32(nIdx), sal_Int32(nStop)))
+ {
+ nAdd = 20;
+ }
+ nIdx = nStop;
+ aIter.SeekAndChgAttrIter( nIdx, pOut );
+ switch( cChar )
+ {
+ case CH_BREAK :
+ {
+ if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
+ rMax = aArg.m_nRowWidth;
+ aArg.m_nRowWidth = 0;
+ aArg.NewWord();
+ aIter.SeekAndChgAttrIter( ++nIdx, pOut );
+ }
+ break;
+ case CH_TAB :
+ {
+ aArg.NewWord();
+ aIter.SeekAndChgAttrIter( ++nIdx, pOut );
+ }
+ break;
+ case CHAR_SOFTHYPHEN:
+ ++nIdx;
+ break;
+ case CHAR_HARDBLANK:
+ case CHAR_HARDHYPHEN:
+ {
+ OUString sTmp( cChar );
+ SwDrawTextInfo aDrawInf( pSh,
+ *pOut, sTmp, 0, 1, 0, false );
+ nCurrentWidth = aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ aArg.m_nWordWidth += nCurrentWidth;
+ aArg.m_nRowWidth += nCurrentWidth;
+ if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
+ rAbsMin = aArg.m_nWordWidth;
+ aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
+ aArg.m_nNoLineBreak = sal_Int32(nIdx++);
+ }
+ break;
+ case CH_TXTATR_BREAKWORD:
+ case CH_TXTATR_INWORD:
+ {
+ if( !pHint )
+ break;
+ tools::Long nOldWidth = aArg.m_nWordWidth;
+ tools::Long nOldAdd = aArg.m_nWordAdd;
+ aArg.NewWord();
+
+ switch( pHint->Which() )
+ {
+ case RES_TXTATR_FLYCNT :
+ {
+ SwFrameFormat *pFrameFormat = pHint->GetFlyCnt().GetFrameFormat();
+ const SvxLRSpaceItem &rLR = pFrameFormat->GetLRSpace();
+ if( RES_DRAWFRMFMT == pFrameFormat->Which() )
+ {
+ const SdrObject* pSObj = pFrameFormat->FindSdrObject();
+ if( pSObj )
+ nCurrentWidth = pSObj->GetCurrentBoundRect().GetWidth();
+ else
+ nCurrentWidth = 0;
+ }
+ else
+ {
+ const SwFormatFrameSize& rTmpSize = pFrameFormat->GetFrameSize();
+ if( RES_FLYFRMFMT == pFrameFormat->Which()
+ && rTmpSize.GetWidthPercent() )
+ {
+ // This is a hack for the following situation: In the paragraph there's a
+ // text frame with relative size. Then let's take 0.5 cm as minimum width
+ // and USHRT_MAX as maximum width
+ // It were cleaner and maybe necessary later on to iterate over the content
+ // of the text frame and call GetMinMaxSize recursively
+ nCurrentWidth = FLYINCNT_MIN_WIDTH; // 0.5 cm
+ rMax = std::max(rMax, sal_uLong(USHRT_MAX));
+ }
+ else
+ nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth();
+ }
+ nCurrentWidth += rLR.GetLeft();
+ nCurrentWidth += rLR.GetRight();
+ aArg.m_nWordAdd = nOldWidth + nOldAdd;
+ aArg.m_nWordWidth = nCurrentWidth;
+ aArg.m_nRowWidth += nCurrentWidth;
+ if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
+ rAbsMin = aArg.m_nWordWidth;
+ aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
+ break;
+ }
+ case RES_TXTATR_FTN :
+ {
+ const OUString aText = pHint->GetFootnote().GetNumStr();
+ if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
+ aText.getLength() ) )
+ nAdd = 20;
+ break;
+ }
+
+ case RES_TXTATR_FIELD :
+ case RES_TXTATR_ANNOTATION :
+ {
+ SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
+ const OUString aText = pField->ExpandField(true, nullptr);
+ if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
+ aText.getLength() ) )
+ nAdd = 20;
+ break;
+ }
+ default:
+ aArg.m_nWordWidth = nOldWidth;
+ aArg.m_nWordAdd = nOldAdd;
+ }
+ aIter.SeekAndChgAttrIter( ++nIdx, pOut );
+ }
+ break;
+ case CH_TXT_ATR_INPUTFIELDSTART:
+ case CH_TXT_ATR_INPUTFIELDEND:
+ case CH_TXT_ATR_FORMELEMENT:
+ case CH_TXT_ATR_FIELDSTART:
+ case CH_TXT_ATR_FIELDSEP:
+ case CH_TXT_ATR_FIELDEND:
+ { // just skip it and continue with the content...
+ aIter.SeekAndChgAttrIter( ++nIdx, pOut );
+ }
+ break;
+ }
+ }
+ if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
+ rMax = aArg.m_nRowWidth;
+
+ nLROffset += rRightMargin.GetRight();
+
+ rAbsMin += nLROffset;
+ rAbsMin += nAdd;
+ rMin += nLROffset;
+ rMin += nAdd;
+ if (static_cast<tools::Long>(rMin) < aNodeArgs.m_nMinWidth)
+ rMin = aNodeArgs.m_nMinWidth;
+ if (static_cast<tools::Long>(rAbsMin) < aNodeArgs.m_nMinWidth)
+ rAbsMin = aNodeArgs.m_nMinWidth;
+ rMax += aNodeArgs.m_nMaxWidth;
+ rMax += nLROffset;
+ rMax += nAdd;
+ if( rMax < rMin ) // e.g. Frames with flow through only contribute to the minimum
+ rMax = rMin;
+ pOut->SetMapMode( aOldMap );
+}
+
+/**
+ * Calculates the width of the text part specified by nStart and nEnd,
+ * the height of the line containing nStart is divided by this width,
+ * indicating the scaling factor, if the text part is rotated.
+ * Having CH_BREAKs in the text part, this method returns the scaling
+ * factor for the longest of the text parts separated by the CH_BREAK
+ *
+ * Changing this method very likely requires changing of "GetMinMaxSize"
+ */
+sal_uInt16 SwTextFrame::GetScalingOfSelectedText(
+ TextFrameIndex nStart, TextFrameIndex nEnd)
+{
+ assert(GetOffset() <= nStart && (!GetFollow() || nStart < GetFollow()->GetOffset()));
+ SwViewShell const*const pSh = getRootFrame()->GetCurrShell();
+ assert(pSh);
+ OutputDevice *const pOut = &pSh->GetRefDev();
+ assert(pOut);
+
+ MapMode aOldMap( pOut->GetMapMode() );
+ pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
+
+ if (nStart == nEnd)
+ {
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ SwScriptInfo aScriptInfo;
+ SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
+ aIter.SeekAndChgAttrIter( nStart, pOut );
+
+ Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ GetText(), sal_Int32(nStart),
+ g_pBreakIt->GetLocale( aIter.GetFnt()->GetLanguage() ),
+ WordType::DICTIONARY_WORD, true );
+
+ if (sal_Int32(nStart) == aBound.startPos)
+ {
+ // cursor is at left or right border of word
+ pOut->SetMapMode( aOldMap );
+ return 100;
+ }
+
+ nStart = TextFrameIndex(aBound.startPos);
+ nEnd = TextFrameIndex(aBound.endPos);
+
+ if (nStart == nEnd)
+ {
+ pOut->SetMapMode( aOldMap );
+ return 100;
+ }
+ }
+
+ SwScriptInfo aScriptInfo;
+ SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
+
+ // We do not want scaling attributes to be considered during this
+ // calculation. For this, we push a temporary scaling attribute with
+ // scaling value 100 and priority flag on top of the scaling stack
+ SwAttrHandler& rAH = aIter.GetAttrHandler();
+ SvxCharScaleWidthItem aItem(100, RES_CHRATR_SCALEW);
+ SwTextAttrEnd aAttr( aItem, 0, COMPLETE_STRING );
+ aAttr.SetPriorityAttr( true );
+ rAH.PushAndChg( aAttr, *(aIter.GetFnt()) );
+
+ TextFrameIndex nIdx = nStart;
+
+ sal_uLong nWidth = 0;
+ sal_uLong nProWidth = 0;
+
+ while( nIdx < nEnd )
+ {
+ aIter.SeekAndChgAttrIter( nIdx, pOut );
+
+ // scan for end of portion
+ TextFrameIndex const nNextChg = std::min(aIter.GetNextAttr(), aScriptInfo.NextScriptChg(nIdx));
+
+ TextFrameIndex nStop = nIdx;
+ sal_Unicode cChar = CH_BLANK;
+ SwTextAttr* pHint = nullptr;
+
+ // stop at special characters in [ nIdx, nNextChg ]
+ while( nStop < nEnd && nStop < nNextChg )
+ {
+ cChar = GetText()[sal_Int32(nStop)];
+ if (
+ CH_TAB == cChar ||
+ CH_BREAK == cChar ||
+ CHAR_HARDBLANK == cChar ||
+ CHAR_HARDHYPHEN == cChar ||
+ CHAR_SOFTHYPHEN == cChar ||
+ CH_TXT_ATR_INPUTFIELDSTART == cChar ||
+ CH_TXT_ATR_INPUTFIELDEND == cChar ||
+ CH_TXT_ATR_FORMELEMENT == cChar ||
+ CH_TXT_ATR_FIELDSTART == cChar ||
+ CH_TXT_ATR_FIELDSEP == cChar ||
+ CH_TXT_ATR_FIELDEND == cChar ||
+ (
+ (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) &&
+ (nullptr == (pHint = aIter.GetAttr(nStop)))
+ )
+ )
+ {
+ break;
+ }
+ else
+ ++nStop;
+ }
+
+ // calculate text widths up to cChar
+ if ( nStop > nIdx )
+ {
+ SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nIdx), sal_Int32(nStop - nIdx));
+ nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ }
+
+ nIdx = nStop;
+ aIter.SeekAndChgAttrIter( nIdx, pOut );
+
+ if ( cChar == CH_BREAK )
+ {
+ nWidth = std::max( nWidth, nProWidth );
+ nProWidth = 0;
+ nIdx++;
+ }
+ else if ( cChar == CH_TAB )
+ {
+ // tab receives width of one space
+ SwDrawTextInfo aDrawInf(pSh, *pOut, OUStringChar(CH_BLANK), 0, 1);
+ nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ nIdx++;
+ }
+ else if ( cChar == CHAR_SOFTHYPHEN )
+ ++nIdx;
+ else if ( cChar == CHAR_HARDBLANK || cChar == CHAR_HARDHYPHEN )
+ {
+ OUString sTmp( cChar );
+ SwDrawTextInfo aDrawInf(pSh, *pOut, sTmp, 0, 1);
+ nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ nIdx++;
+ }
+ else if ( pHint && ( cChar == CH_TXTATR_BREAKWORD || cChar == CH_TXTATR_INWORD ) )
+ {
+ switch( pHint->Which() )
+ {
+ case RES_TXTATR_FTN :
+ {
+ const OUString aText = pHint->GetFootnote().GetNumStr();
+ SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
+
+ nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ break;
+ }
+
+ case RES_TXTATR_FIELD :
+ case RES_TXTATR_ANNOTATION :
+ {
+ SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
+ OUString const aText = pField->ExpandField(true, getRootFrame());
+ SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
+
+ nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
+ break;
+ }
+
+ default:
+ {
+ // any suggestions for a default action?
+ }
+ } // end of switch
+ nIdx++;
+ }
+ else if (CH_TXT_ATR_INPUTFIELDSTART == cChar ||
+ CH_TXT_ATR_INPUTFIELDEND == cChar ||
+ CH_TXT_ATR_FORMELEMENT == cChar ||
+ CH_TXT_ATR_FIELDSTART == cChar ||
+ CH_TXT_ATR_FIELDSEP == cChar ||
+ CH_TXT_ATR_FIELDEND == cChar)
+ { // just skip it and continue with the content...
+ ++nIdx;
+ }
+ } // end of while
+
+ nWidth = std::max( nWidth, nProWidth );
+
+ // search for the line containing nStart
+ if (HasPara())
+ {
+ SwTextInfo aInf(this);
+ SwTextIter aLine(this, &aInf);
+ aLine.CharToLine( nStart );
+ pOut->SetMapMode( aOldMap );
+ return o3tl::narrowing<sal_uInt16>( nWidth ?
+ ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 );
+ }
+ // no frame or no paragraph, we take the height of the character
+ // at nStart as line height
+
+ aIter.SeekAndChgAttrIter( nStart, pOut );
+ pOut->SetMapMode( aOldMap );
+
+ SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nStart), 1);
+ return o3tl::narrowing<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 );
+}
+
+std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const
+{
+ std::vector<SwFlyAtContentFrame*> aObjs;
+ const SwSortedObjs* pSortedObjs = GetDrawObjs();
+ if (!pSortedObjs)
+ {
+ return aObjs;
+ }
+
+ for (const auto& pSortedObj : *pSortedObjs)
+ {
+ SwFlyFrame* pFlyFrame = pSortedObj->DynCastFlyFrame();
+ if (!pFlyFrame)
+ {
+ continue;
+ }
+
+ if (!pFlyFrame->IsFlySplitAllowed())
+ {
+ continue;
+ }
+
+ aObjs.push_back(static_cast<SwFlyAtContentFrame*>(pFlyFrame));
+ }
+
+ return aObjs;
+}
+
+SwFlyAtContentFrame* SwTextFrame::HasNonLastSplitFlyDrawObj() const
+{
+ const SwTextFrame* pFollow = GetFollow();
+ if (!pFollow)
+ {
+ return nullptr;
+ }
+
+ if (mnOffset != pFollow->GetOffset())
+ {
+ return nullptr;
+ }
+
+ // At this point we know what we're part of a chain that is an anchor for split fly frames, but
+ // we're not the last one. See if we have a matching fly.
+
+ // Look up the master of the anchor.
+ const SwTextFrame* pAnchor = this;
+ while (pAnchor->IsFollow())
+ {
+ pAnchor = pAnchor->FindMaster();
+ }
+ for (const auto& pFly : pAnchor->GetSplitFlyDrawObjs())
+ {
+ // Nominally all flys are anchored in the master; see if this fly is effectively anchored in
+ // us.
+ SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
+ if (pFlyAnchor != this)
+ {
+ continue;
+ }
+ if (pFly->GetFollow())
+ {
+ return pFly;
+ }
+ }
+
+ return nullptr;
+}
+
+bool SwTextFrame::IsEmptyMasterWithSplitFly() const
+{
+ if (!IsEmptyMaster())
+ {
+ return false;
+ }
+
+ if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
+ {
+ return false;
+ }
+
+ SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
+ if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
+ {
+ return false;
+ }
+
+ if (mnOffset != GetFollow()->GetOffset())
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool SwTextFrame::IsEmptyWithSplitFly() const
+{
+ if (IsFollow())
+ {
+ return false;
+ }
+
+ if (GetTextNodeFirst()->GetSwAttrSet().HasItem(RES_PAGEDESC))
+ {
+ return false;
+ }
+
+ if (getFrameArea().Bottom() <= GetUpper()->getFramePrintArea().Bottom())
+ {
+ return false;
+ }
+
+ // This is a master that doesn't fit the current parent.
+ if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
+ {
+ return false;
+ }
+
+ SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
+ if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
+ {
+ return false;
+ }
+
+ // It has a split fly anchored to it.
+ if (pFlyFrame->GetFrameFormat().GetVertOrient().GetPos() >= 0)
+ {
+ return false;
+ }
+
+ // Negative vertical offset means that visually it already may have a first line.
+ // Consider that, we may need to split the frame, so the fly frame is on one page and the empty
+ // paragraph's frame is on a next page.
+ return true;
+}
+
+SwTwips SwTextNode::GetWidthOfLeadingTabs() const
+{
+ SwTwips nRet = 0;
+
+ sal_Int32 nIdx = 0;
+
+ while ( nIdx < GetText().getLength() )
+ {
+ const sal_Unicode cCh = GetText()[nIdx];
+ if ( cCh!='\t' && cCh!=' ' )
+ {
+ break;
+ }
+ ++nIdx;
+ }
+
+ if ( nIdx > 0 )
+ {
+ SwPosition aPos( *this, nIdx );
+
+ // Find the non-follow text frame:
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
+ {
+ // Only consider master frames:
+ if (!pFrame->IsFollow() &&
+ pFrame->GetTextNodeForFirstText() == this)
+ {
+ SwRectFnSet aRectFnSet(pFrame);
+ SwRect aRect;
+ pFrame->GetCharRect( aRect, aPos );
+ nRet = pFrame->IsRightToLeft() ?
+ aRectFnSet.GetPrtRight(*pFrame) - aRectFnSet.GetRight(aRect) :
+ aRectFnSet.GetLeft(aRect) - aRectFnSet.GetPrtLeft(*pFrame);
+ break;
+ }
+ }
+ }
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx
new file mode 100644
index 0000000000..eb15400cf5
--- /dev/null
+++ b/sw/source/core/text/itratr.hxx
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <o3tl/deleter.hxx>
+#include "atrhndl.hxx"
+#include <swfont.hxx>
+
+namespace sw { struct MergedPara; }
+class SwTextAttr;
+class SwTextNode;
+class SwRedlineItr;
+class SwViewShell;
+class SwTextFrame;
+
+class SwAttrIter
+{
+ friend class SwFontSave;
+protected:
+
+ SwAttrHandler m_aAttrHandler;
+ SwViewShell *m_pViewShell;
+ SwFont* m_pFont;
+ SwScriptInfo* m_pScriptInfo;
+
+private:
+ VclPtr<OutputDevice> m_pLastOut;
+ /// count currently open hints, redlines, ext-input
+ short m_nChgCnt;
+ std::unique_ptr<SwRedlineItr, o3tl::default_delete<SwRedlineItr>> m_pRedline;
+ /// current iteration index in HintStarts
+ size_t m_nStartIndex;
+ /// current iteration index in HintEnds
+ size_t m_nEndIndex;
+ /// current iteration index in text node
+ sal_Int32 m_nPosition;
+ sal_uInt8 m_nPropFont;
+ o3tl::enumarray<SwFontScript, const void*> m_aFontCacheIds;
+ o3tl::enumarray<SwFontScript, sal_uInt16> m_aFontIdx;
+ /// input: the current text node
+ const SwTextNode* m_pTextNode;
+ sw::MergedPara const* m_pMergedPara;
+
+ void SeekFwd(sal_Int32 nOldPos, sal_Int32 nNewPos);
+ void SetFnt( SwFont* pNew ) { m_pFont = pNew; }
+ void InitFontAndAttrHandler(
+ SwTextNode const& rPropsNode, SwTextNode const& rTextNode,
+ std::u16string_view aText, bool const* pbVertLayout,
+ bool const* pbVertLayoutLRBT);
+
+protected:
+ void Chg( SwTextAttr const *pHt );
+ void Rst( SwTextAttr const *pHt );
+ void CtorInitAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const* pFrame = nullptr);
+ explicit SwAttrIter(SwTextNode const * pTextNode);
+
+public:
+ /// All subclasses of this always have a SwTextFrame passed to the
+ /// constructor, but SwAttrIter itself may be created without a
+ /// SwTextFrame in certain special cases via this ctor here
+ SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame = nullptr);
+
+ virtual ~SwAttrIter();
+
+ SwRedlineItr *GetRedln() { return m_pRedline.get(); }
+ // The parameter returns the position of the next change before or at the
+ // char position.
+ TextFrameIndex GetNextAttr() const;
+ /// Enables the attributes used at char pos nPos in the logical font
+ bool Seek(TextFrameIndex nPos);
+ // Creates the font at the specified position via Seek() and checks
+ // if it's a symbol font.
+ bool IsSymbol(TextFrameIndex nPos);
+
+ /** Executes ChgPhysFnt if Seek() returns true
+ * and change font to merge character border with neighbours.
+ **/
+ bool SeekAndChgAttrIter(TextFrameIndex nPos, OutputDevice* pOut);
+ bool SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont );
+
+ // Do we possibly have nasty things like footnotes?
+ bool MaybeHasHints() const;
+
+ // Returns the attribute for a position
+ SwTextAttr *GetAttr(TextFrameIndex nPos) const;
+
+ SwFont *GetFnt() { return m_pFont; }
+ const SwFont *GetFnt() const { return m_pFont; }
+
+ sal_uInt8 GetPropFont() const { return m_nPropFont; }
+ void SetPropFont( const sal_uInt8 nNew ) { m_nPropFont = nNew; }
+
+ SwAttrHandler& GetAttrHandler() { return m_aAttrHandler; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx
new file mode 100644
index 0000000000..a39eb77301
--- /dev/null
+++ b/sw/source/core/text/itrcrsr.cxx
@@ -0,0 +1,2023 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <paratr.hxx>
+#include <flyfrm.hxx>
+#include <pam.hxx>
+#include <swselectionlist.hxx>
+#include <sortedobjs.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <frmatr.hxx>
+#include <tgrditem.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <pagefrm.hxx>
+
+#include "itrtxt.hxx"
+#include <txtfrm.hxx>
+#include <flyfrms.hxx>
+#include "porfld.hxx"
+#include "porfly.hxx"
+#include "pordrop.hxx"
+#include <crstate.hxx>
+#include "pormulti.hxx"
+#include <numrule.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+// Not reentrant !!!
+// is set in GetCharRect and is interpreted in UnitUp/Down.
+bool SwTextCursor::s_bRightMargin = false;
+
+// After calculating the position of a character during GetCharRect
+// this function allows to find the coordinates of a position (defined
+// in pCMS->pSpecialPos) inside a special portion (e.g., a field)
+static void lcl_GetCharRectInsideField( SwTextSizeInfo& rInf, SwRect& rOrig,
+ const SwCursorMoveState& rCMS,
+ const SwLinePortion& rPor )
+{
+ OSL_ENSURE( rCMS.m_pSpecialPos, "Information about special pos missing" );
+
+ if ( rPor.InFieldGrp() && !static_cast<const SwFieldPortion&>(rPor).GetExp().isEmpty() )
+ {
+ const sal_Int32 nCharOfst = rCMS.m_pSpecialPos->nCharOfst;
+ sal_Int32 nFieldIdx = 0;
+ sal_Int32 nFieldLen = 0;
+
+ OUString sString;
+ const OUString* pString = nullptr;
+ const SwLinePortion* pPor = &rPor;
+ do
+ {
+ if ( pPor->InFieldGrp() )
+ {
+ sString = static_cast<const SwFieldPortion*>(pPor)->GetExp();
+ pString = &sString;
+ nFieldLen = pString->getLength();
+ }
+ else
+ {
+ pString = nullptr;
+ nFieldLen = 0;
+ }
+
+ if ( ! pPor->GetNextPortion() || nFieldIdx + nFieldLen > nCharOfst )
+ break;
+
+ nFieldIdx = nFieldIdx + nFieldLen;
+ rOrig.Pos().AdjustX(pPor->Width() );
+ pPor = pPor->GetNextPortion();
+
+ } while ( true );
+
+ OSL_ENSURE( nCharOfst >= nFieldIdx, "Request of position inside field failed" );
+ sal_Int32 nLen = nCharOfst - nFieldIdx + 1;
+
+ if ( pString )
+ {
+ // get script for field portion
+ rInf.GetFont()->SetActual( SwScriptInfo::WhichFont(0, *pString) );
+
+ TextFrameIndex const nOldLen = pPor->GetLen();
+ const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen - 1));
+ const SwTwips nX1 = pPor->GetLen() ?
+ pPor->GetTextSize( rInf ).Width() :
+ 0;
+
+ SwTwips nX2 = 0;
+ if ( rCMS.m_bRealWidth )
+ {
+ const_cast<SwLinePortion*>(pPor)->SetLen(TextFrameIndex(nLen));
+ nX2 = pPor->GetTextSize( rInf ).Width();
+ }
+
+ const_cast<SwLinePortion*>(pPor)->SetLen( nOldLen );
+
+ rOrig.Pos().AdjustX(nX1 );
+ rOrig.Width( ( nX2 > nX1 ) ?
+ ( nX2 - nX1 ) :
+ 1 );
+ }
+ }
+ else
+ {
+ // special cases: no common fields, e.g., graphic number portion,
+ // FlyInCntPortions, Notes
+ rOrig.Width( rCMS.m_bRealWidth && rPor.Width() ? rPor.Width() : 1 );
+ }
+}
+
+// #i111284#
+namespace {
+ bool IsLabelAlignmentActive( const SwTextNode& rTextNode )
+ {
+ bool bRet( false );
+
+ if ( rTextNode.GetNumRule() )
+ {
+ int nListLevel = rTextNode.GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ const SwNumFormat& rNumFormat =
+ rTextNode.GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
+ if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT )
+ {
+ bRet = true;
+ }
+ }
+
+ return bRet;
+ }
+} // end of anonymous namespace
+
+void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf )
+{
+ CtorInitTextIter( pNewFrame, pNewInf );
+
+ m_pInf = pNewInf;
+ GetInfo().SetFont( GetFnt() );
+ const SwTextNode *const pNode = m_pFrame->GetTextNodeForParaProps();
+
+ SvxFirstLineIndentItem const& rFirstLine(pNode->GetSwAttrSet().GetFirstLineIndent());
+ SvxTextLeftMarginItem const& rTextLeftMargin(pNode->GetSwAttrSet().GetTextLeftMargin());
+ // #i95907#
+ // #i111284#
+ const SwTextNode *pTextNode = m_pFrame->GetTextNodeForParaProps();
+ const bool bLabelAlignmentActive = IsLabelAlignmentActive( *pTextNode );
+ const bool bListLevelIndentsApplicable = pTextNode->AreListLevelIndentsApplicable() != ::sw::ListLevelIndents::No;
+ const bool bListLevelIndentsApplicableAndLabelAlignmentActive = bListLevelIndentsApplicable && bLabelAlignmentActive;
+
+ // Carefully adjust the text formatting ranges.
+
+ // This whole area desperately needs some rework. There are
+ // quite a couple of values that need to be considered:
+ // 1. paragraph indent
+ // 2. paragraph first line indent
+ // 3. numbering indent
+ // 4. numbering spacing to text
+ // 5. paragraph border
+ // Note: These values have already been used during calculation
+ // of the printing area of the paragraph.
+ const int nLMWithNum = pNode->GetLeftMarginWithNum( true );
+ if ( m_pFrame->IsRightToLeft() )
+ {
+ // this calculation is identical this the calculation for L2R layout - see below
+ mnLeft = m_pFrame->getFrameArea().Left() +
+ m_pFrame->getFramePrintArea().Left() +
+ nLMWithNum -
+ pNode->GetLeftMarginWithNum() -
+ // #i95907#
+ // #i111284#
+ // rSpace.GetLeft() + rSpace.GetTextLeft();
+ (rTextLeftMargin.GetLeft(rFirstLine) - rTextLeftMargin.GetTextLeft());
+ }
+ else
+ {
+ // #i95907#
+ // #i111284#
+ if ( bListLevelIndentsApplicableAndLabelAlignmentActive ||
+ !pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) )
+ {
+ // this calculation is identical this the calculation for R2L layout - see above
+ mnLeft = m_pFrame->getFrameArea().Left() +
+ m_pFrame->getFramePrintArea().Left() +
+ nLMWithNum -
+ pNode->GetLeftMarginWithNum() -
+ // #i95907#
+ // #i111284#
+ (rTextLeftMargin.GetLeft(rFirstLine) - rTextLeftMargin.GetTextLeft());
+ }
+ else
+ {
+ mnLeft = m_pFrame->getFrameArea().Left() +
+ std::max(tools::Long(rTextLeftMargin.GetTextLeft() + nLMWithNum),
+ m_pFrame->getFramePrintArea().Left() );
+ }
+ }
+
+ mnRight = m_pFrame->getFrameArea().Left() + m_pFrame->getFramePrintArea().Left() + m_pFrame->getFramePrintArea().Width();
+
+ if( mnLeft >= mnRight &&
+ // #i53066# Omit adjustment of nLeft for numbered
+ // paras inside cells inside new documents:
+ ( pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) ||
+ !m_pFrame->IsInTab() ||
+ (bListLevelIndentsApplicable && nLMWithNum == rTextLeftMargin.GetTextLeft())
+ || (!bLabelAlignmentActive && nLMWithNum == 0)))
+ {
+ mnLeft = m_pFrame->getFramePrintArea().Left() + m_pFrame->getFrameArea().Left();
+ if( mnLeft >= mnRight ) // e.g. with large paragraph indentations in slim table columns
+ mnRight = mnLeft + 1; // einen goennen wir uns immer
+ }
+
+ if( m_pFrame->IsFollow() && m_pFrame->GetOffset() )
+ mnFirst = mnLeft;
+ else
+ {
+ short nFLOfst = 0;
+ tools::Long nFirstLineOfs = 0;
+ if( !pNode->GetFirstLineOfsWithNum( nFLOfst ) &&
+ rFirstLine.IsAutoFirst())
+ {
+ nFirstLineOfs = GetFnt()->GetSize( GetFnt()->GetActual() ).Height();
+ LanguageType const aLang = m_pFrame->GetLangOfChar(
+ TextFrameIndex(0), css::i18n::ScriptType::ASIAN);
+ if (aLang != LANGUAGE_KOREAN && aLang != LANGUAGE_JAPANESE)
+ nFirstLineOfs<<=1;
+
+ // tdf#129448: Auto first-line indent should not be effected by line space.
+ // Below is for compatibility with old documents.
+ if (!pNode->getIDocumentSettingAccess()->get(DocumentSettingId::AUTO_FIRST_LINE_INDENT_DISREGARD_LINE_SPACE))
+ {
+ const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
+ if( pSpace )
+ {
+ switch( pSpace->GetLineSpaceRule() )
+ {
+ case SvxLineSpaceRule::Auto:
+ break;
+ case SvxLineSpaceRule::Min:
+ {
+ if( nFirstLineOfs < pSpace->GetLineHeight() )
+ nFirstLineOfs = pSpace->GetLineHeight();
+ break;
+ }
+ case SvxLineSpaceRule::Fix:
+ nFirstLineOfs = pSpace->GetLineHeight();
+ break;
+ default: OSL_FAIL( ": unknown LineSpaceRule" );
+ }
+ switch( pSpace->GetInterLineSpaceRule() )
+ {
+ case SvxInterLineSpaceRule::Off:
+ break;
+ case SvxInterLineSpaceRule::Prop:
+ {
+ tools::Long nTmp = pSpace->GetPropLineSpace();
+ // 50% is the minimum, at 0% we switch to
+ // the default value 100%...
+ if( nTmp < 50 )
+ nTmp = nTmp ? 50 : 100;
+
+ nTmp *= nFirstLineOfs;
+ nTmp /= 100;
+ if( !nTmp )
+ ++nTmp;
+ nFirstLineOfs = nTmp;
+ break;
+ }
+ case SvxInterLineSpaceRule::Fix:
+ {
+ nFirstLineOfs += pSpace->GetInterLineSpace();
+ break;
+ }
+ default: OSL_FAIL( ": unknown InterLineSpaceRule" );
+ }
+ }
+ }
+ }
+ else
+ nFirstLineOfs = nFLOfst;
+
+ // #i95907#
+ // #i111284#
+ if ( m_pFrame->IsRightToLeft() ||
+ bListLevelIndentsApplicableAndLabelAlignmentActive ||
+ !pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) )
+ {
+ if ( nFirstLineOfs < 0 && m_pFrame->IsInTab() &&
+ mnLeft == m_pFrame->getFramePrintArea().Left() + m_pFrame->getFrameArea().Left() &&
+ !m_pFrame->IsRightToLeft() &&
+ !bListLevelIndentsApplicableAndLabelAlignmentActive )
+ {
+ // tdf#130218 always show hanging indent in narrow table cells
+ // to avoid hiding the text content of the first line
+ mnLeft -= nFirstLineOfs;
+ }
+
+ mnFirst = mnLeft + nFirstLineOfs;
+ }
+ else
+ {
+ mnFirst = m_pFrame->getFrameArea().Left() +
+ std::max(rTextLeftMargin.GetTextLeft() + nLMWithNum + nFirstLineOfs,
+ m_pFrame->getFramePrintArea().Left() );
+ }
+
+ // Note: <SwTextFrame::GetAdditionalFirstLineOffset()> returns a negative
+ // value for the new list label position and space mode LABEL_ALIGNMENT
+ // and label alignment CENTER and RIGHT in L2R layout respectively
+ // label alignment LEFT and CENTER in R2L layout
+ mnFirst += m_pFrame->GetAdditionalFirstLineOffset();
+
+ if( mnFirst >= mnRight )
+ mnFirst = mnRight - 1;
+ }
+ const SvxAdjustItem& rAdjust = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
+ mnAdjust = rAdjust.GetAdjust();
+
+ // left is left and right is right
+ if ( m_pFrame->IsRightToLeft() )
+ {
+ if ( SvxAdjust::Left == mnAdjust )
+ mnAdjust = SvxAdjust::Right;
+ else if ( SvxAdjust::Right == mnAdjust )
+ mnAdjust = SvxAdjust::Left;
+ }
+
+ m_bOneBlock = rAdjust.GetOneWord() == SvxAdjust::Block;
+ m_bLastBlock = rAdjust.GetLastBlock() == SvxAdjust::Block;
+ m_bLastCenter = rAdjust.GetLastBlock() == SvxAdjust::Center;
+
+ // #i91133#
+ mnTabLeft = pNode->GetLeftMarginForTabCalculation();
+
+ DropInit();
+}
+
+void SwTextMargin::DropInit()
+{
+ mnDropLeft = mnDropLines = mnDropHeight = mnDropDescent = 0;
+ const SwParaPortion *pPara = GetInfo().GetParaPortion();
+ if( pPara )
+ {
+ const SwDropPortion *pPorDrop = pPara->FindDropPortion();
+ if ( pPorDrop )
+ {
+ mnDropLeft = pPorDrop->GetDropLeft();
+ mnDropLines = pPorDrop->GetLines();
+ mnDropHeight = pPorDrop->GetDropHeight();
+ mnDropDescent = pPorDrop->GetDropDescent();
+ }
+ }
+}
+
+// The function is interpreting / observing / evaluating / keeping / respecting the first line indention and the specified width.
+SwTwips SwTextMargin::GetLineStart() const
+{
+ SwTwips nRet = GetLeftMargin();
+ if( GetAdjust() != SvxAdjust::Left &&
+ !m_pCurr->GetFirstPortion()->IsMarginPortion() )
+ {
+ // If the first portion is a Margin, then the
+ // adjustment is expressed by the portions.
+ if( GetAdjust() == SvxAdjust::Right )
+ nRet = Right() - CurrWidth();
+ else if( GetAdjust() == SvxAdjust::Center )
+ nRet += (GetLineWidth() - CurrWidth()) / 2;
+ }
+ return nRet;
+}
+
+void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *pNewInf )
+{
+ CtorInitTextMargin( pNewFrame, pNewInf );
+ // 6096: Attention, the iterators are derived!
+ // GetInfo().SetOut( GetInfo().GetWin() );
+}
+
+static bool isTrailingDecoration(SwLinePortion* p)
+{
+ // Optional no-width portion, followed only by no-width portions and/or terminating portions?
+ for (; p; p = p->GetNextPortion())
+ {
+ if (p->IsMarginPortion() || p->IsBreakPortion())
+ return true;
+ if (p->Width())
+ return false;
+ }
+ return true; // no more portions
+}
+
+// tdf#120715 tdf#43100: Make width for some HolePortions, so cursor will be able to move into it.
+// It should not change the layout, so this should be called after the layout is calculated.
+void SwTextCursor::AddExtraBlankWidth()
+{
+ SwLinePortion* pPos = m_pCurr->GetNextPortion();
+ while (pPos)
+ {
+ SwLinePortion* pNextPos = pPos->GetNextPortion();
+ // Do it only if it is the last portion that able to handle the cursor,
+ // else the next portion would miscalculate the cursor position
+ if (pPos->ExtraBlankWidth() && isTrailingDecoration(pNextPos))
+ {
+ pPos->Width(pPos->Width() + pPos->ExtraBlankWidth());
+ pPos->ExtraBlankWidth(0);
+ }
+ pPos = pNextPos;
+ }
+}
+
+// 1170: Ancient bug: Shift-End forgets the last character ...
+void SwTextCursor::GetEndCharRect(SwRect* pOrig, const TextFrameIndex nOfst,
+ SwCursorMoveState* pCMS, const tools::Long nMax )
+{
+ // 1170: Ambiguity of document positions
+ s_bRightMargin = true;
+ CharCursorToLine(nOfst);
+
+ // Somehow twisted: nOfst names the position behind the last
+ // character of the last line == This is the position in front of the first character
+ // of the line, in which we are situated:
+ if( nOfst != GetStart() || !m_pCurr->GetLen() )
+ {
+ // 8810: Master line RightMargin, after that LeftMargin
+ GetCharRect( pOrig, nOfst, pCMS, nMax );
+ s_bRightMargin = nOfst >= GetEnd() && nOfst < TextFrameIndex(GetInfo().GetText().getLength());
+ return;
+ }
+
+ if( !GetPrev() || !GetPrev()->GetLen() || !PrevLine() )
+ {
+ GetCharRect( pOrig, nOfst, pCMS, nMax );
+ return;
+ }
+
+ // If necessary, as catch up, do the adjustment
+ GetAdjusted();
+
+ tools::Long nX = 0;
+ tools::Long nLast = 0;
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+
+ SwTwips nTmpHeight, nTmpAscent;
+ CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+ sal_uInt16 nPorHeight = nTmpHeight;
+ sal_uInt16 nPorAscent = nTmpAscent;
+
+ // Search for the last Text/EndPortion of the line
+ while( pPor )
+ {
+ nX = nX + pPor->Width();
+ if( pPor->InTextGrp() || ( pPor->GetLen() && !pPor->IsFlyPortion()
+ && !pPor->IsHolePortion() ) || pPor->IsBreakPortion() )
+ {
+ nLast = nX;
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+ pPor = pPor->GetNextPortion();
+ }
+
+ const Size aCharSize( 1, nTmpHeight );
+ pOrig->Pos( GetTopLeft() );
+ pOrig->SSize( aCharSize );
+ pOrig->Pos().AdjustX(nLast );
+ const SwTwips nTmpRight = Right() - 1;
+ if( pOrig->Left() > nTmpRight )
+ pOrig->Pos().setX( nTmpRight );
+
+ if ( pCMS && pCMS->m_bRealHeight )
+ {
+ if ( nTmpAscent > nPorAscent )
+ pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent );
+ else
+ pCMS->m_aRealHeight.setX( 0 );
+ OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" );
+ pCMS->m_aRealHeight.setY( nPorHeight );
+ }
+}
+
+// internal function, called by SwTextCursor::GetCharRect() to calculate
+// the relative character position in the current line.
+// pOrig refers to x and y coordinates, width and height of the cursor
+// pCMS is used for restricting the cursor, if there are different font
+// heights in one line ( first value = offset to y of pOrig, second
+// value = real height of (shortened) cursor
+void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst,
+ SwCursorMoveState* pCMS )
+{
+ const OUString aText = GetInfo().GetText();
+ SwTextSizeInfo aInf( GetInfo(), &aText, m_nStart );
+ if( GetPropFont() )
+ aInf.GetFont()->SetProportion( GetPropFont() );
+ SwTwips nTmpAscent, nTmpHeight; // Line height
+ CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+ const Size aCharSize( 1, nTmpHeight );
+ const Point aCharPos;
+ pOrig->Pos( aCharPos );
+ pOrig->SSize( aCharSize );
+
+ // If we are looking for a position inside a field which covers
+ // more than one line we may not skip any "empty portions" at the
+ // beginning of a line
+ const bool bInsideFirstField = pCMS && pCMS->m_pSpecialPos &&
+ ( pCMS->m_pSpecialPos->nLineOfst ||
+ SwSPExtendRange::BEFORE ==
+ pCMS->m_pSpecialPos->nExtendRange );
+
+ bool bWidth = pCMS && pCMS->m_bRealWidth;
+ if( !m_pCurr->GetLen() && !m_pCurr->Width() )
+ {
+ if ( pCMS && pCMS->m_bRealHeight )
+ {
+ pCMS->m_aRealHeight.setX( 0 );
+ pCMS->m_aRealHeight.setY( nTmpHeight );
+ }
+ }
+ else
+ {
+ SwTwips nPorHeight = nTmpHeight;
+ SwTwips nPorAscent = nTmpAscent;
+ SwTwips nX = 0;
+ SwTwips nTmpFirst = 0;
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ SwBidiPortion* pLastBidiPor = nullptr;
+ TextFrameIndex nLastBidiIdx(-1);
+ SwTwips nLastBidiPorWidth = 0;
+ std::deque<sal_uInt16>* pKanaComp = m_pCurr->GetpKanaComp();
+ sal_uInt16 nSpaceIdx = 0;
+ size_t nKanaIdx = 0;
+ tools::Long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0;
+
+ bool bNoText = true;
+
+ // First all portions without Len at beginning of line are skipped.
+ // Exceptions are the mean special portions from WhichFirstPortion:
+ // Num, ErgoSum, FootnoteNum, FieldRests
+ // 8477: but also the only Textportion of an empty line with
+ // Right/Center-Adjustment! So not just pPor->GetExpandPortion() ...
+ while( pPor && !pPor->GetLen() && ! bInsideFirstField )
+ {
+ nX += pPor->Width();
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nX += pPor->CalcSpacing( nSpaceAdd, aInf );
+ if( bNoText )
+ nTmpFirst = nX;
+ // 8670: EndPortions count once as TextPortions.
+ // if( pPor->InTextGrp() || pPor->IsBreakPortion() )
+ if( pPor->InTextGrp() || pPor->IsBreakPortion() || pPor->InTabGrp() )
+ {
+ bNoText = false;
+ nTmpFirst = nX;
+ }
+ if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
+ ++nKanaIdx;
+ }
+ if( pPor->InFixMargGrp() )
+ {
+ if( pPor->IsMarginPortion() )
+ bNoText = false;
+ else
+ {
+ // fix margin portion => next SpaceAdd, KanaComp value
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
+ ++nKanaIdx;
+ }
+ }
+ pPor = pPor->GetNextPortion();
+ }
+
+ if( !pPor )
+ {
+ // There's just Spezialportions.
+ nX = nTmpFirst;
+ }
+ else
+ {
+ if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() &&
+ (!pPor->InFieldGrp() || pPor->GetAscent() ) )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+ while( pPor && !pPor->IsBreakPortion() && ( aInf.GetIdx() < nOfst ||
+ ( bWidth && ( pPor->IsKernPortion() || pPor->IsMultiPortion() ) ) ) )
+ {
+ if( !pPor->IsMarginPortion() && !pPor->IsPostItsPortion() &&
+ (!pPor->InFieldGrp() || pPor->GetAscent() ) )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+
+ // If we are behind the portion, we add the portion width to
+ // nX. Special case: nOfst = aInf.GetIdx() + pPor->GetLen().
+ // For common portions (including BidiPortions) we want to add
+ // the portion width to nX. For MultiPortions, nExtra = 0,
+ // therefore we go to the 'else' branch and start a recursion.
+ const TextFrameIndex nExtra( (pPor->IsMultiPortion()
+ && !static_cast<SwMultiPortion*>(pPor)->IsBidi()
+ && !bWidth)
+ ? 0 : 1 );
+ if ( aInf.GetIdx() + pPor->GetLen() < nOfst + nExtra )
+ {
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nX += pPor->PrtWidth() +
+ pPor->CalcSpacing( nSpaceAdd, aInf );
+ else
+ {
+ if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() )
+ {
+ // update to current SpaceAdd, KanaComp values
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if ( pKanaComp &&
+ ( nKanaIdx + 1 ) < pKanaComp->size()
+ )
+ ++nKanaIdx;
+ }
+ if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
+ !pPor->GetNextPortion()->IsMarginPortion() ) )
+ nX += pPor->PrtWidth();
+ }
+ if( pPor->IsMultiPortion() )
+ {
+ if ( static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
+ ++nKanaIdx;
+ }
+
+ // if we are right behind a BidiPortion, we have to
+ // hold a pointer to the BidiPortion in order to
+ // find the correct cursor position, depending on the
+ // cursor level
+ if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() &&
+ aInf.GetIdx() + pPor->GetLen() == nOfst )
+ {
+ pLastBidiPor = static_cast<SwBidiPortion*>(pPor);
+ nLastBidiIdx = aInf.GetIdx();
+ nLastBidiPorWidth = pLastBidiPor->Width() +
+ pLastBidiPor->CalcSpacing( nSpaceAdd, aInf );
+ }
+ }
+
+ aInf.SetIdx( aInf.GetIdx() + pPor->GetLen() );
+ pPor = pPor->GetNextPortion();
+ }
+ else
+ {
+ if( pPor->IsMultiPortion() )
+ {
+ nTmpAscent = AdjustBaseLine( *m_pCurr, pPor );
+ GetInfo().SetMulti( true );
+ pOrig->Pos().AdjustY(nTmpAscent - nPorAscent );
+
+ if( pCMS && pCMS->m_b2Lines )
+ {
+ const bool bRecursion (pCMS->m_p2Lines);
+ if ( !bRecursion )
+ {
+ pCMS->m_p2Lines.reset(new Sw2LinesPos);
+ pCMS->m_p2Lines->aLine = SwRect(aCharPos, aCharSize);
+ }
+
+ if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
+ {
+ if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
+ pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_270;
+ else
+ pCMS->m_p2Lines->nMultiType = MultiPortionType::ROT_90;
+ }
+ else if( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
+ pCMS->m_p2Lines->nMultiType = MultiPortionType::TWOLINE;
+ else if( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ pCMS->m_p2Lines->nMultiType = MultiPortionType::BIDI;
+ else
+ pCMS->m_p2Lines->nMultiType = MultiPortionType::RUBY;
+
+ SwTwips nTmpWidth = pPor->Width();
+ if( nSpaceAdd )
+ nTmpWidth += pPor->CalcSpacing(nSpaceAdd, aInf);
+
+ SwRect aRect( Point(aCharPos.X() + nX, pOrig->Top() ),
+ Size( nTmpWidth, pPor->Height() ) );
+
+ if ( ! bRecursion )
+ pCMS->m_p2Lines->aPortion = aRect;
+ else
+ pCMS->m_p2Lines->aPortion2 = aRect;
+ }
+
+ // In a multi-portion we use GetCharRect()-function
+ // recursively and must add the x-position
+ // of the multi-portion.
+ TextFrameIndex const nOldStart = m_nStart;
+ SwTwips nOldY = m_nY;
+ sal_uInt8 nOldProp = GetPropFont();
+ m_nStart = aInf.GetIdx();
+ SwLineLayout* pOldCurr = m_pCurr;
+ m_pCurr = &static_cast<SwMultiPortion*>(pPor)->GetRoot();
+ if( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
+ SetPropFont( 50 );
+
+ SwTextGridItem const*const pGrid(
+ GetGridItem(GetTextFrame()->FindPageFrame()));
+ const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
+ const sal_uInt16 nRubyHeight = bHasGrid ?
+ pGrid->GetRubyHeight() : 0;
+
+ if( m_nStart + m_pCurr->GetLen() <= nOfst && GetNext() &&
+ ( ! static_cast<SwMultiPortion*>(pPor)->IsRuby() ||
+ static_cast<SwMultiPortion*>(pPor)->OnTop() ) )
+ {
+ sal_uInt16 nOffset;
+ // in grid mode we may only add the height of the
+ // ruby line if ruby line is on top
+ if ( bHasGrid &&
+ static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
+ static_cast<SwMultiPortion*>(pPor)->OnTop() )
+ nOffset = nRubyHeight;
+ else
+ nOffset = GetLineHeight();
+
+ pOrig->Pos().AdjustY(nOffset );
+ Next();
+ }
+
+ const bool bSpaceChg = static_cast<SwMultiPortion*>(pPor)->
+ ChgSpaceAdd( m_pCurr, nSpaceAdd );
+ Point aOldPos = pOrig->Pos();
+
+ // Ok, for ruby portions in grid mode we have to
+ // temporarily set the inner line height to the
+ // outer line height because that value is needed
+ // for the adjustment inside the recursion
+ const sal_uInt16 nOldRubyHeight = m_pCurr->Height();
+ const sal_uInt16 nOldRubyRealHeight = m_pCurr->GetRealHeight();
+ const bool bChgHeight =
+ static_cast<SwMultiPortion*>(pPor)->IsRuby() && bHasGrid;
+
+ if ( bChgHeight )
+ {
+ m_pCurr->Height( pOldCurr->Height() - nRubyHeight );
+ m_pCurr->SetRealHeight( pOldCurr->GetRealHeight() -
+ nRubyHeight );
+ }
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ {
+ aLayoutModeModifier.Modify(
+ static_cast<SwBidiPortion*>(pPor)->GetLevel() % 2 );
+ }
+
+ GetCharRect_( pOrig, nOfst, pCMS );
+
+ if ( bChgHeight )
+ {
+ m_pCurr->Height( nOldRubyHeight );
+ m_pCurr->SetRealHeight( nOldRubyRealHeight );
+ }
+
+ // if we are still in the first row of
+ // our 2 line multiportion, we use the FirstMulti flag
+ // to indicate this
+ if ( static_cast<SwMultiPortion*>(pPor)->IsDouble() )
+ {
+ // the recursion may have damaged our font size
+ SetPropFont( nOldProp );
+ GetInfo().GetFont()->SetProportion( 100 );
+
+ if ( m_pCurr == &static_cast<SwMultiPortion*>(pPor)->GetRoot() )
+ {
+ GetInfo().SetFirstMulti( true );
+
+ // we want to treat a double line portion like a
+ // single line portion, if there is no text in
+ // the second line
+ if ( !m_pCurr->GetNext() ||
+ !m_pCurr->GetNext()->GetLen() )
+ GetInfo().SetMulti( false );
+ }
+ }
+ // ruby portions are treated like single line portions
+ else if( static_cast<SwMultiPortion*>(pPor)->IsRuby() ||
+ static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ GetInfo().SetMulti( false );
+
+ // calculate cursor values
+ if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
+ {
+ GetInfo().SetMulti( false );
+ tools::Long nTmp = pOrig->Width();
+ pOrig->Width( pOrig->Height() );
+ pOrig->Height( nTmp );
+ nTmp = pOrig->Left() - aOldPos.X();
+
+ // if we travel into our rotated portion from
+ // a line below, we have to take care, that the
+ // y coord in pOrig is less than line height:
+ if ( nTmp )
+ nTmp--;
+
+ pOrig->Pos().setX( nX + aOldPos.X() );
+ if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
+ pOrig->Pos().setY( aOldPos.Y() + nTmp );
+ else
+ pOrig->Pos().setY( aOldPos.Y()
+ + pPor->Height() - nTmp - pOrig->Height() );
+ if ( pCMS && pCMS->m_bRealHeight )
+ {
+ pCMS->m_aRealHeight.setY( -pCMS->m_aRealHeight.Y() );
+ // result for rotated multi portion is not
+ // correct for reverse (270 degree) portions
+ if( static_cast<SwMultiPortion*>(pPor)->IsRevers() )
+ {
+ if ( SvxParaVertAlignItem::Align::Automatic ==
+ GetLineInfo().GetVertAlign() )
+ // if vertical alignment is set to auto,
+ // we switch from base line alignment
+ // to centered alignment
+ pCMS->m_aRealHeight.setX(
+ ( pOrig->Width() +
+ pCMS->m_aRealHeight.Y() ) / 2 );
+ else
+ pCMS->m_aRealHeight.setX(
+ pOrig->Width() -
+ pCMS->m_aRealHeight.X() +
+ pCMS->m_aRealHeight.Y() );
+ }
+ }
+ }
+ else
+ {
+ pOrig->Pos().AdjustY(aOldPos.Y() );
+ if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ {
+ const SwTwips nPorWidth = pPor->Width() +
+ pPor->CalcSpacing( nSpaceAdd, aInf );
+ const SwTwips nInsideOfst = pOrig->Pos().X();
+ pOrig->Pos().setX( nX + nPorWidth -
+ nInsideOfst - pOrig->Width() );
+ }
+ else
+ pOrig->Pos().AdjustX(nX );
+
+ if( static_cast<SwMultiPortion*>(pPor)->HasBrackets() )
+ pOrig->Pos().AdjustX(
+ static_cast<SwDoubleLinePortion*>(pPor)->PreWidth() );
+ }
+
+ if( bSpaceChg )
+ SwDoubleLinePortion::ResetSpaceAdd( m_pCurr );
+
+ m_pCurr = pOldCurr;
+ m_nStart = nOldStart;
+ m_nY = nOldY;
+ m_bPrev = false;
+
+ return;
+ }
+ if ( pPor->PrtWidth() )
+ {
+ // tdf#30731: To get the correct nOfst width, we need
+ // to send the whole portion string to GetTextSize()
+ // and ask it to return the width of nOfst by calling
+ // SetMeasureLen(). Cutting the string at nOfst can
+ // give the wrong width if nOfst is in e.g. the middle
+ // of a ligature. See SwFntObj::DrawText().
+ TextFrameIndex const nOldLen = pPor->GetLen();
+ aInf.SetLen( pPor->GetLen() );
+ pPor->SetLen( nOfst - aInf.GetIdx() );
+ aInf.SetMeasureLen(pPor->GetLen());
+ if (aInf.GetLen() < aInf.GetMeasureLen())
+ {
+ pPor->SetLen(aInf.GetMeasureLen());
+ aInf.SetLen(pPor->GetLen());
+ }
+ if( nX || !pPor->InNumberGrp() )
+ {
+ SeekAndChg( aInf );
+ const bool bOldOnWin = aInf.OnWin();
+ aInf.SetOnWin( false ); // no BULLETs!
+ SwTwips nTmp = nX;
+ aInf.SetKanaComp( pKanaComp );
+ aInf.SetKanaIdx( nKanaIdx );
+ nX += pPor->GetTextSize( aInf ).Width();
+ aInf.SetOnWin( bOldOnWin );
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nX += pPor->CalcSpacing( nSpaceAdd, aInf );
+ if( bWidth )
+ {
+ pPor->SetLen(pPor->GetLen() + TextFrameIndex(1));
+ aInf.SetMeasureLen(pPor->GetLen());
+ if (aInf.GetLen() < aInf.GetMeasureLen())
+ {
+ pPor->SetLen(aInf.GetMeasureLen());
+ aInf.SetLen(pPor->GetLen());
+ }
+ aInf.SetOnWin( false ); // no BULLETs!
+ nTmp += pPor->GetTextSize( aInf ).Width();
+ aInf.SetOnWin( bOldOnWin );
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nTmp += pPor->CalcSpacing(nSpaceAdd, aInf);
+ pOrig->Width( nTmp - nX );
+ }
+ }
+ pPor->SetLen( nOldLen );
+
+ // Shift the cursor with the right border width
+ // Note: nX remains positive because GetTextSize() also include the width of the right border
+ if( aInf.GetIdx() < nOfst && nOfst < aInf.GetIdx() + pPor->GetLen() )
+ {
+ // Find the current drop portion part and use its right border
+ if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 )
+ {
+ SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor);
+ const SwDropPortionPart* pCurrPart = pDrop->GetPart();
+ TextFrameIndex nSumLength(0);
+ while( pCurrPart && (nSumLength += pCurrPart->GetLen()) < nOfst - aInf.GetIdx() )
+ {
+ pCurrPart = pCurrPart->GetFollow();
+ }
+ if( pCurrPart && nSumLength != nOfst - aInf.GetIdx() &&
+ pCurrPart->GetFont().GetRightBorder() && !pCurrPart->GetJoinBorderWithNext() )
+ {
+ nX -= pCurrPart->GetFont().GetRightBorderSpace();
+ }
+ }
+ else if( GetInfo().GetFont()->GetRightBorder() && !pPor->GetJoinBorderWithNext())
+ {
+ nX -= GetInfo().GetFont()->GetRightBorderSpace();
+ }
+ }
+ }
+ bWidth = false;
+ break;
+ }
+ }
+ }
+
+ if( pPor )
+ {
+ OSL_ENSURE( !pPor->InNumberGrp() || bInsideFirstField, "Number surprise" );
+ bool bEmptyField = false;
+ if( pPor->InFieldGrp() && pPor->GetLen() )
+ {
+ SwFieldPortion *pTmp = static_cast<SwFieldPortion*>(pPor);
+ while( pTmp->HasFollow() && pTmp->GetExp().isEmpty() )
+ {
+ sal_uInt16 nAddX = pTmp->Width();
+ SwLinePortion *pNext = pTmp->GetNextPortion();
+ while( pNext && !pNext->InFieldGrp() )
+ {
+ OSL_ENSURE( !pNext->GetLen(), "Where's my field follow?" );
+ nAddX = nAddX + pNext->Width();
+ pNext = pNext->GetNextPortion();
+ }
+ if( !pNext )
+ break;
+ pTmp = static_cast<SwFieldPortion*>(pNext);
+ nPorHeight = pTmp->Height();
+ nPorAscent = pTmp->GetAscent();
+ nX += nAddX;
+ bEmptyField = true;
+ }
+ }
+ // 8513: Fields in justified text, skipped
+ while( pPor && !pPor->GetLen() && ! bInsideFirstField &&
+ ( pPor->IsFlyPortion() || pPor->IsKernPortion() ||
+ pPor->IsBlankPortion() || pPor->InTabGrp() ||
+ ( !bEmptyField && pPor->InFieldGrp() ) ) )
+ {
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nX += pPor->PrtWidth() +
+ pPor->CalcSpacing( nSpaceAdd, aInf );
+ else
+ {
+ if( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
+ ++nKanaIdx;
+ }
+ if ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
+ !pPor->GetNextPortion()->IsMarginPortion() ) )
+ nX += pPor->PrtWidth();
+ }
+ if( pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp && ( nKanaIdx + 1 ) < pKanaComp->size() )
+ ++nKanaIdx;
+ }
+ if( !pPor->IsFlyPortion() )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+ pPor = pPor->GetNextPortion();
+ }
+
+ if( aInf.GetIdx() == nOfst && pPor && pPor->InHyphGrp() &&
+ pPor->GetNextPortion() && pPor->GetNextPortion()->InFixGrp() )
+ {
+ // All special portions have to be skipped
+ // Taking the German word "zusammen" as example: zu-[FLY]sammen, 'u' == 19, 's' == 20; Right()
+ // Without the adjustment we end up in front of '-', with the
+ // adjustment in front of the 's'.
+ while( pPor && !pPor->GetLen() )
+ {
+ nX += pPor->Width();
+ if( !pPor->IsMarginPortion() )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+ pPor = pPor->GetNextPortion();
+ }
+ }
+ if( pPor && pCMS )
+ {
+ if( pCMS->m_bFieldInfo && pPor->InFieldGrp() && pPor->Width() )
+ pOrig->Width( pPor->Width() );
+ if( pPor->IsDropPortion() )
+ {
+ nPorAscent = static_cast<SwDropPortion*>(pPor)->GetDropHeight();
+ // The drop height is only calculated, if we have more than
+ // one line. Otherwise it is 0.
+ if ( ! nPorAscent)
+ nPorAscent = pPor->Height();
+ nPorHeight = nPorAscent;
+ pOrig->Height( nPorHeight +
+ static_cast<SwDropPortion*>(pPor)->GetDropDescent() );
+ if( nTmpHeight < pOrig->Height() )
+ {
+ nTmpAscent = nPorAscent;
+ nTmpHeight = sal_uInt16( pOrig->Height() );
+ }
+ }
+ if( bWidth && pPor->PrtWidth() && pPor->GetLen() &&
+ aInf.GetIdx() == nOfst )
+ {
+ if( !pPor->IsFlyPortion() && pPor->Height() &&
+ pPor->GetAscent() )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+ SwTwips nTmp;
+ if (TextFrameIndex(2) > pPor->GetLen())
+ {
+ nTmp = pPor->Width();
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nTmp += pPor->CalcSpacing( nSpaceAdd, aInf );
+ }
+ else
+ {
+ const bool bOldOnWin = aInf.OnWin();
+ TextFrameIndex const nOldLen = pPor->GetLen();
+ aInf.SetLen( pPor->GetLen() );
+ pPor->SetLen( TextFrameIndex(1) );
+ aInf.SetMeasureLen(pPor->GetLen());
+ if (aInf.GetLen() < aInf.GetMeasureLen())
+ {
+ pPor->SetLen(aInf.GetMeasureLen());
+ aInf.SetLen(pPor->GetLen());
+ }
+ SeekAndChg( aInf );
+ aInf.SetOnWin( false ); // no BULLETs!
+ aInf.SetKanaComp( pKanaComp );
+ aInf.SetKanaIdx( nKanaIdx );
+ nTmp = pPor->GetTextSize( aInf ).Width();
+ aInf.SetOnWin( bOldOnWin );
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ nTmp += pPor->CalcSpacing( nSpaceAdd, aInf );
+ pPor->SetLen( nOldLen );
+ }
+ pOrig->Width( nTmp );
+ }
+
+ // travel inside field portion?
+ if ( pCMS->m_pSpecialPos )
+ {
+ // apply attributes to font
+ Seek( nOfst );
+ lcl_GetCharRectInsideField( aInf, *pOrig, *pCMS, *pPor );
+ }
+ }
+ }
+
+ // special case: We are at the beginning of a BidiPortion or
+ // directly behind a BidiPortion
+ if ( pCMS &&
+ ( pLastBidiPor ||
+ ( pPor &&
+ pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ) )
+ {
+ // we determine if the cursor has to blink before or behind
+ // the bidi portion
+ if ( pLastBidiPor )
+ {
+ const sal_uInt8 nPortionLevel = pLastBidiPor->GetLevel();
+
+ if ( pCMS->m_nCursorBidiLevel >= nPortionLevel )
+ {
+ // we came from inside the bidi portion, we want to blink
+ // behind the portion
+ pOrig->Pos().AdjustX( -nLastBidiPorWidth );
+
+ // Again, there is a special case: logically behind
+ // the portion can actually mean that the cursor is inside
+ // the portion. This can happen is the last portion
+ // inside the bidi portion is a nested bidi portion
+ SwLineLayout& rLineLayout =
+ static_cast<SwMultiPortion*>(pLastBidiPor)->GetRoot();
+
+ const SwLinePortion *pLast = rLineLayout.FindLastPortion();
+ if ( pLast->IsMultiPortion() )
+ {
+ OSL_ENSURE( static_cast<const SwMultiPortion*>(pLast)->IsBidi(),
+ "Non-BidiPortion inside BidiPortion" );
+ TextFrameIndex const nIdx = aInf.GetIdx();
+ // correct the index before using CalcSpacing.
+ aInf.SetIdx(nLastBidiIdx);
+ pOrig->Pos().AdjustX(pLast->Width() +
+ pLast->CalcSpacing( nSpaceAdd, aInf ) );
+ aInf.SetIdx(nIdx);
+ }
+ }
+ }
+ else
+ {
+ const sal_uInt8 nPortionLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel();
+
+ if ( pCMS->m_nCursorBidiLevel >= nPortionLevel )
+ {
+ // we came from inside the bidi portion, we want to blink
+ // behind the portion
+ pOrig->Pos().AdjustX(pPor->Width() +
+ pPor->CalcSpacing( nSpaceAdd, aInf ) );
+ }
+ }
+ }
+
+ pOrig->Pos().AdjustX(nX );
+
+ if ( pCMS && pCMS->m_bRealHeight )
+ {
+ nTmpAscent = AdjustBaseLine( *m_pCurr, nullptr, nPorHeight, nPorAscent );
+ if ( nTmpAscent > nPorAscent )
+ pCMS->m_aRealHeight.setX( nTmpAscent - nPorAscent );
+ else
+ pCMS->m_aRealHeight.setX( 0 );
+ OSL_ENSURE( nPorHeight, "GetCharRect: Missing Portion-Height" );
+ if ( nTmpHeight > nPorHeight )
+ pCMS->m_aRealHeight.setY( nPorHeight );
+ else
+ pCMS->m_aRealHeight.setY( nTmpHeight );
+ }
+ }
+}
+
+void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst,
+ SwCursorMoveState* pCMS, const tools::Long nMax )
+{
+ CharCursorToLine(nOfst);
+
+ // Indicates that a position inside a special portion (field, number portion)
+ // is requested.
+ const bool bSpecialPos = pCMS && pCMS->m_pSpecialPos;
+ TextFrameIndex nFindOfst = nOfst;
+
+ if ( bSpecialPos )
+ {
+ const SwSPExtendRange nExtendRange = pCMS->m_pSpecialPos->nExtendRange;
+
+ OSL_ENSURE( ! pCMS->m_pSpecialPos->nLineOfst || SwSPExtendRange::BEFORE != nExtendRange,
+ "LineOffset AND Number Portion?" );
+
+ // portions which are behind the string
+ if ( SwSPExtendRange::BEHIND == nExtendRange )
+ ++nFindOfst;
+
+ // skip lines for fields which cover more than one line
+ for ( sal_Int32 i = 0; i < pCMS->m_pSpecialPos->nLineOfst; i++ )
+ Next();
+ }
+
+ // If necessary, as catch up, do the adjustment
+ GetAdjusted();
+ AddExtraBlankWidth();
+
+ const Point aCharPos( GetTopLeft() );
+
+ GetCharRect_( pOrig, nFindOfst, pCMS );
+
+ pOrig->Pos().AdjustX(aCharPos.X() );
+ pOrig->Pos().AdjustY(aCharPos.Y() );
+
+ if( pCMS && pCMS->m_b2Lines && pCMS->m_p2Lines )
+ {
+ pCMS->m_p2Lines->aLine.Pos().AdjustX(aCharPos.X() );
+ pCMS->m_p2Lines->aLine.Pos().AdjustY(aCharPos.Y() );
+ pCMS->m_p2Lines->aPortion.Pos().AdjustX(aCharPos.X() );
+ pCMS->m_p2Lines->aPortion.Pos().AdjustY(aCharPos.Y() );
+ }
+
+ if( nMax )
+ {
+ if( pOrig->Top() + pOrig->Height() > nMax )
+ {
+ if( pOrig->Top() > nMax )
+ pOrig->Top( nMax );
+ pOrig->Height( nMax - pOrig->Top() );
+ }
+ if ( pCMS && pCMS->m_bRealHeight && pCMS->m_aRealHeight.Y() >= 0 )
+ {
+ tools::Long nTmp = pCMS->m_aRealHeight.X() + pOrig->Top();
+ if( nTmp >= nMax )
+ {
+ pCMS->m_aRealHeight.setX( nMax - pOrig->Top() );
+ pCMS->m_aRealHeight.setY( 0 );
+ }
+ else if( nTmp + pCMS->m_aRealHeight.Y() > nMax )
+ pCMS->m_aRealHeight.setY( nMax - nTmp );
+ }
+ }
+}
+
+/**
+ * Determines if SwTextCursor::GetModelPositionForViewPoint() should consider the next portion when calculating the
+ * doc model position from a Point.
+ */
+static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, SwTwips nWidth30, sal_uInt16 nX)
+{
+ if (!pPor->GetNextPortion() || pPor->IsBreakPortion())
+ {
+ return false;
+ }
+
+ // tdf#138592: consider all following zero-width text portions of current text portion,
+ // like combining characters.
+ if (nWidth30 == nX && pPor->IsTextPortion() && pPor->GetNextPortion()->IsTextPortion()
+ && pPor->GetNextPortion()->Width() == 0)
+ return true;
+
+ // If we're past the target position, stop the iteration in general.
+ // Exception: don't stop the iteration between as-char fly portions and their comments.
+ if (nWidth30 >= nX && (!pPor->IsFlyCntPortion() || !pPor->GetNextPortion()->IsPostItsPortion()))
+ {
+ // Normally returns false.
+
+ // Another exception: If the cursor is at the very end of the portion, and the next portion is a comment,
+ // then place the cursor after the zero-width comment. This is primarily to benefit the very end of a line.
+ return nWidth30 == nX && pPor->GetNextPortion()->IsPostItsPortion();
+ }
+
+ return true;
+}
+
+// Return: Offset in String
+TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, const Point &rPoint,
+ bool bChgNode, SwCursorMoveState* pCMS ) const
+{
+ // If necessary, as catch up, do the adjustment
+ GetAdjusted();
+
+ const OUString &rText = GetInfo().GetText();
+ TextFrameIndex nOffset(0);
+
+ // x is the horizontal offset within the line.
+ SwTwips x = rPoint.X();
+ const SwTwips nLeftMargin = GetLineStart();
+ SwTwips nRightMargin = GetLineEnd() +
+ ( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 );
+ if( nRightMargin == nLeftMargin )
+ nRightMargin += 30;
+
+ const bool bLeftOver = x < nLeftMargin;
+ if( bLeftOver )
+ x = nLeftMargin;
+ const bool bRightOver = x > nRightMargin;
+ const bool bRightAllowed = pCMS && ( pCMS->m_eState == CursorMoveState::NONE );
+
+ // Until here everything in document coordinates.
+ x -= nLeftMargin;
+
+ SwTwips nX = x;
+
+ // If there are attribute changes in the line, search for the paragraph,
+ // in which nX is situated.
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ TextFrameIndex nCurrStart = m_nStart;
+ bool bLastHyph = false;
+
+ std::deque<sal_uInt16> *pKanaComp = m_pCurr->GetpKanaComp();
+ TextFrameIndex const nOldIdx = GetInfo().GetIdx();
+ sal_uInt16 nSpaceIdx = 0;
+ size_t nKanaIdx = 0;
+ tools::Long nSpaceAdd = m_pCurr->IsSpaceAdd() ? m_pCurr->GetLLSpaceAdd( 0 ) : 0;
+ short nKanaComp = pKanaComp ? (*pKanaComp)[0] : 0;
+
+ // nWidth is the width of the line, or the width of
+ // the paragraph with the font change, in which nX is situated.
+
+ SwTwips nWidth = pPor->Width();
+ if ( m_pCurr->IsSpaceAdd() || pKanaComp )
+ {
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ {
+ const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart );
+ nWidth = nWidth + pPor->CalcSpacing( nSpaceAdd, GetInfo() );
+ }
+ if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) ||
+ ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
+ )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if( pKanaComp )
+ {
+ if ( nKanaIdx + 1 < pKanaComp->size() )
+ nKanaComp = (*pKanaComp)[++nKanaIdx];
+ else
+ nKanaComp = 0;
+ }
+ }
+ }
+
+ SwTwips nWidth30;
+ if ( pPor->IsPostItsPortion() )
+ nWidth30 = 0;
+ else
+ nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ?
+ 30 :
+ nWidth;
+
+ while (ConsiderNextPortionForCursorOffset(pPor, nWidth30, nX))
+ {
+ nX = nX - nWidth;
+ nCurrStart = nCurrStart + pPor->GetLen();
+ pPor = pPor->GetNextPortion();
+ nWidth = pPor->Width();
+ if ( m_pCurr->IsSpaceAdd() || pKanaComp )
+ {
+ if ( pPor->InSpaceGrp() && nSpaceAdd )
+ {
+ const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart );
+ nWidth = nWidth + pPor->CalcSpacing( nSpaceAdd, GetInfo() );
+ }
+
+ if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) ||
+ ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() )
+ )
+ {
+ if ( m_pCurr->IsSpaceAdd() )
+ {
+ if ( ++nSpaceIdx < m_pCurr->GetLLSpaceAddCount() )
+ nSpaceAdd = m_pCurr->GetLLSpaceAdd( nSpaceIdx );
+ else
+ nSpaceAdd = 0;
+ }
+
+ if ( pKanaComp )
+ {
+ if( nKanaIdx + 1 < pKanaComp->size() )
+ nKanaComp = (*pKanaComp)[++nKanaIdx];
+ else
+ nKanaComp = 0;
+ }
+ }
+ }
+
+ if ( pPor->IsPostItsPortion() )
+ nWidth30 = 0;
+ else
+ nWidth30 = ! nWidth && pPor->GetLen() && pPor->InToxRefOrFieldGrp() ?
+ 30 :
+ nWidth;
+ if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() )
+ bLastHyph = pPor->InHyphGrp();
+ }
+
+ const bool bLastPortion = (nullptr == pPor->GetNextPortion());
+
+ if( nX==nWidth )
+ {
+ SwLinePortion *pNextPor = pPor->GetNextPortion();
+ while( pNextPor && pNextPor->InFieldGrp() && !pNextPor->Width() )
+ {
+ nCurrStart = nCurrStart + pPor->GetLen();
+ pPor = pNextPor;
+ if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() )
+ bLastHyph = pPor->InHyphGrp();
+ pNextPor = pPor->GetNextPortion();
+ }
+ }
+
+ const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nOldIdx );
+
+ TextFrameIndex nLength = pPor->GetLen();
+
+ const bool bFieldInfo = pCMS && pCMS->m_bFieldInfo;
+
+ if( bFieldInfo && ( nWidth30 < nX || bRightOver || bLeftOver ||
+ ( pPor->InNumberGrp() && !pPor->IsFootnoteNumPortion() ) ||
+ ( pPor->IsMarginPortion() && nWidth > nX + 30 ) ) )
+ pCMS->m_bPosCorr = true;
+
+ // #i27615#
+ if (pCMS && pCMS->m_bInFrontOfLabel)
+ {
+ if (2 * nX >= nWidth || !pPor->InNumberGrp() || pPor->IsFootnoteNumPortion())
+ pCMS->m_bInFrontOfLabel = false;
+ }
+
+ // 7684: We are exactly ended up at their HyphPortion. It is our task to
+ // provide, that we end up in the String.
+ // 7993: If length = 0, then we must exit...
+ if( !nLength )
+ {
+ if( pCMS )
+ {
+ if( pPor->IsFlyPortion() && bFieldInfo )
+ pCMS->m_bPosCorr = true;
+
+ if (!bRightOver && nX)
+ {
+ if( pPor->IsFootnoteNumPortion())
+ pCMS->m_bFootnoteNoInfo = true;
+ else if (pPor->InNumberGrp() ) // #i23726#
+ {
+ pCMS->m_nInNumPortionOffset = nX;
+ pCMS->m_bInNumPortion = true;
+ }
+ }
+ }
+ if( !nCurrStart )
+ return TextFrameIndex(0);
+
+ // 7849, 7816: pPor->GetHyphPortion is mandatory!
+ if( ( !bRightAllowed && bLastHyph ) ||
+ ( pPor->IsMarginPortion() && !pPor->GetNextPortion() &&
+ // 46598: Consider the situation: We might end up behind the last character,
+ // in the last line of a centered paragraph
+ nCurrStart < TextFrameIndex(rText.getLength())))
+ --nCurrStart;
+ else if( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->IsFollow()
+ && nWidth > nX )
+ {
+ if( bFieldInfo )
+ --nCurrStart;
+ else
+ {
+ sal_uInt16 nHeight = pPor->Height();
+ if ( !nHeight || nHeight > nWidth )
+ nHeight = nWidth;
+ if( bChgNode && nWidth - nHeight/2 > nX )
+ --nCurrStart;
+ }
+ }
+ if (!pPor->InFieldGrp() || !static_cast<SwFieldPortion const*>(pPor)->IsFollow()
+ || !pCMS || !pCMS->m_pSpecialPos)
+ {
+ return nCurrStart;
+ }
+ }
+ if (TextFrameIndex(1) == nLength || pPor->InFieldGrp())
+ {
+ if ( nWidth )
+ {
+ // no quick return for as-character frames, we want to peek inside
+ if (!(bChgNode && pPos && pPor->IsFlyCntPortion())
+ // if we want to get the position inside the field, we should not return
+ && (!pCMS || !pCMS->m_pSpecialPos))
+ {
+ if ( pPor->InFieldGrp() ||
+ ( pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->IsBidi() ) )
+ {
+ sal_uInt16 nHeight = 0;
+ if( !bFieldInfo )
+ {
+ nHeight = pPor->Height();
+ if ( !nHeight || nHeight > nWidth )
+ nHeight = nWidth;
+ }
+
+ if( nWidth - nHeight/2 <= nX &&
+ ( ! pPor->InFieldGrp() ||
+ !static_cast<SwFieldPortion*>(pPor)->HasFollow() ) )
+ {
+ if (pPor->InFieldGrp())
+ {
+ nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen();
+ }
+ else
+ {
+ ++nCurrStart;
+ }
+ }
+ }
+ else if ( ( !pPor->IsFlyPortion() || ( pPor->GetNextPortion() &&
+ !pPor->GetNextPortion()->IsMarginPortion() &&
+ !pPor->GetNextPortion()->IsHolePortion() ) )
+ && ( nWidth/2 < nX ) &&
+ ( !bFieldInfo ||
+ ( pPor->GetNextPortion() &&
+ pPor->GetNextPortion()->IsPostItsPortion() ) )
+ && ( bRightAllowed || !bLastHyph ))
+ ++nCurrStart;
+
+ return nCurrStart;
+ }
+ }
+ else
+ {
+ if ( pPor->IsPostItsPortion() || pPor->IsBreakPortion() ||
+ pPor->InToxRefGrp() )
+ {
+ SwPostItsPortion* pPostItsPortion = pPor->IsPostItsPortion() ? dynamic_cast<SwPostItsPortion*>(pPor) : nullptr;
+ if (pPostItsPortion)
+ {
+ if (!pPostItsPortion->IsScript()) // tdf#141079
+ {
+ // Offset would be nCurrStart + nLength below, do the same for post-it portions.
+ nCurrStart += pPor->GetLen();
+ }
+ }
+ return nCurrStart;
+ }
+ if ( pPor->InFieldGrp() )
+ {
+ if( bRightOver && !static_cast<SwFieldPortion*>(pPor)->HasFollow() )
+ {
+ nCurrStart += static_cast<SwFieldPortion*>(pPor)->GetFieldLen();
+ }
+ return nCurrStart;
+ }
+ }
+ }
+
+ // Skip space at the end of the line
+ if( bLastPortion && (m_pCurr->GetNext() || m_pFrame->GetFollow() )
+ && sal_Int32(nLength) != 0
+ && rText[sal_Int32(nCurrStart + nLength) - 1] == ' ')
+ {
+ --nLength;
+ }
+
+ if( nWidth > nX ||
+ ( nWidth == nX && pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsDouble() ) )
+ {
+ if( pPor->IsMultiPortion() )
+ {
+ // In a multi-portion we use GetModelPositionForViewPoint()-function recursively
+ SwTwips nTmpY = rPoint.Y() - m_pCurr->GetAscent() + pPor->GetAscent();
+ // if we are in the first line of a double line portion, we have
+ // to add a value to nTmpY for not staying in this line
+ // we also want to skip the first line, if we are inside ruby
+ if ( ( static_cast<SwTextSizeInfo*>(m_pInf)->IsMulti() &&
+ static_cast<SwTextSizeInfo*>(m_pInf)->IsFirstMulti() ) ||
+ ( static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
+ static_cast<SwMultiPortion*>(pPor)->OnTop() ) )
+ nTmpY += static_cast<SwMultiPortion*>(pPor)->Height();
+
+ // Important for cursor traveling in ruby portions:
+ // We have to set nTmpY to 0 in order to stay in the first row
+ // if the phonetic line is the second row
+ if ( static_cast<SwMultiPortion*>(pPor)->IsRuby() &&
+ ! static_cast<SwMultiPortion*>(pPor)->OnTop() )
+ nTmpY = 0;
+
+ SwTextCursorSave aSave( const_cast<SwTextCursor*>(this), static_cast<SwMultiPortion*>(pPor),
+ nTmpY, nX, nCurrStart, nSpaceAdd );
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ if ( static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ {
+ const sal_uInt8 nBidiLevel = static_cast<SwBidiPortion*>(pPor)->GetLevel();
+ aLayoutModeModifier.Modify( nBidiLevel % 2 );
+ }
+
+ if( static_cast<SwMultiPortion*>(pPor)->HasRotation() )
+ {
+ nTmpY -= m_nY;
+ if( !static_cast<SwMultiPortion*>(pPor)->IsRevers() )
+ nTmpY = pPor->Height() - nTmpY;
+ if( nTmpY < 0 )
+ nTmpY = 0;
+ nX = o3tl::narrowing<sal_uInt16>(nTmpY);
+ }
+
+ if( static_cast<SwMultiPortion*>(pPor)->HasBrackets() )
+ {
+ const sal_uInt16 nPreWidth = static_cast<SwDoubleLinePortion*>(pPor)->PreWidth();
+ if ( nX > nPreWidth )
+ nX = nX - nPreWidth;
+ else
+ nX = 0;
+ }
+
+ return GetModelPositionForViewPoint( pPos, Point( GetLineStart() + nX, rPoint.Y() ),
+ bChgNode, pCMS );
+ }
+ if( pPor->InTextGrp() || pPor->IsHolePortion() )
+ {
+ sal_uInt8 nOldProp;
+ if( GetPropFont() )
+ {
+ const_cast<SwFont*>(GetFnt())->SetProportion( GetPropFont() );
+ nOldProp = GetFnt()->GetPropr();
+ }
+ else
+ nOldProp = 0;
+ {
+ SwTextSizeInfo aSizeInf( GetInfo(), &rText, nCurrStart );
+ const_cast<SwTextCursor*>(this)->SeekAndChg( aSizeInf );
+ SwTextSlot aDiffText( &aSizeInf, pPor, false, false );
+ SwFontSave aSave( aSizeInf, pPor->IsDropPortion() ?
+ static_cast<SwDropPortion*>(pPor)->GetFnt() : nullptr );
+
+ SwParaPortion* pPara = const_cast<SwParaPortion*>(GetInfo().GetParaPortion());
+ OSL_ENSURE( pPara, "No paragraph!" );
+
+ // protect against bugs elsewhere
+ SAL_WARN_IF( aSizeInf.GetIdx().get() + pPor->GetLen().get() > aSizeInf.GetText().getLength(), "sw", "portion and text are out of sync" );
+ TextFrameIndex nSafeLen( std::min(pPor->GetLen().get(), aSizeInf.GetText().getLength() - aSizeInf.GetIdx().get()) );
+
+ SwDrawTextInfo aDrawInf( aSizeInf.GetVsh(),
+ *aSizeInf.GetOut(),
+ &pPara->GetScriptInfo(),
+ aSizeInf.GetText(),
+ aSizeInf.GetIdx(),
+ nSafeLen );
+
+ // Drop portion works like a multi portion, just its parts are not portions
+ if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 )
+ {
+ SwDropPortion* pDrop = static_cast<SwDropPortion*>(pPor);
+ const SwDropPortionPart* pCurrPart = pDrop->GetPart();
+ sal_uInt16 nSumWidth = 0;
+ sal_uInt16 nSumBorderWidth = 0;
+ // Shift offset with the right and left border of previous parts and left border of actual one
+ while (pCurrPart && nSumWidth <= nX - sal_Int32(nCurrStart))
+ {
+ nSumWidth += pCurrPart->GetWidth();
+ if( pCurrPart->GetFont().GetLeftBorder() && !pCurrPart->GetJoinBorderWithPrev() )
+ {
+ nSumBorderWidth += pCurrPart->GetFont().GetLeftBorderSpace();
+ }
+ if (nSumWidth <= nX - sal_Int32(nCurrStart) && pCurrPart->GetFont().GetRightBorder() &&
+ !pCurrPart->GetJoinBorderWithNext() )
+ {
+ nSumBorderWidth += pCurrPart->GetFont().GetRightBorderSpace();
+ }
+ pCurrPart = pCurrPart->GetFollow();
+ }
+ nX = std::max(static_cast<SwTwips>(0), nX - nSumBorderWidth);
+ }
+ // Shift the offset with the left border width
+ else if( GetInfo().GetFont()->GetLeftBorder() && !pPor->GetJoinBorderWithPrev() )
+ {
+ nX = std::max(static_cast<SwTwips>(0), nX - GetInfo().GetFont()->GetLeftBorderSpace());
+ }
+
+ aDrawInf.SetOffset( nX );
+
+ if ( nSpaceAdd )
+ {
+ TextFrameIndex nCharCnt(0);
+ // #i41860# Thai justified alignment needs some
+ // additional information:
+ aDrawInf.SetNumberOfBlanks( pPor->InTextGrp() ?
+ static_cast<const SwTextPortion*>(pPor)->GetSpaceCnt( aSizeInf, nCharCnt ) :
+ TextFrameIndex(0) );
+ }
+
+ if ( pPor->InFieldGrp() && pCMS && pCMS->m_pSpecialPos )
+ aDrawInf.SetLen( TextFrameIndex(COMPLETE_STRING) );
+
+ aDrawInf.SetSpace( nSpaceAdd );
+ aDrawInf.SetFont( aSizeInf.GetFont() );
+ aDrawInf.SetFrame( m_pFrame );
+ aDrawInf.SetSnapToGrid( aSizeInf.SnapToGrid() );
+ aDrawInf.SetPosMatchesBounds( pCMS && pCMS->m_bPosMatchesBounds );
+
+ if ( SwFontScript::CJK == aSizeInf.GetFont()->GetActual() &&
+ pPara->GetScriptInfo().CountCompChg() &&
+ ! pPor->InFieldGrp() )
+ aDrawInf.SetKanaComp( nKanaComp );
+
+ nLength = aSizeInf.GetFont()->GetModelPositionForViewPoint_( aDrawInf );
+
+ // get position inside field portion?
+ if ( pPor->InFieldGrp() && pCMS && pCMS->m_pSpecialPos )
+ {
+ pCMS->m_pSpecialPos->nCharOfst = sal_Int32(nLength);
+ // follow portions: need to add the length of all previous
+ // portions for the same field
+ if (static_cast<SwFieldPortion const*>(pPor)->IsFollow())
+ {
+ int nLines(0);
+ std::vector<SwFieldPortion const*> portions;
+ for (SwLineLayout const* pLine = GetInfo().GetParaPortion();
+ true; pLine = pLine->GetNext())
+ {
+ for (SwLinePortion const* pLP = pLine; pLP && pLP != pPor; pLP = pLP->GetNextPortion())
+ {
+ if (pLP->InFieldGrp())
+ {
+ SwFieldPortion const* pField(static_cast<SwFieldPortion const*>(pLP));
+ if (!pField->IsFollow())
+ {
+ nLines = 0;
+ portions.clear();
+ }
+ if (pLine == m_pCurr)
+ {
+ portions.emplace_back(pField);
+ }
+ }
+ }
+ if (pLine == m_pCurr)
+ {
+ break;
+ }
+ ++nLines;
+ }
+ for (SwFieldPortion const* pField : portions)
+ {
+ pCMS->m_pSpecialPos->nCharOfst += pField->GetExp().getLength();
+ }
+ pCMS->m_pSpecialPos->nLineOfst = nLines;
+ }
+ nLength = TextFrameIndex(0);
+ }
+ else if (bFieldInfo && nLength == pPor->GetLen() &&
+ (! pPor->GetNextPortion() ||
+ ! pPor->GetNextPortion()->IsPostItsPortion()))
+ {
+ --nLength;
+ }
+
+ // set cursor bidi level
+ if ( pCMS )
+ pCMS->m_nCursorBidiLevel =
+ aDrawInf.GetCursorBidiLevel();
+ }
+ if( nOldProp )
+ const_cast<SwFont*>(GetFnt())->SetProportion( nOldProp );
+ }
+ else
+ {
+ sw::FlyContentPortion* pFlyPor(nullptr);
+ if(bChgNode && pPos && (pFlyPor = dynamic_cast<sw::FlyContentPortion*>(pPor)))
+ {
+ // JP 24.11.94: if the Position is not in Fly, then
+ // we many not return with COMPLETE_STRING as value!
+ // (BugId: 9692 + Change in feshview)
+ SwFlyInContentFrame *pTmp = pFlyPor->GetFlyFrame();
+ SwFrame* pLower = pTmp->GetLower();
+ // Allow non-text-frames to get SwGrfNode for as-char anchored images into pPos
+ // instead of the closest SwTextNode, to be consistent with at-char behavior.
+ bool bChgNodeInner = pLower
+ && (pLower->IsTextFrame() || pLower->IsLayoutFrame() || pLower->IsNoTextFrame());
+ Point aTmpPoint( rPoint );
+
+ if ( m_pFrame->IsRightToLeft() )
+ m_pFrame->SwitchLTRtoRTL( aTmpPoint );
+
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchHorizontalToVertical( aTmpPoint );
+
+ if( bChgNodeInner && pTmp->getFrameArea().Contains( aTmpPoint ) &&
+ !( pTmp->IsProtected() ) )
+ {
+ pFlyPor->GetFlyCursorOfst(aTmpPoint, *pPos, pCMS);
+ // After a change of the frame, our font must be still
+ // available for/in the OutputDevice.
+ // For comparison: Paint and new SwFlyCntPortion !
+ static_cast<SwTextSizeInfo*>(m_pInf)->SelectFont();
+
+ // 6776: The pIter->GetModelPositionForViewPoint is returning here
+ // from a nesting with COMPLETE_STRING.
+ return TextFrameIndex(COMPLETE_STRING);
+ }
+ }
+ else
+ nLength = pPor->GetModelPositionForViewPoint( nX );
+ }
+ }
+ nOffset = nCurrStart + nLength;
+
+ // 7684: We end up in front of the HyphPortion. We must assure
+ // that we end up in the string.
+ // If we are at end of line in front of FlyFrames, we must proceed the same way.
+ if( nOffset && pPor->GetLen() == nLength && pPor->GetNextPortion() &&
+ !pPor->GetNextPortion()->GetLen() && pPor->GetNextPortion()->InHyphGrp() )
+ --nOffset;
+
+ return nOffset;
+}
+
+/** Looks for text portions which are inside the given rectangle
+
+ For a rectangular text selection every text portions which is inside the given
+ rectangle has to be put into the SwSelectionList as SwPaM
+ From these SwPaM the SwCursors will be created.
+
+ @param rSelList
+ The container for the overlapped text portions
+
+ @param rRect
+ A rectangle in document coordinates, text inside this rectangle has to be
+ selected.
+
+ @return [ true, false ]
+ true if any overlapping text portion has been found and put into list
+ false if no portion overlaps, the list has been unchanged
+*/
+bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect ) const
+{
+ bool bRet = false;
+ // GetPaintArea() instead getFrameArea() for negative indents
+ SwRect aTmpFrame( GetPaintArea() );
+ if( !rRect.Overlaps( aTmpFrame ) )
+ return false;
+ if( rSelList.checkContext( this ) )
+ {
+ SwRect aRect( aTmpFrame );
+ aRect.Intersection( rRect );
+ SwPosition aPosL( MapViewToModelPos(TextFrameIndex(0)) );
+ if( IsEmpty() )
+ {
+ SwPaM *pPam = new SwPaM( aPosL, aPosL );
+ rSelList.insertPaM( pPam );
+ }
+ else if( aRect.HasArea() )
+ {
+ SwPosition aOld(aPosL.GetNodes().GetEndOfContent());
+ SwPosition aPosR( aPosL );
+ Point aPoint;
+ SwTextInfo aInf( const_cast<SwTextFrame*>(this) );
+ SwTextIter aLine( const_cast<SwTextFrame*>(this), &aInf );
+ // We have to care for top-to-bottom layout, where right becomes top etc.
+ SwRectFnSet aRectFnSet(this);
+ SwTwips nTop = aRectFnSet.GetTop(aRect);
+ SwTwips nBottom = aRectFnSet.GetBottom(aRect);
+ SwTwips nLeft = aRectFnSet.GetLeft(aRect);
+ SwTwips nRight = aRectFnSet.GetRight(aRect);
+ SwTwips nY = aLine.Y(); // Top position of the first line
+ SwTwips nLastY = nY;
+ while( nY < nTop && aLine.Next() ) // line above rectangle
+ {
+ nLastY = nY;
+ nY = aLine.Y();
+ }
+ bool bLastLine = false;
+ if( nY < nTop && !aLine.GetNext() )
+ {
+ bLastLine = true;
+ nY += aLine.GetLineHeight();
+ }
+ do // check the lines for overlapping
+ {
+ if( nLastY < nTop ) // if the last line was above rectangle
+ nLastY = nTop;
+ if( nY > nBottom ) // if the current line leaves the rectangle
+ nY = nBottom;
+ if( nY >= nLastY ) // gotcha: overlapping
+ {
+ nLastY += nY;
+ nLastY /= 2;
+ if( aRectFnSet.IsVert() )
+ {
+ aPoint.setX( nLastY );
+ aPoint.setY( nLeft );
+ }
+ else
+ {
+ aPoint.setX( nLeft );
+ aPoint.setY( nLastY );
+ }
+ // Looking for the position of the left border of the rectangle
+ // in this text line
+ SwCursorMoveState aState( CursorMoveState::UpDown );
+ if( GetModelPositionForViewPoint( &aPosL, aPoint, &aState ) )
+ {
+ if( aRectFnSet.IsVert() )
+ {
+ aPoint.setX( nLastY );
+ aPoint.setY( nRight );
+ }
+ else
+ {
+ aPoint.setX( nRight );
+ aPoint.setY( nLastY );
+ }
+ // If we get a right position and if the left position
+ // is not the same like the left position of the line before
+ // which could happen e.g. for field portions or fly frames
+ // a SwPaM will be inserted with these positions
+ if( GetModelPositionForViewPoint( &aPosR, aPoint, &aState ) &&
+ aOld != aPosL)
+ {
+ SwPaM *pPam = new SwPaM( aPosL, aPosR );
+ rSelList.insertPaM( pPam );
+ aOld = aPosL;
+ }
+ }
+ }
+ if( aLine.Next() )
+ {
+ nLastY = nY;
+ nY = aLine.Y();
+ }
+ else if( !bLastLine )
+ {
+ bLastLine = true;
+ nLastY = nY;
+ nY += aLine.GetLineHeight();
+ }
+ else
+ break;
+ }while( nLastY < nBottom );
+ }
+ }
+ if( GetDrawObjs() )
+ {
+ const SwSortedObjs &rObjs = *GetDrawObjs();
+ for (SwAnchoredObject* pAnchoredObj : rObjs)
+ {
+ const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame();
+ if( !pFly )
+ continue;
+ if( pFly->IsFlyInContentFrame() && pFly->FillSelection( rSelList, rRect ) )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
new file mode 100644
index 0000000000..c0b4894f8a
--- /dev/null
+++ b/sw/source/core/text/itrform2.cxx
@@ -0,0 +1,3321 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+
+#include <memory>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <editeng/lspcitem.hxx>
+#include <txtflcnt.hxx>
+#include <txtftn.hxx>
+#include <flyfrms.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtftn.hxx>
+#include <ftninfo.hxx>
+#include <charfmt.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <layfrm.hxx>
+#include <viewsh.hxx>
+#include <viewopt.hxx>
+#include <paratr.hxx>
+#include "itrform2.hxx"
+#include "porrst.hxx"
+#include "portab.hxx"
+#include "porfly.hxx"
+#include "portox.hxx"
+#include "porref.hxx"
+#include "porfld.hxx"
+#include "porftn.hxx"
+#include "porhyph.hxx"
+#include "pordrop.hxx"
+#include "redlnitr.hxx"
+#include <sortedobjs.hxx>
+#include <fmtanchr.hxx>
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include <doc.hxx>
+#include "pormulti.hxx"
+#include <unotools/charclass.hxx>
+#include <xmloff/odffields.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IMark.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <comphelper/processfactory.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <comphelper/string.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>
+#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
+#include <com/sun/star/rdf/XLiteral.hpp>
+#include <com/sun/star/text/XTextContent.hpp>
+
+using namespace ::com::sun::star;
+
+namespace {
+ //! Calculates and sets optimal repaint offset for the current line
+ tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
+ SwLineLayout const &rCurr,
+ TextFrameIndex nOldLineEnd,
+ const std::vector<tools::Long> &rFlyStarts );
+ //! Determine if we need to build hidden portions
+ bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);
+
+ // Check whether the two font has the same border
+ bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
+}
+
+static void ClearFly( SwTextFormatInfo &rInf )
+{
+ delete rInf.GetFly();
+ rInf.SetFly(nullptr);
+}
+
+void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf )
+{
+ CtorInitTextPainter( pNewFrame, pNewInf );
+ m_pInf = pNewInf;
+ m_pDropFormat = GetInfo().GetDropFormat();
+ m_pMulti = nullptr;
+
+ m_bOnceMore = false;
+ m_bFlyInContentBase = false;
+ m_bTruncLines = false;
+ m_nContentEndHyph = 0;
+ m_nContentMidHyph = 0;
+ m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING);
+ m_nRightScanIdx = TextFrameIndex(0);
+ m_pByEndIter.reset();
+ m_pFirstOfBorderMerge = nullptr;
+
+ if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
+ {
+ OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" );
+ m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
+ }
+
+}
+
+SwTextFormatter::~SwTextFormatter()
+{
+ // Extremely unlikely, but still possible
+ // e.g.: field splits up, widows start to matter
+ if( GetInfo().GetRest() )
+ {
+ delete GetInfo().GetRest();
+ GetInfo().SetRest(nullptr);
+ }
+}
+
+void SwTextFormatter::Insert( SwLineLayout *pLay )
+{
+ // Insert BEHIND the current element
+ if ( m_pCurr )
+ {
+ pLay->SetNext( m_pCurr->GetNext() );
+ m_pCurr->SetNext( pLay );
+ }
+ else
+ m_pCurr = pLay;
+}
+
+sal_uInt16 SwTextFormatter::GetFrameRstHeight() const
+{
+ // We want the rest height relative to the page.
+ // If we're in a table, then pFrame->GetUpper() is not the page.
+
+ // GetFrameRstHeight() is being called with Footnote.
+ // Wrong: const SwFrame *pUpper = pFrame->GetUpper();
+ const SwFrame *pPage = m_pFrame->FindPageFrame();
+ const SwTwips nHeight = pPage->getFrameArea().Top()
+ + pPage->getFramePrintArea().Top()
+ + pPage->getFramePrintArea().Height() - Y();
+ if( 0 > nHeight )
+ return m_pCurr->Height();
+ else
+ return sal_uInt16( nHeight );
+}
+
+bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion* pPortion)
+{
+ if (pPortion == m_pFirstOfBorderMerge)
+ {
+ m_pFirstOfBorderMerge = nullptr;
+ return true;
+ }
+ return false;
+}
+
+SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf )
+{
+ // Save values and initialize rInf
+ SwLinePortion *pUnderflow = rInf.GetUnderflow();
+ if( !pUnderflow )
+ return nullptr;
+
+ // We format backwards, i.e. attribute changes can happen the next
+ // line again.
+ // Can be seen in 8081.sdw, if you enter text in the first line
+
+ TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos();
+ TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos();
+
+ // Save flys and set to 0, or else segmentation fault
+ // Not ClearFly(rInf) !
+ SwFlyPortion *pFly = rInf.GetFly();
+ rInf.SetFly( nullptr );
+
+ FeedInf( rInf );
+ rInf.SetLast( m_pCurr );
+ // pUnderflow does not need to be deleted, because it will drown in the following
+ // Truncate()
+ rInf.SetUnderflow(nullptr);
+ rInf.SetSoftHyphPos( nSoftHyphPos );
+ rInf.SetUnderScorePos( nUnderScorePos );
+ rInf.SetPaintOfst( GetLeftMargin() );
+
+ // We look for the portion with the under-flow position
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ if( pPor != pUnderflow )
+ {
+ // pPrev will be the last portion before pUnderflow,
+ // which still has a real width.
+ // Exception: SoftHyphPortion must not be forgotten, of course!
+ // Although they don't have a width.
+ SwLinePortion *pTmpPrev = pPor;
+ while( pPor && pPor != pUnderflow )
+ {
+ if( !pPor->IsKernPortion() &&
+ ( pPor->Width() || pPor->IsSoftHyphPortion() ) )
+ {
+ while( pTmpPrev != pPor )
+ {
+ pTmpPrev->Move( rInf );
+ rInf.SetLast( pTmpPrev );
+ pTmpPrev = pTmpPrev->GetNextPortion();
+ OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
+ };
+ }
+ pPor = pPor->GetNextPortion();
+ }
+ pPor = pTmpPrev;
+ if( pPor && // Skip flys and initials when underflow.
+ ( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
+ pPor->IsFlyCntPortion() ) )
+ {
+ pPor->Move( rInf );
+ rInf.SetLast( pPor );
+ rInf.SetStopUnderflow( true );
+ pPor = pUnderflow;
+ }
+ }
+
+ // What? The under-flow portion is not in the portion chain?
+ OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );
+
+ // Snapshot
+ if ( pPor==rInf.GetLast() )
+ {
+ // We end up here, if the portion triggering the under-flow
+ // spans over the whole line. E.g. if a word spans across
+ // multiple lines and flows into a fly in the second line.
+ rInf.SetFly( pFly );
+ pPor->Truncate();
+ return pPor; // Is that enough?
+ }
+ // End the snapshot
+
+ // X + Width == 0 with SoftHyph > Line?!
+ if( !pPor || !(rInf.X() + pPor->Width()) )
+ {
+ delete pFly;
+ return nullptr;
+ }
+
+ // Preparing for Format()
+ // We need to chip off the chain behind pLast, because we Insert after the Format()
+ SeekAndChg( rInf );
+
+ // line width is adjusted, so that pPor does not fit to current
+ // line anymore
+ rInf.Width( rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0) );
+ rInf.SetLen( pPor->GetLen() );
+ rInf.SetFull( false );
+ if( pFly )
+ {
+ // We need to recalculate the FlyPortion due to the following reason:
+ // If the base line is lowered by a big font in the middle of the line,
+ // causing overlapping with a fly, the FlyPortion has a wrong size/fixed
+ // size.
+ rInf.SetFly( pFly );
+ CalcFlyWidth( rInf );
+ }
+ rInf.GetLast()->SetNextPortion(nullptr);
+
+ // The SwLineLayout is an exception to this, which splits at the first
+ // portion change.
+ // Here only the other way around:
+ if( rInf.GetLast() == m_pCurr )
+ {
+ if( pPor->InTextGrp() && !pPor->InExpGrp() )
+ {
+ const PortionType nOldWhich = m_pCurr->GetWhichPor();
+ *static_cast<SwLinePortion*>(m_pCurr) = *pPor;
+ m_pCurr->SetNextPortion( pPor->GetNextPortion() );
+ m_pCurr->SetWhichPor( nOldWhich );
+ pPor->SetNextPortion( nullptr );
+ delete pPor;
+ pPor = m_pCurr;
+ }
+ }
+
+ // Make sure that m_pFirstOfBorderMerge does not point to a portion which
+ // will be deleted by Truncate() below.
+ SwLinePortion* pNext = pPor->GetNextPortion();
+ while (pNext)
+ {
+ if (ClearIfIsFirstOfBorderMerge(pNext))
+ break;
+ pNext = pNext->GetNextPortion();
+ }
+ pPor->Truncate();
+ SwLinePortion *const pRest( rInf.GetRest() );
+ if (pRest && pRest->InFieldGrp() &&
+ static_cast<SwFieldPortion*>(pRest)->IsNoLength())
+ {
+ // HACK: decrement again, so we pick up the suffix in next line!
+ m_pByEndIter->PrevAttr();
+ }
+ delete pRest;
+ rInf.SetRest(nullptr);
+ return pPor;
+}
+
+void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
+ SwLinePortion *pPor )
+{
+ SwLinePortion *pLast = nullptr;
+ // The new portion is inserted, but everything's different for
+ // LineLayout...
+ if( pPor == m_pCurr )
+ {
+ if ( m_pCurr->GetNextPortion() )
+ {
+ pLast = pPor;
+ pPor = m_pCurr->GetNextPortion();
+ }
+
+ // i#112181 - Prevent footnote anchor being wrapped to next line
+ // without preceding word
+ rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
+ }
+ else
+ {
+ pLast = rInf.GetLast();
+ if( pLast->GetNextPortion() )
+ {
+ while( pLast->GetNextPortion() )
+ pLast = pLast->GetNextPortion();
+ rInf.SetLast( pLast );
+ }
+ pLast->Insert( pPor );
+
+ rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
+
+ // Adjust maxima
+ if( m_pCurr->Height() < pPor->Height() )
+ m_pCurr->Height( pPor->Height(), pPor->IsTextPortion() );
+ if( m_pCurr->GetAscent() < pPor->GetAscent() )
+ m_pCurr->SetAscent( pPor->GetAscent() );
+ if( m_pCurr->GetHangingBaseline() < pPor->GetHangingBaseline() )
+ m_pCurr->SetHangingBaseline( pPor->GetHangingBaseline() );
+
+ if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY))
+ {
+ // For DOCX with compat=14 the only shape in line defines height of the line in spite of used font
+ if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0))
+ {
+ m_pCurr->SetAscent(pLast->GetAscent());
+ m_pCurr->Height(pLast->Height());
+ }
+ }
+ }
+
+ // Sometimes chains are constructed (e.g. by hyphenate)
+ rInf.SetLast( pPor );
+ while( pPor )
+ {
+ if (!pPor->IsDropPortion())
+ MergeCharacterBorder(*pPor, pLast, rInf);
+
+ pPor->Move( rInf );
+ rInf.SetLast( pPor );
+ pLast = pPor;
+ pPor = pPor->GetNextPortion();
+ }
+}
+
+void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
+{
+ OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING,
+ "SwTextFormatter::BuildPortions: bad text length in info" );
+
+ rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
+
+ // First NewTextPortion() decides whether pCurr ends up in pPor.
+ // We need to make sure that the font is being set in any case.
+ // This is done automatically in CalcAscent.
+ rInf.SetLast( m_pCurr );
+ rInf.ForcedLeftMargin( 0 );
+
+ OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );
+
+ if( !m_pCurr->GetAscent() && !m_pCurr->Height() )
+ CalcAscent( rInf, m_pCurr );
+
+ SeekAndChg( rInf );
+
+ // Width() is shortened in CalcFlyWidth if we have a FlyPortion
+ OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" );
+ CalcFlyWidth( rInf );
+ SwFlyPortion *pFly = rInf.GetFly();
+ if( pFly )
+ {
+ if ( 0 < pFly->GetFix() )
+ ClearFly( rInf );
+ else
+ rInf.SetFull(true);
+ }
+
+ ::std::optional<TextFrameIndex> oMovedFlyIndex;
+ if (SwTextFrame const*const pFollow = GetTextFrame()->GetFollow())
+ {
+ // flys are always on master!
+ if (GetTextFrame()->GetDrawObjs() && pFollow->GetUpper() != GetTextFrame()->GetUpper())
+ {
+ for (SwAnchoredObject const*const pAnchoredObj : *GetTextFrame()->GetDrawObjs())
+ {
+ // tdf#146500 try to stop where a fly is anchored in the follow
+ // that has recently been moved (presumably by splitting this
+ // frame); similar to check in SwFlowFrame::MoveBwd()
+ if (pAnchoredObj->RestartLayoutProcess()
+ && !pAnchoredObj->IsTmpConsiderWrapInfluence())
+ {
+ SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat().GetAnchor());
+ assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA);
+ TextFrameIndex const nAnchor(GetTextFrame()->MapModelToViewPos(*rAnchor.GetContentAnchor()));
+ if (pFollow->GetOffset() <= nAnchor
+ && (pFollow->GetFollow() == nullptr
+ || nAnchor < pFollow->GetFollow()->GetOffset()))
+ {
+ if (!oMovedFlyIndex || nAnchor < *oMovedFlyIndex)
+ {
+ oMovedFlyIndex.emplace(nAnchor);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SwLinePortion *pPor = NewPortion(rInf, oMovedFlyIndex);
+
+ // Asian grid stuff
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ const bool bHasGrid = pGrid && rInf.SnapToGrid() &&
+ GRID_LINES_CHARS == pGrid->GetGridType();
+
+
+ const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
+ const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0;
+
+ // used for grid mode only:
+ // the pointer is stored, because after formatting of non-asian text,
+ // the width of the kerning portion has to be adjusted
+ // Inserting a SwKernPortion before a SwTabPortion isn't necessary
+ // and will break the SwTabPortion.
+ SwKernPortion* pGridKernPortion = nullptr;
+
+ bool bFull = false;
+ SwTwips nUnderLineStart = 0;
+ rInf.Y( Y() );
+
+ while( pPor && !rInf.IsStop() )
+ {
+ OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
+ rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()),
+ "SwTextFormatter::BuildPortions: bad length in info" );
+
+ // We have to check the script for fields in order to set the
+ // correct nActual value for the font.
+ if( pPor->InFieldGrp() )
+ static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );
+
+ if( ! bHasGrid && rInf.HasScriptSpace() &&
+ rInf.GetLast() && rInf.GetLast()->InTextGrp() &&
+ rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() )
+ {
+ SwFontScript nNxtActual = rInf.GetFont()->GetActual();
+ SwFontScript nLstActual = nNxtActual;
+ sal_uInt16 nLstHeight = o3tl::narrowing<sal_uInt16>(rInf.GetFont()->GetHeight());
+ bool bAllowBehind = false;
+ const CharClass& rCC = GetAppCharClass();
+
+ // are there any punctuation characters on both sides
+ // of the kerning portion?
+ if ( pPor->InFieldGrp() )
+ {
+ OUString aAltText;
+ if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) &&
+ !aAltText.isEmpty() )
+ {
+ bAllowBehind = rCC.isLetterNumeric( aAltText, 0 );
+
+ const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
+ if ( pTmpFnt )
+ nNxtActual = pTmpFnt->GetActual();
+ }
+ }
+ else
+ {
+ const OUString& rText = rInf.GetText();
+ sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
+ bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx);
+ }
+
+ const SwLinePortion* pLast = rInf.GetLast();
+ if ( bAllowBehind && pLast )
+ {
+ bool bAllowBefore = false;
+
+ if ( pLast->InFieldGrp() )
+ {
+ OUString aAltText;
+ if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) &&
+ !aAltText.isEmpty() )
+ {
+ bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 );
+
+ const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont();
+ if ( pTmpFnt )
+ {
+ nLstActual = pTmpFnt->GetActual();
+ nLstHeight = o3tl::narrowing<sal_uInt16>(pTmpFnt->GetHeight());
+ }
+ }
+ }
+ else if ( rInf.GetIdx() )
+ {
+ bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1);
+ // Note: ScriptType returns values in [1,4]
+ if ( bAllowBefore )
+ nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1);
+ }
+
+ nLstHeight /= 5;
+ // does the kerning portion still fit into the line?
+ if( bAllowBefore && ( nLstActual != nNxtActual ) &&
+ // tdf#89288 we want to insert space between CJK and non-CJK text only.
+ ( nLstActual == SwFontScript::CJK || nNxtActual == SwFontScript::CJK ) &&
+ nLstHeight && rInf.X() + nLstHeight <= rInf.Width() &&
+ ! pPor->InTabGrp() )
+ {
+ SwKernPortion* pKrn =
+ new SwKernPortion( *rInf.GetLast(), nLstHeight,
+ pLast->InFieldGrp() && pPor->InFieldGrp() );
+
+ // ofz#58550 Direct-leak, pKrn adds itself as the NextPortion
+ // of rInf.GetLast(), but may use CopyLinePortion to add a copy
+ // of itself, which will then be left dangling with the following
+ // SetNextPortion(nullptr)
+ SwLinePortion *pNext = rInf.GetLast()->GetNextPortion();
+ if (pNext != pKrn)
+ delete pNext;
+
+ rInf.GetLast()->SetNextPortion( nullptr );
+ InsertPortion( rInf, pKrn );
+ }
+ }
+ }
+ else if ( bHasGrid && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
+ {
+ // insert a grid kerning portion
+ pGridKernPortion = pPor->IsKernPortion() ?
+ static_cast<SwKernPortion*>(pPor) :
+ new SwKernPortion( *m_pCurr );
+
+ // if we have a new GridKernPortion, we initially calculate
+ // its size so that its ends on the grid
+ const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
+ const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
+ SwRectFnSet aRectFnSet(pPageFrame);
+
+ const tools::Long nGridOrigin = pBody ?
+ aRectFnSet.GetPrtLeft(*pBody) :
+ aRectFnSet.GetPrtLeft(*pPageFrame);
+
+ SwTwips nStartX = rInf.X() + GetLeftMargin();
+ if ( aRectFnSet.IsVert() )
+ {
+ Point aPoint( nStartX, 0 );
+ m_pFrame->SwitchHorizontalToVertical( aPoint );
+ nStartX = aPoint.Y();
+ }
+
+ const SwTwips nOfst = nStartX - nGridOrigin;
+ if ( nOfst )
+ {
+ const sal_uLong i = ( nOfst > 0 ) ?
+ ( ( nOfst - 1 ) / nGridWidth + 1 ) :
+ 0;
+ const SwTwips nKernWidth = i * nGridWidth - nOfst;
+ const SwTwips nRestWidth = rInf.Width() - rInf.X();
+
+ if ( nKernWidth <= nRestWidth )
+ pGridKernPortion->Width( nKernWidth );
+ }
+
+ if ( pGridKernPortion != pPor )
+ InsertPortion( rInf, pGridKernPortion );
+ }
+
+ if( pPor->IsDropPortion() )
+ MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor));
+
+ // the multi-portion has its own format function
+ if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) )
+ bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) );
+ else
+ bFull = pPor->Format( rInf );
+
+ if( rInf.IsRuby() && !rInf.GetRest() )
+ bFull = true;
+
+ // if we are underlined, we store the beginning of this underlined
+ // segment for repaint optimization
+ if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
+ nUnderLineStart = GetLeftMargin() + rInf.X();
+
+ if ( pPor->IsFlyPortion() )
+ m_pCurr->SetFly( true );
+ // some special cases, where we have to take care for the repaint
+ // offset:
+ // 1. Underlined portions due to special underline feature
+ // 2. Right Tab
+ // 3. BidiPortions
+ // 4. other Multiportions
+ // 5. DropCaps
+ // 6. Grid Mode
+ else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) &&
+ // 1. Underlined portions
+ nUnderLineStart &&
+ // reformat is at end of an underlined portion and next portion
+ // is not underlined
+ ( ( rInf.GetReformatStart() == rInf.GetIdx() &&
+ LINESTYLE_NONE == m_pFont->GetUnderline()
+ ) ||
+ // reformat is inside portion and portion is underlined
+ ( rInf.GetReformatStart() >= rInf.GetIdx() &&
+ rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
+ LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
+ rInf.SetPaintOfst( nUnderLineStart );
+ else if ( ! rInf.GetPaintOfst() &&
+ // 2. Right Tab
+ ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) ||
+ // 3. BidiPortions
+ ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ||
+ // 4. Multi Portion and 5. Drop Caps
+ ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
+ rInf.GetReformatStart() >= rInf.GetIdx() &&
+ rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() )
+ // 6. Grid Mode
+ || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
+ )
+ )
+ // we store the beginning of the critical portion as our
+ // paint offset
+ rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );
+
+ // under one of these conditions we are allowed to delete the
+ // start of the underline portion
+ if ( IsUnderlineBreak( *pPor, *m_pFont ) )
+ nUnderLineStart = 0;
+
+ if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
+ SetFlyInCntBase();
+ // bUnderflow needs to be reset or we wrap again at the next softhyphen
+ if ( !bFull )
+ {
+ rInf.ClrUnderflow();
+ if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
+ pPor->GetLen() && !pPor->InFieldGrp() )
+ {
+ // The distance between two different scripts is set
+ // to 20% of the fontheight.
+ TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
+ if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) &&
+ nTmp != TextFrameIndex(rInf.GetText().getLength()) &&
+ (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN ||
+ m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) )
+ {
+ const SwTwips nDist = rInf.GetFont()->GetHeight()/5;
+
+ if( nDist )
+ {
+ // we do not want a kerning portion if any end
+ // would be a punctuation character
+ const CharClass& rCC = GetAppCharClass();
+ if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
+ && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
+ {
+ // does the kerning portion still fit into the line?
+ if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() )
+ new SwKernPortion( *pPor, nDist );
+ else
+ bFull = true;
+ }
+ }
+ }
+ }
+ }
+
+ if ( bHasGrid && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
+ {
+ TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
+ const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width();
+
+ const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() );
+ const SwFontScript nNextScript =
+ nTmp >= TextFrameIndex(rInf.GetText().getLength())
+ ? SwFontScript::CJK
+ : m_pScriptInfo->WhichFont(nTmp);
+
+ // snap non-asian text to grid if next portion is ASIAN or
+ // there are no more portions in this line
+ // be careful when handling an underflow event: the gridkernportion
+ // could have been deleted
+ if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
+ ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
+ {
+ OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );
+
+ // calculate size
+ SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion();
+ sal_uInt16 nSumWidth = pPor->Width();
+ while ( pTmpPor )
+ {
+ nSumWidth = nSumWidth + pTmpPor->Width();
+ pTmpPor = pTmpPor->GetNextPortion();
+ }
+
+ const SwTwips i = nSumWidth ?
+ ( nSumWidth - 1 ) / nGridWidth + 1 :
+ 0;
+ const SwTwips nTmpWidth = i * nGridWidth;
+ const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth);
+ const SwTwips nKernWidth_1 = pGrid->IsSnapToChars() ?
+ nKernWidth / 2 : 0;
+
+ OSL_ENSURE( nKernWidth <= nRestWidth,
+ "Not enough space left for adjusting non-asian text in grid mode" );
+ if (nKernWidth_1)
+ {
+ pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
+ rInf.X( rInf.X() + nKernWidth_1 );
+ }
+
+ if ( ! bFull && nKernWidth - nKernWidth_1 > 0 )
+ new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1),
+ false, true );
+
+ pGridKernPortion = nullptr;
+ }
+ else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() ||
+ pPor->IsFlyCntPortion() || pPor->InNumberGrp() ||
+ pPor->InFieldGrp() || nCurrScript != nNextScript )
+ // next portion should snap to grid
+ pGridKernPortion = nullptr;
+ }
+
+ rInf.SetFull( bFull );
+
+ // Restportions from fields with multiple lines don't yet have the right ascent
+ if ( !pPor->GetLen() && !pPor->IsFlyPortion()
+ && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
+ && !pPor->IsMultiPortion() )
+ CalcAscent( rInf, pPor );
+
+ InsertPortion( rInf, pPor );
+ if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi()))
+ {
+ (void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion!
+ }
+ pPor = NewPortion(rInf, oMovedFlyIndex);
+ }
+
+ if( !rInf.IsStop() )
+ {
+ // The last right centered, decimal tab
+ SwTabPortion *pLastTab = rInf.GetLastTab();
+ if( pLastTab )
+ pLastTab->FormatEOL( rInf );
+ else if( rInf.GetLast() && rInf.LastKernPortion() )
+ rInf.GetLast()->FormatEOL( rInf );
+ }
+ if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp()
+ && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() )
+ rInf.SetNumDone( false );
+
+ // Delete fly in any case
+ ClearFly( rInf );
+
+ // Reinit the tab overflow flag after the line
+ rInf.SetTabOverflow( false );
+}
+
+void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
+{
+ if( SvxAdjust::Left != GetAdjust() && !m_pMulti)
+ {
+ pCurrent->SetFormatAdj(true);
+ if( IsFlyInCntBase() )
+ {
+ CalcAdjLine( pCurrent );
+ // For e.g. centered fly we need to switch the RefPoint
+ // That's why bAlways = true
+ UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
+ }
+ }
+}
+
+void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
+{
+ bool bCalc = false;
+ if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
+ {
+ // Numbering + InterNetFields can keep an own font, then their size is
+ // independent from hard attribute values
+ SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
+ SwFontSave aSave( rInf, pFieldFnt );
+ pPor->Height( rInf.GetTextHeight() );
+ pPor->SetAscent( rInf.GetAscent() );
+ bCalc = true;
+ }
+ // i#89179
+ // tab portion representing the list tab of a list label gets the
+ // same height and ascent as the corresponding number portion
+ else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
+ rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
+ static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
+ {
+ const SwLinePortion* pLast = rInf.GetLast();
+ pPor->Height( pLast->Height() );
+ pPor->SetAscent( pLast->GetAscent() );
+ }
+ else if (pPor->GetWhichPor() == PortionType::Bookmark
+ && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
+ {
+ // bookmark at end of paragraph: *don't* advance iterator, use the
+ // current font instead; it's possible that there's a font size on the
+ // paragraph and it's overridden on the last line of the paragraph and
+ // we don't want to apply it via SwBookmarkPortion and grow the line
+ // height (example: n758883.docx)
+ SwLinePortion const*const pLast = rInf.GetLast();
+ assert(pLast);
+ pPor->Height( pLast->Height(), false );
+ pPor->SetAscent( pLast->GetAscent() );
+ }
+ else
+ {
+ const SwLinePortion *pLast = rInf.GetLast();
+ bool bChg = false;
+
+ // In empty lines the attributes are switched on via SeekStart
+ const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
+ if ( pPor->IsQuoVadisPortion() )
+ bChg = SeekStartAndChg( rInf, true );
+ else
+ {
+ if( bFirstPor )
+ {
+ if( !rInf.GetText().isEmpty() )
+ {
+ if ( pPor->GetLen() || !rInf.GetIdx()
+ || ( m_pCurr != pLast && !pLast->IsFlyPortion() )
+ || !m_pCurr->IsRest() ) // instead of !rInf.GetRest()
+ bChg = SeekAndChg( rInf );
+ else
+ bChg = SeekAndChgBefore( rInf );
+ }
+ else if ( m_pMulti )
+ // do not open attributes starting at 0 in empty multi
+ // portions (rotated numbering followed by a footnote
+ // can cause trouble, because the footnote attribute
+ // starts at 0, but if we open it, the attribute handler
+ // cannot handle it.
+ bChg = false;
+ else
+ bChg = SeekStartAndChg( rInf );
+ }
+ else
+ bChg = SeekAndChg( rInf );
+ }
+ if( bChg || bFirstPor || !pPor->GetAscent()
+ || !rInf.GetLast()->InTextGrp() )
+ {
+ pPor->SetHangingBaseline( rInf.GetHangingBaseline() );
+ pPor->SetAscent( rInf.GetAscent() );
+ pPor->Height( rInf.GetTextHeight() );
+ bCalc = true;
+ }
+ else
+ {
+ pPor->Height( pLast->Height() );
+ pPor->SetAscent( pLast->GetAscent() );
+ }
+ }
+
+ if( pPor->InTextGrp() && bCalc )
+ {
+ pPor->SetAscent(pPor->GetAscent() +
+ rInf.GetFont()->GetTopBorderSpace());
+ pPor->Height(pPor->Height() +
+ rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace() );
+ }
+}
+
+namespace {
+
+class SwMetaPortion : public SwTextPortion
+{
+ Color m_aShadowColor;
+public:
+ SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ void SetShadowColor(const Color& rCol ) { m_aShadowColor = rCol; }
+};
+
+/// A content control portion is a text portion that is inside RES_TXTATR_CONTENTCONTROL.
+class SwContentControlPortion : public SwTextPortion
+{
+ SwTextContentControl* m_pTextContentControl;
+public:
+ 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;
+};
+}
+
+void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::Meta,
+ // custom shading (RDF metadata)
+ COL_BLACK == m_aShadowColor
+ ? nullptr
+ : &m_aShadowColor );
+
+ SwTextPortion::Paint( rInf );
+ }
+}
+
+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;
+ }
+
+ SwTextNode* pTextNode = pContentControl->GetTextNode();
+ SwDoc& rDoc = pTextNode->GetDoc();
+ if (rDoc.IsInHeaderFooter(*pTextNode))
+ {
+ // Form control in header/footer makes no sense, would allow multiple values for the same
+ // control.
+ return false;
+ }
+
+ // Check if this is the first content control portion of this content control.
+ sal_Int32 nStart = m_pTextContentControl->GetStart();
+ sal_Int32 nEnd = *m_pTextContentControl->GetEnd();
+ TextFrameIndex nViewStart = rInf.GetTextFrame()->MapModelToView(pTextNode, nStart);
+ TextFrameIndex nViewEnd = rInf.GetTextFrame()->MapModelToView(pTextNode, nEnd);
+ // The content control portion starts 1 char after the starting dummy character.
+ if (rInf.GetIdx() != nViewStart + TextFrameIndex(1))
+ {
+ // Ignore: don't process and also don't emit plain text fallback.
+ return true;
+ }
+
+ const SwPaM aPam(*pTextNode, nEnd, *pTextNode, nStart);
+ static sal_Unicode const aForbidden[] = {
+ CH_TXTATR_BREAKWORD,
+ 0
+ };
+ const OUString aText = comphelper::string::removeAny(aPam.GetText(), aForbidden);
+
+ 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;
+ }
+ case SwContentControlType::CHECKBOX:
+ {
+ pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>();
+ auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get());
+ pCheckBoxWidget->Checked = pContentControl->GetChecked();
+ pCheckBoxWidget->OnValue = pContentControl->GetCheckedState();
+ pCheckBoxWidget->OffValue = pContentControl->GetUncheckedState();
+ break;
+ }
+ case SwContentControlType::DROP_DOWN_LIST:
+ {
+ pDescriptor = std::make_unique<vcl::PDFWriter::ListBoxWidget>();
+ auto pListWidget = static_cast<vcl::PDFWriter::ListBoxWidget*>(pDescriptor.get());
+ pListWidget->DropDown = true;
+ sal_Int32 nIndex = 0;
+ for (const auto& rItem : pContentControl->GetListItems())
+ {
+ pListWidget->Entries.push_back(rItem.m_aDisplayText);
+ if (rItem.m_aDisplayText == aText)
+ pListWidget->SelectedEntries.push_back(nIndex);
+ ++nIndex;
+ }
+ break;
+ }
+ case SwContentControlType::COMBO_BOX:
+ {
+ pDescriptor = std::make_unique<vcl::PDFWriter::ComboBoxWidget>();
+ auto pComboWidget = static_cast<vcl::PDFWriter::ComboBoxWidget*>(pDescriptor.get());
+ for (const auto& rItem : pContentControl->GetListItems())
+ {
+ pComboWidget->Entries.push_back(rItem.m_aDisplayText);
+ }
+ break;
+ }
+ case SwContentControlType::DATE:
+ {
+ pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>();
+ auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
+ pEditWidget->Format = vcl::PDFWriter::Date;
+ // GetDateFormat() uses a syntax that works with SvNumberFormatter::PutEntry(), PDF's
+ // AFDate_FormatEx() uses a similar syntax, but uses lowercase characters in case of
+ // "Y", "M" and "D" at least.
+ pEditWidget->DateFormat = pContentControl->GetDateFormat().toAsciiLowerCase();
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (!pDescriptor)
+ {
+ return false;
+ }
+
+ const SwFont* pFont = rInf.GetFont();
+ if (pFont)
+ {
+ pDescriptor->TextFont = pFont->GetActualFont();
+ }
+
+ // Description for accessibility purposes.
+ if (!pContentControl->GetAlias().isEmpty())
+ {
+ pDescriptor->Description = pContentControl->GetAlias();
+ }
+
+ // Map the text of the content control to the descriptor's text.
+ pDescriptor->Text = aText;
+
+ // Calculate the bounding rectangle of this content control, which can be one or more layout
+ // portions in one or more lines.
+ SwRect aLocation;
+ auto pTextFrame = const_cast<SwTextFrame*>(rInf.GetTextFrame());
+ SwTextSizeInfo aInf(pTextFrame);
+ SwTextCursor aLine(pTextFrame, &aInf);
+ SwRect aStartRect, aEndRect;
+ aLine.GetCharRect(&aStartRect, nViewStart);
+ aLine.GetCharRect(&aEndRect, nViewEnd);
+
+ // Handling RTL text direction
+ if(rInf.GetTextFrame()->IsRightToLeft())
+ {
+ rInf.GetTextFrame()->SwitchLTRtoRTL( aStartRect );
+ rInf.GetTextFrame()->SwitchLTRtoRTL( aEndRect );
+ }
+ // TODO: handle rInf.GetTextFrame()->IsVertical()
+
+ aLocation = aStartRect;
+ aLocation.Union(aEndRect);
+ pDescriptor->Location = aLocation.SVRect();
+
+ pPDFExtOutDevData->WrapBeginStructureElement(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);
+ }
+}
+
+namespace sw::mark {
+ OUString ExpandFieldmark(IFieldmark* pBM)
+ {
+ if (pBM->GetFieldname() == ODF_FORMCHECKBOX)
+ {
+ ::sw::mark::ICheckboxFieldmark const*const pCheckboxFm(
+ dynamic_cast<ICheckboxFieldmark const*>(pBM));
+ assert(pCheckboxFm);
+ return pCheckboxFm->IsChecked()
+ ? u"\u2612"_ustr
+ : u"\u2610"_ustr;
+ }
+ assert(pBM->GetFieldname() == ODF_FORMDROPDOWN);
+ const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters();
+ sal_Int32 nCurrentIdx = 0;
+ const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
+ if(pResult != pParameters->end())
+ pResult->second >>= nCurrentIdx;
+
+ const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
+ if (pListEntries != pParameters->end())
+ {
+ uno::Sequence< OUString > vListEntries;
+ pListEntries->second >>= vListEntries;
+ if (nCurrentIdx < vListEntries.getLength())
+ return vListEntries[nCurrentIdx];
+ }
+
+ static constexpr OUStringLiteral vEnSpaces = u"\u2002\u2002\u2002\u2002\u2002";
+ return vEnSpaces;
+ }
+}
+
+SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
+{
+ SwTextPortion *pPor = nullptr;
+ if( GetFnt()->IsTox() )
+ {
+ pPor = new SwToxPortion;
+ }
+ else if ( GetFnt()->IsInputField() )
+ {
+ if (rInf.GetOpt().IsFieldName())
+ {
+ OUString aFieldName = SwFieldType::GetTypeStr(SwFieldTypesEnum::Input);
+ // assume this is only the *first* portion and follows will be created elsewhere => input field must start at Idx
+ assert(rInf.GetText()[sal_Int32(rInf.GetIdx())] == CH_TXT_ATR_INPUTFIELDSTART);
+ TextFrameIndex nFieldLen(-1);
+ for (TextFrameIndex i = rInf.GetIdx() + TextFrameIndex(1); ; ++i)
+ {
+ assert(rInf.GetText()[sal_Int32(i)] != CH_TXT_ATR_INPUTFIELDSTART); // can't nest
+ if (rInf.GetText()[sal_Int32(i)] == CH_TXT_ATR_INPUTFIELDEND)
+ {
+ nFieldLen = i + TextFrameIndex(1) - rInf.GetIdx();
+ break;
+ }
+ }
+ assert(2 <= sal_Int32(nFieldLen));
+ pPor = new SwFieldPortion(aFieldName, nullptr, nFieldLen);
+ }
+ else
+ {
+ pPor = new SwTextInputFieldPortion();
+ }
+ }
+ else
+ {
+ if( GetFnt()->IsRef() )
+ pPor = new SwRefPortion;
+ else if (GetFnt()->IsMeta())
+ {
+ auto pMetaPor = new SwMetaPortion;
+
+ // set custom LO_EXT_SHADING color, if it exists
+ SwTextFrame const*const pFrame(rInf.GetTextFrame());
+ SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
+ SwPaM aPam(aPosition);
+ uno::Reference<text::XTextContent> const xRet(
+ SwUnoCursorHelper::GetNestedTextContent(
+ *aPam.GetPointNode().GetTextNode(), aPosition.GetContentIndex(), false) );
+ if (xRet.is())
+ {
+ const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
+ static uno::Reference< uno::XComponentContext > xContext(
+ ::comphelper::getProcessComponentContext());
+
+ static uno::Reference< rdf::XURI > xODF_SHADING(
+ rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);
+
+ uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(
+ rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY);
+
+ const css::uno::Reference<css::rdf::XResource> xSubject(xRet, uno::UNO_QUERY);
+ const uno::Reference<rdf::XRepository>& xRepository =
+ xDocumentMetadataAccess->getRDFRepository();
+ const uno::Reference<container::XEnumeration> xEnum(
+ xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);
+
+ while (xEnum->hasMoreElements())
+ {
+ rdf::Statement stmt;
+ if (!(xEnum->nextElement() >>= stmt)) {
+ throw uno::RuntimeException();
+ }
+ const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
+ if (!xObject.is()) continue;
+ if (xEnum->hasMoreElements()) {
+ SAL_INFO("sw.uno", "ignoring other odf:shading statements");
+ }
+ Color rColor = Color::STRtoRGB(xObject->getValue());
+ pMetaPor->SetShadowColor(rColor);
+ break;
+ }
+ }
+ pPor = pMetaPor;
+ }
+ else if (GetFnt()->IsContentControl())
+ {
+ SwTextFrame const*const pFrame(rInf.GetTextFrame());
+ SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
+ SwTextNode* pTextNode = aPosition.GetNode().GetTextNode();
+ SwTextContentControl* pTextContentControl = nullptr;
+ if (pTextNode)
+ {
+ sal_Int32 nIndex = aPosition.GetContentIndex();
+ if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
+ {
+ pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
+ }
+ }
+ pPor = new SwContentControlPortion(pTextContentControl);
+ }
+ else
+ {
+ // Only at the End!
+ // If pCurr does not have a width, it can however already have content.
+ // E.g. for non-displayable characters
+
+ auto const ch(rInf.GetChar(rInf.GetIdx()));
+ SwTextFrame const*const pFrame(rInf.GetTextFrame());
+ SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
+ sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
+ if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
+ {
+ if (ch == CH_TXT_ATR_FIELDSTART)
+ pPor = new SwFieldFormDatePortion(pBM, true);
+ else if (ch == CH_TXT_ATR_FIELDSEP)
+ pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark?
+ else if (ch == CH_TXT_ATR_FIELDEND)
+ pPor = new SwFieldFormDatePortion(pBM, false);
+ }
+ else if (ch == CH_TXT_ATR_FIELDSTART)
+ pPor = new SwFieldMarkPortion();
+ else if (ch == CH_TXT_ATR_FIELDSEP)
+ pPor = new SwFieldMarkPortion();
+ else if (ch == CH_TXT_ATR_FIELDEND)
+ pPor = new SwFieldMarkPortion();
+ else if (ch == CH_TXT_ATR_FORMELEMENT)
+ {
+ OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???");
+ if (pBM != nullptr)
+ {
+ if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
+ {
+ pPor = new SwFieldFormCheckboxPortion();
+ }
+ else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
+ {
+ pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
+ }
+ /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT.
+ * Otherwise file will crash on open.
+ */
+ else if (pBM->GetFieldname( ) == ODF_FORMTEXT)
+ {
+ pPor = new SwFieldMarkPortion();
+ }
+ }
+ }
+ if( !pPor )
+ {
+ if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() )
+ pPor = m_pCurr;
+ else
+ pPor = new SwTextPortion;
+ }
+ }
+ }
+ return pPor;
+}
+
+// We calculate the length, the following portion limits are defined:
+// 1) Tabs
+// 2) Linebreaks
+// 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD
+// 4) next attribute change
+
+SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
+{
+ // If we're at the line's beginning, we take pCurr
+ // If pCurr is not derived from SwTextPortion, we need to duplicate
+ Seek( rInf.GetIdx() );
+ SwTextPortion *pPor = WhichTextPor( rInf );
+
+ // until next attribute change:
+ const TextFrameIndex nNextAttr = GetNextAttr();
+ TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength()));
+
+ // end of script type:
+ const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx());
+ nNextChg = std::min( nNextChg, nNextScript );
+
+ // end of direction:
+ const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx());
+ nNextChg = std::min( nNextChg, nNextDir );
+
+ // hidden change (potentially via bookmark):
+ const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx());
+ nNextChg = std::min( nNextChg, nNextHidden );
+
+ // bookmarks
+ const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx());
+ nNextChg = std::min(nNextChg, nNextBookmark);
+
+ // Turbo boost:
+ // We assume that font characters are not larger than twice
+ // as wide as height.
+ // Very crazy: we need to take the ascent into account.
+
+ // Mind the trap! GetSize() contains the wished-for height, the real height
+ // is only known in CalcAscent!
+
+ // The ratio is even crazier: a blank in Times New Roman has an ascent of
+ // 182, a height of 200 and a width of 53!
+ // It follows that a line with a lot of blanks is processed incorrectly.
+ // Therefore we increase from factor 2 to 8 (due to negative kerning).
+
+ pPor->SetLen(TextFrameIndex(1));
+ CalcAscent( rInf, pPor );
+
+ const SwFont* pTmpFnt = rInf.GetFont();
+ sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ),
+ sal_Int32( pPor->GetAscent() ) ) / 8;
+ if ( !nExpect )
+ nExpect = 1;
+ nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect);
+ if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect))
+ nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength()));
+
+ // we keep an invariant during method calls:
+ // there are no portion ending characters like hard spaces
+ // or tabs in [ nLeftScanIdx, nRightScanIdx ]
+ if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx )
+ {
+ if ( nNextChg > m_nRightScanIdx )
+ nNextChg = m_nRightScanIdx =
+ rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg );
+ }
+ else
+ {
+ m_nLeftScanIdx = rInf.GetIdx();
+ nNextChg = m_nRightScanIdx =
+ rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg );
+ }
+
+ pPor->SetLen( nNextChg - rInf.GetIdx() );
+ rInf.SetLen( pPor->GetLen() );
+ return pPor;
+}
+
+// first portions have no length
+SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
+{
+ SwLinePortion *pPor = nullptr;
+
+ if( rInf.GetRest() )
+ {
+ // Tabs and fields
+ if( '\0' != rInf.GetHookChar() )
+ return nullptr;
+
+ pPor = rInf.GetRest();
+ if( pPor->IsErgoSumPortion() )
+ rInf.SetErgoDone(true);
+ else
+ if( pPor->IsFootnoteNumPortion() )
+ rInf.SetFootnoteDone(true);
+ else
+ if( pPor->InNumberGrp() )
+ rInf.SetNumDone(true);
+
+ rInf.SetRest(nullptr);
+ m_pCurr->SetRest( true );
+ return pPor;
+ }
+
+ // We can stand in the follow, it's crucial that
+ // pFrame->GetOffset() == 0!
+ if( rInf.GetIdx() )
+ {
+ // We now too can elongate FootnotePortions and ErgoSumPortions
+
+ // 1. The ErgoSumTexts
+ if( !rInf.IsErgoDone() )
+ {
+ if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
+ pPor = NewErgoSumPortion( rInf );
+ rInf.SetErgoDone( true );
+ }
+
+ // 2. Arrow portions
+ if( !pPor && !rInf.IsArrowDone() )
+ {
+ if( m_pFrame->GetOffset() && !m_pFrame->IsFollow() &&
+ rInf.GetIdx() == m_pFrame->GetOffset() )
+ pPor = new SwArrowPortion( *m_pCurr );
+ rInf.SetArrowDone( true );
+ }
+
+ // 3. Kerning portions at beginning of line in grid mode
+ if ( ! pPor && ! m_pCurr->GetNextPortion() )
+ {
+ SwTextGridItem const*const pGrid(
+ GetGridItem(GetTextFrame()->FindPageFrame()));
+ if ( pGrid )
+ pPor = new SwKernPortion( *m_pCurr );
+ }
+
+ // 4. The line rests (multiline fields)
+ if( !pPor )
+ {
+ pPor = rInf.GetRest();
+ // Only for pPor of course
+ if( pPor )
+ {
+ m_pCurr->SetRest( true );
+ rInf.SetRest(nullptr);
+ }
+ }
+ }
+ else
+ {
+ // 5. The foot note count
+ if( !rInf.IsFootnoteDone() )
+ {
+ OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
+ "Rotated number portion trouble" );
+
+ const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame();
+ rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum );
+ if( bFootnoteNum )
+ pPor = NewFootnoteNumPortion( rInf );
+ rInf.SetFootnoteDone( true );
+ }
+
+ // 6. The ErgoSumTexts of course also exist in the TextMaster,
+ // it's crucial whether the SwFootnoteFrame is aFollow
+ if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() )
+ {
+ if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
+ pPor = NewErgoSumPortion( rInf );
+ rInf.SetErgoDone( true );
+ }
+
+ // 7. The numbering
+ if( !rInf.IsNumDone() && !pPor )
+ {
+ OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
+ "Rotated number portion trouble" );
+
+ // If we're in the follow, then of course not
+ if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
+ pPor = NewNumberPortion( rInf );
+ rInf.SetNumDone( true );
+ }
+ // 8. The DropCaps
+ if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
+ pPor = NewDropPortion( rInf );
+
+ // 9. Kerning portions at beginning of line in grid mode
+ if ( !pPor && !m_pCurr->GetNextPortion() )
+ {
+ SwTextGridItem const*const pGrid(
+ GetGridItem(GetTextFrame()->FindPageFrame()));
+ if ( pGrid )
+ pPor = new SwKernPortion( *m_pCurr );
+ }
+ }
+
+ // 10. Decimal tab portion at the beginning of each line in table cells
+ if ( !pPor && !m_pCurr->GetNextPortion() &&
+ GetTextFrame()->IsInTab() &&
+ GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
+ {
+ pPor = NewTabPortion( rInf, true );
+ }
+
+ // 11. suffix of meta-field
+ if (!pPor)
+ {
+ pPor = TryNewNoLengthPortion(rInf);
+ }
+
+ // 12. bookmarks
+ // check this *last* so that BuildMultiPortion() can find it!
+ if (!pPor && rInf.CheckCurrentPosBookmark())
+ {
+ const auto& bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx());
+ if (!bookmark.empty())
+ {
+ // only for character width, maybe replaced with ] later
+ sal_Unicode mark = '[';
+
+ pPor = new SwBookmarkPortion(mark, bookmark);
+ }
+ }
+
+ return pPor;
+}
+
+static bool lcl_OldFieldRest( const SwLineLayout* pCurr )
+{
+ if( !pCurr->GetNext() )
+ return false;
+ const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion();
+ bool bRet = false;
+ while( pPor && !bRet )
+ {
+ bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) ||
+ (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField());
+ if( !pPor->GetLen() )
+ break;
+ pPor = pPor->GetNextPortion();
+ }
+ return bRet;
+}
+
+/* NewPortion sets rInf.nLen
+ * A SwTextPortion is limited by a tab, break, txtatr or attr change
+ * We can have three cases:
+ * 1) The line is full and the wrap was not emulated
+ * -> return 0;
+ * 2) The line is full and a wrap was emulated
+ * -> Reset width and return new FlyPortion
+ * 3) We need to construct a new portion
+ * -> CalcFlyWidth emulates the width and return portion, if needed
+ */
+
+SwLinePortion *SwTextFormatter::NewPortion(SwTextFormatInfo &rInf,
+ ::std::optional<TextFrameIndex> const oMovedFlyIndex)
+{
+ if (oMovedFlyIndex && *oMovedFlyIndex <= rInf.GetIdx())
+ {
+ SAL_WARN_IF(*oMovedFlyIndex != rInf.GetIdx(), "sw.core", "stopping too late, no portion break at fly anchor?");
+ rInf.SetStop(true);
+ return nullptr;
+ }
+
+ // Underflow takes precedence
+ rInf.SetStopUnderflow( false );
+ if( rInf.GetUnderflow() )
+ {
+ OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" );
+ return Underflow( rInf );
+ }
+
+ // If the line is full, flys and Underflow portions could be waiting ...
+ if( rInf.IsFull() )
+ {
+ // LineBreaks and Flys (bug05.sdw)
+ // IsDummy()
+ if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) )
+ return nullptr;
+
+ // When the text bumps into the Fly, or when the Fly comes first because
+ // it juts out over the left edge, GetFly() is returned.
+ // When IsFull() and no GetFly() is available, naturally zero is returned.
+ if( rInf.GetFly() )
+ {
+ if( rInf.GetLast()->IsBreakPortion() )
+ {
+ delete rInf.GetFly();
+ rInf.SetFly( nullptr );
+ }
+
+ return rInf.GetFly();
+ }
+
+ // A nasty special case: A frame without wrap overlaps the Footnote area.
+ // We must declare the Footnote portion as rest of line, so that
+ // SwTextFrame::Format doesn't abort (the text mass already was formatted).
+ if( rInf.GetRest() )
+ rInf.SetNewLine( true );
+ else
+ {
+ // When the next line begins with a rest of a field, but now no
+ // rest remains, the line must definitely be formatted anew!
+ if( lcl_OldFieldRest( GetCurr() ) )
+ rInf.SetNewLine( true );
+ else
+ {
+ SwLinePortion *pFirst = WhichFirstPortion( rInf );
+ if( pFirst )
+ {
+ rInf.SetNewLine( true );
+ if( pFirst->InNumberGrp() )
+ rInf.SetNumDone( false) ;
+ delete pFirst;
+ }
+ }
+ }
+
+ return nullptr;
+ }
+
+ SwLinePortion *pPor = WhichFirstPortion( rInf );
+
+ // Check for Hidden Portion:
+ if ( !pPor )
+ {
+ TextFrameIndex nEnd = rInf.GetIdx();
+ if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) )
+ pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() );
+ }
+
+ if( !pPor )
+ {
+ if( ( !m_pMulti || m_pMulti->IsBidi() ) &&
+ // i#42734
+ // No multi portion if there is a hook character waiting:
+ ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) )
+ {
+ // We open a multiportion part, if we enter a multi-line part
+ // of the paragraph.
+ TextFrameIndex nEnd = rInf.GetIdx();
+ std::optional<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti );
+ if( pCreate )
+ {
+ SwMultiPortion* pTmp = nullptr;
+
+ if ( SwMultiCreatorId::Bidi == pCreate->nId )
+ pTmp = new SwBidiPortion( nEnd, pCreate->nLevel );
+ else if ( SwMultiCreatorId::Ruby == pCreate->nId )
+ {
+ pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(),
+ GetTextFrame()->GetDoc().getIDocumentSettingAccess(),
+ nEnd, TextFrameIndex(0), rInf );
+ }
+ else if( SwMultiCreatorId::Rotate == pCreate->nId )
+ {
+ pTmp = new SwRotatedPortion( *pCreate, nEnd,
+ GetTextFrame()->IsRightToLeft() );
+ GetTextFrame()->SetHasRotatedPortions(true);
+ }
+ else
+ pTmp = new SwDoubleLinePortion( *pCreate, nEnd );
+
+ pCreate.reset();
+ CalcFlyWidth( rInf );
+
+ return pTmp;
+ }
+ }
+ // Tabs and Fields
+ sal_Unicode cChar = rInf.GetHookChar();
+
+ if( cChar )
+ {
+ /* We fetch cChar again to be sure that the tab is pending now and
+ * didn't move to the next line (as happens behind frames).
+ * However, when a FieldPortion is in the rest, we must naturally fetch
+ * the cChar from the field content, e.g. DecimalTabs and fields (22615)
+ */
+ if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() )
+ cChar = rInf.GetChar( rInf.GetIdx() );
+ rInf.ClearHookChar();
+ }
+ else
+ {
+ if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()))
+ {
+ rInf.SetFull(true);
+ CalcFlyWidth( rInf );
+ return pPor;
+ }
+ cChar = rInf.GetChar( rInf.GetIdx() );
+ }
+
+ switch( cChar )
+ {
+ case CH_TAB:
+ pPor = NewTabPortion( rInf, false ); break;
+
+ case CH_BREAK:
+ {
+ SwTextAttr* pHint = GetAttr(rInf.GetIdx());
+ pPor = new SwBreakPortion(*rInf.GetLast(), pHint);
+ break;
+ }
+
+ case CHAR_SOFTHYPHEN: // soft hyphen
+ pPor = new SwSoftHyphPortion; break;
+
+ case CHAR_HARDBLANK: // no-break space
+ // Please check tdf#115067 if you want to edit the char
+ pPor = new SwBlankPortion( cChar ); break;
+
+ case CHAR_HARDHYPHEN: // non-breaking hyphen
+ pPor = new SwBlankPortion( '-' ); break;
+
+ case CHAR_ZWSP: // zero width space
+ case CHAR_WJ : // word joiner
+ pPor = new SwControlCharPortion( cChar ); break;
+
+ case CH_TXTATR_BREAKWORD:
+ case CH_TXTATR_INWORD:
+ if( rInf.HasHint( rInf.GetIdx() ) )
+ {
+ pPor = NewExtraPortion( rInf );
+ break;
+ }
+ [[fallthrough]];
+ default :
+ {
+ SwTabPortion* pLastTabPortion = rInf.GetLastTab();
+ if ( pLastTabPortion && cChar == rInf.GetTabDecimal() )
+ {
+ // Abandon dec. tab position if line is full
+ // We have a decimal tab portion in the line and the next character has to be
+ // aligned at the tab stop position. We store the width from the beginning of
+ // the tab stop portion up to the portion containing the decimal separator:
+ if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ &&
+ PortionType::TabDecimal == pLastTabPortion->GetWhichPor() )
+ {
+ OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" );
+ const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = o3tl::narrowing<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() );
+ static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition );
+ rInf.SetTabDecimal( 0 );
+ }
+ else
+ rInf.SetFull( rInf.GetLastTab()->Format( rInf ) );
+ }
+
+ if( rInf.GetRest() )
+ {
+ if( rInf.IsFull() )
+ {
+ rInf.SetNewLine(true);
+ return nullptr;
+ }
+ pPor = rInf.GetRest();
+ rInf.SetRest(nullptr);
+ }
+ else
+ {
+ if( rInf.IsFull() )
+ return nullptr;
+ pPor = NewTextPortion( rInf );
+ }
+ break;
+ }
+ }
+
+ // if a portion is created despite there being a pending RestPortion,
+ // then it is a field which has been split (e.g. because it contains a Tab)
+ if( pPor && rInf.GetRest() )
+ pPor->SetLen(TextFrameIndex(0));
+
+ // robust:
+ if( !pPor || rInf.IsStop() )
+ {
+ delete pPor;
+ return nullptr;
+ }
+ }
+
+ assert(pPor && "can only reach here with pPor existing");
+
+ // Special portions containing numbers (footnote anchor, footnote number,
+ // numbering) can be contained in a rotated portion, if the user
+ // choose a rotated character attribute.
+ if (!m_pMulti)
+ {
+ if ( pPor->IsFootnotePortion() )
+ {
+ const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote();
+
+ if ( pTextFootnote )
+ {
+ SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
+ const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();
+ const SwEndNoteInfo* pInfo;
+ if( rFootnote.IsEndNote() )
+ pInfo = &pDoc->GetEndNoteInfo();
+ else
+ pInfo = &pDoc->GetFootnoteInfo();
+ const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
+
+ Degree10 nDir(0);
+ if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) )
+ nDir = pItem->GetValue();
+
+ if ( nDir )
+ {
+ delete pPor;
+ pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1),
+ 900_deg10 == nDir
+ ? DIR_BOTTOM2TOP
+ : DIR_TOP2BOTTOM );
+ }
+ }
+ }
+ else if ( pPor->InNumberGrp() )
+ {
+ const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
+
+ if ( pNumFnt )
+ {
+ Degree10 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() );
+ if ( nDir )
+ {
+ delete pPor;
+ pPor = new SwRotatedPortion(TextFrameIndex(0), 900_deg10 == nDir
+ ? DIR_BOTTOM2TOP
+ : DIR_TOP2BOTTOM );
+
+ rInf.SetNumDone( false );
+ rInf.SetFootnoteDone( false );
+ }
+ }
+ }
+ }
+
+ // The font is set in output device,
+ // the ascent and the height will be calculated.
+ if( !pPor->GetAscent() && !pPor->Height() )
+ CalcAscent( rInf, pPor );
+ rInf.SetLen( pPor->GetLen() );
+
+ // In CalcFlyWidth Width() will be shortened if a FlyPortion is present.
+ CalcFlyWidth( rInf );
+
+ // One must not forget that pCurr as GetLast() must provide reasonable values:
+ if( !m_pCurr->Height() )
+ {
+ OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" );
+ m_pCurr->Height( pPor->Height(), false );
+ m_pCurr->SetAscent( pPor->GetAscent() );
+ }
+
+ OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong");
+ if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() )
+ {
+ delete pPor;
+ pPor = rInf.GetFly();
+ }
+ return pPor;
+}
+
+TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos)
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
+ "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" );
+
+ // For the formatting routines, we set pOut to the reference device.
+ SwHookOut aHook( GetInfo() );
+ if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength()))
+ GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength()));
+
+ bool bBuild = true;
+ SetFlyInCntBase( false );
+ GetInfo().SetLineHeight( 0 );
+ GetInfo().SetLineNetHeight( 0 );
+
+ // Recycling must be suppressed by changed line height and also
+ // by changed ascent (lowering of baseline).
+ const SwTwips nOldHeight = m_pCurr->Height();
+ const SwTwips nOldAscent = m_pCurr->GetAscent();
+
+ m_pCurr->SetEndHyph( false );
+ m_pCurr->SetMidHyph( false );
+
+ // fly positioning can make it necessary format a line several times
+ // for this, we have to keep a copy of our rest portion
+ SwLinePortion* pField = GetInfo().GetRest();
+ std::unique_ptr<SwFieldPortion> xSaveField;
+
+ if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() )
+ xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) ));
+
+ // for an optimal repaint rectangle, we want to compare fly portions
+ // before and after the BuildPortions call
+ const bool bOptimizeRepaint = AllowRepaintOpt();
+ TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen();
+ std::vector<tools::Long> flyStarts;
+
+ // these are the conditions for a fly position comparison
+ if ( bOptimizeRepaint && m_pCurr->IsFly() )
+ {
+ SwLinePortion* pPor = m_pCurr->GetFirstPortion();
+ tools::Long nPOfst = 0;
+ while ( pPor )
+ {
+ if ( pPor->IsFlyPortion() )
+ // insert start value of fly portion
+ flyStarts.push_back( nPOfst );
+
+ nPOfst += pPor->Width();
+ pPor = pPor->GetNextPortion();
+ }
+ }
+
+ // Here soon the underflow check follows.
+ while( bBuild )
+ {
+ GetInfo().SetFootnoteInside( false );
+ GetInfo().SetOtherThanFootnoteInside( false );
+
+ // These values must not be reset by FormatReset();
+ const bool bOldNumDone = GetInfo().IsNumDone();
+ const bool bOldFootnoteDone = GetInfo().IsFootnoteDone();
+ const bool bOldArrowDone = GetInfo().IsArrowDone();
+ const bool bOldErgoDone = GetInfo().IsErgoDone();
+
+ // besides other things, this sets the repaint offset to 0
+ FormatReset( GetInfo() );
+
+ GetInfo().SetNumDone( bOldNumDone );
+ GetInfo().SetFootnoteDone(bOldFootnoteDone);
+ GetInfo().SetArrowDone( bOldArrowDone );
+ GetInfo().SetErgoDone( bOldErgoDone );
+
+ // build new portions for this line
+ BuildPortions( GetInfo() );
+
+ if( GetInfo().IsStop() )
+ {
+ m_pCurr->SetLen(TextFrameIndex(0));
+ m_pCurr->Height( GetFrameRstHeight() + 1, false );
+ m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 );
+
+ // Don't oversize the line in case of split flys, so we don't try to move the anchor
+ // of a precede fly forward, next to its follow.
+ if (m_pFrame->HasNonLastSplitFlyDrawObj())
+ {
+ m_pCurr->SetRealHeight(GetFrameRstHeight());
+ }
+
+ m_pCurr->Width(0);
+ m_pCurr->Truncate();
+ return nStartPos;
+ }
+ else if( GetInfo().IsDropInit() )
+ {
+ DropInit();
+ GetInfo().SetDropInit( false );
+ }
+
+ m_pCurr->CalcLine( *this, GetInfo() );
+ CalcRealHeight( GetInfo().IsNewLine() );
+
+ //i#120864 For Special case that at the first calculation couldn't get
+ //correct height. And need to recalculate for the right height.
+ SwLinePortion* pPorTmp = m_pCurr->GetNextPortion();
+ if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() &&
+ m_pCurr->Height() > pPorTmp->Height())))
+ {
+ SwTwips nTmpAscent, nTmpHeight;
+ CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+ AlignFlyInCntBase( Y() + tools::Long( nTmpAscent ) );
+ m_pCurr->CalcLine( *this, GetInfo() );
+ CalcRealHeight();
+ }
+
+ // bBuild decides if another lap of honor is done
+ if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() )
+ {
+ m_pCurr->SetRealHeight( GetInfo().GetLineHeight() );
+ bBuild = false;
+ }
+ else
+ {
+ bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
+ || GetInfo().CheckFootnotePortion(m_pCurr);
+ if( bBuild )
+ {
+ // fdo44018-2.doc: only restore m_bNumDone if a SwNumberPortion will be truncated
+ for (SwLinePortion * pPor = m_pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion())
+ {
+ if (pPor->InNumberGrp())
+ {
+ GetInfo().SetNumDone( bOldNumDone );
+ break;
+ }
+ }
+ GetInfo().ResetMaxWidthDiff();
+
+ // delete old rest
+ if ( GetInfo().GetRest() )
+ {
+ delete GetInfo().GetRest();
+ GetInfo().SetRest( nullptr );
+ }
+
+ // set original rest portion
+ if ( xSaveField )
+ GetInfo().SetRest( new SwFieldPortion( *xSaveField ) );
+
+ m_pCurr->SetLen(TextFrameIndex(0));
+ m_pCurr->Width(0);
+ m_pCurr->Truncate();
+ }
+ }
+ }
+
+ // In case of compat mode, it's possible that a tab portion is wider after
+ // formatting than before. If this is the case, we also have to make sure
+ // the SwLineLayout is wider as well.
+ if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN))
+ {
+ sal_uInt16 nSum = 0;
+ SwLinePortion* pPor = m_pCurr->GetFirstPortion();
+
+ while (pPor)
+ {
+ nSum += pPor->Width();
+ pPor = pPor->GetNextPortion();
+ }
+
+ if (nSum > m_pCurr->Width())
+ m_pCurr->Width(nSum);
+ }
+
+ // calculate optimal repaint rectangle
+ if ( bOptimizeRepaint )
+ {
+ GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) );
+ flyStarts.clear();
+ }
+ else
+ // Special case: we do not allow an optimization of the repaint
+ // area, but during formatting the repaint offset is set to indicate
+ // a maximum value for the offset. This value has to be reset:
+ GetInfo().SetPaintOfst( 0 );
+
+ // This corrects the start of the reformat range if something has
+ // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt
+ // will give us a wrong result if we have to reformat another line
+ GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() );
+
+ // delete master copy of rest portion
+ xSaveField.reset();
+
+ TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen();
+
+ // adjust text if kana compression is enabled
+ if ( GetInfo().CompressLine() )
+ {
+ SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr );
+
+ // adjust repaint offset
+ if ( nRepaintOfst < GetInfo().GetPaintOfst() )
+ GetInfo().SetPaintOfst( nRepaintOfst );
+ }
+
+ CalcAdjustLine( m_pCurr );
+
+ if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() )
+ {
+ SetFlyInCntBase();
+ GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling
+ // all following line must be painted and when Flys are around,
+ // also formatted
+ GetInfo().SetShift( true );
+ }
+
+ if ( IsFlyInCntBase() && !IsQuick() )
+ UpdatePos( m_pCurr, GetTopLeft(), GetStart() );
+
+ return nNewStart;
+}
+
+void SwTextFormatter::RecalcRealHeight()
+{
+ do
+ {
+ CalcRealHeight();
+ } while (Next());
+}
+
+void SwTextFormatter::CalcRealHeight( bool bNewLine )
+{
+ SwTwips nLineHeight = m_pCurr->Height();
+ m_pCurr->SetClipping( false );
+
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ if ( pGrid && GetInfo().SnapToGrid() )
+ {
+ const sal_uInt16 nGridWidth = pGrid->GetBaseHeight();
+ const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight();
+ const bool bRubyTop = ! pGrid->GetRubyTextBelow();
+
+ nLineHeight = nGridWidth + nRubyHeight;
+ const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight;
+ nLineHeight *= nAmpRatio;
+
+ const sal_uInt16 nAsc = m_pCurr->GetAscent() +
+ ( bRubyTop ?
+ ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 :
+ ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 );
+
+ m_pCurr->Height( nLineHeight, false );
+ m_pCurr->SetAscent( nAsc );
+ m_pInf->GetParaPortion()->SetFixLineHeight();
+
+ // we ignore any line spacing options except from ...
+ const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing();
+ if ( ! IsParaLine() && pSpace &&
+ SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() )
+ {
+ sal_uLong nTmp = pSpace->GetPropLineSpace();
+
+ if( nTmp < 100 )
+ nTmp = 100;
+
+ nTmp *= nLineHeight;
+ nLineHeight = nTmp / 100;
+ }
+
+ m_pCurr->SetRealHeight( nLineHeight );
+ return;
+ }
+
+ // The dummy flag is set on lines that only contain flyportions, these shouldn't
+ // consider register-true and so on. Unfortunately an empty line can be at
+ // the end of a paragraph (empty paragraphs or behind a Shift-Return),
+ // which should consider the register.
+ if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext()
+ && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength())
+ && !bNewLine))
+ {
+ const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
+ if( pSpace )
+ {
+ switch( pSpace->GetLineSpaceRule() )
+ {
+ case SvxLineSpaceRule::Auto:
+ // shrink first line of paragraph too on spacing < 100%
+ if (IsParaLine() &&
+ pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop
+ && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
+ {
+ tools::Long nTmp = pSpace->GetPropLineSpace();
+ // Word will render < 50% too but it's just not readable
+ if( nTmp < 50 )
+ nTmp = nTmp ? 50 : 100;
+ if (nTmp<100) { // code adapted from fixed line height
+ nTmp *= nLineHeight;
+ nTmp /= 100;
+ if( !nTmp )
+ ++nTmp;
+ nLineHeight = nTmp;
+ sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80%
+#if 0
+ // could do clipping here (like Word does)
+ // but at 0.5 its unreadable either way...
+ if( nAsc < pCurr->GetAscent() ||
+ nLineHeight - nAsc < pCurr->Height() -
+ pCurr->GetAscent() )
+ pCurr->SetClipping( true );
+#endif
+ m_pCurr->SetAscent( nAsc );
+ m_pCurr->Height( nLineHeight, false );
+ m_pInf->GetParaPortion()->SetFixLineHeight();
+ }
+ }
+ break;
+ case SvxLineSpaceRule::Min:
+ {
+ if( nLineHeight < pSpace->GetLineHeight() )
+ nLineHeight = pSpace->GetLineHeight();
+ break;
+ }
+ case SvxLineSpaceRule::Fix:
+ {
+ nLineHeight = pSpace->GetLineHeight();
+ const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80%
+ if( nAsc < m_pCurr->GetAscent() ||
+ nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() )
+ m_pCurr->SetClipping( true );
+ m_pCurr->Height( nLineHeight, false );
+ m_pCurr->SetAscent( nAsc );
+ m_pInf->GetParaPortion()->SetFixLineHeight();
+ }
+ break;
+ default: OSL_FAIL( ": unknown LineSpaceRule" );
+ }
+ // Note: for the _first_ line the line spacing of the previous
+ // paragraph is applied in SwFlowFrame::CalcUpperSpace()
+ if( !IsParaLine() )
+ switch( pSpace->GetInterLineSpaceRule() )
+ {
+ case SvxInterLineSpaceRule::Off:
+ break;
+ case SvxInterLineSpaceRule::Prop:
+ {
+ tools::Long nTmp = pSpace->GetPropLineSpace();
+ // 50% is the minimum, if 0% we switch to the
+ // default value 100% ...
+ if( nTmp < 50 )
+ nTmp = nTmp ? 50 : 100;
+
+ // extend line height by (nPropLineSpace - 100) percent of the font height
+ nTmp -= 100;
+ nTmp *= m_pCurr->GetTextHeight();
+ nTmp /= 100;
+ nTmp += nLineHeight;
+ if (nTmp < 1)
+ nTmp = 1;
+ nLineHeight = nTmp;
+ break;
+ }
+ case SvxInterLineSpaceRule::Fix:
+ {
+ nLineHeight = nLineHeight + pSpace->GetInterLineSpace();
+ break;
+ }
+ default: OSL_FAIL( ": unknown InterLineSpaceRule" );
+ }
+ }
+
+ if( IsRegisterOn() )
+ {
+ SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height();
+ SwRectFnSet aRectFnSet(m_pFrame);
+ if ( aRectFnSet.IsVert() )
+ nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY );
+ nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() );
+ const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() );
+ if( nDiff )
+ nLineHeight += RegDiff() - nDiff;
+ }
+ }
+ m_pCurr->SetRealHeight( nLineHeight );
+}
+
+void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const
+{
+ // delete Fly in any case!
+ ClearFly( rInf );
+ rInf.Init();
+
+ rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
+ rInf.SetRoot( m_pCurr );
+ rInf.SetLineStart( m_nStart );
+ rInf.SetIdx( m_nStart );
+ rInf.Left( Left() );
+ rInf.Right( Right() );
+ rInf.First( FirstLeft() );
+ rInf.LeftMargin(GetLeftMargin());
+
+ rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) );
+ rInf.Width( rInf.RealWidth() );
+ if( const_cast<SwTextFormatter*>(this)->GetRedln() )
+ {
+ const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() );
+ const_cast<SwTextFormatter*>(this)->GetRedln()->Reset();
+ }
+}
+
+void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf )
+{
+ m_pFirstOfBorderMerge = nullptr;
+ m_pCurr->Truncate();
+ m_pCurr->Init();
+
+ // delete pSpaceAdd and pKanaComp
+ m_pCurr->FinishSpaceAdd();
+ m_pCurr->FinishKanaComp();
+ m_pCurr->ResetFlags();
+ FeedInf( rInf );
+}
+
+bool SwTextFormatter::CalcOnceMore()
+{
+ if( m_pDropFormat )
+ {
+ const sal_uInt16 nOldDrop = GetDropHeight();
+ CalcDropHeight( m_pDropFormat->GetLines() );
+ m_bOnceMore = nOldDrop != GetDropHeight();
+ }
+ else
+ m_bOnceMore = false;
+ return m_bOnceMore;
+}
+
+SwTwips SwTextFormatter::CalcBottomLine() const
+{
+ SwTwips nRet = Y() + GetLineHeight();
+ SwTwips nMin = GetInfo().GetTextFly().GetMinBottom();
+ if( nMin && ++nMin > nRet )
+ {
+ SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height()
+ - m_pFrame->getFramePrintArea().Top();
+ if( nRet + nDist < nMin )
+ {
+ const bool bRepaint = HasTruncLines() &&
+ GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1;
+ nRet = nMin - nDist;
+ if( bRepaint )
+ {
+ const_cast<SwRepaint&>(GetInfo().GetParaPortion()
+ ->GetRepaint()).Bottom( nRet-1 );
+ const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 );
+ }
+ }
+ }
+ return nRet;
+}
+
+// FME/OD: This routine does a limited text formatting.
+SwTwips SwTextFormatter::CalcFitToContent_()
+{
+ FormatReset( GetInfo() );
+ BuildPortions( GetInfo() );
+ m_pCurr->CalcLine( *this, GetInfo() );
+ return m_pCurr->Width();
+}
+
+// determines if the calculation of a repaint offset is allowed
+// otherwise each line is painted from 0 (this is a copy of the beginning
+// of the former SwTextFormatter::Recycle() function
+bool SwTextFormatter::AllowRepaintOpt() const
+{
+ // reformat position in front of current line? Only in this case
+ // we want to set the repaint offset
+ bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() &&
+ m_pCurr->GetLen();
+
+ // a special case is the last line of a block adjusted paragraph:
+ if ( bOptimizeRepaint )
+ {
+ switch( GetAdjust() )
+ {
+ case SvxAdjust::Block:
+ {
+ if( IsLastBlock() || IsLastCenter() )
+ bOptimizeRepaint = false;
+ else
+ {
+ // ????: blank in the last master line (blocksat.sdw)
+ bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow();
+ if ( bOptimizeRepaint )
+ {
+ SwLinePortion *pPos = m_pCurr->GetFirstPortion();
+ while ( pPos && !pPos->IsFlyPortion() )
+ pPos = pPos->GetNextPortion();
+ bOptimizeRepaint = !pPos;
+ }
+ }
+ break;
+ }
+ case SvxAdjust::Center:
+ case SvxAdjust::Right:
+ bOptimizeRepaint = false;
+ break;
+ default: ;
+ }
+ }
+
+ // Again another special case: invisible SoftHyphs
+ const TextFrameIndex nReformat = GetInfo().GetReformatStart();
+ if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat)
+ {
+ const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength())
+ ? 0
+ : GetInfo().GetText()[ sal_Int32(nReformat) ];
+ bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh )
+ || ! GetInfo().HasHint( nReformat );
+ }
+
+ return bOptimizeRepaint;
+}
+
+void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom )
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
+ "SwTextFormatter::CalcUnclipped with unswapped frame" );
+
+ SwTwips nFlyAsc, nFlyDesc;
+ m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc );
+ rTop = Y() + GetCurr()->GetAscent();
+ rBottom = rTop + nFlyDesc;
+ rTop -= nFlyAsc;
+}
+
+void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart,
+ TextFrameIndex const nStartIdx, bool bAlways) const
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
+ "SwTextFormatter::UpdatePos with unswapped frame" );
+
+ if( GetInfo().IsTest() )
+ return;
+ SwLinePortion *pFirst = pCurrent->GetFirstPortion();
+ SwLinePortion *pPos = pFirst;
+ SwTextPaintInfo aTmpInf( GetInfo() );
+ aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() );
+ aTmpInf.ResetSpaceIdx();
+ aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() );
+ aTmpInf.ResetKanaIdx();
+
+ // The frame's size
+ aTmpInf.SetIdx( nStartIdx );
+ aTmpInf.SetPos( aStart );
+
+ SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
+ pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
+
+ const SwTwips nTmpHeight = pCurrent->GetRealHeight();
+ SwTwips nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height();
+ AsCharFlags nFlags = AsCharFlags::UlSpace;
+ if( GetMulti() )
+ {
+ aTmpInf.SetDirection( GetMulti()->GetDirection() );
+ if( GetMulti()->HasRotation() )
+ {
+ nFlags |= AsCharFlags::Rotate;
+ if( GetMulti()->IsRevers() )
+ {
+ nFlags |= AsCharFlags::Reverse;
+ aTmpInf.X( aTmpInf.X() - nAscent );
+ }
+ else
+ aTmpInf.X( aTmpInf.X() + nAscent );
+ }
+ else
+ {
+ if ( GetMulti()->IsBidi() )
+ nFlags |= AsCharFlags::Bidi;
+ aTmpInf.Y( aTmpInf.Y() + nAscent );
+ }
+ }
+ else
+ aTmpInf.Y( aTmpInf.Y() + nAscent );
+
+ while( pPos )
+ {
+ // We only know one case where changing the position (caused by the
+ // adjustment) could be relevant for a portion: We need to SetRefPoint
+ // for FlyCntPortions.
+ if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
+ && ( bAlways || !IsQuick() ) )
+ {
+ pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
+
+ if( pPos->IsGrfNumPortion() )
+ {
+ if( !nFlyAsc && !nFlyDesc )
+ {
+ nTmpAscent = nAscent;
+ nFlyAsc = nAscent;
+ nTmpDescent = nTmpHeight - nAscent;
+ nFlyDesc = nTmpDescent;
+ }
+ static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
+ nFlyAsc, nFlyDesc );
+ }
+ else
+ {
+ Point aBase( aTmpInf.GetPos() );
+ if ( GetInfo().GetTextFrame()->IsVertical() )
+ GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase );
+
+ static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(),
+ aBase, nTmpAscent, nTmpDescent, nFlyAsc,
+ nFlyDesc, nFlags );
+ }
+ }
+ if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() )
+ {
+ OSL_ENSURE( !GetMulti(), "Too much multi" );
+ const_cast<SwTextFormatter*>(this)->m_pMulti = static_cast<SwMultiPortion*>(pPos);
+ SwLineLayout *pLay = &GetMulti()->GetRoot();
+ Point aSt( aTmpInf.X(), aStart.Y() );
+
+ if ( GetMulti()->HasBrackets() )
+ {
+ OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles");
+ aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() );
+ }
+ else if( GetMulti()->HasRotation() )
+ {
+ aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() );
+ if( GetMulti()->IsRevers() )
+ aSt.AdjustX(GetMulti()->Width() );
+ else
+ aSt.AdjustY(GetMulti()->Height() );
+ }
+ else if ( GetMulti()->IsBidi() )
+ // jump to end of the bidi portion
+ aSt.AdjustX(pLay->Width() );
+
+ TextFrameIndex nStIdx = aTmpInf.GetIdx();
+ do
+ {
+ UpdatePos( pLay, aSt, nStIdx, bAlways );
+ nStIdx = nStIdx + pLay->GetLen();
+ aSt.AdjustY(pLay->Height() );
+ pLay = pLay->GetNext();
+ } while ( pLay );
+ const_cast<SwTextFormatter*>(this)->m_pMulti = nullptr;
+ }
+ pPos->Move( aTmpInf );
+ pPos = pPos->GetNextPortion();
+ }
+}
+
+void SwTextFormatter::AlignFlyInCntBase( tools::Long nBaseLine ) const
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
+ "SwTextFormatter::AlignFlyInCntBase with unswapped frame" );
+
+ if( GetInfo().IsTest() )
+ return;
+ SwLinePortion *pFirst = m_pCurr->GetFirstPortion();
+ SwLinePortion *pPos = pFirst;
+ AsCharFlags nFlags = AsCharFlags::None;
+ if( GetMulti() && GetMulti()->HasRotation() )
+ {
+ nFlags |= AsCharFlags::Rotate;
+ if( GetMulti()->IsRevers() )
+ nFlags |= AsCharFlags::Reverse;
+ }
+
+ SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
+
+ while( pPos )
+ {
+ if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
+ {
+ m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
+
+ if( pPos->IsGrfNumPortion() )
+ static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
+ nFlyAsc, nFlyDesc );
+ else
+ {
+ Point aBase;
+ if ( GetInfo().GetTextFrame()->IsVertical() )
+ {
+ nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine );
+ aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() );
+ }
+ else
+ aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine );
+
+ static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent,
+ nFlyAsc, nFlyDesc, nFlags );
+ }
+ }
+ pPos = pPos->GetNextPortion();
+ }
+}
+
+bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const
+{
+ OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" );
+ if( GetCurr() )
+ {
+ // First we check, whether a fly overlaps with the line.
+ // = GetLineHeight()
+ const sal_uInt16 nHeight = GetCurr()->GetRealHeight();
+ SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight );
+
+ SwRect aLineVert( aLine );
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchHorizontalToVertical( aLineVert );
+ SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) );
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchVerticalToHorizontal( aInter );
+
+ if( !aInter.HasArea() )
+ return false;
+
+ // We now check every portion that could have lowered for overlapping
+ // with the fly.
+ const SwLinePortion *pPos = GetCurr()->GetFirstPortion();
+ aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
+ aLine.Height( GetCurr()->Height() );
+
+ while( pPos )
+ {
+ aLine.Width( pPos->Width() );
+
+ aLineVert = aLine;
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchHorizontalToVertical( aLineVert );
+ aInter = rInf.GetTextFly().GetFrame( aLineVert );
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchVerticalToHorizontal( aInter );
+
+ // New flys from below?
+ if( !pPos->IsFlyPortion() )
+ {
+ if( aInter.Overlaps( aLine ) )
+ {
+ aInter.Intersection_( aLine );
+ if( aInter.HasArea() )
+ {
+ // To be evaluated during reformat of this line:
+ // RealHeight including spacing
+ rInf.SetLineHeight( nHeight );
+ // Height without extra spacing
+ rInf.SetLineNetHeight( m_pCurr->Height() );
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // The fly portion is not intersected by a fly anymore
+ if ( ! aInter.Overlaps( aLine ) )
+ {
+ rInf.SetLineHeight( nHeight );
+ rInf.SetLineNetHeight( m_pCurr->Height() );
+ return true;
+ }
+ else
+ {
+ aInter.Intersection_( aLine );
+
+ // No area means a fly has become invalid because of
+ // lowering the line => reformat the line
+ // we also have to reformat the line, if the fly size
+ // differs from the intersection interval's size.
+ if( ! aInter.HasArea() ||
+ static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() )
+ {
+ rInf.SetLineHeight( nHeight );
+ rInf.SetLineNetHeight( m_pCurr->Height() );
+ return true;
+ }
+ }
+ }
+
+ aLine.Left( aLine.Left() + pPos->Width() );
+ pPos = pPos->GetNextPortion();
+ }
+ }
+ return false;
+}
+
+void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf )
+{
+ if( GetMulti() || rInf.GetFly() )
+ return;
+
+ SwTextFly& rTextFly = rInf.GetTextFly();
+ if( !rTextFly.IsOn() || rInf.IsIgnoreFly() )
+ return;
+
+ const SwLinePortion *pLast = rInf.GetLast();
+
+ tools::Long nAscent;
+ tools::Long nTop = Y();
+ tools::Long nHeight;
+
+ if( rInf.GetLineHeight() )
+ {
+ // Real line height has already been calculated, we only have to
+ // search for intersections in the lower part of the strip
+ nAscent = m_pCurr->GetAscent();
+ nHeight = rInf.GetLineNetHeight();
+ nTop += rInf.GetLineHeight() - nHeight;
+ }
+ else
+ {
+ // We make a first guess for the lines real height
+ if ( ! m_pCurr->GetRealHeight() )
+ CalcRealHeight();
+
+ nAscent = pLast->GetAscent();
+ nHeight = pLast->Height();
+
+ if ( m_pCurr->GetRealHeight() > nHeight )
+ nTop += m_pCurr->GetRealHeight() - nHeight;
+ else
+ // Important for fixed space between lines
+ nHeight = m_pCurr->GetRealHeight();
+ }
+
+ const tools::Long nLeftMar = GetLeftMargin();
+ const tools::Long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin();
+
+ SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X()
+ + nLeftMar - nLeftMin , nHeight );
+
+ bool bWordFlyWrap = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS);
+ // tdf#116486: consider also the upper margin from getFramePrintArea because intersections
+ // with this additional space should lead to repositioning of paragraphs
+ // For compatibility we grab a related compat flag:
+ if (bWordFlyWrap && IsFirstTextLine())
+ {
+ tools::Long nUpper = m_pFrame->getFramePrintArea().Top();
+ // Make sure that increase only happens in case the upper spacing comes from the upper
+ // margin of the current text frame, not because of a lower spacing of the previous text
+ // frame.
+ nUpper -= m_pFrame->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid();
+ // Increase the rectangle
+ if( nUpper > 0 && nTop >= nUpper )
+ aLine.SubTop( nUpper );
+ }
+
+ if (IsFirstTextLine())
+ {
+ // Check if a compatibility mode requires considering also the lower margin of this text
+ // frame, intersections with this additional space should lead to shifting the paragraph
+ // marker down.
+ SwTwips nLower = m_pFrame->GetLowerMarginForFlyIntersect();
+ if (nLower > 0)
+ {
+ aLine.AddBottom(nLower);
+ }
+ }
+
+ SwRect aLineVert( aLine );
+ if ( m_pFrame->IsRightToLeft() )
+ m_pFrame->SwitchLTRtoRTL( aLineVert );
+
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchHorizontalToVertical( aLineVert );
+
+ // GetFrame(...) determines and returns the intersection rectangle
+ SwRect aInter( rTextFly.GetFrame( aLineVert ) );
+
+ if ( m_pFrame->IsRightToLeft() )
+ m_pFrame->SwitchRTLtoLTR( aInter );
+
+ if ( m_pFrame->IsVertical() )
+ m_pFrame->SwitchVerticalToHorizontal( aInter );
+
+ if (!aInter.IsEmpty() && aInter.Bottom() < nTop)
+ {
+ // Intersects with the frame area (with upper margin), but not with the print area (without
+ // upper margin). Don't reserve space for the fly portion in this case, text is allowed to
+ // flow there.
+ aInter.Height(0);
+ }
+
+ if( !aInter.Overlaps( aLine ) )
+ return;
+
+ aLine.Left( rInf.X() + nLeftMar );
+ bool bForced = false;
+ bool bSplitFly = false;
+ for (const auto& pObj : rTextFly.GetAnchoredObjList())
+ {
+ auto pFlyFrame = pObj->DynCastFlyFrame();
+ if (!pFlyFrame)
+ {
+ continue;
+ }
+
+ if (!pFlyFrame->IsFlySplitAllowed())
+ {
+ continue;
+ }
+
+ bSplitFly = true;
+ break;
+ }
+ if( aInter.Left() <= nLeftMin )
+ {
+ SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left();
+ SwTwips nFramePrintAreaLeft = GetTextFrame()->getFramePrintArea().Left();
+ if( nFramePrintAreaLeft < 0 )
+ nFrameLeft += GetTextFrame()->getFramePrintArea().Left();
+ if( aInter.Left() < nFrameLeft )
+ {
+ aInter.Left(nFrameLeft); // both sets left and reduces width
+ if (bSplitFly && nFramePrintAreaLeft > 0 && nFramePrintAreaLeft < aInter.Width())
+ {
+ // We wrap around a split fly, the fly portion is on the
+ // left of the paragraph and we have a positive
+ // paragraph margin. Don't take space twice in this case
+ // (margin, fly portion), decrease the width of the fly
+ // portion accordingly.
+ aInter.Right(aInter.Right() - nFramePrintAreaLeft);
+ }
+ }
+
+ tools::Long nAddMar = 0;
+ if (GetTextFrame()->IsRightToLeft())
+ {
+ nAddMar = GetTextFrame()->getFrameArea().Right() - Right();
+ if ( nAddMar < 0 )
+ nAddMar = 0;
+ }
+ else
+ nAddMar = nLeftMar - nFrameLeft;
+
+ aInter.Width( aInter.Width() + nAddMar );
+ // For a negative first line indent, we set this flag to show
+ // that the indentation/margin has been moved.
+ // This needs to be respected by the DefaultTab at the zero position.
+ if( IsFirstTextLine() && HasNegFirst() )
+ bForced = true;
+ }
+ aInter.Intersection( aLine );
+ if( !aInter.HasArea() )
+ return;
+
+ bool bFullLine = aLine.Left() == aInter.Left() &&
+ aLine.Right() == aInter.Right();
+ if (!bFullLine && bWordFlyWrap && !GetTextFrame()->IsInTab())
+ {
+ // Word style: if there is minimal space remaining, then handle that similar to a full line
+ // and put the actual empty paragraph below the fly.
+ SwTwips nLimit = MINLAY;
+ if (bSplitFly)
+ {
+ // We wrap around a floating table, that has a larger minimal wrap distance.
+ nLimit = TEXT_MIN_SMALL;
+ }
+
+ bFullLine = std::abs(aLine.Left() - aInter.Left()) < nLimit
+ && std::abs(aLine.Right() - aInter.Right()) < nLimit;
+ }
+
+ // Although no text is left, we need to format another line,
+ // because also empty lines need to avoid a Fly with no wrapping.
+ if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
+ {
+ rInf.SetNewLine( true );
+ // We know that for dummies, it holds ascent == height
+ m_pCurr->SetDummy(true);
+ }
+
+ // aInter becomes frame-local
+ aInter.Pos().AdjustX( -nLeftMar );
+ SwFlyPortion *pFly = new SwFlyPortion( aInter );
+ if( bForced )
+ {
+ m_pCurr->SetForcedLeftMargin();
+ rInf.ForcedLeftMargin( o3tl::narrowing<sal_uInt16>(aInter.Width()) );
+ }
+
+ if( bFullLine )
+ {
+ // In order to properly flow around Flys with different
+ // wrapping attributes, we need to increase by units of line height.
+ // The last avoiding line should be adjusted in height, so that
+ // we don't get a frame spacing effect.
+ // It is important that ascent == height, because the FlyPortion
+ // values are transferred to pCurr in CalcLine and IsDummy() relies
+ // on this behaviour.
+ // To my knowledge we only have two places where DummyLines can be
+ // created: here and in MakeFlyDummies.
+ // IsDummy() is evaluated in IsFirstTextLine(), when moving lines
+ // and in relation with DropCaps.
+ pFly->Height( aInter.Height() );
+
+ // nNextTop now contains the margin's bottom edge, which we avoid
+ // or the next margin's top edge, which we need to respect.
+ // That means we can comfortably grow up to this value; that's how
+ // we save a few empty lines.
+ tools::Long nNextTop = rTextFly.GetNextTop();
+ if ( m_pFrame->IsVertical() )
+ nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop );
+ if( nNextTop > aInter.Bottom() )
+ {
+ SwTwips nH = nNextTop - aInter.Top();
+ if( nH < SAL_MAX_UINT16 )
+ pFly->Height( nH );
+ }
+ if( nAscent < pFly->Height() )
+ pFly->SetAscent( nAscent );
+ else
+ pFly->SetAscent( pFly->Height() );
+ }
+ else
+ {
+ if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
+ {
+ // Don't use nHeight, or we have a huge descent
+ pFly->Height( pLast->Height() );
+ pFly->SetAscent( pLast->GetAscent() );
+ }
+ else
+ {
+ pFly->Height( aInter.Height() );
+ if( nAscent < pFly->Height() )
+ pFly->SetAscent( nAscent );
+ else
+ pFly->SetAscent( pFly->Height() );
+ }
+ }
+
+ rInf.SetFly( pFly );
+
+ if( pFly->GetFix() < rInf.Width() )
+ rInf.Width( pFly->GetFix() );
+
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ if ( !pGrid )
+ return;
+
+ const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
+ const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
+
+ SwRectFnSet aRectFnSet(pPageFrame);
+
+ const tools::Long nGridOrigin = pBody ?
+ aRectFnSet.GetPrtLeft(*pBody) :
+ aRectFnSet.GetPrtLeft(*pPageFrame);
+
+ const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
+ const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc);
+
+ SwTwips nStartX = GetLeftMargin();
+ if ( aRectFnSet.IsVert() )
+ {
+ Point aPoint( nStartX, 0 );
+ m_pFrame->SwitchHorizontalToVertical( aPoint );
+ nStartX = aPoint.Y();
+ }
+
+ const SwTwips nOfst = nStartX - nGridOrigin;
+ const SwTwips nTmpWidth = rInf.Width() + nOfst;
+
+ const SwTwips i = nTmpWidth / nGridWidth + 1;
+
+ const SwTwips nNewWidth = ( i - 1 ) * nGridWidth - nOfst;
+ if ( nNewWidth > 0 )
+ rInf.Width( nNewWidth );
+ else
+ rInf.Width( 0 );
+
+
+}
+
+SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf,
+ SwTextAttr *pHint ) const
+{
+ const SwFrame *pFrame = m_pFrame;
+
+ SwFlyInContentFrame *pFly;
+ SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
+ if( RES_FLYFRMFMT == pFrameFormat->Which() )
+ {
+ // set Lock pFrame to avoid m_pCurr getting deleted
+ TextFrameLockGuard aGuard(m_pFrame);
+ pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame);
+ }
+ else
+ pFly = nullptr;
+ // aBase is the document-global position, from which the new extra portion is placed
+ // aBase.X() = Offset in the line after the current position
+ // aBase.Y() = LineIter.Y() + Ascent of the current position
+
+ SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
+ // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)>
+ // to change line spacing behaviour at paragraph - Compatibility to MS Word
+ //SwLinePortion *pPos = pCurr->GetFirstPortion();
+ //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
+ m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
+
+ // If the ascent of the frame is larger than the ascent of the current position,
+ // we use this one when calculating the base, or the frame would be positioned
+ // too much to the top, sliding down after all causing a repaint in an area
+ // he actually never was in.
+ SwTwips nAscent = 0;
+
+ const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical();
+
+ const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() &&
+ 0 != ( bTextFrameVertical ?
+ pFly->GetRefPoint().X() :
+ pFly->GetRefPoint().Y() );
+
+ if ( bUseFlyAscent )
+ nAscent = std::abs( int( bTextFrameVertical ?
+ pFly->GetRelPos().X() :
+ pFly->GetRelPos().Y() ) );
+
+ // Check if be prefer to use the ascent of the last portion:
+ if ( IsQuick() ||
+ !bUseFlyAscent ||
+ nAscent < rInf.GetLast()->GetAscent() )
+ {
+ nAscent = rInf.GetLast()->GetAscent();
+ }
+ else if( nAscent > nFlyAsc )
+ nFlyAsc = nAscent;
+
+ Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent );
+ AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None;
+ if( GetMulti() && GetMulti()->HasRotation() )
+ {
+ nMode |= AsCharFlags::Rotate;
+ if( GetMulti()->IsRevers() )
+ nMode |= AsCharFlags::Reverse;
+ }
+
+ Point aTmpBase( aBase );
+ if ( GetInfo().GetTextFrame()->IsVertical() )
+ GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
+
+ SwFlyCntPortion* pRet(nullptr);
+ if( pFly )
+ {
+ pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
+ // We need to make sure that our font is set again in the OutputDevice
+ // It could be that the FlyInCnt was added anew and GetFlyFrame() would
+ // in turn cause, that it'd be created anew again.
+ // This one's frames get formatted right away, which change the font.
+ rInf.SelectFont();
+ if( pRet->GetAscent() > nAscent )
+ {
+ aBase.setY( Y() + pRet->GetAscent() );
+ nMode |= AsCharFlags::UlSpace;
+ if( !rInf.IsTest() )
+ {
+ aTmpBase = aBase;
+ if ( GetInfo().GetTextFrame()->IsVertical() )
+ GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
+
+ pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent,
+ nTmpDescent, nFlyAsc, nFlyDesc, nMode );
+ }
+ }
+ }
+ else
+ {
+ pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
+ }
+ return pRet;
+}
+
+/* Drop portion is a special case, because it has parts which aren't portions
+ but we have handle them just like portions */
+void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion )
+{
+ if( rPortion.GetLines() <= 1 )
+ return;
+
+ SwDropPortionPart* pCurrPart = rPortion.GetPart();
+ while( pCurrPart )
+ {
+ if( pCurrPart->GetFollow() &&
+ ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) )
+ {
+ pCurrPart->SetJoinBorderWithNext(true);
+ pCurrPart->GetFollow()->SetJoinBorderWithPrev(true);
+ }
+ pCurrPart = pCurrPart->GetFollow();
+ }
+}
+
+void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf )
+{
+ const SwFont aCurFont = *rInf.GetFont();
+ if( !aCurFont.HasBorder() )
+ return;
+
+ if (pPrev && pPrev->GetJoinBorderWithNext() )
+ {
+ // In some case border merge is called twice to the portion
+ if( !rPortion.GetJoinBorderWithPrev() )
+ {
+ rPortion.SetJoinBorderWithPrev(true);
+ if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() )
+ rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace());
+ }
+ }
+ else
+ {
+ rPortion.SetJoinBorderWithPrev(false);
+ m_pFirstOfBorderMerge = &rPortion;
+ }
+
+ // Get next portion's font
+ bool bSeek = false;
+ if (!rInf.IsFull() && // Not the last portion of the line (in case of line break)
+ rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph
+ {
+ bSeek = Seek(rInf.GetIdx() + rPortion.GetLen());
+ }
+ // Don't join the next portion if SwKernPortion sits between two different boxes.
+ bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev();
+ // If next portion has the same border then merge
+ if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect )
+ {
+ // In some case border merge is called twice to the portion
+ if( !rPortion.GetJoinBorderWithNext() )
+ {
+ rPortion.SetJoinBorderWithNext(true);
+ if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() )
+ rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace());
+ }
+ }
+ // If this is the last portion of the merge group then make the real height merge
+ else
+ {
+ rPortion.SetJoinBorderWithNext(false);
+ if( m_pFirstOfBorderMerge != &rPortion )
+ {
+ // Calculate maximum height and ascent
+ SwLinePortion* pActPor = m_pFirstOfBorderMerge;
+ sal_uInt16 nMaxAscent = 0;
+ sal_uInt16 nMaxHeight = 0;
+ bool bReachCurrent = false;
+ while( pActPor )
+ {
+ if( nMaxHeight < pActPor->Height() )
+ nMaxHeight = pActPor->Height();
+ if( nMaxAscent < pActPor->GetAscent() )
+ nMaxAscent = pActPor->GetAscent();
+
+ pActPor = pActPor->GetNextPortion();
+ if( !pActPor && !bReachCurrent )
+ {
+ pActPor = &rPortion;
+ bReachCurrent = true;
+ }
+ }
+
+ // Change all portion's height and ascent
+ pActPor = m_pFirstOfBorderMerge;
+ bReachCurrent = false;
+ while( pActPor )
+ {
+ if( nMaxHeight > pActPor->Height() )
+ pActPor->Height(nMaxHeight);
+ if( nMaxAscent > pActPor->GetAscent() )
+ pActPor->SetAscent(nMaxAscent);
+
+ pActPor = pActPor->GetNextPortion();
+ if( !pActPor && !bReachCurrent )
+ {
+ pActPor = &rPortion;
+ bReachCurrent = true;
+ }
+ }
+ m_pFirstOfBorderMerge = nullptr;
+ }
+ }
+ Seek(rInf.GetIdx());
+}
+
+namespace {
+ // calculates and sets optimal repaint offset for the current line
+ tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
+ SwLineLayout const &rCurr,
+ TextFrameIndex const nOldLineEnd,
+ const std::vector<tools::Long> &rFlyStarts )
+ {
+ SwTextFormatInfo& txtFormatInfo = rThis.GetInfo();
+ if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() )
+ // the reformat position is behind our new line, that means
+ // something of our text has moved to the next line
+ return 0;
+
+ TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd);
+
+ // in case we do not have any fly in our line, our repaint position
+ // is the changed position - 1
+ if ( rFlyStarts.empty() && ! rCurr.IsFly() )
+ {
+ // this is the maximum repaint offset determined during formatting
+ // for example: the beginning of the first right tab stop
+ // if this value is 0, this means that we do not have an upper
+ // limit for the repaint offset
+ const tools::Long nFormatRepaint = txtFormatInfo.GetPaintOfst();
+
+ if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3))
+ return 0;
+
+ // step back two positions for smoother repaint
+ nReformat -= TextFrameIndex(2);
+
+ // i#28795, i#34607, i#38388
+ // step back more characters, this is required by complex scripts
+ // e.g., for Khmer (thank you, Javier!)
+ static const TextFrameIndex nMaxContext(10);
+ if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext)
+ nReformat = nReformat - nMaxContext;
+ else
+ {
+ nReformat = txtFormatInfo.GetLineStart();
+ //reset the margin flag - prevent loops
+ SwTextCursor::SetRightMargin(false);
+ }
+
+ // Weird situation: Our line used to end with a hole portion
+ // and we delete some characters at the end of our line. We have
+ // to take care for repainting the blanks which are not anymore
+ // covered by the hole portion
+ while ( nReformat > txtFormatInfo.GetLineStart() &&
+ CH_BLANK == txtFormatInfo.GetChar( nReformat ) )
+ --nReformat;
+
+ OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" );
+ SwRect aRect;
+
+ // Note: GetChareRect is not const. It definitely changes the
+ // bMulti flag. We have to save and restore the old value.
+ bool bOldMulti = txtFormatInfo.IsMulti();
+ rThis.GetCharRect( &aRect, nReformat );
+ txtFormatInfo.SetMulti( bOldMulti );
+
+ return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) :
+ aRect.Left();
+ }
+ else
+ {
+ // nReformat may be wrong, if something around flys has changed:
+ // we compare the former and the new fly positions in this line
+ // if anything has changed, we carefully have to adjust the right
+ // repaint position
+ tools::Long nPOfst = 0;
+ size_t nCnt = 0;
+ tools::Long nX = 0;
+ TextFrameIndex nIdx = rThis.GetInfo().GetLineStart();
+ SwLinePortion* pPor = rCurr.GetFirstPortion();
+
+ while ( pPor )
+ {
+ if ( pPor->IsFlyPortion() )
+ {
+ // compare start of fly with former start of fly
+ if (nCnt < rFlyStarts.size() &&
+ nX == rFlyStarts[ nCnt ] &&
+ nIdx < nReformat
+ )
+ // found fix position, nothing has changed left from nX
+ nPOfst = nX + pPor->Width();
+ else
+ break;
+
+ nCnt++;
+ }
+ nX = nX + pPor->Width();
+ nIdx = nIdx + pPor->GetLen();
+ pPor = pPor->GetNextPortion();
+ }
+
+ return nPOfst + rThis.GetLeftMargin();
+ }
+ }
+
+ // Determine if we need to build hidden portions
+ bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos)
+ {
+ // Only if hidden text should not be shown:
+ // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() )
+ const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar();
+ const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting();
+ if (bShowInDocView || bShowForPrinting)
+ return false;
+
+ const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
+ TextFrameIndex nHiddenStart;
+ TextFrameIndex nHiddenEnd;
+ rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd );
+ if ( nHiddenEnd )
+ {
+ rPos = nHiddenEnd;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond)
+ {
+ return
+ rFirst.GetTopBorder() == rSecond.GetTopBorder() &&
+ rFirst.GetBottomBorder() == rSecond.GetBottomBorder() &&
+ rFirst.GetLeftBorder() == rSecond.GetLeftBorder() &&
+ rFirst.GetRightBorder() == rSecond.GetRightBorder() &&
+ rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() &&
+ rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() &&
+ rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() &&
+ rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() &&
+ rFirst.GetOrientation() == rSecond.GetOrientation() &&
+ rFirst.GetShadowColor() == rSecond.GetShadowColor() &&
+ rFirst.GetShadowWidth() == rSecond.GetShadowWidth() &&
+ rFirst.GetShadowLocation() == rSecond.GetShadowLocation();
+ }
+
+} //end unnamed namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrform2.hxx b/sw/source/core/text/itrform2.hxx
new file mode 100644
index 0000000000..2d71fad34c
--- /dev/null
+++ b/sw/source/core/text/itrform2.hxx
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "itrpaint.hxx"
+
+class SwFlyCntPortion;
+class SwDropPortion;
+class SwFormatDrop;
+class SwTextAttr;
+class SwNumberPortion;
+class SwErgoSumPortion;
+class SwExpandPortion;
+class SwMultiPortion;
+class SwFootnotePortion;
+
+class SwTextFormatter : public SwTextPainter
+{
+ const SwFormatDrop *m_pDropFormat;
+ SwMultiPortion* m_pMulti; // during formatting a multi-portion
+ sal_uInt8 m_nContentEndHyph; // Counts consecutive hyphens at the line end
+ sal_uInt8 m_nContentMidHyph; // Counts consecutive hyphens before flies
+ TextFrameIndex m_nLeftScanIdx; // for increasing performance during
+ TextFrameIndex m_nRightScanIdx; // scanning for portion ends
+ bool m_bOnceMore : 1; // Another round?
+ bool m_bFlyInContentBase : 1; // Base reference that sets a character-bound frame
+ bool m_bTruncLines : 1; // Flag for extending the repaint rect, if needed
+ bool m_bUnclipped : 1; // Flag whether repaint is larger than the fixed line height
+ std::unique_ptr<sw::MergedAttrIterByEnd> m_pByEndIter; // HACK for TryNewNoLengthPortion
+ SwLinePortion* m_pFirstOfBorderMerge; // The first text portion of a joined border (during portion building)
+
+ SwLinePortion *NewPortion(SwTextFormatInfo &rInf, ::std::optional<TextFrameIndex>);
+ SwTextPortion *NewTextPortion( SwTextFormatInfo &rInf );
+ SwLinePortion *NewExtraPortion( SwTextFormatInfo &rInf );
+ SwTabPortion *NewTabPortion( SwTextFormatInfo &rInf, bool bAuto ) const;
+ SwNumberPortion *NewNumberPortion( SwTextFormatInfo &rInf ) const;
+ SwDropPortion *NewDropPortion( SwTextFormatInfo &rInf );
+ SwNumberPortion *NewFootnoteNumPortion( SwTextFormatInfo const &rInf ) const;
+ SwErgoSumPortion *NewErgoSumPortion( SwTextFormatInfo const &rInf ) const;
+ SwExpandPortion *NewFieldPortion( SwTextFormatInfo &rInf,
+ const SwTextAttr *pHt ) const;
+ SwFootnotePortion *NewFootnotePortion( SwTextFormatInfo &rInf, SwTextAttr *pHt );
+
+ /**
+ Sets a new portion for an object anchored as character
+ */
+ SwFlyCntPortion *NewFlyCntPortion( SwTextFormatInfo &rInf,
+ SwTextAttr *pHt ) const;
+ SwLinePortion *WhichFirstPortion( SwTextFormatInfo &rInf );
+ SwTextPortion *WhichTextPor( SwTextFormatInfo &rInf ) const;
+ SwExpandPortion * TryNewNoLengthPortion( SwTextFormatInfo const & rInfo );
+
+ // The center piece of formatting
+ void BuildPortions( SwTextFormatInfo &rInf );
+
+ bool BuildMultiPortion( SwTextFormatInfo &rInf, SwMultiPortion& rMulti );
+
+ /**
+ Calculation of the emulated right side.
+
+ Determines the next object, that reaches into the rest of the line and
+ constructs the appropriate FlyPortion.
+ SwTextFly::GetFrame(const SwRect&, bool) will be needed for this.
+
+ The right edge can be shortened by flys
+ */
+ void CalcFlyWidth( SwTextFormatInfo &rInf );
+
+ // Is overloaded by SwTextFormatter because of UpdatePos
+ void CalcAdjustLine( SwLineLayout *pCurr );
+
+ // considers line spacing attributes
+ void CalcRealHeight( bool bNewLine = false );
+
+ // Transfers the data to rInf
+ void FeedInf( SwTextFormatInfo &rInf ) const;
+
+ // Treats underflow situations
+ SwLinePortion *Underflow( SwTextFormatInfo &rInf );
+
+ // Calculates the ascent and the height from the fontmetric
+ void CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor );
+
+ // determines, if an optimized repaint rectangle is allowed
+ bool AllowRepaintOpt() const;
+
+ // Is called by FormatLine
+ void FormatReset( SwTextFormatInfo &rInf );
+
+ /**
+ The position of the portions changes with the adjustment.
+
+ This method updates the reference point of the anchored as character objects,
+ for example after adjustment change (right alignment, justified, etc.)
+ Mainly to correct the X position.
+ */
+ void UpdatePos(SwLineLayout *pCurr, Point aStart, TextFrameIndex nStartIdx,
+ bool bAlways = false ) const;
+
+ /**
+ Set all anchored as character objects to the passed BaseLine
+ (in Y direction).
+ */
+ void AlignFlyInCntBase( tools::Long nBaseLine ) const;
+
+ /**
+ This is called after the real height of the line has been calculated
+ Therefore it is possible, that more flys from below intersect with the
+ line, or that flys from above do not intersect with the line anymore.
+ We check this and return true, meaning that the line has to be
+ formatted again.
+ */
+ bool ChkFlyUnderflow( SwTextFormatInfo &rInf ) const;
+
+ // Insert portion
+ void InsertPortion( SwTextFormatInfo &rInf, SwLinePortion *pPor );
+
+ // Guess height for the DropPortion
+ void GuessDropHeight( const sal_uInt16 nLines );
+
+public:
+ // Calculate the height for the DropPortion
+ void CalcDropHeight( const sal_uInt16 nLines );
+
+ // Calculates the paragraphs bottom, takes anchored objects within it into
+ // account which have a wrap setting of "wrap at 1st paragraph"
+ SwTwips CalcBottomLine() const;
+
+ // Takes character-bound objects into account when calculating the
+ // repaint rect in lines with fixed line height
+ void CalcUnclipped( SwTwips& rTop, SwTwips& rBottom );
+
+ // Amongst others for DropCaps
+ bool CalcOnceMore();
+
+ void CtorInitTextFormatter( SwTextFrame *pFrame, SwTextFormatInfo *pInf );
+ SwTextFormatter(SwTextFrame *pTextFrame, SwTextFormatInfo *pTextFormatInf)
+ : SwTextPainter(pTextFrame->GetTextNodeFirst())
+ , m_bUnclipped(false)
+ {
+ CtorInitTextFormatter( pTextFrame, pTextFormatInf );
+ }
+ virtual ~SwTextFormatter() override;
+
+ TextFrameIndex FormatLine(TextFrameIndex nStart);
+
+ void RecalcRealHeight();
+
+ // We format a line for interactive hyphenation
+ bool Hyphenate(SwInterHyphInfoTextFrame & rInf);
+
+ // A special method for QuoVadis texts:
+ // nErgo is the page number of the ErgoSum Footnote
+ // At 0 it's still unclear
+ TextFrameIndex FormatQuoVadis(TextFrameIndex nStart);
+
+ // The emergency break: Cancel formatting, discard line
+ bool IsStop() const { return GetInfo().IsStop(); }
+
+ // The counterpart: Continue formatting at all costs
+ bool IsNewLine() const { return GetInfo().IsNewLine(); }
+
+ // FormatQuick(); Refresh formatting information
+ bool IsQuick() const { return GetInfo().IsQuick(); }
+
+ // Create a SwLineLayout if needed, which avoids Footnote/Fly to oscillate
+ void MakeDummyLine();
+
+ // SwTextIter functionality
+ void Insert( SwLineLayout *pLine );
+
+ // The remaining height to the page border
+ sal_uInt16 GetFrameRstHeight() const;
+
+ // How wide would you be without any bounds (Flys etc.)?
+ SwTwips CalcFitToContent_( );
+
+ SwLinePortion* MakeRestPortion(const SwLineLayout* pLine, TextFrameIndex nPos);
+
+ const SwFormatDrop *GetDropFormat() const { return m_pDropFormat; }
+ void ClearDropFormat() { m_pDropFormat = nullptr; }
+
+ SwMultiPortion *GetMulti() const { return m_pMulti; }
+
+ bool IsOnceMore() const { return m_bOnceMore; }
+ void SetOnceMore( bool bNew ) { m_bOnceMore = bNew; }
+
+ bool HasTruncLines() const { return m_bTruncLines; }
+ void SetTruncLines( bool bNew ) { m_bTruncLines = bNew; }
+
+ bool IsUnclipped() const { return m_bUnclipped; }
+ void SetUnclipped( bool bNew ) { m_bUnclipped = bNew; }
+
+ bool IsFlyInCntBase() const { return m_bFlyInContentBase; }
+ void SetFlyInCntBase( bool bNew = true ) { m_bFlyInContentBase = bNew; }
+
+ SwTextFormatInfo &GetInfo()
+ { return static_cast<SwTextFormatInfo&>(SwTextIter::GetInfo()); }
+ const SwTextFormatInfo &GetInfo() const
+ { return static_cast<const SwTextFormatInfo&>(SwTextIter::GetInfo()); }
+
+ void InitCntHyph() { CntHyphens( m_nContentEndHyph, m_nContentMidHyph ); }
+ const sal_uInt8 &CntEndHyph() const { return m_nContentEndHyph; }
+ const sal_uInt8 &CntMidHyph() const { return m_nContentMidHyph; }
+ sal_uInt8 &CntEndHyph() { return m_nContentEndHyph; }
+ sal_uInt8 &CntMidHyph() { return m_nContentMidHyph; }
+
+ /**
+ * Merge border of the drop portion with modifying the font of
+ * the portions' part. Removing left or right border.
+ * @param rPortion drop portion for merge
+ **/
+ static void MergeCharacterBorder( SwDropPortion const & rPortion );
+
+ /**
+ * Merge border of the line portion with setting the portion's
+ * m_bJoinBorderWidthNext and m_bJoinBorderWidthPrev members and
+ * changing the size (width, height and ascent) of the portion
+ * to get a merged border.
+ * @param rPortion portion for merge
+ * @param pPrev portion immediately before rPortion
+ * @param rInf contain information
+ **/
+ void MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf );
+
+ bool ClearIfIsFirstOfBorderMerge(SwLinePortion const *pPortion);
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx
new file mode 100644
index 0000000000..5b6bb1288d
--- /dev/null
+++ b/sw/source/core/text/itrpaint.cxx
@@ -0,0 +1,807 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <viewopt.hxx>
+#include <tools/multisel.hxx>
+#include <editeng/udlnitem.hxx>
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+
+#include <EnhancedPDFExportHelper.hxx>
+#include <IDocumentSettingAccess.hxx>
+
+#include <viewsh.hxx>
+#include "itrpaint.hxx"
+#include <txtfrm.hxx>
+#include <swfont.hxx>
+#include "txtpaint.hxx"
+#include "porfld.hxx"
+#include "portab.hxx"
+#include <txatbase.hxx>
+#include <charfmt.hxx>
+#include "redlnitr.hxx"
+#include "porrst.hxx"
+#include "pormulti.hxx"
+#include <doc.hxx>
+
+// Returns, if we have an underline breaking situation
+// Adding some more conditions here means you also have to change them
+// in SwTextPainter::CheckSpecialUnderline
+bool IsUnderlineBreak( const SwLinePortion& rPor, const SwFont& rFnt )
+{
+ return LINESTYLE_NONE == rFnt.GetUnderline() ||
+ rPor.IsFlyPortion() || rPor.IsFlyCntPortion() ||
+ rPor.IsBreakPortion() || rPor.IsMarginPortion() ||
+ rPor.IsHolePortion() ||
+ ( rPor.IsMultiPortion() && ! static_cast<const SwMultiPortion&>(rPor).IsBidi() ) ||
+ rFnt.GetEscapement() < 0 || rFnt.IsWordLineMode() ||
+ SvxCaseMap::SmallCaps == rFnt.GetCaseMap();
+}
+
+static Color GetUnderColor( const SwFont *pFont )
+{
+ return pFont->GetUnderColor() == COL_AUTO ?
+ pFont->GetColor() : pFont->GetUnderColor();
+}
+
+void SwTextPainter::CtorInitTextPainter( SwTextFrame *pNewFrame, SwTextPaintInfo *pNewInf )
+{
+ CtorInitTextCursor( pNewFrame, pNewInf );
+ m_pInf = pNewInf;
+ SwFont *pMyFnt = GetFnt();
+ GetInfo().SetFont( pMyFnt );
+ m_bPaintDrop = false;
+}
+
+SwLinePortion *SwTextPainter::CalcPaintOfst(const SwRect &rPaint, bool& rbSkippedNumPortions)
+{
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ GetInfo().SetPaintOfst( 0 );
+ SwTwips nPaintOfst = rPaint.Left();
+
+ // nPaintOfst was exactly set to the end, therefore <=
+ // nPaintOfst is document global, therefore add up nLeftMar
+ // const sal_uInt16 nLeftMar = sal_uInt16(GetLeftMargin());
+ // 8310: paint of LineBreaks in empty lines.
+ if( nPaintOfst && m_pCurr->Width() )
+ {
+ SwLinePortion *pLast = nullptr;
+ // 7529 and 4757: not <= nPaintOfst
+ while( pPor && GetInfo().X() + pPor->Width() + (pPor->Height()/2)
+ < nPaintOfst )
+ {
+ if( pPor->InSpaceGrp() && GetInfo().GetSpaceAdd() )
+ {
+ tools::Long nTmp = GetInfo().X() +pPor->Width() +
+ pPor->CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
+ if( nTmp + (pPor->Height()/2) >= nPaintOfst )
+ break;
+ GetInfo().X( nTmp );
+ GetInfo().SetIdx( GetInfo().GetIdx() + pPor->GetLen() );
+ }
+ else
+ pPor->Move( GetInfo() );
+ if (pPor->InNumberGrp()
+ && !static_cast<SwNumberPortion const*>(pPor)->HasFollow())
+ {
+ rbSkippedNumPortions = true; // all numbering portions were skipped?
+ }
+ pLast = pPor;
+ pPor = pPor->GetNextPortion();
+ }
+
+ // 7529: if PostIts return also pLast.
+ if( pLast && !pLast->Width() && pLast->IsPostItsPortion() )
+ {
+ pPor = pLast;
+ GetInfo().SetIdx( GetInfo().GetIdx() - pPor->GetLen() );
+ }
+ }
+ return pPor;
+}
+
+// There are two possibilities to output transparent font:
+// 1) DrawRect on the whole line and DrawText afterwards
+// (objectively fast, subjectively slow)
+// 2) For every portion a DrawRect with subsequent DrawText is done
+// (objectively slow, subjectively fast)
+// Since the user usually judges subjectively the second method is set as default.
+void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip,
+ const bool bUnderSized,
+ ::std::optional<SwTaggedPDFHelper> & roTaggedLabel,
+ ::std::optional<SwTaggedPDFHelper> & roTaggedParagraph,
+ bool const isPDFTaggingEnabled)
+{
+#if OSL_DEBUG_LEVEL > 1
+// sal_uInt16 nFntHeight = GetInfo().GetFont()->GetHeight( GetInfo().GetVsh(), GetInfo().GetOut() );
+// sal_uInt16 nFntAscent = GetInfo().GetFont()->GetAscent( GetInfo().GetVsh(), GetInfo().GetOut() );
+#endif
+
+ // maybe catch-up adjustment
+ GetAdjusted();
+ AddExtraBlankWidth();
+ GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
+ GetInfo().ResetSpaceIdx();
+ GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
+ GetInfo().ResetKanaIdx();
+ // The size of the frame
+ GetInfo().SetIdx( GetStart() );
+ GetInfo().SetPos( GetTopLeft() );
+
+ const bool bDrawInWindow = GetInfo().OnWin();
+
+ // 6882: blank lines can't be optimized by removing them if Formatting Marks are shown
+ const bool bEndPor = GetInfo().GetOpt().IsParagraph() && GetInfo().GetText().isEmpty();
+
+ bool bSkippedNumPortions(false);
+ SwLinePortion *pPor = bEndPor ? m_pCurr->GetFirstPortion() : CalcPaintOfst(rPaint, bSkippedNumPortions);
+
+ if (bSkippedNumPortions // ugly but hard to check earlier in PaintSwFrame:
+ && !GetInfo().GetTextFrame()->GetTextNodeForParaProps()->IsOutline())
+ { // there is a num portion but it is outside of the frame area and not painted
+ assert(!roTaggedLabel);
+ assert(!roTaggedParagraph);
+ Frame_Info aFrameInfo(*m_pFrame, false); // open LBody
+ roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *GetInfo().GetOut());
+ }
+
+ SwTaggedPDFHelper::EndCurrentLink(*GetInfo().GetOut());
+
+ // Optimization!
+ SwTwips nMaxRight = std::min<SwTwips>( rPaint.Right(), Right() );
+ const SwTwips nTmpLeft = GetInfo().X();
+ //compatibility settings: allow tabstop text to exceed right margin
+ const auto& iDSA = GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess();
+ const bool bTabOverMargin = iDSA.get(DocumentSettingId::TAB_OVER_MARGIN);
+ const bool bTabOverSpacing = iDSA.get(DocumentSettingId::TAB_OVER_SPACING);
+ if (bTabOverMargin || bTabOverSpacing)
+ {
+ SwLinePortion* pPorIter = pPor;
+ while( pPorIter )
+ {
+ if( pPorIter->InTabGrp() )
+ {
+ const SwTabPortion* pTabPor = static_cast<SwTabPortion*>(pPorIter);
+ const SwTwips nTabPos = nTmpLeft + pTabPor->GetTabPos();
+ if( nMaxRight < nTabPos )
+ {
+ nMaxRight = rPaint.Right();
+ break;
+ }
+ }
+ pPorIter = pPorIter->GetNextPortion();
+ }
+ }
+ if( !bEndPor && nTmpLeft >= nMaxRight )
+ return;
+
+ // DropCaps!
+ // 7538: of course for the printer, too
+ if( !m_bPaintDrop )
+ {
+ // 8084: Optimization, less painting
+ // AMA: By 8084 7538 has been revived
+ // bDrawInWindow removed, so that DropCaps also can be printed
+ m_bPaintDrop = pPor == m_pCurr->GetFirstPortion()
+ && GetDropLines() >= GetLineNr();
+ }
+
+ SwTwips nTmpHeight, nTmpAscent;
+ CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+
+ // bClip decides if there's a need to clip
+ // The whole thing must be done before retouching
+
+ bool bClip = ( bDrawInWindow || bUnderSized ) && !rClip.IsChg();
+ if( bClip && pPor )
+ {
+ // If TopLeft or BottomLeft of the line are outside, the we must clip.
+ // The check for Right() is done in the output loop ...
+
+ if( GetInfo().GetPos().X() < rPaint.Left() ||
+ GetInfo().GetPos().Y() < rPaint.Top() ||
+ GetInfo().GetPos().Y() + nTmpHeight > rPaint.Top() + rPaint.Height() )
+ {
+ bClip = false;
+ rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ static bool bClipAlways = false;
+ if( bClip && bClipAlways )
+ { bClip = false;
+ rClip.ChgClip( rPaint );
+ }
+#endif
+ }
+
+ // Alignment
+ OutputDevice* pOut = GetInfo().GetOut();
+ Point aPnt1( nTmpLeft, GetInfo().GetPos().Y() );
+ if ( aPnt1.X() < rPaint.Left() )
+ aPnt1.setX( rPaint.Left() );
+ if ( aPnt1.Y() < rPaint.Top() )
+ aPnt1.setY( rPaint.Top() );
+ Point aPnt2( GetInfo().GetPos().X() + nMaxRight - GetInfo().X(),
+ GetInfo().GetPos().Y() + nTmpHeight );
+ if ( aPnt2.X() > rPaint.Right() )
+ aPnt2.setX( rPaint.Right() );
+ if ( aPnt2.Y() > rPaint.Bottom() )
+ aPnt2.setY( rPaint.Bottom() );
+
+ const SwRect aLineRect( aPnt1, aPnt2 );
+
+ if( m_pCurr->IsClipping() )
+ {
+ const SwTextFrame& rFrame = *GetInfo().GetTextFrame();
+ // tdf#117448 at small fixed line height, enlarge clipping area in table cells
+ // to show previously clipped text content on the area of paragraph margins
+ if ( rFrame.IsInTab() )
+ rClip.ChgClip( aLineRect, m_pFrame, false, rFrame.GetTopMargin(), rFrame.GetBottomMargin() );
+ else
+ rClip.ChgClip( aLineRect, m_pFrame );
+ bClip = false;
+ }
+
+ if( !pPor && !bEndPor )
+ return;
+
+ // Baseline output also if non-TextPortion (compare TabPor with Fill)
+ // if no special vertical alignment is used,
+ // we calculate Y value for the whole line
+ SwTextGridItem const*const pGrid(GetGridItem(GetTextFrame()->FindPageFrame()));
+ const bool bAdjustBaseLine =
+ GetLineInfo().HasSpecialAlign( GetTextFrame()->IsVertical() ) ||
+ ( nullptr != pGrid ) || m_pCurr->GetHangingBaseline();
+ const SwTwips nLineBaseLine = GetInfo().GetPos().Y() + nTmpAscent;
+ if ( ! bAdjustBaseLine )
+ GetInfo().Y( nLineBaseLine );
+
+ // 7529: Pre-paint post-its
+ if( GetInfo().OnWin() && pPor && !pPor->Width() )
+ {
+ SeekAndChg( GetInfo() );
+
+ if( bAdjustBaseLine )
+ {
+ const SwTwips nOldY = GetInfo().Y();
+
+ GetInfo().Y( GetInfo().GetPos().Y() + AdjustBaseLine( *m_pCurr, nullptr,
+ GetInfo().GetFont()->GetHeight( GetInfo().GetVsh(), *pOut ),
+ GetInfo().GetFont()->GetAscent( GetInfo().GetVsh(), *pOut )
+ ) );
+
+ pPor->PrePaint( GetInfo(), pPor );
+ GetInfo().Y( nOldY );
+ }
+ else
+ pPor->PrePaint( GetInfo(), pPor );
+ }
+
+ // 7923: EndPortions output chars, too, that's why we change the font
+ if( bEndPor )
+ SeekStartAndChg( GetInfo() );
+
+ const bool bRest = m_pCurr->IsRest();
+ bool bFirst = true;
+
+ SwArrowPortion *pArrow = nullptr;
+ // Reference portion for the paragraph end portion
+ SwLinePortion* pEndTempl = m_pCurr->GetFirstPortion();
+
+ while( pPor )
+ {
+ bool bSeeked = true;
+ GetInfo().SetLen( pPor->GetLen() );
+
+ const SwTwips nOldY = GetInfo().Y();
+
+ if ( bAdjustBaseLine )
+ {
+ GetInfo().Y( GetInfo().GetPos().Y() + AdjustBaseLine( *m_pCurr, pPor ) );
+
+ // we store the last portion, because a possible paragraph
+ // end character has the same font as this portion
+ // (only in special vertical alignment case, otherwise the first
+ // portion of the line is used)
+ if ( pPor->Width() && pPor->InTextGrp() )
+ pEndTempl = pPor;
+ }
+
+ // set redlining for line break symbol
+ if ( pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() && GetRedln() )
+ {
+ SeekAndChg( GetInfo() );
+ if ( m_pCurr->GetRedlineEndType() != RedlineType::None )
+ static_cast<SwBreakPortion&>(*pPor).SetRedline( m_pCurr->GetRedlineEndType() );
+ }
+
+ // A special case are GluePortions which output blanks.
+
+ // 6168: Avoid that the rest of a FieldPortion gets the attributes of the
+ // next portion with SeekAndChgBefore():
+ if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
+ SeekAndChgBefore( GetInfo() );
+ else if ( pPor->IsQuoVadisPortion() )
+ {
+ // A remark on QuoVadis/ErgoSum:
+ // We use the Font set for the Paragraph for these portions.
+ // Thus, we initialize:
+ TextFrameIndex nOffset = GetInfo().GetIdx();
+ SeekStartAndChg( GetInfo(), true );
+ if( GetRedln() && m_pCurr->HasRedline() )
+ {
+ std::pair<SwTextNode const*, sal_Int32> const pos(
+ GetTextFrame()->MapViewToModel(nOffset));
+ GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0);
+ }
+ }
+ else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
+ SeekAndChg( GetInfo() );
+ else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
+ {
+ // Paragraph symbols should have the same font as the paragraph in front of them,
+ // except for the case that there's redlining in the paragraph
+ if( GetRedln() )
+ SeekAndChg( GetInfo() );
+ else
+ SeekAndChgBefore( GetInfo() );
+ }
+ else
+ bSeeked = false;
+
+ // bRest = false;
+
+ // If the end of the portion juts out, it is clipped.
+ // A safety distance of half the height is added, so that
+ // TTF-"f" isn't overlapping into the page margin.
+ if( bClip &&
+ GetInfo().X() + pPor->Width() + ( pPor->Height() / 2 ) > nMaxRight )
+ {
+ bClip = false;
+ rClip.ChgClip( rPaint, m_pFrame, m_pCurr->HasUnderscore() );
+ }
+
+ // Portions, which lay "below" the text like post-its
+ SwLinePortion *pNext = pPor->GetNextPortion();
+ if( GetInfo().OnWin() && pNext && !pNext->Width() )
+ {
+ // Fix 11289: Fields were omitted here because of Last!=Owner during
+ // loading Brief.sdw. Now the fields are allowed again,
+ // by bSeeked Last!=Owner is being avoided.
+ if ( !bSeeked )
+ SeekAndChg( GetInfo() );
+ pNext->PrePaint( GetInfo(), pPor );
+ }
+
+ // We calculate a separate font for underlining.
+ CheckSpecialUnderline( pPor, bAdjustBaseLine ? nOldY : 0 );
+ SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
+ if ( pUnderLineFnt )
+ {
+ const Point aTmpPoint( GetInfo().X(),
+ bAdjustBaseLine ?
+ pUnderLineFnt->GetPos().Y() :
+ nLineBaseLine );
+ pUnderLineFnt->SetPos( aTmpPoint );
+ }
+
+ // in extended input mode we do not want a common underline font.
+ SwUnderlineFont* pOldUnderLineFnt = nullptr;
+ if ( GetRedln() && GetRedln()->ExtOn() )
+ {
+ pOldUnderLineFnt = GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+ }
+
+ // multiple numbering portions are possible :(
+ if ((pPor->InNumberGrp() // also footnote label
+ // weird special case, bullet with soft hyphen
+ || (pPor->InHyphGrp() && pNext && pNext->InNumberGrp()))
+ && !GetInfo().GetTextFrame()->GetTextNodeForParaProps()->IsOutline()
+ && !roTaggedLabel) // note: CalcPaintOfst may skip some portions
+ {
+ assert(isPDFTaggingEnabled);
+ Por_Info aPorInfo(*pPor, *this, 1); // open Lbl
+ roTaggedLabel.emplace(nullptr, nullptr, &aPorInfo, *pOut);
+ }
+
+ {
+ // #i16816# tagged pdf support
+ Por_Info aPorInfo(*pPor, *this, 0);
+ SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, &aPorInfo, *pOut );
+
+ if( pPor->IsMultiPortion() )
+ PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor) );
+ else
+ pPor->Paint( GetInfo() );
+ }
+
+ // lazy open LBody and paragraph tag after num portions have been painted to Lbl
+ if (pPor->InNumberGrp() // also footnote label
+ // note: numbering portion may be split if it has multiple scripts
+ && !static_cast<SwNumberPortion const*>(pPor)->HasFollow()) // so wait for the last one
+ {
+ if (!GetInfo().GetTextFrame()->GetTextNodeForParaProps()->IsOutline())
+ {
+ assert(roTaggedLabel);
+ roTaggedLabel.reset(); // close Lbl
+ assert(!roTaggedParagraph);
+ Frame_Info aFrameInfo(*m_pFrame, false); // open LBody
+ roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *pOut);
+ }
+ else
+ {
+ assert(!roTaggedLabel);
+ }
+ }
+
+ // reset underline font
+ if ( pOldUnderLineFnt )
+ GetInfo().SetUnderFnt( pOldUnderLineFnt );
+
+ // reset (for special vertical alignment)
+ GetInfo().Y( nOldY );
+
+ bFirst &= !pPor->GetLen();
+ if( pNext || !pPor->IsMarginPortion() )
+ pPor->Move( GetInfo() );
+ if( pPor->IsArrowPortion() && GetInfo().OnWin() && !pArrow )
+ pArrow = static_cast<SwArrowPortion*>(pPor);
+
+ pPor = bDrawInWindow || GetInfo().X() <= nMaxRight ||
+ // #i16816# tagged pdf support
+ ( GetInfo().GetVsh() &&
+ GetInfo().GetVsh()->GetViewOptions()->IsPDFExport() &&
+ pNext && pNext->IsHolePortion() ) ?
+ pNext :
+ nullptr;
+ if (!pPor && isPDFTaggingEnabled && (roTaggedLabel || !roTaggedParagraph))
+ { // check if the end of the list label is off-screen
+ auto FindEndOfNumbering = [&](SwLinePortion const* pP) {
+ while (pP)
+ {
+ if (pP->InNumberGrp()
+ && !static_cast<SwNumberPortion const*>(pP)->HasFollow())
+ {
+ if (roTaggedLabel)
+ {
+ roTaggedLabel.reset();
+ } // else, if the numbering isn't visible at all, no Lbl
+ if (!GetInfo().GetTextFrame()->GetTextNodeForParaProps()->IsOutline())
+ {
+ Frame_Info aFrameInfo(*m_pFrame, false); // open LBody
+ roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *GetInfo().GetOut());
+ }
+ return true;
+ }
+ pP = pP->GetNextPortion();
+ }
+ return false;
+ };
+ if (!FindEndOfNumbering(pNext)) // check rest of current line
+ {
+ // check lines that will be cut off
+ if (rPaint.Bottom() < Y() + GetLineHeight())
+ {
+ for (SwLineLayout const* pLine = GetNext(); pLine; pLine = pLine->GetNext())
+ {
+ if (FindEndOfNumbering(pLine->GetFirstPortion()))
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // delete underline font
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+
+ // paint remaining stuff
+ if( bDrawInWindow )
+ {
+ // If special vertical alignment is enabled, GetInfo().Y() is the
+ // top of the current line. Therefore is has to be adjusted for
+ // the painting of the remaining stuff. We first store the old value.
+ const SwTwips nOldY = GetInfo().Y();
+
+ if( !GetNextLine() &&
+ GetInfo().GetVsh() && !GetInfo().GetVsh()->IsPreview() &&
+ GetInfo().GetOpt().IsParagraph() && !GetTextFrame()->GetFollow() &&
+ GetInfo().GetIdx() >= TextFrameIndex(GetInfo().GetText().getLength()))
+ {
+ bool bHasRedlineEnd( GetRedln() && m_pCurr->HasRedlineEnd() );
+ RedlineType eRedlineEnd = bHasRedlineEnd ? m_pCurr->GetRedlineEndType() : RedlineType::None;
+ if( bHasRedlineEnd )
+ {
+ TextFrameIndex nOffset = GetInfo().GetIdx();
+ SeekStartAndChg( GetInfo(), true );
+ std::pair<SwTextNode const*, sal_Int32> const pos(
+ GetTextFrame()->MapViewToModel(nOffset));
+ GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0);
+ }
+ const SwTmpEndPortion aEnd( *pEndTempl,
+ bHasRedlineEnd && eRedlineEnd != RedlineType::Delete ? m_pFont->GetUnderline() : LINESTYLE_NONE,
+ bHasRedlineEnd && eRedlineEnd == RedlineType::Delete ? m_pFont->GetStrikeout() : STRIKEOUT_NONE,
+ bHasRedlineEnd ? m_pFont->GetColor() : COL_AUTO );
+ GetFnt()->ChgPhysFnt( GetInfo().GetVsh(), *pOut );
+
+ if ( bAdjustBaseLine )
+ GetInfo().Y( GetInfo().GetPos().Y()
+ + AdjustBaseLine( *m_pCurr, &aEnd ) );
+ GetInfo().X( GetInfo().X() +
+ ( GetCurr()->IsHanging() ? GetCurr()->GetHangingMargin() : 0 ) );
+ aEnd.Paint( GetInfo() );
+ GetInfo().Y( nOldY );
+ }
+ if( GetInfo().GetVsh() && !GetInfo().GetVsh()->IsPreview() )
+ {
+ const bool bNextUndersized =
+ ( GetTextFrame()->GetNext() &&
+ 0 == GetTextFrame()->GetNext()->getFramePrintArea().Height() &&
+ GetTextFrame()->GetNext()->IsTextFrame() &&
+ static_cast<SwTextFrame*>(GetTextFrame()->GetNext())->IsUndersized() ) ;
+
+ if( bUnderSized || bNextUndersized )
+ {
+ if ( bAdjustBaseLine )
+ GetInfo().Y( GetInfo().GetPos().Y() + m_pCurr->GetAscent() );
+
+ // Left arrow (text overflowing)
+ if( pArrow )
+ GetInfo().DrawRedArrow( *pArrow );
+
+ // GetInfo().Y() must be current baseline
+ SwTwips nDiff = GetInfo().Y() + nTmpHeight - nTmpAscent - GetTextFrame()->getFrameArea().Bottom();
+ if( ( nDiff > 0 &&
+ (GetEnd() < TextFrameIndex(GetInfo().GetText().getLength()) ||
+ ( nDiff > nTmpHeight/2 && GetPrevLine() ) ) ) ||
+ (nDiff >= 0 && bNextUndersized) )
+
+ {
+ // Right arrow (text overflowing)
+ SwArrowPortion aArrow( GetInfo() );
+ GetInfo().DrawRedArrow( aArrow );
+ }
+
+ GetInfo().Y( nOldY );
+ }
+ }
+ }
+
+ if( m_pCurr->IsClipping() )
+ rClip.ChgClip( rPaint, m_pFrame );
+}
+
+void SwTextPainter::CheckSpecialUnderline( const SwLinePortion* pPor,
+ tools::Long nAdjustBaseLine )
+{
+ // Check if common underline should not be continued
+ if ( IsUnderlineBreak( *pPor, *m_pFont ) )
+ {
+ // delete underline font
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+ return;
+ }
+ // Reuse calculated underline font as much as possible.
+ if (GetInfo().GetUnderFnt() &&
+ GetInfo().GetIdx() + pPor->GetLen() <= GetInfo().GetUnderFnt()->GetEnd() + TextFrameIndex(1))
+ {
+ SwFont &rFont = GetInfo().GetUnderFnt()->GetFont();
+ const Color aColor = GetUnderColor( GetInfo().GetFont() );
+ if ( GetUnderColor( &rFont ) != aColor )
+ rFont.SetColor( aColor );
+ return;
+ }
+
+ // If current underline matches the common underline font, we continue
+ // to use the common underline font.
+ // Bug 120769:Color of underline display wrongly
+ if ( GetInfo().GetUnderFnt() &&
+ GetInfo().GetUnderFnt()->GetFont().GetUnderline() == GetFnt()->GetUnderline() &&
+ GetInfo().GetFont() && GetInfo().GetFont()->GetUnderColor() != COL_AUTO )
+ return;
+ //Bug 120769(End)
+
+ OSL_ENSURE( GetFnt() && LINESTYLE_NONE != GetFnt()->GetUnderline(),
+ "CheckSpecialUnderline without underlined font" );
+ MultiSelection aUnderMulti( Range( 0, GetInfo().GetText().getLength() ) );
+ const SwFont* pParaFnt = GetAttrHandler().GetFont();
+ if( pParaFnt && pParaFnt->GetUnderline() == GetFnt()->GetUnderline() )
+ aUnderMulti.SelectAll();
+
+ if (sw::MergedPara const*const pMerged = GetTextFrame()->GetMergedPara())
+ {
+ // first, add the paragraph properties to MultiSelection - if there are
+ // Hints too, they will override the positions if they're added later
+ sal_Int32 nTmp(0);
+ for (auto const& e : pMerged->extents)
+ {
+ if (const SvxUnderlineItem* pItem = e.pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_UNDERLINE))
+ {
+ const bool bUnderSelect(m_pFont->GetUnderline() ==
+ pItem->GetLineStyle());
+ aUnderMulti.Select(Range(nTmp, nTmp + e.nEnd - e.nStart - 1),
+ bUnderSelect);
+ }
+ nTmp += e.nEnd - e.nStart;
+ }
+ }
+
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*GetTextFrame());
+ for (SwTextAttr const* pTextAttr = iter.NextAttr(&pNode); pTextAttr;
+ pTextAttr = iter.NextAttr(&pNode))
+ {
+ SvxUnderlineItem const*const pItem =
+ CharFormat::GetItem(*pTextAttr, RES_CHRATR_UNDERLINE);
+
+ if (pItem)
+ {
+ TextFrameIndex const nStart(
+ GetTextFrame()->MapModelToView(pNode, pTextAttr->GetStart()));
+ TextFrameIndex const nEnd(
+ GetTextFrame()->MapModelToView(pNode, *pTextAttr->End()));
+ if (nEnd > nStart)
+ {
+ const bool bUnderSelect = m_pFont->GetUnderline() == pItem->GetLineStyle();
+ aUnderMulti.Select(Range(sal_Int32(nStart), sal_Int32(nEnd) - 1),
+ bUnderSelect);
+ }
+ }
+ }
+
+ const TextFrameIndex nIndx = GetInfo().GetIdx();
+ TextFrameIndex nUnderEnd(0);
+ const size_t nCnt = aUnderMulti.GetRangeCount();
+
+ // find the underline range the current portion is contained in
+ for( size_t i = 0; i < nCnt; ++i )
+ {
+ const Range& rRange = aUnderMulti.GetRange( i );
+ if (nUnderEnd == TextFrameIndex(rRange.Min()))
+ nUnderEnd = TextFrameIndex(rRange.Max());
+ else if (nIndx >= TextFrameIndex(rRange.Min()))
+ {
+ nUnderEnd = TextFrameIndex(rRange.Max());
+ }
+ else
+ break;
+ }
+
+ if ( GetEnd() && GetEnd() <= nUnderEnd )
+ nUnderEnd = GetEnd() - TextFrameIndex(1);
+
+ // calculate the new common underline font
+ SwFont* pUnderlineFnt = nullptr;
+ Point aCommonBaseLine;
+
+ // check, if underlining is not isolated
+ if (nIndx + GetInfo().GetLen() < nUnderEnd + TextFrameIndex(1))
+ {
+ // here starts the algorithm for calculating the underline font
+ SwScriptInfo& rScriptInfo = GetInfo().GetParaPortion()->GetScriptInfo();
+ SwAttrIter aIter(*GetInfo().GetTextFrame()->GetTextNodeFirst(),
+ rScriptInfo, GetTextFrame());
+
+ TextFrameIndex nTmpIdx = nIndx;
+ sal_uLong nSumWidth = 0;
+ sal_uLong nSumHeight = 0;
+ sal_uLong nBold = 0;
+ sal_uInt16 nMaxBaseLineOfst = 0;
+ int nNumberOfPortions = 0;
+
+ while (nTmpIdx <= nUnderEnd && pPor)
+ {
+ if ( pPor->IsFlyPortion() || pPor->IsFlyCntPortion() ||
+ pPor->IsBreakPortion() || pPor->IsMarginPortion() ||
+ pPor->IsHolePortion() ||
+ ( pPor->IsMultiPortion() && ! static_cast<const SwMultiPortion*>(pPor)->IsBidi() ) )
+ break;
+
+ aIter.Seek( nTmpIdx );
+ if ( aIter.GetFnt()->GetEscapement() < 0 || m_pFont->IsWordLineMode() ||
+ SvxCaseMap::SmallCaps == m_pFont->GetCaseMap() )
+ break;
+
+ if ( !aIter.GetFnt()->GetEscapement() )
+ {
+ nSumWidth += pPor->Width();
+ const sal_uLong nFontHeight = aIter.GetFnt()->GetHeight();
+
+ // If we do not have a common baseline we take the baseline
+ // and the font of the lowest portion.
+ if ( nAdjustBaseLine )
+ {
+ const sal_uInt16 nTmpBaseLineOfst = AdjustBaseLine( *m_pCurr, pPor );
+ if ( nMaxBaseLineOfst < nTmpBaseLineOfst )
+ {
+ nMaxBaseLineOfst = nTmpBaseLineOfst;
+ nSumHeight = nFontHeight;
+ }
+ }
+ // in horizontal layout we build a weighted sum of the heights
+ else
+ nSumHeight += pPor->Width() * nFontHeight;
+
+ if ( WEIGHT_NORMAL != aIter.GetFnt()->GetWeight() )
+ nBold += pPor->Width();
+ }
+
+ ++nNumberOfPortions;
+
+ nTmpIdx += pPor->GetLen();
+ pPor = pPor->GetNextPortion();
+ }
+
+ // resulting height
+ if ( nNumberOfPortions > 1 && nSumWidth )
+ {
+ const sal_uLong nNewFontHeight = nAdjustBaseLine ?
+ nSumHeight :
+ nSumHeight / nSumWidth;
+
+ pUnderlineFnt = new SwFont( *GetInfo().GetFont() );
+
+ // font height
+ const SwFontScript nActual = pUnderlineFnt->GetActual();
+ pUnderlineFnt->SetSize( Size( pUnderlineFnt->GetSize( nActual ).Width(),
+ nNewFontHeight ), nActual );
+
+ // font weight
+ if ( 2 * nBold > nSumWidth )
+ pUnderlineFnt->SetWeight( WEIGHT_BOLD, nActual );
+ else
+ pUnderlineFnt->SetWeight( WEIGHT_NORMAL, nActual );
+
+ // common base line
+ aCommonBaseLine.setY( nAdjustBaseLine + nMaxBaseLineOfst );
+ }
+ }
+
+ // an escaped redlined portion should also have a special underlining
+ if( ! pUnderlineFnt && m_pFont->GetEscapement() > 0 && GetRedln() &&
+ GetRedln()->ChkSpecialUnderline() )
+ pUnderlineFnt = new SwFont( *m_pFont );
+
+ delete GetInfo().GetUnderFnt();
+
+ if ( pUnderlineFnt )
+ {
+ pUnderlineFnt->SetProportion( 100 );
+ pUnderlineFnt->SetEscapement( 0 );
+ pUnderlineFnt->SetStrikeout( STRIKEOUT_NONE );
+ pUnderlineFnt->SetOverline( LINESTYLE_NONE );
+ const Color aFillColor( COL_TRANSPARENT );
+ pUnderlineFnt->SetFillColor( aFillColor );
+
+ GetInfo().SetUnderFnt( new SwUnderlineFont( *pUnderlineFnt, nUnderEnd,
+ aCommonBaseLine ) );
+ }
+ else
+ // I'm sorry, we do not have a special underlining font for you.
+ GetInfo().SetUnderFnt( nullptr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrpaint.hxx b/sw/source/core/text/itrpaint.hxx
new file mode 100644
index 0000000000..893db371db
--- /dev/null
+++ b/sw/source/core/text/itrpaint.hxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "itrtxt.hxx"
+
+#include <optional>
+
+class SwSaveClip; // SwTextPainter
+class SwMultiPortion;
+class SwTaggedPDFHelper;
+
+class SwTextPainter : public SwTextCursor
+{
+ bool m_bPaintDrop;
+
+ SwLinePortion *CalcPaintOfst(const SwRect &rPaint, bool& rbSkippedNumPortions);
+ void CheckSpecialUnderline( const SwLinePortion* pPor,
+ tools::Long nAdjustBaseLine = 0 );
+protected:
+ void CtorInitTextPainter( SwTextFrame *pFrame, SwTextPaintInfo *pInf );
+ explicit SwTextPainter(SwTextNode const * pTextNode)
+ : SwTextCursor(pTextNode)
+ , m_bPaintDrop(false)
+ {
+ }
+
+public:
+ SwTextPainter(SwTextFrame *pTextFrame, SwTextPaintInfo *pTextPaintInf)
+ : SwTextCursor(pTextFrame->GetTextNodeFirst())
+ {
+ CtorInitTextPainter( pTextFrame, pTextPaintInf );
+ }
+ void DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip,
+ const bool bUnderSz,
+ ::std::optional<SwTaggedPDFHelper> & roTaggedLabel,
+ ::std::optional<SwTaggedPDFHelper> & roTaggedParagraph,
+ bool isPDFTaggingEnabled);
+ void PaintDropPortion();
+ // if PaintMultiPortion is called recursively, we have to pass the
+ // surrounding SwBidiPortion
+ void PaintMultiPortion( const SwRect &rPaint, SwMultiPortion& rMulti,
+ const SwMultiPortion* pEnvPor = nullptr );
+ void SetPaintDrop( const bool bNew ) { m_bPaintDrop = bNew; }
+ bool IsPaintDrop() const { return m_bPaintDrop; }
+ SwTextPaintInfo &GetInfo()
+ { return static_cast<SwTextPaintInfo&>(SwTextIter::GetInfo()); }
+ const SwTextPaintInfo &GetInfo() const
+ { return static_cast<const SwTextPaintInfo&>(SwTextIter::GetInfo()); }
+};
+
+bool IsUnderlineBreak( const SwLinePortion& rPor, const SwFont& rFnt );
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrtxt.cxx b/sw/source/core/text/itrtxt.cxx
new file mode 100644
index 0000000000..1d1eed3e08
--- /dev/null
+++ b/sw/source/core/text/itrtxt.cxx
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ndtxt.hxx>
+#include <txatbase.hxx>
+#include <paratr.hxx>
+#include <vcl/outdev.hxx>
+#include <editeng/paravertalignitem.hxx>
+
+#include "pormulti.hxx"
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include "porfld.hxx"
+#include "porrst.hxx"
+
+#include "itrtxt.hxx"
+#include <txtfrm.hxx>
+
+void SwTextIter::CtorInitTextIter( SwTextFrame *pNewFrame, SwTextInfo *pNewInf )
+{
+ assert(pNewFrame->GetPara());
+
+ CtorInitAttrIter( *pNewFrame->GetTextNodeFirst(), pNewFrame->GetPara()->GetScriptInfo(), pNewFrame );
+
+ SwTextNode const*const pNode = pNewFrame->GetTextNodeForParaProps();
+
+ m_pFrame = pNewFrame;
+ m_pInf = pNewInf;
+ m_aLineInf.CtorInitLineInfo( pNode->GetSwAttrSet(), *pNode );
+ m_nFrameStart = m_pFrame->getFrameArea().Pos().Y() + m_pFrame->getFramePrintArea().Pos().Y();
+ SwTextIter::Init();
+
+ // Order is important: only execute FillRegister if GetValue!=0
+ m_bRegisterOn = pNode->GetSwAttrSet().GetRegister().GetValue()
+ && m_pFrame->FillRegister( m_nRegStart, m_nRegDiff );
+}
+
+void SwTextIter::Init()
+{
+ m_pCurr = m_pInf->GetParaPortion();
+ m_nStart = m_pInf->GetTextStart();
+ m_nY = m_nFrameStart;
+ m_bPrev = true;
+ m_pPrev = nullptr;
+ m_nLineNr = 1;
+}
+
+void SwTextIter::CalcAscentAndHeight( SwTwips &rAscent, SwTwips &rHeight ) const
+{
+ rHeight = GetLineHeight();
+ rAscent = m_pCurr->GetAscent() + rHeight - m_pCurr->Height();
+}
+
+SwLineLayout *SwTextIter::GetPrev_()
+{
+ m_pPrev = nullptr;
+ m_bPrev = true;
+ SwLineLayout *pLay = m_pInf->GetParaPortion();
+ if( m_pCurr == pLay )
+ return nullptr;
+ while( pLay->GetNext() != m_pCurr )
+ pLay = pLay->GetNext();
+ m_pPrev = pLay;
+ return m_pPrev;
+}
+
+const SwLineLayout *SwTextIter::GetPrev()
+{
+ if(! m_bPrev)
+ GetPrev_();
+ return m_pPrev;
+}
+
+const SwLineLayout *SwTextIter::Prev()
+{
+ if( !m_bPrev )
+ GetPrev_();
+ if( m_pPrev )
+ {
+ m_bPrev = false;
+ m_pCurr = m_pPrev;
+ m_nStart = m_nStart - m_pCurr->GetLen();
+ m_nY = m_nY - GetLineHeight();
+ if( !m_pCurr->IsDummy() && !(--m_nLineNr) )
+ ++m_nLineNr;
+ return m_pCurr;
+ }
+ else
+ return nullptr;
+}
+
+const SwLineLayout *SwTextIter::Next()
+{
+ if(m_pCurr->GetNext())
+ {
+ m_pPrev = m_pCurr;
+ m_bPrev = true;
+ m_nStart = m_nStart + m_pCurr->GetLen();
+ m_nY += GetLineHeight();
+ if( m_pCurr->GetLen() || ( m_nLineNr>1 && !m_pCurr->IsDummy() ) )
+ ++m_nLineNr;
+ m_pCurr = m_pCurr->GetNext();
+ return m_pCurr;
+ }
+ else
+ return nullptr;
+}
+
+const SwLineLayout *SwTextIter::NextLine()
+{
+ const SwLineLayout *pNext = Next();
+ while( pNext && pNext->IsDummy() && pNext->GetNext() )
+ {
+ pNext = Next();
+ }
+ return pNext;
+}
+
+const SwLineLayout *SwTextIter::GetNextLine() const
+{
+ const SwLineLayout *pNext = m_pCurr->GetNext();
+ while( pNext && pNext->IsDummy() && pNext->GetNext() )
+ {
+ pNext = pNext->GetNext();
+ }
+ return pNext;
+}
+
+const SwLineLayout *SwTextIter::GetPrevLine()
+{
+ const SwLineLayout *pRoot = m_pInf->GetParaPortion();
+ if( pRoot == m_pCurr )
+ return nullptr;
+ const SwLineLayout *pLay = pRoot;
+
+ while( pLay->GetNext() != m_pCurr )
+ pLay = pLay->GetNext();
+
+ if( pLay->IsDummy() )
+ {
+ const SwLineLayout *pTmp = pRoot;
+ pLay = pRoot->IsDummy() ? nullptr : pRoot;
+ while( pTmp->GetNext() != m_pCurr )
+ {
+ if( !pTmp->IsDummy() )
+ pLay = pTmp;
+ pTmp = pTmp->GetNext();
+ }
+ }
+
+ // If nothing has changed, then there are only dummy's
+ return pLay;
+}
+
+const SwLineLayout *SwTextIter::PrevLine()
+{
+ const SwLineLayout *pMyPrev = Prev();
+ if( !pMyPrev )
+ return nullptr;
+
+ const SwLineLayout *pLast = pMyPrev;
+ while( pMyPrev && pMyPrev->IsDummy() )
+ {
+ pLast = pMyPrev;
+ pMyPrev = Prev();
+ }
+ return pMyPrev ? pMyPrev : pLast;
+}
+
+void SwTextIter::Bottom()
+{
+ while( Next() )
+ {
+ // nothing
+ }
+}
+
+void SwTextIter::CharToLine(TextFrameIndex const nChar)
+{
+ while( m_nStart + m_pCurr->GetLen() <= nChar && Next() )
+ ;
+ while( m_nStart > nChar && Prev() )
+ ;
+}
+
+// 1170: takes into account ambiguities:
+const SwLineLayout *SwTextCursor::CharCursorToLine(TextFrameIndex const nPosition)
+{
+ CharToLine( nPosition );
+ if( nPosition != m_nStart )
+ s_bRightMargin = false;
+ bool bPrevious = s_bRightMargin && m_pCurr->GetLen() && GetPrev() &&
+ GetPrev()->GetLen();
+ if (bPrevious && nPosition && CH_BREAK == GetInfo().GetChar(nPosition - TextFrameIndex(1)))
+ bPrevious = false;
+ return bPrevious ? PrevLine() : m_pCurr;
+}
+
+SwTwips SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine,
+ const SwLinePortion* pPor,
+ SwTwips nPorHeight, SwTwips nPorAscent,
+ const bool bAutoToCentered ) const
+{
+ if ( pPor )
+ {
+ nPorHeight = pPor->Height();
+ nPorAscent = pPor->GetAscent();
+ }
+
+ SwTwips nOfst = rLine.GetRealHeight() - rLine.Height();
+
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+
+ if ( pGrid && GetInfo().SnapToGrid() && pGrid->IsSquaredMode() )
+ {
+ const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight();
+ const bool bRubyTop = ! pGrid->GetRubyTextBelow();
+
+ if ( GetInfo().IsMulti() )
+ // we are inside the GetCharRect recursion for multi portions
+ // we center the portion in its surrounding line
+ nOfst = ( m_pCurr->Height() - nPorHeight ) / 2 + nPorAscent;
+ else
+ {
+ // We have to take care for ruby portions.
+ // The ruby portion is NOT centered
+ nOfst = nOfst + nPorAscent;
+
+ if ( ! pPor || ! pPor->IsMultiPortion() ||
+ ! static_cast<const SwMultiPortion*>(pPor)->IsRuby() )
+ {
+ // Portions which are bigger than on grid distance are
+ // centered inside the whole line.
+
+ //for text refactor
+ const sal_uInt16 nLineNet = rLine.Height() - nRubyHeight;
+ //const sal_uInt16 nLineNet = ( nPorHeight > nGridWidth ) ?
+ // rLine.Height() - nRubyHeight :
+ // nGridWidth;
+ nOfst += ( nLineNet - nPorHeight ) / 2;
+ if ( bRubyTop )
+ nOfst += nRubyHeight;
+ }
+ }
+ }
+ else
+ {
+ switch ( GetLineInfo().GetVertAlign() ) {
+ case SvxParaVertAlignItem::Align::Top :
+ nOfst = nOfst + nPorAscent;
+ break;
+ case SvxParaVertAlignItem::Align::Center :
+ OSL_ENSURE( rLine.Height() >= nPorHeight, "Portion height > Line height");
+ nOfst += ( rLine.Height() - nPorHeight ) / 2 + nPorAscent;
+ break;
+ case SvxParaVertAlignItem::Align::Bottom :
+ nOfst += rLine.Height() - nPorHeight + nPorAscent;
+ break;
+ case SvxParaVertAlignItem::Align::Automatic :
+ if ( bAutoToCentered || GetInfo().GetTextFrame()->IsVertical() )
+ {
+ // Vertical text has these cases to calculate the baseline:
+ // - Implicitly TB and RL: the origo is the top right corner, offset is the
+ // ascent.
+ // - (Implicitly TB and) LR: the origo is the top left corner, offset is the
+ // descent.
+ // - BT and LR: the origo is the bottom left corner, offset is the ascent.
+ if (GetInfo().GetTextFrame()->IsVertLR() && !GetInfo().GetTextFrame()->IsVertLRBT())
+ nOfst += rLine.Height() - ( rLine.Height() - nPorHeight ) / 2 - nPorAscent;
+ else
+ {
+ SwTwips nLineHeight = 0;
+ bool bHadClearingBreak = false;
+ if (GetInfo().GetTextFrame()->IsVertical())
+ {
+ // Ignore the height of clearing break portions in the automatic
+ // alignment case.
+ const SwLinePortion* pLinePor = rLine.GetFirstPortion();
+ while (pLinePor)
+ {
+ bool bClearingBreak = false;
+ if (pLinePor->IsBreakPortion())
+ {
+ auto pBreakPortion = static_cast<const SwBreakPortion*>(pLinePor);
+ bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE;
+ if (bClearingBreak)
+ {
+ bHadClearingBreak = true;
+ }
+ }
+ if (!bClearingBreak && pLinePor->Height() > nLineHeight)
+ {
+ nLineHeight = pLinePor->Height();
+ }
+ pLinePor = pLinePor->GetNextPortion();
+ }
+ }
+
+ if (!bHadClearingBreak)
+ {
+ nLineHeight = rLine.Height();
+ }
+
+ nOfst += ( nLineHeight - nPorHeight ) / 2 + nPorAscent;
+ }
+ break;
+ }
+ [[fallthrough]];
+ case SvxParaVertAlignItem::Align::Baseline :
+ // base line
+ if (pPor && pPor->GetHangingBaseline())
+ {
+ nOfst += rLine.GetAscent() // Romn baseline of the line.
+ - rLine.GetHangingBaseline() // Hanging baseline of the line.
+ + pPor->GetHangingBaseline(); // Romn baseline of the portion.
+ }
+ else
+ nOfst = nOfst + rLine.GetAscent();
+ break;
+ }
+ }
+
+ return nOfst;
+}
+
+void SwTextIter::TwipsToLine( const SwTwips y)
+{
+ while( m_nY + GetLineHeight() <= y && Next() )
+ ;
+ while( m_nY > y && Prev() )
+ ;
+}
+
+// Local helper function to check, if pCurr needs a field rest portion:
+static bool lcl_NeedsFieldRest( const SwLineLayout* pCurr )
+{
+ const SwLinePortion *pPor = pCurr->GetNextPortion();
+ bool bRet = false;
+ while( pPor && !bRet )
+ {
+ bRet = pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->HasFollow();
+ if( !pPor->GetNextPortion() || !pPor->GetNextPortion()->InFieldGrp() )
+ break;
+ pPor = pPor->GetNextPortion();
+ }
+ return bRet;
+}
+
+void SwTextIter::TruncLines( bool bNoteFollow )
+{
+ SwLineLayout *pDel = m_pCurr->GetNext();
+ TextFrameIndex const nEnd = m_nStart + m_pCurr->GetLen();
+
+ if( pDel )
+ {
+ m_pCurr->SetNext( nullptr );
+ if (MaybeHasHints() && bNoteFollow)
+ {
+ GetInfo().GetParaPortion()->SetFollowField( pDel->IsRest() ||
+ lcl_NeedsFieldRest( m_pCurr ) );
+
+ // bug 88534: wrong positioning of flys
+ SwTextFrame* pFollow = GetTextFrame()->GetFollow();
+ if ( pFollow && ! pFollow->IsLocked() &&
+ nEnd == pFollow->GetOffset() )
+ {
+ TextFrameIndex nRangeEnd = nEnd;
+ SwLineLayout* pLine = pDel;
+
+ // determine range to be searched for flys anchored as characters
+ while ( pLine )
+ {
+ nRangeEnd = nRangeEnd + pLine->GetLen();
+ pLine = pLine->GetNext();
+ }
+
+ // examine hints in range nEnd - (nEnd + nRangeChar)
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*GetTextFrame());
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if( RES_TXTATR_FLYCNT == pHt->Which() )
+ {
+ // check if hint is in our range
+ TextFrameIndex const nTmpPos(
+ GetTextFrame()->MapModelToView(pNode, pHt->GetStart()));
+ if ( nEnd <= nTmpPos && nTmpPos < nRangeEnd )
+ pFollow->InvalidateRange_(
+ SwCharRange( nTmpPos, nTmpPos ) );
+ }
+ }
+ }
+ }
+ delete pDel;
+ }
+ if( m_pCurr->IsDummy() &&
+ !m_pCurr->GetLen() &&
+ m_nStart < TextFrameIndex(GetTextFrame()->GetText().getLength()))
+ {
+ m_pCurr->SetRealHeight( 1 );
+ }
+ if (MaybeHasHints())
+ m_pFrame->RemoveFootnote( nEnd );
+}
+
+void SwTextIter::CntHyphens( sal_uInt8 &nEndCnt, sal_uInt8 &nMidCnt) const
+{
+ nEndCnt = 0;
+ nMidCnt = 0;
+ if ( m_bPrev && m_pPrev && !m_pPrev->IsEndHyph() && !m_pPrev->IsMidHyph() )
+ return;
+ SwLineLayout *pLay = m_pInf->GetParaPortion();
+ if( m_pCurr == pLay )
+ return;
+ while( pLay != m_pCurr )
+ {
+ if ( pLay->IsEndHyph() )
+ nEndCnt++;
+ else
+ nEndCnt = 0;
+ if ( pLay->IsMidHyph() )
+ nMidCnt++;
+ else
+ nMidCnt = 0;
+ pLay = pLay->GetNext();
+ }
+}
+
+// Change current output device to formatting device, this has to be done before
+// formatting.
+SwHookOut::SwHookOut( SwTextSizeInfo& rInfo ) :
+ pInf( &rInfo ),
+ pOut( rInfo.GetOut() ),
+ bOnWin( rInfo.OnWin() )
+{
+ OSL_ENSURE( rInfo.GetRefDev(), "No reference device for text formatting" );
+
+ // set new values
+ rInfo.SetOut( rInfo.GetRefDev() );
+ rInfo.SetOnWin( false );
+}
+
+SwHookOut::~SwHookOut()
+{
+ pInf->SetOut( pOut );
+ pInf->SetOnWin( bOnWin );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/itrtxt.hxx b/sw/source/core/text/itrtxt.hxx
new file mode 100644
index 0000000000..8db063d616
--- /dev/null
+++ b/sw/source/core/text/itrtxt.hxx
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <swtypes.hxx>
+#include "itratr.hxx"
+#include "inftxt.hxx"
+
+struct SwPosition;
+struct SwCursorMoveState;
+class SwMarginPortion;
+class SwFlyPortion;
+
+class SwTextIter : public SwAttrIter
+{
+protected:
+ SwLineInfo m_aLineInf;
+ SwTextFrame *m_pFrame;
+ SwTextInfo *m_pInf;
+ SwLineLayout *m_pCurr;
+ SwLineLayout *m_pPrev;
+ SwTwips m_nFrameStart;
+ SwTwips m_nY;
+ SwTwips m_nRegStart; // The register's start position (Y)
+ TextFrameIndex m_nStart; // Start in the text string, end = pCurr->GetLen()
+ sal_uInt16 m_nRegDiff; // Register's line distance
+ sal_Int32 m_nLineNr; // Line number
+ bool m_bPrev : 1;
+ bool m_bRegisterOn : 1; // Keep in register
+ bool m_bOneBlock : 1; // Justified text: Dispose single words
+ bool m_bLastBlock : 1; // Justified text: Also the last line
+ bool m_bLastCenter : 1; // Justified text: Center last line
+
+ SwLineLayout *GetPrev_();
+
+ // Reset in the first line
+ void Init();
+ void CtorInitTextIter( SwTextFrame *pFrame, SwTextInfo *pInf );
+ explicit SwTextIter(SwTextNode const * pTextNode)
+ : SwAttrIter(pTextNode)
+ , m_pFrame(nullptr)
+ , m_pInf(nullptr)
+ , m_pCurr(nullptr)
+ , m_pPrev(nullptr)
+ , m_nFrameStart(0)
+ , m_nY(0)
+ , m_nRegStart(0)
+ , m_nStart(0)
+ , m_nRegDiff(0)
+ , m_nLineNr(0)
+ , m_bPrev(false)
+ , m_bRegisterOn(false)
+ , m_bOneBlock(false)
+ , m_bLastBlock(false)
+ , m_bLastCenter(false)
+ {
+ }
+public:
+ SwTextIter(SwTextFrame *pTextFrame, SwTextInfo *pTextInf)
+ : SwAttrIter(pTextFrame->GetTextNodeFirst())
+ , m_bOneBlock(false)
+ , m_bLastBlock(false)
+ , m_bLastCenter(false)
+ {
+ CtorInitTextIter(pTextFrame, pTextInf);
+ }
+ const SwLineLayout *GetCurr() const { return m_pCurr; } // NEVER 0!
+ const SwLineLayout *GetNext() const { return m_pCurr->GetNext(); }
+ const SwLineLayout *GetPrev();
+ TextFrameIndex GetLength() const { return m_pCurr->GetLen(); }
+ sal_Int32 GetLineNr() const { return m_nLineNr; }
+ TextFrameIndex GetStart() const { return m_nStart; }
+ TextFrameIndex GetEnd() const { return GetStart() + GetLength(); }
+ SwTwips Y() const { return m_nY; }
+
+ SwTwips RegStart() const { return m_nRegStart; }
+ sal_uInt16 RegDiff() const { return m_nRegDiff; }
+ bool IsRegisterOn() const { return m_bRegisterOn; }
+
+ SwTextInfo &GetInfo() { return *m_pInf; }
+ const SwTextInfo &GetInfo() const { return *m_pInf; }
+
+ void Top() { Init(); }
+ void Bottom();
+ const SwLineLayout *Next();
+ const SwLineLayout *Prev();
+
+ // Skips the FlyFrames dummy line
+ const SwLineLayout *NextLine();
+ const SwLineLayout *PrevLine();
+ const SwLineLayout *GetNextLine() const;
+ const SwLineLayout *GetPrevLine();
+
+ void CharToLine(TextFrameIndex);
+ void TwipsToLine(const SwTwips);
+
+ // Truncates all after pCurr
+ void TruncLines( bool bNoteFollow = false );
+
+ SwTwips GetLineHeight() const { return m_pCurr->GetRealHeight(); }
+ void CalcAscentAndHeight( SwTwips &rAscent, SwTwips &rHeight ) const;
+
+ // Lots of trouble for querying pCurr == pPara
+ bool IsFirstTextLine() const
+ { return m_nStart == GetInfo().GetTextStart() &&
+ !( m_pCurr->IsDummy() && GetNextLine() ); }
+
+ // Replacement for the old IsFirstLine()
+ bool IsParaLine() const
+ { return m_pCurr == m_pInf->GetParaPortion(); }
+
+ const SwLineInfo &GetLineInfo() const { return m_aLineInf; }
+ SwTwips GetFirstPos() const { return m_nFrameStart; }
+ inline bool SeekAndChg( SwTextSizeInfo &rInf );
+ inline bool SeekAndChgBefore( SwTextSizeInfo &rInf );
+ inline bool SeekStartAndChg( SwTextSizeInfo &rInf, const bool bPara=false );
+
+ SwTextFrame *GetTextFrame() { return m_pFrame; }
+ const SwTextFrame *GetTextFrame() const { return m_pFrame; }
+
+ // Counts consecutive hyphens in order to be within the boundary given by MaxHyphens
+ void CntHyphens( sal_uInt8 &nEndCnt, sal_uInt8 &nMidCnt) const;
+};
+
+class SwTextMargin : public SwTextIter
+{
+private:
+ SwTwips mnLeft;
+ SwTwips mnRight;
+ SwTwips mnFirst;
+ sal_uInt16 mnDropLeft;
+ sal_uInt16 mnDropHeight;
+ sal_uInt16 mnDropDescent;
+ sal_uInt16 mnDropLines;
+ SvxAdjust mnAdjust;
+ // #i91133#
+ SwTwips mnTabLeft;
+
+protected:
+ // For FormatQuoVadis
+ void Right( const SwTwips nNew ) { mnRight = nNew; }
+
+ void CtorInitTextMargin( SwTextFrame *pFrame, SwTextSizeInfo *pInf );
+ explicit SwTextMargin(SwTextNode const * pTextNode)
+ : SwTextIter(pTextNode)
+ , mnLeft(0)
+ , mnRight(0)
+ , mnFirst(0)
+ , mnDropLeft(0)
+ , mnDropHeight(0)
+ , mnDropDescent(0)
+ , mnDropLines(0)
+ , mnAdjust(SvxAdjust::Left)
+ , mnTabLeft(0)
+ {
+ }
+public:
+ SwTextMargin(SwTextFrame *pTextFrame, SwTextSizeInfo *pTextSizeInf)
+ : SwTextIter(pTextFrame->GetTextNodeFirst())
+ {
+ CtorInitTextMargin( pTextFrame, pTextSizeInf );
+ }
+ inline SwTwips GetLeftMargin() const;
+ inline SwTwips Left() const;
+ SwTwips Right() const { return mnRight; }
+ SwTwips FirstLeft() const { return mnFirst; }
+ SwTwips CurrWidth() const { return m_pCurr->PrtWidth(); }
+ SwTwips GetLineStart() const;
+ SwTwips GetLineEnd() const { return GetLineStart() + CurrWidth(); }
+ Point GetTopLeft() const { return Point( GetLineStart(), Y() ); }
+ bool IsOneBlock() const { return m_bOneBlock; }
+ bool IsLastBlock() const { return m_bLastBlock; }
+ bool IsLastCenter() const { return m_bLastCenter; }
+ SvxAdjust GetAdjust() const { return mnAdjust; }
+ sal_uInt16 GetLineWidth() const
+ { return sal_uInt16( Right() - GetLeftMargin() + 1 ); }
+ SwTwips GetLeftMin() const { return std::min(mnFirst, mnLeft); }
+ bool HasNegFirst() const { return mnFirst < mnLeft; }
+
+ // #i91133#
+ SwTwips GetTabLeft() const
+ {
+ return mnTabLeft;
+ }
+ // DropCaps
+ sal_uInt16 GetDropLines() const { return mnDropLines; }
+ void SetDropLines( const sal_uInt16 nNew ) { mnDropLines = nNew; }
+ sal_uInt16 GetDropLeft() const { return mnDropLeft; }
+ sal_uInt16 GetDropHeight() const { return mnDropHeight; }
+ void SetDropHeight( const sal_uInt16 nNew ) { mnDropHeight = nNew; }
+ sal_uInt16 GetDropDescent() const { return mnDropDescent; }
+ void SetDropDescent( const sal_uInt16 nNew ) { mnDropDescent = nNew; }
+ void DropInit();
+
+ // Returns the TextPos for start and end of the current line without whitespace
+ // Implemented in frminf.cxx
+ TextFrameIndex GetTextStart() const;
+ TextFrameIndex GetTextEnd() const;
+
+ SwTextSizeInfo &GetInfo()
+ { return static_cast<SwTextSizeInfo&>(SwTextIter::GetInfo()); }
+ const SwTextSizeInfo &GetInfo() const
+ { return static_cast<const SwTextSizeInfo&>(SwTextIter::GetInfo()); }
+
+};
+
+class SwTextAdjuster : public SwTextMargin
+{
+ // Adjusts the portion, if we have adjustment and FlyFrames
+ void CalcFlyAdjust( SwLineLayout *pCurr );
+
+ // Calls SplitGlues and CalcBlockAdjust
+ void FormatBlock( );
+
+ // Creates the glue chain for short lines
+ SwMarginPortion* CalcRightMargin( SwLineLayout *pCurr, SwTwips nReal = 0 );
+
+ // Calculate the adjustment (FlyPortions)
+ SwFlyPortion *CalcFlyPortion( const tools::Long nRealWidth,
+ const SwRect &rCurrRect );
+
+protected:
+ explicit SwTextAdjuster(SwTextNode const * pTextNode) : SwTextMargin(pTextNode) { }
+ // Creates the Glues for adjusted paragraphs
+ void CalcNewBlock( SwLineLayout *pCurr, const SwLinePortion *pStopAt,
+ SwTwips nReal = 0, bool bSkipKashida = false );
+ SwTwips CalcKanaAdj( SwLineLayout *pCurr );
+
+public:
+ // Is overloaded by SwTextFormatter due to UpdatePos
+ void CalcAdjLine( SwLineLayout *pCurr );
+
+ // For adjusting afterwards
+ void GetAdjusted() const
+ {
+ if( m_pCurr->IsFormatAdj() )
+ const_cast<SwTextAdjuster*>(this)->CalcAdjLine( m_pCurr );
+ }
+
+ // Special treatment for DropCaps
+ void CalcDropAdjust();
+ void CalcDropRepaint();
+};
+
+class SwTextCursor : public SwTextAdjuster
+{
+ // A small helper-class to save SwTextCursor member, manipulate them
+ // and to restore them
+ friend class SwTextCursorSave;
+
+ // Ambiguities
+ static bool s_bRightMargin;
+ void GetCharRect_(SwRect *, TextFrameIndex, SwCursorMoveState *);
+protected:
+ void CtorInitTextCursor( SwTextFrame *pFrame, SwTextSizeInfo *pInf );
+ explicit SwTextCursor(SwTextNode const * pTextNode) : SwTextAdjuster(pTextNode) { }
+ void AddExtraBlankWidth();
+public:
+ SwTextCursor( SwTextFrame *pTextFrame, SwTextSizeInfo *pTextSizeInf )
+ : SwTextAdjuster(pTextFrame->GetTextNodeFirst())
+ {
+ CtorInitTextCursor(pTextFrame, pTextSizeInf);
+ }
+ void GetCharRect(SwRect *, TextFrameIndex, SwCursorMoveState* = nullptr,
+ const tools::Long nMax = 0 );
+ void GetEndCharRect(SwRect *, TextFrameIndex, SwCursorMoveState* = nullptr,
+ const tools::Long nMax = 0 );
+ TextFrameIndex GetModelPositionForViewPoint( SwPosition *pPos, const Point &rPoint,
+ bool bChgNode, SwCursorMoveState* = nullptr ) const;
+ // Respects ambiguities: For the implementation see below
+ const SwLineLayout *CharCursorToLine(TextFrameIndex const nPos);
+
+ // calculates baseline for portion rPor
+ // bAutoToCentered indicates, if AUTOMATIC mode means CENTERED or BASELINE
+ SwTwips AdjustBaseLine( const SwLineLayout& rLine, const SwLinePortion* pPor,
+ SwTwips nPorHeight = 0, SwTwips nAscent = 0,
+ const bool bAutoToCentered = false ) const;
+
+ static void SetRightMargin( const bool bNew ){ s_bRightMargin = bNew; }
+ static bool IsRightMargin() { return s_bRightMargin; }
+};
+
+// Change current output device to printer, this has to be done before
+// formatting.
+class SwHookOut
+{
+ SwTextSizeInfo* pInf;
+ VclPtr<OutputDevice> pOut;
+ bool bOnWin;
+public:
+ explicit SwHookOut( SwTextSizeInfo& rInfo );
+ ~SwHookOut();
+};
+
+inline bool SwTextIter::SeekAndChg( SwTextSizeInfo &rInf )
+{
+ return SeekAndChgAttrIter( rInf.GetIdx(), rInf.GetOut() );
+}
+
+inline bool SwTextIter::SeekAndChgBefore( SwTextSizeInfo &rInf )
+{
+ if ( rInf.GetIdx() )
+ return SeekAndChgAttrIter(rInf.GetIdx() - TextFrameIndex(1), rInf.GetOut());
+ else
+ return SeekAndChgAttrIter( rInf.GetIdx(), rInf.GetOut() );
+}
+
+inline bool SwTextIter::SeekStartAndChg( SwTextSizeInfo &rInf, const bool bPara )
+{
+ return SeekStartAndChgAttrIter( rInf.GetOut(), bPara );
+}
+
+inline SwTwips SwTextMargin::GetLeftMargin() const
+{
+ return IsFirstTextLine() ? mnFirst : Left();
+}
+
+inline SwTwips SwTextMargin::Left() const
+{
+ return (mnDropLines >= m_nLineNr && 1 != m_nLineNr) ? mnFirst + mnDropLeft : mnLeft;
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/noteurl.cxx b/sw/source/core/text/noteurl.cxx
new file mode 100644
index 0000000000..fa91ea252d
--- /dev/null
+++ b/sw/source/core/text/noteurl.cxx
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <noteurl.hxx>
+
+// Global variable
+SwNoteURL* pNoteURL = nullptr;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/pordrop.hxx b/sw/source/core/text/pordrop.hxx
new file mode 100644
index 0000000000..50dd00e566
--- /dev/null
+++ b/sw/source/core/text/pordrop.hxx
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "portxt.hxx"
+#include <swfont.hxx>
+
+#include <memory>
+
+class SwFont;
+
+// DropCap cache, global variable initialized/destroyed in txtinit.cxx
+// and used in txtdrop.cxx for initial calculation
+
+class SwDropCapCache;
+extern SwDropCapCache *pDropCapCache;
+
+// A drop portion can consist of one or more parts in order to allow
+// attribute changes inside them.
+class SwDropPortionPart
+{
+ std::unique_ptr<SwDropPortionPart> m_pFollow;
+ std::unique_ptr<SwFont> m_pFnt;
+ TextFrameIndex m_nLen;
+ sal_uInt16 m_nWidth;
+ bool m_bJoinBorderWithNext;
+ bool m_bJoinBorderWithPrev;
+
+public:
+ SwDropPortionPart( SwFont& rFont, const TextFrameIndex nL )
+ : m_pFnt( &rFont ), m_nLen( nL ), m_nWidth( 0 ), m_bJoinBorderWithNext(false), m_bJoinBorderWithPrev(false) {};
+ ~SwDropPortionPart();
+
+ SwDropPortionPart* GetFollow() const { return m_pFollow.get(); };
+ void SetFollow( std::unique_ptr<SwDropPortionPart> pNew ) { m_pFollow = std::move(pNew); };
+ SwFont& GetFont() const { return *m_pFnt; }
+ TextFrameIndex GetLen() const { return m_nLen; }
+ sal_uInt16 GetWidth() const { return m_nWidth; }
+ void SetWidth( sal_uInt16 nNew ) { m_nWidth = nNew; }
+
+ bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; }
+ bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; }
+ void SetJoinBorderWithPrev( const bool bJoinPrev ) { m_bJoinBorderWithPrev = bJoinPrev; }
+ void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; }
+};
+
+/// Text portion for the Format -> Paragraph -> Drop Caps functionality.
+class SwDropPortion : public SwTextPortion
+{
+ friend class SwDropCapCache;
+ std::unique_ptr<SwDropPortionPart> m_pPart; // due to script/attribute changes
+ sal_uInt16 m_nLines; // Line count
+ sal_uInt16 m_nDropHeight; // Height
+ sal_uInt16 m_nDropDescent; // Distance to the next line
+ sal_uInt16 m_nDistance; // Distance to the text
+ sal_uInt16 m_nFix; // Fixed position
+ short m_nY; // Y Offset
+
+ bool FormatText( SwTextFormatInfo &rInf );
+ void PaintText( const SwTextPaintInfo &rInf ) const;
+
+public:
+ SwDropPortion( const sal_uInt16 nLineCnt,
+ const sal_uInt16 nDropHeight,
+ const sal_uInt16 nDropDescent,
+ const sal_uInt16 nDistance );
+ virtual ~SwDropPortion() override;
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ void PaintDrop( const SwTextPaintInfo &rInf ) const;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+
+ sal_uInt16 GetLines() const { return m_nLines; }
+ sal_uInt16 GetDistance() const { return m_nDistance; }
+ sal_uInt16 GetDropHeight() const { return m_nDropHeight; }
+ sal_uInt16 GetDropDescent() const { return m_nDropDescent; }
+ sal_uInt16 GetDropLeft() const { return Width() + m_nFix; }
+
+ SwDropPortionPart* GetPart() const { return m_pPart.get(); }
+ void SetPart( std::unique_ptr<SwDropPortionPart> pNew ) { m_pPart = std::move(pNew); }
+
+ void SetY( short nNew ) { m_nY = nNew; }
+
+ SwFont* GetFnt() const { return m_pPart ? &m_pPart->GetFont() : nullptr; }
+
+ static void DeleteDropCapCache();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx
new file mode 100644
index 0000000000..0884db6fce
--- /dev/null
+++ b/sw/source/core/text/porexp.cxx
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <viewopt.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <SwPortionHandler.hxx>
+#include "inftxt.hxx"
+#include "porexp.hxx"
+
+TextFrameIndex SwExpandPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const
+{ return SwLinePortion::GetModelPositionForViewPoint( nOfst ); }
+
+bool SwExpandPortion::GetExpText( const SwTextSizeInfo&, OUString &rText ) const
+{
+ rText.clear();
+ // Do not do: return 0 != rText.Len();
+ // Reason being: empty fields replace CH_TXTATR with an empty string
+ return true;
+}
+
+void SwExpandPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString(), GetWhichPor() );
+}
+
+void SwExpandPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwExpandPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwPosSize SwExpandPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwTextSlot aDiffText( &rInf, this, false, false );
+ return rInf.GetTextSize();
+}
+
+bool SwExpandPortion::Format( SwTextFormatInfo &rInf )
+{
+ SwTextSlot aDiffText( &rInf, this, true, false );
+ TextFrameIndex const nFullLen = rInf.GetLen();
+
+ // As odd as it may seem: the query for GetLen() must return
+ // false due to the ExpandPortions _after_ the aDiffText (see SoftHyphs)
+ // caused by the SetFull ...
+ if( !nFullLen )
+ {
+ // Do not Init(), because we need height and ascent
+ Width(0);
+ return false;
+ }
+ return SwTextPortion::Format( rInf );
+}
+
+void SwExpandPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ rInf.DrawCSDFHighlighting(*this); // here it detects as CS and not DF
+
+ SwTextSlot aDiffText( &rInf, this, true, true );
+ const SwFont aOldFont = *rInf.GetFont();
+ if( GetJoinBorderWithPrev() )
+ const_cast<SwTextPaintInfo&>(rInf).GetFont()->SetLeftBorder(nullptr);
+ if( GetJoinBorderWithNext() )
+ const_cast<SwTextPaintInfo&>(rInf).GetFont()->SetRightBorder(nullptr);
+// rInf.DrawCSDFHighlighting(*this); // here it detects as DF and only the '/' is detected as CS
+
+ rInf.DrawBackBrush( *this );
+ rInf.DrawBorder( *this );
+
+ // Do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ // The contents of field portions is not considered during the
+ // calculation of the directions. Therefore we let vcl handle
+ // the calculation by removing the BIDI_STRONG_FLAG temporarily.
+ SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
+ aLayoutModeModifier.SetAuto();
+
+ // ST2
+ if ( rInf.GetSmartTags() || rInf.GetGrammarCheckList() )
+ rInf.DrawMarkedText( *this, rInf.GetLen(), false,
+ nullptr != rInf.GetSmartTags(), nullptr != rInf.GetGrammarCheckList() );
+ else
+ rInf.DrawText( *this, rInf.GetLen() );
+
+ if( GetJoinBorderWithPrev() || GetJoinBorderWithNext() )
+ *const_cast<SwTextPaintInfo&>(rInf).GetFont() = aOldFont;
+}
+
+SwLinePortion *SwBlankPortion::Compress() { return this; }
+
+/**
+ * If a Line is full of HardBlanks and overflows, we must not generate
+ * underflows!
+ * Causes problems with Fly
+ */
+sal_uInt16 SwBlankPortion::MayUnderflow( const SwTextFormatInfo &rInf,
+ TextFrameIndex const nIdx, bool bUnderflow)
+{
+ if( rInf.StopUnderflow() )
+ return 0;
+ const SwLinePortion *pPos = rInf.GetRoot();
+ if( pPos->GetNextPortion() )
+ pPos = pPos->GetNextPortion();
+ while( pPos && pPos->IsBlankPortion() )
+ pPos = pPos->GetNextPortion();
+ if( !pPos || !rInf.GetIdx() || ( !pPos->GetLen() && pPos == rInf.GetRoot() ) )
+ return 0; // There are just BlankPortions left
+
+ // If a Blank is preceding us, we do not need to trigger underflow
+ // If a Blank is succeeding us, we do not need to pass on the underflow
+ if (bUnderflow
+ && nIdx + TextFrameIndex(1) < TextFrameIndex(rInf.GetText().getLength())
+ && CH_BLANK == rInf.GetText()[sal_Int32(nIdx) + 1])
+ {
+ return 0;
+ }
+ if( nIdx && !const_cast<SwTextFormatInfo&>(rInf).GetFly() )
+ {
+ while( pPos && !pPos->IsFlyPortion() )
+ pPos = pPos->GetNextPortion();
+ if( !pPos )
+ {
+ // We check to see if there are useful line breaks, blanks or fields etc. left
+ // In case there still are some, no underflow
+ // If there are Flys, we still allow the underflow
+ TextFrameIndex nBlank = nIdx;
+ while( --nBlank > rInf.GetLineStart() )
+ {
+ const sal_Unicode cCh = rInf.GetChar( nBlank );
+ if( CH_BLANK == cCh ||
+ (( CH_TXTATR_BREAKWORD == cCh || CH_TXTATR_INWORD == cCh )
+ && rInf.HasHint( nBlank ) ) )
+ break;
+ }
+ if( nBlank <= rInf.GetLineStart() )
+ return 0;
+ }
+ }
+ if (nIdx < TextFrameIndex(2))
+ return 1;
+ sal_Unicode const cCh(rInf.GetChar(nIdx - TextFrameIndex(1)));
+ if (CH_BLANK == cCh)
+ return 1;
+ if( CH_BREAK == cCh )
+ return 0;
+ return 2;
+}
+
+/**
+ * Format End of Line
+ */
+void SwBlankPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ sal_uInt16 nMay = MayUnderflow( rInf, rInf.GetIdx() - mnLineLength, true );
+ if( !nMay )
+ return;
+
+ if( nMay > 1 )
+ {
+ if( rInf.GetLast() == this )
+ rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) );
+ rInf.X( rInf.X() - PrtWidth() );
+ rInf.SetIdx( rInf.GetIdx() - GetLen() );
+ }
+ Truncate();
+ rInf.SetUnderflow( this );
+ if( rInf.GetLast()->IsKernPortion() )
+ rInf.SetUnderflow( rInf.GetLast() );
+}
+
+/**
+ * Pass on the underflows and trigger them ourselves!
+ */
+bool SwBlankPortion::Format( SwTextFormatInfo &rInf )
+{
+ const bool bFull = rInf.IsUnderflow() || SwExpandPortion::Format( rInf );
+ if( bFull && MayUnderflow( rInf, rInf.GetIdx(), rInf.IsUnderflow() ) )
+ {
+ Truncate();
+ rInf.SetUnderflow( this );
+ if( rInf.GetLast()->IsKernPortion() )
+ rInf.SetUnderflow( rInf.GetLast() );
+ }
+ return bFull;
+}
+
+void SwBlankPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // Draw field shade (can be disabled individually)
+ if (!m_bMulti) // No gray background for multiportion brackets
+ rInf.DrawViewOpt(*this, PortionType::Blank);
+ SwExpandPortion::Paint(rInf);
+
+ if (m_cChar == CHAR_HARDBLANK)
+ {
+ if (rInf.GetOpt().IsBlank())
+ {
+ // Draw tilde or degree sign
+ OUString aMarker = (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess()
+ .get(DocumentSettingId::USE_VARIABLE_WIDTH_NBSP)
+ ? u"~"_ustr
+ : u"°"_ustr);
+
+ SwPosSize aMarkerSize(rInf.GetTextSize(aMarker));
+ Point aPos(rInf.GetPos());
+
+ std::shared_ptr<SwRect> pPortionRect = std::make_shared<SwRect>();
+ rInf.CalcRect(*this, pPortionRect.get());
+ aPos.AdjustX((pPortionRect->Width() / 2) - (aMarkerSize.Width() / 2));
+
+ SwTextPaintInfo aInf(rInf, &aMarker);
+ aInf.SetPos(aPos);
+ SwTextPortion aMarkerPor;
+ aMarkerPor.Width(aMarkerSize.Width());
+ aMarkerPor.Height(aMarkerSize.Height());
+ aMarkerPor.SetAscent(GetAscent());
+
+ Color colorBackup = aInf.GetFont()->GetColor();
+ aInf.GetFont()->SetColor(NON_PRINTING_CHARACTER_COLOR);
+ aInf.DrawText(aMarkerPor, TextFrameIndex(aMarker.getLength()), true);
+ aInf.GetFont()->SetColor(colorBackup);
+ }
+ }
+}
+
+bool SwBlankPortion::GetExpText( const SwTextSizeInfo& rInf, OUString &rText ) const
+{
+ if (m_cChar == CHAR_HARDBLANK
+ && rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::USE_VARIABLE_WIDTH_NBSP))
+ rText = OUString(CH_BLANK);
+ else
+ rText = OUString(m_cChar);
+
+ return true;
+}
+
+void SwBlankPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString( m_cChar ), GetWhichPor() );
+}
+
+void SwBlankPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwBlankPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("char"),
+ BAD_CAST(OUString(m_cChar).toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("multi"),
+ BAD_CAST(OString::boolean(m_bMulti).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwPostItsPortion::SwPostItsPortion( bool bScrpt )
+ : m_bScript( bScrpt )
+{
+ mnLineLength = TextFrameIndex(1);
+ SetWhichPor( PortionType::PostIts );
+}
+
+void SwPostItsPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( rInf.OnWin() && Width() )
+ rInf.DrawPostIts( IsScript() );
+}
+
+sal_uInt16 SwPostItsPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ // Unbelievable: PostIts are always visible
+ return rInf.OnWin() ? SwViewOption::GetPostItsWidth( rInf.GetOut() ) : 0;
+}
+
+bool SwPostItsPortion::Format( SwTextFormatInfo &rInf )
+{
+ const bool bRet = SwLinePortion::Format( rInf );
+ // PostIts should not have an effect on line height etc.
+ SetAscent( 1 );
+ Height( 1 );
+ return bRet;
+}
+
+bool SwPostItsPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ if( rInf.OnWin() && rInf.GetOpt().IsPostIts() )
+ rText = " ";
+ else
+ rText.clear();
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx
new file mode 100644
index 0000000000..82de5b1be4
--- /dev/null
+++ b/sw/source/core/text/porexp.hxx
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "portxt.hxx"
+
+class SwExpandPortion : public SwTextPortion
+{
+public:
+ SwExpandPortion() { SetWhichPor( PortionType::Expand ); }
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+/// Non-breaking space or non-breaking hyphen.
+class SwBlankPortion : public SwExpandPortion
+{
+ sal_Unicode m_cChar;
+ bool m_bMulti; // For multiportion brackets
+public:
+ SwBlankPortion( sal_Unicode cCh, bool bMult = false )
+ : m_cChar( cCh ), m_bMulti( bMult )
+ { SetLen(TextFrameIndex(1)); SetWhichPor( PortionType::Blank ); }
+
+ virtual SwLinePortion *Compress() override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ static sal_uInt16 MayUnderflow(const SwTextFormatInfo &rInf, TextFrameIndex nIdx,
+ bool bUnderflow );
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+class SwPostItsPortion : public SwExpandPortion
+{
+ bool m_bScript;
+public:
+ explicit SwPostItsPortion( bool bScrpt );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ bool IsScript() const { return m_bScript; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx
new file mode 100644
index 0000000000..1a30a4ecd7
--- /dev/null
+++ b/sw/source/core/text/porfld.cxx
@@ -0,0 +1,1462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+
+#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"
+#include "porfld.hxx"
+#include "inftxt.hxx"
+#include <fmtornt.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <viewsh.hxx>
+#include <doc.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <rootfrm.hxx>
+#include <breakit.hxx>
+#include "porftn.hxx"
+#include <accessibilityoptions.hxx>
+#include <editeng/lrspitem.hxx>
+#include <unicode/ubidi.h>
+#include <bookmark.hxx>
+#include <docufld.hxx>
+
+using namespace ::com::sun::star;
+
+SwLinePortion *SwFieldPortion::Compress()
+{ return (GetLen() || !m_aExpand.isEmpty() || SwLinePortion::Compress()) ? this : nullptr; }
+
+SwFieldPortion *SwFieldPortion::Clone( const OUString &rExpand ) const
+{
+ std::unique_ptr<SwFont> pNewFnt;
+ if( m_pFont )
+ {
+ pNewFnt.reset(new SwFont( *m_pFont ));
+ }
+ // #i107143#
+ // pass placeholder property to created <SwFieldPortion> instance.
+ SwFieldPortion* pClone = new SwFieldPortion(rExpand, std::move(pNewFnt));
+ pClone->SetNextOffset( m_nNextOffset );
+ pClone->m_bNoLength = m_bNoLength;
+ return pClone;
+}
+
+void SwFieldPortion::TakeNextOffset( const SwFieldPortion* pField )
+{
+ OSL_ENSURE( pField, "TakeNextOffset: Missing Source" );
+ m_nNextOffset = pField->GetNextOffset();
+ m_aExpand = m_aExpand.replaceAt(0, sal_Int32(m_nNextOffset), u"");
+ m_bFollow = true;
+}
+
+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_bNoLength( false )
+{
+ SetWhichPor( PortionType::Field );
+}
+
+SwFieldPortion::SwFieldPortion( const SwFieldPortion& rField )
+ : SwExpandPortion( rField )
+ , m_aExpand( rField.GetExp() )
+ , m_nNextOffset( rField.GetNextOffset() )
+ , m_nNextScriptChg( rField.m_nNextScriptChg )
+ , m_nFieldLen(rField.m_nFieldLen)
+ , m_nViewWidth( rField.m_nViewWidth )
+ , m_bFollow( rField.IsFollow() )
+ , m_bLeft( rField.IsLeft() )
+ , m_bHide( rField.IsHide() )
+ , m_bCenter( rField.IsCenter() )
+ , m_bHasFollow( rField.HasFollow() )
+ , m_bAnimated ( rField.m_bAnimated )
+ , m_bNoPaint( rField.m_bNoPaint)
+ , m_bReplace( rField.m_bReplace )
+ , m_bNoLength( rField.m_bNoLength )
+{
+ if ( rField.HasFont() )
+ m_pFont.reset( new SwFont( *rField.GetFont() ) );
+
+ SetWhichPor( PortionType::Field );
+}
+
+SwFieldPortion::~SwFieldPortion()
+{
+ m_pFont.reset();
+}
+
+sal_uInt16 SwFieldPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ // even though this is const, nViewWidth should be computed at the very end:
+ SwFieldPortion* pThis = const_cast<SwFieldPortion*>(this);
+ if( !Width() && rInf.OnWin() && !rInf.GetOpt().IsPagePreview() &&
+ !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() )
+ {
+ if( !m_nViewWidth )
+ pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width();
+ }
+ else
+ pThis->m_nViewWidth = 0;
+ return m_nViewWidth;
+}
+
+namespace {
+
+/**
+ * Never just use SetLen(0)
+ */
+class SwFieldSlot
+{
+ std::shared_ptr<const vcl::text::TextLayoutCache> m_pOldCachedVclData;
+ const OUString *pOldText;
+ OUString aText;
+ TextFrameIndex nIdx;
+ TextFrameIndex nLen;
+ sal_Unicode nOrigHookChar;
+ SwTextFormatInfo *pInf;
+ bool bOn;
+public:
+ SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor );
+ ~SwFieldSlot();
+};
+
+}
+
+SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pPor )
+ : pOldText(nullptr)
+ , nIdx(0)
+ , nLen(0)
+ , nOrigHookChar(0)
+ , pInf(nullptr)
+{
+ bOn = pPor->GetExpText( *pNew, aText );
+
+ // The text will be replaced ...
+ if( !bOn )
+ return;
+
+ pInf = const_cast<SwTextFormatInfo*>(pNew);
+ nIdx = pInf->GetIdx();
+ nLen = pInf->GetLen();
+ pOldText = &(pInf->GetText());
+ nOrigHookChar = pInf->GetHookChar();
+ m_pOldCachedVclData = pInf->GetCachedVclData();
+ pInf->SetLen(TextFrameIndex(aText.getLength()));
+ pInf->SetCachedVclData(nullptr);
+ if( pPor->IsFollow() )
+ {
+ pInf->SetFakeLineStart( nIdx > pInf->GetLineStart() );
+ pInf->SetIdx(TextFrameIndex(0));
+ }
+ else
+ {
+ TextFrameIndex nEnd(pOldText->getLength());
+ if (nIdx < nEnd)
+ {
+ sal_Int32 const nFieldLen(pPor->GetFieldLen());
+ aText = (*pOldText).replaceAt(sal_Int32(nIdx), nFieldLen, aText);
+ }
+ else if (nIdx == nEnd)
+ aText = *pOldText + aText;
+ else
+ SAL_WARN("sw.core", "SwFieldSlot bad SwFieldPortion index.");
+ }
+ pInf->SetText( aText );
+}
+
+SwFieldSlot::~SwFieldSlot()
+{
+ if( bOn )
+ {
+ pInf->SetCachedVclData(m_pOldCachedVclData);
+ pInf->SetText( *pOldText );
+ // ofz#64109 at last for ruby-text when we restore the original text to
+ // continue laying out the 'body' text of the ruby, then a tab or other
+ // 'hook char' in the text drawn above it shouldn't affect the 'body'
+ // While there are other cases, such as tdf#148360, where the tab in an
+ // inline expanded field, that should affect the body.
+ if (pInf->IsRuby())
+ pInf->SetHookChar(nOrigHookChar);
+ pInf->SetIdx( nIdx );
+ pInf->SetLen( nLen );
+ pInf->SetFakeLineStart( false );
+ }
+}
+
+void SwFieldPortion::CheckScript( const SwTextSizeInfo &rInf )
+{
+ OUString aText;
+ if (!GetExpText(rInf, aText) || aText.isEmpty())
+ return;
+
+ SwFontScript nActual = m_pFont ? m_pFont->GetActual() : rInf.GetFont()->GetActual();
+ sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, 0 );
+ sal_Int32 nChg = 0;
+ if( i18n::ScriptType::WEAK == nScript )
+ {
+ nChg = g_pBreakIt->GetBreakIter()->endOfScript(aText,0,nScript);
+ if (nChg < aText.getLength() && nChg >= 0)
+ nScript = g_pBreakIt->GetBreakIter()->getScriptType( aText, nChg );
+ }
+
+ // nNextScriptChg will be evaluated during SwFieldPortion::Format()
+
+ if (nChg < aText.getLength() && nChg >= 0)
+ m_nNextScriptChg = TextFrameIndex(
+ g_pBreakIt->GetBreakIter()->endOfScript(aText, nChg, nScript));
+ else
+ m_nNextScriptChg = TextFrameIndex(aText.getLength());
+
+ SwFontScript nTmp;
+ switch ( nScript ) {
+ case i18n::ScriptType::LATIN : nTmp = SwFontScript::Latin; break;
+ case i18n::ScriptType::ASIAN : nTmp = SwFontScript::CJK; break;
+ case i18n::ScriptType::COMPLEX : nTmp = SwFontScript::CTL; break;
+ default: nTmp = nActual;
+ }
+
+ // #i16354# Change script type for RTL text to CTL.
+ const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
+ // #i98418#
+ const sal_uInt8 nFieldDir = (IsNumberPortion() || IsFootnoteNumPortion())
+ ? rSI.GetDefaultDir()
+ : rSI.DirType(IsFollow() ? rInf.GetIdx() - m_nFieldLen : rInf.GetIdx());
+
+ {
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nFieldDir, nullptr, &nError );
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+ ubidi_getLogicalRun( pBidi, 0, &nEnd, &nCurrDir );
+ ubidi_close( pBidi );
+ const TextFrameIndex nNextDirChg(nEnd);
+ m_nNextScriptChg = std::min( m_nNextScriptChg, nNextDirChg );
+
+ // #i89825# change the script type also to CTL
+ // if there is no strong LTR char in the LTR run (numbers)
+ if (nCurrDir != UBIDI_RTL &&
+ (UBIDI_LTR != nFieldDir || i18n::ScriptType::COMPLEX == nScript))
+ {
+ nCurrDir = UBIDI_RTL;
+ for( sal_Int32 nCharIdx = 0; nCharIdx < nEnd; ++nCharIdx )
+ {
+ UCharDirection nCharDir = u_charDirection ( aText[ nCharIdx ]);
+ if ( nCharDir == U_LEFT_TO_RIGHT ||
+ nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
+ nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
+ {
+ nCurrDir = UBIDI_LTR;
+ break;
+ }
+ }
+ }
+
+ if (nCurrDir == UBIDI_RTL)
+ {
+ nTmp = SwFontScript::CTL;
+ // If we decided that this range was RTL after all and the
+ // previous range was complex but clipped to the start of this
+ // range, then extend it to be complex over the additional RTL range
+ if (nScript == i18n::ScriptType::COMPLEX)
+ m_nNextScriptChg = nNextDirChg;
+ }
+ }
+
+ // #i98418#
+ // keep determined script type for footnote portions as preferred script type.
+ // For footnote portions a font can not be created directly - see footnote
+ // portion format method.
+ if ( IsFootnotePortion() )
+ {
+ static_cast<SwFootnotePortion*>(this)->SetPreferredScriptType( nTmp );
+ }
+ else if ( nTmp != nActual )
+ {
+ if( !m_pFont )
+ m_pFont.reset( new SwFont( *rInf.GetFont() ) );
+ m_pFont->SetActual( nTmp );
+ }
+
+}
+
+bool SwFieldPortion::Format( SwTextFormatInfo &rInf )
+{
+ // Scope wegen aDiffText::DTOR!
+ bool bFull = false;
+ bool bEOL = false;
+ TextFrameIndex const nTextRest = TextFrameIndex(rInf.GetText().getLength()) - rInf.GetIdx();
+ {
+ TextFrameIndex nRest;
+ SwFieldSlot aDiffText( &rInf, this );
+ SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
+ aLayoutModeModifier.SetAuto();
+
+ // Field portion has to be split in several parts if
+ // 1. There are script/direction changes inside the field
+ // 2. There are portion breaks (tab, break) inside the field:
+ const TextFrameIndex nOldFullLen = rInf.GetLen();
+ TextFrameIndex nFullLen = rInf.ScanPortionEnd(rInf.GetIdx(), rInf.GetIdx() + nOldFullLen) - rInf.GetIdx();
+ if ( m_nNextScriptChg < nFullLen )
+ {
+ nFullLen = m_nNextScriptChg;
+ rInf.SetHookChar( 0 );
+ }
+ rInf.SetLen( nFullLen );
+
+ if (TextFrameIndex(COMPLETE_STRING) != rInf.GetUnderScorePos() &&
+ rInf.GetUnderScorePos() > rInf.GetIdx() )
+ rInf.SetUnderScorePos( rInf.GetIdx() );
+
+ if( m_pFont )
+ m_pFont->AllocFontCacheId( rInf.GetVsh(), m_pFont->GetActual() );
+
+ SwFontSave aSave( rInf, m_pFont.get() );
+
+ // Length must be 0: the length is set for bFull after format
+ // and passed along in nRest. Or else the old length would be
+ // retained and be used for nRest!
+ SetLen(TextFrameIndex(0));
+ TextFrameIndex const nFollow(IsFollow() ? TextFrameIndex(0) : m_nFieldLen);
+
+ // As odd is may seem: the query for GetLen() must return false due
+ // to the ExpandPortions _after_ aDiffText (see SoftHyphs), caused
+ // by SetFull.
+ if( !nFullLen )
+ {
+ // Don't Init(), as we need height and ascent
+ Width(0);
+ bFull = rInf.Width() <= rInf.GetPos().X();
+ }
+ else
+ {
+ TextFrameIndex const nOldLineStart = rInf.GetLineStart();
+ if( IsFollow() )
+ rInf.SetLineStart(TextFrameIndex(0));
+ rInf.SetNotEOL( nFullLen == nOldFullLen && nTextRest > nFollow );
+
+ // the height depending on the fields font is set,
+ // this is required for SwTextGuess::Guess
+ Height( rInf.GetTextHeight() + rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace() );
+ // If a kerning portion is inserted after our field portion,
+ // the ascent and height must be known
+ SetAscent( rInf.GetAscent() + rInf.GetFont()->GetTopBorderSpace() );
+ bFull = SwTextPortion::Format( rInf );
+ rInf.SetNotEOL( false );
+ rInf.SetLineStart( nOldLineStart );
+ }
+ TextFrameIndex const nTmpLen = GetLen();
+ bEOL = !nTmpLen && nFollow && bFull;
+ nRest = nOldFullLen - nTmpLen;
+
+ // The char is held in the first position
+ // Unconditionally after format!
+ SetLen( m_bNoLength ? TextFrameIndex(0) : nFollow );
+
+ if( nRest )
+ {
+ // aExpand has not yet been shortened; the new Ofst is a
+ // result of nRest
+ TextFrameIndex nNextOfst = TextFrameIndex(m_aExpand.getLength()) - nRest;
+
+ if ( IsQuoVadisPortion() )
+ nNextOfst = nNextOfst + TextFrameIndex(static_cast<SwQuoVadisPortion*>(this)->GetContText().getLength());
+
+ OUString aNew( m_aExpand.copy(sal_Int32(nNextOfst)) );
+ m_aExpand = m_aExpand.copy(0, sal_Int32(nNextOfst));
+
+ // These characters should not be contained in the follow
+ // field portion. They are handled via the HookChar mechanism.
+ const sal_Unicode nNew = !aNew.isEmpty() ? aNew[0] : 0;
+ auto IsHook = [](const sal_Unicode cNew) -> bool
+ {
+ switch (cNew)
+ {
+ case CH_BREAK:
+ case CH_TAB:
+ case CHAR_HARDHYPHEN: // non-breaking hyphen
+ case CHAR_SOFTHYPHEN:
+ case CHAR_HARDBLANK:
+ case CHAR_ZWSP:
+ case CHAR_WJ:
+ case CH_TXTATR_BREAKWORD:
+ case CH_TXTATR_INWORD:
+ {
+ return true;
+ }
+ default:
+ return false;
+ }
+ };
+ if (IsHook(nNew))
+ {
+ if (nNew == CH_BREAK)
+ {
+ bFull = true;
+ }
+ aNew = aNew.copy(1);
+ ++nNextOfst;
+ }
+
+ // Even if there is no more text left for a follow field,
+ // we have to build a follow field portion (without font),
+ // otherwise the HookChar mechanism would not work.
+ SwFieldPortion *pField = Clone( aNew );
+ if( !aNew.isEmpty() && !pField->GetFont() )
+ {
+ pField->SetFont( std::make_unique<SwFont>( *rInf.GetFont() ) );
+ }
+ if (IsFollow() || Compress())
+ { // empty this will be deleted in SwLineLayout::CalcLine()
+ // anyway so make sure pField doesn't have a stale flag
+ pField->SetFollow( true );
+ }
+ if (pField->Compress() && !std::all_of(std::u16string_view(aNew).begin(),
+ std::u16string_view(aNew).end(), IsHook))
+ { // empty pField will be deleted in SwLineLayout::CalcLine()
+ // anyway so make sure this one doesn't have a stale flag
+ SetHasFollow( true );
+ }
+
+ // For a newly created field, nNextOffset contains the Offset
+ // of its start of the original string
+ // If a FollowField is created when formatting, this FollowField's
+ // Offset is being held in nNextOffset
+ m_nNextOffset = m_nNextOffset + nNextOfst;
+ pField->SetNextOffset( m_nNextOffset );
+ rInf.SetRest( pField );
+ }
+ }
+
+ if( bEOL && rInf.GetLast() && !rInf.GetUnderflow() )
+ rInf.GetLast()->FormatEOL( rInf );
+ return bFull;
+}
+
+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_bContentControl)
+ {
+ // A very liberal use of the background
+ rInf.DrawViewOpt( *this, PortionType::Field );
+ SwExpandPortion::Paint( rInf );
+ }
+}
+
+bool SwFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ rText = m_aExpand;
+ if( rText.isEmpty() && rInf.OnWin() &&
+ !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() &&
+ rInf.GetOpt().IsFieldShadings() &&
+ !HasFollow() )
+ rText = " ";
+ return true;
+}
+
+void SwFieldPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), m_aExpand, GetWhichPor() );
+}
+
+void SwFieldPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFieldPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("expand"), BAD_CAST(m_aExpand.toUtf8().getStr()));
+
+ if (m_pFont)
+ {
+ m_pFont->dumpAsXml(pWriter);
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwPosSize SwFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwFontSave aSave( rInf, m_pFont.get() );
+ SwPosSize aSize( SwExpandPortion::GetTextSize( rInf ) );
+ return aSize;
+}
+
+SwFieldPortion *SwHiddenPortion::Clone(const OUString &rExpand ) const
+{
+ std::unique_ptr<SwFont> pNewFnt;
+ if( m_pFont )
+ pNewFnt.reset(new SwFont( *m_pFont ));
+ return new SwHiddenPortion( rExpand, std::move(pNewFnt) );
+}
+
+void SwHiddenPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ {
+ SwFontSave aSave( rInf, m_pFont.get() );
+ rInf.DrawViewOpt( *this, PortionType::Hidden );
+ SwExpandPortion::Paint( rInf );
+ }
+}
+
+bool SwHiddenPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ // Do not query for IsHidden()!
+ return SwFieldPortion::GetExpText( rInf, rText );
+}
+
+SwNumberPortion::SwNumberPortion( const OUString &rExpand,
+ std::unique_ptr<SwFont> pFont,
+ const bool bLft,
+ const bool bCntr,
+ const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive )
+ : SwFieldPortion(rExpand, std::move(pFont), TextFrameIndex(0))
+ , m_nFixWidth(0)
+ , m_nMinDist(nMinDst)
+ , mbLabelAlignmentPosAndSpaceModeActive(bLabelAlignmentPosAndSpaceModeActive)
+{
+ SetWhichPor( PortionType::Number );
+ SetLeft( bLft );
+ SetHide( false );
+ SetCenter( bCntr );
+}
+
+TextFrameIndex SwNumberPortion::GetModelPositionForViewPoint(const sal_uInt16) const
+{
+ return TextFrameIndex(0);
+}
+
+SwFieldPortion *SwNumberPortion::Clone( const OUString &rExpand ) const
+{
+ std::unique_ptr<SwFont> pNewFnt;
+ if( m_pFont )
+ pNewFnt.reset(new SwFont( *m_pFont ));
+
+ return new SwNumberPortion( rExpand, std::move(pNewFnt), IsLeft(), IsCenter(),
+ m_nMinDist, mbLabelAlignmentPosAndSpaceModeActive );
+}
+
+/**
+ * We can create multiple NumFields
+ * Tricky, if one enters enough previous-text in the dialog box
+ * to cause the line to overflow
+ * We need to keep the Fly's evasion tactics in mind
+ */
+bool SwNumberPortion::Format( SwTextFormatInfo &rInf )
+{
+ SetHide( false );
+ const bool bFull = SwFieldPortion::Format( rInf );
+ SetLen(TextFrameIndex(0));
+ // a numbering portion can be contained in a rotated portion!!!
+ m_nFixWidth = rInf.IsMulti() ? Height() : Width();
+ rInf.SetNumDone( !rInf.GetRest() );
+ if( rInf.IsNumDone() )
+ {
+// SetAscent( rInf.GetAscent() );
+ OSL_ENSURE( Height() && mnAscent, "NumberPortions without Height | Ascent" );
+
+ tools::Long nDiff( 0 );
+
+ if ( !mbLabelAlignmentPosAndSpaceModeActive )
+ {
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) &&
+ // #i32902#
+ !IsFootnoteNumPortion() )
+ {
+ nDiff = rInf.Left()
+ + rInf.GetTextFrame()->GetTextNodeForParaProps()->
+ GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset()
+ - rInf.First()
+ + rInf.ForcedLeftMargin();
+ }
+ else
+ {
+ nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
+ }
+ }
+ // The text part of the numbering should always at least
+ // start at the left margin
+ if( nDiff < 0 )
+ nDiff = 0;
+ else if ( nDiff > rInf.X() )
+ nDiff -= rInf.X();
+ else
+ nDiff = 0;
+
+ if( nDiff < m_nFixWidth + m_nMinDist )
+ nDiff = m_nFixWidth + m_nMinDist;
+
+ // Numbering evades the Fly, no nDiff in the second round
+ // Tricky special case: FlyFrame is in an Area we're just about to
+ // acquire
+ // The NumberPortion is marked as hidden
+ const bool bFly = rInf.GetFly() ||
+ ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() );
+ if( nDiff > rInf.Width() )
+ {
+ nDiff = rInf.Width();
+ if ( bFly )
+ SetHide( true );
+ }
+
+ // A numbering portion can be inside a SwRotatedPortion. Then the
+ // Height has to be changed
+ if ( rInf.IsMulti() )
+ {
+ if ( Height() < nDiff )
+ Height( nDiff );
+ }
+ else if( Width() < nDiff )
+ Width( nDiff );
+ }
+ return bFull;
+}
+
+
+/**
+ * A FormatEOL indicates that the subsequent text did not fit onto
+ * the line anymore. In order for the Numbering to follow through,
+ * we hide this NumberPortion
+ */
+void SwNumberPortion::FormatEOL( SwTextFormatInfo& )
+{
+
+ // This caused trouble with flys anchored as characters.
+ // If one of these is numbered but does not fit to the line,
+ // it calls this function, causing a loop because both the number
+ // portion and the fly portion go to the next line
+// SetHide( true );
+}
+
+
+/**
+ * A hidden NumberPortion is not displayed, unless there are TextPortions in
+ * this line or there's just one line at all
+ */
+void SwNumberPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() )
+ {
+ SwLinePortion *pTmp = GetNextPortion();
+ while ( pTmp && !pTmp->InTextGrp() )
+ pTmp = pTmp->GetNextPortion();
+ if ( !pTmp )
+ return;
+ }
+
+ // calculate the width of the number portion, including follows
+ const sal_uInt16 nOldWidth = Width();
+ sal_uInt16 nSumWidth = 0;
+ sal_uInt16 nOffset = 0;
+
+ const SwLinePortion* pTmp = this;
+ while ( pTmp && pTmp->InNumberGrp() )
+ {
+ nSumWidth = nSumWidth + pTmp->Width();
+ if ( static_cast<const SwNumberPortion*>(pTmp)->HasFollow() )
+ pTmp = pTmp->GetNextPortion();
+ else
+ {
+ nOffset = pTmp->Width() - static_cast<const SwNumberPortion*>(pTmp)->m_nFixWidth;
+ break;
+ }
+ }
+
+ // The master portion takes care for painting the background of the
+ // follow field portions
+ if ( ! IsFollow() )
+ {
+ SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this);
+ pThis->Width( nSumWidth );
+ rInf.DrawViewOpt( *this, PortionType::Number );
+ pThis->Width( nOldWidth );
+ }
+
+ if( m_aExpand.isEmpty() )
+ return;
+
+ const SwFont *pTmpFnt = rInf.GetFont();
+ bool bPaintSpace = ( LINESTYLE_NONE != pTmpFnt->GetUnderline() ||
+ LINESTYLE_NONE != pTmpFnt->GetOverline() ||
+ STRIKEOUT_NONE != pTmpFnt->GetStrikeout() ) &&
+ !pTmpFnt->IsWordLineMode();
+ if( bPaintSpace && m_pFont )
+ bPaintSpace = ( LINESTYLE_NONE != m_pFont->GetUnderline() ||
+ LINESTYLE_NONE != m_pFont->GetOverline() ||
+ STRIKEOUT_NONE != m_pFont->GetStrikeout() ) &&
+ !m_pFont->IsWordLineMode();
+
+ SwFontSave aSave( rInf, m_pFont.get() );
+
+ if( m_nFixWidth == Width() && ! HasFollow() )
+ SwExpandPortion::Paint( rInf );
+ else
+ {
+ // logical const: reset width
+ SwNumberPortion *pThis = const_cast<SwNumberPortion*>(this);
+ bPaintSpace = bPaintSpace && m_nFixWidth < nOldWidth;
+ sal_uInt16 nSpaceOffs = m_nFixWidth;
+ pThis->Width( m_nFixWidth );
+
+ if( ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) ||
+ ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() ) )
+ SwExpandPortion::Paint( rInf );
+ else
+ {
+ SwTextPaintInfo aInf( rInf );
+ if( nOffset < m_nMinDist )
+ nOffset = 0;
+ else
+ {
+ if( IsCenter() )
+ {
+ /* #110778# a / 2 * 2 == a is not a tautology */
+ sal_uInt16 nTmpOffset = nOffset;
+ nOffset /= 2;
+ if( nOffset < m_nMinDist )
+ nOffset = nTmpOffset - m_nMinDist;
+ }
+ else
+ nOffset = nOffset - m_nMinDist;
+ }
+ aInf.X( aInf.X() + nOffset );
+ SwExpandPortion::Paint( aInf );
+ if( bPaintSpace )
+ nSpaceOffs = nSpaceOffs + nOffset;
+ }
+ if( bPaintSpace && nOldWidth > nSpaceOffs )
+ {
+ SwTextPaintInfo aInf( rInf );
+ aInf.X( aInf.X() + nSpaceOffs );
+
+ // #i53199# Adjust position of underline:
+ if ( rInf.GetUnderFnt() )
+ {
+ const Point aNewPos( aInf.GetPos().X(), rInf.GetUnderFnt()->GetPos().Y() );
+ rInf.GetUnderFnt()->SetPos( aNewPos );
+ }
+
+ pThis->Width( nOldWidth - nSpaceOffs + 12 );
+ {
+ SwTextSlot aDiffText( &aInf, this, true, false, " " );
+ aInf.DrawText( *this, aInf.GetLen(), true );
+ }
+ }
+ pThis->Width( nOldWidth );
+ }
+}
+
+SwBulletPortion::SwBulletPortion( const sal_UCS4 cBullet,
+ std::u16string_view rBulletFollowedBy,
+ std::unique_ptr<SwFont> pFont,
+ const bool bLft,
+ const bool bCntr,
+ const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive )
+ : SwNumberPortion( OUString(&cBullet, 1) + rBulletFollowedBy,
+ std::move(pFont), bLft, bCntr, nMinDst,
+ bLabelAlignmentPosAndSpaceModeActive )
+{
+ SetWhichPor( PortionType::Bullet );
+}
+
+#define GRFNUM_SECURE 10
+
+SwGrfNumPortion::SwGrfNumPortion(
+ const OUString& rGraphicFollowedBy,
+ const SvxBrushItem* pGrfBrush, OUString const & referer,
+ const SwFormatVertOrient* pGrfOrient, const Size& rGrfSize,
+ const bool bLft, const bool bCntr, const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive ) :
+ SwNumberPortion( rGraphicFollowedBy, nullptr, bLft, bCntr, nMinDst,
+ bLabelAlignmentPosAndSpaceModeActive ),
+ m_pBrush( new SvxBrushItem(RES_BACKGROUND) ), m_nId( 0 )
+{
+ SetWhichPor( PortionType::GrfNum );
+ SetAnimated( false );
+ m_bReplace = false;
+ if( pGrfBrush )
+ {
+ m_pBrush.reset(pGrfBrush->Clone());
+ const Graphic* pGraph = pGrfBrush->GetGraphic(referer);
+ if( pGraph )
+ SetAnimated( pGraph->IsAnimated() );
+ else
+ m_bReplace = true;
+ }
+ if( pGrfOrient )
+ {
+ m_nYPos = pGrfOrient->GetPos();
+ m_eOrient = pGrfOrient->GetVertOrient();
+ }
+ else
+ {
+ m_nYPos = 0;
+ m_eOrient = text::VertOrientation::TOP;
+ }
+ Width( rGrfSize.Width() + 2 * GRFNUM_SECURE );
+ m_nFixWidth = Width();
+ m_nGrfHeight = rGrfSize.Height() + 2 * GRFNUM_SECURE;
+ Height( sal_uInt16(m_nGrfHeight) );
+ m_bNoPaint = false;
+}
+
+SwGrfNumPortion::~SwGrfNumPortion()
+{
+ if ( IsAnimated() )
+ {
+ Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
+ if (pGraph)
+ pGraph->StopAnimation( nullptr, m_nId );
+ }
+ m_pBrush.reset();
+}
+
+void SwGrfNumPortion::StopAnimation( const OutputDevice* pOut )
+{
+ if ( IsAnimated() )
+ {
+ Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
+ if (pGraph)
+ pGraph->StopAnimation( pOut, m_nId );
+ }
+}
+
+bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf )
+{
+ SetHide( false );
+// Width( nFixWidth );
+ sal_uInt16 nFollowedByWidth( 0 );
+ if ( mbLabelAlignmentPosAndSpaceModeActive )
+ {
+ SwFieldPortion::Format( rInf );
+ nFollowedByWidth = Width();
+ SetLen(TextFrameIndex(0));
+ }
+ Width( m_nFixWidth + nFollowedByWidth );
+ const bool bFull = rInf.Width() < rInf.X() + Width();
+ const bool bFly = rInf.GetFly() ||
+ ( rInf.GetLast() && rInf.GetLast()->IsFlyPortion() );
+ SetAscent( GetRelPos() > 0 ? GetRelPos() : 0 );
+ if( GetAscent() > Height() )
+ Height( GetAscent() );
+
+ if( bFull )
+ {
+ Width( rInf.Width() - rInf.X() );
+ if( bFly )
+ {
+ SetLen(TextFrameIndex(0));
+ m_bNoPaint = true;
+ rInf.SetNumDone( false );
+ return true;
+ }
+ }
+ rInf.SetNumDone( true );
+// long nDiff = rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
+ tools::Long nDiff = mbLabelAlignmentPosAndSpaceModeActive
+ ? 0
+ : rInf.Left() - rInf.First() + rInf.ForcedLeftMargin();
+ // The TextPortion should at least always start on the
+ // left margin
+ if( nDiff < 0 )
+ nDiff = 0;
+ else if ( nDiff > rInf.X() )
+ nDiff -= rInf.X();
+ if( nDiff < m_nFixWidth + m_nMinDist )
+ nDiff = m_nFixWidth + m_nMinDist;
+
+ // Numbering evades Fly, no nDiff in the second round
+ // Tricky special case: FlyFrame is in the Area we were just
+ // about to get a hold of.
+ // The NumberPortion is marked as hidden
+ if( nDiff > rInf.Width() )
+ {
+ nDiff = rInf.Width();
+ if( bFly )
+ SetHide( true );
+ }
+
+ if( Width() < nDiff )
+ Width( nDiff );
+ return bFull;
+}
+
+
+/**
+ * A hidden NumberPortion is not displayed, unless there are TextPortions in
+ * this line or there's only one line at all
+ */
+void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( m_bNoPaint )
+ return;
+ if ( IsHide() && rInf.GetParaPortion() && rInf.GetParaPortion()->GetNext() )
+ {
+ SwLinePortion *pTmp = GetNextPortion();
+ while ( pTmp && !pTmp->InTextGrp() )
+ pTmp = pTmp->GetNextPortion();
+ if ( !pTmp )
+ return;
+ }
+ Point aPos( rInf.X() + GRFNUM_SECURE, rInf.Y() - GetRelPos() + GRFNUM_SECURE );
+ tools::Long nTmpWidth = std::max( tools::Long(0), static_cast<tools::Long>(m_nFixWidth - 2 * GRFNUM_SECURE) );
+ Size aSize( nTmpWidth, GetGrfHeight() - 2 * GRFNUM_SECURE );
+
+ const bool bTmpLeft = mbLabelAlignmentPosAndSpaceModeActive ||
+ ( IsLeft() && ! rInf.GetTextFrame()->IsRightToLeft() ) ||
+ ( ! IsLeft() && ! IsCenter() && rInf.GetTextFrame()->IsRightToLeft() );
+
+ if( m_nFixWidth < Width() && !bTmpLeft )
+ {
+ sal_uInt16 nOffset = Width() - m_nFixWidth;
+ if( nOffset < m_nMinDist )
+ nOffset = 0;
+ else
+ {
+ if( IsCenter() )
+ {
+ nOffset /= 2;
+ if( nOffset < m_nMinDist )
+ nOffset = Width() - m_nFixWidth - m_nMinDist;
+ }
+ else
+ nOffset = nOffset - m_nMinDist;
+ }
+ aPos.AdjustX(nOffset );
+ }
+
+ if( m_bReplace )
+ {
+ const tools::Long nTmpH = GetNextPortion() ? GetNextPortion()->GetAscent() : 120;
+ aSize = Size( nTmpH, nTmpH );
+ aPos.setY( rInf.Y() - nTmpH );
+ }
+ SwRect aTmp( aPos, aSize );
+
+ bool bDraw = true;
+
+ if ( IsAnimated() )
+ {
+ bDraw = !rInf.GetOpt().IsGraphic();
+ if( !m_nId )
+ {
+ SetId( reinterpret_cast<sal_IntPtr>( rInf.GetTextFrame() ) );
+ rInf.GetTextFrame()->SetAnimation();
+ }
+ if( aTmp.Overlaps( rInf.GetPaintRect() ) && !bDraw )
+ {
+ rInf.NoteAnimation();
+ const SwViewShell* pViewShell = rInf.GetVsh();
+
+ // virtual device, not pdf export
+ if( OUTDEV_VIRDEV == rInf.GetOut()->GetOutDevType() &&
+ pViewShell && pViewShell->GetWin() )
+ {
+ Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
+ if (pGraph)
+ pGraph->StopAnimation(nullptr,m_nId);
+ rInf.GetTextFrame()->getRootFrame()->GetCurrShell()->InvalidateWindows( aTmp );
+ }
+
+ else if ( pViewShell &&
+ !pViewShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() &&
+ !pViewShell->IsPreview() &&
+ // #i9684# Stop animation during printing/pdf export.
+ pViewShell->GetWin() )
+ {
+ Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
+ if (pGraph)
+ {
+ const OutputDevice* pOut = rInf.GetOut();
+ assert(pOut);
+ pGraph->StartAnimation(
+ *const_cast<OutputDevice*>(pOut), aPos, aSize, m_nId);
+ }
+ }
+
+ // pdf export, printing, preview, stop animations...
+ else
+ bDraw = true;
+ }
+ if( bDraw )
+ {
+
+ Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic());
+ if (pGraph)
+ pGraph->StopAnimation( nullptr, m_nId );
+ }
+ }
+
+ SwRect aRepaint( rInf.GetPaintRect() );
+ const SwTextFrame& rFrame = *rInf.GetTextFrame();
+ if( rFrame.IsVertical() )
+ {
+ rFrame.SwitchHorizontalToVertical( aTmp );
+ rFrame.SwitchHorizontalToVertical( aRepaint );
+ }
+
+ if( rFrame.IsRightToLeft() )
+ {
+ rFrame.SwitchLTRtoRTL( aTmp );
+ rFrame.SwitchLTRtoRTL( aRepaint );
+ }
+
+ if( bDraw && aTmp.HasArea() )
+ {
+ const OutputDevice* pOut = rInf.GetOut();
+ assert(pOut);
+ DrawGraphic( m_pBrush.get(), *const_cast<OutputDevice*>(pOut),
+ aTmp, aRepaint, m_bReplace ? GRFNUM_REPLACE : GRFNUM_YES );
+ }
+}
+
+void SwGrfNumPortion::SetBase( tools::Long nLnAscent, tools::Long nLnDescent,
+ tools::Long nFlyAsc, tools::Long nFlyDesc )
+{
+ if ( GetOrient() == text::VertOrientation::NONE )
+ return;
+
+ SetRelPos( 0 );
+ if ( GetOrient() == text::VertOrientation::CENTER )
+ SetRelPos( GetGrfHeight() / 2 );
+ else if ( GetOrient() == text::VertOrientation::TOP )
+ SetRelPos( GetGrfHeight() - GRFNUM_SECURE );
+ else if ( GetOrient() == text::VertOrientation::BOTTOM )
+ ;
+ else if ( GetOrient() == text::VertOrientation::CHAR_CENTER )
+ SetRelPos( ( GetGrfHeight() + nLnAscent - nLnDescent ) / 2 );
+ else if ( GetOrient() == text::VertOrientation::CHAR_TOP )
+ SetRelPos( nLnAscent );
+ else if ( GetOrient() == text::VertOrientation::CHAR_BOTTOM )
+ SetRelPos( GetGrfHeight() - nLnDescent );
+ else
+ {
+ if( GetGrfHeight() >= nFlyAsc + nFlyDesc )
+ {
+ // If I'm as large as the line, I do not need to adjust
+ // at the line; I'll leave the max. ascent unchanged
+ SetRelPos( nFlyAsc );
+ }
+ else if ( GetOrient() == text::VertOrientation::LINE_CENTER )
+ SetRelPos( ( GetGrfHeight() + nFlyAsc - nFlyDesc ) / 2 );
+ else if ( GetOrient() == text::VertOrientation::LINE_TOP )
+ SetRelPos( nFlyAsc );
+ else if ( GetOrient() == text::VertOrientation::LINE_BOTTOM )
+ SetRelPos( GetGrfHeight() - nFlyDesc );
+ }
+}
+
+void SwTextFrame::StopAnimation( const OutputDevice* pOut )
+{
+ OSL_ENSURE( HasAnimation(), "SwTextFrame::StopAnimation: Which Animation?" );
+ if( !HasPara() )
+ return;
+
+ SwLineLayout *pLine = GetPara();
+ while( pLine )
+ {
+ SwLinePortion *pPor = pLine->GetNextPortion();
+ while( pPor )
+ {
+ if( pPor->IsGrfNumPortion() )
+ static_cast<SwGrfNumPortion*>(pPor)->StopAnimation( pOut );
+ // The NumberPortion is always at the first char,
+ // which means we can cancel as soon as we've reached a portion
+ // with a length > 0
+ pPor = pPor->GetLen() ? nullptr : pPor->GetNextPortion();
+ }
+ pLine = pLine->GetLen() ? nullptr : pLine->GetNext();
+ }
+}
+
+/**
+ * Initializes the script array and clears the width array
+ */
+SwCombinedPortion::SwCombinedPortion( const OUString &rText )
+ : SwFieldPortion( rText )
+ , m_aWidth{ static_cast<sal_uInt16>(0),
+ static_cast<sal_uInt16>(0),
+ static_cast<sal_uInt16>(0) }
+ , m_nUpPos(0)
+ , m_nLowPos(0)
+ , m_nProportion(55)
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::Combined );
+ if( m_aExpand.getLength() > 6 )
+ m_aExpand = m_aExpand.copy( 0, 6 );
+
+ // Initialization of the scripttype array,
+ // the arrays of width and position are filled by the format function
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ SwFontScript nScr = SW_SCRIPTS;
+ for( sal_Int32 i = 0; i < rText.getLength(); ++i )
+ {
+ switch ( g_pBreakIt->GetBreakIter()->getScriptType( rText, i ) ) {
+ case i18n::ScriptType::LATIN : nScr = SwFontScript::Latin; break;
+ case i18n::ScriptType::ASIAN : nScr = SwFontScript::CJK; break;
+ case i18n::ScriptType::COMPLEX : nScr = SwFontScript::CTL; break;
+ }
+ m_aScrType[i] = nScr;
+ }
+}
+
+void SwCombinedPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?");
+ if( !Width() )
+ return;
+
+ rInf.DrawBackBrush( *this );
+ rInf.DrawViewOpt( *this, PortionType::Field );
+
+ // do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ const sal_Int32 nCount = m_aExpand.getLength();
+ if( !nCount )
+ return;
+ OSL_ENSURE( nCount < 7, "Too much combined characters" );
+
+ // the first character of the second row
+ const sal_Int32 nTop = ( nCount + 1 ) / 2;
+
+ SwFont aTmpFont( *rInf.GetFont() );
+ aTmpFont.SetProportion( m_nProportion ); // a smaller font
+ SwFontSave aFontSave( rInf, &aTmpFont );
+
+ Point aOldPos = rInf.GetPos();
+ Point aOutPos( aOldPos.X(), aOldPos.Y() - m_nUpPos );// Y of the first row
+ for( sal_Int32 i = 0 ; i < nCount; ++i )
+ {
+ if( i == nTop ) // change the row
+ aOutPos.setY( aOldPos.Y() + m_nLowPos ); // Y of the second row
+ aOutPos.setX( aOldPos.X() + m_aPos[i] ); // X position
+ const SwFontScript nAct = m_aScrType[i]; // script type
+ aTmpFont.SetActual( nAct );
+
+ // if there're more than 4 characters to display, we choose fonts
+ // with 2/3 of the original font width.
+ if( m_aWidth[ nAct ] )
+ {
+ Size aTmpSz = aTmpFont.GetSize( nAct );
+ if( aTmpSz.Width() != m_aWidth[ nAct ] )
+ {
+ aTmpSz.setWidth( m_aWidth[ nAct ] );
+ aTmpFont.SetSize( aTmpSz, nAct );
+ }
+ }
+ const_cast<SwTextPaintInfo&>(rInf).SetPos( aOutPos );
+ rInf.DrawText(m_aExpand, *this, TextFrameIndex(i), TextFrameIndex(1));
+ }
+ // rInf is const, so we have to take back our manipulations
+ const_cast<SwTextPaintInfo&>(rInf).SetPos( aOldPos );
+
+}
+
+bool SwCombinedPortion::Format( SwTextFormatInfo &rInf )
+{
+ const sal_Int32 nCount = m_aExpand.getLength();
+ if( !nCount )
+ {
+ Width( 0 );
+ return false;
+ }
+
+ OSL_ENSURE( nCount < 7, "Too much combined characters" );
+
+ // If there are leading "weak"-scripttyped characters in this portion,
+ // they get the actual scripttype.
+ for( sal_Int32 i = 0; i < nCount && SW_SCRIPTS == m_aScrType[i]; ++i )
+ m_aScrType[i] = rInf.GetFont()->GetActual();
+ if( nCount > 4 )
+ {
+ // more than four? Ok, then we need the 2/3 font width
+ for( sal_Int32 i = 0; i < m_aExpand.getLength(); ++i )
+ {
+ OSL_ENSURE( m_aScrType[i] < SW_SCRIPTS, "Combined: Script fault" );
+ if( !m_aWidth[ m_aScrType[i] ] )
+ {
+ rInf.GetOut()->SetFont( rInf.GetFont()->GetFnt( m_aScrType[i] ) );
+ m_aWidth[ m_aScrType[i] ] =
+ o3tl::narrowing<sal_uInt16>(2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3);
+ }
+ }
+ }
+
+ const sal_Int32 nTop = ( nCount + 1 ) / 2; // the first character of the second line
+ SwViewShell *pSh = rInf.GetTextFrame()->getRootFrame()->GetCurrShell();
+ SwFont aTmpFont( *rInf.GetFont() );
+ SwFontSave aFontSave( rInf, &aTmpFont );
+ m_nProportion = 55;
+ // In nMainAscent/Descent we store the ascent and descent
+ // of the original surrounding font
+ sal_uInt16 nMaxDescent, nMaxAscent, nMaxWidth;
+ sal_uInt16 nMainDescent = rInf.GetFont()->GetHeight( pSh, *rInf.GetOut() );
+ const sal_uInt16 nMainAscent = rInf.GetFont()->GetAscent( pSh, *rInf.GetOut() );
+ nMainDescent = nMainDescent - nMainAscent;
+ // we start with a 50% font, but if we notice that the combined portion
+ // becomes bigger than the surrounding font, we check 45% and maybe 40%.
+ do
+ {
+ m_nProportion -= 5;
+ aTmpFont.SetProportion( m_nProportion );
+ memset( &m_aPos, 0, sizeof(m_aPos) );
+ nMaxDescent = 0;
+ nMaxAscent = 0;
+ nMaxWidth = 0;
+ m_nUpPos = m_nLowPos = 0;
+
+ // Now we get the width of all characters.
+ // The ascent and the width of the first line are stored in the
+ // ascent member of the portion, the descent in nLowPos.
+ // The ascent, descent and width of the second line are stored in the
+ // local nMaxAscent, nMaxDescent and nMaxWidth variables.
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ SwFontScript nScrp = m_aScrType[i];
+ aTmpFont.SetActual( nScrp );
+ if( m_aWidth[ nScrp ] )
+ {
+ Size aFontSize( aTmpFont.GetSize( nScrp ) );
+ aFontSize.setWidth( m_aWidth[ nScrp ] );
+ aTmpFont.SetSize( aFontSize, nScrp );
+ }
+
+ SwDrawTextInfo aDrawInf(pSh, *rInf.GetOut(), m_aExpand, i, 1);
+ Size aSize = aTmpFont.GetTextSize_( aDrawInf );
+ const sal_uInt16 nAsc = aTmpFont.GetAscent( pSh, *rInf.GetOut() );
+ m_aPos[ i ] = o3tl::narrowing<sal_uInt16>(aSize.Width());
+ if( i == nTop ) // enter the second line
+ {
+ m_nLowPos = nMaxDescent;
+ Height( nMaxDescent + nMaxAscent );
+ Width( nMaxWidth );
+ SetAscent( nMaxAscent );
+ nMaxAscent = 0;
+ nMaxDescent = 0;
+ nMaxWidth = 0;
+ }
+ nMaxWidth = nMaxWidth + m_aPos[ i ];
+ if( nAsc > nMaxAscent )
+ nMaxAscent = nAsc;
+ if( aSize.Height() - nAsc > nMaxDescent )
+ nMaxDescent = aSize.Height() - nAsc;
+ }
+ // for one or two characters we double the width of the portion
+ if( nCount < 3 )
+ {
+ nMaxWidth *= 2;
+ Width( 2*Width() );
+ if( nCount < 2 )
+ {
+ Height( nMaxAscent + nMaxDescent );
+ m_nLowPos = nMaxDescent;
+ }
+ }
+ Height( Height() + nMaxDescent + nMaxAscent );
+ m_nUpPos = nMaxAscent;
+ SetAscent( Height() - nMaxDescent - m_nLowPos );
+ } while( m_nProportion > 40 && ( GetAscent() > nMainAscent ||
+ Height() - GetAscent() > nMainDescent ) );
+ // if the combined portion is smaller than the surrounding text,
+ // the portion grows. This looks better, if there's a character background.
+ if( GetAscent() < nMainAscent )
+ {
+ Height( Height() + nMainAscent - GetAscent() );
+ SetAscent( nMainAscent );
+ }
+ if( Height() < nMainAscent + nMainDescent )
+ Height( nMainAscent + nMainDescent );
+
+ // We calculate the x positions of the characters in both lines...
+ sal_uInt16 nTopDiff = 0;
+ sal_uInt16 nBotDiff = 0;
+ if( nMaxWidth > Width() )
+ {
+ nTopDiff = ( nMaxWidth - Width() ) / 2;
+ Width( nMaxWidth );
+ }
+ else
+ nBotDiff = ( Width() - nMaxWidth ) / 2;
+ switch( nTop)
+ {
+ case 3: m_aPos[1] = m_aPos[0] + nTopDiff;
+ [[fallthrough]];
+ case 2: m_aPos[nTop-1] = Width() - m_aPos[nTop-1];
+ }
+ m_aPos[0] = 0;
+ switch( nCount )
+ {
+ case 5: m_aPos[4] = m_aPos[3] + nBotDiff;
+ [[fallthrough]];
+ case 3: m_aPos[nTop] = nBotDiff; break;
+ case 6: m_aPos[4] = m_aPos[3] + nBotDiff;
+ [[fallthrough]];
+ case 4: m_aPos[nTop] = 0;
+ [[fallthrough]];
+ case 2: m_aPos[nCount-1] = Width() - m_aPos[nCount-1];
+ }
+
+ // Does the combined portion fit the line?
+ const bool bFull = rInf.Width() < rInf.X() + Width();
+ if( bFull )
+ {
+ if( rInf.GetLineStart() == rInf.GetIdx() && (!rInf.GetLast()->InFieldGrp()
+ || !static_cast<SwFieldPortion*>(rInf.GetLast())->IsFollow() ) )
+ Width( rInf.Width() - rInf.X() );
+ else
+ {
+ Truncate();
+ Width( 0 );
+ SetLen(TextFrameIndex(0));
+ if( rInf.GetLast() )
+ rInf.GetLast()->FormatEOL( rInf );
+ }
+ }
+ return bFull;
+}
+
+sal_uInt16 SwCombinedPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ if( !GetLen() ) // for the dummy part at the end of the line, where
+ return 0; // the combined portion doesn't fit.
+ return SwFieldPortion::GetViewWidth( rInf );
+}
+
+SwFieldPortion *SwFieldFormDropDownPortion::Clone(const OUString &rExpand) const
+{
+ return new SwFieldFormDropDownPortion(m_pFieldMark, rExpand);
+}
+
+void SwFieldFormDropDownPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ SwFieldPortion::Paint( rInf );
+
+ ::sw::mark::DropDownFieldmark* pDropDownField = dynamic_cast< ::sw::mark::DropDownFieldmark* >(m_pFieldMark);
+ if(pDropDownField)
+ {
+ SwRect aPaintArea;
+ rInf.CalcRect( *this, &aPaintArea );
+ pDropDownField->SetPortionPaintArea(aPaintArea);
+ }
+}
+
+SwFieldPortion *SwFieldFormDatePortion::Clone(const OUString &/*rExpand*/) const
+{
+ return new SwFieldFormDatePortion(m_pFieldMark, m_bStart);
+}
+
+void SwFieldFormDatePortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ SwFieldPortion::Paint( rInf );
+
+ ::sw::mark::DateFieldmark* pDateField = dynamic_cast< ::sw::mark::DateFieldmark* >(m_pFieldMark);
+ if(pDateField)
+ {
+ SwRect aPaintArea;
+ rInf.CalcRect( *this, &aPaintArea );
+ if(m_bStart)
+ pDateField->SetPortionPaintAreaStart(aPaintArea);
+ else
+ pDateField->SetPortionPaintAreaEnd(aPaintArea);
+ }
+}
+
+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
new file mode 100644
index 0000000000..b923729424
--- /dev/null
+++ b/sw/source/core/text/porfld.hxx
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <swtypes.hxx>
+#include <swfont.hxx>
+#include "porexp.hxx"
+#include <o3tl/enumarray.hxx>
+
+class SvxBrushItem;
+class SwFormatVertOrient;
+
+class SwFieldPortion : public SwExpandPortion
+{
+ friend class SwTextFormatter;
+protected:
+ OUString m_aExpand; // The expanded field
+ std::unique_ptr<SwFont> m_pFont; // For multi-line fields
+ TextFrameIndex m_nNextOffset; // Offset of the follow in the original string
+ TextFrameIndex m_nNextScriptChg;
+ TextFrameIndex m_nFieldLen; //< Length of field text, 1 for normal fields, any number for input fields
+ // TODO ^ do we need this as member or is base class len enough?
+ sal_uInt16 m_nViewWidth; // Screen width for empty fields
+ bool m_bFollow : 1; // 2nd or later part of a field
+ bool m_bLeft : 1; // Used by SwNumberPortion
+ bool m_bHide : 1; // Used by SwNumberPortion
+ bool m_bCenter : 1; // Used by SwNumberPortion
+ bool m_bHasFollow : 1; // Continues on the next line
+ bool m_bAnimated : 1; // Used by SwGrfNumPortion
+ bool m_bNoPaint : 1; // Used by SwGrfNumPortion
+ bool m_bReplace : 1; // Used by SwGrfNumPortion
+ bool m_bNoLength : 1; // HACK for meta suffix (no CH_TXTATR)
+ bool m_bContentControl = false;
+
+ void SetFont( std::unique_ptr<SwFont> pNew ) { m_pFont = std::move(pNew); }
+ bool IsNoLength() const { return m_bNoLength; }
+ void SetNoLength() { m_bNoLength = true; }
+
+public:
+ SwFieldPortion( const SwFieldPortion& rField );
+ SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, TextFrameIndex nLen = TextFrameIndex(1));
+ virtual ~SwFieldPortion() override;
+
+ void TakeNextOffset( const SwFieldPortion* pField );
+ void CheckScript( const SwTextSizeInfo &rInf );
+ bool HasFont() const { return nullptr != m_pFont; }
+ // #i89179# - made public
+ const SwFont *GetFont() const { return m_pFont.get(); }
+
+ const OUString& GetExp() const { return m_aExpand; }
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+
+ // Empty fields are also allowed
+ virtual SwLinePortion *Compress() override;
+
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override;
+
+ bool IsFollow() const { return m_bFollow; }
+ void SetFollow( bool bNew ) { m_bFollow = bNew; }
+
+ bool IsLeft() const { return m_bLeft; }
+ void SetLeft( bool bNew ) { m_bLeft = bNew; }
+
+ bool IsHide() const { return m_bHide; }
+ void SetHide( bool bNew ) { m_bHide = bNew; }
+
+ bool IsCenter() const { return m_bCenter; }
+ void SetCenter( bool bNew ) { m_bCenter = bNew; }
+
+ bool HasFollow() const { return m_bHasFollow; }
+ void SetHasFollow( bool bNew ) { m_bHasFollow = bNew; }
+
+ TextFrameIndex GetNextOffset() const { return m_nNextOffset; }
+ void SetNextOffset(TextFrameIndex nNew) { m_nNextOffset = nNew; }
+
+ TextFrameIndex GetFieldLen() const { return m_nFieldLen; }
+
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const;
+
+ // Extra GetTextSize because of pFnt
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void SetContentControl(bool bContentControl) { m_bContentControl = bContentControl; }
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+/**
+ * Distinguish only for painting/hide
+ */
+class SwHiddenPortion : public SwFieldPortion
+{
+public:
+ SwHiddenPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFntL = nullptr )
+ : SwFieldPortion( rExpand, std::move(pFntL) )
+ { SetLen(TextFrameIndex(1)); SetWhichPor( PortionType::Hidden ); }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const override;
+};
+
+class SwNumberPortion : public SwFieldPortion
+{
+protected:
+ sal_uInt16 m_nFixWidth; // See Glues
+ sal_uInt16 m_nMinDist; // Minimal distance to the text
+ bool mbLabelAlignmentPosAndSpaceModeActive;
+
+public:
+ SwNumberPortion( const OUString &rExpand,
+ std::unique_ptr<SwFont> pFnt,
+ const bool bLeft,
+ const bool bCenter,
+ const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const override;
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+};
+
+class SwBulletPortion : public SwNumberPortion
+{
+public:
+ SwBulletPortion( const sal_UCS4 cCh,
+ std::u16string_view rBulletFollowedBy,
+ std::unique_ptr<SwFont> pFnt,
+ const bool bLeft,
+ const bool bCenter,
+ const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive );
+};
+
+class SwGrfNumPortion : public SwNumberPortion
+{
+ std::unique_ptr<SvxBrushItem> m_pBrush;
+ tools::Long m_nId; // For StopAnimation
+ SwTwips m_nYPos; // _Always_ contains the current RelPos
+ SwTwips m_nGrfHeight;
+ sal_Int16 m_eOrient;
+public:
+ SwGrfNumPortion( const OUString& rGraphicFollowedBy,
+ const SvxBrushItem* pGrfBrush,
+ OUString const & referer,
+ const SwFormatVertOrient* pGrfOrient,
+ const Size& rGrfSize,
+ const bool bLeft,
+ const bool bCenter,
+ const sal_uInt16 nMinDst,
+ const bool bLabelAlignmentPosAndSpaceModeActive );
+ virtual ~SwGrfNumPortion() override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ void SetBase( tools::Long nLnAscent, tools::Long nLnDescent,
+ tools::Long nFlyAscent, tools::Long nFlyDescent );
+
+ void StopAnimation( const OutputDevice* pOut );
+
+ bool IsAnimated() const { return m_bAnimated; }
+ void SetAnimated( bool bNew ) { m_bAnimated = bNew; }
+ void SetRelPos( SwTwips nNew ) { m_nYPos = nNew; }
+ void SetId( tools::Long nNew ) const
+ { const_cast<SwGrfNumPortion*>(this)->m_nId = nNew; }
+ SwTwips GetRelPos() const { return m_nYPos; }
+ SwTwips GetGrfHeight() const { return m_nGrfHeight; }
+ sal_Int16 GetOrient() const { return m_eOrient; }
+};
+
+/**
+ * Used in for asian layout specialities to display up to six characters
+ * in 2 rows and 2-3 columns.
+ * E.g.: <pre>
+ * A.. A.. A.B A.B A.B.C A.B.C
+ * ... ..B .C. C.D .D.E. D.E.F
+ * </pre>
+ */
+class SwCombinedPortion : public SwFieldPortion
+{
+ sal_uInt16 m_aPos[6]; // up to six X positions
+ o3tl::enumarray<SwFontScript,sal_uInt16> m_aWidth; // one width for every scripttype
+ SwFontScript m_aScrType[6]; // scripttype of every character
+ sal_uInt16 m_nUpPos; // the Y position of the upper baseline
+ sal_uInt16 m_nLowPos; // the Y position of the lower baseline
+ sal_uInt8 m_nProportion; // relative font height
+public:
+ explicit SwCombinedPortion( const OUString &rExpand );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override;
+};
+
+namespace sw::mark { class IFieldmark; }
+
+class SwFieldFormDropDownPortion : public SwFieldPortion
+{
+public:
+ explicit SwFieldFormDropDownPortion(sw::mark::IFieldmark *pFieldMark, const OUString &rExpand)
+ : SwFieldPortion(rExpand)
+ , m_pFieldMark(pFieldMark)
+ {
+ }
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const override;
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+
+private:
+ sw::mark::IFieldmark* m_pFieldMark;
+};
+
+class SwFieldFormDatePortion : public SwFieldPortion
+{
+public:
+ explicit SwFieldFormDatePortion(sw::mark::IFieldmark *pFieldMark, bool bStart)
+ : SwFieldPortion("")
+ , m_pFieldMark(pFieldMark)
+ , m_bStart(bStart)
+ {
+ }
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand) const override;
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+
+private:
+ sw::mark::IFieldmark* m_pFieldMark;
+ 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/porfly.cxx b/sw/source/core/text/porfly.cxx
new file mode 100644
index 0000000000..14d1bf6eaa
--- /dev/null
+++ b/sw/source/core/text/porfly.cxx
@@ -0,0 +1,423 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <dcontact.hxx>
+#include <dflyobj.hxx>
+#include <pam.hxx>
+#include "portab.hxx"
+#include <flyfrm.hxx>
+#include <rootfrm.hxx>
+#include <frmfmt.hxx>
+#include <viewsh.hxx>
+#include <textboxhelper.hxx>
+#include <IDocumentState.hxx>
+
+#include <sal/log.hxx>
+#include <fmtanchr.hxx>
+#include <fmtflcnt.hxx>
+#include <flyfrms.hxx>
+#include <txatbase.hxx>
+#include "porfly.hxx"
+#include "porlay.hxx"
+#include "inftxt.hxx"
+
+#include <sortedobjs.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <PostItMgr.hxx>
+
+/**
+ * class SwFlyPortion => we expect a frame-locale SwRect!
+ */
+
+void SwFlyPortion::Paint( const SwTextPaintInfo& ) const
+{
+}
+
+bool SwFlyPortion::Format( SwTextFormatInfo &rInf )
+{
+ OSL_ENSURE( GetFix() >= rInf.X(), "SwFlyPortion::Format" );
+
+ // tabs must be expanded
+ if( rInf.GetLastTab() )
+ rInf.GetLastTab()->FormatEOL( rInf );
+
+ rInf.GetLast()->FormatEOL( rInf );
+
+ SetBlankWidth(0);
+ if (auto blankWidth = rInf.GetLast()->ExtraBlankWidth())
+ {
+ // Swallow previous blank width
+ SetBlankWidth(blankWidth);
+ rInf.GetLast()->ExtraBlankWidth(0);
+ rInf.X(rInf.X() - blankWidth); // Step back
+ }
+
+ PrtWidth( o3tl::narrowing<sal_uInt16>(GetFix() - rInf.X() + PrtWidth()) );
+ if( !Width() )
+ {
+ OSL_ENSURE( Width(), "+SwFlyPortion::Format: a fly is a fly is a fly" );
+ Width(1);
+ }
+
+ // resetting
+ rInf.SetFly( nullptr );
+ rInf.Width( rInf.RealWidth() );
+ rInf.GetParaPortion()->SetFly();
+
+ // trailing blank:
+ if( rInf.GetIdx() < TextFrameIndex(rInf.GetText().getLength())
+ && TextFrameIndex(1) < rInf.GetIdx()
+ && !rInf.GetRest()
+ && ' ' == rInf.GetChar( rInf.GetIdx() )
+ && ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1))
+ && ( !rInf.GetLast() || !rInf.GetLast()->IsBreakPortion() ) )
+ {
+ SetBlankWidth(GetBlankWidth() + rInf.GetTextSize(OUString(' ')).Width());
+ SetLen(TextFrameIndex(1));
+ }
+
+ const sal_uInt16 nNewWidth = o3tl::narrowing<sal_uInt16>(rInf.X() + PrtWidth());
+ if( rInf.Width() <= nNewWidth )
+ {
+ Truncate();
+ if( nNewWidth > rInf.Width() )
+ {
+ PrtWidth( nNewWidth - rInf.Width() );
+ SetFixWidth( PrtWidth() );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SwFlyCntPortion::Format( SwTextFormatInfo &rInf )
+{
+ bool bFull = rInf.Width() < rInf.X() + PrtWidth();
+
+ if( bFull )
+ {
+ // If the line is full, and the character-bound frame is at
+ // the beginning of a line
+ // If it is not possible to side step into a Fly
+ // "Begin of line" criteria ( ! rInf.X() ) has to be extended.
+ // KerningPortions at beginning of line, e.g., for grid layout
+ // must be considered.
+ const SwLinePortion* pLastPor = rInf.GetLast();
+ const auto nLeft = ( pLastPor &&
+ ( pLastPor->IsKernPortion() ||
+ pLastPor->IsErgoSumPortion() ) ) ?
+ pLastPor->Width() :
+ 0;
+
+ if( nLeft == rInf.X() && ! rInf.GetFly() )
+ {
+ Width( rInf.Width() );
+ bFull = false; // so that notes can still be placed in this line
+ }
+ else
+ {
+ if( !rInf.GetFly() )
+ rInf.SetNewLine( true );
+ Width(0);
+ SetAscent(0);
+ SetLen(TextFrameIndex(0));
+ if( rInf.GetLast() )
+ rInf.GetLast()->FormatEOL( rInf );
+
+ return bFull;
+ }
+ }
+
+ rInf.GetParaPortion()->SetFly();
+ return bFull;
+}
+
+//TODO: improve documentation
+/** move character-bound objects inside the given area
+ *
+ * This allows moving those objects from Master to Follow, or vice versa.
+ *
+ * @param pNew
+ * @param nStart
+ * @param nEnd
+ */
+void SwTextFrame::MoveFlyInCnt(SwTextFrame *pNew,
+ TextFrameIndex const nStart, TextFrameIndex const nEnd)
+{
+ SwSortedObjs *pObjs = GetDrawObjs();
+ if ( nullptr == pObjs )
+ return;
+
+ for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i )
+ {
+ // Consider changed type of <SwSortedList> entries
+ SwAnchoredObject* pAnchoredObj = (*pObjs)[i];
+ const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor();
+ if (rAnch.GetAnchorId() == RndStdIds::FLY_AS_CHAR)
+ {
+ const SwPosition* pPos = rAnch.GetContentAnchor();
+ TextFrameIndex const nIndex(MapModelToViewPos(*pPos));
+ if (nStart <= nIndex && nIndex < nEnd)
+ {
+ if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() )
+ {
+ RemoveFly( pFlyFrame );
+ pNew->AppendFly( pFlyFrame );
+ }
+ else if ( dynamic_cast< const SwAnchoredDrawObject *>( pAnchoredObj ) != nullptr )
+ {
+ RemoveDrawObj( *pAnchoredObj );
+ pNew->AppendDrawObj( *pAnchoredObj );
+ }
+ --i;
+ }
+ }
+ }
+}
+
+TextFrameIndex SwTextFrame::CalcFlyPos( SwFrameFormat const * pSearch )
+{
+ sw::MergedAttrIter iter(*this);
+ for (SwTextAttr const* pHt = iter.NextAttr(); pHt; pHt = iter.NextAttr())
+ {
+ if( RES_TXTATR_FLYCNT == pHt->Which() )
+ {
+ SwFrameFormat* pFrameFormat = pHt->GetFlyCnt().GetFrameFormat();
+ if( pFrameFormat == pSearch )
+ {
+ return TextFrameIndex(pHt->GetStart());
+ }
+ }
+ }
+ OSL_ENSURE(false, "CalcFlyPos: Not Found!");
+ return TextFrameIndex(COMPLETE_STRING);
+}
+
+void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const
+{
+ // Baseline output
+ // Re-paint everything at a CompletePaint call
+ SwRect aRepaintRect(rInf.GetPaintRect());
+
+ if(rInf.GetTextFrame()->IsRightToLeft())
+ rInf.GetTextFrame()->SwitchLTRtoRTL(aRepaintRect);
+
+ if(rInf.GetTextFrame()->IsVertical())
+ rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect);
+
+ if(!((m_pFly->IsCompletePaint() ||
+ m_pFly->getFrameArea().Overlaps(aRepaintRect)) &&
+ SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), m_pFly->getRootFrame()->GetCurrShell())))
+ return;
+
+ SwRect aRect(m_pFly->getFrameArea());
+ if(!m_pFly->IsCompletePaint())
+ aRect.Intersection_(aRepaintRect);
+
+ // GetFlyFrame() may change the layout mode at the output device.
+ {
+ SwLayoutModeModifier aLayoutModeModifier(*rInf.GetOut());
+ m_pFly->PaintSwFrame(const_cast<vcl::RenderContext&>(*rInf.GetOut()), aRect);
+
+ // track changes: cross out the image, if it is deleted
+ const SwFrame *pFrame = m_pFly->Lower();
+ if ( GetAuthor() != std::string::npos && IsDeleted() && pFrame )
+ {
+ SwRect aPaintRect( pFrame->GetPaintArea() );
+
+ const AntialiasingFlags nFormerAntialiasing( rInf.GetOut()->GetAntialiasing() );
+ const bool bIsAntiAliasing = officecfg::Office::Common::Drawinglayer::AntiAliasing::get();
+ if ( bIsAntiAliasing )
+ const_cast<vcl::RenderContext&>(*rInf.GetOut()).SetAntialiasing(AntialiasingFlags::Enable);
+ tools::Long startX = aPaintRect.Left( ), endX = aPaintRect.Right();
+ tools::Long startY = aPaintRect.Top( ), endY = aPaintRect.Bottom();
+ const_cast<vcl::RenderContext&>(*rInf.GetOut()).SetLineColor(
+ SwPostItMgr::GetColorAnchor(GetAuthor()) );
+ const_cast<vcl::RenderContext&>(*rInf.GetOut()).DrawLine(Point(startX, startY), Point(endX, endY));
+ const_cast<vcl::RenderContext&>(*rInf.GetOut()).DrawLine(Point(startX, endY), Point(endX, startY));
+ if ( bIsAntiAliasing )
+ const_cast<vcl::RenderContext&>(*rInf.GetOut()).SetAntialiasing(nFormerAntialiasing);
+ }
+ }
+ const_cast<SwTextPaintInfo&>(rInf).GetRefDev()->SetLayoutMode(rInf.GetOut()->GetLayoutMode());
+
+ // As the OutputDevice might be anything, the font must be re-selected.
+ // Being in const method should not be a problem.
+ const_cast<SwTextPaintInfo&>(rInf).SelectFont();
+
+ assert(rInf.GetVsh());
+ SAL_WARN_IF(rInf.GetVsh()->GetOut() != rInf.GetOut(), "sw.core", "SwFlyCntPortion::Paint: Outdev has changed");
+ if(rInf.GetVsh())
+ const_cast<SwTextPaintInfo&>(rInf).SetOut(rInf.GetVsh()->GetOut());
+}
+
+void sw::DrawFlyCntPortion::Paint(const SwTextPaintInfo&) const
+{
+ if(!m_pContact->GetAnchorFrame())
+ {
+ // No direct positioning of the drawing object is needed
+ m_pContact->ConnectToLayout();
+ }
+}
+
+/**
+ * Use the dimensions of pFly->OutRect()
+ */
+SwFlyCntPortion::SwFlyCntPortion()
+ : m_bMax(false)
+ , m_bDeleted(false)
+ , m_nAuthor(std::string::npos)
+ , m_eAlign(sw::LineAlign::NONE)
+{
+ mnLineLength = TextFrameIndex(1);
+ SetWhichPor(PortionType::FlyCnt);
+}
+
+sw::FlyContentPortion::FlyContentPortion(SwFlyInContentFrame* pFly)
+ : m_pFly(pFly)
+{
+ SAL_WARN_IF(!pFly, "sw.core", "SwFlyCntPortion::SwFlyCntPortion: no SwFlyInContentFrame!");
+}
+
+sw::DrawFlyCntPortion::DrawFlyCntPortion(SwFrameFormat const & rFormat)
+ : m_pContact(nullptr)
+{
+ rFormat.CallSwClientNotify(sw::CreatePortionHint(&m_pContact));
+ assert(m_pContact);
+}
+
+sw::FlyContentPortion* sw::FlyContentPortion::Create(const SwTextFrame& rFrame, SwFlyInContentFrame* pFly, const Point& rBase, tools::Long nLnAscent, tools::Long nLnDescent, tools::Long nFlyAsc, tools::Long nFlyDesc, AsCharFlags nFlags)
+{
+ auto pNew(new sw::FlyContentPortion(pFly));
+ pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init);
+ return pNew;
+}
+
+sw::DrawFlyCntPortion* sw::DrawFlyCntPortion::Create(const SwTextFrame& rFrame, SwFrameFormat const & rFormat, const Point& rBase, tools::Long nLnAscent, tools::Long nLnDescent, tools::Long nFlyAsc, tools::Long nFlyDesc, AsCharFlags nFlags)
+{
+ auto pNew(new DrawFlyCntPortion(rFormat));
+ pNew->SetBase(rFrame, rBase, nLnAscent, nLnDescent, nFlyAsc, nFlyDesc, nFlags | AsCharFlags::UlSpace | AsCharFlags::Init);
+ return pNew;
+}
+
+sw::DrawFlyCntPortion::~DrawFlyCntPortion() {};
+sw::FlyContentPortion::~FlyContentPortion() {};
+
+SdrObject* sw::FlyContentPortion::GetSdrObj(const SwTextFrame&)
+{
+ return m_pFly->GetVirtDrawObj();
+}
+
+SdrObject* sw::DrawFlyCntPortion::GetSdrObj(const SwTextFrame& rFrame)
+{
+ SdrObject* pSdrObj;
+ // Determine drawing object ('master' or 'virtual') by frame
+ pSdrObj = m_pContact->GetDrawObjectByAnchorFrame(rFrame);
+ if(!pSdrObj)
+ {
+ SAL_WARN("sw.core", "SwFlyCntPortion::SetBase(..) - No drawing object found by <GetDrawContact()->GetDrawObjectByAnchorFrame( rFrame )>");
+ pSdrObj = m_pContact->GetMaster();
+ }
+
+ // Call <SwAnchoredDrawObject::MakeObjPos()> to assure that flag at
+ // the <DrawFrameFormat> and at the <SwAnchoredDrawObject> instance are
+ // correctly set
+ if(pSdrObj)
+ m_pContact->GetAnchoredObj(pSdrObj)->MakeObjPos();
+ return pSdrObj;
+}
+
+/**
+ * After setting the RefPoints, the ascent needs to be recalculated
+ * because it is dependent on RelPos
+ *
+ * @param rBase CAUTION: needs to be an absolute value!
+ */
+void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase,
+ tools::Long nLnAscent, tools::Long nLnDescent,
+ tools::Long nFlyAsc, tools::Long nFlyDesc,
+ AsCharFlags nFlags )
+{
+ // Use new class to position object
+ // Determine drawing object
+ SdrObject* pSdrObj = GetSdrObj(rFrame);
+ if (!pSdrObj)
+ return;
+
+ // position object
+ objectpositioning::SwAsCharAnchoredObjectPosition aObjPositioning(
+ *pSdrObj,
+ rBase, nFlags,
+ nLnAscent, nLnDescent, nFlyAsc, nFlyDesc );
+
+ // Scope of local variable <aObjPosInProgress>
+ {
+ SwObjPositioningInProgress aObjPosInProgress( *pSdrObj );
+ aObjPositioning.CalcPosition();
+ }
+
+ if (auto pFormat = FindFrameFormat(pSdrObj))
+ {
+ if (pFormat->GetOtherTextBoxFormats()
+ && pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR)
+ {
+ // TODO: Improve security with moving this sync call to other place,
+ // where it works for typing but not during layout calc.
+ const bool bModified = pFormat->GetDoc()->getIDocumentState().IsEnableSetModified();
+ pFormat->GetDoc()->getIDocumentState().SetEnableSetModified(false);
+ SwTextBoxHelper::synchronizeGroupTextBoxProperty(SwTextBoxHelper::changeAnchor, pFormat,
+ pFormat->FindRealSdrObject());
+ SwTextBoxHelper::synchronizeGroupTextBoxProperty(SwTextBoxHelper::syncTextBoxSize,
+ pFormat, pFormat->FindRealSdrObject());
+ pFormat->GetDoc()->getIDocumentState().SetEnableSetModified(bModified);
+ }
+ }
+
+ SetAlign( aObjPositioning.GetLineAlignment() );
+
+ m_aRef = aObjPositioning.GetAnchorPos();
+ if( nFlags & AsCharFlags::Rotate )
+ SvXSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() );
+ else
+ SvLSize( aObjPositioning.GetObjBoundRectInclSpacing().SSize() );
+ if( Height() )
+ {
+ // GetRelPosY returns the relative position to baseline (if 0, the
+ // upper border of the FlyCnt if on the baseline of a line)
+ SwTwips nRelPos = aObjPositioning.GetRelPosY();
+ if ( nRelPos < 0 )
+ {
+ mnAscent = -nRelPos;
+ if( mnAscent > Height() )
+ Height( mnAscent );
+ }
+ else
+ {
+ mnAscent = 0;
+ Height( Height() + o3tl::narrowing<sal_uInt16>(nRelPos) );
+ }
+ }
+ else
+ {
+ Height( 1 );
+ mnAscent = 0;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porfly.hxx b/sw/source/core/text/porfly.hxx
new file mode 100644
index 0000000000..a519c1109c
--- /dev/null
+++ b/sw/source/core/text/porfly.hxx
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <ascharanchoredobjectposition.hxx>
+
+#include "porglue.hxx"
+#include <flyfrms.hxx>
+
+class SwDrawContact;
+class SwTextFrame;
+struct SwCursorMoveState;
+
+class SwFlyPortion : public SwFixPortion
+{
+ sal_uInt16 m_nBlankWidth;
+public:
+ explicit SwFlyPortion( const SwRect &rFlyRect )
+ : SwFixPortion(rFlyRect), m_nBlankWidth( 0 ) { SetWhichPor( PortionType::Fly ); }
+ sal_uInt16 GetBlankWidth( ) const { return m_nBlankWidth; }
+ void SetBlankWidth( const sal_uInt16 nNew ) { m_nBlankWidth = nNew; }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+};
+
+/// This portion represents an as-character anchored fly (shape, frame, etc.)
+class SwFlyCntPortion : public SwLinePortion
+{
+ Point m_aRef; // Relatively to this point we calculate the AbsPos
+ bool m_bMax; // Line adjustment and height == line height
+ bool m_bDeleted; // Part of tracked deletion: it needs strikethrough
+ size_t m_nAuthor; // Redline author for color of the strikethrough
+ sw::LineAlign m_eAlign;
+
+ virtual SdrObject* GetSdrObj(const SwTextFrame&) =0;
+
+public:
+ SwFlyCntPortion();
+ const Point& GetRefPoint() const { return m_aRef; }
+ bool IsMax() const { return m_bMax; }
+ bool IsDeleted() const { return m_bDeleted; }
+ void SetAuthor(size_t nAuthor) { m_nAuthor = nAuthor; }
+ size_t GetAuthor() const { return m_nAuthor; }
+ sw::LineAlign GetAlign() const { return m_eAlign; }
+ void SetAlign(sw::LineAlign eAlign) { m_eAlign = eAlign; }
+ void SetMax(bool bMax) { m_bMax = bMax; }
+ void SetDeleted(bool bDeleted) { m_bDeleted = bDeleted; }
+ void SetBase(const SwTextFrame& rFrame, const Point& rBase, tools::Long nLnAscent, tools::Long nLnDescent, tools::Long nFlyAscent, tools::Long nFlyDescent, AsCharFlags nFlags);
+ virtual bool Format(SwTextFormatInfo& rInf) override;
+};
+
+namespace sw
+{
+ class FlyContentPortion final : public SwFlyCntPortion
+ {
+ SwFlyInContentFrame* m_pFly;
+ virtual SdrObject* GetSdrObj(const SwTextFrame&) override;
+ public:
+ FlyContentPortion(SwFlyInContentFrame* pFly);
+ static FlyContentPortion* Create(const SwTextFrame& rFrame, SwFlyInContentFrame* pFly, const Point& rBase, tools::Long nAscent, tools::Long nDescent, tools::Long nFlyAsc, tools::Long nFlyDesc, AsCharFlags nFlags);
+ SwFlyInContentFrame* GetFlyFrame() { return m_pFly; }
+ void GetFlyCursorOfst(Point& rPoint, SwPosition& rPos, SwCursorMoveState* pCMS) const { m_pFly->GetModelPositionForViewPoint(&rPos, rPoint, pCMS); };
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+ virtual ~FlyContentPortion() override;
+ };
+ class DrawFlyCntPortion final : public SwFlyCntPortion
+ {
+ SwDrawContact* m_pContact;
+ virtual SdrObject* GetSdrObj(const SwTextFrame&) override;
+ public:
+ DrawFlyCntPortion(SwFrameFormat const & rFormat);
+ static DrawFlyCntPortion* Create(const SwTextFrame& rFrame, SwFrameFormat const & rFormat, const Point& rBase, tools::Long nAsc, tools::Long nDescent, tools::Long nFlyAsc, tools::Long nFlyDesc, AsCharFlags nFlags);
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+ virtual ~DrawFlyCntPortion() override;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porftn.hxx b/sw/source/core/text/porftn.hxx
new file mode 100644
index 0000000000..925a04f779
--- /dev/null
+++ b/sw/source/core/text/porftn.hxx
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "porfld.hxx"
+
+class SwTextFootnote;
+
+class SwFootnotePortion : public SwFieldPortion
+{
+ SwTextFootnote *m_pFootnote;
+ sal_uInt16 m_nOrigHeight;
+ // #i98418#
+ bool mbPreferredScriptTypeSet;
+ SwFontScript mnPreferredScriptType;
+public:
+ SwFootnotePortion( const OUString &rExpand, SwTextFootnote *pFootnote,
+ sal_uInt16 nOrig = USHRT_MAX );
+ sal_uInt16& Orig() { return m_nOrigHeight; }
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ // #i98418#
+ void SetPreferredScriptType( SwFontScript nPreferredScriptType );
+
+ const SwTextFootnote* GetTextFootnote() const { return m_pFootnote; };
+};
+
+class SwFootnoteNumPortion : public SwNumberPortion
+{
+public:
+ SwFootnoteNumPortion( const OUString &rExpand, std::unique_ptr<SwFont> pFntL )
+ : SwNumberPortion( rExpand, std::move(pFntL), true, false, 0, false )
+ { SetWhichPor( PortionType::FootnoteNum ); }
+};
+
+/**
+ * Used in footnotes if they break across pages, master has this portion at the end.
+ *
+ * Created only in case Tools -> Footnotes and Endnotes sets the End of footnote to a non-empty
+ * value.
+ */
+class SwQuoVadisPortion : public SwFieldPortion
+{
+ OUString m_aErgo;
+public:
+ SwQuoVadisPortion( const OUString &rExp, OUString aStr );
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+
+ void SetNumber( const OUString& rStr ) { m_aErgo = rStr; }
+ const OUString& GetQuoText() const { return m_aExpand; }
+ const OUString &GetContText() const { return m_aErgo; }
+
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+};
+
+/**
+ * Used in footnotes if they break across pages, follow starts with this portion.
+ *
+ * Created only in case Tools -> Footnotes and Endnotes sets the Start of next page to a non-empty
+ * value.
+ */
+class SwErgoSumPortion : public SwFieldPortion
+{
+public:
+ SwErgoSumPortion( const OUString &rExp, std::u16string_view rStr );
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ // Field cloner for SplitGlue
+ virtual SwFieldPortion *Clone( const OUString &rExpand ) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porglue.cxx b/sw/source/core/text/porglue.cxx
new file mode 100644
index 0000000000..5f1fd2a7dc
--- /dev/null
+++ b/sw/source/core/text/porglue.cxx
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <swrect.hxx>
+#include <viewopt.hxx>
+#include "porglue.hxx"
+#include "inftxt.hxx"
+#include "porlay.hxx"
+#include "porfly.hxx"
+#include <comphelper/string.hxx>
+
+SwGluePortion::SwGluePortion( const sal_uInt16 nInitFixWidth )
+ : m_nFixWidth( nInitFixWidth )
+{
+ PrtWidth( m_nFixWidth );
+ SetWhichPor( PortionType::Glue );
+}
+
+TextFrameIndex SwGluePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const
+{
+ // FIXME why nOfst > GetLen() ? is that supposed to be > Width() ?
+ if( !GetLen() || nOfst > sal_Int32(GetLen()) || !Width() )
+ return SwLinePortion::GetModelPositionForViewPoint( nOfst );
+ else
+ return TextFrameIndex(nOfst / (Width() / sal_Int32(GetLen())));
+}
+
+SwPosSize SwGluePortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ if (TextFrameIndex(1) >= GetLen() || rInf.GetLen() > GetLen() || !Width() || !GetLen())
+ return SwPosSize(*this);
+ else
+ return SwPosSize((Width() / sal_Int32(GetLen())) * sal_Int32(rInf.GetLen()), Height());
+}
+
+bool SwGluePortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ if( GetLen() && rInf.OnWin() &&
+ rInf.GetOpt().IsBlank() && rInf.IsNoSymbol() )
+ {
+ OUStringBuffer aBuf(GetLen().get());
+ comphelper::string::padToLength(aBuf, sal_Int32(GetLen()), CH_BULLET);
+ rText = aBuf.makeStringAndClear();
+ return true;
+ }
+ return false;
+}
+
+void SwGluePortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( !GetLen() )
+ return;
+
+ if( rInf.GetFont()->IsPaintBlank() )
+ {
+ const sal_Int32 nCount = GetFixWidth() / sal_Int32(GetLen());
+ OUStringBuffer aBuf(nCount);
+ comphelper::string::padToLength(aBuf, nCount, ' ');
+ OUString aText(aBuf.makeStringAndClear());
+ SwTextPaintInfo aInf( rInf, &aText );
+ aInf.DrawText(*this, TextFrameIndex(aText.getLength()), true);
+ }
+
+ if( !(rInf.OnWin() && rInf.GetOpt().IsBlank() && rInf.IsNoSymbol()) )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ const sal_Unicode cChar = rInf.GetChar( rInf.GetIdx() );
+ OSL_ENSURE( CH_BLANK == cChar || CH_BULLET == cChar,
+ "SwGluePortion::Paint: blank expected" );
+#endif
+ if (TextFrameIndex(1) == GetLen())
+ {
+ OUString aBullet( CH_BULLET );
+ SwPosSize aBulletSize( rInf.GetTextSize( aBullet ) );
+ Point aPos( rInf.GetPos() );
+ aPos.AdjustX((Width()/2) - (aBulletSize.Width()/2) );
+ SwTextPaintInfo aInf( rInf, &aBullet );
+ aInf.SetPos( aPos );
+ SwTextPortion aBulletPor;
+ aBulletPor.Width( aBulletSize.Width() );
+ aBulletPor.Height( aBulletSize.Height() );
+ aBulletPor.SetAscent( GetAscent() );
+ aInf.DrawText(aBulletPor, TextFrameIndex(aBullet.getLength()), true);
+ }
+ else
+ {
+ SwTextSlot aSlot( &rInf, this, true, false );
+ rInf.DrawText( *this, rInf.GetLen(), true );
+ }
+}
+
+void SwGluePortion::MoveGlue( SwGluePortion *pTarget, const tools::Long nPrtGlue )
+{
+ auto nPrt = std::min( nPrtGlue, GetPrtGlue() );
+ if( 0 < nPrt )
+ {
+ pTarget->AddPrtWidth( nPrt ); //TODO: overflow
+ SubPrtWidth( nPrt ); //TODO: overflow
+ }
+}
+
+void SwGluePortion::Join( SwGluePortion *pVictim )
+{
+ // The GluePortion is extracted and flushed away ...
+ AddPrtWidth( pVictim->PrtWidth() );
+ SetLen( pVictim->GetLen() + GetLen() );
+ if( Height() < pVictim->Height() )
+ Height( pVictim->Height() );
+
+ AdjFixWidth();
+ Cut( pVictim );
+ delete pVictim;
+}
+
+void SwGluePortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwGluePortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fix-width"), BAD_CAST(OString::number(m_nFixWidth).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * We're expecting a frame-local SwRect!
+ */
+SwFixPortion::SwFixPortion( const SwRect &rRect )
+ :SwGluePortion( sal_uInt16(rRect.Width()) ), m_nFix( sal_uInt16(rRect.Left()) )
+{
+ Height( sal_uInt16(rRect.Height()) );
+ SetWhichPor( PortionType::Fix );
+}
+
+SwFixPortion::SwFixPortion()
+ : SwGluePortion(0), m_nFix(0)
+{
+ SetWhichPor( PortionType::Fix );
+}
+
+void SwFixPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwFixPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("fix"),
+ BAD_CAST(OString::number(m_nFix).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwMarginPortion::SwMarginPortion()
+ :SwGluePortion( 0 )
+{
+ SetWhichPor( PortionType::Margin );
+}
+
+/**
+ * In the outer loop all portions are inspected - the GluePortions
+ * at the end are processed first.
+ * The end is shifted forwardly till no more GluePortions remain.
+ * Always GluePortion-pairs (pLeft and pRight) are treated, where
+ * textportions between pLeft and pRight are moved at the back of
+ * pRight if pRight has enough Glue. With every move part of the
+ * Glue is transferred from pRight to pLeft.
+ * The next loop starts with the processed pLeft as pRight.
+ */
+void SwMarginPortion::AdjustRight( const SwLineLayout *pCurr )
+{
+ SwGluePortion *pRight = nullptr;
+ bool bNoMove = nullptr != pCurr->GetpKanaComp();
+ while( pRight != this )
+ {
+
+ // 1) We search for the left Glue
+ SwLinePortion *pPos = this;
+ SwGluePortion *pLeft = nullptr;
+ while( pPos )
+ {
+ if( pPos->InFixMargGrp() )
+ pLeft = static_cast<SwGluePortion*>(pPos);
+ pPos = pPos->GetNextPortion();
+ if( pPos == pRight)
+ pPos = nullptr;
+ }
+
+ // Two adjoining FlyPortions are merged
+ if( pRight && pLeft && pLeft->GetNextPortion() == pRight )
+ {
+ pRight->MoveAllGlue( pLeft );
+ pRight = nullptr;
+ }
+ auto nRightGlue = pRight && 0 < pRight->GetPrtGlue()
+ ? pRight->GetPrtGlue() : 0;
+ // 2) balance left and right Glue
+ // But not for tabs ...
+ if( pLeft && nRightGlue && !pRight->InTabGrp() )
+ {
+ // pPrev is the portion immediately before pRight
+ SwLinePortion *pPrev = pRight->FindPrevPortion( pLeft );
+
+ if ( pRight->IsFlyPortion() && pRight->GetLen() )
+ {
+ SwFlyPortion *pFly = static_cast<SwFlyPortion *>(pRight);
+ if ( pFly->GetBlankWidth() < nRightGlue )
+ {
+ // Creating new TextPortion, that takes over the
+ // Blank previously swallowed by the Fly.
+ nRightGlue = nRightGlue - pFly->GetBlankWidth();
+ pFly->SubPrtWidth( pFly->GetBlankWidth() );
+ pFly->SetLen(TextFrameIndex(0));
+ SwTextPortion *pNewPor = new SwTextPortion;
+ pNewPor->SetLen(TextFrameIndex(1));
+ pNewPor->Height( pFly->Height() );
+ pNewPor->Width( pFly->GetBlankWidth() );
+ pFly->Insert( pNewPor );
+ }
+ else
+ pPrev = pLeft;
+ }
+ while( pPrev != pLeft )
+ {
+ if( bNoMove || pPrev->PrtWidth() >= nRightGlue ||
+ pPrev->InHyphGrp() || pPrev->IsKernPortion() )
+ {
+ // The portion before the pRight cannot be moved
+ // because no Glue is remaining.
+ // We set the break condition:
+ pPrev = pLeft;
+ }
+ else
+ {
+ nRightGlue = nRightGlue - pPrev->PrtWidth();
+ // pPrev is moved behind pRight. For this the
+ // Glue value between pRight and pLeft gets balanced.
+ pRight->MoveGlue( pLeft, pPrev->PrtWidth() );
+ // Now fix the linking of our portions.
+ SwLinePortion *pPrevPrev = pPrev->FindPrevPortion( pLeft );
+ pPrevPrev->SetNextPortion( pRight );
+ pPrev->SetNextPortion( pRight->GetNextPortion() );
+ pRight->SetNextPortion( pPrev );
+ if ( pPrev->GetNextPortion() && pPrev->InTextGrp()
+ && pPrev->GetNextPortion()->IsHolePortion() )
+ {
+ SwHolePortion *pHolePor =
+ static_cast<SwHolePortion*>(pPrev->GetNextPortion());
+ if ( !pHolePor->GetNextPortion() ||
+ !pHolePor->GetNextPortion()->InFixMargGrp() )
+ {
+ pPrev->AddPrtWidth( pHolePor->GetBlankWidth() );
+ pPrev->SetLen(pPrev->GetLen() + TextFrameIndex(1));
+ pPrev->SetNextPortion( pHolePor->GetNextPortion() );
+ delete pHolePor;
+ }
+ }
+ pPrev = pPrevPrev;
+ }
+ }
+ }
+ // If no left Glue remains, we set the break condition.
+ pRight = pLeft ? pLeft : this;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porglue.hxx b/sw/source/core/text/porglue.hxx
new file mode 100644
index 0000000000..aaaeec339f
--- /dev/null
+++ b/sw/source/core/text/porglue.hxx
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "porlin.hxx"
+
+class SwRect;
+class SwLineLayout;
+
+/// A glue portion is either a base class for other portions that want to have a certain width to
+/// push text out (e.g. tab portions) or used to align SwQuoVadisPortion instances on the right for
+/// footnotes.
+class SwGluePortion : public SwLinePortion
+{
+private:
+ sal_uInt16 m_nFixWidth;
+public:
+ explicit SwGluePortion( const sal_uInt16 nInitFixWidth );
+
+ void Join( SwGluePortion *pVictim );
+
+ inline tools::Long GetPrtGlue() const;
+ sal_uInt16 GetFixWidth() const { return m_nFixWidth; }
+ void SetFixWidth( const sal_uInt16 nNew ) { m_nFixWidth = nNew; }
+ void MoveGlue( SwGluePortion *pTarget, const tools::Long nPrtGlue );
+ inline void MoveAllGlue( SwGluePortion *pTarget );
+ inline void MoveHalfGlue( SwGluePortion *pTarget );
+ inline void AdjFixWidth();
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const override;
+};
+
+class SwFixPortion : public SwGluePortion
+{
+ sal_uInt16 m_nFix; // The width offset in the line
+public:
+ explicit SwFixPortion( const SwRect &rFlyRect );
+ SwFixPortion();
+ void SetFix( const sal_uInt16 nNewFix ) { m_nFix = nNewFix; }
+ sal_uInt16 GetFix() const { return m_nFix; }
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const override;
+};
+
+class SwMarginPortion : public SwGluePortion
+{
+public:
+ explicit SwMarginPortion();
+ void AdjustRight( const SwLineLayout* pCurr );
+};
+
+inline tools::Long SwGluePortion::GetPrtGlue() const
+{ return Width() - m_nFixWidth; }
+
+// The FixWidth MUST NEVER be larger than the accumulated width!
+inline void SwGluePortion::AdjFixWidth()
+{
+ if( m_nFixWidth > PrtWidth() )
+ m_nFixWidth = PrtWidth();
+}
+
+inline void SwGluePortion::MoveAllGlue( SwGluePortion *pTarget )
+{
+ MoveGlue( pTarget, GetPrtGlue() );
+}
+
+inline void SwGluePortion::MoveHalfGlue( SwGluePortion *pTarget )
+{
+ MoveGlue( pTarget, GetPrtGlue() / 2 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porhyph.hxx b/sw/source/core/text/porhyph.hxx
new file mode 100644
index 0000000000..dc3c6651d0
--- /dev/null
+++ b/sw/source/core/text/porhyph.hxx
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "porexp.hxx"
+
+class SwHyphPortion : public SwExpandPortion
+{
+public:
+ SwHyphPortion()
+ {
+ SetWhichPor( PortionType::Hyphen );
+ }
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+class SwHyphStrPortion : public SwHyphPortion
+{
+ OUString m_aExpand;
+public:
+ explicit SwHyphStrPortion(std::u16string_view rStr)
+ : m_aExpand(OUString::Concat(rStr) + "-")
+ {
+ SetWhichPor( PortionType::HyphenStr );
+ }
+
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+};
+
+class SwSoftHyphPortion : public SwHyphPortion
+{
+ bool m_bExpand;
+ sal_uInt16 m_nViewWidth;
+
+public:
+ SwSoftHyphPortion();
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual SwLinePortion *Compress() override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+ void SetExpand( const bool bNew ) { m_bExpand = bNew; }
+ bool IsExpand() const { return m_bExpand; }
+
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+};
+
+class SwSoftHyphStrPortion : public SwHyphStrPortion
+{
+public:
+ explicit SwSoftHyphStrPortion( std::u16string_view rStr );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
new file mode 100644
index 0000000000..cae54845be
--- /dev/null
+++ b/sw/source/core/text/porlay.cxx
@@ -0,0 +1,2996 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include "porlay.hxx"
+#include "itrform2.hxx"
+#include "porglue.hxx"
+#include "redlnitr.hxx"
+#include "porfly.hxx"
+#include "porrst.hxx"
+#include "pormulti.hxx"
+#include "pordrop.hxx"
+#include <breakit.hxx>
+#include <unicode/uchar.h>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/UnicodeType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <paratr.hxx>
+#include <sal/log.hxx>
+#include <optional>
+#include <editeng/adjustitem.hxx>
+#include <editeng/charhiddenitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <svl/asiancfg.hxx>
+#include <svl/languageoptions.hxx>
+#include <tools/multisel.hxx>
+#include <unotools/charclass.hxx>
+#include <charfmt.hxx>
+#include <docary.hxx>
+#include <fmtanchr.hxx>
+#include <redline.hxx>
+#include <calbck.hxx>
+#include <doc.hxx>
+#include <swscanner.hxx>
+#include <txatbase.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentContentOperations.hxx>
+#include <IMark.hxx>
+#include <sortedobjs.hxx>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/text/XBookmarksSupplier.hpp>
+#include <officecfg/Office/Common.hxx>
+#include <comphelper/processfactory.hxx>
+#include <docsh.hxx>
+#include <unobookmark.hxx>
+#include <unocrsrhelper.hxx>
+#include <frmatr.hxx>
+#include <vcl/kernarray.hxx>
+#include <editeng/ulspitem.hxx>
+#include <com/sun/star/rdf/Statement.hpp>
+#include <com/sun/star/rdf/URI.hpp>
+#include <com/sun/star/rdf/URIs.hpp>
+#include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
+#include <com/sun/star/rdf/XLiteral.hpp>
+#include <com/sun/star/text/XTextContent.hpp>
+
+#include <unicode/ubidi.h>
+#include <i18nutil/scripttypedetector.hxx>
+#include <i18nutil/unicode.hxx>
+
+using namespace ::com::sun::star;
+using namespace i18n::ScriptType;
+
+/*
+ https://www.khtt.net/en/page/1821/the-big-kashida-secret
+
+ the rules of priorities that govern the addition of kashidas in Arabic text
+ made ... for ... Explorer 5.5 browser.
+
+ The kashida justification is based on a connection priority scheme that
+ decides where kashidas are put automatically.
+
+ This is how the software decides on kashida-inserting priorities:
+ 1. First it looks for characters with the highest priority in each word,
+ which means kashida-extensions will only been used in one position in each
+ word. Not more.
+ 2. The kashida will be connected to the character with the highest priority.
+ 3. If kashida connection opportunities are found with an equal level of
+ priority in one word, the kashida will be placed towards the end of the
+ word.
+
+ The priority list of characters and the positioning is as follows:
+ 1. after a kashida that is manually placed in the text by the user,
+ 2. after a Seen or Sad (initial and medial form),
+ 3. before the final form of Taa Marbutah, Haa, Dal,
+ 4. before the final form of Alef, Tah Lam, Kaf and Gaf,
+ 5. before the preceding medial Baa of Ra, Ya and Alef Maqsurah,
+ 6. before the final form of Waw, Ain, Qaf and Fa,
+ 7. before the final form of other characters that can be connected.
+*/
+
+#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g )
+#define isAinChar(c) IS_JOINING_GROUP((c), AIN)
+#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF)
+#define isDalChar(c) IS_JOINING_GROUP((c), DAL)
+#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH))
+#define isGafChar(c) IS_JOINING_GROUP((c), GAF)
+#define isHehChar(c) IS_JOINING_GROUP((c), HEH)
+#define isKafChar(c) IS_JOINING_GROUP((c), KAF)
+#define isLamChar(c) IS_JOINING_GROUP((c), LAM)
+#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF))
+#define isRehChar(c) IS_JOINING_GROUP((c), REH)
+#define isTahChar(c) IS_JOINING_GROUP((c), TAH)
+#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA)
+#define isWawChar(c) IS_JOINING_GROUP((c), WAW)
+#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN))
+
+// Beh and characters that behave like Beh in medial form.
+static bool isBehChar(sal_Unicode cCh)
+{
+ bool bRet = false;
+ switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
+ {
+ case U_JG_BEH:
+ case U_JG_NOON:
+ case U_JG_AFRICAN_NOON:
+ case U_JG_NYA:
+ case U_JG_YEH:
+ case U_JG_FARSI_YEH:
+ case U_JG_BURUSHASKI_YEH_BARREE:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+// Yeh and characters that behave like Yeh in final form.
+static bool isYehChar(sal_Unicode cCh)
+{
+ bool bRet = false;
+ switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
+ {
+ case U_JG_YEH:
+ case U_JG_FARSI_YEH:
+ case U_JG_YEH_BARREE:
+ case U_JG_BURUSHASKI_YEH_BARREE:
+ case U_JG_YEH_WITH_TAIL:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+static bool isTransparentChar ( sal_Unicode cCh )
+{
+ return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT;
+}
+
+// Checks if cCh + cNectCh builds a ligature (used for Kashidas)
+static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh )
+{
+ // Lam + Alef
+ return ( isLamChar ( cCh ) && isAlefChar ( cNextCh ));
+}
+
+// Checks if cCh is connectable to cPrevCh (used for Kashidas)
+static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )
+{
+ const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE );
+ bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING;
+
+ // check for ligatures cPrevChar + cChar
+ if( bRet )
+ bRet = !lcl_IsLigature( cPrevCh, cCh );
+
+ return bRet;
+}
+
+static bool lcl_HasStrongLTR ( std::u16string_view rText, sal_Int32 nStart, sal_Int32 nEnd )
+ {
+ for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
+ {
+ const UCharDirection nCharDir = u_charDirection ( rText[ nCharIdx ] );
+ if ( nCharDir == U_LEFT_TO_RIGHT ||
+ nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
+ nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
+ return true;
+ }
+ return false;
+ }
+
+// This is (meant to be) functionally equivalent to 'delete m_pNext' where
+// deleting a SwLineLayout recursively deletes the owned m_pNext SwLineLayout.
+//
+// Here, instead of using a potentially deep stack, iterate over all the
+// SwLineLayouts that would be deleted recursively and delete them linearly
+void SwLineLayout::DeleteNext()
+{
+ if (!m_pNext)
+ return;
+ SwLineLayout* pNext = m_pNext;
+ do
+ {
+ SwLineLayout* pLastNext = pNext;
+ pNext = pNext->GetNext();
+ pLastNext->SetNext(nullptr);
+ delete pLastNext;
+ }
+ while (pNext);
+}
+
+void SwLineLayout::Height(const SwTwips nNew, const bool bText)
+{
+ SwPosSize::Height(nNew);
+ if (bText)
+ m_nTextHeight = nNew;
+}
+
+// class SwLineLayout: This is the layout of a single line, which is made
+// up of its dimension, the character count and the word spacing in the line.
+// Line objects are managed in an own pool, in order to store them continuously
+// in memory so that they are paged out together and don't fragment memory.
+SwLineLayout::~SwLineLayout()
+{
+ Truncate();
+ DeleteNext();
+ m_pLLSpaceAdd.reset();
+ m_pKanaComp.reset();
+}
+
+SwLinePortion *SwLineLayout::Insert( SwLinePortion *pIns )
+{
+ // First attribute change: copy mass and length from *pIns into the first
+ // text portion
+ if( !mpNextPortion )
+ {
+ if( GetLen() )
+ {
+ mpNextPortion = SwTextPortion::CopyLinePortion(*this);
+ if( IsBlinking() )
+ {
+ SetBlinking( false );
+ }
+ }
+ else
+ {
+ SetNextPortion( pIns );
+ return pIns;
+ }
+ }
+ // Call with scope or we'll end up with recursion!
+ return mpNextPortion->SwLinePortion::Insert( pIns );
+}
+
+SwLinePortion *SwLineLayout::Append( SwLinePortion *pIns )
+{
+ // First attribute change: copy mass and length from *pIns into the first
+ // text portion
+ if( !mpNextPortion )
+ mpNextPortion = SwTextPortion::CopyLinePortion(*this);
+ // Call with scope or we'll end up with recursion!
+ return mpNextPortion->SwLinePortion::Append( pIns );
+}
+
+// For special treatment of empty lines
+
+bool SwLineLayout::Format( SwTextFormatInfo &rInf )
+{
+ if( GetLen() )
+ return SwTextPortion::Format( rInf );
+
+ Height( rInf.GetTextHeight() );
+ return true;
+}
+
+// We collect all FlyPortions at the beginning of the line and make that a
+// MarginPortion.
+SwMarginPortion *SwLineLayout::CalcLeftMargin()
+{
+ SwMarginPortion *pLeft = (GetNextPortion() && GetNextPortion()->IsMarginPortion()) ?
+ static_cast<SwMarginPortion *>(GetNextPortion()) : nullptr;
+ if( !GetNextPortion() )
+ SetNextPortion(SwTextPortion::CopyLinePortion(*this));
+ if( !pLeft )
+ {
+ pLeft = new SwMarginPortion;
+ pLeft->SetNextPortion( GetNextPortion() );
+ SetNextPortion( pLeft );
+ }
+ else
+ {
+ pLeft->Height( 0 );
+ pLeft->Width( 0 );
+ pLeft->SetLen(TextFrameIndex(0));
+ pLeft->SetAscent( 0 );
+ pLeft->SetNextPortion( nullptr );
+ pLeft->SetFixWidth(0);
+ }
+
+ SwLinePortion *pPos = pLeft->GetNextPortion();
+ while( pPos )
+ {
+ if( pPos->IsFlyPortion() )
+ {
+ // The FlyPortion gets sucked out...
+ pLeft->Join( static_cast<SwGluePortion*>(pPos) );
+ pPos = pLeft->GetNextPortion();
+ if( GetpKanaComp() && !GetKanaComp().empty() )
+ GetKanaComp().pop_front();
+ }
+ else
+ pPos = nullptr;
+ }
+ return pLeft;
+}
+
+void SwLineLayout::InitSpaceAdd()
+{
+ if ( !m_pLLSpaceAdd )
+ CreateSpaceAdd();
+ else
+ SetLLSpaceAdd( 0, 0 );
+}
+
+void SwLineLayout::CreateSpaceAdd( const tools::Long nInit )
+{
+ m_pLLSpaceAdd.reset( new std::vector<tools::Long> );
+ SetLLSpaceAdd( nInit, 0 );
+}
+
+// #i3952# Returns true if there are only blanks in [nStt, nEnd[
+// Used to implement IgnoreTabsAndBlanksForLineCalculation compat flag
+static bool lcl_HasOnlyBlanks(std::u16string_view rText, TextFrameIndex nStt, TextFrameIndex nEnd)
+{
+ while ( nStt < nEnd )
+ {
+ switch (rText[sal_Int32(nStt++)])
+ {
+ case 0x0020: // SPACE
+ case 0x2002: // EN SPACE
+ case 0x2003: // EM SPACE
+ case 0x2005: // FOUR-PER-EM SPACE
+ case 0x3000: // IDEOGRAPHIC SPACE
+ continue;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+// Swapped out from FormatLine()
+void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf )
+{
+ const sal_uInt16 nLineWidth = rInf.RealWidth();
+
+ sal_uInt16 nFlyAscent = 0;
+ sal_uInt16 nFlyHeight = 0;
+ sal_uInt16 nFlyDescent = 0;
+
+ // If this line has a clearing break, then this is the portion's height.
+ sal_uInt16 nBreakHeight = 0;
+
+ bool bOnlyPostIts = true;
+ SetHanging( false );
+
+ bool bTmpDummy = !GetLen();
+ SwFlyCntPortion* pFlyCnt = nullptr;
+ if( bTmpDummy )
+ {
+ nFlyAscent = 0;
+ nFlyHeight = 0;
+ nFlyDescent = 0;
+ }
+
+ // #i3952#
+ const bool bIgnoreBlanksAndTabsForLineHeightCalculation =
+ rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::IGNORE_TABS_AND_BLANKS_FOR_LINE_CALCULATION);
+
+ bool bHasBlankPortion = false;
+ bool bHasOnlyBlankPortions = true;
+ bool bHasFlyPortion = false;
+
+ if( mpNextPortion )
+ {
+ SetContent( false );
+ if( mpNextPortion->IsBreakPortion() )
+ {
+ SetLen( mpNextPortion->GetLen() );
+ if( GetLen() )
+ bTmpDummy = false;
+ }
+ else
+ {
+ const SwTwips nLineHeight = Height();
+ Init( GetNextPortion() );
+ SwLinePortion *pPos = mpNextPortion;
+ SwLinePortion *pLast = this;
+ sal_uInt16 nMaxDescent = 0;
+
+ // A group is a segment in the portion chain of pCurr or a fixed
+ // portion spanning to the end or the next fixed portion
+ while( pPos )
+ {
+ SAL_WARN_IF( PortionType::NONE == pPos->GetWhichPor(),
+ "sw.core", "SwLineLayout::CalcLine: don't use SwLinePortions !" );
+
+ // Null portions are eliminated. They can form if two FlyFrames
+ // overlap.
+ // coverity[deref_arg] - "Cut" means next "GetNextPortion" returns a different Portion
+ if( !pPos->Compress() )
+ {
+ // Only take over Height and Ascent if the rest of the line
+ // is empty.
+ if( !pPos->GetNextPortion() )
+ {
+ if( !Height() )
+ Height( pPos->Height(), false );
+ if( !GetAscent() )
+ SetAscent( pPos->GetAscent() );
+ }
+ SwLinePortion* pPortion = pLast->Cut( pPos );
+ rLine.ClearIfIsFirstOfBorderMerge(pPortion);
+ delete pPortion;
+ pPos = pLast->GetNextPortion();
+ continue;
+ }
+
+ TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + mnLineLength;
+ mnLineLength += pPos->GetLen();
+ AddPrtWidth( pPos->Width() );
+
+ // #i3952#
+ if (bIgnoreBlanksAndTabsForLineHeightCalculation && !rInf.GetLineStart())
+ {
+ if ( pPos->InTabGrp() || pPos->IsHolePortion() ||
+ ( pPos->IsTextPortion() &&
+ lcl_HasOnlyBlanks( rInf.GetText(), nPorSttIdx, nPorSttIdx + pPos->GetLen() ) ) )
+ {
+ pLast = pPos;
+ pPos = pPos->GetNextPortion();
+ bHasBlankPortion = true;
+ continue;
+ }
+ }
+
+ // Ignore drop portion height
+ // tdf#130804 ... and bookmark portions
+ if ((pPos->IsDropPortion() && static_cast<SwDropPortion*>(pPos)->GetLines() > 1)
+ || pPos->GetWhichPor() == PortionType::Bookmark)
+ {
+ pLast = pPos;
+ pPos = pPos->GetNextPortion();
+ continue;
+ }
+
+ bHasOnlyBlankPortions = false;
+
+ // We had an attribute change: Sum up/build maxima of length and mass
+
+ SwTwips nPosHeight = pPos->Height();
+ SwTwips nPosAscent = pPos->GetAscent();
+
+ SAL_WARN_IF( nPosHeight < nPosAscent,
+ "sw.core", "SwLineLayout::CalcLine: bad ascent or height" );
+
+ if( pPos->IsHangingPortion() )
+ {
+ SetHanging(true);
+ rInf.GetParaPortion()->SetMargin();
+ }
+ else if( !bHasFlyPortion && ( pPos->IsFlyCntPortion() || pPos->IsFlyPortion() ) )
+ bHasFlyPortion = true;
+
+ // A line break portion only influences the height of the line in case it's the only
+ // portion in the line, except when it's a clearing break.
+ bool bClearingBreak = false;
+ if (pPos->IsBreakPortion())
+ {
+ auto pBreakPortion = static_cast<SwBreakPortion*>(pPos);
+ bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE;
+ nBreakHeight = nPosHeight;
+ }
+ if (!(pPos->IsBreakPortion() && !bClearingBreak) || !Height())
+ {
+ if (!pPos->IsPostItsPortion()) bOnlyPostIts = false;
+
+ if( bTmpDummy && !mnLineLength )
+ {
+ if( pPos->IsFlyPortion() )
+ {
+ if( nFlyHeight < nPosHeight )
+ nFlyHeight = nPosHeight;
+ if( nFlyAscent < nPosAscent )
+ nFlyAscent = nPosAscent;
+ if( nFlyDescent < nPosHeight - nPosAscent )
+ nFlyDescent = nPosHeight - nPosAscent;
+ }
+ else
+ {
+ if( pPos->InNumberGrp() )
+ {
+ sal_uInt16 nTmp = rInf.GetFont()->GetAscent(
+ rInf.GetVsh(), *rInf.GetOut() );
+ if( nTmp > nPosAscent )
+ {
+ nPosHeight += nTmp - nPosAscent;
+ nPosAscent = nTmp;
+ }
+ nTmp = rInf.GetFont()->GetHeight( rInf.GetVsh(),
+ *rInf.GetOut() );
+ if( nTmp > nPosHeight )
+ nPosHeight = nTmp;
+ }
+ Height( nPosHeight, false );
+ mnAscent = nPosAscent;
+ nMaxDescent = nPosHeight - nPosAscent;
+ }
+ }
+ else if( !pPos->IsFlyPortion() )
+ {
+ if( Height() < nPosHeight )
+ {
+ // Height is set to 0 when Init() is called.
+ if (bIgnoreBlanksAndTabsForLineHeightCalculation && pPos->IsFlyCntPortion())
+ // Compat flag set: take the line height, if it's larger.
+ Height(std::max(nPosHeight, nLineHeight), false);
+ else
+ // Just care about the portion height.
+ Height(nPosHeight, pPos->IsTextPortion());
+ }
+ SwFlyCntPortion* pAsFly(nullptr);
+ if(pPos->IsFlyCntPortion())
+ pAsFly = static_cast<SwFlyCntPortion*>(pPos);
+ if( pAsFly || ( pPos->IsMultiPortion()
+ && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() ) )
+ rLine.SetFlyInCntBase();
+ if(pAsFly && pAsFly->GetAlign() != sw::LineAlign::NONE)
+ {
+ pAsFly->SetMax(false);
+ if( !pFlyCnt || pPos->Height() > pFlyCnt->Height() )
+ pFlyCnt = pAsFly;
+ }
+ else
+ {
+ if( mnAscent < nPosAscent )
+ mnAscent = nPosAscent;
+ if( nMaxDescent < nPosHeight - nPosAscent )
+ nMaxDescent = nPosHeight - nPosAscent;
+ }
+ }
+ }
+ else if( pPos->GetLen() )
+ bTmpDummy = false;
+
+ if( !HasContent() && !pPos->InNumberGrp() )
+ {
+ if ( pPos->InExpGrp() )
+ {
+ OUString aText;
+ if( pPos->GetExpText( rInf, aText ) && !aText.isEmpty() )
+ SetContent(true);
+ }
+ else if( ( pPos->InTextGrp() || pPos->IsMultiPortion() ) &&
+ pPos->GetLen() )
+ SetContent(true);
+ }
+
+ bTmpDummy &= !HasContent() && ( !pPos->Width() || pPos->IsFlyPortion() );
+
+ pLast = pPos;
+ pPos = pPos->GetNextPortion();
+ }
+
+ if( pFlyCnt )
+ {
+ if( pFlyCnt->Height() == Height() )
+ {
+ pFlyCnt->SetMax( true );
+ if( Height() > nMaxDescent + mnAscent )
+ {
+ if( sw::LineAlign::BOTTOM == pFlyCnt->GetAlign() )
+ mnAscent = Height() - nMaxDescent;
+ else if( sw::LineAlign::CENTER == pFlyCnt->GetAlign() )
+ mnAscent = ( Height() + mnAscent - nMaxDescent ) / 2;
+ }
+ pFlyCnt->SetAscent( mnAscent );
+ }
+ }
+
+ if( bTmpDummy && nFlyHeight )
+ {
+ mnAscent = nFlyAscent;
+ if( nFlyDescent > nFlyHeight - nFlyAscent )
+ Height( nFlyHeight + nFlyDescent, false );
+ else
+ {
+ if (nBreakHeight > nFlyHeight)
+ {
+ // The line has no content, but it has a clearing break: then the line
+ // height is not only the intersection of the fly and line's rectangle, but
+ // also includes the clearing break's height.
+ Height(nBreakHeight, false);
+ }
+ else
+ {
+ Height(nFlyHeight, false);
+ }
+ }
+ }
+ else if( nMaxDescent > Height() - mnAscent )
+ Height( nMaxDescent + mnAscent, false );
+
+ if( bOnlyPostIts && !( bHasBlankPortion && bHasOnlyBlankPortions ) )
+ {
+ Height( rInf.GetFont()->GetHeight( rInf.GetVsh(), *rInf.GetOut() ) );
+ mnAscent = rInf.GetFont()->GetAscent( rInf.GetVsh(), *rInf.GetOut() );
+ }
+ }
+ }
+ else
+ {
+ SetContent( !bTmpDummy );
+
+ // #i3952#
+ if ( bIgnoreBlanksAndTabsForLineHeightCalculation &&
+ lcl_HasOnlyBlanks( rInf.GetText(), rInf.GetLineStart(), rInf.GetLineStart() + GetLen() ) )
+ {
+ bHasBlankPortion = true;
+ }
+ }
+
+ // #i3952# Whitespace does not increase line height
+ if ( bHasBlankPortion && bHasOnlyBlankPortions )
+ {
+ sal_uInt16 nTmpAscent = GetAscent();
+ sal_uInt16 nTmpHeight = Height();
+ rLine.GetAttrHandler().GetDefaultAscentAndHeight( rInf.GetVsh(), *rInf.GetOut(), nTmpAscent, nTmpHeight );
+
+ short nEscapement = rLine.GetAttrHandler().GetFont()->GetEscapement();
+ if (GetAscent() && Height() && !nTmpAscent && !nTmpHeight
+ && (nEscapement == DFLT_ESC_AUTO_SUPER || nEscapement == DFLT_ESC_AUTO_SUB))
+ {
+ // We already had a calculated ascent + height, it would be cleared, automatic
+ // sub/superscript is set and we have no content. In this case it makes no sense to
+ // clear the old, correct ascent/height.
+ nTmpAscent = GetAscent();
+ nTmpHeight = Height();
+ }
+
+ if (nTmpAscent < GetAscent() || GetAscent() <= 0)
+ SetAscent(nTmpAscent);
+ if (nTmpHeight < Height() || Height() <= 0)
+ Height(nTmpHeight, false);
+ }
+
+ // Robust:
+ if( nLineWidth < Width() )
+ Width( nLineWidth );
+ SAL_WARN_IF( nLineWidth < Width(), "sw.core", "SwLineLayout::CalcLine: line is bursting" );
+ SetDummy( bTmpDummy );
+ std::pair<SwTextNode const*, sal_Int32> const start(
+ rInf.GetTextFrame()->MapViewToModel(rLine.GetStart()));
+ std::pair<SwTextNode const*, sal_Int32> const end(
+ rInf.GetTextFrame()->MapViewToModel(rLine.GetEnd()));
+ bool bHasRedline = rLine.GetRedln();
+ if( bHasRedline )
+ {
+ OUString sRedlineText;
+ bool bHasRedlineEnd;
+ enum RedlineType eRedlineEnd;
+ bHasRedline = rLine.GetRedln()->CheckLine(start.first->GetIndex(), start.second,
+ end.first->GetIndex(), end.second, sRedlineText, bHasRedlineEnd, eRedlineEnd);
+ if( bHasRedline )
+ {
+ SetRedlineText( sRedlineText );
+ if( bHasRedlineEnd )
+ SetRedlineEnd( bHasRedlineEnd );
+ if( eRedlineEnd != RedlineType::None )
+ SetRedlineEndType( eRedlineEnd );
+ }
+ }
+ SetRedline( bHasRedline );
+
+ // redlining: set crossing out for deleted anchored objects
+ if ( !bHasFlyPortion )
+ return;
+
+ SwLinePortion *pPos = mpNextPortion;
+ TextFrameIndex nLineLength;
+ while ( pPos )
+ {
+ TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength;
+ nLineLength += pPos->GetLen();
+ // anchored as characters
+ if( pPos->IsFlyCntPortion() )
+ {
+ bool bDeleted = false;
+ size_t nAuthor = std::string::npos;
+ if ( bHasRedline )
+ {
+ OUString sRedlineText;
+ bool bHasRedlineEnd;
+ enum RedlineType eRedlineEnd;
+ std::pair<SwTextNode const*, sal_Int32> const flyStart(
+ rInf.GetTextFrame()->MapViewToModel(nPorSttIdx));
+ bool bHasFlyRedline = rLine.GetRedln()->CheckLine(flyStart.first->GetIndex(),
+ flyStart.second, flyStart.first->GetIndex(), flyStart.second, sRedlineText,
+ bHasRedlineEnd, eRedlineEnd, /*pAuthorAtPos=*/&nAuthor);
+ bDeleted = bHasFlyRedline && eRedlineEnd == RedlineType::Delete;
+ }
+ static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted);
+ static_cast<SwFlyCntPortion*>(pPos)->SetAuthor(nAuthor);
+ }
+ // anchored to characters
+ else if ( pPos->IsFlyPortion() )
+ {
+ const IDocumentRedlineAccess& rIDRA =
+ rInf.GetTextFrame()->GetDoc().getIDocumentRedlineAccess();
+ SwSortedObjs *pObjs = rInf.GetTextFrame()->GetDrawObjs();
+ if ( pObjs && IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) )
+ {
+ for ( size_t i = 0; rInf.GetTextFrame()->GetDrawObjs() && i < pObjs->size(); ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i];
+ if ( auto pFly = pAnchoredObj->DynCastFlyFrame() )
+ {
+ bool bDeleted = false;
+ size_t nAuthor = std::string::npos;
+ const SwFormatAnchor& rAnchor = pAnchoredObj->GetFrameFormat().GetAnchor();
+ if ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR )
+ {
+ SwPosition aAnchor = *rAnchor.GetContentAnchor();
+ SwRedlineTable::size_type n = 0;
+ const SwRangeRedline* pFnd =
+ rIDRA.GetRedlineTable().FindAtPosition( aAnchor, n );
+ if ( pFnd && RedlineType::Delete == pFnd->GetType() )
+ {
+ bDeleted = true;
+ nAuthor = pFnd->GetAuthor();
+ }
+ }
+ pFly->SetDeleted(bDeleted);
+ pFly->SetAuthor(nAuthor);
+ }
+ }
+ }
+ }
+ pPos = pPos->GetNextPortion();
+ }
+}
+
+// #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor>
+// to control, if the fly content portions and line portion are considered.
+void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent,
+ SwTwips& _orDescent,
+ SwTwips& _orObjAscent,
+ SwTwips& _orObjDescent,
+ const SwLinePortion* _pDontConsiderPortion,
+ const bool _bNoFlyCntPorAndLinePor ) const
+{
+ _orAscent = 0;
+ _orDescent = 0;
+ _orObjAscent = 0;
+ _orObjDescent = 0;
+
+ const SwLinePortion* pTmpPortion = this;
+ if ( !pTmpPortion->GetLen() && pTmpPortion->GetNextPortion() )
+ {
+ pTmpPortion = pTmpPortion->GetNextPortion();
+ }
+
+ while ( pTmpPortion )
+ {
+ if ( !pTmpPortion->IsBreakPortion() && !pTmpPortion->IsFlyPortion() &&
+ // tdf#130804 ignore bookmark portions
+ pTmpPortion->GetWhichPor() != PortionType::Bookmark &&
+ ( !_bNoFlyCntPorAndLinePor ||
+ ( !pTmpPortion->IsFlyCntPortion() &&
+ !(pTmpPortion == this && pTmpPortion->GetNextPortion() ) ) ) )
+ {
+ SwTwips nPortionAsc = pTmpPortion->GetAscent();
+ SwTwips nPortionDesc = pTmpPortion->Height() - nPortionAsc;
+
+ const bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ?
+ static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() :
+ ( pTmpPortion != _pDontConsiderPortion );
+
+ if ( bFlyCmp )
+ {
+ _orObjAscent = std::max( _orObjAscent, nPortionAsc );
+ _orObjDescent = std::max( _orObjDescent, nPortionDesc );
+ }
+
+ if ( !pTmpPortion->IsFlyCntPortion() && !pTmpPortion->IsGrfNumPortion() )
+ {
+ _orAscent = std::max( _orAscent, nPortionAsc );
+ _orDescent = std::max( _orDescent, nPortionDesc );
+ }
+ }
+ pTmpPortion = pTmpPortion->GetNextPortion();
+ }
+}
+
+void SwLineLayout::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwLineLayout::ResetFlags()
+{
+ m_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bFly
+ = m_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline
+ = m_bRedlineEnd = m_bForcedLeftMargin = m_bHanging = false;
+ m_eRedlineEnd = RedlineType::None;
+}
+
+SwLineLayout::SwLineLayout()
+ : m_pNext( nullptr ),
+ m_nRealHeight( 0 ),
+ m_nTextHeight( 0 ),
+ m_bUnderscore( false )
+{
+ ResetFlags();
+ SetWhichPor( PortionType::Lay );
+}
+
+SwLinePortion *SwLineLayout::GetFirstPortion() const
+{
+ const SwLinePortion *pRet = mpNextPortion ? mpNextPortion : this;
+ return const_cast<SwLinePortion*>(pRet);
+}
+
+SwCharRange &SwCharRange::operator+=(const SwCharRange &rRange)
+{
+ if (TextFrameIndex(0) != rRange.m_nLen)
+ {
+ if (TextFrameIndex(0) == m_nLen) {
+ m_nStart = rRange.m_nStart;
+ m_nLen = rRange.m_nLen ;
+ }
+ else {
+ if(rRange.m_nStart + rRange.m_nLen > m_nStart + m_nLen) {
+ m_nLen = rRange.m_nStart + rRange.m_nLen - m_nStart;
+ }
+ if(rRange.m_nStart < m_nStart) {
+ m_nLen += m_nStart - rRange.m_nStart;
+ m_nStart = rRange.m_nStart;
+ }
+ }
+ }
+ return *this;
+}
+
+SwScriptInfo::SwScriptInfo()
+ : m_nInvalidityPos(0)
+ , m_nDefaultDir(0)
+{
+};
+
+SwScriptInfo::~SwScriptInfo()
+{
+}
+
+// Converts i18n Script Type (LATIN, ASIAN, COMPLEX, WEAK) to
+// Sw Script Types (SwFontScript::Latin, SwFontScript::CJK, SwFontScript::CTL), used to identify the font
+static SwFontScript lcl_ScriptToFont(sal_uInt16 const nScript)
+{
+ switch ( nScript ) {
+ case i18n::ScriptType::LATIN : return SwFontScript::Latin;
+ case i18n::ScriptType::ASIAN : return SwFontScript::CJK;
+ case i18n::ScriptType::COMPLEX : return SwFontScript::CTL;
+ }
+
+ OSL_FAIL( "Somebody tells lies about the script type!" );
+ return SwFontScript::Latin;
+}
+
+SwFontScript SwScriptInfo::WhichFont(TextFrameIndex const nIdx) const
+{
+ const sal_uInt16 nScript(ScriptType(nIdx));
+ return lcl_ScriptToFont(nScript);
+}
+
+SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText)
+{
+ const sal_uInt16 nScript(g_pBreakIt->GetRealScriptOfText(rText, nIdx));
+ return lcl_ScriptToFont(nScript);
+}
+
+static Color getBookmarkColor(const SwTextNode& rNode, const sw::mark::IBookmark* pBookmark)
+{
+ // search custom color in metadata, otherwise use COL_TRANSPARENT;
+ Color c = COL_TRANSPARENT;
+
+ try
+ {
+ SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc());
+ const rtl::Reference< SwXBookmark > xRef = SwXBookmark::CreateXBookmark(rDoc,
+ const_cast<sw::mark::IMark*>(static_cast<const sw::mark::IMark*>(pBookmark)));
+ const css::uno::Reference<css::rdf::XResource> xSubject(xRef);
+ uno::Reference<frame::XModel> xModel = rDoc.GetDocShell()->GetBaseModel();
+
+ static uno::Reference< uno::XComponentContext > xContext(
+ ::comphelper::getProcessComponentContext());
+
+ static uno::Reference< rdf::XURI > xODF_SHADING(
+ rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);
+
+ uno::Reference<rdf::XDocumentMetadataAccess> xDocumentMetadataAccess(
+ rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY);
+ const uno::Reference<rdf::XRepository>& xRepository =
+ xDocumentMetadataAccess->getRDFRepository();
+ const uno::Reference<container::XEnumeration> xEnum(
+ xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);
+
+ rdf::Statement stmt;
+ if ( xEnum->hasMoreElements() && (xEnum->nextElement() >>= stmt) )
+ {
+ const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
+ if ( xObject.is() )
+ c = Color::STRtoRGB(xObject->getValue());
+ }
+ }
+ catch (const lang::IllegalArgumentException&)
+ {
+ }
+
+ return c;
+}
+
+static void InitBookmarks(
+ std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter,
+ std::vector<sw::Extent>::const_iterator iter,
+ std::vector<sw::Extent>::const_iterator const end,
+ TextFrameIndex nOffset,
+ std::vector<std::pair<sw::mark::IBookmark const*, SwScriptInfo::MarkKind>> & rBookmarks,
+ std::vector<std::tuple<TextFrameIndex, SwScriptInfo::MarkKind, Color, OUString>> & o_rBookmarks)
+{
+ SwTextNode const*const pNode(iter->pNode);
+ for (auto const& it : rBookmarks)
+ {
+ assert(iter->pNode == pNode || pNode->GetIndex() < iter->pNode->GetIndex());
+ assert(!oPrevIter || (*oPrevIter)->pNode->GetIndex() <= pNode->GetIndex());
+
+ // search for custom bookmark boundary mark color
+ Color c = getBookmarkColor(*pNode, it.first);
+
+ switch (it.second)
+ {
+ case SwScriptInfo::MarkKind::Start:
+ {
+ // SwUndoSaveContent::DelContentIndex() is rather messy but
+ // apparently bookmarks "on the edge" are deleted if
+ // * point: equals start-of-selection (not end-of-selection)
+ // * expanded: one position equals edge of selection
+ // and other does not (is inside)
+ // interesting case: if end[/start] of the mark is on the
+ // start of first[/end of last] extent, and the other one
+ // is outside this merged paragraph, is it deleted or not?
+ // assume "no" because the line break it contains isn't deleted.
+ SwPosition const& rStart(it.first->GetMarkStart());
+ SwPosition const& rEnd(it.first->GetMarkEnd());
+ assert(&rStart.GetNode() == pNode);
+ while (iter != end)
+ {
+ if (&rStart.GetNode() != iter->pNode // iter moved to next node
+ || rStart.GetContentIndex() < iter->nStart)
+ {
+ if (rEnd.GetNodeIndex() < iter->pNode->GetIndex()
+ || (&rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() <= iter->nStart))
+ {
+ break; // deleted - skip it
+ }
+ else
+ {
+ o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
+ break;
+ }
+ }
+ else if (rStart.GetContentIndex() <= iter->nEnd)
+ {
+ auto const iterNext(iter + 1);
+ if (rStart.GetContentIndex() == iter->nEnd
+ && (iterNext == end
+ ? &rEnd.GetNode() == iter->pNode
+ : (rEnd.GetNodeIndex() < iterNext->pNode->GetIndex()
+ || (&rEnd.GetNode() == iterNext->pNode && rEnd.GetContentIndex() < iterNext->nStart))))
+ {
+ break; // deleted - skip it
+ }
+ else
+ {
+ o_rBookmarks.emplace_back(
+ nOffset + TextFrameIndex(rStart.GetContentIndex() - iter->nStart),
+ it.second, c, it.first->GetName());
+ break;
+ }
+ }
+ else
+ {
+ nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
+ oPrevIter = iter;
+ ++iter; // bookmarks are sorted...
+ }
+ }
+ if (iter == end)
+ {
+ if (pNode->GetIndex() < rEnd.GetNodeIndex()) // pNode is last node of merged
+ {
+ break; // deleted - skip it
+ }
+ else
+ {
+ o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
+ }
+ }
+ break;
+ }
+ case SwScriptInfo::MarkKind::End:
+ {
+ SwPosition const& rEnd(it.first->GetMarkEnd());
+ assert(&rEnd.GetNode() == pNode);
+ while (true)
+ {
+ if (iter == end
+ || &rEnd.GetNode() != iter->pNode // iter moved to next node
+ || rEnd.GetContentIndex() <= iter->nStart)
+ {
+ SwPosition const& rStart(it.first->GetMarkStart());
+ // oPrevIter may point to pNode or a preceding node
+ if (oPrevIter
+ ? ((*oPrevIter)->pNode->GetIndex() < rStart.GetNodeIndex()
+ || ((*oPrevIter)->pNode == &rStart.GetNode()
+ && ((iter != end && &rEnd.GetNode() == iter->pNode && rEnd.GetContentIndex() == iter->nStart)
+ ? (*oPrevIter)->nEnd < rStart.GetContentIndex()
+ : (*oPrevIter)->nEnd <= rStart.GetContentIndex())))
+ : rStart.GetNode() == rEnd.GetNode())
+ {
+ break; // deleted - skip it
+ }
+ else
+ {
+ o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName());
+ break;
+ }
+ }
+ else if (rEnd.GetContentIndex() <= iter->nEnd)
+ {
+ o_rBookmarks.emplace_back(
+ nOffset + TextFrameIndex(rEnd.GetContentIndex() - iter->nStart),
+ it.second, c, it.first->GetName());
+ break;
+ }
+ else
+ {
+ nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
+ oPrevIter = iter;
+ ++iter;
+ }
+ }
+ break;
+ }
+ case SwScriptInfo::MarkKind::Point:
+ {
+ SwPosition const& rPos(it.first->GetMarkPos());
+ assert(&rPos.GetNode() == pNode);
+ while (iter != end)
+ {
+ if (&rPos.GetNode() != iter->pNode // iter moved to next node
+ || rPos.GetContentIndex() < iter->nStart)
+ {
+ break; // deleted - skip it
+ }
+ else if (rPos.GetContentIndex() <= iter->nEnd)
+ {
+ if (rPos.GetContentIndex() == iter->nEnd
+ && rPos.GetContentIndex() != iter->pNode->Len())
+ {
+ break; // deleted - skip it
+ }
+ else
+ {
+ o_rBookmarks.emplace_back(
+ nOffset + TextFrameIndex(rPos.GetContentIndex() - iter->nStart),
+ it.second, c, it.first->GetName());
+ }
+ break;
+ }
+ else
+ {
+ nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
+ oPrevIter = iter;
+ ++iter;
+ }
+ }
+ break;
+ }
+ }
+ if (iter == end)
+ {
+ break; // remaining marks are hidden
+ }
+ }
+}
+
+// searches for script changes in rText and stores them
+void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
+ sw::MergedPara const*const pMerged)
+{
+ InitScriptInfo( rNode, pMerged, m_nDefaultDir == UBIDI_RTL );
+}
+
+void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
+ sw::MergedPara const*const pMerged, bool bRTL)
+{
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ const OUString& rText(pMerged ? pMerged->mergedText : rNode.GetText());
+
+ // HIDDEN TEXT INFORMATION
+
+ m_Bookmarks.clear();
+ m_HiddenChg.clear();
+ if (pMerged)
+ {
+ SwTextNode const* pNode(nullptr);
+ TextFrameIndex nOffset(0);
+ std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter;
+ for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end();
+ oPrevIter = iter)
+ {
+ if (iter->pNode == pNode)
+ {
+ nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
+ ++iter;
+ continue; // skip extents at end of previous node
+ }
+ pNode = iter->pNode;
+ Range aRange( 0, pNode->Len() > 0 ? pNode->Len() - 1 : 0 );
+ MultiSelection aHiddenMulti( aRange );
+ std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks;
+ CalcHiddenRanges(*pNode, aHiddenMulti, &bookmarks);
+
+ InitBookmarks(oPrevIter, iter, pMerged->extents.end(), nOffset, bookmarks, m_Bookmarks);
+
+ for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
+ {
+ const Range& rRange = aHiddenMulti.GetRange( i );
+ const sal_Int32 nStart = rRange.Min();
+ const sal_Int32 nEnd = rRange.Max() + 1;
+ bool isStartHandled(false);
+ ::std::optional<sal_Int32> oExtend;
+
+ if (nEnd <= iter->nStart)
+ { // entirely in gap, skip this hidden range
+ continue;
+ }
+
+ do
+ {
+ if (!isStartHandled && nStart <= iter->nEnd)
+ {
+ isStartHandled = true;
+ if (nStart <= iter->nStart && !m_HiddenChg.empty()
+ && m_HiddenChg.back() == nOffset)
+ {
+ // previous one went until end of extent, extend it
+ oExtend.emplace(::std::min(iter->nEnd, nEnd) - ::std::max(iter->nStart, nStart));
+ }
+ else
+ {
+ m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nStart - iter->nStart, sal_Int32(0))));
+ }
+ }
+ else if (oExtend)
+ {
+ *oExtend += ::std::min(iter->nEnd, nEnd) - iter->nStart;
+ }
+ if (nEnd <= iter->nEnd)
+ {
+ if (oExtend)
+ {
+ m_HiddenChg.back() += TextFrameIndex(*oExtend);
+ }
+ else
+ {
+ m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nEnd - iter->nStart, sal_Int32(0))));
+ }
+ break; // iterate to next hidden range
+ }
+ nOffset += TextFrameIndex(iter->nEnd - iter->nStart);
+ ++iter;
+ }
+ while (iter != pMerged->extents.end() && iter->pNode == pNode);
+ if (iter == pMerged->extents.end() || iter->pNode != pNode)
+ {
+ if (isStartHandled)
+ { // dangling end
+ if (oExtend)
+ {
+ m_HiddenChg.back() += TextFrameIndex(*oExtend);
+ }
+ else
+ {
+ m_HiddenChg.push_back(nOffset);
+ }
+ } // else: beyond last extent in node, ignore
+ break; // skip hidden ranges beyond last extent in node
+ }
+ }
+ }
+ }
+ else
+ {
+ Range aRange( 0, !rText.isEmpty() ? rText.getLength() - 1 : 0 );
+ MultiSelection aHiddenMulti( aRange );
+ std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> bookmarks;
+ CalcHiddenRanges(rNode, aHiddenMulti, &bookmarks);
+
+ for (auto const& it : bookmarks)
+ {
+ // don't show __RefHeading__ bookmarks, which are hidden in Navigator, too
+ // (They are inserted automatically e.g. with the ToC at the beginning of
+ // the headings)
+ if (it.first->GetName().startsWith(
+ IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()))
+ {
+ continue;
+ }
+
+ // search for custom bookmark boundary mark color
+ Color c = getBookmarkColor(rNode, it.first);
+
+ switch (it.second)
+ {
+ case MarkKind::Start:
+ m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkStart().GetContentIndex()), it.second, c, it.first->GetName());
+ break;
+ case MarkKind::End:
+ m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkEnd().GetContentIndex()), it.second, c, it.first->GetName());
+ break;
+ case MarkKind::Point:
+ m_Bookmarks.emplace_back(TextFrameIndex(it.first->GetMarkPos().GetContentIndex()), it.second, c, it.first->GetName());
+ break;
+ }
+ }
+
+ m_HiddenChg.reserve( aHiddenMulti.GetRangeCount() * 2 );
+ for (sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i)
+ {
+ const Range& rRange = aHiddenMulti.GetRange( i );
+ const sal_Int32 nStart = rRange.Min();
+ const sal_Int32 nEnd = rRange.Max() + (rText.isEmpty() ? 0 : 1);
+
+ m_HiddenChg.push_back( TextFrameIndex(nStart) );
+ m_HiddenChg.push_back( TextFrameIndex(nEnd) );
+ }
+ }
+
+ // SCRIPT AND SCRIPT RELATED INFORMATION
+
+ TextFrameIndex nChg = m_nInvalidityPos;
+
+ // COMPLETE_STRING means the data structure is up to date
+ m_nInvalidityPos = TextFrameIndex(COMPLETE_STRING);
+
+ // this is the default direction
+ m_nDefaultDir = static_cast<sal_uInt8>(bRTL ? UBIDI_RTL : UBIDI_LTR);
+
+ // counter for script info arrays
+ size_t nCnt = 0;
+ // counter for compression information arrays
+ size_t nCntComp = 0;
+ // counter for kashida array
+ size_t nCntKash = 0;
+
+ sal_Int16 nScript = i18n::ScriptType::LATIN;
+
+ // compression type
+ const CharCompressType aCompEnum = rNode.getIDocumentSettingAccess()->getCharacterCompressionType();
+
+ auto const& rParaItems((pMerged ? *pMerged->pParaPropsNode : rNode).GetSwAttrSet());
+ // justification type
+ const bool bAdjustBlock = SvxAdjust::Block == rParaItems.GetAdjust().GetAdjust();
+
+ // FIND INVALID RANGES IN SCRIPT INFO ARRAYS:
+
+ if( nChg )
+ {
+ // if change position = 0 we do not use any data from the arrays
+ // because by deleting all characters of the first group at the beginning
+ // of a paragraph nScript is set to a wrong value
+ SAL_WARN_IF( !CountScriptChg(), "sw.core", "Where're my changes of script?" );
+ while( nCnt < CountScriptChg() )
+ {
+ if ( nChg > GetScriptChg( nCnt ) )
+ nCnt++;
+ else
+ {
+ nScript = GetScriptType( nCnt );
+ break;
+ }
+ }
+ if( CharCompressType::NONE != aCompEnum )
+ {
+ while( nCntComp < CountCompChg() )
+ {
+ if ( nChg <= GetCompStart( nCntComp ) )
+ break;
+ nCntComp++;
+ }
+ }
+ if ( bAdjustBlock )
+ {
+ while( nCntKash < CountKashida() )
+ {
+ if ( nChg <= GetKashida( nCntKash ) )
+ break;
+ nCntKash++;
+ }
+ }
+ }
+
+ // ADJUST nChg VALUE:
+
+ // by stepping back one position we know that we are inside a group
+ // declared as an nScript group
+ if ( nChg )
+ --nChg;
+
+ const TextFrameIndex nGrpStart = nCnt ? GetScriptChg(nCnt - 1) : TextFrameIndex(0);
+
+ // we go back in our group until we reach the first character of
+ // type nScript
+ while ( nChg > nGrpStart &&
+ nScript != g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)))
+ --nChg;
+
+ // If we are at the start of a group, we do not trust nScript,
+ // we better get nScript from the breakiterator:
+ if ( nChg == nGrpStart )
+ nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
+
+ // INVALID DATA FROM THE SCRIPT INFO ARRAYS HAS TO BE DELETED:
+
+ // remove invalid entries from script information arrays
+ m_ScriptChanges.erase(m_ScriptChanges.begin() + nCnt, m_ScriptChanges.end());
+
+ // get the start of the last compression group
+ TextFrameIndex nLastCompression = nChg;
+ if( nCntComp )
+ {
+ --nCntComp;
+ nLastCompression = GetCompStart( nCntComp );
+ if( nChg >= nLastCompression + GetCompLen( nCntComp ) )
+ {
+ nLastCompression = nChg;
+ ++nCntComp;
+ }
+ }
+
+ // remove invalid entries from compression information arrays
+ m_CompressionChanges.erase(m_CompressionChanges.begin() + nCntComp,
+ m_CompressionChanges.end());
+
+ // get the start of the last kashida group
+ TextFrameIndex nLastKashida = nChg;
+ if( nCntKash && i18n::ScriptType::COMPLEX == nScript )
+ {
+ --nCntKash;
+ nLastKashida = GetKashida( nCntKash );
+ }
+
+ // remove invalid entries from kashida array
+ m_Kashida.erase(m_Kashida.begin() + nCntKash, m_Kashida.end());
+
+ // TAKE CARE OF WEAK CHARACTERS: WE MUST FIND AN APPROPRIATE
+ // SCRIPT FOR WEAK CHARACTERS AT THE BEGINNING OF A PARAGRAPH
+
+ if (WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)))
+ {
+ // If the beginning of the current group is weak, this means that
+ // all of the characters in this group are weak. We have to assign
+ // the scripts to these characters depending on the fonts which are
+ // set for these characters to display them.
+ TextFrameIndex nEnd(
+ g_pBreakIt->GetBreakIter()->endOfScript(rText, sal_Int32(nChg), WEAK));
+
+ if (nEnd > TextFrameIndex(rText.getLength()) || nEnd < TextFrameIndex(0))
+ nEnd = TextFrameIndex(rText.getLength());
+
+ nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
+
+ SAL_WARN_IF( i18n::ScriptType::LATIN != nScript &&
+ i18n::ScriptType::ASIAN != nScript &&
+ i18n::ScriptType::COMPLEX != nScript, "sw.core", "Wrong default language" );
+
+ nChg = nEnd;
+
+ // Get next script type or set to weak in order to exit
+ sal_uInt8 nNextScript = (nEnd < TextFrameIndex(rText.getLength()))
+ ? static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nEnd)))
+ : sal_uInt8(WEAK);
+
+ if ( nScript != nNextScript )
+ {
+ m_ScriptChanges.emplace_back(nEnd, nScript);
+ nCnt++;
+ nScript = nNextScript;
+ }
+ }
+
+ // UPDATE THE SCRIPT INFO ARRAYS:
+
+ while (nChg < TextFrameIndex(rText.getLength())
+ || (m_ScriptChanges.empty() && rText.isEmpty()))
+ {
+ SAL_WARN_IF( i18n::ScriptType::WEAK == nScript,
+ "sw.core", "Inserting WEAK into SwScriptInfo structure" );
+
+ TextFrameIndex nSearchStt = nChg;
+ nChg = TextFrameIndex(g_pBreakIt->GetBreakIter()->endOfScript(
+ rText, sal_Int32(nSearchStt), nScript));
+
+ if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0))
+ nChg = TextFrameIndex(rText.getLength());
+
+ // special case for dotted circle since it can be used with complex
+ // before a mark, so we want it associated with the mark's script
+ // tdf#112594: another special case for NNBSP followed by a Mongolian
+ // character, since NNBSP has special uses in Mongolian (tdf#112594)
+ auto nPos = sal_Int32(nChg);
+ auto nPrevPos = nPos;
+ auto nPrevChar = rText.iterateCodePoints(&nPrevPos, -1);
+ if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0) &&
+ i18n::ScriptType::WEAK == g_pBreakIt->GetBreakIter()->getScriptType(rText, nPrevPos))
+ {
+ auto nChar = rText.iterateCodePoints(&nPos, 0);
+ auto nType = unicode::getUnicodeType(nChar);
+ if (nType == css::i18n::UnicodeType::NON_SPACING_MARK ||
+ nType == css::i18n::UnicodeType::ENCLOSING_MARK ||
+ nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK ||
+ (nPrevChar == CHAR_NNBSP &&
+ u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN))
+ {
+ nPos = nPrevPos;
+ }
+ }
+ m_ScriptChanges.emplace_back(TextFrameIndex(nPos), nScript);
+ ++nCnt;
+
+ // if current script is asian, we search for compressible characters
+ // in this range
+ if ( CharCompressType::NONE != aCompEnum &&
+ i18n::ScriptType::ASIAN == nScript )
+ {
+ CompType ePrevState = NONE;
+ CompType eState = NONE;
+ TextFrameIndex nPrevChg = nLastCompression;
+
+ while ( nLastCompression < nChg )
+ {
+ sal_Unicode cChar = rText[ sal_Int32(nLastCompression) ];
+
+ // examine current character
+ switch ( cChar )
+ {
+ // Left punctuation found
+ case 0x3008: case 0x300A: case 0x300C: case 0x300E:
+ case 0x3010: case 0x3014: case 0x3016: case 0x3018:
+ case 0x301A: case 0x301D:
+ case 0xFF08: case 0xFF3B: case 0xFF5B:
+ eState = SPECIAL_LEFT;
+ break;
+ // Right punctuation found
+ case 0x3009: case 0x300B:
+ case 0x300D: case 0x300F: case 0x3011: case 0x3015:
+ case 0x3017: case 0x3019: case 0x301B: case 0x301E:
+ case 0x301F:
+ case 0xFF09: case 0xFF3D: case 0xFF5D:
+ eState = SPECIAL_RIGHT;
+ break;
+ case 0x3001: case 0x3002: // Fullstop or comma
+ case 0xFF0C: case 0xFF0E: case 0xFF1A: case 0xFF1B:
+ eState = SPECIAL_MIDDLE ;
+ break;
+ default:
+ eState = ( 0x3040 <= cChar && 0x3100 > cChar ) ? KANA : NONE;
+ }
+
+ // insert range of compressible characters
+ if( ePrevState != eState )
+ {
+ if ( ePrevState != NONE )
+ {
+ // insert start and type
+ if ( CharCompressType::PunctuationAndKana == aCompEnum ||
+ ePrevState != KANA )
+ {
+ m_CompressionChanges.emplace_back(nPrevChg,
+ nLastCompression - nPrevChg, ePrevState);
+ }
+ }
+
+ ePrevState = eState;
+ nPrevChg = nLastCompression;
+ }
+
+ nLastCompression++;
+ }
+
+ // we still have to examine last entry
+ if ( ePrevState != NONE )
+ {
+ // insert start and type
+ if ( CharCompressType::PunctuationAndKana == aCompEnum ||
+ ePrevState != KANA )
+ {
+ m_CompressionChanges.emplace_back(nPrevChg,
+ nLastCompression - nPrevChg, ePrevState);
+ }
+ }
+ }
+
+ // we search for connecting opportunities (kashida)
+ else if ( bAdjustBlock && i18n::ScriptType::COMPLEX == nScript )
+ {
+ // sw_redlinehide: this is the only place that uses SwScanner with
+ // frame text, so we convert to sal_Int32 here
+ std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfCharM(
+ [&pMerged](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar)
+ {
+ std::pair<SwTextNode const*, sal_Int32> const pos(
+ sw::MapViewToModel(*pMerged, TextFrameIndex(nBegin)));
+ return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, script);
+ });
+ std::function<LanguageType (sal_Int32, sal_Int32, bool)> const pGetLangOfChar1(
+ [&rNode](sal_Int32 const nBegin, sal_uInt16 const script, bool const bNoChar)
+ { return rNode.GetLang(nBegin, bNoChar ? 0 : 1, script); });
+ auto pGetLangOfChar(pMerged ? pGetLangOfCharM : pGetLangOfChar1);
+ SwScanner aScanner( std::move(pGetLangOfChar), rText, nullptr, ModelToViewHelper(),
+ i18n::WordType::DICTIONARY_WORD,
+ sal_Int32(nLastKashida), sal_Int32(nChg));
+
+ // the search has to be performed on a per word base
+ while ( aScanner.NextWord() )
+ {
+ const OUString& rWord = aScanner.GetWord();
+
+ sal_Int32 nIdx = 0, nPrevIdx = 0;
+ sal_Int32 nKashidaPos = -1;
+ sal_Unicode cCh, cPrevCh = 0;
+
+ int nPriorityLevel = 7; // 0..6 = level found
+ // 7 not found
+
+ sal_Int32 nWordLen = rWord.getLength();
+
+ // ignore trailing vowel chars
+ while( nWordLen && isTransparentChar( rWord[ nWordLen - 1 ] ))
+ --nWordLen;
+
+ while (nIdx < nWordLen)
+ {
+ cCh = rWord[ nIdx ];
+
+ // 1. Priority:
+ // after user inserted kashida
+ if ( 0x640 == cCh )
+ {
+ nKashidaPos = aScanner.GetBegin() + nIdx;
+ nPriorityLevel = 0;
+ }
+
+ // 2. Priority:
+ // after a Seen or Sad
+ if (nPriorityLevel >= 1 && nIdx < nWordLen - 1)
+ {
+ if( isSeenOrSadChar( cCh )
+ && (rWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion
+ {
+ nKashidaPos = aScanner.GetBegin() + nIdx;
+ nPriorityLevel = 1;
+ }
+ }
+
+ // 3. Priority:
+ // before final form of Teh Marbuta, Heh, Dal
+ if ( nPriorityLevel >= 2 && nIdx > 0 )
+ {
+ if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining)
+ isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word
+ ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word
+ {
+
+ SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aScanner.GetBegin() + nPrevIdx;
+ nPriorityLevel = 2;
+ }
+ }
+ }
+
+ // 4. Priority:
+ // before final form of Alef, Tah, Lam, Kaf or Gaf
+ if ( nPriorityLevel >= 3 && nIdx > 0 )
+ {
+ if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word
+ (( isLamChar ( cCh ) || // Lam,
+ isTahChar ( cCh ) || // Tah,
+ isKafChar ( cCh ) || // Kaf (all dual joining)
+ isGafChar ( cCh ) )
+ && nIdx == nWordLen - 1)) // only at end of word
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aScanner.GetBegin() + nPrevIdx;
+ nPriorityLevel = 3;
+ }
+ }
+ }
+
+ // 5. Priority:
+ // before medial Beh-like
+ if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 )
+ {
+ if ( isBehChar ( cCh ) )
+ {
+ // check if next character is Reh or Yeh-like
+ sal_Unicode cNextCh = rWord[ nIdx + 1 ];
+ if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh ))
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aScanner.GetBegin() + nPrevIdx;
+ nPriorityLevel = 4;
+ }
+ }
+ }
+ }
+
+ // 6. Priority:
+ // before the final form of Waw, Ain, Qaf and Feh
+ if ( nPriorityLevel >= 5 && nIdx > 0 )
+ {
+ if ( isWawChar ( cCh ) || // Wav (right joining)
+ // final form may appear in the middle of word
+ (( isAinChar ( cCh ) || // Ain (dual joining)
+ isQafChar ( cCh ) || // Qaf (dual joining)
+ isFehChar ( cCh ) ) // Feh (dual joining)
+ && nIdx == nWordLen - 1)) // only at end of word
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aScanner.GetBegin() + nPrevIdx;
+ nPriorityLevel = 5;
+ }
+ }
+ }
+
+ // other connecting possibilities
+ if ( nPriorityLevel >= 6 && nIdx > 0 )
+ {
+ // Reh, Zain
+ if ( isRehChar ( cCh ) )
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "sw.core", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aScanner.GetBegin() + nPrevIdx;
+ nPriorityLevel = 6;
+ }
+ }
+ }
+
+ // Do not consider vowel marks when checking if a character
+ // can be connected to previous character.
+ if ( !isTransparentChar ( cCh) )
+ {
+ cPrevCh = cCh;
+ nPrevIdx = nIdx;
+ }
+
+ ++nIdx;
+ } // end of current word
+
+ if ( -1 != nKashidaPos )
+ {
+ m_Kashida.insert(m_Kashida.begin() + nCntKash, TextFrameIndex(nKashidaPos));
+ nCntKash++;
+ }
+ } // end of kashida search
+ }
+
+ if (nChg < TextFrameIndex(rText.getLength()))
+ nScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg)));
+
+ nLastCompression = nChg;
+ nLastKashida = nChg;
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ // check kashida data
+ TextFrameIndex nTmpKashidaPos(-1);
+ bool bWrongKash = false;
+ for (size_t i = 0; i < m_Kashida.size(); ++i)
+ {
+ TextFrameIndex nCurrKashidaPos = GetKashida( i );
+ if ( nCurrKashidaPos <= nTmpKashidaPos )
+ {
+ bWrongKash = true;
+ break;
+ }
+ nTmpKashidaPos = nCurrKashidaPos;
+ }
+ SAL_WARN_IF( bWrongKash, "sw.core", "Kashida array contains wrong data" );
+#endif
+
+ // remove invalid entries from direction information arrays
+ m_DirectionChanges.clear();
+
+ // Perform Unicode Bidi Algorithm for text direction information
+ {
+ UpdateBidiInfo( rText );
+
+ // #i16354# Change script type for RTL text to CTL:
+ // 1. All text in RTL runs will use the CTL font
+ // #i89825# change the script type also to CTL (hennerdrewes)
+ // 2. Text in embedded LTR runs that does not have any strong LTR characters (numbers!)
+ for (size_t nDirIdx = 0; nDirIdx < m_DirectionChanges.size(); ++nDirIdx)
+ {
+ const sal_uInt8 nCurrDirType = GetDirType( nDirIdx );
+ // nStart is start of RTL run:
+ const TextFrameIndex nStart = nDirIdx > 0 ? GetDirChg(nDirIdx - 1) : TextFrameIndex(0);
+ // nEnd is end of RTL run:
+ const TextFrameIndex nEnd = GetDirChg( nDirIdx );
+
+ if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run
+ (nCurrDirType > UBIDI_LTR && // non-strong text in embedded LTR run
+ !lcl_HasStrongLTR(rText, sal_Int32(nStart), sal_Int32(nEnd))))
+ {
+ // nScriptIdx points into the ScriptArrays:
+ size_t nScriptIdx = 0;
+
+ // Skip entries in ScriptArray which are not inside the RTL run:
+ // Make nScriptIdx become the index of the script group with
+ // 1. nStartPosOfGroup <= nStart and
+ // 2. nEndPosOfGroup > nStart
+ while ( GetScriptChg( nScriptIdx ) <= nStart )
+ ++nScriptIdx;
+
+ const TextFrameIndex nStartPosOfGroup = nScriptIdx
+ ? GetScriptChg(nScriptIdx - 1)
+ : TextFrameIndex(0);
+ const sal_uInt8 nScriptTypeOfGroup = GetScriptType( nScriptIdx );
+
+ SAL_WARN_IF( nStartPosOfGroup > nStart || GetScriptChg( nScriptIdx ) <= nStart,
+ "sw.core", "Script override with CTL font trouble" );
+
+ // Check if we have to insert a new script change at
+ // position nStart. If nStartPosOfGroup < nStart,
+ // we have to insert a new script change:
+ if (nStart > TextFrameIndex(0) && nStartPosOfGroup < nStart)
+ {
+ m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx,
+ ScriptChangeInfo(nStart, nScriptTypeOfGroup) );
+ ++nScriptIdx;
+ }
+
+ // Remove entries in ScriptArray which end inside the RTL run:
+ while (nScriptIdx < m_ScriptChanges.size()
+ && GetScriptChg(nScriptIdx) <= nEnd)
+ {
+ m_ScriptChanges.erase(m_ScriptChanges.begin() + nScriptIdx);
+ }
+
+ // Insert a new entry in ScriptArray for the end of the RTL run:
+ m_ScriptChanges.insert(m_ScriptChanges.begin() + nScriptIdx,
+ ScriptChangeInfo(nEnd, i18n::ScriptType::COMPLEX) );
+
+#if OSL_DEBUG_LEVEL > 1
+ // Check that ScriptChangeInfos are in increasing order of
+ // position and that we don't have "empty" changes.
+ sal_uInt8 nLastTyp = i18n::ScriptType::WEAK;
+ TextFrameIndex nLastPos = TextFrameIndex(0);
+ for (const auto& rScriptChange : m_ScriptChanges)
+ {
+ SAL_WARN_IF( nLastTyp == rScriptChange.type ||
+ nLastPos >= rScriptChange.position,
+ "sw.core", "Heavy InitScriptType() confusion" );
+ nLastPos = rScriptChange.position;
+ nLastTyp = rScriptChange.type;
+ }
+#endif
+ }
+ }
+ }
+}
+
+void SwScriptInfo::UpdateBidiInfo( const OUString& rText )
+{
+ // remove invalid entries from direction information arrays
+ m_DirectionChanges.clear();
+
+ // Bidi functions from icu 2.0
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( rText.getLength(), 0, &nError );
+ nError = U_ZERO_ERROR;
+
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(rText.getStr()), rText.getLength(),
+ m_nDefaultDir, nullptr, &nError );
+ nError = U_ZERO_ERROR;
+ int nCount = ubidi_countRuns( pBidi, &nError );
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+ for ( int nIdx = 0; nIdx < nCount; ++nIdx )
+ {
+ ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
+ m_DirectionChanges.emplace_back(TextFrameIndex(nEnd), nCurrDir);
+ nStart = nEnd;
+ }
+
+ ubidi_close( pBidi );
+}
+
+// returns the position of the next character which belongs to another script
+// than the character of the actual (input) position.
+// If there's no script change until the end of the paragraph, it will return
+// COMPLETE_STRING.
+// Scripts are Asian (Chinese, Japanese, Korean),
+// Latin ( English etc.)
+// and Complex ( Hebrew, Arabian )
+TextFrameIndex SwScriptInfo::NextScriptChg(const TextFrameIndex nPos) const
+{
+ const size_t nEnd = CountScriptChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ if( nPos < GetScriptChg( nX ) )
+ return GetScriptChg( nX );
+ }
+
+ return TextFrameIndex(COMPLETE_STRING);
+}
+
+// returns the script of the character at the input position
+sal_Int16 SwScriptInfo::ScriptType(const TextFrameIndex nPos) const
+{
+ const size_t nEnd = CountScriptChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ if( nPos < GetScriptChg( nX ) )
+ return GetScriptType( nX );
+ }
+
+ // the default is the application language script
+ return SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
+}
+
+TextFrameIndex SwScriptInfo::NextDirChg(const TextFrameIndex nPos,
+ const sal_uInt8* pLevel ) const
+{
+ const sal_uInt8 nCurrDir = pLevel ? *pLevel : 62;
+ const size_t nEnd = CountDirChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ if( nPos < GetDirChg( nX ) &&
+ ( nX + 1 == nEnd || GetDirType( nX + 1 ) <= nCurrDir ) )
+ return GetDirChg( nX );
+ }
+
+ return TextFrameIndex(COMPLETE_STRING);
+}
+
+sal_uInt8 SwScriptInfo::DirType(const TextFrameIndex nPos) const
+{
+ const size_t nEnd = CountDirChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ if( nPos < GetDirChg( nX ) )
+ return GetDirType( nX );
+ }
+
+ return 0;
+}
+
+TextFrameIndex SwScriptInfo::NextHiddenChg(TextFrameIndex const nPos) const
+{
+ for (auto const& it : m_HiddenChg)
+ {
+ if (nPos < it)
+ {
+ return it;
+ }
+ }
+ return TextFrameIndex(COMPLETE_STRING);
+}
+
+TextFrameIndex SwScriptInfo::NextBookmark(TextFrameIndex const nPos) const
+{
+ for (auto const& it : m_Bookmarks)
+ {
+ if (nPos < std::get<0>(it))
+ {
+ return std::get<0>(it);
+ }
+ }
+ return TextFrameIndex(COMPLETE_STRING);
+}
+
+std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>>
+ SwScriptInfo::GetBookmarks(TextFrameIndex const nPos)
+{
+ std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> aColors;
+ for (auto const& it : m_Bookmarks)
+ {
+ if (nPos == std::get<0>(it))
+ {
+ const OUString& sName = std::get<3>(it);
+ // filter hidden bookmarks imported from OOXML
+ // TODO import them as hidden bookmarks
+ if ( !( sName.startsWith("_Toc") || sName.startsWith("_Ref") ) )
+ aColors.push_back(std::tuple<MarkKind, Color,
+ OUString>(std::get<1>(it), std::get<2>(it), std::get<3>(it)));
+ }
+ else if (nPos < std::get<0>(it))
+ {
+ break;
+ }
+ }
+
+ // sort bookmark boundary marks at the same position
+ // mark order: ] | [
+ // color order: [c1 [c2 [c3 ... c3] c2] c1]
+ sort(aColors.begin(), aColors.end(),
+ [](std::tuple<MarkKind, Color, OUString> const a, std::tuple<MarkKind, Color, OUString> const b) {
+ return (MarkKind::End == std::get<0>(a) && MarkKind::End != std::get<0>(b)) ||
+ (MarkKind::Point == std::get<0>(a) && MarkKind::Start == std::get<0>(b)) ||
+ // if both are end or start, order by color
+ (MarkKind::End == std::get<0>(a) && MarkKind::End == std::get<0>(b) && std::get<1>(a) < std::get<1>(b)) ||
+ (MarkKind::Start == std::get<0>(a) && MarkKind::Start == std::get<0>(b) && std::get<1>(b) < std::get<1>(a));});
+
+ return aColors;
+}
+
+// Takes a string and replaced the hidden ranges with cChar.
+sal_Int32 SwScriptInfo::MaskHiddenRanges( const SwTextNode& rNode, OUStringBuffer & rText,
+ const sal_Int32 nStt, const sal_Int32 nEnd,
+ const sal_Unicode cChar )
+{
+ assert(rNode.GetText().getLength() == rText.getLength());
+
+ std::vector<sal_Int32> aList;
+ sal_Int32 nHiddenStart;
+ sal_Int32 nHiddenEnd;
+ sal_Int32 nNumOfHiddenChars = 0;
+ GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
+ auto rFirst( aList.crbegin() );
+ auto rLast( aList.crend() );
+ while ( rFirst != rLast )
+ {
+ nHiddenEnd = *(rFirst++);
+ nHiddenStart = *(rFirst++);
+
+ if ( nHiddenEnd < nStt || nHiddenStart > nEnd )
+ continue;
+
+ while ( nHiddenStart < nHiddenEnd && nHiddenStart < nEnd )
+ {
+ if (nHiddenStart >= nStt)
+ {
+ rText[nHiddenStart] = cChar;
+ ++nNumOfHiddenChars;
+ }
+ ++nHiddenStart;
+ }
+ }
+
+ return nNumOfHiddenChars;
+}
+
+// Takes a SwTextNode and deletes the hidden ranges from the node.
+void SwScriptInfo::DeleteHiddenRanges( SwTextNode& rNode )
+{
+ std::vector<sal_Int32> aList;
+ sal_Int32 nHiddenStart;
+ sal_Int32 nHiddenEnd;
+ GetBoundsOfHiddenRange( rNode, 0, nHiddenStart, nHiddenEnd, &aList );
+ auto rFirst( aList.crbegin() );
+ auto rLast( aList.crend() );
+ while ( rFirst != rLast )
+ {
+ nHiddenEnd = *(rFirst++);
+ nHiddenStart = *(rFirst++);
+
+ SwPaM aPam( rNode, nHiddenStart, rNode, nHiddenEnd );
+ rNode.getIDocumentContentOperations().DeleteRange( aPam );
+ }
+}
+
+bool SwScriptInfo::GetBoundsOfHiddenRange( const SwTextNode& rNode, sal_Int32 nPos,
+ sal_Int32& rnStartPos, sal_Int32& rnEndPos,
+ std::vector<sal_Int32>* pList )
+{
+ rnStartPos = COMPLETE_STRING;
+ rnEndPos = 0;
+
+ bool bNewContainsHiddenChars = false;
+
+ // Optimization: First examine the flags at the text node:
+
+ if ( !rNode.IsCalcHiddenCharFlags() )
+ {
+ bool bWholePara = rNode.HasHiddenCharAttribute( true );
+ bool bContainsHiddenChars = rNode.HasHiddenCharAttribute( false );
+ if ( !bContainsHiddenChars )
+ return false;
+
+ if ( bWholePara )
+ {
+ if ( pList )
+ {
+ pList->push_back( 0 );
+ pList->push_back(rNode.GetText().getLength());
+ }
+
+ rnStartPos = 0;
+ rnEndPos = rNode.GetText().getLength();
+ return true;
+ }
+ }
+
+ // sw_redlinehide: this won't work if it's merged
+#if 0
+ const SwScriptInfo* pSI = SwScriptInfo::GetScriptInfo( rNode );
+ if ( pSI )
+ {
+
+ // Check first, if we have a valid SwScriptInfo object for this text node:
+
+ bNewContainsHiddenChars = pSI->GetBoundsOfHiddenRange( nPos, rnStartPos, rnEndPos, pList );
+ const bool bNewHiddenCharsHidePara =
+ rnStartPos == 0 && rnEndPos >= rNode.GetText().getLength();
+ rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
+ }
+ else
+#endif
+ {
+
+ // No valid SwScriptInfo Object, we have to do it the hard way:
+
+ Range aRange(0, (!rNode.GetText().isEmpty())
+ ? rNode.GetText().getLength() - 1
+ : 0);
+ MultiSelection aHiddenMulti( aRange );
+ SwScriptInfo::CalcHiddenRanges(rNode, aHiddenMulti, nullptr);
+ for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
+ {
+ const Range& rRange = aHiddenMulti.GetRange( i );
+ const sal_Int32 nHiddenStart = rRange.Min();
+ const sal_Int32 nHiddenEnd = rRange.Max() + 1;
+
+ if ( nHiddenStart > nPos )
+ break;
+ if (nPos < nHiddenEnd)
+ {
+ rnStartPos = nHiddenStart;
+ rnEndPos = std::min<sal_Int32>(nHiddenEnd,
+ rNode.GetText().getLength());
+ break;
+ }
+ }
+
+ if ( pList )
+ {
+ for( sal_Int32 i = 0; i < aHiddenMulti.GetRangeCount(); ++i )
+ {
+ const Range& rRange = aHiddenMulti.GetRange( i );
+ pList->push_back( rRange.Min() );
+ pList->push_back( rRange.Max() + 1 );
+ }
+ }
+
+ bNewContainsHiddenChars = aHiddenMulti.GetRangeCount() > 0;
+ }
+
+ return bNewContainsHiddenChars;
+}
+
+bool SwScriptInfo::GetBoundsOfHiddenRange(TextFrameIndex nPos,
+ TextFrameIndex & rnStartPos, TextFrameIndex & rnEndPos) const
+{
+ rnStartPos = TextFrameIndex(COMPLETE_STRING);
+ rnEndPos = TextFrameIndex(0);
+
+ const size_t nEnd = CountHiddenChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ const TextFrameIndex nHiddenStart = GetHiddenChg( nX++ );
+ const TextFrameIndex nHiddenEnd = GetHiddenChg( nX );
+
+ if ( nHiddenStart > nPos )
+ break;
+ if (nPos < nHiddenEnd)
+ {
+ rnStartPos = nHiddenStart;
+ rnEndPos = nHiddenEnd;
+ break;
+ }
+ }
+
+ return CountHiddenChg() > 0;
+}
+
+bool SwScriptInfo::IsInHiddenRange( const SwTextNode& rNode, sal_Int32 nPos )
+{
+ sal_Int32 nStartPos;
+ sal_Int32 nEndPos;
+ SwScriptInfo::GetBoundsOfHiddenRange( rNode, nPos, nStartPos, nEndPos );
+ return nStartPos != COMPLETE_STRING;
+}
+
+#ifdef DBG_UTIL
+// returns the type of the compressed character
+SwScriptInfo::CompType SwScriptInfo::DbgCompType(const TextFrameIndex nPos) const
+{
+ const size_t nEnd = CountCompChg();
+ for( size_t nX = 0; nX < nEnd; ++nX )
+ {
+ const TextFrameIndex nChg = GetCompStart(nX);
+
+ if ( nPos < nChg )
+ return NONE;
+
+ if( nPos < nChg + GetCompLen( nX ) )
+ return GetCompType( nX );
+ }
+ return NONE;
+}
+#endif
+
+// returns, if there are compressible kanas or specials
+// between nStart and nEnd
+size_t SwScriptInfo::HasKana(TextFrameIndex const nStart, TextFrameIndex const nLen) const
+{
+ const size_t nCnt = CountCompChg();
+ TextFrameIndex nEnd = nStart + nLen;
+
+ for( size_t nX = 0; nX < nCnt; ++nX )
+ {
+ TextFrameIndex nKanaStart = GetCompStart(nX);
+ TextFrameIndex nKanaEnd = nKanaStart + GetCompLen(nX);
+
+ if ( nKanaStart >= nEnd )
+ return SAL_MAX_SIZE;
+
+ if ( nStart < nKanaEnd )
+ return nX;
+ }
+
+ return SAL_MAX_SIZE;
+}
+
+tools::Long SwScriptInfo::Compress(KernArray& rKernArray, TextFrameIndex nIdx, TextFrameIndex nLen,
+ const sal_uInt16 nCompress, const sal_uInt16 nFontHeight,
+ bool bCenter,
+ Point* pPoint ) const
+{
+ SAL_WARN_IF( !nCompress, "sw.core", "Compression without compression?!" );
+ SAL_WARN_IF( !nLen, "sw.core", "Compression without text?!" );
+ const size_t nCompCount = CountCompChg();
+
+ // In asian typography, there are full width and half width characters.
+ // Full width punctuation characters can be compressed by 50%
+ // to determine this, we compare the font width with 75% of its height
+ const tools::Long nMinWidth = ( 3 * nFontHeight ) / 4;
+
+ size_t nCompIdx = HasKana( nIdx, nLen );
+
+ if ( SAL_MAX_SIZE == nCompIdx )
+ return 0;
+
+ TextFrameIndex nChg = GetCompStart( nCompIdx );
+ TextFrameIndex nCompLen = GetCompLen( nCompIdx );
+ sal_Int32 nI = 0;
+ nLen += nIdx;
+
+ if( nChg > nIdx )
+ {
+ nI = sal_Int32(nChg - nIdx);
+ nIdx = nChg;
+ }
+ else if( nIdx < nChg + nCompLen )
+ nCompLen -= nIdx - nChg;
+
+ if( nIdx > nLen || nCompIdx >= nCompCount )
+ return 0;
+
+ tools::Long nSub = 0;
+ tools::Long nLast = nI ? rKernArray[ nI - 1 ] : 0;
+ do
+ {
+ const CompType nType = GetCompType( nCompIdx );
+#ifdef DBG_UTIL
+ SAL_WARN_IF( nType != DbgCompType( nIdx ), "sw.core", "Gimme the right type!" );
+#endif
+ nCompLen += nIdx;
+ if( nCompLen > nLen )
+ nCompLen = nLen;
+
+ // are we allowed to compress the character?
+ if ( rKernArray[ nI ] - nLast < nMinWidth )
+ {
+ nIdx++; nI++;
+ }
+ else
+ {
+ while( nIdx < nCompLen )
+ {
+ SAL_WARN_IF( SwScriptInfo::NONE == nType, "sw.core", "None compression?!" );
+
+ // nLast is width of current character
+ nLast -= rKernArray[ nI ];
+
+ nLast *= nCompress;
+ tools::Long nMove = 0;
+ if( SwScriptInfo::KANA != nType )
+ {
+ nLast /= 24000;
+ if( pPoint && SwScriptInfo::SPECIAL_LEFT == nType )
+ {
+ if( nI )
+ nMove = nLast;
+ else
+ {
+ pPoint->AdjustX(nLast );
+ nLast = 0;
+ }
+ }
+ else if( bCenter && SwScriptInfo::SPECIAL_MIDDLE == nType )
+ nMove = nLast / 2;
+ }
+ else
+ nLast /= 100000;
+ nSub -= nLast;
+ nLast = rKernArray[ nI ];
+ if( nI && nMove )
+ rKernArray.adjust(nI - 1, nMove);
+ rKernArray.adjust(nI, -nSub);
+ ++nI;
+ ++nIdx;
+ }
+ }
+
+ if( nIdx >= nLen )
+ break;
+
+ TextFrameIndex nTmpChg = nLen;
+ if( ++nCompIdx < nCompCount )
+ {
+ nTmpChg = GetCompStart( nCompIdx );
+ if( nTmpChg > nLen )
+ nTmpChg = nLen;
+ nCompLen = GetCompLen( nCompIdx );
+ }
+
+ while( nIdx < nTmpChg )
+ {
+ nLast = rKernArray[ nI ];
+ rKernArray.adjust(nI, -nSub);
+ ++nI;
+ ++nIdx;
+ }
+ } while( nIdx < nLen );
+ return nSub;
+}
+
+// Note on calling KashidaJustify():
+// Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
+// total number of kashida positions, or the number of kashida positions after some positions
+// have been dropped, depending on the state of the m_KashidaInvalid set.
+
+sal_Int32 SwScriptInfo::KashidaJustify( KernArray* pKernArray,
+ sal_Bool* pKashidaArray,
+ TextFrameIndex const nStt,
+ TextFrameIndex const nLen,
+ tools::Long nSpaceAdd ) const
+{
+ SAL_WARN_IF( !nLen, "sw.core", "Kashida justification without text?!" );
+
+ if( !IsKashidaLine(nStt))
+ return -1;
+
+ // evaluate kashida information in collected in SwScriptInfo
+
+ size_t nCntKash = 0;
+ while( nCntKash < CountKashida() )
+ {
+ if ( nStt <= GetKashida( nCntKash ) )
+ break;
+ ++nCntKash;
+ }
+
+ const TextFrameIndex nEnd = nStt + nLen;
+
+ size_t nCntKashEnd = nCntKash;
+ while ( nCntKashEnd < CountKashida() )
+ {
+ if ( nEnd <= GetKashida( nCntKashEnd ) )
+ break;
+ ++nCntKashEnd;
+ }
+
+ size_t nActualKashCount = nCntKashEnd - nCntKash;
+ for (size_t i = nCntKash; i < nCntKashEnd; ++i)
+ {
+ if ( nActualKashCount && !IsKashidaValid ( i ) )
+ --nActualKashCount;
+ }
+
+ if ( !pKernArray )
+ return nActualKashCount;
+
+ // do nothing if there is no more kashida
+ if ( nCntKash < CountKashida() )
+ {
+ // skip any invalid kashidas
+ while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
+ ++nCntKash;
+
+ TextFrameIndex nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash)
+ ? GetKashida(nCntKash)
+ : nEnd;
+ tools::Long nKashAdd = nSpaceAdd;
+
+ while ( nIdx < nEnd )
+ {
+ TextFrameIndex nArrayPos = nIdx - nStt;
+
+ // mark Kashida insertion positions, code in VCL will use this
+ // array to know where to insert Kashida.
+ if (pKashidaArray)
+ pKashidaArray[sal_Int32(nArrayPos)] = true;
+
+ // next kashida position
+ ++nCntKash;
+ while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
+ ++nCntKash;
+
+ nIdx = nCntKash < nCntKashEnd && IsKashidaValid(nCntKash) ? GetKashida(nCntKash) : nEnd;
+ if ( nIdx > nEnd )
+ nIdx = nEnd;
+
+ const TextFrameIndex nArrayEnd = nIdx - nStt;
+
+ while ( nArrayPos < nArrayEnd )
+ {
+ pKernArray->adjust(sal_Int32(nArrayPos), nKashAdd);
+ ++nArrayPos;
+ }
+ nKashAdd += nSpaceAdd;
+ }
+ }
+
+ return 0;
+}
+
+// Checks if the current text is 'Arabic' text. Note that only the first
+// character has to be checked because a ctl portion only contains one
+// script, see NewTextPortion
+bool SwScriptInfo::IsArabicText(const OUString& rText,
+ TextFrameIndex const nStt, TextFrameIndex const nLen)
+{
+ using namespace ::com::sun::star::i18n;
+ static const ScriptTypeList typeList[] = {
+ { UnicodeScript_kArabic, UnicodeScript_kArabic, sal_Int16(UnicodeScript_kArabic) }, // 11,
+ { UnicodeScript_kScriptCount, UnicodeScript_kScriptCount, sal_Int16(UnicodeScript_kScriptCount) } // 88
+ };
+
+ // go forward if current position does not hold a regular character:
+ const CharClass& rCC = GetAppCharClass();
+ sal_Int32 nIdx = sal_Int32(nStt);
+ const sal_Int32 nEnd = sal_Int32(nStt + nLen);
+ while ( nIdx < nEnd && !rCC.isLetterNumeric( rText, nIdx ) )
+ {
+ ++nIdx;
+ }
+
+ if( nIdx == nEnd )
+ {
+ // no regular character found in this portion. Go backward:
+ --nIdx;
+ while ( nIdx >= 0 && !rCC.isLetterNumeric( rText, nIdx ) )
+ {
+ --nIdx;
+ }
+ }
+
+ if( nIdx >= 0 )
+ {
+ const sal_Unicode cCh = rText[nIdx];
+ const sal_Int16 type = unicode::getUnicodeScriptType( cCh, typeList, sal_Int16(UnicodeScript_kScriptCount) );
+ return type == sal_Int16(UnicodeScript_kArabic);
+ }
+ return false;
+}
+
+bool SwScriptInfo::IsKashidaValid(size_t const nKashPos) const
+{
+ return m_KashidaInvalid.find(nKashPos) == m_KashidaInvalid.end();
+}
+
+void SwScriptInfo::ClearKashidaInvalid(size_t const nKashPos)
+{
+ m_KashidaInvalid.erase(nKashPos);
+}
+
+// bMark == true:
+// marks the first valid kashida in the given text range as invalid
+// bMark == false:
+// clears all kashida invalid flags in the given text range
+bool SwScriptInfo::MarkOrClearKashidaInvalid(
+ TextFrameIndex const nStt, TextFrameIndex const nLen,
+ bool bMark, sal_Int32 nMarkCount)
+{
+ size_t nCntKash = 0;
+ while( nCntKash < CountKashida() )
+ {
+ if ( nStt <= GetKashida( nCntKash ) )
+ break;
+ nCntKash++;
+ }
+
+ const TextFrameIndex nEnd = nStt + nLen;
+
+ while ( nCntKash < CountKashida() )
+ {
+ if ( nEnd <= GetKashida( nCntKash ) )
+ break;
+ if(bMark)
+ {
+ if ( MarkKashidaInvalid ( nCntKash ) )
+ {
+ --nMarkCount;
+ if (!nMarkCount)
+ return true;
+ }
+ }
+ else
+ {
+ ClearKashidaInvalid ( nCntKash );
+ }
+ nCntKash++;
+ }
+ return false;
+}
+
+bool SwScriptInfo::MarkKashidaInvalid(size_t const nKashPos)
+{
+ return m_KashidaInvalid.insert(nKashPos).second;
+}
+
+// retrieve the kashida positions in the given text range
+void SwScriptInfo::GetKashidaPositions(
+ TextFrameIndex const nStt, TextFrameIndex const nLen,
+ std::vector<TextFrameIndex>& rKashidaPosition)
+{
+ size_t nCntKash = 0;
+ while( nCntKash < CountKashida() )
+ {
+ if ( nStt <= GetKashida( nCntKash ) )
+ break;
+ nCntKash++;
+ }
+
+ const TextFrameIndex nEnd = nStt + nLen;
+
+ size_t nCntKashEnd = nCntKash;
+ while ( nCntKashEnd < CountKashida() )
+ {
+ if ( nEnd <= GetKashida( nCntKashEnd ) )
+ break;
+ rKashidaPosition.push_back(GetKashida(nCntKashEnd));
+ nCntKashEnd++;
+ }
+}
+
+void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen)
+{
+ m_NoKashidaLine.push_back( nStt );
+ m_NoKashidaLineEnd.push_back( nStt + nLen );
+}
+
+// determines if the line uses kashida justification
+bool SwScriptInfo::IsKashidaLine(TextFrameIndex const nCharIdx) const
+{
+ for (size_t i = 0; i < m_NoKashidaLine.size(); ++i)
+ {
+ if (nCharIdx >= m_NoKashidaLine[i] && nCharIdx < m_NoKashidaLineEnd[i])
+ return false;
+ }
+ return true;
+}
+
+void SwScriptInfo::ClearNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen)
+{
+ size_t i = 0;
+ while (i < m_NoKashidaLine.size())
+ {
+ if (nStt + nLen >= m_NoKashidaLine[i] && nStt < m_NoKashidaLineEnd[i])
+ {
+ m_NoKashidaLine.erase(m_NoKashidaLine.begin() + i);
+ m_NoKashidaLineEnd.erase(m_NoKashidaLineEnd.begin() + i);
+ }
+ else
+ ++i;
+ }
+}
+
+// mark the given character indices as invalid kashida positions
+void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt,
+ const TextFrameIndex* pKashidaPositions)
+{
+ SAL_WARN_IF( !pKashidaPositions || nCnt == 0, "sw.core", "Where are kashidas?" );
+
+ size_t nCntKash = 0;
+ sal_Int32 nKashidaPosIdx = 0;
+
+ while (nCntKash < CountKashida() && nKashidaPosIdx < nCnt)
+ {
+ if ( pKashidaPositions [nKashidaPosIdx] > GetKashida( nCntKash ) )
+ {
+ ++nCntKash;
+ continue;
+ }
+
+ if ( pKashidaPositions [nKashidaPosIdx] != GetKashida( nCntKash ) || !IsKashidaValid ( nCntKash ) )
+ return; // something is wrong
+
+ MarkKashidaInvalid ( nCntKash );
+ nKashidaPosIdx++;
+ }
+}
+
+TextFrameIndex SwScriptInfo::ThaiJustify( std::u16string_view aText, KernArray* pKernArray,
+ TextFrameIndex const nStt,
+ TextFrameIndex const nLen,
+ TextFrameIndex nNumberOfBlanks,
+ tools::Long nSpaceAdd )
+{
+ SAL_WARN_IF( nStt + nLen > TextFrameIndex(aText.size()), "sw.core", "String in ThaiJustify too small" );
+
+ SwTwips nNumOfTwipsToDistribute = nSpaceAdd * sal_Int32(nNumberOfBlanks) /
+ SPACING_PRECISION_FACTOR;
+
+ tools::Long nSpaceSum = 0;
+ TextFrameIndex nCnt(0);
+
+ for (sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI)
+ {
+ const sal_Unicode cCh = aText[sal_Int32(nStt) + nI];
+
+ // check if character is not above or below base
+ if ( ( 0xE34 > cCh || cCh > 0xE3A ) &&
+ ( 0xE47 > cCh || cCh > 0xE4E ) && cCh != 0xE31 )
+ {
+ if (nNumberOfBlanks > TextFrameIndex(0))
+ {
+ nSpaceAdd = nNumOfTwipsToDistribute / sal_Int32(nNumberOfBlanks);
+ --nNumberOfBlanks;
+ nNumOfTwipsToDistribute -= nSpaceAdd;
+ }
+ nSpaceSum += nSpaceAdd;
+ ++nCnt;
+ }
+
+ if (pKernArray)
+ pKernArray->adjust(nI, nSpaceSum);
+ }
+
+ return nCnt;
+}
+
+SwScriptInfo* SwScriptInfo::GetScriptInfo( const SwTextNode& rTNd,
+ SwTextFrame const**const o_ppFrame,
+ bool const bAllowInvalid)
+{
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
+ SwScriptInfo* pScriptInfo = nullptr;
+
+ for( SwTextFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() )
+ {
+ pScriptInfo = const_cast<SwScriptInfo*>(pLast->GetScriptInfo());
+ if ( pScriptInfo )
+ {
+ if (bAllowInvalid ||
+ TextFrameIndex(COMPLETE_STRING) == pScriptInfo->GetInvalidityA())
+ {
+ if (o_ppFrame)
+ {
+ *o_ppFrame = pLast;
+ }
+ break;
+ }
+ pScriptInfo = nullptr;
+ }
+ }
+
+ return pScriptInfo;
+}
+
+SwParaPortion::SwParaPortion()
+{
+ FormatReset();
+ m_bFlys = m_bFootnoteNum = m_bMargin = false;
+ SetWhichPor( PortionType::Para );
+}
+
+SwParaPortion::~SwParaPortion()
+{
+}
+
+TextFrameIndex SwParaPortion::GetParLen() const
+{
+ TextFrameIndex nLen(0);
+ const SwLineLayout *pLay = this;
+ while( pLay )
+ {
+ nLen += pLay->GetLen();
+ pLay = pLay->GetNext();
+ }
+ return nLen;
+}
+
+bool SwParaPortion::HasNumberingPortion(FootnoteOrNot const eFootnote) const
+{
+ SwLinePortion const* pPortion(nullptr);
+ // the first line may contain only fly portion...
+ for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext())
+ {
+ pPortion = pLine->GetFirstPortion();
+ while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion()))
+ { // skip margins and fly spacers - numbering should be first then
+ pPortion = pPortion->GetNextPortion();
+ }
+ }
+ if (pPortion && pPortion->InHyphGrp())
+ { // weird special case, bullet with soft hyphen
+ pPortion = pPortion->GetNextPortion();
+ }
+ return pPortion && pPortion->InNumberGrp()
+ && (eFootnote == SwParaPortion::FootnoteToo || !pPortion->IsFootnoteNumPortion());
+}
+
+bool SwParaPortion::HasContentPortions() const
+{
+ SwLinePortion const* pPortion(nullptr);
+ for (SwLineLayout const* pLine = this; pLine && !pPortion; pLine = pLine->GetNext())
+ {
+ pPortion = pLine->GetFirstPortion();
+ while (pPortion && (pPortion->InGlueGrp() || pPortion->IsKernPortion() || pPortion->IsFlyPortion()))
+ { // skip margins and fly spacers
+ pPortion = pPortion->GetNextPortion();
+ }
+ }
+ return pPortion != nullptr;
+}
+
+const SwDropPortion *SwParaPortion::FindDropPortion() const
+{
+ const SwLineLayout *pLay = this;
+ while( pLay && pLay->IsDummy() )
+ pLay = pLay->GetNext();
+ while( pLay )
+ {
+ const SwLinePortion *pPos = pLay->GetNextPortion();
+ while ( pPos && !pPos->GetLen() )
+ pPos = pPos->GetNextPortion();
+ if( pPos && pPos->IsDropPortion() )
+ return static_cast<const SwDropPortion *>(pPos);
+ pLay = pLay->GetLen() ? nullptr : pLay->GetNext();
+ }
+ return nullptr;
+}
+
+void SwParaPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwParaPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwLineLayout::Init( SwLinePortion* pNextPortion )
+{
+ Height( 0, false );
+ Width( 0 );
+ SetLen(TextFrameIndex(0));
+ SetAscent( 0 );
+ SetRealHeight( 0 );
+ SetNextPortion( pNextPortion );
+}
+
+// looks for hanging punctuation portions in the paragraph
+// and return the maximum right offset of them.
+// If no such portion is found, the Margin/Hanging-flags will be updated.
+SwTwips SwLineLayout::GetHangingMargin_() const
+{
+ SwLinePortion* pPor = GetNextPortion();
+ bool bFound = false;
+ SwTwips nDiff = 0;
+ while( pPor)
+ {
+ if( pPor->IsHangingPortion() )
+ {
+ nDiff = static_cast<SwHangingPortion*>(pPor)->GetInnerWidth() - pPor->Width();
+ if( nDiff )
+ bFound = true;
+ }
+ // the last post its portion
+ else if ( pPor->IsPostItsPortion() && ! pPor->GetNextPortion() )
+ nDiff = mnAscent;
+
+ pPor = pPor->GetNextPortion();
+ }
+ if( !bFound ) // update the hanging-flag
+ const_cast<SwLineLayout*>(this)->SetHanging( false );
+ return nDiff;
+}
+
+SwTwips SwTextFrame::HangingMargin() const
+{
+ SAL_WARN_IF( !HasPara(), "sw.core", "Don't call me without a paraportion" );
+ if( !GetPara()->IsMargin() )
+ return 0;
+ const SwLineLayout* pLine = GetPara();
+ SwTwips nRet = 0;
+ do
+ {
+ SwTwips nDiff = pLine->GetHangingMargin();
+ if( nDiff > nRet )
+ nRet = nDiff;
+ pLine = pLine->GetNext();
+ } while ( pLine );
+ if( !nRet ) // update the margin-flag
+ const_cast<SwParaPortion*>(GetPara())->SetMargin( false );
+ return nRet;
+}
+
+SwTwips SwTextFrame::GetLowerMarginForFlyIntersect() const
+{
+ const IDocumentSettingAccess& rIDSA = GetDoc().getIDocumentSettingAccess();
+ if (!rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN))
+ {
+ // Word >= 2013 style or Writer style: lower margin is ignored when determining the text
+ // frame height.
+ return 0;
+ }
+
+ const SwAttrSet* pAttrSet = GetTextNodeForParaProps()->GetpSwAttrSet();
+ if (!pAttrSet)
+ {
+ return 0;
+ }
+
+ // If it has multiple lines, then probably it already has the needed fly portion.
+ // Limit this to empty paragraphs for now.
+ if ((GetPara() && GetPara()->GetNext()) || !GetText().isEmpty())
+ {
+ return 0;
+ }
+
+ return pAttrSet->GetULSpace().GetLower();
+}
+
+void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode,
+ MultiSelection & rHiddenMulti,
+ std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks)
+{
+ assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1)
+ || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len()));
+
+ const SvxCharHiddenItem* pItem = rNode.GetSwAttrSet().GetItemIfSet( RES_CHRATR_HIDDEN );
+ if( pItem && pItem->GetValue() )
+ {
+ rHiddenMulti.SelectAll();
+ }
+
+ const SwpHints* pHints = rNode.GetpSwpHints();
+
+ if( pHints )
+ {
+ for( size_t nTmp = 0; nTmp < pHints->Count(); ++nTmp )
+ {
+ const SwTextAttr* pTextAttr = pHints->Get( nTmp );
+ const SvxCharHiddenItem* pHiddenItem = CharFormat::GetItem( *pTextAttr, RES_CHRATR_HIDDEN );
+ if( pHiddenItem )
+ {
+ const sal_Int32 nSt = pTextAttr->GetStart();
+ const sal_Int32 nEnd = *pTextAttr->End();
+ if( nEnd > nSt )
+ {
+ Range aTmp( nSt, nEnd - 1 );
+ rHiddenMulti.Select( aTmp, pHiddenItem->GetValue() );
+ }
+ }
+ }
+ }
+
+ for (const SwContentIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext())
+ {
+ const sw::mark::IMark* pMark = pIndex->GetMark();
+ const sw::mark::IBookmark* pBookmark = dynamic_cast<const sw::mark::IBookmark*>(pMark);
+ if (pBookmarks && pBookmark)
+ {
+ if (!pBookmark->IsExpanded())
+ {
+ pBookmarks->emplace_back(pBookmark, MarkKind::Point);
+ }
+ else if (pIndex == &pBookmark->GetMarkStart().nContent)
+ {
+ pBookmarks->emplace_back(pBookmark, MarkKind::Start);
+ }
+ else
+ {
+ assert(pIndex == &pBookmark->GetMarkEnd().nContent);
+ pBookmarks->emplace_back(pBookmark, MarkKind::End);
+ }
+ }
+
+ // condition is evaluated in DocumentFieldsManager::UpdateExpFields()
+ if (pBookmark && pBookmark->IsHidden())
+ {
+ // intersect bookmark range with textnode range and add the intersection to rHiddenMulti
+
+ const sal_Int32 nSt = pBookmark->GetMarkStart().GetContentIndex();
+ const sal_Int32 nEnd = pBookmark->GetMarkEnd().GetContentIndex();
+
+ if( nEnd > nSt )
+ {
+ Range aTmp( nSt, nEnd - 1 );
+ rHiddenMulti.Select(aTmp, true);
+ }
+ }
+ }
+}
+
+void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection &rHiddenMulti, bool bSelect)
+{
+ assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1)
+ || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len()));
+
+ const IDocumentRedlineAccess& rIDRA = rNode.getIDocumentRedlineAccess();
+ if ( !IDocumentRedlineAccess::IsShowChanges( rIDRA.GetRedlineFlags() ) )
+ return;
+
+ SwRedlineTable::size_type nAct = rIDRA.GetRedlinePos( rNode, RedlineType::Any );
+
+ for ( ; nAct < rIDRA.GetRedlineTable().size(); nAct++ )
+ {
+ const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ];
+
+ if (pRed->Start()->GetNode() > rNode)
+ break;
+
+ if (pRed->GetType() != RedlineType::Delete)
+ continue;
+
+ sal_Int32 nRedlStart;
+ sal_Int32 nRedlnEnd;
+ pRed->CalcStartEnd( rNode.GetIndex(), nRedlStart, nRedlnEnd );
+ //clip it if the redline extends past the end of the nodes text
+ nRedlnEnd = std::min<sal_Int32>(nRedlnEnd, rNode.GetText().getLength());
+ if ( nRedlnEnd > nRedlStart )
+ {
+ Range aTmp( nRedlStart, nRedlnEnd - 1 );
+ rHiddenMulti.Select( aTmp, bSelect );
+ }
+ }
+}
+
+// Returns a MultiSection indicating the hidden ranges.
+void SwScriptInfo::CalcHiddenRanges( const SwTextNode& rNode,
+ MultiSelection & rHiddenMulti,
+ std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> *const pBookmarks)
+{
+ selectHiddenTextProperty(rNode, rHiddenMulti, pBookmarks);
+
+ // If there are any hidden ranges in the current text node, we have
+ // to unhide the redlining ranges:
+ selectRedLineDeleted(rNode, rHiddenMulti, false);
+
+ // We calculated a lot of stuff. Finally we can update the flags at the text node.
+
+ const bool bNewContainsHiddenChars = rHiddenMulti.GetRangeCount() > 0;
+ bool bNewHiddenCharsHidePara = false;
+ if ( bNewContainsHiddenChars )
+ {
+ const Range& rRange = rHiddenMulti.GetRange( 0 );
+ const sal_Int32 nHiddenStart = rRange.Min();
+ const sal_Int32 nHiddenEnd = rRange.Max() + 1;
+ bNewHiddenCharsHidePara =
+ (nHiddenStart == 0 && nHiddenEnd >= rNode.GetText().getLength());
+ }
+ rNode.SetHiddenCharAttribute( bNewHiddenCharsHidePara, bNewContainsHiddenChars );
+}
+
+TextFrameIndex SwScriptInfo::CountCJKCharacters(const OUString &rText,
+ TextFrameIndex nPos, TextFrameIndex const nEnd, LanguageType aLang)
+{
+ TextFrameIndex nCount(0);
+ if (nEnd > nPos)
+ {
+ sal_Int32 nDone = 0;
+ const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang );
+ while ( nPos < nEnd )
+ {
+ nPos = TextFrameIndex(g_pBreakIt->GetBreakIter()->nextCharacters(
+ rText, sal_Int32(nPos),
+ rLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone));
+ nCount++;
+ }
+ }
+ else
+ nCount = nEnd - nPos ;
+
+ return nCount;
+}
+
+void SwScriptInfo::CJKJustify( const OUString& rText, KernArray& rKernArray,
+ TextFrameIndex const nStt,
+ TextFrameIndex const nLen, LanguageType aLang,
+ tools::Long nSpaceAdd, bool bIsSpaceStop )
+{
+ assert( sal_Int32(nStt) >= 0 );
+ if (sal_Int32(nLen) <= 0)
+ return;
+
+ tools::Long nSpaceSum = 0;
+ const lang::Locale &rLocale = g_pBreakIt->GetLocale( aLang );
+ sal_Int32 nDone = 0;
+ sal_Int32 nNext(nStt);
+ for ( sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI )
+ {
+ if (nI + sal_Int32(nStt) == nNext)
+ {
+ nNext = g_pBreakIt->GetBreakIter()->nextCharacters( rText, nNext,
+ rLocale,
+ i18n::CharacterIteratorMode::SKIPCELL, 1, nDone );
+ if (nNext < sal_Int32(nStt + nLen) || !bIsSpaceStop)
+ nSpaceSum += nSpaceAdd;
+ }
+ rKernArray.adjust(nI, nSpaceSum);
+ }
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porlay.hxx b/sw/source/core/text/porlay.hxx
new file mode 100644
index 0000000000..3b07b70161
--- /dev/null
+++ b/sw/source/core/text/porlay.hxx
@@ -0,0 +1,352 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <scriptinfo.hxx>
+
+#include <swrect.hxx>
+#include <swtypes.hxx>
+#include "portxt.hxx"
+#include <svx/ctredlin.hxx>
+
+#include <vector>
+#include <deque>
+
+class SwMarginPortion;
+class SwDropPortion;
+class SwTextFormatter;
+
+class SwCharRange
+{
+private:
+ TextFrameIndex m_nStart;
+ TextFrameIndex m_nLen;
+
+public:
+ SwCharRange(TextFrameIndex const nInitStart = TextFrameIndex(0),
+ TextFrameIndex const nInitLen = TextFrameIndex(0))
+ : m_nStart( nInitStart ), m_nLen(nInitLen) {}
+ TextFrameIndex & Start() { return m_nStart; }
+ TextFrameIndex const& Start() const { return m_nStart; }
+ void LeftMove(TextFrameIndex const nNew)
+ { if ( nNew < m_nStart ) { m_nLen += m_nStart-nNew; m_nStart = nNew; } }
+ TextFrameIndex & Len() { return m_nLen; }
+ TextFrameIndex const& Len() const { return m_nLen; }
+ bool operator<(const SwCharRange &rRange) const
+ { return m_nStart < rRange.m_nStart; }
+ bool operator>(const SwCharRange &rRange) const
+ { return m_nStart + m_nLen > rRange.m_nStart + rRange.m_nLen; }
+ bool operator!=(const SwCharRange &rRange) const
+ { return *this < rRange || *this > rRange; }
+ SwCharRange &operator+=(const SwCharRange &rRange);
+};
+
+// SwRepaint is a document-global SwRect
+// nOfst states from where in the first line should be painted
+// nRightOfst gives the right margin
+class SwRepaint : public SwRect
+{
+ SwTwips m_nOffset;
+ SwTwips m_nRightOffset;
+public:
+ SwRepaint() : SwRect(), m_nOffset( 0 ), m_nRightOffset( 0 ) {}
+
+ SwTwips GetOffset() const { return m_nOffset; }
+ void SetOffset( const SwTwips nNew ) { m_nOffset = nNew; }
+ SwTwips GetRightOfst() const { return m_nRightOffset; }
+ void SetRightOfst( const SwTwips nNew ) { m_nRightOffset = nNew; }
+};
+
+/// Collection of SwLinePortion instances, representing one line of text.
+/// Typically owned by an SwParaPortion.
+class SW_DLLPUBLIC SwLineLayout : public SwTextPortion
+{
+private:
+ SwLineLayout *m_pNext; // The next Line
+ std::unique_ptr<std::vector<tools::Long>> m_pLLSpaceAdd; // Used for justified alignment
+ std::unique_ptr<std::deque<sal_uInt16>> m_pKanaComp; // Used for Kana compression
+ SwTwips m_nRealHeight; // The height resulting from line spacing and register
+ SwTwips m_nTextHeight; // The max height of all non-FlyCnt portions in this Line
+ bool m_bFormatAdj : 1;
+ bool m_bDummy : 1;
+ bool m_bEndHyph : 1;
+ bool m_bMidHyph : 1;
+ bool m_bFly : 1;
+ bool m_bRest : 1;
+ bool m_bBlinking : 1;
+ bool m_bClipping : 1; // Clipping needed for exact line height
+ bool m_bContent : 1; // Text for line numbering
+ bool m_bRedline : 1; // The Redlining
+ bool m_bRedlineEnd: 1; // Redlining for paragraph mark: tracked change at the end
+ bool m_bForcedLeftMargin : 1; // Left adjustment moved by the Fly
+ bool m_bHanging : 1; // Contains a hanging portion in the margin
+ bool m_bUnderscore : 1;
+
+ enum RedlineType m_eRedlineEnd; // redline type of pilcrow and line break symbols
+
+ OUString m_sRedlineText; // shortened text of (first) tracked deletion shown in margin
+
+ SwTwips GetHangingMargin_() const;
+
+ void DeleteNext();
+public:
+ // From SwPosSize
+ using SwPosSize::Height;
+ virtual void Height(const SwTwips nNew, const bool bText = true) override;
+
+ // From SwLinePortion
+ virtual SwLinePortion *Insert( SwLinePortion *pPortion ) override;
+ virtual SwLinePortion *Append( SwLinePortion *pPortion ) override;
+ SwLinePortion *GetFirstPortion() const;
+
+ // Flags
+ void ResetFlags();
+ void SetFormatAdj( const bool bNew ) { m_bFormatAdj = bNew; }
+ bool IsFormatAdj() const { return m_bFormatAdj; }
+ void SetEndHyph( const bool bNew ) { m_bEndHyph = bNew; }
+ bool IsEndHyph() const { return m_bEndHyph; }
+ void SetMidHyph( const bool bNew ) { m_bMidHyph = bNew; }
+ bool IsMidHyph() const { return m_bMidHyph; }
+ void SetFly( const bool bNew ) { m_bFly = bNew; }
+ bool IsFly() const { return m_bFly; }
+ void SetRest( const bool bNew ) { m_bRest = bNew; }
+ bool IsRest() const { return m_bRest; }
+ void SetBlinking( const bool bNew ) { m_bBlinking = bNew; }
+ bool IsBlinking() const { return m_bBlinking; }
+ void SetContent( const bool bNew ) { m_bContent = bNew; }
+ bool HasContent() const { return m_bContent; }
+ void SetRedline( const bool bNew ) { m_bRedline = bNew; }
+ bool HasRedline() const { return m_bRedline; }
+ void SetRedlineEnd( const bool bNew ) { m_bRedlineEnd = bNew; }
+ bool HasRedlineEnd() const { return m_bRedlineEnd; }
+ void SetRedlineEndType( const enum RedlineType eNew ) { m_eRedlineEnd = eNew; }
+ RedlineType GetRedlineEndType() const { return m_eRedlineEnd; }
+ void SetRedlineText ( const OUString& sText ) { m_sRedlineText = sText; }
+ const OUString* GetRedlineText() const { return &m_sRedlineText; }
+ void SetForcedLeftMargin() { m_bForcedLeftMargin = true; }
+ bool HasForcedLeftMargin() const { return m_bForcedLeftMargin; }
+ void SetHanging( const bool bNew ) { m_bHanging = bNew; }
+ bool IsHanging() const { return m_bHanging; }
+ void SetUnderscore( const bool bNew ) { m_bUnderscore = bNew; }
+ bool HasUnderscore() const { return m_bUnderscore; }
+
+ // Respecting empty dummy lines
+ void SetDummy( const bool bNew ) { m_bDummy = bNew; }
+ bool IsDummy() const { return m_bDummy; }
+
+ void SetClipping( const bool bNew ) { m_bClipping = bNew; }
+ bool IsClipping() const { return m_bClipping; }
+
+ SwLineLayout();
+ virtual ~SwLineLayout() override;
+
+ SwLineLayout *GetNext() { return m_pNext; }
+ const SwLineLayout *GetNext() const { return m_pNext; }
+ void SetNext( SwLineLayout *pNew ) { m_pNext = pNew; }
+
+ void Init( SwLinePortion *pNextPortion = nullptr);
+
+ // Collects the data for the line
+ void CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf );
+
+ void SetRealHeight( SwTwips nNew ) { m_nRealHeight = nNew; }
+ SwTwips GetRealHeight() const { return m_nRealHeight; }
+
+ SwTwips GetTextHeight() const { return m_nTextHeight; }
+
+ // Creates the glue chain for short lines
+ SwMarginPortion *CalcLeftMargin();
+
+ SwTwips GetHangingMargin() const
+ { return GetHangingMargin_(); }
+
+ // For special treatment for empty lines
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+
+ // Stuff for justified alignment
+ bool IsSpaceAdd() const { return m_pLLSpaceAdd != nullptr; }
+ void InitSpaceAdd(); // Creates pLLSpaceAdd if necessary
+ void CreateSpaceAdd( const tools::Long nInit = 0 );
+ void FinishSpaceAdd() { m_pLLSpaceAdd.reset(); }
+ sal_uInt16 GetLLSpaceAddCount() const { return sal::static_int_cast< sal_uInt16 >(m_pLLSpaceAdd->size()); }
+ void SetLLSpaceAdd( tools::Long nNew, sal_uInt16 nIdx )
+ {
+ if ( nIdx == GetLLSpaceAddCount() )
+ m_pLLSpaceAdd->push_back( nNew );
+ else
+ (*m_pLLSpaceAdd)[ nIdx ] = nNew;
+ }
+ tools::Long GetLLSpaceAdd( sal_uInt16 nIdx ) { return (*m_pLLSpaceAdd)[ nIdx ]; }
+ void RemoveFirstLLSpaceAdd() { m_pLLSpaceAdd->erase( m_pLLSpaceAdd->begin() ); }
+ std::vector<tools::Long>* GetpLLSpaceAdd() const { return m_pLLSpaceAdd.get(); }
+
+ // Stuff for Kana compression
+ void SetKanaComp( std::unique_ptr<std::deque<sal_uInt16>> pNew ){ m_pKanaComp = std::move(pNew); }
+ void FinishKanaComp() { m_pKanaComp.reset(); }
+ std::deque<sal_uInt16>* GetpKanaComp() const { return m_pKanaComp.get(); }
+ std::deque<sal_uInt16>& GetKanaComp() { return *m_pKanaComp; }
+
+ /** determine ascent and descent for positioning of as-character anchored
+ object
+
+ OD 07.01.2004 #i11859# - previously local method <lcl_MaxAscDescent>
+ Method calculates maximum ascents and descents of the line layout.
+ One value considering as-character anchored objects, one without these
+ objects.
+ Portions for other anchored objects aren't considered.
+ OD 2005-05-20 #i47162# - add optional parameter <_bNoFlyCntPorAndLinePor>
+ to control, if the fly content portions and line portion are considered.
+
+ @param _orAscent
+ output parameter - maximum ascent without as-character anchored objects
+
+ @param _orDescent
+ output parameter - maximum descent without as-character anchored objects
+
+ @param _orObjAscent
+ output parameter - maximum ascent with as-character anchored objects
+
+ @param _orObjDescent
+ output parameter - maximum descent with as-character anchored objects
+
+ @param _pDontConsiderPortion
+ input parameter - portion, which isn't considered for calculating
+ <_orObjAscent> and <_orObjDescent>, if it isn't a portion for a
+ as-character anchored object or it isn't as high as the line.
+
+ @param _bNoFlyCntPorAndLinePor
+ optional input parameter - boolean, indicating that fly content portions
+ and the line portion are considered or not.
+ */
+ void MaxAscentDescent( SwTwips& _orAscent,
+ SwTwips& _orDescent,
+ SwTwips& _orObjAscent,
+ SwTwips& _orObjDescent,
+ const SwLinePortion* _pDontConsiderPortion = nullptr,
+ const bool _bNoFlyCntPorAndLinePor = false ) const;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+/// Collection of SwLineLayout instances, represents the paragraph text in Writer layout.
+/// Typically owned by an SwTextFrame.
+class SwParaPortion : public SwLineLayout
+{
+ // Area that needs repainting
+ SwRepaint m_aRepaint;
+ // Area that needs reformatting
+ SwCharRange m_aReformat;
+ SwScriptInfo m_aScriptInfo;
+
+ // Fraction aZoom;
+ tools::Long m_nDelta;
+
+ // If a SwTextFrame is locked, no changes occur to the formatting data (under
+ // pLine) (compare with Orphans)
+ bool m_bFlys : 1; // Overlapping Flys?
+ bool m_bPrep : 1; // PREP_*
+ bool m_bPrepWidows : 1; // PrepareHint::Widows
+ bool m_bPrepAdjust : 1; // PrepareHint::AdjustSizeWithoutFormatting
+ bool m_bPrepMustFit : 1; // PrepareHint::MustFit
+ bool m_bFollowField : 1; // We have a bit of field left for the Follow
+
+ bool m_bFixLineHeight : 1; // Fixed line height
+ bool m_bFootnoteNum : 1; // is the frame that may contain a footnotenumberportion
+ bool m_bMargin : 1; // contains a hanging punctuation in the margin
+
+public:
+ SwParaPortion();
+ virtual ~SwParaPortion() override;
+
+ // Resets all formatting information (except for bFlys)
+ inline void FormatReset();
+
+ // Resets the Flags
+ inline void ResetPreps();
+
+ // Get/Set methods
+ SwRepaint& GetRepaint() { return m_aRepaint; }
+ const SwRepaint& GetRepaint() const { return m_aRepaint; }
+ SwCharRange& GetReformat() { return m_aReformat; }
+ const SwCharRange& GetReformat() const { return m_aReformat; }
+ void SetDelta(tools::Long nDelta) { m_nDelta = nDelta; }
+ tools::Long GetDelta() const { return m_nDelta; }
+ SwScriptInfo& GetScriptInfo() { return m_aScriptInfo; }
+ const SwScriptInfo& GetScriptInfo() const { return m_aScriptInfo; }
+
+ // For SwTextFrame::Format: returns the paragraph's current length
+ TextFrameIndex GetParLen() const;
+
+ // For Prepare()
+ bool UpdateQuoVadis( std::u16string_view rQuo );
+
+ // Flags
+ void SetFly() { m_bFlys = true; }
+ bool HasFly() const { return m_bFlys; }
+
+ // Preps
+ void SetPrep() { m_bPrep = true; }
+ bool IsPrep() const { return m_bPrep; }
+ void SetPrepWidows() { m_bPrepWidows = true; }
+ bool IsPrepWidows() const { return m_bPrepWidows; }
+ void SetPrepMustFit( const bool bNew ) { m_bPrepMustFit = bNew; }
+ bool IsPrepMustFit() const { return m_bPrepMustFit; }
+ void SetPrepAdjust() { m_bPrepAdjust = true; }
+ bool IsPrepAdjust() const { return m_bPrepAdjust; }
+ void SetFollowField( const bool bNew ) { m_bFollowField = bNew; }
+ bool IsFollowField() const { return m_bFollowField; }
+ void SetFixLineHeight() { m_bFixLineHeight = true; }
+ bool IsFixLineHeight() const { return m_bFixLineHeight; }
+
+ void SetFootnoteNum( const bool bNew ) { m_bFootnoteNum = bNew; }
+ bool IsFootnoteNum() const { return m_bFootnoteNum; }
+ void SetMargin( const bool bNew = true ) { m_bMargin = bNew; }
+ bool IsMargin() const { return m_bMargin; }
+ enum FootnoteOrNot { OnlyNumbering, FootnoteToo };
+ bool HasNumberingPortion(FootnoteOrNot) const;
+ bool HasContentPortions() const;
+
+ // Set nErgo in the QuoVadisPortion
+ void SetErgoSumNum( const OUString &rErgo );
+
+ const SwDropPortion *FindDropPortion() const;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+inline void SwParaPortion::ResetPreps()
+{
+ m_bPrep = m_bPrepWidows = m_bPrepAdjust = m_bPrepMustFit = false;
+}
+
+inline void SwParaPortion::FormatReset()
+{
+ m_nDelta = 0;
+ m_aReformat = SwCharRange(TextFrameIndex(0), TextFrameIndex(COMPLETE_STRING));
+ // bFlys needs to be retained in SwTextFrame::Format_() so that empty
+ // paragraphs that needed to avoid Frames with no flow, reformat
+ // when the Frame disappears from the Area
+ // bFlys = false;
+ ResetPreps();
+ m_bFollowField = m_bFixLineHeight = m_bMargin = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porlin.cxx b/sw/source/core/text/porlin.cxx
new file mode 100644
index 0000000000..a420d64301
--- /dev/null
+++ b/sw/source/core/text/porlin.cxx
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/outdev.hxx>
+#include <SwPortionHandler.hxx>
+
+#include "porlin.hxx"
+#include "portxt.hxx"
+#include "inftxt.hxx"
+#include "pormulti.hxx"
+#if OSL_DEBUG_LEVEL > 0
+
+static bool ChkChain( SwLinePortion *pStart )
+{
+ SwLinePortion *pPor = pStart->GetNextPortion();
+ sal_uInt16 nCount = 0;
+ while( pPor )
+ {
+ ++nCount;
+ OSL_ENSURE( nCount < 200 && pPor != pStart,
+ "ChkChain(): lost in chains" );
+ if( nCount >= 200 || pPor == pStart )
+ {
+ // the lifesaver
+ pPor = pStart->GetNextPortion();
+ pStart->SetNextPortion(nullptr);
+ pPor->Truncate();
+ pStart->SetNextPortion( pPor );
+ return false;
+ }
+ pPor = pPor->GetNextPortion();
+ }
+ return true;
+}
+#endif
+
+SwLinePortion::~SwLinePortion()
+{
+}
+
+SwLinePortion *SwLinePortion::Compress()
+{
+ return GetLen() || Width() ? this : nullptr;
+}
+
+sal_uInt16 SwLinePortion::GetViewWidth( const SwTextSizeInfo & ) const
+{
+ return 0;
+}
+
+SwLinePortion::SwLinePortion( ) :
+ mpNextPortion( nullptr ),
+ mnLineLength( 0 ),
+ mnAscent( 0 ),
+ mnHangingBaseline( 0 ),
+ mnWhichPor( PortionType::NONE ),
+ m_bJoinBorderWithPrev(false),
+ m_bJoinBorderWithNext(false)
+{
+}
+
+void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf,
+ const SwLinePortion* pLast ) const
+{
+ OSL_ENSURE( rInf.OnWin(), "SwLinePortion::PrePaint: don't prepaint on a printer");
+ OSL_ENSURE( !Width(), "SwLinePortion::PrePaint: For Width()==0 only!");
+
+ const sal_uInt16 nViewWidth = GetViewWidth( rInf );
+
+ if( ! nViewWidth )
+ return;
+
+ const sal_uInt16 nHalfView = nViewWidth / 2;
+ sal_uInt16 nLastWidth = pLast->Width() + pLast->ExtraBlankWidth();
+
+ if ( pLast->InSpaceGrp() && rInf.GetSpaceAdd(/*bShrink=*/true) )
+ nLastWidth += pLast->CalcSpacing( rInf.GetSpaceAdd(/*bShrink=*/true), rInf );
+
+ sal_uInt16 nPos;
+ SwTextPaintInfo aInf( rInf );
+
+ const bool bBidiPor = rInf.GetTextFrame()->IsRightToLeft() !=
+ bool( vcl::text::ComplexTextLayoutFlags::BiDiRtl & rInf.GetOut()->GetLayoutMode() );
+
+ Degree10 nDir = bBidiPor ?
+ 1800_deg10 :
+ rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() );
+
+ // pLast == this *only* for the 1st portion in the line so nLastWidth is 0;
+ // allow this too, will paint outside the frame but might look better...
+ if (nLastWidth > nHalfView || pLast == this)
+ {
+ switch (nDir.get())
+ {
+ case 0:
+ nPos = sal_uInt16( rInf.X() );
+ nPos += nLastWidth - nHalfView;
+ aInf.X( nPos );
+ break;
+ case 900:
+ nPos = sal_uInt16( rInf.Y() );
+ nPos -= nLastWidth - nHalfView;
+ aInf.Y( nPos );
+ break;
+ case 1800:
+ nPos = sal_uInt16( rInf.X() );
+ nPos -= nLastWidth - nHalfView;
+ aInf.X( nPos );
+ break;
+ case 2700:
+ nPos = sal_uInt16( rInf.Y() );
+ nPos += nLastWidth - nHalfView;
+ aInf.Y( nPos );
+ break;
+ }
+ }
+
+ SwLinePortion *pThis = const_cast<SwLinePortion*>(this);
+ pThis->Width( nViewWidth );
+ Paint( aInf );
+ pThis->Width(0);
+}
+
+void SwLinePortion::CalcTextSize( const SwTextSizeInfo &rInf )
+{
+ if( GetLen() == rInf.GetLen() )
+ *static_cast<SwPosSize*>(this) = GetTextSize( rInf );
+ else
+ {
+ SwTextSizeInfo aInf( rInf );
+ aInf.SetLen( GetLen() );
+ *static_cast<SwPosSize*>(this) = GetTextSize( aInf );
+ }
+}
+
+// all following portions will be deleted
+void SwLinePortion::Truncate_()
+{
+ SwLinePortion *pPos = mpNextPortion;
+ do
+ {
+ OSL_ENSURE( pPos != this, "SwLinePortion::Truncate: loop" );
+ SwLinePortion *pLast = pPos;
+ pPos = pPos->GetNextPortion();
+ pLast->SetNextPortion( nullptr );
+ delete pLast;
+
+ } while( pPos );
+
+ mpNextPortion = nullptr;
+}
+
+// It always will be inserted after us.
+SwLinePortion *SwLinePortion::Insert( SwLinePortion *pIns )
+{
+ pIns->FindLastPortion()->SetNextPortion( mpNextPortion );
+ SetNextPortion( pIns );
+#if OSL_DEBUG_LEVEL > 0
+ ChkChain( this );
+#endif
+ return pIns;
+}
+
+SwLinePortion *SwLinePortion::FindLastPortion()
+{
+ SwLinePortion *pPos = this;
+ // Find the end and link pLinPortion to the last one...
+ while( pPos->GetNextPortion() )
+ {
+ pPos = pPos->GetNextPortion();
+ }
+ return pPos;
+}
+
+SwLinePortion *SwLinePortion::Append( SwLinePortion *pIns )
+{
+ SwLinePortion *pPos = FindLastPortion();
+ pPos->SetNextPortion( pIns );
+ pIns->SetNextPortion( nullptr );
+#if OSL_DEBUG_LEVEL > 0
+ ChkChain( this );
+#endif
+ return pIns;
+}
+
+SwLinePortion *SwLinePortion::Cut( SwLinePortion *pVictim )
+{
+ SwLinePortion *pPrev = pVictim->FindPrevPortion( this );
+ OSL_ENSURE( pPrev, "SwLinePortion::Cut(): can't cut" );
+ // note: if pVictim is a follow then clearing pPrev's m_bHasFollow here is
+ // tricky because it could be that the HookChar inserted a tab portion
+ // between 2 field portions
+ pPrev->SetNextPortion( pVictim->GetNextPortion() );
+ pVictim->SetNextPortion(nullptr);
+ return pVictim;
+}
+
+SwLinePortion *SwLinePortion::FindPrevPortion( const SwLinePortion *pRoot )
+{
+ OSL_ENSURE( pRoot != this, "SwLinePortion::FindPrevPortion(): invalid root" );
+ SwLinePortion *pPos = const_cast<SwLinePortion*>(pRoot);
+ while( pPos->GetNextPortion() && pPos->GetNextPortion() != this )
+ {
+ pPos = pPos->GetNextPortion();
+ }
+ OSL_ENSURE( pPos->GetNextPortion(),
+ "SwLinePortion::FindPrevPortion: blowing in the wind");
+ return pPos;
+}
+
+TextFrameIndex SwLinePortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const
+{
+ if( nOfst > ( PrtWidth() / 2 ) )
+ return GetLen();
+ else
+ return TextFrameIndex(0);
+}
+
+SwPosSize SwLinePortion::GetTextSize( const SwTextSizeInfo & ) const
+{
+ OSL_ENSURE( false, "SwLinePortion::GetTextSize: don't ask me about sizes, "
+ "I'm only a stupid SwLinePortion" );
+ return SwPosSize();
+}
+
+bool SwLinePortion::Format( SwTextFormatInfo &rInf )
+{
+ if( rInf.X() > rInf.Width() )
+ {
+ Truncate();
+ rInf.SetUnderflow( this );
+ return true;
+ }
+
+ const SwLinePortion *pLast = rInf.GetLast();
+ Height( pLast->Height() );
+ SetAscent( pLast->GetAscent() );
+ const sal_uInt16 nNewWidth = o3tl::narrowing<sal_uInt16>(rInf.X() + PrtWidth());
+ // Only portions with true width can return true
+ // Notes for example never set bFull==true
+ if( rInf.Width() <= nNewWidth && PrtWidth() && ! IsKernPortion() )
+ {
+ Truncate();
+ if( nNewWidth > rInf.Width() )
+ PrtWidth( nNewWidth - rInf.Width() );
+ rInf.GetLast()->FormatEOL( rInf );
+ return true;
+ }
+ return false;
+}
+
+// Format end of line
+
+void SwLinePortion::FormatEOL( SwTextFormatInfo & )
+{ }
+
+void SwLinePortion::Move(SwTextPaintInfo & rInf) const
+{
+ bool bB2T = rInf.GetDirection() == DIR_BOTTOM2TOP;
+ const bool bFrameDir = rInf.GetTextFrame()->IsRightToLeft();
+ bool bCounterDir = ( ! bFrameDir && DIR_RIGHT2LEFT == rInf.GetDirection() ) ||
+ ( bFrameDir && DIR_LEFT2RIGHT == rInf.GetDirection() );
+
+ SwTwips nTmp = PrtWidth() + ExtraBlankWidth();
+ if ( InSpaceGrp() && rInf.GetSpaceAdd(/*bShrink=*/true) )
+ {
+ nTmp += CalcSpacing( rInf.GetSpaceAdd(/*bShrink=*/true), rInf );
+ if( rInf.IsRotated() )
+ rInf.Y( rInf.Y() + ( bB2T ? -nTmp : nTmp ) );
+ else if ( bCounterDir )
+ rInf.X( rInf.X() - nTmp );
+ else
+ rInf.X( rInf.X() + nTmp );
+ }
+ else
+ {
+ if( InFixMargGrp() && !IsMarginPortion() )
+ {
+ rInf.IncSpaceIdx();
+ rInf.IncKanaIdx();
+ }
+ if( rInf.IsRotated() )
+ rInf.Y(rInf.Y() + (bB2T ? -nTmp : nTmp));
+ else if ( bCounterDir )
+ rInf.X(rInf.X() - nTmp);
+ else
+ rInf.X(rInf.X() + nTmp);
+ }
+ if (IsMultiPortion() && static_cast<SwMultiPortion const*>(this)->HasTabulator())
+ rInf.IncSpaceIdx();
+
+ rInf.SetIdx( rInf.GetIdx() + GetLen() );
+}
+
+SwTwips SwLinePortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const
+{
+ return 0;
+}
+
+bool SwLinePortion::GetExpText( const SwTextSizeInfo &, OUString & ) const
+{
+ return false;
+}
+
+void SwLinePortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString(), GetWhichPor() );
+}
+
+void SwLinePortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLinePortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwLinePortion::dumpAsXmlAttributes(xmlTextWriterPtr pWriter, std::u16string_view rText, TextFrameIndex nOffset) const
+{
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("width"),
+ BAD_CAST(OString::number(Width()).getStr()));
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("height"),
+ BAD_CAST(OString::number(Height()).getStr()));
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("length"),
+ BAD_CAST(OString::number(static_cast<sal_Int32>(mnLineLength)).getStr()));
+ (void)xmlTextWriterWriteAttribute(
+ pWriter, BAD_CAST("type"),
+ BAD_CAST(sw::PortionTypeToString(GetWhichPor())));
+ OUString aText( rText.substr(sal_Int32(nOffset), sal_Int32(GetLen())) );
+ for (int i = 0; i < 32; ++i)
+ aText = aText.replace(i, '*');
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("portion"),
+ BAD_CAST(aText.toUtf8().getStr()));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx
new file mode 100644
index 0000000000..50ee3ed159
--- /dev/null
+++ b/sw/source/core/text/porlin.hxx
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <libxml/xmlwriter.h>
+
+#include "possiz.hxx"
+#include <txttypes.hxx>
+#include <TextFrameIndex.hxx>
+#include <rtl/ustring.hxx>
+
+class SwTextSizeInfo;
+class SwTextPaintInfo;
+class SwTextFormatInfo;
+class SwPortionHandler;
+
+/// Portion groups
+/// @see enum PortionType in txttypes.hxx
+#define PORGRP_TXT 0x8000
+#define PORGRP_EXP 0x4000
+#define PORGRP_FLD 0x2000
+#define PORGRP_HYPH 0x1000
+#define PORGRP_NUMBER 0x0800
+#define PORGRP_GLUE 0x0400
+#define PORGRP_FIX 0x0200
+#define PORGRP_TAB 0x0100
+// Small special groups
+#define PORGRP_FIXMARG 0x0040
+//#define PORGRP_? 0x0020
+#define PORGRP_TABNOTLFT 0x0010
+#define PORGRP_TOXREF 0x0008
+
+/// Base class for anything that can be part of a line in the Writer layout.
+/// Typically owned by SwLineLayout.
+class SAL_DLLPUBLIC_RTTI SwLinePortion: public SwPosSize
+{
+protected:
+ // Here we have areas with different attributes
+ SwLinePortion *mpNextPortion;
+ // Count of chars and spaces on the line
+ TextFrameIndex mnLineLength;
+ SwTwips mnAscent; // Maximum ascender
+ SwTwips mnHangingBaseline;
+
+ SwLinePortion();
+private:
+ PortionType mnWhichPor; // Who's who?
+ bool m_bJoinBorderWithPrev;
+ bool m_bJoinBorderWithNext;
+ SwTwips m_nExtraBlankWidth = 0; // width of spaces after the break
+
+ void Truncate_();
+
+public:
+ explicit inline SwLinePortion(const SwLinePortion &rPortion);
+ virtual ~SwLinePortion();
+
+ // Access methods
+ SwLinePortion *GetNextPortion() const { return mpNextPortion; }
+ inline SwLinePortion &operator=(const SwLinePortion &rPortion);
+ TextFrameIndex GetLen() const { return mnLineLength; }
+ void SetLen(TextFrameIndex const nLen) { mnLineLength = nLen; }
+ void SetNextPortion( SwLinePortion *pNew ){ mpNextPortion = pNew; }
+ SwTwips &GetAscent() { return mnAscent; }
+ SwTwips GetAscent() const { return mnAscent; }
+ void SetAscent( const SwTwips nNewAsc ) { mnAscent = nNewAsc; }
+ void PrtWidth( SwTwips nNewWidth ) { Width( nNewWidth ); }
+ SwTwips PrtWidth() const { return Width(); }
+ void AddPrtWidth( const SwTwips nNew ) { Width( Width() + nNew ); }
+ void SubPrtWidth( const SwTwips nNew ) { Width( Width() - nNew ); }
+ SwTwips ExtraBlankWidth() const { return m_nExtraBlankWidth; }
+ void ExtraBlankWidth(const SwTwips nNew) { m_nExtraBlankWidth = nNew; }
+ SwTwips GetHangingBaseline() const { return mnHangingBaseline; }
+ void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline = nNewBaseline; }
+
+ // Insert methods
+ virtual SwLinePortion *Insert( SwLinePortion *pPortion );
+ virtual SwLinePortion *Append( SwLinePortion *pPortion );
+ SwLinePortion *Cut( SwLinePortion *pVictim );
+ inline void Truncate();
+
+ // Returns 0, if there's no payload
+ virtual SwLinePortion *Compress();
+
+ void SetWhichPor( const PortionType nNew ) { mnWhichPor = nNew; }
+ PortionType GetWhichPor( ) const { return mnWhichPor; }
+
+// Group queries
+ bool InTextGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_TXT) != 0; }
+ bool InGlueGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_GLUE) != 0; }
+ bool InTabGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_TAB) != 0; }
+ bool InHyphGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_HYPH) != 0; }
+ bool InNumberGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_NUMBER) != 0; }
+ bool InFixGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_FIX) != 0; }
+ bool InFieldGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_FLD) != 0; }
+ bool InToxRefGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_TOXREF) != 0; }
+ bool InToxRefOrFieldGrp() const { return (sal_uInt16(mnWhichPor) & ( PORGRP_FLD | PORGRP_TOXREF )) != 0; }
+ bool InExpGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_EXP) != 0; }
+ bool InFixMargGrp() const { return (sal_uInt16(mnWhichPor) & PORGRP_FIXMARG) != 0; }
+ bool InSpaceGrp() const { return InTextGrp() || IsMultiPortion(); }
+// Individual queries
+ bool IsGrfNumPortion() const { return mnWhichPor == PortionType::GrfNum; }
+ bool IsFlyCntPortion() const { return mnWhichPor == PortionType::FlyCnt; }
+ bool IsBlankPortion() const { return mnWhichPor == PortionType::Blank; }
+ bool IsBreakPortion() const { return mnWhichPor == PortionType::Break; }
+ bool IsErgoSumPortion() const { return mnWhichPor == PortionType::ErgoSum; }
+ bool IsQuoVadisPortion() const { return mnWhichPor == PortionType::QuoVadis; }
+ bool IsTabLeftPortion() const { return mnWhichPor == PortionType::TabLeft; }
+ bool IsTabRightPortion() const { return mnWhichPor == PortionType::TabRight; }
+ bool IsTabCenterPortion() const { return mnWhichPor == PortionType::TabCenter; }
+ bool IsTabDecimalPortion() const { return mnWhichPor == PortionType::TabDecimal; }
+ bool IsFootnoteNumPortion() const { return mnWhichPor == PortionType::FootnoteNum; }
+ bool IsFootnotePortion() const { return mnWhichPor == PortionType::Footnote; }
+ bool IsDropPortion() const { return mnWhichPor == PortionType::Drop; }
+ bool IsLayPortion() const { return mnWhichPor == PortionType::Lay; }
+ bool IsParaPortion() const { return mnWhichPor == PortionType::Para; }
+ bool IsMarginPortion() const { return mnWhichPor == PortionType::Margin; }
+ bool IsFlyPortion() const { return mnWhichPor == PortionType::Fly; }
+ bool IsHolePortion() const { return mnWhichPor == PortionType::Hole; }
+ bool IsSoftHyphPortion() const { return mnWhichPor == PortionType::SoftHyphen; }
+ bool IsPostItsPortion() const { return mnWhichPor == PortionType::PostIts; }
+ bool IsCombinedPortion() const { return mnWhichPor == PortionType::Combined; }
+ bool IsTextPortion() const { return mnWhichPor == PortionType::Text; }
+ bool IsHangingPortion() const { return mnWhichPor == PortionType::Hanging; }
+ bool IsKernPortion() const { return mnWhichPor == PortionType::Kern; }
+ bool IsArrowPortion() const { return mnWhichPor == PortionType::Arrow; }
+ bool IsMultiPortion() const { return mnWhichPor == PortionType::Multi; }
+ bool IsNumberPortion() const { return mnWhichPor == PortionType::Number; } // #i23726#
+ bool IsControlCharPortion() const { return mnWhichPor == PortionType::ControlChar || mnWhichPor == PortionType::Bookmark; }
+
+ // Positioning
+ SwLinePortion *FindPrevPortion( const SwLinePortion *pRoot );
+ SwLinePortion *FindLastPortion();
+
+ /// the parameter is actually SwTwips apparently?
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const;
+ void CalcTextSize( const SwTextSizeInfo &rInfo );
+
+ // Output
+ virtual void Paint( const SwTextPaintInfo &rInf ) const = 0;
+ void PrePaint( const SwTextPaintInfo &rInf, const SwLinePortion *pLast ) const;
+
+ virtual bool Format( SwTextFormatInfo &rInf );
+ // Is called for the line's last portion
+ virtual void FormatEOL( SwTextFormatInfo &rInf );
+ void Move(SwTextPaintInfo & rInf) const;
+
+ // For SwTextSlot
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const;
+
+ // For SwFieldPortion, SwSoftHyphPortion
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const;
+
+ // for text- and multi-portions
+ virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const;
+
+ bool GetJoinBorderWithPrev() const { return m_bJoinBorderWithPrev; }
+ bool GetJoinBorderWithNext() const { return m_bJoinBorderWithNext; }
+ void SetJoinBorderWithPrev( const bool bJoinPrev ) { m_bJoinBorderWithPrev = bJoinPrev; }
+ void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; }
+
+ virtual void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& rOffset) const;
+ void dumpAsXmlAttributes(xmlTextWriterPtr writer, std::u16string_view rText,
+ TextFrameIndex nOffset) const;
+};
+
+inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion)
+{
+ *static_cast<SwPosSize*>(this) = rPortion;
+ mnLineLength = rPortion.mnLineLength;
+ mnAscent = rPortion.mnAscent;
+ mnHangingBaseline = rPortion.mnHangingBaseline;
+ mnWhichPor = rPortion.mnWhichPor;
+ m_bJoinBorderWithPrev = rPortion.m_bJoinBorderWithPrev;
+ m_bJoinBorderWithNext = rPortion.m_bJoinBorderWithNext;
+ m_nExtraBlankWidth = rPortion.m_nExtraBlankWidth;
+ return *this;
+}
+
+inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) :
+ SwPosSize( rPortion ),
+ mpNextPortion( nullptr ),
+ mnLineLength( rPortion.mnLineLength ),
+ mnAscent( rPortion.mnAscent ),
+ mnHangingBaseline( rPortion.mnHangingBaseline ),
+ mnWhichPor( rPortion.mnWhichPor ),
+ m_bJoinBorderWithPrev( rPortion.m_bJoinBorderWithPrev ),
+ m_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext ),
+ m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth)
+{
+}
+
+inline void SwLinePortion::Truncate()
+{
+ if ( mpNextPortion )
+ Truncate_();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx
new file mode 100644
index 0000000000..7771ab9b5e
--- /dev/null
+++ b/sw/source/core/text/pormulti.cxx
@@ -0,0 +1,2635 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <deque>
+#include <memory>
+
+#include <hintids.hxx>
+
+#include <editeng/twolinesitem.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <vcl/outdev.hxx>
+#include <txatbase.hxx>
+#include <fmtruby.hxx>
+#include <txtatr.hxx>
+#include <charfmt.hxx>
+#include <layfrm.hxx>
+#include <SwPortionHandler.hxx>
+#include <EnhancedPDFExportHelper.hxx>
+#include "pormulti.hxx"
+#include "inftxt.hxx"
+#include "itrpaint.hxx"
+#include <viewopt.hxx>
+#include "itrform2.hxx"
+#include "porfld.hxx"
+#include "porglue.hxx"
+#include "porrst.hxx"
+#include <pagefrm.hxx>
+#include <rowfrm.hxx>
+#include <tgrditem.hxx>
+#include <swtable.hxx>
+#include <fmtfsize.hxx>
+#include <doc.hxx>
+
+using namespace ::com::sun::star;
+
+// A SwMultiPortion is not a simple portion,
+// it's a container, which contains almost a SwLineLayoutPortion.
+// This SwLineLayout could be followed by other textportions via pPortion
+// and by another SwLineLayout via pNext to realize a doubleline portion.
+SwMultiPortion::~SwMultiPortion()
+{
+}
+
+void SwMultiPortion::Paint( const SwTextPaintInfo & ) const
+{
+ OSL_FAIL( "Don't try SwMultiPortion::Paint, try SwTextPainter::PaintMultiPortion" );
+}
+
+// Summarize the internal lines to calculate the (external) size.
+// The internal line has to calculate first.
+void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf )
+{
+ Width( 0 );
+ Height( 0 );
+ SetAscent( 0 );
+ SetFlyInContent( false );
+ SwLineLayout *pLay = &GetRoot();
+ do
+ {
+ pLay->CalcLine( rLine, rInf );
+ if( rLine.IsFlyInCntBase() )
+ SetFlyInContent( true );
+ if( IsRuby() && ( OnTop() == ( pLay == &GetRoot() ) ) )
+ {
+ // An empty phonetic line don't need an ascent or a height.
+ if( !pLay->Width() )
+ {
+ pLay->SetAscent( 0 );
+ pLay->Height( 0 );
+ }
+ if( OnTop() )
+ SetAscent( GetAscent() + pLay->Height() );
+ }
+ else
+ SetAscent( GetAscent() + pLay->GetAscent() );
+
+ // Increase the line height, except for ruby text on the right.
+ if ( !IsRuby() || !OnRight() || pLay == &GetRoot() )
+ Height( Height() + pLay->Height() );
+ else
+ {
+ // We already added the width after building the portion,
+ // so no need to add it twice.
+ break;
+ }
+
+ if( Width() < pLay->Width() )
+ Width( pLay->Width() );
+ pLay = pLay->GetNext();
+ } while ( pLay );
+ if( !HasBrackets() )
+ return;
+
+ sal_uInt16 nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nHeight;
+ if( nTmp > Height() )
+ {
+ const sal_uInt16 nAdd = ( nTmp - Height() ) / 2;
+ GetRoot().SetAscent( GetRoot().GetAscent() + nAdd );
+ GetRoot().Height( GetRoot().Height() + nAdd );
+ Height( nTmp );
+ }
+ nTmp = static_cast<SwDoubleLinePortion*>(this)->GetBrackets()->nAscent;
+ if( nTmp > GetAscent() )
+ SetAscent( nTmp );
+}
+
+SwTwips SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const
+{
+ return 0;
+}
+
+bool SwMultiPortion::ChgSpaceAdd( SwLineLayout*, tools::Long ) const
+{
+ return false;
+}
+
+void SwMultiPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+void SwMultiPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwMultiPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ // Intentionally not incrementing nOffset here, one of the child portions will do that.
+
+ const SwLineLayout* pLine = &GetRoot();
+ while (pLine)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout"));
+ pLine->dumpAsXmlAttributes(pWriter, rText, nOffset);
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while (pPor)
+ {
+ pPor->dumpAsXml(pWriter, rText, nOffset);
+ pPor = pPor->GetNextPortion();
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+ pLine = pLine->GetNext();
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+// sets the tabulator-flag, if there's any tabulator-portion inside.
+void SwMultiPortion::ActualizeTabulator()
+{
+ SwLinePortion* pPor = GetRoot().GetFirstPortion();
+ // First line
+ for( m_bTab1 = m_bTab2 = false; pPor; pPor = pPor->GetNextPortion() )
+ if( pPor->InTabGrp() )
+ SetTab1( true );
+ if( GetRoot().GetNext() )
+ {
+ // Second line
+ pPor = GetRoot().GetNext()->GetFirstPortion();
+ do
+ {
+ if( pPor->InTabGrp() )
+ SetTab2( true );
+ pPor = pPor->GetNextPortion();
+ } while ( pPor );
+ }
+}
+
+SwRotatedPortion::SwRotatedPortion( const SwMultiCreator& rCreate,
+ TextFrameIndex const nEnd, bool bRTL )
+ : SwMultiPortion( nEnd )
+{
+ const SvxCharRotateItem* pRot = static_cast<const SvxCharRotateItem*>(rCreate.pItem);
+ if( !pRot )
+ {
+ const SwTextAttr& rAttr = *rCreate.pAttr;
+ const SfxPoolItem *const pItem =
+ CharFormat::GetItem(rAttr, RES_CHRATR_ROTATE);
+ if ( pItem )
+ {
+ pRot = static_cast<const SvxCharRotateItem*>(pItem);
+ }
+ }
+ if( pRot )
+ {
+ sal_uInt8 nDir;
+ if ( bRTL )
+ nDir = pRot->IsBottomToTop() ? 3 : 1;
+ else
+ nDir = pRot->IsBottomToTop() ? 1 : 3;
+
+ SetDirection( nDir );
+ }
+}
+
+SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv)
+ : SwMultiPortion( nEnd ), m_nLevel( nLv )
+{
+ SetBidi();
+
+ if ( m_nLevel % 2 )
+ SetDirection( DIR_RIGHT2LEFT );
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+SwTwips SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const
+{
+ return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR;
+}
+
+bool SwBidiPortion::ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const
+{
+ if( !HasTabulator() && nSpaceAdd > 0 && !pCurr->IsSpaceAdd() )
+ {
+ pCurr->CreateSpaceAdd();
+ pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
+ return true;
+ }
+
+ return false;
+}
+
+TextFrameIndex SwBidiPortion::GetSpaceCnt(const SwTextSizeInfo &rInf) const
+{
+ // Calculate number of blanks for justified alignment
+ TextFrameIndex nTmpStart = rInf.GetIdx();
+ TextFrameIndex nNull(0);
+ TextFrameIndex nBlanks(0);
+
+ for (SwLinePortion* pPor = GetRoot().GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ nBlanks = nBlanks + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ else if ( pPor->IsMultiPortion() &&
+ static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ nBlanks = nBlanks + static_cast<SwBidiPortion*>(pPor)->GetSpaceCnt( rInf );
+
+ const_cast<SwTextSizeInfo &>(rInf).SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ }
+ const_cast<SwTextSizeInfo &>(rInf).SetIdx( nTmpStart );
+ return nBlanks;
+}
+
+// This constructor is for the continuation of a doubleline portion
+// in the next line.
+// It takes the same brackets and if the original has no content except
+// brackets, these will be deleted.
+SwDoubleLinePortion::SwDoubleLinePortion(
+ SwDoubleLinePortion& rDouble, TextFrameIndex const nEnd)
+ : SwMultiPortion(nEnd)
+ , m_nLineDiff(0)
+ , m_nBlank1(0)
+ , m_nBlank2(0)
+{
+ SetDirection( rDouble.GetDirection() );
+ SetDouble();
+ if( rDouble.GetBrackets() )
+ {
+ SetBrackets( rDouble );
+ // An empty multiportion needs no brackets.
+ // Notice: GetLen() might be zero, if the multiportion contains
+ // the second part of a field and the width might be zero, if
+ // it contains a note only. In this cases the brackets are okay.
+ // But if the length and the width are both zero, the portion
+ // is really empty.
+ if( rDouble.Width() == rDouble.BracketWidth() )
+ rDouble.ClearBrackets();
+ }
+}
+
+// This constructor uses the textattribute to get the right brackets.
+// The textattribute could be a 2-line-attribute or a character- or
+// internet style, which contains the 2-line-attribute.
+SwDoubleLinePortion::SwDoubleLinePortion(
+ const SwMultiCreator& rCreate, TextFrameIndex const nEnd)
+ : SwMultiPortion(nEnd)
+ , m_pBracket(new SwBracket)
+ , m_nLineDiff(0)
+ , m_nBlank1(0)
+ , m_nBlank2(0)
+{
+ m_pBracket->nAscent = 0;
+ m_pBracket->nHeight = 0;
+ m_pBracket->nPreWidth = 0;
+ m_pBracket->nPostWidth = 0;
+
+ SetDouble();
+ const SvxTwoLinesItem* pTwo = static_cast<const SvxTwoLinesItem*>(rCreate.pItem);
+ if( pTwo )
+ m_pBracket->nStart = TextFrameIndex(0);
+ else
+ {
+ const SwTextAttr& rAttr = *rCreate.pAttr;
+ m_pBracket->nStart = rCreate.nStartOfAttr;
+
+ const SfxPoolItem * const pItem =
+ CharFormat::GetItem( rAttr, RES_CHRATR_TWO_LINES );
+ if ( pItem )
+ {
+ pTwo = static_cast<const SvxTwoLinesItem*>(pItem);
+ }
+ }
+ if( pTwo )
+ {
+ m_pBracket->cPre = pTwo->GetStartBracket();
+ m_pBracket->cPost = pTwo->GetEndBracket();
+ }
+ else
+ {
+ m_pBracket->cPre = 0;
+ m_pBracket->cPost = 0;
+ }
+ SwFontScript nTmp = SW_SCRIPTS;
+ if( m_pBracket->cPre > 255 )
+ {
+ OUString aText(m_pBracket->cPre);
+ nTmp = SwScriptInfo::WhichFont(0, aText);
+ }
+ m_pBracket->nPreScript = nTmp;
+ nTmp = SW_SCRIPTS;
+ if( m_pBracket->cPost > 255 )
+ {
+ OUString aText(m_pBracket->cPost);
+ nTmp = SwScriptInfo::WhichFont(0, aText);
+ }
+ m_pBracket->nPostScript = nTmp;
+
+ if( !m_pBracket->cPre && !m_pBracket->cPost )
+ {
+ m_pBracket.reset();
+ }
+
+ // double line portions have the same direction as the frame directions
+ if ( rCreate.nLevel % 2 )
+ SetDirection( DIR_RIGHT2LEFT );
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+// paints the wished bracket,
+// if the multiportion has surrounding brackets.
+// The X-position of the SwTextPaintInfo will be modified:
+// the open bracket sets position behind itself,
+// the close bracket in front of itself.
+void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf,
+ tools::Long nSpaceAdd,
+ bool bOpen ) const
+{
+ sal_Unicode cCh = bOpen ? m_pBracket->cPre : m_pBracket->cPost;
+ if( !cCh )
+ return;
+ const sal_uInt16 nChWidth = bOpen ? PreWidth() : PostWidth();
+ if( !nChWidth )
+ return;
+ if( !bOpen )
+ rInf.X( rInf.X() + Width() - PostWidth() +
+ ( nSpaceAdd > 0 ? CalcSpacing( nSpaceAdd, rInf ) : 0 ) );
+
+ SwBlankPortion aBlank( cCh, true );
+ aBlank.SetAscent( m_pBracket->nAscent );
+ aBlank.Width( nChWidth );
+ aBlank.Height( m_pBracket->nHeight );
+ {
+ SwFont aTmpFnt( *rInf.GetFont() );
+ SwFontScript nAct = bOpen ? m_pBracket->nPreScript : m_pBracket->nPostScript;
+ if( SW_SCRIPTS > nAct )
+ aTmpFnt.SetActual( nAct );
+ aTmpFnt.SetProportion( 100 );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ aBlank.Paint( rInf );
+ }
+ if( bOpen )
+ rInf.X( rInf.X() + PreWidth() );
+}
+
+// creates the bracket-structure
+// and fills it, if not both characters are 0x00.
+void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble )
+{
+ if( rDouble.m_pBracket )
+ {
+ m_pBracket.reset( new SwBracket );
+ m_pBracket->cPre = rDouble.m_pBracket->cPre;
+ m_pBracket->cPost = rDouble.m_pBracket->cPost;
+ m_pBracket->nPreScript = rDouble.m_pBracket->nPreScript;
+ m_pBracket->nPostScript = rDouble.m_pBracket->nPostScript;
+ m_pBracket->nStart = rDouble.m_pBracket->nStart;
+ }
+}
+
+// calculates the size of the brackets => pBracket,
+// reduces the nMaxWidth-parameter ( minus bracket-width )
+// and moves the rInf-x-position behind the opening bracket.
+void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth )
+{
+ nMaxWidth -= rInf.X();
+ SwFont aTmpFnt( *rInf.GetFont() );
+ aTmpFnt.SetProportion( 100 );
+ m_pBracket->nAscent = 0;
+ m_pBracket->nHeight = 0;
+ if( m_pBracket->cPre )
+ {
+ OUString aStr( m_pBracket->cPre );
+ SwFontScript nActualScr = aTmpFnt.GetActual();
+ if( SW_SCRIPTS > m_pBracket->nPreScript )
+ aTmpFnt.SetActual( m_pBracket->nPreScript );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ SwPosSize aSize = rInf.GetTextSize( aStr );
+ m_pBracket->nAscent = rInf.GetAscent();
+ m_pBracket->nHeight = aSize.Height();
+ aTmpFnt.SetActual( nActualScr );
+ if( nMaxWidth > aSize.Width() )
+ {
+ m_pBracket->nPreWidth = aSize.Width();
+ nMaxWidth -= aSize.Width();
+ rInf.X( rInf.X() + aSize.Width() );
+ }
+ else
+ {
+ m_pBracket->nPreWidth = 0;
+ nMaxWidth = 0;
+ }
+ }
+ else
+ m_pBracket->nPreWidth = 0;
+ if( m_pBracket->cPost )
+ {
+ OUString aStr( m_pBracket->cPost );
+ if( SW_SCRIPTS > m_pBracket->nPostScript )
+ aTmpFnt.SetActual( m_pBracket->nPostScript );
+ SwFontSave aSave( rInf, &aTmpFnt );
+ SwPosSize aSize = rInf.GetTextSize( aStr );
+ const sal_uInt16 nTmpAsc = rInf.GetAscent();
+ if( nTmpAsc > m_pBracket->nAscent )
+ {
+ m_pBracket->nHeight += nTmpAsc - m_pBracket->nAscent;
+ m_pBracket->nAscent = nTmpAsc;
+ }
+ if( aSize.Height() > m_pBracket->nHeight )
+ m_pBracket->nHeight = aSize.Height();
+ if( nMaxWidth > aSize.Width() )
+ {
+ m_pBracket->nPostWidth = aSize.Width();
+ nMaxWidth -= aSize.Width();
+ }
+ else
+ {
+ m_pBracket->nPostWidth = 0;
+ nMaxWidth = 0;
+ }
+ }
+ else
+ m_pBracket->nPostWidth = 0;
+ nMaxWidth += rInf.X();
+}
+
+// calculates the number of blanks in each line and
+// the difference of the width of the two lines.
+// These results are used from the text adjustment.
+void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf )
+{
+ SwLinePortion* pPor = GetRoot().GetFirstPortion();
+ TextFrameIndex nNull(0);
+ TextFrameIndex nStart = rInf.GetIdx();
+ SetTab1( false );
+ SetTab2( false );
+ for (m_nBlank1 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ m_nBlank1 = m_nBlank1 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ if( pPor->InTabGrp() )
+ SetTab1( true );
+ }
+ m_nLineDiff = GetRoot().Width();
+ if( GetRoot().GetNext() )
+ {
+ pPor = GetRoot().GetNext()->GetFirstPortion();
+ m_nLineDiff -= GetRoot().GetNext()->Width();
+ }
+ for (m_nBlank2 = TextFrameIndex(0); pPor; pPor = pPor->GetNextPortion())
+ {
+ if( pPor->InTextGrp() )
+ m_nBlank2 = m_nBlank2 + static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nNull );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ if( pPor->InTabGrp() )
+ SetTab2( true );
+ }
+ rInf.SetIdx( nStart );
+}
+
+SwTwips SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const
+{
+ return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR;
+}
+
+// Merges the spaces for text adjustment from the inner and outer part.
+// Inside the doubleline portion the wider line has no spaceadd-array, the
+// smaller line has such an array to reach width of the wider line.
+// If the surrounding line has text adjustment and the doubleline portion
+// contains no tabulator, it is necessary to create/manipulate the inner
+// space arrays.
+bool SwDoubleLinePortion::ChgSpaceAdd( SwLineLayout* pCurr,
+ tools::Long nSpaceAdd ) const
+{
+ bool bRet = false;
+ if( !HasTabulator() && nSpaceAdd > 0 )
+ {
+ if( !pCurr->IsSpaceAdd() )
+ {
+ // The wider line gets the spaceadd from the surrounding line direct
+ pCurr->CreateSpaceAdd();
+ pCurr->SetLLSpaceAdd( nSpaceAdd, 0 );
+ bRet = true;
+ }
+ else
+ {
+ sal_Int32 const nMyBlank = sal_Int32(GetSmallerSpaceCnt());
+ sal_Int32 const nOther = sal_Int32(GetSpaceCnt());
+ SwTwips nMultiSpace = pCurr->GetLLSpaceAdd( 0 ) * nMyBlank + nOther * nSpaceAdd;
+
+ if( nMyBlank )
+ nMultiSpace /= sal_Int32(nMyBlank);
+
+// pCurr->SetLLSpaceAdd( nMultiSpace, 0 );
+ // #i65711# SetLLSpaceAdd replaces the first value,
+ // instead we want to insert a new first value:
+ std::vector<tools::Long>* pVec = pCurr->GetpLLSpaceAdd();
+ pVec->insert( pVec->begin(), nMultiSpace );
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+// cancels the manipulation from SwDoubleLinePortion::ChangeSpaceAdd(..)
+void SwDoubleLinePortion::ResetSpaceAdd( SwLineLayout* pCurr )
+{
+ pCurr->RemoveFirstLLSpaceAdd();
+ if( !pCurr->GetLLSpaceAddCount() )
+ pCurr->FinishSpaceAdd();
+}
+
+SwDoubleLinePortion::~SwDoubleLinePortion()
+{
+}
+
+// constructs a ruby portion, i.e. an additional text is displayed
+// beside the main text, e.g. phonetic characters.
+SwRubyPortion::SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex const nEnd)
+ : SwMultiPortion( nEnd )
+ , m_nRubyOffset( rRuby.GetRubyOffset() )
+ , m_nAdjustment( rRuby.GetAdjustment() )
+{
+ SetDirection( rRuby.GetDirection() );
+ SetRubyPosition( rRuby.GetRubyPosition() );
+ SetRuby();
+}
+
+// constructs a ruby portion, i.e. an additional text is displayed
+// beside the main text, e.g. phonetic characters.
+SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
+ const IDocumentSettingAccess& rIDocumentSettingAccess,
+ TextFrameIndex const nEnd, TextFrameIndex const nOffs,
+ const SwTextSizeInfo &rInf )
+ : SwMultiPortion( nEnd )
+{
+ SetRuby();
+ OSL_ENSURE( SwMultiCreatorId::Ruby == rCreate.nId, "Ruby expected" );
+ OSL_ENSURE( RES_TXTATR_CJK_RUBY == rCreate.pAttr->Which(), "Wrong attribute" );
+ const SwFormatRuby& rRuby = rCreate.pAttr->GetRuby();
+ m_nAdjustment = rRuby.GetAdjustment();
+ m_nRubyOffset = nOffs;
+
+ const SwTextFrame *pFrame = rInf.GetTextFrame();
+ RubyPosition ePos = static_cast<RubyPosition>( rRuby.GetPosition() );
+
+ // RIGHT is designed for horizontal writing mode only.
+ if ( ePos == RubyPosition::RIGHT && pFrame->IsVertical() )
+ ePos = RubyPosition::ABOVE;
+
+ // In grid mode we force the ruby text to the upper or lower line
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid( GetGridItem(pFrame->FindPageFrame()) );
+ if ( pGrid )
+ ePos = pGrid->GetRubyTextBelow() ? RubyPosition::BELOW : RubyPosition::ABOVE;
+ }
+
+ SetRubyPosition( ePos );
+
+ const SwCharFormat *const pFormat =
+ static_txtattr_cast<SwTextRuby const*>(rCreate.pAttr)->GetCharFormat();
+ std::unique_ptr<SwFont> pRubyFont;
+ if( pFormat )
+ {
+ const SwAttrSet& rSet = pFormat->GetAttrSet();
+ pRubyFont.reset(new SwFont( rFnt ));
+ pRubyFont->SetDiffFnt( &rSet, &rIDocumentSettingAccess );
+
+ // we do not allow a vertical font for the ruby text
+ pRubyFont->SetVertical( rFnt.GetOrientation() , OnRight() );
+ }
+
+ OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) );
+ SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) );
+ pField->SetNextOffset( nOffs );
+ pField->SetFollow( true );
+
+ if( OnTop() )
+ GetRoot().SetNextPortion( pField );
+ else
+ {
+ GetRoot().SetNext( new SwLineLayout() );
+ GetRoot().GetNext()->SetNextPortion( pField );
+ }
+
+ // ruby portions have the same direction as the frame directions
+ if ( rCreate.nLevel % 2 )
+ {
+ // switch right and left ruby adjustment in rtl environment
+ if ( css::text::RubyAdjust_LEFT == m_nAdjustment )
+ m_nAdjustment = css::text::RubyAdjust_RIGHT;
+ else if ( css::text::RubyAdjust_RIGHT == m_nAdjustment )
+ m_nAdjustment = css::text::RubyAdjust_LEFT;
+
+ SetDirection( DIR_RIGHT2LEFT );
+ }
+ else
+ SetDirection( DIR_LEFT2RIGHT );
+}
+
+// In ruby portion there are different alignments for
+// the ruby text and the main text.
+// Left, right, centered and two possibilities of block adjustment
+// The block adjustment is realized by spacing between the characters,
+// either with a half space or no space in front of the first letter and
+// a half space at the end of the last letter.
+// Notice: the smaller line will be manipulated, normally it's the ruby line,
+// but it could be the main text, too.
+// If there is a tabulator in smaller line, no adjustment is possible.
+void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf )
+{
+ SwTwips nLineDiff = GetRoot().Width() - GetRoot().GetNext()->Width();
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ if( !nLineDiff )
+ return;
+ SwLineLayout *pCurr;
+ if( nLineDiff < 0 )
+ { // The first line has to be adjusted.
+ if( GetTab1() )
+ return;
+ pCurr = &GetRoot();
+ nLineDiff = -nLineDiff;
+ }
+ else
+ { // The second line has to be adjusted.
+ if( GetTab2() )
+ return;
+ pCurr = GetRoot().GetNext();
+ rInf.SetIdx( nOldIdx + GetRoot().GetLen() );
+ }
+ sal_uInt16 nLeft = 0; // the space in front of the first letter
+ sal_uInt16 nRight = 0; // the space at the end of the last letter
+ TextFrameIndex nSub(0);
+ switch ( m_nAdjustment )
+ {
+ case css::text::RubyAdjust_CENTER: nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
+ [[fallthrough]];
+ case css::text::RubyAdjust_RIGHT: nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight); break;
+ case css::text::RubyAdjust_BLOCK: nSub = TextFrameIndex(1);
+ [[fallthrough]];
+ case css::text::RubyAdjust_INDENT_BLOCK:
+ {
+ TextFrameIndex nCharCnt(0);
+ SwLinePortion *pPor;
+ for( pPor = pCurr->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion() )
+ {
+ if( pPor->InTextGrp() )
+ static_cast<SwTextPortion*>(pPor)->GetSpaceCnt( rInf, nCharCnt );
+ rInf.SetIdx( rInf.GetIdx() + pPor->GetLen() );
+ }
+ if( nCharCnt > nSub )
+ {
+ SwTwips nCalc = nLineDiff / sal_Int32(nCharCnt - nSub);
+ short nTmp;
+ if( nCalc < SHRT_MAX )
+ nTmp = -short(nCalc);
+ else
+ nTmp = SHRT_MIN;
+
+ pCurr->CreateSpaceAdd( SPACING_PRECISION_FACTOR * nTmp );
+ nLineDiff -= nCalc * (sal_Int32(nCharCnt) - 1);
+ }
+ if( nLineDiff > 1 )
+ {
+ nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2);
+ nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight);
+ }
+ break;
+ }
+ default: OSL_FAIL( "New ruby adjustment" );
+ }
+ if( nLeft || nRight )
+ {
+ if( !pCurr->GetNextPortion() )
+ pCurr->SetNextPortion(SwTextPortion::CopyLinePortion(*pCurr));
+ if( nLeft )
+ {
+ SwMarginPortion *pMarg = new SwMarginPortion;
+ pMarg->AddPrtWidth( nLeft );
+ pMarg->SetNextPortion( pCurr->GetNextPortion() );
+ pCurr->SetNextPortion( pMarg );
+ }
+ if( nRight )
+ {
+ SwMarginPortion *pMarg = new SwMarginPortion;
+ pMarg->AddPrtWidth( nRight );
+ pCurr->FindLastPortion()->Append( pMarg );
+ }
+ }
+
+ pCurr->Width( Width() );
+ rInf.SetIdx( nOldIdx );
+}
+
+// has to change the nRubyOffset, if there's a fieldportion
+// in the phonetic line.
+// The nRubyOffset is the position in the rubystring, where the
+// next SwRubyPortion has start the displaying of the phonetics.
+void SwRubyPortion::CalcRubyOffset()
+{
+ const SwLineLayout *pCurr = &GetRoot();
+ if( !OnTop() )
+ {
+ pCurr = pCurr->GetNext();
+ if( !pCurr )
+ return;
+ }
+ const SwLinePortion *pPor = pCurr->GetFirstPortion();
+ const SwFieldPortion *pField = nullptr;
+ while( pPor )
+ {
+ if( pPor->InFieldGrp() )
+ pField = static_cast<const SwFieldPortion*>(pPor);
+ pPor = pPor->GetNextPortion();
+ }
+ if( pField )
+ {
+ if( pField->HasFollow() )
+ m_nRubyOffset = pField->GetNextOffset();
+ else
+ m_nRubyOffset = TextFrameIndex(COMPLETE_STRING);
+ }
+}
+
+// A little helper function for GetMultiCreator(..)
+// It extracts the 2-line-format from a 2-line-attribute or a character style.
+// The rValue is set to true, if the 2-line-attribute's value is set and
+// no 2-line-format reference is passed. If there is a 2-line-format reference,
+// then the rValue is set only, if the 2-line-attribute's value is set _and_
+// the 2-line-formats has the same brackets.
+static bool lcl_Check2Lines(const SfxPoolItem *const pItem,
+ const SvxTwoLinesItem* &rpRef, bool &rValue)
+{
+ if( pItem )
+ {
+ rValue = static_cast<const SvxTwoLinesItem*>(pItem)->GetValue();
+ if( !rpRef )
+ rpRef = static_cast<const SvxTwoLinesItem*>(pItem);
+ else if( static_cast<const SvxTwoLinesItem*>(pItem)->GetEndBracket() !=
+ rpRef->GetEndBracket() ||
+ static_cast<const SvxTwoLinesItem*>(pItem)->GetStartBracket() !=
+ rpRef->GetStartBracket() )
+ rValue = false;
+ return true;
+ }
+ return false;
+}
+
+static bool lcl_Has2Lines(const SwTextAttr& rAttr,
+ const SvxTwoLinesItem* &rpRef, bool &rValue)
+{
+ const SfxPoolItem* pItem = CharFormat::GetItem(rAttr, RES_CHRATR_TWO_LINES);
+ return lcl_Check2Lines(pItem, rpRef, rValue);
+}
+
+// is a little help function for GetMultiCreator(..)
+// It extracts the charrotation from a charrotate-attribute or a character style.
+// The rValue is set to true, if the charrotate-attribute's value is set and
+// no charrotate-format reference is passed.
+// If there is a charrotate-format reference, then the rValue is set only,
+// if the charrotate-attribute's value is set _and_ identical
+// to the charrotate-format's value.
+static bool lcl_CheckRotation(const SfxPoolItem *const pItem,
+ const SvxCharRotateItem* &rpRef, bool &rValue)
+{
+ if ( pItem )
+ {
+ rValue = static_cast<const SvxCharRotateItem*>(pItem)->GetValue() != 0_deg10;
+ if( !rpRef )
+ rpRef = static_cast<const SvxCharRotateItem*>(pItem);
+ else if( static_cast<const SvxCharRotateItem*>(pItem)->GetValue() !=
+ rpRef->GetValue() )
+ rValue = false;
+ return true;
+ }
+
+ return false;
+}
+
+static bool lcl_HasRotation(const SwTextAttr& rAttr,
+ const SvxCharRotateItem* &rpRef, bool &rValue)
+{
+ const SfxPoolItem* pItem = CharFormat::GetItem( rAttr, RES_CHRATR_ROTATE );
+ return lcl_CheckRotation(pItem, rpRef, rValue);
+}
+
+namespace sw {
+ namespace {
+
+ // need to use a very special attribute iterator here that returns
+ // both the hints and the nodes, so that GetMultiCreator() can handle
+ // items in the nodes' set properly
+ class MergedAttrIterMulti
+ : public MergedAttrIterBase
+ {
+ private:
+ bool m_First = true;
+ public:
+ MergedAttrIterMulti(SwTextFrame const& rFrame) : MergedAttrIterBase(rFrame) {}
+ SwTextAttr const* NextAttr(SwTextNode const*& rpNode);
+ // can't have operator= because m_pMerged/m_pNode const
+ void Assign(MergedAttrIterMulti const& rOther)
+ {
+ assert(m_pMerged == rOther.m_pMerged);
+ assert(m_pNode == rOther.m_pNode);
+ m_CurrentExtent = rOther.m_CurrentExtent;
+ m_CurrentHint = rOther.m_CurrentHint;
+ m_First = rOther.m_First;
+ }
+ };
+
+ }
+
+ SwTextAttr const* MergedAttrIterMulti::NextAttr(SwTextNode const*& rpNode)
+ {
+ if (m_First)
+ {
+ m_First = false;
+ rpNode = m_pMerged
+ ? !m_pMerged->extents.empty()
+ ? m_pMerged->extents[0].pNode
+ : m_pMerged->pFirstNode
+ : m_pNode;
+ return nullptr;
+ }
+ if (m_pMerged)
+ {
+ const auto nExtentsSize = m_pMerged->extents.size();
+ while (m_CurrentExtent < nExtentsSize)
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ auto nHintsCount = pHints->Count();
+ while (m_CurrentHint < nHintsCount)
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ if (rExtent.nEnd < pHint->GetStart())
+ {
+ break;
+ }
+ ++m_CurrentHint;
+ if (rExtent.nStart <= pHint->GetStart())
+ {
+ rpNode = rExtent.pNode;
+ return pHint;
+ }
+ }
+ }
+ ++m_CurrentExtent;
+ if (m_CurrentExtent < nExtentsSize &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
+ {
+ m_CurrentHint = 0; // reset
+ rpNode = m_pMerged->extents[m_CurrentExtent].pNode;
+ return nullptr;
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ ++m_CurrentHint;
+ rpNode = m_pNode;
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ }
+}
+
+// If we (e.g. the position rPos) are inside a two-line-attribute or
+// a ruby-attribute, the attribute will be returned in a SwMultiCreator-struct,
+// otherwise the function returns zero.
+// The rPos parameter is set to the end of the multiportion,
+// normally this is the end of the attribute,
+// but sometimes it is the start of another attribute, which finished or
+// interrupts the first attribute.
+// E.g. a ruby portion interrupts a 2-line-attribute, a 2-line-attribute
+// with different brackets interrupts another 2-line-attribute.
+std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos,
+ SwMultiPortion const * pMulti ) const
+{
+ SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
+
+ // get the last embedding level
+ sal_uInt8 nCurrLevel;
+ if ( pMulti )
+ {
+ OSL_ENSURE( pMulti->IsBidi(), "Nested MultiPortion is not BidiPortion" );
+ // level associated with bidi-portion;
+ nCurrLevel = static_cast<SwBidiPortion const *>(pMulti)->GetLevel();
+ }
+ else
+ // no nested bidi portion required
+ nCurrLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+
+ // check if there is a field at rPos:
+ sal_uInt8 nNextLevel = nCurrLevel;
+ bool bFieldBidi = false;
+
+ if (rPos < TextFrameIndex(GetText().getLength()) && CH_TXTATR_BREAKWORD == GetChar(rPos))
+ {
+ bFieldBidi = true;
+ }
+ else
+ nNextLevel = rSI.DirType( rPos );
+
+ if (TextFrameIndex(GetText().getLength()) != rPos && nNextLevel > nCurrLevel)
+ {
+ rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel);
+ if (TextFrameIndex(COMPLETE_STRING) == rPos)
+ return {};
+ SwMultiCreator aRet;
+ aRet.pItem = nullptr;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aRet.nId = SwMultiCreatorId::Bidi;
+ aRet.nLevel = nCurrLevel + 1;
+ return aRet;
+ }
+
+ // a bidi portion can only contain other bidi portions
+ if ( pMulti )
+ return {};
+
+ // need the node that contains input rPos
+ std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos));
+ const SvxCharRotateItem* pActiveRotateItem(nullptr);
+ const SvxCharRotateItem* pNodeRotateItem(nullptr);
+ const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr);
+ const SvxTwoLinesItem* pNodeTwoLinesItem(nullptr);
+ SwTextAttr const* pActiveTwoLinesHint(nullptr);
+ SwTextAttr const* pActiveRotateHint(nullptr);
+ const SwTextAttr *pRuby = nullptr;
+ sw::MergedAttrIterMulti iterAtStartOfNode(*m_pFrame);
+ bool bTwo = false;
+ bool bRot = false;
+
+ for (sw::MergedAttrIterMulti iter = *m_pFrame; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pAttr = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ if (pAttr)
+ {
+ assert(pNode->GetIndex() <= startPos.first->GetIndex()); // should break earlier
+ if (startPos.first->GetIndex() <= pNode->GetIndex())
+ {
+ if (startPos.first->GetIndex() != pNode->GetIndex()
+ || startPos.second < pAttr->GetStart())
+ {
+ break;
+ }
+ if (startPos.second < pAttr->GetAnyEnd())
+ {
+ // sw_redlinehide: ruby *always* splits
+ if (RES_TXTATR_CJK_RUBY == pAttr->Which())
+ pRuby = pAttr;
+ else
+ {
+ const SvxCharRotateItem* pRoTmp = nullptr;
+ if (lcl_HasRotation( *pAttr, pRoTmp, bRot ))
+ {
+ pActiveRotateHint = bRot ? pAttr : nullptr;
+ pActiveRotateItem = pRoTmp;
+ }
+ const SvxTwoLinesItem* p2Tmp = nullptr;
+ if (lcl_Has2Lines( *pAttr, p2Tmp, bTwo ))
+ {
+ pActiveTwoLinesHint = bTwo ? pAttr : nullptr;
+ pActiveTwoLinesItem = p2Tmp;
+ }
+ }
+ }
+ }
+ }
+ else if (pNode) // !pAttr && pNode means the node changed
+ {
+ if (startPos.first->GetIndex() < pNode->GetIndex())
+ {
+ break; // only one node initially
+ }
+ if (startPos.first->GetIndex() == pNode->GetIndex())
+ {
+ iterAtStartOfNode.Assign(iter);
+ if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState(
+ RES_CHRATR_ROTATE, true, &pNodeRotateItem) &&
+ pNodeRotateItem->GetValue())
+ {
+ pActiveRotateItem = pNodeRotateItem;
+ }
+ else
+ {
+ pNodeRotateItem = nullptr;
+ }
+ if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState(
+ RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) &&
+ pNodeTwoLinesItem->GetValue())
+ {
+ pActiveTwoLinesItem = pNodeTwoLinesItem;
+ }
+ else
+ {
+ pNodeTwoLinesItem = nullptr;
+ }
+ }
+ }
+ }
+ if (!pRuby && !pActiveTwoLinesItem && !pActiveRotateItem)
+ return {};
+
+ if( pRuby )
+ { // The winner is ... a ruby attribute and so
+ // the end of the multiportion is the end of the ruby attribute.
+ rPos = m_pFrame->MapModelToView(startPos.first, *pRuby->End());
+ SwMultiCreator aRet;
+ aRet.pItem = nullptr;
+ aRet.pAttr = pRuby;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ aRet.nId = SwMultiCreatorId::Ruby;
+ aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+ return aRet;
+ }
+ if (pActiveTwoLinesHint ||
+ (pNodeTwoLinesItem && SfxPoolItem::areSame(pNodeTwoLinesItem, pActiveTwoLinesItem) &&
+ rPos < TextFrameIndex(GetText().getLength())))
+ { // The winner is a 2-line-attribute,
+ // the end of the multiportion depends on the following attributes...
+ SwMultiCreator aRet;
+
+ // We note the endpositions of the 2-line attributes in aEnd as stack
+ std::deque<TextFrameIndex> aEnd;
+
+ // The bOn flag signs the state of the last 2-line attribute in the
+ // aEnd-stack, it is compatible with the winner-attribute or
+ // it interrupts the other attribute.
+ bool bOn = true;
+
+ if (pActiveTwoLinesHint)
+ {
+ aRet.pItem = nullptr;
+ aRet.pAttr = pActiveTwoLinesHint;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ if (pNodeTwoLinesItem)
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ bOn = pNodeTwoLinesItem->GetEndBracket() ==
+ pActiveTwoLinesItem->GetEndBracket() &&
+ pNodeTwoLinesItem->GetStartBracket() ==
+ pActiveTwoLinesItem->GetStartBracket();
+ }
+ else
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
+ }
+ }
+ else
+ {
+ aRet.pItem = pNodeTwoLinesItem;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ }
+ aRet.nId = SwMultiCreatorId::Double;
+ aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0;
+
+ // pActiveTwoLinesHint is the last 2-line-attribute, which contains
+ // the actual position.
+
+ // At this moment we know that at position rPos the "winner"-attribute
+ // causes a 2-line-portion. The end of the attribute is the end of the
+ // portion, if there's no interrupting attribute.
+ // There are two kinds of interrupters:
+ // - ruby attributes stops the 2-line-attribute, the end of the
+ // multiline is the start of the ruby attribute
+ // - 2-line-attributes with value "Off" or with different brackets,
+ // these attributes may interrupt the winner, but they could be
+ // neutralized by another 2-line-attribute starting at the same
+ // position with the same brackets as the winner-attribute.
+
+ // In the following loop rPos is the critical position and it will be
+ // evaluated, if at rPos starts an interrupting or a maintaining
+ // continuity attribute.
+
+ // iterAtStartOfNode is positioned to the first hint of the node
+ // (if any); the node item itself has already been handled above
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= rPos)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_TWO_LINES);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(rPos <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (rPos < nTmpStart)
+ {
+ // If bOn is false and the next attribute starts later than rPos
+ // the winner attribute is interrupted at rPos.
+ // If the start of the next attribute is behind the end of
+ // the last attribute on the aEnd-stack, this is the endposition
+ // on the stack is the end of the 2-line portion.
+ if (!bOn || aEnd.back() < nTmpStart)
+ break;
+ // At this moment, bOn is true and the next attribute starts
+ // behind rPos, so we could move rPos to the next startpoint
+ rPos = nTmpStart;
+ // We clean up the aEnd-stack, endpositions equal to rPos are
+ // superfluous.
+ while( !aEnd.empty() && aEnd.back() <= rPos )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ // If the endstack is empty, we simulate an attribute with
+ // state true and endposition rPos
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( rPos );
+ bOn = true;
+ }
+ }
+ // A ruby attribute stops the 2-line immediately
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ return aRet;
+ if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo)
+ : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo))
+ { // We have an interesting attribute...
+ if( bTwo == bOn )
+ { // .. with the same state, so the last attribute could
+ // be continued.
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ { // .. with a different state.
+ bOn = bTwo;
+ // If this is smaller than the last on the stack, we put
+ // it on the stack. If it has the same endposition, the last
+ // could be removed.
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( bOn && !aEnd.empty() )
+ rPos = aEnd.back();
+ return aRet;
+ }
+ if (pActiveRotateHint ||
+ (pNodeRotateItem && SfxPoolItem::areSame(pNodeRotateItem, pActiveRotateItem) &&
+ rPos < TextFrameIndex(GetText().getLength())))
+ { // The winner is a rotate-attribute,
+ // the end of the multiportion depends on the following attributes...
+ SwMultiCreator aRet;
+ aRet.nId = SwMultiCreatorId::Rotate;
+
+ // We note the endpositions of the 2-line attributes in aEnd as stack
+ std::deque<TextFrameIndex> aEnd;
+
+ // The bOn flag signs the state of the last 2-line attribute in the
+ // aEnd-stack, which could interrupts the winning rotation attribute.
+ bool bOn = pNodeTwoLinesItem != nullptr;
+ aEnd.push_front(TextFrameIndex(GetText().getLength()));
+
+ // first, search for the start position of the next TWOLINE portion
+ // because the ROTATE portion must end there at the latest
+ TextFrameIndex n2Start = rPos;
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= n2Start)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeTwoLinesItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_TWO_LINES);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(n2Start <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (n2Start < nTmpStart)
+ {
+ if (bOn || aEnd.back() < nTmpStart)
+ break;
+ n2Start = nTmpStart;
+ while( !aEnd.empty() && aEnd.back() <= n2Start )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( n2Start );
+ bOn = false;
+ }
+ }
+ // A ruby attribute stops immediately
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ {
+ bOn = true;
+ break;
+ }
+ const SvxTwoLinesItem* p2Lines = nullptr;
+ if (pTmp ? lcl_Has2Lines(*pTmp, p2Lines, bTwo)
+ : lcl_Check2Lines(pNodeTwoLinesItem, p2Lines, bTwo))
+ {
+ if( bTwo == bOn )
+ {
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ {
+ bOn = bTwo;
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( !bOn && !aEnd.empty() )
+ n2Start = aEnd.back();
+
+ aEnd.clear();
+
+ // now, search for the end of the ROTATE portion, similar to above
+ bOn = true;
+ if (pActiveRotateHint)
+ {
+ aRet.pItem = nullptr;
+ aRet.pAttr = pActiveRotateHint;
+ aRet.nStartOfAttr = m_pFrame->MapModelToView(startPos.first, aRet.pAttr->GetStart());
+ if (pNodeRotateItem)
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ bOn = pNodeRotateItem->GetValue() ==
+ pActiveRotateItem->GetValue();
+ }
+ else
+ {
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End()));
+ }
+ }
+ else
+ {
+ aRet.pItem = pNodeRotateItem;
+ aRet.pAttr = nullptr;
+ aRet.nStartOfAttr = TextFrameIndex(-1);
+ aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len()));
+ }
+ for (sw::MergedAttrIterMulti iter = iterAtStartOfNode; ; )
+ {
+ SwTextNode const* pNode(nullptr);
+ SwTextAttr const*const pTmp = iter.NextAttr(pNode);
+ if (!pNode)
+ {
+ break;
+ }
+ assert(startPos.first->GetIndex() <= pNode->GetIndex());
+ TextFrameIndex nTmpStart;
+ TextFrameIndex nTmpEnd;
+ if (pTmp)
+ {
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pTmp->GetAnyEnd());
+ if (nTmpEnd <= rPos)
+ continue;
+ nTmpStart = m_pFrame->MapModelToView(pNode, pTmp->GetStart());
+ }
+ else
+ {
+ pNodeRotateItem = pNode->GetSwAttrSet().GetItemIfSet(
+ RES_CHRATR_ROTATE);
+ nTmpStart = m_pFrame->MapModelToView(pNode, 0);
+ nTmpEnd = m_pFrame->MapModelToView(pNode, pNode->Len());
+ assert(rPos <= nTmpEnd); // next node must not have smaller index
+ }
+
+ if (rPos < nTmpStart)
+ {
+ if (!bOn || aEnd.back() < nTmpStart)
+ break;
+ rPos = nTmpStart;
+ while( !aEnd.empty() && aEnd.back() <= rPos )
+ {
+ bOn = !bOn;
+ aEnd.pop_back();
+ }
+ if( aEnd.empty() )
+ {
+ aEnd.push_front( rPos );
+ bOn = true;
+ }
+ }
+ if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which())
+ {
+ bOn = false;
+ break;
+ }
+ // TODO why does this use bTwo, not bRot ???
+ if (pTmp ? lcl_HasRotation(*pTmp, pActiveRotateItem, bTwo)
+ : lcl_CheckRotation(pNodeRotateItem, pActiveRotateItem, bTwo))
+ {
+ if( bTwo == bOn )
+ {
+ if (aEnd.back() < nTmpEnd)
+ aEnd.back() = nTmpEnd;
+ }
+ else
+ {
+ bOn = bTwo;
+ if (nTmpEnd < aEnd.back())
+ aEnd.push_back( nTmpEnd );
+ else if( aEnd.size() > 1 )
+ aEnd.pop_back();
+ else
+ aEnd.back() = nTmpEnd;
+ }
+ }
+ }
+ if( bOn && !aEnd.empty() )
+ rPos = aEnd.back();
+ if( rPos > n2Start )
+ rPos = n2Start;
+ return aRet;
+ }
+ return {};
+}
+
+namespace {
+
+// A little helper class to manage the spaceadd-arrays of the text adjustment
+// during a PaintMultiPortion.
+// The constructor prepares the array for the first line of multiportion,
+// the SecondLine-function restores the values for the first line and prepares
+// the second line.
+// The destructor restores the values of the last manipulation.
+class SwSpaceManipulator
+{
+ SwTextPaintInfo& m_rInfo;
+ SwMultiPortion& m_rMulti;
+ std::vector<tools::Long>* m_pOldSpaceAdd;
+ sal_uInt16 m_nOldSpaceIndex;
+ tools::Long m_nSpaceAdd;
+ bool m_bSpaceChg;
+ sal_uInt8 m_nOldDir;
+
+public:
+ SwSpaceManipulator( SwTextPaintInfo& rInf, SwMultiPortion& rMult );
+ ~SwSpaceManipulator();
+ void SecondLine();
+ tools::Long GetSpaceAdd() const { return m_nSpaceAdd; }
+};
+
+}
+
+SwSpaceManipulator::SwSpaceManipulator(SwTextPaintInfo& rInf, SwMultiPortion& rMult)
+ : m_rInfo(rInf)
+ , m_rMulti(rMult)
+ , m_nSpaceAdd(0)
+{
+ m_pOldSpaceAdd = m_rInfo.GetpSpaceAdd();
+ m_nOldSpaceIndex = m_rInfo.GetSpaceIdx();
+ m_nOldDir = m_rInfo.GetDirection();
+ m_rInfo.SetDirection(m_rMulti.GetDirection());
+ m_bSpaceChg = false;
+
+ if (m_rMulti.IsDouble())
+ {
+ m_nSpaceAdd = (m_pOldSpaceAdd && !m_rMulti.HasTabulator()) ? m_rInfo.GetSpaceAdd() : 0;
+ if (m_rMulti.GetRoot().IsSpaceAdd())
+ {
+ m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ m_bSpaceChg = m_rMulti.ChgSpaceAdd(&m_rMulti.GetRoot(), m_nSpaceAdd);
+ }
+ else if (m_rMulti.HasTabulator())
+ m_rInfo.SetpSpaceAdd(nullptr);
+ }
+ else if (!m_rMulti.IsBidi())
+ {
+ m_rInfo.SetpSpaceAdd(m_rMulti.GetRoot().GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ }
+}
+
+void SwSpaceManipulator::SecondLine()
+{
+ if (m_bSpaceChg)
+ {
+ m_rInfo.RemoveFirstSpaceAdd();
+ m_bSpaceChg = false;
+ }
+ SwLineLayout* pLay = m_rMulti.GetRoot().GetNext();
+ if( pLay->IsSpaceAdd() )
+ {
+ m_rInfo.SetpSpaceAdd(pLay->GetpLLSpaceAdd());
+ m_rInfo.ResetSpaceIdx();
+ m_bSpaceChg = m_rMulti.ChgSpaceAdd(pLay, m_nSpaceAdd);
+ }
+ else
+ {
+ m_rInfo.SetpSpaceAdd((!m_rMulti.IsDouble() || m_rMulti.HasTabulator()) ? nullptr
+ : m_pOldSpaceAdd);
+ m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
+ }
+}
+
+SwSpaceManipulator::~SwSpaceManipulator()
+{
+ if (m_bSpaceChg)
+ {
+ m_rInfo.RemoveFirstSpaceAdd();
+ m_bSpaceChg = false;
+ }
+ m_rInfo.SetpSpaceAdd(m_pOldSpaceAdd);
+ m_rInfo.SetSpaceIdx(m_nOldSpaceIndex);
+ m_rInfo.SetDirection(m_nOldDir);
+}
+
+// Manages the paint for a SwMultiPortion.
+// External, for the calling function, it seems to be a normal Paint-function,
+// internal it is like a SwTextFrame::PaintSwFrame with multiple DrawTextLines
+void SwTextPainter::PaintMultiPortion( const SwRect &rPaint,
+ SwMultiPortion& rMulti, const SwMultiPortion* pEnvPor )
+{
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ const bool bHasGrid = pGrid && GetInfo().SnapToGrid();
+ sal_uInt16 nRubyHeight = 0;
+ bool bRubyTop = true;
+
+ if ( bHasGrid && pGrid->IsSquaredMode() )
+ {
+ nRubyHeight = pGrid->GetRubyHeight();
+ bRubyTop = ! pGrid->GetRubyTextBelow();
+ }
+
+ // do not allow grid mode for first line in ruby portion
+ const bool bRubyInGrid = bHasGrid && rMulti.IsRuby();
+
+ const sal_uInt16 nOldHeight = rMulti.Height();
+ const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
+
+ if ( bRubyInGrid )
+ {
+ GetInfo().SetSnapToGrid( ! bRubyTop );
+ if (pGrid->IsSquaredMode())
+ rMulti.Height( m_pCurr->Height() );
+ }
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ bool bEnvDir = false;
+ bool bThisDir = false;
+ bool bFrameDir = false;
+ if ( rMulti.IsBidi() )
+ {
+ // these values are needed for the calculation of the x coordinate
+ // and the layout mode
+ OSL_ENSURE( ! pEnvPor || pEnvPor->IsBidi(),
+ "Oh no, I expected a BidiPortion" );
+ bFrameDir = GetInfo().GetTextFrame()->IsRightToLeft();
+ bEnvDir = pEnvPor ? ((static_cast<const SwBidiPortion*>(pEnvPor)->GetLevel() % 2) != 0) : bFrameDir;
+ bThisDir = (static_cast<SwBidiPortion&>(rMulti).GetLevel() % 2) != 0;
+ }
+
+#if OSL_DEBUG_LEVEL > 1
+ // only paint first level bidi portions
+ if( rMulti.Width() > 1 && ! pEnvPor )
+ GetInfo().DrawViewOpt( rMulti, PortionType::Field );
+#endif
+
+ if ( bRubyInGrid && pGrid->IsSquaredMode() )
+ rMulti.Height( nOldHeight );
+
+ // do we have to repaint a post it portion?
+ if( GetInfo().OnWin() && rMulti.GetNextPortion() &&
+ ! rMulti.GetNextPortion()->Width() )
+ rMulti.GetNextPortion()->PrePaint( GetInfo(), &rMulti );
+
+ // old values must be saved and restored at the end
+ TextFrameIndex const nOldLen = GetInfo().GetLen();
+ const SwTwips nOldX = GetInfo().X();
+ const SwTwips nOldY = GetInfo().Y();
+ TextFrameIndex const nOldIdx = GetInfo().GetIdx();
+
+ SwSpaceManipulator aManip( GetInfo(), rMulti );
+
+ std::optional<SwFontSave> oFontSave;
+ std::unique_ptr<SwFont> pTmpFnt;
+
+ if( rMulti.IsDouble() )
+ {
+ pTmpFnt.reset(new SwFont( *GetInfo().GetFont() ));
+ if( rMulti.IsDouble() )
+ {
+ SetPropFont( 50 );
+ pTmpFnt->SetProportion( GetPropFont() );
+ }
+ oFontSave.emplace( GetInfo(), pTmpFnt.get(), this );
+ }
+ else
+ {
+ pTmpFnt = nullptr;
+ }
+
+ if( rMulti.HasBrackets() )
+ {
+ // WP is mandatory
+ Por_Info const por(rMulti, *this, 1);
+ SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
+
+ TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
+ SeekAndChg( GetInfo() );
+ static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(), 0, true );
+ GetInfo().SetIdx( nTmpOldIdx );
+ }
+
+ const SwTwips nTmpX = GetInfo().X();
+
+ SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion
+ SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line
+ SwTwips nOfst = 0;
+
+ // GetInfo().Y() is the baseline from the surrounding line. We must switch
+ // this temporary to the baseline of the inner lines of the multiportion.
+ if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ {
+ GetInfo().Y( nOldY - rMulti.GetAscent() );
+ nOfst = nTmpX + rMulti.Width();
+ }
+ else
+ {
+ GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
+ nOfst = nTmpX;
+ }
+ }
+ else if ( rMulti.IsBidi() )
+ {
+ // does the current bidi portion has the same direction
+ // as its environment?
+ if ( bEnvDir != bThisDir )
+ {
+ // different directions, we have to adjust the x coordinate
+ SwTwips nMultiWidth = rMulti.Width() +
+ rMulti.CalcSpacing( GetInfo().GetSpaceAdd(), GetInfo() );
+
+ if ( bFrameDir == bThisDir )
+ GetInfo().X( GetInfo().X() - nMultiWidth );
+ else
+ GetInfo().X( GetInfo().X() + nMultiWidth );
+ }
+
+ nOfst = nOldY - rMulti.GetAscent();
+
+ // set layout mode
+ aLayoutModeModifier.Modify( bThisDir );
+ }
+ else
+ nOfst = nOldY - rMulti.GetAscent();
+
+ bool bRest = pLay->IsRest();
+ bool bFirst = true;
+
+ OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(),
+ " Only BiDi portions are allowed to use the common underlining font" );
+
+ ::std::optional<SwTaggedPDFHelper> oTag;
+ if (rMulti.IsDouble())
+ {
+ Por_Info const por(rMulti, *this, 2);
+ oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
+ }
+ else if (rMulti.IsRuby())
+ {
+ Por_Info const por(rMulti, *this, bRubyTop ? 1 : 2);
+ oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
+ GetInfo().SetRuby( rMulti.OnTop() );
+ }
+
+ do
+ {
+ if ( bHasGrid && pGrid->IsSquaredMode() )
+ {
+ if( rMulti.HasRotation() )
+ {
+ const sal_uInt16 nAdjustment = ( pLay->Height() - pPor->Height() ) / 2 +
+ pPor->GetAscent();
+ if( rMulti.IsRevers() )
+ GetInfo().X( nOfst - nAdjustment );
+ else
+ GetInfo().X( nOfst + nAdjustment );
+ }
+ else
+ {
+ // special treatment for ruby portions in grid mode
+ SwTwips nAdjustment = 0;
+ if ( rMulti.IsRuby() )
+ {
+ if ( bRubyTop != ( pLay == &rMulti.GetRoot() ) )
+ // adjust base text
+ nAdjustment = ( m_pCurr->Height() - nRubyHeight - pPor->Height() ) / 2;
+ else if ( bRubyTop )
+ // adjust upper ruby text
+ nAdjustment = nRubyHeight - pPor->Height();
+ // else adjust lower ruby text
+ }
+
+ GetInfo().Y( nOfst + nAdjustment + pPor->GetAscent() );
+ }
+ }
+ else if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ GetInfo().X( nOfst - AdjustBaseLine( *pLay, pPor, 0, 0, true ) );
+ else
+ GetInfo().X( nOfst + AdjustBaseLine( *pLay, pPor ) );
+ }
+ else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() )
+ {
+ SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, static_cast<SwTwips>(0) );
+ GetInfo().Y( nOfst + nLineDiff );
+ // Draw the ruby text on top of the preserved space.
+ GetInfo().X( GetInfo().X() - pPor->Height() );
+ }
+ else
+ GetInfo().Y( nOfst + AdjustBaseLine( *pLay, pPor ) );
+
+ bool bSeeked = true;
+ GetInfo().SetLen( pPor->GetLen() );
+
+ if( bRest && pPor->InFieldGrp() && !pPor->GetLen() )
+ {
+ if( static_cast<SwFieldPortion*>(pPor)->HasFont() )
+ bSeeked = false;
+ else
+ SeekAndChgBefore( GetInfo() );
+ }
+ else if( pPor->InTextGrp() || pPor->InFieldGrp() || pPor->InTabGrp() )
+ SeekAndChg( GetInfo() );
+ else if ( !bFirst && pPor->IsBreakPortion() && GetInfo().GetOpt().IsParagraph() )
+ {
+ if( GetRedln() )
+ SeekAndChg( GetInfo() );
+ else
+ SeekAndChgBefore( GetInfo() );
+ }
+ else
+ bSeeked = false;
+
+ SwLinePortion *pNext = pPor->GetNextPortion();
+ if(GetInfo().OnWin() && pNext && !pNext->Width() )
+ {
+ if ( !bSeeked )
+ SeekAndChg( GetInfo() );
+ pNext->PrePaint( GetInfo(), pPor );
+ }
+
+ CheckSpecialUnderline( pPor );
+ SwUnderlineFont* pUnderLineFnt = GetInfo().GetUnderFnt();
+ if ( pUnderLineFnt )
+ {
+ if ( rMulti.IsDouble() )
+ pUnderLineFnt->GetFont().SetProportion( 50 );
+ pUnderLineFnt->SetPos( GetInfo().GetPos() );
+ }
+
+ if ( rMulti.IsBidi() )
+ {
+ // we do not allow any rotation inside a bidi portion
+ SwFont* pTmpFont = GetInfo().GetFont();
+ pTmpFont->SetVertical( 0_deg10, GetInfo().GetTextFrame()->IsVertical() );
+ }
+
+ if( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() )
+ {
+ // but we do allow nested bidi portions
+ OSL_ENSURE( rMulti.IsBidi(), "Only nesting of bidi portions is allowed" );
+ PaintMultiPortion( rPaint, static_cast<SwMultiPortion&>(*pPor), &rMulti );
+ }
+ else
+ {
+ Por_Info const por(*pPor, *this, 0);
+ SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
+
+ pPor->Paint( GetInfo() );
+ }
+
+ bFirst &= !pPor->GetLen();
+ if( pNext || !pPor->IsMarginPortion() )
+ pPor->Move( GetInfo() );
+
+ pPor = pNext;
+
+ // If there's no portion left, we go to the next line
+ if( !pPor && pLay->GetNext() )
+ {
+ pLay = pLay->GetNext();
+ pPor = pLay->GetFirstPortion();
+ bRest = pLay->IsRest();
+ aManip.SecondLine();
+
+ // delete underline font
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+
+ if( rMulti.HasRotation() )
+ {
+ if( rMulti.IsRevers() )
+ {
+ nOfst += pLay->Height();
+ GetInfo().Y( nOldY - rMulti.GetAscent() );
+ }
+ else
+ {
+ nOfst -= pLay->Height();
+ GetInfo().Y( nOldY - rMulti.GetAscent() + rMulti.Height() );
+ }
+ }
+ else if ( bHasGrid && rMulti.IsRuby() )
+ {
+ GetInfo().SetSnapToGrid( bRubyTop );
+ GetInfo().X( nTmpX );
+ if (pGrid->IsSquaredMode() )
+ {
+ if ( bRubyTop )
+ nOfst += nRubyHeight;
+ else
+ nOfst += m_pCurr->Height() - nRubyHeight;
+ }
+ else
+ {
+ nOfst += rMulti.GetRoot().Height();
+ }
+ }
+ else if ( rMulti.IsRuby() && rMulti.OnRight() )
+ {
+ GetInfo().SetDirection( DIR_TOP2BOTTOM );
+ GetInfo().SetRuby( true );
+ } else
+ {
+ GetInfo().X( nTmpX );
+ // We switch to the baseline of the next inner line
+ nOfst += rMulti.GetRoot().Height();
+ }
+ if (rMulti.IsRuby())
+ {
+ oTag.reset();
+ Por_Info const por(rMulti, *this, bRubyTop ? 2 : 1);
+ oTag.emplace(nullptr, nullptr, &por, *GetInfo().GetOut());
+ }
+ }
+ } while( pPor );
+
+ if (rMulti.IsDouble())
+ {
+ oTag.reset();
+ }
+
+ if ( bRubyInGrid )
+ GetInfo().SetSnapToGrid( bOldGridModeAllowed );
+
+ // delete underline font
+ if ( ! rMulti.IsBidi() )
+ {
+ delete GetInfo().GetUnderFnt();
+ GetInfo().SetUnderFnt( nullptr );
+ }
+
+ GetInfo().SetIdx( nOldIdx );
+ GetInfo().Y( nOldY );
+
+ if( rMulti.HasBrackets() )
+ {
+ // WP is mandatory
+ Por_Info const por(rMulti, *this, 1);
+ SwTaggedPDFHelper const tag(nullptr, nullptr, &por, *GetInfo().GetOut());
+
+ TextFrameIndex const nTmpOldIdx = GetInfo().GetIdx();
+ GetInfo().SetIdx(static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart);
+ SeekAndChg( GetInfo() );
+ GetInfo().X( nOldX );
+ static_cast<SwDoubleLinePortion&>(rMulti).PaintBracket( GetInfo(),
+ aManip.GetSpaceAdd(), false );
+ GetInfo().SetIdx( nTmpOldIdx );
+ }
+ // Restore the saved values
+ GetInfo().X( nOldX );
+ GetInfo().SetLen( nOldLen );
+ oFontSave.reset();
+ pTmpFnt.reset();
+ SetPropFont( 0 );
+}
+
+static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField )
+{
+ SwLinePortion* pLast = pLine;
+ rpField = pLine->GetNextPortion();
+ while( rpField && !rpField->InFieldGrp() )
+ {
+ pLast = rpField;
+ rpField = rpField->GetNextPortion();
+ }
+ bool bRet = rpField != nullptr;
+ if( bRet )
+ {
+ if( static_cast<SwFieldPortion*>(rpField)->IsFollow() )
+ {
+ rpField->Truncate();
+ pLast->SetNextPortion( nullptr );
+ }
+ else
+ rpField = nullptr;
+ }
+ pLine->Truncate();
+ return bRet;
+}
+
+// If a multi portion completely has to go to the
+// next line, this function is called to truncate
+// the rest of the remaining multi portion
+static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
+ TextFrameIndex const nStartIdx)
+{
+ rMulti.GetRoot().Truncate();
+ rMulti.GetRoot().SetLen(TextFrameIndex(0));
+ rMulti.GetRoot().Width(0);
+// rMulti.CalcSize( *this, aInf );
+ if ( rMulti.GetRoot().GetNext() )
+ {
+ rMulti.GetRoot().GetNext()->Truncate();
+ rMulti.GetRoot().GetNext()->SetLen(TextFrameIndex(0));
+ rMulti.GetRoot().GetNext()->Width( 0 );
+ }
+ rMulti.Width( 0 );
+ rMulti.SetLen(TextFrameIndex(0));
+ rInf.SetIdx( nStartIdx );
+}
+
+// Manages the formatting of a SwMultiPortion. External, for the calling
+// function, it seems to be a normal Format-function, internal it is like a
+// SwTextFrame::Format_ with multiple BuildPortions
+bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
+ SwMultiPortion& rMulti )
+{
+ SwTwips nMaxWidth = rInf.Width();
+ SwTwips nOldX = 0;
+
+ if( rMulti.HasBrackets() )
+ {
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ rInf.SetIdx( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets()->nStart );
+ SeekAndChg( rInf );
+ nOldX = GetInfo().X();
+ static_cast<SwDoubleLinePortion&>(rMulti).FormatBrackets( rInf, nMaxWidth );
+ rInf.SetIdx( nOldIdx );
+ }
+
+ SeekAndChg( rInf );
+ std::optional<SwFontSave> oFontSave;
+ std::unique_ptr<SwFont> xTmpFont;
+ if( rMulti.IsDouble() )
+ {
+ xTmpFont.reset(new SwFont( *rInf.GetFont() ));
+ if( rMulti.IsDouble() )
+ {
+ SetPropFont( 50 );
+ xTmpFont->SetProportion( GetPropFont() );
+ }
+ oFontSave.emplace(rInf, xTmpFont.get(), this);
+ }
+
+ SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() );
+ if ( rMulti.IsBidi() )
+ {
+ // set layout mode
+ aLayoutModeModifier.Modify( ! rInf.GetTextFrame()->IsRightToLeft() );
+ }
+
+ SwTwips nTmpX = 0;
+
+ if( rMulti.HasRotation() )
+ {
+ // For nMaxWidth we take the height of the body frame.
+ // #i25067#: If the current frame is inside a table, we restrict
+ // nMaxWidth to the current frame height, unless the frame size
+ // attribute is set to variable size:
+
+ // We set nTmpX (which is used for portion calculating) to the
+ // current Y value
+ const SwPageFrame* pPage = m_pFrame->FindPageFrame();
+ OSL_ENSURE( pPage, "No page in frame!");
+ const SwLayoutFrame* pUpperFrame = pPage;
+
+ if ( m_pFrame->IsInTab() )
+ {
+ pUpperFrame = m_pFrame->GetUpper();
+ while ( pUpperFrame && !pUpperFrame->IsCellFrame() )
+ pUpperFrame = pUpperFrame->GetUpper();
+ assert(pUpperFrame); //pFrame is in table but does not have an upper cell frame
+ if (!pUpperFrame)
+ return false;
+ const SwTableLine* pLine = static_cast<const SwRowFrame*>(pUpperFrame->GetUpper())->GetTabLine();
+ const SwFormatFrameSize& rFrameFormatSize = pLine->GetFrameFormat()->GetFrameSize();
+ if ( SwFrameSize::Variable == rFrameFormatSize.GetHeightSizeType() )
+ pUpperFrame = pPage;
+ }
+ if ( pUpperFrame == pPage && !m_pFrame->IsInFootnote() )
+ pUpperFrame = pPage->FindBodyCont();
+
+ nMaxWidth = pUpperFrame ?
+ ( rInf.GetTextFrame()->IsVertical() ?
+ pUpperFrame->getFramePrintArea().Width() :
+ pUpperFrame->getFramePrintArea().Height() ) :
+ USHRT_MAX;
+ }
+ else
+ nTmpX = rInf.X();
+
+ SwMultiPortion* pOldMulti = m_pMulti;
+
+ m_pMulti = &rMulti;
+ SwLineLayout *pOldCurr = m_pCurr;
+ TextFrameIndex const nOldStart = GetStart();
+ SwTwips nMinWidth = nTmpX + 1;
+ SwTwips nActWidth = nMaxWidth;
+ const TextFrameIndex nStartIdx = rInf.GetIdx();
+ TextFrameIndex nMultiLen = rMulti.GetLen();
+
+ SwLinePortion *pFirstRest;
+ SwLinePortion *pSecondRest;
+ if( rMulti.IsFormatted() )
+ {
+ if( !lcl_ExtractFieldFollow( &rMulti.GetRoot(), pFirstRest )
+ && rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
+ lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pFirstRest );
+ if( !rMulti.IsDouble() && rMulti.GetRoot().GetNext() )
+ lcl_ExtractFieldFollow( rMulti.GetRoot().GetNext(), pSecondRest );
+ else
+ pSecondRest = nullptr;
+ }
+ else
+ {
+ pFirstRest = rMulti.GetRoot().GetNextPortion();
+ pSecondRest = rMulti.GetRoot().GetNext() ?
+ rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
+ if( pFirstRest )
+ rMulti.GetRoot().SetNextPortion( nullptr );
+ if( pSecondRest )
+ rMulti.GetRoot().GetNext()->SetNextPortion( nullptr );
+ rMulti.SetFormatted();
+ nMultiLen = nMultiLen - rInf.GetIdx();
+ }
+
+ // save some values
+ const OUString* pOldText = &(rInf.GetText());
+ const SwTwips nOldPaintOfst = rInf.GetPaintOfst();
+ std::shared_ptr<const vcl::text::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData());
+ rInf.SetCachedVclData(nullptr);
+
+ OUString const aMultiStr( rInf.GetText().copy(0, sal_Int32(nMultiLen + rInf.GetIdx())) );
+ rInf.SetText( aMultiStr );
+ SwTextFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth );
+ // Do we allow break cuts? The FirstMulti-Flag is evaluated during
+ // line break determination.
+ bool bFirstMulti = rInf.GetIdx() != rInf.GetLineStart();
+
+ SwLinePortion *pNextFirst = nullptr;
+ SwLinePortion *pNextSecond = nullptr;
+ bool bRet = false;
+
+ SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
+ const bool bHasGrid = pGrid && GRID_LINES_CHARS == pGrid->GetGridType();
+
+ bool bRubyTop = false;
+
+ if ( bHasGrid )
+ bRubyTop = ! pGrid->GetRubyTextBelow();
+
+ do
+ {
+ m_pCurr = &rMulti.GetRoot();
+ m_nStart = nStartIdx;
+ bRet = false;
+ FormatReset( aInf );
+ aInf.X( nTmpX );
+ aInf.Width( sal_uInt16(nActWidth) );
+ aInf.RealWidth( sal_uInt16(nActWidth) );
+ aInf.SetFirstMulti( bFirstMulti );
+ aInf.SetNumDone( rInf.IsNumDone() );
+ aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
+
+ // if there's a bookmark at the start of the MultiPortion, it will be
+ // painted with the rotation etc. of the MultiPortion; move it *inside*
+ // so it gets positioned correctly; currently there's no other portion
+ // inserted between the end of WhichFirstPortion() and
+ // BuildMultiPortion()
+ if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
+ {
+ auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast()));
+ auto *const pPrevious = pBookmark->FindPrevPortion(rInf.GetRoot());
+ assert(!pPrevious || pPrevious->GetNextPortion() == pBookmark);
+ if (pPrevious)
+ {
+ pPrevious->SetNextPortion(nullptr);
+ }
+ rInf.SetLast(pPrevious);
+ assert(m_pCurr->GetNextPortion() == nullptr);
+ m_pCurr->SetNextPortion(pBookmark);
+ }
+
+ if( pFirstRest )
+ {
+ OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
+ SwFieldPortion *pField =
+ static_cast<SwFieldPortion*>(pFirstRest)->Clone(
+ static_cast<SwFieldPortion*>(pFirstRest)->GetExp() );
+ pField->SetFollow( true );
+ aInf.SetRest( pField );
+ }
+ aInf.SetRuby( rMulti.IsRuby() && rMulti.OnTop() );
+
+ // in grid mode we temporarily have to disable the grid for the ruby line
+ const bool bOldGridModeAllowed = GetInfo().SnapToGrid();
+ if ( bHasGrid && aInf.IsRuby() && bRubyTop )
+ aInf.SetSnapToGrid( false );
+
+ // If there's no more rubytext, then buildportion is forbidden
+ if( pFirstRest || !aInf.IsRuby() )
+ BuildPortions( aInf );
+
+ aInf.SetSnapToGrid( bOldGridModeAllowed );
+
+ rMulti.CalcSize( *this, aInf );
+ m_pCurr->SetRealHeight( m_pCurr->Height() );
+
+ if( rMulti.IsBidi() )
+ {
+ pNextFirst = aInf.GetRest();
+ break;
+ }
+
+ if( rMulti.HasRotation() && !rMulti.IsDouble() )
+ break;
+ // second line has to be formatted
+ else if( m_pCurr->GetLen()<nMultiLen || rMulti.IsRuby() || aInf.GetRest())
+ {
+ TextFrameIndex const nFirstLen = m_pCurr->GetLen();
+ delete m_pCurr->GetNext();
+ m_pCurr->SetNext( new SwLineLayout() );
+ m_pCurr = m_pCurr->GetNext();
+ m_nStart = aInf.GetIdx();
+ aInf.X( nTmpX );
+ SwTextFormatInfo aTmp( aInf, *m_pCurr, nActWidth );
+ if( rMulti.IsRuby() )
+ {
+ aTmp.SetRuby( !rMulti.OnTop() );
+ pNextFirst = aInf.GetRest();
+ if( pSecondRest )
+ {
+ OSL_ENSURE( pSecondRest->InFieldGrp(), "Fieldrest expected");
+ SwFieldPortion *pField = static_cast<SwFieldPortion*>(pSecondRest)->Clone(
+ static_cast<SwFieldPortion*>(pSecondRest)->GetExp() );
+ pField->SetFollow( true );
+ aTmp.SetRest( pField );
+ }
+ if( !rMulti.OnTop() && nFirstLen < nMultiLen )
+ bRet = true;
+ }
+ else
+ aTmp.SetRest( aInf.GetRest() );
+ aInf.SetRest( nullptr );
+
+ // in grid mode we temporarily have to disable the grid for the ruby line
+ if ( bHasGrid && aTmp.IsRuby() && ! bRubyTop )
+ aTmp.SetSnapToGrid( false );
+
+ BuildPortions( aTmp );
+
+ const SwLinePortion *pRightPortion = rMulti.OnRight() ?
+ rMulti.GetRoot().GetNext()->GetNextPortion() : nullptr;
+ if (pRightPortion)
+ {
+ // The ruby text on the right is vertical.
+ // The width and the height are swapped.
+ SwTwips nHeight = pRightPortion->Height();
+ // Keep room for the ruby text.
+ rMulti.GetRoot().FindLastPortion()->AddPrtWidth( nHeight );
+ }
+
+ aTmp.SetSnapToGrid( bOldGridModeAllowed );
+
+ rMulti.CalcSize( *this, aInf );
+ rMulti.GetRoot().SetRealHeight( rMulti.GetRoot().Height() );
+ m_pCurr->SetRealHeight( m_pCurr->Height() );
+ if( rMulti.IsRuby() )
+ {
+ pNextSecond = aTmp.GetRest();
+ if( pNextFirst )
+ bRet = true;
+ }
+ else
+ pNextFirst = aTmp.GetRest();
+ if( ( !aTmp.IsRuby() && nFirstLen + m_pCurr->GetLen() < nMultiLen )
+ || aTmp.GetRest() )
+ // our guess for width of multiportion was too small,
+ // text did not fit into multiportion
+ bRet = true;
+ }
+ if( rMulti.IsRuby() )
+ break;
+ if( bRet )
+ {
+ // our guess for multiportion width was too small,
+ // we set min to act
+ nMinWidth = nActWidth;
+ nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
+ if ( nActWidth == nMaxWidth && rInf.GetLineStart() == rInf.GetIdx() )
+ // we have too less space, we must allow break cuts
+ // ( the first multi flag is considered during TextPortion::Format_() )
+ bFirstMulti = false;
+ if( nActWidth <= nMinWidth )
+ break;
+ }
+ else
+ {
+ // For Solaris, this optimization can causes trouble:
+ // Setting this to the portion width ( = rMulti.Width() )
+ // can make GetTextBreak inside SwTextGuess::Guess return too small
+ // values. Therefore we add some extra twips.
+ if( nActWidth > nTmpX + rMulti.Width() + 6 )
+ nActWidth = nTmpX + rMulti.Width() + 6;
+ nMaxWidth = nActWidth;
+ nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4;
+ if( nActWidth >= nMaxWidth )
+ break;
+ // we do not allow break cuts during formatting
+ bFirstMulti = true;
+ }
+ delete pNextFirst;
+ pNextFirst = nullptr;
+ } while ( true );
+
+ m_pMulti = pOldMulti;
+
+ m_pCurr = pOldCurr;
+ m_nStart = nOldStart;
+ SetPropFont( 0 );
+
+ rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ?
+ rMulti.GetRoot().GetNext()->GetLen() : TextFrameIndex(0) ) );
+
+ if( rMulti.IsDouble() )
+ {
+ static_cast<SwDoubleLinePortion&>(rMulti).CalcBlanks( rInf );
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() )
+ {
+ SwLineLayout* pLine = &rMulti.GetRoot();
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetLineDiff() > 0 )
+ {
+ rInf.SetIdx( nStartIdx + pLine->GetLen() );
+ pLine = pLine->GetNext();
+ }
+ if( pLine )
+ {
+ GetInfo().SetMulti( true );
+
+ // If the fourth element bSkipKashida of function CalcNewBlock is true, multiportion will be showed in justification.
+ // Kashida (Persian) is a type of justification used in some cursive scripts, particularly Arabic.
+ // In contrast to white-space justification, which increases the length of a line of text by expanding spaces between words or individual letters,
+ // kashida justification is accomplished by elongating characters at certain chosen points.
+ // Kashida justification can be combined with white-space justification to various extents.
+ // The default value of bSkipKashida (the 4th parameter passed to 'CalcNewBlock') is false.
+ // Only when Adjust is SvxAdjust::Block ( alignment is justify ), multiportion will be showed in justification in new code.
+ CalcNewBlock( pLine, nullptr, rMulti.Width(), GetAdjust() != SvxAdjust::Block );
+
+ GetInfo().SetMulti( false );
+ }
+ rInf.SetIdx( nStartIdx );
+ }
+ if( static_cast<SwDoubleLinePortion&>(rMulti).GetBrackets() )
+ {
+ rMulti.Width( rMulti.Width() +
+ static_cast<SwDoubleLinePortion&>(rMulti).BracketWidth() );
+ GetInfo().X( nOldX );
+ }
+ }
+ else
+ {
+ rMulti.ActualizeTabulator();
+ if( rMulti.IsRuby() )
+ {
+ static_cast<SwRubyPortion&>(rMulti).Adjust( rInf );
+ static_cast<SwRubyPortion&>(rMulti).CalcRubyOffset();
+ }
+ }
+ if( rMulti.HasRotation() )
+ {
+ SwTwips nH = rMulti.Width();
+ SwTwips nAsc = rMulti.GetAscent() + ( nH - rMulti.Height() )/2;
+ if( nAsc > nH )
+ nAsc = nH;
+ else if( nAsc < 0 )
+ nAsc = 0;
+ rMulti.Width( rMulti.Height() );
+ rMulti.Height( sal_uInt16(nH) );
+ rMulti.SetAscent( sal_uInt16(nAsc) );
+ bRet = ( rInf.GetPos().X() + rMulti.Width() > rInf.Width() ) &&
+ nStartIdx != rInf.GetLineStart();
+ }
+ else if ( rMulti.IsBidi() )
+ {
+ bRet = rMulti.GetLen() < nMultiLen || pNextFirst;
+ }
+
+ // line break has to be performed!
+ if( bRet )
+ {
+ OSL_ENSURE( !pNextFirst || pNextFirst->InFieldGrp(),
+ "BuildMultiPortion: Surprising restportion, field expected" );
+ SwMultiPortion *pTmp;
+ if( rMulti.IsDouble() )
+ pTmp = new SwDoubleLinePortion( static_cast<SwDoubleLinePortion&>(rMulti),
+ nMultiLen + rInf.GetIdx() );
+ else if( rMulti.IsRuby() )
+ {
+ OSL_ENSURE( !pNextSecond || pNextSecond->InFieldGrp(),
+ "BuildMultiPortion: Surprising restportion, field expected" );
+
+ if ( rInf.GetIdx() == rInf.GetLineStart() )
+ {
+ // the ruby portion has to be split in two portions
+ pTmp = new SwRubyPortion( static_cast<SwRubyPortion&>(rMulti),
+ nMultiLen + rInf.GetIdx() );
+
+ if( pNextSecond )
+ {
+ pTmp->GetRoot().SetNext( new SwLineLayout() );
+ pTmp->GetRoot().GetNext()->SetNextPortion( pNextSecond );
+ }
+ pTmp->SetFollowField();
+ }
+ else
+ {
+ // we try to keep our ruby portion together
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ pTmp = nullptr;
+ // A follow field portion may still be waiting. If nobody wants
+ // it, we delete it.
+ delete pNextSecond;
+ }
+ }
+ else if( rMulti.HasRotation() )
+ {
+ // we try to keep our rotated portion together
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
+ rMulti.GetDirection() );
+ }
+ // during a recursion of BuildMultiPortions we may not build
+ // a new SwBidiPortion, this would cause a memory leak
+ else if( rMulti.IsBidi() && ! m_pMulti )
+ {
+ if ( ! rMulti.GetLen() )
+ lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+
+ // If there is a HolePortion at the end of the bidi portion,
+ // it has to be moved behind the bidi portion. Otherwise
+ // the visual cursor travelling gets into trouble.
+ SwLineLayout& aRoot = rMulti.GetRoot();
+ SwLinePortion* pPor = aRoot.GetFirstPortion();
+ while ( pPor )
+ {
+ if ( pPor->GetNextPortion() && pPor->GetNextPortion()->IsHolePortion() )
+ {
+ SwLinePortion* pHolePor = pPor->GetNextPortion();
+ pPor->SetNextPortion( nullptr );
+ aRoot.SetLen( aRoot.GetLen() - pHolePor->GetLen() );
+ rMulti.SetLen( rMulti.GetLen() - pHolePor->GetLen() );
+ rMulti.SetNextPortion( pHolePor );
+ break;
+ }
+ pPor = pPor->GetNextPortion();
+ }
+
+ pTmp = new SwBidiPortion( nMultiLen + rInf.GetIdx(),
+ static_cast<SwBidiPortion&>(rMulti).GetLevel() );
+ }
+ else
+ pTmp = nullptr;
+
+ if ( ! rMulti.GetLen() && rInf.GetLast() )
+ {
+ SeekAndChgBefore( rInf );
+ rInf.GetLast()->FormatEOL( rInf );
+ }
+
+ if( pNextFirst && pTmp )
+ {
+ pTmp->SetFollowField();
+ pTmp->GetRoot().SetNextPortion( pNextFirst );
+ }
+ else
+ // A follow field portion is still waiting. If nobody wants it,
+ // we delete it.
+ delete pNextFirst;
+
+ rInf.SetRest( pTmp );
+ }
+
+ rInf.SetCachedVclData(pOldCachedVclData);
+ rInf.SetText( *pOldText );
+ rInf.SetPaintOfst( nOldPaintOfst );
+ rInf.SetStop( aInf.IsStop() );
+ rInf.SetNumDone( true );
+ rInf.SetFootnoteDone( true );
+ SeekAndChg( rInf );
+ delete pFirstRest;
+ delete pSecondRest;
+ oFontSave.reset();
+ return bRet;
+}
+
+static bool IsIncompleteRuby(const SwMultiPortion& rHelpMulti)
+{
+ return rHelpMulti.IsRuby() && static_cast<const SwRubyPortion&>(rHelpMulti).GetRubyOffset() < TextFrameIndex(COMPLETE_STRING);
+}
+
+// When a fieldportion at the end of line breaks and needs a following
+// fieldportion in the next line, then the "restportion" of the formatinfo
+// has to be set. Normally this happens during the formatting of the first
+// part of the fieldportion.
+// But sometimes the formatting starts at the line with the following part,
+// especially when the following part is on the next page.
+// In this case the MakeRestPortion-function has to create the following part.
+// The first parameter is the line that contains possibly a first part
+// of a field. When the function finds such field part, it creates the right
+// restportion. This may be a multiportion, e.g. if the field is surrounded by
+// a doubleline- or ruby-portion.
+// The second parameter is the start index of the line.
+SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine,
+ TextFrameIndex nPosition)
+{
+ if( !nPosition )
+ return nullptr;
+ TextFrameIndex nMultiPos = nPosition - pLine->GetLen();
+ const SwMultiPortion *pTmpMulti = nullptr;
+ const SwMultiPortion *pHelpMulti = nullptr;
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ SwFieldPortion *pField = nullptr;
+ while( pPor )
+ {
+ if( pPor->GetLen() && !pHelpMulti )
+ {
+ nMultiPos = nMultiPos + pPor->GetLen();
+ pTmpMulti = nullptr;
+ }
+ if( pPor->InFieldGrp() )
+ {
+ if( !pHelpMulti )
+ pTmpMulti = nullptr;
+ pField = const_cast<SwFieldPortion*>(static_cast<const SwFieldPortion*>(pPor));
+ }
+ else if( pPor->IsMultiPortion() )
+ {
+ OSL_ENSURE( !pHelpMulti || pHelpMulti->IsBidi(),
+ "Nested multiportions are forbidden." );
+
+ pField = nullptr;
+ pTmpMulti = static_cast<const SwMultiPortion*>(pPor);
+ }
+ pPor = pPor->GetNextPortion();
+ // If the last portion is a multi-portion, we enter it
+ // and look for a field portion inside.
+ // If we are already in a multiportion, we could change to the
+ // next line
+ if( !pPor && pTmpMulti )
+ {
+ if( pHelpMulti )
+ { // We're already inside the multiportion, let's take the second
+ // line, if we are in a double line portion
+ if( !pHelpMulti->IsRuby() )
+ pPor = pHelpMulti->GetRoot().GetNext();
+ pTmpMulti = nullptr;
+ }
+ else
+ { // Now we enter a multiportion, in a ruby portion we take the
+ // main line, not the phonetic line, in a doublelineportion we
+ // starts with the first line.
+ pHelpMulti = pTmpMulti;
+ nMultiPos = nMultiPos - pHelpMulti->GetLen();
+ if( pHelpMulti->IsRuby() && pHelpMulti->OnTop() )
+ pPor = pHelpMulti->GetRoot().GetNext();
+ else
+ pPor = pHelpMulti->GetRoot().GetFirstPortion();
+ }
+ }
+ }
+ if( pField && !pField->HasFollow() )
+ pField = nullptr;
+
+ SwLinePortion *pRest = nullptr;
+ if( pField )
+ {
+ const SwTextAttr *pHint = GetAttr(nPosition - TextFrameIndex(1));
+ if ( pHint
+ && ( pHint->Which() == RES_TXTATR_FIELD
+ || pHint->Which() == RES_TXTATR_ANNOTATION ) )
+ {
+ pRest = NewFieldPortion( GetInfo(), pHint );
+ if( pRest->InFieldGrp() )
+ static_cast<SwFieldPortion*>(pRest)->TakeNextOffset( pField );
+ else
+ {
+ delete pRest;
+ pRest = nullptr;
+ }
+ }
+ }
+ if( !pHelpMulti )
+ return pRest;
+
+ nPosition = nMultiPos + pHelpMulti->GetLen();
+ std::optional<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr );
+
+ if ( !pCreate )
+ {
+ OSL_ENSURE( !pHelpMulti->GetLen(), "Multiportion without attribute?" );
+ if ( nMultiPos )
+ --nMultiPos;
+ pCreate = GetInfo().GetMultiCreator( --nMultiPos, nullptr );
+ }
+
+ if (!pCreate)
+ return pRest;
+
+ if( pRest || nMultiPos > nPosition || IsIncompleteRuby(*pHelpMulti))
+ {
+ SwMultiPortion* pTmp;
+ if( pHelpMulti->IsDouble() )
+ pTmp = new SwDoubleLinePortion( *pCreate, nMultiPos );
+ else if( pHelpMulti->IsBidi() )
+ pTmp = new SwBidiPortion( nMultiPos, pCreate->nLevel );
+ else if (IsIncompleteRuby(*pHelpMulti) && pCreate->pAttr)
+ {
+ TextFrameIndex nRubyOffset = static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset();
+ pTmp = new SwRubyPortion( *pCreate, *GetInfo().GetFont(),
+ m_pFrame->GetDoc().getIDocumentSettingAccess(),
+ nMultiPos, nRubyOffset,
+ GetInfo() );
+ }
+ else if( pHelpMulti->HasRotation() )
+ pTmp = new SwRotatedPortion( nMultiPos, pHelpMulti->GetDirection() );
+ else
+ {
+ return pRest;
+ }
+ pCreate.reset();
+ pTmp->SetFollowField();
+ if( pRest )
+ {
+ SwLineLayout *pLay = &pTmp->GetRoot();
+ if( pTmp->IsRuby() && pTmp->OnTop() )
+ {
+ pLay->SetNext( new SwLineLayout() );
+ pLay = pLay->GetNext();
+ }
+ pLay->SetNextPortion( pRest );
+ }
+ return pTmp;
+ }
+ return pRest;
+}
+
+// SwTextCursorSave notes the start and current line of a SwTextCursor,
+// sets them to the values for GetModelPositionForViewPoint inside a multiportion
+// and restores them in the destructor.
+SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor,
+ SwMultiPortion* pMulti,
+ SwTwips nY,
+ SwTwips& nX,
+ TextFrameIndex const nCurrStart,
+ tools::Long nSpaceAdd )
+ : pTextCursor(pCursor),
+ pCurr(pCursor->m_pCurr),
+ nStart(pCursor->m_nStart)
+{
+ pCursor->m_nStart = nCurrStart;
+ pCursor->m_pCurr = &pMulti->GetRoot();
+ while( pCursor->Y() + pCursor->GetLineHeight() < nY &&
+ pCursor->Next() )
+ ; // nothing
+ nWidth = pCursor->m_pCurr->Width();
+ nOldProp = pCursor->GetPropFont();
+
+ if ( pMulti->IsDouble() || pMulti->IsBidi() )
+ {
+ bSpaceChg = pMulti->ChgSpaceAdd( pCursor->m_pCurr, nSpaceAdd );
+
+ TextFrameIndex nSpaceCnt;
+ if ( pMulti->IsDouble() )
+ {
+ pCursor->SetPropFont( 50 );
+ nSpaceCnt = static_cast<SwDoubleLinePortion*>(pMulti)->GetSpaceCnt();
+ }
+ else
+ {
+ TextFrameIndex const nOldIdx = pCursor->GetInfo().GetIdx();
+ pCursor->GetInfo().SetIdx ( nCurrStart );
+ nSpaceCnt = static_cast<SwBidiPortion*>(pMulti)->GetSpaceCnt(pCursor->GetInfo());
+ pCursor->GetInfo().SetIdx ( nOldIdx );
+ }
+
+ if( nSpaceAdd > 0 && !pMulti->HasTabulator() )
+ pCursor->m_pCurr->Width( o3tl::narrowing<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) );
+
+ // For a BidiPortion we have to calculate the offset from the
+ // end of the portion
+ if ( nX && pMulti->IsBidi() )
+ nX = pCursor->m_pCurr->Width() - nX;
+ }
+ else
+ bSpaceChg = false;
+}
+
+SwTextCursorSave::~SwTextCursorSave()
+{
+ if( bSpaceChg )
+ SwDoubleLinePortion::ResetSpaceAdd( pTextCursor->m_pCurr );
+ pTextCursor->m_pCurr->Width( nWidth );
+ pTextCursor->m_pCurr = pCurr;
+ pTextCursor->m_nStart = nStart;
+ pTextCursor->SetPropFont( nOldProp );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/pormulti.hxx b/sw/source/core/text/pormulti.hxx
new file mode 100644
index 0000000000..e5a3da2b32
--- /dev/null
+++ b/sw/source/core/text/pormulti.hxx
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <memory>
+#include "porlay.hxx"
+#include <com/sun/star/text/RubyAdjust.hpp>
+
+class IDocumentSettingAccess;
+class SwTextFormatInfo;
+class SwTextCursor;
+class SwTextPaintInfo;
+class SwTextAttr;
+class SfxPoolItem;
+class SwFont;
+
+// SwMultiCreator is a small structure to create a multiportion.
+// It contains the kind of multiportion and a textattribute
+// or a poolitem.
+// The GetMultiCreator-function fills this structure and
+// the Ctor of the SwMultiPortion uses it.
+enum class SwMultiCreatorId
+{
+ Double, Ruby, Rotate, Bidi
+};
+
+enum class RubyPosition : sal_uInt16
+{
+ ABOVE = 0,
+ BELOW = 1,
+ RIGHT = 2
+};
+
+struct SwMultiCreator
+{
+ TextFrameIndex nStartOfAttr;
+ const SwTextAttr* pAttr;
+ const SfxPoolItem* pItem;
+ SwMultiCreatorId nId;
+ sal_uInt8 nLevel;
+};
+
+// A two-line-portion (SwMultiPortion) could have surrounding brackets,
+// in this case the structure SwBracket will be used.
+struct SwBracket
+{
+ TextFrameIndex nStart; // Start of text attribute determines the font
+ sal_uInt16 nAscent; // Ascent of the brackets
+ sal_uInt16 nHeight; // Height of them
+ sal_uInt16 nPreWidth; // Width of the opening bracket
+ sal_uInt16 nPostWidth; // Width of the closing bracket
+ sal_Unicode cPre; // Initial character, e.g. '('
+ sal_Unicode cPost; // Final character, e.g. ')'
+ SwFontScript nPreScript; // Script of the initial character
+ SwFontScript nPostScript; // Script of the final character
+};
+
+// The SwMultiPortion is line portion inside a line portion,
+// it's a group of portions,
+// e.g. a double line portion in a line
+// or phonetics (ruby)
+// or combined characters
+// or a rotated portion.
+class SAL_DLLPUBLIC_RTTI SwMultiPortion : public SwLinePortion
+{
+ SwLineLayout m_aRoot; // One or more lines
+ bool m_bTab1 :1; // First line tabulator
+ bool m_bTab2 :1; // Second line includes tabulator
+ bool m_bDouble :1; // Double line
+ bool m_bRuby :1; // Phonetics
+ bool m_bBidi :1;
+ bool m_bFormatted :1; // Already formatted
+ bool m_bFollowField :1; // Field follow inside
+ bool m_bFlyInContent:1; // Fly as character inside
+ RubyPosition m_eRubyPosition; // Phonetic position
+ sal_uInt8 m_nDirection:2; // Direction (0/90/180/270 degrees)
+protected:
+ explicit SwMultiPortion(TextFrameIndex const nEnd)
+ : m_bTab1(false)
+ , m_bTab2(false)
+ , m_bDouble(false)
+ , m_bRuby(false)
+ , m_bBidi(false)
+ , m_bFormatted(false)
+ , m_bFollowField(false)
+ , m_bFlyInContent(false)
+ , m_eRubyPosition( RubyPosition::ABOVE )
+ , m_nDirection(0)
+ {
+ SetWhichPor(PortionType::Multi);
+ SetLen(nEnd);
+ }
+ void SetDouble() { m_bDouble = true; }
+ void SetRuby() { m_bRuby = true; }
+ void SetBidi() { m_bBidi = true; }
+ void SetRubyPosition( RubyPosition eNew ) { m_eRubyPosition = eNew; }
+ void SetTab1( bool bNew ) { m_bTab1 = bNew; }
+ void SetTab2( bool bNew ) { m_bTab2 = bNew; }
+ void SetDirection( sal_uInt8 nNew ) { m_nDirection = nNew; }
+ bool GetTab1() const { return m_bTab1; }
+ bool GetTab2() const { return m_bTab2; }
+public:
+ virtual ~SwMultiPortion() override;
+ const SwLineLayout& GetRoot() const { return m_aRoot; }
+ SwLineLayout& GetRoot() { return m_aRoot; }
+
+ bool HasTabulator() const { return m_bTab1 || m_bTab2; }
+ bool IsFormatted() const { return m_bFormatted; }
+ void SetFormatted() { m_bFormatted = true; }
+ bool IsFollowField() const { return m_bFollowField; }
+ void SetFollowField() { m_bFollowField = true; }
+ bool HasFlyInContent() const { return m_bFlyInContent; }
+ void SetFlyInContent( bool bNew ) { m_bFlyInContent = bNew; }
+ bool IsDouble() const { return m_bDouble; }
+ bool IsRuby() const { return m_bRuby; }
+ bool IsBidi() const { return m_bBidi; }
+ bool OnTop() const { return m_eRubyPosition == RubyPosition::ABOVE; }
+ bool OnRight() const { return m_eRubyPosition == RubyPosition::RIGHT; }
+ RubyPosition GetRubyPosition() const { return m_eRubyPosition; }
+ void ActualizeTabulator();
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override;
+ virtual bool ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const;
+
+ // Summarize the internal lines to calculate the (external) size
+ void CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf );
+
+ inline bool HasBrackets() const;
+ bool HasRotation() const { return 0 != (1 & m_nDirection); }
+ bool IsRevers() const { return 0 != (2 & m_nDirection); }
+ sal_uInt8 GetDirection() const { return m_nDirection; }
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+class SwDoubleLinePortion : public SwMultiPortion
+{
+ std::unique_ptr<SwBracket> m_pBracket; // Surrounding brackets
+ SwTwips m_nLineDiff; // Difference of the width of the both lines
+ TextFrameIndex m_nBlank1; ///< Number of blanks in the first line
+ TextFrameIndex m_nBlank2; ///< Number of blanks in the second line
+public:
+ SwDoubleLinePortion(SwDoubleLinePortion& rDouble, TextFrameIndex nEnd);
+ SwDoubleLinePortion(const SwMultiCreator& rCreate, TextFrameIndex nEnd);
+ virtual ~SwDoubleLinePortion() override;
+
+ SwBracket* GetBrackets() const { return m_pBracket.get(); }
+ void SetBrackets( const SwDoubleLinePortion& rDouble );
+ void PaintBracket( SwTextPaintInfo& rInf, tools::Long nSpaceAdd, bool bOpen ) const;
+ void FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth );
+ sal_uInt16 PreWidth() const { return m_pBracket->nPreWidth; };
+ sal_uInt16 PostWidth() const { return m_pBracket->nPostWidth; }
+ void ClearBrackets()
+ { m_pBracket->nPreWidth = m_pBracket->nPostWidth=0; Width( 0 ); }
+ sal_uInt16 BracketWidth(){ return PreWidth() + PostWidth(); }
+
+ void CalcBlanks( SwTextFormatInfo &rInf );
+ static void ResetSpaceAdd( SwLineLayout* pCurr );
+ SwTwips GetLineDiff() const { return m_nLineDiff; }
+ TextFrameIndex GetSpaceCnt() const
+ { return ( m_nLineDiff < 0 ) ? m_nBlank2 : m_nBlank1; }
+ TextFrameIndex GetSmallerSpaceCnt() const
+ { return ( m_nLineDiff < 0 ) ? m_nBlank1 : m_nBlank2; }
+
+ virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override;
+ virtual bool ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const override;
+};
+
+class SwRubyPortion : public SwMultiPortion
+{
+ TextFrameIndex m_nRubyOffset;
+ css::text::RubyAdjust m_nAdjustment;
+ void Adjust_( SwTextFormatInfo &rInf);
+public:
+ SwRubyPortion(const SwRubyPortion& rRuby, TextFrameIndex nEnd);
+
+ SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt,
+ const IDocumentSettingAccess& rIDocumentSettingAccess,
+ TextFrameIndex nEnd, TextFrameIndex nOffs,
+ const SwTextSizeInfo &rInf );
+
+ void CalcRubyOffset();
+ void Adjust( SwTextFormatInfo &rInf )
+ { if(m_nAdjustment != css::text::RubyAdjust_LEFT && GetRoot().GetNext()) Adjust_(rInf); }
+ css::text::RubyAdjust GetAdjustment() const { return m_nAdjustment; }
+ TextFrameIndex GetRubyOffset() const { return m_nRubyOffset; }
+};
+
+class SwRotatedPortion : public SwMultiPortion
+{
+public:
+ SwRotatedPortion(TextFrameIndex const nEnd, sal_uInt8 nDir)
+ : SwMultiPortion( nEnd ) { SetDirection( nDir ); }
+ SwRotatedPortion( const SwMultiCreator& rCreate, TextFrameIndex nEnd,
+ bool bRTL );
+};
+
+class SwBidiPortion : public SwMultiPortion
+{
+ sal_uInt8 m_nLevel;
+
+public:
+ SwBidiPortion(TextFrameIndex nEnd, sal_uInt8 nLv);
+
+ sal_uInt8 GetLevel() const { return m_nLevel; }
+ // Get number of blanks for justified alignment
+ TextFrameIndex GetSpaceCnt(const SwTextSizeInfo &rInf) const;
+ // Calculates extra spacing based on number of blanks
+ virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override;
+ // Manipulate the spacing array at pCurr
+ virtual bool ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const override;
+};
+
+// For cursor travelling in multiportions
+
+class SwTextCursorSave
+{
+ SwTextCursor* pTextCursor;
+ SwLineLayout* pCurr;
+ TextFrameIndex nStart;
+ sal_uInt16 nWidth;
+ sal_uInt8 nOldProp;
+ bool bSpaceChg;
+public:
+ SwTextCursorSave( SwTextCursor* pTextCursor, SwMultiPortion* pMulti,
+ SwTwips nY, SwTwips& nX, TextFrameIndex nCurrStart, tools::Long nSpaceAdd);
+ ~SwTextCursorSave();
+};
+
+inline bool SwMultiPortion::HasBrackets() const
+{
+ return IsDouble() && nullptr != static_cast<const SwDoubleLinePortion*>(this)->GetBrackets();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porref.cxx b/sw/source/core/text/porref.cxx
new file mode 100644
index 0000000000..a64f2df1fe
--- /dev/null
+++ b/sw/source/core/text/porref.cxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <SwPortionHandler.hxx>
+#include <viewopt.hxx>
+
+#include "porref.hxx"
+#include "inftxt.hxx"
+
+void SwRefPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::Ref );
+ SwTextPortion::Paint( rInf );
+ }
+}
+
+SwLinePortion *SwIsoRefPortion::Compress() { return this; }
+
+SwIsoRefPortion::SwIsoRefPortion() : m_nViewWidth(0)
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::IsoRef );
+}
+
+sal_uInt16 SwIsoRefPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ // Although we are const, nViewWidth should be calculated in the last
+ // moment possible
+ SwIsoRefPortion* pThis = const_cast<SwIsoRefPortion*>(this);
+ if( !Width() && rInf.OnWin() && rInf.GetOpt().IsFieldShadings() &&
+ !rInf.GetOpt().IsReadonly() && !rInf.GetOpt().IsPagePreview() )
+ {
+ if( !m_nViewWidth )
+ pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width();
+ }
+ else
+ pThis->m_nViewWidth = 0;
+ return m_nViewWidth;
+}
+
+bool SwIsoRefPortion::Format( SwTextFormatInfo &rInf )
+{
+ return SwLinePortion::Format( rInf );
+}
+
+void SwIsoRefPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ rInf.DrawViewOpt( *this, PortionType::Ref );
+}
+
+void SwIsoRefPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString(), GetWhichPor() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porref.hxx b/sw/source/core/text/porref.hxx
new file mode 100644
index 0000000000..d128030803
--- /dev/null
+++ b/sw/source/core/text/porref.hxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "portxt.hxx"
+
+class SwRefPortion : public SwTextPortion
+{
+public:
+ SwRefPortion() { SetWhichPor(PortionType::Ref); }
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+};
+
+class SwIsoRefPortion : public SwRefPortion
+{
+ sal_uInt16 m_nViewWidth;
+
+public:
+ SwIsoRefPortion();
+ virtual bool Format(SwTextFormatInfo& rInf) override;
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+ virtual SwLinePortion* Compress() override;
+ virtual sal_uInt16 GetViewWidth(const SwTextSizeInfo& rInf) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion(SwPortionHandler& rPH) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx
new file mode 100644
index 0000000000..029adca753
--- /dev/null
+++ b/sw/source/core/text/porrst.cxx
@@ -0,0 +1,949 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <editeng/lspcitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/pgrditem.hxx>
+#include <vcl/svapp.hxx>
+#include <comphelper/scopeguard.hxx>
+
+#include <viewsh.hxx>
+#include <viewopt.hxx>
+#include <ndtxt.hxx>
+#include <pagefrm.hxx>
+#include <paratr.hxx>
+#include <SwPortionHandler.hxx>
+#include "porrst.hxx"
+#include "inftxt.hxx"
+#include "txtpaint.hxx"
+#include <swfntcch.hxx>
+#include <tgrditem.hxx>
+#include <pagedesc.hxx>
+#include <frmatr.hxx>
+#include "redlnitr.hxx"
+#include "atrhndl.hxx"
+#include <rootfrm.hxx>
+#include <formatlinebreak.hxx>
+#include <txatbase.hxx>
+
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentDeviceAccess.hxx>
+
+#include <crsrsh.hxx>
+#include <swtypes.hxx>
+#include <strings.hrc>
+#include <flyfrms.hxx>
+#include <bodyfrm.hxx>
+
+SwTmpEndPortion::SwTmpEndPortion( const SwLinePortion &rPortion,
+ const FontLineStyle eUL,
+ const FontStrikeout eStrkout,
+ const Color& rCol ) :
+ m_eUnderline( eUL ), m_eStrikeout( eStrkout ), m_aColor( rCol )
+{
+ Height( rPortion.Height() );
+ SetAscent( rPortion.GetAscent() );
+ SetWhichPor( PortionType::TempEnd );
+}
+
+void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if (!(rInf.OnWin() && rInf.GetOpt().IsParagraph()))
+ return;
+
+ const SwFont* pOldFnt = rInf.GetFont();
+
+ SwFont aFont(*pOldFnt);
+
+ // Paint strikeout/underline based on redline color and settings
+ // (with an extra pilcrow in the background, because there is
+ // no SetStrikeoutColor(), also SetUnderColor() doesn't work()).
+ if ( m_eUnderline != LINESTYLE_NONE || m_eStrikeout != STRIKEOUT_NONE )
+ {
+ aFont.SetColor( m_aColor );
+ aFont.SetUnderline( m_eUnderline );
+ aFont.SetStrikeout( m_eStrikeout );
+
+ const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont);
+
+ // draw the pilcrow with strikeout/underline in redline color
+ rInf.DrawText(CH_PAR, *this);
+
+ }
+
+ aFont.SetColor( NON_PRINTING_CHARACTER_COLOR );
+ aFont.SetStrikeout( STRIKEOUT_NONE );
+ aFont.SetUnderline( LINESTYLE_NONE );
+ const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont);
+
+ // draw the pilcrow
+ rInf.DrawText(CH_PAR, *this);
+
+ const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt));
+}
+
+SwBreakPortion::SwBreakPortion( const SwLinePortion &rPortion, const SwTextAttr* pAttr )
+ : SwLinePortion( rPortion )
+{
+ mnLineLength = TextFrameIndex(1);
+ m_eRedline = RedlineType::None;
+ SetWhichPor( PortionType::Break );
+
+ m_eClear = SwLineBreakClear::NONE;
+ if (pAttr && pAttr->Which() == RES_TXTATR_LINEBREAK)
+ {
+ m_eClear = pAttr->GetLineBreak().GetValue();
+ }
+ m_nTextHeight = 0;
+}
+
+TextFrameIndex SwBreakPortion::GetModelPositionForViewPoint(const sal_uInt16) const
+{
+ return TextFrameIndex(0);
+}
+
+sal_uInt16 SwBreakPortion::GetViewWidth( const SwTextSizeInfo & ) const
+{ return 0; }
+
+SwLinePortion *SwBreakPortion::Compress()
+{ return (GetNextPortion() && GetNextPortion()->InTextGrp() ? nullptr : this); }
+
+void SwBreakPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( !(rInf.OnWin() && rInf.GetOpt().IsLineBreak()) )
+ return;
+
+ // Reduce height to text height for the duration of the print, so the vertical height will look
+ // correct for the line break character, even for clearing breaks.
+ SwTwips nHeight = Height();
+ SwTwips nVertPosOffset = (nHeight - m_nTextHeight) / 2;
+ auto pPortion = const_cast<SwBreakPortion*>(this);
+ pPortion->Height(m_nTextHeight, false);
+ if (rInf.GetTextFrame()->IsVertical())
+ {
+ // Compensate for the offset done in SwTextCursor::AdjustBaseLine() for the vertical case.
+ const_cast<SwTextPaintInfo&>(rInf).Y(rInf.Y() + nVertPosOffset);
+ }
+ comphelper::ScopeGuard g(
+ [pPortion, nHeight, &rInf, nVertPosOffset]
+ {
+ if (rInf.GetTextFrame()->IsVertical())
+ {
+ const_cast<SwTextPaintInfo&>(rInf).Y(rInf.Y() - nVertPosOffset);
+ }
+ pPortion->Height(nHeight, false);
+ });
+
+ rInf.DrawLineBreak( *this );
+
+ // paint redlining
+ if (m_eRedline == RedlineType::None)
+ return;
+
+ sal_Int16 nNoBreakWidth = rInf.GetTextSize(S_NOBREAK_FOR_REDLINE).Width();
+ if ( nNoBreakWidth > 0 )
+ {
+ // approximate portion size with multiple no-break spaces
+ // and draw these spaces (at least a single one) by DrawText
+ // painting the requested redline underline/strikeout
+ sal_Int16 nSpaces = (LINE_BREAK_WIDTH + nNoBreakWidth/2) / nNoBreakWidth;
+ OUStringBuffer aBuf(S_NOBREAK_FOR_REDLINE);
+ for (sal_Int16 i = 1; i < nSpaces; ++i)
+ aBuf.append(S_NOBREAK_FOR_REDLINE);
+
+ const SwFont* pOldFnt = rInf.GetFont();
+
+ SwFont aFont(*pOldFnt);
+
+ if (m_eRedline == RedlineType::Delete)
+ aFont.SetUnderline( LINESTYLE_NONE );
+ else
+ aFont.SetStrikeout( STRIKEOUT_NONE );
+
+ const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont);
+
+ rInf.DrawText(aBuf.makeStringAndClear(), *this);
+
+ const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt));
+ }
+}
+
+bool SwBreakPortion::Format( SwTextFormatInfo &rInf )
+{
+ const SwLinePortion *pRoot = rInf.GetRoot();
+ Width( 0 );
+ Height( pRoot->Height() );
+ m_nTextHeight = Height();
+
+ // See if this is a clearing break. If so, calculate how much we need to "jump down" so the next
+ // line can again use the full text width.
+ SwLineBreakClear eClear = m_eClear;
+ if (rInf.GetTextFrame()->IsRightToLeft() && eClear != SwLineBreakClear::ALL)
+ {
+ // RTL ignores left/right breaks.
+ eClear = SwLineBreakClear::NONE;
+ }
+ if (eClear != SwLineBreakClear::NONE)
+ {
+ SwTextFly& rTextFly = rInf.GetTextFly();
+ if (rTextFly.IsOn())
+ {
+ SwTwips nHeight = rTextFly.GetMaxBottom(*this, rInf) - rInf.Y();
+ if (nHeight > Height())
+ {
+ Height(nHeight, /*bText=*/false);
+ }
+ }
+ }
+
+ SetAscent( pRoot->GetAscent() );
+ if (rInf.GetIdx() + TextFrameIndex(1) == TextFrameIndex(rInf.GetText().getLength()))
+ rInf.SetNewLine( true );
+ return true;
+}
+
+void SwBreakPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+void SwBreakPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex&
+ nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwBreakPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("text-height"),
+ BAD_CAST(OString::number(m_nTextHeight).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SwLineBreakClear SwBreakPortion::GetClear() const { return m_eClear; }
+
+SwKernPortion::SwKernPortion( SwLinePortion &rPortion, short nKrn,
+ bool bBG, bool bGK ) :
+ m_nKern( nKrn ), m_bBackground( bBG ), m_bGridKern( bGK )
+{
+ Height( rPortion.Height() );
+ SetAscent( rPortion.GetAscent() );
+ mnLineLength = TextFrameIndex(0);
+ SetWhichPor( PortionType::Kern );
+ if( m_nKern > 0 )
+ Width( m_nKern );
+ rPortion.Insert( this );
+}
+
+SwKernPortion::SwKernPortion( const SwLinePortion& rPortion ) :
+ m_nKern( 0 ), m_bBackground( false ), m_bGridKern( true )
+{
+ Height( rPortion.Height() );
+ SetAscent( rPortion.GetAscent() );
+
+ mnLineLength = TextFrameIndex(0);
+ SetWhichPor( PortionType::Kern );
+}
+
+void SwKernPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( !Width() )
+ return;
+
+ // bBackground is set for Kerning Portions between two fields
+ if ( m_bBackground )
+ rInf.DrawViewOpt( *this, PortionType::Field );
+
+ rInf.DrawBackBrush( *this );
+ if (GetJoinBorderWithNext() ||GetJoinBorderWithPrev())
+ rInf.DrawBorder( *this );
+
+ // do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ if( rInf.GetFont()->IsPaintBlank() )
+ {
+ SwRect aClipRect;
+ rInf.CalcRect( *this, &aClipRect );
+ SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) );
+ aClip.ChgClip( aClipRect );
+ rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(2), true );
+ }
+}
+
+void SwKernPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ if ( m_bGridKern )
+ return;
+
+ if( rInf.GetLast() == this )
+ rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) );
+ if( m_nKern < 0 )
+ Width( -m_nKern );
+ else
+ Width( 0 );
+ rInf.GetLast()->FormatEOL( rInf );
+}
+
+SwArrowPortion::SwArrowPortion( const SwLinePortion &rPortion ) :
+ m_bLeft( true )
+{
+ Height( rPortion.Height() );
+ SetAscent( rPortion.GetAscent() );
+ mnLineLength = TextFrameIndex(0);
+ SetWhichPor( PortionType::Arrow );
+}
+
+SwArrowPortion::SwArrowPortion( const SwTextPaintInfo &rInf )
+ : m_bLeft( false )
+{
+ Height( o3tl::narrowing<sal_uInt16>(rInf.GetTextFrame()->getFramePrintArea().Height()) );
+ m_aPos.setX( rInf.GetTextFrame()->getFrameArea().Left() +
+ rInf.GetTextFrame()->getFramePrintArea().Right() );
+ m_aPos.setY( rInf.GetTextFrame()->getFrameArea().Top() +
+ rInf.GetTextFrame()->getFramePrintArea().Bottom() );
+ SetWhichPor( PortionType::Arrow );
+}
+
+void SwArrowPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ const_cast<SwArrowPortion*>(this)->m_aPos = rInf.GetPos();
+}
+
+SwLinePortion *SwArrowPortion::Compress() { return this; }
+
+SwTwips SwTextFrame::EmptyHeight() const
+{
+ if (IsCollapse()) {
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if ( auto pCrSh = dynamic_cast<SwCursorShell*>( pSh ) ) {
+ // this is called during formatting so avoid recursive layout
+ SwContentFrame const*const pCurrFrame = pCrSh->GetCurrFrame(false);
+ if (pCurrFrame==static_cast<SwContentFrame const *>(this)) {
+ // do nothing
+ } else {
+ return 1;
+ }
+ } else {
+ return 1;
+ }
+ }
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::EmptyHeight with swapped frame" );
+
+ std::unique_ptr<SwFont> pFnt;
+ const SwTextNode& rTextNode = *GetTextNodeForParaProps();
+ const IDocumentSettingAccess* pIDSA = rTextNode.getIDocumentSettingAccess();
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if ( rTextNode.HasSwAttrSet() )
+ {
+ const SwAttrSet *pAttrSet = &( rTextNode.GetSwAttrSet() );
+ pFnt.reset(new SwFont( pAttrSet, pIDSA ));
+ }
+ else
+ {
+ SwFontAccess aFontAccess( &rTextNode.GetAnyFormatColl(), pSh);
+ pFnt.reset(new SwFont( aFontAccess.Get()->GetFont() ));
+ pFnt->CheckFontCacheId( pSh, pFnt->GetActual() );
+ }
+
+ if ( IsVertical() )
+ pFnt->SetVertical( 2700_deg10 );
+
+ OutputDevice* pOut = pSh ? pSh->GetOut() : nullptr;
+ if ( !pOut || !pSh->GetViewOptions()->getBrowseMode() ||
+ pSh->GetViewOptions()->IsPrtFormat() )
+ {
+ pOut = rTextNode.getIDocumentDeviceAccess().getReferenceDevice(true);
+ }
+
+ const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess();
+ if (IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
+ && !getRootFrame()->IsHideRedlines())
+ {
+ const SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any );
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ SwAttrHandler aAttrHandler;
+ aAttrHandler.Init(rTextNode.GetSwAttrSet(),
+ *rTextNode.getIDocumentSettingAccess());
+ SwRedlineItr aRedln( rTextNode, *pFnt, aAttrHandler,
+ nRedlPos, SwRedlineItr::Mode::Show);
+ }
+ }
+
+ SwTwips nRet;
+ if( !pOut )
+ nRet = IsVertical() ?
+ getFramePrintArea().SSize().Width() + 1 :
+ getFramePrintArea().SSize().Height() + 1;
+ else
+ {
+ pFnt->SetFntChg( true );
+ pFnt->ChgPhysFnt( pSh, *pOut );
+ nRet = pFnt->GetHeight( pSh, *pOut );
+ }
+ return nRet;
+}
+
+bool SwTextFrame::FormatEmpty()
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::FormatEmpty with swapped frame" );
+
+ bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( );
+
+ // sw_redlinehide: just disable FormatEmpty optimisation for now
+ // Split fly frames: non-last parts of the anchor want this optimization to clear the old
+ // content.
+ SwFlyAtContentFrame* pNonLastSplitFlyDrawObj = HasNonLastSplitFlyDrawObj();
+ bool bHasNonLastSplitFlyDrawObj = pNonLastSplitFlyDrawObj != nullptr;
+
+ if (pNonLastSplitFlyDrawObj && pNonLastSplitFlyDrawObj->IsWrapOnAllPages())
+ {
+ // Split fly: the anchor is non-empty on all pages in the "wrap on all pages" case.
+ bHasNonLastSplitFlyDrawObj = false;
+ }
+
+ if ((HasFollow() && !bHasNonLastSplitFlyDrawObj) || GetMergedPara() || (GetTextNodeFirst()->GetpSwpHints() && !bHasNonLastSplitFlyDrawObj) ||
+ nullptr != GetTextNodeForParaProps()->GetNumRule() ||
+ GetTextNodeFirst()->HasHiddenCharAttribute(true) ||
+ IsInFootnote() || ( HasPara() && GetPara()->IsPrepMustFit() ) )
+ return false;
+ const SwAttrSet& aSet = GetTextNodeForParaProps()->GetSwAttrSet();
+ const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
+ if( !bCollapse && ( ( ( ! IsRightToLeft() && ( SvxAdjust::Left != nAdjust ) ) ||
+ ( IsRightToLeft() && ( SvxAdjust::Right != nAdjust ) ) ) ||
+ aSet.GetRegister().GetValue() ) )
+ return false;
+ const SvxLineSpacingItem &rSpacing = aSet.GetLineSpacing();
+ if( !bCollapse && ( SvxLineSpaceRule::Min == rSpacing.GetLineSpaceRule() ||
+ SvxLineSpaceRule::Fix == rSpacing.GetLineSpaceRule() ||
+ aSet.GetFirstLineIndent().IsAutoFirst()))
+ {
+ return false;
+ }
+
+ SwTextFly aTextFly( this );
+ SwRect aRect;
+ bool bFirstFlyCheck = 0 != getFramePrintArea().Height();
+ if ( !bCollapse && bFirstFlyCheck &&
+ aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) && !bHasNonLastSplitFlyDrawObj )
+ return false;
+
+ if (IsEmptyWithSplitFly())
+ {
+ // We don't want this optimization in case the paragraph is not really empty, because it has
+ // a fly frame and it also needs space for the empty paragraph in a next line.
+ return false;
+ }
+
+ // only need to check one node because of early return on GetMerged()
+ for (SwContentIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex();
+ pIndex; pIndex = pIndex->GetNext())
+ {
+ sw::mark::IMark const*const pMark = pIndex->GetMark();
+ if (dynamic_cast<const sw::mark::IBookmark*>(pMark) != nullptr)
+ { // need bookmark portions!
+ return false;
+ }
+ }
+
+ SwTwips nHeight = EmptyHeight();
+
+ if (aSet.GetParaGrid().GetValue() &&
+ IsInDocBody() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if ( pGrid )
+ nHeight = pGrid->GetBaseHeight() + pGrid->GetRubyHeight();
+ }
+
+ SwRectFnSet aRectFnSet(this);
+ SwTwips nChg = nHeight - aRectFnSet.GetHeight(getFramePrintArea());
+ const SwBodyFrame* pBody = FindBodyFrame();
+ if (pNonLastSplitFlyDrawObj && pBody)
+ {
+ // See if we need to increase the text frame height due to split flys. This is necessary for
+ // anchors of inner floating tables, where moving to a next page moves indirectly, so we
+ // want a correct text frame height.
+ SwTwips nFrameBottom = aRectFnSet.GetBottom(getFrameArea()) + nChg;
+ SwTwips nFlyBottom = aRectFnSet.GetBottom(pNonLastSplitFlyDrawObj->getFrameArea());
+ SwTwips nBodyBottom = aRectFnSet.GetBottom(pBody->getFrameArea());
+ if (nFlyBottom > nBodyBottom)
+ {
+ // This is the legacy case where flys may overlap with footer frames.
+ nFlyBottom = nBodyBottom;
+ }
+ if (pNonLastSplitFlyDrawObj->isFrameAreaPositionValid() && nFlyBottom > nFrameBottom)
+ {
+ nChg += (nFlyBottom - nFrameBottom);
+ }
+ }
+
+ if( !nChg )
+ SetUndersized( false );
+ AdjustFrame( nChg );
+
+ if (GetHasRotatedPortions())
+ {
+ ClearPara();
+ SetHasRotatedPortions(false);
+ }
+
+ RemoveFromCache();
+ if( !IsEmpty() )
+ {
+ SetEmpty( true );
+ SetCompletePaint();
+ }
+ if( !bCollapse && !bFirstFlyCheck &&
+ aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) )
+ return false;
+
+ // #i35635# - call method <HideAndShowObjects()>
+ // to assure that objects anchored at the empty paragraph are
+ // correctly visible resp. invisible.
+ HideAndShowObjects();
+ return true;
+}
+
+bool SwTextFrame::FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff )
+{
+ const SwFrame *pFrame = this;
+ rRegDiff = 0;
+ while( !( ( SwFrameType::Body | SwFrameType::Fly )
+ & pFrame->GetType() ) && pFrame->GetUpper() )
+ pFrame = pFrame->GetUpper();
+ if( ( SwFrameType::Body| SwFrameType::Fly ) & pFrame->GetType() )
+ {
+ SwRectFnSet aRectFnSet(pFrame);
+ rRegStart = aRectFnSet.GetPrtTop(*pFrame);
+ pFrame = pFrame->FindPageFrame();
+ if( pFrame->IsPageFrame() )
+ {
+ SwPageDesc* pDesc = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(pFrame))->FindPageDesc();
+ if( pDesc )
+ {
+ rRegDiff = pDesc->GetRegHeight();
+ if( !rRegDiff )
+ {
+ const SwTextFormatColl *pFormat = pDesc->GetRegisterFormatColl();
+ if( pFormat )
+ {
+ const SvxLineSpacingItem &rSpace = pFormat->GetLineSpacing();
+ if( SvxLineSpaceRule::Fix == rSpace.GetLineSpaceRule() )
+ {
+ rRegDiff = rSpace.GetLineHeight();
+ pDesc->SetRegHeight( rRegDiff );
+ pDesc->SetRegAscent( ( 4 * rRegDiff ) / 5 );
+ }
+ else
+ {
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ SwFontAccess aFontAccess( pFormat, pSh );
+ SwFont aFnt( aFontAccess.Get()->GetFont() );
+
+ OutputDevice *pOut = nullptr;
+ if( !pSh || !pSh->GetViewOptions()->getBrowseMode() ||
+ pSh->GetViewOptions()->IsPrtFormat() )
+ pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true );
+
+ if( pSh && !pOut )
+ pOut = pSh->GetWin()->GetOutDev();
+
+ if( !pOut )
+ pOut = Application::GetDefaultDevice();
+
+ MapMode aOldMap( pOut->GetMapMode() );
+ pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
+
+ aFnt.ChgFnt( pSh, *pOut );
+ rRegDiff = aFnt.GetHeight( pSh, *pOut );
+ sal_uInt16 nNetHeight = rRegDiff;
+
+ switch( rSpace.GetLineSpaceRule() )
+ {
+ case SvxLineSpaceRule::Auto:
+ break;
+ case SvxLineSpaceRule::Min:
+ {
+ if( rRegDiff < rSpace.GetLineHeight() )
+ rRegDiff = rSpace.GetLineHeight();
+ break;
+ }
+ default:
+ OSL_FAIL( ": unknown LineSpaceRule" );
+ }
+ switch( rSpace.GetInterLineSpaceRule() )
+ {
+ case SvxInterLineSpaceRule::Off:
+ break;
+ case SvxInterLineSpaceRule::Prop:
+ {
+ tools::Long nTmp = rSpace.GetPropLineSpace();
+ if( nTmp < 50 )
+ nTmp = nTmp ? 50 : 100;
+ nTmp *= rRegDiff;
+ nTmp /= 100;
+ if( !nTmp )
+ ++nTmp;
+ rRegDiff = o3tl::narrowing<sal_uInt16>(nTmp);
+ nNetHeight = rRegDiff;
+ break;
+ }
+ case SvxInterLineSpaceRule::Fix:
+ {
+ rRegDiff = rRegDiff + rSpace.GetInterLineSpace();
+ nNetHeight = rRegDiff;
+ break;
+ }
+ default: OSL_FAIL( ": unknown InterLineSpaceRule" );
+ }
+ pDesc->SetRegHeight( rRegDiff );
+ pDesc->SetRegAscent( rRegDiff - nNetHeight +
+ aFnt.GetAscent( pSh, *pOut ) );
+ pOut->SetMapMode( aOldMap );
+ }
+ }
+ }
+ const tools::Long nTmpDiff = pDesc->GetRegAscent() - rRegDiff;
+ if ( aRectFnSet.IsVert() )
+ rRegStart -= nTmpDiff;
+ else
+ rRegStart += nTmpDiff;
+ }
+ }
+ }
+ return ( 0 != rRegDiff );
+}
+
+void SwHiddenTextPortion::Paint( const SwTextPaintInfo & rInf) const
+{
+#ifdef DBG_UTIL
+ OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut());
+ Color aCol( rInf.GetOpt().GetFieldShadingsColor() );
+ Color aOldColor( pOut->GetFillColor() );
+ pOut->SetFillColor( aCol );
+ Point aPos( rInf.GetPos() );
+ aPos.AdjustY( -150 );
+ aPos.AdjustX( -25 );
+ SwRect aRect( aPos, Size( 100, 200 ) );
+ pOut->DrawRect( aRect.SVRect() );
+ pOut->SetFillColor( aOldColor );
+#else
+ (void)rInf;
+#endif
+}
+
+bool SwHiddenTextPortion::Format( SwTextFormatInfo &rInf )
+{
+ Width( 0 );
+ rInf.GetTextFrame()->HideFootnotes( rInf.GetIdx(), rInf.GetIdx() + GetLen() );
+
+ return false;
+};
+
+bool SwControlCharPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo,
+ OUString & rOutString, SwFont & rTmpFont, int &) const
+{
+ if (mcChar == CHAR_WJ || !rTextPaintInfo.GetOpt().IsFieldShadings())
+ {
+ return false;
+ }
+
+ switch (mcChar)
+ {
+ case CHAR_ZWSP:
+ rOutString = "/"; break;
+// case CHAR_LRM :
+// rText = sal_Unicode(0x2514); break;
+// case CHAR_RLM :
+// rText = sal_Unicode(0x2518); break;
+ default:
+ assert(false);
+ break;
+ }
+
+ rTmpFont.SetEscapement( CHAR_ZWSP == mcChar ? DFLT_ESC_AUTO_SUB : -25 );
+ const sal_uInt16 nProp = 40;
+ rTmpFont.SetProportion( nProp ); // a smaller font
+
+ return true;
+}
+
+bool SwBookmarkPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo,
+ OUString & rOutString, SwFont & rFont, int & rDeltaY) const
+{
+ // custom color is visible without field shading, too
+ if (!rTextPaintInfo.GetOpt().IsShowBookmarks())
+ {
+ return false;
+ }
+
+ rOutString = OUStringChar(mcChar);
+
+ // init font: we want OpenSymbol to ensure it doesn't look too crazy;
+ // thin and a bit higher than the surrounding text
+ auto const nOrigAscent(rFont.GetAscent(rTextPaintInfo.GetVsh(), *rTextPaintInfo.GetOut()));
+ rFont.SetName("OpenSymbol", rFont.GetActual());
+ Size aSize(rFont.GetSize(rFont.GetActual()));
+ // use also the external leading (line gap) of the portion, but don't use
+ // 100% of it because i can't figure out how to baseline align that
+ assert(aSize.Height() != 0);
+ auto const nFactor = aSize.Height() > 0 ? (Height() * 95) / aSize.Height() : Height();
+ rFont.SetProportion(nFactor);
+ rFont.SetWeight(WEIGHT_THIN, rFont.GetActual());
+ rFont.SetColor(rTextPaintInfo.GetOpt().GetFieldShadingsColor());
+ // reset these to default...
+ rFont.SetAlign(ALIGN_BASELINE);
+ rFont.SetUnderline(LINESTYLE_NONE);
+ rFont.SetOverline(LINESTYLE_NONE);
+ rFont.SetStrikeout(STRIKEOUT_NONE);
+ rFont.SetOutline(false);
+ rFont.SetShadow(false);
+ rFont.SetTransparent(false);
+ rFont.SetEmphasisMark(FontEmphasisMark::NONE);
+ rFont.SetEscapement(0);
+ rFont.SetPitch(PITCH_DONTKNOW, rFont.GetActual());
+ rFont.SetRelief(FontRelief::NONE);
+
+ // adjust Y position to account for different baselines of the fonts
+ auto const nOSAscent(rFont.GetAscent(rTextPaintInfo.GetVsh(), *rTextPaintInfo.GetOut()));
+ rDeltaY = nOSAscent - nOrigAscent;
+
+ return true;
+}
+
+void SwControlCharPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( !Width() ) // is only set during prepaint mode
+ return;
+
+ rInf.DrawViewOpt(*this, GetWhichPor());
+
+ int deltaY(0);
+ SwFont aTmpFont( *rInf.GetFont() );
+ OUString aOutString;
+
+ if (!(rInf.OnWin()
+ && !rInf.GetOpt().IsPagePreview()
+ && !rInf.GetOpt().IsReadonly()
+ && DoPaint(rInf, aOutString, aTmpFont, deltaY)))
+ return;
+
+ SwFontSave aFontSave( rInf, &aTmpFont );
+
+ if ( !mnHalfCharWidth )
+ mnHalfCharWidth = rInf.GetTextSize( aOutString ).Width() / 2;
+
+ Point aOldPos = rInf.GetPos();
+ Point aNewPos( aOldPos );
+ auto const deltaX((Width() / 2) - mnHalfCharWidth);
+ switch (rInf.GetFont()->GetOrientation(rInf.GetTextFrame()->IsVertical()).get())
+ {
+ case 0:
+ aNewPos.AdjustX(deltaX);
+ aNewPos.AdjustY(deltaY);
+ break;
+ case 900:
+ aNewPos.AdjustY(-deltaX);
+ aNewPos.AdjustX(deltaY);
+ break;
+ case 2700:
+ aNewPos.AdjustY(deltaX);
+ aNewPos.AdjustX(-deltaY);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos );
+
+ rInf.DrawText( aOutString, *this );
+
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aOldPos );
+}
+
+void SwBookmarkPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( !Width() ) // is only set during prepaint mode
+ return;
+
+ rInf.DrawViewOpt(*this, GetWhichPor());
+
+ int deltaY(0);
+ SwFont aTmpFont( *rInf.GetFont() );
+ OUString aOutString;
+
+ if (!(rInf.OnWin()
+ && !rInf.GetOpt().IsPagePreview()
+ && !rInf.GetOpt().IsReadonly()
+ && DoPaint(rInf, aOutString, aTmpFont, deltaY)))
+ return;
+
+ SwFontSave aFontSave( rInf, &aTmpFont );
+
+ if ( !mnHalfCharWidth )
+ mnHalfCharWidth = rInf.GetTextSize( aOutString ).Width() / 2;
+
+ Point aOldPos = rInf.GetPos();
+ Point aNewPos( aOldPos );
+ auto const deltaX((Width() / 2) - mnHalfCharWidth);
+ switch (rInf.GetFont()->GetOrientation(rInf.GetTextFrame()->IsVertical()).get())
+ {
+ case 0:
+ aNewPos.AdjustX(deltaX);
+ aNewPos.AdjustY(deltaY);
+ break;
+ case 900:
+ aNewPos.AdjustY(-deltaX);
+ aNewPos.AdjustX(deltaY);
+ break;
+ case 2700:
+ aNewPos.AdjustY(deltaX);
+ aNewPos.AdjustX(-deltaY);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ // draw end marks before the character position
+ if ( m_nStart == 0 || m_nEnd == 0 )
+ {
+ // single type boundary marks are there outside of the bookmark text
+ // some |text| here
+ // [[ ]]
+ if (m_nStart > 1)
+ aNewPos.AdjustX(static_cast<tools::Long>(mnHalfCharWidth) * -2 * (m_oColors.size() - 1));
+ }
+ else if ( m_nStart != 0 && m_nEnd != 0 )
+ // both end and start boundary marks: adjust them around the bookmark position
+ // |te|xt|
+ // ]] [[
+ aNewPos.AdjustX(static_cast<tools::Long>(mnHalfCharWidth) * -(2 * m_nEnd - 1 + m_nPoint) );
+
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos );
+
+ for ( const auto& it : m_oColors )
+ {
+ // set bold for custom colored bookmark symbol
+ // and draw multiple symbols showing all custom colors
+ aTmpFont.SetWeight( COL_TRANSPARENT == std::get<1>(it) ? WEIGHT_THIN : WEIGHT_BOLD, aTmpFont.GetActual() );
+ aTmpFont.SetColor( COL_TRANSPARENT == std::get<1>(it) ? rInf.GetOpt().GetFieldShadingsColor() : std::get<1>(it) );
+ aOutString = OUString(std::get<0>(it) == SwScriptInfo::MarkKind::Start ? '[' : ']');
+
+ // MarkKind::Point: drawn I-beam (e.g. U+2336) as overlapping ][
+ if ( std::get<0>(it) == SwScriptInfo::MarkKind::Point )
+ {
+ aNewPos.AdjustX(-mnHalfCharWidth * 5/16);
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos );
+ rInf.DrawText( aOutString, *this );
+
+ // when the overlapping vertical lines are 50 pixel width on the screen,
+ // this distance (half width * 5/8) still results precise overlapping
+ aNewPos.AdjustX(mnHalfCharWidth * 5/8);
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos );
+ aOutString = OUString('[');
+ }
+ rInf.DrawText( aOutString, *this );
+ // place the next symbol after the previous one
+ // TODO: fix orientation and start/end
+ aNewPos.AdjustX(mnHalfCharWidth * 2);
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aNewPos );
+ }
+
+ const_cast< SwTextPaintInfo& >( rInf ).SetPos( aOldPos );
+}
+
+void SwBookmarkPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ OUStringBuffer aStr;
+ for ( const auto& it : m_oColors )
+ {
+ aStr.append("#" + std::get<2>(it) + " " + SwResId(STR_BOOKMARK_DEF_NAME));
+ switch (std::get<0>(it))
+ {
+ case SwScriptInfo::MarkKind::Point:
+ break;
+ case SwScriptInfo::MarkKind::Start:
+ aStr.append(" " + SwResId(STR_CAPTION_BEGINNING));
+ break;
+ case SwScriptInfo::MarkKind::End:
+ aStr.append(" " + SwResId(STR_CAPTION_END));
+ break;
+ }
+ }
+
+ rPH.Special( GetLen(), aStr.makeStringAndClear(), GetWhichPor() );
+}
+
+void SwBookmarkPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwBookmarkPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ if (!m_oColors.empty())
+ {
+ OUStringBuffer aStr;
+ for (const auto& rColor : m_oColors)
+ {
+ aStr.append("#" + std::get<2>(rColor) + " " + SwResId(STR_BOOKMARK_DEF_NAME));
+ switch (std::get<0>(rColor))
+ {
+ case SwScriptInfo::MarkKind::Point:
+ break;
+ case SwScriptInfo::MarkKind::Start:
+ aStr.append(" " + SwResId(STR_CAPTION_BEGINNING));
+ break;
+ case SwScriptInfo::MarkKind::End:
+ aStr.append(" " + SwResId(STR_CAPTION_END));
+ break;
+ }
+ }
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("colors"),
+ BAD_CAST(aStr.makeStringAndClear().toUtf8().getStr()));
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+bool SwControlCharPortion::Format( SwTextFormatInfo &rInf )
+{
+ const SwLinePortion* pRoot = rInf.GetRoot();
+ Width( 0 );
+ Height( pRoot->Height() );
+ SetAscent( pRoot->GetAscent() );
+
+ return false;
+}
+
+sal_uInt16 SwControlCharPortion::GetViewWidth( const SwTextSizeInfo& rInf ) const
+{
+ if( !mnViewWidth )
+ mnViewWidth = rInf.GetTextSize(OUString(' ')).Width();
+
+ return mnViewWidth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx
new file mode 100644
index 0000000000..c5e57c688a
--- /dev/null
+++ b/sw/source/core/text/porrst.hxx
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <tools/gen.hxx>
+
+#include <TextFrameIndex.hxx>
+#include <txttypes.hxx>
+#include <txtfrm.hxx>
+#include <svx/ctredlin.hxx>
+#include <scriptinfo.hxx>
+
+#include "porlin.hxx"
+#include "portxt.hxx"
+#include "possiz.hxx"
+
+class SwPortionHandler;
+class SwTextPaintInfo;
+class SwTextSizeInfo;
+class SwFont;
+
+#define LINE_BREAK_WIDTH 150
+#define SPECIAL_FONT_HEIGHT 200
+
+class SwTextFormatInfo;
+
+class SwTmpEndPortion : public SwLinePortion
+{
+ const FontLineStyle m_eUnderline;
+ const FontStrikeout m_eStrikeout;
+ Color m_aColor;
+
+public:
+ explicit SwTmpEndPortion( const SwLinePortion &rPortion,
+ const FontLineStyle eUnderline,
+ const FontStrikeout eStrikeout,
+ const Color& rColor );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+};
+
+enum class SwLineBreakClear;
+
+class SwBreakPortion : public SwLinePortion
+{
+ RedlineType m_eRedline;
+
+ /// Tracks the type of the breaking clear from SwTextLineBreak, if there is one.
+ SwLineBreakClear m_eClear;
+
+ /// Height of the line-break character itself, without spacing added for clearing.
+ SwTwips m_nTextHeight;
+
+public:
+ explicit SwBreakPortion(const SwLinePortion& rPortion, const SwTextAttr* pAttr);
+ // Returns 0 if we have no usable data
+ virtual SwLinePortion *Compress() override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+
+ static constexpr OUString S_NOBREAK_FOR_REDLINE = u"\u00A0"_ustr;
+ void SetRedline( const RedlineType eRedline ) { m_eRedline = eRedline; }
+
+ SwLineBreakClear GetClear() const;
+};
+
+class SwKernPortion : public SwLinePortion
+{
+ short m_nKern;
+ bool m_bBackground;
+ bool m_bGridKern;
+
+public:
+
+ // This constructor automatically appends the portion to rPortion
+ // bBG indicates, that the background of the kerning portion has to
+ // be painted, e.g., if the portion if positioned between to fields.
+ // bGridKern indicates, that the kerning portion is used to provide
+ // additional space in grid mode.
+ SwKernPortion( SwLinePortion &rPortion, short nKrn,
+ bool bBG = false, bool bGridKern = false );
+
+ // This constructor only sets the height and ascent to the values
+ // of rPortion. It is only used for kerning portions for grid mode
+ explicit SwKernPortion( const SwLinePortion &rPortion );
+
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+};
+
+/// Indicator that the content does not fit into a fixed height frame (red triangle on the UI).
+class SwArrowPortion : public SwLinePortion
+{
+ Point m_aPos;
+ bool m_bLeft;
+public:
+ explicit SwArrowPortion( const SwLinePortion &rPortion );
+ explicit SwArrowPortion( const SwTextPaintInfo &rInf );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual SwLinePortion *Compress() override;
+ bool IsLeft() const { return m_bLeft; }
+ const Point& GetPos() const { return m_aPos; }
+};
+
+// The characters which are forbidden at the start of a line like the dot and
+// other punctuation marks are allowed to display in the margin of the page
+// by a user option.
+// The SwHangingPortion is the corresponding textportion to do that.
+class SwHangingPortion : public SwTextPortion
+{
+ sal_uInt16 m_nInnerWidth;
+public:
+ explicit SwHangingPortion( SwPosSize aSize ) : m_nInnerWidth( aSize.Width() )
+ {
+ SetWhichPor( PortionType::Hanging );
+ SetLen(TextFrameIndex(1));
+ Height( aSize.Height() );
+ }
+
+ sal_uInt16 GetInnerWidth() const { return m_nInnerWidth; }
+};
+
+// Used to hide text
+class SwHiddenTextPortion : public SwLinePortion
+{
+public:
+ explicit SwHiddenTextPortion(TextFrameIndex const nLen)
+ {
+ SetWhichPor( PortionType::HiddenText ); SetLen( nLen );
+ }
+
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+};
+
+class SwControlCharPortion : public SwLinePortion
+{
+
+private:
+ mutable sal_uInt16 mnViewWidth; // used to cache a calculated value
+protected:
+ mutable sal_uInt16 mnHalfCharWidth; // used to cache a calculated value
+ sal_Unicode mcChar;
+
+public:
+
+ explicit SwControlCharPortion( sal_Unicode cChar )
+ : mnViewWidth( 0 ), mnHalfCharWidth( 0 ), mcChar( cChar )
+ {
+ SetWhichPor( PortionType::ControlChar ); SetLen( TextFrameIndex(1) );
+ }
+
+ virtual bool DoPaint(SwTextPaintInfo const& rInf,
+ OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo& rInf ) const override;
+};
+
+/// for showing bookmark starts and ends; note that in contrast to
+/// SwControlCharPortion these do not have a character in the text.
+class SwBookmarkPortion : public SwControlCharPortion
+{
+ // custom colors defined by metadata
+ std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> m_oColors;
+ // number of MarkKind marks
+ sal_Int16 m_nStart, m_nEnd, m_nPoint;
+ bool m_bHasCustomColor;
+
+public:
+ explicit SwBookmarkPortion(sal_Unicode const cChar, std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>>rColors)
+ : SwControlCharPortion(cChar), m_oColors(rColors), m_nStart(0), m_nEnd(0), m_nPoint(0), m_bHasCustomColor(false)
+ {
+ SetWhichPor(PortionType::Bookmark);
+ SetLen(TextFrameIndex(0));
+ for (const auto& it : m_oColors)
+ {
+ if (std::get<0>(it) == SwScriptInfo::MarkKind::Start)
+ m_nStart++;
+ else if (std::get<0>(it) == SwScriptInfo::MarkKind::End)
+ m_nEnd++;
+ else
+ m_nPoint++;
+
+ if (!m_bHasCustomColor && COL_TRANSPARENT != std::get<1>(it))
+ m_bHasCustomColor = true;
+ }
+ }
+
+ virtual bool DoPaint(SwTextPaintInfo const& rInf,
+ OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual SwLinePortion * Compress() override { return this; }
+ virtual void HandlePortion(SwPortionHandler& rPH) const override;
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& rOffset) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/portab.hxx b/sw/source/core/text/portab.hxx
new file mode 100644
index 0000000000..9ffe7be506
--- /dev/null
+++ b/sw/source/core/text/portab.hxx
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "porglue.hxx"
+
+class SwTabPortion : public SwFixPortion
+{
+ const sal_uInt16 m_nTabPos;
+ const sal_Unicode m_cFill;
+ const bool m_bAutoTabStop;
+
+ // Format() branches either into PreFormat() or PostFormat()
+ bool PreFormat( SwTextFormatInfo &rInf );
+public:
+ SwTabPortion( const sal_uInt16 nTabPos, const sal_Unicode cFill, const bool bAutoTab = true );
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+ bool PostFormat( SwTextFormatInfo &rInf );
+ bool IsFilled() const { return 0 != m_cFill; }
+ sal_uInt16 GetTabPos() const { return m_nTabPos; }
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+};
+
+class SwTabLeftPortion : public SwTabPortion
+{
+public:
+ SwTabLeftPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar, bool bAutoTab )
+ : SwTabPortion( nTabPosVal, cFillChar, bAutoTab )
+ { SetWhichPor( PortionType::TabLeft ); }
+};
+
+class SwTabRightPortion : public SwTabPortion
+{
+public:
+ SwTabRightPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar )
+ : SwTabPortion( nTabPosVal, cFillChar )
+ { SetWhichPor( PortionType::TabRight ); }
+};
+
+class SwTabCenterPortion : public SwTabPortion
+{
+public:
+ SwTabCenterPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cFillChar )
+ : SwTabPortion( nTabPosVal, cFillChar )
+ { SetWhichPor( PortionType::TabCenter ); }
+};
+
+class SwTabDecimalPortion : public SwTabPortion
+{
+ const sal_Unicode mcTab;
+
+ /*
+ * During text formatting, we already store the width of the portions
+ * following the tab stop up to the decimal position. This value is
+ * evaluated during pLastTab->FormatEOL. FME 2006-01-06 #127428#.
+ */
+ sal_uInt16 mnWidthOfPortionsUpTpDecimalPosition;
+
+public:
+ SwTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab,
+ const sal_Unicode cFillChar )
+ : SwTabPortion( nTabPosVal, cFillChar ),
+ mcTab(cTab),
+ mnWidthOfPortionsUpTpDecimalPosition( USHRT_MAX )
+ { SetWhichPor( PortionType::TabDecimal ); }
+
+ sal_Unicode GetTabDecimal() const { return mcTab; }
+
+ void SetWidthOfPortionsUpToDecimalPosition( sal_uInt16 nNew )
+ {
+ mnWidthOfPortionsUpTpDecimalPosition = nNew;
+ }
+ sal_uInt16 GetWidthOfPortionsUpToDecimalPosition() const
+ {
+ return mnWidthOfPortionsUpTpDecimalPosition;
+ }
+};
+
+class SwAutoTabDecimalPortion : public SwTabDecimalPortion
+{
+public:
+ SwAutoTabDecimalPortion( const sal_uInt16 nTabPosVal, const sal_Unicode cTab,
+ const sal_Unicode cFillChar )
+ : SwTabDecimalPortion( nTabPosVal, cTab, cFillChar )
+ {
+ SetLen(TextFrameIndex(0));
+ }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/portox.cxx b/sw/source/core/text/portox.cxx
new file mode 100644
index 0000000000..bd72e83b04
--- /dev/null
+++ b/sw/source/core/text/portox.cxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <SwPortionHandler.hxx>
+#include <viewopt.hxx>
+
+#include "portox.hxx"
+#include "inftxt.hxx"
+
+void SwToxPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::Tox );
+ SwTextPortion::Paint( rInf );
+ }
+}
+
+SwLinePortion *SwIsoToxPortion::Compress() { return this; }
+
+SwIsoToxPortion::SwIsoToxPortion() : m_nViewWidth(0)
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::IsoTox );
+}
+
+sal_uInt16 SwIsoToxPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ // Although we are const, nViewWidth should be calculated in the last
+ // moment possible
+ SwIsoToxPortion* pThis = const_cast<SwIsoToxPortion*>(this);
+ // nViewWidth need to be calculated
+ if( !Width() && rInf.OnWin() &&
+ !rInf.GetOpt().IsPagePreview() &&
+ !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() )
+ {
+ if( !m_nViewWidth )
+ pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width();
+ }
+ else
+ pThis->m_nViewWidth = 0;
+ return m_nViewWidth;
+}
+
+bool SwIsoToxPortion::Format( SwTextFormatInfo &rInf )
+{
+ return SwLinePortion::Format( rInf );
+}
+
+void SwIsoToxPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ rInf.DrawViewOpt( *this, PortionType::Tox );
+}
+
+void SwIsoToxPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString(), GetWhichPor() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/portox.hxx b/sw/source/core/text/portox.hxx
new file mode 100644
index 0000000000..07d7c44fb2
--- /dev/null
+++ b/sw/source/core/text/portox.hxx
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "portxt.hxx"
+
+class SwToxPortion : public SwTextPortion
+{
+public:
+ SwToxPortion() { SetWhichPor(PortionType::Tox); }
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+};
+
+class SwIsoToxPortion : public SwToxPortion
+{
+ sal_uInt16 m_nViewWidth;
+
+public:
+ SwIsoToxPortion();
+ virtual bool Format(SwTextFormatInfo& rInf) override;
+ virtual void Paint(const SwTextPaintInfo& rInf) const override;
+ virtual SwLinePortion* Compress() override;
+ virtual sal_uInt16 GetViewWidth(const SwTextSizeInfo& rInf) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion(SwPortionHandler& rPH) const override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
new file mode 100644
index 0000000000..85691ef21e
--- /dev/null
+++ b/sw/source/core/text/portxt.cxx
@@ -0,0 +1,925 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <i18nlangtag/mslangid.hxx>
+#include <breakit.hxx>
+#include <hintids.hxx>
+#include <EnhancedPDFExportHelper.hxx>
+#include <SwPortionHandler.hxx>
+#include "porlay.hxx"
+#include "inftxt.hxx"
+#include "guess.hxx"
+#include "porfld.hxx"
+#include <pagefrm.hxx>
+#include <tgrditem.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+
+#include <IMark.hxx>
+#include <pam.hxx>
+#include <doc.hxx>
+#include <xmloff/odffields.hxx>
+#include <viewopt.hxx>
+
+using namespace ::sw::mark;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::i18n::ScriptType;
+
+// Returns for how many characters an extra space has to be added
+// (for justified alignment).
+static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf,
+ const OUString* pStr, const SwLinePortion& rPor)
+{
+ TextFrameIndex nPos, nEnd;
+ const SwScriptInfo* pSI = nullptr;
+
+ if ( pStr )
+ {
+ // passing a string means we are inside a field
+ nPos = TextFrameIndex(0);
+ nEnd = TextFrameIndex(pStr->getLength());
+ }
+ else
+ {
+ nPos = rInf.GetIdx();
+ nEnd = rInf.GetIdx() + rPor.GetLen();
+ pStr = &rInf.GetText();
+ pSI = &const_cast<SwParaPortion*>(rInf.GetParaPortion())->GetScriptInfo();
+ }
+
+ TextFrameIndex nCnt(0);
+ sal_uInt8 nScript = 0;
+
+ // If portion consists of Asian characters and language is not
+ // Korean, we add extra space to each character.
+ // first we get the script type
+ if ( pSI )
+ nScript = pSI->ScriptType( nPos );
+ else
+ nScript = static_cast<sal_uInt8>(
+ g_pBreakIt->GetBreakIter()->getScriptType(*pStr, sal_Int32(nPos)));
+
+ // Note: rInf.GetIdx() can differ from nPos,
+ // e.g., when rPor is a field portion. nPos refers to the string passed
+ // to the function, rInf.GetIdx() refers to the original string.
+
+ // We try to find out which justification mode is required. This is done by
+ // evaluating the script type and the language attribute set for this portion
+
+ // Asian Justification: Each character get some extra space
+ if ( nEnd > nPos && ASIAN == nScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
+
+ if (!MsLangId::isKorean(aLang))
+ {
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && ( pPor->IsKernPortion() ||
+ pPor->IsControlCharPortion() ||
+ pPor->IsPostItsPortion() ) )
+ pPor = pPor->GetNextPortion();
+
+ nCnt += SwScriptInfo::CountCJKCharacters( *pStr, nPos, nEnd, aLang );
+
+ if ( !pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ||
+ pPor->IsBreakPortion() )
+ --nCnt;
+
+ return nCnt;
+ }
+ }
+
+ // Kashida Justification: Insert Kashidas
+ if ( nEnd > nPos && pSI && COMPLEX == nScript )
+ {
+ if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() )
+ {
+ const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nullptr, nPos, nEnd - nPos);
+ // i60591: need to check result of KashidaJustify
+ // determine if kashida justification is applicable
+ if (nKashRes != -1)
+ return TextFrameIndex(nKashRes);
+ }
+ }
+
+ // Thai Justification: Each character cell gets some extra space
+ if ( nEnd > nPos && COMPLEX == nScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(rInf.GetIdx(), nScript);
+
+ if ( LANGUAGE_THAI == aLang )
+ {
+ nCnt = SwScriptInfo::ThaiJustify(*pStr, nullptr, nPos, nEnd - nPos);
+
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && ( pPor->IsKernPortion() ||
+ pPor->IsControlCharPortion() ||
+ pPor->IsPostItsPortion() ) )
+ pPor = pPor->GetNextPortion();
+
+ if ( nCnt && ( ! pPor || pPor->IsHolePortion() || pPor->InFixMargGrp() ) )
+ --nCnt;
+
+ return nCnt;
+ }
+ }
+
+ // Here starts the good old "Look for blanks and add space to them" part.
+ // Note: We do not want to add space to an isolated latin blank in front
+ // of some complex characters in RTL environment
+ const bool bDoNotAddSpace =
+ LATIN == nScript && (nEnd == nPos + TextFrameIndex(1)) && pSI &&
+ ( i18n::ScriptType::COMPLEX ==
+ pSI->ScriptType(nPos + TextFrameIndex(1))) &&
+ rInf.GetTextFrame() && rInf.GetTextFrame()->IsRightToLeft();
+
+ if ( bDoNotAddSpace )
+ return nCnt;
+
+ TextFrameIndex nTextEnd = std::min(nEnd, TextFrameIndex(pStr->getLength()));
+ for ( ; nPos < nTextEnd; ++nPos )
+ {
+ if (CH_BLANK == (*pStr)[ sal_Int32(nPos) ])
+ ++nCnt;
+ }
+
+ // We still have to examine the next character:
+ // If the next character is ASIAN and not KOREAN we have
+ // to add an extra space
+ // nPos refers to the original string, even if a field string has
+ // been passed to this function
+ nPos = rInf.GetIdx() + rPor.GetLen();
+ if (nPos < TextFrameIndex(rInf.GetText().getLength()))
+ {
+ sal_uInt8 nNextScript = 0;
+ const SwLinePortion* pPor = rPor.GetNextPortion();
+ if ( pPor && pPor->IsKernPortion() )
+ pPor = pPor->GetNextPortion();
+
+ if (!pPor || pPor->InFixMargGrp())
+ return nCnt;
+
+ // next character is inside a field?
+ if ( CH_TXTATR_BREAKWORD == rInf.GetChar( nPos ) && pPor->InExpGrp() )
+ {
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ pPor->GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+
+ nNextScript = static_cast<sal_uInt8>(g_pBreakIt->GetBreakIter()->getScriptType( aStr, 0 ));
+ }
+ else
+ nNextScript = static_cast<sal_uInt8>(
+ g_pBreakIt->GetBreakIter()->getScriptType(rInf.GetText(), sal_Int32(nPos)));
+
+ if( ASIAN == nNextScript )
+ {
+ LanguageType aLang =
+ rInf.GetTextFrame()->GetLangOfChar(nPos, nNextScript);
+
+ if (!MsLangId::isKorean(aLang))
+ ++nCnt;
+ }
+ }
+
+ return nCnt;
+}
+
+SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion)
+{
+ SwTextPortion *const pNew(new SwTextPortion);
+ static_cast<SwLinePortion&>(*pNew) = rPortion;
+ pNew->SetWhichPor( PortionType::Text ); // overwrite that!
+ return pNew;
+}
+
+void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess )
+{
+ // The word/char is larger than the line
+ // Special case 1: The word is larger than the line
+ // We truncate ...
+ const sal_uInt16 nLineWidth = o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X());
+ TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx();
+ if (nLen > TextFrameIndex(0))
+ {
+ // special case: guess does not always provide the correct
+ // width, only in common cases.
+ if ( !rGuess.BreakWidth() )
+ {
+ rInf.SetLen( nLen );
+ SetLen( nLen );
+ CalcTextSize( rInf );
+
+ // changing these values requires also changing them in
+ // guess.cxx
+ sal_uInt16 nItalic = 0;
+ if( ITALIC_NONE != rInf.GetFont()->GetItalic() && !rInf.NotEOL() )
+ {
+ nItalic = Height() / 12;
+ }
+ Width( Width() + nItalic );
+ }
+ else
+ {
+ Width( rGuess.BreakWidth() );
+ SetLen( nLen );
+ }
+ }
+ // special case: first character does not fit to line
+ else if ( rGuess.CutPos() == rInf.GetLineStart() )
+ {
+ SetLen( TextFrameIndex(1) );
+ Width( nLineWidth );
+ }
+ else
+ {
+ SetLen( TextFrameIndex(0) );
+ Width( 0 );
+ }
+}
+
+void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf )
+{
+ Truncate();
+ Height( 0 );
+ Width( 0 );
+ SetLen( TextFrameIndex(0) );
+ SetAscent( 0 );
+ rInf.SetUnderflow( this );
+}
+
+static bool lcl_HasContent( const SwFieldPortion& rField, SwTextFormatInfo const &rInf )
+{
+ OUString aText;
+ return rField.GetExpText( rInf, aText ) && !aText.isEmpty();
+}
+
+bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
+{
+ // 5744: If only the hyphen does not fit anymore, we still need to wrap
+ // the word, or else return true!
+ if( rInf.IsUnderflow() && rInf.GetSoftHyphPos() )
+ {
+ // soft hyphen portion has triggered an underflow event because
+ // of an alternative spelling position
+ bool bFull = false;
+ const bool bHyph = rInf.ChgHyph( true );
+ if( rInf.IsHyphenate() )
+ {
+ SwTextGuess aGuess;
+ // check for alternative spelling left from the soft hyphen
+ // this should usually be true but
+ aGuess.AlternativeSpelling(rInf, rInf.GetSoftHyphPos() - TextFrameIndex(1));
+ bFull = CreateHyphen( rInf, aGuess );
+ OSL_ENSURE( bFull, "Problem with hyphenation!!!" );
+ }
+ rInf.ChgHyph( bHyph );
+ rInf.SetSoftHyphPos( TextFrameIndex(0) );
+ return bFull;
+ }
+
+ SwTextGuess aGuess;
+ bool bFull = !aGuess.Guess( *this, rInf, Height() );
+
+ // tdf#158776 for the last full text portion, call Guess() again to allow more text in the
+ // adjusted line by shrinking spaces using the know space count from the first Guess() call
+ const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust();
+ if ( bFull && rAdjust == SvxAdjust::Block &&
+ aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
+ rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING) &&
+ // tdf#158436 avoid shrinking at underflow, e.g. no-break space after a
+ // very short word resulted endless loop
+ !rInf.IsUnderflow() )
+ {
+ sal_Int32 nSpacesInLine(0);
+ for (sal_Int32 i = sal_Int32(rInf.GetLineStart()); i < sal_Int32(aGuess.BreakPos()); ++i)
+ {
+ sal_Unicode cChar = rInf.GetText()[i];
+ if ( cChar == CH_BLANK )
+ ++nSpacesInLine;
+ }
+
+ // call with an extra space: shrinking can result a new word in the line
+ // and a new space before that, which is also a shrank space
+ // (except if the line was already broken at a soft hyphen, i.e. inside a word)
+ if ( aGuess.BreakPos() < TextFrameIndex(rInf.GetText().getLength()) &&
+ rInf.GetText()[sal_Int32(aGuess.BreakPos())] != CHAR_SOFTHYPHEN )
+ {
+ ++nSpacesInLine;
+ }
+
+ bFull = !aGuess.Guess( *this, rInf, Height(), nSpacesInLine );
+ }
+
+ // these are the possible cases:
+ // A Portion fits to current line
+ // B Portion does not fit to current line but a possible line break
+ // within the portion has been found by the break iterator, 2 subcases
+ // B1 break is hyphen
+ // B2 break is word end
+ // C Portion does not fit to current line and no possible line break
+ // has been found by break iterator, 2 subcases:
+ // C1 break iterator found a possible line break in portion before us
+ // ==> this break is used (underflow)
+ // C2 break iterator does not found a possible line break at all:
+ // ==> line break
+
+ // case A: line not yet full
+ if ( !bFull )
+ {
+ Width( aGuess.BreakWidth() );
+ ExtraBlankWidth(aGuess.ExtraBlankWidth());
+ // Caution!
+ if( !InExpGrp() || InFieldGrp() )
+ SetLen( rInf.GetLen() );
+
+ short nKern = rInf.GetFont()->CheckKerning();
+ if( nKern > 0 && rInf.Width() < rInf.X() + Width() + nKern )
+ {
+ nKern = static_cast<short>(rInf.Width() - rInf.X() - Width() - 1);
+ if( nKern < 0 )
+ nKern = 0;
+ }
+ if( nKern )
+ new SwKernPortion( *this, nKern );
+ }
+ // special case: hanging portion
+ else if( aGuess.GetHangingPortion() )
+ {
+ Width( aGuess.BreakWidth() );
+ SetLen( aGuess.BreakPos() - rInf.GetIdx() );
+ aGuess.GetHangingPortion()->SetAscent( GetAscent() );
+ Insert( aGuess.ReleaseHangingPortion() );
+ }
+ // breakPos >= index
+ else if (aGuess.BreakPos() >= rInf.GetIdx() && aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING))
+ {
+ // case B1
+ if( aGuess.HyphWord().is() && aGuess.BreakPos() > rInf.GetLineStart()
+ && ( aGuess.BreakPos() > rInf.GetIdx() ||
+ ( rInf.GetLast() && ! rInf.GetLast()->IsFlyPortion() ) ) )
+ {
+ CreateHyphen( rInf, aGuess );
+ if ( rInf.GetFly() )
+ rInf.GetRoot()->SetMidHyph( true );
+ else
+ rInf.GetRoot()->SetEndHyph( true );
+ }
+ // case C1
+ // - Footnote portions with fake line start (i.e., not at beginning of line)
+ // should keep together with the text portion. (Note: no keep together
+ // with only footnote portions.
+ // - TabPortions not at beginning of line should keep together with the
+ // text portion, if they are not followed by a blank
+ // (work around different definition of tab stop character - breaking or
+ // non breaking character - in compatibility mode)
+ else if ( ( IsFootnotePortion() && rInf.IsFakeLineStart() &&
+
+ rInf.IsOtherThanFootnoteInside() ) ||
+ ( rInf.GetLast() &&
+ rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) &&
+ rInf.GetLast()->InTabGrp() &&
+ rInf.GetLineStart() + rInf.GetLast()->GetLen() < rInf.GetIdx() &&
+ aGuess.BreakPos() == rInf.GetIdx() &&
+ CH_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
+ CH_FULL_BLANK != rInf.GetChar( rInf.GetIdx() ) &&
+ CH_SIX_PER_EM != rInf.GetChar( rInf.GetIdx() ) ) )
+ BreakUnderflow( rInf );
+ // case B2
+ else if( rInf.GetIdx() > rInf.GetLineStart() ||
+ aGuess.BreakPos() > rInf.GetIdx() ||
+ // this is weird: during formatting the follow of a field
+ // the values rInf.GetIdx and rInf.GetLineStart are replaced
+ // IsFakeLineStart indicates GetIdx > GetLineStart
+ rInf.IsFakeLineStart() ||
+ rInf.GetFly() ||
+ rInf.IsFirstMulti() ||
+ ( rInf.GetLast() &&
+ ( rInf.GetLast()->IsFlyPortion() ||
+ ( rInf.GetLast()->InFieldGrp() &&
+ ! rInf.GetLast()->InNumberGrp() &&
+ ! rInf.GetLast()->IsErgoSumPortion() &&
+ lcl_HasContent(*static_cast<SwFieldPortion*>(rInf.GetLast()),rInf ) ) ) ) )
+ {
+ Width( aGuess.BreakWidth() );
+
+ SetLen( aGuess.BreakPos() - rInf.GetIdx() );
+
+ OSL_ENSURE( aGuess.BreakStart() >= aGuess.FieldDiff(),
+ "Trouble with expanded field portions during line break" );
+ TextFrameIndex const nRealStart = aGuess.BreakStart() - aGuess.FieldDiff();
+ if( aGuess.BreakPos() < nRealStart && !InExpGrp() )
+ {
+ SwHolePortion *pNew = new SwHolePortion( *this );
+ pNew->SetLen( nRealStart - aGuess.BreakPos() );
+ pNew->Width(0);
+ pNew->ExtraBlankWidth( aGuess.ExtraBlankWidth() );
+ Insert( pNew );
+
+ // UAX #14 Unicode Line Breaking Algorithm Non-tailorable Line breaking rule LB6:
+ // https://www.unicode.org/reports/tr14/#LB6 Do not break before hard line breaks
+ if (auto ch = rInf.GetChar(aGuess.BreakStart()); !ch || ch == CH_BREAK)
+ bFull = false; // Keep following SwBreakPortion / para break in the same line
+ }
+ }
+ else // case C2, last exit
+ BreakCut( rInf, aGuess );
+ }
+ // breakPos < index or no breakpos at all
+ else
+ {
+ bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
+ if (aGuess.BreakPos() != TextFrameIndex(COMPLETE_STRING) &&
+ aGuess.BreakPos() != rInf.GetLineStart() &&
+ ( !bFirstPor || rInf.GetFly() || rInf.GetLast()->IsFlyPortion() ||
+ rInf.IsFirstMulti() ) &&
+ ( !rInf.GetLast()->IsBlankPortion() ||
+ SwBlankPortion::MayUnderflow(rInf, rInf.GetIdx() - TextFrameIndex(1), true)))
+ { // case C1 (former BreakUnderflow())
+ BreakUnderflow( rInf );
+ }
+ else
+ // case C2, last exit
+ BreakCut(rInf, aGuess);
+ }
+
+ return bFull;
+}
+
+bool SwTextPortion::Format( SwTextFormatInfo &rInf )
+{
+ // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN.
+ if( rInf.GetLineWidth() < 0 || (!GetLen() && !InExpGrp()) )
+ {
+ Height( 0 );
+ Width( 0 );
+ SetLen( TextFrameIndex(0) );
+ SetAscent( 0 );
+ SetNextPortion( nullptr ); // ????
+ return true;
+ }
+
+ OSL_ENSURE( rInf.RealWidth() || (rInf.X() == rInf.Width()),
+ "SwTextPortion::Format: missing real width" );
+ OSL_ENSURE( Height(), "SwTextPortion::Format: missing height" );
+
+ return Format_( rInf );
+}
+
+// Format end of line
+// 5083: We can have awkward cases e.g.:
+// "from {Santa}"
+// Santa wraps, "from " turns into "from" and " " in a justified
+// paragraph, in which the glue gets expanded instead of merged
+// with the MarginPortion.
+
+// rInf.nIdx points to the next word, nIdx-1 is the portion's last char
+void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ if( ( GetNextPortion() &&
+ ( !GetNextPortion()->IsKernPortion() || GetNextPortion()->GetNextPortion() ) ) ||
+ !GetLen() ||
+ rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()) ||
+ TextFrameIndex(1) >= rInf.GetIdx() ||
+ ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) ||
+ rInf.GetLast()->IsHolePortion() )
+ return;
+
+ // calculate number of blanks
+ TextFrameIndex nX(rInf.GetIdx() - TextFrameIndex(1));
+ TextFrameIndex nHoleLen(1);
+ while( nX && nHoleLen < GetLen() && CH_BLANK == rInf.GetChar( --nX ) )
+ nHoleLen++;
+
+ // First set ourselves and the insert, because there could be
+ // a SwLineLayout
+ sal_uInt16 nBlankSize;
+ if( nHoleLen == GetLen() )
+ nBlankSize = Width();
+ else
+ nBlankSize = sal_Int32(nHoleLen) * rInf.GetTextSize(OUString(' ')).Width();
+ Width( Width() - nBlankSize );
+ rInf.X( rInf.X() - nBlankSize );
+ SetLen( GetLen() - nHoleLen );
+ SwLinePortion *pHole = new SwHolePortion( *this );
+ static_cast<SwHolePortion *>( pHole )->SetBlankWidth( nBlankSize );
+ static_cast<SwHolePortion *>( pHole )->SetLen( nHoleLen );
+ Insert( pHole );
+
+}
+
+TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfst) const
+{
+ OSL_ENSURE( false, "SwTextPortion::GetModelPositionForViewPoint: don't use this method!" );
+ return SwLinePortion::GetModelPositionForViewPoint( nOfst );
+}
+
+// The GetTextSize() assumes that the own length is correct
+SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwPosSize aSize = rInf.GetTextSize();
+ if( !GetJoinBorderWithPrev() )
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() );
+ if( !GetJoinBorderWithNext() )
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace() );
+
+ aSize.Height(aSize.Height() +
+ rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace() );
+
+ return aSize;
+}
+
+void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDEND == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ assert(false); // this is some debugging only code
+ rInf.DrawBackBrush( *this );
+ const OUString aText(CH_TXT_ATR_SUBST_FIELDEND);
+ rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
+ }
+ else if (rInf.OnWin() && TextFrameIndex(1) == rInf.GetLen()
+ && CH_TXT_ATR_FIELDSTART == rInf.GetText()[sal_Int32(rInf.GetIdx())])
+ {
+ assert(false); // this is some debugging only code
+ rInf.DrawBackBrush( *this );
+ const OUString aText(CH_TXT_ATR_SUBST_FIELDSTART);
+ rInf.DrawText(aText, *this, TextFrameIndex(0), TextFrameIndex(aText.getLength()));
+ }
+ else if( GetLen() )
+ {
+ rInf.DrawBackBrush( *this );
+ rInf.DrawBorder( *this );
+
+ rInf.DrawCSDFHighlighting(*this);
+
+ // do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ auto const* pWrongList = rInf.GetpWrongList();
+ auto const* pGrammarCheckList = rInf.GetGrammarCheckList();
+ auto const* pSmarttags = rInf.GetSmartTags();
+
+ const bool bWrong = nullptr != pWrongList;
+ const bool bGrammarCheck = nullptr != pGrammarCheckList;
+ const bool bSmartTags = nullptr != pSmarttags;
+
+ if ( bWrong || bSmartTags || bGrammarCheck )
+ rInf.DrawMarkedText( *this, rInf.GetLen(), bWrong, bSmartTags, bGrammarCheck );
+ else
+ rInf.DrawText( *this, rInf.GetLen() );
+ }
+}
+
+bool SwTextPortion::GetExpText( const SwTextSizeInfo &, OUString & ) const
+{
+ return false;
+}
+
+// Responsible for the justified paragraph. They calculate the blank
+// count and the resulting added space.
+TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf,
+ TextFrameIndex& rCharCnt) const
+{
+ TextFrameIndex nCnt(0);
+ TextFrameIndex nPos(0);
+
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
+ if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars())
+ return TextFrameIndex(0);
+ }
+
+ if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
+ {
+ if (OUString ExpOut;
+ (!IsBlankPortion()
+ || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
+ && !InNumberGrp() && !IsCombinedPortion())
+ {
+ // OnWin() likes to return a blank instead of an empty string from
+ // time to time. We cannot use that here at all, however.
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+
+ nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
+ nPos = TextFrameIndex(aStr.getLength());
+ }
+ }
+ else if( !IsDropPortion() )
+ {
+ nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
+ nPos = GetLen();
+ }
+ rCharCnt = rCharCnt + nPos;
+ return nCnt;
+}
+
+SwTwips SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const
+{
+ TextFrameIndex nCnt(0);
+
+ if ( rInf.SnapToGrid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(rInf.GetTextFrame()->FindPageFrame()));
+ if (pGrid && GRID_LINES_CHARS == pGrid->GetGridType() && pGrid->IsSnapToChars())
+ return 0;
+ }
+
+ if ( InExpGrp() || PortionType::InputField == GetWhichPor() )
+ {
+ if (OUString ExpOut;
+ (!IsBlankPortion()
+ || (GetExpText(rInf, ExpOut) && OUStringChar(CH_BLANK) == ExpOut))
+ && !InNumberGrp() && !IsCombinedPortion())
+ {
+ // OnWin() likes to return a blank instead of an empty string from
+ // time to time. We cannot use that here at all, however.
+ bool bOldOnWin = rInf.OnWin();
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( false );
+
+ OUString aStr;
+ GetExpText( rInf, aStr );
+ const_cast<SwTextSizeInfo &>(rInf).SetOnWin( bOldOnWin );
+ if( nSpaceAdd > 0 )
+ nCnt = nCnt + lcl_AddSpace( rInf, &aStr, *this );
+ else
+ {
+ nSpaceAdd = -nSpaceAdd;
+ nCnt = TextFrameIndex(aStr.getLength());
+ }
+ }
+ }
+ else if( !IsDropPortion() )
+ {
+ if( nSpaceAdd > 0 )
+ nCnt = nCnt + lcl_AddSpace( rInf, nullptr, *this );
+ else
+ {
+ nSpaceAdd = -nSpaceAdd;
+ nCnt = GetLen();
+ SwLinePortion* pPor = GetNextPortion();
+
+ // we do not want an extra space in front of margin portions
+ if ( nCnt )
+ {
+ while ( pPor && !pPor->Width() && ! pPor->IsHolePortion() )
+ pPor = pPor->GetNextPortion();
+
+ if ( !pPor || pPor->InFixMargGrp() || pPor->IsHolePortion() )
+ --nCnt;
+ }
+ }
+ }
+
+ return sal_Int32(nCnt) * (nSpaceAdd > LONG_MAX/2 ? LONG_MAX/2 - nSpaceAdd : nSpaceAdd)
+ / SPACING_PRECISION_FACTOR;
+}
+
+void SwTextPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+SwTextInputFieldPortion::SwTextInputFieldPortion()
+{
+ SetWhichPor( PortionType::InputField );
+}
+
+bool SwTextInputFieldPortion::Format(SwTextFormatInfo &rTextFormatInfo)
+{
+ return SwTextPortion::Format(rTextFormatInfo);
+}
+
+void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if ( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::InputField );
+ SwTextSlot aPaintText( &rInf, this, true, true, OUString() );
+ SwTextPortion::Paint( rInf );
+ }
+ else
+ {
+ // highlight empty input field, elsewhere they are completely invisible for the user
+ SwRect aIntersect;
+ rInf.CalcRect(*this, &aIntersect);
+ const sal_uInt16 aAreaWidth = rInf.GetTextSize(OUString(' ')).Width();
+ aIntersect.Left(aIntersect.Left() - aAreaWidth/2);
+ aIntersect.Width(aAreaWidth);
+
+ if (aIntersect.HasArea()
+ && rInf.OnWin()
+ && rInf.GetOpt().IsFieldShadings()
+ && !rInf.GetOpt().IsPagePreview())
+ {
+ OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut());
+ pOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ pOut->SetFillColor(rInf.GetOpt().GetFieldShadingsColor());
+ pOut->SetLineColor();
+ pOut->DrawRect(aIntersect.SVRect());
+ pOut->Pop();
+ }
+ }
+}
+
+bool SwTextInputFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ sal_Int32 nIdx(rInf.GetIdx());
+ sal_Int32 nLen(GetLen());
+ if ( rInf.GetChar( rInf.GetIdx() ) == CH_TXT_ATR_INPUTFIELDSTART )
+ {
+ ++nIdx;
+ --nLen;
+ }
+ if (rInf.GetChar(rInf.GetIdx() + GetLen() - TextFrameIndex(1)) == CH_TXT_ATR_INPUTFIELDEND)
+ {
+ --nLen;
+ }
+ rText = rInf.GetText().copy( nIdx, std::min( nLen, rInf.GetText().getLength() - nIdx ) );
+
+ return true;
+}
+
+SwPosSize SwTextInputFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ SwTextSlot aFormatText( &rInf, this, true, false );
+ if (rInf.GetLen() == TextFrameIndex(0))
+ {
+ return SwPosSize( 0, 0 );
+ }
+
+ return rInf.GetTextSize();
+}
+
+SwHolePortion::SwHolePortion( const SwTextPortion &rPor )
+ : m_nBlankWidth( 0 )
+{
+ SetLen( TextFrameIndex(1) );
+ Height( rPor.Height() );
+ Width(0);
+ SetAscent( rPor.GetAscent() );
+ SetWhichPor( PortionType::Hole );
+}
+
+SwLinePortion *SwHolePortion::Compress() { return this; }
+
+// The GetTextSize() assumes that the own length is correct
+SwPosSize SwHolePortion::GetTextSize(const SwTextSizeInfo& rInf) const
+{
+ SwPosSize aSize = rInf.GetTextSize();
+ if (!GetJoinBorderWithPrev())
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace());
+ if (!GetJoinBorderWithNext())
+ aSize.Width(aSize.Width() + rInf.GetFont()->GetRightBorderSpace());
+
+ aSize.Height(aSize.Height() +
+ rInf.GetFont()->GetTopBorderSpace() +
+ rInf.GetFont()->GetBottomBorderSpace());
+
+ return aSize;
+}
+
+void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( !rInf.GetOut() )
+ return;
+
+ bool bPDFExport = rInf.GetVsh()->GetViewOptions()->IsPDFExport();
+
+ // #i16816# export stuff only needed for tagged pdf support
+ if (bPDFExport && !SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) )
+ return;
+
+ // #i68503# the hole must have no decoration for a consistent visual appearance
+ const SwFont* pOrigFont = rInf.GetFont();
+ std::unique_ptr<SwFont> pHoleFont;
+ std::optional<SwFontSave> oFontSave;
+ if( pOrigFont->GetUnderline() != LINESTYLE_NONE
+ || pOrigFont->GetOverline() != LINESTYLE_NONE
+ || pOrigFont->GetStrikeout() != STRIKEOUT_NONE )
+ {
+ pHoleFont.reset(new SwFont( *pOrigFont ));
+ pHoleFont->SetUnderline( LINESTYLE_NONE );
+ pHoleFont->SetOverline( LINESTYLE_NONE );
+ pHoleFont->SetStrikeout( STRIKEOUT_NONE );
+ oFontSave.emplace( rInf, pHoleFont.get() );
+ }
+
+ if (bPDFExport)
+ {
+ rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(1));
+ }
+ else
+ {
+ // tdf#43244: Paint spaces even at end of line,
+ // but only if this paint is not called for pdf export, to keep that pdf export intact
+ rInf.DrawText(*this, rInf.GetLen());
+ }
+
+ oFontSave.reset();
+ pHoleFont.reset();
+}
+
+bool SwHolePortion::Format( SwTextFormatInfo &rInf )
+{
+ return rInf.IsFull() || rInf.X() >= rInf.Width();
+}
+
+void SwHolePortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+void SwHolePortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText, TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHolePortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("blank-width"),
+ BAD_CAST(OString::number(m_nBlankWidth).getStr()));
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void SwFieldMarkPortion::Paint( const SwTextPaintInfo & /*rInf*/) const
+{
+ // These shouldn't be painted!
+ //SwTextPortion::Paint(rInf);
+}
+
+bool SwFieldMarkPortion::Format( SwTextFormatInfo & )
+{
+ Width(0);
+ return false;
+}
+
+void SwFieldFormCheckboxPortion::Paint( const SwTextPaintInfo& rInf ) const
+{
+ SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
+
+ IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
+
+ OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX,
+ "Where is my form field bookmark???");
+
+ if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
+ {
+ const ICheckboxFieldmark* pCheckboxFm = dynamic_cast<ICheckboxFieldmark const*>(pBM);
+ bool bChecked = pCheckboxFm && pCheckboxFm->IsChecked();
+ rInf.DrawCheckBox(*this, bChecked);
+ }
+}
+
+bool SwFieldFormCheckboxPortion::Format( SwTextFormatInfo & rInf )
+{
+ SwPosition const aPosition(rInf.GetTextFrame()->MapViewToModelPos(rInf.GetIdx()));
+ IFieldmark const*const pBM = rInf.GetTextFrame()->GetDoc().getIDocumentMarkAccess()->getFieldmarkAt(aPosition);
+ OSL_ENSURE(pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX, "Where is my form field bookmark???");
+ if (pBM && pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
+ {
+ // the width of the checkbox portion is the same as its height since it's a square
+ // and that size depends on the font size.
+ // See:
+ // http://document-foundation-mail-archive.969070.n3.nabble.com/Wrong-copy-paste-in-SwFieldFormCheckboxPortion-Format-td4269112.html
+ Width( rInf.GetTextHeight( ) );
+ Height( rInf.GetTextHeight( ) );
+ SetAscent( rInf.GetAscent( ) );
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx
new file mode 100644
index 0000000000..c826395272
--- /dev/null
+++ b/sw/source/core/text/portxt.hxx
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include "porlin.hxx"
+
+class SwTextGuess;
+
+/// This portion represents a part of the paragraph string.
+class SwTextPortion : public SwLinePortion
+{
+ void BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess );
+ void BreakUnderflow( SwTextFormatInfo &rInf );
+ bool Format_( SwTextFormatInfo &rInf );
+
+public:
+ SwTextPortion(){ SetWhichPor( PortionType::Text ); }
+ static SwTextPortion * CopyLinePortion(const SwLinePortion &rPortion);
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void FormatEOL( SwTextFormatInfo &rInf ) override;
+ virtual TextFrameIndex GetModelPositionForViewPoint(sal_uInt16 nOfst) const override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override;
+
+ // Counts the spaces for justified paragraph
+ TextFrameIndex GetSpaceCnt(const SwTextSizeInfo &rInf, TextFrameIndex& rCnt) const;
+
+ bool CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGuess );
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+};
+
+class SwTextInputFieldPortion : public SwTextPortion
+{
+public:
+ SwTextInputFieldPortion();
+
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const override;
+ virtual SwPosSize GetTextSize( const SwTextSizeInfo &rInfo ) const override;
+};
+
+class SwHolePortion : public SwLinePortion
+{
+ sal_uInt16 m_nBlankWidth;
+public:
+ explicit SwHolePortion( const SwTextPortion &rPor );
+ sal_uInt16 GetBlankWidth( ) const { return m_nBlankWidth; }
+ void SetBlankWidth( const sal_uInt16 nNew ) { m_nBlankWidth = nNew; }
+ virtual SwLinePortion *Compress() override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+ virtual SwPosSize GetTextSize(const SwTextSizeInfo& rInfo) const override;
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+
+ // Accessibility: pass information about this portion to the PortionHandler
+ virtual void HandlePortion( SwPortionHandler& rPH ) const override;
+
+ void dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const override;
+};
+
+class SwFieldMarkPortion : public SwTextPortion
+{
+ public:
+ SwFieldMarkPortion() : SwTextPortion()
+ {
+ SetWhichPor(PortionType::FieldMark);
+ }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+};
+
+class SwFieldFormCheckboxPortion : public SwTextPortion
+{
+public:
+ SwFieldFormCheckboxPortion() : SwTextPortion()
+ {
+ SetWhichPor(PortionType::FieldFormCheckbox);
+ }
+ virtual void Paint( const SwTextPaintInfo &rInf ) const override;
+ virtual bool Format( SwTextFormatInfo &rInf ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/possiz.hxx b/sw/source/core/text/possiz.hxx
new file mode 100644
index 0000000000..f66bd1988c
--- /dev/null
+++ b/sw/source/core/text/possiz.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <tools/gen.hxx>
+#include <swtypes.hxx>
+
+// Compared to the SV sizes SwPosSize is always positive
+class SwPosSize
+{
+ SwTwips m_nWidth;
+ SwTwips m_nHeight;
+public:
+ SwPosSize( const SwTwips nW = 0, const SwTwips nH = 0 )
+ : m_nWidth(nW)
+ , m_nHeight(nH)
+ {
+ }
+ explicit SwPosSize( const Size &rSize )
+ : m_nWidth(SwTwips(rSize.Width()))
+ , m_nHeight(SwTwips(rSize.Height()))
+ {
+ }
+#if defined(__COVERITY__)
+ virtual ~SwPosSize() COVERITY_NOEXCEPT_FALSE {}
+#else
+ virtual ~SwPosSize() {}
+#endif
+ SwPosSize(SwPosSize const &) = default;
+ SwPosSize(SwPosSize &&) = default;
+ SwPosSize & operator =(SwPosSize const &) = default;
+ SwPosSize & operator =(SwPosSize &&) = default;
+ SwTwips Height() const { return m_nHeight; }
+ virtual void Height(const SwTwips nNew, const bool = true) { m_nHeight = nNew; }
+ SwTwips Width() const { return m_nWidth; }
+ void Width( const SwTwips nNew ) { m_nWidth = nNew; }
+ Size SvLSize() const { return Size( m_nWidth, m_nHeight ); }
+ void SvLSize( const Size &rSize )
+ {
+ m_nWidth = SwTwips(rSize.Width());
+ m_nHeight = SwTwips(rSize.Height());
+ }
+ void SvXSize( const Size &rSize )
+ {
+ m_nHeight = SwTwips(rSize.Width());
+ m_nWidth = SwTwips(rSize.Height());
+ }
+ SwPosSize& operator=( const Size &rSize )
+ {
+ m_nWidth = SwTwips(rSize.Width());
+ m_nHeight = SwTwips(rSize.Height());
+ return *this;
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
new file mode 100644
index 0000000000..0e89b7f75a
--- /dev/null
+++ b/sw/source/core/text/redlnitr.cxx
@@ -0,0 +1,1201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <hintids.hxx>
+#include <o3tl/safeint.hxx>
+#include <svl/whiter.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <scriptinfo.hxx>
+#include <swmodule.hxx>
+#include <redline.hxx>
+#include <txatbase.hxx>
+#include <docary.hxx>
+#include "itratr.hxx"
+#include <ndtxt.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <IDocumentMarkAccess.hxx>
+#include <IMark.hxx>
+#include <bookmark.hxx>
+#include <rootfrm.hxx>
+#include <breakit.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/settings.hxx>
+#include <txtfrm.hxx>
+#include <ftnfrm.hxx>
+#include <vcl/svapp.hxx>
+#include "redlnitr.hxx"
+#include <extinput.hxx>
+#include <fmtpdsc.hxx>
+#include <editeng/charhiddenitem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/formatbreakitem.hxx>
+#include <editeng/udlnitem.hxx>
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class HideIterator
+{
+private:
+ IDocumentRedlineAccess const& m_rIDRA;
+ IDocumentMarkAccess const& m_rIDMA;
+ bool const m_isHideRedlines;
+ sw::FieldmarkMode const m_eFieldmarkMode;
+ bool const m_isHideParagraphBreaks;
+ SwPosition const m_Start;
+ /// next redline
+ SwRedlineTable::size_type m_RedlineIndex;
+ /// next fieldmark
+ std::pair<sw::mark::IFieldmark const*, std::optional<SwPosition>> m_Fieldmark;
+ std::optional<SwPosition> m_oNextFieldmarkHide;
+ /// previous paragraph break - because m_pStartPos/EndPos are non-owning
+ std::optional<std::pair<SwPosition, SwPosition>> m_oParagraphBreak;
+ /// current start/end pair
+ SwPosition const* m_pStartPos;
+ SwPosition const* m_pEndPos;
+
+public:
+ SwPosition const* GetStartPos() const { return m_pStartPos; }
+ SwPosition const* GetEndPos() const { return m_pEndPos; }
+
+ HideIterator(SwTextNode & rTextNode,
+ bool const isHideRedlines, sw::FieldmarkMode const eMode,
+ sw::ParagraphBreakMode const ePBMode)
+ : m_rIDRA(rTextNode.getIDocumentRedlineAccess())
+ , m_rIDMA(*rTextNode.getIDocumentMarkAccess())
+ , m_isHideRedlines(isHideRedlines)
+ , m_eFieldmarkMode(eMode)
+ , m_isHideParagraphBreaks(ePBMode == sw::ParagraphBreakMode::Hidden)
+ , m_Start(rTextNode, 0)
+ , m_RedlineIndex(isHideRedlines ? m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any) : SwRedlineTable::npos)
+ , m_pStartPos(nullptr)
+ , m_pEndPos(&m_Start)
+ {
+ }
+
+ // delete redlines and fieldmarks can't overlap, due to sw::CalcBreaks()
+ // and no combining of adjacent redlines
+ // -> dummy chars are delete-redlined *iff* entire fieldmark is
+ // Note: caller is responsible for checking for immediately adjacent hides
+ bool Next()
+ {
+ SwPosition const* pNextRedlineHide(nullptr);
+ assert(m_pEndPos);
+ if (m_isHideRedlines)
+ {
+ // position on current or next redline
+ for (; m_RedlineIndex < m_rIDRA.GetRedlineTable().size(); ++m_RedlineIndex)
+ {
+ SwRangeRedline const*const pRed = m_rIDRA.GetRedlineTable()[m_RedlineIndex];
+
+ if (m_pEndPos->GetNodeIndex() < pRed->Start()->GetNodeIndex())
+ break;
+
+ if (pRed->GetType() != RedlineType::Delete)
+ continue;
+
+ auto [pStart, pEnd] = pRed->StartEnd(); // SwPosition*
+ if (*pStart == *pEnd)
+ { // only allowed while moving (either way?)
+// assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags()));
+ continue;
+ }
+ if (pStart->GetNode().IsTableNode())
+ {
+ assert(pEnd->GetNode() == m_Start.GetNode() && pEnd->GetContentIndex() == 0);
+ continue; // known pathology, ignore it
+ }
+ if (*m_pEndPos <= *pStart)
+ {
+ pNextRedlineHide = pStart;
+ break; // the next one
+ }
+ }
+ }
+
+ // position on current or next fieldmark
+ m_oNextFieldmarkHide.reset();
+ if (m_eFieldmarkMode != sw::FieldmarkMode::ShowBoth)
+ {
+ sal_Unicode const magic(m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
+ ? CH_TXT_ATR_FIELDSTART
+ : CH_TXT_ATR_FIELDSEP);
+ SwTextNode* pTextNode = m_pEndPos->GetNode().GetTextNode();
+ sal_Int32 const nPos = pTextNode ? pTextNode->GetText().indexOf(
+ magic, m_pEndPos->GetContentIndex()) : -1;
+ if (nPos != -1)
+ {
+ m_oNextFieldmarkHide.emplace(*pTextNode, nPos);
+ sw::mark::IFieldmark const*const pFieldmark(
+ m_eFieldmarkMode == sw::FieldmarkMode::ShowResult
+ ? m_rIDMA.getFieldmarkAt(*m_oNextFieldmarkHide)
+ : m_rIDMA.getInnerFieldmarkFor(*m_oNextFieldmarkHide));
+ assert(pFieldmark);
+ m_Fieldmark.first = pFieldmark;
+ // for cursor travelling, there should be 2 visible chars;
+ // whichever char is hidden, the cursor travelling needs to
+ // be adapted in any case to skip in some situation or other;
+ // always hide the CH_TXT_ATR_FIELDSEP for now
+ if (m_eFieldmarkMode == sw::FieldmarkMode::ShowResult)
+ {
+ m_Fieldmark.second.emplace(
+ sw::mark::FindFieldSep(*m_Fieldmark.first));
+ m_Fieldmark.second->AdjustContent(+1);
+ m_oNextFieldmarkHide->AdjustContent(+1); // skip start
+ }
+ else
+ {
+ m_Fieldmark.second.emplace(pFieldmark->GetMarkEnd());
+ m_Fieldmark.second->AdjustContent(-1);
+ }
+ }
+ }
+
+ // == can happen only if redline starts inside field command, and in
+ // that case redline will end before field separator
+ assert(!pNextRedlineHide || !m_oNextFieldmarkHide
+ || *pNextRedlineHide != *m_oNextFieldmarkHide
+ || *m_rIDRA.GetRedlineTable()[m_RedlineIndex]->End() < *m_Fieldmark.second);
+ if (pNextRedlineHide
+ && (!m_oNextFieldmarkHide || *pNextRedlineHide < *m_oNextFieldmarkHide))
+ {
+ SwRangeRedline const*const pRed(m_rIDRA.GetRedlineTable()[m_RedlineIndex]);
+ m_pStartPos = pRed->Start();
+ m_pEndPos = pRed->End();
+ ++m_RedlineIndex;
+ return true;
+ }
+ else if (m_oNextFieldmarkHide)
+ {
+ assert(!pNextRedlineHide || *m_oNextFieldmarkHide <= *pNextRedlineHide);
+ m_pStartPos = &*m_oNextFieldmarkHide;
+ m_pEndPos = &*m_Fieldmark.second;
+ return true;
+ }
+ else
+ {
+ assert(!pNextRedlineHide && !m_oNextFieldmarkHide);
+ auto const hasHiddenItem = [](auto const& rNode) {
+ auto const& rpSet(rNode.GetAttr(RES_PARATR_LIST_AUTOFMT).GetStyleHandle());
+ return rpSet ? rpSet->Get(RES_CHRATR_HIDDEN).GetValue() : false;
+ };
+ auto const hasBreakBefore = [](SwTextNode const& rNode) {
+ if (rNode.GetAttr(RES_PAGEDESC).GetPageDesc())
+ {
+ return true;
+ }
+ switch (rNode.GetAttr(RES_BREAK).GetBreak())
+ {
+ case SvxBreak::ColumnBefore:
+ case SvxBreak::ColumnBoth:
+ case SvxBreak::PageBefore:
+ case SvxBreak::PageBoth:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ };
+ auto const hasBreakAfter = [](SwTextNode const& rNode) {
+ switch (rNode.GetAttr(RES_BREAK).GetBreak())
+ {
+ case SvxBreak::ColumnAfter:
+ case SvxBreak::ColumnBoth:
+ case SvxBreak::PageAfter:
+ case SvxBreak::PageBoth:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ };
+ if (m_isHideParagraphBreaks
+ && m_pEndPos->GetNode().IsTextNode() // ooo27109-1.sxw
+ // only merge if next node is also text node
+ && m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->IsTextNode()
+ && hasHiddenItem(*m_pEndPos->GetNode().GetTextNode())
+ // no merge if there's a page break on any node
+ && !hasBreakBefore(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode())
+ // first node, see SwTextFrame::GetBreak()
+ && !hasBreakAfter(*m_Start.GetNode().GetTextNode()))
+ {
+ m_oParagraphBreak.emplace(
+ SwPosition(*m_pEndPos->GetNode().GetTextNode(), m_pEndPos->GetNode().GetTextNode()->Len()),
+ SwPosition(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode(), 0));
+ m_pStartPos = &m_oParagraphBreak->first;
+ m_pEndPos = &m_oParagraphBreak->second;
+ return true;
+ }
+ else // nothing
+ {
+ m_pStartPos = nullptr;
+ m_pEndPos = nullptr;
+ return false;
+ }
+ }
+ }
+};
+
+}
+
+namespace sw {
+
+std::unique_ptr<sw::MergedPara>
+CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode,
+ FrameMode const eMode)
+{
+ if (!rFrame.getRootFrame()->HasMergedParas())
+ {
+ return nullptr;
+ }
+ bool bHaveRedlines(false);
+ std::vector<SwTextNode *> nodes{ &rTextNode };
+ std::vector<SwTableNode *> tables;
+ std::vector<SwSectionNode *> sections;
+ std::vector<sw::Extent> extents;
+ OUStringBuffer mergedText;
+ SwTextNode * pParaPropsNode(nullptr);
+ SwTextNode * pNode(&rTextNode);
+ sal_Int32 nLastEnd(0);
+ for (auto iter = HideIterator(rTextNode,
+ rFrame.getRootFrame()->IsHideRedlines(),
+ rFrame.getRootFrame()->GetFieldmarkMode(),
+ rFrame.getRootFrame()->GetParagraphBreakMode());
+ iter.Next(); )
+ {
+ SwPosition const*const pStart(iter.GetStartPos());
+ SwPosition const*const pEnd(iter.GetEndPos());
+ bHaveRedlines = true;
+ assert(pNode != &rTextNode || &pStart->GetNode() == &rTextNode); // detect calls with wrong start node
+ if (pStart->GetContentIndex() != nLastEnd) // not 0 so we eliminate adjacent deletes
+ {
+ extents.emplace_back(pNode, nLastEnd, pStart->GetContentIndex());
+ mergedText.append(pNode->GetText().subView(nLastEnd, pStart->GetContentIndex() - nLastEnd));
+ }
+ if (&pEnd->GetNode() != pNode)
+ {
+ if (pNode == &rTextNode)
+ {
+ pNode->SetRedlineMergeFlag(SwNode::Merge::First);
+ } // else: was already set before
+ int nLevel(0);
+ for (SwNodeOffset j = pNode->GetIndex() + 1; j < pEnd->GetNodeIndex(); ++j)
+ {
+ SwNode *const pTmp(pNode->GetNodes()[j]);
+ if (nLevel == 0)
+ {
+ if (pTmp->IsTextNode())
+ {
+ nodes.push_back(pTmp->GetTextNode());
+ }
+ else if (pTmp->IsTableNode())
+ {
+ tables.push_back(pTmp->GetTableNode());
+ }
+ else if (pTmp->IsSectionNode())
+ {
+ sections.push_back(pTmp->GetSectionNode());
+ }
+ }
+ if (pTmp->IsStartNode())
+ {
+ ++nLevel;
+ }
+ else if (pTmp->IsEndNode())
+ {
+ --nLevel;
+ }
+ pTmp->SetRedlineMergeFlag(SwNode::Merge::Hidden);
+ }
+ // note: in DelLastPara() case, the end node is not actually merged
+ // and is likely a SwTableNode!
+ if (!pEnd->GetNode().IsTextNode())
+ {
+ assert(pEnd->GetNode() != pStart->GetNode());
+ // must set pNode too because it will mark the last node
+ pNode = nodes.back();
+ assert(pNode == pNode->GetNodes()[pEnd->GetNodeIndex() - 1]);
+ if (pNode != &rTextNode)
+ { // something might depend on last merged one being NonFirst?
+ pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ }
+ nLastEnd = pNode->Len();
+ }
+ else
+ {
+ pNode = pEnd->GetNode().GetTextNode();
+ nodes.push_back(pNode);
+ pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ nLastEnd = pEnd->GetContentIndex();
+ }
+ }
+ else
+ {
+ nLastEnd = pEnd->GetContentIndex();
+ }
+ }
+ if (pNode == &rTextNode)
+ {
+ if (rTextNode.GetRedlineMergeFlag() != SwNode::Merge::None)
+ {
+ rTextNode.SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ }
+ // Reset flag of the following text node since we know it's not merged;
+ // also any table/sections in between.
+ // * the following SwTextNode is in same nodes section as pNode (nLevel=0)
+ // * the start nodes that don't have a SwTextNode before them
+ // on their level, and their corresponding end nodes
+ // * the first SwTextNode inside each start node of the previous point
+ // Other (non-first) SwTextNodes in nested sections shouldn't be reset!
+ int nLevel(0);
+ for (SwNodeOffset j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j)
+ {
+ SwNode *const pTmp(pNode->GetNodes()[j]);
+ if (!pTmp->IsCreateFrameWhenHidingRedlines())
+ { // clear stale flag caused by editing with redlines shown
+ pTmp->SetRedlineMergeFlag(SwNode::Merge::None);
+ }
+ if (pTmp->IsStartNode())
+ {
+ ++nLevel;
+ }
+ else if (pTmp->IsEndNode())
+ {
+ if (nLevel == 0)
+ {
+ break; // there is no following text node; avoid leaving section
+ }
+ --nLevel;
+ }
+ else if (pTmp->IsTextNode())
+ {
+ if (nLevel == 0)
+ {
+ break; // done
+ }
+ else
+ { // skip everything other than 1st text node in section!
+ j = pTmp->EndOfSectionIndex() - 1; // will be incremented again
+ }
+ }
+ }
+ if (!bHaveRedlines)
+ {
+ if (rTextNode.IsInList() && !rTextNode.GetNum(rFrame.getRootFrame()))
+ {
+ rTextNode.AddToListRLHidden(); // try to add it...
+ }
+ return nullptr;
+ }
+ if (nLastEnd != pNode->Len())
+ {
+ extents.emplace_back(pNode, nLastEnd, pNode->Len());
+ mergedText.append(pNode->GetText().subView(nLastEnd, pNode->Len() - nLastEnd));
+ }
+ if (extents.empty()) // there was no text anywhere
+ {
+ assert(mergedText.isEmpty());
+ pParaPropsNode = pNode; // if every node is empty, the last one wins
+ }
+ else
+ {
+ assert(!mergedText.isEmpty());
+ pParaPropsNode = extents.begin()->pNode; // para props from first node that isn't empty
+ }
+// pParaPropsNode = &rTextNode; // well, actually...
+ // keep lists up to date with visible nodes
+ if (pParaPropsNode->IsInList() && !pParaPropsNode->GetNum(rFrame.getRootFrame()))
+ {
+ pParaPropsNode->AddToListRLHidden(); // try to add it...
+ }
+ for (auto const pTextNode : nodes)
+ {
+ if (pTextNode != pParaPropsNode)
+ {
+ pTextNode->RemoveFromListRLHidden();
+ }
+ }
+ if (eMode == FrameMode::Existing)
+ {
+ // remove existing footnote frames for first node;
+ // for non-first nodes with own frames, DelFrames will remove all
+ // (could possibly call lcl_ChangeFootnoteRef, not sure if worth it)
+ // note: must be done *before* changing listeners!
+ // for non-first nodes that are already merged with this frame,
+ // need to remove here too, otherwise footnotes can be removed only
+ // by lucky accident, e.g. TruncLines().
+ auto itExtent(extents.begin());
+ for (auto const pTextNode : nodes)
+ {
+ sal_Int32 nLast(0);
+ std::vector<std::pair<sal_Int32, sal_Int32>> hidden;
+ for ( ; itExtent != extents.end(); ++itExtent)
+ {
+ if (itExtent->pNode != pTextNode)
+ {
+ break;
+ }
+ if (itExtent->nStart != 0)
+ {
+ assert(itExtent->nStart != nLast);
+ hidden.emplace_back(nLast, itExtent->nStart);
+ }
+ nLast = itExtent->nEnd;
+ }
+ if (nLast != pTextNode->Len())
+ {
+ hidden.emplace_back(nLast, pTextNode->Len());
+ }
+ sw::RemoveFootnotesForNode(*rFrame.getRootFrame(), *pTextNode, &hidden);
+ }
+ // unfortunately DelFrames() must be done before StartListening too,
+ // otherwise footnotes cannot be deleted by SwTextFootnote::DelFrames!
+ auto const end(--nodes.rend());
+ for (auto iter = nodes.rbegin(); iter != end; ++iter)
+ {
+ (**iter).DelFrames(rFrame.getRootFrame());
+ }
+ // also delete tables & sections here; not necessary, but convenient
+ for (auto const pTableNode : tables)
+ {
+ pTableNode->DelFrames(rFrame.getRootFrame());
+ }
+ for (auto const pSectionNode : sections)
+ {
+ pSectionNode->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/);
+ }
+ }
+ auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents),
+ mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode,
+ nodes.back()));
+ for (SwTextNode * pTmp : nodes)
+ {
+ pRet->listener.StartListening(pTmp);
+ }
+ rFrame.EndListeningAll();
+ return pRet;
+}
+
+} // namespace sw
+
+void SwAttrIter::InitFontAndAttrHandler(
+ SwTextNode const& rPropsNode,
+ SwTextNode const& rTextNode,
+ std::u16string_view aText,
+ bool const*const pbVertLayout,
+ bool const*const pbVertLayoutLRBT)
+{
+ // Build a font matching the default paragraph style:
+ SwFontAccess aFontAccess( &rPropsNode.GetAnyFormatColl(), m_pViewShell );
+ // It is possible that Init is called more than once, e.g., in a
+ // SwTextFrame::FormatOnceMore situation or (since sw_redlinehide)
+ // from SwAttrIter::Seek(); in the latter case SwTextSizeInfo::m_pFnt
+ // is an alias of m_pFont so it must not be deleted!
+ if (m_pFont)
+ {
+ *m_pFont = aFontAccess.Get()->GetFont();
+ }
+ else
+ {
+ m_pFont = new SwFont( aFontAccess.Get()->GetFont() );
+ }
+
+ // set font to vertical if frame layout is vertical
+ // if it's a re-init, the vert flag never changes
+ bool bVertLayoutLRBT = false;
+ if (pbVertLayoutLRBT)
+ bVertLayoutLRBT = *pbVertLayoutLRBT;
+ if (pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout())
+ {
+ m_pFont->SetVertical(m_pFont->GetOrientation(), true, bVertLayoutLRBT);
+ }
+
+ // Initialize the default attribute of the attribute handler
+ // based on the attribute array cached together with the font.
+ // If any further attributes for the paragraph are given in pAttrSet
+ // consider them during construction of the default array, and apply
+ // them to the font
+ m_aAttrHandler.Init(aFontAccess.Get()->GetDefault(), rTextNode.GetpSwAttrSet(),
+ *rTextNode.getIDocumentSettingAccess(), m_pViewShell, *m_pFont,
+ pbVertLayout ? *pbVertLayout : m_aAttrHandler.IsVertLayout(),
+ bVertLayoutLRBT );
+
+ m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
+
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ m_pFont->SetActual( m_pScriptInfo->WhichFont(TextFrameIndex(0)) );
+
+ TextFrameIndex nChg(0);
+ size_t nCnt = 0;
+
+ do
+ {
+ if ( nCnt >= m_pScriptInfo->CountScriptChg() )
+ break;
+ nChg = m_pScriptInfo->GetScriptChg( nCnt );
+ SwFontScript nTmp = SW_SCRIPTS;
+ switch ( m_pScriptInfo->GetScriptType( nCnt++ ) ) {
+ case i18n::ScriptType::ASIAN :
+ if( !m_aFontCacheIds[SwFontScript::CJK] ) nTmp = SwFontScript::CJK;
+ break;
+ case i18n::ScriptType::COMPLEX :
+ if( !m_aFontCacheIds[SwFontScript::CTL] ) nTmp = SwFontScript::CTL;
+ break;
+ default:
+ if( !m_aFontCacheIds[SwFontScript::Latin ] ) nTmp = SwFontScript::Latin;
+ }
+ if( nTmp < SW_SCRIPTS )
+ {
+ m_pFont->CheckFontCacheId( m_pViewShell, nTmp );
+ m_pFont->GetFontCacheId( m_aFontCacheIds[ nTmp ], m_aFontIdx[ nTmp ], nTmp );
+ }
+ }
+ while (nChg < TextFrameIndex(aText.size()));
+}
+
+void SwAttrIter::CtorInitAttrIter(SwTextNode & rTextNode,
+ SwScriptInfo & rScriptInfo, SwTextFrame const*const pFrame)
+{
+ // during HTML-Import it can happen, that no layout exists
+ SwRootFrame* pRootFrame = rTextNode.getIDocumentLayoutAccess().GetCurrentLayout();
+ m_pViewShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
+
+ m_pScriptInfo = &rScriptInfo;
+
+ // set font to vertical if frame layout is vertical
+ bool bVertLayout = false;
+ bool bVertLayoutLRBT = false;
+ bool bRTL = false;
+ if ( pFrame )
+ {
+ if ( pFrame->IsVertical() )
+ {
+ bVertLayout = true;
+ }
+ if (pFrame->IsVertLRBT())
+ {
+ bVertLayoutLRBT = true;
+ }
+ bRTL = pFrame->IsRightToLeft();
+ m_pMergedPara = pFrame->GetMergedPara();
+ }
+
+ // determine script changes if not already done for current paragraph
+ assert(m_pScriptInfo);
+ if (m_pScriptInfo->GetInvalidityA() != TextFrameIndex(COMPLETE_STRING))
+ m_pScriptInfo->InitScriptInfo(rTextNode, m_pMergedPara, bRTL);
+
+ InitFontAndAttrHandler(
+ m_pMergedPara ? *m_pMergedPara->pParaPropsNode : rTextNode,
+ rTextNode,
+ m_pMergedPara ? m_pMergedPara->mergedText : rTextNode.GetText(),
+ & bVertLayout,
+ & bVertLayoutLRBT);
+
+ m_nStartIndex = m_nEndIndex = m_nPosition = m_nChgCnt = 0;
+ m_nPropFont = 0;
+ SwDoc& rDoc = rTextNode.GetDoc();
+ const IDocumentRedlineAccess& rIDRA = rTextNode.getIDocumentRedlineAccess();
+
+ // sw_redlinehide: this is a Ring - pExtInp is the first PaM that's inside
+ // the node. It's not clear whether there can be more than 1 PaM in the
+ // Ring, and this code doesn't handle that case; neither did the old code.
+ const SwExtTextInput* pExtInp = rDoc.GetExtTextInput( rTextNode );
+ if (!pExtInp && m_pMergedPara)
+ {
+ SwTextNode const* pNode(&rTextNode);
+ for (auto const& rExtent : m_pMergedPara->extents)
+ {
+ if (rExtent.pNode != pNode)
+ {
+ pNode = rExtent.pNode;
+ pExtInp = rDoc.GetExtTextInput(*pNode);
+ if (pExtInp)
+ break;
+ }
+ }
+ }
+ const bool bShow = IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags())
+ && pRootFrame && !pRootFrame->IsHideRedlines();
+ if (!(pExtInp || m_pMergedPara || bShow))
+ return;
+
+ SwRedlineTable::size_type nRedlPos = rIDRA.GetRedlinePos( rTextNode, RedlineType::Any );
+ if (SwRedlineTable::npos == nRedlPos && m_pMergedPara)
+ {
+ SwTextNode const* pNode(&rTextNode);
+ for (auto const& rExtent : m_pMergedPara->extents)
+ { // note: have to search because extents based only on Delete
+ if (rExtent.pNode != pNode)
+ {
+ pNode = rExtent.pNode;
+ nRedlPos = rIDRA.GetRedlinePos(*pNode, RedlineType::Any);
+ if (SwRedlineTable::npos != nRedlPos)
+ break;
+ }
+ }
+ // TODO this is true initially but after delete ops it may be false... need to delete m_pMerged somewhere?
+ // assert(SwRedlineTable::npos != nRedlPos);
+ // false now with fieldmarks
+ assert(!pRootFrame
+ || pRootFrame->GetFieldmarkMode() != sw::FieldmarkMode::ShowBoth
+ || SwRedlineTable::npos != nRedlPos || m_pMergedPara->extents.size() <= 1);
+ }
+ if (!(pExtInp || m_pMergedPara || SwRedlineTable::npos != nRedlPos))
+ return;
+
+ const std::vector<ExtTextInputAttr> *pArr = nullptr;
+ if( pExtInp )
+ {
+ pArr = &pExtInp->GetAttrs();
+ Seek( TextFrameIndex(0) );
+ }
+
+ m_pRedline.reset(new SwRedlineItr( rTextNode, *m_pFont, m_aAttrHandler, nRedlPos,
+ (pRootFrame && pRootFrame->IsHideRedlines())
+ ? SwRedlineItr::Mode::Hide
+ : bShow
+ ? SwRedlineItr::Mode::Show
+ : SwRedlineItr::Mode::Ignore,
+ pArr, pExtInp ? pExtInp->Start() : nullptr));
+
+ if( m_pRedline->IsOn() )
+ ++m_nChgCnt;
+}
+
+// The Redline-Iterator
+// The following information/states exist in RedlineIterator:
+//
+// m_nFirst is the first index of RedlineTable, which overlaps with the paragraph.
+//
+// m_nAct is the currently active (if m_bOn is set) or the next possible index.
+// m_nStart and m_nEnd give you the borders of the object within the paragraph.
+//
+// If m_bOn is set, the font has been manipulated according to it.
+//
+// If m_nAct is set to SwRedlineTable::npos (via Reset()), then currently no
+// Redline is active, m_nStart and m_nEnd are invalid.
+SwRedlineItr::SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt,
+ SwAttrHandler& rAH, sal_Int32 nRed,
+ Mode const mode,
+ const std::vector<ExtTextInputAttr> *pArr,
+ SwPosition const*const pExtInputStart)
+ : m_rDoc( rTextNd.GetDoc() )
+ , m_rAttrHandler( rAH )
+ , m_nNdIdx( rTextNd.GetIndex() )
+ , m_nFirst( nRed )
+ , m_nAct( SwRedlineTable::npos )
+ , m_nStart( COMPLETE_STRING )
+ , m_nEnd( COMPLETE_STRING )
+ , m_bOn( false )
+ , m_eMode( mode )
+{
+ if( pArr )
+ {
+ assert(pExtInputStart);
+ m_pExt.reset( new SwExtend(*pArr, pExtInputStart->GetNodeIndex(),
+ pExtInputStart->GetContentIndex()) );
+ }
+ else
+ m_pExt = nullptr;
+ assert(m_pExt || m_eMode != Mode::Ignore); // only create if necessary
+ Seek(rFnt, m_nNdIdx, 0, COMPLETE_STRING);
+}
+
+SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE
+{
+ Clear( nullptr );
+ m_pExt.reset();
+}
+
+// The return value of SwRedlineItr::Seek tells you if the current font
+// has been manipulated by leaving (-1) or accessing (+1) of a section
+short SwRedlineItr::Seek(SwFont& rFnt,
+ SwNodeOffset const nNode, sal_Int32 const nNew, sal_Int32 const nOld)
+{
+ short nRet = 0;
+ if( ExtOn() )
+ return 0; // Abbreviation: if we're within an ExtendTextInputs
+ // there can't be other changes of attributes (not even by redlining)
+ if (m_eMode == Mode::Show)
+ {
+ if (m_bOn)
+ {
+ if (nNew >= m_nEnd)
+ {
+ --nRet;
+ Clear_( &rFnt ); // We go behind the current section
+ ++m_nAct; // and check the next one
+ }
+ else if (nNew < m_nStart)
+ {
+ --nRet;
+ Clear_( &rFnt ); // We go in front of the current section
+ if (m_nAct > m_nFirst)
+ m_nAct = m_nFirst; // the test has to run from the beginning
+ else
+ return nRet + EnterExtend(rFnt, nNode, nNew); // There's none prior to us
+ }
+ else
+ return nRet + EnterExtend(rFnt, nNode, nNew); // We stayed in the same section
+ }
+ if (SwRedlineTable::npos == m_nAct || nOld > nNew)
+ m_nAct = m_nFirst;
+
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ const SwRedlineTable& rTable = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable();
+
+ for ( ; m_nAct < rTable.size() ; ++m_nAct)
+ {
+ rTable[ m_nAct ]->CalcStartEnd(nNode, m_nStart, m_nEnd);
+
+ if (nNew < m_nEnd)
+ {
+ if (nNew >= m_nStart) // only possible candidate
+ {
+ m_bOn = true;
+ const SwRangeRedline *pRed = rTable[ m_nAct ];
+
+ if (m_pSet)
+ m_pSet->ClearItem();
+ else
+ {
+ SwAttrPool& rPool =
+ const_cast<SwDoc&>(m_rDoc).GetAttrPool();
+ m_pSet = std::make_unique<SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1>>(rPool);
+ }
+
+ if( 1 < pRed->GetStackCount() )
+ FillHints( pRed->GetAuthor( 1 ), pRed->GetType( 1 ) );
+ FillHints( pRed->GetAuthor(), pRed->GetType() );
+
+ SfxWhichIter aIter( *m_pSet );
+
+ // moved text: dark green with double underline or strikethrough
+ if ( pRed->IsMoved() )
+ {
+ m_pSet->Put(SvxColorItem( COL_GREEN, RES_CHRATR_COLOR ));
+ if (SfxItemState::SET == m_pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true))
+ m_pSet->Put(SvxCrossedOutItem( STRIKEOUT_DOUBLE, RES_CHRATR_CROSSEDOUT ));
+ else
+ m_pSet->Put(SvxUnderlineItem( LINESTYLE_DOUBLE, RES_CHRATR_UNDERLINE ));
+ }
+
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while( nWhich )
+ {
+ const SfxPoolItem* pItem;
+ if( ( nWhich < RES_CHRATR_END ) &&
+ ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) )
+ {
+ SwTextAttr* pAttr = MakeRedlineTextAttr(
+ const_cast<SwDoc&>(m_rDoc),
+ *const_cast<SfxPoolItem*>(pItem) );
+ pAttr->SetPriorityAttr( true );
+ m_Hints.push_back(pAttr);
+ m_rAttrHandler.PushAndChg( *pAttr, rFnt );
+ }
+ nWhich = aIter.NextWhich();
+ }
+
+ ++nRet;
+ }
+ break;
+ }
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ }
+ }
+ else if (m_eMode == Mode::Hide)
+ { // ... just iterate to update m_nAct for GetNextRedln();
+ // there is no need to care about formatting in this mode
+ if (m_nAct == SwRedlineTable::npos || nOld == COMPLETE_STRING)
+ { // reset, or move backward
+ m_nAct = m_nFirst;
+ }
+ for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
+ { // only Start matters in this mode
+ // Seeks until it finds a RL that starts at or behind the seek pos.
+ // - then update m_nStart/m_nEnd to the intersection of it with the
+ // current node (if any).
+ // The only way to skip to a different node is if there is a Delete
+ // RL, so if there is no intersection we'll never skip again.
+ // Note: here, assume that delete can't nest inside delete!
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct]);
+ SwPosition const*const pStart(pRedline->Start());
+ if (pRedline->GetType() == RedlineType::Delete
+ && (nNode < pStart->GetNodeIndex()
+ || (nNode == pStart->GetNodeIndex()
+ && nNew <= pStart->GetContentIndex())))
+ {
+ pRedline->CalcStartEnd(nNode, m_nStart, m_nEnd);
+ break;
+ }
+ m_nStart = COMPLETE_STRING;
+ m_nEnd = COMPLETE_STRING;
+ }
+ }
+ return nRet + EnterExtend(rFnt, nNode, nNew);
+}
+
+void SwRedlineItr::FillHints( std::size_t nAuthor, RedlineType eType )
+{
+ switch ( eType )
+ {
+ case RedlineType::Insert:
+ SW_MOD()->GetInsertAuthorAttr(nAuthor, *m_pSet);
+ break;
+ case RedlineType::Delete:
+ SW_MOD()->GetDeletedAuthorAttr(nAuthor, *m_pSet);
+ break;
+ case RedlineType::Format:
+ case RedlineType::FmtColl:
+ SW_MOD()->GetFormatAuthorAttr(nAuthor, *m_pSet);
+ break;
+ default:
+ break;
+ }
+}
+
+void SwRedlineItr::ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg )
+{
+ OSL_ENSURE( IsOn(), "SwRedlineItr::ChangeTextAttr: Off?" );
+
+ if (m_eMode != Mode::Show && !m_pExt)
+ return;
+
+ if( bChg )
+ {
+ if (m_pExt && m_pExt->IsOn())
+ m_rAttrHandler.PushAndChg( rHt, *m_pExt->GetFont() );
+ else
+ m_rAttrHandler.PushAndChg( rHt, *pFnt );
+ }
+ else
+ {
+ OSL_ENSURE( ! m_pExt || ! m_pExt->IsOn(), "Pop of attribute during opened extension" );
+ m_rAttrHandler.PopAndChg( rHt, *pFnt );
+ }
+}
+
+void SwRedlineItr::Clear_( SwFont* pFnt )
+{
+ OSL_ENSURE( m_bOn, "SwRedlineItr::Clear: Off?" );
+ m_bOn = false;
+ for (auto const& hint : m_Hints)
+ {
+ if( pFnt )
+ m_rAttrHandler.PopAndChg( *hint, *pFnt );
+ else
+ m_rAttrHandler.Pop( *hint );
+ SwTextAttr::Destroy(hint, const_cast<SwDoc&>(m_rDoc).GetAttrPool() );
+ }
+ m_Hints.clear();
+}
+
+/// Ignore mode: does nothing.
+/// Show mode: returns end of redline if currently in one, or start of next
+/// Hide mode: returns start of next redline in current node, plus (if it's a
+/// Delete) its end position and number of consecutive RLs
+std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>>
+SwRedlineItr::GetNextRedln(sal_Int32 nNext, SwTextNode const*const pNode,
+ SwRedlineTable::size_type & rAct)
+{
+ sal_Int32 nStart(m_nStart);
+ sal_Int32 nEnd(m_nEnd);
+ nNext = NextExtend(pNode->GetIndex(), nNext);
+ if (m_eMode == Mode::Ignore || SwRedlineTable::npos == m_nFirst)
+ return std::make_pair(nNext, std::make_pair(nullptr, 0));
+ if (SwRedlineTable::npos == rAct)
+ {
+ rAct = m_nFirst;
+ }
+ if (rAct != m_nAct)
+ {
+ while (rAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
+ pRedline->CalcStartEnd(pNode->GetIndex(), nStart, nEnd);
+ if (m_eMode != Mode::Hide
+ || pRedline->GetType() == RedlineType::Delete)
+ {
+ break;
+ }
+ ++rAct; // Hide mode: search a Delete RL
+ }
+ }
+ if (rAct == m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ return std::make_pair(nNext, std::make_pair(nullptr, 0)); // no Delete here
+ }
+ if (m_bOn || (m_eMode == Mode::Show && nStart == 0))
+ { // in Ignore mode, the end of redlines isn't relevant, except as returned in the second in the pair!
+ if (nEnd < nNext)
+ nNext = nEnd;
+ }
+ else if (nStart <= nNext)
+ {
+ if (m_eMode == Mode::Show)
+ {
+ nNext = nStart;
+ }
+ else
+ {
+ assert(m_eMode == Mode::Hide);
+ SwRangeRedline const* pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct]);
+ assert(pRedline->GetType() == RedlineType::Delete); //?
+ if (pRedline->GetType() == RedlineType::Delete)
+ {
+ nNext = nStart;
+ size_t nSkipped(1); // (consecutive) candidates to be skipped
+ while (rAct + nSkipped <
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())
+ {
+ SwRangeRedline const*const pNext =
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[rAct + nSkipped];
+ if (*pRedline->End() < *pNext->Start())
+ {
+ break; // done for now
+ }
+ else if (*pNext->Start() == *pRedline->End() &&
+ pNext->GetType() == RedlineType::Delete)
+ {
+ // consecutive delete - continue
+ pRedline = pNext;
+ }
+ ++nSkipped;
+ }
+ return std::make_pair(nNext, std::make_pair(pRedline, nSkipped));
+ }
+ }
+ }
+ return std::make_pair(nNext, std::make_pair(nullptr, 0));
+}
+
+bool SwRedlineItr::ChkSpecialUnderline_() const
+{
+ // If the underlining or the escapement is caused by redlining,
+ // we always apply the SpecialUnderlining, i.e. the underlining
+ // below the base line
+ for (SwTextAttr* pHint : m_Hints)
+ {
+ const sal_uInt16 nWhich = pHint->Which();
+ if( RES_CHRATR_UNDERLINE == nWhich ||
+ RES_CHRATR_ESCAPEMENT == nWhich )
+ return true;
+ }
+ return false;
+}
+
+bool SwRedlineItr::CheckLine(
+ SwNodeOffset const nStartNode, sal_Int32 const nChkStart,
+ SwNodeOffset const nEndNode, sal_Int32 nChkEnd, OUString& rRedlineText,
+ bool& bRedlineEnd, RedlineType& eRedlineEnd, size_t* pAuthorAtPos)
+{
+ // note: previously this would return true in the (!m_bShow && m_pExt)
+ // case, but surely that was a bug?
+ if (m_nFirst == SwRedlineTable::npos || m_eMode != Mode::Show)
+ return false;
+ if( nChkEnd == nChkStart && pAuthorAtPos == nullptr ) // empty lines look one char further
+ ++nChkEnd;
+ sal_Int32 nOldStart = m_nStart;
+ sal_Int32 nOldEnd = m_nEnd;
+ SwRedlineTable::size_type const nOldAct = m_nAct;
+ bool bRet = bRedlineEnd = false;
+ eRedlineEnd = RedlineType::None;
+
+ SwPosition const start(*m_rDoc.GetNodes()[nStartNode]->GetContentNode(), nChkStart);
+ SwPosition const end(*m_rDoc.GetNodes()[nEndNode]->GetContentNode(), nChkEnd);
+ SwRangeRedline const* pPrevRedline = nullptr;
+ bool isBreak(false);
+ for (m_nAct = m_nFirst; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++m_nAct)
+ {
+ SwRangeRedline const*const pRedline(
+ m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ] );
+ // collect text of the hidden redlines at the end of the line
+ bool isExtendText(false);
+ switch (ComparePosition(*pRedline->Start(), *pRedline->End(), start, end))
+ {
+ case SwComparePosition::Behind:
+ isBreak = true;
+ break;
+ case SwComparePosition::OverlapBehind:
+ case SwComparePosition::CollideStart:
+ case SwComparePosition::Outside:
+ case SwComparePosition::Equal:
+ // store redlining at line end (for line break formatting)
+ eRedlineEnd = pRedline->GetType();
+ bRedlineEnd = true;
+ isBreak = true;
+ if (pAuthorAtPos)
+ *pAuthorAtPos = pRedline->GetAuthor();
+ [[fallthrough]];
+ case SwComparePosition::OverlapBefore:
+ case SwComparePosition::CollideEnd:
+ case SwComparePosition::Inside:
+ {
+ bRet = true;
+ // start to collect text of invisible redlines for ChangesInMargin layout
+ if (rRedlineText.isEmpty() && !pRedline->IsVisible())
+ {
+ rRedlineText = const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true);
+ pPrevRedline = pRedline;
+ isExtendText = true;
+ }
+ // join the text of the next invisible redlines in the same position
+ // i.e. characters deleted by pressing backspace or delete
+ else if (pPrevRedline && !pRedline->IsVisible() &&
+ *pRedline->Start() == *pPrevRedline->Start() && *pRedline->End() == *pPrevRedline->End() )
+ {
+ OUString sExtendText(const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true));
+ if (!sExtendText.isEmpty())
+ {
+ if (rRedlineText.getLength() < 12)
+ {
+ // TODO: remove extra space from GetDescr(true),
+ // but show deletion of paragraph or line break
+ rRedlineText = rRedlineText +
+ const_cast<SwRangeRedline*>(pRedline)->GetDescr(/*bSimplified=*/true).subView(1);
+ }
+ else
+ rRedlineText = OUString::Concat(rRedlineText.subView(0, rRedlineText.getLength() - 3)) + "...";
+ }
+ isExtendText = true;
+ }
+ break;
+ }
+ case SwComparePosition::Before:
+ break; // -Werror=switch
+ }
+ if (isBreak && !isExtendText)
+ {
+ break;
+ }
+ }
+
+ m_nStart = nOldStart;
+ m_nEnd = nOldEnd;
+ m_nAct = nOldAct;
+ return bRet;
+}
+
+void SwExtend::ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr )
+{
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFnt.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFnt.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFnt.SetUnderline( LINESTYLE_DOTTED );
+
+ if ( nAttr & ExtTextInputAttr::RedText )
+ rFnt.SetColor( COL_RED );
+
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFnt.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFnt.SetBackColor( rStyleSettings.GetHighlightColor() );
+ }
+ if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ rFnt.SetGreyWave( true );
+}
+
+short SwExtend::Enter(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+{
+ OSL_ENSURE( !m_pFont, "SwExtend: Enter with Font" );
+ if (nNode != m_nNode)
+ return 0;
+ OSL_ENSURE( !Inside(), "SwExtend: Enter without Leave" );
+ m_nPos = nNew;
+ if( Inside() )
+ {
+ m_pFont.reset( new SwFont(rFnt) );
+ ActualizeFont( rFnt, m_rArr[m_nPos - m_nStart] );
+ return 1;
+ }
+ return 0;
+}
+
+bool SwExtend::Leave_(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+{
+ OSL_ENSURE(nNode == m_nNode && Inside(), "SwExtend: Leave without Enter");
+ if (nNode != m_nNode)
+ return true;
+ const ExtTextInputAttr nOldAttr = m_rArr[m_nPos - m_nStart];
+ m_nPos = nNew;
+ if( Inside() )
+ { // We stayed within the ExtendText-section
+ const ExtTextInputAttr nAttr = m_rArr[m_nPos - m_nStart];
+ if( nOldAttr != nAttr ) // Is there an (inner) change of attributes?
+ {
+ rFnt = *m_pFont;
+ ActualizeFont( rFnt, nAttr );
+ }
+ }
+ else
+ {
+ rFnt = *m_pFont;
+ m_pFont.reset();
+ return true;
+ }
+ return false;
+}
+
+sal_Int32 SwExtend::Next(SwNodeOffset const nNode, sal_Int32 nNext)
+{
+ if (nNode != m_nNode)
+ return nNext;
+ if (m_nPos < m_nStart)
+ {
+ if (nNext > m_nStart)
+ nNext = m_nStart;
+ }
+ else if (m_nPos < m_nEnd)
+ {
+ sal_Int32 nIdx = m_nPos - m_nStart;
+ const ExtTextInputAttr nAttr = m_rArr[ nIdx ];
+ while (o3tl::make_unsigned(++nIdx) < m_rArr.size() && nAttr == m_rArr[nIdx])
+ ; //nothing
+ nIdx = nIdx + m_nStart;
+ if( nNext > nIdx )
+ nNext = nIdx;
+ }
+ return nNext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/redlnitr.hxx b/sw/source/core/text/redlnitr.hxx
new file mode 100644
index 0000000000..5c30131264
--- /dev/null
+++ b/sw/source/core/text/redlnitr.hxx
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <docary.hxx>
+#include <swfont.hxx>
+
+#include <vcl/commandevent.hxx>
+
+#include <cstddef>
+#include <deque>
+#include <memory>
+#include <vector>
+
+class SwTextNode;
+class SwDoc;
+class SfxItemSet;
+class SwAttrHandler;
+
+class SwExtend
+{
+ std::unique_ptr<SwFont> m_pFont;
+ const std::vector<ExtTextInputAttr> &m_rArr;
+ /// position of start of SwExtTextInput
+ SwNodeOffset const m_nNode;
+ sal_Int32 const m_nStart;
+ /// current position (inside)
+ sal_Int32 m_nPos;
+ /// position of end of SwExtTextInput (in same node as start)
+ sal_Int32 const m_nEnd;
+ bool Leave_(SwFont& rFnt, SwNodeOffset nNode, sal_Int32 nNew);
+ bool Inside() const { return (m_nPos >= m_nStart && m_nPos < m_nEnd); }
+ static void ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr );
+public:
+ SwExtend(const std::vector<ExtTextInputAttr> &rArr,
+ SwNodeOffset const nNode, sal_Int32 const nStart)
+ : m_rArr(rArr)
+ , m_nNode(nNode)
+ , m_nStart(nStart)
+ , m_nPos(COMPLETE_STRING)
+ , m_nEnd(m_nStart + rArr.size())
+ {}
+ bool IsOn() const { return m_pFont != nullptr; }
+ void Reset() { m_pFont.reset(); m_nPos = COMPLETE_STRING; }
+ bool Leave(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+ { return m_pFont && Leave_(rFnt, nNode, nNew); }
+ short Enter(SwFont& rFnt, SwNodeOffset nNode, sal_Int32 nNew);
+ sal_Int32 Next(SwNodeOffset nNode, sal_Int32 nNext);
+ SwFont* GetFont() { return m_pFont.get(); }
+ void UpdateFont(SwFont &rFont) { ActualizeFont(rFont, m_rArr[m_nPos - m_nStart]); }
+};
+
+class SwRedlineItr
+{
+ std::deque<SwTextAttr *> m_Hints;
+ const SwDoc& m_rDoc;
+ SwAttrHandler& m_rAttrHandler;
+ std::unique_ptr<SfxItemSet> m_pSet;
+ std::unique_ptr<SwExtend> m_pExt;
+ // note: this isn't actually used in the merged-para (Hide) case
+ SwNodeOffset const m_nNdIdx;
+ SwRedlineTable::size_type const m_nFirst;
+ SwRedlineTable::size_type m_nAct;
+ sal_Int32 m_nStart;
+ sal_Int32 m_nEnd;
+ bool m_bOn;
+public:
+ enum class Mode { Show, Ignore, Hide };
+private:
+ Mode const m_eMode;
+
+ void Clear_( SwFont* pFnt );
+ bool ChkSpecialUnderline_() const;
+ void FillHints( std::size_t nAuthor, RedlineType eType );
+ short EnterExtend(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+ {
+ if (m_pExt) return m_pExt->Enter(rFnt, nNode, nNew);
+ return 0;
+ }
+ sal_Int32 NextExtend(SwNodeOffset const nNode, sal_Int32 const nNext) {
+ if (m_pExt) return m_pExt->Next(nNode, nNext);
+ return nNext;
+ }
+public:
+ SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt, SwAttrHandler& rAH,
+ sal_Int32 nRedlPos, Mode mode,
+ const std::vector<ExtTextInputAttr> *pArr = nullptr,
+ SwPosition const* pExtInputStart = nullptr);
+ ~SwRedlineItr() COVERITY_NOEXCEPT_FALSE;
+ SwRedlineTable::size_type GetAct() const { return m_nAct; }
+ bool IsOn() const { return m_bOn || (m_pExt && m_pExt->IsOn()); }
+ void Clear( SwFont* pFnt ) { if (m_bOn) Clear_( pFnt ); }
+ void ChangeTextAttr( SwFont* pFnt, SwTextAttr const &rHt, bool bChg );
+ short Seek(SwFont& rFnt, SwNodeOffset nNode, sal_Int32 nNew, sal_Int32 nOld);
+ void Reset() {
+ if (m_nAct != m_nFirst) m_nAct = SwRedlineTable::npos;
+ if (m_pExt) m_pExt->Reset();
+ }
+ std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> GetNextRedln(
+ sal_Int32 nNext, SwTextNode const* pNode, SwRedlineTable::size_type & rAct);
+ bool ChkSpecialUnderline() const
+ { return IsOn() && ChkSpecialUnderline_(); }
+ bool CheckLine(SwNodeOffset nStartNode, sal_Int32 nChkStart, SwNodeOffset nEndNode,
+ sal_Int32 nChkEnd, OUString& rRedlineText, bool& bRedlineEnd,
+ RedlineType& eRedlineEnd, size_t* pAuthorAtPos = nullptr);
+ bool LeaveExtend(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew)
+ { return m_pExt->Leave(rFnt, nNode, nNew); }
+ bool ExtOn() {
+ if (m_pExt) return m_pExt->IsOn();
+ return false;
+ }
+ void UpdateExtFont( SwFont &rFnt ) {
+ OSL_ENSURE( ExtOn(), "UpdateExtFont without ExtOn" );
+ m_pExt->UpdateFont( rFnt );
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtcache.cxx b/sw/source/core/text/txtcache.cxx
new file mode 100644
index 0000000000..188fa7bec6
--- /dev/null
+++ b/sw/source/core/text/txtcache.cxx
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "txtcache.hxx"
+#include <txtfrm.hxx>
+#include "porlay.hxx"
+
+#include <sfx2/viewsh.hxx>
+#include <osl/diagnose.h>
+#include <view.hxx>
+
+SwTextLine::SwTextLine( SwTextFrame const *pFrame, std::unique_ptr<SwParaPortion> pNew ) :
+ SwCacheObj( static_cast<void const *>(pFrame) ),
+ m_pLine( std::move(pNew) )
+{
+}
+
+SwTextLine::~SwTextLine()
+{
+}
+
+void SwTextLine::UpdateCachePos()
+{
+ // note: SwTextFrame lives longer than its SwTextLine, see ~SwTextFrame
+ assert(m_pOwner);
+ const_cast<SwTextFrame *>(static_cast<SwTextFrame const *>(m_pOwner))->SetCacheIdx(GetCachePos());
+}
+
+SwCacheObj *SwTextLineAccess::NewObj()
+{
+ return new SwTextLine( static_cast<SwTextFrame const *>(m_pOwner) );
+}
+
+SwParaPortion *SwTextLineAccess::GetPara()
+{
+ SwTextLine *pRet;
+ if ( m_pObj )
+ pRet = static_cast<SwTextLine*>(m_pObj);
+ else
+ {
+ pRet = static_cast<SwTextLine*>(Get(false));
+ const_cast<SwTextFrame *>(static_cast<SwTextFrame const *>(m_pOwner))->SetCacheIdx( pRet->GetCachePos() );
+ }
+ if ( !pRet->GetPara() )
+ pRet->SetPara( new SwParaPortion, true/*bDelete*/ );
+ return pRet->GetPara();
+}
+
+SwTextLineAccess::SwTextLineAccess( const SwTextFrame *pOwn ) :
+ SwCacheAccess( *SwTextFrame::GetTextCache(), pOwn, pOwn->GetCacheIdx() )
+{
+}
+
+bool SwTextLineAccess::IsAvailable() const
+{
+ return m_pObj && static_cast<SwTextLine*>(m_pObj)->GetPara();
+}
+
+bool SwTextFrame::HasPara_() const
+{
+ SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()->
+ Get( this, GetCacheIdx(), false ));
+ if ( pTextLine )
+ {
+ if ( pTextLine->GetPara() )
+ return true;
+ }
+ else
+ const_cast<SwTextFrame*>(this)->mnCacheIndex = USHRT_MAX;
+
+ return false;
+}
+
+SwParaPortion *SwTextFrame::GetPara()
+{
+ if ( GetCacheIdx() != USHRT_MAX )
+ {
+ SwTextLine *pLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()->
+ Get( this, GetCacheIdx(), false ));
+ if ( pLine )
+ return pLine->GetPara();
+ else
+ mnCacheIndex = USHRT_MAX;
+ }
+ return nullptr;
+}
+
+void SwTextFrame::ClearPara()
+{
+ OSL_ENSURE( !IsLocked(), "+SwTextFrame::ClearPara: this is locked." );
+ if ( !IsLocked() && GetCacheIdx() != USHRT_MAX )
+ {
+ SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()->
+ Get( this, GetCacheIdx(), false ));
+ if ( pTextLine )
+ {
+ pTextLine->SetPara( nullptr, true/*bDelete*/ );
+ }
+ else
+ mnCacheIndex = USHRT_MAX;
+ }
+}
+
+void SwTextFrame::RemoveFromCache()
+{
+ if (GetCacheIdx() != USHRT_MAX)
+ {
+ s_pTextCache->Delete(this, GetCacheIdx());
+ SetCacheIdx(USHRT_MAX);
+ }
+}
+
+void SwTextFrame::SetPara( SwParaPortion *pNew, bool bDelete )
+{
+ if ( GetCacheIdx() != USHRT_MAX )
+ {
+ // Only change the information, the CacheObj stays there
+ SwTextLine *pTextLine = static_cast<SwTextLine*>(SwTextFrame::GetTextCache()->
+ Get( this, GetCacheIdx(), false ));
+ if ( pTextLine )
+ {
+ pTextLine->SetPara( pNew, bDelete );
+ }
+ else
+ {
+ OSL_ENSURE( !pNew, "+SetPara: Losing SwParaPortion" );
+ mnCacheIndex = USHRT_MAX;
+ }
+ }
+ else if ( pNew )
+ { // Insert a new one
+ SwTextLine *pTextLine = new SwTextLine( this, std::unique_ptr<SwParaPortion>(pNew) );
+ if (SwTextFrame::GetTextCache()->Insert(pTextLine, false))
+ mnCacheIndex = pTextLine->GetCachePos();
+ else
+ {
+ OSL_FAIL( "+SetPara: InsertCache failed." );
+ }
+ }
+}
+
+/** Prevent the SwParaPortions of the *visible* paragraphs from being deleted;
+ they would just be recreated on the next paint.
+
+ Heuristic: 100 per view are visible
+
+ If the cache is too small, enlarge it to ensure there are sufficient free
+ entries for the layout so it doesn't have to throw away a node's
+ SwParaPortion when it starts formatting the next node.
+*/
+SwSaveSetLRUOfst::SwSaveSetLRUOfst()
+{
+ sal_uInt16 nVisibleShells(0);
+ for (auto pView = SfxViewShell::GetFirst(true, checkSfxViewShell<SwView>);
+ pView != nullptr;
+ pView = SfxViewShell::GetNext(*pView, true, checkSfxViewShell<SwView>))
+ {
+ // Apparently we are not interested here what document pView is for, but only in the
+ // total number of shells in the process?
+ ++nVisibleShells;
+ }
+
+ sal_uInt16 const nPreserved(100 * nVisibleShells);
+ SwCache & rCache(*SwTextFrame::GetTextCache());
+ if (rCache.GetCurMax() < nPreserved + 250)
+ {
+ rCache.IncreaseMax(nPreserved + 250 - rCache.GetCurMax());
+ }
+ rCache.SetLRUOfst(nPreserved);
+}
+
+SwSaveSetLRUOfst::~SwSaveSetLRUOfst()
+{
+ SwTextFrame::GetTextCache()->ResetLRUOfst();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtcache.hxx b/sw/source/core/text/txtcache.hxx
new file mode 100644
index 0000000000..ee18f14c3b
--- /dev/null
+++ b/sw/source/core/text/txtcache.hxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <swcache.hxx>
+#include "porlay.hxx"
+#include <memory>
+
+class SwTextFrame;
+
+class SwTextLine : public SwCacheObj
+{
+ std::unique_ptr<SwParaPortion> m_pLine;
+
+ virtual void UpdateCachePos() override;
+
+public:
+ SwTextLine(SwTextFrame const* pFrame, std::unique_ptr<SwParaPortion> pNew = nullptr);
+ virtual ~SwTextLine() override;
+
+ SwParaPortion* GetPara() { return m_pLine.get(); }
+ const SwParaPortion* GetPara() const { return m_pLine.get(); }
+
+ void SetPara(SwParaPortion* pNew, bool bDelete)
+ {
+ if (!bDelete)
+ {
+ // coverity[leaked_storage] - intentional, ownership transferred
+ (void)m_pLine.release();
+ }
+ m_pLine.reset(pNew);
+ }
+};
+
+class SwTextLineAccess : public SwCacheAccess
+{
+protected:
+ virtual SwCacheObj* NewObj() override;
+
+public:
+ explicit SwTextLineAccess(const SwTextFrame* pOwner);
+
+ SwParaPortion* GetPara();
+
+ bool IsAvailable() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtdrop.cxx b/sw/source/core/text/txtdrop.cxx
new file mode 100644
index 0000000000..afbe3b1470
--- /dev/null
+++ b/sw/source/core/text/txtdrop.cxx
@@ -0,0 +1,1093 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <vcl/svapp.hxx>
+#include <paratr.hxx>
+#include <txtfrm.hxx>
+#include <charfmt.hxx>
+#include <viewopt.hxx>
+#include <viewsh.hxx>
+#include "pordrop.hxx"
+#include "itrform2.hxx"
+#include "txtpaint.hxx"
+#include <breakit.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <editeng/langitem.hxx>
+#include <charatr.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <calbck.hxx>
+#include <doc.hxx>
+#include <IDocumentSettingAccess.hxx>
+
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star;
+
+/**
+ * Calculates if a drop caps portion intersects with a fly
+ * The width and height of the drop caps portion are passed as arguments,
+ * the position is calculated from the values in rInf
+ */
+static bool lcl_IsDropFlyInter( const SwTextFormatInfo &rInf,
+ sal_uInt16 nWidth, sal_uInt16 nHeight )
+{
+ const SwTextFly& rTextFly = rInf.GetTextFly();
+ if( rTextFly.IsOn() )
+ {
+ SwRect aRect( rInf.GetTextFrame()->getFrameArea().Pos(), Size( nWidth, nHeight) );
+ aRect.Pos() += rInf.GetTextFrame()->getFramePrintArea().Pos();
+ aRect.Pos().AdjustX(rInf.X() );
+ aRect.Pos().setY( rInf.Y() );
+ aRect = rTextFly.GetFrame( aRect );
+ return aRect.HasArea();
+ }
+
+ return false;
+}
+
+namespace {
+
+class SwDropSave
+{
+ SwTextPaintInfo* pInf;
+ sal_Int32 nIdx;
+ sal_Int32 nLen;
+ tools::Long nX;
+ tools::Long nY;
+
+public:
+ explicit SwDropSave( const SwTextPaintInfo &rInf );
+ ~SwDropSave();
+};
+
+}
+
+SwDropSave::SwDropSave( const SwTextPaintInfo &rInf ) :
+ pInf( const_cast<SwTextPaintInfo*>(&rInf) ), nIdx( rInf.GetIdx() ),
+ nLen( rInf.GetLen() ), nX( rInf.X() ), nY( rInf.Y() )
+{
+}
+
+SwDropSave::~SwDropSave()
+{
+ pInf->SetIdx(TextFrameIndex(nIdx));
+ pInf->SetLen(TextFrameIndex(nLen));
+ pInf->X( nX );
+ pInf->Y( nY );
+}
+
+/// SwDropPortionPart DTor
+SwDropPortionPart::~SwDropPortionPart()
+{
+ m_pFollow.reset();
+ m_pFnt.reset();
+}
+
+/// SwDropPortion CTor, DTor
+SwDropPortion::SwDropPortion( const sal_uInt16 nLineCnt,
+ const sal_uInt16 nDrpHeight,
+ const sal_uInt16 nDrpDescent,
+ const sal_uInt16 nDist )
+ : m_nLines( nLineCnt ),
+ m_nDropHeight(nDrpHeight),
+ m_nDropDescent(nDrpDescent),
+ m_nDistance(nDist),
+ m_nFix(0),
+ m_nY(0)
+{
+ SetWhichPor( PortionType::Drop );
+}
+
+SwDropPortion::~SwDropPortion()
+{
+ m_pPart.reset();
+}
+
+/// nWishLen = 0 indicates that we want a whole word
+sal_Int32 SwTextNode::GetDropLen( sal_Int32 nWishLen ) const
+{
+ sal_Int32 nEnd = GetText().getLength();
+ if( nWishLen && nWishLen < nEnd )
+ nEnd = nWishLen;
+
+ if (! nWishLen)
+ {
+ // find first word
+ const SwAttrSet& rAttrSet = GetSwAttrSet();
+ const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText( GetText(), 0 );
+
+ LanguageType eLanguage;
+
+ switch ( nTextScript )
+ {
+ case i18n::ScriptType::ASIAN :
+ eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
+ break;
+ case i18n::ScriptType::COMPLEX :
+ eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
+ break;
+ default :
+ eLanguage = rAttrSet.GetLanguage().GetLanguage();
+ break;
+ }
+
+ Boundary aBound =
+ g_pBreakIt->GetBreakIter()->getWordBoundary( GetText(), 0,
+ g_pBreakIt->GetLocale( eLanguage ), WordType::DICTIONARY_WORD, true );
+
+ nEnd = aBound.endPos;
+ }
+
+ sal_Int32 i = 0;
+ for( ; i < nEnd; ++i )
+ {
+ sal_Unicode const cChar = GetText()[i];
+ if( CH_TAB == cChar || CH_BREAK == cChar ||
+ (( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar )
+ && GetTextAttrForCharAt(i)) )
+ break;
+ }
+ return i;
+}
+
+/// nWishLen = 0 indicates that we want a whole word
+TextFrameIndex SwTextFrame::GetDropLen(TextFrameIndex const nWishLen) const
+{
+ TextFrameIndex nEnd(GetText().getLength());
+ if (nWishLen && nWishLen < nEnd)
+ nEnd = nWishLen;
+
+ if (! nWishLen)
+ {
+ // find first word
+ const SwAttrSet& rAttrSet = GetTextNodeForParaProps()->GetSwAttrSet();
+ const sal_uInt16 nTextScript = g_pBreakIt->GetRealScriptOfText(GetText(), 0);
+
+ LanguageType eLanguage;
+
+ switch ( nTextScript )
+ {
+ case i18n::ScriptType::ASIAN :
+ eLanguage = rAttrSet.GetCJKLanguage().GetLanguage();
+ break;
+ case i18n::ScriptType::COMPLEX :
+ eLanguage = rAttrSet.GetCTLLanguage().GetLanguage();
+ break;
+ default :
+ eLanguage = rAttrSet.GetLanguage().GetLanguage();
+ break;
+ }
+
+ Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ GetText(), 0, g_pBreakIt->GetLocale(eLanguage),
+ WordType::DICTIONARY_WORD, true );
+
+ nEnd = TextFrameIndex(aBound.endPos);
+ }
+
+ TextFrameIndex i(0);
+ for ( ; i < nEnd; ++i)
+ {
+ sal_Unicode const cChar = GetText()[sal_Int32(i)];
+ if (CH_TAB == cChar || CH_BREAK == cChar ||
+ CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
+ {
+#ifndef NDEBUG
+ if (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
+ {
+ std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(i));
+ assert(pos.first->GetTextAttrForCharAt(pos.second) != nullptr);
+ }
+#endif
+ break;
+ }
+ }
+ return i;
+}
+
+/**
+ * If a dropcap is found the return value is true otherwise false. The
+ * drop cap sizes passed back by reference are font height, drop height
+ * and drop descent.
+ */
+bool SwTextNode::GetDropSize(int& rFontHeight, int& rDropHeight, int& rDropDescent) const
+{
+ rFontHeight = 0;
+ rDropHeight = 0;
+ rDropDescent =0;
+
+ const SwAttrSet& rSet = GetSwAttrSet();
+ const SwFormatDrop& rDrop = rSet.GetDrop();
+
+ // Return (0,0) if there is no drop cap at this paragraph
+ if( 1 >= rDrop.GetLines() ||
+ ( !rDrop.GetChars() && !rDrop.GetWholeWord() ) )
+ {
+ return false;
+ }
+
+ // get text frame
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
+ for( SwTextFrame* pLastFrame = aIter.First(); pLastFrame; pLastFrame = aIter.Next() )
+ {
+ // Only (master-) text frames can have a drop cap.
+ if (!pLastFrame->IsFollow() &&
+ pLastFrame->GetTextNodeForFirstText() == this)
+ {
+
+ if( !pLastFrame->HasPara() )
+ pLastFrame->GetFormatted();
+
+ if ( !pLastFrame->IsEmpty() )
+ {
+ const SwParaPortion* pPara = pLastFrame->GetPara();
+ OSL_ENSURE( pPara, "GetDropSize could not find the ParaPortion, I'll guess the drop cap size" );
+
+ if ( pPara )
+ {
+ const SwLinePortion* pFirstPor = pPara->GetFirstPortion();
+ if (pFirstPor && pFirstPor->IsDropPortion())
+ {
+ const SwDropPortion* pDrop = static_cast<const SwDropPortion*>(pFirstPor);
+ rDropHeight = pDrop->GetDropHeight();
+ rDropDescent = pDrop->GetDropDescent();
+ if (const SwFont *pFont = pDrop->GetFnt())
+ rFontHeight = pFont->GetSize(pFont->GetActual()).Height();
+ else
+ {
+ const SvxFontHeightItem& rItem = rSet.Get(RES_CHRATR_FONTSIZE);
+ rFontHeight = rItem.GetHeight();
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ if (rFontHeight==0 && rDropHeight==0 && rDropDescent==0)
+ {
+ const sal_uInt16 nLines = rDrop.GetLines();
+
+ const SvxFontHeightItem& rItem = rSet.Get( RES_CHRATR_FONTSIZE );
+ rFontHeight = rItem.GetHeight();
+ rDropHeight = nLines * rFontHeight;
+ rDropDescent = rFontHeight / 5;
+ return false;
+ }
+
+ return true;
+}
+
+/// Manipulate the width, otherwise the chars are being stretched
+void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const
+{
+ OSL_ENSURE( m_nDropHeight && m_pPart && m_nLines != 1, "Drop Portion painted twice" );
+
+ const SwDropPortionPart* pCurrPart = GetPart();
+ const TextFrameIndex nOldLen = GetLen();
+ const sal_uInt16 nOldWidth = Width();
+ const sal_uInt16 nOldAscent = GetAscent();
+
+ const SwTwips nBasePosY = rInf.Y();
+ const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY + m_nY );
+ const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent + m_nY );
+ SwDropSave aSave( rInf );
+ // for text inside drop portions we let vcl handle the text directions
+ SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
+ aLayoutModeModifier.SetAuto();
+
+ while ( pCurrPart )
+ {
+ const_cast<SwDropPortion*>(this)->SetLen( pCurrPart->GetLen() );
+ const_cast<SwDropPortion*>(this)->Width( pCurrPart->GetWidth() );
+ const_cast<SwTextPaintInfo&>(rInf).SetLen( pCurrPart->GetLen() );
+ SwFontSave aFontSave( rInf, &pCurrPart->GetFont() );
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
+
+ if ( rInf.OnWin() &&
+ !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() &&
+ (!pCurrPart->GetFont().GetBackColor() || *pCurrPart->GetFont().GetBackColor() == COL_TRANSPARENT) )
+ {
+ rInf.DrawBackground( *this );
+ }
+
+ SwTextPortion::Paint( rInf );
+
+ const_cast<SwTextPaintInfo&>(rInf).SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
+ const_cast<SwTextPaintInfo&>(rInf).X( rInf.X() + pCurrPart->GetWidth() );
+ pCurrPart = pCurrPart->GetFollow();
+ }
+
+ const_cast<SwTextPaintInfo&>(rInf).Y( nBasePosY );
+ const_cast<SwDropPortion*>(this)->Width( nOldWidth );
+ const_cast<SwDropPortion*>(this)->SetLen( nOldLen );
+ const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
+}
+
+void SwDropPortion::PaintDrop( const SwTextPaintInfo &rInf ) const
+{
+ // normal output is being done during the normal painting
+ if( ! m_nDropHeight || ! m_pPart || m_nLines == 1 )
+ return;
+
+ // set the lying values
+ const sal_uInt16 nOldHeight = Height();
+ const sal_uInt16 nOldWidth = Width();
+ const sal_uInt16 nOldAscent = GetAscent();
+ const SwTwips nOldPosY = rInf.Y();
+ const SwTwips nOldPosX = rInf.X();
+ const SwParaPortion *pPara = rInf.GetParaPortion();
+ const Point aOutPos( nOldPosX, nOldPosY - pPara->GetAscent()
+ - pPara->GetRealHeight() + pPara->Height() );
+ // make good for retouching
+
+ // Set baseline
+ const_cast<SwTextPaintInfo&>(rInf).Y( aOutPos.Y() + m_nDropHeight );
+
+ // for background
+ const_cast<SwDropPortion*>(this)->Height( m_nDropHeight + m_nDropDescent );
+ const_cast<SwDropPortion*>(this)->SetAscent( m_nDropHeight );
+
+ // Always adapt Clipregion to us, never set it off using the existing ClipRect
+ // as that could be set for the line
+ SwRect aClipRect;
+ if ( rInf.OnWin() )
+ {
+ aClipRect = SwRect( aOutPos, SvLSize() );
+ aClipRect.Intersection( rInf.GetPaintRect() );
+ }
+ SwSaveClip aClip( const_cast<OutputDevice*>(rInf.GetOut()) );
+ aClip.ChgClip( aClipRect, rInf.GetTextFrame() );
+
+ // Just do, what we always do ...
+ PaintText( rInf );
+
+ // save old values
+ const_cast<SwDropPortion*>(this)->Height( nOldHeight );
+ const_cast<SwDropPortion*>(this)->Width( nOldWidth );
+ const_cast<SwDropPortion*>(this)->SetAscent( nOldAscent );
+ const_cast<SwTextPaintInfo&>(rInf).Y( nOldPosY );
+}
+
+void SwDropPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // normal output is being done here
+ if( !(! m_nDropHeight || ! m_pPart || 1 == m_nLines) )
+ return;
+
+ if ( rInf.OnWin() &&
+ !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() )
+ rInf.DrawBackground( *this );
+
+ // make sure that font is not rotated
+ std::unique_ptr<SwFont> pTmpFont;
+ if ( rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() ) )
+ {
+ pTmpFont.reset(new SwFont( *rInf.GetFont() ));
+ pTmpFont->SetVertical( 0_deg10, rInf.GetTextFrame()->IsVertical() );
+ }
+
+ SwFontSave aFontSave( rInf, pTmpFont.get() );
+ // for text inside drop portions we let vcl handle the text directions
+ SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
+ aLayoutModeModifier.SetAuto();
+
+ SwTextPortion::Paint( rInf );
+}
+
+bool SwDropPortion::FormatText( SwTextFormatInfo &rInf )
+{
+ const TextFrameIndex nOldLen = GetLen();
+ const TextFrameIndex nOldInfLen = rInf.GetLen();
+ if (!SwTextPortion::Format( rInf ))
+ return false;
+
+ // looks like shit, but what can we do?
+ rInf.SetUnderflow( nullptr );
+ Truncate();
+ SetLen( nOldLen );
+ rInf.SetLen( nOldInfLen );
+
+ return true;
+}
+
+SwPosSize SwDropPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
+{
+ sal_uInt16 nMyX = 0;
+ TextFrameIndex nIdx(0);
+
+ const SwDropPortionPart* pCurrPart = GetPart();
+
+ // skip parts
+ while ( pCurrPart && nIdx + pCurrPart->GetLen() < rInf.GetLen() )
+ {
+ nMyX = nMyX + pCurrPart->GetWidth();
+ nIdx = nIdx + pCurrPart->GetLen();
+ pCurrPart = pCurrPart->GetFollow();
+ }
+
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ TextFrameIndex const nOldLen = rInf.GetLen();
+
+ const_cast<SwTextSizeInfo&>(rInf).SetIdx( nIdx );
+ const_cast<SwTextSizeInfo&>(rInf).SetLen( rInf.GetLen() - nIdx );
+
+ if( pCurrPart )
+ {
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
+ }
+
+ // robust
+ SwFontSave aFontSave( rInf, pCurrPart ? &pCurrPart->GetFont() : nullptr );
+ SwPosSize aPosSize( SwTextPortion::GetTextSize( rInf ) );
+ aPosSize.Width( aPosSize.Width() + nMyX );
+
+ const_cast<SwTextSizeInfo&>(rInf).SetIdx( nOldIdx );
+ const_cast<SwTextSizeInfo&>(rInf).SetLen( nOldLen );
+ if( pCurrPart )
+ {
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithNext(false);
+ const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(false);
+ }
+
+ return aPosSize;
+}
+
+TextFrameIndex SwDropPortion::GetModelPositionForViewPoint(const sal_uInt16) const
+{
+ return TextFrameIndex(0);
+}
+
+void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines )
+{
+ const SwLinePortion *const pOldCurr = GetCurr();
+ sal_uInt16 nDropHght = 0;
+ SwTwips nAscent = 0;
+ SwTwips nHeight = 0;
+ sal_uInt16 nDropLns = 0;
+ const bool bRegisterOld = IsRegisterOn();
+ m_bRegisterOn = false;
+
+ Top();
+
+ while( GetCurr()->IsDummy() )
+ {
+ if ( !Next() )
+ break;
+ }
+
+ // If we have only one line we return 0
+ if( GetNext() || GetDropLines() == 1 )
+ {
+ for( ; nDropLns < nLines; nDropLns++ )
+ {
+ if ( GetCurr()->IsDummy() )
+ break;
+ else
+ {
+ CalcAscentAndHeight( nAscent, nHeight );
+ nDropHght = nDropHght + nHeight;
+ m_bRegisterOn = bRegisterOld;
+ }
+ if ( !Next() )
+ {
+ nDropLns++;
+ break;
+ }
+ }
+
+ // We hit the line ascent when reaching the last line!
+ nDropHght = nDropHght - nHeight;
+ nDropHght = nDropHght + nAscent;
+ Top();
+ }
+ m_bRegisterOn = bRegisterOld;
+ SetDropDescent( nHeight - nAscent );
+ SetDropHeight( nDropHght );
+ SetDropLines( nDropLns );
+ // Find old position!
+ while( pOldCurr != GetCurr() )
+ {
+ if( !Next() )
+ {
+ OSL_ENSURE( false, "SwTextFormatter::_CalcDropHeight: left Toulouse" );
+ break;
+ }
+ }
+}
+
+/**
+ * We assume that the font height doesn't change and that at first there
+ * are at least as many lines, as the DropCap-setting claims
+ */
+void SwTextFormatter::GuessDropHeight( const sal_uInt16 nLines )
+{
+ OSL_ENSURE( nLines, "GuessDropHeight: Give me more Lines!" );
+ SwTwips nAscent = 0;
+ SwTwips nHeight = 0;
+ SetDropLines( nLines );
+ if ( GetDropLines() > 1 )
+ {
+ CalcRealHeight();
+ CalcAscentAndHeight( nAscent, nHeight );
+ }
+ SetDropDescent( nHeight - nAscent );
+ SetDropHeight( nHeight * nLines - GetDropDescent() );
+}
+
+SwDropPortion *SwTextFormatter::NewDropPortion( SwTextFormatInfo &rInf )
+{
+ if( !m_pDropFormat )
+ return nullptr;
+
+ TextFrameIndex nPorLen(m_pDropFormat->GetWholeWord() ? 0 : m_pDropFormat->GetChars());
+ nPorLen = m_pFrame->GetDropLen( nPorLen );
+ if( !nPorLen )
+ {
+ ClearDropFormat();
+ return nullptr;
+ }
+
+ SwDropPortion *pDropPor = nullptr;
+
+ // first or second round?
+ if ( !( GetDropHeight() || IsOnceMore() ) )
+ {
+ if ( GetNext() )
+ CalcDropHeight( m_pDropFormat->GetLines() );
+ else
+ GuessDropHeight( m_pDropFormat->GetLines() );
+ }
+
+ // the DropPortion
+ if( GetDropHeight() )
+ pDropPor = new SwDropPortion( GetDropLines(), GetDropHeight(),
+ GetDropDescent(), m_pDropFormat->GetDistance() );
+ else
+ pDropPor = new SwDropPortion( 0,0,0,m_pDropFormat->GetDistance() );
+
+ pDropPor->SetLen( nPorLen );
+
+ // If it was not possible to create a proper drop cap portion
+ // due to avoiding endless loops. We return a drop cap portion
+ // with an empty SwDropCapPart. For these portions the current
+ // font is used.
+ if ( GetDropLines() < 2 )
+ {
+ SetPaintDrop( true );
+ return pDropPor;
+ }
+
+ // build DropPortionParts:
+ OSL_ENSURE( ! rInf.GetIdx(), "Drop Portion not at 0 position!" );
+ TextFrameIndex nNextChg(0);
+ const SwCharFormat* pFormat = m_pDropFormat->GetCharFormat();
+ SwDropPortionPart* pCurrPart = nullptr;
+
+ while ( nNextChg < nPorLen )
+ {
+ // check for attribute changes and if the portion has to split:
+ Seek( nNextChg );
+
+ // the font is deleted in the destructor of the drop portion part
+ SwFont* pTmpFnt = new SwFont( *rInf.GetFont() );
+ if ( pFormat )
+ {
+ const SwAttrSet& rSet = pFormat->GetAttrSet();
+ pTmpFnt->SetDiffFnt(&rSet, &m_pFrame->GetDoc().getIDocumentSettingAccess());
+ }
+
+ // we do not allow a vertical font for the drop portion
+ pTmpFnt->SetVertical( 0_deg10, rInf.GetTextFrame()->IsVertical() );
+
+ // find next attribute change / script change
+ const TextFrameIndex nTmpIdx = nNextChg;
+ TextFrameIndex nNextAttr = GetNextAttr();
+ nNextChg = m_pScriptInfo->NextScriptChg( nTmpIdx );
+ if( nNextChg > nNextAttr )
+ nNextChg = nNextAttr;
+ if ( nNextChg > nPorLen )
+ nNextChg = nPorLen;
+
+ std::unique_ptr<SwDropPortionPart> pPart(
+ new SwDropPortionPart( *pTmpFnt, nNextChg - nTmpIdx ) );
+ auto pPartTemp = pPart.get();
+
+ if ( ! pCurrPart )
+ pDropPor->SetPart( std::move(pPart) );
+ else
+ pCurrPart->SetFollow( std::move(pPart) );
+
+ pCurrPart = pPartTemp;
+ }
+
+ SetPaintDrop( true );
+ return pDropPor;
+}
+
+void SwTextPainter::PaintDropPortion()
+{
+ const SwDropPortion *pDrop = GetInfo().GetParaPortion()->FindDropPortion();
+ OSL_ENSURE( pDrop, "DrapCop-Portion not available." );
+ if( !pDrop )
+ return;
+
+ const SwTwips nOldY = GetInfo().Y();
+
+ Top();
+
+ GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() );
+ GetInfo().ResetSpaceIdx();
+ GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() );
+ GetInfo().ResetKanaIdx();
+
+ // 8047: Drops and Dummies
+ while( !m_pCurr->GetLen() && Next() )
+ ;
+
+ // MarginPortion and Adjustment!
+ const SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ tools::Long nX = 0;
+ while( pPor && !pPor->IsDropPortion() )
+ {
+ nX = nX + pPor->Width();
+ pPor = pPor->GetNextPortion();
+ }
+ Point aLineOrigin( GetTopLeft() );
+
+ aLineOrigin.AdjustX(nX );
+ SwTwips nTmpAscent, nTmpHeight;
+ CalcAscentAndHeight( nTmpAscent, nTmpHeight );
+ aLineOrigin.AdjustY(nTmpAscent );
+ GetInfo().SetIdx( GetStart() );
+ GetInfo().SetPos( aLineOrigin );
+ GetInfo().SetLen( pDrop->GetLen() );
+
+ pDrop->PaintDrop( GetInfo() );
+
+ GetInfo().Y( nOldY );
+}
+
+// Since the calculation of the font size is expensive, this is being
+// channeled through a DropCapCache
+#define DROP_CACHE_SIZE 10
+
+class SwDropCapCache
+{
+ const void* m_aFontCacheId[ DROP_CACHE_SIZE ] = {};
+ OUString m_aText[ DROP_CACHE_SIZE ];
+ sal_uInt16 m_aFactor[ DROP_CACHE_SIZE ];
+ sal_uInt16 m_aWishedHeight[ DROP_CACHE_SIZE ] = {};
+ short m_aDescent[ DROP_CACHE_SIZE ];
+ sal_uInt16 m_nIndex = 0;
+public:
+ SwDropCapCache() = default;
+ void CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf );
+};
+
+void SwDropPortion::DeleteDropCapCache()
+{
+ delete pDropCapCache;
+}
+
+void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf )
+{
+ const void* nFntCacheId = nullptr;
+ sal_uInt16 nTmpIdx = 0;
+
+ OSL_ENSURE( pDrop->GetPart(),"DropPortion without part during font calculation");
+
+ SwDropPortionPart* pCurrPart = pDrop->GetPart();
+ const bool bUseCache = ! pCurrPart->GetFollow() && !pCurrPart->GetFont().HasBorder();
+ TextFrameIndex nIdx = rInf.GetIdx();
+ OUString aStr(rInf.GetText().copy(sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen())));
+
+ tools::Long nDescent = 0;
+ tools::Long nFactor = -1;
+
+ if ( bUseCache )
+ {
+ SwFont& rFnt = pCurrPart->GetFont();
+ rFnt.CheckFontCacheId( rInf.GetVsh(), rFnt.GetActual() );
+ rFnt.GetFontCacheId( nFntCacheId, nTmpIdx, rFnt.GetActual() );
+
+ nTmpIdx = 0;
+
+ while( nTmpIdx < DROP_CACHE_SIZE &&
+ ( m_aText[ nTmpIdx ] != aStr || m_aFontCacheId[ nTmpIdx ] != nFntCacheId ||
+ m_aWishedHeight[ nTmpIdx ] != pDrop->GetDropHeight() ) )
+ ++nTmpIdx;
+ }
+
+ // we have to calculate a new font scaling factor if
+ // 1. we did not find a scaling factor in the cache or
+ // 2. we are not allowed to use the cache because the drop portion
+ // consists of more than one part
+ if( nTmpIdx >= DROP_CACHE_SIZE || ! bUseCache )
+ {
+ ++m_nIndex;
+ m_nIndex %= DROP_CACHE_SIZE;
+ nTmpIdx = m_nIndex;
+
+ tools::Long nWishedHeight = pDrop->GetDropHeight();
+ tools::Long nAscent = 0;
+
+ // find out biggest font size for initial scaling factor
+ tools::Long nMaxFontHeight = 1;
+ while ( pCurrPart )
+ {
+ const SwFont& rFnt = pCurrPart->GetFont();
+ const tools::Long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );
+ if ( nCurrHeight > nMaxFontHeight )
+ nMaxFontHeight = nCurrHeight;
+
+ pCurrPart = pCurrPart->GetFollow();
+ }
+
+ nFactor = ( 1000 * nWishedHeight ) / nMaxFontHeight;
+
+ if ( bUseCache )
+ {
+ // save keys for cache
+ m_aFontCacheId[ nTmpIdx ] = nFntCacheId;
+ m_aText[ nTmpIdx ] = aStr;
+ m_aWishedHeight[ nTmpIdx ] = sal_uInt16(nWishedHeight);
+ // save initial scaling factor
+ m_aFactor[ nTmpIdx ] = o3tl::narrowing<sal_uInt16>(nFactor);
+ }
+
+ bool bGrow = (pDrop->GetLen() != TextFrameIndex(0));
+
+ // for growing control
+ tools::Long nMax = USHRT_MAX;
+ tools::Long nMin = 0;
+#if OSL_DEBUG_LEVEL > 1
+ tools::Long nGrow = 0;
+#endif
+
+ bool bWinUsed = false;
+ vcl::Font aOldFnt;
+ MapMode aOldMap( MapUnit::MapTwip );
+ OutputDevice* pOut = rInf.GetOut();
+ OutputDevice* pWin;
+ if( rInf.GetVsh() && rInf.GetVsh()->GetWin() )
+ pWin = rInf.GetVsh()->GetWin()->GetOutDev();
+ else
+ pWin = Application::GetDefaultDevice();
+
+ // adjust punctuation?
+ bool bKeepBaseline = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess()
+ .get(DocumentSettingId::DROP_CAP_PUNCTUATION) &&
+ !rInf.GetDropFormat()->GetWholeWord(); // && rInf.GetDropFormat()->GetChars() == 1;
+
+ while( bGrow )
+ {
+ // reset pCurrPart to first part
+ pCurrPart = pDrop->GetPart();
+ bool bFirstGlyphRect = true;
+ tools::Rectangle aCommonRect, aRect;
+
+ while ( pCurrPart )
+ {
+ // current font
+ SwFont& rFnt = pCurrPart->GetFont();
+
+ // Get height including proportion
+ const tools::Long nCurrHeight = rFnt.GetHeight( rFnt.GetActual() );
+
+ // Get without proportion
+ const sal_uInt8 nOldProp = rFnt.GetPropr();
+ rFnt.SetProportion( 100 );
+ Size aOldSize( 0, rFnt.GetHeight( rFnt.GetActual() ) );
+
+ Size aNewSize( 0, ( nFactor * nCurrHeight ) / 1000 );
+ rFnt.SetSize( aNewSize, rFnt.GetActual() );
+ rFnt.ChgPhysFnt( rInf.GetVsh(), *pOut );
+
+ nAscent = rFnt.GetAscent( rInf.GetVsh(), *pOut );
+
+ // we get the rectangle that covers all chars
+ bool bHaveGlyphRect = pOut->GetTextBoundRect( aRect, rInf.GetText(), 0,
+ sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
+ && ! aRect.IsEmpty();
+
+ if ( ! bHaveGlyphRect )
+ {
+ // getting glyph boundaries failed for some reason,
+ // we take the window for calculating sizes
+ if ( pWin )
+ {
+ if ( ! bWinUsed )
+ {
+ bWinUsed = true;
+ aOldMap = pWin->GetMapMode( );
+ pWin->SetMapMode( MapMode( MapUnit::MapTwip ) );
+ aOldFnt = pWin->GetFont();
+ }
+ pWin->SetFont( rFnt.GetActualFont() );
+
+ bHaveGlyphRect = pWin->GetTextBoundRect( aRect, rInf.GetText(), 0,
+ sal_Int32(nIdx), sal_Int32(pCurrPart->GetLen()))
+ && ! aRect.IsEmpty();
+ }
+ if (!bHaveGlyphRect)
+ {
+ // We do not have a window or our window could not
+ // give us glyph boundaries.
+ aRect = tools::Rectangle( Point( 0, 0 ), Size( 0, nAscent ) );
+ }
+ }
+
+ // extend rectangle to the baseline to avoid of giant dashes,
+ // quotation marks, bullet, asterisks etc.
+ if ( bKeepBaseline && aRect.Top() < 0 )
+ {
+ aRect.SetBottom(0);
+ aRect.SetTop(aRect.Top() - nAscent/60);
+ }
+
+ // Now we (hopefully) have a bounding rectangle for the
+ // glyphs of the current portion and the ascent of the current
+ // font
+
+ // reset font size and proportion
+ rFnt.SetSize( aOldSize, rFnt.GetActual() );
+ rFnt.SetProportion( nOldProp );
+
+ // Modify the bounding rectangle with the borders
+ // Robust: If the padding is so big as drop cap letter has no enough space than
+ // remove all padding.
+ if( rFnt.GetTopBorderSpace() + rFnt.GetBottomBorderSpace() >= nWishedHeight )
+ {
+ rFnt.SetTopBorderDist(0);
+ rFnt.SetBottomBorderDist(0);
+ rFnt.SetRightBorderDist(0);
+ rFnt.SetLeftBorderDist(0);
+ }
+
+ if( rFnt.GetTopBorder() )
+ {
+ aRect.setHeight(aRect.GetHeight() + rFnt.GetTopBorderSpace());
+ aRect.SetPosY(aRect.Top() - rFnt.GetTopBorderSpace());
+ }
+
+ if( rFnt.GetBottomBorder() )
+ {
+ aRect.setHeight(aRect.GetHeight() + rFnt.GetBottomBorderSpace());
+ }
+
+ if ( bFirstGlyphRect )
+ {
+ aCommonRect = aRect;
+ bFirstGlyphRect = false;
+ }
+ else
+ aCommonRect.Union( aRect );
+
+ nIdx = nIdx + pCurrPart->GetLen();
+ pCurrPart = pCurrPart->GetFollow();
+ }
+
+ // now we have a union ( aCommonRect ) of all glyphs with
+ // respect to a common baseline : 0
+
+ // get descent and ascent from union
+ if ( rInf.GetTextFrame()->IsVertical() )
+ {
+ nDescent = aCommonRect.Left();
+ nAscent = aCommonRect.Right();
+
+ if ( nDescent < 0 )
+ nDescent = -nDescent;
+ }
+ else
+ {
+ nDescent = aCommonRect.Bottom();
+ nAscent = aCommonRect.Top();
+ }
+ if ( nAscent < 0 )
+ nAscent = -nAscent;
+
+ const tools::Long nHght = nAscent + nDescent;
+ if ( nHght )
+ {
+ if ( nHght > nWishedHeight )
+ nMax = nFactor;
+ else
+ {
+ if ( bUseCache )
+ m_aFactor[ nTmpIdx ] = o3tl::narrowing<sal_uInt16>(nFactor);
+ nMin = nFactor;
+ }
+
+ nFactor = ( nFactor * nWishedHeight ) / nHght;
+ bGrow = ( nFactor > nMin ) && ( nFactor < nMax );
+#if OSL_DEBUG_LEVEL > 1
+ if ( bGrow )
+ nGrow++;
+#endif
+ nIdx = rInf.GetIdx();
+ }
+ else
+ bGrow = false;
+ }
+
+ if ( bWinUsed )
+ {
+ // reset window if it has been used
+ pWin->SetMapMode( aOldMap );
+ pWin->SetFont( aOldFnt );
+ }
+
+ if ( bUseCache )
+ m_aDescent[ nTmpIdx ] = -short( nDescent );
+ }
+
+ pCurrPart = pDrop->GetPart();
+
+ // did made any new calculations or did we use the cache?
+ if ( -1 == nFactor )
+ {
+ nFactor = m_aFactor[ nTmpIdx ];
+ nDescent = m_aDescent[ nTmpIdx ];
+ }
+ else
+ nDescent = -nDescent;
+
+ while ( pCurrPart )
+ {
+ // scale current font
+ SwFont& rFnt = pCurrPart->GetFont();
+ Size aNewSize( 0, ( nFactor * rFnt.GetHeight( rFnt.GetActual() ) ) / 1000 );
+
+ const sal_uInt8 nOldProp = rFnt.GetPropr();
+ rFnt.SetProportion( 100 );
+ rFnt.SetSize( aNewSize, rFnt.GetActual() );
+ rFnt.SetProportion( nOldProp );
+
+ pCurrPart = pCurrPart->GetFollow();
+ }
+ pDrop->SetY( static_cast<short>(nDescent) );
+}
+
+bool SwDropPortion::Format( SwTextFormatInfo &rInf )
+{
+ bool bFull = false;
+ m_nFix = o3tl::narrowing<sal_uInt16>(rInf.X());
+
+ SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() );
+ aLayoutModeModifier.SetAuto();
+
+ if( m_nDropHeight && m_pPart && m_nLines!=1 )
+ {
+ if( !pDropCapCache )
+ pDropCapCache = new SwDropCapCache;
+
+ // adjust font sizes to fit into the rectangle
+ pDropCapCache->CalcFontSize( this, rInf );
+
+ const tools::Long nOldX = rInf.X();
+ {
+ SwDropSave aSave( rInf );
+ SwDropPortionPart* pCurrPart = m_pPart.get();
+
+ while ( pCurrPart )
+ {
+ rInf.SetLen( pCurrPart->GetLen() );
+ SwFont& rFnt = pCurrPart->GetFont();
+ {
+ SwFontSave aFontSave( rInf, &rFnt );
+ SetJoinBorderWithNext(pCurrPart->GetJoinBorderWithNext());
+ SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev());
+ bFull = FormatText( rInf );
+
+ if ( bFull )
+ break;
+ }
+
+ const SwTwips nTmpWidth =
+ ( InSpaceGrp() && rInf.GetSpaceAdd() ) ?
+ Width() + CalcSpacing( rInf.GetSpaceAdd(), rInf ) :
+ Width();
+
+ // set values
+ pCurrPart->SetWidth( o3tl::narrowing<sal_uInt16>(nTmpWidth) );
+
+ // Move
+ rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() );
+ rInf.X( rInf.X() + nTmpWidth );
+ pCurrPart = pCurrPart->GetFollow();
+ }
+ SetJoinBorderWithNext(false);
+ SetJoinBorderWithPrev(false);
+ Width( o3tl::narrowing<sal_uInt16>(rInf.X() - nOldX) );
+ }
+
+ // reset my length
+ SetLen( rInf.GetLen() );
+
+ // Quit when Flys are overlapping
+ if( ! bFull )
+ bFull = lcl_IsDropFlyInter( rInf, Width(), m_nDropHeight );
+
+ if( bFull )
+ {
+ // FormatText could have caused nHeight to be 0
+ if ( !Height() )
+ Height( rInf.GetTextHeight() );
+
+ // And now for another round
+ m_nDropHeight = m_nLines = 0;
+ m_pPart.reset();
+
+ // Meanwhile use normal formatting
+ bFull = SwTextPortion::Format( rInf );
+ }
+ else
+ rInf.SetDropInit( true );
+
+ Height( rInf.GetTextHeight() );
+ SetAscent( rInf.GetAscent() );
+ }
+ else
+ bFull = SwTextPortion::Format( rInf );
+
+ if( bFull )
+ m_nDistance = 0;
+ else
+ {
+ const sal_uInt16 nWant = Width() + GetDistance();
+ const sal_uInt16 nRest = o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X());
+ if( ( nWant > nRest ) ||
+ lcl_IsDropFlyInter( rInf, Width() + GetDistance(), m_nDropHeight ) )
+ m_nDistance = 0;
+
+ Width( Width() + m_nDistance );
+ }
+ return bFull;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx
new file mode 100644
index 0000000000..84f32e3097
--- /dev/null
+++ b/sw/source/core/text/txtfld.cxx
@@ -0,0 +1,715 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <fmtfld.hxx>
+#include <txtfld.hxx>
+#include <charfmt.hxx>
+#include <fmtautofmt.hxx>
+
+#include <viewsh.hxx>
+#include <doc.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <ndtxt.hxx>
+#include <fldbas.hxx>
+#include <viewopt.hxx>
+#include <flyfrm.hxx>
+#include <viewimp.hxx>
+#include <swfont.hxx>
+#include <swmodule.hxx>
+#include "porfld.hxx"
+#include "porftn.hxx"
+#include "porref.hxx"
+#include "portox.hxx"
+#include "porfly.hxx"
+#include "itrform2.hxx"
+#include <chpfld.hxx>
+#include <dbfld.hxx>
+#include <expfld.hxx>
+#include <docufld.hxx>
+#include <pagedesc.hxx>
+#include <fmtmeta.hxx>
+#include <reffld.hxx>
+#include <flddat.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <redline.hxx>
+#include <sfx2/docfile.hxx>
+#include <svl/grabbagitem.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/whiter.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <officecfg/Office/Writer.hxx>
+
+static bool lcl_IsInBody( SwFrame const *pFrame )
+{
+ if ( pFrame->IsInDocBody() )
+ return true;
+
+ while (const SwFlyFrame* pFly = pFrame->FindFlyFrame())
+ pFrame = pFly->GetAnchorFrame();
+ 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
+{
+ SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
+ const bool bName = rInf.GetOpt().IsFieldName();
+
+ // set language
+ const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf );
+ if (pField->GetLanguage() != GetFnt()->GetLanguage())
+ pField->SetLanguage( GetFnt()->GetLanguage() );
+
+ SwViewShell *pSh = rInf.GetVsh();
+
+ switch (pField->GetTyp()->Which())
+ {
+ case SwFieldIds::Script:
+ case SwFieldIds::Postit:
+ return new SwPostItsPortion(SwFieldIds::Script == pField->GetTyp()->Which());
+ case SwFieldIds::CombinedChars:
+ if (!bName)
+ return new SwCombinedPortion(ExpandField(*pField, *this, rInf));
+ break;
+ case SwFieldIds::HiddenText:
+ return new SwHiddenPortion(ExpandField(*pField, *this, rInf));
+ case SwFieldIds::Chapter:
+ if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
+ {
+ static_cast<SwChapterField*>(pField)->ChangeExpansion(
+ *m_pFrame, &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode());
+ }
+ break;
+ case SwFieldIds::DocStat:
+ if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
+ {
+ static_cast<SwDocStatField*>(pField)->ChangeExpansion(m_pFrame);
+ }
+ break;
+ case SwFieldIds::PageNumber:
+ if (!bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields())
+ {
+ auto pPageNr = static_cast<SwPageNumberFieldType*>(pField->GetTyp());
+
+ const SwRootFrame* pTmpRootFrame = pSh->GetLayout();
+ const bool bVirt = pTmpRootFrame->IsVirtPageNum();
+
+ sal_uInt16 nVirtNum = m_pFrame->GetVirtPageNum();
+ sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum();
+ SvxNumType nNumFormat = SvxNumType(-1);
+ 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())
+ {
+ auto pExpField = static_cast<SwGetExpField*>(pField);
+ if (!::lcl_IsInBody(m_pFrame))
+ {
+ pExpField->ChgBodyTextFlag(false);
+ pExpField->ChangeExpansion(*m_pFrame,
+ *static_txtattr_cast<SwTextField const*>(pHint));
+ }
+ else if (!pExpField->IsInBodyText())
+ {
+ // Was something else previously, thus: expand first, then convert it!
+ pExpField->ChangeExpansion(*m_pFrame,
+ *static_txtattr_cast<SwTextField const*>(pHint));
+ pExpField->ChgBodyTextFlag(true);
+ }
+ }
+ break;
+ case SwFieldIds::Database:
+ if (!bName)
+ {
+ static_cast<SwDBField*>(pField)->ChgBodyTextFlag(::lcl_IsInBody(m_pFrame));
+ }
+ break;
+ case SwFieldIds::RefPageGet:
+ if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields())
+ {
+ static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(
+ *m_pFrame, static_txtattr_cast<SwTextField const*>(pHint));
+ }
+ break;
+ case SwFieldIds::JumpEdit:
+ {
+ std::unique_ptr<SwFont> pFont;
+ if (!bName)
+ {
+ pFont = std::make_unique<SwFont>(*m_pFont);
+ pFont->SetDiffFnt(
+ &static_cast<SwJumpEditField*>(pField)->GetCharFormat()->GetAttrSet(),
+ &m_pFrame->GetDoc().getIDocumentSettingAccess());
+ }
+ return new SwJumpFieldPortion(ExpandField(*pField, *this, rInf), pField->GetPar2(),
+ std::move(pFont), pField->GetFormat());
+ }
+ case SwFieldIds::GetRef:
+ if (!bName)
+ {
+ auto pGetRef = static_cast<SwGetRefField*>(pField);
+ if (pGetRef->GetSubType() == REF_STYLE)
+ pGetRef->UpdateField(static_txtattr_cast<SwTextField const*>(pHint), m_pFrame);
+ }
+ break;
+ default:
+ break;
+ }
+ return new SwFieldPortion(ExpandField(*pField, *this, rInf));
+}
+
+static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix)
+{
+ ::sw::Meta *const pMeta(
+ static_cast<SwFormatMeta &>(rHint.GetAttr()).GetMeta() );
+ OUString fix;
+ ::sw::MetaField *const pField( dynamic_cast< ::sw::MetaField * >(pMeta) );
+ OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?");
+ if (pField)
+ {
+ OUString color;
+ pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix, &color);
+ }
+ return new SwFieldPortion( fix );
+}
+
+/**
+ * Try to create a new portion with zero length, for an end of a hint
+ * (where there is no CH_TXTATR). Because there may be multiple hint ends at a
+ * given index, m_pByEndIter is used to keep track of the already created
+ * portions. But the portions created here may actually be deleted again,
+ * due to Underflow. In that case, m_pByEndIter must be decremented,
+ * so the portion will be created again on the next line.
+ */
+SwExpandPortion * SwTextFormatter::TryNewNoLengthPortion(SwTextFormatInfo const & rInfo)
+{
+ const TextFrameIndex nIdx(rInfo.GetIdx());
+
+ // sw_redlinehide: because there is a dummy character at the start of these
+ // hints, it's impossible to have ends of hints from different nodes at the
+ // same view position, so it's sufficient to check the hints of the current
+ // node. However, m_pByEndIter exists for the whole text frame, so
+ // it's necessary to iterate all hints for that purpose...
+ if (!m_pByEndIter)
+ {
+ m_pByEndIter.reset(new sw::MergedAttrIterByEnd(*rInfo.GetTextFrame()));
+ }
+ SwTextNode const* pNode(nullptr);
+ for (SwTextAttr const* pHint = m_pByEndIter->NextAttr(pNode); pHint;
+ pHint = m_pByEndIter->NextAttr(pNode))
+ {
+ SwTextAttr & rHint(const_cast<SwTextAttr&>(*pHint));
+ TextFrameIndex const nEnd(
+ rInfo.GetTextFrame()->MapModelToView(pNode, rHint.GetAnyEnd()));
+ if (nEnd > nIdx)
+ {
+ m_pByEndIter->PrevAttr();
+ break;
+ }
+ if (nEnd == nIdx)
+ {
+ if (RES_TXTATR_METAFIELD == rHint.Which())
+ {
+ SwFieldPortion *const pPortion(
+ lcl_NewMetaPortion(rHint, false));
+ pPortion->SetNoLength(); // no CH_TXTATR at hint end!
+ return pPortion;
+ }
+ }
+ }
+ return nullptr;
+}
+
+SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf )
+{
+ SwTextAttr *pHint = GetAttr( rInf.GetIdx() );
+ SwLinePortion *pRet = nullptr;
+ if( !pHint )
+ {
+ pRet = new SwTextPortion;
+ pRet->SetLen(TextFrameIndex(1));
+ rInf.SetLen(TextFrameIndex(1));
+ return pRet;
+ }
+
+ switch( pHint->Which() )
+ {
+ case RES_TXTATR_FLYCNT :
+ {
+ pRet = NewFlyCntPortion( rInf, pHint );
+ break;
+ }
+ case RES_TXTATR_FTN :
+ {
+ pRet = NewFootnotePortion( rInf, pHint );
+ break;
+ }
+ case RES_TXTATR_FIELD :
+ case RES_TXTATR_ANNOTATION :
+ {
+ pRet = NewFieldPortion( rInf, pHint );
+ break;
+ }
+ case RES_TXTATR_REFMARK :
+ {
+ pRet = new SwIsoRefPortion;
+ break;
+ }
+ case RES_TXTATR_TOXMARK :
+ {
+ pRet = new SwIsoToxPortion;
+ break;
+ }
+ case RES_TXTATR_METAFIELD:
+ {
+ pRet = lcl_NewMetaPortion( *pHint, true );
+ break;
+ }
+ default: ;
+ }
+ if( !pRet )
+ {
+ auto pFieldPortion = new SwFieldPortion( "" );
+ if (pHint->Which() == RES_TXTATR_CONTENTCONTROL)
+ {
+ pFieldPortion->SetContentControl(true);
+ }
+ pRet = pFieldPortion;
+ rInf.SetLen(TextFrameIndex(1));
+ }
+ return pRet;
+}
+
+/**
+ * OOXML spec says that w:rPr inside w:pPr specifies formatting for the paragraph mark symbol (i.e. the control
+ * character than can be configured to be shown). However, in practice MSO also uses it as direct formatting
+ * for numbering in that paragraph. I don't know if the problem is in the spec or in MSWord.
+ */
+static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextFormatInfo& rInf,
+ const IDocumentSettingAccess* pIDSA,
+ const SwAttrSet* pFormat)
+{
+ if( !pIDSA->get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
+ return;
+
+ SwFormatAutoFormat const& rListAutoFormat(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT));
+ std::shared_ptr<SfxItemSet> pSet(rListAutoFormat.GetStyleHandle());
+
+ // Check each item and in case it should be ignored, then clear it.
+ if (!pSet)
+ return;
+
+ std::unique_ptr<SfxItemSet> const pCleanedSet = pSet->Clone();
+
+ if (pCleanedSet->HasItem(RES_TXTATR_CHARFMT))
+ {
+ // Insert attributes of referenced char format into current set
+ const SwFormatCharFormat& rCharFormat = pCleanedSet->Get(RES_TXTATR_CHARFMT);
+ const SwAttrSet& rStyleAttrs = static_cast<const SwCharFormat *>(rCharFormat.GetRegisteredIn())->GetAttrSet();
+ SfxWhichIter aIter(rStyleAttrs);
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while (nWhich)
+ {
+ if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich, /*bIsCharStyle=*/true)
+ && !pCleanedSet->HasItem(nWhich)
+ && !(pFormat && pFormat->HasItem(nWhich))
+ && rStyleAttrs.GetItemState(nWhich) > SfxItemState::DEFAULT)
+ {
+ // Copy from parent sets only allowed items which will not overwrite
+ // values explicitly defined in current set (pCleanedSet) or in pFormat
+ if (const SfxPoolItem* pItem = rStyleAttrs.GetItem(nWhich, true))
+ pCleanedSet->Put(*pItem);
+ }
+ nWhich = aIter.NextWhich();
+ }
+
+ // It is not required here anymore, all referenced items are inserted
+ pCleanedSet->ClearItem(RES_TXTATR_CHARFMT);
+ };
+
+ SfxItemIter aIter(*pSet);
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ while (pItem)
+ {
+ if (SwTextNode::IsIgnoredCharFormatForNumbering(pItem->Which()))
+ pCleanedSet->ClearItem(pItem->Which());
+ else if (pFormat && pFormat->HasItem(pItem->Which()))
+ pCleanedSet->ClearItem(pItem->Which());
+ else if (pItem->Which() == RES_CHRATR_BACKGROUND)
+ {
+ bool bShadingWasImported = false;
+ // If Shading was imported, it should not be converted to a Highlight,
+ // but remain as Shading which is ignored for numbering.
+ if (pCleanedSet->HasItem(RES_CHRATR_GRABBAG))
+ {
+ SfxGrabBagItem aGrabBag = pCleanedSet->Get(RES_CHRATR_GRABBAG, /*bSrchInParent=*/false);
+ std::map<OUString, css::uno::Any>& rMap = aGrabBag.GetGrabBag();
+ auto aIterator = rMap.find("CharShadingMarker");
+ if (aIterator != rMap.end())
+ aIterator->second >>= bShadingWasImported;
+ }
+
+ // If used, BACKGROUND is converted to HIGHLIGHT. So also ignore if a highlight already exists.
+ if (bShadingWasImported
+ || pCleanedSet->HasItem(RES_CHRATR_HIGHLIGHT)
+ || (pFormat && pFormat->HasItem(RES_CHRATR_HIGHLIGHT)))
+ {
+ pCleanedSet->ClearItem(pItem->Which());
+ }
+ }
+ pItem = aIter.NextItem();
+ };
+
+ // SetDiffFnt resets the background color (why?), so capture it and re-apply if it had a value,
+ // because an existing value should override anything inherited from the paragraph marker.
+ const std::optional<Color> oFontBackColor = pNumFnt->GetBackColor();
+ // The same is true for the highlight color.
+ const Color aHighlight = pNumFnt->GetHighlightColor();
+
+ pNumFnt->SetDiffFnt(pCleanedSet.get(), pIDSA);
+
+ if (oFontBackColor)
+ pNumFnt->SetBackColor(oFontBackColor);
+ if (aHighlight != COL_TRANSPARENT)
+ pNumFnt->SetHighlightColor(aHighlight);
+}
+
+static const SwRangeRedline* lcl_GetRedlineAtNodeInsertionOrDeletion( const SwTextNode& rTextNode,
+ bool& bIsMoved )
+{
+ const SwDoc& rDoc = rTextNode.GetDoc();
+ SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rTextNode, RedlineType::Any );
+
+ if( SwRedlineTable::npos != nRedlPos )
+ {
+ const SwNodeOffset nNdIdx = rTextNode.GetIndex();
+ const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
+ for( ; nRedlPos < rTable.size() ; ++nRedlPos )
+ {
+ const SwRangeRedline* pTmp = rTable[ nRedlPos ];
+ SwNodeOffset nStart = pTmp->GetPoint()->GetNodeIndex(),
+ nEnd = pTmp->GetMark()->GetNodeIndex();
+ if( nStart > nEnd )
+ std::swap(nStart, nEnd);
+ if( RedlineType::Delete == pTmp->GetType() ||
+ RedlineType::Insert == pTmp->GetType() )
+ {
+ if( nStart <= nNdIdx && nEnd > nNdIdx )
+ {
+ bIsMoved = pTmp->IsMoved();
+ return pTmp;
+ }
+ }
+ if( nStart > nNdIdx )
+ break;
+ }
+ }
+ return nullptr;
+}
+
+static bool lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, const std::unique_ptr<SwFont>& pNumFnt )
+{
+ if ( rInf.GetVsh()->GetLayout()->IsHideRedlines() )
+ return false;
+
+ bool bIsMoved;
+ const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode, bIsMoved );
+ if (!pRedlineNum)
+ return false;
+
+ // moved text: dark green with double underline or strikethrough
+ if ( bIsMoved )
+ {
+ pNumFnt->SetColor(COL_GREEN);
+ if ( RedlineType::Delete == pRedlineNum->GetType() )
+ pNumFnt->SetStrikeout(STRIKEOUT_DOUBLE);
+ else
+ pNumFnt->SetUnderline(LINESTYLE_DOUBLE);
+ return true;
+ }
+
+ SwAttrPool& rPool = rInf.GetVsh()->GetDoc()->GetAttrPool();
+ SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1> aSet(rPool);
+
+ std::size_t aAuthor = (1 < pRedlineNum->GetStackCount())
+ ? pRedlineNum->GetAuthor( 1 )
+ : pRedlineNum->GetAuthor();
+
+ if ( RedlineType::Delete == pRedlineNum->GetType() )
+ SW_MOD()->GetDeletedAuthorAttr(aAuthor, aSet);
+ else
+ SW_MOD()->GetInsertAuthorAttr(aAuthor, aSet);
+
+ if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
+ pNumFnt->SetColor(pItem->GetValue());
+ if (const SvxUnderlineItem* pItem = aSet.GetItemIfSet(RES_CHRATR_UNDERLINE))
+ pNumFnt->SetUnderline(pItem->GetLineStyle());
+ if (const SvxCrossedOutItem* pItem = aSet.GetItemIfSet(RES_CHRATR_CROSSEDOUT))
+ pNumFnt->SetStrikeout( pItem->GetStrikeout() );
+
+ return true;
+}
+
+SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) const
+{
+ if( rInf.IsNumDone() || rInf.GetTextStart() != m_nStart
+ || rInf.GetTextStart() != rInf.GetIdx() )
+ return nullptr;
+
+ SwNumberPortion *pRet = nullptr;
+ // sw_redlinehide: at this point it's certain that pTextNd is the node with
+ // the numbering of the frame; only the actual number-vector (GetNumString)
+ // depends on the hide-mode in the layout so other calls don't need to care
+ const SwTextNode *const pTextNd = GetTextFrame()->GetTextNodeForParaProps();
+ const SwNumRule* pNumRule = pTextNd->GetNumRule();
+
+ // Has a "valid" number?
+ // sw_redlinehide: check that pParaPropsNode is the correct one
+ assert(pTextNd->IsNumbered(m_pFrame->getRootFrame()) == pTextNd->IsNumbered(nullptr));
+ if (pTextNd->IsNumbered(m_pFrame->getRootFrame()) && pTextNd->IsCountedInList())
+ {
+ int nLevel = pTextNd->GetActualListLevel();
+
+ if (nLevel < 0)
+ nLevel = 0;
+
+ if (nLevel >= MAXLEVEL)
+ nLevel = MAXLEVEL - 1;
+
+ const SwNumFormat &rNumFormat = pNumRule->Get( nLevel );
+ const bool bLeft = SvxAdjust::Left == rNumFormat.GetNumAdjust();
+ const bool bCenter = SvxAdjust::Center == rNumFormat.GetNumAdjust();
+ const bool bLabelAlignmentPosAndSpaceModeActive(
+ rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT );
+ const sal_uInt16 nMinDist = bLabelAlignmentPosAndSpaceModeActive
+ ? 0 : rNumFormat.GetCharTextDistance();
+
+ if( SVX_NUM_BITMAP == rNumFormat.GetNumberingType() )
+ {
+ OUString referer;
+ if (auto const sh1 = rInf.GetVsh()) {
+ if (auto const doc = sh1->GetDoc()) {
+ auto const sh2 = doc->GetPersist();
+ if (sh2 != nullptr && sh2->HasName()) {
+ referer = sh2->GetMedium()->GetName();
+ }
+ }
+ }
+ pRet = new SwGrfNumPortion( pTextNd->GetLabelFollowedBy(),
+ rNumFormat.GetBrush(), referer,
+ rNumFormat.GetGraphicOrientation(),
+ rNumFormat.GetGraphicSize(),
+ bLeft, bCenter, nMinDist,
+ bLabelAlignmentPosAndSpaceModeActive );
+ tools::Long nTmpA = rInf.GetLast()->GetAscent();
+ tools::Long nTmpD = rInf.GetLast()->Height() - nTmpA;
+ if( !rInf.IsTest() )
+ static_cast<SwGrfNumPortion*>(pRet)->SetBase( nTmpA, nTmpD, nTmpA, nTmpD );
+ }
+ else
+ {
+ // The SwFont is created dynamically and passed in the ctor,
+ // as the CharFormat only returns an SV-Font.
+ // In the dtor of SwNumberPortion, the SwFont is deleted.
+ const SwAttrSet* pFormat = rNumFormat.GetCharFormat() ?
+ &rNumFormat.GetCharFormat()->GetAttrSet() :
+ nullptr;
+ const IDocumentSettingAccess* pIDSA = pTextNd->getIDocumentSettingAccess();
+
+ if( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() )
+ {
+ const std::optional<vcl::Font> pFormatFnt = rNumFormat.GetBulletFont();
+
+ // Build a new bullet font basing on the current paragraph font:
+ std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));
+
+ // #i53199#
+ if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
+ {
+ // i18463:
+ // Underline style of paragraph font should not be considered
+ // Overline style of paragraph font should not be considered
+ // Weight style of paragraph font should not be considered
+ // Posture style of paragraph font should not be considered
+ pNumFnt->SetUnderline( LINESTYLE_NONE );
+ pNumFnt->SetOverline( LINESTYLE_NONE );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL );
+ }
+
+ // Apply the explicit attributes from the character style
+ // associated with the numbering to the new bullet font.
+ if( pFormat )
+ pNumFnt->SetDiffFnt( pFormat, pIDSA );
+
+ checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);
+
+ if ( pFormatFnt )
+ {
+ const SwFontScript nAct = pNumFnt->GetActual();
+ pNumFnt->SetFamily( pFormatFnt->GetFamilyType(), nAct );
+ pNumFnt->SetName( pFormatFnt->GetFamilyName(), nAct );
+ pNumFnt->SetStyleName( pFormatFnt->GetStyleName(), nAct );
+ pNumFnt->SetCharSet( pFormatFnt->GetCharSet(), nAct );
+ pNumFnt->SetPitch( pFormatFnt->GetPitch(), nAct );
+ }
+
+ // we do not allow a vertical font
+ pNumFnt->SetVertical( pNumFnt->GetOrientation(),
+ m_pFrame->IsVertical() );
+
+ lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt );
+
+ // --> OD 2008-01-23 #newlistelevelattrs#
+ if (rNumFormat.GetBulletChar())
+ {
+ pRet = new SwBulletPortion(rNumFormat.GetBulletChar(),
+ pTextNd->GetLabelFollowedBy(),
+ std::move(pNumFnt),
+ bLeft, bCenter, nMinDist,
+ bLabelAlignmentPosAndSpaceModeActive);
+ }
+ }
+ else
+ {
+ // Show Changes mode shows the actual numbering (SwListRedlineType::HIDDEN) and
+ // the original one (SwListRedlineType::ORIGTEXT) instead of the fake numbering
+ // (SwListRedlineType::SHOW, which counts removed and inserted numbered paragraphs
+ // in a single list)
+ bool bHasHiddenNum = false;
+ OUString aText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::HIDDEN) );
+ const SwDoc& rDoc = pTextNd->GetDoc();
+ const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
+ if ( rTable.size() && !rInf.GetVsh()->GetLayout()->IsHideRedlines() )
+ {
+ OUString aHiddenText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::ORIGTEXT) );
+
+ if ( !aText.isEmpty() || !aHiddenText.isEmpty() )
+ {
+ bool bDisplayChangedParagraphNumbering = officecfg::Office::Writer::Comparison::DisplayChangedParagraphNumbering::get();
+ if (bDisplayChangedParagraphNumbering && aText != aHiddenText && !aHiddenText.isEmpty())
+ {
+ bHasHiddenNum = true;
+ // show also original number after the actual one enclosed in [ and ],
+ // and replace tabulator with space to avoid messy indentation
+ // resulted by the longer numbering, e.g. "1.[2.]" instead of "1.".
+ aText = aText + "[" + aHiddenText + "]"
+ + pTextNd->GetLabelFollowedBy().replaceAll("\t", " ");
+ }
+ else if (!aText.isEmpty())
+ aText += pTextNd->GetLabelFollowedBy();
+ }
+ }
+ else if (pTextNd->getIDocumentSettingAccess()->get(DocumentSettingId::NO_NUMBERING_SHOW_FOLLOWBY)
+ || !aText.isEmpty())
+ aText += pTextNd->GetLabelFollowedBy();
+
+ // Not just an optimization ...
+ // A number portion without text will be assigned a width of 0.
+ // The succeeding text portion will flow into the BreakCut in the BreakLine,
+ // although we have rInf.GetLast()->GetFlyPortion()!
+ if( !aText.isEmpty() )
+ {
+
+ // Build a new numbering font basing on the current paragraph font:
+ std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));
+
+ const SwTextNode& rTextNode = *rInf.GetTextFrame()->GetTextNodeForParaProps();
+ if (const SwpHints* pHints = rTextNode.GetpSwpHints())
+ {
+ // Also look for an empty character hint that sits at the paragraph end:
+ for (size_t i = 0; i < pHints->Count(); ++i)
+ {
+ const SwTextAttr* pHint = pHints->GetSortedByEnd(i);
+ if (pHint->Which() == RES_TXTATR_AUTOFMT && pHint->GetEnd()
+ && pHint->GetStart() == *pHint->GetEnd()
+ && pHint->GetStart() == rTextNode.GetText().getLength())
+ {
+ std::shared_ptr<SfxItemSet> pSet
+ = pHint->GetAutoFormat().GetStyleHandle();
+ if (pSet)
+ {
+ pNumFnt->SetDiffFnt(pSet.get(), pIDSA);
+ break;
+ }
+ }
+ }
+ }
+
+ // #i53199#
+ if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
+ {
+ // i18463:
+ // Underline style of paragraph font should not be considered
+ pNumFnt->SetUnderline( LINESTYLE_NONE );
+ // Overline style of paragraph font should not be considered
+ pNumFnt->SetOverline( LINESTYLE_NONE );
+ }
+
+ // Apply the explicit attributes from the character style
+ // associated with the numbering to the new bullet font.
+ if( pFormat )
+ pNumFnt->SetDiffFnt( pFormat, pIDSA );
+
+ checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);
+
+ if ( !lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ) && bHasHiddenNum )
+ pNumFnt->SetColor(NON_PRINTING_CHARACTER_COLOR);
+
+ // we do not allow a vertical font
+ pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() );
+
+ pRet = new SwNumberPortion( aText, std::move(pNumFnt),
+ bLeft, bCenter, nMinDist,
+ bLabelAlignmentPosAndSpaceModeActive );
+ }
+ }
+ }
+ }
+ return pRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx
new file mode 100644
index 0000000000..ff5566f537
--- /dev/null
+++ b/sw/source/core/text/txtfly.cxx
@@ -0,0 +1,1493 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/outdev.hxx>
+
+#include <pagefrm.hxx>
+#include <rootfrm.hxx>
+#include <pam.hxx>
+#include <swfont.hxx>
+#include <swregion.hxx>
+#include <dflyobj.hxx>
+#include <drawfont.hxx>
+#include <flyfrm.hxx>
+#include <flyfrms.hxx>
+#include <fmtornt.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <ndtxt.hxx>
+#include <txtfly.hxx>
+#include "inftxt.hxx"
+#include "porrst.hxx"
+#include "txtpaint.hxx"
+#include <notxtfrm.hxx>
+#include <fmtcnct.hxx>
+#include <svx/obj3d.hxx>
+#include <editeng/txtrange.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <fmtsrnd.hxx>
+#include <fmtanchr.hxx>
+#include <frmfmt.hxx>
+#include <fmtfollowtextflow.hxx>
+#include <pagedesc.hxx>
+#include <sortedobjs.hxx>
+#include <IDocumentDrawModelAccess.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <formatlinebreak.hxx>
+#include <svx/svdoedge.hxx>
+
+#ifdef DBG_UTIL
+#include <viewsh.hxx>
+#include <doc.hxx>
+#endif
+
+using namespace ::com::sun::star;
+
+namespace
+{
+ // #i68520#
+ struct AnchoredObjOrder
+ {
+ bool mbR2L;
+ SwRectFn mfnRect;
+
+ AnchoredObjOrder( const bool bR2L,
+ SwRectFn fnRect )
+ : mbR2L( bR2L ),
+ mfnRect( fnRect )
+ {}
+
+ bool operator()( const SwAnchoredObject* pListedAnchoredObj,
+ const SwAnchoredObject* pNewAnchoredObj )
+ {
+ const SwRect& aBoundRectOfListedObj( pListedAnchoredObj->GetObjRectWithSpaces() );
+ const SwRect& aBoundRectOfNewObj( pNewAnchoredObj->GetObjRectWithSpaces() );
+ if ( ( mbR2L &&
+ ( (aBoundRectOfListedObj.*mfnRect->fnGetRight)() ==
+ (aBoundRectOfNewObj.*mfnRect->fnGetRight)() ) ) ||
+ ( !mbR2L &&
+ ( (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() ==
+ (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() ) ) )
+ {
+ SwTwips nTopDiff =
+ (*mfnRect->fnYDiff)( (aBoundRectOfNewObj.*mfnRect->fnGetTop)(),
+ (aBoundRectOfListedObj.*mfnRect->fnGetTop)() );
+ if ( nTopDiff == 0 &&
+ ( ( mbR2L &&
+ ( (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() >
+ (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() ) ) ||
+ ( !mbR2L &&
+ ( (aBoundRectOfNewObj.*mfnRect->fnGetRight)() <
+ (aBoundRectOfListedObj.*mfnRect->fnGetRight)() ) ) ) )
+ {
+ return true;
+ }
+ else if ( nTopDiff > 0 )
+ {
+ return true;
+ }
+ }
+ else if ( ( mbR2L &&
+ ( (aBoundRectOfListedObj.*mfnRect->fnGetRight)() >
+ (aBoundRectOfNewObj.*mfnRect->fnGetRight)() ) ) ||
+ ( !mbR2L &&
+ ( (aBoundRectOfListedObj.*mfnRect->fnGetLeft)() <
+ (aBoundRectOfNewObj.*mfnRect->fnGetLeft)() ) ) )
+ {
+ return true;
+ }
+
+ return false;
+ }
+ };
+}
+
+SwContourCache::SwContourCache() :
+ mnPointCount( 0 )
+{
+}
+
+SwContourCache::~SwContourCache()
+{
+}
+
+void SwContourCache::ClrObject( sal_uInt16 nPos )
+{
+ mnPointCount -= mvItems[ nPos ].mxTextRanger->GetPointCount();
+ mvItems.erase(mvItems.begin() + nPos);
+}
+
+void ClrContourCache( const SdrObject *pObj )
+{
+ if( pContourCache && pObj )
+ for( sal_uInt16 i = 0; i < pContourCache->GetCount(); ++i )
+ if( pObj == pContourCache->GetObject( i ) )
+ {
+ pContourCache->ClrObject( i );
+ break;
+ }
+}
+
+void ClrContourCache()
+{
+ if( pContourCache )
+ {
+ pContourCache->mvItems.clear();
+ pContourCache->mnPointCount = 0;
+ }
+}
+
+// #i68520#
+SwRect SwContourCache::CalcBoundRect( const SwAnchoredObject* pAnchoredObj,
+ const SwRect &rLine,
+ const SwTextFrame* pFrame,
+ const tools::Long nXPos,
+ const bool bRight )
+{
+ SwRect aRet;
+ const SwFrameFormat* pFormat = &(pAnchoredObj->GetFrameFormat());
+ bool bHandleContour(pFormat->GetSurround().IsContour());
+
+ if(!bHandleContour)
+ {
+ // RotateFlyFrame3: Object has no set contour, but for rotated
+ // FlyFrames we can create a 'default' contour to make text
+ // flow around the free, non-covered
+ const SwFlyFreeFrame* pSwFlyFreeFrame(dynamic_cast< const SwFlyFreeFrame* >(pAnchoredObj));
+
+ if(nullptr != pSwFlyFreeFrame && pSwFlyFreeFrame->supportsAutoContour())
+ {
+ bHandleContour = true;
+ }
+ }
+
+ if( bHandleContour &&
+ ( pAnchoredObj->DynCastFlyFrame() == nullptr ||
+ ( static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower() &&
+ static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower()->IsNoTextFrame() ) ) )
+ {
+ aRet = pAnchoredObj->GetObjRectWithSpaces();
+ if( aRet.Overlaps( rLine ) )
+ {
+ if( !pContourCache )
+ pContourCache = new SwContourCache;
+
+ aRet = pContourCache->ContourRect(
+ pFormat, pAnchoredObj->GetDrawObj(), pFrame, rLine, nXPos, bRight );
+ }
+ else
+ aRet.Width( 0 );
+ }
+ else
+ {
+ aRet = pAnchoredObj->GetObjRectWithSpaces();
+ }
+
+ return aRet;
+}
+
+SwRect SwContourCache::ContourRect( const SwFormat* pFormat,
+ const SdrObject* pObj, const SwTextFrame* pFrame, const SwRect &rLine,
+ const tools::Long nXPos, const bool bRight )
+{
+ SwRect aRet;
+ sal_uInt16 nPos = 0; // Search in the Cache
+ while( nPos < GetCount() && pObj != mvItems[ nPos ].mpSdrObj )
+ ++nPos;
+ if( GetCount() == nPos ) // Not found
+ {
+ if( GetCount() == POLY_CNT )
+ {
+ mnPointCount -= mvItems.back().mxTextRanger->GetPointCount();
+ mvItems.pop_back();
+ }
+ ::basegfx::B2DPolyPolygon aPolyPolygon;
+ std::optional<::basegfx::B2DPolyPolygon> pPolyPolygon;
+
+ if ( auto pVirtFlyDrawObj = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) )
+ {
+ // GetContour() causes the graphic to be loaded, which may cause
+ // the graphic to change its size, call ClrObject()
+ tools::PolyPolygon aPoly;
+ if( !pVirtFlyDrawObj->GetFlyFrame()->GetContour( aPoly ) )
+ aPoly = tools::PolyPolygon( pVirtFlyDrawObj->
+ GetFlyFrame()->getFrameArea().SVRect() );
+ aPolyPolygon.clear();
+ aPolyPolygon.append(aPoly.getB2DPolyPolygon());
+ }
+ else
+ {
+ if( DynCastE3dObject( pObj ) == nullptr )
+ {
+ aPolyPolygon = pObj->TakeXorPoly();
+ }
+
+ pPolyPolygon = pObj->TakeContour();
+ }
+ const SvxLRSpaceItem &rLRSpace = pFormat->GetLRSpace();
+ const SvxULSpaceItem &rULSpace = pFormat->GetULSpace();
+ CacheItem item {
+ pObj, // due to #37347 the Object must be entered only after GetContour()
+ std::make_unique<TextRanger>( aPolyPolygon, pPolyPolygon ? &*pPolyPolygon : nullptr, 20,
+ o3tl::narrowing<sal_uInt16>(rLRSpace.GetLeft()), o3tl::narrowing<sal_uInt16>(rLRSpace.GetRight()),
+ pFormat->GetSurround().IsOutside(), false, pFrame->IsVertical() )
+ };
+ mvItems.insert(mvItems.begin(), std::move(item));
+ mvItems[0].mxTextRanger->SetUpper( rULSpace.GetUpper() );
+ mvItems[0].mxTextRanger->SetLower( rULSpace.GetLower() );
+
+ pPolyPolygon.reset();
+
+ mnPointCount += mvItems[0].mxTextRanger->GetPointCount();
+ while( mnPointCount > POLY_MAX && mvItems.size() > POLY_MIN )
+ {
+ mnPointCount -= mvItems.back().mxTextRanger->GetPointCount();
+ mvItems.pop_back();
+ }
+ }
+ else if( nPos )
+ {
+ CacheItem item = std::move(mvItems[nPos]);
+ mvItems.erase(mvItems.begin() + nPos);
+ mvItems.insert(mvItems.begin(), std::move(item));
+ }
+ SwRectFnSet aRectFnSet(pFrame);
+ tools::Long nTmpTop = aRectFnSet.GetTop(rLine);
+ // fnGetBottom is top + height
+ tools::Long nTmpBottom = aRectFnSet.GetBottom(rLine);
+
+ Range aRange( std::min( nTmpTop, nTmpBottom ), std::max( nTmpTop, nTmpBottom ) );
+
+ std::deque<tools::Long>* pTmp = mvItems[0].mxTextRanger->GetTextRanges( aRange );
+
+ const size_t nCount = pTmp->size();
+ if( 0 != nCount )
+ {
+ size_t nIdx = 0;
+ while( nIdx < nCount && (*pTmp)[ nIdx ] < nXPos )
+ ++nIdx;
+ bool bOdd = nIdx % 2;
+ bool bSet = true;
+ if( bOdd )
+ --nIdx; // within interval
+ else if( ! bRight && ( nIdx >= nCount || (*pTmp)[ nIdx ] != nXPos ) )
+ {
+ if( nIdx )
+ nIdx -= 2; // an interval to the left
+ else
+ bSet = false; // before the first interval
+ }
+
+ if( bSet && nIdx < nCount )
+ {
+ aRectFnSet.SetTopAndHeight( aRet, aRectFnSet.GetTop(rLine),
+ aRectFnSet.GetHeight(rLine) );
+ aRectFnSet.SetLeft( aRet, (*pTmp)[ nIdx ] );
+ aRectFnSet.SetRight( aRet, (*pTmp)[ nIdx + 1 ] + 1 );
+ }
+ }
+ return aRet;
+}
+
+SwTextFly::SwTextFly()
+ : m_pPage(nullptr)
+ , mpCurrAnchoredObj(nullptr)
+ , m_pCurrFrame(nullptr)
+ , m_pMaster(nullptr)
+ , m_nMinBottom(0)
+ , m_nNextTop(0)
+ , m_nCurrFrameNodeIndex(0)
+ , m_bOn(false)
+ , m_bTopRule(false)
+ , mbIgnoreCurrentFrame(false)
+ , mbIgnoreContour(false)
+ , mbIgnoreObjsInHeaderFooter(false)
+
+{
+}
+
+SwTextFly::SwTextFly( const SwTextFrame *pFrame )
+{
+ CtorInitTextFly( pFrame );
+}
+
+SwTextFly::SwTextFly( const SwTextFly& rTextFly )
+{
+ m_pPage = rTextFly.m_pPage;
+ mpCurrAnchoredObj = rTextFly.mpCurrAnchoredObj;
+ m_pCurrFrame = rTextFly.m_pCurrFrame;
+ m_pMaster = rTextFly.m_pMaster;
+ if( rTextFly.mpAnchoredObjList )
+ {
+ mpAnchoredObjList.reset( new SwAnchoredObjList( *(rTextFly.mpAnchoredObjList) ) );
+ }
+
+ m_bOn = rTextFly.m_bOn;
+ m_bTopRule = rTextFly.m_bTopRule;
+ m_nMinBottom = rTextFly.m_nMinBottom;
+ m_nNextTop = rTextFly.m_nNextTop;
+ m_nCurrFrameNodeIndex = rTextFly.m_nCurrFrameNodeIndex;
+ mbIgnoreCurrentFrame = rTextFly.mbIgnoreCurrentFrame;
+ mbIgnoreContour = rTextFly.mbIgnoreContour;
+ mbIgnoreObjsInHeaderFooter = rTextFly.mbIgnoreObjsInHeaderFooter;
+}
+
+SwTextFly::~SwTextFly()
+{
+}
+
+void SwTextFly::CtorInitTextFly( const SwTextFrame *pFrame )
+{
+ mbIgnoreCurrentFrame = false;
+ mbIgnoreContour = false;
+ mbIgnoreObjsInHeaderFooter = false;
+ m_pPage = pFrame->FindPageFrame();
+ const SwFlyFrame* pTmp = pFrame->FindFlyFrame();
+ // #i68520#
+ mpCurrAnchoredObj = pTmp;
+ m_pCurrFrame = pFrame;
+ m_pMaster = m_pCurrFrame->IsFollow() ? nullptr : m_pCurrFrame;
+ // If we're not overlapped by a frame or if a FlyCollection does not exist
+ // at all, we switch off forever.
+ // It could be, however, that a line is added while formatting, that
+ // extends into a frame.
+ // That's why we do not optimize for: bOn = pSortedFlys && IsAnyFrame();
+ m_bOn = m_pPage->GetSortedObjs() != nullptr;
+ m_bTopRule = true;
+ m_nMinBottom = 0;
+ m_nNextTop = 0;
+ m_nCurrFrameNodeIndex = NODE_OFFSET_MAX;
+}
+
+SwRect SwTextFly::GetFrame_( const SwRect &rRect ) const
+{
+ SwRect aRet;
+ if( ForEach( rRect, &aRet, true ) )
+ {
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+ aRectFnSet.SetTop( aRet, aRectFnSet.GetTop(rRect) );
+
+ // Do not always adapt the bottom
+ const SwTwips nRetBottom = aRectFnSet.GetBottom(aRet);
+ const SwTwips nRectBottom = aRectFnSet.GetBottom(rRect);
+ if ( aRectFnSet.YDiff( nRetBottom, nRectBottom ) > 0 ||
+ aRectFnSet.GetHeight(aRet) < 0 )
+ aRectFnSet.SetBottom( aRet, nRectBottom );
+ }
+ return aRet;
+}
+
+bool SwTextFly::IsAnyFrame() const
+{
+ SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame));
+
+ OSL_ENSURE( m_bOn, "IsAnyFrame: Why?" );
+ SwRect aRect(m_pCurrFrame->getFrameArea().Pos() + m_pCurrFrame->getFramePrintArea().Pos(),
+ m_pCurrFrame->getFramePrintArea().SSize());
+
+ return ForEach( aRect, nullptr, false );
+}
+
+bool SwTextFly::IsAnyObj( const SwRect &rRect ) const
+{
+ OSL_ENSURE( m_bOn, "SwTextFly::IsAnyObj: Who's knocking?" );
+
+ SwRect aRect( rRect );
+ if ( aRect.IsEmpty() )
+ {
+ aRect = SwRect(m_pCurrFrame->getFrameArea().Pos() + m_pCurrFrame->getFramePrintArea().Pos(),
+ m_pCurrFrame->getFramePrintArea().SSize());
+
+ SwTwips nLower = m_pCurrFrame->GetLowerMarginForFlyIntersect();
+ if (nLower > 0)
+ {
+ aRect.AddBottom(nLower);
+ }
+ }
+
+ const SwSortedObjs *pSorted = m_pPage->GetSortedObjs();
+ if( pSorted ) // bOn actually makes sure that we have objects on the side,
+ // but who knows who deleted something in the meantime?
+ {
+ for ( size_t i = 0; i < pSorted->size(); ++i )
+ {
+ const SwAnchoredObject* pObj = (*pSorted)[i];
+
+ const SwRect aBound( pObj->GetObjRectWithSpaces() );
+
+ // Optimization
+ if( pObj->GetObjRect().Left() > aRect.Right() )
+ continue;
+
+ // #i68520#
+ if( mpCurrAnchoredObj != pObj && aBound.Overlaps( aRect ) )
+ return true;
+ }
+ }
+ return false;
+}
+
+const SwTextFrame* SwTextFly::GetMaster_()
+{
+ m_pMaster = m_pCurrFrame;
+ while (m_pMaster && m_pMaster->IsFollow())
+ m_pMaster = m_pMaster->FindMaster();
+ return m_pMaster;
+}
+
+void SwTextFly::DrawTextOpaque( SwDrawTextInfo &rInf )
+{
+ SwSaveClip aClipSave( rInf.GetpOut() );
+ SwRect aRect( rInf.GetPos(), rInf.GetSize() );
+ if( rInf.GetSpace() )
+ {
+ TextFrameIndex const nTmpLen = TextFrameIndex(COMPLETE_STRING) == rInf.GetLen()
+ ? TextFrameIndex(rInf.GetText().getLength())
+ : rInf.GetLen();
+ if( rInf.GetSpace() > 0 )
+ {
+ sal_Int32 nSpaceCnt = 0;
+ const TextFrameIndex nEndPos = rInf.GetIdx() + nTmpLen;
+ for (TextFrameIndex nPos = rInf.GetIdx(); nPos < nEndPos; ++nPos)
+ {
+ if (CH_BLANK == rInf.GetText()[sal_Int32(nPos)])
+ ++nSpaceCnt;
+ }
+ if( nSpaceCnt )
+ aRect.Width( aRect.Width() + nSpaceCnt * rInf.GetSpace() );
+ }
+ else
+ aRect.Width( aRect.Width() - sal_Int32(nTmpLen) * rInf.GetSpace() );
+ }
+
+ if( aClipSave.IsOn() && rInf.GetOut().IsClipRegion() )
+ {
+ SwRect aClipRect( rInf.GetOut().GetClipRegion().GetBoundRect() );
+ aRect.Intersection( aClipRect );
+ }
+
+ SwRegionRects aRegion( aRect );
+
+ bool bOpaque = false;
+ // #i68520#
+ const sal_uInt32 nCurrOrd = mpCurrAnchoredObj
+ ? mpCurrAnchoredObj->GetDrawObj()->GetOrdNum()
+ : SAL_MAX_UINT32;
+ OSL_ENSURE( !m_bTopRule, "DrawTextOpaque: Wrong TopRule" );
+
+ // #i68520#
+ const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 );
+ if (nCount > 0)
+ {
+ const SdrLayerID nHellId = m_pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId();
+ for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i )
+ {
+ // #i68520#
+ const SwAnchoredObject* pTmpAnchoredObj = (*mpAnchoredObjList)[i];
+ const SwFlyFrame* pFly = pTmpAnchoredObj->DynCastFlyFrame();
+ if( pFly && mpCurrAnchoredObj != pTmpAnchoredObj )
+ {
+ // #i68520#
+ if( aRegion.GetOrigin().Overlaps( pFly->getFrameArea() ) )
+ {
+ const SwFrameFormat *pFormat = pFly->GetFormat();
+ const SwFormatSurround &rSur = pFormat->GetSurround();
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ // Only the ones who are opaque and more to the top
+ if( ! pFly->IsBackgroundTransparent() &&
+ css::text::WrapTextMode_THROUGH == rSur.GetSurround() &&
+ ( !rSur.IsAnchorOnly() ||
+ // #i68520#
+ GetMaster() == pFly->GetAnchorFrame() ||
+ ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) &&
+ (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId())
+ )
+ ) &&
+ // #i68520#
+ pTmpAnchoredObj->GetDrawObj()->GetLayer() != nHellId &&
+ nCurrOrd < pTmpAnchoredObj->GetDrawObj()->GetOrdNum()
+ )
+ {
+ // Except for the content is transparent
+ const SwNoTextFrame *pNoText =
+ pFly->Lower() && pFly->Lower()->IsNoTextFrame()
+ ? static_cast<const SwNoTextFrame*>(pFly->Lower())
+ : nullptr;
+ if ( !pNoText ||
+ (!pNoText->IsTransparent() && !rSur.IsContour()) )
+ {
+ bOpaque = true;
+ aRegion -= pFly->getFrameArea();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Point aPos( rInf.GetPos().X(), rInf.GetPos().Y() + rInf.GetAscent() );
+ const Point aOldPos(rInf.GetPos());
+ rInf.SetPos( aPos );
+
+ if( !bOpaque )
+ {
+ if( rInf.GetKern() )
+ rInf.GetFont()->DrawStretchText_( rInf );
+ else
+ rInf.GetFont()->DrawText_( rInf );
+ rInf.SetPos(aOldPos);
+ return;
+ }
+ else if( !aRegion.empty() )
+ {
+ // What a huge effort ...
+ SwSaveClip aClipVout( rInf.GetpOut() );
+ for( size_t i = 0; i < aRegion.size(); ++i )
+ {
+ SwRect &rRect = aRegion[i];
+ if( rRect != aRegion.GetOrigin() )
+ aClipVout.ChgClip( rRect );
+ if( rInf.GetKern() )
+ rInf.GetFont()->DrawStretchText_( rInf );
+ else
+ rInf.GetFont()->DrawText_( rInf );
+ }
+ }
+ rInf.SetPos(aOldPos);
+}
+
+void SwTextFly::DrawFlyRect( OutputDevice* pOut, const SwRect &rRect )
+{
+ SwRegionRects aRegion( rRect );
+ OSL_ENSURE( !m_bTopRule, "DrawFlyRect: Wrong TopRule" );
+ // #i68520#
+ const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 );
+ if (nCount > 0)
+ {
+ const SdrLayerID nHellId = m_pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId();
+ for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i )
+ {
+ // #i68520#
+ const SwAnchoredObject* pAnchoredObjTmp = (*mpAnchoredObjList)[i];
+ if (mpCurrAnchoredObj == pAnchoredObjTmp)
+ continue;
+
+ // #i68520#
+ const SwFlyFrame* pFly = pAnchoredObjTmp->DynCastFlyFrame();
+ if (pFly)
+ {
+ // #i68520#
+ const SwFormatSurround& rSur = pAnchoredObjTmp->GetFrameFormat().GetSurround();
+
+ // OD 24.01.2003 #106593# - correct clipping of fly frame area.
+ // Consider that fly frame background/shadow can be transparent
+ // and <SwAlignRect(..)> fly frame area
+ // #i47804# - consider transparent graphics
+ // and OLE objects.
+ bool bClipFlyArea =
+ ( ( css::text::WrapTextMode_THROUGH == rSur.GetSurround() )
+ // #i68520#
+ ? (pAnchoredObjTmp->GetDrawObj()->GetLayer() != nHellId)
+ : !rSur.IsContour() ) &&
+ !pFly->IsBackgroundTransparent() &&
+ ( !pFly->Lower() ||
+ !pFly->Lower()->IsNoTextFrame() ||
+ !static_cast<const SwNoTextFrame*>(pFly->Lower())->IsTransparent() );
+ if ( bClipFlyArea )
+ {
+ // #i68520#
+ SwRect aFly( pAnchoredObjTmp->GetObjRect() );
+ // OD 24.01.2003 #106593#
+ ::SwAlignRect( aFly, m_pPage->getRootFrame()->GetCurrShell(), pOut );
+ if( !aFly.IsEmpty() )
+ aRegion -= aFly;
+ }
+ }
+ }
+ }
+
+ for( size_t i = 0; i < aRegion.size(); ++i )
+ {
+ pOut->DrawRect( aRegion[i].SVRect() );
+ }
+}
+
+/**
+ * #i26945# - change first parameter
+ * Now it's the <SwAnchoredObject> instance of the floating screen object
+ */
+bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj,
+ const bool bInFootnote,
+ const bool bInFooterOrHeader )
+{
+ // #i68520#
+ // <mpCurrAnchoredObj> is set, if <m_pCurrFrame> is inside a fly frame
+ if( _pAnchoredObj != mpCurrAnchoredObj )
+ {
+ // #i26945#
+ const SdrObject* pNew = _pAnchoredObj->GetDrawObj();
+ // #102344# Ignore connectors which have one or more connections
+ if (const SdrEdgeObj* pEdgeObj = dynamic_cast<const SdrEdgeObj*>(pNew))
+ {
+ if (pEdgeObj->GetConnectedNode(true) || pEdgeObj->GetConnectedNode(false))
+ {
+ return false;
+ }
+ }
+
+ if( ( bInFootnote || bInFooterOrHeader ) && m_bTopRule )
+ {
+ // #i26945#
+ const SwFrameFormat& rFrameFormat = _pAnchoredObj->GetFrameFormat();
+ const SwFormatAnchor& rNewA = rFrameFormat.GetAnchor();
+ if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId())
+ {
+ if ( bInFootnote )
+ return false;
+
+ if ( bInFooterOrHeader )
+ {
+ const SwFormatVertOrient& aVert( rFrameFormat.GetVertOrient() );
+ bool bVertPrt = aVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA ||
+ aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA;
+ if( bVertPrt )
+ return false;
+ }
+ }
+ }
+
+ // #i68520#
+ // bEvade: consider pNew, if we are not inside a fly
+ // consider pNew, if pNew is lower of <mpCurrAnchoredObj>
+ bool bEvade = !mpCurrAnchoredObj ||
+ Is_Lower_Of( mpCurrAnchoredObj->DynCastFlyFrame(), pNew);
+
+ auto pFly = _pAnchoredObj->DynCastFlyFrame();
+ if (pFly && pFly->IsFlySplitAllowed())
+ {
+ // Check if _pAnchoredObj is a split fly inside an other split fly. Always collect such
+ // flys, otherwise the inner anchor text will overlap with the inner fly.
+ SwFrame* pFlyAnchor = const_cast<SwAnchoredObject*>(_pAnchoredObj)
+ ->GetAnchorFrameContainingAnchPos();
+ if (pFlyAnchor && pFlyAnchor->IsInFly())
+ {
+ auto pOuterFly = pFlyAnchor->FindFlyFrame();
+ if (pOuterFly && pOuterFly->IsFlySplitAllowed())
+ {
+ return true;
+ }
+ }
+ }
+
+ if ( !bEvade )
+ {
+ // We are currently inside a fly frame and pNew is not
+ // inside this fly frame. We can do some more checks if
+ // we have to consider pNew.
+
+ // If bTopRule is not set, we ignore the frame types.
+ // We directly check the z-order
+ if ( !m_bTopRule )
+ bEvade = true;
+ else
+ {
+ // Within chained Flys we only avoid Lower
+ // #i68520#
+ const SwFormatChain &rChain = mpCurrAnchoredObj->GetFrameFormat().GetChain();
+ if ( !rChain.GetPrev() && !rChain.GetNext() )
+ {
+ // #i26945#
+ const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat().GetAnchor();
+ // #i68520#
+ const SwFormatAnchor& rCurrA = mpCurrAnchoredObj->GetFrameFormat().GetAnchor();
+
+ // If <mpCurrAnchoredObj> is anchored as character, its content
+ // does not wrap around pNew
+ if (RndStdIds::FLY_AS_CHAR == rCurrA.GetAnchorId())
+ return false;
+
+ // If pNew is anchored to page and <mpCurrAnchoredObj is not anchored
+ // to page, the content of <mpCurrAnchoredObj> does not wrap around pNew
+ // If both pNew and <mpCurrAnchoredObj> are anchored to page, we can do
+ // some more checks
+ if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId())
+ {
+ if (RndStdIds::FLY_AT_PAGE == rCurrA.GetAnchorId())
+ {
+ bEvade = true;
+ }
+ else
+ return false;
+ }
+ else if (RndStdIds::FLY_AT_PAGE == rCurrA.GetAnchorId())
+ return false; // Page anchored ones only avoid page anchored ones
+ else if (RndStdIds::FLY_AT_FLY == rNewA.GetAnchorId())
+ bEvade = true; // Non-page anchored ones avoid frame anchored ones
+ else if( RndStdIds::FLY_AT_FLY == rCurrA.GetAnchorId() )
+ return false; // Frame anchored ones do not avoid paragraph anchored ones
+ // #i57062#
+ // In order to avoid loop situation, it's decided to adjust
+ // the wrapping behaviour of content of at-paragraph/at-character
+ // anchored objects to one in the page header/footer and
+ // the document body --> content of at-paragraph/at-character
+ // anchored objects doesn't wrap around each other.
+ else
+ return false;
+ }
+ }
+
+ // But: we never avoid a subordinate one and additionally we only avoid when overlapping.
+ // #i68520#
+ bEvade &= ( mpCurrAnchoredObj->GetDrawObj()->GetOrdNum() < pNew->GetOrdNum() );
+ if( bEvade )
+ {
+ // #i68520#
+ const SwRect& aTmp( _pAnchoredObj->GetObjRectWithSpaces() );
+ if ( !aTmp.Overlaps( mpCurrAnchoredObj->GetObjRectWithSpaces() ) )
+ bEvade = false;
+ }
+ }
+
+ if ( bEvade )
+ {
+ // #i26945#
+ const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat().GetAnchor();
+ OSL_ENSURE( RndStdIds::FLY_AS_CHAR != rNewA.GetAnchorId(),
+ "Don't call GetTop with a FlyInContentFrame" );
+ if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId())
+ return true; // We always avoid page anchored ones
+
+ // If Flys anchored at paragraph are caught in a FlyCnt, then
+ // their influence ends at the borders of the FlyCnt!
+ // If we are currently formatting the text of the FlyCnt, then
+ // it has to get out of the way of the Frame anchored at paragraph!
+ // m_pCurrFrame is the anchor of pNew?
+ // #i26945#
+ const SwFrame* pTmp = _pAnchoredObj->GetAnchorFrame();
+ if (pTmp == m_pCurrFrame)
+ return true;
+ if( pTmp->IsTextFrame() && ( pTmp->IsInFly() || pTmp->IsInFootnote() ) )
+ {
+ // #i26945#
+ Point aPos = _pAnchoredObj->GetObjRect().Pos();
+ pTmp = GetVirtualUpper( pTmp, aPos );
+ }
+ // #i26945#
+ // If <pTmp> is a text frame inside a table, take the upper
+ // of the anchor frame, which contains the anchor position.
+ else if ( pTmp->IsTextFrame() && pTmp->IsInTab() )
+ {
+ pTmp = const_cast<SwAnchoredObject*>(_pAnchoredObj)
+ ->GetAnchorFrameContainingAnchPos()->GetUpper();
+ }
+ // #i28701# - consider all objects in same context,
+ // if wrapping style is considered on object positioning.
+ // Thus, text will wrap around negative positioned objects.
+ // #i3317# - remove condition on checking,
+ // if wrappings style is considered on object positioning.
+ // Thus, text is wrapping around negative positioned objects.
+ // #i35640# - no consideration of negative
+ // positioned objects, if wrapping style isn't considered on
+ // object position and former text wrapping is applied.
+ // This condition is typically for documents imported from the
+ // OpenOffice.org file format.
+ const IDocumentSettingAccess* pIDSA = &m_pCurrFrame->GetDoc().getIDocumentSettingAccess();
+ if ( ( pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) ||
+ !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ) &&
+ ::FindContext( pTmp, SwFrameType::None ) == ::FindContext(m_pCurrFrame, SwFrameType::None))
+ {
+ return true;
+ }
+
+ const SwFrame* pHeader = nullptr;
+ if (m_pCurrFrame->GetNext() != pTmp &&
+ (IsFrameInSameContext( pTmp, m_pCurrFrame ) ||
+ // #i13832#, #i24135# wrap around objects in page header
+ ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) &&
+ nullptr != ( pHeader = pTmp->FindFooterOrHeader() ) &&
+ m_pCurrFrame->IsInDocBody())))
+ {
+ if( pHeader || RndStdIds::FLY_AT_FLY == rNewA.GetAnchorId() )
+ return true;
+
+ // Compare indices:
+ // The Index of the other is retrieved from the anchor attr.
+ SwNodeOffset nTmpIndex = rNewA.GetAnchorNode()->GetIndex();
+ // Now check whether the current paragraph is before the anchor
+ // of the displaced object in the text, then we don't have to
+ // get out of its way.
+ // If possible determine Index via SwFormatAnchor because
+ // otherwise it's quite expensive.
+ if (NODE_OFFSET_MAX == m_nCurrFrameNodeIndex)
+ m_nCurrFrameNodeIndex = m_pCurrFrame->GetTextNodeFirst()->GetIndex();
+
+ if (FrameContainsNode(*m_pCurrFrame, nTmpIndex) || nTmpIndex < m_nCurrFrameNodeIndex)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+SwRect SwTextFly::GetFrameArea() const
+{
+ // i#28701 - consider complete frame area for new text wrapping
+ SwRect aRect;
+ if (m_pCurrFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING))
+ {
+ aRect = m_pCurrFrame->getFramePrintArea();
+ aRect += m_pCurrFrame->getFrameArea().Pos();
+ }
+ else
+ {
+ aRect = m_pCurrFrame->getFrameArea();
+ }
+ return aRect;
+}
+
+// #i68520#
+SwAnchoredObjList& SwTextFly::InitAnchoredObjList()
+{
+ OSL_ENSURE( m_pCurrFrame, "InitFlyList: No Frame, no FlyList" );
+ // #i68520#
+ OSL_ENSURE( !mpAnchoredObjList, "InitFlyList: FlyList already initialized" );
+
+ SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame));
+
+ const SwSortedObjs *pSorted = m_pPage->GetSortedObjs();
+ const size_t nCount = pSorted ? pSorted->size() : 0;
+ // --> #108724# Page header/footer content doesn't have to wrap around
+ // floating screen objects
+ // which was added simply to be compatible with MS Office.
+ // MSO still allows text to wrap around in-table-flies in headers/footers/footnotes
+ const bool bFooterHeader = nullptr != m_pCurrFrame->FindFooterOrHeader();
+ const IDocumentSettingAccess* pIDSA = &m_pCurrFrame->GetDoc().getIDocumentSettingAccess();
+ // #i40155# - check, if frame is marked not to wrap
+ const bool bAllowCompatWrap = m_pCurrFrame->IsInTab() && (bFooterHeader || m_pCurrFrame->IsInFootnote());
+ const bool bWrapAllowed = ( pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ||
+ bAllowCompatWrap ||
+ (!m_pCurrFrame->IsInFootnote() && !bFooterHeader));
+
+ m_bOn = false;
+
+ // #i68520#
+ mpAnchoredObjList.reset(new SwAnchoredObjList);
+
+ if( nCount && bWrapAllowed )
+ {
+ SwRect const aRect(GetFrameArea());
+ // Make ourselves a little smaller than we are,
+ // so that 1-Twip-overlappings are ignored (#49532)
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+ const tools::Long nRight = aRectFnSet.GetRight(aRect) - 1;
+ const tools::Long nLeft = aRectFnSet.GetLeft(aRect) + 1;
+ const bool bR2L = m_pCurrFrame->IsRightToLeft();
+
+ const IDocumentDrawModelAccess& rIDDMA = m_pCurrFrame->GetDoc().getIDocumentDrawModelAccess();
+
+ for( size_t i = 0; i < nCount; ++i )
+ {
+ // #i68520#
+ // do not consider hidden objects
+ // check, if object has to be considered for text wrap
+ // #118809# - If requested, do not consider
+ // objects in page header|footer for text frames not in page
+ // header|footer. This is requested for the calculation of
+ // the base offset for objects <SwTextFrame::CalcBaseOfstForFly()>
+ // #i20505# Do not consider oversized objects
+ SwAnchoredObject* pAnchoredObj = (*pSorted)[ i ];
+ assert(pAnchoredObj);
+ if ( !pAnchoredObj ||
+ !rIDDMA.IsVisibleLayerId( pAnchoredObj->GetDrawObj()->GetLayer() ) ||
+ !pAnchoredObj->ConsiderForTextWrap() ||
+ ( mbIgnoreObjsInHeaderFooter && !bFooterHeader &&
+ pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) ||
+ ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat().GetFollowTextFlow().GetValue() )
+ )
+ {
+ continue;
+ }
+
+ const SwRect aBound( pAnchoredObj->GetObjRectWithSpaces() );
+ if ( nRight < aRectFnSet.GetLeft(aBound) ||
+ aRectFnSet.YDiff( aRectFnSet.GetTop(aRect),
+ aRectFnSet.GetBottom(aBound) ) > 0 ||
+ nLeft > aRectFnSet.GetRight(aBound) ||
+ aRectFnSet.GetHeight(aBound) >
+ 2 * aRectFnSet.GetHeight(m_pPage->getFrameArea()) )
+ {
+ continue;
+ }
+
+ // #i26945# - pass <pAnchoredObj> to method
+ // <GetTop(..)> instead of only the <SdrObject> instance of the
+ // anchored object
+ if (GetTop(pAnchoredObj, m_pCurrFrame->IsInFootnote(), bFooterHeader))
+ {
+ // OD 11.03.2003 #107862# - adjust insert position:
+ // overlapping objects should be sorted from left to right and
+ // inside left to right sorting from top to bottom.
+ // If objects on the same position are found, they are sorted
+ // on its width.
+ // #i68520#
+ {
+ SwAnchoredObjList::iterator aInsPosIter =
+ std::lower_bound( mpAnchoredObjList->begin(),
+ mpAnchoredObjList->end(),
+ pAnchoredObj,
+ AnchoredObjOrder( bR2L, aRectFnSet.FnRect() ) );
+
+ mpAnchoredObjList->insert( aInsPosIter, pAnchoredObj );
+ }
+
+ const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround();
+ // #i68520#
+ if ( rFlyFormat.IsAnchorOnly() &&
+ pAnchoredObj->GetAnchorFrame() == GetMaster() )
+ {
+ const SwFormatVertOrient &rTmpFormat =
+ pAnchoredObj->GetFrameFormat().GetVertOrient();
+ if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() )
+ m_nMinBottom = ( aRectFnSet.IsVert() && m_nMinBottom ) ?
+ std::min( m_nMinBottom, aBound.Left() ) :
+ std::max( m_nMinBottom, aRectFnSet.GetBottom(aBound) );
+ }
+
+ m_bOn = true;
+ }
+ }
+ if( m_nMinBottom )
+ {
+ SwTwips nMax = aRectFnSet.GetPrtBottom(*m_pCurrFrame->GetUpper());
+ if( aRectFnSet.YDiff( m_nMinBottom, nMax ) > 0 )
+ m_nMinBottom = nMax;
+ }
+ }
+
+ // #i68520#
+ return *mpAnchoredObjList;
+}
+
+SwTwips SwTextFly::CalcMinBottom() const
+{
+ SwTwips nRet = 0;
+ const SwContentFrame *pLclMaster = GetMaster();
+ OSL_ENSURE(pLclMaster, "SwTextFly without master");
+ const SwSortedObjs *pDrawObj = pLclMaster ? pLclMaster->GetDrawObjs() : nullptr;
+ const size_t nCount = pDrawObj ? pDrawObj->size() : 0;
+ if( nCount )
+ {
+ SwTwips nEndOfFrame = m_pCurrFrame->getFrameArea().Bottom();
+ for( size_t i = 0; i < nCount; ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*pDrawObj)[ i ];
+ const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround();
+ if( rFlyFormat.IsAnchorOnly() )
+ {
+ const SwFormatVertOrient &rTmpFormat =
+ pAnchoredObj->GetFrameFormat().GetVertOrient();
+ if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() )
+ {
+ const SwRect& aBound( pAnchoredObj->GetObjRectWithSpaces() );
+ if( aBound.Top() < nEndOfFrame )
+ nRet = std::max( nRet, SwTwips(aBound.Bottom()) );
+ }
+ }
+ }
+ SwTwips nMax = m_pCurrFrame->GetUpper()->getFrameArea().Top() +
+ m_pCurrFrame->GetUpper()->getFramePrintArea().Bottom();
+ if( nRet > nMax )
+ nRet = nMax;
+ }
+ return nRet;
+}
+
+SwTwips SwTextFly::GetMaxBottom(const SwBreakPortion& rPortion, const SwTextFormatInfo& rInfo) const
+{
+ // Note that m_pCurrFrame is already swapped at this stage, so it's correct to bypass
+ // SwRectFnSet here.
+ SwTwips nRet = 0;
+ size_t nCount(m_bOn ? GetAnchoredObjList().size() : 0);
+
+ // Get the horizontal position of the break portion in absolute twips. The frame area is in
+ // absolute twips, the frame's print area is relative to the frame area. Finally the portion's
+ // position is relative to the frame's print area.
+ SwTwips nX = rInfo.X();
+ nX += m_pCurrFrame->getFrameArea().Left();
+ nX += m_pCurrFrame->getFramePrintArea().Left();
+
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ const SwAnchoredObject* pAnchoredObj = (*mpAnchoredObjList)[i];
+
+ if (pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader())
+ {
+ // Anchored in the header or footer, ignore it for clearing break purposes.
+ continue;
+ }
+
+ SwRect aRect(pAnchoredObj->GetObjRectWithSpaces());
+
+ if (m_pCurrFrame->IsVertical())
+ {
+ m_pCurrFrame->SwitchVerticalToHorizontal(aRect);
+ }
+
+ if (rPortion.GetClear() == SwLineBreakClear::LEFT)
+ {
+ if (nX < aRect.Left())
+ {
+ // Want to jump down to the first line that's unblocked on the left. This object is
+ // on the right of the break, ignore it.
+ continue;
+ }
+ }
+ if (rPortion.GetClear() == SwLineBreakClear::RIGHT)
+ {
+ if (nX > aRect.Right())
+ {
+ // Want to jump down to the first line that's unblocked on the right. This object is
+ // on the left of the break, ignore it.
+ continue;
+ }
+ }
+ SwTwips nBottom = aRect.Top() + aRect.Height();
+ if (nBottom > nRet)
+ {
+ nRet = nBottom;
+ }
+ }
+ return nRet;
+}
+
+bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const
+{
+ SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame));
+
+ // Optimization
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+
+ // tdf#127235 stop if the area is larger than the page
+ if( aRectFnSet.GetHeight(m_pPage->getFrameArea()) < aRectFnSet.GetHeight(rRect))
+ {
+ // get the doc model description
+ const SwPageDesc* pPageDesc = m_pPage->GetPageDesc();
+
+ // if there is no next page style or it is the same as the current
+ // => stop trying to place the frame (it would end in an infinite loop)
+ if( pPageDesc &&
+ ( !pPageDesc->GetFollow() || pPageDesc->GetFollow() == pPageDesc) )
+ {
+ return false;
+ }
+ }
+
+ bool bRet = false;
+ // #i68520#
+ const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 );
+ if (nCount > 0)
+ {
+ for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i )
+ {
+ // #i68520#
+ const SwAnchoredObject* pAnchoredObj = (*mpAnchoredObjList)[i];
+
+ SwRect aRect( pAnchoredObj->GetObjRectWithSpaces() );
+
+ if( aRectFnSet.GetLeft(aRect) > aRectFnSet.GetRight(rRect) )
+ break;
+
+ // #i68520#
+ if ( mpCurrAnchoredObj != pAnchoredObj && aRect.Overlaps( rRect ) )
+ {
+ // #i68520#
+ const SwFormat* pFormat( &(pAnchoredObj->GetFrameFormat()) );
+ const SwFormatSurround &rSur = pFormat->GetSurround();
+ if( bAvoid )
+ {
+ // If the text flows below, it has no influence on
+ // formatting. In LineIter::DrawText() it is "just"
+ // necessary to cleverly set the ClippingRegions
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ if( ( css::text::WrapTextMode_THROUGH == rSur.GetSurround() &&
+ ( !rSur.IsAnchorOnly() ||
+ // #i68520#
+ GetMaster() == pAnchoredObj->GetAnchorFrame() ||
+ ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) &&
+ (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId())) ) )
+ || aRect.Top() == FAR_AWAY )
+ continue;
+ }
+
+ // #i58642#
+ // Compare <GetMaster()> instead of <m_pCurrFrame> with the
+ // anchor frame of the anchored object, because a follow frame
+ // has to ignore the anchored objects of its master frame.
+ // Note: Anchored objects are always registered at the master
+ // frame, exception are as-character anchored objects,
+ // but these aren't handled here.
+ // #i68520#
+ if ( mbIgnoreCurrentFrame &&
+ GetMaster() == pAnchoredObj->GetAnchorFrame() )
+ continue;
+
+ if( pRect )
+ {
+ // #i68520#
+ SwRect aFly = AnchoredObjToRect( pAnchoredObj, rRect );
+ if( aFly.IsEmpty() || !aFly.Overlaps( rRect ) )
+ continue;
+ if( !bRet || (
+ (!m_pCurrFrame->IsRightToLeft() &&
+ ( aRectFnSet.GetLeft(aFly) <
+ aRectFnSet.GetLeft(*pRect) ) ) ||
+ (m_pCurrFrame->IsRightToLeft() &&
+ ( aRectFnSet.GetRight(aFly) >
+ aRectFnSet.GetRight(*pRect) ) ) ) )
+ *pRect = aFly;
+ if( rSur.IsContour() )
+ {
+ bRet = true;
+ continue;
+ }
+ }
+ bRet = true;
+ break;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+// #i68520#
+SwAnchoredObjList::size_type SwTextFly::GetPos( const SwAnchoredObject* pAnchoredObj ) const
+{
+ SwAnchoredObjList::size_type nCount = GetAnchoredObjList().size();
+ SwAnchoredObjList::size_type nRet = 0;
+ while ( nRet < nCount && pAnchoredObj != (*mpAnchoredObjList)[ nRet ] )
+ ++nRet;
+ return nRet;
+}
+
+// #i68520#
+void SwTextFly::CalcRightMargin( SwRect &rFly,
+ SwAnchoredObjList::size_type nFlyPos,
+ const SwRect &rLine ) const
+{
+ // Usually the right margin is the right margin of the Printarea
+ OSL_ENSURE( !m_pCurrFrame->IsVertical() || !m_pCurrFrame->IsSwapped(),
+ "SwTextFly::CalcRightMargin with swapped frame" );
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+ // #118796# - correct determination of right of printing area
+ SwTwips nRight = aRectFnSet.GetPrtRight(*m_pCurrFrame);
+ SwTwips nFlyRight = aRectFnSet.GetRight(rFly);
+ SwRect aLine( rLine );
+ aRectFnSet.SetRight( aLine, nRight );
+ aRectFnSet.SetLeft( aLine, aRectFnSet.GetLeft(rFly) );
+
+ // It is possible that there is another object that is _above_ us
+ // and protrudes into the same line.
+ // Flys with run-through are invisible for those below, i.e., they
+ // are ignored for computing the margins of other Flys.
+ // 3301: pNext->getFrameArea().Overlaps( rLine ) is necessary
+ // #i68520#
+ css::text::WrapTextMode eSurroundForTextWrap;
+
+ bool bStop = false;
+ // #i68520#
+ SwAnchoredObjList::size_type nPos = 0;
+
+ // #i68520#
+ while( nPos < mpAnchoredObjList->size() && !bStop )
+ {
+ if( nPos == nFlyPos )
+ {
+ ++nPos;
+ continue;
+ }
+ // #i68520#
+ const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nPos++ ];
+ if ( pNext == mpCurrAnchoredObj )
+ continue;
+ eSurroundForTextWrap = GetSurroundForTextWrap( pNext );
+ if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap )
+ continue;
+
+ const SwRect aTmp( SwContourCache::CalcBoundRect
+ ( pNext, aLine, m_pCurrFrame, nFlyRight, true ) );
+ SwTwips nTmpRight = aRectFnSet.GetRight(aTmp);
+
+ // optimization:
+ // Record in nNextTop at which Y-position frame related changes are
+ // likely. This is so that, despite only looking at frames in the
+ // current line height, for frames without wrap the line height is
+ // incremented so that with a single line the lower border of the frame
+ // (or possibly the upper border of another frame) is reached.
+ // Especially in HTML documents there are often (dummy) paragraphs in
+ // 2 pt font, and they used to only evade big frames after huge numbers
+ // of empty lines.
+ const tools::Long nTmpTop = aRectFnSet.GetTop(aTmp);
+ if( aRectFnSet.YDiff( nTmpTop, aRectFnSet.GetTop(aLine) ) > 0 )
+ {
+ if( aRectFnSet.YDiff( m_nNextTop, nTmpTop ) > 0 )
+ SetNextTop( nTmpTop ); // upper border of next frame
+ }
+ else if (!aRectFnSet.GetWidth(aTmp)) // typical for Objects with contour wrap
+ { // For Objects with contour wrap that start before the current
+ // line, and end below it, but do not actually overlap it, the
+ // optimization has to be disabled, because the circumstances
+ // can change in the next line.
+ if( ! aRectFnSet.GetHeight(aTmp) ||
+ aRectFnSet.YDiff( aRectFnSet.GetBottom(aTmp),
+ aRectFnSet.GetTop(aLine) ) > 0 )
+ SetNextTop( 0 );
+ }
+ if( aTmp.Overlaps( aLine ) && nTmpRight > nFlyRight )
+ {
+ nFlyRight = nTmpRight;
+ if( css::text::WrapTextMode_RIGHT == eSurroundForTextWrap ||
+ css::text::WrapTextMode_PARALLEL == eSurroundForTextWrap )
+ {
+ // overrule the FlyFrame
+ if( nRight > nFlyRight )
+ nRight = nFlyRight;
+ bStop = true;
+ }
+ }
+ }
+ aRectFnSet.SetRight( rFly, nRight );
+}
+
+// #i68520#
+void SwTextFly::CalcLeftMargin( SwRect &rFly,
+ SwAnchoredObjList::size_type nFlyPos,
+ const SwRect &rLine ) const
+{
+ OSL_ENSURE( !m_pCurrFrame->IsVertical() || !m_pCurrFrame->IsSwapped(),
+ "SwTextFly::CalcLeftMargin with swapped frame" );
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+ // #118796# - correct determination of left of printing area
+ SwTwips nLeft = aRectFnSet.GetPrtLeft(*m_pCurrFrame);
+ const SwTwips nFlyLeft = aRectFnSet.GetLeft(rFly);
+
+ if( nLeft > nFlyLeft )
+ nLeft = rFly.Left();
+
+ SwRect aLine( rLine );
+ aRectFnSet.SetLeft( aLine, nLeft );
+
+ // It is possible that there is another object that is _above_ us
+ // and protrudes into the same line.
+ // Flys with run-through are invisible for those below, i.e., they
+ // are ignored for computing the margins of other Flys.
+ // 3301: pNext->getFrameArea().Overlaps( rLine ) is necessary
+
+ // #i68520#
+ SwAnchoredObjList::size_type nMyPos = nFlyPos;
+ while( ++nFlyPos < mpAnchoredObjList->size() )
+ {
+ // #i68520#
+ const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nFlyPos ];
+ const SwRect& aTmp( pNext->GetObjRectWithSpaces() );
+ if( aRectFnSet.GetLeft(aTmp) >= nFlyLeft )
+ break;
+ }
+
+ while( nFlyPos )
+ {
+ if( --nFlyPos == nMyPos )
+ continue;
+ // #i68520#
+ const SwAnchoredObject* pNext = (*mpAnchoredObjList)[ nFlyPos ];
+ if( pNext == mpCurrAnchoredObj )
+ continue;
+ css::text::WrapTextMode eSurroundForTextWrap = GetSurroundForTextWrap( pNext );
+ if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap )
+ continue;
+
+ const SwRect aTmp( SwContourCache::CalcBoundRect
+ (pNext, aLine, m_pCurrFrame, nFlyLeft, false) );
+
+ if( aRectFnSet.GetLeft(aTmp) < nFlyLeft && aTmp.Overlaps( aLine ) )
+ {
+ // #118796# - no '+1', because <..fnGetRight>
+ // returns the correct value.
+ SwTwips nTmpRight = aRectFnSet.GetRight(aTmp);
+ if ( nLeft <= nTmpRight )
+ nLeft = nTmpRight;
+
+ break;
+ }
+ }
+ aRectFnSet.SetLeft( rFly, nLeft );
+}
+
+// #i68520#
+SwRect SwTextFly::AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj,
+ const SwRect &rLine ) const
+{
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+
+ const tools::Long nXPos = m_pCurrFrame->IsRightToLeft() ?
+ rLine.Right() :
+ aRectFnSet.GetLeft(rLine);
+
+ SwRect aFly = mbIgnoreContour ?
+ pAnchoredObj->GetObjRectWithSpaces() :
+ SwContourCache::CalcBoundRect(pAnchoredObj, rLine, m_pCurrFrame,
+ nXPos, !m_pCurrFrame->IsRightToLeft());
+
+ if( !aFly.Width() )
+ return aFly;
+
+ // so the line may grow up to the lower edge of the frame
+ SetNextTop( aRectFnSet.GetBottom(aFly) );
+ SwAnchoredObjList::size_type nFlyPos = GetPos( pAnchoredObj );
+
+ // LEFT and RIGHT, we grow the rectangle.
+ // We have some problems, when several frames are to be seen.
+ // At the moment, only the easier case is assumed:
+ // + LEFT means that the text must flow on the left of the frame,
+ // that is the frame expands to the right edge of the print area
+ // or to the next frame.
+ // + RIGHT is the opposite.
+ // Otherwise the set distance between text and frame is always
+ // added up.
+ switch( GetSurroundForTextWrap( pAnchoredObj ) )
+ {
+ case css::text::WrapTextMode_LEFT :
+ {
+ CalcRightMargin( aFly, nFlyPos, rLine );
+ break;
+ }
+ case css::text::WrapTextMode_RIGHT :
+ {
+ CalcLeftMargin( aFly, nFlyPos, rLine );
+ break;
+ }
+ case css::text::WrapTextMode_NONE :
+ {
+ CalcRightMargin( aFly, nFlyPos, rLine );
+ CalcLeftMargin( aFly, nFlyPos, rLine );
+ break;
+ }
+ default:
+ break;
+ }
+ return aFly;
+}
+
+// #i68520#
+
+// Wrap only on sides with at least 2cm space for the text
+#define TEXT_MIN 1134
+
+// Wrap on both sides up to a frame width of 1.5cm
+#define FRAME_MAX 850
+
+css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObject* pAnchoredObj ) const
+{
+ const SwFrameFormat* pFormat = &(pAnchoredObj->GetFrameFormat());
+ const SwFormatSurround &rFlyFormat = pFormat->GetSurround();
+ css::text::WrapTextMode eSurroundForTextWrap = rFlyFormat.GetSurround();
+
+ if( rFlyFormat.IsAnchorOnly() && pAnchoredObj->GetAnchorFrame() != GetMaster() )
+ {
+ const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+ if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
+ (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()))
+ {
+ return css::text::WrapTextMode_NONE;
+ }
+ }
+
+ // in cause of run-through and nowrap ignore smartly
+ if( css::text::WrapTextMode_THROUGH == eSurroundForTextWrap ||
+ css::text::WrapTextMode_NONE == eSurroundForTextWrap )
+ return eSurroundForTextWrap;
+
+ // left is left and right is right
+ if (m_pCurrFrame->IsRightToLeft())
+ {
+ if ( css::text::WrapTextMode_LEFT == eSurroundForTextWrap )
+ eSurroundForTextWrap = css::text::WrapTextMode_RIGHT;
+ else if ( css::text::WrapTextMode_RIGHT == eSurroundForTextWrap )
+ eSurroundForTextWrap = css::text::WrapTextMode_LEFT;
+ }
+
+ // "ideal page wrap":
+ if ( css::text::WrapTextMode_DYNAMIC == eSurroundForTextWrap )
+ {
+ SwRectFnSet aRectFnSet(m_pCurrFrame);
+ const tools::Long nCurrLeft = aRectFnSet.GetPrtLeft(*m_pCurrFrame);
+ const tools::Long nCurrRight = aRectFnSet.GetPrtRight(*m_pCurrFrame);
+ const SwRect& aRect( pAnchoredObj->GetObjRectWithSpaces() );
+ tools::Long nFlyLeft = aRectFnSet.GetLeft(aRect);
+ tools::Long nFlyRight = aRectFnSet.GetRight(aRect);
+
+ if ( nFlyRight < nCurrLeft || nFlyLeft > nCurrRight )
+ eSurroundForTextWrap = css::text::WrapTextMode_PARALLEL;
+ else
+ {
+ tools::Long nLeft = nFlyLeft - nCurrLeft;
+ tools::Long nRight = nCurrRight - nFlyRight;
+ if( nFlyRight - nFlyLeft > FRAME_MAX )
+ {
+ if( nLeft < nRight )
+ nLeft = 0;
+ else
+ nRight = 0;
+ }
+ const int textMin = GetMaster()->GetDoc()
+ .getIDocumentSettingAccess().get(DocumentSettingId::SURROUND_TEXT_WRAP_SMALL )
+ ? TEXT_MIN_SMALL : TEXT_MIN;
+
+ // In case there is no space on either side, then css::text::WrapTextMode_PARALLEL
+ // gives the same result when doing the initial layout or a layout
+ // update after editing, so prefer that over css::text::WrapTextMode_NONE.
+ if (nLeft == 0 && nRight == 0)
+ return css::text::WrapTextMode_PARALLEL;
+
+ if( nLeft < textMin )
+ nLeft = 0;
+ if( nRight < textMin )
+ nRight = 0;
+ if( nLeft )
+ eSurroundForTextWrap = nRight ? css::text::WrapTextMode_PARALLEL : css::text::WrapTextMode_LEFT;
+ else
+ eSurroundForTextWrap = nRight ? css::text::WrapTextMode_RIGHT: css::text::WrapTextMode_NONE;
+ }
+ }
+
+ return eSurroundForTextWrap;
+}
+
+bool SwTextFly::IsAnyFrame( const SwRect &rLine ) const
+{
+
+ SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame));
+
+ OSL_ENSURE( m_bOn, "IsAnyFrame: Why?" );
+
+ return ForEach( rLine, nullptr, false );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx
new file mode 100644
index 0000000000..18eb78db83
--- /dev/null
+++ b/sw/source/core/text/txtfrm.cxx
@@ -0,0 +1,4219 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_wasm_strip.h>
+
+#include <hintids.hxx>
+#include <hints.hxx>
+#include <svl/ctloptions.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/pgrditem.hxx>
+#include <unotools/configmgr.hxx>
+#include <swmodule.hxx>
+#include <SwSmartTagMgr.hxx>
+#include <doc.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentDeviceAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <viewsh.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <paratr.hxx>
+#include <viewopt.hxx>
+#include <flyfrm.hxx>
+#include <tabfrm.hxx>
+#include <frmatr.hxx>
+#include <frmtool.hxx>
+#include <tgrditem.hxx>
+#include <dbg_lay.hxx>
+#include <fmtfld.hxx>
+#include <fmtftn.hxx>
+#include <txtfld.hxx>
+#include <txtftn.hxx>
+#include <ftninfo.hxx>
+#include <fmtline.hxx>
+#include <txtfrm.hxx>
+#include <notxtfrm.hxx>
+#include <sectfrm.hxx>
+#include "itrform2.hxx"
+#include "widorp.hxx"
+#include "txtcache.hxx"
+#include <fntcache.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <lineinfo.hxx>
+#include <SwPortionHandler.hxx>
+#include <dcontact.hxx>
+#include <sortedobjs.hxx>
+#include <txtflcnt.hxx>
+#include <fmtflcnt.hxx>
+#include <fmtcntnt.hxx>
+#include <numrule.hxx>
+#include <GrammarContact.hxx>
+#include <calbck.hxx>
+#include <ftnidx.hxx>
+#include <ftnfrm.hxx>
+
+#include <wrtsh.hxx>
+#include <view.hxx>
+#include <edtwin.hxx>
+#include <FrameControlsManager.hxx>
+
+namespace sw {
+
+ MergedAttrIterBase::MergedAttrIterBase(SwTextFrame const& rFrame)
+ : m_pMerged(rFrame.GetMergedPara())
+ , m_pNode(m_pMerged ? nullptr : rFrame.GetTextNodeFirst())
+ , m_CurrentExtent(0)
+ , m_CurrentHint(0)
+ {
+ }
+
+ SwTextAttr const* MergedAttrIter::NextAttr(SwTextNode const** ppNode)
+ {
+ if (m_pMerged)
+ {
+ while (m_CurrentExtent < m_pMerged->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ while (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr *const pHint(pHints->Get(m_CurrentHint));
+ if (rExtent.nEnd < pHint->GetStart()
+ // <= if it has no end or isn't empty
+ || (rExtent.nEnd == pHint->GetStart()
+ && (!pHint->GetEnd()
+ || *pHint->GetEnd() != pHint->GetStart())))
+ {
+ break;
+ }
+ ++m_CurrentHint;
+ if (rExtent.nStart <= pHint->GetStart())
+ {
+ if (ppNode)
+ {
+ *ppNode = rExtent.pNode;
+ }
+ return pHint;
+ }
+ }
+ }
+ ++m_CurrentExtent;
+ if (m_CurrentExtent < m_pMerged->extents.size() &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode)
+ {
+ m_CurrentHint = 0; // reset
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(pHints->Get(m_CurrentHint));
+ ++m_CurrentHint;
+ if (ppNode)
+ {
+ *ppNode = m_pNode;
+ }
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ }
+
+ MergedAttrIterByEnd::MergedAttrIterByEnd(SwTextFrame const& rFrame)
+ : m_pNode(rFrame.GetMergedPara() ? nullptr : rFrame.GetTextNodeFirst())
+ , m_CurrentHint(0)
+ {
+ if (!m_pNode)
+ {
+ MergedAttrIterReverse iter(rFrame);
+ SwTextNode const* pNode(nullptr);
+ while (SwTextAttr const* pHint = iter.PrevAttr(&pNode))
+ {
+ m_Hints.emplace_back(pNode, pHint);
+ }
+ }
+ }
+
+ SwTextAttr const* MergedAttrIterByEnd::NextAttr(SwTextNode const*& rpNode)
+ {
+ if (m_pNode)
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints)
+ {
+ if (m_CurrentHint < pHints->Count())
+ {
+ SwTextAttr const*const pHint(
+ pHints->GetSortedByEnd(m_CurrentHint));
+ ++m_CurrentHint;
+ rpNode = m_pNode;
+ return pHint;
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ if (m_CurrentHint < m_Hints.size())
+ {
+ auto const ret = m_Hints[m_Hints.size() - m_CurrentHint - 1];
+ ++m_CurrentHint;
+ rpNode = ret.first;
+ return ret.second;
+ }
+ return nullptr;
+ }
+ }
+
+ void MergedAttrIterByEnd::PrevAttr()
+ {
+ assert(0 < m_CurrentHint); // should only rewind as far as 0
+ --m_CurrentHint;
+ }
+
+ MergedAttrIterReverse::MergedAttrIterReverse(SwTextFrame const& rFrame)
+ : MergedAttrIterBase(rFrame)
+ {
+ if (m_pMerged)
+ {
+ m_CurrentExtent = m_pMerged->extents.size();
+ SwpHints const*const pHints(0 < m_CurrentExtent
+ ? m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints()
+ : nullptr);
+ if (pHints)
+ {
+ pHints->SortIfNeedBe();
+ m_CurrentHint = pHints->Count();
+ }
+ }
+ else
+ {
+ if (SwpHints const*const pHints = m_pNode->GetpSwpHints())
+ {
+ pHints->SortIfNeedBe();
+ m_CurrentHint = pHints->Count();
+ }
+ }
+ }
+
+ SwTextAttr const* MergedAttrIterReverse::PrevAttr(SwTextNode const** ppNode)
+ {
+ if (m_pMerged)
+ {
+ while (0 < m_CurrentExtent)
+ {
+ sw::Extent const& rExtent(m_pMerged->extents[m_CurrentExtent-1]);
+ if (SwpHints const*const pHints = rExtent.pNode->GetpSwpHints())
+ {
+ while (0 < m_CurrentHint)
+ {
+ SwTextAttr *const pHint(
+ pHints->GetSortedByEnd(m_CurrentHint - 1));
+ if (pHint->GetAnyEnd() < rExtent.nStart
+ // <= if it has end and isn't empty
+ || (pHint->GetEnd()
+ && *pHint->GetEnd() != pHint->GetStart()
+ && *pHint->GetEnd() == rExtent.nStart))
+ {
+ break;
+ }
+ --m_CurrentHint;
+ if (pHint->GetAnyEnd() <= rExtent.nEnd)
+ {
+ if (ppNode)
+ {
+ *ppNode = rExtent.pNode;
+ }
+ return pHint;
+ }
+ }
+ }
+ --m_CurrentExtent;
+ if (0 < m_CurrentExtent &&
+ rExtent.pNode != m_pMerged->extents[m_CurrentExtent-1].pNode)
+ {
+ SwpHints const*const pHints(
+ m_pMerged->extents[m_CurrentExtent-1].pNode->GetpSwpHints());
+ m_CurrentHint = pHints ? pHints->Count() : 0; // reset
+ if (pHints)
+ pHints->SortIfNeedBe();
+ }
+ }
+ return nullptr;
+ }
+ else
+ {
+ SwpHints const*const pHints(m_pNode->GetpSwpHints());
+ if (pHints && 0 < m_CurrentHint)
+ {
+ SwTextAttr const*const pHint(pHints->GetSortedByEnd(m_CurrentHint - 1));
+ --m_CurrentHint;
+ if (ppNode)
+ {
+ *ppNode = m_pNode;
+ }
+ return pHint;
+ }
+ return nullptr;
+ }
+ }
+
+ bool FrameContainsNode(SwContentFrame const& rFrame, SwNodeOffset const nNodeIndex)
+ {
+ if (rFrame.IsTextFrame())
+ {
+ SwTextFrame const& rTextFrame(static_cast<SwTextFrame const&>(rFrame));
+ if (sw::MergedPara const*const pMerged = rTextFrame.GetMergedPara())
+ {
+ SwNodeOffset const nFirst(pMerged->pFirstNode->GetIndex());
+ SwNodeOffset const nLast(pMerged->pLastNode->GetIndex());
+ return (nFirst <= nNodeIndex && nNodeIndex <= nLast);
+ }
+ else
+ {
+ return rTextFrame.GetTextNodeFirst()->GetIndex() == nNodeIndex;
+ }
+ }
+ else
+ {
+ assert(rFrame.IsNoTextFrame());
+ return static_cast<SwNoTextFrame const&>(rFrame).GetNode()->GetIndex() == nNodeIndex;
+ }
+ }
+
+ bool IsParaPropsNode(SwRootFrame const& rLayout, SwTextNode const& rNode)
+ {
+ if (rLayout.HasMergedParas())
+ {
+ if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(&rLayout)))
+ {
+ sw::MergedPara const*const pMerged(pFrame->GetMergedPara());
+ if (pMerged && pMerged->pParaPropsNode != &rNode)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ SwTextNode *
+ GetParaPropsNode(SwRootFrame const& rLayout, SwNode const& rPos)
+ {
+ const SwTextNode *const pTextNode(rPos.GetTextNode());
+ if (pTextNode && !sw::IsParaPropsNode(rLayout, *pTextNode))
+ {
+ return static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))->GetMergedPara()->pParaPropsNode;
+ }
+ else
+ {
+ return const_cast<SwTextNode*>(pTextNode);
+ }
+ }
+
+ SwPosition
+ GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos)
+ {
+ SwPosition pos(rPos);
+ SwTextNode const*const pNode(pos.GetNode().GetTextNode());
+ if (pNode)
+ pos.Assign( *sw::GetParaPropsNode(rLayout, *pNode) );
+ return pos;
+ }
+
+ std::pair<SwTextNode *, SwTextNode *>
+ GetFirstAndLastNode(SwRootFrame const& rLayout, SwNode const& rPos)
+ {
+ SwTextNode *const pTextNode(const_cast<SwTextNode*>(rPos.GetTextNode()));
+ if (pTextNode && rLayout.HasMergedParas())
+ {
+ if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout)))
+ {
+ if (sw::MergedPara const*const pMerged = pFrame->GetMergedPara())
+ {
+ return std::make_pair(pMerged->pFirstNode, const_cast<SwTextNode*>(pMerged->pLastNode));
+ }
+ }
+ }
+ return std::make_pair(pTextNode, pTextNode);
+ }
+
+ SwTextNode const& GetAttrMerged(SfxItemSet & rFormatSet,
+ SwTextNode const& rNode, SwRootFrame const*const pLayout)
+ {
+ rNode.SwContentNode::GetAttr(rFormatSet);
+ if (pLayout && pLayout->HasMergedParas())
+ {
+ auto pFrame = static_cast<SwTextFrame*>(rNode.getLayoutFrame(pLayout));
+ if (sw::MergedPara const*const pMerged = pFrame ? pFrame->GetMergedPara() : nullptr)
+ {
+ if (pMerged->pFirstNode != &rNode)
+ {
+ rFormatSet.ClearItem(RES_PAGEDESC);
+ rFormatSet.ClearItem(RES_BREAK);
+ static_assert(RES_PAGEDESC + 1 == sal_uInt16(RES_BREAK),
+ "first-node items must be adjacent");
+ SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(*rFormatSet.GetPool());
+ pMerged->pFirstNode->SwContentNode::GetAttr(firstSet);
+ rFormatSet.Put(firstSet);
+
+ }
+ if (pMerged->pParaPropsNode != &rNode)
+ {
+ for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
+ {
+ if (i != RES_PAGEDESC && i != RES_BREAK)
+ {
+ rFormatSet.ClearItem(i);
+ }
+ }
+ for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
+ {
+ rFormatSet.ClearItem(i);
+ }
+ SfxItemSetFixed<RES_PARATR_BEGIN, RES_PAGEDESC,
+ RES_BREAK+1, RES_FRMATR_END,
+ XATTR_FILL_FIRST, XATTR_FILL_LAST+1>
+ propsSet(*rFormatSet.GetPool());
+ pMerged->pParaPropsNode->SwContentNode::GetAttr(propsSet);
+ rFormatSet.Put(propsSet);
+ return *pMerged->pParaPropsNode;
+ }
+ // keep all the CHRATR/UNKNOWNATR anyway...
+ }
+ }
+ return rNode;
+ }
+
+} // namespace sw
+
+/// Switches width and height of the text frame
+void SwTextFrame::SwapWidthAndHeight()
+{
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+
+ if ( ! mbIsSwapped )
+ {
+ const tools::Long nPrtOfstX = aPrt.Pos().X();
+ aPrt.Pos().setX( aPrt.Pos().Y() );
+
+ if( IsVertLR() )
+ {
+ aPrt.Pos().setY( nPrtOfstX );
+ }
+ else
+ {
+ aPrt.Pos().setY( getFrameArea().Width() - ( nPrtOfstX + aPrt.Width() ) );
+ }
+ }
+ else
+ {
+ const tools::Long nPrtOfstY = aPrt.Pos().Y();
+ aPrt.Pos().setY( aPrt.Pos().X() );
+
+ if( IsVertLR() )
+ {
+ aPrt.Pos().setX( nPrtOfstY );
+ }
+ else
+ {
+ aPrt.Pos().setX( getFrameArea().Height() - ( nPrtOfstY + aPrt.Height() ) );
+ }
+ }
+
+ const tools::Long nPrtWidth = aPrt.Width();
+ aPrt.Width( aPrt.Height() );
+ aPrt.Height( nPrtWidth );
+ }
+
+ {
+ const tools::Long nFrameWidth = getFrameArea().Width();
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( aFrm.Height() );
+ aFrm.Height( nFrameWidth );
+ }
+
+ mbIsSwapped = ! mbIsSwapped;
+}
+
+/**
+ * Calculates the coordinates of a rectangle when switching from
+ * horizontal to vertical layout.
+ */
+void SwTextFrame::SwitchHorizontalToVertical( SwRect& rRect ) const
+{
+ // calc offset inside frame
+ tools::Long nOfstX, nOfstY;
+ if ( IsVertLR() )
+ {
+ if (IsVertLRBT())
+ {
+ // X and Y offsets here mean the position of the point that will be the top left corner
+ // after the switch.
+ nOfstX = rRect.Left() + rRect.Width() - getFrameArea().Left();
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ }
+ else
+ {
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ }
+ }
+ else
+ {
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ nOfstY = rRect.Top() + rRect.Height() - getFrameArea().Top();
+ }
+
+ const tools::Long nWidth = rRect.Width();
+ const tools::Long nHeight = rRect.Height();
+
+ if ( IsVertLR() )
+ {
+ rRect.Left(getFrameArea().Left() + nOfstY);
+ }
+ else
+ {
+ if ( mbIsSwapped )
+ rRect.Left( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
+ else
+ // frame is rotated
+ rRect.Left( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
+ }
+
+ if (IsVertLRBT())
+ {
+ if (mbIsSwapped)
+ rRect.Top(getFrameArea().Top() + getFrameArea().Width() - nOfstX);
+ else
+ rRect.Top(getFrameArea().Top() + getFrameArea().Height() - nOfstX);
+ }
+ else
+ rRect.Top(getFrameArea().Top() + nOfstX);
+ rRect.Width( nHeight );
+ rRect.Height( nWidth );
+}
+
+/**
+ * Calculates the coordinates of a point when switching from
+ * horizontal to vertical layout.
+ */
+void SwTextFrame::SwitchHorizontalToVertical( Point& rPoint ) const
+{
+ if (IsVertLRBT())
+ {
+ // The horizontal origo is the top left corner, the LRBT origo is the
+ // bottom left corner. Finally x and y has to be swapped.
+ SAL_WARN_IF(!mbIsSwapped, "sw.core",
+ "SwTextFrame::SwitchHorizontalToVertical, IsVertLRBT, not swapped");
+ Point aPoint(rPoint);
+ rPoint.setX(getFrameArea().Left() + (aPoint.Y() - getFrameArea().Top()));
+ // This would be bottom - x delta, but bottom is top + height, finally
+ // width (and not height), as it's swapped.
+ rPoint.setY(getFrameArea().Top() + getFrameArea().Width()
+ - (aPoint.X() - getFrameArea().Left()));
+ return;
+ }
+
+ // calc offset inside frame
+ const tools::Long nOfstX = rPoint.X() - getFrameArea().Left();
+ const tools::Long nOfstY = rPoint.Y() - getFrameArea().Top();
+ if ( IsVertLR() )
+ rPoint.setX( getFrameArea().Left() + nOfstY );
+ else
+ {
+ if ( mbIsSwapped )
+ rPoint.setX( getFrameArea().Left() + getFrameArea().Height() - nOfstY );
+ else
+ // calc rotated coords
+ rPoint.setX( getFrameArea().Left() + getFrameArea().Width() - nOfstY );
+ }
+
+ rPoint.setY( getFrameArea().Top() + nOfstX );
+}
+
+/**
+ * Calculates the a limit value when switching from
+ * horizontal to vertical layout.
+ */
+tools::Long SwTextFrame::SwitchHorizontalToVertical( tools::Long nLimit ) const
+{
+ Point aTmp( 0, nLimit );
+ SwitchHorizontalToVertical( aTmp );
+ return aTmp.X();
+}
+
+/**
+ * Calculates the coordinates of a rectangle when switching from
+ * vertical to horizontal layout.
+ */
+void SwTextFrame::SwitchVerticalToHorizontal( SwRect& rRect ) const
+{
+ tools::Long nOfstX;
+
+ // calc offset inside frame
+ if ( IsVertLR() )
+ nOfstX = rRect.Left() - getFrameArea().Left();
+ else
+ {
+ if ( mbIsSwapped )
+ nOfstX = getFrameArea().Left() + getFrameArea().Height() - ( rRect.Left() + rRect.Width() );
+ else
+ nOfstX = getFrameArea().Left() + getFrameArea().Width() - ( rRect.Left() + rRect.Width() );
+ }
+
+ tools::Long nOfstY;
+ if (IsVertLRBT())
+ {
+ // Note that mbIsSwapped only affects the frame area, not rRect, so rRect.Height() is used
+ // here unconditionally.
+ if (mbIsSwapped)
+ nOfstY = getFrameArea().Top() + getFrameArea().Width() - (rRect.Top() + rRect.Height());
+ else
+ nOfstY = getFrameArea().Top() + getFrameArea().Height() - (rRect.Top() + rRect.Height());
+ }
+ else
+ nOfstY = rRect.Top() - getFrameArea().Top();
+ const tools::Long nWidth = rRect.Height();
+ const tools::Long nHeight = rRect.Width();
+
+ // calc rotated coords
+ rRect.Left( getFrameArea().Left() + nOfstY );
+ rRect.Top( getFrameArea().Top() + nOfstX );
+ rRect.Width( nWidth );
+ rRect.Height( nHeight );
+}
+
+/**
+ * Calculates the coordinates of a point when switching from
+ * vertical to horizontal layout.
+ */
+void SwTextFrame::SwitchVerticalToHorizontal( Point& rPoint ) const
+{
+ tools::Long nOfstX;
+
+ // calc offset inside frame
+ if ( IsVertLR() )
+ // X offset is Y - left.
+ nOfstX = rPoint.X() - getFrameArea().Left();
+ else
+ {
+ // X offset is right - X.
+ if ( mbIsSwapped )
+ nOfstX = getFrameArea().Left() + getFrameArea().Height() - rPoint.X();
+ else
+ nOfstX = getFrameArea().Left() + getFrameArea().Width() - rPoint.X();
+ }
+
+ tools::Long nOfstY;
+ if (IsVertLRBT())
+ {
+ // Y offset is bottom - Y.
+ if (mbIsSwapped)
+ nOfstY = getFrameArea().Top() + getFrameArea().Width() - rPoint.Y();
+ else
+ nOfstY = getFrameArea().Top() + getFrameArea().Height() - rPoint.Y();
+ }
+ else
+ // Y offset is Y - top.
+ nOfstY = rPoint.Y() - getFrameArea().Top();
+
+ // calc rotated coords
+ rPoint.setX( getFrameArea().Left() + nOfstY );
+ rPoint.setY( getFrameArea().Top() + nOfstX );
+}
+
+/**
+ * Calculates the a limit value when switching from
+ * vertical to horizontal layout.
+ */
+tools::Long SwTextFrame::SwitchVerticalToHorizontal( tools::Long nLimit ) const
+{
+ Point aTmp( nLimit, 0 );
+ SwitchVerticalToHorizontal( aTmp );
+ return aTmp.Y();
+}
+
+SwFrameSwapper::SwFrameSwapper( const SwTextFrame* pTextFrame, bool bSwapIfNotSwapped )
+ : pFrame( pTextFrame ), bUndo( false )
+{
+ if (pFrame->IsVertical() && bSwapIfNotSwapped != pFrame->IsSwapped())
+ {
+ bUndo = true;
+ const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
+ }
+}
+
+SwFrameSwapper::~SwFrameSwapper()
+{
+ if ( bUndo )
+ const_cast<SwTextFrame*>(pFrame)->SwapWidthAndHeight();
+}
+
+void SwTextFrame::SwitchLTRtoRTL( SwRect& rRect ) const
+{
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ tools::Long nWidth = rRect.Width();
+ rRect.Left( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) +
+ getFramePrintArea().Width() - rRect.Right() - 1 );
+
+ rRect.Width( nWidth );
+}
+
+void SwTextFrame::SwitchLTRtoRTL( Point& rPoint ) const
+{
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ rPoint.setX( 2 * ( getFrameArea().Left() + getFramePrintArea().Left() ) + getFramePrintArea().Width() - rPoint.X() - 1 );
+}
+
+SwLayoutModeModifier::SwLayoutModeModifier( const OutputDevice& rOutp ) :
+ m_rOut( rOutp ), m_nOldLayoutMode( rOutp.GetLayoutMode() )
+{
+}
+
+SwLayoutModeModifier::~SwLayoutModeModifier()
+{
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( m_nOldLayoutMode );
+}
+
+void SwLayoutModeModifier::Modify( bool bChgToRTL )
+{
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( bChgToRTL ?
+ vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl :
+ vcl::text::ComplexTextLayoutFlags::BiDiStrong );
+}
+
+void SwLayoutModeModifier::SetAuto()
+{
+ const vcl::text::ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ const_cast<OutputDevice&>(m_rOut).SetLayoutMode( nNewLayoutMode );
+}
+
+SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageType eCurLang ) :
+ rOut( rOutp ), nOldLanguageType( rOutp.GetDigitLanguage() )
+{
+ LanguageType eLang = eCurLang;
+ if (utl::ConfigManager::IsFuzzing())
+ eLang = LANGUAGE_ENGLISH_US;
+ else
+ {
+ const SvtCTLOptions::TextNumerals nTextNumerals = SvtCTLOptions::GetCTLTextNumerals();
+
+ if ( SvtCTLOptions::NUMERALS_HINDI == nTextNumerals )
+ eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
+ else if ( SvtCTLOptions::NUMERALS_ARABIC == nTextNumerals )
+ eLang = LANGUAGE_ENGLISH;
+ else if ( SvtCTLOptions::NUMERALS_SYSTEM == nTextNumerals )
+ eLang = ::GetAppLanguage();
+ }
+
+ const_cast<OutputDevice&>(rOut).SetDigitLanguage( eLang );
+}
+
+SwDigitModeModifier::~SwDigitModeModifier()
+{
+ const_cast<OutputDevice&>(rOut).SetDigitLanguage( nOldLanguageType );
+}
+
+void SwTextFrame::Init()
+{
+ OSL_ENSURE( !IsLocked(), "+SwTextFrame::Init: this is locked." );
+ if( !IsLocked() )
+ {
+ ClearPara();
+ SetHasRotatedPortions(false);
+ // set flags directly to save a ResetPreps call,
+ // and thereby an unnecessary GetPara call
+ // don't set bOrphan, bLocked or bWait to false!
+ // bOrphan = bFlag7 = bFlag8 = false;
+ }
+}
+
+SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib,
+ sw::FrameMode const eMode)
+ : SwContentFrame( pNode, pSib )
+ , mnAllLines( 0 )
+ , mnThisLines( 0 )
+ , mnFlyAnchorOfst( 0 )
+ , mnFlyAnchorOfstNoWrap( 0 )
+ , mnFlyAnchorVertOfstNoWrap( 0 )
+ , mnFootnoteLine( 0 )
+ , mnHeightOfLastLine( 0 )
+ , mnAdditionalFirstLineOffset( 0 )
+ , mnOffset( 0 )
+ , mnCacheIndex( USHRT_MAX )
+ , mbLocked( false )
+ , mbWidow( false )
+ , mbJustWidow( false )
+ , mbEmpty( false )
+ , mbInFootnoteConnect( false )
+ , mbFootnote( false )
+ , mbRepaint( false )
+ , mbHasRotatedPortions( false )
+ , mbFieldFollow( false )
+ , mbHasAnimation( false )
+ , mbIsSwapped( false )
+ , mbFollowFormatAllowed( true )
+{
+ mnFrameType = SwFrameType::Txt;
+ // note: this may call SwClientNotify if it's in a list so do it last
+ // note: this may change this->pRegisteredIn to m_pMergedPara->listeners
+ m_pMergedPara = CheckParaRedlineMerge(*this, *pNode, eMode);
+}
+
+void SwTextFrame::dumpAsXmlAttributes(xmlTextWriterPtr writer) const
+{
+ SwContentFrame::dumpAsXmlAttributes(writer);
+
+ const SwTextNode *pTextNode = GetTextNodeFirst();
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pTextNode->GetIndex()) );
+
+ OString aMode = "Horizontal"_ostr;
+ if (IsVertLRBT())
+ {
+ aMode = "VertBTLR"_ostr;
+ }
+ else if (IsVertLR())
+ {
+ aMode = "VertLR"_ostr;
+ }
+ else if (IsVertical())
+ {
+ aMode = "Vertical"_ostr;
+ }
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("WritingMode"), BAD_CAST(aMode.getStr()));
+}
+
+void SwTextFrame::dumpAsXml(xmlTextWriterPtr writer) const
+{
+ (void)xmlTextWriterStartElement(writer, reinterpret_cast<const xmlChar*>("txt"));
+ dumpAsXmlAttributes( writer );
+ if ( HasFollow() )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "follow" ), "%" SAL_PRIuUINT32, GetFollow()->GetFrameId() );
+
+ if (m_pPrecede != nullptr)
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "precede" ), "%" SAL_PRIuUINT32, static_cast<SwTextFrame*>(m_pPrecede)->GetFrameId() );
+
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("offset"), BAD_CAST(OString::number(static_cast<sal_Int32>(mnOffset)).getStr()));
+
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "merged" ) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "paraPropsNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(pMerged->pParaPropsNode->GetIndex()) );
+ for (auto const& e : pMerged->extents)
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "extent" ) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(e.pNode->GetIndex()) );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "start" ), "%" SAL_PRIdINT32, e.nStart );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "end" ), "%" SAL_PRIdINT32, e.nEnd );
+ (void)xmlTextWriterEndElement( writer );
+ }
+ (void)xmlTextWriterEndElement( writer );
+ }
+
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("infos"));
+ dumpInfosAsXml(writer);
+ (void)xmlTextWriterEndElement(writer);
+
+ // Dump Anchored objects if any
+ const SwSortedObjs* pAnchored = GetDrawObjs();
+ if ( pAnchored && pAnchored->size() > 0 )
+ {
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "anchored" ) );
+
+ for (SwAnchoredObject* pObject : *pAnchored)
+ {
+ pObject->dumpAsXml( writer );
+ }
+
+ (void)xmlTextWriterEndElement( writer );
+ }
+
+ // Dump the children
+ OUString aText = GetText( );
+ for ( int i = 0; i < 32; i++ )
+ {
+ aText = aText.replace( i, '*' );
+ }
+ auto nTextOffset = static_cast<sal_Int32>(GetOffset());
+ sal_Int32 nTextLength = aText.getLength() - nTextOffset;
+ if (const SwTextFrame* pTextFrameFollow = GetFollow())
+ {
+ nTextLength = static_cast<sal_Int32>(pTextFrameFollow->GetOffset() - GetOffset());
+ }
+ if (nTextLength > 0)
+ {
+ OString aText8
+ = OUStringToOString(aText.subView(nTextOffset, nTextLength), RTL_TEXTENCODING_UTF8);
+ (void)xmlTextWriterWriteString( writer,
+ reinterpret_cast<const xmlChar *>(aText8.getStr( )) );
+ }
+ if (const SwParaPortion* pPara = GetPara())
+ {
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("SwParaPortion"));
+ TextFrameIndex nOffset(0);
+ const OUString& rText = GetText();
+ (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", pPara);
+ const SwLineLayout* pLine = pPara;
+ if (IsFollow())
+ {
+ nOffset += GetOffset();
+ }
+ while (pLine)
+ {
+ (void)xmlTextWriterStartElement(writer, BAD_CAST("SwLineLayout"));
+ pLine->dumpAsXmlAttributes(writer, rText, nOffset);
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while (pPor)
+ {
+ pPor->dumpAsXml(writer, rText, nOffset);
+ pPor = pPor->GetNextPortion();
+ }
+ (void)xmlTextWriterEndElement(writer);
+ pLine = pLine->GetNext();
+ }
+ (void)xmlTextWriterEndElement(writer);
+ }
+
+ (void)xmlTextWriterEndElement(writer);
+}
+
+namespace sw {
+
+SwTextFrame * MakeTextFrame(SwTextNode & rNode, SwFrame *const pSibling,
+ sw::FrameMode const eMode)
+{
+ return new SwTextFrame(&rNode, pSibling, eMode);
+}
+
+void RemoveFootnotesForNode(
+ SwRootFrame const& rLayout, SwTextNode const& rTextNode,
+ std::vector<std::pair<sal_Int32, sal_Int32>> const*const pExtents)
+{
+ if (pExtents && pExtents->empty())
+ {
+ return; // nothing to do
+ }
+ const SwFootnoteIdxs &rFootnoteIdxs = rTextNode.GetDoc().GetFootnoteIdxs();
+ size_t nPos = 0;
+ SwNodeOffset const nIndex = rTextNode.GetIndex();
+ rFootnoteIdxs.SeekEntry( rTextNode, &nPos );
+ if (nPos < rFootnoteIdxs.size())
+ {
+ while (nPos && rTextNode == (rFootnoteIdxs[ nPos ]->GetTextNode()))
+ --nPos;
+ if (nPos || rTextNode != (rFootnoteIdxs[ nPos ]->GetTextNode()))
+ ++nPos;
+ }
+ size_t iter(0);
+ for ( ; nPos < rFootnoteIdxs.size(); ++nPos)
+ {
+ SwTextFootnote* pTextFootnote = rFootnoteIdxs[ nPos ];
+ if (pTextFootnote->GetTextNode().GetIndex() > nIndex)
+ break;
+ if (pExtents)
+ {
+ while ((*pExtents)[iter].second <= pTextFootnote->GetStart())
+ {
+ ++iter;
+ if (iter == pExtents->size())
+ {
+ return;
+ }
+ }
+ if (pTextFootnote->GetStart() < (*pExtents)[iter].first)
+ {
+ continue;
+ }
+ }
+ pTextFootnote->DelFrames(&rLayout);
+ }
+}
+
+} // namespace sw
+
+void SwTextFrame::DestroyImpl()
+{
+ // Remove associated SwParaPortion from s_pTextCache
+ ClearPara();
+
+ assert(!GetDoc().IsInDtor()); // this shouldn't be happening with ViewShell owning layout
+ if (!GetDoc().IsInDtor() && HasFootnote())
+ {
+ if (m_pMergedPara)
+ {
+ SwTextNode const* pNode(nullptr);
+ for (auto const& e : m_pMergedPara->extents)
+ {
+ if (e.pNode != pNode)
+ {
+ pNode = e.pNode;
+ // sw_redlinehide: not sure if it's necessary to check
+ // if the nodes are still alive here, which would require
+ // accessing WriterMultiListener::m_vDepends
+ sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
+ }
+ }
+ }
+ else
+ {
+ SwTextNode *const pNode(static_cast<SwTextNode*>(GetDep()));
+ if (pNode)
+ {
+ sw::RemoveFootnotesForNode(*getRootFrame(), *pNode, nullptr);
+ }
+ }
+ }
+
+ if (!GetDoc().IsInDtor())
+ {
+ if (SwView* pView = GetActiveView())
+ pView->GetEditWin().GetFrameControlsManager().RemoveControls(this);
+ }
+
+ SwContentFrame::DestroyImpl();
+}
+
+SwTextFrame::~SwTextFrame()
+{
+ RemoveFromCache();
+}
+
+namespace sw {
+
+// 1. if real insert => correct nStart/nEnd for full nLen
+// 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted
+static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged,
+ bool const isRealInsert,
+ SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen)
+{
+ assert(!isRealInsert || nLen); // can 0 happen? yes, for redline in empty node
+ assert(nIndex <= rNode.Len());
+ assert(nIndex + nLen <= rNode.Len());
+ assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
+ if (!nLen)
+ {
+ return TextFrameIndex(0);
+ }
+ OUStringBuffer text(rMerged.mergedText);
+ sal_Int32 nTFIndex(0); // index used for insertion at the end
+ sal_Int32 nInserted(0);
+ bool bInserted(false);
+ bool bFoundNode(false);
+ auto itInsert(rMerged.extents.end());
+ for (auto it = rMerged.extents.begin(); it != rMerged.extents.end(); ++it)
+ {
+ if (it->pNode == &rNode)
+ {
+ if (isRealInsert)
+ {
+ bFoundNode = true;
+ if (it->nStart <= nIndex && nIndex <= it->nEnd)
+ { // note: this can happen only once
+ text.insert(nTFIndex + (nIndex - it->nStart),
+ rNode.GetText().subView(nIndex, nLen));
+ it->nEnd += nLen;
+ nInserted = nLen;
+ assert(!bInserted);
+ bInserted = true;
+ }
+ else if (nIndex < it->nStart)
+ {
+ if (itInsert == rMerged.extents.end())
+ {
+ itInsert = it;
+ }
+ it->nStart += nLen;
+ it->nEnd += nLen;
+ }
+ }
+ else
+ {
+ assert(it == rMerged.extents.begin() || (it-1)->pNode != &rNode || (it-1)->nEnd < nIndex);
+ if (nIndex + nLen < it->nStart)
+ {
+ itInsert = it;
+ break;
+ }
+ if (nIndex < it->nStart)
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(nIndex, it->nStart - nIndex));
+ nInserted += it->nStart - nIndex;
+ it->nStart = nIndex;
+ bInserted = true;
+ }
+ assert(it->nStart <= nIndex);
+ if (nIndex <= it->nEnd)
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ while (it->nEnd < nIndex + nLen)
+ {
+ auto *const pNext(
+ (it+1) != rMerged.extents.end() && (it+1)->pNode == it->pNode
+ ? &*(it+1)
+ : nullptr);
+ if (pNext && pNext->nStart <= nIndex + nLen)
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(it->nEnd, pNext->nStart - it->nEnd));
+ nTFIndex += pNext->nStart - it->nEnd;
+ nInserted += pNext->nStart - it->nEnd;
+ pNext->nStart = it->nStart;
+ it = rMerged.extents.erase(it);
+ }
+ else
+ {
+ text.insert(nTFIndex,
+ rNode.GetText().subView(it->nEnd, nIndex + nLen - it->nEnd));
+ nTFIndex += nIndex + nLen - it->nEnd;
+ nInserted += nIndex + nLen - it->nEnd;
+ it->nEnd = nIndex + nLen;
+ }
+ }
+ bInserted = true;
+ break;
+ }
+ }
+ }
+ else if (rNode.GetIndex() < it->pNode->GetIndex() || bFoundNode)
+ {
+ if (itInsert == rMerged.extents.end())
+ {
+ itInsert = it;
+ }
+ break;
+ }
+ if (itInsert == rMerged.extents.end())
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ }
+ }
+// assert((bFoundNode || rMerged.extents.empty()) && "text node not found - why is it sending hints to us");
+ if (!bInserted)
+ { // must be in a gap
+ rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen);
+ text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen));
+ nInserted = nLen;
+ if (rMerged.extents.size() == 1 // also if it was empty!
+ || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex())
+ { // text inserted after current para-props node
+ rMerged.pParaPropsNode->RemoveFromListRLHidden();
+ rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode);
+ rMerged.pParaPropsNode->AddToListRLHidden();
+ }
+ // called from SwRangeRedline::InvalidateRange()
+ if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden)
+ {
+ const_cast<SwTextNode&>(rNode).SetRedlineMergeFlag(SwNode::Merge::NonFirst);
+ }
+ }
+ rMerged.mergedText = text.makeStringAndClear();
+ return TextFrameIndex(nInserted);
+}
+
+// 1. if real delete => correct nStart/nEnd for full nLen
+// 2. if rl delete => do not correct nStart/nEnd but just exclude deleted
+TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
+ bool const isRealDelete,
+ SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen)
+{
+ assert(nIndex <= rNode.Len());
+ assert(rMerged.pFirstNode->GetIndex() <= rNode.GetIndex() && rNode.GetIndex() <= rMerged.pLastNode->GetIndex());
+ OUStringBuffer text(rMerged.mergedText);
+ sal_Int32 nTFIndex(0);
+ sal_Int32 nToDelete(nLen);
+ sal_Int32 nDeleted(0);
+ size_t nFoundNode(0);
+ size_t nErased(0);
+ auto it = rMerged.extents.begin();
+ for (; it != rMerged.extents.end(); )
+ {
+ bool bErase(false);
+ if (it->pNode == &rNode)
+ {
+ ++nFoundNode;
+ if (nIndex + nToDelete < it->nStart)
+ {
+ nToDelete = 0;
+ if (!isRealDelete)
+ {
+ break;
+ }
+ it->nStart -= nLen;
+ it->nEnd -= nLen;
+ }
+ else
+ {
+ if (nIndex < it->nStart)
+ {
+ // do not adjust nIndex into the text frame index space!
+ nToDelete -= it->nStart - nIndex;
+ nIndex = it->nStart;
+ // note: continue with the if check below, no else!
+ }
+ if (it->nStart <= nIndex && nIndex < it->nEnd)
+ {
+ sal_Int32 const nDeleteHere(nIndex + nToDelete <= it->nEnd
+ ? nToDelete
+ : it->nEnd - nIndex);
+ text.remove(nTFIndex + (nIndex - it->nStart), nDeleteHere);
+ bErase = nDeleteHere == it->nEnd - it->nStart;
+ if (bErase)
+ {
+ ++nErased;
+ assert(it->nStart == nIndex);
+ it = rMerged.extents.erase(it);
+ }
+ else if (isRealDelete)
+ { // adjust for deleted text
+ it->nStart -= (nLen - nToDelete);
+ it->nEnd -= (nLen - nToDelete + nDeleteHere);
+ if (it != rMerged.extents.begin()
+ && (it-1)->pNode == &rNode
+ && (it-1)->nEnd == it->nStart)
+ { // merge adjacent extents
+ nTFIndex += it->nEnd - it->nStart;
+ (it-1)->nEnd = it->nEnd;
+ it = rMerged.extents.erase(it);
+ bErase = true; // skip increment
+ }
+ }
+ else
+ { // exclude text marked as deleted
+ if (nIndex + nDeleteHere == it->nEnd)
+ {
+ it->nEnd -= nDeleteHere;
+ }
+ else
+ {
+ if (nIndex == it->nStart)
+ {
+ it->nStart += nDeleteHere;
+ }
+ else
+ {
+ sal_Int32 const nOldEnd(it->nEnd);
+ it->nEnd = nIndex;
+ it = rMerged.extents.emplace(it+1,
+ it->pNode, nIndex + nDeleteHere, nOldEnd);
+ }
+ assert(nDeleteHere == nToDelete);
+ }
+ }
+ nDeleted += nDeleteHere;
+ nToDelete -= nDeleteHere;
+ nIndex += nDeleteHere;
+ if (!isRealDelete && nToDelete == 0)
+ {
+ break;
+ }
+ }
+ }
+ }
+ else if (nFoundNode != 0)
+ {
+ break;
+ }
+ if (!bErase)
+ {
+ nTFIndex += it->nEnd - it->nStart;
+ ++it;
+ }
+ }
+// assert(nFoundNode != 0 && "text node not found - why is it sending hints to us");
+ assert(nIndex <= rNode.Len() + nLen);
+ // if there's a remaining deletion, it must be in gap at the end of the node
+// can't do: might be last one in node was erased assert(nLen == 0 || rMerged.empty() || (it-1)->nEnd <= nIndex);
+ // note: if first node gets deleted then that must call DelFrames as
+ // pFirstNode is never updated
+ if (nErased && nErased == nFoundNode)
+ { // all visible text from node was erased
+#if 1
+ if (rMerged.pParaPropsNode == &rNode)
+ {
+ rMerged.pParaPropsNode->RemoveFromListRLHidden();
+ rMerged.pParaPropsNode = rMerged.extents.empty()
+ ? const_cast<SwTextNode*>(rMerged.pLastNode)
+ : rMerged.extents.front().pNode;
+ rMerged.pParaPropsNode->AddToListRLHidden();
+ }
+#endif
+// NOPE must listen on all non-hidden nodes; particularly on pLastNode rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode));
+ }
+ rMerged.mergedText = text.makeStringAndClear();
+ return TextFrameIndex(nDeleted);
+}
+
+std::pair<SwTextNode*, sal_Int32>
+MapViewToModel(MergedPara const& rMerged, TextFrameIndex const i_nIndex)
+{
+ sal_Int32 nIndex(i_nIndex);
+ sw::Extent const* pExtent(nullptr);
+ for (const auto& rExt : rMerged.extents)
+ {
+ pExtent = &rExt;
+ if (nIndex < (pExtent->nEnd - pExtent->nStart))
+ {
+ return std::make_pair(pExtent->pNode, pExtent->nStart + nIndex);
+ }
+ nIndex = nIndex - (pExtent->nEnd - pExtent->nStart);
+ }
+ assert(nIndex == 0 && "view index out of bounds");
+ return pExtent
+ ? std::make_pair(pExtent->pNode, pExtent->nEnd) //1-past-the-end index
+ : std::make_pair(const_cast<SwTextNode*>(rMerged.pLastNode), rMerged.pLastNode->Len());
+}
+
+TextFrameIndex MapModelToView(MergedPara const& rMerged, SwTextNode const*const pNode, sal_Int32 const nIndex)
+{
+ assert(rMerged.pFirstNode->GetIndex() <= pNode->GetIndex()
+ && pNode->GetIndex() <= rMerged.pLastNode->GetIndex());
+ sal_Int32 nRet(0);
+ bool bFoundNode(false);
+ for (auto const& e : rMerged.extents)
+ {
+ if (pNode->GetIndex() < e.pNode->GetIndex())
+ {
+ return TextFrameIndex(nRet);
+ }
+ if (e.pNode == pNode)
+ {
+ if (e.nStart <= nIndex && nIndex <= e.nEnd)
+ {
+ return TextFrameIndex(nRet + (nIndex - e.nStart));
+ }
+ else if (nIndex < e.nStart)
+ {
+ // in gap before this extent => map to 0 here TODO???
+ return TextFrameIndex(nRet);
+ }
+ bFoundNode = true;
+ }
+ else if (bFoundNode)
+ {
+ break;
+ }
+ nRet += e.nEnd - e.nStart;
+ }
+ if (bFoundNode)
+ {
+ // must be in a gap at the end of the node
+ assert(nIndex <= pNode->Len());
+ return TextFrameIndex(nRet);
+ }
+ else if (rMerged.extents.empty())
+ {
+ assert(nIndex <= pNode->Len());
+ return TextFrameIndex(0);
+ }
+ return TextFrameIndex(rMerged.mergedText.getLength());
+}
+
+} // namespace sw
+
+std::pair<SwTextNode*, sal_Int32>
+SwTextFrame::MapViewToModel(TextFrameIndex const nIndex) const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ return sw::MapViewToModel(*pMerged, nIndex);
+ }
+ else
+ {
+ return std::make_pair(static_cast<SwTextNode*>(const_cast<sw::BroadcastingModify*>(
+ SwFrame::GetDep())), sal_Int32(nIndex));
+ }
+}
+
+SwPosition SwTextFrame::MapViewToModelPos(TextFrameIndex const nIndex) const
+{
+ std::pair<SwTextNode*, sal_Int32> const ret(MapViewToModel(nIndex));
+ return SwPosition(*ret.first, ret.second);
+}
+
+TextFrameIndex SwTextFrame::MapModelToView(SwTextNode const*const pNode, sal_Int32 const nIndex) const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+ return sw::MapModelToView(*pMerged, pNode, nIndex);
+ }
+ else
+ {
+ assert(static_cast<const SwTextNode*>(SwFrame::GetDep()) == pNode);
+ return TextFrameIndex(nIndex);
+ }
+}
+
+TextFrameIndex SwTextFrame::MapModelToViewPos(SwPosition const& rPos) const
+{
+ SwTextNode const*const pNode(rPos.GetNode().GetTextNode());
+ sal_Int32 const nIndex(rPos.GetContentIndex());
+ return MapModelToView(pNode, nIndex);
+}
+
+void SwTextFrame::SetMergedPara(std::unique_ptr<sw::MergedPara> p)
+{
+ SwTextNode *const pFirst(m_pMergedPara ? m_pMergedPara->pFirstNode : nullptr);
+ m_pMergedPara = std::move(p);
+ if (pFirst)
+ {
+ if (m_pMergedPara)
+ {
+ assert(pFirst == m_pMergedPara->pFirstNode);
+ }
+ else
+ {
+ pFirst->Add(this); // must register at node again
+ }
+ }
+ // postcondition: frame must be listening somewhere
+ assert(m_pMergedPara || GetDep());
+}
+
+const OUString& SwTextFrame::GetText() const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->mergedText;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep())->GetText();
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeForParaProps() const
+{
+ // FIXME can GetPara be 0 ? yes... this is needed in SwContentNotify::SwContentNotify() which is called before any formatting is started
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ {
+// assert(pMerged->pFirstNode == pMerged->pParaPropsNode); // surprising news!
+ return pMerged->pParaPropsNode;
+ }
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeForFirstText() const
+{
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->extents.empty()
+ ? pMerged->pFirstNode
+ : pMerged->extents.front().pNode;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwTextNode const* SwTextFrame::GetTextNodeFirst() const
+{
+//nope assert(GetPara());
+ sw::MergedPara const*const pMerged(GetMergedPara());
+ if (pMerged)
+ return pMerged->pFirstNode;
+ else
+ return static_cast<SwTextNode const*>(SwFrame::GetDep());
+}
+
+SwDoc const& SwTextFrame::GetDoc() const
+{
+ return GetTextNodeFirst()->GetDoc();
+}
+
+LanguageType SwTextFrame::GetLangOfChar(TextFrameIndex const nIndex,
+ sal_uInt16 const nScript, bool const bNoChar) const
+{
+ // a single character can be mapped uniquely!
+ std::pair<SwTextNode const*, sal_Int32> const pos(MapViewToModel(nIndex));
+ return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, nScript);
+}
+
+void SwTextFrame::ResetPreps()
+{
+ if ( GetCacheIdx() != USHRT_MAX )
+ {
+ if (SwParaPortion *pPara = GetPara())
+ pPara->ResetPreps();
+ }
+}
+
+bool SwTextFrame::IsHiddenNow() const
+{
+ SwFrameSwapper aSwapper( this, true );
+
+ if( !getFrameArea().Width() && isFrameAreaDefinitionValid() && GetUpper()->isFrameAreaDefinitionValid() ) // invalid when stack overflows (StackHack)!
+ {
+// OSL_FAIL( "SwTextFrame::IsHiddenNow: thin frame" );
+ return true;
+ }
+
+ bool bHiddenCharsHidePara(false);
+ bool bHiddenParaField(false);
+ if (m_pMergedPara)
+ {
+ TextFrameIndex nHiddenStart(COMPLETE_STRING);
+ TextFrameIndex nHiddenEnd(0);
+ if (auto const pScriptInfo = GetScriptInfo())
+ {
+ pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0),
+ nHiddenStart, nHiddenEnd);
+ }
+ else // ParaPortion is created in Format, but this is called earlier
+ {
+ SwScriptInfo aInfo;
+ aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, m_pMergedPara.get(), IsRightToLeft());
+ aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
+ nHiddenStart, nHiddenEnd);
+ }
+ if (TextFrameIndex(0) == nHiddenStart &&
+ TextFrameIndex(GetText().getLength()) <= nHiddenEnd)
+ {
+ bHiddenCharsHidePara = true;
+ }
+ sw::MergedAttrIter iter(*this);
+ SwTextNode const* pNode(nullptr);
+ int nNewResultWeight = 0;
+ for (SwTextAttr const* pHint = iter.NextAttr(&pNode); pHint; pHint = iter.NextAttr(&pNode))
+ {
+ if (pHint->Which() == RES_TXTATR_FIELD)
+ {
+ // see also SwpHints::CalcHiddenParaField()
+ const SwFormatField& rField = pHint->GetFormatField();
+ int nCurWeight = pNode->GetDoc().FieldCanHideParaWeight(rField.GetField()->GetTyp()->Which());
+ if (nCurWeight > nNewResultWeight)
+ {
+ nNewResultWeight = nCurWeight;
+ bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ else if (nCurWeight == nNewResultWeight && bHiddenParaField)
+ {
+ // Currently, for both supported hiding types (HiddenPara, Database), "Don't hide"
+ // takes precedence - i.e., if there's a "Don't hide" field of that weight, we only
+ // care about fields of higher weight.
+ bHiddenParaField = pNode->GetDoc().FieldHidesPara(*rField.GetField());
+ }
+ }
+ }
+ }
+ else
+ {
+ bHiddenCharsHidePara = static_cast<SwTextNode const*>(SwFrame::GetDep())->HasHiddenCharAttribute( true );
+ bHiddenParaField = static_cast<SwTextNode const*>(SwFrame::GetDep())->IsHiddenByParaField();
+ }
+ const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+
+ if ( pVsh && ( bHiddenCharsHidePara || bHiddenParaField ) )
+ {
+
+ if (
+ ( bHiddenParaField &&
+ ( !pVsh->GetViewOptions()->IsShowHiddenPara() &&
+ !pVsh->GetViewOptions()->IsFieldName() ) ) ||
+ ( bHiddenCharsHidePara &&
+ !pVsh->GetViewOptions()->IsShowHiddenChar() ) )
+ {
+ // in order to put the cursor in the body text, one paragraph must
+ // be visible - check this for the 1st body paragraph
+ if (IsInDocBody() && FindPrevCnt() == nullptr)
+ {
+ bool isAllHidden(true);
+ for (SwContentFrame const* pNext = FindNextCnt(true);
+ pNext != nullptr; pNext = pNext->FindNextCnt(true))
+ {
+ if (!pNext->IsTextFrame()
+ || !static_cast<SwTextFrame const*>(pNext)->IsHiddenNow())
+ {
+ isAllHidden = false;
+ break;
+ }
+ }
+ if (isAllHidden)
+ {
+ SAL_INFO("sw.core", "unhiding one body paragraph");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/// Removes Textfrm's attachments, when it's hidden
+void SwTextFrame::HideHidden()
+{
+ OSL_ENSURE( !GetFollow() && IsHiddenNow(),
+ "HideHidden on visible frame of hidden frame has follow" );
+
+ HideFootnotes(GetOffset(), TextFrameIndex(COMPLETE_STRING));
+ HideAndShowObjects();
+
+ // format information is obsolete
+ ClearPara();
+}
+
+void SwTextFrame::HideFootnotes(TextFrameIndex const nStart, TextFrameIndex const nEnd)
+{
+ SwPageFrame *pPage = nullptr;
+ sw::MergedAttrIter iter(*this);
+ SwTextNode const* pNode(nullptr);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if (pHt->Which() == RES_TXTATR_FTN)
+ {
+ TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
+ if (nEnd < nIdx)
+ break;
+ if (nStart <= nIdx)
+ {
+ if (!pPage)
+ pPage = FindPageFrame();
+ pPage->RemoveFootnote( this, static_cast<const SwTextFootnote*>(pHt) );
+ }
+ }
+ }
+}
+
+/**
+ * as-character anchored graphics, which are used for a graphic bullet list.
+ * As long as these graphic bullet list aren't imported, do not hide a
+ * at-character anchored object, if
+ * (a) the document is an imported WW8 document -
+ * checked by checking certain compatibility options -
+ * (b) the paragraph is the last content in the document and
+ * (c) the anchor character is an as-character anchored graphic.
+ */
+bool sw_HideObj( const SwTextFrame& _rFrame,
+ const RndStdIds _eAnchorType,
+ SwFormatAnchor const& rFormatAnchor,
+ SwAnchoredObject* _pAnchoredObj )
+{
+ bool bRet( true );
+
+ if (_eAnchorType == RndStdIds::FLY_AT_CHAR)
+ {
+ const IDocumentSettingAccess *const pIDSA = &_rFrame.GetDoc().getIDocumentSettingAccess();
+ if ( !pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) &&
+ !pIDSA->get(DocumentSettingId::OLD_LINE_SPACING) &&
+ !pIDSA->get(DocumentSettingId::USE_FORMER_OBJECT_POS) &&
+ pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) &&
+ _rFrame.IsInDocBody() && !_rFrame.FindNextCnt() )
+ {
+ SwTextNode const& rNode(*rFormatAnchor.GetAnchorNode()->GetTextNode());
+ assert(FrameContainsNode(_rFrame, rNode.GetIndex()));
+ sal_Int32 const nObjAnchorPos(rFormatAnchor.GetAnchorContentOffset());
+ const sal_Unicode cAnchorChar = nObjAnchorPos < rNode.Len()
+ ? rNode.GetText()[nObjAnchorPos]
+ : 0;
+ if (cAnchorChar == CH_TXTATR_BREAKWORD)
+ {
+ const SwTextAttr* const pHint(
+ rNode.GetTextAttrForCharAt(nObjAnchorPos, RES_TXTATR_FLYCNT));
+ if ( pHint )
+ {
+ const SwFrameFormat* pFrameFormat =
+ static_cast<const SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
+ if ( pFrameFormat->Which() == RES_FLYFRMFMT )
+ {
+ SwNodeIndex nContentIndex = *(pFrameFormat->GetContent().GetContentIdx());
+ ++nContentIndex;
+ if ( nContentIndex.GetNode().IsNoTextNode() )
+ {
+ bRet = false;
+ // set needed data structure values for object positioning
+ SwRectFnSet aRectFnSet(&_rFrame);
+ SwRect aLastCharRect( _rFrame.getFrameArea() );
+ aRectFnSet.SetWidth( aLastCharRect, 1 );
+ _pAnchoredObj->maLastCharRect = aLastCharRect;
+ _pAnchoredObj->mnLastTopOfLine = aRectFnSet.GetTop(aLastCharRect);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return bRet;
+}
+
+/**
+ * Hide/show objects
+ *
+ * Method hides respectively shows objects, which are anchored at paragraph,
+ * at/as a character of the paragraph, corresponding to the paragraph and
+ * paragraph portion visibility.
+ *
+ * - is called from HideHidden() - should hide objects in hidden paragraphs and
+ * - from Format_() - should hide/show objects in partly visible paragraphs
+ */
+void SwTextFrame::HideAndShowObjects()
+{
+ if ( GetDrawObjs() )
+ {
+ if ( IsHiddenNow() )
+ {
+ // complete paragraph is hidden. Thus, hide all objects
+ for (SwAnchoredObject* i : *GetDrawObjs())
+ {
+ SdrObject* pObj = i->DrawObj();
+ SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
+ // under certain conditions
+ const RndStdIds eAnchorType( pContact->GetAnchorId() );
+ if ((eAnchorType != RndStdIds::FLY_AT_CHAR) ||
+ sw_HideObj(*this, eAnchorType, pContact->GetAnchorFormat(),
+ i ))
+ {
+ pContact->MoveObjToInvisibleLayer( pObj );
+ }
+ }
+ }
+ else
+ {
+ // paragraph is visible, but can contain hidden text portion.
+ // first we check if objects are allowed to be hidden:
+ const SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+ const bool bShouldBeHidden = !pVsh || !pVsh->GetWin() ||
+ !pVsh->GetViewOptions()->IsShowHiddenChar();
+
+ // Thus, show all objects, which are anchored at paragraph and
+ // hide/show objects, which are anchored at/as character, according
+ // to the visibility of the anchor character.
+ for (SwAnchoredObject* i : *GetDrawObjs())
+ {
+ SdrObject* pObj = i->DrawObj();
+ SwContact* pContact = static_cast<SwContact*>(pObj->GetUserCall());
+ // Determine anchor type only once
+ const RndStdIds eAnchorType( pContact->GetAnchorId() );
+
+ if (eAnchorType == RndStdIds::FLY_AT_PARA)
+ {
+ pContact->MoveObjToVisibleLayer( pObj );
+ }
+ else if ((eAnchorType == RndStdIds::FLY_AT_CHAR) ||
+ (eAnchorType == RndStdIds::FLY_AS_CHAR))
+ {
+ sal_Int32 nHiddenStart;
+ sal_Int32 nHiddenEnd;
+ const SwFormatAnchor& rAnchorFormat = pContact->GetAnchorFormat();
+ SwScriptInfo::GetBoundsOfHiddenRange(
+ *rAnchorFormat.GetAnchorNode()->GetTextNode(),
+ rAnchorFormat.GetAnchorContentOffset(), nHiddenStart, nHiddenEnd);
+ // Under certain conditions
+ if ( nHiddenStart != COMPLETE_STRING && bShouldBeHidden &&
+ sw_HideObj(*this, eAnchorType, rAnchorFormat, i))
+ {
+ pContact->MoveObjToInvisibleLayer( pObj );
+ }
+ else
+ pContact->MoveObjToVisibleLayer( pObj );
+ }
+ else
+ {
+ OSL_FAIL( "<SwTextFrame::HideAndShowObjects()> - object not anchored at/inside paragraph!?" );
+ }
+ }
+ }
+ }
+
+ if (IsFollow())
+ {
+ SwTextFrame *pMaster = FindMaster();
+ OSL_ENSURE(pMaster, "SwTextFrame without master");
+ if (pMaster)
+ pMaster->HideAndShowObjects();
+ }
+}
+
+/**
+ * Returns the first possible break point in the current line.
+ * This method is used in SwTextFrame::Format() to decide whether the previous
+ * line has to be formatted as well.
+ * nFound is <= nEndLine.
+ */
+TextFrameIndex SwTextFrame::FindBrk(std::u16string_view aText,
+ const TextFrameIndex nStart,
+ const TextFrameIndex nEnd)
+{
+ sal_Int32 nFound = sal_Int32(nStart);
+ const sal_Int32 nEndLine = std::min(sal_Int32(nEnd), sal_Int32(aText.size()) - 1);
+
+ // Skip all leading blanks.
+ while( nFound <= nEndLine && ' ' == aText[nFound] )
+ {
+ nFound++;
+ }
+
+ // A tricky situation with the TextAttr-Dummy-character (in this case "$"):
+ // "Dr.$Meyer" at the beginning of the second line. Typing a blank after that
+ // doesn't result in the word moving into first line, even though that would work.
+ // For this reason we don't skip the dummy char.
+ while( nFound <= nEndLine && ' ' != aText[nFound] )
+ {
+ nFound++;
+ }
+
+ return TextFrameIndex(nFound);
+}
+
+bool SwTextFrame::IsIdxInside(TextFrameIndex const nPos, TextFrameIndex const nLen) const
+{
+ if (nPos == TextFrameIndex(COMPLETE_STRING)) // the "not found" range
+ return false;
+// Silence over-eager warning emitted at least by GCC trunk towards 6:
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-overflow"
+#endif
+ if (nLen != TextFrameIndex(COMPLETE_STRING) && GetOffset() > nPos + nLen) // the range preceded us
+#if defined __GNUC__ && !defined __clang__
+#pragma GCC diagnostic pop
+#endif
+ return false;
+
+ if( !GetFollow() ) // the range doesn't precede us,
+ return true; // nobody follows us.
+
+ TextFrameIndex const nMax = GetFollow()->GetOffset();
+
+ // either the range overlap or our text has been deleted
+ // sw_redlinehide: GetText() should be okay here because it has already
+ // been updated in the INS/DEL hint case
+ if (nMax > nPos || nMax > TextFrameIndex(GetText().getLength()))
+ return true;
+
+ // changes made in the first line of a follow can modify the master
+ const SwParaPortion* pPara = GetFollow()->GetPara();
+ return pPara && ( nPos <= nMax + pPara->GetLen() );
+}
+
+inline void SwTextFrame::InvalidateRange(const SwCharRange &aRange, const tools::Long nD)
+{
+ if ( IsIdxInside( aRange.Start(), aRange.Len() ) )
+ InvalidateRange_( aRange, nD );
+}
+
+void SwTextFrame::InvalidateRange_( const SwCharRange &aRange, const tools::Long nD)
+{
+ if ( !HasPara() )
+ { InvalidateSize();
+ return;
+ }
+
+ SetWidow( false );
+ SwParaPortion *pPara = GetPara();
+
+ bool bInv = false;
+ if( 0 != nD )
+ {
+ // In nDelta the differences between old and new
+ // linelengths are being added, that's why it's negative
+ // if chars have been added and positive, if chars have
+ // deleted
+ pPara->SetDelta(pPara->GetDelta() + nD);
+ bInv = true;
+ }
+ SwCharRange &rReformat = pPara->GetReformat();
+ if(aRange != rReformat) {
+ if (TextFrameIndex(COMPLETE_STRING) == rReformat.Len())
+ rReformat = aRange;
+ else
+ rReformat += aRange;
+ bInv = true;
+ }
+ if(bInv)
+ {
+ InvalidateSize();
+ }
+}
+
+void SwTextFrame::CalcLineSpace()
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::CalcLineSpace with swapped frame!" );
+
+ if( IsLocked() || !HasPara() )
+ return;
+
+ if( GetDrawObjs() ||
+ GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent().IsAutoFirst())
+ {
+ Init();
+ return;
+ }
+
+ SwParaPortion *const pPara(GetPara());
+ assert(pPara);
+ if (pPara->IsFixLineHeight())
+ {
+ Init();
+ return;
+ }
+
+ Size aNewSize( getFramePrintArea().SSize() );
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
+ SwTextFormatter aLine( this, &aInf );
+ if( aLine.GetDropLines() )
+ {
+ Init();
+ return;
+ }
+
+ aLine.Top();
+ aLine.RecalcRealHeight();
+
+ aNewSize.setHeight( (aLine.Y() - getFrameArea().Top()) + aLine.GetLineHeight() );
+
+ SwTwips nDelta = aNewSize.Height() - getFramePrintArea().Height();
+ // Underflow with free-flying frames
+ if( aInf.GetTextFly().IsOn() )
+ {
+ SwRect aTmpFrame( getFrameArea() );
+ if( nDelta < 0 )
+ aTmpFrame.Height( getFramePrintArea().Height() );
+ else
+ aTmpFrame.Height( aNewSize.Height() );
+ if( aInf.GetTextFly().Relax( aTmpFrame ) )
+ {
+ Init();
+ return;
+ }
+ }
+
+ if( !nDelta )
+ return;
+
+ SwTextFrameBreak aBreak( this );
+ if( GetFollow() || aBreak.IsBreakNow( aLine ) )
+ {
+ // if there is a Follow() or if we need to break here, reformat
+ Init();
+ }
+ else
+ {
+ // everything is business as usual...
+ pPara->SetPrepAdjust();
+ pPara->SetPrep();
+ }
+}
+
+static void lcl_SetWrong( SwTextFrame& rFrame, SwTextNode const& rNode,
+ sal_Int32 const nPos, sal_Int32 const nCnt, bool const bMove)
+{
+ if ( !rFrame.IsFollow() )
+ {
+ SwTextNode& rTextNode = const_cast<SwTextNode&>(rNode);
+ sw::GrammarContact* pGrammarContact = sw::getGrammarContactFor(rTextNode);
+ SwGrammarMarkUp* pWrongGrammar = pGrammarContact ?
+ pGrammarContact->getGrammarCheck( rTextNode, false ) :
+ rTextNode.GetGrammarCheck();
+ bool bGrammarProxy = pWrongGrammar != rTextNode.GetGrammarCheck();
+ if( bMove )
+ {
+ if( rTextNode.GetWrong() )
+ rTextNode.GetWrong()->Move( nPos, nCnt );
+ if( pWrongGrammar )
+ pWrongGrammar->MoveGrammar( nPos, nCnt );
+ if( bGrammarProxy && rTextNode.GetGrammarCheck() )
+ rTextNode.GetGrammarCheck()->MoveGrammar( nPos, nCnt );
+ if( rTextNode.GetSmartTags() )
+ rTextNode.GetSmartTags()->Move( nPos, nCnt );
+ }
+ else
+ {
+ if( rTextNode.GetWrong() )
+ rTextNode.GetWrong()->Invalidate( nPos, nCnt );
+ if( pWrongGrammar )
+ pWrongGrammar->Invalidate( nPos, nCnt );
+ if( rTextNode.GetSmartTags() )
+ rTextNode.GetSmartTags()->Invalidate( nPos, nCnt );
+ }
+ const sal_Int32 nEnd = nPos + (nCnt > 0 ? nCnt : 1 );
+ if ( !rTextNode.GetWrong() && !rTextNode.IsWrongDirty() )
+ {
+ rTextNode.SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) );
+ rTextNode.GetWrong()->SetInvalid( nPos, nEnd );
+ }
+ if ( !rTextNode.GetSmartTags() && !rTextNode.IsSmartTagDirty() )
+ {
+ rTextNode.SetSmartTags( std::make_unique<SwWrongList>( WRONGLIST_SMARTTAG ) );
+ rTextNode.GetSmartTags()->SetInvalid( nPos, nEnd );
+ }
+ rTextNode.SetWrongDirty(sw::WrongState::TODO);
+ rTextNode.SetGrammarCheckDirty( true );
+ rTextNode.SetWordCountDirty( true );
+ rTextNode.SetAutoCompleteWordDirty( true );
+ rTextNode.SetSmartTagDirty( true );
+ }
+
+ SwRootFrame *pRootFrame = rFrame.getRootFrame();
+ if (pRootFrame)
+ {
+ pRootFrame->SetNeedGrammarCheck( true );
+ }
+
+ SwPageFrame *pPage = rFrame.FindPageFrame();
+ if( pPage )
+ {
+ pPage->InvalidateSpelling();
+ pPage->InvalidateAutoCompleteWords();
+ pPage->InvalidateWordCount();
+ pPage->InvalidateSmartTags();
+ }
+}
+
+static void lcl_SetScriptInval(SwTextFrame& rFrame, TextFrameIndex const nPos)
+{
+ if( rFrame.GetPara() )
+ rFrame.GetPara()->GetScriptInfo().SetInvalidityA( nPos );
+}
+
+// note: SwClientNotify will be called once for every frame => just fix own Ofst
+static void lcl_ModifyOfst(SwTextFrame & rFrame,
+ TextFrameIndex const nPos, TextFrameIndex const nLen,
+ TextFrameIndex (* op)(TextFrameIndex const&, TextFrameIndex const&))
+{
+ assert(nLen != TextFrameIndex(COMPLETE_STRING));
+ if (rFrame.IsFollow() && nPos < rFrame.GetOffset())
+ {
+ rFrame.ManipOfst( std::max(nPos, op(rFrame.GetOffset(), nLen)) );
+ assert(sal_Int32(rFrame.GetOffset()) <= rFrame.GetText().getLength());
+ }
+}
+
+namespace {
+
+void UpdateMergedParaForMove(sw::MergedPara & rMerged,
+ SwTextFrame & rTextFrame,
+ bool & o_rbRecalcFootnoteFlag,
+ SwTextNode const& rDestNode,
+ SwTextNode const& rNode,
+ sal_Int32 const nDestStart,
+ sal_Int32 const nSourceStart,
+ sal_Int32 const nLen)
+{
+ std::vector<std::pair<sal_Int32, sal_Int32>> deleted;
+ sal_Int32 const nSourceEnd(nSourceStart + nLen);
+ sal_Int32 nLastEnd(0);
+ for (const auto& rExt : rMerged.extents)
+ {
+ if (rExt.pNode == &rNode)
+ {
+ sal_Int32 const nStart(std::max(nLastEnd, nSourceStart));
+ sal_Int32 const nEnd(std::min(rExt.nStart, nSourceEnd));
+ if (nStart < nEnd)
+ {
+ deleted.emplace_back(nStart, nEnd);
+ }
+ nLastEnd = rExt.nEnd;
+ if (nSourceEnd <= rExt.nEnd)
+ {
+ break;
+ }
+ }
+ else if (rNode.GetIndex() < rExt.pNode->GetIndex())
+ {
+ break;
+ }
+ }
+ if (nLastEnd != rNode.Len()) // without nLen, string yet to be removed
+ {
+ if (nLastEnd < nSourceEnd)
+ {
+ deleted.emplace_back(std::max(nLastEnd, nSourceStart), nSourceEnd);
+ }
+ }
+ if (deleted.empty())
+ return;
+
+ o_rbRecalcFootnoteFlag = true;
+ for (auto const& it : deleted)
+ {
+ sal_Int32 const nStart(it.first - nSourceStart + nDestStart);
+ TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, false,
+ rDestNode, nStart, it.second - it.first);
+//FIXME asserts valid for join - but if called from split, the new node isn't there yet and it will be added later... assert(nDeleted);
+// assert(nDeleted == it.second - it.first);
+ if(nDeleted)
+ {
+ // InvalidateRange/lcl_SetScriptInval was called sufficiently for InsertText
+ lcl_SetWrong(rTextFrame, rDestNode, nStart, it.first - it.second, false);
+ TextFrameIndex const nIndex(sw::MapModelToView(rMerged, &rDestNode, nStart));
+ lcl_ModifyOfst(rTextFrame, nIndex, nDeleted, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+}
+
+} // namespace
+
+/**
+ * Related: fdo#56031 filter out attribute changes that don't matter for
+ * humans/a11y to stop flooding the destination mortal with useless noise
+ */
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+static bool isA11yRelevantAttribute(sal_uInt16 nWhich)
+{
+ return nWhich != RES_CHRATR_RSID;
+}
+
+static bool hasA11yRelevantAttribute( const std::vector<sal_uInt16>& rWhichFmtAttr )
+{
+ for( sal_uInt16 nWhich : rWhichFmtAttr )
+ if ( isA11yRelevantAttribute( nWhich ) )
+ return true;
+
+ return false;
+}
+#endif // ENABLE_WASM_STRIP_ACCESSIBILITY
+
+// Note: for now this overrides SwClient::SwClientNotify; the intermediary
+// classes still override SwClient::Modify, which should continue to work
+// as their implementation of SwClientNotify is SwClient's which calls Modify.
+// Therefore we also don't need to call SwClient::SwClientNotify(rModify, rHint)
+// because that's all it does, and this implementation calls
+// SwContentFrame::SwClientNotify() when appropriate.
+void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint)
+{
+ SfxPoolItem const* pOld(nullptr);
+ SfxPoolItem const* pNew(nullptr);
+ sw::MoveText const* pMoveText(nullptr);
+ sw::InsertText const* pInsertText(nullptr);
+ sw::DeleteText const* pDeleteText(nullptr);
+ sw::DeleteChar const* pDeleteChar(nullptr);
+ sw::RedlineDelText const* pRedlineDelText(nullptr);
+ sw::RedlineUnDelText const* pRedlineUnDelText(nullptr);
+
+ sal_uInt16 nWhich = 0;
+ if (rHint.GetId() == SfxHintId::SwLegacyModify)
+ {
+ auto pHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
+ pOld = pHint->m_pOld;
+ pNew = pHint->m_pNew;
+ nWhich = pHint->GetWhich();
+ }
+ else if (rHint.GetId() == SfxHintId::SwInsertText)
+ {
+ pInsertText = static_cast<const sw::InsertText*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDeleteText)
+ {
+ pDeleteText = static_cast<const sw::DeleteText*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDeleteChar)
+ {
+ pDeleteChar = static_cast<const sw::DeleteChar*>(&rHint);
+ }
+ else if (rHint.GetId() == SfxHintId::SwDocPosUpdateAtIndex)
+ {
+ auto pDocPosAt = static_cast<const sw::DocPosUpdateAtIndex*>(&rHint);
+ Broadcast(SfxHint()); // notify SwAccessibleParagraph
+ if(IsLocked())
+ return;
+ if(pDocPosAt->m_nDocPos > getFrameArea().Top())
+ return;
+ TextFrameIndex const nIndex(MapModelToView(
+ &pDocPosAt->m_rNode,
+ pDocPosAt->m_nIndex));
+ InvalidateRange(SwCharRange(nIndex, TextFrameIndex(1)));
+ return;
+ }
+ else if (rHint.GetId() == SfxHintId::SwVirtPageNumHint)
+ {
+ auto& rVirtPageNumHint = const_cast<sw::VirtPageNumHint&>(static_cast<const sw::VirtPageNumHint&>(rHint));
+ if(!IsInDocBody() || IsFollow() || rVirtPageNumHint.IsFound())
+ return;
+ if(const SwPageFrame* pPage = FindPageFrame())
+ pPage->UpdateVirtPageNumInfo(rVirtPageNumHint, this);
+ return;
+ }
+ else if (auto const pHt = dynamic_cast<sw::MoveText const*>(&rHint))
+ {
+ pMoveText = pHt;
+ }
+ else if (auto const pHynt = dynamic_cast<sw::RedlineDelText const*>(&rHint))
+ {
+ pRedlineDelText = pHynt;
+ }
+ else if (auto const pHnt = dynamic_cast<sw::RedlineUnDelText const*>(&rHint))
+ {
+ pRedlineUnDelText = pHnt;
+ }
+ else
+ {
+ assert(!"unexpected hint");
+ }
+
+ if (m_pMergedPara)
+ {
+ assert(m_pMergedPara->listener.IsListeningTo(&rModify));
+ }
+
+ SwTextNode const& rNode(static_cast<SwTextNode const&>(rModify));
+
+ // modifications concerning frame attributes are processed by the base class
+ if( IsInRange( aFrameFormatSetRange, nWhich ) || RES_FMT_CHG == nWhich )
+ {
+ if (m_pMergedPara)
+ { // ignore item set changes that don't apply
+ SwTextNode const*const pAttrNode(
+ (nWhich == RES_PAGEDESC || nWhich == RES_BREAK)
+ ? m_pMergedPara->pFirstNode
+ : m_pMergedPara->pParaPropsNode);
+ if (pAttrNode != &rModify)
+ {
+ return;
+ }
+ }
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew));
+ if( nWhich == RES_FMT_CHG && getRootFrame()->GetCurrShell() )
+ {
+ // collection has changed
+ Prepare();
+ InvalidatePrt_();
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ SetDerivedR2L( false );
+ CheckDirChange();
+ // Force complete paint due to existing indents.
+ SetCompletePaint();
+ InvalidateLineNum();
+ }
+ return;
+ }
+
+ if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
+ {
+ if (isPARATR(nWhich) || isPARATR_LIST(nWhich)) // FRMATR handled above
+ {
+ return; // ignore it
+ }
+ }
+
+ Broadcast(SfxHint()); // notify SwAccessibleParagraph
+
+ // while locked ignore all modifications
+ if( IsLocked() )
+ return;
+
+ // save stack
+ // warning: one has to ensure that all variables are set
+ TextFrameIndex nPos;
+ TextFrameIndex nLen;
+ bool bSetFieldsDirty = false;
+ bool bRecalcFootnoteFlag = false;
+
+ if (pRedlineDelText)
+ {
+ if (m_pMergedPara)
+ {
+ sal_Int32 const nNPos = pRedlineDelText->nStart;
+ sal_Int32 const nNLen = pRedlineDelText->nLen;
+ nPos = MapModelToView(&rNode, nNPos);
+ // update merged before doing anything else
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, nNPos, nNLen);
+ const sal_Int32 m = -nNLen;
+ if (nLen && IsIdxInside(nPos, nLen))
+ {
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
+ }
+ lcl_SetWrong( *this, rNode, nNPos, m, false );
+ if (nLen)
+ {
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ }
+ else if (pRedlineUnDelText)
+ {
+ if (m_pMergedPara)
+ {
+ sal_Int32 const nNPos = pRedlineUnDelText->nStart;
+ sal_Int32 const nNLen = pRedlineUnDelText->nLen;
+ nPos = MapModelToView(&rNode, nNPos);
+ nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, nNPos, nNLen);
+ if (IsIdxInside(nPos, nLen))
+ {
+ if (!nLen)
+ {
+ // Refresh NumPortions even when line is empty!
+ if (nPos)
+ InvalidateSize();
+ else
+ Prepare();
+ }
+ else
+ InvalidateRange_( SwCharRange( nPos, nLen ), nNLen );
+ }
+ lcl_SetWrong( *this, rNode, nNPos, nNLen, false );
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else if (pMoveText)
+ {
+ if (m_pMergedPara
+ && m_pMergedPara->pFirstNode->GetIndex() <= pMoveText->pDestNode->GetIndex()
+ && pMoveText->pDestNode->GetIndex() <= m_pMergedPara->pLastNode->GetIndex())
+ { // if it's not 2 nodes in merged frame, assume the target node doesn't have frames at all
+ assert(abs(rNode.GetIndex() - pMoveText->pDestNode->GetIndex()) == SwNodeOffset(1));
+ UpdateMergedParaForMove(*m_pMergedPara,
+ *this,
+ bRecalcFootnoteFlag,
+ *pMoveText->pDestNode, rNode,
+ pMoveText->nDestStart,
+ pMoveText->nSourceStart,
+ pMoveText->nLen);
+ }
+ else
+ {
+ // there is a situation where this is okay: from JoinNext, which will then call CheckResetRedlineMergeFlag, which will then create merged from scratch for this frame
+ // assert(!m_pMergedPara || !getRootFrame()->IsHideRedlines() || !pMoveText->pDestNode->getLayoutFrame(getRootFrame()));
+ }
+ }
+ else if (pInsertText)
+ {
+ nPos = MapModelToView(&rNode, pInsertText->nPos);
+ // unlike redlines, inserting into fieldmark must be explicitly handled
+ bool isHidden(false);
+ switch (getRootFrame()->GetFieldmarkMode())
+ {
+ case sw::FieldmarkMode::ShowCommand:
+ isHidden = pInsertText->isInsideFieldmarkResult;
+ break;
+ case sw::FieldmarkMode::ShowResult:
+ isHidden = pInsertText->isInsideFieldmarkCommand;
+ break;
+ case sw::FieldmarkMode::ShowBoth: // just to avoid the warning
+ break;
+ }
+ if (!isHidden)
+ {
+ nLen = TextFrameIndex(pInsertText->nLen);
+ if (m_pMergedPara)
+ {
+ UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, pInsertText->nPos, pInsertText->nLen);
+ }
+ if( IsIdxInside( nPos, nLen ) )
+ {
+ if( !nLen )
+ {
+ // Refresh NumPortions even when line is empty!
+ if( nPos )
+ InvalidateSize();
+ else
+ Prepare();
+ }
+ else
+ InvalidateRange_( SwCharRange( nPos, nLen ), pInsertText->nLen );
+ }
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>);
+ }
+ lcl_SetWrong( *this, rNode, pInsertText->nPos, pInsertText->nLen, true );
+ }
+ else if (pDeleteText)
+ {
+ nPos = MapModelToView(&rNode, pDeleteText->nStart);
+ if (m_pMergedPara)
+ { // update merged before doing anything else
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteText->nStart, pDeleteText->nLen);
+ }
+ else
+ {
+ nLen = TextFrameIndex(pDeleteText->nLen);
+ }
+ const sal_Int32 m = -pDeleteText->nLen;
+ if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen))
+ {
+ if( !nLen )
+ InvalidateSize();
+ else
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m );
+ }
+ lcl_SetWrong( *this, rNode, pDeleteText->nStart, m, true );
+ if (nLen)
+ {
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else if (pDeleteChar)
+ {
+ nPos = MapModelToView(&rNode, pDeleteChar->m_nPos);
+ if (m_pMergedPara)
+ {
+ nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, pDeleteChar->m_nPos, 1);
+ }
+ else
+ {
+ nLen = TextFrameIndex(1);
+ }
+ lcl_SetWrong( *this, rNode, pDeleteChar->m_nPos, -1, true );
+ if (nLen)
+ {
+ InvalidateRange( SwCharRange(nPos, nLen), -1 );
+ lcl_SetScriptInval( *this, nPos );
+ bSetFieldsDirty = bRecalcFootnoteFlag = true;
+ lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>);
+ }
+ }
+ else switch (nWhich)
+ {
+ case RES_LINENUMBER:
+ {
+ assert(false); // should have been forwarded to SwContentFrame
+ InvalidateLineNum();
+ }
+ break;
+ case RES_UPDATE_ATTR:
+ {
+ const SwUpdateAttr* pNewUpdate = static_cast<const SwUpdateAttr*>(pNew);
+
+ sal_Int32 const nNPos = pNewUpdate->getStart();
+ sal_Int32 const nNLen = pNewUpdate->getEnd() - nNPos;
+ nPos = MapModelToView(&rNode, nNPos);
+ nLen = MapModelToView(&rNode, nNPos + nNLen) - nPos;
+ if( IsIdxInside( nPos, nLen ) )
+ {
+ // We need to reformat anyways, even if the invalidated
+ // range is empty.
+ // E.g.: empty line, set 14 pt!
+
+ // FootnoteNumbers need to be formatted
+ if( !nLen )
+ nLen = TextFrameIndex(1);
+
+ InvalidateRange_( SwCharRange( nPos, nLen) );
+ const sal_uInt16 nTmp = pNewUpdate->getWhichAttr();
+
+ if( ! nTmp || RES_TXTATR_CHARFMT == nTmp || RES_TXTATR_INETFMT == nTmp || RES_TXTATR_AUTOFMT == nTmp ||
+ RES_FMT_CHG == nTmp || RES_ATTRSET_CHG == nTmp )
+ {
+ lcl_SetWrong( *this, rNode, nNPos, nNPos + nNLen, false );
+ lcl_SetScriptInval( *this, nPos );
+ }
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) &&
+ hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) )
+ {
+ SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
+ if ( pViewSh )
+ {
+ pViewSh->InvalidateAccessibleParaAttrs( *this );
+ }
+ }
+#endif
+ }
+ break;
+ case RES_OBJECTDYING:
+ break;
+
+ case RES_PARATR_LINESPACING:
+ {
+ CalcLineSpace();
+ InvalidateSize();
+ InvalidatePrt_();
+ if( IsInSct() && !GetPrev() )
+ {
+ SwSectionFrame *pSect = FindSctFrame();
+ if( pSect->ContainsAny() == this )
+ pSect->InvalidatePrt();
+ }
+
+ // i#11859
+ // (1) Also invalidate next frame on next page/column.
+ // (2) Skip empty sections and hidden paragraphs
+ // Thus, use method <InvalidateNextPrtArea()>
+ InvalidateNextPrtArea();
+
+ SetCompletePaint();
+ }
+ break;
+
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ {
+ sal_Int32 const nNPos = static_cast<const SwFormatField*>(pNew)->GetTextField()->GetStart();
+ nPos = MapModelToView(&rNode, nNPos);
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ {
+ if (SfxPoolItem::areSame( pNew, pOld ))
+ {
+ // only repaint
+ // opt: invalidate window?
+ InvalidatePage();
+ SetCompletePaint();
+ }
+ else
+ InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ bSetFieldsDirty = true;
+ // ST2
+ if ( SwSmartTagMgr::Get().IsSmartTagsEnabled() )
+ lcl_SetWrong( *this, rNode, nNPos, nNPos + 1, false );
+ }
+ break;
+
+ case RES_TXTATR_FTN :
+ {
+ if (!IsInFootnote())
+ { // the hint may be sent from the anchor node, or from a
+ // node in the footnote; the anchor index is only valid in the
+ // anchor node!
+ assert(rNode == static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetTextNode());
+ nPos = MapModelToView(&rNode,
+ static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetStart());
+ }
+#ifdef _MSC_VER
+ else nPos = TextFrameIndex(42); // shut up MSVC 2017 spurious warning C4701
+#endif
+ if (IsInFootnote() || IsIdxInside(nPos, TextFrameIndex(1)))
+ Prepare( PrepareHint::FootnoteInvalidation, static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote() );
+ break;
+ }
+
+ case RES_ATTRSET_CHG:
+ {
+ InvalidateLineNum();
+
+ const SwAttrSet& rNewSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet();
+ int nClear = 0;
+ sal_uInt16 nCount = rNewSet.Count();
+
+ if( const SwFormatFootnote* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FTN, false ) )
+ {
+ nPos = MapModelToView(&rNode, pItem->GetTextFootnote()->GetStart());
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ Prepare( PrepareHint::FootnoteInvalidation, pNew );
+ nClear = 0x01;
+ --nCount;
+ }
+
+ if( const SwFormatField* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FIELD, false ) )
+ {
+ nPos = MapModelToView(&rNode, pItem->GetTextField()->GetStart());
+ if (IsIdxInside(nPos, TextFrameIndex(1)))
+ {
+ const SfxPoolItem* pOldItem = pOld ?
+ &(static_cast<const SwAttrSetChg*>(pOld)->GetChgSet()->Get(RES_TXTATR_FIELD)) : nullptr;
+ if (SfxPoolItem::areSame( pItem, pOldItem ))
+ {
+ InvalidatePage();
+ SetCompletePaint();
+ }
+ else
+ InvalidateRange_(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ nClear |= 0x02;
+ --nCount;
+ }
+ bool bLineSpace = SfxItemState::SET == rNewSet.GetItemState(
+ RES_PARATR_LINESPACING, false ),
+ bRegister = SfxItemState::SET == rNewSet.GetItemState(
+ RES_PARATR_REGISTER, false );
+ if ( bLineSpace || bRegister )
+ {
+ if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ {
+ Prepare( bRegister ? PrepareHint::Register : PrepareHint::AdjustSizeWithoutFormatting );
+ CalcLineSpace();
+ InvalidateSize();
+ InvalidatePrt_();
+
+ // i#11859
+ // (1) Also invalidate next frame on next page/column.
+ // (2) Skip empty sections and hidden paragraphs
+ // Thus, use method <InvalidateNextPrtArea()>
+ InvalidateNextPrtArea();
+
+ SetCompletePaint();
+ }
+ nClear |= 0x04;
+ if ( bLineSpace )
+ {
+ --nCount;
+ if ((!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ && IsInSct() && !GetPrev())
+ {
+ SwSectionFrame *pSect = FindSctFrame();
+ if( pSect->ContainsAny() == this )
+ pSect->InvalidatePrt();
+ }
+ }
+ if ( bRegister )
+ --nCount;
+ }
+ if ( SfxItemState::SET == rNewSet.GetItemState( RES_PARATR_SPLIT,
+ false ))
+ {
+ if (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ {
+ if (GetPrev())
+ CheckKeep();
+ Prepare();
+ InvalidateSize();
+ }
+ nClear |= 0x08;
+ --nCount;
+ }
+
+ if( SfxItemState::SET == rNewSet.GetItemState( RES_BACKGROUND, false)
+ && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify)
+ && !IsFollow() && GetDrawObjs() )
+ {
+ SwSortedObjs *pObjs = GetDrawObjs();
+ for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*pObjs)[i];
+ if ( auto pFly = pAnchoredObj->DynCastFlyFrame() )
+ {
+ if( !pFly->IsFlyInContentFrame() )
+ {
+ const SvxBrushItem &rBack =
+ pFly->GetAttrSet()->GetBackground();
+ // #GetTransChg#
+ // following condition determines, if the fly frame
+ // "inherites" the background color of text frame.
+ // This is the case, if fly frame background
+ // color is "no fill"/"auto fill" and if the fly frame
+ // has no background graphic.
+ // Thus, check complete fly frame background
+ // color and *not* only its transparency value
+ if ( (rBack.GetColor() == COL_TRANSPARENT) &&
+ rBack.GetGraphicPos() == GPOS_NONE )
+ {
+ pFly->SetCompletePaint();
+ pFly->InvalidatePage();
+ }
+ }
+ }
+ }
+ }
+
+ if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_TXTATR_CHARFMT, false ) )
+ {
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ lcl_SetScriptInval( *this, TextFrameIndex(0) );
+ }
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_LANGUAGE, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CJK_LANGUAGE, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CTL_LANGUAGE, false ) )
+ lcl_SetWrong( *this, rNode, 0, COMPLETE_STRING, false );
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_FONT, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CJK_FONT, false ) ||
+ SfxItemState::SET ==
+ rNewSet.GetItemState( RES_CHRATR_CTL_FONT, false ) )
+ lcl_SetScriptInval( *this, TextFrameIndex(0) );
+ else if ( SfxItemState::SET ==
+ rNewSet.GetItemState( RES_FRAMEDIR, false )
+ && (!m_pMergedPara || m_pMergedPara->pParaPropsNode == &rModify))
+ {
+ SetDerivedR2L( false );
+ CheckDirChange();
+ // Force complete paint due to existing indents.
+ SetCompletePaint();
+ }
+
+ if( nCount )
+ {
+ if( getRootFrame()->GetCurrShell() )
+ {
+ Prepare();
+ InvalidatePrt_();
+ }
+
+ if (nClear || (m_pMergedPara &&
+ (m_pMergedPara->pParaPropsNode != &rModify ||
+ m_pMergedPara->pFirstNode != &rModify)))
+ {
+ assert(pOld);
+ SwAttrSetChg aOldSet( *static_cast<const SwAttrSetChg*>(pOld) );
+ SwAttrSetChg aNewSet( *static_cast<const SwAttrSetChg*>(pNew) );
+
+ if (m_pMergedPara && m_pMergedPara->pParaPropsNode != &rModify)
+ {
+ for (sal_uInt16 i = RES_PARATR_BEGIN; i != RES_FRMATR_END; ++i)
+ {
+ if (i != RES_BREAK && i != RES_PAGEDESC)
+ {
+ aOldSet.ClearItem(i);
+ aNewSet.ClearItem(i);
+ }
+ }
+ for (sal_uInt16 i = XATTR_FILL_FIRST; i <= XATTR_FILL_LAST; ++i)
+ {
+ aOldSet.ClearItem(i);
+ aNewSet.ClearItem(i);
+ }
+ }
+ if (m_pMergedPara && m_pMergedPara->pFirstNode != &rModify)
+ {
+ aOldSet.ClearItem(RES_BREAK);
+ aNewSet.ClearItem(RES_BREAK);
+ aOldSet.ClearItem(RES_PAGEDESC);
+ aNewSet.ClearItem(RES_PAGEDESC);
+ }
+
+ if( 0x01 & nClear )
+ {
+ aOldSet.ClearItem( RES_TXTATR_FTN );
+ aNewSet.ClearItem( RES_TXTATR_FTN );
+ }
+ if( 0x02 & nClear )
+ {
+ aOldSet.ClearItem( RES_TXTATR_FIELD );
+ aNewSet.ClearItem( RES_TXTATR_FIELD );
+ }
+ if ( 0x04 & nClear )
+ {
+ if ( bLineSpace )
+ {
+ aOldSet.ClearItem( RES_PARATR_LINESPACING );
+ aNewSet.ClearItem( RES_PARATR_LINESPACING );
+ }
+ if ( bRegister )
+ {
+ aOldSet.ClearItem( RES_PARATR_REGISTER );
+ aNewSet.ClearItem( RES_PARATR_REGISTER );
+ }
+ }
+ if ( 0x08 & nClear )
+ {
+ aOldSet.ClearItem( RES_PARATR_SPLIT );
+ aNewSet.ClearItem( RES_PARATR_SPLIT );
+ }
+ if (aOldSet.Count() || aNewSet.Count())
+ {
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(&aOldSet, &aNewSet));
+ }
+ }
+ else
+ SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew));
+ }
+
+#if !ENABLE_WASM_STRIP_ACCESSIBILITY
+ if (isA11yRelevantAttribute(nWhich))
+ {
+ SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr;
+ if ( pViewSh )
+ {
+ pViewSh->InvalidateAccessibleParaAttrs( *this );
+ }
+ }
+#endif
+ }
+ break;
+ case RES_PARATR_SPLIT:
+ if ( GetPrev() )
+ CheckKeep();
+ Prepare();
+ bSetFieldsDirty = true;
+ break;
+ case RES_FRAMEDIR :
+ assert(false); // should have been forwarded to SwContentFrame
+ SetDerivedR2L( false );
+ CheckDirChange();
+ break;
+ default:
+ {
+ Prepare();
+ InvalidatePrt_();
+ if ( !nWhich )
+ {
+ // is called by e. g. HiddenPara with 0
+ SwFrame *pNxt = FindNext();
+ if ( nullptr != pNxt )
+ pNxt->InvalidatePrt();
+ }
+ }
+ } // switch
+
+ if( bSetFieldsDirty )
+ GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, SwNodeOffset(1) );
+
+ if ( bRecalcFootnoteFlag )
+ CalcFootnoteFlag();
+}
+
+void SwTextFrame::PrepWidows( const sal_uInt16 nNeed, bool bNotify )
+{
+ OSL_ENSURE(GetFollow() && nNeed, "+SwTextFrame::Prepare: lost all friends");
+
+ SwParaPortion *pPara = GetPara();
+ if ( !pPara )
+ return;
+ pPara->SetPrepWidows();
+
+ sal_uInt16 nHave = nNeed;
+
+ // We yield a few lines and shrink in CalcPreps()
+ SwSwapIfNotSwapped swap( this );
+
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+ aLine.Bottom();
+ TextFrameIndex nTmpLen = aLine.GetCurr()->GetLen();
+ while( nHave && aLine.PrevLine() )
+ {
+ if( nTmpLen )
+ --nHave;
+ nTmpLen = aLine.GetCurr()->GetLen();
+ }
+
+ // If it's certain that we can yield lines, the Master needs
+ // to check the widow rule
+ if( !nHave )
+ {
+ bool bSplit = true;
+ if( !IsFollow() ) // only a master decides about orphans
+ {
+ const WidowsAndOrphans aWidOrp( this );
+ bSplit = ( aLine.GetLineNr() >= aWidOrp.GetOrphansLines() &&
+ aLine.GetLineNr() >= aLine.GetDropLines() );
+ }
+
+ if( bSplit )
+ {
+ GetFollow()->SetOffset( aLine.GetEnd() );
+ aLine.TruncLines( true );
+ if( pPara->IsFollowField() )
+ GetFollow()->SetFieldFollow( true );
+ }
+ }
+ if ( bNotify )
+ {
+ InvalidateSize_();
+ InvalidatePage();
+ }
+}
+
+static bool lcl_ErgoVadis(SwTextFrame* pFrame, TextFrameIndex & rPos, const PrepareHint ePrep)
+{
+ const SwFootnoteInfo &rFootnoteInfo = pFrame->GetDoc().GetFootnoteInfo();
+ if( ePrep == PrepareHint::ErgoSum )
+ {
+ if( rFootnoteInfo.m_aErgoSum.isEmpty() )
+ return false;
+ rPos = pFrame->GetOffset();
+ }
+ else
+ {
+ if( rFootnoteInfo.m_aQuoVadis.isEmpty() )
+ return false;
+ if( pFrame->HasFollow() )
+ rPos = pFrame->GetFollow()->GetOffset();
+ else
+ rPos = TextFrameIndex(pFrame->GetText().getLength());
+ if( rPos )
+ --rPos; // our last character
+ }
+ return true;
+}
+
+// Silence over-eager warning emitted at least by GCC 5.3.1
+#if defined __GNUC__ && !defined __clang__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstrict-overflow"
+#endif
+bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid,
+ bool bNotify )
+{
+ bool bParaPossiblyInvalid = false;
+
+ SwFrameSwapper aSwapper( this, false );
+
+ if ( IsEmpty() )
+ {
+ switch ( ePrep )
+ {
+ case PrepareHint::BossChanged:
+ SetInvalidVert( true ); // Test
+ [[fallthrough]];
+ case PrepareHint::WidowsOrphans:
+ case PrepareHint::Widows:
+ case PrepareHint::FootnoteInvalidationGone : return bParaPossiblyInvalid;
+
+ case PrepareHint::FramePositionChanged :
+ {
+ // We also need an InvalidateSize for Areas (with and without columns),
+ // so that we format and bUndersized is set (if needed)
+ if( IsInFly() || IsInSct() )
+ {
+ SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
+ GetUpper()->getFramePrintArea().Bottom();
+ if( nTmpBottom < getFrameArea().Bottom() )
+ break;
+ }
+ // Are there any free-flying frames on this page?
+ SwTextFly aTextFly( this );
+ if( aTextFly.IsOn() )
+ {
+ // Does any free-flying frame overlap?
+ if ( aTextFly.Relax() || IsUndersized() )
+ break;
+ }
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ break;
+
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
+ break;
+
+ // i#28701 - consider anchored objects
+ if ( GetDrawObjs() )
+ break;
+
+ return bParaPossiblyInvalid;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Split fly anchors are technically empty (have no SwParaPortion), but otherwise behave like
+ // other split text frames, which are non-empty.
+ bool bSplitFlyAnchor = GetOffset() == TextFrameIndex(0) && HasFollow()
+ && GetFollow()->GetOffset() == TextFrameIndex(0);
+
+ if( !HasPara() && !bSplitFlyAnchor && PrepareHint::MustFit != ePrep )
+ {
+ SetInvalidVert( true ); // Test
+ OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect pair" );
+ if ( bNotify )
+ InvalidateSize();
+ else
+ InvalidateSize_();
+ return bParaPossiblyInvalid;
+ }
+
+ // Get object from cache while locking
+ SwTextLineAccess aAccess( this );
+ SwParaPortion *pPara = aAccess.GetPara();
+
+ switch( ePrep )
+ {
+ case PrepareHint::FootnoteMove :
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Height(0);
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Height(0);
+ }
+
+ InvalidatePrt_();
+ InvalidateSize_();
+ [[fallthrough]];
+ case PrepareHint::AdjustSizeWithoutFormatting :
+ pPara->SetPrepAdjust();
+ if( IsFootnoteNumFrame() != pPara->IsFootnoteNum() ||
+ IsUndersized() )
+ {
+ InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
+ if( GetOffset() && !IsFollow() )
+ SetOffset_(TextFrameIndex(0));
+ }
+ break;
+ case PrepareHint::MustFit :
+ pPara->SetPrepMustFit(true);
+ [[fallthrough]];
+ case PrepareHint::WidowsOrphans :
+ pPara->SetPrepAdjust();
+ break;
+ case PrepareHint::Widows :
+ // MustFit is stronger than anything else
+ if( pPara->IsPrepMustFit() )
+ return bParaPossiblyInvalid;
+ // see comment in WidowsAndOrphans::FindOrphans and CalcPreps()
+ PrepWidows( *static_cast<const sal_uInt16 *>(pVoid), bNotify );
+ break;
+
+ case PrepareHint::FootnoteInvalidation :
+ {
+ SwTextFootnote const *pFootnote = static_cast<SwTextFootnote const *>(pVoid);
+ if( IsInFootnote() )
+ {
+ // Am I the first TextFrame of a footnote?
+ if( !GetPrev() )
+ // So we're a TextFrame of the footnote, which has
+ // to display the footnote number or the ErgoSum text
+ InvalidateRange(SwCharRange(TextFrameIndex(0), TextFrameIndex(1)), 1);
+
+ if( !GetNext() )
+ {
+ // We're the last Footnote; we need to update the
+ // QuoVadis texts now
+ const SwFootnoteInfo &rFootnoteInfo = GetDoc().GetFootnoteInfo();
+ if( !pPara->UpdateQuoVadis( rFootnoteInfo.m_aQuoVadis ) )
+ {
+ TextFrameIndex nPos = pPara->GetParLen();
+ if( nPos )
+ --nPos;
+ InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), 1);
+ }
+ }
+ }
+ else
+ {
+ // We are the TextFrame _with_ the footnote
+ TextFrameIndex const nPos = MapModelToView(
+ &pFootnote->GetTextNode(), pFootnote->GetStart());
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)), 1);
+ }
+ break;
+ }
+ case PrepareHint::BossChanged :
+ {
+ // Test
+ {
+ SetInvalidVert( false );
+ bool bOld = IsVertical();
+ SetInvalidVert( true );
+ if( bOld != IsVertical() )
+ InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(COMPLETE_STRING)));
+ }
+
+ if( HasFollow() )
+ {
+ TextFrameIndex nNxtOfst = GetFollow()->GetOffset();
+ if( nNxtOfst )
+ --nNxtOfst;
+ InvalidateRange(SwCharRange( nNxtOfst, TextFrameIndex(1)), 1);
+ }
+ if( IsInFootnote() )
+ {
+ TextFrameIndex nPos;
+ if( lcl_ErgoVadis( this, nPos, PrepareHint::QuoVadis ) )
+ InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
+ if( lcl_ErgoVadis( this, nPos, PrepareHint::ErgoSum ) )
+ InvalidateRange( SwCharRange( nPos, TextFrameIndex(1)) );
+ }
+ // If we have a page number field, we must invalidate those spots
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*this);
+ TextFrameIndex const nEnd = GetFollow()
+ ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ TextFrameIndex const nStart(MapModelToView(pNode, pHt->GetStart()));
+ if (nStart >= GetOffset())
+ {
+ if (nStart >= nEnd)
+ break;
+
+ // If we're flowing back and own a Footnote, the Footnote also flows
+ // with us. So that it doesn't obstruct us, we send ourselves
+ // an ADJUST_FRM.
+ // pVoid != 0 means MoveBwd()
+ const sal_uInt16 nWhich = pHt->Which();
+ if (RES_TXTATR_FIELD == nWhich ||
+ (HasFootnote() && pVoid && RES_TXTATR_FTN == nWhich))
+ InvalidateRange(SwCharRange(nStart, TextFrameIndex(1)), 1);
+ }
+ }
+ // A new boss, a new chance for growing
+ if( IsUndersized() )
+ {
+ InvalidateSize_();
+ InvalidateRange(SwCharRange(GetOffset(), TextFrameIndex(1)), 1);
+ }
+ break;
+ }
+
+ case PrepareHint::FramePositionChanged :
+ {
+ if ( isFramePrintAreaValid() )
+ {
+ SwTextGridItem const*const pGrid(GetGridItem(FindPageFrame()));
+ if (pGrid && GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue())
+ InvalidatePrt();
+ }
+
+ // If we don't overlap with anybody:
+ // did any free-flying frame overlapped _before_ the position change?
+ bool bFormat = pPara->HasFly();
+ if( !bFormat )
+ {
+ if( IsInFly() )
+ {
+ SwTwips nTmpBottom = GetUpper()->getFrameArea().Top() +
+ GetUpper()->getFramePrintArea().Bottom();
+ if( nTmpBottom < getFrameArea().Bottom() )
+ bFormat = true;
+ }
+ if( !bFormat )
+ {
+ if ( GetDrawObjs() )
+ {
+ const size_t nCnt = GetDrawObjs()->size();
+ for ( size_t i = 0; i < nCnt; ++i )
+ {
+ SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i];
+ // i#28701 - consider all
+ // to-character anchored objects
+ if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId()
+ == RndStdIds::FLY_AT_CHAR )
+ {
+ bFormat = true;
+ break;
+ }
+ }
+ }
+ if( !bFormat )
+ {
+ // Are there any free-flying frames on this page?
+ SwTextFly aTextFly( this );
+ if( aTextFly.IsOn() )
+ {
+ // Does any free-flying frame overlap?
+ const bool bRelaxed = aTextFly.Relax();
+ bFormat = bRelaxed || IsUndersized();
+ if (bRelaxed)
+ {
+ // It's possible that pPara was deleted above; retrieve it again
+ pPara = aAccess.GetPara();
+ }
+ }
+ }
+ }
+ }
+
+ if( bFormat )
+ {
+ if( !IsLocked() )
+ {
+ if( pPara->GetRepaint().HasArea() )
+ SetCompletePaint();
+ Init();
+ pPara = nullptr;
+ InvalidateSize_();
+ }
+ }
+ else
+ {
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ bParaPossiblyInvalid = Prepare( PrepareHint::Register, nullptr, bNotify );
+ // The Frames need to be readjusted, which caused by changes
+ // in position
+ else if( HasFootnote() )
+ {
+ bParaPossiblyInvalid = Prepare( PrepareHint::AdjustSizeWithoutFormatting, nullptr, bNotify );
+ InvalidateSize_();
+ }
+ else
+ return bParaPossiblyInvalid; // So that there's no SetPrep()
+
+ if (bParaPossiblyInvalid)
+ {
+ // It's possible that pPara was deleted above; retrieve it again
+ pPara = aAccess.GetPara();
+ }
+
+ }
+ break;
+ }
+ case PrepareHint::Register:
+ if (GetTextNodeForParaProps()->GetSwAttrSet().GetRegister().GetValue())
+ {
+ pPara->SetPrepAdjust();
+ CalcLineSpace();
+
+ // It's possible that pPara was deleted above; retrieve it again
+ bParaPossiblyInvalid = true;
+ pPara = aAccess.GetPara();
+
+ InvalidateSize();
+ InvalidatePrt_();
+ SwFrame* pNxt = GetIndNext();
+ if ( nullptr != pNxt )
+ {
+ pNxt->InvalidatePrt_();
+ if ( pNxt->IsLayoutFrame() )
+ pNxt->InvalidatePage();
+ }
+ SetCompletePaint();
+ }
+ break;
+ case PrepareHint::FootnoteInvalidationGone :
+ {
+ // If a Follow is calling us, because a footnote is being deleted, our last
+ // line has to be formatted, so that the first line of the Follow can flow up.
+ // Which had flowed to the next page to be together with the footnote (this is
+ // especially true for areas with columns)
+ OSL_ENSURE( GetFollow(), "PrepareHint::FootnoteInvalidationGone may only be called by Follow" );
+ TextFrameIndex nPos = GetFollow()->GetOffset();
+ if( IsFollow() && GetOffset() == nPos ) // If we don't have a mass of text, we call our
+ FindMaster()->Prepare( PrepareHint::FootnoteInvalidationGone ); // Master's Prepare
+ if( nPos )
+ --nPos; // The char preceding our Follow
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
+ return bParaPossiblyInvalid;
+ }
+ case PrepareHint::ErgoSum:
+ case PrepareHint::QuoVadis:
+ {
+ TextFrameIndex nPos;
+ if( lcl_ErgoVadis( this, nPos, ePrep ) )
+ InvalidateRange(SwCharRange(nPos, TextFrameIndex(1)));
+ }
+ break;
+ case PrepareHint::FlyFrameAttributesChanged:
+ {
+ if( pVoid )
+ {
+ TextFrameIndex const nWhere = CalcFlyPos( static_cast<SwFrameFormat const *>(pVoid) );
+ OSL_ENSURE( TextFrameIndex(COMPLETE_STRING) != nWhere, "Prepare: Why me?" );
+ InvalidateRange(SwCharRange(nWhere, TextFrameIndex(1)));
+ return bParaPossiblyInvalid;
+ }
+ [[fallthrough]]; // else: continue with default case block
+ }
+ case PrepareHint::Clear:
+ default:
+ {
+ if( IsLocked() )
+ {
+ if( PrepareHint::FlyFrameArrive == ePrep || PrepareHint::FlyFrameLeave == ePrep )
+ {
+ TextFrameIndex const nLen = (GetFollow()
+ ? GetFollow()->GetOffset()
+ : TextFrameIndex(COMPLETE_STRING))
+ - GetOffset();
+ InvalidateRange( SwCharRange( GetOffset(), nLen ) );
+ }
+ }
+ else
+ {
+ if( pPara->GetRepaint().HasArea() )
+ SetCompletePaint();
+ Init();
+ pPara = nullptr;
+ if( GetOffset() && !IsFollow() )
+ SetOffset_( TextFrameIndex(0) );
+ if ( bNotify )
+ InvalidateSize();
+ else
+ InvalidateSize_();
+ }
+ return bParaPossiblyInvalid; // no SetPrep() happened
+ }
+ }
+ if( pPara )
+ {
+ pPara->SetPrep();
+ }
+
+ return bParaPossiblyInvalid;
+}
+#if defined __GNUC__ && !defined __clang__
+# pragma GCC diagnostic pop
+#endif
+
+/**
+ * Small Helper class:
+ * Prepares a test format.
+ * The frame is changed in size and position, its SwParaPortion is moved aside
+ * and a new one is created.
+ * To achieve this, run formatting with bTestFormat flag set.
+ * In the destructor the TextFrame is reset to its original state.
+ */
+class SwTestFormat
+{
+ SwTextFrame *pFrame;
+ SwParaPortion *pOldPara;
+ SwRect aOldFrame, aOldPrt;
+public:
+ SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPrv, SwTwips nMaxHeight );
+ ~SwTestFormat();
+};
+
+SwTestFormat::SwTestFormat( SwTextFrame* pTextFrame, const SwFrame* pPre, SwTwips nMaxHeight )
+ : pFrame( pTextFrame )
+{
+ aOldFrame = pFrame->getFrameArea();
+ aOldPrt = pFrame->getFramePrintArea();
+
+ SwRectFnSet aRectFnSet(pFrame);
+ SwTwips nLower = aRectFnSet.GetBottomMargin(*pFrame);
+
+ {
+ // indeed, here the GetUpper()->getFramePrintArea() gets copied and manipulated
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
+ aFrm.setSwRect(pFrame->GetUpper()->getFramePrintArea());
+ aFrm += pFrame->GetUpper()->getFrameArea().Pos();
+ aRectFnSet.SetHeight( aFrm, nMaxHeight );
+
+ if( pFrame->GetPrev() )
+ {
+ aRectFnSet.SetPosY(
+ aFrm,
+ aRectFnSet.GetBottom(pFrame->GetPrev()->getFrameArea()) - ( aRectFnSet.IsVert() ? nMaxHeight + 1 : 0 ) );
+ }
+ }
+
+ SwBorderAttrAccess aAccess( SwFrame::GetCache(), pFrame );
+ const SwBorderAttrs &rAttrs = *aAccess.Get();
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetPosX(aPrt, rAttrs.CalcLeft( pFrame ) );
+ }
+
+ if( pPre )
+ {
+ SwTwips nUpper = pFrame->CalcUpperSpace( &rAttrs, pPre );
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetPosY(aPrt, nUpper );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aRectFnSet.SetHeight( aPrt, std::max( tools::Long(0) , aRectFnSet.GetHeight(pFrame->getFrameArea()) - aRectFnSet.GetTop(aPrt) - nLower ) );
+ aRectFnSet.SetWidth( aPrt, aRectFnSet.GetWidth(pFrame->getFrameArea()) - ( rAttrs.CalcLeft( pFrame ) + rAttrs.CalcRight( pFrame ) ) );
+ }
+
+ pOldPara = pFrame->HasPara() ? pFrame->GetPara() : nullptr;
+ pFrame->SetPara( new SwParaPortion(), false );
+ OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped before Format_" );
+
+ if ( pFrame->IsVertical() )
+ pFrame->SwapWidthAndHeight();
+
+ SwTextFormatInfo aInf( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, false, true, true );
+ SwTextFormatter aLine( pFrame, &aInf );
+
+ pFrame->Format_( aLine, aInf );
+
+ if ( pFrame->IsVertical() )
+ pFrame->SwapWidthAndHeight();
+
+ OSL_ENSURE( ! pFrame->IsSwapped(), "A frame is swapped after Format_" );
+}
+
+SwTestFormat::~SwTestFormat()
+{
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFrame);
+ aFrm.setSwRect(aOldFrame);
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*pFrame);
+ aPrt.setSwRect(aOldPrt);
+ }
+
+ pFrame->SetPara( pOldPara );
+}
+
+bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bSplit )
+{
+ PROTOCOL_ENTER( this, PROT::TestFormat, DbgAction::NONE, nullptr )
+
+ if( IsLocked() && GetUpper()->getFramePrintArea().Width() <= 0 )
+ return false;
+
+ SwTestFormat aSave( this, pPrv, rMaxHeight );
+
+ return SwTextFrame::WouldFit(rMaxHeight, bSplit, true, false);
+}
+
+/**
+ * We should not and don't need to reformat.
+ * We assume that we already formatted and that the formatting
+ * data is still current.
+ *
+ * We also assume that the frame width of the Master and Follow
+ * are the same. That's why we're not calling FindBreak() for
+ * FindOrphans().
+ * The required height is coming from nMaxHeight.
+ *
+ * @returns true if I can split
+ */
+bool SwTextFrame::WouldFit(SwTwips &rMaxHeight, bool &bSplit, bool bTst, bool bMoveBwd)
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::WouldFit with swapped frame" );
+ SwRectFnSet aRectFnSet(this);
+
+ if( IsLocked() )
+ return false;
+
+ // it can happen that the IdleCollector removed the cached information
+ if( !IsEmpty() )
+ GetFormatted();
+
+ // i#27801 - correction: 'short cut' for empty paragraph
+ // can *not* be applied, if test format is in progress. The test format doesn't
+ // adjust the frame and the printing area - see method <SwTextFrame::Format_(..)>,
+ // which is called in <SwTextFrame::TestFormat(..)>
+ if ( IsEmpty() && !bTst )
+ {
+ bSplit = false;
+ SwTwips nHeight = aRectFnSet.IsVert() ? getFramePrintArea().SSize().Width() : getFramePrintArea().SSize().Height();
+ if( rMaxHeight < nHeight )
+ return false;
+ else
+ {
+ rMaxHeight -= nHeight;
+ return true;
+ }
+ }
+
+ // GetPara can still be 0 in edge cases
+ // We return true in order to be reformatted on the new Page
+ OSL_ENSURE( HasPara() || IsHiddenNow(), "WouldFit: GetFormatted() and then !HasPara()" );
+ if( !HasPara() || ( !aRectFnSet.GetHeight(getFrameArea()) && IsHiddenNow() ) )
+ return true;
+
+ // Because the Orphan flag only exists for a short moment, we also check
+ // whether the Framesize is set to very huge by CalcPreps, in order to
+ // force a MoveFwd
+ if (IsWidow() || (aRectFnSet.IsVert()
+ ? (0 == getFrameArea().Left())
+ : (sw::WIDOW_MAGIC - 20000 < getFrameArea().Bottom())))
+ {
+ SetWidow(false);
+ if ( GetFollow() )
+ {
+ // If we've ended up here due to a Widow request by our Follow, we check
+ // whether there's a Follow with a real height at all.
+ // Else (e.g. for newly created SctFrames) we ignore the IsWidow() and
+ // still check if we can find enough room
+ if (((!aRectFnSet.IsVert() && getFrameArea().Bottom() <= sw::WIDOW_MAGIC - 20000) ||
+ ( aRectFnSet.IsVert() && 0 < getFrameArea().Left() ) ) &&
+ ( GetFollow()->IsVertical() ?
+ !GetFollow()->getFrameArea().Width() :
+ !GetFollow()->getFrameArea().Height() ) )
+ {
+ SwTextFrame* pFoll = GetFollow()->GetFollow();
+ while( pFoll &&
+ ( pFoll->IsVertical() ?
+ !pFoll->getFrameArea().Width() :
+ !pFoll->getFrameArea().Height() ) )
+ pFoll = pFoll->GetFollow();
+ if( pFoll )
+ return false;
+ }
+ else
+ return false;
+ }
+ }
+
+ SwSwapIfNotSwapped swap( this );
+
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+
+ WidowsAndOrphans aFrameBreak( this, rMaxHeight, bSplit );
+
+ bool bRet = true;
+
+ aLine.Bottom();
+ // is breaking necessary?
+ bSplit = !aFrameBreak.IsInside( aLine );
+ if ( bSplit )
+ bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit(aLine, rMaxHeight, bTst, bMoveBwd);
+ else
+ {
+ // we need the total height including the current line
+ aLine.Top();
+ do
+ {
+ rMaxHeight -= aLine.GetLineHeight();
+ } while ( aLine.Next() );
+ }
+
+ return bRet;
+}
+
+SwTwips SwTextFrame::GetParHeight() const
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::GetParHeight with swapped frame" );
+
+ if( !HasPara() )
+ { // For non-empty paragraphs this is a special case
+ // For UnderSized we can simply just ask 1 Twip more
+ sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(getFramePrintArea().SSize().Height());
+ if( IsUndersized() )
+ {
+ if( IsEmpty() || GetText().isEmpty() )
+ nRet = o3tl::narrowing<sal_uInt16>(EmptyHeight());
+ else
+ ++nRet;
+ }
+ return nRet;
+ }
+
+ // TODO: Refactor and improve code
+ const SwLineLayout* pLineLayout = GetPara();
+ SwTwips nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0;
+
+ // Is this paragraph scrolled? Our height until now is at least
+ // one line height too low then
+ if( GetOffset() && !IsFollow() )
+ nHeight *= 2;
+
+ while ( pLineLayout && pLineLayout->GetNext() )
+ {
+ pLineLayout = pLineLayout->GetNext();
+ nHeight = nHeight + pLineLayout->GetRealHeight();
+ }
+
+ return nHeight;
+}
+
+/**
+ * @returns this _always_ in the formatted state!
+ */
+SwTextFrame* SwTextFrame::GetFormatted( bool bForceQuickFormat )
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ SwSwapIfSwapped swap( this );
+
+ // In case the SwLineLayout was cleared out of the s_pTextCache, recreate it
+ // Not for empty paragraphs
+ if( !HasPara() && !(isFrameAreaDefinitionValid() && IsEmpty()) )
+ {
+ // Calc() must be called, because frame position can be wrong
+ const bool bFormat = isFrameAreaSizeValid();
+ Calc(pRenderContext); // calls Format() if invalid
+
+ // If the flags were valid (hence bFormat=true), Calc did nothing,
+ // so Format() must be called manually in order to recreate
+ // the SwLineLayout that has been deleted from the
+ // SwTextFrame::s_pTextCache (hence !HasPara() above).
+ // Optimization with FormatQuick()
+ if( bFormat && !FormatQuick( bForceQuickFormat ) )
+ Format(getRootFrame()->GetCurrShell()->GetOut());
+ }
+
+ return this;
+}
+
+SwTwips SwTextFrame::CalcFitToContent()
+{
+ // i#31490
+ // If we are currently locked, we better return with a
+ // fairly reasonable value:
+ if ( IsLocked() )
+ return getFramePrintArea().Width();
+
+ SwParaPortion* pOldPara = GetPara();
+ SwParaPortion *pDummy = new SwParaPortion();
+ SetPara( pDummy, false );
+ const SwPageFrame* pPage = FindPageFrame();
+
+ const Point aOldFramePos = getFrameArea().Pos();
+ const SwTwips nOldFrameWidth = getFrameArea().Width();
+ const SwTwips nOldPrtWidth = getFramePrintArea().Width();
+ const SwTwips nPageWidth = GetUpper()->IsVertical() ?
+ pPage->getFramePrintArea().Height() :
+ pPage->getFramePrintArea().Width();
+
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( nPageWidth );
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( nPageWidth );
+ }
+
+ // i#25422 objects anchored as character in RTL
+ if ( IsRightToLeft() )
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Pos().AdjustX(nOldFrameWidth - nPageWidth );
+ }
+
+ TextFrameLockGuard aLock( this );
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
+ aInf.SetIgnoreFly( true );
+ SwTextFormatter aLine( this, &aInf );
+ SwHookOut aHook( aInf );
+
+ // i#54031 - assure minimum of MINLAY twips.
+ const SwTwips nMax = std::max( SwTwips(MINLAY), aLine.CalcFitToContent_() + 1 );
+
+ {
+ SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+ aFrm.Width( nOldFrameWidth );
+
+ // i#25422 objects anchored as character in RTL
+ if ( IsRightToLeft() )
+ {
+ aFrm.Pos() = aOldFramePos;
+ }
+ }
+
+ {
+ SwFrameAreaDefinition::FramePrintAreaWriteAccess aPrt(*this);
+ aPrt.Width( nOldPrtWidth );
+ }
+
+ SetPara( pOldPara );
+
+ return nMax;
+}
+
+/**
+ * Simulate format for a list item paragraph, whose list level attributes
+ * are in LABEL_ALIGNMENT mode, in order to determine additional first
+ * line offset for the real text formatting due to the value of label
+ * adjustment attribute of the list level.
+ */
+void SwTextFrame::CalcAdditionalFirstLineOffset()
+{
+ if ( IsLocked() )
+ return;
+
+ // reset additional first line offset
+ mnAdditionalFirstLineOffset = 0;
+
+ const SwTextNode* pTextNode( GetTextNodeForParaProps() );
+ // sw_redlinehide: check that pParaPropsNode is the correct one
+ assert(pTextNode->IsNumbered(getRootFrame()) == pTextNode->IsNumbered(nullptr));
+ if (!(pTextNode->IsNumbered(getRootFrame()) &&
+ pTextNode->IsCountedInList() && pTextNode->GetNumRule()))
+ return;
+
+ int nListLevel = pTextNode->GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ const SwNumFormat& rNumFormat =
+ pTextNode->GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
+ if ( rNumFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_ALIGNMENT )
+ return;
+
+ // keep current paragraph portion and apply dummy paragraph portion
+ SwParaPortion* pOldPara = GetPara();
+ SwParaPortion *pDummy = new SwParaPortion();
+ SetPara( pDummy, false );
+
+ // lock paragraph
+ TextFrameLockGuard aLock( this );
+
+ // simulate text formatting
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, false, true, true );
+ aInf.SetIgnoreFly( true );
+ SwTextFormatter aLine( this, &aInf );
+ SwHookOut aHook( aInf );
+ aLine.CalcFitToContent_();
+
+ // determine additional first line offset
+ const SwLinePortion* pFirstPortion = aLine.GetCurr()->GetFirstPortion();
+ if ( pFirstPortion->InNumberGrp() && !pFirstPortion->IsFootnoteNumPortion() )
+ {
+ SwTwips nNumberPortionWidth( pFirstPortion->Width() );
+
+ const SwLinePortion* pPortion = pFirstPortion->GetNextPortion();
+ while ( pPortion &&
+ pPortion->InNumberGrp() && !pPortion->IsFootnoteNumPortion())
+ {
+ nNumberPortionWidth += pPortion->Width();
+ pPortion = pPortion->GetNextPortion();
+ }
+
+ if ( ( IsRightToLeft() &&
+ rNumFormat.GetNumAdjust() == SvxAdjust::Left ) ||
+ ( !IsRightToLeft() &&
+ rNumFormat.GetNumAdjust() == SvxAdjust::Right ) )
+ {
+ mnAdditionalFirstLineOffset = -nNumberPortionWidth;
+ }
+ else if ( rNumFormat.GetNumAdjust() == SvxAdjust::Center )
+ {
+ mnAdditionalFirstLineOffset = -(nNumberPortionWidth/2);
+ }
+ }
+
+ // restore paragraph portion
+ SetPara( pOldPara );
+}
+
+/**
+ * Determine the height of the last line for the calculation of
+ * the proportional line spacing
+ *
+ * Height of last line will be stored in new member
+ * mnHeightOfLastLine and can be accessed via method
+ * GetHeightOfLastLine()
+ *
+ * @param _bUseFont force the usage of the former algorithm to
+ * determine the height of the last line, which
+ * uses the font
+ */
+void SwTextFrame::CalcHeightOfLastLine( const bool _bUseFont )
+{
+ // i#71281
+ // Invalidate printing area, if height of last line changes
+ const SwTwips nOldHeightOfLastLine( mnHeightOfLastLine );
+
+ // determine output device
+ SwViewShell* pVsh = getRootFrame()->GetCurrShell();
+ OSL_ENSURE( pVsh, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no SwViewShell" );
+
+ // i#78921
+ // There could be no <SwViewShell> instance in the case of loading a binary
+ // StarOffice file format containing an embedded Writer document.
+ if ( !pVsh )
+ {
+ return;
+ }
+ OutputDevice* pOut = pVsh->GetOut();
+ const IDocumentSettingAccess *const pIDSA = &GetDoc().getIDocumentSettingAccess();
+ if ( !pVsh->GetViewOptions()->getBrowseMode() ||
+ pVsh->GetViewOptions()->IsPrtFormat() )
+ {
+ pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true );
+ }
+ OSL_ENSURE( pOut, "<SwTextFrame::_GetHeightOfLastLineForPropLineSpacing()> - no OutputDevice" );
+
+ if ( !pOut )
+ {
+ return;
+ }
+
+ // determine height of last line
+ if ( _bUseFont || pIDSA->get(DocumentSettingId::OLD_LINE_SPACING ) )
+ {
+ // former determination of last line height for proportional line
+ // spacing - take height of font set at the paragraph
+ // FIXME actually... must the font match across all nodes?
+ SwFont aFont( &GetTextNodeForParaProps()->GetSwAttrSet(), pIDSA );
+
+ // we must ensure that the font is restored correctly on the OutputDevice
+ // otherwise Last!=Owner could occur
+ if ( pLastFont )
+ {
+ SwFntObj *pOldFont = pLastFont;
+ pLastFont = nullptr;
+ aFont.SetFntChg( true );
+ aFont.ChgPhysFnt( pVsh, *pOut );
+ mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
+ assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
+ pLastFont->Unlock();
+ pLastFont = pOldFont;
+ pLastFont->SetDevFont( pVsh, *pOut );
+ }
+ else
+ {
+ vcl::Font aOldFont = pOut->GetFont();
+ aFont.SetFntChg( true );
+ aFont.ChgPhysFnt( pVsh, *pOut );
+ mnHeightOfLastLine = aFont.GetHeight( pVsh, *pOut );
+ assert(pLastFont && "coverity[var_deref_model] - pLastFont should be set in SwSubFont::ChgFnt");
+ pLastFont->Unlock();
+ pLastFont = nullptr;
+ pOut->SetFont( aOldFont );
+ }
+ }
+ else
+ {
+ // new determination of last line height - take actually height of last line
+ // i#89000
+ // assure same results, if paragraph is undersized
+ if ( IsUndersized() )
+ {
+ mnHeightOfLastLine = 0;
+ }
+ else
+ {
+ bool bCalcHeightOfLastLine = true;
+ if ( ( !HasPara() && IsEmpty( ) ) || GetText().isEmpty() )
+ {
+ mnHeightOfLastLine = EmptyHeight();
+ bCalcHeightOfLastLine = false;
+ }
+
+ if ( bCalcHeightOfLastLine )
+ {
+ OSL_ENSURE( HasPara(),
+ "<SwTextFrame::CalcHeightOfLastLine()> - missing paragraph portions." );
+ const SwLineLayout* pLineLayout = GetPara();
+ while ( pLineLayout && pLineLayout->GetNext() )
+ {
+ // iteration to last line
+ pLineLayout = pLineLayout->GetNext();
+ }
+ if ( pLineLayout )
+ {
+ SwTwips nAscent, nDescent, nDummy1, nDummy2;
+ // i#47162 - suppress consideration of
+ // fly content portions and the line portion.
+ pLineLayout->MaxAscentDescent( nAscent, nDescent,
+ nDummy1, nDummy2,
+ nullptr, true );
+ // i#71281
+ // Suppress wrong invalidation of printing area, if method is
+ // called recursive.
+ // Thus, member <mnHeightOfLastLine> is only set directly, if
+ // no recursive call is needed.
+ const SwTwips nNewHeightOfLastLine = nAscent + nDescent;
+ // i#47162 - if last line only contains
+ // fly content portions, <mnHeightOfLastLine> is zero.
+ // In this case determine height of last line by the font
+ if ( nNewHeightOfLastLine == 0 )
+ {
+ CalcHeightOfLastLine( true );
+ }
+ else
+ {
+ mnHeightOfLastLine = nNewHeightOfLastLine;
+ }
+ }
+ }
+ }
+ }
+ // i#71281
+ // invalidate printing area, if height of last line changes
+ if ( mnHeightOfLastLine != nOldHeightOfLastLine )
+ {
+ InvalidatePrt();
+ }
+}
+
+/**
+ * Method returns the value of the inter line spacing for a text frame.
+ * Such a value exists for proportional line spacings ("1,5 Lines",
+ * "Double", "Proportional" and for leading line spacing ("Leading").
+ *
+ * @param _bNoPropLineSpacing (default = false) control whether the
+ * value of a proportional line spacing is
+ * returned or not
+ */
+tools::Long SwTextFrame::GetLineSpace( const bool _bNoPropLineSpace ) const
+{
+ tools::Long nRet = 0;
+
+ const SvxLineSpacingItem &rSpace = GetTextNodeForParaProps()->GetSwAttrSet().GetLineSpacing();
+
+ switch( rSpace.GetInterLineSpaceRule() )
+ {
+ case SvxInterLineSpaceRule::Prop:
+ {
+ if ( _bNoPropLineSpace )
+ {
+ break;
+ }
+
+ // i#11860 - adjust spacing implementation for object positioning
+ // - compatibility to MS Word
+ nRet = GetHeightOfLastLine();
+
+ tools::Long nTmp = nRet;
+ nTmp *= rSpace.GetPropLineSpace();
+ nTmp /= 100;
+ nTmp -= nRet;
+ if ( nTmp > 0 )
+ nRet = nTmp;
+ else
+ nRet = 0;
+ }
+ break;
+ case SvxInterLineSpaceRule::Fix:
+ {
+ if ( rSpace.GetInterLineSpace() > 0 )
+ nRet = rSpace.GetInterLineSpace();
+ }
+ break;
+ default:
+ break;
+ }
+ return nRet;
+}
+
+sal_uInt16 SwTextFrame::FirstLineHeight() const
+{
+ if ( !HasPara() )
+ {
+ if( IsEmpty() && isFrameAreaDefinitionValid() )
+ return IsVertical() ? o3tl::narrowing<sal_uInt16>(getFramePrintArea().Width()) : o3tl::narrowing<sal_uInt16>(getFramePrintArea().Height());
+ return USHRT_MAX;
+ }
+ const SwParaPortion *pPara = GetPara();
+ if ( !pPara )
+ return USHRT_MAX;
+
+ // tdf#146500 Lines with only fly overlap cannot be "moved", so the idea
+ // here is to continue until there's some text.
+ // FIXME ideally we want to count a fly to the line in which it is anchored
+ // - it may even be anchored in some other paragraph! SwFlyPortion doesn't
+ // have a pointer sadly so no way to find out.
+ sal_uInt16 nHeight(0);
+ for (SwLineLayout const* pLine = pPara; pLine; pLine = pLine->GetNext())
+ {
+ nHeight += pLine->Height();
+ if (::sw::FindNonFlyPortion(*pLine))
+ {
+ break;
+ }
+ }
+ return nHeight;
+}
+
+sal_Int32 SwTextFrame::GetLineCount(TextFrameIndex const nPos)
+{
+ sal_Int32 nRet = 0;
+ SwTextFrame *pFrame = this;
+ do
+ {
+ pFrame->GetFormatted();
+ if( !pFrame->HasPara() )
+ break;
+ SwTextSizeInfo aInf( pFrame );
+ SwTextMargin aLine( pFrame, &aInf );
+ if (TextFrameIndex(COMPLETE_STRING) == nPos)
+ aLine.Bottom();
+ else
+ aLine.CharToLine( nPos );
+ nRet = nRet + aLine.GetLineNr();
+ pFrame = pFrame->GetFollow();
+ } while ( pFrame && pFrame->GetOffset() <= nPos );
+ return nRet;
+}
+
+void SwTextFrame::ChgThisLines()
+{
+ // not necessary to format here (GetFormatted etc.), because we have to come from there!
+ sal_Int32 nNew = 0;
+ const SwLineNumberInfo &rInf = GetDoc().GetLineNumberInfo();
+ if ( !GetText().isEmpty() && HasPara() )
+ {
+ SwTextSizeInfo aInf( this );
+ SwTextMargin aLine( this, &aInf );
+ if ( rInf.IsCountBlankLines() )
+ {
+ aLine.Bottom();
+ nNew = aLine.GetLineNr();
+ }
+ else
+ {
+ do
+ {
+ if( aLine.GetCurr()->HasContent() )
+ ++nNew;
+ } while ( aLine.NextLine() );
+ }
+ }
+ else if ( rInf.IsCountBlankLines() )
+ nNew = 1;
+
+ if ( nNew == mnThisLines )
+ return;
+
+ if (!IsInTab() && GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber().IsCount())
+ {
+ mnAllLines -= mnThisLines;
+ mnThisLines = nNew;
+ mnAllLines += mnThisLines;
+ SwFrame *pNxt = GetNextContentFrame();
+ while( pNxt && pNxt->IsInTab() )
+ {
+ pNxt = pNxt->FindTabFrame();
+ if( nullptr != pNxt )
+ pNxt = pNxt->FindNextCnt();
+ }
+ if( pNxt )
+ pNxt->InvalidateLineNum();
+
+ // Extend repaint to the bottom.
+ if ( HasPara() )
+ {
+ SwRepaint& rRepaint = GetPara()->GetRepaint();
+ rRepaint.Bottom( std::max( rRepaint.Bottom(),
+ getFrameArea().Top()+getFramePrintArea().Bottom()));
+ }
+ }
+ else // Paragraphs which are not counted should not manipulate the AllLines.
+ mnThisLines = nNew;
+}
+
+void SwTextFrame::RecalcAllLines()
+{
+ ValidateLineNum();
+
+ if ( IsInTab() )
+ return;
+
+ const sal_Int32 nOld = GetAllLines();
+ const SwFormatLineNumber &rLineNum = GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber();
+ sal_Int32 nNewNum;
+ const bool bRestart = GetDoc().GetLineNumberInfo().IsRestartEachPage();
+
+ if ( !IsFollow() && rLineNum.GetStartValue() && rLineNum.IsCount() )
+ nNewNum = rLineNum.GetStartValue() - 1;
+ // If it is a follow or not has not be considered if it is a restart at each page; the
+ // restart should also take effect at follows.
+ else if ( bRestart && FindPageFrame()->FindFirstBodyContent() == this )
+ {
+ nNewNum = 0;
+ }
+ else
+ {
+ SwContentFrame *pPrv = GetPrevContentFrame();
+ while ( pPrv &&
+ (pPrv->IsInTab() || pPrv->IsInDocBody() != IsInDocBody()) )
+ pPrv = pPrv->GetPrevContentFrame();
+
+ // i#78254 Restart line numbering at page change
+ // First body content may be in table!
+ if ( bRestart && pPrv && pPrv->FindPageFrame() != FindPageFrame() )
+ pPrv = nullptr;
+
+ nNewNum = pPrv ? static_cast<SwTextFrame*>(pPrv)->GetAllLines() : 0;
+ }
+ if ( rLineNum.IsCount() )
+ nNewNum += GetThisLines();
+
+ if ( nOld == nNewNum )
+ return;
+
+ mnAllLines = nNewNum;
+ SwContentFrame *pNxt = GetNextContentFrame();
+ while ( pNxt &&
+ (pNxt->IsInTab() || pNxt->IsInDocBody() != IsInDocBody()) )
+ pNxt = pNxt->GetNextContentFrame();
+ if ( pNxt )
+ {
+ if ( pNxt->GetUpper() != GetUpper() )
+ pNxt->InvalidateLineNum();
+ else
+ pNxt->InvalidateLineNum_();
+ }
+}
+
+void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const
+{
+ const SwParaPortion* pPara = isFrameAreaDefinitionValid() ? GetPara() : nullptr;
+
+ if (pPara)
+ {
+ if ( IsFollow() )
+ rPH.Skip( GetOffset() );
+
+ const SwLineLayout* pLine = pPara;
+ while ( pLine )
+ {
+ const SwLinePortion* pPor = pLine->GetFirstPortion();
+ while ( pPor )
+ {
+ pPor->HandlePortion( rPH );
+ pPor = pPor->GetNextPortion();
+ }
+
+ rPH.LineBreak();
+ pLine = pLine->GetNext();
+ }
+ }
+
+ rPH.Finish();
+}
+
+const SwScriptInfo* SwTextFrame::GetScriptInfo() const
+{
+ const SwParaPortion* pPara = GetPara();
+ return pPara ? &pPara->GetScriptInfo() : nullptr;
+}
+
+/**
+ * Helper function for SwTextFrame::CalcBasePosForFly()
+ */
+static SwTwips lcl_CalcFlyBasePos( const SwTextFrame& rFrame, SwRect aFlyRect,
+ SwTextFly const & rTextFly )
+{
+ SwRectFnSet aRectFnSet(&rFrame);
+ SwTwips nRet = rFrame.IsRightToLeft() ?
+ aRectFnSet.GetRight(rFrame.getFrameArea()) :
+ aRectFnSet.GetLeft(rFrame.getFrameArea());
+
+ do
+ {
+ SwRect aRect = rTextFly.GetFrame( aFlyRect );
+ if ( 0 != aRectFnSet.GetWidth(aRect) )
+ {
+ if ( rFrame.IsRightToLeft() )
+ {
+ if ( aRectFnSet.GetRight(aRect) -
+ aRectFnSet.GetRight(aFlyRect) >= 0 )
+ {
+ aRectFnSet.SetRight(
+aFlyRect, aRectFnSet.GetLeft(aRect) );
+ nRet = aRectFnSet.GetLeft(aRect);
+ }
+ else
+ break;
+ }
+ else
+ {
+ if ( aRectFnSet.GetLeft(aFlyRect) -
+ aRectFnSet.GetLeft(aRect) >= 0 )
+ {
+ aRectFnSet.SetLeft(
+aFlyRect, aRectFnSet.GetRight(aRect) + 1 );
+ nRet = aRectFnSet.GetRight(aRect);
+ }
+ else
+ break;
+ }
+ }
+ else
+ break;
+ }
+ while ( aRectFnSet.GetWidth(aFlyRect) > 0 );
+
+ return nRet;
+}
+
+void SwTextFrame::CalcBaseOfstForFly()
+{
+ OSL_ENSURE( !IsVertical() || !IsSwapped(),
+ "SwTextFrame::CalcBasePosForFly with swapped frame!" );
+
+ if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_FLY_OFFSETS))
+ return;
+
+ SwRectFnSet aRectFnSet(this);
+
+ SwRect aFlyRect( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
+
+ // Get first 'real' line and adjust position and height of line rectangle.
+ // Correct behaviour if no 'real' line exists
+ // (empty paragraph with and without a dummy portion)
+ SwTwips nFlyAnchorVertOfstNoWrap = 0;
+ {
+ SwTwips nTop = aRectFnSet.GetTop(aFlyRect);
+ const SwLineLayout* pLay = GetPara();
+ SwTwips nLineHeight = 200;
+ while( pLay && pLay->IsDummy() && pLay->GetNext() )
+ {
+ nTop += pLay->Height();
+ nFlyAnchorVertOfstNoWrap += pLay->Height();
+ pLay = pLay->GetNext();
+ }
+ if ( pLay )
+ {
+ nLineHeight = pLay->Height();
+ }
+ aRectFnSet.SetTopAndHeight( aFlyRect, nTop, nLineHeight );
+ }
+
+ SwTextFly aTextFly( this );
+ aTextFly.SetIgnoreCurrentFrame( true );
+ aTextFly.SetIgnoreContour( true );
+ // ignore objects in page header|footer for
+ // text frames not in page header|footer
+ aTextFly.SetIgnoreObjsInHeaderFooter( true );
+ SwTwips nRet1 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
+ aTextFly.SetIgnoreCurrentFrame( false );
+ SwTwips nRet2 = lcl_CalcFlyBasePos( *this, aFlyRect, aTextFly );
+
+ // make values relative to frame start position
+ SwTwips nLeft = IsRightToLeft() ?
+ aRectFnSet.GetRight(getFrameArea()) :
+ aRectFnSet.GetLeft(getFrameArea());
+
+ mnFlyAnchorOfst = nRet1 - nLeft;
+ mnFlyAnchorOfstNoWrap = nRet2 - nLeft;
+
+ if (!GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS))
+ return;
+
+ if (mnFlyAnchorOfstNoWrap > 0)
+ mnFlyAnchorVertOfstNoWrap = nFlyAnchorVertOfstNoWrap;
+}
+
+SwTwips SwTextFrame::GetBaseVertOffsetForFly(bool bIgnoreFlysAnchoredAtThisFrame) const
+{
+ return bIgnoreFlysAnchoredAtThisFrame ? 0 : mnFlyAnchorVertOfstNoWrap;
+}
+
+/**
+ * Repaint all text frames of the given text node
+ */
+void SwTextFrame::repaintTextFrames( const SwTextNode& rNode )
+{
+ SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rNode);
+ for( const SwTextFrame *pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
+ {
+ SwRect aRec( pFrame->GetPaintArea() );
+ const SwRootFrame *pRootFrame = pFrame->getRootFrame();
+ SwViewShell *pCurShell = pRootFrame ? pRootFrame->GetCurrShell() : nullptr;
+ if( pCurShell )
+ pCurShell->InvalidateWindows( aRec );
+ }
+}
+
+void SwTextFrame::UpdateOutlineContentVisibilityButton(SwWrtShell* pWrtSh) const
+{
+ if (pWrtSh && pWrtSh->GetViewOptions()->IsShowOutlineContentVisibilityButton() &&
+ GetTextNodeFirst()->IsOutline())
+ {
+ SwEditWin& rEditWin = pWrtSh->GetView().GetEditWin();
+ SwFrameControlsManager& rMngr = rEditWin.GetFrameControlsManager();
+ rMngr.SetOutlineContentVisibilityButton(this);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtftn.cxx b/sw/source/core/text/txtftn.cxx
new file mode 100644
index 0000000000..c1fa749c93
--- /dev/null
+++ b/sw/source/core/text/txtftn.cxx
@@ -0,0 +1,1590 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <utility>
+#include <viewsh.hxx>
+#include <doc.hxx>
+#include <IDocumentLayoutAccess.hxx>
+#include <pagefrm.hxx>
+#include <rootfrm.hxx>
+#include <ndtxt.hxx>
+#include <SwPortionHandler.hxx>
+#include <txtftn.hxx>
+#include <flyfrm.hxx>
+#include <fmtftn.hxx>
+#include <ftninfo.hxx>
+#include <charfmt.hxx>
+#include <rowfrm.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/charrotateitem.hxx>
+#include <tabfrm.hxx>
+#include <sortedobjs.hxx>
+
+#include <swfont.hxx>
+#include "porftn.hxx"
+#include "porfly.hxx"
+#include "porlay.hxx"
+#include <txtfrm.hxx>
+#include "itrform2.hxx"
+#include <ftnfrm.hxx>
+#include <pagedesc.hxx>
+#include "redlnitr.hxx"
+#include <sectfrm.hxx>
+#include <layouter.hxx>
+#include <frmtool.hxx>
+#include <ndindex.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <swmodule.hxx>
+#include <unotextrange.hxx>
+#include <redline.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/crossedoutitem.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/awt/CharSet.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+
+using namespace ::com::sun::star;
+
+bool SwTextFrame::IsFootnoteNumFrame_() const
+{
+ if (IsInTab())
+ return false; // tdf#102073 first frame in cell doesn't have mpPrev set
+ const SwFootnoteFrame* pFootnote = FindFootnoteFrame()->GetMaster();
+ while( pFootnote && !pFootnote->ContainsContent() )
+ pFootnote = pFootnote->GetMaster();
+ return !pFootnote;
+}
+
+/**
+ * Looks for the TextFrame matching the SwTextFootnote within a master-follow chain
+ */
+SwTextFrame *SwTextFrame::FindFootnoteRef( const SwTextFootnote *pFootnote )
+{
+ SwTextFrame *pFrame = this;
+ const bool bFwd = MapModelToView(&pFootnote->GetTextNode(), pFootnote->GetStart()) >= GetOffset();
+ while( pFrame )
+ {
+ if( SwFootnoteBossFrame::FindFootnote( pFrame, pFootnote ) )
+ return pFrame;
+ pFrame = bFwd ? pFrame->GetFollow() :
+ pFrame->IsFollow() ? pFrame->FindMaster() : nullptr;
+ }
+ return pFrame;
+}
+
+void SwTextFrame::SetHasRotatedPortions(bool bHasRotatedPortions)
+{
+ mbHasRotatedPortions = bHasRotatedPortions;
+}
+
+#ifdef DBG_UTIL
+void SwTextFrame::CalcFootnoteFlag(TextFrameIndex nStop) // For testing the SplitFrame
+#else
+void SwTextFrame::CalcFootnoteFlag()
+#endif
+{
+ mbFootnote = false;
+
+#ifdef DBG_UTIL
+ const TextFrameIndex nEnd = nStop != TextFrameIndex(COMPLETE_STRING)
+ ? nStop
+ : GetFollow() ? GetFollow()->GetOffset() : TextFrameIndex(COMPLETE_STRING);
+#else
+ const TextFrameIndex nEnd = GetFollow()
+ ? GetFollow()->GetOffset()
+ : TextFrameIndex(COMPLETE_STRING);
+#endif
+
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIter iter(*this);
+ for (SwTextAttr const* pHt = iter.NextAttr(&pNode); pHt; pHt = iter.NextAttr(&pNode))
+ {
+ if ( pHt->Which() == RES_TXTATR_FTN )
+ {
+ TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
+ if ( nEnd < nIdx )
+ break;
+ if( GetOffset() <= nIdx )
+ {
+ mbFootnote = true;
+ break;
+ }
+ }
+ }
+}
+
+bool SwTextFrame::CalcPrepFootnoteAdjust()
+{
+ OSL_ENSURE( HasFootnote(), "Who´s calling me?" );
+ SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame( true );
+ const SwFootnoteFrame *pFootnote = pBoss->FindFirstFootnote( this );
+ if (pFootnote && FTNPOS_CHAPTER != GetDoc().GetFootnoteInfo().m_ePos &&
+ ( !pBoss->GetUpper()->IsSctFrame() ||
+ !static_cast<SwSectionFrame*>(pBoss->GetUpper())->IsFootnoteAtEnd() ) )
+ {
+ const SwFootnoteContFrame *pCont = pBoss->FindFootnoteCont();
+ bool bReArrange = true;
+
+ SwRectFnSet aRectFnSet(this);
+ if ( pCont && aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()),
+ aRectFnSet.GetBottom(getFrameArea()) ) > 0 )
+ {
+ pBoss->RearrangeFootnotes( aRectFnSet.GetBottom(getFrameArea()), false,
+ pFootnote->GetAttr() );
+ ValidateBodyFrame();
+ ValidateFrame();
+ pFootnote = pBoss->FindFirstFootnote( this );
+ }
+ else
+ bReArrange = false;
+ if( !pCont || !pFootnote || bReArrange != (pFootnote->FindFootnoteBossFrame() == pBoss) )
+ {
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this );
+ SwTextFormatter aLine( this, &aInf );
+ aLine.TruncLines();
+ SetPara( nullptr ); // May be deleted!
+ ResetPreps();
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Local helper function. Checks if nLower should be taken as the boundary
+ * for the footnote.
+ */
+static SwTwips lcl_GetFootnoteLower( const SwTextFrame* pFrame, SwTwips nLower )
+{
+ // nLower is an absolute value. It denotes the bottom of the line
+ // containing the footnote.
+ SwRectFnSet aRectFnSet(pFrame);
+
+ OSL_ENSURE( !pFrame->IsVertical() || !pFrame->IsSwapped(),
+ "lcl_GetFootnoteLower with swapped frame" );
+
+ SwTwips nAdd;
+ SwTwips nRet = nLower;
+
+ // Check if text is inside a table.
+ if ( pFrame->IsInTab() )
+ {
+ // If pFrame is inside a table, we have to check if
+ // a) The table is not allowed to split or
+ // b) The table row is not allowed to split
+
+ // Inside a table, there are no footnotes,
+ // see SwFrame::FindFootnoteBossFrame. So we don't have to check
+ // the case that pFrame is inside a (footnote collecting) section
+ // within the table.
+ const SwFrame* pRow = pFrame;
+ while( !pRow->IsRowFrame() || !pRow->GetUpper()->IsTabFrame() )
+ pRow = pRow->GetUpper();
+ const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pRow->GetUpper());
+
+ OSL_ENSURE( pTabFrame && pRow &&
+ pRow->GetUpper()->IsTabFrame(), "Upper of row should be tab" );
+
+ const bool bDontSplit = !pTabFrame->IsFollow() &&
+ !pTabFrame->IsLayoutSplitAllowed();
+
+ SwTwips nMin = 0;
+ if ( bDontSplit )
+ nMin = aRectFnSet.GetBottom(pTabFrame->getFrameArea());
+ else if ( !static_cast<const SwRowFrame*>(pRow)->IsRowSplitAllowed() )
+ nMin = aRectFnSet.GetBottom(pRow->getFrameArea());
+
+ if ( nMin && aRectFnSet.YDiff( nMin, nLower ) > 0 )
+ nRet = nMin;
+
+ nAdd = aRectFnSet.GetBottomMargin(*pRow->GetUpper());
+ }
+ else
+ nAdd = aRectFnSet.GetBottomMargin(*pFrame);
+
+ if( nAdd > 0 )
+ {
+ if ( aRectFnSet.IsVert() )
+ nRet -= nAdd;
+ else
+ nRet += nAdd;
+ }
+
+ // #i10770#: If there are fly frames anchored at previous paragraphs,
+ // the deadline should consider their lower borders.
+ const SwFrame* pStartFrame = pFrame->GetUpper()->GetLower();
+ OSL_ENSURE( pStartFrame, "Upper has no lower" );
+ SwTwips nFlyLower = aRectFnSet.IsVert() ? LONG_MAX : 0;
+ while ( pStartFrame != pFrame )
+ {
+ OSL_ENSURE( pStartFrame, "Frame chain is broken" );
+ if ( pStartFrame->GetDrawObjs() )
+ {
+ const SwSortedObjs &rObjs = *pStartFrame->GetDrawObjs();
+ for (SwAnchoredObject* pAnchoredObj : rObjs)
+ {
+ SwRect aRect( pAnchoredObj->GetObjRect() );
+
+ auto pFlyFrame = pAnchoredObj->DynCastFlyFrame();
+ if ( !pFlyFrame ||
+ pFlyFrame->isFrameAreaDefinitionValid() )
+ {
+ const SwTwips nBottom = aRectFnSet.GetBottom(aRect);
+ if ( aRectFnSet.YDiff( nBottom, nFlyLower ) > 0 )
+ nFlyLower = nBottom;
+ }
+ }
+ }
+
+ pStartFrame = pStartFrame->GetNext();
+ }
+
+ if ( aRectFnSet.IsVert() )
+ nRet = std::min( nRet, nFlyLower );
+ else
+ nRet = std::max( nRet, nFlyLower );
+
+ return nRet;
+}
+
+SwTwips SwTextFrame::GetFootnoteLine( const SwTextFootnote *pFootnote ) const
+{
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),
+ "SwTextFrame::GetFootnoteLine with swapped frame" );
+
+ SwTextFrame *pThis = const_cast<SwTextFrame*>(this);
+
+ if( !HasPara() )
+ {
+ // #109071# GetFormatted() does not work here, because most probably
+ // the frame is currently locked. We return the previous value.
+ return pThis->mnFootnoteLine > 0 ?
+ pThis->mnFootnoteLine :
+ IsVertical() ? getFrameArea().Left() : getFrameArea().Bottom();
+ }
+
+ SwTwips nRet;
+ {
+ SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this));
+
+ SwTextInfo aInf( pThis );
+ SwTextIter aLine( pThis, &aInf );
+ TextFrameIndex const nPos(MapModelToView(
+ &pFootnote->GetTextNode(), pFootnote->GetStart()));
+ aLine.CharToLine( nPos );
+
+ nRet = aLine.Y() + aLine.GetLineHeight();
+ if( IsVertical() )
+ nRet = SwitchHorizontalToVertical( nRet );
+ }
+
+ nRet = lcl_GetFootnoteLower( pThis, nRet );
+
+ pThis->mnFootnoteLine = nRet;
+ return nRet;
+}
+
+/**
+ * Calculates the maximum reachable height for the TextFrame in the Footnote Area.
+ * The cell's bottom margin with the Footnote Reference limit's this height.
+ */
+SwTwips SwTextFrame::GetFootnoteFrameHeight_() const
+{
+ OSL_ENSURE( !IsFollow() && IsInFootnote(), "SwTextFrame::SetFootnoteLine: moon walk" );
+
+ const SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame();
+ const SwTextFrame *pRef = static_cast<const SwTextFrame *>(pFootnoteFrame->GetRef());
+ const SwFootnoteBossFrame *pBoss = FindFootnoteBossFrame();
+ if( pBoss != pRef->FindFootnoteBossFrame( !pFootnoteFrame->GetAttr()->
+ GetFootnote().IsEndNote() ) )
+ return 0;
+
+ SwSwapIfSwapped swap(const_cast<SwTextFrame *>(this));
+
+ SwTwips nHeight = pRef->IsInFootnoteConnect() ?
+ 1 : pRef->GetFootnoteLine( pFootnoteFrame->GetAttr() );
+ if( nHeight )
+ {
+ // As odd as it may seem: the first Footnote on the page may not touch the
+ // Footnote Reference, when entering text in the Footnote Area.
+ const SwFrame *pCont = pFootnoteFrame->GetUpper();
+
+ // Height within the Container which we're allowed to consume anyways
+ SwRectFnSet aRectFnSet(pCont);
+ SwTwips nTmp = aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*pCont),
+ aRectFnSet.GetTop(getFrameArea()) );
+
+#if OSL_DEBUG_LEVEL > 0
+ if( nTmp < 0 )
+ {
+ bool bInvalidPos = false;
+ const SwLayoutFrame* pTmp = GetUpper();
+ while( !bInvalidPos && pTmp )
+ {
+ bInvalidPos = !pTmp->isFrameAreaPositionValid() ||
+ !pTmp->Lower()->isFrameAreaPositionValid();
+ if( pTmp == pCont )
+ break;
+ pTmp = pTmp->GetUpper();
+ }
+ OSL_ENSURE( bInvalidPos, "Hanging below FootnoteCont" );
+ }
+#endif
+
+ if ( aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight) > 0 )
+ {
+ // Growth potential of the container
+ if ( !pRef->IsInFootnoteConnect() )
+ {
+ SwSaveFootnoteHeight aSave( const_cast<SwFootnoteBossFrame*>(pBoss), nHeight );
+ nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true );
+ }
+ else
+ nHeight = const_cast<SwFootnoteContFrame*>(static_cast<const SwFootnoteContFrame*>(pCont))->Grow( LONG_MAX, true );
+
+ nHeight += nTmp;
+ if( nHeight < 0 )
+ nHeight = 0;
+ }
+ else
+ { // The container has to shrink
+ nTmp += aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()), nHeight);
+ if( nTmp > 0 )
+ nHeight = nTmp;
+ else
+ nHeight = 0;
+ }
+ }
+
+ return nHeight;
+}
+
+SwTextFrame *SwTextFrame::FindQuoVadisFrame()
+{
+ // Check whether we're in a FootnoteFrame
+ if( GetIndPrev() || !IsInFootnote() )
+ return nullptr;
+
+ // To the preceding FootnoteFrame
+ SwFootnoteFrame *pFootnoteFrame = FindFootnoteFrame()->GetMaster();
+ if( !pFootnoteFrame )
+ return nullptr;
+
+ // Now the last Content
+ SwContentFrame *pCnt = pFootnoteFrame->ContainsContent();
+ if( !pCnt )
+ return nullptr;
+ SwContentFrame *pLast;
+ do
+ { pLast = pCnt;
+ pCnt = pCnt->GetNextContentFrame();
+ } while( pCnt && pFootnoteFrame->IsAnLower( pCnt ) );
+ return static_cast<SwTextFrame*>(pLast);
+}
+
+void SwTextFrame::RemoveFootnote(TextFrameIndex const nStart, TextFrameIndex const nLen)
+{
+ if ( !IsFootnoteAllowed() )
+ return;
+
+ bool bRollBack = nLen != TextFrameIndex(COMPLETE_STRING);
+ TextFrameIndex nEnd;
+ SwTextFrame* pSource;
+ if( bRollBack )
+ {
+ nEnd = nStart + nLen;
+ pSource = GetFollow();
+ if( !pSource )
+ return;
+ }
+ else
+ {
+ nEnd = TextFrameIndex(COMPLETE_STRING);
+ pSource = this;
+ }
+
+ SwPageFrame* pUpdate = nullptr;
+ bool bRemove = false;
+ SwFootnoteBossFrame *pFootnoteBoss = nullptr;
+ SwFootnoteBossFrame *pEndBoss = nullptr;
+ bool bFootnoteEndDoc = FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos;
+ SwTextNode const* pNode(nullptr);
+ sw::MergedAttrIterReverse iter(*this);
+ for (SwTextAttr const* pHt = iter.PrevAttr(&pNode); pHt; pHt = iter.PrevAttr(&pNode))
+ {
+ if (RES_TXTATR_FTN != pHt->Which())
+ continue;
+
+ TextFrameIndex const nIdx(MapModelToView(pNode, pHt->GetStart()));
+ if (nStart > nIdx)
+ break;
+
+ if (nEnd >= nIdx)
+ {
+ SwTextFootnote const*const pFootnote(static_cast<SwTextFootnote const*>(pHt));
+ const bool bEndn = pFootnote->GetFootnote().IsEndNote();
+
+ if (bEndn)
+ {
+ if (!pEndBoss)
+ pEndBoss = pSource->FindFootnoteBossFrame();
+ }
+ else
+ {
+ if (!pFootnoteBoss)
+ {
+ pFootnoteBoss = pSource->FindFootnoteBossFrame( true );
+ if( pFootnoteBoss->GetUpper()->IsSctFrame() )
+ {
+ SwSectionFrame* pSect = static_cast<SwSectionFrame*>(
+ pFootnoteBoss->GetUpper());
+ if (pSect->IsFootnoteAtEnd())
+ bFootnoteEndDoc = false;
+ }
+ }
+ }
+
+ // We don't delete, but move instead.
+ // Three cases are to be considered:
+ // 1) There's neither Follow nor PrevFollow:
+ // -> RemoveFootnote() (maybe even a OSL_ENSURE(value))
+ //
+ // 2) nStart > GetOffset, I have a Follow
+ // -> Footnote moves into Follow
+ //
+ // 3) nStart < GetOffset, I am a Follow
+ // -> Footnote moves into the PrevFollow
+ //
+ // Both need to be on one Page/in one Column
+ SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote(pSource, pFootnote);
+
+ if (pFootnoteFrame)
+ {
+ const bool bEndDoc = bEndn || bFootnoteEndDoc;
+ if( bRollBack )
+ {
+ while (pFootnoteFrame)
+ {
+ pFootnoteFrame->SetRef( this );
+ pFootnoteFrame = pFootnoteFrame->GetFollow();
+ SetFootnote( true );
+ }
+ }
+ else if (GetFollow())
+ {
+ SwContentFrame *pDest = GetFollow();
+ while (pDest->GetFollow() && static_cast<SwTextFrame*>(pDest->
+ GetFollow())->GetOffset() <= nIdx)
+ pDest = pDest->GetFollow();
+ OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote(
+ pDest,pFootnote),"SwTextFrame::RemoveFootnote: footnote exists");
+
+ // Never deregister; always move
+ if (bEndDoc ||
+ !pFootnoteFrame->FindFootnoteBossFrame()->IsBefore(pDest->FindFootnoteBossFrame(!bEndn))
+ )
+ {
+ SwPageFrame* pTmp = pFootnoteFrame->FindPageFrame();
+ if( pUpdate && pUpdate != pTmp )
+ pUpdate->UpdateFootnoteNum();
+ pUpdate = pTmp;
+ while ( pFootnoteFrame )
+ {
+ pFootnoteFrame->SetRef( pDest );
+ pFootnoteFrame = pFootnoteFrame->GetFollow();
+ }
+ }
+ else
+ {
+ pFootnoteBoss->MoveFootnotes( this, pDest, pFootnote );
+ bRemove = true;
+ }
+ static_cast<SwTextFrame*>(pDest)->SetFootnote( true );
+
+ OSL_ENSURE( SwFootnoteBossFrame::FindFootnote( pDest,
+ pFootnote),"SwTextFrame::RemoveFootnote: footnote ChgRef failed");
+ }
+ else
+ {
+ if (!bEndDoc || ( bEndn && pEndBoss->IsInSct() &&
+ !SwLayouter::Collecting( &GetDoc(),
+ pEndBoss->FindSctFrame(), nullptr ) ))
+ {
+ if( bEndn )
+ pEndBoss->RemoveFootnote( this, pFootnote );
+ else
+ pFootnoteBoss->RemoveFootnote( this, pFootnote );
+ bRemove = bRemove || !bEndDoc;
+ OSL_ENSURE( !SwFootnoteBossFrame::FindFootnote( this, pFootnote ),
+ "SwTextFrame::RemoveFootnote: can't get off that footnote" );
+ }
+ }
+ }
+ }
+ }
+ if (pUpdate)
+ pUpdate->UpdateFootnoteNum();
+
+ // We break the oscillation
+ if (bRemove && !bFootnoteEndDoc && HasPara())
+ {
+ ValidateBodyFrame();
+ ValidateFrame();
+ }
+
+ // We call the RemoveFootnote from within the FindBreak, because the last line is
+ // to be passed to the Follow. The Offset of the Follow is, however, outdated;
+ // it'll be set soon. CalcFntFlag depends on a correctly set Follow Offset.
+ // Therefore we temporarily calculate the Follow Offset here
+ TextFrameIndex nOldOfst(COMPLETE_STRING);
+ if( HasFollow() && nStart > GetOffset() )
+ {
+ nOldOfst = GetFollow()->GetOffset();
+ GetFollow()->ManipOfst(nStart + (bRollBack ? nLen : TextFrameIndex(0)));
+ }
+ pSource->CalcFootnoteFlag();
+ if (nOldOfst < TextFrameIndex(COMPLETE_STRING))
+ GetFollow()->ManipOfst( nOldOfst );
+}
+
+
+/**
+ * We basically only have two possibilities:
+ *
+ * a) The Footnote is already present
+ * => we move it, if another pSrcFrame has been found
+ *
+ * b) The Footnote is not present
+ * => we have it created for us
+ *
+ * Whether the Footnote ends up on our Page/Column, doesn't matter in this
+ * context.
+ *
+ * Optimization for Endnotes.
+ *
+ * Another problem: if the Deadline falls within the Footnote Area, we need
+ * to move the Footnote.
+ *
+ * @returns false on any type of error
+ */
+void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDeadLine )
+{
+ OSL_ENSURE( !IsVertical() || !IsSwapped(),
+ "SwTextFrame::ConnectFootnote with swapped frame" );
+
+ mbFootnote = true;
+ mbInFootnoteConnect = true; // Just reset!
+ // See if pFootnote is an endnote on a separate endnote page.
+ const IDocumentSettingAccess& rSettings = GetDoc().getIDocumentSettingAccess();
+ const bool bContinuousEndnotes = rSettings.get(DocumentSettingId::CONTINUOUS_ENDNOTES);
+ const bool bEnd = pFootnote->GetFootnote().IsEndNote();
+
+ // We want to store this value, because it is needed as a fallback
+ // in GetFootnoteLine(), if there is no paragraph information available
+ mnFootnoteLine = nDeadLine;
+
+ // We always need a parent (Page/Column)
+ SwSectionFrame *pSect;
+ SwContentFrame *pContent = this;
+ if( bEnd && IsInSct() )
+ {
+ pSect = FindSctFrame();
+ if( pSect->IsEndnAtEnd() )
+ pContent = pSect->FindLastContent( SwFindMode::EndNote );
+ if( !pContent )
+ pContent = this;
+ }
+
+ SwFootnoteBossFrame *pBoss = pContent->FindFootnoteBossFrame( !bEnd );
+
+ pSect = pBoss->FindSctFrame();
+ bool bDocEnd = bEnd ? !( pSect && pSect->IsEndnAtEnd() ) :
+ ( !( pSect && pSect->IsFootnoteAtEnd() ) &&
+ FTNPOS_CHAPTER == GetDoc().GetFootnoteInfo().m_ePos);
+
+ // Footnote can be registered with the Follow
+ SwContentFrame *pSrcFrame = FindFootnoteRef( pFootnote );
+
+ if( bDocEnd )
+ {
+ if ((pSect || bContinuousEndnotes) && pSrcFrame)
+ {
+ SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote );
+ if (pFootnoteFrame && (pFootnoteFrame->IsInSct() || bContinuousEndnotes))
+ {
+ // We either have a foot/endnote that goes to the end of the section or are in Word
+ // compatibility mode where endnotes go to the end of the document. Handle both
+ // cases by removing the footnote here, then later appending them to the correct
+ // last page of the document or section.
+ pBoss->RemoveFootnote( pSrcFrame, pFootnote );
+ pSrcFrame = nullptr;
+ }
+ }
+ }
+ else if( bEnd && pSect )
+ {
+ SwFootnoteFrame *pFootnoteFrame = pSrcFrame ? SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote ) : nullptr;
+ if( pFootnoteFrame && !pFootnoteFrame->GetUpper() )
+ pFootnoteFrame = nullptr;
+ SwDoc *const pDoc = &GetDoc();
+ if( SwLayouter::Collecting( pDoc, pSect, pFootnoteFrame ) )
+ {
+ if( !pSrcFrame )
+ {
+ SwFootnoteFrame *pNew = new SwFootnoteFrame(pDoc->GetDfltFrameFormat(),this,this,pFootnote);
+ SwNodeIndex aIdx( *pFootnote->GetStartNode(), 1 );
+ ::InsertCnt_( pNew, pDoc, aIdx.GetIndex() );
+ pDoc->getIDocumentLayoutAccess().GetLayouter()->CollectEndnote( pNew );
+ }
+ else if( pSrcFrame != this )
+ SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
+ mbInFootnoteConnect = false;
+ return;
+ }
+ else if (pSrcFrame && pFootnoteFrame)
+ {
+ SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();
+ if( !pFootnoteBoss->IsInSct() ||
+ pFootnoteBoss->ImplFindSctFrame()->GetSection()!=pSect->GetSection() )
+ {
+ pBoss->RemoveFootnote( pSrcFrame, pFootnote );
+ pSrcFrame = nullptr;
+ }
+ }
+ }
+
+ if( bDocEnd || bEnd )
+ {
+ if( !pSrcFrame )
+ pBoss->AppendFootnote( this, pFootnote );
+ else if( pSrcFrame != this )
+ SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
+ mbInFootnoteConnect = false;
+ return;
+ }
+
+ SwSaveFootnoteHeight aHeight( pBoss, nDeadLine );
+
+ if( !pSrcFrame ) // No Footnote was found at all
+ pBoss->AppendFootnote( this, pFootnote );
+ else
+ {
+ SwFootnoteFrame *pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pSrcFrame, pFootnote );
+ SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();
+
+ bool bBrutal = false;
+
+ if( pFootnoteBoss == pBoss ) // Ref and Footnote are on the same Page/Column
+ {
+ SwFrame *pCont = pFootnoteFrame->GetUpper();
+
+ SwRectFnSet aRectFnSet(pCont);
+ tools::Long nDiff = aRectFnSet.YDiff( aRectFnSet.GetTop(pCont->getFrameArea()),
+ nDeadLine );
+
+ if( nDiff >= 0 )
+ {
+ // If the Footnote has been registered to a Follow, we need to
+ // rewire it now too
+ if ( pSrcFrame != this )
+ SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
+
+ // We have some room left, so the Footnote can grow
+ if ( pFootnoteFrame->GetFollow() && nDiff > 0 )
+ {
+ SwFrameDeleteGuard aDeleteGuard(pCont);
+ SwTwips nHeight = aRectFnSet.GetHeight(pCont->getFrameArea());
+ pBoss->RearrangeFootnotes( nDeadLine, false, pFootnote );
+ ValidateBodyFrame();
+ ValidateFrame();
+ SwViewShell *pSh = getRootFrame()->GetCurrShell();
+ if ( pSh && nHeight == aRectFnSet.GetHeight(pCont->getFrameArea()) )
+ // So that we don't miss anything
+ pSh->InvalidateWindows( pCont->getFrameArea() );
+ }
+ mbInFootnoteConnect = false;
+ return;
+ }
+ else
+ bBrutal = true;
+ }
+ else
+ {
+ // Ref and Footnote are not on one Page; attempt to move is necessary
+ SwFrame* pTmp = this;
+ while( pTmp->GetNext() && pSrcFrame != pTmp )
+ pTmp = pTmp->GetNext();
+ if( pSrcFrame == pTmp )
+ bBrutal = true;
+ else
+ { // If our Parent is in a column Area, but the Page already has a
+ // FootnoteContainer, we can only brute force it
+ if( pSect && pSect->FindFootnoteBossFrame( !bEnd )->FindFootnoteCont() )
+ bBrutal = true;
+
+ else if ( !pFootnoteFrame->GetPrev() ||
+ pFootnoteBoss->IsBefore( pBoss )
+ )
+ {
+ SwFootnoteBossFrame *pSrcBoss = pSrcFrame->FindFootnoteBossFrame( !bEnd );
+ pSrcBoss->MoveFootnotes( pSrcFrame, this, pFootnote );
+ }
+ else
+ SwFootnoteBossFrame::ChangeFootnoteRef( pSrcFrame, pFootnote, this );
+ }
+ }
+
+ // The brute force method: Remove Footnote and append.
+ // We need to call SetFootnoteDeadLine(), as we can more easily adapt the
+ // nMaxFootnoteHeight after RemoveFootnote
+ if( bBrutal )
+ {
+ pBoss->RemoveFootnote( pSrcFrame, pFootnote, false );
+ std::unique_ptr<SwSaveFootnoteHeight> pHeight(bEnd ? nullptr : new SwSaveFootnoteHeight( pBoss, nDeadLine ));
+ pBoss->AppendFootnote( this, pFootnote );
+ }
+ }
+
+ // In column Areas, that not yet reach the Page's border a RearrangeFootnotes is not
+ // useful yet, as the Footnote container has not yet been calculated
+ if( !pSect || !pSect->Growable() )
+ {
+ // Validate environment, to avoid oscillation
+ SwSaveFootnoteHeight aNochmal( pBoss, nDeadLine );
+ ValidateBodyFrame();
+ pBoss->RearrangeFootnotes( nDeadLine, true );
+ ValidateFrame();
+ }
+ else if( pSect->IsFootnoteAtEnd() )
+ {
+ ValidateBodyFrame();
+ ValidateFrame();
+ }
+
+ mbInFootnoteConnect = false;
+}
+
+/**
+ * The portion for the Footnote Reference in the Text
+ */
+SwFootnotePortion *SwTextFormatter::NewFootnotePortion( SwTextFormatInfo &rInf,
+ SwTextAttr *pHint )
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
+ "NewFootnotePortion with unswapped frame" );
+
+ SwTextFootnote *pFootnote = static_cast<SwTextFootnote*>(pHint);
+
+ if( !m_pFrame->IsFootnoteAllowed() )
+ return new SwFootnotePortion("", pFootnote);
+
+ const SwFormatFootnote& rFootnote = pFootnote->GetFootnote();
+ SwDoc *const pDoc = &m_pFrame->GetDoc();
+
+ if( rInf.IsTest() )
+ return new SwFootnotePortion(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()), pFootnote);
+
+ SwSwapIfSwapped swap(m_pFrame);
+
+ sal_uInt16 nReal;
+ {
+ sal_uInt16 nOldReal = m_pCurr->GetRealHeight();
+ sal_uInt16 nOldAscent = m_pCurr->GetAscent();
+ sal_uInt16 nOldHeight = m_pCurr->Height();
+ CalcRealHeight();
+ nReal = m_pCurr->GetRealHeight();
+ if( nReal < nOldReal )
+ nReal = nOldReal;
+ m_pCurr->SetRealHeight( nOldReal );
+ m_pCurr->Height( nOldHeight );
+ m_pCurr->SetAscent( nOldAscent );
+ }
+
+ SwTwips nLower = Y() + nReal;
+
+ const bool bVertical = m_pFrame->IsVertical();
+ if( bVertical )
+ nLower = m_pFrame->SwitchHorizontalToVertical( nLower );
+
+ nLower = lcl_GetFootnoteLower( m_pFrame, nLower );
+
+ // We just refresh.
+ // The Connect does not do anything useful in this case, but will
+ // mostly throw away the Footnote and create it anew.
+ if( !rInf.IsQuick() )
+ m_pFrame->ConnectFootnote( pFootnote, nLower );
+
+ SwTextFrame *pScrFrame = m_pFrame->FindFootnoteRef( pFootnote );
+ SwFootnoteBossFrame *pBoss = m_pFrame->FindFootnoteBossFrame( !rFootnote.IsEndNote() );
+ SwFootnoteFrame *pFootnoteFrame = nullptr;
+ if( pScrFrame )
+ pFootnoteFrame = SwFootnoteBossFrame::FindFootnote( pScrFrame, pFootnote );
+
+ // We see whether our Append has caused some Footnote to
+ // still be on the Page/Column. If not, our line disappears too,
+ // which will lead to the following undesired behaviour:
+ // Footnote1 still fits onto the Page/Column, but Footnote2 doesn't.
+ // The Footnote2 Reference remains on the Page/Column. The Footnote itself
+ // is on the next Page/Column.
+ //
+ // Exception: If the Page/Column cannot accommodate another line,
+ // the Footnote Reference should be moved to the next one.
+ if( !rFootnote.IsEndNote() )
+ {
+ SwSectionFrame *pSct = pBoss->FindSctFrame();
+ bool bAtSctEnd = pSct && pSct->IsFootnoteAtEnd();
+ if( FTNPOS_CHAPTER != pDoc->GetFootnoteInfo().m_ePos || bAtSctEnd )
+ {
+ SwFrame* pFootnoteCont = pBoss->FindFootnoteCont();
+ // If the Parent is within an Area, it can only be a Column of this
+ // Area. If this one is not the first Column, we can avoid it.
+ if( !m_pFrame->IsInTab() && ( GetLineNr() > 1 || m_pFrame->GetPrev() ||
+ ( !bAtSctEnd && m_pFrame->GetIndPrev() ) ||
+ ( pSct && pBoss->GetPrev() ) ) )
+ {
+ if( !pFootnoteCont )
+ {
+ rInf.SetStop( true );
+ return nullptr;
+ }
+ else
+ {
+ // There must not be any Footnote Containers in column Areas and at the same time on the
+ // Page/Page column
+ if( pSct && !bAtSctEnd ) // Is the Container in a (column) Area?
+ {
+ SwFootnoteBossFrame* pTmp = pBoss->FindSctFrame()->FindFootnoteBossFrame( true );
+ SwFootnoteContFrame* pFootnoteC = pTmp->FindFootnoteCont();
+ if( pFootnoteC )
+ {
+ SwFootnoteFrame* pTmpFrame = static_cast<SwFootnoteFrame*>(pFootnoteC->Lower());
+ if( pTmpFrame && *pTmpFrame < pFootnote )
+ {
+ rInf.SetStop( true );
+ return nullptr;
+ }
+ }
+ }
+ // Is this the last Line that fits?
+ SwTwips nTmpBot = Y() + nReal * 2;
+
+ if( bVertical )
+ nTmpBot = m_pFrame->SwitchHorizontalToVertical( nTmpBot );
+
+ SwRectFnSet aRectFnSet(pFootnoteCont);
+
+ const tools::Long nDiff = aRectFnSet.YDiff(
+ aRectFnSet.GetTop(pFootnoteCont->getFrameArea()),
+ nTmpBot );
+
+ if( pScrFrame && nDiff < 0 )
+ {
+ if( pFootnoteFrame )
+ {
+ SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame();
+ if( pFootnoteBoss != pBoss )
+ {
+ // We're in the last Line and the Footnote has moved
+ // to another Page. We also want to be on that Page!
+ rInf.SetStop( true );
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // Finally: Create FootnotePortion and exit ...
+ SwFootnotePortion *pRet = new SwFootnotePortion(
+ rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame()),
+ pFootnote, nReal );
+ rInf.SetFootnoteInside( true );
+
+ return pRet;
+}
+
+/**
+ * The portion for the Footnote Numbering in the Footnote Area
+ */
+SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const &rInf ) const
+{
+ OSL_ENSURE( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() && !rInf.IsFootnoteDone(),
+ "This is the wrong place for a ftnnumber" );
+ if( rInf.GetTextStart() != m_nStart ||
+ rInf.GetTextStart() != rInf.GetIdx() )
+ return nullptr;
+
+ const SwFootnoteFrame* pFootnoteFrame = m_pFrame->FindFootnoteFrame();
+ const SwTextFootnote* pFootnote = pFootnoteFrame->GetAttr();
+
+ // Aha! So we're in the Footnote Area!
+ SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pFootnote->GetFootnote());
+
+ SwDoc *const pDoc = &m_pFrame->GetDoc();
+ OUString aFootnoteText(rFootnote.GetViewNumStr(*pDoc, m_pFrame->getRootFrame(), true));
+
+ const SwEndNoteInfo* pInfo;
+ if( rFootnote.IsEndNote() )
+ pInfo = &pDoc->GetEndNoteInfo();
+ else
+ pInfo = &pDoc->GetFootnoteInfo();
+
+ const SwAttrSet* pParSet = &rInf.GetCharAttr();
+ const IDocumentSettingAccess* pIDSA = &pDoc->getIDocumentSettingAccess();
+ std::unique_ptr<SwFont> pNumFnt(new SwFont( pParSet, pIDSA ));
+
+ // #i37142#
+ // Underline style of paragraph font should not be considered
+ // Overline style of paragraph font should not be considered
+ // Weight style of paragraph font should not be considered
+ // Posture style of paragraph font should not be considered
+ // See also #i18463# and SwTextFormatter::NewNumberPortion()
+ pNumFnt->SetUnderline( LINESTYLE_NONE );
+ pNumFnt->SetOverline( LINESTYLE_NONE );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK );
+ pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK );
+ pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL );
+
+ const rtl::Reference<SwXTextRange> xAnchor = rFootnote.getAnchor(*pDoc);
+ if (xAnchor.is())
+ {
+ auto aAny = xAnchor->getPropertyValue("CharFontCharSet");
+ sal_Int16 eCharSet;
+ if ((aAny >>= eCharSet) && eCharSet == awt::CharSet::SYMBOL)
+ {
+ OUString aFontName;
+ aAny = xAnchor->getPropertyValue("CharFontName");
+ if (aAny >>= aFontName)
+ {
+ pNumFnt->SetName(aFontName, SwFontScript::Latin);
+ pNumFnt->SetName(aFontName, SwFontScript::CJK);
+ pNumFnt->SetName(aFontName, SwFontScript::CTL);
+ pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::Latin);
+ pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CJK);
+ pNumFnt->SetCharSet(RTL_TEXTENCODING_SYMBOL, SwFontScript::CTL);
+ }
+ }
+ }
+
+ const SwAttrSet& rSet = pInfo->GetCharFormat(*pDoc)->GetAttrSet();
+ pNumFnt->SetDiffFnt(&rSet, pIDSA );
+ pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() );
+
+ // tdf#85610 apply redline coloring to the footnote numbering in the footnote area
+ SwUnoInternalPaM aPam(*pDoc);
+ if ( ::sw::XTextRangeToSwPaM(aPam, xAnchor) )
+ {
+ SwRedlineTable::size_type nRedlinePos = 0;
+ const SwRedlineTable& rTable = pDoc->getIDocumentRedlineAccess().GetRedlineTable();
+ const SwRangeRedline* pRedline = rTable.FindAtPosition( *aPam.Start(), nRedlinePos );
+ if (pRedline)
+ {
+ SwAttrPool& rPool = pDoc->GetAttrPool();
+ SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1> aSet(rPool);
+
+ std::size_t aAuthor = (1 < pRedline->GetStackCount())
+ ? pRedline->GetAuthor( 1 )
+ : pRedline->GetAuthor();
+
+ if ( RedlineType::Delete == pRedline->GetType() )
+ SW_MOD()->GetDeletedAuthorAttr(aAuthor, aSet);
+ else
+ SW_MOD()->GetInsertAuthorAttr(aAuthor, aSet);
+
+ if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
+ pNumFnt->SetColor(pItem->GetValue());
+ if (const SvxUnderlineItem* pItem = aSet.GetItemIfSet(RES_CHRATR_UNDERLINE))
+ pNumFnt->SetUnderline(pItem->GetLineStyle());
+ if (const SvxCrossedOutItem* pItem = aSet.GetItemIfSet(RES_CHRATR_CROSSEDOUT))
+ pNumFnt->SetStrikeout( pItem->GetStrikeout() );
+ }
+ }
+
+ SwFootnoteNumPortion* pNewPor = new SwFootnoteNumPortion( aFootnoteText, std::move(pNumFnt) );
+ pNewPor->SetLeft( !m_pFrame->IsRightToLeft() );
+ return pNewPor;
+}
+
+static OUString lcl_GetPageNumber( const SwPageFrame* pPage )
+{
+ OSL_ENSURE( pPage, "GetPageNumber: Homeless TextFrame" );
+ const sal_uInt16 nVirtNum = pPage->GetVirtPageNum();
+ const SvxNumberType& rNum = pPage->GetPageDesc()->GetNumType();
+ return rNum.GetNumStr( nVirtNum );
+}
+
+SwErgoSumPortion *SwTextFormatter::NewErgoSumPortion( SwTextFormatInfo const &rInf ) const
+{
+ // We cannot assume we're a Follow
+ if( !m_pFrame->IsInFootnote() || m_pFrame->GetPrev() ||
+ rInf.IsErgoDone() || rInf.GetIdx() != m_pFrame->GetOffset() ||
+ m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() )
+ return nullptr;
+
+ // we are in the footnote container
+ const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo();
+ SwTextFrame *pQuoFrame = m_pFrame->FindQuoVadisFrame();
+ if( !pQuoFrame )
+ return nullptr;
+ const SwPageFrame* pPage = m_pFrame->FindPageFrame();
+ const SwPageFrame* pQuoPage = pQuoFrame->FindPageFrame();
+ if( pPage == pQuoFrame->FindPageFrame() )
+ return nullptr; // If the QuoVadis is on the same Column/Page
+ const OUString aPage = lcl_GetPageNumber( pPage );
+ SwParaPortion *pPara = pQuoFrame->GetPara();
+ if( pPara )
+ pPara->SetErgoSumNum( aPage );
+ if( rFootnoteInfo.m_aErgoSum.isEmpty() )
+ return nullptr;
+ SwErgoSumPortion *pErgo = new SwErgoSumPortion( rFootnoteInfo.m_aErgoSum,
+ lcl_GetPageNumber( pQuoPage ) );
+ return pErgo;
+}
+
+TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset)
+{
+ OSL_ENSURE( ! m_pFrame->IsVertical() || ! m_pFrame->IsSwapped(),
+ "SwTextFormatter::FormatQuoVadis with swapped frame" );
+
+ if( !m_pFrame->IsInFootnote() || m_pFrame->ImplFindFootnoteFrame()->GetAttr()->GetFootnote().IsEndNote() )
+ return nOffset;
+
+ const SwFrame* pErgoFrame = m_pFrame->FindFootnoteFrame()->GetFollow();
+ if( !pErgoFrame && m_pFrame->HasFollow() )
+ pErgoFrame = m_pFrame->GetFollow();
+ if( !pErgoFrame )
+ return nOffset;
+
+ if( pErgoFrame == m_pFrame->GetNext() )
+ {
+ SwFrame *pCol = m_pFrame->FindColFrame();
+ while( pCol && !pCol->GetNext() )
+ pCol = pCol->GetUpper()->FindColFrame();
+ if( pCol )
+ return nOffset;
+ }
+ else
+ {
+ const SwPageFrame* pPage = m_pFrame->FindPageFrame();
+ const SwPageFrame* pErgoPage = pErgoFrame->FindPageFrame();
+ if( pPage == pErgoPage )
+ return nOffset; // If the ErgoSum is on the same Page
+ }
+
+ SwTextFormatInfo &rInf = GetInfo();
+ const SwFootnoteInfo &rFootnoteInfo = m_pFrame->GetDoc().GetFootnoteInfo();
+ if( rFootnoteInfo.m_aQuoVadis.isEmpty() )
+ return nOffset;
+
+ // A remark on QuoVadis/ErgoSum:
+ // We use the Font set for the Paragraph for these texts.
+ // Thus, we initialize:
+ // TODO: ResetFont();
+ FeedInf( rInf );
+ SeekStartAndChg( rInf, true );
+ if( GetRedln() && m_pCurr->HasRedline() )
+ {
+ std::pair<SwTextNode const*, sal_Int32> const pos(
+ GetTextFrame()->MapViewToModel(nOffset));
+ GetRedln()->Seek(*m_pFont, pos.first->GetIndex(), pos.second, 0);
+ }
+
+ // A tricky special case: Flyfrms extend into the Line and are at the
+ // position we want to insert the Quovadis text
+ // Let's see if it is that bad indeed:
+ SwLinePortion *pPor = m_pCurr->GetFirstPortion();
+ sal_uInt16 nLastLeft = 0;
+ while( pPor )
+ {
+ if ( pPor->IsFlyPortion() )
+ nLastLeft = static_cast<SwFlyPortion*>(pPor)->GetFix() +
+ static_cast<SwFlyPortion*>(pPor)->Width();
+ pPor = pPor->GetNextPortion();
+ }
+
+ // The old game all over again: we want the Line to wrap around
+ // at a certain point, so we adjust the width.
+ // nLastLeft is now basically the right margin
+ const sal_uInt16 nOldRealWidth = rInf.RealWidth();
+ rInf.RealWidth( nOldRealWidth - nLastLeft );
+
+ OUString aErgo = lcl_GetPageNumber( pErgoFrame->FindPageFrame() );
+ SwQuoVadisPortion *pQuo = new SwQuoVadisPortion(rFootnoteInfo.m_aQuoVadis, aErgo );
+ pQuo->SetAscent( rInf.GetAscent() );
+ pQuo->Height( rInf.GetTextHeight() );
+ pQuo->Format( rInf );
+ sal_uInt16 nQuoWidth = pQuo->Width();
+ SwLinePortion* pCurrPor = pQuo;
+
+ while ( rInf.GetRest() )
+ {
+ SwLinePortion* pFollow = rInf.GetRest();
+ rInf.SetRest( nullptr );
+ pCurrPor->Move( rInf );
+
+ OSL_ENSURE( pFollow->IsQuoVadisPortion(),
+ "Quo Vadis, rest of QuoVadisPortion" );
+
+ // format the rest and append it to the other QuoVadis parts
+ pFollow->Format( rInf );
+ nQuoWidth = nQuoWidth + pFollow->Width();
+
+ pCurrPor->Append( pFollow );
+ pCurrPor = pFollow;
+ }
+
+ Right( Right() - nQuoWidth );
+
+ TextFrameIndex nRet;
+ {
+ SwSwapIfNotSwapped swap(m_pFrame);
+
+ nRet = FormatLine( m_nStart );
+ }
+
+ Right( rInf.Left() + nOldRealWidth - 1 );
+
+ nLastLeft = nOldRealWidth - m_pCurr->Width();
+ FeedInf( rInf );
+
+ // It's possible that there's a Margin Portion at the end, which would
+ // just cause a lot of trouble, when respanning
+ pPor = m_pCurr->FindLastPortion();
+ SwGluePortion *pGlue = pPor->IsMarginPortion() ? static_cast<SwMarginPortion*>(pPor) : nullptr;
+ if( pGlue )
+ {
+ pGlue->Height( 0 );
+ pGlue->Width( 0 );
+ pGlue->SetLen(TextFrameIndex(0));
+ pGlue->SetAscent( 0 );
+ pGlue->SetNextPortion( nullptr );
+ pGlue->SetFixWidth(0);
+ }
+
+ // Luxury: We make sure the QuoVadis text appears on the right, by
+ // using Glues.
+ nLastLeft = nLastLeft - nQuoWidth;
+ if( nLastLeft )
+ {
+ if( nLastLeft > pQuo->GetAscent() ) // Minimum distance
+ {
+ switch( GetAdjust() )
+ {
+ case SvxAdjust::Block:
+ {
+ if( !m_pCurr->GetLen() ||
+ CH_BREAK != GetInfo().GetChar(m_nStart + m_pCurr->GetLen() - TextFrameIndex(1)))
+ nLastLeft = pQuo->GetAscent();
+ nQuoWidth = nQuoWidth + nLastLeft;
+ break;
+ }
+ case SvxAdjust::Right:
+ {
+ nLastLeft = pQuo->GetAscent();
+ nQuoWidth = nQuoWidth + nLastLeft;
+ break;
+ }
+ case SvxAdjust::Center:
+ {
+ nQuoWidth = nQuoWidth + pQuo->GetAscent();
+ tools::Long nDiff = nLastLeft - nQuoWidth;
+ if( nDiff < 0 )
+ {
+ nLastLeft = pQuo->GetAscent();
+ nQuoWidth = o3tl::narrowing<sal_uInt16>(-nDiff + nLastLeft);
+ }
+ else
+ {
+ nQuoWidth = 0;
+ nLastLeft = sal_uInt16(( pQuo->GetAscent() + nDiff ) / 2);
+ }
+ break;
+ }
+ default:
+ nQuoWidth = nQuoWidth + nLastLeft;
+ }
+ }
+ else
+ nQuoWidth = nQuoWidth + nLastLeft;
+ if( nLastLeft )
+ {
+ pGlue = new SwGluePortion(0);
+ pGlue->Width( nLastLeft );
+ pPor->Append( pGlue );
+ pPor = pPor->GetNextPortion();
+ }
+ }
+
+ // Finally: we insert the QuoVadis Portion
+ pCurrPor = pQuo;
+ while ( pCurrPor )
+ {
+ // pPor->Append deletes the pPortion pointer of pPor.
+ // Therefore we have to keep a pointer to the next portion
+ pQuo = static_cast<SwQuoVadisPortion*>(pCurrPor->GetNextPortion());
+ pPor->Append( pCurrPor );
+ pPor = pPor->GetNextPortion();
+ pCurrPor = pQuo;
+ }
+
+ m_pCurr->Width( m_pCurr->Width() + nQuoWidth );
+
+ // And adjust again, due to the adjustment and due to the following special
+ // case:
+ // The DummyUser has set a smaller Font in the Line than the one used
+ // by the QuoVadis text ...
+ CalcAdjustLine( m_pCurr );
+
+ return nRet;
+}
+
+/**
+ * This function creates a Line that reaches to the other Page Margin.
+ * DummyLines or DummyPortions make sure, that oscillations stop, because
+ * there's no way to flow back.
+ * They are used for Footnotes in paragraph-bound Frames and for Footnote
+ * oscillations
+ */
+void SwTextFormatter::MakeDummyLine()
+{
+ sal_uInt16 nRstHeight = GetFrameRstHeight();
+ if( m_pCurr && nRstHeight > m_pCurr->Height() )
+ {
+ SwLineLayout *pLay = new SwLineLayout;
+ nRstHeight = nRstHeight - m_pCurr->Height();
+ pLay->Height( nRstHeight );
+ pLay->SetAscent( nRstHeight );
+ Insert( pLay );
+ Next();
+ }
+}
+
+namespace {
+
+class SwFootnoteSave
+{
+ SwTextSizeInfo* m_pInf;
+ SwFont* m_pFnt;
+ std::unique_ptr<SwFont> m_pOld;
+
+ SwFootnoteSave(const SwFootnoteSave&) = delete;
+ SwFootnoteSave& operator=(const SwFootnoteSave&) = delete;
+
+public:
+ SwFootnoteSave( const SwTextSizeInfo &rInf,
+ const SwTextFootnote *pTextFootnote,
+ const bool bApplyGivenScriptType,
+ const SwFontScript nGivenScriptType );
+ ~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE;
+};
+
+}
+
+SwFootnoteSave::SwFootnoteSave(const SwTextSizeInfo& rInf, const SwTextFootnote* pTextFootnote,
+ const bool bApplyGivenScriptType,
+ const SwFontScript nGivenScriptType)
+ : m_pInf(&const_cast<SwTextSizeInfo&>(rInf))
+ , m_pFnt(nullptr)
+{
+ if( pTextFootnote && rInf.GetTextFrame() )
+ {
+ m_pFnt = const_cast<SwTextSizeInfo&>(rInf).GetFont();
+ m_pOld.reset(new SwFont(*m_pFnt));
+ m_pOld->GetTox() = m_pFnt->GetTox();
+ m_pFnt->GetTox() = 0;
+ SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
+ const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();
+
+ // #i98418#
+ if ( bApplyGivenScriptType )
+ {
+ m_pFnt->SetActual(nGivenScriptType);
+ }
+ else
+ {
+ // examine text and set script
+ OUString aTmpStr(rFootnote.GetViewNumStr(*pDoc, rInf.GetTextFrame()->getRootFrame()));
+ m_pFnt->SetActual(SwScriptInfo::WhichFont(0, aTmpStr));
+ }
+
+ const SwEndNoteInfo* pInfo;
+ if( rFootnote.IsEndNote() )
+ pInfo = &pDoc->GetEndNoteInfo();
+ else
+ pInfo = &pDoc->GetFootnoteInfo();
+ const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
+ m_pFnt->SetDiffFnt(&rSet, &pDoc->getIDocumentSettingAccess());
+
+ // we reduce footnote size, if we are inside a double line portion
+ if (!m_pOld->GetEscapement() && 50 == m_pOld->GetPropr())
+ {
+ Size aSize = m_pFnt->GetSize(m_pFnt->GetActual());
+ m_pFnt->SetSize(Size(aSize.Width() / 2, aSize.Height() / 2), m_pFnt->GetActual());
+ }
+
+ // set the correct rotation at the footnote font
+ if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) )
+ m_pFnt->SetVertical(pItem->GetValue(),
+ rInf.GetTextFrame()->IsVertical());
+
+ m_pFnt->ChgPhysFnt(m_pInf->GetVsh(), *m_pInf->GetOut());
+
+ if( const SvxBrushItem* pItem = rSet.GetItemIfSet( RES_CHRATR_BACKGROUND ) )
+ m_pFnt->SetBackColor(pItem->GetColor());
+ }
+ else
+ m_pFnt = nullptr;
+}
+
+SwFootnoteSave::~SwFootnoteSave() COVERITY_NOEXCEPT_FALSE
+{
+ if (m_pFnt)
+ {
+ // Put back SwFont
+ *m_pFnt = *m_pOld;
+ m_pFnt->GetTox() = m_pOld->GetTox();
+ m_pFnt->ChgPhysFnt(m_pInf->GetVsh(), *m_pInf->GetOut());
+ m_pOld.reset();
+ }
+}
+
+SwFootnotePortion::SwFootnotePortion( const OUString &rExpand,
+ SwTextFootnote *pFootn, sal_uInt16 nReal )
+ : SwFieldPortion( rExpand, nullptr )
+ , m_pFootnote(pFootn)
+ , m_nOrigHeight( nReal )
+ // #i98418#
+ , mbPreferredScriptTypeSet( false )
+ , mnPreferredScriptType( SwFontScript::Latin )
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::Footnote );
+}
+
+bool SwFootnotePortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const
+{
+ rText = m_aExpand;
+ return true;
+}
+
+bool SwFootnotePortion::Format( SwTextFormatInfo &rInf )
+{
+ // #i98418#
+// SwFootnoteSave aFootnoteSave( rInf, pFootnote );
+ SwFootnoteSave aFootnoteSave( rInf, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
+ // the idx is manipulated in SwExpandPortion::Format
+ // this flag indicates, that a footnote is allowed to trigger
+ // an underflow during SwTextGuess::Guess
+ rInf.SetFakeLineStart( rInf.GetIdx() > rInf.GetLineStart() );
+ const bool bFull = SwFieldPortion::Format( rInf );
+ rInf.SetFakeLineStart( false );
+ SetAscent( rInf.GetAscent() );
+ Height( rInf.GetTextHeight() );
+ rInf.SetFootnoteDone( !bFull );
+ if( !bFull )
+ rInf.SetParaFootnote();
+ return bFull;
+}
+
+void SwFootnotePortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // #i98418#
+// SwFootnoteSave aFootnoteSave( rInf, pFootnote );
+ SwFootnoteSave aFootnoteSave( rInf, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
+ rInf.DrawViewOpt( *this, PortionType::Footnote );
+ SwExpandPortion::Paint( rInf );
+}
+
+SwPosSize SwFootnotePortion::GetTextSize( const SwTextSizeInfo &rInfo ) const
+{
+ // #i98418#
+// SwFootnoteSave aFootnoteSave( rInfo, pFootnote );
+ SwFootnoteSave aFootnoteSave( rInfo, m_pFootnote, mbPreferredScriptTypeSet, mnPreferredScriptType );
+ return SwExpandPortion::GetTextSize( rInfo );
+}
+
+// #i98418#
+void SwFootnotePortion::SetPreferredScriptType( SwFontScript nPreferredScriptType )
+{
+ mbPreferredScriptTypeSet = true;
+ mnPreferredScriptType = nPreferredScriptType;
+}
+
+SwFieldPortion *SwQuoVadisPortion::Clone( const OUString &rExpand ) const
+{
+ return new SwQuoVadisPortion( rExpand, m_aErgo );
+}
+
+SwQuoVadisPortion::SwQuoVadisPortion( const OUString &rExp, OUString aStr )
+ : SwFieldPortion( rExp ), m_aErgo(std::move(aStr))
+{
+ SetLen(TextFrameIndex(0));
+ SetWhichPor( PortionType::QuoVadis );
+}
+
+bool SwQuoVadisPortion::Format( SwTextFormatInfo &rInf )
+{
+ // First try; maybe the Text fits
+ CheckScript( rInf );
+ bool bFull = SwFieldPortion::Format( rInf );
+ SetLen(TextFrameIndex(0));
+
+ if( bFull )
+ {
+ // Second try; we make the String shorter
+ m_aExpand = "...";
+ bFull = SwFieldPortion::Format( rInf );
+ SetLen(TextFrameIndex(0));
+ if( bFull )
+ // Third try; we're done: we crush
+ Width( sal_uInt16(rInf.Width() - rInf.X()) );
+
+ // No multiline Fields for QuoVadis and ErgoSum
+ if( rInf.GetRest() )
+ {
+ delete rInf.GetRest();
+ rInf.SetRest( nullptr );
+ }
+ }
+ return bFull;
+}
+
+bool SwQuoVadisPortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const
+{
+ rText = m_aExpand;
+ // if this QuoVadisPortion has a follow, the follow is responsible for
+ // the ergo text.
+ if ( ! HasFollow() )
+ rText += m_aErgo;
+ return true;
+}
+
+void SwQuoVadisPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), m_aExpand + m_aErgo, GetWhichPor() );
+}
+
+void SwQuoVadisPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // We _always_ want to output per DrawStretchText, because nErgo
+ // can quickly switch
+ if( PrtWidth() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::QuoVadis );
+ SwTextSlot aDiffText( &rInf, this, true, false );
+ SwFontSave aSave( rInf, m_pFont.get() );
+ rInf.DrawText( *this, rInf.GetLen(), true );
+ }
+}
+
+SwFieldPortion *SwErgoSumPortion::Clone( const OUString &rExpand ) const
+{
+ return new SwErgoSumPortion( rExpand, std::u16string_view() );
+}
+
+SwErgoSumPortion::SwErgoSumPortion(const OUString &rExp, std::u16string_view rStr)
+ : SwFieldPortion( rExp )
+{
+ SetLen(TextFrameIndex(0));
+ m_aExpand += rStr;
+
+ // One blank distance to the text
+ m_aExpand += " ";
+ SetWhichPor( PortionType::ErgoSum );
+}
+
+TextFrameIndex SwErgoSumPortion::GetModelPositionForViewPoint(const sal_uInt16) const
+{
+ return TextFrameIndex(0);
+}
+
+bool SwErgoSumPortion::Format( SwTextFormatInfo &rInf )
+{
+ const bool bFull = SwFieldPortion::Format( rInf );
+ SetLen(TextFrameIndex(0));
+ rInf.SetErgoDone( true );
+
+ // No multiline Fields for QuoVadis and ErgoSum
+ if( bFull && rInf.GetRest() )
+ {
+ delete rInf.GetRest();
+ rInf.SetRest( nullptr );
+ }
+
+ // We return false in order to get some text into the current line,
+ // even if it's full (better than looping)
+ return false;
+}
+
+void SwParaPortion::SetErgoSumNum( const OUString& rErgo )
+{
+ SwLineLayout *pLay = this;
+ while( pLay->GetNext() )
+ {
+ pLay = pLay->GetNext();
+ }
+ SwLinePortion *pPor = pLay;
+ SwQuoVadisPortion *pQuo = nullptr;
+ while( pPor && !pQuo )
+ {
+ if ( pPor->IsQuoVadisPortion() )
+ pQuo = static_cast<SwQuoVadisPortion*>(pPor);
+ pPor = pPor->GetNextPortion();
+ }
+ if( pQuo )
+ pQuo->SetNumber( rErgo );
+}
+
+/**
+ * Is called in SwTextFrame::Prepare()
+ */
+bool SwParaPortion::UpdateQuoVadis( std::u16string_view rQuo )
+{
+ SwLineLayout *pLay = this;
+ while( pLay->GetNext() )
+ {
+ pLay = pLay->GetNext();
+ }
+ SwLinePortion *pPor = pLay;
+ SwQuoVadisPortion *pQuo = nullptr;
+ while( pPor && !pQuo )
+ {
+ if ( pPor->IsQuoVadisPortion() )
+ pQuo = static_cast<SwQuoVadisPortion*>(pPor);
+ pPor = pPor->GetNextPortion();
+ }
+
+ if( !pQuo )
+ return false;
+
+ return pQuo->GetQuoText() == rQuo;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txthyph.cxx b/sw/source/core/text/txthyph.cxx
new file mode 100644
index 0000000000..e9f6a9d0e6
--- /dev/null
+++ b/sw/source/core/text/txthyph.cxx
@@ -0,0 +1,582 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <breakit.hxx>
+#include <editeng/unolingu.hxx>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <viewopt.hxx>
+#include <viewsh.hxx>
+#include <SwPortionHandler.hxx>
+#include "porhyph.hxx"
+#include "inftxt.hxx"
+#include "itrform2.hxx"
+#include "guess.hxx"
+#include <rootfrm.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::linguistic2;
+using namespace ::com::sun::star::i18n;
+
+Reference< XHyphenatedWord > SwTextFormatInfo::HyphWord(
+ const OUString &rText, const sal_Int32 nMinTrail )
+{
+ if( rText.getLength() < 4 || m_pFnt->IsSymbol(m_pVsh) )
+ return nullptr;
+ Reference< XHyphenator > xHyph = ::GetHyphenator();
+ Reference< XHyphenatedWord > xHyphWord;
+
+ if( xHyph.is() )
+ xHyphWord = xHyph->hyphenate( rText,
+ g_pBreakIt->GetLocale( m_pFnt->GetLanguage() ),
+ rText.getLength() - nMinTrail, GetHyphValues() );
+ return xHyphWord;
+
+}
+
+/**
+ * We format a row for interactive hyphenation
+ */
+bool SwTextFrame::Hyphenate(SwInterHyphInfoTextFrame & rHyphInf)
+{
+ vcl::RenderContext* pRenderContext = getRootFrame()->GetCurrShell()->GetOut();
+ OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"swapped frame at SwTextFrame::Hyphenate" );
+
+ assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
+
+ // We lock it, to start with
+ OSL_ENSURE( !IsLocked(), "SwTextFrame::Hyphenate: this is locked" );
+
+ // The frame::Frame must have a valid SSize!
+ Calc(pRenderContext);
+ GetFormatted();
+
+ bool bRet = false;
+ if( !IsEmpty() )
+ {
+ // We always need to enable hyphenation
+ // Don't be afraid: the SwTextIter saves the old row in the hyphenate
+ TextFrameLockGuard aLock( this );
+
+ if ( IsVertical() )
+ SwapWidthAndHeight();
+
+ SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this, true ); // true for interactive hyph!
+ SwTextFormatter aLine( this, &aInf );
+ aLine.CharToLine( rHyphInf.m_nStart );
+
+ // If we're within the first word of a row, it could've been hyphenated
+ // in the row earlier.
+ // That's why we go one row back.
+ if( aLine.Prev() )
+ {
+ SwLinePortion *pPor = aLine.GetCurr()->GetFirstPortion();
+ while( pPor->GetNextPortion() )
+ pPor = pPor->GetNextPortion();
+ if( pPor->GetWhichPor() == PortionType::SoftHyphen ||
+ pPor->GetWhichPor() == PortionType::SoftHyphenStr )
+ aLine.Next();
+ }
+
+ const TextFrameIndex nEnd = rHyphInf.m_nEnd;
+ while( !bRet && aLine.GetStart() < nEnd )
+ {
+ bRet = aLine.Hyphenate( rHyphInf );
+ if( !aLine.Next() )
+ break;
+ }
+
+ if ( IsVertical() )
+ SwapWidthAndHeight();
+ }
+ return bRet;
+}
+
+/**
+ * We format a row for interactive hyphenation
+ * We can assume that we've already formatted.
+ * We just reformat the row, the hyphenator will be prepared like
+ * the UI expects it to be.
+ * TODO: We can of course optimize this a lot.
+ */
+void SetParaPortion( SwTextInfo *pInf, SwParaPortion *pRoot )
+{
+ OSL_ENSURE( pRoot, "SetParaPortion: no root anymore" );
+ pInf->m_pPara = pRoot;
+}
+
+bool SwTextFormatter::Hyphenate(SwInterHyphInfoTextFrame & rHyphInf)
+{
+ SwTextFormatInfo &rInf = GetInfo();
+
+ // We never need to hyphenate anything in the last row
+ // Except for, if it contains a FlyPortion or if it's the
+ // last row of the Master
+ if( !GetNext() && !rInf.GetTextFly().IsOn() && !m_pFrame->GetFollow() )
+ return false;
+
+ TextFrameIndex nWrdStart = m_nStart;
+
+ // We need to retain the old row
+ // E.g.: The attribute for hyphenation was not set, but
+ // it's always set in SwTextFrame::Hyphenate, because we want
+ // to set breakpoints.
+ SwLineLayout *pOldCurr = m_pCurr;
+
+ InitCntHyph();
+
+ // 5298: IsParaLine() (ex.IsFirstLine) calls GetParaPortion().
+ // We have to create the same conditions: in the first line
+ // we format SwParaPortions...
+ if( pOldCurr->IsParaPortion() )
+ {
+ SwParaPortion *pPara = new SwParaPortion();
+ SetParaPortion( &rInf, pPara );
+ m_pCurr = pPara;
+ OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: not the first" );
+ }
+ else
+ m_pCurr = new SwLineLayout();
+
+ nWrdStart = FormatLine( nWrdStart );
+
+ // You always should keep in mind that for example there are fields
+ // which can be hyphenated
+ if( m_pCurr->PrtWidth() && m_pCurr->GetLen() )
+ {
+ // We must be prepared that there are FlyFrames in the line,
+ // at which line breaking is possible. So we search for the first
+ // HyphPortion in the specified range.
+
+ SwLinePortion *pPos = m_pCurr->GetNextPortion();
+ const TextFrameIndex nPamStart = rHyphInf.m_nStart;
+ nWrdStart = m_nStart;
+ const TextFrameIndex nEnd = rHyphInf.m_nEnd;
+ while( pPos )
+ {
+ // Either we are above or we are running into a HyphPortion
+ // at the end of line or before a Fly.
+ if( nWrdStart >= nEnd )
+ {
+ nWrdStart = TextFrameIndex(0);
+ break;
+ }
+
+ if( nWrdStart >= nPamStart && pPos->InHyphGrp()
+ && ( !pPos->IsSoftHyphPortion()
+ || static_cast<SwSoftHyphPortion*>(pPos)->IsExpand() ) )
+ {
+ nWrdStart = nWrdStart + pPos->GetLen();
+ break;
+ }
+
+ nWrdStart = nWrdStart + pPos->GetLen();
+ pPos = pPos->GetNextPortion();
+ }
+ // When pPos is null, no hyphen position was found.
+ if( !pPos )
+ nWrdStart = TextFrameIndex(0);
+ }
+ else
+ // In case the whole line is zero-length, that's the same situation as
+ // above when the portion iteration ends without explicitly breaking
+ // from the loop.
+ nWrdStart = TextFrameIndex(0);
+
+ // the old LineLayout is set again ...
+ delete m_pCurr;
+ m_pCurr = pOldCurr;
+
+ if( pOldCurr->IsParaPortion() )
+ {
+ SetParaPortion( &rInf, static_cast<SwParaPortion*>(pOldCurr) );
+ OSL_ENSURE( IsParaLine(), "SwTextFormatter::Hyphenate: even not the first" );
+ }
+
+ if (nWrdStart == TextFrameIndex(0))
+ return false;
+
+ // nWrdStart contains the position in string that should be hyphenated
+ rHyphInf.m_nWordStart = nWrdStart;
+
+ TextFrameIndex nLen(0);
+ const TextFrameIndex nEnd = nWrdStart;
+
+ // we search forwards
+ Reference< XHyphenatedWord > xHyphWord;
+
+ Boundary const aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
+ rInf.GetText(), sal_Int32(nWrdStart),
+ g_pBreakIt->GetLocale( rInf.GetFont()->GetLanguage() ), WordType::DICTIONARY_WORD, true );
+ nWrdStart = TextFrameIndex(aBound.startPos);
+ nLen = TextFrameIndex(aBound.endPos) - nWrdStart;
+ if (nLen == TextFrameIndex(0))
+ return false;
+
+ OUString const aSelText(rInf.GetText().copy(sal_Int32(nWrdStart), sal_Int32(nLen)));
+ const sal_Int32 nMinTrail = (nWrdStart + nLen > nEnd)
+ ? sal_Int32(nWrdStart + nLen - nEnd) - 1
+ : 0;
+
+ //!! rHyphInf.SetHyphWord( ... ) must done here
+ xHyphWord = rInf.HyphWord( aSelText, nMinTrail );
+ if ( xHyphWord.is() )
+ {
+ rHyphInf.SetHyphWord( xHyphWord );
+ rHyphInf.m_nWordStart = nWrdStart;
+ rHyphInf.m_nWordLen = nLen;
+ return true;
+ }
+
+ return false;
+}
+
+bool SwTextPortion::CreateHyphen( SwTextFormatInfo &rInf, SwTextGuess const &rGuess )
+{
+ const Reference< XHyphenatedWord >& xHyphWord = rGuess.HyphWord();
+
+ OSL_ENSURE( !mpNextPortion, "SwTextPortion::CreateHyphen(): another portion, another planet..." );
+ OSL_ENSURE( xHyphWord.is(), "SwTextPortion::CreateHyphen(): You are lucky! The code is robust here." );
+
+ if( rInf.IsHyphForbud() ||
+ mpNextPortion || // robust
+ !xHyphWord.is() || // more robust
+ // multi-line fields can't be hyphenated interactively
+ ( rInf.IsInterHyph() && InFieldGrp() ) )
+ return false;
+
+ std::unique_ptr<SwHyphPortion> pHyphPor;
+ TextFrameIndex nPorEnd;
+ SwTextSizeInfo aInf( rInf );
+
+ // first case: hyphenated word has alternative spelling
+ if ( xHyphWord->isAlternativeSpelling() )
+ {
+ SvxAlternativeSpelling aAltSpell = SvxGetAltSpelling( xHyphWord );
+ OSL_ENSURE( aAltSpell.bIsAltSpelling, "no alternative spelling" );
+
+ OUString aAltText = aAltSpell.aReplacement;
+ nPorEnd = TextFrameIndex(aAltSpell.nChangedPos) + rGuess.BreakStart() - rGuess.FieldDiff();
+ sal_Int32 nTmpLen = 0;
+
+ // soft hyphen at alternative spelling position?
+ if( rInf.GetText()[sal_Int32(rInf.GetSoftHyphPos())] == CHAR_SOFTHYPHEN )
+ {
+ pHyphPor.reset(new SwSoftHyphStrPortion( aAltText ));
+ nTmpLen = 1;
+ }
+ else {
+ pHyphPor.reset(new SwHyphStrPortion( aAltText ));
+ }
+
+ // length of pHyphPor is adjusted
+ pHyphPor->SetLen( TextFrameIndex(aAltText.getLength() + 1) );
+ static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf );
+ pHyphPor->SetLen( TextFrameIndex(aAltSpell.nChangedLength + nTmpLen) );
+ }
+ else
+ {
+ // second case: no alternative spelling
+ pHyphPor.reset(new SwHyphPortion);
+ pHyphPor->SetLen(TextFrameIndex(1));
+
+ static const void* nLastFontCacheId = nullptr;
+ static sal_uInt16 aMiniCacheH = 0, aMiniCacheW = 0;
+ const void* nTmpFontCacheId;
+ sal_uInt16 nFntIdx;
+ rInf.GetFont()->GetFontCacheId( nTmpFontCacheId, nFntIdx, rInf.GetFont()->GetActual() );
+ if( !nLastFontCacheId || nLastFontCacheId != nTmpFontCacheId ) {
+ nLastFontCacheId = nTmpFontCacheId;
+ static_cast<SwPosSize&>(*pHyphPor) = pHyphPor->GetTextSize( rInf );
+ aMiniCacheH = pHyphPor->Height();
+ aMiniCacheW = pHyphPor->Width();
+ } else {
+ pHyphPor->Height( aMiniCacheH );
+ pHyphPor->Width( aMiniCacheW );
+ }
+ pHyphPor->SetLen(TextFrameIndex(0));
+
+ // values required for this
+ nPorEnd = TextFrameIndex(xHyphWord->getHyphenPos() + 1)
+ + rGuess.BreakStart() - rGuess.FieldDiff();
+ }
+
+ // portion end must be in front of us
+ // we do not put hyphens at start of line
+ if ( nPorEnd > rInf.GetIdx() ||
+ ( nPorEnd == rInf.GetIdx() && rInf.GetLineStart() != rInf.GetIdx() ) )
+ {
+ aInf.SetLen( nPorEnd - rInf.GetIdx() );
+ pHyphPor->SetAscent( GetAscent() );
+ SetLen( aInf.GetLen() );
+ CalcTextSize( aInf );
+
+ Insert( pHyphPor.release() );
+
+ short nKern = rInf.GetFont()->CheckKerning();
+ if( nKern )
+ new SwKernPortion( *this, nKern );
+
+ return true;
+ }
+
+ // last exit for the lost
+ pHyphPor.reset();
+ BreakCut( rInf, rGuess );
+ return false;
+}
+
+bool SwHyphPortion::GetExpText( const SwTextSizeInfo &/*rInf*/, OUString &rText ) const
+{
+ rText = "-";
+ return true;
+}
+
+void SwHyphPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), OUString('-'), GetWhichPor() );
+}
+
+void SwHyphPortion::dumpAsXml(xmlTextWriterPtr pWriter, const OUString& rText,
+ TextFrameIndex& nOffset) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHyphPortion"));
+ dumpAsXmlAttributes(pWriter, rText, nOffset);
+ nOffset += GetLen();
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+bool SwHyphPortion::Format( SwTextFormatInfo &rInf )
+{
+ const SwLinePortion *pLast = rInf.GetLast();
+ Height( pLast->Height() );
+ SetAscent( pLast->GetAscent() );
+ OUString aText;
+
+ if( !GetExpText( rInf, aText ) )
+ return false;
+
+ PrtWidth( rInf.GetTextSize( aText ).Width() );
+ const bool bFull = rInf.Width() <= rInf.X() + PrtWidth();
+ if( bFull && !rInf.IsUnderflow() ) {
+ Truncate();
+ rInf.SetUnderflow( this );
+ }
+
+ return bFull;
+}
+
+bool SwHyphStrPortion::GetExpText( const SwTextSizeInfo &, OUString &rText ) const
+{
+ rText = m_aExpand;
+ return true;
+}
+
+void SwHyphStrPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Special( GetLen(), m_aExpand, GetWhichPor() );
+}
+
+SwLinePortion *SwSoftHyphPortion::Compress() { return this; }
+
+SwSoftHyphPortion::SwSoftHyphPortion() :
+ m_bExpand(false), m_nViewWidth(0)
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::SoftHyphen );
+}
+
+sal_uInt16 SwSoftHyphPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const
+{
+ // Although we're in the const, nViewWidth should be calculated at
+ // the last possible moment
+ if( !Width() && rInf.OnWin() && rInf.GetOpt().IsSoftHyph() && !IsExpand() )
+ {
+ if( !m_nViewWidth )
+ const_cast<SwSoftHyphPortion*>(this)->m_nViewWidth
+ = rInf.GetTextSize(OUString('-')).Width();
+ }
+ else
+ const_cast<SwSoftHyphPortion*>(this)->m_nViewWidth = 0;
+ return m_nViewWidth;
+}
+
+/**
+ * Cases:
+ *
+ * 1) SoftHyph is in the line, ViewOpt off
+ * -> invisible, neighbors unchanged
+ * 2) SoftHyph is in the line, ViewOpt on
+ * -> visible, neighbors unchanged
+ * 3) SoftHyph is at the end of the line, ViewOpt on or off
+ * -> always visible, neighbors unchanged
+ */
+void SwSoftHyphPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ if( Width() )
+ {
+ rInf.DrawViewOpt( *this, PortionType::SoftHyphen );
+ SwExpandPortion::Paint( rInf );
+ }
+}
+
+/**
+ * We get the final width from the FormatEOL()
+ *
+ * During the underflow-phase we determine, whether or not
+ * there's an alternative spelling at all ...
+ *
+ * Case 1: "Au-to"
+ * 1) {Au}{-}{to}, {to} does not fit anymore => underflow
+ * 2) {-} calls hyphenate => no alternative
+ * 3) FormatEOL() and bFull = true
+ *
+ * Case 2: "Zuc-ker"
+ * 1) {Zuc}{-}{ker}, {ker} does not fit anymore => underflow
+ * 2) {-} calls hyphenate => alternative!
+ * 3) Underflow() and bFull = true
+ * 4) {Zuc} calls hyphenate => {Zuk}{-}{ker}
+ */
+bool SwSoftHyphPortion::Format( SwTextFormatInfo &rInf )
+{
+ bool bFull = true;
+
+ // special case for old German spelling
+ if( rInf.IsUnderflow() )
+ {
+ if( rInf.GetSoftHyphPos() )
+ return true;
+
+ const bool bHyph = rInf.ChgHyph( true );
+ if( rInf.IsHyphenate() )
+ {
+ rInf.SetSoftHyphPos( rInf.GetIdx() );
+ Width(0);
+ // if the soft hyphened word has an alternative spelling
+ // when hyphenated (old German spelling), the soft hyphen
+ // portion has to trigger an underflow
+ SwTextGuess aGuess;
+ bFull = rInf.IsInterHyph() ||
+ !aGuess.AlternativeSpelling(rInf, rInf.GetIdx() - TextFrameIndex(1));
+ }
+ rInf.ChgHyph( bHyph );
+
+ if( bFull && !rInf.IsHyphForbud() )
+ {
+ rInf.SetSoftHyphPos(TextFrameIndex(0));
+ FormatEOL( rInf );
+ if ( rInf.GetFly() )
+ rInf.GetRoot()->SetMidHyph( true );
+ else
+ rInf.GetRoot()->SetEndHyph( true );
+ }
+ else
+ {
+ rInf.SetSoftHyphPos( rInf.GetIdx() );
+ Truncate();
+ rInf.SetUnderflow( this );
+ }
+ return true;
+ }
+
+ rInf.SetSoftHyphPos(TextFrameIndex(0));
+ SetExpand( true );
+ bFull = SwHyphPortion::Format( rInf );
+ SetExpand( false );
+ if( !bFull )
+ {
+ // By default, we do not have a width, but we do have a height
+ Width(0);
+ }
+ return bFull;
+}
+
+/**
+ * Format End of Line
+ */
+void SwSoftHyphPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ if( IsExpand() )
+ return;
+
+ SetExpand( true );
+ if( rInf.GetLast() == this )
+ rInf.SetLast( FindPrevPortion( rInf.GetRoot() ) );
+
+ // We need to reset the old values
+ const SwTwips nOldX = rInf.X();
+ TextFrameIndex const nOldIdx = rInf.GetIdx();
+ rInf.X( rInf.X() - PrtWidth() );
+ rInf.SetIdx( rInf.GetIdx() - GetLen() );
+ const bool bFull = SwHyphPortion::Format( rInf );
+
+ // Shady business: We're allowed to get wider, but a Fly is also
+ // being processed, which needs a correct X position
+ if( bFull || !rInf.GetFly() )
+ rInf.X( nOldX );
+ else
+ rInf.X( nOldX + Width() );
+ rInf.SetIdx( nOldIdx );
+}
+
+/**
+ * We're expanding:
+ * - if the special characters should be visible
+ * - if we're at the end of the line
+ * - if we're before a (real/emulated) line break
+ */
+bool SwSoftHyphPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const
+{
+ if( IsExpand() || ( rInf.OnWin() && rInf.GetOpt().IsSoftHyph() ) ||
+ ( GetNextPortion() && ( GetNextPortion()->InFixGrp() ||
+ GetNextPortion()->IsDropPortion() || GetNextPortion()->IsLayPortion() ||
+ GetNextPortion()->IsParaPortion() || GetNextPortion()->IsBreakPortion() ) ) )
+ {
+ return SwHyphPortion::GetExpText( rInf, rText );
+ }
+ return false;
+}
+
+void SwSoftHyphPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ const PortionType nWhich = ! Width() ?
+ PortionType::SoftHyphenComp :
+ GetWhichPor();
+ rPH.Special( GetLen(), OUString('-'), nWhich );
+}
+
+void SwSoftHyphStrPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // Bug or feature?:
+ // {Zu}{k-}{ker}, {k-} will be gray instead of {-}
+ rInf.DrawViewOpt( *this, PortionType::SoftHyphen );
+ SwHyphStrPortion::Paint( rInf );
+}
+
+SwSoftHyphStrPortion::SwSoftHyphStrPortion( std::u16string_view rStr )
+ : SwHyphStrPortion( rStr )
+{
+ SetLen(TextFrameIndex(1));
+ SetWhichPor( PortionType::SoftHyphenStr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtinit.cxx b/sw/source/core/text/txtinit.cxx
new file mode 100644
index 0000000000..2b8bb2e95d
--- /dev/null
+++ b/sw/source/core/text/txtinit.cxx
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <swcache.hxx>
+#include <fntcache.hxx>
+#include <swfntcch.hxx>
+#include <txtfrm.hxx>
+#include "pordrop.hxx"
+#include <init.hxx>
+#include <txtfly.hxx>
+#include <dbg_lay.hxx>
+
+SwCache *SwTextFrame::s_pTextCache = nullptr;
+SwContourCache *pContourCache = nullptr;
+SwDropCapCache *pDropCapCache = nullptr;
+
+// Are ONLY used in init.cxx.
+// There we have extern void TextFinit()
+// and extern void TextInit_(...)
+
+void TextInit_()
+{
+ pFntCache = new SwFntCache; // Cache for SwSubFont -> SwFntObj = { Font aFont, Font* pScrFont, Font* pPrtFont, OutputDevice* pPrinter, ... }
+ pSwFontCache = new SwFontCache; // Cache for SwTextFormatColl -> SwFontObj = { SwFont aSwFont, SfxPoolItem* pDefaultArray }
+ SwCache *pTextCache = new SwCache( 250 // Cache for SwTextFrame -> SwTextLine = { SwParaPortion* pLine }
+#ifdef DBG_UTIL
+ , "static SwTextFrame::s_pTextCache"_ostr
+#endif
+ );
+ SwTextFrame::SetTextCache( pTextCache );
+ PROTOCOL_INIT
+}
+
+void TextFinit()
+{
+ PROTOCOL_STOP
+ delete SwTextFrame::GetTextCache();
+ delete pSwFontCache;
+ delete pFntCache;
+ delete pContourCache;
+ SwDropPortion::DeleteDropCapCache();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtpaint.cxx b/sw/source/core/text/txtpaint.cxx
new file mode 100644
index 0000000000..ccd9647bd9
--- /dev/null
+++ b/sw/source/core/text/txtpaint.cxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "txtpaint.hxx"
+#include <txtfrm.hxx>
+#include <swrect.hxx>
+#include <rootfrm.hxx>
+
+SwSaveClip::~SwSaveClip()
+{
+ // We recover the old state
+ if( !(m_pOut && m_bChg) )
+ return;
+
+ if ( m_pOut->GetConnectMetaFile() )
+ m_pOut->Pop();
+ else
+ {
+ if( m_bOn )
+ m_pOut->SetClipRegion( m_aClip );
+ else
+ m_pOut->SetClipRegion();
+ }
+ m_bChg = false;
+}
+
+void SwSaveClip::ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame,
+ bool bEnlargeRect,
+ sal_Int32 nEnlargeTop,
+ sal_Int32 nEnlargeBottom )
+{
+ SwRect aOldRect( rRect );
+ const bool bVertical = pFrame && pFrame->IsVertical();
+
+ if ( pFrame && pFrame->IsRightToLeft() )
+ pFrame->SwitchLTRtoRTL( const_cast<SwRect&>(rRect) );
+
+ if ( bVertical )
+ pFrame->SwitchHorizontalToVertical( const_cast<SwRect&>(rRect) );
+
+ if ( !m_pOut || (!rRect.HasArea() && !m_pOut->IsClipRegion()) )
+ {
+ const_cast<SwRect&>(rRect) = aOldRect;
+ return;
+ }
+
+ if ( !m_bChg )
+ {
+ if ( m_pOut->GetConnectMetaFile() )
+ m_pOut->Push();
+ else if ( m_bOn )
+ m_aClip = m_pOut->GetClipRegion();
+ }
+
+ if ( !rRect.HasArea() )
+ m_pOut->SetClipRegion();
+ else
+ {
+ tools::Rectangle aRect( rRect.SVRect() );
+
+ // Having underscores in our line, we enlarged the repaint area
+ // (see frmform.cxx) because for some fonts it could be too small.
+ // Consequently, we have to enlarge the clipping rectangle as well.
+ if ( bEnlargeRect && ! bVertical )
+ aRect.AdjustBottom(40 );
+
+ // enlarge clip for paragraph margins at small fixed line height
+ if ( nEnlargeTop > 0 )
+ aRect.AdjustTop( -nEnlargeTop );
+
+ if ( nEnlargeBottom > 0 )
+ aRect.AdjustBottom( nEnlargeBottom );
+
+ // If the ClipRect is identical, nothing will happen
+ if( m_pOut->IsClipRegion() ) // no && because of Mac
+ {
+ if ( aRect == m_pOut->GetClipRegion().GetBoundRect() )
+ {
+ const_cast<SwRect&>(rRect) = aOldRect;
+ return;
+ }
+ }
+
+ if( SwRootFrame::HasSameRect( rRect ) )
+ m_pOut->SetClipRegion();
+ else
+ {
+ const vcl::Region aClipRegion( aRect );
+ m_pOut->SetClipRegion( aClipRegion );
+ }
+ }
+ m_bChg = true;
+
+ const_cast<SwRect&>(rRect) = aOldRect;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txtpaint.hxx b/sw/source/core/text/txtpaint.hxx
new file mode 100644
index 0000000000..1f9700af4c
--- /dev/null
+++ b/sw/source/core/text/txtpaint.hxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/outdev.hxx>
+
+class SwRect; // SwSaveClip
+class SwTextFrame;
+
+class SwSaveClip final
+{
+ vcl::Region m_aClip;
+ const bool m_bOn;
+ bool m_bChg;
+
+ VclPtr<OutputDevice> m_pOut;
+ void ChgClip_( const SwRect &rRect, const SwTextFrame* pFrame,
+ bool bEnlargeRect,
+ sal_Int32 nEnlargeTop,
+ sal_Int32 nEnlargeBottom );
+public:
+ explicit SwSaveClip(OutputDevice* pOutDev)
+ : m_bOn(pOutDev && pOutDev->IsClipRegion())
+ , m_bChg(false)
+ , m_pOut(pOutDev)
+ {
+ }
+
+ ~SwSaveClip();
+ void ChgClip( const SwRect &rRect, const SwTextFrame* pFrame = nullptr,
+ bool bEnlargeRect = false,
+ sal_Int32 nEnlargeTop = 0,
+ sal_Int32 nEnlargeBottom = 0)
+ { if( m_pOut ) ChgClip_( rRect, pFrame,
+ bEnlargeRect, nEnlargeTop, nEnlargeBottom ); }
+ bool IsOn() const { return m_bOn; }
+ bool IsChg() const { return m_bChg; }
+};
+
+
+#ifdef DBG_UTIL
+
+class SwDbgOut
+{
+protected:
+ VclPtr<OutputDevice> pOut;
+public:
+ inline SwDbgOut( OutputDevice* pOutDev, const bool bOn );
+};
+
+class DbgBackColor : public SwDbgOut
+{
+ Color aOldFillColor;
+public:
+ DbgBackColor( OutputDevice* pOut, const bool bOn );
+ ~DbgBackColor();
+};
+
+class DbgRect : public SwDbgOut
+{
+public:
+ DbgRect( OutputDevice* pOut, const tools::Rectangle &rRect,
+ const bool bOn,
+ Color eColor );
+};
+
+inline SwDbgOut::SwDbgOut( OutputDevice* pOutDev, const bool bOn )
+ :pOut( bOn ? pOutDev : nullptr )
+{ }
+
+inline DbgBackColor::DbgBackColor( OutputDevice* pOutDev, const bool bOn )
+ :SwDbgOut( pOutDev, bOn )
+{
+ if( pOut )
+ {
+ aOldFillColor = pOut->GetFillColor();
+ pOut->SetFillColor( COL_RED );
+ }
+}
+
+inline DbgBackColor::~DbgBackColor()
+{
+ if( pOut )
+ {
+ pOut->SetFillColor( aOldFillColor );
+ }
+}
+
+inline DbgRect::DbgRect( OutputDevice* pOutDev, const tools::Rectangle &rRect,
+ const bool bOn,
+ Color eColor )
+ : SwDbgOut( pOutDev, bOn )
+{
+ if( pOut )
+ {
+ const Color aColor( eColor );
+ Color aLineColor = pOut->GetLineColor();
+ pOut->SetLineColor( aColor );
+ Color aFillColor = pOut->GetFillColor();
+ pOut->SetFillColor( COL_TRANSPARENT );
+ pOut->DrawRect( rRect );
+ pOut->SetLineColor( aLineColor );
+ pOut->SetFillColor( aFillColor );
+ }
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/txttab.cxx b/sw/source/core/text/txttab.cxx
new file mode 100644
index 0000000000..e7141aaec5
--- /dev/null
+++ b/sw/source/core/text/txttab.cxx
@@ -0,0 +1,663 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <hintids.hxx>
+#include <comphelper/string.hxx>
+#include <editeng/tstpitem.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <doc.hxx>
+#include <SwPortionHandler.hxx>
+
+#include <viewopt.hxx>
+#include "portab.hxx"
+#include "inftxt.hxx"
+#include "itrform2.hxx"
+#include <txtfrm.hxx>
+#include "porfld.hxx"
+#include <memory>
+
+/**
+ * #i24363# tab stops relative to indent
+ *
+ * Return the first tab stop that is > nSearchPos.
+ * If the tab stop is outside the print area, we
+ * return 0 if it is not the first tab stop.
+ */
+const SvxTabStop* SwLineInfo::GetTabStop(const SwTwips nSearchPos, SwTwips& nRight) const
+{
+ for( sal_uInt16 i = 0; i < m_oRuler->Count(); ++i )
+ {
+ const SvxTabStop &rTabStop = m_oRuler->operator[](i);
+ if (nRight && rTabStop.GetTabPos() > nRight)
+ {
+ // Consider the first tabstop to always be in-bounds.
+ if (!i)
+ nRight = rTabStop.GetTabPos();
+ return i ? nullptr : &rTabStop;
+ }
+ if( rTabStop.GetTabPos() > nSearchPos )
+ {
+ if (!i && !nRight)
+ nRight = rTabStop.GetTabPos();
+ return &rTabStop;
+ }
+ }
+ return nullptr;
+}
+
+sal_uInt16 SwLineInfo::NumberOfTabStops() const
+{
+ return m_oRuler->Count();
+}
+
+SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto ) const
+{
+ IDocumentSettingAccess const& rIDSA(rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess());
+ const bool bTabOverSpacing = rIDSA.get(DocumentSettingId::TAB_OVER_SPACING);
+ const bool bTabsRelativeToIndent = rIDSA.get(DocumentSettingId::TABS_RELATIVE_TO_INDENT);
+
+ // Update search location - since Center/Decimal tabstops' width is dependent on the following text.
+ SwTabPortion* pTmpLastTab = rInf.GetLastTab();
+ if (pTmpLastTab && (pTmpLastTab->IsTabCenterPortion() || pTmpLastTab->IsTabDecimalPortion()))
+ pTmpLastTab->PostFormat(rInf);
+
+ sal_Unicode cFill = 0;
+ sal_Unicode cDec = 0;
+ SvxTabAdjust eAdj;
+
+ sal_uInt16 nNewTabPos;
+ bool bAutoTabStop = true;
+ {
+ const bool bRTL = m_pFrame->IsRightToLeft();
+ // #i24363# tab stops relative to indent
+ // nTabLeft: The absolute value, the tab stops are relative to: Tabs origin.
+
+ // #i91133#
+ const SwTwips nTabLeft = bRTL
+ ? m_pFrame->getFrameArea().Right() -
+ ( bTabsRelativeToIndent ? GetTabLeft() : 0 )
+ : m_pFrame->getFrameArea().Left() +
+ ( bTabsRelativeToIndent ? GetTabLeft() : 0 );
+
+ // The absolute position, where we started the line formatting
+ SwTwips nLinePos = GetLeftMargin();
+ if ( bRTL )
+ {
+ Point aPoint( nLinePos, 0 );
+ m_pFrame->SwitchLTRtoRTL( aPoint );
+ nLinePos = aPoint.X();
+ }
+
+ // The current position, relative to the line start
+ SwTwips nTabPos = rInf.GetLastTab() ? rInf.GetLastTab()->GetTabPos() : 0;
+ if( nTabPos < rInf.X() )
+ {
+ nTabPos = rInf.X();
+ }
+
+ // The current position in absolute coordinates
+ const SwTwips nCurrentAbsPos = bRTL ?
+ nLinePos - nTabPos :
+ nLinePos + nTabPos;
+
+ SwTwips nMyRight;
+ if ( m_pFrame->IsVertLR() )
+ nMyRight = Left();
+ else
+ nMyRight = Right();
+
+ if ( m_pFrame->IsVertical() )
+ {
+ Point aRightTop( nMyRight, m_pFrame->getFrameArea().Top() );
+ m_pFrame->SwitchHorizontalToVertical( aRightTop );
+ nMyRight = aRightTop.Y();
+ }
+
+ SwTwips nNextPos = 0;
+ bool bAbsoluteNextPos = false;
+
+ // #i24363# tab stops relative to indent
+ // nSearchPos: The current position relative to the tabs origin
+ const SwTwips nSearchPos = bRTL ?
+ nTabLeft - nCurrentAbsPos :
+ nCurrentAbsPos - nTabLeft;
+
+ // First, we examine the tab stops set at the paragraph style or
+ // any hard set tab stops:
+ // Note: If there are no user defined tab stops, there is always a
+ // default tab stop.
+ const SwTwips nOldRight = nMyRight;
+ // Accept left-tabstops beyond the paragraph margin for bTabOverSpacing
+ if (bTabOverSpacing)
+ nMyRight = 0;
+ const SvxTabStop* pTabStop = m_aLineInf.GetTabStop( nSearchPos, nMyRight );
+ if (!nMyRight)
+ nMyRight = nOldRight;
+ if (pTabStop)
+ {
+ cFill = ' ' != pTabStop->GetFill() ? pTabStop->GetFill() : 0;
+ cDec = pTabStop->GetDecimal();
+ eAdj = pTabStop->GetAdjustment();
+ nNextPos = pTabStop->GetTabPos();
+ if(!bTabsRelativeToIndent && eAdj == SvxTabAdjust::Default && nSearchPos < 0)
+ {
+ //calculate default tab position of default tabs in negative indent
+ nNextPos = ( nSearchPos / nNextPos ) * nNextPos;
+ }
+ else if (pTabStop->GetTabPos() > nMyRight
+ && pTabStop->GetAdjustment() != SvxTabAdjust::Left)
+ {
+ // A rather special situation. The tabstop found is:
+ // 1.) in a document compatible with MS formats
+ // 2.) not a left tabstop.
+ // 3.) not the first tabstop (in that case nMyRight was adjusted to match tabPos).
+ // 4.) beyond the end of the text area
+ // Therefore, they act like right-tabstops at the edge of the para area.
+ // This benefits DOCX 2013+, and doesn't hurt the earlier formats,
+ // since up till now these were just treated as automatic tabstops.
+ eAdj = SvxTabAdjust::Right;
+ bAbsoluteNextPos = true;
+ nNextPos = rInf.Width();
+ }
+ bAutoTabStop = false;
+ }
+ else
+ {
+ sal_uInt16 nDefTabDist = m_aLineInf.GetDefTabStop();
+ if( USHRT_MAX == nDefTabDist )
+ {
+ const SvxTabStopItem& rTab =
+ m_pFrame->GetAttrSet()->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP );
+ if( rTab.Count() )
+ nDefTabDist = o3tl::narrowing<sal_uInt16>(rTab[0].GetTabPos());
+ else
+ nDefTabDist = SVX_TAB_DEFDIST;
+ m_aLineInf.SetDefTabStop( nDefTabDist );
+ }
+ SwTwips nCount = nSearchPos;
+
+ // Minimum tab stop width is 1
+ if (nDefTabDist <= 0)
+ nDefTabDist = 1;
+
+ nCount /= nDefTabDist;
+ nNextPos = ( nCount < 0 || ( !nCount && nSearchPos <= 0 ) )
+ ? ( nCount * nDefTabDist )
+ : ( ( nCount + 1 ) * nDefTabDist );
+
+ // --> FME 2004-09-21 #117919 Minimum tab stop width is 1 or 51 twips:
+ const SwTwips nMinimumTabWidth = m_pFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) ? 0 : 50;
+ if( ( bRTL && nTabLeft - nNextPos >= nCurrentAbsPos - nMinimumTabWidth ) ||
+ ( !bRTL && nNextPos + nTabLeft <= nCurrentAbsPos + nMinimumTabWidth ) )
+ {
+ nNextPos += nDefTabDist;
+ }
+ cFill = 0;
+ eAdj = SvxTabAdjust::Left;
+ }
+
+ // #i115705# - correction and refactoring:
+ // overrule determined next tab stop position in order to apply
+ // a tab stop at the left margin under the following conditions:
+ // - the new tab portion is inside the hanging indent
+ // - a tab stop at the left margin is allowed
+ // - the determined next tab stop is a default tab stop position OR
+ // the determined next tab stop is beyond the left margin
+ {
+ tools::Long nLeftMarginTabPos = 0;
+ {
+ if ( !bTabsRelativeToIndent )
+ {
+ if ( bRTL )
+ {
+ Point aPoint( Left(), 0 );
+ m_pFrame->SwitchLTRtoRTL( aPoint );
+ nLeftMarginTabPos = m_pFrame->getFrameArea().Right() - aPoint.X();
+ }
+ else
+ {
+ nLeftMarginTabPos = Left() - m_pFrame->getFrameArea().Left();
+ }
+ }
+ if( m_pCurr->HasForcedLeftMargin() )
+ {
+ SwLinePortion* pPor = m_pCurr->GetNextPortion();
+ while( pPor && !pPor->IsFlyPortion() )
+ {
+ pPor = pPor->GetNextPortion();
+ }
+ if ( pPor )
+ {
+ nLeftMarginTabPos += pPor->Width();
+ }
+ }
+ }
+ const bool bNewTabPortionInsideHangingIndent =
+ bRTL ? nCurrentAbsPos > nTabLeft - nLeftMarginTabPos
+ : nCurrentAbsPos < nTabLeft + nLeftMarginTabPos;
+ if ( bNewTabPortionInsideHangingIndent )
+ {
+ // If the paragraph is not inside a list having a list tab stop following
+ // the list label or no further tab stop found in such a paragraph or
+ // the next tab stop position does not equal the list tab stop,
+ // a tab stop at the left margin can be applied. If this condition is
+ // not hold, it is overruled by compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST.
+ const bool bTabAtLeftMarginAllowed =
+ ( !m_aLineInf.IsListTabStopIncluded() ||
+ !pTabStop ||
+ nNextPos != m_aLineInf.GetListTabStopPosition() ) ||
+ // compatibility option TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST:
+ m_pFrame->GetDoc().getIDocumentSettingAccess().
+ get(DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST);
+ if ( bTabAtLeftMarginAllowed )
+ {
+ if ( !pTabStop || eAdj == SvxTabAdjust::Default ||
+ ( nNextPos > nLeftMarginTabPos ) )
+ {
+ eAdj = SvxTabAdjust::Default;
+ cFill = 0;
+ nNextPos = nLeftMarginTabPos;
+ }
+ }
+ }
+ }
+
+ if (!bAbsoluteNextPos)
+ nNextPos += bRTL ? nLinePos - nTabLeft : nTabLeft - nLinePos;
+ OSL_ENSURE( nNextPos >= 0, "GetTabStop: Don't go back!" );
+ nNewTabPos = sal_uInt16(nNextPos);
+ }
+
+ SwTabPortion *pTabPor = nullptr;
+ if ( bAuto )
+ {
+ if ( SvxTabAdjust::Decimal == eAdj &&
+ 1 == m_aLineInf.NumberOfTabStops() )
+ pTabPor = new SwAutoTabDecimalPortion( nNewTabPos, cDec, cFill );
+ }
+ else
+ {
+ switch( eAdj )
+ {
+ case SvxTabAdjust::Right :
+ {
+ pTabPor = new SwTabRightPortion( nNewTabPos, cFill );
+ break;
+ }
+ case SvxTabAdjust::Center :
+ {
+ pTabPor = new SwTabCenterPortion( nNewTabPos, cFill );
+ break;
+ }
+ case SvxTabAdjust::Decimal :
+ {
+ pTabPor = new SwTabDecimalPortion( nNewTabPos, cDec, cFill );
+ break;
+ }
+ default:
+ {
+ OSL_ENSURE( SvxTabAdjust::Left == eAdj || SvxTabAdjust::Default == eAdj,
+ "+SwTextFormatter::NewTabPortion: unknown adjustment" );
+ pTabPor = new SwTabLeftPortion( nNewTabPos, cFill, bAutoTabStop );
+ break;
+ }
+ }
+ }
+ if (pTabPor)
+ rInf.UpdateTabSeen(pTabPor->GetWhichPor());
+
+ return pTabPor;
+}
+
+/**
+ * The base class is initialized without setting anything
+ */
+SwTabPortion::SwTabPortion( const sal_uInt16 nTabPosition, const sal_Unicode cFillChar, const bool bAutoTab )
+ : m_nTabPos(nTabPosition), m_cFill(cFillChar), m_bAutoTabStop( bAutoTab )
+{
+ mnLineLength = TextFrameIndex(1);
+ OSL_ENSURE(!IsFilled() || ' ' != m_cFill, "SwTabPortion::CTOR: blanks ?!");
+ SetWhichPor( PortionType::Tab );
+}
+
+bool SwTabPortion::Format( SwTextFormatInfo &rInf )
+{
+ SwTabPortion *pLastTab = rInf.GetLastTab();
+ if( pLastTab == this )
+ return PostFormat( rInf );
+ if( pLastTab )
+ pLastTab->PostFormat( rInf );
+ return PreFormat( rInf );
+}
+
+void SwTabPortion::FormatEOL( SwTextFormatInfo &rInf )
+{
+ if( rInf.GetLastTab() == this )
+ PostFormat( rInf );
+}
+
+bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf )
+{
+ OSL_ENSURE( rInf.X() <= GetTabPos(), "SwTabPortion::PreFormat: rush hour" );
+
+ // Here we settle down ...
+ SetFix( o3tl::narrowing<sal_uInt16>(rInf.X()) );
+
+ IDocumentSettingAccess const& rIDSA(rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess());
+ const bool bTabCompat = rIDSA.get(DocumentSettingId::TAB_COMPAT);
+ const bool bTabOverflow = rIDSA.get(DocumentSettingId::TAB_OVERFLOW);
+ const bool bTabOverMargin = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN);
+ const bool bTabOverSpacing = rIDSA.get(DocumentSettingId::TAB_OVER_SPACING);
+ const sal_Int32 nTextFrameWidth = rInf.GetTextFrame()->getFrameArea().Width();
+
+ // The minimal width of a tab is one blank at least.
+ // #i37686# In compatibility mode, the minimum width
+ // should be 1, even for non-left tab stops.
+ sal_uInt16 nMinimumTabWidth = 1;
+ if ( !bTabCompat )
+ {
+ // #i89179#
+ // tab portion representing the list tab of a list label gets the
+ // same font as the corresponding number portion
+ std::optional< SwFontSave > oSave;
+ if ( GetLen() == TextFrameIndex(0) &&
+ rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
+ static_cast<SwNumberPortion*>(rInf.GetLast())->HasFont() )
+ {
+ const SwFont* pNumberPortionFont =
+ static_cast<SwNumberPortion*>(rInf.GetLast())->GetFont();
+ oSave.emplace( rInf, const_cast<SwFont*>(pNumberPortionFont) );
+ }
+ OUString aTmp( ' ' );
+ SwTextSizeInfo aInf( rInf, &aTmp );
+ nMinimumTabWidth = aInf.GetTextSize().Width();
+ }
+ PrtWidth( nMinimumTabWidth );
+
+ // Break tab stop to next line if:
+ // 1. Minimal width does not fit to line anymore.
+ // 2. An underflow event was called for the tab portion.
+ bool bFull = ( bTabCompat && rInf.IsUnderflow() ) ||
+ ( rInf.Width() <= rInf.X() + PrtWidth() && rInf.X() <= rInf.Width() ) ;
+
+ // #95477# Rotated tab stops get the width of one blank
+ const Degree10 nDir = rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() );
+
+ if( ! bFull && 0_deg10 == nDir )
+ {
+ const PortionType nWhich = GetWhichPor();
+ switch( nWhich )
+ {
+ case PortionType::TabRight:
+ case PortionType::TabDecimal:
+ case PortionType::TabCenter:
+ {
+ if( PortionType::TabDecimal == nWhich )
+ rInf.SetTabDecimal(
+ static_cast<SwTabDecimalPortion*>(this)->GetTabDecimal());
+ rInf.SetLastTab( this );
+ break;
+ }
+ case PortionType::TabLeft:
+ {
+ // handle this case in PostFormat
+ if ((bTabOverMargin || bTabOverSpacing) && GetTabPos() > rInf.Width()
+ && (!m_bAutoTabStop || (!bTabOverMargin && rInf.X() > rInf.Width())))
+ {
+ if (bTabOverMargin || GetTabPos() < nTextFrameWidth)
+ {
+ rInf.SetLastTab(this);
+ break;
+ }
+ else
+ {
+ assert(!bTabOverMargin && bTabOverSpacing && GetTabPos() >= nTextFrameWidth);
+ bFull = true;
+ break;
+ }
+ }
+
+ PrtWidth( o3tl::narrowing<sal_uInt16>(GetTabPos() - rInf.X()) );
+ bFull = rInf.Width() <= rInf.X() + PrtWidth();
+
+ // In tabulator compatibility mode, we reset the bFull flag
+ // if the tabulator is at the end of the paragraph and the
+ // tab stop position is outside the frame:
+ bool bAtParaEnd = rInf.GetIdx() + GetLen() == TextFrameIndex(rInf.GetText().getLength());
+ if ( bFull && bTabCompat &&
+ ( ( bTabOverflow && ( rInf.IsTabOverflow() || !m_bAutoTabStop ) ) || bAtParaEnd ) &&
+ GetTabPos() >= nTextFrameWidth)
+ {
+ bFull = false;
+ if ( bTabOverflow && !m_bAutoTabStop )
+ rInf.SetTabOverflow( true );
+ }
+
+ break;
+ }
+ default: OSL_ENSURE( false, "SwTabPortion::PreFormat: unknown adjustment" );
+ }
+ }
+
+ if( bFull )
+ {
+ // We have to look for endless loops, if the width is smaller than one blank
+ if( rInf.GetIdx() == rInf.GetLineStart() &&
+ // #119175# TabStop should be forced to current
+ // line if there is a fly reducing the line width:
+ !rInf.GetFly() )
+ {
+ PrtWidth( o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X()) );
+ SetFixWidth( PrtWidth() );
+ }
+ else
+ {
+ Height( 0 );
+ Width( 0 );
+ SetLen( TextFrameIndex(0) );
+ SetAscent( 0 );
+ SetNextPortion( nullptr ); //?????
+ }
+ return true;
+ }
+ else
+ {
+ // A trick with impact: The new Tabportions now behave like
+ // FlyFrames, located in the line - including adjustment !
+ SetFixWidth( PrtWidth() );
+ return false;
+ }
+}
+
+bool SwTabPortion::PostFormat( SwTextFormatInfo &rInf )
+{
+ bool bTabOverMargin = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::TAB_OVER_MARGIN);
+ bool bTabOverSpacing = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::TAB_OVER_SPACING);
+ if (rInf.GetTextFrame()->IsInSct())
+ bTabOverMargin = false;
+
+ // If the tab position is larger than the right margin, it gets scaled down by default.
+ // However, if compat mode enabled, we allow tabs to go over the margin: the rest of the paragraph is not broken into lines.
+ const sal_uInt16 nRight
+ = bTabOverMargin
+ ? GetTabPos()
+ : bTabOverSpacing
+ ? std::min<long>(GetTabPos(), rInf.GetTextFrame()->getFrameArea().Right())
+ : std::min(GetTabPos(), rInf.Width());
+ const SwLinePortion *pPor = GetNextPortion();
+
+ sal_uInt16 nPorWidth = 0;
+ while( pPor )
+ {
+ nPorWidth = nPorWidth + pPor->Width();
+ pPor = pPor->GetNextPortion();
+ }
+
+ const PortionType nWhich = GetWhichPor();
+ const bool bTabCompat = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
+
+ if ((bTabOverMargin || bTabOverSpacing) && PortionType::TabLeft == nWhich)
+ {
+ nPorWidth = 0;
+ }
+
+ // #127428# Abandon dec. tab position if line is full
+ if ( bTabCompat && PortionType::TabDecimal == nWhich )
+ {
+ sal_uInt16 nPrePorWidth = static_cast<const SwTabDecimalPortion*>(this)->GetWidthOfPortionsUpToDecimalPosition();
+
+ // no value was set => no decimal character was found
+ if ( USHRT_MAX != nPrePorWidth )
+ {
+ if ( !bTabOverMargin && nPrePorWidth && nPorWidth - nPrePorWidth > rInf.Width() - nRight )
+ {
+ nPrePorWidth += nPorWidth - nPrePorWidth - ( rInf.Width() - nRight );
+ }
+
+ nPorWidth = nPrePorWidth - 1;
+ }
+ }
+
+ if( PortionType::TabCenter == nWhich )
+ {
+ // centered tabs are problematic:
+ // We have to detect how much fits into the line.
+ sal_uInt16 nNewWidth = nPorWidth /2;
+ if (!bTabOverMargin && !bTabOverSpacing && nNewWidth > rInf.Width() - nRight)
+ nNewWidth = nPorWidth - (rInf.Width() - nRight);
+ nPorWidth = nNewWidth;
+ }
+
+ const sal_uInt16 nDiffWidth = nRight - GetFix();
+
+ if( nDiffWidth > nPorWidth )
+ {
+ const sal_uInt16 nOldWidth = GetFixWidth();
+ const sal_uInt16 nAdjDiff = nDiffWidth - nPorWidth;
+ if( nAdjDiff > GetFixWidth() )
+ PrtWidth( nAdjDiff );
+ // Don't be afraid: we have to move rInf further.
+ // The right-tab till now only had the width of one blank.
+ // Now that we stretched, the difference had to be added to rInf.X() !
+ rInf.X( rInf.X() + PrtWidth() - nOldWidth );
+ }
+ SetFixWidth( PrtWidth() );
+ // reset last values
+ rInf.SetLastTab(nullptr);
+ if( PortionType::TabDecimal == nWhich )
+ rInf.SetTabDecimal(0);
+
+ return rInf.Width() <= rInf.X();
+}
+
+/**
+ * Ex: LineIter::DrawTab()
+ */
+void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const
+{
+ // #i89179#
+ // tab portion representing the list tab of a list label gets the
+ // same font as the corresponding number portion
+ std::optional< SwFontSave > oSave;
+ bool bAfterNumbering = false;
+ if (GetLen() == TextFrameIndex(0))
+ {
+ const SwLinePortion* pPrevPortion =
+ const_cast<SwTabPortion*>(this)->FindPrevPortion( rInf.GetParaPortion() );
+ if ( pPrevPortion &&
+ pPrevPortion->InNumberGrp() &&
+ static_cast<const SwNumberPortion*>(pPrevPortion)->HasFont() )
+ {
+ const SwFont* pNumberPortionFont =
+ static_cast<const SwNumberPortion*>(pPrevPortion)->GetFont();
+ oSave.emplace( rInf, const_cast<SwFont*>(pNumberPortionFont) );
+ bAfterNumbering = true;
+ }
+ }
+ rInf.DrawBackBrush( *this );
+ if( !bAfterNumbering )
+ rInf.DrawBorder( *this );
+
+ // do we have to repaint a post it portion?
+ if( rInf.OnWin() && mpNextPortion && !mpNextPortion->Width() )
+ mpNextPortion->PrePaint( rInf, this );
+
+ // display special characters
+ if( rInf.OnWin() && rInf.GetOpt().IsTab() )
+ {
+ // filled tabs are shaded in gray
+ if( IsFilled() )
+ rInf.DrawViewOpt( *this, PortionType::Tab );
+ else
+ rInf.DrawTab( *this );
+ }
+
+ // Tabs should be underlined at once
+ if( rInf.GetFont()->IsPaintBlank() )
+ {
+ // Tabs with filling/filled tabs
+ const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(' ')).Width();
+
+ // Robust:
+ if( nCharWidth )
+ {
+ // Always with kerning, also on printer!
+ sal_uInt16 nChar = Width() / nCharWidth;
+ OUStringBuffer aBuf(nChar);
+ comphelper::string::padToLength(aBuf, nChar, ' ');
+ rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0),
+ TextFrameIndex(nChar), true);
+ }
+ }
+
+ // Display fill characters
+ if( !IsFilled() )
+ return;
+
+ // Tabs with filling/filled tabs
+ const sal_uInt16 nCharWidth = rInf.GetTextSize(OUString(m_cFill)).Width();
+ OSL_ENSURE( nCharWidth, "!SwTabPortion::Paint: sophisticated tabchar" );
+
+ // Robust:
+ if( nCharWidth )
+ {
+ // Always with kerning, also on printer!
+ sal_uInt16 nChar = Width() / nCharWidth;
+ if ( m_cFill == '_' )
+ ++nChar; // to avoid gaps
+ OUStringBuffer aBuf(nChar);
+ comphelper::string::padToLength(aBuf, nChar, m_cFill);
+ rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0),
+ TextFrameIndex(nChar), true);
+ }
+}
+
+void SwAutoTabDecimalPortion::Paint( const SwTextPaintInfo & ) const
+{
+}
+
+void SwTabPortion::HandlePortion( SwPortionHandler& rPH ) const
+{
+ rPH.Text( GetLen(), GetWhichPor() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/widorp.cxx b/sw/source/core/text/widorp.cxx
new file mode 100644
index 0000000000..11e4193d7b
--- /dev/null
+++ b/sw/source/core/text/widorp.cxx
@@ -0,0 +1,673 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <layfrm.hxx>
+#include <ftnboss.hxx>
+#include <ndtxt.hxx>
+#include <paratr.hxx>
+#include <editeng/orphitem.hxx>
+#include <editeng/widwitem.hxx>
+#include <editeng/keepitem.hxx>
+#include <editeng/spltitem.hxx>
+#include <frmatr.hxx>
+#include <txtftn.hxx>
+#include <fmtftn.hxx>
+#include <rowfrm.hxx>
+
+#include "widorp.hxx"
+#include <txtfrm.hxx>
+#include "itrtxt.hxx"
+#include <sectfrm.hxx>
+#include <ftnfrm.hxx>
+#include <pagefrm.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <sortedobjs.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrm.hxx>
+
+#undef WIDOWTWIPS
+
+namespace
+{
+
+// A Follow on the same page as its master is nasty.
+bool IsNastyFollow( const SwTextFrame *pFrame )
+{
+ OSL_ENSURE( !pFrame->IsFollow() || !pFrame->GetPrev() ||
+ static_cast<const SwTextFrame*>(pFrame->GetPrev())->GetFollow() == pFrame,
+ "IsNastyFollow: What is going on here?" );
+ return pFrame->IsFollow() && pFrame->GetPrev();
+}
+
+}
+
+SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst )
+ : m_nRstHeight(nRst), m_pFrame(pNewFrame)
+{
+ SwSwapIfSwapped swap(m_pFrame);
+ SwRectFnSet aRectFnSet(m_pFrame);
+ m_nOrigin = aRectFnSet.GetPrtTop(*m_pFrame);
+ m_bKeep = !m_pFrame->IsMoveable() || IsNastyFollow( m_pFrame );
+ if( !m_bKeep && m_pFrame->IsInSct() )
+ {
+ const SwSectionFrame* const pSct = m_pFrame->FindSctFrame();
+ m_bKeep = pSct->Lower()->IsColumnFrame() && !pSct->MoveAllowed( m_pFrame );
+ }
+ m_bKeep = m_bKeep || !m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetSplit().GetValue() ||
+ m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep().GetValue();
+ m_bBreak = false;
+
+ if( !m_nRstHeight && !m_pFrame->IsFollow() && m_pFrame->IsInFootnote() && m_pFrame->HasPara() )
+ {
+ m_nRstHeight = m_pFrame->GetFootnoteFrameHeight();
+ m_nRstHeight += aRectFnSet.GetHeight(m_pFrame->getFramePrintArea()) -
+ aRectFnSet.GetHeight(m_pFrame->getFrameArea());
+ if( m_nRstHeight < 0 )
+ m_nRstHeight = 0;
+ }
+}
+
+/**
+ * BP 18.6.93: Widows.
+ * In contrast to the first implementation the Widows are not calculated
+ * in advance but detected when formatting the split Follow.
+ * In Master the Widows-calculation is dropped completely
+ * (nWidows is manipulated). If the Follow detects that the
+ * Widows rule applies it sends a Prepare to its predecessor.
+ * A special problem is when the Widow rule applies but in Master
+ * there are some lines available.
+ *
+ * BP(22.07.92): Calculation of Widows and Orphans.
+ * The method returns true if one of the rules matches.
+ *
+ * One difficulty with Widows and different formats between
+ * Master- and Follow-Frame:
+ * Example: If the first column is 3cm and the second is 4cm and
+ * Widows is set to 3, the decision if the Widows rule matches can not
+ * be done until the Follow is formatted. Unfortunately this is crucial
+ * to decide if the whole paragraph goes to the next page or not.
+ */
+bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const
+{
+ bool bFit = false;
+
+ SwSwapIfSwapped swap(m_pFrame);
+ SwRectFnSet aRectFnSet(m_pFrame);
+ // nOrigin is an absolute value, rLine refers to the swapped situation.
+
+ SwTwips nTmpY;
+ if ( m_pFrame->IsVertical() )
+ nTmpY = m_pFrame->SwitchHorizontalToVertical( rLine.Y() + rLine.GetLineHeight() );
+ else
+ nTmpY = rLine.Y() + rLine.GetLineHeight();
+
+ SwTwips nLineHeight = aRectFnSet.YDiff( nTmpY , m_nOrigin );
+
+ // Calculate extra space for bottom border.
+ nLineHeight += aRectFnSet.GetBottomMargin(*m_pFrame);
+
+ if( m_nRstHeight )
+ bFit = m_nRstHeight >= nLineHeight;
+ else
+ {
+ // The Frame has a height to fit on the page.
+ SwTwips nHeight =
+ aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*m_pFrame->GetUpper()), m_nOrigin );
+ SwTwips nDiff = nHeight - nLineHeight;
+
+ // Hide whitespace may require not to insert a new page.
+ SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
+ if (!pPageFrame->CheckPageHeightValidForHideWhitespace(nDiff))
+ nDiff = 0;
+
+ // If everything is inside the existing frame the result is true;
+ bFit = nDiff >= 0;
+
+ // If it didn't fit, try to add the space of footnotes that are anchored
+ // in frames below (in next-chain of) this one as they will need to move
+ // forward anyway if this frame is split.
+ // - except if in tables (need to check if row is splittable?
+ // also, multiple columns looks difficult)
+ if (!bFit && !m_pFrame->IsInTab())
+ {
+ if (SwFootnoteBossFrame const*const pBoss = m_pFrame->FindFootnoteBossFrame())
+ {
+ if (SwFootnoteContFrame const*const pCont = pBoss->FindFootnoteCont())
+ {
+ SwContentFrame const* pContent(m_pFrame);
+ while (pContent->HasFollow())
+ {
+ pContent = pContent->GetFollow();
+ }
+ // start with first text frame that isn't a follow
+ // (ignoring Keep attribute for now, MakeAll should handle it?)
+ pContent = pContent->GetNextContentFrame();
+ ::std::set<SwContentFrame const*> nextFrames;
+ while (pBoss->IsAnLower(pContent))
+ {
+ nextFrames.insert(pContent);
+ pContent = pContent->GetNextContentFrame();
+ }
+ SwTwips nNextFootnotes(0);
+ for (SwFootnoteFrame const* pFootnote = static_cast<SwFootnoteFrame const*>(pCont->Lower());
+ pFootnote != nullptr;
+ pFootnote = static_cast<SwFootnoteFrame const*>(pFootnote->GetNext()))
+ {
+ SwContentFrame const*const pAnchor = pFootnote->GetRef();
+ if (nextFrames.find(pAnchor) != nextFrames.end())
+ {
+ nNextFootnotes += aRectFnSet.GetHeight(pFootnote->getFrameArea());
+ }
+ }
+ bFit = 0 <= nDiff + nNextFootnotes;
+ SAL_INFO_IF(bFit, "sw.core", "no text frame break because ignoring "
+ << nNextFootnotes << " footnote height");
+ }
+ }
+ }
+ if (!bFit && rLine.MaybeHasHints() && m_pFrame->GetFollow()
+ // tdf#153319 RemoveFootnote only works if this frame doesn't
+ && !rLine.GetNext() // contain the footnote portion
+ // if using same footnote container as the follow, pointless to try?
+ && m_pFrame->FindFootnoteBossFrame() != m_pFrame->GetFollow()->FindFootnoteBossFrame())
+ {
+ // possibly a footnote that is anchored beyond the end of this
+ // (the last) line is in the way, try to remove it and check again
+ m_pFrame->RemoveFootnote(rLine.GetEnd());
+ nHeight = aRectFnSet.YDiff( aRectFnSet.GetPrtBottom(*m_pFrame->GetUpper()), m_nOrigin );
+ bFit = nHeight >= nLineHeight;
+ }
+ if ( !bFit )
+ {
+ if ( rLine.GetNext() &&
+ m_pFrame->IsInTab() && !m_pFrame->GetFollow() && !m_pFrame->GetIndNext() )
+ {
+ // add additional space taken as lower space as last content in a table
+ // for all text lines except the last one.
+ nHeight += m_pFrame->CalcAddLowerSpaceAsLastInTableCell();
+ bFit = nHeight >= nLineHeight;
+ }
+ }
+ if( !bFit )
+ {
+ // The LineHeight exceeds the current Frame height.
+ // Call a test Grow to detect if the Frame could
+ // grow the requested area.
+ nHeight += m_pFrame->GrowTst( LONG_MAX );
+
+ // The Grow() returns the height by which the Upper of the TextFrame
+ // would let the TextFrame grow.
+ // The TextFrame itself can grow as much as it wants.
+ bFit = nHeight >= nLineHeight;
+ }
+ }
+
+ return bFit;
+}
+
+bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine )
+{
+ SwSwapIfSwapped swap(m_pFrame);
+
+ // bKeep is stronger than IsBreakNow()
+ // Is there enough space ?
+ if( m_bKeep || IsInside( rLine ) )
+ m_bBreak = false;
+ else
+ {
+ /* This class assumes that the SwTextMargin is processed from Top to
+ * Bottom. Because of performance reasons we stop splitting in the
+ * following cases:
+ * If only one line does not fit.
+ * Special case: with DummyPortions there is LineNr == 1, though we
+ * want to split.
+ */
+ // Include DropLines
+
+ bool bFirstLine = 1 == rLine.GetLineNr() && !rLine.GetPrev();
+ m_bBreak = true;
+
+ if (bFirstLine && m_pFrame->IsEmptyWithSplitFly())
+ {
+ // Not really the first line, visually we may have a previous line (including the fly
+ // frame) already.
+ bFirstLine = false;
+ }
+
+ if( ( bFirstLine && m_pFrame->GetIndPrev() )
+ || ( rLine.GetLineNr() <= rLine.GetDropLines() ) )
+ {
+ m_bKeep = true;
+ m_bBreak = false;
+ }
+ else if(bFirstLine && m_pFrame->IsInFootnote() && !m_pFrame->FindFootnoteFrame()->GetPrev())
+ {
+ SwLayoutFrame* pTmp = m_pFrame->FindFootnoteBossFrame()->FindBodyCont();
+ if( !pTmp || !pTmp->Lower() )
+ m_bBreak = false;
+ }
+ }
+
+ return m_bBreak;
+}
+
+void SwTextFrameBreak::SetRstHeight( const SwTextMargin &rLine )
+{
+ // Consider bottom margin
+ SwRectFnSet aRectFnSet(m_pFrame);
+
+ m_nRstHeight = aRectFnSet.GetBottomMargin(*m_pFrame);
+
+ if ( aRectFnSet.IsVert() )
+ {
+ if ( m_pFrame->IsVertLR() )
+ m_nRstHeight = aRectFnSet.YDiff( m_pFrame->SwitchHorizontalToVertical( rLine.Y() ) , m_nOrigin );
+ else
+ m_nRstHeight += m_nOrigin - m_pFrame->SwitchHorizontalToVertical( rLine.Y() );
+ }
+ else
+ m_nRstHeight += rLine.Y() - m_nOrigin;
+}
+
+WidowsAndOrphans::WidowsAndOrphans( SwTextFrame *pNewFrame, const SwTwips nRst,
+ bool bChkKeep )
+ : SwTextFrameBreak( pNewFrame, nRst ), m_nWidLines( 0 ), m_nOrphLines( 0 )
+{
+ SwSwapIfSwapped swap(m_pFrame);
+
+ if( m_bKeep )
+ {
+ // If paragraph should not be split but is larger than
+ // the page, then bKeep is overruled.
+ if( bChkKeep && !m_pFrame->GetPrev() && !m_pFrame->IsInFootnote() &&
+ m_pFrame->IsMoveable() &&
+ ( !m_pFrame->IsInSct() || m_pFrame->FindSctFrame()->MoveAllowed(m_pFrame) ) )
+ m_bKeep = false;
+ // Even if Keep is set, Orphans has to be respected.
+ // e.g. if there are chained frames where a Follow in the last frame
+ // receives a Keep, because it is not (forward) movable -
+ // nevertheless the paragraph can request lines from the Master
+ // because of the Orphan rule.
+ if( m_pFrame->IsFollow() )
+ m_nWidLines = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetWidows().GetValue();
+ }
+ else
+ {
+ const SwAttrSet& rSet = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet();
+ const SvxOrphansItem &rOrph = rSet.GetOrphans();
+ if ( rOrph.GetValue() > 1 )
+ m_nOrphLines = rOrph.GetValue();
+ if ( m_pFrame->IsFollow() )
+ m_nWidLines = rSet.GetWidows().GetValue();
+
+ }
+
+ if ( !(m_bKeep || m_nWidLines || m_nOrphLines) )
+ return;
+
+ bool bResetFlags = false;
+
+ bool bWordTableCell = false;
+ if (m_pFrame->IsInFly())
+ {
+ // Enable widow / orphan control in Word-style table cells in split rows, at least inside
+ // flys.
+ const SwDoc& rDoc = m_pFrame->GetTextNodeForParaProps()->GetDoc();
+ const IDocumentSettingAccess& rIDSA = rDoc.getIDocumentSettingAccess();
+ bWordTableCell = rIDSA.get(DocumentSettingId::TABLE_ROW_KEEP);
+ }
+
+ if ( m_pFrame->IsInTab() && !bWordTableCell )
+ {
+ // For compatibility reasons, we disable Keep/Widows/Orphans
+ // inside splittable row frames:
+ if ( m_pFrame->GetNextCellLeaf() || m_pFrame->IsInFollowFlowRow() )
+ {
+ const SwFrame* pTmpFrame = m_pFrame->GetUpper();
+ while ( !pTmpFrame->IsRowFrame() )
+ pTmpFrame = pTmpFrame->GetUpper();
+ if ( static_cast<const SwRowFrame*>(pTmpFrame)->IsRowSplitAllowed() )
+ bResetFlags = true;
+ }
+ }
+
+ if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
+ {
+ // Inside of footnotes there are good reasons to turn off the Keep attribute
+ // as well as Widows/Orphans.
+ SwFootnoteFrame *pFootnote = m_pFrame->FindFootnoteFrame();
+ const bool bFt = !pFootnote->GetAttr()->GetFootnote().IsEndNote();
+ if( !pFootnote->GetPrev() &&
+ pFootnote->FindFootnoteBossFrame( bFt ) != pFootnote->GetRef()->FindFootnoteBossFrame( bFt )
+ && ( !m_pFrame->IsInSct() || m_pFrame->FindSctFrame()->MoveAllowed(m_pFrame) ) )
+ {
+ bResetFlags = true;
+ }
+ }
+
+ if ( bResetFlags )
+ {
+ m_bKeep = false;
+ m_nOrphLines = 0;
+ m_nWidLines = 0;
+ }
+}
+
+/**
+ * The Find*-Methods do not only search, but adjust the SwTextMargin to the
+ * line where the paragraph should have a break and truncate the paragraph there.
+ * FindBreak()
+ */
+bool WidowsAndOrphans::FindBreak( SwTextFrame *pFrame, SwTextMargin &rLine,
+ bool bHasToFit )
+{
+ // i#16128 - Why member <pFrame> _*and*_ parameter <pFrame>??
+ // Thus, assertion on situation, that these are different to figure out why.
+ OSL_ENSURE( m_pFrame == pFrame, "<WidowsAndOrphans::FindBreak> - pFrame != pFrame" );
+
+ SwSwapIfSwapped swap(m_pFrame);
+
+ bool bRet = true;
+ sal_uInt16 nOldOrphans = m_nOrphLines;
+ if( bHasToFit )
+ m_nOrphLines = 0;
+ rLine.Bottom();
+
+ if( !IsBreakNowWidAndOrp( rLine ) )
+ bRet = false;
+ if( !FindWidows( pFrame, rLine ) )
+ {
+ bool bBack = false;
+
+ while( IsBreakNowWidAndOrp( rLine ) )
+ {
+ if( rLine.PrevLine() )
+ bBack = true;
+ else
+ break;
+ }
+ // Usually Orphans are not taken into account for HasToFit.
+ // But if Dummy-Lines are concerned and the Orphans rule is violated
+ // we make an exception: We leave behind one Dummyline and take
+ // the whole text to the next page/column.
+ if( rLine.GetLineNr() <= nOldOrphans &&
+ rLine.GetInfo().GetParaPortion()->IsDummy() &&
+ ( ( bHasToFit && bRet ) || IsBreakNow( rLine ) ) )
+ rLine.Top();
+
+ rLine.TruncLines( true );
+ bRet = bBack;
+ }
+ m_nOrphLines = nOldOrphans;
+
+ return bRet;
+}
+
+/**
+ * FindWidows positions the SwTextMargin of the Master to the line where to
+ * break by examining and formatting the Follow.
+ * Returns true if the Widows-rule matches, that means that the
+ * paragraph should not be split (keep) !
+ */
+bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine )
+{
+ OSL_ENSURE( ! pFrame->IsVertical() || ! pFrame->IsSwapped(),
+ "WidowsAndOrphans::FindWidows with swapped frame" );
+
+ if( !m_nWidLines || !pFrame->IsFollow() )
+ return false;
+
+ rLine.Bottom();
+
+ // We can still cut something off
+ SwTextFrame *pMaster = pFrame->FindMaster();
+ OSL_ENSURE(pMaster, "+WidowsAndOrphans::FindWidows: Widows in a master?");
+ if( !pMaster )
+ return false;
+
+ // If the first line of the Follow does not fit, the master
+ // probably is full of Dummies. In this case a PrepareHint::Widows would be fatal.
+ if( pMaster->GetOffset() == pFrame->GetOffset() )
+ return false;
+
+ // Remaining height of the master
+ SwRectFnSet aRectFnSet(pFrame);
+
+ const SwTwips nDocPrtTop = aRectFnSet.GetPrtTop(*pFrame);
+ SwTwips nOldHeight;
+ SwTwips nTmpY = rLine.Y() + rLine.GetLineHeight();
+
+ if ( aRectFnSet.IsVert() )
+ {
+ nTmpY = pFrame->SwitchHorizontalToVertical( nTmpY );
+ nOldHeight = -aRectFnSet.GetHeight(pFrame->getFramePrintArea());
+ }
+ else
+ nOldHeight = aRectFnSet.GetHeight(pFrame->getFramePrintArea());
+
+ const SwTwips nChg = aRectFnSet.YDiff( nTmpY, nDocPrtTop + nOldHeight );
+
+ // below the Widows-threshold...
+ if( rLine.GetLineNr() >= m_nWidLines )
+ {
+ // Follow to Master I
+ // If the Follow *grows*, there is the chance for the Master to
+ // receive lines, that it was forced to hand over to the Follow lately:
+ // Prepare(Need); check that below nChg!
+ // (0W, 2O, 2M, 2F) + 1F = 3M, 2F
+ if( rLine.GetLineNr() > m_nWidLines && pFrame->IsJustWidow() )
+ {
+ // If the Master is locked, it has probably just donated a line
+ // to us, we don't return that just because we turned it into
+ // multiple lines (e.g. via frames).
+ if( !pMaster->IsLocked() && pMaster->GetUpper() )
+ {
+ const SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(),
+ aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) );
+ if ( nTmpRstHeight >=
+ rLine.GetInfo().GetParaPortion()->Height() )
+ {
+ pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting );
+ pMaster->InvalidateSize_();
+ pMaster->InvalidatePage();
+ }
+ }
+
+ pFrame->SetJustWidow( false );
+ }
+ return false;
+ }
+
+ // Follow to Master II
+ // If the Follow *shrinks*, maybe the Master can absorb the whole Orphan.
+ // (0W, 2O, 2M, 1F) - 1F = 3M, 0F -> PrepareHint::AdjustSizeWithoutFormatting
+ // (0W, 2O, 3M, 2F) - 1F = 2M, 2F -> PrepareHint::Widows
+
+ if( 0 > nChg && !pMaster->IsLocked() && pMaster->GetUpper() )
+ {
+ SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(),
+ aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) );
+ if( nTmpRstHeight >= rLine.GetInfo().GetParaPortion()->Height() )
+ {
+ pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting );
+ pMaster->InvalidateSize_();
+ pMaster->InvalidatePage();
+ pFrame->SetJustWidow( false );
+ return false;
+ }
+ }
+
+ // Master to Follow
+ // If the Follow contains fewer lines than Widows after formatting,
+ // we still can move over some lines from the Master. If this triggers
+ // the Orphans rule of the Master, the Master frame must be Grow()n
+ // in its CalcPreps(), such that it won't fit onto its page anymore.
+ // But if the Master Frame can still lose a few lines, we need to
+ // do a Shrink() in the CalcPreps(); the Follow with the Widows then
+ // moves onto the page of the Master, but remains unsplit, so that
+ // it (finally) moves onto the next page. So much for the theory!
+ //
+ // We only request one line at a time for now, because a Master's line
+ // could result in multiple lines for us.
+ // Therefore, the CalcFollow() remains in control until the Follow got all
+ // necessary lines.
+ sal_Int32 nNeed = 1; // was: nWidLines - rLine.GetLineNr();
+
+ // Special case: Master cannot give lines to follow
+ // i#91421
+ if ( !pMaster->GetIndPrev() )
+ {
+ pMaster->ChgThisLines();
+ sal_Int32 nLines = pMaster->GetThisLines();
+ if(nLines == 0 && pMaster->HasPara())
+ {
+ const SwParaPortion *pMasterPara = pMaster->GetPara();
+ if(pMasterPara && pMasterPara->GetNext())
+ nLines = 2;
+ }
+ if( nLines <= nNeed )
+ return false;
+
+ if (pFrame->IsInTab())
+ {
+ const SwFrame* pRow = pFrame;
+ while (pRow && !pRow->IsRowFrame())
+ {
+ pRow = pRow->GetUpper();
+ }
+
+ if (pRow && pRow->HasFixSize())
+ {
+ // This is a follow frame and our side is fixed.
+ const SwAttrSet& rSet = pFrame->GetTextNodeForParaProps()->GetSwAttrSet();
+ const SvxOrphansItem& rOrph = rSet.GetOrphans();
+ if (nLines <= static_cast<sal_Int32>(rOrph.GetValue()))
+ {
+ // If the master gives us a line as part of widow control, then its orphan
+ // control will move everything to the follow, which is worse than having no
+ // widow / orphan control at all. Don't send a Widows prepare hint, in this
+ // case.
+ return true;
+ }
+ }
+ }
+ }
+
+ pMaster->Prepare( PrepareHint::Widows, static_cast<void*>(&nNeed) );
+ return true;
+}
+
+namespace sw {
+
+auto FindNonFlyPortion(SwLineLayout const& rLine) -> bool
+{
+ for (SwLinePortion const* pPortion = rLine.GetFirstPortion();
+ pPortion; pPortion = pPortion->GetNextPortion())
+ {
+ switch (pPortion->GetWhichPor())
+ {
+ case PortionType::Fly:
+ case PortionType::Glue:
+ case PortionType::Margin:
+ break;
+ default:
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+} // namespace sw
+
+bool WidowsAndOrphans::WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTst, bool bMoveBwd )
+{
+ // Here it does not matter, if pFrame is swapped or not.
+ // IsInside() takes care of itself
+
+ // We expect that rLine is set to the last line
+ OSL_ENSURE( !rLine.GetNext(), "WouldFit: aLine::Bottom missed!" );
+ sal_Int32 nLineCnt = rLine.GetLineNr();
+
+ // First satisfy the Orphans-rule and the wish for initials ...
+ const sal_uInt16 nMinLines = std::max( GetOrphansLines(), rLine.GetDropLines() );
+ if ( nLineCnt < nMinLines )
+ return false;
+
+ rLine.Top();
+ SwTwips nLineSum = rLine.GetLineHeight();
+
+ // tdf#146500 for MoveBwd(), want at least 1 line with non-fly
+ bool hasNonFly(!bMoveBwd);
+ if (!hasNonFly)
+ {
+ hasNonFly = ::sw::FindNonFlyPortion(*rLine.GetCurr());
+ }
+ while (nMinLines > rLine.GetLineNr() || !hasNonFly)
+ {
+ if( !rLine.NextLine() )
+ {
+ if (nMinLines > rLine.GetLineNr())
+ return false;
+ else
+ break;
+ }
+ nLineSum += rLine.GetLineHeight();
+ if (!hasNonFly)
+ {
+ hasNonFly = ::sw::FindNonFlyPortion(*rLine.GetCurr());
+ }
+ }
+
+ // We do not fit
+ if( !IsInside( rLine ) )
+ return false;
+
+ // Check the Widows-rule
+ if( !m_nWidLines && !m_pFrame->IsFollow() )
+ {
+ // Usually we only have to check for Widows if we are a Follow.
+ // On WouldFit the rule has to be checked for the Master too,
+ // because we are just in the middle of calculating the break.
+ // In Ctor of WidowsAndOrphans the nWidLines are only calced for
+ // Follows from the AttrSet - so we catch up now:
+ const SwAttrSet& rSet = m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet();
+ m_nWidLines = rSet.GetWidows().GetValue();
+ }
+
+ // After Orphans/Initials, do enough lines remain for Widows?
+ // If we are currently doing a test formatting, we may not
+ // consider the widows rule for two reasons:
+ // 1. The columns may have different widths.
+ // Widow lines would have wrong width.
+ // 2. Test formatting is only done up to the given space.
+ // we do not have any lines for widows at all.
+ if( bTst || nLineCnt - nMinLines >= m_nWidLines )
+ {
+ if( rMaxHeight >= nLineSum )
+ {
+ rMaxHeight -= nLineSum;
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/widorp.hxx b/sw/source/core/text/widorp.hxx
new file mode 100644
index 0000000000..996a7fc913
--- /dev/null
+++ b/sw/source/core/text/widorp.hxx
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#pragma once
+
+#include <swtypes.hxx>
+#include "itrtxt.hxx"
+
+class SwTextFrame;
+
+class SwTextFrameBreak
+{
+private:
+ SwTwips m_nRstHeight;
+ SwTwips m_nOrigin;
+protected:
+ SwTextFrame *m_pFrame;
+ bool m_bBreak;
+ bool m_bKeep;
+public:
+ SwTextFrameBreak( SwTextFrame *pFrame, const SwTwips nRst = 0 );
+ bool IsBreakNow( SwTextMargin &rLine );
+ bool IsKeepAlways() const { return m_bKeep; }
+
+ void SetKeep( const bool bNew ) { m_bKeep = bNew; }
+
+ bool IsInside( SwTextMargin const &rLine ) const;
+
+ // In order to be able to handle special cases with Footnote.
+ // SetRstHeight sets the rest height for SwTextFrameBreak. This is needed
+ // to call TruncLines() without IsBreakNow() returning another value.
+ // We assume that rLine is pointing to the last non-fitting line.
+
+ void SetRstHeight( const SwTextMargin &rLine );
+};
+
+class WidowsAndOrphans : public SwTextFrameBreak
+{
+private:
+ sal_uInt16 m_nWidLines, m_nOrphLines;
+
+public:
+ WidowsAndOrphans( SwTextFrame *pFrame, const SwTwips nRst = 0,
+ bool bCheckKeep = true );
+ bool FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine );
+ sal_uInt16 GetOrphansLines() const
+ { return m_nOrphLines; }
+ void ClrOrphLines(){ m_nOrphLines = 0; }
+
+ bool FindBreak( SwTextFrame *pFrame, SwTextMargin &rLine, bool bHasToFit );
+ bool WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTest, bool bMoveBwd );
+ // i#16128 - This method is named this way to avoid confusion with
+ // base class method <SwTextFrameBreak::IsBreakNow>, which isn't virtual.
+ bool IsBreakNowWidAndOrp( SwTextMargin &rLine )
+ {
+ bool isOnFirstLine = (rLine.GetLineNr() == 1 && !rLine.GetPrev());
+ if ( isOnFirstLine && rLine.GetCurr()->IsDummy()) {
+ return IsBreakNow( rLine );
+ }
+ if ( rLine.GetLineNr() > m_nOrphLines ) {
+ return IsBreakNow( rLine );
+ }
+ return false;
+ }
+};
+
+namespace sw {
+
+auto FindNonFlyPortion(SwLineLayout const& rLine) -> bool;
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/wrong.cxx b/sw/source/core/text/wrong.cxx
new file mode 100644
index 0000000000..00be8d5fac
--- /dev/null
+++ b/sw/source/core/text/wrong.cxx
@@ -0,0 +1,938 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <swtypes.hxx>
+
+#include <SwGrammarMarkUp.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <utility>
+
+#include <osl/diagnose.h>
+
+SwWrongArea::SwWrongArea( OUString aType, WrongListType listType,
+ css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag,
+ sal_Int32 nPos,
+ sal_Int32 nLen)
+: maType(std::move(aType)), mxPropertyBag(xPropertyBag), mnPos(nPos), mnLen(nLen), mpSubList(nullptr)
+{
+ mColor = getWrongAreaColor(listType, xPropertyBag);
+ mLineType = getWrongAreaLineType(listType, xPropertyBag);
+}
+
+SwWrongArea::SwWrongArea( OUString aType,
+ css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag,
+ sal_Int32 nPos,
+ sal_Int32 nLen,
+ SwWrongList* pSubList)
+: maType(std::move(aType)), mxPropertyBag(xPropertyBag), mnPos(nPos), mnLen(nLen), mpSubList(pSubList), mLineType(WRONGAREA_NONE)
+{
+ if (pSubList != nullptr)
+ {
+ mColor = getWrongAreaColor(pSubList->GetWrongListType(), xPropertyBag);
+ mLineType = getWrongAreaLineType(pSubList->GetWrongListType(), xPropertyBag);
+ }
+}
+
+SwWrongList::SwWrongList( WrongListType eType ) :
+ meType (eType),
+ mnBeginInvalid(COMPLETE_STRING), // everything correct... (the invalid area starts beyond the string)
+ mnEndInvalid (COMPLETE_STRING)
+{
+ maList.reserve( 5 );
+}
+
+SwWrongList::~SwWrongList()
+{
+ ClearList();
+}
+
+SwWrongList* SwWrongList::Clone()
+{
+ SwWrongList* pClone = new SwWrongList( meType );
+ pClone->CopyFrom( *this );
+ return pClone;
+}
+
+void SwWrongList::CopyFrom( const SwWrongList& rCopy )
+{
+ maList = rCopy.maList;
+ meType = rCopy.meType;
+ mnBeginInvalid = rCopy.mnBeginInvalid;
+ mnEndInvalid = rCopy.mnEndInvalid;
+ for(SwWrongArea & i : maList)
+ {
+ if( i.mpSubList )
+ i.mpSubList = i.mpSubList->Clone();
+ }
+}
+
+void SwWrongList::ClearList()
+{
+ for (SwWrongArea & i : maList)
+ {
+ delete i.mpSubList;
+ i.mpSubList = nullptr;
+ }
+ maList.clear();
+}
+
+/** If a word is incorrectly selected, this method returns begin and length of it.
+
+ @param[in,out] rChk starting position of the word to check
+ @param[out] rLn length of the word
+
+ @return <true> if incorrectly selected, <false> otherwise
+ */
+bool SwWrongList::InWrongWord( sal_Int32 &rChk, sal_Int32 &rLn ) const
+{
+ const sal_uInt16 nPos = GetWrongPos( rChk );
+ if ( nPos >= Count() )
+ return false;
+ const sal_Int32 nWrPos = Pos( nPos );
+ if ( nWrPos <= rChk )
+ {
+ rLn = Len( nPos );
+ if( nWrPos + rLn <= rChk )
+ return false;
+ rChk = nWrPos;
+ return true;
+ }
+ return false;
+}
+
+/** Calculate first incorrectly selected area.
+
+ @param[in,out] rChk starting position of the word to check
+ @param[in,out] rLn length of the word
+
+ @return <true> if incorrectly selected area was found, <false> otherwise
+ */
+bool SwWrongList::Check( sal_Int32 &rChk, sal_Int32 &rLn ) const
+{
+ sal_uInt16 nPos = GetWrongPos( rChk );
+ rLn += rChk;
+
+ if( nPos == Count() )
+ return false;
+
+ sal_Int32 nWrPos = Pos( nPos );
+ sal_Int32 nEnd = nWrPos + Len( nPos );
+ if( nEnd == rChk )
+ {
+ ++nPos;
+ if( nPos == Count() )
+ return false;
+
+ nWrPos = Pos( nPos );
+ nEnd = nWrPos + Len( nPos );
+ }
+ if( nEnd > rChk && nWrPos < rLn )
+ {
+ if( nWrPos > rChk )
+ rChk = nWrPos;
+ if( nEnd < rLn )
+ rLn = nEnd;
+ rLn -= rChk;
+ return 0 != rLn;
+ }
+ return false;
+}
+
+/** Find next incorrectly selected position.
+
+ @param[in] rChk starting position of the word to check
+
+ @return starting position of incorrectly selected area, <COMPLETE_STRING> otherwise
+ */
+sal_Int32 SwWrongList::NextWrong( sal_Int32 nChk ) const
+{
+ sal_Int32 nRet = COMPLETE_STRING;
+ sal_uInt16 nPos = GetWrongPos( nChk );
+ if( nPos < Count() )
+ {
+ nRet = Pos( nPos );
+ if( nRet < nChk && nRet + Len( nPos ) <= nChk )
+ {
+ if( ++nPos < Count() )
+ nRet = Pos( nPos );
+ else
+ nRet = COMPLETE_STRING;
+ }
+ }
+ if( nRet > GetBeginInv() && nChk < GetEndInv() )
+ nRet = std::max(nChk, GetBeginInv());
+ return nRet;
+}
+
+/** Find the first position that is greater or equal to the given value.
+
+ @note Resulting position might be behind the last element of the array.
+ @param[in] nValue value for comparison
+
+ @return first position that is greater or equal to the given value
+ */
+sal_uInt16 SwWrongList::GetWrongPos( sal_Int32 nValue ) const
+{
+ sal_uInt16 nMax = Count();
+ sal_uInt16 nMin = 0;
+
+ if( nMax > 0 )
+ {
+ // For smart tag lists, we may not use a binary search. We return the
+ // position of the first smart tag which covers nValue
+ if ( !maList[0].maType.isEmpty() || maList[0].mpSubList )
+ {
+ auto aIter = std::find_if(maList.begin(), maList.end(),
+ [nValue](const SwWrongArea& rST) {
+ return (rST.mnPos <= nValue && nValue < rST.mnPos + rST.mnLen)
+ || (rST.mnPos > nValue);
+ });
+ return o3tl::narrowing<sal_uInt16>(std::distance(maList.begin(), aIter));
+ }
+
+ --nMax;
+ sal_uInt16 nMid = 0;
+ while( nMin <= nMax )
+ {
+ nMid = nMin + ( nMax - nMin ) / 2;
+ const sal_Int32 nTmp = Pos( nMid );
+ if( nTmp == nValue )
+ {
+ nMin = nMid;
+ break;
+ }
+ else if( nTmp < nValue )
+ {
+ if( nTmp + Len( nMid ) >= nValue )
+ {
+ nMin = nMid;
+ break;
+ }
+ nMin = nMid + 1;
+ }
+ else if( nMid == 0 )
+ {
+ break;
+ }
+ else
+ nMax = nMid - 1;
+ }
+ }
+
+ // nMin now points to an index i into the wrong list which
+ // 1. nValue is inside [ Area[i].pos, Area[i].pos + Area[i].len ] (inclusive!!!)
+ // 2. nValue < Area[i].pos
+
+ return nMin;
+}
+
+void SwWrongList::Invalidate_( sal_Int32 nBegin, sal_Int32 nEnd )
+{
+ if ( nBegin < GetBeginInv() )
+ mnBeginInvalid = nBegin;
+ if ( nEnd > GetEndInv() || GetEndInv() == COMPLETE_STRING )
+ mnEndInvalid = nEnd;
+}
+
+void SwWrongList::SetInvalid( sal_Int32 nBegin, sal_Int32 nEnd )
+{
+ mnBeginInvalid = nBegin;
+ mnEndInvalid = nEnd;
+}
+
+/** Change all values after the given position.
+
+ Needed after insert/deletion of characters.
+
+ @param nPos position after that everything should be changed
+ @param nDiff amount how much the positions should be moved
+ */
+void SwWrongList::Move( sal_Int32 nPos, sal_Int32 nDiff )
+{
+ sal_uInt16 i = GetWrongPos( nPos );
+ if( nDiff < 0 )
+ {
+ const sal_Int32 nEnd = nPos - nDiff;
+ sal_uInt16 nLst = i;
+ bool bJump = false;
+ while( nLst < Count() && Pos( nLst ) < nEnd )
+ ++nLst;
+ if( nLst > i )
+ {
+ const sal_Int32 nWrPos = Pos( nLst - 1 );
+ if ( nWrPos <= nPos )
+ {
+ sal_Int32 nWrLen = Len( nLst - 1 );
+ // calculate new length of word
+ nWrLen = ( nEnd > nWrPos + nWrLen ) ?
+ nPos - nWrPos :
+ nWrLen + nDiff;
+ if( nWrLen )
+ {
+ maList[--nLst].mnLen = nWrLen;
+ bJump = true;
+ }
+ }
+ }
+ Remove( i, nLst - i );
+
+ if ( bJump )
+ ++i;
+ if( COMPLETE_STRING == GetBeginInv() )
+ SetInvalid( nPos ? nPos - 1 : nPos, nPos + 1 );
+ else
+ {
+ ShiftLeft( mnBeginInvalid, nPos, nEnd );
+ if( mnEndInvalid != COMPLETE_STRING )
+ ShiftLeft( mnEndInvalid, nPos, nEnd );
+ Invalidate_( nPos ? nPos - 1 : nPos, nPos + 1 );
+ }
+ }
+ else
+ {
+ const sal_Int32 nEnd = nPos + nDiff;
+ if( COMPLETE_STRING != GetBeginInv() )
+ {
+ if( mnBeginInvalid > nPos )
+ mnBeginInvalid += nDiff;
+ if( mnEndInvalid >= nPos && mnEndInvalid != COMPLETE_STRING )
+ mnEndInvalid += nDiff;
+ }
+ // If the pointer is in the middle of a wrong word,
+ // invalidation must happen from the beginning of that word.
+ if( i < Count() )
+ {
+ const sal_Int32 nWrPos = Pos( i );
+ if (nPos >= nWrPos)
+ {
+ Invalidate( nWrPos, nEnd );
+ const sal_Int32 nWrLen = Len( i ) + nDiff;
+ maList[i++].mnLen = nWrLen;
+ Invalidate( nWrPos, nWrPos + nWrLen );
+ }
+ }
+ else
+ Invalidate( nPos, nEnd );
+ }
+ while( i < Count() )
+ {
+ maList[i++].mnPos += nDiff;
+ }
+}
+
+// TODO: Complete documentation
+/** Remove given range of entries
+
+ For a given range [nPos, nPos + nLen[ and an index nIndex, this function
+ basically counts the number of SwWrongArea entries starting with nIndex
+ up to nPos + nLen. All these entries are removed.
+
+ @param rStart ???
+ @param rEnd ???
+ @param nPos starting position of the range
+ @param nLen length of the range
+ @param nIndex index to start lookup at
+ @param nCursorPos ???
+
+ @return <true> if ???
+ */
+auto SwWrongList::Fresh( sal_Int32 &rStart, sal_Int32 &rEnd, sal_Int32 nPos,
+ sal_Int32 nLen, sal_uInt16 nIndex, sal_Int32 nCursorPos ) -> FreshState
+{
+ // length of word must be greater than 0
+ // only report a spelling error if the cursor position is outside the word,
+ // so that the user is not annoyed while typing
+ FreshState eRet = nLen
+ ? (nCursorPos > nPos + nLen || nCursorPos < nPos)
+ ? FreshState::FRESH
+ : FreshState::CURSOR
+ : FreshState::NOTHING;
+
+ sal_Int32 nWrPos = 0;
+ sal_Int32 nWrEnd = rEnd;
+ sal_uInt16 nCnt = nIndex;
+ if( nCnt < Count() )
+ {
+ nWrPos = Pos( nCnt );
+ if( nWrPos < nPos && rStart > nWrPos )
+ rStart = nWrPos;
+ }
+
+ while( nCnt < Count() )
+ {
+ nWrPos = Pos( nCnt );
+ if ( nWrPos >= nPos )
+ break;
+ nWrEnd = nWrPos + Len( nCnt++ );
+ }
+
+ if( nCnt < Count() && nWrPos == nPos && Len( nCnt ) == nLen )
+ {
+ ++nCnt;
+ eRet = FreshState::FRESH;
+ }
+ else
+ {
+ if (FreshState::FRESH == eRet)
+ {
+ if( rStart > nPos )
+ rStart = nPos;
+ nWrEnd = nPos + nLen;
+ }
+ }
+
+ nPos += nLen;
+
+ if( nCnt < Count() )
+ {
+ nWrPos = Pos( nCnt );
+ if( nWrPos < nPos && rStart > nWrPos )
+ rStart = nWrPos;
+ }
+
+ while( nCnt < Count() )
+ {
+ nWrPos = Pos( nCnt );
+ if ( nWrPos >= nPos )
+ break;
+ nWrEnd = nWrPos + Len( nCnt++ );
+ }
+
+ if( rEnd < nWrEnd )
+ rEnd = nWrEnd;
+
+ Remove( nIndex, nCnt - nIndex );
+
+ return eRet;
+}
+
+void SwWrongList::Invalidate( sal_Int32 nBegin, sal_Int32 nEnd )
+{
+ if (COMPLETE_STRING == GetBeginInv())
+ SetInvalid( nBegin, nEnd );
+ else
+ Invalidate_( nBegin, nEnd );
+}
+
+bool SwWrongList::InvalidateWrong( )
+{
+ if( Count() )
+ {
+ const sal_Int32 nFirst = Pos( 0 );
+ const sal_Int32 nLast = Pos( Count() - 1 ) + Len( Count() - 1 );
+ Invalidate( nFirst, nLast );
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<SwWrongList> SwWrongList::SplitList( sal_Int32 nSplitPos )
+{
+ std::unique_ptr<SwWrongList> pRet;
+ sal_uInt16 nLst = 0;
+ while( nLst < Count() && Pos( nLst ) < nSplitPos )
+ ++nLst;
+ if( nLst )
+ {
+ sal_Int32 nWrPos = Pos( nLst - 1 );
+ sal_Int32 nWrLen = Len( nLst - 1 );
+ if ( nWrPos+nWrLen > nSplitPos )
+ {
+ nWrLen += nWrPos - nSplitPos;
+ maList[--nLst].mnPos = nSplitPos;
+ maList[nLst].mnLen = nWrLen;
+ }
+ }
+ if( nLst )
+ {
+ if( WRONGLIST_GRAMMAR == GetWrongListType() )
+ pRet.reset(new SwGrammarMarkUp());
+ else
+ pRet.reset(new SwWrongList( GetWrongListType() ));
+ pRet->Insert(0, maList.begin(), ( nLst >= maList.size() ? maList.end() : maList.begin() + nLst ) );
+ pRet->SetInvalid( GetBeginInv(), GetEndInv() );
+ pRet->Invalidate_( nSplitPos ? nSplitPos - 1 : nSplitPos, nSplitPos );
+ Remove( 0, nLst );
+ }
+ if( COMPLETE_STRING == GetBeginInv() )
+ SetInvalid( 0, 1 );
+ else
+ {
+ ShiftLeft( mnBeginInvalid, 0, nSplitPos );
+ if( mnEndInvalid != COMPLETE_STRING )
+ ShiftLeft( mnEndInvalid, 0, nSplitPos );
+ Invalidate_( 0, 1 );
+ }
+ for (nLst = 0; nLst < Count(); ++nLst )
+ {
+ maList[nLst].mnPos -= nSplitPos;
+ }
+ return pRet;
+}
+
+void SwWrongList::JoinList( SwWrongList* pNext, sal_Int32 nInsertPos )
+{
+ if (pNext)
+ {
+ OSL_ENSURE( GetWrongListType() == pNext->GetWrongListType(), "type mismatch with next list" );
+
+ sal_uInt16 nCnt = Count();
+ pNext->Move( 0, nInsertPos );
+ Insert(nCnt, pNext->maList.begin(), pNext->maList.end());
+
+ Invalidate( pNext->GetBeginInv(), pNext->GetEndInv() );
+ if( nCnt && Count() > nCnt )
+ {
+ sal_Int32 nWrPos = Pos( nCnt );
+ sal_Int32 nWrLen = Len( nCnt );
+ if( !nWrPos )
+ {
+ nWrPos += nInsertPos;
+ nWrLen -= nInsertPos;
+ maList[nCnt].mnPos = nWrPos;
+ maList[nCnt].mnLen = nWrLen;
+ }
+ if( nWrPos == Pos( nCnt - 1 ) + Len( nCnt - 1 ) )
+ {
+ nWrLen += Len( nCnt - 1 );
+ maList[nCnt - 1].mnLen = nWrLen;
+ Remove( nCnt, 1 );
+ }
+ }
+ }
+ Invalidate( nInsertPos ? nInsertPos - 1 : nInsertPos, nInsertPos + 1 );
+}
+
+void SwWrongList::InsertSubList( sal_Int32 nNewPos, sal_Int32 nNewLen, sal_uInt16 nWhere, SwWrongList* pSubList )
+{
+ if (pSubList)
+ {
+ OSL_ENSURE( GetWrongListType() == pSubList->GetWrongListType(), "type mismatch with sub list" );
+ }
+ std::vector<SwWrongArea>::iterator i = maList.begin();
+ if ( nWhere >= maList.size() )
+ i = maList.end(); // robust
+ else
+ i += nWhere;
+ maList.insert(i, SwWrongArea( OUString(), nullptr, nNewPos, nNewLen, pSubList ) );
+}
+
+// New functions: Necessary because SwWrongList has been changed to use std::vector
+void SwWrongList::Insert(sal_uInt16 nWhere, std::vector<SwWrongArea>::iterator startPos, std::vector<SwWrongArea>::iterator const & endPos)
+{
+ std::vector<SwWrongArea>::iterator i = maList.begin();
+ if ( nWhere >= maList.size() )
+ i = maList.end(); // robust
+ else
+ i += nWhere;
+ maList.insert(i, startPos, endPos); // insert [startPos, endPos[ before i
+
+ // ownership of the sublist is passed to maList, therefore we have to set the
+ // pSubList-Pointers to 0
+ while ( startPos != endPos )
+ {
+ (*startPos).mpSubList = nullptr;
+ ++startPos;
+ }
+}
+
+void SwWrongList::Remove(sal_uInt16 nIdx, sal_uInt16 nLen )
+{
+ if ( nIdx >= maList.size() ) return;
+ std::vector<SwWrongArea>::iterator i1 = maList.begin();
+ i1 += nIdx;
+
+ std::vector<SwWrongArea>::iterator i2 = i1;
+ if ( nIdx + nLen >= o3tl::narrowing<sal_uInt16>(maList.size()) )
+ i2 = maList.end(); // robust
+ else
+ i2 += nLen;
+
+ std::vector<SwWrongArea>::iterator iLoop = i1;
+ while ( iLoop != i2 )
+ {
+ delete (*iLoop).mpSubList;
+ ++iLoop;
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ const int nOldSize = Count();
+#endif
+
+ maList.erase(i1, i2);
+
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( Count() + nLen == nOldSize, "SwWrongList::Remove() trouble" );
+#endif
+}
+
+void SwWrongList::RemoveEntry( sal_Int32 nBegin, sal_Int32 nEnd ) {
+ std::vector<SwWrongArea>::const_iterator aEnd(maList.end());
+ auto aDelIter = std::find_if(maList.cbegin(), aEnd,
+ [nBegin](const SwWrongArea& rST) { return rST.mnPos >= nBegin; });
+ auto aIter = aDelIter;
+ if( WRONGLIST_GRAMMAR == GetWrongListType() )
+ {
+ if( nBegin < nEnd )
+ {
+ aIter = std::find_if(aDelIter, aEnd,
+ [nEnd](const SwWrongArea& rST) { return rST.mnPos >= nEnd; });
+ }
+ }
+ else
+ {
+ aIter = std::find_if(aDelIter, aEnd,
+ [nBegin, nEnd](const SwWrongArea& rST) {
+ return (rST.mnPos != nBegin) || ((rST.mnPos + rST.mnLen) != nEnd);
+ });
+ }
+ auto nDel = o3tl::narrowing<sal_uInt16>(std::distance(aDelIter, aIter));
+ if( nDel )
+ {
+ auto nDelPos = o3tl::narrowing<sal_uInt16>(std::distance(maList.cbegin(), aDelIter));
+ Remove( nDelPos, nDel );
+ }
+}
+
+bool SwWrongList::LookForEntry( sal_Int32 nBegin, sal_Int32 nEnd ) {
+ auto aIter = std::find_if(maList.begin(), maList.end(),
+ [nBegin](const SwWrongArea& rST) { return rST.mnPos >= nBegin; });
+ return aIter != maList.end()
+ && nBegin == (*aIter).mnPos
+ && nEnd == (*aIter).mnPos + (*aIter).mnLen;
+}
+
+void SwWrongList::Insert( const OUString& rType,
+ css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag,
+ sal_Int32 nNewPos, sal_Int32 nNewLen )
+{
+ auto aIter = std::find_if(maList.begin(), maList.end(),
+ [nNewPos](const SwWrongArea& rST) { return nNewPos <= rST.mnPos; });
+ if ( aIter != maList.end() && nNewPos == (*aIter).mnPos )
+ {
+ const sal_Int32 nSTPos = (*aIter).mnPos;
+
+ aIter = std::find_if(aIter, maList.end(),
+ [nSTPos, nNewLen](const SwWrongArea& rST) { return rST.mnPos != nSTPos || nNewLen < rST.mnLen; });
+ }
+
+ maList.insert(aIter, SwWrongArea( rType, meType, xPropertyBag, nNewPos, nNewLen) );
+}
+
+namespace sw {
+
+WrongListIteratorBase::WrongListIteratorBase(SwTextFrame const& rFrame,
+ SwWrongList const* (SwTextNode::*pGetWrongList)() const)
+ : m_pGetWrongList(pGetWrongList)
+ , m_pMergedPara(rFrame.GetMergedPara())
+ , m_CurrentExtent(0)
+ , m_CurrentIndex(0)
+ , m_pWrongList(m_pMergedPara
+ ? nullptr
+ : (rFrame.GetTextNodeFirst()->*pGetWrongList)())
+{
+}
+
+WrongListIteratorBase::WrongListIteratorBase(SwWrongList const& rWrongList)
+ : m_pGetWrongList(nullptr)
+ , m_pMergedPara(nullptr)
+ , m_CurrentExtent(0)
+ , m_CurrentIndex(0)
+ , m_pWrongList(&rWrongList)
+{
+}
+
+WrongListIterator::WrongListIterator(SwTextFrame const& rFrame,
+ SwWrongList const* (SwTextNode::*pGetWrongList)() const)
+ : WrongListIteratorBase(rFrame, pGetWrongList)
+{
+}
+
+WrongListIterator::WrongListIterator(SwWrongList const& rWrongList)
+ : WrongListIteratorBase(rWrongList)
+{
+}
+
+bool WrongListIterator::Check(TextFrameIndex & rStart, TextFrameIndex & rLen)
+{
+ if (m_pMergedPara)
+ {
+ if (rStart < m_CurrentIndex)
+ { // rewind
+ m_CurrentExtent = 0;
+ m_CurrentIndex = TextFrameIndex(0);
+ }
+ while (m_CurrentExtent < m_pMergedPara->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]);
+ if (rStart + rLen <= m_CurrentIndex)
+ {
+ return false;
+ }
+ else if (rStart < m_CurrentIndex)
+ {
+ rLen -= m_CurrentIndex - rStart;
+ assert(0 < sal_Int32(rLen));
+ rStart = m_CurrentIndex;
+ }
+ if (m_CurrentIndex <= rStart &&
+ rStart < m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart))
+ {
+ SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)());
+ // found the extent containing start - first, call Check
+ sal_Int32 nStart(rExtent.nStart + sal_Int32(rStart - m_CurrentIndex)); // (m_CurrentIndex - m_CurrentNodeIndex));
+ sal_Int32 nLen;
+ if (sal_Int32(rLen) < rExtent.nEnd - nStart)
+ {
+ nLen = sal_Int32(rLen);
+ }
+ else
+ {
+ sal_Int32 nInLen(rLen);
+ nLen = rExtent.nEnd - nStart;
+ nInLen -= nLen;
+ for (size_t i = m_CurrentExtent + 1;
+ i < m_pMergedPara->extents.size(); ++i)
+ {
+ sw::Extent const& rExtentEnd(m_pMergedPara->extents[i]);
+ if (rExtentEnd.pNode != rExtent.pNode)
+ {
+ break;
+ }
+ // add gap too
+ nLen += rExtentEnd.nStart - m_pMergedPara->extents[i-1].nEnd;
+ if (nInLen <= rExtentEnd.nEnd - rExtentEnd.nStart)
+ {
+ nLen += nInLen;
+ break;
+ }
+ nLen += rExtentEnd.nEnd - rExtentEnd.nStart;
+ nInLen -= rExtentEnd.nEnd - rExtentEnd.nStart;
+ }
+ }
+ if (pWrongList && pWrongList->Check(nStart, nLen))
+ {
+ // check if there's overlap with this extent
+ if (rExtent.nStart <= nStart && nStart < rExtent.nEnd)
+ {
+ // yes - now compute end position / length
+ sal_Int32 const nEnd(nStart + nLen);
+ rStart = m_CurrentIndex + TextFrameIndex(nStart - rExtent.nStart);
+ TextFrameIndex const nOrigLen(rLen);
+ if (nEnd <= rExtent.nEnd)
+ {
+ rLen = TextFrameIndex(nEnd - nStart);
+ }
+ else // have to search other extents for the end...
+ {
+ rLen = TextFrameIndex(rExtent.nEnd - nStart);
+ for (size_t i = m_CurrentExtent + 1;
+ i < m_pMergedPara->extents.size(); ++i)
+ {
+ sw::Extent const& rExtentEnd(m_pMergedPara->extents[i]);
+ if (rExtentEnd.pNode != rExtent.pNode
+ || nEnd <= rExtentEnd.nStart)
+ {
+ break;
+ }
+ if (nEnd <= rExtentEnd.nEnd)
+ {
+ rLen += TextFrameIndex(nEnd - rExtentEnd.nStart);
+ break;
+ }
+ rLen += TextFrameIndex(rExtentEnd.nEnd - rExtentEnd.nStart);
+ }
+ }
+ assert(rLen <= nOrigLen); (void) nOrigLen;
+ return true;
+ }
+ }
+ }
+ m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart);
+ ++m_CurrentExtent;
+ }
+ return false;
+ }
+ else if (m_pWrongList)
+ {
+ sal_Int32 nStart(rStart);
+ sal_Int32 nLen(rLen);
+ bool const bRet(m_pWrongList->Check(nStart, nLen));
+ rStart = TextFrameIndex(nStart);
+ rLen = TextFrameIndex(nLen);
+ return bRet;
+ }
+ return false;
+}
+
+const SwWrongArea*
+WrongListIterator::GetWrongElement(TextFrameIndex const nStart)
+{
+ if (m_pMergedPara)
+ {
+ if (nStart < m_CurrentIndex)
+ { // rewind
+ m_CurrentExtent = 0;
+ m_CurrentIndex = TextFrameIndex(0);
+ }
+ while (m_CurrentExtent < m_pMergedPara->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]);
+ if (m_CurrentIndex <= nStart &&
+ nStart <= m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart))
+ {
+ // note: the returned object isn't wrapped because fntcache.cxx
+ // does not look at its positions, only its formatting props
+ SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)());
+ if (pWrongList)
+ {
+ sal_Int32 const nNStart(rExtent.nStart + sal_Int32(nStart - m_CurrentIndex)); // (m_CurrentIndex - m_CurrentNodeIndex));
+ sal_Int16 const nPos(pWrongList->GetWrongPos(nNStart));
+ return pWrongList->GetElement(nPos);
+ }
+ }
+ m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart);
+ ++m_CurrentExtent;
+ }
+ return nullptr;
+ }
+ else if (m_pWrongList)
+ {
+ sal_Int16 const nPos(m_pWrongList->GetWrongPos(sal_Int32(nStart)));
+ return m_pWrongList->GetElement(nPos);
+ }
+ return nullptr;
+}
+
+WrongListIteratorCounter::WrongListIteratorCounter(SwTextFrame const& rFrame,
+ SwWrongList const* (SwTextNode::*pGetWrongList)() const)
+ : WrongListIteratorBase(rFrame, pGetWrongList)
+{
+}
+
+WrongListIteratorCounter::WrongListIteratorCounter(SwWrongList const& rWrongList)
+ : WrongListIteratorBase(rWrongList)
+{
+}
+
+sal_uInt16 WrongListIteratorCounter::GetElementCount()
+{
+ if (m_pMergedPara)
+ {
+ sal_uInt16 nRet(0);
+ m_CurrentExtent = 0;
+ m_CurrentIndex = TextFrameIndex(0);
+ SwNode const* pNode(nullptr);
+ sal_uInt16 InCurrentNode(0);
+ while (m_CurrentExtent < m_pMergedPara->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]);
+ if (rExtent.pNode != pNode)
+ {
+ InCurrentNode = 0;
+ pNode = rExtent.pNode;
+ }
+ SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)());
+ for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode)
+ {
+ SwWrongArea const*const pWrong(pWrongList->GetElement(InCurrentNode));
+ TextFrameIndex const nExtentEnd(
+ m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart));
+ if (nExtentEnd <= TextFrameIndex(pWrong->mnPos))
+ {
+ break; // continue outer loop
+ }
+ if (m_CurrentIndex < TextFrameIndex(pWrong->mnPos + pWrong->mnLen))
+ {
+ ++nRet;
+ }
+ }
+ m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart);
+ ++m_CurrentExtent;
+ }
+ return nRet;
+ }
+ else if (m_pWrongList)
+ {
+ return m_pWrongList->Count();
+ }
+ return 0;
+}
+
+std::optional<std::pair<TextFrameIndex, TextFrameIndex>>
+WrongListIteratorCounter::GetElementAt(sal_uInt16 nIndex)
+{
+ if (m_pMergedPara)
+ {
+ m_CurrentExtent = 0;
+ m_CurrentIndex = TextFrameIndex(0);
+ SwNode const* pNode(nullptr);
+ sal_uInt16 InCurrentNode(0);
+ while (m_CurrentExtent < m_pMergedPara->extents.size())
+ {
+ sw::Extent const& rExtent(m_pMergedPara->extents[m_CurrentExtent]);
+ if (rExtent.pNode != pNode)
+ {
+ InCurrentNode = 0;
+ pNode = rExtent.pNode;
+ }
+ SwWrongList const*const pWrongList((rExtent.pNode->*m_pGetWrongList)());
+ for (; pWrongList && InCurrentNode < pWrongList->Count(); ++InCurrentNode)
+ {
+ SwWrongArea const*const pWrong(pWrongList->GetElement(InCurrentNode));
+ TextFrameIndex const nExtentEnd(
+ m_CurrentIndex + TextFrameIndex(rExtent.nEnd - rExtent.nStart));
+ if (nExtentEnd <= TextFrameIndex(pWrong->mnPos))
+ {
+ break; // continue outer loop
+ }
+ if (m_CurrentIndex < TextFrameIndex(pWrong->mnPos + pWrong->mnLen))
+ {
+ if (nIndex == 0)
+ {
+ return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>(
+ std::pair<TextFrameIndex, TextFrameIndex>(
+ m_CurrentIndex - TextFrameIndex(rExtent.nStart -
+ std::max(rExtent.nStart, pWrong->mnPos)),
+ m_CurrentIndex - TextFrameIndex(rExtent.nStart -
+ std::min(pWrong->mnPos + pWrong->mnLen, rExtent.nEnd))));
+ }
+ --nIndex;
+ }
+ }
+ m_CurrentIndex += TextFrameIndex(rExtent.nEnd - rExtent.nStart);
+ ++m_CurrentExtent;
+ }
+ return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>();
+ }
+ else if (m_pWrongList)
+ {
+ SwWrongArea const*const pWrong(m_pWrongList->GetElement(nIndex));
+ return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>(
+ std::pair<TextFrameIndex, TextFrameIndex>(
+ TextFrameIndex(pWrong->mnPos),
+ TextFrameIndex(pWrong->mnPos + pWrong->mnLen)));
+ }
+ return std::optional<std::pair<TextFrameIndex, TextFrameIndex>>();
+}
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx
new file mode 100644
index 0000000000..a14c5a4851
--- /dev/null
+++ b/sw/source/core/text/xmldump.cxx
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <frame.hxx>
+#include <frmfmt.hxx>
+#include <ftnfrm.hxx>
+#include <sectfrm.hxx>
+#include <tabfrm.hxx>
+#include <pagefrm.hxx>
+#include <txtfrm.hxx>
+#include <cellfrm.hxx>
+#include <flyfrm.hxx>
+#include <hffrm.hxx>
+#include <rootfrm.hxx>
+#include <ndtxt.hxx>
+#include <sortedobjs.hxx>
+#include <swfont.hxx>
+#include <txttypes.hxx>
+#include <anchoredobject.hxx>
+#include <libxml/xmlwriter.h>
+#include <SwPortionHandler.hxx>
+#include <view.hxx>
+#include <flyfrms.hxx>
+#include <svx/svdobj.hxx>
+
+#include "porlay.hxx"
+
+const char* sw::PortionTypeToString(PortionType nType)
+{
+ switch (nType)
+ {
+ case PortionType::NONE:
+ return "PortionType::NONE";
+ case PortionType::FlyCnt:
+ return "PortionType::FlyCnt";
+
+ case PortionType::Hole:
+ return "PortionType::Hole";
+ case PortionType::TempEnd:
+ return "PortionType::TempEnd";
+ case PortionType::Break:
+ return "PortionType::Break";
+ case PortionType::Kern:
+ return "PortionType::Kern";
+ case PortionType::Arrow:
+ return "PortionType::Arrow";
+ case PortionType::Multi:
+ return "PortionType::Multi";
+ case PortionType::HiddenText:
+ return "PortionType::HiddenText";
+ case PortionType::ControlChar:
+ return "PortionType::ControlChar";
+ case PortionType::Bookmark:
+ return "PortionType::Bookmark";
+
+ case PortionType::Text:
+ return "PortionType::Text";
+ case PortionType::Lay:
+ return "PortionType::Lay";
+ case PortionType::Para:
+ return "PortionType::Para";
+ case PortionType::Hanging:
+ return "PortionType::Hanging";
+
+ case PortionType::Drop:
+ return "PortionType::Drop";
+ case PortionType::Tox:
+ return "PortionType::Tox";
+ case PortionType::IsoTox:
+ return "PortionType::IsoTox";
+ case PortionType::Ref:
+ return "PortionType::Ref";
+ case PortionType::IsoRef:
+ return "PortionType::IsoRef";
+ case PortionType::Meta:
+ return "PortionType::Meta";
+ case PortionType::ContentControl:
+ return "PortionType::ContentControl";
+ case PortionType::FieldMark:
+ return "PortionType::FieldMark";
+ case PortionType::FieldFormCheckbox:
+ return "PortionType::FieldFormCheckbox";
+ case PortionType::InputField:
+ return "PortionType::InputField";
+
+ case PortionType::Expand:
+ return "PortionType::Expand";
+ case PortionType::Blank:
+ return "PortionType::Blank";
+ case PortionType::PostIts:
+ return "PortionType::PostIts";
+
+ case PortionType::Hyphen:
+ return "PortionType::Hyphen";
+ case PortionType::HyphenStr:
+ return "PortionType::HyphenStr";
+ case PortionType::SoftHyphen:
+ return "PortionType::SoftHyphen";
+ case PortionType::SoftHyphenStr:
+ return "PortionType::SoftHyphenStr";
+ case PortionType::SoftHyphenComp:
+ return "PortionType::SoftHyphenComp";
+
+ case PortionType::Field:
+ return "PortionType::Field";
+ case PortionType::Hidden:
+ return "PortionType::Hidden";
+ case PortionType::QuoVadis:
+ return "PortionType::QuoVadis";
+ case PortionType::ErgoSum:
+ return "PortionType::ErgoSum";
+ case PortionType::Combined:
+ return "PortionType::Combined";
+ case PortionType::Footnote:
+ return "PortionType::Footnote";
+
+ case PortionType::FootnoteNum:
+ return "PortionType::FootnoteNum";
+ case PortionType::Number:
+ return "PortionType::Number";
+ case PortionType::Bullet:
+ return "PortionType::Bullet";
+ case PortionType::GrfNum:
+ return "PortionType::GrfNum";
+
+ case PortionType::Glue:
+ return "PortionType::Glue";
+
+ case PortionType::Margin:
+ return "PortionType::Margin";
+
+ case PortionType::Fix:
+ return "PortionType::Fix";
+ case PortionType::Fly:
+ return "PortionType::Fly";
+
+ case PortionType::Tab:
+ return "PortionType::Tab";
+
+ case PortionType::TabRight:
+ return "PortionType::TabRight";
+ case PortionType::TabCenter:
+ return "PortionType::TabCenter";
+ case PortionType::TabDecimal:
+ return "PortionType::TabDecimal";
+
+ case PortionType::TabLeft:
+ return "PortionType::TabLeft";
+ default:
+ return "Unknown";
+ }
+}
+
+void SwFrame::dumpTopMostAsXml(xmlTextWriterPtr writer) const
+{
+ const SwFrame* pFrame = this;
+ while (pFrame->GetUpper())
+ {
+ pFrame = pFrame->GetUpper();
+ }
+
+ pFrame->dumpAsXml(writer);
+}
+
+void SwFrame::dumpInfosAsXml( xmlTextWriterPtr writer ) const
+{
+ // output the Frame
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) );
+ getFrameArea().dumpAsXmlAttributes(writer);
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("mbFixSize"), BAD_CAST(OString::boolean(HasFixSize()).getStr()));
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("mbFrameAreaPositionValid"), BAD_CAST(OString::boolean(isFrameAreaPositionValid()).getStr()));
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("mbFrameAreaSizeValid"), BAD_CAST(OString::boolean(isFrameAreaSizeValid()).getStr()));
+ (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("mbFramePrintAreaValid"), BAD_CAST(OString::boolean(isFramePrintAreaValid()).getStr()));
+ (void)xmlTextWriterEndElement( writer );
+
+ // output the print area
+ (void)xmlTextWriterStartElement( writer, BAD_CAST( "prtBounds" ) );
+ getFramePrintArea().dumpAsXmlAttributes(writer);
+ (void)xmlTextWriterEndElement( writer );
+}
+
+void SwFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const
+{
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "id" ), "%" SAL_PRIuUINT32, GetFrameId() );
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "symbol" ), "%s", BAD_CAST( typeid( *this ).name( ) ) );
+ if ( GetNext( ) )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "next" ), "%" SAL_PRIuUINT32, GetNext()->GetFrameId() );
+ if ( GetPrev( ) )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "prev" ), "%" SAL_PRIuUINT32, GetPrev()->GetFrameId() );
+ if ( GetUpper( ) )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "upper" ), "%" SAL_PRIuUINT32, GetUpper()->GetFrameId() );
+ if ( GetLower( ) )
+ (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "lower" ), "%" SAL_PRIuUINT32, GetLower()->GetFrameId() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */