diff options
Diffstat (limited to 'sw/source/core/text')
62 files changed, 6530 insertions, 3035 deletions
diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 088f21c00b10..bbe216898f70 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -17,11 +17,12 @@ * 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 <EnhancedPDFExportHelper.hxx> #include <hintids.hxx> #include <sot/exchange.hxx> @@ -33,6 +34,7 @@ #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> @@ -65,36 +67,35 @@ #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 <stack> #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> -using namespace ::com::sun::star; - -// Some static data structures - -TableColumnsMap SwEnhancedPDFExportHelper::s_aTableColumnsMap; -LinkIdMap SwEnhancedPDFExportHelper::s_aLinkIdMap; -NumListIdMap SwEnhancedPDFExportHelper::s_aNumListIdMap; -NumListBodyIdMap SwEnhancedPDFExportHelper::s_aNumListBodyIdMap; -FrameTagIdMap SwEnhancedPDFExportHelper::s_aFrameTagIdMap; +#include <stack> +#include <map> +#include <set> +#include <optional> -LanguageType SwEnhancedPDFExportHelper::s_eLanguageDefault = LANGUAGE_SYSTEM; +using namespace ::com::sun::star; #if OSL_DEBUG_LEVEL > 1 @@ -125,19 +126,58 @@ void lcl_DBGCheckStack() #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"; +constexpr OUString aTableHeadingName = u"Table Heading"_ustr; +constexpr OUString aQuotations = u"Quotations"_ustr; +constexpr OUString aCaption = u"Caption"_ustr; +constexpr OUString aHeading = u"Heading"_ustr; +constexpr OUString aQuotation = u"Quotation"_ustr; +constexpr OUString aSourceText = u"Source Text"_ustr; // PDF Tag Names: constexpr OUStringLiteral aDocumentString = u"Document"; -constexpr OUStringLiteral aDivString = u"Div"; +constexpr OUString aDivString = u"Div"_ustr; constexpr OUStringLiteral aSectString = u"Sect"; constexpr OUStringLiteral aHString = u"H"; constexpr OUStringLiteral aH1String = u"H1"; @@ -146,11 +186,16 @@ 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 aListBodyString = u"LBody"; +constexpr OUStringLiteral aListLabelString = u"Lbl"; +constexpr OUString aListBodyString = u"LBody"_ustr; constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote"; -constexpr OUStringLiteral aCaptionString = u"Caption"; +constexpr OUString aCaptionString = u"Caption"_ustr; constexpr OUStringLiteral aIndexString = u"Index"; constexpr OUStringLiteral aTOCString = u"TOC"; constexpr OUStringLiteral aTOCIString = u"TOCI"; @@ -160,11 +205,11 @@ constexpr OUStringLiteral aTDString = u"TD"; constexpr OUStringLiteral aTHString = u"TH"; constexpr OUStringLiteral aBibEntryString = u"BibEntry"; constexpr OUStringLiteral aQuoteString = u"Quote"; -constexpr OUStringLiteral aSpanString = u"Span"; +constexpr OUString aSpanString = u"Span"_ustr; constexpr OUStringLiteral aCodeString = u"Code"; constexpr OUStringLiteral aFigureString = u"Figure"; constexpr OUStringLiteral aFormulaString = u"Formula"; -constexpr OUStringLiteral aLinkString = u"Link"; +constexpr OUString aLinkString = u"Link"_ustr; constexpr OUStringLiteral aNoteString = u"Note"; // returns true if first paragraph in cell frame has 'table heading' style @@ -183,6 +228,22 @@ bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) 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; } @@ -208,25 +269,29 @@ bool lcl_IsInNonStructEnv( const SwFrame& rFrame ) } // Generate key from frame for reopening tags: -void* lcl_GetKeyFromFrame( const SwFrame& rFrame ) +void const* lcl_GetKeyFromFrame( const SwFrame& rFrame ) { - void* pKey = nullptr; + void const* pKey = nullptr; if ( rFrame.IsPageFrame() ) - pKey = const_cast<void*>(static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()))); + pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess())); else if ( rFrame.IsTextFrame() ) - pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst())); + pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst()); else if ( rFrame.IsSctFrame() ) - pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection())); + pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection()); else if ( rFrame.IsTabFrame() ) - pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable())); + pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable()); else if ( rFrame.IsRowFrame() ) - pKey = const_cast<void*>(static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine())); + 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 = const_cast<void*>(static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan( *pTable ))); + 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; @@ -272,7 +337,7 @@ bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, con // 3. Check for hidden text attribute if(rNd.IsHidden()) return false; - if(!rShell.GotoFormatField(rField) || rShell.SelectHiddenRange()) + if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false)) { rShell.SwCursorShell::ClearMark(); return false; @@ -280,6 +345,40 @@ bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, con 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; + std::pair<Point, bool> const tmp(center, false); + SwContentFrame const*const pFrame( + pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp)); + if (pFrame->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, @@ -336,10 +435,16 @@ SwTaggedPDFHelper::~SwTaggedPDFHelper() #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; - sal_Int32 nReopenTag = -1; + void const* pReopenKey(nullptr); bool bContinue = false; // in some cases we just have to reopen a tag without early returning if ( mpFrameInfo ) @@ -356,12 +461,13 @@ bool SwTaggedPDFHelper::CheckReopenTag() // - 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() ) + else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink) { const SwFormatAnchor& rAnchor = static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor(); @@ -376,29 +482,29 @@ bool SwTaggedPDFHelper::CheckReopenTag() if ( pKeyFrame ) { - void* pKey = lcl_GetKeyFromFrame( *pKeyFrame ); - - if ( pKey ) + 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 { - FrameTagIdMap& rFrameTagIdMap = SwEnhancedPDFExportHelper::GetFrameTagIdMap(); - const FrameTagIdMap::const_iterator aIter = rFrameTagIdMap.find( pKey ); - if ( aIter != rFrameTagIdMap.end() ) - nReopenTag = (*aIter).second; + pReopenKey = pKey; } } } - if ( -1 != nReopenTag ) + 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(); - const bool bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag ); - OSL_ENSURE( bSuccess, "Failed to reopen tag" ); - -#if OSL_DEBUG_LEVEL > 1 - aStructStack.push_back( 99 ); -#endif + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey); + mpPDFExtOutDevData->SetCurrentStructureElement(id); - bRet = bSuccess; + bRet = true; } return bRet && !bContinue; @@ -408,8 +514,7 @@ void SwTaggedPDFHelper::CheckRestoreTag() const { if ( m_nRestoreCurrentTag != -1 ) { - const bool bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag ); - OSL_ENSURE( bSuccess, "Failed to restore reopened tag" ); + mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag ); #if OSL_DEBUG_LEVEL > 1 aStructStack.pop_back(); @@ -417,16 +522,62 @@ void SwTaggedPDFHelper::CheckRestoreTag() const } } -void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString ) +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->BeginStructureElement( eType, rString ); + 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( static_cast<sal_uInt16>(eType) ); + 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 @@ -437,40 +588,21 @@ void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUS if ( mpNumInfo ) { - const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(mpNumInfo->mrFrame); + 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 = SwEnhancedPDFExportHelper::GetNumListIdMap(); + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); rNumListIdMap[ pNodeNum ] = nId; } else if ( vcl::PDFWriter::LIBody == eType ) { - NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); rNumListBodyIdMap[ pNodeNum ] = nId; } } - else 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.IsTextFrame() && rFrame.GetDrawObjs() ) || - ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) || - ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) ) - { - const void* pKey = lcl_GetKeyFromFrame( rFrame ); - - if ( pKey ) - { - FrameTagIdMap& rFrameTagIdMap = SwEnhancedPDFExportHelper::GetFrameTagIdMap(); - rFrameTagIdMap[ pKey ] = nId; - } - } - } SetAttributes( eType ); } @@ -484,6 +616,23 @@ void SwTaggedPDFHelper::EndTag() #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 ) { @@ -510,6 +659,7 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bool bHeight = false; bool bBox = false; bool bRowSpan = false; + bool bAltText = false; // Check which attributes to set: @@ -519,6 +669,10 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bWritingMode = true; break; + case vcl::PDFWriter::Note: + bPlacement = true; + break; + case vcl::PDFWriter::Table : bPlacement = bWritingMode = @@ -537,6 +691,8 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) break; case vcl::PDFWriter::TableHeader : + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column); + [[fallthrough]]; case vcl::PDFWriter::TableData : bPlacement = bWritingMode = @@ -545,6 +701,12 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) bRowSpan = true; break; + case vcl::PDFWriter::Caption: + if (pFrame->IsSctFrame()) + { + break; + } + [[fallthrough]]; case vcl::PDFWriter::H1 : case vcl::PDFWriter::H2 : case vcl::PDFWriter::H3 : @@ -553,7 +715,6 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) case vcl::PDFWriter::H6 : case vcl::PDFWriter::Paragraph : case vcl::PDFWriter::Heading : - case vcl::PDFWriter::Caption : case vcl::PDFWriter::BlockQuote : bPlacement = @@ -568,11 +729,33 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) 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; } @@ -632,9 +815,9 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) if ( bTextIndent ) { OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); - const SvxLRSpaceItem &rSpace = - static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); - nVal = rSpace.GetTextFirstLineOffset(); + const SvxFirstLineIndentItem& rFirstLine( + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent()); + nVal = rFirstLine.GetTextFirstLineOffset(); if ( 0 != nVal ) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal ); } @@ -658,9 +841,25 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) } } - // Formally here bAlternateText was triggered for PDF export, but this - // was moved for more general use to primitives and usage in - // VclMetafileProcessor2D (see processGraphicPrimitive2D). + // 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 ) { @@ -688,9 +887,9 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) if ( bRowSpan ) { - const SwCellFrame* pThisCell = dynamic_cast<const SwCellFrame*>(pFrame); - if ( pThisCell ) + if ( pFrame->IsCellFrame() ) { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame); nVal = pThisCell->GetTabBox()->getRowSpan(); if ( nVal > 1 ) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal ); @@ -701,7 +900,7 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) SwRectFnSet fnRectX(pTabFrame); - const TableColumnsMapEntry& rCols = SwEnhancedPDFExportHelper::GetTableColumnsMap()[ pTable ]; + const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]); const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea()); const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea()); @@ -717,6 +916,12 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) } } } + + if (mpFrameInfo->m_isLink) + { + SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea()); + LinkLinkLink(*mpPDFExtOutDevData, aRect); + } } /* @@ -758,6 +963,58 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) 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; } @@ -802,7 +1059,7 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) { const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); - const LanguageType nDefaultLang = SwEnhancedPDFExportHelper::GetDefaultLanguage(); + const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); if ( nDefaultLang != nCurrentLanguage ) mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) ); @@ -810,18 +1067,55 @@ void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) if ( bLinkAttribute ) { - const LinkIdMap& rLinkIdMap = SwEnhancedPDFExportHelper::GetLinkIdMap(); SwRect aPorRect; rInf.CalcRect( *pPor, &aPorRect ); - const Point aPorCenter = aPorRect.Center(); - auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(), - [&aPorCenter](const IdMapEntry& rEntry) { return rEntry.first.IsInside(aPorCenter); }); - if (aIter != rLinkIdMap.end()) + 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()) { - sal_Int32 nLinkId = (*aIter).second; - mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::LinkAnnotation, nLinkId ); + 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); } } @@ -832,14 +1126,23 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() return; const SwFrame& rFrame = mpNumInfo->mrFrame; - OSL_ENSURE( rFrame.IsTextFrame(), "numbered only for text frames" ); + assert(rFrame.IsTextFrame()); const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame); // Lowers of NonStructureElements should not be considered: - - if ( lcl_IsInNonStructEnv( rTextFrame ) || rTextFrame.IsFollow() ) + 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()); @@ -871,7 +1174,7 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() if ( bNewSubListStart || bNoLabel ) { // Fine, we try to reopen the appropriate list body - NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); if ( bNewSubListStart ) { @@ -911,7 +1214,7 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() { // 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 = SwEnhancedPDFExportHelper::GetNumListIdMap(); + 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); @@ -942,9 +1245,9 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() else { // clear list maps in case a list has been interrupted - NumListIdMap& rNumListIdMap = SwEnhancedPDFExportHelper::GetNumListIdMap(); + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); rNumListIdMap.clear(); - NumListBodyIdMap& rNumListBodyIdMap = SwEnhancedPDFExportHelper::GetNumListBodyIdMap(); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); rNumListBodyIdMap.clear(); } @@ -958,7 +1261,12 @@ void SwTaggedPDFHelper::BeginNumberedListStructureElements() if ( bNewItemTag ) { BeginTag( vcl::PDFWriter::ListItem, aListItemString ); - BeginTag( vcl::PDFWriter::LIBody, aListBodyString ); + 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); + } } } @@ -968,7 +1276,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // Lowers of NonStructureElements should not be considered: - if ( lcl_IsInNonStructEnv( *pFrame ) ) + if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame()) return; // Check if we have to reopen an existing structure element. @@ -1026,7 +1334,35 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() { const SwSection* pSection = static_cast<const SwSectionFrame*>(pFrame)->GetSection(); - if ( SectionType::ToxContent == pSection->GetType() ) + + // 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 ) @@ -1057,8 +1393,18 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() case SwFrameType::Txt : { - const SwTextNode* pTextNd = - static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps(); + 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; @@ -1111,13 +1457,11 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // Heading: H1 - H6 - if (pTextNd->IsOutline() + if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1; + nRealLevel >= 0 + && !pTextNd->IsInRedlines() && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd)) { - int nRealLevel = pTextNd->GetAttrOutlineLevel()-1; - nRealLevel = std::min(nRealLevel, 5); - - nPDFType = static_cast<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel); switch(nRealLevel) { case 0 : @@ -1135,10 +1479,31 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() case 4 : aPDFType = aH5String; break; - default: + 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 @@ -1154,7 +1519,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() ) { // Special case: Open additional TOCI tag: - BeginTag( vcl::PDFWriter::TOCI, aTOCIString ); + BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString); } } } @@ -1173,7 +1538,7 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame); const SwTable* pTable = pTabFrame->GetTable(); - TableColumnsMap& rTableColumnsMap = SwEnhancedPDFExportHelper::GetTableColumnsMap(); + TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap); const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable ); if ( aIter == rTableColumnsMap.end() ) @@ -1255,9 +1620,20 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() // 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->Lower() && pFly->Lower()->IsNoTextFrame() ) + 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; @@ -1302,6 +1678,20 @@ void SwTaggedPDFHelper::BeginBlockStructureElements() 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(); @@ -1311,6 +1701,103 @@ void SwTaggedPDFHelper::EndStructureElements() 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; @@ -1322,6 +1809,26 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() 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; @@ -1336,49 +1843,58 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() 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 : { - 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 ); - } - // Check for Link: if( pInetFormatAttr ) { - nPDFType = vcl::PDFWriter::Link; - aPDFType = aLinkString; + 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) { - nPDFType = vcl::PDFWriter::Quote; - aPDFType = aQuoteString; + if (!isContinueSpan) + { + nPDFType = vcl::PDFWriter::Quote; + aPDFType = aQuoteString; + CreateCurrentSpan(rInf, sStyleName); + } } else if (sStyleName == aSourceText) { - nPDFType = vcl::PDFWriter::Code; - aPDFType = aCodeString; + if (!isContinueSpan) + { + nPDFType = vcl::PDFWriter::Code; + aPDFType = aCodeString; + CreateCurrentSpan(rInf, sStyleName); + } } - else + else if (!isContinueSpan) { const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); const SwFontScript nFont = rInf.GetFont()->GetActual(); - const LanguageType nDefaultLang = SwEnhancedPDFExportHelper::GetDefaultLanguage(); + const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() || LINESTYLE_NONE != rInf.GetFont()->GetOverline() || @@ -1394,6 +1910,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() aPDFType = sStyleName; else aPDFType = aSpanString; + CreateCurrentSpan(rInf, sStyleName); } } } @@ -1428,7 +1945,73 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() } break; - case PortionType::Table : + 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 : @@ -1481,27 +2064,22 @@ SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh, } } - s_aTableColumnsMap.clear(); - s_aLinkIdMap.clear(); - s_aNumListIdMap.clear(); - s_aNumListBodyIdMap.clear(); - s_aFrameTagIdMap.clear(); - #if OSL_DEBUG_LEVEL > 1 aStructStack.clear(); #endif const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); - sal_uInt16 nLangRes = RES_CHRATR_LANGUAGE; + 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; - s_eLanguageDefault = static_cast<const SvxLanguageItem*>(&mrSh.GetDoc()->GetDefault( nLangRes ))->GetLanguage(); + const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes ); + auto const eLanguageDefault = rLangItem.GetLanguage(); - EnhancedPDFExport(); + EnhancedPDFExport(eLanguageDefault); } SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper() @@ -1511,9 +2089,10 @@ SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper() tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage, const tools::Rectangle& rRectangle) const { - SwPostItMode nPostItMode = mrPrintData.GetPrintPostIts(); - if (nPostItMode != SwPostItMode::InMargins) + 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); @@ -1531,7 +2110,7 @@ tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* p return aRect; } -void SwEnhancedPDFExportHelper::EnhancedPDFExport() +void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault) { vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() ); @@ -1541,12 +2120,12 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // set the document locale - css::lang::Locale aDocLocale( LanguageTag( SwEnhancedPDFExportHelper::GetDefaultLanguage() ).getLocale() ); + lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() ); pPDFExtOutDevData->SetDocumentLocale( aDocLocale ); // Prepare the output device: - mrOut.Push( PushFlags::MAPMODE ); + mrOut.Push( vcl::PushFlags::MAPMODE ); MapMode aMapMode( mrOut.GetMapMode() ); aMapMode.SetMapUnit( MapUnit::MapTwip ); mrOut.SetMapMode( aMapMode ); @@ -1561,6 +2140,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() if ( !mbEditEngineOnly ) { + assert(pPDFExtOutDevData->GetSwPDFState() == nullptr); + pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault)); // POSTITS @@ -1623,10 +2204,10 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // 3. Check for hidden text attribute if ( !pTNd->IsHidden() && mrSh.GotoINetAttr( p->rINetAttr ) && - !mrSh.SelectHiddenRange() ) + !mrSh.IsInHiddenRange(/*bSelect=*/false) ) { // Select the hyperlink: - mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) ) { // First, we create the destination, because there may be more @@ -1635,24 +2216,24 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() p->rINetAttr.GetINetFormat().GetValue(), INetURLObject::DecodeMechanism::Unambiguous ) ); - // We have to distinguish between intern and real URLs - const bool bIntern = '#' == aURL[0]; + // 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 aTmp; - aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + 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 ( bIntern ) + if ( bInternal ) { aURL = aURL.copy( 1 ); mrSh.SwCursorShell::ClearMark(); @@ -1678,11 +2259,10 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() } } - if ( !bIntern || -1 != nDestId ) + if ( !bInternal || -1 != nDestId ) { // #i44368# Links in Header/Footer - const SwPosition aPos( *pTNd ); - const bool bHeaderFooter = pDoc->IsInHeaderFooter( aPos.nNode ); + const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd ); // Create links for all selected rectangles: const size_t nNumOfRects = aTmp.size(); @@ -1699,21 +2279,21 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Link Export tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect())); const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum); // Store link info for tagged pdf output: const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); - s_aLinkIdMap.push_back( aLinkEntry ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); // Connect Link and Destination: - if ( bIntern ) + if ( bInternal ) pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); else pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); // #i44368# Links in Header/Footer if ( bHeaderFooter ) - MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bIntern ); + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText); } } } @@ -1724,25 +2304,24 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // HYPERLINKS (Graphics, Frames, OLEs ) - SwFrameFormats* pTable = pDoc->GetSpzFrameFormats(); - const size_t nSpzFrameFormatsCount = pTable->size(); - for( size_t n = 0; n < nSpzFrameFormatsCount; ++n ) + for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats()) { - SwFrameFormat* pFrameFormat = (*pTable)[n]; - const SfxPoolItem* pItem; + const SwFormatURL* pItem; if ( RES_DRAWFRMFMT != pFrameFormat->Which() && GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) && - SfxItemState::SET == pFrameFormat->GetAttrSet().GetItemState( RES_URL, true, &pItem ) ) + (pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) ) { const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); - OUString aURL( static_cast<const SwFormatURL*>(pItem)->GetURL() ); - const bool bIntern = !aURL.isEmpty() && '#' == aURL[0]; + OUString aURL( pItem->GetURL() ); + if (aURL.isEmpty()) + continue; + const bool bInternal = '#' == aURL[0]; // Create the destination for internal links: sal_Int32 nDestId = -1; - if ( bIntern ) + if ( bInternal ) { aURL = aURL.copy( 1 ); mrSh.SwCursorShell::ClearMark(); @@ -1767,11 +2346,11 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() } } - if ( !bIntern || -1 != nDestId ) + 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 ); @@ -1780,10 +2359,14 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() { tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + 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 ( bIntern ) + if ( bInternal ) pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); else pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); @@ -1792,12 +2375,12 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor(); if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) { - const SwPosition* pPosition = rAnch.GetContentAnchor(); - if ( pPosition && pDoc->IsInHeaderFooter( pPosition->nNode ) ) + const SwNode* pAnchorNode = rAnch.GetAnchorNode(); + if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) ) { - const SwTextNode* pTNd = pPosition->nNode.GetNode().GetTextNode(); + const SwTextNode* pTNd = pAnchorNode->GetTextNode(); if ( pTNd ) - MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bIntern ); + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName); } } } @@ -1808,7 +2391,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Turn media shapes into Screen annotations. if (SdrObject* pObject = pFrameFormat->FindRealSdrObject()) { - SwRect aSnapRect = pObject->GetSnapRect(); + SwRect aSnapRect(pObject->GetSnapRect()); std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect); if (aScreenPageNums.empty()) continue; @@ -1817,15 +2400,26 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() 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, nScreenPageNum); + sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject); if (aMediaURL.startsWith("vnd.sun.star.Package:")) { // Embedded media. @@ -1858,11 +2452,10 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() continue; // Select the field: mrSh.SwCursorShell::SetMark(); - mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); // Link Rectangles - SwRects aTmp; - aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + SwRects const aTmp(GetCursorRectsContainingText(mrSh)); OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); mrSh.SwCursorShell::ClearMark(); @@ -1870,7 +2463,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Destination Rectangle const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField()); const OUString& rRefName = pField->GetSetRefName(); - mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo() ); + mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() ); const SwRect& rDestRect = mrSh.GetCharRect(); const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); @@ -1885,8 +2478,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); // #i44368# Links in Header/Footer - const SwPosition aPos( *pTNd ); - const bool bHeaderFooter = pDoc->IsInHeaderFooter( aPos.nNode ); + 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(); @@ -1903,11 +2496,11 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Link Export aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum); // Store link info for tagged pdf output: const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); - s_aLinkIdMap.push_back( aLinkEntry ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); // Connect Link and Destination: pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); @@ -1915,7 +2508,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // #i44368# Links in Header/Footer if ( bHeaderFooter ) { - MakeHeaderFooterLinks( *pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true ); + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true, content); } } } @@ -1935,12 +2528,11 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ]; SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode()); - mrSh.GetCursor_()->GetPoint()->nNode = rTNd; - mrSh.GetCursor_()->GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() ); + 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.SelectHiddenRange() + if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false) || (mrSh.GetLayout()->IsHideRedlines() && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote))) { @@ -1951,7 +2543,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Select the footnote: mrSh.SwCursorShell::SetMark(); - mrSh.SwCursorShell::Right( 1, CRSR_SKIP_CHARS ); + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); // Link Rectangle SwRects aTmp; @@ -1969,38 +2561,60 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() // Goto footnote text: if ( mrSh.GotoFootnoteText() ) { - // Link PageNums - std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect ); - // 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 ); - - for (sal_Int32 aLinkPageNum : aLinkPageNums) + if ( -1 != nDestPageNum ) { - // Link Export - tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); - const sal_Int32 nLinkId = - pPDFExtOutDevData->CreateLink(aRect, aLinkPageNum); + 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 ); - s_aLinkIdMap.push_back( aLinkEntry ); - - if ( -1 != nDestPageNum ) - { - aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect()); - // Destination Export - const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(rDestRect.SVRect(), nDestPageNum); - - // Connect Link and Destination: - pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); - } + 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 ); } } } @@ -2121,8 +2735,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() for ( const auto& rBookmark : rBookmarks ) { OUString aBookmarkName( rBookmark.aBookmark ); - const bool bIntern = '#' == aBookmarkName[0]; - if ( bIntern ) + const bool bInternal = '#' == aBookmarkName[0]; + if ( bInternal ) { aBookmarkName = aBookmarkName.copy( 1 ); JumpToSwMark( &mrSh, aBookmarkName ); @@ -2157,6 +2771,9 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName ); } rBookmarks.clear(); + assert(pPDFExtOutDevData->GetSwPDFState()); + delete pPDFExtOutDevData->GetSwPDFState(); + pPDFExtOutDevData->SetSwPDFState(nullptr); } // Restore view, cursor, and outdev: @@ -2173,6 +2790,53 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() 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) @@ -2191,36 +2855,96 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() const auto& rAuthorityField = *static_cast<const SwAuthorityField*>(pFormatField->GetField()); - if (!rAuthorityField.HasURL()) - { - continue; - } - const OUString& rURL = rAuthorityField.GetAuthEntry()->GetAuthorField(AUTH_FIELD_URL); - const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); - if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + if (auto targetType = rAuthorityField.GetTargetType(); + targetType == SwAuthorityField::TargetType::UseDisplayURL + || targetType == SwAuthorityField::TargetType::UseTargetURL) { - continue; - } + // Since the target type specifies to use an URL, link to it + const OUString& rURL = rAuthorityField.GetAbsoluteURL(); + if (rURL.getLength() == 0) + { + continue; + } - // Select the field. - mrSh.SwCursorShell::SetMark(); - mrSh.SwCursorShell::Right(1, CRSR_SKIP_CHARS); + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } - // Create the links. - for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_()) - { - for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + 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) { - tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); - sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, rLinkPageNum); - IdMapEntry aLinkEntry(rLinkRect, nLinkId); - s_aLinkIdMap.push_back(aLinkEntry); - pPDFExtOutDevData->SetLinkURL(nLinkId, rURL); + 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; - mrSh.SwCursorShell::ClearMark(); + 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(); + } } } @@ -2297,7 +3021,8 @@ void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rP const SwRect& rLinkRect, sal_Int32 nDestId, const OUString& rURL, - bool bIntern ) const + 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: @@ -2305,35 +3030,35 @@ void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rP 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() ) { - // 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, aHFLinkPageNum); + // Link PageNums + std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect ); - // Connect Link and Destination: - if ( bIntern ) - rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId ); - else - rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL ); - } + 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 index 3a007ce055d2..53fd4b13c013 100644 --- a/sw/source/core/text/SwGrammarMarkUp.cxx +++ b/sw/source/core/text/SwGrammarMarkUp.cxx @@ -54,9 +54,9 @@ void SwGrammarMarkUp::MoveGrammar( sal_Int32 nPos, sal_Int32 nDiff ) } } -SwGrammarMarkUp* SwGrammarMarkUp::SplitGrammarList( sal_Int32 nSplitPos ) +std::unique_ptr<SwGrammarMarkUp> SwGrammarMarkUp::SplitGrammarList( sal_Int32 nSplitPos ) { - SwGrammarMarkUp* pNew = static_cast<SwGrammarMarkUp*>(SplitList( 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(), @@ -64,7 +64,7 @@ SwGrammarMarkUp* SwGrammarMarkUp::SplitGrammarList( sal_Int32 nSplitPos ) if( pIter != maSentence.begin() ) { if( !pNew ) { - pNew = new SwGrammarMarkUp(); + pNew.reset(new SwGrammarMarkUp()); pNew->SetInvalid( 0, COMPLETE_STRING ); } pNew->maSentence.insert( pNew->maSentence.begin(), maSentence.begin(), pIter ); diff --git a/sw/source/core/text/atrhndl.hxx b/sw/source/core/text/atrhndl.hxx index 1033242492c3..efe1ae954958 100644 --- a/sw/source/core/text/atrhndl.hxx +++ b/sw/source/core/text/atrhndl.hxx @@ -18,9 +18,8 @@ */ #pragma once -#define NUM_ATTRIBUTE_STACKS 44 +#define NUM_ATTRIBUTE_STACKS 45 -#include <memory> #include <vector> #include <swfntcch.hxx> @@ -45,7 +44,9 @@ private: // 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::unique_ptr<SwFont> m_pFnt; + std::optional<SwFont> m_oFnt; + + int m_nINETFMT = 0; // for font's SetURL bool m_bVertLayout; bool m_bVertLayoutLRBT; @@ -104,15 +105,15 @@ public: inline void SwAttrHandler::ResetFont( SwFont& rFnt ) const { - OSL_ENSURE(m_pFnt, "ResetFont without a font"); - if (m_pFnt) - rFnt = *m_pFnt; + OSL_ENSURE(m_oFnt, "ResetFont without a font"); + if (m_oFnt) + rFnt = *m_oFnt; }; inline const SwFont* SwAttrHandler::GetFont() const { - return m_pFnt.get(); + return m_oFnt ? &*m_oFnt : nullptr; }; -/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
\ No newline at end of file +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/atrstck.cxx b/sw/source/core/text/atrstck.cxx index 4cc7b501094f..82a3da7fbf17 100644 --- a/sw/source/core/text/atrstck.cxx +++ b/sw/source/core/text/atrstck.cxx @@ -123,6 +123,7 @@ const sal_uInt8 StackPos[ RES_TXTATR_WITHEND_END - RES_CHRATR_BEGIN + 1 ] = 42, // RES_TXTATR_CJK_RUBY, // 53 0, // RES_TXTATR_UNKNOWN_CONTAINER, // 54 43, // RES_TXTATR_INPUTFIELD // 55 + 44, // RES_TXTATR_CONTENTCONTROL // 56 }; namespace CharFormat @@ -220,9 +221,8 @@ static bool lcl_ChgHyperLinkColor( const SwTextAttr& rAttr, // take color from character format 'unvisited link' rINetAttr.SetVisited(false); const SwCharFormat* pTmpFormat = rINetAttr.GetCharFormat(); - const SfxPoolItem* pItem; - if (SfxItemState::SET == pTmpFormat->GetItemState(RES_CHRATR_COLOR, true, &pItem)) - *pColor = pItem->StaticWhichCast(RES_CHRATR_COLOR).GetValue(); + if (const SvxColorItem* pItem = pTmpFormat->GetItemIfSet(RES_CHRATR_COLOR)) + *pColor = pItem->GetValue(); rINetAttr.SetVisited(true); } return true; @@ -237,8 +237,8 @@ static bool lcl_ChgHyperLinkColor( const SwTextAttr& rAttr, if ( pShell->GetWin() && ( - (rINetAttr.IsVisited() && SwViewOption::IsVisitedLinks()) || - (!rINetAttr.IsVisited() && SwViewOption::IsLinks()) + (rINetAttr.IsVisited() && pShell->GetViewOptions()->IsVisitedLinks()) || + (!rINetAttr.IsVisited() && pShell->GetViewOptions()->IsLinks()) ) ) { @@ -247,12 +247,12 @@ static bool lcl_ChgHyperLinkColor( const SwTextAttr& rAttr, if (rINetAttr.IsVisited()) { // take color from view option 'visited link color' - *pColor = SwViewOption::GetVisitedLinksColor(); + *pColor = pShell->GetViewOptions()->GetVisitedLinksColor(); } else { // take color from view option 'unvisited link color' - *pColor = SwViewOption::GetLinksColor(); + *pColor = pShell->GetViewOptions()->GetLinksColor(); } } return true; @@ -322,14 +322,10 @@ void SwAttrHandler::Init( const SfxPoolItem** pPoolItem, const SwAttrSet* pAS, // 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_pFnt) - { - *m_pFnt = rFnt; - } + if (m_oFnt) + *m_oFnt = rFnt; else - { - m_pFnt.reset(new SwFont(rFnt)); - } + m_oFnt.emplace(rFnt); } void SwAttrHandler::Reset( ) @@ -371,6 +367,13 @@ void SwAttrHandler::PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) } } } + + if (rAttr.Which() == RES_TXTATR_INETFMT) + { + if (m_nINETFMT == 0) + rFnt.SetURL(true); + ++m_nINETFMT; + } } // this is the usual case, we have a basic attribute, push it onto the // stack and change the font @@ -437,6 +440,14 @@ void SwAttrHandler::PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); if ( !pSet ) return; + if (rAttr.Which() == RES_TXTATR_INETFMT) + { + assert(m_nINETFMT > 0); + --m_nINETFMT; + if (m_nINETFMT == 0) + rFnt.SetURL(false); + } + for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) { const SfxPoolItem* pItem; @@ -476,8 +487,7 @@ void SwAttrHandler::Pop( const SwTextAttr& 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" ); + assert(nAttr < RES_TXTATR_WITHEND_END); const sal_uInt16 nStackPos = StackPos[ nAttr ]; const SwTextAttr* pTopAt = GetTop(nStackPos); @@ -521,6 +531,10 @@ void SwAttrHandler::ActivateTop( SwFont& rFnt, const sal_uInt16 nAttr ) { rFnt.GetMeta()--; } + else if (nAttr == RES_TXTATR_CONTENTCONTROL) + { + rFnt.GetContentControl()--; + } else if ( RES_TXTATR_CJK_RUBY == nAttr ) { // ruby stack has no more attributes @@ -531,7 +545,7 @@ void SwAttrHandler::ActivateTop( SwFont& rFnt, const sal_uInt16 nAttr ) if ( pTwoLineAttr ) { - const auto& rTwoLineItem = CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES )->StaticWhichCast(RES_CHRATR_TWO_LINES); + const auto& rTwoLineItem = *CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES ); bTwoLineAct = rTwoLineItem.GetValue(); } else @@ -546,7 +560,7 @@ void SwAttrHandler::ActivateTop( SwFont& rFnt, const sal_uInt16 nAttr ) if ( pRotateAttr ) { - const auto& rRotateItem = CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE )->StaticWhichCast(RES_CHRATR_ROTATE); + const auto& rRotateItem = *CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE ); rFnt.SetVertical( rRotateItem.GetValue(), m_bVertLayout ); } else @@ -751,7 +765,7 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) if ( pTwoLineAttr ) { - const auto& rTwoLineItem = CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES )->StaticWhichCast(RES_CHRATR_TWO_LINES); + const auto& rTwoLineItem = *CharFormat::GetItem( *pTwoLineAttr, RES_CHRATR_TWO_LINES ); bTwoLineAct = rTwoLineItem.GetValue(); } else @@ -786,7 +800,7 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) if ( pRotateAttr ) { - const auto& rRotateItem = CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE )->StaticWhichCast(RES_CHRATR_ROTATE); + const auto& rRotateItem = *CharFormat::GetItem( *pRotateAttr, RES_CHRATR_ROTATE ); rFnt.SetVertical( rRotateItem.GetValue(), m_bVertLayout ); } else @@ -815,6 +829,16 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) else rFnt.GetMeta()--; break; + case RES_TXTATR_CONTENTCONTROL: + if (bPush) + { + rFnt.GetContentControl()++; + } + else + { + rFnt.GetContentControl()--; + } + break; case RES_TXTATR_INPUTFIELD : if ( bPush ) rFnt.GetInputField()++; @@ -828,11 +852,11 @@ void SwAttrHandler::FontChg(const SfxPoolItem& rItem, SwFont& rFnt, bool bPush ) void SwAttrHandler::GetDefaultAscentAndHeight( SwViewShell const * pShell, OutputDevice const & rOut, sal_uInt16& nAscent, sal_uInt16& nHeight ) const { - OSL_ENSURE(m_pFnt, "No font available for GetDefaultAscentAndHeight"); + OSL_ENSURE(m_oFnt, "No font available for GetDefaultAscentAndHeight"); - if (m_pFnt) + if (m_oFnt) { - SwFont aFont( *m_pFnt ); + SwFont aFont( *m_oFnt ); nHeight = aFont.GetHeight( pShell, rOut ); nAscent = aFont.GetAscent( pShell, rOut ); } diff --git a/sw/source/core/text/frmcrsr.cxx b/sw/source/core/text/frmcrsr.cxx index a4cab82d92b1..94fcdf453cfa 100644 --- a/sw/source/core/text/frmcrsr.cxx +++ b/sw/source/core/text/frmcrsr.cxx @@ -180,7 +180,7 @@ bool SwTextFrame::GetCharRect( SwRect& rOrig, const SwPosition &rPos, { OSL_ENSURE( ! IsVertical() || ! IsSwapped(),"SwTextFrame::GetCharRect with swapped frame" ); - if( IsLocked() || IsHiddenNow() ) + if (IsLocked()) return false; // Find the right frame first. We need to keep in mind that: @@ -629,7 +629,7 @@ bool SwTextFrame::GetModelPositionForViewPoint_(SwPosition* pPos, const Point& r } } bool bChgFillData = false; - if( pFillData && FindPageFrame()->getFrameArea().IsInside( aOldPoint ) ) + if( pFillData && FindPageFrame()->getFrameArea().Contains( aOldPoint ) ) { FillCursorPos( *pFillData ); bChgFillData = true; @@ -671,7 +671,7 @@ bool SwTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& rPoint, bool SwTextFrame::LeftMargin(SwPaM *pPam) const { - assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep())); SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), SwTextCursor::IsRightMargin() ); @@ -705,7 +705,7 @@ bool SwTextFrame::LeftMargin(SwPaM *pPam) const bool SwTextFrame::RightMargin(SwPaM *pPam, bool bAPI) const { - assert(GetMergedPara() || &pPam->GetNode() == static_cast<SwContentNode const*>(GetDep())); + assert(GetMergedPara() || &pPam->GetPointNode() == static_cast<SwContentNode const*>(GetDep())); SwTextFrame *pFrame = GetAdjFrameAtPos( const_cast<SwTextFrame*>(this), *pPam->GetPoint(), SwTextCursor::IsRightMargin() ); @@ -764,8 +764,8 @@ bool SwTextFrame::UnitUp_( SwPaM *pPam, const SwTwips nOffset, SwSetToRightMargin aSet; if( IsInTab() && - pPam->GetNode().StartOfSectionNode() != - pPam->GetNode( false ).StartOfSectionNode() ) + 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. @@ -831,13 +831,13 @@ bool SwTextFrame::UnitUp_( SwPaM *pPam, const SwTwips nOffset, // See comment in SwTextFrame::GetModelPositionForViewPoint() #if OSL_DEBUG_LEVEL > 0 - const sal_uLong nOldNode = pPam->GetPoint()->nNode.GetIndex(); + 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()->nNode.GetIndex(), + OSL_ENSURE( nOldNode == pPam->GetPoint()->GetNodeIndex(), "SwTextFrame::UnitUp: illegal node change" ); #endif @@ -1151,8 +1151,8 @@ bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset, { if ( IsInTab() && - pPam->GetNode().StartOfSectionNode() != - pPam->GetNode( false ).StartOfSectionNode() ) + 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. @@ -1192,7 +1192,7 @@ bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset, aCharBox.Width( aCharBox.SSize().Width() / 2 ); #if OSL_DEBUG_LEVEL > 0 // See comment in SwTextFrame::GetModelPositionForViewPoint() - const sal_uLong nOldNode = pPam->GetPoint()->nNode.GetIndex(); + const SwNodeOffset nOldNode = pPam->GetPoint()->GetNodeIndex(); #endif if ( pNextLine && ! bFirstOfDouble ) aLine.NextLine(); @@ -1200,7 +1200,7 @@ bool SwTextFrame::UnitDown_(SwPaM *pPam, const SwTwips nOffset, TextFrameIndex nTmpOfst = aLine.GetModelPositionForViewPoint( pPam->GetPoint(), aCharBox.Pos(), false ); #if OSL_DEBUG_LEVEL > 0 - OSL_ENSURE( nOldNode == pPam->GetPoint()->nNode.GetIndex(), + OSL_ENSURE( nOldNode == pPam->GetPoint()->GetNodeIndex(), "SwTextFrame::UnitDown: illegal node change" ); #endif @@ -1319,7 +1319,7 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const nNextCol = 0; } else - ++nNextCol; // Empty columns require column brakes + ++nNextCol; // Empty columns require column breaks } if( pTmp != GetUpper()->GetUpper() ) // Did we end up in another column? { @@ -1418,7 +1418,7 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const if( nDiff > 0 ) { nDiff /= nDist; - rFill.Fill().nParaCnt = static_cast<sal_uInt16>(nDiff + 1); + rFill.Fill().nParaCnt = o3tl::narrowing<sal_uInt16>(nDiff + 1); rFill.nLineWidth = 0; rFill.bInner = false; rFill.bEmpty = true; @@ -1431,16 +1431,18 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const else { const SvxTabStopItem &rRuler = pSet->GetTabStops(); - const SvxLRSpaceItem &rLRSpace = pSet->GetLRSpace(); + 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() + rLRSpace.GetLeft() + + SwTwips nLeft = rFill.Left() + rTextLeftMargin.GetLeft(rFirstLine) + GetTextNodeForParaProps()->GetLeftMarginWithNum(); - SwTwips nRight = rFill.Right() - rLRSpace.GetRight(); + SwTwips nRight = rFill.Right() - rRightMargin.GetRight(); SwTwips nCenter = ( nLeft + nRight ) / 2; rRect.Left( nLeft ); if( SwFillMode::Margin == rFill.Mode() ) @@ -1510,7 +1512,7 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const } else if( rFill.X() > nLeft ) { - SwTwips nTextLeft = rFill.Left() + rLRSpace.GetTextLeft() + + SwTwips nTextLeft = rFill.Left() + rTextLeftMargin.GetTextLeft() + GetTextNodeForParaProps()->GetLeftMarginWithNum(true); rFill.nLineWidth += rFill.bFirstLine ? nLeft : nTextLeft; SwTwips nLeftTab; @@ -1536,7 +1538,7 @@ void SwTextFrame::FillCursorPos( SwFillData& rFill ) const else { const SvxTabStopItem& rTab = - pSet->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP ); + pSet->GetPool()->GetUserOrPoolDefaultItem( RES_PARATR_TABSTOP ); const SwTwips nDefTabDist = rTab[0].GetTabPos(); nRightTab = nLeftTab - nTextLeft; nRightTab /= nDefTabDist; diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 0304661e8438..4e2caf27b276 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -17,6 +17,8 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <config_wasm_strip.h> + #include <sal/config.h> #include <sal/log.hxx> @@ -47,6 +49,9 @@ #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 @@ -212,7 +217,7 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) if( pPara ) { pPara->GetReformat() = SwCharRange(); - pPara->GetDelta() = 0; + pPara->SetDelta(0); } } @@ -310,7 +315,7 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) if( pPara ) { pPara->GetReformat() = SwCharRange(); - pPara->GetDelta() = 0; + pPara->SetDelta(0); } } @@ -323,7 +328,7 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) const tools::Long nRemaining = - aRectFnSet.BottomDist( GetUpper()->getFrameArea(), nOldBottom ); - if ( nRemaining > 0 && !GetUpper()->IsSctFrame() && + if ( nRemaining > 0 && nRemaining != ( aRectFnSet.IsVert() ? nMyPos - getFrameArea().Right() : getFrameArea().Top() - nMyPos ) ) @@ -337,7 +342,47 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) 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; @@ -347,14 +392,14 @@ void SwTextFrame::MakePos() for (SwRedlineTable::size_type nRedlnPos = 0; nRedlnPos < rTable.size(); ++nRedlnPos) { SwRangeRedline* pRedln = rTable[nRedlnPos]; - if (pTextNode->GetIndex() == pRedln->GetPoint()->nNode.GetNode().GetIndex()) + if (pTextNode->GetIndex() == pRedln->GetPoint()->GetNode().GetIndex()) { pRedln->MaybeNotifyRedlinePositionModification(getFrameArea().Top()); if (GetMergedPara() && pRedln->GetType() == RedlineType::Delete - && pRedln->GetPoint()->nNode != pRedln->GetMark()->nNode) + && pRedln->GetPoint()->GetNode() != pRedln->GetMark()->GetNode()) { - pTextNode = pRedln->End()->nNode.GetNode().GetTextNode(); + pTextNode = pRedln->End()->GetNode().GetTextNode(); } } } @@ -456,7 +501,7 @@ void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit ) else nRstHeight = getFrameArea().Left() + getFrameArea().Width() - ( GetUpper()->getFrameArea().Left() + GetUpper()->getFramePrintArea().Left() ); - } + } else nRstHeight = GetUpper()->getFrameArea().Top() + GetUpper()->getFramePrintArea().Top() @@ -517,9 +562,6 @@ void SwTextFrame::AdjustFrame( const SwTwips nChgHght, bool bHasToFit ) css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips CurrentPos ) { - css::uno::Sequence< css::style::TabStop > tabs(1); - css::style::TabStop ts; - SwTextFormatInfo aInf( getRootFrame()->GetCurrShell()->GetOut(), this ); SwTextFormatter aLine( this, &aInf ); SwTextCursor TextCursor( this, &aInf ); @@ -533,10 +575,11 @@ css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips C if( !pTS ) { - return css::uno::Sequence< css::style::TabStop >(); + 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(); @@ -550,8 +593,7 @@ css::uno::Sequence< css::style::TabStop > SwTextFrame::GetTabStopInfo( SwTwips C default: break; // prevent warning } - tabs[0] = ts; - return tabs; + return { ts }; } // AdjustFollow expects the following situation: @@ -568,17 +610,27 @@ void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, // We got the rest of the text mass: Delete all Follows // DummyPortions() are a special case. // Special cases are controlled by parameter <nMode>. - if( HasFollow() && !(nMode & 1) && nOffset == nEnd ) + bool bDontJoin = nMode & 1; + if( HasFollow() && !bDontJoin && nOffset == nEnd ) { while( GetFollow() ) { if( GetFollow()->IsLocked() ) { - OSL_FAIL( "+SwTextFrame::JoinFrame: Follow is locked." ); + // 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(); } @@ -591,20 +643,46 @@ void SwTextFrame::AdjustFollow_( SwTextFormatter &rLine, const TextFrameIndex nNewOfst = (IsInFootnote() && (!GetIndNext() || HasFollow())) ? rLine.FormatQuoVadis(nOffset) : nOffset; - if( !(nMode & 1) ) + 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)); @@ -666,16 +744,20 @@ SwContentFrame *SwTextFrame::JoinFrame() // 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( - dynamic_cast<SwTextFrame*>(pFoll->FindNextCnt( true )), + pNext ? pNext->DynCastTextFrame() : nullptr, this ); } } +#endif + pFoll->Cut(); SetFollow(pNxt); SwFrame::DestroyFrame(pFoll); @@ -700,16 +782,19 @@ void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos) // 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( - dynamic_cast<SwTextFrame*>(pNew->FindNextCnt( true )), + pNext ? pNext->DynCastTextFrame() : nullptr, this ); } } +#endif // If footnotes end up in pNew bz our actions, we need // to re-register them @@ -758,7 +843,7 @@ void SwTextFrame::SplitFrame(TextFrameIndex const nTextPos) void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst) { - // We do not need to invalidate out Follow. + // We do not need to invalidate our Follow. // We are a Follow, get formatted right away and call // SetOffset() from there mnOffset = nNewOfst; @@ -768,7 +853,7 @@ void SwTextFrame::SetOffset_(TextFrameIndex const nNewOfst) SwCharRange &rReformat = pPara->GetReformat(); rReformat.Start() = TextFrameIndex(0); rReformat.Len() = TextFrameIndex(GetText().getLength()); - pPara->GetDelta() = sal_Int32(rReformat.Len()); + pPara->SetDelta(sal_Int32(rReformat.Len())); } InvalidateSize(); } @@ -987,7 +1072,7 @@ bool SwTextFrame::CalcPreps() return bRet; } -// We rewire the footnotes and the character bound objects +// Move the as-character objects - footnotes must be moved by RemoveFootnote! void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ) { if( pFrame->GetOffset() < nNew ) @@ -996,6 +1081,54 @@ void SwTextFrame::ChangeOffset( SwTextFrame* pFrame, TextFrameIndex nNew ) MoveFlyInCnt( pFrame, nNew, TextFrameIndex(COMPLETE_STRING) ); } +static bool isFirstVisibleFrameInBody(const SwTextFrame* pFrame) +{ + const SwFrame* pBodyFrame = pFrame->FindBodyFrame(); + if (!pBodyFrame) + return false; + for (const SwFrame* pCur = pFrame;;) + { + for (const SwFrame* pPrev = pCur->GetPrev(); pPrev; pPrev = pPrev->GetPrev()) + if (!pPrev->IsHiddenNow()) + return false; + pCur = pCur->GetUpper(); + assert(pCur); // We found pBodyFrame, right? + if (pCur->IsBodyFrame()) + return true; + } +} + +static bool hasFly(const SwTextFrame* pFrame) +{ + if (auto pDrawObjs = pFrame->GetDrawObjs(); pDrawObjs && pDrawObjs->size()) + { + auto anchorId = (*pDrawObjs)[0]->GetFrameFormat()->GetAnchor().GetAnchorId(); + if (anchorId == RndStdIds::FLY_AT_PARA || anchorId == RndStdIds::FLY_AT_CHAR) + return true; + } + return false; +} + +static bool hasAtPageFly(const SwFrame* pFrame) +{ + auto pPageFrame = pFrame->FindPageFrame(); + if (!pPageFrame) + return false; + auto pPageDrawObjs = pPageFrame->GetDrawObjs(); + if (pPageDrawObjs) + { + for (const auto pObject : *pPageDrawObjs) + if (pObject->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) + return true; + } + return false; +} + +static bool isReallyEmptyMaster(const SwTextFrame* pFrame) +{ + return pFrame->IsEmptyMaster() && (!pFrame->GetDrawObjs() || !pFrame->GetDrawObjs()->size()); +} + void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, WidowsAndOrphans &rFrameBreak, TextFrameIndex const nStrLen, @@ -1021,20 +1154,57 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, !rFrameBreak.IsInside( rLine ) ) : rFrameBreak.IsBreakNow( rLine ) ) ) ) ? 1 : 0; + + SwTextFormatInfo& rInf = rLine.GetInfo(); + bool bEmptyWithSplitFly = false; + 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; + bEmptyWithSplitFly = true; + } + + const SwFrame *pBodyFrame = FindBodyFrame(); + // i#84870 // no split of text frame, which only contains an as-character anchored object - bool bOnlyContainsAsCharAnchoredObj = + bool bLoneAsCharAnchoredObj = + pBodyFrame && !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; + (*GetDrawObjs())[0]->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR; - if ( nNew && bOnlyContainsAsCharAnchoredObj ) + if (bLoneAsCharAnchoredObj) { - nNew = 0; + // Still try split text frame if we have columns. + if (FindColFrame()) + bLoneAsCharAnchoredObj = false; + // tdf#160526: only no split if there is no preceding frames on same page + else if (!isFirstVisibleFrameInBody(this)) + bLoneAsCharAnchoredObj = false; + else + nNew = 0; + } + else if (nNew) + { + if (IsFollow()) + { + // tdf#160549: do not split the frame at the very beginning again, if its master was empty + auto precede = static_cast<SwTextFrame*>(GetPrecede()); + assert(precede); + auto precedeText = precede->DynCastTextFrame(); + assert(precedeText); + if (isReallyEmptyMaster(precedeText)) + nNew = 0; + } + else if (!bEmptyWithSplitFly) + { + // Do not split immediately in the beginning of page (unless there is an at-para or + // at-char or at-page fly, which pushes the rest down) + if (isFirstVisibleFrameInBody(this) && !hasFly(this) && pBodyFrame && !hasAtPageFly(pBodyFrame)) + nNew = 0; + } } if ( nNew ) @@ -1042,8 +1212,6 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, SplitFrame( nEnd ); } - const SwFrame *pBodyFrame = FindBodyFrame(); - const tools::Long nBodyHeight = pBodyFrame ? ( IsVertical() ? pBodyFrame->getFrameArea().Width() : pBodyFrame->getFrameArea().Height() ) : 0; @@ -1052,7 +1220,7 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // are valid now pPara->GetReformat() = SwCharRange(); bool bDelta = pPara->GetDelta() != 0; - pPara->GetDelta() = 0; + pPara->SetDelta(0); if( rLine.IsStop() ) { @@ -1067,7 +1235,11 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // AdjustFollow might execute JoinFrame() because of this. // Else, nEnd is the end of the last line in the Master. TextFrameIndex nOld = nEnd; - nEnd = rLine.GetEnd(); + // 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 ) @@ -1082,6 +1254,7 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // 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() ) { @@ -1107,18 +1280,43 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, // 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) || - (nStrLen == TextFrameIndex(0) && GetTextNodeForParaProps()->GetNumRule())) + if (!bLoneAsCharAnchoredObj + && (bHasVisibleNumRule + || (nStrLen > TextFrameIndex(0) + && (nEnd != rLine.GetStart() || rInf.GetRest()))) ) { SplitFrame( nEnd ); @@ -1142,7 +1340,7 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, 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 ) + if (nChg < 0 && !bDelta && bLoneAsCharAnchoredObj) { nChg = 0; } @@ -1169,7 +1367,7 @@ void SwTextFrame::FormatAdjust( SwTextFormatter &rLine, } // bPrev is set whether Reformat.Start() was called because of Prev(). -// Else, wo don't know whether we can limit the repaint or not. +// 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(), @@ -1177,8 +1375,8 @@ bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev ) SwParaPortion *pPara = rLine.GetInfo().GetParaPortion(); const SwLineLayout *pOldCur = rLine.GetCurr(); const TextFrameIndex nOldLen = pOldCur->GetLen(); - const sal_uInt16 nOldAscent = pOldCur->GetAscent(); - const sal_uInt16 nOldHeight = pOldCur->Height(); + 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; @@ -1277,7 +1475,8 @@ bool SwTextFrame::FormatLine( SwTextFormatter &rLine, const bool bPrev ) } // Calculating the good ol' nDelta - pPara->GetDelta() -= sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen); + const sal_Int32 nDiff = sal_Int32(pNew->GetLen()) - sal_Int32(nOldLen); + pPara->SetDelta(pPara->GetDelta() - nDiff); // Stop! if( rLine.IsStop() ) @@ -1596,9 +1795,27 @@ void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf, // 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 ); } @@ -1687,7 +1904,8 @@ void SwTextFrame::FormatOnceMore( SwTextFormatter &rLine, SwTextFormatInfo &rInf } } -void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pPara ) +void SwTextFrame::FormatImpl(vcl::RenderContext* pRenderContext, SwParaPortion *pPara, + std::vector<SwAnchoredObject *> & rIntersectingObjs) { const bool bIsEmpty = GetText().isEmpty(); @@ -1722,6 +1940,18 @@ void SwTextFrame::Format_( vcl::RenderContext* pRenderContext, SwParaPortion *pP 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(); @@ -1757,9 +1987,7 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr if( aRectFnSet.GetWidth(getFramePrintArea()) <= 0 ) { // If MustFit is set, we shrink to the Upper's bottom edge if needed. - // Else we just take a standard size of 12 Pt. (240 twip). SwTextLineAccess aAccess( this ); - tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); if( aAccess.GetPara()->IsPrepMustFit() ) { @@ -1768,16 +1996,8 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr if( nDiff > 0 ) Shrink( nDiff ); } - else if( 240 < nFrameHeight ) - { - Shrink( nFrameHeight - 240 ); - } - else if( 240 > nFrameHeight ) - { - Grow( 240 - nFrameHeight ); - } - nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); + tools::Long nFrameHeight = aRectFnSet.GetHeight(getFrameArea()); const tools::Long nTop = aRectFnSet.GetTopMargin(*this); if( nTop > nFrameHeight ) @@ -1793,7 +2013,14 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr return; } - const TextFrameIndex nStrLen(GetText().getLength()); + TextFrameIndex nStrLen(GetText().getLength()); + + if (HasNonLastSplitFlyDrawObj()) + { + // Non-last part of split fly anchor: consider this empty. + nStrLen = TextFrameIndex(0); + } + if ( nStrLen || !FormatEmpty() ) { @@ -1906,7 +2133,13 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr } do { - Format_( pRenderContext, aAccess.GetPara() ); + ::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(); @@ -1914,12 +2147,79 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr // 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; - else - break; + continue; + } } - else - break; + 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 ) { @@ -1937,6 +2237,18 @@ void SwTextFrame::Format( vcl::RenderContext* pRenderContext, const SwBorderAttr { 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); + } } } @@ -2054,7 +2366,7 @@ bool SwTextFrame::FormatQuick( bool bForceQuickFormat ) // Delete reformat pPara->GetReformat() = SwCharRange(); - pPara->GetDelta() = 0; + pPara->SetDelta(0); return true; } diff --git a/sw/source/core/text/frminf.cxx b/sw/source/core/text/frminf.cxx index 92b287d7bd16..a123691db703 100644 --- a/sw/source/core/text/frminf.cxx +++ b/sw/source/core/text/frminf.cxx @@ -19,8 +19,6 @@ #include <sal/config.h> -#include <o3tl/safeint.hxx> - #include <frminf.hxx> #include "itrtxt.hxx" @@ -82,7 +80,7 @@ bool SwTextFrameInfo::IsFilled( const sal_uInt8 nPercent ) const tools::Long nWidth = m_pFrame->getFramePrintArea().Width(); nWidth *= nPercent; nWidth /= 100; - return o3tl::make_unsigned(nWidth) <= pLay->Width(); + return nWidth <= pLay->Width(); } // Where does the text start (without whitespace)? (document global) diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx index 7f35ea7633b6..d6d47f7ae637 100644 --- a/sw/source/core/text/frmpaint.cxx +++ b/sw/source/core/text/frmpaint.cxx @@ -68,7 +68,7 @@ class SwExtraPainter const SwLineNumberInfo &m_rLineInf; SwTwips m_nX; SwTwips m_nRedX; - sal_uLong m_nLineNr; + sal_Int32 m_nLineNr; sal_uInt16 m_nDivider; bool m_bGoLeft; bool IsClipChg() const { return m_aClip.IsChg(); } @@ -82,9 +82,16 @@ public: sal_Int16 eHor, bool bLnNm ); SwFont* GetFont() const { return m_pFnt.get(); } void IncLineNr() { ++m_nLineNr; } - bool HasNumber() const { return !( m_nLineNr % m_rLineInf.GetCountBy() ); } + 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 { - if( !m_nDivider ) return false; + assert( m_rLineInf.GetDividerCountBy() != 0 ); + if( !m_nDivider || m_rLineInf.GetDividerCountBy() == 0 ) + return false; return !(m_nLineNr % m_rLineInf.GetDividerCountBy()); } @@ -170,7 +177,7 @@ SwExtraPainter::SwExtraPainter( const SwTextFrame *pFrame, SwViewShell *pVwSh, if( text::HoriOrientation::INSIDE == eHor || text::HoriOrientation::OUTSIDE == eHor ) { - if (!oIsRightPage) + if (!oIsRightPage.has_value()) oIsRightPage = pFrame->FindPageFrame()->OnRightPage(); if (*oIsRightPage) eHor = eHor == text::HoriOrientation::INSIDE ? text::HoriOrientation::LEFT : text::HoriOrientation::RIGHT; @@ -248,7 +255,7 @@ void SwExtraPainter::PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, SwRect aRct( Point( aTmpPos.X(), aTmpPos.Y() - pTmpFnt->GetAscent( m_pSh, *m_pSh->GetOut() ) ), aSize ); - if( !m_aRect.IsInside( aRct ) ) + if( !m_aRect.Contains( aRct ) ) { if( aRct.Intersection( m_aRect ).IsEmpty() ) bPaint = false; @@ -280,7 +287,7 @@ void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax ) if( !IsClipChg() ) { SwRect aRct( aStart, aEnd ); - if( !m_aRect.IsInside( aRct ) ) + if( !m_aRect.Contains( aRct ) ) { if( aRct.Intersection( m_aRect ).IsEmpty() ) return; @@ -398,7 +405,7 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) const bool bNum = bLineNum && ( aExtra.HasNumber() || aExtra.HasDivider() ); if( bRedInMargin || bNum ) { - sal_uInt16 nTmpHeight, nTmpAscent; + SwTwips nTmpHeight, nTmpAscent; aLine.CalcAscentAndHeight( nTmpAscent, nTmpHeight ); if ( bRedInMargin ) { @@ -494,6 +501,8 @@ SwRect SwTextFrame::GetPaintSwRect() bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const { + PaintParagraphStylesHighlighting(); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); if( pSh && ( pSh->GetViewOptions()->IsParagraph() || bInitFont ) ) { @@ -532,13 +541,13 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const 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()->nNode.GetIndex() ) + 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()->nNode.GetIndex() ) + if ( rTextNode.GetIndex() == pNextRedline->Start()->GetNodeIndex() ) eRedline = pNextRedline->GetType(); } } @@ -563,11 +572,13 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const pFnt->ChgPhysFnt( pSh, *pSh->GetOut() ); Point aPos = getFrameArea().Pos() + getFramePrintArea().Pos(); - const SvxLRSpaceItem &rSpace = - GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace(); + const SvxFirstLineIndentItem& rFirstLine( + GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent()); - if ( rSpace.GetTextFirstLineOffset() > 0 ) - aPos.AdjustX(rSpace.GetTextFirstLineOffset() ); + if (0 < rFirstLine.GetTextFirstLineOffset()) + { + aPos.AdjustX(rFirstLine.GetTextFirstLineOffset()); + } std::unique_ptr<SwSaveClip, o3tl::default_delete<SwSaveClip>> xClip; if( IsUndersized() ) @@ -594,7 +605,8 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const } // Don't show the paragraph mark for collapsed paragraphs, when they are hidden - if ( EmptyHeight( ) > 1 ) + // 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 ); @@ -630,19 +642,13 @@ bool SwTextFrame::PaintEmpty( const SwRect &rRect, bool bCheck ) const return false; } -void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, SwPrintData const*const) const +void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect) const { ResetRepaint(); // #i16816# tagged pdf support SwViewShell *pSh = getRootFrame()->GetCurrShell(); - Num_Info aNumInfo( *this ); - SwTaggedPDFHelper aTaggedPDFHelperNumbering( &aNumInfo, nullptr, nullptr, rRenderContext ); - - Frame_Info aFrameInfo( *this ); - SwTaggedPDFHelper aTaggedPDFHelperParagraph( nullptr, &aFrameInfo, nullptr, rRenderContext ); - if( IsEmpty() && PaintEmpty( rRect, true ) ) return; @@ -669,6 +675,31 @@ void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& } } + // 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)); @@ -753,7 +784,7 @@ void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& { do { - aLine.DrawTextLine( rRect, aClip, IsUndersized() ); + aLine.DrawTextLine(rRect, aClip, IsUndersized(), oTaggedLabel, oTaggedParagraph, isPDFTaggingEnabled); } while( aLine.Next() && aLine.Y() <= nBottom ); } @@ -766,10 +797,14 @@ void SwTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& 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 index 8bf9819c4d66..c3a94187a7ea 100644 --- a/sw/source/core/text/guess.cxx +++ b/sw/source/core/text/guess.cxx @@ -28,9 +28,11 @@ #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; @@ -42,13 +44,115 @@ 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 ) + const sal_uInt16 nPorHeight, sal_Int32 nSpacesInLine ) { m_nCutPos = rInf.GetIdx(); @@ -78,33 +182,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, const SvxAdjust& rAdjust = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust(); - // tdf#104668 space chars at the end should be cut if the compatibility option is enabled - // for LTR mode only - if ( !rInf.GetTextFrame()->IsRightToLeft() ) + // allow up to 20% shrinking of the spaces + if ( nSpacesInLine ) { - if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( - DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)) - { - if ( rAdjust == SvxAdjust::Right || rAdjust == SvxAdjust::Center ) - { - TextFrameIndex nSpaceCnt(0); - for (sal_Int32 i = rInf.GetText().getLength() - 1; - sal_Int32(rInf.GetIdx()) <= i; --i) - { - sal_Unicode cChar = rInf.GetText()[i]; - if ( cChar != CH_BLANK && cChar != CH_FULL_BLANK && cChar != CH_SIX_PER_EM ) - break; - ++nSpaceCnt; - } - TextFrameIndex nCharsCnt = nMaxLen - nSpaceCnt; - if ( nSpaceCnt && nCharsCnt < rPor.GetLen() ) - { - nMaxLen = nCharsCnt; - if ( !nMaxLen ) - return true; - } - } - } + static constexpr OUStringLiteral STR_BLANK = u" "; + sal_Int16 nSpaceWidth = rInf.GetTextSize(STR_BLANK).Width(); + nLineWidth += nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth); } if ( rInf.GetLen() < nMaxLen ) @@ -170,12 +253,16 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, { // 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 = m_nBreakWidth + nItalic; + m_nBreakWidth += nItalic; // save maximum width for later use if ( nMaxSizeDiff ) @@ -183,7 +270,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakWidth += nLeftRightBorderSpace; - return true; + return bRet; } } @@ -195,8 +282,104 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, // 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); } @@ -226,8 +409,13 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, // 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 = m_nBreakWidth + nItalic; + m_nBreakWidth += nItalic; // save maximum width for later use if ( nMaxSizeDiff ) @@ -235,7 +423,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakWidth += nLeftRightBorderSpace; - return true; + return bRet; } } @@ -250,36 +438,11 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, TextFrameIndex nPorLen(0); // do not call the break iterator nCutPos is a blank - sal_Unicode cCutChar = m_nCutPos < TextFrameIndex(rInf.GetText().getLength()) - ? rInf.GetText()[sal_Int32(m_nCutPos)] - : 0; + sal_Unicode cCutChar = rInf.GetChar(m_nCutPos); if (IsBlank(cCutChar)) { - m_nBreakPos = m_nCutPos; - TextFrameIndex nX = m_nBreakPos; - - if ( rAdjust == SvxAdjust::Left ) - { - // we step back until a non blank character has been found - // or there is only one more character left - while (nX && TextFrameIndex(rInf.GetText().getLength()) < m_nBreakPos && - IsBlank(rInf.GetChar(--nX))) - --m_nBreakPos; - } - else // #i20878# - { - while (nX && m_nBreakPos > rInf.GetLineStart() + TextFrameIndex(1) && - IsBlank(rInf.GetChar(--nX))) - --m_nBreakPos; - } - - if( m_nBreakPos > rInf.GetIdx() ) - nPorLen = m_nBreakPos - rInf.GetIdx(); - while (++m_nCutPos < TextFrameIndex(rInf.GetText().getLength()) && - IsBlank(rInf.GetChar(m_nCutPos))) - ; // nothing - - m_nBreakStart = m_nCutPos; + m_nCutPos = m_nBreakStart = AdjustCutPos(m_nCutPos, m_nBreakPos, rInf); + nPorLen = m_nBreakPos - rInf.GetIdx(); } else { @@ -422,7 +585,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, m_nBreakStart = m_nBreakPos; - bHyph = BreakType::HYPHENATION == aResult.breakType; + bHyph = BreakType::HYPHENATION == aResult.breakType && + // allow hyphenation of the word only if it's not disabled by character formatting + LANGUAGE_NONE != rInf.GetTextFrame()->GetLangOfChar( + TextFrameIndex( sal_Int32(m_nBreakPos) + + aResult.rHyphenatedWord->getHyphenationPos() ), + 1, true, /*bNoneIfNoHyphenation=*/true ); if (bHyph && m_nBreakPos != TextFrameIndex(COMPLETE_STRING)) { @@ -497,7 +665,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, 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( aTmpSize ) ); + m_pHanging.reset( new SwHangingPortion( std::move(aTmpSize) ) ); m_pHanging->SetLen( nHangingLen ); nPorLen = m_nCutPos - rInf.GetIdx(); } @@ -548,6 +716,13 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf, 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; diff --git a/sw/source/core/text/guess.hxx b/sw/source/core/text/guess.hxx index 696a09fc8589..5a7a9ac1cfa2 100644 --- a/sw/source/core/text/guess.hxx +++ b/sw/source/core/text/guess.hxx @@ -38,19 +38,21 @@ class SwTextGuess 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_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 ); + 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; } diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index bb63a36ae157..ddcca31668db 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -21,7 +21,9 @@ #include <unotools/linguprops.hxx> #include <unotools/lingucfg.hxx> +#include <fmtinfmt.hxx> #include <hintids.hxx> +#include <txatbase.hxx> #include <svl/ctloptions.hxx> #include <sfx2/infobar.hxx> #include <sfx2/printer.hxx> @@ -29,7 +31,6 @@ #include <editeng/hyphenzoneitem.hxx> #include <editeng/hngpnctitem.hxx> #include <editeng/scriptspaceitem.hxx> -#include <editeng/brushitem.hxx> #include <editeng/splwrap.hxx> #include <editeng/pgrditem.hxx> #include <editeng/tstpitem.hxx> @@ -38,12 +39,12 @@ #include <SwSmartTagMgr.hxx> #include <breakit.hxx> #include <editeng/forbiddenruleitem.hxx> -#include <paintfrm.hxx> #include <swmodule.hxx> #include <vcl/svapp.hxx> #include <viewsh.hxx> #include <viewopt.hxx> #include <frmtool.hxx> +#include <fmturl.hxx> #include <IDocumentSettingAccess.hxx> #include <IDocumentDeviceAccess.hxx> #include <IDocumentMarkAccess.hxx> @@ -51,6 +52,7 @@ #include <rootfrm.hxx> #include "inftxt.hxx" #include <noteurl.hxx> +#include "porfly.hxx" #include "porftn.hxx" #include "porrst.hxx" #include "itratr.hxx" @@ -66,6 +68,18 @@ #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; @@ -99,7 +113,7 @@ SwLineInfo::~SwLineInfo() void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, const SwTextNode& rTextNode ) { - m_pRuler.reset( new SvxTabStopItem( rAttrSet.GetTabStops() ) ); + m_oRuler.emplace( rAttrSet.GetTabStops() ); if ( rTextNode.GetListTabStopPosition( m_nListTabStopPosition ) ) { m_bListTabStopIncluded = true; @@ -107,15 +121,15 @@ void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, // insert the list tab stop into SvxTabItem instance <pRuler> const SvxTabStop aListTabStop( m_nListTabStopPosition, SvxTabAdjust::Left ); - m_pRuler->Insert( aListTabStop ); + m_oRuler->Insert( aListTabStop ); // remove default tab stops, which are before the inserted list tab stop - for ( sal_uInt16 i = 0; i < m_pRuler->Count(); i++ ) + for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ ) { - if ( (*m_pRuler)[i].GetTabPos() < m_nListTabStopPosition && - (*m_pRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) + if ( (*m_oRuler)[i].GetTabPos() < m_nListTabStopPosition && + (*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) { - m_pRuler->Remove(i); + m_oRuler->Remove(i); continue; } } @@ -124,12 +138,12 @@ void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, if ( !rTextNode.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ) { // remove default tab stop at position 0 - for ( sal_uInt16 i = 0; i < m_pRuler->Count(); i++ ) + for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ ) { - if ( (*m_pRuler)[i].GetTabPos() == 0 && - (*m_pRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) + if ( (*m_oRuler)[i].GetTabPos() == 0 && + (*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default ) { - m_pRuler->Remove(i); + m_oRuler->Remove(i); break; } } @@ -191,6 +205,7 @@ SwTextSizeInfo::SwTextSizeInfo() , m_pText(nullptr) , m_nIdx(0) , m_nLen(0) +, m_nMeasureLen(COMPLETE_STRING) , m_nKanaIdx(0) , m_bOnWin (false) , m_bNotEOL (false) @@ -221,6 +236,7 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew ) 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() ), @@ -280,14 +296,14 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF // Set default layout mode ( LTR or RTL ). if ( m_pFrame->IsRightToLeft() ) { - m_pOut->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl ); - m_pRef->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl ); + 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( ComplexTextLayoutFlags::BiDiStrong ); - m_pRef->SetLayoutMode( ComplexTextLayoutFlags::BiDiStrong ); + m_pOut->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong ); + m_pRef->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong ); m_nDirection = DIR_LEFT2RIGHT; } @@ -309,7 +325,7 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF m_pText = &m_pFrame->GetText(); m_nIdx = nNewIdx; - m_nLen = TextFrameIndex(COMPLETE_STRING); + 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 = @@ -332,6 +348,7 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pTex 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() ), @@ -407,6 +424,7 @@ SwPosSize SwTextSizeInfo::GetTextSize() const 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() ); @@ -417,7 +435,7 @@ SwPosSize SwTextSizeInfo::GetTextSize() const void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex nIndex, const TextFrameIndex nLength, const sal_uInt16 nComp, sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, - vcl::TextLayoutCache const*const pCache) const + vcl::text::TextLayoutCache const*const pCache) const { SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength, 0, false, pCache); @@ -426,14 +444,14 @@ void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex aDrawInf.SetSnapToGrid( SnapToGrid() ); aDrawInf.SetKanaComp( nComp ); SwPosSize aSize( m_pFnt->GetTextSize_( aDrawInf ) ); - nMaxSizeDiff = static_cast<sal_uInt16>(aDrawInf.GetKanaDiff()); + 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::TextLayoutCache const*const pCache) const + vcl::text::TextLayoutCache const*const pCache) const { const SwScriptInfo& rScriptInfo = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); @@ -454,7 +472,7 @@ TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth, const TextFrameIndex nMaxLen, const sal_uInt16 nComp, TextFrameIndex& rExtraCharPos, - vcl::TextLayoutCache const*const pCache) const + vcl::text::TextLayoutCache const*const pCache) const { const SwScriptInfo& rScriptInfo = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); @@ -521,39 +539,6 @@ SwTextPaintInfo::SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint ) CtorInitTextPaintInfo( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, rPaint ); } -/// Returns if the current background color is dark. -static bool lcl_IsDarkBackground( const SwTextPaintInfo& rInf ) -{ - std::optional<Color> pCol = rInf.GetFont()->GetBackColor(); - if( ! pCol || COL_TRANSPARENT == *pCol ) - { - const SvxBrushItem* pItem; - SwRect aOrigBackRect; - drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes; - - // Consider, that [GetBackgroundBrush(...)] can set <pCol> - // See implementation in /core/layout/paintfrm.cxx - // There is a background color, if there is a background brush and - // its color is *not* "no fill"/"auto fill". - if( rInf.GetTextFrame()->GetBackgroundBrush( aFillAttributes, pItem, pCol, aOrigBackRect, false, /*bConsiderTextBox=*/false ) ) - { - if ( !pCol ) - pCol = pItem->GetColor(); - - // Determined color <pCol> can be <COL_TRANSPARENT>. Thus, check it. - if ( *pCol == COL_TRANSPARENT) - pCol.reset(); - } - else - pCol.reset(); - } - - if( !pCol ) - pCol = aGlobalRetoucheColor; - - return pCol->IsDark(); -} - namespace { /** @@ -604,7 +589,7 @@ SwTransparentTextGuard::~SwTransparentTextGuard() Gradient aVCLGradient; sal_uInt8 nTransPercentVcl = 255 - m_rPaintInf.GetFont()->GetColor().GetAlpha(); const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); - aVCLGradient.SetStyle(GradientStyle::Linear); + aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR); aVCLGradient.SetStartColor(aTransColor); aVCLGradient.SetEndColor(aTransColor); aVCLGradient.SetAngle(0_deg10); @@ -653,7 +638,7 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPo aDrawInf.SetUnderFnt( m_pUnderFnt ); const tools::Long nSpaceAdd = ( rPor.IsBlankPortion() || rPor.IsDropPortion() || - rPor.InNumberGrp() ) ? 0 : GetSpaceAdd(); + rPor.InNumberGrp() ) ? 0 : GetSpaceAdd(/*bShrink=*/true); if ( nSpaceAdd ) { TextFrameIndex nCharCnt(0); @@ -681,7 +666,7 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPo // Draw text next to the left border Point aFontPos(m_aPos); - if( m_pFnt->GetLeftBorder() && !static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev() ) + if( m_pFnt->GetLeftBorder() && rPor.InTextGrp() && !static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev() ) { const sal_uInt16 nLeftBorderSpace = m_pFnt->GetLeftBorderSpace(); if ( GetTextFrame()->IsRightToLeft() ) @@ -716,7 +701,9 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPo std::unique_ptr<SwTransparentTextGuard, o3tl::default_delete<SwTransparentTextGuard>> pTransparentText; if (m_pFnt->GetColor() != COL_AUTO && m_pFnt->GetColor().IsTransparent()) { - pTransparentText.reset(new SwTransparentTextGuard(rPor, *this, aDrawInf)); + // 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() ) @@ -955,7 +942,7 @@ static void lcl_DrawSpecial( const SwTextPaintInfo& rTextPaintInfo, const SwLine Point aTmpPos( nX, nY ); rNonConstTextPaintInfo.SetPos( aTmpPos ); sal_uInt16 nOldWidth = rPor.Width(); - const_cast<SwLinePortion&>(rPor).Width( static_cast<sal_uInt16>(aFontSize.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) ); @@ -996,6 +983,13 @@ 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 ); @@ -1008,7 +1002,24 @@ void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const CHAR_LINEBREAK_RTL : CHAR_LINEBREAK; const sal_uInt8 nOptions = 0; - lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions ); + 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 ); @@ -1089,8 +1100,7 @@ void SwTextPaintInfo::DrawPostIts( bool bScript ) const if ( GetTextFrame()->IsVertical() ) GetTextFrame()->SwitchHorizontalToVertical( aTmpRect ); - const tools::Rectangle aRect( aTmpRect.SVRect() ); - SwViewOption::PaintPostIts( const_cast<OutputDevice*>(GetOut()), aRect, bScript ); + GetOpt().PaintPostIts( const_cast<OutputDevice*>(GetOut()), aTmpRect, bScript ); } @@ -1101,19 +1111,22 @@ void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool if ( !aIntersect.HasArea() ) return; - if (OnWin() && SwViewOption::IsFieldShadings() && + if (OnWin() && GetOpt().IsFieldShadings() && !GetOpt().IsPagePreview()) { OutputDevice* pOut = const_cast<OutputDevice*>(GetOut()); - pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); - pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() ); + 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=10; + const int delta = 25; tools::Rectangle r(aIntersect.Left()+delta, aIntersect.Top()+delta, aIntersect.Right()-delta, aIntersect.Bottom()-delta); - m_pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + m_pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); m_pOut->SetLineColor( Color(0, 0, 0)); m_pOut->SetFillColor(); m_pOut->DrawRect( r ); @@ -1125,7 +1138,7 @@ void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool m_pOut->Pop(); } -void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor ) const +void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor, const Color *pColor ) const { OSL_ENSURE( OnWin(), "SwTextPaintInfo::DrawBackground: printer pollution ?" ); @@ -1136,18 +1149,14 @@ void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor ) const return; OutputDevice* pOut = const_cast<OutputDevice*>(GetOut()); - pOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); - // For dark background we do not want to have a filled rectangle - if ( GetVsh() && GetVsh()->GetWin() && lcl_IsDarkBackground( *this ) ) - { - pOut->SetLineColor( SwViewOption::GetFontColor() ); - } + if ( pColor ) + pOut->SetFillColor( *pColor ); else - { - pOut->SetFillColor( SwViewOption::GetFieldShadingsColor() ); - pOut->SetLineColor(); - } + pOut->SetFillColor( GetOpt().GetFieldShadingsColor() ); + + pOut->SetLineColor(); DrawRect( aIntersect, true ); pOut->Pop(); @@ -1162,7 +1171,7 @@ void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const { SwPosition const aPosition(m_pFrame->MapViewToModelPos(GetIdx())); const ::sw::mark::IMark* pFieldmark = - m_pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition); + m_pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); bool bIsStartMark = (TextFrameIndex(1) == GetLen() && CH_TXT_ATR_FIELDSTART == GetText()[sal_Int32(GetIdx())]); if(pFieldmark) { @@ -1171,12 +1180,12 @@ void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const if(bIsStartMark) SAL_INFO("sw.core", "Found StartMark"); if (OnWin() && (pFieldmark!=nullptr || bIsStartMark) && - SwViewOption::IsFieldShadings() && + GetOpt().IsFieldShadings() && !GetOpt().IsPagePreview()) { OutputDevice* pOutDev = const_cast<OutputDevice*>(GetOut()); - pOutDev->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); - pOutDev->SetFillColor( SwViewOption::GetFieldShadingsColor() ); + pOutDev->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + pOutDev->SetFillColor( GetOpt().GetFieldShadingsColor() ); pOutDev->SetLineColor( ); pOutDev->DrawRect( aIntersect.SVRect() ); pOutDev->Pop(); @@ -1208,89 +1217,7 @@ void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const aFillColor = *m_pFnt->GetBackColor(); } - // tdf#104349 do not highlight portions of space chars before end of line if the compatibility option is enabled - // for LTR mode only - if ( !GetTextFrame()->IsRightToLeft() ) - { - if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS)) - { - bool draw = false; - bool full = false; - SwLinePortion *pPos = const_cast<SwLinePortion *>(&rPor); - TextFrameIndex nIdx = GetIdx(); - TextFrameIndex nLen; - - do - { - nLen = pPos->GetLen(); - for (TextFrameIndex i = nIdx; i < (nIdx + nLen); ++i) - { - if (i < TextFrameIndex(GetText().getLength()) - && GetText()[sal_Int32(i)] == CH_TXTATR_NEWLINE) - { - if ( i >= (GetIdx() + rPor.GetLen()) ) - { - goto drawcontinue; - } - } - if (i >= TextFrameIndex(GetText().getLength()) - || GetText()[sal_Int32(i)] != CH_BLANK) - { - draw = true; - if ( i >= (GetIdx() + rPor.GetLen()) ) - { - full = true; - goto drawcontinue; - } - } - } - nIdx += nLen; - pPos = pPos->GetNextPortion(); - } while ( pPos ); - - drawcontinue: - - if ( !draw ) - return; - - if ( !full ) - { - pPos = const_cast<SwLinePortion *>(&rPor); - nIdx = GetIdx(); - - nLen = pPos->GetLen(); - for (TextFrameIndex i = nIdx + nLen - TextFrameIndex(1); - i >= nIdx; --i) - { - if (i < TextFrameIndex(GetText().getLength()) - && GetText()[sal_Int32(i)] == CH_TXTATR_NEWLINE) - { - continue; - } - if (i >= TextFrameIndex(GetText().getLength()) - || GetText()[sal_Int32(i)] != CH_BLANK) - { - sal_uInt16 nOldWidth = rPor.Width(); - sal_uInt16 nNewWidth = GetTextSize(m_pOut, nullptr, - GetText(), nIdx, (i + TextFrameIndex(1) - nIdx)).Width(); - - const_cast<SwLinePortion&>(rPor).Width( nNewWidth ); - CalcRect( rPor, nullptr, &aIntersect, true ); - const_cast<SwLinePortion&>(rPor).Width( nOldWidth ); - - if ( !aIntersect.HasArea() ) - { - return; - } - - break; - } - } - } - } - } - - pTmpOut->Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + pTmpOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); pTmpOut->SetFillColor(aFillColor); pTmpOut->SetLineColor(); @@ -1312,71 +1239,276 @@ void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const } } +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 + 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::ControlChar: - if ( !GetOpt().IsPagePreview() - && !GetOpt().IsReadonly() - && SwViewOption::IsFieldShadings() - && ( PortionType::Number != nWhich - || m_pFrame->GetTextNodeForParaProps()->HasMarkedLabel())) // #i27615# + if ( !GetOpt().IsPagePreview() + && !GetOpt().IsReadonly() ) + { + switch( nWhich ) { - bDraw = PortionType::Footnote != nWhich || m_pFrame->IsFootnoteAllowed(); + case PortionType::Tab: + if ( GetOpt().IsViewMetaChars() ) + bDraw = GetOpt().IsTab(); + break; + case PortionType::SoftHyphen: + if ( GetOpt().IsViewMetaChars() ) + bDraw = GetOpt().IsSoftHyph(); + break; + case PortionType::Blank: + if ( GetOpt().IsViewMetaChars() ) + bDraw = GetOpt().IsHardBlank(); + break; + case PortionType::ControlChar: + if ( GetOpt().IsViewMetaChars() ) + bDraw = true; + break; + case PortionType::Bookmark: + // no shading + break; + case PortionType::Footnote: + case PortionType::QuoVadis: + case PortionType::Number: + case PortionType::Hidden: + case PortionType::Tox: + case PortionType::Ref: + case PortionType::Meta: + case PortionType::ContentControl: + case PortionType::Field: + case PortionType::InputField: + // input field shading also in read-only mode + if (GetOpt().IsFieldShadings() + && ( PortionType::Number != nWhich + || m_pFrame->GetTextNodeForParaProps()->HasMarkedLabel())) // #i27615# + { + bDraw = PortionType::Footnote != nWhich || m_pFrame->IsFootnoteAllowed(); + } + break; + default: + { + OSL_ENSURE( false, "SwTextPaintInfo::DrawViewOpt: don't know how to draw this" ); + break; + } } - break; - case PortionType::Bookmark: - // no shading - break; - case PortionType::InputField: - // input field shading also in read-only mode - if ( !GetOpt().IsPagePreview() - && SwViewOption::IsFieldShadings() ) + } + + if ( bDraw ) + DrawBackground( rPor, pColor ); +} + +void SwTextPaintInfo::NotifyURL_(const SwLinePortion& rPor) const +{ + assert(pNoteURL); + + SwRect aIntersect; + CalcRect(rPor, nullptr, &aIntersect); + + if (aIntersect.HasArea()) + { + SwTextNode* pNd = const_cast<SwTextNode*>(GetTextFrame()->GetTextNodeFirst()); + SwTextAttr* const pAttr = pNd->GetTextAttrAt(sal_Int32(GetIdx()), RES_TXTATR_INETFMT); + if (pAttr) { - bDraw = true; + const SwFormatINetFormat& rFormat = pAttr->GetINetFormat(); + pNoteURL->InsertURLNote(rFormat.GetValue(), rFormat.GetTargetFrame(), aIntersect); } - break; - case PortionType::Table: - 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: + else if (rPor.IsFlyCntPortion()) { - OSL_ENSURE( false, "SwTextPaintInfo::DrawViewOpt: don't know how to draw this" ); - break; + if (auto* pFlyContentPortion = dynamic_cast<const sw::FlyContentPortion*>(&rPor)) + { + if (auto* pFlyFtame = pFlyContentPortion->GetFlyFrame()) + { + if (auto* pFormat = pFlyFtame->GetFormat()) + { + auto& url = pFormat->GetURL(); // TODO: url.GetMap() ? + pNoteURL->InsertURLNote(url.GetURL(), url.GetTargetFrameName(), aIntersect); + } + } + } } } - if ( bDraw ) - DrawBackground( rPor ); } static void lcl_InitHyphValues( PropertyValues &rVals, - sal_Int16 nMinLeading, sal_Int16 nMinTrailing, bool bNoCapsHyphenation ) + sal_Int16 nMinLeading, sal_Int16 nMinTrailing, + bool bNoCapsHyphenation, bool bNoLastWordHyphenation, + sal_Int16 nMinWordLength, sal_Int16 nTextHyphZone, bool bKeep, sal_Int16 nKeepType, + sal_Int16 nCompoundMinLeading ) { sal_Int32 nLen = rVals.getLength(); if (0 == nLen) // yet to be initialized? { - rVals.realloc( 3 ); + rVals.realloc( 9 ); PropertyValue *pVal = rVals.getArray(); pVal[0].Name = UPN_HYPH_MIN_LEADING; @@ -1390,13 +1522,43 @@ static void lcl_InitHyphValues( PropertyValues &rVals, 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; + + pVal[6].Name = UPN_HYPH_KEEP_TYPE; + pVal[6].Handle = UPH_HYPH_KEEP_TYPE; + pVal[6].Value <<= nKeepType; + + pVal[7].Name = UPN_HYPH_COMPOUND_MIN_LEADING; + pVal[7].Handle = UPH_HYPH_COMPOUND_MIN_LEADING; + pVal[7].Value <<= nCompoundMinLeading; + + pVal[8].Name = UPN_HYPH_KEEP; + pVal[8].Handle = UPH_HYPH_KEEP; + pVal[8].Value <<= bKeep; } - else if (3 == nLen) // already initialized once? + else if (9 == 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; + pVal[6].Value <<= nKeepType; + pVal[7].Value <<= nCompoundMinLeading; + pVal[8].Value <<= bKeep; } else { OSL_FAIL( "unexpected size of sequence" ); @@ -1405,7 +1567,7 @@ static void lcl_InitHyphValues( PropertyValues &rVals, const PropertyValues & SwTextFormatInfo::GetHyphValues() const { - OSL_ENSURE( 3 == m_aHyphVals.getLength(), + OSL_ENSURE( 9 == m_aHyphVals.getLength(), "hyphenation values not yet initialized" ); return m_aHyphVals; } @@ -1423,8 +1585,16 @@ bool SwTextFormatInfo::InitHyph( const bool bAutoHyphen ) { 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(); - lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing, bNoCapsHyphenation); + const bool bNoLastWordHyphenation = rAttr.IsNoLastWordHyphenation(); + const sal_Int16 nTextHyphZone = rAttr.GetTextHyphenZone(); + const bool bKeep = rAttr.IsKeep(); + const sal_Int16 nKeepType = rAttr.GetKeepType(); + const sal_Int16 nCompoundMinimalLeading = std::max(rAttr.GetCompoundMinLead(), sal_uInt8(2)); + lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing, + bNoCapsHyphenation, bNoLastWordHyphenation, + nMinimalWordLength, nTextHyphZone, bKeep, nKeepType, nCompoundMinimalLeading ); } return bAuto; } @@ -1456,7 +1626,7 @@ void SwTextFormatInfo::CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwT SetLineStart(TextFrameIndex(0)); SvtCTLOptions::TextNumerals const nTextNumerals( - SW_MOD()->GetCTLOptions().GetCTLTextNumerals()); + SvtCTLOptions::GetCTLTextNumerals()); // cannot cache for NUMERALS_CONTEXT because we need to know the string // for the whole paragraph now if (nTextNumerals != SvtCTLOptions::NUMERALS_CONTEXT) @@ -1483,7 +1653,9 @@ bool SwTextFormatInfo::IsHyphenate() const return false; LanguageType eTmp = GetFont()->GetLanguage(); - if( LANGUAGE_DONTKNOW == eTmp || LANGUAGE_NONE == eTmp ) + // 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(); @@ -1501,7 +1673,7 @@ bool SwTextFormatInfo::IsHyphenate() const pShell->AppendInfoBarWhenReady( "hyphenationmissing", SwResId(STR_HYPH_MISSING), SwResId(STR_HYPH_MISSING_DETAIL) - .replaceFirst("%1", g_pBreakIt->GetLocale(eTmp).Language), + .replaceFirst("%1", LanguageTag::convertToBcp47( g_pBreakIt->GetLocale(eTmp))), InfobarType::WARNING); } } @@ -1616,6 +1788,37 @@ SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf, 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(); @@ -1758,7 +1961,9 @@ SwTwips SwTextFormatInfo::GetLineWidth() const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( DocumentSettingId::TAB_OVER_MARGIN); - if (!bTabOverMargin) + const bool bTabOverSpacing = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::TAB_OVER_SPACING); + if (!bTabOverMargin && !bTabOverSpacing) return nLineWidth; SwTabPortion* pLastTab = GetLastTab(); @@ -1789,6 +1994,12 @@ SwTwips SwTextFormatInfo::GetLineWidth() // 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; } @@ -1803,6 +2014,7 @@ SwTextSlot::SwTextSlot( , m_pOldGrammarCheckList(nullptr) , nIdx(0) , nLen(0) + , nMeasureLen(0) , pInf(nullptr) { if( rCh.isEmpty() ) @@ -1822,11 +2034,15 @@ SwTextSlot::SwTextSlot( 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 @@ -1900,6 +2116,7 @@ SwTextSlot::~SwTextSlot() pInf->SetText( *pOldText ); pInf->SetIdx( nIdx ); pInf->SetLen( nLen ); + pInf->SetMeasureLen( nMeasureLen ); // ST2 // Restore old smart tag list diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index bb245d5b031f..9c4126a7a6e9 100644 --- a/sw/source/core/text/inftxt.hxx +++ b/sw/source/core/text/inftxt.hxx @@ -19,6 +19,7 @@ #pragma once #include <memory> +#include <optional> #include <com/sun/star/beans/PropertyValues.hpp> #include <map> @@ -60,7 +61,7 @@ class SwLineInfo { friend class SwTextIter; - std::unique_ptr<SvxTabStopItem> m_pRuler; + std::optional<SvxTabStopItem> m_oRuler; const SvxLineSpacingItem *m_pSpace; SvxParaVertAlignItem::Align m_nVertAlign; sal_uInt16 m_nDefTabStop; @@ -70,12 +71,11 @@ class SwLineInfo void CtorInitLineInfo( const SwAttrSet& rAttrSet, const SwTextNode& rTextNode ); - SwLineInfo(); - ~SwLineInfo(); + SW_DLLPUBLIC SwLineInfo(); + SW_DLLPUBLIC ~SwLineInfo(); public: // #i24363# tab stops relative to indent - returns the tab stop following nSearchPos or NULL - const SvxTabStop *GetTabStop( const SwTwips nSearchPos, - const SwTwips nRight ) const; + 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 @@ -145,7 +145,7 @@ protected: // 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<vcl::TextLayoutCache> m_pCachedVclData; + std::shared_ptr<const vcl::text::TextLayoutCache> m_pCachedVclData; SwFont *m_pFnt; SwUnderlineFont *m_pUnderFnt; // Font for underlining @@ -154,6 +154,7 @@ protected: 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; @@ -182,12 +183,12 @@ public: SwTextSizeInfo( const SwTextSizeInfo &rInf ); SwTextSizeInfo( const SwTextSizeInfo &rInf, const OUString* pText, TextFrameIndex nIdx = TextFrameIndex(0) ); - SwTextSizeInfo(SwTextFrame *pTextFrame, TextFrameIndex nIndex = TextFrameIndex(0)); + SW_DLLPUBLIC 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::unique_ptr<SwMultiCreator> GetMultiCreator(TextFrameIndex &rPos, SwMultiPortion const* pM) const; + std::optional<SwMultiCreator> GetMultiCreator(TextFrameIndex &rPos, SwMultiPortion const* pM) const; bool OnWin() const { return m_bOnWin; } void SetOnWin( const bool bNew ) { m_bOnWin = bNew; } @@ -251,7 +252,7 @@ public: void GetTextSize( const SwScriptInfo* pSI, TextFrameIndex nIdx, TextFrameIndex nLen, const sal_uInt16 nComp, sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff, - vcl::TextLayoutCache const* = nullptr) const; + vcl::text::TextLayoutCache const* = nullptr) const; inline SwPosSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, TextFrameIndex nLen) const; inline SwPosSize GetTextSize( const OUString &rText ) const; @@ -259,19 +260,22 @@ public: TextFrameIndex GetTextBreak( const tools::Long nLineWidth, const TextFrameIndex nMaxLen, const sal_uInt16 nComp, - vcl::TextLayoutCache const*) const; + vcl::text::TextLayoutCache const*) const; TextFrameIndex GetTextBreak( const tools::Long nLineWidth, const TextFrameIndex nMaxLen, const sal_uInt16 nComp, TextFrameIndex& rExtraCharPos, - vcl::TextLayoutCache const*) const; + 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! @@ -325,11 +329,11 @@ public: { return ( m_pKanaComp && m_nKanaIdx < m_pKanaComp->size() ) ? (*m_pKanaComp)[m_nKanaIdx] : 0; } - const std::shared_ptr<vcl::TextLayoutCache>& GetCachedVclData() const + const std::shared_ptr<const vcl::text::TextLayoutCache>& GetCachedVclData() const { return m_pCachedVclData; } - void SetCachedVclData(std::shared_ptr<vcl::TextLayoutCache> const& pCachedVclData) + void SetCachedVclData(std::shared_ptr<const vcl::text::TextLayoutCache> const& pCachedVclData) { m_pCachedVclData = pCachedVclData; } @@ -354,6 +358,7 @@ class SwTextPaintInfo : public SwTextSizeInfo const bool bGrammarCheck = false ); SwTextPaintInfo &operator=(const SwTextPaintInfo&) = delete; + void NotifyURL_(const SwLinePortion& rPor) const; protected: SwTextPaintInfo() @@ -399,8 +404,8 @@ public: void DrawLineBreak( const SwLinePortion &rPor ) const; void DrawRedArrow( const SwLinePortion &rPor ) const; void DrawPostIts( bool bScript ) const; - void DrawBackground( const SwLinePortion &rPor ) const; - void DrawViewOpt( const SwLinePortion &rPor, PortionType nWhich ) 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; /** @@ -412,6 +417,14 @@ public: void DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const; + void DrawCSDFHighlighting(const SwLinePortion &rPor) const; + + void NotifyURL(const SwLinePortion& rPor) const + { + if (URLNotify()) + NotifyURL_(rPor); + } + /** * Calculate the rectangular area where the portion takes place. * @param[in] rPor portion for which the method specify the painting area @@ -437,8 +450,10 @@ public: void SetSpaceIdx( sal_uInt16 nNew ) { m_nSpaceIdx = nNew; } void IncSpaceIdx() { ++m_nSpaceIdx; } void RemoveFirstSpaceAdd() { m_pSpaceAdd->erase( m_pSpaceAdd->begin() ); } - tools::Long GetSpaceAdd() const - { return ( m_pSpaceAdd && m_nSpaceIdx < m_pSpaceAdd->size() ) + 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; } @@ -509,6 +524,16 @@ class SwTextFormatInfo : public SwTextPaintInfo 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 ); @@ -558,7 +583,7 @@ public: void SetRoot( SwLineLayout *pNew ) { m_pRoot = pNew; } SwLinePortion *GetLast() { return m_pLast; } - void SetLast( SwLinePortion *pNewLast ) { m_pLast = pNewLast; } + void SetLast(SwLinePortion* pNewLast); bool IsFull() const { return m_bFull; } void SetFull( const bool bNew ) { m_bFull = bNew; } bool IsHyphForbud() const @@ -585,6 +610,13 @@ public: 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; } @@ -673,7 +705,7 @@ public: class SwTextSlot final { OUString aText; - std::shared_ptr<vcl::TextLayoutCache> m_pOldCachedVclData; + std::shared_ptr<const vcl::text::TextLayoutCache> m_pOldCachedVclData; const OUString *pOldText; sw::WrongListIterator * m_pOldSmartTagList; sw::WrongListIterator * m_pOldGrammarCheckList; @@ -681,6 +713,7 @@ class SwTextSlot final std::unique_ptr<sw::WrongListIterator> m_pTempIter; TextFrameIndex nIdx; TextFrameIndex nLen; + TextFrameIndex nMeasureLen; bool bOn; SwTextSizeInfo *pInf; @@ -715,6 +748,12 @@ inline sal_uInt16 SwTextSizeInfo::GetTextHeight() const 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())); diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx index a5944e49e357..4dcaf03df1f8 100644 --- a/sw/source/core/text/itradj.cxx +++ b/sw/source/core/text/itradj.cxx @@ -122,7 +122,7 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, // 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() ); + rKashidas = rSI.KashidaJustify(nullptr, nullptr, rItr.GetStart(), rItr.GetLength()); if (rKashidas <= 0) // nothing to do return true; @@ -147,7 +147,7 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) nNext = nEnd; - sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); + sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx); if (nKashidasInAttr > 0) { // Kashida glyph looks suspicious, skip Kashida justification @@ -164,8 +164,8 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, } else { - ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode(); - rInf.GetOut()->SetLayoutMode ( nOldLayout | ComplexTextLayoutFlags::BiDiRtl ); + 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, @@ -212,7 +212,7 @@ static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwT if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd) nNext = nEnd; - sal_Int32 nKashidasInAttr = rSI.KashidaJustify ( nullptr, nullptr, nIdx, nNext - nIdx ); + 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 ) ) @@ -290,6 +290,8 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, bool bDoNotJustifyTab = false; SwLinePortion *pPos = pCurrent->GetNextPortion(); + // calculate real text width for shrinking + tools::Long nBreakWidth = 0; while( pPos ) { @@ -367,6 +369,11 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, 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 ) @@ -382,7 +389,7 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, } } - pCurrent->SetLLSpaceAdd( nSpaceAdd , nSpaceIdx ); + pCurrent->SetLLSpaceAdd( nSpaceSub ? nSpaceSub : nSpaceAdd, nSpaceIdx ); pPos->Width( static_cast<SwGluePortion*>(pPos)->GetFixWidth() ); } else if (IsOneBlock() && nCharCnt > TextFrameIndex(1)) @@ -399,6 +406,10 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent, else ++nGluePortion; } + else + { + nBreakWidth += pPos->Width(); + } GetInfo().SetIdx( GetInfo().GetIdx() + pPos->GetLen() ); if ( pPos == pStopAt ) { @@ -488,7 +499,7 @@ SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) else nCompress = 10000 - nCompress; - ( pCurrent->GetKanaComp() )[ nKanaIdx ] = static_cast<sal_uInt16>(nCompress); + ( pCurrent->GetKanaComp() )[ nKanaIdx ] = o3tl::narrowing<sal_uInt16>(nCompress); nKanaDiffSum = 0; } @@ -509,11 +520,11 @@ SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) { if ( pPos->InTextGrp() ) { - const sal_uInt16 nMinWidth = pPos->Width(); + const SwTwips nMinWidth = pPos->Width(); // get maximum portion width from info structure, calculated // during text formatting - sal_uInt16 nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos ); + SwTwips nMaxWidthDiff = GetInfo().GetMaxWidthDiff( pPos ); // check, if information is stored under other key if ( !nMaxWidthDiff && pPos == pCurrent->GetFirstPortion() ) @@ -524,7 +535,7 @@ SwTwips SwTextAdjuster::CalcKanaAdj( SwLineLayout* pCurrent ) } else if( pPos->InGlueGrp() && pPos->InFixMargGrp() ) { - pPos->Width( static_cast<sal_uInt16>(pPos->Width() - nDecompress) ); + pPos->Width(pPos->Width() - nDecompress); if ( pPos->InTabGrp() ) // set fix width to width @@ -747,7 +758,7 @@ void SwTextAdjuster::CalcDropAdjust() OSL_ENSURE( 1<GetDropLines() && SvxAdjust::Left!=GetAdjust() && SvxAdjust::Block!=GetAdjust(), "CalcDropAdjust: No reason for DropAdjustment." ); - const sal_uInt16 nLineNumber = GetLineNr(); + const sal_Int32 nLineNumber = GetLineNr(); // 1) Skip dummies Top(); @@ -780,7 +791,7 @@ void SwTextAdjuster::CalcDropAdjust() const auto nDropLineStart = GetLineStart() + pLeft->Width() + pDropPor->Width(); auto nMinLeft = nDropLineStart; - for( sal_uInt16 i = 1; i < GetDropLines(); ++i ) + for( sal_Int32 i = 1; i < GetDropLines(); ++i ) { if( NextLine() ) { @@ -831,7 +842,7 @@ void SwTextAdjuster::CalcDropRepaint() SwRepaint &rRepaint = GetInfo().GetParaPortion()->GetRepaint(); if( rRepaint.Top() > Y() ) rRepaint.Top( Y() ); - for( sal_uInt16 i = 1; i < GetDropLines(); ++i ) + for( sal_Int32 i = 1; i < GetDropLines(); ++i ) NextLine(); const SwTwips nBottom = Y() + GetLineHeight() - 1; if( rRepaint.Bottom() < nBottom ) diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index a0ae8073c27b..e498db01c323 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -57,6 +57,10 @@ #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; @@ -260,6 +264,7 @@ 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 ... { @@ -267,7 +272,7 @@ void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos) // 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 < pHints->Count()) && + while ((m_nEndIndex < nHintsCount) && ((pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos)) { // Close the TextAttributes, whose StartPos were before or at @@ -278,7 +283,7 @@ void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos) } else // skip the not opened ends { - while ((m_nEndIndex < pHints->Count()) && + while ((m_nEndIndex < nHintsCount) && (pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos)) { m_nEndIndex++; @@ -287,7 +292,7 @@ void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos) // 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 < pHints->Count()) && + while ((m_nStartIndex < nHintsCount) && ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos)) { @@ -329,7 +334,6 @@ bool SwAttrIter::Seek(TextFrameIndex const nNewPos) } while (nPos < m_pTextNode->Len()); } - assert(m_nChgCnt == 0); // should have reset it all? there cannot be ExtOn() inside of a Delete redline, surely? // 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. @@ -443,7 +447,7 @@ static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems } else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER) { - pAttrs[RES_CHRATR_END] = pItem; + pAttrs[RES_CHRATR_END - RES_CHRATR_BEGIN] = pItem; } } } @@ -459,8 +463,8 @@ static bool CanSkipOverRedline( size_t nStartIndex(rStartIndex); size_t nEndIndex(rEndIndex); SwPosition const*const pRLEnd(rRedline.End()); - if (!pRLEnd->nNode.GetNode().IsTextNode() // if fully deleted... - || pRLEnd->nContent == pRLEnd->nNode.GetNode().GetTextNode()->Len()) + if (!pRLEnd->GetNode().IsTextNode() // if fully deleted... + || pRLEnd->GetContentIndex() == pRLEnd->GetNode().GetTextNode()->Len()) { // shortcut: nothing follows redline // current state is end state @@ -470,15 +474,15 @@ static bool CanSkipOverRedline( // 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->nNode.GetNode()) + 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->nNode.GetNode() - ? pRLEnd->nContent.GetIndex() + sal_Int32 const nRedlineEnd(rStartNode == pRLEnd->GetNode() + ? pRLEnd->GetContentIndex() : rStartNode.Len()); for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex) { @@ -509,6 +513,7 @@ static bool CanSkipOverRedline( case RES_TXTATR_INETFMT: case RES_TXTATR_CJK_RUBY: case RES_TXTATR_INPUTFIELD: + case RES_TXTATR_CONTENTCONTROL: { if (!isTheAnswerYes) return false; // always break } @@ -539,10 +544,10 @@ static bool CanSkipOverRedline( } } assert(nEndIndex == pStartHints->Count() || - pRLEnd->nContent.GetIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd()); + pRLEnd->GetContentIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd()); } - if (&rStartNode != &pRLEnd->nNode.GetNode()) + if (rStartNode != pRLEnd->GetNode()) { nStartIndex = 0; nEndIndex = 0; @@ -554,17 +559,17 @@ static bool CanSkipOverRedline( // ... and the charfmt must be *nominally* the same SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, }; - if (&rStartNode != &pRLEnd->nNode.GetNode()) + if (rStartNode != pRLEnd->GetNode()) { // nodes' attributes are only needed if there are different nodes InsertCharAttrs(activeCharAttrsEnd, - pRLEnd->nNode.GetNode().GetTextNode()->GetSwAttrSet()); + pRLEnd->GetNode().GetTextNode()->GetSwAttrSet()); } - if (SwpHints *const pEndHints = pRLEnd->nNode.GetNode().GetTextNode()->GetpSwpHints()) + 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->nNode.GetNode() + sal_Int32 const nRedlineStart(rStartNode == pRLEnd->GetNode() ? nStartRedline : 0); #endif @@ -575,7 +580,7 @@ static bool CanSkipOverRedline( // 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->nContent.GetIndex() < pAttr->GetStart()) + if (pRLEnd->GetContentIndex() < pAttr->GetStart()) { break; } @@ -586,7 +591,7 @@ static bool CanSkipOverRedline( continue; } assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise? - if (*pAttr->End() <= pRLEnd->nContent.GetIndex()) + if (*pAttr->End() <= pRLEnd->GetContentIndex()) { continue; } @@ -599,6 +604,7 @@ static bool CanSkipOverRedline( case RES_TXTATR_INETFMT: case RES_TXTATR_CJK_RUBY: case RES_TXTATR_INPUTFIELD: + case RES_TXTATR_CONTENTCONTROL: { if (!isTheAnswerYes) return false; } @@ -629,7 +635,7 @@ static bool CanSkipOverRedline( default: assert(false); } } - if (&rStartNode != &pRLEnd->nNode.GetNode()) + 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 @@ -638,7 +644,7 @@ static bool CanSkipOverRedline( SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex)); if (!pAttr->End()) continue; - if (pRLEnd->nContent.GetIndex() < *pAttr->End()) + if (pRLEnd->GetContentIndex() < *pAttr->End()) { break; } @@ -653,9 +659,9 @@ static bool CanSkipOverRedline( } for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i) { - // all of these are poolable -// assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->IsItemPoolable(*activeCharAttrsStart[i])); - if (activeCharAttrsStart[i] != activeCharAttrsEnd[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; } @@ -743,20 +749,20 @@ TextFrameIndex SwAttrIter::GetNextAttr() const if (redline.second.first) { assert(m_pMergedPara); - assert(redline.second.first->End()->nNode.GetIndex() <= m_pMergedPara->pLastNode->GetIndex() - || !redline.second.first->End()->nNode.GetNode().IsTextNode()); + 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()->nNode.GetNode() != pTextNode) + if (&redline.second.first->End()->GetNode() != pTextNode) { - pTextNode = redline.second.first->End()->nNode.GetNode().GetTextNode(); - nPosition = redline.second.first->End()->nContent.GetIndex(); + pTextNode = redline.second.first->End()->GetNode().GetTextNode(); + nPosition = redline.second.first->End()->GetContentIndex(); } else { - nPosition = redline.second.first->End()->nContent.GetIndex(); + nPosition = redline.second.first->End()->GetContentIndex(); } } else @@ -869,7 +875,7 @@ public: 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 - sal_uLong m_nIndex; // index of the node + SwNodeOffset m_nIndex; // index of the node void Minimum( tools::Long nNew ) { if (nNew > m_nMinWidth) m_nMinWidth = nNew; @@ -878,7 +884,7 @@ public: } -static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) +static void lcl_MinMaxNode(SwFrameFormat* pNd, SwMinMaxNodeArgs& rIn) { const SwFormatAnchor& rFormatA = pNd->GetAnchor(); @@ -888,9 +894,9 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) return; } - const SwPosition *pPos = rFormatA.GetContentAnchor(); - OSL_ENSURE(pPos && pIn, "Unexpected NULL arguments"); - if (!pPos || !pIn || pIn->m_nIndex != pPos->nNode.GetIndex()) + const SwNode *pAnchorNode = rFormatA.GetAnchorNode(); + OSL_ENSURE(pAnchorNode, "Unexpected NULL arguments"); + if (!pAnchorNode || rIn.m_nIndex != pAnchorNode->GetIndex()) return; tools::Long nMin, nMax; @@ -901,7 +907,7 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) // Does the frame contain a table at the start or the end? const SwNodes& rNodes = pNd->GetDoc()->GetNodes(); const SwFormatContent& rFlyContent = pNd->GetContent(); - sal_uLong nStt = rFlyContent.GetContentIdx()->GetIndex(); + SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex(); SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode(); if( !pTableNd ) { @@ -953,7 +959,7 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() ) { - pIn->Minimum( nMin ); + rIn.Minimum( nMin ); return; } @@ -966,33 +972,33 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) { if( nDiff ) { - pIn->m_nRightRest -= pIn->m_nRightDiff; - pIn->m_nRightDiff = nDiff; + rIn.m_nRightRest -= rIn.m_nRightDiff; + rIn.m_nRightDiff = nDiff; } if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() ) { - if (pIn->m_nRightRest > 0) - pIn->m_nRightRest = 0; + if (rIn.m_nRightRest > 0) + rIn.m_nRightRest = 0; } - pIn->m_nRightRest -= nMin; + rIn.m_nRightRest -= nMin; break; } case text::HoriOrientation::LEFT: { if( nDiff ) { - pIn->m_nLeftRest -= pIn->m_nLeftDiff; - pIn->m_nLeftDiff = nDiff; + rIn.m_nLeftRest -= rIn.m_nLeftDiff; + rIn.m_nLeftDiff = nDiff; } - if (text::RelOrientation::FRAME != rOrient.GetRelationOrient() && pIn->m_nLeftRest < 0) - pIn->m_nLeftRest = 0; - pIn->m_nLeftRest -= nMin; + if (text::RelOrientation::FRAME != rOrient.GetRelationOrient() && rIn.m_nLeftRest < 0) + rIn.m_nLeftRest = 0; + rIn.m_nLeftRest -= nMin; break; } default: { - pIn->m_nMaxWidth += nMax; - pIn->Minimum( nMin ); + rIn.m_nMaxWidth += nMax; + rIn.Minimum(nMin); } } } @@ -1003,13 +1009,13 @@ static void lcl_MinMaxNode( SwFrameFormat* pNd, SwMinMaxNodeArgs* pIn ) * 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( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rMax, +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(); + pOut = pSh->GetWin()->GetOutDev(); if( !pOut ) pOut = Application::GetDefaultDevice(); @@ -1020,8 +1026,9 @@ void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rM rMax = 0; rAbsMin = 0; - const SvxLRSpaceItem &rSpace = GetSwAttrSet().GetLRSpace(); - tools::Long nLROffset = rSpace.GetTextLeft() + GetLeftMarginWithNum( true ); + 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 ) @@ -1031,17 +1038,17 @@ void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rM aNodeArgs.m_nMinWidth = 0; aNodeArgs.m_nMaxWidth = 0; aNodeArgs.m_nLeftRest = nLROffset; - aNodeArgs.m_nRightRest = rSpace.GetRight(); + aNodeArgs.m_nRightRest = rRightMargin.GetRight(); aNodeArgs.m_nLeftDiff = 0; aNodeArgs.m_nRightDiff = 0; if( nIndex ) { - SwFrameFormats* pTmp = const_cast<SwFrameFormats*>(GetDoc().GetSpzFrameFormats()); - if( pTmp ) + sw::SpzFrameFormats* pSpzs = const_cast<sw::SpzFrameFormats*>(GetDoc().GetSpzFrameFormats()); + if(pSpzs) { aNodeArgs.m_nIndex = nIndex; - for( SwFrameFormat *pFormat : *pTmp ) - lcl_MinMaxNode( pFormat, &aNodeArgs ); + for(auto pFormat: *pSpzs) + lcl_MinMaxNode(pFormat, aNodeArgs); } } if (aNodeArgs.m_nLeftRest < 0) @@ -1051,7 +1058,7 @@ void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rM aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nLeftRest; if (aNodeArgs.m_nRightRest < 0) - aNodeArgs.Minimum(rSpace.GetRight() - aNodeArgs.m_nRightRest); + aNodeArgs.Minimum(rRightMargin.GetRight() - aNodeArgs.m_nRightRest); aNodeArgs.m_nRightRest -= aNodeArgs.m_nRightDiff; if (aNodeArgs.m_nRightRest < 0) aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nRightRest; @@ -1224,7 +1231,7 @@ void SwTextNode::GetMinMaxSize( sal_uLong nIndex, sal_uLong& rMin, sal_uLong &rM if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth) rMax = aArg.m_nRowWidth; - nLROffset += rSpace.GetRight(); + nLROffset += rRightMargin.GetRight(); rAbsMin += nLROffset; rAbsMin += nAdd; @@ -1301,7 +1308,7 @@ sal_uInt16 SwTextFrame::GetScalingOfSelectedText( // 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 ); + SwTextAttrEnd aAttr( SfxPoolItemHolder(getRootFrame()->GetCurrShell()->GetAttrPool(), &aItem), 0, COMPLETE_STRING ); aAttr.SetPriorityAttr( true ); rAH.PushAndChg( aAttr, *(aIter.GetFnt()) ); @@ -1432,7 +1439,7 @@ sal_uInt16 SwTextFrame::GetScalingOfSelectedText( SwTextIter aLine(this, &aInf); aLine.CharToLine( nStart ); pOut->SetMapMode( aOldMap ); - return static_cast<sal_uInt16>( nWidth ? + return o3tl::narrowing<sal_uInt16>( nWidth ? ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 ); } // no frame or no paragraph, we take the height of the character @@ -1442,7 +1449,142 @@ sal_uInt16 SwTextFrame::GetScalingOfSelectedText( pOut->SetMapMode( aOldMap ); SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nStart), 1); - return static_cast<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 ); + 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 @@ -1463,8 +1605,7 @@ SwTwips SwTextNode::GetWidthOfLeadingTabs() const if ( nIdx > 0 ) { - SwPosition aPos( *this ); - aPos.nContent += nIdx; + SwPosition aPos( *this, nIdx ); // Find the non-follow text frame: SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this); diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx index e4dedc22cda1..4ad2c6d04c5f 100644 --- a/sw/source/core/text/itratr.hxx +++ b/sw/source/core/text/itratr.hxx @@ -29,7 +29,7 @@ class SwRedlineItr; class SwViewShell; class SwTextFrame; -class SwAttrIter +class SAL_DLLPUBLIC_RTTI SwAttrIter { friend class SwFontSave; protected: @@ -61,14 +61,14 @@ private: void SetFnt( SwFont* pNew ) { m_pFont = pNew; } void InitFontAndAttrHandler( SwTextNode const& rPropsNode, SwTextNode const& rTextNode, - OUString const& rText, bool const* pbVertLayout, + 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); + SW_DLLPUBLIC explicit SwAttrIter(SwTextNode const * pTextNode); public: /// All subclasses of this always have a SwTextFrame passed to the @@ -76,7 +76,7 @@ public: /// SwTextFrame in certain special cases via this ctor here SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame = nullptr); - virtual ~SwAttrIter(); + SW_DLLPUBLIC virtual ~SwAttrIter(); SwRedlineItr *GetRedln() { return m_pRedline.get(); } // The parameter returns the position of the next change before or at the diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx index f7c6380cb3bb..ea399a66f5d9 100644 --- a/sw/source/core/text/itrcrsr.cxx +++ b/sw/source/core/text/itrcrsr.cxx @@ -82,7 +82,7 @@ static void lcl_GetCharRectInsideField( SwTextSizeInfo& rInf, SwRect& rOrig, if ( ! pPor->GetNextPortion() || nFieldIdx + nFieldLen > nCharOfst ) break; - nFieldIdx = nFieldIdx + nFieldLen; + nFieldIdx += nFieldLen; rOrig.Pos().AdjustX(pPor->Width() ); pPor = pPor->GetNextPortion(); @@ -142,7 +142,7 @@ namespace { nListLevel = MAXLEVEL - 1; const SwNumFormat& rNumFormat = - rTextNode.GetNumRule()->Get( static_cast<sal_uInt16>(nListLevel) ); + rTextNode.GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) ); if ( rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT ) { bRet = true; @@ -161,12 +161,13 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p GetInfo().SetFont( GetFnt() ); const SwTextNode *const pNode = m_pFrame->GetTextNodeForParaProps(); - const SvxLRSpaceItem &rSpace = pNode->GetSwAttrSet().GetLRSpace(); + 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(); + const bool bListLevelIndentsApplicable = pTextNode->AreListLevelIndentsApplicable() != ::sw::ListLevelIndents::No; const bool bListLevelIndentsApplicableAndLabelAlignmentActive = bListLevelIndentsApplicable && bLabelAlignmentActive; // Carefully adjust the text formatting ranges. @@ -191,9 +192,7 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p // #i95907# // #i111284# // rSpace.GetLeft() + rSpace.GetTextLeft(); - ( bListLevelIndentsApplicableAndLabelAlignmentActive - ? 0 - : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + (rTextLeftMargin.GetLeft(rFirstLine) - rTextLeftMargin.GetTextLeft()); } else { @@ -209,14 +208,12 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p pNode->GetLeftMarginWithNum() - // #i95907# // #i111284# - ( bListLevelIndentsApplicableAndLabelAlignmentActive - ? 0 - : ( rSpace.GetLeft() - rSpace.GetTextLeft() ) ); + (rTextLeftMargin.GetLeft(rFirstLine) - rTextLeftMargin.GetTextLeft()); } else { mnLeft = m_pFrame->getFrameArea().Left() + - std::max( tools::Long( rSpace.GetTextLeft() + nLMWithNum ), + std::max(tools::Long(rTextLeftMargin.GetTextLeft() + nLMWithNum), m_pFrame->getFramePrintArea().Left() ); } } @@ -228,7 +225,8 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p // paras inside cells inside new documents: ( pNode->getIDocumentSettingAccess()->get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) || !m_pFrame->IsInTab() || - ( !nLMWithNum && (!bLabelAlignmentActive || bListLevelIndentsApplicable) ) ) ) + (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 @@ -242,7 +240,7 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p short nFLOfst = 0; tools::Long nFirstLineOfs = 0; if( !pNode->GetFirstLineOfsWithNum( nFLOfst ) && - rSpace.IsAutoFirst() ) + rFirstLine.IsAutoFirst()) { nFirstLineOfs = GetFnt()->GetSize( GetFnt()->GetActual() ).Height(); LanguageType const aLang = m_pFrame->GetLangOfChar( @@ -250,49 +248,54 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p if (aLang != LANGUAGE_KOREAN && aLang != LANGUAGE_JAPANESE) nFirstLineOfs<<=1; - const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); - if( pSpace ) + // 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)) { - switch( pSpace->GetLineSpaceRule() ) + const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing(); + if( pSpace ) { - case SvxLineSpaceRule::Auto: - break; - case SvxLineSpaceRule::Min: + switch( pSpace->GetLineSpaceRule() ) { - if( nFirstLineOfs < pSpace->GetLineHeight() ) - nFirstLineOfs = pSpace->GetLineHeight(); + case SvxLineSpaceRule::Auto: 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; + case SvxLineSpaceRule::Min: + { + if( nFirstLineOfs < pSpace->GetLineHeight() ) + nFirstLineOfs = pSpace->GetLineHeight(); + break; + } + case SvxLineSpaceRule::Fix: + nFirstLineOfs = pSpace->GetLineHeight(); break; + default: OSL_FAIL( ": unknown LineSpaceRule" ); } - case SvxInterLineSpaceRule::Fix: + switch( pSpace->GetInterLineSpaceRule() ) { - nFirstLineOfs += pSpace->GetInterLineSpace(); + 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" ); } - default: OSL_FAIL( ": unknown InterLineSpaceRule" ); } } } @@ -320,7 +323,7 @@ void SwTextMargin::CtorInitTextMargin( SwTextFrame *pNewFrame, SwTextSizeInfo *p else { mnFirst = m_pFrame->getFrameArea().Left() + - std::max( rSpace.GetTextLeft() + nLMWithNum+ nFirstLineOfs, + std::max(rTextLeftMargin.GetTextLeft() + nLMWithNum + nFirstLineOfs, m_pFrame->getFramePrintArea().Left() ); } @@ -396,6 +399,38 @@ void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *p // 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 ) @@ -428,7 +463,7 @@ void SwTextCursor::GetEndCharRect(SwRect* pOrig, const TextFrameIndex nOfst, tools::Long nLast = 0; SwLinePortion *pPor = m_pCurr->GetFirstPortion(); - sal_uInt16 nTmpHeight, nTmpAscent; + SwTwips nTmpHeight, nTmpAscent; CalcAscentAndHeight( nTmpAscent, nTmpHeight ); sal_uInt16 nPorHeight = nTmpHeight; sal_uInt16 nPorAscent = nTmpAscent; @@ -436,7 +471,7 @@ void SwTextCursor::GetEndCharRect(SwRect* pOrig, const TextFrameIndex nOfst, // Search for the last Text/EndPortion of the line while( pPor ) { - nX = nX + pPor->Width(); + nX += pPor->Width(); if( pPor->InTextGrp() || ( pPor->GetLen() && !pPor->IsFlyPortion() && !pPor->IsHolePortion() ) || pPor->IsBreakPortion() ) { @@ -479,7 +514,7 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, SwTextSizeInfo aInf( GetInfo(), &aText, m_nStart ); if( GetPropFont() ) aInf.GetFont()->SetProportion( GetPropFont() ); - sal_uInt16 nTmpAscent, nTmpHeight; // Line height + SwTwips nTmpAscent, nTmpHeight; // Line height CalcAscentAndHeight( nTmpAscent, nTmpHeight ); const Size aCharSize( 1, nTmpHeight ); const Point aCharPos; @@ -505,8 +540,8 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, } else { - sal_uInt16 nPorHeight = nTmpHeight; - sal_uInt16 nPorAscent = nTmpAscent; + SwTwips nPorHeight = nTmpHeight; + SwTwips nPorAscent = nTmpAscent; SwTwips nX = 0; SwTwips nTmpFirst = 0; SwLinePortion *pPor = m_pCurr->GetFirstPortion(); @@ -882,9 +917,21 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, } 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(); - pPor->SetLen( nOfst - aInf.GetIdx() ); 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 ); @@ -900,7 +947,12 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, if( bWidth ) { pPor->SetLen(pPor->GetLen() + TextFrameIndex(1)); - aInf.SetLen( pPor->GetLen() ); + 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 ); @@ -935,7 +987,7 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, { nX -= GetInfo().GetFont()->GetRightBorderSpace(); } - } + } } bWidth = false; break; @@ -957,7 +1009,7 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, while( pNext && !pNext->InFieldGrp() ) { OSL_ENSURE( !pNext->GetLen(), "Where's my field follow?" ); - nAddX = nAddX + pNext->Width(); + nAddX += pNext->Width(); pNext = pNext->GetNextPortion(); } if( !pNext ) @@ -1077,8 +1129,14 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, { const bool bOldOnWin = aInf.OnWin(); TextFrameIndex const nOldLen = pPor->GetLen(); - pPor->SetLen( TextFrameIndex(1) ); 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 ); @@ -1197,21 +1255,18 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst, ++nFindOfst; // skip lines for fields which cover more than one line - for ( sal_uInt16 i = 0; i < pCMS->m_pSpecialPos->nLineOfst; i++ ) + 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 ); - // This actually would have to be "-1 LogicToPixel", but that seems too - // expensive, so it's a value (-12), that should hopefully be OK. - const SwTwips nTmpRight = Right() - 12; - pOrig->Pos().AdjustX(aCharPos.X() ); pOrig->Pos().AdjustY(aCharPos.Y() ); @@ -1223,11 +1278,6 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst, pCMS->m_p2Lines->aPortion.Pos().AdjustY(aCharPos.Y() ); } - const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN); - // Make sure the cursor respects the right margin, unless in compat mode, where the tab size has priority over the margin size. - if( pOrig->Left() > nTmpRight && !bTabOverMargin) - pOrig->Pos().setX( nTmpRight ); - if( nMax ) { if( pOrig->Top() + pOrig->Height() > nMax ) @@ -1248,23 +1298,13 @@ void SwTextCursor::GetCharRect( SwRect* pOrig, TextFrameIndex const nOfst, pCMS->m_aRealHeight.setY( nMax - nTmp ); } } - tools::Long nOut = pOrig->Right() - GetTextFrame()->getFrameArea().Right(); - if( nOut > 0 ) - { - if( GetTextFrame()->getFrameArea().Width() < GetTextFrame()->getFramePrintArea().Left() - + GetTextFrame()->getFramePrintArea().Width() ) - nOut += GetTextFrame()->getFrameArea().Width() - GetTextFrame()->getFramePrintArea().Left() - - GetTextFrame()->getFramePrintArea().Width(); - if( nOut > 0 ) - pOrig->Pos().AdjustX( -(nOut + 10) ); - } } /** * Determines if SwTextCursor::GetModelPositionForViewPoint() should consider the next portion when calculating the * doc model position from a Point. */ -static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, sal_uInt16 nWidth30, sal_uInt16 nX) +static bool ConsiderNextPortionForCursorOffset(const SwLinePortion* pPor, SwTwips nWidth30, sal_uInt16 nX) { if (!pPor->GetNextPortion() || pPor->IsBreakPortion()) { @@ -1313,21 +1353,17 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con if( bLeftOver ) x = nLeftMargin; const bool bRightOver = x > nRightMargin; - if( bRightOver ) - x = nRightMargin; - const bool bRightAllowed = pCMS && ( pCMS->m_eState == CursorMoveState::NONE ); // Until here everything in document coordinates. x -= nLeftMargin; - sal_uInt16 nX = sal_uInt16( x ); + 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 bHolePortion = false; bool bLastHyph = false; std::deque<sal_uInt16> *pKanaComp = m_pCurr->GetpKanaComp(); @@ -1340,13 +1376,13 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con // nWidth is the width of the line, or the width of // the paragraph with the font change, in which nX is situated. - sal_uInt16 nWidth = pPor->Width(); + SwTwips nWidth = pPor->Width(); if ( m_pCurr->IsSpaceAdd() || pKanaComp ) { if ( pPor->InSpaceGrp() && nSpaceAdd ) { const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart ); - nWidth = nWidth + sal_uInt16( pPor->CalcSpacing( nSpaceAdd, GetInfo() ) ); + nWidth += pPor->CalcSpacing( nSpaceAdd, GetInfo() ); } if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) || ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->HasTabulator() ) @@ -1370,7 +1406,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con } } - sal_uInt16 nWidth30; + SwTwips nWidth30; if ( pPor->IsPostItsPortion() ) nWidth30 = 0; else @@ -1380,9 +1416,8 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con while (ConsiderNextPortionForCursorOffset(pPor, nWidth30, nX)) { - nX = nX - nWidth; - nCurrStart = nCurrStart + pPor->GetLen(); - bHolePortion = pPor->IsHolePortion(); + nX -= nWidth; + nCurrStart += pPor->GetLen(); pPor = pPor->GetNextPortion(); nWidth = pPor->Width(); if ( m_pCurr->IsSpaceAdd() || pKanaComp ) @@ -1390,7 +1425,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con if ( pPor->InSpaceGrp() && nSpaceAdd ) { const_cast<SwTextSizeInfo&>(GetInfo()).SetIdx( nCurrStart ); - nWidth = nWidth + sal_uInt16( pPor->CalcSpacing( nSpaceAdd, GetInfo() ) ); + nWidth += pPor->CalcSpacing( nSpaceAdd, GetInfo() ); } if( ( pPor->InFixMargGrp() && ! pPor->IsMarginPortion() ) || @@ -1432,7 +1467,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con SwLinePortion *pNextPor = pPor->GetNextPortion(); while( pNextPor && pNextPor->InFieldGrp() && !pNextPor->Width() ) { - nCurrStart = nCurrStart + pPor->GetLen(); + nCurrStart += pPor->GetLen(); pPor = pNextPor; if( !pPor->IsFlyPortion() && !pPor->IsMarginPortion() ) bLastHyph = pPor->InHyphGrp(); @@ -1483,7 +1518,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con return TextFrameIndex(0); // 7849, 7816: pPor->GetHyphPortion is mandatory! - if( bHolePortion || ( !bRightAllowed && bLastHyph ) || + 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 @@ -1503,7 +1538,11 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con --nCurrStart; } } - return nCurrStart; + if (!pPor->InFieldGrp() || !static_cast<SwFieldPortion const*>(pPor)->IsFollow() + || !pCMS || !pCMS->m_pSpecialPos) + { + return nCurrStart; + } } if (TextFrameIndex(1) == nLength || pPor->InFieldGrp()) { @@ -1582,8 +1621,11 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con // Skip space at the end of the line if( bLastPortion && (m_pCurr->GetNext() || m_pFrame->GetFollow() ) - && rText[sal_Int32(nCurrStart + nLength) - 1] == ' ' ) + && sal_Int32(nLength) != 0 + && rText[sal_Int32(nCurrStart + nLength) - 1] == ' ') + { --nLength; + } if( nWidth > nX || ( nWidth == nX && pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsDouble() ) ) @@ -1625,14 +1667,14 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con nTmpY = pPor->Height() - nTmpY; if( nTmpY < 0 ) nTmpY = 0; - nX = static_cast<sal_uInt16>(nTmpY); + 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; + nX -= nPreWidth; else nX = 0; } @@ -1640,7 +1682,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con return GetModelPositionForViewPoint( pPos, Point( GetLineStart() + nX, rPoint.Y() ), bChgNode, pCMS ); } - if( pPor->InTextGrp() ) + if( pPor->InTextGrp() || pPor->IsHolePortion() ) { sal_uInt8 nOldProp; if( GetPropFont() ) @@ -1653,19 +1695,23 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con { SwTextSizeInfo aSizeInf( GetInfo(), &rText, nCurrStart ); const_cast<SwTextCursor*>(this)->SeekAndChg( aSizeInf ); - SwTextSlot aDiffText( &aSizeInf, static_cast<SwTextPortion*>(pPor), false, false ); + 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(), - pPor->GetLen() ); + nSafeLen ); // Drop portion works like a multi portion, just its parts are not portions if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 ) @@ -1689,12 +1735,12 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con } pCurrPart = pCurrPart->GetFollow(); } - nX = std::max(0, nX - nSumBorderWidth); + 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(0, nX - GetInfo().GetFont()->GetLeftBorderSpace()); + nX = std::max(static_cast<SwTwips>(0), nX - GetInfo().GetFont()->GetLeftBorderSpace()); } aDrawInf.SetOffset( nX ); @@ -1729,6 +1775,43 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con 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); } @@ -1736,11 +1819,6 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con if ( pCMS ) pCMS->m_nCursorBidiLevel = aDrawInf.GetCursorBidiLevel(); - - if( bFieldInfo && nLength == pPor->GetLen() && - ( ! pPor->GetNextPortion() || - ! pPor->GetNextPortion()->IsPostItsPortion() ) ) - --nLength; } if( nOldProp ) const_cast<SwFont*>(GetFnt())->SetProportion( nOldProp ); @@ -1755,8 +1833,10 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con // (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->IsTextFrame() || pLower->IsLayoutFrame() || pLower->IsNoTextFrame()); Point aTmpPoint( rPoint ); if ( m_pFrame->IsRightToLeft() ) @@ -1765,7 +1845,7 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con if ( m_pFrame->IsVertical() ) m_pFrame->SwitchHorizontalToVertical( aTmpPoint ); - if( bChgNodeInner && pTmp->getFrameArea().IsInside( aTmpPoint ) && + if( bChgNodeInner && pTmp->getFrameArea().Contains( aTmpPoint ) && !( pTmp->IsProtected() ) ) { pFlyPor->GetFlyCursorOfst(aTmpPoint, *pPos, pCMS); @@ -1817,7 +1897,7 @@ bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect bool bRet = false; // GetPaintArea() instead getFrameArea() for negative indents SwRect aTmpFrame( GetPaintArea() ); - if( !rRect.IsOver( aTmpFrame ) ) + if( !rRect.Overlaps( aTmpFrame ) ) return false; if( rSelList.checkContext( this ) ) { @@ -1831,7 +1911,7 @@ bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect } else if( aRect.HasArea() ) { - SwPosition aOld(aPosL.nNode.GetNodes().GetEndOfContent()); + SwPosition aOld(aPosL.GetNodes().GetEndOfContent()); SwPosition aPosR( aPosL ); Point aPoint; SwTextInfo aInf( const_cast<SwTextFrame*>(this) ); @@ -1924,7 +2004,7 @@ bool SwTextFrame::FillSelection( SwSelectionList& rSelList, const SwRect& rRect const SwSortedObjs &rObjs = *GetDrawObjs(); for (SwAnchoredObject* pAnchoredObj : rObjs) { - const SwFlyFrame* pFly = dynamic_cast<const SwFlyFrame*>(pAnchoredObj); + const SwFlyFrame* pFly = pAnchoredObj->DynCastFlyFrame(); if( !pFly ) continue; if( pFly->IsFlyInContentFrame() && pFly->FillSelection( rSelList, rRect ) ) diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index d35159f12d84..046aa6bde1ea 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -45,6 +45,8 @@ #include "porhyph.hxx" #include "pordrop.hxx" #include "redlnitr.hxx" +#include <sortedobjs.hxx> +#include <fmtanchr.hxx> #include <pagefrm.hxx> #include <tgrditem.hxx> #include <doc.hxx> @@ -54,8 +56,18 @@ #include <IDocumentSettingAccess.hxx> #include <IMark.hxx> #include <IDocumentMarkAccess.hxx> - -#include <vector> +#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; @@ -143,6 +155,16 @@ sal_uInt16 SwTextFormatter::GetFrameRstHeight() const 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 @@ -235,7 +257,7 @@ SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) // line width is adjusted, so that pPor does not fit to current // line anymore - rInf.Width( static_cast<sal_uInt16>(rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0)) ); + rInf.Width( rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0) ); rInf.SetLen( pPor->GetLen() ); rInf.SetFull( false ); if( pFly ) @@ -271,11 +293,8 @@ SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf ) SwLinePortion* pNext = pPor->GetNextPortion(); while (pNext) { - if (pNext == m_pFirstOfBorderMerge) - { - m_pFirstOfBorderMerge = nullptr; + if (ClearIfIsFirstOfBorderMerge(pNext)) break; - } pNext = pNext->GetNextPortion(); } pPor->Truncate(); @@ -327,6 +346,8 @@ void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf, 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)) { @@ -385,7 +406,38 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) rInf.SetFull(true); } - SwLinePortion *pPor = NewPortion( rInf ); + ::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())); @@ -424,7 +476,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) { SwFontScript nNxtActual = rInf.GetFont()->GetActual(); SwFontScript nLstActual = nNxtActual; - sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()); + sal_uInt16 nLstHeight = o3tl::narrowing<sal_uInt16>(rInf.GetFont()->GetHeight()); bool bAllowBehind = false; const CharClass& rCC = GetAppCharClass(); @@ -467,7 +519,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) if ( pTmpFnt ) { nLstActual = pTmpFnt->GetActual(); - nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight()); + nLstHeight = o3tl::narrowing<sal_uInt16>(pTmpFnt->GetHeight()); } } } @@ -482,18 +534,29 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) 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 && pGrid->IsSnapToChars() && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + else if ( bHasGrid && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) { // insert a grid kerning portion pGridKernPortion = pPor->IsKernPortion() ? @@ -528,7 +591,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) const SwTwips nRestWidth = rInf.Width() - rInf.X(); if ( nKernWidth <= nRestWidth ) - pGridKernPortion->Width( static_cast<sal_uInt16>(nKernWidth) ); + pGridKernPortion->Width( nKernWidth ); } if ( pGridKernPortion != pPor ) @@ -615,7 +678,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN || m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) ) { - const sal_uInt16 nDist = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()/5); + const SwTwips nDist = rInf.GetFont()->GetHeight()/5; if( nDist ) { @@ -636,7 +699,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) } } - if ( bHasGrid && pGrid->IsSnapToChars() && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) + if ( bHasGrid && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() ) { TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen(); const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width(); @@ -670,15 +733,18 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) 0; const SwTwips nTmpWidth = i * nGridWidth; const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth); - const sal_uInt16 nKernWidth_1 = static_cast<sal_uInt16>(nKernWidth / 2); + 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 ); + } - pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 ); - rInf.X( rInf.X() + nKernWidth_1 ); - - if ( ! bFull ) + if ( ! bFull && nKernWidth - nKernWidth_1 > 0 ) new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1), false, true ); @@ -704,7 +770,7 @@ void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf ) { (void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion! } - pPor = NewPortion( rInf ); + pPor = NewPortion(rInf, oMovedFlyIndex); } if( !rInf.IsStop() ) @@ -786,6 +852,7 @@ void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ) // 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 @@ -794,10 +861,16 @@ void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ) { if( !rInf.GetText().isEmpty() ) { - if ( pPor->GetLen() || !rInf.GetIdx() - || ( m_pCurr != pLast && !pLast->IsFlyPortion() ) - || !m_pCurr->IsRest() ) // instead of !rInf.GetRest() + if ((rInf.GetIdx() != TextFrameIndex(rInf.GetText().getLength()) + || rInf.GetRest() // field continued - not empty + || !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH)) + && (pPor->GetLen() || !rInf.GetIdx() + || (m_pCurr != pLast && !pLast->IsFlyPortion()) + || !m_pCurr->IsRest())) // instead of !rInf.GetRest() + { bChg = SeekAndChg( rInf ); + } else bChg = SeekAndChgBefore( rInf ); } @@ -817,6 +890,7 @@ void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor ) if( bChg || bFirstPor || !pPor->GetAscent() || !rInf.GetLast()->InTextGrp() ) { + pPor->SetHangingBaseline( rInf.GetHangingBaseline() ); pPor->SetAscent( rInf.GetAscent() ); pPor->Height( rInf.GetTextHeight() ); bCalc = true; @@ -842,32 +916,248 @@ 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 ); + 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>(); + auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get()); + pEditWidget->MultiLine = true; + break; + } + case SwContentControlType::CHECKBOX: + { + pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>(); + auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get()); + pCheckBoxWidget->Checked = pContentControl->GetChecked(); + // If it's checked already, then leave the default "Yes" OnValue unchanged, so the + // appropriate appearance is found by PDF readers. + if (!pCheckBoxWidget->Checked) + { + 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(OUString(ODF_FORMDROPDOWN_RESULT)); + 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(OUString(ODF_FORMDROPDOWN_LISTENTRY)); + const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY); if (pListEntries != pParameters->end()) { uno::Sequence< OUString > vListEntries; @@ -892,7 +1182,6 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const { 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); @@ -906,7 +1195,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const } } assert(2 <= sal_Int32(nFieldLen)); - pPor = new SwFieldPortion(aFieldName, nullptr, false, nFieldLen); + pPor = new SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input), nullptr, nFieldLen); } else { @@ -919,7 +1208,66 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const pPor = new SwRefPortion; else if (GetFnt()->IsMeta()) { - pPor = new SwMetaPortion; + 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 { @@ -927,10 +1275,10 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const // If pCurr does not have a width, it can however already have content. // E.g. for non-displayable characters - auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]); + 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()->getFieldmarkFor(aPosition); + sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition); if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE) { if (ch == CH_TXT_ATR_FIELDSTART) @@ -970,7 +1318,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const } if( !pPor ) { - if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() ) + if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() && !GetFnt()->IsURL() ) pPor = m_pCurr; else pPor = new SwTextPortion; @@ -993,25 +1341,20 @@ SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf ) Seek( rInf.GetIdx() ); SwTextPortion *pPor = WhichTextPor( rInf ); + TextFrameIndex nNextChg(rInf.GetText().getLength()); + // 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); + + nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, nNextHidden, nNextBookmark }); // Turbo boost: // We assume that font characters are not larger than twice @@ -1030,13 +1373,12 @@ SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf ) 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())); + auto nCharWidthGuess = std::min(pTmpFnt->GetHeight(), pPor->GetAscent()) / 8; + if (!nCharWidthGuess) + nCharWidthGuess = 1; + auto nExpect = rInf.GetIdx() + TextFrameIndex(rInf.GetLineWidth() / nCharWidthGuess); + if (nExpect > rInf.GetIdx() && nNextChg > nExpect) + nNextChg = nExpect; // we keep an invariant during method calls: // there are no portion ending characters like hard spaces @@ -1196,32 +1538,13 @@ SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf) // check this *last* so that BuildMultiPortion() can find it! if (!pPor && rInf.CheckCurrentPosBookmark()) { - auto const bookmark(m_pScriptInfo->GetBookmark(rInf.GetIdx())); - if (static_cast<bool>(bookmark)) + const auto& bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx()); + if (!bookmark.empty()) { - sal_Unicode mark; - if ((bookmark & (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End)) - == (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End)) - { - //mark = u'\u2336'; // not in OpenSymbol :( - mark = '|'; - // hmm ... paint U+2345 over U+2346 should be same width? - // and U+237F // or U+2E20/U+2E21 - } - else if (bookmark & SwScriptInfo::MarkKind::Start) - { - mark = '['; - } - else if (bookmark & SwScriptInfo::MarkKind::End) - { - mark = ']'; - } - else - { - assert(bookmark & SwScriptInfo::MarkKind::Point); - mark = '|'; - } - pPor = new SwBookmarkPortion(mark); + // only for character width, maybe replaced with ] later + sal_Unicode mark = '['; + + pPor = new SwBookmarkPortion(mark, bookmark); } } @@ -1256,8 +1579,16 @@ static bool lcl_OldFieldRest( const SwLineLayout* pCurr ) * -> CalcFlyWidth emulates the width and return portion, if needed */ -SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) +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() ) @@ -1335,7 +1666,7 @@ SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) // We open a multiportion part, if we enter a multi-line part // of the paragraph. TextFrameIndex nEnd = rInf.GetIdx(); - std::unique_ptr<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti ); + std::optional<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti ); if( pCreate ) { SwMultiPortion* pTmp = nullptr; @@ -1394,7 +1725,11 @@ SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) pPor = NewTabPortion( rInf, false ); break; case CH_BREAK: - pPor = new SwBreakPortion( *rInf.GetLast() ); break; + { + SwTextAttr* pHint = GetAttr(rInf.GetIdx()); + pPor = new SwBreakPortion(*rInf.GetLast(), pHint); + break; + } case CHAR_SOFTHYPHEN: // soft hyphen pPor = new SwSoftHyphPortion; break; @@ -1431,7 +1766,7 @@ SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) PortionType::TabDecimal == pLastTabPortion->GetWhichPor() ) { OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" ); - const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = static_cast<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() ); + const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = o3tl::narrowing<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() ); static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition ); rInf.SetTabDecimal( 0 ); } @@ -1494,11 +1829,9 @@ SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) pInfo = &pDoc->GetFootnoteInfo(); const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet(); - const SfxPoolItem* pItem; Degree10 nDir(0); - if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE, - true, &pItem )) - nDir = static_cast<const SvxCharRotateItem*>(pItem)->GetValue(); + if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) ) + nDir = pItem->GetValue(); if ( nDir ) { @@ -1574,11 +1907,12 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) // Recycling must be suppressed by changed line height and also // by changed ascent (lowering of baseline). - const sal_uInt16 nOldHeight = m_pCurr->Height(); - const sal_uInt16 nOldAscent = m_pCurr->GetAscent(); + const SwTwips nOldHeight = m_pCurr->Height(); + const SwTwips nOldAscent = m_pCurr->GetAscent(); m_pCurr->SetEndHyph( false ); m_pCurr->SetMidHyph( false ); + m_pCurr->SetLastHyph( false ); // fly positioning can make it necessary format a line several times // for this, we have to keep a copy of our rest portion @@ -1618,6 +1952,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) // 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(); @@ -1625,6 +1960,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) FormatReset( GetInfo() ); GetInfo().SetNumDone( bOldNumDone ); + GetInfo().SetFootnoteDone(bOldFootnoteDone); GetInfo().SetArrowDone( bOldArrowDone ); GetInfo().SetErgoDone( bOldErgoDone ); @@ -1636,6 +1972,14 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) 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; @@ -1655,7 +1999,7 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() && m_pCurr->Height() > pPorTmp->Height()))) { - sal_uInt16 nTmpAscent, nTmpHeight; + SwTwips nTmpAscent, nTmpHeight; CalcAscentAndHeight( nTmpAscent, nTmpHeight ); AlignFlyInCntBase( Y() + tools::Long( nTmpAscent ) ); m_pCurr->CalcLine( *this, GetInfo() ); @@ -1674,7 +2018,15 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) || GetInfo().CheckFootnotePortion(m_pCurr); if( bBuild ) { - GetInfo().SetNumDone( bOldNumDone ); + // 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 @@ -1772,7 +2124,7 @@ void SwTextFormatter::RecalcRealHeight() void SwTextFormatter::CalcRealHeight( bool bNewLine ) { - sal_uInt16 nLineHeight = m_pCurr->Height(); + SwTwips nLineHeight = m_pCurr->Height(); m_pCurr->SetClipping( false ); SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); @@ -1806,7 +2158,7 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) nTmp = 100; nTmp *= nLineHeight; - nLineHeight = static_cast<sal_uInt16>(nTmp / 100); + nLineHeight = nTmp / 100; } m_pCurr->SetRealHeight( nLineHeight ); @@ -1841,7 +2193,7 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) nTmp /= 100; if( !nTmp ) ++nTmp; - nLineHeight = static_cast<sal_uInt16>(nTmp); + nLineHeight = nTmp; sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80% #if 0 // could do clipping here (like Word does) @@ -1899,7 +2251,7 @@ void SwTextFormatter::CalcRealHeight( bool bNewLine ) nTmp += nLineHeight; if (nTmp < 1) nTmp = 1; - nLineHeight = static_cast<sal_uInt16>(nTmp); + nLineHeight = nTmp; break; } case SvxInterLineSpaceRule::Fix: @@ -2099,8 +2451,8 @@ void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart, SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc; pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc ); - const sal_uInt16 nTmpHeight = pCurrent->GetRealHeight(); - sal_uInt16 nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height(); + const SwTwips nTmpHeight = pCurrent->GetRealHeight(); + SwTwips nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height(); AsCharFlags nFlags = AsCharFlags::UlSpace; if( GetMulti() ) { @@ -2285,7 +2637,7 @@ bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const // New flys from below? if( !pPos->IsFlyPortion() ) { - if( aInter.IsOver( aLine ) ) + if( aInter.Overlaps( aLine ) ) { aInter.Intersection_( aLine ); if( aInter.HasArea() ) @@ -2302,7 +2654,7 @@ bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const else { // The fly portion is not intersected by a fly anymore - if ( ! aInter.IsOver( aLine ) ) + if ( ! aInter.Overlaps( aLine ) ) { rInf.SetLineHeight( nHeight ); rInf.SetLineNetHeight( m_pCurr->Height() ); @@ -2378,17 +2730,34 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) 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 (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS) - && IsFirstTextLine()) + if (bWordFlyWrap && IsFirstTextLine()) { - const tools::Long nUpper = m_pFrame->getFramePrintArea().Top(); + 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 ); @@ -2413,23 +2782,52 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) aInter.Height(0); } - if( !aInter.IsOver( aLine ) ) + 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(); - if( GetTextFrame()->getFramePrintArea().Left() < 0 ) + SwTwips nFramePrintAreaLeft = GetTextFrame()->getFramePrintArea().Left(); + if( nFramePrintAreaLeft < 0 ) nFrameLeft += GetTextFrame()->getFramePrintArea().Left(); if( aInter.Left() < nFrameLeft ) - 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 ( m_pFrame->IsRightToLeft() ) + if (GetTextFrame()->IsRightToLeft()) { - nAddMar = m_pFrame->getFrameArea().Right() - Right(); + nAddMar = GetTextFrame()->getFrameArea().Right() - Right(); if ( nAddMar < 0 ) nAddMar = 0; } @@ -2447,8 +2845,22 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) if( !aInter.HasArea() ) return; - const bool bFullLine = aLine.Left() == aInter.Left() && + 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. @@ -2465,7 +2877,7 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) if( bForced ) { m_pCurr->SetForcedLeftMargin(); - rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) ); + rInf.ForcedLeftMargin( o3tl::narrowing<sal_uInt16>(aInter.Width()) ); } if( bFullLine ) @@ -2481,7 +2893,7 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) // created: here and in MakeFlyDummies. // IsDummy() is evaluated in IsFirstTextLine(), when moving lines // and in relation with DropCaps. - pFly->Height( sal_uInt16(aInter.Height()) ); + 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. @@ -2494,10 +2906,10 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) { SwTwips nH = nNextTop - aInter.Top(); if( nH < SAL_MAX_UINT16 ) - pFly->Height( sal_uInt16( nH ) ); + pFly->Height( nH ); } if( nAscent < pFly->Height() ) - pFly->SetAscent( sal_uInt16(nAscent) ); + pFly->SetAscent( nAscent ); else pFly->SetAscent( pFly->Height() ); } @@ -2511,9 +2923,9 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) } else { - pFly->Height( sal_uInt16(aInter.Height()) ); + pFly->Height( aInter.Height() ); if( nAscent < pFly->Height() ) - pFly->SetAscent( sal_uInt16(nAscent) ); + pFly->SetAscent( nAscent ); else pFly->SetAscent( pFly->Height() ); } @@ -2551,11 +2963,11 @@ void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf ) const SwTwips nOfst = nStartX - nGridOrigin; const SwTwips nTmpWidth = rInf.Width() + nOfst; - const sal_uLong i = nTmpWidth / nGridWidth + 1; + const SwTwips i = nTmpWidth / nGridWidth + 1; - const tools::Long nNewWidth = ( i - 1 ) * nGridWidth - nOfst; + const SwTwips nNewWidth = ( i - 1 ) * nGridWidth - nOfst; if ( nNewWidth > 0 ) - rInf.Width( static_cast<sal_uInt16>(nNewWidth) ); + rInf.Width( nNewWidth ); else rInf.Width( 0 ); @@ -2570,7 +2982,11 @@ SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, 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 @@ -2588,7 +3004,7 @@ SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, // 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. - sal_uInt16 nAscent = 0; + SwTwips nAscent = 0; const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical(); @@ -2598,9 +3014,9 @@ SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf, pFly->GetRefPoint().Y() ); if ( bUseFlyAscent ) - nAscent = static_cast<sal_uInt16>( std::abs( int( bTextFrameVertical ? + nAscent = std::abs( int( bTextFrameVertical ? pFly->GetRelPos().X() : - pFly->GetRelPos().Y() ) ) ); + pFly->GetRelPos().Y() ) ); // Check if be prefer to use the ascent of the last portion: if ( IsQuick() || diff --git a/sw/source/core/text/itrform2.hxx b/sw/source/core/text/itrform2.hxx index bd986e4be324..2d71fad34cd5 100644 --- a/sw/source/core/text/itrform2.hxx +++ b/sw/source/core/text/itrform2.hxx @@ -45,7 +45,7 @@ class SwTextFormatter : public SwTextPainter 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 ); + SwLinePortion *NewPortion(SwTextFormatInfo &rInf, ::std::optional<TextFrameIndex>); SwTextPortion *NewTextPortion( SwTextFormatInfo &rInf ); SwLinePortion *NewExtraPortion( SwTextFormatInfo &rInf ); SwTabPortion *NewTabPortion( SwTextFormatInfo &rInf, bool bAuto ) const; @@ -238,6 +238,8 @@ public: * @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 index 75493bad82be..f02beed8ce5b 100644 --- a/sw/source/core/text/itrpaint.cxx +++ b/sw/source/core/text/itrpaint.cxx @@ -32,6 +32,8 @@ #include <txtfrm.hxx> #include <swfont.hxx> #include "txtpaint.hxx" +#include "porfld.hxx" +#include "porfly.hxx" #include "portab.hxx" #include <txatbase.hxx> #include <charfmt.hxx> @@ -39,6 +41,7 @@ #include "porrst.hxx" #include "pormulti.hxx" #include <doc.hxx> +#include <fmturl.hxx> // Returns, if we have an underline breaking situation // Adding some more conditions here means you also have to change them @@ -69,7 +72,7 @@ void SwTextPainter::CtorInitTextPainter( SwTextFrame *pNewFrame, SwTextPaintInfo m_bPaintDrop = false; } -SwLinePortion *SwTextPainter::CalcPaintOfst( const SwRect &rPaint ) +SwLinePortion *SwTextPainter::CalcPaintOfst(const SwRect &rPaint, bool& rbSkippedNumPortions) { SwLinePortion *pPor = m_pCurr->GetFirstPortion(); GetInfo().SetPaintOfst( 0 ); @@ -97,6 +100,11 @@ SwLinePortion *SwTextPainter::CalcPaintOfst( const SwRect &rPaint ) } 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(); } @@ -118,7 +126,10 @@ SwLinePortion *SwTextPainter::CalcPaintOfst( const SwRect &rPaint ) // (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 ) + 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() ); @@ -127,6 +138,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, // maybe catch-up adjustment GetAdjusted(); + AddExtraBlankWidth(); GetInfo().SetpSpaceAdd( m_pCurr->GetpLLSpaceAdd() ); GetInfo().ResetSpaceIdx(); GetInfo().SetKanaComp( m_pCurr->GetpKanaComp() ); @@ -140,13 +152,28 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, // 6882: blank lines can't be optimized by removing them if Formatting Marks are shown const bool bEndPor = GetInfo().GetOpt().IsParagraph() && GetInfo().GetText().isEmpty(); - SwLinePortion *pPor = bEndPor ? m_pCurr->GetFirstPortion() : CalcPaintOfst( rPaint ); + 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 setting: allow tabstop text to exceed right margin - if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) + //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 ) @@ -178,7 +205,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, && GetDropLines() >= GetLineNr(); } - sal_uInt16 nTmpHeight, nTmpAscent; + SwTwips nTmpHeight, nTmpAscent; CalcAscentAndHeight( nTmpAscent, nTmpHeight ); // bClip decides if there's a need to clip @@ -243,7 +270,7 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, SwTextGridItem const*const pGrid(GetGridItem(GetTextFrame()->FindPageFrame())); const bool bAdjustBaseLine = GetLineInfo().HasSpecialAlign( GetTextFrame()->IsVertical() ) || - ( nullptr != pGrid ); + ( nullptr != pGrid ) || m_pCurr->GetHangingBaseline(); const SwTwips nLineBaseLine = GetInfo().GetPos().Y() + nTmpAscent; if ( ! bAdjustBaseLine ) GetInfo().Y( nLineBaseLine ); @@ -385,9 +412,21 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, 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 ); + Por_Info aPorInfo(*pPor, *this, 0); SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, &aPorInfo, *pOut ); if( pPor->IsMultiPortion() ) @@ -396,6 +435,25 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, 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 ); @@ -403,6 +461,24 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, // reset (for special vertical alignment) GetInfo().Y( nOldY ); + if (GetFnt()->IsURL() && pPor->InTextGrp()) + GetInfo().NotifyURL(*pPor); + else if (pPor->IsFlyCntPortion()) + { + if (auto* pFlyContentPortion = dynamic_cast<sw::FlyContentPortion*>(pPor)) + { + if (auto* pFlyFrame = pFlyContentPortion->GetFlyFrame()) + { + if (auto* pFormat = pFlyFrame->GetFormat()) + { + auto& url = pFormat->GetURL(); + if (!url.GetURL().isEmpty()) // TODO: url.GetMap() ? + GetInfo().NotifyURL(*pPor); + } + } + } + } + bFirst &= !pPor->GetLen(); if( pNext || !pPor->IsMarginPortion() ) pPor->Move( GetInfo() ); @@ -416,6 +492,44 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, 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 @@ -543,12 +657,11 @@ void SwTextPainter::CheckSpecialUnderline( const SwLinePortion* pPor, sal_Int32 nTmp(0); for (auto const& e : pMerged->extents) { - const SfxPoolItem* pItem; - if (SfxItemState::SET == e.pNode->GetSwAttrSet().GetItemState( - RES_CHRATR_UNDERLINE, true, &pItem)) + if (const SvxUnderlineItem* pItem = e.pNode->GetSwAttrSet().GetItemIfSet( + RES_CHRATR_UNDERLINE)) { const bool bUnderSelect(m_pFont->GetUnderline() == - static_cast<SvxUnderlineItem const*>(pItem)->GetLineStyle()); + pItem->GetLineStyle()); aUnderMulti.Select(Range(nTmp, nTmp + e.nEnd - e.nStart - 1), bUnderSelect); } diff --git a/sw/source/core/text/itrpaint.hxx b/sw/source/core/text/itrpaint.hxx index c016cff4e735..893db371dbb4 100644 --- a/sw/source/core/text/itrpaint.hxx +++ b/sw/source/core/text/itrpaint.hxx @@ -21,14 +21,17 @@ #include "itrtxt.hxx" +#include <optional> + class SwSaveClip; // SwTextPainter class SwMultiPortion; +class SwTaggedPDFHelper; class SwTextPainter : public SwTextCursor { bool m_bPaintDrop; - SwLinePortion *CalcPaintOfst( const SwRect &rPaint ); + SwLinePortion *CalcPaintOfst(const SwRect &rPaint, bool& rbSkippedNumPortions); void CheckSpecialUnderline( const SwLinePortion* pPor, tools::Long nAdjustBaseLine = 0 ); protected: @@ -46,7 +49,10 @@ public: CtorInitTextPainter( pTextFrame, pTextPaintInf ); } void DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, - const bool bUnderSz ); + 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 diff --git a/sw/source/core/text/itrtxt.cxx b/sw/source/core/text/itrtxt.cxx index 220977a24d6d..1d1eed3e0837 100644 --- a/sw/source/core/text/itrtxt.cxx +++ b/sw/source/core/text/itrtxt.cxx @@ -27,6 +27,7 @@ #include <pagefrm.hxx> #include <tgrditem.hxx> #include "porfld.hxx" +#include "porrst.hxx" #include "itrtxt.hxx" #include <txtfrm.hxx> @@ -60,7 +61,7 @@ void SwTextIter::Init() m_nLineNr = 1; } -void SwTextIter::CalcAscentAndHeight( sal_uInt16 &rAscent, sal_uInt16 &rHeight ) const +void SwTextIter::CalcAscentAndHeight( SwTwips &rAscent, SwTwips &rHeight ) const { rHeight = GetLineHeight(); rAscent = m_pCurr->GetAscent() + rHeight - m_pCurr->Height(); @@ -211,9 +212,9 @@ const SwLineLayout *SwTextCursor::CharCursorToLine(TextFrameIndex const nPositio return bPrevious ? PrevLine() : m_pCurr; } -sal_uInt16 SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine, +SwTwips SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine, const SwLinePortion* pPor, - sal_uInt16 nPorHeight, sal_uInt16 nPorAscent, + SwTwips nPorHeight, SwTwips nPorAscent, const bool bAutoToCentered ) const { if ( pPor ) @@ -222,7 +223,7 @@ sal_uInt16 SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine, nPorAscent = pPor->GetAscent(); } - sal_uInt16 nOfst = rLine.GetRealHeight() - rLine.Height(); + SwTwips nOfst = rLine.GetRealHeight() - rLine.Height(); SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame())); @@ -283,13 +284,54 @@ sal_uInt16 SwTextCursor::AdjustBaseLine( const SwLineLayout& rLine, if (GetInfo().GetTextFrame()->IsVertLR() && !GetInfo().GetTextFrame()->IsVertLRBT()) nOfst += rLine.Height() - ( rLine.Height() - nPorHeight ) / 2 - nPorAscent; else - nOfst += ( rLine.Height() - nPorHeight ) / 2 + nPorAscent; + { + 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 - nOfst = nOfst + rLine.GetAscent(); + 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; } } diff --git a/sw/source/core/text/itrtxt.hxx b/sw/source/core/text/itrtxt.hxx index f155ee6cc484..b5ea78369d5f 100644 --- a/sw/source/core/text/itrtxt.hxx +++ b/sw/source/core/text/itrtxt.hxx @@ -16,8 +16,8 @@ * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ -#ifndef INCLUDED_SW_SOURCE_CORE_TEXT_ITRTXT_HXX -#define INCLUDED_SW_SOURCE_CORE_TEXT_ITRTXT_HXX +#pragma once + #include <swtypes.hxx> #include "itratr.hxx" #include "inftxt.hxx" @@ -40,7 +40,7 @@ protected: 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_uInt16 m_nLineNr; // Line number + 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 @@ -84,7 +84,7 @@ public: const SwLineLayout *GetNext() const { return m_pCurr->GetNext(); } const SwLineLayout *GetPrev(); TextFrameIndex GetLength() const { return m_pCurr->GetLen(); } - sal_uInt16 GetLineNr() const { return m_nLineNr; } + 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; } @@ -113,8 +113,8 @@ public: // Truncates all after pCurr void TruncLines( bool bNoteFollow = false ); - sal_uInt16 GetLineHeight() const { return m_pCurr->GetRealHeight(); } - void CalcAscentAndHeight( sal_uInt16 &rAscent, sal_uInt16 &rHeight ) const; + SwTwips GetLineHeight() const { return m_pCurr->GetRealHeight(); } + void CalcAscentAndHeight( SwTwips &rAscent, SwTwips &rHeight ) const; // Lots of trouble for querying pCurr == pPara bool IsFirstTextLine() const @@ -156,7 +156,7 @@ protected: // For FormatQuoVadis void Right( const SwTwips nNew ) { mnRight = nNew; } - void CtorInitTextMargin( SwTextFrame *pFrame, SwTextSizeInfo *pInf ); + SW_DLLPUBLIC void CtorInitTextMargin( SwTextFrame *pFrame, SwTextSizeInfo *pInf ); explicit SwTextMargin(SwTextNode const * pTextNode) : SwTextIter(pTextNode) , mnLeft(0) @@ -270,6 +270,7 @@ class SwTextCursor : public SwTextAdjuster 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()) @@ -287,8 +288,8 @@ public: // calculates baseline for portion rPor // bAutoToCentered indicates, if AUTOMATIC mode means CENTERED or BASELINE - sal_uInt16 AdjustBaseLine( const SwLineLayout& rLine, const SwLinePortion* pPor, - sal_uInt16 nPorHeight = 0, sal_uInt16 nAscent = 0, + 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; } @@ -335,6 +336,6 @@ inline SwTwips SwTextMargin::Left() const return (mnDropLines >= m_nLineNr && 1 != m_nLineNr) ? mnFirst + mnDropLeft : mnLeft; } -#endif + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/noteurl.cxx b/sw/source/core/text/noteurl.cxx index fa91ea252d5f..d66736280bb5 100644 --- a/sw/source/core/text/noteurl.cxx +++ b/sw/source/core/text/noteurl.cxx @@ -19,7 +19,42 @@ #include <noteurl.hxx> +#include <vcl/imap.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/outdev.hxx> + // Global variable -SwNoteURL* pNoteURL = nullptr; +thread_local SwNoteURL* pNoteURL = nullptr; + +void SwNoteURL::InsertURLNote(const OUString& rURL, const OUString& rTarget, const SwRect& rRect) +{ + const size_t nCount = m_List.size(); + for (size_t i = 0; i < nCount; ++i) + if (rRect == m_List[i].GetRect()) + return; + + m_List.emplace_back(rURL, rTarget, rRect); +} + +void SwNoteURL::FillImageMap(ImageMap* pMap, const Point& rPos, const MapMode& rMap) +{ + assert(pMap && "FillImageMap: No ImageMap, no cookies!"); + const size_t nCount = m_List.size(); + if (nCount) + { + MapMode aMap(MapUnit::Map100thMM); + for (size_t i = 0; i < nCount; ++i) + { + const SwURLNote& rNote = m_List[i]; + SwRect aSwRect(rNote.GetRect()); + aSwRect -= rPos; + tools::Rectangle aRect(OutputDevice::LogicToLogic(aSwRect.SVRect(), rMap, aMap)); + IMapRectangleObject aObj(aRect, rNote.GetURL(), OUString(), OUString(), + rNote.GetTarget(), OUString(), true, false); + pMap->InsertIMapObject(aObj); + } + } +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/pordrop.hxx b/sw/source/core/text/pordrop.hxx index d89b0cfd1c9d..50dd00e5668e 100644 --- a/sw/source/core/text/pordrop.hxx +++ b/sw/source/core/text/pordrop.hxx @@ -60,6 +60,7 @@ public: void SetJoinBorderWithNext( const bool bJoinNext ) { m_bJoinBorderWithNext = bJoinNext; } }; +/// Text portion for the Format -> Paragraph -> Drop Caps functionality. class SwDropPortion : public SwTextPortion { friend class SwDropCapCache; diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx index 29f470c1490c..0884db6fce76 100644 --- a/sw/source/core/text/porexp.cxx +++ b/sw/source/core/text/porexp.cxx @@ -18,6 +18,7 @@ */ #include <viewopt.hxx> +#include <IDocumentSettingAccess.hxx> #include <SwPortionHandler.hxx> #include "inftxt.hxx" #include "porexp.hxx" @@ -38,6 +39,16 @@ 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 ); @@ -63,12 +74,15 @@ bool SwExpandPortion::Format( SwTextFormatInfo &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 ); @@ -194,14 +208,52 @@ bool SwBlankPortion::Format( SwTextFormatInfo &rInf ) void SwBlankPortion::Paint( const SwTextPaintInfo &rInf ) const { - if( !m_bMulti ) // No gray background for multiportion brackets - rInf.DrawViewOpt( *this, PortionType::Blank ); - SwExpandPortion::Paint( rInf ); + // 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&, OUString &rText ) const +bool SwBlankPortion::GetExpText( const SwTextSizeInfo& rInf, OUString &rText ) const { - rText = OUString(m_cChar); + 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; } @@ -210,6 +262,21 @@ 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 ) { diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx index 9c7be5be5aaf..82de5b1be4fc 100644 --- a/sw/source/core/text/porexp.hxx +++ b/sw/source/core/text/porexp.hxx @@ -33,8 +33,12 @@ public: // 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; @@ -54,6 +58,9 @@ public: // 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 diff --git a/sw/source/core/text/porfld.cxx b/sw/source/core/text/porfld.cxx index fdb2e4442916..d2ae4be00d1b 100644 --- a/sw/source/core/text/porfld.cxx +++ b/sw/source/core/text/porfld.cxx @@ -21,10 +21,15 @@ #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" @@ -42,7 +47,8 @@ #include <accessibilityoptions.hxx> #include <editeng/lrspitem.hxx> #include <unicode/ubidi.h> -#include <bookmrk.hxx> +#include <bookmark.hxx> +#include <docufld.hxx> using namespace ::com::sun::star; @@ -58,7 +64,7 @@ SwFieldPortion *SwFieldPortion::Clone( const OUString &rExpand ) const } // #i107143# // pass placeholder property to created <SwFieldPortion> instance. - SwFieldPortion* pClone = new SwFieldPortion( rExpand, std::move(pNewFnt), m_bPlaceHolder ); + SwFieldPortion* pClone = new SwFieldPortion(rExpand, std::move(pNewFnt)); pClone->SetNextOffset( m_nNextOffset ); pClone->m_bNoLength = m_bNoLength; return pClone; @@ -68,19 +74,18 @@ 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), ""); + m_aExpand = m_aExpand.replaceAt(0, sal_Int32(m_nNextOffset), u""); m_bFollow = true; } -SwFieldPortion::SwFieldPortion(const OUString &rExpand, std::unique_ptr<SwFont> pFont, bool bPlaceHold, TextFrameIndex const nFieldLen) - : m_aExpand(rExpand), m_pFont(std::move(pFont)), m_nNextOffset(0) +SwFieldPortion::SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFont, TextFrameIndex const nFieldLen) + : m_aExpand(std::move(aExpand)), m_pFont(std::move(pFont)), m_nNextOffset(0) , m_nNextScriptChg(COMPLETE_STRING), m_nFieldLen(nFieldLen), m_nViewWidth(0) , m_bFollow( false ), m_bLeft( false), m_bHide( false) , m_bCenter (false), m_bHasFollow( false ) , m_bAnimated( false), m_bNoPaint( false) - , m_bReplace( false), m_bPlaceHolder( bPlaceHold ) + , m_bReplace(false) , m_bNoLength( false ) - , m_nAttrFieldType(0) { SetWhichPor( PortionType::Field ); } @@ -100,9 +105,7 @@ SwFieldPortion::SwFieldPortion( const SwFieldPortion& rField ) , m_bAnimated ( rField.m_bAnimated ) , m_bNoPaint( rField.m_bNoPaint) , m_bReplace( rField.m_bReplace ) - , m_bPlaceHolder( rField.m_bPlaceHolder ) , m_bNoLength( rField.m_bNoLength ) - , m_nAttrFieldType( rField.m_nAttrFieldType) { if ( rField.HasFont() ) m_pFont.reset( new SwFont( *rField.GetFont() ) ); @@ -120,7 +123,7 @@ 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() && SwViewOption::IsFieldShadings() ) + !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() ) { if( !m_nViewWidth ) pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width(); @@ -137,11 +140,12 @@ namespace { */ class SwFieldSlot { - std::shared_ptr<vcl::TextLayoutCache> m_pOldCachedVclData; + 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: @@ -155,6 +159,7 @@ SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pP : pOldText(nullptr) , nIdx(0) , nLen(0) + , nOrigHookChar(0) , pInf(nullptr) { bOn = pPor->GetExpText( *pNew, aText ); @@ -167,6 +172,7 @@ SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pP nIdx = pInf->GetIdx(); nLen = pInf->GetLen(); pOldText = &(pInf->GetText()); + nOrigHookChar = pInf->GetHookChar(); m_pOldCachedVclData = pInf->GetCachedVclData(); pInf->SetLen(TextFrameIndex(aText.getLength())); pInf->SetCachedVclData(nullptr); @@ -175,10 +181,18 @@ SwFieldSlot::SwFieldSlot( const SwTextFormatInfo* pNew, const SwFieldPortion *pP pInf->SetFakeLineStart( nIdx > pInf->GetLineStart() ); pInf->SetIdx(TextFrameIndex(0)); } - else if (nIdx < TextFrameIndex(pOldText->getLength())) + else { - sal_Int32 const nFieldLen(pPor->GetFieldLen()); - aText = (*pOldText).replaceAt(sal_Int32(nIdx), nFieldLen, aText); + 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 ); } @@ -189,6 +203,13 @@ SwFieldSlot::~SwFieldSlot() { 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 ); @@ -381,25 +402,37 @@ bool SwFieldPortion::Format( SwTextFormatInfo &rInf ) // 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; - switch (nNew) + auto IsHook = [](const sal_Unicode cNew, bool const isSpace = false) -> bool { - case CH_BREAK : bFull = true; - [[fallthrough]]; - case ' ' : - 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: + switch (cNew) { - aNew = aNew.copy( 1 ); - ++nNextOfst; - break; + case ' ': // tdf#159101 this one is not in ScanPortionEnd + // but is required for justified text + return isSpace; + 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; } - default: ; + }; + if (IsHook(nNew, true)) + { + if (nNew == CH_BREAK) + { + bFull = true; + } + aNew = aNew.copy(1); + ++nNextOfst; } // Even if there is no more text left for a follow field, @@ -410,8 +443,17 @@ bool SwFieldPortion::Format( SwTextFormatInfo &rInf ) { pField->SetFont( std::make_unique<SwFont>( *rInf.GetFont() ) ); } - pField->SetFollow( true ); - SetHasFollow( true ); + 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 @@ -433,7 +475,7 @@ void SwFieldPortion::Paint( const SwTextPaintInfo &rInf ) const SwFontSave aSave( rInf, m_pFont.get() ); // OSL_ENSURE(GetLen() <= TextFrameIndex(1), "SwFieldPortion::Paint: rest-portion pollution?"); - if( Width() && ( !m_bPlaceHolder || rInf.GetOpt().IsShowPlaceHolderFields() ) ) + if (Width() && !m_bContentControl) { // A very liberal use of the background rInf.DrawViewOpt( *this, PortionType::Field ); @@ -446,7 +488,7 @@ bool SwFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) c rText = m_aExpand; if( rText.isEmpty() && rInf.OnWin() && !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && - SwViewOption::IsFieldShadings() && + rInf.GetOpt().IsFieldShadings() && !HasFollow() ) rText = " "; return true; @@ -454,14 +496,24 @@ bool SwFieldPortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) c void SwFieldPortion::HandlePortion( SwPortionHandler& rPH ) const { - sal_Int32 nH = 0; - sal_Int32 nW = 0; + 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) { - nH = m_pFont->GetSize(m_pFont->GetActual()).Height(); - nW = m_pFont->GetSize(m_pFont->GetActual()).Width(); + m_pFont->dumpAsXml(pWriter); } - rPH.Special( GetLen(), m_aExpand, GetWhichPor(), nH, nW, m_pFont.get() ); + + (void)xmlTextWriterEndElement(pWriter); } SwPosSize SwFieldPortion::GetTextSize( const SwTextSizeInfo &rInf ) const @@ -501,10 +553,10 @@ SwNumberPortion::SwNumberPortion( const OUString &rExpand, const bool bCntr, const sal_uInt16 nMinDst, const bool bLabelAlignmentPosAndSpaceModeActive ) - : SwFieldPortion( rExpand, std::move(pFont) ), - m_nFixWidth(0), - m_nMinDist( nMinDst ), - mbLabelAlignmentPosAndSpaceModeActive( bLabelAlignmentPosAndSpaceModeActive ) + : SwFieldPortion(rExpand, std::move(pFont), TextFrameIndex(0)) + , m_nFixWidth(0) + , m_nMinDist(nMinDst) + , mbLabelAlignmentPosAndSpaceModeActive(bLabelAlignmentPosAndSpaceModeActive) { SetWhichPor( PortionType::Number ); SetLeft( bLft ); @@ -550,13 +602,16 @@ bool SwNumberPortion::Format( SwTextFormatInfo &rInf ) if ( !mbLabelAlignmentPosAndSpaceModeActive ) { - if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) && + if ((!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::IGNORE_FIRST_LINE_INDENT_IN_NUMBERING) && // #i32902# - !IsFootnoteNumPortion() ) + !IsFootnoteNumPortion()) || + // tdf#159382 + (IsFootnoteNumPortion() && + rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::NO_GAP_AFTER_NOTE_NUMBER))) { nDiff = rInf.Left() + rInf.GetTextFrame()->GetTextNodeForParaProps()-> - GetSwAttrSet().GetLRSpace().GetTextFirstLineOffset() + GetSwAttrSet().GetFirstLineIndent().GetTextFirstLineOffset() - rInf.First() + rInf.ForcedLeftMargin(); } @@ -595,10 +650,10 @@ bool SwNumberPortion::Format( SwTextFormatInfo &rInf ) if ( rInf.IsMulti() ) { if ( Height() < nDiff ) - Height( sal_uInt16( nDiff ) ); + Height( nDiff ); } else if( Width() < nDiff ) - Width( sal_uInt16(nDiff) ); + Width( nDiff ); } return bFull; } @@ -785,7 +840,7 @@ SwGrfNumPortion::SwGrfNumPortion( m_nYPos = 0; m_eOrient = text::VertOrientation::TOP; } - Width( static_cast<sal_uInt16>(rGrfSize.Width() + 2 * GRFNUM_SECURE) ); + Width( rGrfSize.Width() + 2 * GRFNUM_SECURE ); m_nFixWidth = Width(); m_nGrfHeight = rGrfSize.Height() + 2 * GRFNUM_SECURE; Height( sal_uInt16(m_nGrfHeight) ); @@ -834,7 +889,7 @@ bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf ) if( bFull ) { - Width( rInf.Width() - static_cast<sal_uInt16>(rInf.X()) ); + Width( rInf.Width() - rInf.X() ); if( bFly ) { SetLen(TextFrameIndex(0)); @@ -869,7 +924,7 @@ bool SwGrfNumPortion::Format( SwTextFormatInfo &rInf ) } if( Width() < nDiff ) - Width( sal_uInt16(nDiff) ); + Width( nDiff ); return bFull; } @@ -935,7 +990,7 @@ void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const SetId( reinterpret_cast<sal_IntPtr>( rInf.GetTextFrame() ) ); rInf.GetTextFrame()->SetAnimation(); } - if( aTmp.IsOver( rInf.GetPaintRect() ) && !bDraw ) + if( aTmp.Overlaps( rInf.GetPaintRect() ) && !bDraw ) { rInf.NoteAnimation(); const SwViewShell* pViewShell = rInf.GetVsh(); @@ -959,8 +1014,10 @@ void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const Graphic* pGraph = const_cast<Graphic*>(m_pBrush->GetGraphic()); if (pGraph) { + const OutputDevice* pOut = rInf.GetOut(); + assert(pOut); pGraph->StartAnimation( - const_cast<OutputDevice*>(rInf.GetOut()), aPos, aSize, m_nId ); + *const_cast<OutputDevice*>(pOut), aPos, aSize, m_nId); } } @@ -993,7 +1050,9 @@ void SwGrfNumPortion::Paint( const SwTextPaintInfo &rInf ) const if( bDraw && aTmp.HasArea() ) { - DrawGraphic( m_pBrush.get(), const_cast<OutputDevice*>(rInf.GetOut()), + const OutputDevice* pOut = rInf.GetOut(); + assert(pOut); + DrawGraphic( m_pBrush.get(), *const_cast<OutputDevice*>(pOut), aTmp, aRepaint, m_bReplace ? GRFNUM_REPLACE : GRFNUM_YES ); } } @@ -1062,6 +1121,9 @@ void SwTextFrame::StopAnimation( const OutputDevice* pOut ) */ 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) @@ -1166,7 +1228,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) { rInf.GetOut()->SetFont( rInf.GetFont()->GetFnt( m_aScrType[i] ) ); m_aWidth[ m_aScrType[i] ] = - static_cast<sal_uInt16>(2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3); + o3tl::narrowing<sal_uInt16>(2 * rInf.GetOut()->GetFontMetric().GetFontSize().Width() / 3); } } } @@ -1213,7 +1275,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) 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 ] = static_cast<sal_uInt16>(aSize.Width()); + m_aPos[ i ] = o3tl::narrowing<sal_uInt16>(aSize.Width()); if( i == nTop ) // enter the second line { m_nLowPos = nMaxDescent; @@ -1228,7 +1290,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) if( nAsc > nMaxAscent ) nMaxAscent = nAsc; if( aSize.Height() - nAsc > nMaxDescent ) - nMaxDescent = static_cast<sal_uInt16>(aSize.Height() - nAsc); + nMaxDescent = aSize.Height() - nAsc; } // for one or two characters we double the width of the portion if( nCount < 3 ) @@ -1291,7 +1353,7 @@ bool SwCombinedPortion::Format( SwTextFormatInfo &rInf ) { if( rInf.GetLineStart() == rInf.GetIdx() && (!rInf.GetLast()->InFieldGrp() || !static_cast<SwFieldPortion*>(rInf.GetLast())->IsFollow() ) ) - Width( static_cast<sal_uInt16>( rInf.Width() - rInf.X() ) ); + Width( rInf.Width() - rInf.X() ); else { Truncate(); @@ -1350,4 +1412,57 @@ void SwFieldFormDatePortion::Paint( const SwTextPaintInfo &rInf ) const } } +SwFieldPortion* SwJumpFieldPortion::Clone(const OUString& rExpand) const +{ + auto pRet = new SwJumpFieldPortion(*this); + pRet->m_aExpand = rExpand; + return pRet; +} + +bool SwJumpFieldPortion::DescribePDFControl(const SwTextPaintInfo& rInf) const +{ + auto pPDFExtOutDevData + = dynamic_cast<vcl::PDFExtOutDevData*>(rInf.GetOut()->GetExtOutDevData()); + if (!pPDFExtOutDevData) + return false; + + if (!pPDFExtOutDevData->GetIsExportFormFields()) + return false; + + if (m_nFormat != SwJumpEditFormat::JE_FMT_TEXT) + return false; + + vcl::PDFWriter::EditWidget aDescriptor; + + aDescriptor.Border = true; + aDescriptor.BorderColor = COL_BLACK; + + SwRect aLocation; + rInf.CalcRect(*this, &aLocation); + aDescriptor.Location = aLocation.SVRect(); + + // Map the text of the field to the descriptor's text. + static sal_Unicode constexpr aForbidden[] = { CH_TXTATR_BREAKWORD, 0 }; + aDescriptor.Text = comphelper::string::removeAny(GetExp(), aForbidden); + + // Description for accessibility purposes. + if (!m_sHelp.isEmpty()) + aDescriptor.Description = m_sHelp; + + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); + pPDFExtOutDevData->CreateControl(aDescriptor); + pPDFExtOutDevData->EndStructureElement(); + + return true; +} + +void SwJumpFieldPortion::Paint(const SwTextPaintInfo& rInf) const +{ + if (Width() && DescribePDFControl(rInf)) + return; + + if (rInf.GetOpt().IsShowPlaceHolderFields()) + SwFieldPortion::Paint(rInf); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfld.hxx b/sw/source/core/text/porfld.hxx index 519e56f8f58c..b92372942425 100644 --- a/sw/source/core/text/porfld.hxx +++ b/sw/source/core/text/porfld.hxx @@ -50,8 +50,8 @@ protected: bool m_bAnimated : 1; // Used by SwGrfNumPortion bool m_bNoPaint : 1; // Used by SwGrfNumPortion bool m_bReplace : 1; // Used by SwGrfNumPortion - const bool m_bPlaceHolder : 1; bool m_bNoLength : 1; // HACK for meta suffix (no CH_TXTATR) + bool m_bContentControl = false; void SetFont( std::unique_ptr<SwFont> pNew ) { m_pFont = std::move(pNew); } bool IsNoLength() const { return m_bNoLength; } @@ -59,10 +59,9 @@ protected: public: SwFieldPortion( const SwFieldPortion& rField ); - SwFieldPortion(const OUString &rExpand, std::unique_ptr<SwFont> pFnt = nullptr, bool bPlaceHolder = false, TextFrameIndex nLen = TextFrameIndex(1)); + SwFieldPortion(OUString aExpand, std::unique_ptr<SwFont> pFnt = nullptr, TextFrameIndex nLen = TextFrameIndex(1)); virtual ~SwFieldPortion() override; - sal_uInt16 m_nAttrFieldType; void TakeNextOffset( const SwFieldPortion* pField ); void CheckScript( const SwTextSizeInfo &rInf ); bool HasFont() const { return nullptr != m_pFont; } @@ -107,6 +106,11 @@ public: // 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; }; /** @@ -207,7 +211,7 @@ public: 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 + 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 @@ -257,4 +261,25 @@ private: bool m_bStart; }; +class SwJumpFieldPortion final : public SwFieldPortion +{ +public: + explicit SwJumpFieldPortion(OUString aExpand, OUString aHelp, std::unique_ptr<SwFont> pFont, + sal_uInt32 nFormat) + : SwFieldPortion(std::move(aExpand), std::move(pFont)) + , m_nFormat(nFormat) + , m_sHelp(std::move(aHelp)) + { + } + virtual SwFieldPortion* Clone(const OUString& rExpand) const override; + + virtual void Paint(const SwTextPaintInfo& rInf) const override; + +private: + sal_uInt32 m_nFormat; // SwJumpEditFormat from SwField::GetFormat() + OUString m_sHelp; + + bool DescribePDFControl(const SwTextPaintInfo& rInf) const; +}; + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/porfly.cxx b/sw/source/core/text/porfly.cxx index 34edc3082b71..19ac692cf42f 100644 --- a/sw/source/core/text/porfly.cxx +++ b/sw/source/core/text/porfly.cxx @@ -26,11 +26,11 @@ #include <frmfmt.hxx> #include <viewsh.hxx> #include <textboxhelper.hxx> +#include <IDocumentState.hxx> #include <sal/log.hxx> #include <fmtanchr.hxx> #include <fmtflcnt.hxx> -#include <fmtornt.hxx> #include <flyfrms.hxx> #include <txatbase.hxx> #include "porfly.hxx" @@ -39,6 +39,7 @@ #include <sortedobjs.hxx> #include <officecfg/Office/Common.hxx> +#include <PostItMgr.hxx> /** * class SwFlyPortion => we expect a frame-locale SwRect! @@ -57,7 +58,17 @@ bool SwFlyPortion::Format( SwTextFormatInfo &rInf ) rInf.GetLastTab()->FormatEOL( rInf ); rInf.GetLast()->FormatEOL( rInf ); - PrtWidth( static_cast<sal_uInt16>(GetFix() - rInf.X() + PrtWidth()) ); + + 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" ); @@ -77,11 +88,11 @@ bool SwFlyPortion::Format( SwTextFormatInfo &rInf ) && ' ' != rInf.GetChar(rInf.GetIdx() - TextFrameIndex(1)) && ( !rInf.GetLast() || !rInf.GetLast()->IsBreakPortion() ) ) { - SetBlankWidth( rInf.GetTextSize(OUString(' ')).Width() ); + SetBlankWidth(GetBlankWidth() + rInf.GetTextSize(OUString(' ')).Width()); SetLen(TextFrameIndex(1)); } - const sal_uInt16 nNewWidth = static_cast<sal_uInt16>(rInf.X() + PrtWidth()); + const sal_uInt16 nNewWidth = o3tl::narrowing<sal_uInt16>(rInf.X() + PrtWidth()); if( rInf.Width() <= nNewWidth ) { Truncate(); @@ -108,7 +119,7 @@ bool SwFlyCntPortion::Format( SwTextFormatInfo &rInf ) // KerningPortions at beginning of line, e.g., for grid layout // must be considered. const SwLinePortion* pLastPor = rInf.GetLast(); - const sal_uInt16 nLeft = ( pLastPor && + const auto nLeft = ( pLastPor && ( pLastPor->IsKernPortion() || pLastPor->IsErgoSumPortion() ) ) ? pLastPor->Width() : @@ -157,14 +168,14 @@ void SwTextFrame::MoveFlyInCnt(SwTextFrame *pNew, { // Consider changed type of <SwSortedList> entries SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; - const SwFormatAnchor& rAnch = pAnchoredObj->GetFrameFormat().GetAnchor(); + 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 = dynamic_cast<SwFlyFrame *>( pAnchoredObj ) ) + if ( auto pFlyFrame = pAnchoredObj->DynCastFlyFrame() ) { RemoveFly( pFlyFrame ); pNew->AppendFly( pFlyFrame ); @@ -211,7 +222,7 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect); if(!((m_pFly->IsCompletePaint() || - m_pFly->getFrameArea().IsOver(aRepaintRect)) && + m_pFly->getFrameArea().Overlaps(aRepaintRect)) && SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), m_pFly->getRootFrame()->GetCurrShell()))) return; @@ -226,7 +237,7 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const // track changes: cross out the image, if it is deleted const SwFrame *pFrame = m_pFly->Lower(); - if ( IsDeleted() && pFrame ) + if ( GetAuthor() != std::string::npos && IsDeleted() && pFrame ) { SwRect aPaintRect( pFrame->GetPaintArea() ); @@ -236,7 +247,8 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& rInf) const 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(NON_PRINTING_CHARACTER_COLOR); + 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 ) @@ -270,6 +282,7 @@ void sw::DrawFlyCntPortion::Paint(const SwTextPaintInfo&) const SwFlyCntPortion::SwFlyCntPortion() : m_bMax(false) , m_bDeleted(false) + , m_nAuthor(std::string::npos) , m_eAlign(sw::LineAlign::NONE) { mnLineLength = TextFrameIndex(1); @@ -359,51 +372,20 @@ void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase, aObjPositioning.CalcPosition(); } - SwFrameFormat* pShape = FindFrameFormat(pSdrObj); - const SwFormatAnchor& rAnchor(pShape->GetAnchor()); - if (rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR) + if (auto pFormat = FindFrameFormat(pSdrObj)) { - // This is an inline draw shape, see if it has a textbox. - SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT); - if (pTextBox) + if (pFormat->GetOtherTextBoxFormats() + && pFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) { - // It has, so look up its text rectangle, and adjust the position - // of the textbox accordingly. - // Both rectangles are absolute, SwFormatHori/VertOrient's position - // is relative to the print area of the anchor text frame. - tools::Rectangle aTextRectangle = SwTextBoxHelper::getTextRectangle(pShape); - tools::Long nXoffs = SwTextBoxHelper::getTextRectangle(pShape, false).getX(); - - const auto aPos(pShape->GetAnchor().GetContentAnchor()); - SwFormatVertOrient aVert(pTextBox->GetVertOrient()); - SwFormatHoriOrient aHori(pTextBox->GetHoriOrient()); - - // tdf#138598 Replace vertical alignment of As_char textboxes in footer - // tdf#140158 Remove horizontal positioning of As_char textboxes, because - // the anchor moving does the same for it. - if (!aPos->nNode.GetNode().FindFooterStartNode()) - { - aVert.SetVertOrient(css::text::VertOrientation::NONE); - aVert.SetRelationOrient(css::text::RelOrientation::FRAME); - sal_Int32 const nTop = aTextRectangle.getY() - rFrame.getFrameArea().Top() - - rFrame.getFramePrintArea().Top(); - aVert.SetPos(nTop); - } - else - { - aVert.SetVertOrient(css::text::VertOrientation::NONE); - aVert.SetPos(SwTextBoxHelper::getTextRectangle(pShape, false).getY()); - } - - SwFormatAnchor aNewTxBxAnchor(pTextBox->GetAnchor()); - aNewTxBxAnchor.SetAnchor(aPos); - aHori.SetPos(nXoffs); - - pTextBox->LockModify(); - pTextBox->SetFormatAttr(aNewTxBxAnchor); - pTextBox->SetFormatAttr(aVert); - pTextBox->SetFormatAttr(aHori); - pTextBox->UnlockModify(); + // 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); } } @@ -421,14 +403,14 @@ void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase, SwTwips nRelPos = aObjPositioning.GetRelPosY(); if ( nRelPos < 0 ) { - mnAscent = static_cast<sal_uInt16>(-nRelPos); + mnAscent = -nRelPos; if( mnAscent > Height() ) Height( mnAscent ); } else { mnAscent = 0; - Height( Height() + static_cast<sal_uInt16>(nRelPos) ); + Height( Height() + o3tl::narrowing<sal_uInt16>(nRelPos) ); } } else diff --git a/sw/source/core/text/porfly.hxx b/sw/source/core/text/porfly.hxx index fdd974050513..2c56563a4436 100644 --- a/sw/source/core/text/porfly.hxx +++ b/sw/source/core/text/porfly.hxx @@ -46,6 +46,7 @@ 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; @@ -55,6 +56,8 @@ public: 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; } @@ -73,6 +76,7 @@ namespace sw 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; } + const SwFlyInContentFrame* GetFlyFrame() const { 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; diff --git a/sw/source/core/text/porftn.hxx b/sw/source/core/text/porftn.hxx index a5ecda64d072..925a04f7793b 100644 --- a/sw/source/core/text/porftn.hxx +++ b/sw/source/core/text/porftn.hxx @@ -68,7 +68,7 @@ class SwQuoVadisPortion : public SwFieldPortion { OUString m_aErgo; public: - SwQuoVadisPortion( const OUString &rExp, const OUString& rStr ); + 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; diff --git a/sw/source/core/text/porglue.cxx b/sw/source/core/text/porglue.cxx index 7c09ded23a2f..5f1fd2a7dc8d 100644 --- a/sw/source/core/text/porglue.cxx +++ b/sw/source/core/text/porglue.cxx @@ -54,7 +54,7 @@ bool SwGluePortion::GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) co if( GetLen() && rInf.OnWin() && rInf.GetOpt().IsBlank() && rInf.IsNoSymbol() ) { - OUStringBuffer aBuf; + OUStringBuffer aBuf(GetLen().get()); comphelper::string::padToLength(aBuf, sal_Int32(GetLen()), CH_BULLET); rText = aBuf.makeStringAndClear(); return true; @@ -69,8 +69,9 @@ void SwGluePortion::Paint( const SwTextPaintInfo &rInf ) const if( rInf.GetFont()->IsPaintBlank() ) { - OUStringBuffer aBuf; - comphelper::string::padToLength(aBuf, GetFixWidth() / sal_Int32(GetLen()), ' '); + 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); @@ -128,6 +129,18 @@ void SwGluePortion::Join( SwGluePortion *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! */ @@ -144,6 +157,18 @@ SwFixPortion::SwFixPortion() 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 ) { diff --git a/sw/source/core/text/porglue.hxx b/sw/source/core/text/porglue.hxx index 05f72beb9fb3..aaaeec339f31 100644 --- a/sw/source/core/text/porglue.hxx +++ b/sw/source/core/text/porglue.hxx @@ -24,6 +24,9 @@ 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: @@ -44,6 +47,8 @@ public: 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 @@ -54,6 +59,8 @@ public: 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 diff --git a/sw/source/core/text/porhyph.hxx b/sw/source/core/text/porhyph.hxx index 43341ce1daf9..dc3c6651d0a8 100644 --- a/sw/source/core/text/porhyph.hxx +++ b/sw/source/core/text/porhyph.hxx @@ -36,6 +36,9 @@ public: // 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 diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx index 3d26e990c50c..50aaa43b7745 100644 --- a/sw/source/core/text/porlay.cxx +++ b/sw/source/core/text/porlay.cxx @@ -33,7 +33,7 @@ #include <unicode/uchar.h> #include <com/sun/star/i18n/ScriptType.hpp> #include <com/sun/star/i18n/CharacterIteratorMode.hpp> -#include <com/sun/star/i18n/CTLScriptType.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> @@ -41,58 +41,93 @@ #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 <calc.hxx> #include <IDocumentRedlineAccess.hxx> #include <IDocumentSettingAccess.hxx> #include <IDocumentContentOperations.hxx> -#include <IDocumentFieldsAccess.hxx> #include <IMark.hxx> #include <sortedobjs.hxx> -#include <dcontact.hxx> - -using namespace ::com::sun::star; -using namespace i18n::ScriptType; +#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) -#if U_ICU_VERSION_MAJOR_NUM >= 58 #define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH)) -#else -#define isFehChar(c) IS_JOINING_GROUP((c), FEH) -#endif #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) -#if U_ICU_VERSION_MAJOR_NUM >= 58 #define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF)) -#else -#define isQafChar(c) IS_JOINING_GROUP((c), QAF) -#endif #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 charters that behave like Beh in medial form. +// Beh and characters that behave like Beh in medial form. static bool isBehChar(sal_Unicode cCh) { bool bRet = false; @@ -100,9 +135,7 @@ static bool isBehChar(sal_Unicode cCh) { case U_JG_BEH: case U_JG_NOON: -#if U_ICU_VERSION_MAJOR_NUM >= 58 case U_JG_AFRICAN_NOON: -#endif case U_JG_NYA: case U_JG_YEH: case U_JG_FARSI_YEH: @@ -117,7 +150,7 @@ static bool isBehChar(sal_Unicode cCh) return bRet; } -// Yeh and charters that behave like Yeh in final form. +// Yeh and characters that behave like Yeh in final form. static bool isYehChar(sal_Unicode cCh) { bool bRet = false; @@ -185,21 +218,18 @@ void SwLineLayout::DeleteNext() { if (!m_pNext) return; - std::vector<SwLineLayout*> aNexts; SwLineLayout* pNext = m_pNext; do { - aNexts.push_back(pNext); SwLineLayout* pLastNext = pNext; pNext = pNext->GetNext(); pLastNext->SetNext(nullptr); + delete pLastNext; } while (pNext); - for (auto a : aNexts) - delete a; } -void SwLineLayout::Height(const sal_uInt16 nNew, const bool bText) +void SwLineLayout::Height(const SwTwips nNew, const bool bText) { SwPosSize::Height(nNew); if (bText) @@ -318,20 +348,25 @@ void SwLineLayout::CreateSpaceAdd( const tools::Long nInit ) SetLLSpaceAdd( nInit, 0 ); } -// Returns true if there are only blanks in [nStt, nEnd[ +// #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) { - bool bBlankOnly = true; while ( nStt < nEnd ) { - const sal_Unicode cChar = rText[ sal_Int32(nStt++) ]; - if ( ' ' != cChar && CH_FULL_BLANK != cChar && CH_SIX_PER_EM != cChar ) + switch (rText[sal_Int32(nStt++)]) { - bBlankOnly = false; - break; + 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 bBlankOnly; + return true; } // Swapped out from FormatLine() @@ -342,6 +377,10 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) 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 ); @@ -374,7 +413,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) } else { - const sal_uInt16 nLineHeight = Height(); + const SwTwips nLineHeight = Height(); Init( GetNextPortion() ); SwLinePortion *pPos = mpNextPortion; SwLinePortion *pLast = this; @@ -389,6 +428,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // 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 @@ -400,7 +440,9 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) if( !GetAscent() ) SetAscent( pPos->GetAscent() ); } - delete pLast->Cut( pPos ); + SwLinePortion* pPortion = pLast->Cut( pPos ); + rLine.ClearIfIsFirstOfBorderMerge(pPortion); + delete pPortion; pPos = pLast->GetNextPortion(); continue; } @@ -410,7 +452,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) AddPrtWidth( pPos->Width() ); // #i3952# - if ( bIgnoreBlanksAndTabsForLineHeightCalculation ) + if (bIgnoreBlanksAndTabsForLineHeightCalculation && !rInf.GetLineStart()) { if ( pPos->InTabGrp() || pPos->IsHolePortion() || ( pPos->IsTextPortion() && @@ -437,8 +479,8 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) // We had an attribute change: Sum up/build maxima of length and mass - sal_uInt16 nPosHeight = pPos->Height(); - sal_uInt16 nPosAscent = pPos->GetAscent(); + SwTwips nPosHeight = pPos->Height(); + SwTwips nPosAscent = pPos->GetAscent(); SAL_WARN_IF( nPosHeight < nPosAscent, "sw.core", "SwLineLayout::CalcLine: bad ascent or height" ); @@ -451,10 +493,16 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) else if( !bHasFlyPortion && ( pPos->IsFlyCntPortion() || pPos->IsFlyPortion() ) ) bHasFlyPortion = true; - // To prevent that a paragraph-end-character does not change - // the line height through a Descent and thus causing the line - // to reformat. - if ( !pPos->IsBreakPortion() || !Height() ) + // 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; @@ -567,7 +615,19 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) if( nFlyDescent > nFlyHeight - nFlyAscent ) Height( nFlyHeight + nFlyDescent, false ); else - Height( nFlyHeight, false ); + { + 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 ); @@ -591,14 +651,28 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) } } - // #i3952# + // #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 ); - SetAscent( nTmpAscent ); - Height( nTmpHeight, false ); + + 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: @@ -630,65 +704,70 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) SetRedline( bHasRedline ); // redlining: set crossing out for deleted anchored objects - if ( bHasFlyPortion ) + if ( !bHasFlyPortion ) + return; + + SwLinePortion *pPos = mpNextPortion; + TextFrameIndex nLineLength; + while ( pPos ) { - SwLinePortion *pPos = mpNextPortion; - TextFrameIndex nLineLength; - while ( pPos ) + TextFrameIndex const nPorSttIdx = rInf.GetLineStart() + nLineLength; + nLineLength += pPos->GetLen(); + // anchored as characters + if( pPos->IsFlyCntPortion() ) { - 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 ) { - bool bDeleted = false; - 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, /*bFullLine=*/false); - bDeleted = bHasFlyRedline && eRedlineEnd == RedlineType::Delete; - } - static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted); + 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; } - // anchored to characters - else if ( pPos->IsFlyPortion() ) + 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() ) ) { - 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 ) { - for ( size_t i = 0; rInf.GetTextFrame()->GetDrawObjs() && i < pObjs->size(); ++i ) + SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i]; + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) { - SwAnchoredObject* pAnchoredObj = (*rInf.GetTextFrame()->GetDrawObjs())[i]; - if ( auto pFly = dynamic_cast<SwFlyFrame *>( pAnchoredObj ) ) + bool bDeleted = false; + size_t nAuthor = std::string::npos; + const SwFormatAnchor& rAnchor = pAnchoredObj->GetFrameFormat()->GetAnchor(); + if ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR ) { - bool bDeleted = false; - 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() ) { - SwPosition aAnchor = *rAnchor.GetContentAnchor(); - const SwPaM aPam(aAnchor, aAnchor); - if ( rIDRA.HasRedline( aPam, RedlineType::Delete, - /*bStartOrEndInRange=*/false) ) - { - bDeleted = true; - } + bDeleted = true; + nAuthor = pFnd->GetAuthor(); } - pFly->SetDeleted(bDeleted); } - + pFly->SetDeleted(bDeleted); + pFly->SetAuthor(nAuthor); } } } - pPos = pPos->GetNextPortion(); } + pPos = pPos->GetNextPortion(); } } @@ -721,9 +800,8 @@ void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent, ( !pTmpPortion->IsFlyCntPortion() && !(pTmpPortion == this && pTmpPortion->GetNextPortion() ) ) ) ) { - SwTwips nPortionAsc = static_cast<SwTwips>(pTmpPortion->GetAscent()); - SwTwips nPortionDesc = static_cast<SwTwips>(pTmpPortion->Height()) - - nPortionAsc; + SwTwips nPortionAsc = pTmpPortion->GetAscent(); + SwTwips nPortionDesc = pTmpPortion->Height() - nPortionAsc; const bool bFlyCmp = pTmpPortion->IsFlyCntPortion() ? static_cast<const SwFlyCntPortion*>(pTmpPortion)->IsMax() : @@ -745,9 +823,19 @@ void SwLineLayout::MaxAscentDescent( SwTwips& _orAscent, } } +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_bFormatAdj = m_bDummy = m_bEndHyph = m_bMidHyph = m_bLastHyph = m_bFly = m_bRest = m_bBlinking = m_bClipping = m_bContent = m_bRedline = m_bRedlineEnd = m_bForcedLeftMargin = m_bHanging = false; m_eRedlineEnd = RedlineType::None; @@ -826,19 +914,64 @@ SwFontScript SwScriptInfo::WhichFont(sal_Int32 nIdx, OUString const& rText) 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::pair<TextFrameIndex, SwScriptInfo::MarkKind>> & o_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: @@ -854,39 +987,39 @@ static void InitBookmarks( // 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.nNode.GetNode() == pNode); + assert(&rStart.GetNode() == pNode); while (iter != end) { - if (&rStart.nNode.GetNode() != iter->pNode // iter moved to next node - || rStart.nContent.GetIndex() < iter->nStart) + if (&rStart.GetNode() != iter->pNode // iter moved to next node + || rStart.GetContentIndex() < iter->nStart) { - if (rEnd.nNode.GetIndex() < iter->pNode->GetIndex() - || (&rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() <= 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); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); break; } } - else if (rStart.nContent.GetIndex() <= iter->nEnd) + else if (rStart.GetContentIndex() <= iter->nEnd) { auto const iterNext(iter + 1); - if (rStart.nContent.GetIndex() == iter->nEnd + if (rStart.GetContentIndex() == iter->nEnd && (iterNext == end - ? &rEnd.nNode.GetNode() == iter->pNode - : (rEnd.nNode.GetIndex() < iterNext->pNode->GetIndex() - || (&rEnd.nNode.GetNode() == iterNext->pNode && rEnd.nContent.GetIndex() < iterNext->nStart)))) + ? &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.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rStart.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); break; } } @@ -899,13 +1032,13 @@ static void InitBookmarks( } if (iter == end) { - if (pNode->GetIndex() < rEnd.nNode.GetIndex()) // pNode is last node of merged + if (pNode->GetIndex() < rEnd.GetNodeIndex()) // pNode is last node of merged { break; // deleted - skip it } else { - o_rBookmarks.emplace_back(nOffset, it.second); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); } } break; @@ -913,36 +1046,36 @@ static void InitBookmarks( case SwScriptInfo::MarkKind::End: { SwPosition const& rEnd(it.first->GetMarkEnd()); - assert(&rEnd.nNode.GetNode() == pNode); + assert(&rEnd.GetNode() == pNode); while (true) { if (iter == end - || &rEnd.nNode.GetNode() != iter->pNode // iter moved to next node - || rEnd.nContent.GetIndex() <= iter->nStart) + || &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.nNode.GetIndex() - || ((*oPrevIter)->pNode == &rStart.nNode.GetNode() - && ((iter != end && &rEnd.nNode.GetNode() == iter->pNode && rEnd.nContent.GetIndex() == iter->nStart) - ? (*oPrevIter)->nEnd < rStart.nContent.GetIndex() - : (*oPrevIter)->nEnd <= rStart.nContent.GetIndex()))) - : rStart.nNode == rEnd.nNode) + ? ((*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); + o_rBookmarks.emplace_back(nOffset, it.second, c, it.first->GetName()); break; } } - else if (rEnd.nContent.GetIndex() <= iter->nEnd) + else if (rEnd.GetContentIndex() <= iter->nEnd) { o_rBookmarks.emplace_back( - nOffset + TextFrameIndex(rEnd.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rEnd.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); break; } else @@ -957,26 +1090,26 @@ static void InitBookmarks( case SwScriptInfo::MarkKind::Point: { SwPosition const& rPos(it.first->GetMarkPos()); - assert(&rPos.nNode.GetNode() == pNode); + assert(&rPos.GetNode() == pNode); while (iter != end) { - if (&rPos.nNode.GetNode() != iter->pNode // iter moved to next node - || rPos.nContent.GetIndex() < iter->nStart) + if (&rPos.GetNode() != iter->pNode // iter moved to next node + || rPos.GetContentIndex() < iter->nStart) { break; // deleted - skip it } - else if (rPos.nContent.GetIndex() <= iter->nEnd) + else if (rPos.GetContentIndex() <= iter->nEnd) { - if (rPos.nContent.GetIndex() == iter->nEnd - && rPos.nContent.GetIndex() != iter->pNode->Len()) + if (rPos.GetContentIndex() == iter->nEnd + && rPos.GetContentIndex() != iter->pNode->Len()) { break; // deleted - skip it } else { o_rBookmarks.emplace_back( - nOffset + TextFrameIndex(rPos.nContent.GetIndex() - iter->nStart), - it.second); + nOffset + TextFrameIndex(rPos.GetContentIndex() - iter->nStart), + it.second, c, it.first->GetName()); } break; } @@ -990,6 +1123,10 @@ static void InitBookmarks( break; } } + if (iter == end) + { + break; // remaining marks are hidden + } } } @@ -1017,11 +1154,12 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, TextFrameIndex nOffset(0); std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter; for (auto iter = pMerged->extents.begin(); iter != pMerged->extents.end(); - oPrevIter = iter, ++iter) + oPrevIter = iter) { if (iter->pNode == pNode) { nOffset += TextFrameIndex(iter->nEnd - iter->nStart); + ++iter; continue; // skip extents at end of previous node } pNode = iter->pNode; @@ -1037,39 +1175,66 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, 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; - while (true) + if (nEnd <= iter->nStart) + { // entirely in gap, skip this hidden range + continue; + } + + do { - // because of the selectRedLineDeleted call, never overlaps - // extents, must be contained inside one extent - assert(!(iter->nStart <= nStart && nStart < iter->nEnd && iter->nEnd < nEnd)); - assert(!(nStart < iter->nStart && iter->nStart < nEnd && nEnd <= iter->nEnd)); - if (iter->nStart <= nStart && nEnd <= iter->nEnd) + if (!isStartHandled && nStart <= iter->nEnd) { - if (iter->nStart == nStart && !m_HiddenChg.empty() + isStartHandled = true; + if (nStart <= iter->nStart && !m_HiddenChg.empty() && m_HiddenChg.back() == nOffset) { // previous one went until end of extent, extend it - m_HiddenChg.back() += TextFrameIndex(nEnd - iter->nStart); + oExtend.emplace(::std::min(iter->nEnd, nEnd) - ::std::max(iter->nStart, nStart)); } - else // new one + else { - m_HiddenChg.push_back(nOffset + TextFrameIndex(nStart - iter->nStart)); - m_HiddenChg.push_back(nOffset + TextFrameIndex(nEnd - iter->nStart)); + m_HiddenChg.push_back(nOffset + TextFrameIndex(::std::max(nStart - iter->nStart, sal_Int32(0)))); } - break; } - else + else if (oExtend) { - nOffset += TextFrameIndex(iter->nEnd - iter->nStart); - ++iter; - // because selectRedLineDeleted, must find it in pNode - assert(iter != pMerged->extents.end()); - assert(iter->pNode == pNode); + *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 } } - nOffset += TextFrameIndex(iter->nEnd - iter->nStart); } } else @@ -1081,25 +1246,38 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, 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().nContent.GetIndex()), it.second); + 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().nContent.GetIndex()), it.second); + 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().nContent.GetIndex()), it.second); + 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() + 1; + const sal_Int32 nEnd = rRange.Max() + (rText.isEmpty() ? 0 : 1); m_HiddenChg.push_back( TextFrameIndex(nStart) ); m_HiddenChg.push_back( TextFrameIndex(nEnd) ); @@ -1274,50 +1452,28 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, if (nChg > TextFrameIndex(rText.getLength()) || nChg < TextFrameIndex(0)) nChg = TextFrameIndex(rText.getLength()); - // #i28203# - // for 'complex' portions, we make sure that a portion does not contain more - // than one script: - if( i18n::ScriptType::COMPLEX == nScript ) - { - const short nScriptType = ScriptTypeDetector::getCTLScriptType( - rText, sal_Int32(nSearchStt) ); - TextFrameIndex nNextCTLScriptStart = nSearchStt; - short nCurrentScriptType = nScriptType; - while( css::i18n::CTLScriptType::CTL_UNKNOWN == nCurrentScriptType || nScriptType == nCurrentScriptType ) - { - nNextCTLScriptStart = TextFrameIndex( - ScriptTypeDetector::endOfCTLScriptType( - rText, sal_Int32(nNextCTLScriptStart))); - if (nNextCTLScriptStart >= TextFrameIndex(rText.getLength()) - || nNextCTLScriptStart >= nChg) - break; - nCurrentScriptType = ScriptTypeDetector::getCTLScriptType( - rText, sal_Int32(nNextCTLScriptStart)); - } - nChg = std::min( nChg, nNextCTLScriptStart ); - } - // 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 - if (nChg < TextFrameIndex(rText.getLength()) && nChg > TextFrameIndex(0) - && (i18n::ScriptType::WEAK == - g_pBreakIt->GetBreakIter()->getScriptType(rText, sal_Int32(nChg) - 1))) + // 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)) { - int8_t nType = u_charType(rText[sal_Int32(nChg)]); - if (nType == U_NON_SPACING_MARK || nType == U_ENCLOSING_MARK || - nType == U_COMBINING_SPACING_MARK ) - { - m_ScriptChanges.emplace_back(nChg-TextFrameIndex(1), nScript); - } - else + 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)) { - m_ScriptChanges.emplace_back(nChg, nScript); + nPos = nPrevPos; } } - else - { - m_ScriptChanges.emplace_back(nChg, nScript); - } + m_ScriptChanges.emplace_back(TextFrameIndex(nPos), nScript); ++nCnt; // if current script is asian, we search for compressible characters @@ -1340,6 +1496,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, 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 @@ -1347,9 +1504,11 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, 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: @@ -1406,7 +1565,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, [&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( pGetLangOfChar, rText, nullptr, ModelToViewHelper(), + SwScanner aScanner( std::move(pGetLangOfChar), rText, nullptr, ModelToViewHelper(), i18n::WordType::DICTIONARY_WORD, sal_Int32(nLastKashida), sal_Int32(nChg)); @@ -1415,10 +1574,9 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, { const OUString& rWord = aScanner.GetWord(); - sal_Int32 nIdx = 0; + sal_Int32 nIdx = 0, nPrevIdx = 0; sal_Int32 nKashidaPos = -1; - sal_Unicode cCh; - sal_Unicode cPrevCh = 0; + sal_Unicode cCh, cPrevCh = 0; int nPriorityLevel = 7; // 0..6 = level found // 7 not found @@ -1466,7 +1624,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 2; } } @@ -1487,7 +1645,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 3; } } @@ -1502,12 +1660,12 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // 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() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 4; } } @@ -1529,7 +1687,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 5; } } @@ -1545,7 +1703,7 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) { - nKashidaPos = aScanner.GetBegin() + nIdx - 1; + nKashidaPos = aScanner.GetBegin() + nPrevIdx; nPriorityLevel = 6; } } @@ -1554,7 +1712,10 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode, // 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 @@ -1776,29 +1937,47 @@ TextFrameIndex SwScriptInfo::NextBookmark(TextFrameIndex const nPos) const { for (auto const& it : m_Bookmarks) { - if (nPos < it.first) + if (nPos < std::get<0>(it)) { - return it.first; + return std::get<0>(it); } } return TextFrameIndex(COMPLETE_STRING); } -auto SwScriptInfo::GetBookmark(TextFrameIndex const nPos) const -> MarkKind +std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> + SwScriptInfo::GetBookmarks(TextFrameIndex const nPos) { - MarkKind ret{0}; + std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> aColors; for (auto const& it : m_Bookmarks) { - if (nPos == it.first) + if (nPos == std::get<0>(it)) { - ret |= it.second; + 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 < it.first) + else if (nPos < std::get<0>(it)) { break; } } - return ret; + + // 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. @@ -2019,7 +2198,7 @@ size_t SwScriptInfo::HasKana(TextFrameIndex const nStart, TextFrameIndex const n return SAL_MAX_SIZE; } -tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, +tools::Long SwScriptInfo::Compress(KernArray& rKernArray, TextFrameIndex nIdx, TextFrameIndex nLen, const sal_uInt16 nCompress, const sal_uInt16 nFontHeight, bool bCenter, Point* pPoint ) const @@ -2055,7 +2234,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, return 0; tools::Long nSub = 0; - tools::Long nLast = nI ? pKernArray[ nI - 1 ] : 0; + tools::Long nLast = nI ? rKernArray[ nI - 1 ] : 0; do { const CompType nType = GetCompType( nCompIdx ); @@ -2067,7 +2246,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, nCompLen = nLen; // are we allowed to compress the character? - if ( pKernArray[ nI ] - nLast < nMinWidth ) + if ( rKernArray[ nI ] - nLast < nMinWidth ) { nIdx++; nI++; } @@ -2078,7 +2257,7 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, SAL_WARN_IF( SwScriptInfo::NONE == nType, "sw.core", "None compression?!" ); // nLast is width of current character - nLast -= pKernArray[ nI ]; + nLast -= rKernArray[ nI ]; nLast *= nCompress; tools::Long nMove = 0; @@ -2101,10 +2280,11 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, else nLast /= 100000; nSub -= nLast; - nLast = pKernArray[ nI ]; + nLast = rKernArray[ nI ]; if( nI && nMove ) - pKernArray[ nI - 1 ] += nMove; - pKernArray[ nI++ ] -= nSub; + rKernArray.adjust(nI - 1, nMove); + rKernArray.adjust(nI, -nSub); + ++nI; ++nIdx; } } @@ -2123,8 +2303,9 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, while( nIdx < nTmpChg ) { - nLast = pKernArray[ nI ]; - pKernArray[ nI++ ] -= nSub; + nLast = rKernArray[ nI ]; + rKernArray.adjust(nI, -nSub); + ++nI; ++nIdx; } } while( nIdx < nLen ); @@ -2136,8 +2317,8 @@ tools::Long SwScriptInfo::Compress(tools::Long* pKernArray, TextFrameIndex nIdx, // 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( tools::Long* pKernArray, - tools::Long* pScrArray, +sal_Int32 SwScriptInfo::KashidaJustify( KernArray* pKernArray, + sal_Bool* pKashidaArray, TextFrameIndex const nStt, TextFrameIndex const nLen, tools::Long nSpaceAdd ) const @@ -2193,6 +2374,11 @@ sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, { 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)) @@ -2206,9 +2392,7 @@ sal_Int32 SwScriptInfo::KashidaJustify( tools::Long* pKernArray, while ( nArrayPos < nArrayEnd ) { - pKernArray[ sal_Int32(nArrayPos) ] += nKashAdd; - if ( pScrArray ) - pScrArray[ sal_Int32(nArrayPos) ] += nKashAdd; + pKernArray->adjust(sal_Int32(nArrayPos), nKashAdd); ++nArrayPos; } nKashAdd += nSpaceAdd; @@ -2395,13 +2579,13 @@ void SwScriptInfo::MarkKashidasInvalid(sal_Int32 const nCnt, } } -TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pKernArray, - tools::Long* pScrArray, TextFrameIndex const nStt, +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(rText.getLength()), "sw.core", "String in ThaiJustify too small" ); + SAL_WARN_IF( nStt + nLen > TextFrameIndex(aText.size()), "sw.core", "String in ThaiJustify too small" ); SwTwips nNumOfTwipsToDistribute = nSpaceAdd * sal_Int32(nNumberOfBlanks) / SPACING_PRECISION_FACTOR; @@ -2411,7 +2595,7 @@ TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pK for (sal_Int32 nI = 0; nI < sal_Int32(nLen); ++nI) { - const sal_Unicode cCh = rText[sal_Int32(nStt) + nI]; + const sal_Unicode cCh = aText[sal_Int32(nStt) + nI]; // check if character is not above or below base if ( ( 0xE34 > cCh || cCh > 0xE3A ) && @@ -2427,8 +2611,8 @@ TextFrameIndex SwScriptInfo::ThaiJustify( const OUString& rText, tools::Long* pK ++nCnt; } - if ( pKernArray ) pKernArray[ nI ] += nSpaceSum; - if ( pScrArray ) pScrArray[ nI ] += nSpaceSum; + if (pKernArray) + pKernArray->adjust(nI, nSpaceSum); } return nCnt; @@ -2485,6 +2669,40 @@ TextFrameIndex SwParaPortion::GetParLen() const 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; @@ -2502,6 +2720,16 @@ const SwDropPortion *SwParaPortion::FindDropPortion() const 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 ); @@ -2558,6 +2786,32 @@ SwTwips SwTextFrame::HangingMargin() const 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) @@ -2565,9 +2819,8 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, assert((rNode.GetText().isEmpty() && rHiddenMulti.GetTotalRange().Len() == 1) || (rNode.GetText().getLength() == rHiddenMulti.GetTotalRange().Len())); - const SfxPoolItem* pItem = nullptr; - if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState( RES_CHRATR_HIDDEN, true, &pItem ) && - static_cast<const SvxCharHiddenItem*>(pItem)->GetValue() ) + const SvxCharHiddenItem* pItem = rNode.GetSwAttrSet().GetItemIfSet( RES_CHRATR_HIDDEN ); + if( pItem && pItem->GetValue() ) { rHiddenMulti.SelectAll(); } @@ -2593,7 +2846,7 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, } } - for (const SwIndex* pIndex = rNode.GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) + 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); @@ -2614,33 +2867,13 @@ void SwScriptInfo::selectHiddenTextProperty(const SwTextNode& rNode, } } - bool bHide = false; + // condition is evaluated in DocumentFieldsManager::UpdateExpFields() if (pBookmark && pBookmark->IsHidden()) { - // bookmark is marked as hidden - bHide = true; - - // bookmark is marked as hidden with conditions - if (!pBookmark->GetHideCondition().isEmpty()) - { - SwDoc& rDoc = const_cast<SwDoc&>(rNode.GetDoc()); - SwCalc aCalc(rDoc); - rDoc.getIDocumentFieldsAccess().FieldsToCalc(aCalc, rNode.GetIndex(), SAL_MAX_INT32); - - SwSbxValue aValue = aCalc.Calculate(pBookmark->GetHideCondition()); - if(!aValue.IsVoidValue()) - { - bHide = aValue.GetBool(); - } - } - } - - if (bHide) - { // intersect bookmark range with textnode range and add the intersection to rHiddenMulti - const sal_Int32 nSt = pBookmark->GetMarkStart().nContent.GetIndex(); - const sal_Int32 nEnd = pBookmark->GetMarkEnd().nContent.GetIndex(); + const sal_Int32 nSt = pBookmark->GetMarkStart().GetContentIndex(); + const sal_Int32 nEnd = pBookmark->GetMarkEnd().GetContentIndex(); if( nEnd > nSt ) { @@ -2666,7 +2899,7 @@ void SwScriptInfo::selectRedLineDeleted(const SwTextNode& rNode, MultiSelection { const SwRangeRedline* pRed = rIDRA.GetRedlineTable()[ nAct ]; - if (pRed->Start()->nNode > rNode.GetIndex()) + if (pRed->Start()->GetNode() > rNode) break; if (pRed->GetType() != RedlineType::Delete) @@ -2734,12 +2967,12 @@ TextFrameIndex SwScriptInfo::CountCJKCharacters(const OUString &rText, return nCount; } -void SwScriptInfo::CJKJustify( const OUString& rText, tools::Long* pKernArray, - tools::Long* pScrArray, TextFrameIndex const nStt, +void SwScriptInfo::CJKJustify( const OUString& rText, KernArray& rKernArray, + TextFrameIndex const nStt, TextFrameIndex const nLen, LanguageType aLang, tools::Long nSpaceAdd, bool bIsSpaceStop ) { - assert( pKernArray != nullptr && sal_Int32(nStt) >= 0 ); + assert( sal_Int32(nStt) >= 0 ); if (sal_Int32(nLen) <= 0) return; @@ -2757,9 +2990,7 @@ void SwScriptInfo::CJKJustify( const OUString& rText, tools::Long* pKernArray, if (nNext < sal_Int32(nStt + nLen) || !bIsSpaceStop) nSpaceSum += nSpaceAdd; } - pKernArray[ nI ] += nSpaceSum; - if ( pScrArray ) - pScrArray[ nI ] += nSpaceSum; + 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 index 249ed81f088f..fe8aeb861e50 100644 --- a/sw/source/core/text/porlay.hxx +++ b/sw/source/core/text/porlay.hxx @@ -75,18 +75,19 @@ public: /// Collection of SwLinePortion instances, representing one line of text. /// Typically owned by an SwParaPortion. -class SwLineLayout : public SwTextPortion +class SAL_DLLPUBLIC_RTTI 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 - sal_uInt16 m_nRealHeight; // The height resulting from line spacing and register - sal_uInt16 m_nTextHeight; // The max height of all non-FlyCnt portions in this Line + 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_bLastHyph : 1; bool m_bFly : 1; bool m_bRest : 1; bool m_bBlinking : 1; @@ -108,12 +109,12 @@ private: public: // From SwPosSize using SwPosSize::Height; - virtual void Height(const sal_uInt16 nNew, const bool bText = true) override; + 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; + SW_DLLPUBLIC SwLinePortion *GetFirstPortion() const; // Flags void ResetFlags(); @@ -123,6 +124,8 @@ public: bool IsEndHyph() const { return m_bEndHyph; } void SetMidHyph( const bool bNew ) { m_bMidHyph = bNew; } bool IsMidHyph() const { return m_bMidHyph; } + void SetLastHyph( const bool bNew ) { m_bLastHyph = bNew; } + bool IsLastHyph() const { return m_bLastHyph; } void SetFly( const bool bNew ) { m_bFly = bNew; } bool IsFly() const { return m_bFly; } void SetRest( const bool bNew ) { m_bRest = bNew; } @@ -165,10 +168,10 @@ public: // Collects the data for the line void CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ); - void SetRealHeight( sal_uInt16 nNew ) { m_nRealHeight = nNew; } - sal_uInt16 GetRealHeight() const { return m_nRealHeight; } + void SetRealHeight( SwTwips nNew ) { m_nRealHeight = nNew; } + SwTwips GetRealHeight() const { return m_nRealHeight; } - sal_uInt16 GetTextHeight() const { return m_nTextHeight; } + SwTwips GetTextHeight() const { return m_nTextHeight; } // Creates the glue chain for short lines SwMarginPortion *CalcLeftMargin(); @@ -240,6 +243,9 @@ public: 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. @@ -265,7 +271,7 @@ class SwParaPortion : public SwLineLayout 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; // contains a footnotenumberportion + bool m_bFootnoteNum : 1; // is the frame that may contain a footnotenumberportion bool m_bMargin : 1; // contains a hanging punctuation in the margin public: @@ -283,8 +289,8 @@ public: const SwRepaint& GetRepaint() const { return m_aRepaint; } SwCharRange& GetReformat() { return m_aReformat; } const SwCharRange& GetReformat() const { return m_aReformat; } - tools::Long& GetDelta() { return m_nDelta; } - const tools::Long& GetDelta() const { return m_nDelta; } + 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; } @@ -316,11 +322,17 @@ public: 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() diff --git a/sw/source/core/text/porlin.cxx b/sw/source/core/text/porlin.cxx index 035f670d464b..a420d64301ff 100644 --- a/sw/source/core/text/porlin.cxx +++ b/sw/source/core/text/porlin.cxx @@ -21,6 +21,7 @@ #include <SwPortionHandler.hxx> #include "porlin.hxx" +#include "portxt.hxx" #include "inftxt.hxx" #include "pormulti.hxx" #if OSL_DEBUG_LEVEL > 0 @@ -67,6 +68,7 @@ SwLinePortion::SwLinePortion( ) : mpNextPortion( nullptr ), mnLineLength( 0 ), mnAscent( 0 ), + mnHangingBaseline( 0 ), mnWhichPor( PortionType::NONE ), m_bJoinBorderWithPrev(false), m_bJoinBorderWithNext(false) @@ -85,16 +87,16 @@ void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf, return; const sal_uInt16 nHalfView = nViewWidth / 2; - sal_uInt16 nLastWidth = pLast->Width(); + sal_uInt16 nLastWidth = pLast->Width() + pLast->ExtraBlankWidth(); - if ( pLast->InSpaceGrp() && rInf.GetSpaceAdd() ) - nLastWidth = nLastWidth + static_cast<sal_uInt16>(pLast->CalcSpacing( rInf.GetSpaceAdd(), rInf )); + 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( ComplexTextLayoutFlags::BiDiRtl & rInf.GetOut()->GetLayoutMode() ); + bool( vcl::text::ComplexTextLayoutFlags::BiDiRtl & rInf.GetOut()->GetLayoutMode() ); Degree10 nDir = bBidiPor ? 1800_deg10 : @@ -201,6 +203,9 @@ 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; @@ -246,7 +251,7 @@ bool SwLinePortion::Format( SwTextFormatInfo &rInf ) const SwLinePortion *pLast = rInf.GetLast(); Height( pLast->Height() ); SetAscent( pLast->GetAscent() ); - const sal_uInt16 nNewWidth = static_cast<sal_uInt16>(rInf.X() + PrtWidth()); + 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() ) @@ -265,16 +270,17 @@ bool SwLinePortion::Format( SwTextFormatInfo &rInf ) void SwLinePortion::FormatEOL( SwTextFormatInfo & ) { } -void SwLinePortion::Move( SwTextPaintInfo &rInf ) +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() ); - if ( InSpaceGrp() && rInf.GetSpaceAdd() ) + SwTwips nTmp = PrtWidth() + ExtraBlankWidth(); + if ( InSpaceGrp() && rInf.GetSpaceAdd(/*bShrink=*/true) ) { - SwTwips nTmp = PrtWidth() + CalcSpacing( rInf.GetSpaceAdd(), rInf ); + nTmp += CalcSpacing( rInf.GetSpaceAdd(/*bShrink=*/true), rInf ); if( rInf.IsRotated() ) rInf.Y( rInf.Y() + ( bB2T ? -nTmp : nTmp ) ); else if ( bCounterDir ) @@ -290,19 +296,19 @@ void SwLinePortion::Move( SwTextPaintInfo &rInf ) rInf.IncKanaIdx(); } if( rInf.IsRotated() ) - rInf.Y( rInf.Y() + ( bB2T ? -PrtWidth() : PrtWidth() ) ); + rInf.Y(rInf.Y() + (bB2T ? -nTmp : nTmp)); else if ( bCounterDir ) - rInf.X( rInf.X() - PrtWidth() ); + rInf.X(rInf.X() - nTmp); else - rInf.X( rInf.X() + PrtWidth() ); + rInf.X(rInf.X() + nTmp); } - if( IsMultiPortion() && static_cast<SwMultiPortion*>(this)->HasTabulator() ) + if (IsMultiPortion() && static_cast<SwMultiPortion const*>(this)->HasTabulator()) rInf.IncSpaceIdx(); rInf.SetIdx( rInf.GetIdx() + GetLen() ); } -tools::Long SwLinePortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const +SwTwips SwLinePortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const { return 0; } @@ -314,7 +320,39 @@ bool SwLinePortion::GetExpText( const SwTextSizeInfo &, OUString & ) const void SwLinePortion::HandlePortion( SwPortionHandler& rPH ) const { - rPH.Special( GetLen(), OUString(), GetWhichPor(), Height(), Width() ); + 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 index 3d2b8dad29e4..d2298bdfb03c 100644 --- a/sw/source/core/text/porlin.hxx +++ b/sw/source/core/text/porlin.hxx @@ -18,6 +18,8 @@ */ #pragma once +#include <libxml/xmlwriter.h> + #include "possiz.hxx" #include <txttypes.hxx> #include <TextFrameIndex.hxx> @@ -28,38 +30,24 @@ 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 SwLinePortion: public SwPosSize +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; - sal_uInt16 mnAscent; // Maximum ascender + 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_(); @@ -73,13 +61,17 @@ public: TextFrameIndex GetLen() const { return mnLineLength; } void SetLen(TextFrameIndex const nLen) { mnLineLength = nLen; } void SetNextPortion( SwLinePortion *pNew ){ mpNextPortion = pNew; } - sal_uInt16 &GetAscent() { return mnAscent; } - sal_uInt16 GetAscent() const { return mnAscent; } - void SetAscent( const sal_uInt16 nNewAsc ) { mnAscent = nNewAsc; } - void PrtWidth( sal_uInt16 nNewWidth ) { Width( nNewWidth ); } - sal_uInt16 PrtWidth() const { return Width(); } - void AddPrtWidth( const sal_uInt16 nNew ) { Width( Width() + nNew ); } - void SubPrtWidth( const sal_uInt16 nNew ) { Width( Width() - nNew ); } + 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 ); @@ -152,7 +144,7 @@ public: virtual bool Format( SwTextFormatInfo &rInf ); // Is called for the line's last portion virtual void FormatEOL( SwTextFormatInfo &rInf ); - void Move( SwTextPaintInfo &rInf ); + void Move(SwTextPaintInfo & rInf) const; // For SwTextSlot virtual bool GetExpText( const SwTextSizeInfo &rInf, OUString &rText ) const; @@ -161,7 +153,7 @@ public: virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo &rInf ) const; // for text- and multi-portions - virtual tools::Long CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const; + 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; @@ -170,6 +162,11 @@ public: 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) @@ -177,9 +174,11 @@ 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; } @@ -188,9 +187,11 @@ inline SwLinePortion::SwLinePortion(const SwLinePortion &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_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext ), + m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth) { } diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx index c51fb973ad29..aaf8339b37ef 100644 --- a/sw/source/core/text/pormulti.cxx +++ b/sw/source/core/text/pormulti.cxx @@ -31,6 +31,7 @@ #include <charfmt.hxx> #include <layfrm.hxx> #include <SwPortionHandler.hxx> +#include <EnhancedPDFExportHelper.hxx> #include "pormulti.hxx" #include "inftxt.hxx" #include "itrpaint.hxx" @@ -119,7 +120,7 @@ void SwMultiPortion::CalcSize( SwTextFormatter& rLine, SwTextFormatInfo &rInf ) SetAscent( nTmp ); } -tools::Long SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const +SwTwips SwMultiPortion::CalcSpacing( tools::Long , const SwTextSizeInfo & ) const { return 0; } @@ -134,6 +135,31 @@ 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() { @@ -193,7 +219,7 @@ SwBidiPortion::SwBidiPortion(TextFrameIndex const nEnd, sal_uInt8 nLv) SetDirection( DIR_LEFT2RIGHT ); } -tools::Long SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const +SwTwips SwBidiPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo& rInf ) const { return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt(rInf)) * nSpaceAdd / SPACING_PRECISION_FACTOR; } @@ -351,12 +377,12 @@ void SwDoubleLinePortion::PaintBracket( SwTextPaintInfo &rInf, aBlank.Width( nChWidth ); aBlank.Height( m_pBracket->nHeight ); { - std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) ); + SwFont aTmpFnt( *rInf.GetFont() ); SwFontScript nAct = bOpen ? m_pBracket->nPreScript : m_pBracket->nPostScript; if( SW_SCRIPTS > nAct ) - pTmpFnt->SetActual( nAct ); - pTmpFnt->SetProportion( 100 ); - SwFontSave aSave( rInf, pTmpFnt.get() ); + aTmpFnt.SetActual( nAct ); + aTmpFnt.SetProportion( 100 ); + SwFontSave aSave( rInf, &aTmpFnt ); aBlank.Paint( rInf ); } if( bOpen ) @@ -384,21 +410,21 @@ void SwDoubleLinePortion::SetBrackets( const SwDoubleLinePortion& rDouble ) void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxWidth ) { nMaxWidth -= rInf.X(); - std::unique_ptr<SwFont> pTmpFnt( new SwFont( *rInf.GetFont() ) ); - pTmpFnt->SetProportion( 100 ); + 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 = pTmpFnt->GetActual(); + SwFontScript nActualScr = aTmpFnt.GetActual(); if( SW_SCRIPTS > m_pBracket->nPreScript ) - pTmpFnt->SetActual( m_pBracket->nPreScript ); - SwFontSave aSave( rInf, pTmpFnt.get() ); + aTmpFnt.SetActual( m_pBracket->nPreScript ); + SwFontSave aSave( rInf, &aTmpFnt ); SwPosSize aSize = rInf.GetTextSize( aStr ); m_pBracket->nAscent = rInf.GetAscent(); m_pBracket->nHeight = aSize.Height(); - pTmpFnt->SetActual( nActualScr ); + aTmpFnt.SetActual( nActualScr ); if( nMaxWidth > aSize.Width() ) { m_pBracket->nPreWidth = aSize.Width(); @@ -417,8 +443,8 @@ void SwDoubleLinePortion::FormatBrackets( SwTextFormatInfo &rInf, SwTwips& nMaxW { OUString aStr( m_pBracket->cPost ); if( SW_SCRIPTS > m_pBracket->nPostScript ) - pTmpFnt->SetActual( m_pBracket->nPostScript ); - SwFontSave aSave( rInf, pTmpFnt.get() ); + 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 ) @@ -479,7 +505,7 @@ void SwDoubleLinePortion::CalcBlanks( SwTextFormatInfo &rInf ) rInf.SetIdx( nStart ); } -tools::Long SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const +SwTwips SwDoubleLinePortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo & ) const { return HasTabulator() ? 0 : sal_Int32(GetSpaceCnt()) * nSpaceAdd / SPACING_PRECISION_FACTOR; } @@ -592,7 +618,7 @@ SwRubyPortion::SwRubyPortion( const SwMultiCreator& rCreate, const SwFont& rFnt, } OUString aStr = rRuby.GetText().copy( sal_Int32(nOffs) ); - SwFieldPortion *pField = new SwFieldPortion( aStr, std::move(pRubyFont) ); + SwFieldPortion *pField = new SwFieldPortion( std::move(aStr), std::move(pRubyFont) ); pField->SetNextOffset( nOffs ); pField->SetFollow( true ); @@ -654,9 +680,9 @@ void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf ) TextFrameIndex nSub(0); switch ( m_nAdjustment ) { - case css::text::RubyAdjust_CENTER: nRight = static_cast<sal_uInt16>(nLineDiff / 2); + case css::text::RubyAdjust_CENTER: nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2); [[fallthrough]]; - case css::text::RubyAdjust_RIGHT: nLeft = static_cast<sal_uInt16>(nLineDiff - nRight); break; + 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: @@ -683,8 +709,8 @@ void SwRubyPortion::Adjust_( SwTextFormatInfo &rInf ) } if( nLineDiff > 1 ) { - nRight = static_cast<sal_uInt16>(nLineDiff / 2); - nLeft = static_cast<sal_uInt16>(nLineDiff - nRight); + nRight = o3tl::narrowing<sal_uInt16>(nLineDiff / 2); + nLeft = o3tl::narrowing<sal_uInt16>(nLineDiff - nRight); } break; } @@ -846,12 +872,14 @@ namespace sw { } if (m_pMerged) { - while (m_CurrentExtent < m_pMerged->extents.size()) + 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()) { - while (m_CurrentHint < pHints->Count()) + auto nHintsCount = pHints->Count(); + while (m_CurrentHint < nHintsCount) { SwTextAttr const*const pHint(pHints->Get(m_CurrentHint)); if (rExtent.nEnd < pHint->GetStart()) @@ -867,7 +895,7 @@ namespace sw { } } ++m_CurrentExtent; - if (m_CurrentExtent < m_pMerged->extents.size() && + if (m_CurrentExtent < nExtentsSize && rExtent.pNode != m_pMerged->extents[m_CurrentExtent].pNode) { m_CurrentHint = 0; // reset @@ -904,7 +932,7 @@ namespace sw { // 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::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos, +std::optional<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex &rPos, SwMultiPortion const * pMulti ) const { SwScriptInfo& rSI = const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo(); @@ -936,26 +964,26 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & { rPos = bFieldBidi ? rPos + TextFrameIndex(1) : rSI.NextDirChg(rPos, &nCurrLevel); if (TextFrameIndex(COMPLETE_STRING) == rPos) - return nullptr; - std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); - pRet->pItem = nullptr; - pRet->pAttr = nullptr; - pRet->nStartOfAttr = TextFrameIndex(-1); - pRet->nId = SwMultiCreatorId::Bidi; - pRet->nLevel = nCurrLevel + 1; - return pRet; + 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 nullptr; + return {}; // need the node that contains input rPos std::pair<SwTextNode const*, sal_Int32> startPos(m_pFrame->MapViewToModel(rPos)); const SvxCharRotateItem* pActiveRotateItem(nullptr); - const SfxPoolItem* pNodeRotateItem(nullptr); + const SvxCharRotateItem* pNodeRotateItem(nullptr); const SvxTwoLinesItem* pActiveTwoLinesItem(nullptr); - const SfxPoolItem* pNodeTwoLinesItem(nullptr); + const SvxTwoLinesItem* pNodeTwoLinesItem(nullptr); SwTextAttr const* pActiveTwoLinesHint(nullptr); SwTextAttr const* pActiveRotateHint(nullptr); const SwTextAttr *pRuby = nullptr; @@ -1004,59 +1032,57 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } } } - else if (pNode) // !pAttr && pNode means the node changed + // !pAttr && pNode means the node changed + if (startPos.first->GetIndex() < pNode->GetIndex()) + { + break; // only one node initially + } + if (startPos.first->GetIndex() == pNode->GetIndex()) { - if (startPos.first->GetIndex() < pNode->GetIndex()) + iterAtStartOfNode.Assign(iter); + if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( + RES_CHRATR_ROTATE, true, &pNodeRotateItem) && + pNodeRotateItem->GetValue()) { - break; // only one node initially + pActiveRotateItem = pNodeRotateItem; } - if (startPos.first->GetIndex() == pNode->GetIndex()) + else { - iterAtStartOfNode.Assign(iter); - if (SfxItemState::SET == pNode->GetSwAttrSet().GetItemState( - RES_CHRATR_ROTATE, true, &pNodeRotateItem) && - static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue()) - { - pActiveRotateItem = static_cast<const SvxCharRotateItem*>(pNodeRotateItem); - } - else - { - pNodeRotateItem = nullptr; - } - if (SfxItemState::SET == startPos.first->GetSwAttrSet().GetItemState( - RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem) && - static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetValue()) - { - pActiveTwoLinesItem = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem); - } - else - { - pNodeTwoLinesItem = nullptr; - } + 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 nullptr; + 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()); - std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); - pRet->pItem = nullptr; - pRet->pAttr = pRuby; - pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); - pRet->nId = SwMultiCreatorId::Ruby; - pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; - return pRet; + 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 && pNodeTwoLinesItem == pActiveTwoLinesItem && + (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... - std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); + SwMultiCreator aRet; // We note the endpositions of the 2-line attributes in aEnd as stack std::deque<TextFrameIndex> aEnd; @@ -1068,31 +1094,31 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & if (pActiveTwoLinesHint) { - pRet->pItem = nullptr; - pRet->pAttr = pActiveTwoLinesHint; - pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); + 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 = static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetEndBracket() == + bOn = pNodeTwoLinesItem->GetEndBracket() == pActiveTwoLinesItem->GetEndBracket() && - static_cast<const SvxTwoLinesItem*>(pNodeTwoLinesItem)->GetStartBracket() == + pNodeTwoLinesItem->GetStartBracket() == pActiveTwoLinesItem->GetStartBracket(); } else { - aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End())); + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End())); } } else { - pRet->pItem = pNodeTwoLinesItem; - pRet->pAttr = nullptr; - pRet->nStartOfAttr = TextFrameIndex(-1); + aRet.pItem = pNodeTwoLinesItem; + aRet.pAttr = nullptr; + aRet.nStartOfAttr = TextFrameIndex(-1); aEnd.push_front(m_pFrame->MapModelToView(startPos.first, startPos.first->Len())); } - pRet->nId = SwMultiCreatorId::Double; - pRet->nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; + aRet.nId = SwMultiCreatorId::Double; + aRet.nLevel = GetTextFrame()->IsRightToLeft() ? 1 : 0; // pActiveTwoLinesHint is the last 2-line-attribute, which contains // the actual position. @@ -1134,9 +1160,8 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } else { - pNodeTwoLinesItem = nullptr; - pNode->GetSwAttrSet().GetItemState( - RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem); + 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 @@ -1171,7 +1196,7 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } // A ruby attribute stops the 2-line immediately if (pTmp && RES_TXTATR_CJK_RUBY == pTmp->Which()) - return pRet; + return aRet; if (pTmp ? lcl_Has2Lines(*pTmp, pActiveTwoLinesItem, bTwo) : lcl_Check2Lines(pNodeTwoLinesItem, pActiveTwoLinesItem, bTwo)) { // We have an interesting attribute... @@ -1198,15 +1223,15 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } if( bOn && !aEnd.empty() ) rPos = aEnd.back(); - return pRet; + return aRet; } if (pActiveRotateHint || - (pNodeRotateItem && pNodeRotateItem == pActiveRotateItem && + (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... - std::unique_ptr<SwMultiCreator> pRet(new SwMultiCreator); - pRet->nId = SwMultiCreatorId::Rotate; + SwMultiCreator aRet; + aRet.nId = SwMultiCreatorId::Rotate; // We note the endpositions of the 2-line attributes in aEnd as stack std::deque<TextFrameIndex> aEnd; @@ -1239,9 +1264,8 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } else { - pNodeTwoLinesItem = nullptr; - pNode->GetSwAttrSet().GetItemState( - RES_CHRATR_TWO_LINES, true, &pNodeTwoLinesItem); + 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 @@ -1299,25 +1323,25 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & bOn = true; if (pActiveRotateHint) { - pRet->pItem = nullptr; - pRet->pAttr = pActiveRotateHint; - pRet->nStartOfAttr = m_pFrame->MapModelToView(startPos.first, pRet->pAttr->GetStart()); + 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 = static_cast<const SvxCharRotateItem*>(pNodeRotateItem)->GetValue() == + bOn = pNodeRotateItem->GetValue() == pActiveRotateItem->GetValue(); } else { - aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *pRet->pAttr->End())); + aEnd.push_front(m_pFrame->MapModelToView(startPos.first, *aRet.pAttr->End())); } } else { - pRet->pItem = pNodeRotateItem; - pRet->pAttr = nullptr; - pRet->nStartOfAttr = TextFrameIndex(-1); + 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; ; ) @@ -1340,9 +1364,8 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & } else { - pNodeRotateItem = nullptr; - pNode->GetSwAttrSet().GetItemState( - RES_CHRATR_ROTATE, true, &pNodeRotateItem); + 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 @@ -1394,9 +1417,9 @@ std::unique_ptr<SwMultiCreator> SwTextSizeInfo::GetMultiCreator(TextFrameIndex & rPos = aEnd.back(); if( rPos > n2Start ) rPos = n2Start; - return pRet; + return aRet; } - return nullptr; + return {}; } namespace { @@ -1557,7 +1580,7 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, SwSpaceManipulator aManip( GetInfo(), rMulti ); - std::unique_ptr<SwFontSave> pFontSave; + std::optional<SwFontSave> oFontSave; std::unique_ptr<SwFont> pTmpFnt; if( rMulti.IsDouble() ) @@ -1568,16 +1591,19 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, SetPropFont( 50 ); pTmpFnt->SetProportion( GetPropFont() ); } - pFontSave.reset(new SwFontSave( GetInfo(), pTmpFnt.get(), this )); + oFontSave.emplace( GetInfo(), pTmpFnt.get(), this ); } else { - pFontSave = nullptr; 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() ); @@ -1636,8 +1662,18 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, OSL_ENSURE( nullptr == GetInfo().GetUnderFnt() || rMulti.IsBidi(), " Only BiDi portions are allowed to use the common underlining font" ); - if ( rMulti.IsRuby() ) + ::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 { @@ -1679,7 +1715,7 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, } else if ( rMulti.IsRuby() && rMulti.OnRight() && GetInfo().IsRuby() ) { - SwTwips nLineDiff = std::max(( rMulti.GetRoot().Height() - pPor->Width() ) / 2, 0 ); + 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() ); @@ -1740,7 +1776,15 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, 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() ); + } + + if (GetFnt()->IsURL() && pPor->InTextGrp()) + GetInfo().NotifyURL(*pPor); bFirst &= !pPor->GetLen(); if( pNext || !pPor->IsMarginPortion() ) @@ -1799,9 +1843,20 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, // 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 ); @@ -1817,6 +1872,10 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, 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() ); @@ -1828,7 +1887,7 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, // Restore the saved values GetInfo().X( nOldX ); GetInfo().SetLen( nOldLen ); - pFontSave.reset(); + oFontSave.reset(); pTmpFnt.reset(); SetPropFont( 0 ); } @@ -1898,16 +1957,17 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, } SeekAndChg( rInf ); - std::unique_ptr<SwFontSave> xFontSave; + std::optional<SwFontSave> oFontSave; + std::unique_ptr<SwFont> xTmpFont; if( rMulti.IsDouble() ) { - SwFont* pTmpFnt = new SwFont( *rInf.GetFont() ); + xTmpFont.reset(new SwFont( *rInf.GetFont() )); if( rMulti.IsDouble() ) { SetPropFont( 50 ); - pTmpFnt->SetProportion( GetPropFont() ); + xTmpFont->SetProportion( GetPropFont() ); } - xFontSave.reset(new SwFontSave(rInf, pTmpFnt, this)); + oFontSave.emplace(rInf, xTmpFont.get(), this); } SwLayoutModeModifier aLayoutModeModifier( *GetInfo().GetOut() ); @@ -1995,7 +2055,7 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, // save some values const OUString* pOldText = &(rInf.GetText()); const SwTwips nOldPaintOfst = rInf.GetPaintOfst(); - std::shared_ptr<vcl::TextLayoutCache> const pOldCachedVclData(rInf.GetCachedVclData()); + 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())) ); @@ -2285,6 +2345,9 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, // 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() ) @@ -2354,10 +2417,15 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf, SeekAndChg( rInf ); delete pFirstRest; delete pSecondRest; - xFontSave.reset(); + 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 @@ -2453,7 +2521,7 @@ SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine, return pRest; nPosition = nMultiPos + pHelpMulti->GetLen(); - std::unique_ptr<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr ); + std::optional<SwMultiCreator> pCreate = GetInfo().GetMultiCreator( nMultiPos, nullptr ); if ( !pCreate ) { @@ -2466,19 +2534,19 @@ SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine, if (!pCreate) return pRest; - if( pRest || nMultiPos > nPosition || ( pHelpMulti->IsRuby() && - static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset() < TextFrameIndex(COMPLETE_STRING))) + 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( pHelpMulti->IsRuby() ) + 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, static_cast<const SwRubyPortion*>(pHelpMulti)->GetRubyOffset(), + nMultiPos, nRubyOffset, GetInfo() ); } else if( pHelpMulti->HasRotation() ) @@ -2510,7 +2578,7 @@ SwLinePortion* SwTextFormatter::MakeRestPortion( const SwLineLayout* pLine, SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor, SwMultiPortion* pMulti, SwTwips nY, - sal_uInt16& nX, + SwTwips& nX, TextFrameIndex const nCurrStart, tools::Long nSpaceAdd ) : pTextCursor(pCursor), @@ -2544,7 +2612,7 @@ SwTextCursorSave::SwTextCursorSave( SwTextCursor* pCursor, } if( nSpaceAdd > 0 && !pMulti->HasTabulator() ) - pCursor->m_pCurr->Width( static_cast<sal_uInt16>(nWidth + nSpaceAdd * sal_Int32(nSpaceCnt) / SPACING_PRECISION_FACTOR) ); + 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 diff --git a/sw/source/core/text/pormulti.hxx b/sw/source/core/text/pormulti.hxx index c94dd3125629..e5a3da2b329c 100644 --- a/sw/source/core/text/pormulti.hxx +++ b/sw/source/core/text/pormulti.hxx @@ -77,7 +77,7 @@ struct SwBracket // or phonetics (ruby) // or combined characters // or a rotated portion. -class SwMultiPortion : public SwLinePortion +class SAL_DLLPUBLIC_RTTI SwMultiPortion : public SwLinePortion { SwLineLayout m_aRoot; // One or more lines bool m_bTab1 :1; // First line tabulator @@ -136,7 +136,7 @@ public: void ActualizeTabulator(); virtual void Paint( const SwTextPaintInfo &rInf ) const override; - virtual tools::Long CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &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 @@ -149,6 +149,9 @@ public: // 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 @@ -180,7 +183,7 @@ public: TextFrameIndex GetSmallerSpaceCnt() const { return ( m_nLineDiff < 0 ) ? m_nBlank1 : m_nBlank2; } - virtual tools::Long CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + virtual SwTwips CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; virtual bool ChgSpaceAdd( SwLineLayout* pCurr, tools::Long nSpaceAdd ) const override; }; @@ -224,7 +227,7 @@ public: // Get number of blanks for justified alignment TextFrameIndex GetSpaceCnt(const SwTextSizeInfo &rInf) const; // Calculates extra spacing based on number of blanks - virtual tools::Long CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const override; + 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; }; @@ -241,7 +244,7 @@ class SwTextCursorSave bool bSpaceChg; public: SwTextCursorSave( SwTextCursor* pTextCursor, SwMultiPortion* pMulti, - SwTwips nY, sal_uInt16& nX, TextFrameIndex nCurrStart, tools::Long nSpaceAdd); + SwTwips nY, SwTwips& nX, TextFrameIndex nCurrStart, tools::Long nSpaceAdd); ~SwTextCursorSave(); }; diff --git a/sw/source/core/text/porref.cxx b/sw/source/core/text/porref.cxx index 502b681c0803..a64f2df1fe1d 100644 --- a/sw/source/core/text/porref.cxx +++ b/sw/source/core/text/porref.cxx @@ -45,7 +45,7 @@ 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() && SwViewOption::IsFieldShadings() && + if( !Width() && rInf.OnWin() && rInf.GetOpt().IsFieldShadings() && !rInf.GetOpt().IsReadonly() && !rInf.GetOpt().IsPagePreview() ) { if( !m_nViewWidth ) diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx index 6f578e0744cd..ff1e029ae141 100644 --- a/sw/source/core/text/porrst.cxx +++ b/sw/source/core/text/porrst.cxx @@ -22,7 +22,10 @@ #include <editeng/escapementitem.hxx> #include <editeng/lrspitem.hxx> #include <editeng/pgrditem.hxx> +#include <editeng/fontitem.hxx> #include <vcl/svapp.hxx> +#include <comphelper/scopeguard.hxx> + #include <viewsh.hxx> #include <viewopt.hxx> #include <ndtxt.hxx> @@ -39,12 +42,19 @@ #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 <IDocumentLayoutAccess.hxx> #include <crsrsh.hxx> +#include <swtypes.hxx> +#include <strings.hrc> +#include <flyfrms.hxx> +#include <bodyfrm.hxx> SwTmpEndPortion::SwTmpEndPortion( const SwLinePortion &rPortion, const FontLineStyle eUL, @@ -66,6 +76,16 @@ void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const SwFont aFont(*pOldFnt); + const SwDoc& rDoc = rInf.GetTextFrame()->GetDoc(); + if (aFont.IsSymbol(rDoc.getIDocumentLayoutAccess().GetCurrentViewShell())) + { + const SvxFontItem& rFontItem = rDoc.GetDefault(RES_CHRATR_FONT); + aFont.SetName( rFontItem.GetFamilyName(), SwFontScript::Latin ); + aFont.SetStyleName( rFontItem.GetStyleName(), SwFontScript::Latin ); + aFont.SetFamily( rFontItem.GetFamily(), SwFontScript::Latin ); + aFont.SetPitch( rFontItem.GetPitch(), SwFontScript::Latin ); + aFont.SetCharSet( rFontItem.GetCharSet(), SwFontScript::Latin ); + } // 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()). @@ -93,12 +113,19 @@ void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt)); } -SwBreakPortion::SwBreakPortion( const SwLinePortion &rPortion ) +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 @@ -117,37 +144,58 @@ 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) + if (m_eRedline == RedlineType::None) + return; + + sal_Int16 nNoBreakWidth = rInf.GetTextSize(S_NOBREAK_FOR_REDLINE).Width(); + if ( nNoBreakWidth > 0 ) { - 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); + // 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(); + const SwFont* pOldFnt = rInf.GetFont(); - SwFont aFont(*pOldFnt); + SwFont aFont(*pOldFnt); - if (m_eRedline == RedlineType::Delete) - aFont.SetUnderline( LINESTYLE_NONE ); - else - aFont.SetStrikeout( STRIKEOUT_NONE ); + if (m_eRedline == RedlineType::Delete) + aFont.SetUnderline( LINESTYLE_NONE ); + else + aFont.SetStrikeout( STRIKEOUT_NONE ); - const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont); + const_cast<SwTextPaintInfo&>(rInf).SetFont(&aFont); - rInf.DrawText(aBuf.makeStringAndClear(), *this); + rInf.DrawText(aBuf.makeStringAndClear(), *this); - const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt)); - } + const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt)); } } @@ -156,6 +204,29 @@ 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 ); @@ -167,6 +238,21 @@ 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 ) @@ -243,7 +329,7 @@ SwArrowPortion::SwArrowPortion( const SwLinePortion &rPortion ) : SwArrowPortion::SwArrowPortion( const SwTextPaintInfo &rInf ) : m_bLeft( false ) { - Height( static_cast<sal_uInt16>(rInf.GetTextFrame()->getFramePrintArea().Height()) ); + 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() + @@ -338,7 +424,18 @@ bool SwTextFrame::FormatEmpty() bool bCollapse = EmptyHeight( ) == 1 && IsCollapse( ); // sw_redlinehide: just disable FormatEmpty optimisation for now - if (HasFollow() || GetMergedPara() || GetTextNodeFirst()->GetpSwpHints() || + // 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() ) ) @@ -352,18 +449,27 @@ bool SwTextFrame::FormatEmpty() const SvxLineSpacingItem &rSpacing = aSet.GetLineSpacing(); if( !bCollapse && ( SvxLineSpaceRule::Min == rSpacing.GetLineSpaceRule() || SvxLineSpaceRule::Fix == rSpacing.GetLineSpaceRule() || - aSet.GetLRSpace().IsAutoFirst() ) ) + aSet.GetFirstLineIndent().IsAutoFirst())) + { return false; + } SwTextFly aTextFly( this ); SwRect aRect; bool bFirstFlyCheck = 0 != getFramePrintArea().Height(); if ( !bCollapse && bFirstFlyCheck && - aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) ) + 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 (SwIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex(); + for (SwContentIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex(); pIndex; pIndex = pIndex->GetNext()) { sw::mark::IMark const*const pMark = pIndex->GetMark(); @@ -384,7 +490,26 @@ bool SwTextFrame::FormatEmpty() } SwRectFnSet aRectFnSet(this); - const SwTwips nChg = nHeight - aRectFnSet.GetHeight(getFramePrintArea()); + 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 ); @@ -455,7 +580,7 @@ bool SwTextFrame::FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff ) pOut = GetDoc().getIDocumentDeviceAccess().getReferenceDevice( true ); if( pSh && !pOut ) - pOut = pSh->GetWin(); + pOut = pSh->GetWin()->GetOutDev(); if( !pOut ) pOut = Application::GetDefaultDevice(); @@ -493,7 +618,7 @@ bool SwTextFrame::FillRegister( SwTwips& rRegStart, sal_uInt16& rRegDiff ) nTmp /= 100; if( !nTmp ) ++nTmp; - rRegDiff = static_cast<sal_uInt16>(nTmp); + rRegDiff = o3tl::narrowing<sal_uInt16>(nTmp); nNetHeight = rRegDiff; break; } @@ -527,7 +652,7 @@ void SwHiddenTextPortion::Paint( const SwTextPaintInfo & rInf) const { #ifdef DBG_UTIL OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut()); - Color aCol( SwViewOption::GetFieldShadingsColor() ); + Color aCol( rInf.GetOpt().GetFieldShadingsColor() ); Color aOldColor( pOut->GetFillColor() ); pOut->SetFillColor( aCol ); Point aPos( rInf.GetPos() ); @@ -549,10 +674,10 @@ bool SwHiddenTextPortion::Format( SwTextFormatInfo &rInf ) return false; }; -bool SwControlCharPortion::DoPaint(SwTextPaintInfo const&, +bool SwControlCharPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo, OUString & rOutString, SwFont & rTmpFont, int &) const { - if (mcChar == CHAR_WJ || !SwViewOption::IsFieldShadings()) + if (mcChar == CHAR_WJ || !rTextPaintInfo.GetOpt().IsFieldShadings()) { return false; } @@ -580,6 +705,7 @@ bool SwControlCharPortion::DoPaint(SwTextPaintInfo const&, 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; @@ -594,10 +720,11 @@ bool SwBookmarkPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo, 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 - auto const nFactor = (Height() * 95) / aSize.Height(); + 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(NON_PRINTING_CHARACTER_COLOR); + rFont.SetColor(rTextPaintInfo.GetOpt().GetFieldShadingsColor()); // reset these to default... rFont.SetAlign(ALIGN_BASELINE); rFont.SetUnderline(LINESTYLE_NONE); @@ -668,6 +795,151 @@ void SwControlCharPortion::Paint( const SwTextPaintInfo &rInf ) const 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_aColors.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_aColors ) + { + // 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_aColors ) + { + 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_aColors.empty()) + { + OUStringBuffer aStr; + for (const auto& rColor : m_aColors) + { + 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(); diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx index c92c50db3faa..56108cd08846 100644 --- a/sw/source/core/text/porrst.hxx +++ b/sw/source/core/text/porrst.hxx @@ -24,6 +24,7 @@ #include <txttypes.hxx> #include <txtfrm.hxx> #include <svx/ctredlin.hxx> +#include <scriptinfo.hxx> #include "porlin.hxx" #include "portxt.hxx" @@ -53,12 +54,20 @@ public: 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 ); + 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; @@ -69,8 +78,13 @@ public: // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override; - static constexpr OUStringLiteral S_NOBREAK_FOR_REDLINE = u"\u00A0"; + 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 @@ -97,6 +111,7 @@ public: 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; @@ -146,8 +161,8 @@ class SwControlCharPortion : public SwLinePortion private: mutable sal_uInt16 mnViewWidth; // used to cache a calculated value - mutable sal_uInt16 mnHalfCharWidth; // used to cache a calculated value protected: + mutable sal_uInt16 mnHalfCharWidth; // used to cache a calculated value sal_Unicode mcChar; public: @@ -169,17 +184,39 @@ public: /// 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_aColors; + // number of MarkKind marks + sal_Int16 m_nStart, m_nEnd, m_nPoint; + bool m_bHasCustomColor; + public: - explicit SwBookmarkPortion(sal_Unicode const cChar) - : SwControlCharPortion(cChar) + explicit SwBookmarkPortion(sal_Unicode const cChar, std::vector<std::tuple<SwScriptInfo::MarkKind, Color, OUString>> aColors) + : SwControlCharPortion(cChar), m_aColors(std::move(aColors)), m_nStart(0), m_nEnd(0), m_nPoint(0), m_bHasCustomColor(false) { SetWhichPor(PortionType::Bookmark); SetLen(TextFrameIndex(0)); + for (const auto& it : m_aColors) + { + 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/portox.cxx b/sw/source/core/text/portox.cxx index 982ec4f5fb25..bd72e83b042d 100644 --- a/sw/source/core/text/portox.cxx +++ b/sw/source/core/text/portox.cxx @@ -48,7 +48,7 @@ sal_uInt16 SwIsoToxPortion::GetViewWidth( const SwTextSizeInfo &rInf ) const // nViewWidth need to be calculated if( !Width() && rInf.OnWin() && !rInf.GetOpt().IsPagePreview() && - !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() ) + !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() ) { if( !m_nViewWidth ) pThis->m_nViewWidth = rInf.GetTextSize(OUString(' ')).Width(); diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index ed9d3c517bb5..c36b4fed21ef 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -113,7 +113,7 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, { if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() ) { - const sal_Int32 nKashRes = pSI->KashidaJustify( nullptr, nullptr, nPos, nEnd - nPos ); + 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) @@ -129,7 +129,7 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf, if ( LANGUAGE_THAI == aLang ) { - nCnt = SwScriptInfo::ThaiJustify( *pStr, nullptr, nullptr, nPos, nEnd - nPos ); + nCnt = SwScriptInfo::ThaiJustify(*pStr, nullptr, nPos, nEnd - nPos); const SwLinePortion* pPor = rPor.GetNextPortion(); if ( pPor && ( pPor->IsKernPortion() || @@ -221,7 +221,7 @@ 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 = static_cast<sal_uInt16>(rInf.Width() - rInf.X()); + const sal_uInt16 nLineWidth = o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X()); TextFrameIndex nLen = rGuess.CutPos() - rInf.GetIdx(); if (nLen > TextFrameIndex(0)) { @@ -302,7 +302,44 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) } SwTextGuess aGuess; - const bool bFull = !aGuess.Guess( *this, rInf, Height() ); + 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 inside a word with hyphenation) + // TODO: handle the case, if the line contains extra amount of spaces + if ( + // no automatic hyphenation + !aGuess.HyphWord().is() && + // no hyphenation at soft hyphen + aGuess.BreakPos() < TextFrameIndex(rInf.GetText().getLength()) && + rInf.GetText()[sal_Int32(aGuess.BreakPos())] != CHAR_SOFTHYPHEN ) + { + ++nSpacesInLine; + } + + if ( nSpacesInLine > 0 ) + bFull = !aGuess.Guess( *this, rInf, Height(), nSpacesInLine ); + } // these are the possible cases: // A Portion fits to current line @@ -321,6 +358,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) if ( !bFull ) { Width( aGuess.BreakWidth() ); + ExtraBlankWidth(aGuess.ExtraBlankWidth()); // Caution! if( !InExpGrp() || InFieldGrp() ) SetLen( rInf.GetLen() ); @@ -336,7 +374,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) new SwKernPortion( *this, nKern ); } // special case: hanging portion - else if( bFull && aGuess.GetHangingPortion() ) + else if( aGuess.GetHangingPortion() ) { Width( aGuess.BreakWidth() ); SetLen( aGuess.BreakPos() - rInf.GetIdx() ); @@ -393,12 +431,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) ! rInf.GetLast()->IsErgoSumPortion() && lcl_HasContent(*static_cast<SwFieldPortion*>(rInf.GetLast()),rInf ) ) ) ) ) { - // GetLineWidth() takes care of DocumentSettingId::TAB_OVER_MARGIN. - if (aGuess.BreakWidth() <= rInf.GetLineWidth()) - Width( aGuess.BreakWidth() ); - else - // this actually should not happen - Width( sal_uInt16(rInf.Width() - rInf.X()) ); + Width( aGuess.BreakWidth() ); SetLen( aGuess.BreakPos() - rInf.GetIdx() ); @@ -409,7 +442,14 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) { 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 @@ -430,7 +470,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) } else // case C2, last exit - BreakCut( rInf, aGuess ); + BreakCut(rInf, aGuess); } return bFull; @@ -491,9 +531,9 @@ void SwTextPortion::FormatEOL( SwTextFormatInfo &rInf ) 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 ); + SwHolePortion* pHole = new SwHolePortion(*this); + pHole->SetBlankWidth(nBlankSize); + pHole->SetLen(nHoleLen); Insert( pHole ); } @@ -543,6 +583,8 @@ void SwTextPortion::Paint( const SwTextPaintInfo &rInf ) const 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 ); @@ -584,7 +626,10 @@ TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf, if ( InExpGrp() || PortionType::InputField == GetWhichPor() ) { - if( !IsBlankPortion() && !InNumberGrp() && !IsCombinedPortion() ) + 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. @@ -608,7 +653,7 @@ TextFrameIndex SwTextPortion::GetSpaceCnt(const SwTextSizeInfo &rInf, return nCnt; } -tools::Long SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const +SwTwips SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) const { TextFrameIndex nCnt(0); @@ -621,7 +666,10 @@ tools::Long SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeI if ( InExpGrp() || PortionType::InputField == GetWhichPor() ) { - if( !IsBlankPortion() && !InNumberGrp() && !IsCombinedPortion() ) + 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. @@ -662,16 +710,16 @@ tools::Long SwTextPortion::CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeI } } - return sal_Int32(nCnt) * nSpaceAdd / SPACING_PRECISION_FACTOR; + 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(), Height(), Width() ); + rPH.Text( GetLen(), GetWhichPor() ); } SwTextInputFieldPortion::SwTextInputFieldPortion() - : SwTextPortion() { SetWhichPor( PortionType::InputField ); } @@ -700,12 +748,12 @@ void SwTextInputFieldPortion::Paint( const SwTextPaintInfo &rInf ) const if (aIntersect.HasArea() && rInf.OnWin() - && SwViewOption::IsFieldShadings() + && rInf.GetOpt().IsFieldShadings() && !rInf.GetOpt().IsPagePreview()) { OutputDevice* pOut = const_cast<OutputDevice*>(rInf.GetOut()); - pOut->Push(PushFlags::LINECOLOR | PushFlags::FILLCOLOR); - pOut->SetFillColor(SwViewOption::GetFieldShadingsColor()); + pOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + pOut->SetFillColor(rInf.GetOpt().GetFieldShadingsColor()); pOut->SetLineColor(); pOut->DrawRect(aIntersect.SVRect()); pOut->Pop(); @@ -747,25 +795,44 @@ SwHolePortion::SwHolePortion( const SwTextPortion &rPor ) { 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 (!SwTaggedPDFHelper::IsExportTaggedPDF( *rInf.GetOut()) ) + 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::unique_ptr<SwFontSave> pFontSave; + std::optional<SwFontSave> oFontSave; if( pOrigFont->GetUnderline() != LINESTYLE_NONE || pOrigFont->GetOverline() != LINESTYLE_NONE || pOrigFont->GetStrikeout() != STRIKEOUT_NONE ) @@ -774,12 +841,21 @@ void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const pHoleFont->SetUnderline( LINESTYLE_NONE ); pHoleFont->SetOverline( LINESTYLE_NONE ); pHoleFont->SetStrikeout( STRIKEOUT_NONE ); - pFontSave.reset(new SwFontSave( rInf, pHoleFont.get() )); + oFontSave.emplace( rInf, pHoleFont.get() ); } - rInf.DrawText(" ", *this, TextFrameIndex(0), TextFrameIndex(1)); + 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()); + } - pFontSave.reset(); + oFontSave.reset(); pHoleFont.reset(); } @@ -793,6 +869,18 @@ 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! diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx index ec44a2bafc76..c826395272e1 100644 --- a/sw/source/core/text/portxt.hxx +++ b/sw/source/core/text/portxt.hxx @@ -38,7 +38,7 @@ public: 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 tools::Long CalcSpacing( tools::Long nSpaceAdd, const SwTextSizeInfo &rInf ) 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; @@ -69,10 +69,14 @@ public: 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 diff --git a/sw/source/core/text/possiz.hxx b/sw/source/core/text/possiz.hxx index fc31ffc11e39..f66bd1988ca4 100644 --- a/sw/source/core/text/possiz.hxx +++ b/sw/source/core/text/possiz.hxx @@ -19,22 +19,22 @@ #pragma once #include <tools/gen.hxx> -#include <sal/types.h> +#include <swtypes.hxx> // Compared to the SV sizes SwPosSize is always positive class SwPosSize { - sal_uInt16 m_nWidth; - sal_uInt16 m_nHeight; + SwTwips m_nWidth; + SwTwips m_nHeight; public: - SwPosSize( const sal_uInt16 nW = 0, const sal_uInt16 nH = 0 ) + SwPosSize( const SwTwips nW = 0, const SwTwips nH = 0 ) : m_nWidth(nW) , m_nHeight(nH) { } explicit SwPosSize( const Size &rSize ) - : m_nWidth(sal_uInt16(rSize.Width())) - ,m_nHeight(sal_uInt16(rSize.Height())) + : m_nWidth(SwTwips(rSize.Width())) + , m_nHeight(SwTwips(rSize.Height())) { } #if defined(__COVERITY__) @@ -46,25 +46,25 @@ public: SwPosSize(SwPosSize &&) = default; SwPosSize & operator =(SwPosSize const &) = default; SwPosSize & operator =(SwPosSize &&) = default; - sal_uInt16 Height() const { return m_nHeight; } - virtual void Height(const sal_uInt16 nNew, const bool = true) { m_nHeight = nNew; } - sal_uInt16 Width() const { return m_nWidth; } - void Width( const sal_uInt16 nNew ) { m_nWidth = nNew; } + 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 = sal_uInt16(rSize.Width()); - m_nHeight = sal_uInt16(rSize.Height()); + m_nWidth = SwTwips(rSize.Width()); + m_nHeight = SwTwips(rSize.Height()); } void SvXSize( const Size &rSize ) { - m_nHeight = sal_uInt16(rSize.Width()); - m_nWidth = sal_uInt16(rSize.Height()); + m_nHeight = SwTwips(rSize.Width()); + m_nWidth = SwTwips(rSize.Height()); } SwPosSize& operator=( const Size &rSize ) { - m_nWidth = sal_uInt16(rSize.Width()); - m_nHeight = sal_uInt16(rSize.Height()); + m_nWidth = SwTwips(rSize.Width()); + m_nHeight = SwTwips(rSize.Height()); return *this; } }; diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx index faeb0fbbaa8d..afa87aba0963 100644 --- a/sw/source/core/text/redlnitr.cxx +++ b/sw/source/core/text/redlnitr.cxx @@ -37,7 +37,7 @@ #include <IDocumentLayoutAccess.hxx> #include <IDocumentMarkAccess.hxx> #include <IMark.hxx> -#include <bookmrk.hxx> +#include <bookmark.hxx> #include <rootfrm.hxx> #include <breakit.hxx> #include <vcl/commandevent.hxx> @@ -47,6 +47,12 @@ #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; @@ -59,12 +65,15 @@ private: 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::unique_ptr<SwPosition>> m_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; @@ -74,13 +83,15 @@ public: SwPosition const* GetEndPos() const { return m_pEndPos; } HideIterator(SwTextNode & rTextNode, - bool const isHideRedlines, sw::FieldmarkMode const eMode) + 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(m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any)) + , m_RedlineIndex(isHideRedlines ? m_rIDRA.GetRedlinePos(rTextNode, RedlineType::Any) : SwRedlineTable::npos) , m_pStartPos(nullptr) , m_pEndPos(&m_Start) { @@ -101,22 +112,21 @@ public: { SwRangeRedline const*const pRed = m_rIDRA.GetRedlineTable()[m_RedlineIndex]; - if (m_pEndPos->nNode.GetIndex() < pRed->Start()->nNode.GetIndex()) + if (m_pEndPos->GetNodeIndex() < pRed->Start()->GetNodeIndex()) break; if (pRed->GetType() != RedlineType::Delete) continue; - SwPosition const*const pStart(pRed->Start()); - SwPosition const*const pEnd(pRed->End()); + auto [pStart, pEnd] = pRed->StartEnd(); // SwPosition* if (*pStart == *pEnd) { // only allowed while moving (either way?) // assert(IDocumentRedlineAccess::IsHideChanges(rIDRA.GetRedlineFlags())); continue; } - if (pStart->nNode.GetNode().IsTableNode()) + if (pStart->GetNode().IsTableNode()) { - assert(pEnd->nNode == m_Start.nNode && pEnd->nContent.GetIndex() == 0); + assert(pEnd->GetNode() == m_Start.GetNode() && pEnd->GetContentIndex() == 0); continue; // known pathology, ignore it } if (*m_pEndPos <= *pStart) @@ -134,16 +144,16 @@ public: sal_Unicode const magic(m_eFieldmarkMode == sw::FieldmarkMode::ShowResult ? CH_TXT_ATR_FIELDSTART : CH_TXT_ATR_FIELDSEP); - SwTextNode* pTextNode = m_pEndPos->nNode.GetNode().GetTextNode(); + SwTextNode* pTextNode = m_pEndPos->GetNode().GetTextNode(); sal_Int32 const nPos = pTextNode ? pTextNode->GetText().indexOf( - magic, m_pEndPos->nContent.GetIndex()) : -1; + 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.getFieldmarkFor(*m_oNextFieldmarkHide)); + : m_rIDMA.getInnerFieldmarkFor(*m_oNextFieldmarkHide)); assert(pFieldmark); m_Fieldmark.first = pFieldmark; // for cursor travelling, there should be 2 visible chars; @@ -152,16 +162,15 @@ public: // always hide the CH_TXT_ATR_FIELDSEP for now if (m_eFieldmarkMode == sw::FieldmarkMode::ShowResult) { - m_Fieldmark.second.reset( - new SwPosition(sw::mark::FindFieldSep(*m_Fieldmark.first))); - ++m_Fieldmark.second->nContent; - ++m_oNextFieldmarkHide->nContent; // skip start + 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.reset( - new SwPosition(pFieldmark->GetMarkEnd())); - --m_Fieldmark.second->nContent; + m_Fieldmark.second.emplace(pFieldmark->GetMarkEnd()); + m_Fieldmark.second->AdjustContent(-1); } } } @@ -184,15 +193,69 @@ public: { assert(!pNextRedlineHide || *m_oNextFieldmarkHide <= *pNextRedlineHide); m_pStartPos = &*m_oNextFieldmarkHide; - m_pEndPos = m_Fieldmark.second.get(); + m_pEndPos = &*m_Fieldmark.second; return true; } - else // nothing + else { assert(!pNextRedlineHide && !m_oNextFieldmarkHide); - m_pStartPos = nullptr; - m_pEndPos = nullptr; - return false; + 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; + } } } }; @@ -220,25 +283,27 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, sal_Int32 nLastEnd(0); for (auto iter = HideIterator(rTextNode, rFrame.getRootFrame()->IsHideRedlines(), - rFrame.getRootFrame()->GetFieldmarkMode()); iter.Next(); ) + 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->nNode.GetNode() == &rTextNode); // detect calls with wrong start node - if (pStart->nContent != nLastEnd) // not 0 so we eliminate adjacent deletes + 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->nContent.GetIndex()); - mergedText.append(pNode->GetText().subView(nLastEnd, pStart->nContent.GetIndex() - nLastEnd)); + extents.emplace_back(pNode, nLastEnd, pStart->GetContentIndex()); + mergedText.append(pNode->GetText().subView(nLastEnd, pStart->GetContentIndex() - nLastEnd)); } - if (&pEnd->nNode.GetNode() != pNode) + if (&pEnd->GetNode() != pNode) { if (pNode == &rTextNode) { pNode->SetRedlineMergeFlag(SwNode::Merge::First); } // else: was already set before int nLevel(0); - for (sal_uLong j = pNode->GetIndex() + 1; j < pEnd->nNode.GetIndex(); ++j) + for (SwNodeOffset j = pNode->GetIndex() + 1; j < pEnd->GetNodeIndex(); ++j) { SwNode *const pTmp(pNode->GetNodes()[j]); if (nLevel == 0) @@ -268,12 +333,12 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, } // note: in DelLastPara() case, the end node is not actually merged // and is likely a SwTableNode! - if (!pEnd->nNode.GetNode().IsTextNode()) + if (!pEnd->GetNode().IsTextNode()) { - assert(pEnd->nNode != pStart->nNode); + assert(pEnd->GetNode() != pStart->GetNode()); // must set pNode too because it will mark the last node pNode = nodes.back(); - assert(pNode == pNode->GetNodes()[pEnd->nNode.GetIndex() - 1]); + assert(pNode == pNode->GetNodes()[pEnd->GetNodeIndex() - 1]); if (pNode != &rTextNode) { // something might depend on last merged one being NonFirst? pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst); @@ -282,15 +347,15 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, } else { - pNode = pEnd->nNode.GetNode().GetTextNode(); + pNode = pEnd->GetNode().GetTextNode(); nodes.push_back(pNode); pNode->SetRedlineMergeFlag(SwNode::Merge::NonFirst); - nLastEnd = pEnd->nContent.GetIndex(); + nLastEnd = pEnd->GetContentIndex(); } } else { - nLastEnd = pEnd->nContent.GetIndex(); + nLastEnd = pEnd->GetContentIndex(); } } if (pNode == &rTextNode) @@ -308,7 +373,7 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, // * 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 (sal_uLong j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j) + for (SwNodeOffset j = pNode->GetIndex() + 1; j < pNode->GetNodes().Count(); ++j) { SwNode *const pTmp(pNode->GetNodes()[j]); if (!pTmp->IsCreateFrameWhenHidingRedlines()) @@ -422,7 +487,7 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, } for (auto const pSectionNode : sections) { - pSectionNode->DelFrames(rFrame.getRootFrame()); + pSectionNode->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/); } } auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents), @@ -441,7 +506,7 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, void SwAttrIter::InitFontAndAttrHandler( SwTextNode const& rPropsNode, SwTextNode const& rTextNode, - OUString const& rText, + std::u16string_view aText, bool const*const pbVertLayout, bool const*const pbVertLayoutLRBT) { @@ -511,7 +576,7 @@ void SwAttrIter::InitFontAndAttrHandler( m_pFont->GetFontCacheId( m_aFontCacheIds[ nTmp ], m_aFontIdx[ nTmp ], nTmp ); } } - while (nChg < TextFrameIndex(rText.getLength())); + while (nChg < TextFrameIndex(aText.size())); } void SwAttrIter::CtorInitAttrIter(SwTextNode & rTextNode, @@ -646,14 +711,16 @@ SwRedlineItr::SwRedlineItr( const SwTextNode& rTextNd, SwFont& rFnt, , 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->nNode.GetIndex(), - pExtInputStart->nContent.GetIndex()) ); + m_pExt.reset( new SwExtend(*pArr, pExtInputStart->GetNodeIndex(), + pExtInputStart->GetContentIndex()) ); } else m_pExt = nullptr; @@ -670,7 +737,7 @@ SwRedlineItr::~SwRedlineItr() COVERITY_NOEXCEPT_FALSE // 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, - sal_uLong const nNode, sal_Int32 const nNew, sal_Int32 const nOld) + SwNodeOffset const nNode, sal_Int32 const nNew, sal_Int32 const nOld) { short nRet = 0; if( ExtOn() ) @@ -703,17 +770,18 @@ short SwRedlineItr::Seek(SwFont& rFnt, m_nStart = COMPLETE_STRING; m_nEnd = COMPLETE_STRING; + const SwRedlineTable& rTable = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable(); - for ( ; m_nAct < m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++m_nAct) + for ( ; m_nAct < rTable.size() ; ++m_nAct) { - m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]->CalcStartEnd(nNode, m_nStart, m_nEnd); + 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 = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nAct ]; + const SwRangeRedline *pRed = rTable[ m_nAct ]; if (m_pSet) m_pSet->ClearItem(); @@ -721,7 +789,7 @@ short SwRedlineItr::Seek(SwFont& rFnt, { SwAttrPool& rPool = const_cast<SwDoc&>(m_rDoc).GetAttrPool(); - m_pSet = std::make_unique<SfxItemSet>(rPool, svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1>{}); + m_pSet = std::make_unique<SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1>>(rPool); } if( 1 < pRed->GetStackCount() ) @@ -729,12 +797,23 @@ short SwRedlineItr::Seek(SwFont& rFnt, 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 == m_pSet->GetItemState( nWhich, true, &pItem ) ) ) + ( SfxItemState::SET == aIter.GetItemState( true, &pItem ) ) ) { SwTextAttr* pAttr = MakeRedlineTextAttr( const_cast<SwDoc&>(m_rDoc), @@ -773,9 +852,9 @@ short SwRedlineItr::Seek(SwFont& rFnt, m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[m_nAct]); SwPosition const*const pStart(pRedline->Start()); if (pRedline->GetType() == RedlineType::Delete - && (nNode < pStart->nNode.GetIndex() - || (nNode == pStart->nNode.GetIndex() - && nNew <= pStart->nContent.GetIndex()))) + && (nNode < pStart->GetNodeIndex() + || (nNode == pStart->GetNodeIndex() + && nNew <= pStart->GetContentIndex()))) { pRedline->CalcStartEnd(nNode, m_nStart, m_nEnd); break; @@ -837,7 +916,7 @@ void SwRedlineItr::Clear_( SwFont* pFnt ) m_rAttrHandler.PopAndChg( *hint, *pFnt ); else m_rAttrHandler.Pop( *hint ); - SwTextAttr::Destroy(hint, const_cast<SwDoc&>(m_rDoc).GetAttrPool() ); + SwTextAttr::Destroy(hint); } m_Hints.clear(); } @@ -939,15 +1018,15 @@ bool SwRedlineItr::ChkSpecialUnderline_() const } bool SwRedlineItr::CheckLine( - sal_uLong const nStartNode, sal_Int32 const nChkStart, - sal_uLong const nEndNode, sal_Int32 nChkEnd, OUString& rRedlineText, - bool& bRedlineEnd, RedlineType& eRedlineEnd, bool bFullLine) + 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 && bFullLine ) // empty lines look one char further + if( nChkEnd == nChkStart && pAuthorAtPos == nullptr ) // empty lines look one char further ++nChkEnd; sal_Int32 nOldStart = m_nStart; sal_Int32 nOldEnd = m_nEnd; @@ -978,6 +1057,8 @@ bool SwRedlineItr::CheckLine( eRedlineEnd = pRedline->GetType(); bRedlineEnd = true; isBreak = true; + if (pAuthorAtPos) + *pAuthorAtPos = pRedline->GetAuthor(); [[fallthrough]]; case SwComparePosition::OverlapBefore: case SwComparePosition::CollideEnd: @@ -1032,6 +1113,8 @@ 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 ) @@ -1052,7 +1135,7 @@ void SwExtend::ActualizeFont( SwFont &rFnt, ExtTextInputAttr nAttr ) rFnt.SetGreyWave( true ); } -short SwExtend::Enter(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) +short SwExtend::Enter(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew) { OSL_ENSURE( !m_pFont, "SwExtend: Enter with Font" ); if (nNode != m_nNode) @@ -1068,7 +1151,7 @@ short SwExtend::Enter(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) return 0; } -bool SwExtend::Leave_(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) +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) @@ -1093,7 +1176,7 @@ bool SwExtend::Leave_(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) return false; } -sal_Int32 SwExtend::Next(sal_uLong const nNode, sal_Int32 nNext) +sal_Int32 SwExtend::Next(SwNodeOffset const nNode, sal_Int32 nNext) { if (nNode != m_nNode) return nNext; diff --git a/sw/source/core/text/redlnitr.hxx b/sw/source/core/text/redlnitr.hxx index 0d0e013ff6d5..5c301312640e 100644 --- a/sw/source/core/text/redlnitr.hxx +++ b/sw/source/core/text/redlnitr.hxx @@ -39,18 +39,18 @@ class SwExtend std::unique_ptr<SwFont> m_pFont; const std::vector<ExtTextInputAttr> &m_rArr; /// position of start of SwExtTextInput - sal_uLong const m_nNode; + 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, sal_uLong nNode, sal_Int32 nNew); + 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, - sal_uLong const nNode, sal_Int32 const nStart) + SwNodeOffset const nNode, sal_Int32 const nStart) : m_rArr(rArr) , m_nNode(nNode) , m_nStart(nStart) @@ -59,10 +59,10 @@ public: {} bool IsOn() const { return m_pFont != nullptr; } void Reset() { m_pFont.reset(); m_nPos = COMPLETE_STRING; } - bool Leave(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + bool Leave(SwFont& rFnt, SwNodeOffset const nNode, sal_Int32 const nNew) { return m_pFont && Leave_(rFnt, nNode, nNew); } - short Enter(SwFont& rFnt, sal_uLong nNode, sal_Int32 nNew); - sal_Int32 Next(sal_uLong nNode, sal_Int32 nNext); + 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]); } }; @@ -75,7 +75,7 @@ class SwRedlineItr std::unique_ptr<SfxItemSet> m_pSet; std::unique_ptr<SwExtend> m_pExt; // note: this isn't actually used in the merged-para (Hide) case - sal_uLong const m_nNdIdx; + SwNodeOffset const m_nNdIdx; SwRedlineTable::size_type const m_nFirst; SwRedlineTable::size_type m_nAct; sal_Int32 m_nStart; @@ -89,12 +89,12 @@ private: void Clear_( SwFont* pFnt ); bool ChkSpecialUnderline_() const; void FillHints( std::size_t nAuthor, RedlineType eType ); - short EnterExtend(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + 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(sal_uLong const nNode, sal_Int32 const nNext) { + sal_Int32 NextExtend(SwNodeOffset const nNode, sal_Int32 const nNext) { if (m_pExt) return m_pExt->Next(nNode, nNext); return nNext; } @@ -108,7 +108,7 @@ public: 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, sal_uLong nNode, sal_Int32 nNew, sal_Int32 nOld); + 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(); @@ -117,10 +117,10 @@ public: sal_Int32 nNext, SwTextNode const* pNode, SwRedlineTable::size_type & rAct); bool ChkSpecialUnderline() const { return IsOn() && ChkSpecialUnderline_(); } - bool CheckLine(sal_uLong nStartNode, sal_Int32 nChkStart, sal_uLong nEndNode, + bool CheckLine(SwNodeOffset nStartNode, sal_Int32 nChkStart, SwNodeOffset nEndNode, sal_Int32 nChkEnd, OUString& rRedlineText, bool& bRedlineEnd, - RedlineType& eRedlineEnd, bool bFullLine = true); - bool LeaveExtend(SwFont& rFnt, sal_uLong const nNode, sal_Int32 const nNew) + 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(); diff --git a/sw/source/core/text/txtcache.hxx b/sw/source/core/text/txtcache.hxx index e71be076fabb..ee18f14c3b0c 100644 --- a/sw/source/core/text/txtcache.hxx +++ b/sw/source/core/text/txtcache.hxx @@ -41,7 +41,10 @@ public: void SetPara(SwParaPortion* pNew, bool bDelete) { if (!bDelete) + { + // coverity[leaked_storage] - intentional, ownership transferred (void)m_pLine.release(); + } m_pLine.reset(pNew); } }; diff --git a/sw/source/core/text/txtdrop.cxx b/sw/source/core/text/txtdrop.cxx index 312eb4dd71ba..afbe3b1470eb 100644 --- a/sw/source/core/text/txtdrop.cxx +++ b/sw/source/core/text/txtdrop.cxx @@ -36,6 +36,7 @@ #include <editeng/fhgtitem.hxx> #include <calbck.hxx> #include <doc.hxx> +#include <IDocumentSettingAccess.hxx> using namespace ::com::sun::star::i18n; using namespace ::com::sun::star; @@ -324,7 +325,7 @@ void SwDropPortion::PaintText( const SwTextPaintInfo &rInf ) const const_cast<SwDropPortion*>(this)->SetJoinBorderWithPrev(pCurrPart->GetJoinBorderWithPrev()); if ( rInf.OnWin() && - !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() && + !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() && (!pCurrPart->GetFont().GetBackColor() || *pCurrPart->GetFont().GetBackColor() == COL_TRANSPARENT) ) { rInf.DrawBackground( *this ); @@ -397,7 +398,7 @@ void SwDropPortion::Paint( const SwTextPaintInfo &rInf ) const return; if ( rInf.OnWin() && - !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && SwViewOption::IsFieldShadings() ) + !rInf.GetOpt().IsPagePreview() && !rInf.GetOpt().IsReadonly() && rInf.GetOpt().IsFieldShadings() ) rInf.DrawBackground( *this ); // make sure that font is not rotated @@ -484,8 +485,8 @@ void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines ) { const SwLinePortion *const pOldCurr = GetCurr(); sal_uInt16 nDropHght = 0; - sal_uInt16 nAscent = 0; - sal_uInt16 nHeight = 0; + SwTwips nAscent = 0; + SwTwips nHeight = 0; sal_uInt16 nDropLns = 0; const bool bRegisterOld = IsRegisterOn(); m_bRegisterOn = false; @@ -545,8 +546,8 @@ void SwTextFormatter::CalcDropHeight( const sal_uInt16 nLines ) void SwTextFormatter::GuessDropHeight( const sal_uInt16 nLines ) { OSL_ENSURE( nLines, "GuessDropHeight: Give me more Lines!" ); - sal_uInt16 nAscent = 0; - sal_uInt16 nHeight = 0; + SwTwips nAscent = 0; + SwTwips nHeight = 0; SetDropLines( nLines ); if ( GetDropLines() > 1 ) { @@ -678,7 +679,7 @@ void SwTextPainter::PaintDropPortion() Point aLineOrigin( GetTopLeft() ); aLineOrigin.AdjustX(nX ); - sal_uInt16 nTmpAscent, nTmpHeight; + SwTwips nTmpAscent, nTmpHeight; CalcAscentAndHeight( nTmpAscent, nTmpHeight ); aLineOrigin.AdjustY(nTmpAscent ); GetInfo().SetIdx( GetStart() ); @@ -775,7 +776,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf m_aText[ nTmpIdx ] = aStr; m_aWishedHeight[ nTmpIdx ] = sal_uInt16(nWishedHeight); // save initial scaling factor - m_aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + m_aFactor[ nTmpIdx ] = o3tl::narrowing<sal_uInt16>(nFactor); } bool bGrow = (pDrop->GetLen() != TextFrameIndex(0)); @@ -793,10 +794,15 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf OutputDevice* pOut = rInf.GetOut(); OutputDevice* pWin; if( rInf.GetVsh() && rInf.GetVsh()->GetWin() ) - pWin = 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 @@ -855,6 +861,14 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf } } + // 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 @@ -877,7 +891,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf if( rFnt.GetTopBorder() ) { aRect.setHeight(aRect.GetHeight() + rFnt.GetTopBorderSpace()); - aRect.setY(aRect.getY() - rFnt.GetTopBorderSpace()); + aRect.SetPosY(aRect.Top() - rFnt.GetTopBorderSpace()); } if( rFnt.GetBottomBorder() ) @@ -925,7 +939,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf else { if ( bUseCache ) - m_aFactor[ nTmpIdx ] = static_cast<sal_uInt16>(nFactor); + m_aFactor[ nTmpIdx ] = o3tl::narrowing<sal_uInt16>(nFactor); nMin = nFactor; } @@ -982,7 +996,7 @@ void SwDropCapCache::CalcFontSize( SwDropPortion* pDrop, SwTextFormatInfo &rInf bool SwDropPortion::Format( SwTextFormatInfo &rInf ) { bool bFull = false; - m_nFix = static_cast<sal_uInt16>(rInf.X()); + m_nFix = o3tl::narrowing<sal_uInt16>(rInf.X()); SwLayoutModeModifier aLayoutModeModifier( *rInf.GetOut() ); aLayoutModeModifier.SetAuto(); @@ -1020,7 +1034,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) Width(); // set values - pCurrPart->SetWidth( static_cast<sal_uInt16>(nTmpWidth) ); + pCurrPart->SetWidth( o3tl::narrowing<sal_uInt16>(nTmpWidth) ); // Move rInf.SetIdx( rInf.GetIdx() + pCurrPart->GetLen() ); @@ -1029,7 +1043,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) } SetJoinBorderWithNext(false); SetJoinBorderWithPrev(false); - Width( static_cast<sal_uInt16>(rInf.X() - nOldX) ); + Width( o3tl::narrowing<sal_uInt16>(rInf.X() - nOldX) ); } // reset my length @@ -1066,7 +1080,7 @@ bool SwDropPortion::Format( SwTextFormatInfo &rInf ) else { const sal_uInt16 nWant = Width() + GetDistance(); - const sal_uInt16 nRest = static_cast<sal_uInt16>(rInf.Width() - rInf.X()); + 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; diff --git a/sw/source/core/text/txtfld.cxx b/sw/source/core/text/txtfld.cxx index e150f7f489f1..e522f4e08929 100644 --- a/sw/source/core/text/txtfld.cxx +++ b/sw/source/core/text/txtfld.cxx @@ -58,241 +58,143 @@ #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; - else - { - const SwFrame *pTmp = pFrame; - const SwFlyFrame *pFly; - while ( nullptr != (pFly = pTmp->FindFlyFrame()) ) - pTmp = pFly->GetAnchorFrame(); - return pTmp->IsInDocBody(); - } + + while (const SwFlyFrame* pFly = pFrame->FindFlyFrame()) + pFrame = pFly->GetAnchorFrame(); + return pFrame->IsInDocBody(); +} + +static OUString ExpandField(const SwField& rField, const SwTextFormatter& rFormatter, + const SwTextFormatInfo& rInf) +{ + if (rInf.GetOpt().IsFieldName()) + return rField.GetFieldName(); + + const SwViewShell* pSh = rInf.GetVsh(); + const SwDoc* pDoc(pSh ? pSh->GetDoc() : nullptr); + const bool bInClipboard(!pDoc || pDoc->IsClipBoard()); + return rField.ExpandField(bInClipboard, rFormatter.GetTextFrame()->getRootFrame()); } SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf, const SwTextAttr *pHint ) const { - SwExpandPortion *pRet = nullptr; - SwFrame *pFrame = m_pFrame; SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField()); const bool bName = rInf.GetOpt().IsFieldName(); - SwCharFormat* pChFormat = nullptr; - bool bNewFlyPor = false; - sal_uInt16 subType = 0; - // set language const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf ); if (pField->GetLanguage() != GetFnt()->GetLanguage()) - { pField->SetLanguage( GetFnt()->GetLanguage() ); - // let the visual note know about its new language - if (pField->GetTyp()->Which()==SwFieldIds::Postit) - const_cast<SwFormatField*> (&pHint->GetFormatField())->Broadcast( SwFormatFieldHint( &pHint->GetFormatField(), SwFormatFieldHintWhich::LANGUAGE ) ); - } SwViewShell *pSh = rInf.GetVsh(); - SwDoc *const pDoc( pSh ? pSh->GetDoc() : nullptr ); - bool const bInClipboard( pDoc == nullptr || pDoc->IsClipBoard() ); - bool bPlaceHolder = false; - switch( pField->GetTyp()->Which() ) + switch (pField->GetTyp()->Which()) { case SwFieldIds::Script: case SwFieldIds::Postit: - pRet = new SwPostItsPortion( SwFieldIds::Script == pField->GetTyp()->Which() ); - break; - + return new SwPostItsPortion(SwFieldIds::Script == pField->GetTyp()->Which()); case SwFieldIds::CombinedChars: - { - if( bName ) - pRet = new SwFieldPortion( pField->GetFieldName() ); - else - pRet = new SwCombinedPortion( pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - } + if (!bName) + return new SwCombinedPortion(ExpandField(*pField, *this, rInf)); break; - case SwFieldIds::HiddenText: - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwHiddenPortion(aStr); - } - break; - + return new SwHiddenPortion(ExpandField(*pField, *this, rInf)); case SwFieldIds::Chapter: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) - { - static_cast<SwChapterField*>(pField)->ChangeExpansion(*pFrame, - &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode()); - } + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); + static_cast<SwChapterField*>(pField)->ChangeExpansion( + *m_pFrame, &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode()); } break; - case SwFieldIds::DocStat: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) - { - static_cast<SwDocStatField*>(pField)->ChangeExpansion( pFrame ); - } + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); + static_cast<SwDocStatField*>(pField)->ChangeExpansion(m_pFrame); } - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGECOUNTFLD; break; - case SwFieldIds::PageNumber: - { - if( !bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields()) { - SwPageNumberFieldType *pPageNr = static_cast<SwPageNumberFieldType *>(pField->GetTyp()); + auto pPageNr = static_cast<SwPageNumberFieldType*>(pField->GetTyp()); const SwRootFrame* pTmpRootFrame = pSh->GetLayout(); const bool bVirt = pTmpRootFrame->IsVirtPageNum(); - sal_uInt16 nVirtNum = pFrame->GetVirtPageNum(); + sal_uInt16 nVirtNum = m_pFrame->GetVirtPageNum(); sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum(); SvxNumType nNumFormat = SvxNumType(-1); - if(SVX_NUM_PAGEDESC == pField->GetFormat()) - nNumFormat = pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType(); - static_cast<SwPageNumberField*>(pField) - ->ChangeExpansion(nVirtNum, nNumPages); - pPageNr->ChangeExpansion(pDoc, - bVirt, nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr); + 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); } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); - } - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGENUMBERFLD; break; - } case SwFieldIds::GetExp: - { - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - SwGetExpField* pExpField = static_cast<SwGetExpField*>(pField); - if( !::lcl_IsInBody( pFrame ) ) + auto pExpField = static_cast<SwGetExpField*>(pField); + if (!::lcl_IsInBody(m_pFrame)) { - pExpField->ChgBodyTextFlag( false ); - pExpField->ChangeExpansion(*pFrame, - *static_txtattr_cast<SwTextField const*>(pHint)); + pExpField->ChgBodyTextFlag(false); + pExpField->ChangeExpansion(*m_pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); } - else if( !pExpField->IsInBodyText() ) + else if (!pExpField->IsInBodyText()) { // Was something else previously, thus: expand first, then convert it! - pExpField->ChangeExpansion(*pFrame, - *static_txtattr_cast<SwTextField const*>(pHint)); - pExpField->ChgBodyTextFlag( true ); + pExpField->ChangeExpansion(*m_pFrame, + *static_txtattr_cast<SwTextField const*>(pHint)); + pExpField->ChgBodyTextFlag(true); } } - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion( aStr ); - } break; - } case SwFieldIds::Database: - { - if( !bName ) - { - SwDBField* pDBField = static_cast<SwDBField*>(pField); - pDBField->ChgBodyTextFlag( ::lcl_IsInBody( pFrame ) ); - } + if (!bName) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); + static_cast<SwDBField*>(pField)->ChgBodyTextFlag(::lcl_IsInBody(m_pFrame)); } break; - } case SwFieldIds::RefPageGet: - if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() ) - { - static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(*pFrame, - static_txtattr_cast<SwTextField const*>(pHint)); - } + if (!bName && pSh && !pSh->Imp()->IsUpdateExpFields()) { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); + static_cast<SwRefPageGetField*>(pField)->ChangeExpansion( + *m_pFrame, static_txtattr_cast<SwTextField const*>(pHint)); } break; - case SwFieldIds::JumpEdit: - if( !bName ) - pChFormat = static_cast<SwJumpEditField*>(pField)->GetCharFormat(); - bNewFlyPor = true; - bPlaceHolder = true; - break; - case SwFieldIds::GetRef: - subType = static_cast<SwGetRefField*>(pField)->GetSubType(); + { + std::unique_ptr<SwFont> pFont; + if (!bName) { - OUString const str( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(str); + pFont = std::make_unique<SwFont>(*m_pFont); + pFont->SetDiffFnt( + &static_cast<SwJumpEditField*>(pField)->GetCharFormat()->GetAttrSet(), + &m_pFrame->GetDoc().getIDocumentSettingAccess()); } - if( subType == REF_BOOKMARK ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_BOOKMARKFLD; - else if( subType == REF_SETREFATTR ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_SETREFATTRFLD; - break; - case SwFieldIds::DateTime: - subType = static_cast<SwDateTimeField*>(pField)->GetSubType(); + return new SwJumpFieldPortion(ExpandField(*pField, *this, rInf), pField->GetPar2(), + std::move(pFont), pField->GetFormat()); + } + case SwFieldIds::GetRef: + if (!bName) { - OUString const str( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(str); + auto pGetRef = static_cast<SwGetRefField*>(pField); + if (pGetRef->GetSubType() == REF_STYLE) + pGetRef->UpdateField(static_txtattr_cast<SwTextField const*>(pHint), m_pFrame); } - if( subType & DATEFLD ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_DATEFLD; - else if( subType & TIMEFLD ) - static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_TIMEFLD; break; default: - { - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr); - } - } - - if( bNewFlyPor ) - { - std::unique_ptr<SwFont> pTmpFnt; - if( !bName ) - { - pTmpFnt.reset(new SwFont( *m_pFont )); - pTmpFnt->SetDiffFnt(&pChFormat->GetAttrSet(), &m_pFrame->GetDoc().getIDocumentSettingAccess()); - } - OUString const aStr( bName - ? pField->GetFieldName() - : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) ); - pRet = new SwFieldPortion(aStr, std::move(pTmpFnt), bPlaceHolder); + break; } - - return pRet; + return new SwFieldPortion(ExpandField(*pField, *this, rInf)); } static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix) @@ -304,7 +206,8 @@ static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefi OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?"); if (pField) { - pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix); + OUString color; + pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix, &color); } return new SwFieldPortion( fix ); } @@ -405,7 +308,12 @@ SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf ) } if( !pRet ) { - pRet = new SwFieldPortion( "" ); + auto pFieldPortion = new SwFieldPortion( "" ); + if (pHint->Which() == RES_TXTATR_CONTENTCONTROL) + { + pFieldPortion->SetContentControl(true); + } + pRet = pFieldPortion; rInf.SetLen(TextFrameIndex(1)); } return pRet; @@ -423,39 +331,9 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm if( !pIDSA->get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING )) return; - SwFormatAutoFormat const& rListAutoFormat(static_cast<SwFormatAutoFormat const&>(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT))); + SwFormatAutoFormat const& rListAutoFormat(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT)); std::shared_ptr<SfxItemSet> pSet(rListAutoFormat.GetStyleHandle()); - // TODO remove this fallback (for WW8/RTF) - bool isDOCX = pIDSA->get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS); - if (!isDOCX && !pSet) - { - TextFrameIndex const nTextLen(rInf.GetTextFrame()->GetText().getLength()); - SwTextNode const* pNode(nullptr); - sw::MergedAttrIterReverse iter(*rInf.GetTextFrame()); - for (SwTextAttr const* pHint = iter.PrevAttr(&pNode); pHint; - pHint = iter.PrevAttr(&pNode)) - { - TextFrameIndex const nHintEnd( - rInf.GetTextFrame()->MapModelToView(pNode, pHint->GetAnyEnd())); - if (nHintEnd < nTextLen) - { - break; // only those at para end are interesting - } - // Formatting for the paragraph mark is usually set to apply only to the - // (non-existent) extra character at end of the text node, but there can be - // other hints too (ending at nTextLen), so look for all matching hints. - // Still the (non-existent) extra character at the end is preferred if it exists. - if (pHint->Which() == RES_TXTATR_AUTOFMT) - { - pSet = pHint->GetAutoFormat().GetStyleHandle(); - // When we find an empty hint (start == end) we got what we are looking for. - if (pHint->GetStart() == *pHint->End()) - break; - } - } - } - // Check each item and in case it should be ignored, then clear it. if (!pSet) return; @@ -473,7 +351,8 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm { if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich, /*bIsCharStyle=*/true) && !pCleanedSet->HasItem(nWhich) - && !(pFormat && pFormat->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 @@ -530,63 +409,84 @@ static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextForm if (oFontBackColor) pNumFnt->SetBackColor(oFontBackColor); - if (aHighlight != COL_TRANSPARENT) + if (aHighlight != COL_TRANSPARENT && !pCleanedSet->HasItem(RES_CHRATR_HIGHLIGHT)) pNumFnt->SetHighlightColor(aHighlight); } -static const SwRangeRedline* lcl_GetRedlineAtNodeInsertionOrDeletion( const SwTextNode& rTextNode ) +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 sal_uLong nNdIdx = rTextNode.GetIndex(); - for( ; nRedlPos < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() ; ++nRedlPos ) + const SwNodeOffset nNdIdx = rTextNode.GetIndex(); + const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + for( ; nRedlPos < rTable.size() ; ++nRedlPos ) { - const SwRangeRedline* pTmp = rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ 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() ) { - const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End(); - if( pRStt->nNode < nNdIdx && pREnd->nNode >= nNdIdx ) + if( nStart <= nNdIdx && nEnd > nNdIdx ) + { + bIsMoved = pTmp->IsMoved(); return pTmp; + } } + if( nStart > nNdIdx ) + break; } } return nullptr; } -static void lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, const std::unique_ptr<SwFont>& pNumFnt ) +static bool lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, const std::unique_ptr<SwFont>& pNumFnt ) { if ( rInf.GetVsh()->GetLayout()->IsHideRedlines() ) - return; + return false; - const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode ); + bool bIsMoved; + const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode, bIsMoved ); if (!pRedlineNum) - return; + return false; - std::unique_ptr<SfxItemSet> pSet; + // 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(); - pSet = std::make_unique<SfxItemSet>(rPool, svl::Items<RES_CHRATR_BEGIN, RES_CHRATR_END-1>{}); + 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, *pSet); + SW_MOD()->GetDeletedAuthorAttr(aAuthor, aSet); else - SW_MOD()->GetInsertAuthorAttr(aAuthor, *pSet); - - const SfxPoolItem* pItem = nullptr; - if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_COLOR, true, &pItem)) - pNumFnt->SetColor(static_cast<const SvxColorItem*>(pItem)->GetValue()); - if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_UNDERLINE, true, &pItem)) - pNumFnt->SetUnderline(static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle()); - if (SfxItemState::SET == pSet->GetItemState(RES_CHRATR_CROSSEDOUT, true, &pItem)) - pNumFnt->SetStrikeout( static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout() ); + 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 @@ -657,7 +557,7 @@ SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) con if( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() ) { - const vcl::Font *pFormatFnt = rNumFormat.GetBulletFont(); + 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 )); @@ -715,11 +615,37 @@ SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) con } else { - OUString aText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame()) ); - if ( !aText.isEmpty() ) + // 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() ) { - aText += pTextNd->GetLabelFollowedBy(); + 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. @@ -748,7 +674,8 @@ SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) con checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat); - lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ); + 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() ); diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index a5fb1f6b6731..7c4d9a2e160d 100644 --- a/sw/source/core/text/txtfly.cxx +++ b/sw/source/core/text/txtfly.cxx @@ -33,6 +33,8 @@ #include <frmtool.hxx> #include <ndtxt.hxx> #include <txtfly.hxx> +#include "inftxt.hxx" +#include "porrst.hxx" #include "txtpaint.hxx" #include <notxtfrm.hxx> #include <fmtcnct.hxx> @@ -48,6 +50,7 @@ #include <sortedobjs.hxx> #include <IDocumentDrawModelAccess.hxx> #include <IDocumentSettingAccess.hxx> +#include <formatlinebreak.hxx> #include <svx/svdoedge.hxx> #ifdef DBG_UTIL @@ -159,7 +162,7 @@ SwRect SwContourCache::CalcBoundRect( const SwAnchoredObject* pAnchoredObj, const bool bRight ) { SwRect aRet; - const SwFrameFormat* pFormat = &(pAnchoredObj->GetFrameFormat()); + const SwFrameFormat* pFormat = pAnchoredObj->GetFrameFormat(); bool bHandleContour(pFormat->GetSurround().IsContour()); if(!bHandleContour) @@ -176,12 +179,12 @@ SwRect SwContourCache::CalcBoundRect( const SwAnchoredObject* pAnchoredObj, } if( bHandleContour && - ( dynamic_cast< const SwFlyFrame *>( pAnchoredObj ) == nullptr || + ( pAnchoredObj->DynCastFlyFrame() == nullptr || ( static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower() && static_cast<const SwFlyFrame*>(pAnchoredObj)->Lower()->IsNoTextFrame() ) ) ) { aRet = pAnchoredObj->GetObjRectWithSpaces(); - if( aRet.IsOver( rLine ) ) + if( aRet.Overlaps( rLine ) ) { if( !pContourCache ) pContourCache = new SwContourCache; @@ -216,7 +219,7 @@ SwRect SwContourCache::ContourRect( const SwFormat* pFormat, mvItems.pop_back(); } ::basegfx::B2DPolyPolygon aPolyPolygon; - std::unique_ptr<::basegfx::B2DPolyPolygon> pPolyPolygon; + std::optional<::basegfx::B2DPolyPolygon> pPolyPolygon; if ( auto pVirtFlyDrawObj = dynamic_cast< const SwVirtFlyDrawObj *>( pObj ) ) { @@ -224,27 +227,26 @@ SwRect SwContourCache::ContourRect( const SwFormat* pFormat, // the graphic to change its size, call ClrObject() tools::PolyPolygon aPoly; if( !pVirtFlyDrawObj->GetFlyFrame()->GetContour( aPoly ) ) - aPoly = tools::PolyPolygon( static_cast<const SwVirtFlyDrawObj*>(pObj)-> + aPoly = tools::PolyPolygon( pVirtFlyDrawObj-> GetFlyFrame()->getFrameArea().SVRect() ); aPolyPolygon.clear(); aPolyPolygon.append(aPoly.getB2DPolyPolygon()); } else { - if( dynamic_cast< const E3dObject *>( pObj ) == nullptr ) + if( DynCastE3dObject( pObj ) == nullptr ) { aPolyPolygon = pObj->TakeXorPoly(); } - ::basegfx::B2DPolyPolygon aContourPoly(pObj->TakeContour()); - pPolyPolygon.reset(new ::basegfx::B2DPolyPolygon(aContourPoly)); + 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.get(), 20, - static_cast<sal_uInt16>(rLRSpace.GetLeft()), static_cast<sal_uInt16>(rLRSpace.GetRight()), + 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)); @@ -371,7 +373,7 @@ void SwTextFly::CtorInitTextFly( const SwTextFrame *pFrame ) m_bTopRule = true; m_nMinBottom = 0; m_nNextTop = 0; - m_nCurrFrameNodeIndex = ULONG_MAX; + m_nCurrFrameNodeIndex = NODE_OFFSET_MAX; } SwRect SwTextFly::GetFrame_( const SwRect &rRect ) const @@ -412,6 +414,12 @@ bool SwTextFly::IsAnyObj( const SwRect &rRect ) const { 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(); @@ -429,7 +437,7 @@ bool SwTextFly::IsAnyObj( const SwRect &rRect ) const continue; // #i68520# - if( mpCurrAnchoredObj != pObj && aBound.IsOver( aRect ) ) + if( mpCurrAnchoredObj != pObj && aBound.Overlaps( aRect ) ) return true; } } @@ -485,7 +493,7 @@ void SwTextFly::DrawTextOpaque( SwDrawTextInfo &rInf ) OSL_ENSURE( !m_bTopRule, "DrawTextOpaque: Wrong TopRule" ); // #i68520# - const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList()->size() : 0 ); + const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 ); if (nCount > 0) { const SdrLayerID nHellId = m_pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId(); @@ -493,22 +501,21 @@ void SwTextFly::DrawTextOpaque( SwDrawTextInfo &rInf ) { // #i68520# const SwAnchoredObject* pTmpAnchoredObj = (*mpAnchoredObjList)[i]; - if( dynamic_cast<const SwFlyFrame*>(pTmpAnchoredObj) && - mpCurrAnchoredObj != pTmpAnchoredObj ) + const SwFlyFrame* pFly = pTmpAnchoredObj->DynCastFlyFrame(); + if( pFly && mpCurrAnchoredObj != pTmpAnchoredObj ) { // #i68520# - const SwFlyFrame& rFly = dynamic_cast<const SwFlyFrame&>(*pTmpAnchoredObj); - if( aRegion.GetOrigin().IsOver( rFly.getFrameArea() ) ) + if( aRegion.GetOrigin().Overlaps( pFly->getFrameArea() ) ) { - const SwFrameFormat *pFormat = rFly.GetFormat(); + 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( ! rFly.IsBackgroundTransparent() && + if( ! pFly->IsBackgroundTransparent() && css::text::WrapTextMode_THROUGH == rSur.GetSurround() && ( !rSur.IsAnchorOnly() || // #i68520# - GetMaster() == rFly.GetAnchorFrame() || + GetMaster() == pFly->GetAnchorFrame() || ((RndStdIds::FLY_AT_PARA != rAnchor.GetAnchorId()) && (RndStdIds::FLY_AT_CHAR != rAnchor.GetAnchorId()) ) @@ -520,14 +527,14 @@ void SwTextFly::DrawTextOpaque( SwDrawTextInfo &rInf ) { // Except for the content is transparent const SwNoTextFrame *pNoText = - rFly.Lower() && rFly.Lower()->IsNoTextFrame() - ? static_cast<const SwNoTextFrame*>(rFly.Lower()) + pFly->Lower() && pFly->Lower()->IsNoTextFrame() + ? static_cast<const SwNoTextFrame*>(pFly->Lower()) : nullptr; if ( !pNoText || (!pNoText->IsTransparent() && !rSur.IsContour()) ) { bOpaque = true; - aRegion -= rFly.getFrameArea(); + aRegion -= pFly->getFrameArea(); } } } @@ -571,7 +578,7 @@ 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 ); + const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 ); if (nCount > 0) { const SdrLayerID nHellId = m_pPage->getRootFrame()->GetCurrShell()->getIDocumentDrawModelAccess().GetHellId(); @@ -583,11 +590,11 @@ void SwTextFly::DrawFlyRect( OutputDevice* pOut, const SwRect &rRect ) continue; // #i68520# - const SwFlyFrame* pFly = dynamic_cast<const SwFlyFrame*>(pAnchoredObjTmp); + const SwFlyFrame* pFly = pAnchoredObjTmp->DynCastFlyFrame(); if (pFly) { // #i68520# - const SwFormatSurround& rSur = pAnchoredObjTmp->GetFrameFormat().GetSurround(); + 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 @@ -648,8 +655,8 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, if( ( bInFootnote || bInFooterOrHeader ) && m_bTopRule ) { // #i26945# - const SwFrameFormat& rFrameFormat = _pAnchoredObj->GetFrameFormat(); - const SwFormatAnchor& rNewA = rFrameFormat.GetAnchor(); + const SwFrameFormat* pFrameFormat = _pAnchoredObj->GetFrameFormat(); + const SwFormatAnchor& rNewA = pFrameFormat->GetAnchor(); if (RndStdIds::FLY_AT_PAGE == rNewA.GetAnchorId()) { if ( bInFootnote ) @@ -657,7 +664,7 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, if ( bInFooterOrHeader ) { - const SwFormatVertOrient& aVert( rFrameFormat.GetVertOrient() ); + const SwFormatVertOrient& aVert(pFrameFormat->GetVertOrient()); bool bVertPrt = aVert.GetRelationOrient() == text::RelOrientation::PRINT_AREA || aVert.GetRelationOrient() == text::RelOrientation::PAGE_PRINT_AREA; if( bVertPrt ) @@ -670,7 +677,24 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, // bEvade: consider pNew, if we are not inside a fly // consider pNew, if pNew is lower of <mpCurrAnchoredObj> bool bEvade = !mpCurrAnchoredObj || - Is_Lower_Of( dynamic_cast<const SwFlyFrame*>(mpCurrAnchoredObj), pNew); + 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 ) { @@ -686,13 +710,14 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, { // Within chained Flys we only avoid Lower // #i68520# - const SwFormatChain &rChain = mpCurrAnchoredObj->GetFrameFormat().GetChain(); + const SwFrameFormat* pCurObjFormat = mpCurrAnchoredObj->GetFrameFormat(); + const SwFormatChain& rChain = pCurObjFormat->GetChain(); if ( !rChain.GetPrev() && !rChain.GetNext() ) { // #i26945# - const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat().GetAnchor(); + const SwFormatAnchor& rNewA = _pAnchoredObj->GetFrameFormat()->GetAnchor(); // #i68520# - const SwFormatAnchor& rCurrA = mpCurrAnchoredObj->GetFrameFormat().GetAnchor(); + const SwFormatAnchor& rCurrA = pCurObjFormat->GetAnchor(); // If <mpCurrAnchoredObj> is anchored as character, its content // does not wrap around pNew @@ -736,7 +761,7 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, { // #i68520# const SwRect& aTmp( _pAnchoredObj->GetObjRectWithSpaces() ); - if ( !aTmp.IsOver( mpCurrAnchoredObj->GetObjRectWithSpaces() ) ) + if ( !aTmp.Overlaps( mpCurrAnchoredObj->GetObjRectWithSpaces() ) ) bEvade = false; } } @@ -744,86 +769,109 @@ bool SwTextFly::GetTop( const SwAnchoredObject* _pAnchoredObj, 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() ) ) + if (const SwFrameFormat* pAnchoredObjFormat = _pAnchoredObj->GetFrameFormat()) { + const SwFormatAnchor& rNewA = pAnchoredObjFormat->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# - 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() ) + const SwFrame* pTmp = _pAnchoredObj->GetAnchorFrame(); + if (pTmp == m_pCurrFrame) return true; - - // Compare indices: - // The Index of the other is retrieved from the anchor attr. - sal_uLong nTmpIndex = rNewA.GetContentAnchor()->nNode.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 (ULONG_MAX == m_nCurrFrameNodeIndex) - m_nCurrFrameNodeIndex = m_pCurrFrame->GetTextNodeFirst()->GetIndex(); - - if (FrameContainsNode(*m_pCurrFrame, nTmpIndex) || nTmpIndex < m_nCurrFrameNodeIndex) + 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() +SwAnchoredObjList& SwTextFly::InitAnchoredObjList() { OSL_ENSURE( m_pCurrFrame, "InitFlyList: No Frame, no FlyList" ); // #i68520# @@ -847,23 +895,12 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() m_bOn = false; + // #i68520# + mpAnchoredObjList.reset(new SwAnchoredObjList); + if( nCount && bWrapAllowed ) { - // #i68520# - mpAnchoredObjList.reset(new SwAnchoredObjList ); - - // #i28701# - consider complete frame area for new - // text wrapping - SwRect aRect; - if ( pIDSA->get(DocumentSettingId::USE_FORMER_TEXT_WRAPPING) ) - { - aRect = m_pCurrFrame->getFramePrintArea(); - aRect += m_pCurrFrame->getFrameArea().Pos(); - } - else - { - aRect = m_pCurrFrame->getFrameArea(); - } + SwRect const aRect(GetFrameArea()); // Make ourselves a little smaller than we are, // so that 1-Twip-overlappings are ignored (#49532) SwRectFnSet aRectFnSet(m_pCurrFrame); @@ -890,7 +927,7 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() !pAnchoredObj->ConsiderForTextWrap() || ( mbIgnoreObjsInHeaderFooter && !bFooterHeader && pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) || - ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat().GetFollowTextFlow().GetValue() ) + ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat()->GetFollowTextFlow().GetValue() ) ) { continue; @@ -928,13 +965,13 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() mpAnchoredObjList->insert( aInsPosIter, pAnchoredObj ); } - const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround(); + const SwFrameFormat* pObjFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatSurround& rFlyFormat = pObjFormat->GetSurround(); // #i68520# if ( rFlyFormat.IsAnchorOnly() && pAnchoredObj->GetAnchorFrame() == GetMaster() ) { - const SwFormatVertOrient &rTmpFormat = - pAnchoredObj->GetFrameFormat().GetVertOrient(); + const SwFormatVertOrient &rTmpFormat = pObjFormat->GetVertOrient(); if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() ) m_nMinBottom = ( aRectFnSet.IsVert() && m_nMinBottom ) ? std::min( m_nMinBottom, aBound.Left() ) : @@ -951,14 +988,9 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() m_nMinBottom = nMax; } } - else - { - // #i68520# - mpAnchoredObjList.reset( new SwAnchoredObjList ); - } // #i68520# - return mpAnchoredObjList.get(); + return *mpAnchoredObjList; } SwTwips SwTextFly::CalcMinBottom() const @@ -974,11 +1006,11 @@ SwTwips SwTextFly::CalcMinBottom() const for( size_t i = 0; i < nCount; ++i ) { SwAnchoredObject* pAnchoredObj = (*pDrawObj)[ i ]; - const SwFormatSurround &rFlyFormat = pAnchoredObj->GetFrameFormat().GetSurround(); + const SwFrameFormat* pObjFormat = pAnchoredObj->GetFrameFormat(); + const SwFormatSurround& rFlyFormat = pObjFormat->GetSurround(); if( rFlyFormat.IsAnchorOnly() ) { - const SwFormatVertOrient &rTmpFormat = - pAnchoredObj->GetFrameFormat().GetVertOrient(); + const SwFormatVertOrient &rTmpFormat = pObjFormat->GetVertOrient(); if( text::VertOrientation::BOTTOM != rTmpFormat.GetVertOrient() ) { const SwRect& aBound( pAnchoredObj->GetObjRectWithSpaces() ); @@ -995,6 +1027,64 @@ SwTwips SwTextFly::CalcMinBottom() const 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)); @@ -1019,7 +1109,7 @@ bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const bool bRet = false; // #i68520# - const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList()->size() : 0 ); + const SwAnchoredObjList::size_type nCount( m_bOn ? GetAnchoredObjList().size() : 0 ); if (nCount > 0) { for( SwAnchoredObjList::size_type i = 0; i < nCount; ++i ) @@ -1033,10 +1123,10 @@ bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const break; // #i68520# - if ( mpCurrAnchoredObj != pAnchoredObj && aRect.IsOver( rRect ) ) + if ( mpCurrAnchoredObj != pAnchoredObj && aRect.Overlaps( rRect ) ) { // #i68520# - const SwFormat* pFormat( &(pAnchoredObj->GetFrameFormat()) ); + const SwFormat* pFormat(pAnchoredObj->GetFrameFormat()); const SwFormatSurround &rSur = pFormat->GetSurround(); if( bAvoid ) { @@ -1070,7 +1160,7 @@ bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const { // #i68520# SwRect aFly = AnchoredObjToRect( pAnchoredObj, rRect ); - if( aFly.IsEmpty() || !aFly.IsOver( rRect ) ) + if( aFly.IsEmpty() || !aFly.Overlaps( rRect ) ) continue; if( !bRet || ( (!m_pCurrFrame->IsRightToLeft() && @@ -1098,7 +1188,7 @@ bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const // #i68520# SwAnchoredObjList::size_type SwTextFly::GetPos( const SwAnchoredObject* pAnchoredObj ) const { - SwAnchoredObjList::size_type nCount = GetAnchoredObjList()->size(); + SwAnchoredObjList::size_type nCount = GetAnchoredObjList().size(); SwAnchoredObjList::size_type nRet = 0; while ( nRet < nCount && pAnchoredObj != (*mpAnchoredObjList)[ nRet ] ) ++nRet; @@ -1125,7 +1215,7 @@ void SwTextFly::CalcRightMargin( SwRect &rFly, // 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().IsOver( rLine ) is necessary + // 3301: pNext->getFrameArea().Overlaps( rLine ) is necessary // #i68520# css::text::WrapTextMode eSurroundForTextWrap; @@ -1178,7 +1268,7 @@ void SwTextFly::CalcRightMargin( SwRect &rFly, aRectFnSet.GetTop(aLine) ) > 0 ) SetNextTop( 0 ); } - if( aTmp.IsOver( aLine ) && nTmpRight > nFlyRight ) + if( aTmp.Overlaps( aLine ) && nTmpRight > nFlyRight ) { nFlyRight = nTmpRight; if( css::text::WrapTextMode_RIGHT == eSurroundForTextWrap || @@ -1216,7 +1306,7 @@ void SwTextFly::CalcLeftMargin( SwRect &rFly, // 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().IsOver( rLine ) is necessary + // 3301: pNext->getFrameArea().Overlaps( rLine ) is necessary // #i68520# SwAnchoredObjList::size_type nMyPos = nFlyPos; @@ -1244,7 +1334,7 @@ void SwTextFly::CalcLeftMargin( SwRect &rFly, const SwRect aTmp( SwContourCache::CalcBoundRect (pNext, aLine, m_pCurrFrame, nFlyLeft, false) ); - if( aRectFnSet.GetLeft(aTmp) < nFlyLeft && aTmp.IsOver( aLine ) ) + if( aRectFnSet.GetLeft(aTmp) < nFlyLeft && aTmp.Overlaps( aLine ) ) { // #118796# - no '+1', because <..fnGetRight> // returns the correct value. @@ -1318,15 +1408,12 @@ SwRect SwTextFly::AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj, // Wrap only on sides with at least 2cm space for the text #define TEXT_MIN 1134 -// MS Word wraps on sides with even less space (value guessed). -#define TEXT_MIN_SMALL 300 - // 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 SwFrameFormat* pFormat = pAnchoredObj->GetFrameFormat(); const SwFormatSurround &rFlyFormat = pFormat->GetSurround(); css::text::WrapTextMode eSurroundForTextWrap = rFlyFormat.GetSurround(); diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx index 6a91cfd36a3d..8e5a1a904b80 100644 --- a/sw/source/core/text/txtfrm.cxx +++ b/sw/source/core/text/txtfrm.cxx @@ -17,6 +17,8 @@ * 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> @@ -24,7 +26,7 @@ #include <editeng/lrspitem.hxx> #include <editeng/brushitem.hxx> #include <editeng/pgrditem.hxx> -#include <unotools/configmgr.hxx> +#include <comphelper/configuration.hxx> #include <swmodule.hxx> #include <SwSmartTagMgr.hxx> #include <doc.hxx> @@ -66,7 +68,7 @@ #include <fmtflcnt.hxx> #include <fmtcntnt.hxx> #include <numrule.hxx> -#include <IGrammarContact.hxx> +#include <GrammarContact.hxx> #include <calbck.hxx> #include <ftnidx.hxx> #include <ftnfrm.hxx> @@ -285,15 +287,15 @@ namespace sw { } } - bool FrameContainsNode(SwContentFrame const& rFrame, sal_uLong const nNodeIndex) + 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()) { - sal_uLong const nFirst(pMerged->pFirstNode->GetIndex()); - sal_uLong const nLast(pMerged->pLastNode->GetIndex()); + SwNodeOffset const nFirst(pMerged->pFirstNode->GetIndex()); + SwNodeOffset const nLast(pMerged->pLastNode->GetIndex()); return (nFirst <= nNodeIndex && nNodeIndex <= nLast); } else @@ -325,16 +327,16 @@ namespace sw { } SwTextNode * - GetParaPropsNode(SwRootFrame const& rLayout, SwNodeIndex const& rPos) + GetParaPropsNode(SwRootFrame const& rLayout, SwNode const& rPos) { - SwTextNode *const pTextNode(rPos.GetNode().GetTextNode()); + const SwTextNode *const pTextNode(rPos.GetTextNode()); if (pTextNode && !sw::IsParaPropsNode(rLayout, *pTextNode)) { return static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))->GetMergedPara()->pParaPropsNode; } else { - return pTextNode; + return const_cast<SwTextNode*>(pTextNode); } } @@ -342,19 +344,16 @@ namespace sw { GetParaPropsPos(SwRootFrame const& rLayout, SwPosition const& rPos) { SwPosition pos(rPos); - SwTextNode const*const pNode(pos.nNode.GetNode().GetTextNode()); + SwTextNode const*const pNode(pos.GetNode().GetTextNode()); if (pNode) - { - pos.nNode = *sw::GetParaPropsNode(rLayout, *pNode); - pos.nContent.Assign(pos.nNode.GetNode().GetContentNode(), 0); - } + pos.Assign( *sw::GetParaPropsNode(rLayout, *pNode) ); return pos; } std::pair<SwTextNode *, SwTextNode *> - GetFirstAndLastNode(SwRootFrame const& rLayout, SwNodeIndex const& rPos) + GetFirstAndLastNode(SwRootFrame const& rLayout, SwNode const& rPos) { - SwTextNode *const pTextNode(rPos.GetNode().GetTextNode()); + SwTextNode *const pTextNode(const_cast<SwTextNode*>(rPos.GetTextNode())); if (pTextNode && rLayout.HasMergedParas()) { if (SwTextFrame const*const pFrame = static_cast<SwTextFrame*>(pTextNode->getLayoutFrame(&rLayout))) @@ -383,8 +382,7 @@ namespace sw { rFormatSet.ClearItem(RES_BREAK); static_assert(RES_PAGEDESC + 1 == sal_uInt16(RES_BREAK), "first-node items must be adjacent"); - SfxItemSet firstSet(*rFormatSet.GetPool(), - svl::Items<RES_PAGEDESC, RES_BREAK>{}); + SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> firstSet(*rFormatSet.GetPool()); pMerged->pFirstNode->SwContentNode::GetAttr(firstSet); rFormatSet.Put(firstSet); @@ -402,10 +400,10 @@ namespace sw { { rFormatSet.ClearItem(i); } - SfxItemSet propsSet(*rFormatSet.GetPool(), - svl::Items<RES_PARATR_BEGIN, RES_PAGEDESC, + SfxItemSetFixed<RES_PARATR_BEGIN, RES_PAGEDESC, RES_BREAK+1, RES_FRMATR_END, - XATTR_FILL_FIRST, XATTR_FILL_LAST+1>{}); + XATTR_FILL_FIRST, XATTR_FILL_LAST+1> + propsSet(*rFormatSet.GetPool()); pMerged->pParaPropsNode->SwContentNode::GetAttr(propsSet); rFormatSet.Put(propsSet); return *pMerged->pParaPropsNode; @@ -713,13 +711,13 @@ SwLayoutModeModifier::~SwLayoutModeModifier() void SwLayoutModeModifier::Modify( bool bChgToRTL ) { const_cast<OutputDevice&>(m_rOut).SetLayoutMode( bChgToRTL ? - ComplexTextLayoutFlags::BiDiStrong | ComplexTextLayoutFlags::BiDiRtl : - ComplexTextLayoutFlags::BiDiStrong ); + vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl : + vcl::text::ComplexTextLayoutFlags::BiDiStrong ); } void SwLayoutModeModifier::SetAuto() { - const ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~ComplexTextLayoutFlags::BiDiStrong; + const vcl::text::ComplexTextLayoutFlags nNewLayoutMode = m_nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong; const_cast<OutputDevice&>(m_rOut).SetLayoutMode( nNewLayoutMode ); } @@ -727,11 +725,11 @@ SwDigitModeModifier::SwDigitModeModifier( const OutputDevice& rOutp, LanguageTyp rOut( rOutp ), nOldLanguageType( rOutp.GetDigitLanguage() ) { LanguageType eLang = eCurLang; - if (utl::ConfigManager::IsFuzzing()) + if (comphelper::IsFuzzing()) eLang = LANGUAGE_ENGLISH_US; else { - const SvtCTLOptions::TextNumerals nTextNumerals = SW_MOD()->GetCTLOptions().GetCTLTextNumerals(); + const SvtCTLOptions::TextNumerals nTextNumerals = SvtCTLOptions::GetCTLTextNumerals(); if ( SvtCTLOptions::NUMERALS_HINDI == nTextNumerals ) eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; @@ -795,6 +793,124 @@ SwTextFrame::SwTextFrame(SwTextNode * const pNode, SwFrame* pSib, 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, @@ -813,13 +929,13 @@ void RemoveFootnotesForNode( } const SwFootnoteIdxs &rFootnoteIdxs = rTextNode.GetDoc().GetFootnoteIdxs(); size_t nPos = 0; - sal_uLong const nIndex = rTextNode.GetIndex(); + SwNodeOffset const nIndex = rTextNode.GetIndex(); rFootnoteIdxs.SeekEntry( rTextNode, &nPos ); if (nPos < rFootnoteIdxs.size()) { - while (nPos && &rTextNode == &(rFootnoteIdxs[ nPos ]->GetTextNode())) + while (nPos && rTextNode == (rFootnoteIdxs[ nPos ]->GetTextNode())) --nPos; - if (nPos || &rTextNode != &(rFootnoteIdxs[ nPos ]->GetTextNode())) + if (nPos || rTextNode != (rFootnoteIdxs[ nPos ]->GetTextNode())) ++nPos; } size_t iter(0); @@ -1261,15 +1377,14 @@ TextFrameIndex SwTextFrame::MapModelToView(SwTextNode const*const pNode, sal_Int } else { - assert(static_cast<SwTextNode*>(const_cast<sw::BroadcastingModify*>(SwFrame::GetDep())) == pNode); return TextFrameIndex(nIndex); } } TextFrameIndex SwTextFrame::MapModelToViewPos(SwPosition const& rPos) const { - SwTextNode const*const pNode(rPos.nNode.GetNode().GetTextNode()); - sal_Int32 const nIndex(rPos.nContent.GetIndex()); + SwTextNode const*const pNode(rPos.GetNode().GetTextNode()); + sal_Int32 const nIndex(rPos.GetContentIndex()); return MapModelToView(pNode, nIndex); } @@ -1285,7 +1400,7 @@ void SwTextFrame::SetMergedPara(std::unique_ptr<sw::MergedPara> p) } else { - pFirst->Add(this); // must register at node again + pFirst->Add(*this); // must register at node again } } // postcondition: frame must be listening somewhere @@ -1343,11 +1458,11 @@ SwDoc const& SwTextFrame::GetDoc() const } LanguageType SwTextFrame::GetLangOfChar(TextFrameIndex const nIndex, - sal_uInt16 const nScript, bool const bNoChar) const + sal_uInt16 const nScript, bool const bNoChar, bool const bNoneIfNoHyphenation) 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); + return pos.first->GetLang(pos.second, bNoChar ? 0 : 1, nScript, bNoneIfNoHyphenation); } void SwTextFrame::ResetPreps() @@ -1369,6 +1484,9 @@ bool SwTextFrame::IsHiddenNow() const return true; } + if (SwContentFrame::IsHiddenNow()) + return true; + bool bHiddenCharsHidePara(false); bool bHiddenParaField(false); if (m_pMergedPara) @@ -1434,6 +1552,19 @@ bool SwTextFrame::IsHiddenNow() const ( 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) + { + for (SwContentFrame const* pNext = FindNextCnt(true); + pNext != nullptr; pNext = pNext->FindNextCnt(true)) + { + if (!pNext->IsHiddenNow()) + return true; + } + SAL_INFO("sw.core", "unhiding one body paragraph"); + return false; + } return true; } } @@ -1487,7 +1618,7 @@ void SwTextFrame::HideFootnotes(TextFrameIndex const nStart, TextFrameIndex cons */ bool sw_HideObj( const SwTextFrame& _rFrame, const RndStdIds _eAnchorType, - SwPosition const& rAnchorPos, + SwFormatAnchor const& rFormatAnchor, SwAnchoredObject* _pAnchoredObj ) { bool bRet( true ); @@ -1501,9 +1632,9 @@ bool sw_HideObj( const SwTextFrame& _rFrame, pIDSA->get(DocumentSettingId::CONSIDER_WRAP_ON_OBJECT_POSITION) && _rFrame.IsInDocBody() && !_rFrame.FindNextCnt() ) { - SwTextNode const& rNode(*rAnchorPos.nNode.GetNode().GetTextNode()); + SwTextNode const& rNode(*rFormatAnchor.GetAnchorNode()->GetTextNode()); assert(FrameContainsNode(_rFrame, rNode.GetIndex())); - sal_Int32 const nObjAnchorPos(rAnchorPos.nContent.GetIndex()); + sal_Int32 const nObjAnchorPos(rFormatAnchor.GetAnchorContentOffset()); const sal_Unicode cAnchorChar = nObjAnchorPos < rNode.Len() ? rNode.GetText()[nObjAnchorPos] : 0; @@ -1562,7 +1693,7 @@ void SwTextFrame::HideAndShowObjects() // under certain conditions const RndStdIds eAnchorType( pContact->GetAnchorId() ); if ((eAnchorType != RndStdIds::FLY_AT_CHAR) || - sw_HideObj(*this, eAnchorType, pContact->GetContentAnchor(), + sw_HideObj(*this, eAnchorType, pContact->GetAnchorFormat(), i )) { pContact->MoveObjToInvisibleLayer( pObj ); @@ -1596,13 +1727,13 @@ void SwTextFrame::HideAndShowObjects() { sal_Int32 nHiddenStart; sal_Int32 nHiddenEnd; - const SwPosition& rAnchor = pContact->GetContentAnchor(); + const SwFormatAnchor& rAnchorFormat = pContact->GetAnchorFormat(); SwScriptInfo::GetBoundsOfHiddenRange( - *rAnchor.nNode.GetNode().GetTextNode(), - rAnchor.nContent.GetIndex(), nHiddenStart, nHiddenEnd); + *rAnchorFormat.GetAnchorNode()->GetTextNode(), + rAnchorFormat.GetAnchorContentOffset(), nHiddenStart, nHiddenEnd); // Under certain conditions if ( nHiddenStart != COMPLETE_STRING && bShouldBeHidden && - sw_HideObj(*this, eAnchorType, rAnchor, i)) + sw_HideObj(*this, eAnchorType, rAnchorFormat, i)) { pContact->MoveObjToInvisibleLayer( pObj ); } @@ -1632,15 +1763,15 @@ void SwTextFrame::HideAndShowObjects() * line has to be formatted as well. * nFound is <= nEndLine. */ -TextFrameIndex SwTextFrame::FindBrk(const OUString &rText, +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), rText.getLength() - 1); + const sal_Int32 nEndLine = std::min(sal_Int32(nEnd), sal_Int32(aText.size()) - 1); // Skip all leading blanks. - while( nFound <= nEndLine && ' ' == rText[nFound] ) + while( nFound <= nEndLine && ' ' == aText[nFound] ) { nFound++; } @@ -1649,7 +1780,7 @@ TextFrameIndex SwTextFrame::FindBrk(const OUString &rText, // "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 && ' ' != rText[nFound] ) + while( nFound <= nEndLine && ' ' != aText[nFound] ) { nFound++; } @@ -1659,6 +1790,8 @@ TextFrameIndex SwTextFrame::FindBrk(const OUString &rText, 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 @@ -1709,7 +1842,7 @@ void SwTextFrame::InvalidateRange_( const SwCharRange &aRange, const tools::Long // linelengths are being added, that's why it's negative // if chars have been added and positive, if chars have // deleted - pPara->GetDelta() += nD; + pPara->SetDelta(pPara->GetDelta() + nD); bInv = true; } SwCharRange &rReformat = pPara->GetReformat(); @@ -1735,7 +1868,7 @@ void SwTextFrame::CalcLineSpace() return; if( GetDrawObjs() || - GetTextNodeForParaProps()->GetSwAttrSet().GetLRSpace().IsAutoFirst()) + GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent().IsAutoFirst()) { Init(); return; @@ -1802,48 +1935,48 @@ static void lcl_SetWrong( SwTextFrame& rFrame, SwTextNode const& rNode, { if ( !rFrame.IsFollow() ) { - SwTextNode* pTextNode = const_cast<SwTextNode*>(&rNode); - IGrammarContact* pGrammarContact = getGrammarContact( *pTextNode ); + SwTextNode& rTextNode = const_cast<SwTextNode&>(rNode); + sw::GrammarContact* pGrammarContact = sw::getGrammarContactFor(rTextNode); SwGrammarMarkUp* pWrongGrammar = pGrammarContact ? - pGrammarContact->getGrammarCheck( *pTextNode, false ) : - pTextNode->GetGrammarCheck(); - bool bGrammarProxy = pWrongGrammar != pTextNode->GetGrammarCheck(); + pGrammarContact->getGrammarCheck( rTextNode, false ) : + rTextNode.GetGrammarCheck(); + bool bGrammarProxy = pWrongGrammar != rTextNode.GetGrammarCheck(); if( bMove ) { - if( pTextNode->GetWrong() ) - pTextNode->GetWrong()->Move( nPos, nCnt ); + if( rTextNode.GetWrong() ) + rTextNode.GetWrong()->Move( nPos, nCnt ); if( pWrongGrammar ) pWrongGrammar->MoveGrammar( nPos, nCnt ); - if( bGrammarProxy && pTextNode->GetGrammarCheck() ) - pTextNode->GetGrammarCheck()->MoveGrammar( nPos, nCnt ); - if( pTextNode->GetSmartTags() ) - pTextNode->GetSmartTags()->Move( nPos, nCnt ); + if( bGrammarProxy && rTextNode.GetGrammarCheck() ) + rTextNode.GetGrammarCheck()->MoveGrammar( nPos, nCnt ); + if( rTextNode.GetSmartTags() ) + rTextNode.GetSmartTags()->Move( nPos, nCnt ); } else { - if( pTextNode->GetWrong() ) - pTextNode->GetWrong()->Invalidate( nPos, nCnt ); + if( rTextNode.GetWrong() ) + rTextNode.GetWrong()->Invalidate( nPos, nCnt ); if( pWrongGrammar ) pWrongGrammar->Invalidate( nPos, nCnt ); - if( pTextNode->GetSmartTags() ) - pTextNode->GetSmartTags()->Invalidate( nPos, nCnt ); + if( rTextNode.GetSmartTags() ) + rTextNode.GetSmartTags()->Invalidate( nPos, nCnt ); } const sal_Int32 nEnd = nPos + (nCnt > 0 ? nCnt : 1 ); - if ( !pTextNode->GetWrong() && !pTextNode->IsWrongDirty() ) + if ( !rTextNode.GetWrong() && !rTextNode.IsWrongDirty() ) { - pTextNode->SetWrong( new SwWrongList( WRONGLIST_SPELL ) ); - pTextNode->GetWrong()->SetInvalid( nPos, nEnd ); + rTextNode.SetWrong( std::make_unique<SwWrongList>( WRONGLIST_SPELL ) ); + rTextNode.GetWrong()->SetInvalid( nPos, nEnd ); } - if ( !pTextNode->GetSmartTags() && !pTextNode->IsSmartTagDirty() ) + if ( !rTextNode.GetSmartTags() && !rTextNode.IsSmartTagDirty() ) { - pTextNode->SetSmartTags( new SwWrongList( WRONGLIST_SMARTTAG ) ); - pTextNode->GetSmartTags()->SetInvalid( nPos, nEnd ); + rTextNode.SetSmartTags( std::make_unique<SwWrongList>( WRONGLIST_SMARTTAG ) ); + rTextNode.GetSmartTags()->SetInvalid( nPos, nEnd ); } - pTextNode->SetWrongDirty(SwTextNode::WrongState::TODO); - pTextNode->SetGrammarCheckDirty( true ); - pTextNode->SetWordCountDirty( true ); - pTextNode->SetAutoCompleteWordDirty( true ); - pTextNode->SetSmartTagDirty( true ); + rTextNode.SetWrongDirty(sw::WrongState::TODO); + rTextNode.SetGrammarCheckDirty( true ); + rTextNode.SetWordCountDirty( true ); + rTextNode.SetAutoCompleteWordDirty( true ); + rTextNode.SetSmartTagDirty( true ); } SwRootFrame *pRootFrame = rFrame.getRootFrame(); @@ -1936,7 +2069,7 @@ void UpdateMergedParaForMove(sw::MergedPara & rMerged, // assert(nDeleted == it.second - it.first); if(nDeleted) { - // InvalidateRange/lcl_SetScriptInval was called sufficiently for SwInsText + // 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>); @@ -1950,6 +2083,7 @@ void UpdateMergedParaForMove(sw::MergedPara & rMerged, * 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; @@ -1963,6 +2097,7 @@ static bool hasA11yRelevantAttribute( const std::vector<sal_uInt16>& rWhichFmtAt 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 @@ -1975,16 +2110,55 @@ 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 (auto const pHint = dynamic_cast<sw::LegacyModifyHint const*>(&rHint)) + 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; @@ -2116,7 +2290,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) && 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(std::abs(static_cast<tools::Long>(rNode.GetIndex()) - static_cast<tools::Long>(pMoveText->pDestNode->GetIndex())) == 1); + assert(abs(rNode.GetIndex() - pMoveText->pDestNode->GetIndex()) == SwNodeOffset(1)); UpdateMergedParaForMove(*m_pMergedPara, *this, bRecalcFootnoteFlag, @@ -2131,23 +2305,28 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) // assert(!m_pMergedPara || !getRootFrame()->IsHideRedlines() || !pMoveText->pDestNode->getLayoutFrame(getRootFrame())); } } - else switch (nWhich) + else if (pInsertText) { - case RES_LINENUMBER: + nPos = MapModelToView(&rNode, pInsertText->nPos); + // unlike redlines, inserting into fieldmark must be explicitly handled + bool isHidden(false); + switch (getRootFrame()->GetFieldmarkMode()) { - assert(false); // should have been forwarded to SwContentFrame - InvalidateLineNum(); + 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; } - break; - case RES_INS_TXT: + if (!isHidden) { - sal_Int32 const nNPos = static_cast<const SwInsText*>(pNew)->nPos; - sal_Int32 const nNLen = static_cast<const SwInsText*>(pNew)->nLen; - nPos = MapModelToView(&rNode, nNPos); - nLen = TextFrameIndex(nNLen); + nLen = TextFrameIndex(pInsertText->nLen); if (m_pMergedPara) { - UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, nNPos, nNLen); + UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, pInsertText->nPos, pInsertText->nLen); } if( IsIdxInside( nPos, nLen ) ) { @@ -2160,64 +2339,67 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) Prepare(); } else - InvalidateRange_( SwCharRange( nPos, nLen ), nNLen ); + InvalidateRange_( SwCharRange( nPos, nLen ), pInsertText->nLen ); } - lcl_SetWrong( *this, rNode, nNPos, nNLen, true ); lcl_SetScriptInval( *this, nPos ); bSetFieldsDirty = true; lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator+<sal_Int32, Tag_TextFrameIndex>); } - break; - case RES_DEL_CHR: + 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 { - sal_Int32 const nNPos = static_cast<const SwDelChr*>(pNew)->nPos; - nPos = MapModelToView(&rNode, nNPos); - if (m_pMergedPara) - { - nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, nNPos, 1); - } - else - { - nLen = TextFrameIndex(1); - } - lcl_SetWrong( *this, rNode, nNPos, -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>); - } + nLen = TextFrameIndex(pDeleteText->nLen); } - break; - case RES_DEL_TXT: + const sal_Int32 m = -pDeleteText->nLen; + if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen)) { - sal_Int32 const nNPos = static_cast<const SwDelText*>(pNew)->nStart; - sal_Int32 const nNLen = static_cast<const SwDelText*>(pNew)->nLen; - nPos = MapModelToView(&rNode, nNPos); - if (m_pMergedPara) - { // update merged before doing anything else - nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, nNPos, nNLen); - } + if( !nLen ) + InvalidateSize(); else - { - nLen = TextFrameIndex(nNLen); - } - const sal_Int32 m = -nNLen; - if ((!m_pMergedPara || nLen) && IsIdxInside(nPos, nLen)) - { - if( !nLen ) - InvalidateSize(); - else - InvalidateRange( SwCharRange(nPos, TextFrameIndex(1)), m ); - } - lcl_SetWrong( *this, rNode, nNPos, m, true ); - if (nLen) - { - lcl_SetScriptInval( *this, nPos ); - bSetFieldsDirty = bRecalcFootnoteFlag = true; - lcl_ModifyOfst(*this, nPos, nLen, &o3tl::operator-<sal_Int32, Tag_TextFrameIndex>); - } + 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: @@ -2249,6 +2431,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) } } +#if !ENABLE_WASM_STRIP_ACCESSIBILITY if( isA11yRelevantAttribute( pNewUpdate->getWhichAttr() ) && hasA11yRelevantAttribute( pNewUpdate->getFmtAttrs() ) ) { @@ -2258,6 +2441,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) pViewSh->InvalidateAccessibleParaAttrs( *this ); } } +#endif } break; case RES_OBJECTDYING: @@ -2292,7 +2476,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) nPos = MapModelToView(&rNode, nNPos); if (IsIdxInside(nPos, TextFrameIndex(1))) { - if( pNew == pOld ) + if (SfxPoolItem::areSame( pNew, pOld )) { // only repaint // opt: invalidate window? @@ -2315,7 +2499,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) { // 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()); + assert(rNode == static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetTextNode()); nPos = MapModelToView(&rNode, static_cast<const SwFormatFootnote*>(pNew)->GetTextFootnote()->GetStart()); } @@ -2332,29 +2516,26 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) InvalidateLineNum(); const SwAttrSet& rNewSet = *static_cast<const SwAttrSetChg*>(pNew)->GetChgSet(); - const SfxPoolItem* pItem = nullptr; int nClear = 0; sal_uInt16 nCount = rNewSet.Count(); - if( SfxItemState::SET == rNewSet.GetItemState( RES_TXTATR_FTN, false, &pItem )) + if( const SwFormatFootnote* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FTN, false ) ) { - nPos = MapModelToView(&rNode, - static_cast<const SwFormatFootnote*>(pItem)->GetTextFootnote()->GetStart()); + nPos = MapModelToView(&rNode, pItem->GetTextFootnote()->GetStart()); if (IsIdxInside(nPos, TextFrameIndex(1))) Prepare( PrepareHint::FootnoteInvalidation, pNew ); nClear = 0x01; --nCount; } - if( SfxItemState::SET == rNewSet.GetItemState( RES_TXTATR_FIELD, false, &pItem )) + if( const SwFormatField* pItem = rNewSet.GetItemIfSet( RES_TXTATR_FIELD, false ) ) { - nPos = MapModelToView(&rNode, - static_cast<const SwFormatField*>(pItem)->GetTextField()->GetStart()); + 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( pItem == pOldItem ) + if (SfxPoolItem::areSame( pItem, pOldItem )) { InvalidatePage(); SetCompletePaint(); @@ -2423,7 +2604,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) for ( size_t i = 0; GetDrawObjs() && i < pObjs->size(); ++i ) { SwAnchoredObject* pAnchoredObj = (*pObjs)[i]; - if ( auto pFly = dynamic_cast<SwFlyFrame *>( pAnchoredObj ) ) + if ( auto pFly = pAnchoredObj->DynCastFlyFrame() ) { if( !pFly->IsFlyInContentFrame() ) { @@ -2555,6 +2736,7 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) SwContentFrame::SwClientNotify(rModify, sw::LegacyModifyHint(pOld, pNew)); } +#if !ENABLE_WASM_STRIP_ACCESSIBILITY if (isA11yRelevantAttribute(nWhich)) { SwViewShell* pViewSh = getRootFrame() ? getRootFrame()->GetCurrShell() : nullptr; @@ -2563,25 +2745,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) pViewSh->InvalidateAccessibleParaAttrs( *this ); } } +#endif } break; - - // Process SwDocPosUpdate - case RES_DOCPOS_UPDATE: - { - if( pOld && pNew ) - { - const SwDocPosUpdate *pDocPos = static_cast<const SwDocPosUpdate*>(pOld); - if( pDocPos->nDocPos <= getFrameArea().Top() ) - { - const SwFormatField *pField = static_cast<const SwFormatField *>(pNew); - TextFrameIndex const nIndex(MapModelToView(&rNode, - pField->GetTextField()->GetStart())); - InvalidateRange(SwCharRange(nIndex, TextFrameIndex(1))); - } - } - break; - } case RES_PARATR_SPLIT: if ( GetPrev() ) CheckKeep(); @@ -2608,38 +2774,12 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, SfxHint const& rHint) } // switch if( bSetFieldsDirty ) - GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, 1 ); + GetDoc().getIDocumentFieldsAccess().SetFieldsDirty( true, &rNode, SwNodeOffset(1) ); if ( bRecalcFootnoteFlag ) CalcFootnoteFlag(); } -bool SwTextFrame::GetInfo( SfxPoolItem &rHint ) const -{ - if ( RES_VIRTPAGENUM_INFO == rHint.Which() && IsInDocBody() && ! IsFollow() ) - { - SwVirtPageNumInfo &rInfo = static_cast<SwVirtPageNumInfo&>(rHint); - const SwPageFrame *pPage = FindPageFrame(); - if ( pPage ) - { - if ( pPage == rInfo.GetOrigPage() && !GetPrev() ) - { - // this should be the one - // (could only differ temporarily; is that disturbing?) - rInfo.SetInfo( pPage, this ); - return false; - } - if ( pPage->GetPhyPageNum() < rInfo.GetOrigPage()->GetPhyPageNum() && - (!rInfo.GetPage() || pPage->GetPhyPageNum() > rInfo.GetPage()->GetPhyPageNum())) - { - // this could be the one - rInfo.SetInfo( pPage, this ); - } - } - } - return true; -} - void SwTextFrame::PrepWidows( const sal_uInt16 nNeed, bool bNotify ) { OSL_ENSURE(GetFollow() && nNeed, "+SwTextFrame::Prepare: lost all friends"); @@ -2775,7 +2915,12 @@ bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid, } } - if( !HasPara() && PrepareHint::MustFit != ePrep ) + // 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" ); @@ -2953,7 +3098,7 @@ bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid, SwAnchoredObject* pAnchoredObj = (*GetDrawObjs())[i]; // i#28701 - consider all // to-character anchored objects - if ( pAnchoredObj->GetFrameFormat().GetAnchor().GetAnchorId() + if ( pAnchoredObj->GetFrameFormat()->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR ) { bFormat = true; @@ -2968,7 +3113,13 @@ bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid, if( aTextFly.IsOn() ) { // Does any free-flying frame overlap? - bFormat = aTextFly.Relax() || IsUndersized(); + const bool bRelaxed = aTextFly.Relax(); + bFormat = bRelaxed || IsUndersized(); + if (bRelaxed) + { + // It's possible that pPara was deleted above; retrieve it again + pPara = aAccess.GetPara(); + } } } } @@ -3210,7 +3361,7 @@ bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bS SwTestFormat aSave( this, pPrv, rMaxHeight ); - return SwTextFrame::WouldFit( rMaxHeight, bSplit, true ); + return SwTextFrame::WouldFit(rMaxHeight, bSplit, true, false); } /** @@ -3225,7 +3376,7 @@ bool SwTextFrame::TestFormat( const SwFrame* pPrv, SwTwips &rMaxHeight, bool &bS * * @returns true if I can split */ -bool SwTextFrame::WouldFit( SwTwips &rMaxHeight, bool &bSplit, bool bTst ) +bool SwTextFrame::WouldFit(SwTwips &rMaxHeight, bool &bSplit, bool bTst, bool bMoveBwd) { OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::WouldFit with swapped frame" ); @@ -3308,7 +3459,7 @@ bool SwTextFrame::WouldFit( SwTwips &rMaxHeight, bool &bSplit, bool bTst ) // is breaking necessary? bSplit = !aFrameBreak.IsInside( aLine ); if ( bSplit ) - bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit( aLine, rMaxHeight, bTst ); + bRet = !aFrameBreak.IsKeepAlways() && aFrameBreak.WouldFit(aLine, rMaxHeight, bTst, bMoveBwd); else { // we need the total height including the current line @@ -3322,7 +3473,7 @@ bool SwTextFrame::WouldFit( SwTwips &rMaxHeight, bool &bSplit, bool bTst ) return bRet; } -sal_uInt16 SwTextFrame::GetParHeight() const +SwTwips SwTextFrame::GetParHeight() const { OSL_ENSURE( ! IsVertical() || ! IsSwapped(), "SwTextFrame::GetParHeight with swapped frame" ); @@ -3330,11 +3481,11 @@ sal_uInt16 SwTextFrame::GetParHeight() const if( !HasPara() ) { // For non-empty paragraphs this is a special case // For UnderSized we can simply just ask 1 Twip more - sal_uInt16 nRet = static_cast<sal_uInt16>(getFramePrintArea().SSize().Height()); + sal_uInt16 nRet = o3tl::narrowing<sal_uInt16>(getFramePrintArea().SSize().Height()); if( IsUndersized() ) { if( IsEmpty() || GetText().isEmpty() ) - nRet = static_cast<sal_uInt16>(EmptyHeight()); + nRet = o3tl::narrowing<sal_uInt16>(EmptyHeight()); else ++nRet; } @@ -3343,7 +3494,7 @@ sal_uInt16 SwTextFrame::GetParHeight() const // TODO: Refactor and improve code const SwLineLayout* pLineLayout = GetPara(); - sal_uInt16 nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0; + SwTwips nHeight = pLineLayout ? pLineLayout->GetRealHeight() : 0; // Is this paragraph scrolled? Our height until now is at least // one line height too low then @@ -3485,7 +3636,7 @@ void SwTextFrame::CalcAdditionalFirstLineOffset() nListLevel = MAXLEVEL - 1; const SwNumFormat& rNumFormat = - pTextNode->GetNumRule()->Get( static_cast<sal_uInt16>(nListLevel) ); + pTextNode->GetNumRule()->Get( o3tl::narrowing<sal_uInt16>(nListLevel) ); if ( rNumFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_ALIGNMENT ) return; @@ -3732,19 +3883,33 @@ sal_uInt16 SwTextFrame::FirstLineHeight() const if ( !HasPara() ) { if( IsEmpty() && isFrameAreaDefinitionValid() ) - return IsVertical() ? static_cast<sal_uInt16>(getFramePrintArea().Width()) : static_cast<sal_uInt16>(getFramePrintArea().Height()); + 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; - return pPara->Height(); + // 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_uInt16 SwTextFrame::GetLineCount(TextFrameIndex const nPos) +sal_Int32 SwTextFrame::GetLineCount(TextFrameIndex const nPos) { - sal_uInt16 nRet = 0; + sal_Int32 nRet = 0; SwTextFrame *pFrame = this; do { @@ -3766,7 +3931,7 @@ sal_uInt16 SwTextFrame::GetLineCount(TextFrameIndex const nPos) void SwTextFrame::ChgThisLines() { // not necessary to format here (GetFormatted etc.), because we have to come from there! - sal_uInt32 nNew = 0; + sal_Int32 nNew = 0; const SwLineNumberInfo &rInf = GetDoc().GetLineNumberInfo(); if ( !GetText().isEmpty() && HasPara() ) { @@ -3826,9 +3991,9 @@ void SwTextFrame::RecalcAllLines() if ( IsInTab() ) return; - const sal_uLong nOld = GetAllLines(); + const sal_Int32 nOld = GetAllLines(); const SwFormatLineNumber &rLineNum = GetTextNodeForParaProps()->GetSwAttrSet().GetLineNumber(); - sal_uLong nNewNum; + sal_Int32 nNewNum; const bool bRestart = GetDoc().GetLineNumberInfo().IsRestartEachPage(); if ( !IsFollow() && rLineNum.GetStartValue() && rLineNum.IsCount() ) @@ -3892,7 +4057,7 @@ void SwTextFrame::VisitPortions( SwPortionHandler& rPH ) const pPor = pPor->GetNextPortion(); } - rPH.LineBreak(pLine->Width()); + rPH.LineBreak(); pLine = pLine->GetNext(); } } diff --git a/sw/source/core/text/txtftn.cxx b/sw/source/core/text/txtftn.cxx index 5b4b9f7492e0..c1fa749c93f5 100644 --- a/sw/source/core/text/txtftn.cxx +++ b/sw/source/core/text/txtftn.cxx @@ -21,6 +21,7 @@ #include <string_view> +#include <utility> #include <viewsh.hxx> #include <doc.hxx> #include <IDocumentLayoutAccess.hxx> @@ -53,6 +54,13 @@ #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> @@ -240,7 +248,7 @@ static SwTwips lcl_GetFootnoteLower( const SwTextFrame* pFrame, SwTwips nLower ) { SwRect aRect( pAnchoredObj->GetObjRect() ); - auto pFlyFrame = dynamic_cast<SwFlyFrame*>( pAnchoredObj ); + auto pFlyFrame = pAnchoredObj->DynCastFlyFrame(); if ( !pFlyFrame || pFlyFrame->isFrameAreaDefinitionValid() ) { @@ -288,7 +296,7 @@ SwTwips SwTextFrame::GetFootnoteLine( const SwTextFootnote *pFootnote ) const &pFootnote->GetTextNode(), pFootnote->GetStart())); aLine.CharToLine( nPos ); - nRet = aLine.Y() + SwTwips(aLine.GetLineHeight()); + nRet = aLine.Y() + aLine.GetLineHeight(); if( IsVertical() ) nRet = SwitchHorizontalToVertical( nRet ); } @@ -656,7 +664,7 @@ void SwTextFrame::ConnectFootnote( SwTextFootnote *pFootnote, const SwTwips nDea mbInFootnoteConnect = false; return; } - else if( pSrcFrame ) + else if (pSrcFrame && pFootnoteFrame) { SwFootnoteBossFrame *pFootnoteBoss = pFootnoteFrame->FindFootnoteBossFrame(); if( !pFootnoteBoss->IsInSct() || @@ -964,16 +972,15 @@ SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK ); pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL ); - const auto xAnchor = rFootnote.getAnchor(*pDoc); - uno::Reference<beans::XPropertySet> xAnchorProps(xAnchor, uno::UNO_QUERY); - if (xAnchorProps.is()) + const rtl::Reference<SwXTextRange> xAnchor = rFootnote.getAnchor(*pDoc); + if (xAnchor.is()) { - auto aAny = xAnchorProps->getPropertyValue("CharFontCharSet"); + auto aAny = xAnchor->getPropertyValue("CharFontCharSet"); sal_Int16 eCharSet; if ((aAny >>= eCharSet) && eCharSet == awt::CharSet::SYMBOL) { OUString aFontName; - aAny = xAnchorProps->getPropertyValue("CharFontName"); + aAny = xAnchor->getPropertyValue("CharFontName"); if (aAny >>= aFontName) { pNumFnt->SetName(aFontName, SwFontScript::Latin); @@ -990,6 +997,36 @@ SwNumberPortion *SwTextFormatter::NewFootnoteNumPortion( SwTextFormatInfo const 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; @@ -1181,7 +1218,7 @@ TextFrameIndex SwTextFormatter::FormatQuoVadis(TextFrameIndex const nOffset) if( nDiff < 0 ) { nLastLeft = pQuo->GetAscent(); - nQuoWidth = static_cast<sal_uInt16>(-nDiff + nLastLeft); + nQuoWidth = o3tl::narrowing<sal_uInt16>(-nDiff + nLastLeft); } else { @@ -1313,17 +1350,14 @@ SwFootnoteSave::SwFootnoteSave(const SwTextSizeInfo& rInf, const SwTextFootnote* } // set the correct rotation at the footnote font - const SfxPoolItem* pItem; - if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE, - true, &pItem )) - m_pFnt->SetVertical(static_cast<const SvxCharRotateItem*>(pItem)->GetValue(), + 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( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_BACKGROUND, - true, &pItem )) - m_pFnt->SetBackColor(static_cast<const SvxBrushItem*>(pItem)->GetColor()); + if( const SvxBrushItem* pItem = rSet.GetItemIfSet( RES_CHRATR_BACKGROUND ) ) + m_pFnt->SetBackColor(pItem->GetColor()); } else m_pFnt = nullptr; @@ -1408,8 +1442,8 @@ SwFieldPortion *SwQuoVadisPortion::Clone( const OUString &rExpand ) const return new SwQuoVadisPortion( rExpand, m_aErgo ); } -SwQuoVadisPortion::SwQuoVadisPortion( const OUString &rExp, const OUString& rStr ) - : SwFieldPortion( rExp ), m_aErgo(rStr) +SwQuoVadisPortion::SwQuoVadisPortion( const OUString &rExp, OUString aStr ) + : SwFieldPortion( rExp ), m_aErgo(std::move(aStr)) { SetLen(TextFrameIndex(0)); SetWhichPor( PortionType::QuoVadis ); diff --git a/sw/source/core/text/txthyph.cxx b/sw/source/core/text/txthyph.cxx index 7f9f6f6cd611..a718296e8a2e 100644 --- a/sw/source/core/text/txthyph.cxx +++ b/sw/source/core/text/txthyph.cxx @@ -32,7 +32,6 @@ 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; @@ -355,6 +354,16 @@ 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(); diff --git a/sw/source/core/text/txtinit.cxx b/sw/source/core/text/txtinit.cxx index b8a6540f6318..2b8bb2e95d21 100644 --- a/sw/source/core/text/txtinit.cxx +++ b/sw/source/core/text/txtinit.cxx @@ -40,7 +40,7 @@ void TextInit_() 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" + , "static SwTextFrame::s_pTextCache"_ostr #endif ); SwTextFrame::SetTextCache( pTextCache ); diff --git a/sw/source/core/text/txttab.cxx b/sw/source/core/text/txttab.cxx index 8e8203c72270..e19dd8efe9b4 100644 --- a/sw/source/core/text/txttab.cxx +++ b/sw/source/core/text/txttab.cxx @@ -40,27 +40,39 @@ * 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, const SwTwips nRight ) const +const SvxTabStop* SwLineInfo::GetTabStop(const SwTwips nSearchPos, SwTwips& nRight) const { - for( sal_uInt16 i = 0; i < m_pRuler->Count(); ++i ) + for( sal_uInt16 i = 0; i < m_oRuler->Count(); ++i ) { - const SvxTabStop &rTabStop = m_pRuler->operator[](i); - if( rTabStop.GetTabPos() > SwTwips(nRight) ) + 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_pRuler->Count(); + 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())) @@ -78,8 +90,6 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto // nTabLeft: The absolute value, the tab stops are relative to: Tabs origin. // #i91133# - const bool bTabsRelativeToIndent = - m_pFrame->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT); const SwTwips nTabLeft = bRTL ? m_pFrame->getFrameArea().Right() - ( bTabsRelativeToIndent ? GetTabLeft() : 0 ) @@ -121,6 +131,7 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto } SwTwips nNextPos = 0; + bool bAbsoluteNextPos = false; // #i24363# tab stops relative to indent // nSearchPos: The current position relative to the tabs origin @@ -132,8 +143,14 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto // 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 ( pTabStop ) + if (!nMyRight) + nMyRight = nOldRight; + if (pTabStop) { cFill = ' ' != pTabStop->GetFill() ? pTabStop->GetFill() : 0; cDec = pTabStop->GetDecimal(); @@ -144,6 +161,21 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto //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 @@ -152,9 +184,9 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto if( USHRT_MAX == nDefTabDist ) { const SvxTabStopItem& rTab = - m_pFrame->GetAttrSet()->GetPool()->GetDefaultItem( RES_PARATR_TABSTOP ); + m_pFrame->GetAttrSet()->GetPool()->GetUserOrPoolDefaultItem( RES_PARATR_TABSTOP ); if( rTab.Count() ) - nDefTabDist = static_cast<sal_uInt16>(rTab[0].GetTabPos()); + nDefTabDist = o3tl::narrowing<sal_uInt16>(rTab[0].GetTabPos()); else nDefTabDist = SVX_TAB_DEFDIST; m_aLineInf.SetDefTabStop( nDefTabDist ); @@ -247,7 +279,8 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto } } - nNextPos += bRTL ? nLinePos - nTabLeft : nTabLeft - nLinePos; + if (!bAbsoluteNextPos) + nNextPos += bRTL ? nLinePos - nTabLeft : nTabLeft - nLinePos; OSL_ENSURE( nNextPos >= 0, "GetTabStop: Don't go back!" ); nNewTabPos = sal_uInt16(nNextPos); } @@ -287,6 +320,8 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto } } } + if (pTabPor) + rInf.UpdateTabSeen(pTabPor->GetWhichPor()); return pTabPor; } @@ -295,11 +330,11 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto * The base class is initialized without setting anything */ SwTabPortion::SwTabPortion( const sal_uInt16 nTabPosition, const sal_Unicode cFillChar, const bool bAutoTab ) - : SwFixPortion(), m_nTabPos(nTabPosition), m_cFill(cFillChar), m_bAutoTabStop( bAutoTab ) + : m_nTabPos(nTabPosition), m_cFill(cFillChar), m_bAutoTabStop( bAutoTab ) { mnLineLength = TextFrameIndex(1); OSL_ENSURE(!IsFilled() || ' ' != m_cFill, "SwTabPortion::CTOR: blanks ?!"); - SetWhichPor( PortionType::Table ); + SetWhichPor( PortionType::Tab ); } bool SwTabPortion::Format( SwTextFormatInfo &rInf ) @@ -323,12 +358,14 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) OSL_ENSURE( rInf.X() <= GetTabPos(), "SwTabPortion::PreFormat: rush hour" ); // Here we settle down ... - SetFix( static_cast<sal_uInt16>(rInf.X()) ); + 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 @@ -339,14 +376,14 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) // #i89179# // tab portion representing the list tab of a list label gets the // same font as the corresponding number portion - std::unique_ptr< SwFontSave > pSave; + 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(); - pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) ); + oSave.emplace( rInf, const_cast<SwFont*>(pNumberPortionFont) ); } OUString aTmp( ' ' ); SwTextSizeInfo aInf( rInf, &aTmp ); @@ -381,13 +418,23 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) case PortionType::TabLeft: { // handle this case in PostFormat - if( bTabOverMargin && !m_bAutoTabStop && GetTabPos() > rInf.Width() ) + if ((bTabOverMargin || bTabOverSpacing) && GetTabPos() > rInf.Width() + && (!m_bAutoTabStop || (!bTabOverMargin && rInf.X() > rInf.Width()))) { - rInf.SetLastTab( this ); - break; + if (bTabOverMargin || GetTabPos() < nTextFrameWidth) + { + rInf.SetLastTab(this); + break; + } + else + { + assert(!bTabOverMargin && bTabOverSpacing && GetTabPos() >= nTextFrameWidth); + bFull = true; + break; + } } - PrtWidth( static_cast<sal_uInt16>(GetTabPos() - rInf.X()) ); + PrtWidth( o3tl::narrowing<sal_uInt16>(GetTabPos() - rInf.X()) ); bFull = rInf.Width() <= rInf.X() + PrtWidth(); // In tabulator compatibility mode, we reset the bFull flag @@ -396,7 +443,7 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) bool bAtParaEnd = rInf.GetIdx() + GetLen() == TextFrameIndex(rInf.GetText().getLength()); if ( bFull && bTabCompat && ( ( bTabOverflow && ( rInf.IsTabOverflow() || !m_bAutoTabStop ) ) || bAtParaEnd ) && - GetTabPos() >= rInf.GetTextFrame()->getFrameArea().Width() ) + GetTabPos() >= nTextFrameWidth) { bFull = false; if ( bTabOverflow && !m_bAutoTabStop ) @@ -417,7 +464,7 @@ bool SwTabPortion::PreFormat( SwTextFormatInfo &rInf ) // line if there is a fly reducing the line width: !rInf.GetFly() ) { - PrtWidth( static_cast<sal_uInt16>(rInf.Width() - rInf.X()) ); + PrtWidth( o3tl::narrowing<sal_uInt16>(rInf.Width() - rInf.X()) ); SetFixWidth( PrtWidth() ); } else @@ -443,13 +490,19 @@ 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() : std::min(GetTabPos(), rInf.Width()); + 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; @@ -462,7 +515,7 @@ bool SwTabPortion::PostFormat( SwTextFormatInfo &rInf ) const PortionType nWhich = GetWhichPor(); const bool bTabCompat = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT); - if ( bTabOverMargin && PortionType::TabLeft == nWhich ) + if ((bTabOverMargin || bTabOverSpacing) && PortionType::TabLeft == nWhich) { nPorWidth = 0; } @@ -489,7 +542,7 @@ bool SwTabPortion::PostFormat( SwTextFormatInfo &rInf ) // centered tabs are problematic: // We have to detect how much fits into the line. sal_uInt16 nNewWidth = nPorWidth /2; - if( !bTabOverMargin && nNewWidth > rInf.Width() - nRight ) + if (!bTabOverMargin && !bTabOverSpacing && nNewWidth > rInf.Width() - nRight) nNewWidth = nPorWidth - (rInf.Width() - nRight); nPorWidth = nNewWidth; } @@ -524,7 +577,7 @@ 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::unique_ptr< SwFontSave > pSave; + std::optional< SwFontSave > oSave; bool bAfterNumbering = false; if (GetLen() == TextFrameIndex(0)) { @@ -536,7 +589,7 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const { const SwFont* pNumberPortionFont = static_cast<const SwNumberPortion*>(pPrevPortion)->GetFont(); - pSave.reset( new SwFontSave( rInf, const_cast<SwFont*>(pNumberPortionFont) ) ); + oSave.emplace( rInf, const_cast<SwFont*>(pNumberPortionFont) ); bAfterNumbering = true; } } @@ -553,7 +606,7 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const { // filled tabs are shaded in gray if( IsFilled() ) - rInf.DrawViewOpt( *this, PortionType::Table ); + rInf.DrawViewOpt( *this, PortionType::Tab ); else rInf.DrawTab( *this ); } @@ -569,7 +622,7 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const { // Always with kerning, also on printer! sal_uInt16 nChar = Width() / nCharWidth; - OUStringBuffer aBuf; + OUStringBuffer aBuf(nChar); comphelper::string::padToLength(aBuf, nChar, ' '); rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0), TextFrameIndex(nChar), true); @@ -591,7 +644,7 @@ void SwTabPortion::Paint( const SwTextPaintInfo &rInf ) const sal_uInt16 nChar = Width() / nCharWidth; if ( m_cFill == '_' ) ++nChar; // to avoid gaps - OUStringBuffer aBuf; + OUStringBuffer aBuf(nChar); comphelper::string::padToLength(aBuf, nChar, m_cFill); rInf.DrawText(aBuf.makeStringAndClear(), *this, TextFrameIndex(0), TextFrameIndex(nChar), true); @@ -604,7 +657,7 @@ void SwAutoTabDecimalPortion::Paint( const SwTextPaintInfo & ) const void SwTabPortion::HandlePortion( SwPortionHandler& rPH ) const { - rPH.Text( GetLen(), GetWhichPor(), Height(), Width() ); + 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 index 141fd9437a84..5446ac8cec15 100644 --- a/sw/source/core/text/widorp.cxx +++ b/sw/source/core/text/widorp.cxx @@ -36,6 +36,12 @@ #include <sectfrm.hxx> #include <ftnfrm.hxx> #include <pagefrm.hxx> +#include <IDocumentSettingAccess.hxx> +#include <sortedobjs.hxx> +#include <anchoredobject.hxx> +#include <flyfrm.hxx> + +#include <com/sun/star/text/ParagraphHyphenationKeepType.hpp> #undef WIDOWTWIPS @@ -127,15 +133,54 @@ bool SwTextFrameBreak::IsInside( SwTextMargin const &rLine ) const 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()) { @@ -194,6 +239,14 @@ bool SwTextFrameBreak::IsBreakNow( SwTextMargin &rLine ) 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() ) ) { @@ -267,7 +320,17 @@ WidowsAndOrphans::WidowsAndOrphans( SwTextFrame *pNewFrame, const SwTwips nRst, bool bResetFlags = false; - if ( m_pFrame->IsInTab() ) + 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: @@ -397,15 +460,80 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) const SwTwips nChg = aRectFnSet.YDiff( nTmpY, nDocPrtTop + nOldHeight ); - // below the Widows-threshold... - if( rLine.GetLineNr() >= m_nWidLines ) + // hyphenation-keep: truncate a hyphenated line at the end of + // the column, page or spread (but not more) + int nExtraWidLines = 0; + if( rLine.GetLineNr() >= m_nWidLines && pMaster->HasPara() && + ( rLine.GetLineNr() == m_nWidLines || !rLine.GetCurr()->IsEndHyph() ) ) + { + SwParaPortion *pMasterPara = pMaster->GetPara(); + const SwAttrSet& rSet = pFrame->GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxHyphenZoneItem &rAttr = rSet.GetHyphenZone(); + + bool bKeep = rAttr.IsHyphen() && rAttr.IsKeep() && rAttr.GetKeepType(); + + // if PAGE or SPREAD, allow hyphenation at bottom of the non-last columns + if( bKeep && pFrame->IsInSct() && ( + rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::SPREAD || + rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::PAGE ) ) + { + const SwSectionFrame* const pSct = pFrame->FindSctFrame(); + // multi-column section + if ( pSct->Lower()->IsColumnFrame() && pSct->Lower()->GetNext() ) + { + const SwFrame *pCol = pFrame->FindColFrame(); + // and not in the last column + if (pCol && !pCol->GetNext()) + { + bKeep = false; + } + } + } + + // if SPREAD, allow hyphenation at bottom of left pages + if ( bKeep && rAttr.GetKeepType() == css::text::ParagraphHyphenationKeepType::SPREAD && + pFrame->FindPageFrame()->OnRightPage() ) + { + bKeep = false; + } + + if ( bKeep && pMasterPara && pMasterPara->GetNext() ) + { + SwLineLayout * pNext = pMasterPara->GetNext(); + SwLineLayout * pCurr = pNext; + SwLineLayout * pPrev = pNext; + while ( pNext->GetNext() ) + { + pPrev = pCurr; + pCurr = pNext; + pNext = pNext->GetNext(); + } + // hyphenated line, but not the last remaining one + if ( pNext->IsEndHyph() && !pNext->IsLastHyph() ) + { + nExtraWidLines = rLine.GetLineNr() - m_nWidLines + 1; + // set remaining line to "last remaining hyphenated line" + // to avoid truncating multiple hyphenated lines instead + // of a single one + if ( pCurr->IsEndHyph() ) + pCurr->SetLastHyph( true ); + // also unset the line before the remaining one + // TODO: check also the line after the truncated line? + if ( pPrev->IsLastHyph() ) + pPrev->SetLastHyph( false ); + } + } + } + + // below the Widows-threshold..., with an extra hyphenated line + if( rLine.GetLineNr() >= m_nWidLines + nExtraWidLines ) { // 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( rLine.GetLineNr() > m_nWidLines + nExtraWidLines && 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 @@ -415,7 +543,7 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) const SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(), aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) ); if ( nTmpRstHeight >= - SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) ) + rLine.GetInfo().GetParaPortion()->Height() ) { pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); pMaster->InvalidateSize_(); @@ -437,7 +565,7 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) { SwTwips nTmpRstHeight = aRectFnSet.BottomDist( pMaster->getFrameArea(), aRectFnSet.GetPrtBottom(*pMaster->GetUpper()) ); - if( nTmpRstHeight >= SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) ) + if( nTmpRstHeight >= rLine.GetInfo().GetParaPortion()->Height() ) { pMaster->Prepare( PrepareHint::AdjustSizeWithoutFormatting ); pMaster->InvalidateSize_(); @@ -461,14 +589,14 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) // could result in multiple lines for us. // Therefore, the CalcFollow() remains in control until the Follow got all // necessary lines. - sal_uInt16 nNeed = 1; // was: nWidLines - rLine.GetLineNr(); + sal_Int32 nNeed = 1; // was: nWidLines - rLine.GetLineNr(); // Special case: Master cannot give lines to follow // i#91421 if ( !pMaster->GetIndPrev() ) { pMaster->ChgThisLines(); - sal_uLong nLines = pMaster->GetThisLines(); + sal_Int32 nLines = pMaster->GetThisLines(); if(nLines == 0 && pMaster->HasPara()) { const SwParaPortion *pMasterPara = pMaster->GetPara(); @@ -477,20 +605,68 @@ bool WidowsAndOrphans::FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine ) } 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; } -bool WidowsAndOrphans::WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTst ) +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_uInt16 nLineCnt = rLine.GetLineNr(); + sal_Int32 nLineCnt = rLine.GetLineNr(); // First satisfy the Orphans-rule and the wish for initials ... const sal_uInt16 nMinLines = std::max( GetOrphansLines(), rLine.GetDropLines() ); @@ -500,11 +676,26 @@ bool WidowsAndOrphans::WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool rLine.Top(); SwTwips nLineSum = rLine.GetLineHeight(); - while( nMinLines > rLine.GetLineNr() ) + // 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() ) - return false; + { + if (nMinLines > rLine.GetLineNr()) + return false; + else + break; + } nLineSum += rLine.GetLineHeight(); + if (!hasNonFly) + { + hasNonFly = ::sw::FindNonFlyPortion(*rLine.GetCurr()); + } } // We do not fit diff --git a/sw/source/core/text/widorp.hxx b/sw/source/core/text/widorp.hxx index c24bd5273f52..996a7fc913bc 100644 --- a/sw/source/core/text/widorp.hxx +++ b/sw/source/core/text/widorp.hxx @@ -18,11 +18,11 @@ */ #pragma once -class SwTextFrame; - #include <swtypes.hxx> #include "itrtxt.hxx" +class SwTextFrame; + class SwTextFrameBreak { private: @@ -63,7 +63,7 @@ public: void ClrOrphLines(){ m_nOrphLines = 0; } bool FindBreak( SwTextFrame *pFrame, SwTextMargin &rLine, bool bHasToFit ); - bool WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTest ); + 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 ) @@ -79,4 +79,10 @@ public: } }; +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 index 5a70c7be9507..00be8d5fac4f 100644 --- a/sw/source/core/text/wrong.cxx +++ b/sw/source/core/text/wrong.cxx @@ -22,25 +22,26 @@ #include <SwGrammarMarkUp.hxx> #include <ndtxt.hxx> #include <txtfrm.hxx> +#include <utility> #include <osl/diagnose.h> -SwWrongArea::SwWrongArea( const OUString& rType, WrongListType listType, +SwWrongArea::SwWrongArea( OUString aType, WrongListType listType, css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, sal_Int32 nPos, sal_Int32 nLen) -: maType(rType), mnPos(nPos), mnLen(nLen), mpSubList(nullptr) +: maType(std::move(aType)), mxPropertyBag(xPropertyBag), mnPos(nPos), mnLen(nLen), mpSubList(nullptr) { mColor = getWrongAreaColor(listType, xPropertyBag); mLineType = getWrongAreaLineType(listType, xPropertyBag); } -SwWrongArea::SwWrongArea( const OUString& rType, +SwWrongArea::SwWrongArea( OUString aType, css::uno::Reference< css::container::XStringKeyMap > const & xPropertyBag, sal_Int32 nPos, sal_Int32 nLen, SwWrongList* pSubList) -: maType(rType), mnPos(nPos), mnLen(nLen), mpSubList(pSubList), mLineType(WRONGAREA_NONE) +: maType(std::move(aType)), mxPropertyBag(xPropertyBag), mnPos(nPos), mnLen(nLen), mpSubList(pSubList), mLineType(WRONGAREA_NONE) { if (pSubList != nullptr) { @@ -203,7 +204,7 @@ sal_uInt16 SwWrongList::GetWrongPos( sal_Int32 nValue ) const return (rST.mnPos <= nValue && nValue < rST.mnPos + rST.mnLen) || (rST.mnPos > nValue); }); - return static_cast<sal_uInt16>(std::distance(maList.begin(), aIter)); + return o3tl::narrowing<sal_uInt16>(std::distance(maList.begin(), aIter)); } --nMax; @@ -442,9 +443,9 @@ bool SwWrongList::InvalidateWrong( ) return false; } -SwWrongList* SwWrongList::SplitList( sal_Int32 nSplitPos ) +std::unique_ptr<SwWrongList> SwWrongList::SplitList( sal_Int32 nSplitPos ) { - SwWrongList *pRet = nullptr; + std::unique_ptr<SwWrongList> pRet; sal_uInt16 nLst = 0; while( nLst < Count() && Pos( nLst ) < nSplitPos ) ++nLst; @@ -462,9 +463,9 @@ SwWrongList* SwWrongList::SplitList( sal_Int32 nSplitPos ) if( nLst ) { if( WRONGLIST_GRAMMAR == GetWrongListType() ) - pRet = new SwGrammarMarkUp(); + pRet.reset(new SwGrammarMarkUp()); else - pRet = new SwWrongList( GetWrongListType() ); + 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 ); @@ -559,7 +560,7 @@ void SwWrongList::Remove(sal_uInt16 nIdx, sal_uInt16 nLen ) i1 += nIdx; std::vector<SwWrongArea>::iterator i2 = i1; - if ( nIdx + nLen >= static_cast<sal_uInt16>(maList.size()) ) + if ( nIdx + nLen >= o3tl::narrowing<sal_uInt16>(maList.size()) ) i2 = maList.end(); // robust else i2 += nLen; @@ -602,10 +603,10 @@ void SwWrongList::RemoveEntry( sal_Int32 nBegin, sal_Int32 nEnd ) { return (rST.mnPos != nBegin) || ((rST.mnPos + rST.mnLen) != nEnd); }); } - auto nDel = static_cast<sal_uInt16>(std::distance(aDelIter, aIter)); + auto nDel = o3tl::narrowing<sal_uInt16>(std::distance(aDelIter, aIter)); if( nDel ) { - auto nDelPos = static_cast<sal_uInt16>(std::distance(maList.cbegin(), aDelIter)); + auto nDelPos = o3tl::narrowing<sal_uInt16>(std::distance(maList.cbegin(), aDelIter)); Remove( nDelPos, nDel ); } } diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx index fd3d0ef4d62e..86d736a0c8b9 100644 --- a/sw/source/core/text/xmldump.cxx +++ b/sw/source/core/text/xmldump.cxx @@ -26,273 +26,134 @@ #include <libxml/xmlwriter.h> #include <SwPortionHandler.hxx> #include <view.hxx> +#include <flyfrms.hxx> #include <svx/svdobj.hxx> -namespace { +#include "porlay.hxx" -class XmlPortionDumper:public SwPortionHandler +const char* sw::PortionTypeToString(PortionType nType) { - private: - xmlTextWriterPtr m_Writer; - TextFrameIndex m_Ofs; - const OUString& m_rText; - OUString m_aLine; - - static const char* getTypeName(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::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::Table: - return "PortionType::Table"; - - 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"; - } - } - - public: - explicit XmlPortionDumper(xmlTextWriterPtr some_writer, const OUString& rText) - : m_Writer(some_writer) - , m_Ofs(0) - , m_rText(rText) - { - } - - /** - @param nLength - length of this portion in the model string - @param rText - text which is painted on-screen - */ - virtual void Text( TextFrameIndex nLength, - PortionType nType, - sal_Int32 nHeight, - sal_Int32 nWidth) override - { - (void)xmlTextWriterStartElement(m_Writer, BAD_CAST("Text")); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nLength"), "%i", - static_cast<int>(static_cast<sal_Int32>(nLength))); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nType"), "%s", - getTypeName(nType)); - if (nHeight > 0) - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nHeight"), "%i", - static_cast<int>(nHeight)); - if (nWidth > 0) - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nWidth"), "%i", - static_cast<int>(nWidth)); - if (nLength > TextFrameIndex(0)) - (void)xmlTextWriterWriteAttribute( - m_Writer, BAD_CAST("Portion"), - BAD_CAST(m_rText.copy(sal_Int32(m_Ofs), sal_Int32(nLength)).toUtf8().getStr())); - - (void)xmlTextWriterEndElement(m_Writer); - m_aLine += m_rText.subView(sal_Int32(m_Ofs), sal_Int32(nLength)); - m_Ofs += nLength; - } - - /** - @param nLength - length of this portion in the model string - @param rText - text which is painted on-screen - @param nType - type of this portion - @param nHeight - font size of the painted text - */ - virtual void Special( TextFrameIndex nLength, - const OUString & rText, - PortionType nType, - sal_Int32 nHeight, - sal_Int32 nWidth, - const SwFont* pFont ) override - { - (void)xmlTextWriterStartElement(m_Writer, BAD_CAST("Special")); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nLength"), "%i", - static_cast<int>(static_cast<sal_Int32>(nLength))); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nType"), "%s", - getTypeName(nType)); - OString sText8 = OUStringToOString( rText, RTL_TEXTENCODING_UTF8 ); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("rText"), "%s", sText8.getStr()); - - if (nHeight > 0) - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nHeight"), "%i", - static_cast<int>(nHeight)); - - if (nWidth > 0) - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nWidth"), "%i", - static_cast<int>(nWidth)); - - if (pFont) - pFont->dumpAsXml(m_Writer); - - (void)xmlTextWriterEndElement(m_Writer); - m_aLine += rText; - m_Ofs += nLength; - } - - virtual void LineBreak( sal_Int32 nWidth ) override - { - (void)xmlTextWriterStartElement(m_Writer, BAD_CAST("LineBreak")); - if (nWidth > 0) - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nWidth"), "%i", - static_cast<int>(nWidth)); - if (!m_aLine.isEmpty()) - { - (void)xmlTextWriterWriteAttribute(m_Writer, BAD_CAST("Line"), - BAD_CAST(m_aLine.toUtf8().getStr())); - m_aLine.clear(); - } - (void)xmlTextWriterEndElement(m_Writer); - } - - /** - * @param nLength - * number of 'model string' characters to be skipped - */ - virtual void Skip( TextFrameIndex nLength ) override - { - (void)xmlTextWriterStartElement(m_Writer, BAD_CAST("Skip")); - (void)xmlTextWriterWriteFormatAttribute(m_Writer, BAD_CAST("nLength"), "%i", - static_cast<int>(static_cast<sal_Int32>(nLength))); - (void)xmlTextWriterEndElement(m_Writer); - m_Ofs += nLength; - } - - virtual void Finish( ) override + switch (nType) { - (void)xmlTextWriterStartElement(m_Writer, BAD_CAST("Finish")); - (void)xmlTextWriterEndElement(m_Writer); - } - -}; - - xmlTextWriterPtr lcl_createDefaultWriter() - { - xmlTextWriterPtr writer = xmlNewTextWriterFilename( "layout.xml", 0 ); - xmlTextWriterSetIndent(writer,1); - (void)xmlTextWriterSetIndentString(writer, BAD_CAST(" ")); - (void)xmlTextWriterStartDocument( writer, nullptr, nullptr, nullptr ); - return writer; - } - - void lcl_freeWriter( xmlTextWriterPtr writer ) - { - (void)xmlTextWriterEndDocument( writer ); - xmlFreeTextWriter( writer ); - } + 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"; + } + return "Unknown"; } void SwFrame::dumpTopMostAsXml(xmlTextWriterPtr writer) const @@ -306,194 +167,6 @@ void SwFrame::dumpTopMostAsXml(xmlTextWriterPtr writer) const pFrame->dumpAsXml(writer); } -void SwFrame::dumpAsXml( xmlTextWriterPtr writer ) const -{ - bool bCreateWriter = ( nullptr == writer ); - if ( bCreateWriter ) - writer = lcl_createDefaultWriter(); - - const char *name = nullptr; - - switch ( GetType( ) ) - { - case SwFrameType::Root: - name = "root"; - break; - case SwFrameType::Page: - name = "page"; - break; - case SwFrameType::Column: - name = "column"; - break; - case SwFrameType::Header: - name = "header"; - break; - case SwFrameType::Footer: - name = "footer"; - break; - case SwFrameType::FtnCont: - name = "ftncont"; - break; - case SwFrameType::Ftn: - name = "ftn"; - break; - case SwFrameType::Body: - name = "body"; - break; - case SwFrameType::Fly: - name = "fly"; - break; - case SwFrameType::Section: - name = "section"; - break; - case SwFrameType::Tab: - name = "tab"; - break; - case SwFrameType::Row: - name = "row"; - break; - case SwFrameType::Cell: - name = "cell"; - break; - case SwFrameType::Txt: - name = "txt"; - break; - case SwFrameType::NoTxt: - name = "notxt"; - break; - default: break; - } - - if ( name != nullptr ) - { - (void)xmlTextWriterStartElement( writer, reinterpret_cast<const xmlChar *>(name) ); - - dumpAsXmlAttributes( writer ); - - if (IsRootFrame()) - { - const SwRootFrame* pRootFrame = static_cast<const SwRootFrame*>(this); - (void)xmlTextWriterStartElement(writer, BAD_CAST("sfxViewShells")); - SwView* pView = static_cast<SwView*>(SfxViewShell::GetFirst(true, checkSfxViewShell<SwView>)); - while (pView) - { - if (pRootFrame->GetCurrShell()->GetSfxViewShell() && pView->GetObjectShell() == pRootFrame->GetCurrShell()->GetSfxViewShell()->GetObjectShell()) - pView->dumpAsXml(writer); - pView = static_cast<SwView*>(SfxViewShell::GetNext(*pView, true, checkSfxViewShell<SwView>)); - } - (void)xmlTextWriterEndElement(writer); - } - - if (IsPageFrame()) - { - const SwPageFrame* pPageFrame = static_cast<const SwPageFrame*>(this); - (void)xmlTextWriterStartElement(writer, BAD_CAST("page_status")); - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyLayout"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyLayout()).getStr())); - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyContent"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyContent()).getStr())); - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidFlyInCnt"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidFlyInCnt()).getStr())); - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidLayout"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidLayout()).getStr())); - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("ValidContent"), BAD_CAST(OString::boolean(!pPageFrame->IsInvalidContent()).getStr())); - (void)xmlTextWriterEndElement(writer); - (void)xmlTextWriterStartElement(writer, BAD_CAST("page_info")); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("phyNum"), "%d", pPageFrame->GetPhyPageNum()); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("virtNum"), "%d", pPageFrame->GetVirtPageNum()); - OUString aFormatName = pPageFrame->GetPageDesc()->GetName(); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("pageDesc"), "%s", BAD_CAST(OUStringToOString(aFormatName, RTL_TEXTENCODING_UTF8).getStr())); - (void)xmlTextWriterEndElement(writer); - if (auto const* pObjs = pPageFrame->GetSortedObjs()) - { - (void)xmlTextWriterStartElement(writer, BAD_CAST("sorted_objs")); - for (SwAnchoredObject const*const pObj : *pObjs) - { // just print pointer, full details will be printed on its anchor frame - // this nonsense is needed because of multiple inheritance - if (SwFlyFrame const*const pFly = dynamic_cast<SwFlyFrame const*>(pObj)) - { - (void)xmlTextWriterStartElement(writer, BAD_CAST("fly")); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", pFly); - } - else - { - (void)xmlTextWriterStartElement(writer, BAD_CAST(pObj->getElementName())); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", pObj); - } - (void)xmlTextWriterEndElement(writer); - } - (void)xmlTextWriterEndElement(writer); - } - } - - if (IsTextFrame()) - { - const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); - sw::MergedPara const*const pMerged(pTextFrame->GetMergedPara()); - if (pMerged) - { - (void)xmlTextWriterStartElement( writer, BAD_CAST( "merged" ) ); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "paraPropsNodeIndex" ), "%" SAL_PRIuUINTPTR, pMerged->pParaPropsNode->GetIndex() ); - for (auto const& e : pMerged->extents) - { - (void)xmlTextWriterStartElement( writer, BAD_CAST( "extent" ) ); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIuUINTPTR, 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 ); - } - } - - if (IsCellFrame()) - { - SwCellFrame const* pCellFrame(static_cast<SwCellFrame const*>(this)); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "rowspan" ), "%ld", pCellFrame->GetLayoutRowSpan() ); - } - - (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 - if ( IsTextFrame( ) ) - { - const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); - OUString aText = pTextFrame->GetText( ); - for ( int i = 0; i < 32; i++ ) - { - aText = aText.replace( i, '*' ); - } - OString aText8 =OUStringToOString( aText, - RTL_TEXTENCODING_UTF8 ); - (void)xmlTextWriterWriteString( writer, - reinterpret_cast<const xmlChar *>(aText8.getStr( )) ); - XmlPortionDumper pdumper( writer, aText ); - pTextFrame->VisitPortions( pdumper ); - - } - else - { - dumpChildrenAsXml( writer ); - } - (void)xmlTextWriterEndElement( writer ); - } - - if ( bCreateWriter ) - lcl_freeWriter( writer ); -} - void SwFrame::dumpInfosAsXml( xmlTextWriterPtr writer ) const { // output the Frame @@ -511,10 +184,6 @@ void SwFrame::dumpInfosAsXml( xmlTextWriterPtr writer ) const (void)xmlTextWriterEndElement( writer ); } -// Hack: somehow conversion from "..." to va_list does -// bomb on two string literals in the format. -const char* const TMP_FORMAT = "%" SAL_PRIuUINTPTR; - void SwFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const { (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this ); @@ -528,120 +197,6 @@ void SwFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "upper" ), "%" SAL_PRIuUINT32, GetUpper()->GetFrameId() ); if ( GetLower( ) ) (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "lower" ), "%" SAL_PRIuUINT32, GetLower()->GetFrameId() ); - if (IsFootnoteFrame()) - { - SwFootnoteFrame const*const pFF(static_cast<SwFootnoteFrame const*>(this)); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("ref"), "%" SAL_PRIuUINT32, pFF->GetRef()->GetFrameId() ); - if (pFF->GetMaster()) - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("master"), "%" SAL_PRIuUINT32, pFF->GetMaster()->GetFrameId() ); - if (pFF->GetFollow()) - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST("follow"), "%" SAL_PRIuUINT32, pFF->GetFollow()->GetFrameId() ); - } - if (IsSctFrame()) - { - SwSectionFrame const*const pFrame(static_cast<SwSectionFrame const*>(this)); - SwSectionNode const*const pNode(pFrame->GetSection() ? pFrame->GetSection()->GetFormat()->GetSectionNode() : nullptr); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("sectionNodeIndex"), TMP_FORMAT, pNode ? pNode->GetIndex() : -1); - } - if ( IsTextFrame( ) ) - { - const SwTextFrame *pTextFrame = static_cast<const SwTextFrame *>(this); - const SwTextNode *pTextNode = pTextFrame->GetTextNodeFirst(); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), TMP_FORMAT, pTextNode->GetIndex() ); - - OString aMode = "Horizontal"; - if (IsVertLRBT()) - { - aMode = "VertBTLR"; - } - else if (IsVertLR()) - { - aMode = "VertLR"; - } - else if (IsVertical()) - { - aMode = "Vertical"; - } - (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("WritingMode"), BAD_CAST(aMode.getStr())); - } - if (IsHeaderFrame() || IsFooterFrame()) - { - const SwHeadFootFrame *pHeadFootFrame = static_cast<const SwHeadFootFrame*>(this); - OUString aFormatName = pHeadFootFrame->GetFormat()->GetName(); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "fmtName" ), "%s", BAD_CAST(OUStringToOString(aFormatName, RTL_TEXTENCODING_UTF8).getStr())); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "fmtPtr" ), "%p", pHeadFootFrame->GetFormat()); - } -} - -void SwFrame::dumpChildrenAsXml( xmlTextWriterPtr writer ) const -{ - const SwFrame *pFrame = GetLower( ); - for ( ; pFrame != nullptr; pFrame = pFrame->GetNext( ) ) - { - pFrame->dumpAsXml( writer ); - } -} - -void SwAnchoredObject::dumpAsXml( xmlTextWriterPtr writer ) const -{ - bool bCreateWriter = ( nullptr == writer ); - if ( bCreateWriter ) - writer = lcl_createDefaultWriter(); - - (void)xmlTextWriterStartElement( writer, BAD_CAST( getElementName() ) ); - (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "ptr" ), "%p", this ); - - (void)xmlTextWriterStartElement( writer, BAD_CAST( "bounds" ) ); - // don't call GetObjBoundRect(), it modifies the layout - SwRect(GetDrawObj()->GetLastBoundRect()).dumpAsXmlAttributes(writer); - (void)xmlTextWriterEndElement( writer ); - - if (const SdrObject* pObject = GetDrawObj()) - pObject->dumpAsXml(writer); - - (void)xmlTextWriterEndElement( writer ); - - if ( bCreateWriter ) - lcl_freeWriter( writer ); -} - -void SwFont::dumpAsXml(xmlTextWriterPtr writer) const -{ - (void)xmlTextWriterStartElement(writer, BAD_CAST("SwFont")); - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", this); - // do not use Color::AsRGBHexString() as that omits the transparency - (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("color"), "%08" SAL_PRIxUINT32, sal_uInt32(GetColor())); - (void)xmlTextWriterEndElement(writer); -} - -void SwTextFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const -{ - SwFrame::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 SwSectionFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const -{ - SwFrame::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<SwSectionFrame*>( m_pPrecede )->GetFrameId() ); -} - -void SwTabFrame::dumpAsXmlAttributes( xmlTextWriterPtr writer ) const -{ - SwFrame::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<SwTabFrame*>( m_pPrecede )->GetFrameId() ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |